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