Skip to content

Commit 146a250

Browse files
lpasselinshagrenSBoulanger
authored
Version 1.1.0 (#92)
* Run test with different python versions (#65) * Add matrix to workflow * Change python versions list * Change python versions list * Add k4a versions to matrix * Typofix * Drop k4a from matrix * Add dataclasses requirement for python <3.7 * Fix python 3.6 test behavior * Fix python 3.6 test behavior * Restore fail-fast option * fix conversion seconds to ns * fix conversion seconds to ns * fix timestamp ns to us Co-authored-by: Louis-Philippe Asselin <[email protected]> * fix install using pip --editable --user (#67) * Codecov support (#64) * Codecov support * Add badge * Order badges * fix capture.transformed_depth_point_cloud (#73) * version 1.0.1 * Added transformed_ir with transform_depth_image_to_color_camera_custom functionality (#76) * Added transform_depth_image_to_color_camera_custom functionality * keeping things c * add interpolation option condition as a parameter * returned the depth image * unpack return value if not None so avoid error * Image timestamp support (#88) * Support for capture images timestamps * Support for capture images timestamps * Add more changes * version 1.1.0 * fix lint * readme fix wrong example version of SDK Co-authored-by: Ilya Gruzinov <[email protected]> Co-authored-by: Samuel Boulanger <[email protected]>
1 parent 89ba085 commit 146a250

14 files changed

+195
-19
lines changed

.github/workflows/ci.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ jobs:
7070
pip install -e .
7171
- name: Run tests
7272
run: |
73-
make test-no-hardware
73+
make test-ci
7474
- name: Coverage
7575
uses: codecov/codecov-action@v1
7676
with:

Makefile

+5-1
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,8 @@ test-hardware:
3636
pytest -m "device" $(TESTS)
3737

3838
test-no-hardware:
39-
pytest -m "not device" $(TESTS)
39+
pytest -m "not device" $(TESTS)
40+
41+
test-ci:
42+
pytest -m "not device and not opengl" $(TESTS)
43+

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ pip install pyk4a
3232
### Windows
3333

3434
Make sure you replace the paths in the following instructions with your own k4a sdk path.
35-
It is important to replace `1.2.0` with your installed version of the SDK.
35+
It is important to replace `1.4.1` with your installed version of the SDK.
3636

3737
```
3838
pip install pyk4a --no-use-pep517 --global-option=build_ext --global-option="-IC:\Program Files\Azure Kinect SDK v1.4.1\sdk\include" --global-option="-LC:\Program Files\Azure Kinect SDK v1.4.1\sdk\windows-desktop\amd64\release\lib"

example/viewer_transformation.py

+2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ def main():
2121
cv2.imshow("Transformed Depth", colorize(capture.transformed_depth, (None, 5000)))
2222
if capture.transformed_color is not None:
2323
cv2.imshow("Transformed Color", capture.transformed_color)
24+
if capture.transformed_ir is not None:
25+
cv2.imshow("Transformed IR", colorize(capture.transformed_ir, (None, 500), colormap=cv2.COLORMAP_JET))
2426

2527
key = cv2.waitKey(10)
2628
if key != -1:

pyk4a/__init__.py

+7-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,12 @@
1313
from .errors import K4AException, K4ATimeoutException
1414
from .playback import PyK4APlayback, SeekOrigin
1515
from .pyk4a import ColorControlCapabilities, PyK4A
16-
from .transformation import color_image_to_depth_camera, depth_image_to_color_camera, depth_image_to_point_cloud
16+
from .transformation import (
17+
color_image_to_depth_camera,
18+
depth_image_to_color_camera,
19+
depth_image_to_color_camera_custom,
20+
depth_image_to_point_cloud,
21+
)
1722

1823

1924
__all__ = (
@@ -37,4 +42,5 @@
3742
"color_image_to_depth_camera",
3843
"depth_image_to_point_cloud",
3944
"depth_image_to_color_camera",
45+
"depth_image_to_color_camera_custom",
4046
)

pyk4a/capture.py

+48-4
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,12 @@
66

77
from .calibration import Calibration
88
from .config import ImageFormat
9-
from .transformation import color_image_to_depth_camera, depth_image_to_color_camera, depth_image_to_point_cloud
9+
from .transformation import (
10+
color_image_to_depth_camera,
11+
depth_image_to_color_camera,
12+
depth_image_to_color_camera_custom,
13+
depth_image_to_point_cloud,
14+
)
1015

1116

1217
class PyK4ACapture:
@@ -19,31 +24,60 @@ def __init__(
1924
self._color_format = color_format
2025

2126
self._color: Optional[np.ndarray] = None
27+
self._color_timestamp_usec: int = 0
2228
self._depth: Optional[np.ndarray] = None
29+
self._depth_timestamp_usec: int = 0
2330
self._ir: Optional[np.ndarray] = None
31+
self._ir_timestamp_usec: int = 0
2432
self._depth_point_cloud: Optional[np.ndarray] = None
2533
self._transformed_depth: Optional[np.ndarray] = None
2634
self._transformed_depth_point_cloud: Optional[np.ndarray] = None
2735
self._transformed_color: Optional[np.ndarray] = None
36+
self._transformed_ir: Optional[np.ndarray] = None
2837

2938
@property
3039
def color(self) -> Optional[np.ndarray]:
3140
if self._color is None:
32-
self._color = k4a_module.capture_get_color_image(self._capture_handle, self.thread_safe)
41+
self._color, self._color_timestamp_usec = k4a_module.capture_get_color_image(
42+
self._capture_handle, self.thread_safe
43+
)
3344
return self._color
3445

46+
@property
47+
def color_timestamp_usec(self) -> int:
48+
"""Device timestamp for color image. Not equal host machine timestamp!"""
49+
if self._color is None:
50+
self.color
51+
return self._color_timestamp_usec
52+
3553
@property
3654
def depth(self) -> Optional[np.ndarray]:
3755
if self._depth is None:
38-
self._depth = k4a_module.capture_get_depth_image(self._capture_handle, self.thread_safe)
56+
self._depth, self._depth_timestamp_usec = k4a_module.capture_get_depth_image(
57+
self._capture_handle, self.thread_safe
58+
)
3959
return self._depth
4060

61+
@property
62+
def depth_timestamp_usec(self) -> int:
63+
"""Device timestamp for depth image. Not equal host machine timestamp!. Like as equal IR image timestamp"""
64+
if self._depth is None:
65+
self.depth
66+
return self._depth_timestamp_usec
67+
4168
@property
4269
def ir(self) -> Optional[np.ndarray]:
70+
"""Device timestamp for IR image. Not equal host machine timestamp!. Like as equal depth image timestamp"""
4371
if self._ir is None:
44-
self._ir = k4a_module.capture_get_ir_image(self._capture_handle, self.thread_safe)
72+
self._ir, self._ir_timestamp_usec = k4a_module.capture_get_ir_image(self._capture_handle, self.thread_safe)
4573
return self._ir
4674

75+
@property
76+
def ir_timestamp_usec(self) -> int:
77+
if self._ir is None:
78+
self.ir
79+
return self._ir_timestamp_usec
80+
4781
@property
4882
def transformed_depth(self) -> Optional[np.ndarray]:
4983
if self._transformed_depth is None and self.depth is not None:
@@ -78,3 +112,13 @@ def transformed_color(self) -> Optional[np.ndarray]:
78112
self.color, self.depth, self._calibration, self.thread_safe
79113
)
80114
return self._transformed_color
115+
116+
@property
117+
def transformed_ir(self) -> Optional[np.ndarray]:
118+
if self._transformed_ir is None and self.ir is not None and self.depth is not None:
119+
result = depth_image_to_color_camera_custom(self.depth, self.ir, self._calibration, self.thread_safe)
120+
if result is None:
121+
return None
122+
else:
123+
self._transformed_ir, self._transformed_depth = result
124+
return self._transformed_ir

pyk4a/pyk4a.cpp

+104-9
Original file line numberDiff line numberDiff line change
@@ -467,6 +467,7 @@ extern "C" {
467467
dims[2] = 1;
468468
*img_dst = (PyArrayObject*) PyArray_SimpleNewFromData(3, dims, NPY_UINT8, buffer);
469469
break;
470+
case K4A_IMAGE_FORMAT_CUSTOM16:
470471
case K4A_IMAGE_FORMAT_DEPTH16:
471472
case K4A_IMAGE_FORMAT_IR16:
472473
dims[0] = k4a_image_get_height_pixels(*img_src);
@@ -506,6 +507,9 @@ extern "C" {
506507
case K4A_IMAGE_FORMAT_COLOR_BGRA32:
507508
pixel_size = (int)sizeof(uint32_t);
508509
break;
510+
case K4A_IMAGE_FORMAT_CUSTOM16:
511+
pixel_size = (unsigned int)sizeof(int16_t);
512+
break;
509513
default:
510514
// Not supported
511515
return K4A_RESULT_FAILED;
@@ -596,6 +600,89 @@ extern "C" {
596600
}
597601
}
598602

603+
static PyObject* transformation_depth_image_to_color_camera_custom(PyObject* self, PyObject* args){
604+
k4a_transformation_t* transformation_handle;
605+
PyObject *capsule;
606+
int thread_safe;
607+
int interp_nearest;
608+
PyThreadState *thread_state;
609+
k4a_result_t res;
610+
PyArrayObject *d_array;
611+
PyArrayObject *c_array;
612+
k4a_color_resolution_t color_resolution;
613+
k4a_transformation_interpolation_type_t interpolation_type;
614+
615+
616+
PyArg_ParseTuple(args, "OpO!O!Ip", &capsule, &thread_safe, &PyArray_Type, &d_array, &PyArray_Type, &c_array, &color_resolution, &interp_nearest);
617+
618+
if (interp_nearest){
619+
interpolation_type = K4A_TRANSFORMATION_INTERPOLATION_TYPE_NEAREST;
620+
}
621+
else {
622+
interpolation_type = K4A_TRANSFORMATION_INTERPOLATION_TYPE_LINEAR;
623+
}
624+
625+
transformation_handle = (k4a_transformation_t*)PyCapsule_GetPointer(capsule, CAPSULE_TRANSFORMATION_NAME);
626+
627+
k4a_image_t* depth_image_transformed = (k4a_image_t*) malloc(sizeof(k4a_image_t));
628+
k4a_image_t* custom_image_transformed = (k4a_image_t*) malloc(sizeof(k4a_image_t));
629+
630+
k4a_image_t depth_image;
631+
k4a_image_t custom_image;
632+
res = numpy_to_k4a_image(d_array, &depth_image, K4A_IMAGE_FORMAT_DEPTH16);
633+
if (K4A_RESULT_SUCCEEDED == res) {
634+
res = numpy_to_k4a_image(c_array, &custom_image, K4A_IMAGE_FORMAT_CUSTOM16);
635+
}
636+
thread_state = _gil_release(thread_safe);
637+
if (K4A_RESULT_SUCCEEDED == res) {
638+
res = k4a_image_create(
639+
k4a_image_get_format(depth_image),
640+
RESOLUTION_TO_DIMS[color_resolution][0],
641+
RESOLUTION_TO_DIMS[color_resolution][1],
642+
RESOLUTION_TO_DIMS[color_resolution][0] * (int)sizeof(uint16_t),
643+
depth_image_transformed);
644+
}
645+
if (K4A_RESULT_SUCCEEDED == res) {
646+
res = k4a_image_create(
647+
k4a_image_get_format(custom_image),
648+
RESOLUTION_TO_DIMS[color_resolution][0],
649+
RESOLUTION_TO_DIMS[color_resolution][1],
650+
RESOLUTION_TO_DIMS[color_resolution][0] * static_cast<int32_t>(sizeof(int16_t)),
651+
custom_image_transformed);
652+
}
653+
654+
if (K4A_RESULT_SUCCEEDED == res) {
655+
res = k4a_transformation_depth_image_to_color_camera_custom(
656+
*transformation_handle,
657+
depth_image,
658+
custom_image,
659+
*depth_image_transformed,
660+
*custom_image_transformed,
661+
interpolation_type,
662+
0);
663+
k4a_image_release(depth_image);
664+
k4a_image_release(custom_image);
665+
}
666+
_gil_restore(thread_state);
667+
PyArrayObject* np_depth_image;
668+
PyArrayObject* np_custom_image;
669+
if (K4A_RESULT_SUCCEEDED == res) {
670+
res = k4a_image_to_numpy(depth_image_transformed, &np_depth_image);
671+
}
672+
if (K4A_RESULT_SUCCEEDED == res) {
673+
res = k4a_image_to_numpy(custom_image_transformed, &np_custom_image);
674+
}
675+
676+
if (K4A_RESULT_SUCCEEDED == res) {
677+
return Py_BuildValue("OO", np_custom_image, np_depth_image);
678+
}
679+
else {
680+
free(depth_image_transformed);
681+
free(custom_image_transformed);
682+
return Py_BuildValue("");
683+
}
684+
}
685+
599686
static PyObject* transformation_depth_image_to_point_cloud(PyObject* self, PyObject* args) {
600687
k4a_transformation_t* transformation_handle;
601688
PyObject *capsule;
@@ -706,6 +793,7 @@ extern "C" {
706793
k4a_capture_t* capture_handle;
707794
PyObject *capsule;
708795
int thread_safe;
796+
uint64_t device_timestamp_usec = 0;
709797
PyThreadState *thread_state;
710798
k4a_result_t res = K4A_RESULT_FAILED;
711799

@@ -715,7 +803,7 @@ extern "C" {
715803
k4a_image_t* image = (k4a_image_t*) malloc(sizeof(k4a_image_t));
716804
if (image == NULL) {
717805
fprintf(stderr, "Cannot allocate memory");
718-
return Py_BuildValue("");
806+
return Py_BuildValue("NK", Py_None, device_timestamp_usec);
719807
}
720808

721809
thread_state = _gil_release(thread_safe);
@@ -728,18 +816,20 @@ extern "C" {
728816
}
729817

730818
if (K4A_RESULT_SUCCEEDED == res) {
731-
return PyArray_Return(np_image);
819+
device_timestamp_usec = k4a_image_get_device_timestamp_usec(*image);
820+
return Py_BuildValue("NK", np_image, device_timestamp_usec);
732821
}
733822
else {
734823
free(image);
735-
return Py_BuildValue("");
824+
return Py_BuildValue("NK", Py_None, device_timestamp_usec);
736825
}
737826
}
738827

739828
static PyObject* capture_get_depth_image(PyObject* self, PyObject* args){
740829
k4a_capture_t* capture_handle;
741830
PyObject *capsule;
742831
int thread_safe;
832+
uint64_t device_timestamp_usec = 0;
743833
PyThreadState *thread_state;
744834
k4a_result_t res = K4A_RESULT_FAILED;
745835

@@ -749,7 +839,7 @@ extern "C" {
749839
k4a_image_t* image = (k4a_image_t*) malloc(sizeof(k4a_image_t));
750840
if (image == NULL) {
751841
fprintf(stderr, "Cannot allocate memory");
752-
return Py_BuildValue("");
842+
return Py_BuildValue("NK", Py_None, device_timestamp_usec);
753843
}
754844

755845
thread_state = _gil_release(thread_safe);
@@ -762,18 +852,20 @@ extern "C" {
762852
}
763853

764854
if (K4A_RESULT_SUCCEEDED == res) {
765-
return PyArray_Return(np_image);
855+
device_timestamp_usec = k4a_image_get_device_timestamp_usec(*image);
856+
return Py_BuildValue("NK", np_image, device_timestamp_usec);
766857
}
767858
else {
768859
free(image);
769-
return Py_BuildValue("");
860+
return Py_BuildValue("NK", Py_None, device_timestamp_usec);
770861
}
771862
}
772863

773864
static PyObject* capture_get_ir_image(PyObject* self, PyObject* args){
774865
k4a_capture_t* capture_handle;
775866
PyObject *capsule;
776867
int thread_safe;
868+
uint64_t device_timestamp_usec = 0;
777869
PyThreadState *thread_state;
778870
k4a_result_t res = K4A_RESULT_FAILED;
779871

@@ -783,7 +875,7 @@ extern "C" {
783875
k4a_image_t* image = (k4a_image_t*) malloc(sizeof(k4a_image_t));
784876
if (image == NULL) {
785877
fprintf(stderr, "Cannot allocate memory");
786-
return Py_BuildValue("");
878+
return Py_BuildValue("NK", Py_None, device_timestamp_usec);
787879
}
788880

789881
thread_state = _gil_release(thread_safe);
@@ -796,11 +888,12 @@ extern "C" {
796888
}
797889

798890
if (K4A_RESULT_SUCCEEDED == res) {
799-
return PyArray_Return(np_image);
891+
device_timestamp_usec = k4a_image_get_device_timestamp_usec(*image);
892+
return Py_BuildValue("NK", np_image, device_timestamp_usec);
800893
}
801894
else {
802895
free(image);
803-
return Py_BuildValue("");
896+
return Py_BuildValue("NK", Py_None, device_timestamp_usec);
804897
}
805898
}
806899

@@ -888,6 +981,7 @@ extern "C" {
888981
return Py_BuildValue("II(fff)", res, valid, target_point3d_mm.xyz.x, target_point3d_mm.xyz.y, target_point3d_mm.xyz.z);
889982
}
890983

984+
891985
static PyObject* playback_open(PyObject* self, PyObject *args) {
892986
int thread_safe;
893987
PyThreadState *thread_state;
@@ -1148,6 +1242,7 @@ extern "C" {
11481242
{"calibration_get_from_raw", calibration_get_from_raw, METH_VARARGS, "Create new calibration handle from raw json."},
11491243
{"transformation_create", transformation_create, METH_VARARGS, "Create transformation handle from calibration"},
11501244
{"transformation_depth_image_to_color_camera", transformation_depth_image_to_color_camera, METH_VARARGS, "Transforms the depth map into the geometry of the color camera."},
1245+
{"transformation_depth_image_to_color_camera_custom", transformation_depth_image_to_color_camera_custom, METH_VARARGS, "Transforms the custom & depth map into the geometry of the color camera."},
11511246
{"transformation_color_image_to_depth_camera", transformation_color_image_to_depth_camera, METH_VARARGS, "Transforms the color image into the geometry of the depth camera."},
11521247
{"transformation_depth_image_to_point_cloud", transformation_depth_image_to_point_cloud, METH_VARARGS, "Transforms the depth map to a point cloud."},
11531248
{"calibration_3d_to_3d", calibration_3d_to_3d, METH_VARARGS, "Transforms the coordinates between 2 3D systems"},

pyk4a/transformation.py

+12
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,18 @@ def depth_image_to_color_camera(depth: np.ndarray, calibration: Calibration, thr
1717
)
1818

1919

20+
def depth_image_to_color_camera_custom(
21+
depth: np.ndarray, custom: np.ndarray, calibration: Calibration, thread_safe: bool, interp_nearest: bool = True,
22+
) -> Optional[np.ndarray]:
23+
"""
24+
Transforms depth image and custom image to color_image space
25+
Return empty result if transformation failed
26+
"""
27+
return k4a_module.transformation_depth_image_to_color_camera_custom(
28+
calibration.transformation_handle, thread_safe, depth, custom, calibration.color_resolution, interp_nearest,
29+
)
30+
31+
2032
def depth_image_to_point_cloud(
2133
depth: np.ndarray, calibration: Calibration, thread_safe: bool, calibration_type_depth=True
2234
) -> Optional[np.ndarray]:

pyproject.toml

+2-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ line-length = 120
66

77
[tool.pytest.ini_options]
88
markers = [
9-
"device: Tests require connected real device"
9+
"device: Tests require connected real device",
10+
"opengl: Tests require opengl for GPU accelerated depth engine software"
1011
]
1112
addopts = "--cov=pyk4a --cov-report=xml --verbose"

0 commit comments

Comments
 (0)