# LuxonisDataset

## Overview

`LuxonisDataset` is the high-level API for datasets stored in the Luxonis Data Format (LDF). It manages records, annotations, task
and class metadata, splits, local or remote storage, cloning, merging, validation, and export.

Install the data dependencies and import the class from the public `luxonis_ml.data` package:

```bash
python -m pip install "luxonis-ml[data]"
```

```python
from luxonis_ml.data import LuxonisDataset

dataset = LuxonisDataset("parking_lot")
```

Constructing `LuxonisDataset` opens the matching dataset when it exists or initializes it when it does not. Local datasets are
stored under `~/luxonis_ml` by default. Set `LUXONISML_BASE_PATH` to use another base directory.

To recreate a local dataset with the same name, explicitly delete its local state during initialization:

```python
dataset = LuxonisDataset("parking_lot", delete_local=True)
```

> **Note**
> `delete_local` and `delete_remote` remove data. For remote-backed datasets, set each flag deliberately according to which copy should be replaced.

## Add Records

`dataset.add(...)` accepts any iterable of `DatasetRecord` objects or compatible dictionaries. A generator is convenient for large
datasets because it avoids holding every record in memory.

Each record contains:

| Field | Description |
| --- | --- |
| `file` | Path to one media file. |
| `files` | Source-name-to-path mapping for synchronized multi-source samples. Use either `file` or `files`. |
| `task_name` | Optional task group. The default is an empty string. |
| `annotation` | Optional classification, spatial, array, or annotation-metadata payload. |
| `sample_metadata` | Optional JSON-serializable metadata for the complete sample. |

The following generator adds normalized bounding boxes:

```python
from pathlib import Path

from luxonis_ml.data import LuxonisDataset

image_dir = Path("data/parking_lot/images")

def records():
    yield {
        "file": image_dir / "frame_001.jpg",
        "task_name": "detection",
        "sample_metadata": {
            "camera": "left",
            "frame_id": 1,
        },
        "annotation": {
            "class": "car",
            "instance_id": 17,
            "boundingbox": {
                "x": 0.10,
                "y": 0.20,
                "w": 0.30,
                "h": 0.40,
            },
        },
    }

dataset = LuxonisDataset("parking_lot")
dataset.add(records())
```

Bounding-box coordinates use normalized `xywh`: `x` and `y` identify the top-left corner, and all four values are relative to the
image dimensions.

### Multi-Source Records

Use `files` for synchronized inputs such as RGB and depth images:

```python
def records():
    yield {
        "files": {
            "rgb": "data/rgb/frame_001.png",
            "depth": "data/depth/frame_001.png",
        },
        "task_name": "detection",
        "annotation": {
            "class": "person",
            "boundingbox": {"x": 0.1, "y": 0.1, "w": 0.3, "h": 0.4},
        },
    }
```

The source names become keys in `LuxonisLoader`'s `sample.images` dictionary.

### Annotation Types

LDF supports:

 * classification through `class`
 * normalized `xywh` bounding boxes through `boundingbox`
 * normalized `(x, y, visibility)` keypoint triplets through `keypoints`
 * semantic segmentation through `segmentation`
 * instance segmentation through `instance_segmentation`
 * arbitrary `.npy` targets through `array`
 * flexible annotation labels through `metadata`

Segmentation inputs can be polygons, binary masks, or COCO run-length encoded masks. When several records describe the same
physical object, assign the same `instance_id` to associate its bounding box, keypoints, and instance mask reliably.

> **Note**
> An annotation that includes `class` also contributes a classification target. `sample_metadata` is different from `annotation["metadata"]`: sample metadata is returned as `sample.metadata`, while annotation metadata becomes a label task.

See the [LDF annotation API
reference](https://docs.luxonis.com/software-v3/ai-inference/model-source/training/luxonis-ml/luxonis-ml-api-reference/ldf/annotation.md)
for every accepted payload shape and mask encoding.

## Define Splits

`LuxonisLoader` requires split definitions. Calling `make_splits()` with no arguments creates `train`, `val`, and `test` splits
using an 80:10:10 ratio:

```python
dataset.make_splits()
```

Pass any named ratios that sum to `1.0`:

```python
dataset.make_splits({"train": 0.7, "val": 0.2, "test": 0.1})
```

You can also assign exact files:

```python
dataset.make_splits(
    {
        "train": [image_dir / "frame_001.jpg", image_dir / "frame_002.jpg"],
        "val": [image_dir / "frame_003.jpg"],
        "test": [image_dir / "frame_004.jpg"],
    }
)
```

Split names are not restricted to `train`, `val`, and `test`. By default, later calls assign only records that are not already in
a split. Replace the complete split definition with:

```python
dataset.make_splits(
    {"train": 0.8, "val": 0.1, "test": 0.1},
    replace_old_splits=True,
)
```

## Clone and Merge Datasets

Clone a complete dataset or selected splits under a new name:

```python
clone = dataset.clone(
    new_dataset_name="parking_lot_clone",
    push_to_cloud=False,
)

train_clone = dataset.clone(
    new_dataset_name="parking_lot_train",
    push_to_cloud=False,
    splits_to_clone=["train"],
)
```

> **Note**
> Cloning replaces any dataset that already uses `new_dataset_name`.

Merge another dataset into the current one:

```python
dataset.merge_with(other_dataset, inplace=True)
```

Or create a separate merged dataset:

```python
merged = dataset.merge_with(
    other_dataset,
    inplace=False,
    new_dataset_name="parking_lot_merged",
)
```

Both datasets must use the same bucket storage type for an out-of-place merge. Use `splits_to_merge` to limit the incoming data to
selected splits.

## Export Datasets

Export LDF into a supported third-party format with `DatasetType`:

```python
from luxonis_ml.enums import DatasetType

output = dataset.export(
    "exports/parking_lot_coco",
    dataset_type=DatasetType.COCO,
)
```

Supported export families include:

 * native LDF
 * COCO and Pascal VOC
 * Darknet, YOLOv4, YOLOv6, and YOLOv8 bounding boxes
 * YOLOv8 instance segmentation and keypoints
 * Ultralytics NDJSON detection, instance segmentation, and keypoints
 * CreateML and TensorFlow Object Detection CSV
 * classification directory and FiftyOne classification
 * segmentation mask directory

With the exception of Unity SOLO, every format supported by
[`LuxonisParser`](https://docs.luxonis.com/software-v3/ai-inference/model-source/training/luxonis-ml/luxonis-parser.md) is also
available as an export target. Formats with multiple task-specific variants, such as YOLOv8 and Ultralytics NDJSON, use separate
`DatasetType` values for bounding boxes, instance segmentation, and keypoints.

> **Note**
> An exporter writes only the annotation types its target format can represent. For example, a Pascal VOC export keeps bounding boxes, while a classification-directory export keeps classification labels. Unsupported annotations are reported and omitted from the export.

The output path must not already exist. Set `zip_output=True` to create an archive, or `max_partition_size_gb` to divide a large
export into multiple parts.

## Remote Storage

Datasets can use Google Cloud Storage or S3-compatible object storage. Install the matching extra and configure `LUXONISML_BUCKET`
together with the provider credentials.

```python
from luxonis_ml.data import BucketStorage, LuxonisDataset, UpdateMode

remote_dataset = LuxonisDataset(
    "parking_lot",
    bucket_storage=BucketStorage.GCS,
)

remote_dataset.pull_from_cloud(update_mode=UpdateMode.MISSING)
```

Push a local dataset to a configured bucket:

```python
dataset.push_to_cloud(
    bucket_storage=BucketStorage.GCS,
    update_mode=UpdateMode.MISSING,
)
```

`UpdateMode.MISSING` transfers media files not present at the destination. `UpdateMode.ALL` transfers all media files. Annotations
and metadata are synchronized on every push or pull regardless of the media update mode.

Common environment variables are:

| Variable | Purpose |
| --- | --- |
| `LUXONISML_BUCKET` | Remote dataset bucket name. |
| `LUXONISML_TEAM_ID` | Dataset namespace; defaults to `offline`. |
| `GOOGLE_APPLICATION_CREDENTIALS` | Google Cloud credentials file. |
| `AWS_ACCESS_KEY_ID` | S3 access key. |
| `AWS_SECRET_ACCESS_KEY` | S3 secret key. |
| `AWS_S3_ENDPOINT_URL` | Optional endpoint for S3-compatible services. |

## Command-Line Interface

The `luxonis_ml data` command covers the complete dataset lifecycle:

```bash
luxonis_ml data ls
luxonis_ml data info parking_lot
luxonis_ml data inspect parking_lot
luxonis_ml data health parking_lot
luxonis_ml data sanitize parking_lot
luxonis_ml data export parking_lot --type coco
luxonis_ml data clone parking_lot parking_lot_clone
luxonis_ml data merge source_dataset target_dataset
luxonis_ml data push parking_lot --bucket-storage gcs
luxonis_ml data pull parking_lot --bucket-storage gcs
luxonis_ml data delete parking_lot --local
```

Run `luxonis_ml data <command> --help` for every option, including split selection, remote storage, partitioned export, and
non-interactive deletion.
