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 `jpegxl` | ✓ |
72//! | JPEG XL Recompression         | Cargo feature `jpegxl` | x |
73//! | JPEG XL                       | Cargo feature `jpegxl` | ✓ |
74//! | RLE Lossless                  | Cargo feature `rle` | x |
75//!
76//! Cargo features behind `native` (`jpeg`, `rle`) are added by default.
77//! They provide implementations that are written in pure Rust
78//! and are likely available in all supported platforms without issues.
79//! Additional codecs are opt-in by enabling Cargo features,
80//! for scenarios where a native implementation is not available,
81//! or alternative implementations are available.
82//!
83//! - `charls` provides support for JPEG-LS
84//!   by linking to the CharLS reference implementation,
85//!   which is written in C++.
86//!   No alternative JPEG-LS implementations are available at the moment.
87//! - `openjpeg-sys` provides a binding to the OpenJPEG reference implementation,
88//!   which is written in C and is statically linked.
89//!   It may offer better performance than the pure Rust implementation,
90//!   but cannot be used in WebAssembly.
91//!   Include `openjpeg-sys-threads` to build OpenJPEG with multithreading.
92//! - `openjp2` provides a binding to a computer-translated Rust port of OpenJPEG.
93//!   Due to the nature of this crate,
94//!   it might not work on all modern platforms.
95//! - `jpegxl` adds JPEG XL support using `jxl-oxide` for decoding
96//!   and `zune-jpegxl` for encoding.
97//!
98//! Transfer syntaxes which are not supported,
99//! either due to being unable to read the data set
100//! or decode encapsulated pixel data,
101//! are listed as _stubs_ for partial support.
102//! The full list is available in the [`entries`] module.
103//! These stubs may also be replaced by separate libraries
104//! if using the inventory-based registry.
105//!
106//! [inventory]: https://docs.rs/inventory/0.3.15/inventory
107
108use dicom_encoding::transfer_syntax::{AdapterFreeTransferSyntax as Ts, Codec};
109use lazy_static::lazy_static;
110use std::collections::hash_map::Entry;
111use std::collections::HashMap;
112use std::fmt;
113
114pub use dicom_encoding::{TransferSyntax, TransferSyntaxIndex};
115pub mod entries;
116
117mod adapters;
118#[cfg(feature = "deflate")]
119mod deflate;
120
121#[cfg(feature = "inventory-registry")]
122pub use dicom_encoding::inventory;
123
124/// Main implementation of a registry of DICOM transfer syntaxes.
125///
126/// Consumers would generally use [`TransferSyntaxRegistry`] instead.
127pub struct TransferSyntaxRegistryImpl {
128    m: HashMap<&'static str, TransferSyntax>,
129}
130
131impl fmt::Debug for TransferSyntaxRegistryImpl {
132    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
133        let entries: HashMap<&str, &str> =
134            self.m.iter().map(|(uid, ts)| (*uid, ts.name())).collect();
135        f.debug_struct("TransferSyntaxRegistryImpl")
136            .field("m", &entries)
137            .finish()
138    }
139}
140
141impl TransferSyntaxRegistryImpl {
142    /// Obtain an iterator of all registered transfer syntaxes.
143    pub fn iter(&self) -> impl Iterator<Item = &TransferSyntax> {
144        self.m.values()
145    }
146
147    /// Obtain a DICOM codec by transfer syntax UID.
148    fn get<U: AsRef<str>>(&self, uid: U) -> Option<&TransferSyntax> {
149        let ts_uid = uid
150            .as_ref()
151            .trim_end_matches(|c: char| c.is_whitespace() || c == '\0');
152        self.m.get(ts_uid)
153    }
154
155    /// Register the given transfer syntax (TS) to the system. It can override
156    /// another TS with the same UID, in the only case that the TS requires
157    /// certain codecs which are not supported by the previously registered
158    /// TS. If no such requirements are imposed, this function returns `false`
159    /// and no changes are made.
160    fn register(&mut self, ts: TransferSyntax) -> bool {
161        match self.m.entry(ts.uid()) {
162            Entry::Occupied(mut e) => {
163                let replace = match (&e.get().codec(), ts.codec()) {
164                    (Codec::Dataset(None), Codec::Dataset(Some(_)))
165                    | (
166                        Codec::EncapsulatedPixelData(None, None),
167                        Codec::EncapsulatedPixelData(..),
168                    )
169                    | (
170                        Codec::EncapsulatedPixelData(Some(_), None),
171                        Codec::EncapsulatedPixelData(Some(_), Some(_)),
172                    )
173                    | (
174                        Codec::EncapsulatedPixelData(None, Some(_)),
175                        Codec::EncapsulatedPixelData(Some(_), Some(_)),
176                    ) => true,
177                    // weird one ahead: the two specifiers do not agree on
178                    // requirements, better keep it as a separate match arm for
179                    // debugging purposes
180                    (Codec::Dataset(None), Codec::EncapsulatedPixelData(_, _)) => {
181                        tracing::warn!("Inconsistent requirements for transfer syntax {}: `Dataset` cannot be replaced by `EncapsulatedPixelData`", ts.uid());
182                        false
183                    }
184                    // another weird one:
185                    // the two codecs do not agree on requirements
186                    (Codec::EncapsulatedPixelData(_, _), Codec::Dataset(None)) => {
187                        tracing::warn!("Inconsistent requirements for transfer syntax {}: `EncapsulatedPixelData` cannot be replaced by `Dataset`", ts.uid());
188                        false
189                    }
190                    // ignoring TS with less or equal implementation
191                    _ => false,
192                };
193
194                if replace {
195                    e.insert(ts);
196                    true
197                } else {
198                    false
199                }
200            }
201            Entry::Vacant(e) => {
202                e.insert(ts);
203                true
204            }
205        }
206    }
207}
208
209impl TransferSyntaxIndex for TransferSyntaxRegistryImpl {
210    #[inline]
211    fn get(&self, uid: &str) -> Option<&TransferSyntax> {
212        Self::get(self, uid)
213    }
214}
215
216impl TransferSyntaxRegistry {
217    /// Obtain an iterator of all registered transfer syntaxes.
218    #[inline]
219    pub fn iter(&self) -> impl Iterator<Item = &TransferSyntax> {
220        get_registry().iter()
221    }
222}
223
224/// Zero-sized representative of the main transfer syntax registry.
225#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)]
226pub struct TransferSyntaxRegistry;
227
228impl TransferSyntaxIndex for TransferSyntaxRegistry {
229    #[inline]
230    fn get(&self, uid: &str) -> Option<&TransferSyntax> {
231        get_registry().get(uid)
232    }
233}
234
235lazy_static! {
236
237    static ref REGISTRY: TransferSyntaxRegistryImpl = {
238        let mut registry = TransferSyntaxRegistryImpl {
239            m: HashMap::with_capacity(32),
240        };
241
242        use self::entries::*;
243        let built_in_ts: [TransferSyntax; 45] = [
244            IMPLICIT_VR_LITTLE_ENDIAN.erased(),
245            EXPLICIT_VR_LITTLE_ENDIAN.erased(),
246            EXPLICIT_VR_BIG_ENDIAN.erased(),
247
248            ENCAPSULATED_UNCOMPRESSED_EXPLICIT_VR_LITTLE_ENDIAN.erased(),
249
250            DEFLATED_EXPLICIT_VR_LITTLE_ENDIAN.erased(),
251            JPIP_REFERENCED_DEFLATE.erased(),
252            JPIP_HTJ2K_REFERENCED_DEFLATE.erased(),
253
254            JPEG_BASELINE.erased(),
255            JPEG_EXTENDED.erased(),
256            JPEG_LOSSLESS_NON_HIERARCHICAL.erased(),
257            JPEG_LOSSLESS_NON_HIERARCHICAL_FIRST_ORDER_PREDICTION.erased(),
258            JPEG_LS_LOSSLESS_IMAGE_COMPRESSION.erased(),
259            JPEG_LS_LOSSY_IMAGE_COMPRESSION.erased(),
260            JPEG_2000_IMAGE_COMPRESSION_LOSSLESS_ONLY.erased(),
261            JPEG_2000_IMAGE_COMPRESSION.erased(),
262            JPEG_2000_PART2_MULTI_COMPONENT_IMAGE_COMPRESSION_LOSSLESS_ONLY.erased(),
263            JPEG_2000_PART2_MULTI_COMPONENT_IMAGE_COMPRESSION.erased(),
264            HIGH_THROUGHPUT_JPEG_2000_IMAGE_COMPRESSION_LOSSLESS_ONLY.erased(),
265            HIGH_THROUGHPUT_JPEG_2000_WITH_RPCL_OPTIONS_IMAGE_COMPRESSION_LOSSLESS_ONLY.erased(),
266            HIGH_THROUGHPUT_JPEG_2000_IMAGE_COMPRESSION.erased(),
267            JPEG_XL_LOSSLESS.erased(),
268            JPEG_XL_RECOMPRESSION.erased(),
269            JPEG_XL.erased(),
270            JPIP_REFERENCED.erased(),
271            JPIP_HTJ2K_REFERENCED.erased(),
272            MPEG2_MAIN_PROFILE_MAIN_LEVEL.erased(),
273            FRAGMENTABLE_MPEG2_MAIN_PROFILE_MAIN_LEVEL.erased(),
274            MPEG2_MAIN_PROFILE_HIGH_LEVEL.erased(),
275            FRAGMENTABLE_MPEG2_MAIN_PROFILE_HIGH_LEVEL.erased(),
276            MPEG4_AVC_H264_HIGH_PROFILE.erased(),
277            FRAGMENTABLE_MPEG4_AVC_H264_HIGH_PROFILE.erased(),
278            MPEG4_AVC_H264_BD_COMPATIBLE_HIGH_PROFILE.erased(),
279            FRAGMENTABLE_MPEG4_AVC_H264_BD_COMPATIBLE_HIGH_PROFILE.erased(),
280            MPEG4_AVC_H264_HIGH_PROFILE_FOR_2D_VIDEO.erased(),
281            FRAGMENTABLE_MPEG4_AVC_H264_HIGH_PROFILE_FOR_2D_VIDEO.erased(),
282            MPEG4_AVC_H264_HIGH_PROFILE_FOR_3D_VIDEO.erased(),
283            FRAGMENTABLE_MPEG4_AVC_H264_HIGH_PROFILE_FOR_3D_VIDEO.erased(),
284            MPEG4_AVC_H264_STEREO_HIGH_PROFILE.erased(),
285            FRAGMENTABLE_MPEG4_AVC_H264_STEREO_HIGH_PROFILE.erased(),
286            HEVC_H265_MAIN_PROFILE.erased(),
287            HEVC_H265_MAIN_10_PROFILE.erased(),
288            RLE_LOSSLESS.erased(),
289            SMPTE_ST_2110_20_UNCOMPRESSED_PROGRESSIVE.erased(),
290            SMPTE_ST_2110_20_UNCOMPRESSED_INTERLACED.erased(),
291            SMPTE_ST_2110_30_PCM.erased(),
292        ];
293
294        // add built-in TSes manually
295        for ts in built_in_ts {
296            registry.register(ts);
297        }
298        // add TSes from inventory, if available
299        inventory_populate(&mut registry);
300
301        registry
302    };
303}
304
305#[cfg(feature = "inventory-registry")]
306#[inline]
307fn inventory_populate(registry: &mut TransferSyntaxRegistryImpl) {
308    use dicom_encoding::transfer_syntax::TransferSyntaxFactory;
309
310    for TransferSyntaxFactory(tsf) in inventory::iter::<TransferSyntaxFactory> {
311        let ts = tsf();
312        registry.register(ts);
313    }
314}
315
316#[cfg(not(feature = "inventory-registry"))]
317#[inline]
318fn inventory_populate(_: &mut TransferSyntaxRegistryImpl) {
319    // do nothing
320}
321
322/// Retrieve a reference to the global codec registry.
323#[inline]
324pub(crate) fn get_registry() -> &'static TransferSyntaxRegistryImpl {
325    &REGISTRY
326}
327
328/// create a TS with an unsupported pixel encapsulation
329pub(crate) const fn create_ts_stub(uid: &'static str, name: &'static str) -> Ts {
330    TransferSyntax::new_ele(uid, name, Codec::EncapsulatedPixelData(None, None))
331}
332
333/// Retrieve the default transfer syntax.
334pub fn default() -> Ts {
335    entries::IMPLICIT_VR_LITTLE_ENDIAN
336}
337
338#[cfg(test)]
339mod tests {
340    use dicom_encoding::TransferSyntaxIndex;
341
342    use crate::TransferSyntaxRegistry;
343
344    #[test]
345    fn has_mandatory_tss() {
346        let implicit_vr_le = TransferSyntaxRegistry
347            .get("1.2.840.10008.1.2")
348            .expect("transfer syntax registry should provide Implicit VR Little Endian");
349        assert_eq!(implicit_vr_le.uid(), "1.2.840.10008.1.2");
350        assert!(implicit_vr_le.is_fully_supported());
351
352        // should also work with trailing null character
353        let implicit_vr_le_2 = TransferSyntaxRegistry.get("1.2.840.10008.1.2\0").expect(
354            "transfer syntax registry should provide Implicit VR Little Endian with padded TS UID",
355        );
356
357        assert_eq!(implicit_vr_le_2.uid(), implicit_vr_le.uid());
358
359        let explicit_vr_le = TransferSyntaxRegistry
360            .get("1.2.840.10008.1.2.1")
361            .expect("transfer syntax registry should provide Explicit VR Little Endian");
362        assert_eq!(explicit_vr_le.uid(), "1.2.840.10008.1.2.1");
363        assert!(explicit_vr_le.is_fully_supported());
364
365        // should also work with trailing null character
366        let explicit_vr_le_2 = TransferSyntaxRegistry.get("1.2.840.10008.1.2.1\0").expect(
367            "transfer syntax registry should provide Explicit VR Little Endian with padded TS UID",
368        );
369
370        assert_eq!(explicit_vr_le_2.uid(), explicit_vr_le.uid());
371    }
372
373    #[test]
374    fn provides_iter() {
375        let all_tss: Vec<_> = TransferSyntaxRegistry.iter().collect();
376
377        assert!(all_tss.len() >= 2);
378
379        // contains at least Implicit VR Little Endian and Explicit VR Little Endian
380        assert!(all_tss.iter().any(|ts| ts.uid() == "1.2.840.10008.1.2"));
381        assert!(all_tss.iter().any(|ts| ts.uid() == "1.2.840.10008.1.2.1"));
382    }
383}