> For the complete documentation index, see [llms.txt](https://docs.unitlab.ai/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.unitlab.ai/api-sdk-cli/sdks/data-group-automation.md).

# Data Group automation

Create multimodal Data Groups from uploaded Assets using the Unitlab SDK. Define JSON filename rules, preview matches, set required views and save layouts.

Create one work item from related files, such as two camera views of a road scene. Upload the media to an Assets folder first, then use filename rules to match members into groups.

![Auto-Groups custom layout editor](https://292810646-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FGjVLUz4wthGkGlRKM6rM%2Fuploads%2F8eyEMKLe8RbL8q4uIsNW%2Fauto-groups-custom-layout.png?alt=media\&token=810552ad-bdf3-44b8-b959-3f3afe77847d)

*Grouping configuration defines group keys, tile identities, completeness rules and the saved layout used in the Workbench.*

## Prepare the source folder

Install the [Python SDK](https://docs.unitlab.ai/api-sdk-cli/get-started/install-the-python-sdk-and-cli) and [configure your workspace API key](https://docs.unitlab.ai/api-sdk-cli/get-started/authentication-and-configuration). The examples use SDK 3.1.3. Run Python from the working directory below, with four real images under `road-views`:

```
working-directory/
├── road-views/
│   ├── drive001_front.jpg
│   ├── drive001_left.jpg
│   ├── drive002_front.jpg
│   └── drive002_left.jpg
└── grouping.json  # Only needed for the JSON configuration option
```

Run the Python steps in order in one session or script. Choose either the Python template or the JSON configuration in the next section; both define the `config` used later.

```python
from unitlab import UnitlabClient

client = UnitlabClient()
uploaded = client.assets.upload("./road-views", folder="Road views")
if uploaded.failed:
    raise RuntimeError("Resolve upload failures before grouping.")
for asset in uploaded.assets:
    if asset.wait().failed:
        raise RuntimeError(f"Asset is not ready: {asset.id}")

folder = client.assets.folder(uploaded.folder_id)
```

This folder's filenames identify both a `session_id`, such as `drive001`, and a view, such as `front`. For this example, confirm `uploaded.uploaded == 4` and no failures. You can also skip the upload and use `client.assets.folder("FOLDER_ID")`, replacing `FOLDER_ID` with an existing source folder's ID. Its contents must follow the same naming rules.

## Define the grouping rules

{% tabs %}
{% tab title="Python template" %}

```python
from unitlab import tiles_from_template

config = tiles_from_template(
    "{session_id}_{view}",
    {"view": ["front", "left"]},
)
config["group_name_template"] = "Drive {session_id}"
config["minimum_matched_tiles"] = 2
config["required_tiles"] = ["front", "left"]
config["incomplete_group_handling"] = "complete_only"
```

The helper compiles literal names into matching rules. Fields in `tile_values` identify the tiles; remaining fields identify each group. Without the completeness overrides above, the helper allows incomplete groups with at least one matching tile.
{% endtab %}

{% tab title="JSON configuration" %}
Save this as `grouping.json` in the working directory beside `road-views`:

```json
{
  "grouping_keys": ["session_id"],
  "group_name_template": "Drive {session_id}",
  "tiles": [
    {
      "tile_id": "front",
      "title": "Front camera",
      "match_rule": "^(?P<session_id>drive\\d+)_front\\.jpg$",
      "exclusions": []
    },
    {
      "tile_id": "left",
      "title": "Left camera",
      "match_rule": "^(?P<session_id>drive\\d+)_left\\.jpg$",
      "exclusions": []
    }
  ],
  "minimum_matched_tiles": 2,
  "required_tiles": ["front", "left"],
  "incomplete_group_handling": "complete_only",
  "recursive": true,
  "layout_type": "grid"
}
```

```python
import json
from pathlib import Path

config = json.loads(Path("grouping.json").read_text(encoding="utf-8"))
```

Backslashes are escaped inside JSON strings. This configuration matches existing files; it does not upload paths or download URLs.
{% endtab %}
{% endtabs %}

## Preview, then create

```python
estimate = folder.estimate_grouping(config)
print(estimate)
```

For the four files above, expect `asset_count: 4`, `estimated_valid_groups: 2`, and empty `conflicts` and `skipped` lists. Check `valid` and resolve errors before continuing. A file matching multiple tiles, or multiple files matching the same tile in one group, is a conflict. When the estimate matches your intended groups:

```python
result = folder.auto_group(config)
print(result.created_count, result.grouped_folder_id)
grouped_folder = result.grouped_folder()
```

Creation writes a separate grouped folder and leaves the source folder intact. Repeating creation produces another grouped folder, so keep the returned ID.

Expect `result.created_count == 2` for this example. These are logical groups and tile assignments in Unitlab, not filesystem directories or new copies of your images:

```
Grouped result in Unitlab
├── Drive drive001
│   ├── front → drive001_front.jpg
│   └── left  → drive001_left.jpg
└── Drive drive002
    ├── front → drive002_front.jpg
    └── left  → drive002_left.jpg
```

The template option names tiles `front` and `left`; the JSON option uses the same tile IDs with display titles `Front camera` and `Left camera`.

To inspect an automatically proposed pattern:

```python
print(folder.suggest_grouping())
```

`folder.auto_group()` without a configuration uses a confident server suggestion. If there is no confident pattern, supply your own configuration.

## Configuration reference

| Field                       | Meaning                                                                                                       |
| --------------------------- | ------------------------------------------------------------------------------------------------------------- |
| `grouping_keys`             | Named regex captures that jointly identify one group.                                                         |
| `group_name_template`       | A title using those captures, such as `Drive {session_id}`.                                                   |
| `tiles`                     | Expected views, each with a unique `tile_id`, title and `match_rule`.                                         |
| `exclusions`                | Patterns to exclude from a tile's matches.                                                                    |
| `minimum_matched_tiles`     | Minimum number of views required, from one to the number of defined tiles.                                    |
| `required_tiles`            | Defined tile IDs that must be present.                                                                        |
| `incomplete_group_handling` | `complete_only` requires all defined tiles. `allow_incomplete` still enforces the required tiles and minimum. |
| `recursive`                 | Includes descendant folders when true.                                                                        |
| `layout_type`               | Saved arrangement, such as `grid` or `list`.                                                                  |
| `layout`                    | Optional explicit panel layout for a custom arrangement.                                                      |

Matching checks the folder-relative path, then the filename. Use stable naming keys that reflect the real-world grouping relationship.

For a custom layout, each panel identifies a tile and provides percentage-based `x`, `y`, `width` and `height` values. The downloadable [grouping-config.json](https://1625055228-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F1fWt922WFnUJmXYAzZfh%2Fuploads%2FqwRauYMHv0TjAnKMzhFq%2Fgrouping-config.json?alt=media) example uses two non-overlapping panels side by side. See [Data Groups and layouts](https://docs.unitlab.ai/documentation/data/data-groups-and-layouts) for the visual workflow.

## Attach the grouped result

Replace `PROJECT_ID` with the destination project's ID. Continue using `grouped_folder` from the creation step:

```python
project = client.projects.get("PROJECT_ID")
preview = project.attach_preview(folder_ids=[grouped_folder.id])
print(preview)

attached = project.attach(folder_ids=[grouped_folder.id])
print(attached.created_count, attached.data_group_ids)
```

Use the grouped folder, not the original loose-file folder. If selected data includes video, supply the required `fps`. Alternatively, add the grouped folder to a [dataset](https://docs.unitlab.ai/api-sdk-cli/sdks/datasets), publish a version and attach that version.

After attachment, open the destination project and inspect one group. Confirm that its two camera views and saved layout are correct before annotators begin work. Creating groups in Assets alone does not add them to a project.

## Limits and JSON distinctions

Folders above 5,000 files or estimates above 1,000 groups exceed this SDK operation's supported grouping size. Split the source into smaller folders and preview each one. Invalid rules, zero valid groups and conflicting names need correction before creation.

The current SDK and CLI do not accept a generic manifest listing explicit file members for each group. Grouping JSON contains matching rules for existing Assets. It is separate from [annotation JSON](/api-sdk-cli/sdks/annotation-imports.md), a LiDAR `scene.json`, and a release-package manifest.

Use [CLI Assets and folders](https://docs.unitlab.ai/api-sdk-cli/clis/cli-assets-and-folders#group-related-files) for the same configuration from a terminal. Explore [cross-modal annotation workflows](https://unitlab.ai/en/multimodal-annotation) and [Unitlab's data curation platform](https://unitlab.ai/en/data-curation).
