openigtlink_rust/protocol/types/
image.rs

1//! IMAGE message type implementation
2//!
3//! The IMAGE message is used to transfer 2D/3D medical image data.
4//! This is one of the most commonly used OpenIGTLink message types.
5//!
6//! # Use Cases
7//!
8//! - **CT/MRI Image Transfer** - Sending volumetric medical images from scanner to workstation
9//! - **Ultrasound Streaming** - Real-time 2D ultrasound image streaming during procedures
10//! - **X-ray Images** - Transferring radiographic images for real-time guidance
11//! - **Image-Guided Surgery** - Displaying pre-operative images in surgical navigation systems
12//! - **Multi-Modal Registration** - Aligning images from different modalities (CT, MRI, PET)
13//!
14//! # Supported Image Types
15//!
16//! - **Scalar Types**: 8/16/32-bit integers, 32/64-bit floating point
17//! - **Components**: 1 (grayscale), 3 (RGB), 4 (RGBA)
18//! - **Dimensions**: 2D (single slice) or 3D (volume)
19//! - **Coordinate Systems**: RAS (Right-Anterior-Superior), LPS (Left-Posterior-Superior)
20//!
21//! # Examples
22//!
23//! ## Sending a CT Image Slice
24//!
25//! ```no_run
26//! use openigtlink_rust::protocol::types::{ImageMessage, ImageScalarType, Endian, CoordinateSystem};
27//! use openigtlink_rust::io::IgtlClient;
28//!
29//! let mut client = IgtlClient::connect("127.0.0.1:18944")?;
30//!
31//! let mut image = ImageMessage::new();
32//! image.set_device_name("CTScanner");
33//! image.set_scalar_type(ImageScalarType::Uint16);
34//! image.set_dimensions([512, 512, 1]);
35//! image.set_spacing([0.5, 0.5, 1.0]); // 0.5mm pixel spacing
36//! image.set_num_components(1); // Grayscale
37//! image.set_endian(Endian::Little);
38//! image.set_coordinate_system(CoordinateSystem::LPS);
39//!
40//! // Simulated CT data (512x512 16-bit)
41//! let ct_data: Vec<u8> = vec![0; 512 * 512 * 2];
42//! image.set_image_data(ct_data);
43//!
44//! client.send(&image)?;
45//! # Ok::<(), openigtlink_rust::IgtlError>(())
46//! ```
47//!
48//! ## Receiving RGB Ultrasound Image
49//!
50//! ```no_run
51//! use openigtlink_rust::io::IgtlServer;
52//! use openigtlink_rust::protocol::types::ImageMessage;
53//! use openigtlink_rust::protocol::message::Message;
54//!
55//! let server = IgtlServer::bind("0.0.0.0:18944")?;
56//! let mut client_conn = server.accept()?;
57//!
58//! let message = client_conn.receive()?;
59//!
60//! if message.header.message_type == "IMAGE" {
61//!     let image = ImageMessage::from_bytes(&message.body)?;
62//!     println!("Received image: {}x{}x{}",
63//!              image.size[0], image.size[1], image.size[2]);
64//!     println!("Scalar type: {:?}", image.scalar_type);
65//!     println!("Components: {}", image.num_components);
66//! }
67//! # Ok::<(), openigtlink_rust::IgtlError>(())
68//! ```
69
70use crate::protocol::message::Message;
71use crate::error::{IgtlError, Result};
72use bytes::{Buf, BufMut};
73
74/// Image scalar type
75#[derive(Debug, Clone, Copy, PartialEq, Eq)]
76pub enum ImageScalarType {
77    Int8 = 2,
78    Uint8 = 3,
79    Int16 = 4,
80    Uint16 = 5,
81    Int32 = 6,
82    Uint32 = 7,
83    Float32 = 10,
84    Float64 = 11,
85}
86
87impl ImageScalarType {
88    /// Get size in bytes
89    pub fn size(&self) -> usize {
90        match self {
91            ImageScalarType::Int8 | ImageScalarType::Uint8 => 1,
92            ImageScalarType::Int16 | ImageScalarType::Uint16 => 2,
93            ImageScalarType::Int32 | ImageScalarType::Uint32 | ImageScalarType::Float32 => 4,
94            ImageScalarType::Float64 => 8,
95        }
96    }
97
98    /// Create from type value
99    pub fn from_u8(value: u8) -> Result<Self> {
100        match value {
101            2 => Ok(ImageScalarType::Int8),
102            3 => Ok(ImageScalarType::Uint8),
103            4 => Ok(ImageScalarType::Int16),
104            5 => Ok(ImageScalarType::Uint16),
105            6 => Ok(ImageScalarType::Int32),
106            7 => Ok(ImageScalarType::Uint32),
107            10 => Ok(ImageScalarType::Float32),
108            11 => Ok(ImageScalarType::Float64),
109            _ => Err(IgtlError::InvalidSize {
110                expected: 0,
111                actual: value as usize,
112            }),
113        }
114    }
115}
116
117/// Endianness type
118#[derive(Debug, Clone, Copy, PartialEq, Eq)]
119pub enum Endian {
120    Big = 1,
121    Little = 2,
122}
123
124impl Endian {
125    /// Create from endian value
126    pub fn from_u8(value: u8) -> Result<Self> {
127        match value {
128            1 => Ok(Endian::Big),
129            2 => Ok(Endian::Little),
130            _ => Err(IgtlError::InvalidSize {
131                expected: 1,
132                actual: value as usize,
133            }),
134        }
135    }
136}
137
138/// Coordinate system type
139#[derive(Debug, Clone, Copy, PartialEq, Eq)]
140pub enum CoordinateSystem {
141    RAS = 1,  // Right-Anterior-Superior (common in medical imaging)
142    LPS = 2,  // Left-Posterior-Superior
143}
144
145impl CoordinateSystem {
146    /// Create from coordinate value
147    pub fn from_u8(value: u8) -> Result<Self> {
148        match value {
149            1 => Ok(CoordinateSystem::RAS),
150            2 => Ok(CoordinateSystem::LPS),
151            _ => Err(IgtlError::InvalidSize {
152                expected: 1,
153                actual: value as usize,
154            }),
155        }
156    }
157}
158
159/// IMAGE message for 2D/3D medical image data
160///
161/// # OpenIGTLink Specification
162/// - Message type: "IMAGE"
163/// - Header: VERSION (uint16) + NUM_COMPONENTS (uint8) + SCALAR_TYPE (uint8) + ENDIAN (uint8) + COORD (uint8) + SIZE (uint16[3]) + MATRIX (float32[12])
164/// - Header size: 2 + 1 + 1 + 1 + 1 + 6 + 48 = 60 bytes
165/// - Followed by image data
166#[derive(Debug, Clone, PartialEq)]
167pub struct ImageMessage {
168    /// Protocol version (should be 1 or 2)
169    pub version: u16,
170    /// Number of components (1=scalar, 3=RGB, 4=RGBA)
171    pub num_components: u8,
172    /// Scalar type
173    pub scalar_type: ImageScalarType,
174    /// Endianness of image data
175    pub endian: Endian,
176    /// Coordinate system
177    pub coordinate: CoordinateSystem,
178    /// Image size [columns, rows, slices]
179    pub size: [u16; 3],
180    /// 4x3 transformation matrix (stored row-major, upper 3x4 of 4x4 matrix)
181    pub matrix: [[f32; 4]; 3],
182    /// Image data (raw bytes)
183    pub data: Vec<u8>,
184}
185
186impl ImageMessage {
187    /// Create a new IMAGE message
188    pub fn new(
189        scalar_type: ImageScalarType,
190        size: [u16; 3],
191        data: Vec<u8>,
192    ) -> Result<Self> {
193        let num_components = 1;
194        let expected_size = (size[0] as usize) * (size[1] as usize) * (size[2] as usize)
195            * (num_components as usize) * scalar_type.size();
196
197        if data.len() != expected_size {
198            return Err(IgtlError::InvalidSize {
199                expected: expected_size,
200                actual: data.len(),
201            });
202        }
203
204        Ok(ImageMessage {
205            version: 1,
206            num_components,
207            scalar_type,
208            endian: Endian::Big,
209            coordinate: CoordinateSystem::RAS,
210            size,
211            matrix: [
212                [1.0, 0.0, 0.0, 0.0],
213                [0.0, 1.0, 0.0, 0.0],
214                [0.0, 0.0, 1.0, 0.0],
215            ],
216            data,
217        })
218    }
219
220    /// Create with RGB components
221    pub fn rgb(
222        scalar_type: ImageScalarType,
223        size: [u16; 3],
224        data: Vec<u8>,
225    ) -> Result<Self> {
226        let num_components = 3;
227        let expected_size = (size[0] as usize) * (size[1] as usize) * (size[2] as usize)
228            * (num_components as usize) * scalar_type.size();
229
230        if data.len() != expected_size {
231            return Err(IgtlError::InvalidSize {
232                expected: expected_size,
233                actual: data.len(),
234            });
235        }
236
237        Ok(ImageMessage {
238            version: 1,
239            num_components,
240            scalar_type,
241            endian: Endian::Big,
242            coordinate: CoordinateSystem::RAS,
243            size,
244            matrix: [
245                [1.0, 0.0, 0.0, 0.0],
246                [0.0, 1.0, 0.0, 0.0],
247                [0.0, 0.0, 1.0, 0.0],
248            ],
249            data,
250        })
251    }
252
253    /// Set transformation matrix
254    pub fn with_matrix(mut self, matrix: [[f32; 4]; 3]) -> Self {
255        self.matrix = matrix;
256        self
257    }
258
259    /// Set coordinate system
260    pub fn with_coordinate(mut self, coordinate: CoordinateSystem) -> Self {
261        self.coordinate = coordinate;
262        self
263    }
264
265    /// Get total number of pixels
266    pub fn num_pixels(&self) -> usize {
267        (self.size[0] as usize) * (self.size[1] as usize) * (self.size[2] as usize)
268    }
269}
270
271impl Message for ImageMessage {
272    fn message_type() -> &'static str {
273        "IMAGE"
274    }
275
276    fn encode_content(&self) -> Result<Vec<u8>> {
277        let mut buf = Vec::with_capacity(60 + self.data.len());
278
279        // Encode VERSION (uint16)
280        buf.put_u16(self.version);
281
282        // Encode NUM_COMPONENTS (uint8)
283        buf.put_u8(self.num_components);
284
285        // Encode SCALAR_TYPE (uint8)
286        buf.put_u8(self.scalar_type as u8);
287
288        // Encode ENDIAN (uint8)
289        buf.put_u8(self.endian as u8);
290
291        // Encode COORD (uint8)
292        buf.put_u8(self.coordinate as u8);
293
294        // Encode SIZE (uint16[3])
295        for &s in &self.size {
296            buf.put_u16(s);
297        }
298
299        // Encode MATRIX (float32[12]) - row-major order
300        for row in &self.matrix {
301            for &val in row {
302                buf.put_f32(val);
303            }
304        }
305
306        // Encode image data
307        buf.extend_from_slice(&self.data);
308
309        Ok(buf)
310    }
311
312    fn decode_content(mut data: &[u8]) -> Result<Self> {
313        if data.len() < 60 {
314            return Err(IgtlError::InvalidSize {
315                expected: 60,
316                actual: data.len(),
317            });
318        }
319
320        // Decode VERSION (uint16)
321        let version = data.get_u16();
322
323        // Decode NUM_COMPONENTS (uint8)
324        let num_components = data.get_u8();
325
326        // Decode SCALAR_TYPE (uint8)
327        let scalar_type = ImageScalarType::from_u8(data.get_u8())?;
328
329        // Decode ENDIAN (uint8)
330        let endian = Endian::from_u8(data.get_u8())?;
331
332        // Decode COORD (uint8)
333        let coordinate = CoordinateSystem::from_u8(data.get_u8())?;
334
335        // Decode SIZE (uint16[3])
336        let size = [data.get_u16(), data.get_u16(), data.get_u16()];
337
338        // Decode MATRIX (float32[12])
339        let mut matrix = [[0.0f32; 4]; 3];
340        for row in &mut matrix {
341            for val in row {
342                *val = data.get_f32();
343            }
344        }
345
346        // Decode image data (remaining bytes)
347        let image_data = data.to_vec();
348
349        // Validate data size
350        let expected_size = (size[0] as usize) * (size[1] as usize) * (size[2] as usize)
351            * (num_components as usize) * scalar_type.size();
352
353        if image_data.len() != expected_size {
354            return Err(IgtlError::InvalidSize {
355                expected: expected_size,
356                actual: image_data.len(),
357            });
358        }
359
360        Ok(ImageMessage {
361            version,
362            num_components,
363            scalar_type,
364            endian,
365            coordinate,
366            size,
367            matrix,
368            data: image_data,
369        })
370    }
371}
372
373#[cfg(test)]
374mod tests {
375    use super::*;
376
377    #[test]
378    fn test_message_type() {
379        assert_eq!(ImageMessage::message_type(), "IMAGE");
380    }
381
382    #[test]
383    fn test_scalar_type_size() {
384        assert_eq!(ImageScalarType::Uint8.size(), 1);
385        assert_eq!(ImageScalarType::Uint16.size(), 2);
386        assert_eq!(ImageScalarType::Float32.size(), 4);
387        assert_eq!(ImageScalarType::Float64.size(), 8);
388    }
389
390    #[test]
391    fn test_new_2d() {
392        let size = [256, 256, 1];
393        let data_size = 256 * 256 * 1;
394        let data = vec![0u8; data_size];
395
396        let img = ImageMessage::new(ImageScalarType::Uint8, size, data).unwrap();
397        assert_eq!(img.size, size);
398        assert_eq!(img.num_components, 1);
399    }
400
401    #[test]
402    fn test_new_3d() {
403        let size = [128, 128, 64];
404        let data_size = 128 * 128 * 64;
405        let data = vec![0u8; data_size];
406
407        let img = ImageMessage::new(ImageScalarType::Uint8, size, data).unwrap();
408        assert_eq!(img.size, size);
409        assert_eq!(img.num_pixels(), 128 * 128 * 64);
410    }
411
412    #[test]
413    fn test_rgb() {
414        let size = [100, 100, 1];
415        let data_size = 100 * 100 * 3; // 3 components
416        let data = vec![0u8; data_size];
417
418        let img = ImageMessage::rgb(ImageScalarType::Uint8, size, data).unwrap();
419        assert_eq!(img.num_components, 3);
420    }
421
422    #[test]
423    fn test_invalid_data_size() {
424        let size = [10, 10, 1];
425        let data = vec![0u8; 50]; // Too small
426
427        let result = ImageMessage::new(ImageScalarType::Uint8, size, data);
428        assert!(result.is_err());
429    }
430
431    #[test]
432    fn test_with_matrix() {
433        let size = [10, 10, 1];
434        let data = vec![0u8; 100];
435        let matrix = [
436            [2.0, 0.0, 0.0, 10.0],
437            [0.0, 2.0, 0.0, 20.0],
438            [0.0, 0.0, 1.0, 0.0],
439        ];
440
441        let img = ImageMessage::new(ImageScalarType::Uint8, size, data)
442            .unwrap()
443            .with_matrix(matrix);
444
445        assert_eq!(img.matrix[0][3], 10.0);
446        assert_eq!(img.matrix[1][3], 20.0);
447    }
448
449    #[test]
450    fn test_encode_decode_small() {
451        let size = [4, 4, 1];
452        let data = vec![128u8; 16];
453
454        let original = ImageMessage::new(ImageScalarType::Uint8, size, data).unwrap();
455        let encoded = original.encode_content().unwrap();
456        let decoded = ImageMessage::decode_content(&encoded).unwrap();
457
458        assert_eq!(decoded.version, original.version);
459        assert_eq!(decoded.num_components, original.num_components);
460        assert_eq!(decoded.scalar_type, original.scalar_type);
461        assert_eq!(decoded.size, original.size);
462        assert_eq!(decoded.data, original.data);
463    }
464
465    #[test]
466    fn test_roundtrip_uint16() {
467        let size = [8, 8, 2];
468        let data_size = 8 * 8 * 2 * 2; // uint16 = 2 bytes
469        let data = vec![0u8; data_size];
470
471        let original = ImageMessage::new(ImageScalarType::Uint16, size, data).unwrap();
472        let encoded = original.encode_content().unwrap();
473        let decoded = ImageMessage::decode_content(&encoded).unwrap();
474
475        assert_eq!(decoded.scalar_type, ImageScalarType::Uint16);
476        assert_eq!(decoded.size, size);
477        assert_eq!(decoded.data.len(), data_size);
478    }
479
480    #[test]
481    fn test_roundtrip_with_matrix() {
482        let size = [5, 5, 1];
483        let data = vec![255u8; 25];
484        let matrix = [
485            [1.0, 0.0, 0.0, 5.0],
486            [0.0, 1.0, 0.0, 10.0],
487            [0.0, 0.0, 1.0, 15.0],
488        ];
489
490        let original = ImageMessage::new(ImageScalarType::Uint8, size, data)
491            .unwrap()
492            .with_matrix(matrix)
493            .with_coordinate(CoordinateSystem::LPS);
494
495        let encoded = original.encode_content().unwrap();
496        let decoded = ImageMessage::decode_content(&encoded).unwrap();
497
498        assert_eq!(decoded.coordinate, CoordinateSystem::LPS);
499        assert_eq!(decoded.matrix, matrix);
500    }
501
502    #[test]
503    fn test_decode_invalid_header() {
504        let data = vec![0u8; 50]; // Too short
505        let result = ImageMessage::decode_content(&data);
506        assert!(result.is_err());
507    }
508}