kinect_v2/
body_index_capture.rs

1use std::sync::Arc;
2
3use kinect_v2_sys::{
4    DEFAULT_FRAME_WAIT_TIMEOUT_MS, WAITABLE_HANDLE,
5    body_index::{BodyIndexFrame, BodyIndexFrameReader},
6    kinect::{self, KinectSensor},
7};
8use windows::Win32::Foundation::{E_FAIL, WAIT_OBJECT_0, WAIT_TIMEOUT};
9use windows::Win32::System::Threading::WaitForSingleObject;
10use windows::{Win32::Foundation::WAIT_EVENT, core::Error};
11
12/// Manages the Kinect sensor and provides access to body index frame data.
13///
14/// This struct is responsible for initializing and holding the necessary Kinect
15/// resources to capture body index frames.
16pub struct BodyIndexFrameCapture {
17    kinect: KinectSensor, // keep the kinect sensor instance alive.
18}
19
20impl BodyIndexFrameCapture {
21    /// Creates a new `BodyIndexFrameCapture` instance.
22    ///
23    /// This function initializes the default Kinect sensor, opens it,
24    /// and sets up the body index frame source and reader.
25    ///
26    /// # Errors
27    ///
28    /// Returns an error if the Kinect sensor cannot be initialized,
29    /// opened, or if the body index frame source is not active.
30    pub fn new() -> Result<Self, Error> {
31        let kinect = kinect::get_default_kinect_sensor()?;
32        kinect.open()?;
33
34        Ok(BodyIndexFrameCapture { kinect })
35    }
36
37    /// Returns an iterator over body index frames.
38    ///
39    /// The iterator will block waiting for new frames. Each item yielded by
40    /// the iterator is a `Result<BodyIndexFrameData, Error>`, allowing for error
41    /// handling during frame acquisition.
42    ///
43    /// # Errors
44    ///
45    /// Returns an error if it fails to subscribe to the frame arrived event,
46    /// which is necessary for the iterator to function.
47    pub fn iter(&self) -> Result<BodyIndexFrameCaptureIter, Error> {
48        let source = self.kinect.body_index_frame_source()?;
49        // Open the reader to activate the source.
50        let reader = source.open_reader()?;
51        // Ensure the body index frame source is active.
52        // If not, event subscription and frame acquisition might fail.
53        if !source.get_is_active()? {
54            log::warn!(
55                "Body index frame source is not active, cannot subscribe to frame arrived event."
56            );
57            return Err(Error::from_hresult(E_FAIL));
58        }
59
60        let mut waitable_handle = WAITABLE_HANDLE::default();
61        reader.subscribe_frame_arrived(&mut waitable_handle)?;
62        Ok(BodyIndexFrameCaptureIter {
63            reader,
64            waitable_handle,
65            timeout_ms: DEFAULT_FRAME_WAIT_TIMEOUT_MS,
66        })
67    }
68}
69
70/// An iterator that yields body index frames from a Kinect sensor.
71///
72/// This iterator blocks until a new frame is available or an error occurs.
73/// It is created by calling the `iter` method on `BodyIndexFrameCapture`.
74pub struct BodyIndexFrameCaptureIter {
75    reader: BodyIndexFrameReader,
76    waitable_handle: WAITABLE_HANDLE,
77    timeout_ms: u32,
78}
79
80impl Drop for BodyIndexFrameCaptureIter {
81    fn drop(&mut self) {
82        // Best effort to unsubscribe from the frame arrived event.
83        // Errors in `drop` are typically logged or ignored, as panicking in drop is problematic.
84        if let Err(e) = self.reader.unsubscribe_frame_arrived(self.waitable_handle) {
85            log::warn!("Failed to unsubscribe body index frame arrived event: {e:?}");
86        }
87    }
88}
89
90impl Iterator for BodyIndexFrameCaptureIter {
91    type Item = Result<BodyIndexFrameData, Error>;
92
93    fn next(&mut self) -> Option<Self::Item> {
94        loop {
95            let wait_status: WAIT_EVENT =
96                unsafe { WaitForSingleObject(self.waitable_handle, self.timeout_ms) };
97
98            if wait_status == WAIT_OBJECT_0 {
99                // Frame event was signaled.
100                // Use a closure and the `?` operator for cleaner error handling.
101                let result = (|| {
102                    let event_args = self
103                        .reader
104                        .get_frame_arrived_event_data(self.waitable_handle)?;
105                    let frame_reference = event_args.get_frame_reference()?;
106                    let body_index_frame = frame_reference.acquire_frame()?;
107                    BodyIndexFrameData::new(&body_index_frame)
108                })(); // Immediately invoke the closure
109                return Some(result);
110            } else if wait_status == WAIT_TIMEOUT {
111                // No new frame arrived within the timeout period.
112                // Continue waiting as this is a blocking iterator.
113                continue;
114            } else {
115                return Some(Err(Error::from_hresult(E_FAIL)));
116            }
117        }
118    }
119}
120
121#[derive(Debug, Clone)]
122pub struct BodyIndexStatistics {
123    /// Number of pixels for each body (indices 0-5)
124    pub body_pixel_counts: [u32; 6],
125    /// Number of background pixels (body index 255)
126    pub background_pixels: u32,
127    /// Number of pixels with unknown body index (not 0-5 or 255)
128    pub unknown_pixels: u32,
129    /// Total number of pixels belonging to any body
130    pub total_body_pixels: u32,
131    /// Total number of pixels in the frame
132    pub total_pixels: u32,
133    /// Percentage of frame covered by bodies
134    pub body_coverage_percentage: f32,
135}
136
137impl BodyIndexStatistics {
138    /// Returns the number of actively tracked bodies (bodies with > 0 pixels)
139    pub fn active_body_count(&self) -> usize {
140        self.body_pixel_counts
141            .iter()
142            .filter(|&&count| count > 0)
143            .count()
144    }
145
146    /// Returns the body index with the most pixels, if any bodies are present
147    pub fn dominant_body_index(&self) -> Option<usize> {
148        self.body_pixel_counts
149            .iter()
150            .enumerate()
151            .max_by_key(|(_, count)| *count)
152            .and_then(|(index, count)| if *count > 0 { Some(index) } else { None })
153    }
154}
155
156#[derive(Debug, Clone)]
157pub struct BodyIndexFrameData {
158    pub width: u32,
159    pub height: u32,
160    pub timestamp: u64,
161    pub data: Arc<[u8]>,
162}
163
164impl BodyIndexFrameData {
165    pub fn new(body_index_frame: &BodyIndexFrame) -> Result<Self, Error> {
166        let frame_description = body_index_frame.get_frame_description()?;
167        let width = frame_description.get_width()? as u32;
168        let height = frame_description.get_height()? as u32;
169        let timestamp = body_index_frame.get_relative_time()? as u64;
170
171        // Body index frames contain one byte per pixel indicating which body
172        // the pixel belongs to (0-5 for up to 6 tracked bodies, 255 for background)
173        let buffer_size = width * height;
174        let mut data = Vec::with_capacity(buffer_size as usize);
175
176        let raw_buffer = body_index_frame.access_underlying_buffer()?;
177        assert!(
178            raw_buffer.len() as u32 == buffer_size,
179            "Raw buffer size does not match expected size"
180        );
181        data.extend_from_slice(raw_buffer);
182
183        Ok(Self {
184            width,
185            height,
186            timestamp,
187            data: Arc::from(data),
188        })
189    }
190
191    /// Gets the body index for a specific pixel (x, y).
192    /// Returns None if the coordinates are out of bounds.
193    /// Returns Some(body_index) where body_index is 0-5 for tracked bodies,
194    /// or 255 for background pixels.
195    pub fn get_body_index_at(&self, x: u32, y: u32) -> Option<u8> {
196        if x >= self.width || y >= self.height {
197            return None;
198        }
199        let index = (y * self.width + x) as usize;
200        self.data.get(index).copied()
201    }
202
203    /// Returns true if the pixel at (x, y) belongs to a tracked body.
204    pub fn is_body_pixel(&self, x: u32, y: u32) -> bool {
205        self.get_body_index_at(x, y)
206            .map(|body_index| body_index < 6) // 0-5 are valid body indices
207            .unwrap_or(false)
208    }
209
210    /// Returns true if the pixel at (x, y) is background.
211    pub fn is_background_pixel(&self, x: u32, y: u32) -> bool {
212        self.get_body_index_at(x, y)
213            .map(|body_index| body_index == 255) // 255 is background
214            .unwrap_or(false)
215    }
216
217    /// Gets statistics about body coverage in the frame.
218    pub fn get_body_statistics(&self) -> BodyIndexStatistics {
219        let mut body_pixel_counts = [0u32; 6]; // Count for each body (0-5)
220        let mut background_pixels = 0u32;
221        let mut unknown_pixels = 0u32;
222
223        for &body_index in self.data.iter() {
224            match body_index {
225                0..=5 => body_pixel_counts[body_index as usize] += 1,
226                255 => background_pixels += 1,
227                _ => unknown_pixels += 1,
228            }
229        }
230
231        let total_pixels = self.width * self.height;
232        let total_body_pixels: u32 = body_pixel_counts.iter().sum();
233
234        BodyIndexStatistics {
235            body_pixel_counts,
236            background_pixels,
237            unknown_pixels,
238            total_body_pixels,
239            total_pixels,
240            body_coverage_percentage: (total_body_pixels as f32 / total_pixels as f32) * 100.0,
241        }
242    }
243}
244
245impl Default for BodyIndexFrameData {
246    fn default() -> Self {
247        Self {
248            width: 0,
249            height: 0,
250            timestamp: 0,
251            data: Arc::from([]),
252        }
253    }
254}
255
256#[cfg(test)]
257mod tests {
258    use super::*;
259    use anyhow::anyhow;
260    use std::sync::mpsc;
261
262    #[test]
263    fn body_index_capture_test() -> anyhow::Result<()> {
264        let (tx, rx) = mpsc::channel::<BodyIndexFrameData>();
265        let max_frames_to_capture = 10;
266        let capture_thread = std::thread::spawn(move || -> anyhow::Result<()> {
267            let capture = BodyIndexFrameCapture::new()?;
268            for (frame_count, frame) in capture.iter()?.enumerate() {
269                if frame_count >= max_frames_to_capture {
270                    break;
271                }
272                let data = frame.map_err(|e| anyhow!("Error capturing body index frame: {}", e))?;
273                if tx.send(data).is_err() {
274                    break;
275                }
276            }
277            Ok(())
278        });
279
280        let processing_thread = std::thread::spawn(move || -> anyhow::Result<()> {
281            for _ in 0..max_frames_to_capture {
282                let frame_data = match rx.recv() {
283                    Ok(data) => data,
284                    Err(_) => break,
285                };
286                println!(
287                    "Received body index frame: {}x{}, {} bytes, timestamp: {}",
288                    frame_data.width,
289                    frame_data.height,
290                    frame_data.data.len(),
291                    frame_data.timestamp
292                );
293                anyhow::ensure!(
294                    frame_data.width > 0,
295                    "Unexpected width: {}",
296                    frame_data.width
297                );
298                anyhow::ensure!(
299                    frame_data.height > 0,
300                    "Unexpected height: {}",
301                    frame_data.height
302                );
303                anyhow::ensure!(!frame_data.data.is_empty(), "Frame data is empty");
304                anyhow::ensure!(frame_data.timestamp > 0, "Timestamp is not positive");
305            }
306            Ok(())
307        });
308
309        capture_thread
310            .join()
311            .map_err(|e| anyhow!("Body index capture thread join error: {:?}", e))??;
312        processing_thread
313            .join()
314            .map_err(|e| anyhow!("Processing thread join error: {:?}", e))??;
315        Ok(())
316    }
317}