Initial commit: RealSense D435 API server

Drop-in replacement for the Nuwa-HP60C camera container, exposing RGB +
aligned depth + intrinsics via REST/WebSocket so the existing
pestello-pose-detector pipeline can consume frames without changes.

Key adaptations for D4xx on USB 2.0 (this robot has no USB 3 port exposed
externally — see STATUS.md):
- pyrealsense2 built from source; the librealsense v2.55.1 install rule
  doesn't ship the main .so, so we copy it from the build dir and add an
  __init__.py that re-exports the submodule.
- Color stream uses YUYV format on USB 2.x (BGR8/RGB8 time out on this
  bus); converted to BGR via cv2.cvtColor in capture().
- rs.align is skipped on USB 2 (incompatible with YUYV); color is
  cv2-resized to depth shape for approximate per-pixel alignment.
- Driver auto-detects usb_type_descriptor and falls back to the
  full-resolution BGR8 + rs.align pipeline when on USB 3.

Tested: /api/{health,status,discover,rgb,depth,depth/color,capture,
intrinsics} all return 200 with valid payloads at 640x480.
This commit is contained in:
robot committed 2026-05-19 00:58:23 +02:00
commit 07d95792e3
58 files changed
+6987

No files matched your search

+117
View File
@@ -0,0 +1,117 @@
# Multi-stage build for the RealSense D4xx API server on aarch64 (Jetson)
# and x86_64. The Intel apt repo ships librealsense2 runtime for arm64
# but no pyrealsense2 wheel, so the Python bindings are built from source
# in a dedicated builder stage and copied into the runtime image.
# ---------------------------------------------------------------------------
# Stage 1: build pyrealsense2 (Python bindings) from source
# ---------------------------------------------------------------------------
FROM ubuntu:22.04 AS librealsense-builder
ENV DEBIAN_FRONTEND=noninteractive
ARG RS_VERSION=v2.55.1
RUN apt-get update && apt-get install -y --no-install-recommends \
git cmake build-essential pkg-config ca-certificates \
libssl-dev libusb-1.0-0-dev libudev-dev \
python3 python3-dev python3-pip \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /src
RUN git clone --depth 1 --branch ${RS_VERSION} https://github.com/IntelRealSense/librealsense.git
WORKDIR /src/librealsense/build
RUN cmake .. \
-DCMAKE_BUILD_TYPE=Release \
-DBUILD_EXAMPLES=OFF \
-DBUILD_GRAPHICAL_EXAMPLES=OFF \
-DBUILD_TOOLS=OFF \
-DBUILD_UNIT_TESTS=OFF \
-DBUILD_NETWORK_DEVICE=OFF \
-DBUILD_PYTHON_BINDINGS=ON \
-DPYTHON_EXECUTABLE=/usr/bin/python3 \
-DCMAKE_INSTALL_PREFIX=/opt/install \
&& make -j$(nproc) \
&& make install
# librealsense@v2.55.1 has a buggy install rule that doesn't install the
# main pyrealsense2.so — only pyrsutils.so. We pick the main .so straight
# from the build dir to work around it.
RUN mkdir -p /opt/pyrealsense2 \
&& cp -r /usr/lib/python3/dist-packages/pyrealsense2/. /opt/pyrealsense2/ \
&& cp /src/librealsense/build/Release/pyrealsense2.cpython-310-aarch64-linux-gnu.so /opt/pyrealsense2/ \
&& ls /opt/pyrealsense2/pyrealsense2.cpython-310-aarch64-linux-gnu.so > /dev/null \
&& printf 'from .pyrealsense2 import *\n' > /opt/pyrealsense2/__init__.py \
&& echo "=== /opt/pyrealsense2 contents ===" \
&& ls -la /opt/pyrealsense2
# ---------------------------------------------------------------------------
# Stage 2: build React frontend
# ---------------------------------------------------------------------------
FROM node:20-alpine AS frontend-build
WORKDIR /app
COPY src/frontend/react/package*.json ./
RUN npm ci
COPY src/frontend/react/ .
RUN npm run build
# ---------------------------------------------------------------------------
# Stage 3: runtime image
# ---------------------------------------------------------------------------
FROM ubuntu:22.04 AS runtime
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates curl gnupg \
python3 python3-pip python3-venv \
libusb-1.0-0 libudev1 \
libgl1 libglib2.0-0 libsm6 libxext6 libxrender1 \
usbutils \
&& rm -rf /var/lib/apt/lists/*
# Intel RealSense apt repo for the librealsense2 native runtime (pinned to v2.55.1).
# Intel rotated the signing key in 2025: the legacy librealsense.pgp file on
# their server is the old 2018 key (C8B3A55A...), but the current `jammy`
# Packages index is signed by the new key FB0B24895113F120. We fetch the new
# key from Ubuntu's keyserver and dearmor it into the trusted keyring.
RUN mkdir -p /etc/apt/keyrings \
&& curl -fsSL "https://keyserver.ubuntu.com/pks/lookup?op=get&search=0xFB0B24895113F120" \
| gpg --dearmor -o /etc/apt/keyrings/librealsense.gpg \
&& echo "deb [signed-by=/etc/apt/keyrings/librealsense.gpg] https://librealsense.intel.com/Debian/apt-repo jammy main" \
> /etc/apt/sources.list.d/librealsense.list \
&& apt-get update \
&& apt-get install -y --no-install-recommends \
librealsense2=2.55.1-0~realsense.3337 \
librealsense2-udev-rules=2.55.1-0~realsense.3337 \
&& rm -rf /var/lib/apt/lists/*
# Python bindings (built in stage 1). System Python 3.10 looks in
# /usr/lib/python3/dist-packages, so install there.
COPY --from=librealsense-builder /opt/pyrealsense2 /usr/lib/python3/dist-packages/pyrealsense2
WORKDIR /app
# Python dependencies — keep this layer cacheable: install before copying source.
RUN pip3 install --no-cache-dir \
"fastapi>=0.115" \
"uvicorn[standard]>=0.34" \
"pydantic>=2.0" \
"pydantic-settings>=2.0" \
"python-dotenv>=1.0" \
"nats-py>=2.9" \
"numpy>=1.24,<2" \
"opencv-python-headless>=4.8"
# Application source
COPY src/backend/ src/backend/
COPY --from=frontend-build /app/dist src/frontend/react/dist/
# Entrypoint
COPY scripts/entrypoint.sh /app/entrypoint.sh
RUN chmod +x /app/entrypoint.sh
# Optional .env (compose can also inject via env_file)
COPY .env* ./
EXPOSE 8878
ENTRYPOINT ["/app/entrypoint.sh"]