pub struct Decoder {
pub iou_threshold: f32,
pub score_threshold: f32,
pub nms: Option<Nms>,
pub pre_nms_top_k: usize,
pub max_det: usize,
/* private fields */
}Fields§
§iou_threshold: f32§score_threshold: f32§nms: Option<Nms>NMS mode (always a concrete variant after build — Nms::Auto is
resolved during DecoderBuilder::build() and never stored here):
Some(ClassAgnostic)— class-agnostic NMSSome(ClassAware)— class-aware NMSNone— NMS bypassed (end-to-end models)
pre_nms_top_k: usizeMaximum number of candidate boxes fed into NMS after score filtering.
Reduces O(N²) NMS cost when many low-confidence proposals pass the
threshold (common during COCO mAP evaluation with threshold ≈ 0.001).
Candidates are ranked by score; only the top pre_nms_top_k proceed
to NMS. Default: 300. Ignored when nms is None.
§⚠️ Validation vs Deployment
The default of 300 is tuned for deployment (score threshold ≥ 0.25) where few anchors pass the score filter, making top-K a no-op in practice while bounding worst-case NMS cost.
For mAP evaluation (score threshold ≈ 0.001), most of the 8 400
YOLO anchors pass the score filter. At pre_nms_top_k = 300, roughly
74 % of candidates that would survive NMS are discarded before NMS
runs, causing ~9 pp box mAP loss — a measurement artifact, not a
model quality issue.
| Use case | pre_nms_top_k | score_threshold |
|---|---|---|
| Deployment | 300 (default) | ≥ 0.25 |
| COCO mAP evaluation | 8 400 (all anchors) | 0.001 |
| Unbounded | 0 (no limit) | any |
Post-processing latency scales with the number of candidates entering
NMS. At deployment thresholds the candidate count is already small, so
raising pre_nms_top_k has negligible cost. At validation thresholds
the increase is measurable but necessary for correct recall.
max_det: usizeMaximum number of detections returned after NMS. Matches the
Ultralytics max_det parameter. Default: 300.
This bound applies uniformly across all segmentation and detection
decode paths reached via Decoder::decode / Decoder::decode_proto.
The output Vec’s capacity is only an allocation hint; the post-NMS
detection count is bounded solely by max_det (EDGEAI-1302).
Implementations§
Source§impl Decoder
impl Decoder
Sourcepub fn model_type(&self) -> &ModelType
pub fn model_type(&self) -> &ModelType
This function returns the parsed model type of the decoder.
§Examples
let decoder = DecoderBuilder::default()
.with_config_yaml_str(config_yaml)
.build()?;
assert!(matches!(
decoder.model_type(),
ModelType::ModelPackDetSplit { .. }
));Sourcepub fn normalized_boxes(&self) -> Option<bool>
pub fn normalized_boxes(&self) -> Option<bool>
Returns the box coordinate format if known from the model config.
Some(true): Boxes are in normalized [0,1] coordinatesSome(false): Boxes are in pixel coordinates relative to model inputNone: Unknown, caller must infer (e.g., check if any coordinate > 1.0)
This is determined by the model config’s normalized field, not the NMS
mode. When coordinates are in pixels or unknown, the caller may need
to normalize using the model input dimensions.
§Examples
let decoder = DecoderBuilder::default()
.with_config_yaml_str(config_yaml)
.build()?;
// Config doesn't specify normalized, so it's None
assert!(decoder.normalized_boxes().is_none());Sourcepub fn input_dims(&self) -> Option<(usize, usize)>
pub fn input_dims(&self) -> Option<(usize, usize)>
Model input dimensions (width, height) captured from the
schema’s input.shape / input.dshape, or None when the
schema did not declare an input spec (e.g. flat YAML configs
or DecoderBuilder::add_output(...) programmatic builds).
Used together with normalized_boxes:
when the decoder reports normalized_boxes() == Some(false) and
input_dims() is Some((w, h)), the decoder divides post-NMS
bbox coordinates by (w, h) so they enter the canonical [0, 1]
range before mask cropping (EDGEAI-1303). When input_dims() is
None, the decoder cannot perform the division and the existing
protobox > 2.0 reject acts as a safety net.
§Examples
let json = r#"{
"schema_version": 2,
"nms": "class_agnostic",
"input": {
"shape": [1, 640, 640, 3],
"dshape": [{"batch": 1}, {"height": 640}, {"width": 640}, {"num_features": 3}]
},
"outputs": [{
"name": "out", "type": "detection",
"shape": [1, 38, 256],
"dshape": [{"batch": 1}, {"num_features": 38}, {"num_boxes": 256}],
"decoder": "ultralytics", "encoding": "direct", "normalized": false
}]
}"#;
let schema: SchemaV2 = serde_json::from_str(json).unwrap();
let decoder = DecoderBuilder::default().with_schema(schema).build()?;
assert_eq!(decoder.input_dims(), Some((640, 640)));Sourcepub fn decode(
&self,
outputs: &[&TensorDyn],
output_boxes: &mut Vec<DetectBox>,
output_masks: &mut Vec<Segmentation>,
) -> Result<(), DecoderError>
pub fn decode( &self, outputs: &[&TensorDyn], output_boxes: &mut Vec<DetectBox>, output_masks: &mut Vec<Segmentation>, ) -> Result<(), DecoderError>
Decode model outputs into detection boxes and segmentation masks.
This is the primary decode API. Accepts TensorDyn outputs directly
from model inference. Automatically dispatches to quantized or float
paths based on the tensor dtype.
§Arguments
outputs- Tensor outputs from model inferenceoutput_boxes- Destination for decoded detection boxes (cleared first)output_masks- Destination for decoded segmentation masks (cleared first)
§output_boxes / output_masks capacity
The capacity of the supplied Vecs is only an allocation hint —
it is not a cap on the number of detections returned. The
post-NMS detection count is bounded by Decoder::max_det (set
via DecoderBuilder::with_max_det, default 300). Passing
Vec::new() (capacity 0) returns up to max_det detections;
pre-allocating with Vec::with_capacity only avoids the
reallocation when the decoder grows the buffer.
§Errors
Returns DecoderError if tensor mapping fails, dtypes are unsupported,
or the outputs don’t match the decoder’s model configuration.
Sourcepub fn decode_proto(
&self,
outputs: &[&TensorDyn],
output_boxes: &mut Vec<DetectBox>,
) -> Result<Option<ProtoData>, DecoderError>
pub fn decode_proto( &self, outputs: &[&TensorDyn], output_boxes: &mut Vec<DetectBox>, ) -> Result<Option<ProtoData>, DecoderError>
Decode model outputs into detection boxes, returning raw proto data for segmentation models instead of materialized masks.
Accepts TensorDyn outputs directly from model inference.
Detections are always decoded into output_boxes regardless of model type.
Returns Ok(None) for detection-only and ModelPack models.
Returns Ok(Some(ProtoData)) for YOLO segmentation models.
§Arguments
outputs- Tensor outputs from model inferenceoutput_boxes- Destination for decoded detection boxes (cleared first)
§output_boxes capacity
The capacity of output_boxes is only an allocation hint — it
is not a cap on the number of detections returned. The
post-NMS detection count is bounded by Decoder::max_det (set
via DecoderBuilder::with_max_det, default 300). Passing
Vec::new() (capacity 0) returns up to max_det detections.
§Errors
Returns DecoderError if tensor mapping fails, dtypes are unsupported,
or the outputs don’t match the decoder’s model configuration.
Trait Implementations§
Source§impl Clone for Decoder
impl Clone for Decoder
Source§fn clone(&self) -> Self
fn clone(&self) -> Self
Cloning a Decoder preserves the legacy decode path
(decode_program) but drops the per-scale fast path:
PerScaleDecoder owns mutable per-frame scratch buffers and is
not Clone. Decoders built from a per-scale schema should be
rebuilt via DecoderBuilder rather than cloned to preserve the
fast path; cloning is intended for tests and rare configs.
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read moreAuto Trait Implementations§
impl !Freeze for Decoder
impl RefUnwindSafe for Decoder
impl Send for Decoder
impl Sync for Decoder
impl Unpin for Decoder
impl UnsafeUnpin for Decoder
impl UnwindSafe for Decoder
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
Source§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more