Skip to main content

dicom_toolkit_data/
element.rs

1//! DICOM data element — a `(Tag, VR, Value)` triple.
2//!
3//! Ports DCMTK's `DcmElement` hierarchy into a single flat struct.
4
5use crate::dataset::DataSet;
6use crate::value::Value;
7use dicom_toolkit_dict::{Tag, Vr};
8use std::fmt;
9
10/// A single DICOM data element.
11#[derive(Debug, Clone, PartialEq)]
12pub struct Element {
13    pub tag: Tag,
14    pub vr: Vr,
15    pub value: Value,
16}
17
18impl Element {
19    // ── Constructors ──────────────────────────────────────────────────────────
20
21    pub fn new(tag: Tag, vr: Vr, value: Value) -> Self {
22        Self { tag, vr, value }
23    }
24
25    /// Single or multi-valued string element.
26    pub fn string(tag: Tag, vr: Vr, s: &str) -> Self {
27        Self::new(tag, vr, Value::Strings(vec![s.to_string()]))
28    }
29
30    /// Multi-valued string element from a slice.
31    pub fn strings(tag: Tag, vr: Vr, values: &[&str]) -> Self {
32        Self::new(
33            tag,
34            vr,
35            Value::Strings(values.iter().map(|s| s.to_string()).collect()),
36        )
37    }
38
39    pub fn u16(tag: Tag, value: u16) -> Self {
40        Self::new(tag, Vr::US, Value::U16(vec![value]))
41    }
42
43    pub fn u32(tag: Tag, value: u32) -> Self {
44        Self::new(tag, Vr::UL, Value::U32(vec![value]))
45    }
46
47    pub fn i32(tag: Tag, value: i32) -> Self {
48        Self::new(tag, Vr::SL, Value::I32(vec![value]))
49    }
50
51    pub fn f64(tag: Tag, value: f64) -> Self {
52        Self::new(tag, Vr::FD, Value::F64(vec![value]))
53    }
54
55    pub fn bytes(tag: Tag, vr: Vr, data: Vec<u8>) -> Self {
56        Self::new(tag, vr, Value::U8(data))
57    }
58
59    pub fn sequence(tag: Tag, items: Vec<DataSet>) -> Self {
60        Self::new(tag, Vr::SQ, Value::Sequence(items))
61    }
62
63    pub fn uid(tag: Tag, uid: &str) -> Self {
64        Self::new(tag, Vr::UI, Value::Uid(uid.to_string()))
65    }
66
67    // ── Getters ───────────────────────────────────────────────────────────────
68
69    pub fn is_empty(&self) -> bool {
70        self.value.is_empty()
71    }
72
73    pub fn string_value(&self) -> Option<&str> {
74        self.value.as_string()
75    }
76
77    pub fn strings_value(&self) -> Option<&[String]> {
78        self.value.as_strings()
79    }
80
81    pub fn u16_value(&self) -> Option<u16> {
82        self.value.as_u16()
83    }
84
85    pub fn u32_value(&self) -> Option<u32> {
86        self.value.as_u32()
87    }
88
89    pub fn i32_value(&self) -> Option<i32> {
90        self.value.as_i32()
91    }
92
93    pub fn f64_value(&self) -> Option<f64> {
94        self.value.as_f64()
95    }
96
97    pub fn bytes_value(&self) -> Option<&[u8]> {
98        self.value.as_bytes()
99    }
100
101    pub fn items(&self) -> Option<&[DataSet]> {
102        match &self.value {
103            Value::Sequence(items) => Some(items.as_slice()),
104            _ => None,
105        }
106    }
107}
108
109impl fmt::Display for Element {
110    /// Format like dcmdump: `(GGGG,EEEE) VR [value] # length, multiplicity`
111    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
112        let tag_str = format!("({:04X},{:04X})", self.tag.group, self.tag.element);
113        let vr_str = self.vr.code();
114        let display_val = self.value.to_display_string();
115        let length = self.value.encoded_len();
116        let mult = self.value.multiplicity();
117
118        // String VRs use [brackets]; numeric and binary VRs display bare.
119        let value_part = if self.vr.is_string()
120            || matches!(self.vr, Vr::UI | Vr::PN | Vr::DA | Vr::TM | Vr::DT | Vr::AT)
121        {
122            if display_val.is_empty() {
123                "(no value available)".to_string()
124            } else {
125                format!("[{}]", display_val)
126            }
127        } else {
128            display_val
129        };
130
131        write!(
132            f,
133            "{} {} {} # {}, {}",
134            tag_str, vr_str, value_part, length, mult
135        )
136    }
137}
138
139// ── Tests ─────────────────────────────────────────────────────────────────────
140
141#[cfg(test)]
142mod tests {
143    use super::*;
144    use dicom_toolkit_dict::tags;
145
146    #[test]
147    fn element_string_roundtrip() {
148        let e = Element::string(tags::PATIENT_NAME, Vr::PN, "Smith^John");
149        assert_eq!(e.string_value(), Some("Smith^John"));
150        assert!(!e.is_empty());
151    }
152
153    #[test]
154    fn element_strings_roundtrip() {
155        let e = Element::strings(tags::IMAGE_TYPE, Vr::CS, &["ORIGINAL", "PRIMARY"]);
156        let s = e.strings_value().unwrap();
157        assert_eq!(s, &["ORIGINAL", "PRIMARY"]);
158        assert_eq!(e.value.multiplicity(), 2);
159    }
160
161    #[test]
162    fn element_u16_roundtrip() {
163        let e = Element::u16(tags::ROWS, 512);
164        assert_eq!(e.u16_value(), Some(512));
165        assert_eq!(e.vr, Vr::US);
166    }
167
168    #[test]
169    fn element_u32_roundtrip() {
170        let e = Element::u32(tags::STUDY_INSTANCE_UID, 999);
171        // tag mismatch in real DICOM, but tests constructors
172        assert_eq!(e.u32_value(), Some(999));
173        assert_eq!(e.vr, Vr::UL);
174    }
175
176    #[test]
177    fn element_i32_roundtrip() {
178        let tag = Tag::new(0x0020, 0x0013);
179        let e = Element::i32(tag, -42);
180        assert_eq!(e.i32_value(), Some(-42));
181        assert_eq!(e.vr, Vr::SL);
182    }
183
184    #[test]
185    fn element_f64_roundtrip() {
186        let tag = Tag::new(0x0018, 0x0050);
187        let e = Element::f64(tag, 2.78);
188        assert!((e.f64_value().unwrap() - 2.78).abs() < 1e-9);
189        assert_eq!(e.vr, Vr::FD);
190    }
191
192    #[test]
193    fn element_bytes_roundtrip() {
194        let tag = Tag::new(0x0042, 0x0011);
195        let data = vec![0xDE, 0xAD, 0xBE, 0xEF];
196        let e = Element::bytes(tag, Vr::OB, data.clone());
197        assert_eq!(e.bytes_value(), Some(data.as_slice()));
198    }
199
200    #[test]
201    fn element_uid_roundtrip() {
202        let e = Element::uid(tags::SOP_CLASS_UID, "1.2.840.10008.1.1");
203        assert_eq!(e.string_value(), Some("1.2.840.10008.1.1"));
204        assert_eq!(e.vr, Vr::UI);
205    }
206
207    #[test]
208    fn element_sequence_roundtrip() {
209        let item = DataSet::new();
210        let e = Element::sequence(Tag::new(0x0008, 0x1115), vec![item]);
211        assert_eq!(e.items().unwrap().len(), 1);
212        assert_eq!(e.vr, Vr::SQ);
213    }
214
215    #[test]
216    fn element_display_string() {
217        let e = Element::string(tags::PATIENT_NAME, Vr::PN, "Smith^John");
218        let s = e.to_string();
219        assert!(s.contains("(0010,0010)"), "tag not in display: {}", s);
220        assert!(s.contains("PN"), "VR not in display: {}", s);
221        assert!(s.contains("[Smith^John]"), "value not in display: {}", s);
222        // # length, multiplicity
223        assert!(s.contains("# "), "length marker missing: {}", s);
224        assert!(s.contains(", 1"), "multiplicity not in display: {}", s);
225    }
226
227    #[test]
228    fn element_display_u16() {
229        let e = Element::u16(tags::ROWS, 512);
230        let s = e.to_string();
231        assert!(s.contains("(0028,0010)"), "tag not in display: {}", s);
232        assert!(s.contains("US"), "VR not in display: {}", s);
233        assert!(s.contains("512"), "value not in display: {}", s);
234    }
235
236    #[test]
237    fn element_is_empty() {
238        let e = Element::new(tags::PATIENT_NAME, Vr::PN, Value::Empty);
239        assert!(e.is_empty());
240    }
241}