Re-introduce files from bd18ed9c09

This commit is contained in:
Brandon Thomas committed 2022-03-31 19:41:21 -04:00
1 parent 61dac586c6
commit 20607c5367
67 files changed
+10088

No files matched your search

+3
View File
@@ -0,0 +1,3 @@
*~
.idea/
target/
Generated
+2420
View File
File diff suppressed because it is too large. Load diff
+51
View File
@@ -0,0 +1,51 @@
[package]
name = "cloudcam"
version = "0.0.0"
edition = "2018"
authors = [ "Brandon Thomas <bt@brand.io>",
"Brandon Thomas <echelon@gmail.com>" ]
[[bin]]
name = "cloudcam_visualizer"
path = "src/main_opencv.rs"
[[bin]]
name = "cloudcam_points"
path = "src/main_points.rs"
[[bin]]
name = "cloudcam_zeromq"
path = "src/main_zeromq.rs"
[dependencies]
anyhow = "1.0.32"
arcball = "1.0.0" # camera
byteorder = "1.4.2"
bytes = "1.0.1"
cgmath = "0.17.0" # for arcball, but probably good for matrices, vecs, etc.
clap = "3.0.0-beta.1"
clipboard = "0.5" # to support imgui...
env_logger = "0.7.1"
gl = "0.14.0" # this looks like bare bones OpenGL
glfw = "0.37.0" # GL framework - windows, contexts, devices
glfw-sys = "3.3.2" # Does this have linear algebra?
glium = { version = "0.26.0", default-features = true } # high-level OpenGL abstraction (*not* vanilla OpenGL)
gltf = "0.15.2" # glTF loader (more modern OBJ)
image = "0.23.8" # all the image formats
imgui = "0.3.0" # OpenGL windowing toolkit (experimental bindings)
imgui-glium-renderer = "0.3.0"
imgui-opengl-renderer = "0.7.0"
imgui-sdl2 = "0.9.0" # for imgui-opengl-renderer
imgui-winit-support = "0.3.1"
k4a-sys-temp = "0.2.3"
kinect = "0.0.5"
#kinect = { path = "../kinect.rs" }
libc = "0.2.68"
log = "0.4.11"
nalgebra = "0.21.1" # linear algebra and matrices, fantastic docs
nalgebra-glm = "0.7.0" # computer graphics-friendly nalgebra, inspired by C++'s glm
rand = "0.7.3"
sdl2 = "0.33" # for imgui-opengl-renderer
tobj = { version = "1.0", features = ["log"] }# Another obj loader (using older version to match `tobj_viewer` binary/crate)
winit = "=0.21.0" # cross-platform window management, because imgui
zmq = { version = "0.9", features = ['vendored'] }
+179
View File
@@ -0,0 +1,179 @@
Cloudcam (WIP)
==============
This is a Rust server that polls Kinect camera(s) and sends
streaming point clouds to Unreal Engine or some other consumer.
Right now the implementation is incredibly naive, but in the
future we may be able to implement compression, keyframes, and
other advanced features.
This was forked from my original "rust-experiments" repo, so it
has a lot of dead code that needs to be culled. Once this is ready
it will simply be code that interfaces with Kinect and talks to
consumers.
The binary of interest is `main_zeromq.rs` (`cloudcam_zeromq`).
Installation / Requirements
---------------------------
Install the following packages on Linux:
* clang
* cmake
* libsdl2-dev
* libxcb-xfixes0-dev
* GLFW build requirements:
* libpthread (eg. libpthread-stubs0-dev)
* libx11-dev
* libxcursor-dev
* libxi-dev
* libxinerama-dev
* libxrandr-dev
Supporting devices over USB in Linux
------------------------------------
Sometimes the cameras just won't work. We need to increase USB memory and
copy a device config file.
Modify this:
```
sudo sh -c 'echo 2047 > /sys/module/usbcore/parameters/usbfs_memory_mb'
```
Grub bootloader modifications:
```
sudo vi /etc/default/grub
```
Then modify this directive to add the following:
```
GRUB_CMDLINE_LINUX_DEFAULT="[KEEP EXISTING PARTS THE SAME] usbcore.usbfs_memory_mb=2047"
```
Then
```
sudo update-grub
```
(Reboot)
If `sudo k4aviewer` works but non-sudo doesn't,
```
git clone git@github.com:microsoft/Azure-Kinect-Sensor-SDK.git
cd Azure-Kinect-Sensor-SDK/
sudo cp scripts/99-k4a.rules /etc/udev/rules.d/
```
Then disconnect and reconnect the USB device. It should work now.
Ubuntu 18.04 Installation
-------------------------
Configure the Microsoft PPA and install the packages:
```
# Configure PPA
curl -sSL https://packages.microsoft.com/keys/microsoft.asc | sudo apt-key add -
sudo apt-add-repository https://packages.microsoft.com/ubuntu/18.04/prod
sudo apt-get update
#Install packages:
sudo apt install k4a-tools
sudo apt install libk4a1.1-dev
```
Ubuntu 20.04 LTS Support
------------------------
Azure Kinect isn't officially supported on 20.04
mpdroid's comment on October 17, 2020 seems to indicate that there is a workaround:
https://feedback.azure.com/forums/920053-azure-kinect-dk/suggestions/40368301-support-for-ubuntu-20-04-lts?page=1&per\_page=20
```
These are the steps I followed to install k4a-tools, libk4a and libk4abt on Ubuntu 20.04. The general steps are as outlined in
https://docs.microsoft.com/en-us/azure/Kinect-dk/sensor-sdk-download, with a couple of hacks to make things work on 20.04.
- use of 18.04 repo, even though OS is 20.04
- installed lower versions of tools and libraries (as latest versions of sensor and body tracker don't seem to be compatible on 20.04)
$ curl -sSL https://packages.microsoft.com/keys/microsoft.asc | sudo apt-key add -
$ sudo apt-add-repository https://packages.microsoft.com/ubuntu/18.04/prod
$ curl -sSL https://packages.microsoft.com/config/ubuntu/18.04/prod.list | sudo tee /etc/apt/sources.list.d/microsoft-prod.list
$ curl -sSL https://packages.microsoft.com/keys/microsoft.asc | sudo apt-key add -
$ sudo apt-get update
$ sudo apt install libk4a1.3-dev
$ sudo apt install libk4abt1.0-dev
$ sudo apt install k4a-tools=1.3.0
- Verify sensor library by launching camera viewer
$ k4aviewer
- Clone and build [Azure Kinect Samples](https://github.com/microsoft/Azure-Kinect-Samples).
- Run `simple_3d_viewer` to verify body tracker works.
```
There will be complaints:
```
# E: The repository 'https://packages.microsoft.com/ubuntu/18.04/prod focal Release' does not have a Release file.
# N: Updating from such a repository can't be done securely, and is therefore disabled by default.
```
Just ignore them.
Webcam
------
Update:
This seems to work with some combination of reloading the driver (`./webcam/reload_driver.sh`), running VLC
(maybe it sets the video buffer mode?), killing firefox, and starting random browsers and going to meet.
Eventually it seems to work.
Docs and software:
- software: https://github.com/webcamoid/akvcam
- wiki: https://github.com/webcamoid/akvcam/wiki/Usage-and-examples
The configuration I use is as follows (this may have changed with the new ini file):
- /dev/video0 is the input
- /dev/video1 is the output
Check to see if the driver is okay:
```
v4l2-compliance -d /dev/video0 -f
```
Check properties:
```
v4l2-ctl -d /dev/video0
```
Playback:
```
ffplay /dev/video1
```
Test writing random noise:
```
cat /dev/urandom > /dev/video0
```
This may have the answers for my bugs:
- https://github.com/webcamoid/akvcam/issues/6
+3
View File
@@ -0,0 +1,3 @@
pub mod obj_loader;
pub mod positionable_object;
pub mod renderable_object;
+114
View File
@@ -0,0 +1,114 @@
use anyhow::Result as AnyhowResult;
use std::path::Path;
use std::time::{Duration, Instant};
use glium::{self, Display};
use glium::vertex::VertexBufferAny;
use std::f32;
#[derive(Copy, Clone)]
pub struct ExtractedVertex {
pub position: [f32; 3],
pub normal: [f32; 3],
pub color_diffuse: [f32; 3],
pub color_specular: [f32; 4],
pub tex_coords: [f32; 2], // [u, v]
}
/// Returns a vertex buffer that should be rendered as `TrianglesList`.
/// Adapted from https://github.com/Twinklebear/tobj_viewer/blob/master/src/support/mod.rs
pub fn load_wavefront(path: &Path) -> AnyhowResult<Vec<ExtractedVertex>>
//pub fn load_wavefront(display: &Display, path: &Path)
//-> (VertexBufferAny, f32)
{
// TODO: This appears to be one of two hooks for the data to be uploaded to OpenGL
implement_vertex!(ExtractedVertex, position, normal, color_diffuse, color_specular, tex_coords);
let mut min_pos = [f32::INFINITY; 3];
let mut max_pos = [f32::NEG_INFINITY; 3];
let mut vertex_data = Vec::new();
println!("loading {:?}...", path);
let (models, mats) = tobj::load_obj(path)?;
println!("loaded {:?}...", path);
// Just upload the first object in the group
for model in &models {
let mesh = &model.mesh;
//println!("Uploading model: {}", model.name);
for idx in &mesh.indices {
let i = *idx as usize;
let pos = [
mesh.positions[3 * i],
mesh.positions[3 * i + 1],
mesh.positions[3 * i + 2],
];
let normal = if !mesh.normals.is_empty() {
[
mesh.normals[3 * i],
mesh.normals[3 * i + 1],
mesh.normals[3 * i + 2],
]
} else {
[0.0, 0.0, 0.0]
};
let tex_coords = if !mesh.texcoords.is_empty() {
// texcoord = [u, v];
[
mesh.texcoords[i * 2],
mesh.texcoords[i * 2 + 1]
]
} else {
[0.0, 0.0]
};
let (color_diffuse, color_specular) = match mesh.material_id {
Some(i) => (
mats[i].diffuse,
[
mats[i].specular[0],
mats[i].specular[1],
mats[i].specular[2],
mats[i].shininess,
],
),
None => ([0.8, 0.8, 0.8], [0.15, 0.15, 0.15, 15.0]),
};
vertex_data.push(ExtractedVertex {
position: pos,
normal: normal,
color_diffuse: color_diffuse,
color_specular: color_specular,
tex_coords: tex_coords,
});
// Update our min/max pos so we can figure out the bounding box of the object
// to view it
for i in 0..3 {
min_pos[i] = f32::min(min_pos[i], pos[i]);
max_pos[i] = f32::max(max_pos[i], pos[i]);
}
}
}
Ok(vertex_data)
// TODO: This appears to be the second hook:
// Compute scale factor to fit the model with a [-1, 1] bounding box
/*let diagonal_len = 6.0;
let current_len = f32::powf(max_pos[0] - min_pos[0], 2.0)
+ f32::powf(max_pos[1] - min_pos[1], 2.0)
+ f32::powf(max_pos[2] - min_pos[2], 2.0);
let scale = f32::sqrt(diagonal_len / current_len);
println!("Model scaled by {} to fit", scale);
(
glium::vertex::VertexBuffer::new(display, &vertex_data)
.unwrap()
.into_vertex_buffer_any(),
scale,
)*/
}
+142
View File
@@ -0,0 +1,142 @@
use crate::assets::renderable_object::RenderableObject;
use gl::types::*;
use nalgebra::{Vector4, Rotation3, Translation3, Matrix4, Vector3, RealField};
use std::ffi::CString;
use std::os::raw::c_char;
use crate::opengl::wrapper::uniform::Uniform;
#[derive(Copy,Clone,Debug,PartialEq,Eq)]
pub enum ObjectType {
Skybox,
Level,
Character,
Unknown,
}
pub struct PositionableObject {
pub renderable_object: RenderableObject,
// Transformation data for the object
pub scale: Vector3<f32>,
pub rotation: Vector4<f32>,
pub translation: Vector3<f32>,
//pub rotation_typed: Rotation3<f32>,
//pub translation_typed: Translation3<f32>,
//pub is_transformed: bool,
pub object_type: ObjectType,
}
impl PositionableObject {
pub fn new(renderable_object: RenderableObject) -> Self {
Self {
renderable_object,
scale: Vector3::new(1.0, 1.0, 1.0),
rotation: Vector4::default(),
translation: Vector3::default(),
//rotation_typed: Rotation3::identity(),
//translation_typed: Translation3::identity(),
//is_transformed: true,
object_type: ObjectType::Unknown,
}
}
pub fn rotate(&mut self, x: f32, y: f32, z: f32) {
self.rotation.x = x;
self.rotation.y = y;
self.rotation.z = z;
}
pub fn rotate_x(&mut self, x: f32) {
self.rotation.x = x;
}
pub fn rotate_y(&mut self, y: f32) {
self.rotation.y = y;
}
pub fn rotate_z(&mut self, z: f32) {
self.rotation.z = z;
}
/// Add pi to x rotation
pub fn flip_x(&mut self) {
self.rotation.x += f32::pi();
}
/// Add pi to y rotation
pub fn flip_y(&mut self) {
self.rotation.y += f32::pi();
}
/// Add pi to z rotation
pub fn flip_z(&mut self) {
self.rotation.z += f32::pi();
}
pub fn increment_rotation_x(&mut self, x: f32) {
self.rotation.x += x;
}
pub fn increment_rotation_y(&mut self, y: f32) {
self.rotation.y += y;
}
pub fn increment_rotation_z(&mut self, z: f32) {
self.rotation.z += z;
}
pub fn scale_nonuniform(&mut self, x: f32, y: f32, z: f32) {
self.scale.x = x;
self.scale.y = y;
self.scale.z = z;
}
pub fn scale(&mut self, scale: f32) {
self.scale.x = scale;
self.scale.y = scale;
self.scale.z = scale;
}
pub fn translate(&mut self, x: f32, y: f32, z: f32) {
self.translation.x = x;
self.translation.y = y;
self.translation.z = z;
}
pub fn translate_x(&mut self, x: f32) {
self.translation.x = x;
}
pub fn translate_y(&mut self, y: f32) {
self.translation.y = y;
}
pub fn translate_z(&mut self, z: f32) {
self.translation.z = z;
}
pub fn draw(&self, model_transform_id: Uniform) {
let mut transformation = Matrix4::identity();
let rotation = Matrix4::from_euler_angles(self.rotation.x, self.rotation.y, self.rotation.z);
transformation.append_nonuniform_scaling_mut(&self.scale);
transformation = transformation * rotation;
transformation.append_translation_mut(&self.translation);
//println!("matrix: {:?}", transformation);
//println!("translation: {:?}", &self.translation);
let mat_ptr = transformation.as_ptr();
self.renderable_object.vao.bind();
unsafe {
gl::UniformMatrix4fv(model_transform_id.id(), 1, gl::FALSE, mat_ptr);
}
self.renderable_object.draw(true);
}
}
+247
View File
@@ -0,0 +1,247 @@
use anyhow::Result as AnyhowResult;
use anyhow::bail;
use crate::assets::obj_loader::{ExtractedVertex, load_wavefront};
use crate::opengl::wrapper::buffer::Buffer;
use crate::opengl::wrapper::texture::Texture;
use crate::opengl::wrapper::vertex_array::VertexArray;
use gl::types::*;
use std::ffi::{c_void, CString};
use std::mem::size_of;
use std::os::raw::c_char;
use std::path::Path;
use std::ptr::null;
use image::{DynamicImage, Pixel, Rgba, RgbaImage, ImageBuffer, RgbImage};
use crate::opengl::wrapper::uniform::Uniform;
use image::imageops::{flip_horizontal, flip_vertical};
use crate::opengl::wrapper::attribute::Attribute;
/// A collection of all the graphics-related data to render: vertices, normals, etc.
pub struct RenderableObject {
pub vao: VertexArray,
pub vertex_buffer: Buffer,
pub normal_buffer: Buffer,
pub color_buffer: Buffer,
pub texture_coords_buffer: Buffer,
pub texture: Texture,
pub num_vertices: usize,
pub buffered: bool,
}
impl RenderableObject {
pub fn new() -> Self {
unsafe {
let vao = VertexArray::new_initialized();
vao.bind();
let vertex_buffer = Buffer::new_initialized();
let normal_buffer = Buffer::new_initialized();
let color_buffer = Buffer::new_initialized();
let texture_coords_buffer = Buffer::new_initialized();
let texture = Texture::new_initialized();
Self {
vao,
vertex_buffer,
normal_buffer,
color_buffer,
texture_coords_buffer,
texture,
num_vertices: 0,
buffered: false,
}
}
}
pub fn from_wavefront(path: &Path, shader_id: GLuint) -> AnyhowResult<Self> {
let vertices = load_wavefront(&path)?;
let mut renderable_object = RenderableObject::new();
renderable_object.load_vertices(&vertices, shader_id);
renderable_object.load_texture_coordinates(&vertices, shader_id);
Ok(renderable_object)
}
pub fn load_vertices(&mut self, vertex_data: &Vec<ExtractedVertex>, shader_id: GLuint) {
self.vao.bind();
self.vertex_buffer.bind_as_array_buffer();
let size = vertex_data.len() * 3 * size_of::<f32>();
let mut vertices : Vec<f32> = Vec::with_capacity(vertex_data.len() * 3);
for v in vertex_data.into_iter() {
vertices.extend(&v.position);
}
self.num_vertices = vertex_data.len();
println!("Object vertices loaded: {}", vertices.len());
unsafe {
let vertices_ptr = vertices.as_ptr() as *const c_void;
gl::BufferData(
gl::ARRAY_BUFFER,
size as isize,
vertices_ptr,
gl::STATIC_DRAW,
);
let name : CString = CString::new("position").expect("string is correct");
let name_ptr : *const c_char = name.as_ptr() as *const c_char;
let loc = gl::GetAttribLocation(shader_id, name_ptr);
println!("Attrib location: {}", loc);
gl::EnableVertexAttribArray(loc as u32);
gl::VertexAttribPointer(loc as u32, 3, gl::FLOAT, gl::FALSE, 0, null());
}
}
pub fn set_vertex_type(&mut self, vertex_type: &Uniform) {
/*self.vao.bind();
let loc = vertex_type.id();
println!("Uniform location: {}", loc);
unsafe {
//gl::EnableVertexAttribArray(loc as u32);
//gl::VertexAttribIPointer(loc as u32, 1, gl::UNSIGNED_BYTE, 0, null());
//gl::VertexAttribI1ui(loc as u32, 0);
//gl::Uniform1i(loc, 1);
}*/
//vertex_type.set_uint(0);
}
pub fn load_texture_coordinates(&mut self, vertex_data: &Vec<ExtractedVertex>, shader_id: GLuint) {
self.vao.bind();
self.texture_coords_buffer.bind_as_array_buffer();
let size = vertex_data.len() * 2 * size_of::<f32>();
let mut tex_coords : Vec<f32> = Vec::with_capacity(vertex_data.len() * 2);
for v in vertex_data.into_iter() {
tex_coords.extend(&v.tex_coords);
}
unsafe {
let tex_coords_ptr = tex_coords.as_ptr() as *const c_void;
gl::BufferData(
gl::ARRAY_BUFFER,
size as isize,
tex_coords_ptr,
gl::STATIC_DRAW,
);
let name : CString = CString::new("vTextureCoord").expect("string is correct");
let name_ptr : *const c_char = name.as_ptr() as *const c_char;
let loc = gl::GetAttribLocation(shader_id, name_ptr);
println!("Attrib location: {}", loc);
gl::EnableVertexAttribArray(loc as u32);
gl::VertexAttribPointer(loc as u32, 2, gl::FLOAT, gl::FALSE, 0, null());
// TODO: This is in my old OpenGL program, but not the rust package?
//gl::TexCoordPointer(2, gl::FLOAT, 0, tex_coords_ptr);
}
}
// TODO: Option for 'rgb'/'rgba'
// TODO: Option to flip texture on any axis
pub fn load_texture(&mut self, filename: &str, texture_uniform: &Uniform) -> AnyhowResult<()> {
let img = image::open(&filename)?;
let mut rgba_image= img.to_rgba();
//let rgb_image : RgbImage = flip_horizontal(&rgb_image);
let rgba_image : RgbaImage = flip_vertical(&rgba_image);
let width = rgba_image.width();
let height = rgba_image.height();
let img_data = rgba_image.into_raw();
let pixel_data = img_data.as_ptr() as *const c_void;
self.texture.bind_as_texture_2d();
println!("Loading texture into OpenGL {}x{}...", width, height);
unsafe {
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::LINEAR as i32);
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::LINEAR as i32);
//gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, gl::REPEAT as i32);
//gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, gl::REPEAT as i32);
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, gl::CLAMP_TO_BORDER as i32);
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, gl::CLAMP_TO_BORDER as i32);
let border_color : Vec<f32> = vec![0.0, 1.0, 0.0, 1.0];
let border_color_ptr = border_color.as_ptr() as *const f32;
gl::TexParameterfv(gl::TEXTURE_2D, gl::TEXTURE_BORDER_COLOR, border_color_ptr);
gl::TexImage2D(
gl::TEXTURE_2D,
0, // image level (mipmap); 0 is base
gl::RGBA as i32, // internal format
width as i32,
height as i32,
0, // border; "must be 0"
gl::RGBA, // format of pixels
gl::UNSIGNED_BYTE, // pixel data type
pixel_data,
);
/*
// Checkerboard texture.
let pixels : Vec<f32> = vec![
0.0, 0.0, 0.0, 1.0,
1.0, 1.0, 1.0, 1.0,
0.0, 0.0, 0.0, 1.0,
1.0, 1.0, 1.0, 1.0,
];
let pixels_ptr = pixels.as_ptr() as *const c_void;
gl::TexImage2D(
gl::TEXTURE_2D,
0, // image level (mipmap); 0 is base
gl::RGBA as i32, // internal format
2,
2,
0, // border; "must be 0"
gl::RGBA, // format of pixels
gl::FLOAT, // pixel data type
pixels_ptr,
);
*/
gl::GenerateMipmap(gl::TEXTURE_2D);
println!("texture uniform: {}", texture_uniform.id());
// TODO: This is in my OpenGL program, but is broken here.
//gl::Uniform1i(texture_uniform.id(), 0);
gl::ActiveTexture(gl::TEXTURE0);
}
Ok(())
}
pub fn draw(&self, vao_already_bound: bool) {
if !vao_already_bound {
self.vao.bind();
}
unsafe {
gl::BindTexture(gl::TEXTURE_2D, self.texture.id());
gl::DrawArrays(gl::TRIANGLES, 0, self.num_vertices as i32);
}
}
}
@@ -0,0 +1,17 @@
use crate::point_cloud::debug::capture_proxy::CaptureProxy;
use kinect::Calibration;
pub trait CaptureProvider {
/** Return the number of cameras. */
fn get_num_cameras(&self) -> usize;
/**
* Take the latest captures, if available, through interior mutability.
* This leaves the mutex holding an empty vec.
*/
fn get_captures(&self) -> Option<Vec<CaptureProxy>>;
/** Get device calibrations for all cameras. */
fn get_calibrations(&self) -> &Vec<Calibration>;
}
@@ -0,0 +1,183 @@
use anyhow::Result as AnyhowResult;
use crate::azure_kinect::capture::device_capturer::CaptureProvider;
use crate::point_cloud::debug::capture_proxy::CaptureProxy;
use crate::point_cloud::debug::image_proxy::ImageProxy;
use k4a_sys_temp as k4a_sys;
use kinect::{ImageFormat, Calibration};
pub struct FakeDeviceCaptureProvider {
num_cameras: usize,
captures: Vec<CaptureProxy>,
all_device_calibrations: Vec<Calibration>,
}
impl FakeDeviceCaptureProvider {
pub fn new() -> AnyhowResult<Self> {
// TODO: Color dimension stride is wrong (and maybe others)
let color = ImageProxy::from_file("output/color_frame_0", 3840, 2160, 0, ImageFormat::ColorBgra32)?;
let depth = ImageProxy::from_file("output/depth_frame_0", 640, 576, 1280, ImageFormat::Depth16)?;
let frame_1 = CaptureProxy::from_image_proxy_pair(color, depth);
// TODO: Color dimension stride is wrong (and maybe others)
let color = ImageProxy::from_file("output/color_frame_1", 3840, 2160, 0, ImageFormat::ColorBgra32)?;
let depth = ImageProxy::from_file("output/depth_frame_1", 640, 576, 1280, ImageFormat::Depth16)?;
let frame_2 = CaptureProxy::from_image_proxy_pair(color, depth);
let mut captures = Vec::new();
captures.push(frame_1);
captures.push(frame_2);
let mut calibration = Calibration::default();
// NB: Experimentally determined "constants" from real devices
// These are necessary to compute the depth camera transform into color camera space.
calibration.0.color_resolution = 5;
calibration.0.depth_mode = 2;
calibration.0.depth_camera_calibration.resolution_width = 640;
calibration.0.depth_camera_calibration.resolution_height = 576;
calibration.0.depth_camera_calibration.metric_radius = 1.7399998;
calibration.0.depth_camera_calibration.extrinsics.rotation = [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0];
calibration.0.depth_camera_calibration.extrinsics.translation = [0.0, 0.0, 0.0];
calibration.0.depth_camera_calibration.intrinsics.type_ = 4;
calibration.0.depth_camera_calibration.intrinsics.parameter_count = 14;
calibration.0.depth_camera_calibration.intrinsics.parameters.param.cx = 325.4652;
calibration.0.depth_camera_calibration.intrinsics.parameters.param.cy = 339.75952;
calibration.0.depth_camera_calibration.intrinsics.parameters.param.fx = 503.72073;
calibration.0.depth_camera_calibration.intrinsics.parameters.param.fy = 503.82776;
calibration.0.depth_camera_calibration.intrinsics.parameters.param.k1 = 5.7966313;
calibration.0.depth_camera_calibration.intrinsics.parameters.param.k2 = 4.1091304;
calibration.0.depth_camera_calibration.intrinsics.parameters.param.k3 = 0.22066948;
calibration.0.depth_camera_calibration.intrinsics.parameters.param.k4 = 6.1226273;
calibration.0.depth_camera_calibration.intrinsics.parameters.param.k5 = 6.053665;
calibration.0.depth_camera_calibration.intrinsics.parameters.param.k6 = 1.1403816;
calibration.0.depth_camera_calibration.intrinsics.parameters.param.codx = 0.0;
calibration.0.depth_camera_calibration.intrinsics.parameters.param.cody = 0.0;
calibration.0.depth_camera_calibration.intrinsics.parameters.param.p2 = -0.00003910405;
calibration.0.depth_camera_calibration.intrinsics.parameters.param.p1 = -0.00009726079;
calibration.0.depth_camera_calibration.intrinsics.parameters.param.metric_radius = 0.0;
calibration.0.color_camera_calibration.resolution_width = 3840;
calibration.0.color_camera_calibration.resolution_height = 2160;
calibration.0.color_camera_calibration.metric_radius = 1.7;
calibration.0.color_camera_calibration.extrinsics.rotation = [0.99998903, -0.0045318827, 0.001187322, 0.0044161947, 0.9964592, 0.08396203, -0.001563624, -0.08395587, 0.99646825];
calibration.0.color_camera_calibration.extrinsics.translation = [-32.02683, -2.3786376, 3.9904723];
calibration.0.color_camera_calibration.intrinsics.type_ = 4;
calibration.0.color_camera_calibration.intrinsics.parameter_count = 14;
calibration.0.color_camera_calibration.intrinsics.parameters.param.cx = 1919.9363;
calibration.0.color_camera_calibration.intrinsics.parameters.param.cy = 1105.1877;
calibration.0.color_camera_calibration.intrinsics.parameters.param.fx = 1832.5886;
calibration.0.color_camera_calibration.intrinsics.parameters.param.fy = 1832.6873;
calibration.0.color_camera_calibration.intrinsics.parameters.param.k1 = 0.4812507;
calibration.0.color_camera_calibration.intrinsics.parameters.param.k2 = -2.5299993;
calibration.0.color_camera_calibration.intrinsics.parameters.param.k3 = 1.4068391;
calibration.0.color_camera_calibration.intrinsics.parameters.param.k4 = 0.36012942;
calibration.0.color_camera_calibration.intrinsics.parameters.param.k5 = -2.355542;
calibration.0.color_camera_calibration.intrinsics.parameters.param.k6 = 1.3372931;
calibration.0.color_camera_calibration.intrinsics.parameters.param.codx = 0.0;
calibration.0.color_camera_calibration.intrinsics.parameters.param.cody = 0.0;
calibration.0.color_camera_calibration.intrinsics.parameters.param.p2 = -0.00037526494;
calibration.0.color_camera_calibration.intrinsics.parameters.param.p1 = 0.00054938474;
calibration.0.color_camera_calibration.intrinsics.parameters.param.metric_radius = 0.0;
calibration.0.extrinsics = [
[
k4a_sys::_k4a_calibration_extrinsics_t {
rotation: [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0],
translation: [0.0, 0.0, 0.0]
},
k4a_sys::_k4a_calibration_extrinsics_t {
rotation: [0.99998903, -0.0045318827, 0.001187322, 0.0044161947, 0.9964592, 0.08396203, -0.001563624, -0.08395587, 0.99646825],
translation: [-32.02683, -2.3786376, 3.9904723]
},
k4a_sys::_k4a_calibration_extrinsics_t {
rotation: [0.00908543, 0.1157082, -0.99324167, -0.9999575, -0.0005106917, -0.009206355, -0.001572491, 0.9932831, 0.11569864],
translation: [0.0, 0.0, 0.0]
},
k4a_sys::_k4a_calibration_extrinsics_t {
rotation: [0.004534903, 0.112131886, -0.993683, -0.9999893, -0.00040909834, -0.004609848, -0.00092342496, 0.99369323, 0.11212883],
translation: [-51.077, 3.2191863, 1.8031791]
}
],
[
k4a_sys::_k4a_calibration_extrinsics_t {
rotation: [0.99998903, 0.0044161947, -0.001563624, -0.0045318827, 0.9964592, -0.08395587, 0.001187322, 0.08396203, 0.99646825],
translation: [32.043224, 2.560097, -3.7386374]
},
k4a_sys::_k4a_calibration_extrinsics_t {
rotation: [1.0, 0.00000000026193447, 0.0, 0.00000000026193447, 1.0, -0.000000007450581, 0.0, -0.000000007450581, 1.0],
translation: [0.0000038146973, 0.0, 0.0]
},
k4a_sys::_k4a_calibration_extrinsics_t {
rotation: [0.007381657, 0.031944036, -0.99946237, -0.9999551, -0.005697875, -0.007567407, -0.005936545, 0.9994734, 0.03190054],
translation: [4.300721, -32.00875, 2.0599582]
},
k4a_sys::_k4a_calibration_extrinsics_t {
rotation: [0.0028468627, 0.028323233, -0.99959475, -0.99998194, -0.005210849, -0.0029956135, -0.005293583, 0.9995853, 0.028307885],
translation: [-46.929596, -28.807508, 3.8983316]
}
],
[
k4a_sys::_k4a_calibration_extrinsics_t {
rotation: [0.00908543, -0.9999575, -0.001572491, 0.1157082, -0.0005106917, 0.9932831, -0.99324167, -0.009206355, 0.11569864],
translation: [0.0, 0.0, 0.0]
},
k4a_sys::_k4a_calibration_extrinsics_t {
rotation: [0.007381657, -0.9999551, -0.005936545, 0.031944036, -0.005697875, 0.9994734, -0.99946237, -0.007567407, 0.03190054],
translation: [-32.02683, -2.3786376, 3.9904723]
},
k4a_sys::_k4a_calibration_extrinsics_t {
rotation: [0.99999994, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.99999994],
translation: [0.0, 0.0, 0.0]
},
k4a_sys::_k4a_calibration_extrinsics_t {
rotation: [0.99998313, 0.0045562233, -0.003596194, -0.0045539755, 0.99998945, 0.00063277053, 0.0035990402, -0.0006163829, 0.99999326],
translation: [-51.077, 3.2191863, 1.8031791]
}
],
[
k4a_sys::_k4a_calibration_extrinsics_t {
rotation: [0.004534903, -0.9999893, -0.00092342496, 0.112131886, -0.00040909834, 0.99369323, -0.993683, -0.004609848, 0.11212883],
translation: [3.452446, 3.9368703, -50.941692]
},
k4a_sys::_k4a_calibration_extrinsics_t {
rotation: [0.0028468627, -0.99998194, -0.005293583, 0.028323233, -0.005210849, 0.9995853, -0.99959475, -0.0029956135, 0.028307885],
translation: [-28.652746, -2.717628, -47.10723]
},
k4a_sys::_k4a_calibration_extrinsics_t {
rotation: [0.99998313, -0.0045539755, 0.0035990402, 0.0045562233, 0.99998945, -0.0006163829, -0.003596194, 0.00063277053, 0.99999326],
translation: [51.08431, -2.9853227, -1.9888868]
},
k4a_sys::_k4a_calibration_extrinsics_t {
rotation: [0.99999994, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.99999994],
translation: [-0.0000038146973, 0.00000023841858, 0.00000011920929] }
]
];
let mut all_device_calibrations = Vec::new();
for _ in 0 .. 2 {
all_device_calibrations.push(calibration.clone());
}
Ok(Self {
num_cameras: 2,
captures,
all_device_calibrations,
})
}
}
impl CaptureProvider for FakeDeviceCaptureProvider {
fn get_num_cameras(&self) -> usize {
self.num_cameras
}
fn get_captures(&self) -> Option<Vec<CaptureProxy>> {
let capture_clones = self.captures.iter()
.map(|capture| capture.clone())
.collect();
Some(capture_clones)
}
fn get_calibrations(&self) -> &Vec<Calibration> {
&self.all_device_calibrations
}
}
+3
View File
@@ -0,0 +1,3 @@
pub mod device_capturer;
pub mod fake_device_capturer;
pub mod multi_device_capturer;
@@ -0,0 +1,263 @@
use crate::azure_kinect::capture::device_capturer::CaptureProvider;
use crate::point_cloud::debug::capture_proxy::CaptureProxy;
use k4a_sys_temp as k4a_sys;
use kinect::{Device, KinectError, Capture, CaptureError, GetCaptureError, Calibration};
use std::sync::Arc;
use std::sync::Mutex;
// Allowing at least 160 microseconds between depth cameras should ensure they do not interfere with one another.
const MIN_TIME_BETWEEN_DEPTH_CAMERA_PICTURES_USEC : libc::int32_t = 160;
pub struct MultiDeviceCapturer {
num_cameras: usize,
pub primary_device: Device, // TODO: Temporary public viz
secondary_devices: Vec<Device>,
/// Calibrations for primary + all secondaries in order.
all_device_calibrations: Vec<Calibration>,
capture_provider: Arc<MultiDeviceCaptureProvider>,
}
pub struct MultiDeviceCaptureProvider {
num_cameras: usize,
/// Calibrations for primary + all secondaries in order.
all_device_calibrations: Vec<Calibration>,
captures: Arc<Mutex<Vec<Capture>>>, // TODO: Shouldn't need to wrap in mutex if we wrap struct instead.
}
impl MultiDeviceCapturer {
// TODO: Don't panic everywere.
/// Open the primary and secondary devices.
pub fn new(num_devices: u32, limit_secondary_devices: Option<usize>) -> Result<Self, KinectError> {
let mut primary_device = None;
let mut secondary_devices = Vec::new();
let mut calibrations = Vec::new();
let depth_mode : k4a_sys::k4a_depth_mode_t = 2; //k4a_sys::K4A_DEPTH_MODE_NFOV_UNBINNED;
let color_format: k4a_sys::k4a_color_resolution_t = k4a_sys::k4a_color_resolution_t_K4A_COLOR_RESOLUTION_2160P;
for i in 0..num_devices {
let mut device = Device::open(i)?;
let jack_status = device.get_synchronization_jack_status()?;
if jack_status.sync_out_jack_connected && !jack_status.sync_in_jack_connected {
if primary_device.is_some() {
// NB: This assumes a daisy chain topology.
panic!("We already one primary device. We can't have two.");
}
let calibration = device.get_calibration(depth_mode, color_format).unwrap();
calibrations.push(calibration);
primary_device = Some(device);
continue;
}
if !jack_status.sync_in_jack_connected {
panic!("Secondary device does not have 'in' jack connected!");
}
let calibration = device.get_calibration(depth_mode, color_format).unwrap();
secondary_devices.push(device);
calibrations.push(calibration);
}
if primary_device.is_none() {
panic!("Could not find primary device!");
}
if let Some(limit) = limit_secondary_devices {
secondary_devices.truncate(limit);
}
let num_cameras = 1 + secondary_devices.len();
let primary = primary_device.expect("There must be a primary device");
let calibrations2 = calibrations.clone();
Ok(Self {
num_cameras,
primary_device: primary,
secondary_devices,
all_device_calibrations: calibrations,
capture_provider: Arc::new(MultiDeviceCaptureProvider::new(num_cameras, calibrations2)),
})
}
/// Return the number of cameras
pub fn get_num_cameras(&self) -> usize {
self.num_cameras
}
// TODO:
// Per Microsoft's "green screen" example, it's possible to differentially configure things such
// as exposure, but we won't consider that at the moment. When we do, there is a lot of good
// documentation in the 'green_screen' source code that speaks to important timing delay
// considerations.
/// Start cameras all with the same configuration
pub fn start_cameras(&self) -> Result<(), KinectError> {
let secondary_config = get_secondary_device_config();
// NB: Secondary devices *MUST* be started before the master!
for device in self.secondary_devices.iter() {
device.start_cameras(secondary_config)?;
}
// Once the secondaries start, we can start the primary.
let primary_config = get_primary_device_config();
self.primary_device.start_cameras(primary_config)?;
Ok(())
}
pub fn get_synchronized_captures(&self) -> Result<Vec<Capture>, GetCaptureError> {
let mut captures = Vec::with_capacity(1 + self.secondary_devices.len());
// TODO: -1 is K4A_WAIT_INDEFINITE
let capture = self.primary_device.get_capture(-1)?;
captures.push(capture);
for device in self.secondary_devices.iter() {
let capture = device.get_capture(-1)?;
captures.push(capture);
}
// TODO TODO TODO - Lots of sync logic goes here
// TODO TODO TODO - Lots of sync logic goes here
// TODO TODO TODO - Lots of sync logic goes here
// TODO TODO TODO - Lots of sync logic goes here
Ok(captures)
}
/// Get a capture provider for another thread
pub fn get_sync_capture_provider(&self) -> Arc<MultiDeviceCaptureProvider> {
self.capture_provider.clone()
}
}
impl MultiDeviceCaptureProvider {
pub fn new(num_cameras: usize, all_device_calibrations: Vec<Calibration>) -> Self {
Self {
num_cameras,
all_device_calibrations,
captures: Arc::new(Mutex::new(Vec::new())),
}
}
/**
* Consume captures and replace whatever we currently hold.
*/
pub fn set_captures(&self, captures: Vec<Capture>) {
match self.captures.lock() {
Ok(mut lock) => {
*lock = captures;
},
Err(_) => {},
}
}
}
impl CaptureProvider for MultiDeviceCaptureProvider {
/** Return the number of cameras. */
fn get_num_cameras(&self) -> usize {
self.num_cameras
}
/**
* Take the latest captures, if available, through interior mutability.
* This leaves the mutex holding an empty vec.
*/
fn get_captures(&self) -> Option<Vec<CaptureProxy>> {
self.captures.lock()
.ok()
.map(|mut v| (*v).split_off(0))
.map(|mut captures| captures.into_iter()
.map(|capture| CaptureProxy::consume_k4a_capture(capture))
.collect())
}
fn get_calibrations(&self) -> &Vec<Calibration> {
&self.all_device_calibrations
}
}
pub fn start_capture_thread(capturer: MultiDeviceCapturer) {
let mut capture_provider = capturer.get_sync_capture_provider();
loop {
let captures = capturer.get_synchronized_captures()
.expect("Should get captures");
capture_provider.set_captures(captures);
}
}
fn get_primary_device_config() -> k4a_sys::k4a_device_configuration_t {
let mut config = get_default_device_config();
config.wired_sync_mode = k4a_sys::k4a_wired_sync_mode_t_K4A_WIRED_SYNC_MODE_MASTER;
config.synchronized_images_only = true;
// Two depth images should be seperated by MIN_TIME_BETWEEN_DEPTH_CAMERA_PICTURES_USEC to ensure the depth imaging
// sensor doesn't interfere with the other. To accomplish this the master depth image captures
// (MIN_TIME_BETWEEN_DEPTH_CAMERA_PICTURES_USEC / 2) before the color image, and the subordinate camera captures its
// depth image (MIN_TIME_BETWEEN_DEPTH_CAMERA_PICTURES_USEC / 2) after the color image. This gives us two depth
// images centered around the color image as closely as possible.
config.depth_delay_off_color_usec = -(MIN_TIME_BETWEEN_DEPTH_CAMERA_PICTURES_USEC / 2);
// TODO: These are the old values I used that are NOT in 'green_screen'.
// They may need to be adjusted
config.color_resolution = k4a_sys::k4a_color_resolution_t_K4A_COLOR_RESOLUTION_2160P;
config.depth_mode = k4a_sys::k4a_depth_mode_t_K4A_DEPTH_MODE_NFOV_UNBINNED;
//config.camera_fps = k4a_sys::k4a_fps_t_K4A_FRAMES_PER_SECOND_30;
config
}
fn get_secondary_device_config() -> k4a_sys::k4a_device_configuration_t {
let mut config = get_default_device_config();
config.wired_sync_mode = k4a_sys::k4a_wired_sync_mode_t_K4A_WIRED_SYNC_MODE_SUBORDINATE;
// Two depth images should be seperated by MIN_TIME_BETWEEN_DEPTH_CAMERA_PICTURES_USEC to ensure the depth imaging
// sensor doesn't interfere with the other. To accomplish this the master depth image captures
// (MIN_TIME_BETWEEN_DEPTH_CAMERA_PICTURES_USEC / 2) before the color image, and the subordinate camera captures its
// depth image (MIN_TIME_BETWEEN_DEPTH_CAMERA_PICTURES_USEC / 2) after the color image. This gives us two depth
// images centered around the color image as closely as possible.
config.depth_delay_off_color_usec = MIN_TIME_BETWEEN_DEPTH_CAMERA_PICTURES_USEC / 2;
// TODO: These are the old values I used that are NOT in 'green_screen'.
// They may need to be adjusted
config.color_resolution = k4a_sys::k4a_color_resolution_t_K4A_COLOR_RESOLUTION_2160P;
config.depth_mode = k4a_sys::k4a_depth_mode_t_K4A_DEPTH_MODE_NFOV_UNBINNED;
//config.camera_fps = k4a_sys::k4a_fps_t_K4A_FRAMES_PER_SECOND_30;
config
}
/// Sensible shared defaults
fn get_default_device_config() -> k4a_sys::k4a_device_configuration_t {
k4a_sys::k4a_device_configuration_t {
color_format : k4a_sys::k4a_image_format_t_K4A_IMAGE_FORMAT_COLOR_BGRA32,
color_resolution : k4a_sys::k4a_color_resolution_t_K4A_COLOR_RESOLUTION_720P,
// No need for depth during calibration
// TODO: We need it.
depth_mode : k4a_sys::k4a_depth_mode_t_K4A_DEPTH_MODE_WFOV_UNBINNED,
// Don't use all USB bandwidth
// TODO: This will be a problem.
camera_fps : k4a_sys::k4a_fps_t_K4A_FRAMES_PER_SECOND_15,
// Must be zero for master
subordinate_delay_off_master_usec : 0,
synchronized_images_only : true,
// These fields were not set and come from `K4A_DEVICE_CONFIG_INIT_DISABLE_ALL`:
depth_delay_off_color_usec: 0,
disable_streaming_indicator: false,
wired_sync_mode: k4a_sys::k4a_wired_sync_mode_t_K4A_WIRED_SYNC_MODE_STANDALONE,
}
}
+763
View File
@@ -0,0 +1,763 @@
//! Wrappers for the k4a-sys crate to make it easier to use.
//! I tried using the open source 'k4a-rs' crate from the same author,
//! but it's in a bad state and won't compile against any version of 'k4a-sys'.
use std::mem::MaybeUninit;
use std::{ptr, fmt};
use std::ptr::null_mut;
use k4a_sys;
use std::error::Error;
use std::fmt::Formatter;
/*pub fn device_open() {
k4a_sys::k4a_device_open(k4a_sys::K4A_DEVICE_DEFAULT)
if k4a_device_open(device_idx, &mut device) != k4a_buffer_result_t_K4A_BUFFER_RESULT_SUCCEEDED {
println!("Failed to open device #{}", device_idx);
continue;
}
}
}*/
// TODO: this is kind of lame.
/// A library error
#[derive(Debug)]
pub enum KinectError {
UnableToOpen { error_code: u32 },
UnableToGetSerialNumber,
UnableToStartCameras { error_code: u32 },
UnableToCreateImage { error_code: u32 },
UnableToGetSyncJackStatus { error_code: u32 },
}
/// A Kinect Device Handle
#[derive(Debug)]
pub struct Device {
device_pointer: k4a_sys::k4a_device_t,
}
impl Device {
/// Get the number of installed devices
pub fn get_installed_count() -> u32 {
unsafe {
k4a_sys::k4a_device_get_installed_count()
}
}
/// Open a device with the given index
pub fn open(device_index: u32) -> Result<Self, KinectError> {
let mut device_pointer: k4a_sys::k4a_device_t = ptr::null_mut();
unsafe {
let result = k4a_sys::k4a_device_open(device_index, &mut device_pointer);
if result != k4a_sys::k4a_buffer_result_t_K4A_BUFFER_RESULT_SUCCEEDED {
return Err(KinectError::UnableToOpen { error_code: result })
}
}
Ok(Device {
device_pointer,
})
}
/// Fetch the device serial number.
pub fn get_serial_number(&self) -> Result<String, KinectError> {
// First we interrogate the serial number size.
let mut serial_number_length: usize = 0;
let result = unsafe {
k4a_sys::k4a_device_get_serialnum(self.device_pointer, ptr::null_mut(), &mut serial_number_length)
};
if result != k4a_sys::k4a_buffer_result_t_K4A_BUFFER_RESULT_TOO_SMALL {
return Err(KinectError::UnableToGetSerialNumber);
}
// Now we request to fill a serial number buffer.
let mut serial_number = vec![0i8; serial_number_length];
let serial_number_ptr = (&mut serial_number).as_mut_ptr();
let result = unsafe {
k4a_sys::k4a_device_get_serialnum(self.device_pointer, serial_number_ptr, &mut serial_number_length)
};
if result != k4a_sys::k4a_buffer_result_t_K4A_BUFFER_RESULT_SUCCEEDED {
return Err(KinectError::UnableToGetSerialNumber);
}
// NB: Library shouldn't be returning i8's
let serial_number = serial_number.iter().map(|v| *v as u8).collect();
String::from_utf8(serial_number)
.map(|s| s.trim_matches(char::from(0)).into()) // Remove trailing null byte
.map_err(|_| KinectError::UnableToGetSerialNumber)
}
/// Get the device synchronization jack statuses.
/// Each device has an 'in' jack and an 'out' jack.
pub fn get_synchronization_jack_status(&self) -> Result<SynchronizationJackStatus, KinectError> {
let mut sync_in_jack_connected = false;
let mut sync_out_jack_connected = false;
let result = unsafe {
k4a_sys::k4a_device_get_sync_jack(self.device_pointer,
&mut sync_in_jack_connected, &mut sync_out_jack_connected)
};
if result != k4a_sys::k4a_result_t_K4A_RESULT_SUCCEEDED {
return Err(KinectError::UnableToGetSyncJackStatus { error_code: result });
}
Ok(SynchronizationJackStatus {
sync_in_jack_connected,
sync_out_jack_connected,
})
}
// TODO: having a 'DeviceConfigurations' struct that goes unused is kind of gross.
/// Start the cameras
pub fn start_cameras(&self,
device_config: k4a_sys::k4a_device_configuration_t)
-> Result<(), KinectError>
{
let result = unsafe {
k4a_sys::k4a_device_start_cameras(self.device_pointer, &device_config)
};
if result != k4a_sys::k4a_buffer_result_t_K4A_BUFFER_RESULT_SUCCEEDED {
return Err(KinectError::UnableToStartCameras { error_code: result });
}
return Ok(())
}
// TODO: More sensible defaults, or get rid of this entirely.
/// Start the cameras.
pub fn start_cameras_default_config(&self) -> Result<(), KinectError> {
let mut device_config = DeviceConfiguration::new();
// NB: Although the Kinect docs say this format isn't natively supported by the color camera
// and that extra CPU is required, this is the only color mode supported by 'k4aviewer' 3D view.
device_config.0.color_format = k4a_sys::k4a_image_format_t_K4A_IMAGE_FORMAT_COLOR_BGRA32;
device_config.0.color_resolution = k4a_sys::k4a_color_resolution_t_K4A_COLOR_RESOLUTION_2160P;
device_config.0.depth_mode = k4a_sys::k4a_depth_mode_t_K4A_DEPTH_MODE_NFOV_UNBINNED;
device_config.0.camera_fps = k4a_sys::k4a_fps_t_K4A_FRAMES_PER_SECOND_30;
self.start_cameras(device_config.0)
}
/// Stops the color and depth camera capture.
///
/// The streaming of individual sensors stops as a result of this call. Once called,
/// k4a_device_start_cameras() may be called again to resume sensor streaming.
/// This function may be called while another thread is blocking in k4a_device_get_capture().
/// Calling this function while another thread is in that function will result in that function
/// returning a failure.
pub fn stop_cameras(&self) {
unsafe {
k4a_sys::k4a_device_stop_cameras(self.device_pointer)
}
}
/// Get capture and return a new buffer.
pub fn get_capture(&self, timeout_ms: i32) -> Result<Capture, GetCaptureError> {
let mut capture_buffer: k4a_sys::k4a_capture_t = ptr::null_mut();
self.get_capture_buffered(&mut capture_buffer, timeout_ms)
.map(|_| Capture(capture_buffer)) // TODO: Can capture be null?
}
/// Get capture and reuse an existing buffer.
pub fn get_capture_buffered(&self, capture_buffer: &mut k4a_sys::k4a_capture_t, timeout_ms: i32)
-> Result<(), GetCaptureError>
{
let result = unsafe {
k4a_sys::k4a_device_get_capture(self.device_pointer, capture_buffer, timeout_ms)
};
match result {
k4a_sys::k4a_wait_result_t_K4A_WAIT_RESULT_SUCCEEDED => { /* ok, continue */ },
k4a_sys::k4a_wait_result_t_K4A_WAIT_RESULT_TIMEOUT => {
return Err(GetCaptureError::TimeoutError);
},
k4a_sys::k4a_wait_result_t_K4A_WAIT_RESULT_FAILED => {
return Err(GetCaptureError::TimeoutError);
}
_ => {
return Err(GetCaptureError::UnknownError(result));
}
}
Ok(())
}
/// Get the camera calibration for the entire Azure Kinect device.
///
/// The calibration represents the data needed to transform between the camera views and may be
/// different for each operating depth_mode and color_resolution the device is configured to
/// operate in.
/// The calibration output is used as input to all calibration and transformation functions.
pub fn get_calibration(&self,
depth_mode: k4a_sys::k4a_depth_mode_t,
color_resolution: k4a_sys::k4a_color_resolution_t)
-> Result<Calibration, GetCalibrationError>
{
// TODO: Why isn't the way I've been using to init structures before still working?
//let mut calibration_buffer: k4a_sys::k4a_calibration_t = ptr::null_mut();
/*let mut calibration_buffer: k4a_sys::k4a_calibration_t = k4a_sys::k4a_calibration_t {
color_camera_calibration: 0,
color_resolution: 0,
depth_camera_calibration: 0,
depth_mode: 0,
extrinsics: [0,0,0,0],
};*/
unsafe {
let mut calibration_buffer: MaybeUninit<k4a_sys::k4a_calibration_t> = MaybeUninit::uninit();
let result = k4a_sys::k4a_device_get_calibration(
self.device_pointer,
depth_mode,
color_resolution,
calibration_buffer.as_mut_ptr(),
);
match result {
k4a_sys::k4a_result_t_K4A_RESULT_SUCCEEDED => { /* ok, continue */ },
k4a_sys::k4a_result_t_K4A_RESULT_FAILED => {
return Err(GetCalibrationError::FailedError);
},
_ => {
return Err(GetCalibrationError::UnknownError(result));
},
}
let handle = calibration_buffer.assume_init();
let calibration = Calibration(handle);
Ok(calibration)
}
}
}
/// Synchronization jack status.
#[derive(Debug,Copy,Clone)]
pub struct SynchronizationJackStatus {
pub sync_in_jack_connected: bool,
pub sync_out_jack_connected: bool,
}
/// Errors for GetCapture
#[derive(Debug)]
pub enum GetCaptureError {
TimeoutError,
FailedError,
UnknownError(u32),
}
#[derive(Debug)]
pub enum CaptureError {
NullCapture,
}
impl fmt::Display for CaptureError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "CaptureError")
}
}
impl Error for CaptureError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
None
}
}
/// Errors for GetCalibration
#[derive(Debug)]
pub enum GetCalibrationError {
FailedError,
UnknownError(u32),
}
#[derive(Clone)]
pub struct Calibration(pub k4a_sys::k4a_calibration_t);
impl Calibration {
pub fn default() -> Self {
let extrinsics = k4a_sys::_k4a_calibration_extrinsics_t {
rotation: [0.0f32, 0.0f32, 0.0f32, 0.0f32, 0.0f32, 0.0f32, 0.0f32, 0.0f32, 0.0f32],
translation: [0.0f32, 0.0f32, 0.0f32],
};
let extrinsics_4 = [
extrinsics.clone(),
extrinsics.clone(),
extrinsics.clone(),
extrinsics.clone(),
];
let extrinsics_4_4 = [
extrinsics_4.clone(),
extrinsics_4.clone(),
extrinsics_4.clone(),
extrinsics_4.clone(),
];
let camera_calibration = k4a_sys::_k4a_calibration_camera_t {
resolution_width: 0,
resolution_height: 0,
metric_radius: 0.0f32,
extrinsics: extrinsics.clone(),
intrinsics: k4a_sys::_k4a_calibration_intrinsics_t {
type_: 0,
parameter_count: 0,
parameters: k4a_sys::k4a_calibration_intrinsic_parameters_t {
param: k4a_sys::k4a_calibration_intrinsic_parameters_t__param {
cx: 0.0f32,
cy: 0.0f32,
fx: 0.0f32,
fy: 0.0f32,
k1: 0.0f32,
k2: 0.0f32,
k3: 0.0f32,
k4: 0.0f32,
k5: 0.0f32,
k6: 0.0f32,
codx: 0.0f32,
cody: 0.0f32,
p2: 0.0f32,
p1: 0.0f32,
metric_radius: 0.0f32,
},
},
},
};
let mut calibration = k4a_sys::k4a_calibration_t {
color_camera_calibration: camera_calibration.clone(),
depth_camera_calibration: camera_calibration.clone(),
color_resolution: 0,
depth_mode: 0,
extrinsics: extrinsics_4_4,
};
Self(calibration)
}
// TODO: Make this the `Debug` trait output instead.
pub fn debug_print(&self) {
println!("===== CALIBRATION =====");
println!("\t Color resolution: {}", self.0.color_resolution);
println!("\t Depth mode: {}", self.0.depth_mode);
println!("\t Extrinsics: {:?}", self.0.extrinsics);
println!("\t depth.resolution_width: {}", self.0.depth_camera_calibration.resolution_width);
println!("\t depth.resolution_height: {}", self.0.depth_camera_calibration.resolution_height);
println!("\t depth.metric_radius: {}", self.0.depth_camera_calibration.metric_radius);
println!("\t depth.extrinsics: {:?}", self.0.depth_camera_calibration.extrinsics);
println!("\t depth.intrinsics.type: {}", self.0.depth_camera_calibration.intrinsics.type_);
println!("\t depth.intrinsics.parameter_count: {}", self.0.depth_camera_calibration.intrinsics.parameter_count);
unsafe {
// NB: This is a union field, so we have to use unsafe access
println!("\t depth.intrinsics.parameters.param.cx: {}", self.0.depth_camera_calibration.intrinsics.parameters.param.cx);
println!("\t depth.intrinsics.parameters.param.cy: {}", self.0.depth_camera_calibration.intrinsics.parameters.param.cy);
println!("\t depth.intrinsics.parameters.param.fx: {}", self.0.depth_camera_calibration.intrinsics.parameters.param.fx);
println!("\t depth.intrinsics.parameters.param.fy: {}", self.0.depth_camera_calibration.intrinsics.parameters.param.fy);
println!("\t depth.intrinsics.parameters.param.k1: {}", self.0.depth_camera_calibration.intrinsics.parameters.param.k1);
println!("\t depth.intrinsics.parameters.param.k2: {}", self.0.depth_camera_calibration.intrinsics.parameters.param.k2);
println!("\t depth.intrinsics.parameters.param.k3: {}", self.0.depth_camera_calibration.intrinsics.parameters.param.k3);
println!("\t depth.intrinsics.parameters.param.k4: {}", self.0.depth_camera_calibration.intrinsics.parameters.param.k4);
println!("\t depth.intrinsics.parameters.param.k5: {}", self.0.depth_camera_calibration.intrinsics.parameters.param.k5);
println!("\t depth.intrinsics.parameters.param.k6: {}", self.0.depth_camera_calibration.intrinsics.parameters.param.k6);
println!("\t depth.intrinsics.parameters.param.codx: {}", self.0.depth_camera_calibration.intrinsics.parameters.param.codx);
println!("\t depth.intrinsics.parameters.param.cody: {}", self.0.depth_camera_calibration.intrinsics.parameters.param.cody);
println!("\t depth.intrinsics.parameters.param.p2: {}", self.0.depth_camera_calibration.intrinsics.parameters.param.p2);
println!("\t depth.intrinsics.parameters.param.p1: {}", self.0.depth_camera_calibration.intrinsics.parameters.param.p1);
println!("\t depth.intrinsics.parameters.param.metric_radius: {}", self.0.depth_camera_calibration.intrinsics.parameters.param.metric_radius);
}
println!("\t color.resolution_width: {}", self.0.color_camera_calibration.resolution_width);
println!("\t color.resolution_height: {}", self.0.color_camera_calibration.resolution_height);
println!("\t color.metric_radius: {}", self.0.color_camera_calibration.metric_radius);
println!("\t color.extrinsics: {:?}", self.0.color_camera_calibration.extrinsics);
println!("\t color.intrinsics.type: {}", self.0.color_camera_calibration.intrinsics.type_);
println!("\t color.intrinsics.parameter_count: {}", self.0.color_camera_calibration.intrinsics.parameter_count);
unsafe {
// NB: This is a union field, so we have to use unsafe access
println!("\t color.intrinsics.parameters.param.cx: {}", self.0.color_camera_calibration.intrinsics.parameters.param.cx);
println!("\t color.intrinsics.parameters.param.cy: {}", self.0.color_camera_calibration.intrinsics.parameters.param.cy);
println!("\t color.intrinsics.parameters.param.fx: {}", self.0.color_camera_calibration.intrinsics.parameters.param.fx);
println!("\t color.intrinsics.parameters.param.fy: {}", self.0.color_camera_calibration.intrinsics.parameters.param.fy);
println!("\t color.intrinsics.parameters.param.k1: {}", self.0.color_camera_calibration.intrinsics.parameters.param.k1);
println!("\t color.intrinsics.parameters.param.k2: {}", self.0.color_camera_calibration.intrinsics.parameters.param.k2);
println!("\t color.intrinsics.parameters.param.k3: {}", self.0.color_camera_calibration.intrinsics.parameters.param.k3);
println!("\t color.intrinsics.parameters.param.k4: {}", self.0.color_camera_calibration.intrinsics.parameters.param.k4);
println!("\t color.intrinsics.parameters.param.k5: {}", self.0.color_camera_calibration.intrinsics.parameters.param.k5);
println!("\t color.intrinsics.parameters.param.k6: {}", self.0.color_camera_calibration.intrinsics.parameters.param.k6);
println!("\t color.intrinsics.parameters.param.codx: {}", self.0.color_camera_calibration.intrinsics.parameters.param.codx);
println!("\t color.intrinsics.parameters.param.cody: {}", self.0.color_camera_calibration.intrinsics.parameters.param.cody);
println!("\t color.intrinsics.parameters.param.p2: {}", self.0.color_camera_calibration.intrinsics.parameters.param.p2);
println!("\t color.intrinsics.parameters.param.p1: {}", self.0.color_camera_calibration.intrinsics.parameters.param.p1);
println!("\t color.intrinsics.parameters.param.metric_radius: {}", self.0.color_camera_calibration.intrinsics.parameters.param.metric_radius);
}
println!("==========");
}
}
/// Adapted from k4a-sys. Represents a capture.
#[derive(Debug)]
pub struct Capture(pub k4a_sys::k4a_capture_t);
impl Capture {
pub fn get_depth_image(&self) -> Result<Image, CaptureError> {
let image = unsafe {
k4a_sys::k4a_capture_get_depth_image(self.0)
};
if image.is_null() {
return Err(CaptureError::NullCapture);
}
Ok(Image(image))
}
pub fn get_color_image(&self) -> Result<Image, CaptureError> {
let image = unsafe {
k4a_sys::k4a_capture_get_color_image(self.0)
};
if image.is_null() {
return Err(CaptureError::NullCapture);
}
Ok(Image(image))
}
pub fn get_ir_image(&self) -> Result<Image, CaptureError> {
let image = unsafe {
k4a_sys::k4a_capture_get_ir_image(self.0)
};
if image.is_null() {
return Err(CaptureError::NullCapture);
}
Ok(Image(image))
}
/// Returns the underlying opaque handle *without* an additional refcount.
/// Do not deallocate it.
pub fn get_handle(&self) -> k4a_sys::k4a_capture_t {
self.0
}
}
/// Remove a libk4a capture refcount on every drop.
/// When the refcount drops to zero, the capture goes away.
impl Drop for Capture {
fn drop(&mut self) {
unsafe {
k4a_sys::k4a_capture_release(self.0);
self.0 = null_mut();
}
}
}
/// Handles are refcounted by libk4a. The final reference is destroyed
impl Clone for Capture {
fn clone(&self) -> Self {
// We must increment the refcount.
let handle = self.get_handle();
unsafe {
k4a_sys::k4a_capture_reference(handle);
}
Self {
0: handle,
}
}
fn clone_from(&mut self, source: &Self) {
let handle = source.get_handle();
unsafe {
k4a_sys::k4a_capture_reference(handle);
}
unsafe {
// drop refcount, potentially releasing if reached zero
k4a_sys::k4a_capture_release(self.0);
}
self.0 = handle;
}
}
/// FIXME FIXME FIXME: WHY IS THIS NOT SEND WITH ARC<MUTEX<T>>!?
/// We are making k4a_sys::k4a_capture_t Send only when wrapped with Arc<Mutex<>>,
/// but the compiler can't figure that out. Freaky gross.
///
/// "Rust automatically determines whether a type is Send and/or Sync. Anything that has a
/// raw ptr inside, which is what you have here, is considered !Send and !Sync. This isn’t because
/// it automatically means the type is unsafe in Send/Sync terms, but rather more like a lint for
/// the code author: they need to determine the safety themselves, and then if they’re safe,
/// manually impl Send and/or Sync, as appropriate, for the type."
///
/// Actually Rust is doing a good job here.
///
/// https://users.rust-lang.org/t/solved-how-to-move-non-send-between-threads-or-an-alternative/19928/11
unsafe impl Send for Capture{}
/// TODO: We also need to send device
unsafe impl Send for Device{}
/// Adapted from k4a-sys. Represents an image within a capture.
#[derive(Debug)]
pub struct Image(pub k4a_sys::k4a_image_t);
impl Image {
/// Create a blank image.
///
/// This function is used to create images of formats that have consistent stride. The function
/// is not suitable for compressed formats that may not be represented by the same number of bytes
/// per line.
///
/// For most image formats, the function will allocate an image buffer of size
/// height_pixels * stride_bytes. Buffers K4A_IMAGE_FORMAT_COLOR_NV12 format will allocate an
/// additional height_pixels / 2 set of lines (each of stride_bytes). This function cannot be used
/// to allocate K4A_IMAGE_FORMAT_COLOR_MJPG buffers.
///
/// To create an image object without the API allocating memory, or to represent an image that has
/// a non-deterministic stride, use k4a_image_create_from_buffer().
///
/// The k4a_image_t is created with a reference count of 1.
///
/// When finished using the created image, release it with k4a_image_release.
///
/// stride_bytes - The number of bytes per horizontal line of the image. If set to 0, the stride
/// will be set to the minimum size given the format and width_pixels.
///
pub fn create(format: ImageFormat,
width: u32,
height: u32,
stride_bytes: u32)
-> Result<Self, KinectError>
{
let mut handle = null_mut();
let result = unsafe {
k4a_sys::k4a_image_create(
format as k4a_sys::k4a_image_format_t,
width as i32,
height as i32,
stride_bytes as i32,
&mut handle
)
};
if result != k4a_sys::k4a_result_t_K4A_RESULT_SUCCEEDED {
return Err(KinectError::UnableToCreateImage { error_code: result });
}
Ok(Image(handle))
}
pub fn get_height_pixels(&self) -> usize {
unsafe {
k4a_sys::k4a_image_get_height_pixels(self.0) as usize
}
}
pub fn get_width_pixels(&self) -> usize {
unsafe {
k4a_sys::k4a_image_get_width_pixels(self.0) as usize
}
}
pub fn get_stride_bytes(&self) -> usize {
unsafe {
k4a_sys::k4a_image_get_stride_bytes(self.0) as usize
}
}
pub fn get_size(&self) -> usize {
unsafe {
k4a_sys::k4a_image_get_size(self.0) // returns size_t
}
}
pub fn get_buffer(&self) -> *mut u8 {
unsafe {
k4a_sys::k4a_image_get_buffer(self.0)
}
}
/// Use this function to determine the format of the image buffer.
/// This function is not expected to fail, all k4a_image_t's are created with a
/// known format. If the image_handle is invalid, the function will return
/// K4A_IMAGE_FORMAT_CUSTOM.
pub fn get_format(&self) -> ImageFormat {
let format = unsafe {
k4a_sys::k4a_image_get_format(self.0)
};
format.into()
}
/// Returns the underlying opaque handle *without* an additional refcount.
/// Do not deallocate it.
pub fn get_handle(&self) -> k4a_sys::k4a_image_t {
self.0
}
}
/// Remove a libk4a image refcount on every drop.
/// When the refcount drops to zero, the image goes away.
impl Drop for Image {
fn drop(&mut self) {
unsafe {
k4a_sys::k4a_image_release(self.0);
}
}
}
/// Handles are refcounted by libk4a. The final reference is destroyed
impl Clone for Image {
fn clone(&self) -> Self {
// We must increment the refcount.
let handle = self.get_handle();
unsafe {
k4a_sys::k4a_image_reference(handle);
}
Self {
0: handle,
}
}
fn clone_from(&mut self, source: &Self) {
let handle = source.get_handle();
unsafe {
k4a_sys::k4a_image_reference(handle);
}
unsafe {
// drop refcount, potentially releasing if reached zero
k4a_sys::k4a_image_release(self.0);
}
self.0 = handle;
}
}
#[derive(Debug,Clone,Copy)]
pub enum ImageFormat {
ColorMjpg,
ColorNv12,
ColorYuy2,
ColorBgra32,
/// Depth image type DEPTH16.
/// Each pixel of DEPTH16 data is two bytes of little endian unsigned depth data.
/// The unit of the data is in millimeters from the origin of the camera.
/// Stride indicates the length of each line in bytes and should be used to determine
/// the start location of each line of the image in memory.
Depth16,
Ir16,
Custom8,
Custom16,
Custom,
UnknownFormatError, // FIXME: Just return Result<T>?
}
impl ImageFormat {
pub fn to_k4a(&self) -> k4a_sys::k4a_image_format_t {
match self {
ImageFormat::ColorMjpg => 0,
ImageFormat::ColorNv12 => 1,
ImageFormat::ColorYuy2 => 2,
ImageFormat::ColorBgra32 => 3,
ImageFormat::Depth16 => 4,
ImageFormat::Ir16 => 5,
ImageFormat::Custom8 => 6,
ImageFormat::Custom16 => 7,
ImageFormat::Custom => 8,
ImageFormat::UnknownFormatError => 255, // TODO: Better handling?
}
}
}
impl From<k4a_sys::k4a_image_format_t> for ImageFormat {
fn from(format: k4a_sys::k4a_image_format_t) -> Self {
match format {
0 => ImageFormat::ColorMjpg,
1 => ImageFormat::ColorNv12,
2 => ImageFormat::ColorYuy2,
3 => ImageFormat::ColorBgra32,
4 => ImageFormat::Depth16,
5 => ImageFormat::Ir16,
6 => ImageFormat::Custom8,
7 => ImageFormat::Custom16,
8 => ImageFormat::Custom,
_ => ImageFormat::UnknownFormatError,
}
}
}
/// Deallocate open device handles
impl Drop for Device {
fn drop(&mut self) {
unsafe {
k4a_sys::k4a_device_close(self.device_pointer);
}
}
}
/// Copied from k4a-sys
pub struct DeviceConfiguration (k4a_sys::k4a_device_configuration_t);
/// Copied from k4a-sys
impl DeviceConfiguration {
pub fn new() -> Self {
Self (k4a_sys::k4a_device_configuration_t {
color_format: k4a_sys::k4a_image_format_t_K4A_IMAGE_FORMAT_COLOR_MJPG,
color_resolution: k4a_sys::k4a_color_resolution_t_K4A_COLOR_RESOLUTION_720P,
depth_mode: k4a_sys::k4a_depth_mode_t_K4A_DEPTH_MODE_WFOV_2X2BINNED,
camera_fps: 0,
synchronized_images_only: false,
depth_delay_off_color_usec: 0,
wired_sync_mode: 0,
subordinate_delay_off_master_usec: 0,
disable_streaming_indicator: false,
})
}
}
#[derive(Clone,Debug)]
pub struct Resolution {
pub width: i32,
pub height: i32,
}
#[derive(Clone,Debug)]
pub struct Transformation {
transformation: k4a_sys::k4a_transformation_t,
pub color_resolution: Resolution,
pub depth_resolution: Resolution,
}
impl Transformation {
/// Creates a transformation associated with a calibration
pub fn from_calibration(calibration: &Calibration) -> Self {
let transformation = unsafe {
k4a_sys::k4a_transformation_create(&calibration.0)
};
Self {
transformation,
color_resolution: Resolution {
width: calibration.0.color_camera_calibration.resolution_width,
height: calibration.0.color_camera_calibration.resolution_height,
},
depth_resolution: Resolution {
width: calibration.0.depth_camera_calibration.resolution_width,
height: calibration.0.depth_camera_calibration.resolution_height,
},
}
}
/// Returns the underlying opaque handle *without* an additional refcount.
/// Do not deallocate it.
pub fn get_handle(&self) -> k4a_sys::k4a_transformation_t {
self.transformation
}
}
impl Drop for Transformation {
fn drop(&mut self) {
unsafe {
k4a_sys::k4a_transformation_destroy(self.transformation);
}
}
}
+2
View File
@@ -0,0 +1,2 @@
pub mod capture;
pub mod sensor_control;
+71
View File
@@ -0,0 +1,71 @@
use crate::image_debug::depth_to_image;
use image::DynamicImage;
use kinect::{Capture, Device};
use std::sync::Arc;
use std::sync::Mutex;
/** Self-locking holder of Capture objects. */
pub struct CaptureProvider {
capture: Arc<Mutex<Option<Capture>>>,
}
impl CaptureProvider {
pub fn new() -> Self {
CaptureProvider {
capture: Arc::new(Mutex::new(None)),
}
}
/**
* Take the latest capture, if available, through interior mutability.
* This leaves the mutex holding None.
*/
pub fn get_capture(&self) -> Option<Capture> {
self.capture.lock()
.ok()
.and_then(|mut lock| lock.take())
}
/**
* Consume capture and replace whatever we currently hold.
*/
pub fn set_capture(&self, capture: Capture) {
match self.capture.lock() {
Ok(mut lock) => {
*lock = Some(capture)
},
Err(_) => {},
}
}
}
pub fn capture_thread(capture_provider: Arc<CaptureProvider>, device: Option<Device>, start_camera: bool) {
let start_camera = device.is_none() || start_camera;
let device = match device {
Some(device) => device,
None => {
let installed_devices = Device::get_installed_count();
println!("Installed devices: {}", installed_devices);
let device = Device::open(0).unwrap();
println!("Device: {:?}", device);
device
},
};
let serial_number = device.get_serial_number().unwrap();
println!("Device: {:?}", serial_number);
if start_camera {
println!("Starting cameras...");
device.start_cameras_default_config().unwrap();
}
loop {
let capture = device.get_capture(1000)
.expect("Should be able to get frame capture.");
capture_provider.set_capture(capture);
}
}
+62
View File
@@ -0,0 +1,62 @@
/// A core color type
#[derive(Clone, Debug, Default, PartialEq)]
pub struct RgbaF32 {
pub red: f32,
pub green: f32,
pub blue: f32,
pub alpha: f32,
}
impl RgbaF32 {
/// Black (alpha 1.0)
pub fn black() -> Self {
Self {
red: 0.0,
green: 0.0,
blue: 0.0,
alpha: 1.0,
}
}
pub fn rgb(r: f32, g: f32, b: f32) -> Self {
Self {
red: r,
green: g,
blue: b,
alpha: 1.0,
}
}
pub fn gray(gray: f32) -> Self {
Self {
red: gray,
green: gray,
blue: gray,
alpha: 1.0,
}
}
}
#[cfg(test)]
mod test {
use crate::core_types::RgbaF32;
#[test]
pub fn test_rgbaf32() {
let black = RgbaF32::black();
assert_eq!(black.red, 0.0f32);
assert_eq!(black.green, 0.0f32);
assert_eq!(black.blue, 0.0f32);
assert_eq!(black.alpha, 1.0f32);
}
#[test]
pub fn test_gray() {
let gray = RgbaF32::gray(0.5);
assert_eq!(gray.red, 0.5f32);
assert_eq!(gray.green, 0.5f32);
assert_eq!(gray.blue, 0.5f32);
assert_eq!(gray.alpha, 1.0f32);
}
}
+2
View File
@@ -0,0 +1,2 @@
pub mod read_file_string_contents;
pub mod write_to_file_from_byte_ptr;
+10
View File
@@ -0,0 +1,10 @@
use anyhow::Result as AnyhowResult;
use std::fs::File;
use std::io::Read;
pub fn read_file_string_contents(filename: &str) -> AnyhowResult<String> {
let mut file = File::open(filename)?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
Ok(contents)
}
+23
View File
@@ -0,0 +1,23 @@
use anyhow::Result as AnyhowResult;
use std::fs::OpenOptions;
use std::io::Write;
use std::ptr;
/// Raw memory copy into a file
pub fn write_to_file_from_byte_ptr(filename: &str, byte_src: *const u8, size_bytes: usize) -> AnyhowResult<()> {
let mut file = OpenOptions::new()
.write(true)
.create(true)
.open(filename)?;
let mut vec = vec![0u8; size_bytes];
unsafe {
let vec_ptr = vec.as_mut_ptr();
//ptr::write_bytes(vec_ptr, color_src, size_bytes);
ptr::copy_nonoverlapping(byte_src, vec_ptr, size_bytes);
}
file.write(&vec)?;
Ok(())
}
+276
View File
@@ -0,0 +1,276 @@
use anyhow::Result as AnyhowResult;
use kinect::Calibration;
use crate::ProgramArgs;
use crate::core_types::RgbaF32;
use crate::gui::enhanced_window::EnhancedWindow;
use crate::gui::mouse_camera_arcball::MouseCameraArcball;
use crate::azure_kinect::capture::device_capturer::CaptureProvider;
use crate::point_cloud::pixel_structs::BgraPixel;
use crate::point_cloud::point_cloud_visualiser::{ColorizationStrategy, PointCloudVisualizer, PointCloudVisualizerError};
use crate::point_cloud::viewer_image::ViewerImage;
use crate::webcam::WebcamWriter;
use gl;
use imgui::*;
use imgui::Image;
use std::path::Path;
use std::sync::{Arc, Mutex, PoisonError, MutexGuard};
use std::time::{Instant, Duration};
pub fn run(capture_provider: Arc<CaptureProvider>, program_args: ProgramArgs) -> AnyhowResult<()> {
let mut webcam_writer = None;
if program_args.enable_webcam_output_writing {
webcam_writer = Some(WebcamWriter::open_file("/dev/video0", 1280, 720, 3)
.expect("should be able to create webcamwriter"));
}
let sdl_arcball = Arc::new(Mutex::new(MouseCameraArcball::new(1280, 720)));
let sdl_context = sdl2::init().unwrap();
let video = sdl_context.video().unwrap();
{
let gl_attr = video.gl_attr();
gl_attr.set_context_profile(sdl2::video::GLProfile::Core);
gl_attr.set_context_version(3, 0);
}
let window = video.window("rust-imgui-sdl2 demo", 3000, 2000)
.position_centered()
.resizable()
.opengl()
.allow_highdpi()
.build()
.unwrap();
let _gl_context = window.gl_create_context().expect("Couldn't create GL context");
gl::load_with(|s| video.gl_get_proc_address(s) as _);
//enable_opengl_debugging();
let mut imgui = imgui::Context::create();
imgui.set_ini_filename(None);
imgui.io_mut().config_windows_move_from_title_bar_only = true;
/*let gl_texture_n64 = load_texture("n64logo.png");
let imgui_texture_n64 = TextureId::from(gl_texture_n64 as usize);
let mut gl_texture_snes = load_texture("sneslogo.png");
let mut imgui_texture_snes = TextureId::from(gl_texture_snes as usize);*/
let colorization_strategy = ColorizationStrategy::Color;
let clear_color = RgbaF32::gray(0.5);
let mut visualizer = PointCloudVisualizer::new(
capture_provider.get_num_cameras(),
capture_provider.get_calibrations(),
true,
colorization_strategy,
clear_color,
sdl_arcball.clone(),
);
//rebinder.restore();
let texture = ViewerImage::create(
// TODO: The following commented-out values work and look great,
// but now we're doing suboptimal webcam stuff:
//1280,
//1152,
1280,
720,
).expect("ViewerImage texture creation should work");
let mut imgui_visualizer_xyz_texture_0 : Option<TextureId> = None;
let mut imgui_visualizer_xyz_texture_1 : Option<TextureId> = None;
let imgui_point_cloud_convert_xy_table= TextureId::from(visualizer.point_cloud_converters.get(0).unwrap().xy_table_texture.id() as usize);
let imgui_kinect_final_output = TextureId::from(texture.texture_id() as usize);
let mut imgui_sdl2 = imgui_sdl2::ImguiSdl2::new(&mut imgui, &window);
let renderer = imgui_opengl_renderer::Renderer::new(&mut imgui, |s| video.gl_get_proc_address(s) as _);
let mut event_pump = sdl_context.event_pump().unwrap();
let mut last_frame = Instant::now();
let mut last_change = Instant::now();
let mut camera_index = 0;
visualizer.setup_rendering()?;
'running: loop {
use sdl2::event::Event;
use sdl2::keyboard::Keycode;
for event in event_pump.poll_iter() {
imgui_sdl2.handle_event(&mut imgui, &event);
// TODO: This is how Imgui appears to tell SDL to ignore certain events.
if imgui_sdl2.ignore_event(&event) { continue; }
match event {
Event::Quit {..} | Event::KeyDown { keycode: Some(Keycode::Escape), .. } => {
break 'running
},
_ => {}
}
/*// Also send to the arcball to update the view matrix.
match sdl_arcball.lock() {
Ok(mut arcball) => arcball.process_event(&event),
Err(_) => {},
}*/
}
imgui_sdl2.prepare_frame(imgui.io_mut(), &window, &event_pump.mouse_state());
let now = Instant::now();
let delta = now - last_frame;
let delta_s = delta.as_secs() as f32 + delta.subsec_nanos() as f32 / 1_000_000_000.0;
last_frame = now;
imgui.io_mut().delta_time = delta_s;
let ui = imgui.frame();
//ui.show_demo_window(&mut true);
const window_width : f32 = 1290.0;
const window_height : f32 = 760.0;
if imgui_visualizer_xyz_texture_0.is_none() {
// TODO: TEMPORARY MESS TO SUPPORT MULTI-CAMERA
if visualizer.xyz_textures.get(0).unwrap().id() != 0 {
imgui_visualizer_xyz_texture_0 = Some(TextureId::from(visualizer.xyz_textures.get(0).unwrap().id() as usize));
}
}
if imgui_visualizer_xyz_texture_1.is_none() {
// TODO: TEMPORARY MESS TO SUPPORT MULTI-CAMERA
if visualizer.xyz_textures.get(1).unwrap().id() != 0 {
imgui_visualizer_xyz_texture_1 = Some(TextureId::from(visualizer.xyz_textures.get(1).unwrap().id() as usize));
}
}
Window::new(im_str!("Visualizer XYZ Texture (0)"))
.scrollable(true)
.size([window_width, window_height], Condition::FirstUseEver)
.position([0.0, 0.0], Condition::FirstUseEver)
.build(&ui, || {
match imgui_visualizer_xyz_texture_0.as_ref() {
None => {},
Some(xyz_texture) => {
Image::new(xyz_texture.clone(), [1280.0, 720.0]).build(&ui);
},
}
});
Window::new(im_str!("Visualizer XYZ Texture (1)"))
.size([window_width, window_height], Condition::FirstUseEver)
.position([window_width + 50.0, 0.0], Condition::FirstUseEver)
.movable(false)
.build(&ui, || {
match imgui_visualizer_xyz_texture_1.as_ref() {
None => {},
Some(xyz_texture) => {
Image::new(xyz_texture.clone(), [1280.0, 720.0]).build(&ui);
},
}
});
Window::new(im_str!("Point Cloud Converter XY Table"))
.size([window_width, window_height], Condition::FirstUseEver)
.position([0.0, window_height + 50.0], Condition::FirstUseEver)
.build(&ui, || {
Image::new(imgui_point_cloud_convert_xy_table, [1280.0, 720.0]).build(&ui);
});
// NB: This is larger. The texture is 1280x1152.
EnhancedWindow::new(&im_str!("Final Output: {:?}", colorization_strategy))
.size([window_width, window_height + 435.0], Condition::FirstUseEver)
.position([window_width + 50.0, window_height + 50.0], Condition::FirstUseEver)
.mouse_inputs(true)
.build(&ui, |window| {
if let Some(mouse_state) = window.get_window_bounded_mouse_state() {
match sdl_arcball.lock() {
Ok(mut arcball) => arcball.process_mouse_state(mouse_state),
Err(_) => {},
}
}
Image::new(imgui_kinect_final_output, [1280.0, 720.0]).build(&ui);
});
unsafe {
gl::ClearColor(0.2, 0.2, 0.2, 1.0);
gl::Clear(gl::COLOR_BUFFER_BIT);
}
imgui_sdl2.prepare_render(&ui, &window);
renderer.render(ui);
window.gl_swap_window();
if let Some(mut captures) = capture_provider.get_captures() {
if program_args.save_captures {
// Only save the last frame (for now)
for (i, capture) in captures.iter().enumerate() {
let suffix = format!("{}", i);
capture.debug_save_files(Path::new("output/"), &suffix).unwrap();
}
}
visualizer.update_texture_id(texture.texture_id(), captures)
.map(|_| {})
.map_err(|err| {
match err {
PointCloudVisualizerError::MissingDepthImage => { println!("Missing depth image"); },
PointCloudVisualizerError::MissingColorImage => { println!("Missing color image"); },
_ => {
unreachable!("Error: {:?}", err);
}
}
});
}
match sdl_arcball.lock() {
Ok(mut arcball) => {
arcball.animate();
},
Err(_) => {},
}
if let Some(mut webcam) = webcam_writer.as_mut() {
webcam.write_current_frame_to_file(texture.texture_id())
.expect("should write");
}
let change_delta = last_frame - last_change;
if change_delta > Duration::from_millis(5_000) {
camera_index = (camera_index + 1) % capture_provider.get_num_cameras();
last_change = Instant::now();
}
/*if change_delta > Duration::from_millis(5000) {
colorization_strategy = match colorization_strategy {
ColorizationStrategy::Simple => ColorizationStrategy::Shaded, // NB: We won't do simple.
ColorizationStrategy::Shaded => ColorizationStrategy::Color,
ColorizationStrategy::Color => ColorizationStrategy::Shaded,
};
println!("Changing colorization strategy: {:?}", colorization_strategy);
visualizer.set_colorization_strategy(colorization_strategy);
last_change = Instant::now();
}*/
//rebinder.restore();
std::thread::sleep(::std::time::Duration::new(0, 1_000_000_000u32 / 60));
}
Ok(())
}
+168
View File
@@ -0,0 +1,168 @@
/// A wrapper around `Imgui::Window` that adds window-scoped devices.
pub struct EnhancedWindow <'a> {
pub window: imgui::Window<'a>,
}
impl <'a> EnhancedWindow <'a> {
pub fn new(name: &'a imgui::ImStr) -> Self {
let mut window = imgui::Window::new(name)
.scrollable(true);
Self {
window,
}
}
/// Enables/disables catching mouse input.
///
/// Enabled by default.
/// Note: Hovering test will pass through when disabled
#[inline]
pub fn mouse_inputs(mut self, value: bool) -> Self {
// NB: Builder API consumes window, so we have to move it.
self.window = self.window.mouse_inputs(value);
self
}
/// Sets the window position, which is applied based on the given condition value
#[inline]
pub fn position(mut self, position: [f32; 2], condition: imgui::Condition) -> Self {
// NB: Builder API consumes window, so we have to move it.
self.window = self.window.position(position, condition);
self
}
/// Sets the window size, which is applied based on the given condition value
#[inline]
pub fn size(mut self, size: [f32; 2], condition: imgui::Condition) -> Self {
// NB: Builder API consumes window, so we have to move it.
self.window = self.window.size(size, condition);
self
}
/// Creates a window and runs a closure to construct the contents.
///
/// Note: the closure is not called if no window content is visible (e.g. window is collapsed
/// or fully clipped).
pub fn build<F>(self, ui: &imgui::Ui, f: F)
where F: FnOnce(WindowScopedUi)
{
if let Some(window) = self.begin(ui) {
let win_helper = WindowScopedUi::new(&ui);
f(win_helper);
window.end(ui);
}
}
/// Creates a window and starts appending to it.
///
/// Returns `Some(WindowToken)` if the window is visible. After content has been
/// rendered, the token must be ended by calling `.end()`.
///
/// Returns `None` if the window is not visible and no content should be rendered.
#[must_use]
pub fn begin(self, ui: &imgui::Ui) -> Option<imgui::WindowToken> {
self.window.begin(ui)
}
}
/// State for a mouse mapped to the dimensions of an `Imgui::Window`.
#[derive(Debug, Clone)]
pub struct CompleteMouseState {
// Cursor position
pub cursor_x: f32,
pub cursor_y: f32,
// Scroll wheel velocity (typically just '0', '1', or '-1').
pub scroll_wheel: f32,
pub scroll_wheel_h: f32,
// Buttons
pub left_button_down: bool,
pub right_button_down: bool,
pub middle_button_down: bool,
pub extra_button_1_down: bool,
pub extra_button_2_down: bool,
}
/// Exposes some reusable calculations on top of `imgui::Ui` in the context of window usage.
pub struct WindowScopedUi<'a> {
ui: &'a imgui::Ui<'a>,
}
impl <'a> WindowScopedUi<'a> {
pub fn new(ui: &'a imgui::Ui) -> Self {
Self {
ui
}
}
/// Returns true if the current window is hovered
#[inline]
pub fn is_window_hovered(&self) -> bool {
self.ui.is_window_hovered()
}
/// Return the cursor position within the window.
/// None is returned if the window isn't hovered, or the mouse is logically not within the drawn
/// texture area.
pub fn get_cursor(&self) -> Option<[f32; 2]> {
if !self.is_window_hovered() {
return None;
}
// Global mouse coordinate
let mouse_pos = self.ui.io().mouse_pos;
// Anchor point of the window's top left corner
let window_pos = self.ui.window_pos();
// Start of textured data (padding offset created by title and window padding).
let cursor_pos = self.ui.cursor_pos();
let x = mouse_pos[0] - window_pos[0] - cursor_pos[0];
let y = mouse_pos[1] - window_pos[1] - cursor_pos[1];
if x.is_sign_negative() || y.is_sign_negative() {
return None; // Outside of bounds (eg. cursor within title area)
}
Some([x, y])
}
/// Return the mouse scroll state. If the mouse is not hovering our window, None is returned.
pub fn get_mouse_wheel(&self) -> Option<f32> {
if self.get_cursor().is_none() {
return None;
}
Some(self.ui.io().mouse_wheel)
}
/// Mouse buttons: 0=left, 1=right, 2=middle + extras
pub fn get_mouse_down(&self) -> Option<[bool; 5]> {
if self.get_cursor().is_none() {
return None;
}
Some(self.ui.io().mouse_down)
}
/// Get the current mouse state
/// If the cursor isn't within the window, None is returned.
pub fn get_window_bounded_mouse_state(&self) -> Option<CompleteMouseState> {
self.get_cursor()
.map(|cursor| {
let io = self.ui.io();
let mouse = io.mouse_down;
CompleteMouseState {
cursor_x: cursor[0],
cursor_y: cursor[1],
scroll_wheel: io.mouse_wheel,
scroll_wheel_h: io.mouse_wheel_h,
left_button_down: mouse[0],
right_button_down: mouse[1],
middle_button_down: mouse[2],
extra_button_1_down: mouse[3],
extra_button_2_down: mouse[4],
}
})
}
}
+3
View File
@@ -0,0 +1,3 @@
//! This module will contain all the Imgui, SDL, mouse controls, etc. code
pub mod enhanced_window;
pub mod mouse_camera_arcball;
+214
View File
@@ -0,0 +1,214 @@
use arcball::ArcballCamera;
use cgmath::Vector3;
use cgmath::{Matrix4, Vector2};
use crate::gui::enhanced_window::CompleteMouseState;
use sdl2::event::Event;
use sdl2::mouse::MouseButton;
const MIN_ZOOM : f32 = 1.0;
const MAX_ZOOM : f32 = 120.0;
const DEFAULT_ZOOM: f32 = 65.0;
// TODO: Enable camera resizing (or change of texture to differently sized texture)
/// Wrap a camera arcball for use with SDL
pub struct MouseCameraArcball {
// Arcball camera state
arcball: ArcballCamera<f32>,
// Dimensions of the window
window_width: u32,
window_height: u32,
// Last mouse state
left_mouse_active: bool,
right_mouse_active: bool,
last_x: f32,
last_y: f32,
last_zoom: f32,
animation_counter: f32,
}
impl MouseCameraArcball {
/// CTOR
pub fn new(window_width: u32, window_height: u32) -> Self {
let center = Vector3::new(0.0, 0.0, 0.0);
let zoom_speed = 1.0;
let screen = [window_width as f32, window_height as f32];
let mut arcball = ArcballCamera::new(center, zoom_speed, screen);
// TODO: This should be cleaned up to use pure math rather than stupid experimentally determined fixes
arcball.zoom(-8.0, 0.16);
let mut prev = Vector2::new(window_width as f32, (window_height/2) as f32);
for i in 0 .. window_width / 2 {
let pos = Vector2::new(prev.x - 1.0, (window_height/2) as f32);
arcball.rotate(prev, pos);
prev = pos.clone();
}
for i in 0..600 {
let delta = Vector2::new(0.0, 1.0);
arcball.pan(delta, 0.16);
}
Self {
arcball,
window_width,
window_height,
left_mouse_active: false,
right_mouse_active: false,
last_x: 0.0,
last_y: 0.0,
last_zoom: DEFAULT_ZOOM,
animation_counter: 0.0,
}
}
/// Process an SDL event.
/// This is a bad idea in a multi-window Imgui setup since the SDL coordinates and events take up
/// the entire process window and may be sequestered on a per-window level. That said, this is
/// reasonable for single-window apps.
pub fn process_event(&mut self, event: &Event) {
match event {
Event::MouseWheel { timestamp: _, window_id: _, which: _, x: _, y, direction: _ } => {
//println!("Mouse wheel event.");
let y = *y as f32;
self.mouse_wheel(y);
},
Event::MouseButtonDown { timestamp: _, window_id: _, which: _, mouse_btn, clicks: _, x: _, y: _ } => {
//println!("Mouse button down event.");
self.mouse_button_press(mouse_btn, true);
},
Event::MouseButtonUp { timestamp: _, window_id: _, which: _, mouse_btn, clicks: _, x: _, y: _ } => {
//println!("Mouse button up event.");
self.mouse_button_press(mouse_btn, false);
},
Event::MouseMotion { timestamp: _, window_id: _, which: _, mousestate: _, x, y, xrel: _, yrel: _ } => {
let x = *x as f32;
let y = *y as f32;
self.mouse_motion(x, y);
self.last_x = x;
self.last_y = y;
},
_ => {},
}
}
/// Process Imgui window-scoped mouse state.
/// Since we don't process individual events, we set all of the internal state at once.
pub fn process_mouse_state(&mut self, mouse_state: CompleteMouseState) {
// TODO: Cleanup
self.right_mouse_active = mouse_state.right_button_down;
self.left_mouse_active = mouse_state.left_button_down;
self.mouse_motion(mouse_state.cursor_x, mouse_state.cursor_y);
self.mouse_wheel(mouse_state.scroll_wheel);
self.last_x = mouse_state.cursor_x;
self.last_y = mouse_state.cursor_y;
}
/// Get the view matrix
pub fn get_view_matrix(&self) -> Matrix4<f32> {
self.arcball.get_mat4()
}
/// Get the perspective matrix
pub fn get_perspective_matrix(&self) -> Matrix4<f32> {
let zoom = self.last_zoom;
let y_fov = zoom.to_radians();
let aspect = self.window_width as f32 / self.window_height as f32;
let n = 0.1f32;
let f = 1000.0f32;
// From linmath.h included in Microsoft's k4a open source code
let a = 1.0f32 / (y_fov / 2.0).tan();
// NB: This is column-major, which is dumb.
Matrix4::new(
// Column 0
a / aspect,
0.0,
0.0,
0.0,
// Column 1
0.0,
a,
0.0,
0.0,
// Column 2
0.0,
0.0,
-((f + n) / (f - n)),
-((2.0 * f * n) / (f - n)),
// Column 3
0.0,
0.0,
-1.0, // NB: This is not zero
0.0,
)
}
pub fn animate(&mut self) {
let x = self.animation_counter.sin();
let y = self.animation_counter.cos();
self.animation_counter.sin();
let mouse_delta = Vector2::new(x * 4.0, y * 4.0);
self.arcball.pan(mouse_delta, 0.16);
let x = self.animation_counter.sin();
if x > 0.0 {
let y = 1.0;
let prev = Vector2::new(0.0, 0.0);
let cur = Vector2::new(y, y);
self.arcball.rotate(prev, cur);
} else {
let y = 1.0;
let prev = Vector2::new(y, y);
let cur = Vector2::new(0.0, 0.0);
self.arcball.rotate(prev, cur);
};
self.animation_counter += 0.05;
}
fn mouse_wheel(&mut self, y: f32) {
self.arcball.zoom(y, 0.16);
// NB: 'y' is a scroll amount, typically 1 or -1.
let zoom = (self.last_zoom + y)
.max(MIN_ZOOM)
.min(MAX_ZOOM);
self.last_zoom = zoom;
}
fn mouse_button_press(&mut self, mouse_button: &MouseButton, down: bool) {
match mouse_button {
MouseButton::Left => self.left_mouse_active = down,
MouseButton::Right => self.right_mouse_active = down,
_ => {},
}
}
fn mouse_motion(&mut self, x: f32, y: f32) {
if self.left_mouse_active {
let previous = Vector2::new(self.last_x, self.last_y);
let current = Vector2::new(x, y);
self.arcball.rotate(previous, current);
} else if self.right_mouse_active {
let mouse_delta = Vector2::new(
x - self.last_x,
self.last_y - y, // Invert scrolling
);
self.arcball.pan(mouse_delta, 0.16);
}
}
}
+159
View File
@@ -0,0 +1,159 @@
//! Format conversion
//! k4a frames, image-rs, opencv, OpenGL textures, etc.
use crate::point_cloud::pixel_structs::{BgraPixel, DepthPixel};
use glium::texture::RawImage2d;
use image::DynamicImage;
use image::GenericImage;
use image::ImageBuffer;
use image::ImageError;
use image::Rgba;
use image::RgbaImage;
use image::error::{ImageFormatHint, UnsupportedError};
use image::flat::{FlatSamples, SampleLayout};
use k4a_sys_temp as k4a_sys;
use kinect::Image;
use kinect::ImageFormat;
use std::mem::size_of;
use std::slice;
// https://docs.rs/image/0.23.2/image/flat/index.html
pub fn depth_to_image(image: &Image) -> Result<DynamicImage, ImageError> {
let len = image.get_size();
let samples = unsafe { slice::from_raw_parts(image.get_buffer(), len) };
let layout = SampleLayout::row_major_packed(
4,
image.get_width_pixels() as u32,
image.get_height_pixels() as u32);
let buffer = FlatSamples {
samples,
layout,
color_hint: None,
};
let view = buffer.as_view::<Rgba<u8>>()
.expect("view should work");
let img: RgbaImage = ImageBuffer::new(
image.get_width_pixels() as u32,
image.get_height_pixels() as u32);
let mut img = DynamicImage::ImageRgba8(img);
img.copy_from(&view, 0, 0).expect("Should be able to copy");
Ok(img)
}
// Not efficient, but good for debugging
pub fn k4a_image_to_rust_image_for_debug(image: &Image) -> Result<RgbaImage, ImageError> {
let width = image.get_width_pixels() as u32;
let height= image.get_height_pixels() as u32;
let image_buffer = image.get_buffer();
let image_format = image.get_format();
println!("k4a_image_to_rust: {}x{} (format={:?}", width, height, image_format);
let output_image = DynamicImage::new_rgba16(width, height);
let mut rgba_image = output_image.to_rgba();
let mut offset = 0;
match image_format {
ImageFormat::Depth16 => {
let typed_buffer = image_buffer as *const DepthPixel;
for y in 0 .. height {
for x in 0 .. width {
let pixel = unsafe { *typed_buffer.offset(offset) };
let scaled_pixel = pixel as f32 / std::u16::MAX as f32;
let scaled_pixel = (scaled_pixel * std::u8::MAX as f32) as u8;
rgba_image.put_pixel(x, y, Rgba([scaled_pixel, scaled_pixel, scaled_pixel, 255]));
offset += 1;
}
}
},
ImageFormat::ColorBgra32 => {
let typed_buffer = image_buffer as *const BgraPixel;
for y in 0 .. height {
for x in 0 .. width {
let pixel = unsafe { &*typed_buffer.offset(offset) };
// BGRA32: The fourth byte is the alpha channel and is unused in the Azure Kinect APIs.
rgba_image.put_pixel(x, y, Rgba([pixel.red, pixel.green, pixel.blue, 255]));
offset += 1;
}
}
},
ImageFormat::Custom => {
// Stride is image_width * bytes_per_pixel, so we solve for bytes_per_pixel
let stride = image.get_stride_bytes();
let image_width = image.get_width_pixels();
let bytes_per_pixel = stride / image_width;
// The only format we know how to represent is k4a_float2_t.
let known_size = size_of::<k4a_sys::k4a_float2_t>();
if bytes_per_pixel != known_size {
return Err(ImageError::Unsupported(UnsupportedError::from(ImageFormatHint::Unknown)))
}
let typed_buffer = image_buffer as *const k4a_sys::k4a_float2_t;
let mut max_x = -10000.0;
let mut max_y = -10000.0;
let mut min_x= 10000.0;
let mut min_y = 10000.0;
offset = 0;
for _ in 0 .. height {
for _ in 0 .. width {
unsafe {
let pixel = &*typed_buffer.offset(offset);
let xp = pixel.xy.x;
let yp = pixel.xy.y;
if xp < min_x {
min_x = xp;
}
if xp > max_x {
max_x = xp;
}
if yp < min_y {
min_y = yp;
}
if yp > max_y {
max_y = yp;
}
}
offset += 1;
}
}
//println!("Max x: {}", max_x);
//println!("Max y: {}", max_y);
//println!("Min x: {}", min_x);
//println!("Min y: {}", min_y);
offset = 0;
for y in 0 .. height {
for x in 0 .. width {
unsafe {
let pixel = &*typed_buffer.offset(offset);
let xp = pixel.xy.x;
let yp = pixel.xy.y;
let scaled_x = (xp - min_x) / (max_x - min_x);
let scaled_x = (255.0 * scaled_x) as u8;
let scaled_y = (yp - min_y) / (max_y - min_y);
let scaled_y = (255.0 * scaled_y) as u8;
rgba_image.put_pixel(x, y, Rgba([scaled_x, 0, scaled_y, 255]));
}
offset += 1;
}
}
},
_ => unimplemented!("conversion not implemented for: {:?}", image_format),
};
return Ok(rgba_image)
}
+19
View File
@@ -0,0 +1,19 @@
use clipboard::{ClipboardContext, ClipboardProvider};
use imgui::{ClipboardBackend, ImStr, ImString};
pub struct ClipboardSupport(ClipboardContext);
pub fn init() -> Option<ClipboardSupport> {
ClipboardContext::new()
.ok()
.map(|ctx| ClipboardSupport(ctx))
}
impl ClipboardBackend for ClipboardSupport {
fn get(&mut self) -> Option<ImString> {
self.0.get_contents().ok().map(|text| text.into())
}
fn set(&mut self, text: &ImStr) {
let _ = self.0.set_contents(text.to_str().to_owned());
}
}
+139
View File
@@ -0,0 +1,139 @@
use std::time::Instant;
use glium::{Display, Surface};
use glium::glutin;
use glium::glutin::event::{Event, WindowEvent};
use glium::glutin::event_loop::{ControlFlow, EventLoop};
use glium::glutin::window::WindowBuilder;
use imgui::{Context, FontConfig, FontSource, Ui};
use imgui_glium_renderer::Renderer;
use imgui_winit_support::{HiDpiMode, WinitPlatform};
mod clip;
pub struct System {
pub event_loop: EventLoop<()>,
pub display: glium::Display,
pub imgui: Context,
pub platform: WinitPlatform,
pub renderer: Renderer,
pub font_size: f32,
}
pub fn init(title: &str) -> System {
let title = match title.rfind('/') {
Some(idx) => title.split_at(idx + 1).1,
None => title,
};
let event_loop = EventLoop::new();
let context = glutin::ContextBuilder::new().with_vsync(true);
let builder = WindowBuilder::new()
.with_title(title.to_owned())
.with_inner_size(glutin::dpi::LogicalSize::new(1024f64, 768f64));
let display =
Display::new(builder, context, &event_loop).expect("Failed to initialize display");
let mut imgui = Context::create();
imgui.set_ini_filename(None);
if let Some(backend) = clip::init() {
imgui.set_clipboard_backend(Box::new(backend));
} else {
eprintln!("Failed to initialize clipboard");
}
let mut platform = WinitPlatform::init(&mut imgui);
{
let gl_window = display.gl_window();
let window = gl_window.window();
platform.attach_window(imgui.io_mut(), &window, HiDpiMode::Rounded);
}
// Load the OpenGL function pointers
gl::load_with(|symbol| display.gl_window().get_proc_address(symbol));
let hidpi_factor = platform.hidpi_factor();
let font_size = (13.0 * hidpi_factor) as f32;
imgui.fonts().add_font(&[
FontSource::DefaultFontData {
config: Some(FontConfig {
size_pixels: font_size,
..FontConfig::default()
}),
},
/*FontSource::TtfData {
data: include_bytes!("../../../resources/mplus-1p-regular.ttf"),
size_pixels: font_size,
config: Some(FontConfig {
rasterizer_multiply: 1.75,
glyph_ranges: FontGlyphRanges::japanese(),
..FontConfig::default()
}),
},*/
]);
imgui.io_mut().font_global_scale = (1.0 / hidpi_factor) as f32;
let renderer = Renderer::init(&mut imgui, &display).expect("Failed to initialize renderer");
System {
event_loop,
display,
imgui,
platform,
renderer,
font_size,
}
}
impl System {
pub fn main_loop<F: FnMut(&mut bool, &mut Ui) + 'static>(self, mut run_ui: F) {
let System {
event_loop,
display,
mut imgui,
mut platform,
mut renderer,
..
} = self;
let mut last_frame = Instant::now();
event_loop.run(move |event, _, control_flow| match event {
Event::NewEvents(_) => last_frame = imgui.io_mut().update_delta_time(last_frame),
Event::MainEventsCleared => {
let gl_window = display.gl_window();
platform
.prepare_frame(imgui.io_mut(), &gl_window.window())
.expect("Failed to prepare frame");
gl_window.window().request_redraw();
}
Event::RedrawRequested(_) => {
let mut ui = imgui.frame();
let mut run = true;
run_ui(&mut run, &mut ui);
if !run {
*control_flow = ControlFlow::Exit;
}
let gl_window = display.gl_window();
let mut target = display.draw();
target.clear_color_srgb(1.0, 1.0, 1.0, 1.0);
platform.prepare_render(&ui, gl_window.window());
let draw_data = ui.render();
renderer
.render(&mut target, draw_data)
.expect("Rendering failed");
target.finish().expect("Failed to swap buffers");
}
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => *control_flow = ControlFlow::Exit,
event => {
let gl_window = display.gl_window();
platform.handle_event(imgui.io_mut(), gl_window.window(), &event);
}
})
}
}
+132
View File
@@ -0,0 +1,132 @@
#![allow(warnings)]
#[macro_use] extern crate clap;
#[macro_use] extern crate glium;
#[macro_use] extern crate imgui;
#[macro_use] extern crate log;
extern crate anyhow;
extern crate arcball;
extern crate cgmath;
extern crate clipboard;
extern crate gl;
extern crate glfw;
extern crate glfw_sys;
extern crate image;
extern crate imgui_glium_renderer;
extern crate imgui_opengl_renderer;
extern crate imgui_sdl2;
extern crate imgui_winit_support;
extern crate libc;
extern crate rand;
extern crate sdl2;
extern crate tobj;
extern crate winit;
extern crate gltf;
use std::sync::Arc;
use std::{thread, env};
use anyhow::Result as AnyhowResult;
use anyhow::bail;
use kinect::{Device, Calibration};
use azure_kinect::sensor_control::capture_thread;
use azure_kinect::capture::multi_device_capturer::{MultiDeviceCapturer, start_capture_thread};
use azure_kinect::capture::fake_device_capturer::FakeDeviceCaptureProvider;
use azure_kinect::capture::device_capturer::CaptureProvider;
use clap::{Clap};
pub mod assets;
pub mod azure_kinect;
pub mod core_types;
pub mod files;
pub mod graphics_imgui;
pub mod gui;
pub mod image_debug;
pub mod imgui_support;
pub mod opengl;
pub mod point_cloud;
pub mod webcam;
const ENV_RUST_LOG : &'static str = "RUST_LOG";
const DEFAULT_RUST_LOG: &'static str = "debug,actix_web=info"; // NB: We don't use actix, but this is preserved for syntax ref
/// This is the serial number for the camera I have mounted to the utility wall.
const PRIMARY_DEVICE_SERIAL : &'static str = "000513594512";
/// This is a second device
const SECONDARY_DEVICE_SERIAL : &'static str = "000886694512";
#[derive(Clap, Debug)]
struct Opts {
#[clap(long, parse(try_from_str = true_or_false), default_value = "true")]
pub enable_cameras: bool,
#[clap(long, parse(try_from_str = true_or_false), default_value = "false")]
pub enable_webcam: bool,
#[clap(long, parse(try_from_str = true_or_false), default_value = "false")]
pub save_captures: bool,
}
fn true_or_false(s: &str) -> Result<bool, &'static str> {
match s {
"true" => Ok(true),
"false" => Ok(false),
_ => Err("expected `true` or `false`"),
}
}
/// Arguments to pass through the program
pub struct ProgramArgs {
pub save_captures: bool,
pub enable_webcam_output_writing: bool,
}
pub fn main() -> AnyhowResult<()> {
if env::var(ENV_RUST_LOG)
.as_ref()
.ok()
.is_none()
{
println!("Setting default logging level to \"{}\", override with env var {}.",
DEFAULT_RUST_LOG, ENV_RUST_LOG);
env::set_var(ENV_RUST_LOG, DEFAULT_RUST_LOG);
}
env_logger::init();
let opts = Opts::parse();
println!("opts: {:?}", opts);
// Validate args.
if opts.save_captures && !opts.enable_cameras {
bail!("Can't save captures without cameras enabled.");
}
let program_args = ProgramArgs {
enable_webcam_output_writing: opts.enable_webcam,
save_captures: opts.save_captures,
};
let capture_provider: Arc<dyn CaptureProvider> = if opts.enable_cameras {
let multi_device = MultiDeviceCapturer::new(2, Some(1))
.expect("multi-device create");
multi_device.start_cameras().expect("start cameras");
let capture_provider = multi_device.get_sync_capture_provider();
thread::spawn(move || start_capture_thread(multi_device));
capture_provider
} else {
Arc::new(FakeDeviceCaptureProvider::new().unwrap())
};
//let calibration = capture_provider.get_calibration().clone();
//calibration.debug_print();
graphics_imgui::run(capture_provider, program_args)?;
Ok(())
}
+39
View File
@@ -0,0 +1,39 @@
pub mod zeromq;
pub use anyhow::Result as AnyhowResult;
use anyhow::anyhow;
use crate::zeromq::xy_table::create_xy_table;
use k4a_sys_temp as k4a_sys;
use kinect::{Device, DeviceConfiguration};
use std::thread;
use std::time::Duration;
use crate::zeromq::cpu_calculate_point_cloud::calculate_point_cloud;
fn main() -> AnyhowResult<()> {
let device = Device::open(0)?;
let mut config = DeviceConfiguration::init_disable_all();
config.0.depth_mode = k4a_sys::k4a_depth_mode_t_K4A_DEPTH_MODE_WFOV_2X2BINNED;
config.0.camera_fps = k4a_sys::k4a_fps_t_K4A_FRAMES_PER_SECOND_30;
let calibration = device.get_calibration(config.0.depth_mode, config.0.color_resolution)?;
let xy_table = create_xy_table(&calibration)?;
device.start_cameras(&config)?;
loop {
println!("Get capture");
let capture = device.get_capture(500)?;
let depth_image = capture.get_depth_image()
.ok_or(anyhow!("capture not present"))?;
let point_cloud = calculate_point_cloud(&depth_image, &xy_table)?;
println!("Number points: {}", point_cloud.point_count);
}
Ok(())
}
+283
View File
@@ -0,0 +1,283 @@
//! Generate random points to send over ZeroMQ to a consumer
#[allow(unused_imports)]
mod zeromq;
use anyhow::Result as AnyhowResult;
use anyhow::anyhow;
use byteorder::{WriteBytesExt, LittleEndian, BigEndian};
use k4a_sys_temp as k4a_sys;
use kinect::{Device, DeviceConfiguration, Image};
use std::io::Write;
use std::thread;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use zeromq::cpu_calculate_point_cloud::PointCloudResult;
use zeromq::cpu_calculate_point_cloud::calculate_point_cloud2;
use zeromq::cpu_calculate_point_cloud::calculate_point_cloud3;
use zeromq::color::Color;
use zeromq::cpu_transformation::DepthTransformer;
use zeromq::point::Point;
use zeromq::xy_table::create_xy_table;
use zeromq::xy_table::create_xy_table_from_depth_calibration;
use zeromq::xy_table::create_xy_table_from_color_calibration;
use zmq::{Error, Socket, Context, DONTWAIT};
const SOCKET_ADDRESS : &'static str = "tcp://127.0.0.1:8888";
const DATA_LENGTH_COMMAND : u32 = 1; // Denotes the command that sends the data length
const POINT_DATA_BEGIN_PAYLOAD_COMMAND : u32 = 2; // Denotes the command that sends the variable-length data
const POINT_DATA_CONTINUE_PAYLOAD_COMMAND : u32 = 3; // Denotes the command that sends the variable-length data
/// The maximum number of points to send per ZeroMQ "packet".
/// This is also defined in C++, so it needs to be adjusted in multiple places.
const MAX_SEND_POINTS_PER_PACKET : usize = 3000;
enum MessagingState {
Sending_DataLength,
//Receiving_DataLengthAck,
Sending_PointDataBegin,
//Receiving_PointDataAck,
Sending_PointDataContinue,
GrabPointCloud,
}
fn main() -> AnyhowResult<()> {
let device = Device::open(0)?;
let mut config = DeviceConfiguration::init_disable_all();
config.0.camera_fps = k4a_sys::k4a_fps_t_K4A_FRAMES_PER_SECOND_15;
//config.0.camera_fps = k4a_sys::k4a_fps_t_K4A_FRAMES_PER_SECOND_30;
// TODO: Pick correct binning + FOV.
// NB: Similar to NFOV_UNBINNED, but less dense (faster).
// Not used by any program I'm aware of.
//config.0.depth_mode = k4a_sys::k4a_depth_mode_t_K4A_DEPTH_MODE_NFOV_2X2BINNED; // 320x228
// NB: NFOV_UNBINNED was used by original Rust experiment.
// This appears to be much denser, and is a tighter angle. Slow on CPU.
//config.0.depth_mode = k4a_sys::k4a_depth_mode_t_K4A_DEPTH_MODE_NFOV_UNBINNED; // looks good w/ 720P
// NB: WFOV_2X2BINNED was used by the original 'cloudcam_zeromq'.
// It's less dense, and wider. Much more performant. Fast on CPU.
//config.0.depth_mode = k4a_sys::k4a_depth_mode_t_K4A_DEPTH_MODE_WFOV_2X2BINNED; // DISTORTED w/ 720P
// Not used by anything, AFAICT.
// I got this working in Rust + Color Camera
config.0.depth_mode = k4a_sys::k4a_depth_mode_t_K4A_DEPTH_MODE_WFOV_UNBINNED; // 1024x1024 (looks good w/ 720P)
config.0.color_format = k4a_sys::k4a_image_format_t_K4A_IMAGE_FORMAT_COLOR_BGRA32;
config.0.color_resolution = k4a_sys::k4a_color_resolution_t_K4A_COLOR_RESOLUTION_720P; // 1280x721
//config.0.color_resolution = k4a_sys::k4a_color_resolution_t_K4A_COLOR_RESOLUTION_1080P; // 1920x1080 (this gets truncated.)
//config.0.color_resolution = k4a_sys::k4a_color_resolution_t_K4A_COLOR_RESOLUTION_2160P; // 4K, what the original program did
let calibration = device.get_calibration(config.0.depth_mode, config.0.color_resolution)?;
//let xy_table = create_xy_table_from_depth_calibration(&calibration)?;
let xy_table = create_xy_table_from_color_calibration(&calibration)?;
let transformer = DepthTransformer::new(&calibration);
device.start_cameras(&config)?;
// ===============================================================
let context = zmq::Context::new();
let mut socket = context.socket(zmq::PUSH).unwrap();
//let mut socket = context.socket(zmq::REQ).unwrap();
//socket.bind(SOCKET_ADDRESS).unwrap();
socket.connect(SOCKET_ADDRESS).unwrap();
let mut messaging_state = MessagingState::Sending_DataLength;
let mut color = Color::get_time_based_color();
let mut points : Vec<Point> = Vec::new();
loop {
let maybe_points = get_point_cloud(&device, &xy_table, color, &transformer);
points = match maybe_points {
Err(e) => {
println!("Error: {:?}", e);
thread::sleep(Duration::from_millis(5000));
continue;
},
Ok(points) => points,
};
break;
};
if !points.is_empty() {
//print_pointcloud_maxima(&points);
} else {
println!("> No points?");
}
let mut packet_number = 0;
loop {
match messaging_state {
MessagingState::Sending_DataLength => {
//println!("Sending DATA LENGTH: {}", points.len());
//let message = encode_data_length(&points);
//send_message(&socket, &message)?;
messaging_state = MessagingState::Sending_PointDataBegin;
},
MessagingState::Sending_PointDataBegin => {
//println!("Begin Sending PCD (points: {})", points.len());
let message = encode_point_data(&mut points, true);
send_message(&socket, &message)?;
if points.is_empty() {
messaging_state = MessagingState::GrabPointCloud;
} else {
messaging_state = MessagingState::Sending_PointDataContinue;
}
},
MessagingState::Sending_PointDataContinue => {
// NB: Unfortunately the C++ program has difficulty with all points.
if packet_number > 2000000 {
println!("Packet elapsed");
packet_number = 0;
messaging_state = MessagingState::GrabPointCloud;
continue;
}
//println!("Continue Sending PCD (points: {})", points.len());
let message = encode_point_data(&mut points, false);
send_message(&socket, &message)?;
if points.is_empty() {
messaging_state = MessagingState::GrabPointCloud;
} else {
messaging_state = MessagingState::Sending_PointDataContinue;
}
packet_number += 1;
},
MessagingState::GrabPointCloud => {
//println!("Grabbing another frame...");
color = Color::get_time_based_color();
points = get_point_cloud(&device, &xy_table, color, &transformer)?;
if !points.is_empty() {
//print_pointcloud_maxima(&points);
} else {
println!("No points?");
}
messaging_state = MessagingState::Sending_DataLength;
continue;
},
}
}
Ok(())
}
fn get_point_cloud(device: &Device, xy_table: &Image, color: Color, transformer: &DepthTransformer)
-> AnyhowResult<Vec<Point>>
{
let capture = device.get_capture(5000)?;
let depth_image = capture.get_depth_image()
.ok_or(anyhow!("depth image not present"))?;
let color_image = capture.get_color_image()
.ok_or(anyhow!("color image not present"))?;
let depth_image2 = transformer.transform(&depth_image)?;
let mut points =
calculate_point_cloud3(
&depth_image2,
&xy_table,
//color,
&color_image
)?;
//println!("Points: {}", points.len());
Ok(points)
}
/// Returns the fixed size data length command.
fn encode_data_length(points: &Vec<Point>) -> Vec<u8> {
let mut buf = Vec::with_capacity(8);
buf.write_u32::<LittleEndian>(DATA_LENGTH_COMMAND);
buf.write_u32::<LittleEndian>(points.len() as u32);
buf
}
/// Returns a variable length point data payload.
fn encode_point_data(points: &mut Vec<Point>, is_beginning: bool) -> Vec<u8> {
let point_bytes = Point::size_bytes() * MAX_SEND_POINTS_PER_PACKET;
let mut buf = Vec::with_capacity(4 + point_bytes);
if is_beginning {
//println!("begin");
buf.write_u32::<LittleEndian>(POINT_DATA_BEGIN_PAYLOAD_COMMAND); // COMMAND #
} else {
//println!("continue");
buf.write_u32::<LittleEndian>(POINT_DATA_CONTINUE_PAYLOAD_COMMAND); // COMMAND #
}
let subset = if points.len() > MAX_SEND_POINTS_PER_PACKET {
points.drain(0..MAX_SEND_POINTS_PER_PACKET).collect::<Vec<Point>>()
} else {
points.drain(0..points.len()).collect::<Vec<Point>>()
};
buf.write_u32::<LittleEndian>(subset.len() as u32); // LENGTH
for point in subset {
let bytes = point.to_bytes();
buf.write_all(&bytes);
}
buf
}
/// Send data over the socket
fn send_message(socket: &Socket, data_bytes: &Vec<u8>) -> AnyhowResult<()> {
socket.send(&data_bytes, 0)?;
Ok(())
}
/// Receive ack from server
fn receive_ack(socket: &Socket) -> AnyhowResult<()> {
let _result = socket.recv_bytes(DONTWAIT)?;
Ok(())
}
fn reconnect_socket(context: &Context, socket: Socket, address: &str) -> Socket {
//println!("[reconnect] Creating new socket...");
let mut socket = match context.socket(zmq::REQ) {
Ok(s) => {
//println!("New socket created.");
s
},
Err(e) => {
//println!("Error creating new socket: {:?}", e);
return socket;
},
};
//println!("Connecting new socket...");
match socket.connect(address) {
Ok(_) => {
//println!("New socket connected.");
},
Err(err) => {
//println!("Error connecting new socket: {:?}", err);
},
}
return socket;
}
+43
View File
@@ -0,0 +1,43 @@
use std::ffi::CString;
use std::ptr;
use std::str;
use gl;
use gl::types::*;
pub fn compile_shader(src: &str, shader_type: GLenum) -> GLuint {
let shader;
unsafe {
shader = gl::CreateShader(shader_type);
// Attempt to compile the shader
let c_str = CString::new(src.as_bytes()).unwrap();
gl::ShaderSource(shader, 1, &c_str.as_ptr(), ptr::null());
gl::CompileShader(shader);
// Get the compile status
let mut status = gl::FALSE as GLint;
gl::GetShaderiv(shader, gl::COMPILE_STATUS, &mut status);
// Fail on error
if status != (gl::TRUE as GLint) {
let mut len = 0;
gl::GetShaderiv(shader, gl::INFO_LOG_LENGTH, &mut len);
let mut buf = Vec::with_capacity(len as usize);
buf.set_len((len as usize) - 1); // subtract 1 to skip the trailing null character
gl::GetShaderInfoLog(
shader,
len,
ptr::null_mut(),
buf.as_mut_ptr() as *mut GLchar,
);
panic!(
"{}",
str::from_utf8(&buf)
.ok()
.expect("ShaderInfoLog not valid utf8")
);
}
}
shader
}
+34
View File
@@ -0,0 +1,34 @@
//! Add an OpenGL debugging callback.
use std::ffi::CStr;
use std::os::raw::c_void;
use std::ptr::null;
use gl;
use gl::types::*;
extern "system" fn glDebugOutput(
_source: GLenum,
_gltype: GLenum,
id: GLuint,
_severity: GLenum,
_length: GLsizei,
message: *const GLchar,
_userParam: *mut c_void)
{
let message = unsafe { CStr::from_ptr(message) };
//println!(">>> [GL DEBUG] id: {:?} message: {:?} source: {:?} type: {:?} severity: {:?}",
// id, message, source, gltype, severity);
println!(">>> [GL DEBUG] id: {:?} message: {:?}", id, message);
}
pub fn enable_opengl_debugging() {
unsafe {
// Set debugging. This can be expensive, so we should be able to trigger it with a flag.
gl::Enable(gl::DEBUG_OUTPUT);
gl::Enable(gl::DEBUG_OUTPUT_SYNCHRONOUS);
gl::DebugMessageCallback(Some(glDebugOutput), null());
// Opt into everything.
gl::DebugMessageControl(gl::DONT_CARE, gl::DONT_CARE, gl::DONT_CARE, 0, null(), gl::TRUE);
}
}
+35
View File
@@ -0,0 +1,35 @@
use gl::types::*;
use std::ptr;
use std::str;
pub fn link_shader_program(program: GLuint, vertex_shader: GLuint, fragment_shader: GLuint) -> GLuint {
unsafe {
gl::AttachShader(program, vertex_shader);
gl::AttachShader(program, fragment_shader);
gl::LinkProgram(program);
// Get the link status
let mut status = gl::FALSE as GLint;
gl::GetProgramiv(program, gl::LINK_STATUS, &mut status);
// Fail on error
if status != (gl::TRUE as GLint) {
let mut len: GLint = 0;
gl::GetProgramiv(program, gl::INFO_LOG_LENGTH, &mut len);
let mut buf = Vec::with_capacity(len as usize);
buf.set_len((len as usize) - 1); // subtract 1 to skip the trailing null character
gl::GetProgramInfoLog(
program,
len,
ptr::null_mut(),
buf.as_mut_ptr() as *mut GLchar,
);
panic!(
"{}",
str::from_utf8(&buf)
.ok()
.expect("ProgramInfoLog not valid utf8")
);
}
program
}
}
+53
View File
@@ -0,0 +1,53 @@
use gl::types::*;
use gl;
use image;
use std::ffi::c_void;
use std::path::Path;
/// Load a texture from a file.
pub fn load_texture_from_file(filename: &str) -> GLuint {
let mut texture_id = 0;
unsafe {
gl::GenTextures(1, &mut texture_id);
gl::BindTexture(gl::TEXTURE_2D, texture_id);
// Set up filtering params and other texture attributes
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::LINEAR as i32);
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::LINEAR as i32);
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, gl::REPEAT as i32);
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, gl::REPEAT as i32);
}
let img = image::open(&Path::new(filename))
.expect("failed to load")
.to_rgba();
let width = img.dimensions().0 as i32;
let height = img.dimensions().1 as i32;
let data = img.into_raw();
println!("Loaded image: {}x{}", width, height);
unsafe {
gl::PixelStorei(gl::UNPACK_ROW_LENGTH, 0);
gl::TexImage2D(
gl::TEXTURE_2D,
0,
gl::RGBA as i32,
//format as i32,
width,
height,
0,
gl::RGBA,
//format,
gl::UNSIGNED_BYTE,
&data[0] as *const u8 as *const c_void,
);
//gl::BindTexture(gl::TEXTURE_2D, 0);
}
println!("Returning texture id: {}", texture_id);
return texture_id;
}
+77
View File
@@ -0,0 +1,77 @@
//! Useful matrix functions
pub const fn translation_matrix_4x4_flat(x: f32, y: f32, z: f32) -> [f32; 16] {
[
1.0, 0.0, 0.0, x,
0.0, 1.0, 0.0, y,
0.0, 0.0, 1.0, z,
0.0, 0.0, 0.0, 1.0,
]
}
pub const fn identity_matrix_4x4() -> [[f32; 4]; 4] {
[
[1.0, 0.0, 0.0, 0.0],
[0.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
[0.0, 0.0, 0.0, 1.0],
]
}
pub const fn identity_matrix_4x4_flat() -> [f32; 16] {
[
1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0,
]
}
pub const fn zero_matrix_4x4_flat() -> [f32; 16] {
return [
0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0,
];
}
/// This is the view matrix k4aviewer starts with
pub const fn initial_view_matrix_4x4() -> [[f32; 4]; 4] {
[
[-1.0, 0.0, 8.74228e-08, 0.0],
[0.0, 1.0, 0.0, 0.0],
[-8.74228e-08, 0.0, -1.0, 0.0],
[2.62268e-07, 1.0, -5.0, 1.0],
]
}
/// This is the view matrix k4aviewer starts with
pub const fn initial_view_matrix_4x4_flat() -> [f32; 16] {
[
-1.0, 0.0, 8.74228e-08, 0.0,
0.0, 1.0, 0.0, 0.0,
-8.74228e-08, 0.0, -1.0, 0.0,
2.62268e-07, 1.0, -5.0, 1.0,
]
}
/// This is the projection matrix k4aviewer starts with
pub const fn initial_projection_matrix_4x4() -> [[f32; 4]; 4] {
[
[1.41272, 0.0, 0.0, 0.0],
[0.0, 1.56969, 0.0, 0.0],
[0.0, 0.0, -1.002, -1.0],
[0.0, 0.0, -0.2002, 0.0],
]
}
/// This is the projection matrix k4aviewer starts with
pub const fn initial_projection_matrix_4x4_flat() -> [f32; 16] {
[
1.41272, 0.0, 0.0, 0.0,
0.0, 1.56969, 0.0, 0.0,
0.0, 0.0, -1.002, -1.0,
0.0, 0.0, -0.2002, 0.0,
]
}
+7
View File
@@ -0,0 +1,7 @@
pub mod compile_shader;
pub mod debug;
pub mod link_program;
pub mod load_texture_from_file;
pub mod matrices;
pub mod rebinder;
pub mod wrapper;
+75
View File
@@ -0,0 +1,75 @@
use gl;
use gl::types::*;
/// Rebinder records the current state of OpenGL bindings so that it can restore them later.
/// I'm writing this because the code I wrote/adapted is unfortunately a mess.
pub struct Rebinder {
texture_id: GLint,
array_buffer_id: GLint,
element_array_buffer_id: GLint,
pixel_unpack_buffer_id: GLint,
renderbuffer_binding: GLint,
vertex_array_binding: GLint,
draw_framebuffer_binding: GLint,
read_framebuffer_binding: GLint,
current_program: GLint,
active_texture: GLint,
is_depth_test: u8,
}
impl Rebinder {
/// Capture a current snapshot of the bindings.
pub fn snapshot() -> Self {
let mut bindings = Self {
texture_id: 0,
array_buffer_id: 0,
element_array_buffer_id: 0,
pixel_unpack_buffer_id: 0,
renderbuffer_binding: 0,
vertex_array_binding: 0,
draw_framebuffer_binding: 0,
read_framebuffer_binding: 0,
current_program: 0,
active_texture: 0,
is_depth_test: 0,
};
unsafe {
gl::GetIntegerv(gl::TEXTURE_BINDING_2D, &mut bindings.texture_id);
gl::GetIntegerv(gl::ARRAY_BUFFER_BINDING, &mut bindings.array_buffer_id);
gl::GetIntegerv(gl::ELEMENT_ARRAY_BUFFER_BINDING, &mut bindings.element_array_buffer_id);
gl::GetIntegerv(gl::PIXEL_UNPACK_BUFFER_BINDING, &mut bindings.pixel_unpack_buffer_id);
gl::GetIntegerv(gl::RENDERBUFFER_BINDING, &mut bindings.renderbuffer_binding);
gl::GetIntegerv(gl::VERTEX_ARRAY_BINDING, &mut bindings.vertex_array_binding);
gl::GetIntegerv(gl::DRAW_FRAMEBUFFER_BINDING, &mut bindings.draw_framebuffer_binding);
gl::GetIntegerv(gl::READ_FRAMEBUFFER_BINDING, &mut bindings.read_framebuffer_binding);
gl::GetIntegerv(gl::CURRENT_PROGRAM, &mut bindings.current_program);
gl::GetIntegerv(gl::ACTIVE_TEXTURE, &mut bindings.active_texture);
gl::GetBooleanv(gl::DEPTH_TEST, &mut bindings.is_depth_test);
}
bindings
}
/// Restore the snapshot of the bindings.
/// This consumes the rebinder.
pub fn restore(self) {
unsafe {
gl::BindTexture(gl::TEXTURE_2D, self.texture_id as u32);
gl::BindBuffer(gl::ARRAY_BUFFER, self.array_buffer_id as u32);
gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, self.element_array_buffer_id as u32);
gl::BindBuffer(gl::PIXEL_UNPACK_BUFFER, self.pixel_unpack_buffer_id as u32);
gl::BindRenderbuffer(gl::RENDERBUFFER, self.renderbuffer_binding as u32);
gl::BindVertexArray(self.vertex_array_binding as u32);
gl::BindFramebuffer(gl::DRAW_FRAMEBUFFER, self.draw_framebuffer_binding as u32);
gl::BindFramebuffer(gl::READ_FRAMEBUFFER, self.read_framebuffer_binding as u32);
gl::UseProgram(self.current_program as u32);
gl::ActiveTexture(self.active_texture as u32);
if self.is_depth_test == 0 {
gl::Disable(gl::DEPTH_TEST);
} else {
gl::Enable(gl::DEPTH_TEST);
}
}
}
}
+49
View File
@@ -0,0 +1,49 @@
use anyhow::Result as AnyhowResult;
use anyhow::bail;
use gl::types::*;
use std::os::raw::c_char;
use std::ffi::CString;
/// A typesafe handle on attribute IDs with convenience methods.
///
/// Unlike "Uniforms", which are global in GLSL, "Attributes" are applied on a per-vertex basis.
/// They can be used to convey typical information (eg. normals) or completely custom information
/// (eg. tangents or whatever).
///
/// https://www.opengl.org/sdk/docs/tutorials/ClockworkCoders/attributes.php
///
#[derive(Clone, Debug, Copy)]
pub struct Attribute {
id: GLint,
}
impl Attribute {
pub fn lookup(identifier: &str, opengl_program_id: GLuint) -> AnyhowResult<Self> {
let id_cstr : CString = CString::new(identifier)?;
let id_cstr_ptr : *const c_char = id_cstr.as_ptr() as *const c_char;
let mut loc = -1;
unsafe {
loc = gl::GetAttribLocation(opengl_program_id, id_cstr_ptr);
}
if loc < 0 {
bail!("Couldn't find attribute named '{}' in OpenGL program {}.", identifier, opengl_program_id);
}
Ok(Self {
id: loc,
})
}
pub fn id(&self) -> GLint {
self.id
}
/*pub fn set_uint(&self, value: GLuint) {
let loc = self.id() as GLuint;
unsafe {
gl::VertexAttribI1ui(loc, value);
}
}*/
}
+64
View File
@@ -0,0 +1,64 @@
//! These wrappers make it easier to use certain OpenGL types.
//! This draws inspiration from some of the code in Microsoft's MIT-licensed libk4a,
//! particularly the code in `openglhelpers.h`.
use gl::types::*;
/// A slightly more convenient OpenGL Buffer type
#[derive(Clone, Debug)]
pub struct Buffer {
id: GLuint,
}
impl Buffer {
pub fn new() -> Self {
return Self {
id: 0,
}
}
pub fn new_initialized() -> Self {
let mut new_object= Self::new();
new_object.init();
new_object
}
pub fn is_initialized(&self) -> bool {
self.id != 0
}
pub fn id(&self) -> GLuint {
self.id
}
pub fn init(&mut self) {
self.reset();
self.gen();
}
pub fn reset(&mut self) {
if self.is_initialized() {
self.delete();
self.id = 0;
}
}
pub fn bind_as_array_buffer(&self) {
unsafe {
gl::BindBuffer(gl::ARRAY_BUFFER, self.id);
}
}
pub fn gen(&mut self) {
unsafe {
gl::GenBuffers(1, &mut self.id);
}
}
pub fn delete(&mut self) {
unsafe {
gl::DeleteBuffers(1, &mut self.id);
}
}
}
+6
View File
@@ -0,0 +1,6 @@
pub mod attribute;
pub mod buffer;
pub mod other_misc_wrapper;
pub mod texture;
pub mod uniform;
pub mod vertex_array;
+206
View File
@@ -0,0 +1,206 @@
//! These wrappers make it easier to use certain OpenGL types.
//! This draws inspiration from some of the code in Microsoft's MIT-licensed libk4a,
//! particularly the code in `openglhelpers.h`.
use std::fmt::Formatter;
use std::ptr::null;
use gl;
use gl::types::*;
/// A slightly more convenient OpenGL Framebuffer type
#[derive(Clone, Debug)]
pub struct Framebuffer {
id: GLuint,
}
impl Framebuffer {
pub fn new() -> Self {
return Self {
id: 0,
}
}
pub fn new_initialized() -> Self {
let mut new_object= Self::new();
new_object.init();
new_object
}
pub fn is_initialized(&self) -> bool {
self.id != 0
}
pub fn id(&self) -> GLuint {
self.id
}
pub fn init(&mut self) {
self.reset();
self.gen();
}
pub fn reset(&mut self) {
if self.is_initialized() {
self.delete();
self.id = 0;
}
}
pub fn gen(&mut self) {
unsafe {
gl::GenFramebuffers(1, &mut self.id);
}
}
pub fn delete(&mut self) {
unsafe {
gl::DeleteFramebuffers(1, &mut self.id);
}
}
}
/// A slightly more convenient OpenGL Renderbuffer type
#[derive(Clone, Debug)]
pub struct Renderbuffer {
id: GLuint,
}
impl Renderbuffer {
pub fn new() -> Self {
return Self {
id: 0,
}
}
pub fn new_initialized() -> Self {
let mut new_object= Self::new();
new_object.init();
new_object
}
pub fn is_initialized(&self) -> bool {
self.id != 0
}
pub fn id(&self) -> GLuint {
self.id
}
pub fn init(&mut self) {
self.reset();
self.gen();
}
pub fn reset(&mut self) {
if self.is_initialized() {
self.delete();
self.id = 0;
}
}
fn gen(&mut self) {
unsafe {
gl::GenRenderbuffers(1, &mut self.id);
}
}
fn delete(&mut self) {
unsafe {
gl::DeleteRenderbuffers(1, &mut self.id);
}
}
}
/// Represents the OpenGL errors returned by `glGetError()`.
/// https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glGetError.xhtml
/// When an error flag is set, results of a GL operation are undefined only if GL_OUT_OF_MEMORY
/// has occurred. In all other cases, the command generating the error is ignored and has no effect
/// on the GL state or frame buffer contents. If the generating command returns a value, it
/// returns 0. If glGetError itself generates an error, it returns 0.
#[derive(Copy,Clone,Debug)]
pub enum OpenGlError {
/// No error has been recorded. The value of this symbolic constant is guaranteed to be 0.
/// NB: While GL_NO_ERROR is represented here, it won't be returned by the convenience function.
NoError,
/// An unacceptable value is specified for an enumerated argument. The offending command is
/// ignored and has no other side effect than to set the error flag.
InvalidEnum,
/// A numeric argument is out of range. The offending command is ignored and has no other side
/// effect than to set the error flag.
InvalidValue,
/// The specified operation is not allowed in the current state. The offending command is
/// ignored and has no other side effect than to set the error flag.
InvalidOperation,
/// The framebuffer object is not complete. The offending command is ignored and has no other
/// side effect than to set the error flag.
InvalidFramebufferOperation,
/// There is not enough memory left to execute the command. The state of the GL is undefined,
/// except for the state of the error flags, after this error is recorded.
OutOfMemory,
/// An attempt has been made to perform an operation that would cause an internal stack to
/// underflow.
StackUnderflow,
/// An attempt has been made to perform an operation that would cause an internal stack to
/// overflow.
StackOverflow,
/// Invented error if we can't determine which enum was returned.
UnknownError(u32),
}
impl std::fmt::Display for OpenGlError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let explain = match self {
OpenGlError::NoError => "OpenGL error: No Error (this shouldn't ever be set)".into(),
OpenGlError::InvalidEnum => "OpenGL error: Invalid Enum".into(),
OpenGlError::InvalidValue => "OpenGL error: Invalid Value".into(),
OpenGlError::InvalidOperation => "OpenGL error: Invalid Operation".into(),
OpenGlError::InvalidFramebufferOperation => "OpenGL error: Invalid Framebuffer Operation".into(),
OpenGlError::OutOfMemory => "OpenGL error: Out of Memory".into(),
OpenGlError::StackUnderflow => "OpenGL error: Stack Underflow".into(),
OpenGlError::StackOverflow => "OpenGL error: Stack Overflow".into(),
OpenGlError::UnknownError(result) => format!("OpenGL error: Unknown Error {}", result),
};
write!(f, "{}", explain)
}
}
impl std::error::Error for OpenGlError {
fn source(&self) -> Option<&(dyn std::error::Error +'static)> {
// Generic error, no backtrace.
None
}
}
/// Query OpenGL for the current error.
pub fn gl_get_error() -> Result<(), OpenGlError> {
let result = unsafe {
gl::GetError()
};
match result {
gl::NO_ERROR => Ok(()),
gl::INVALID_ENUM => Err(OpenGlError::InvalidEnum),
gl::INVALID_VALUE => Err(OpenGlError::InvalidValue),
gl::INVALID_OPERATION => Err(OpenGlError::InvalidOperation),
gl::INVALID_FRAMEBUFFER_OPERATION => Err(OpenGlError::InvalidFramebufferOperation),
gl::OUT_OF_MEMORY => Err(OpenGlError::OutOfMemory),
gl::STACK_UNDERFLOW => Err(OpenGlError::StackUnderflow),
gl::STACK_OVERFLOW => Err(OpenGlError::StackOverflow),
_ => Err(OpenGlError::UnknownError(result)),
}
}
/// Calculate the stride width for OpenGL
/// Useful for `gl::VertexAttribPointer`.
pub fn get_stride<T>(size: usize) -> gl::types::GLint {
(size * std::mem::size_of::<T>()) as gl::types::GLint
}
/// Calculate the offset for OpenGL
/// Useful for `gl::VertexAttribPointer`.
pub fn get_pointer_offset<T>(offset: usize) -> *const gl::types::GLvoid {
match offset {
0 => null(),
_ => (offset * std::mem::size_of::<T>()) as *const gl::types::GLvoid,
}
}
+63
View File
@@ -0,0 +1,63 @@
//! These wrappers make it easier to use certain OpenGL types.
//! This draws inspiration from some of the code in Microsoft's MIT-licensed libk4a,
//! particularly the code in `openglhelpers.h`.
use gl::types::*;
/// A slightly more convenient OpenGL Texture type
#[derive(Clone, Debug)]
pub struct Texture {
id: GLuint,
}
impl Texture {
pub fn new() -> Self {
return Self {
id: 0,
}
}
pub fn new_initialized() -> Self {
let mut new_object= Self::new();
new_object.init();
new_object
}
pub fn is_initialized(&self) -> bool {
self.id != 0
}
pub fn id(&self) -> GLuint {
self.id
}
pub fn init(&mut self) {
self.reset();
self.gen();
}
pub fn reset(&mut self) {
if self.is_initialized() {
self.delete();
self.id = 0;
}
}
pub fn bind_as_texture_2d(&self) {
unsafe {
gl::BindTexture(gl::TEXTURE_2D, self.id);
}
}
fn gen(&mut self) {
unsafe {
gl::GenTextures(1, &mut self.id);
}
}
fn delete(&mut self) {
unsafe {
gl::DeleteTextures(1, &mut self.id);
}
}
}
+35
View File
@@ -0,0 +1,35 @@
use anyhow::Result as AnyhowResult;
use anyhow::bail;
use gl::types::*;
use std::os::raw::c_char;
use std::ffi::CString;
/// A typesafe handle on uniform IDs with convenience methods.
#[derive(Clone, Debug, Copy)]
pub struct Uniform {
id: GLint,
}
impl Uniform {
pub fn lookup(identifier: &str, opengl_program_id: GLuint) -> AnyhowResult<Self> {
let id_cstr : CString = CString::new(identifier)?;
let id_cstr_ptr : *const c_char = id_cstr.as_ptr() as *const c_char;
let mut loc = -1;
unsafe {
loc = gl::GetUniformLocation(opengl_program_id, id_cstr_ptr);
}
if loc < 0 {
bail!("Couldn't find uniform named '{}' in OpenGL program {}.", identifier, opengl_program_id);
}
Ok(Self {
id: loc,
})
}
pub fn id(&self) -> GLint {
self.id
}
}
+64
View File
@@ -0,0 +1,64 @@
//! These wrappers make it easier to use certain OpenGL types.
//! This draws inspiration from some of the code in Microsoft's MIT-licensed libk4a,
//! particularly the code in `openglhelpers.h`.
use gl::types::*;
/// A slightly more convenient OpenGL VertexArray type
#[derive(Clone, Debug)]
pub struct VertexArray {
id: GLuint,
}
impl VertexArray {
pub fn new() -> Self {
return Self {
id: 0,
}
}
pub fn new_initialized() -> Self {
let mut new_object= Self::new();
new_object.init();
new_object
}
pub fn is_initialized(&self) -> bool {
self.id != 0
}
pub fn id(&self) -> GLuint {
self.id
}
pub fn init(&mut self) {
self.reset();
self.gen();
}
pub fn reset(&mut self) {
if self.is_initialized() {
self.delete();
self.id = 0;
}
}
pub fn bind(&self) {
unsafe {
gl::BindVertexArray(self.id);
}
}
fn gen(&mut self) {
unsafe {
gl::GenVertexArrays(1, &mut self.id);
}
}
fn delete(&mut self) {
unsafe {
gl::DeleteVertexArrays(1, &mut self.id);
}
}
}
+123
View File
@@ -0,0 +1,123 @@
use anyhow::Result as AnyhowResult;
use crate::point_cloud::debug::image_proxy::ImageProxy;
use k4a_sys_temp as k4a_sys;
use kinect::CaptureError;
use kinect;
use std::path::Path;
use std::sync::{RwLock, Mutex, PoisonError, MutexGuard, Arc};
use winit::event::VirtualKeyCode::Mute;
/// Store either a wrapped capture, or a k4a Capture.
/// Useful for debugging.
#[derive(Clone)]
pub struct CaptureProxy {
storage: UnderlyingStorage,
}
#[derive(Clone)]
enum UnderlyingStorage {
CameraImageBytes {
color_image: ImageProxy,
depth_image: ImageProxy,
},
K4aCapture {
capture: kinect::Capture,
maybe_color_image: Option<ImageProxy>,
maybe_depth_image: Option<ImageProxy>,
}
}
impl CaptureProxy {
pub fn from_image_proxy_pair(color_image: ImageProxy, depth_image: ImageProxy) -> Self {
Self {
storage: UnderlyingStorage::CameraImageBytes {
color_image,
depth_image,
}
}
}
pub fn from_k4a_capture(capture: &kinect::Capture) -> Self {
// NB: We need to increase the refcount.
// K4a manages the memory under the hood.
let capture = capture.clone();
Self::consume_k4a_capture(capture)
}
pub fn consume_k4a_capture(capture: kinect::Capture) -> Self {
// NB: I tried to lazily unpack these, but interior mutability Sync/Send was a nightmare.
// The poor ergonomics were not worth it.
let color_image = capture.get_color_image()
.map(|image| ImageProxy::consume_k4a_image(image))
.ok();
let depth_image = capture.get_depth_image()
.map(|image| ImageProxy::consume_k4a_image(image))
.ok();
Self {
storage: UnderlyingStorage::K4aCapture {
capture,
maybe_color_image: color_image,
maybe_depth_image: depth_image,
}
}
}
pub fn is_k4a(&self) -> bool {
match &self.storage {
UnderlyingStorage::CameraImageBytes{ .. } => false,
UnderlyingStorage::K4aCapture { .. } => true,
}
}
pub fn get_color_image(&self) -> AnyhowResult<&ImageProxy> {
match &self.storage {
UnderlyingStorage::CameraImageBytes { color_image, .. } => Ok(color_image),
UnderlyingStorage::K4aCapture{ maybe_color_image, .. } => {
let inner = maybe_color_image.as_ref()
.ok_or(CaptureError::NullCapture)?;
Ok(inner)
}
}
}
pub fn get_depth_image(&self) -> AnyhowResult<&ImageProxy> {
match &self.storage {
UnderlyingStorage::CameraImageBytes { depth_image, .. } => Ok(depth_image),
UnderlyingStorage::K4aCapture{ maybe_depth_image, .. } => {
let inner = maybe_depth_image.as_ref()
.ok_or(CaptureError::NullCapture)?;
Ok(inner)
}
}
}
pub fn debug_get_capture(&self) -> kinect::Capture {
match &self.storage {
UnderlyingStorage::CameraImageBytes{ .. } => unimplemented!("Cannot get capture for non-k4a"),
UnderlyingStorage::K4aCapture { capture, .. } => capture.clone(),
}
}
pub fn debug_save_files(&self, path_prefix: &Path, suffix: &str) -> AnyhowResult<()> {
match &self.storage {
UnderlyingStorage::CameraImageBytes{ .. } => unimplemented!("No need to debug save for raw bytes"),
UnderlyingStorage::K4aCapture { maybe_color_image, maybe_depth_image, .. } => {
if let Some(color_image) = maybe_color_image {
let filename = path_prefix.join(format!("./color_frame_{}", suffix));
color_image.debug_save_file(filename.to_str().unwrap())?;
}
if let Some(depth_image) = maybe_depth_image {
let filename = path_prefix.join(format!("./depth_frame_{}", suffix));
depth_image.debug_save_file(filename.to_str().unwrap())?;
}
Ok(())
},
}
}
}
+177
View File
@@ -0,0 +1,177 @@
use anyhow::Result as AnyhowResult;
use crate::files::write_to_file_from_byte_ptr::write_to_file_from_byte_ptr;
use k4a_sys_temp as k4a_sys;
use kinect::ImageFormat;
use kinect;
use std::fs::File;
use std::fs;
use std::io::Read;
use std::ptr::null_mut;
/// Store either raw bytes, or wrap a k4a::Image
/// This is meant to be plumbed through the system instead of a k4a::Image (depth or color image)
#[derive(Clone)]
pub struct ImageProxy {
storage: UnderlyingStorage,
}
/// The underlying data store
#[derive(Clone)]
enum UnderlyingStorage {
/// Our own serialization
Bytes {
bytes: Vec<u8>,
width: usize,
height: usize,
stride_bytes: usize,
format: ImageFormat,
},
/// From the Kinect camera
K4aImage(kinect::Image),
}
impl ImageProxy {
pub fn from_file(
filename: &str,
width: usize,
height: usize,
stride_bytes: usize,
format: ImageFormat) -> AnyhowResult<Self>
{
let mut file = File::open(filename)?;
let metadata = fs::metadata(&filename)?;
let mut buffer = vec![0u8; metadata.len() as usize];
file.read(&mut buffer)?;
let storage = UnderlyingStorage::Bytes {
bytes: buffer,
width,
height,
stride_bytes,
format,
};
Ok(Self {
storage,
})
}
pub fn from_k4a_image(image: &kinect::Image) -> Self {
// NB: We need to increase the refcount.
// K4a manages the memory under the hood.
let image = image.clone();
let storage = UnderlyingStorage::K4aImage(image);
Self {
storage,
}
}
pub fn consume_k4a_image(image: kinect::Image) -> Self {
let storage = UnderlyingStorage::K4aImage(image);
Self {
storage,
}
}
pub fn is_k4a(&self) -> bool {
match &self.storage {
UnderlyingStorage::Bytes { .. } => false,
UnderlyingStorage::K4aImage(_) => true,
}
}
pub fn len(&self) -> usize {
match &self.storage {
UnderlyingStorage::Bytes { bytes, .. } => bytes.len(),
UnderlyingStorage::K4aImage(image) => image.get_size(),
}
}
pub fn as_ptr(&self) -> *const u8 {
match &self.storage {
UnderlyingStorage::Bytes{ ref bytes, .. } => {
bytes.as_ptr()
},
UnderlyingStorage::K4aImage(ref image) => {
image.get_buffer() as *const u8
},
}
}
pub fn get_buffer(&self) -> *const u8 {
self.as_ptr()
}
pub fn get_width_pixels(&self) -> usize {
match &self.storage {
UnderlyingStorage::Bytes { width, .. } => *width,
UnderlyingStorage::K4aImage(image) => image.get_width_pixels(),
}
}
pub fn get_height_pixels(&self) -> usize {
match &self.storage {
UnderlyingStorage::Bytes { height, .. } => *height,
UnderlyingStorage::K4aImage(image) => image.get_height_pixels(),
}
}
pub fn get_stride_bytes(&self) -> usize {
match &self.storage {
UnderlyingStorage::Bytes { stride_bytes, .. } => *stride_bytes,
UnderlyingStorage::K4aImage(image) => image.get_stride_bytes(),
}
}
pub fn get_format(&self) -> ImageFormat {
match &self.storage {
UnderlyingStorage::Bytes { format, .. } => *format,
UnderlyingStorage::K4aImage(image) => image.get_format(),
}
}
pub fn get_handle(&self) -> k4a_sys::k4a_image_t {
match &self.storage {
UnderlyingStorage::Bytes { bytes, width, height, stride_bytes, format } => {
// TODO: We're not handing over ownership of the buffer!
// The k4a_image cannot be used after this ImageProxy dies!
let mut handle = null_mut();
//println!("fake.get_handle(): {:?}, {}, {}x{}", format, stride_bytes, width, height);
unsafe {
k4a_sys::k4a_image_create_from_buffer(
format.to_k4a(),
*width as i32,
*height as i32,
*stride_bytes as i32,
bytes.as_ptr() as *mut u8, // TODO: Technically we won't write.
bytes.len(),
None,
null_mut(),
&mut handle,
);
}
handle
},
UnderlyingStorage::K4aImage(image) => {
//println!("real.get_handle(): {:?}, {}, {}x{}", image.get_format(), image.get_stride_bytes(), image.get_width_pixels(), image.get_height_pixels());
image.get_handle()
},
}
}
pub fn debug_save_file(&self, filename: &str) -> AnyhowResult<()> {
match &self.storage {
UnderlyingStorage::Bytes { .. } => unimplemented!("No need to debug save raw bytes"),
UnderlyingStorage::K4aImage(image) => {
let byte_src = image.get_buffer();
let size_bytes = image.get_size();
write_to_file_from_byte_ptr(filename, byte_src, size_bytes)
}
}
}
}
+2
View File
@@ -0,0 +1,2 @@
pub mod image_proxy;
pub mod capture_proxy;
@@ -0,0 +1,564 @@
//! This is a port of Microsoft's libk4a `gpudepthtopointcloudconverter.h`.
//! This code turns depth images into point clouds.
use std::ffi::CString;
use std::fmt::Formatter;
use std::mem::size_of;
use std::os::raw::{c_char, c_int, c_void};
use std::ptr;
use std::ptr::null;
use std::str;
use gl;
use gl::types::*;
use crate::files::read_file_string_contents::read_file_string_contents;
use crate::files::write_to_file_from_byte_ptr::write_to_file_from_byte_ptr;
use crate::opengl::compile_shader::compile_shader;
use crate::opengl::wrapper::buffer::Buffer;
use crate::opengl::wrapper::other_misc_wrapper::OpenGlError;
use crate::opengl::wrapper::other_misc_wrapper::gl_get_error;
use crate::opengl::wrapper::texture::Texture;
use crate::point_cloud::debug::image_proxy::ImageProxy;
use crate::point_cloud::pixel_structs::DepthPixel;
use k4a_sys_temp as k4a_sys;
use kinect::{Image, Calibration, ImageFormat};
use kinect;
pub type Result<T> = std::result::Result<T, PointCloudComputeError>;
#[derive(Clone, Debug)]
pub enum PointCloudComputeError {
OpenGlError(OpenGlError),
UnknownError,
}
impl From<OpenGlError> for PointCloudComputeError {
fn from(error: OpenGlError) -> Self {
PointCloudComputeError::OpenGlError(error)
}
}
impl std::fmt::Display for PointCloudComputeError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let description = match self {
PointCloudComputeError::OpenGlError(inner) => {
format!("Compute OpenGL error: {}", inner)
},
PointCloudComputeError::UnknownError => "Unknown Compute Error".into(),
};
write!(f, "{}", description)
}
}
impl std::error::Error for PointCloudComputeError {
fn source(&self) -> Option<&(dyn std::error::Error +'static)> {
// Generic error, no backtrace.
None
}
}
/// The format that the point cloud texture uses internally to store points.
/// If you want to use the texture that this outputs from your shader, you
/// need to pass this as the format argument to glBindImageTexture().
pub const POINT_CLOUD_TEXTURE_FORMAT : GLuint = gl::RGBA32F;
pub struct GpuPointCloudConverter {
/// The OpenGL program
shader_program_id: GLuint,
/// The x,y table as a texture
pub xy_table_texture: Texture,
/// Preallocated texture for depth image so we don't have to reallocate every frame.
pub depth_image_texture: Texture,
depth_image_pixel_buffer: Buffer,
/// Uniform location in the shader program
// TODO: Turns out this does nothing
//dest_tex_id: GLint,
/// Uniform location in the shader program
xy_table_id: GLint,
/// Uniform location in the shader program
depth_image_id: GLint,
}
impl GpuPointCloudConverter {
pub fn new() -> Self {
let program_id = unsafe { gl::CreateProgram() };
let point_cloud_compute_shader = read_file_string_contents("src/point_cloud/shaders/point_cloud_compute_shader.glsl").unwrap();
let shader_id = compile_shader(&point_cloud_compute_shader, gl::COMPUTE_SHADER);
link_program(program_id, shader_id);
let mut dest_tex_id = 0;
let mut xy_table_id = 0;
let mut depth_image_id = 0;
/// Uniform variable name in OpenGL shader program
// TODO: Turns out this does nothing.
let DEST_TEX : CString = CString::new("destTex").expect("string is correct");
let DEST_TEX_PTR : *const c_char = DEST_TEX.as_ptr() as *const c_char;
/// Uniform variable name in OpenGL shader program
let XY_TABLE : CString = CString::new("xyTable").expect("string is correct");
let XY_TABLE_PTR: *const c_char = XY_TABLE.as_ptr() as *const c_char;
/// Uniform variable name in OpenGL shader program
let DEPTH_IMAGE : CString = CString::new("depthImage").expect("string is correct");
let DEPTH_IMAGE_PTR : *const c_char = DEPTH_IMAGE.as_ptr() as *const c_char;
unsafe {
dest_tex_id = gl::GetUniformLocation(program_id, DEST_TEX_PTR);
xy_table_id = gl::GetUniformLocation(program_id, XY_TABLE_PTR);
depth_image_id = gl::GetUniformLocation(program_id, DEPTH_IMAGE_PTR);
}
GpuPointCloudConverter {
shader_program_id: program_id,
//shader_id,
//dest_tex_id,
xy_table_id,
depth_image_id,
depth_image_texture: Texture::new(),
xy_table_texture: Texture::new(),
depth_image_pixel_buffer: Buffer::new(),
}
}
/// Takes depth data and turns it into a texture containing the XYZ coordinates of the depth map
/// using the most recently set-to-active XY table. The input depth image and output texture
/// (if already set) must be of the same resolution that was used to generate that XY table, or
/// else behavior is undefined.
///
/// Essentially a reimplementation of k4a::transform::depth_image_to_point_cloud on the GPU.
/// This is much more performant than k4a::transform::depth_image_to_point_cloud, but is a bit
/// more unwieldly to use since you have to use its output in shaders.
///
/// The output texture has an internal format of GL_RGBA32F and is intended to be used directly
/// by other OpenGL shaders as an image2d uniform.
///
/// To avoid excess image allocations, you can reuse a texture that was previously output
/// by this function, provided the depth image and XY table previously used was for the same
/// sized texture.
///
pub fn convert(&self,
//depth_image: &kinect::Image,
depth_image: &ImageProxy,
output_texture: &mut Texture,
camera_index: usize
) -> Result<()> {
if !self.xy_table_texture.is_initialized() {
return Err(PointCloudComputeError::UnknownError);
}
// Create output texture if it doesn't already exist
//
// We don't use the alpha channel, but it turns out OpenGL doesn't
// actually have a 3-component (i.e. RGB32F) format - you get
// 1, 2, or 4 components.
//
let width = depth_image.get_width_pixels() as i32;
let height = depth_image.get_height_pixels() as i32;
if !output_texture.is_initialized() {
output_texture.init();
unsafe {
gl::ActiveTexture(gl::TEXTURE0);
gl::BindTexture(gl::TEXTURE_2D, output_texture.id());
// The format that the point cloud texture uses internally to store points.
// If you want to use the texture that this outputs from your shader, you
// need to pass this as the format argument to glBindImageTexture().
// static constexpr GLenum PointCloudTextureFormat = GL_RGBA32F;
// gl::TexStorage2D(gl::TEXTURE_2D, 1, POINT_CLOUD_TEXTURE_FORMAT, width, height);
gl::TexStorage2D(gl::TEXTURE_2D, 1, gl::RGBA32F, width, height);
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::NEAREST as i32);
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::NEAREST as i32);
}
}
unsafe {
// Upload data to uniform texture
gl::BindBuffer(gl::PIXEL_UNPACK_BUFFER, self.depth_image_pixel_buffer.id());
gl::BindTexture(gl::TEXTURE_2D, self.depth_image_texture.id());
let num_bytes: GLuint = (width * height * size_of::<u16>() as i32) as GLuint; // libc::uint16_t = u16
let length = (width * height) as usize;
//let num_bytes: GLuint = (width * height * size_of::<DepthPixel>() as i32) as GLuint; // libc::uint16_t = u16
// GLubyte *textureMappedBuffer = reinterpret_cast<GLubyte *>(...)
let texture_mapped_buffer = gl::MapBufferRange(
gl::PIXEL_UNPACK_BUFFER,
0,
num_bytes as isize,
//(std::mem::size_of::<DepthPixel>() * length) as GLsizeiptr,
gl::MAP_WRITE_BIT | gl::MAP_INVALIDATE_BUFFER_BIT
//);
) as *mut DepthPixel;
if texture_mapped_buffer as usize == 0 {
return Err(PointCloudComputeError::UnknownError);
}
let depth_image_buffer = depth_image.get_buffer();
//let filename = format!("output/depth_src_{}", camera_index);
//let size = (width * height) as usize * size_of::<u16>();
//write_to_file_from_byte_ptr(&filename, depth_image_buffer, size).unwrap();
let typed_depth_image_buffer = depth_image_buffer as *const DepthPixel;
std::ptr::copy::<DepthPixel>(typed_depth_image_buffer, texture_mapped_buffer, length as usize);
let result = gl::UnmapBuffer(gl::PIXEL_UNPACK_BUFFER);
if result == gl::FALSE {
return Err(PointCloudComputeError::UnknownError);
}
gl::TexSubImage2D(
gl::TEXTURE_2D, // target
0, // level
0, // x offset
0, // y offset
width,
height,
gl::RED_INTEGER, //constexpr GLenum depthImageDataFormat = GL_RED_INTEGER;
gl::UNSIGNED_SHORT, //constexpr GLenum depthImageDataType = GL_UNSIGNED_SHORT;
null(), // data
);
gl::BindBuffer(gl::PIXEL_UNPACK_BUFFER, 0);
gl::UseProgram(self.shader_program_id);
// Bind textures that we're going to pass to the texture
gl::ActiveTexture(gl::TEXTURE0);
gl::BindTexture(gl::TEXTURE_2D, output_texture.id());
gl::BindImageTexture(
0,
output_texture.id(),
0,
gl::FALSE,
0,
gl::WRITE_ONLY,
//POINT_CLOUD_TEXTURE_FORMAT
//static constexpr GLenum PointCloudTextureFormat = GL_RGBA32F;
gl::RGBA32F,
);
gl::ActiveTexture(gl::TEXTURE1);
gl::BindTexture(gl::TEXTURE_2D, self.depth_image_texture.id());
gl::BindImageTexture(
1,
self.depth_image_texture.id(),
0,
gl::FALSE,
0,
gl::READ_ONLY,
gl::R16UI, //constexpr GLenum depthImageInternalFormat = GL_R16UI;
);
gl::Uniform1i(self.depth_image_id, 1);
gl::ActiveTexture(gl::TEXTURE2);
gl::BindTexture(gl::TEXTURE_2D, self.xy_table_texture.id());
gl::BindImageTexture(
2,
self.xy_table_texture.id(),
0,
gl::FALSE,
0,
gl::READ_ONLY,
gl::RG32F, //constexpr GLenum xyTableInternalFormat = GL_RG32F;
);
gl::Uniform1i(self.xy_table_id, 2);
gl::DispatchCompute(
depth_image.get_width_pixels() as u32,
depth_image.get_height_pixels() as u32,
1,
);
// Wait for the rendering to finish before allowing reads to the texture we just wrote
gl::MemoryBarrier(gl::TEXTURE_FETCH_BARRIER_BIT);
gl_get_error()?;
// NB(bt): Restore to default active texture? Looks like this is required for 'imgui'
// This is needed despite using glGetInteger(GL_ACTIVE_TEXTURE) and resetting that value.
//gl::ActiveTexture(gl::TEXTURE0);
/*
// Try to save the texture buffer to file... (not working yet)
let size_bytes = (width * height * 4);
let mut vec = vec![0u8; size_bytes as usize];
let mut pointer = vec.as_mut_ptr() as *mut c_void;
// Pull the texture out as bytes.
//gl::ActiveTexture(gl::TEXTURE1);
gl::BindTexture(gl::TEXTURE_2D, output_texture.id());
gl_get_error().unwrap();
gl::GetTextureImage(
output_texture.id(),
0, // level
gl::RGBA32F, // format
gl::UNSIGNED_BYTE, // type
size_bytes, // buffer size
pointer,
);
gl_get_error().unwrap();
*/
Ok(())
}
}
/// Set the XY table that will be used by future calls to Convert(). Get an XY table by calling
/// GenerateXyTable().
pub fn set_active_xy_table(&mut self, xy_table: &ImageProxy) -> Result<()> {
let width = xy_table.get_width_pixels() as i32;
let height = xy_table.get_height_pixels() as i32;
// Upload the XY table as a texture so we can use it as a uniform
self.xy_table_texture.init();
unsafe {
gl::BindTexture(gl::TEXTURE_2D, self.xy_table_texture.id());
gl::TexStorage2D(
gl::TEXTURE_2D,
1,
gl::RG32F, // constexpr GLenum xyTableInternalFormat = GL_RG32F;
width,
height,
);
let xy_table_buffer = xy_table.get_buffer();
gl::TexSubImage2D(
gl::TEXTURE_2D,
0, // level
0, // xoffset
0, // yoffset
width,
height,
gl::RG, //constexpr GLenum xyTableDataFormat = GL_RG;
gl::FLOAT, //constexpr GLenum xyTableDataType = GL_FLOAT;
xy_table_buffer as *const c_void,
);
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::NEAREST as i32);
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::NEAREST as i32);
}
// Pre-allocate a texture for the depth images so we don't have to
// reallocate on every frame
self.depth_image_texture.init();
self.depth_image_pixel_buffer.init();
let depth_image_size_bytes = (width * height * size_of::<u16>() as i32) as isize; // libc::uint16_t = u16
unsafe {
gl::BindBuffer(gl::PIXEL_UNPACK_BUFFER, self.depth_image_pixel_buffer.id());
gl::BufferData(
gl::PIXEL_UNPACK_BUFFER,
depth_image_size_bytes,
ptr::null_mut(),
gl::STREAM_DRAW,
);
gl::BindBuffer(gl::PIXEL_UNPACK_BUFFER, 0);
gl::BindTexture(gl::TEXTURE_2D, self.depth_image_texture.id());
gl::TexStorage2D(
gl::TEXTURE_2D,
1,
gl::R16UI, // constexpr GLenum depthImageInternalFormat = GL_R16UI;
width,
height,
);
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::NEAREST as i32);
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::NEAREST as i32);
}
gl_get_error()
.map_err(|err| PointCloudComputeError::OpenGlError(err))
}
/// Creates a k4a::image containing the XY tables from calibration based on calibrationType.
/// The table is a 2D array of k4a_float2_t's with the same resolution as the camera of calibrationType
/// specified in calibration.
///
/// You can use this table to convert a depth image into a point cloud, e.g. by using the Convert method.
/// Conversion is done by multiplying the depth pixel value by the XY table values - i.e. the result
/// pixel will be (xyTable[p].x * depthImage[p], xyTable[p].y * depthImage[p], depthImage[p]), where
/// p is the index of a given pixel.
pub fn generate_xy_table(calibration: &Calibration,
calibration_type: k4a_sys::k4a_calibration_type_t)
-> Result<Image>
{
let camera_calibration : k4a_sys::k4a_calibration_camera_t = match calibration_type {
k4a_sys::k4a_calibration_type_t_K4A_CALIBRATION_TYPE_COLOR => calibration.0.color_camera_calibration,
k4a_sys::k4a_calibration_type_t_K4A_CALIBRATION_TYPE_DEPTH => calibration.0.depth_camera_calibration,
_ => return Err(PointCloudComputeError::UnknownError),
};
let width = camera_calibration.resolution_width as u32;
let height = camera_calibration.resolution_height as u32;
let stride_bytes = width * size_of::<k4a_sys::k4a_float2_t>() as u32;
let xy_table = Image::create(
ImageFormat::Custom,
width,
height,
stride_bytes,
).map_err(|_| PointCloudComputeError::UnknownError)?;
// typedef union
// {
// // XY or array representation of vector
// struct _xy
// {
// float x; // < X component of a vector
// float y; // < Y component of a vector
// } xy; // < X, Y representation of a vector
// float v[2]; // < Array representation of a vector
// } k4a_float2_t;
let mut p = k4a_sys::k4a_float2_t {
xy: k4a_sys::k4a_float2_t__xy {
x: 0.0,
y: 0.0,
}
};
// typedef union
// {
// // XYZ or array representation of vector.
// struct _xyz
// {
// float x; // < X component of a vector.
// float y; // < Y component of a vector.
// float z; // < Z component of a vector.
// } xyz; // < X, Y, Z representation of a vector.
// float v[3]; // < Array representation of a vector.
// } k4a_float3_t;
let mut ray = k4a_sys::k4a_float3_t {
xyz: k4a_sys::k4a_float3_t__xyz {
x: 0.0,
y: 0.0,
z: 0.0,
}
};
let mut idx = 0;
unsafe {
let table_data = xy_table.get_buffer();
let typed_buffer = table_data as *mut k4a_sys::k4a_float2_t;
for y in 0..height {
p.xy.y = y as f32;
for x in 0..width {
p.xy.x = x as f32;
let mut valid: c_int = -1;
let result = unsafe {
// https://docs.rs/k4a-sys/0.2.0/k4a_sys/fn.k4a_calibration_2d_to_3d.html
k4a_sys::k4a_calibration_2d_to_3d(
&calibration.0,
&p, // source point 2d
1.0, // source depth mm
calibration_type, // source camera
calibration_type, // target camera
&mut ray, // target point3d mm
&mut valid // set to 1 when valid result, 0 when coordinate is not valid
)
};
if result != k4a_sys::k4a_buffer_result_t_K4A_BUFFER_RESULT_SUCCEEDED {
return Err(PointCloudComputeError::UnknownError);
}
if valid == 1 {
unsafe {
/*
println!("[size: {}x{}] 2D coord {}x{} --> 3D cord {}x{}x{}",
width, height, p.xy.x, p.xy.y, ray.xyz.x, ray.xyz.y, ray.xyz.z);
[size: 1280x720] 2D coord 0x0 --> 3D cord -1.0120127x-0.5833072x1
[size: 1280x720] 2D coord 1x0 --> 3D cord -1.010335x-0.5832499x1
[size: 1280x720] 2D coord 2x0 --> 3D cord -1.0086561x-0.58319193x1
...
[size: 1280x720] 2D coord 1277x719 --> 3D cord 1.0083642x0.55422443x1
[size: 1280x720] 2D coord 1278x719 --> 3D cord 1.0100702x0.55429035x1
[size: 1280x720] 2D coord 1279x719 --> 3D cord 1.0117699x0.55435246x1
...
[size: 640x576] 2D coord 0x0 --> 3D cord -0.97488284x-1.0173141x1
[size: 640x576] 2D coord 1x0 --> 3D cord -0.97017723x-1.0155252x1
[size: 640x576] 2D coord 2x0 --> 3D cord -0.9654954x-1.0137504x1
...
[size: 640x576] 2D coord 637x575 --> 3D cord 0.7947599x0.60008353x1
[size: 640x576] 2D coord 638x575 --> 3D cord 0.79833484x0.60085475x1
[size: 640x576] 2D coord 639x575 --> 3D cord 0.8019226x0.60163087x1
*/
(*typed_buffer.offset(idx)).xy.x = ray.xyz.x;
(*typed_buffer.offset(idx)).xy.y = ray.xyz.y;
}
} else {
unsafe {
// This pixel is invalid
(*typed_buffer.offset(idx)).xy.x = 0.0;
(*typed_buffer.offset(idx)).xy.y = 0.0;
}
}
idx += 1;
}
}
}
Ok(xy_table)
}
}
fn link_program(program: GLuint, shader: GLuint) -> GLuint {
unsafe {
gl::AttachShader(program, shader);
gl::LinkProgram(program);
// Get the link status
let mut status = gl::FALSE as GLint;
gl::GetProgramiv(program, gl::LINK_STATUS, &mut status);
// Fail on error
if status != (gl::TRUE as GLint) {
let mut len: GLint = 0;
gl::GetProgramiv(program, gl::INFO_LOG_LENGTH, &mut len);
let mut buf = Vec::with_capacity(len as usize);
buf.set_len((len as usize) - 1); // subtract 1 to skip the trailing null character
gl::GetProgramInfoLog(
program,
len,
ptr::null_mut(),
buf.as_mut_ptr() as *mut GLchar,
);
panic!(
"{}",
str::from_utf8(&buf)
.ok()
.expect("ProgramInfoLog not valid utf8")
);
}
program
}
}
+7
View File
@@ -0,0 +1,7 @@
pub mod debug;
pub mod gpu_point_cloud_converter;
pub mod pixel_structs;
pub mod point_cloud_renderer;
pub mod point_cloud_visualiser;
pub mod util;
pub mod viewer_image;
+12
View File
@@ -0,0 +1,12 @@
use libc::uint16_t;
use libc::uint8_t;
#[repr(C)]
pub struct BgraPixel {
pub blue: uint8_t,
pub green: uint8_t,
pub red: uint8_t,
pub alpha: uint8_t,
}
pub type DepthPixel = uint16_t;
+453
View File
@@ -0,0 +1,453 @@
//! This is a port of Microsoft's libk4a `k4apointcloudrenderer.cpp`.
//! This provides the visual output.
use anyhow::Result as AnyhowResult;
use arcball::ArcballCamera;
use crate::assets::obj_loader::{load_wavefront, ExtractedVertex};
use crate::assets::renderable_object::RenderableObject;
use crate::files::read_file_string_contents::read_file_string_contents;
use crate::files::write_to_file_from_byte_ptr::write_to_file_from_byte_ptr;
use crate::gui::mouse_camera_arcball::MouseCameraArcball;
use crate::opengl::compile_shader::compile_shader;
use crate::opengl::link_program::link_shader_program;
use crate::opengl::wrapper::buffer::Buffer;
use crate::opengl::wrapper::other_misc_wrapper::{gl_get_error, OpenGlError};
use crate::opengl::wrapper::texture::Texture;
use crate::opengl::wrapper::vertex_array::VertexArray;
use crate::point_cloud::debug::image_proxy::ImageProxy;
use crate::point_cloud::pixel_structs::BgraPixel;
use gl::types::*;
use gl;
use gltf::Gltf;
use std::ffi::CString;
use std::fmt::Formatter;
use std::fs::{File, OpenOptions};
use std::io::{Read, Write, BufReader};
use std::mem::size_of;
use std::os::raw::{c_char, c_void};
use std::path::Path;
use std::ptr::{null, null_mut};
use std::ptr;
use std::str;
use std::sync::{Arc, Mutex};
use std::time::{SystemTime, UNIX_EPOCH};
use tobj::{load_obj, load_mtl};
use crate::assets::positionable_object::{PositionableObject, ObjectType};
use cgmath::{Matrix4, SquareMatrix, Matrix};
use crate::opengl::wrapper::uniform::Uniform;
use crate::opengl::matrices::{initial_projection_matrix_4x4_flat, initial_view_matrix_4x4_flat, identity_matrix_4x4, initial_view_matrix_4x4, initial_projection_matrix_4x4};
use image::DynamicImage;
use crate::opengl::wrapper::attribute::Attribute;
use nalgebra::RealField;
use cgmath::num_traits::ToPrimitive;
pub type Result<T> = std::result::Result<T, PointCloudRendererError>;
#[derive(Clone, Debug)]
pub enum PointCloudRendererError {
OpenGlError(OpenGlError),
UnknownError,
}
impl From<OpenGlError> for PointCloudRendererError {
fn from(error: OpenGlError) -> Self {
PointCloudRendererError::OpenGlError(error)
}
}
impl std::fmt::Display for PointCloudRendererError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let description = match self {
PointCloudRendererError::OpenGlError(inner) => {
format!("Renderer OpenGL error: {}", inner)
},
PointCloudRendererError::UnknownError => "Unknown Renderer Error".into(),
};
write!(f, "{}", description)
}
}
impl std::error::Error for PointCloudRendererError {
fn source(&self) -> Option<&(dyn std::error::Error +'static)> {
// Generic error, no backtrace.
None
}
}
pub struct PointCloudRenderer {
num_cameras: usize,
arcball_camera: Arc<Mutex<MouseCameraArcball>>,
/// The OpenGL program
shader_program_id: GLuint,
/// The OpenGL vertex shader
vertex_shader_id: GLuint,
/// The OpenGL fragment shader
fragment_shader_id: GLuint,
// TODO: better matrix types
view: [f32; 16],
projection: [f32; 16],
view_matrix: [[f32; 4]; 4],
projection_matrix: [[f32; 4]; 4],
/// Matrix applied to each model
/// If a model doesn't set its own, it defaults to identity matrix
default_model_view_matrix: [[f32; 4]; 4],
/// Renderer setting: size of the rendered points
point_size: u8,
/// Renderer setting: shading enabled
enable_shading: bool,
/// Uniform location in the shader program.
enable_shading_index: GLint,
/// Point array size
vertex_arrays_size_bytes: Vec<GLsizei>,
/// Uniform location in the shader program.
view_transform_id: Uniform,
/// Uniform location in the shader program.
projection_transform_id: Uniform,
/// Uniform location in the shader program.
model_transform_id: Uniform,
/// An integer uniform that tells GLSL what type of data it's dealing with.
/// This should allow us to mix Point Cloud data and Geometric Objects
/// 1 = object
/// 2 = pointCloud
uniform_vertex_type: Uniform,
/// Used to signal which camera's point cloud texture is active.
uniform_point_cloud_texture_to_use: Uniform,
/// Uniform for object textures in the shader program.
object_texture_uniform: Uniform,
/// Location of the texture coordinate input
texture_coordinates_attribute: Attribute,
/// Uniform location in the shader program.
point_cloud_texture_indices: Vec<GLint>,
/// 'in vec4 inColor'
color_vertex_attribute_location: GLint,
vertex_array_objects: Vec<VertexArray>,
vertex_color_buffer_objects: Vec<Buffer>,
renderable_objects: Vec<PositionableObject>
}
impl PointCloudRenderer {
pub fn new(num_cameras: usize, arcball: Arc<Mutex<MouseCameraArcball>>) -> AnyhowResult<Self> {
unsafe {
gl::Enable(gl::PROGRAM_POINT_SIZE);
}
let program_id = unsafe { gl::CreateProgram() };
let mut point_cloud_vertex_shader = read_file_string_contents("src/point_cloud/shaders/point_cloud_vertex_shader.glsl").unwrap();
let mut point_cloud_fragment_shader = read_file_string_contents("src/point_cloud/shaders/point_cloud_fragment_shader.glsl").unwrap();
//let mut point_cloud_vertex_shader = read_file_string_contents("src/point_cloud/shaders/simple_vertex_shader.glsl").unwrap();
let vertex_shader_id = compile_shader(&point_cloud_vertex_shader, gl::VERTEX_SHADER);
let fragment_shader_id = compile_shader(&point_cloud_fragment_shader, gl::FRAGMENT_SHADER);
link_shader_program(program_id, vertex_shader_id, fragment_shader_id);
/// Uniform variable name in OpenGL shader program
let ENABLE_SHADING : CString = CString::new("enableShading").expect("string is correct");
let ENABLE_SHADING_PTR : *const c_char = ENABLE_SHADING.as_ptr() as *const c_char;
/// Uniform variable name in OpenGL shader program
let POINT_CLOUD_0 : CString = CString::new("pointCloudTexture0").expect("string is correct");
let POINT_CLOUD_0_PTR : *const c_char = POINT_CLOUD_0.as_ptr() as *const c_char;
let POINT_CLOUD_1 : CString = CString::new("pointCloudTexture1").expect("string is correct");
let POINT_CLOUD_1_PTR : *const c_char = POINT_CLOUD_1.as_ptr() as *const c_char;
let COLOR_LOCATION : CString = CString::new("inColor").expect("string is correct");
let COLOR_LOCATION_PTR : *const c_char = COLOR_LOCATION.as_ptr() as *const c_char;
let mut point_cloud_texture_indices = Vec::with_capacity(num_cameras);
let mut view_index = 0;
let mut projection_index = 0;
let mut enable_shading_index = 0;
unsafe {
// This function returns -1 if name does not correspond to an active uniform variable in
// program, if name starts with the reserved prefix "gl_", or if name is associated with an
// atomic counter or a named uniform block.
// FIXME: THe 'view' and 'projection' uniforms are not binding for some reason.
enable_shading_index = gl::GetUniformLocation(program_id, ENABLE_SHADING_PTR);
// TODO: This is hardcoded to 2
point_cloud_texture_indices.push(gl::GetUniformLocation(program_id, POINT_CLOUD_0_PTR));
point_cloud_texture_indices.push(gl::GetUniformLocation(program_id, POINT_CLOUD_1_PTR));
}
let model_transform = Uniform::lookup("model", program_id)?;
let view_transform = Uniform::lookup("view", program_id)?;
let projection_transform = Uniform::lookup("projection", program_id)?;
let object_texture_uniform = Uniform::lookup("objTexture", program_id)?;
let texture_coordinate_attribute = Attribute::lookup("vTextureCoord", program_id)?;
let uniform_vertex_type = Uniform::lookup("vertexType", program_id)?;
let uniform_point_cloud_texture_to_use = Uniform::lookup("pointCloudTextureToUse", program_id)?;
let color_vertex_attribute_location = unsafe {
let location = gl::GetAttribLocation(program_id, COLOR_LOCATION_PTR);
gl::EnableVertexAttribArray(location as u32);
gl::VertexAttribPointer(
location as u32,
gl::BGRA as i32,
gl::UNSIGNED_BYTE,
gl::TRUE,
0 as i32,
0 as *const c_void,
);
location
};
Ok(Self {
num_cameras,
arcball_camera: arcball,
view: initial_view_matrix_4x4_flat(),
projection: initial_projection_matrix_4x4_flat(),
view_matrix: initial_view_matrix_4x4(),
projection_matrix: initial_projection_matrix_4x4(),
default_model_view_matrix: identity_matrix_4x4(),
shader_program_id: program_id,
vertex_shader_id,
fragment_shader_id,
object_texture_uniform,
texture_coordinates_attribute: texture_coordinate_attribute,
uniform_vertex_type,
uniform_point_cloud_texture_to_use,
point_size: 1,
enable_shading: false,
view_transform_id: view_transform,
projection_transform_id: projection_transform,
model_transform_id: model_transform,
enable_shading_index,
point_cloud_texture_indices,
vertex_array_objects: Vec::with_capacity(num_cameras),
vertex_arrays_size_bytes : Vec::with_capacity(num_cameras),
vertex_color_buffer_objects: Vec::with_capacity(num_cameras),
color_vertex_attribute_location,
renderable_objects: Vec::new(),
})
}
///
/// Run once to set up initial rendering
///
pub fn setup_rendering(&mut self) -> AnyhowResult<()> {
for _ in 0 .. self.num_cameras {
let vao = VertexArray::new_initialized();
vao.bind();
self.vertex_array_objects.push(vao);
let buffer = Buffer::new_initialized();
self.vertex_color_buffer_objects.push(buffer);
self.vertex_arrays_size_bytes.push(0);
}
Ok(())
}
///
/// Update the view matrices
///
pub fn update_view_projection(&mut self, view: [[f32; 4]; 4], _projection: [[f32; 4]; 4]) {
self.view_matrix = view;
self.projection_matrix = initial_projection_matrix_4x4();
}
///
///
///
pub fn update_point_clouds(&mut self,
color_images: &Vec<ImageProxy>,
point_cloud_textures: &Vec<Texture>) -> Result<()>
{
for i in 0 .. self.num_cameras {
let color_image_bytes = color_images.get(i).unwrap();
let color_image_size_bytes = color_image_bytes.len() as i32;
let vao = self.vertex_array_objects.get(i).unwrap();
let vertex_color_buffer = self.vertex_color_buffer_objects.get(i).unwrap();
let mut vertex_array_size_bytes = self.vertex_arrays_size_bytes.get_mut(i).unwrap();
vao.bind();
vertex_color_buffer.bind_as_array_buffer();
//if *vertex_array_size_bytes != color_image_size_bytes {
//println!("Establishing buffer");
unsafe {
gl::BufferData(
gl::ARRAY_BUFFER,
color_image_size_bytes as isize,
null(),
gl::STREAM_DRAW
);
}
*vertex_array_size_bytes = color_image_size_bytes;
//}
let vertex_mapped_buffer = unsafe {
gl::MapBufferRange(
gl::ARRAY_BUFFER,
0,
color_image_size_bytes as isize,
gl::MAP_WRITE_BIT | gl::MAP_INVALIDATE_BUFFER_BIT
) as *mut u8
};
// if vertex_mapped_buffer as usize == 0 {
// let error = gl_get_error().expect_err("should be map buffer range error");
// return Err(PointCloudRendererError::OpenGlError(error));
// }
let result = unsafe {
// NB: This is the working texturing:
std::ptr::copy_nonoverlapping::<u8>(
color_image_bytes.as_ptr(),
vertex_mapped_buffer,
color_image_size_bytes as usize);
gl::UnmapBuffer(gl::ARRAY_BUFFER)
};
// if result == gl::FALSE {
// let error = gl_get_error().expect_err("should be unmap buffer error");
// return Err(PointCloudRendererError::OpenGlError(error));
// }
// THIS IS THE POINT CLOUD
let point_cloud_texture = point_cloud_textures.get(i).unwrap();
unsafe {
gl::BindTexture(gl::TEXTURE_2D, point_cloud_texture.id());
gl::BindImageTexture(
i as GLuint, // image unit (zero-indexed) // TODO - why does it work this way?
point_cloud_texture.id(), // texture
0, // level
gl::FALSE, // layered
0, // layer
gl::READ_ONLY, // access
gl::RGBA32F, //POINT_CLOUD_TEXTURE_FORMAT,
);
gl::ActiveTexture(gl::TEXTURE0);
gl::EnableVertexAttribArray(self.color_vertex_attribute_location as u32);
gl::VertexAttribPointer(
self.color_vertex_attribute_location as u32,
gl::BGRA as i32,
gl::UNSIGNED_BYTE,
gl::TRUE,
0 as i32,
0 as *const c_void,
);
}
}
unsafe {
gl::BindVertexArray(0);
}
gl_get_error()
.map_err(|err| PointCloudRendererError::OpenGlError(err))
}
///
///
///
pub fn render(&mut self) -> Result<()> {
// Animate the models.
for obj in self.renderable_objects.iter_mut() {
match obj.object_type {
ObjectType::Level => continue,
ObjectType::Skybox => continue,
_ => {},
}
obj.increment_rotation_y(0.05); // TODO: Should be based on wall clock instead
}
unsafe {
gl::UseProgram(self.shader_program_id);
gl::Enable(gl::DEPTH_TEST);
gl::Enable(gl::BLEND);
gl::BlendFunc(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA);
gl::PointSize(self.point_size as f32);
// Update view/projection matrices in shader
let typed_projection = self.projection_matrix.as_ptr() as *const GLfloat;
gl::UniformMatrix4fv(self.projection_transform_id.id(), 1, gl::FALSE, typed_projection);
let typed_view = self.view_matrix.as_ptr() as *const GLfloat;
gl::UniformMatrix4fv(self.view_transform_id.id(), 1, gl::FALSE, typed_view);
// Default model transform
let typed_model_view = self.default_model_view_matrix.as_ptr() as *const GLfloat;
gl::UniformMatrix4fv(self.model_transform_id.id(), 1, gl::FALSE, typed_model_view);
// Update render settings in shader
let _enable_shading = if self.enable_shading { 1 } else { 0 };
let enable_shading = 0; // TODO FIXME FIXME FIXME
gl::Uniform1i(self.enable_shading_index, enable_shading);
// Tell GLSL we're doing point clouds
gl::Uniform1i(self.uniform_vertex_type.id() as GLint, 2);
for i in 0 .. self.num_cameras {
let vao = self.vertex_array_objects.get(i).unwrap();
let vertex_array_size_bytes = self.vertex_arrays_size_bytes.get(i).unwrap();
let size = vertex_array_size_bytes / size_of::<BgraPixel>() as i32;
vao.bind();
gl::Uniform1i(self.uniform_point_cloud_texture_to_use.id() as GLint, i as GLint);
gl::DrawArrays(gl::POINTS, 0, size);
}
gl::BindVertexArray(0);
// Now tell GLSL we're doing objects
gl::Uniform1i(self.uniform_vertex_type.id() as GLint, 1);
for positionable in self.renderable_objects.iter() {
positionable.draw(self.model_transform_id);
}
gl_get_error()
.map_err(|err| PointCloudRendererError::OpenGlError(err))
}
}
pub fn set_point_size(&mut self, point_size: u8) {
self.point_size = point_size;
}
pub fn set_enable_shading(&mut self, enable_shading: bool) {
self.enable_shading = enable_shading;
}
}
// TODO: This is useful to write solid colors instead of webcam color data
// std::ptr::copy::<u8>(color_src, vertex_mapped_buffer as *mut u8, color_image_size_bytes as usize);
// std::ptr::write_bytes(vertex_mapped_buffer, 255, color_image_size_bytes as usize);
+577
View File
@@ -0,0 +1,577 @@
//! Based off Microsoft's MIT-licensed k4a library code and examples, specifically
//! `k4apointcloudvisualizer.h`
use std::fmt::Formatter;
use std::mem::size_of;
use std::sync::{Arc, Mutex};
use gl::types::*;
use anyhow::Result as AnyhowResult;
use crate::azure_kinect::sensor_control::CaptureProvider;
use crate::core_types::RgbaF32;
use crate::gui::mouse_camera_arcball::MouseCameraArcball;
use crate::opengl::wrapper::other_misc_wrapper::OpenGlError;
use crate::opengl::wrapper::other_misc_wrapper::{Framebuffer, Renderbuffer};
use crate::opengl::wrapper::texture::Texture;
use crate::point_cloud::debug::capture_proxy::CaptureProxy;
use crate::point_cloud::debug::image_proxy::ImageProxy;
use crate::point_cloud::gpu_point_cloud_converter::{GpuPointCloudConverter, PointCloudComputeError};
use crate::point_cloud::pixel_structs::BgraPixel;
use crate::point_cloud::pixel_structs::DepthPixel;
use crate::point_cloud::point_cloud_renderer::{PointCloudRenderer, PointCloudRendererError};
use crate::point_cloud::util::{colorize_depth_blue_to_red, get_depth_mode_range, ValueRange};
use crate::point_cloud::viewer_image::ViewerImage;
use k4a_sys_temp as k4a_sys;
use kinect::{Image, Calibration, ImageFormat, Capture, Transformation};
pub type Result<T> = std::result::Result<T, PointCloudVisualizerError>;
#[derive(Clone, Debug)]
pub enum PointCloudVisualizerError {
OpenGlError(OpenGlError),
PointCloudRendererError(PointCloudRendererError),
PointCloudComputeError(PointCloudComputeError),
FramebufferError,
DepthToColorConversionFailed,
/// Capture is missing depth info; drop the frame
MissingDepthImage,
/// Capture is missing color info; drop the frame
MissingColorImage,
// TODO: Implement this
ColorSupportNotYetImplemented,
UnsupportedMode,
UnknownError,
}
impl From<OpenGlError> for PointCloudVisualizerError {
fn from(error: OpenGlError) -> Self {
PointCloudVisualizerError::OpenGlError(error)
}
}
impl From<PointCloudComputeError> for PointCloudVisualizerError {
fn from(error: PointCloudComputeError) -> Self {
PointCloudVisualizerError::PointCloudComputeError(error)
}
}
impl std::fmt::Display for PointCloudVisualizerError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let description = match self {
PointCloudVisualizerError::OpenGlError(inner) => {
format!("Visualizer OpenGL error: {}", inner)
},
PointCloudVisualizerError::PointCloudRendererError(inner) => {
format!("Visualizer PointCloudRenderer error: {}", inner)
},
PointCloudVisualizerError::PointCloudComputeError(inner) => {
format!("Visualizer PointCloudCompute error: {}", inner)
},
PointCloudVisualizerError::FramebufferError => "Visualizer Framebuffer Error".into(),
PointCloudVisualizerError::DepthToColorConversionFailed => "Visualizer Depth To Color Conversion Error".into(),
PointCloudVisualizerError::UnsupportedMode => "Visualizer Unsupported Mode Error".into(),
PointCloudVisualizerError::MissingDepthImage => "Visualizer Missing Depth Image (drop frame)".into(),
PointCloudVisualizerError::MissingColorImage => "Visualizer Missing Color Image (drop frame)".into(),
PointCloudVisualizerError::ColorSupportNotYetImplemented => "Visualizer Color Support Not Yet Implemented (TODO)".into(),
PointCloudVisualizerError::UnknownError => "Visualizer Unknown Error".into(),
};
write!(f, "{}", description)
}
}
impl std::error::Error for PointCloudVisualizerError {
fn source(&self) -> Option<&(dyn std::error::Error +'static)> {
// Generic error, no backtrace.
None
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum ColorizationStrategy {
Simple,
Shaded,
Color,
}
pub struct PointCloudVisualizer {
arcball_camera: Arc<Mutex<MouseCameraArcball>>,
num_cameras: usize,
m_dimensions_width : i32,
m_dimensions_height : i32,
enable_color_point_cloud: bool,
colorization_strategy: ColorizationStrategy,
pub point_cloud_renderer: PointCloudRenderer,
pub point_cloud_converters: Vec<GpuPointCloudConverter>,
/// Calibration data for all cameras
all_device_calibrations: Vec<Calibration>,
/// Transformations are constructed from camera calibrations
transformations: Vec<Transformation>,
/// Near and far minima/maxima for the current depth sensor mode.
/// These are constructed for each camera from the camera calibrations.
expected_depth_value_ranges: Vec<ValueRange>,
frame_buffer: Framebuffer,
depth_buffer: Renderbuffer,
last_captures: Vec<Option<CaptureProxy>>,
/// Buffer that holds the depth image transformed to the color coordinate space.
/// Used in color mode only.
transformed_depth_images: Vec<Option<ImageProxy>>,
/// In color mode, this is just a shallow copy of the latest color image.
/// In depth mode, this is a buffer that holds the colorization of the depth image.
point_cloud_colorizations: Vec<Option<ImageProxy>>,
/// Holds the XYZ point cloud as a texture.
/// Format is XYZA, where A (the alpha channel) is unused.
pub xyz_textures: Vec<Texture>,
color_xy_tables: Vec<ImageProxy>,
depth_xy_tables: Vec<ImageProxy>,
/// Background clear color for OpenGL.
clear_color: RgbaF32,
/// For debugging
/// If present, use these instead of camera captures.
debug_static_color_frames: Vec<ImageProxy>,
debug_static_depth_frames: Vec<ImageProxy>,
}
// TODO: Dedup
struct CleanupGuard {}
impl Drop for CleanupGuard {
fn drop(&mut self) {
unsafe {
gl::BindFramebuffer(gl::FRAMEBUFFER, 0);
}
}
}
impl PointCloudVisualizer {
///
/// CTOR
///
pub fn new(num_cameras: usize,
all_device_calibrations: &Vec<Calibration>,
enable_color_point_cloud: bool,
initial_colorization_strategy: ColorizationStrategy,
clear_color: RgbaF32,
arcball_camera: Arc<Mutex<MouseCameraArcball>>) -> Self
{
// Resolution of the point cloud texture
// constexpr ImageDimensions PointCloudVisualizerTextureDimensions = { 1280, 1152 };
let width = 1280;
let height= 1152;
let depth_buffer = Renderbuffer::new_initialized();
unsafe {
gl::BindRenderbuffer(gl::RENDERBUFFER, depth_buffer.id());
gl::RenderbufferStorage(gl::RENDERBUFFER, gl::DEPTH_COMPONENT, width, height);
}
let mut expected_depth_value_ranges = Vec::new();
let mut transformations = Vec::new();
for calibration in all_device_calibrations {
let value_range = get_depth_mode_range(calibration.0.depth_mode)
.expect("Should be in correct depth sensor mode.");
expected_depth_value_ranges.push(value_range);
let transformation = Transformation::from_calibration(&calibration);
transformations.push(transformation);
}
let mut point_cloud_converters = Vec::with_capacity(num_cameras);
let mut transformed_depth_images = Vec::with_capacity(num_cameras);
let mut point_cloud_colorizations = Vec::with_capacity(num_cameras);
let mut xyz_textures = Vec::with_capacity(num_cameras);
let mut last_captures = Vec::with_capacity(num_cameras);
let mut color_xy_tables = Vec::with_capacity(num_cameras);
let mut depth_xy_tables = Vec::with_capacity(num_cameras);
for i in 0 .. num_cameras {
point_cloud_converters.push(GpuPointCloudConverter::new());
transformed_depth_images.push(None);
point_cloud_colorizations.push(None);
last_captures.push(None);
xyz_textures.push(Texture::new());
let calibration = all_device_calibrations.get(i).unwrap();
// TODO: generate color xytable only on `enable_color_point_cloud`.
let color_xy_table = GpuPointCloudConverter::generate_xy_table(
&calibration,
k4a_sys::k4a_calibration_type_t_K4A_CALIBRATION_TYPE_COLOR,
).unwrap();
let depth_xy_table = GpuPointCloudConverter::generate_xy_table(
&calibration,
k4a_sys::k4a_calibration_type_t_K4A_CALIBRATION_TYPE_DEPTH,
).unwrap();
color_xy_tables.push(ImageProxy::consume_k4a_image(color_xy_table));
depth_xy_tables.push(ImageProxy::consume_k4a_image(depth_xy_table));
}
let mut debug_static_color_frames = Vec::new();
//debug_static_color_frames.push(ImageProxy::from_file("output/color_src_0", 0, 0, 0, ImageFormat::ColorBgra32).unwrap());
//debug_static_color_frames.push(ImageProxy::from_file("output/color_src_1", 0, 0, 0, ImageFormat::ColorBgra32).unwrap());
let mut debug_static_depth_frames = Vec::new();
//debug_static_depth_frames.push(ImageProxy::from_file("output/depth_src_0", 3840, 2160, 7680, ImageFormat::Depth16).unwrap());
//debug_static_depth_frames.push(ImageProxy::from_file("output/depth_src_1", 3840, 2160, 7680, ImageFormat::Depth16).unwrap());
let mut visualizer = Self {
num_cameras,
arcball_camera: arcball_camera.clone(),
m_dimensions_width: width,
m_dimensions_height: height,
enable_color_point_cloud,
point_cloud_renderer: PointCloudRenderer::new(num_cameras, arcball_camera.clone()).expect("should build"),
point_cloud_converters,
frame_buffer: Framebuffer::new_initialized(),
depth_buffer,
color_xy_tables,
depth_xy_tables,
colorization_strategy: initial_colorization_strategy,
transformations,
last_captures,
transformed_depth_images,
point_cloud_colorizations,
xyz_textures,
expected_depth_value_ranges,
clear_color,
debug_static_color_frames,
debug_static_depth_frames,
all_device_calibrations: all_device_calibrations.clone(),
};
visualizer.set_colorization_strategy(initial_colorization_strategy).expect("Should work");
visualizer
}
/// Setup initial rendering
///
pub fn setup_rendering(&mut self) -> AnyhowResult<()> {
self.point_cloud_renderer.setup_rendering()
}
///
///
///
pub fn set_point_size(&mut self, point_size: u8) {
self.point_cloud_renderer.set_point_size(point_size);
}
///
///
///
pub fn update_texture(&mut self, texture: &ViewerImage, mut captures: Vec<CaptureProxy>) -> Result<()> {
self.update_texture_id(texture.texture_id(), captures)
}
///
/// Take the latest capture, calculate updates to the xyz texture, then render the point cloud to
/// the output texture.
///
pub fn update_texture_id(&mut self, texture_id: GLuint, mut captures: Vec<CaptureProxy>) -> Result<()> {
// Update the point cloud renderer with the latest point data
self.update_point_clouds(captures)?;
// Set up rendering to a texture
unsafe {
gl::BindRenderbuffer(gl::RENDERBUFFER, self.depth_buffer.id());
gl::BindFramebuffer(gl::FRAMEBUFFER, self.frame_buffer.id());
}
let _cleanup_guard = CleanupGuard {};
unsafe {
gl::FramebufferRenderbuffer(
gl::FRAMEBUFFER,
gl::DEPTH_ATTACHMENT,
gl::RENDERBUFFER,
self.depth_buffer.id()
);
gl::FramebufferTexture(gl::FRAMEBUFFER, gl::COLOR_ATTACHMENT0, texture_id, 0);
// DrawBuffers writes to a COLOR BUFFER
// COLOR_ATTACHMENT{N} - The fragment shader output value is written into the nth color
// attachment of the current framebuffer.
gl::DrawBuffers(1, &gl::COLOR_ATTACHMENT0);
let frame_buffer_status = gl::CheckFramebufferStatus(gl::FRAMEBUFFER);
if frame_buffer_status != gl::FRAMEBUFFER_COMPLETE {
return Err(PointCloudVisualizerError::FramebufferError);
}
gl::Viewport(0, 0, self.m_dimensions_width, self.m_dimensions_height);
gl::Enable(gl::DEPTH_TEST);
gl::ClearColor(
self.clear_color.red,
self.clear_color.green,
self.clear_color.blue,
self.clear_color.alpha,
);
gl::ClearDepth(1.0);
gl::Clear(gl::COLOR_BUFFER_BIT | gl::DEPTH_BUFFER_BIT);
}
match self.arcball_camera.lock() {
Ok(arcball) => {
let view: [[f32; 4]; 4] = (arcball.get_view_matrix()).into();
let perspective: [[f32; 4]; 4] = (arcball.get_perspective_matrix()).into();
self.point_cloud_renderer.update_view_projection(view, perspective);
},
Err(_) => {},
}
let render_status = self.point_cloud_renderer.render();
unsafe {
gl::BindRenderbuffer(gl::RENDERBUFFER, 0);
}
render_status
.map_err(|err| PointCloudVisualizerError::PointCloudRendererError(err))
}
///
///
///
fn update_point_clouds(&mut self, mut captures: Vec<CaptureProxy>) -> Result<()> {
if captures.len() != self.num_cameras {
println!("Length of captures in insufficient (unplug the cameras from USB and retry): {}", captures.len());
return Ok(());
}
for (camera_index, capture) in captures.drain(0 .. self.num_cameras)
.into_iter()
.enumerate() {
self.update_point_clouds_for_camera(camera_index, capture)?;
}
let mut color_captures = Vec::new();
let color_frames = if self.debug_static_color_frames.len() > 0 {
&self.debug_static_color_frames
} else {
// TODO: wrt k4a::Image.clone(), k4a should use ref counting. (I did this one other place a few commits ago.
// Hope it's not ref leaking.)
let colorizations: Vec<ImageProxy> = self.point_cloud_colorizations.iter()
.map(|img| img.as_ref().map(|img| img.clone()).unwrap()) // Potential ref leak #1
.collect();
color_captures = colorizations;
&color_captures
};
self.point_cloud_renderer.update_point_clouds(
&color_frames,
&self.xyz_textures
).map_err(|err| PointCloudVisualizerError::PointCloudRendererError(err))
}
fn update_point_clouds_for_camera(&mut self, camera_index: usize, capture: CaptureProxy) -> Result<()> {
//
// Depth Image extractor
//
let transformation = self.transformations.get(camera_index).unwrap();
let mut depth_image = match capture.get_depth_image() {
Ok(img) => img,
Err(_e) => {
// Capture doesn't have depth info. Drop the capture.
return Err(PointCloudVisualizerError::MissingDepthImage);
},
};
let maybe_color_image = capture.get_color_image();
if self.enable_color_point_cloud {
if maybe_color_image.is_err() {
return Err(PointCloudVisualizerError::MissingColorImage);
}
if self.colorization_strategy == ColorizationStrategy::Color {
// TODO: TEMP SUPPORT MULTI-CAMERA
if let Some(transformed_depth_image) = self.transformed_depth_images.get_mut(camera_index)
.unwrap()
.as_mut() {
unsafe {
let result = k4a_sys::k4a_transformation_depth_image_to_color_camera(
transformation.get_handle(),
depth_image.get_handle(),
transformed_depth_image.get_handle(),
);
if result != k4a_sys::k4a_buffer_result_t_K4A_BUFFER_RESULT_SUCCEEDED {
return Err(PointCloudVisualizerError::DepthToColorConversionFailed);
}
depth_image = transformed_depth_image;
}
}
}
}
//
// Convert depth image to depth texture
//
let use_depth_image = self.debug_static_depth_frames.get(camera_index)
.unwrap_or(&depth_image);
let result = self.point_cloud_converters.get(camera_index).unwrap().convert(
&use_depth_image,
&mut self.xyz_textures.get_mut(camera_index).unwrap(), // TODO: TEMP MULTI CAMERA SUPPORT
camera_index
);
if let Err(err) = result {
return Err(PointCloudVisualizerError::PointCloudComputeError(err));
}
// TODO: TEMP MULTI CAMERA SUPPORT
if let Some(mut inner) = self.last_captures.get_mut(camera_index) {
*inner = Some(capture.clone());
}
if self.colorization_strategy == ColorizationStrategy::Color {
let color_image = maybe_color_image.expect("logic above should ensure present");
// TODO: TEMP MULTI CAMERA SUPPORT
if let Some(mut inner) = self.point_cloud_colorizations.get_mut(camera_index) {
*inner = Some(color_image.clone());
}
} else {
// This creates a color spectrum based on depth.
let dst_length = use_depth_image.get_width_pixels() * use_depth_image.get_height_pixels();
let depth_value_range = self.expected_depth_value_ranges.get(camera_index).unwrap();
unsafe {
// src: DepthPixel
let src_pixel_buffer = use_depth_image.get_buffer();
let typed_src_pixel_buffer = src_pixel_buffer as *const DepthPixel;
// TODO TEMP MULTI-CAMERA SUPPORT
// dst: BgraPixel
let dst_pixel_buffer = self.point_cloud_colorizations.get_mut(camera_index)
.unwrap()
.as_ref()
.expect("point cloud color image must be set")
.get_buffer();
let typed_dst_pixel_buffer = dst_pixel_buffer as *mut BgraPixel;
for i in 0 .. dst_length as isize {
let src_pixel = *typed_src_pixel_buffer.offset(i);
let pixel = colorize_depth_blue_to_red(src_pixel, depth_value_range);
(*typed_dst_pixel_buffer.offset(i)).red = pixel.red;
(*typed_dst_pixel_buffer.offset(i)).green = pixel.green;
(*typed_dst_pixel_buffer.offset(i)).blue = pixel.blue;
(*typed_dst_pixel_buffer.offset(i)).alpha = pixel.alpha;
}
}
}
Ok(())
}
///
///
///
pub fn set_colorization_strategy(&mut self, strategy: ColorizationStrategy) -> Result<()> {
for i in 0 .. self.num_cameras {
self.set_colorization_strategy_for_camera(i, strategy)?;
}
Ok(())
}
fn set_colorization_strategy_for_camera(&mut self, camera_index: usize, strategy: ColorizationStrategy) -> Result<()> {
if strategy == ColorizationStrategy::Color && !self.enable_color_point_cloud {
return Err(PointCloudVisualizerError::UnsupportedMode);
}
let calibration = self.all_device_calibrations.get(camera_index).unwrap();
self.colorization_strategy = strategy;
self.point_cloud_renderer.set_enable_shading(strategy == ColorizationStrategy::Shaded);
if strategy == ColorizationStrategy::Color {
let width = calibration.0.color_camera_calibration.resolution_width;
let height = calibration.0.color_camera_calibration.resolution_height;
let stride = width * size_of::<DepthPixel>() as i32;
// TODO: TEMP MULTI-CAMERA SUPPORT
if let Some(mut transformed_depth_image) = self.transformed_depth_images.get_mut(camera_index) {
*transformed_depth_image = Some(ImageProxy::consume_k4a_image(Image::create(
ImageFormat::Depth16,
width as u32,
height as u32,
stride as u32,
).expect("Construction should work FIXME")));
}
for point_cloud_converter in self.point_cloud_converters.iter_mut() {
point_cloud_converter.set_active_xy_table(&self.color_xy_tables.get(camera_index).unwrap())?; // TODO: TEMP MULTI-CAMERA SUPPORT
}
} else {
let width = calibration.0.depth_camera_calibration.resolution_width as u32;
let height = calibration.0.depth_camera_calibration.resolution_height as u32;
let stride = width as i32 * size_of::<BgraPixel>() as i32;
// TODO: TEMP MULTI-CAMERA SUPPORT
if let Some(mut point_cloud_colorization) = self.point_cloud_colorizations.get_mut(camera_index) {
*point_cloud_colorization = Some(ImageProxy::consume_k4a_image(Image::create(
ImageFormat::ColorBgra32,
width,
height,
stride as u32,
).expect("Construction should work FIXME")));
}
for point_cloud_converter in self.point_cloud_converters.iter_mut() {
let xy_table = self.depth_xy_tables.get(camera_index).unwrap();
point_cloud_converter.set_active_xy_table(&xy_table)?;
}
}
self.xyz_textures.get_mut(0).unwrap().reset(); // TODO: TEMP MULTI-CAMERA SUPPORT
// TODO: TEMP MULTI-CAMERA SUPPORT
if let Some(capture) = self.last_captures.get_mut(camera_index)
.unwrap()
.as_ref()
{
let capture = (*capture).clone();
// TODO: TEMP MULTI-CAMERA SUPPORT
let mut captures = Vec::new();
captures.push(capture);
self.update_point_clouds(captures);
}
Ok(())
}
}
@@ -0,0 +1,53 @@
// This is taken from Microsoft's MIT-licensed k4a libraries.
// From the file `tools/k4aviewer/graphics/shaders/gpudepthtopointcloudconverter.cpp`
#version 430
layout(location=0, rgba32f) writeonly uniform image2D destTex;
layout(location=1, r16ui) readonly uniform uimage2D depthImage;
layout(location=2, rg32f) readonly uniform image2D xyTable;
layout(local_size_x = 1, local_size_y = 1) in;
void main()
{
ivec2 pixel = ivec2(gl_GlobalInvocationID.xy);
float vertexValue = float(imageLoad(depthImage, pixel));
// TODO: The problem is that these are bound to (0.0, 0.0). It isn't working.
vec2 xyValue = imageLoad(xyTable, pixel).xy;
float alpha = 1.0f;
// vertexPosition format: RGB colors as floats, but not range [0.0, 1.0], but rather [0.0, 255.0]
// HOWEVER, there are operations that reverse the sign of the 'x' channel ('r') and scale down by 1000 below.
vec3 vertexPosition = vec3(vertexValue * xyValue.x, vertexValue * xyValue.y, vertexValue);
// Invalid pixels have their XY table values set to 0.
// Set the rest of their values to 0 so clients can pick them out.
//
if (xyValue.x == 0.0f && xyValue.y == 0.0f)
{
alpha = 0.0f;
vertexValue = 0.0f;
}
// Vertex positions are in millimeters, but everything else is in meters, so we need to convert
//
vertexPosition /= 1000.0f;
// OpenGL and K4A have different conventions on which direction is positive -
// we need to flip the X coordinate.
//
vertexPosition.x *= -1;
// Distance Threshold
// TODO: Make configurable.
/*if (vertexPosition.z > 1.5) {
alpha = 0.0f;
vertexPosition.x = 0.0;
vertexPosition.y = 0.0;
vertexPosition.z = 0.0;
}*/
imageStore(destTex, pixel, vec4(vertexPosition, alpha));
}
@@ -0,0 +1,39 @@
// This is taken from Microsoft's MIT-licensed k4a libraries.
// From the file `tools/k4aviewer/graphics/shaders/k4apointcloudshaders.h`
#version 430
in vec4 vertexColor;
out vec4 fragmentColor;
in vec2 texCoord;
uniform bool enableShading;
uniform int vertexType;
uniform sampler2D objTexture; // C++ method
//layout(location=2, rgba32f) readonly uniform image2D objTexture; // k4a method
void main()
{
if (vertexColor.a == 0.0f)
{
discard;
}
if (vertexType == 1)
{
// Object mode
vec2 coord = texCoord;
fragmentColor = texture2D(objTexture, coord);
}
else if (vertexType == 2)
{
// Point cloud mode
fragmentColor = vertexColor;
}
if (fragmentColor.a == 0.0f)
{
discard;
}
}
@@ -0,0 +1,212 @@
// This is taken from Microsoft's MIT-licensed k4a libraries.
// From the file `tools/k4aviewer/graphics/shaders/k4apointcloudshaders.h`
#version 430
// NB: It appears that locations need to be wide enough apart, or the colors bleed across locations.
layout(location=0) in vec4 inColor;
//layout(location=10) in vec4 inColor1; // TODO: Get rid of this!
// For use with objects.
// Might restructure the entire program.
in vec3 position;
in vec3 normal;
in vec2 vTextureCoord;
out vec2 texCoord;
out vec4 vertexColor;
// Transformation matrices
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
// 1 = object, 2 = pointCloud
uniform int vertexType;
// NB: This is not a scalable approach. But it might work for now.
// 0 = camera0, 1 = camera1
uniform int pointCloudTextureToUse;
layout(rgba32f, binding=0) readonly uniform image2D pointCloudTexture0;
layout(rgba32f, binding=1) readonly uniform image2D pointCloudTexture1;
uniform bool enableShading;
// bool GetPoint3d(in vec2 pointCloudSize, in ivec2 point2d, out vec3 point3d)
// {
// if (point2d.x < 0 || point2d.x >= pointCloudSize.x ||
// point2d.y < 0 || point3d.y >= pointCloudSize.y)
// {
// return false;
// }
//
// point3d = imageLoad(pointCloudTexture0, point2d).xyz;
// if (point3d.z <= 0)
// {
// return false;
// }
//
// return true;
// }
void main()
{
ivec2 pointCloudSize0 = imageSize(pointCloudTexture0);
int pointCloudLength0 = pointCloudSize0.x * pointCloudSize0.y;
vec3 vertexPosition;
vec4 colorOut;
// We're having to multiplex on gl_VertexID, and I don't think we get double the range for two
// cameras. This effectively 'downsamples' each camera. We need to find a way to double this.
// I hate drawing like this
//if (gl_VertexID < pointCloudLength0) { // TODO - this is the one to enable
if (gl_VertexID < 10) {
// Camera #0
ivec2 currentDepthPixelCoordinates = ivec2(gl_VertexID % pointCloudSize0.x, gl_VertexID / pointCloudSize0.x);
vertexPosition = imageLoad(pointCloudTexture0, currentDepthPixelCoordinates).xyz;
} else {
// Camera #1
ivec2 pointCloudSize1 = imageSize(pointCloudTexture1);
int pointCloudLength1 = pointCloudSize1.x * pointCloudSize1.y;
ivec2 currentDepthPixelCoordinates = ivec2(gl_VertexID % pointCloudSize1.x, gl_VertexID / pointCloudSize1.x);
// // Let's move the model away a bit.
// vec3 originalPosition = imageLoad(pointCloudTexture1, currentDepthPixelCoordinates).xyz;
// vertexPosition = vec3(
// originalPosition.x - 1.5,
// originalPosition.y,
// originalPosition.z
// );
vertexPosition = imageLoad(pointCloudTexture1, currentDepthPixelCoordinates).xyz;
vertexPosition = position;
}
if (view[0][0] > 0.5) {
vertexPosition.x -= 2.0;
vertexPosition.y -= 2.0;
}
vertexColor = colorOut;
texCoord = vTextureCoord;
if (vertexType == 1) {
// Object mode
vertexColor = colorOut;
texCoord = vTextureCoord;
} else if (vertexType == 2) {
// Point cloud mode
if (pointCloudTextureToUse == 0) {
ivec2 currentDepthPixelCoordinates = ivec2(gl_VertexID % pointCloudSize0.x, gl_VertexID / pointCloudSize0.x);
vertexPosition = imageLoad(pointCloudTexture0, currentDepthPixelCoordinates).xyz;
vertexPosition.x += 1.0; // Camera 0 looks best on the left
colorOut = vec4(
inColor.r,
inColor.g,
inColor.b,
255
);
} else if (pointCloudTextureToUse == 1) {
ivec2 pointCloudSize1 = imageSize(pointCloudTexture1);
int pointCloudLength1 = pointCloudSize1.x * pointCloudSize1.y;
ivec2 currentDepthPixelCoordinates = ivec2(gl_VertexID % pointCloudSize1.x, gl_VertexID / pointCloudSize1.x);
vertexPosition = imageLoad(pointCloudTexture1, currentDepthPixelCoordinates).xyz;
vertexPosition.x -= 1.0; // Camera 1 looks best on the right
colorOut = vec4(
inColor.r,
inColor.g,
inColor.b,
255
);
}
if (vertexPosition.z > 1.5) {
// Depth threshold
// This leads directly to pixel discard
colorOut.a = 0.0;
}
vertexColor = colorOut;
}
gl_Position = projection * view * model * vec4(vertexPosition, 1);
// Pass along the 'invalid pixel' flag as the alpha channel
//
// if (vertexPosition.z == 0.0f)
// {
// vertexColor.a = 0.0f;
// }
// NB: This is affecting the second camera. Disabling for now.
// if (enableShading)
// {
// // Compute the location of the closest neighbor pixel to compute lighting
// //
// vec3 closestNeighbor = vertexPosition;
//
// // If no neighbors have data, default to 1 meter behind point.
// //
// closestNeighbor.z += 1.0f;
//
// vec3 outPoint;
// if (GetPoint3d(pointCloudSize, currentDepthPixelCoordinates - ivec2(1, 0), outPoint))
// {
// if (closestNeighbor.z > outPoint.z)
// {
// closestNeighbor = outPoint;
// }
// }
// if (GetPoint3d(pointCloudSize, currentDepthPixelCoordinates + ivec2(1, 0), outPoint))
// {
// if (closestNeighbor.z > outPoint.z)
// {
// closestNeighbor = outPoint;
// }
// }
// if (GetPoint3d(pointCloudSize, currentDepthPixelCoordinates - ivec2(0, 1), outPoint))
// {
// if (closestNeighbor.z > outPoint.z)
// {
// closestNeighbor = outPoint;
// }
// }
// if (GetPoint3d(pointCloudSize, currentDepthPixelCoordinates + ivec2(0, 1), outPoint))
// {
// if (closestNeighbor.z > outPoint.z)
// {
// closestNeighbor = outPoint;
// }
// }
//
// vec3 lightPosition = vec3(0, 0, 0);
// float occlusion = length(vertexPosition - closestNeighbor) * 20.0f;
// float diffuse = 1.0f - clamp(occlusion, 0.0f, 0.6f);
//
// float distance = length(lightPosition - vertexPosition);
//
// // Attenuation term for light source that covers distance up to 50 meters
// // http://wiki.ogre3d.org/tiki-index.php?page=-Point+Light+Attenuation
// //
// float attenuation = 1.0 / (1.0 + 0.09 * distance + 0.032 * distance * distance);
//
// vertexColor = vec4(attenuation * diffuse * vertexColor.rgb, vertexColor.a);
// }
}
@@ -0,0 +1,23 @@
//#version 140
#version 420
uniform mat4 view;
uniform mat4 projection;
in vec4 inColor; // UNUSED
layout(rgba32f, binding=0) readonly uniform image2D pointCloudTexture0; // UNUSED
layout(rgba32f, binding=1) readonly uniform image2D pointCloudTexture1; // UNUSED
in vec3 position;
in vec3 normal;
uniform bool enableShading; // UNUSED
out vec3 v_position;
//out vec3 v_normal;
void main() {
v_position = position;
//v_normal = normal;
gl_Position = projection * view * vec4(v_position * 0.005, 1.0);
}
+69
View File
@@ -0,0 +1,69 @@
use crate::point_cloud::pixel_structs::{DepthPixel, BgraPixel};
use imgui::sys::igColorConvertHSVtoRGB;
use k4a_sys_temp as k4a_sys;
/// Ranges of values we can expect from the depth sensor, depending on its current configuration.
pub struct ValueRange {
/// Lowest (nearest) value reported from the depth sensor in millimeters.
pub near: DepthPixel,
/// Highest (furthest) value reported from the depth sensor in millimeters.
pub far: DepthPixel,
}
/// Gets the range of values that we expect to see from the depth camera
/// when using a given depth mode, in millimeters
pub fn get_depth_mode_range(depth_mode: k4a_sys::k4a_depth_mode_t) -> Option<ValueRange> {
match depth_mode {
k4a_sys::k4a_depth_mode_t_K4A_DEPTH_MODE_NFOV_2X2BINNED => Some(ValueRange { near: 500, far: 5800 }),
k4a_sys::k4a_depth_mode_t_K4A_DEPTH_MODE_NFOV_UNBINNED => Some(ValueRange { near: 500, far: 4000 }),
k4a_sys::k4a_depth_mode_t_K4A_DEPTH_MODE_WFOV_2X2BINNED => Some(ValueRange { near: 250, far: 3000 }),
k4a_sys::k4a_depth_mode_t_K4A_DEPTH_MODE_WFOV_UNBINNED => Some(ValueRange { near: 250, far: 2500 }),
k4a_sys::k4a_depth_mode_t_K4A_DEPTH_MODE_PASSIVE_IR => None, // TODO: Return error
_ => None, // TODO: Return error
}
}
/// This is a port of Microsoft's `ColorizeBlueToRed` in the open source / MIT-licensed libk4a
pub fn colorize_depth_blue_to_red(depth_pixel: DepthPixel, value_range: &ValueRange) -> BgraPixel {
let pixel_max = libc::uint8_t::max_value();
let mut result = BgraPixel { blue: 0, green: 0, red: 0, alpha: pixel_max };
if depth_pixel == 0 {
// If the pixel is actually zero and not just below the min value, make it black.
return result;
}
let clamped_value = depth_pixel
.min(value_range.far)
.max(value_range.near);
// Normalize to [0, 1]
let mut hue = (clamped_value - value_range.near) as f32 / (value_range.far - value_range.near) as f32;
// The 'hue' coordinate in HSV is a polar coordinate, so it 'wraps'.
// Purple starts after blue and is close enough to red to be a bit unclear,
// so we want to go from blue to red. Purple starts around .6666667,
// so we want to normalize to [0, .6666667].
let range = 2.0f32 / 3.0;
hue *= range;
// We want blue to be close and red to be far, so we need to reflect the
// hue across the middle of the range.
hue = range - hue;
let mut red = 0.0f32;
let mut green= 0.0f32;
let mut blue = 0.0f32;
unsafe {
igColorConvertHSVtoRGB(hue, 1.0, 1.0, &mut red, &mut green, &mut blue);
}
let pixel_max = pixel_max as f32;
result.red = (red * pixel_max) as u8;
result.green = (green * pixel_max) as u8;
result.blue = (blue * pixel_max) as u8;
result
}
+199
View File
@@ -0,0 +1,199 @@
use std::fmt::Formatter;
use std::ptr::null;
use gl;
use gl::types::*;
use libc::uint8_t;
use crate::opengl::wrapper::buffer::Buffer;
use crate::opengl::wrapper::other_misc_wrapper::OpenGlError;
use crate::opengl::wrapper::other_misc_wrapper::gl_get_error;
use crate::opengl::wrapper::texture::Texture;
use crate::point_cloud::pixel_structs::BgraPixel;
pub type Result<T> = std::result::Result<T, ViewerImageError>;
#[derive(Clone, Debug)]
pub enum ViewerImageError {
OpenGlError(OpenGlError),
}
impl From<OpenGlError> for ViewerImageError {
fn from(error: OpenGlError) -> Self {
ViewerImageError::OpenGlError(error)
}
}
impl std::fmt::Display for ViewerImageError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let description = match self {
ViewerImageError::OpenGlError(inner) => {
format!("Visualizer OpenGL error: {}", inner)
},
};
write!(f, "{}", description)
}
}
impl std::error::Error for ViewerImageError {
fn source(&self) -> Option<&(dyn std::error::Error +'static)> {
// Generic error, no backtrace.
None
}
}
#[derive(Clone, Debug)]
pub struct ImageDimensions {
pub width: u32,
pub height: u32,
}
#[derive(Clone, Debug)]
pub struct ViewerImage {
dimensions: ImageDimensions,
format: GLenum,
texture: Texture,
texture_buffer: Buffer,
texture_buffer_size: GLuint,
}
// GLenum format = GL_BGRA (default), but overridden as gl::RGBA
// GLenum internalFormat = GL_RGBA8 (default)
impl ViewerImage {
/// private ctor
fn new(
dimensions: ImageDimensions,
format: GLenum
) -> Result<Self> {
let texture_buffer_size = (dimensions.width * dimensions.height) as GLuint
* get_format_pixel_element_count(format);
let texture = Texture::new_initialized();
let texture_buffer = Buffer::new_initialized();
unsafe {
gl::BindBuffer(gl::PIXEL_UNPACK_BUFFER, texture_buffer.id());
gl::BufferData(gl::PIXEL_UNPACK_BUFFER, texture_buffer_size as isize, null(), gl::STREAM_DRAW);
gl::BindBuffer(gl::PIXEL_UNPACK_BUFFER, 0);
}
Ok(Self {
dimensions,
format,
texture_buffer_size,
texture,
texture_buffer,
})
}
pub fn create(
width: u32,
height: u32,
) -> Result<Self>
{
let dimensions = ImageDimensions { width, height };
let format = gl::RGBA;
let mut viewer_image = ViewerImage::new(dimensions, format)?;
unsafe {
gl::BindTexture(gl::TEXTURE_2D, viewer_image.texture.id());
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::LINEAR as i32);
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::LINEAR as i32);
gl::PixelStorei(gl::UNPACK_ROW_LENGTH, 0);
gl::TexStorage2D(
gl::TEXTURE_2D,
1,
gl::RGBA8,
width as i32,
height as i32,
);
viewer_image.update_texture(None)?;
}
Ok(viewer_image)
}
pub fn texture_id(&self) -> GLuint {
self.texture.id()
}
pub unsafe fn update_texture(&mut self, data: Option<&[uint8_t]>) -> Result<()> {
gl::BindBuffer(gl::PIXEL_UNPACK_BUFFER, self.texture_buffer.id());
gl::BindTexture(gl::TEXTURE_2D, self.texture.id());
let _cleanup_guard = CleanupGuard {};
let buffer = gl::MapBufferRange(
gl::PIXEL_UNPACK_BUFFER,
0,
self.texture_buffer_size as isize,
gl::MAP_WRITE_BIT | gl::MAP_INVALIDATE_BUFFER_BIT,
) as *mut BgraPixel; // TODO: It's actually RGBA, not BGRA.
if buffer as usize == 0 {
return Err(ViewerImageError::OpenGlError(gl_get_error().err().unwrap()));
}
// TODO COPY DATA
match data {
Some(_) => {
unreachable!("TODO")
},
None => {
unsafe {
*buffer = std::mem::zeroed();
}
},
}
let result = gl::UnmapBuffer(gl::PIXEL_UNPACK_BUFFER);
if result == 0 {
return Err(ViewerImageError::OpenGlError(gl_get_error().err().unwrap()));
}
gl::TexSubImage2D(
gl::TEXTURE_2D,
0,
0,
0,
self.dimensions.width as i32,
self.dimensions.height as i32,
self.format,
gl::UNSIGNED_BYTE,
null(),
);
gl_get_error()
.map_err(|err| ViewerImageError::OpenGlError(err))
}
}
pub fn get_format_pixel_element_count(format: GLenum) -> GLuint {
match format {
gl::RED => 1,
gl::RG => 2,
gl::RGB | gl::BGR => 3,
gl::RGBA | gl::BGRA => 4,
_ => unreachable!("bad get_format_pixel_element_count") // TODO: Ugh.
}
}
// TODO: Dedup
struct CleanupGuard {}
impl Drop for CleanupGuard {
fn drop(&mut self) {
println!("Running CleanupGuard");
unsafe {
gl::BindBuffer(gl::PIXEL_UNPACK_BUFFER, 0);
}
}
}
+108
View File
@@ -0,0 +1,108 @@
use crate::opengl::wrapper::other_misc_wrapper::{gl_get_error, OpenGlError};
use gl::types::*;
use gl;
use std::ffi::c_void;
use std::fmt::Formatter;
use std::fs::{File, OpenOptions};
use std::io::Write;
use std::path::Path;
use std::{fmt, io};
/// WebcamWriter uses Webcamoid's akvcam kernel driver to stream output to a /dev/video*
/// file. Their wiki contains all of the setup instructions, and I'm checked in some example
/// configs into this repo.
///
/// https://github.com/webcamoid/akvcam
pub struct WebcamWriter {
/// Internal byte buffer to pull from OpenGL texture and flush to file.
buffer: Vec<u8>,
buffer_size: usize,
/// File to flush raw RGB24 bytes to.
file: File,
}
impl WebcamWriter {
/// Create a WebcamWriter that outputs to the supplied file.
/// The file handle is created and owned by WebcamWriter.
pub fn open_file(filename: &str, width: usize, height: usize, bit_depth: usize)
-> Result<Self, io::Error>
{
let path = Path::new(filename);
let file = OpenOptions::new()
.read(true)
.truncate(true)
.write(true)
.open(path)?;
let buffer_size = width * height * bit_depth;
info!("Webcam writer buffer size: {} (width: {}, height: {}, bit depth: {}",
buffer_size, width, height, bit_depth);
let mut buffer = Vec::with_capacity(buffer_size);
buffer.resize(buffer_size, 0);
Ok(Self {
buffer,
buffer_size,
file,
})
}
// TODO: Make it easy to update for new textures of different sizes (dynamic buffer resize)
/// Grab the current texture held by `texture_id` and immediately write it to file.
pub fn write_current_frame_to_file(&mut self, texture_id: GLuint) -> Result<(), WebcamError> {
let typed_buffer = self.buffer.as_mut_ptr() as *mut c_void;
unsafe {
gl::BindTexture(gl::TEXTURE_2D, texture_id);
gl::GetnTexImage(gl::TEXTURE_2D, 0, gl::RGB, gl::UNSIGNED_BYTE, self.buffer_size as i32, typed_buffer);
gl_get_error()?;
gl::BindTexture(gl::TEXTURE_2D, 0);
}
self.file.write_all(&self.buffer)?;
self.file.flush()?;
Ok(())
}
}
/// Errors from this module.
#[derive(Debug)]
pub enum WebcamError {
IoError(io::Error),
OpenGlError(OpenGlError),
}
impl fmt::Display for WebcamError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let explain = match self {
WebcamError::IoError(e) => format!("IoError: {:?}", e),
WebcamError::OpenGlError(e) => format!("OpenGL error: {:?}", e),
};
write!(f, "{}", explain)
}
}
impl std::error::Error for WebcamError {
fn source(&self) -> Option<&(dyn std::error::Error +'static)> {
None // Generic error, no backtrace.
}
}
impl From<io::Error> for WebcamError {
fn from(error: io::Error) -> Self {
WebcamError::IoError(error)
}
}
impl From<OpenGlError> for WebcamError {
fn from(error: OpenGlError) -> Self {
WebcamError::OpenGlError(error)
}
}
+104
View File
@@ -0,0 +1,104 @@
use std::time::{UNIX_EPOCH, SystemTime};
use rand::distributions::Uniform;
use rand::Rng;
#[derive(Copy, Clone, Debug)]
pub enum Color {
Black,
White,
Red,
Green,
Blue,
Yellow, // Red + Green
Teal, // Green + Blue
Purple, // Red + Blue
Custom { r: u8, g: u8, b: u8, a: u8 },
}
impl Color {
pub fn get_time_based_color() -> Self {
let start = SystemTime::now();
let timestamp = start
.duration_since(UNIX_EPOCH)
.expect("Time should work.");
let seconds = timestamp.as_secs();
match seconds % 10 {
0 | 1 => Color::Purple, //Color::Black,
2 | 3 => Color::Blue,
4 | 5 => Color::Red,
6 | 7 => Color::Green,
8 | 9 => Color::White,
_ => Color::White,
}
}
pub fn get_random_rgb_color() -> Self {
let mut rng = rand::thread_rng();
let range = Uniform::new_inclusive(0u8, 255u8);
let r = rng.sample(&range);
let g = rng.sample(&range);
let b = rng.sample(&range);
Color::Custom { r, g, b, a: 255 }
}
pub fn get_u8_r(&self) -> u8 {
match &self {
Color::Black => 0,
Color::White => 255,
Color::Red => 255,
Color::Green => 0,
Color::Blue => 0,
Color::Yellow => 255,
Color::Teal => 0,
Color::Purple => 255,
Color::Custom {r, g, b, a} => *r,
}
}
pub fn get_u8_g(&self) -> u8 {
match &self {
Color::Black => 0,
Color::White => 255,
Color::Red => 0,
Color::Green => 255,
Color::Blue => 0,
Color::Yellow => 255,
Color::Teal => 255,
Color::Purple => 0,
Color::Custom {r, g, b, a} => *g,
}
}
pub fn get_u8_b(&self) -> u8 {
match &self {
Color::Black => 0,
Color::White => 255,
Color::Red => 0,
Color::Green => 0,
Color::Blue => 255,
Color::Yellow => 0,
Color::Teal => 255,
Color::Purple => 255,
Color::Custom {r, g, b, a} => *b,
}
}
pub fn get_u8_a(&self) -> u8 {
match &self {
Color::Black => 255,
Color::White => 255,
Color::Red => 255,
Color::Green => 255,
Color::Blue => 255,
Color::Yellow => 255,
Color::Teal => 255,
Color::Purple => 255,
Color::Custom {r, g, b, a} => *a,
}
}
}
+150
View File
@@ -0,0 +1,150 @@
use anyhow::anyhow;
use cgmath::num_traits::Float;
use crate::AnyhowResult;
use crate::zeromq::color::Color;
use crate::zeromq::point::Point;
use k4a_sys_temp as k4a_sys;
use kinect::Image;
use kinect::ImageFormat;
use std::mem::size_of;
pub struct PointCloudResult {
pub point_cloud_image: Image,
pub point_count: u32,
}
/// This has been adapted from libk4a sources (MIT-licensed).
pub fn calculate_point_cloud(depth_image: &Image, xy_table_image: &Image) -> AnyhowResult<PointCloudResult> {
let width = depth_image.get_width_pixels();
let height = depth_image.get_height_pixels();
let point_cloud_image= Image::create(
ImageFormat::Custom,
width as u32,
height as u32,
width as u32 * size_of::<k4a_sys::k4a_float3_t>() as u32,
)?;
let point_cloud_data = point_cloud_image.get_buffer() as *mut k4a_sys::k4a_float3_t;
let xy_table_data = xy_table_image.get_buffer() as *mut k4a_sys::k4a_float2_t;
let depth_data = depth_image.get_buffer() as *mut u16; // uint16_t
let depth_data_length = (width * height) as isize;
let mut point_count = 0;
for i in 0 .. depth_data_length {
unsafe {
// TODO: This is missing `isnan` checks.
// if (depth_data[i] != 0 && !isnan(xy_table_data[i].xy.x) && !isnan(xy_table_data[i].xy.y))
if (*depth_data.offset(i)) != 0 {
(*point_cloud_data.offset(i)).xyz.x = (*xy_table_data.offset(i)).xy.x * ((*depth_data.offset(i)) as f32);
(*point_cloud_data.offset(i)).xyz.y = (*xy_table_data.offset(i)).xy.y * ((*depth_data.offset(i)) as f32);
(*point_cloud_data.offset(i)).xyz.z = (*depth_data.offset(i)) as f32;
point_count += 1;
} else {
(*point_cloud_data.offset(i)).xyz.x = f32::nan();
(*point_cloud_data.offset(i)).xyz.y = f32::nan();
(*point_cloud_data.offset(i)).xyz.z = f32::nan();
}
}
}
Ok(PointCloudResult {
point_cloud_image,
point_count,
})
}
// Directly return as a vector.
pub fn calculate_point_cloud2(depth_image: &Image, xy_table_image: &Image, color: Color) -> AnyhowResult<Vec<Point>> {
let width = depth_image.get_width_pixels();
let height = depth_image.get_height_pixels();
{
let xy_width = xy_table_image.get_width_pixels();
let xy_height = xy_table_image.get_height_pixels();
if width != xy_width || height != xy_height {
return Err(anyhow!("Depth image ({}x{}) and XY table ({}x{}) dimensions are not equal!",
width, height, xy_width, xy_height));
}
}
let xy_table_data = xy_table_image.get_buffer() as *mut k4a_sys::k4a_float2_t;
let depth_data = depth_image.get_buffer() as *mut u16; // uint16_t
let depth_data_length = (width * height) as isize;
let mut points = Vec::new();
for i in 0 .. depth_data_length {
unsafe {
// TODO: This is missing `isnan` checks.
// if (depth_data[i] != 0 && !isnan(xy_table_data[i].xy.x) && !isnan(xy_table_data[i].xy.y))
if (*depth_data.offset(i)) != 0 {
let x = (*xy_table_data.offset(i)).xy.x * ((*depth_data.offset(i)) as f32);
let y = (*xy_table_data.offset(i)).xy.y * ((*depth_data.offset(i)) as f32);
let z = (*depth_data.offset(i)) as f32;
points.push(Point::at(x, y, z, color));
}
}
}
Ok(points)
}
#[repr(C, packed)]
#[derive(Copy, Clone)]
struct ColorCameraPoint {
pub b: u8,
pub g: u8,
pub r: u8,
pub a: u8,
}
// Directly return as a vector.
pub fn calculate_point_cloud3(depth_image: &Image, xy_table_image: &Image, color_image: &Image) -> AnyhowResult<Vec<Point>> {
let width = depth_image.get_width_pixels();
let height = depth_image.get_height_pixels();
{
let xy_width = xy_table_image.get_width_pixels();
let xy_height = xy_table_image.get_height_pixels();
if width != xy_width || height != xy_height {
return Err(anyhow!("Depth image ({}x{}) and XY table ({}x{}) dimensions are not equal!",
width, height, xy_width, xy_height));
}
}
let xy_table_data = xy_table_image.get_buffer() as *mut k4a_sys::k4a_float2_t;
let depth_data = depth_image.get_buffer() as *mut u16; // uint16_t
let color_data = color_image.get_buffer() as *mut ColorCameraPoint;
let depth_data_length = (width * height) as isize;
let mut points = Vec::new();
for i in 0 .. depth_data_length {
unsafe {
// TODO: This is missing `isnan` checks.
// if (depth_data[i] != 0 && !isnan(xy_table_data[i].xy.x) && !isnan(xy_table_data[i].xy.y))
if (*depth_data.offset(i)) != 0 {
let x = (*xy_table_data.offset(i)).xy.x * ((*depth_data.offset(i)) as f32);
let y = (*xy_table_data.offset(i)).xy.y * ((*depth_data.offset(i)) as f32);
let z = (*depth_data.offset(i)) as f32;
let color = (*color_data.offset(i)) as ColorCameraPoint;
let point_color = Color::Custom {
r: color.r,
b: color.b,
g: color.g,
a: 255
};
points.push(Point::at(x, y, z, point_color));
}
}
}
Ok(points)
}
+72
View File
@@ -0,0 +1,72 @@
use crate::AnyhowResult;
use anyhow::anyhow;
use k4a_sys_temp;
use kinect::{Transformation, Image, Calibration, ImageFormat};
use std::mem::size_of;
/// Transform Depth images into Color Camera space.
pub struct DepthTransformer {
transformation: Transformation,
output_width: u32,
output_height: u32,
output_stride_bytes: u32,
}
impl DepthTransformer {
pub fn new(calibration: &Calibration) -> Self {
let transformation = Transformation::from_calibration(&calibration);
// TODO(bt): Temporary hardcoding for K4A_COLOR_RESOLUTION_2160P
let output_width = transformation.color_resolution.width as u32;
let output_height = transformation.color_resolution.height as u32;
//let output_width = 3840; //transformation.color_resolution.width as u32;
//let output_height = 2160; //transformation.color_resolution.height as u32;
let output_stride_bytes = output_width * size_of::<libc::uint16_t>() as u32; // NB: Depth pixel
Self {
transformation,
output_width,
output_height,
output_stride_bytes,
}
}
pub fn transform(&self, depth_image: &Image) -> AnyhowResult<Image> {
let transform_h = self.transformation.get_handle();
let depth_image_h = depth_image.get_handle();
println!("Width, height, stride: {}, {}, {}", self.output_width, self.output_height, self.output_stride_bytes);
let output_image = Image::create(
ImageFormat::Depth16,
self.output_width,
self.output_height,
self.output_stride_bytes,
).map_err(|e| anyhow!("Can't create the image: {:?}", e))?;
println!("Image created");
let output_image_h = output_image.get_handle();
println!("About to transform...");
let result = unsafe {
k4a_sys_temp::k4a_transformation_depth_image_to_color_camera(
transform_h,
depth_image_h,
output_image_h,
)
};
println!("Transformed");
if result != k4a_sys_temp::k4a_buffer_result_t_K4A_BUFFER_RESULT_SUCCEEDED {
return Err(anyhow!("Depth transformation failed."));
}
Ok(output_image)
}
// TODO
//pub fn transform_in_place(&mut self) {
//}
}
+38
View File
@@ -0,0 +1,38 @@
use cgmath::num_traits::Float;
use crate::zeromq::point::Point;
/// Print the minima and maxima along each dimension.
pub fn print_pointcloud_maxima(points: &Vec<Point>) {
let mut max_x = f32::min_value();
let mut min_x = f32::max_value();
let mut max_y = f32::min_value();
let mut min_y = f32::max_value();
let mut max_z = f32::min_value();
let mut min_z = f32::max_value();
for pt in points.iter() {
if pt.x < min_x {
min_x = pt.x;
}
if pt.x > max_x {
max_x = pt.x;
}
if pt.y < min_y {
min_y = pt.y;
}
if pt.y > max_y {
max_y = pt.y;
}
if pt.z < min_z {
min_z = pt.z;
}
if pt.z > max_z {
max_z = pt.z;
}
}
// minmax x : -2612.5303, 4902.1655 | y: -3491.938, 1000.92487 | z: 163, 5494
// minmax x : -2615.739, 4918.527 | y: -3507.2483, 1007.1275 | z: 163, 5518
println!("minmax x : {}, {} | y: {}, {} | z: {}, {}",
min_x, max_x, min_y, max_y, min_z, max_z);
}
+67
View File
@@ -0,0 +1,67 @@
use crate::zeromq::point::Point;
use byteorder::{WriteBytesExt, LittleEndian};
use std::io::Write;
/*
/// Denotes the command that sends the variable-length data
const POINT_DATA_BEGIN_PAYLOAD_COMMAND : u32 = 2;
/// Denotes the command that sends the variable-length data
const POINT_DATA_CONTINUE_PAYLOAD_COMMAND : u32 = 3;
/// The maximum number of points to send per ZeroMQ "packet".
/// This is also defined in C++, so it needs to be adjusted in multiple places.
const MAX_SEND_POINTS_PER_PACKET : usize = 3000;
/// Streaming points
struct StreamingPointsPacket {
is_first_point_packet: bool,
points: Vec<Point>,
}
impl StreamingPointsPacket {
fn from_all_points(points: Vec<Point>) -> Self {
Self {
is_first_point_packet: true,
points,
}
}
/// Over the wire, this looks like:
///
/// | command | 4 bytes (u32) |
/// | point_count | 4 bytes (u32) |
/// | points[] ... | [16 bytes] ... |
///
fn encode(&self) -> Vec<u8> {
let point_bytes = Point::size_bytes() * MAX_SEND_POINTS_PER_PACKET;
let mut buf = Vec::with_capacity(4 + point_bytes);
if self.is_first_point_packet {
buf.write_u32::<LittleEndian>(POINT_DATA_BEGIN_PAYLOAD_COMMAND); // COMMAND #
} else {
buf.write_u32::<LittleEndian>(POINT_DATA_CONTINUE_PAYLOAD_COMMAND); // COMMAND #
}
//let subset = if points.len() > MAX_SEND_POINTS_PER_PACKET {
// points.drain(0..MAX_SEND_POINTS_PER_PACKET).collect::<Vec<Point>>()
//} else {
// points.drain(0..points.len()).collect::<Vec<Point>>()
//};
buf.write_u32::<LittleEndian>(self.points.len() as u32); // LENGTH
for point in self.points.iter() {
let bytes = point.to_bytes();
buf.write_all(&bytes);
}
buf
}
}
*/
+7
View File
@@ -0,0 +1,7 @@
pub mod cpu_calculate_point_cloud;
pub mod color;
pub mod debug;
pub mod message;
pub mod point;
pub mod cpu_transformation;
pub mod xy_table;
+78
View File
@@ -0,0 +1,78 @@
use byteorder::{WriteBytesExt, LittleEndian, BigEndian};
use bytes::{BytesMut, BufMut};
use crate::zeromq::color::Color;
use rand::Rng;
use rand::distributions::Uniform;
#[derive(Debug)]
pub struct Point {
// Position
pub x: f32,
pub y: f32,
pub z: f32,
// Color
pub r: u8,
pub g: u8,
pub b: u8,
pub a: u8,
}
impl Point {
pub fn size_bytes() -> usize {
16 // 3xf32, 4xu8
}
pub fn at(x: f32, y: f32, z: f32, color: Color) -> Self {
Point {
x,
y,
z,
r: color.get_u8_r(),
g: color.get_u8_g(),
b: color.get_u8_b(),
a: color.get_u8_a(),
}
}
pub fn at_random_range(min: f32, max: f32, color: Color) -> Self {
let mut rng = rand::thread_rng();
let range = Uniform::new_inclusive(min, max);
let x = rng.sample(&range);
let y = rng.sample(&range);
let z = rng.sample(&range);
Point {
x,
y,
z,
r: color.get_u8_r(),
g: color.get_u8_g(),
b: color.get_u8_b(),
a: color.get_u8_a(),
}
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut buf = Vec::with_capacity(32);
buf.write_f32::<LittleEndian>(self.x);
buf.write_f32::<LittleEndian>(self.y);
buf.write_f32::<LittleEndian>(self.z);
buf.write_u8(self.r);
buf.write_u8(self.g);
buf.write_u8(self.b);
buf.write_u8(self.a);
buf
}
pub fn location_string(&self) -> String {
format!("{}, {}, {}", self.x, self.y, self.z)
}
pub fn debug_string(&self) -> String {
format!("(location: {}, {}, {}); (color: {}, {}, {}, alpha: {})",
self.x, self.y, self.z,
self.r, self.g, self.b, self.a)
}
}
+93
View File
@@ -0,0 +1,93 @@
use crate::AnyhowResult;
use k4a_sys_temp as k4a_sys;
use kinect::{Calibration, Image, ImageFormat};
use std::mem::size_of;
use std::os::raw::c_int;
/**
* Creates an XY depth lookup table. (Taken from MIT-licensed `fastpointcloud.exe` in the K4A libs; doc below)
*
* The Azure Kinect Fastpointcloud example computes a 3d point cloud from a depth map. The example precomputes a lookup table
* by storing x- and y-scale factors for every pixel. At runtime, the 3d X-coordinate of a pixel in millimeters is derived
* by multiplying the pixel's depth value with the corresponding x-scale factor. The 3d Y-coordinate is obtained by
* multiplying with the y-scale factor.
*
* This method represents an alternative to calling k4a_transformation_depth_image_to_point_cloud() and lends itself
* to efficient implementation on the GPU.
*/
pub fn create_xy_table_from_depth_calibration(calibration: &Calibration) -> AnyhowResult<Image> {
let width = calibration.depth_camera_resolution_width();
let height = calibration.depth_camera_resolution_height();
create_xy_table(calibration, width, height)
}
pub fn create_xy_table_from_color_calibration(calibration: &Calibration) -> AnyhowResult<Image> {
let width = calibration.color_camera_resolution_width();
let height = calibration.color_camera_resolution_height();
create_xy_table(calibration, width, height)
}
pub fn create_xy_table(calibration: &Calibration, width: i32, height: i32) -> AnyhowResult<Image> {
let image = Image::create(
ImageFormat::Custom,
width as u32,
height as u32,
width as u32 * size_of::<k4a_sys::k4a_float2_t>() as u32,
)?;
let typed_buffer = image.get_buffer() as *mut k4a_sys::k4a_float2_t;
let mut p = k4a_sys::k4a_float2_t {
xy: k4a_sys::k4a_float2_t__xy {
x: 0.0,
y: 0.0,
}
};
let mut ray = k4a_sys::k4a_float3_t {
xyz: k4a_sys::k4a_float3_t__xyz {
x: 0.0,
y: 0.0,
z: 0.0,
}
};
let mut idx = 0;
for y in 0 .. height {
p.xy.y = y as f32;
for x in 0 .. width {
p.xy.x = x as f32;
let mut valid: c_int = -1;
let result = unsafe {
k4a_sys::k4a_calibration_2d_to_3d(
&calibration.0,
&p, // source point 2d
1.0, // source depth mm
k4a_sys::k4a_calibration_type_t_K4A_CALIBRATION_TYPE_DEPTH, // source camera
k4a_sys::k4a_calibration_type_t_K4A_CALIBRATION_TYPE_DEPTH, // target camera
&mut ray, // target point3d mm
&mut valid // set to 1 when valid result, 0 when coordinate is not valid
)
};
if valid == 1 {
unsafe {
(*typed_buffer.offset(idx)).xy.x = ray.xyz.x;
(*typed_buffer.offset(idx)).xy.y = ray.xyz.y;
}
} else {
unsafe {
(*typed_buffer.offset(idx)).xy.x = 0.0;
(*typed_buffer.offset(idx)).xy.y = 0.0;
}
}
idx += 1;
}
}
Ok(image)
}
+60
View File
@@ -0,0 +1,60 @@
let mut reconnect = false;
let mut fail_count = 0;
/*
let color = get_color();
let point = Point::at_random_range(-1000.0f32, 1000.0f32, color);
let bytes = point.to_bytes();
//println!("Point : {}", point.debug_string());
if reconnect {
socket = reconnect_socket(&context, socket, SOCKET_ADDRESS);
reconnect = false;
messaging_state = MessagingState::Sending;
}
match messaging_state {
MessagingState::Sending => {
//println!("Sending request...");
//let result = socket.send("hello world!", 0);
let result = socket.send(&bytes, 0);
match result {
Ok(_) => {
//println!("Sent!");
//thread::sleep(Duration::from_millis(250));
messaging_state = MessagingState::Receiving;
},
Err(e) => {
//eprintln!("Send Error ({}): {:?}", e.to_raw(), e);
//thread::sleep(Duration::from_millis(250));
fail_count += 1;
},
}
},
MessagingState::Receiving => {
//println!("Awaiting response...");
let result = socket.recv_bytes(DONTWAIT);
match result {
Ok(_) => {
//println!("Response received!");
messaging_state = MessagingState::Sending;
},
Err(e) => {
//eprintln!("Recv Error ({}): {:?}", e.to_raw(), e);
//thread::sleep(Duration::from_millis(250));
fail_count += 1;
},
}
},
}
if fail_count > 5 {
reconnect = true;
fail_count = 0;
//thread::sleep(Duration::from_millis(2000));
}
*/