1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
/*!
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(())
}