# DENKflow SDK - LLM skills reference

DenkFlow is a pipeline-based inference SDK by DENKweit. Pipelines either load exported `.denkflow` graphs from the Vision AI Hub or are built manually from `.denkmodel` files. The common lifecycle is:

**create/load -> optionally override runtime -> initialize -> subscribe -> publish -> run/start -> receive**

## Concepts

- **Pipeline**: the inference graph. Add or reconfigure nodes before `initialize()`.
- **InitializedPipeline**: the executable pipeline handle after initialization.
- **Node**: one processing step such as resize, detection, classification, OCR, segmentation, anomaly detection, bounding box filtering, bounding box class filtering, image patches, const tensor, or virtual camera.
- **Topic**: a named channel connecting nodes, usually shaped like `node_name/output_name`.
- **Tensor**: payload type such as `ImageTensor`, `BoundingBoxTensor`, `ScalarTensor`, `OcrTensor`, `SegmentationMaskTensor`, or `InstanceSegmentationMaskTensor`.
- **Receiver / TensorReceiver**: subscribes to a topic and lets you pull typed results after a run.
- **LicenseSource**: `HubLicenseSource` (online) or `OneTimeLicenseSource` (offline after first activation). Created from a personal access token (PAT) and optional license ID.

## Environment variables

| Variable                   | Purpose                                                                                                   |
| -------------------------- | --------------------------------------------------------------------------------------------------------- |
| `DENKFLOW_DATA_DIRECTORY`  | Overrides where license state, TensorRT cache, and OpenVINO cache are stored. On Linux the default is `$HOME/.config/denkflow` (often `/root/.config/denkflow` in containers). In Docker you can mount a host volume at that default path and omit this variable. |
| `ORT_DYLIB_PATH`           | Override path to the ONNX Runtime shared library if auto-detection fails.                                 |
| `DENKFLOW_ENABLE_ORT_LOGS` | Set to enable ONNX Runtime log output.                                                                    |

## Execution providers

Model-backed nodes and some processing nodes accept an execution provider string:

| String     | Target           |
| ---------- | ---------------- |
| `cpu`      | CPU (default)    |
| `cuda`     | NVIDIA CUDA      |
| `tensorrt` | NVIDIA TensorRT  |
| `openvino` | Intel OpenVINO   |
| `directml` | Windows DirectML |

Device ID rules:

- `cpu`: usually `0` and ignored
- `cuda`, `tensorrt`, `directml`: GPU index `0`, `1`, ...
- `openvino`: `-1` for CPU, `>= 0` for GPU index, `-2` for NPU

## Inspecting and overriding exported pipelines

Exported `.denkflow` files already carry runtime intent from the export target, but you can still inspect them and override node placement before initialization.

- Python: before `initialize()` — `pipeline.get_node_names()`, `pipeline.set_node_device(node_name, execution_provider, device_id)`. After `initialize()` — `pipeline.get_topics_for_pipeline_input()`, `pipeline.get_topics_for_pipeline_output()` (topic lists are only available on an initialized pipeline).
- C-API: `denkflow_pipeline_get_node_names(...)`, `denkflow_pipeline_set_node_device(...)`, then after initialization `denkflow_initialized_pipeline_get_topics_for_pipeline_input(...)` and `denkflow_initialized_pipeline_get_topics_for_pipeline_output(...)`

Use runtime overrides before initialization:

```python
pipeline = Pipeline.from_denkflow("model.denkflow", pat="YOUR-PAT")
print(pipeline.get_node_names())
pipeline.set_node_device("detection_node", "cuda", 0)
pipeline.initialize()
print(pipeline.get_topics_for_pipeline_input())
print(pipeline.get_topics_for_pipeline_output())
```

---

## Python

### Install

```bash
pip install denkflow
pip install denkflow[gpu]
pip install denkflow[openvino]
pip install denkflow[directml]
```

### Load a `.denkflow` export

```python
from denkflow import Pipeline, ImageTensor

pipeline = Pipeline.from_denkflow("model.denkflow", pat="YOUR-PAT")
# Optional kwargs when using pat (not with license_source): endpoint=..., license_id=..., one_time_registration=True
# or with an explicit license source:
# from denkflow import HubLicenseSource
# license = HubLicenseSource.from_pat("PAT", license_id="LICENSE-ID")
# pipeline = Pipeline.from_denkflow("model.denkflow", license_source=license)

print(pipeline.get_node_names())
pipeline.set_node_device("detection_node", "cuda", 0)  # optional override (before initialize)
pipeline.initialize()

print(pipeline.get_topics_for_pipeline_input())
print(pipeline.get_topics_for_pipeline_output())

receiver = pipeline.subscribe("bounding_box_filter_node/filtered_bounding_boxes")
pipeline.publish_image_tensor("camera/image", ImageTensor.from_file("image.jpg"))
pipeline.run()

results = receiver.receive_bounding_box_tensor().to_objects(0.5)
for result in results:
    print(result.class_label.name, result.confidence)
```

### Build a custom pipeline

```python
from denkflow import Pipeline, ImageTensor, HubLicenseSource
import numpy as np

license_source = HubLicenseSource.from_pat("PAT", license_id="LIC-ID")
pipeline = Pipeline()

resize_shape = pipeline.add_const_tensor_node(
    "resize-shape",
    np.array([1088, 1280], dtype=np.int64),
)
resize = pipeline.add_image_resize_node(
    "resize",
    "input/image",
    resize_shape.output,
    resize_mode="CenterPadBlack",
    execution_provider="cpu",
    device_id=0,
)
detector = pipeline.add_object_detection_node(
    "detector",
    resize.output,
    "od.denkmodel",
    license_source,
    execution_provider="cuda",
    device_id=0,
    scale_bounding_boxes=True,
)
filtered = pipeline.add_bounding_box_filter_node(
    "filter",
    detector.output,
    iou_threshold_source=0.5,
    score_threshold_source=0.5,
)

pipeline.initialize()

receiver = pipeline.subscribe(filtered.output)
pipeline.publish_image_tensor("input/image", ImageTensor.from_file("image.jpg"))
pipeline.run()
boxes = receiver.receive_bounding_box_tensor().to_objects(0.5)
```

### Pipeline methods

| Method                                                                                                                                                                                      | Notes                                                                                       |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
| `Pipeline.from_denkflow(path, *, pat=..., license_source=..., endpoint=..., license_id=..., one_time_registration=False)`                                                                     | Load an exported pipeline; use either `pat` or `license_source`, not both                   |
| `set_node_device(node_name, execution_provider, device_id)`                                                                                                                                 | Override one node before `initialize()`                                                     |
| `get_node_names()`                                                                                                                                                                          | Inspect node names on an uninitialized pipeline                                             |
| `get_topics_for_pipeline_input()`                                                                                                                                                           | Inspect publishable input topics (after `initialize()` only)                                |
| `get_topics_for_pipeline_output()`                                                                                                                                                          | Inspect subscribable output topics (after `initialize()` only)                             |
| `add_const_tensor_node(name, np_array)`                                                                                                                                                     | Constant tensor node                                                                        |
| `add_virtual_camera_node(name, folder)`                                                                                                                                                     | Emits `ImageTensor` frames from a folder                                                    |
| `add_image_resize_node(name, image_src, size_src, *, resize_mode="CenterPadBlack", execution_provider="cpu", device_id=0)`                                                                              | Resize node; `resize_mode` is one of `"CenterPadBlack"`, `"Stretch"`, or `"CenterPadWhite"` |
| `add_object_detection_node(name, image_src, model, license, *, execution_provider="cpu", device_id=0, scale_bounding_boxes=True)`                                                                       | Object detection node                                                                       |
| `add_image_classification_node(name, image_src, model, license, *, execution_provider="cpu", device_id=0)`                                                                                              | Classification node                                                                         |
| `add_bounding_box_filter_node(name, bbox_src, *, iou_threshold_source=None, score_threshold_source=None, execution_provider="cpu", device_id=0)`                                                        | NMS / score filtering                                                                       |
| `add_bounding_box_class_filter_node(name, bounding_boxes_source, class_indices_source, *, execution_provider="cpu", device_id=0)`                                                                       | Filter detections by an allow-list of class indices                                         |
| `add_image_patches_node(name, image_src, bbox_src, size_src_or_tuple, *, resize_mode="CenterPadBlack", resize_method="Bilinear", bounding_box_extend_ratio=0.0, execution_provider="cpu", device_id=0)` | Extract and resize crops                                                                    |
| `change_image_patches_node(name, *, resize_mode=None, resize_method=None, bounding_box_extend_ratio=None)`                                                                                              | Reconfigure an image-patches node before `initialize()`                                     |
| `add_ocr_node(name, image_src, model, license, *, execution_provider="cpu", device_id=0)`                                                                                                               | OCR node                                                                                    |
| `add_image_segmentation_node(name, image_src, model, license, *, execution_provider="cpu", device_id=0)`                                                                                                | Semantic segmentation                                                                       |
| `add_image_instance_segmentation_node(name, image_src, model, license, *, execution_provider="cpu", device_id=0)`                                                                                       | Instance segmentation                                                                       |
| `add_image_anomaly_detection_node(name, image_src, model, license, *, execution_provider="cpu", device_id=0)`                                                                                           | Anomaly score + mask                                                                        |

`resize_mode` (shared by the resize, image-patches, and `change_image_patches_node` calls) is a string — one of `"CenterPadBlack"`, `"Stretch"`, or `"CenterPadWhite"`. `resize_method` is a string — one of `"Nearest"`, `"Bilinear"`, `"Bicubic"`, or `"Area"`.

### Receiver methods

Subscribe with `pipeline.subscribe(topic)`. Common typed receives:

- `receiver.receive_image_tensor()`
- `receiver.receive_bounding_box_tensor()` -> `.to_objects(confidence_threshold)`
- `receiver.receive_scalar_tensor()` -> `.to_objects()`
- `receiver.receive_ocr_tensor()` -> `.to_objects()`
- `receiver.receive_segmentation_mask_tensor()` -> `.to_objects(threshold)`
- `receiver.receive_instance_segmentation_mask_tensor()` -> `.to_objects(bbox_tensor, seg_threshold, conf_threshold)`

Useful decoded result shapes:

- bounding boxes: `.x1`, `.y1`, `.x2`, `.y2`, `.confidence`, `.class_label.name`
- scalar results: `.value`, `.class_label`

### `ImageTensor` creation

```python
# From files
ImageTensor.from_file("path.jpg")
ImageTensor.from_files(["a.jpg", "b.jpg"])

# Generic numpy constructor: data type is inferred from arr.dtype
ImageTensor.from_numpy(arr, memory_layout="HWC", channel_layout="BGR")
ImageTensor.from_numpy(arr, memory_layout="BCHW", channel_layout="BGR")

# Already pre-normalized to BGR / BCHW / float32 — copies internally
ImageTensor.from_numpy_raw(float32_bgr_bchw_array)

# Already pre-normalized to BGR / BCHW / float32 — zero-copy, the array
# MUST stay alive and unmodified for the lifetime of the pipeline run.
ImageTensor.from_numpy_unsafe(float32_bgr_bchw_array)

# Useful methods on an ImageTensor
tensor.to_images()  # -> list of RGB uint8 numpy arrays
tensor.hash()       # -> stable bytes hash of tensor contents
```

`memory_layout` is a string composed of `B`, `C`, `H`, `W` (e.g. `"HWC"`, `"BHWC"`, `"BCHW"`).
`channel_layout` is `"GRAY"` or any combination of `R`, `G`, `B`, `A` (e.g. `"BGR"`, `"RGB"`, `"RGBA"`).

`from_numpy_opencv` no longer exists; use `from_numpy(..., memory_layout="HWC", channel_layout="BGR")` instead.

### Licensing

```python
from denkflow import HubLicenseSource

license_source = HubLicenseSource.from_pat("PAT", license_id="LICENSE-ID")

offline = license_source.to_one_time_license_source()
offline.refresh()
```

### Logging

```python
from denkflow import set_log_level

set_log_level("DEBUG")  # ERROR, WARN, INFO, DEBUG, TRACE
```

---

## C / C++

### Install

Download the release for your platform from [DENKflow-C-API Releases](https://github.com/DENKweitGmbH/DENKflow-C-API/releases). You get `denkflow.h` plus the shared library (`libdenkflow.so` / `libdenkflow.dll`).

```bash
g++ main.cpp -I./include -L./lib -ldenkflow -Wl,-rpath,'$ORIGIN/lib' -o app
```

### Naming conventions

- All public functions are prefixed with `denkflow_` (e.g. `denkflow_pipeline_new`).
- All public types are prefixed with `Denkflow` (e.g. `DenkflowPipeline`, `DenkflowReceiverTensor`, `DenkflowResizeMode`).
- Enum variants use the `Type_Variant` form (e.g. `DenkflowResizeMode_CenterPadBlack`, `DenkflowResult_Ok`).
- The error buffer size constant is `DENKFLOW_ERROR_BUFFER_SIZE` (an `extern const`, not a compile-time constant).
- Optional string arguments accept `NULL` / `nullptr` directly; the legacy `NULL_BYTE` sentinel has been removed.

### Error handling pattern

Every C-API function returns `DenkflowResult`. Always check it:

```c
#include <stdio.h>
#include <stdlib.h>
#include "denkflow.h"

void handle_error(enum DenkflowResult code, const char* fn) {
    if (code != DenkflowResult_Ok) {
        char* buf = (char*)malloc(DENKFLOW_ERROR_BUFFER_SIZE);
        denkflow_get_last_error(buf);
        printf("%s failed: %s\n", fn, buf);
        free(buf);
        exit(1);
    }
}
```

### Load a `.denkflow` export

```c
DenkflowPipeline*            pipeline    = NULL;
DenkflowInitializedPipeline* initialized = NULL;
DenkflowHubLicenseSource*    license     = NULL;
DenkflowReceiverTensor*      receiver    = NULL;
DenkflowImageTensor*         image       = NULL;
DenkflowBoundingBoxTensor*   tensor      = NULL;
DenkflowBoundingBoxResults*  results     = NULL;

handle_error(denkflow_hub_license_source_from_pat(&license, "PAT", "LICENSE-ID", NULL),
             "denkflow_hub_license_source_from_pat");
handle_error(denkflow_pipeline_from_denkflow(&pipeline, "model.denkflow", (void*)license),
             "denkflow_pipeline_from_denkflow");

/* optional override before initialization */
handle_error(denkflow_pipeline_set_node_device(pipeline, "detection_node", "cuda", 0),
             "denkflow_pipeline_set_node_device");

handle_error(denkflow_initialize_pipeline(&initialized, &pipeline),
             "denkflow_initialize_pipeline");
handle_error(denkflow_initialized_pipeline_subscribe(&receiver, initialized,
                 "bounding_box_filter_node/filtered_bounding_boxes"),
             "denkflow_initialized_pipeline_subscribe");
handle_error(denkflow_image_tensor_from_file(&image, "image.jpg"),
             "denkflow_image_tensor_from_file");
handle_error(denkflow_initialized_pipeline_publish_image_tensor(initialized, "camera/image", &image),
             "denkflow_initialized_pipeline_publish_image_tensor");
handle_error(denkflow_initialized_pipeline_run(initialized, 8000),
             "denkflow_initialized_pipeline_run");
handle_error(denkflow_receiver_receive_bounding_box_tensor(&tensor, receiver),
             "denkflow_receiver_receive_bounding_box_tensor");
handle_error(denkflow_bounding_box_tensor_to_objects(&results, tensor, 0.5f),
             "denkflow_bounding_box_tensor_to_objects");

for (size_t i = 0; i < results->bounding_boxes_length; i++) {
    printf("%s: %f\n",
           results->bounding_boxes[i].class_label.name,
           results->bounding_boxes[i].confidence);
}

denkflow_free_object((void**)&results);
denkflow_free_object((void**)&tensor);
denkflow_free_object((void**)&receiver);
denkflow_free_object((void**)&initialized);
denkflow_free_object((void**)&license);
```

### Build a custom pipeline

The C-API supports custom pipeline construction.

```c
DenkflowPipeline*                          pipeline    = NULL;
DenkflowHubLicenseSource*                  license     = NULL;
DenkflowConstTensorNodeReference*          size        = NULL;
DenkflowImageResizeNodeReference*          resize      = NULL;
DenkflowImageObjectDetectionNodeReference* detector    = NULL;
DenkflowInitializedPipeline*               initialized = NULL;
DenkflowReceiverTensor*                    receiver    = NULL;
DenkflowImageTensor*                       image       = NULL;
DenkflowBoundingBoxTensor*                 tensor      = NULL;

int64_t resize_shape[2] = {1088, 1280};

handle_error(denkflow_hub_license_source_from_pat(&license, "PAT", "LICENSE-ID", NULL),
             "denkflow_hub_license_source_from_pat");
handle_error(denkflow_pipeline_new(&pipeline), "denkflow_pipeline_new");
handle_error(denkflow_pipeline_add_const_tensor_node_int(&size, pipeline, "resize-shape",
                                                         resize_shape, 2),
             "denkflow_pipeline_add_const_tensor_node_int");
handle_error(denkflow_pipeline_add_image_resize_node(
                 &resize, pipeline, "resize", "input/image", size->output,
                 DenkflowResizeMode_CenterPadBlack, "cpu", 0),
             "denkflow_pipeline_add_image_resize_node");
handle_error(denkflow_pipeline_add_image_object_detection_node(
                 &detector, pipeline, "detector", resize->output, "od.denkmodel",
                 (void*)license, "cuda", 0),
             "denkflow_pipeline_add_image_object_detection_node");

handle_error(denkflow_initialize_pipeline(&initialized, &pipeline),
             "denkflow_initialize_pipeline");
handle_error(denkflow_initialized_pipeline_subscribe(&receiver, initialized, detector->output),
             "denkflow_initialized_pipeline_subscribe");
handle_error(denkflow_image_tensor_from_file(&image, "image.jpg"),
             "denkflow_image_tensor_from_file");
handle_error(denkflow_initialized_pipeline_publish_image_tensor(initialized, "input/image", &image),
             "denkflow_initialized_pipeline_publish_image_tensor");
handle_error(denkflow_initialized_pipeline_run(initialized, 8000),
             "denkflow_initialized_pipeline_run");
handle_error(denkflow_receiver_receive_bounding_box_tensor(&tensor, receiver),
             "denkflow_receiver_receive_bounding_box_tensor");
```

### Key C-API functions

| Function                                                                                                                                                              | Purpose                                                                                                                   |
| --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| `denkflow_hub_license_source_from_pat(&out, pat, license_id_or_NULL, endpoint_or_NULL)`                                                                               | Create a hub license source                                                                                               |
| `denkflow_pipeline_new(&out)`                                                                                                                                         | Create an empty pipeline                                                                                                  |
| `denkflow_pipeline_from_denkflow(&out, path, license)`                                                                                                                | Load an exported `.denkflow` pipeline                                                                                     |
| `denkflow_pipeline_get_node_names(&out, pipeline)`                                                                                                                    | Inspect node names before initialization                                                                                  |
| `denkflow_pipeline_set_node_device(pipeline, node_name, execution_provider, device_id)`                                                                               | Override one node before initialization                                                                                   |
| `denkflow_pipeline_set_channel_size(pipeline, channel_size)`                                                                                                          | Set pipeline channel buffer size                                                                                          |
| `denkflow_pipeline_with_intra_threads(pipeline, intra_threads)`                                                                                                       | Configure ONNX intra-op threads                                                                                           |
| `denkflow_pipeline_with_inter_threads(pipeline, inter_threads)`                                                                                                       | Configure ONNX inter-op threads                                                                                           |
| `denkflow_pipeline_add_const_tensor_node_int(&out, pipeline, node_name, values, len)`                                                                                 | Add integer const tensor node                                                                                             |
| `denkflow_pipeline_add_const_tensor_node_uint(&out, pipeline, node_name, values, len)`                                                                                | Add unsigned const tensor node                                                                                            |
| `denkflow_pipeline_add_const_tensor_node_float(&out, pipeline, node_name, values, len)`                                                                               | Add float const tensor node                                                                                               |
| `denkflow_pipeline_add_virtual_camera_node(&out, pipeline, node_name, folder)`                                                                                        | Add virtual camera node                                                                                                   |
| `denkflow_pipeline_add_image_resize_node(&out, pipeline, node_name, image_topic, target_size_topic, resize_mode, execution_provider, device_id)`                      | Add resize node (`resize_mode` is `DenkflowResizeMode`)                                                                   |
| `denkflow_pipeline_add_image_object_detection_node(&out, pipeline, node_name, image_topic, model_path, license, execution_provider, device_id)`                       | Add object detection node                                                                                                 |
| `denkflow_pipeline_add_image_classification_node(&out, pipeline, node_name, image_topic, model_path, license, execution_provider, device_id)`                         | Add classification node                                                                                                   |
| `denkflow_pipeline_add_bounding_box_filter_node(&out, pipeline, node_name, bbox_topic, iou_topic, score_topic, execution_provider, device_id)`                        | Add bounding box (NMS / score) filter node                                                                                |
| `denkflow_pipeline_add_bounding_box_class_filter_node(&out, pipeline, node_name, bounding_boxes_topic, class_indices_topic, execution_provider, device_id)`           | Add a class-allow-list filter node                                                                                        |
| `denkflow_pipeline_add_image_patches_node(&out, pipeline, node_name, image_topic, bbox_topic, size_topic, resize_mode, resize_method, execution_provider, device_id)` | Add image patches node (`resize_mode` is `DenkflowResizeMode`, `resize_method` is `DenkflowImagePatchesNodeResizeMethod`) |
| `denkflow_pipeline_change_image_patches_node(pipeline, node_name, params)`                                                                                            | Reconfigure an image-patches node before initialization (`params` is `DenkflowImagePatchesNodeParams`)                    |
| `denkflow_pipeline_add_ocr_node(&out, pipeline, node_name, image_topic, model_path, license, execution_provider, device_id)`                                          | Add OCR node                                                                                                              |
| `denkflow_pipeline_add_image_segmentation_node(&out, pipeline, node_name, image_topic, model_path, license, execution_provider, device_id)`                           | Add semantic segmentation node                                                                                            |
| `denkflow_pipeline_add_image_instance_segmentation_node(&out, pipeline, node_name, image_topic, model_path, license, execution_provider, device_id)`                  | Add instance segmentation node                                                                                            |
| `denkflow_pipeline_add_image_anomaly_detection_node(&out, pipeline, node_name, image_topic, model_path, license, execution_provider, device_id)`                      | Add anomaly detection node                                                                                                |
| `denkflow_initialize_pipeline(&out, &pipeline)`                                                                                                                       | Initialize and consume the pipeline pointer                                                                               |
| `denkflow_initialized_pipeline_get_topics_for_pipeline_input(&out, initialized)`                                                                                      | Inspect publishable input topics                                                                                          |
| `denkflow_initialized_pipeline_get_topics_for_pipeline_output(&out, initialized)`                                                                                     | Inspect subscribable output topics                                                                                        |
| `denkflow_initialized_pipeline_subscribe(&out, initialized, topic)`                                                                                                   | Subscribe to an output topic                                                                                              |
| `denkflow_image_tensor_from_file(&out, path)`                                                                                                                         | Load one image from a file                                                                                                |
| `denkflow_image_tensor_from_files(&out, paths, count)`                                                                                                                | Load an image batch                                                                                                       |
| `denkflow_image_tensor_from_buffer(&out, buffer, data_type, batch, w, h, ch, memory_layout, channel_layout)`                                                          | Build from raw bytes with on-the-fly conversion                                                                           |
| `denkflow_image_tensor_from_buffer_raw(&out, float_buffer, batch, ch, h, w)`                                                                                          | Build from pre-normalized BGR/BCHW float32, copies internally                                                             |
| `denkflow_image_tensor_from_buffer_unsafe(&out, float_buffer, batch, ch, h, w)`                                                                                       | Build from pre-normalized BGR/BCHW float32, NO copy (caller manages lifetime)                                             |
| `denkflow_initialized_pipeline_publish_image_tensor(initialized, topic, &image)`                                                                                      | Publish image input and consume the image pointer                                                                         |
| `denkflow_initialized_pipeline_run(initialized, timeout_ms)`                                                                                                          | Run once                                                                                                                  |
| `denkflow_initialized_pipeline_start(initialized, timeout_ms)`                                                                                                        | Run in a loop                                                                                                             |
| `denkflow_initialized_pipeline_cancel(initialized)`                                                                                                                   | Cancel a running loop                                                                                                     |
| `denkflow_receiver_receive_image_tensor(&out, receiver)`                                                                                                              | Receive raw image output                                                                                                  |
| `denkflow_receiver_receive_bounding_box_tensor(&out, receiver)`                                                                                                       | Receive detection output                                                                                                  |
| `denkflow_receiver_receive_scalar_tensor(&out, receiver)`                                                                                                             | Receive classification output                                                                                             |
| `denkflow_receiver_receive_ocr_tensor(&out, receiver)`                                                                                                                | Receive OCR output                                                                                                        |
| `denkflow_receiver_receive_segmentation_mask_tensor(&out, receiver)`                                                                                                  | Receive segmentation output                                                                                               |
| `denkflow_receiver_receive_instance_segmentation_mask_tensor(&out, receiver)`                                                                                         | Receive instance segmentation output                                                                                      |
| `denkflow_bounding_box_tensor_to_objects(&out, tensor, threshold)`                                                                                                    | Decode bounding boxes                                                                                                     |
| `denkflow_scalar_tensor_to_objects(&out, tensor)`                                                                                                                     | Decode scalar results                                                                                                     |
| `denkflow_ocr_tensor_to_objects(&out, tensor)`                                                                                                                        | Decode OCR strings                                                                                                        |
| `denkflow_free_object((void**)&ptr)`                                                                                                                                  | Free any allocated object                                                                                                 |
| `denkflow_get_last_error(buffer)`                                                                                                                                     | Write the most recent error message into `buffer` (must be at least `DENKFLOW_ERROR_BUFFER_SIZE` bytes)                   |
| `denkflow_set_log_level(level)`                                                                                                                                       | Set log level: `"ERROR"`, `"WARN"`, `"INFO"`, `"DEBUG"`, `"TRACE"`                                                        |

### Memory management

Every pointer returned by the C-API must eventually be freed with `denkflow_free_object((void**)&ptr)`. Publishing an image tensor consumes the pointer and sets it to `NULL`. Initializing a pipeline consumes the `DenkflowPipeline*` and returns a `DenkflowInitializedPipeline*`.

### `ImageTensor` raw-data string arguments

The `denkflow_image_tensor_from_buffer` constructor takes the data type, memory layout, and channel layout as plain strings:

- `data_type`: `"uint8"`, `"uint16"`, `"float32"`
- `memory_layout`: any combination of `B`, `C`, `H`, `W` (e.g. `"HWC"`, `"BHWC"`, `"BCHW"`)
- `channel_layout`: `"GRAY"` or any combination of `R`, `G`, `B`, `A` (e.g. `"BGR"`, `"RGB"`, `"RGBA"`)

The `_raw` and `_unsafe` constructors do not take these strings — they require the buffer to already be `BGR / BCHW / float32`.

---

## Common topic names

Typical topics in exported `.denkflow` graphs:

| Kind                    | Typical topic                                      |
| ----------------------- | -------------------------------------------------- |
| Input image             | `camera/image`                                     |
| Object detection output | `bounding_box_filter_node/filtered_bounding_boxes` |
| Classification output   | `classification_node/output`                       |
| OCR output              | `ocr_node/output`                                  |
| Segmentation output     | `segmentation_node/output`                         |

Do not hardcode topic names if you can inspect them. Prefer:

- Python: after `initialize()`, `get_topics_for_pipeline_input()` and `get_topics_for_pipeline_output()`
- C-API: `denkflow_initialized_pipeline_get_topics_for_pipeline_input(...)` and `denkflow_initialized_pipeline_get_topics_for_pipeline_output(...)`
