Release v0.24.1#81
Conversation
Signed-off-by: Sébastien Taylor <[email protected]>
There was a problem hiding this comment.
Pull request overview
Patch release v0.24.1 intended to fix a contract mismatch where Decoder::normalized_boxes() (and C/Python equivalents) reported the schema annotation rather than the post-decode coordinate space actually returned to callers.
Changes:
- Update
Decoder::normalized_boxes()semantics/docs to report the post-decode coordinate space. - Update Rust/C/Python documentation to describe the corrected contract.
- Update/extend tests and bump workspace + Python package versions to
0.24.1.
Reviewed changes
Copilot reviewed 12 out of 13 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| NOTICE | Update crate version attributions to 0.24.1. |
| crates/python/src/decoder.rs | Update Python API docs for normalized_boxes / input_dims. |
| crates/python/pyproject.toml | Bump Python package version to 0.24.1. |
| crates/decoder/tests/per_scale_parity.rs | Update per-scale parity assertion to new normalized_boxes() contract. |
| crates/decoder/tests/decoder_normalized_flag.rs | Update assertions for new contract and add regression test for input_dims == None. |
| crates/decoder/src/decoder/tests.rs | Update internal test expectation for Hailo YOLOv8-seg schema. |
| crates/decoder/src/decoder/mod.rs | Implement new normalized_boxes() logic + updated rustdoc. |
| crates/decoder/src/decoder/builder.rs | Adjust per-scale normalization commentary to match new contract. |
| crates/capi/src/decoder.rs | Update C API docs describing hal_decoder_normalized_boxes. |
| crates/capi/include/edgefirst/hal.h | Update public header docs for hal_decoder_normalized_boxes. |
| CHANGELOG.md | Add v0.24.1 changelog entry describing the contract fix. |
| Cargo.toml | Bump workspace + internal crate versions to 0.24.1. |
| Cargo.lock | Lockfile updates reflecting 0.24.1 versions. |
| @@ -316,21 +325,35 @@ impl Decoder { | |||
| /// # } | |||
| /// ``` | |||
| pub fn normalized_boxes(&self) -> Option<bool> { | |||
| self.normalized | |||
| // Report the effective post-decode coordinate space. The | |||
| // internal `self.normalized` is the *input* policy fed to | |||
| // `yolo::maybe_normalize_boxes_in_place`; that helper divides | |||
| // by `input_dims` exactly when `normalized == Some(false)` and | |||
| // `input_dims` is a non-zero `(w, h)`. The output the caller | |||
| // sees is therefore normalized in that case — surface that. | |||
| match (self.normalized, self.input_dims) { | |||
| (Some(true), _) => Some(true), | |||
| (Some(false), Some((w, h))) if w != 0 && h != 0 => Some(true), | |||
| (Some(false), _) => Some(false), | |||
| (None, _) => None, | |||
| } | |||
| /// Reports the **post-decode** state, not the raw schema annotation: | ||
| /// when the schema declares pixel-space outputs but | ||
| /// :attr:`input_dims` is known, the decoder internally divides bbox | ||
| /// channels by ``(W, H)`` before returning, so the boxes the caller | ||
| /// receives are in ``[0, 1]`` and this getter returns ``True``. | ||
| /// Callers must not re-normalize in that case. |
| * Reports the **post-decode** state, not the raw schema annotation: when | ||
| * the schema declares pixel-space outputs but `hal_decoder_input_dims()` | ||
| * is known, the decoder internally divides bbox channels by `(W, H)` | ||
| * before returning, so the boxes the caller receives are in `[0, 1]` | ||
| * and this function returns `1`. Only when no input dimensions are | ||
| * available does pixel-space leak out and this function return `0`. | ||
| * | ||
| * Callers MUST NOT re-normalize when this function returns `1`; | ||
| * dividing already-normalized coordinates by `(W, H)` again collapses | ||
| * detections to ~0. |
Signed-off-by: Sébastien Taylor <[email protected]>
Per-scale decoders: accessor reports post-decode coordinate space (Some(false) + valid input_dims upgrades to Some(true)). All other decoders: raw schema annotation returned verbatim. C header regenerated via cbindgen from updated Rust doc. CHANGELOG entry tightened to match actual fix scope. Signed-off-by: Sébastien Taylor <[email protected]>
- Flip accessor assertions in pixel_space_input_with_normalized_false_decodes and split_schema_pixel_space_with_normalized_false_decodes from Some(true) to Some(false): non-per-scale paths surface the raw schema flag. - Rewrite file-level doc-block to describe the per-path contract and point readers at Decoder::normalized_boxes rustdoc; note which sub-claims each test section pins. - Update normalized_false_without_input_dims_reports_false rationale from "input_dims absent so helper cannot run" to "non-per-scale always returns raw flag". - Add normalized_none_reports_none (non-per-scale + None -> None). - Add normalized_true_reports_true (non-per-scale + Some(true) -> Some(true)). - Add non_per_scale_normalized_false_with_input_dims_reports_false_but_normalizes: accessor returns Some(false) AND decoded boxes land in [0,1], pinning both the under-claiming flag and the internal normalizer firing. Signed-off-by: Sébastien Taylor <[email protected]>
Signed-off-by: Sébastien Taylor <[email protected]>
Signed-off-by: Sébastien Taylor <[email protected]>
Signed-off-by: Sébastien Taylor <[email protected]>
PR feedback addressed (6 follow-up commits,
|
| Suite | Passed | Failed | Ignored |
|---|---|---|---|
--lib --features tracker |
390 | 0 | 4 |
decoder_normalized_flag |
8 | 0 | 0 |
per_scale_parity |
29 | 0 | 2 |
| doc-tests | 61 | 0 | 0 |
edgefirst-hal-capi |
1 | 0 | 0 |
All gates green locally: cargo check --workspace, make format lint, verify_version, make sbom.
Known remaining gaps (potential follow-up tickets)
ModelType::YoloSplitSegDet: helper fires ondecode(yolo.rs:1282) but NOT on the tracker macroprocess_tracked_yolo_segmentation_split!(~postprocess.rs:1773) or on_protovariants. Asymmetric — accessor correctly under-claimsSome(false). Same-pattern fix would mirror what landed forYoloSegDet.ModelType::YoloSegDet2Way: helper not called anywhere in either tracker or non-tracker paths. Likely emits pixel-space whennormalized: false. Architect's investigation surfaced this as a likely-pre-existing bug independent of the original contract leak. Worth a JIRA ticket.
Both can be deferred — they don't regress against the pre-0.24.1 behavior; the accessor simply doesn't claim a contract it can't honor.
Test plan
- CI: full Rust + Python test matrix
- CI: SBOM + license policy gate
- On-target: gst bridge smoke test on the platform that triggered the original
coords ≈ 0regression, exercising both per-scale andYoloSegDetdecoders (including tracked variants)
…ry points Signed-off-by: Sébastien Taylor <[email protected]>
Four paths now invoke maybe_normalize_boxes_in_place uniformly: per-scale, YoloSegDet, YoloSplitSegDet, YoloSegDet2Way. Update Doxygen, Python docstrings, and CHANGELOG to name all four and drop the stale two-path claim. Regenerate C header via cbindgen. Signed-off-by: Sébastien Taylor <[email protected]>
Update decoder_normalized_flag.rs for fa8e919: flip split_schema_pixel_space_with_normalized_false_decodes to assert Some(true) now that YoloSplitSegDet normalizes uniformly; replace the stale non_per_scale_non_yolo_segdet test (premise invalidated) with detection_only_normalized_false_with_input_dims_reports_false_raw using a YoloDet schema (still returns raw flag); add yolo_split_segdet_tracker_path_normalizes and yolo_segdet_2way_decode_normalizes to pin the new uniform-normalization contract for the tracker and decode entry points on those types; tighten the file-level doc-block to reflect the complete coverage. Signed-off-by: Sébastien Taylor <[email protected]>
Round 4 — full uniform-normalization closure (commits
|
| ModelType | decode | decode_proto | decode_tracked | decode_tracked_proto |
|---|---|---|---|---|
| Per-scale | Y | Y | Y | Y |
YoloSegDet |
Y | Y | Y | Y |
YoloSplitSegDet |
Y | Y (new) | Y (new) | Y (new) |
YoloSegDet2Way |
Y (new) | Y (new) | Y (new) | Y (new) |
Total helper callsites: 19 (was 11 at the start of this round).
Accessor (fa8e919)
fn legacy_path_normalizes_uniformly(&self) -> bool {
matches!(
self.model_type,
ModelType::YoloSegDet { .. }
| ModelType::YoloSplitSegDet { .. }
| ModelType::YoloSegDet2Way { .. }
)
}Tests (c252df5)
- Flipped two now-stale assertions (
split_schema_pixel_space_with_normalized_false_decodes, the YoloSplitSegDet under-claim regression). - Refactored
non_per_scale_non_yolo_segdet_normalized_false_with_input_dims_reports_falseintodetection_only_normalized_false_with_input_dims_reports_false_rawusing aYoloDetschema (which intentionally stays at the raw-flag contract). - Added two new tracker-path regressions:
yolo_split_segdet_tracker_path_normalizes— pinsYoloSplitSegDetdecode_trackedupgrade + boxes in[0,1].yolo_segdet_2way_decode_normalizes— pinsYoloSegDet2Waydecodeupgrade + boxes in[0,1].
- Updated file-level doc-block to describe the now-complete four-path uniform contract.
Docs (73d04bc)
- C Doxygen / regenerated C header: rewritten from "two normalizing paths" to "four normalizing paths"; explicitly names the still-raw-flag families (
YoloDet,YoloSplitDet,YoloEndToEnd*,ModelPack*). - Python getter docstrings: same widening, using
:attr:cross-refs. - CHANGELOG rewritten in place to describe the complete fix: implementation + accessor halves, and the intentional raw-flag exceptions.
Test summary
| Suite | Passed | Failed | Ignored |
|---|---|---|---|
--lib --features tracker (unit) |
390 | 0 | 4 |
decoder_normalized_flag --features tracker |
10 | 0 | 0 |
per_scale_parity |
29 | 0 | 2 |
Integration (decoder_from_edgefirst_json, decoder_capacity, decode_vs_proto_parity) |
7 | 0 | 0 |
| doc-tests | 61 | 0 | 0 |
CAPI test_decoder_normalized_boxes |
1 | 0 | 0 |
All gates green: cargo check --workspace, make format lint, verify_version (after Round 1 bump), SBOM + license policy clean from round 1.
Confirmed remaining gaps (intentional; suitable for follow-up tickets, NOT release blockers)
YoloEndToEnd*: schemas declaringnormalized: falsepaired with these models would still receive pixel-space boxes — the embedded-NMS coordinate contract makes the right answer unclear. Worth a JIRA ticket to clarify whetherYoloEndToEnd*schemas can legitimately declarenormalized: falseand, if so, whether the helper should fire there too.ModelPack*: separate model family with its own conventions. Same situation — needs a clarification ticket.YoloSegDet2Waydecode_trackedtest: the implementation is in place (macro atpostprocess.rs:1974now calls the helper), but no synthetic test exercises this entry point because constructing a 2-way schema + ByteTrack stack programmatically is non-trivial. Thedecodepath IS pinned by the newyolo_segdet_2way_decode_normalizestest. Low priority — covered implicitly by source-level audit.
Test plan
- CI: full Rust + Python test matrix
- CI: SBOM + license policy gate
- On-target: gst bridge smoke test exercising all four ModelType families (per-scale,
YoloSegDet,YoloSplitSegDet,YoloSegDet2Way) via bothdecodeanddecode_trackedto confirm coordinates land in[0, 1]for all of them.
|



Summary
Patch release fixing the
Decoder::normalized_boxes()accessor contract leak: the public flag now describes the post-decode coordinate space the caller actually receives, not the raw schema annotation.When a schema declared
normalized: falseand the decoder also knewinput_dims, every decode path internally divided bbox channels by(W, H)viayolo::maybe_normalize_boxes_in_place(EDGEAI-1303) — butnormalized_boxes()still returnedSome(false)("pixel-space"). Downstream callers that trusted the flag (notably the GStreamer bridge) re-normalized themselves and collapsed detections to ~0.The accessor now returns
Some(true)whenever the internal helper runs, andSome(false)only wheninput_dimsis unknown and pixel-space genuinely leaks out. No control-flow changes — the in-place helper still triggers on the same input policy. Only the public accessor and surrounding docs move; the C / Python equivalents inherit the fix automatically.Three existing assertions that enshrined the buggy state flipped to expect
Some(true):crates/decoder/tests/decoder_normalized_flag.rs(combined-Detection and SplitSegDet)crates/decoder/src/decoder/tests.rs(Hailo YOLOv8-seg schema)crates/decoder/tests/per_scale_parity.rs(per-scale parity helper)New regression
normalized_false_without_input_dims_reports_falsepins theSome(false)branch via a programmatic builder that leavesinput_dimsunset.Test plan
coords ≈ 0regression and confirm segmentation boxes land in[0, 1]