Skip to main content

dicom_toolkit_codec/
registry.rs

1//! Codec registry — maps DICOM transfer syntax UIDs to codec implementations.
2//!
3//! Ports DCMTK's `DcmCodecList` singleton. Codecs can be registered either
4//! statically (via compile-time dependencies) or dynamically.
5
6use std::collections::HashMap;
7use std::sync::{Arc, LazyLock, RwLock};
8
9use dicom_toolkit_core::error::{DcmError, DcmResult};
10use dicom_toolkit_data::value::PixelData;
11use dicom_toolkit_dict::ts::transfer_syntaxes;
12
13// ── ImageCodec trait ──────────────────────────────────────────────────────────
14
15/// Trait for DICOM image codecs.
16///
17/// Each codec handles one or more transfer syntaxes.  The codec is responsible
18/// for decompressing/compressing pixel data items.
19pub trait ImageCodec: Send + Sync {
20    /// UID(s) of the transfer syntax(es) this codec handles.
21    fn transfer_syntax_uids(&self) -> &[&str];
22
23    /// Decode all frames in `encapsulated` pixel data.
24    ///
25    /// Returns the raw, uncompressed pixel bytes for all frames concatenated.
26    fn decode(
27        &self,
28        encapsulated: &PixelData,
29        rows: u16,
30        columns: u16,
31        samples_per_pixel: u8,
32        bits_allocated: u8,
33    ) -> DcmResult<Vec<u8>>;
34
35    /// Encode raw pixel bytes into an encapsulated `PixelData`.
36    fn encode(
37        &self,
38        pixels: &[u8],
39        rows: u16,
40        columns: u16,
41        samples_per_pixel: u8,
42        bits_allocated: u8,
43    ) -> DcmResult<PixelData>;
44}
45
46// ── Built-in RLE codec ────────────────────────────────────────────────────────
47
48struct RleCodec;
49
50impl ImageCodec for RleCodec {
51    fn transfer_syntax_uids(&self) -> &[&str] {
52        &[transfer_syntaxes::RLE_LOSSLESS.uid]
53    }
54
55    fn decode(
56        &self,
57        pixel_data: &PixelData,
58        rows: u16,
59        columns: u16,
60        samples_per_pixel: u8,
61        bits_allocated: u8,
62    ) -> DcmResult<Vec<u8>> {
63        let fragments = match pixel_data {
64            PixelData::Encapsulated { fragments, .. } => fragments,
65            PixelData::Native { bytes } => return Ok(bytes.clone()),
66        };
67
68        let mut all_frames = Vec::new();
69        for fragment in fragments {
70            let frame = crate::rle::rle_decode_frame(
71                fragment,
72                rows,
73                columns,
74                samples_per_pixel,
75                bits_allocated,
76            )?;
77            all_frames.extend_from_slice(&frame);
78        }
79        Ok(all_frames)
80    }
81
82    fn encode(
83        &self,
84        pixels: &[u8],
85        rows: u16,
86        columns: u16,
87        samples_per_pixel: u8,
88        bits_allocated: u8,
89    ) -> DcmResult<PixelData> {
90        let encoded =
91            crate::rle::rle_encode_frame(pixels, rows, columns, samples_per_pixel, bits_allocated)?;
92        Ok(PixelData::Encapsulated {
93            offset_table: vec![0],
94            fragments: vec![encoded],
95        })
96    }
97}
98
99// ── Built-in JPEG codec ───────────────────────────────────────────────────────
100
101struct JpegCodec {
102    uids: Vec<&'static str>,
103}
104
105impl JpegCodec {
106    fn baseline() -> Self {
107        Self {
108            uids: vec![
109                transfer_syntaxes::JPEG_BASELINE.uid,
110                transfer_syntaxes::JPEG_EXTENDED.uid,
111            ],
112        }
113    }
114}
115
116impl ImageCodec for JpegCodec {
117    fn transfer_syntax_uids(&self) -> &[&str] {
118        &self.uids
119    }
120
121    fn decode(
122        &self,
123        pixel_data: &PixelData,
124        _rows: u16,
125        _columns: u16,
126        _samples_per_pixel: u8,
127        _bits_allocated: u8,
128    ) -> DcmResult<Vec<u8>> {
129        let fragments = match pixel_data {
130            PixelData::Encapsulated { fragments, .. } => fragments,
131            PixelData::Native { bytes } => return Ok(bytes.clone()),
132        };
133
134        let mut all_frames = Vec::new();
135        for fragment in fragments {
136            let frame = crate::jpeg::decoder::decode_jpeg(fragment)?;
137            all_frames.extend_from_slice(&frame.data);
138        }
139        Ok(all_frames)
140    }
141
142    fn encode(
143        &self,
144        pixels: &[u8],
145        rows: u16,
146        columns: u16,
147        samples_per_pixel: u8,
148        _bits_allocated: u8,
149    ) -> DcmResult<PixelData> {
150        use crate::jpeg::params::JpegParams;
151        let encoded = crate::jpeg::encoder::encode_jpeg(
152            pixels,
153            columns,
154            rows,
155            samples_per_pixel,
156            &JpegParams::default(),
157        )?;
158        Ok(PixelData::Encapsulated {
159            offset_table: vec![0],
160            fragments: vec![encoded],
161        })
162    }
163}
164
165// ── JPEG-LS codec ─────────────────────────────────────────────────────────────
166
167struct JpegLsCodec;
168
169impl ImageCodec for JpegLsCodec {
170    fn transfer_syntax_uids(&self) -> &[&str] {
171        &[
172            transfer_syntaxes::JPEG_LS_LOSSLESS.uid,
173            transfer_syntaxes::JPEG_LS_LOSSY.uid,
174        ]
175    }
176
177    fn decode(
178        &self,
179        pixel_data: &PixelData,
180        _rows: u16,
181        _columns: u16,
182        _samples_per_pixel: u8,
183        _bits_allocated: u8,
184    ) -> DcmResult<Vec<u8>> {
185        let fragments = match pixel_data {
186            PixelData::Encapsulated { fragments, .. } => fragments,
187            PixelData::Native { bytes } => return Ok(bytes.clone()),
188        };
189        let empty = vec![];
190        let data = fragments.first().unwrap_or(&empty);
191        let decoded = crate::jpeg_ls::decoder::decode_jpeg_ls(data)?;
192        Ok(decoded.pixels)
193    }
194
195    fn encode(
196        &self,
197        pixels: &[u8],
198        rows: u16,
199        columns: u16,
200        samples_per_pixel: u8,
201        bits_allocated: u8,
202    ) -> DcmResult<PixelData> {
203        let near = 0; // Lossless by default.
204        let encoded = crate::jpeg_ls::encoder::encode_jpeg_ls(
205            pixels,
206            columns as u32,
207            rows as u32,
208            bits_allocated,
209            samples_per_pixel,
210            near,
211        )?;
212        Ok(PixelData::Encapsulated {
213            offset_table: vec![],
214            fragments: vec![encoded],
215        })
216    }
217}
218
219// ── CodecRegistry ─────────────────────────────────────────────────────────────
220
221/// Registry of all available image codecs, keyed by transfer syntax UID.
222pub struct CodecRegistry {
223    codecs: RwLock<HashMap<String, Arc<dyn ImageCodec>>>,
224}
225
226impl CodecRegistry {
227    /// Create an empty registry.
228    pub fn new() -> Self {
229        Self {
230            codecs: RwLock::new(HashMap::new()),
231        }
232    }
233
234    /// Register a codec (replaces any existing codec for the same UID).
235    pub fn register(&self, codec: Arc<dyn ImageCodec>) {
236        let mut map = self.codecs.write().unwrap();
237        for uid in codec.transfer_syntax_uids() {
238            map.insert(uid.to_string(), Arc::clone(&codec));
239        }
240    }
241
242    /// Look up a codec by transfer syntax UID.
243    pub fn find(&self, transfer_syntax_uid: &str) -> Option<Arc<dyn ImageCodec>> {
244        self.codecs
245            .read()
246            .unwrap()
247            .get(transfer_syntax_uid)
248            .cloned()
249    }
250
251    /// Look up a codec or return a [`DcmError::NoCodec`] error.
252    pub fn find_required(&self, transfer_syntax_uid: &str) -> DcmResult<Arc<dyn ImageCodec>> {
253        self.find(transfer_syntax_uid)
254            .ok_or_else(|| DcmError::NoCodec {
255                uid: transfer_syntax_uid.to_string(),
256            })
257    }
258}
259
260impl Default for CodecRegistry {
261    fn default() -> Self {
262        Self::new()
263    }
264}
265
266// ── Global registry ───────────────────────────────────────────────────────────
267
268/// Global codec registry, pre-populated with built-in codecs.
269pub static GLOBAL_REGISTRY: LazyLock<CodecRegistry> = LazyLock::new(|| {
270    let reg = CodecRegistry::new();
271    reg.register(Arc::new(RleCodec));
272    reg.register(Arc::new(JpegCodec::baseline()));
273    reg.register(Arc::new(JpegLsCodec));
274    reg
275});
276
277// ── Flat functional API ───────────────────────────────────────────────────────
278
279/// Transfer syntax UIDs that this crate can decode.
280const SUPPORTED_TS: &[&str] = &[
281    transfer_syntaxes::RLE_LOSSLESS.uid,
282    transfer_syntaxes::JPEG_BASELINE.uid,
283    transfer_syntaxes::JPEG_EXTENDED.uid,
284    transfer_syntaxes::JPEG_LOSSLESS.uid,
285    transfer_syntaxes::JPEG_LOSSLESS_SV1.uid,
286    transfer_syntaxes::JPEG_LS_LOSSLESS.uid,
287    transfer_syntaxes::JPEG_LS_LOSSY.uid,
288];
289
290/// Registered codec information for a transfer syntax.
291#[derive(Debug, Clone, Copy)]
292pub struct CodecInfo {
293    /// Transfer Syntax UID this codec handles.
294    pub transfer_syntax_uid: &'static str,
295    /// Human-readable name.
296    pub name: &'static str,
297}
298
299/// Returns `true` if a decoder is available for the given transfer syntax UID.
300pub fn can_decode(ts_uid: &str) -> bool {
301    SUPPORTED_TS.contains(&ts_uid)
302}
303
304/// Returns all transfer syntax UIDs that this crate can decode.
305pub fn supported_transfer_syntaxes() -> &'static [&'static str] {
306    SUPPORTED_TS
307}
308
309/// Decode a single pixel-data fragment for the given transfer syntax.
310///
311/// `data` must be the raw fragment bytes (RLE header + data, or JPEG bitstream).
312/// Returns the decoded pixel bytes in native little-endian order.
313pub fn decode_pixel_data(
314    ts_uid: &str,
315    data: &[u8],
316    rows: u16,
317    cols: u16,
318    bits_allocated: u16,
319    samples: u16,
320) -> DcmResult<Vec<u8>> {
321    match ts_uid {
322        uid if uid == transfer_syntaxes::RLE_LOSSLESS.uid => {
323            crate::rle::RleCodec::decode(data, rows, cols, bits_allocated, samples)
324        }
325        uid if uid == transfer_syntaxes::JPEG_BASELINE.uid
326            || uid == transfer_syntaxes::JPEG_EXTENDED.uid
327            || uid == transfer_syntaxes::JPEG_LOSSLESS.uid
328            || uid == transfer_syntaxes::JPEG_LOSSLESS_SV1.uid =>
329        {
330            crate::jpeg::JpegDecoder::decode_frame(data).map(|f| f.pixels)
331        }
332        uid if uid == transfer_syntaxes::JPEG_LS_LOSSLESS.uid
333            || uid == transfer_syntaxes::JPEG_LS_LOSSY.uid =>
334        {
335            crate::jpeg_ls::JpegLsCodec::decode_frame(data).map(|f| f.pixels)
336        }
337        uid => Err(DcmError::NoCodec {
338            uid: uid.to_string(),
339        }),
340    }
341}
342
343// ── Tests ─────────────────────────────────────────────────────────────────────
344
345#[cfg(test)]
346mod tests {
347    use super::*;
348    use dicom_toolkit_dict::ts::transfer_syntaxes;
349
350    #[test]
351    fn global_registry_has_rle() {
352        let codec = GLOBAL_REGISTRY.find(transfer_syntaxes::RLE_LOSSLESS.uid);
353        assert!(codec.is_some());
354    }
355
356    #[test]
357    fn global_registry_has_jpeg_baseline() {
358        let codec = GLOBAL_REGISTRY.find(transfer_syntaxes::JPEG_BASELINE.uid);
359        assert!(codec.is_some());
360    }
361
362    #[test]
363    fn global_registry_has_jpeg_extended() {
364        let codec = GLOBAL_REGISTRY.find(transfer_syntaxes::JPEG_EXTENDED.uid);
365        assert!(codec.is_some());
366    }
367
368    #[test]
369    fn global_registry_has_jpeg_ls() {
370        let codec = GLOBAL_REGISTRY.find(transfer_syntaxes::JPEG_LS_LOSSLESS.uid);
371        assert!(codec.is_some());
372    }
373
374    #[test]
375    fn unknown_uid_returns_none() {
376        let codec = GLOBAL_REGISTRY.find("1.2.3.4.5.999");
377        assert!(codec.is_none());
378    }
379
380    #[test]
381    fn find_required_returns_error_for_unknown() {
382        let result = GLOBAL_REGISTRY.find_required("1.9.9.9.9");
383        assert!(matches!(result, Err(DcmError::NoCodec { .. })));
384    }
385
386    // ── Flat API tests ────────────────────────────────────────────────────────
387
388    #[test]
389    fn codec_registry_can_decode_rle() {
390        assert!(can_decode(transfer_syntaxes::RLE_LOSSLESS.uid));
391    }
392
393    #[test]
394    fn codec_registry_can_decode_jpeg_baseline() {
395        assert!(can_decode(transfer_syntaxes::JPEG_BASELINE.uid));
396    }
397
398    #[test]
399    fn codec_registry_cannot_decode_unknown() {
400        assert!(!can_decode("1.2.3.4.5.999"));
401    }
402
403    #[test]
404    fn supported_transfer_syntaxes_is_non_empty() {
405        let list = supported_transfer_syntaxes();
406        assert!(!list.is_empty());
407        assert!(list.contains(&transfer_syntaxes::RLE_LOSSLESS.uid));
408        assert!(list.contains(&transfer_syntaxes::JPEG_BASELINE.uid));
409    }
410
411    #[test]
412    fn rle_codec_roundtrip_via_registry() {
413        use crate::rle::rle_encode_frame;
414
415        let rows = 4u16;
416        let cols = 4u16;
417        let samples = 1u8;
418        let bits = 8u8;
419        let pixels: Vec<u8> = (0u8..16).collect();
420
421        let encoded_frame = rle_encode_frame(&pixels, rows, cols, samples, bits).unwrap();
422        let pixel_data = PixelData::Encapsulated {
423            offset_table: vec![0],
424            fragments: vec![encoded_frame],
425        };
426
427        let codec = GLOBAL_REGISTRY
428            .find(transfer_syntaxes::RLE_LOSSLESS.uid)
429            .unwrap();
430        let decoded = codec
431            .decode(&pixel_data, rows, cols, samples, bits)
432            .unwrap();
433        assert_eq!(&decoded[..16], &pixels[..]);
434    }
435}