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::protocol::message::Message;
7use crate::error::{IgtlError, Result};
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.iter().position(|&b| b == 0).unwrap_or(64);
207            let patient_name = String::from_utf8(patient_name_bytes[..patient_name_len].to_vec())?;
208
209            // Decode PATIENT_ID (char[64])
210            let patient_id_bytes = &data[..64];
211            data.advance(64);
212            let patient_id_len = patient_id_bytes.iter().position(|&b| b == 0).unwrap_or(64);
213            let patient_id = String::from_utf8(patient_id_bytes[..patient_id_len].to_vec())?;
214
215            // Decode TIMESTAMP (uint64)
216            let timestamp = data.get_u64();
217
218            // Decode SIZE (uint16[3])
219            let size = [data.get_u16(), data.get_u16(), data.get_u16()];
220
221            // Decode SCALAR_TYPE (uint8)
222            let scalar_type = data.get_u8();
223
224            // Decode Reserved (uint8)
225            let _reserved = data.get_u8();
226
227            images.push(ImageMetaElement {
228                name,
229                id,
230                modality,
231                patient_name,
232                patient_id,
233                timestamp,
234                size,
235                scalar_type,
236            });
237        }
238
239        if !data.is_empty() {
240            return Err(IgtlError::InvalidSize {
241                expected: 0,
242                actual: data.len(),
243            });
244        }
245
246        Ok(ImgMetaMessage { images })
247    }
248}
249
250#[cfg(test)]
251mod tests {
252    use super::*;
253
254    #[test]
255    fn test_message_type() {
256        assert_eq!(ImgMetaMessage::message_type(), "IMGMETA");
257    }
258
259    #[test]
260    fn test_empty() {
261        let msg = ImgMetaMessage::empty();
262        assert!(msg.is_empty());
263        assert_eq!(msg.len(), 0);
264    }
265
266    #[test]
267    fn test_new() {
268        let elem = ImageMetaElement::new("Image1", "IMG001", "CT");
269        assert_eq!(elem.name, "Image1");
270        assert_eq!(elem.id, "IMG001");
271        assert_eq!(elem.modality, "CT");
272    }
273
274    #[test]
275    fn test_with_patient() {
276        let elem = ImageMetaElement::new("Image1", "IMG001", "MRI")
277            .with_patient("John Doe", "P12345");
278        assert_eq!(elem.patient_name, "John Doe");
279        assert_eq!(elem.patient_id, "P12345");
280    }
281
282    #[test]
283    fn test_with_size() {
284        let elem = ImageMetaElement::new("Image1", "IMG001", "CT")
285            .with_size([512, 512, 128]);
286        assert_eq!(elem.size, [512, 512, 128]);
287    }
288
289    #[test]
290    fn test_add_image() {
291        let mut msg = ImgMetaMessage::empty();
292        msg.add_image(ImageMetaElement::new("Image1", "IMG001", "CT"));
293        assert_eq!(msg.len(), 1);
294    }
295
296    #[test]
297    fn test_encode_single() {
298        let elem = ImageMetaElement::new("TestImage", "TEST001", "CT");
299        let msg = ImgMetaMessage::new(vec![elem]);
300        let encoded = msg.encode_content().unwrap();
301
302        assert_eq!(encoded.len(), 260);
303    }
304
305    #[test]
306    fn test_roundtrip() {
307        let original = ImgMetaMessage::new(vec![
308            ImageMetaElement::new("CTScan1", "CT001", "CT")
309                .with_patient("Jane Smith", "P67890")
310                .with_timestamp(1234567890)
311                .with_size([512, 512, 200])
312                .with_scalar_type(5), // uint16
313        ]);
314
315        let encoded = original.encode_content().unwrap();
316        let decoded = ImgMetaMessage::decode_content(&encoded).unwrap();
317
318        assert_eq!(decoded.images.len(), 1);
319        assert_eq!(decoded.images[0].name, "CTScan1");
320        assert_eq!(decoded.images[0].id, "CT001");
321        assert_eq!(decoded.images[0].modality, "CT");
322        assert_eq!(decoded.images[0].patient_name, "Jane Smith");
323        assert_eq!(decoded.images[0].patient_id, "P67890");
324        assert_eq!(decoded.images[0].timestamp, 1234567890);
325        assert_eq!(decoded.images[0].size, [512, 512, 200]);
326        assert_eq!(decoded.images[0].scalar_type, 5);
327    }
328
329    #[test]
330    fn test_roundtrip_multiple() {
331        let original = ImgMetaMessage::new(vec![
332            ImageMetaElement::new("CT1", "CT001", "CT"),
333            ImageMetaElement::new("MRI1", "MRI001", "MRI"),
334            ImageMetaElement::new("US1", "US001", "Ultrasound"),
335        ]);
336
337        let encoded = original.encode_content().unwrap();
338        let decoded = ImgMetaMessage::decode_content(&encoded).unwrap();
339
340        assert_eq!(decoded.images.len(), 3);
341        assert_eq!(decoded.images[0].modality, "CT");
342        assert_eq!(decoded.images[1].modality, "MRI");
343        assert_eq!(decoded.images[2].modality, "Ultrasound");
344    }
345
346    #[test]
347    fn test_empty_message() {
348        let msg = ImgMetaMessage::empty();
349        let encoded = msg.encode_content().unwrap();
350        let decoded = ImgMetaMessage::decode_content(&encoded).unwrap();
351
352        assert_eq!(decoded.images.len(), 0);
353    }
354
355    #[test]
356    fn test_decode_invalid_size() {
357        let data = vec![0u8; 259]; // One byte short
358        let result = ImgMetaMessage::decode_content(&data);
359        assert!(result.is_err());
360    }
361}