Skip to main content

dicom_toolkit_codec/jp2k/
decoder.rs

1//! JPEG 2000 decoder — wraps the forked `dicom-toolkit-jpeg2000` crate for DICOM use.
2//!
3//! Decodes raw J2K codestreams (as embedded in DICOM encapsulated pixel data)
4//! at native bit depth, preserving the full dynamic range of 12-bit and 16-bit
5//! medical images.
6
7use dicom_toolkit_core::error::{DcmError, DcmResult};
8
9/// Decoded JPEG 2000 frame with native bit-depth pixel data.
10#[derive(Debug, Clone)]
11pub struct DecodedFrame {
12    /// Raw pixel bytes. For bit_depth ≤ 8: one byte per sample.
13    /// For bit_depth > 8: two bytes per sample in little-endian order.
14    pub pixels: Vec<u8>,
15    /// Width of the decoded image.
16    pub width: u32,
17    /// Height of the decoded image.
18    pub height: u32,
19    /// Bits per sample (e.g., 8, 12, 16).
20    pub bits_per_sample: u8,
21    /// Number of components (1 = grayscale, 3 = RGB).
22    pub components: u8,
23}
24
25/// Decode a JPEG 2000 codestream into pixel data at native bit depth.
26///
27/// The input `data` should be a raw JPEG 2000 codestream (starting with
28/// `FF 4F` SOC marker), as embedded in DICOM encapsulated pixel data fragments.
29/// JP2 container format (starting with `00 00 00 0C 6A 50 20 20`) is also
30/// accepted.
31pub fn decode_jp2k(data: &[u8]) -> DcmResult<DecodedFrame> {
32    use dicom_toolkit_jpeg2000::{DecodeSettings, Image};
33
34    if data.is_empty() {
35        return Err(DcmError::DecompressionError {
36            reason: "empty JPEG 2000 codestream".into(),
37        });
38    }
39
40    let settings = DecodeSettings {
41        resolve_palette_indices: false,
42        strict: false,
43        target_resolution: None,
44    };
45
46    let image = Image::new(data, &settings).map_err(|e| DcmError::DecompressionError {
47        reason: format!("JPEG 2000 parse error: {e}"),
48    })?;
49
50    let raw = image
51        .decode_native()
52        .map_err(|e| DcmError::DecompressionError {
53            reason: format!("JPEG 2000 decode error: {e}"),
54        })?;
55
56    Ok(DecodedFrame {
57        pixels: raw.data,
58        width: raw.width,
59        height: raw.height,
60        bits_per_sample: raw.bit_depth,
61        components: raw.num_components,
62    })
63}
64
65#[cfg(test)]
66mod tests {
67    use super::*;
68
69    // SOC (FF4F) + SIZ marker for a minimal 2x2, 8-bit, 1-component image
70    // This is a smoke test — real J2K testing requires actual encoded images.
71
72    #[test]
73    fn decode_empty_returns_error() {
74        let result = decode_jp2k(&[]);
75        assert!(result.is_err());
76    }
77
78    #[test]
79    fn decode_garbage_returns_error() {
80        let result = decode_jp2k(&[0x00, 0x01, 0x02, 0x03]);
81        assert!(result.is_err());
82    }
83}