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)]
19pub struct CuDepthMapFormat {
20 pub width: u32,
21 pub height: u32,
22 pub stride: u32,
23}
24
25impl CuDepthMapFormat {
26 pub fn is_valid(&self) -> bool {
27 self.width <= self.stride
28 }
29
30 pub fn required_elements(&self) -> usize {
31 self.stride as usize * self.height as usize
32 }
33}
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Reflect)]
37#[serde(rename_all = "snake_case")]
38pub enum CuDepthInvalidValue {
39 Unsigned(u64),
40 Signed(i64),
41 FloatBits(u64),
42}
43
44#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Reflect)]
46pub struct CuDepthEncodingDescriptor {
47 pub units_per_meter: f32,
49 pub invalid: Option<CuDepthInvalidValue>,
51}
52
53pub trait CuDepthEncoding: Copy + Debug + Default + Send + Sync + 'static {
55 type Sample: ElementType;
56
57 const DESCRIPTOR: CuDepthEncodingDescriptor;
58 const TYPE_PATH: &'static str;
59 const SHORT_TYPE_PATH: &'static str;
60 const TYPE_IDENT: &'static str;
61
62 fn decode(sample: Self::Sample) -> Option<Length>;
66
67 fn encode_sample(sample: Option<Length>) -> Self::Sample;
71}
72
73#[derive(Debug, Default, Clone, Copy)]
75pub struct CuDepthLength;
76
77impl CuDepthEncoding for CuDepthLength {
78 type Sample = Length;
79
80 const DESCRIPTOR: CuDepthEncodingDescriptor = CuDepthEncodingDescriptor {
81 units_per_meter: 1.0,
82 invalid: None,
83 };
84 const TYPE_PATH: &'static str = "cu_sensor_payloads::CuDepthMap";
85 const SHORT_TYPE_PATH: &'static str = "CuDepthMap";
86 const TYPE_IDENT: &'static str = "CuDepthMap";
87
88 fn decode(sample: Self::Sample) -> Option<Length> {
89 Some(sample)
90 }
91
92 fn encode_sample(sample: Option<Length>) -> Self::Sample {
93 sample.unwrap_or_else(|| Length::new::<meter>(f32::NAN))
94 }
95}
96
97pub const fn depth_resolution_meters(meters: f32) -> Length {
102 Length { value: meters }
103}
104
105pub trait CuDepthScale: Copy + Debug + Default + Send + Sync + 'static {
107 const RESOLUTION: Length;
108 const UNITS_PER_METER: f32 = 1.0 / Self::RESOLUTION.value;
109}
110
111#[derive(Debug, Default, Clone, Copy)]
116pub struct CuDepthScaleRatio<const NUMERATOR: u64, const DENOMINATOR: u64>;
117
118impl<const NUMERATOR: u64, const DENOMINATOR: u64> CuDepthScale
119 for CuDepthScaleRatio<NUMERATOR, DENOMINATOR>
120{
121 const RESOLUTION: Length = depth_resolution_meters(NUMERATOR as f32 / DENOMINATOR as f32);
122 const UNITS_PER_METER: f32 = DENOMINATOR as f32 / NUMERATOR as f32;
123}
124
125pub type CuDepthMillimeter = CuDepthScaleRatio<1, 1_000>;
127
128pub type CuDepthDecimeter = CuDepthScaleRatio<1, 10>;
130
131pub trait CuDepthIntegerSample: ElementType + 'static {
133 const MAX: u64;
134
135 fn to_u64(self) -> u64;
136 fn from_u64(value: u64) -> Self;
137}
138
139macro_rules! impl_depth_integer_sample {
140 ($($sample:ty),+ $(,)?) => {
141 $(
142 impl CuDepthIntegerSample for $sample {
143 const MAX: u64 = <$sample>::MAX as u64;
144
145 fn to_u64(self) -> u64 {
146 self as u64
147 }
148
149 fn from_u64(value: u64) -> Self {
150 value as Self
151 }
152 }
153 )+
154 };
155}
156
157impl_depth_integer_sample!(u8, u16, u32, u64);
158
159#[derive(Debug, Default, Clone, Copy)]
176pub struct CuDepthInteger<T, S>(PhantomData<(T, S)>);
177
178impl<T, S> CuDepthEncoding for CuDepthInteger<T, S>
179where
180 T: CuDepthIntegerSample,
181 S: CuDepthScale,
182{
183 type Sample = T;
184
185 const DESCRIPTOR: CuDepthEncodingDescriptor = CuDepthEncodingDescriptor {
186 units_per_meter: S::UNITS_PER_METER,
187 invalid: Some(CuDepthInvalidValue::Unsigned(0)),
188 };
189 const TYPE_PATH: &'static str = "cu_sensor_payloads::CuDepthMapInteger";
190 const SHORT_TYPE_PATH: &'static str = "CuDepthMapInteger";
191 const TYPE_IDENT: &'static str = "CuDepthMapInteger";
192
193 fn decode(sample: Self::Sample) -> Option<Length> {
194 let raw = sample.to_u64();
195 (raw != 0).then(|| depth_resolution_meters(raw as f32 / S::UNITS_PER_METER))
196 }
197
198 fn encode_sample(sample: Option<Length>) -> Self::Sample {
199 let Some(meters) = sample.map(|sample| sample.get::<meter>()) else {
200 return T::from_u64(0);
201 };
202 if !meters.is_finite() || meters <= 0.0 {
203 return T::from_u64(0);
204 }
205 let raw = (meters * S::UNITS_PER_METER)
206 .round()
207 .clamp(1.0, T::MAX as f32) as u64;
208 T::from_u64(raw)
209 }
210}
211
212#[derive(Debug, Default, Clone, Reflect)]
218#[reflect(from_reflect = false, no_field_bounds, type_path = false)]
219pub struct CuDepthMap<A, E = CuDepthLength>
220where
221 E: CuDepthEncoding,
222 A: ArrayLike<Element = E::Sample> + Send + Sync + 'static,
223{
224 pub format: CuDepthMapFormat,
225 #[reflect(ignore)]
226 pub buffer_handle: CuHandle<A>,
227 #[reflect(ignore)]
228 encoding: PhantomData<E>,
229}
230
231impl<A, E> TypePath for CuDepthMap<A, E>
232where
233 E: CuDepthEncoding,
234 A: ArrayLike<Element = E::Sample> + Send + Sync + 'static,
235{
236 fn type_path() -> &'static str {
237 E::TYPE_PATH
238 }
239
240 fn short_type_path() -> &'static str {
241 E::SHORT_TYPE_PATH
242 }
243
244 fn type_ident() -> Option<&'static str> {
245 Some(E::TYPE_IDENT)
246 }
247
248 fn crate_name() -> Option<&'static str> {
249 Some("cu_sensor_payloads")
250 }
251
252 fn module_path() -> Option<&'static str> {
253 Some("cu_sensor_payloads")
254 }
255}
256
257impl<A, E> Encode for CuDepthMap<A, E>
258where
259 E: CuDepthEncoding,
260 A: ArrayLike<Element = E::Sample> + Send + Sync + 'static,
261 CuHandle<A>: Encode,
262{
263 fn encode<Enc: Encoder>(&self, encoder: &mut Enc) -> Result<(), EncodeError> {
264 Encode::encode(&self.format, encoder)?;
265 Encode::encode(&self.buffer_handle, encoder)
266 }
267}
268
269impl<A, E> Decode<()> for CuDepthMap<A, E>
270where
271 E: CuDepthEncoding,
272 A: ArrayLike<Element = E::Sample> + Send + Sync + 'static,
273 CuHandle<A>: Decode<()>,
274{
275 fn decode<D: Decoder<Context = ()>>(decoder: &mut D) -> Result<Self, DecodeError> {
276 Ok(Self {
277 format: Decode::decode(decoder)?,
278 buffer_handle: Decode::decode(decoder)?,
279 encoding: PhantomData,
280 })
281 }
282}
283
284impl<'de, A, E> Deserialize<'de> for CuDepthMap<A, E>
285where
286 E: CuDepthEncoding,
287 A: ArrayLike<Element = E::Sample> + Send + Sync + 'static,
288 CuHandle<A>: Deserialize<'de>,
289{
290 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
291 where
292 D: serde::Deserializer<'de>,
293 {
294 #[derive(Deserialize)]
295 struct Wire<H> {
296 format: CuDepthMapFormat,
297 encoding: Option<CuDepthEncodingDescriptor>,
298 handle: H,
299 }
300
301 let wire = Wire::<CuHandle<A>>::deserialize(deserializer)?;
302 if let Some(encoding) = wire.encoding
303 && encoding != E::DESCRIPTOR
304 {
305 return Err(serde::de::Error::custom(format!(
306 "Depth encoding {:?} does not match {}",
307 encoding,
308 E::SHORT_TYPE_PATH
309 )));
310 }
311 Ok(Self {
312 format: wire.format,
313 buffer_handle: wire.handle,
314 encoding: PhantomData,
315 })
316 }
317}
318
319impl<A, E> Serialize for CuDepthMap<A, E>
320where
321 E: CuDepthEncoding,
322 A: ArrayLike<Element = E::Sample> + Send + Sync + 'static,
323 CuHandle<A>: Serialize,
324{
325 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
326 where
327 S: Serializer,
328 {
329 use serde::ser::SerializeStruct;
330 let mut state = serializer.serialize_struct(E::SHORT_TYPE_PATH, 3)?;
331 state.serialize_field("format", &self.format)?;
332 state.serialize_field("encoding", &E::DESCRIPTOR)?;
333 state.serialize_field("handle", &self.buffer_handle)?;
334 state.end()
335 }
336}
337
338impl<A, E> CuDepthMap<A, E>
339where
340 E: CuDepthEncoding,
341 A: ArrayLike<Element = E::Sample> + Send + Sync + 'static,
342{
343 pub const fn encoding_descriptor() -> CuDepthEncodingDescriptor {
344 E::DESCRIPTOR
345 }
346
347 pub fn new_encoded(format: CuDepthMapFormat, buffer_handle: CuHandle<A>) -> Self {
348 assert!(
349 E::DESCRIPTOR.units_per_meter.is_finite() && E::DESCRIPTOR.units_per_meter > 0.0,
350 "Depth encoding must have a finite positive resolution."
351 );
352 assert!(
353 format.is_valid(),
354 "Depth-map stride must be at least its width."
355 );
356 assert!(
357 format.required_elements() <= buffer_handle.with_inner(|inner| inner.len()),
358 "Buffer size must at least match the depth-map format."
359 );
360 Self {
361 format,
362 buffer_handle,
363 encoding: PhantomData,
364 }
365 }
366
367 pub fn get_raw(&self, x: u32, y: u32) -> Option<E::Sample> {
369 if x >= self.format.width || y >= self.format.height {
370 return None;
371 }
372 let index = y as usize * self.format.stride as usize + x as usize;
373 self.buffer_handle
374 .with_inner(|inner| inner.as_ref().get(index).copied())
375 }
376
377 pub fn decode_sample(sample: E::Sample) -> Option<Length> {
379 E::decode(sample)
380 }
381
382 pub fn get(&self, x: u32, y: u32) -> Option<Length> {
386 Self::decode_sample(self.get_raw(x, y)?)
387 }
388
389 pub fn get_meters(&self, x: u32, y: u32) -> Option<f32> {
391 self.get(x, y).map(|depth| depth.get::<meter>())
392 }
393
394 pub fn convert_into<B, F>(&self, target: &mut CuDepthMap<B, F>) -> CuResult<()>
399 where
400 F: CuDepthEncoding,
401 B: ArrayLike<Element = F::Sample> + Send + Sync + 'static,
402 {
403 if self.format.width != target.format.width || self.format.height != target.format.height {
404 return Err(CuError::from(format!(
405 "Depth-map dimensions differ: source is {}x{}, target is {}x{}",
406 self.format.width, self.format.height, target.format.width, target.format.height
407 )));
408 }
409 if self.buffer_handle.storage_id() == target.buffer_handle.storage_id() {
410 return Err(CuError::from(
411 "Depth-map conversion requires distinct source and target storage",
412 ));
413 }
414
415 let convert = |source: &[E::Sample],
416 source_format: CuDepthMapFormat,
417 destination: &mut [F::Sample],
418 destination_format: CuDepthMapFormat| {
419 for y in 0..source_format.height as usize {
420 let source_row = y * source_format.stride as usize;
421 let destination_row = y * destination_format.stride as usize;
422 for x in 0..source_format.width as usize {
423 destination[destination_row + x] =
424 F::encode_sample(E::decode(source[source_row + x]));
425 }
426 }
427 };
428
429 if self.buffer_handle.storage_id() < target.buffer_handle.storage_id() {
430 self.with_samples(|source, source_format| {
431 target.with_samples_mut(|destination, destination_format| {
432 convert(source, source_format, destination, destination_format);
433 });
434 });
435 } else {
436 target.with_samples_mut(|destination, destination_format| {
437 self.with_samples(|source, source_format| {
438 convert(source, source_format, destination, destination_format);
439 });
440 });
441 }
442 Ok(())
443 }
444
445 pub fn with_samples<R>(&self, f: impl FnOnce(&[E::Sample], CuDepthMapFormat) -> R) -> R {
450 let format = self.format;
451 self.buffer_handle
452 .with_inner(|inner| f(&inner[..format.required_elements()], format))
453 }
454
455 pub fn with_samples_mut<R>(
460 &mut self,
461 f: impl FnOnce(&mut [E::Sample], CuDepthMapFormat) -> R,
462 ) -> R {
463 let format = self.format;
464 self.buffer_handle
465 .with_inner_mut(|inner| f(&mut inner[..format.required_elements()], format))
466 }
467
468 pub fn payload_should_log(&self) -> bool {
469 self.buffer_handle.payload_should_log()
470 }
471
472 pub fn apply_handle_content_policy(&self, mode: cu29::pool::HandleContent) {
473 self.buffer_handle.apply_handle_content_policy(mode);
474 }
475
476 pub fn mark_touched(&self) {
477 self.buffer_handle.mark_touched();
478 }
479}
480
481impl<A> CuDepthMap<A, CuDepthLength>
482where
483 A: ArrayLike<Element = Length> + Send + Sync + 'static,
484{
485 pub fn new(format: CuDepthMapFormat, buffer_handle: CuHandle<A>) -> Self {
486 Self::new_encoded(format, buffer_handle)
487 }
488}
489
490impl<A, T, S> CuDepthMap<A, CuDepthInteger<T, S>>
491where
492 T: CuDepthIntegerSample,
493 S: CuDepthScale,
494 A: ArrayLike<Element = T> + Send + Sync + 'static,
495{
496 pub fn from_integer(format: CuDepthMapFormat, buffer_handle: CuHandle<A>) -> Self {
497 Self::new_encoded(format, buffer_handle)
498 }
499}
500
501impl<A, E> cu29::pool::HandleContentAware for CuDepthMap<A, E>
502where
503 E: CuDepthEncoding,
504 A: ArrayLike<Element = E::Sample> + Send + Sync + 'static,
505{
506}
507
508#[cfg(test)]
509mod tests {
510 use super::*;
511
512 type U16MillimeterDepth = CuDepthMap<Vec<u16>, CuDepthInteger<u16, CuDepthMillimeter>>;
513
514 const FORMAT: CuDepthMapFormat = CuDepthMapFormat {
515 width: 3,
516 height: 2,
517 stride: 4,
518 };
519
520 #[test]
521 fn padded_depth_map_indexes_distances_in_meters() {
522 let depth = CuDepthMap::new(
523 FORMAT,
524 CuHandle::new_detached(
525 [1.0, 1.5, f32::NAN, 99.0, 2.0, 2.5, 3.0, 99.0]
526 .map(Length::new::<meter>)
527 .to_vec(),
528 ),
529 );
530
531 assert_eq!(depth.get(0, 0), Some(Length::new::<meter>(1.0)));
532 assert_eq!(depth.get_meters(1, 1), Some(2.5));
533 assert!(depth.get_meters(2, 0).expect("in bounds").is_nan());
534 assert_eq!(depth.get(3, 0), None);
535 assert_eq!(depth.get(0, 2), None);
536 }
537
538 #[test]
539 fn sample_slice_covers_the_declared_layout_under_one_access() {
540 let depth = CuDepthMap::new(
541 FORMAT,
542 CuHandle::new_detached(
543 [1.0, 1.5, 2.0, 99.0, 2.5, 3.0, 3.5, 99.0, 100.0]
544 .map(Length::new::<meter>)
545 .to_vec(),
546 ),
547 );
548
549 depth.with_samples(|samples, format| {
550 assert_eq!(format, FORMAT);
551 assert_eq!(samples.len(), FORMAT.required_elements());
552 assert_eq!(samples[format.stride as usize], Length::new::<meter>(2.5));
553 assert_eq!(samples[3], Length::new::<meter>(99.0));
554 });
555 }
556
557 #[test]
558 fn sample_slice_can_be_mutated_under_one_access() {
559 let mut depth = CuDepthMap::new(
560 FORMAT,
561 CuHandle::new_detached(vec![Length::new::<meter>(0.0); FORMAT.required_elements()]),
562 );
563
564 depth.with_samples_mut(|samples, format| {
565 let index = format.stride as usize + 1;
566 samples[index] = Length::new::<meter>(4.25);
567 });
568
569 assert_eq!(depth.get_meters(1, 1), Some(4.25));
570 }
571
572 #[test]
573 fn compact_millimeter_depth_decodes_without_changing_raw_storage() {
574 let depth = U16MillimeterDepth::from_integer(
575 FORMAT,
576 CuHandle::new_detached(vec![1_000, 1_500, 0, 99, 2_000, 2_500, 3_000, 99]),
577 );
578
579 assert_eq!(depth.get_raw(1, 0), Some(1_500));
580 assert_eq!(depth.get_meters(1, 0), Some(1.5));
581 assert_eq!(depth.get_raw(2, 0), Some(0));
582 assert_eq!(depth.get(2, 0), None);
583 depth.with_samples(|samples, format| {
584 assert_eq!(format, FORMAT);
585 assert_eq!(samples, &[1_000, 1_500, 0, 99, 2_000, 2_500, 3_000, 99]);
586 });
587 }
588
589 #[test]
590 fn depth_maps_convert_between_encodings_without_touching_padding() {
591 let source = CuDepthMap::new(
592 FORMAT,
593 CuHandle::new_detached(
594 [1.234, f32::NAN, 2.0, -999.0, 3.5, 4.0, 5.0, -999.0]
595 .map(Length::new::<meter>)
596 .to_vec(),
597 ),
598 );
599 let target_format = CuDepthMapFormat {
600 width: FORMAT.width,
601 height: FORMAT.height,
602 stride: 5,
603 };
604 let mut compact = U16MillimeterDepth::from_integer(
605 target_format,
606 CuHandle::new_detached(vec![777u16; target_format.required_elements()]),
607 );
608
609 source.convert_into(&mut compact).expect("convert to u16");
610 compact.with_samples(|samples, _| {
611 assert_eq!(
612 samples,
613 &[1_234, 0, 2_000, 777, 777, 3_500, 4_000, 5_000, 777, 777]
614 );
615 });
616
617 let mut typed = CuDepthMap::new(
618 FORMAT,
619 CuHandle::new_detached(vec![Length::new::<meter>(-1.0); FORMAT.required_elements()]),
620 );
621 compact.convert_into(&mut typed).expect("convert to Length");
622 assert_eq!(typed.get_meters(0, 0), Some(1.234));
623 assert!(
624 typed
625 .get_raw(1, 0)
626 .expect("in bounds")
627 .get::<meter>()
628 .is_nan()
629 );
630 }
631
632 #[test]
633 fn depth_encodings_have_distinct_type_paths() {
634 assert_eq!(
635 <CuDepthMap<Vec<Length>> as TypePath>::type_path(),
636 "cu_sensor_payloads::CuDepthMap"
637 );
638 assert_eq!(
639 <U16MillimeterDepth as TypePath>::type_path(),
640 "cu_sensor_payloads::CuDepthMapInteger"
641 );
642 }
643
644 #[test]
645 fn integer_depth_supports_all_widths_and_arbitrary_length_scales() {
646 fn assert_integer_sample<T: CuDepthIntegerSample>() {}
647 assert_integer_sample::<u8>();
648 assert_integer_sample::<u16>();
649 assert_integer_sample::<u32>();
650 assert_integer_sample::<u64>();
651
652 type U16DecimeterDepth = CuDepthMap<Vec<u16>, CuDepthInteger<u16, CuDepthDecimeter>>;
653 let format = CuDepthMapFormat {
654 width: 1,
655 height: 1,
656 stride: 1,
657 };
658 let depth = U16DecimeterDepth::from_integer(format, CuHandle::new_detached(vec![2_000]));
659
660 assert_eq!(CuDepthDecimeter::RESOLUTION, Length::new::<meter>(0.1));
661 assert_eq!(
662 U16DecimeterDepth::encoding_descriptor().units_per_meter,
663 10.0
664 );
665 assert_eq!(depth.get_meters(0, 0), Some(200.0));
666 }
667
668 #[test]
669 fn format_rejects_short_stride() {
670 assert!(
671 !CuDepthMapFormat {
672 width: 4,
673 height: 2,
674 stride: 3,
675 }
676 .is_valid()
677 );
678 }
679
680 #[test]
681 fn serde_wire_uses_handle_field() {
682 let depth = CuDepthMap::new(
683 FORMAT,
684 CuHandle::new_detached(
685 [1.0, 1.5, 2.0, 0.0, 2.5, 3.0, 3.5, 0.0]
686 .map(Length::new::<meter>)
687 .to_vec(),
688 ),
689 );
690 let value = serde_json::to_value(depth).expect("serialize depth");
691 assert_eq!(value["format"]["stride"], 4);
692 assert_eq!(value["encoding"]["units_per_meter"], 1.0);
693 assert!(value["encoding"]["invalid"].is_null());
694 assert!(value.get("handle").is_some());
695 assert!(value.get("buffer_handle").is_none());
696 assert!(value.get("seq").is_none());
697 }
698
699 #[test]
700 fn compact_serde_wire_describes_units_and_invalid_value() {
701 let depth = U16MillimeterDepth::from_integer(
702 FORMAT,
703 CuHandle::new_detached(vec![0u16; FORMAT.required_elements()]),
704 );
705 let value = serde_json::to_value(depth).expect("serialize compact depth");
706
707 assert_eq!(value["encoding"]["units_per_meter"], 1_000.0);
708 assert_eq!(value["encoding"]["invalid"]["unsigned"], 0);
709 }
710}