kinect_v2/
infrared_capture.rs

1use std::sync::Arc;
2
3use kinect_v2_sys::{
4    DEFAULT_FRAME_WAIT_TIMEOUT_MS, KINECT_DEFAULT_CAPTURE_FPS, WAITABLE_HANDLE,
5    infrared::{InfraredFrame, InfraredFrameReader},
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 infrared frame data.
13///
14/// This struct is responsible for initializing and holding the necessary Kinect
15/// resources to capture infrared frames.
16pub struct InfraredFrameCapture {
17    kinect: KinectSensor, // keep the kinect sensor instance alive.
18}
19
20impl InfraredFrameCapture {
21    /// Creates a new `InfraredFrameCapture` instance.
22    ///
23    /// This function initializes the default Kinect sensor, opens it,
24    /// and sets up the infrared frame source and reader.
25    ///
26    /// # Errors
27    ///
28    /// Returns an error if the Kinect sensor cannot be initialized,
29    /// opened, or if the infrared 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(InfraredFrameCapture { kinect })
35    }
36
37    /// Returns an iterator over infrared frames.
38    ///
39    /// The iterator will block waiting for new frames. Each item yielded by
40    /// the iterator is a `Result<InfraredFrameData, 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<InfraredFrameCaptureIter, Error> {
48        let source = self.kinect.infrared_frame_source()?;
49        // Open the reader to activate the source.
50        let reader = source.open_reader()?;
51        // Ensure the infrared frame source is active.
52        // If not, event subscription and frame acquisition might fail.
53        if !source.get_is_active()? {
54            log::warn!(
55                "Infrared frame source is not active, cannot subscribe to frame arrived event."
56            );
57            return Err(Error::from_hresult(E_FAIL));
58        }
59
60        let waitable_handle = reader.subscribe_frame_arrived()?;
61        Ok(InfraredFrameCaptureIter {
62            reader,
63            waitable_handle,
64            timeout_ms: DEFAULT_FRAME_WAIT_TIMEOUT_MS,
65        })
66    }
67}
68
69/// An iterator that yields infrared frames from a Kinect sensor.
70///
71/// This iterator blocks until a new frame is available or an error occurs.
72/// It is created by calling the `iter` method on `InfraredFrameCapture`.
73pub struct InfraredFrameCaptureIter {
74    reader: InfraredFrameReader,
75    waitable_handle: WAITABLE_HANDLE,
76    timeout_ms: u32,
77}
78
79impl Drop for InfraredFrameCaptureIter {
80    fn drop(&mut self) {
81        // Best effort to unsubscribe from the frame arrived event.
82        // Errors in `drop` are typically logged or ignored, as panicking in drop is problematic.
83        if let Err(e) = self.reader.unsubscribe_frame_arrived(self.waitable_handle) {
84            log::warn!("Failed to unsubscribe infrared frame arrived event: {e:?}");
85        }
86    }
87}
88
89impl Iterator for InfraredFrameCaptureIter {
90    type Item = Result<InfraredFrameData, Error>;
91
92    fn next(&mut self) -> Option<Self::Item> {
93        loop {
94            let wait_status: WAIT_EVENT =
95                unsafe { WaitForSingleObject(self.waitable_handle, self.timeout_ms) };
96
97            if wait_status == WAIT_OBJECT_0 {
98                // Frame event was signaled.
99                // Use a closure and the `?` operator for cleaner error handling.
100                let result = (|| {
101                    let event_args = self
102                        .reader
103                        .get_frame_arrived_event_data(self.waitable_handle)?;
104                    let frame_reference = event_args.get_frame_reference()?;
105                    let infrared_frame = frame_reference.acquire_frame()?;
106                    InfraredFrameData::new(&infrared_frame)
107                })(); // Immediately invoke the closure
108                return Some(result);
109            } else if wait_status == WAIT_TIMEOUT {
110                // No new frame arrived within the timeout period.
111                // Continue waiting as this is a blocking iterator.
112                continue;
113            } else {
114                return Some(Err(Error::from_hresult(E_FAIL)));
115            }
116        }
117    }
118}
119
120#[derive(Debug, Clone)]
121pub struct InfraredFrameData {
122    pub width: u32,
123    pub height: u32,
124    pub fps: u32, // Infrared frames typically run at 30 FPS
125    pub timestamp: u64,
126    pub data: Arc<[u16]>, // Infrared data is typically 16-bit unsigned integers
127}
128
129impl InfraredFrameData {
130    pub fn new(infrared_frame: &InfraredFrame) -> Result<Self, Error> {
131        let frame_description = infrared_frame.get_frame_description()?;
132        let width = frame_description.get_width()? as u32;
133        let height = frame_description.get_height()? as u32;
134        let timestamp = infrared_frame.get_relative_time()? as u64;
135        let raw_buffer = infrared_frame.access_underlying_buffer()?;
136
137        Ok(Self {
138            width,
139            height,
140            fps: KINECT_DEFAULT_CAPTURE_FPS,
141            timestamp,
142            data: Arc::from(raw_buffer.to_vec()),
143        })
144    }
145
146    /// Gets the infrared intensity at a specific pixel coordinate.
147    ///
148    /// Returns `None` if the coordinates are out of bounds.
149    pub fn get_intensity_at(&self, x: u32, y: u32) -> Option<u16> {
150        if x >= self.width || y >= self.height {
151            return None;
152        }
153        let index = (y * self.width + x) as usize;
154        self.data.get(index).copied()
155    }
156
157    /// Normalizes infrared values to 0.0-1.0 range for visualization.
158    ///
159    /// This is useful for converting raw infrared values to a normalized
160    /// range suitable for image processing or visualization.
161    pub fn normalize_intensity(&self, intensity: u16) -> f32 {
162        // Typical infrared values range from 0 to 65535 (16-bit)
163        intensity as f32 / 65535.0
164    }
165
166    /// Converts infrared frame data to 8-bit grayscale for visualization.
167    ///
168    /// This method scales the 16-bit infrared values down to 8-bit values
169    /// suitable for standard image formats.
170    pub fn to_grayscale_u8(&self) -> Vec<u8> {
171        self.data
172            .iter()
173            .map(|&intensity| (intensity >> 8) as u8) // Simple bit-shift scaling
174            .collect()
175    }
176
177    /// Gets the minimum and maximum infrared intensity values in the frame.
178    ///
179    /// This is useful for adaptive scaling and analysis of the infrared data.
180    pub fn get_intensity_range(&self) -> (u16, u16) {
181        let min_intensity = *self.data.iter().min().unwrap_or(&0);
182        let max_intensity = *self.data.iter().max().unwrap_or(&0);
183        (min_intensity, max_intensity)
184    }
185}
186
187impl Default for InfraredFrameData {
188    fn default() -> Self {
189        Self {
190            width: 0,
191            height: 0,
192            fps: KINECT_DEFAULT_CAPTURE_FPS,
193            timestamp: 0,
194            data: Arc::from([]),
195        }
196    }
197}
198
199#[cfg(test)]
200mod tests {
201    use super::*;
202    use anyhow::anyhow;
203    use std::sync::mpsc;
204
205    #[test]
206    fn infrared_capture_test() -> anyhow::Result<()> {
207        let (tx, rx) = mpsc::channel::<InfraredFrameData>();
208        let max_frames_to_capture = 10;
209        let capture_thread = std::thread::spawn(move || -> anyhow::Result<()> {
210            let capture = InfraredFrameCapture::new()?;
211            for (frame_count, frame) in capture.iter()?.enumerate() {
212                if frame_count >= max_frames_to_capture {
213                    break;
214                }
215                let data = frame.map_err(|e| anyhow!("Error capturing infrared frame: {}", e))?;
216                if tx.send(data).is_err() {
217                    break;
218                }
219            }
220            Ok(())
221        });
222
223        let processing_thread = std::thread::spawn(move || -> anyhow::Result<()> {
224            for _ in 0..max_frames_to_capture {
225                let frame_data = match rx.recv() {
226                    Ok(data) => data,
227                    Err(_) => break,
228                };
229                println!(
230                    "Received infrared frame: {}x{}, {} values, timestamp: {}, fps: {}",
231                    frame_data.width,
232                    frame_data.height,
233                    frame_data.data.len(),
234                    frame_data.timestamp,
235                    frame_data.fps
236                );
237                anyhow::ensure!(
238                    frame_data.width > 0,
239                    "Unexpected width: {}",
240                    frame_data.width
241                );
242                anyhow::ensure!(
243                    frame_data.height > 0,
244                    "Unexpected height: {}",
245                    frame_data.height
246                );
247                anyhow::ensure!(!frame_data.data.is_empty(), "Frame data is empty");
248                anyhow::ensure!(frame_data.timestamp > 0, "Timestamp is not positive");
249                anyhow::ensure!(frame_data.fps > 0, "FPS is not positive");
250            }
251            Ok(())
252        });
253
254        capture_thread
255            .join()
256            .map_err(|e| anyhow!("Infrared capture thread join error: {:?}", e))??;
257        processing_thread
258            .join()
259            .map_err(|e| anyhow!("Processing thread join error: {:?}", e))??;
260        Ok(())
261    }
262}