Node
A Node is the smallest composable unit in SiMa Neat.
Nodes are the things a Graph wires together: decode stages, preprocess stages, model stages, postprocess stages, sources, sinks, and application boundaries.
Reference:
What a Node represents
A node contributes one logical piece of work plus the metadata the builder needs to wire it safely:
- runtime work, such as decode, convert, preprocess, inference, postprocess, or sink behavior;
- input/output contracts, such as expected media type, tensor shape, dtype, or caps;
- deterministic backend naming, so
describe(), diagnostics, metrics, and probes stay stable.
The main rule is simple:
Graph = nodes wired together
Model is the model-aware node source. A Model can be added directly to a Graph, or can provide reusable stage fragments such as preprocess, inference, and postprocess.
Pre-built node groups
Some common patterns are more than one low-level node. Neat exposes those as pre-built groups so users do not have to hand-wire every source, decoder, converter, queue, or sink.
Think of a group as a reusable mini-graph: a named bundle of nodes with a clear contract.
Examples:
VideoInputGroup(...): file/video source + decode path.RtspDecodedInput(...): RTSP source + decode path.- model route fragments: preprocess + inference + decode/postprocess around a compiled model.
#include "neat/runtime.h"
#include "neat/node_groups.h"
simaai::neat::Graph graph;
simaai::neat::nodes::groups::VideoInputGroupOptions vopt;
vopt.path = "/data/sample.mp4";
graph.add(simaai::neat::nodes::groups::VideoInputGroup(vopt));
Use pre-built groups when the shape is standard and the interesting part of your app is not the internal media plumbing. If you need custom topology, build the equivalent Graph yourself from lower-level nodes.
Boundary nodes: Input and Output
Input and Output are nodes too. They are special because they describe where data enters or leaves a graph fragment.
The important mental model:
Input("name")andOutput("name")are named doors.
At the outside of the final app, those doors become public runtime APIs:
Input("image")becomesrun.push("image", ...).Output("classes")becomesrun.pull("classes", ...).
Inside a larger graph, those same doors are just connection points. Neat removes the internal boundary nodes while building the executable runtime path and wires the real work directly.
simaai::neat::Graph route("route");
route.add(simaai::neat::nodes::Input("image"));
route.add(model);
route.add(simaai::neat::nodes::Output("classes"));
Used as a complete app:
auto run = route.build();
run.push("image", simaai::neat::TensorList{image_tensor});
auto classes = run.pull("classes");
Used inside a larger app:
simaai::neat::Graph app("app");
app.connect(camera, route);
app.connect(route, telemetry);
Conceptually, the internal boundary nodes lower to direct wiring:
camera -> model -> telemetry
The names are still preserved for diagnostics, endpoint inspection, metrics, and visualization. They just do not create extra queues, copies, or fake runtime sinks in the middle of the app.
App-pushed input
Use nodes::Input when your application already has the frame or tensor and wants to push it into Neat.
simaai::neat::Graph graph;
simaai::neat::InputOptions iopt;
iopt.format = "RGB";
iopt.width = 224;
iopt.height = 224;
graph.add(simaai::neat::nodes::Input("image", iopt));
Sample output
Use nodes::Output when the consumer needs a full Sample: payload plus stream/frame/timestamp metadata.
simaai::neat::Graph graph;
graph.add(simaai::neat::nodes::Input("image"));
graph.add(model);
graph.add(simaai::neat::nodes::Output("classes"));
const auto run = graph.build();
Use Graph::add_output_tensor(...) when you want the simpler tensor-first output path with predictable tensor shape/format and do not need the richer sample envelope.
Quick decision guide
- Source is file, camera, or RTSP: use a pre-built input group from
nodes::groups. - Source is app-produced tensor/frame: use
nodes::Input. - Output consumer needs stream/frame/timestamp metadata: use
nodes::Outputand pullSampleobjects. - Output consumer only needs numeric data: use tensor-first output helpers and pull
Tensorobjects. - Need a reusable bundle: make a
Graphfragment from nodes and add/connect it like any other building block.
Why this matters
- One concept covers atomic stages, boundary declarations, and pre-built groups.
- Reusable graph fragments behave like functions: inputs in, outputs out, no accidental runtime sources/sinks in the middle.
- Diagnostics stay friendly because node names and boundary names survive lowering.
- Runtime stays efficient because internal boundaries do not force hidden copies or queues.