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