1use crate::error::{IoError, IoResult};
8use quick_xml::events::Event;
9use quick_xml::Reader;
10
11#[derive(Debug, Clone, Copy, Default)]
12pub struct MatrixSize {
13 pub x: u32,
14 pub y: u32,
15 pub z: u32,
16}
17
18#[derive(Debug, Clone, Copy, Default)]
19pub struct FieldOfView {
20 pub x: f32,
21 pub y: f32,
22 pub z: f32,
23}
24
25#[derive(Debug, Clone, Copy, Default)]
26pub struct EncodingLimit {
27 pub minimum: u32,
28 pub maximum: u32,
29 pub center: u32,
30}
31
32#[derive(Debug, Clone, Default)]
33pub struct EncodingInfo {
34 pub encoded_matrix: MatrixSize,
35 pub recon_matrix: MatrixSize,
36 pub encoded_fov: FieldOfView,
37 pub recon_fov: FieldOfView,
38 pub trajectory: String, pub ky_limit: EncodingLimit, pub kz_limit: EncodingLimit, pub slice_limit: EncodingLimit,
42}
43
44#[non_exhaustive]
45#[derive(Debug, Clone, Default)]
46pub struct IsmrmrdHeader {
47 pub system_vendor: String,
48 pub system_model: String,
49 pub field_strength_t: f32,
50 pub receiver_channels: u32,
51 pub encoding: EncodingInfo,
52}
53
54impl IsmrmrdHeader {
55 pub fn parse(xml: &str) -> IoResult<Self> {
60 let mut reader = Reader::from_str(xml);
61 reader.config_mut().trim_text(true);
62
63 let mut hdr = IsmrmrdHeader::default();
64
65 let mut path: Vec<String> = Vec::with_capacity(16);
67 let mut buf = Vec::new();
68
69 loop {
70 match reader.read_event_into(&mut buf) {
71 Err(e) => {
72 return Err(IoError::Xml(format!(
73 "xml at {}: {e}",
74 reader.buffer_position()
75 )))
76 }
77 Ok(Event::Eof) => break,
78 Ok(Event::Start(e)) => {
79 let name = local_name(e.name().as_ref());
80 path.push(name);
81 }
82 Ok(Event::End(_)) => {
83 path.pop();
84 }
85 Ok(Event::Empty(_)) => {
86 }
88 Ok(Event::Text(t)) => {
89 let text = t.unescape().map_err(|e| IoError::Xml(e.to_string()))?;
90 let text = text.trim();
91 if text.is_empty() {
92 continue;
93 }
94 apply_field(&path, text, &mut hdr);
95 }
96 _ => {}
97 }
98 buf.clear();
99 }
100
101 Ok(hdr)
102 }
103
104 #[inline]
106 pub fn recon_size(&self) -> MatrixSize {
107 self.encoding.recon_matrix
108 }
109
110 #[inline]
112 pub fn encoded_size(&self) -> MatrixSize {
113 self.encoding.encoded_matrix
114 }
115}
116
117fn local_name(bytes: &[u8]) -> String {
118 let s = std::str::from_utf8(bytes).unwrap_or("");
120 match s.rfind(':') {
121 Some(i) => s[i + 1..].to_string(),
122 None => s.to_string(),
123 }
124}
125
126fn apply_field(path: &[String], text: &str, hdr: &mut IsmrmrdHeader) {
128 let tail: Vec<&str> = path.iter().map(String::as_str).collect();
131
132 if ends_with(&tail, &["acquisitionSystemInformation", "systemVendor"]) {
134 hdr.system_vendor = text.to_string();
135 } else if ends_with(&tail, &["acquisitionSystemInformation", "systemModel"]) {
136 hdr.system_model = text.to_string();
137 } else if ends_with(
138 &tail,
139 &["acquisitionSystemInformation", "systemFieldStrength_T"],
140 ) {
141 hdr.field_strength_t = text.parse().unwrap_or(0.0);
142 } else if ends_with(&tail, &["acquisitionSystemInformation", "receiverChannels"]) {
143 hdr.receiver_channels = text.parse().unwrap_or(0);
144 }
145 else if ends_with(&tail, &["encoding", "encodedSpace", "matrixSize", "x"]) {
147 hdr.encoding.encoded_matrix.x = text.parse().unwrap_or(0);
148 } else if ends_with(&tail, &["encoding", "encodedSpace", "matrixSize", "y"]) {
149 hdr.encoding.encoded_matrix.y = text.parse().unwrap_or(0);
150 } else if ends_with(&tail, &["encoding", "encodedSpace", "matrixSize", "z"]) {
151 hdr.encoding.encoded_matrix.z = text.parse().unwrap_or(0);
152 }
153 else if ends_with(&tail, &["encoding", "encodedSpace", "fieldOfView_mm", "x"]) {
155 hdr.encoding.encoded_fov.x = text.parse().unwrap_or(0.0);
156 } else if ends_with(&tail, &["encoding", "encodedSpace", "fieldOfView_mm", "y"]) {
157 hdr.encoding.encoded_fov.y = text.parse().unwrap_or(0.0);
158 } else if ends_with(&tail, &["encoding", "encodedSpace", "fieldOfView_mm", "z"]) {
159 hdr.encoding.encoded_fov.z = text.parse().unwrap_or(0.0);
160 }
161 else if ends_with(&tail, &["encoding", "reconSpace", "matrixSize", "x"]) {
163 hdr.encoding.recon_matrix.x = text.parse().unwrap_or(0);
164 } else if ends_with(&tail, &["encoding", "reconSpace", "matrixSize", "y"]) {
165 hdr.encoding.recon_matrix.y = text.parse().unwrap_or(0);
166 } else if ends_with(&tail, &["encoding", "reconSpace", "matrixSize", "z"]) {
167 hdr.encoding.recon_matrix.z = text.parse().unwrap_or(0);
168 }
169 else if ends_with(&tail, &["encoding", "reconSpace", "fieldOfView_mm", "x"]) {
171 hdr.encoding.recon_fov.x = text.parse().unwrap_or(0.0);
172 } else if ends_with(&tail, &["encoding", "reconSpace", "fieldOfView_mm", "y"]) {
173 hdr.encoding.recon_fov.y = text.parse().unwrap_or(0.0);
174 } else if ends_with(&tail, &["encoding", "reconSpace", "fieldOfView_mm", "z"]) {
175 hdr.encoding.recon_fov.z = text.parse().unwrap_or(0.0);
176 }
177 else if ends_with(&tail, &["encoding", "trajectory"]) {
179 hdr.encoding.trajectory = text.to_string();
180 }
181 else if path.len() >= 4
183 && path[path.len() - 4] == "encoding"
184 && path[path.len() - 3] == "encodingLimits"
185 {
186 let section = &path[path.len() - 2];
187 let which = &path[path.len() - 1];
188 let target: Option<&mut EncodingLimit> = match section.as_str() {
189 "kspace_encoding_step_1" => Some(&mut hdr.encoding.ky_limit),
190 "kspace_encoding_step_2" => Some(&mut hdr.encoding.kz_limit),
191 "slice" => Some(&mut hdr.encoding.slice_limit),
192 _ => None,
193 };
194 if let Some(t) = target {
195 match which.as_str() {
196 "minimum" => t.minimum = text.parse().unwrap_or(0),
197 "maximum" => t.maximum = text.parse().unwrap_or(0),
198 "center" => t.center = text.parse().unwrap_or(0),
199 _ => {}
200 }
201 }
202 }
203}
204
205#[inline]
206fn ends_with(path: &[&str], suffix: &[&str]) -> bool {
207 if path.len() < suffix.len() {
208 return false;
209 }
210 path[path.len() - suffix.len()..] == *suffix
211}
212
213#[cfg(test)]
217mod tests {
218 use super::*;
219
220 const SAMPLE: &str = r#"<?xml version="1.0"?>
221 <ismrmrdHeader xmlns="http://www.ismrm.org/ISMRMRD">
222 <acquisitionSystemInformation>
223 <systemVendor>GE MEDICAL SYSTEMS</systemVendor>
224 <systemModel>Orchestra SDK</systemModel>
225 <systemFieldStrength_T>3.000000</systemFieldStrength_T>
226 <receiverChannels>8</receiverChannels>
227 </acquisitionSystemInformation>
228 <encoding>
229 <encodedSpace>
230 <matrixSize><x>352</x><y>216</y><z>30</z></matrixSize>
231 <fieldOfView_mm><x>280</x><y>280</y><z>4.5</z></fieldOfView_mm>
232 </encodedSpace>
233 <reconSpace>
234 <matrixSize><x>512</x><y>512</y><z>0</z></matrixSize>
235 <fieldOfView_mm><x>280</x><y>280</y><z>4.5</z></fieldOfView_mm>
236 </reconSpace>
237 <encodingLimits>
238 <kspace_encoding_step_1><minimum>0</minimum><maximum>215</maximum><center>108</center></kspace_encoding_step_1>
239 <kspace_encoding_step_2><minimum>0</minimum><maximum>0</maximum><center>0</center></kspace_encoding_step_2>
240 <slice><minimum>0</minimum><maximum>29</maximum><center>15</center></slice>
241 </encodingLimits>
242 <trajectory>cartesian</trajectory>
243 </encoding>
244 </ismrmrdHeader>"#;
245
246 #[test]
247 fn parses_header_fields() {
248 let h = IsmrmrdHeader::parse(SAMPLE).unwrap();
249 assert_eq!(h.system_vendor, "GE MEDICAL SYSTEMS");
250 assert_eq!(h.receiver_channels, 8);
251 assert!((h.field_strength_t - 3.0).abs() < 1e-6);
252
253 assert_eq!(h.encoding.encoded_matrix.x, 352);
254 assert_eq!(h.encoding.encoded_matrix.y, 216);
255 assert_eq!(h.encoding.encoded_matrix.z, 30);
256 assert_eq!(h.encoding.recon_matrix.x, 512);
257 assert_eq!(h.encoding.recon_matrix.y, 512);
258
259 assert_eq!(h.encoding.ky_limit.maximum, 215);
260 assert_eq!(h.encoding.slice_limit.maximum, 29);
261 assert_eq!(h.encoding.trajectory, "cartesian");
262 }
263}