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