dicom_object/
mem.rs

1//! This module contains the implementation for an in-memory DICOM object.
2//!
3//! Use [`InMemDicomObject`] for your DICOM data set construction needs.
4//! Values of this type support infallible insertion, removal, and retrieval
5//! of elements by DICOM tag,
6//! or name (keyword) with a data element dictionary look-up.
7//!
8//! If you wish to build a complete DICOM file,
9//! you can start from an `InMemDicomObject`
10//! and complement it with a [file meta group table](crate::meta)
11//! (see [`with_meta`](InMemDicomObject::with_meta)
12//! and [`with_exact_meta`](InMemDicomObject::with_exact_meta)).
13//!
14//! # Example
15//!
16//! A new DICOM data set can be built by providing a sequence of data elements.
17//! Insertion and removal methods are also available.
18//!
19//! ```
20//! # use dicom_core::{DataElement, VR, dicom_value};
21//! # use dicom_dictionary_std::tags;
22//! # use dicom_dictionary_std::uids;
23//! # use dicom_object::InMemDicomObject;
24//! let mut obj = InMemDicomObject::from_element_iter([
25//!     DataElement::new(tags::SOP_CLASS_UID, VR::UI, uids::COMPUTED_RADIOGRAPHY_IMAGE_STORAGE),
26//!     DataElement::new(tags::SOP_INSTANCE_UID, VR::UI, "2.25.60156688944589400766024286894543900794"),
27//!     // ...
28//! ]);
29//!
30//! // continue adding elements
31//! obj.put(DataElement::new(tags::MODALITY, VR::CS, "CR"));
32//! ```
33//!
34//! In-memory DICOM objects may have a byte length recorded,
35//! if it was part of a data set sequence with explicit length.
36//! If necessary, this number can be obtained via the [`HasLength`] trait.
37//! However, any modifications made to the object will reset this length
38//! to [_undefined_](dicom_core::Length::UNDEFINED).
39use dicom_core::ops::{
40    ApplyOp, AttributeAction, AttributeOp, AttributeSelector, AttributeSelectorStep,
41};
42use itertools::Itertools;
43use smallvec::SmallVec;
44use snafu::{ensure, OptionExt, ResultExt};
45use std::borrow::Cow;
46use std::fs::File;
47use std::io::{BufRead, BufReader, Read};
48use std::path::Path;
49use std::{collections::BTreeMap, io::Write};
50
51use crate::file::ReadPreamble;
52use crate::ops::{
53    ApplyError, ApplyResult, IncompatibleTypesSnafu, ModifySnafu, UnsupportedActionSnafu,
54};
55use crate::{meta::FileMetaTable, FileMetaTableBuilder};
56use crate::{
57    AccessByNameError, AccessError, AtAccessError, BuildMetaTableSnafu, CreateParserSnafu,
58    CreatePrinterSnafu, DicomObject, ElementNotFoundSnafu, FileDicomObject, InvalidGroupSnafu,
59    MissingElementValueSnafu, MissingLeafElementSnafu, NoSpaceSnafu, NoSuchAttributeNameSnafu,
60    NoSuchDataElementAliasSnafu, NoSuchDataElementTagSnafu, NotASequenceSnafu, OpenFileSnafu,
61    ParseMetaDataSetSnafu, ParseSopAttributeSnafu, PrematureEndSnafu, PrepareMetaTableSnafu,
62    PrintDataSetSnafu, PrivateCreatorNotFoundSnafu, PrivateElementError, ReadError, ReadFileSnafu,
63    ReadPreambleBytesSnafu, ReadTokenSnafu, ReadUnsupportedTransferSyntaxSnafu,
64    UnexpectedTokenSnafu, WithMetaError, WriteError,
65};
66use dicom_core::dictionary::{DataDictionary, DataDictionaryEntry};
67use dicom_core::header::{GroupNumber, HasLength, Header};
68use dicom_core::value::{DataSetSequence, PixelFragmentSequence, Value, ValueType, C};
69use dicom_core::{DataElement, Length, PrimitiveValue, Tag, VR};
70use dicom_dictionary_std::{tags, StandardDataDictionary};
71use dicom_encoding::transfer_syntax::TransferSyntaxIndex;
72use dicom_encoding::{encode::EncodeTo, text::SpecificCharacterSet, TransferSyntax};
73use dicom_parser::dataset::{DataSetReader, DataToken, IntoTokensOptions};
74use dicom_parser::{
75    dataset::{read::Error as ParserError, DataSetWriter, IntoTokens},
76    StatefulDecode,
77};
78use dicom_transfer_syntax_registry::TransferSyntaxRegistry;
79
80/// A full in-memory DICOM data element.
81pub type InMemElement<D = StandardDataDictionary> = DataElement<InMemDicomObject<D>, InMemFragment>;
82
83/// The type of a pixel data fragment.
84pub type InMemFragment = dicom_core::value::InMemFragment;
85
86type Result<T, E = AccessError> = std::result::Result<T, E>;
87
88type ParserResult<T> = std::result::Result<T, ParserError>;
89
90/// A DICOM object that is fully contained in memory.
91///
92/// See the [module-level documentation](self)
93/// for more details.
94#[derive(Debug, Clone)]
95pub struct InMemDicomObject<D = StandardDataDictionary> {
96    /// the element map
97    entries: BTreeMap<Tag, InMemElement<D>>,
98    /// the data dictionary
99    dict: D,
100    /// The length of the DICOM object in bytes.
101    /// It is usually undefined, unless it is part of an item
102    /// in a sequence with a specified length in its item header.
103    len: Length,
104    /// In case the SpecificCharSet changes we need to mark the object as dirty,
105    /// because changing the character set may change the length in bytes of
106    /// stored text. It has to be public for now because we need
107    pub(crate) charset_changed: bool,
108}
109
110impl<D> PartialEq for InMemDicomObject<D> {
111    // This implementation ignores the data dictionary.
112    fn eq(&self, other: &Self) -> bool {
113        self.entries == other.entries
114    }
115}
116
117impl<D> HasLength for InMemDicomObject<D> {
118    fn length(&self) -> Length {
119        self.len
120    }
121}
122
123impl<'s, D: 's> DicomObject for &'s InMemDicomObject<D>
124where
125    D: DataDictionary,
126    D: Clone,
127{
128    type Element = &'s InMemElement<D>;
129
130    fn element(&self, tag: Tag) -> Result<Self::Element> {
131        self.entries
132            .get(&tag)
133            .context(NoSuchDataElementTagSnafu { tag })
134    }
135
136    fn element_by_name(&self, name: &str) -> Result<Self::Element, AccessByNameError> {
137        let tag = self.lookup_name(name)?;
138        self.element(tag).map_err(|e| e.into_access_by_name(name))
139    }
140}
141
142impl FileDicomObject<InMemDicomObject<StandardDataDictionary>> {
143    /// Create a DICOM object by reading from a file.
144    ///
145    /// This function assumes the standard file encoding structure:
146    /// first it automatically detects whether the 128-byte preamble is present,
147    /// skipping it if found.
148    /// Then it reads the file meta group,
149    /// followed by the rest of the data set.
150    pub fn open_file<P: AsRef<Path>>(path: P) -> Result<Self, ReadError> {
151        Self::open_file_with_dict(path, StandardDataDictionary)
152    }
153
154    /// Create a DICOM object by reading from a byte source.
155    ///
156    /// This function assumes the standard file encoding structure:
157    /// first it automatically detects whether the 128-byte preamble is present,
158    /// skipping it if found.
159    /// Then it reads the file meta group,
160    /// followed by the rest of the data set.
161    pub fn from_reader<S>(src: S) -> Result<Self, ReadError>
162    where
163        S: Read,
164    {
165        Self::from_reader_with_dict(src, StandardDataDictionary)
166    }
167}
168
169impl InMemDicomObject<StandardDataDictionary> {
170    /// Create a new empty DICOM object.
171    pub fn new_empty() -> Self {
172        InMemDicomObject {
173            entries: BTreeMap::new(),
174            dict: StandardDataDictionary,
175            len: Length::UNDEFINED,
176            charset_changed: false,
177        }
178    }
179
180    /// Construct a DICOM object from a fallible source of structured elements.
181    #[inline]
182    pub fn from_element_source<I>(iter: I) -> Result<Self>
183    where
184        I: IntoIterator<Item = Result<InMemElement<StandardDataDictionary>>>,
185    {
186        Self::from_element_source_with_dict(iter, StandardDataDictionary)
187    }
188
189    /// Construct a DICOM object from a non-fallible source of structured elements.
190    #[inline]
191    pub fn from_element_iter<I>(iter: I) -> Self
192    where
193        I: IntoIterator<Item = InMemElement<StandardDataDictionary>>,
194    {
195        Self::from_iter_with_dict(iter, StandardDataDictionary)
196    }
197
198    /// Construct a DICOM object representing a command set,
199    /// from a non-fallible iterator of structured elements.
200    ///
201    /// This method will automatically insert
202    /// a _Command Group Length_ (0000,0000) element
203    /// based on the command elements found in the sequence.
204    #[inline]
205    pub fn command_from_element_iter<I>(iter: I) -> Self
206    where
207        I: IntoIterator<Item = InMemElement<StandardDataDictionary>>,
208    {
209        Self::command_from_iter_with_dict(iter, StandardDataDictionary)
210    }
211
212    /// Read an object from a source using the given decoder.
213    ///
214    /// Note: [`read_dataset_with_ts`] and [`read_dataset_with_ts_cs`]
215    /// may be easier to use.
216    ///
217    /// [`read_dataset_with_ts`]: InMemDicomObject::read_dataset_with_ts
218    /// [`read_dataset_with_ts_cs`]: InMemDicomObject::read_dataset_with_ts_cs
219    #[inline]
220    pub fn read_dataset<S>(decoder: S) -> Result<Self, ReadError>
221    where
222        S: StatefulDecode,
223    {
224        Self::read_dataset_with_dict(decoder, StandardDataDictionary)
225    }
226
227    /// Read an object from a source,
228    /// using the given transfer syntax and default character set.
229    ///
230    /// If the attribute _Specific Character Set_ is found in the encoded data,
231    /// this will override the given character set.
232    #[inline]
233    pub fn read_dataset_with_ts_cs<S>(
234        from: S,
235        ts: &TransferSyntax,
236        cs: SpecificCharacterSet,
237    ) -> Result<Self, ReadError>
238    where
239        S: Read,
240    {
241        Self::read_dataset_with_dict_ts_cs(from, StandardDataDictionary, ts, cs)
242    }
243
244    /// Read an object from a source,
245    /// using the given transfer syntax.
246    ///
247    /// The default character set is assumed
248    /// until _Specific Character Set_ is found in the encoded data,
249    /// after which the text decoder will be overriden accordingly.
250    #[inline]
251    pub fn read_dataset_with_ts<S>(from: S, ts: &TransferSyntax) -> Result<Self, ReadError>
252    where
253        S: Read,
254    {
255        Self::read_dataset_with_dict_ts_cs(
256            from,
257            StandardDataDictionary,
258            ts,
259            SpecificCharacterSet::default(),
260        )
261    }
262}
263
264impl<D> FileDicomObject<InMemDicomObject<D>>
265where
266    D: DataDictionary,
267    D: Clone,
268{
269    /// Create a new empty object, using the given dictionary and
270    /// file meta table.
271    pub fn new_empty_with_dict_and_meta(dict: D, meta: FileMetaTable) -> Self {
272        FileDicomObject {
273            meta,
274            obj: InMemDicomObject {
275                entries: BTreeMap::new(),
276                dict,
277                len: Length::UNDEFINED,
278                charset_changed: false,
279            },
280        }
281    }
282
283    /// Create a DICOM object by reading from a file.
284    ///
285    /// This function assumes the standard file encoding structure:
286    /// first it automatically detects whether the 128-byte preamble is present,
287    /// skipping it when found.
288    /// Then it reads the file meta group,
289    /// followed by the rest of the data set.
290    pub fn open_file_with_dict<P: AsRef<Path>>(path: P, dict: D) -> Result<Self, ReadError> {
291        Self::open_file_with(path, dict, TransferSyntaxRegistry)
292    }
293
294    /// Create a DICOM object by reading from a file.
295    ///
296    /// This function assumes the standard file encoding structure:
297    /// first it automatically detects whether the 128-byte preamble is present,
298    /// skipping it when found.
299    /// Then it reads the file meta group,
300    /// followed by the rest of the data set.
301    ///
302    /// This function allows you to choose a different transfer syntax index,
303    /// but its use is only advised when the built-in transfer syntax registry
304    /// is insufficient. Otherwise, please use [`open_file_with_dict`] instead.
305    ///
306    /// [`open_file_with_dict`]: #method.open_file_with_dict
307    pub fn open_file_with<P, R>(path: P, dict: D, ts_index: R) -> Result<Self, ReadError>
308    where
309        P: AsRef<Path>,
310        R: TransferSyntaxIndex,
311    {
312        Self::open_file_with_all_options(path, dict, ts_index, None, ReadPreamble::Auto)
313    }
314
315    // detect the presence of a preamble
316    // and provide a better `ReadPreamble` option accordingly
317    fn detect_preamble<S>(reader: &mut BufReader<S>) -> std::io::Result<ReadPreamble>
318    where
319        S: Read,
320    {
321        let buf = reader.fill_buf()?;
322        let buflen = buf.len();
323
324        if buflen < 4 {
325            return Err(std::io::ErrorKind::UnexpectedEof.into());
326        }
327
328        if buflen >= 132 && &buf[128..132] == b"DICM" {
329            return Ok(ReadPreamble::Always);
330        }
331
332        if &buf[0..4] == b"DICM" {
333            return Ok(ReadPreamble::Never);
334        }
335
336        // could not detect
337        Ok(ReadPreamble::Auto)
338    }
339
340    pub(crate) fn open_file_with_all_options<P, R>(
341        path: P,
342        dict: D,
343        ts_index: R,
344        read_until: Option<Tag>,
345        mut read_preamble: ReadPreamble,
346    ) -> Result<Self, ReadError>
347    where
348        P: AsRef<Path>,
349        R: TransferSyntaxIndex,
350    {
351        let path = path.as_ref();
352        let mut file =
353            BufReader::new(File::open(path).with_context(|_| OpenFileSnafu { filename: path })?);
354
355        if read_preamble == ReadPreamble::Auto {
356            read_preamble = Self::detect_preamble(&mut file)
357                .with_context(|_| ReadFileSnafu { filename: path })?;
358        }
359
360        if read_preamble == ReadPreamble::Auto || read_preamble == ReadPreamble::Always {
361            let mut buf = [0u8; 128];
362            // skip the preamble
363            file.read_exact(&mut buf)
364                .with_context(|_| ReadFileSnafu { filename: path })?;
365        }
366
367        // read metadata header
368        let mut meta = FileMetaTable::from_reader(&mut file).context(ParseMetaDataSetSnafu)?;
369
370        // read rest of data according to metadata, feed it to object
371        if let Some(ts) = ts_index.get(&meta.transfer_syntax) {
372            let mut dataset = DataSetReader::new_with_ts(file, ts).context(CreateParserSnafu)?;
373            let obj = InMemDicomObject::build_object(
374                &mut dataset,
375                dict,
376                false,
377                Length::UNDEFINED,
378                read_until,
379            )?;
380
381            // if Media Storage SOP Class UID is empty attempt to infer from SOP Class UID
382            if meta.media_storage_sop_class_uid().is_empty() {
383                if let Some(elem) = obj.get(tags::SOP_CLASS_UID) {
384                    meta.media_storage_sop_class_uid = elem
385                        .value()
386                        .to_str()
387                        .context(ParseSopAttributeSnafu)?
388                        .to_string();
389                }
390            }
391
392            // if Media Storage SOP Instance UID is empty attempt to infer from SOP Instance UID
393            if meta.media_storage_sop_instance_uid().is_empty() {
394                if let Some(elem) = obj.get(tags::SOP_INSTANCE_UID) {
395                    meta.media_storage_sop_instance_uid = elem
396                        .value()
397                        .to_str()
398                        .context(ParseSopAttributeSnafu)?
399                        .to_string();
400                }
401            }
402
403            Ok(FileDicomObject { meta, obj })
404        } else {
405            ReadUnsupportedTransferSyntaxSnafu {
406                uid: meta.transfer_syntax,
407            }
408            .fail()
409        }
410    }
411
412    /// Create a DICOM object by reading from a byte source.
413    ///
414    /// This function assumes the standard file encoding structure:
415    /// first it automatically detects whether the 128-byte preamble is present,
416    /// skipping it when found.
417    /// Then it reads the file meta group,
418    /// followed by the rest of the data set.
419    pub fn from_reader_with_dict<S>(src: S, dict: D) -> Result<Self, ReadError>
420    where
421        S: Read,
422    {
423        Self::from_reader_with(src, dict, TransferSyntaxRegistry)
424    }
425
426    /// Create a DICOM object by reading from a byte source.
427    ///
428    /// This function assumes the standard file encoding structure:
429    /// first it automatically detects whether the preamble is present,
430    /// skipping it when found.
431    /// Then it reads the file meta group,
432    /// followed by the rest of the data set.
433    ///
434    /// This function allows you to choose a different transfer syntax index,
435    /// but its use is only advised when the built-in transfer syntax registry
436    /// is insufficient. Otherwise, please use [`from_reader_with_dict`] instead.
437    ///
438    /// [`from_reader_with_dict`]: #method.from_reader_with_dict
439    pub fn from_reader_with<'s, S, R>(src: S, dict: D, ts_index: R) -> Result<Self, ReadError>
440    where
441        S: Read + 's,
442        R: TransferSyntaxIndex,
443    {
444        Self::from_reader_with_all_options(src, dict, ts_index, None, ReadPreamble::Auto)
445    }
446
447    pub(crate) fn from_reader_with_all_options<'s, S, R>(
448        src: S,
449        dict: D,
450        ts_index: R,
451        read_until: Option<Tag>,
452        mut read_preamble: ReadPreamble,
453    ) -> Result<Self, ReadError>
454    where
455        S: Read + 's,
456        R: TransferSyntaxIndex,
457    {
458        let mut file = BufReader::new(src);
459
460        if read_preamble == ReadPreamble::Auto {
461            read_preamble = Self::detect_preamble(&mut file).context(ReadPreambleBytesSnafu)?;
462        }
463
464        if read_preamble == ReadPreamble::Always {
465            // skip preamble
466            let mut buf = [0u8; 128];
467            // skip the preamble
468            file.read_exact(&mut buf).context(ReadPreambleBytesSnafu)?;
469        }
470
471        // read metadata header
472        let meta = FileMetaTable::from_reader(&mut file).context(ParseMetaDataSetSnafu)?;
473
474        // read rest of data according to metadata, feed it to object
475        if let Some(ts) = ts_index.get(&meta.transfer_syntax) {
476            let mut dataset = DataSetReader::new_with_ts(file, ts).context(CreateParserSnafu)?;
477            let obj = InMemDicomObject::build_object(
478                &mut dataset,
479                dict,
480                false,
481                Length::UNDEFINED,
482                read_until,
483            )?;
484            Ok(FileDicomObject { meta, obj })
485        } else {
486            ReadUnsupportedTransferSyntaxSnafu {
487                uid: meta.transfer_syntax,
488            }
489            .fail()
490        }
491    }
492}
493
494impl FileDicomObject<InMemDicomObject<StandardDataDictionary>> {
495    /// Create a new empty object, using the given file meta table.
496    pub fn new_empty_with_meta(meta: FileMetaTable) -> Self {
497        FileDicomObject {
498            meta,
499            obj: InMemDicomObject {
500                entries: BTreeMap::new(),
501                dict: StandardDataDictionary,
502                len: Length::UNDEFINED,
503                charset_changed: false,
504            },
505        }
506    }
507}
508
509impl<D> InMemDicomObject<D>
510where
511    D: DataDictionary,
512    D: Clone,
513{
514    /// Create a new empty object, using the given dictionary for name lookup.
515    pub fn new_empty_with_dict(dict: D) -> Self {
516        InMemDicomObject {
517            entries: BTreeMap::new(),
518            dict,
519            len: Length::UNDEFINED,
520            charset_changed: false,
521        }
522    }
523
524    /// Construct a DICOM object from an iterator of structured elements.
525    pub fn from_element_source_with_dict<I>(iter: I, dict: D) -> Result<Self>
526    where
527        I: IntoIterator<Item = Result<InMemElement<D>>>,
528    {
529        let entries: Result<_> = iter.into_iter().map_ok(|e| (e.tag(), e)).collect();
530        Ok(InMemDicomObject {
531            entries: entries?,
532            dict,
533            len: Length::UNDEFINED,
534            charset_changed: false,
535        })
536    }
537
538    /// Construct a DICOM object from a non-fallible iterator of structured elements.
539    pub fn from_iter_with_dict<I>(iter: I, dict: D) -> Self
540    where
541        I: IntoIterator<Item = InMemElement<D>>,
542    {
543        let entries = iter.into_iter().map(|e| (e.tag(), e)).collect();
544        InMemDicomObject {
545            entries,
546            dict,
547            len: Length::UNDEFINED,
548            charset_changed: false,
549        }
550    }
551
552    /// Construct a DICOM object representing a command set,
553    /// from a non-fallible iterator of structured elements.
554    ///
555    /// This method will automatically insert
556    /// a _Command Group Length_ (0000,0000) element
557    /// based on the command elements found in the sequence.
558    pub fn command_from_iter_with_dict<I>(iter: I, dict: D) -> Self
559    where
560        I: IntoIterator<Item = InMemElement<D>>,
561    {
562        let mut calculated_length: u32 = 0;
563        let mut entries: BTreeMap<_, _> = iter
564            .into_iter()
565            .map(|e| {
566                // count the length of command set elements
567                if e.tag().0 == 0x0000 && e.tag().1 != 0x0000 {
568                    let l = e.value().length();
569                    calculated_length += if l.is_defined() { even_len(l.0) } else { 0 } + 8;
570                }
571
572                (e.tag(), e)
573            })
574            .collect();
575
576        entries.insert(
577            Tag(0, 0),
578            InMemElement::new(Tag(0, 0), VR::UL, PrimitiveValue::from(calculated_length)),
579        );
580
581        InMemDicomObject {
582            entries,
583            dict,
584            len: Length::UNDEFINED,
585            charset_changed: false,
586        }
587    }
588
589    /// Read an object from a source,
590    /// using the given decoder
591    /// and the given dictionary for name lookup.
592    pub fn read_dataset_with_dict<S>(decoder: S, dict: D) -> Result<Self, ReadError>
593    where
594        S: StatefulDecode,
595        D: DataDictionary,
596    {
597        let mut dataset = DataSetReader::new(decoder, Default::default());
598        InMemDicomObject::build_object(&mut dataset, dict, false, Length::UNDEFINED, None)
599    }
600
601    /// Read an object from a source,
602    /// using the given data dictionary and transfer syntax.
603    #[inline]
604    pub fn read_dataset_with_dict_ts<S>(
605        from: S,
606        dict: D,
607        ts: &TransferSyntax,
608    ) -> Result<Self, ReadError>
609    where
610        S: Read,
611        D: DataDictionary,
612    {
613        Self::read_dataset_with_dict_ts_cs(from, dict, ts, SpecificCharacterSet::default())
614    }
615
616    /// Read an object from a source,
617    /// using the given data dictionary,
618    /// transfer syntax,
619    /// and the given character set to assume by default.
620    ///
621    /// If the attribute _Specific Character Set_ is found in the encoded data,
622    /// this will override the given character set.
623    pub fn read_dataset_with_dict_ts_cs<S>(
624        from: S,
625        dict: D,
626        ts: &TransferSyntax,
627        cs: SpecificCharacterSet,
628    ) -> Result<Self, ReadError>
629    where
630        S: Read,
631        D: DataDictionary,
632    {
633        let from = BufReader::new(from);
634        let mut dataset = DataSetReader::new_with_ts_cs(from, ts, cs).context(CreateParserSnafu)?;
635        InMemDicomObject::build_object(&mut dataset, dict, false, Length::UNDEFINED, None)
636    }
637
638    // Standard methods follow. They are not placed as a trait implementation
639    // because they may require outputs to reference the lifetime of self,
640    // which is not possible without GATs.
641
642    /// Retrieve a particular DICOM element by its tag.
643    ///
644    /// An error is returned if the element does not exist.
645    /// For an alternative to this behavior,
646    /// see [`element_opt`](InMemDicomObject::element_opt).
647    pub fn element(&self, tag: Tag) -> Result<&InMemElement<D>> {
648        self.entries
649            .get(&tag)
650            .context(NoSuchDataElementTagSnafu { tag })
651    }
652
653    /// Retrieve a particular DICOM element by its name.
654    ///
655    /// This method translates the given attribute name into its tag
656    /// before retrieving the element.
657    /// If the attribute is known in advance,
658    /// using [`element`](InMemDicomObject::element)
659    /// with a tag constant is preferred.
660    ///
661    /// An error is returned if the element does not exist.
662    /// For an alternative to this behavior,
663    /// see [`element_by_name_opt`](InMemDicomObject::element_by_name_opt).
664    pub fn element_by_name(&self, name: &str) -> Result<&InMemElement<D>, AccessByNameError> {
665        let tag = self.lookup_name(name)?;
666        self.entries
667            .get(&tag)
668            .with_context(|| NoSuchDataElementAliasSnafu {
669                tag,
670                alias: name.to_string(),
671            })
672    }
673
674    /// Retrieve a particular DICOM element that might not exist by its tag.
675    ///
676    /// If the element does not exist,
677    /// `None` is returned.
678    pub fn element_opt(&self, tag: Tag) -> Result<Option<&InMemElement<D>>, AccessError> {
679        match self.element(tag) {
680            Ok(e) => Ok(Some(e)),
681            Err(super::AccessError::NoSuchDataElementTag { .. }) => Ok(None),
682        }
683    }
684
685    /// Get a particular DICOM attribute from this object by tag.
686    ///
687    /// If the element does not exist,
688    /// `None` is returned.
689    pub fn get(&self, tag: Tag) -> Option<&InMemElement<D>> {
690        self.entries.get(&tag)
691    }
692
693    // Get a mutable reference to a particular DICOM attribute from this object by tag.
694    //
695    // Should be private as it would allow a user to change the tag of an
696    // element and diverge from the dictionary
697    fn get_mut(&mut self, tag: Tag) -> Option<&mut InMemElement<D>> {
698        self.entries.get_mut(&tag)
699    }
700
701    /// Retrieve a particular DICOM element that might not exist by its name.
702    ///
703    /// If the element does not exist,
704    /// `None` is returned.
705    ///
706    /// This method translates the given attribute name into its tag
707    /// before retrieving the element.
708    /// If the attribute is known in advance,
709    /// using [`element_opt`](InMemDicomObject::element_opt)
710    /// with a tag constant is preferred.
711    pub fn element_by_name_opt(
712        &self,
713        name: &str,
714    ) -> Result<Option<&InMemElement<D>>, AccessByNameError> {
715        match self.element_by_name(name) {
716            Ok(e) => Ok(Some(e)),
717            Err(AccessByNameError::NoSuchDataElementAlias { .. }) => Ok(None),
718            Err(e) => Err(e),
719        }
720    }
721
722    fn find_private_creator(&self, group: GroupNumber, creator: &str) -> Option<&Tag> {
723        let range = Tag(group, 0)..Tag(group, 0xFF);
724        for (tag, elem) in self.entries.range(range) {
725            // Private Creators are always LO
726            // https://dicom.nema.org/medical/dicom/2024a/output/chtml/part05/sect_7.8.html
727            if elem.header().vr() == VR::LO && elem.to_str().unwrap_or_default() == creator {
728                return Some(tag);
729            }
730        }
731        None
732    }
733
734    /// Get a private element from the dataset using the group number, creator and element number.
735    ///
736    /// An error is raised when the group number is not odd,
737    /// the private creator is not found in the group,
738    /// or the private element is not found.
739    ///
740    /// For more info, see the [DICOM standard section on private elements][1].
741    ///
742    /// [1]: https://dicom.nema.org/medical/dicom/2024a/output/chtml/part05/sect_7.8.html
743    ///
744    /// ## Example
745    ///
746    /// ```
747    /// # use dicom_core::{VR, PrimitiveValue, Tag, DataElement};
748    /// # use dicom_object::{InMemDicomObject, PrivateElementError};
749    /// # use std::error::Error;
750    /// let mut ds = InMemDicomObject::from_element_iter([
751    ///     DataElement::new(
752    ///         Tag(0x0009, 0x0010),
753    ///         VR::LO,
754    ///         PrimitiveValue::from("CREATOR 1"),
755    ///     ),
756    ///     DataElement::new(Tag(0x0009, 0x01001), VR::DS, "1.0"),
757    /// ]);
758    /// assert_eq!(
759    ///     ds.private_element(0x0009, "CREATOR 1", 0x01)?
760    ///         .value()
761    ///         .to_str()?,
762    ///     "1.0"
763    /// );
764    /// # Ok::<(), Box<dyn Error>>(())
765    /// ```
766    pub fn private_element(
767        &self,
768        group: GroupNumber,
769        creator: &str,
770        element: u8,
771    ) -> Result<&InMemElement<D>, PrivateElementError> {
772        let tag = self.find_private_creator(group, creator).ok_or_else(|| {
773            PrivateCreatorNotFoundSnafu {
774                group,
775                creator: creator.to_string(),
776            }
777            .build()
778        })?;
779
780        let element_num = (tag.element() << 8) | (element as u16);
781        self.get(Tag(group, element_num)).ok_or_else(|| {
782            ElementNotFoundSnafu {
783                group,
784                creator: creator.to_string(),
785                elem: element,
786            }
787            .build()
788        })
789    }
790
791    /// Insert a data element to the object, replacing (and returning) any
792    /// previous element of the same attribute.
793    /// This might invalidate all sequence and item lengths if the charset of the
794    /// element changes.
795    pub fn put(&mut self, elt: InMemElement<D>) -> Option<InMemElement<D>> {
796        self.put_element(elt)
797    }
798
799    /// Insert a data element to the object, replacing (and returning) any
800    /// previous element of the same attribute.
801    /// This might invalidate all sequence and item lengths if the charset of the
802    /// element changes.
803    pub fn put_element(&mut self, elt: InMemElement<D>) -> Option<InMemElement<D>> {
804        self.len = Length::UNDEFINED;
805        self.invalidate_if_charset_changed(elt.tag());
806        self.entries.insert(elt.tag(), elt)
807    }
808
809    /// Insert a private element into the dataset, replacing (and returning) any
810    /// previous element of the same attribute.
811    ///
812    /// This function will find the next available private element block in the given
813    /// group. If the creator already exists, the element will be added to the block
814    /// already reserved for that creator. If it does not exist, then a new block
815    /// will be reserved for the creator in the specified group.
816    /// An error is returned if there is no space left in the group.
817    ///
818    /// For more info, see the [DICOM standard section on private elements][1].
819    ///
820    /// [1]: https://dicom.nema.org/medical/dicom/2024a/output/chtml/part05/sect_7.8.html
821    ///
822    /// ## Example
823    /// ```
824    /// # use dicom_core::{VR, PrimitiveValue, Tag, DataElement, header::Header};
825    /// # use dicom_object::InMemDicomObject;
826    /// # use std::error::Error;
827    /// let mut ds = InMemDicomObject::new_empty();
828    /// ds.put_private_element(
829    ///     0x0009,
830    ///     "CREATOR 1",
831    ///     0x02,
832    ///     VR::DS,
833    ///     PrimitiveValue::from("1.0"),
834    /// )?;
835    /// assert_eq!(
836    ///     ds.private_element(0x0009, "CREATOR 1", 0x02)?
837    ///         .value()
838    ///         .to_str()?,
839    ///     "1.0"
840    /// );
841    /// assert_eq!(
842    ///     ds.private_element(0x0009, "CREATOR 1", 0x02)?
843    ///         .header()
844    ///         .tag(),
845    ///     Tag(0x0009, 0x0102)
846    /// );
847    /// # Ok::<(), Box<dyn Error>>(())
848    /// ```
849    pub fn put_private_element(
850        &mut self,
851        group: GroupNumber,
852        creator: &str,
853        element: u8,
854        vr: VR,
855        value: PrimitiveValue,
856    ) -> Result<Option<InMemElement<D>>, PrivateElementError> {
857        ensure!(group % 2 == 1, InvalidGroupSnafu { group });
858        let private_creator = self.find_private_creator(group, creator);
859        if let Some(tag) = private_creator {
860            // Private creator already exists
861            let tag = Tag(group, tag.element() << 8 | (element as u16));
862            Ok(self.put_element(DataElement::new(tag, vr, value)))
863        } else {
864            // Find last reserved block of tags.
865            let range = Tag(group, 0)..Tag(group, 0xFF);
866            let last_entry = self.entries.range(range).next_back();
867            let next_available = match last_entry {
868                Some((tag, _)) => tag.element() + 1,
869                None => 0x01,
870            };
871            if next_available < 0xFF {
872                // Put private creator
873                let tag = Tag(group, next_available);
874                self.put_str(tag, VR::LO, creator);
875
876                // Put private element
877                let tag = Tag(group, next_available << 8 | (element as u16));
878                Ok(self.put_element(DataElement::new(tag, vr, value)))
879            } else {
880                NoSpaceSnafu { group }.fail()
881            }
882        }
883    }
884
885    /// Insert a new element with a string value to the object,
886    /// replacing (and returning) any previous element of the same attribute.
887    pub fn put_str(
888        &mut self,
889        tag: Tag,
890        vr: VR,
891        string: impl Into<String>,
892    ) -> Option<InMemElement<D>> {
893        self.put_element(DataElement::new(tag, vr, string.into()))
894    }
895
896    /// Remove a DICOM element by its tag,
897    /// reporting whether it was present.
898    pub fn remove_element(&mut self, tag: Tag) -> bool {
899        if self.entries.remove(&tag).is_some() {
900            self.len = Length::UNDEFINED;
901            true
902        } else {
903            false
904        }
905    }
906
907    /// Remove a DICOM element by its keyword,
908    /// reporting whether it was present.
909    pub fn remove_element_by_name(&mut self, name: &str) -> Result<bool, AccessByNameError> {
910        let tag = self.lookup_name(name)?;
911        Ok(self.entries.remove(&tag).is_some()).map(|removed| {
912            if removed {
913                self.len = Length::UNDEFINED;
914            }
915            removed
916        })
917    }
918
919    /// Remove and return a particular DICOM element by its tag.
920    pub fn take_element(&mut self, tag: Tag) -> Result<InMemElement<D>> {
921        self.entries
922            .remove(&tag)
923            .map(|e| {
924                self.len = Length::UNDEFINED;
925                e
926            })
927            .context(NoSuchDataElementTagSnafu { tag })
928    }
929
930    /// Remove and return a particular DICOM element by its tag,
931    /// if it is present,
932    /// returns `None` otherwise.
933    pub fn take(&mut self, tag: Tag) -> Option<InMemElement<D>> {
934        self.entries.remove(&tag).map(|e| {
935            self.len = Length::UNDEFINED;
936            e
937        })
938    }
939
940    /// Remove and return a particular DICOM element by its name.
941    pub fn take_element_by_name(
942        &mut self,
943        name: &str,
944    ) -> Result<InMemElement<D>, AccessByNameError> {
945        let tag = self.lookup_name(name)?;
946        self.entries
947            .remove(&tag)
948            .map(|e| {
949                self.len = Length::UNDEFINED;
950                e
951            })
952            .with_context(|| NoSuchDataElementAliasSnafu {
953                tag,
954                alias: name.to_string(),
955            })
956    }
957
958    /// Modify the object by
959    /// retaining only the DICOM data elements specified by the predicate.
960    ///
961    /// The elements are visited in ascending tag order,
962    /// and those for which `f(&element)` returns `false` are removed.
963    pub fn retain(&mut self, mut f: impl FnMut(&InMemElement<D>) -> bool) {
964        self.entries.retain(|_, elem| f(elem));
965        self.len = Length::UNDEFINED;
966    }
967
968    /// Obtain a temporary mutable reference to a DICOM value by tag,
969    /// so that mutations can be applied within.
970    ///
971    /// If found, this method resets all related lengths recorded
972    /// and returns `true`.
973    /// Returns `false` otherwise.
974    ///
975    /// # Example
976    ///
977    /// ```
978    /// # use dicom_core::{DataElement, VR, dicom_value};
979    /// # use dicom_dictionary_std::tags;
980    /// # use dicom_object::InMemDicomObject;
981    /// let mut obj = InMemDicomObject::from_element_iter([
982    ///     DataElement::new(tags::LOSSY_IMAGE_COMPRESSION_RATIO, VR::DS, dicom_value!(Strs, ["25"])),
983    /// ]);
984    ///
985    /// // update lossy image compression ratio
986    /// obj.update_value(tags::LOSSY_IMAGE_COMPRESSION_RATIO, |e| {
987    ///     e.primitive_mut().unwrap().extend_str(["2.56"]);
988    /// });
989    ///
990    /// assert_eq!(
991    ///     obj.get(tags::LOSSY_IMAGE_COMPRESSION_RATIO).unwrap().value().to_str().unwrap(),
992    ///     "25\\2.56"
993    /// );
994    /// ```
995    pub fn update_value(
996        &mut self,
997        tag: Tag,
998        f: impl FnMut(&mut Value<InMemDicomObject<D>, InMemFragment>),
999    ) -> bool {
1000        self.invalidate_if_charset_changed(tag);
1001        if let Some(e) = self.entries.get_mut(&tag) {
1002            e.update_value(f);
1003            self.len = Length::UNDEFINED;
1004            true
1005        } else {
1006            false
1007        }
1008    }
1009
1010    /// Obtain a temporary mutable reference to a DICOM value by AttributeSelector,
1011    /// so that mutations can be applied within.
1012    ///
1013    /// If found, this method resets all related lengths recorded
1014    /// and returns `true`.
1015    /// Returns `false` otherwise.
1016    ///
1017    /// See the documentation of [`AttributeSelector`] for more information
1018    /// on how to write attribute selectors.
1019    ///
1020    /// Note: Consider using [`apply`](ApplyOp::apply) when possible.
1021    ///
1022    /// # Example
1023    ///
1024    /// ```
1025    /// # use dicom_core::{DataElement, VR, dicom_value, value::DataSetSequence};
1026    /// # use dicom_dictionary_std::tags;
1027    /// # use dicom_object::InMemDicomObject;
1028    /// # use dicom_core::ops::{AttributeAction, AttributeOp, ApplyOp};
1029    /// let mut dcm = InMemDicomObject::from_element_iter([
1030    ///     DataElement::new(
1031    ///         tags::OTHER_PATIENT_I_DS_SEQUENCE,
1032    ///         VR::SQ,
1033    ///         DataSetSequence::from(vec![InMemDicomObject::from_element_iter([
1034    ///             DataElement::new(
1035    ///                 tags::PATIENT_ID,
1036    ///                 VR::LO,
1037    ///                 dicom_value!(Str, "1234")
1038    ///             )])
1039    ///         ])
1040    ///     ),
1041    /// ]);
1042    /// let selector = (
1043    ///     tags::OTHER_PATIENT_I_DS_SEQUENCE,
1044    ///     0,
1045    ///     tags::PATIENT_ID
1046    /// );
1047    ///
1048    /// // update referenced SOP instance UID for deidentification potentially
1049    /// dcm.update_value_at(*&selector, |e| {
1050    ///     let mut v = e.primitive_mut().unwrap();
1051    ///     *v = dicom_value!(Str, "abcd");
1052    /// });
1053    ///
1054    /// assert_eq!(
1055    ///     dcm.entry_at(*&selector).unwrap().value().to_str().unwrap(),
1056    ///     "abcd"
1057    /// );
1058    /// ```
1059    pub fn update_value_at(
1060        &mut self,
1061        selector: impl Into<AttributeSelector>,
1062        f: impl FnMut(&mut Value<InMemDicomObject<D>, InMemFragment>),
1063    ) -> Result<(), AtAccessError> {
1064        self.entry_at_mut(selector)
1065            .map(|e| e.update_value(f))
1066            .map(|_| {
1067                self.len = Length::UNDEFINED;
1068            })
1069    }
1070
1071    /// Obtain the DICOM value by finding the element
1072    /// that matches the given selector.
1073    ///
1074    /// Returns an error if the respective element or any of its parents
1075    /// cannot be found.
1076    ///
1077    /// See the documentation of [`AttributeSelector`] for more information
1078    /// on how to write attribute selectors.
1079    ///
1080    /// # Example
1081    ///
1082    /// ```no_run
1083    /// # use dicom_core::prelude::*;
1084    /// # use dicom_core::ops::AttributeSelector;
1085    /// # use dicom_dictionary_std::tags;
1086    /// # use dicom_object::InMemDicomObject;
1087    /// # let obj: InMemDicomObject = unimplemented!();
1088    /// let referenced_sop_instance_iod = obj.value_at(
1089    ///     (
1090    ///         tags::SHARED_FUNCTIONAL_GROUPS_SEQUENCE,
1091    ///         tags::REFERENCED_IMAGE_SEQUENCE,
1092    ///         tags::REFERENCED_SOP_INSTANCE_UID,
1093    ///     ))?
1094    ///     .to_str()?;
1095    /// # Ok::<_, Box<dyn std::error::Error>>(())
1096    /// ```
1097    pub fn value_at(
1098        &self,
1099        selector: impl Into<AttributeSelector>,
1100    ) -> Result<&Value<InMemDicomObject<D>, InMemFragment>, AtAccessError> {
1101        let selector: AttributeSelector = selector.into();
1102
1103        let mut obj = self;
1104        for (i, step) in selector.iter().enumerate() {
1105            match step {
1106                // reached the leaf
1107                AttributeSelectorStep::Tag(tag) => {
1108                    return obj.get(*tag).map(|e| e.value()).with_context(|| {
1109                        MissingLeafElementSnafu {
1110                            selector: selector.clone(),
1111                        }
1112                    });
1113                }
1114                // navigate further down
1115                AttributeSelectorStep::Nested { tag, item } => {
1116                    let e = obj
1117                        .entries
1118                        .get(tag)
1119                        .with_context(|| crate::MissingSequenceSnafu {
1120                            selector: selector.clone(),
1121                            step_index: i as u32,
1122                        })?;
1123
1124                    // get items
1125                    let items = e.items().with_context(|| NotASequenceSnafu {
1126                        selector: selector.clone(),
1127                        step_index: i as u32,
1128                    })?;
1129
1130                    // if item.length == i and action is a constructive action, append new item
1131                    obj =
1132                        items
1133                            .get(*item as usize)
1134                            .with_context(|| crate::MissingSequenceSnafu {
1135                                selector: selector.clone(),
1136                                step_index: i as u32,
1137                            })?;
1138                }
1139            }
1140        }
1141
1142        unreachable!()
1143    }
1144
1145    /// Change the 'specific_character_set' tag to ISO_IR 192, marking the dataset as UTF-8
1146    pub fn convert_to_utf8(&mut self) {
1147        self.put(DataElement::new(
1148            tags::SPECIFIC_CHARACTER_SET,
1149            VR::CS,
1150            "ISO_IR 192",
1151        ));
1152    }
1153
1154    /// Get a DataElement by AttributeSelector
1155    ///
1156    /// If the element or other intermediate elements do not exist, the method will return an error.
1157    ///
1158    /// See the documentation of [`AttributeSelector`] for more information
1159    /// on how to write attribute selectors.
1160    ///
1161    /// If you only need the value, use [`value_at`](Self::value_at).
1162    pub fn entry_at(
1163        &self,
1164        selector: impl Into<AttributeSelector>,
1165    ) -> Result<&InMemElement<D>, AtAccessError> {
1166        let selector: AttributeSelector = selector.into();
1167
1168        let mut obj = self;
1169        for (i, step) in selector.iter().enumerate() {
1170            match step {
1171                // reached the leaf
1172                AttributeSelectorStep::Tag(tag) => {
1173                    return obj.get(*tag).with_context(|| MissingLeafElementSnafu {
1174                        selector: selector.clone(),
1175                    })
1176                }
1177                // navigate further down
1178                AttributeSelectorStep::Nested { tag, item } => {
1179                    let e = obj
1180                        .entries
1181                        .get(tag)
1182                        .with_context(|| crate::MissingSequenceSnafu {
1183                            selector: selector.clone(),
1184                            step_index: i as u32,
1185                        })?;
1186
1187                    // get items
1188                    let items = e.items().with_context(|| NotASequenceSnafu {
1189                        selector: selector.clone(),
1190                        step_index: i as u32,
1191                    })?;
1192
1193                    // if item.length == i and action is a constructive action, append new item
1194                    obj =
1195                        items
1196                            .get(*item as usize)
1197                            .with_context(|| crate::MissingSequenceSnafu {
1198                                selector: selector.clone(),
1199                                step_index: i as u32,
1200                            })?;
1201                }
1202            }
1203        }
1204
1205        unreachable!()
1206    }
1207
1208    // Get a mutable reference to a particular entry by AttributeSelector
1209    //
1210    // Should be private for the same reason as `self.get_mut`
1211    fn entry_at_mut(
1212        &mut self,
1213        selector: impl Into<AttributeSelector>,
1214    ) -> Result<&mut InMemElement<D>, AtAccessError> {
1215        let selector: AttributeSelector = selector.into();
1216
1217        let mut obj = self;
1218        for (i, step) in selector.iter().enumerate() {
1219            match step {
1220                // reached the leaf
1221                AttributeSelectorStep::Tag(tag) => {
1222                    return obj.get_mut(*tag).with_context(|| MissingLeafElementSnafu {
1223                        selector: selector.clone(),
1224                    })
1225                }
1226                // navigate further down
1227                AttributeSelectorStep::Nested { tag, item } => {
1228                    let e =
1229                        obj.entries
1230                            .get_mut(tag)
1231                            .with_context(|| crate::MissingSequenceSnafu {
1232                                selector: selector.clone(),
1233                                step_index: i as u32,
1234                            })?;
1235
1236                    // get items
1237                    let items = e.items_mut().with_context(|| NotASequenceSnafu {
1238                        selector: selector.clone(),
1239                        step_index: i as u32,
1240                    })?;
1241
1242                    // if item.length == i and action is a constructive action, append new item
1243                    obj = items.get_mut(*item as usize).with_context(|| {
1244                        crate::MissingSequenceSnafu {
1245                            selector: selector.clone(),
1246                            step_index: i as u32,
1247                        }
1248                    })?;
1249                }
1250            }
1251        }
1252
1253        unreachable!()
1254    }
1255
1256    /// Apply the given attribute operation on this object.
1257    ///
1258    /// For more complex updates, see [`update_value_at`].
1259    ///
1260    /// See the [`dicom_core::ops`] module
1261    /// for more information.
1262    ///
1263    /// # Examples
1264    ///
1265    /// ```rust
1266    /// # use dicom_core::header::{DataElement, VR};
1267    /// # use dicom_core::value::PrimitiveValue;
1268    /// # use dicom_dictionary_std::tags;
1269    /// # use dicom_object::mem::*;
1270    /// # use dicom_object::ops::ApplyResult;
1271    /// use dicom_core::ops::{ApplyOp, AttributeAction, AttributeOp};
1272    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
1273    /// // given an in-memory DICOM object
1274    /// let mut obj = InMemDicomObject::from_element_iter([
1275    ///     DataElement::new(
1276    ///         tags::PATIENT_NAME,
1277    ///         VR::PN,
1278    ///         PrimitiveValue::from("Rosling^Hans")
1279    ///     ),
1280    /// ]);
1281    ///
1282    /// // apply patient name change
1283    /// obj.apply(AttributeOp::new(
1284    ///   tags::PATIENT_NAME,
1285    ///   AttributeAction::SetStr("Patient^Anonymous".into()),
1286    /// ))?;
1287    ///
1288    /// assert_eq!(
1289    ///     obj.element(tags::PATIENT_NAME)?.to_str()?,
1290    ///     "Patient^Anonymous",
1291    /// );
1292    /// # Ok(())
1293    /// # }
1294    /// ```
1295    fn apply(&mut self, op: AttributeOp) -> ApplyResult {
1296        let AttributeOp { selector, action } = op;
1297        let dict = self.dict.clone();
1298
1299        let mut obj = self;
1300        for (i, step) in selector.iter().enumerate() {
1301            match step {
1302                // reached the leaf
1303                AttributeSelectorStep::Tag(tag) => return obj.apply_leaf(*tag, action),
1304                // navigate further down
1305                AttributeSelectorStep::Nested { tag, item } => {
1306                    let e =
1307                        obj.entries
1308                            .get_mut(tag)
1309                            .ok_or_else(|| ApplyError::MissingSequence {
1310                                selector: selector.clone(),
1311                                step_index: i as u32,
1312                            })?;
1313
1314                    // get items
1315                    let items = e.items_mut().ok_or_else(|| ApplyError::NotASequence {
1316                        selector: selector.clone(),
1317                        step_index: i as u32,
1318                    })?;
1319
1320                    // if item.length == i and action is a constructive action, append new item
1321                    obj = if items.len() == *item as usize && action.is_constructive() {
1322                        items.push(InMemDicomObject::new_empty_with_dict(dict.clone()));
1323                        items.last_mut().unwrap()
1324                    } else {
1325                        items.get_mut(*item as usize).ok_or_else(|| {
1326                            ApplyError::MissingSequence {
1327                                selector: selector.clone(),
1328                                step_index: i as u32,
1329                            }
1330                        })?
1331                    };
1332                }
1333            }
1334        }
1335        unreachable!()
1336    }
1337
1338    fn apply_leaf(&mut self, tag: Tag, action: AttributeAction) -> ApplyResult {
1339        self.invalidate_if_charset_changed(tag);
1340        match action {
1341            AttributeAction::Remove => {
1342                self.remove_element(tag);
1343                Ok(())
1344            }
1345            AttributeAction::Empty => {
1346                if let Some(e) = self.entries.get_mut(&tag) {
1347                    let vr = e.vr();
1348                    // replace element
1349                    *e = DataElement::empty(tag, vr);
1350                    self.len = Length::UNDEFINED;
1351                }
1352                Ok(())
1353            }
1354            AttributeAction::SetVr(new_vr) => {
1355                if let Some(e) = self.entries.remove(&tag) {
1356                    let (header, value) = e.into_parts();
1357                    let e = DataElement::new(header.tag, new_vr, value);
1358                    self.put(e);
1359                } else {
1360                    self.put(DataElement::empty(tag, new_vr));
1361                }
1362                Ok(())
1363            }
1364            AttributeAction::Set(new_value) => {
1365                self.apply_change_value_impl(tag, new_value);
1366                Ok(())
1367            }
1368            AttributeAction::SetStr(string) => {
1369                let new_value = PrimitiveValue::from(&*string);
1370                self.apply_change_value_impl(tag, new_value);
1371                Ok(())
1372            }
1373            AttributeAction::SetIfMissing(new_value) => {
1374                if self.get(tag).is_none() {
1375                    self.apply_change_value_impl(tag, new_value);
1376                }
1377                Ok(())
1378            }
1379            AttributeAction::SetStrIfMissing(string) => {
1380                if self.get(tag).is_none() {
1381                    let new_value = PrimitiveValue::from(&*string);
1382                    self.apply_change_value_impl(tag, new_value);
1383                }
1384                Ok(())
1385            }
1386            AttributeAction::Replace(new_value) => {
1387                if self.get(tag).is_some() {
1388                    self.apply_change_value_impl(tag, new_value);
1389                }
1390                Ok(())
1391            }
1392            AttributeAction::ReplaceStr(string) => {
1393                if self.get(tag).is_some() {
1394                    let new_value = PrimitiveValue::from(&*string);
1395                    self.apply_change_value_impl(tag, new_value);
1396                }
1397                Ok(())
1398            }
1399            AttributeAction::PushStr(string) => self.apply_push_str_impl(tag, string),
1400            AttributeAction::PushI32(integer) => self.apply_push_i32_impl(tag, integer),
1401            AttributeAction::PushU32(integer) => self.apply_push_u32_impl(tag, integer),
1402            AttributeAction::PushI16(integer) => self.apply_push_i16_impl(tag, integer),
1403            AttributeAction::PushU16(integer) => self.apply_push_u16_impl(tag, integer),
1404            AttributeAction::PushF32(number) => self.apply_push_f32_impl(tag, number),
1405            AttributeAction::PushF64(number) => self.apply_push_f64_impl(tag, number),
1406            AttributeAction::Truncate(limit) => {
1407                self.update_value(tag, |value| value.truncate(limit));
1408                Ok(())
1409            }
1410            _ => UnsupportedActionSnafu.fail(),
1411        }
1412    }
1413
1414    fn apply_change_value_impl(&mut self, tag: Tag, new_value: PrimitiveValue) {
1415        self.invalidate_if_charset_changed(tag);
1416
1417        if let Some(e) = self.entries.get_mut(&tag) {
1418            let vr = e.vr();
1419            // handle edge case: if VR is SQ and suggested value is empty,
1420            // then create an empty data set sequence
1421            let new_value = if vr == VR::SQ && new_value.is_empty() {
1422                DataSetSequence::empty().into()
1423            } else {
1424                Value::from(new_value)
1425            };
1426            *e = DataElement::new(tag, vr, new_value);
1427            self.len = Length::UNDEFINED;
1428        } else {
1429            // infer VR from tag
1430            let vr = dicom_dictionary_std::StandardDataDictionary
1431                .by_tag(tag)
1432                .and_then(|entry| entry.vr().exact())
1433                .unwrap_or(VR::UN);
1434            // insert element
1435
1436            // handle edge case: if VR is SQ and suggested value is empty,
1437            // then create an empty data set sequence
1438            let new_value = if vr == VR::SQ && new_value.is_empty() {
1439                DataSetSequence::empty().into()
1440            } else {
1441                Value::from(new_value)
1442            };
1443
1444            self.put(DataElement::new(tag, vr, new_value));
1445        }
1446    }
1447
1448    fn invalidate_if_charset_changed(&mut self, tag: Tag) {
1449        if tag == tags::SPECIFIC_CHARACTER_SET {
1450            self.charset_changed = true;
1451        }
1452    }
1453
1454    fn apply_push_str_impl(&mut self, tag: Tag, string: Cow<'static, str>) -> ApplyResult {
1455        if let Some(e) = self.entries.remove(&tag) {
1456            let (header, value) = e.into_parts();
1457            match value {
1458                Value::Primitive(mut v) => {
1459                    self.invalidate_if_charset_changed(tag);
1460                    // extend value
1461                    v.extend_str([string]).context(ModifySnafu)?;
1462                    // reinsert element
1463                    self.put(DataElement::new(tag, header.vr, v));
1464                    Ok(())
1465                }
1466
1467                Value::PixelSequence(..) => IncompatibleTypesSnafu {
1468                    kind: ValueType::PixelSequence,
1469                }
1470                .fail(),
1471                Value::Sequence(..) => IncompatibleTypesSnafu {
1472                    kind: ValueType::DataSetSequence,
1473                }
1474                .fail(),
1475            }
1476        } else {
1477            // infer VR from tag
1478            let vr = dicom_dictionary_std::StandardDataDictionary
1479                .by_tag(tag)
1480                .and_then(|entry| entry.vr().exact())
1481                .unwrap_or(VR::UN);
1482            // insert element
1483            self.put(DataElement::new(tag, vr, PrimitiveValue::from(&*string)));
1484            Ok(())
1485        }
1486    }
1487
1488    fn apply_push_i32_impl(&mut self, tag: Tag, integer: i32) -> ApplyResult {
1489        if let Some(e) = self.entries.remove(&tag) {
1490            let (header, value) = e.into_parts();
1491            match value {
1492                Value::Primitive(mut v) => {
1493                    // extend value
1494                    v.extend_i32([integer]).context(ModifySnafu)?;
1495                    // reinsert element
1496                    self.put(DataElement::new(tag, header.vr, v));
1497                    Ok(())
1498                }
1499
1500                Value::PixelSequence(..) => IncompatibleTypesSnafu {
1501                    kind: ValueType::PixelSequence,
1502                }
1503                .fail(),
1504                Value::Sequence(..) => IncompatibleTypesSnafu {
1505                    kind: ValueType::DataSetSequence,
1506                }
1507                .fail(),
1508            }
1509        } else {
1510            // infer VR from tag
1511            let vr = dicom_dictionary_std::StandardDataDictionary
1512                .by_tag(tag)
1513                .and_then(|entry| entry.vr().exact())
1514                .unwrap_or(VR::SL);
1515            // insert element
1516            self.put(DataElement::new(tag, vr, PrimitiveValue::from(integer)));
1517            Ok(())
1518        }
1519    }
1520
1521    fn apply_push_u32_impl(&mut self, tag: Tag, integer: u32) -> ApplyResult {
1522        if let Some(e) = self.entries.remove(&tag) {
1523            let (header, value) = e.into_parts();
1524            match value {
1525                Value::Primitive(mut v) => {
1526                    // extend value
1527                    v.extend_u32([integer]).context(ModifySnafu)?;
1528                    // reinsert element
1529                    self.put(DataElement::new(tag, header.vr, v));
1530                    Ok(())
1531                }
1532
1533                Value::PixelSequence(..) => IncompatibleTypesSnafu {
1534                    kind: ValueType::PixelSequence,
1535                }
1536                .fail(),
1537                Value::Sequence(..) => IncompatibleTypesSnafu {
1538                    kind: ValueType::DataSetSequence,
1539                }
1540                .fail(),
1541            }
1542        } else {
1543            // infer VR from tag
1544            let vr = dicom_dictionary_std::StandardDataDictionary
1545                .by_tag(tag)
1546                .and_then(|entry| entry.vr().exact())
1547                .unwrap_or(VR::UL);
1548            // insert element
1549            self.put(DataElement::new(tag, vr, PrimitiveValue::from(integer)));
1550            Ok(())
1551        }
1552    }
1553
1554    fn apply_push_i16_impl(&mut self, tag: Tag, integer: i16) -> ApplyResult {
1555        if let Some(e) = self.entries.remove(&tag) {
1556            let (header, value) = e.into_parts();
1557            match value {
1558                Value::Primitive(mut v) => {
1559                    // extend value
1560                    v.extend_i16([integer]).context(ModifySnafu)?;
1561                    // reinsert element
1562                    self.put(DataElement::new(tag, header.vr, v));
1563                    Ok(())
1564                }
1565
1566                Value::PixelSequence(..) => IncompatibleTypesSnafu {
1567                    kind: ValueType::PixelSequence,
1568                }
1569                .fail(),
1570                Value::Sequence(..) => IncompatibleTypesSnafu {
1571                    kind: ValueType::DataSetSequence,
1572                }
1573                .fail(),
1574            }
1575        } else {
1576            // infer VR from tag
1577            let vr = dicom_dictionary_std::StandardDataDictionary
1578                .by_tag(tag)
1579                .and_then(|entry| entry.vr().exact())
1580                .unwrap_or(VR::SS);
1581            // insert element
1582            self.put(DataElement::new(tag, vr, PrimitiveValue::from(integer)));
1583            Ok(())
1584        }
1585    }
1586
1587    fn apply_push_u16_impl(&mut self, tag: Tag, integer: u16) -> ApplyResult {
1588        if let Some(e) = self.entries.remove(&tag) {
1589            let (header, value) = e.into_parts();
1590            match value {
1591                Value::Primitive(mut v) => {
1592                    // extend value
1593                    v.extend_u16([integer]).context(ModifySnafu)?;
1594                    // reinsert element
1595                    self.put(DataElement::new(tag, header.vr, v));
1596                    Ok(())
1597                }
1598
1599                Value::PixelSequence(..) => IncompatibleTypesSnafu {
1600                    kind: ValueType::PixelSequence,
1601                }
1602                .fail(),
1603                Value::Sequence(..) => IncompatibleTypesSnafu {
1604                    kind: ValueType::DataSetSequence,
1605                }
1606                .fail(),
1607            }
1608        } else {
1609            // infer VR from tag
1610            let vr = dicom_dictionary_std::StandardDataDictionary
1611                .by_tag(tag)
1612                .and_then(|entry| entry.vr().exact())
1613                .unwrap_or(VR::US);
1614            // insert element
1615            self.put(DataElement::new(tag, vr, PrimitiveValue::from(integer)));
1616            Ok(())
1617        }
1618    }
1619
1620    fn apply_push_f32_impl(&mut self, tag: Tag, number: f32) -> ApplyResult {
1621        if let Some(e) = self.entries.remove(&tag) {
1622            let (header, value) = e.into_parts();
1623            match value {
1624                Value::Primitive(mut v) => {
1625                    // extend value
1626                    v.extend_f32([number]).context(ModifySnafu)?;
1627                    // reinsert element
1628                    self.put(DataElement::new(tag, header.vr, v));
1629                    Ok(())
1630                }
1631
1632                Value::PixelSequence(..) => IncompatibleTypesSnafu {
1633                    kind: ValueType::PixelSequence,
1634                }
1635                .fail(),
1636                Value::Sequence(..) => IncompatibleTypesSnafu {
1637                    kind: ValueType::DataSetSequence,
1638                }
1639                .fail(),
1640            }
1641        } else {
1642            // infer VR from tag
1643            let vr = dicom_dictionary_std::StandardDataDictionary
1644                .by_tag(tag)
1645                .and_then(|entry| entry.vr().exact())
1646                .unwrap_or(VR::FL);
1647            // insert element
1648            self.put(DataElement::new(tag, vr, PrimitiveValue::from(number)));
1649            Ok(())
1650        }
1651    }
1652
1653    fn apply_push_f64_impl(&mut self, tag: Tag, number: f64) -> ApplyResult {
1654        if let Some(e) = self.entries.remove(&tag) {
1655            let (header, value) = e.into_parts();
1656            match value {
1657                Value::Primitive(mut v) => {
1658                    // extend value
1659                    v.extend_f64([number]).context(ModifySnafu)?;
1660                    // reinsert element
1661                    self.put(DataElement::new(tag, header.vr, v));
1662                    Ok(())
1663                }
1664
1665                Value::PixelSequence(..) => IncompatibleTypesSnafu {
1666                    kind: ValueType::PixelSequence,
1667                }
1668                .fail(),
1669                Value::Sequence(..) => IncompatibleTypesSnafu {
1670                    kind: ValueType::DataSetSequence,
1671                }
1672                .fail(),
1673            }
1674        } else {
1675            // infer VR from tag
1676            let vr = dicom_dictionary_std::StandardDataDictionary
1677                .by_tag(tag)
1678                .and_then(|entry| entry.vr().exact())
1679                .unwrap_or(VR::FD);
1680            // insert element
1681            self.put(DataElement::new(tag, vr, PrimitiveValue::from(number)));
1682            Ok(())
1683        }
1684    }
1685
1686    /// Write this object's data set into the given writer,
1687    /// with the given encoder specifications,
1688    /// without preamble, magic code, nor file meta group.
1689    ///
1690    /// The text encoding to use will be the default character set
1691    /// until _Specific Character Set_ is found in the data set,
1692    /// in which then that character set will be used.
1693    ///
1694    /// Note: [`write_dataset_with_ts`] and [`write_dataset_with_ts_cs`]
1695    /// may be easier to use.
1696    ///
1697    /// [`write_dataset_with_ts`]: #method.write_dataset_with_ts
1698    /// [`write_dataset_with_ts_cs`]: #method.write_dataset_with_ts_cs
1699    pub fn write_dataset<W, E>(&self, to: W, encoder: E) -> Result<(), WriteError>
1700    where
1701        W: Write,
1702        E: EncodeTo<W>,
1703    {
1704        // prepare data set writer
1705        let mut dset_writer = DataSetWriter::new(to, encoder);
1706        let required_options = IntoTokensOptions::new(self.charset_changed);
1707        // write object
1708        dset_writer
1709            .write_sequence(self.into_tokens_with_options(required_options))
1710            .context(PrintDataSetSnafu)?;
1711
1712        Ok(())
1713    }
1714
1715    /// Write this object's data set into the given printer,
1716    /// with the specified transfer syntax and character set,
1717    /// without preamble, magic code, nor file meta group.
1718    ///
1719    /// If the attribute _Specific Character Set_ is found in the data set,
1720    /// the last parameter is overridden accordingly.
1721    /// See also [`write_dataset_with_ts`](Self::write_dataset_with_ts).
1722    pub fn write_dataset_with_ts_cs<W>(
1723        &self,
1724        to: W,
1725        ts: &TransferSyntax,
1726        cs: SpecificCharacterSet,
1727    ) -> Result<(), WriteError>
1728    where
1729        W: Write,
1730    {
1731        // prepare data set writer
1732        let mut dset_writer = DataSetWriter::with_ts_cs(to, ts, cs).context(CreatePrinterSnafu)?;
1733        let required_options = IntoTokensOptions::new(self.charset_changed);
1734
1735        // write object
1736        dset_writer
1737            .write_sequence(self.into_tokens_with_options(required_options))
1738            .context(PrintDataSetSnafu)?;
1739
1740        Ok(())
1741    }
1742
1743    /// Write this object's data set into the given writer,
1744    /// with the specified transfer syntax,
1745    /// without preamble, magic code, nor file meta group.
1746    ///
1747    /// The default character set is assumed
1748    /// until the _Specific Character Set_ is found in the data set,
1749    /// after which the text encoder is overridden accordingly.
1750    pub fn write_dataset_with_ts<W>(&self, to: W, ts: &TransferSyntax) -> Result<(), WriteError>
1751    where
1752        W: Write,
1753    {
1754        self.write_dataset_with_ts_cs(to, ts, SpecificCharacterSet::default())
1755    }
1756
1757    /// Encapsulate this object to contain a file meta group
1758    /// as described exactly by the given table.
1759    ///
1760    /// **Note:** this method will not adjust the file meta group
1761    /// to be semantically valid for the object.
1762    /// Namely, the _Media Storage SOP Instance UID_
1763    /// and _Media Storage SOP Class UID_
1764    /// are not updated based on the receiving data set.
1765    pub fn with_exact_meta(self, meta: FileMetaTable) -> FileDicomObject<Self> {
1766        FileDicomObject { meta, obj: self }
1767    }
1768
1769    /// Encapsulate this object to contain a file meta group,
1770    /// created through the given file meta table builder.
1771    ///
1772    /// A complete file meta group should provide
1773    /// the _Transfer Syntax UID_,
1774    /// the _Media Storage SOP Instance UID_,
1775    /// and the _Media Storage SOP Class UID_.
1776    /// The last two will be filled with the values of
1777    /// _SOP Instance UID_ and _SOP Class UID_
1778    /// if they are present in this object.
1779    ///
1780    /// # Example
1781    ///
1782    /// ```no_run
1783    /// # use dicom_core::{DataElement, VR};
1784    /// # use dicom_dictionary_std::tags;
1785    /// # use dicom_dictionary_std::uids;
1786    /// use dicom_object::{InMemDicomObject, meta::FileMetaTableBuilder};
1787    ///
1788    /// let obj = InMemDicomObject::from_element_iter([
1789    ///     DataElement::new(tags::SOP_CLASS_UID, VR::UI, uids::COMPUTED_RADIOGRAPHY_IMAGE_STORAGE),
1790    ///     DataElement::new(tags::SOP_INSTANCE_UID, VR::UI, "2.25.60156688944589400766024286894543900794"),
1791    ///     // ...
1792    /// ]);
1793    ///
1794    /// let obj = obj.with_meta(FileMetaTableBuilder::new()
1795    ///     .transfer_syntax(uids::EXPLICIT_VR_LITTLE_ENDIAN))?;
1796    ///
1797    /// // can now save everything to a file
1798    /// let meta = obj.write_to_file("out.dcm")?;
1799    /// # Result::<_, Box<dyn std::error::Error>>::Ok(())
1800    /// ```
1801    pub fn with_meta(
1802        self,
1803        mut meta: FileMetaTableBuilder,
1804    ) -> Result<FileDicomObject<Self>, WithMetaError> {
1805        if let Some(elem) = self.get(tags::SOP_INSTANCE_UID) {
1806            meta = meta.media_storage_sop_instance_uid(
1807                elem.value().to_str().context(PrepareMetaTableSnafu)?,
1808            );
1809        }
1810        if let Some(elem) = self.get(tags::SOP_CLASS_UID) {
1811            meta = meta
1812                .media_storage_sop_class_uid(elem.value().to_str().context(PrepareMetaTableSnafu)?);
1813        }
1814        Ok(FileDicomObject {
1815            meta: meta.build().context(BuildMetaTableSnafu)?,
1816            obj: self,
1817        })
1818    }
1819
1820    /// Obtain an iterator over the elements of this object.
1821    pub fn iter(&self) -> impl Iterator<Item = &InMemElement<D>> + '_ {
1822        self.into_iter()
1823    }
1824
1825    /// Obtain an iteartor over the tags of the object's elements.
1826    pub fn tags(&self) -> impl Iterator<Item = Tag> + '_ {
1827        self.entries.keys().copied()
1828    }
1829
1830    // private methods
1831
1832    /// Build an object by consuming a data set parser.
1833    fn build_object<I>(
1834        dataset: &mut I,
1835        dict: D,
1836        in_item: bool,
1837        len: Length,
1838        read_until: Option<Tag>,
1839    ) -> Result<Self, ReadError>
1840    where
1841        I: ?Sized + Iterator<Item = ParserResult<DataToken>>,
1842    {
1843        let mut entries: BTreeMap<Tag, InMemElement<D>> = BTreeMap::new();
1844        // perform a structured parsing of incoming tokens
1845        while let Some(token) = dataset.next() {
1846            let elem = match token.context(ReadTokenSnafu)? {
1847                DataToken::PixelSequenceStart => {
1848                    // stop reading if reached `read_until` tag
1849                    if read_until
1850                        .map(|t| t <= Tag(0x7fe0, 0x0010))
1851                        .unwrap_or(false)
1852                    {
1853                        break;
1854                    }
1855                    let value = InMemDicomObject::build_encapsulated_data(&mut *dataset)?;
1856                    DataElement::new(Tag(0x7fe0, 0x0010), VR::OB, value)
1857                }
1858                DataToken::ElementHeader(header) => {
1859                    // stop reading if reached `read_until` tag
1860                    if read_until.map(|t| t <= header.tag).unwrap_or(false) {
1861                        break;
1862                    }
1863
1864                    // fetch respective value, place it in the entries
1865                    let next_token = dataset.next().context(MissingElementValueSnafu)?;
1866                    match next_token.context(ReadTokenSnafu)? {
1867                        DataToken::PrimitiveValue(v) => InMemElement::new_with_len(
1868                            header.tag,
1869                            header.vr,
1870                            header.len,
1871                            Value::Primitive(v),
1872                        ),
1873                        token => {
1874                            return UnexpectedTokenSnafu { token }.fail();
1875                        }
1876                    }
1877                }
1878                DataToken::SequenceStart { tag, len } => {
1879                    // stop reading if reached `read_until` tag
1880                    if read_until.map(|t| t <= tag).unwrap_or(false) {
1881                        break;
1882                    }
1883
1884                    // delegate sequence building to another function
1885                    let items = Self::build_sequence(tag, len, &mut *dataset, &dict)?;
1886                    DataElement::new_with_len(
1887                        tag,
1888                        VR::SQ,
1889                        len,
1890                        Value::Sequence(DataSetSequence::new(items, len)),
1891                    )
1892                }
1893                DataToken::ItemEnd if in_item => {
1894                    // end of item, leave now
1895                    return Ok(InMemDicomObject {
1896                        entries,
1897                        dict,
1898                        len,
1899                        charset_changed: false,
1900                    });
1901                }
1902                token => return UnexpectedTokenSnafu { token }.fail(),
1903            };
1904            entries.insert(elem.tag(), elem);
1905        }
1906
1907        Ok(InMemDicomObject {
1908            entries,
1909            dict,
1910            len,
1911            charset_changed: false,
1912        })
1913    }
1914
1915    /// Build an encapsulated pixel data by collecting all fragments into an
1916    /// in-memory DICOM value.
1917    fn build_encapsulated_data<I>(
1918        dataset: I,
1919    ) -> Result<Value<InMemDicomObject<D>, InMemFragment>, ReadError>
1920    where
1921        I: Iterator<Item = ParserResult<DataToken>>,
1922    {
1923        // continue fetching tokens to retrieve:
1924        // - the offset table
1925        // - the various compressed fragments
1926        //
1927        // Note: as there is still no standard way to represent this in memory,
1928        // this code will currently flatten all compressed fragments into a
1929        // single vector.
1930
1931        let mut offset_table = None;
1932
1933        let mut fragments = C::new();
1934
1935        for token in dataset {
1936            match token.context(ReadTokenSnafu)? {
1937                DataToken::OffsetTable(table) => {
1938                    offset_table = Some(table);
1939                }
1940                DataToken::ItemValue(data) => {
1941                    fragments.push(data);
1942                }
1943                DataToken::ItemEnd => {
1944                    // at the end of the first item ensure the presence of
1945                    // an empty offset_table here, so that the next items
1946                    // are seen as compressed fragments
1947                    if offset_table.is_none() {
1948                        offset_table = Some(Vec::new())
1949                    }
1950                }
1951                DataToken::ItemStart { len: _ } => { /* no-op */ }
1952                DataToken::SequenceEnd => {
1953                    // end of pixel data
1954                    break;
1955                }
1956                // the following variants are unexpected
1957                token @ DataToken::ElementHeader(_)
1958                | token @ DataToken::PixelSequenceStart
1959                | token @ DataToken::SequenceStart { .. }
1960                | token @ DataToken::PrimitiveValue(_) => {
1961                    return UnexpectedTokenSnafu { token }.fail();
1962                }
1963            }
1964        }
1965
1966        Ok(Value::PixelSequence(PixelFragmentSequence::new(
1967            offset_table.unwrap_or_default(),
1968            fragments,
1969        )))
1970    }
1971
1972    /// Build a DICOM sequence by consuming a data set parser.
1973    fn build_sequence<I>(
1974        _tag: Tag,
1975        _len: Length,
1976        dataset: &mut I,
1977        dict: &D,
1978    ) -> Result<C<InMemDicomObject<D>>, ReadError>
1979    where
1980        I: ?Sized + Iterator<Item = ParserResult<DataToken>>,
1981    {
1982        let mut items: C<_> = SmallVec::new();
1983        while let Some(token) = dataset.next() {
1984            match token.context(ReadTokenSnafu)? {
1985                DataToken::ItemStart { len } => {
1986                    items.push(Self::build_object(
1987                        &mut *dataset,
1988                        dict.clone(),
1989                        true,
1990                        len,
1991                        None,
1992                    )?);
1993                }
1994                DataToken::SequenceEnd => {
1995                    return Ok(items);
1996                }
1997                token => return UnexpectedTokenSnafu { token }.fail(),
1998            };
1999        }
2000
2001        // iterator fully consumed without a sequence delimiter
2002        PrematureEndSnafu.fail()
2003    }
2004
2005    fn lookup_name(&self, name: &str) -> Result<Tag, AccessByNameError> {
2006        self.dict
2007            .by_name(name)
2008            .context(NoSuchAttributeNameSnafu { name })
2009            .map(|e| e.tag())
2010    }
2011}
2012
2013impl<D> ApplyOp for InMemDicomObject<D>
2014where
2015    D: DataDictionary,
2016    D: Clone,
2017{
2018    type Err = ApplyError;
2019
2020    #[inline]
2021    fn apply(&mut self, op: AttributeOp) -> ApplyResult {
2022        self.apply(op)
2023    }
2024}
2025
2026impl<'a, D> IntoIterator for &'a InMemDicomObject<D> {
2027    type Item = &'a InMemElement<D>;
2028    type IntoIter = ::std::collections::btree_map::Values<'a, Tag, InMemElement<D>>;
2029
2030    fn into_iter(self) -> Self::IntoIter {
2031        self.entries.values()
2032    }
2033}
2034
2035impl<D> IntoIterator for InMemDicomObject<D> {
2036    type Item = InMemElement<D>;
2037    type IntoIter = Iter<D>;
2038
2039    fn into_iter(self) -> Self::IntoIter {
2040        Iter {
2041            inner: self.entries.into_iter(),
2042        }
2043    }
2044}
2045
2046/// Base iterator type for an in-memory DICOM object.
2047#[derive(Debug)]
2048pub struct Iter<D> {
2049    inner: ::std::collections::btree_map::IntoIter<Tag, InMemElement<D>>,
2050}
2051
2052impl<D> Iterator for Iter<D> {
2053    type Item = InMemElement<D>;
2054
2055    fn next(&mut self) -> Option<Self::Item> {
2056        self.inner.next().map(|x| x.1)
2057    }
2058
2059    fn size_hint(&self) -> (usize, Option<usize>) {
2060        self.inner.size_hint()
2061    }
2062
2063    fn count(self) -> usize {
2064        self.inner.count()
2065    }
2066}
2067
2068impl<D> Extend<InMemElement<D>> for InMemDicomObject<D> {
2069    fn extend<I>(&mut self, iter: I)
2070    where
2071        I: IntoIterator<Item = InMemElement<D>>,
2072    {
2073        self.len = Length::UNDEFINED;
2074        self.entries.extend(iter.into_iter().map(|e| (e.tag(), e)))
2075    }
2076}
2077
2078fn even_len(l: u32) -> u32 {
2079    (l + 1) & !1
2080}
2081
2082#[cfg(test)]
2083mod tests {
2084    use super::*;
2085    use crate::open_file;
2086    use byteordered::Endianness;
2087    use dicom_core::chrono::FixedOffset;
2088    use dicom_core::value::{DicomDate, DicomDateTime, DicomTime};
2089    use dicom_core::{dicom_value, header::DataElementHeader};
2090    use dicom_encoding::{
2091        decode::{basic::BasicDecoder, implicit_le::ImplicitVRLittleEndianDecoder},
2092        encode::{implicit_le::ImplicitVRLittleEndianEncoder, EncoderFor},
2093    };
2094    use dicom_parser::StatefulDecoder;
2095
2096    fn assert_obj_eq<D>(obj1: &InMemDicomObject<D>, obj2: &InMemDicomObject<D>)
2097    where
2098        D: std::fmt::Debug,
2099    {
2100        // debug representation because it makes a stricter comparison and
2101        // assumes that Undefined lengths are equal.
2102        assert_eq!(format!("{:?}", obj1), format!("{:?}", obj2))
2103    }
2104
2105    #[test]
2106    fn inmem_object_compare() {
2107        let mut obj1 = InMemDicomObject::new_empty();
2108        let mut obj2 = InMemDicomObject::new_empty();
2109        assert_eq!(obj1, obj2);
2110        let empty_patient_name = DataElement::empty(Tag(0x0010, 0x0010), VR::PN);
2111        obj1.put(empty_patient_name.clone());
2112        assert_ne!(obj1, obj2);
2113        obj2.put(empty_patient_name.clone());
2114        assert_obj_eq(&obj1, &obj2);
2115    }
2116
2117    #[test]
2118    fn inmem_object_read_dataset() {
2119        let data_in = [
2120            0x10, 0x00, 0x10, 0x00, // Tag(0x0010, 0x0010)
2121            0x08, 0x00, 0x00, 0x00, // Length: 8
2122            b'D', b'o', b'e', b'^', b'J', b'o', b'h', b'n',
2123        ];
2124
2125        let decoder = ImplicitVRLittleEndianDecoder::default();
2126        let text = SpecificCharacterSet::default();
2127        let mut cursor = &data_in[..];
2128        let parser = StatefulDecoder::new(
2129            &mut cursor,
2130            decoder,
2131            BasicDecoder::new(Endianness::Little),
2132            text,
2133        );
2134
2135        let obj = InMemDicomObject::read_dataset(parser).unwrap();
2136
2137        let mut gt = InMemDicomObject::new_empty();
2138
2139        let patient_name = DataElement::new(
2140            Tag(0x0010, 0x0010),
2141            VR::PN,
2142            dicom_value!(Strs, ["Doe^John"]),
2143        );
2144        gt.put(patient_name);
2145
2146        assert_eq!(obj, gt);
2147    }
2148
2149    #[test]
2150    fn inmem_object_read_dataset_with_ts_cs() {
2151        let data_in = [
2152            0x10, 0x00, 0x10, 0x00, // Tag(0x0010, 0x0010)
2153            0x08, 0x00, 0x00, 0x00, // Length: 8
2154            b'D', b'o', b'e', b'^', b'J', b'o', b'h', b'n',
2155        ];
2156
2157        let ts = TransferSyntaxRegistry.get("1.2.840.10008.1.2").unwrap();
2158        let cs = SpecificCharacterSet::default();
2159        let mut cursor = &data_in[..];
2160
2161        let obj = InMemDicomObject::read_dataset_with_dict_ts_cs(
2162            &mut cursor,
2163            StandardDataDictionary,
2164            &ts,
2165            cs,
2166        )
2167        .unwrap();
2168
2169        let mut gt = InMemDicomObject::new_empty();
2170
2171        let patient_name = DataElement::new(
2172            Tag(0x0010, 0x0010),
2173            VR::PN,
2174            dicom_value!(Strs, ["Doe^John"]),
2175        );
2176        gt.put(patient_name);
2177
2178        assert_eq!(obj, gt);
2179    }
2180
2181    /// Reading a data set
2182    /// saves the original length of a text element.
2183    #[test]
2184    fn inmem_object_read_dataset_saves_len() {
2185        let data_in = [
2186            // SpecificCharacterSet (0008,0005)
2187            0x08, 0x00, 0x05, 0x00, //
2188            // Length: 10
2189            0x0a, 0x00, 0x00, 0x00, //
2190            b'I', b'S', b'O', b'_', b'I', b'R', b' ', b'1', b'0', b'0',
2191            // ReferringPhysicianName (0008,0090)
2192            0x08, 0x00, 0x90, 0x00, //
2193            // Length: 12
2194            0x0c, 0x00, 0x00, 0x00, b'S', b'i', b'm', 0xF5, b'e', b's', b'^', b'J', b'o', 0xE3,
2195            b'o', b' ',
2196        ];
2197
2198        let ts = TransferSyntaxRegistry.get("1.2.840.10008.1.2").unwrap();
2199        let mut cursor = &data_in[..];
2200
2201        let obj =
2202            InMemDicomObject::read_dataset_with_dict_ts(&mut cursor, StandardDataDictionary, &ts)
2203                .unwrap();
2204
2205        let physician_name = obj.element(Tag(0x0008, 0x0090)).unwrap();
2206        assert_eq!(physician_name.header().len, Length(12));
2207        assert_eq!(physician_name.value().to_str().unwrap(), "Simões^João");
2208    }
2209
2210    #[test]
2211    fn inmem_object_write_dataset() {
2212        let mut obj = InMemDicomObject::new_empty();
2213
2214        let patient_name =
2215            DataElement::new(Tag(0x0010, 0x0010), VR::PN, dicom_value!(Str, "Doe^John"));
2216        obj.put(patient_name);
2217
2218        let mut out = Vec::new();
2219
2220        let printer = EncoderFor::new(ImplicitVRLittleEndianEncoder::default());
2221
2222        obj.write_dataset(&mut out, printer).unwrap();
2223
2224        assert_eq!(
2225            out,
2226            &[
2227                0x10, 0x00, 0x10, 0x00, // Tag(0x0010, 0x0010)
2228                0x08, 0x00, 0x00, 0x00, // Length: 8
2229                b'D', b'o', b'e', b'^', b'J', b'o', b'h', b'n',
2230            ][..],
2231        );
2232    }
2233
2234    #[test]
2235    fn inmem_object_write_dataset_with_ts() {
2236        let mut obj = InMemDicomObject::new_empty();
2237
2238        let patient_name =
2239            DataElement::new(Tag(0x0010, 0x0010), VR::PN, dicom_value!(Str, "Doe^John"));
2240        obj.put(patient_name);
2241
2242        let mut out = Vec::new();
2243
2244        let ts = TransferSyntaxRegistry.get("1.2.840.10008.1.2.1").unwrap();
2245
2246        obj.write_dataset_with_ts(&mut out, &ts).unwrap();
2247
2248        assert_eq!(
2249            out,
2250            &[
2251                0x10, 0x00, 0x10, 0x00, // Tag(0x0010, 0x0010)
2252                b'P', b'N', // VR: PN
2253                0x08, 0x00, // Length: 8
2254                b'D', b'o', b'e', b'^', b'J', b'o', b'h', b'n',
2255            ][..],
2256        );
2257    }
2258
2259    #[test]
2260    fn inmem_object_write_dataset_with_ts_cs() {
2261        let mut obj = InMemDicomObject::new_empty();
2262
2263        let patient_name =
2264            DataElement::new(Tag(0x0010, 0x0010), VR::PN, dicom_value!(Str, "Doe^John"));
2265        obj.put(patient_name);
2266
2267        let mut out = Vec::new();
2268
2269        let ts = TransferSyntaxRegistry.get("1.2.840.10008.1.2").unwrap();
2270        let cs = SpecificCharacterSet::default();
2271
2272        obj.write_dataset_with_ts_cs(&mut out, &ts, cs).unwrap();
2273
2274        assert_eq!(
2275            out,
2276            &[
2277                0x10, 0x00, 0x10, 0x00, // Tag(0x0010, 0x0010)
2278                0x08, 0x00, 0x00, 0x00, // Length: 8
2279                b'D', b'o', b'e', b'^', b'J', b'o', b'h', b'n',
2280            ][..],
2281        );
2282    }
2283
2284    /// writing a DICOM date time into an object
2285    /// should include value padding
2286    #[test]
2287    fn inmem_object_write_datetime_odd() {
2288        let mut obj = InMemDicomObject::new_empty();
2289
2290        // add a number that will be encoded in text
2291        let instance_number =
2292            DataElement::new(Tag(0x0020, 0x0013), VR::IS, PrimitiveValue::from(1_i32));
2293        obj.put(instance_number);
2294
2295        // add a date time
2296        let dt = DicomDateTime::from_date_and_time_with_time_zone(
2297            DicomDate::from_ymd(2022, 11, 22).unwrap(),
2298            DicomTime::from_hms(18, 09, 35).unwrap(),
2299            FixedOffset::east_opt(3600).unwrap(),
2300        )
2301        .unwrap();
2302        let instance_coercion_date_time =
2303            DataElement::new(Tag(0x0008, 0x0015), VR::DT, dicom_value!(DateTime, dt));
2304        obj.put(instance_coercion_date_time);
2305
2306        // explicit VR Little Endian
2307        let ts = TransferSyntaxRegistry.get("1.2.840.10008.1.2.1").unwrap();
2308
2309        let mut out = Vec::new();
2310        obj.write_dataset_with_ts(&mut out, &ts)
2311            .expect("should write DICOM data without errors");
2312
2313        assert_eq!(
2314            out,
2315            &[
2316                // instance coercion date time
2317                0x08, 0x00, 0x15, 0x00, // Tag(0x0008, 0x0015)
2318                b'D', b'T', // VR: DT
2319                0x14, 0x00, // Length: 20 bytes
2320                b'2', b'0', b'2', b'2', b'1', b'1', b'2', b'2', // date
2321                b'1', b'8', b'0', b'9', b'3', b'5', // time
2322                b'+', b'0', b'1', b'0', b'0', // offset
2323                b' ', // padding to even length
2324                // instance number
2325                0x20, 0x00, 0x13, 0x00, // Tag(0x0020, 0x0013)
2326                b'I', b'S', // VR: IS
2327                0x02, 0x00, // Length: 2 bytes
2328                b'1', b' ' // 1, with padding
2329            ][..],
2330        );
2331    }
2332
2333    /// Writes a file from scratch
2334    /// and opens it to check that the data is equivalent.
2335    #[test]
2336    fn inmem_write_to_file_with_meta() {
2337        let sop_uid = "1.4.645.212121";
2338        let mut obj = InMemDicomObject::new_empty();
2339
2340        obj.put(DataElement::new(
2341            Tag(0x0010, 0x0010),
2342            VR::PN,
2343            dicom_value!(Strs, ["Doe^John"]),
2344        ));
2345        obj.put(DataElement::new(
2346            Tag(0x0008, 0x0060),
2347            VR::CS,
2348            dicom_value!(Strs, ["CR"]),
2349        ));
2350        obj.put(DataElement::new(
2351            Tag(0x0008, 0x0018),
2352            VR::UI,
2353            dicom_value!(Strs, [sop_uid]),
2354        ));
2355
2356        let file_object = obj
2357            .with_meta(
2358                FileMetaTableBuilder::default()
2359                    // Explicit VR Little Endian
2360                    .transfer_syntax("1.2.840.10008.1.2.1")
2361                    // Computed Radiography image storage
2362                    .media_storage_sop_class_uid("1.2.840.10008.5.1.4.1.1.1")
2363                    .media_storage_sop_instance_uid(sop_uid),
2364            )
2365            .unwrap();
2366
2367        // create temporary file path and write object to that file
2368        let dir = tempfile::tempdir().unwrap();
2369        let mut file_path = dir.into_path();
2370        file_path.push(format!("{}.dcm", sop_uid));
2371
2372        file_object.write_to_file(&file_path).unwrap();
2373
2374        // read the file back to validate the outcome
2375        let saved_object = open_file(file_path).unwrap();
2376        assert_eq!(file_object, saved_object);
2377    }
2378
2379    /// Creating a file DICOM object from an in-mem DICOM object
2380    /// infers the SOP instance UID.
2381    #[test]
2382    fn inmem_with_meta_infers_sop_instance_uid() {
2383        let sop_uid = "1.4.645.252521";
2384        let mut obj = InMemDicomObject::new_empty();
2385
2386        obj.put(DataElement::new(
2387            tags::SOP_INSTANCE_UID,
2388            VR::UI,
2389            PrimitiveValue::from(sop_uid),
2390        ));
2391
2392        let file_object = obj
2393            .with_meta(
2394                // Media Storage SOP Instance UID deliberately not set
2395                FileMetaTableBuilder::default()
2396                    // Explicit VR Little Endian
2397                    .transfer_syntax("1.2.840.10008.1.2.1")
2398                    // Computed Radiography image storage
2399                    .media_storage_sop_class_uid("1.2.840.10008.5.1.4.1.1.1"),
2400            )
2401            .unwrap();
2402
2403        let meta = file_object.meta();
2404
2405        assert_eq!(
2406            meta.media_storage_sop_instance_uid
2407                .trim_end_matches(|c| c == '\0'),
2408            sop_uid.trim_end_matches(|c| c == '\0'),
2409        );
2410    }
2411
2412    /// Write a file from scratch, with exact file meta table.
2413    #[test]
2414    fn inmem_write_to_file_with_exact_meta() {
2415        let sop_uid = "1.4.645.212121";
2416        let mut obj = InMemDicomObject::new_empty();
2417
2418        obj.put(DataElement::new(
2419            Tag(0x0010, 0x0010),
2420            VR::PN,
2421            dicom_value!(Strs, ["Doe^John"]),
2422        ));
2423        obj.put(DataElement::new(
2424            Tag(0x0008, 0x0060),
2425            VR::CS,
2426            dicom_value!(Strs, ["CR"]),
2427        ));
2428        obj.put(DataElement::new(
2429            Tag(0x0008, 0x0018),
2430            VR::UI,
2431            dicom_value!(Strs, [sop_uid]),
2432        ));
2433
2434        let file_object = obj.with_exact_meta(
2435            FileMetaTableBuilder::default()
2436                // Explicit VR Little Endian
2437                .transfer_syntax("1.2.840.10008.1.2.1")
2438                // Computed Radiography image storage
2439                .media_storage_sop_class_uid("1.2.840.10008.5.1.4.1.1.1")
2440                .media_storage_sop_instance_uid(sop_uid)
2441                .build()
2442                .unwrap(),
2443        );
2444
2445        // create temporary file path and write object to that file
2446        let dir = tempfile::tempdir().unwrap();
2447        let mut file_path = dir.into_path();
2448        file_path.push(format!("{}.dcm", sop_uid));
2449
2450        file_object.write_to_file(&file_path).unwrap();
2451
2452        // read the file back to validate the outcome
2453        let saved_object = open_file(file_path).unwrap();
2454        assert_eq!(file_object, saved_object);
2455    }
2456
2457    #[test]
2458    fn inmem_object_get() {
2459        let another_patient_name = DataElement::new(
2460            Tag(0x0010, 0x0010),
2461            VR::PN,
2462            PrimitiveValue::Str("Doe^John".to_string()),
2463        );
2464        let mut obj = InMemDicomObject::new_empty();
2465        obj.put(another_patient_name.clone());
2466        let elem1 = (&obj).element(Tag(0x0010, 0x0010)).unwrap();
2467        assert_eq!(elem1, &another_patient_name);
2468    }
2469
2470    #[test]
2471    fn infer_media_sop_from_dataset_sop_elements() {
2472        let sop_instance_uid = "1.4.645.313131";
2473        let sop_class_uid = "1.2.840.10008.5.1.4.1.1.2";
2474        let mut obj = InMemDicomObject::new_empty();
2475
2476        obj.put(DataElement::new(
2477            Tag(0x0008, 0x0018),
2478            VR::UI,
2479            dicom_value!(Strs, [sop_instance_uid]),
2480        ));
2481        obj.put(DataElement::new(
2482            Tag(0x0008, 0x0016),
2483            VR::UI,
2484            dicom_value!(Strs, [sop_class_uid]),
2485        ));
2486
2487        let file_object = obj.with_exact_meta(
2488            FileMetaTableBuilder::default()
2489                .transfer_syntax("1.2.840.10008.1.2.1")
2490                // Media Storage SOP Class and Instance UIDs are missing and set to an empty string
2491                .media_storage_sop_class_uid("")
2492                .media_storage_sop_instance_uid("")
2493                .build()
2494                .unwrap(),
2495        );
2496
2497        // create temporary file path and write object to that file
2498        let dir = tempfile::tempdir().unwrap();
2499        let mut file_path = dir.into_path();
2500        file_path.push(format!("{}.dcm", sop_instance_uid));
2501
2502        file_object.write_to_file(&file_path).unwrap();
2503
2504        // read the file back to validate the outcome
2505        let saved_object = open_file(file_path).unwrap();
2506
2507        // verify that the empty string media storage sop instance and class UIDs have been inferred from the sop instance and class UID
2508        assert_eq!(
2509            saved_object.meta().media_storage_sop_instance_uid(),
2510            sop_instance_uid
2511        );
2512        assert_eq!(
2513            saved_object.meta().media_storage_sop_class_uid(),
2514            sop_class_uid
2515        );
2516    }
2517
2518    #[test]
2519    fn inmem_object_get_opt() {
2520        let another_patient_name = DataElement::new(
2521            Tag(0x0010, 0x0010),
2522            VR::PN,
2523            PrimitiveValue::Str("Doe^John".to_string()),
2524        );
2525        let mut obj = InMemDicomObject::new_empty();
2526        obj.put(another_patient_name.clone());
2527        let elem1 = obj.element_opt(Tag(0x0010, 0x0010)).unwrap();
2528        assert_eq!(elem1, Some(&another_patient_name));
2529
2530        // try a missing element, should return None
2531        assert_eq!(obj.element_opt(Tag(0x0010, 0x0020)).unwrap(), None);
2532    }
2533
2534    #[test]
2535    fn inmem_object_get_by_name() {
2536        let another_patient_name = DataElement::new(
2537            Tag(0x0010, 0x0010),
2538            VR::PN,
2539            PrimitiveValue::Str("Doe^John".to_string()),
2540        );
2541        let mut obj = InMemDicomObject::new_empty();
2542        obj.put(another_patient_name.clone());
2543        let elem1 = (&obj).element_by_name("PatientName").unwrap();
2544        assert_eq!(elem1, &another_patient_name);
2545    }
2546
2547    #[test]
2548    fn inmem_object_get_by_name_opt() {
2549        let another_patient_name = DataElement::new(
2550            Tag(0x0010, 0x0010),
2551            VR::PN,
2552            PrimitiveValue::Str("Doe^John".to_string()),
2553        );
2554        let mut obj = InMemDicomObject::new_empty();
2555        obj.put(another_patient_name.clone());
2556        let elem1 = obj.element_by_name_opt("PatientName").unwrap();
2557        assert_eq!(elem1, Some(&another_patient_name));
2558
2559        // try a missing element, should return None
2560        assert_eq!(obj.element_by_name_opt("PatientID").unwrap(), None);
2561    }
2562
2563    #[test]
2564    fn inmem_object_take_element() {
2565        let another_patient_name = DataElement::new(
2566            Tag(0x0010, 0x0010),
2567            VR::PN,
2568            PrimitiveValue::Str("Doe^John".to_string()),
2569        );
2570        let mut obj = InMemDicomObject::new_empty();
2571        obj.put(another_patient_name.clone());
2572        let elem1 = obj.take_element(Tag(0x0010, 0x0010)).unwrap();
2573        assert_eq!(elem1, another_patient_name);
2574        assert!(matches!(
2575            obj.take_element(Tag(0x0010, 0x0010)),
2576            Err(AccessError::NoSuchDataElementTag {
2577                tag: Tag(0x0010, 0x0010),
2578                ..
2579            })
2580        ));
2581    }
2582
2583    #[test]
2584    fn inmem_object_take_element_by_name() {
2585        let another_patient_name = DataElement::new(
2586            Tag(0x0010, 0x0010),
2587            VR::PN,
2588            PrimitiveValue::Str("Doe^John".to_string()),
2589        );
2590        let mut obj = InMemDicomObject::new_empty();
2591        obj.put(another_patient_name.clone());
2592        let elem1 = obj.take_element_by_name("PatientName").unwrap();
2593        assert_eq!(elem1, another_patient_name);
2594        assert!(matches!(
2595            obj.take_element_by_name("PatientName"),
2596            Err(AccessByNameError::NoSuchDataElementAlias {
2597                tag: Tag(0x0010, 0x0010),
2598                alias,
2599                ..
2600            }) if alias == "PatientName"));
2601    }
2602
2603    #[test]
2604    fn inmem_object_remove_element() {
2605        let another_patient_name = DataElement::new(
2606            Tag(0x0010, 0x0010),
2607            VR::PN,
2608            PrimitiveValue::Str("Doe^John".to_string()),
2609        );
2610        let mut obj = InMemDicomObject::new_empty();
2611        obj.put(another_patient_name.clone());
2612        assert!(obj.remove_element(Tag(0x0010, 0x0010)));
2613        assert_eq!(obj.remove_element(Tag(0x0010, 0x0010)), false);
2614    }
2615
2616    #[test]
2617    fn inmem_object_remove_element_by_name() {
2618        let another_patient_name = DataElement::new(
2619            Tag(0x0010, 0x0010),
2620            VR::PN,
2621            PrimitiveValue::Str("Doe^John".to_string()),
2622        );
2623        let mut obj = InMemDicomObject::new_empty();
2624        obj.put(another_patient_name.clone());
2625        assert!(obj.remove_element_by_name("PatientName").unwrap());
2626        assert_eq!(obj.remove_element_by_name("PatientName").unwrap(), false);
2627    }
2628
2629    /// Elements are traversed in tag order.
2630    #[test]
2631    fn inmem_traverse_elements() {
2632        let sop_uid = "1.4.645.212121";
2633        let mut obj = InMemDicomObject::new_empty();
2634
2635        obj.put(DataElement::new(
2636            Tag(0x0010, 0x0010),
2637            VR::PN,
2638            dicom_value!(Strs, ["Doe^John"]),
2639        ));
2640        obj.put(DataElement::new(
2641            Tag(0x0008, 0x0060),
2642            VR::CS,
2643            dicom_value!(Strs, ["CR"]),
2644        ));
2645        obj.put(DataElement::new(
2646            Tag(0x0008, 0x0018),
2647            VR::UI,
2648            dicom_value!(Strs, [sop_uid]),
2649        ));
2650
2651        {
2652            let mut iter = obj.iter();
2653            assert_eq!(
2654                *iter.next().unwrap().header(),
2655                DataElementHeader::new(Tag(0x0008, 0x0018), VR::UI, Length(sop_uid.len() as u32)),
2656            );
2657            assert_eq!(
2658                *iter.next().unwrap().header(),
2659                DataElementHeader::new(Tag(0x0008, 0x0060), VR::CS, Length(2)),
2660            );
2661            assert_eq!(
2662                *iter.next().unwrap().header(),
2663                DataElementHeader::new(Tag(0x0010, 0x0010), VR::PN, Length(8)),
2664            );
2665        }
2666
2667        // .tags()
2668        let tags: Vec<_> = obj.tags().collect();
2669        assert_eq!(
2670            tags,
2671            vec![
2672                Tag(0x0008, 0x0018),
2673                Tag(0x0008, 0x0060),
2674                Tag(0x0010, 0x0010),
2675            ]
2676        );
2677
2678        // .into_iter()
2679        let mut iter = obj.into_iter();
2680        assert_eq!(
2681            iter.next(),
2682            Some(DataElement::new(
2683                Tag(0x0008, 0x0018),
2684                VR::UI,
2685                dicom_value!(Strs, [sop_uid]),
2686            )),
2687        );
2688        assert_eq!(
2689            iter.next(),
2690            Some(DataElement::new(
2691                Tag(0x0008, 0x0060),
2692                VR::CS,
2693                dicom_value!(Strs, ["CR"]),
2694            )),
2695        );
2696        assert_eq!(
2697            iter.next(),
2698            Some(DataElement::new(
2699                Tag(0x0010, 0x0010),
2700                VR::PN,
2701                PrimitiveValue::from("Doe^John"),
2702            )),
2703        );
2704    }
2705
2706    #[test]
2707    fn inmem_empty_object_into_tokens() {
2708        let obj = InMemDicomObject::new_empty();
2709        let tokens = obj.into_tokens();
2710        assert_eq!(tokens.count(), 0);
2711    }
2712
2713    #[test]
2714    fn inmem_shallow_object_from_tokens() {
2715        let tokens = vec![
2716            DataToken::ElementHeader(DataElementHeader {
2717                tag: Tag(0x0008, 0x0060),
2718                vr: VR::CS,
2719                len: Length(2),
2720            }),
2721            DataToken::PrimitiveValue(PrimitiveValue::Str("MG".to_owned())),
2722            DataToken::ElementHeader(DataElementHeader {
2723                tag: Tag(0x0010, 0x0010),
2724                vr: VR::PN,
2725                len: Length(8),
2726            }),
2727            DataToken::PrimitiveValue(PrimitiveValue::Str("Doe^John".to_owned())),
2728        ];
2729
2730        let gt_obj = InMemDicomObject::from_element_iter(vec![
2731            DataElement::new(
2732                Tag(0x0010, 0x0010),
2733                VR::PN,
2734                PrimitiveValue::Str("Doe^John".to_string()),
2735            ),
2736            DataElement::new(
2737                Tag(0x0008, 0x0060),
2738                VR::CS,
2739                PrimitiveValue::Str("MG".to_string()),
2740            ),
2741        ]);
2742
2743        let obj = InMemDicomObject::build_object(
2744            &mut tokens.into_iter().map(Result::Ok),
2745            StandardDataDictionary,
2746            false,
2747            Length::UNDEFINED,
2748            None,
2749        )
2750        .unwrap();
2751
2752        assert_obj_eq(&obj, &gt_obj);
2753    }
2754
2755    #[test]
2756    fn inmem_shallow_object_into_tokens() {
2757        let patient_name = DataElement::new(
2758            Tag(0x0010, 0x0010),
2759            VR::PN,
2760            PrimitiveValue::Str("Doe^John".to_string()),
2761        );
2762        let modality = DataElement::new(
2763            Tag(0x0008, 0x0060),
2764            VR::CS,
2765            PrimitiveValue::Str("MG".to_string()),
2766        );
2767        let mut obj = InMemDicomObject::new_empty();
2768        obj.put(patient_name);
2769        obj.put(modality);
2770
2771        let tokens: Vec<_> = obj.into_tokens().collect();
2772
2773        assert_eq!(
2774            tokens,
2775            vec![
2776                DataToken::ElementHeader(DataElementHeader {
2777                    tag: Tag(0x0008, 0x0060),
2778                    vr: VR::CS,
2779                    len: Length(2),
2780                }),
2781                DataToken::PrimitiveValue(PrimitiveValue::Str("MG".to_owned())),
2782                DataToken::ElementHeader(DataElementHeader {
2783                    tag: Tag(0x0010, 0x0010),
2784                    vr: VR::PN,
2785                    len: Length(8),
2786                }),
2787                DataToken::PrimitiveValue(PrimitiveValue::Str("Doe^John".to_owned())),
2788            ]
2789        );
2790    }
2791
2792    #[test]
2793    fn inmem_deep_object_from_tokens() {
2794        use smallvec::smallvec;
2795
2796        let obj_1 = InMemDicomObject::from_element_iter(vec![
2797            DataElement::new(Tag(0x0018, 0x6012), VR::US, Value::Primitive(1_u16.into())),
2798            DataElement::new(Tag(0x0018, 0x6014), VR::US, Value::Primitive(2_u16.into())),
2799        ]);
2800
2801        let obj_2 = InMemDicomObject::from_element_iter(vec![DataElement::new(
2802            Tag(0x0018, 0x6012),
2803            VR::US,
2804            Value::Primitive(4_u16.into()),
2805        )]);
2806
2807        let gt_obj = InMemDicomObject::from_element_iter(vec![
2808            DataElement::new(
2809                Tag(0x0018, 0x6011),
2810                VR::SQ,
2811                Value::from(DataSetSequence::new(
2812                    smallvec![obj_1, obj_2],
2813                    Length::UNDEFINED,
2814                )),
2815            ),
2816            DataElement::new(Tag(0x0020, 0x4000), VR::LT, Value::Primitive("TEST".into())),
2817        ]);
2818
2819        let tokens: Vec<_> = vec![
2820            DataToken::SequenceStart {
2821                tag: Tag(0x0018, 0x6011),
2822                len: Length::UNDEFINED,
2823            },
2824            DataToken::ItemStart {
2825                len: Length::UNDEFINED,
2826            },
2827            DataToken::ElementHeader(DataElementHeader {
2828                tag: Tag(0x0018, 0x6012),
2829                vr: VR::US,
2830                len: Length(2),
2831            }),
2832            DataToken::PrimitiveValue(PrimitiveValue::U16([1].as_ref().into())),
2833            DataToken::ElementHeader(DataElementHeader {
2834                tag: Tag(0x0018, 0x6014),
2835                vr: VR::US,
2836                len: Length(2),
2837            }),
2838            DataToken::PrimitiveValue(PrimitiveValue::U16([2].as_ref().into())),
2839            DataToken::ItemEnd,
2840            DataToken::ItemStart {
2841                len: Length::UNDEFINED,
2842            },
2843            DataToken::ElementHeader(DataElementHeader {
2844                tag: Tag(0x0018, 0x6012),
2845                vr: VR::US,
2846                len: Length(2),
2847            }),
2848            DataToken::PrimitiveValue(PrimitiveValue::U16([4].as_ref().into())),
2849            DataToken::ItemEnd,
2850            DataToken::SequenceEnd,
2851            DataToken::ElementHeader(DataElementHeader {
2852                tag: Tag(0x0020, 0x4000),
2853                vr: VR::LT,
2854                len: Length(4),
2855            }),
2856            DataToken::PrimitiveValue(PrimitiveValue::Str("TEST".into())),
2857        ];
2858
2859        let obj = InMemDicomObject::build_object(
2860            &mut tokens.into_iter().map(Result::Ok),
2861            StandardDataDictionary,
2862            false,
2863            Length::UNDEFINED,
2864            None,
2865        )
2866        .unwrap();
2867
2868        assert_obj_eq(&obj, &gt_obj);
2869    }
2870
2871    #[test]
2872    fn inmem_deep_object_into_tokens() {
2873        use smallvec::smallvec;
2874
2875        let obj_1 = InMemDicomObject::from_element_iter(vec![
2876            DataElement::new(Tag(0x0018, 0x6012), VR::US, Value::Primitive(1_u16.into())),
2877            DataElement::new(Tag(0x0018, 0x6014), VR::US, Value::Primitive(2_u16.into())),
2878        ]);
2879
2880        let obj_2 = InMemDicomObject::from_element_iter(vec![DataElement::new(
2881            Tag(0x0018, 0x6012),
2882            VR::US,
2883            Value::Primitive(4_u16.into()),
2884        )]);
2885
2886        let main_obj = InMemDicomObject::from_element_iter(vec![
2887            DataElement::new(
2888                Tag(0x0018, 0x6011),
2889                VR::SQ,
2890                Value::from(DataSetSequence::new(
2891                    smallvec![obj_1, obj_2],
2892                    Length::UNDEFINED,
2893                )),
2894            ),
2895            DataElement::new(Tag(0x0020, 0x4000), VR::LT, Value::Primitive("TEST".into())),
2896        ]);
2897
2898        let tokens: Vec<_> = main_obj.into_tokens().collect();
2899
2900        assert_eq!(
2901            tokens,
2902            vec![
2903                DataToken::SequenceStart {
2904                    tag: Tag(0x0018, 0x6011),
2905                    len: Length::UNDEFINED,
2906                },
2907                DataToken::ItemStart {
2908                    len: Length::UNDEFINED,
2909                },
2910                DataToken::ElementHeader(DataElementHeader {
2911                    tag: Tag(0x0018, 0x6012),
2912                    vr: VR::US,
2913                    len: Length(2),
2914                }),
2915                DataToken::PrimitiveValue(PrimitiveValue::U16([1].as_ref().into())),
2916                DataToken::ElementHeader(DataElementHeader {
2917                    tag: Tag(0x0018, 0x6014),
2918                    vr: VR::US,
2919                    len: Length(2),
2920                }),
2921                DataToken::PrimitiveValue(PrimitiveValue::U16([2].as_ref().into())),
2922                DataToken::ItemEnd,
2923                DataToken::ItemStart {
2924                    len: Length::UNDEFINED,
2925                },
2926                DataToken::ElementHeader(DataElementHeader {
2927                    tag: Tag(0x0018, 0x6012),
2928                    vr: VR::US,
2929                    len: Length(2),
2930                }),
2931                DataToken::PrimitiveValue(PrimitiveValue::U16([4].as_ref().into())),
2932                DataToken::ItemEnd,
2933                DataToken::SequenceEnd,
2934                DataToken::ElementHeader(DataElementHeader {
2935                    tag: Tag(0x0020, 0x4000),
2936                    vr: VR::LT,
2937                    len: Length(4),
2938                }),
2939                DataToken::PrimitiveValue(PrimitiveValue::Str("TEST".into())),
2940            ]
2941        );
2942    }
2943
2944    #[test]
2945    fn inmem_encapsulated_pixel_data_from_tokens() {
2946        use smallvec::smallvec;
2947
2948        let gt_obj = InMemDicomObject::from_element_iter(vec![DataElement::new(
2949            Tag(0x7fe0, 0x0010),
2950            VR::OB,
2951            Value::from(PixelFragmentSequence::new_fragments(smallvec![vec![
2952                0x33;
2953                32
2954            ]])),
2955        )]);
2956
2957        let tokens: Vec<_> = vec![
2958            DataToken::PixelSequenceStart,
2959            DataToken::ItemStart { len: Length(0) },
2960            DataToken::ItemEnd,
2961            DataToken::ItemStart { len: Length(32) },
2962            DataToken::ItemValue(vec![0x33; 32]),
2963            DataToken::ItemEnd,
2964            DataToken::SequenceEnd,
2965        ];
2966
2967        let obj = InMemDicomObject::build_object(
2968            &mut tokens.into_iter().map(Result::Ok),
2969            StandardDataDictionary,
2970            false,
2971            Length::UNDEFINED,
2972            None,
2973        )
2974        .unwrap();
2975
2976        assert_obj_eq(&obj, &gt_obj);
2977    }
2978
2979    #[test]
2980    fn inmem_encapsulated_pixel_data_into_tokens() {
2981        use smallvec::smallvec;
2982
2983        let main_obj = InMemDicomObject::from_element_iter(vec![DataElement::new(
2984            Tag(0x7fe0, 0x0010),
2985            VR::OB,
2986            Value::from(PixelFragmentSequence::new_fragments(smallvec![vec![
2987                0x33;
2988                32
2989            ]])),
2990        )]);
2991
2992        let tokens: Vec<_> = main_obj.into_tokens().collect();
2993
2994        assert_eq!(
2995            tokens,
2996            vec![
2997                DataToken::PixelSequenceStart,
2998                DataToken::ItemStart { len: Length(0) },
2999                DataToken::ItemEnd,
3000                DataToken::ItemStart { len: Length(32) },
3001                DataToken::ItemValue(vec![0x33; 32]),
3002                DataToken::ItemEnd,
3003                DataToken::SequenceEnd,
3004            ]
3005        );
3006    }
3007
3008    /// Test attribute operations on in-memory DICOM objects.
3009    #[test]
3010    fn inmem_ops() {
3011        // create a base DICOM object
3012        let base_obj = InMemDicomObject::from_element_iter([
3013            DataElement::new(
3014                tags::SERIES_INSTANCE_UID,
3015                VR::UI,
3016                PrimitiveValue::from("2.25.137041794342168732369025909031346220736.1"),
3017            ),
3018            DataElement::new(
3019                tags::SERIES_INSTANCE_UID,
3020                VR::UI,
3021                PrimitiveValue::from("2.25.137041794342168732369025909031346220736.1"),
3022            ),
3023            DataElement::new(
3024                tags::SOP_INSTANCE_UID,
3025                VR::UI,
3026                PrimitiveValue::from("2.25.137041794342168732369025909031346220736.1.1"),
3027            ),
3028            DataElement::new(
3029                tags::STUDY_DESCRIPTION,
3030                VR::LO,
3031                PrimitiveValue::from("Test study"),
3032            ),
3033            DataElement::new(
3034                tags::INSTITUTION_NAME,
3035                VR::LO,
3036                PrimitiveValue::from("Test Hospital"),
3037            ),
3038            DataElement::new(tags::ROWS, VR::US, PrimitiveValue::from(768_u16)),
3039            DataElement::new(tags::COLUMNS, VR::US, PrimitiveValue::from(1024_u16)),
3040            DataElement::new(
3041                tags::LOSSY_IMAGE_COMPRESSION,
3042                VR::CS,
3043                PrimitiveValue::from("01"),
3044            ),
3045            DataElement::new(
3046                tags::LOSSY_IMAGE_COMPRESSION_RATIO,
3047                VR::DS,
3048                PrimitiveValue::from("5"),
3049            ),
3050            DataElement::new(
3051                tags::LOSSY_IMAGE_COMPRESSION_METHOD,
3052                VR::DS,
3053                PrimitiveValue::from("ISO_10918_1"),
3054            ),
3055        ]);
3056
3057        {
3058            // remove
3059            let mut obj = base_obj.clone();
3060            let op = AttributeOp {
3061                selector: AttributeSelector::from(tags::STUDY_DESCRIPTION),
3062                action: AttributeAction::Remove,
3063            };
3064
3065            obj.apply(op).unwrap();
3066
3067            assert_eq!(obj.get(tags::STUDY_DESCRIPTION), None);
3068        }
3069        {
3070            let mut obj = base_obj.clone();
3071
3072            // set if missing does nothing
3073            // on an existing string
3074            let op = AttributeOp {
3075                selector: tags::INSTITUTION_NAME.into(),
3076                action: AttributeAction::SetIfMissing("Nope Hospital".into()),
3077            };
3078
3079            obj.apply(op).unwrap();
3080
3081            assert_eq!(
3082                obj.get(tags::INSTITUTION_NAME),
3083                Some(&DataElement::new(
3084                    tags::INSTITUTION_NAME,
3085                    VR::LO,
3086                    PrimitiveValue::from("Test Hospital"),
3087                ))
3088            );
3089
3090            // replace string
3091            let op = AttributeOp::new(
3092                tags::INSTITUTION_NAME,
3093                AttributeAction::ReplaceStr("REMOVED".into()),
3094            );
3095
3096            obj.apply(op).unwrap();
3097
3098            assert_eq!(
3099                obj.get(tags::INSTITUTION_NAME),
3100                Some(&DataElement::new(
3101                    tags::INSTITUTION_NAME,
3102                    VR::LO,
3103                    PrimitiveValue::from("REMOVED"),
3104                ))
3105            );
3106
3107            // replacing a non-existing attribute
3108            // does nothing
3109            let op = AttributeOp::new(
3110                tags::REQUESTING_PHYSICIAN,
3111                AttributeAction::ReplaceStr("Doctor^Anonymous".into()),
3112            );
3113
3114            obj.apply(op).unwrap();
3115
3116            assert_eq!(obj.get(tags::REQUESTING_PHYSICIAN), None);
3117
3118            // but DetIfMissing works
3119            let op = AttributeOp::new(
3120                tags::REQUESTING_PHYSICIAN,
3121                AttributeAction::SetStrIfMissing("Doctor^Anonymous".into()),
3122            );
3123
3124            obj.apply(op).unwrap();
3125
3126            assert_eq!(
3127                obj.get(tags::REQUESTING_PHYSICIAN),
3128                Some(&DataElement::new(
3129                    tags::REQUESTING_PHYSICIAN,
3130                    VR::PN,
3131                    PrimitiveValue::from("Doctor^Anonymous"),
3132                ))
3133            );
3134        }
3135        {
3136            // reset string
3137            let mut obj = base_obj.clone();
3138            let op = AttributeOp::new(
3139                tags::REQUESTING_PHYSICIAN,
3140                AttributeAction::SetStr("Doctor^Anonymous".into()),
3141            );
3142
3143            obj.apply(op).unwrap();
3144
3145            assert_eq!(
3146                obj.get(tags::REQUESTING_PHYSICIAN),
3147                Some(&DataElement::new(
3148                    tags::REQUESTING_PHYSICIAN,
3149                    VR::PN,
3150                    PrimitiveValue::from("Doctor^Anonymous"),
3151                ))
3152            );
3153        }
3154
3155        {
3156            // extend with number
3157            let mut obj = base_obj.clone();
3158            let op = AttributeOp::new(
3159                tags::LOSSY_IMAGE_COMPRESSION_RATIO,
3160                AttributeAction::PushF64(1.25),
3161            );
3162
3163            obj.apply(op).unwrap();
3164
3165            assert_eq!(
3166                obj.get(tags::LOSSY_IMAGE_COMPRESSION_RATIO),
3167                Some(&DataElement::new(
3168                    tags::LOSSY_IMAGE_COMPRESSION_RATIO,
3169                    VR::DS,
3170                    dicom_value!(Strs, ["5", "1.25"]),
3171                ))
3172            );
3173        }
3174    }
3175
3176    /// Test attribute operations on nested data sets.
3177    #[test]
3178    fn nested_inmem_ops() {
3179        let obj_1 = InMemDicomObject::from_element_iter([
3180            DataElement::new(Tag(0x0018, 0x6012), VR::US, PrimitiveValue::from(1_u16)),
3181            DataElement::new(Tag(0x0018, 0x6014), VR::US, PrimitiveValue::from(2_u16)),
3182        ]);
3183
3184        let obj_2 = InMemDicomObject::from_element_iter([DataElement::new(
3185            Tag(0x0018, 0x6012),
3186            VR::US,
3187            PrimitiveValue::from(4_u16),
3188        )]);
3189
3190        let mut main_obj = InMemDicomObject::from_element_iter(vec![
3191            DataElement::new(
3192                tags::SEQUENCE_OF_ULTRASOUND_REGIONS,
3193                VR::SQ,
3194                DataSetSequence::from(vec![obj_1, obj_2]),
3195            ),
3196            DataElement::new(Tag(0x0020, 0x4000), VR::LT, Value::Primitive("TEST".into())),
3197        ]);
3198
3199        let selector: AttributeSelector =
3200            (tags::SEQUENCE_OF_ULTRASOUND_REGIONS, 0, Tag(0x0018, 0x6014)).into();
3201
3202        main_obj
3203            .apply(AttributeOp::new(selector, AttributeAction::Set(3.into())))
3204            .unwrap();
3205
3206        assert_eq!(
3207            main_obj
3208                .get(tags::SEQUENCE_OF_ULTRASOUND_REGIONS)
3209                .unwrap()
3210                .items()
3211                .unwrap()[0]
3212                .get(Tag(0x0018, 0x6014))
3213                .unwrap()
3214                .value(),
3215            &PrimitiveValue::from(3).into(),
3216        );
3217
3218        let selector: AttributeSelector =
3219            (tags::SEQUENCE_OF_ULTRASOUND_REGIONS, 1, Tag(0x0018, 0x6012)).into();
3220
3221        main_obj
3222            .apply(AttributeOp::new(selector, AttributeAction::Remove))
3223            .unwrap();
3224
3225        // item should be empty
3226        assert_eq!(
3227            main_obj
3228                .get(tags::SEQUENCE_OF_ULTRASOUND_REGIONS)
3229                .unwrap()
3230                .items()
3231                .unwrap()[1]
3232                .tags()
3233                .collect::<Vec<_>>(),
3234            Vec::<Tag>::new(),
3235        );
3236
3237        // trying to access the removed element returns an error
3238        assert!(matches!(
3239            main_obj.value_at((tags::SEQUENCE_OF_ULTRASOUND_REGIONS, 1, Tag(0x0018, 0x6012),)),
3240            Err(AtAccessError::MissingLeafElement { .. })
3241        ))
3242    }
3243
3244    /// Test that constructive operations create items if necessary.
3245    #[test]
3246    fn constructive_op() {
3247        let mut obj = InMemDicomObject::from_element_iter([DataElement::new(
3248            tags::SEQUENCE_OF_ULTRASOUND_REGIONS,
3249            VR::SQ,
3250            DataSetSequence::empty(),
3251        )]);
3252
3253        let op = AttributeOp::new(
3254            (
3255                tags::SEQUENCE_OF_ULTRASOUND_REGIONS,
3256                0,
3257                tags::REGION_SPATIAL_FORMAT,
3258            ),
3259            AttributeAction::Set(5_u16.into()),
3260        );
3261
3262        obj.apply(op).unwrap();
3263
3264        // should have an item
3265        assert_eq!(
3266            obj.get(tags::SEQUENCE_OF_ULTRASOUND_REGIONS)
3267                .unwrap()
3268                .items()
3269                .unwrap()
3270                .len(),
3271            1,
3272        );
3273
3274        // item should have 1 element
3275        assert_eq!(
3276            &obj.get(tags::SEQUENCE_OF_ULTRASOUND_REGIONS)
3277                .unwrap()
3278                .items()
3279                .unwrap()[0],
3280            &InMemDicomObject::from_element_iter([DataElement::new(
3281                tags::REGION_SPATIAL_FORMAT,
3282                VR::US,
3283                PrimitiveValue::from(5_u16)
3284            )]),
3285        );
3286
3287        // new value can be accessed using value_at
3288        assert_eq!(
3289            obj.value_at((
3290                tags::SEQUENCE_OF_ULTRASOUND_REGIONS,
3291                0,
3292                tags::REGION_SPATIAL_FORMAT
3293            ))
3294            .unwrap(),
3295            &Value::from(PrimitiveValue::from(5_u16)),
3296        )
3297    }
3298
3299    /// Test that operations on in-memory DICOM objects
3300    /// can create sequences from scratch.
3301    #[test]
3302    fn inmem_ops_can_create_seq() {
3303        let mut obj = InMemDicomObject::new_empty();
3304
3305        obj.apply(AttributeOp::new(
3306            tags::SEQUENCE_OF_ULTRASOUND_REGIONS,
3307            AttributeAction::SetIfMissing(PrimitiveValue::Empty),
3308        ))
3309        .unwrap();
3310
3311        {
3312            // should create an empty sequence
3313            let sequence_ultrasound = obj
3314                .get(tags::SEQUENCE_OF_ULTRASOUND_REGIONS)
3315                .expect("should have sequence element");
3316
3317            assert_eq!(sequence_ultrasound.vr(), VR::SQ);
3318
3319            assert_eq!(sequence_ultrasound.items().as_deref(), Some(&[][..]),);
3320        }
3321
3322        obj.apply(AttributeOp::new(
3323            (
3324                tags::SEQUENCE_OF_ULTRASOUND_REGIONS,
3325                tags::REGION_SPATIAL_FORMAT,
3326            ),
3327            AttributeAction::Set(1_u16.into()),
3328        ))
3329        .unwrap();
3330
3331        {
3332            // sequence should now have an item
3333            assert_eq!(
3334                obj.get(tags::SEQUENCE_OF_ULTRASOUND_REGIONS)
3335                    .unwrap()
3336                    .items()
3337                    .map(|items| items.len()),
3338                Some(1),
3339            );
3340        }
3341    }
3342
3343    /// Test that operations on in-memory DICOM objects
3344    /// can truncate sequences.
3345    #[test]
3346    fn inmem_ops_can_truncate_seq() {
3347        let mut obj = InMemDicomObject::from_element_iter([
3348            DataElement::new(
3349                tags::SEQUENCE_OF_ULTRASOUND_REGIONS,
3350                VR::SQ,
3351                DataSetSequence::from(vec![InMemDicomObject::new_empty()]),
3352            ),
3353            DataElement::new_with_len(
3354                tags::PIXEL_DATA,
3355                VR::OB,
3356                Length::UNDEFINED,
3357                PixelFragmentSequence::new(vec![], vec![vec![0xcc; 8192], vec![0x55; 1024]]),
3358            ),
3359        ]);
3360
3361        // removes the single item in the sequences
3362        obj.apply(AttributeOp::new(
3363            tags::SEQUENCE_OF_ULTRASOUND_REGIONS,
3364            AttributeAction::Truncate(0),
3365        ))
3366        .unwrap();
3367
3368        {
3369            let sequence_ultrasound = obj
3370                .get(tags::SEQUENCE_OF_ULTRASOUND_REGIONS)
3371                .expect("should have sequence element");
3372            assert_eq!(sequence_ultrasound.items().as_deref(), Some(&[][..]),);
3373        }
3374
3375        // remove one of the fragments
3376        obj.apply(AttributeOp::new(
3377            tags::PIXEL_DATA,
3378            AttributeAction::Truncate(1),
3379        ))
3380        .unwrap();
3381
3382        {
3383            // pixel data should now have a single fragment
3384            assert_eq!(
3385                obj.get(tags::PIXEL_DATA)
3386                    .unwrap()
3387                    .fragments()
3388                    .map(|fragments| fragments.len()),
3389                Some(1),
3390            );
3391        }
3392    }
3393
3394    #[test]
3395    fn inmem_obj_reset_defined_length() {
3396        let mut entries: BTreeMap<Tag, InMemElement<StandardDataDictionary>> = BTreeMap::new();
3397
3398        let patient_name =
3399            DataElement::new(tags::PATIENT_NAME, VR::CS, PrimitiveValue::from("Doe^John"));
3400
3401        let study_description = DataElement::new(
3402            tags::STUDY_DESCRIPTION,
3403            VR::LO,
3404            PrimitiveValue::from("Test study"),
3405        );
3406
3407        entries.insert(tags::PATIENT_NAME, patient_name.clone());
3408
3409        // create object and force an arbitrary defined Length value
3410        let obj = InMemDicomObject::<StandardDataDictionary> {
3411            entries,
3412            dict: StandardDataDictionary,
3413            len: Length(1),
3414            charset_changed: false,
3415        };
3416
3417        assert!(obj.length().is_defined());
3418
3419        let mut o = obj.clone();
3420        o.put_element(study_description);
3421        assert!(o.length().is_undefined());
3422
3423        let mut o = obj.clone();
3424        o.remove_element(tags::PATIENT_NAME);
3425        assert!(o.length().is_undefined());
3426
3427        let mut o = obj.clone();
3428        o.remove_element_by_name("PatientName").unwrap();
3429        assert!(o.length().is_undefined());
3430
3431        let mut o = obj.clone();
3432        o.take_element(tags::PATIENT_NAME).unwrap();
3433        assert!(o.length().is_undefined());
3434
3435        let mut o = obj.clone();
3436        o.take_element_by_name("PatientName").unwrap();
3437        assert!(o.length().is_undefined());
3438
3439        // resets Length even when retain does not make any changes
3440        let mut o = obj.clone();
3441        o.retain(|e| e.tag() == tags::PATIENT_NAME);
3442        assert!(o.length().is_undefined());
3443
3444        let mut o = obj.clone();
3445        o.apply(AttributeOp::new(
3446            tags::PATIENT_NAME,
3447            AttributeAction::Remove,
3448        ))
3449        .unwrap();
3450        assert!(o.length().is_undefined());
3451
3452        let mut o = obj.clone();
3453        o.apply(AttributeOp::new(tags::PATIENT_NAME, AttributeAction::Empty))
3454            .unwrap();
3455        assert!(o.length().is_undefined());
3456
3457        let mut o = obj.clone();
3458        o.apply(AttributeOp::new(
3459            tags::PATIENT_NAME,
3460            AttributeAction::SetVr(VR::IS),
3461        ))
3462        .unwrap();
3463        assert!(o.length().is_undefined());
3464
3465        let mut o = obj.clone();
3466        o.apply(AttributeOp::new(
3467            tags::PATIENT_NAME,
3468            AttributeAction::Set(dicom_value!(Str, "Unknown")),
3469        ))
3470        .unwrap();
3471        assert!(o.length().is_undefined());
3472
3473        let mut o = obj.clone();
3474        o.apply(AttributeOp::new(
3475            tags::PATIENT_NAME,
3476            AttributeAction::SetStr("Patient^Anonymous".into()),
3477        ))
3478        .unwrap();
3479        assert!(o.length().is_undefined());
3480
3481        let mut o = obj.clone();
3482        o.apply(AttributeOp::new(
3483            tags::PATIENT_AGE,
3484            AttributeAction::SetIfMissing(dicom_value!(75)),
3485        ))
3486        .unwrap();
3487        assert!(o.length().is_undefined());
3488
3489        let mut o = obj.clone();
3490        o.apply(AttributeOp::new(
3491            tags::PATIENT_ADDRESS,
3492            AttributeAction::SetStrIfMissing("Chicago".into()),
3493        ))
3494        .unwrap();
3495        assert!(o.length().is_undefined());
3496
3497        let mut o = obj.clone();
3498        o.apply(AttributeOp::new(
3499            tags::PATIENT_NAME,
3500            AttributeAction::Replace(dicom_value!(Str, "Unknown")),
3501        ))
3502        .unwrap();
3503        assert!(o.length().is_undefined());
3504
3505        let mut o = obj.clone();
3506        o.apply(AttributeOp::new(
3507            tags::PATIENT_NAME,
3508            AttributeAction::ReplaceStr("Unknown".into()),
3509        ))
3510        .unwrap();
3511        assert!(o.length().is_undefined());
3512
3513        let mut o = obj.clone();
3514        o.apply(AttributeOp::new(
3515            tags::PATIENT_NAME,
3516            AttributeAction::PushStr("^Prof".into()),
3517        ))
3518        .unwrap();
3519        assert!(o.length().is_undefined());
3520
3521        let mut o = obj.clone();
3522        o.apply(AttributeOp::new(
3523            tags::PATIENT_NAME,
3524            AttributeAction::PushI32(-16),
3525        ))
3526        .unwrap();
3527        assert!(o.length().is_undefined());
3528
3529        let mut o = obj.clone();
3530        o.apply(AttributeOp::new(
3531            tags::PATIENT_NAME,
3532            AttributeAction::PushU32(16),
3533        ))
3534        .unwrap();
3535        assert!(o.length().is_undefined());
3536
3537        let mut o = obj.clone();
3538        o.apply(AttributeOp::new(
3539            tags::PATIENT_NAME,
3540            AttributeAction::PushI16(-16),
3541        ))
3542        .unwrap();
3543        assert!(o.length().is_undefined());
3544
3545        let mut o = obj.clone();
3546        o.apply(AttributeOp::new(
3547            tags::PATIENT_NAME,
3548            AttributeAction::PushU16(16),
3549        ))
3550        .unwrap();
3551        assert!(o.length().is_undefined());
3552
3553        let mut o = obj.clone();
3554        o.apply(AttributeOp::new(
3555            tags::PATIENT_NAME,
3556            AttributeAction::PushF32(16.16),
3557        ))
3558        .unwrap();
3559        assert!(o.length().is_undefined());
3560
3561        let mut o = obj.clone();
3562        o.apply(AttributeOp::new(
3563            tags::PATIENT_NAME,
3564            AttributeAction::PushF64(16.1616),
3565        ))
3566        .unwrap();
3567        assert!(o.length().is_undefined());
3568    }
3569
3570    #[test]
3571    fn create_commands() {
3572        // empty
3573        let obj = InMemDicomObject::command_from_element_iter([]);
3574        assert_eq!(
3575            obj.get(tags::COMMAND_GROUP_LENGTH)
3576                .map(|e| e.value().to_int::<u32>().unwrap()),
3577            Some(0)
3578        );
3579
3580        // C-FIND-RQ
3581        let obj = InMemDicomObject::command_from_element_iter([
3582            // affected SOP class UID: 8 + 28 = 36
3583            DataElement::new(
3584                tags::AFFECTED_SOP_CLASS_UID,
3585                VR::UI,
3586                PrimitiveValue::from("1.2.840.10008.5.1.4.1.2.1.1"),
3587            ),
3588            // command field: 36 + 8 + 2 = 46
3589            DataElement::new(
3590                tags::COMMAND_FIELD,
3591                VR::US,
3592                // 0020H: C-FIND-RQ message
3593                dicom_value!(U16, [0x0020]),
3594            ),
3595            // message ID: 46 + 8 + 2 = 56
3596            DataElement::new(tags::MESSAGE_ID, VR::US, dicom_value!(U16, [0])),
3597            //priority: 56 + 8 + 2 = 66
3598            DataElement::new(
3599                tags::PRIORITY,
3600                VR::US,
3601                // medium
3602                dicom_value!(U16, [0x0000]),
3603            ),
3604            // data set type: 66 + 8 + 2 = 76
3605            DataElement::new(
3606                tags::COMMAND_DATA_SET_TYPE,
3607                VR::US,
3608                dicom_value!(U16, [0x0001]),
3609            ),
3610        ]);
3611        assert_eq!(
3612            obj.get(tags::COMMAND_GROUP_LENGTH)
3613                .map(|e| e.value().to_int::<u32>().unwrap()),
3614            Some(76)
3615        );
3616
3617        let storage_sop_class_uid = "1.2.840.10008.5.1.4.1.1.4";
3618        let storage_sop_instance_uid = "2.25.221314879990624101283043547144116927116";
3619
3620        // C-STORE-RQ
3621        let obj = InMemDicomObject::command_from_element_iter([
3622            // group length (should be ignored in calculations and overridden)
3623            DataElement::new(
3624                tags::COMMAND_GROUP_LENGTH,
3625                VR::UL,
3626                PrimitiveValue::from(9999_u32),
3627            ),
3628            // SOP Class UID: 8 + 26 = 34
3629            DataElement::new(
3630                tags::AFFECTED_SOP_CLASS_UID,
3631                VR::UI,
3632                dicom_value!(Str, storage_sop_class_uid),
3633            ),
3634            // command field: 34 + 8 + 2 = 44
3635            DataElement::new(tags::COMMAND_FIELD, VR::US, dicom_value!(U16, [0x0001])),
3636            // message ID: 44 + 8 + 2 = 54
3637            DataElement::new(tags::MESSAGE_ID, VR::US, dicom_value!(U16, [1])),
3638            //priority: 54 + 8 + 2 = 64
3639            DataElement::new(tags::PRIORITY, VR::US, dicom_value!(U16, [0x0000])),
3640            // data set type: 64 + 8 + 2 = 74
3641            DataElement::new(
3642                tags::COMMAND_DATA_SET_TYPE,
3643                VR::US,
3644                dicom_value!(U16, [0x0000]),
3645            ),
3646            // affected SOP Instance UID: 74 + 8 + 44 = 126
3647            DataElement::new(
3648                tags::AFFECTED_SOP_INSTANCE_UID,
3649                VR::UI,
3650                dicom_value!(Str, storage_sop_instance_uid),
3651            ),
3652        ]);
3653
3654        assert_eq!(
3655            obj.get(tags::COMMAND_GROUP_LENGTH)
3656                .map(|e| e.value().to_int::<u32>().unwrap()),
3657            Some(126)
3658        );
3659    }
3660
3661    #[test]
3662    fn test_even_len() {
3663        assert_eq!(even_len(0), 0);
3664        assert_eq!(even_len(1), 2);
3665        assert_eq!(even_len(2), 2);
3666        assert_eq!(even_len(3), 4);
3667        assert_eq!(even_len(4), 4);
3668        assert_eq!(even_len(5), 6);
3669    }
3670
3671    #[test]
3672    fn can_update_value() {
3673        let mut obj = InMemDicomObject::from_element_iter([DataElement::new(
3674            tags::ANATOMIC_REGION_SEQUENCE,
3675            VR::SQ,
3676            DataSetSequence::empty(),
3677        )]);
3678        assert_eq!(
3679            obj.get(tags::ANATOMIC_REGION_SEQUENCE).map(|e| e.length()),
3680            Some(Length(0)),
3681        );
3682
3683        assert_eq!(
3684            obj.update_value(tags::BURNED_IN_ANNOTATION, |_value| {
3685                panic!("should not be called")
3686            }),
3687            false,
3688        );
3689
3690        let o = obj.update_value(tags::ANATOMIC_REGION_SEQUENCE, |value| {
3691            // add an item
3692            let items = value.items_mut().unwrap();
3693            items.push(InMemDicomObject::from_element_iter([DataElement::new(
3694                tags::INSTANCE_NUMBER,
3695                VR::IS,
3696                PrimitiveValue::from(1),
3697            )]));
3698        });
3699        assert_eq!(o, true);
3700
3701        assert!(obj
3702            .get(tags::ANATOMIC_REGION_SEQUENCE)
3703            .unwrap()
3704            .length()
3705            .is_undefined());
3706    }
3707
3708    #[test]
3709    fn deep_sequence_change_encoding_writes_undefined_sequence_length() {
3710        use smallvec::smallvec;
3711
3712        let obj_1 = InMemDicomObject::from_element_iter(vec![
3713            //The length of this string is 20 bytes in ISO_IR 100 but should be 22 bytes in ISO_IR 192 (UTF-8)
3714            DataElement::new(
3715                tags::STUDY_DESCRIPTION,
3716                VR::SL,
3717                Value::Primitive("MORFOLOGÍA Y FUNCIÓN".into()),
3718            ),
3719            //ISO_IR 100 and ISO_IR 192 length are the same
3720            DataElement::new(
3721                tags::SERIES_DESCRIPTION,
3722                VR::SL,
3723                Value::Primitive("0123456789".into()),
3724            ),
3725        ]);
3726
3727        let some_tag = Tag(0x0018, 0x6011);
3728
3729        let inner_sequence = InMemDicomObject::from_element_iter(vec![DataElement::new(
3730            some_tag,
3731            VR::SQ,
3732            Value::from(DataSetSequence::new(
3733                smallvec![obj_1],
3734                Length(30), //20 bytes from study, 10 from series
3735            )),
3736        )]);
3737        let outer_sequence = DataElement::new(
3738            some_tag,
3739            VR::SQ,
3740            Value::from(DataSetSequence::new(
3741                smallvec![inner_sequence.clone(), inner_sequence],
3742                Length(60), //20 bytes from study, 10 from series
3743            )),
3744        );
3745
3746        let original_object = InMemDicomObject::from_element_iter(vec![
3747            DataElement::new(tags::SPECIFIC_CHARACTER_SET, VR::CS, "ISO_IR 100"),
3748            outer_sequence,
3749        ]);
3750
3751        assert_eq!(
3752            original_object
3753                .get(some_tag)
3754                .expect("object should be present")
3755                .length(),
3756            Length(60)
3757        );
3758
3759        let mut changed_charset = original_object.clone();
3760        changed_charset.convert_to_utf8();
3761        assert!(changed_charset.charset_changed);
3762
3763        use dicom_parser::dataset::DataToken as token;
3764        let options = IntoTokensOptions::new(true);
3765        let converted_tokens: Vec<_> = changed_charset.into_tokens_with_options(options).collect();
3766
3767        assert_eq!(
3768            vec![
3769                token::ElementHeader(DataElementHeader {
3770                    tag: Tag(0x0008, 0x0005),
3771                    vr: VR::CS,
3772                    len: Length(10),
3773                }),
3774                token::PrimitiveValue("ISO_IR 192".into()),
3775                token::SequenceStart {
3776                    tag: Tag(0x0018, 0x6011),
3777                    len: Length::UNDEFINED,
3778                },
3779                token::ItemStart {
3780                    len: Length::UNDEFINED
3781                },
3782                token::SequenceStart {
3783                    tag: Tag(0x0018, 0x6011),
3784                    len: Length::UNDEFINED,
3785                },
3786                token::ItemStart {
3787                    len: Length::UNDEFINED
3788                },
3789                token::ElementHeader(DataElementHeader {
3790                    tag: Tag(0x0008, 0x1030),
3791                    vr: VR::SL,
3792                    len: Length(22),
3793                }),
3794                token::PrimitiveValue("MORFOLOGÍA Y FUNCIÓN".into()),
3795                token::ElementHeader(DataElementHeader {
3796                    tag: Tag(0x0008, 0x103E),
3797                    vr: VR::SL,
3798                    len: Length(10),
3799                }),
3800                token::PrimitiveValue("0123456789".into()),
3801                token::ItemEnd,
3802                token::SequenceEnd,
3803                token::ItemEnd,
3804                token::ItemStart {
3805                    len: Length::UNDEFINED
3806                },
3807                token::SequenceStart {
3808                    tag: Tag(0x0018, 0x6011),
3809                    len: Length::UNDEFINED,
3810                },
3811                token::ItemStart {
3812                    len: Length::UNDEFINED
3813                },
3814                token::ElementHeader(DataElementHeader {
3815                    tag: Tag(0x0008, 0x1030),
3816                    vr: VR::SL,
3817                    len: Length(22),
3818                }),
3819                token::PrimitiveValue("MORFOLOGÍA Y FUNCIÓN".into()),
3820                token::ElementHeader(DataElementHeader {
3821                    tag: Tag(0x0008, 0x103E),
3822                    vr: VR::SL,
3823                    len: Length(10),
3824                }),
3825                token::PrimitiveValue("0123456789".into()),
3826                token::ItemEnd,
3827                token::SequenceEnd,
3828                token::ItemEnd,
3829                token::SequenceEnd,
3830            ],
3831            converted_tokens
3832        );
3833    }
3834
3835    #[test]
3836    fn private_elements() {
3837        let mut ds = InMemDicomObject::from_element_iter(vec![
3838            DataElement::new(
3839                Tag(0x0009, 0x0010),
3840                VR::LO,
3841                PrimitiveValue::from("CREATOR 1"),
3842            ),
3843            DataElement::new(
3844                Tag(0x0009, 0x0011),
3845                VR::LO,
3846                PrimitiveValue::from("CREATOR 2"),
3847            ),
3848            DataElement::new(
3849                Tag(0x0011, 0x0010),
3850                VR::LO,
3851                PrimitiveValue::from("CREATOR 3"),
3852            ),
3853        ]);
3854        ds.put_private_element(
3855            0x0009,
3856            "CREATOR 1",
3857            0x01,
3858            VR::DS,
3859            PrimitiveValue::Str("1.0".to_string()),
3860        )
3861        .unwrap();
3862        ds.put_private_element(
3863            0x0009,
3864            "CREATOR 4",
3865            0x02,
3866            VR::DS,
3867            PrimitiveValue::Str("1.0".to_string()),
3868        )
3869        .unwrap();
3870
3871        let res = ds.put_private_element(
3872            0x0012,
3873            "CREATOR 4",
3874            0x02,
3875            VR::DS,
3876            PrimitiveValue::Str("1.0".to_string()),
3877        );
3878        assert_eq!(
3879            &res.err().unwrap().to_string(),
3880            "Group number must be odd, found 0x0012"
3881        );
3882
3883        assert_eq!(
3884            ds.private_element(0x0009, "CREATOR 1", 0x01)
3885                .unwrap()
3886                .value()
3887                .to_str()
3888                .unwrap(),
3889            "1.0"
3890        );
3891        assert_eq!(
3892            ds.private_element(0x0009, "CREATOR 4", 0x02)
3893                .unwrap()
3894                .value()
3895                .to_str()
3896                .unwrap(),
3897            "1.0"
3898        );
3899        assert_eq!(
3900            ds.private_element(0x0009, "CREATOR 4", 0x02)
3901                .unwrap()
3902                .header()
3903                .tag(),
3904            Tag(0x0009, 0x1202)
3905        );
3906    }
3907
3908    #[test]
3909    fn private_element_group_full() {
3910        let mut ds = InMemDicomObject::from_element_iter(
3911            (0..=0x00FFu16)
3912                .into_iter()
3913                .map(|i| {
3914                    DataElement::new(Tag(0x0009, i), VR::LO, PrimitiveValue::from("CREATOR 1"))
3915                })
3916                .collect::<Vec<DataElement<_>>>(),
3917        );
3918        let res = ds.put_private_element(0x0009, "TEST", 0x01, VR::DS, PrimitiveValue::from("1.0"));
3919        assert_eq!(
3920            res.err().unwrap().to_string(),
3921            "No space available in group 0x0009"
3922        );
3923    }
3924}