Skip to main content

lerc_reader/
types.rs

1use std::any::TypeId;
2
3use lerc_band_materialize::{
4    copy_band_values_into_slice, BandLayout as MaterializeLayout, BandMaterializer,
5};
6use lerc_core::{BandLayout, BandSetInfo, BlobInfo, Error, PixelData, Result};
7use ndarray::{ArrayD, IxDyn};
8
9use crate::allocation::{checked_mul, default_vec, vec_with_capacity};
10
11#[derive(Debug, Clone, PartialEq)]
12pub struct Decoded {
13    pub info: BlobInfo,
14    pub pixels: PixelData,
15    pub mask: Option<Vec<u8>>,
16}
17
18impl Decoded {
19    pub fn into_ndarray<T: NdArrayElement>(self) -> Result<ArrayD<T>> {
20        let shape = self.info.ndarray_shape();
21        self.pixels.into_ndarray(&shape)
22    }
23
24    pub fn into_mask_ndarray(self) -> Result<Option<ArrayD<u8>>> {
25        let shape = self.info.mask_ndarray_shape();
26        self.mask
27            .map(|mask| {
28                ArrayD::from_shape_vec(IxDyn(&shape), mask).map_err(|err| {
29                    Error::InvalidBlob(format!("failed to build ndarray from decoded mask: {err}"))
30                })
31            })
32            .transpose()
33    }
34}
35
36#[derive(Debug, Clone, PartialEq)]
37pub struct DecodedF64 {
38    pub info: BlobInfo,
39    pub pixels: Vec<f64>,
40    pub mask: Option<Vec<u8>>,
41}
42
43impl DecodedF64 {
44    pub fn into_ndarray(self) -> Result<ArrayD<f64>> {
45        ArrayD::from_shape_vec(IxDyn(&self.info.ndarray_shape()), self.pixels).map_err(|err| {
46            Error::InvalidBlob(format!(
47                "failed to build ndarray from decoded pixels: {err}"
48            ))
49        })
50    }
51
52    pub fn into_mask_ndarray(self) -> Result<Option<ArrayD<u8>>> {
53        let shape = self.info.mask_ndarray_shape();
54        self.mask
55            .map(|mask| {
56                ArrayD::from_shape_vec(IxDyn(&shape), mask).map_err(|err| {
57                    Error::InvalidBlob(format!("failed to build ndarray from decoded mask: {err}"))
58                })
59            })
60            .transpose()
61    }
62}
63
64#[derive(Debug, Clone, PartialEq)]
65pub struct DecodedBandSet {
66    pub info: BandSetInfo,
67    pub bands: Vec<PixelData>,
68    pub band_masks: Vec<Option<Vec<u8>>>,
69}
70
71impl DecodedBandSet {
72    pub fn into_ndarray<T: BandElement>(self) -> Result<ArrayD<T>> {
73        self.into_ndarray_with_layout(BandLayout::Interleaved)
74    }
75
76    pub fn into_ndarray_with_layout<T: BandElement>(self, layout: BandLayout) -> Result<ArrayD<T>> {
77        let shape = self.info.ndarray_shape_for_layout(layout);
78        let values = self.into_vec_with_layout(layout)?;
79        ArrayD::from_shape_vec(IxDyn(&shape), values).map_err(|err| {
80            Error::InvalidBlob(format!(
81                "failed to build ndarray from decoded band set: {err}"
82            ))
83        })
84    }
85
86    pub fn into_vec_with_layout<T: BandElement>(self, layout: BandLayout) -> Result<Vec<T>> {
87        if self.bands.len() == 1 {
88            return T::from_pixel_data(self.bands.into_iter().next().unwrap());
89        }
90
91        let mut materializer = BandMaterializer::new(
92            self.info.bands[0].pixel_count()?,
93            self.info.depth() as usize,
94            self.info.band_count(),
95            materialize_layout(layout),
96        )
97        .map_err(materialize_error)?;
98        for (band_index, band) in self.bands.into_iter().enumerate() {
99            copy_pixel_data_into_materializer(&mut materializer, band_index, band)?;
100        }
101        materializer.finish().map_err(materialize_error)
102    }
103
104    pub fn copy_into_slice<T: BandElement>(self, layout: BandLayout, out: &mut [T]) -> Result<()> {
105        let pixel_count = self.info.bands[0].pixel_count()?;
106        let depth = self.info.depth() as usize;
107        let band_count = self.info.band_count();
108        let expected_len = self.info.value_count()?;
109        if out.len() != expected_len {
110            return Err(Error::InvalidBlob(format!(
111                "output slice length {} does not match decoded band set length {}",
112                out.len(),
113                expected_len
114            )));
115        }
116
117        for (band_index, band) in self.bands.into_iter().enumerate() {
118            copy_pixel_data_into_layout_slice(
119                out,
120                band_index,
121                pixel_count,
122                depth,
123                band_count,
124                layout,
125                band,
126            )?;
127        }
128        Ok(())
129    }
130
131    pub fn into_band_mask_ndarray(self) -> Result<Option<ArrayD<u8>>> {
132        into_band_mask_ndarray(self.info, self.band_masks)
133    }
134}
135
136pub fn into_band_mask_ndarray(
137    info: BandSetInfo,
138    band_masks: Vec<Option<Vec<u8>>>,
139) -> Result<Option<ArrayD<u8>>> {
140    if band_masks.iter().all(Option::is_none) {
141        return Ok(None);
142    }
143
144    let pixel_count = info.bands[0].pixel_count()?;
145    let band_count = info.band_count();
146    let shape = info.band_mask_ndarray_shape();
147
148    if band_count == 1 {
149        let mask = band_masks
150            .into_iter()
151            .next()
152            .flatten()
153            .map(Ok)
154            .unwrap_or_else(|| default_vec(pixel_count, "decoded band mask"))?;
155        return ArrayD::from_shape_vec(IxDyn(&shape), mask)
156            .map(Some)
157            .map_err(|err| {
158                Error::InvalidBlob(format!("failed to build ndarray from decoded mask: {err}"))
159            });
160    }
161
162    let merged_len = checked_mul(pixel_count, band_count, "decoded band mask length")?;
163    let mut merged = vec_with_capacity(merged_len, "decoded band mask")?;
164    for pixel in 0..pixel_count {
165        for band_mask in &band_masks {
166            merged.push(band_mask.as_ref().map(|mask| mask[pixel]).unwrap_or(1));
167        }
168    }
169
170    ArrayD::from_shape_vec(IxDyn(&shape), merged)
171        .map(Some)
172        .map_err(|err| {
173            Error::InvalidBlob(format!(
174                "failed to build ndarray from decoded band mask: {err}"
175            ))
176        })
177}
178
179trait SupportedElementValue: Copy + 'static + IntoF64 {
180    const KIND: BandElementKind;
181}
182
183macro_rules! match_pixel_data_values {
184    ($band:expr, |$values:ident| $body:expr) => {
185        match $band {
186            PixelData::I8($values) => $body,
187            PixelData::U8($values) => $body,
188            PixelData::I16($values) => $body,
189            PixelData::U16($values) => $body,
190            PixelData::I32($values) => $body,
191            PixelData::U32($values) => $body,
192            PixelData::F32($values) => $body,
193            PixelData::F64($values) => $body,
194        }
195    };
196}
197
198fn copy_pixel_data_into_materializer<T: BandElement>(
199    materializer: &mut BandMaterializer<T>,
200    band_index: usize,
201    band: PixelData,
202) -> Result<()> {
203    match_pixel_data_values!(band, |values| {
204        copy_typed_values_into_materializer(materializer, band_index, &values)
205    })
206}
207
208fn copy_typed_values_into_materializer<T: BandElement, U: SupportedElementValue>(
209    materializer: &mut BandMaterializer<T>,
210    band_index: usize,
211    values: &[U],
212) -> Result<()> {
213    if T::KIND == U::KIND {
214        // SAFETY: equal BandElementKind values mean T and U are the same
215        // supported primitive type, so the slice layout and alignment are
216        // unchanged.
217        let typed = unsafe { cast_slice::<U, T>(values) };
218        return materializer
219            .copy_band(band_index, typed)
220            .map_err(materialize_error);
221    }
222    if T::KIND == BandElementKind::F64 {
223        return materializer
224            .copy_band_with(band_index, |index| {
225                // SAFETY: this branch is only entered when T is f64.
226                unsafe_cast::<T, f64>(values[index].into_f64())
227            })
228            .map_err(materialize_error);
229    }
230    Err(Error::InvalidBlob(format!(
231        "cannot decode {} pixels into ndarray<{}>",
232        data_type_name::<U>(),
233        std::any::type_name::<T>()
234            .rsplit("::")
235            .next()
236            .unwrap_or("unknown"),
237    )))
238}
239
240fn copy_pixel_data_into_layout_slice<T: BandElement>(
241    out: &mut [T],
242    band_index: usize,
243    pixel_count: usize,
244    depth: usize,
245    band_count: usize,
246    layout: BandLayout,
247    band: PixelData,
248) -> Result<()> {
249    match_pixel_data_values!(band, |values| {
250        copy_typed_values_into_layout_slice(
251            out,
252            band_index,
253            pixel_count,
254            depth,
255            band_count,
256            layout,
257            &values,
258        )
259    })
260}
261
262fn copy_typed_values_into_layout_slice<T: BandElement, U: SupportedElementValue>(
263    out: &mut [T],
264    band_index: usize,
265    pixel_count: usize,
266    depth: usize,
267    band_count: usize,
268    layout: BandLayout,
269    values: &[U],
270) -> Result<()> {
271    if T::KIND == U::KIND {
272        // SAFETY: equal BandElementKind values mean T and U are the same
273        // supported primitive type, so the slice layout and alignment are
274        // unchanged.
275        let typed = unsafe { cast_slice::<U, T>(values) };
276        return copy_band_values_into_slice(
277            out,
278            typed,
279            pixel_count,
280            depth,
281            band_index,
282            band_count,
283            materialize_layout(layout),
284        )
285        .map_err(materialize_error);
286    }
287    if T::KIND == BandElementKind::F64 {
288        let band_len = pixel_count
289            .checked_mul(depth.max(1))
290            .ok_or_else(|| Error::InvalidBlob("decoded band length overflows usize".into()))?;
291        if values.len() != band_len {
292            return Err(Error::InvalidBlob(
293                "decoded band length does not match its metadata".into(),
294            ));
295        }
296        for (value_index, value) in values.iter().copied().enumerate() {
297            let out_index = match layout {
298                BandLayout::Interleaved => {
299                    if depth <= 1 {
300                        value_index * band_count + band_index
301                    } else {
302                        let pixel = value_index / depth;
303                        let sample = value_index % depth;
304                        (pixel * band_count + band_index) * depth + sample
305                    }
306                }
307                BandLayout::Bsq => band_index * band_len + value_index,
308            };
309            // SAFETY: this branch is only entered when T is f64.
310            out[out_index] = unsafe_cast::<T, f64>(value.into_f64());
311        }
312        return Ok(());
313    }
314    Err(Error::InvalidBlob(format!(
315        "cannot decode {} pixels into ndarray<{}>",
316        data_type_name::<U>(),
317        std::any::type_name::<T>()
318            .rsplit("::")
319            .next()
320            .unwrap_or("unknown"),
321    )))
322}
323
324unsafe fn cast_slice<U, T>(values: &[U]) -> &[T] {
325    // SAFETY: callers must guarantee U and T are the same primitive element type.
326    unsafe { &*(values as *const [U] as *const [T]) }
327}
328
329fn unsafe_cast<T, U: Copy>(value: U) -> T {
330    // SAFETY: callers only use this helper for same-size primitive casts where
331    // T is known by branch guards to match the source value representation.
332    unsafe { std::mem::transmute_copy(&value) }
333}
334
335trait IntoF64 {
336    fn into_f64(self) -> f64;
337}
338
339impl IntoF64 for i8 {
340    fn into_f64(self) -> f64 {
341        self as f64
342    }
343}
344impl IntoF64 for u8 {
345    fn into_f64(self) -> f64 {
346        self as f64
347    }
348}
349impl IntoF64 for i16 {
350    fn into_f64(self) -> f64 {
351        self as f64
352    }
353}
354impl IntoF64 for u16 {
355    fn into_f64(self) -> f64 {
356        self as f64
357    }
358}
359impl IntoF64 for i32 {
360    fn into_f64(self) -> f64 {
361        self as f64
362    }
363}
364impl IntoF64 for u32 {
365    fn into_f64(self) -> f64 {
366        self as f64
367    }
368}
369impl IntoF64 for f32 {
370    fn into_f64(self) -> f64 {
371        self as f64
372    }
373}
374impl IntoF64 for f64 {
375    fn into_f64(self) -> f64 {
376        self
377    }
378}
379
380fn data_type_name<T: 'static>() -> &'static str {
381    if TypeId::of::<T>() == TypeId::of::<i8>() {
382        "i8"
383    } else if TypeId::of::<T>() == TypeId::of::<u8>() {
384        "u8"
385    } else if TypeId::of::<T>() == TypeId::of::<i16>() {
386        "i16"
387    } else if TypeId::of::<T>() == TypeId::of::<u16>() {
388        "u16"
389    } else if TypeId::of::<T>() == TypeId::of::<i32>() {
390        "i32"
391    } else if TypeId::of::<T>() == TypeId::of::<u32>() {
392        "u32"
393    } else if TypeId::of::<T>() == TypeId::of::<f32>() {
394        "f32"
395    } else if TypeId::of::<T>() == TypeId::of::<f64>() {
396        "f64"
397    } else {
398        "unknown"
399    }
400}
401
402pub trait NdArrayElement: Sized + Clone {
403    fn from_pixel_data(pixels: PixelData) -> Result<Vec<Self>>;
404}
405
406mod private {
407    pub trait Sealed {}
408
409    impl Sealed for i8 {}
410    impl Sealed for u8 {}
411    impl Sealed for i16 {}
412    impl Sealed for u16 {}
413    impl Sealed for i32 {}
414    impl Sealed for u32 {}
415    impl Sealed for f32 {}
416    impl Sealed for f64 {}
417}
418
419#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
420pub enum BandElementKind {
421    I8,
422    U8,
423    I16,
424    U16,
425    I32,
426    U32,
427    F32,
428    F64,
429}
430
431pub trait BandElement: NdArrayElement + private::Sealed + Copy + Default + 'static {
432    const KIND: BandElementKind;
433}
434
435macro_rules! impl_exact_ndarray_element {
436    ($ty:ty, $variant:ident, $name:literal) => {
437        impl NdArrayElement for $ty {
438            fn from_pixel_data(pixels: PixelData) -> Result<Vec<Self>> {
439                match pixels {
440                    PixelData::$variant(values) => Ok(values),
441                    other => Err(Error::InvalidBlob(format!(
442                        "cannot decode {} pixels into ndarray<{}>",
443                        other.data_type().name(),
444                        $name
445                    ))),
446                }
447            }
448        }
449    };
450}
451
452impl_exact_ndarray_element!(i8, I8, "i8");
453impl_exact_ndarray_element!(u8, U8, "u8");
454impl_exact_ndarray_element!(i16, I16, "i16");
455impl_exact_ndarray_element!(u16, U16, "u16");
456impl_exact_ndarray_element!(i32, I32, "i32");
457impl_exact_ndarray_element!(u32, U32, "u32");
458impl_exact_ndarray_element!(f32, F32, "f32");
459
460impl NdArrayElement for f64 {
461    fn from_pixel_data(pixels: PixelData) -> Result<Vec<Self>> {
462        Ok(pixels.to_f64())
463    }
464}
465
466macro_rules! impl_band_element {
467    ($ty:ty, $kind:ident) => {
468        impl BandElement for $ty {
469            const KIND: BandElementKind = BandElementKind::$kind;
470        }
471
472        impl SupportedElementValue for $ty {
473            const KIND: BandElementKind = BandElementKind::$kind;
474        }
475    };
476}
477
478impl_band_element!(i8, I8);
479impl_band_element!(u8, U8);
480impl_band_element!(i16, I16);
481impl_band_element!(u16, U16);
482impl_band_element!(i32, I32);
483impl_band_element!(u32, U32);
484impl_band_element!(f32, F32);
485impl_band_element!(f64, F64);
486
487fn materialize_layout(layout: BandLayout) -> MaterializeLayout {
488    match layout {
489        BandLayout::Interleaved => MaterializeLayout::Interleaved,
490        BandLayout::Bsq => MaterializeLayout::Bsq,
491    }
492}
493
494fn materialize_error(err: lerc_band_materialize::MaterializeError) -> Error {
495    Error::InvalidBlob(err.to_string())
496}
497
498trait PixelDataExt {
499    fn into_ndarray<T: NdArrayElement>(self, shape: &[usize]) -> Result<ArrayD<T>>;
500}
501
502impl PixelDataExt for PixelData {
503    fn into_ndarray<T: NdArrayElement>(self, shape: &[usize]) -> Result<ArrayD<T>> {
504        ArrayD::from_shape_vec(IxDyn(shape), T::from_pixel_data(self)?).map_err(|err| {
505            Error::InvalidBlob(format!(
506                "failed to build ndarray from decoded pixels: {err}"
507            ))
508        })
509    }
510}