Skip to content

Allow a dataset record to carry multiple detections - #469

Closed
kozlov721 wants to merge 13 commits into
mainfrom
feat/record-multiple-annotations
Closed

Allow a dataset record to carry multiple detections#469
kozlov721 wants to merge 13 commits into
mainfrom
feat/record-multiple-annotations

Conversation

@kozlov721

@kozlov721 kozlov721 commented Aug 1, 2026

Copy link
Copy Markdown
Collaborator

Purpose

A DatasetRecord could hold only one Detection, so a sample with several objects had to be yielded as several records that repeated the same file and metadata. Grouping them in one record is the natural shape, and it is what reconstructing records from loader output needs.

Split out of the feat/vizlab branch, which had grown to 81 commits; this is one of eight self-contained pieces that do not belong to vizlab itself.

Specification

DatasetRecord.annotation becomes list[Detection] | None. A single Detection is still accepted and upgraded to a one-element list by a before-validator, so existing generators keep working unchanged.

  • Detections are flattened into the same parquet rows, so the on-disk format does not depend on how a record was grouped; each secondary media source still gets exactly one null-annotation row rather than one per detection.
  • LuxonisDataset.add() infers the task from every detection and raises when their classes belong to different tasks, instead of inferring from the first one and silently filing the rest under it — which would pollute that task's class list and mis-group labels that training then consumes as ground truth.
  • The parser's task splitter groups a record's detections by resolved task name.

Dependencies & Potential Impact

Backwards compatible for producers (single detections still work) and for the on-disk format (identical rows). One deliberate new failure mode: a record whose detections resolve to different tasks now raises instead of guessing.

Deployment Plan

None / not applicable

Testing & Validation

tests/test_ldf/test_record_list.py (single vs. list equivalence, flattening, one null row per secondary source, sub-detections), new task-inference coverage in tests/test_data/test_task_ingestion.py, and grouping coverage in tests/test_data/test_parsers.py.

AI Usage

Assisted-by: Claude:claude-fable-5


Stacked on #468 (feat/ldf-package). Based on that branch so the diff shows only this feature; GitHub retargets it to main once #468 merges.

Summary by CodeRabbit

  • New Features

    • Records can now contain multiple annotations and detections.
    • Dataset ingestion processes every annotation and groups results by task.
    • Annotation data is preserved during export, including labels, metadata, classifications, sub-detections, and nested array references.
    • Unlabeled records are correctly handled across configured tasks.
  • Bug Fixes

    • Improved mask and array path resolution for multi-annotation records.
    • Clear errors are raised when annotations span multiple tasks without an explicit task.
  • Tests

    • Added coverage for parsing, task inference, normalization, nested arrays, and export behavior.

kozlov721 and others added 2 commits August 1, 2026 20:54
Move the LDF annotation models and the ParquetRecord row schema into a
new lightweight `luxonis_ml.ldf` package with its own extra, so consumers
can depend on the data format without pulling in the full data stack.
`luxonis_ml.data.datasets.annotation` and `luxonis_ml.data.utils.parquet`
re-export the moved names, so existing imports keep working.

Pure move: no behavior change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
`DatasetRecord.annotation` becomes `list[Detection] | None`; a single
`Detection` is still accepted and upgraded to a one-element list, so
existing generators keep working. Detections are flattened into the same
parquet rows, and each secondary media source still gets exactly one
null-annotation row rather than one per detection.

`add()` infers the record's task from every detection and refuses to
guess when their classes belong to different tasks, instead of silently
filing them all under the first detection's task. The parser's task
splitter groups a record's detections by resolved task name.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@kozlov721, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 45 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: d0c9626e-4167-4c7c-b713-92ec0037a887

📥 Commits

Reviewing files that changed from the base of the PR and between 54c30aa and aa6429f.

📒 Files selected for processing (1)
  • tests/test_data/test_dataset.py
📝 Walkthrough

Walkthrough

The change adds multi-detection support across dataset records, parsers, ingestion, array processing, and parquet serialization. Task grouping and inference now process every annotation, with validation for ambiguous task mappings.

Changes

Multi-annotation support

Layer / File(s) Summary
Annotation contract and serialization
luxonis_ml/ldf/annotation.py, tests/test_ldf/test_record_list.py
DatasetRecord.annotation accepts detection lists and normalizes scalar detections. Parquet conversion serializes all detections, nested detections, metadata, classification, and null rows.
Parser annotation grouping
luxonis_ml/data/parsers/base_parser.py, luxonis_ml/data/parsers/native_parser.py, tests/test_data/test_parsers.py
BaseParser groups detections by task and yields copied records with task-specific annotation lists. Tests cover mapped and unlabeled records.
Dataset task ingestion and array processing
luxonis_ml/data/datasets/luxonis_dataset.py, tests/test_data/test_task_ingestion.py, tests/test_data/test_native_paths.py, tests/test_data/test_dataset.py
Dataset ingestion processes every annotation, infers a task only when mappings agree, updates annotation state for each detection, and resolves arrays in root and nested detections.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant DatasetRecord
  participant BaseParser
  participant LuxonisDataset
  participant ParquetConversion

  DatasetRecord->>BaseParser: Provide scalar or list annotations
  BaseParser->>BaseParser: Group detections by resolved task
  BaseParser->>LuxonisDataset: Yield task-specific record copies
  LuxonisDataset->>LuxonisDataset: Infer task and process every annotation
  LuxonisDataset->>ParquetConversion: Serialize all detections and nested data
Loading

Possibly related PRs

Suggested labels: data, tests

Suggested reviewers: klemen1999, conorsim, tersekmatija

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 15.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: allowing a dataset record to contain multiple detections.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/record-multiple-annotations

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added the enhancement New feature or request label Aug 1, 2026
kozlov721 and others added 2 commits August 1, 2026 22:34
Formatting only, as taplo writes it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Formatting only, as taplo writes it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@codecov

codecov Bot commented Aug 1, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.72611% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 95.08%. Comparing base (4763b22) to head (aa6429f).
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
luxonis_ml/data/parsers/base_parser.py 88.88% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #469      +/-   ##
==========================================
+ Coverage   95.03%   95.08%   +0.05%     
==========================================
  Files         177      178       +1     
  Lines       14557    14673     +116     
==========================================
+ Hits        13834    13952     +118     
+ Misses        723      721       -2     
Flag Coverage Δ
pytest-ubuntu-latest 95.08% <98.72%> (+0.05%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

@kozlov721
kozlov721 marked this pull request as ready for review August 2, 2026 20:42
@kozlov721
kozlov721 requested a review from a team as a code owner August 2, 2026 20:42
@kozlov721
kozlov721 requested review from klemen1999 and removed request for a team August 2, 2026 20:42
`DatasetRecord.annotation` becomes `list[Detection] | None`; a single
`Detection` is still accepted and upgraded to a one-element list, so
existing generators keep working. Detections are flattened into the same
parquet rows, and each secondary media source still gets exactly one
null-annotation row rather than one per detection.

`add()` infers the record's task from every detection and refuses to
guess when their classes belong to different tasks, instead of silently
filing them all under the first detection's task. The parser's task
splitter groups a record's detections by resolved task name.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@kozlov721
kozlov721 force-pushed the feat/record-multiple-annotations branch from 1c54c6f to 295bf66 Compare August 3, 2026 08:55

@klemen1999 klemen1999 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possible issue found in the NativeParser:
If one follows the new DatasetRecord format that supports multiple annotations then current NativeParser can crash. The problem is here where we still assume records with single annotation. This is still true for datasets exported by us but if one constructs LDF by themselves they could now have multiple records. We should handle this in the parser

@klemen1999

Copy link
Copy Markdown
Collaborator

And maybe an important breaking change node to this PR is that something like this:

record = DatasetRecord(annotation=Detection(class_name="car"))
record.annotation.class_name  # "car"

doesn't work anymore since now record.annotation is a list[Detection] and record.annotation.class_name would yield AttributeError.
I don't think it is a problematic breaking change but it is something that should be noted.

Base automatically changed from feat/ldf-package to main August 4, 2026 02:30
coderabbitai[bot]

This comment was marked as resolved.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@luxonis_ml/ldf/annotation.py`:
- Around line 1464-1471: Update LuxonisDataset._process_arrays to recursively
traverse each detection’s sub_detections before serialization, processing every
nested detection.array with the same local-path and remote-upload/rewrite
behavior as root detections. Ensure nested arrays are no longer left relative or
unuploaded, and add regression coverage for both local and remote datasets.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 8a6bcf9b-53b1-4116-b8b3-0a40e33d43c6

📥 Commits

Reviewing files that changed from the base of the PR and between b29fdbd and 50c7f8d.

📒 Files selected for processing (8)
  • luxonis_ml/data/datasets/luxonis_dataset.py
  • luxonis_ml/data/parsers/base_parser.py
  • luxonis_ml/data/parsers/native_parser.py
  • luxonis_ml/ldf/annotation.py
  • tests/test_data/test_native_paths.py
  • tests/test_data/test_parsers.py
  • tests/test_data/test_task_ingestion.py
  • tests/test_ldf/test_record_list.py
🚧 Files skipped from review as they are similar to previous changes (7)
  • luxonis_ml/data/parsers/native_parser.py
  • tests/test_ldf/test_record_list.py
  • tests/test_data/test_native_paths.py
  • tests/test_data/test_task_ingestion.py
  • luxonis_ml/data/parsers/base_parser.py
  • luxonis_ml/data/datasets/luxonis_dataset.py
  • tests/test_data/test_parsers.py

Comment thread luxonis_ml/ldf/annotation.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/test_data/test_dataset.py`:
- Around line 566-569: Strengthen the remote-path assertions in the dataset test
by comparing root and nested against the expected UUID-derived filenames from
storage metadata or the upload API, rather than only checking each path’s
basename. Use the _process_arrays contract and available expected UUID values to
verify both uploaded array paths were renamed correctly.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 3994bd23-c409-4901-b673-f7a9e1036162

📥 Commits

Reviewing files that changed from the base of the PR and between 50c7f8d and 54c30aa.

📒 Files selected for processing (2)
  • luxonis_ml/data/datasets/luxonis_dataset.py
  • tests/test_data/test_dataset.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • luxonis_ml/data/datasets/luxonis_dataset.py

Comment thread tests/test_data/test_dataset.py Outdated
@kozlov721
kozlov721 requested a review from klemen1999 August 4, 2026 20:52
@kozlov721
kozlov721 marked this pull request as draft August 6, 2026 11:25
@kozlov721 kozlov721 closed this Aug 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants