dicom_object/
lib.rs

1#![allow(clippy::derive_partial_eq_without_eq)]
2//! This crate contains a high-level abstraction for reading and manipulating
3//! DICOM objects.
4//! At this level, objects are comparable to a dictionary of elements,
5//! in which some of them can have DICOM objects themselves.
6//! The end user should prefer using this abstraction when dealing with DICOM
7//! objects.
8//!
9//! Loading a DICOM file can be done with ease via the function [`open_file`].
10//! For additional file reading options, use [`OpenFileOptions`].
11//! New DICOM instances can be built from scratch using [`InMemDicomObject`]
12//! (see the [`mem`] module for more details).
13//!
14//! # Examples
15//!
16//! Read an object and fetch some attributes:
17//!
18//! ```no_run
19//! use dicom_dictionary_std::tags;
20//! use dicom_object::open_file;
21//! # fn foo() -> Result<(), Box<dyn std::error::Error>> {
22//! let obj = open_file("0001.dcm")?;
23//!
24//! let patient_name = obj.element(tags::PATIENT_NAME)?.to_str()?;
25//! let modality = obj.element_by_name("Modality")?.to_str()?;
26//! # Ok(())
27//! # }
28//! ```
29//!
30//! Elements can be fetched by tag,
31//! either by creating a [`Tag`]
32//! or by using one of the [readily available constants][const]
33//! from the [`dicom-dictionary-std`][dictionary-std] crate.
34//!
35//! [const]: dicom_dictionary_std::tags
36//! [dictionary-std]: https://docs.rs/dicom-dictionary-std
37//!
38//! By default, the entire data set is fully loaded into memory.
39//! The pixel data and following elements can be ignored
40//! by using [`OpenFileOptions`]:
41//!
42//! ```no_run
43//! use dicom_object::OpenFileOptions;
44//!
45//! let obj = OpenFileOptions::new()
46//!     .read_until(dicom_dictionary_std::tags::PIXEL_DATA)
47//!     .open_file("0002.dcm")?;
48//! # Result::<(), dicom_object::ReadError>::Ok(())
49//! ```
50//!
51//! Once a data set element is looked up,
52//! one will typically wish to inspect the value within.
53//! Methods are available for converting the element's DICOM value
54//! into something more usable in Rust.
55//!
56//! ```
57//! # use dicom_dictionary_std::tags;
58//! # use dicom_object::{DefaultDicomObject, Tag};
59//! # fn something(obj: DefaultDicomObject) -> Result<(), Box<dyn std::error::Error>> {
60//! let patient_date = obj.element(tags::PATIENT_BIRTH_DATE)?.to_date()?;
61//! let pixel_data_bytes = obj.element(tags::PIXEL_DATA)?.to_bytes()?;
62//! # Ok(())
63//! # }
64//! ```
65//!
66//! **Note:** if you need to decode the pixel data first,
67//! see the [dicom-pixeldata] crate.
68//!
69//! [dicom-pixeldata]: https://docs.rs/dicom-pixeldata
70//!
71//! Finally, DICOM objects can be serialized back into DICOM encoded bytes.
72//! A method is provided for writing a file DICOM object into a new DICOM file.
73//!
74//! ```no_run
75//! # use dicom_object::{DefaultDicomObject, Tag};
76//! # fn something(obj: DefaultDicomObject) -> Result<(), Box<dyn std::error::Error>> {
77//! obj.write_to_file("0001_new.dcm")?;
78//! # Ok(())
79//! # }
80//! ```
81//!
82//! This method requires you to write a [file meta table] first.
83//! When creating a new DICOM object from scratch,
84//! use a [`FileMetaTableBuilder`] to construct the file meta group,
85//! then use [`with_meta`] or [`with_exact_meta`]:
86//!
87//! [file meta table]: crate::meta::FileMetaTable
88//! [`FileMetaTableBuilder`]: crate::meta::FileMetaTableBuilder
89//! [`with_meta`]: crate::InMemDicomObject::with_meta
90//! [`with_exact_meta`]: crate::InMemDicomObject::with_exact_meta
91//!
92//! ```no_run
93//! # use dicom_object::{InMemDicomObject, FileMetaTableBuilder};
94//! # fn something(obj: InMemDicomObject) -> Result<(), Box<dyn std::error::Error>> {
95//! use dicom_dictionary_std::uids;
96//!
97//! let file_obj = obj.with_meta(
98//!     FileMetaTableBuilder::new()
99//!         // Implicit VR Little Endian
100//!         .transfer_syntax(uids::IMPLICIT_VR_LITTLE_ENDIAN)
101//!         // Computed Radiography image storage
102//!         .media_storage_sop_class_uid("1.2.840.10008.5.1.4.1.1.1")
103//! )?;
104//! file_obj.write_to_file("0001_new.dcm")?;
105//! # Ok(())
106//! # }
107//! ```
108//!
109//! In order to write a plain DICOM data set,
110//! use one of the various data set writing methods
111//! such as [`write_dataset_with_ts`]:
112//!
113//! [`write_dataset_with_ts`]: crate::InMemDicomObject::write_dataset_with_ts
114//! ```
115//! # use dicom_object::InMemDicomObject;
116//! # use dicom_core::{DataElement, Tag, VR};
117//! # fn run() -> Result<(), Box<dyn std::error::Error>> {
118//! // build your object
119//! let mut obj = InMemDicomObject::new_empty();
120//! let patient_name = DataElement::new(
121//!     Tag(0x0010, 0x0010),
122//!     VR::PN,
123//!     "Doe^John",
124//! );
125//! obj.put(patient_name);
126//!
127//! // write the object's data set
128//! let mut serialized = Vec::new();
129//! let ts = dicom_transfer_syntax_registry::entries::EXPLICIT_VR_LITTLE_ENDIAN.erased();
130//! obj.write_dataset_with_ts(&mut serialized, &ts)?;
131//! assert!(!serialized.is_empty());
132//! # Ok(())
133//! # }
134//! # run().unwrap();
135//! ```
136pub mod file;
137pub mod mem;
138pub mod meta;
139pub mod ops;
140pub mod tokens;
141
142pub use crate::file::{from_reader, open_file, OpenFileOptions};
143pub use crate::mem::InMemDicomObject;
144pub use crate::meta::{FileMetaTable, FileMetaTableBuilder};
145use dicom_core::ops::AttributeSelector;
146use dicom_core::DataDictionary;
147pub use dicom_core::Tag;
148pub use dicom_dictionary_std::StandardDataDictionary;
149
150/// The default implementation of a root DICOM object.
151pub type DefaultDicomObject<D = StandardDataDictionary> = FileDicomObject<mem::InMemDicomObject<D>>;
152
153use dicom_core::header::{GroupNumber, Header};
154use dicom_encoding::adapters::{PixelDataObject, RawPixelData};
155use dicom_encoding::transfer_syntax::TransferSyntaxIndex;
156use dicom_parser::dataset::{DataSetWriter, IntoTokens};
157use dicom_transfer_syntax_registry::TransferSyntaxRegistry;
158use smallvec::SmallVec;
159use snafu::{Backtrace, OptionExt, ResultExt, Snafu};
160use std::borrow::Cow;
161use std::fs::File;
162use std::io::{BufWriter, Write};
163use std::path::Path;
164
165/// The current implementation class UID generically referring to DICOM-rs.
166///
167/// Automatically generated as per the standard, part 5, section B.2.
168///
169/// This UID is subject to changes in future versions.
170pub const IMPLEMENTATION_CLASS_UID: &str = "2.25.130984950029899771041107395941696826170";
171
172/// The current implementation version name generically referring to DICOM-rs.
173///
174/// This names is subject to changes in future versions.
175pub const IMPLEMENTATION_VERSION_NAME: &str = "DICOM-rs 0.6";
176
177/// Trait type for a DICOM object.
178/// This is a high-level abstraction where an object is accessed and
179/// manipulated as dictionary of entries indexed by tags, which in
180/// turn may contain a DICOM object.
181///
182/// This trait interface is experimental and prone to sudden changes.
183pub trait DicomObject {
184    type Element: Header;
185
186    /// Retrieve a particular DICOM element by its tag.
187    fn element(&self, tag: Tag) -> Result<Self::Element, AccessError>;
188
189    /// Retrieve a particular DICOM element by its name.
190    fn element_by_name(&self, name: &str) -> Result<Self::Element, AccessByNameError>;
191
192    /// Retrieve the processed meta information table, if available.
193    ///
194    /// This table will generally not be reachable from children objects
195    /// in another object with a valid meta table. As such, it is recommended
196    /// for this method to be called at the root of a DICOM object.
197    fn meta(&self) -> Option<&FileMetaTable> {
198        None
199    }
200}
201
202/// An error which may occur when loading a DICOM object
203#[derive(Debug, Snafu)]
204#[non_exhaustive]
205pub enum ReadError {
206    #[snafu(display("Could not open file '{}'", filename.display()))]
207    OpenFile {
208        filename: std::path::PathBuf,
209        backtrace: Backtrace,
210        source: std::io::Error,
211    },
212    #[snafu(display("Could not read from file '{}'", filename.display()))]
213    ReadFile {
214        filename: std::path::PathBuf,
215        backtrace: Backtrace,
216        source: std::io::Error,
217    },
218    /// Could not read preamble bytes
219    ReadPreambleBytes {
220        backtrace: Backtrace,
221        source: std::io::Error,
222    },
223    #[snafu(display("Could not parse meta group data set"))]
224    ParseMetaDataSet {
225        #[snafu(backtrace)]
226        source: crate::meta::Error,
227    },
228    #[snafu(display("Could not parse sop attribute"))]
229    ParseSopAttribute {
230        #[snafu(source(from(dicom_core::value::ConvertValueError, Box::from)))]
231        source: Box<dicom_core::value::ConvertValueError>,
232        backtrace: Backtrace,
233    },
234    #[snafu(display("Could not create data set parser"))]
235    CreateParser {
236        #[snafu(backtrace)]
237        source: dicom_parser::dataset::read::Error,
238    },
239    #[snafu(display("Could not read data set token"))]
240    ReadToken {
241        #[snafu(backtrace)]
242        source: dicom_parser::dataset::read::Error,
243    },
244    #[snafu(display("Missing element value after header token"))]
245    MissingElementValue { backtrace: Backtrace },
246    #[snafu(display("Unsupported transfer syntax `{}`", uid))]
247    ReadUnsupportedTransferSyntax { uid: String, backtrace: Backtrace },
248    #[snafu(display("Unexpected token {:?}", token))]
249    UnexpectedToken {
250        token: Box<dicom_parser::dataset::DataToken>,
251        backtrace: Backtrace,
252    },
253    #[snafu(display("Premature data set end"))]
254    PrematureEnd { backtrace: Backtrace },
255}
256
257/// An error which may occur when writing a DICOM object
258#[derive(Debug, Snafu)]
259#[non_exhaustive]
260pub enum WriteError {
261    #[snafu(display("Could not write to file '{}'", filename.display()))]
262    WriteFile {
263        filename: std::path::PathBuf,
264        backtrace: Backtrace,
265        source: std::io::Error,
266    },
267    #[snafu(display("Could not write object preamble"))]
268    WritePreamble {
269        backtrace: Backtrace,
270        source: std::io::Error,
271    },
272    #[snafu(display("Could not write magic code"))]
273    WriteMagicCode {
274        backtrace: Backtrace,
275        source: std::io::Error,
276    },
277    #[snafu(display("Could not create data set printer"))]
278    CreatePrinter {
279        #[snafu(backtrace)]
280        source: dicom_parser::dataset::write::Error,
281    },
282    #[snafu(display("Could not print meta group data set"))]
283    PrintMetaDataSet {
284        #[snafu(backtrace)]
285        source: crate::meta::Error,
286    },
287    #[snafu(display("Could not print data set"))]
288    PrintDataSet {
289        #[snafu(backtrace)]
290        source: dicom_parser::dataset::write::Error,
291    },
292    #[snafu(display("Unsupported transfer syntax `{}`", uid))]
293    WriteUnsupportedTransferSyntax { uid: String, backtrace: Backtrace },
294}
295
296/// An error which may occur during private element look-up or insertion
297#[derive(Debug, Snafu)]
298#[non_exhaustive]
299pub enum PrivateElementError {
300    /// Group number must be odd
301    #[snafu(display("Group number must be odd, found {:#06x}", group))]
302    InvalidGroup { group: GroupNumber },
303    /// Private creator not found in group
304    #[snafu(display("Private creator {} not found in group {:#06x}", creator, group))]
305    PrivateCreatorNotFound { creator: String, group: GroupNumber },
306    /// Element not found in group
307    #[snafu(display(
308        "Private Creator {} found in group {:#06x}, but elem {:#06x} not found",
309        creator,
310        group,
311        elem
312    ))]
313    ElementNotFound {
314        creator: String,
315        group: GroupNumber,
316        elem: u8,
317    },
318    /// No space available for more private elements in the group
319    #[snafu(display("No space available in group {:#06x}", group))]
320    NoSpace { group: GroupNumber },
321}
322
323/// An error which may occur when looking up a DICOM object's attributes.
324#[derive(Debug, Snafu)]
325#[non_exhaustive]
326pub enum AccessError {
327    #[snafu(display("No such data element with tag {}", tag))]
328    NoSuchDataElementTag { tag: Tag, backtrace: Backtrace },
329}
330
331impl AccessError {
332    pub fn into_access_by_name(self, alias: impl Into<String>) -> AccessByNameError {
333        match self {
334            AccessError::NoSuchDataElementTag { tag, backtrace } => {
335                AccessByNameError::NoSuchDataElementAlias {
336                    tag,
337                    alias: alias.into(),
338                    backtrace,
339                }
340            }
341        }
342    }
343}
344
345/// An error which may occur when looking up a DICOM object's attributes
346/// at an arbitrary depth,
347/// such as through [`value_at`](crate::InMemDicomObject::value_at).
348#[derive(Debug, Snafu)]
349#[non_exhaustive]
350#[snafu(visibility(pub(crate)))]
351pub enum AtAccessError {
352    /// Missing itermediate sequence for {selector} at step {step_index}
353    MissingSequence {
354        selector: AttributeSelector,
355        step_index: u32,
356    },
357    /// Step {step_index} for {selector} is not a data set sequence
358    NotASequence {
359        selector: AttributeSelector,
360        step_index: u32,
361    },
362    /// Missing element at last step for {selector}
363    MissingLeafElement { selector: AttributeSelector },
364}
365
366/// An error which may occur when looking up a DICOM object's attributes
367/// by a keyword (or alias) instead of by tag.
368///
369/// These accesses incur a look-up at the data element dictionary,
370/// which may fail if no such entry exists.
371#[derive(Debug, Snafu)]
372pub enum AccessByNameError {
373    #[snafu(display("No such data element {} (with tag {})", alias, tag))]
374    NoSuchDataElementAlias {
375        tag: Tag,
376        alias: String,
377        backtrace: Backtrace,
378    },
379
380    /// Could not resolve attribute name from the data dictionary
381    #[snafu(display("Unknown data attribute named `{}`", name))]
382    NoSuchAttributeName { name: String, backtrace: Backtrace },
383}
384
385#[derive(Debug, Snafu)]
386#[non_exhaustive]
387pub enum WithMetaError {
388    /// Could not build file meta table
389    BuildMetaTable {
390        #[snafu(backtrace)]
391        source: crate::meta::Error,
392    },
393    /// Could not prepare file meta table
394    PrepareMetaTable {
395        #[snafu(source(from(dicom_core::value::ConvertValueError, Box::from)))]
396        source: Box<dicom_core::value::ConvertValueError>,
397        backtrace: Backtrace,
398    },
399}
400
401/// A root DICOM object retrieved from a standard DICOM file,
402/// containing additional information from the file meta group
403/// in a separate table value.
404#[derive(Debug, Clone, PartialEq)]
405pub struct FileDicomObject<O> {
406    meta: FileMetaTable,
407    obj: O,
408}
409
410impl<O> FileDicomObject<O> {
411    /// Retrieve the processed meta header table.
412    pub fn meta(&self) -> &FileMetaTable {
413        &self.meta
414    }
415
416    /// Retrieve a mutable reference to the processed meta header table.
417    ///
418    /// Considerable care should be taken when modifying this table,
419    /// as it may influence object reading and writing operations.
420    pub fn meta_mut(&mut self) -> &mut FileMetaTable {
421        &mut self.meta
422    }
423
424    /// Retrieve the inner DICOM object structure, discarding the meta table.
425    pub fn into_inner(self) -> O {
426        self.obj
427    }
428}
429
430impl<O> FileDicomObject<O>
431where
432    for<'a> &'a O: IntoTokens,
433{
434    /// Write the entire object as a DICOM file
435    /// into the given file path.
436    /// Preamble, magic code, and file meta group will be included
437    /// before the inner object.
438    pub fn write_to_file<P: AsRef<Path>>(&self, path: P) -> Result<(), WriteError> {
439        let path = path.as_ref();
440        let file = File::create(path).context(WriteFileSnafu { filename: path })?;
441        let mut to = BufWriter::new(file);
442
443        // write preamble
444        to.write_all(&[0_u8; 128][..])
445            .context(WriteFileSnafu { filename: path })?;
446
447        // write magic sequence
448        to.write_all(b"DICM")
449            .context(WriteFileSnafu { filename: path })?;
450
451        // write meta group
452        self.meta.write(&mut to).context(PrintMetaDataSetSnafu)?;
453
454        // prepare encoder
455        let ts = TransferSyntaxRegistry
456            .get(&self.meta.transfer_syntax)
457            .with_context(|| WriteUnsupportedTransferSyntaxSnafu {
458                uid: self.meta.transfer_syntax.clone(),
459            })?;
460        let mut dset_writer = DataSetWriter::with_ts(to, ts).context(CreatePrinterSnafu)?;
461
462        // We use the default options, because only the inner object knows if something needs to change
463        dset_writer
464            .write_sequence((&self.obj).into_tokens())
465            .context(PrintDataSetSnafu)?;
466
467        Ok(())
468    }
469
470    /// Write the entire object as a DICOM file
471    /// into the given writer.
472    /// Preamble, magic code, and file meta group will be included
473    /// before the inner object.
474    pub fn write_all<W: Write>(&self, to: W) -> Result<(), WriteError> {
475        let mut to = BufWriter::new(to);
476
477        // write preamble
478        to.write_all(&[0_u8; 128][..]).context(WritePreambleSnafu)?;
479
480        // write magic sequence
481        to.write_all(b"DICM").context(WriteMagicCodeSnafu)?;
482
483        // write meta group
484        self.meta.write(&mut to).context(PrintMetaDataSetSnafu)?;
485
486        // prepare encoder
487        let ts = TransferSyntaxRegistry
488            .get(&self.meta.transfer_syntax)
489            .with_context(|| WriteUnsupportedTransferSyntaxSnafu {
490                uid: self.meta.transfer_syntax.clone(),
491            })?;
492        let mut dset_writer = DataSetWriter::with_ts(to, ts).context(CreatePrinterSnafu)?;
493
494        // We use the default options, because only the inner object knows if something needs to change
495        dset_writer
496            .write_sequence((&self.obj).into_tokens())
497            .context(PrintDataSetSnafu)?;
498
499        Ok(())
500    }
501
502    /// Write the file meta group set into the given writer.
503    ///
504    /// This is equivalent to `self.meta().write(to)`.
505    pub fn write_meta<W: Write>(&self, to: W) -> Result<(), WriteError> {
506        self.meta.write(to).context(PrintMetaDataSetSnafu)
507    }
508
509    /// Write the inner data set into the given writer,
510    /// without preamble, magic code, nor file meta group.
511    ///
512    /// The transfer syntax is selected from the file meta table.
513    pub fn write_dataset<W: Write>(&self, to: W) -> Result<(), WriteError> {
514        let to = BufWriter::new(to);
515
516        // prepare encoder
517        let ts = TransferSyntaxRegistry
518            .get(&self.meta.transfer_syntax)
519            .with_context(|| WriteUnsupportedTransferSyntaxSnafu {
520                uid: self.meta.transfer_syntax.clone(),
521            })?;
522        let mut dset_writer = DataSetWriter::with_ts(to, ts).context(CreatePrinterSnafu)?;
523
524        // write object
525        dset_writer
526            .write_sequence((&self.obj).into_tokens())
527            .context(PrintDataSetSnafu)?;
528
529        Ok(())
530    }
531}
532
533impl<O> ::std::ops::Deref for FileDicomObject<O> {
534    type Target = O;
535
536    fn deref(&self) -> &Self::Target {
537        &self.obj
538    }
539}
540
541impl<O> ::std::ops::DerefMut for FileDicomObject<O> {
542    fn deref_mut(&mut self) -> &mut Self::Target {
543        &mut self.obj
544    }
545}
546
547impl<O> DicomObject for FileDicomObject<O>
548where
549    O: DicomObject,
550{
551    type Element = <O as DicomObject>::Element;
552
553    fn element(&self, tag: Tag) -> Result<Self::Element, AccessError> {
554        self.obj.element(tag)
555    }
556
557    fn element_by_name(&self, name: &str) -> Result<Self::Element, AccessByNameError> {
558        self.obj.element_by_name(name)
559    }
560
561    fn meta(&self) -> Option<&FileMetaTable> {
562        Some(&self.meta)
563    }
564}
565
566impl<'a, O: 'a> DicomObject for &'a FileDicomObject<O>
567where
568    O: DicomObject,
569{
570    type Element = <O as DicomObject>::Element;
571
572    fn element(&self, tag: Tag) -> Result<Self::Element, AccessError> {
573        self.obj.element(tag)
574    }
575
576    fn element_by_name(&self, name: &str) -> Result<Self::Element, AccessByNameError> {
577        self.obj.element_by_name(name)
578    }
579}
580
581/// This implementation creates an iterator
582/// to the elements of the underlying data set,
583/// consuming the whole object.
584/// The attributes in the file meta group are _not_ included.
585///
586/// To obtain an iterator over the meta elements,
587/// use [`meta().to_element_iter()`](FileMetaTable::to_element_iter).
588impl<O> IntoIterator for FileDicomObject<O>
589where
590    O: IntoIterator,
591{
592    type Item = <O as IntoIterator>::Item;
593    type IntoIter = <O as IntoIterator>::IntoIter;
594
595    fn into_iter(self) -> Self::IntoIter {
596        self.obj.into_iter()
597    }
598}
599
600/// This implementation creates an iterator
601/// to the elements of the underlying data set.
602/// The attributes in the file meta group are _not_ included.
603///
604/// To obtain an iterator over the meta elements,
605/// use [`meta().to_element_iter()`](FileMetaTable::to_element_iter).
606impl<'a, O> IntoIterator for &'a FileDicomObject<O>
607where
608    &'a O: IntoIterator,
609{
610    type Item = <&'a O as IntoIterator>::Item;
611    type IntoIter = <&'a O as IntoIterator>::IntoIter;
612
613    fn into_iter(self) -> Self::IntoIter {
614        (&self.obj).into_iter()
615    }
616}
617
618/// Implement basic pixeldata encoder/decoder functionality
619impl<D> PixelDataObject for FileDicomObject<InMemDicomObject<D>>
620where
621    D: DataDictionary + Clone,
622{
623    fn transfer_syntax_uid(&self) -> &str {
624        self.meta.transfer_syntax()
625    }
626
627    /// Return the Rows attribute or None if it is not found
628    fn rows(&self) -> Option<u16> {
629        self.get(dicom_dictionary_std::tags::ROWS)?.uint16().ok()
630    }
631
632    /// Return the Columns attribute or None if it is not found
633    fn cols(&self) -> Option<u16> {
634        self.get(dicom_dictionary_std::tags::COLUMNS)?.uint16().ok()
635    }
636
637    /// Return the SamplesPerPixel attribute or None if it is not found
638    fn samples_per_pixel(&self) -> Option<u16> {
639        self.get(dicom_dictionary_std::tags::SAMPLES_PER_PIXEL)?
640            .uint16()
641            .ok()
642    }
643
644    /// Return the BitsAllocated attribute or None if it is not set
645    fn bits_allocated(&self) -> Option<u16> {
646        self.get(dicom_dictionary_std::tags::BITS_ALLOCATED)?
647            .uint16()
648            .ok()
649    }
650
651    /// Return the BitsStored attribute or None if it is not set
652    fn bits_stored(&self) -> Option<u16> {
653        self.get(dicom_dictionary_std::tags::BITS_STORED)?
654            .uint16()
655            .ok()
656    }
657
658    fn photometric_interpretation(&self) -> Option<&str> {
659        self.get(dicom_dictionary_std::tags::PHOTOMETRIC_INTERPRETATION)?
660            .string()
661            .ok()
662            .map(|s| s.trim_end())
663    }
664
665    /// Return the NumberOfFrames attribute or None if it is not set
666    fn number_of_frames(&self) -> Option<u32> {
667        self.get(dicom_dictionary_std::tags::NUMBER_OF_FRAMES)?
668            .to_int()
669            .ok()
670    }
671
672    /// Returns the number of fragments or None for native pixel data
673    fn number_of_fragments(&self) -> Option<u32> {
674        let pixel_data = self.get(dicom_dictionary_std::tags::PIXEL_DATA)?;
675        match pixel_data.value() {
676            dicom_core::DicomValue::Primitive(_p) => Some(1),
677            dicom_core::DicomValue::PixelSequence(v) => Some(v.fragments().len() as u32),
678            dicom_core::DicomValue::Sequence(..) => None,
679        }
680    }
681
682    /// Return a specific encoded pixel fragment by index as a `Vec<u8>`
683    /// or `None` if no pixel data is found.
684    ///
685    /// Non-encapsulated pixel data can be retrieved by requesting fragment #0.
686    ///
687    /// Panics if `fragment` is out of bounds for the encapsulated pixel data fragments.
688    fn fragment(&self, fragment: usize) -> Option<Cow<[u8]>> {
689        let pixel_data = self.get(dicom_dictionary_std::tags::PIXEL_DATA)?;
690        match pixel_data.value() {
691            dicom_core::DicomValue::PixelSequence(v) => {
692                Some(Cow::Borrowed(v.fragments()[fragment].as_ref()))
693            }
694            dicom_core::DicomValue::Primitive(p) if fragment == 0 => Some(p.to_bytes()),
695            _ => None,
696        }
697    }
698
699    fn offset_table(&self) -> Option<Cow<[u32]>> {
700        let pixel_data = self.get(dicom_dictionary_std::tags::PIXEL_DATA)?;
701        match pixel_data.value() {
702            dicom_core::DicomValue::Primitive(_) => None,
703            dicom_core::DicomValue::Sequence(_) => None,
704            dicom_core::DicomValue::PixelSequence(seq) => Some(Cow::from(seq.offset_table())),
705        }
706    }
707
708    /// Should return either a byte slice/vector if native pixel data
709    /// or byte fragments if encapsulated.
710    /// Returns None if no pixel data is found
711    fn raw_pixel_data(&self) -> Option<RawPixelData> {
712        let pixel_data = self.get(dicom_dictionary_std::tags::PIXEL_DATA)?;
713        match pixel_data.value() {
714            dicom_core::DicomValue::Primitive(p) => {
715                // Create 1 fragment with all bytes
716                let fragment = p.to_bytes().to_vec();
717                let mut fragments = SmallVec::new();
718                fragments.push(fragment);
719                Some(RawPixelData {
720                    fragments,
721                    offset_table: SmallVec::new(),
722                })
723            }
724            dicom_core::DicomValue::PixelSequence(v) => {
725                let (offset_table, fragments) = v.clone().into_parts();
726                Some(RawPixelData {
727                    fragments,
728                    offset_table,
729                })
730            }
731            dicom_core::DicomValue::Sequence(..) => None,
732        }
733    }
734}
735
736#[cfg(test)]
737mod tests {
738    use dicom_core::{DataElement, PrimitiveValue, VR};
739
740    use crate::meta::FileMetaTableBuilder;
741    use crate::{AccessError, FileDicomObject, InMemDicomObject};
742
743    fn assert_type_not_too_large<T>(max_size: usize) {
744        let size = std::mem::size_of::<T>();
745        if size > max_size {
746            panic!(
747                "Type {} of byte size {} exceeds acceptable size {}",
748                std::any::type_name::<T>(),
749                size,
750                max_size
751            );
752        }
753    }
754
755    #[test]
756    fn errors_not_too_large() {
757        assert_type_not_too_large::<AccessError>(56);
758    }
759
760    #[test]
761    fn smoke_test() {
762        const FILE_NAME: &str = ".smoke-test.dcm";
763
764        let meta = FileMetaTableBuilder::new()
765            .transfer_syntax(
766                dicom_transfer_syntax_registry::entries::EXPLICIT_VR_LITTLE_ENDIAN.uid(),
767            )
768            .media_storage_sop_class_uid("1.2.840.10008.5.1.4.1.1.1")
769            .media_storage_sop_instance_uid("1.2.3.456")
770            .implementation_class_uid("1.2.345.6.7890.1.234")
771            .build()
772            .unwrap();
773        let obj = FileDicomObject::new_empty_with_meta(meta);
774
775        obj.write_to_file(FILE_NAME).unwrap();
776
777        let obj2 = FileDicomObject::open_file(FILE_NAME).unwrap();
778
779        assert_eq!(obj, obj2);
780
781        let _ = std::fs::remove_file(FILE_NAME);
782    }
783
784    /// A FileDicomObject<InMemDicomObject>
785    /// can be used like a DICOM object.
786    #[test]
787    fn file_dicom_object_can_use_inner() {
788        let mut obj = InMemDicomObject::new_empty();
789
790        obj.put(DataElement::new(
791            dicom_dictionary_std::tags::PATIENT_NAME,
792            VR::PN,
793            PrimitiveValue::from("John Doe"),
794        ));
795
796        let mut obj = obj
797            .with_meta(
798                FileMetaTableBuilder::new()
799                    .media_storage_sop_class_uid("1.2.840.10008.5.1.4.1.1.7")
800                    .media_storage_sop_instance_uid("1.2.23456789")
801                    .transfer_syntax("1.2.840.10008.1.2.1"),
802            )
803            .unwrap();
804
805        // contains patient name
806        assert_eq!(
807            obj.element(dicom_dictionary_std::tags::PATIENT_NAME)
808                .unwrap()
809                .value()
810                .to_str()
811                .unwrap(),
812            "John Doe",
813        );
814
815        // can be removed with take
816        obj.take_element(dicom_dictionary_std::tags::PATIENT_NAME)
817            .unwrap();
818
819        assert!(matches!(
820            obj.element(dicom_dictionary_std::tags::PATIENT_NAME),
821            Err(AccessError::NoSuchDataElementTag { .. }),
822        ));
823    }
824
825    #[test]
826    fn file_dicom_object_can_iterate_over_elements() {
827        let mut obj = InMemDicomObject::new_empty();
828
829        obj.put(DataElement::new(
830            dicom_dictionary_std::tags::PATIENT_NAME,
831            VR::PN,
832            PrimitiveValue::from("John Doe"),
833        ));
834        obj.put(DataElement::new(
835            dicom_dictionary_std::tags::SOP_INSTANCE_UID,
836            VR::PN,
837            PrimitiveValue::from("1.2.987654321"),
838        ));
839
840        let obj = obj
841            .with_meta(
842                FileMetaTableBuilder::new()
843                    .media_storage_sop_class_uid("1.2.840.10008.5.1.4.1.1.7")
844                    .media_storage_sop_instance_uid("1.2.987654321")
845                    .transfer_syntax("1.2.840.10008.1.2.1"),
846            )
847            .unwrap();
848
849        // iter
850        let mut iter = (&obj).into_iter();
851        assert_eq!(
852            iter.next().unwrap().header().tag,
853            dicom_dictionary_std::tags::SOP_INSTANCE_UID
854        );
855        assert_eq!(
856            iter.next().unwrap().header().tag,
857            dicom_dictionary_std::tags::PATIENT_NAME
858        );
859        assert_eq!(iter.next(), None);
860
861        // into_iter
862        let mut iter = obj.into_iter();
863        assert_eq!(
864            iter.next().unwrap().header().tag,
865            dicom_dictionary_std::tags::SOP_INSTANCE_UID
866        );
867        assert_eq!(
868            iter.next().unwrap().header().tag,
869            dicom_dictionary_std::tags::PATIENT_NAME
870        );
871        assert_eq!(iter.next(), None);
872    }
873}