openigtlink_rust/protocol/types/
lbmeta.rs

1//! LBMETA (LabelMeta) message type implementation
2//!
3//! The LBMETA message is used to transfer label/segmentation metadata not available
4//! in LABEL messages, such as label names, colors, and owner information.
5
6use crate::protocol::message::Message;
7use crate::error::{IgtlError, Result};
8use bytes::{Buf, BufMut};
9
10/// Label metadata element
11#[derive(Debug, Clone, PartialEq)]
12pub struct LabelMetaElement {
13    /// Name or description of the label (max 64 chars)
14    pub name: String,
15    /// ID to query the LABEL (max 20 chars)
16    pub id: String,
17    /// Label value (intensity value in the label image)
18    pub label: u8,
19    /// RGBA color for visualization
20    pub rgba: [u8; 4],
21    /// Number of pixels in each direction (RI, RJ, RK)
22    pub size: [u16; 3],
23    /// Owner image ID (max 20 chars)
24    pub owner: String,
25}
26
27impl LabelMetaElement {
28    /// Create a new label metadata element
29    pub fn new(
30        name: impl Into<String>,
31        id: impl Into<String>,
32        label: u8,
33    ) -> Self {
34        LabelMetaElement {
35            name: name.into(),
36            id: id.into(),
37            label,
38            rgba: [255, 255, 255, 255], // Default to white
39            size: [0, 0, 0],
40            owner: String::new(),
41        }
42    }
43
44    /// Set RGBA color
45    pub fn with_rgba(mut self, rgba: [u8; 4]) -> Self {
46        self.rgba = rgba;
47        self
48    }
49
50    /// Set label size
51    pub fn with_size(mut self, size: [u16; 3]) -> Self {
52        self.size = size;
53        self
54    }
55
56    /// Set owner image
57    pub fn with_owner(mut self, owner: impl Into<String>) -> Self {
58        self.owner = owner.into();
59        self
60    }
61}
62
63/// LBMETA message containing multiple label metadata elements
64///
65/// # OpenIGTLink Specification
66/// - Message type: "LBMETA"
67/// - Each element: NAME (char[64]) + ID (char[20]) + LABEL (uint8) + Reserved (uint8) + RGBA (uint8[4]) + SIZE (uint16[3]) + OWNER (char[20])
68/// - Element size: 64 + 20 + 1 + 1 + 4 + 6 + 20 = 116 bytes
69#[derive(Debug, Clone, PartialEq)]
70pub struct LbMetaMessage {
71    /// List of label metadata elements
72    pub labels: Vec<LabelMetaElement>,
73}
74
75impl LbMetaMessage {
76    /// Create a new LBMETA message
77    pub fn new(labels: Vec<LabelMetaElement>) -> Self {
78        LbMetaMessage { labels }
79    }
80
81    /// Create an empty LBMETA message
82    pub fn empty() -> Self {
83        LbMetaMessage { labels: Vec::new() }
84    }
85
86    /// Add a label metadata element
87    pub fn add_label(&mut self, label: LabelMetaElement) {
88        self.labels.push(label);
89    }
90
91    /// Get number of labels
92    pub fn len(&self) -> usize {
93        self.labels.len()
94    }
95
96    /// Check if message has no labels
97    pub fn is_empty(&self) -> bool {
98        self.labels.is_empty()
99    }
100}
101
102impl Message for LbMetaMessage {
103    fn message_type() -> &'static str {
104        "LBMETA"
105    }
106
107    fn encode_content(&self) -> Result<Vec<u8>> {
108        let mut buf = Vec::with_capacity(self.labels.len() * 116);
109
110        for label in &self.labels {
111            // Encode NAME (char[64])
112            let mut name_bytes = [0u8; 64];
113            let name_str = label.name.as_bytes();
114            let copy_len = name_str.len().min(63);
115            name_bytes[..copy_len].copy_from_slice(&name_str[..copy_len]);
116            buf.extend_from_slice(&name_bytes);
117
118            // Encode ID (char[20])
119            let mut id_bytes = [0u8; 20];
120            let id_str = label.id.as_bytes();
121            let copy_len = id_str.len().min(19);
122            id_bytes[..copy_len].copy_from_slice(&id_str[..copy_len]);
123            buf.extend_from_slice(&id_bytes);
124
125            // Encode LABEL (uint8)
126            buf.put_u8(label.label);
127
128            // Encode Reserved (uint8)
129            buf.put_u8(0);
130
131            // Encode RGBA (uint8[4])
132            buf.extend_from_slice(&label.rgba);
133
134            // Encode SIZE (uint16[3])
135            for &s in &label.size {
136                buf.put_u16(s);
137            }
138
139            // Encode OWNER (char[20])
140            let mut owner_bytes = [0u8; 20];
141            let owner_str = label.owner.as_bytes();
142            let copy_len = owner_str.len().min(19);
143            owner_bytes[..copy_len].copy_from_slice(&owner_str[..copy_len]);
144            buf.extend_from_slice(&owner_bytes);
145        }
146
147        Ok(buf)
148    }
149
150    fn decode_content(mut data: &[u8]) -> Result<Self> {
151        let mut labels = Vec::new();
152
153        while data.len() >= 116 {
154            // Decode NAME (char[64])
155            let name_bytes = &data[..64];
156            data.advance(64);
157            let name_len = name_bytes.iter().position(|&b| b == 0).unwrap_or(64);
158            let name = String::from_utf8(name_bytes[..name_len].to_vec())?;
159
160            // Decode ID (char[20])
161            let id_bytes = &data[..20];
162            data.advance(20);
163            let id_len = id_bytes.iter().position(|&b| b == 0).unwrap_or(20);
164            let id = String::from_utf8(id_bytes[..id_len].to_vec())?;
165
166            // Decode LABEL (uint8)
167            let label = data.get_u8();
168
169            // Decode Reserved (uint8)
170            let _reserved = data.get_u8();
171
172            // Decode RGBA (uint8[4])
173            let rgba = [data.get_u8(), data.get_u8(), data.get_u8(), data.get_u8()];
174
175            // Decode SIZE (uint16[3])
176            let size = [data.get_u16(), data.get_u16(), data.get_u16()];
177
178            // Decode OWNER (char[20])
179            let owner_bytes = &data[..20];
180            data.advance(20);
181            let owner_len = owner_bytes.iter().position(|&b| b == 0).unwrap_or(20);
182            let owner = String::from_utf8(owner_bytes[..owner_len].to_vec())?;
183
184            labels.push(LabelMetaElement {
185                name,
186                id,
187                label,
188                rgba,
189                size,
190                owner,
191            });
192        }
193
194        if !data.is_empty() {
195            return Err(IgtlError::InvalidSize {
196                expected: 0,
197                actual: data.len(),
198            });
199        }
200
201        Ok(LbMetaMessage { labels })
202    }
203}
204
205#[cfg(test)]
206mod tests {
207    use super::*;
208
209    #[test]
210    fn test_message_type() {
211        assert_eq!(LbMetaMessage::message_type(), "LBMETA");
212    }
213
214    #[test]
215    fn test_empty() {
216        let msg = LbMetaMessage::empty();
217        assert!(msg.is_empty());
218        assert_eq!(msg.len(), 0);
219    }
220
221    #[test]
222    fn test_new() {
223        let elem = LabelMetaElement::new("Liver", "LBL001", 1);
224        assert_eq!(elem.name, "Liver");
225        assert_eq!(elem.id, "LBL001");
226        assert_eq!(elem.label, 1);
227        assert_eq!(elem.rgba, [255, 255, 255, 255]);
228    }
229
230    #[test]
231    fn test_with_rgba() {
232        let elem = LabelMetaElement::new("Heart", "LBL002", 2)
233            .with_rgba([255, 0, 0, 255]);
234        assert_eq!(elem.rgba, [255, 0, 0, 255]);
235    }
236
237    #[test]
238    fn test_with_size() {
239        let elem = LabelMetaElement::new("Kidney", "LBL003", 3)
240            .with_size([256, 256, 100]);
241        assert_eq!(elem.size, [256, 256, 100]);
242    }
243
244    #[test]
245    fn test_add_label() {
246        let mut msg = LbMetaMessage::empty();
247        msg.add_label(LabelMetaElement::new("Liver", "LBL001", 1));
248        assert_eq!(msg.len(), 1);
249    }
250
251    #[test]
252    fn test_encode_single() {
253        let elem = LabelMetaElement::new("TestLabel", "TEST001", 1);
254        let msg = LbMetaMessage::new(vec![elem]);
255        let encoded = msg.encode_content().unwrap();
256
257        assert_eq!(encoded.len(), 116);
258    }
259
260    #[test]
261    fn test_roundtrip() {
262        let original = LbMetaMessage::new(vec![
263            LabelMetaElement::new("Liver", "LBL001", 1)
264                .with_rgba([139, 69, 19, 255])
265                .with_size([512, 512, 200])
266                .with_owner("CT001"),
267        ]);
268
269        let encoded = original.encode_content().unwrap();
270        let decoded = LbMetaMessage::decode_content(&encoded).unwrap();
271
272        assert_eq!(decoded.labels.len(), 1);
273        assert_eq!(decoded.labels[0].name, "Liver");
274        assert_eq!(decoded.labels[0].id, "LBL001");
275        assert_eq!(decoded.labels[0].label, 1);
276        assert_eq!(decoded.labels[0].rgba, [139, 69, 19, 255]);
277        assert_eq!(decoded.labels[0].size, [512, 512, 200]);
278        assert_eq!(decoded.labels[0].owner, "CT001");
279    }
280
281    #[test]
282    fn test_roundtrip_multiple() {
283        let original = LbMetaMessage::new(vec![
284            LabelMetaElement::new("Liver", "LBL001", 1)
285                .with_rgba([139, 69, 19, 255]),
286            LabelMetaElement::new("Heart", "LBL002", 2)
287                .with_rgba([255, 0, 0, 255]),
288            LabelMetaElement::new("Kidney", "LBL003", 3)
289                .with_rgba([0, 255, 0, 255]),
290        ]);
291
292        let encoded = original.encode_content().unwrap();
293        let decoded = LbMetaMessage::decode_content(&encoded).unwrap();
294
295        assert_eq!(decoded.labels.len(), 3);
296        assert_eq!(decoded.labels[0].name, "Liver");
297        assert_eq!(decoded.labels[1].name, "Heart");
298        assert_eq!(decoded.labels[2].name, "Kidney");
299    }
300
301    #[test]
302    fn test_empty_message() {
303        let msg = LbMetaMessage::empty();
304        let encoded = msg.encode_content().unwrap();
305        let decoded = LbMetaMessage::decode_content(&encoded).unwrap();
306
307        assert_eq!(decoded.labels.len(), 0);
308    }
309
310    #[test]
311    fn test_decode_invalid_size() {
312        let data = vec![0u8; 115]; // One byte short
313        let result = LbMetaMessage::decode_content(&data);
314        assert!(result.is_err());
315    }
316}