1#![allow(clippy::derive_partial_eq_without_eq)]
2pub 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
150pub 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
165pub const IMPLEMENTATION_CLASS_UID: &str = "2.25.130984950029899771041107395941696826170";
171
172pub const IMPLEMENTATION_VERSION_NAME: &str = "DICOM-rs 0.6";
176
177pub trait DicomObject {
184 type Element: Header;
185
186 fn element(&self, tag: Tag) -> Result<Self::Element, AccessError>;
188
189 fn element_by_name(&self, name: &str) -> Result<Self::Element, AccessByNameError>;
191
192 fn meta(&self) -> Option<&FileMetaTable> {
198 None
199 }
200}
201
202#[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 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#[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#[derive(Debug, Snafu)]
298#[non_exhaustive]
299pub enum PrivateElementError {
300 #[snafu(display("Group number must be odd, found {:#06x}", group))]
302 InvalidGroup { group: GroupNumber },
303 #[snafu(display("Private creator {} not found in group {:#06x}", creator, group))]
305 PrivateCreatorNotFound { creator: String, group: GroupNumber },
306 #[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 #[snafu(display("No space available in group {:#06x}", group))]
320 NoSpace { group: GroupNumber },
321}
322
323#[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#[derive(Debug, Snafu)]
349#[non_exhaustive]
350#[snafu(visibility(pub(crate)))]
351pub enum AtAccessError {
352 MissingSequence {
354 selector: AttributeSelector,
355 step_index: u32,
356 },
357 NotASequence {
359 selector: AttributeSelector,
360 step_index: u32,
361 },
362 MissingLeafElement { selector: AttributeSelector },
364}
365
366#[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 #[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 BuildMetaTable {
390 #[snafu(backtrace)]
391 source: crate::meta::Error,
392 },
393 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#[derive(Debug, Clone, PartialEq)]
405pub struct FileDicomObject<O> {
406 meta: FileMetaTable,
407 obj: O,
408}
409
410impl<O> FileDicomObject<O> {
411 pub fn meta(&self) -> &FileMetaTable {
413 &self.meta
414 }
415
416 pub fn meta_mut(&mut self) -> &mut FileMetaTable {
421 &mut self.meta
422 }
423
424 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 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 to.write_all(&[0_u8; 128][..])
445 .context(WriteFileSnafu { filename: path })?;
446
447 to.write_all(b"DICM")
449 .context(WriteFileSnafu { filename: path })?;
450
451 self.meta.write(&mut to).context(PrintMetaDataSetSnafu)?;
453
454 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 dset_writer
464 .write_sequence((&self.obj).into_tokens())
465 .context(PrintDataSetSnafu)?;
466
467 Ok(())
468 }
469
470 pub fn write_all<W: Write>(&self, to: W) -> Result<(), WriteError> {
475 let mut to = BufWriter::new(to);
476
477 to.write_all(&[0_u8; 128][..]).context(WritePreambleSnafu)?;
479
480 to.write_all(b"DICM").context(WriteMagicCodeSnafu)?;
482
483 self.meta.write(&mut to).context(PrintMetaDataSetSnafu)?;
485
486 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 dset_writer
496 .write_sequence((&self.obj).into_tokens())
497 .context(PrintDataSetSnafu)?;
498
499 Ok(())
500 }
501
502 pub fn write_meta<W: Write>(&self, to: W) -> Result<(), WriteError> {
506 self.meta.write(to).context(PrintMetaDataSetSnafu)
507 }
508
509 pub fn write_dataset<W: Write>(&self, to: W) -> Result<(), WriteError> {
514 let to = BufWriter::new(to);
515
516 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 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
581impl<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
600impl<'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
618impl<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 fn rows(&self) -> Option<u16> {
629 self.get(dicom_dictionary_std::tags::ROWS)?.uint16().ok()
630 }
631
632 fn cols(&self) -> Option<u16> {
634 self.get(dicom_dictionary_std::tags::COLUMNS)?.uint16().ok()
635 }
636
637 fn samples_per_pixel(&self) -> Option<u16> {
639 self.get(dicom_dictionary_std::tags::SAMPLES_PER_PIXEL)?
640 .uint16()
641 .ok()
642 }
643
644 fn bits_allocated(&self) -> Option<u16> {
646 self.get(dicom_dictionary_std::tags::BITS_ALLOCATED)?
647 .uint16()
648 .ok()
649 }
650
651 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 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 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 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 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 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 #[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 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 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 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 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}