1mod kernels;
44
45use edgefirst_tensor::{CpuAccess, DType, QuantMode, TensorDyn, TensorMapTrait, TensorTrait};
46use half::f16;
47
48use crate::configs::DimName;
49use crate::schema::{
50 Activation, DecodeMode, DecoderKind, LogicalOutput, LogicalType, Upsample, UpsampleMethod,
51};
52use crate::{DecoderError, DecoderResult};
53use kernels::{ClassOut, ConfKind, ConfOut, Dequant, Grid, Score};
54
55const MAX_CLASSES: usize = 1 << 16;
57
58#[derive(Debug, Clone, Copy, PartialEq)]
60#[non_exhaustive]
61pub enum SemanticInput {
62 Scores,
64 ClassMap,
66}
67
68#[derive(Debug, Clone, Copy, PartialEq, Eq)]
70pub struct SemanticLayout {
71 pub classes: Option<usize>,
73 pub height: usize,
75 pub width: usize,
77}
78
79#[derive(Debug, Clone, PartialEq)]
82pub struct SemanticConfig {
83 input: SemanticInput,
84 name: Option<String>,
85 position: Option<(usize, usize)>,
86 shape: Vec<usize>,
87 layout: SemanticLayout,
88 upsample: Upsample,
89 mode: DecodeMode,
90 threshold: Option<f32>,
91 activation: Option<Activation>,
92 activation_required: Option<Activation>,
93 quantization: Option<edgefirst_tensor::Quantization>,
94}
95
96impl SemanticConfig {
97 pub fn scores(
103 shape: &[usize],
104 class_axis: usize,
105 height_axis: usize,
106 width_axis: usize,
107 ) -> DecoderResult<Self> {
108 let layout = SemanticLayout {
109 classes: Some(class_axis),
110 height: height_axis,
111 width: width_axis,
112 };
113 Self::new(SemanticInput::Scores, shape, layout)
114 }
115
116 pub fn class_map(
119 shape: &[usize],
120 height_axis: usize,
121 width_axis: usize,
122 ) -> DecoderResult<Self> {
123 let layout = SemanticLayout {
124 classes: None,
125 height: height_axis,
126 width: width_axis,
127 };
128 Self::new(SemanticInput::ClassMap, shape, layout)
129 }
130
131 fn new(input: SemanticInput, shape: &[usize], layout: SemanticLayout) -> DecoderResult<Self> {
132 let axes = [layout.classes, Some(layout.height), Some(layout.width)];
133 let named: Vec<usize> = axes.iter().flatten().copied().collect();
134 if named.iter().any(|&a| a >= shape.len()) {
135 return Err(DecoderError::InvalidShape(format!(
136 "semantic axes {named:?} out of range for shape {shape:?}"
137 )));
138 }
139 if named.len() != named.iter().collect::<std::collections::HashSet<_>>().len() {
140 return Err(DecoderError::InvalidShape(format!(
141 "semantic axes {named:?} must be distinct"
142 )));
143 }
144 if let Some((axis, &n)) = shape
145 .iter()
146 .enumerate()
147 .find(|&(i, &n)| n != 1 && !named.contains(&i))
148 {
149 return Err(DecoderError::InvalidShape(format!(
150 "semantic output shape {shape:?} has unnamed axis {axis} of size {n}"
151 )));
152 }
153 if shape.contains(&0) {
154 return Err(DecoderError::InvalidShape(format!(
155 "semantic output shape {shape:?} is empty"
156 )));
157 }
158 let config = Self {
159 input,
160 name: None,
161 position: None,
162 shape: shape.to_vec(),
163 layout,
164 upsample: Upsample {
165 method: UpsampleMethod::Bilinear,
166 align_corners: false,
167 },
168 mode: DecodeMode::Accurate,
169 threshold: None,
170 activation: None,
171 activation_required: None,
172 quantization: None,
173 };
174 if config.classes() > MAX_CLASSES {
175 return Err(DecoderError::NotSupported(format!(
176 "{} classes exceed the decoder's limit of {MAX_CLASSES}",
177 config.classes()
178 )));
179 }
180 Ok(config)
181 }
182
183 pub fn from_logical(logical: &LogicalOutput) -> DecoderResult<Self> {
187 let label = logical.name.as_deref().unwrap_or("<anonymous>");
188 if logical.is_split() {
189 return Err(DecoderError::NotSupported(format!(
190 "semantic output `{label}` is split into physical tensors"
191 )));
192 }
193 let (input, role) = match logical.type_ {
194 Some(LogicalType::Segmentation) => (SemanticInput::Scores, "segmentation"),
195 Some(LogicalType::Masks) => (SemanticInput::ClassMap, "masks"),
196 other => {
197 return Err(DecoderError::InvalidConfig(format!(
198 "semantic output `{label}` has type {other:?}; expected segmentation or masks"
199 )))
200 }
201 };
202 crate::configs::validate_dshape(role, &logical.shape, &logical.dshape)?;
203 let modelpack = logical.decoder == Some(DecoderKind::ModelPack);
204 let mut config = if modelpack && input == SemanticInput::Scores {
205 let (c, h, w) = modelpack_axes(&logical.shape, &logical.dshape)?;
206 Self::scores(&logical.shape, c, h, w)?.with_mode(DecodeMode::Fast)
207 } else {
208 Self::new(input, &logical.shape, layout_from_dshape(logical, input)?)?
209 };
210 config.name = logical.name.clone();
211 if let Some(upsample) = logical.semantic.upsample {
212 config = config.with_upsample(upsample)?;
213 }
214 if let Some(mode) = logical.semantic.decode_mode {
215 config.mode = mode;
216 }
217 config.threshold = logical.semantic.threshold;
218 config.activation = logical.activation_applied;
219 config.activation_required = logical.activation_required;
220 config.check_activation()?;
221 if let Some(q) = &logical.quantization {
222 config.quantization = Some(q.try_into().map_err(|e| {
223 DecoderError::InvalidConfig(format!("semantic output `{label}` quantization: {e}"))
224 })?);
225 }
226 Ok(config)
227 }
228
229 pub(crate) fn from_modelpack(seg: &crate::configs::Segmentation) -> DecoderResult<Self> {
233 let (classes, height, width) = modelpack_axes(&seg.shape, &seg.dshape)?;
234 let mut config =
235 Self::scores(&seg.shape, classes, height, width)?.with_mode(DecodeMode::Fast);
236 if let Some(q) = &seg.quantization {
237 config.quantization = Some(edgefirst_tensor::Quantization::per_tensor(q.0, q.1));
238 }
239 Ok(config)
240 }
241
242 pub fn with_upsample(mut self, upsample: Upsample) -> DecoderResult<Self> {
244 if !matches!(
245 upsample.method,
246 UpsampleMethod::Bilinear | UpsampleMethod::Nearest
247 ) {
248 return Err(DecoderError::NotSupported(format!(
249 "semantic upsampling {:?} is not supported",
250 upsample.method
251 )));
252 }
253 self.upsample = upsample;
254 Ok(self)
255 }
256
257 pub fn with_mode(mut self, mode: DecodeMode) -> Self {
259 self.mode = mode;
260 self
261 }
262
263 pub fn with_threshold(mut self, threshold: f32) -> Self {
266 self.threshold = Some(threshold);
267 self
268 }
269
270 pub fn with_activation(mut self, activation: Activation) -> DecoderResult<Self> {
276 self.activation = Some(activation);
277 self.check_activation()?;
278 Ok(self)
279 }
280
281 pub fn with_activation_required(mut self, activation: Activation) -> DecoderResult<Self> {
291 self.activation_required = Some(activation);
292 self.check_activation()?;
293 Ok(self)
294 }
295
296 fn check_activation(&self) -> DecoderResult<()> {
297 let single = self.input == SemanticInput::Scores && self.grid().classes == 1;
298 match (self.activation, self.activation_required) {
299 (Some(_), Some(_)) => Err(DecoderError::InvalidConfig(
300 "a semantic output cannot both apply and require an activation".to_string(),
301 )),
302 (Some(Activation::Tanh), _) | (_, Some(Activation::Tanh)) => Err(
303 DecoderError::NotSupported("tanh semantic scores are not supported".to_string()),
304 ),
305 (Some(Activation::Softmax), _) | (_, Some(Activation::Softmax)) if single => {
306 Err(DecoderError::InvalidConfig(
307 "softmax over a single channel is constant; declare sigmoid".to_string(),
308 ))
309 }
310 _ => Ok(()),
311 }
312 }
313
314 pub fn with_quantization(mut self, quantization: edgefirst_tensor::Quantization) -> Self {
316 self.quantization = Some(quantization);
317 self
318 }
319
320 pub fn with_name(mut self, name: impl Into<String>) -> Self {
322 self.name = Some(name.into());
323 self
324 }
325
326 pub fn with_position(mut self, index: usize, count: usize) -> Self {
329 self.position = Some((index, count));
330 self
331 }
332
333 pub fn input(&self) -> SemanticInput {
335 self.input
336 }
337
338 pub fn name(&self) -> Option<&str> {
340 self.name.as_deref()
341 }
342
343 pub fn shape(&self) -> &[usize] {
345 &self.shape
346 }
347
348 pub fn layout(&self) -> SemanticLayout {
350 self.layout
351 }
352
353 pub fn classes(&self) -> usize {
357 match (self.input, self.layout.classes) {
358 (SemanticInput::ClassMap, _) => 0,
359 (_, Some(axis)) if self.shape[axis] > 1 => self.shape[axis],
360 _ => 2,
361 }
362 }
363
364 pub fn height(&self) -> usize {
366 self.shape[self.layout.height]
367 }
368
369 pub fn width(&self) -> usize {
371 self.shape[self.layout.width]
372 }
373
374 pub fn upsample(&self) -> Upsample {
376 self.upsample
377 }
378
379 pub fn mode(&self) -> DecodeMode {
381 self.mode
382 }
383
384 pub fn threshold(&self) -> f32 {
387 self.threshold.unwrap_or(match self.activation {
388 Some(Activation::Sigmoid) | Some(Activation::Softmax) => 0.5,
389 _ => 0.0,
390 })
391 }
392
393 pub fn matches(&self, tensor: &TensorDyn) -> bool {
395 squeeze(tensor.shape()) == squeeze(&self.shape)
396 }
397
398 fn grid(&self) -> Grid {
399 let mut strides = vec![1usize; self.shape.len()];
400 for i in (0..self.shape.len().saturating_sub(1)).rev() {
401 strides[i] = strides[i + 1] * self.shape[i + 1];
402 }
403 let (classes, cs) = match self.layout.classes {
404 Some(axis) => (self.shape[axis], strides[axis]),
405 None => (1, 0),
406 };
407 Grid {
408 classes,
409 height: self.height(),
410 width: self.width(),
411 cs,
412 ys: strides[self.layout.height],
413 xs: strides[self.layout.width],
414 }
415 }
416
417 fn grid_for(&self, input: &TensorDyn) -> DecoderResult<Grid> {
420 let mut grid = self.grid();
421 let Some(strides) = crate::decoder::tensor_bridge::padded_strides(input)? else {
422 return Ok(grid);
423 };
424 let stride = |axis: usize, packed: usize| {
426 squeezed_axis(&self.shape, axis)
427 .and_then(|k| {
428 input
429 .shape()
430 .iter()
431 .enumerate()
432 .filter(|&(_, &n)| n > 1)
433 .nth(k)
434 })
435 .map_or(packed, |(i, _)| strides[i])
436 };
437 if let Some(axis) = self.layout.classes {
438 grid.cs = stride(axis, grid.cs);
439 }
440 grid.ys = stride(self.layout.height, grid.ys);
441 grid.xs = stride(self.layout.width, grid.xs);
442 Ok(grid)
443 }
444}
445
446fn check_extent(len: usize, g: Grid, what: &str) -> DecoderResult<()> {
448 let extent = (g.classes - 1) * g.cs + (g.height - 1) * g.ys + (g.width - 1) * g.xs + 1;
449 if extent > len {
450 return Err(DecoderError::InvalidShape(format!(
451 "{what} holds {len} elements but its layout addresses {extent}"
452 )));
453 }
454 Ok(())
455}
456
457fn squeeze(shape: &[usize]) -> Vec<usize> {
458 shape.iter().copied().filter(|&n| n != 1).collect()
459}
460
461pub(crate) fn modelpack_class_axis(dshape: &[(DimName, usize)]) -> usize {
464 dshape
465 .iter()
466 .position(|(n, _)| *n == DimName::NumClasses)
467 .unwrap_or(3)
468}
469
470fn modelpack_axes(
473 shape: &[usize],
474 dshape: &[(DimName, usize)],
475) -> DecoderResult<(usize, usize, usize)> {
476 if shape.len() != 4 {
477 return Err(DecoderError::InvalidShape(format!(
478 "ModelPack segmentation shape {shape:?}; expected rank 4"
479 )));
480 }
481 let find =
482 |name: DimName, nhwc: usize| dshape.iter().position(|(n, _)| *n == name).unwrap_or(nhwc);
483 Ok((
484 modelpack_class_axis(dshape),
485 find(DimName::Height, 1),
486 find(DimName::Width, 2),
487 ))
488}
489
490fn layout_from_dshape(
491 logical: &LogicalOutput,
492 input: SemanticInput,
493) -> DecoderResult<SemanticLayout> {
494 let label = logical.name.as_deref().unwrap_or("<anonymous>");
495 let find = |name: DimName| logical.dshape.iter().position(|(n, _)| *n == name);
496 if !logical.dshape.is_empty() {
497 let (Some(height), Some(width)) = (find(DimName::Height), find(DimName::Width)) else {
498 return Err(DecoderError::InvalidConfig(format!(
499 "semantic output `{label}` dshape must name height and width"
500 )));
501 };
502 let classes = match input {
503 SemanticInput::Scores => find(DimName::NumClasses),
504 SemanticInput::ClassMap => None,
505 };
506 return Ok(SemanticLayout {
507 classes,
508 height,
509 width,
510 });
511 }
512 let spatial: Vec<usize> = (0..logical.shape.len())
514 .filter(|&i| logical.shape[i] != 1)
515 .collect();
516 match spatial.as_slice() {
517 [h, w] => Ok(SemanticLayout {
518 classes: None,
519 height: *h,
520 width: *w,
521 }),
522 _ => Err(DecoderError::InvalidConfig(format!(
523 "semantic output `{label}` needs a dshape naming num_classes, height and width"
524 ))),
525 }
526}
527
528#[derive(Debug, Clone, Copy, Default, PartialEq)]
530#[non_exhaustive]
531pub struct SemanticOptions {
532 pub mode: Option<DecodeMode>,
534 pub letterbox: Option<[f32; 4]>,
539}
540
541impl SemanticOptions {
542 pub fn new() -> Self {
544 Self::default()
545 }
546
547 pub fn with_mode(mut self, mode: DecodeMode) -> Self {
549 self.mode = Some(mode);
550 self
551 }
552
553 pub fn with_letterbox(mut self, letterbox: [f32; 4]) -> Self {
555 self.letterbox = Some(letterbox);
556 self
557 }
558}
559
560pub fn select_output<'a>(
564 outputs: &[&'a TensorDyn],
565 config: &SemanticConfig,
566) -> DecoderResult<&'a TensorDyn> {
567 let mut found = outputs.iter().copied().filter(|t| config.matches(t));
568 let (first, second) = (found.next(), found.next());
569 if let Some(name) = config.name() {
570 if let Some(t) = outputs
571 .iter()
572 .copied()
573 .find(|t| t.name() == name && config.matches(t))
574 {
575 return Ok(t);
576 }
577 }
578 match (first, second, config.position) {
579 (Some(t), None, _) => Ok(t),
580 (Some(_), Some(_), Some((index, count))) if outputs.len() == count => outputs
581 .get(index)
582 .copied()
583 .filter(|t| config.matches(t))
584 .ok_or_else(|| {
585 DecoderError::InvalidConfig(format!(
586 "the output at position {index} does not have the semantic output's shape {:?}",
587 config.shape()
588 ))
589 }),
590 (Some(_), Some(_), _) => Err(DecoderError::InvalidConfig(format!(
591 "several outputs have the semantic output's shape {:?}; pass the outputs in model order or name the tensors",
592 config.shape()
593 ))),
594 (None, _, _) => Err(DecoderError::InvalidShape(format!(
595 "no output has the semantic output's shape {:?}; got {:?}",
596 config.shape(),
597 outputs.iter().map(|t| t.shape().to_vec()).collect::<Vec<_>>()
598 ))),
599 }
600}
601
602pub fn decode_semantic(
623 input: &TensorDyn,
624 config: &SemanticConfig,
625 dst: &mut TensorDyn,
626 confidence: Option<&mut TensorDyn>,
627 options: &SemanticOptions,
628) -> DecoderResult<()> {
629 if !config.matches(input) {
630 return Err(DecoderError::InvalidShape(format!(
631 "semantic input shape {:?} does not match the configured {:?}",
632 input.shape(),
633 config.shape()
634 )));
635 }
636 let (out_h, out_w) = class_map_dims(dst, "class map")?;
637 if let Some(conf) = confidence.as_deref() {
638 if class_map_dims(conf, "confidence")? != (out_h, out_w) {
639 return Err(DecoderError::InvalidShape(format!(
640 "confidence shape {:?} differs from the class map's {:?}",
641 conf.shape(),
642 dst.shape()
643 )));
644 }
645 if config.input == SemanticInput::ClassMap {
646 return Err(DecoderError::NotSupported(
647 "a class-map output has no confidence".to_string(),
648 ));
649 }
650 }
651 let capacity = match dst.dtype() {
652 DType::U8 => u8::CAPACITY,
653 DType::U16 => u16::CAPACITY,
654 other => {
655 return Err(DecoderError::NotSupported(format!(
656 "class map dtype {other:?}; use u8 or u16"
657 )))
658 }
659 };
660 if config.classes() > capacity {
661 return Err(DecoderError::NotSupported(format!(
662 "{} classes do not fit a {:?} class map",
663 config.classes(),
664 dst.dtype()
665 )));
666 }
667
668 input.cpu_access().require(CpuAccess::Read)?;
669 dst.cpu_access().require(CpuAccess::Write)?;
670 if let Some(conf) = confidence.as_deref() {
671 conf.cpu_access().require(CpuAccess::Write)?;
672 }
673
674 let geometry = Geometry::new(config, options, out_h, out_w)?;
675 match config.input {
676 SemanticInput::ClassMap => decode_class_map(input, config, &geometry, dst),
677 SemanticInput::Scores => decode_scores(input, config, &geometry, dst, confidence),
678 }
679}
680
681fn class_map_dims(t: &TensorDyn, what: &str) -> DecoderResult<(usize, usize)> {
683 match *t.shape() {
684 [h, w] if h > 0 && w > 0 => {
685 if t.row_stride().is_some_and(|s| s != w * t.dtype().size()) {
686 return Err(DecoderError::NotSupported(format!(
687 "{what} tensor has a padded row stride"
688 )));
689 }
690 Ok((h, w))
691 }
692 _ => Err(DecoderError::InvalidShape(format!(
693 "{what} tensor shape {:?}; expected [H, W]",
694 t.shape()
695 ))),
696 }
697}
698
699struct Geometry {
701 mode: DecodeMode,
702 method: UpsampleMethod,
703 align_corners: bool,
704 roi: [f32; 4],
705 out_h: usize,
706 out_w: usize,
707 src_h: usize,
708 src_w: usize,
709}
710
711impl Geometry {
712 fn new(
713 config: &SemanticConfig,
714 options: &SemanticOptions,
715 out_h: usize,
716 out_w: usize,
717 ) -> DecoderResult<Self> {
718 let [x0, y0, x1, y1] = options.letterbox.unwrap_or([0.0, 0.0, 1.0, 1.0]);
719 let valid =
720 |a: f32, b: f32| a.is_finite() && b.is_finite() && 0.0 <= a && a < b && b <= 1.0;
721 if !valid(x0, x1) || !valid(y0, y1) {
722 return Err(DecoderError::InvalidConfig(format!(
723 "letterbox {:?} must satisfy 0 <= min < max <= 1",
724 [x0, y0, x1, y1]
725 )));
726 }
727 let (src_h, src_w) = (config.height(), config.width());
728 Ok(Self {
729 mode: options.mode.unwrap_or(config.mode),
730 method: config.upsample.method,
731 align_corners: config.upsample.align_corners,
732 roi: [
733 x0 * src_w as f32,
734 y0 * src_h as f32,
735 (x1 - x0) * src_w as f32,
736 (y1 - y0) * src_h as f32,
737 ],
738 out_h,
739 out_w,
740 src_h,
741 src_w,
742 })
743 }
744
745 fn bilinear_scores(&self) -> bool {
747 self.mode == DecodeMode::Accurate && self.method == UpsampleMethod::Bilinear
748 }
749
750 fn nearest(&self) -> (Vec<u32>, Vec<u32>) {
751 let [x0, y0, w, h] = self.roi;
752 (
753 kernels::nearest_taps(self.out_h, self.src_h, y0, h),
754 kernels::nearest_taps(self.out_w, self.src_w, x0, w),
755 )
756 }
757
758 fn bilinear(&self) -> (Vec<kernels::Tap>, Vec<kernels::Tap>) {
759 let [x0, y0, w, h] = self.roi;
760 (
761 kernels::bilinear_taps(self.out_h, self.src_h, y0, h, self.align_corners),
762 kernels::bilinear_taps(self.out_w, self.src_w, x0, w, self.align_corners),
763 )
764 }
765}
766
767enum Quant {
770 PerTensor(Dequant),
771 PerChannel {
772 scales: Vec<f32>,
773 zero_points: Vec<i32>,
774 },
775}
776
777fn squeezed_axis(shape: &[usize], axis: usize) -> Option<usize> {
780 (shape.get(axis)? > &1).then(|| shape[..axis].iter().filter(|&&n| n > 1).count())
781}
782
783fn resolve_quant(
787 input: &TensorDyn,
788 config: &SemanticConfig,
789 needs_real: bool,
790) -> DecoderResult<Quant> {
791 if matches!(input.dtype(), DType::F16 | DType::F32) {
792 return Ok(Quant::PerTensor(Dequant::IDENTITY));
793 }
794 let (q, shape) = match (input.quantization(), config.quantization.as_ref()) {
795 (Some(q), _) => (q, input.shape()),
796 (None, Some(q)) => (q, config.shape()),
797 (None, None) if needs_real => {
798 return Err(DecoderError::QuantMissing {
799 dtype: input.dtype(),
800 role: "semantic scores",
801 level: 0,
802 })
803 }
804 (None, None) => return Ok(Quant::PerTensor(Dequant::IDENTITY)),
805 };
806 let check = |s: f32| {
807 if s.is_finite() && s > 0.0 {
808 Ok(s)
809 } else {
810 Err(DecoderError::InvalidConfig(format!(
811 "semantic quantization scale {s} must be positive"
812 )))
813 }
814 };
815 let per_channel = |scales: &[f32], zps: Vec<i32>, axis: usize| {
816 let class_axis = config
817 .layout
818 .classes
819 .and_then(|a| squeezed_axis(config.shape(), a));
820 if class_axis.is_none() || squeezed_axis(shape, axis) != class_axis {
821 return Err(DecoderError::NotSupported(format!(
822 "per-channel quantization on axis {axis}, which is not the class axis"
823 )));
824 }
825 let classes = config.grid().classes;
826 if scales.len() != classes || zps.len() != classes {
827 return Err(DecoderError::InvalidConfig(format!(
828 "per-channel quantization has {} scales and {} zero points for {classes} classes",
829 scales.len(),
830 zps.len()
831 )));
832 }
833 scales.iter().try_for_each(|&s| check(s).map(|_| ()))?;
834 Ok(Quant::PerChannel {
835 scales: scales.to_vec(),
836 zero_points: zps,
837 })
838 };
839 match q.mode() {
840 QuantMode::PerTensorSymmetric { scale } => Ok(Quant::PerTensor(Dequant {
841 scale: check(scale)?,
842 zero_point: 0.0,
843 })),
844 QuantMode::PerTensor { scale, zero_point } => Ok(Quant::PerTensor(Dequant {
845 scale: check(scale)?,
846 zero_point: zero_point as f32,
847 })),
848 QuantMode::PerChannelSymmetric { scales, axis } => {
849 per_channel(scales, vec![0; scales.len()], axis)
850 }
851 QuantMode::PerChannel {
852 scales,
853 zero_points,
854 axis,
855 } => per_channel(scales, zero_points.to_vec(), axis),
856 }
857}
858
859fn map_err(what: &str) -> impl FnOnce(edgefirst_tensor::Error) -> DecoderError + '_ {
860 move |e| {
861 DecoderError::from_map(e, |e| {
862 DecoderError::InvalidConfig(format!("{what} map failed: {e}"))
863 })
864 }
865}
866
867fn decode_scores(
868 input: &TensorDyn,
869 config: &SemanticConfig,
870 geometry: &Geometry,
871 dst: &mut TensorDyn,
872 confidence: Option<&mut TensorDyn>,
873) -> DecoderResult<()> {
874 let g = config.grid_for(input)?;
875 let needs_real = g.classes == 1 || confidence.is_some();
876 macro_rules! typed {
877 ($t:ty) => {{
878 let map = input
879 .as_typed::<$t>()
880 .ok_or_else(|| DecoderError::Internal("semantic input dtype".to_string()))?
881 .map_read()
882 .map_err(map_err("semantic input"))?;
883 check_extent(map.as_slice().len(), g, "semantic input")?;
884 match resolve_quant(input, config, needs_real)? {
885 Quant::PerTensor(dq) => {
886 scores_into(map.as_slice(), g, config, dq, geometry, dst, confidence)
887 }
888 Quant::PerChannel {
889 scales,
890 zero_points,
891 } => {
892 let real = dequantize_per_channel(map.as_slice(), g, &scales, &zero_points);
893 scores_into(
894 &real,
895 g,
896 config,
897 Dequant::IDENTITY,
898 geometry,
899 dst,
900 confidence,
901 )
902 }
903 }
904 }};
905 }
906 match input.dtype() {
907 DType::F32 => typed!(f32),
908 DType::F16 => typed!(f16),
909 DType::U8 => typed!(u8),
910 DType::I8 => typed!(i8),
911 DType::U16 => typed!(u16),
912 DType::I16 => typed!(i16),
913 other => Err(DecoderError::NotSupported(format!(
914 "semantic score dtype {other:?}"
915 ))),
916 }
917}
918
919fn dequantize_per_channel<T: Score>(
920 data: &[T],
921 g: Grid,
922 scales: &[f32],
923 zero_points: &[i32],
924) -> Vec<f32> {
925 let mut out = vec![0f32; data.len()];
926 for (c, (&s, &z)) in scales.iter().zip(zero_points).enumerate() {
927 let z = z as f32;
928 for y in 0..g.height {
929 for x in 0..g.width {
930 let i = c * g.cs + y * g.ys + x * g.xs;
931 out[i] = (data[i].to_f32() - z) * s;
932 }
933 }
934 }
935 out
936}
937
938fn scores_into<T: Score>(
939 data: &[T],
940 g: Grid,
941 config: &SemanticConfig,
942 dq: Dequant,
943 geometry: &Geometry,
944 dst: &mut TensorDyn,
945 confidence: Option<&mut TensorDyn>,
946) -> DecoderResult<()> {
947 let single = g.classes == 1;
948 let cut = dq.stored(config.threshold());
949 let needs_winners = !single || !geometry.bilinear_scores() || confidence.is_some();
953 let winners = needs_winners.then(|| {
954 if single {
955 kernels::threshold_map(data, g, cut)
956 } else {
957 kernels::argmax_map(data, g)
958 }
959 });
960 match dst.dtype() {
961 DType::U8 => write_classes::<T, u8>(data, g, cut, winners.as_deref(), geometry, dst)?,
962 _ => write_classes::<T, u16>(data, g, cut, winners.as_deref(), geometry, dst)?,
963 }
964 let (Some(conf), Some(winners)) = (confidence, winners) else {
965 return Ok(());
966 };
967 let kind = match (single, config.activation, config.activation_required) {
968 (true, Some(_), _) => ConfKind::BinaryProbability,
969 (true, None, _) => ConfKind::BinaryLogit,
970 (false, Some(_), _) => ConfKind::Probability,
971 (false, None, Some(Activation::Sigmoid)) => ConfKind::Sigmoid,
972 (false, None, _) => ConfKind::Softmax,
973 };
974 let low = kernels::confidence_map(data, g, &winners, dq, kind);
975 match conf.dtype() {
976 DType::F32 => write_confidence::<f32>(&low, g.width, geometry, conf),
977 DType::U8 => write_confidence::<u8>(&low, g.width, geometry, conf),
978 other => Err(DecoderError::NotSupported(format!(
979 "confidence dtype {other:?}; use f32 or u8"
980 ))),
981 }
982}
983
984fn write_classes<T: Score, O: ClassOut + edgefirst_tensor::Element>(
985 data: &[T],
986 g: Grid,
987 cut: f32,
988 winners: Option<&[u16]>,
989 geometry: &Geometry,
990 dst: &mut TensorDyn,
991) -> DecoderResult<()> {
992 let mut map = dst
993 .as_typed_mut::<O>()
994 .ok_or_else(|| DecoderError::Internal("class map dtype".to_string()))?
995 .map_for_write()
996 .map_err(map_err("class map"))?;
997 let out = map.as_mut_slice();
998 match (geometry.bilinear_scores(), winners) {
999 (true, Some(winners)) if g.classes > 1 => {
1000 let (ys, xs) = geometry.bilinear();
1001 kernels::upsample_argmax(data, g, winners, &ys, &xs, out);
1002 }
1003 (true, _) => {
1004 let (ys, xs) = geometry.bilinear();
1005 kernels::upsample_threshold(data, g, cut, &ys, &xs, out);
1006 }
1007 (false, Some(winners)) => {
1008 let (ys, xs) = geometry.nearest();
1009 kernels::upsample_nearest(winners, g.width, &ys, &xs, out, O::from_class);
1010 }
1011 (false, None) => {
1012 return Err(DecoderError::Internal(
1013 "nearest decode without a class map".to_string(),
1014 ))
1015 }
1016 }
1017 Ok(())
1018}
1019
1020fn write_confidence<C: ConfOut + edgefirst_tensor::Element>(
1021 low: &[f32],
1022 width: usize,
1023 geometry: &Geometry,
1024 conf: &mut TensorDyn,
1025) -> DecoderResult<()> {
1026 let mut map = conf
1027 .as_typed_mut::<C>()
1028 .ok_or_else(|| DecoderError::Internal("confidence dtype".to_string()))?
1029 .map_for_write()
1030 .map_err(map_err("confidence"))?;
1031 let out = map.as_mut_slice();
1032 if geometry.bilinear_scores() {
1033 let (ys, xs) = geometry.bilinear();
1034 kernels::upsample_conf_bilinear(low, width, &ys, &xs, out);
1035 } else {
1036 let (ys, xs) = geometry.nearest();
1037 kernels::upsample_nearest(low, width, &ys, &xs, out, C::from_conf);
1038 }
1039 Ok(())
1040}
1041
1042fn decode_class_map(
1044 input: &TensorDyn,
1045 config: &SemanticConfig,
1046 geometry: &Geometry,
1047 dst: &mut TensorDyn,
1048) -> DecoderResult<()> {
1049 let g = config.grid_for(input)?;
1050 macro_rules! typed {
1051 ($t:ty) => {{
1052 let map = input
1053 .as_typed::<$t>()
1054 .ok_or_else(|| DecoderError::Internal("class map input dtype".to_string()))?
1055 .map_read()
1056 .map_err(map_err("class map input"))?;
1057 check_extent(map.as_slice().len(), g, "class map input")?;
1058 class_map_into(map.as_slice(), g, geometry, dst)
1059 }};
1060 }
1061 match input.dtype() {
1062 DType::U8 => typed!(u8),
1063 DType::U16 => typed!(u16),
1064 DType::I32 => typed!(i32),
1065 DType::U32 => typed!(u32),
1066 DType::I64 => typed!(i64),
1067 other => Err(DecoderError::NotSupported(format!(
1068 "class map input dtype {other:?}"
1069 ))),
1070 }
1071}
1072
1073fn class_map_into<T>(
1074 data: &[T],
1075 g: Grid,
1076 geometry: &Geometry,
1077 dst: &mut TensorDyn,
1078) -> DecoderResult<()>
1079where
1080 T: Copy + TryInto<u16> + std::fmt::Display,
1081{
1082 let limit = match dst.dtype() {
1083 DType::U8 => u8::CAPACITY,
1084 _ => u16::CAPACITY,
1085 };
1086 let classes = (0..g.height)
1087 .flat_map(|y| (0..g.width).map(move |x| data[y * g.ys + x * g.xs]))
1088 .map(|v| {
1089 v.try_into()
1090 .ok()
1091 .filter(|&c: &u16| (c as usize) < limit)
1092 .ok_or_else(|| {
1093 DecoderError::InvalidConfig(format!(
1094 "class index {v} does not fit a {:?} class map",
1095 dst.dtype()
1096 ))
1097 })
1098 })
1099 .collect::<DecoderResult<Vec<u16>>>()?;
1100 let (ys, xs) = geometry.nearest();
1101 macro_rules! write {
1102 ($o:ty) => {{
1103 let mut map = dst
1104 .as_typed_mut::<$o>()
1105 .ok_or_else(|| DecoderError::Internal("class map dtype".to_string()))?
1106 .map_for_write()
1107 .map_err(map_err("class map"))?;
1108 kernels::upsample_nearest(
1109 &classes,
1110 geometry.src_w,
1111 &ys,
1112 &xs,
1113 map.as_mut_slice(),
1114 <$o>::from_class,
1115 );
1116 }};
1117 }
1118 match dst.dtype() {
1119 DType::U8 => write!(u8),
1120 _ => write!(u16),
1121 }
1122 Ok(())
1123}