Skip to main content

dicom_toolkit_data/
meta_info.rs

1//! DICOM File Meta Information (group 0002).
2//!
3//! Manages the header present in every DICOM Part 10 file.
4
5use crate::dataset::DataSet;
6use crate::element::Element;
7use dicom_toolkit_core::error::{DcmError, DcmResult};
8use dicom_toolkit_dict::{tags, Vr};
9
10// ── Constants ─────────────────────────────────────────────────────────────────
11
12/// File Meta Information Version (0002,0001): OB value [0x00, 0x01].
13pub const META_VERSION: [u8; 2] = [0x00, 0x01];
14
15/// Implementation class UID for this library.
16pub const IMPL_CLASS_UID: &str = "1.2.276.0.7230010.3.1.9999.1";
17
18/// Implementation version name for this library.
19pub const IMPL_VERSION_NAME: &str = "DCMTK-RS-0.1";
20
21// ── FileMetaInformation ───────────────────────────────────────────────────────
22
23/// DICOM File Meta Information (group 0002,xxxx).
24///
25/// Every DICOM Part 10 file starts with this header, always encoded as
26/// Explicit VR Little Endian.
27#[derive(Debug, Clone, PartialEq)]
28pub struct FileMetaInformation {
29    pub media_storage_sop_class_uid: String,
30    pub media_storage_sop_instance_uid: String,
31    pub transfer_syntax_uid: String,
32    pub implementation_class_uid: String,
33    pub implementation_version_name: String,
34}
35
36impl FileMetaInformation {
37    /// Create new File Meta Information with default implementation identifiers.
38    pub fn new(sop_class_uid: &str, sop_instance_uid: &str, ts_uid: &str) -> Self {
39        Self {
40            media_storage_sop_class_uid: sop_class_uid.to_string(),
41            media_storage_sop_instance_uid: sop_instance_uid.to_string(),
42            transfer_syntax_uid: ts_uid.to_string(),
43            implementation_class_uid: IMPL_CLASS_UID.to_string(),
44            implementation_version_name: IMPL_VERSION_NAME.to_string(),
45        }
46    }
47
48    /// Convert to a DataSet for encoding.
49    ///
50    /// Does NOT include (0002,0000) group length — the writer computes that.
51    pub fn to_dataset(&self) -> DataSet {
52        let mut ds = DataSet::new();
53
54        ds.insert(Element::bytes(
55            tags::FILE_META_INFORMATION_VERSION,
56            Vr::OB,
57            META_VERSION.to_vec(),
58        ));
59        ds.insert(Element::uid(
60            tags::MEDIA_STORAGE_SOP_CLASS_UID,
61            &self.media_storage_sop_class_uid,
62        ));
63        ds.insert(Element::uid(
64            tags::MEDIA_STORAGE_SOP_INSTANCE_UID,
65            &self.media_storage_sop_instance_uid,
66        ));
67        ds.insert(Element::uid(
68            tags::TRANSFER_SYNTAX_UID,
69            &self.transfer_syntax_uid,
70        ));
71        ds.insert(Element::uid(
72            tags::IMPLEMENTATION_CLASS_UID,
73            &self.implementation_class_uid,
74        ));
75        ds.insert(Element::string(
76            tags::IMPLEMENTATION_VERSION_NAME,
77            Vr::SH,
78            &self.implementation_version_name,
79        ));
80
81        ds
82    }
83
84    /// Parse from a DataSet read from the file.
85    pub fn from_dataset(ds: &DataSet) -> DcmResult<Self> {
86        let transfer_syntax_uid = ds
87            .get_string(tags::TRANSFER_SYNTAX_UID)
88            .ok_or_else(|| DcmError::InvalidFile {
89                reason: "missing Transfer Syntax UID (0002,0010)".into(),
90            })?
91            .trim_end_matches('\0')
92            .to_string();
93
94        let media_storage_sop_class_uid = ds
95            .get_string(tags::MEDIA_STORAGE_SOP_CLASS_UID)
96            .unwrap_or("")
97            .trim_end_matches('\0')
98            .to_string();
99
100        let media_storage_sop_instance_uid = ds
101            .get_string(tags::MEDIA_STORAGE_SOP_INSTANCE_UID)
102            .unwrap_or("")
103            .trim_end_matches('\0')
104            .to_string();
105
106        let implementation_class_uid = ds
107            .get_string(tags::IMPLEMENTATION_CLASS_UID)
108            .unwrap_or(IMPL_CLASS_UID)
109            .trim_end_matches('\0')
110            .to_string();
111
112        let implementation_version_name = ds
113            .get_string(tags::IMPLEMENTATION_VERSION_NAME)
114            .unwrap_or(IMPL_VERSION_NAME)
115            .trim_end_matches('\0')
116            .to_string();
117
118        Ok(Self {
119            media_storage_sop_class_uid,
120            media_storage_sop_instance_uid,
121            transfer_syntax_uid,
122            implementation_class_uid,
123            implementation_version_name,
124        })
125    }
126}
127
128// ── Tests ─────────────────────────────────────────────────────────────────────
129
130#[cfg(test)]
131mod tests {
132    use super::*;
133
134    #[test]
135    fn meta_roundtrip_to_from_dataset() {
136        let meta = FileMetaInformation::new(
137            "1.2.840.10008.5.1.4.1.1.2",
138            "1.2.3.4.5.6.7",
139            "1.2.840.10008.1.2.1",
140        );
141        let ds = meta.to_dataset();
142        let back = FileMetaInformation::from_dataset(&ds).unwrap();
143        assert_eq!(meta.transfer_syntax_uid, back.transfer_syntax_uid);
144        assert_eq!(
145            meta.media_storage_sop_class_uid,
146            back.media_storage_sop_class_uid
147        );
148        assert_eq!(
149            meta.media_storage_sop_instance_uid,
150            back.media_storage_sop_instance_uid
151        );
152    }
153
154    #[test]
155    fn meta_from_dataset_missing_ts_uid_errors() {
156        let ds = DataSet::new();
157        assert!(FileMetaInformation::from_dataset(&ds).is_err());
158    }
159
160    #[test]
161    fn meta_to_dataset_has_version() {
162        let meta = FileMetaInformation::new("", "", "1.2.840.10008.1.2.1");
163        let ds = meta.to_dataset();
164        let ver = ds.get_bytes(tags::FILE_META_INFORMATION_VERSION).unwrap();
165        assert_eq!(ver, &[0x00, 0x01]);
166    }
167}