dicom_core/dictionary/data_element.rs
1//! Core data element dictionary types
2
3use std::str::FromStr;
4
5use snafu::{ensure, Backtrace, OptionExt, ResultExt, Snafu};
6
7use crate::{
8 ops::{AttributeSelector, AttributeSelectorStep},
9 Tag, VR,
10};
11
12/// Specification of a range of tags pertaining to an attribute.
13/// Very often, the dictionary of attributes indicates a unique
14/// group part and element part `(group,elem)`,
15/// but occasionally an attribute may cover
16/// a range of groups or elements instead.
17/// For example,
18/// _Overlay Data_ (60xx,3000) has more than one possible tag,
19/// since it is part of a repeating group.
20/// Moreover, a unique variant is defined for group length tags
21/// and another one for private creator tags.
22#[derive(Debug, Copy, Clone, PartialEq)]
23pub enum TagRange {
24 /// Only a specific tag
25 Single(Tag),
26 /// The two rightmost digits of the _group_ portion are open:
27 /// `(GGxx,EEEE)`
28 Group100(Tag),
29 /// The two rightmost digits of the _element_ portion are open:
30 /// `(GGGG,EExx)`
31 Element100(Tag),
32 /// Generic group length tag,
33 /// refers to any attribute of the form `(GGGG,0000)`,
34 /// _save for the following exceptions_
35 /// which have their own single tag record:
36 ///
37 /// - _Command Group Length_ (0000,0000)
38 /// - _File Meta Information Group Length_ (0002,0000)
39 GroupLength,
40 /// Generic private creator tag,
41 /// refers to any tag from (GGGG,0010) to (GGGG,00FF),
42 /// where `GGGG` is an odd number.
43 PrivateCreator,
44}
45
46impl TagRange {
47 /// Retrieve the inner tag representation of this range.
48 ///
49 /// Open components are zeroed out.
50 /// Returns a zeroed out tag
51 /// (equivalent to _Command Group Length_)
52 /// if it is a group length tag.
53 /// If it is a private creator tag,
54 /// this method returns `Tag(0x0009, 0x0010)`.
55 pub fn inner(self) -> Tag {
56 match self {
57 TagRange::Single(tag) => tag,
58 TagRange::Group100(tag) => tag,
59 TagRange::Element100(tag) => tag,
60 TagRange::GroupLength => Tag(0x0000, 0x0000),
61 TagRange::PrivateCreator => Tag(0x0009, 0x0010),
62 }
63 }
64}
65
66/// An error returned when parsing an invalid tag range.
67#[derive(Debug, Snafu)]
68#[non_exhaustive]
69pub enum TagRangeParseError {
70 #[snafu(display("Not enough tag components, expected tag (group, element)"))]
71 MissingTag { backtrace: Backtrace },
72 #[snafu(display("Not enough tag components, expected tag element"))]
73 MissingTagElement { backtrace: Backtrace },
74 #[snafu(display(
75 "tag component `group` has an invalid length: got {} but must be 4",
76 got
77 ))]
78 InvalidGroupLength { got: usize, backtrace: Backtrace },
79 #[snafu(display(
80 "tag component `element` has an invalid length: got {} but must be 4",
81 got
82 ))]
83 InvalidElementLength { got: usize, backtrace: Backtrace },
84 #[snafu(display("unsupported tag range"))]
85 UnsupportedTagRange { backtrace: Backtrace },
86 #[snafu(display("invalid tag component `group`"))]
87 InvalidTagGroup {
88 backtrace: Backtrace,
89 source: std::num::ParseIntError,
90 },
91 #[snafu(display("invalid tag component `element`"))]
92 InvalidTagElement {
93 backtrace: Backtrace,
94 source: std::num::ParseIntError,
95 },
96}
97
98impl FromStr for TagRange {
99 type Err = TagRangeParseError;
100
101 fn from_str(mut s: &str) -> Result<Self, Self::Err> {
102 if s.starts_with('(') && s.ends_with(')') {
103 s = &s[1..s.len() - 1];
104 }
105 let mut parts = s.split(',');
106 let group = parts.next().context(MissingTagSnafu)?;
107 let elem = parts.next().context(MissingTagElementSnafu)?;
108 ensure!(
109 group.len() == 4,
110 InvalidGroupLengthSnafu { got: group.len() }
111 );
112 ensure!(
113 elem.len() == 4,
114 InvalidElementLengthSnafu { got: elem.len() }
115 );
116
117 match (&group.as_bytes()[2..], &elem.as_bytes()[2..]) {
118 (b"xx", b"xx") => UnsupportedTagRangeSnafu.fail(),
119 (b"xx", _) => {
120 // Group100
121 let group =
122 u16::from_str_radix(&group[..2], 16).context(InvalidTagGroupSnafu)? << 8;
123 let elem = u16::from_str_radix(elem, 16).context(InvalidTagElementSnafu)?;
124 Ok(TagRange::Group100(Tag(group, elem)))
125 }
126 (_, b"xx") => {
127 // Element100
128 let group = u16::from_str_radix(group, 16).context(InvalidTagGroupSnafu)?;
129 let elem =
130 u16::from_str_radix(&elem[..2], 16).context(InvalidTagElementSnafu)? << 8;
131 Ok(TagRange::Element100(Tag(group, elem)))
132 }
133 (_, _) => {
134 // single element
135 let group = u16::from_str_radix(group, 16).context(InvalidTagGroupSnafu)?;
136 let elem = u16::from_str_radix(elem, 16).context(InvalidTagElementSnafu)?;
137 Ok(TagRange::Single(Tag(group, elem)))
138 }
139 }
140 }
141}
142
143/// A "virtual" value representation (VR) descriptor
144/// which extends the standard enumeration with context-dependent VRs.
145///
146/// It is used by element dictionary entries to describe circumstances
147/// in which the real VR may depend on context.
148/// As an example, the _Pixel Data_ attribute
149/// can have a value representation of either [`OB`](VR::OB) or [`OW`](VR::OW).
150#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)]
151#[non_exhaustive]
152pub enum VirtualVr {
153 /// The value representation is exactly known
154 /// and does not depend on context.
155 Exact(VR),
156 /// Represents a pixel data sample value
157 /// with a short magnitude.
158 ///
159 /// The value representation depends on
160 /// the pixel data value sample representation.
161 /// If pixel data values are signed
162 /// (represented by a _Pixel Representation_ value of `1`),
163 /// then values with this virtual VR
164 /// should be interpreted as signed 16 bit integers
165 /// ([`SS`](VR::SS)),
166 /// otherwise they should be interpreted as unsigned 16 bit integers
167 /// ([`US`](VR::US)).
168 Xs,
169 /// Represents overlay data sample values.
170 ///
171 /// It can be either [`OB`](VR::OB) or [`OW`](VR::OW).
172 Ox,
173 /// Represents pixel data sample value.
174 ///
175 /// It can be either [`OB`](VR::OB) or [`OW`](VR::OW).
176 Px,
177 /// Represents LUT data, which can be [`US`](VR::US) or [`OW`](VR::OW)
178 Lt,
179}
180
181impl From<VR> for VirtualVr {
182 fn from(value: VR) -> Self {
183 VirtualVr::Exact(value)
184 }
185}
186
187impl VirtualVr {
188 /// Return the underlying value representation
189 /// in the case that it can be unambiguously defined without context.
190 pub fn exact(self) -> Option<VR> {
191 match self {
192 VirtualVr::Exact(vr) => Some(vr),
193 _ => None,
194 }
195 }
196
197 /// Return the underlying value representation,
198 /// making a relaxed conversion if it cannot be
199 /// accurately resolved without context.
200 ///
201 /// - [`Xs`](VirtualVr::Xs) is relaxed to [`US`](VR::US)
202 /// - [`Ox`](VirtualVr::Ox) is relaxed to [`OW`](VR::OW)
203 /// - [`Px`](VirtualVr::Px) is relaxed to [`OW`](VR::OW)
204 /// - [`Lt`](VirtualVr::Lt) is relaxed to [`OW`](VR::OW)
205 ///
206 /// This method is ill-advised for uses where
207 /// the corresponding attribute is important.
208 pub fn relaxed(self) -> VR {
209 match self {
210 VirtualVr::Exact(vr) => vr,
211 VirtualVr::Xs => VR::US,
212 VirtualVr::Ox => VR::OW,
213 VirtualVr::Px => VR::OW,
214 VirtualVr::Lt => VR::OW,
215 }
216 }
217}
218
219/// An error during attribute selector parsing
220#[derive(Debug, Snafu)]
221pub struct ParseSelectorError(ParseSelectorErrorInner);
222
223#[derive(Debug, Snafu)]
224enum ParseSelectorErrorInner {
225 /// missing item index delimiter `[`
226 MissingItemDelimiter,
227 /// invalid tag or unrecognized keyword
228 ParseKey,
229 /// invalid item index, should be an unsigned integer
230 ParseItemIndex,
231 /// last selector step should select a plain tag
232 ParseLeaf,
233}
234
235/// Type trait for a dictionary of DICOM attributes.
236///
237/// The main purpose of an attribute dictionary is
238/// to retrieve a record containing additional information about a data element,
239/// in one of the following ways:
240///
241/// - By DICOM tag, via [`by_tag`][1];
242/// - By its keyword (also known as alias) via [`by_name`][2];
243/// - By an expression which may either be a keyword
244/// or a tag printed in one of its standard forms,
245/// using [`by_expr`][3].
246///
247/// These methods will return `None`
248/// when the tag or name is not recognized by the dictionary.
249///
250/// In addition,
251/// the data element dictionary provides
252/// built-in DICOM tag and selector (path) parsers for convenience.
253/// [`parse_tag`][4] converts an arbitrary expression to a tag,
254/// whereas [`parse_selector`][5] produces an [attribute selector][6].
255///
256/// [1]: DataDictionary::by_tag
257/// [2]: DataDictionary::by_name
258/// [3]: DataDictionary::by_expr
259/// [4]: DataDictionary::parse_tag
260/// [5]: DataDictionary::parse_selector
261/// [6]: crate::ops::AttributeSelector
262pub trait DataDictionary {
263 /// The type of the dictionary entry.
264 type Entry: DataDictionaryEntry;
265
266 /// Fetch a data element entry by its tag.
267 fn by_tag(&self, tag: Tag) -> Option<&Self::Entry>;
268
269 /// Fetch an entry by its usual alias
270 /// (e.g. "PatientName" or "SOPInstanceUID").
271 /// Aliases (or keyword)
272 /// are usually in UpperCamelCase,
273 /// not separated by spaces,
274 /// and are case sensitive.
275 ///
276 /// Querying the dictionary by name is usually
277 /// slightly more expensive than by DICOM tag.
278 /// If the parameter provided is a string literal
279 /// (e.g. `"StudyInstanceUID"`),
280 /// then it may be better to use [`by_tag`][1]
281 /// with a known tag constant
282 /// (such as [`tags::STUDY_INSTANCE_UID`][2]
283 /// from the [`dicom-dictionary-std`][3] crate).
284 ///
285 /// [1]: DataDictionary::by_tag
286 /// [2]: https://docs.rs/dicom-dictionary-std/0.5.0/dicom_dictionary_std/tags/constant.STUDY_INSTANCE_UID.html
287 /// [3]: https://docs.rs/dicom-dictionary-std/0.5.0
288 fn by_name(&self, name: &str) -> Option<&Self::Entry>;
289
290 /// Fetch an entry by its alias or by DICOM tag expression.
291 ///
292 /// This method accepts a tag descriptor in any of the following formats:
293 ///
294 /// - `(gggg,eeee)`:
295 /// a 4-digit hexadecimal group part
296 /// and a 4-digit hexadecimal element part
297 /// surrounded by parentheses
298 /// - `gggg,eeee`:
299 /// a 4-digit hexadecimal group part
300 /// and a 4-digit hexadecimal element part
301 /// not surrounded by parentheses
302 /// - _`KeywordName`_:
303 /// an exact match (case sensitive) by DICOM tag keyword
304 ///
305 /// When failing to identify the intended syntax or the tag keyword,
306 /// `None` is returned.
307 fn by_expr(&self, tag: &str) -> Option<&Self::Entry> {
308 match tag.parse() {
309 Ok(tag) => self.by_tag(tag),
310 Err(_) => self.by_name(tag),
311 }
312 }
313
314 /// Use this data element dictionary to interpret a DICOM tag.
315 ///
316 /// This method accepts a tag descriptor in any of the following formats:
317 ///
318 /// - `(gggg,eeee)`:
319 /// a 4-digit hexadecimal group part
320 /// and a 4-digit hexadecimal element part
321 /// surrounded by parentheses
322 /// - `gggg,eeee`:
323 /// a 4-digit hexadecimal group part
324 /// and a 4-digit hexadecimal element part
325 /// not surrounded by parentheses
326 /// - _`KeywordName`_:
327 /// an exact match (case sensitive) by DICOM tag keyword
328 ///
329 /// When failing to identify the intended syntax or the tag keyword,
330 /// `None` is returned.
331 fn parse_tag(&self, tag: &str) -> Option<Tag> {
332 tag.parse().ok().or_else(|| {
333 // look for tag in standard data dictionary
334 self.by_name(tag).map(|e| e.tag())
335 })
336 }
337
338 /// Parse a string as an [attribute selector][1].
339 ///
340 /// Attribute selectors are defined by the syntax
341 /// `( «key»([«item»])? . )* «key» `
342 /// where_`«key»`_ is either a DICOM tag or keyword
343 /// as accepted by this dictionary
344 /// when calling the method [`parse_tag`](DataDictionary::parse_tag).
345 /// More details about the syntax can be found
346 /// in the documentation of [`AttributeSelector`][1].
347 ///
348 /// Returns an error if the string does not follow the given syntax,
349 /// or one of the key components could not be resolved.
350 ///
351 /// [1]: crate::ops::AttributeSelector
352 ///
353 /// ### Examples of valid input:
354 ///
355 /// - `(0002,00010)`:
356 /// _Transfer Syntax UID_
357 /// - `00101010`:
358 /// _Patient Age_
359 /// - `0040A168[0].CodeValue`:
360 /// _Code Value_ in first item of _Concept Code Sequence_
361 /// - `SequenceOfUltrasoundRegions.RegionSpatialFormat`:
362 /// _Region Spatial Format_ in first item of _Sequence of Ultrasound Regions_
363 fn parse_selector(&self, selector_text: &str) -> Result<AttributeSelector, ParseSelectorError> {
364 let mut steps = crate::value::C::new();
365 for part in selector_text.split('.') {
366 // detect if intermediate
367 if part.ends_with(']') {
368 let split_i = part.find('[').context(MissingItemDelimiterSnafu)?;
369 let tag_part = &part[0..split_i];
370 let item_index_part = &part[split_i + 1..part.len() - 1];
371
372 let tag: Tag = self.parse_tag(tag_part).context(ParseKeySnafu)?;
373 let item: u32 = item_index_part.parse().ok().context(ParseItemIndexSnafu)?;
374 steps.push(AttributeSelectorStep::Nested { tag, item });
375 } else {
376 // treat it as a tag step
377 let tag: Tag = self.parse_tag(part).context(ParseKeySnafu)?;
378 steps.push(AttributeSelectorStep::Tag(tag));
379 }
380 }
381
382 Ok(AttributeSelector::new(steps).context(ParseLeafSnafu)?)
383 }
384}
385
386/// The data element dictionary entry type,
387/// representing a DICOM attribute.
388pub trait DataDictionaryEntry {
389 /// The full possible tag range of the atribute,
390 /// which this dictionary entry can represent.
391 fn tag_range(&self) -> TagRange;
392
393 /// Fetch a single tag applicable to this attribute.
394 ///
395 /// Note that this is not necessarily
396 /// the original tag used as key for this entry.
397 fn tag(&self) -> Tag {
398 self.tag_range().inner()
399 }
400 /// The alias of the attribute, with no spaces, usually in UpperCamelCase.
401 fn alias(&self) -> &str;
402
403 /// The extended value representation descriptor of the attribute.
404 /// The use of [`VirtualVr`] is to attend to edge cases
405 /// in which the representation of a value
406 /// depends on surrounding context.
407 fn vr(&self) -> VirtualVr;
408}
409
410/// A data type for a dictionary entry with full ownership.
411#[derive(Debug, PartialEq, Clone)]
412pub struct DataDictionaryEntryBuf {
413 /// The attribute tag range
414 pub tag: TagRange,
415 /// The alias of the attribute, with no spaces, usually InCapitalizedCamelCase
416 pub alias: String,
417 /// The _typical_ value representation of the attribute
418 pub vr: VirtualVr,
419}
420
421impl DataDictionaryEntry for DataDictionaryEntryBuf {
422 fn tag_range(&self) -> TagRange {
423 self.tag
424 }
425 fn alias(&self) -> &str {
426 self.alias.as_str()
427 }
428 fn vr(&self) -> VirtualVr {
429 self.vr
430 }
431}
432
433/// A data type for a dictionary entry with a string slice for its alias.
434#[derive(Debug, PartialEq, Clone)]
435pub struct DataDictionaryEntryRef<'a> {
436 /// The attribute tag or tag range
437 pub tag: TagRange,
438 /// The alias of the attribute, with no spaces, usually InCapitalizedCamelCase
439 pub alias: &'a str,
440 /// The extended value representation descriptor of the attribute
441 pub vr: VirtualVr,
442}
443
444impl<'a> DataDictionaryEntry for DataDictionaryEntryRef<'a> {
445 fn tag_range(&self) -> TagRange {
446 self.tag
447 }
448 fn alias(&self) -> &str {
449 self.alias
450 }
451 fn vr(&self) -> VirtualVr {
452 self.vr
453 }
454}
455
456/// Utility data structure that resolves to a DICOM attribute tag
457/// at a later time.
458#[derive(Debug, Clone)]
459pub struct TagByName<N, D> {
460 dict: D,
461 name: N,
462}
463
464impl<N, D> TagByName<N, D>
465where
466 N: AsRef<str>,
467 D: DataDictionary,
468{
469 /// Create a tag resolver by name using the given dictionary.
470 pub fn new(dictionary: D, name: N) -> TagByName<N, D> {
471 TagByName {
472 dict: dictionary,
473 name,
474 }
475 }
476}
477
478impl<N, D> From<TagByName<N, D>> for Option<Tag>
479where
480 N: AsRef<str>,
481 D: DataDictionary,
482{
483 fn from(tag: TagByName<N, D>) -> Option<Tag> {
484 tag.dict.by_name(tag.name.as_ref()).map(|e| e.tag())
485 }
486}
487
488#[cfg(test)]
489mod tests {
490 use super::TagRange;
491 use crate::header::Tag;
492
493 #[test]
494 fn test_parse_tag_range() {
495 let tag: TagRange = "(1234,5678)".parse().unwrap();
496 assert_eq!(tag, TagRange::Single(Tag(0x1234, 0x5678)));
497
498 let tag: TagRange = "1234,5678".parse().unwrap();
499 assert_eq!(tag, TagRange::Single(Tag(0x1234, 0x5678)));
500
501 let tag: TagRange = "12xx,5678".parse().unwrap();
502 assert_eq!(tag, TagRange::Group100(Tag(0x1200, 0x5678)));
503
504 let tag: TagRange = "1234,56xx".parse().unwrap();
505 assert_eq!(tag, TagRange::Element100(Tag(0x1234, 0x5600)));
506 }
507}