1. Start from the scaffold
Create the motion watcher project with tatolab new, run it, and find your way around its files.
tatolab new writes a small, working stream. You’ll change it into the motion watcher over the next steps.
Create and run the project
Section titled “Create and run the project”tatolab new motion-watchercd motion-watcheruv synctatolab devA window opens with your camera, colors inverted, and the terminal shows a brightness record every second. Keep tatolab dev running for the whole tutorial. Each time you save a file, it loads the stream again with your change.
If tatolab dev says the name main is held by another stream, the quickstart’s stream is still loaded: press Ctrl-C in its terminal, or run tatolab rm main if you kept it.
A tour of stream.py
Section titled “A tour of stream.py”Open stream.py. Apart from its docstring, it’s this:
from tatolab.stream import CameraSource, DisplayWindow, StreamBuilder, stream
from nodes.brightness_meter import BrightnessMeterfrom nodes.inverting_effect import InvertingEffect
@streamdef main(stream_builder: StreamBuilder) -> None: """Camera, inverted, in a window; brightness logged once a second.""" source = stream_builder.add(CameraSource) effect = stream_builder.add(InvertingEffect) meter = stream_builder.add(BrightnessMeter) window = stream_builder.add( DisplayWindow, config={"title": "Tatolab", "scaling": "fit"} ) stream_builder.connect(source.output("video"), effect.input("video_from_upstream")) stream_builder.connect(effect.output("video_to_downstream"), window.input("video")) stream_builder.connect( effect.output("video_to_downstream"), meter.input("video_from_upstream") ) stream_builder.expose(effect.output("video_to_downstream"))Line by line:
@streammarksmainas a stream. The runtime calls it once, when the stream loads, to find out which nodes to create and how to connect them. It doesn’t run per frame.stream_builder.add(CameraSource)adds a node. You pass the class itself, not an instance.CameraSourceandDisplayWindoware built into the runtime;InvertingEffectandBrightnessMeterare your project’s own, innodes/.config={...}sets a node’s settings. For built-in nodes, your editor can complete the keys and flag typos.stream_builder.connect(output, input)links an output port to an input port. The effect’s output is connected twice, to the window and to the meter: one output can feed any number of inputs, and it isn’t split between them.stream_builder.expose(...)lets other streams on this machine read that output. Outputs are internal to their stream until you expose them. You’ll use this in step 3.
A tour of the nodes
Section titled “A tour of the nodes”nodes/inverting_effect.py is a GPU effect: one GLSL function that runs on the GPU for every pixel of every frame.
nodes/brightness_meter.py is a CPU node. Once a second it reads one frame’s pixels as a numpy array with frame.cpu(), computes the average brightness, and logs it. It runs in its own process, reading the effect’s output beside the window, so if it were slow, the window wouldn’t notice.
In the next step you’ll replace both with a node of your own.
Rename the stream
Section titled “Rename the stream”The stream’s name comes from the function’s name, so this one is called main. Give it a name that says what it is. In stream.py, change the function’s name, its docstring and the window’s title:
@streamdef motion_watcher(stream_builder: StreamBuilder) -> None: """Watches the camera for motion.""" source = stream_builder.add(CameraSource) effect = stream_builder.add(InvertingEffect) meter = stream_builder.add(BrightnessMeter) window = stream_builder.add( DisplayWindow, config={"title": "Motion watcher", "scaling": "fit"} ) # … the connect and expose lines stay as they areSave. tatolab dev loads the stream again, now named motion_watcher, and the window’s title changes. The name matters later: it’s how you refer to the stream in commands like tatolab logs --stream motion_watcher, and it’s part of every exposed port’s address.
Next you’ll write a node that reports motion: Write your first node.