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}