Skip to content

Commit 2791f6e

Browse files
lpasselinannStein
andauthored
Version 1.4.0 (#170)
* Feature/playback get imu sample (#150) * feat(playback-imu-sample): add "get_next_imu_sample" to playback * fix(playback-imu-sample): PR changes, add correct indentation, add import in playback.py * add info related to conda and DLL not found error * update black version (fix click bug) (#168) * update black version (fix click bug) * black reformat * fix typo Co-authored-by: Louis-Philippe Asselin <[email protected]> Co-authored-by: annStein <[email protected]>
1 parent 905d77b commit 2791f6e

15 files changed

+164
-56
lines changed

README.md

+2
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ pip install pyk4a
3434

3535
In most cases `pip install pyk4a` is enough to install this package.
3636

37+
When using an anaconda environment, you need to set the environment variable `CONDA_DLL_SEARCH_MODIFICATION_ENABLE=1` https://github.com/conda/conda/issues/10897
38+
3739
Because of the numerous issues received from Windows users, the installer ([setup.py](setup.py)) automatically detects the kinect SDK path.
3840

3941
When the installer is not able to find the path, the following snippet can help.

example/threads.py

+10-3
Original file line numberDiff line numberDiff line change
@@ -93,10 +93,16 @@ def draw(results: Dict[int, Dict[bool, int]]):
9393
plt.ylabel("Operations Count")
9494
plt.xlabel("CPU Workers count")
9595
plt.plot(
96-
results.keys(), [result[True] for result in results.values()], "r", label="Thread safe",
96+
results.keys(),
97+
[result[True] for result in results.values()],
98+
"r",
99+
label="Thread safe",
97100
)
98101
plt.plot(
99-
results.keys(), [result[False] for result in results.values()], "g", label="Non thread safe",
102+
results.keys(),
103+
[result[False] for result in results.values()],
104+
"g",
105+
label="Non thread safe",
100106
)
101107
plt.legend()
102108

@@ -105,7 +111,8 @@ def draw(results: Dict[int, Dict[bool, int]]):
105111
plt.ylabel("Difference, %")
106112
plt.xlabel("CPU Workers count")
107113
plt.plot(
108-
results.keys(), [float(result[False] - result[True]) / result[True] * 100 for result in results.values()],
114+
results.keys(),
115+
[float(result[False] - result[True]) / result[True] * 100 for result in results.values()],
109116
)
110117
xmin, xmax, ymin, ymax = plt.axis()
111118
if ymin > 0:

example/viewer_point_cloud.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,11 @@ def main():
3636
fig = plt.figure()
3737
ax = fig.add_subplot(111, projection="3d")
3838
ax.scatter(
39-
points[:, 0], points[:, 1], points[:, 2], s=1, c=colors / 255,
39+
points[:, 0],
40+
points[:, 1],
41+
points[:, 2],
42+
s=1,
43+
c=colors / 255,
4044
)
4145
ax.set_xlabel("x")
4246
ax.set_ylabel("y")

example/viewer_transformation.py

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

77

88
def main():
9-
k4a = PyK4A(Config(color_resolution=pyk4a.ColorResolution.RES_720P, depth_mode=pyk4a.DepthMode.NFOV_UNBINNED,))
9+
k4a = PyK4A(
10+
Config(
11+
color_resolution=pyk4a.ColorResolution.RES_720P,
12+
depth_mode=pyk4a.DepthMode.NFOV_UNBINNED,
13+
)
14+
)
1015
k4a.start()
1116

1217
while True:

pyk4a/calibration.py

+37-24
Original file line numberDiff line numberDiff line change
@@ -53,15 +53,19 @@ def _convert_3d_to_3d(
5353
target_camera: CalibrationType,
5454
) -> Tuple[float, float, float]:
5555
"""
56-
Transform a 3d point of a source coordinate system into a 3d
57-
point of the target coordinate system.
58-
:param source_point_3d The 3D coordinates in millimeters representing a point in source_camera.
59-
:param source_camera The current camera.
60-
:param target_camera The target camera.
61-
:return The 3D coordinates in millimeters representing a point in target camera.
56+
Transform a 3d point of a source coordinate system into a 3d
57+
point of the target coordinate system.
58+
:param source_point_3d The 3D coordinates in millimeters representing a point in source_camera.
59+
:param source_camera The current camera.
60+
:param target_camera The target camera.
61+
:return The 3D coordinates in millimeters representing a point in target camera.
6262
"""
6363
res, target_point_3d = k4a_module.calibration_3d_to_3d(
64-
self._calibration_handle, self.thread_safe, source_point_3d, source_camera, target_camera,
64+
self._calibration_handle,
65+
self.thread_safe,
66+
source_point_3d,
67+
source_camera,
68+
target_camera,
6569
)
6670

6771
_verify_error(res)
@@ -81,16 +85,21 @@ def _convert_2d_to_3d(
8185
target_camera: CalibrationType,
8286
) -> Tuple[float, float, float]:
8387
"""
84-
Transform a 3d point of a source coordinate system into a 3d
85-
point of the target coordinate system.
86-
:param source_pixel_2d The 2D coordinates in px of source_camera color_image.
87-
:param source_depth Depth in mm
88-
:param source_camera The current camera.
89-
:param target_camera The target camera.
90-
:return The 3D coordinates in mm representing a point in target camera.
88+
Transform a 3d point of a source coordinate system into a 3d
89+
point of the target coordinate system.
90+
:param source_pixel_2d The 2D coordinates in px of source_camera color_image.
91+
:param source_depth Depth in mm
92+
:param source_camera The current camera.
93+
:param target_camera The target camera.
94+
:return The 3D coordinates in mm representing a point in target camera.
9195
"""
9296
res, valid, target_point_3d = k4a_module.calibration_2d_to_3d(
93-
self._calibration_handle, self.thread_safe, source_pixel_2d, source_depth, source_camera, target_camera,
97+
self._calibration_handle,
98+
self.thread_safe,
99+
source_pixel_2d,
100+
source_depth,
101+
source_camera,
102+
target_camera,
94103
)
95104

96105
_verify_error(res)
@@ -107,7 +116,7 @@ def convert_2d_to_3d(
107116
target_camera: Optional[CalibrationType] = None,
108117
):
109118
"""
110-
Transform a 2d pixel to a 3d point of the target coordinate system.
119+
Transform a 2d pixel to a 3d point of the target coordinate system.
111120
"""
112121
if target_camera is None:
113122
target_camera = source_camera
@@ -120,15 +129,19 @@ def _convert_3d_to_2d(
120129
target_camera: CalibrationType,
121130
) -> Tuple[float, float]:
122131
"""
123-
Transform a 3d point of a source coordinate system into a 3d
124-
point of the target coordinate system.
125-
:param source_point_3d The 3D coordinates in mm of source_camera.
126-
:param source_camera The current camera.
127-
:param target_camera The target camera.
128-
:return The 3D coordinates in mm representing a point in target camera.
132+
Transform a 3d point of a source coordinate system into a 3d
133+
point of the target coordinate system.
134+
:param source_point_3d The 3D coordinates in mm of source_camera.
135+
:param source_camera The current camera.
136+
:param target_camera The target camera.
137+
:return The 3D coordinates in mm representing a point in target camera.
129138
"""
130139
res, valid, target_px_2d = k4a_module.calibration_3d_to_2d(
131-
self._calibration_handle, self.thread_safe, source_point_3d, source_camera, target_camera,
140+
self._calibration_handle,
141+
self.thread_safe,
142+
source_point_3d,
143+
source_camera,
144+
target_camera,
132145
)
133146

134147
_verify_error(res)
@@ -144,7 +157,7 @@ def convert_3d_to_2d(
144157
target_camera: Optional[CalibrationType] = None,
145158
):
146159
"""
147-
Transform a 3d point to a 2d pixel of the target coordinate system.
160+
Transform a 3d point to a 2d pixel of the target coordinate system.
148161
"""
149162
if target_camera is None:
150163
target_camera = source_camera

pyk4a/capture.py

+8-2
Original file line numberDiff line numberDiff line change
@@ -138,15 +138,21 @@ def transformed_depth(self) -> Optional[np.ndarray]:
138138
def depth_point_cloud(self) -> Optional[np.ndarray]:
139139
if self._depth_point_cloud is None and self.depth is not None:
140140
self._depth_point_cloud = depth_image_to_point_cloud(
141-
self._depth, self._calibration, self.thread_safe, calibration_type_depth=True,
141+
self._depth,
142+
self._calibration,
143+
self.thread_safe,
144+
calibration_type_depth=True,
142145
)
143146
return self._depth_point_cloud
144147

145148
@property
146149
def transformed_depth_point_cloud(self) -> Optional[np.ndarray]:
147150
if self._transformed_depth_point_cloud is None and self.transformed_depth is not None:
148151
self._transformed_depth_point_cloud = depth_image_to_point_cloud(
149-
self.transformed_depth, self._calibration, self.thread_safe, calibration_type_depth=False,
152+
self.transformed_depth,
153+
self._calibration,
154+
self.thread_safe,
155+
calibration_type_depth=False,
150156
)
151157
return self._transformed_depth_point_cloud
152158

pyk4a/playback.py

+12-5
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from .config import FPS, ColorResolution, DepthMode, ImageFormat, WiredSyncMode
1515
from .errors import K4AException, _verify_error
1616
from .module import k4a_module
17+
from .pyk4a import ImuSample
1718
from .results import Result, StreamResult
1819

1920

@@ -62,7 +63,7 @@ def __exit__(self):
6263
@property
6364
def path(self) -> Path:
6465
"""
65-
Record file path
66+
Record file path
6667
"""
6768
return self._path
6869

@@ -93,7 +94,7 @@ def configuration(self) -> Configuration:
9394
@property
9495
def length(self) -> int:
9596
"""
96-
Record length in usec
97+
Record length in usec
9798
"""
9899
if self._length is None:
99100
self._validate_is_open()
@@ -130,7 +131,7 @@ def calibration(self) -> Calibration:
130131

131132
def open(self) -> None:
132133
"""
133-
Open record file
134+
Open record file
134135
"""
135136
if self._handle:
136137
raise K4AException("Playback already opened")
@@ -142,15 +143,15 @@ def open(self) -> None:
142143

143144
def close(self):
144145
"""
145-
Close record file
146+
Close record file
146147
"""
147148
self._validate_is_open()
148149
k4a_module.playback_close(self._handle, self.thread_safe)
149150
self._handle = None
150151

151152
def seek(self, offset: int, origin: SeekOrigin = SeekOrigin.BEGIN) -> None:
152153
"""
153-
Seek playback pointer to specified offset
154+
Seek playback pointer to specified offset
154155
"""
155156
self._validate_is_open()
156157
result = k4a_module.playback_seek_timestamp(self._handle, self.thread_safe, offset, int(origin))
@@ -178,6 +179,12 @@ def get_previouse_capture(self):
178179
thread_safe=self.thread_safe,
179180
)
180181

182+
def get_next_imu_sample(self) -> Optional["ImuSample"]:
183+
self._validate_is_open()
184+
result, imu_sample = k4a_module.playback_get_next_imu_sample(self._handle, self.thread_safe)
185+
self._verify_stream_error(result)
186+
return imu_sample
187+
181188
def _validate_is_open(self):
182189
if not self._handle:
183190
raise K4AException("Playback not opened.")

pyk4a/pyk4a.cpp

+26
Original file line numberDiff line numberDiff line change
@@ -1312,6 +1312,31 @@ static PyObject *playback_get_previous_capture(PyObject *self, PyObject *args) {
13121312
return Py_BuildValue("IN", result, capsule_capture);
13131313
}
13141314

1315+
static PyObject *playback_get_next_imu_sample(PyObject *self, PyObject *args) {
1316+
int thread_safe;
1317+
PyThreadState *thread_state;
1318+
PyObject *capsule;
1319+
k4a_playback_t *playback_handle;
1320+
k4a_imu_sample_t imu_sample;
1321+
k4a_stream_result_t result;
1322+
1323+
PyArg_ParseTuple(args, "Op", &capsule, &thread_safe);
1324+
playback_handle = (k4a_playback_t *)PyCapsule_GetPointer(capsule, CAPSULE_PLAYBACK_NAME);
1325+
1326+
thread_state = _gil_release(thread_safe);
1327+
result = k4a_playback_get_next_imu_sample(*playback_handle, &imu_sample);
1328+
_gil_restore(thread_state);
1329+
1330+
if (result != K4A_STREAM_RESULT_SUCCEEDED) {
1331+
return Py_BuildValue("IN", result, Py_None);
1332+
}
1333+
return Py_BuildValue("I{s:f,s:(fff),s:L,s:(fff),s:L}", result, "temperature", imu_sample.temperature, "acc_sample",
1334+
imu_sample.acc_sample.xyz.x, imu_sample.acc_sample.xyz.y, imu_sample.acc_sample.xyz.z,
1335+
"acc_timestamp", imu_sample.acc_timestamp_usec, "gyro_sample", imu_sample.gyro_sample.xyz.x,
1336+
imu_sample.gyro_sample.xyz.y, imu_sample.gyro_sample.xyz.z, "gyro_timestamp",
1337+
imu_sample.gyro_timestamp_usec);
1338+
}
1339+
13151340
static PyObject *record_create(PyObject *self, PyObject *args) {
13161341
k4a_device_t *device_handle = NULL;
13171342
PyObject *device_capsule;
@@ -1471,6 +1496,7 @@ static PyMethodDef Pyk4aMethods[] = {
14711496
{"playback_get_next_capture", playback_get_next_capture, METH_VARARGS, "Get next capture from playback"},
14721497
{"playback_get_previous_capture", playback_get_previous_capture, METH_VARARGS,
14731498
"Get previous capture from playback"},
1499+
{"playback_get_next_imu_sample", playback_get_next_imu_sample, METH_VARARGS, "Get next imu sample from playback"},
14741500
{"color_image_get_exposure_usec", color_image_get_exposure_usec, METH_VARARGS,
14751501
"Get color image exposure in microseconds"},
14761502
{"color_image_get_white_balance", color_image_get_white_balance, METH_VARARGS, "Get color image white balance"},

pyk4a/pyk4a.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,10 @@ def _stop_imu(self):
105105
res = k4a_module.device_stop_imu(self._device_handle, self.thread_safe)
106106
_verify_error(res)
107107

108-
def get_capture(self, timeout=TIMEOUT_WAIT_INFINITE,) -> "PyK4ACapture":
108+
def get_capture(
109+
self,
110+
timeout=TIMEOUT_WAIT_INFINITE,
111+
) -> "PyK4ACapture":
109112
"""
110113
Fetch a capture from the device and return a PyK4ACapture object. Images are
111114
lazily fetched.

pyk4a/record.py

+5-5
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ def __del__(self):
2626
self.close()
2727

2828
def create(self) -> None:
29-
""" Create record file """
29+
"""Create record file"""
3030
if self.created:
3131
raise K4AException(f"Record already created {self._path}")
3232
device_handle = self._device._device_handle if self._device else None
@@ -38,13 +38,13 @@ def create(self) -> None:
3838
self._handle = handle
3939

4040
def close(self):
41-
""" Close record """
41+
"""Close record"""
4242
self._validate_is_created()
4343
k4a_module.record_close(self._handle, self.thread_safe)
4444
self._handle = None
4545

4646
def write_header(self):
47-
""" Write MKV header """
47+
"""Write MKV header"""
4848
self._validate_is_created()
4949
if self.header_written:
5050
raise K4AException(f"Header already written {self._path}")
@@ -54,7 +54,7 @@ def write_header(self):
5454
self._header_written = True
5555

5656
def write_capture(self, capture: PyK4ACapture):
57-
""" Write capture to file (send to queue) """
57+
"""Write capture to file (send to queue)"""
5858
self._validate_is_created()
5959
if not self.header_written:
6060
self.write_header()
@@ -64,7 +64,7 @@ def write_capture(self, capture: PyK4ACapture):
6464
self._captures_count += 1
6565

6666
def flush(self):
67-
""" Flush queue"""
67+
"""Flush queue"""
6868
self._validate_is_created()
6969
result: Result = k4a_module.record_flush(self._handle, self.thread_safe)
7070
if result != Result.Success:

pyk4a/transformation.py

+19-4
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,31 @@ def depth_image_to_color_camera(depth: np.ndarray, calibration: Calibration, thr
1212
Return empty result if transformation failed
1313
"""
1414
return k4a_module.transformation_depth_image_to_color_camera(
15-
calibration.transformation_handle, thread_safe, depth, calibration.color_resolution,
15+
calibration.transformation_handle,
16+
thread_safe,
17+
depth,
18+
calibration.color_resolution,
1619
)
1720

1821

1922
def depth_image_to_color_camera_custom(
20-
depth: np.ndarray, custom: np.ndarray, calibration: Calibration, thread_safe: bool, interp_nearest: bool = True,
23+
depth: np.ndarray,
24+
custom: np.ndarray,
25+
calibration: Calibration,
26+
thread_safe: bool,
27+
interp_nearest: bool = True,
2128
) -> Optional[np.ndarray]:
2229
"""
2330
Transforms depth image and custom image to color_image space
2431
Return empty result if transformation failed
2532
"""
2633
return k4a_module.transformation_depth_image_to_color_camera_custom(
27-
calibration.transformation_handle, thread_safe, depth, custom, calibration.color_resolution, interp_nearest,
34+
calibration.transformation_handle,
35+
thread_safe,
36+
depth,
37+
custom,
38+
calibration.color_resolution,
39+
interp_nearest,
2840
)
2941

3042

@@ -36,7 +48,10 @@ def depth_image_to_point_cloud(
3648
Return empty result if transformation failed
3749
"""
3850
return k4a_module.transformation_depth_image_to_point_cloud(
39-
calibration.transformation_handle, thread_safe, depth, calibration_type_depth,
51+
calibration.transformation_handle,
52+
thread_safe,
53+
depth,
54+
calibration_type_depth,
4055
)
4156

4257

0 commit comments

Comments
 (0)