diff --git a/Cargo.lock b/Cargo.lock index 55e8e28e5..0b34d921d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -340,6 +340,7 @@ name = "dicom-core" version = "0.7.0" dependencies = [ "chrono", + "either", "itertools", "num-traits", "safe-transmute", @@ -632,9 +633,9 @@ dependencies = [ [[package]] name = "either" -version = "1.11.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "encode_unicode" diff --git a/core/Cargo.toml b/core/Cargo.toml index 7c3bbcc44..5dd3dd77d 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -11,6 +11,7 @@ readme = "README.md" [dependencies] chrono = { version = "0.4.31", default-features = false, features = ["std", "clock"] } +either = "1.13.0" itertools = "0.12" num-traits = "0.2.12" safe-transmute = "0.11.0" diff --git a/core/src/value/mod.rs b/core/src/value/mod.rs index 4f67e4f7c..dc8db031e 100644 --- a/core/src/value/mod.rs +++ b/core/src/value/mod.rs @@ -23,6 +23,8 @@ pub use self::primitive::{ ValueType, }; +pub use either::Either; + /// An aggregation of one or more elements in a value. pub type C = SmallVec<[T; 2]>; @@ -43,6 +45,39 @@ pub trait DicomValueType: HasLength { fn cardinality(&self) -> usize; } +impl HasLength for Either +where + L: HasLength, + R: HasLength, +{ + fn length(&self) -> Length { + match self { + Either::Left(l) => l.length(), + Either::Right(r) => r.length(), + } + } +} + +impl DicomValueType for Either +where + L: DicomValueType, + R: DicomValueType, +{ + fn value_type(&self) -> ValueType { + match self { + Either::Left(l) => l.value_type(), + Either::Right(r) => r.value_type(), + } + } + + fn cardinality(&self) -> usize { + match self { + Either::Left(l) => l.cardinality(), + Either::Right(r) => r.cardinality(), + } + } +} + /// Representation of a full DICOM value, which may be either primitive or /// another DICOM object. /// diff --git a/object/src/lib.rs b/object/src/lib.rs index 674390a53..510a5c4ba 100644 --- a/object/src/lib.rs +++ b/object/src/lib.rs @@ -173,7 +173,7 @@ pub use crate::meta::{FileMetaTable, FileMetaTableBuilder}; use dicom_core::ops::AttributeSelector; use dicom_core::value::{DicomValueType, ValueType}; pub use dicom_core::Tag; -use dicom_core::{DataDictionary, DicomValue}; +use dicom_core::{DataDictionary, DicomValue, PrimitiveValue}; pub use dicom_dictionary_std::StandardDataDictionary; /// The default implementation of a root DICOM object. @@ -184,6 +184,8 @@ use dicom_encoding::adapters::{PixelDataObject, RawPixelData}; use dicom_encoding::transfer_syntax::TransferSyntaxIndex; use dicom_parser::dataset::{DataSetWriter, IntoTokens}; use dicom_transfer_syntax_registry::TransferSyntaxRegistry; +use itertools::Either; +use meta::FileMetaAttribute; use smallvec::SmallVec; use snafu::{Backtrace, OptionExt, ResultExt, Snafu}; use std::borrow::Cow; @@ -205,12 +207,18 @@ pub const IMPLEMENTATION_VERSION_NAME: &str = "DICOM-rs 0.6"; /// An error which occurs when fetching a value #[derive(Debug, Snafu)] +#[non_exhaustive] pub enum AttributeError { - /// could not fetch value behind attribute {tag} - FetchValue { - tag: Tag, - keyword: Option>, - source: dicom_core::value::ConvertValueError, + /// A custom error when encoding fails. + /// Read the `message` and the underlying `source` + /// for more details. + #[snafu(whatever, display("{}", message))] + Custom { + /// The error message. + message: String, + /// The underlying error cause, if any. + #[snafu(source(from(Box, Some)))] + source: Option>, }, /// the value cannot be converted to the requested type @@ -249,11 +257,12 @@ pub trait DicomAttribute: DicomValueType { where Self: 'a; - /// Obtain an in-memory representation of the value, - /// cloning the value one level deep if necessary. - fn to_dicom_value<'a>( - &'a self, - ) -> Result, Self::PixelData<'a>>, AttributeError>; + /// Obtain an in-memory representation of the primitive value, + /// cloning the value if necessary. + /// + /// An error is returned if the value is a data set sequence or + /// an encapsulated pixel data fragment sequence. + fn to_primitive_value(&self) -> Result; /// Obtain one of the items in the attribute, /// if the attribute value represents a data set sequence. @@ -288,36 +297,37 @@ pub trait DicomAttribute: DicomValueType { /// Obtain the attribute's value as a string, /// converting it if necessary. fn to_str<'a>(&'a self) -> Result, AttributeError> { - Ok(Cow::Owned( - self.to_dicom_value()? - .to_str() - .context(ConvertValueSnafu)? - .to_string(), - )) + Ok(Cow::Owned(self.to_primitive_value()?.to_str().to_string())) } /// Obtain the attribute's value as a 16-bit unsigned integer, /// converting it if necessary. fn to_u16(&self) -> Result { - self.to_dicom_value()?.to_int().context(ConvertValueSnafu) + self.to_primitive_value()? + .to_int() + .context(ConvertValueSnafu) } /// Obtain the attribute's value as a 32-bit signed integer, /// converting it if necessary. fn to_i32(&self) -> Result { - self.to_dicom_value()?.to_int().context(ConvertValueSnafu) + self.to_primitive_value()? + .to_int() + .context(ConvertValueSnafu) } /// Obtain the attribute's value as a 32-bit unsigned integer, /// converting it if necessary. fn to_u32(&self) -> Result { - self.to_dicom_value()?.to_int().context(ConvertValueSnafu) + self.to_primitive_value()? + .to_int() + .context(ConvertValueSnafu) } /// Obtain the attribute's value as a 32-bit floating-point number, /// converting it if necessary. fn to_f32(&self) -> Result { - self.to_dicom_value()? + self.to_primitive_value()? .to_float32() .context(ConvertValueSnafu) } @@ -325,7 +335,7 @@ pub trait DicomAttribute: DicomValueType { /// Obtain the attribute's value as a 64-bit floating-point number, /// converting it if necessary. fn to_f64(&self) -> Result { - self.to_dicom_value()? + self.to_primitive_value()? .to_float64() .context(ConvertValueSnafu) } @@ -333,12 +343,7 @@ pub trait DicomAttribute: DicomValueType { /// Obtain the attribute's value as bytes, /// converting it if necessary. fn to_bytes<'a>(&'a self) -> Result, AttributeError> { - Ok(Cow::Owned( - self.to_dicom_value()? - .to_bytes() - .context(ConvertValueSnafu)? - .to_vec(), - )) + Ok(Cow::Owned(self.to_primitive_value()?.to_bytes().to_vec())) } } @@ -406,10 +411,17 @@ where where Self: 'a, P: 'a; #[inline] - fn to_dicom_value<'a>( - &'a self, - ) -> Result, Self::PixelData<'a>>, AttributeError> { - Ok(self.shallow_clone()) + fn to_primitive_value<'a>(&'a self) -> Result { + match self { + DicomValue::Primitive(value) => Ok(value.clone()), + _ => Err(AttributeError::ConvertValue { + source: dicom_core::value::ConvertValueError { + requested: "primitive", + original: self.value_type(), + cause: None, + }, + }), + } } #[inline] @@ -464,10 +476,17 @@ where where Self: 'a, P: 'a; #[inline] - fn to_dicom_value<'a>( - &'a self, - ) -> Result, Self::PixelData<'a>>, AttributeError> { - Ok(self.shallow_clone()) + fn to_primitive_value(&self) -> Result { + match self { + DicomValue::Primitive(value) => Ok(value.clone()), + _ => Err(AttributeError::ConvertValue { + source: dicom_core::value::ConvertValueError { + requested: "primitive", + original: self.value_type(), + cause: None, + }, + }), + } } #[inline] @@ -856,16 +875,65 @@ impl ::std::ops::DerefMut for FileDicomObject { } } +impl DicomAttribute for Either +where + L: DicomAttribute, + R: DicomAttribute, +{ + type Item<'a> = Either, R::Item<'a>> + where Self: 'a; + type PixelData<'a> = Either, R::PixelData<'a>> + where Self: 'a; + + fn to_primitive_value<'a>(&'a self) -> Result { + match self { + Either::Left(l) => l.to_primitive_value(), + Either::Right(r) => r.to_primitive_value(), + } + } + + fn item(&self, index: u32) -> Result, AttributeError> { + match self { + Either::Left(l) => l.item(index).map(Either::Left), + Either::Right(r) => r.item(index).map(Either::Right), + } + } + + fn num_items(&self) -> Option { + match self { + Either::Left(l) => l.num_items(), + Either::Right(r) => r.num_items(), + } + } + + fn fragment(&self, index: u32) -> Result, AttributeError> { + match self { + Either::Left(l) => l.fragment(index).map(Either::Left), + Either::Right(r) => r.fragment(index).map(Either::Right), + } + } +} + impl DicomObject for FileDicomObject where O: DicomObject, { - type Attribute<'a> = ::Attribute<'a> - where O: 'a; + type Attribute<'a> = Either, O::Attribute<'a>> + where Self: 'a, + O: 'a; #[inline] fn get_opt(&self, tag: Tag) -> Result>, AccessError> { - self.obj.get_opt(tag) + match tag { + Tag(0x0002, _) => { + let attr = self.meta.get_opt(tag)?; + Ok(attr.map(Either::Left)) + } + _ => { + let attr = self.obj.get_opt(tag)?; + Ok(attr.map(Either::Right)) + } + } } #[inline] @@ -873,17 +941,41 @@ where &self, name: &str, ) -> Result>, AccessByNameError> { - self.obj.get_by_name_opt(name) + match name { + "FileMetaInformationGroupLength" + | "FileMetaInformationVersion" + | "MediaStorageSOPClassUID" + | "MediaStorageSOPInstanceUID" + | "TransferSyntaxUID" + | "ImplementationClassUID" + | "ImplementationVersionName" + | "SourceApplicationEntityTitle" + | "SendingApplicationEntityTitle" + | "ReceivingApplicationEntityTitle" + | "PrivateInformationCreatorUID" + | "PrivateInformation" => { + let attr = self.meta.get_by_name_opt(name)?; + Ok(attr.map(Either::Left)) + } + _ => { + let attr = self.obj.get_by_name_opt(name)?; + Ok(attr.map(Either::Right)) + } + } } #[inline] fn get(&self, tag: Tag) -> Result, AccessError> { - self.obj.get(tag) - } - - #[inline] - fn get_by_name(&self, name: &str) -> Result, AccessByNameError> { - self.obj.get_by_name(name) + match tag { + Tag(0x0002, _) => { + let attr = self.meta.get(tag)?; + Ok(Either::Left(attr)) + } + _ => { + let attr = self.obj.get(tag)?; + Ok(Either::Right(attr)) + } + } } } @@ -1085,6 +1177,7 @@ where #[cfg(test)] mod tests { use dicom_core::{DataElement, PrimitiveValue, VR}; + use dicom_dictionary_std::{tags, uids}; use crate::meta::FileMetaTableBuilder; use crate::{AccessError, FileDicomObject, InMemDicomObject}; @@ -1220,8 +1313,48 @@ mod tests { assert_eq!(iter.next(), None); } + /// Can access file meta properties via the DicomObject trait + #[test] + fn dicom_object_api_on_file_dicom_object() { + use crate::{DicomAttribute as _, DicomObject as _}; + + let meta = FileMetaTableBuilder::new() + .transfer_syntax(uids::RLE_LOSSLESS) + .media_storage_sop_class_uid(uids::ENHANCED_MR_IMAGE_STORAGE) + .media_storage_sop_instance_uid("2.25.94766187067244888884745908966163363746") + .build() + .unwrap(); + let obj = FileDicomObject::new_empty_with_meta(meta); + + assert_eq!( + obj.get(tags::TRANSFER_SYNTAX_UID) + .unwrap() + .to_str() + .unwrap(), + uids::RLE_LOSSLESS + ); + + let sop_class_uid = obj + .get_opt(tags::MEDIA_STORAGE_SOP_CLASS_UID) + .unwrap(); + let sop_class_uid = sop_class_uid.as_ref() + .map(|v| v.to_str().unwrap()); + assert_eq!( + sop_class_uid.as_deref(), + Some(uids::ENHANCED_MR_IMAGE_STORAGE) + ); + + assert_eq!( + obj.get_by_name("MediaStorageSOPInstanceUID") + .unwrap() + .to_str() + .unwrap(), + "2.25.94766187067244888884745908966163363746" + ); + } + #[test] - fn attribute_api_on_primitive_values() { + fn operations_api_on_primitive_values() { use crate::DicomAttribute; use dicom_dictionary_std::tags; diff --git a/object/src/meta.rs b/object/src/meta.rs index 0b3dfc862..920858891 100644 --- a/object/src/meta.rs +++ b/object/src/meta.rs @@ -3,7 +3,9 @@ use byteordered::byteorder::{ByteOrder, LittleEndian}; use dicom_core::dicom_value; use dicom_core::header::{DataElement, EmptyObject, HasLength, Header}; use dicom_core::ops::{ApplyOp, AttributeAction, AttributeOp, AttributeSelectorStep}; -use dicom_core::value::{PrimitiveValue, Value, ValueType}; +use dicom_core::value::{ + ConvertValueError, DicomValueType, InMemFragment, PrimitiveValue, Value, ValueType, +}; use dicom_core::{Length, Tag, VR}; use dicom_dictionary_std::tags; use dicom_encoding::decode::{self, DecodeFrom}; @@ -13,13 +15,17 @@ use dicom_encoding::text::{self, TextCodec}; use dicom_encoding::TransferSyntax; use dicom_parser::dataset::{DataSetWriter, IntoTokens}; use snafu::{ensure, Backtrace, OptionExt, ResultExt, Snafu}; +use std::borrow::Cow; use std::io::{Read, Write}; use crate::ops::{ ApplyError, ApplyResult, IllegalExtendSnafu, IncompatibleTypesSnafu, MandatorySnafu, UnsupportedActionSnafu, UnsupportedAttributeSnafu, }; -use crate::{IMPLEMENTATION_CLASS_UID, IMPLEMENTATION_VERSION_NAME}; +use crate::{ + AttributeError, DicomAttribute, DicomObject, IMPLEMENTATION_CLASS_UID, + IMPLEMENTATION_VERSION_NAME, +}; const DICM_MAGIC_CODE: [u8; 4] = [b'D', b'I', b'C', b'M']; @@ -106,7 +112,7 @@ pub enum Error { }, } -type Result = std::result::Result; +type Result = std::result::Result; /// DICOM File Meta Information Table. /// @@ -786,6 +792,307 @@ impl FileMetaTable { } } +/// An attribute selector for a file meta information table. +#[derive(Debug)] +pub struct FileMetaAttribute<'a> { + meta: &'a FileMetaTable, + tag_e: u16, +} + +impl HasLength for FileMetaAttribute<'_> { + fn length(&self) -> Length { + match Tag(0x0002, self.tag_e) { + tags::FILE_META_INFORMATION_GROUP_LENGTH => Length(4), + tags::MEDIA_STORAGE_SOP_CLASS_UID => { + Length(self.meta.media_storage_sop_class_uid.len() as u32) + } + tags::MEDIA_STORAGE_SOP_INSTANCE_UID => { + Length(self.meta.media_storage_sop_instance_uid.len() as u32) + } + tags::IMPLEMENTATION_CLASS_UID => { + Length(self.meta.implementation_class_uid.len() as u32) + } + tags::IMPLEMENTATION_VERSION_NAME => Length( + self.meta + .implementation_version_name + .as_ref() + .map(|s| s.len() as u32) + .unwrap_or(0), + ), + tags::SOURCE_APPLICATION_ENTITY_TITLE => Length( + self.meta + .source_application_entity_title + .as_ref() + .map(|s| s.len() as u32) + .unwrap_or(0), + ), + tags::SENDING_APPLICATION_ENTITY_TITLE => Length( + self.meta + .sending_application_entity_title + .as_ref() + .map(|s| s.len() as u32) + .unwrap_or(0), + ), + tags::TRANSFER_SYNTAX_UID => Length(self.meta.transfer_syntax.len() as u32), + tags::PRIVATE_INFORMATION_CREATOR_UID => Length( + self.meta + .private_information_creator_uid + .as_ref() + .map(|s| s.len() as u32) + .unwrap_or(0), + ), + _ => unreachable!(), + } + } +} + +impl DicomValueType for FileMetaAttribute<'_> { + fn value_type(&self) -> ValueType { + match Tag(0x0002, self.tag_e) { + tags::MEDIA_STORAGE_SOP_CLASS_UID + | tags::MEDIA_STORAGE_SOP_INSTANCE_UID + | tags::TRANSFER_SYNTAX_UID + | tags::IMPLEMENTATION_CLASS_UID + | tags::IMPLEMENTATION_VERSION_NAME + | tags::SOURCE_APPLICATION_ENTITY_TITLE + | tags::SENDING_APPLICATION_ENTITY_TITLE + | tags::RECEIVING_APPLICATION_ENTITY_TITLE + | tags::PRIVATE_INFORMATION_CREATOR_UID => ValueType::Str, + tags::FILE_META_INFORMATION_GROUP_LENGTH => ValueType::U32, + tags::FILE_META_INFORMATION_VERSION => ValueType::U8, + tags::PRIVATE_INFORMATION => ValueType::U8, + _ => unreachable!(), + } + } + + fn cardinality(&self) -> usize { + match Tag(0x0002, self.tag_e) { + tags::MEDIA_STORAGE_SOP_CLASS_UID + | tags::MEDIA_STORAGE_SOP_INSTANCE_UID + | tags::SOURCE_APPLICATION_ENTITY_TITLE + | tags::SENDING_APPLICATION_ENTITY_TITLE + | tags::RECEIVING_APPLICATION_ENTITY_TITLE + | tags::TRANSFER_SYNTAX_UID + | tags::IMPLEMENTATION_CLASS_UID + | tags::IMPLEMENTATION_VERSION_NAME + | tags::PRIVATE_INFORMATION_CREATOR_UID => 1, + tags::FILE_META_INFORMATION_GROUP_LENGTH => 1, + tags::PRIVATE_INFORMATION => 1, + tags::FILE_META_INFORMATION_VERSION => 2, + _ => 1, + } + } +} + +impl<'a> DicomAttribute for FileMetaAttribute<'a> { + type Item<'b> = EmptyObject + where Self: 'b; + type PixelData<'b> = InMemFragment + where Self: 'b; + + fn to_primitive_value<'b>( + &'b self, + ) -> Result { + Ok(match Tag(0x0002, self.tag_e) { + tags::FILE_META_INFORMATION_GROUP_LENGTH => { + PrimitiveValue::from(self.meta.information_group_length) + } + tags::FILE_META_INFORMATION_VERSION => { + PrimitiveValue::from(self.meta.information_version) + } + tags::MEDIA_STORAGE_SOP_CLASS_UID => { + PrimitiveValue::from(self.meta.media_storage_sop_class_uid.clone()) + } + tags::MEDIA_STORAGE_SOP_INSTANCE_UID => { + PrimitiveValue::from(self.meta.media_storage_sop_instance_uid.clone()) + } + tags::SOURCE_APPLICATION_ENTITY_TITLE => { + PrimitiveValue::from(self.meta.source_application_entity_title.clone().unwrap()) + } + tags::SENDING_APPLICATION_ENTITY_TITLE => { + PrimitiveValue::from(self.meta.sending_application_entity_title.clone().unwrap()) + } + tags::RECEIVING_APPLICATION_ENTITY_TITLE => PrimitiveValue::from( + self.meta + .receiving_application_entity_title + .clone() + .unwrap(), + ), + tags::TRANSFER_SYNTAX_UID => PrimitiveValue::from(self.meta.transfer_syntax.clone()), + tags::IMPLEMENTATION_CLASS_UID => { + PrimitiveValue::from(self.meta.implementation_class_uid.clone()) + } + tags::IMPLEMENTATION_VERSION_NAME => { + PrimitiveValue::from(self.meta.implementation_version_name.clone().unwrap()) + } + tags::PRIVATE_INFORMATION_CREATOR_UID => { + PrimitiveValue::from(self.meta.private_information_creator_uid.clone().unwrap()) + } + tags::PRIVATE_INFORMATION => { + PrimitiveValue::from(self.meta.private_information.clone().unwrap()) + } + _ => unreachable!(), + }) + } + + fn to_str<'b>(&'b self) -> std::result::Result, AttributeError> { + match Tag(0x0002, self.tag_e) { + tags::FILE_META_INFORMATION_GROUP_LENGTH => { + Ok(self.meta.information_group_length.to_string().into()) + } + tags::FILE_META_INFORMATION_VERSION => Ok(format!( + "{:02X}{:02X}", + self.meta.information_version[0], self.meta.information_version[1] + ) + .into()), + tags::MEDIA_STORAGE_SOP_CLASS_UID => { + Ok(Cow::Borrowed(self.meta.media_storage_sop_class_uid())) + } + tags::MEDIA_STORAGE_SOP_INSTANCE_UID => { + Ok(Cow::Borrowed(self.meta.media_storage_sop_instance_uid())) + } + tags::TRANSFER_SYNTAX_UID => Ok(Cow::Borrowed(self.meta.transfer_syntax())), + tags::IMPLEMENTATION_CLASS_UID => { + Ok(Cow::Borrowed(self.meta.implementation_class_uid())) + } + tags::IMPLEMENTATION_VERSION_NAME => Ok(self + .meta + .implementation_version_name + .as_deref() + .map(Cow::Borrowed) + .unwrap_or_default()), + tags::SOURCE_APPLICATION_ENTITY_TITLE => Ok(self + .meta + .source_application_entity_title + .as_deref() + .map(Cow::Borrowed) + .unwrap_or_default()), + tags::SENDING_APPLICATION_ENTITY_TITLE => Ok(self + .meta + .sending_application_entity_title + .as_deref() + .map(Cow::Borrowed) + .unwrap_or_default()), + tags::RECEIVING_APPLICATION_ENTITY_TITLE => Ok(self + .meta + .receiving_application_entity_title + .as_deref() + .map(Cow::Borrowed) + .unwrap_or_default()), + tags::PRIVATE_INFORMATION_CREATOR_UID => Ok(self + .meta + .private_information_creator_uid + .as_deref() + .map(|v| { + Cow::Borrowed(v.trim_end_matches(|c: char| c.is_whitespace() || c == '\0')) + }) + .unwrap_or_default()), + tags::PRIVATE_INFORMATION => Err(AttributeError::ConvertValue { + source: ConvertValueError { + cause: None, + original: ValueType::U8, + requested: "str", + }, + }), + _ => unreachable!(), + } + } + + fn item(&self, _index: u32) -> Result, AttributeError> { + Err(AttributeError::NotDataSet) + } + + fn num_items(&self) -> Option { + None + } + + fn fragment(&self, _index: u32) -> Result, AttributeError> { + Err(AttributeError::NotPixelData) + } + + fn num_fragments(&self) -> Option { + None + } +} + +impl DicomObject for FileMetaTable { + type Attribute<'a> = FileMetaAttribute<'a> + where + Self: 'a; + + fn get_opt<'a>( + &'a self, + tag: Tag, + ) -> std::result::Result>, crate::AccessError> { + // check that the attribute value is in the table, + // then return a suitable `FileMetaAttribute` + + if match tag { + // mandatory attributes + tags::FILE_META_INFORMATION_GROUP_LENGTH + | tags::FILE_META_INFORMATION_VERSION + | tags::MEDIA_STORAGE_SOP_CLASS_UID + | tags::MEDIA_STORAGE_SOP_INSTANCE_UID + | tags::TRANSFER_SYNTAX_UID + | tags::IMPLEMENTATION_CLASS_UID + | tags::IMPLEMENTATION_VERSION_NAME => true, + // optional attributes + tags::SOURCE_APPLICATION_ENTITY_TITLE + if self.source_application_entity_title.is_some() => + { + true + } + tags::SENDING_APPLICATION_ENTITY_TITLE + if self.sending_application_entity_title.is_some() => + { + true + } + tags::RECEIVING_APPLICATION_ENTITY_TITLE + if self.receiving_application_entity_title.is_some() => + { + true + } + tags::PRIVATE_INFORMATION_CREATOR_UID + if self.private_information_creator_uid.is_some() => + { + true + } + tags::PRIVATE_INFORMATION if self.private_information.is_some() => true, + _ => false, + } { + Ok(Some(FileMetaAttribute { + meta: self, + tag_e: tag.element(), + })) + } else { + Ok(None) + } + } + + fn get_by_name_opt<'a>( + &'a self, + name: &str, + ) -> std::result::Result>, crate::AccessByNameError> { + let tag = match name { + "FileMetaInformationGroupLength" => tags::FILE_META_INFORMATION_GROUP_LENGTH, + "FileMetaInformationVersion" => tags::FILE_META_INFORMATION_VERSION, + "MediaStorageSOPClassUID" => tags::MEDIA_STORAGE_SOP_CLASS_UID, + "MediaStorageSOPInstanceUID" => tags::MEDIA_STORAGE_SOP_INSTANCE_UID, + "TransferSyntaxUID" => tags::TRANSFER_SYNTAX_UID, + "ImplementationClassUID" => tags::IMPLEMENTATION_CLASS_UID, + "ImplementationVersionName" => tags::IMPLEMENTATION_VERSION_NAME, + "SourceApplicationEntityTitle" => tags::SOURCE_APPLICATION_ENTITY_TITLE, + "SendingApplicationEntityTitle" => tags::SENDING_APPLICATION_ENTITY_TITLE, + "ReceivingApplicationEntityTitle" => tags::RECEIVING_APPLICATION_ENTITY_TITLE, + "PrivateInformationCreatorUID" => tags::PRIVATE_INFORMATION_CREATOR_UID, + "PrivateInformation" => tags::PRIVATE_INFORMATION, + _ => return Ok(None), + }; + self.get_opt(tag) + .map_err(|_| crate::NoSuchAttributeNameSnafu { name }.build()) + } +} + impl ApplyOp for FileMetaTable { type Err = ApplyError; @@ -1382,4 +1689,44 @@ mod tests { "1.2.840.10008.5.1.4.1.1.7", ); } + + /// Can access file meta properties via the DicomObject trait + #[test] + fn dicom_object_api() { + use crate::{DicomAttribute as _, DicomObject as _}; + use dicom_dictionary_std::uids; + + let meta = FileMetaTableBuilder::new() + .transfer_syntax(uids::RLE_LOSSLESS) + .media_storage_sop_class_uid(uids::ENHANCED_MR_IMAGE_STORAGE) + .media_storage_sop_instance_uid("2.25.94766187067244888884745908966163363746") + .implementation_version_name("RUSTY_DICOM_269") + .build() + .unwrap(); + + assert_eq!( + meta.get(tags::TRANSFER_SYNTAX_UID) + .unwrap() + .to_str() + .unwrap(), + uids::RLE_LOSSLESS + ); + + let sop_class_uid = meta.get_opt(tags::MEDIA_STORAGE_SOP_CLASS_UID).unwrap(); + let sop_class_uid = sop_class_uid.as_ref().map(|v| v.to_str().unwrap()); + assert_eq!( + sop_class_uid.as_deref(), + Some(uids::ENHANCED_MR_IMAGE_STORAGE) + ); + + assert_eq!( + meta.get_by_name("MediaStorageSOPInstanceUID") + .unwrap() + .to_str() + .unwrap(), + "2.25.94766187067244888884745908966163363746" + ); + + assert!(meta.get_opt(tags::PRIVATE_INFORMATION).unwrap().is_none()); + } }