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}