1use bincode::de::Decoder;
2use bincode::enc::Encoder;
3use bincode::error::{DecodeError, EncodeError};
4use bincode::{Decode, Encode};
5use core::fmt::Debug;
6use core::marker::PhantomData;
7use cu29::prelude::*;
8use cu29::units::si::f32::Length;
9use cu29::units::si::length::meter;
10use serde::{Deserialize, Serialize, Serializer};
11
12#[derive(
17 Default, Debug, Encode, Decode, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Reflect,
18)]
19#[bincode(describe)]
20pub struct CuDepthMapFormat {
21 pub width: u32,
22 pub height: u32,
23 pub stride: u32,
24}
25
26impl CuDepthMapFormat {
27 pub fn is_valid(&self) -> bool {
28 self.width <= self.stride
29 }
30
31 pub fn required_elements(&self) -> usize {
32 self.stride as usize * self.height as usize
33 }
34}
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Reflect)]
38#[serde(rename_all = "snake_case")]
39pub enum CuDepthInvalidValue {
40 Unsigned(u64),
41 Signed(i64),
42 FloatBits(u64),
43}
44
45#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Reflect)]
47pub struct CuDepthEncodingDescriptor {
48 pub units_per_meter: f32,
50 pub invalid: Option<CuDepthInvalidValue>,
52}
53
54pub trait CuDepthEncoding: Copy + Debug + Default + Send + Sync + 'static {
56 type Sample: ElementType;
57
58 const DESCRIPTOR: CuDepthEncodingDescriptor;
59 const SERIALIZED_MAP_SCHEMA: &'static str = GENERIC_DEPTH_MAP_SERIALIZED_SCHEMA;
60 const TYPE_PATH: &'static str;
61 const SHORT_TYPE_PATH: &'static str;
62 const TYPE_IDENT: &'static str;
63
64 fn decode(sample: Self::Sample) -> Option<Length>;
68
69 fn encode_sample(sample: Option<Length>) -> Self::Sample;
73}
74
75#[derive(Debug, Default, Clone, Copy)]
77pub struct CuDepthLength;
78
79impl CuDepthEncoding for CuDepthLength {
80 type Sample = Length;
81
82 const DESCRIPTOR: CuDepthEncodingDescriptor = CuDepthEncodingDescriptor {
83 units_per_meter: 1.0,
84 invalid: None,
85 };
86 const SERIALIZED_MAP_SCHEMA: &'static str = LENGTH_DEPTH_MAP_SERIALIZED_SCHEMA;
87 const TYPE_PATH: &'static str = "cu_sensor_payloads::CuDepthMap";
88 const SHORT_TYPE_PATH: &'static str = "CuDepthMap";
89 const TYPE_IDENT: &'static str = "CuDepthMap";
90
91 fn decode(sample: Self::Sample) -> Option<Length> {
92 Some(sample)
93 }
94
95 fn encode_sample(sample: Option<Length>) -> Self::Sample {
96 sample.unwrap_or_else(|| Length::new::<meter>(f32::NAN))
97 }
98}
99
100pub const fn depth_resolution_meters(meters: f32) -> Length {
105 Length { value: meters }
106}
107
108pub trait CuDepthScale: Copy + Debug + Default + Send + Sync + 'static {
110 const RESOLUTION: Length;
111 const UNITS_PER_METER: f32 = 1.0 / Self::RESOLUTION.value;
112}
113
114#[derive(Debug, Default, Clone, Copy)]
119pub struct CuDepthScaleRatio<const NUMERATOR: u64, const DENOMINATOR: u64>;
120
121impl<const NUMERATOR: u64, const DENOMINATOR: u64> CuDepthScale
122 for CuDepthScaleRatio<NUMERATOR, DENOMINATOR>
123{
124 const RESOLUTION: Length = depth_resolution_meters(NUMERATOR as f32 / DENOMINATOR as f32);
125 const UNITS_PER_METER: f32 = DENOMINATOR as f32 / NUMERATOR as f32;
126}
127
128pub type CuDepthMillimeter = CuDepthScaleRatio<1, 1_000>;
130
131pub type CuDepthDecimeter = CuDepthScaleRatio<1, 10>;
133
134pub trait CuDepthIntegerSample: ElementType + 'static {
136 const MAX: u64;
137
138 fn to_u64(self) -> u64;
139 fn from_u64(value: u64) -> Self;
140}
141
142macro_rules! impl_depth_integer_sample {
143 ($($sample:ty),+ $(,)?) => {
144 $(
145 impl CuDepthIntegerSample for $sample {
146 const MAX: u64 = <$sample>::MAX as u64;
147
148 fn to_u64(self) -> u64 {
149 self as u64
150 }
151
152 fn from_u64(value: u64) -> Self {
153 value as Self
154 }
155 }
156 )+
157 };
158}
159
160impl_depth_integer_sample!(u8, u16, u32, u64);
161
162#[derive(Debug, Default, Clone, Copy)]
179pub struct CuDepthInteger<T, S>(PhantomData<(T, S)>);
180
181impl<T, S> CuDepthEncoding for CuDepthInteger<T, S>
182where
183 T: CuDepthIntegerSample,
184 S: CuDepthScale,
185{
186 type Sample = T;
187
188 const DESCRIPTOR: CuDepthEncodingDescriptor = CuDepthEncodingDescriptor {
189 units_per_meter: S::UNITS_PER_METER,
190 invalid: Some(CuDepthInvalidValue::Unsigned(0)),
191 };
192 const SERIALIZED_MAP_SCHEMA: &'static str = INTEGER_DEPTH_MAP_SERIALIZED_SCHEMA;
193 const TYPE_PATH: &'static str = "cu_sensor_payloads::CuDepthMapInteger";
194 const SHORT_TYPE_PATH: &'static str = "CuDepthMapInteger";
195 const TYPE_IDENT: &'static str = "CuDepthMapInteger";
196
197 fn decode(sample: Self::Sample) -> Option<Length> {
198 let raw = sample.to_u64();
199 (raw != 0).then(|| depth_resolution_meters(raw as f32 / S::UNITS_PER_METER))
200 }
201
202 fn encode_sample(sample: Option<Length>) -> Self::Sample {
203 let Some(meters) = sample.map(|sample| sample.get::<meter>()) else {
204 return T::from_u64(0);
205 };
206 if !meters.is_finite() || meters <= 0.0 {
207 return T::from_u64(0);
208 }
209 let raw = (meters * S::UNITS_PER_METER)
210 .round()
211 .clamp(1.0, T::MAX as f32) as u64;
212 T::from_u64(raw)
213 }
214}
215
216#[derive(Debug, Default, Clone, Reflect)]
222#[reflect(
223 from_reflect = false,
224 no_field_bounds,
225 type_path = false,
226 SerializedPayloadSchema
227)]
228pub struct CuDepthMap<A, E = CuDepthLength>
229where
230 E: CuDepthEncoding,
231 A: ArrayLike<Element = E::Sample> + Send + Sync + 'static,
232{
233 pub format: CuDepthMapFormat,
234 pub buffer_handle: CuHandle<A>,
235 #[reflect(ignore)]
236 encoding: PhantomData<E>,
237}
238
239impl<A, E> TypePath for CuDepthMap<A, E>
240where
241 E: CuDepthEncoding,
242 A: ArrayLike<Element = E::Sample> + Send + Sync + 'static,
243{
244 fn type_path() -> &'static str {
245 E::TYPE_PATH
246 }
247
248 fn short_type_path() -> &'static str {
249 E::SHORT_TYPE_PATH
250 }
251
252 fn type_ident() -> Option<&'static str> {
253 Some(E::TYPE_IDENT)
254 }
255
256 fn crate_name() -> Option<&'static str> {
257 Some("cu_sensor_payloads")
258 }
259
260 fn module_path() -> Option<&'static str> {
261 Some("cu_sensor_payloads")
262 }
263}
264
265impl<A, E> Encode for CuDepthMap<A, E>
266where
267 E: CuDepthEncoding,
268 A: ArrayLike<Element = E::Sample> + Send + Sync + 'static,
269 CuHandle<A>: Encode,
270{
271 fn encode<Enc: Encoder>(&self, encoder: &mut Enc) -> Result<(), EncodeError> {
272 Encode::encode(&self.format, encoder)?;
273 Encode::encode(&self.buffer_handle, encoder)
274 }
275}
276
277impl<A, E> bincode::ValueDecode for CuDepthMap<A, E>
278where
279 E: CuDepthEncoding,
280 A: ArrayLike<Element = E::Sample> + Send + Sync + 'static,
281 CuHandle<A>: bincode::ValueDecode,
282{
283 const DECODE: &'static bincode::ValueDecodeSpec = &bincode::ValueDecodeSpec::Record {
284 shape: bincode::value_decode::RecordShape::Struct,
285 fields: &[
286 bincode::value_decode::ValueDecodeField {
287 selector: bincode::value_decode::FieldSelector::Named("format"),
288 declaration_index: 0,
289 value: bincode::value_decode::ValueDecodeRef::of::<CuDepthMapFormat>(),
290 },
291 bincode::value_decode::ValueDecodeField {
292 selector: bincode::value_decode::FieldSelector::Named("buffer_handle"),
293 declaration_index: 1,
294 value: bincode::value_decode::ValueDecodeRef::of::<CuHandle<A>>(),
295 },
296 ],
297 };
298}
299
300impl<A, E> Decode<()> for CuDepthMap<A, E>
301where
302 E: CuDepthEncoding,
303 A: ArrayLike<Element = E::Sample> + Send + Sync + 'static,
304 CuHandle<A>: Decode<()>,
305{
306 fn decode<D: Decoder<Context = ()>>(decoder: &mut D) -> Result<Self, DecodeError> {
307 Ok(Self {
308 format: Decode::decode(decoder)?,
309 buffer_handle: Decode::decode(decoder)?,
310 encoding: PhantomData,
311 })
312 }
313}
314
315impl<'de, A, E> Deserialize<'de> for CuDepthMap<A, E>
316where
317 E: CuDepthEncoding,
318 A: ArrayLike<Element = E::Sample> + Send + Sync + 'static,
319 CuHandle<A>: Deserialize<'de>,
320{
321 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
322 where
323 D: serde::Deserializer<'de>,
324 {
325 #[derive(Deserialize)]
326 struct Wire<H> {
327 format: CuDepthMapFormat,
328 encoding: Option<CuDepthEncodingDescriptor>,
329 handle: H,
330 }
331
332 let wire = Wire::<CuHandle<A>>::deserialize(deserializer)?;
333 if let Some(encoding) = wire.encoding
334 && encoding != E::DESCRIPTOR
335 {
336 return Err(serde::de::Error::custom(format!(
337 "Depth encoding {:?} does not match {}",
338 encoding,
339 E::SHORT_TYPE_PATH
340 )));
341 }
342 Ok(Self {
343 format: wire.format,
344 buffer_handle: wire.handle,
345 encoding: PhantomData,
346 })
347 }
348}
349
350impl<A, E> Serialize for CuDepthMap<A, E>
351where
352 E: CuDepthEncoding,
353 A: ArrayLike<Element = E::Sample> + Send + Sync + 'static,
354 CuHandle<A>: Serialize,
355{
356 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
357 where
358 S: Serializer,
359 {
360 use serde::ser::SerializeStruct;
361 let mut state = serializer.serialize_struct(E::SHORT_TYPE_PATH, 3)?;
362 state.serialize_field("format", &self.format)?;
363 state.serialize_field("encoding", &E::DESCRIPTOR)?;
364 state.serialize_field("handle", &self.buffer_handle)?;
365 state.end()
366 }
367}
368
369impl<A, E> CuDepthMap<A, E>
370where
371 E: CuDepthEncoding,
372 A: ArrayLike<Element = E::Sample> + Send + Sync + 'static,
373{
374 pub const fn encoding_descriptor() -> CuDepthEncodingDescriptor {
375 E::DESCRIPTOR
376 }
377
378 pub fn new_encoded(format: CuDepthMapFormat, buffer_handle: CuHandle<A>) -> Self {
379 assert!(
380 E::DESCRIPTOR.units_per_meter.is_finite() && E::DESCRIPTOR.units_per_meter > 0.0,
381 "Depth encoding must have a finite positive resolution."
382 );
383 assert!(
384 format.is_valid(),
385 "Depth-map stride must be at least its width."
386 );
387 assert!(
388 format.required_elements() <= buffer_handle.with_inner(|inner| inner.len()),
389 "Buffer size must at least match the depth-map format."
390 );
391 Self {
392 format,
393 buffer_handle,
394 encoding: PhantomData,
395 }
396 }
397
398 pub fn get_raw(&self, x: u32, y: u32) -> Option<E::Sample> {
400 if x >= self.format.width || y >= self.format.height {
401 return None;
402 }
403 let index = y as usize * self.format.stride as usize + x as usize;
404 self.buffer_handle
405 .with_inner(|inner| inner.as_ref().get(index).copied())
406 }
407
408 pub fn decode_sample(sample: E::Sample) -> Option<Length> {
410 E::decode(sample)
411 }
412
413 pub fn get(&self, x: u32, y: u32) -> Option<Length> {
417 Self::decode_sample(self.get_raw(x, y)?)
418 }
419
420 pub fn get_meters(&self, x: u32, y: u32) -> Option<f32> {
422 self.get(x, y).map(|depth| depth.get::<meter>())
423 }
424
425 pub fn convert_into<B, F>(&self, target: &mut CuDepthMap<B, F>) -> CuResult<()>
430 where
431 F: CuDepthEncoding,
432 B: ArrayLike<Element = F::Sample> + Send + Sync + 'static,
433 {
434 if self.format.width != target.format.width || self.format.height != target.format.height {
435 return Err(CuError::from(format!(
436 "Depth-map dimensions differ: source is {}x{}, target is {}x{}",
437 self.format.width, self.format.height, target.format.width, target.format.height
438 )));
439 }
440 if self.buffer_handle.storage_id() == target.buffer_handle.storage_id() {
441 return Err(CuError::from(
442 "Depth-map conversion requires distinct source and target storage",
443 ));
444 }
445
446 let convert = |source: &[E::Sample],
447 source_format: CuDepthMapFormat,
448 destination: &mut [F::Sample],
449 destination_format: CuDepthMapFormat| {
450 for y in 0..source_format.height as usize {
451 let source_row = y * source_format.stride as usize;
452 let destination_row = y * destination_format.stride as usize;
453 for x in 0..source_format.width as usize {
454 destination[destination_row + x] =
455 F::encode_sample(E::decode(source[source_row + x]));
456 }
457 }
458 };
459
460 if self.buffer_handle.storage_id() < target.buffer_handle.storage_id() {
461 self.with_samples(|source, source_format| {
462 target.with_samples_mut(|destination, destination_format| {
463 convert(source, source_format, destination, destination_format);
464 });
465 });
466 } else {
467 target.with_samples_mut(|destination, destination_format| {
468 self.with_samples(|source, source_format| {
469 convert(source, source_format, destination, destination_format);
470 });
471 });
472 }
473 Ok(())
474 }
475
476 pub fn with_samples<R>(&self, f: impl FnOnce(&[E::Sample], CuDepthMapFormat) -> R) -> R {
481 let format = self.format;
482 self.buffer_handle
483 .with_inner(|inner| f(&inner[..format.required_elements()], format))
484 }
485
486 pub fn with_samples_mut<R>(
491 &mut self,
492 f: impl FnOnce(&mut [E::Sample], CuDepthMapFormat) -> R,
493 ) -> R {
494 let format = self.format;
495 self.buffer_handle
496 .with_inner_mut(|inner| f(&mut inner[..format.required_elements()], format))
497 }
498
499 pub fn payload_should_log(&self) -> bool {
500 self.buffer_handle.payload_should_log()
501 }
502
503 pub fn apply_handle_content_policy(&self, mode: cu29::pool::HandleContent) {
504 self.buffer_handle.apply_handle_content_policy(mode);
505 }
506
507 pub fn mark_touched(&self) {
508 self.buffer_handle.mark_touched();
509 }
510}
511
512impl<A> CuDepthMap<A, CuDepthLength>
513where
514 A: ArrayLike<Element = Length> + Send + Sync + 'static,
515{
516 pub fn new(format: CuDepthMapFormat, buffer_handle: CuHandle<A>) -> Self {
517 Self::new_encoded(format, buffer_handle)
518 }
519}
520
521impl<A, T, S> CuDepthMap<A, CuDepthInteger<T, S>>
522where
523 T: CuDepthIntegerSample,
524 S: CuDepthScale,
525 A: ArrayLike<Element = T> + Send + Sync + 'static,
526{
527 pub fn from_integer(format: CuDepthMapFormat, buffer_handle: CuHandle<A>) -> Self {
528 Self::new_encoded(format, buffer_handle)
529 }
530}
531
532impl<A, E> cu29::pool::HandleContentAware for CuDepthMap<A, E>
533where
534 E: CuDepthEncoding,
535 A: ArrayLike<Element = E::Sample> + Send + Sync + 'static,
536{
537}
538
539macro_rules! depth_map_serialized_schema {
540 ($sample_schema:literal, $invalid_schema:literal) => {
541 concat!(
542 r#"{"$schema":"https://json-schema.org/draft-07/schema#","type":"object","properties":{
543"format":{"type":"object","properties":{"width":{"type":"integer","minimum":0},"height":{"type":"integer","minimum":0},"stride":{"type":"integer","minimum":0}},"required":["width","height","stride"],"additionalProperties":false},
544"encoding":{"type":"object","properties":{"units_per_meter":{"type":"number"},"invalid":"#,
545 $invalid_schema,
546 r#"},"required":["units_per_meter","invalid"],"additionalProperties":false},
547"handle":{"type":"array","items":"#,
548 $sample_schema,
549 r#"}},"required":["format","encoding","handle"],"additionalProperties":false}"#
550 )
551 };
552}
553
554const LENGTH_DEPTH_MAP_SERIALIZED_SCHEMA: &str =
555 depth_map_serialized_schema!(r#"{"type":"number"}"#, r#"{"type":"null"}"#);
556
557const INTEGER_DEPTH_MAP_SERIALIZED_SCHEMA: &str = depth_map_serialized_schema!(
558 r#"{"type":"integer","minimum":0}"#,
559 r#"{"type":"object","properties":{"unsigned":{"type":"integer","const":0}},"required":["unsigned"],"additionalProperties":false}"#
560);
561
562const GENERIC_DEPTH_MAP_SERIALIZED_SCHEMA: &str = depth_map_serialized_schema!(r#"{}"#, r#"{}"#);
563
564impl<A, E> SerializedPayloadSchema for CuDepthMap<A, E>
565where
566 E: CuDepthEncoding,
567 A: ArrayLike<Element = E::Sample> + Send + Sync + 'static,
568{
569 fn serialized_payload_schema() -> &'static str {
570 E::SERIALIZED_MAP_SCHEMA
571 }
572}
573
574#[cfg(test)]
575mod tests {
576 use super::*;
577 use cu29_value::decode::{ValueDecodeDescription, ValueDecodeLimits};
578
579 type U16MillimeterDepth = CuDepthMap<Vec<u16>, CuDepthInteger<u16, CuDepthMillimeter>>;
580
581 const FORMAT: CuDepthMapFormat = CuDepthMapFormat {
582 width: 3,
583 height: 2,
584 stride: 4,
585 };
586
587 #[test]
588 fn test_catalog_decodes_native_integer_depth() {
589 let sample = U16MillimeterDepth::new_encoded(
590 CuDepthMapFormat {
591 width: 3,
592 height: 1,
593 stride: 3,
594 },
595 CuHandle::new_detached(vec![0u16, 1000, 65535]),
596 );
597 let config = bincode::config::standard();
598 let bytes = bincode::encode_to_vec(&sample, config).unwrap();
599 let description = ValueDecodeDescription::from_type::<U16MillimeterDepth>().unwrap();
600 let (decoded, used) = description
601 .decode(&bytes, config, ValueDecodeLimits::default())
602 .unwrap();
603 assert_eq!(used, bytes.len());
604 let Value::Map(fields) = decoded else {
605 panic!("expected named depth fields")
606 };
607 assert_eq!(
608 fields[&Value::String("buffer_handle".into())],
609 Value::Seq(vec![Value::U16(0), Value::U16(1000), Value::U16(65535)])
610 );
611 }
612
613 #[test]
614 fn padded_depth_map_indexes_distances_in_meters() {
615 let depth = CuDepthMap::new(
616 FORMAT,
617 CuHandle::new_detached(
618 [1.0, 1.5, f32::NAN, 99.0, 2.0, 2.5, 3.0, 99.0]
619 .map(Length::new::<meter>)
620 .to_vec(),
621 ),
622 );
623
624 assert_eq!(depth.get(0, 0), Some(Length::new::<meter>(1.0)));
625 assert_eq!(depth.get_meters(1, 1), Some(2.5));
626 assert!(depth.get_meters(2, 0).expect("in bounds").is_nan());
627 assert_eq!(depth.get(3, 0), None);
628 assert_eq!(depth.get(0, 2), None);
629 }
630
631 #[test]
632 fn sample_slice_covers_the_declared_layout_under_one_access() {
633 let depth = CuDepthMap::new(
634 FORMAT,
635 CuHandle::new_detached(
636 [1.0, 1.5, 2.0, 99.0, 2.5, 3.0, 3.5, 99.0, 100.0]
637 .map(Length::new::<meter>)
638 .to_vec(),
639 ),
640 );
641
642 depth.with_samples(|samples, format| {
643 assert_eq!(format, FORMAT);
644 assert_eq!(samples.len(), FORMAT.required_elements());
645 assert_eq!(samples[format.stride as usize], Length::new::<meter>(2.5));
646 assert_eq!(samples[3], Length::new::<meter>(99.0));
647 });
648 }
649
650 #[test]
651 fn sample_slice_can_be_mutated_under_one_access() {
652 let mut depth = CuDepthMap::new(
653 FORMAT,
654 CuHandle::new_detached(vec![Length::new::<meter>(0.0); FORMAT.required_elements()]),
655 );
656
657 depth.with_samples_mut(|samples, format| {
658 let index = format.stride as usize + 1;
659 samples[index] = Length::new::<meter>(4.25);
660 });
661
662 assert_eq!(depth.get_meters(1, 1), Some(4.25));
663 }
664
665 #[test]
666 fn compact_millimeter_depth_decodes_without_changing_raw_storage() {
667 let depth = U16MillimeterDepth::from_integer(
668 FORMAT,
669 CuHandle::new_detached(vec![1_000, 1_500, 0, 99, 2_000, 2_500, 3_000, 99]),
670 );
671
672 assert_eq!(depth.get_raw(1, 0), Some(1_500));
673 assert_eq!(depth.get_meters(1, 0), Some(1.5));
674 assert_eq!(depth.get_raw(2, 0), Some(0));
675 assert_eq!(depth.get(2, 0), None);
676 depth.with_samples(|samples, format| {
677 assert_eq!(format, FORMAT);
678 assert_eq!(samples, &[1_000, 1_500, 0, 99, 2_000, 2_500, 3_000, 99]);
679 });
680 }
681
682 #[test]
683 fn depth_maps_convert_between_encodings_without_touching_padding() {
684 let source = CuDepthMap::new(
685 FORMAT,
686 CuHandle::new_detached(
687 [1.234, f32::NAN, 2.0, -999.0, 3.5, 4.0, 5.0, -999.0]
688 .map(Length::new::<meter>)
689 .to_vec(),
690 ),
691 );
692 let target_format = CuDepthMapFormat {
693 width: FORMAT.width,
694 height: FORMAT.height,
695 stride: 5,
696 };
697 let mut compact = U16MillimeterDepth::from_integer(
698 target_format,
699 CuHandle::new_detached(vec![777u16; target_format.required_elements()]),
700 );
701
702 source.convert_into(&mut compact).expect("convert to u16");
703 compact.with_samples(|samples, _| {
704 assert_eq!(
705 samples,
706 &[1_234, 0, 2_000, 777, 777, 3_500, 4_000, 5_000, 777, 777]
707 );
708 });
709
710 let mut typed = CuDepthMap::new(
711 FORMAT,
712 CuHandle::new_detached(vec![Length::new::<meter>(-1.0); FORMAT.required_elements()]),
713 );
714 compact.convert_into(&mut typed).expect("convert to Length");
715 assert_eq!(typed.get_meters(0, 0), Some(1.234));
716 assert!(
717 typed
718 .get_raw(1, 0)
719 .expect("in bounds")
720 .get::<meter>()
721 .is_nan()
722 );
723 }
724
725 #[test]
726 fn depth_encodings_have_distinct_type_paths() {
727 assert_eq!(
728 <CuDepthMap<Vec<Length>> as TypePath>::type_path(),
729 "cu_sensor_payloads::CuDepthMap"
730 );
731 assert_eq!(
732 <U16MillimeterDepth as TypePath>::type_path(),
733 "cu_sensor_payloads::CuDepthMapInteger"
734 );
735 }
736
737 #[test]
738 fn integer_depth_supports_all_widths_and_arbitrary_length_scales() {
739 fn assert_integer_sample<T: CuDepthIntegerSample>() {}
740 assert_integer_sample::<u8>();
741 assert_integer_sample::<u16>();
742 assert_integer_sample::<u32>();
743 assert_integer_sample::<u64>();
744
745 type U16DecimeterDepth = CuDepthMap<Vec<u16>, CuDepthInteger<u16, CuDepthDecimeter>>;
746 let format = CuDepthMapFormat {
747 width: 1,
748 height: 1,
749 stride: 1,
750 };
751 let depth = U16DecimeterDepth::from_integer(format, CuHandle::new_detached(vec![2_000]));
752
753 assert_eq!(CuDepthDecimeter::RESOLUTION, Length::new::<meter>(0.1));
754 assert_eq!(
755 U16DecimeterDepth::encoding_descriptor().units_per_meter,
756 10.0
757 );
758 assert_eq!(depth.get_meters(0, 0), Some(200.0));
759 }
760
761 #[test]
762 fn format_rejects_short_stride() {
763 assert!(
764 !CuDepthMapFormat {
765 width: 4,
766 height: 2,
767 stride: 3,
768 }
769 .is_valid()
770 );
771 }
772
773 #[test]
774 fn serde_wire_uses_handle_field() {
775 let depth = CuDepthMap::new(
776 FORMAT,
777 CuHandle::new_detached(
778 [1.0, 1.5, 2.0, 0.0, 2.5, 3.0, 3.5, 0.0]
779 .map(Length::new::<meter>)
780 .to_vec(),
781 ),
782 );
783 let value = serde_json::to_value(depth).expect("serialize depth");
784 assert_eq!(value["format"]["stride"], 4);
785 assert_eq!(value["encoding"]["units_per_meter"], 1.0);
786 assert!(value["encoding"]["invalid"].is_null());
787 assert!(value.get("handle").is_some());
788 assert!(value.get("buffer_handle").is_none());
789 assert!(value.get("seq").is_none());
790 }
791
792 #[test]
793 fn compact_serde_wire_describes_units_and_invalid_value() {
794 let depth = U16MillimeterDepth::from_integer(
795 FORMAT,
796 CuHandle::new_detached(vec![0u16; FORMAT.required_elements()]),
797 );
798 let value = serde_json::to_value(depth).expect("serialize compact depth");
799
800 assert_eq!(value["encoding"]["units_per_meter"], 1_000.0);
801 assert_eq!(value["encoding"]["invalid"]["unsigned"], 0);
802 }
803
804 #[test]
805 fn compact_serialized_schema_matches_depth_wire_fields() {
806 let schema = serde_json::from_str::<serde_json::Value>(
807 U16MillimeterDepth::serialized_payload_schema(),
808 )
809 .expect("parse compact depth schema");
810
811 assert_eq!(schema["properties"]["handle"]["items"]["type"], "integer");
812 assert_eq!(
813 schema["properties"]["encoding"]["properties"]["invalid"]["properties"]["unsigned"]["const"],
814 0
815 );
816 assert_eq!(
817 schema["required"],
818 serde_json::json!(["format", "encoding", "handle"])
819 );
820 }
821}