Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions bindings/python/src/pipeline/datatype/ToFConfigBindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ void bind_tofconfig(pybind11::module& m, void* pCallstack) {

py::class_<ToFConfig, Py<ToFConfig>, Buffer, std::shared_ptr<ToFConfig>> toFConfig(m, "ToFConfig", DOC(dai, ToFConfig));
py::enum_<ToFConfig::Profile> toFConfigProfile(toFConfig, "Profile", DOC(dai, ToFConfig, Profile));
py::enum_<ToFConfig::PipeType> toFConfigPipeType(toFConfig, "PipeType", DOC(dai, ToFConfig, PipeType));

///////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////
Expand All @@ -38,6 +39,11 @@ void bind_tofconfig(pybind11::module& m, void* pCallstack) {
.value("HIGH_RANGE", ToFConfig::Profile::HIGH_RANGE)
.export_values();

toFConfigPipeType.value("AUTO", ToFConfig::PipeType::AUTO)
.value("FLOOD", ToFConfig::PipeType::FLOOD)
.value("DOT", ToFConfig::PipeType::DOT)
.export_values();

toFConfig.def(py::init<>())
.def("__repr__", &ToFConfig::str)
// .def(py::init<std::shared_ptr<ToFConfig>>())
Expand All @@ -58,6 +64,18 @@ void bind_tofconfig(pybind11::module& m, void* pCallstack) {
.def_readwrite("enablePhaseUnwrapping", &ToFConfig::enablePhaseUnwrapping, DOC(dai, ToFConfig, enablePhaseUnwrapping))
.def_readwrite("phaseUnwrapErrorThreshold", &ToFConfig::phaseUnwrapErrorThreshold, DOC(dai, ToFConfig, phaseUnwrapErrorThreshold))

// RVC4 / VD55H1 depth post-processing tuning (only honored on RVC4)
.def_readwrite("enableBilateralFilter", &ToFConfig::enableBilateralFilter, DOC(dai, ToFConfig, enableBilateralFilter))
.def_readwrite("bilateralStdFactor", &ToFConfig::bilateralStdFactor, DOC(dai, ToFConfig, bilateralStdFactor))
.def_readwrite("bilateralKernelSize", &ToFConfig::bilateralKernelSize, DOC(dai, ToFConfig, bilateralKernelSize))
.def_readwrite("enableTemporalNoiseReduction", &ToFConfig::enableTemporalNoiseReduction, DOC(dai, ToFConfig, enableTemporalNoiseReduction))
.def_readwrite("tnrMaxGain", &ToFConfig::tnrMaxGain, DOC(dai, ToFConfig, tnrMaxGain))
.def_readwrite("tnrStdFactor", &ToFConfig::tnrStdFactor, DOC(dai, ToFConfig, tnrStdFactor))
.def_readwrite("enableFlyingPixelFilter", &ToFConfig::enableFlyingPixelFilter, DOC(dai, ToFConfig, enableFlyingPixelFilter))
.def_readwrite("flyingPixelDepthThreshold", &ToFConfig::flyingPixelDepthThreshold, DOC(dai, ToFConfig, flyingPixelDepthThreshold))
.def_readwrite("flyingPixelMinDepthOccurrence", &ToFConfig::flyingPixelMinDepthOccurrence, DOC(dai, ToFConfig, flyingPixelMinDepthOccurrence))
.def_readwrite("pipeType", &ToFConfig::pipeType, DOC(dai, ToFConfig, pipeType))

.def("setMedianFilter", &ToFConfig::setMedianFilter, DOC(dai, ToFConfig, setMedianFilter))
.def("setProfilePreset", &ToFConfig::setProfilePreset, DOC(dai, ToFConfig, setProfilePreset))

Expand Down
2 changes: 2 additions & 0 deletions bindings/python/src/pipeline/node/ToFBindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,10 @@ void bind_tof(pybind11::module& m, void* pCallstack) {
.def_readonly("depth", &ToFBase::depth, DOC(dai, node, ToFBase, depth), DOC(dai, node, ToFBase, depth))
.def_readonly("amplitude", &ToFBase::amplitude, DOC(dai, node, ToFBase, amplitude), DOC(dai, node, ToFBase, amplitude))
.def_readonly("intensity", &ToFBase::intensity, DOC(dai, node, ToFBase, intensity), DOC(dai, node, ToFBase, intensity))
.def_readonly("confidence", &ToFBase::confidence, DOC(dai, node, ToFBase, confidence), DOC(dai, node, ToFBase, confidence))
.def_readonly("phase", &ToFBase::phase, DOC(dai, node, ToFBase, phase), DOC(dai, node, ToFBase, phase))
.def_readonly("raw", &ToFBase::raw, DOC(dai, node, ToFBase, raw), DOC(dai, node, ToFBase, raw))
.def_readonly("rawInput", &ToFBase::rawInput, DOC(dai, node, ToFBase, rawInput), DOC(dai, node, ToFBase, rawInput))
.def_readonly("initialConfig", &ToFBase::initialConfig, DOC(dai, node, ToFBase, initialConfig), DOC(dai, node, ToFBase, initialConfig))
.def("build",
py::overload_cast<CameraBoardSocket, ToFConfig::Profile, std::optional<float>>(&ToFBase::build),
Expand Down
8 changes: 6 additions & 2 deletions examples/python/ToF/tof_align.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,12 @@ def colorizeDepth(frameDepth: np.ndarray, minDepth: float, maxDepth: float) -> n
try:
logDepth = np.log(frameDepth.astype(np.float32) + 1e-6)
logDepth[invalidMask] = 0.0
logDepth = np.clip(logDepth, np.log(minDepth + 1e-6), np.log(maxDepth + 1e-6))
depthFrameColor = np.interp(logDepth, (logDepth[~invalidMask].min(), logDepth[~invalidMask].max()), (0, 255))
logMin, logMax = np.log(minDepth + 1e-6), np.log(maxDepth + 1e-6)
logDepth = np.clip(logDepth, logMin, logMax)
# Map from the FIXED depth range (not the per-frame min/max) so a given depth
# always maps to the same color -- otherwise the mapping shifts every frame and
# the image flickers.
Comment on lines +32 to +34

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unecessarry no?

depthFrameColor = np.interp(logDepth, (logMin, logMax), (0, 255))
depthFrameColor = depthFrameColor.astype(np.uint8)
depthFrameColor = cv2.applyColorMap(depthFrameColor, cv2.COLORMAP_JET)
depthFrameColor[invalidMask] = 0
Expand Down
8 changes: 6 additions & 2 deletions examples/python/ToF/tof_all_queues.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,12 @@ def colorizeDepth(frame: np.ndarray, minDepth: float, maxDepth: float) -> np.nda
try:
logDepth = np.log(frame.astype(np.float32) + 1e-6)
logDepth[invalidMask] = 0.0
logDepth = np.clip(logDepth, np.log(minDepth + 1e-6), np.log(maxDepth + 1e-6))
colored = np.interp(logDepth, (logDepth[~invalidMask].min(), logDepth[~invalidMask].max()), (0, 255))
logMin, logMax = np.log(minDepth + 1e-6), np.log(maxDepth + 1e-6)
logDepth = np.clip(logDepth, logMin, logMax)
# Map from the FIXED depth range (not the per-frame min/max) so a given depth
# always maps to the same color -- otherwise the mapping shifts every frame and
# the image flickers.
Comment on lines +22 to +24

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, unnecessarry comment?

colored = np.interp(logDepth, (logMin, logMax), (0, 255))
colored = colored.astype(np.uint8)
colored = cv2.applyColorMap(colored, cv2.COLORMAP_JET)
colored[invalidMask] = 0
Expand Down
8 changes: 6 additions & 2 deletions examples/python/ToF/tof_minimal.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,12 @@ def colorizeDepth(frame: np.ndarray, minDepth: float, maxDepth: float) -> np.nda
try:
logDepth = np.log(frame.astype(np.float32) + 1e-6)
logDepth[invalidMask] = 0.0
logDepth = np.clip(logDepth, np.log(minDepth + 1e-6), np.log(maxDepth + 1e-6))
colored = np.interp(logDepth, (logDepth[~invalidMask].min(), logDepth[~invalidMask].max()), (0, 255))
logMin, logMax = np.log(minDepth + 1e-6), np.log(maxDepth + 1e-6)
logDepth = np.clip(logDepth, logMin, logMax)
# Map from the FIXED depth range (not the per-frame min/max) so a given depth
# always maps to the same color -- otherwise the mapping shifts every frame and
# the image flickers.
colored = np.interp(logDepth, (logMin, logMax), (0, 255))
Comment on lines +22 to +25

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like an unnecessarry comment?

colored = colored.astype(np.uint8)
colored = cv2.applyColorMap(colored, cv2.COLORMAP_JET)
colored[invalidMask] = 0
Expand Down
244 changes: 244 additions & 0 deletions examples/python/ToF/tof_raw_rvc4.py

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CamelCase instead of snake_case

Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
#!/usr/bin/env python3
"""Test script: capture raw depth via Camera + ToFBase pipeline, save to temp dir, verify,
then replay the saved raw sensor frames back into a fresh ToFBase node and plot the result.

Capture pipeline:
┌─────────────────────────┐
│ Camera (CAM_D, ToF) │
└──────────┬──────────────┘
│ .raw
┌──────────────────────────┐
│ ToFBase │
└────┬──────────┬──────────┘
│ .raw │ .depth (also .amplitude, not captured here)
▼ ▼
raw_q depth_q
│ │
▼ ▼
raw_<ts>.npz depth_<ts>.npy <-- saved and validated

Replay pipeline (no Camera node, raw frames fed from disk):
raw_<ts>.npz --> rawInput queue --> ToFBase --> .depth --> depth_q --> matplotlib

Usage:
python tof_raw_rvc4.py
python tof_raw_rvc4.py --socket CAM_D --frames 5
python tof_raw_rvc4.py --ip 192.168.1.100
"""

import argparse
import os
import sys
import tempfile
import time

import matplotlib.pyplot as plt
import numpy as np

import depthai as dai


WARMUP_FRAMES = 10


def parse_args():
parser = argparse.ArgumentParser(description="ToF raw depth save + verify test")
parser.add_argument("--ip", default=None, help="Device IP address (omit for USB)")
parser.add_argument("--socket", default="CAM_D", help="ToF camera board socket (default: CAM_D)")
parser.add_argument("--frames", type=int, default=3,
help="Number of depth frames to save (default: 3)")
parser.add_argument("--fwp", default=None,
help="Optional path to RVC4 firmware package (.tar.xz)")
parser.add_argument("--plot-out", default=None,
help="Path to save the replay depth plot PNG (default: <tempdir>/replay_depth.png)")
return parser.parse_args()


def build_capture_pipeline(socket: dai.CameraBoardSocket, profile):
pipeline = dai.Pipeline()

tof_base = pipeline.create(dai.node.ToFBase)
tof_base.build(boardSocket=socket, profile=profile)

cam = pipeline.create(dai.node.Camera)
cam.setSensorType(dai.CameraSensorType.TOF)
cam.build(boardSocket=tof_base.getBoardSocket())

cam.raw.link(tof_base.rawInput)

# Save the frames actually fed into rawInput, not ToFBase's own (unreliable on RVC4) .raw passthrough.
raw_q = cam.raw.createOutputQueue()
depth_q = tof_base.depth.createOutputQueue()

return pipeline, raw_q, depth_q


def build_replay_pipeline(socket: dai.CameraBoardSocket, profile):
"""Pipeline with only ToFBase (no Camera) -- raw frames are pushed in from the host."""
pipeline = dai.Pipeline()

tof_base = pipeline.create(dai.node.ToFBase)
tof_base.build(boardSocket=socket, profile=profile)

raw_in_q = tof_base.rawInput.createInputQueue()
depth_q = tof_base.depth.createOutputQueue()

return pipeline, raw_in_q, depth_q


def verify_saved_files(out_dir: str, expected_count: int) -> bool:
depth_files = sorted(f for f in os.listdir(out_dir) if f.startswith("depth_") and f.endswith(".npy"))

print(f"\n[Verify] Expected {expected_count} file(s), found {len(depth_files)}")
if len(depth_files) < expected_count:
print(f"[FAIL] Not enough depth files saved.")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Remove extraneous f prefix.

f"[FAIL] Not enough depth files saved." has no placeholders.

🔧 Proposed fix
-        print(f"[FAIL] Not enough depth files saved.")
+        print("Not enough depth files saved.")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
print(f"[FAIL] Not enough depth files saved.")
print("Not enough depth files saved.")
🧰 Tools
🪛 Ruff (0.15.21)

[error] 95-95: f-string without any placeholders

Remove extraneous f prefix

(F541)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/python/ToF/tof_raw_rvc4.py` at line 95, Remove the unnecessary
f-string prefix from the failure message in the depth-file validation path,
keeping the printed text unchanged.

Source: Linters/SAST tools

return False

all_ok = True
for fname in depth_files:
path = os.path.join(out_dir, fname)
size = os.path.getsize(path)
arr = np.load(path)
nonzero = int(np.count_nonzero(arr))
status = "OK" if nonzero > 0 else "EMPTY"
print(f" {fname}: shape={arr.shape} dtype={arr.dtype} nonzero={nonzero} size={size}B [{status}]")
if nonzero == 0:
print(f" [FAIL] {fname} contains only zeros.")
all_ok = False

return all_ok


def load_raw_frames(out_dir: str):
"""Load raw_<ts>.npz files saved during capture, sorted by timestamp."""
raw_files = sorted(f for f in os.listdir(out_dir) if f.startswith("raw_") and f.endswith(".npz"))
frames = []
for fname in raw_files:
with np.load(os.path.join(out_dir, fname)) as npz:
data = npz["data"]
frame_type = getattr(dai.ImgFrame.Type, str(npz["type"]))
ts = int(fname[len("raw_"):-len(".npz")])
frames.append((ts, data, frame_type))
return frames


def replay_raw_frames(socket: dai.CameraBoardSocket, profile, out_dir: str):
"""Feed saved raw frames back into a fresh ToFBase node and collect the resulting depth frames."""
raw_frames = load_raw_frames(out_dir)
if not raw_frames:
print("[Replay] No raw frames found to replay.")
return []

print(f"\n[Replay] Feeding {len(raw_frames)} saved raw frame(s) back into ToFBase...")
pipeline, raw_in_q, depth_q = build_replay_pipeline(socket, profile)

depth_frames = []
with pipeline as p:
p.start()
for ts, data, frame_type in raw_frames:
img = dai.ImgFrame()
img.setCvFrame(data, frame_type)
raw_in_q.send(img)

depth_msg = depth_q.get()
depth_frames.append((ts, depth_msg.getFrame()))
print(f"[Replay] ts={ts}ms -> depth={depth_frames[-1][1].shape}")

return depth_frames


def plot_depth_frames(depth_frames, out_path: str):
"""Plot replayed depth frames side by side and save to a PNG file."""
n = len(depth_frames)
fig, axes = plt.subplots(1, n, figsize=(5 * n, 4), squeeze=False)

for ax, (ts, depth) in zip(axes[0], depth_frames):
im = ax.imshow(depth, cmap="turbo")
ax.set_title(f"ts={ts}ms")
ax.axis("off")
fig.colorbar(im, ax=ax, fraction=0.046, pad=0.04)

fig.suptitle("Replayed ToF depth (from saved raw frames)")
fig.tight_layout()
fig.savefig(out_path, dpi=150)
print(f"[Plot] Saved to: {out_path}")

try:
plt.show()
except Exception:
pass
Comment on lines +167 to +170

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Broad except Exception: pass silently swallows all errors.

Static analysis flags this as blind exception handling with no logging (S110/BLE001), and suggests contextlib.suppress for the pattern (SIM105). At minimum, narrow the exception or log it so real failures (e.g., a broken matplotlib backend vs. an unrelated bug) aren't indistinguishable.

♻️ Proposed refactor
+import contextlib
...
-    try:
-        plt.show()
-    except Exception:
-        pass
+    with contextlib.suppress(Exception):
+        plt.show()
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
try:
plt.show()
except Exception:
pass
import contextlib
with contextlib.suppress(Exception):
plt.show()
🧰 Tools
🪛 Ruff (0.15.21)

[warning] 167-170: Use contextlib.suppress(Exception) instead of try-except-pass

Replace try-except-pass with with contextlib.suppress(Exception): ...

(SIM105)


[error] 169-170: try-except-pass detected, consider logging the exception

(S110)


[warning] 169-169: Do not catch blind exception: Exception

(BLE001)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/python/ToF/tof_raw_rvc4.py` around lines 167 - 170, Update the
exception handling around plt.show() to avoid a bare Exception that silently
suppresses failures: catch only the expected matplotlib display exception, or
log unexpected errors before continuing. Preserve the intended behavior of
ignoring known display-backend failures while making unrelated failures
distinguishable.

Source: Linters/SAST tools



def main():
args = parse_args()

if args.ip:
os.environ["DEPTHAI_DEVICE_NAME_LIST"] = args.ip
if args.fwp:
os.environ["DEPTHAI_DEVICE_RVC4_FWP"] = args.fwp

socket = getattr(dai.CameraBoardSocket, args.socket)
preset = dai.ToFConfig.Profile.HIGH_RANGE

print(f"[Config] socket={args.socket}, frames={args.frames}")
print(f"[Config] DepthAI {dai.__version__}")

out_dir = tempfile.mkdtemp(prefix="tof_depth_test_")
print(f"[Temp] Saving to: {out_dir}")

pipeline, raw_q, depth_q = build_capture_pipeline(socket, preset)

saved = 0
frame_count = 0
warmup_done = False

with pipeline as p:
p.start()
print(f"[Pipeline] Running (warmup={WARMUP_FRAMES} frames)...")

t_start = time.monotonic()

while p.isRunning() and saved < args.frames:
depth_msg = depth_q.get()
raw_msg = raw_q.get()
frame_count += 1

if not warmup_done:
if frame_count >= WARMUP_FRAMES:
warmup_done = True
print(f"[Warmup] Done after {frame_count} frames. Starting capture...")
continue

depth_data = depth_msg.getFrame()
ts = int(depth_msg.getTimestamp().total_seconds() * 1000)
path = os.path.join(out_dir, f"depth_{ts}.npy")
np.save(path, depth_data)

raw_data = raw_msg.getFrame()
raw_path = os.path.join(out_dir, f"raw_{ts}.npz")
np.savez(raw_path, data=raw_data, type=np.array(raw_msg.getType().name))

saved += 1
print(f"[Save {saved}/{args.frames}] depth={depth_data.shape} raw={raw_data.shape} ts={ts}ms")

elapsed = time.monotonic() - t_start
print(f"[Done] {frame_count} total frames in {elapsed:.1f}s")

passed = verify_saved_files(out_dir, args.frames)

print("\n" + ("=" * 40))
if passed:
print("RESULT: PASS — depth files saved and contain valid data")
else:
print("RESULT: FAIL — see errors above")
sys.exit(1)

depth_frames = replay_raw_frames(socket, preset, out_dir)
if depth_frames:
plot_out = args.plot_out or os.path.join(out_dir, "replay_depth.png")
plot_depth_frames(depth_frames, plot_out)


if __name__ == "__main__":
main()
Loading