# LuxonisLoader

## Overview

`LuxonisLoader` provides indexed and iterable access to one or more splits of a `LuxonisDataset`. It resolves media, assembles
labels by task, optionally resizes and augments images and labels together, and returns a `LoaderOutput` for each sample.

The dataset must have splits before you create a loader. See
[LuxonisDataset](https://docs.luxonis.com/software-v3/ai-inference/model-source/training/luxonis-ml/luxonis-dataset.md) for split
creation.

```python
from luxonis_ml.data import LuxonisDataset, LuxonisLoader

dataset = LuxonisDataset("parking_lot")
loader = LuxonisLoader(dataset, view="train")

sample = loader[0]
images = sample.images
labels = sample.labels
metadata = sample.metadata
```

`view` accepts a split name or a list of split names:

```python
loader = LuxonisLoader(dataset, view=["train", "val"])
```

## Loader Output

Every item is a `LoaderOutput` with three attributes:

| Attribute | Contents |
| --- | --- |
| `images` | Dictionary mapping source names to NumPy arrays. |
| `labels` | Dictionary mapping `"task_name/task_type"` keys to NumPy arrays. |
| `metadata` | Stored sample metadata plus optional loader-generated metadata. |

A single-source dataset uses `"image"` as its source name. Access its first image directly with `sample.image`:

```python
for sample in loader:
    image = sample.image
    boxes = sample.labels["detection/boundingbox"]
```

Two-value unpacking is also available when metadata is not needed:

```python
image_or_images, labels = loader[0]
```

For a multi-source record, `image_or_images` is the complete source dictionary. For a single-source record, it is the first image
array. Metadata remains available through `loader[0].metadata`.

### Label Keys and Shapes

Label keys combine the task group and annotation type. A record with `task_name="detection"` produces keys such as
`"detection/boundingbox"`. If no task name was provided, the key begins with `/`, for example `"/classification"`.

| Task type | Output shape | Contents |
| --- | --- | --- |
| `classification` | `(C,)` | Multi-hot class vector. |
| `boundingbox` | `(N, 5)` | Rows in `[class, x, y, w, h]` form. |
| `segmentation` | `(C, H, W)` | Channel-first one-hot semantic mask. |
| `instance_segmentation` | `(N, H, W)` | One binary mask per instance. |
| `keypoints` | `(N, 3 * K)` | Flattened `(x, y, visibility)` triplets. |
| `metadata/<key>` | Value-dependent | Annotation metadata for one field. |

Set `exclude_empty_annotations=True` to omit label keys that have no annotation for a particular sample. Otherwise, the loader
adds correctly shaped empty arrays for known dataset tasks.

## Sample Metadata

Values stored in a record's `sample_metadata` are returned through `sample.metadata`. With `autopopulate_metadata=True`, the
default, the loader also adds a `filenames` mapping:

```python
sample.metadata
# {
#     "filenames": {"image": "frame_001.jpg"},
#     "camera": "left",
#     "frame_id": 1,
# }
```

Return only stored sample metadata with:

```python
loader = LuxonisLoader(dataset, autopopulate_metadata=False)
```

When augmentations are enabled, metadata also includes an `augmentations` mapping with the transformations that affected the
output and their selected runtime parameters. Batch transforms preserve information about all contributing samples under
`batch_augmentation_metadata`.

> **Note**
> Sample metadata and annotation metadata serve different purposes. `sample_metadata` is returned through `sample.metadata`; values stored in `annotation["metadata"]` are returned in `sample.labels` under keys such as `"detection/metadata/weather"`.

## Resize and Color Space

Pass both `height` and `width` to resize every loaded source. `keep_aspect_ratio=True` uses letterbox resizing; set it to `False`
for a direct resize.

```python
loader = LuxonisLoader(
    dataset,
    view="train",
    height=640,
    width=640,
    keep_aspect_ratio=True,
    color_space="RGB",
)
```

`color_space` accepts `"RGB"`, `"BGR"`, or `"GRAY"` and defaults to `"RGB"`. A single value applies to every source, or a
dictionary can configure sources individually:

```python
loader = LuxonisLoader(
    dataset,
    color_space={
        "rgb": "RGB",
        "depth": "GRAY",
    },
)
```

## Augmentation

The default augmentation engine uses [Albumentations](https://albumentations.ai/docs/). Pass a list of configuration dictionaries
or a path to a YAML or JSON file containing that list.

Each item supports:

| Field | Description |
| --- | --- |
| `name` | Albumentations transform name or registered Luxonis transform name. |
| `params` | Transform constructor parameters. |
| `use_for_resizing` | Uses this transform as the pipeline's resize stage. At most one item can enable it. |
| `apply_on_stages` | Any of `train`, `val`, and `test`; defaults to `train`. |

```python
augmentation_config = [
    {
        "name": "HorizontalFlip",
        "params": {"p": 0.5},
    },
    {
        "name": "RandomBrightnessContrast",
        "params": {
            "brightness_limit": 0.15,
            "contrast_limit": 0.15,
            "p": 0.3,
        },
    },
    {
        "name": "Mosaic4",
        "params": {
            "height": 640,
            "width": 640,
            "p": 0.2,
        },
        "apply_on_stages": ["train"],
    },
]

loader = LuxonisLoader(
    dataset,
    view="train",
    augmentation_config=augmentation_config,
    height=640,
    width=640,
    seed=42,
)
```

> **Note**
> An augmentation configuration requires both `height` and `width`. The engine uses them to construct the final resize stage.

When no transform has `use_for_resizing`, the engine inserts direct or letterbox resizing according to `keep_aspect_ratio`. A
probabilistic resize transform falls back to the default resize whenever that transform is not applied.

Transforms are grouped by behavior and applied in this order:

 1. Batch transforms
 2. Spatial transforms
 3. Other custom transforms
 4. Pixel-only transforms

The configured list order is therefore not guaranteed across these groups.

### Built-In Transforms

LuxonisML adds several transforms to the Albumentations engine:

 * `LetterboxResize` for aspect-ratio-preserving resize and padding
 * `CutMix` for patching two samples together
 * `MixUp` for blending two samples
 * `Mosaic4` for composing four samples in a 2-by-2 mosaic
 * `HorizontalSymmetricKeypointsFlip`, `VerticalSymmetricKeypointsFlip`, and `TransposeSymmetricKeypoints` for transforms that
   also swap semantic keypoint pairs

Batch transforms increase the number of source samples needed for one output. For example, `MixUp` uses two samples and `Mosaic4`
uses four. Combining them requires eight source samples for each augmented output.

> **Note**
> Standard Albumentations flips transform keypoint coordinates but do not exchange semantic left/right keypoint labels. Use the symmetric keypoint transforms when the skeleton defines paired keypoints.

See the [augmentation API
reference](https://docs.luxonis.com/software-v3/ai-inference/model-source/training/luxonis-ml/luxonis-ml-api-reference/data/augmentations.md)
for custom transform registration, target handling, transform parameters, and augmentation provenance.

## Runtime Filtering and Remote Datasets

Common loader options include:

 * `filter_task_names=[...]` to include selected task groups
 * `keep_categorical_as_strings=True` to preserve categorical metadata instead of using its integer encoding
 * `min_bbox_visibility` to require a visible fraction of a box after augmentation
 * `bbox_area_threshold` to discard very small normalized boxes and their associated labels
 * `seed` to make random augmentation selection reproducible
 * `update_mode="all"` or `"missing"` to control remote media synchronization

```python
loader = LuxonisLoader(
    dataset,
    view="train",
    filter_task_names=["detection"],
    exclude_empty_annotations=True,
    keep_categorical_as_strings=True,
    update_mode="missing",
)
```

For a remote dataset, annotations and metadata are refreshed when the loader is initialized. `update_mode` defaults to `"all"`;
set it to `"missing"` to download only media that cannot be resolved locally.

Use the [loader API
reference](https://docs.luxonis.com/software-v3/ai-inference/model-source/training/luxonis-ml/luxonis-ml-api-reference/data/loaders.md)
for the complete constructor and output contracts.
