openigtlink_rust/protocol/types/
imgmeta.rs

1//! IMGMETA (ImageMeta) message type implementation
2//!
3//! The IMGMETA message is used to transfer image metadata not available in IMAGE messages,
4//! such as patient information, modality, etc.
5
6use crate::error::{IgtlError, Result};
7use crate::protocol::message::Message;
8use bytes::{Buf, BufMut};
9
10/// Image metadata element
11#[derive(Debug, Clone, PartialEq)]
12pub struct ImageMetaElement {
13    /// Name or description of the image (max 64 chars)
14    pub name: String,
15    /// ID to query the IMAGE (max 20 chars)
16    pub id: String,
17    /// Modality (e.g., "CT", "MRI") (max 32 chars)
18    pub modality: String,
19    /// Patient name (max 64 chars)
20    pub patient_name: String,
21    /// Patient ID (max 64 chars)
22    pub patient_id: String,
23    /// Scan timestamp
24    pub timestamp: u64,
25    /// Number of pixels in each direction (RI, RJ, RK)
26    pub size: [u16; 3],
27    /// Scalar type (same as IMAGE: 3=uint8, 5=uint16, etc.)
28    pub scalar_type: u8,
29}
30
31impl ImageMetaElement {
32    /// Create a new image metadata element
33    pub fn new(
34        name: impl Into<String>,
35        id: impl Into<String>,
36        modality: impl Into<String>,
37    ) -> Self {
38        ImageMetaElement {
39            name: name.into(),
40            id: id.into(),
41            modality: modality.into(),
42            patient_name: String::new(),
43            patient_id: String::new(),
44            timestamp: 0,
45            size: [0, 0, 0],
46            scalar_type: 3, // Default to uint8
47        }
48    }
49
50    /// Set patient information
51    pub fn with_patient(
52        mut self,
53        patient_name: impl Into<String>,
54        patient_id: impl Into<String>,
55    ) -> Self {
56        self.patient_name = patient_name.into();
57        self.patient_id = patient_id.into();
58        self
59    }
60
61    /// Set timestamp
62    pub fn with_timestamp(mut self, timestamp: u64) -> Self {
63        self.timestamp = timestamp;
64        self
65    }
66
67    /// Set image size
68    pub fn with_size(mut self, size: [u16; 3]) -> Self {
69        self.size = size;
70        self
71    }
72
73    /// Set scalar type
74    pub fn with_scalar_type(mut self, scalar_type: u8) -> Self {
75        self.scalar_type = scalar_type;
76        self
77    }
78}
79
80/// IMGMETA message containing multiple image metadata elements
81///
82/// # OpenIGTLink Specification
83/// - Message type: "IMGMETA"
84/// - Each element: NAME (`char[64]`) + ID (`char[20]`) + MODALITY (`char[32]`) + PATIENT_NAME (`char[64]`) + PATIENT_ID (`char[64]`) + TIMESTAMP (uint64) + SIZE (`uint16[3]`) + SCALAR_TYPE (uint8) + Reserved (uint8)
85/// - Element size: 64 + 20 + 32 + 64 + 64 + 8 + 6 + 1 + 1 = 260 bytes
86#[derive(Debug, Clone, PartialEq)]
87pub struct ImgMetaMessage {
88    /// List of image metadata elements
89    pub images: Vec<ImageMetaElement>,
90}
91
92impl ImgMetaMessage {
93    /// Create a new IMGMETA message
94    pub fn new(images: Vec<ImageMetaElement>) -> Self {
95        ImgMetaMessage { images }
96    }
97
98    /// Create an empty IMGMETA message
99    pub fn empty() -> Self {
100        ImgMetaMessage { images: Vec::new() }
101    }
102
103    /// Add an image metadata element
104    pub fn add_image(&mut self, image: ImageMetaElement) {
105        self.images.push(image);
106    }
107
108    /// Get number of images
109    pub fn len(&self) -> usize {
110        self.images.len()
111    }
112
113    /// Check if message has no images
114    pub fn is_empty(&self) -> bool {
115        self.images.is_empty()
116    }
117}
118
119impl Message for ImgMetaMessage {
120    fn message_type() -> &'static str {
121        "IMGMETA"
122    }
123
124    fn encode_content(&self) -> Result<Vec<u8>> {
125        let mut buf = Vec::with_capacity(self.images.len() * 260);
126
127        for img in &self.images {
128            // Encode NAME (`char[64]`)
129            let mut name_bytes = [0u8; 64];
130            let name_str = img.name.as_bytes();
131            let copy_len = name_str.len().min(63);
132            name_bytes[..copy_len].copy_from_slice(&name_str[..copy_len]);
133            buf.extend_from_slice(&name_bytes);
134
135            // Encode ID (`char[20]`)
136            let mut id_bytes = [0u8; 20];
137            let id_str = img.id.as_bytes();
138            let copy_len = id_str.len().min(19);
139            id_bytes[..copy_len].copy_from_slice(&id_str[..copy_len]);
140            buf.extend_from_slice(&id_bytes);
141
142            // Encode MODALITY (`char[32]`)
143            let mut modality_bytes = [0u8; 32];
144            let modality_str = img.modality.as_bytes();
145            let copy_len = modality_str.len().min(31);
146            modality_bytes[..copy_len].copy_from_slice(&modality_str[..copy_len]);
147            buf.extend_from_slice(&modality_bytes);
148
149            // Encode PATIENT_NAME (`char[64]`)
150            let mut patient_name_bytes = [0u8; 64];
151            let patient_name_str = img.patient_name.as_bytes();
152            let copy_len = patient_name_str.len().min(63);
153            patient_name_bytes[..copy_len].copy_from_slice(&patient_name_str[..copy_len]);
154            buf.extend_from_slice(&patient_name_bytes);
155
156            // Encode PATIENT_ID (`char[64]`)
157            let mut patient_id_bytes = [0u8; 64];
158            let patient_id_str = img.patient_id.as_bytes();
159            let copy_len = patient_id_str.len().min(63);
160            patient_id_bytes[..copy_len].copy_from_slice(&patient_id_str[..copy_len]);
161            buf.extend_from_slice(&patient_id_bytes);
162
163            // Encode TIMESTAMP (uint64)
164            buf.put_u64(img.timestamp);
165
166            // Encode SIZE (uint16[3])
167            for &s in &img.size {
168                buf.put_u16(s);
169            }
170
171            // Encode SCALAR_TYPE (uint8)
172            buf.put_u8(img.scalar_type);
173
174            // Encode Reserved (uint8)
175            buf.put_u8(0);
176        }
177
178        Ok(buf)
179    }
180
181    fn decode_content(mut data: &[u8]) -> Result<Self> {
182        let mut images = Vec::new();
183
184        while data.len() >= 260 {
185            // Decode NAME (`char[64]`)
186            let name_bytes = &data[..64];
187            data.advance(64);
188            let name_len = name_bytes.iter().position(|&b| b == 0).unwrap_or(64);
189            let name = String::from_utf8(name_bytes[..name_len].to_vec())?;
190
191            // Decode ID (`char[20]`)
192            let id_bytes = &data[..20];
193            data.advance(20);
194            let id_len = id_bytes.iter().position(|&b| b == 0).unwrap_or(20);
195            let id = String::from_utf8(id_bytes[..id_len].to_vec())?;
196
197            // Decode MODALITY (`char[32]`)
198            let modality_bytes = &data[..32];
199            data.advance(32);
200            let modality_len = modality_bytes.iter().position(|&b| b == 0).unwrap_or(32);
201            let modality = String::from_utf8(modality_bytes[..modality_len].to_vec())?;
202
203            // Decode PATIENT_NAME (`char[64]`)
204            let patient_name_bytes = &data[..64];
205            data.advance(64);
206            let patient_name_len = patient_name_bytes
207                .iter()
208                .position(|&b| b == 0)
209                .unwrap_or(64);
210            let patient_name = String::from_utf8(patient_name_bytes[..patient_name_len].to_vec())?;
211
212            // Decode PATIENT_ID (`char[64]`)
213            let patient_id_bytes = &data[..64];
214            data.advance(64);
215            let patient_id_len = patient_id_bytes.iter().position(|&b| b == 0).unwrap_or(64);
216            let patient_id = String::from_utf8(patient_id_bytes[..patient_id_len].to_vec())?;
217
218            // Decode TIMESTAMP (uint64)
219            let timestamp = data.get_u64();
220
221            // Decode SIZE (uint16[3])
222            let size = [data.get_u16(), data.get_u16(), data.get_u16()];
223
224            // Decode SCALAR_TYPE (uint8)
225            let scalar_type = data.get_u8();
226
227            // Decode Reserved (uint8)
228            let _reserved = data.get_u8();
229
230            images.push(ImageMetaElement {
231                name,
232                id,
233                modality,
234                patient_name,
235                patient_id,
236                timestamp,
237                size,
238                scalar_type,
239            });
240        }
241
242        if !data.is_empty() {
243            return Err(IgtlError::InvalidSize {
244                expected: 0,
245                actual: data.len(),
246            });
247        }
248
249        Ok(ImgMetaMessage { images })
250    }
251}
252
253#[cfg(test)]
254mod tests {
255    use super::*;
256
257    #[test]
258    fn test_message_type() {
259        assert_eq!(ImgMetaMessage::message_type(), "IMGMETA");
260    }
261
262    #[test]
263    fn test_empty() {
264        let msg = ImgMetaMessage::empty();
265        assert!(msg.is_empty());
266        assert_eq!(msg.len(), 0);
267    }
268
269    #[test]
270    fn test_new() {
271        let elem = ImageMetaElement::new("Image1", "IMG001", "CT");
272        assert_eq!(elem.name, "Image1");
273        assert_eq!(elem.id, "IMG001");
274        assert_eq!(elem.modality, "CT");
275    }
276
277    #[test]
278    fn test_with_patient() {
279        let elem =
280            ImageMetaElement::new("Image1", "IMG001", "MRI").with_patient("John Doe", "P12345");
281        assert_eq!(elem.patient_name, "John Doe");
282        assert_eq!(elem.patient_id, "P12345");
283    }
284
285    #[test]
286    fn test_with_size() {
287        let elem = ImageMetaElement::new("Image1", "IMG001", "CT").with_size([512, 512, 128]);
288        assert_eq!(elem.size, [512, 512, 128]);
289    }
290
291    #[test]
292    fn test_add_image() {
293        let mut msg = ImgMetaMessage::empty();
294        msg.add_image(ImageMetaElement::new("Image1", "IMG001", "CT"));
295        assert_eq!(msg.len(), 1);
296    }
297
298    #[test]
299    fn test_encode_single() {
300        let elem = ImageMetaElement::new("TestImage", "TEST001", "CT");
301        let msg = ImgMetaMessage::new(vec![elem]);
302        let encoded = msg.encode_content().unwrap();
303
304        assert_eq!(encoded.len(), 260);
305    }
306
307    #[test]
308    fn test_roundtrip() {
309        let original = ImgMetaMessage::new(vec![
310            ImageMetaElement::new("CTScan1", "CT001", "CT")
311                .with_patient("Jane Smith", "P67890")
312                .with_timestamp(1234567890)
313                .with_size([512, 512, 200])
314                .with_scalar_type(5), // uint16
315        ]);
316
317        let encoded = original.encode_content().unwrap();
318        let decoded = ImgMetaMessage::decode_content(&encoded).unwrap();
319
320        assert_eq!(decoded.images.len(), 1);
321        assert_eq!(decoded.images[0].name, "CTScan1");
322        assert_eq!(decoded.images[0].id, "CT001");
323        assert_eq!(decoded.images[0].modality, "CT");
324        assert_eq!(decoded.images[0].patient_name, "Jane Smith");
325        assert_eq!(decoded.images[0].patient_id, "P67890");
326        assert_eq!(decoded.images[0].timestamp, 1234567890);
327        assert_eq!(decoded.images[0].size, [512, 512, 200]);
328        assert_eq!(decoded.images[0].scalar_type, 5);
329    }
330
331    #[test]
332    fn test_roundtrip_multiple() {
333        let original = ImgMetaMessage::new(vec![
334            ImageMetaElement::new("CT1", "CT001", "CT"),
335            ImageMetaElement::new("MRI1", "MRI001", "MRI"),
336            ImageMetaElement::new("US1", "US001", "Ultrasound"),
337        ]);
338
339        let encoded = original.encode_content().unwrap();
340        let decoded = ImgMetaMessage::decode_content(&encoded).unwrap();
341
342        assert_eq!(decoded.images.len(), 3);
343        assert_eq!(decoded.images[0].modality, "CT");
344        assert_eq!(decoded.images[1].modality, "MRI");
345        assert_eq!(decoded.images[2].modality, "Ultrasound");
346    }
347
348    #[test]
349    fn test_empty_message() {
350        let msg = ImgMetaMessage::empty();
351        let encoded = msg.encode_content().unwrap();
352        let decoded = ImgMetaMessage::decode_content(&encoded).unwrap();
353
354        assert_eq!(decoded.images.len(), 0);
355    }
356
357    #[test]
358    fn test_decode_invalid_size() {
359        let data = vec![0u8; 259]; // One byte short
360        let result = ImgMetaMessage::decode_content(&data);
361        assert!(result.is_err());
362    }
363}