1#![allow(clippy::derive_partial_eq_without_eq)]
2pub mod file;
145pub mod mem;
146pub mod meta;
147pub mod ops;
148pub mod collector;
149pub mod tokens;
150
151pub use crate::file::{from_reader, open_file, OpenFileOptions};
152pub use crate::mem::InMemDicomObject;
153pub use crate::collector::{DicomCollectorOptions, DicomCollector};
154pub use crate::meta::{FileMetaTable, FileMetaTableBuilder};
155use dicom_core::ops::AttributeSelector;
156use dicom_core::DataDictionary;
157pub use dicom_core::Tag;
158pub use dicom_dictionary_std::StandardDataDictionary;
159
160pub type DefaultDicomObject<D = StandardDataDictionary> = FileDicomObject<mem::InMemDicomObject<D>>;
162
163use dicom_core::header::{GroupNumber, Header};
164use dicom_encoding::adapters::{PixelDataObject, RawPixelData};
165use dicom_encoding::transfer_syntax::TransferSyntaxIndex;
166use dicom_parser::dataset::{DataSetWriter, IntoTokens};
167use dicom_transfer_syntax_registry::TransferSyntaxRegistry;
168use smallvec::SmallVec;
169use snafu::{Backtrace, OptionExt, ResultExt, Snafu};
170use std::borrow::Cow;
171use std::fs::File;
172use std::io::{BufWriter, Write};
173use std::path::Path;
174
175pub const IMPLEMENTATION_CLASS_UID: &str = "2.25.262086406829110419931297894772577063974";
182
183pub const IMPLEMENTATION_VERSION_NAME: &str = "DICOM-rs 0.8.1";
188
189pub trait DicomObject {
196 type Element: Header;
197
198 fn element(&self, tag: Tag) -> Result<Self::Element, AccessError>;
200
201 fn element_by_name(&self, name: &str) -> Result<Self::Element, AccessByNameError>;
203
204 fn meta(&self) -> Option<&FileMetaTable> {
210 None
211 }
212}
213
214#[derive(Debug, Snafu)]
216#[non_exhaustive]
217pub enum ReadError {
218 #[snafu(display("Could not open file '{}'", filename.display()))]
219 OpenFile {
220 filename: std::path::PathBuf,
221 backtrace: Backtrace,
222 source: std::io::Error,
223 },
224 #[snafu(display("Could not read from file '{}'", filename.display()))]
225 ReadFile {
226 filename: std::path::PathBuf,
227 backtrace: Backtrace,
228 source: std::io::Error,
229 },
230 ReadPreambleBytes {
232 backtrace: Backtrace,
233 source: std::io::Error,
234 },
235 #[snafu(display("Could not parse meta group data set"))]
236 ParseMetaDataSet {
237 #[snafu(backtrace)]
238 source: crate::meta::Error,
239 },
240 #[snafu(display("Could not parse sop attribute"))]
241 ParseSopAttribute {
242 #[snafu(source(from(dicom_core::value::ConvertValueError, Box::from)))]
243 source: Box<dicom_core::value::ConvertValueError>,
244 backtrace: Backtrace,
245 },
246 #[snafu(display("Could not create data set parser"))]
247 CreateParser {
248 #[snafu(backtrace)]
249 source: dicom_parser::dataset::read::Error,
250 },
251 #[snafu(display("Could not read data set token"))]
252 ReadToken {
253 #[snafu(backtrace)]
254 source: dicom_parser::dataset::read::Error,
255 },
256 #[snafu(display("Missing element value after header token"))]
257 MissingElementValue { backtrace: Backtrace },
258 #[snafu(display("Unsupported transfer syntax `{}`", uid))]
259 ReadUnsupportedTransferSyntax { uid: String, backtrace: Backtrace },
260 #[snafu(display("Unexpected token {:?}", token))]
261 UnexpectedToken {
262 token: Box<dicom_parser::dataset::DataToken>,
263 backtrace: Backtrace,
264 },
265 #[snafu(display("Premature data set end"))]
266 PrematureEnd { backtrace: Backtrace },
267}
268
269#[derive(Debug, Snafu)]
271#[non_exhaustive]
272pub enum WriteError {
273 #[snafu(display("Could not write to file '{}'", filename.display()))]
274 WriteFile {
275 filename: std::path::PathBuf,
276 backtrace: Backtrace,
277 source: std::io::Error,
278 },
279 #[snafu(display("Could not write object preamble"))]
280 WritePreamble {
281 backtrace: Backtrace,
282 source: std::io::Error,
283 },
284 #[snafu(display("Could not write magic code"))]
285 WriteMagicCode {
286 backtrace: Backtrace,
287 source: std::io::Error,
288 },
289 #[snafu(display("Could not create data set printer"))]
290 CreatePrinter {
291 #[snafu(backtrace)]
292 source: dicom_parser::dataset::write::Error,
293 },
294 #[snafu(display("Could not print meta group data set"))]
295 PrintMetaDataSet {
296 #[snafu(backtrace)]
297 source: crate::meta::Error,
298 },
299 #[snafu(display("Could not print data set"))]
300 PrintDataSet {
301 #[snafu(backtrace)]
302 source: dicom_parser::dataset::write::Error,
303 },
304 #[snafu(display("Unsupported transfer syntax `{}`", uid))]
305 WriteUnsupportedTransferSyntax { uid: String, backtrace: Backtrace },
306}
307
308#[derive(Debug, Snafu)]
310#[non_exhaustive]
311pub enum PrivateElementError {
312 #[snafu(display("Group number must be odd, found {:#06x}", group))]
314 InvalidGroup { group: GroupNumber },
315 #[snafu(display("Private creator {} not found in group {:#06x}", creator, group))]
317 PrivateCreatorNotFound { creator: String, group: GroupNumber },
318 #[snafu(display(
320 "Private Creator {} found in group {:#06x}, but elem {:#06x} not found",
321 creator,
322 group,
323 elem
324 ))]
325 ElementNotFound {
326 creator: String,
327 group: GroupNumber,
328 elem: u8,
329 },
330 #[snafu(display("No space available in group {:#06x}", group))]
332 NoSpace { group: GroupNumber },
333}
334
335#[derive(Debug, Snafu)]
337#[non_exhaustive]
338pub enum AccessError {
339 #[snafu(display("No such data element with tag {}", tag))]
340 NoSuchDataElementTag { tag: Tag, backtrace: Backtrace },
341}
342
343impl AccessError {
344 pub fn into_access_by_name(self, alias: impl Into<String>) -> AccessByNameError {
345 match self {
346 AccessError::NoSuchDataElementTag { tag, backtrace } => {
347 AccessByNameError::NoSuchDataElementAlias {
348 tag,
349 alias: alias.into(),
350 backtrace,
351 }
352 }
353 }
354 }
355}
356
357#[derive(Debug, Snafu)]
361#[non_exhaustive]
362#[snafu(visibility(pub(crate)))]
363pub enum AtAccessError {
364 MissingSequence {
366 selector: AttributeSelector,
367 step_index: u32,
368 },
369 NotASequence {
371 selector: AttributeSelector,
372 step_index: u32,
373 },
374 MissingLeafElement { selector: AttributeSelector },
376}
377
378#[derive(Debug, Snafu)]
384pub enum AccessByNameError {
385 #[snafu(display("No such data element {} (with tag {})", alias, tag))]
386 NoSuchDataElementAlias {
387 tag: Tag,
388 alias: String,
389 backtrace: Backtrace,
390 },
391
392 #[snafu(display("Unknown data attribute named `{}`", name))]
394 NoSuchAttributeName { name: String, backtrace: Backtrace },
395}
396
397#[derive(Debug, Snafu)]
398#[non_exhaustive]
399pub enum WithMetaError {
400 BuildMetaTable {
402 #[snafu(backtrace)]
403 source: crate::meta::Error,
404 },
405 PrepareMetaTable {
407 #[snafu(source(from(dicom_core::value::ConvertValueError, Box::from)))]
408 source: Box<dicom_core::value::ConvertValueError>,
409 backtrace: Backtrace,
410 },
411}
412
413#[derive(Debug, Clone, PartialEq)]
417pub struct FileDicomObject<O> {
418 meta: FileMetaTable,
419 obj: O,
420}
421
422impl<O> FileDicomObject<O> {
423 pub fn meta(&self) -> &FileMetaTable {
425 &self.meta
426 }
427
428 pub fn meta_mut(&mut self) -> &mut FileMetaTable {
439 &mut self.meta
440 }
441
442 pub fn update_meta(&mut self, f: impl FnOnce(&mut FileMetaTable)) {
448 f(&mut self.meta);
449 self.meta.update_information_group_length();
450 }
451
452 pub fn into_inner(self) -> O {
454 self.obj
455 }
456}
457
458impl<O> FileDicomObject<O>
459where
460 for<'a> &'a O: IntoTokens,
461{
462 pub fn write_to_file<P: AsRef<Path>>(&self, path: P) -> Result<(), WriteError> {
467 let path = path.as_ref();
468 let file = File::create(path).context(WriteFileSnafu { filename: path })?;
469 let mut to = BufWriter::new(file);
470
471 to.write_all(&[0_u8; 128][..])
473 .context(WriteFileSnafu { filename: path })?;
474
475 to.write_all(b"DICM")
477 .context(WriteFileSnafu { filename: path })?;
478
479 self.meta.write(&mut to).context(PrintMetaDataSetSnafu)?;
481
482 let ts = TransferSyntaxRegistry
484 .get(&self.meta.transfer_syntax)
485 .with_context(|| WriteUnsupportedTransferSyntaxSnafu {
486 uid: self.meta.transfer_syntax.clone(),
487 })?;
488 let mut dset_writer = DataSetWriter::with_ts(to, ts).context(CreatePrinterSnafu)?;
489
490 dset_writer
492 .write_sequence((&self.obj).into_tokens())
493 .context(PrintDataSetSnafu)?;
494
495 Ok(())
496 }
497
498 pub fn write_all<W: Write>(&self, to: W) -> Result<(), WriteError> {
503 let mut to = BufWriter::new(to);
504
505 to.write_all(&[0_u8; 128][..]).context(WritePreambleSnafu)?;
507
508 to.write_all(b"DICM").context(WriteMagicCodeSnafu)?;
510
511 self.meta.write(&mut to).context(PrintMetaDataSetSnafu)?;
513
514 let ts = TransferSyntaxRegistry
516 .get(&self.meta.transfer_syntax)
517 .with_context(|| WriteUnsupportedTransferSyntaxSnafu {
518 uid: self.meta.transfer_syntax.clone(),
519 })?;
520 let mut dset_writer = DataSetWriter::with_ts(to, ts).context(CreatePrinterSnafu)?;
521
522 dset_writer
524 .write_sequence((&self.obj).into_tokens())
525 .context(PrintDataSetSnafu)?;
526
527 Ok(())
528 }
529
530 pub fn write_meta<W: Write>(&self, to: W) -> Result<(), WriteError> {
534 self.meta.write(to).context(PrintMetaDataSetSnafu)
535 }
536
537 pub fn write_dataset<W: Write>(&self, to: W) -> Result<(), WriteError> {
542 let to = BufWriter::new(to);
543
544 let ts = TransferSyntaxRegistry
546 .get(&self.meta.transfer_syntax)
547 .with_context(|| WriteUnsupportedTransferSyntaxSnafu {
548 uid: self.meta.transfer_syntax.clone(),
549 })?;
550 let mut dset_writer = DataSetWriter::with_ts(to, ts).context(CreatePrinterSnafu)?;
551
552 dset_writer
554 .write_sequence((&self.obj).into_tokens())
555 .context(PrintDataSetSnafu)?;
556
557 Ok(())
558 }
559}
560
561impl<O> ::std::ops::Deref for FileDicomObject<O> {
562 type Target = O;
563
564 fn deref(&self) -> &Self::Target {
565 &self.obj
566 }
567}
568
569impl<O> ::std::ops::DerefMut for FileDicomObject<O> {
570 fn deref_mut(&mut self) -> &mut Self::Target {
571 &mut self.obj
572 }
573}
574
575impl<O> DicomObject for FileDicomObject<O>
576where
577 O: DicomObject,
578{
579 type Element = <O as DicomObject>::Element;
580
581 fn element(&self, tag: Tag) -> Result<Self::Element, AccessError> {
582 self.obj.element(tag)
583 }
584
585 fn element_by_name(&self, name: &str) -> Result<Self::Element, AccessByNameError> {
586 self.obj.element_by_name(name)
587 }
588
589 fn meta(&self) -> Option<&FileMetaTable> {
590 Some(&self.meta)
591 }
592}
593
594impl<'a, O: 'a> DicomObject for &'a FileDicomObject<O>
595where
596 O: DicomObject,
597{
598 type Element = <O as DicomObject>::Element;
599
600 fn element(&self, tag: Tag) -> Result<Self::Element, AccessError> {
601 self.obj.element(tag)
602 }
603
604 fn element_by_name(&self, name: &str) -> Result<Self::Element, AccessByNameError> {
605 self.obj.element_by_name(name)
606 }
607}
608
609impl<O> IntoIterator for FileDicomObject<O>
617where
618 O: IntoIterator,
619{
620 type Item = <O as IntoIterator>::Item;
621 type IntoIter = <O as IntoIterator>::IntoIter;
622
623 fn into_iter(self) -> Self::IntoIter {
624 self.obj.into_iter()
625 }
626}
627
628impl<'a, O> IntoIterator for &'a FileDicomObject<O>
635where
636 &'a O: IntoIterator,
637{
638 type Item = <&'a O as IntoIterator>::Item;
639 type IntoIter = <&'a O as IntoIterator>::IntoIter;
640
641 fn into_iter(self) -> Self::IntoIter {
642 (&self.obj).into_iter()
643 }
644}
645
646impl<D> PixelDataObject for FileDicomObject<InMemDicomObject<D>>
648where
649 D: DataDictionary + Clone,
650{
651 fn transfer_syntax_uid(&self) -> &str {
652 self.meta.transfer_syntax()
653 }
654
655 fn rows(&self) -> Option<u16> {
657 self.get(dicom_dictionary_std::tags::ROWS)?.uint16().ok()
658 }
659
660 fn cols(&self) -> Option<u16> {
662 self.get(dicom_dictionary_std::tags::COLUMNS)?.uint16().ok()
663 }
664
665 fn samples_per_pixel(&self) -> Option<u16> {
667 self.get(dicom_dictionary_std::tags::SAMPLES_PER_PIXEL)?
668 .uint16()
669 .ok()
670 }
671
672 fn bits_allocated(&self) -> Option<u16> {
674 self.get(dicom_dictionary_std::tags::BITS_ALLOCATED)?
675 .uint16()
676 .ok()
677 }
678
679 fn bits_stored(&self) -> Option<u16> {
681 self.get(dicom_dictionary_std::tags::BITS_STORED)?
682 .uint16()
683 .ok()
684 }
685
686 fn photometric_interpretation(&self) -> Option<&str> {
687 self.get(dicom_dictionary_std::tags::PHOTOMETRIC_INTERPRETATION)?
688 .string()
689 .ok()
690 .map(|s| s.trim_end())
691 }
692
693 fn number_of_frames(&self) -> Option<u32> {
695 self.get(dicom_dictionary_std::tags::NUMBER_OF_FRAMES)?
696 .to_int()
697 .ok()
698 }
699
700 fn number_of_fragments(&self) -> Option<u32> {
702 let pixel_data = self.get(dicom_dictionary_std::tags::PIXEL_DATA)?;
703 match pixel_data.value() {
704 dicom_core::DicomValue::Primitive(_p) => Some(1),
705 dicom_core::DicomValue::PixelSequence(v) => Some(v.fragments().len() as u32),
706 dicom_core::DicomValue::Sequence(..) => None,
707 }
708 }
709
710 fn fragment(&self, fragment: usize) -> Option<Cow<'_, [u8]>> {
717 let pixel_data = self.get(dicom_dictionary_std::tags::PIXEL_DATA)?;
718 match pixel_data.value() {
719 dicom_core::DicomValue::PixelSequence(v) => {
720 Some(Cow::Borrowed(v.fragments()[fragment].as_ref()))
721 }
722 dicom_core::DicomValue::Primitive(p) if fragment == 0 => Some(p.to_bytes()),
723 _ => None,
724 }
725 }
726
727 fn offset_table(&self) -> Option<Cow<'_, [u32]>> {
728 let pixel_data = self.get(dicom_dictionary_std::tags::PIXEL_DATA)?;
729 match pixel_data.value() {
730 dicom_core::DicomValue::Primitive(_) => None,
731 dicom_core::DicomValue::Sequence(_) => None,
732 dicom_core::DicomValue::PixelSequence(seq) => Some(Cow::from(seq.offset_table())),
733 }
734 }
735
736 fn raw_pixel_data(&self) -> Option<RawPixelData> {
740 let pixel_data = self.get(dicom_dictionary_std::tags::PIXEL_DATA)?;
741 match pixel_data.value() {
742 dicom_core::DicomValue::Primitive(p) => {
743 let fragment = p.to_bytes().to_vec();
745 let mut fragments = SmallVec::new();
746 fragments.push(fragment);
747 Some(RawPixelData {
748 fragments,
749 offset_table: SmallVec::new(),
750 })
751 }
752 dicom_core::DicomValue::PixelSequence(v) => {
753 let (offset_table, fragments) = v.clone().into_parts();
754 Some(RawPixelData {
755 fragments,
756 offset_table,
757 })
758 }
759 dicom_core::DicomValue::Sequence(..) => None,
760 }
761 }
762}
763
764#[cfg(test)]
765mod tests {
766 use dicom_core::{DataElement, PrimitiveValue, VR};
767
768 use crate::meta::FileMetaTableBuilder;
769 use crate::{AccessError, FileDicomObject, InMemDicomObject};
770
771 fn assert_type_not_too_large<T>(max_size: usize) {
772 let size = std::mem::size_of::<T>();
773 if size > max_size {
774 panic!(
775 "Type {} of byte size {} exceeds acceptable size {}",
776 std::any::type_name::<T>(),
777 size,
778 max_size
779 );
780 }
781 }
782
783 #[test]
784 fn errors_not_too_large() {
785 assert_type_not_too_large::<AccessError>(64);
786 }
787
788 #[test]
789 fn smoke_test() {
790 const FILE_NAME: &str = ".smoke-test.dcm";
791
792 let meta = FileMetaTableBuilder::new()
793 .transfer_syntax(
794 dicom_transfer_syntax_registry::entries::EXPLICIT_VR_LITTLE_ENDIAN.uid(),
795 )
796 .media_storage_sop_class_uid("1.2.840.10008.5.1.4.1.1.1")
797 .media_storage_sop_instance_uid("1.2.3.456")
798 .implementation_class_uid("1.2.345.6.7890.1.234")
799 .build()
800 .unwrap();
801 let obj = FileDicomObject::new_empty_with_meta(meta);
802
803 obj.write_to_file(FILE_NAME).unwrap();
804
805 let obj2 = FileDicomObject::open_file(FILE_NAME).unwrap();
806
807 assert_eq!(obj, obj2);
808
809 let _ = std::fs::remove_file(FILE_NAME);
810 }
811
812 #[test]
815 fn file_dicom_object_can_use_inner() {
816 let mut obj = InMemDicomObject::new_empty();
817
818 obj.put(DataElement::new(
819 dicom_dictionary_std::tags::PATIENT_NAME,
820 VR::PN,
821 PrimitiveValue::from("John Doe"),
822 ));
823
824 let mut obj = obj
825 .with_meta(
826 FileMetaTableBuilder::new()
827 .media_storage_sop_class_uid("1.2.840.10008.5.1.4.1.1.7")
828 .media_storage_sop_instance_uid("1.2.23456789")
829 .transfer_syntax("1.2.840.10008.1.2.1"),
830 )
831 .unwrap();
832
833 assert_eq!(
835 obj.element(dicom_dictionary_std::tags::PATIENT_NAME)
836 .unwrap()
837 .value()
838 .to_str()
839 .unwrap(),
840 "John Doe",
841 );
842
843 obj.take_element(dicom_dictionary_std::tags::PATIENT_NAME)
845 .unwrap();
846
847 assert!(matches!(
848 obj.element(dicom_dictionary_std::tags::PATIENT_NAME),
849 Err(AccessError::NoSuchDataElementTag { .. }),
850 ));
851 }
852
853 #[test]
854 fn file_dicom_object_can_iterate_over_elements() {
855 let mut obj = InMemDicomObject::new_empty();
856
857 obj.put(DataElement::new(
858 dicom_dictionary_std::tags::PATIENT_NAME,
859 VR::PN,
860 PrimitiveValue::from("John Doe"),
861 ));
862 obj.put(DataElement::new(
863 dicom_dictionary_std::tags::SOP_INSTANCE_UID,
864 VR::PN,
865 PrimitiveValue::from("1.2.987654321"),
866 ));
867
868 let obj = obj
869 .with_meta(
870 FileMetaTableBuilder::new()
871 .media_storage_sop_class_uid("1.2.840.10008.5.1.4.1.1.7")
872 .media_storage_sop_instance_uid("1.2.987654321")
873 .transfer_syntax("1.2.840.10008.1.2.1"),
874 )
875 .unwrap();
876
877 let mut iter = (&obj).into_iter();
879 assert_eq!(
880 iter.next().unwrap().header().tag,
881 dicom_dictionary_std::tags::SOP_INSTANCE_UID
882 );
883 assert_eq!(
884 iter.next().unwrap().header().tag,
885 dicom_dictionary_std::tags::PATIENT_NAME
886 );
887 assert_eq!(iter.next(), None);
888
889 let mut iter = obj.into_iter();
891 assert_eq!(
892 iter.next().unwrap().header().tag,
893 dicom_dictionary_std::tags::SOP_INSTANCE_UID
894 );
895 assert_eq!(
896 iter.next().unwrap().header().tag,
897 dicom_dictionary_std::tags::PATIENT_NAME
898 );
899 assert_eq!(iter.next(), None);
900 }
901
902 #[test]
903 pub fn file_dicom_can_update_meta() {
904 let meta = FileMetaTableBuilder::new()
905 .transfer_syntax(
906 dicom_transfer_syntax_registry::entries::EXPLICIT_VR_LITTLE_ENDIAN.uid(),
907 )
908 .media_storage_sop_class_uid("1.2.840.10008.5.1.4.1.1.1")
909 .media_storage_sop_instance_uid("2.25.280986007517028771599125034987786349815")
910 .implementation_class_uid("1.2.345.6.7890.1.234")
911 .build()
912 .unwrap();
913 let mut obj = FileDicomObject::new_empty_with_meta(meta);
914
915 obj.update_meta(|meta| {
916 meta.receiving_application_entity_title = Some("SOMETHING".to_string());
917 });
918
919 assert_eq!(
920 obj.meta().receiving_application_entity_title.as_deref(),
921 Some("SOMETHING"),
922 );
923 }
924}