openigtlink_rust/protocol/types/
video.rs

1//! VIDEO message type implementation
2//!
3//! The VIDEO message is used to transfer video frame data for real-time visualization
4//! during image-guided procedures.
5//!
6//! # Use Cases
7//!
8//! - **Endoscopic Surgery** - Streaming laparoscopic or arthroscopic camera feeds
9//! - **Microscopy** - Real-time surgical microscope video during neurosurgery
10//! - **External Cameras** - Room cameras for documenting procedures
11//! - **Ultrasound Video** - Color Doppler or B-mode ultrasound video streams
12//! - **Robotic Surgery** - Multiple camera angles from surgical robots
13//!
14//! # Supported Codecs
15//!
16//! - **H.264 (AVC)** - Efficient compression for high-quality video
17//! - **MJPEG** - Motion JPEG for lower latency
18//! - **VP9** - Open codec alternative
19//! - **Raw/Uncompressed** - Maximum quality, higher bandwidth
20//!
21//! # Examples
22//!
23//! ## Streaming Laparoscopic Video (H.264)
24//!
25//! ```no_run
26//! use openigtlink_rust::protocol::types::{VideoMessage, CodecType};
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 H.264 frame data
36//! let frame_data = vec![0u8; 50000]; // Compressed frame
37//!
38//! let video = VideoMessage::new(
39//!     CodecType::H264,
40//!     1920,
41//!     1080,
42//!     frame_data
43//! );
44//!
45//! let msg = IgtlMessage::new(video, "LaparoscopicCamera")?;
46//! client.send(&msg)?;
47//! # Ok::<(), openigtlink_rust::IgtlError>(())
48//! ```
49//!
50//! ## Receiving MJPEG Surgical Video
51//!
52//! ```no_run
53//! use openigtlink_rust::io::IgtlServer;
54//! use openigtlink_rust::protocol::types::{VideoMessage, CodecType};
55//!
56//! let server = IgtlServer::bind("0.0.0.0:18944")?;
57//! let mut client_conn = server.accept()?;
58//!
59//! loop {
60//!     let message = client_conn.receive::<VideoMessage>()?;
61//!
62//!     println!("Video frame: {}x{}",
63//!              message.content.width, message.content.height);
64//!
65//!     if message.content.codec == CodecType::MJPEG {
66//!         // Decode MJPEG frame
67//!         println!("MJPEG frame size: {} bytes", message.content.frame_data.len());
68//!     }
69//! }
70//! # Ok::<(), openigtlink_rust::IgtlError>(())
71//! ```
72
73use crate::error::{IgtlError, Result};
74use crate::protocol::message::Message;
75use bytes::{Buf, BufMut};
76
77/// Video codec type
78#[derive(Debug, Clone, Copy, PartialEq, Eq)]
79pub enum CodecType {
80    H264 = 1,
81    VP9 = 2,
82    HEVC = 3,
83    MJPEG = 4,
84    Raw = 5,
85}
86
87impl CodecType {
88    /// Create from codec value
89    pub fn from_u8(value: u8) -> Result<Self> {
90        match value {
91            1 => Ok(CodecType::H264),
92            2 => Ok(CodecType::VP9),
93            3 => Ok(CodecType::HEVC),
94            4 => Ok(CodecType::MJPEG),
95            5 => Ok(CodecType::Raw),
96            _ => Err(IgtlError::InvalidSize {
97                expected: 1,
98                actual: value as usize,
99            }),
100        }
101    }
102}
103
104/// VIDEO message for video frame data
105///
106/// # OpenIGTLink Specification
107/// - Message type: "VIDEO"
108/// - Format: CODEC (uint8) + WIDTH (uint16) + HEIGHT (uint16) + Reserved (uint8) + Frame data
109/// - Header size: 6 bytes + variable frame data
110#[derive(Debug, Clone, PartialEq)]
111pub struct VideoMessage {
112    /// Video codec type
113    pub codec: CodecType,
114    /// Frame width in pixels
115    pub width: u16,
116    /// Frame height in pixels
117    pub height: u16,
118    /// Encoded frame data
119    pub frame_data: Vec<u8>,
120}
121
122impl VideoMessage {
123    /// Create a new VIDEO message
124    pub fn new(codec: CodecType, width: u16, height: u16, frame_data: Vec<u8>) -> Self {
125        VideoMessage {
126            codec,
127            width,
128            height,
129            frame_data,
130        }
131    }
132
133    /// Get frame size in bytes
134    pub fn frame_size(&self) -> usize {
135        self.frame_data.len()
136    }
137
138    /// Check if frame is empty
139    pub fn is_empty(&self) -> bool {
140        self.frame_data.is_empty()
141    }
142}
143
144impl Message for VideoMessage {
145    fn message_type() -> &'static str {
146        "VIDEO"
147    }
148
149    fn encode_content(&self) -> Result<Vec<u8>> {
150        let mut buf = Vec::with_capacity(6 + self.frame_data.len());
151
152        // Encode CODEC (uint8)
153        buf.put_u8(self.codec as u8);
154
155        // Encode WIDTH (uint16)
156        buf.put_u16(self.width);
157
158        // Encode HEIGHT (uint16)
159        buf.put_u16(self.height);
160
161        // Encode Reserved (uint8)
162        buf.put_u8(0);
163
164        // Encode frame data
165        buf.extend_from_slice(&self.frame_data);
166
167        Ok(buf)
168    }
169
170    fn decode_content(mut data: &[u8]) -> Result<Self> {
171        if data.len() < 6 {
172            return Err(IgtlError::InvalidSize {
173                expected: 6,
174                actual: data.len(),
175            });
176        }
177
178        // Decode CODEC (uint8)
179        let codec = CodecType::from_u8(data.get_u8())?;
180
181        // Decode WIDTH (uint16)
182        let width = data.get_u16();
183
184        // Decode HEIGHT (uint16)
185        let height = data.get_u16();
186
187        // Decode Reserved (uint8)
188        let _reserved = data.get_u8();
189
190        // Decode frame data (remaining bytes)
191        let frame_data = data.to_vec();
192
193        Ok(VideoMessage {
194            codec,
195            width,
196            height,
197            frame_data,
198        })
199    }
200}
201
202#[cfg(test)]
203mod tests {
204    use super::*;
205
206    #[test]
207    fn test_message_type() {
208        assert_eq!(VideoMessage::message_type(), "VIDEO");
209    }
210
211    #[test]
212    fn test_new() {
213        let frame = vec![0u8; 1920 * 1080 * 3]; // RGB frame
214        let msg = VideoMessage::new(CodecType::Raw, 1920, 1080, frame);
215
216        assert_eq!(msg.width, 1920);
217        assert_eq!(msg.height, 1080);
218        assert_eq!(msg.codec, CodecType::Raw);
219    }
220
221    #[test]
222    fn test_frame_size() {
223        let frame = vec![0u8; 1024];
224        let msg = VideoMessage::new(CodecType::H264, 640, 480, frame);
225
226        assert_eq!(msg.frame_size(), 1024);
227    }
228
229    #[test]
230    fn test_is_empty() {
231        let msg = VideoMessage::new(CodecType::MJPEG, 320, 240, vec![]);
232        assert!(msg.is_empty());
233    }
234
235    #[test]
236    fn test_encode() {
237        let frame = vec![1, 2, 3, 4, 5];
238        let msg = VideoMessage::new(CodecType::H264, 100, 100, frame);
239        let encoded = msg.encode_content().unwrap();
240
241        // 6 bytes header + 5 bytes data = 11 bytes
242        assert_eq!(encoded.len(), 11);
243        assert_eq!(encoded[0], CodecType::H264 as u8);
244    }
245
246    #[test]
247    fn test_roundtrip_h264() {
248        let original = VideoMessage::new(
249            CodecType::H264,
250            1920,
251            1080,
252            vec![0xFF; 1000], // Simulated compressed data
253        );
254
255        let encoded = original.encode_content().unwrap();
256        let decoded = VideoMessage::decode_content(&encoded).unwrap();
257
258        assert_eq!(decoded.codec, CodecType::H264);
259        assert_eq!(decoded.width, 1920);
260        assert_eq!(decoded.height, 1080);
261        assert_eq!(decoded.frame_data.len(), 1000);
262    }
263
264    #[test]
265    fn test_roundtrip_raw() {
266        let original = VideoMessage::new(CodecType::Raw, 640, 480, vec![128u8; 640 * 480 * 3]);
267
268        let encoded = original.encode_content().unwrap();
269        let decoded = VideoMessage::decode_content(&encoded).unwrap();
270
271        assert_eq!(decoded.codec, CodecType::Raw);
272        assert_eq!(decoded.width, 640);
273        assert_eq!(decoded.height, 480);
274    }
275
276    #[test]
277    fn test_decode_invalid_header() {
278        let data = vec![0u8; 5]; // Too short
279        let result = VideoMessage::decode_content(&data);
280        assert!(result.is_err());
281    }
282
283    #[test]
284    fn test_decode_invalid_codec() {
285        let data = vec![99, 0, 100, 0, 100, 0]; // Invalid codec
286        let result = VideoMessage::decode_content(&data);
287        assert!(result.is_err());
288    }
289}