Read and Interpret Model Output
| Field | Value |
|---|---|
| Difficulty | Intermediate |
| Estimated Read Time | 10-15 minutes |
| Labels | output, patterns, sink |
Before you optimize throughput or add complex graph logic, you need a stable, defensive way to read whatever a run hands back. The output is always a Sample, but its shape varies: it might be a single tensor, or a bundle of named fields (as in chapter 009). Reaching for .tensor on a bundle — or assuming a shape that isn't there — is the bug this chapter teaches you to avoid.
We build the same minimal sync graph as before, run one frame, then inspect the result methodically: its kind, whether a tensor is present, how many fields it has, and the tensor's rank. By the end you will have a reusable output-reading pattern that works for any model the runtime serves.
Walkthrough
Configure the input
Declare the input contract — pixel format, width, height, depth — matching the frame we will push. This is the same boundary contract used throughout these chapters.
simaai::neat::InputOptions in;
in.format = "RGB";
in.width = rgb.cols;
in.height = rgb.rows;
in.depth = rgb.channels();
Compose and build the graph
Wire an input node to an output node and build() into a sync Run, passing the frame so build() can negotiate concrete shapes. With no model in between, the output mirrors the input — which is exactly what makes this a clean place to study output structure.
simaai::neat::Graph graph;
graph.add(simaai::neat::nodes::Input(in));
graph.add(simaai::neat::nodes::Output());
// Use Graph::run(...) for a one-shot synchronous frame.
Run one frame
Push one frame and pull one result synchronously. The single run(...) call is the one-frame shortcut; what it returns is the object we are here to dissect.
run.run(...) returns a TensorList — for a single-tensor output that means one entry, which the next step inspects via out.size() and out.front().
// Graph::run is the one-frame synchronous shortcut.
simaai::neat::TensorList out = graph.run(std::vector<cv::Mat>{rgb});
Inspect the sample
This is the lesson: read the structure before the payload. Check presence and kind first, then derive rank from the tensor's shape. Guarding each step (non-empty output, non-empty shape) is what makes an output reader robust against models whose shape you don't control.
Report out.size() and tensor presence, throw if empty or if out.front().shape is empty, then print rank from shape.size(). (The fields=0 line is a placeholder — TensorList does not carry the bundle field structure that the Python Sample does.)
std::cout << "outputs=" << out.size() << " has_tensor=" << (!out.empty() ? "yes" : "no")
<< " fields=" << 0 << "\n";
if (out.empty())
throw std::runtime_error("expected tensor output");
if (out.front().shape.empty())
throw std::runtime_error("output tensor shape is empty");
std::cout << "rank=" << out.front().shape.size() << "\n";
Run
Run the Python and C++ (prebuilt) commands from the Neat install root (the directory that contains share/ and lib/); run the build from source commands from the repo root. This chapter needs no model archive.
C++ (prebuilt):
./lib/sima-neat/tutorials/tutorial_011_interpret_model_output
C++ (build from source):
./build.sh --target tutorial_011_interpret_model_output
./build/tutorials-standalone/tutorial_011_interpret_model_output
Expected output (C++):
outputs=1 has_tensor=yes fields=0
rank=3
[OK] 011_interpret_model_output
The Python build prints the same facts through the Sample surface:
sample_kind=SampleKind.Tensor
has_tensor=True
num_fields=0
output_rank=3
To integrate this chapter's C++ source into your own project with a custom CMakeLists.txt (no extras folder required), see How to Run Tutorials on the landing page.
In Practice
A defensive checklist for reading any model's output.
Classify before you read
- Check
kindfirst. A single-tensor result isSampleKind.Tensor; a multi-field result isSampleKind.Bundle. - For tensor-kind,
tensoris present andfieldsis empty. For bundle-kind, readfieldsand do not assumetensor.
Validate the contract
- Confirm a tensor is present before dereferencing it.
- Confirm
shapeis non-empty before computing rank or indexing dimensions. - Inspect
tensor.dtypewhen the consumer expects a specific element type.
Full source
Show the complete C++ and Python programs
// Inspect a Sample returned by a Graph: kind, tensor, fields, rank.
//
// Usage:
// tutorial_011_interpret_model_output
#include "neat.h"
#include <opencv2/core.hpp>
#include <iostream>
#include <stdexcept>
int main() {
try {
cv::Mat rgb(120, 160, CV_8UC3, cv::Scalar(110, 40, 30));
if (!rgb.isContinuous())
rgb = rgb.clone();
simaai::neat::InputOptions in;
in.format = "RGB";
in.width = rgb.cols;
in.height = rgb.rows;
in.depth = rgb.channels();
simaai::neat::Graph graph;
graph.add(simaai::neat::nodes::Input(in));
graph.add(simaai::neat::nodes::Output());
// Use Graph::run(...) for a one-shot synchronous frame.
// CORE LOGIC
// Graph::run is the one-frame synchronous shortcut.
simaai::neat::TensorList out = graph.run(std::vector<cv::Mat>{rgb});
std::cout << "outputs=" << out.size() << " has_tensor=" << (!out.empty() ? "yes" : "no")
<< " fields=" << 0 << "\n";
if (out.empty())
throw std::runtime_error("expected tensor output");
if (out.front().shape.empty())
throw std::runtime_error("output tensor shape is empty");
std::cout << "rank=" << out.front().shape.size() << "\n";
std::cout << "[OK] 011_interpret_model_output\n";
return 0;
} catch (const std::exception& e) {
std::cerr << "[FAIL] " << e.what() << "\n";
return 1;
}
}