vzense-rust 0.4.2

High-level library for Vzense cameras
Documentation
/*!
This example covers all the functionality provided by the library. It connects to a device, starts a stream, and displays the received data. The `touch_detector` is a very simple example of image processing, using depth data to detect touch events.
*/

// The window module contains routines to open a window and show video output
// based on the winit and pixels crate.
mod window;
use crate::window::{BottomView, WindowMessage};

use std::{sync::Arc, thread, time::Duration};

// By default using the newest Scepter API.
#[cfg(not(feature = "dcam560"))]
use vzense_rust::scepter as camera_api;

// Uses an older API specifically for the DCAM560 model.
#[cfg(feature = "dcam560")]
use vzense_rust::dcam560 as camera_api;

use camera_api::{
    device::Device,
    frame::{get_color_frame, get_depth_scaled_u8_frame, get_ir_frame, read_next_frame},
};

use vzense_rust::{
    ColorFormat, ColorResolution, DEFAULT_PIXEL_COUNT,
    util::{Counter, color_map::TURBO, new_fixed_vec, touch_detector::TouchDetector},
};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // We show two streams stacked vertically in one window.
    // What is shown in the bottom half can be selected by
    // clicking in the window, switching between Color, Touch, and IR.
    // Setting initial state for bottom view
    let mut bottom_view = BottomView::Color;

    // set up communication channels between camera and winit thread
    // window height is 960px since we show two streams stacked vertically
    let (producer, consumer) = window::new("Top: Depth - Bottom: ", 640, 960);

    // Running camera routines in separate thread
    // since winit needs to run in main thread.
    let camera_handle = thread::Builder::new().name("camera".to_string()).spawn(
        move || -> Result<(), String> {
            // Looking for a device. If a device has been found, it will be opened and a stream started.
            let mut device = Device::initialize(Duration::from_secs(3), true)?;

            // The frame rate should not be set above the maximum value for the camera
            device.set_frame_rate(30)?;
            println!("frame rate: {} fps", device.get_frame_rate()?);

            // Choose between RGB and BGR color format, default is BGR.
            device.set_color_format(ColorFormat::Rgb);

            // Setting the color resolution. If not set the default will be 640x480.
            // If color is mapped to depth, color resolution setting will be ignored.
            // NOTE: Setting a higher resolution in this example will lead to distortions
            // since the buffer sent to the winit window is expecting two 640x480 frames.
            let color_resolution = device.set_color_resolution(ColorResolution::Res640x480);

            // Vectors to store image data.
            // let mut depth_mm = new_fixed_vec(DEFAULT_PIXEL_COUNT, 0u16); // 16 bit per pixel
            let mut depth_scaled = new_fixed_vec(DEFAULT_PIXEL_COUNT, 0u8); // 8 bit per pixel
            let mut color_rgb = new_fixed_vec(3 * color_resolution.to_pixel_count(), 0u8); // 24 bit per pixel
            let mut touch = new_fixed_vec(DEFAULT_PIXEL_COUNT, 0u8); // 8 bit per pixel
            let mut distance = new_fixed_vec(DEFAULT_PIXEL_COUNT, 0.0f32); // 32 bit per pixel
            let mut ir = new_fixed_vec(DEFAULT_PIXEL_COUNT, 0u8); // 8 bit per pixel

            // specific settings for DCAM560 //////////////
            #[cfg(feature = "dcam560")]
            {
                use vzense_rust::{DataMode, util::buffer_text};

                // Choose the data output mode
                // Default is Depth + RGB. Other options are
                // IR + RGB and Depth + IR + RGB.
                // We select Depth + IR + RGB to be able to show all three.
                device.set_data_mode(DataMode::DepthAndIRAndRGB);

                // In case of no IR or Depth frame outputs write info into buffer.
                if let Ok(data_mode) = device.get_data_mode() {
                    match data_mode {
                        DataMode::DepthAndRGB => {
                            // info text for missing IR frame
                            buffer_text::no_ir_info(&mut ir)
                        }
                        DataMode::IRAndRGB => {
                            // info text for missing Depth frame
                            buffer_text::no_depth_info(&mut depth_scaled);
                            buffer_text::no_depth_info(&mut touch);
                        }
                        _ => (),
                    }
                }
                // Choose the depth measuring range (Near, Mid, or Far).
                device.set_depth_measuring_range(vzense_rust::DepthMeasuringRange::Near);

                let range = device.get_depth_measuring_range();
                println!("depth measuring range: {} mm to {} mm", range.0, range.1);
            }
            // end of specific settings for DCAM560 ///////

            // Choose the min/max depth in mm for the color mapping of the depth output. These values also bound the depths used in the `TochDetector` to reduce measuring artifacts.
            // In the specs the depth measuring range for the NYX650 is given as min: 300 mm, max: 4500 mm. The depth measuring range for the DCAM560 depends on the range chosen above.
            device.set_depth_range(400, 1200)?;

            // Mapping color frame to depth frame or vice versa. If at least one of the mappings is set to true, the color_resolution is fixed to 640x480.
            device.map_color_to_depth(false);
            device.map_depth_to_color(false);

            // Initialize the touch detector.
            let mut touch_detector =
                TouchDetector::new(&device, 5.0, 50.0, 30, 5, DEFAULT_PIXEL_COUNT);

            // Counter for fps and frame count output.
            let mut counter = Counter::new(10);

            let mut init = true;

            ///////////////////////////////////////////////////////////////////
            // main loop reading frames and sending them to window
            loop {
                // `read_next_frame()` must be called at the beginning of each loop to retrieve new data.

                // Scepter API has an additional `max_wait_time_ms` paramter.
                #[cfg(not(feature = "dcam560"))]
                read_next_frame(&mut device, 500);

                #[cfg(feature = "dcam560")]
                read_next_frame(&mut device);

                // get buffer from pool
                if let Some(mut buffer) = producer.grab_old_buffer() {
                    if let Some(rgba) = Arc::get_mut(&mut buffer.0) {
                        // top view: depth

                        // raw depth data in mm
                        // get_depth_mm_u16_frame(&mut device, &mut depth_mm);

                        // scaled depth data___________________________________
                        get_depth_scaled_u8_frame(&mut device, &mut depth_scaled);

                        // apply color map
                        for (i, d) in depth_scaled.iter().enumerate() {
                            rgba[4 * i..4 * i + 3].copy_from_slice(&TURBO[*d as usize]);
                        }

                        // pixel offset for bottom view
                        let mut o = DEFAULT_PIXEL_COUNT * 4;

                        match bottom_view {
                            BottomView::Color => {
                                // color_______________________________________
                                get_color_frame(&mut device, &mut color_rgb);

                                // The take() is a precaution to prevent an out of range panic in case of color_resolution set higher than 640x480. A distorted image will result in this case (See also comment for device.set_color_resolution() above).
                                for color in color_rgb.chunks_exact(3).take(DEFAULT_PIXEL_COUNT) {
                                    rgba[o..o + 3].copy_from_slice(color);
                                    o += 4;
                                }
                            }
                            BottomView::Touch => {
                                // touch detector______________________________
                                // Should be called after get_depth... call, otherwise `process` does nothing.
                                touch_detector.process(&device, &mut touch, &mut distance);

                                for &t in touch.iter() {
                                    rgba[o..o + 3].copy_from_slice(&[t, t, t]);
                                    o += 4;
                                }
                            }
                            BottomView::Ir => {
                                get_ir_frame(&mut device, &mut ir);

                                for &i in ir.iter() {
                                    rgba[o..o + 3].copy_from_slice(&[i, i, i]);
                                    o += 4;
                                }
                            }
                        }
                    }
                    // send buffer to winit
                    match producer.send(buffer) {
                        Ok(_) => (),
                        Err(msg) => {
                            eprintln!("{msg}");
                            break;
                        }
                    }
                }

                if init {
                    init = false;
                    device.check_pixel_count(color_rgb.len() / 3);
                    println!("frame info: {}", device.get_frame_info());
                    println!();
                    println!("click inside the window to switch the bottom view");
                    println!("close window to exit program");
                }

                // fps and frame count output
                if let Some(info) = counter.fps_frame_count_info() {
                    producer.set_info(bottom_view.to_string() + &info);
                }

                // Check for window event from winit
                if let Ok(msg) = producer.rx_window_message.try_recv() {
                    match msg {
                        // switch to different stream
                        WindowMessage::MouseClick => {
                            bottom_view = match bottom_view {
                                BottomView::Color => BottomView::Touch,
                                BottomView::Touch => BottomView::Ir,
                                BottomView::Ir => BottomView::Color,
                            };
                        }
                        WindowMessage::Exit => {
                            break;
                        }
                    }
                }
            }

            device.shut_down(true);

            Ok(())
        },
    )?;

    // start winit thread
    window::run(Some(camera_handle), consumer).expect("window thread panicked");

    Ok(())
}