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