Skip to content

feat: rf-detr-detector node — object detection on H264 camera streams#9

Closed
edgarriba wants to merge 43 commits intomainfrom
feat/rf-detr-detector
Closed

feat: rf-detr-detector node — object detection on H264 camera streams#9
edgarriba wants to merge 43 commits intomainfrom
feat/rf-detr-detector

Conversation

@edgarriba
Copy link
Copy Markdown
Member

Summary

  • New Python node that subscribes to H264 camera frames and publishes COCO object detections via Zenoh
  • Uses RF-DETR with configurable model size (nano/small/base/medium/large) and Jetson NVDEC hardware H264 decoding
  • Decodes continuously at camera rate; runs inference on a dedicated thread at a configurable target FPS (default 1fps), always using the most recent frame

Key design decisions

  • Decode/infer split: subscriber callback only pushes H264 into GStreamer; a separate inference thread fires at target_fps. Lock-guarded single-slot replaces queue.Queue so inference always sees the latest frame, not a stale buffered one.
  • Model selection: model: large uses the 2026 RF-DETR-Large checkpoint (704px) for best accuracy; falls back to base (560px) if not specified
  • optimize_for_inference(): called at startup to JIT-trace the model and reduce per-frame latency
  • Topic layout: publishes to camera/{name}/detections mirroring the camera namespace; health at rf_detr_{name}/health (distinct from camera node)
  • Jetson-specific: uses nvv4l2decoder + nvvidconv NVDEC pipeline on CUDA; falls back to avdec_h264 CPU decoder when CUDA unavailable

Config fields (configs/tapo_terrace.yaml)

name: rf_detr_tapo_terrace          # unique instance name for health topic
subscribe_topic: camera/tapo_terrace/compressed
publish_topic: camera/tapo_terrace/detections
confidence_threshold: 0.5
model: large                         # nano | small | base | medium | large
target_fps: 1.0                      # inference rate (0.1–30.0)

Test plan

  • Node runs on Jetson Orin (SM87 CUDA, NVDEC pipeline active)
  • Health heartbeat visible at bubbaloop/**/rf_detr_tapo_terrace/health
  • Detections published to bubbaloop/local/{machine}/camera/tapo_terrace/detections at 1fps
  • RF-DETR Large detects 8 objects per frame consistently at ~205ms inference
  • Memory stable at ~1.4G under 1.5G systemd limit

🤖 Generated with Claude Code

edgarriba and others added 30 commits February 1, 2026 09:17
* feat: add 5 official nodes (rtsp-camera, openmeteo, foxglove, recorder, inference)

Extracted from the main bubbaloop repo as standalone crates.
Each node depends on bubbaloop-schemas via git (not path),
has its own Cargo.lock, [workspace] opt-out, and .cargo/config.toml
for local target directory.

Nodes:
- rtsp-camera: RTSP camera capture with GStreamer H264 decode
- openmeteo: Open-Meteo weather data publisher
- foxglove: Foxglove Studio WebSocket bridge
- recorder: MCAP file recorder for ROS-Z topics
- inference: ML inference node for camera processing

Also adds nodes.yaml registry for marketplace discovery and
updates README/CLAUDE.md with the full node table and git dep
instructions.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* feat: add CI workflow, fix deps, complete nodes.yaml registry

- Add GitHub Actions CI with validate, rust-nodes (matrix), and python-nodes jobs
- Fix system-telemetry bubbaloop-schemas dependency from path to git
- Add system-telemetry and network-monitor entries to nodes.yaml

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* refactor: remove foxglove, recorder nodes and rtsp-camera SHM

- Remove foxglove node (Foxglove Studio bridge)
- Remove recorder node (MCAP file recorder)
- Remove shared memory (SHM) publishing from rtsp-camera
  - Drop zenoh shared-memory feature, prost direct dependency
  - Remove shm_task(), frame_to_raw_image(), SHM_POOL_SIZE
  - Keep compressed H264 publishing only
- Update nodes.yaml, CI matrix, README accordingly

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: resolve CI failures across all nodes

- Fix pixi.toml: [project] → [workspace] (deprecated) for rtsp-camera,
  openmeteo, inference
- Add protobuf + pkg-config to system-telemetry pixi.toml (prost-build
  needs protoc)
- Add [workspace] opt-out to system-telemetry Cargo.toml
- Generate pixi.lock for system-telemetry and inference (required by
  setup-pixi cache)
- Run clippy through pixi env in CI so protoc is in PATH
- All nodes now depend on bubbaloop-schemas from main which includes
  camera.proto and weather.proto

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: add libprotobuf dep for protoc binary in CI

The conda-forge `protobuf` package is the Python library; the `protoc`
binary lives in `libprotobuf`. rtsp-camera got it transitively via
GStreamer deps, but openmeteo/inference/system-telemetry need it
explicitly.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: resolve clippy warnings in system-telemetry

- sequence % 10 == 0 → sequence.is_multiple_of(10)
- for (_name, data) in map → for data in map.values()

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: single-camera-per-process rtsp-camera node

Flatten multi-camera config to single-camera Config struct with
validation. Replace cameras_node binary with rtsp_camera_node that
follows the system-telemetry pattern (-c config.yaml, -e endpoint).
Each camera instance gets its own process, config file, and scoped
health topic (rtsp-camera-{name}).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: resolve clippy warnings in rtsp-camera node

Remove needless Default::default() struct update and allow
too_many_arguments on private compressed_task function.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Users must provide their own config via -c flag or --config when
registering with the daemon. Example configs in configs/ serve as
templates to copy and customize.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Clarify the distinction between node.yaml (manifest) and instance
params files (runtime config passed via -c). Add a "Node Configuration"
section to README.md, update CLAUDE.md checklist terminology, add header
comments to all example config files, replace hardcoded RTSP IPs with
placeholder URLs, and fix hostname hyphen sanitization for ros-z topic
compatibility.

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Builds all 4 Rust nodes (rtsp-camera, openmeteo, inference,
system-telemetry) for both x86_64 and ARM64 architectures.
Generates SHA256 checksums and publishes as GitHub Release assets.

Triggered on release publish or manual workflow_dispatch.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Audit all nodes against CLAUDE.md checklist and fix compliance gaps:

- system-telemetry: add topic name validation and rate_hz bounds checking
- network-monitor: add topic/numeric validation, endpoint default, build task
- openmeteo: add config bounds validation, fix Header scope field, scope
  topics with bubbaloop/{scope}/{machine}/ prefix, rename -z to -e flag,
  add HTTP client timeout
- inference: add -c/-e CLI flags, config.yaml, scoped subscribe topic

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
query.key_expr is a property in Zenoh Python API, not a method.
Using query.key_expr() caused TypeError crashes in schema queryable
callbacks, preventing dashboard from discovering node schemas.

Also adds config validation, schema queryable with descriptor.bin,
and proper cleanup on close.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…e node

- Each Rust node now generates custom proto types locally via build.rs +
  src/proto.rs, using extern_path to reuse Header from bubbaloop-schemas
- Migrated all nodes from ros-z to vanilla Zenoh + prost
- Added local protos/ directories with node-specific .proto files
- Added anyhow dependency to openmeteo (was missing, caused compile failure)
- Removed inference node (unused)
- Updated CLAUDE.md with self-contained proto pattern conventions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- CLAUDE.md: add bubbaloop-node-sdk to structure and key files
- skillet-development.md: change "Future: Node SDK" to "Node SDK (Recommended)"
- create-your-first-node.md: show SDK-based scaffold and Node trait
- plugin-development.md: SDK as primary Quick Start path
- README.md: note SDK in Node Lifecycle section
- node-sdk-design.md: mark status as Shipped

All docs now present the SDK as the primary contributor workflow.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- All 4 node.yaml files now include: capabilities, publishes,
  subscribes, commands, and requires fields for MCP discovery
- Remove inference node from nodes.yaml registry and README
  (source was deleted in 67dcc27)
- Update tags in nodes.yaml for better marketplace search

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The inference node was removed from the repository. Update CI validation,
build matrix, and release body to only reference existing nodes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The parse method is only used in tests. Since system-telemetry is a
binary crate, clippy correctly flags it as dead code with -D warnings.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… drop protobuf, snake_case fields

* feat: rewrite camera node to use bubbaloop-node SDK

- Replace 12 direct deps with single bubbaloop-node SDK dependency
- Implement Node trait: init() + run() pattern (main.rs: 118 → 11 lines)
- SDK handles: Zenoh session, health heartbeat, schema queryable, config, shutdown
- Use ProtoPublisher<CompressedImage> with encoding-first metadata
- Add MessageTypeName impl for CompressedImage
- extern_path uses ::bubbaloop_node::schemas::header::v1
- Increase appsink max-buffers to 30, config-interval=1 for periodic SPS/PPS
- Add NAL type diagnostic logging for first frames

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: 10fps rate limiting, dual camera configs (entrance + terrace)

- Add application-level frame rate limiting using drift-free scheduled
  next-frame-time approach; frame_rate config field now enforced
- config.yaml, configs/entrance.yaml, configs/terrace.yaml all set to 10fps
- Update entrance/terrace configs with real IPs and credentials
- Remove unused h264_decode.rs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: use SDK protos dir for header.proto import, add NAL diagnostics

- build.rs: resolve header.proto include path from SDK via
  DEP_BUBBALOOP_NODE_PROTOS_DIR, falling back to local protos/ dir
- h264_capture.rs: config-interval=-1 (SPS/PPS before every IDR)
- rtsp_camera_node.rs: log NAL types for first 150 published frames

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: remove local header.proto, use SDK OUT_DIR via DEP_ metadata

header.proto is now resolved from DEP_BUBBALOOP_NODE_PROTOS_DIR (written
to SDK's OUT_DIR by bubbaloop-node build script). Works for git/path/crates.io
deps. No local proto copy needed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: use bubbaloop-node-build, build.rs is now one line

Replace manual prost-build setup with bubbaloop_node_build::compile_protos.
Add bubbaloop-node-build as build-dependency with local dev patch.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor(rtsp-camera): simplify, drop regex-lite, reduce NAL diagnostics

- config.rs: replace regex-lite validation with direct byte checks;
  remove regex-lite dependency from Cargo.toml
- rtsp_camera_node.rs: extract NAL type scanning into extract_nal_types();
  reduce diagnostic logging from 150 → 10 frames
- CLAUDE.md: full rewrite — correct health topic format ({name}/health),
  bubbaloop-node/bubbaloop-node-build deps, one-line build.rs, multi-
  instance registration, proto setup without copying header.proto

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor(openmeteo,network-monitor): Python + JSON, use sync SDK

openmeteo: rewrite from Rust to Python
- Fetches current/hourly/daily from Open-Meteo free API
- Publishes JSON via bubbaloop-sdk JsonPublisher
- No protobuf, no build step — pixi run run starts immediately
- Location auto-discovered from IP or set in config
- Polling intervals configurable (current: 30s, hourly: 30m, daily: 3h)
- run_node() handles CLI args, health heartbeat, shutdown

network-monitor: drop protobuf, publish JSON
- Removed protobuf/grpcio-tools deps and build step
- Check results are plain dicts: {name, type, target, status, latency_ms}
- Summary: {total, healthy, unhealthy}
- Uses bubbaloop-sdk run_node() for health + shutdown boilerplate
- Dropped 280 lines, kept all functionality

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor(system-telemetry): Rust → Python + JSON, use sync SDK

Replaced Rust/protobuf/sysinfo with Python/psutil/JSON.

- CPU: usage%, per-core%, count, frequency_mhz
- Memory: total/used/available bytes, usage%
- Disk: total/used/available bytes, usage% (root partition)
- Network: bytes_sent/recv totals and per-second rates
- Load: 1/5/15-min averages
- All metrics optional via config collect.{cpu,memory,disk,network,load}
- run_node() handles CLI args, health heartbeat, shutdown
- No build step — pixi run run starts immediately

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(python-nodes): camelCase output + local SDK path, fix run command

- system-telemetry: publish camelCase keys (usagePercent, totalBytes,
  perCore, oneMin…) to match dashboard SystemTelemetryView interfaces
- openmeteo: apply _snake_to_camel to all API output so windSpeed_10m,
  weatherCode, precipitationProbability, temperature_2mMax etc. match
  WeatherView interfaces exactly
- network-monitor: use statusName/latencyMs/statusCode keys directly
  (avoids enum lookup mismatch in toHealthCheck)
- All three: add name field to config.yaml for health topic isolation;
  fix node.yaml run command (remove spurious --); switch pixi.toml
  bubbaloop-sdk to local path (remote branch not yet merged to main)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(network-monitor): use statusName key after renaming from status

* fix(node.yaml): flat command string + valid capabilities for daemon registration

- command must be a plain string (daemon appends -c <config> automatically)
- capabilities must be sensor|actuator|processor|gateway (not custom strings)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs(CLAUDE.md): document 3-step CLI flow, node.yaml constraints, JSON conventions

- add/install/start are three separate steps; document each clearly
- command/build must be flat strings (not nested maps)
- capabilities enum: sensor|actuator|processor|gateway only
- Python SDK conventions, JSON camelCase field naming rule
- updated checklist with node.yaml and Python SDK requirements
- reference implementations: rtsp-camera (Rust), system-telemetry + openmeteo (Python)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor(python-nodes): rename pixi task run → main

pixi run main is clearer than pixi run run

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor(python-nodes): use snake_case field names, remove manual camelCase conversion

Dashboard now applies snakeToCamel() after JSON.parse(), so Python nodes
can publish idiomatic snake_case without any conversion logic:

- openmeteo: remove _snake_to_camel() function, forward raw API data as-is
- system-telemetry: usage_percent, per_core, total_bytes, bytes_sent, one_min, etc.
- network-monitor: status_name, latency_ms, status_code
- CLAUDE.md: update convention — snake_case in nodes, snakeToCamel in dashboard

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: update pixi.lock files + clarify rtsp-camera patch comment

- Regenerate pixi.lock for openmeteo, network-monitor, system-telemetry
  (task renamed run → main caused lock to drift)
- Add note to rtsp-camera/.cargo/config.toml: local patch can be removed
  after bubbaloop PR #64 merges to main

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(ci): switch bubbaloop-sdk dep from local path to git URL

Local path = "/home/nvidia/..." breaks CI. Point to the
feat/encoding-first-decode branch where the Python SDK lives.
Will update to branch = "main" after PR #64 merges.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* ci: move openmeteo/system-telemetry to python-nodes job

These were rewritten from Rust to Python. Remove them from rust-nodes
matrix (which runs cargo build/clippy/test) and add to python-nodes
matrix (which runs pixi install + py_compile syntax check).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: point SDK to main branch after PR #64 merge, remove local patch

- bubbaloop-sdk: branch = "feat/encoding-first-decode" → branch = "main"
- Update pixi.lock files with new commit hash from main
- rtsp-camera: remove local [patch] override (bubbaloop-node now on main)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
- Correct node types: system-telemetry and openmeteo are now Python (not Rust)
- Document 3-step lifecycle: add → install → start (install was missing)
- Add Python SDK (bubbaloop-sdk) pixi.toml dep and example
- Add Rust SDK (bubbaloop-node) minimal example
- Fix health topic format: {name}/health (was health/{name})
- Fix quick start commands: pixi run main (was pixi run run/build-proto)
- Document snake_case JSON convention (dashboard applies snakeToCamel)
- Multi-instance example with tapo-entrance / tapo-terrace
- Clean up stale Rust/protobuf references for Python nodes

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
openmeteo, system-telemetry, network-monitor are now pure Python nodes.
Remove: src/, protos/, build.rs, Cargo.toml, build_proto.py, __pycache__

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
openmeteo and system-telemetry are pure Python — no Cargo config needed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Declares runtime-controllable commands in node.yaml for each node.
These are interface declarations — not yet implemented in node code.
Enables agents to discover what commands each node accepts via
discover_capabilities / manifest queryable.

- system-telemetry: set_rate (publish Hz)
- openmeteo: set_location (lat/lon)
- network-monitor: set_interval (check rate)
- rtsp-camera: set_framerate (capture fps)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Update pixi.toml and pixi.lock for openmeteo, system-telemetry, and
network-monitor to use the feature branch SDK with publisher queryable
mirror. Reverts to main once the bubbaloop PR is merged.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
feat(nodes): publisher queryable mirror + node.yaml commands interface
- load_config(): YAML validation, topic regex, confidence bounds
- build_payload(): timestamped JSON detection output
- H264Decoder: GStreamer appsrc→h264parse→avdec_h264→RGB appsink pipeline
- Detector: RFDETRBase CPU inference, COCO class name mapping
- RfDetrDetectorNode: subscribes H264 frames, decodes, infers, publishes
- 6 unit tests covering config validation and payload builder
- Add pixi 'test' task with GI_TYPELIB_PATH env

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…confidence)

RF-DETR returns supervision.Detections which uses xyxy, class_id, confidence
instead of boxes, labels, scores.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…Surface + cupy

- H264DecoderCUDA: nvv4l2decoder (NVDEC HW decode) → nvvidconv NVBUF_MEM_CUDA_DEVICE
  → ctypes NvBufSurface struct reads surfaceList[0].dataPtr (CUDA device ptr)
  → cupy UnownedMemory wraps ptr (no alloc) → RGBA→RGB CHW float32 on GPU
  → torch.from_dlpack() zero-copy hand-off to torch
- Detector: accepts torch.Tensor (CHW float [0,1]) or np.ndarray; device=cuda|cpu
- RfDetrDetectorNode: auto-selects CUDA path when torch.cuda.is_available()
- pixi.toml: torch from pytorch whl/cu126, add cupy-cuda12x

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…PU Jetson

cupy._check_peer_access probes deviceCanAccessPeer(0,1) which fails when only
one CUDA device exists. Setting cp.cuda.Device(0) prevents the probe.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Without device_id, cupy probes deviceCanAccessPeer for all device pairs,
which fails on single-GPU Jetson Orin (no device 1 exists).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…U bridge

Jetson Orin iGPU uses SURFACE_ARRAY NVMM buffers (memType=4), not linear
CUDA_DEVICE memory. dataPtr is invalid for SURFACE_ARRAY; real zero-copy
requires EGL+CUDA inter-op via DMA-BUF fd (bufferDesc field).

Practical GPU path: nvv4l2decoder (NVDEC hardware decode, no CPU decode
cycles) → nvvidconv CPU RGB → single H2D transfer → RF-DETR on CUDA.

Removes unused ctypes NvBufSurface structs and cupy dependency.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
edgarriba and others added 5 commits April 4, 2026 14:22
nvvidconv outputs RGBA (4 bytes/pixel), but _on_new_sample was
reshaping to (H, W, 3) causing incorrect tensor data. Fix to reshape
as (H, W, 4) then slice [:,:,:3] to drop alpha before H2D copy.

Also update log message: remove "zero-copy" (we do a CPU bridge via
nvvidconv due to Jetson Orin SURFACE_ARRAY NVMM not supporting direct
CUDA ptr access).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The pytorch.org cu126 desktop wheels only include SM80/SM86/SM90
kernels — Jetson Orin's iGPU is SM87 which is missing. Switch to
pypi.jetson-ai-lab.io/jp6/cu126 which provides Jetson-specific builds.

Pin torch==2.8.0 (cp310 aarch64): the only version on this index that
doesn't require libcudss.so (added in 2.9.x). Also pin Python <3.11
since Jetson wheels are built for cp310 only.

Drop cupy-cuda12x — zero-copy NVMM path was abandoned; nvvidconv
gives CPU RGBA which we transfer to CUDA with a single H2D copy.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
torchvision 0.23.0 is the version compatible with torch 2.8.0.
Newer torchvision registers torchvision::nms before torch 2.9 adds
the operator, causing a RuntimeError on import.

numpy<2.0 avoids the ABI mismatch warning: torch 2.8.0 was compiled
against numpy 1.x (numpy 2.x has an incompatible C API).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Prevents ~/.local/lib/python3.10/site-packages from overriding pixi env.
Without this, user-installed numpy 2.x and huggingface-hub 1.3.x shadow
the pixi-managed versions, causing torch RuntimeError and import failures.

Also adds huggingface-hub>=1.5.0,<2.0 constraint (required by transformers
version that rfdetr pulls in).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… unique health topic

- Add model field (nano/small/base/medium/large) — defaults to base, tapo_terrace uses large
- Add target_fps field — inference runs on a dedicated thread at the configured rate;
  H264 decoder runs continuously and stores only the latest frame so inference always
  sees the most recent image rather than a stale buffered one
- Replace queue.Queue with lock+slot in both decoders (CPU and CUDA) to eliminate
  the oldest-frame-first problem when inference rate < decode rate
- Call optimize_for_inference() on model load to JIT-trace for lower latency
- Fix instance name collision: configs/tapo_terrace.yaml uses name: rf_detr_tapo_terrace
  so health topic is distinct from the camera node's tapo_terrace/health
- Fix publish_topic: camera/tapo_terrace/detections mirrors the camera namespace
- Lower default confidence_threshold to 0.5 (was 0.8, which suppressed real detections)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@edgarriba edgarriba marked this pull request as draft April 5, 2026 08:44
edgarriba and others added 8 commits April 5, 2026 11:09
…ETR predict()

RF-DETR predict() accepts CHW float32 [0,1] tensors natively. Replace
PIL.Image.fromarray() in the CPU path with a direct numpy→tensor conversion,
matching the CUDA path which already produced a CHW float32 CUDA tensor.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
PIL was only used to wrap numpy arrays before passing to RF-DETR.
Now we convert numpy→torch tensor directly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…A-only

- Remove H264Decoder (avdec_h264 CPU path) — dead code on Jetson; RF-DETR
  on CPU is impractically slow anyway
- Remove device parameter from Detector — hardcoded cuda, one less indirection
- Remove numpy isinstance check in detect() — H264DecoderCUDA always yields
  a torch.Tensor; the numpy path was only needed for the removed CPU decoder
- Flatten RfDetrDetectorNode.__init__ — no more cuda_ok conditional, decoder
  and detector instantiated directly
- 427 → 324 lines (-24%)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sleep the *remainder* of the interval after inference completes instead of
advancing next_run before inference. This prevents catch-up bursts when
inference is slow (e.g. 5.7s CUDA warm-up on first call caused seq 1-8 to
all fire within 1 second). Output is now exactly target_fps regardless of
inference duration, flooring at inference speed if inference > interval.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…o_entrance_detector

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Directory renamed from rf-detr-detector/ to rfdetr-detector/
- Config name field: tapo_entrance_detector → tapo_terrace_detector
  (was wrongly named entrance while processing the terrace camera)
- Daemon registry and systemd service updated to tapo-terrace-detector

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace 'sleep remainder after inference' with a next_run deadline.
Without this, CUDA JIT warmup (~6s) and empty-frame polls had no memory
of when the last inference fired, causing burst output at startup.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@edgarriba edgarriba force-pushed the feat/rf-detr-detector branch from 3e21f76 to eb0f35c Compare April 6, 2026 07:07
@edgarriba
Copy link
Copy Markdown
Member Author

Superseded by #15 (YOLO11) and #17 (renamed to camera-object-detector)

@edgarriba edgarriba closed this Apr 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant