Skip to content

refactor(camera): use generated CDR values in Python producers - #4655

Draft
TomCC7 wants to merge 1 commit into
cc/feat/cdr-image-cloudfrom
cc/feat/cdr-camera-producers
Draft

TomCC7 wants to merge 1 commit into
cc/feat/cdr-image-cloudfrom
cc/feat/cdr-camera-producers

Conversation

@TomCC7

@TomCC7 TomCC7 commented Oct 20, 2026 •

Copy link
Copy Markdown
Member

Linear project: Message Encoding Refactor

Webcam, CameraModule and GstreamerCameraModule now produce the generated dimos.messages values. Webcam retains RGB conversion, stereo slicing and configured/nominal calibration. Camera metadata publishes generated CameraInfo and TFMessage values without mutating the configured mount. GStreamer copies padded BGR rows before unmapping and preserves source PTS plus offsets in integer nanoseconds.

Viewer blueprint construction now defers pubsub allocation until startup. Camera imports previously constructed an LCM socket even when the selected backend was Zenoh. Backend selection and explicit pubsub behavior remain the same. GStreamer stop also closes its Module resources if the pipeline was never started.

This is a dependent draft after #4654. Native V4L2/RealSense/ZED, downstream perception, robot blueprints and default transport cutover remain later slices. No camera, robot or TCP source was operated. No registry publication or new generator release.

Validation

At 7e2452385ddbf60b19cf9c979e17eefb0109dcf7:

  • 45 focused camera/viewer checks passed from source and against the installed main wheel outside checkout imports. Tests mock OpenCV/RPC/publish boundaries; actual generated values and real GStreamer buffers/pipelines are used.
  • GStreamer acceptance covers padded rows, SDK-buffer lifetime, exact source nanoseconds and ±1ns offsets, invalid source timestamps and shutdown before pipeline startup. These cases ran locally with PyGObject 3.58.1 and existing GStreamer libraries; CI skips them if that optional SDK is absent.
  • Strict typing passed seven production files. All applicable hooks passed.
  • Ordinary main wheel build passed, including automatic built-in message generation. The wheel SHA256 is 8dc489722116223003357f0dc6938ae07221c94493470eccd31919b70cd76c7c; installed pip check passed.
  • The exact documentation Python block ran with generated Image values. CI adds installed-wheel camera checks and scoped typing. CI was not polled; no terminal CI claim.

Try it

The camera-value walkthrough includes port contracts, a short pixel-reader example and offline test commands.

For a fresh source installation, use Python 3.12 and a C++ compiler for the existing main-package A* extension:

demo_dir="$(mktemp -d)"
uv venv --python 3.12 "$demo_dir/venv"
uv pip install --python "$demo_dir/venv/bin/python" \
  'dimos @ git+https://github.com/dimensionalOS/dimos.git@7e2452385ddbf60b19cf9c979e17eefb0109dcf7'
cd "$demo_dir"
"$demo_dir/venv/bin/python" -I - <<'PYTHON'
from dimos.hardware.sensors.camera.webcam import Webcam
from dimos.messages.sensor_msgs.msg import CameraInfo
info = Webcam(width=1280, height=720, stereo_slice="left").camera_info
assert type(info) is CameraInfo
assert (info.width, info.height) == (640, 720)
assert info.header.frame_id == "camera_optical"
print("generated calibration:", info.width, info.height, info.header.frame_id)
PYTHON

This constructs calibration only; it does not start camera capture. Automatic message-source preparation is part of the normal package build. The wheel was tested locally; the equivalent fresh Git install above was not repeated for this camera-only change.

File change summary

Parent-relative to #4654 at ad1a6d50c34431c7de182a0782a2077b21529460: 13 files, 410 additions, 78 deletions.

Category Files Added Deleted
Camera producers and typed specs 5 97 68
Deferred viewer initialization 2 3 3
Camera and viewer regression tests 4 246 7
Documentation and CI 2 64 0

No generated classes or upstream message snapshots are checked in. Native camera WIP is isolated from this PR.

@github-actions github-actions Bot left a comment

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.

go2 replay realtime (arm64)

Details
Benchmark suite Current: 7e24523 Previous: 17fdb6b Ratio
peak memory 1659.762 MB 1589.477 MB 1.04
peak threads 357 threads 350 threads 1.02
network (transport) 2721.811 MB 2730.079 MB 1.00
disk write 2.125 MB 1.707 MB 1.24
instructions 122.445 G 115.497 G 1.06

This comment was automatically generated by workflow using github-action-benchmark.

@codecov

codecov Bot commented Oct 20, 2026

Copy link
Copy Markdown

❌ 27 Tests Failed:

Tests completed Failed Passed Skipped
6515 27 6488 56
View the top 3 failed test(s) by shortest run time
dimos.web.relay_bridge.test_relay_bridge_module::test_relay_key_falls_back_to_global_config
Stack Traces | 0.004s run time
monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0xffa195efde50>

    def test_relay_key_falls_back_to_global_config(monkeypatch) -> None:
        # RELAY_KEY (env or .env) lands in GlobalConfig; the module reads it when
        # its own field is unset. The autouse fixture restores global_config.
        client = FakeClient()
    
        async def fake_connect(url: str, role: str, **kwargs: Any) -> FakeClient:
            return client
    
        patch_relay(monkeypatch, fake_connect)
        global_config.update(relay_key="global-key-0123456789abcdef")
        module = RelayBridgeModule(
            relay_url="http://127.0.0.1:7780", open_browser=False, robot_id="unit-bot"
        )
        try:
>           module.start()

client     = <dimos.web.relay_bridge.module_test_support.FakeClient object at 0xffa195efc6e0>
fake_connect = <function test_relay_key_falls_back_to_global_config.<locals>.fake_connect at 0xffa194e12480>
module     = <dimos.web.relay_bridge.relay_bridge_module.RelayBridgeModule object at 0xffa195efde80>
monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0xffa195efde50>

.../web/relay_bridge/test_relay_bridge_module.py:1047: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
dimos/core/module.py:211: in start
    self._start_main()
        self       = <dimos.web.relay_bridge.relay_bridge_module.RelayBridgeModule object at 0xffa195efde80>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <dimos.web.relay_bridge.relay_bridge_module.RelayBridgeModule object at 0xffa195efde80>

    def _start_main(self) -> None:
        """
        If the subclass defines `async def main(self)` as an async generator
        with exactly one `yield`, run everything before the `yield` as part of
        start().
        """
        main_fn = getattr(type(self), "main", None)
        if main_fn is None:
            return
        if not inspect.isasyncgenfunction(main_fn):
            raise TypeError(
                f"{type(self).__name__}.main must be an `async def` with exactly "
                "one `yield` (an async generator function)"
            )
        loop = self._loop
        if loop is None or not loop.is_running():
>           raise RuntimeError(f"{type(self).__name__}._loop is not running")
E           RuntimeError: RelayBridgeModule._loop is not running

loop       = <_UnixSelectorEventLoop running=False closed=False debug=False>
main_fn    = <function RelayBridgeModule.main at 0xffa25c4bdbc0>
self       = <dimos.web.relay_bridge.relay_bridge_module.RelayBridgeModule object at 0xffa195efde80>

dimos/core/module.py:655: RuntimeError
dimos.hardware.sensors.camera.test_webcam_backend::test_capture_backend[darwin-/dev/video0-/dev/video0-0]
Stack Traces | 0.006s run time
mocker = <pytest_mock.plugin.MockerFixture object at 0xff8f0e20b260>
request = <SubRequest 'webcam' for <Function test_capture_backend[.../video0-/dev/video0-0]>>

    @pytest.fixture
    def webcam(mocker, request):
>       camera = Webcam(camera_index=request.param)

mocker     = <pytest_mock.plugin.MockerFixture object at 0xff8f0e20b260>
request    = <SubRequest 'webcam' for <Function test_capture_backend[.../video0-/dev/video0-0]>>

.../sensors/camera/test_webcam_backend.py:23: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../sensors/camera/webcam.py:76: in __init__
    super().__init__(*args, **kwargs)
        __class__  = <class 'dimos.hardware.sensors.camera.webcam.Webcam'>
        args       = ()
        kwargs     = {'camera_index': '/dev/video0'}
        self       = <dimos.hardware.sensors.camera.webcam.Webcam object at 0xff8f0e20b7d0>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <dimos.hardware.sensors.camera.webcam.Webcam object at 0xff8f0e20b7d0>
kwargs = {'camera_index': '/dev/video0'}
config_type = <class 'dimos.hardware.sensors.camera.webcam.WebcamConfig'>

    def __init__(self, **kwargs: Any) -> None:
        config_type = get_type_hints(type(self))["config"]
>       self.config = config_type(**kwargs)
E       pydantic.errors.PydanticUserError: `WebcamConfig` is not fully defined; you should define `np`, then call `WebcamConfig.model_rebuild()`.
E       
E       For further information visit https://errors.pydantic.dev/2.12/u/class-not-fully-defined

config_type = <class 'dimos.hardware.sensors.camera.webcam.WebcamConfig'>
kwargs     = {'camera_index': '/dev/video0'}
self       = <dimos.hardware.sensors.camera.webcam.Webcam object at 0xff8f0e20b7d0>

.../protocol/service/spec.py:47: PydanticUserError
dimos.hardware.sensors.camera.test_webcam_backend::test_capture_backend[darwin-0-0-0]
Stack Traces | 0.006s run time
mocker = <pytest_mock.plugin.MockerFixture object at 0xff8f0ff83080>
request = <SubRequest 'webcam' for <Function test_capture_backend[darwin-0-0-0]>>

    @pytest.fixture
    def webcam(mocker, request):
>       camera = Webcam(camera_index=request.param)

mocker     = <pytest_mock.plugin.MockerFixture object at 0xff8f0ff83080>
request    = <SubRequest 'webcam' for <Function test_capture_backend[darwin-0-0-0]>>

.../sensors/camera/test_webcam_backend.py:23: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../sensors/camera/webcam.py:76: in __init__
    super().__init__(*args, **kwargs)
        __class__  = <class 'dimos.hardware.sensors.camera.webcam.Webcam'>
        args       = ()
        kwargs     = {'camera_index': 0}
        self       = <dimos.hardware.sensors.camera.webcam.Webcam object at 0xff8f0ff83dd0>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <dimos.hardware.sensors.camera.webcam.Webcam object at 0xff8f0ff83dd0>
kwargs = {'camera_index': 0}
config_type = <class 'dimos.hardware.sensors.camera.webcam.WebcamConfig'>

    def __init__(self, **kwargs: Any) -> None:
        config_type = get_type_hints(type(self))["config"]
>       self.config = config_type(**kwargs)
E       pydantic.errors.PydanticUserError: `WebcamConfig` is not fully defined; you should define `np`, then call `WebcamConfig.model_rebuild()`.
E       
E       For further information visit https://errors.pydantic.dev/2.12/u/class-not-fully-defined

config_type = <class 'dimos.hardware.sensors.camera.webcam.WebcamConfig'>
kwargs     = {'camera_index': 0}
self       = <dimos.hardware.sensors.camera.webcam.Webcam object at 0xff8f0ff83dd0>

.../protocol/service/spec.py:47: PydanticUserError
dimos.hardware.sensors.camera.test_webcam_backend::test_capture_backend[linux- -1 --1-0]
Stack Traces | 0.006s run time
mocker = <pytest_mock.plugin.MockerFixture object at 0xff8f0e388560>
request = <SubRequest 'webcam' for <Function test_capture_backend[linux- -1 --1-0]>>

    @pytest.fixture
    def webcam(mocker, request):
>       camera = Webcam(camera_index=request.param)

mocker     = <pytest_mock.plugin.MockerFixture object at 0xff8f0e388560>
request    = <SubRequest 'webcam' for <Function test_capture_backend[linux- -1 --1-0]>>

.../sensors/camera/test_webcam_backend.py:23: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../sensors/camera/webcam.py:76: in __init__
    super().__init__(*args, **kwargs)
        __class__  = <class 'dimos.hardware.sensors.camera.webcam.Webcam'>
        args       = ()
        kwargs     = {'camera_index': ' -1 '}
        self       = <dimos.hardware.sensors.camera.webcam.Webcam object at 0xff8f0e38b890>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <dimos.hardware.sensors.camera.webcam.Webcam object at 0xff8f0e38b890>
kwargs = {'camera_index': ' -1 '}
config_type = <class 'dimos.hardware.sensors.camera.webcam.WebcamConfig'>

    def __init__(self, **kwargs: Any) -> None:
        config_type = get_type_hints(type(self))["config"]
>       self.config = config_type(**kwargs)
E       pydantic.errors.PydanticUserError: `WebcamConfig` is not fully defined; you should define `np`, then call `WebcamConfig.model_rebuild()`.
E       
E       For further information visit https://errors.pydantic.dev/2.12/u/class-not-fully-defined

config_type = <class 'dimos.hardware.sensors.camera.webcam.WebcamConfig'>
kwargs     = {'camera_index': ' -1 '}
self       = <dimos.hardware.sensors.camera.webcam.Webcam object at 0xff8f0e38b890>

.../protocol/service/spec.py:47: PydanticUserError
dimos.hardware.sensors.camera.test_webcam_backend::test_capture_backend[linux- 2 -2-0]
Stack Traces | 0.006s run time
mocker = <pytest_mock.plugin.MockerFixture object at 0xff8f0fff8560>
request = <SubRequest 'webcam' for <Function test_capture_backend[linux- 2 -2-0]>>

    @pytest.fixture
    def webcam(mocker, request):
>       camera = Webcam(camera_index=request.param)

mocker     = <pytest_mock.plugin.MockerFixture object at 0xff8f0fff8560>
request    = <SubRequest 'webcam' for <Function test_capture_backend[linux- 2 -2-0]>>

.../sensors/camera/test_webcam_backend.py:23: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../sensors/camera/webcam.py:76: in __init__
    super().__init__(*args, **kwargs)
        __class__  = <class 'dimos.hardware.sensors.camera.webcam.Webcam'>
        args       = ()
        kwargs     = {'camera_index': ' 2 '}
        self       = <dimos.hardware.sensors.camera.webcam.Webcam object at 0xff8f0fff8aa0>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <dimos.hardware.sensors.camera.webcam.Webcam object at 0xff8f0fff8aa0>
kwargs = {'camera_index': ' 2 '}
config_type = <class 'dimos.hardware.sensors.camera.webcam.WebcamConfig'>

    def __init__(self, **kwargs: Any) -> None:
        config_type = get_type_hints(type(self))["config"]
>       self.config = config_type(**kwargs)
E       pydantic.errors.PydanticUserError: `WebcamConfig` is not fully defined; you should define `np`, then call `WebcamConfig.model_rebuild()`.
E       
E       For further information visit https://errors.pydantic.dev/2.12/u/class-not-fully-defined

config_type = <class 'dimos.hardware.sensors.camera.webcam.WebcamConfig'>
kwargs     = {'camera_index': ' 2 '}
self       = <dimos.hardware.sensors.camera.webcam.Webcam object at 0xff8f0fff8aa0>

.../protocol/service/spec.py:47: PydanticUserError
dimos.hardware.sensors.camera.test_webcam_backend::test_capture_backend[linux-+2-2-0]
Stack Traces | 0.006s run time
mocker = <pytest_mock.plugin.MockerFixture object at 0xff8f0fff9430>
request = <SubRequest 'webcam' for <Function test_capture_backend[linux-+2-2-0]>>

    @pytest.fixture
    def webcam(mocker, request):
>       camera = Webcam(camera_index=request.param)

mocker     = <pytest_mock.plugin.MockerFixture object at 0xff8f0fff9430>
request    = <SubRequest 'webcam' for <Function test_capture_backend[linux-+2-2-0]>>

.../sensors/camera/test_webcam_backend.py:23: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../sensors/camera/webcam.py:76: in __init__
    super().__init__(*args, **kwargs)
        __class__  = <class 'dimos.hardware.sensors.camera.webcam.Webcam'>
        args       = ()
        kwargs     = {'camera_index': '+2'}
        self       = <dimos.hardware.sensors.camera.webcam.Webcam object at 0xff8f0fff8350>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <dimos.hardware.sensors.camera.webcam.Webcam object at 0xff8f0fff8350>
kwargs = {'camera_index': '+2'}
config_type = <class 'dimos.hardware.sensors.camera.webcam.WebcamConfig'>

    def __init__(self, **kwargs: Any) -> None:
        config_type = get_type_hints(type(self))["config"]
>       self.config = config_type(**kwargs)
E       pydantic.errors.PydanticUserError: `WebcamConfig` is not fully defined; you should define `np`, then call `WebcamConfig.model_rebuild()`.
E       
E       For further information visit https://errors.pydantic.dev/2.12/u/class-not-fully-defined

config_type = <class 'dimos.hardware.sensors.camera.webcam.WebcamConfig'>
kwargs     = {'camera_index': '+2'}
self       = <dimos.hardware.sensors.camera.webcam.Webcam object at 0xff8f0fff8350>

.../protocol/service/spec.py:47: PydanticUserError
dimos.hardware.sensors.camera.test_webcam_backend::test_capture_backend[linux--1--1-0]
Stack Traces | 0.006s run time
mocker = <pytest_mock.plugin.MockerFixture object at 0xff8f0e26db20>
request = <SubRequest 'webcam' for <Function test_capture_backend[linux--1--1-0]>>

    @pytest.fixture
    def webcam(mocker, request):
>       camera = Webcam(camera_index=request.param)

mocker     = <pytest_mock.plugin.MockerFixture object at 0xff8f0e26db20>
request    = <SubRequest 'webcam' for <Function test_capture_backend[linux--1--1-0]>>

.../sensors/camera/test_webcam_backend.py:23: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../sensors/camera/webcam.py:76: in __init__
    super().__init__(*args, **kwargs)
        __class__  = <class 'dimos.hardware.sensors.camera.webcam.Webcam'>
        args       = ()
        kwargs     = {'camera_index': '-1'}
        self       = <dimos.hardware.sensors.camera.webcam.Webcam object at 0xff8f0e1c0b60>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <dimos.hardware.sensors.camera.webcam.Webcam object at 0xff8f0e1c0b60>
kwargs = {'camera_index': '-1'}
config_type = <class 'dimos.hardware.sensors.camera.webcam.WebcamConfig'>

    def __init__(self, **kwargs: Any) -> None:
        config_type = get_type_hints(type(self))["config"]
>       self.config = config_type(**kwargs)
E       pydantic.errors.PydanticUserError: `WebcamConfig` is not fully defined; you should define `np`, then call `WebcamConfig.model_rebuild()`.
E       
E       For further information visit https://errors.pydantic.dev/2.12/u/class-not-fully-defined

config_type = <class 'dimos.hardware.sensors.camera.webcam.WebcamConfig'>
kwargs     = {'camera_index': '-1'}
self       = <dimos.hardware.sensors.camera.webcam.Webcam object at 0xff8f0e1c0b60>

.../protocol/service/spec.py:47: PydanticUserError
dimos.hardware.sensors.camera.test_webcam_backend::test_capture_backend[linux--2--2-0]
Stack Traces | 0.006s run time
mocker = <pytest_mock.plugin.MockerFixture object at 0xff8f0fffc800>
request = <SubRequest 'webcam' for <Function test_capture_backend[linux--2--2-0]>>

    @pytest.fixture
    def webcam(mocker, request):
>       camera = Webcam(camera_index=request.param)

mocker     = <pytest_mock.plugin.MockerFixture object at 0xff8f0fffc800>
request    = <SubRequest 'webcam' for <Function test_capture_backend[linux--2--2-0]>>

.../sensors/camera/test_webcam_backend.py:23: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../sensors/camera/webcam.py:76: in __init__
    super().__init__(*args, **kwargs)
        __class__  = <class 'dimos.hardware.sensors.camera.webcam.Webcam'>
        args       = ()
        kwargs     = {'camera_index': '-2'}
        self       = <dimos.hardware.sensors.camera.webcam.Webcam object at 0xff8f0fffcad0>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <dimos.hardware.sensors.camera.webcam.Webcam object at 0xff8f0fffcad0>
kwargs = {'camera_index': '-2'}
config_type = <class 'dimos.hardware.sensors.camera.webcam.WebcamConfig'>

    def __init__(self, **kwargs: Any) -> None:
        config_type = get_type_hints(type(self))["config"]
>       self.config = config_type(**kwargs)
E       pydantic.errors.PydanticUserError: `WebcamConfig` is not fully defined; you should define `np`, then call `WebcamConfig.model_rebuild()`.
E       
E       For further information visit https://errors.pydantic.dev/2.12/u/class-not-fully-defined

config_type = <class 'dimos.hardware.sensors.camera.webcam.WebcamConfig'>
kwargs     = {'camera_index': '-2'}
self       = <dimos.hardware.sensors.camera.webcam.Webcam object at 0xff8f0fffcad0>

.../protocol/service/spec.py:47: PydanticUserError
dimos.hardware.sensors.camera.test_webcam_backend::test_capture_backend[linux-/dev/v4l/by-id/usb-camera-/dev/v4l/by-id/usb-camera-200]
Stack Traces | 0.006s run time
mocker = <pytest_mock.plugin.MockerFixture object at 0xff8f0fe2a150>
request = <SubRequest 'webcam' for <Function test_capture_backend[.../v4l/by-id/usb-camera-200]>>

    @pytest.fixture
    def webcam(mocker, request):
>       camera = Webcam(camera_index=request.param)

mocker     = <pytest_mock.plugin.MockerFixture object at 0xff8f0fe2a150>
request    = <SubRequest 'webcam' for <Function test_capture_backend[.../v4l/by-id/usb-camera-200]>>

.../sensors/camera/test_webcam_backend.py:23: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../sensors/camera/webcam.py:76: in __init__
    super().__init__(*args, **kwargs)
        __class__  = <class 'dimos.hardware.sensors.camera.webcam.Webcam'>
        args       = ()
        kwargs     = {'camera_index': '.../v4l/by-id/usb-camera'}
        self       = <dimos.hardware.sensors.camera.webcam.Webcam object at 0xff8f0fe2a0f0>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <dimos.hardware.sensors.camera.webcam.Webcam object at 0xff8f0fe2a0f0>
kwargs = {'camera_index': '.../v4l/by-id/usb-camera'}
config_type = <class 'dimos.hardware.sensors.camera.webcam.WebcamConfig'>

    def __init__(self, **kwargs: Any) -> None:
        config_type = get_type_hints(type(self))["config"]
>       self.config = config_type(**kwargs)
E       pydantic.errors.PydanticUserError: `WebcamConfig` is not fully defined; you should define `np`, then call `WebcamConfig.model_rebuild()`.
E       
E       For further information visit https://errors.pydantic.dev/2.12/u/class-not-fully-defined

config_type = <class 'dimos.hardware.sensors.camera.webcam.WebcamConfig'>
kwargs     = {'camera_index': '.../v4l/by-id/usb-camera'}
self       = <dimos.hardware.sensors.camera.webcam.Webcam object at 0xff8f0fe2a0f0>

.../protocol/service/spec.py:47: PydanticUserError
dimos.hardware.sensors.camera.test_webcam_backend::test_capture_backend[linux-/dev/video0-/dev/video0-200]
Stack Traces | 0.006s run time
mocker = <pytest_mock.plugin.MockerFixture object at 0xff8f0fe28980>
request = <SubRequest 'webcam' for <Function test_capture_backend[.../video0-/dev/video0-200]>>

    @pytest.fixture
    def webcam(mocker, request):
>       camera = Webcam(camera_index=request.param)

mocker     = <pytest_mock.plugin.MockerFixture object at 0xff8f0fe28980>
request    = <SubRequest 'webcam' for <Function test_capture_backend[.../video0-/dev/video0-200]>>

.../sensors/camera/test_webcam_backend.py:23: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../sensors/camera/webcam.py:76: in __init__
    super().__init__(*args, **kwargs)
        __class__  = <class 'dimos.hardware.sensors.camera.webcam.Webcam'>
        args       = ()
        kwargs     = {'camera_index': '/dev/video0'}
        self       = <dimos.hardware.sensors.camera.webcam.Webcam object at 0xff8f0fe2b020>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <dimos.hardware.sensors.camera.webcam.Webcam object at 0xff8f0fe2b020>
kwargs = {'camera_index': '/dev/video0'}
config_type = <class 'dimos.hardware.sensors.camera.webcam.WebcamConfig'>

    def __init__(self, **kwargs: Any) -> None:
        config_type = get_type_hints(type(self))["config"]
>       self.config = config_type(**kwargs)
E       pydantic.errors.PydanticUserError: `WebcamConfig` is not fully defined; you should define `np`, then call `WebcamConfig.model_rebuild()`.
E       
E       For further information visit https://errors.pydantic.dev/2.12/u/class-not-fully-defined

config_type = <class 'dimos.hardware.sensors.camera.webcam.WebcamConfig'>
kwargs     = {'camera_index': '/dev/video0'}
self       = <dimos.hardware.sensors.camera.webcam.Webcam object at 0xff8f0fe2b020>

.../protocol/service/spec.py:47: PydanticUserError
dimos.hardware.sensors.camera.test_webcam_backend::test_capture_backend[linux-2-2-0_0]
Stack Traces | 0.006s run time
mocker = <pytest_mock.plugin.MockerFixture object at 0xff8f0fe30a10>
request = <SubRequest 'webcam' for <Function test_capture_backend[linux-2-2-0_0]>>

    @pytest.fixture
    def webcam(mocker, request):
>       camera = Webcam(camera_index=request.param)

mocker     = <pytest_mock.plugin.MockerFixture object at 0xff8f0fe30a10>
request    = <SubRequest 'webcam' for <Function test_capture_backend[linux-2-2-0_0]>>

.../sensors/camera/test_webcam_backend.py:23: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../sensors/camera/webcam.py:76: in __init__
    super().__init__(*args, **kwargs)
        __class__  = <class 'dimos.hardware.sensors.camera.webcam.Webcam'>
        args       = ()
        kwargs     = {'camera_index': 2}
        self       = <dimos.hardware.sensors.camera.webcam.Webcam object at 0xff8f0fe30c50>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <dimos.hardware.sensors.camera.webcam.Webcam object at 0xff8f0fe30c50>
kwargs = {'camera_index': 2}
config_type = <class 'dimos.hardware.sensors.camera.webcam.WebcamConfig'>

    def __init__(self, **kwargs: Any) -> None:
        config_type = get_type_hints(type(self))["config"]
>       self.config = config_type(**kwargs)
E       pydantic.errors.PydanticUserError: `WebcamConfig` is not fully defined; you should define `np`, then call `WebcamConfig.model_rebuild()`.
E       
E       For further information visit https://errors.pydantic.dev/2.12/u/class-not-fully-defined

config_type = <class 'dimos.hardware.sensors.camera.webcam.WebcamConfig'>
kwargs     = {'camera_index': 2}
self       = <dimos.hardware.sensors.camera.webcam.Webcam object at 0xff8f0fe30c50>

.../protocol/service/spec.py:47: PydanticUserError
dimos.hardware.sensors.camera.test_webcam_backend::test_capture_backend[linux-2-2-0_1]
Stack Traces | 0.006s run time
mocker = <pytest_mock.plugin.MockerFixture object at 0xff8f0e1c21e0>
request = <SubRequest 'webcam' for <Function test_capture_backend[linux-2-2-0_1]>>

    @pytest.fixture
    def webcam(mocker, request):
>       camera = Webcam(camera_index=request.param)

mocker     = <pytest_mock.plugin.MockerFixture object at 0xff8f0e1c21e0>
request    = <SubRequest 'webcam' for <Function test_capture_backend[linux-2-2-0_1]>>

.../sensors/camera/test_webcam_backend.py:23: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../sensors/camera/webcam.py:76: in __init__
    super().__init__(*args, **kwargs)
        __class__  = <class 'dimos.hardware.sensors.camera.webcam.Webcam'>
        args       = ()
        kwargs     = {'camera_index': '2'}
        self       = <dimos.hardware.sensors.camera.webcam.Webcam object at 0xff8f0e1c0470>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <dimos.hardware.sensors.camera.webcam.Webcam object at 0xff8f0e1c0470>
kwargs = {'camera_index': '2'}
config_type = <class 'dimos.hardware.sensors.camera.webcam.WebcamConfig'>

    def __init__(self, **kwargs: Any) -> None:
        config_type = get_type_hints(type(self))["config"]
>       self.config = config_type(**kwargs)
E       pydantic.errors.PydanticUserError: `WebcamConfig` is not fully defined; you should define `np`, then call `WebcamConfig.model_rebuild()`.
E       
E       For further information visit https://errors.pydantic.dev/2.12/u/class-not-fully-defined

config_type = <class 'dimos.hardware.sensors.camera.webcam.WebcamConfig'>
kwargs     = {'camera_index': '2'}
self       = <dimos.hardware.sensors.camera.webcam.Webcam object at 0xff8f0e1c0470>

.../protocol/service/spec.py:47: PydanticUserError
dimos.hardware.sensors.camera.test_webcam_backend::test_capture_backend[linux-rtsp://camera/stream-rtsp://camera/stream-0]
Stack Traces | 0.006s run time
mocker = <pytest_mock.plugin.MockerFixture object at 0xff8f0ffe7470>
request = <SubRequest 'webcam' for <Function test_capture_backend[linux-rtsp:.../camera/stream-rtsp:.../camera/stream-0]>>

    @pytest.fixture
    def webcam(mocker, request):
>       camera = Webcam(camera_index=request.param)

mocker     = <pytest_mock.plugin.MockerFixture object at 0xff8f0ffe7470>
request    = <SubRequest 'webcam' for <Function test_capture_backend[linux-rtsp:.../camera/stream-rtsp:.../camera/stream-0]>>

.../sensors/camera/test_webcam_backend.py:23: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../sensors/camera/webcam.py:76: in __init__
    super().__init__(*args, **kwargs)
        __class__  = <class 'dimos.hardware.sensors.camera.webcam.Webcam'>
        args       = ()
        kwargs     = {'camera_index': 'rtsp:.../camera/stream'}
        self       = <dimos.hardware.sensors.camera.webcam.Webcam object at 0xff8f0ffe6120>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <dimos.hardware.sensors.camera.webcam.Webcam object at 0xff8f0ffe6120>
kwargs = {'camera_index': 'rtsp:.../camera/stream'}
config_type = <class 'dimos.hardware.sensors.camera.webcam.WebcamConfig'>

    def __init__(self, **kwargs: Any) -> None:
        config_type = get_type_hints(type(self))["config"]
>       self.config = config_type(**kwargs)
E       pydantic.errors.PydanticUserError: `WebcamConfig` is not fully defined; you should define `np`, then call `WebcamConfig.model_rebuild()`.
E       
E       For further information visit https://errors.pydantic.dev/2.12/u/class-not-fully-defined

config_type = <class 'dimos.hardware.sensors.camera.webcam.WebcamConfig'>
kwargs     = {'camera_index': 'rtsp:.../camera/stream'}
self       = <dimos.hardware.sensors.camera.webcam.Webcam object at 0xff8f0ffe6120>

.../protocol/service/spec.py:47: PydanticUserError
dimos.hardware.sensors.camera.test_webcam::test_webcam_keeps_configured_intrinsics
Stack Traces | 0.009s run time
def test_webcam_keeps_configured_intrinsics() -> None:
        configured = camera_info_from_fov(
            90.0, 640, 480, header=Header(stamp=Time(sec=0, nanosec=0), frame_id="")
        )
>       assert Webcam(camera_info=configured).camera_info is configured

configured = sensor_msgs__msg__CameraInfo(header=std_msgs__msg__Header(stamp=builtin_interfaces__msg__Time(sec=0, nanosec=0, __msgt..., width=0, do_rectify=False, __msgtype__='sensor_msgs/msg/RegionOfInterest'), __msgtype__='sensor_msgs/msg/CameraInfo')

.../sensors/camera/test_webcam.py:38: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../sensors/camera/webcam.py:76: in __init__
    super().__init__(*args, **kwargs)
        __class__  = <class 'dimos.hardware.sensors.camera.webcam.Webcam'>
        args       = ()
        kwargs     = {'camera_info': sensor_msgs__msg__CameraInfo(header=std_msgs__msg__Header(stamp=builtin_interfaces__msg__Time(sec=0, n... width=0, do_rectify=False, __msgtype__='sensor_msgs/msg/RegionOfInterest'), __msgtype__='sensor_msgs/msg/CameraInfo')}
        self       = <dimos.hardware.sensors.camera.webcam.Webcam object at 0xffe598a8e180>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <dimos.hardware.sensors.camera.webcam.Webcam object at 0xffe598a8e180>
kwargs = {'camera_info': sensor_msgs__msg__CameraInfo(header=std_msgs__msg__Header(stamp=builtin_interfaces__msg__Time(sec=0, n... width=0, do_rectify=False, __msgtype__='sensor_msgs/msg/RegionOfInterest'), __msgtype__='sensor_msgs/msg/CameraInfo')}
config_type = <class 'dimos.hardware.sensors.camera.webcam.WebcamConfig'>

    def __init__(self, **kwargs: Any) -> None:
        config_type = get_type_hints(type(self))["config"]
>       self.config = config_type(**kwargs)
E       pydantic.errors.PydanticUserError: `WebcamConfig` is not fully defined; you should define `np`, then call `WebcamConfig.model_rebuild()`.
E       
E       For further information visit https://errors.pydantic.dev/2.12/u/class-not-fully-defined

config_type = <class 'dimos.hardware.sensors.camera.webcam.WebcamConfig'>
kwargs     = {'camera_info': sensor_msgs__msg__CameraInfo(header=std_msgs__msg__Header(stamp=builtin_interfaces__msg__Time(sec=0, n... width=0, do_rectify=False, __msgtype__='sensor_msgs/msg/RegionOfInterest'), __msgtype__='sensor_msgs/msg/CameraInfo')}
self       = <dimos.hardware.sensors.camera.webcam.Webcam object at 0xffe598a8e180>

.../protocol/service/spec.py:47: PydanticUserError
dimos.hardware.sensors.camera.test_webcam::test_webcam_with_size_but_no_focal_length_gets_a_nominal_pinhole
Stack Traces | 0.009s run time
def test_webcam_with_size_but_no_focal_length_gets_a_nominal_pinhole() -> None:
>       info = Webcam(
            camera_info=CameraInfo(
                width=1280,
                height=720,
                header=Header(stamp=Time(sec=0, nanosec=0), frame_id=""),
                distortion_model="",
                d=np.array([], dtype=np.float64),
                k=np.zeros(9, dtype=np.float64),
                r=np.zeros(9, dtype=np.float64),
                p=np.zeros(12, dtype=np.float64),
                binning_x=0,
                binning_y=0,
                roi=RegionOfInterest(x_offset=0, y_offset=0, height=0, width=0, do_rectify=False),
            )
        ).camera_info


.../sensors/camera/test_webcam.py:42: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../sensors/camera/webcam.py:76: in __init__
    super().__init__(*args, **kwargs)
        __class__  = <class 'dimos.hardware.sensors.camera.webcam.Webcam'>
        args       = ()
        kwargs     = {'camera_info': sensor_msgs__msg__CameraInfo(header=std_msgs__msg__Header(stamp=builtin_interfaces__msg__Time(sec=0, n... width=0, do_rectify=False, __msgtype__='sensor_msgs/msg/RegionOfInterest'), __msgtype__='sensor_msgs/msg/CameraInfo')}
        self       = <dimos.hardware.sensors.camera.webcam.Webcam object at 0xffe598a8c5f0>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <dimos.hardware.sensors.camera.webcam.Webcam object at 0xffe598a8c5f0>
kwargs = {'camera_info': sensor_msgs__msg__CameraInfo(header=std_msgs__msg__Header(stamp=builtin_interfaces__msg__Time(sec=0, n... width=0, do_rectify=False, __msgtype__='sensor_msgs/msg/RegionOfInterest'), __msgtype__='sensor_msgs/msg/CameraInfo')}
config_type = <class 'dimos.hardware.sensors.camera.webcam.WebcamConfig'>

    def __init__(self, **kwargs: Any) -> None:
        config_type = get_type_hints(type(self))["config"]
>       self.config = config_type(**kwargs)
E       pydantic.errors.PydanticUserError: `WebcamConfig` is not fully defined; you should define `np`, then call `WebcamConfig.model_rebuild()`.
E       
E       For further information visit https://errors.pydantic.dev/2.12/u/class-not-fully-defined

config_type = <class 'dimos.hardware.sensors.camera.webcam.WebcamConfig'>
kwargs     = {'camera_info': sensor_msgs__msg__CameraInfo(header=std_msgs__msg__Header(stamp=builtin_interfaces__msg__Time(sec=0, n... width=0, do_rectify=False, __msgtype__='sensor_msgs/msg/RegionOfInterest'), __msgtype__='sensor_msgs/msg/CameraInfo')}
self       = <dimos.hardware.sensors.camera.webcam.Webcam object at 0xffe598a8c5f0>

.../protocol/service/spec.py:47: PydanticUserError
dimos.hardware.sensors.camera.test_webcam::test_capture_converts_bgr_and_crops_before_cdr_encoding[right-2]
Stack Traces | 0.01s run time
side = 'right', start = 2
mocker = <pytest_mock.plugin.MockerFixture object at 0xffe598a94b60>

    @pytest.mark.parametrize("side,start", [("left", 0), ("right", 2)])
    def test_capture_converts_bgr_and_crops_before_cdr_encoding(side, start, mocker):
>       camera = Webcam(stereo_slice=side, frame_id_prefix="robot")

mocker     = <pytest_mock.plugin.MockerFixture object at 0xffe598a94b60>
side       = 'right'
start      = 2

.../sensors/camera/test_webcam.py:67: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../sensors/camera/webcam.py:76: in __init__
    super().__init__(*args, **kwargs)
        __class__  = <class 'dimos.hardware.sensors.camera.webcam.Webcam'>
        args       = ()
        kwargs     = {'frame_id_prefix': 'robot', 'stereo_slice': 'right'}
        self       = <dimos.hardware.sensors.camera.webcam.Webcam object at 0xffe598a94ce0>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <dimos.hardware.sensors.camera.webcam.Webcam object at 0xffe598a94ce0>
kwargs = {'frame_id_prefix': 'robot', 'stereo_slice': 'right'}
config_type = <class 'dimos.hardware.sensors.camera.webcam.WebcamConfig'>

    def __init__(self, **kwargs: Any) -> None:
        config_type = get_type_hints(type(self))["config"]
>       self.config = config_type(**kwargs)
E       pydantic.errors.PydanticUserError: `WebcamConfig` is not fully defined; you should define `np`, then call `WebcamConfig.model_rebuild()`.
E       
E       For further information visit https://errors.pydantic.dev/2.12/u/class-not-fully-defined

config_type = <class 'dimos.hardware.sensors.camera.webcam.WebcamConfig'>
kwargs     = {'frame_id_prefix': 'robot', 'stereo_slice': 'right'}
self       = <dimos.hardware.sensors.camera.webcam.Webcam object at 0xffe598a94ce0>

.../protocol/service/spec.py:47: PydanticUserError
dimos.hardware.sensors.camera.test_webcam::test_webcam_stereo_slice_halves_the_nominal_pinhole_width
Stack Traces | 0.01s run time
def test_webcam_stereo_slice_halves_the_nominal_pinhole_width() -> None:
>       info = Webcam(width=1280, height=720, stereo_slice="left").camera_info


.../sensors/camera/test_webcam.py:61: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../sensors/camera/webcam.py:76: in __init__
    super().__init__(*args, **kwargs)
        __class__  = <class 'dimos.hardware.sensors.camera.webcam.Webcam'>
        args       = ()
        kwargs     = {'height': 720, 'stereo_slice': 'left', 'width': 1280}
        self       = <dimos.hardware.sensors.camera.webcam.Webcam object at 0xffe5988a66c0>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <dimos.hardware.sensors.camera.webcam.Webcam object at 0xffe5988a66c0>
kwargs = {'height': 720, 'stereo_slice': 'left', 'width': 1280}
config_type = <class 'dimos.hardware.sensors.camera.webcam.WebcamConfig'>

    def __init__(self, **kwargs: Any) -> None:
        config_type = get_type_hints(type(self))["config"]
>       self.config = config_type(**kwargs)
E       pydantic.errors.PydanticUserError: `WebcamConfig` is not fully defined; you should define `np`, then call `WebcamConfig.model_rebuild()`.
E       
E       For further information visit https://errors.pydantic.dev/2.12/u/class-not-fully-defined

config_type = <class 'dimos.hardware.sensors.camera.webcam.WebcamConfig'>
kwargs     = {'height': 720, 'stereo_slice': 'left', 'width': 1280}
self       = <dimos.hardware.sensors.camera.webcam.Webcam object at 0xffe5988a66c0>

.../protocol/service/spec.py:47: PydanticUserError
dimos.hardware.sensors.camera.test_webcam::test_webcam_without_intrinsics_reports_a_nominal_pinhole
Stack Traces | 0.011s run time
def test_webcam_without_intrinsics_reports_a_nominal_pinhole() -> None:
>       info = Webcam(width=1280, height=720).camera_info


.../sensors/camera/test_webcam.py:28: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../sensors/camera/webcam.py:76: in __init__
    super().__init__(*args, **kwargs)
        __class__  = <class 'dimos.hardware.sensors.camera.webcam.Webcam'>
        args       = ()
        kwargs     = {'height': 720, 'width': 1280}
        self       = <dimos.hardware.sensors.camera.webcam.Webcam object at 0xffe5988e5fa0>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <dimos.hardware.sensors.camera.webcam.Webcam object at 0xffe5988e5fa0>
kwargs = {'height': 720, 'width': 1280}
config_type = <class 'dimos.hardware.sensors.camera.webcam.WebcamConfig'>

    def __init__(self, **kwargs: Any) -> None:
        config_type = get_type_hints(type(self))["config"]
>       self.config = config_type(**kwargs)
E       pydantic.errors.PydanticUserError: `WebcamConfig` is not fully defined; you should define `np`, then call `WebcamConfig.model_rebuild()`.
E       
E       For further information visit https://errors.pydantic.dev/2.12/u/class-not-fully-defined

config_type = <class 'dimos.hardware.sensors.camera.webcam.WebcamConfig'>
kwargs     = {'height': 720, 'width': 1280}
self       = <dimos.hardware.sensors.camera.webcam.Webcam object at 0xffe5988e5fa0>

.../protocol/service/spec.py:47: PydanticUserError
dimos.perception.fiducial.blueprints.test_desk_marker_tf::test_create_desk_webcam_loads_camera_info_yaml
Stack Traces | 0.011s run time
tmp_path = PosixPath('.../pytest-0/popen-gw1/test_create_desk_webcam_loads_0')

    def test_create_desk_webcam_loads_camera_info_yaml(tmp_path: Path) -> None:
        camera_info_yaml = tmp_path / "camera_info.yaml"
        camera_info_yaml.write_text(
            """
    image_width: 1920
    image_height: 1080
    camera_name: macbook_pro_14_2025_center_stage
    distortion_model: plumb_bob
    camera_matrix:
      rows: 3
      cols: 3
      data: [2236.0, 0.0, 990.0, 0.0, 2378.0, 568.0, 0.0, 0.0, 1.0]
    distortion_coefficients:
      rows: 1
      cols: 5
      data: [1.7, -24.5, -0.03, -0.1, 212.1]
    rectification_matrix:
      rows: 3
      cols: 3
      data: [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0]
    projection_matrix:
      rows: 3
      cols: 4
      data: [2236.0, 0.0, 990.0, 0.0, 0.0, 2378.0, 568.0, 0.0, 0.0, 0.0, 1.0, 0.0]
    """.lstrip()
        )
    
>       camera = create_desk_webcam(camera_info_yaml, camera_index=1, fps=7.5)

camera_info_yaml = PosixPath('.../pytest-0/popen-gw1/test_create_desk_webcam_loads_0/camera_info.yaml')
tmp_path   = PosixPath('.../pytest-0/popen-gw1/test_create_desk_webcam_loads_0')

.../fiducial/blueprints/test_desk_marker_tf.py:83: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../fiducial/blueprints/desk_marker_tf.py:52: in create_desk_webcam
    return Webcam(
        camera_index = 1
        camera_info = CameraInfo(height=1080, width=1920, distortion_model='plumb_bob', frame_id='camera_optical', ts=1792464740.441677)
        camera_info_yaml = PosixPath('.../pytest-0/popen-gw1/test_create_desk_webcam_loads_0/camera_info.yaml')
        fps        = 7.5
.../sensors/camera/webcam.py:76: in __init__
    super().__init__(*args, **kwargs)
        __class__  = <class 'dimos.hardware.sensors.camera.webcam.Webcam'>
        args       = ()
        kwargs     = {'camera_index': 1, 'camera_info': CameraInfo(height=1080, width=1920, distortion_model='plumb_bob', frame_id='camera_optical', ts=1792464740.441677), 'fps': 7.5, 'height': 1080, ...}
        self       = <dimos.hardware.sensors.camera.webcam.Webcam object at 0xffa1fb8d79e0>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <dimos.hardware.sensors.camera.webcam.Webcam object at 0xffa1fb8d79e0>
kwargs = {'camera_index': 1, 'camera_info': CameraInfo(height=1080, width=1920, distortion_model='plumb_bob', frame_id='camera_optical', ts=1792464740.441677), 'fps': 7.5, 'height': 1080, ...}
config_type = <class 'dimos.hardware.sensors.camera.webcam.WebcamConfig'>

    def __init__(self, **kwargs: Any) -> None:
        config_type = get_type_hints(type(self))["config"]
>       self.config = config_type(**kwargs)
E       pydantic.errors.PydanticUserError: `WebcamConfig` is not fully defined; you should define `np`, then call `WebcamConfig.model_rebuild()`.
E       
E       For further information visit https://errors.pydantic.dev/2.12/u/class-not-fully-defined

config_type = <class 'dimos.hardware.sensors.camera.webcam.WebcamConfig'>
kwargs     = {'camera_index': 1, 'camera_info': CameraInfo(height=1080, width=1920, distortion_model='plumb_bob', frame_id='camera_optical', ts=1792464740.441677), 'fps': 7.5, 'height': 1080, ...}
self       = <dimos.hardware.sensors.camera.webcam.Webcam object at 0xffa1fb8d79e0>

.../protocol/service/spec.py:47: PydanticUserError
dimos.hardware.sensors.camera.test_webcam::test_capture_converts_bgr_and_crops_before_cdr_encoding[left-0]
Stack Traces | 0.012s run time
side = 'left', start = 0
mocker = <pytest_mock.plugin.MockerFixture object at 0xffe598a8da00>

    @pytest.mark.parametrize("side,start", [("left", 0), ("right", 2)])
    def test_capture_converts_bgr_and_crops_before_cdr_encoding(side, start, mocker):
>       camera = Webcam(stereo_slice=side, frame_id_prefix="robot")

mocker     = <pytest_mock.plugin.MockerFixture object at 0xffe598a8da00>
side       = 'left'
start      = 0

.../sensors/camera/test_webcam.py:67: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../sensors/camera/webcam.py:76: in __init__
    super().__init__(*args, **kwargs)
        __class__  = <class 'dimos.hardware.sensors.camera.webcam.Webcam'>
        args       = ()
        kwargs     = {'frame_id_prefix': 'robot', 'stereo_slice': 'left'}
        self       = <dimos.hardware.sensors.camera.webcam.Webcam object at 0xffe598a8d9d0>
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <dimos.hardware.sensors.camera.webcam.Webcam object at 0xffe598a8d9d0>
kwargs = {'frame_id_prefix': 'robot', 'stereo_slice': 'left'}
config_type = <class 'dimos.hardware.sensors.camera.webcam.WebcamConfig'>

    def __init__(self, **kwargs: Any) -> None:
        config_type = get_type_hints(type(self))["config"]
>       self.config = config_type(**kwargs)
E       pydantic.errors.PydanticUserError: `WebcamConfig` is not fully defined; you should define `np`, then call `WebcamConfig.model_rebuild()`.
E       
E       For further information visit https://errors.pydantic.dev/2.12/u/class-not-fully-defined

config_type = <class 'dimos.hardware.sensors.camera.webcam.WebcamConfig'>
kwargs     = {'frame_id_prefix': 'robot', 'stereo_slice': 'left'}
self       = <dimos.hardware.sensors.camera.webcam.Webcam object at 0xffe598a8d9d0>

.../protocol/service/spec.py:47: PydanticUserError
dimos.codebase_checks.test_docs_branding::test_docs_use_current_branding
Stack Traces | 0.016s run time
def test_docs_use_current_branding() -> None:
        """Fail if any file under docs/ spells the brand "DimOS" instead of "dimOS"."""
        hits = find_old_branding()
        if hits:
            listing = "\n".join(
                f"  - {p.relative_to(DIMOS_PROJECT_ROOT)}:{lineno}: {line.strip()}"
                for p, lineno, line in hits
            )
>           raise AssertionError(f'Found "DimOS" in docs/:\n{listing}\n\nThe brand is spelled "dimOS".')
E           AssertionError: Found "DimOS" in docs/:
E             - docs/usage/visualization.md:179: DimOS environment:
E           
E           The brand is spelled "dimOS".

hits       = [(PosixPath('.../docs/usage/visualization.md'), 179, 'DimOS environment:')]
listing    = '  - docs/usage/visualization.md:179: DimOS environment:'

dimos/codebase_checks/test_docs_branding.py:43: AssertionError
dimos.codebase_checks.test_inline_heavy_imports::test_heavy_imports_are_inline
Stack Traces | 1.91s run time
def test_heavy_imports_are_inline() -> None:
        """Fail if any file imports cv2/open3d/rerun at module level."""
        hits = find_eager_heavy_imports()
        if hits:
            listing = "\n".join(
                f"  - dimos/{f}:{line}: `{module}`"
                for f, lines in sorted(hits.items())
                for line, module in lines
            )
>           raise AssertionError(
                f"Found module-level import(s) of {'/'.join(HEAVY_MODULES)}:\n{listing}\n\n"
                "These libraries load large native extensions into every process that "
                "transitively imports the module. Import them inside the function or "
                "method that uses them; imports needed only for type annotations go "
                "under `if TYPE_CHECKING:`."
            )
E           AssertionError: Found module-level import(s) of cv2/open3d/rerun:
E             - dimos/msgs/image.py:22: `cv2`
E             - dimos/msgs/pointcloud.py:24: `open3d`
E           
E           These libraries load large native extensions into every process that transitively imports the module. Import them inside the function or method that uses them; imports needed only for type annotations go under `if TYPE_CHECKING:`.

hits       = {'msgs/image.py': [(22, 'cv2')], 'msgs/pointcloud.py': [(24, 'open3d')]}
listing    = '  - dimos/msgs/image.py:22: `cv2`\n  - dimos/msgs/pointcloud.py:24: `open3d`'

dimos/codebase_checks/test_inline_heavy_imports.py:92: AssertionError
dimos.core.test_build_native_modules::test_module_locks_pin_the_shared_code_as_it_is_now
Stack Traces | 3.31s run time
@pytest.mark.skipif(not _IN_GIT_CHECKOUT, reason="needs a git checkout to read refs")
    def test_module_locks_pin_the_shared_code_as_it_is_now() -> None:
        """Every module's pin on native/cpp or native/rust must hold this commit's tree.
    
        `nix flake lock` and `cargo build --locked` do not notice that the shared code
        moved -- they only check that the lock is complete -- so a change there can land
        while every module still builds against the revision before it. The revision is
        free to be older than HEAD; what must match is the content it pins.
        """
        stale = {
            _rel(lock): names
            for lock in _RELOCK.flake_locks()
            if (names := _RELOCK.stale_flake_inputs(lock))
        }
        stale |= {
            _rel(manifest): why
            for manifest in _RELOCK.cargo_manifests()
            if (why := _RELOCK.stale_cargo_pins(manifest))
        }
>       assert not stale, (
            "these modules pin shared code that is not the code in this commit, so they build "
            f"against the older version: {stale} -- push, run bin/relock-shared-flakes, commit"
        )
E       AssertionError: these modules pin shared code that is not the code in this commit, so they build against the older version: {'.../memory/rust/Cargo.toml': ['dimos-module @ 6663bcb6e5'], '.../realsense/rust/Cargo.toml': ['dimos-module @ 6663bcb6e5'], '.../v4l2/rust/Cargo.toml': ['dimos-module @ 6663bcb6e5'], '.../livox/rust/Cargo.toml': ['dimos-module @ 6663bcb6e5'], '.../pointlio/rust/Cargo.toml': ['dimos-module @ 6663bcb6e5'], '.../lidar/virtual_mid360/Cargo.toml': ['dimos-module @ 6663bcb6e5'], '.../dim_slam/rust/Cargo.toml': ['dimos-module @ 6663bcb6e5'], '.../ray_tracing/rust/Cargo.toml': ['dimos-module @ 6663bcb6e5'], '.../mls_planner/rust/Cargo.toml': ['dimos-module @ 6663bcb6e5'], '.../local_planner/rust/Cargo.toml': ['dimos-module @ 6663bcb6e5'], '.../fancy/rust/Cargo.toml': ['dimos-module @ 6663bcb6e5'], '.../depth2depth_cloud/rust/Cargo.toml': ['dimos-module @ 6663bcb6e5'], '.../dds/rust/Cargo.toml': ['dimos-module @ 6663bcb6e5'], '.../native-modules/rust/Cargo.toml': ['dimos-module @ 6663bcb6e5']} -- push, run bin/relock-shared-flakes, commit
E       assert not {'.../memory/rust/Cargo.toml': ['dimos-module @ 6663bcb6e5'], '.../realsense/rust/Cargo.toml': ['dimos-module @ 6663bcb6e5'], '.../v4l2/rust/Cargo.toml': ['dimos-module @ 6663bcb6e5'], '.../livox/rust/Cargo.toml': ['dimos-module @ 6663bcb6e5'], ...}

names      = []
stale      = {'.../memory/rust/Cargo.toml': ['dimos-module @ 6663bcb6e5'], '.../camera/realsense/...'dimos-module @ 6663bcb6e5'], '.../livox/rust/Cargo.toml': ['dimos-module @ 6663bcb6e5'], ...}
why        = ['dimos-module @ 6663bcb6e5']

dimos/core/test_build_native_modules.py:476: AssertionError
dimos.web.relay_bridge.test_relay_bridge_module::test_teleop_stop_is_unconditional
Stack Traces | 5.01s run time
request = <SubRequest 'monitor_threads' for <Function test_teleop_stop_is_unconditional>>

    @pytest.fixture(autouse=True)
    def monitor_threads(request):
        # Capture threads before test runs
        test_name = request.node.nodeid
        with _seen_threads_lock:
            _before_test_threads[test_name] = {
                t.ident for t in threading.enumerate() if t.ident is not None
            }
    
        yield
    
        with _seen_threads_lock:
            before = _before_test_threads.get(test_name, set())
    
        # Threads intentionally left running for the whole process and cleaned up on
        # exit, so they don't count as per-test leaks.
        expected_persistent_thread_prefixes = [
            "Dask-Offload",
            # HuggingFace safetensors conversion thread - no user cleanup API
            # https://github..../transformers/issues/29513
            "Thread-auto_conversion",
        ]
        # Zenoh callback threads belong to a session in the process-wide pool, which
        # outlives the test that first opened it on purpose -- sharing one session is
        # the point of the pool, and closing it per test would cut module-scoped
        # fixtures off from their transports mid-module. They go at interpreter exit.
        # The name is "Thread-<n> (pyo3-closure)", so this cannot be a prefix match.
        expected_persistent_thread_infix = "(pyo3-closure)"
    
        def live_new_threads():
            # Threads created during this test that are still running. A thread that
            # has already stopped is not a leak -- it's done, just not yet reaped
            # from threading's registry -- so we key on is_alive(), not presence.
            result = []
            for t in threading.enumerate():
                if t.ident is None or t.ident in before or t.name == "MainThread":
                    continue
                if any(t.name.startswith(prefix) for prefix in expected_persistent_thread_prefixes):
                    continue
                if expected_persistent_thread_infix in t.name:
                    continue
                if t.is_alive():
                    result.append(t)
            return result
    
        # Some C extensions tear their callback threads down asynchronously, so a
        # thread can stay alive briefly after the test cleaned up its owner (notably
        # zenoh's pyo3-closure threads, freed shortly after the session is closed).
        # A single snapshot races that teardown and flags a thread that is about to
        # exit. Give new threads a grace period to drain; only ones that stay alive
        # are real leaks (a genuinely leaked thread never exits).
        deadline = time.monotonic() + 5.0
        leaked = live_new_threads()
        while leaked and time.monotonic() < deadline:
            time.sleep(0.02)
            leaked = live_new_threads()
    
        if not leaked:
            return
    
        with _seen_threads_lock:
            # Report each leaked thread only once across the session.
            truly_new = [t for t in leaked if t.ident not in _seen_threads]
            for t in leaked:
                _seen_threads.add(t.ident)
    
        if not truly_new:
            return
    
        thread_names = [t.name for t in truly_new]
    
>       pytest.fail(
            f"Non-closed threads created during this test. Thread names: {thread_names}. "
            "Please look at the first test that fails and fix that."
        )
E       Failed: Non-closed threads created during this test. Thread names: ['Thread-340 (run_forever)']. Please look at the first test that fails and fix that.

before     = {281069337047424, 281073461223456}
deadline   = 719.856445365
expected_persistent_thread_infix = '(pyo3-closure)'
expected_persistent_thread_prefixes = ['Dask-Offload', 'Thread-auto_conversion']
leaked     = [<Thread(Thread-340 (run_forever), started daemon 281068598849920)>]
live_new_threads = <function monitor_threads.<locals>.live_new_threads at 0xffa194e89bc0>
request    = <SubRequest 'monitor_threads' for <Function test_teleop_stop_is_unconditional>>
t          = <Thread(Thread-340 (run_forever), started daemon 281068598849920)>
test_name  = '.../web/relay_bridge/test_relay_bridge_module.py::test_teleop_stop_is_unconditional'
thread_names = ['Thread-340 (run_forever)']
truly_new  = [<Thread(Thread-340 (run_forever), started daemon 281068598849920)>]

dimos/conftest.py:432: Failed
dimos.hardware.sensors.camera.test_module::test_metadata_preserves_calibration_and_does_not_mutate_mount_template
Stack Traces | 5.02s run time
request = <SubRequest 'monitor_threads' for <Function test_metadata_preserves_calibration_and_does_not_mutate_mount_template>>

    @pytest.fixture(autouse=True)
    def monitor_threads(request):
        # Capture threads before test runs
        test_name = request.node.nodeid
        with _seen_threads_lock:
            _before_test_threads[test_name] = {
                t.ident for t in threading.enumerate() if t.ident is not None
            }
    
        yield
    
        with _seen_threads_lock:
            before = _before_test_threads.get(test_name, set())
    
        # Threads intentionally left running for the whole process and cleaned up on
        # exit, so they don't count as per-test leaks.
        expected_persistent_thread_prefixes = [
            "Dask-Offload",
            # HuggingFace safetensors conversion thread - no user cleanup API
            # https://github..../transformers/issues/29513
            "Thread-auto_conversion",
        ]
        # Zenoh callback threads belong to a session in the process-wide pool, which
        # outlives the test that first opened it on purpose -- sharing one session is
        # the point of the pool, and closing it per test would cut module-scoped
        # fixtures off from their transports mid-module. They go at interpreter exit.
        # The name is "Thread-<n> (pyo3-closure)", so this cannot be a prefix match.
        expected_persistent_thread_infix = "(pyo3-closure)"
    
        def live_new_threads():
            # Threads created during this test that are still running. A thread that
            # has already stopped is not a leak -- it's done, just not yet reaped
            # from threading's registry -- so we key on is_alive(), not presence.
            result = []
            for t in threading.enumerate():
                if t.ident is None or t.ident in before or t.name == "MainThread":
                    continue
                if any(t.name.startswith(prefix) for prefix in expected_persistent_thread_prefixes):
                    continue
                if expected_persistent_thread_infix in t.name:
                    continue
                if t.is_alive():
                    result.append(t)
            return result
    
        # Some C extensions tear their callback threads down asynchronously, so a
        # thread can stay alive briefly after the test cleaned up its owner (notably
        # zenoh's pyo3-closure threads, freed shortly after the session is closed).
        # A single snapshot races that teardown and flags a thread that is about to
        # exit. Give new threads a grace period to drain; only ones that stay alive
        # are real leaks (a genuinely leaked thread never exits).
        deadline = time.monotonic() + 5.0
        leaked = live_new_threads()
        while leaked and time.monotonic() < deadline:
            time.sleep(0.02)
            leaked = live_new_threads()
    
        if not leaked:
            return
    
        with _seen_threads_lock:
            # Report each leaked thread only once across the session.
            truly_new = [t for t in leaked if t.ident not in _seen_threads]
            for t in leaked:
                _seen_threads.add(t.ident)
    
        if not truly_new:
            return
    
        thread_names = [t.name for t in truly_new]
    
>       pytest.fail(
            f"Non-closed threads created during this test. Thread names: {thread_names}. "
            "Please look at the first test that fails and fix that."
        )
E       Failed: Non-closed threads created during this test. Thread names: ['Thread-7298 (run_forever)']. Please look at the first test that fails and fix that.

before     = {280516303319424, 280520622571552}
deadline   = 918.312364691
expected_persistent_thread_infix = '(pyo3-closure)'
expected_persistent_thread_prefixes = ['Dask-Offload', 'Thread-auto_conversion']
leaked     = [<Thread(Thread-7298 (run_forever), started daemon 280515991040384)>]
live_new_threads = <function monitor_threads.<locals>.live_new_threads at 0xff20e097c680>
request    = <SubRequest 'monitor_threads' for <Function test_metadata_preserves_calibration_and_does_not_mutate_mount_template>>
t          = <Thread(Thread-7298 (run_forever), started daemon 280515991040384)>
test_name  = '.../sensors/camera/test_module.py::test_metadata_preserves_calibration_and_does_not_mutate_mount_template'
thread_names = ['Thread-7298 (run_forever)']
truly_new  = [<Thread(Thread-7298 (run_forever), started daemon 280515991040384)>]

dimos/conftest.py:432: Failed
dimos.cli.test_cli_startup::test_go2_blueprint_import_does_not_pull_heavy_deps
Stack Traces | 5.02s run time
def test_go2_blueprint_import_does_not_pull_heavy_deps() -> None:
        # Importing the blueprint builds LCM handles. memq keeps them off the network.
        env = {**os.environ, "LCM_DEFAULT_URL": "memq://"}
        leaked = _leaked_modules(
            "import dimos.robot.unitree.go2.blueprints.smart.unitree_go2",
            BLUEPRINT_MUST_NOT_IMPORT,
            env=env,
        )
>       assert leaked == []
E       AssertionError: assert ['open3d', 'cv2'] == []
E         
E         Left contains 2 more items, first extra item: #x1B[0m#x1B[33m'#x1B[39;49;00m#x1B[33mopen3d#x1B[39;49;00m#x1B[33m'#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         
E         Full diff:
E         #x1B[0m#x1B[91m- []#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[92m+ [#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[92m+     'open3d',#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[92m+     'cv2',#x1B[39;49;00m#x1B[90m#x1B[39;49;00m
E         #x1B[92m+ ]#x1B[39;49;00m#x1B[90m#x1B[39;49;00m

env        = {'ACCEPT_EULA': 'Y', 'ACTIONS_ID_TOKEN_REQUEST_TOKEN': 'eyJhbGciOiJSUzI1NiIsImtpZCI6IjM4ODI2YjE3LTZhMzAtNWY5Yi1iMTY5LT...-version=2.0', 'ACTIONS_ORCHESTRATION_ID': '37fdca5f-1299-8fa3-85d8-99f5c8fb630c.tests.ubuntu-24_04-arm_3_12_fal', ...}
leaked     = ['open3d', 'cv2']

dimos/cli/test_cli_startup.py:135: AssertionError
dimos.hardware.sensors.camera.test_module::test_disabled_mount_publishes_calibration_without_tf
Stack Traces | 5.03s run time
request = <SubRequest 'monitor_threads' for <Function test_disabled_mount_publishes_calibration_without_tf>>

    @pytest.fixture(autouse=True)
    def monitor_threads(request):
        # Capture threads before test runs
        test_name = request.node.nodeid
        with _seen_threads_lock:
            _before_test_threads[test_name] = {
                t.ident for t in threading.enumerate() if t.ident is not None
            }
    
        yield
    
        with _seen_threads_lock:
            before = _before_test_threads.get(test_name, set())
    
        # Threads intentionally left running for the whole process and cleaned up on
        # exit, so they don't count as per-test leaks.
        expected_persistent_thread_prefixes = [
            "Dask-Offload",
            # HuggingFace safetensors conversion thread - no user cleanup API
            # https://github..../transformers/issues/29513
            "Thread-auto_conversion",
        ]
        # Zenoh callback threads belong to a session in the process-wide pool, which
        # outlives the test that first opened it on purpose -- sharing one session is
        # the point of the pool, and closing it per test would cut module-scoped
        # fixtures off from their transports mid-module. They go at interpreter exit.
        # The name is "Thread-<n> (pyo3-closure)", so this cannot be a prefix match.
        expected_persistent_thread_infix = "(pyo3-closure)"
    
        def live_new_threads():
            # Threads created during this test that are still running. A thread that
            # has already stopped is not a leak -- it's done, just not yet reaped
            # from threading's registry -- so we key on is_alive(), not presence.
            result = []
            for t in threading.enumerate():
                if t.ident is None or t.ident in before or t.name == "MainThread":
                    continue
                if any(t.name.startswith(prefix) for prefix in expected_persistent_thread_prefixes):
                    continue
                if expected_persistent_thread_infix in t.name:
                    continue
                if t.is_alive():
                    result.append(t)
            return result
    
        # Some C extensions tear their callback threads down asynchronously, so a
        # thread can stay alive briefly after the test cleaned up its owner (notably
        # zenoh's pyo3-closure threads, freed shortly after the session is closed).
        # A single snapshot races that teardown and flags a thread that is about to
        # exit. Give new threads a grace period to drain; only ones that stay alive
        # are real leaks (a genuinely leaked thread never exits).
        deadline = time.monotonic() + 5.0
        leaked = live_new_threads()
        while leaked and time.monotonic() < deadline:
            time.sleep(0.02)
            leaked = live_new_threads()
    
        if not leaked:
            return
    
        with _seen_threads_lock:
            # Report each leaked thread only once across the session.
            truly_new = [t for t in leaked if t.ident not in _seen_threads]
            for t in leaked:
                _seen_threads.add(t.ident)
    
        if not truly_new:
            return
    
        thread_names = [t.name for t in truly_new]
    
>       pytest.fail(
            f"Non-closed threads created during this test. Thread names: {thread_names}. "
            "Please look at the first test that fails and fix that."
        )
E       Failed: Non-closed threads created during this test. Thread names: ['Thread-7300 (run_forever)']. Please look at the first test that fails and fix that.

before     = {280515991040384, 280516303319424, 280520622571552}
deadline   = 923.355467613
expected_persistent_thread_infix = '(pyo3-closure)'
expected_persistent_thread_prefixes = ['Dask-Offload', 'Thread-auto_conversion']
leaked     = [<Thread(Thread-7300 (run_forever), started daemon 280516286476672)>]
live_new_threads = <function monitor_threads.<locals>.live_new_threads at 0xff20e097e020>
request    = <SubRequest 'monitor_threads' for <Function test_disabled_mount_publishes_calibration_without_tf>>
t          = <Thread(Thread-7300 (run_forever), started daemon 280516286476672)>
test_name  = '.../sensors/camera/test_module.py::test_disabled_mount_publishes_calibration_without_tf'
thread_names = ['Thread-7300 (run_forever)']
truly_new  = [<Thread(Thread-7300 (run_forever), started daemon 280516286476672)>]

dimos/conftest.py:432: Failed

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

This branch was successfully deployed

1 active deployment
cachix — 7e245238 Deployed Oct 20, 2026 by TomCC7 via cachix-build-macos #13365
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