Skip to main content
Version: 0.8.x [Latest Beta]

Creating ImageTensors

Image data needs to be converted into an ImageTensor before it can be published into a pipeline. An ImageTensor can be created from a file or from a memory buffer.

Canonical inference format

The internal format of ImageTensors in the DENKflow library is this:

  • numeric type: float32
  • numeric range: [0, 1]
  • memory layout: Contiguous C-order BCHW
  • channel count: 3
  • channel order: BGR

Where the dimensions stand for:

  • B: Batch size
  • C: Channels
  • H: Height
  • W: Width

The file-based and generic in-memory constructors handle the conversion to this format for you. The pre-normalized in-memory constructors require the input to already match it.

Loading from a file

DENKflow loads and normalizes the image from a file, so no extra metadata arguments are required.

ImageTensor.from_file(path)

  • Input: path to one image file
  • Normalization: handled automatically
  • Result shape: [1, 3, H, W]
image_tensor = denkflow.ImageTensor.from_file("image.jpg")

ImageTensor.from_files(paths)

Batch from multiple image files. All images must have the same size.

  • Result shape: [B, 3, H, W]
image_tensor = denkflow.ImageTensor.from_files(["a.jpg", "b.jpg"])

Loading from in-memory data

Use these when the image data is already in memory — for example coming from a frame grabber, a camera SDK, an image decoded with OpenCV, or a region-of-interest cropped from another buffer.

There are three constructors that all return an ImageTensor for publishing into a pipeline:

VariantPythonC / C++Copies data?Converts data?
Genericfrom_numpydenkflow_image_tensor_from_bufferYesYes
Pre-normalized with Copyfrom_numpy_rawdenkflow_image_tensor_from_buffer_rawYesNo
Pre-normalized no Copyfrom_numpy_unsafedenkflow_image_tensor_from_buffer_unsafeNoNo

The generic variant is the default choice: the buffer / ndarray has an arbitrary data type, channel order and memory layout (e.g. uint8 BGR HWC from cv2.imread, or a uint16 HSI camera frame), and DENKflow performs the conversion to the canonical inference format internally. When using the C-API, the memory layout must always be contiguous and C-order, regardless which of the 3 functions is used.

The two pre-normalized variants skip all conversions, so the input must already be in the canonical inference format. Use the _raw flavour when you want to free or reuse the buffer immediately after the call, and the _unsafe flavour only when the additional copy is a measurable bottleneck and you can guarantee that the buffer outlives the pipeline run that consumes the tensor.

Function signatures

class ImageTensor:
@staticmethod
def from_numpy(
array: numpy.ndarray,
memory_layout: str,
channel_layout: str,
) -> ImageTensor: ...

@staticmethod
def from_numpy_raw(array: numpy.ndarray) -> ImageTensor: ...

@staticmethod
def from_numpy_unsafe(array: numpy.ndarray) -> ImageTensor: ...

The data type for from_numpy is inferred from array.dtype. The two pre-normalized variants require array.dtype == numpy.float32.

Notes

These rules apply identically to the Python and C/C++ APIs.

  • The data_type (C/C++) / array.dtype (Python) for the generic constructor must be one of: uint8, uint16, uint32, uint64, int8, int16, int32, int64, float32, float64, bool. The C short forms (u8, i16, f32, …) are also accepted. (Packed bool arrays are not supported.)
  • memory_layout is any combination of the dimension letters B (batch), C (channel), H (height) and W (width). It must contain exactly one H and one W, at most one B and at most one C. If B or C are omitted, they are assumed to be 1. The parameter is case-insensitive.
  • channel_layout is "gray" / "grayscale" (1 channel) or any 3- or 4-letter permutation of R, G, B and (optionally) A, e.g. "BGR", "RGB", "BGRA", "RGBA". The parameter is case-insensitive.
  • Common combinations:
    • Image read via OpenCV (cv2.imread / cv::Mat::data): memory_layout="HWC", channel_layout="BGR".
    • Image read via PIL (Image.open(...).convert("RGB") then np.asarray): memory_layout="HWC", channel_layout="RGB".
  • The pre-normalized constructors (from_numpy_raw, from_numpy_unsafe, denkflow_image_tensor_from_buffer_raw, denkflow_image_tensor_from_buffer_unsafe) perform no conversions or sanity checks on the array contents. These functions are not guaranteed to throw an error. Passing data that is not normalized produces incorrect inference results. The following is required of the input:
    • The data type must be 32-bit float (Python: numpy.float32)
    • All values must be within the range [0, 1]
    • Memory layout must be BCHW
    • Memory layout must be contiguous
    • Must have 3 channels
    • Channel order must be BGR
  • The _unsafe variants do not copy the input buffer; they borrow it. You must keep the buffer alive (and untouched by other writers) until the pipeline run that consumes the tensor has returned. Violating this invariant is undefined behaviour.

Example: All three constructors side by side

import numpy as np
import denkflow

# Common image dimensions for this example.
batch, channels, height, width = 1, 3, 480, 640

# ----------------------------------------------------------------------
# 1) ImageTensor.from_numpy
# Typical case: a uint8 BGR array with HWC layout, e.g. the output
# of `cv2.imread`. DENKflow converts the dtype (uint8 → float32 in
# [0, 1]), reorders the channels (BGR is already correct here) and
# transposes HWC → BCHW.
# ----------------------------------------------------------------------
# In practice this would be `cv2.imread("path/to/image.jpg")`:
opencv_like_array = np.zeros((height, width, channels), dtype=np.uint8)

tensor_from_numpy = denkflow.ImageTensor.from_numpy(
opencv_like_array,
memory_layout="HWC", # "HWC" with an implicit batch is fine; "BHWC" would be equivalent
channel_layout="BGR",
)

# `tensor_from_numpy` is now ready to be published into a pipeline.

# ----------------------------------------------------------------------
# 2) ImageTensor.from_numpy_raw
# Your data is already a normalized float32 BGR BCHW array (e.g.
# produced by your own preprocessing pipeline). DENKflow skips all
# conversions but still copies the data so you can free / reuse the
# array immediately after the call.
# ----------------------------------------------------------------------
float_bchw_array = np.zeros((batch, channels, height, width), dtype=np.float32)
# ... fill `float_bchw_array` with values in [0, 1], in BGR BCHW order ...

tensor_from_raw = denkflow.ImageTensor.from_numpy_raw(float_bchw_array)

# `float_bchw_array` may be freed or reused now.

# ----------------------------------------------------------------------
# 3) ImageTensor.from_numpy_unsafe
# Same input requirements as `from_numpy_raw`, but DENKflow does NOT
# copy the data — it borrows the underlying buffer. You MUST keep
# the array alive (and untouched) until the pipeline run that
# consumes the tensor has finished.
# ----------------------------------------------------------------------
persistent_float_array = np.zeros((batch, channels, height, width), dtype=np.float32)
# ... fill with values in [0, 1], in BGR BCHW order ...

tensor_from_unsafe = denkflow.ImageTensor.from_numpy_unsafe(persistent_float_array)

# IMPORTANT: do NOT modify or let `persistent_float_array` go out of scope
# until the pipeline run that consumes `tensor_from_unsafe` has returned.

# From here on, all three tensors can be used identically:
# pipeline.publish_image_tensor(input_topic, tensor)

Publishing into a pipeline

Once you have an ImageTensor, publish it into the pipeline's input topic. Publishing transfers ownership of the tensor — it cannot be used afterwards.

pipeline.publish_image_tensor("camera/image", image_tensor)

Which constructor should you pick?

  • For input you have on disk, use from_file / denkflow_image_tensor_from_file (or the _files plural variants for same-sized batches).
  • For in-memory data, default to the generic in-memory constructor (from_numpy / FromImageData / denkflow_image_tensor_from_buffer). The conversion cost is small relative to model inference and it eliminates an entire class of "wrong color order" / "wrong dtype" / "wrong layout" bugs.
  • Switch to from_numpy_raw / denkflow_image_tensor_from_buffer_raw only after you've already produced canonical BGR BCHW float32 data yourself and want to drop the redundant conversion step.
  • Switch to from_numpy_unsafe / denkflow_image_tensor_from_buffer_unsafe only when the extra copy performed by the _raw flavour is a measurable bottleneck and you can guarantee that the buffer stays alive and untouched until the pipeline run consuming the tensor has returned.