edgefirst-decoder
High-performance ML model output decoding for object detection and segmentation.
This crate provides efficient post-processing for YOLO and ModelPack model outputs, supporting both floating-point and quantized inference results.
Supported Models
| Family | Detection | Segmentation | Formats |
|---|---|---|---|
| YOLO | YOLOv5, v8, v11, v26 | Instance seg | float32, int8, uint8 |
| ModelPack | SSD-style | Semantic seg | float32, int8, uint8 |
Features
- Quantized decoding - Direct int8/uint8 processing without dequantization overhead
- Configurable NMS - Class-agnostic or class-aware non-maximum suppression
- Batch processing - Efficient handling of batched model outputs
- Builder pattern - Flexible configuration with sensible defaults
Quick Start
use ;
// Build decoder from model config
let decoder = new
.with_score_threshold
.with_iou_threshold
.with_config_json_str
.build?;
// Decode quantized model output
let mut detections: = Vecwith_capacity;
let mut masks: = Vecwith_capacity;
decoder.decode_quantized?;
// Process results
for det in &detections
Low-Level API
For known model types, use the direct decoding functions:
use decode_yolo_det;
use Quantization;
let mut detections = Vecwith_capacity;
decode_yolo_det;
Configuration
Decoders can be configured via JSON/YAML matching the model's output specification:
NMS Modes
ClassAgnostic- Suppress overlapping boxes regardless of class (default)ClassAware- Only suppress boxes with the same class labelNone- Bypass NMS (for models with built-in NMS)
End-to-End Models (YOLO26)
YOLO26 models embed NMS directly in the model architecture (one-to-one matching heads), eliminating the need for external NMS post-processing.
Configure via the decoder_version field in the model config:
When decoder_version is "yolo26", the decoder:
- Bypasses NMS entirely (the
nmsconfig field is ignored) - Expects post-NMS output format:
[batch, N, 6+]where columns are[x1, y1, x2, y2, conf, class, ...] - Supports both detection-only and detection+segmentation variants
For non-end-to-end YOLO26 exports (end2end=false), use decoder_version: "yolov8" with explicit NMS configuration.
Non-End-to-End Mode
Models exported with end2end=false require external NMS, configurable via the nms field:
Proto Mask API
For segmentation models, the decoder provides two APIs for accessing mask prototype data:
decode_quantized_proto()— returns raw quantized proto data and mask coefficients without materializing pixel masksdecode_float_proto()— returns float proto data and mask coefficients
These are preferred when passing mask data to GPU rendering pipelines (e.g., ImageProcessor::draw_proto_masks()), as they avoid the CPU cost of materializing full-resolution masks.
// GPU rendering path: decode proto data, pass to GL for fused rendering
let = decoder.decode_quantized_proto?;
// Pass proto_data directly to GPU for fused mask overlay
processor.draw_proto_masks?;
Model Type Variants
The decoder automatically selects the appropriate model type based on the config:
| Variant | Tensors | Description |
|---|---|---|
YoloDet |
1 (detection) | Standard YOLO detection |
YoloSegDet |
2 (detection + protos) | YOLO detection + segmentation |
YoloSegDet2Way |
3 (detection + mask_coefs + protos) | YOLO segmentation with 2-way split output |
YoloSplitDet |
2 (boxes + scores) | Split-output detection |
YoloSplitSegDet |
4 (boxes + scores + mask_coeff + protos) | Split-output segmentation |
YoloEndToEndDet |
1 (detection) | End-to-end detection (post-NMS) |
YoloEndToEndSegDet |
2 (detection + protos) | End-to-end segmentation |
YoloSplitEndToEndDet |
3 (boxes + scores + classes) | Split end-to-end detection |
YoloSplitEndToEndSegDet |
5 (boxes + scores + classes + mask_coeff + protos) | Split end-to-end segmentation |
ModelPackDet |
2 (boxes + scores) | ModelPack detection |
ModelPackSegDet |
3 (boxes + scores + segmentation) | ModelPack segmentation |
ModelPackDetSplit |
N (detection layers) | ModelPack split detection |
ModelPackSegDetSplit |
N+1 (detection layers + segmentation) | ModelPack split segmentation |
ModelPackSeg |
1 (segmentation) | ModelPack semantic segmentation |
YOLO-SEG 2-Way Split Format
YoloSegDet2Way handles TFLite INT8 segmentation models that use a 2-way split to separate mask coefficients from the combined detection output. Boxes and class scores remain merged — they share similar value ranges so splitting them yields no quantization benefit. Only mask coefficients (unbounded linear projection) need a separate quantization scale.
Output Tensor Layout
| Output | Name | Shape | Content |
|---|---|---|---|
output0 |
detection | [1, nc+4, N] |
Boxes (4 channels) + class scores (nc channels), combined |
output1 |
protos | [1, H/4, W/4, 32] |
Prototype masks (4D NHWC) |
output2 |
mask_coefs | [1, 32, N] |
Mask coefficients per anchor |
Where N=8400 for 640×640 input (standard YOLO multi-scale anchors: 80×80 + 40×40 + 20×20).
COCO example (nc=80): detection is [1, 84, 8400], mask_coefs is [1, 32, 8400], protos is [1, 160, 160, 32].
Output Identification by Shape
The builder classifies the three outputs by shape alone:
- 4D tensor → protos (only protos are 4D)
- 3D, feature_dim == 32 → mask_coefs
- 3D, feature_dim != 32 → detection (nc+4 channels)
The feature dimension is the smaller of the two non-batch dimensions (channels-first: shape[1]; channels-last: shape[2]).
Edge Case: nc=28
When num_classes + 4 == 32, the detection and mask_coefs feature dimensions collide, making shape-based classification ambiguous. In this case the split is not performed — the model exports 2 outputs (unsplit detection + protos) and is decoded as YoloSegDet instead.
Auto-Selection
The builder selects YoloSegDet2Way automatically when it detects exactly 3 outputs whose shapes match the pattern above (one 4D proto tensor, one 3D tensor with feature_dim=32, one 3D tensor with feature_dim≠32). No extra configuration is required.
Backwards Compatibility
Older 4-output models (separate boxes + scores + mask_coefs + protos, i.e., YoloSplitSegDet) remain supported. The decoder classifies by shape, not by output count.
Tracked Decoding
The tracker feature adds decode_tracked to integrate object tracking directly into the decode step. Each decoded detection box is matched to a persistent track and assigned a stable UUID for the lifetime of the track.
Enable the feature in Cargo.toml:
= { = "0.13", = ["tracker"] }
Usage
use ;
use Tracker; // re-exported from edgefirst-tracker
use ByteTrackBuilder;
let decoder = new
.with_score_threshold
.with_iou_threshold
.with_config_json_str
.build?;
let mut tracker = new
.track_high_conf
.track_update
.build;
let mut detections: = Vecwith_capacity;
let mut masks: = Vecwith_capacity;
let mut tracks: = Vecwith_capacity;
decoder.decode_tracked?;
// detections[i] and tracks[i] correspond to the same object
for in detections.iter.zip
TrackInfo Fields
| Field | Type | Description |
|---|---|---|
uuid |
Uuid |
Stable unique identifier for the track |
tracked_location |
[f32; 4] |
Kalman-smoothed location in XYXY format |
count |
i32 |
Number of times the track has been updated |
created |
u64 |
Timestamp when the track was first created |
last_updated |
u64 |
Timestamp of the most recent update |
The tracked_location reflects the Kalman-filter prediction and may differ slightly from det.bbox, which is the raw decoded box before smoothing.
License
Licensed under the Apache License, Version 2.0. See LICENSE for details.