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