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 content_region: 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_content_region(mut self, region: [f32; 4]) -> Self {
555 self.content_region = Some(region);
556 self
557 }
558
559 pub fn with_convert(
578 mut self,
579 convert: &impl edgefirst_tensor::ContentRegionSource,
580 src: (usize, usize),
581 dst: (usize, usize),
582 ) -> DecoderResult<Self> {
583 let region = convert
584 .content_region(src.0, src.1, dst.0, dst.1)
585 .map_err(|e| DecoderError::InvalidConfig(format!("content region: {e}")))?;
586 self.content_region = region.letterboxed();
587 Ok(self)
588 }
589}
590
591pub fn select_output<'a>(
595 outputs: &[&'a TensorDyn],
596 config: &SemanticConfig,
597) -> DecoderResult<&'a TensorDyn> {
598 let mut found = outputs.iter().copied().filter(|t| config.matches(t));
599 let (first, second) = (found.next(), found.next());
600 if let Some(name) = config.name() {
601 if let Some(t) = outputs
602 .iter()
603 .copied()
604 .find(|t| t.name() == name && config.matches(t))
605 {
606 return Ok(t);
607 }
608 }
609 match (first, second, config.position) {
610 (Some(t), None, _) => Ok(t),
611 (Some(_), Some(_), Some((index, count))) if outputs.len() == count => outputs
612 .get(index)
613 .copied()
614 .filter(|t| config.matches(t))
615 .ok_or_else(|| {
616 DecoderError::InvalidConfig(format!(
617 "the output at position {index} does not have the semantic output's shape {:?}",
618 config.shape()
619 ))
620 }),
621 (Some(_), Some(_), _) => Err(DecoderError::InvalidConfig(format!(
622 "several outputs have the semantic output's shape {:?}; pass the outputs in model order or name the tensors",
623 config.shape()
624 ))),
625 (None, _, _) => Err(DecoderError::InvalidShape(format!(
626 "no output has the semantic output's shape {:?}; got {:?}",
627 config.shape(),
628 outputs.iter().map(|t| t.shape().to_vec()).collect::<Vec<_>>()
629 ))),
630 }
631}
632
633pub fn decode_semantic(
654 input: &TensorDyn,
655 config: &SemanticConfig,
656 dst: &mut TensorDyn,
657 confidence: Option<&mut TensorDyn>,
658 options: &SemanticOptions,
659) -> DecoderResult<()> {
660 if !config.matches(input) {
661 return Err(DecoderError::InvalidShape(format!(
662 "semantic input shape {:?} does not match the configured {:?}",
663 input.shape(),
664 config.shape()
665 )));
666 }
667 let (out_h, out_w) = class_map_dims(dst, "class map")?;
668 if let Some(conf) = confidence.as_deref() {
669 if class_map_dims(conf, "confidence")? != (out_h, out_w) {
670 return Err(DecoderError::InvalidShape(format!(
671 "confidence shape {:?} differs from the class map's {:?}",
672 conf.shape(),
673 dst.shape()
674 )));
675 }
676 if config.input == SemanticInput::ClassMap {
677 return Err(DecoderError::NotSupported(
678 "a class-map output has no confidence".to_string(),
679 ));
680 }
681 }
682 let capacity = match dst.dtype() {
683 DType::U8 => u8::CAPACITY,
684 DType::U16 => u16::CAPACITY,
685 other => {
686 return Err(DecoderError::NotSupported(format!(
687 "class map dtype {other:?}; use u8 or u16"
688 )))
689 }
690 };
691 if config.classes() > capacity {
692 return Err(DecoderError::NotSupported(format!(
693 "{} classes do not fit a {:?} class map",
694 config.classes(),
695 dst.dtype()
696 )));
697 }
698
699 input.cpu_access().require(CpuAccess::Read)?;
700 dst.cpu_access().require(CpuAccess::Write)?;
701 if let Some(conf) = confidence.as_deref() {
702 conf.cpu_access().require(CpuAccess::Write)?;
703 }
704
705 let geometry = Geometry::new(config, options, out_h, out_w)?;
706 match config.input {
707 SemanticInput::ClassMap => decode_class_map(input, config, &geometry, dst),
708 SemanticInput::Scores => decode_scores(input, config, &geometry, dst, confidence),
709 }
710}
711
712fn class_map_dims(t: &TensorDyn, what: &str) -> DecoderResult<(usize, usize)> {
714 match *t.shape() {
715 [h, w] if h > 0 && w > 0 => {
716 if t.row_stride().is_some_and(|s| s != w * t.dtype().size()) {
717 return Err(DecoderError::NotSupported(format!(
718 "{what} tensor has a padded row stride"
719 )));
720 }
721 Ok((h, w))
722 }
723 _ => Err(DecoderError::InvalidShape(format!(
724 "{what} tensor shape {:?}; expected [H, W]",
725 t.shape()
726 ))),
727 }
728}
729
730struct Geometry {
732 mode: DecodeMode,
733 method: UpsampleMethod,
734 align_corners: bool,
735 roi: [f32; 4],
736 out_h: usize,
737 out_w: usize,
738 src_h: usize,
739 src_w: usize,
740}
741
742impl Geometry {
743 fn new(
744 config: &SemanticConfig,
745 options: &SemanticOptions,
746 out_h: usize,
747 out_w: usize,
748 ) -> DecoderResult<Self> {
749 let [x0, y0, x1, y1] = options.content_region.unwrap_or([0.0, 0.0, 1.0, 1.0]);
750 let valid =
751 |a: f32, b: f32| a.is_finite() && b.is_finite() && 0.0 <= a && a < b && b <= 1.0;
752 if !valid(x0, x1) || !valid(y0, y1) {
753 return Err(DecoderError::InvalidConfig(format!(
754 "content region {:?} must satisfy 0 <= min < max <= 1",
755 [x0, y0, x1, y1]
756 )));
757 }
758 let (src_h, src_w) = (config.height(), config.width());
759 Ok(Self {
760 mode: options.mode.unwrap_or(config.mode),
761 method: config.upsample.method,
762 align_corners: config.upsample.align_corners,
763 roi: [
764 x0 * src_w as f32,
765 y0 * src_h as f32,
766 (x1 - x0) * src_w as f32,
767 (y1 - y0) * src_h as f32,
768 ],
769 out_h,
770 out_w,
771 src_h,
772 src_w,
773 })
774 }
775
776 fn bilinear_scores(&self) -> bool {
778 self.mode == DecodeMode::Accurate && self.method == UpsampleMethod::Bilinear
779 }
780
781 fn nearest(&self) -> (Vec<u32>, Vec<u32>) {
782 let [x0, y0, w, h] = self.roi;
783 (
784 kernels::nearest_taps(self.out_h, self.src_h, y0, h),
785 kernels::nearest_taps(self.out_w, self.src_w, x0, w),
786 )
787 }
788
789 fn bilinear(&self) -> (Vec<kernels::Tap>, Vec<kernels::Tap>) {
790 let [x0, y0, w, h] = self.roi;
791 (
792 kernels::bilinear_taps(self.out_h, self.src_h, y0, h, self.align_corners),
793 kernels::bilinear_taps(self.out_w, self.src_w, x0, w, self.align_corners),
794 )
795 }
796}
797
798enum Quant {
801 PerTensor(Dequant),
802 PerChannel {
803 scales: Vec<f32>,
804 zero_points: Vec<i32>,
805 },
806}
807
808fn squeezed_axis(shape: &[usize], axis: usize) -> Option<usize> {
811 (shape.get(axis)? > &1).then(|| shape[..axis].iter().filter(|&&n| n > 1).count())
812}
813
814fn resolve_quant(
818 input: &TensorDyn,
819 config: &SemanticConfig,
820 needs_real: bool,
821) -> DecoderResult<Quant> {
822 if matches!(input.dtype(), DType::F16 | DType::F32) {
823 return Ok(Quant::PerTensor(Dequant::IDENTITY));
824 }
825 let (q, shape) = match (input.quantization(), config.quantization.as_ref()) {
826 (Some(q), _) => (q, input.shape()),
827 (None, Some(q)) => (q, config.shape()),
828 (None, None) if needs_real => {
829 return Err(DecoderError::QuantMissing {
830 dtype: input.dtype(),
831 role: "semantic scores",
832 level: 0,
833 })
834 }
835 (None, None) => return Ok(Quant::PerTensor(Dequant::IDENTITY)),
836 };
837 let check = |s: f32| {
838 if s.is_finite() && s > 0.0 {
839 Ok(s)
840 } else {
841 Err(DecoderError::InvalidConfig(format!(
842 "semantic quantization scale {s} must be positive"
843 )))
844 }
845 };
846 let per_channel = |scales: &[f32], zps: Vec<i32>, axis: usize| {
847 let class_axis = config
848 .layout
849 .classes
850 .and_then(|a| squeezed_axis(config.shape(), a));
851 if class_axis.is_none() || squeezed_axis(shape, axis) != class_axis {
852 return Err(DecoderError::NotSupported(format!(
853 "per-channel quantization on axis {axis}, which is not the class axis"
854 )));
855 }
856 let classes = config.grid().classes;
857 if scales.len() != classes || zps.len() != classes {
858 return Err(DecoderError::InvalidConfig(format!(
859 "per-channel quantization has {} scales and {} zero points for {classes} classes",
860 scales.len(),
861 zps.len()
862 )));
863 }
864 scales.iter().try_for_each(|&s| check(s).map(|_| ()))?;
865 Ok(Quant::PerChannel {
866 scales: scales.to_vec(),
867 zero_points: zps,
868 })
869 };
870 match q.mode() {
871 QuantMode::PerTensorSymmetric { scale } => Ok(Quant::PerTensor(Dequant {
872 scale: check(scale)?,
873 zero_point: 0.0,
874 })),
875 QuantMode::PerTensor { scale, zero_point } => Ok(Quant::PerTensor(Dequant {
876 scale: check(scale)?,
877 zero_point: zero_point as f32,
878 })),
879 QuantMode::PerChannelSymmetric { scales, axis } => {
880 per_channel(scales, vec![0; scales.len()], axis)
881 }
882 QuantMode::PerChannel {
883 scales,
884 zero_points,
885 axis,
886 } => per_channel(scales, zero_points.to_vec(), axis),
887 }
888}
889
890fn map_err(what: &str) -> impl FnOnce(edgefirst_tensor::Error) -> DecoderError + '_ {
891 move |e| {
892 DecoderError::from_map(e, |e| {
893 DecoderError::InvalidConfig(format!("{what} map failed: {e}"))
894 })
895 }
896}
897
898fn decode_scores(
899 input: &TensorDyn,
900 config: &SemanticConfig,
901 geometry: &Geometry,
902 dst: &mut TensorDyn,
903 confidence: Option<&mut TensorDyn>,
904) -> DecoderResult<()> {
905 let g = config.grid_for(input)?;
906 let needs_real = g.classes == 1 || confidence.is_some();
907 macro_rules! typed {
908 ($t:ty) => {{
909 let map = input
910 .as_typed::<$t>()
911 .ok_or_else(|| DecoderError::Internal("semantic input dtype".to_string()))?
912 .map_read()
913 .map_err(map_err("semantic input"))?;
914 check_extent(map.as_slice().len(), g, "semantic input")?;
915 match resolve_quant(input, config, needs_real)? {
916 Quant::PerTensor(dq) => {
917 scores_into(map.as_slice(), g, config, dq, geometry, dst, confidence)
918 }
919 Quant::PerChannel {
920 scales,
921 zero_points,
922 } => {
923 let real = dequantize_per_channel(map.as_slice(), g, &scales, &zero_points);
924 scores_into(
925 &real,
926 g,
927 config,
928 Dequant::IDENTITY,
929 geometry,
930 dst,
931 confidence,
932 )
933 }
934 }
935 }};
936 }
937 match input.dtype() {
938 DType::F32 => typed!(f32),
939 DType::F16 => typed!(f16),
940 DType::U8 => typed!(u8),
941 DType::I8 => typed!(i8),
942 DType::U16 => typed!(u16),
943 DType::I16 => typed!(i16),
944 other => Err(DecoderError::NotSupported(format!(
945 "semantic score dtype {other:?}"
946 ))),
947 }
948}
949
950fn dequantize_per_channel<T: Score>(
951 data: &[T],
952 g: Grid,
953 scales: &[f32],
954 zero_points: &[i32],
955) -> Vec<f32> {
956 let mut out = vec![0f32; data.len()];
957 for (c, (&s, &z)) in scales.iter().zip(zero_points).enumerate() {
958 let z = z as f32;
959 for y in 0..g.height {
960 for x in 0..g.width {
961 let i = c * g.cs + y * g.ys + x * g.xs;
962 out[i] = (data[i].to_f32() - z) * s;
963 }
964 }
965 }
966 out
967}
968
969fn scores_into<T: Score>(
970 data: &[T],
971 g: Grid,
972 config: &SemanticConfig,
973 dq: Dequant,
974 geometry: &Geometry,
975 dst: &mut TensorDyn,
976 confidence: Option<&mut TensorDyn>,
977) -> DecoderResult<()> {
978 let single = g.classes == 1;
979 let cut = dq.stored(config.threshold());
980 let needs_winners = !single || !geometry.bilinear_scores() || confidence.is_some();
984 let winners = needs_winners.then(|| {
985 if single {
986 kernels::threshold_map(data, g, cut)
987 } else {
988 kernels::argmax_map(data, g)
989 }
990 });
991 match dst.dtype() {
992 DType::U8 => write_classes::<T, u8>(data, g, cut, winners.as_deref(), geometry, dst)?,
993 _ => write_classes::<T, u16>(data, g, cut, winners.as_deref(), geometry, dst)?,
994 }
995 let (Some(conf), Some(winners)) = (confidence, winners) else {
996 return Ok(());
997 };
998 let kind = match (single, config.activation, config.activation_required) {
999 (true, Some(_), _) => ConfKind::BinaryProbability,
1000 (true, None, _) => ConfKind::BinaryLogit,
1001 (false, Some(_), _) => ConfKind::Probability,
1002 (false, None, Some(Activation::Sigmoid)) => ConfKind::Sigmoid,
1003 (false, None, _) => ConfKind::Softmax,
1004 };
1005 let low = kernels::confidence_map(data, g, &winners, dq, kind);
1006 match conf.dtype() {
1007 DType::F32 => write_confidence::<f32>(&low, g.width, geometry, conf),
1008 DType::U8 => write_confidence::<u8>(&low, g.width, geometry, conf),
1009 other => Err(DecoderError::NotSupported(format!(
1010 "confidence dtype {other:?}; use f32 or u8"
1011 ))),
1012 }
1013}
1014
1015fn write_classes<T: Score, O: ClassOut + edgefirst_tensor::Element>(
1016 data: &[T],
1017 g: Grid,
1018 cut: f32,
1019 winners: Option<&[u16]>,
1020 geometry: &Geometry,
1021 dst: &mut TensorDyn,
1022) -> DecoderResult<()> {
1023 let mut map = dst
1024 .as_typed_mut::<O>()
1025 .ok_or_else(|| DecoderError::Internal("class map dtype".to_string()))?
1026 .map_for_write()
1027 .map_err(map_err("class map"))?;
1028 let out = map.as_mut_slice();
1029 match (geometry.bilinear_scores(), winners) {
1030 (true, Some(winners)) if g.classes > 1 => {
1031 let (ys, xs) = geometry.bilinear();
1032 kernels::upsample_argmax(data, g, winners, &ys, &xs, out);
1033 }
1034 (true, _) => {
1035 let (ys, xs) = geometry.bilinear();
1036 kernels::upsample_threshold(data, g, cut, &ys, &xs, out);
1037 }
1038 (false, Some(winners)) => {
1039 let (ys, xs) = geometry.nearest();
1040 kernels::upsample_nearest(winners, g.width, &ys, &xs, out, O::from_class);
1041 }
1042 (false, None) => {
1043 return Err(DecoderError::Internal(
1044 "nearest decode without a class map".to_string(),
1045 ))
1046 }
1047 }
1048 Ok(())
1049}
1050
1051fn write_confidence<C: ConfOut + edgefirst_tensor::Element>(
1052 low: &[f32],
1053 width: usize,
1054 geometry: &Geometry,
1055 conf: &mut TensorDyn,
1056) -> DecoderResult<()> {
1057 let mut map = conf
1058 .as_typed_mut::<C>()
1059 .ok_or_else(|| DecoderError::Internal("confidence dtype".to_string()))?
1060 .map_for_write()
1061 .map_err(map_err("confidence"))?;
1062 let out = map.as_mut_slice();
1063 if geometry.bilinear_scores() {
1064 let (ys, xs) = geometry.bilinear();
1065 kernels::upsample_conf_bilinear(low, width, &ys, &xs, out);
1066 } else {
1067 let (ys, xs) = geometry.nearest();
1068 kernels::upsample_nearest(low, width, &ys, &xs, out, C::from_conf);
1069 }
1070 Ok(())
1071}
1072
1073fn decode_class_map(
1075 input: &TensorDyn,
1076 config: &SemanticConfig,
1077 geometry: &Geometry,
1078 dst: &mut TensorDyn,
1079) -> DecoderResult<()> {
1080 let g = config.grid_for(input)?;
1081 macro_rules! typed {
1082 ($t:ty) => {{
1083 let map = input
1084 .as_typed::<$t>()
1085 .ok_or_else(|| DecoderError::Internal("class map input dtype".to_string()))?
1086 .map_read()
1087 .map_err(map_err("class map input"))?;
1088 check_extent(map.as_slice().len(), g, "class map input")?;
1089 class_map_into(map.as_slice(), g, geometry, dst)
1090 }};
1091 }
1092 match input.dtype() {
1093 DType::U8 => typed!(u8),
1094 DType::U16 => typed!(u16),
1095 DType::I32 => typed!(i32),
1096 DType::U32 => typed!(u32),
1097 DType::I64 => typed!(i64),
1098 other => Err(DecoderError::NotSupported(format!(
1099 "class map input dtype {other:?}"
1100 ))),
1101 }
1102}
1103
1104fn class_map_into<T>(
1105 data: &[T],
1106 g: Grid,
1107 geometry: &Geometry,
1108 dst: &mut TensorDyn,
1109) -> DecoderResult<()>
1110where
1111 T: Copy + TryInto<u16> + std::fmt::Display,
1112{
1113 let limit = match dst.dtype() {
1114 DType::U8 => u8::CAPACITY,
1115 _ => u16::CAPACITY,
1116 };
1117 let classes = (0..g.height)
1118 .flat_map(|y| (0..g.width).map(move |x| data[y * g.ys + x * g.xs]))
1119 .map(|v| {
1120 v.try_into()
1121 .ok()
1122 .filter(|&c: &u16| (c as usize) < limit)
1123 .ok_or_else(|| {
1124 DecoderError::InvalidConfig(format!(
1125 "class index {v} does not fit a {:?} class map",
1126 dst.dtype()
1127 ))
1128 })
1129 })
1130 .collect::<DecoderResult<Vec<u16>>>()?;
1131 let (ys, xs) = geometry.nearest();
1132 macro_rules! write {
1133 ($o:ty) => {{
1134 let mut map = dst
1135 .as_typed_mut::<$o>()
1136 .ok_or_else(|| DecoderError::Internal("class map dtype".to_string()))?
1137 .map_for_write()
1138 .map_err(map_err("class map"))?;
1139 kernels::upsample_nearest(
1140 &classes,
1141 geometry.src_w,
1142 &ys,
1143 &xs,
1144 map.as_mut_slice(),
1145 <$o>::from_class,
1146 );
1147 }};
1148 }
1149 match dst.dtype() {
1150 DType::U8 => write!(u8),
1151 _ => write!(u16),
1152 }
1153 Ok(())
1154}