Skip to main content

Graph Basics

FieldValue
DifficultyIntermediate
Estimated Read Time15-20 minutes
Labelsgraph, traversal, metadata

Concept

This tutorial introduces the NEAT graph runtime using the smallest useful graph: push one sample through connected nodes and verify metadata survives traversal.

If you are new to graph APIs, read this chapter before hybrid/multistream graph tutorials. It teaches the baseline mental model:

  • Build graph nodes and edges.
  • Start a GraphSession, then push/pull samples.
  • Validate stream/frame metadata after graph execution.

What this chapter demonstrates:

  • Preferred hybrid path (pipeline node + stage node).
  • Deterministic fallback path (stage-only graph).
  • Signature checks that confirm graph output contracts.

Reference:

Learning Process

  1. Build a minimal graph and push one deterministic tensor sample.
  2. Run preferred pipeline+stage composition, with stage-only fallback when needed.
  3. Pull graph output and validate stream/frame metadata stamping.
  4. Use CHECK, SIGNATURE, and [OK] markers to confirm graph correctness.

Run

NEAT_EXTRAS_ROOT=<sima-neat-*-Linux-extras>
cd $NEAT_EXTRAS_ROOT/lib/sima-neat/tutorials
./tutorial_v2_014_graph_basics

Code

tutorials/014_graph_basics/graph_basics.cpp
// Compose a minimal NEAT Graph: PipelineNode -> StampFrameIdNode, push one sample, pull output.
//
// Usage:
// tutorial_v2_014_graph_basics

#include "neat/graph.h"
#include "neat/nodes.h"
#include "neat/session.h"

#include <cstdint>
#include <iostream>
#include <memory>
#include <stdexcept>
#include <utility>
#include <vector>

namespace {

std::vector<int64_t> contiguous_strides_bytes(const std::vector<int64_t>& shape,
int64_t elem_bytes) {
std::vector<int64_t> strides(shape.size(), 0);
int64_t stride = elem_bytes;
for (int i = static_cast<int>(shape.size()) - 1; i >= 0; --i) {
strides[static_cast<size_t>(i)] = stride;
stride *= shape[static_cast<size_t>(i)];
}
return strides;
}

simaai::neat::Sample make_sample() {
const int w = 8;
const int h = 8;
const int c = 3;
const std::size_t bytes = static_cast<std::size_t>(w) * h * c;

simaai::neat::Tensor t;
t.device = {simaai::neat::DeviceType::CPU, 0};
t.dtype = simaai::neat::TensorDType::UInt8;
t.layout = simaai::neat::TensorLayout::HWC;
t.shape = {h, w, c};
t.semantic.image = simaai::neat::ImageSpec{simaai::neat::ImageSpec::PixelFormat::RGB, ""};
t.storage = simaai::neat::make_cpu_owned_storage(bytes);
t.strides_bytes = contiguous_strides_bytes(t.shape, 1);
t.read_only = false;
{
auto map = t.map(simaai::neat::MapMode::Write);
auto* p = static_cast<std::uint8_t*>(map.data);
for (std::size_t i = 0; i < bytes; ++i)
p[i] = static_cast<std::uint8_t>(i % 255);
}
t.read_only = true;

simaai::neat::Sample s;
s.kind = simaai::neat::SampleKind::Tensor;
s.tensor = std::move(t);
s.stream_id = "graph";
s.frame_id = -1;
return s;
}

} // namespace

int main() {
try {
using namespace simaai::neat::graph;

// CORE LOGIC
// A Graph is a DAG of nodes. Here: a PipelineNode (wraps a classic gst node)
// feeds a StampFrameIdNode, which assigns a monotonic frame id.
Graph g;

const NodeId pipe = g.add(
std::make_shared<nodes::PipelineNode>(simaai::neat::nodes::VideoConvert(), "convert"));
const NodeId stamp = g.add(nodes::StampFrameIdNode("stamp"));
g.connect(pipe, stamp);

std::cout << GraphPrinter::to_text(g) << "\n";

GraphRun run = GraphSession(std::move(g)).build();
run.push(pipe, make_sample());
auto out = run.pull(stamp, /*timeout_ms=*/2000);
run.stop();

if (!out.has_value())
throw std::runtime_error("graph produced no output");
std::cout << "stream=" << out->stream_id << " frame=" << out->frame_id << "\n";
std::cout << "[OK] 014_graph_basics\n";
return 0;
} catch (const std::exception& e) {
std::cerr << "[FAIL] " << e.what() << "\n";
return 1;
}
}

Source