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