Skip to content

Webcam: 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 Webcam.
"""
import time

from loguru import logger

from babyros import node
from medulla.cameras import webcam


def main():
    camera = None
    try:
        camera = webcam.Webcam(
            name="my_webcam",
            camera_id=0
        )
        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
"""
A simple example demonstrating how to use the webcam camera with BabyROS to
stream live video.

Please run this example from a terminal to avoid issues with rerun's spawn mode.
"""
import time

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

from babyros import node


def log_video(image: np.ndarray) -> None:
    rr.log(
        "Continuous_Image_Capture",
        rr.Image(image)
    )


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

    name = "my_webcam"
    base_topic = f"medulla/v1/camera/webcam/{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 a Webcam 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 Webcam instance, alive and reachable by remote clients.

python
camera = webcam.Webcam(
    name="my_webcam",
    camera_id=0
)
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 Webcam instance in the server process. The name variable must exactly match the name passed to Webcam in the server, because both sides derive their topic paths from it.

python
name = "my_webcam"
base_topic = f"medulla/v1/camera/webcam/{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 pushes each incoming frame as a raw NumPy array onto the video topic. Each frame arrives in the log_video callback and is forwarded directly to Rerun using rr.Image. 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.