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 ®ISTRY
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}