In pyzed, other Python threads in the same process stop running while grab() or reset_positional_tracking() is running. It looks like the wrapper doesn’t release the GIL during these calls.
Setup: ZED X Mini, SVGA @ 60 fps, GEN_3 tracking with area memory, ZED SDK [version], Python 3.10, Jetson AGX Orin, JetPack [version].
What I see:
An IMU thread polling get_sensors_data(CURRENT) at 200 Hz only publishes at the camera’s 60 Hz.
During reset_positional_tracking() (2.8 s), it publishes nothing.
A plain heartbeat thread that only calls time.sleep(0.005) and never touches the camera also stalls for 2768 ms during the reset.
Repro:
python
import threading, time
import pyzed.sl as sl
zed = sl.Camera()
init = sl.InitParameters()
init.camera_resolution = sl.RESOLUTION.SVGA
init.camera_fps = 60
assert zed.open(init) == sl.ERROR_CODE.SUCCESS
tp = sl.PositionalTrackingParameters()
tp.enable_area_memory = True
zed.enable_positional_tracking(tp)
def heartbeat():
last = time.monotonic()
while True:
time.sleep(0.005)
now = time.monotonic()
if now - last > 0.05:
print(f"heartbeat stalled {1e3 * (now - last):.0f} ms")
last = now
threading.Thread(target=heartbeat, daemon=True).start()
rt = sl.RuntimeParameters()
for i in range(300):
zed.grab(rt)
if i == 150:
zed.reset_positional_tracking(sl.Transform())
zed.close()
Question: Does pyzed hold the GIL during these calls on purpose? Would it be safe to release it (with nogil:) so other threads, such as an IMU reader, keep running?
thank you for the detailed report and the reproducer.
This is only partially correct. I checked the source code of the ZED Python API:
Camera.grab() releases the GIL (with nogil:) since ZED SDK v5.0.
Camera.get_sensors_data() releases the GIL since ZED SDK v5.1.
Camera.reset_positional_tracking() does not release the GIL in any version, including the latest v5.4.0.
During reset_positional_tracking() (2.8 s), it publishes nothing.
A plain heartbeat thread that only calls time.sleep(0.005) and never touches the camera also stalls for 2768 ms during the reset.
This is caused by the missing GIL release in reset_positional_tracking(). I’m going to report it to the StereoLabs development team so it can be fixed in a future release.
In the meantime, you can patch the wrapper locally since it’s open source. Clone the repository for your ZED SDK version and apply these two changes.
Thanks for the quick and detailed answer. The patch worked. Here are the versions and results.
Versions
ZED SDK 5.3.1 (headers and sl.Camera.get_sdk_version() agree)
pyzed 5.3, built for 5.3.1, Python 3.10.12, numpy 1.26.4
L4T R36.4.7 (JetPack 6.x; nvidia-jetpack meta-package not installed), CUDA 12.6
Ubuntu 22.04.5, ROS 2 Humble
ZED X Mini, FW 2001, SVGA@60, NEURAL_LIGHT, area memory on with a saved .area map
So SDK and pyzed were matched before the patch.
Test
A standalone script (no ROS) with the grab() + get_position() loop on the main thread, a heartbeat thread that only calls time.sleep(0.005), and an IMU thread. reset_positional_tracking() is called on the main thread 10 s into a 20 s run.
before patch
after patch
reset_positional_tracking() duration
2.75 s
2.70 s
heartbeat max gap during reset
2759 ms
5.1 ms
heartbeat max gap during grab()
5.1 ms
5.3 ms
The nogil patch fixes the thread freeze. grab() was never the problem, as you said.
IMU rate: get_sensors_data(CURRENT) vs get_sensors_data_batch()
The 60 Hz I reported wasn’t the GIL. sensors_configuration reports accelerometer and gyroscope at 200 Hz. But get_sensors_data(CURRENT) polled at 200 Hz only returns a new IMU timestamp every 20.00 ms (about 50 Hz). get_sensors_data_batch() returns all samples at 5 ms spacing (200 Hz). It looks like samples arrive in groups of about 4 every 20 ms, and CURRENT only exposes the newest one. I’ve switched to the batch call.
Remaining issue: no IMU samples during reset_positional_tracking()
After the patch, with the IMU thread calling get_sensors_data_batch() at 200 Hz from its own thread:
the IMU thread keeps running during the reset (4003 successful calls in 20 s, about 200/s throughout)
but the IMU sensor timestamps have a 2220 ms gap during the reset. Those samples are never returned later, so they’re missing, not delayed.
Questions
Is it expected that IMU data stops during reset_positional_tracking()? Is there a way to keep the IMU streaming through a reset?
Is get_sensors_data(CURRENT) updating only every 20 ms expected on the ZED X Mini at 200 Hz?
Is it safe to call get_sensors_data_batch() from a separate thread while grab() runs (and during a reset)? Your sample calls it right after grab().
thank you for the detailed tests and for confirming that the patch solves the thread freeze. I’ve reported the missing GIL release in reset_positional_tracking() to the development team so it can be fixed in a future release.
Is it expected that IMU data stops during reset_positional_tracking()? Is there a way to keep the IMU streaming through a reset?
This is caused by how get_sensors_data_batch() works: it returns all the sensor samples associated with the most recent grabbed frame (API reference). While reset_positional_tracking() runs on the main thread, grab() is not called; the samples received during the reset are not associated with any frame, so they are never returned, which matches the 2220 ms gap you measured.
To keep the IMU streaming during a reset, your IMU thread can call get_sensors_data(sl.TIME_REFERENCE.CURRENT) while the reset is in progress, because it does not depend on grab() (Retrieve New Sensor Data). I’ve also asked the development team to evaluate keeping the batched samples available across a reset.
Is get_sensors_data(CURRENT) updating only every 20 ms expected on the ZED X Mini at 200 Hz?
Yes. The IMU samples at 200 Hz, but the samples reach the host in groups, as you correctly noticed, and TIME_REFERENCE.CURRENT only exposes the newest one. get_sensors_data_batch() is the correct function to retrieve the full 200 Hz stream.
Is it safe to call get_sensors_data_batch() from a separate thread while grab() runs (and during a reset)?
Yes, the sensors functions can be called from a thread different from the one running grab(). I also recommend upgrading to the latest ZED SDK v5.5; it fixes possible crashes in applications that call the SDK from a thread other than the grab() one, for example reset_positional_tracking() now waits for the current grab() to complete (ZED SDK 5.5 release notes). Remember to apply the nogil patch again to the v5.5 wrapper.