Skip to main content

lerc_reader/
types.rs

1use std::any::TypeId;
2
3use crate::materialize::{copy_band_values_into_slice, BandMaterializer};
4use lerc_core::{BandLayout, BandSetInfo, BlobInfo, Error, PixelData, Result};
5#[cfg(feature = "ndarray")]
6use ndarray::{ArrayD, IxDyn};
7
8#[cfg(feature = "ndarray")]
9use crate::allocation::{checked_mul, default_vec, vec_with_capacity};
10
11/// Native-typed pixels, validated metadata, and an optional validity mask for one blob.
12#[derive(Debug, Clone, PartialEq)]
13pub struct Decoded {
14    /// Validated blob metadata.
15    pub info: BlobInfo,
16    /// Pixel-interleaved samples in the blob's native numeric type.
17    pub pixels: PixelData,
18    /// Row-major validity bytes, when the blob has a mask.
19    pub mask: Option<Vec<u8>>,
20}
21
22impl Decoded {
23    #[cfg(feature = "ndarray")]
24    /// Borrows this result and copies its pixels into an ndarray.
25    ///
26    /// # Errors
27    /// Returns an error if `T` differs from the native type or the shape is invalid.
28    pub fn to_ndarray<T: NdArrayElement>(&self) -> Result<ArrayD<T>> {
29        self.pixels.clone().into_ndarray(&self.info.ndarray_shape())
30    }
31
32    #[cfg(feature = "ndarray")]
33    /// Consumes this result and moves its pixels into an ndarray.
34    ///
35    /// # Errors
36    /// Returns an error if `T` differs from the native type or the shape is invalid.
37    pub fn into_ndarray<T: NdArrayElement>(self) -> Result<ArrayD<T>> {
38        let shape = self.info.ndarray_shape();
39        self.pixels.into_ndarray(&shape)
40    }
41
42    #[cfg(feature = "ndarray")]
43    /// Consumes this result and moves its optional mask into an ndarray.
44    ///
45    /// # Errors
46    /// Returns an error if decoded metadata and mask length disagree.
47    pub fn into_mask_ndarray(self) -> Result<Option<ArrayD<u8>>> {
48        let shape = self.info.mask_ndarray_shape();
49        self.mask
50            .map(|mask| {
51                ArrayD::from_shape_vec(IxDyn(&shape), mask).map_err(|err| {
52                    Error::invalid_blob(format!("failed to build ndarray from decoded mask: {err}"))
53                })
54            })
55            .transpose()
56    }
57
58    #[cfg(feature = "ndarray")]
59    /// Copies the optional validity mask into an ndarray.
60    ///
61    /// # Errors
62    /// Returns an error if decoded metadata and mask length disagree.
63    pub fn mask_ndarray(&self) -> Result<Option<ArrayD<u8>>> {
64        let shape = self.info.mask_ndarray_shape();
65        self.mask
66            .as_ref()
67            .map(|mask| {
68                ArrayD::from_shape_vec(IxDyn(&shape), mask.clone()).map_err(|err| {
69                    Error::invalid_blob(format!("failed to build ndarray from decoded mask: {err}"))
70                })
71            })
72            .transpose()
73    }
74}
75
76/// Promoted `f64` pixels, validated metadata, and an optional mask for one blob.
77#[derive(Debug, Clone, PartialEq)]
78pub struct DecodedF64 {
79    /// Validated blob metadata.
80    pub info: BlobInfo,
81    /// Pixel-interleaved samples promoted to `f64`.
82    pub pixels: Vec<f64>,
83    /// Row-major validity bytes, when present.
84    pub mask: Option<Vec<u8>>,
85}
86
87impl DecodedF64 {
88    #[cfg(feature = "ndarray")]
89    /// Copies the promoted pixels into an ndarray.
90    ///
91    /// # Errors
92    /// Returns an error if metadata and sample length disagree.
93    pub fn to_ndarray(&self) -> Result<ArrayD<f64>> {
94        ArrayD::from_shape_vec(IxDyn(&self.info.ndarray_shape()), self.pixels.clone()).map_err(
95            |err| {
96                Error::invalid_blob(format!(
97                    "failed to build ndarray from decoded pixels: {err}"
98                ))
99            },
100        )
101    }
102
103    #[cfg(feature = "ndarray")]
104    /// Moves the promoted pixels into an ndarray.
105    ///
106    /// # Errors
107    /// Returns an error if metadata and sample length disagree.
108    pub fn into_ndarray(self) -> Result<ArrayD<f64>> {
109        ArrayD::from_shape_vec(IxDyn(&self.info.ndarray_shape()), self.pixels).map_err(|err| {
110            Error::invalid_blob(format!(
111                "failed to build ndarray from decoded pixels: {err}"
112            ))
113        })
114    }
115
116    #[cfg(feature = "ndarray")]
117    /// Moves the optional validity mask into an ndarray.
118    ///
119    /// # Errors
120    /// Returns an error if metadata and mask length disagree.
121    pub fn into_mask_ndarray(self) -> Result<Option<ArrayD<u8>>> {
122        let shape = self.info.mask_ndarray_shape();
123        self.mask
124            .map(|mask| {
125                ArrayD::from_shape_vec(IxDyn(&shape), mask).map_err(|err| {
126                    Error::invalid_blob(format!("failed to build ndarray from decoded mask: {err}"))
127                })
128            })
129            .transpose()
130    }
131
132    #[cfg(feature = "ndarray")]
133    /// Copies the optional validity mask into an ndarray.
134    ///
135    /// # Errors
136    /// Returns an error if metadata and mask length disagree.
137    pub fn mask_ndarray(&self) -> Result<Option<ArrayD<u8>>> {
138        let shape = self.info.mask_ndarray_shape();
139        self.mask
140            .as_ref()
141            .map(|mask| {
142                ArrayD::from_shape_vec(IxDyn(&shape), mask.clone()).map_err(|err| {
143                    Error::invalid_blob(format!("failed to build ndarray from decoded mask: {err}"))
144                })
145            })
146            .transpose()
147    }
148}
149
150/// Native-typed bands, per-band masks, and shared band-set metadata.
151#[derive(Debug, Clone, PartialEq)]
152pub struct DecodedBandSet {
153    /// Validated metadata for every band.
154    info: BandSetInfo,
155    /// Native-typed pixel buffers in band order.
156    bands: Vec<PixelData>,
157    /// Logical mask for each band, including inherited masks.
158    band_masks: Vec<Option<Vec<u8>>>,
159}
160
161impl DecodedBandSet {
162    pub(crate) fn new(
163        info: BandSetInfo,
164        bands: Vec<PixelData>,
165        band_masks: Vec<Option<Vec<u8>>>,
166    ) -> Result<Self> {
167        validate_band_set_parts(&info, &bands, &band_masks)?;
168        Ok(Self {
169            info,
170            bands,
171            band_masks,
172        })
173    }
174
175    /// Returns validated metadata for the decoded bands.
176    pub fn info(&self) -> &BandSetInfo {
177        &self.info
178    }
179
180    /// Returns native-typed pixel buffers in band order.
181    pub fn bands(&self) -> &[PixelData] {
182        &self.bands
183    }
184
185    /// Returns logical per-band masks, including inherited masks.
186    pub fn band_masks(&self) -> &[Option<Vec<u8>>] {
187        &self.band_masks
188    }
189
190    /// Consumes the result and returns its validated metadata, bands, and masks.
191    pub fn into_parts(self) -> (BandSetInfo, Vec<PixelData>, Vec<Option<Vec<u8>>>) {
192        (self.info, self.bands, self.band_masks)
193    }
194
195    #[cfg(feature = "ndarray")]
196    /// Copies the band set into an interleaved ndarray.
197    ///
198    /// # Errors
199    /// Returns an error for incompatible types or inconsistent shapes.
200    pub fn to_ndarray<T: BandElement>(&self) -> Result<ArrayD<T>> {
201        self.clone().into_ndarray()
202    }
203
204    #[cfg(feature = "ndarray")]
205    /// Moves the band set into an interleaved ndarray.
206    ///
207    /// # Errors
208    /// Returns an error for incompatible types or inconsistent shapes.
209    pub fn into_ndarray<T: BandElement>(self) -> Result<ArrayD<T>> {
210        self.into_ndarray_with_layout(BandLayout::Interleaved)
211    }
212
213    #[cfg(feature = "ndarray")]
214    /// Moves the band set into an ndarray using the requested layout.
215    ///
216    /// # Errors
217    /// Returns an error for incompatible types or inconsistent shapes.
218    pub fn into_ndarray_with_layout<T: BandElement>(self, layout: BandLayout) -> Result<ArrayD<T>> {
219        let shape = self.info.ndarray_shape_for_layout(layout);
220        let values = self.into_vec_with_layout(layout)?;
221        ArrayD::from_shape_vec(IxDyn(&shape), values).map_err(|err| {
222            Error::invalid_blob(format!(
223                "failed to build ndarray from decoded band set: {err}"
224            ))
225        })
226    }
227
228    /// Materializes every band as one homogeneous vector in `layout`.
229    ///
230    /// # Errors
231    /// Returns an error when band types cannot convert to `T` or metadata is inconsistent.
232    pub fn into_vec_with_layout<T: BandElement>(self, layout: BandLayout) -> Result<Vec<T>> {
233        if self.bands.len() == 1 {
234            let band = self
235                .bands
236                .into_iter()
237                .next()
238                .ok_or(Error::Internal("single-band decode lost its only band"))?;
239            return T::from_pixel_data(band);
240        }
241
242        let mut materializer = BandMaterializer::new(
243            self.info.first().pixel_count()?,
244            self.info.depth() as usize,
245            self.info.band_count(),
246            layout,
247        )?;
248        for (band_index, band) in self.bands.into_iter().enumerate() {
249            copy_pixel_data_into_materializer(&mut materializer, band_index, band)?;
250        }
251        materializer.finish()
252    }
253
254    /// Copies every band into an exactly sized caller-provided slice.
255    ///
256    /// # Errors
257    /// Returns an error for the wrong output length, incompatible types, or bad metadata.
258    pub fn copy_into_slice<T: BandElement>(self, layout: BandLayout, out: &mut [T]) -> Result<()> {
259        let pixel_count = self.info.first().pixel_count()?;
260        let depth = self.info.depth() as usize;
261        let band_count = self.info.band_count();
262        let expected_len = self.info.value_count()?;
263        if out.len() != expected_len {
264            return Err(Error::InvalidArgument(
265                "output slice length does not match decoded band set length",
266            ));
267        }
268
269        for (band_index, band) in self.bands.into_iter().enumerate() {
270            copy_pixel_data_into_layout_slice(
271                out,
272                band_index,
273                pixel_count,
274                depth,
275                band_count,
276                layout,
277                band,
278            )?;
279        }
280        Ok(())
281    }
282
283    #[cfg(feature = "ndarray")]
284    /// Moves per-band masks into an ndarray.
285    ///
286    /// # Errors
287    /// Returns an error if mask metadata and lengths disagree.
288    pub fn into_band_mask_ndarray(self) -> Result<Option<ArrayD<u8>>> {
289        band_masks_into_ndarray(self.info, self.band_masks)
290    }
291
292    #[cfg(feature = "ndarray")]
293    /// Copies per-band masks into an ndarray.
294    ///
295    /// # Errors
296    /// Returns an error if mask metadata and lengths disagree.
297    pub fn band_mask_ndarray(&self) -> Result<Option<ArrayD<u8>>> {
298        band_masks_into_ndarray(self.info.clone(), self.band_masks.clone())
299    }
300}
301
302fn validate_band_set_parts(
303    info: &BandSetInfo,
304    bands: &[PixelData],
305    band_masks: &[Option<Vec<u8>>],
306) -> Result<()> {
307    if bands.len() != info.band_count() || band_masks.len() != info.band_count() {
308        return Err(Error::invalid_blob(
309            "decoded band and mask counts do not match band-set metadata",
310        ));
311    }
312    for ((band_info, band), mask) in info.bands().iter().zip(bands).zip(band_masks) {
313        if band.data_type() != band_info.data_type {
314            return Err(Error::invalid_blob(
315                "decoded band type does not match its metadata",
316            ));
317        }
318        if band.len() != band_info.sample_count()? {
319            return Err(Error::invalid_blob(
320                "decoded band length does not match its metadata",
321            ));
322        }
323        match mask {
324            Some(mask) => {
325                if mask.len() != band_info.pixel_count()? {
326                    return Err(Error::invalid_blob(
327                        "decoded band mask length does not match its metadata",
328                    ));
329                }
330                let valid_pixel_count = mask.iter().filter(|&&value| value != 0).count();
331                if valid_pixel_count != band_info.valid_pixel_count as usize {
332                    return Err(Error::invalid_blob(
333                        "decoded band mask count does not match its metadata",
334                    ));
335                }
336            }
337            None if band_info.valid_pixel_count as usize != band_info.pixel_count()? => {
338                return Err(Error::invalid_blob(
339                    "decoded band is missing the mask required by its metadata",
340                ));
341            }
342            None => {}
343        }
344    }
345    Ok(())
346}
347
348#[cfg(feature = "ndarray")]
349pub(crate) fn band_masks_into_ndarray(
350    info: BandSetInfo,
351    band_masks: Vec<Option<Vec<u8>>>,
352) -> Result<Option<ArrayD<u8>>> {
353    if band_masks.iter().all(Option::is_none) {
354        return Ok(None);
355    }
356
357    let pixel_count = info.first().pixel_count()?;
358    let band_count = info.band_count();
359    let shape = info.band_mask_ndarray_shape();
360
361    if band_count == 1 {
362        let mask = band_masks
363            .into_iter()
364            .next()
365            .flatten()
366            .map(Ok)
367            .unwrap_or_else(|| default_vec(pixel_count, "decoded band mask"))?;
368        return ArrayD::from_shape_vec(IxDyn(&shape), mask)
369            .map(Some)
370            .map_err(|err| {
371                Error::invalid_blob(format!("failed to build ndarray from decoded mask: {err}"))
372            });
373    }
374
375    let merged_len = checked_mul(pixel_count, band_count, "decoded band mask length")?;
376    let mut merged = vec_with_capacity(merged_len, "decoded band mask")?;
377    for pixel in 0..pixel_count {
378        for band_mask in &band_masks {
379            merged.push(band_mask.as_ref().map(|mask| mask[pixel]).unwrap_or(1));
380        }
381    }
382
383    ArrayD::from_shape_vec(IxDyn(&shape), merged)
384        .map(Some)
385        .map_err(|err| {
386            Error::invalid_blob(format!(
387                "failed to build ndarray from decoded band mask: {err}"
388            ))
389        })
390}
391
392trait SupportedElementValue: Copy + 'static + IntoF64 {
393    const KIND: BandElementKind;
394}
395
396macro_rules! match_pixel_data_values {
397    ($band:expr, |$values:ident| $body:expr) => {
398        match $band {
399            PixelData::I8($values) => $body,
400            PixelData::U8($values) => $body,
401            PixelData::I16($values) => $body,
402            PixelData::U16($values) => $body,
403            PixelData::I32($values) => $body,
404            PixelData::U32($values) => $body,
405            PixelData::F32($values) => $body,
406            PixelData::F64($values) => $body,
407        }
408    };
409}
410
411fn copy_pixel_data_into_materializer<T: BandElement>(
412    materializer: &mut BandMaterializer<T>,
413    band_index: usize,
414    band: PixelData,
415) -> Result<()> {
416    match_pixel_data_values!(band, |values| {
417        copy_typed_values_into_materializer(materializer, band_index, &values)
418    })
419}
420
421fn copy_typed_values_into_materializer<T: BandElement, U: SupportedElementValue>(
422    materializer: &mut BandMaterializer<T>,
423    band_index: usize,
424    values: &[U],
425) -> Result<()> {
426    if T::KIND == U::KIND {
427        // SAFETY: equal BandElementKind values mean T and U are the same
428        // supported primitive type, so the slice layout and alignment are
429        // unchanged.
430        let typed = unsafe { cast_slice::<U, T>(values) };
431        return materializer.copy_band(band_index, typed);
432    }
433    if T::KIND == BandElementKind::F64 {
434        return materializer.copy_band_with(band_index, |index| {
435            // SAFETY: this branch is only entered when T is f64.
436            unsafe_cast::<T, f64>(values[index].into_f64())
437        });
438    }
439    Err(Error::invalid_blob(format!(
440        "cannot decode {} pixels into ndarray<{}>",
441        data_type_name::<U>(),
442        std::any::type_name::<T>()
443            .rsplit("::")
444            .next()
445            .unwrap_or("unknown"),
446    )))
447}
448
449fn copy_pixel_data_into_layout_slice<T: BandElement>(
450    out: &mut [T],
451    band_index: usize,
452    pixel_count: usize,
453    depth: usize,
454    band_count: usize,
455    layout: BandLayout,
456    band: PixelData,
457) -> Result<()> {
458    match_pixel_data_values!(band, |values| {
459        copy_typed_values_into_layout_slice(
460            out,
461            band_index,
462            pixel_count,
463            depth,
464            band_count,
465            layout,
466            &values,
467        )
468    })
469}
470
471fn copy_typed_values_into_layout_slice<T: BandElement, U: SupportedElementValue>(
472    out: &mut [T],
473    band_index: usize,
474    pixel_count: usize,
475    depth: usize,
476    band_count: usize,
477    layout: BandLayout,
478    values: &[U],
479) -> Result<()> {
480    if T::KIND == U::KIND {
481        // SAFETY: equal BandElementKind values mean T and U are the same
482        // supported primitive type, so the slice layout and alignment are
483        // unchanged.
484        let typed = unsafe { cast_slice::<U, T>(values) };
485        return copy_band_values_into_slice(
486            out,
487            typed,
488            pixel_count,
489            depth,
490            band_index,
491            band_count,
492            layout,
493        );
494    }
495    if T::KIND == BandElementKind::F64 {
496        let band_len = pixel_count
497            .checked_mul(depth.max(1))
498            .ok_or(Error::SizeOverflow("decoded band length"))?;
499        if values.len() != band_len {
500            return Err(Error::invalid_blob(
501                "decoded band length does not match its metadata",
502            ));
503        }
504        for (value_index, value) in values.iter().copied().enumerate() {
505            let out_index = match layout {
506                BandLayout::Interleaved => {
507                    if depth <= 1 {
508                        value_index * band_count + band_index
509                    } else {
510                        let pixel = value_index / depth;
511                        let sample = value_index % depth;
512                        (pixel * band_count + band_index) * depth + sample
513                    }
514                }
515                BandLayout::Bsq => band_index * band_len + value_index,
516            };
517            // SAFETY: this branch is only entered when T is f64.
518            out[out_index] = unsafe_cast::<T, f64>(value.into_f64());
519        }
520        return Ok(());
521    }
522    Err(Error::invalid_blob(format!(
523        "cannot decode {} pixels into ndarray<{}>",
524        data_type_name::<U>(),
525        std::any::type_name::<T>()
526            .rsplit("::")
527            .next()
528            .unwrap_or("unknown"),
529    )))
530}
531
532unsafe fn cast_slice<U, T>(values: &[U]) -> &[T] {
533    debug_assert_eq!(std::mem::size_of::<U>(), std::mem::size_of::<T>());
534    debug_assert_eq!(std::mem::align_of::<U>(), std::mem::align_of::<T>());
535    // SAFETY: callers must guarantee U and T are the same primitive element type.
536    unsafe { &*(values as *const [U] as *const [T]) }
537}
538
539fn unsafe_cast<T, U: Copy>(value: U) -> T {
540    debug_assert_eq!(std::mem::size_of::<U>(), std::mem::size_of::<T>());
541    debug_assert_eq!(std::mem::align_of::<U>(), std::mem::align_of::<T>());
542    // SAFETY: callers only use this helper for same-size primitive casts where
543    // T is known by branch guards to match the source value representation.
544    unsafe { std::mem::transmute_copy(&value) }
545}
546
547trait IntoF64 {
548    fn into_f64(self) -> f64;
549}
550
551impl IntoF64 for i8 {
552    fn into_f64(self) -> f64 {
553        self as f64
554    }
555}
556impl IntoF64 for u8 {
557    fn into_f64(self) -> f64 {
558        self as f64
559    }
560}
561impl IntoF64 for i16 {
562    fn into_f64(self) -> f64 {
563        self as f64
564    }
565}
566impl IntoF64 for u16 {
567    fn into_f64(self) -> f64 {
568        self as f64
569    }
570}
571impl IntoF64 for i32 {
572    fn into_f64(self) -> f64 {
573        self as f64
574    }
575}
576impl IntoF64 for u32 {
577    fn into_f64(self) -> f64 {
578        self as f64
579    }
580}
581impl IntoF64 for f32 {
582    fn into_f64(self) -> f64 {
583        self as f64
584    }
585}
586impl IntoF64 for f64 {
587    fn into_f64(self) -> f64 {
588        self
589    }
590}
591
592fn data_type_name<T: 'static>() -> &'static str {
593    if TypeId::of::<T>() == TypeId::of::<i8>() {
594        "i8"
595    } else if TypeId::of::<T>() == TypeId::of::<u8>() {
596        "u8"
597    } else if TypeId::of::<T>() == TypeId::of::<i16>() {
598        "i16"
599    } else if TypeId::of::<T>() == TypeId::of::<u16>() {
600        "u16"
601    } else if TypeId::of::<T>() == TypeId::of::<i32>() {
602        "i32"
603    } else if TypeId::of::<T>() == TypeId::of::<u32>() {
604        "u32"
605    } else if TypeId::of::<T>() == TypeId::of::<f32>() {
606        "f32"
607    } else if TypeId::of::<T>() == TypeId::of::<f64>() {
608        "f64"
609    } else {
610        "unknown"
611    }
612}
613
614mod private {
615    pub trait Sealed {}
616
617    impl Sealed for i8 {}
618    impl Sealed for u8 {}
619    impl Sealed for i16 {}
620    impl Sealed for u16 {}
621    impl Sealed for i32 {}
622    impl Sealed for u32 {}
623    impl Sealed for f32 {}
624    impl Sealed for f64 {}
625}
626
627/// Runtime discriminator for supported homogeneous band output types.
628#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
629pub enum BandElementKind {
630    /// Signed 8-bit integer.
631    I8,
632    /// Unsigned 8-bit integer.
633    U8,
634    /// Signed 16-bit integer.
635    I16,
636    /// Unsigned 16-bit integer.
637    U16,
638    /// Signed 32-bit integer.
639    I32,
640    /// Unsigned 32-bit integer.
641    U32,
642    /// Single-precision float.
643    F32,
644    /// Double-precision float.
645    F64,
646}
647
648/// Sealed primitive types accepted by homogeneous band-set decode APIs.
649pub trait BandElement: private::Sealed + Copy + Default + Send + Sync + 'static {
650    /// Runtime kind corresponding to this primitive.
651    const KIND: BandElementKind;
652    /// Converts a native decoded buffer when its representation is compatible.
653    fn from_pixel_data(pixels: PixelData) -> Result<Vec<Self>>;
654}
655
656#[cfg(feature = "ndarray")]
657/// Sealed primitive types accepted by single-blob ndarray decode APIs.
658pub trait NdArrayElement: BandElement {}
659
660macro_rules! impl_exact_band_element {
661    ($ty:ty, $variant:ident, $name:literal) => {
662        impl BandElement for $ty {
663            const KIND: BandElementKind = BandElementKind::$variant;
664
665            fn from_pixel_data(pixels: PixelData) -> Result<Vec<Self>> {
666                match pixels {
667                    PixelData::$variant(values) => Ok(values),
668                    other => Err(Error::invalid_blob(format!(
669                        "cannot decode {} pixels into ndarray<{}>",
670                        other.data_type().name(),
671                        $name
672                    ))),
673                }
674            }
675        }
676    };
677}
678
679impl_exact_band_element!(i8, I8, "i8");
680impl_exact_band_element!(u8, U8, "u8");
681impl_exact_band_element!(i16, I16, "i16");
682impl_exact_band_element!(u16, U16, "u16");
683impl_exact_band_element!(i32, I32, "i32");
684impl_exact_band_element!(u32, U32, "u32");
685impl_exact_band_element!(f32, F32, "f32");
686
687impl BandElement for f64 {
688    const KIND: BandElementKind = BandElementKind::F64;
689
690    fn from_pixel_data(pixels: PixelData) -> Result<Vec<Self>> {
691        Ok(pixels.to_f64())
692    }
693}
694
695#[cfg(feature = "ndarray")]
696macro_rules! impl_ndarray_element {
697    ($($ty:ty),+ $(,)?) => { $(impl NdArrayElement for $ty {})+ };
698}
699
700#[cfg(feature = "ndarray")]
701impl_ndarray_element!(i8, u8, i16, u16, i32, u32, f32, f64);
702
703macro_rules! impl_band_element {
704    ($ty:ty, $kind:ident) => {
705        impl SupportedElementValue for $ty {
706            const KIND: BandElementKind = BandElementKind::$kind;
707        }
708    };
709}
710
711impl_band_element!(i8, I8);
712impl_band_element!(u8, U8);
713impl_band_element!(i16, I16);
714impl_band_element!(u16, U16);
715impl_band_element!(i32, I32);
716impl_band_element!(u32, U32);
717impl_band_element!(f32, F32);
718impl_band_element!(f64, F64);
719
720#[cfg(feature = "ndarray")]
721trait PixelDataExt {
722    fn into_ndarray<T: NdArrayElement>(self, shape: &[usize]) -> Result<ArrayD<T>>;
723}
724
725#[cfg(feature = "ndarray")]
726impl PixelDataExt for PixelData {
727    fn into_ndarray<T: NdArrayElement>(self, shape: &[usize]) -> Result<ArrayD<T>> {
728        ArrayD::from_shape_vec(IxDyn(shape), T::from_pixel_data(self)?).map_err(|err| {
729            Error::invalid_blob(format!(
730                "failed to build ndarray from decoded pixels: {err}"
731            ))
732        })
733    }
734}