Skip to main content

dicom_transfer_syntax_registry/
lib.rs

1#![deny(trivial_numeric_casts, unsafe_code, unstable_features)]
2#![warn(
3    missing_debug_implementations,
4    missing_docs,
5    unused_qualifications,
6    unused_import_braces
7)]
8//! This crate contains the DICOM transfer syntax registry.
9//!
10//! The transfer syntax registry maps a DICOM UID of a transfer syntax (TS)
11//! into the respective transfer syntax specifier.
12//! This specifier defines:
13//!
14//! 1. how to read and write DICOM data sets;
15//! 2. how to decode and encode pixel data.
16//!
17//! Support may be partial, in which case the data set can be retrieved
18//! but the pixel data may not be decoded through the DICOM-rs ecosystem.
19//! By default, adapters for encapsulated pixel data
20//! need to be explicitly added by dependent projects,
21//! such as `dicom-pixeldata`.
22//! When adding `dicom-transfer-syntax-registry` yourself,
23//! to include support for some transfer syntaxes with encapsulated pixel data,
24//! add the **`native`** Cargo feature
25//! or one of the other image encoding features available.
26//!
27//! By default, a fixed known set of transfer syntaxes are provided as built in.
28//! Moreover, support for more TSes can be extended by other crates
29//! through the [inventory] pattern,
30//! in which the registry is automatically populated before main.
31//! This is done by enabling the Cargo feature **`inventory-registry`**.
32//! The feature can be left disabled
33//! for environments which do not support `inventory`,
34//! with the downside of only providing the built-in transfer syntaxes.
35//!
36//! All registered TSes will be readily available
37//! through the [`TransferSyntaxRegistry`] type.
38//!
39//! This registry is intended to be used in the development of higher level APIs,
40//! which should learn to negotiate and resolve the expected
41//! transfer syntax automatically.
42//!
43//! ## Transfer Syntaxes
44//!
45//! This crate encompasses basic DICOM level of conformance,
46//! plus support for some transfer syntaxes with compressed pixel data.
47//! _Implicit VR Little Endian_,
48//! _Explicit VR Little Endian_,
49//! and _Explicit VR Big Endian_
50//! are fully supported.
51//! Support may vary for transfer syntaxes which rely on encapsulated pixel data.
52//!
53//! | transfer syntax               | decoding support     | encoding support |
54//! |-------------------------------|----------------------|------------------|
55//! | Deflated Explicit VR Little Endian | Cargo feature `deflate` | ✓ |
56//! | JPEG Baseline (Process 1)     | Cargo feature `jpeg` | ✓ |
57//! | JPEG Extended (Process 2 & 4) | Cargo feature `jpeg` | x |
58//! | JPEG Lossless, Non-Hierarchical (Process 14) | Cargo feature `jpeg` | x |
59//! | JPEG Lossless, Non-Hierarchical, First-Order Prediction (Process 14 [Selection Value 1]) | Cargo feature `jpeg` | x |
60//! | JPEG-LS Lossless              | Cargo feature `charls` | ✓ |
61//! | JPEG-LS Lossy (Near-Lossless) | Cargo feature `charls` | ✓ |
62//! | JPEG 2000 (Lossless Only)     | Cargo feature `openjp2` or `openjpeg-sys` | x |
63//! | JPEG 2000                     | Cargo feature `openjp2` or `openjpeg-sys` | x |
64//! | JPEG 2000 Part 2 Multi-component Image Compression (Lossless Only) | Cargo feature `openjp2` or `openjpeg-sys` | x |
65//! | JPEG 2000 Part 2 Multi-component Image Compression | Cargo feature `openjp2` or `openjpeg-sys` | x |
66//! | JPIP Referenced Deflate       | Cargo feature `deflate` | ✓ |
67//! | High-Throughput JPEG 2000 (Lossless Only) | Cargo feature `openjp2` or `openjpeg-sys` | x |
68//! | High-Throughput JPEG 2000 with RPCL Options (Lossless Only) | Cargo feature `openjp2` or `openjpeg-sys` | x |
69//! | High-Throughput JPEG 2000     | Cargo feature `openjp2` or `openjpeg-sys` | x |
70//! | JPIP HTJ2K Referenced Deflate | Cargo feature `deflate` | ✓ |
71//! | JPEG XL Lossless              | Cargo feature `jxl-oxide` | ✓ (Cargo feature `zune-jpegxl`) |
72//! | JPEG XL Recompression         | Cargo feature `jxl-oxide` | x |
73//! | JPEG XL                       | Cargo feature `jxl-oxide` | ✓ (Cargo feature `zune-jpegxl`) |
74//! | RLE Lossless                  | Cargo feature `rle` | x |
75//! | Deflated Image Frame          | Cargo feature `deflate` | ✓ |
76//!
77//! Cargo features behind `native` (`jpeg`, `rle`, `deflate`)
78//! are added by default in the [`dicom-pixeldata` crate][dicom-pixeldata].
79//! They provide implementations that are written in pure Rust
80//! and are likely available in all supported platforms without issues.
81//! Additional codecs are opt-in by enabling Cargo features,
82//! for scenarios where a native implementation is not available,
83//! or alternative implementations are available.
84//!
85//! - `charls` provides support for JPEG-LS
86//!   by linking to the CharLS reference implementation,
87//!   which is written in C++.
88//!   No alternative JPEG-LS implementations are available at the moment.
89//! - `openjpeg-sys` provides a binding to the OpenJPEG reference implementation,
90//!   which is written in C and is statically linked.
91//!   It may offer better performance than the pure Rust implementation,
92//!   but cannot be used in WebAssembly.
93//!   Include `openjpeg-sys-threads` to build OpenJPEG with multithreading.
94//! - `openjp2` provides a binding to a computer-translated Rust port of OpenJPEG.
95//!   Due to the nature of this crate,
96//!   it might not work on all modern platforms.
97//! - `jxl-oxide` and `zune-jpegxl` offer JPEG XL decoding and encoding.
98//!
99//! Transfer syntaxes which are not supported,
100//! either due to being unable to read the data set
101//! or decode encapsulated pixel data,
102//! are listed as _stubs_ for partial support.
103//! The full list is available in the [`entries`] module.
104//! These stubs may also be replaced by separate libraries
105//! if using the inventory-based registry.
106//!
107//! [inventory]: https://docs.rs/inventory/0.3.15/inventory
108//! [dicom-pixeldata]: https://docs.rs/dicom-pixeldata
109
110use dicom_encoding::transfer_syntax::{AdapterFreeTransferSyntax as Ts, Codec};
111use lazy_static::lazy_static;
112use std::collections::hash_map::Entry;
113use std::collections::HashMap;
114use std::fmt;
115
116pub use dicom_encoding::{TransferSyntax, TransferSyntaxIndex};
117pub mod entries;
118
119mod adapters;
120#[cfg(feature = "deflate")]
121mod deflate;
122
123#[cfg(feature = "inventory-registry")]
124pub use dicom_encoding::inventory;
125
126/// Main implementation of a registry of DICOM transfer syntaxes.
127///
128/// Consumers would generally use [`TransferSyntaxRegistry`] instead.
129pub struct TransferSyntaxRegistryImpl {
130    m: HashMap<&'static str, TransferSyntax>,
131}
132
133impl fmt::Debug for TransferSyntaxRegistryImpl {
134    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
135        let entries: HashMap<&str, &str> =
136            self.m.iter().map(|(uid, ts)| (*uid, ts.name())).collect();
137        f.debug_struct("TransferSyntaxRegistryImpl")
138            .field("m", &entries)
139            .finish()
140    }
141}
142
143impl TransferSyntaxRegistryImpl {
144    /// Obtain an iterator of all registered transfer syntaxes.
145    pub fn iter(&self) -> impl Iterator<Item = &TransferSyntax> {
146        self.m.values()
147    }
148
149    /// Obtain a DICOM codec by transfer syntax UID.
150    fn get<U: AsRef<str>>(&self, uid: U) -> Option<&TransferSyntax> {
151        let ts_uid = uid
152            .as_ref()
153            .trim_end_matches(|c: char| c.is_whitespace() || c == '\0');
154        self.m.get(ts_uid)
155    }
156
157    /// Register the given transfer syntax (TS) to the system. It can override
158    /// another TS with the same UID, in the only case that the TS requires
159    /// certain codecs which are not supported by the previously registered
160    /// TS. If no such requirements are imposed, this function returns `false`
161    /// and no changes are made.
162    fn register(&mut self, ts: TransferSyntax) -> bool {
163        match self.m.entry(ts.uid()) {
164            Entry::Occupied(mut e) => {
165                let replace = match (&e.get().codec(), ts.codec()) {
166                    (Codec::Dataset(None), Codec::Dataset(Some(_)))
167                    | (
168                        Codec::EncapsulatedPixelData(None, None),
169                        Codec::EncapsulatedPixelData(..),
170                    )
171                    | (
172                        Codec::EncapsulatedPixelData(Some(_), None),
173                        Codec::EncapsulatedPixelData(Some(_), Some(_)),
174                    )
175                    | (
176                        Codec::EncapsulatedPixelData(None, Some(_)),
177                        Codec::EncapsulatedPixelData(Some(_), Some(_)),
178                    ) => true,
179                    // weird one ahead: the two specifiers do not agree on
180                    // requirements, better keep it as a separate match arm for
181                    // debugging purposes
182                    (Codec::Dataset(None), Codec::EncapsulatedPixelData(_, _)) => {
183                        tracing::warn!("Inconsistent requirements for transfer syntax {}: `Dataset` cannot be replaced by `EncapsulatedPixelData`", ts.uid());
184                        false
185                    }
186                    // another weird one:
187                    // the two codecs do not agree on requirements
188                    (Codec::EncapsulatedPixelData(_, _), Codec::Dataset(None)) => {
189                        tracing::warn!("Inconsistent requirements for transfer syntax {}: `EncapsulatedPixelData` cannot be replaced by `Dataset`", ts.uid());
190                        false
191                    }
192                    // ignoring TS with less or equal implementation
193                    _ => false,
194                };
195
196                if replace {
197                    e.insert(ts);
198                    true
199                } else {
200                    false
201                }
202            }
203            Entry::Vacant(e) => {
204                e.insert(ts);
205                true
206            }
207        }
208    }
209}
210
211impl TransferSyntaxIndex for TransferSyntaxRegistryImpl {
212    #[inline]
213    fn get(&self, uid: &str) -> Option<&TransferSyntax> {
214        Self::get(self, uid)
215    }
216}
217
218impl TransferSyntaxRegistry {
219    /// Obtain an iterator of all registered transfer syntaxes.
220    #[inline]
221    pub fn iter(&self) -> impl Iterator<Item = &TransferSyntax> {
222        get_registry().iter()
223    }
224}
225
226/// Zero-sized representative of the main transfer syntax registry.
227#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)]
228pub struct TransferSyntaxRegistry;
229
230impl TransferSyntaxIndex for TransferSyntaxRegistry {
231    #[inline]
232    fn get(&self, uid: &str) -> Option<&TransferSyntax> {
233        get_registry().get(uid)
234    }
235}
236
237lazy_static! {
238
239    static ref REGISTRY: TransferSyntaxRegistryImpl = {
240        let mut registry = TransferSyntaxRegistryImpl {
241            m: HashMap::with_capacity(64),
242        };
243
244        use self::entries::*;
245        let built_in_ts: [TransferSyntax; 46] = [
246            IMPLICIT_VR_LITTLE_ENDIAN.erased(),
247            EXPLICIT_VR_LITTLE_ENDIAN.erased(),
248            EXPLICIT_VR_BIG_ENDIAN.erased(),
249
250            ENCAPSULATED_UNCOMPRESSED_EXPLICIT_VR_LITTLE_ENDIAN.erased(),
251
252            DEFLATED_EXPLICIT_VR_LITTLE_ENDIAN.erased(),
253            JPIP_REFERENCED_DEFLATE.erased(),
254            JPIP_HTJ2K_REFERENCED_DEFLATE.erased(),
255
256            JPEG_BASELINE.erased(),
257            JPEG_EXTENDED.erased(),
258            JPEG_LOSSLESS_NON_HIERARCHICAL.erased(),
259            JPEG_LOSSLESS_NON_HIERARCHICAL_FIRST_ORDER_PREDICTION.erased(),
260            JPEG_LS_LOSSLESS_IMAGE_COMPRESSION.erased(),
261            JPEG_LS_LOSSY_IMAGE_COMPRESSION.erased(),
262            JPEG_2000_IMAGE_COMPRESSION_LOSSLESS_ONLY.erased(),
263            JPEG_2000_IMAGE_COMPRESSION.erased(),
264            JPEG_2000_PART2_MULTI_COMPONENT_IMAGE_COMPRESSION_LOSSLESS_ONLY.erased(),
265            JPEG_2000_PART2_MULTI_COMPONENT_IMAGE_COMPRESSION.erased(),
266            HIGH_THROUGHPUT_JPEG_2000_IMAGE_COMPRESSION_LOSSLESS_ONLY.erased(),
267            HIGH_THROUGHPUT_JPEG_2000_WITH_RPCL_OPTIONS_IMAGE_COMPRESSION_LOSSLESS_ONLY.erased(),
268            HIGH_THROUGHPUT_JPEG_2000_IMAGE_COMPRESSION.erased(),
269            JPEG_XL_LOSSLESS.erased(),
270            JPEG_XL_RECOMPRESSION.erased(),
271            JPEG_XL.erased(),
272            JPIP_REFERENCED.erased(),
273            JPIP_HTJ2K_REFERENCED.erased(),
274            MPEG2_MAIN_PROFILE_MAIN_LEVEL.erased(),
275            FRAGMENTABLE_MPEG2_MAIN_PROFILE_MAIN_LEVEL.erased(),
276            MPEG2_MAIN_PROFILE_HIGH_LEVEL.erased(),
277            FRAGMENTABLE_MPEG2_MAIN_PROFILE_HIGH_LEVEL.erased(),
278            MPEG4_AVC_H264_HIGH_PROFILE.erased(),
279            FRAGMENTABLE_MPEG4_AVC_H264_HIGH_PROFILE.erased(),
280            MPEG4_AVC_H264_BD_COMPATIBLE_HIGH_PROFILE.erased(),
281            FRAGMENTABLE_MPEG4_AVC_H264_BD_COMPATIBLE_HIGH_PROFILE.erased(),
282            MPEG4_AVC_H264_HIGH_PROFILE_FOR_2D_VIDEO.erased(),
283            FRAGMENTABLE_MPEG4_AVC_H264_HIGH_PROFILE_FOR_2D_VIDEO.erased(),
284            MPEG4_AVC_H264_HIGH_PROFILE_FOR_3D_VIDEO.erased(),
285            FRAGMENTABLE_MPEG4_AVC_H264_HIGH_PROFILE_FOR_3D_VIDEO.erased(),
286            MPEG4_AVC_H264_STEREO_HIGH_PROFILE.erased(),
287            FRAGMENTABLE_MPEG4_AVC_H264_STEREO_HIGH_PROFILE.erased(),
288            HEVC_H265_MAIN_PROFILE.erased(),
289            HEVC_H265_MAIN_10_PROFILE.erased(),
290            RLE_LOSSLESS.erased(),
291            DEFLATED_IMAGE_FRAME_COMPRESSION.erased(),
292            SMPTE_ST_2110_20_UNCOMPRESSED_PROGRESSIVE.erased(),
293            SMPTE_ST_2110_20_UNCOMPRESSED_INTERLACED.erased(),
294            SMPTE_ST_2110_30_PCM.erased(),
295        ];
296
297        // add built-in TSes manually
298        for ts in built_in_ts {
299            registry.register(ts);
300        }
301        // add TSes from inventory, if available
302        inventory_populate(&mut registry);
303
304        registry
305    };
306}
307
308#[cfg(feature = "inventory-registry")]
309#[inline]
310fn inventory_populate(registry: &mut TransferSyntaxRegistryImpl) {
311    use dicom_encoding::transfer_syntax::TransferSyntaxFactory;
312
313    for TransferSyntaxFactory(tsf) in inventory::iter::<TransferSyntaxFactory> {
314        let ts = tsf();
315        registry.register(ts);
316    }
317}
318
319#[cfg(not(feature = "inventory-registry"))]
320#[inline]
321fn inventory_populate(_: &mut TransferSyntaxRegistryImpl) {
322    // do nothing
323}
324
325/// Retrieve a reference to the global codec registry.
326#[inline]
327pub(crate) fn get_registry() -> &'static TransferSyntaxRegistryImpl {
328    &REGISTRY
329}
330
331/// create a TS with an unsupported pixel encapsulation
332pub(crate) const fn create_ts_stub(uid: &'static str, name: &'static str) -> Ts {
333    TransferSyntax::new_ele(uid, name, Codec::EncapsulatedPixelData(None, None))
334}
335
336/// Retrieve the default transfer syntax.
337pub fn default() -> Ts {
338    entries::IMPLICIT_VR_LITTLE_ENDIAN
339}
340
341#[cfg(test)]
342mod tests {
343    use dicom_encoding::TransferSyntaxIndex;
344
345    use crate::TransferSyntaxRegistry;
346
347    #[test]
348    fn has_mandatory_tss() {
349        let implicit_vr_le = TransferSyntaxRegistry
350            .get("1.2.840.10008.1.2")
351            .expect("transfer syntax registry should provide Implicit VR Little Endian");
352        assert_eq!(implicit_vr_le.uid(), "1.2.840.10008.1.2");
353        assert!(implicit_vr_le.is_fully_supported());
354
355        // should also work with trailing null character
356        let implicit_vr_le_2 = TransferSyntaxRegistry.get("1.2.840.10008.1.2\0").expect(
357            "transfer syntax registry should provide Implicit VR Little Endian with padded TS UID",
358        );
359
360        assert_eq!(implicit_vr_le_2.uid(), implicit_vr_le.uid());
361
362        let explicit_vr_le = TransferSyntaxRegistry
363            .get("1.2.840.10008.1.2.1")
364            .expect("transfer syntax registry should provide Explicit VR Little Endian");
365        assert_eq!(explicit_vr_le.uid(), "1.2.840.10008.1.2.1");
366        assert!(explicit_vr_le.is_fully_supported());
367
368        // should also work with trailing null character
369        let explicit_vr_le_2 = TransferSyntaxRegistry.get("1.2.840.10008.1.2.1\0").expect(
370            "transfer syntax registry should provide Explicit VR Little Endian with padded TS UID",
371        );
372
373        assert_eq!(explicit_vr_le_2.uid(), explicit_vr_le.uid());
374    }
375
376    #[test]
377    fn provides_iter() {
378        let all_tss: Vec<_> = TransferSyntaxRegistry.iter().collect();
379
380        assert!(all_tss.len() >= 2);
381
382        // contains at least Implicit VR Little Endian and Explicit VR Little Endian
383        assert!(all_tss.iter().any(|ts| ts.uid() == "1.2.840.10008.1.2"));
384        assert!(all_tss.iter().any(|ts| ts.uid() == "1.2.840.10008.1.2.1"));
385    }
386}