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, VideoCodec};
27//! use openigtlink_rust::io::IgtlClient;
28//!
29//! let mut client = IgtlClient::connect("127.0.0.1:18944")?;
30//!
31//! let mut video = VideoMessage::new();
32//! video.set_device_name("LaparoscopicCamera");
33//! video.set_codec(VideoCodec::H264);
34//! video.set_width(1920);
35//! video.set_height(1080);
36//! video.set_frame_rate(30);
37//!
38//! // Simulated H.264 frame data
39//! let frame_data = vec![0u8; 50000]; // Compressed frame
40//! video.set_frame_data(frame_data);
41//!
42//! client.send(&video)?;
43//! # Ok::<(), openigtlink_rust::IgtlError>(())
44//! ```
45//!
46//! ## Receiving MJPEG Surgical Video
47//!
48//! ```no_run
49//! use openigtlink_rust::io::IgtlServer;
50//! use openigtlink_rust::protocol::types::{VideoMessage, VideoCodec};
51//! use openigtlink_rust::protocol::message::Message;
52//!
53//! let server = IgtlServer::bind("0.0.0.0:18944")?;
54//! let mut client_conn = server.accept()?;
55//!
56//! loop {
57//!     let message = client_conn.receive()?;
58//!
59//!     if message.header.message_type == "VIDEO" {
60//!         let video = VideoMessage::from_bytes(&message.body)?;
61//!         println!("Video frame: {}x{} @ {}fps",
62//!                  video.width, video.height, video.frame_rate);
63//!
64//!         if video.codec == VideoCodec::Mjpeg {
65//!             // Decode MJPEG frame
66//!             println!("MJPEG frame size: {} bytes", video.frame_data.len());
67//!         }
68//!     }
69//! }
70//! # Ok::<(), openigtlink_rust::IgtlError>(())
71//! ```
72
73use crate::protocol::message::Message;
74use crate::error::{IgtlError, Result};
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(
125        codec: CodecType,
126        width: u16,
127        height: u16,
128        frame_data: Vec<u8>,
129    ) -> Self {
130        VideoMessage {
131            codec,
132            width,
133            height,
134            frame_data,
135        }
136    }
137
138    /// Get frame size in bytes
139    pub fn frame_size(&self) -> usize {
140        self.frame_data.len()
141    }
142
143    /// Check if frame is empty
144    pub fn is_empty(&self) -> bool {
145        self.frame_data.is_empty()
146    }
147}
148
149impl Message for VideoMessage {
150    fn message_type() -> &'static str {
151        "VIDEO"
152    }
153
154    fn encode_content(&self) -> Result<Vec<u8>> {
155        let mut buf = Vec::with_capacity(6 + self.frame_data.len());
156
157        // Encode CODEC (uint8)
158        buf.put_u8(self.codec as u8);
159
160        // Encode WIDTH (uint16)
161        buf.put_u16(self.width);
162
163        // Encode HEIGHT (uint16)
164        buf.put_u16(self.height);
165
166        // Encode Reserved (uint8)
167        buf.put_u8(0);
168
169        // Encode frame data
170        buf.extend_from_slice(&self.frame_data);
171
172        Ok(buf)
173    }
174
175    fn decode_content(mut data: &[u8]) -> Result<Self> {
176        if data.len() < 6 {
177            return Err(IgtlError::InvalidSize {
178                expected: 6,
179                actual: data.len(),
180            });
181        }
182
183        // Decode CODEC (uint8)
184        let codec = CodecType::from_u8(data.get_u8())?;
185
186        // Decode WIDTH (uint16)
187        let width = data.get_u16();
188
189        // Decode HEIGHT (uint16)
190        let height = data.get_u16();
191
192        // Decode Reserved (uint8)
193        let _reserved = data.get_u8();
194
195        // Decode frame data (remaining bytes)
196        let frame_data = data.to_vec();
197
198        Ok(VideoMessage {
199            codec,
200            width,
201            height,
202            frame_data,
203        })
204    }
205}
206
207#[cfg(test)]
208mod tests {
209    use super::*;
210
211    #[test]
212    fn test_message_type() {
213        assert_eq!(VideoMessage::message_type(), "VIDEO");
214    }
215
216    #[test]
217    fn test_new() {
218        let frame = vec![0u8; 1920 * 1080 * 3]; // RGB frame
219        let msg = VideoMessage::new(CodecType::Raw, 1920, 1080, frame);
220
221        assert_eq!(msg.width, 1920);
222        assert_eq!(msg.height, 1080);
223        assert_eq!(msg.codec, CodecType::Raw);
224    }
225
226    #[test]
227    fn test_frame_size() {
228        let frame = vec![0u8; 1024];
229        let msg = VideoMessage::new(CodecType::H264, 640, 480, frame);
230
231        assert_eq!(msg.frame_size(), 1024);
232    }
233
234    #[test]
235    fn test_is_empty() {
236        let msg = VideoMessage::new(CodecType::MJPEG, 320, 240, vec![]);
237        assert!(msg.is_empty());
238    }
239
240    #[test]
241    fn test_encode() {
242        let frame = vec![1, 2, 3, 4, 5];
243        let msg = VideoMessage::new(CodecType::H264, 100, 100, frame);
244        let encoded = msg.encode_content().unwrap();
245
246        // 6 bytes header + 5 bytes data = 11 bytes
247        assert_eq!(encoded.len(), 11);
248        assert_eq!(encoded[0], CodecType::H264 as u8);
249    }
250
251    #[test]
252    fn test_roundtrip_h264() {
253        let original = VideoMessage::new(
254            CodecType::H264,
255            1920,
256            1080,
257            vec![0xFF; 1000], // Simulated compressed data
258        );
259
260        let encoded = original.encode_content().unwrap();
261        let decoded = VideoMessage::decode_content(&encoded).unwrap();
262
263        assert_eq!(decoded.codec, CodecType::H264);
264        assert_eq!(decoded.width, 1920);
265        assert_eq!(decoded.height, 1080);
266        assert_eq!(decoded.frame_data.len(), 1000);
267    }
268
269    #[test]
270    fn test_roundtrip_raw() {
271        let original = VideoMessage::new(
272            CodecType::Raw,
273            640,
274            480,
275            vec![128u8; 640 * 480 * 3],
276        );
277
278        let encoded = original.encode_content().unwrap();
279        let decoded = VideoMessage::decode_content(&encoded).unwrap();
280
281        assert_eq!(decoded.codec, CodecType::Raw);
282        assert_eq!(decoded.width, 640);
283        assert_eq!(decoded.height, 480);
284    }
285
286    #[test]
287    fn test_decode_invalid_header() {
288        let data = vec![0u8; 5]; // Too short
289        let result = VideoMessage::decode_content(&data);
290        assert!(result.is_err());
291    }
292
293    #[test]
294    fn test_decode_invalid_codec() {
295        let data = vec![99, 0, 100, 0, 100, 0]; // Invalid codec
296        let result = VideoMessage::decode_content(&data);
297        assert!(result.is_err());
298    }
299}