fix(sinks): slice list and tuple custom_data values per row#2216
Merged
Borda merged 12 commits intoroboflow:developfrom Apr 17, 2026
Merged
fix(sinks): slice list and tuple custom_data values per row#2216Borda merged 12 commits intoroboflow:developfrom
Borda merged 12 commits intoroboflow:developfrom
Conversation
Extends the per-row slicing already done for numpy arrays in CSVSink and JSONSink to also cover plain Python list and tuple values whose length matches the number of detections. Shorter or differently-sized sequences are still broadcast unchanged to every row to preserve the existing complex-data behaviour.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## develop #2216 +/- ##
=======================================
Coverage 77% 77%
=======================================
Files 66 66
Lines 8189 8195 +6
=======================================
+ Hits 6303 6309 +6
Misses 1886 1886 🚀 New features to boost your workflow:
|
Contributor
There was a problem hiding this comment.
Pull request overview
Fixes per-detection row serialization in CSVSink and JSONSink so that custom_data values provided as plain Python list/tuple are sliced per row (when their length matches the number of detections), matching the existing behavior for numpy.ndarray.
Changes:
- Extend
_slice_valuein both sinks to slicelist/tuplevalues whenlen(value) == number_of_detections. - Thread
n = len(detections.xyxy)through parsing loops to support length-based slicing. - Add/extend unit tests to cover list/tuple per-row slicing across multi-append scenarios.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
src/supervision/detection/tools/csv_sink.py |
Slice list/tuple custom_data per detection row when length matches detection count. |
src/supervision/detection/tools/json_sink.py |
Mirror the same list/tuple slicing behavior in JSON serialization. |
tests/detection/test_csv.py |
Add coverage validating list/tuple custom_data is sliced per row (and scalar still broadcasts). |
tests/detection/test_json.py |
Add coverage validating list/tuple custom_data is sliced per row in JSON output. |
…ings - Describe broadcast vs. per-row slicing rules for custom_data values in CSVSink.append() and JSONSink.append() [resolve roboflow#1] /review finding by foundry:sw-engineer + foundry:doc-scribe (report: .temp/output-review-fix-sinks-slice-list-values-2026-04-17.md) --- Co-authored-by: Claude Code <noreply@anthropic.com> Co-authored-by: OpenAI Codex <codex@openai.com>
- Document n param, all four dispatch rules, Args and Returns sections [resolve roboflow#2] /review finding by foundry:doc-scribe (report: .temp/output-review-fix-sinks-slice-list-values-2026-04-17.md) --- Co-authored-by: Claude Code <noreply@anthropic.com> Co-authored-by: OpenAI Codex <codex@openai.com>
…NSink - Document list/tuple slicing vs. broadcast semantics, Args, and Returns [resolve roboflow#3] /review finding by foundry:doc-scribe (report: .temp/output-review-fix-sinks-slice-list-values-2026-04-17.md) --- Co-authored-by: Claude Code <noreply@anthropic.com> Co-authored-by: OpenAI Codex <codex@openai.com>
- Verify CSVSink/JSONSink slice list values from detections.data per row [resolve roboflow#4] /review finding by foundry:qa-specialist (report: .temp/output-review-fix-sinks-slice-list-values-2026-04-17.md) --- Co-authored-by: Claude Code <noreply@anthropic.com> Co-authored-by: OpenAI Codex <codex@openai.com>
- Cover n=1 degenerate, mismatch fallthrough, mixed types, nested list, string type guard, single-element tuple [resolve roboflow#5] /review finding by foundry:qa-specialist (report: .temp/output-review-fix-sinks-slice-list-values-2026-04-17.md) --- Co-authored-by: Claude Code <noreply@anthropic.com> Co-authored-by: OpenAI Codex <codex@openai.com>
…ocstrings [resolve roboflow#7] /review finding by foundry:doc-scribe (report: .temp/output-review-fix-sinks-slice-list-values-2026-04-17.md) --- Co-authored-by: Claude Code <noreply@anthropic.com> Co-authored-by: OpenAI Codex <codex@openai.com>
--- Co-authored-by: Claude Code <noreply@anthropic.com>
….py and test_json.py
Borda
approved these changes
Apr 17, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Before submitting
Description
Extend the per-row slicing already done for
numpy.ndarrayvalues inCSVSinkandJSONSinkso it also covers plain Pythonlist/tuplevalues whose length matches the number of detections. Shorter or differently-sized sequences keep the current broadcast behavior, so existing complex-data tests continue to pass.Type of Change
Motivation and Context
#2199 fixed the "entire array on every row" bug for
numpy.ndarrayvalues incustom_data, but the patch only added anumpy.ndarraybranch to_slice_value; any non-ndarrayvalue still returned unchanged. When a user passes a plain Pythonlist— e.g., per-detection string ids — every row receives the full list instead of its per-row element:Before this PR the
idscolumn contains"['a', 'b', 'c']"on every row; with the fix each row gets"a","b","c"respectively. The same regression applies toJSONSink.The
complex_datatest cases intests/detection/test_csv.py({"tags": ["urgent", "review"]}broadcast across a single detection) still pass because the length check guarantees the existing broadcast behavior whenever the sequence length does not match the detection count.Changes Made
src/supervision/detection/tools/csv_sink.py—_slice_valuenow also indexeslist/tuplevalues whose length equals the number of detections; other non-array values are still broadcast.src/supervision/detection/tools/json_sink.py— same change, keeping parity between the two sinks.tests/detection/test_csv.py— newtest_csv_sinkparameter covering a 3-row, 2-row, and 1-row sink overlistandtuplecustom data.tests/detection/test_json.py— equivalent new parameter fortest_json_sink.Testing
Google Colab (optional)
Screenshots/Videos (optional)
Additional Notes
Backward compatibility: scalar values (
{"frame_number": 42}) and sequences whose length does not match the number of detections ({"tags": ["urgent", "review"]}against a single detection) still write unchanged to every row, matching the behavior exercised by the existingcomplex_datatests in both sinks.