Skip to content

IDS: Publish Live Video with BabyROS

SUMMARY

Stream live video using BabyROS as the communication layer. Two scripts share responsibility: a camera server that owns the hardware connection, and a client that controls streaming remotely via BabyROS topics.

Example

The camera server (camera_loop.py) runs in Terminal 1 — it connects to the hardware and keeps BabyROS nodes alive. The client (publish_video_example.py) runs in Terminal 2 — it publishes a start signal, subscribes to the video topic to receive and log frames to Rerun, then publishes a stop signal after 10 seconds.

Code

Terminal 1 — Camera Server:

python
"""
Camera loop for the IDS camera.
"""
import time

from loguru import logger

from babyros import node
from medulla.cameras import ids


def main():
    camera = None
    try:
        camera = ids.IDS(
            name="my_ids_camera",
            serial_number="4108909352"
        )
        camera.connect()
        logger.info("Camera Server is running... Press Ctrl+C to stop.")
        while True:
            time.sleep(1)

    except KeyboardInterrupt:
        logger.info("Shutting down server...")
    finally:
        if camera is not None:
            camera.disconnect()
            node.SessionManager.delete()
        logger.info("Completed cleanup.")


if __name__ == "__main__":
    main()

Terminal 2 — BabyROS Client:

python
"""
Stream live video from an IDS camera over BabyROS.
"""
import time

from loguru import logger
import rerun as rr
import numpy as np

from babyros import node


def log_video(data: np.ndarray) -> None:
    rr.log(
        "Continuous_Image_Capture",
        rr.EncodedImage(contents=data, media_type="image/jpeg")
    )


def main():
    rr.init("IDS_Example", spawn=True)

    name = "my_ids_camera"
    base_topic = f"medulla/v1/camera/ids/IDS/{name}"

    start_video_stream_publisher = node.Publisher(
        topic=f"{base_topic}/start_video_stream"
    )
    stop_video_stream_publisher = node.Publisher(
        topic=f"{base_topic}/stop_video_stream"
    )
    video_subscriber = node.Subscriber(
        topic=f"{base_topic}/video",
        callback=log_video,
    )

    try:
        start_video_stream_publisher.publish(data={})
        time.sleep(10)
        stop_video_stream_publisher.publish(data={})
    except KeyboardInterrupt:
        logger.info("Shutting down.")
    finally:
        start_video_stream_publisher.delete()
        stop_video_stream_publisher.delete()
        video_subscriber.delete()
        node.SessionManager.delete()
        logger.info("Completed cleanup.")


if __name__ == "__main__":
    main()

Explanation

Now, let’s break down the code piece by piece.

Camera Server (camera_loop.py)

The server creates an IDS instance and connects to the hardware. After connecting, it enters an infinite sleep loop — this does nothing except keep the process, and the BabyROS nodes registered inside the IDS instance, alive and reachable by remote clients.

python
camera = ids.IDS(
    name="my_ids_camera",
    serial_number="4108909352"
)
camera.connect()
while True:
    time.sleep(1)

On Ctrl+C, the finally block disconnects the camera and calls node.SessionManager.delete() to cleanly tear down the BabyROS session.

python
finally:
    if camera is not None:
        camera.disconnect()
        node.SessionManager.delete()

BabyROS Client (publish_video_example.py)

The client never accesses the camera hardware directly. Instead it constructs three BabyROS nodes whose topic paths mirror those registered by the IDS instance in the server process. The name variable must exactly match the name passed to IDS in the server, because both sides derive their topic paths from it.

python
name = "my_ids_camera"
base_topic = f"medulla/v1/camera/ids/IDS/{name}"

start_video_stream_publisher = node.Publisher(topic=f"{base_topic}/start_video_stream")
stop_video_stream_publisher = node.Publisher(topic=f"{base_topic}/stop_video_stream")
video_subscriber = node.Subscriber(topic=f"{base_topic}/video", callback=log_video)

Publishing an empty payload to start_video_stream signals the server to begin continuous acquisition. The server JPEG-encodes each incoming frame and pushes it onto the video topic. Each frame arrives in the log_video callback and is forwarded to Rerun using rr.EncodedImage, which accepts the compressed bytes directly without an intermediate decode step. After 10 seconds, publishing to stop_video_stream halts acquisition on the server side.

python
start_video_stream_publisher.publish(data={})
time.sleep(10)
stop_video_stream_publisher.publish(data={})

The finally block deletes all three nodes and closes the BabyROS session regardless of how the script exits.

python
finally:
    start_video_stream_publisher.delete()
    stop_video_stream_publisher.delete()
    video_subscriber.delete()
    node.SessionManager.delete()

Run

Start the server first, then the client. Both must run from a terminal.

Terminal 1:

bash
python camera_loop.py

Wait for Camera Server is running... Press Ctrl+C to stop., then:

Terminal 2:

bash
python publish_video_example.py

Frames stream under Continuous_Image_Capture for 10 seconds. Press Ctrl+C in Terminal 1 to shut down the server.