Segmentator¶
Configuration-driven image and video segmentation. A pipeline is described in YAML, so trying a different preprocessing chain, detector or background model is a config edit, not a code edit.
uv run segmentator examples/baseline.yaml
uv run segmentator examples/structure.yaml
uv run segmentator examples/mog2_contours.yaml --video inputs/gasvid1.mp4 --max-frames 300
--video and --output override the source path and the first ffmpeg sink path
for one run.
There is also an editor, which authors these configs with a live preview of every stage and every sink:
Part of the goncanalyser suite. goncanalyser is the Qt workspace where you tune an operator chain by hand; segmentator is the headless engine that runs the tuned recipe over a batch. Every operator goncanalyser exposes exists here as a stage, and the two agree numerically on the same frame with the same parameters.
Where to go next¶
| Installation | uv sync, the gui extra, and the prebuilt AppImage / dmg |
| Writing a config | the config format: Ctx, taps, input:, branching with select |
| The catalogue | every source, stage and sink, and what its parameters mean |
| The editor | the editor, and every operation it allows |
Architecture¶
A pipeline is source → stages → sinks. Stages are an ordered list applied to every
frame; nothing branches on runtime state, so there is no state machine here — the only
"state" is background-warmup vs steady, which BackgroundModel.ready handles with one if.
@dataclass
class Ctx:
image: np.ndarray # working array; stages rebind it
source: np.ndarray # original BGR frame, untouched
index: int
store: dict # side channel within one frame
taps: dict # named stages' outputs, for sinks to pick from
metrics: dict # per-frame scalars, what the json sink writes
rows: dict # per-object detail, what the csv and crops sinks write
class Stage(Protocol):
def apply(self, ctx: Ctx) -> None: ...
class Sink(Protocol):
def write(self, ctx: Ctx) -> bool: ... # False stops the run
def close(self) -> None: ...
Components register themselves by name (@register("stage", "median_blur")), and
build() turns a {type: ..., **params} mapping into an instance. An unknown name
raises with the list of known ones. Nothing else has to be told: the config format,
the error messages and the editor's palette and forms are all read off the registry
and the constructors.
| File | Contents |
|---|---|
| cli.py | CLI |
| segmentator/pipeline.py | Ctx, protocols, registry, Pipeline |
| segmentator/io.py | sources and sinks |
| segmentator/ops/ | pure operators, no stage protocol, no config object |
| segmentator/stages/ | the registered stages, by family |
| segmentator/background_model.py | fixed mean-of-N background |
| segmentator/video_writer.py | ffmpeg/libx264 pipe |
| segmentator/gui/ | the PyQt6 editor — optional, --extra gui |
Parity with goncanalyser¶
Every operator is ported from goncanalyser's features/* — the function bodies,
not a reimplementation — with each Settings field turned into a constructor
argument on the stage that uses it. On the same frame with the same parameters
the two produce identical output: edge_px, corner and keypoint counts, HOG
vectors, LBP codes, histogram plots, contour rows and Hough rows all match
exactly, across the two repos' different OpenCV builds.
tests/test_stages.py holds goncanalyser's own _demo() assertions, re-pointed
at the stages — ported rather than invented, so a passing suite means the two
repos agree on numbers, not merely that both run.