1#![allow(clippy::derive_partial_eq_without_eq)]
2pub mod collector;
189pub mod file;
190pub mod mem;
191pub mod meta;
192pub mod ops;
193pub mod tokens;
194
195pub use crate::collector::{DicomCollector, DicomCollectorOptions};
196pub use crate::file::{from_reader, open_file, OpenFileOptions};
197pub use crate::mem::InMemDicomObject;
198pub use crate::meta::{FileMetaTable, FileMetaTableBuilder};
199use dicom_core::ops::{AttributeSelector, AttributeSelectorStep};
200use dicom_core::value::{DicomValueType, ValueType};
201pub use dicom_core::Tag;
202use dicom_core::{DataDictionary, DicomValue, PrimitiveValue};
203use dicom_dictionary_std::uids;
204pub use dicom_dictionary_std::StandardDataDictionary;
205
206pub type DefaultDicomObject<D = StandardDataDictionary> = FileDicomObject<mem::InMemDicomObject<D>>;
208
209use dicom_core::header::{GroupNumber, HasLength};
210use dicom_encoding::adapters::{PixelDataObject, RawPixelData};
211use dicom_encoding::transfer_syntax::TransferSyntaxIndex;
212use dicom_encoding::Codec;
213use dicom_parser::dataset::{DataSetWriter, IntoTokens};
214use dicom_transfer_syntax_registry::TransferSyntaxRegistry;
215use itertools::Either;
216use meta::FileMetaAttribute;
217use smallvec::SmallVec;
218use snafu::{prelude::*, Backtrace};
219use std::borrow::Cow;
220use std::fs::File;
221use std::io::{BufWriter, Write};
222use std::path::Path;
223
224pub const IMPLEMENTATION_CLASS_UID: &str = "2.25.214312761802046835989399652652980912193";
231
232pub const IMPLEMENTATION_VERSION_NAME: &str = "DICOM-rs 0.9.0";
237
238#[derive(Debug, Snafu)]
240#[non_exhaustive]
241pub enum AttributeError {
242 #[snafu(whatever, display("{}", message))]
246 Custom {
247 message: String,
249 #[snafu(source(from(Box<dyn std::error::Error + Send + Sync + 'static>, Some)))]
251 source: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
252 },
253
254 ConvertValue {
256 source: dicom_core::value::ConvertValueError,
257 },
258
259 NotDataSet,
261
262 DataSetItemOutOfBounds,
264
265 NotPixelData,
267
268 FragmentOutOfBounds,
270}
271
272pub trait DicomAttribute: DicomValueType {
280 type Item<'a>: HasLength
282 where
283 Self: 'a;
284
285 type PixelData<'a>
287 where
288 Self: 'a;
289
290 fn to_primitive_value(&self) -> Result<PrimitiveValue, AttributeError>;
296
297 fn item(&self, index: u32) -> Result<Self::Item<'_>, AttributeError>;
302
303 fn num_items(&self) -> Option<u32>;
307
308 fn fragment(&self, index: u32) -> Result<Self::PixelData<'_>, AttributeError>;
313
314 fn num_fragments(&self) -> Option<u32> {
320 if DicomValueType::value_type(self) == ValueType::PixelSequence {
321 self.num_items()
322 } else {
323 None
324 }
325 }
326
327 fn to_str(&self) -> Result<Cow<'_, str>, AttributeError> {
330 Ok(Cow::Owned(self.to_primitive_value()?.to_str().to_string()))
331 }
332
333 fn to_u16(&self) -> Result<u16, AttributeError> {
336 self.to_primitive_value()?
337 .to_int()
338 .context(ConvertValueSnafu)
339 }
340
341 fn to_i32(&self) -> Result<i32, AttributeError> {
344 self.to_primitive_value()?
345 .to_int()
346 .context(ConvertValueSnafu)
347 }
348
349 fn to_u32(&self) -> Result<u32, AttributeError> {
352 self.to_primitive_value()?
353 .to_int()
354 .context(ConvertValueSnafu)
355 }
356
357 fn to_f32(&self) -> Result<f32, AttributeError> {
360 self.to_primitive_value()?
361 .to_float32()
362 .context(ConvertValueSnafu)
363 }
364
365 fn to_f64(&self) -> Result<f64, AttributeError> {
368 self.to_primitive_value()?
369 .to_float64()
370 .context(ConvertValueSnafu)
371 }
372
373 fn to_bytes(&self) -> Result<Cow<'_, [u8]>, AttributeError> {
376 Ok(Cow::Owned(self.to_primitive_value()?.to_bytes().to_vec()))
377 }
378}
379
380pub trait DicomObject {
442 type Attribute<'a>: DicomAttribute
449 where
450 Self: 'a;
451
452 type LeafAttribute<'a>: DicomAttribute
458 where
459 Self: 'a;
460
461 fn attr_opt(&self, tag: Tag) -> Result<Option<Self::Attribute<'_>>, AccessError>;
468
469 fn attr_by_name_opt(
480 &self,
481 name: &str,
482 ) -> Result<Option<Self::Attribute<'_>>, AccessByNameError>;
483
484 fn attr(&self, tag: Tag) -> Result<Self::Attribute<'_>, AccessError> {
490 self.attr_opt(tag)?
491 .context(NoSuchDataElementTagSnafu { tag })
492 }
493
494 fn at(
499 &self,
500 selector: impl Into<AttributeSelector>,
501 ) -> Result<Self::LeafAttribute<'_>, AtAccessError>;
502
503 fn attr_by_name(&self, name: &str) -> Result<Self::Attribute<'_>, AccessByNameError> {
514 self.attr_by_name_opt(name)?
515 .context(NoSuchAttributeNameSnafu { name })
516 }
517}
518
519impl<O, P> DicomAttribute for DicomValue<O, P>
520where
521 O: HasLength,
522 for<'a> &'a O: HasLength,
523{
524 type Item<'a>
525 = &'a O
526 where
527 Self: 'a,
528 O: 'a;
529 type PixelData<'a>
530 = &'a P
531 where
532 Self: 'a,
533 P: 'a;
534
535 #[inline]
536 fn to_primitive_value(&self) -> Result<PrimitiveValue, AttributeError> {
537 match self {
538 DicomValue::Primitive(value) => Ok(value.clone()),
539 _ => Err(AttributeError::ConvertValue {
540 source: dicom_core::value::ConvertValueError {
541 requested: "primitive",
542 original: self.value_type(),
543 cause: None,
544 },
545 }),
546 }
547 }
548
549 #[inline]
550 fn item(&self, index: u32) -> Result<&O, AttributeError> {
551 let items = self.items().context(NotDataSetSnafu)?;
552 items
553 .get(index as usize)
554 .context(DataSetItemOutOfBoundsSnafu)
555 }
556
557 #[inline]
558 fn num_items(&self) -> Option<u32> {
559 match self {
560 DicomValue::PixelSequence(seq) => Some(seq.fragments().len() as u32),
561 DicomValue::Sequence(seq) => Some(seq.multiplicity()),
562 _ => None,
563 }
564 }
565
566 #[inline]
567 fn fragment(&self, index: u32) -> Result<Self::PixelData<'_>, AttributeError> {
568 match self {
569 DicomValue::PixelSequence(seq) => Ok(seq
570 .fragments()
571 .get(index as usize)
572 .context(FragmentOutOfBoundsSnafu)?),
573 _ => Err(AttributeError::NotPixelData),
574 }
575 }
576
577 #[inline]
578 fn to_str(&self) -> Result<Cow<'_, str>, AttributeError> {
579 DicomValue::to_str(self).context(ConvertValueSnafu)
580 }
581
582 #[inline]
583 fn to_bytes(&self) -> Result<Cow<'_, [u8]>, AttributeError> {
584 DicomValue::to_bytes(self).context(ConvertValueSnafu)
585 }
586}
587
588impl<'b, O, P> DicomAttribute for &'b DicomValue<O, P>
589where
590 O: HasLength,
591 &'b O: HasLength,
592 O: Clone,
593 P: Clone,
594{
595 type Item<'a>
596 = &'b O
597 where
598 Self: 'a,
599 O: 'a;
600 type PixelData<'a>
601 = &'b P
602 where
603 Self: 'a,
604 P: 'a;
605
606 #[inline]
607 fn to_primitive_value(&self) -> Result<PrimitiveValue, AttributeError> {
608 match self {
609 DicomValue::Primitive(value) => Ok(value.clone()),
610 _ => Err(AttributeError::ConvertValue {
611 source: dicom_core::value::ConvertValueError {
612 requested: "primitive",
613 original: self.value_type(),
614 cause: None,
615 },
616 }),
617 }
618 }
619
620 #[inline]
621 fn item(&self, index: u32) -> Result<Self::Item<'_>, AttributeError> {
622 let items = self.items().context(NotDataSetSnafu)?;
623 items
624 .get(index as usize)
625 .context(DataSetItemOutOfBoundsSnafu)
626 }
627
628 #[inline]
629 fn num_items(&self) -> Option<u32> {
630 match self {
631 DicomValue::PixelSequence(seq) => Some(seq.fragments().len() as u32),
632 DicomValue::Sequence(seq) => Some(seq.multiplicity()),
633 _ => None,
634 }
635 }
636
637 #[inline]
638 fn fragment(&self, index: u32) -> Result<Self::PixelData<'_>, AttributeError> {
639 match self {
640 DicomValue::PixelSequence(seq) => seq
641 .fragments()
642 .get(index as usize)
643 .context(FragmentOutOfBoundsSnafu),
644 _ => Err(AttributeError::NotPixelData),
645 }
646 }
647
648 #[inline]
649 fn num_fragments(&self) -> Option<u32> {
650 match self {
651 DicomValue::PixelSequence(seq) => Some(seq.fragments().len() as u32),
652 _ => None,
653 }
654 }
655
656 #[inline]
657 fn to_str(&self) -> Result<Cow<'_, str>, AttributeError> {
658 DicomValue::to_str(self).context(ConvertValueSnafu)
659 }
660
661 #[inline]
662 fn to_bytes(&self) -> Result<Cow<'_, [u8]>, AttributeError> {
663 DicomValue::to_bytes(self).context(ConvertValueSnafu)
664 }
665}
666
667#[derive(Debug, Snafu)]
669#[non_exhaustive]
670pub enum ReadError {
671 #[snafu(display("Could not open file '{}'", filename.display()))]
672 OpenFile {
673 filename: std::path::PathBuf,
674 backtrace: Backtrace,
675 source: std::io::Error,
676 },
677 #[snafu(display("Could not read from file '{}'", filename.display()))]
678 ReadFile {
679 filename: std::path::PathBuf,
680 backtrace: Backtrace,
681 source: std::io::Error,
682 },
683 ReadPreambleBytes {
685 backtrace: Backtrace,
686 source: std::io::Error,
687 },
688 #[snafu(display("Could not parse meta group data set"))]
689 ParseMetaDataSet {
690 #[snafu(backtrace)]
691 source: crate::meta::Error,
692 },
693 #[snafu(display("Could not parse sop attribute"))]
694 ParseSopAttribute {
695 #[snafu(source(from(dicom_core::value::ConvertValueError, Box::from)))]
696 source: Box<dicom_core::value::ConvertValueError>,
697 backtrace: Backtrace,
698 },
699 #[snafu(display("Could not create data set parser"))]
700 CreateParser {
701 #[snafu(backtrace)]
702 source: dicom_parser::dataset::read::Error,
703 },
704 #[snafu(display("Could not read data set token"))]
705 ReadToken {
706 #[snafu(backtrace)]
707 source: dicom_parser::dataset::read::Error,
708 },
709 #[snafu(display("Missing element value after header token"))]
710 MissingElementValue { backtrace: Backtrace },
711 #[snafu(display("Unrecognized transfer syntax `{}`", uid))]
712 ReadUnrecognizedTransferSyntax { uid: String, backtrace: Backtrace },
713 #[snafu(display("Unsupported reading for transfer syntax `{}` ({})", uid, name))]
714 ReadUnsupportedTransferSyntax {
715 uid: &'static str,
716 name: &'static str,
717 backtrace: Backtrace,
718 },
719 #[snafu(display(
720 "Unsupported reading for transfer syntax `{uid}` ({name}, try enabling feature `{feature_name}`)"
721 ))]
722 ReadUnsupportedTransferSyntaxWithSuggestion {
723 uid: &'static str,
724 name: &'static str,
725 feature_name: &'static str,
726 backtrace: Backtrace,
727 },
728 #[snafu(display("Unexpected token {:?}", token))]
729 UnexpectedToken {
730 token: Box<dicom_parser::dataset::DataToken>,
731 backtrace: Backtrace,
732 },
733 #[snafu(display("Premature data set end"))]
734 PrematureEnd { backtrace: Backtrace },
735}
736
737#[derive(Debug, Snafu)]
739#[non_exhaustive]
740pub enum WriteError {
741 #[snafu(display("Could not write to file '{}'", filename.display()))]
742 WriteFile {
743 filename: std::path::PathBuf,
744 backtrace: Backtrace,
745 source: std::io::Error,
746 },
747 #[snafu(display("Could not write object preamble"))]
748 WritePreamble {
749 backtrace: Backtrace,
750 source: std::io::Error,
751 },
752 #[snafu(display("Could not write magic code"))]
753 WriteMagicCode {
754 backtrace: Backtrace,
755 source: std::io::Error,
756 },
757 #[snafu(display("Could not create data set printer"))]
758 CreatePrinter {
759 #[snafu(backtrace)]
760 source: dicom_parser::dataset::write::Error,
761 },
762 #[snafu(display("Could not print meta group data set"))]
763 PrintMetaDataSet {
764 #[snafu(backtrace)]
765 source: crate::meta::Error,
766 },
767 #[snafu(display("Could not print data set"))]
768 PrintDataSet {
769 #[snafu(backtrace)]
770 source: dicom_parser::dataset::write::Error,
771 },
772 #[snafu(display("Unrecognized transfer syntax `{uid}`"))]
773 WriteUnrecognizedTransferSyntax { uid: String, backtrace: Backtrace },
774 #[snafu(display("Unsupported transfer syntax `{uid}` ({name})"))]
775 WriteUnsupportedTransferSyntax {
776 uid: &'static str,
777 name: &'static str,
778 backtrace: Backtrace,
779 },
780 #[snafu(display(
781 "Unsupported transfer syntax `{uid}` ({name}, try enabling feature `{feature_name}`)"
782 ))]
783 WriteUnsupportedTransferSyntaxWithSuggestion {
784 uid: &'static str,
785 name: &'static str,
786 feature_name: &'static str,
787 backtrace: Backtrace,
788 },
789}
790
791#[derive(Debug, Snafu)]
793#[non_exhaustive]
794pub enum PrivateElementError {
795 #[snafu(display("Group number must be odd, found {:#06x}", group))]
797 InvalidGroup { group: GroupNumber },
798 #[snafu(display("Private creator {} not found in group {:#06x}", creator, group))]
800 PrivateCreatorNotFound { creator: String, group: GroupNumber },
801 #[snafu(display(
803 "Private Creator {} found in group {:#06x}, but elem {:#06x} not found",
804 creator,
805 group,
806 elem
807 ))]
808 ElementNotFound {
809 creator: String,
810 group: GroupNumber,
811 elem: u8,
812 },
813 #[snafu(display("No space available in group {:#06x}", group))]
815 NoSpace { group: GroupNumber },
816}
817
818#[derive(Debug, Snafu)]
820#[non_exhaustive]
821pub enum AccessError {
822 #[snafu(display("No such data element with tag {}", tag))]
823 NoSuchDataElementTag { tag: Tag, backtrace: Backtrace },
824}
825
826impl AccessError {
827 pub fn into_access_by_name(self, alias: impl Into<String>) -> AccessByNameError {
828 match self {
829 AccessError::NoSuchDataElementTag { tag, backtrace } => {
830 AccessByNameError::NoSuchDataElementAlias {
831 tag,
832 alias: alias.into(),
833 backtrace,
834 }
835 }
836 }
837 }
838}
839
840#[derive(Debug, Snafu)]
844#[non_exhaustive]
845#[snafu(visibility(pub(crate)))]
846pub enum AtAccessError {
847 MissingSequence {
849 selector: AttributeSelector,
850 step_index: u32,
851 },
852 NotASequence {
854 selector: AttributeSelector,
855 step_index: u32,
856 },
857 MissingLeafElement { selector: AttributeSelector },
859}
860
861#[derive(Debug, Snafu)]
867pub enum AccessByNameError {
868 #[snafu(display("No such data element {} (with tag {})", alias, tag))]
869 NoSuchDataElementAlias {
870 tag: Tag,
871 alias: String,
872 backtrace: Backtrace,
873 },
874
875 #[snafu(display("Unknown data attribute named `{}`", name))]
877 NoSuchAttributeName { name: String, backtrace: Backtrace },
878}
879
880#[derive(Debug, Snafu)]
881#[non_exhaustive]
882pub enum WithMetaError {
883 BuildMetaTable {
885 #[snafu(backtrace)]
886 source: crate::meta::Error,
887 },
888 PrepareMetaTable {
890 #[snafu(source(from(dicom_core::value::ConvertValueError, Box::from)))]
891 source: Box<dicom_core::value::ConvertValueError>,
892 backtrace: Backtrace,
893 },
894}
895
896#[derive(Debug, Clone, PartialEq)]
910pub struct FileDicomObject<O> {
911 meta: FileMetaTable,
912 obj: O,
913}
914
915impl<O> FileDicomObject<O> {
916 pub fn meta(&self) -> &FileMetaTable {
918 &self.meta
919 }
920
921 pub fn meta_mut(&mut self) -> &mut FileMetaTable {
932 &mut self.meta
933 }
934
935 pub fn update_meta(&mut self, f: impl FnOnce(&mut FileMetaTable)) {
941 f(&mut self.meta);
942 self.meta.update_information_group_length();
943 }
944
945 pub fn into_inner(self) -> O {
947 self.obj
948 }
949}
950
951impl<O> FileDicomObject<O>
952where
953 for<'a> &'a O: IntoTokens,
954{
955 pub fn write_to_file<P: AsRef<Path>>(&self, path: P) -> Result<(), WriteError> {
960 let path = path.as_ref();
961 let file = File::create(path).context(WriteFileSnafu { filename: path })?;
962 let mut to = BufWriter::new(file);
963
964 to.write_all(&[0_u8; 128][..])
966 .context(WriteFileSnafu { filename: path })?;
967
968 to.write_all(b"DICM")
970 .context(WriteFileSnafu { filename: path })?;
971
972 self.meta.write(&mut to).context(PrintMetaDataSetSnafu)?;
974
975 self.write_dataset_impl(to)
976 }
977
978 pub fn write_all(&self, to: impl Write) -> Result<(), WriteError> {
983 let mut to = BufWriter::new(to);
984
985 to.write_all(&[0_u8; 128][..]).context(WritePreambleSnafu)?;
987
988 to.write_all(b"DICM").context(WriteMagicCodeSnafu)?;
990
991 self.meta.write(&mut to).context(PrintMetaDataSetSnafu)?;
993
994 self.write_dataset_impl(to)
995 }
996
997 pub fn write_meta<W: Write>(&self, to: W) -> Result<(), WriteError> {
1001 self.meta.write(to).context(PrintMetaDataSetSnafu)
1002 }
1003
1004 pub fn write_dataset<W: Write>(&self, to: W) -> Result<(), WriteError> {
1009 let to = BufWriter::new(to);
1010
1011 self.write_dataset_impl(to)
1012 }
1013
1014 fn write_dataset_impl(&self, to: impl Write) -> Result<(), WriteError> {
1019 let ts_uid = self.meta.transfer_syntax();
1020 let ts = if let Some(ts) = TransferSyntaxRegistry.get(ts_uid) {
1022 ts
1023 } else {
1024 return WriteUnrecognizedTransferSyntaxSnafu {
1025 uid: ts_uid.to_string(),
1026 }
1027 .fail();
1028 };
1029 match ts.codec() {
1030 Codec::Dataset(Some(adapter)) => {
1031 let adapter = adapter.adapt_writer(Box::new(to));
1032 let mut dset_writer =
1033 DataSetWriter::with_ts(adapter, ts).context(CreatePrinterSnafu)?;
1034
1035 dset_writer
1037 .write_sequence((&self.obj).into_tokens())
1038 .context(PrintDataSetSnafu)?;
1039
1040 dset_writer.flush().context(PrintDataSetSnafu)?;
1041
1042 Ok(())
1043 }
1044 Codec::Dataset(None) => {
1045 if ts_uid == uids::DEFLATED_EXPLICIT_VR_LITTLE_ENDIAN
1047 || ts_uid == uids::JPIP_REFERENCED_DEFLATE
1048 || ts_uid == uids::JPIPHTJ2K_REFERENCED_DEFLATE
1049 {
1050 return WriteUnsupportedTransferSyntaxWithSuggestionSnafu {
1051 uid: ts.uid(),
1052 name: ts.name(),
1053 feature_name: "dicom-transfer-syntax-registry/deflate",
1054 }
1055 .fail();
1056 }
1057 WriteUnsupportedTransferSyntaxSnafu {
1058 uid: ts.uid(),
1059 name: ts.name(),
1060 }
1061 .fail()
1062 }
1063 Codec::None | Codec::EncapsulatedPixelData(..) => {
1064 let mut dset_writer = DataSetWriter::with_ts(to, ts).context(CreatePrinterSnafu)?;
1066
1067 dset_writer
1069 .write_sequence((&self.obj).into_tokens())
1070 .context(PrintDataSetSnafu)?;
1071 dset_writer.flush().context(PrintDataSetSnafu)?;
1072
1073 Ok(())
1074 }
1075 }
1076 }
1077}
1078
1079impl<O> ::std::ops::Deref for FileDicomObject<O> {
1080 type Target = O;
1081
1082 fn deref(&self) -> &Self::Target {
1083 &self.obj
1084 }
1085}
1086
1087impl<O> ::std::ops::DerefMut for FileDicomObject<O> {
1088 fn deref_mut(&mut self) -> &mut Self::Target {
1089 &mut self.obj
1090 }
1091}
1092
1093impl<L, R> DicomAttribute for Either<L, R>
1094where
1095 L: DicomAttribute,
1096 R: DicomAttribute,
1097{
1098 type Item<'a>
1099 = Either<L::Item<'a>, R::Item<'a>>
1100 where
1101 Self: 'a;
1102 type PixelData<'a>
1103 = Either<L::PixelData<'a>, R::PixelData<'a>>
1104 where
1105 Self: 'a;
1106
1107 fn to_primitive_value(&self) -> Result<PrimitiveValue, AttributeError> {
1108 match self {
1109 Either::Left(l) => l.to_primitive_value(),
1110 Either::Right(r) => r.to_primitive_value(),
1111 }
1112 }
1113
1114 fn item(&self, index: u32) -> Result<Self::Item<'_>, AttributeError> {
1115 match self {
1116 Either::Left(l) => l.item(index).map(Either::Left),
1117 Either::Right(r) => r.item(index).map(Either::Right),
1118 }
1119 }
1120
1121 fn num_items(&self) -> Option<u32> {
1122 match self {
1123 Either::Left(l) => l.num_items(),
1124 Either::Right(r) => r.num_items(),
1125 }
1126 }
1127
1128 fn fragment(&self, index: u32) -> Result<Self::PixelData<'_>, AttributeError> {
1129 match self {
1130 Either::Left(l) => l.fragment(index).map(Either::Left),
1131 Either::Right(r) => r.fragment(index).map(Either::Right),
1132 }
1133 }
1134}
1135
1136impl<O> DicomObject for FileDicomObject<O>
1137where
1138 O: DicomObject,
1139{
1140 type Attribute<'a>
1141 = Either<FileMetaAttribute<'a>, O::Attribute<'a>>
1142 where
1143 Self: 'a,
1144 O: 'a;
1145
1146 type LeafAttribute<'a>
1147 = Either<FileMetaAttribute<'a>, O::LeafAttribute<'a>>
1148 where
1149 Self: 'a,
1150 O: 'a;
1151
1152 #[inline]
1153 fn attr_opt(&self, tag: Tag) -> Result<Option<Self::Attribute<'_>>, AccessError> {
1154 match tag {
1155 Tag(0x0002, _) => {
1156 let attr = self.meta.attr_opt(tag)?;
1157 Ok(attr.map(Either::Left))
1158 }
1159 _ => {
1160 let attr = self.obj.attr_opt(tag)?;
1161 Ok(attr.map(Either::Right))
1162 }
1163 }
1164 }
1165
1166 #[inline]
1167 fn attr_by_name_opt(
1168 &self,
1169 name: &str,
1170 ) -> Result<Option<Self::Attribute<'_>>, AccessByNameError> {
1171 match name {
1172 "FileMetaInformationGroupLength"
1173 | "FileMetaInformationVersion"
1174 | "MediaStorageSOPClassUID"
1175 | "MediaStorageSOPInstanceUID"
1176 | "TransferSyntaxUID"
1177 | "ImplementationClassUID"
1178 | "ImplementationVersionName"
1179 | "SourceApplicationEntityTitle"
1180 | "SendingApplicationEntityTitle"
1181 | "ReceivingApplicationEntityTitle"
1182 | "PrivateInformationCreatorUID"
1183 | "PrivateInformation" => {
1184 let attr = self.meta.attr_by_name_opt(name)?;
1185 Ok(attr.map(Either::Left))
1186 }
1187 _ => {
1188 let attr = self.obj.attr_by_name_opt(name)?;
1189 Ok(attr.map(Either::Right))
1190 }
1191 }
1192 }
1193
1194 #[inline]
1195 fn attr(&self, tag: Tag) -> Result<Self::Attribute<'_>, AccessError> {
1196 match tag {
1197 Tag(0x0002, _) => {
1198 let attr = self.meta.attr(tag)?;
1199 Ok(Either::Left(attr))
1200 }
1201 _ => {
1202 let attr = self.obj.attr(tag)?;
1203 Ok(Either::Right(attr))
1204 }
1205 }
1206 }
1207
1208 fn at(
1209 &self,
1210 selector: impl Into<AttributeSelector>,
1211 ) -> Result<Self::LeafAttribute<'_>, AtAccessError> {
1212 let selector: AttributeSelector = selector.into();
1213 match selector.first_step() {
1214 AttributeSelectorStep::Tag(tag @ Tag(0x0002, _)) => {
1215 let attr = self
1216 .meta
1217 .attr(*tag)
1218 .map_err(|_| AtAccessError::MissingLeafElement { selector })?;
1219 Ok(Either::Left(attr))
1220 }
1221 _ => {
1222 let attr = self.obj.at(selector)?;
1223 Ok(Either::Right(attr))
1224 }
1225 }
1226 }
1227}
1228
1229impl<'s, O: 's> DicomObject for &'s FileDicomObject<O>
1230where
1231 O: DicomObject,
1232{
1233 type Attribute<'a>
1234 = Either<FileMetaAttribute<'a>, O::Attribute<'a>>
1235 where
1236 Self: 'a,
1237 O: 'a;
1238
1239 type LeafAttribute<'a>
1240 = Either<FileMetaAttribute<'a>, O::LeafAttribute<'a>>
1241 where
1242 Self: 'a,
1243 O: 'a;
1244
1245 #[inline]
1246 fn attr_opt(&self, tag: Tag) -> Result<Option<Self::Attribute<'_>>, AccessError> {
1247 (**self).attr_opt(tag)
1248 }
1249
1250 #[inline]
1251 fn attr_by_name_opt(
1252 &self,
1253 name: &str,
1254 ) -> Result<Option<Self::Attribute<'_>>, AccessByNameError> {
1255 (**self).attr_by_name_opt(name)
1256 }
1257
1258 #[inline]
1259 fn attr(&self, tag: Tag) -> Result<Self::Attribute<'_>, AccessError> {
1260 (**self).attr(tag)
1261 }
1262
1263 #[inline]
1264 fn attr_by_name(&self, name: &str) -> Result<Self::Attribute<'_>, AccessByNameError> {
1265 (**self).attr_by_name(name)
1266 }
1267
1268 #[inline]
1269 fn at(
1270 &self,
1271 selector: impl Into<AttributeSelector>,
1272 ) -> Result<Self::LeafAttribute<'_>, AtAccessError> {
1273 (**self).at(selector)
1274 }
1275}
1276
1277impl<O> IntoIterator for FileDicomObject<O>
1285where
1286 O: IntoIterator,
1287{
1288 type Item = <O as IntoIterator>::Item;
1289 type IntoIter = <O as IntoIterator>::IntoIter;
1290
1291 fn into_iter(self) -> Self::IntoIter {
1292 self.obj.into_iter()
1293 }
1294}
1295
1296impl<'a, O> IntoIterator for &'a FileDicomObject<O>
1303where
1304 &'a O: IntoIterator,
1305{
1306 type Item = <&'a O as IntoIterator>::Item;
1307 type IntoIter = <&'a O as IntoIterator>::IntoIter;
1308
1309 fn into_iter(self) -> Self::IntoIter {
1310 (&self.obj).into_iter()
1311 }
1312}
1313
1314impl<D> PixelDataObject for FileDicomObject<InMemDicomObject<D>>
1316where
1317 D: DataDictionary + Clone,
1318{
1319 fn transfer_syntax_uid(&self) -> &str {
1320 self.meta.transfer_syntax()
1321 }
1322
1323 fn rows(&self) -> Option<u16> {
1325 (**self)
1326 .get(dicom_dictionary_std::tags::ROWS)?
1327 .uint16()
1328 .ok()
1329 }
1330
1331 fn cols(&self) -> Option<u16> {
1333 (**self)
1334 .get(dicom_dictionary_std::tags::COLUMNS)?
1335 .uint16()
1336 .ok()
1337 }
1338
1339 fn samples_per_pixel(&self) -> Option<u16> {
1341 (**self)
1342 .get(dicom_dictionary_std::tags::SAMPLES_PER_PIXEL)?
1343 .uint16()
1344 .ok()
1345 }
1346
1347 fn bits_allocated(&self) -> Option<u16> {
1349 (**self)
1350 .get(dicom_dictionary_std::tags::BITS_ALLOCATED)?
1351 .uint16()
1352 .ok()
1353 }
1354
1355 fn bits_stored(&self) -> Option<u16> {
1357 (**self)
1358 .get(dicom_dictionary_std::tags::BITS_STORED)?
1359 .uint16()
1360 .ok()
1361 }
1362
1363 fn photometric_interpretation(&self) -> Option<&str> {
1364 (**self)
1365 .get(dicom_dictionary_std::tags::PHOTOMETRIC_INTERPRETATION)?
1366 .string()
1367 .ok()
1368 .map(|s| s.trim_end())
1369 }
1370
1371 fn number_of_frames(&self) -> Option<u32> {
1373 (**self)
1374 .get(dicom_dictionary_std::tags::NUMBER_OF_FRAMES)?
1375 .to_int()
1376 .ok()
1377 }
1378
1379 fn number_of_fragments(&self) -> Option<u32> {
1381 let pixel_data = (**self).get(dicom_dictionary_std::tags::PIXEL_DATA)?;
1382 match pixel_data.value() {
1383 DicomValue::Primitive(_p) => Some(1),
1384 DicomValue::PixelSequence(v) => Some(v.fragments().len() as u32),
1385 DicomValue::Sequence(..) => None,
1386 }
1387 }
1388
1389 fn fragment(&self, fragment: usize) -> Option<Cow<'_, [u8]>> {
1396 let pixel_data = (**self).get(dicom_dictionary_std::tags::PIXEL_DATA)?;
1397 match pixel_data.value() {
1398 DicomValue::PixelSequence(v) => Some(Cow::Borrowed(v.fragments()[fragment].as_ref())),
1399 DicomValue::Primitive(p) if fragment == 0 => Some(p.to_bytes()),
1400 _ => None,
1401 }
1402 }
1403
1404 fn offset_table(&self) -> Option<Cow<'_, [u32]>> {
1405 let pixel_data = (**self).get(dicom_dictionary_std::tags::PIXEL_DATA)?;
1406 match pixel_data.value() {
1407 DicomValue::Primitive(_) => None,
1408 DicomValue::Sequence(_) => None,
1409 DicomValue::PixelSequence(seq) => Some(Cow::from(seq.offset_table())),
1410 }
1411 }
1412
1413 fn raw_pixel_data(&self) -> Option<RawPixelData> {
1417 let pixel_data = (**self).get(dicom_dictionary_std::tags::PIXEL_DATA)?;
1418 match pixel_data.value() {
1419 DicomValue::Primitive(p) => {
1420 let fragment = p.to_bytes().to_vec();
1422 let mut fragments = SmallVec::new();
1423 fragments.push(fragment);
1424 Some(RawPixelData {
1425 fragments,
1426 offset_table: SmallVec::new(),
1427 })
1428 }
1429 DicomValue::PixelSequence(v) => {
1430 let (offset_table, fragments) = v.clone().into_parts();
1431 Some(RawPixelData {
1432 fragments,
1433 offset_table,
1434 })
1435 }
1436 DicomValue::Sequence(..) => None,
1437 }
1438 }
1439}
1440
1441#[cfg(test)]
1442mod tests {
1443 use dicom_core::{DataElement, PrimitiveValue, VR};
1444 use dicom_dictionary_std::{tags, uids};
1445
1446 use crate::meta::FileMetaTableBuilder;
1447 use crate::{AccessError, DicomAttribute as _, DicomObject, FileDicomObject, InMemDicomObject};
1448
1449 fn assert_type_not_too_large<T>(max_size: usize) {
1450 let size = std::mem::size_of::<T>();
1451 if size > max_size {
1452 panic!(
1453 "Type {} of byte size {} exceeds acceptable size {}",
1454 std::any::type_name::<T>(),
1455 size,
1456 max_size
1457 );
1458 }
1459 }
1460
1461 #[test]
1462 fn errors_not_too_large() {
1463 assert_type_not_too_large::<AccessError>(64);
1464 }
1465
1466 #[test]
1467 fn smoke_test() {
1468 const FILE_NAME: &str = ".smoke-test.dcm";
1469
1470 let meta = FileMetaTableBuilder::new()
1471 .transfer_syntax(
1472 dicom_transfer_syntax_registry::entries::EXPLICIT_VR_LITTLE_ENDIAN.uid(),
1473 )
1474 .media_storage_sop_class_uid("1.2.840.10008.5.1.4.1.1.1")
1475 .media_storage_sop_instance_uid("1.2.3.456")
1476 .implementation_class_uid("1.2.345.6.7890.1.234")
1477 .build()
1478 .unwrap();
1479 let obj = FileDicomObject::new_empty_with_meta(meta);
1480
1481 obj.write_to_file(FILE_NAME).unwrap();
1482
1483 let obj2 = FileDicomObject::open_file(FILE_NAME).unwrap();
1484
1485 assert_eq!(obj, obj2);
1486
1487 let _ = std::fs::remove_file(FILE_NAME);
1488 }
1489
1490 #[test]
1493 fn file_dicom_object_can_use_inner() {
1494 let mut obj = InMemDicomObject::new_empty();
1495
1496 obj.put(DataElement::new(
1497 dicom_dictionary_std::tags::PATIENT_NAME,
1498 VR::PN,
1499 PrimitiveValue::from("John Doe"),
1500 ));
1501
1502 let mut obj = obj
1503 .with_meta(
1504 FileMetaTableBuilder::new()
1505 .media_storage_sop_class_uid("1.2.840.10008.5.1.4.1.1.7")
1506 .media_storage_sop_instance_uid("1.2.23456789")
1507 .transfer_syntax("1.2.840.10008.1.2.1"),
1508 )
1509 .unwrap();
1510
1511 assert_eq!(
1513 obj.element(tags::PATIENT_NAME)
1514 .unwrap()
1515 .value()
1516 .to_str()
1517 .unwrap(),
1518 "John Doe",
1519 );
1520
1521 assert_eq!(
1523 DicomObject::attr(&obj, tags::PATIENT_NAME)
1524 .unwrap()
1525 .to_str()
1526 .unwrap(),
1527 "John Doe",
1528 );
1529
1530 obj.take_element(tags::PATIENT_NAME).unwrap();
1532
1533 assert!(matches!(
1534 obj.element(tags::PATIENT_NAME),
1535 Err(AccessError::NoSuchDataElementTag { .. }),
1536 ));
1537 }
1538
1539 #[test]
1540 fn file_dicom_object_can_iterate_over_elements() {
1541 let mut obj = InMemDicomObject::new_empty();
1542
1543 obj.put(DataElement::new(
1544 dicom_dictionary_std::tags::PATIENT_NAME,
1545 VR::PN,
1546 PrimitiveValue::from("John Doe"),
1547 ));
1548 obj.put(DataElement::new(
1549 dicom_dictionary_std::tags::SOP_INSTANCE_UID,
1550 VR::PN,
1551 PrimitiveValue::from("1.2.987654321"),
1552 ));
1553
1554 let obj = obj
1555 .with_meta(
1556 FileMetaTableBuilder::new()
1557 .media_storage_sop_class_uid("1.2.840.10008.5.1.4.1.1.7")
1558 .media_storage_sop_instance_uid("1.2.987654321")
1559 .transfer_syntax("1.2.840.10008.1.2.1"),
1560 )
1561 .unwrap();
1562
1563 let mut iter = (&obj).into_iter();
1565 assert_eq!(
1566 iter.next().unwrap().header().tag,
1567 dicom_dictionary_std::tags::SOP_INSTANCE_UID
1568 );
1569 assert_eq!(
1570 iter.next().unwrap().header().tag,
1571 dicom_dictionary_std::tags::PATIENT_NAME
1572 );
1573 assert_eq!(iter.next(), None);
1574
1575 let mut iter = obj.into_iter();
1577 assert_eq!(
1578 iter.next().unwrap().header().tag,
1579 dicom_dictionary_std::tags::SOP_INSTANCE_UID
1580 );
1581 assert_eq!(
1582 iter.next().unwrap().header().tag,
1583 dicom_dictionary_std::tags::PATIENT_NAME
1584 );
1585 assert_eq!(iter.next(), None);
1586 }
1587
1588 #[test]
1590 pub fn file_dicom_can_update_meta() {
1591 let meta = FileMetaTableBuilder::new()
1592 .transfer_syntax(
1593 dicom_transfer_syntax_registry::entries::EXPLICIT_VR_LITTLE_ENDIAN.uid(),
1594 )
1595 .media_storage_sop_class_uid("1.2.840.10008.5.1.4.1.1.1")
1596 .media_storage_sop_instance_uid("2.25.280986007517028771599125034987786349815")
1597 .implementation_class_uid("1.2.345.6.7890.1.234")
1598 .build()
1599 .unwrap();
1600 let mut obj = FileDicomObject::new_empty_with_meta(meta);
1601
1602 obj.update_meta(|meta| {
1603 meta.receiving_application_entity_title = Some("SOMETHING".to_string());
1604 });
1605
1606 assert_eq!(
1607 obj.meta().receiving_application_entity_title.as_deref(),
1608 Some("SOMETHING"),
1609 );
1610 }
1611
1612 #[test]
1613 fn dicom_object_api_on_file_dicom_object() {
1614 use crate::{DicomAttribute as _, DicomObject as _};
1615
1616 let meta = FileMetaTableBuilder::new()
1617 .transfer_syntax(uids::RLE_LOSSLESS)
1618 .media_storage_sop_class_uid(uids::ENHANCED_MR_IMAGE_STORAGE)
1619 .media_storage_sop_instance_uid("2.25.94766187067244888884745908966163363746")
1620 .build()
1621 .unwrap();
1622 let obj = FileDicomObject::new_empty_with_meta(meta);
1623
1624 assert_eq!(
1625 obj.attr(tags::TRANSFER_SYNTAX_UID)
1626 .unwrap()
1627 .to_str()
1628 .unwrap(),
1629 uids::RLE_LOSSLESS
1630 );
1631
1632 let sop_class_uid = obj.attr_opt(tags::MEDIA_STORAGE_SOP_CLASS_UID).unwrap();
1633 let sop_class_uid = sop_class_uid.as_ref().map(|v| v.to_str().unwrap());
1634 assert_eq!(
1635 sop_class_uid.as_deref(),
1636 Some(uids::ENHANCED_MR_IMAGE_STORAGE)
1637 );
1638
1639 assert_eq!(
1640 obj.attr_by_name("MediaStorageSOPInstanceUID")
1641 .unwrap()
1642 .to_str()
1643 .unwrap(),
1644 "2.25.94766187067244888884745908966163363746"
1645 );
1646
1647 assert_eq!(
1648 obj.at(tags::MEDIA_STORAGE_SOP_INSTANCE_UID)
1649 .unwrap()
1650 .to_str()
1651 .unwrap(),
1652 "2.25.94766187067244888884745908966163363746"
1653 );
1654 }
1655
1656 #[test]
1657 fn operations_api_on_primitive_values() {
1658 use crate::DicomAttribute;
1659 use dicom_dictionary_std::tags;
1660
1661 let obj = InMemDicomObject::from_element_iter([
1662 DataElement::new(tags::PATIENT_NAME, VR::PN, PrimitiveValue::from("Doe^John")),
1663 DataElement::new(tags::INSTANCE_NUMBER, VR::IS, PrimitiveValue::from("5")),
1664 ]);
1665
1666 let patient_name = obj.get(tags::PATIENT_NAME).unwrap().value();
1667
1668 assert_eq!(&DicomAttribute::to_str(patient_name).unwrap(), "Doe^John");
1671
1672 let instance_number = obj.get(tags::INSTANCE_NUMBER).unwrap().value();
1675 assert_eq!(DicomAttribute::to_u32(instance_number).unwrap(), 5);
1676
1677 assert_eq!(patient_name.num_items(), None);
1680 assert_eq!(patient_name.num_fragments(), None);
1681 assert!(matches!(
1682 patient_name.item(0),
1683 Err(crate::AttributeError::NotDataSet)
1684 ));
1685
1686 assert_eq!(instance_number.num_items(), None);
1687 assert_eq!(instance_number.num_fragments(), None);
1688 assert!(matches!(
1689 patient_name.item(0),
1690 Err(crate::AttributeError::NotDataSet)
1691 ));
1692 assert!(matches!(
1693 instance_number.item(0),
1694 Err(crate::AttributeError::NotDataSet)
1695 ));
1696 }
1697}