numcodecs_jpeg2000/
lib.rs

1//! [![CI Status]][workflow] [![MSRV]][repo] [![Latest Version]][crates.io] [![Rust Doc Crate]][docs.rs] [![Rust Doc Main]][docs]
2//!
3//! [CI Status]: https://img.shields.io/github/actions/workflow/status/juntyr/numcodecs-rs/ci.yml?branch=main
4//! [workflow]: https://github.com/juntyr/numcodecs-rs/actions/workflows/ci.yml?query=branch%3Amain
5//!
6//! [MSRV]: https://img.shields.io/badge/MSRV-1.82.0-blue
7//! [repo]: https://github.com/juntyr/numcodecs-rs
8//!
9//! [Latest Version]: https://img.shields.io/crates/v/numcodecs-jpeg2000
10//! [crates.io]: https://crates.io/crates/numcodecs-jpeg2000
11//!
12//! [Rust Doc Crate]: https://img.shields.io/docsrs/numcodecs-jpeg2000
13//! [docs.rs]: https://docs.rs/numcodecs-jpeg2000/
14//!
15//! [Rust Doc Main]: https://img.shields.io/badge/docs-main-blue
16//! [docs]: https://juntyr.github.io/numcodecs-rs/numcodecs_jpeg2000
17//!
18//! JPEG 2000 codec implementation for the [`numcodecs`] API.
19
20#![allow(clippy::multiple_crate_versions)] // embedded-io
21
22#[cfg(test)]
23use ::serde_json as _;
24
25use std::borrow::Cow;
26use std::fmt;
27
28use ndarray::{Array, Array1, ArrayBase, Axis, Data, Dimension, IxDyn, ShapeError};
29use num_traits::identities::Zero;
30use numcodecs::{
31    AnyArray, AnyArrayAssignError, AnyArrayDType, AnyArrayView, AnyArrayViewMut, AnyCowArray,
32    Codec, StaticCodec, StaticCodecConfig, StaticCodecVersion,
33};
34use schemars::JsonSchema;
35use serde::{Deserialize, Serialize};
36use thiserror::Error;
37
38mod ffi;
39
40type Jpeg2000CodecVersion = StaticCodecVersion<0, 1, 0>;
41
42#[derive(Clone, Serialize, Deserialize, JsonSchema)]
43// serde cannot deny unknown fields because of the flatten
44#[schemars(deny_unknown_fields)]
45/// Codec providing compression using JPEG 2000.
46///
47/// Arrays that are higher-dimensional than 2D are encoded by compressing each
48/// 2D slice with JPEG 2000 independently. Specifically, the array's shape is
49/// interpreted as `[.., height, width]`. If you want to compress 2D slices
50/// along two different axes, you can swizzle the array axes beforehand.
51pub struct Jpeg2000Codec {
52    /// JPEG 2000 compression mode
53    #[serde(flatten)]
54    pub mode: Jpeg2000CompressionMode,
55    /// The codec's encoding format version. Do not provide this parameter explicitly.
56    #[serde(default, rename = "_version")]
57    pub version: Jpeg2000CodecVersion,
58}
59
60#[derive(Clone, Serialize, Deserialize, JsonSchema)]
61/// JPEG 2000 compression mode
62#[serde(tag = "mode")]
63pub enum Jpeg2000CompressionMode {
64    /// Peak signal-to-noise ratio
65    #[serde(rename = "psnr")]
66    PSNR {
67        /// Peak signal-to-noise ratio
68        psnr: f32,
69    },
70    /// Compression rate
71    #[serde(rename = "rate")]
72    Rate {
73        /// Compression rate, e.g. `10.0` for x10 compression
74        rate: f32,
75    },
76    /// Lossless compression
77    #[serde(rename = "lossless")]
78    Lossless,
79}
80
81impl Codec for Jpeg2000Codec {
82    type Error = Jpeg2000CodecError;
83
84    fn encode(&self, data: AnyCowArray) -> Result<AnyArray, Self::Error> {
85        match data {
86            AnyCowArray::I8(data) => Ok(AnyArray::U8(
87                Array1::from(compress(data, &self.mode)?).into_dyn(),
88            )),
89            AnyCowArray::U8(data) => Ok(AnyArray::U8(
90                Array1::from(compress(data, &self.mode)?).into_dyn(),
91            )),
92            AnyCowArray::I16(data) => Ok(AnyArray::U8(
93                Array1::from(compress(data, &self.mode)?).into_dyn(),
94            )),
95            AnyCowArray::U16(data) => Ok(AnyArray::U8(
96                Array1::from(compress(data, &self.mode)?).into_dyn(),
97            )),
98            AnyCowArray::I32(data) => Ok(AnyArray::U8(
99                Array1::from(compress(data, &self.mode)?).into_dyn(),
100            )),
101            AnyCowArray::U32(data) => Ok(AnyArray::U8(
102                Array1::from(compress(data, &self.mode)?).into_dyn(),
103            )),
104            AnyCowArray::I64(data) => Ok(AnyArray::U8(
105                Array1::from(compress(data, &self.mode)?).into_dyn(),
106            )),
107            AnyCowArray::U64(data) => Ok(AnyArray::U8(
108                Array1::from(compress(data, &self.mode)?).into_dyn(),
109            )),
110            encoded => Err(Jpeg2000CodecError::UnsupportedDtype(encoded.dtype())),
111        }
112    }
113
114    fn decode(&self, encoded: AnyCowArray) -> Result<AnyArray, Self::Error> {
115        let AnyCowArray::U8(encoded) = encoded else {
116            return Err(Jpeg2000CodecError::EncodedDataNotBytes {
117                dtype: encoded.dtype(),
118            });
119        };
120
121        if !matches!(encoded.shape(), [_]) {
122            return Err(Jpeg2000CodecError::EncodedDataNotOneDimensional {
123                shape: encoded.shape().to_vec(),
124            });
125        }
126
127        decompress(&AnyCowArray::U8(encoded).as_bytes())
128    }
129
130    fn decode_into(
131        &self,
132        encoded: AnyArrayView,
133        mut decoded: AnyArrayViewMut,
134    ) -> Result<(), Self::Error> {
135        let decoded_in = self.decode(encoded.cow())?;
136
137        Ok(decoded.assign(&decoded_in)?)
138    }
139}
140
141impl StaticCodec for Jpeg2000Codec {
142    const CODEC_ID: &'static str = "jpeg2000.rs";
143
144    type Config<'de> = Self;
145
146    fn from_config(config: Self::Config<'_>) -> Self {
147        config
148    }
149
150    fn get_config(&self) -> StaticCodecConfig<Self> {
151        StaticCodecConfig::from(self)
152    }
153}
154
155#[derive(Debug, Error)]
156/// Errors that may occur when applying the [`Jpeg2000Codec`].
157pub enum Jpeg2000CodecError {
158    /// [`Jpeg2000Codec`] does not support the dtype
159    #[error("Jpeg2000 does not support the dtype {0}")]
160    UnsupportedDtype(AnyArrayDType),
161    /// [`Jpeg2000Codec`] failed to encode the header
162    #[error("Jpeg2000 failed to encode the header")]
163    HeaderEncodeFailed {
164        /// Opaque source error
165        source: Jpeg2000HeaderError,
166    },
167    /// [`Jpeg2000Codec`] failed to encode the data
168    #[error("Jpeg2000 failed to encode the data")]
169    Jpeg2000EncodeFailed {
170        /// Opaque source error
171        source: Jpeg2000CodingError,
172    },
173    /// [`Jpeg2000Codec`] failed to encode a slice
174    #[error("Jpeg2000 failed to encode a slice")]
175    SliceEncodeFailed {
176        /// Opaque source error
177        source: Jpeg2000SliceError,
178    },
179    /// [`Jpeg2000Codec`] can only decode one-dimensional byte arrays but received
180    /// an array of a different dtype
181    #[error(
182        "Jpeg2000 can only decode one-dimensional byte arrays but received an array of dtype {dtype}"
183    )]
184    EncodedDataNotBytes {
185        /// The unexpected dtype of the encoded array
186        dtype: AnyArrayDType,
187    },
188    /// [`Jpeg2000Codec`] can only decode one-dimensional byte arrays but received
189    /// an array of a different shape
190    #[error("Jpeg2000 can only decode one-dimensional byte arrays but received a byte array of shape {shape:?}")]
191    EncodedDataNotOneDimensional {
192        /// The unexpected shape of the encoded array
193        shape: Vec<usize>,
194    },
195    /// [`Jpeg2000Codec`] failed to decode the header
196    #[error("Jpeg2000 failed to decode the header")]
197    HeaderDecodeFailed {
198        /// Opaque source error
199        source: Jpeg2000HeaderError,
200    },
201    /// [`Jpeg2000Codec`] failed to decode a slice
202    #[error("Jpeg2000 failed to decode a slice")]
203    SliceDecodeFailed {
204        /// Opaque source error
205        source: Jpeg2000SliceError,
206    },
207    /// [`Jpeg2000Codec`] failed to decode from an excessive number of slices
208    #[error("Jpeg2000 failed to decode from an excessive number of slices")]
209    DecodeTooManySlices,
210    /// [`Jpeg2000Codec`] failed to decode the data
211    #[error("Jpeg2000 failed to decode the data")]
212    Jpeg2000DecodeFailed {
213        /// Opaque source error
214        source: Jpeg2000CodingError,
215    },
216    /// [`Jpeg2000Codec`] decoded into an invalid shape not matching the data size
217    #[error("Jpeg2000 decoded into an invalid shape not matching the data size")]
218    DecodeInvalidShape {
219        /// The source of the error
220        source: ShapeError,
221    },
222    /// [`Jpeg2000Codec`] cannot decode into the provided array
223    #[error("Jpeg2000Codec cannot decode into the provided array")]
224    MismatchedDecodeIntoArray {
225        /// The source of the error
226        #[from]
227        source: AnyArrayAssignError,
228    },
229}
230
231#[derive(Debug, Error)]
232#[error(transparent)]
233/// Opaque error for when encoding or decoding the header fails
234pub struct Jpeg2000HeaderError(postcard::Error);
235
236#[derive(Debug, Error)]
237#[error(transparent)]
238/// Opaque error for when encoding or decoding a slice fails
239pub struct Jpeg2000SliceError(postcard::Error);
240
241#[derive(Debug, Error)]
242#[error(transparent)]
243/// Opaque error for when encoding or decoding with JPEG 2000 fails
244pub struct Jpeg2000CodingError(ffi::Jpeg2000Error);
245
246/// Compress the `data` array using JPEG 2000 with the provided `mode`.
247///
248/// # Errors
249///
250/// Errors with
251/// - [`Jpeg2000CodecError::HeaderEncodeFailed`] if encoding the header failed
252/// - [`Jpeg2000CodecError::Jpeg2000EncodeFailed`] if encoding with JPEG 2000
253///   failed
254/// - [`Jpeg2000CodecError::SliceEncodeFailed`] if encoding a slice failed
255pub fn compress<T: Jpeg2000Element, S: Data<Elem = T>, D: Dimension>(
256    data: ArrayBase<S, D>,
257    mode: &Jpeg2000CompressionMode,
258) -> Result<Vec<u8>, Jpeg2000CodecError> {
259    let mut encoded = postcard::to_extend(
260        &CompressionHeader {
261            dtype: T::DTYPE,
262            shape: Cow::Borrowed(data.shape()),
263            version: StaticCodecVersion,
264        },
265        Vec::new(),
266    )
267    .map_err(|err| Jpeg2000CodecError::HeaderEncodeFailed {
268        source: Jpeg2000HeaderError(err),
269    })?;
270
271    // JPEG 2000 cannot handle zero-length dimensions
272    if data.is_empty() {
273        return Ok(encoded);
274    }
275
276    let mut encoded_slice = Vec::new();
277
278    let mut chunk_size = Vec::from(data.shape());
279    let (width, height) = match *chunk_size.as_mut_slice() {
280        [ref mut rest @ .., height, width] => {
281            for r in rest {
282                *r = 1;
283            }
284            (width, height)
285        }
286        [width] => (width, 1),
287        [] => (1, 1),
288    };
289
290    for slice in data.into_dyn().exact_chunks(chunk_size.as_slice()) {
291        encoded_slice.clear();
292
293        ffi::encode_into(
294            slice.iter().copied(),
295            width,
296            height,
297            match mode {
298                Jpeg2000CompressionMode::PSNR { psnr } => ffi::Jpeg2000CompressionMode::PSNR(*psnr),
299                Jpeg2000CompressionMode::Rate { rate } => ffi::Jpeg2000CompressionMode::Rate(*rate),
300                Jpeg2000CompressionMode::Lossless => ffi::Jpeg2000CompressionMode::Lossless,
301            },
302            &mut encoded_slice,
303        )
304        .map_err(|err| Jpeg2000CodecError::Jpeg2000EncodeFailed {
305            source: Jpeg2000CodingError(err),
306        })?;
307
308        encoded = postcard::to_extend(encoded_slice.as_slice(), encoded).map_err(|err| {
309            Jpeg2000CodecError::SliceEncodeFailed {
310                source: Jpeg2000SliceError(err),
311            }
312        })?;
313    }
314
315    Ok(encoded)
316}
317
318/// Decompress the `encoded` data into an array using JPEG 2000.
319///
320/// # Errors
321///
322/// Errors with
323/// - [`Jpeg2000CodecError::HeaderDecodeFailed`] if decoding the header failed
324/// - [`Jpeg2000CodecError::SliceDecodeFailed`] if decoding a slice failed
325/// - [`Jpeg2000CodecError::Jpeg2000DecodeFailed`] if decoding with JPEG 2000
326///   failed
327/// - [`Jpeg2000CodecError::DecodeInvalidShape`] if the encoded data decodes to
328///   an unexpected shape
329/// - [`Jpeg2000CodecError::DecodeTooManySlices`] if the encoded data contains
330///   too many slices
331pub fn decompress(encoded: &[u8]) -> Result<AnyArray, Jpeg2000CodecError> {
332    fn decompress_typed<T: Jpeg2000Element>(
333        mut encoded: &[u8],
334        shape: &[usize],
335    ) -> Result<Array<T, IxDyn>, Jpeg2000CodecError> {
336        let mut decoded = Array::<T, _>::zeros(shape);
337
338        let mut chunk_size = Vec::from(shape);
339        let (width, height) = match *chunk_size.as_mut_slice() {
340            [ref mut rest @ .., height, width] => {
341                for r in rest {
342                    *r = 1;
343                }
344                (width, height)
345            }
346            [width] => (width, 1),
347            [] => (1, 1),
348        };
349
350        for mut slice in decoded.exact_chunks_mut(chunk_size.as_slice()) {
351            let (encoded_slice, rest) =
352                postcard::take_from_bytes::<Cow<[u8]>>(encoded).map_err(|err| {
353                    Jpeg2000CodecError::SliceDecodeFailed {
354                        source: Jpeg2000SliceError(err),
355                    }
356                })?;
357            encoded = rest;
358
359            let (decoded_slice, (_width, _height)) =
360                ffi::decode::<T>(&encoded_slice).map_err(|err| {
361                    Jpeg2000CodecError::Jpeg2000DecodeFailed {
362                        source: Jpeg2000CodingError(err),
363                    }
364                })?;
365            let mut decoded_slice = Array::from_shape_vec((height, width), decoded_slice)
366                .map_err(|source| Jpeg2000CodecError::DecodeInvalidShape { source })?
367                .into_dyn();
368
369            while decoded_slice.ndim() > shape.len() {
370                decoded_slice = decoded_slice.remove_axis(Axis(0));
371            }
372
373            slice.assign(&decoded_slice);
374        }
375
376        if !encoded.is_empty() {
377            return Err(Jpeg2000CodecError::DecodeTooManySlices);
378        }
379
380        Ok(decoded)
381    }
382
383    let (header, encoded) =
384        postcard::take_from_bytes::<CompressionHeader>(encoded).map_err(|err| {
385            Jpeg2000CodecError::HeaderDecodeFailed {
386                source: Jpeg2000HeaderError(err),
387            }
388        })?;
389
390    // Return empty data for zero-size arrays
391    if header.shape.iter().copied().product::<usize>() == 0 {
392        return match header.dtype {
393            Jpeg2000DType::I8 => Ok(AnyArray::I8(Array::zeros(&*header.shape))),
394            Jpeg2000DType::U8 => Ok(AnyArray::U8(Array::zeros(&*header.shape))),
395            Jpeg2000DType::I16 => Ok(AnyArray::I16(Array::zeros(&*header.shape))),
396            Jpeg2000DType::U16 => Ok(AnyArray::U16(Array::zeros(&*header.shape))),
397            Jpeg2000DType::I32 => Ok(AnyArray::I32(Array::zeros(&*header.shape))),
398            Jpeg2000DType::U32 => Ok(AnyArray::U32(Array::zeros(&*header.shape))),
399            Jpeg2000DType::I64 => Ok(AnyArray::I64(Array::zeros(&*header.shape))),
400            Jpeg2000DType::U64 => Ok(AnyArray::U64(Array::zeros(&*header.shape))),
401        };
402    }
403
404    match header.dtype {
405        Jpeg2000DType::I8 => Ok(AnyArray::I8(decompress_typed(encoded, &header.shape)?)),
406        Jpeg2000DType::U8 => Ok(AnyArray::U8(decompress_typed(encoded, &header.shape)?)),
407        Jpeg2000DType::I16 => Ok(AnyArray::I16(decompress_typed(encoded, &header.shape)?)),
408        Jpeg2000DType::U16 => Ok(AnyArray::U16(decompress_typed(encoded, &header.shape)?)),
409        Jpeg2000DType::I32 => Ok(AnyArray::I32(decompress_typed(encoded, &header.shape)?)),
410        Jpeg2000DType::U32 => Ok(AnyArray::U32(decompress_typed(encoded, &header.shape)?)),
411        Jpeg2000DType::I64 => Ok(AnyArray::I64(decompress_typed(encoded, &header.shape)?)),
412        Jpeg2000DType::U64 => Ok(AnyArray::U64(decompress_typed(encoded, &header.shape)?)),
413    }
414}
415
416/// Array element types which can be compressed with JPEG 2000.
417pub trait Jpeg2000Element: ffi::Jpeg2000Element + Zero {
418    /// The dtype representation of the type
419    const DTYPE: Jpeg2000DType;
420}
421
422impl Jpeg2000Element for i8 {
423    const DTYPE: Jpeg2000DType = Jpeg2000DType::I8;
424}
425impl Jpeg2000Element for u8 {
426    const DTYPE: Jpeg2000DType = Jpeg2000DType::U8;
427}
428impl Jpeg2000Element for i16 {
429    const DTYPE: Jpeg2000DType = Jpeg2000DType::I16;
430}
431impl Jpeg2000Element for u16 {
432    const DTYPE: Jpeg2000DType = Jpeg2000DType::U16;
433}
434impl Jpeg2000Element for i32 {
435    const DTYPE: Jpeg2000DType = Jpeg2000DType::I32;
436}
437impl Jpeg2000Element for u32 {
438    const DTYPE: Jpeg2000DType = Jpeg2000DType::U32;
439}
440impl Jpeg2000Element for i64 {
441    const DTYPE: Jpeg2000DType = Jpeg2000DType::I64;
442}
443impl Jpeg2000Element for u64 {
444    const DTYPE: Jpeg2000DType = Jpeg2000DType::U64;
445}
446
447#[derive(Serialize, Deserialize)]
448struct CompressionHeader<'a> {
449    dtype: Jpeg2000DType,
450    #[serde(borrow)]
451    shape: Cow<'a, [usize]>,
452    version: Jpeg2000CodecVersion,
453}
454
455/// Dtypes that JPEG 2000 can compress and decompress
456#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
457#[expect(missing_docs)]
458pub enum Jpeg2000DType {
459    #[serde(rename = "i8", alias = "int8")]
460    I8,
461    #[serde(rename = "u8", alias = "uint8")]
462    U8,
463    #[serde(rename = "i16", alias = "int16")]
464    I16,
465    #[serde(rename = "u16", alias = "uint16")]
466    U16,
467    #[serde(rename = "i32", alias = "int32")]
468    I32,
469    #[serde(rename = "u32", alias = "uint32")]
470    U32,
471    #[serde(rename = "i64", alias = "int64")]
472    I64,
473    #[serde(rename = "u64", alias = "uint64")]
474    U64,
475}
476
477impl fmt::Display for Jpeg2000DType {
478    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
479        fmt.write_str(match self {
480            Self::I8 => "i8",
481            Self::U8 => "u8",
482            Self::I16 => "i16",
483            Self::U16 => "u16",
484            Self::I32 => "i32",
485            Self::U32 => "u32",
486            Self::I64 => "i64",
487            Self::U64 => "u64",
488        })
489    }
490}
491
492#[cfg(test)]
493#[allow(clippy::unwrap_used)]
494mod tests {
495    use ndarray::{Ix0, Ix1, Ix2, Ix3, Ix4};
496
497    use super::*;
498
499    #[test]
500    fn zero_length() {
501        std::mem::drop(simple_logger::init());
502
503        let encoded = compress(
504            Array::<i16, _>::from_shape_vec([3, 0], vec![]).unwrap(),
505            &Jpeg2000CompressionMode::PSNR { psnr: 42.0 },
506        )
507        .unwrap();
508        let decoded = decompress(&encoded).unwrap();
509
510        assert_eq!(decoded.dtype(), AnyArrayDType::I16);
511        assert!(decoded.is_empty());
512        assert_eq!(decoded.shape(), &[3, 0]);
513    }
514
515    #[test]
516    fn small_2d() {
517        std::mem::drop(simple_logger::init());
518
519        let encoded = compress(
520            Array::<i16, _>::from_shape_vec([1, 1], vec![42]).unwrap(),
521            &Jpeg2000CompressionMode::PSNR { psnr: 42.0 },
522        )
523        .unwrap();
524        let decoded = decompress(&encoded).unwrap();
525
526        assert_eq!(decoded.dtype(), AnyArrayDType::I16);
527        assert_eq!(decoded.len(), 1);
528        assert_eq!(decoded.shape(), &[1, 1]);
529    }
530
531    #[test]
532    fn small_lossless_types() {
533        macro_rules! check {
534            ($T:ident($t:ident)) => {
535                check! { $T($t,$t::MIN,$t::MAX) }
536            };
537            ($T:ident($t:ident,$min:expr,$max:expr)) => {
538                let data = Array::<$t, _>::from_shape_vec([4, 1], vec![$min, 0, 42, $max]).unwrap();
539
540                let encoded = compress(
541                    data.view(),
542                    &Jpeg2000CompressionMode::Lossless,
543                )
544                .unwrap();
545                let decoded = decompress(&encoded).unwrap();
546
547                assert_eq!(decoded.len(), 4);
548                assert_eq!(decoded.shape(), &[4, 1]);
549                assert_eq!(decoded, AnyArray::$T(data.into_dyn()));
550            };
551            ($($T:ident($($tt:tt),*)),*) => {
552                $(check! { $T($($tt),*) })*
553            };
554        }
555
556        check! {
557            I8(i8), U8(u8), I16(i16), U16(u16),
558            I32(i32,(i32::MIN/(1<<7)),(i32::MAX/(1<<7))),
559            U32(u32,(u32::MIN),(u32::MAX/(1<<7))),
560            I64(i64,(i64::MIN/(1<<(32+7))),(i64::MAX/(1<<(32+7)))),
561            U64(u64,(u64::MIN),(u64::MAX/(1<<(32+7))))
562        }
563    }
564
565    #[test]
566    fn out_of_range() {
567        macro_rules! check {
568            ($T:ident($t:ident,$($v:expr),*)) => {
569                $(
570                    let data = Array::<$t, _>::from_shape_vec([1, 1], vec![$v]).unwrap();
571                    compress(
572                        data.view(),
573                        &Jpeg2000CompressionMode::Lossless,
574                    )
575                    .unwrap_err();
576                )*
577            };
578            ($($T:ident($($tt:tt),*)),*) => {
579                $(check! { $T($($tt),*) })*
580            };
581        }
582
583        check! {
584            I32(i32,(i32::MIN),(i32::MAX)), U32(u32,(u32::MAX)),
585            I64(i64,(i64::MIN),(i64::MAX)), U64(u64,(u64::MAX))
586        }
587    }
588
589    #[test]
590    fn large_2d() {
591        std::mem::drop(simple_logger::init());
592
593        let encoded = compress(
594            Array::<i16, _>::zeros((64, 64)),
595            &Jpeg2000CompressionMode::PSNR { psnr: 42.0 },
596        )
597        .unwrap();
598        let decoded = decompress(&encoded).unwrap();
599
600        assert_eq!(decoded.dtype(), AnyArrayDType::I16);
601        assert_eq!(decoded.len(), 64 * 64);
602        assert_eq!(decoded.shape(), &[64, 64]);
603    }
604
605    #[test]
606    fn all_modes() {
607        std::mem::drop(simple_logger::init());
608
609        for mode in [
610            Jpeg2000CompressionMode::PSNR { psnr: 42.0 },
611            Jpeg2000CompressionMode::Rate { rate: 5.0 },
612            Jpeg2000CompressionMode::Lossless,
613        ] {
614            let encoded = compress(Array::<i16, _>::zeros((64, 64)), &mode).unwrap();
615            let decoded = decompress(&encoded).unwrap();
616
617            assert_eq!(decoded.dtype(), AnyArrayDType::I16);
618            assert_eq!(decoded.len(), 64 * 64);
619            assert_eq!(decoded.shape(), &[64, 64]);
620        }
621    }
622
623    #[test]
624    fn many_dimensions() {
625        std::mem::drop(simple_logger::init());
626
627        for data in [
628            Array::<i16, Ix0>::from_shape_vec([], vec![42])
629                .unwrap()
630                .into_dyn(),
631            Array::<i16, Ix1>::from_shape_vec([2], vec![1, 2])
632                .unwrap()
633                .into_dyn(),
634            Array::<i16, Ix2>::from_shape_vec([2, 2], vec![1, 2, 3, 4])
635                .unwrap()
636                .into_dyn(),
637            Array::<i16, Ix3>::from_shape_vec([2, 2, 2], vec![1, 2, 3, 4, 5, 6, 7, 8])
638                .unwrap()
639                .into_dyn(),
640            Array::<i16, Ix4>::from_shape_vec(
641                [2, 2, 2, 2],
642                vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
643            )
644            .unwrap()
645            .into_dyn(),
646        ] {
647            let encoded = compress(data.view(), &Jpeg2000CompressionMode::Lossless).unwrap();
648            let decoded = decompress(&encoded).unwrap();
649
650            assert_eq!(decoded, AnyArray::I16(data));
651        }
652    }
653}