1use byteordered::byteorder::{ByteOrder, LittleEndian};
3use dicom_core::dicom_value;
4use dicom_core::header::{DataElement, EmptyObject, HasLength, Header};
5use dicom_core::ops::{
6 ApplyOp, AttributeAction, AttributeOp, AttributeSelector, AttributeSelectorStep,
7};
8use dicom_core::value::{
9 ConvertValueError, DicomValueType, InMemFragment, PrimitiveValue, Value, ValueType,
10};
11use dicom_core::{Length, Tag, VR};
12use dicom_dictionary_std::tags;
13use dicom_encoding::decode::{self, DecodeFrom};
14use dicom_encoding::encode::explicit_le::ExplicitVRLittleEndianEncoder;
15use dicom_encoding::encode::EncoderFor;
16use dicom_encoding::text::{self, TextCodec};
17use dicom_encoding::TransferSyntax;
18use dicom_parser::dataset::{DataSetWriter, IntoTokens};
19use snafu::{ensure, Backtrace, OptionExt, ResultExt, Snafu};
20use std::borrow::Cow;
21use std::io::{Read, Write};
22
23use crate::ops::{
24 ApplyError, ApplyResult, IllegalExtendSnafu, IncompatibleTypesSnafu, MandatorySnafu,
25 UnsupportedActionSnafu, UnsupportedAttributeSnafu,
26};
27use crate::{
28 AtAccessError, AttributeError, DicomAttribute, DicomObject, IMPLEMENTATION_CLASS_UID,
29 IMPLEMENTATION_VERSION_NAME,
30};
31
32const DICM_MAGIC_CODE: [u8; 4] = [b'D', b'I', b'C', b'M'];
33
34#[derive(Debug, Snafu)]
35#[non_exhaustive]
36pub enum Error {
37 #[snafu(display("Could not start reading DICOM data"))]
40 ReadMagicCode {
41 backtrace: Backtrace,
42 source: std::io::Error,
43 },
44
45 #[snafu(display("Could not read data value"))]
48 ReadValueData {
49 backtrace: Backtrace,
50 source: std::io::Error,
51 },
52
53 #[snafu(display("Could not allocate memory"))]
56 AllocationSize {
57 backtrace: Backtrace,
58 source: std::collections::TryReserveError,
59 },
60
61 #[snafu(display("Could not decode text in {}", name))]
64 DecodeText {
65 name: std::borrow::Cow<'static, str>,
66 #[snafu(backtrace)]
67 source: dicom_encoding::text::DecodeTextError,
68 },
69
70 #[snafu(display("Invalid DICOM file (magic code check failed)"))]
72 NotDicom { backtrace: Backtrace },
73
74 #[snafu(display("Could not decode data element"))]
77 DecodeElement {
78 #[snafu(backtrace)]
79 source: dicom_encoding::decode::Error,
80 },
81
82 #[snafu(display("Unexpected data element tagged {}", tag))]
86 UnexpectedTag { tag: Tag, backtrace: Backtrace },
87
88 #[snafu(display("Missing data element `{}`", alias))]
90 MissingElement {
91 alias: &'static str,
92 backtrace: Backtrace,
93 },
94
95 #[snafu(display("Unexpected length {} for data element tagged {}", length, tag))]
98 UnexpectedDataValueLength {
99 tag: Tag,
100 length: Length,
101 backtrace: Backtrace,
102 },
103
104 #[snafu(display("Undefined value length for data element tagged {}", tag))]
107 UndefinedValueLength { tag: Tag, backtrace: Backtrace },
108
109 #[snafu(display("Could not write file meta group data set"))]
111 WriteSet {
112 #[snafu(backtrace)]
113 source: dicom_parser::dataset::write::Error,
114 },
115}
116
117type Result<T, E = Error> = std::result::Result<T, E>;
118
119#[derive(Debug, Clone, PartialEq)]
133pub struct FileMetaTable {
134 pub information_group_length: u32,
136 pub information_version: [u8; 2],
138 pub media_storage_sop_class_uid: String,
140 pub media_storage_sop_instance_uid: String,
142 pub transfer_syntax: String,
144 pub implementation_class_uid: String,
146
147 pub implementation_version_name: Option<String>,
149 pub source_application_entity_title: Option<String>,
151 pub sending_application_entity_title: Option<String>,
153 pub receiving_application_entity_title: Option<String>,
155 pub private_information_creator_uid: Option<String>,
157 pub private_information: Option<Vec<u8>>,
159 }
174
175fn read_str_body<'s, S, T>(source: &'s mut S, text: &T, len: u32) -> Result<String>
177where
178 S: Read + 's,
179 T: TextCodec,
180{
181 let mut v = Vec::new();
182 v.try_reserve_exact(len as usize)
183 .context(AllocationSizeSnafu)?;
184 v.resize(len as usize, 0);
185 source.read_exact(&mut v).context(ReadValueDataSnafu)?;
186
187 text.decode(&v)
188 .context(DecodeTextSnafu { name: text.name() })
189}
190
191impl FileMetaTable {
192 pub fn from_reader<R: Read>(file: R) -> Result<Self> {
198 FileMetaTable::read_from(file)
199 }
200
201 pub fn transfer_syntax(&self) -> &str {
204 self.transfer_syntax
205 .trim_end_matches(|c: char| c.is_whitespace() || c == '\0')
206 }
207
208 pub fn media_storage_sop_instance_uid(&self) -> &str {
211 self.media_storage_sop_instance_uid
212 .trim_end_matches(|c: char| c.is_whitespace() || c == '\0')
213 }
214
215 pub fn media_storage_sop_class_uid(&self) -> &str {
218 self.media_storage_sop_class_uid
219 .trim_end_matches(|c: char| c.is_whitespace() || c == '\0')
220 }
221
222 pub fn implementation_class_uid(&self) -> &str {
225 self.implementation_class_uid
226 .trim_end_matches(|c: char| c.is_whitespace() || c == '\0')
227 }
228
229 pub fn private_information_creator_uid(&self) -> Option<&str> {
232 self.private_information_creator_uid
233 .as_ref()
234 .map(|s| s.trim_end_matches(|c: char| c.is_whitespace() || c == '\0'))
235 }
236
237 pub fn set_transfer_syntax<D, R, W>(&mut self, ts: &TransferSyntax<D, R, W>) {
244 self.transfer_syntax = ts
245 .uid()
246 .trim_end_matches(|c: char| c.is_whitespace() || c == '\0')
247 .to_string();
248 self.update_information_group_length();
249 }
250
251 pub fn update_information_group_length(&mut self) {
255 self.information_group_length = self.calculate_information_group_length();
256 }
257
258 fn apply(&mut self, op: AttributeOp) -> ApplyResult {
263 let AttributeSelectorStep::Tag(tag) = op.selector.first_step() else {
264 return UnsupportedAttributeSnafu.fail();
265 };
266
267 match *tag {
268 tags::TRANSFER_SYNTAX_UID => Self::apply_required_string(op, &mut self.transfer_syntax),
269 tags::MEDIA_STORAGE_SOP_CLASS_UID => {
270 Self::apply_required_string(op, &mut self.media_storage_sop_class_uid)
271 }
272 tags::MEDIA_STORAGE_SOP_INSTANCE_UID => {
273 Self::apply_required_string(op, &mut self.media_storage_sop_instance_uid)
274 }
275 tags::IMPLEMENTATION_CLASS_UID => {
276 Self::apply_required_string(op, &mut self.implementation_class_uid)
277 }
278 tags::IMPLEMENTATION_VERSION_NAME => {
279 Self::apply_optional_string(op, &mut self.implementation_version_name)
280 }
281 tags::SOURCE_APPLICATION_ENTITY_TITLE => {
282 Self::apply_optional_string(op, &mut self.source_application_entity_title)
283 }
284 tags::SENDING_APPLICATION_ENTITY_TITLE => {
285 Self::apply_optional_string(op, &mut self.sending_application_entity_title)
286 }
287 tags::RECEIVING_APPLICATION_ENTITY_TITLE => {
288 Self::apply_optional_string(op, &mut self.receiving_application_entity_title)
289 }
290 tags::PRIVATE_INFORMATION_CREATOR_UID => {
291 Self::apply_optional_string(op, &mut self.private_information_creator_uid)
292 }
293 _ if matches!(
294 op.action,
295 AttributeAction::Remove | AttributeAction::Empty | AttributeAction::Truncate(_)
296 ) =>
297 {
298 Ok(())
301 }
302 _ => UnsupportedAttributeSnafu.fail(),
303 }?;
304
305 self.update_information_group_length();
306
307 Ok(())
308 }
309
310 fn apply_required_string(op: AttributeOp, target_attribute: &mut String) -> ApplyResult {
311 match op.action {
312 AttributeAction::Remove | AttributeAction::Empty => MandatorySnafu.fail(),
313 AttributeAction::SetVr(_) | AttributeAction::Truncate(_) => {
314 Ok(())
316 }
317 AttributeAction::Set(value) | AttributeAction::Replace(value) => {
318 if let Ok(value) = value.string() {
320 *target_attribute = value.to_string();
321 Ok(())
322 } else {
323 IncompatibleTypesSnafu {
324 kind: ValueType::Str,
325 }
326 .fail()
327 }
328 }
329 AttributeAction::SetStr(string) | AttributeAction::ReplaceStr(string) => {
330 *target_attribute = string.to_string();
331 Ok(())
332 }
333 AttributeAction::SetIfMissing(_) | AttributeAction::SetStrIfMissing(_) => {
334 Ok(())
336 }
337 AttributeAction::PushStr(_) => IllegalExtendSnafu.fail(),
338 AttributeAction::PushI32(_)
339 | AttributeAction::PushU32(_)
340 | AttributeAction::PushI16(_)
341 | AttributeAction::PushU16(_)
342 | AttributeAction::PushF32(_)
343 | AttributeAction::PushF64(_) => IncompatibleTypesSnafu {
344 kind: ValueType::Str,
345 }
346 .fail(),
347 _ => UnsupportedActionSnafu.fail(),
348 }
349 }
350
351 fn apply_optional_string(
352 op: AttributeOp,
353 target_attribute: &mut Option<String>,
354 ) -> ApplyResult {
355 match op.action {
356 AttributeAction::Remove => {
357 target_attribute.take();
358 Ok(())
359 }
360 AttributeAction::Empty => {
361 if let Some(s) = target_attribute.as_mut() {
362 s.clear();
363 }
364 Ok(())
365 }
366 AttributeAction::SetVr(_) => {
367 Ok(())
369 }
370 AttributeAction::Set(value) => {
371 if let Ok(value) = value.string() {
373 *target_attribute = Some(value.to_string());
374 Ok(())
375 } else {
376 IncompatibleTypesSnafu {
377 kind: ValueType::Str,
378 }
379 .fail()
380 }
381 }
382 AttributeAction::SetStr(value) => {
383 *target_attribute = Some(value.to_string());
384 Ok(())
385 }
386 AttributeAction::SetIfMissing(value) => {
387 if target_attribute.is_some() {
388 return Ok(());
389 }
390
391 if let Ok(value) = value.string() {
393 *target_attribute = Some(value.to_string());
394 Ok(())
395 } else {
396 IncompatibleTypesSnafu {
397 kind: ValueType::Str,
398 }
399 .fail()
400 }
401 }
402 AttributeAction::SetStrIfMissing(value) => {
403 if target_attribute.is_none() {
404 *target_attribute = Some(value.to_string());
405 }
406 Ok(())
407 }
408 AttributeAction::Replace(value) => {
409 if target_attribute.is_none() {
410 return Ok(());
411 }
412
413 if let Ok(value) = value.string() {
415 *target_attribute = Some(value.to_string());
416 Ok(())
417 } else {
418 IncompatibleTypesSnafu {
419 kind: ValueType::Str,
420 }
421 .fail()
422 }
423 }
424 AttributeAction::ReplaceStr(value) => {
425 if target_attribute.is_some() {
426 *target_attribute = Some(value.to_string());
427 }
428 Ok(())
429 }
430 AttributeAction::PushStr(_) => IllegalExtendSnafu.fail(),
431 AttributeAction::PushI32(_)
432 | AttributeAction::PushU32(_)
433 | AttributeAction::PushI16(_)
434 | AttributeAction::PushU16(_)
435 | AttributeAction::PushF32(_)
436 | AttributeAction::PushF64(_) => IncompatibleTypesSnafu {
437 kind: ValueType::Str,
438 }
439 .fail(),
440 _ => UnsupportedActionSnafu.fail(),
441 }
442 }
443
444 fn calculate_information_group_length(&self) -> u32 {
447 14 + 8
451 + dicom_len(&self.media_storage_sop_class_uid)
452 + 8
453 + dicom_len(&self.media_storage_sop_instance_uid)
454 + 8
455 + dicom_len(&self.transfer_syntax)
456 + 8
457 + dicom_len(&self.implementation_class_uid)
458 + self
459 .implementation_version_name
460 .as_ref()
461 .map(|s| 8 + dicom_len(s))
462 .unwrap_or(0)
463 + self
464 .source_application_entity_title
465 .as_ref()
466 .map(|s| 8 + dicom_len(s))
467 .unwrap_or(0)
468 + self
469 .sending_application_entity_title
470 .as_ref()
471 .map(|s| 8 + dicom_len(s))
472 .unwrap_or(0)
473 + self
474 .receiving_application_entity_title
475 .as_ref()
476 .map(|s| 8 + dicom_len(s))
477 .unwrap_or(0)
478 + self
479 .private_information_creator_uid
480 .as_ref()
481 .map(|s| 8 + dicom_len(s))
482 .unwrap_or(0)
483 + self
484 .private_information
485 .as_ref()
486 .map(|x| 12 + ((x.len() as u32 + 1) & !1))
487 .unwrap_or(0)
488 }
489
490 fn read_from<S: Read>(mut file: S) -> Result<Self> {
493 let mut buff: [u8; 4] = [0; 4];
494 {
495 file.read_exact(&mut buff).context(ReadMagicCodeSnafu)?;
497
498 ensure!(buff == DICM_MAGIC_CODE, NotDicomSnafu);
499 }
500
501 let decoder = decode::file_header_decoder();
502 let text = text::DefaultCharacterSetCodec;
503
504 let builder = FileMetaTableBuilder::new();
505
506 let group_length: u32 = {
507 let (elem, _bytes_read) = decoder
508 .decode_header(&mut file)
509 .context(DecodeElementSnafu)?;
510 if elem.tag() != Tag(0x0002, 0x0000) {
511 return UnexpectedTagSnafu { tag: elem.tag() }.fail();
512 }
513 if elem.length() != Length(4) {
514 return UnexpectedDataValueLengthSnafu {
515 tag: elem.tag(),
516 length: elem.length(),
517 }
518 .fail();
519 }
520 let mut buff: [u8; 4] = [0; 4];
521 file.read_exact(&mut buff).context(ReadValueDataSnafu)?;
522 LittleEndian::read_u32(&buff)
523 };
524
525 let mut total_bytes_read = 0;
526 let mut builder = builder.group_length(group_length);
527
528 while total_bytes_read < group_length {
530 let (elem, header_bytes_read) = decoder
531 .decode_header(&mut file)
532 .context(DecodeElementSnafu)?;
533 let elem_len = match elem.length().get() {
534 None => {
535 return UndefinedValueLengthSnafu { tag: elem.tag() }.fail();
536 }
537 Some(len) => len,
538 };
539 builder = match elem.tag() {
540 Tag(0x0002, 0x0001) => {
541 if elem.length() != Length(2) {
543 return UnexpectedDataValueLengthSnafu {
544 tag: elem.tag(),
545 length: elem.length(),
546 }
547 .fail();
548 }
549 let mut hbuf = [0u8; 2];
550 file.read_exact(&mut hbuf[..]).context(ReadValueDataSnafu)?;
551
552 builder.information_version(hbuf)
553 }
554 Tag(0x0002, 0x0002) => {
556 builder.media_storage_sop_class_uid(read_str_body(&mut file, &text, elem_len)?)
557 }
558 Tag(0x0002, 0x0003) => builder
560 .media_storage_sop_instance_uid(read_str_body(&mut file, &text, elem_len)?),
561 Tag(0x0002, 0x0010) => {
563 builder.transfer_syntax(read_str_body(&mut file, &text, elem_len)?)
564 }
565 Tag(0x0002, 0x0012) => {
567 builder.implementation_class_uid(read_str_body(&mut file, &text, elem_len)?)
568 }
569 Tag(0x0002, 0x0013) => {
570 let mut v = Vec::new();
572 v.try_reserve_exact(elem_len as usize)
573 .context(AllocationSizeSnafu)?;
574 v.resize(elem_len as usize, 0);
575 file.read_exact(&mut v).context(ReadValueDataSnafu)?;
576
577 builder.implementation_version_name(
578 text.decode(&v)
579 .context(DecodeTextSnafu { name: text.name() })?,
580 )
581 }
582 Tag(0x0002, 0x0016) => {
583 let mut v = Vec::new();
585 v.try_reserve_exact(elem_len as usize)
586 .context(AllocationSizeSnafu)?;
587 v.resize(elem_len as usize, 0);
588 file.read_exact(&mut v).context(ReadValueDataSnafu)?;
589
590 builder.source_application_entity_title(
591 text.decode(&v)
592 .context(DecodeTextSnafu { name: text.name() })?,
593 )
594 }
595 Tag(0x0002, 0x0017) => {
596 let mut v = Vec::new();
598 v.try_reserve_exact(elem_len as usize)
599 .context(AllocationSizeSnafu)?;
600 v.resize(elem_len as usize, 0);
601 file.read_exact(&mut v).context(ReadValueDataSnafu)?;
602
603 builder.sending_application_entity_title(
604 text.decode(&v)
605 .context(DecodeTextSnafu { name: text.name() })?,
606 )
607 }
608 Tag(0x0002, 0x0018) => {
609 let mut v = Vec::new();
611 v.try_reserve_exact(elem_len as usize)
612 .context(AllocationSizeSnafu)?;
613 v.resize(elem_len as usize, 0);
614 file.read_exact(&mut v).context(ReadValueDataSnafu)?;
615
616 builder.receiving_application_entity_title(
617 text.decode(&v)
618 .context(DecodeTextSnafu { name: text.name() })?,
619 )
620 }
621 Tag(0x0002, 0x0100) => {
622 let mut v = Vec::new();
624 v.try_reserve_exact(elem_len as usize)
625 .context(AllocationSizeSnafu)?;
626 v.resize(elem_len as usize, 0);
627 file.read_exact(&mut v).context(ReadValueDataSnafu)?;
628
629 builder.private_information_creator_uid(
630 text.decode(&v)
631 .context(DecodeTextSnafu { name: text.name() })?,
632 )
633 }
634 Tag(0x0002, 0x0102) => {
635 let mut v = Vec::new();
637 v.try_reserve_exact(elem_len as usize)
638 .context(AllocationSizeSnafu)?;
639 v.resize(elem_len as usize, 0);
640 file.read_exact(&mut v).context(ReadValueDataSnafu)?;
641
642 builder.private_information(v)
643 }
644 tag @ Tag(0x0002, _) => {
645 tracing::info!("Unknown tag {}", tag);
648 let bytes_read =
650 std::io::copy(&mut (&mut file).take(elem_len as u64), &mut std::io::sink())
651 .context(ReadValueDataSnafu)?;
652 if bytes_read != elem_len as u64 {
653 return UnexpectedDataValueLengthSnafu {
655 tag: elem.tag(),
656 length: elem_len,
657 }
658 .fail();
659 }
660 builder
661 }
662 tag => {
663 tracing::warn!("Unexpected off-group tag {}", tag);
666 let bytes_read =
668 std::io::copy(&mut (&mut file).take(elem_len as u64), &mut std::io::sink())
669 .context(ReadValueDataSnafu)?;
670 if bytes_read != elem_len as u64 {
671 return UnexpectedDataValueLengthSnafu {
673 tag: elem.tag(),
674 length: elem_len,
675 }
676 .fail();
677 }
678 builder
679 }
680 };
681 total_bytes_read = total_bytes_read
682 .saturating_add(header_bytes_read as u32)
683 .saturating_add(elem_len);
684 }
685
686 builder.build()
687 }
688
689 pub fn into_element_iter(self) -> impl Iterator<Item = DataElement<EmptyObject, [u8; 0]>> {
696 let mut elems = vec![
697 DataElement::new(
699 Tag(0x0002, 0x0000),
700 VR::UL,
701 Value::Primitive(self.information_group_length.into()),
702 ),
703 DataElement::new(
704 Tag(0x0002, 0x0001),
705 VR::OB,
706 Value::Primitive(dicom_value!(
707 U8,
708 [self.information_version[0], self.information_version[1]]
709 )),
710 ),
711 DataElement::new(
712 Tag(0x0002, 0x0002),
713 VR::UI,
714 Value::Primitive(self.media_storage_sop_class_uid.into()),
715 ),
716 DataElement::new(
717 Tag(0x0002, 0x0003),
718 VR::UI,
719 Value::Primitive(self.media_storage_sop_instance_uid.into()),
720 ),
721 DataElement::new(
722 Tag(0x0002, 0x0010),
723 VR::UI,
724 Value::Primitive(self.transfer_syntax.into()),
725 ),
726 DataElement::new(
727 Tag(0x0002, 0x0012),
728 VR::UI,
729 Value::Primitive(self.implementation_class_uid.into()),
730 ),
731 ];
732 if let Some(v) = self.implementation_version_name {
733 elems.push(DataElement::new(
734 Tag(0x0002, 0x0013),
735 VR::SH,
736 Value::Primitive(v.into()),
737 ));
738 }
739 if let Some(v) = self.source_application_entity_title {
740 elems.push(DataElement::new(
741 Tag(0x0002, 0x0016),
742 VR::AE,
743 Value::Primitive(v.into()),
744 ));
745 }
746 if let Some(v) = self.sending_application_entity_title {
747 elems.push(DataElement::new(
748 Tag(0x0002, 0x0017),
749 VR::AE,
750 Value::Primitive(v.into()),
751 ));
752 }
753 if let Some(v) = self.receiving_application_entity_title {
754 elems.push(DataElement::new(
755 Tag(0x0002, 0x0018),
756 VR::AE,
757 Value::Primitive(v.into()),
758 ));
759 }
760 if let Some(v) = self.private_information_creator_uid {
761 elems.push(DataElement::new(
762 Tag(0x0002, 0x0100),
763 VR::UI,
764 Value::Primitive(v.into()),
765 ));
766 }
767 if let Some(v) = self.private_information {
768 elems.push(DataElement::new(
769 Tag(0x0002, 0x0102),
770 VR::OB,
771 Value::Primitive(PrimitiveValue::U8(v.into())),
772 ));
773 }
774
775 elems.into_iter()
776 }
777
778 pub fn to_element_iter(&self) -> impl Iterator<Item = DataElement<EmptyObject, [u8; 0]>> + '_ {
783 self.clone().into_element_iter()
784 }
785
786 pub fn write<W: Write>(&self, writer: W) -> Result<()> {
787 let mut dset = DataSetWriter::new(
788 writer,
789 EncoderFor::new(ExplicitVRLittleEndianEncoder::default()),
790 );
791 dset.write_sequence(
794 self.clone()
795 .into_element_iter()
796 .flat_map(IntoTokens::into_tokens),
797 )
798 .context(WriteSetSnafu)?;
799
800 dset.flush().context(WriteSetSnafu)
801 }
802}
803
804#[derive(Debug)]
806pub struct FileMetaAttribute<'a> {
807 meta: &'a FileMetaTable,
808 tag_e: u16,
809}
810
811impl HasLength for FileMetaAttribute<'_> {
812 fn length(&self) -> Length {
813 match Tag(0x0002, self.tag_e) {
814 tags::FILE_META_INFORMATION_GROUP_LENGTH => Length(4),
815 tags::MEDIA_STORAGE_SOP_CLASS_UID => {
816 Length(self.meta.media_storage_sop_class_uid.len() as u32)
817 }
818 tags::MEDIA_STORAGE_SOP_INSTANCE_UID => {
819 Length(self.meta.media_storage_sop_instance_uid.len() as u32)
820 }
821 tags::IMPLEMENTATION_CLASS_UID => {
822 Length(self.meta.implementation_class_uid.len() as u32)
823 }
824 tags::IMPLEMENTATION_VERSION_NAME => Length(
825 self.meta
826 .implementation_version_name
827 .as_ref()
828 .map(|s| s.len() as u32)
829 .unwrap_or(0),
830 ),
831 tags::SOURCE_APPLICATION_ENTITY_TITLE => Length(
832 self.meta
833 .source_application_entity_title
834 .as_ref()
835 .map(|s| s.len() as u32)
836 .unwrap_or(0),
837 ),
838 tags::SENDING_APPLICATION_ENTITY_TITLE => Length(
839 self.meta
840 .sending_application_entity_title
841 .as_ref()
842 .map(|s| s.len() as u32)
843 .unwrap_or(0),
844 ),
845 tags::TRANSFER_SYNTAX_UID => Length(self.meta.transfer_syntax.len() as u32),
846 tags::PRIVATE_INFORMATION_CREATOR_UID => Length(
847 self.meta
848 .private_information_creator_uid
849 .as_ref()
850 .map(|s| s.len() as u32)
851 .unwrap_or(0),
852 ),
853 _ => unreachable!(),
854 }
855 }
856}
857
858impl DicomValueType for FileMetaAttribute<'_> {
859 fn value_type(&self) -> ValueType {
860 match Tag(0x0002, self.tag_e) {
861 tags::MEDIA_STORAGE_SOP_CLASS_UID
862 | tags::MEDIA_STORAGE_SOP_INSTANCE_UID
863 | tags::TRANSFER_SYNTAX_UID
864 | tags::IMPLEMENTATION_CLASS_UID
865 | tags::IMPLEMENTATION_VERSION_NAME
866 | tags::SOURCE_APPLICATION_ENTITY_TITLE
867 | tags::SENDING_APPLICATION_ENTITY_TITLE
868 | tags::RECEIVING_APPLICATION_ENTITY_TITLE
869 | tags::PRIVATE_INFORMATION_CREATOR_UID => ValueType::Str,
870 tags::FILE_META_INFORMATION_GROUP_LENGTH => ValueType::U32,
871 tags::FILE_META_INFORMATION_VERSION => ValueType::U8,
872 tags::PRIVATE_INFORMATION => ValueType::U8,
873 _ => unreachable!(),
874 }
875 }
876
877 fn cardinality(&self) -> usize {
878 match Tag(0x0002, self.tag_e) {
879 tags::MEDIA_STORAGE_SOP_CLASS_UID
880 | tags::MEDIA_STORAGE_SOP_INSTANCE_UID
881 | tags::SOURCE_APPLICATION_ENTITY_TITLE
882 | tags::SENDING_APPLICATION_ENTITY_TITLE
883 | tags::RECEIVING_APPLICATION_ENTITY_TITLE
884 | tags::TRANSFER_SYNTAX_UID
885 | tags::IMPLEMENTATION_CLASS_UID
886 | tags::IMPLEMENTATION_VERSION_NAME
887 | tags::PRIVATE_INFORMATION_CREATOR_UID => 1,
888 tags::FILE_META_INFORMATION_GROUP_LENGTH => 1,
889 tags::PRIVATE_INFORMATION => 1,
890 tags::FILE_META_INFORMATION_VERSION => 2,
891 _ => 1,
892 }
893 }
894}
895
896impl DicomAttribute for FileMetaAttribute<'_> {
897 type Item<'b>
898 = EmptyObject
899 where
900 Self: 'b;
901 type PixelData<'b>
902 = InMemFragment
903 where
904 Self: 'b;
905
906 fn to_primitive_value(&self) -> Result<PrimitiveValue, AttributeError> {
907 Ok(match Tag(0x0002, self.tag_e) {
908 tags::FILE_META_INFORMATION_GROUP_LENGTH => {
909 PrimitiveValue::from(self.meta.information_group_length)
910 }
911 tags::FILE_META_INFORMATION_VERSION => {
912 PrimitiveValue::from(self.meta.information_version)
913 }
914 tags::MEDIA_STORAGE_SOP_CLASS_UID => {
915 PrimitiveValue::from(self.meta.media_storage_sop_class_uid.clone())
916 }
917 tags::MEDIA_STORAGE_SOP_INSTANCE_UID => {
918 PrimitiveValue::from(self.meta.media_storage_sop_instance_uid.clone())
919 }
920 tags::SOURCE_APPLICATION_ENTITY_TITLE => {
921 PrimitiveValue::from(self.meta.source_application_entity_title.clone().unwrap())
922 }
923 tags::SENDING_APPLICATION_ENTITY_TITLE => {
924 PrimitiveValue::from(self.meta.sending_application_entity_title.clone().unwrap())
925 }
926 tags::RECEIVING_APPLICATION_ENTITY_TITLE => PrimitiveValue::from(
927 self.meta
928 .receiving_application_entity_title
929 .clone()
930 .unwrap(),
931 ),
932 tags::TRANSFER_SYNTAX_UID => PrimitiveValue::from(self.meta.transfer_syntax.clone()),
933 tags::IMPLEMENTATION_CLASS_UID => {
934 PrimitiveValue::from(self.meta.implementation_class_uid.clone())
935 }
936 tags::IMPLEMENTATION_VERSION_NAME => {
937 PrimitiveValue::from(self.meta.implementation_version_name.clone().unwrap())
938 }
939 tags::PRIVATE_INFORMATION_CREATOR_UID => {
940 PrimitiveValue::from(self.meta.private_information_creator_uid.clone().unwrap())
941 }
942 tags::PRIVATE_INFORMATION => {
943 PrimitiveValue::from(self.meta.private_information.clone().unwrap())
944 }
945 _ => unreachable!(),
946 })
947 }
948
949 fn to_str(&self) -> std::result::Result<std::borrow::Cow<'_, str>, AttributeError> {
950 match Tag(0x0002, self.tag_e) {
951 tags::FILE_META_INFORMATION_GROUP_LENGTH => {
952 Ok(self.meta.information_group_length.to_string().into())
953 }
954 tags::FILE_META_INFORMATION_VERSION => Ok(format!(
955 "{:02X}{:02X}",
956 self.meta.information_version[0], self.meta.information_version[1]
957 )
958 .into()),
959 tags::MEDIA_STORAGE_SOP_CLASS_UID => {
960 Ok(Cow::Borrowed(self.meta.media_storage_sop_class_uid()))
961 }
962 tags::MEDIA_STORAGE_SOP_INSTANCE_UID => {
963 Ok(Cow::Borrowed(self.meta.media_storage_sop_instance_uid()))
964 }
965 tags::TRANSFER_SYNTAX_UID => Ok(Cow::Borrowed(self.meta.transfer_syntax())),
966 tags::IMPLEMENTATION_CLASS_UID => {
967 Ok(Cow::Borrowed(self.meta.implementation_class_uid()))
968 }
969 tags::IMPLEMENTATION_VERSION_NAME => Ok(self
970 .meta
971 .implementation_version_name
972 .as_deref()
973 .map(Cow::Borrowed)
974 .unwrap_or_default()),
975 tags::SOURCE_APPLICATION_ENTITY_TITLE => Ok(self
976 .meta
977 .source_application_entity_title
978 .as_deref()
979 .map(Cow::Borrowed)
980 .unwrap_or_default()),
981 tags::SENDING_APPLICATION_ENTITY_TITLE => Ok(self
982 .meta
983 .sending_application_entity_title
984 .as_deref()
985 .map(Cow::Borrowed)
986 .unwrap_or_default()),
987 tags::RECEIVING_APPLICATION_ENTITY_TITLE => Ok(self
988 .meta
989 .receiving_application_entity_title
990 .as_deref()
991 .map(Cow::Borrowed)
992 .unwrap_or_default()),
993 tags::PRIVATE_INFORMATION_CREATOR_UID => Ok(self
994 .meta
995 .private_information_creator_uid
996 .as_deref()
997 .map(|v| {
998 Cow::Borrowed(v.trim_end_matches(|c: char| c.is_whitespace() || c == '\0'))
999 })
1000 .unwrap_or_default()),
1001 tags::PRIVATE_INFORMATION => Err(AttributeError::ConvertValue {
1002 source: ConvertValueError {
1003 cause: None,
1004 original: ValueType::U8,
1005 requested: "str",
1006 },
1007 }),
1008 _ => unreachable!(),
1009 }
1010 }
1011
1012 fn item(&self, _index: u32) -> Result<Self::Item<'_>, AttributeError> {
1013 Err(AttributeError::NotDataSet)
1014 }
1015
1016 fn num_items(&self) -> Option<u32> {
1017 None
1018 }
1019
1020 fn fragment(&self, _index: u32) -> Result<Self::PixelData<'_>, AttributeError> {
1021 Err(AttributeError::NotPixelData)
1022 }
1023
1024 fn num_fragments(&self) -> Option<u32> {
1025 None
1026 }
1027}
1028
1029impl DicomObject for FileMetaTable {
1030 type Attribute<'a>
1031 = FileMetaAttribute<'a>
1032 where
1033 Self: 'a;
1034
1035 type LeafAttribute<'a>
1036 = FileMetaAttribute<'a>
1037 where
1038 Self: 'a;
1039
1040 fn attr_opt(
1041 &self,
1042 tag: Tag,
1043 ) -> std::result::Result<Option<Self::Attribute<'_>>, crate::AccessError> {
1044 if match tag {
1048 tags::FILE_META_INFORMATION_GROUP_LENGTH
1050 | tags::FILE_META_INFORMATION_VERSION
1051 | tags::MEDIA_STORAGE_SOP_CLASS_UID
1052 | tags::MEDIA_STORAGE_SOP_INSTANCE_UID
1053 | tags::TRANSFER_SYNTAX_UID
1054 | tags::IMPLEMENTATION_CLASS_UID
1055 | tags::IMPLEMENTATION_VERSION_NAME => true,
1056 tags::SOURCE_APPLICATION_ENTITY_TITLE
1058 if self.source_application_entity_title.is_some() =>
1059 {
1060 true
1061 }
1062 tags::SENDING_APPLICATION_ENTITY_TITLE
1063 if self.sending_application_entity_title.is_some() =>
1064 {
1065 true
1066 }
1067 tags::RECEIVING_APPLICATION_ENTITY_TITLE
1068 if self.receiving_application_entity_title.is_some() =>
1069 {
1070 true
1071 }
1072 tags::PRIVATE_INFORMATION_CREATOR_UID
1073 if self.private_information_creator_uid.is_some() =>
1074 {
1075 true
1076 }
1077 tags::PRIVATE_INFORMATION if self.private_information.is_some() => true,
1078 _ => false,
1079 } {
1080 Ok(Some(FileMetaAttribute {
1081 meta: self,
1082 tag_e: tag.element(),
1083 }))
1084 } else {
1085 Ok(None)
1086 }
1087 }
1088
1089 fn attr_by_name_opt<'a>(
1090 &'a self,
1091 name: &str,
1092 ) -> std::result::Result<Option<Self::Attribute<'a>>, crate::AccessByNameError> {
1093 let tag = match name {
1094 "FileMetaInformationGroupLength" => tags::FILE_META_INFORMATION_GROUP_LENGTH,
1095 "FileMetaInformationVersion" => tags::FILE_META_INFORMATION_VERSION,
1096 "MediaStorageSOPClassUID" => tags::MEDIA_STORAGE_SOP_CLASS_UID,
1097 "MediaStorageSOPInstanceUID" => tags::MEDIA_STORAGE_SOP_INSTANCE_UID,
1098 "TransferSyntaxUID" => tags::TRANSFER_SYNTAX_UID,
1099 "ImplementationClassUID" => tags::IMPLEMENTATION_CLASS_UID,
1100 "ImplementationVersionName" => tags::IMPLEMENTATION_VERSION_NAME,
1101 "SourceApplicationEntityTitle" => tags::SOURCE_APPLICATION_ENTITY_TITLE,
1102 "SendingApplicationEntityTitle" => tags::SENDING_APPLICATION_ENTITY_TITLE,
1103 "ReceivingApplicationEntityTitle" => tags::RECEIVING_APPLICATION_ENTITY_TITLE,
1104 "PrivateInformationCreatorUID" => tags::PRIVATE_INFORMATION_CREATOR_UID,
1105 "PrivateInformation" => tags::PRIVATE_INFORMATION,
1106 _ => return Ok(None),
1107 };
1108 self.attr_opt(tag)
1109 .map_err(|_| crate::NoSuchAttributeNameSnafu { name }.build())
1110 }
1111
1112 fn at(
1113 &self,
1114 selector: impl Into<AttributeSelector>,
1115 ) -> Result<Self::LeafAttribute<'_>, crate::AtAccessError> {
1116 let selector: AttributeSelector = selector.into();
1117 match selector.split_first() {
1118 (AttributeSelectorStep::Tag(tag), None) => self
1119 .attr(tag)
1120 .map_err(|_| AtAccessError::MissingLeafElement { selector }),
1121 (_, Some(_)) => crate::NotASequenceSnafu {
1122 selector,
1123 step_index: 0_u32,
1124 }
1125 .fail(),
1126 (_, None) => unreachable!("broken invariant: nested step at end of selector"),
1127 }
1128 }
1129}
1130
1131impl ApplyOp for FileMetaTable {
1132 type Err = ApplyError;
1133
1134 fn apply(&mut self, op: AttributeOp) -> ApplyResult {
1139 self.apply(op)
1140 }
1141}
1142
1143#[derive(Debug, Default, Clone)]
1145pub struct FileMetaTableBuilder {
1146 information_group_length: Option<u32>,
1148 information_version: Option<[u8; 2]>,
1150 media_storage_sop_class_uid: Option<String>,
1152 media_storage_sop_instance_uid: Option<String>,
1154 transfer_syntax: Option<String>,
1156 implementation_class_uid: Option<String>,
1158
1159 implementation_version_name: Option<String>,
1161 source_application_entity_title: Option<String>,
1163 sending_application_entity_title: Option<String>,
1165 receiving_application_entity_title: Option<String>,
1167 private_information_creator_uid: Option<String>,
1169 private_information: Option<Vec<u8>>,
1171}
1172
1173#[inline]
1176fn padded<T>(s: T, pad: char) -> String
1177where
1178 T: Into<String>,
1179{
1180 let mut s = s.into();
1181 if s.len() % 2 == 1 {
1182 s.push(pad);
1183 }
1184 s
1185}
1186
1187fn ui_padded<T>(s: T) -> String
1189where
1190 T: Into<String>,
1191{
1192 padded(s, '\0')
1193}
1194
1195fn txt_padded<T>(s: T) -> String
1197where
1198 T: Into<String>,
1199{
1200 padded(s, ' ')
1201}
1202
1203impl FileMetaTableBuilder {
1204 pub fn new() -> FileMetaTableBuilder {
1206 FileMetaTableBuilder::default()
1207 }
1208
1209 pub fn group_length(mut self, value: u32) -> FileMetaTableBuilder {
1211 self.information_group_length = Some(value);
1212 self
1213 }
1214
1215 pub fn information_version(mut self, value: [u8; 2]) -> FileMetaTableBuilder {
1217 self.information_version = Some(value);
1218 self
1219 }
1220
1221 pub fn media_storage_sop_class_uid<T>(mut self, value: T) -> FileMetaTableBuilder
1223 where
1224 T: Into<String>,
1225 {
1226 self.media_storage_sop_class_uid = Some(ui_padded(value));
1227 self
1228 }
1229
1230 pub fn media_storage_sop_instance_uid<T>(mut self, value: T) -> FileMetaTableBuilder
1232 where
1233 T: Into<String>,
1234 {
1235 self.media_storage_sop_instance_uid = Some(ui_padded(value));
1236 self
1237 }
1238
1239 pub fn transfer_syntax<T>(mut self, value: T) -> FileMetaTableBuilder
1241 where
1242 T: Into<String>,
1243 {
1244 self.transfer_syntax = Some(ui_padded(value));
1245 self
1246 }
1247
1248 pub fn implementation_class_uid<T>(mut self, value: T) -> FileMetaTableBuilder
1250 where
1251 T: Into<String>,
1252 {
1253 self.implementation_class_uid = Some(ui_padded(value));
1254 self
1255 }
1256
1257 pub fn implementation_version_name<T>(mut self, value: T) -> FileMetaTableBuilder
1259 where
1260 T: Into<String>,
1261 {
1262 self.implementation_version_name = Some(txt_padded(value));
1263 self
1264 }
1265
1266 pub fn source_application_entity_title<T>(mut self, value: T) -> FileMetaTableBuilder
1268 where
1269 T: Into<String>,
1270 {
1271 self.source_application_entity_title = Some(txt_padded(value));
1272 self
1273 }
1274
1275 pub fn sending_application_entity_title<T>(mut self, value: T) -> FileMetaTableBuilder
1277 where
1278 T: Into<String>,
1279 {
1280 self.sending_application_entity_title = Some(txt_padded(value));
1281 self
1282 }
1283
1284 pub fn receiving_application_entity_title<T>(mut self, value: T) -> FileMetaTableBuilder
1286 where
1287 T: Into<String>,
1288 {
1289 self.receiving_application_entity_title = Some(txt_padded(value));
1290 self
1291 }
1292
1293 pub fn private_information_creator_uid<T>(mut self, value: T) -> FileMetaTableBuilder
1295 where
1296 T: Into<String>,
1297 {
1298 self.private_information_creator_uid = Some(ui_padded(value));
1299 self
1300 }
1301
1302 pub fn private_information<T>(mut self, value: T) -> FileMetaTableBuilder
1304 where
1305 T: Into<Vec<u8>>,
1306 {
1307 self.private_information = Some(value.into());
1308 self
1309 }
1310
1311 pub fn build(self) -> Result<FileMetaTable> {
1313 let information_version = self.information_version.unwrap_or(
1314 [0, 1],
1316 );
1317 let media_storage_sop_class_uid = self.media_storage_sop_class_uid.unwrap_or_else(|| {
1318 tracing::warn!("MediaStorageSOPClassUID is missing. Defaulting to empty string.");
1319 String::default()
1320 });
1321 let media_storage_sop_instance_uid =
1322 self.media_storage_sop_instance_uid.unwrap_or_else(|| {
1323 tracing::warn!(
1324 "MediaStorageSOPInstanceUID is missing. Defaulting to empty string."
1325 );
1326 String::default()
1327 });
1328 let transfer_syntax = self.transfer_syntax.context(MissingElementSnafu {
1329 alias: "TransferSyntax",
1330 })?;
1331 let mut implementation_version_name = self.implementation_version_name;
1332 let implementation_class_uid = self.implementation_class_uid.unwrap_or_else(|| {
1333 implementation_version_name = Some(IMPLEMENTATION_VERSION_NAME.to_string());
1335
1336 IMPLEMENTATION_CLASS_UID.to_string()
1337 });
1338
1339 let mut table = FileMetaTable {
1340 information_group_length: 0x00,
1342 information_version,
1343 media_storage_sop_class_uid,
1344 media_storage_sop_instance_uid,
1345 transfer_syntax,
1346 implementation_class_uid,
1347 implementation_version_name,
1348 source_application_entity_title: self.source_application_entity_title,
1349 sending_application_entity_title: self.sending_application_entity_title,
1350 receiving_application_entity_title: self.receiving_application_entity_title,
1351 private_information_creator_uid: self.private_information_creator_uid,
1352 private_information: self.private_information,
1353 };
1354 table.update_information_group_length();
1355 debug_assert!(table.information_group_length > 0);
1356 Ok(table)
1357 }
1358}
1359
1360fn dicom_len<T: AsRef<str>>(x: T) -> u32 {
1361 (x.as_ref().len() as u32 + 1) & !1
1362}
1363
1364#[cfg(test)]
1365mod tests {
1366 use crate::{IMPLEMENTATION_CLASS_UID, IMPLEMENTATION_VERSION_NAME};
1367
1368 use super::{dicom_len, FileMetaTable, FileMetaTableBuilder};
1369 use dicom_core::ops::{AttributeAction, AttributeOp};
1370 use dicom_core::value::Value;
1371 use dicom_core::{dicom_value, DataElement, PrimitiveValue, Tag, VR};
1372 use dicom_dictionary_std::tags;
1373
1374 const TEST_META_1: &[u8] = &[
1375 b'D', b'I', b'C', b'M',
1377 0x02, 0x00, 0x00, 0x00, b'U', b'L', 0x04, 0x00, 0xc8, 0x00, 0x00, 0x00,
1379 0x02, 0x00, 0x01, 0x00, b'O', b'B', 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x01,
1381 0x02, 0x00, 0x02, 0x00, b'U', b'I', 0x1a, 0x00, 0x31, 0x2e, 0x32, 0x2e, 0x38, 0x34, 0x30,
1383 0x2e, 0x31, 0x30, 0x30, 0x30, 0x38, 0x2e, 0x35, 0x2e, 0x31, 0x2e, 0x34, 0x2e, 0x31, 0x2e,
1384 0x31, 0x2e, 0x31, 0x00,
1385 0x02, 0x00, 0x03, 0x00, b'U', b'I', 0x38, 0x00, 0x31, 0x2e, 0x32, 0x2e, 0x33, 0x2e, 0x34,
1387 0x2e, 0x35, 0x2e, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x2e, 0x31, 0x32, 0x33,
1388 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x30, 0x2e, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
1389 0x2e, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x2e, 0x31, 0x32, 0x33, 0x34,
1390 0x35, 0x36, 0x37, 0x00,
1391 0x02, 0x00, 0x10, 0x00, b'U', b'I', 0x14, 0x00, 0x31, 0x2e, 0x32, 0x2e, 0x38, 0x34, 0x30,
1393 0x2e, 0x31, 0x30, 0x30, 0x30, 0x38, 0x2e, 0x31, 0x2e, 0x32, 0x2e, 0x31, 0x00,
1394 0x02, 0x00, 0x12, 0x00, b'U', b'I', 0x14, 0x00, 0x31, 0x2e, 0x32, 0x2e, 0x33, 0x34, 0x35,
1396 0x2e, 0x36, 0x2e, 0x37, 0x38, 0x39, 0x30, 0x2e, 0x31, 0x2e, 0x32, 0x33, 0x34,
1397 0x02, 0x00, 0x13, 0x00, b'S', b'H', 0x10, 0x00, 0x52, 0x55, 0x53, 0x54, 0x59, 0x5f, 0x44,
1401 0x49, 0x43, 0x4f, 0x4d, 0x5f, 0x32, 0x36, 0x39, 0x20,
1402 0x02, 0x00, 0x16, 0x00, b'A', b'E', 0x00, 0x00,
1404 ];
1405
1406 #[test]
1407 fn read_meta_table_from_reader() {
1408 let mut source = TEST_META_1;
1409
1410 let table = FileMetaTable::from_reader(&mut source).unwrap();
1411
1412 let gt = FileMetaTable {
1413 information_group_length: 200,
1414 information_version: [0u8, 1u8],
1415 media_storage_sop_class_uid: "1.2.840.10008.5.1.4.1.1.1\0".to_owned(),
1416 media_storage_sop_instance_uid:
1417 "1.2.3.4.5.12345678.1234567890.1234567.123456789.1234567\0".to_owned(),
1418 transfer_syntax: "1.2.840.10008.1.2.1\0".to_owned(),
1419 implementation_class_uid: "1.2.345.6.7890.1.234".to_owned(),
1420 implementation_version_name: Some("RUSTY_DICOM_269 ".to_owned()),
1421 source_application_entity_title: Some("".to_owned()),
1422 sending_application_entity_title: None,
1423 receiving_application_entity_title: None,
1424 private_information_creator_uid: None,
1425 private_information: None,
1426 };
1427
1428 assert_eq!(table.information_group_length, 200);
1429 assert_eq!(table.information_version, [0u8, 1u8]);
1430 assert_eq!(
1431 table.media_storage_sop_class_uid,
1432 "1.2.840.10008.5.1.4.1.1.1\0"
1433 );
1434 assert_eq!(
1435 table.media_storage_sop_instance_uid,
1436 "1.2.3.4.5.12345678.1234567890.1234567.123456789.1234567\0"
1437 );
1438 assert_eq!(table.transfer_syntax, "1.2.840.10008.1.2.1\0");
1439 assert_eq!(table.implementation_class_uid, "1.2.345.6.7890.1.234");
1440 assert_eq!(
1441 table.implementation_version_name,
1442 Some("RUSTY_DICOM_269 ".to_owned())
1443 );
1444 assert_eq!(table.source_application_entity_title, Some("".into()));
1445 assert_eq!(table.sending_application_entity_title, None);
1446 assert_eq!(table.receiving_application_entity_title, None);
1447 assert_eq!(table.private_information_creator_uid, None);
1448 assert_eq!(table.private_information, None);
1449
1450 assert_eq!(table, gt);
1451 }
1452
1453 #[test]
1454 fn create_meta_table_with_builder() {
1455 let table = FileMetaTableBuilder::new()
1456 .information_version([0, 1])
1457 .media_storage_sop_class_uid("1.2.840.10008.5.1.4.1.1.1")
1458 .media_storage_sop_instance_uid(
1459 "1.2.3.4.5.12345678.1234567890.1234567.123456789.1234567",
1460 )
1461 .transfer_syntax("1.2.840.10008.1.2.1")
1462 .implementation_class_uid("1.2.345.6.7890.1.234")
1463 .implementation_version_name("RUSTY_DICOM_269")
1464 .source_application_entity_title("")
1465 .build()
1466 .unwrap();
1467
1468 let gt = FileMetaTable {
1469 information_group_length: 200,
1470 information_version: [0u8, 1u8],
1471 media_storage_sop_class_uid: "1.2.840.10008.5.1.4.1.1.1\0".to_owned(),
1472 media_storage_sop_instance_uid:
1473 "1.2.3.4.5.12345678.1234567890.1234567.123456789.1234567\0".to_owned(),
1474 transfer_syntax: "1.2.840.10008.1.2.1\0".to_owned(),
1475 implementation_class_uid: "1.2.345.6.7890.1.234".to_owned(),
1476 implementation_version_name: Some("RUSTY_DICOM_269 ".to_owned()),
1477 source_application_entity_title: Some("".to_owned()),
1478 sending_application_entity_title: None,
1479 receiving_application_entity_title: None,
1480 private_information_creator_uid: None,
1481 private_information: None,
1482 };
1483
1484 assert_eq!(table.information_group_length, gt.information_group_length);
1485 assert_eq!(table, gt);
1486 }
1487
1488 #[test]
1490 fn create_meta_table_with_builder_minimal() {
1491 let table = FileMetaTableBuilder::new()
1492 .media_storage_sop_class_uid("1.2.840.10008.5.1.4.1.1.1")
1493 .media_storage_sop_instance_uid(
1494 "1.2.3.4.5.12345678.1234567890.1234567.123456789.1234567",
1495 )
1496 .transfer_syntax("1.2.840.10008.1.2")
1497 .build()
1498 .unwrap();
1499
1500 let gt = FileMetaTable {
1501 information_group_length: 154
1502 + dicom_len(IMPLEMENTATION_CLASS_UID)
1503 + dicom_len(IMPLEMENTATION_VERSION_NAME),
1504 information_version: [0u8, 1u8],
1505 media_storage_sop_class_uid: "1.2.840.10008.5.1.4.1.1.1\0".to_owned(),
1506 media_storage_sop_instance_uid:
1507 "1.2.3.4.5.12345678.1234567890.1234567.123456789.1234567\0".to_owned(),
1508 transfer_syntax: "1.2.840.10008.1.2\0".to_owned(),
1509 implementation_class_uid: IMPLEMENTATION_CLASS_UID.to_owned(),
1510 implementation_version_name: Some(IMPLEMENTATION_VERSION_NAME.to_owned()),
1511 source_application_entity_title: None,
1512 sending_application_entity_title: None,
1513 receiving_application_entity_title: None,
1514 private_information_creator_uid: None,
1515 private_information: None,
1516 };
1517
1518 assert_eq!(table.information_group_length, gt.information_group_length);
1519 assert_eq!(table, gt);
1520 }
1521
1522 #[test]
1524 fn change_transfer_syntax_update_table() {
1525 let mut table = FileMetaTableBuilder::new()
1526 .media_storage_sop_class_uid("1.2.840.10008.5.1.4.1.1.1")
1527 .media_storage_sop_instance_uid(
1528 "1.2.3.4.5.12345678.1234567890.1234567.123456789.1234567",
1529 )
1530 .transfer_syntax("1.2.840.10008.1.2.1")
1531 .build()
1532 .unwrap();
1533
1534 assert_eq!(
1535 table.information_group_length,
1536 156 + dicom_len(IMPLEMENTATION_CLASS_UID) + dicom_len(IMPLEMENTATION_VERSION_NAME)
1537 );
1538
1539 table.set_transfer_syntax(
1540 &dicom_transfer_syntax_registry::entries::IMPLICIT_VR_LITTLE_ENDIAN,
1541 );
1542 assert_eq!(
1543 table.information_group_length,
1544 154 + dicom_len(IMPLEMENTATION_CLASS_UID) + dicom_len(IMPLEMENTATION_VERSION_NAME)
1545 );
1546 }
1547
1548 #[test]
1549 fn read_meta_table_into_iter() {
1550 let table = FileMetaTable {
1551 information_group_length: 200,
1552 information_version: [0u8, 1u8],
1553 media_storage_sop_class_uid: "1.2.840.10008.5.1.4.1.1.1\0".to_owned(),
1554 media_storage_sop_instance_uid:
1555 "1.2.3.4.5.12345678.1234567890.1234567.123456789.1234567\0".to_owned(),
1556 transfer_syntax: "1.2.840.10008.1.2.1\0".to_owned(),
1557 implementation_class_uid: "1.2.345.6.7890.1.234".to_owned(),
1558 implementation_version_name: Some("RUSTY_DICOM_269 ".to_owned()),
1559 source_application_entity_title: Some("".to_owned()),
1560 sending_application_entity_title: None,
1561 receiving_application_entity_title: None,
1562 private_information_creator_uid: None,
1563 private_information: None,
1564 };
1565
1566 assert_eq!(table.calculate_information_group_length(), 200);
1567
1568 let gt = vec![
1569 DataElement::new(Tag(0x0002, 0x0000), VR::UL, dicom_value!(U32, 200)),
1571 DataElement::new(Tag(0x0002, 0x0001), VR::OB, dicom_value!(U8, [0, 1])),
1573 DataElement::new(
1575 Tag(0x0002, 0x0002),
1576 VR::UI,
1577 Value::Primitive("1.2.840.10008.5.1.4.1.1.1\0".into()),
1578 ),
1579 DataElement::new(
1581 Tag(0x0002, 0x0003),
1582 VR::UI,
1583 Value::Primitive(
1584 "1.2.3.4.5.12345678.1234567890.1234567.123456789.1234567\0".into(),
1585 ),
1586 ),
1587 DataElement::new(
1589 Tag(0x0002, 0x0010),
1590 VR::UI,
1591 Value::Primitive("1.2.840.10008.1.2.1\0".into()),
1592 ),
1593 DataElement::new(
1595 Tag(0x0002, 0x0012),
1596 VR::UI,
1597 Value::Primitive("1.2.345.6.7890.1.234".into()),
1598 ),
1599 DataElement::new(
1601 Tag(0x0002, 0x0013),
1602 VR::SH,
1603 Value::Primitive("RUSTY_DICOM_269 ".into()),
1604 ),
1605 DataElement::new(Tag(0x0002, 0x0016), VR::AE, Value::Primitive("".into())),
1607 ];
1608
1609 let elems: Vec<_> = table.into_element_iter().collect();
1610 assert_eq!(elems, gt);
1611 }
1612
1613 #[test]
1614 fn update_table_with_length() {
1615 let mut table = FileMetaTable {
1616 information_group_length: 55, information_version: [0u8, 1u8],
1618 media_storage_sop_class_uid: "1.2.840.10008.5.1.4.1.1.1\0".to_owned(),
1619 media_storage_sop_instance_uid:
1620 "1.2.3.4.5.12345678.1234567890.1234567.123456789.1234567\0".to_owned(),
1621 transfer_syntax: "1.2.840.10008.1.2.1\0".to_owned(),
1622 implementation_class_uid: "1.2.345.6.7890.1.234".to_owned(),
1623 implementation_version_name: Some("RUSTY_DICOM_269 ".to_owned()),
1624 source_application_entity_title: Some("".to_owned()),
1625 sending_application_entity_title: None,
1626 receiving_application_entity_title: None,
1627 private_information_creator_uid: None,
1628 private_information: None,
1629 };
1630
1631 table.update_information_group_length();
1632
1633 assert_eq!(table.information_group_length, 200);
1634 }
1635
1636 #[test]
1637 fn table_ops() {
1638 let mut table = FileMetaTable {
1639 information_group_length: 200,
1640 information_version: [0u8, 1u8],
1641 media_storage_sop_class_uid: "1.2.840.10008.5.1.4.1.1.1\0".to_owned(),
1642 media_storage_sop_instance_uid:
1643 "1.2.3.4.5.12345678.1234567890.1234567.123456789.1234567\0".to_owned(),
1644 transfer_syntax: "1.2.840.10008.1.2.1\0".to_owned(),
1645 implementation_class_uid: "1.2.345.6.7890.1.234".to_owned(),
1646 implementation_version_name: None,
1647 source_application_entity_title: None,
1648 sending_application_entity_title: None,
1649 receiving_application_entity_title: None,
1650 private_information_creator_uid: None,
1651 private_information: None,
1652 };
1653
1654 table
1656 .apply(AttributeOp::new(
1657 tags::IMPLEMENTATION_VERSION_NAME,
1658 AttributeAction::ReplaceStr("MY_DICOM_1.1".into()),
1659 ))
1660 .unwrap();
1661
1662 assert_eq!(table.implementation_version_name, None);
1663
1664 table
1666 .apply(AttributeOp::new(
1667 tags::IMPLEMENTATION_VERSION_NAME,
1668 AttributeAction::SetStr("MY_DICOM_1.1".into()),
1669 ))
1670 .unwrap();
1671
1672 assert_eq!(
1673 table.implementation_version_name.as_deref(),
1674 Some("MY_DICOM_1.1"),
1675 );
1676
1677 table
1679 .apply(AttributeOp::new(
1680 tags::SOURCE_APPLICATION_ENTITY_TITLE,
1681 AttributeAction::Set(PrimitiveValue::Str("RICOOGLE-STORAGE".into())),
1682 ))
1683 .unwrap();
1684
1685 assert_eq!(
1686 table.source_application_entity_title.as_deref(),
1687 Some("RICOOGLE-STORAGE"),
1688 );
1689
1690 table
1692 .apply(AttributeOp::new(
1693 tags::SOURCE_APPLICATION_ENTITY_TITLE,
1694 AttributeAction::SetStrIfMissing("STORE-SCU".into()),
1695 ))
1696 .unwrap();
1697
1698 assert_eq!(
1699 table.source_application_entity_title.as_deref(),
1700 Some("RICOOGLE-STORAGE"),
1701 );
1702
1703 table
1704 .apply(AttributeOp::new(
1705 tags::SENDING_APPLICATION_ENTITY_TITLE,
1706 AttributeAction::SetStrIfMissing("STORE-SCU".into()),
1707 ))
1708 .unwrap();
1709
1710 assert_eq!(
1711 table.sending_application_entity_title.as_deref(),
1712 Some("STORE-SCU"),
1713 );
1714
1715 table
1717 .apply(AttributeOp::new(
1718 tags::MEDIA_STORAGE_SOP_CLASS_UID,
1719 AttributeAction::Replace(PrimitiveValue::Str("1.2.840.10008.5.1.4.1.1.7".into())),
1720 ))
1721 .unwrap();
1722
1723 assert_eq!(
1724 table.media_storage_sop_class_uid(),
1725 "1.2.840.10008.5.1.4.1.1.7",
1726 );
1727 }
1728
1729 #[test]
1732 fn write_read_does_not_fail() {
1733 let mut table = FileMetaTable {
1734 information_group_length: 0,
1735 information_version: [0u8, 1u8],
1736 media_storage_sop_class_uid: "1.2.840.10008.5.1.4.1.1.7".to_owned(),
1737 media_storage_sop_instance_uid: "2.25.137731752600317795446120660167595746868"
1738 .to_owned(),
1739 transfer_syntax: "1.2.840.10008.1.2.4.91".to_owned(),
1740 implementation_class_uid: "2.25.305828488182831875890203105390285383139".to_owned(),
1741 implementation_version_name: Some("MYTOOL100".to_owned()),
1742 source_application_entity_title: Some("RUSTY".to_owned()),
1743 receiving_application_entity_title: None,
1744 sending_application_entity_title: None,
1745 private_information_creator_uid: None,
1746 private_information: None,
1747 };
1748
1749 table.update_information_group_length();
1750
1751 let mut buf = vec![b'D', b'I', b'C', b'M'];
1752 table.write(&mut buf).unwrap();
1753
1754 let table2 = FileMetaTable::from_reader(&mut buf.as_slice())
1755 .expect("Should not fail to read the table from the written data");
1756
1757 assert_eq!(
1758 table.information_group_length,
1759 table2.information_group_length
1760 );
1761 }
1762
1763 #[test]
1765 fn dicom_object_api() {
1766 use crate::{DicomAttribute as _, DicomObject as _};
1767 use dicom_dictionary_std::uids;
1768
1769 let meta = FileMetaTableBuilder::new()
1770 .transfer_syntax(uids::RLE_LOSSLESS)
1771 .media_storage_sop_class_uid(uids::ENHANCED_MR_IMAGE_STORAGE)
1772 .media_storage_sop_instance_uid("2.25.94766187067244888884745908966163363746")
1773 .implementation_version_name("RUSTY_DICOM_269")
1774 .build()
1775 .unwrap();
1776
1777 assert_eq!(
1778 meta.attr(tags::TRANSFER_SYNTAX_UID)
1779 .unwrap()
1780 .to_str()
1781 .unwrap(),
1782 uids::RLE_LOSSLESS
1783 );
1784
1785 let sop_class_uid = meta.attr_opt(tags::MEDIA_STORAGE_SOP_CLASS_UID).unwrap();
1786 let sop_class_uid = sop_class_uid.as_ref().map(|v| v.to_str().unwrap());
1787 assert_eq!(
1788 sop_class_uid.as_deref(),
1789 Some(uids::ENHANCED_MR_IMAGE_STORAGE)
1790 );
1791
1792 assert_eq!(
1793 meta.attr_by_name("MediaStorageSOPInstanceUID")
1794 .unwrap()
1795 .to_str()
1796 .unwrap(),
1797 "2.25.94766187067244888884745908966163363746"
1798 );
1799
1800 assert!(meta.attr_opt(tags::PRIVATE_INFORMATION).unwrap().is_none());
1801 }
1802}