TinyML Edge Impulse Signal Callbacks: Honor Requested Sample Offsets When Paging Input Data

An Edge Impulse input adapter must return the sample range requested by the DSP, not whichever values happen to come next from its storage cursor. A callback can fill every output element and return success while providing the wrong part of an otherwise complete input window.

Treat get_data(offset, length, out_ptr) as an offset-addressed view of a logical signal. The analysis below uses official C++ SDK documentation and the official moving numpy_types.h source read on 19 October 2026. No SDK release number, storage experiment or inference test is claimed.

Preserve the coordinates supplied by the caller

The signal structure reference describes a callback that fills the requested raw-feature range, potentially through multiple calls during preprocessing or inference. The C++ integration example indexes its source with offset + i for destination element i and explicitly cautions that the application does not know each call’s exact offset and length in advance.

This interface does not give an adapter permission to ignore offset after the first call. A storage optimization must preserve the requested mapping even if an observed DSP configuration currently appears to read sequentially. An implementation change, another processing block or a different request size should not silently change what logical elements are returned.

Specify one invariant for the adapter: output element i corresponds to logical input element offset + i, for every requested element. Keep it visible in both the implementation review and test oracle.

A labelled flattened three-axis window maps requested offsets to exact element ranges; the adapter invariant is out[i] equals logical[offset+i].
Original sample-addressing fixture diagram; not a captured DSP trace.

Use a channel-labelled fixture to expose a hidden cursor

Consider a synthetic, already captured three-axis window flattened as:

[x0, y0, z0, x1, y1, z1, x2, y2, z2]

get_data(3, 3) -> [x1, y1, z1]
get_data(0, 3) -> [x0, y0, z0]
get_data(4, 2) -> [y1, z1]
get_data(3, 3) -> [x1, y1, z1]

These are deliberately chosen adapter-conformance requests, not a claim that every SDK pipeline issues this exact sequence. Give the symbolic values distinct numeric contents when implementing the fixture. A forward-only reader that advances to the next triplet after the first call fails the second request despite having all the source data available.

The partial-triplet request is useful because the callback’s coordinates address flattened values, not complete sensor records. Do not silently round offset 4 down to the start of a three-axis record, or divide it by the channel count and then forget the within-record position. The selected model’s flattening order remains unchanged in this exercise.

Page storage without changing signal semantics

A paged adapter can locate the storage page containing the requested first element, copy the available portion, and continue across pages until that logical range is supplied. Its page cache may retain useful data, but cache position must not become the authoritative signal position.

Define the backing representation before calculating any byte address. If storage holds packed integers while the callback produces floats, offset * sizeof(float) is not the storage offset. Map the logical element through the actual encoding and retain the established raw-input interpretation. This article does not change sensor scaling or preprocessing parameters.

A destructive FIFO alone does not provide the required addressable view once earlier samples have been consumed. Choose a retained view that supports the requested coordinates before connecting that source to the callback. Live capture lifetime and overwrite protection are separate design questions; the fixture here deliberately removes them by using a complete immutable window.

Bound the range in element units

The official signal declaration uses size_t offset and length with float output. total_length describes the logical full window for ordinary classification or the new slice for continuous inference. It is not the output buffer’s byte count.

For defensive adapter checks, verify that offset is no greater than the logical element count and that length is no greater than the remaining count. This subtraction-based comparison avoids overflowing an unchecked offset-plus-length calculation. Adapt error reporting to the actual SDK integration; do not return success after supplying a different range or leaving requested elements uninitialized.

Do not interpret total_length as a measured bound on cumulative storage traffic or as a guarantee that every element is requested once. Those assumptions are unnecessary to the mapping contract and can invalidate a paging optimization.

Compare the adapter before comparing classification scores

Run the same requested ranges through a straightforward RAM-backed reference and the proposed paged adapter. Compare each output element and the returned status, including repeated ranges and page crossings. Log requested offsets rather than merely counting callback calls.

Only after those checks agree should fixed-input preprocessing and inference results be compared. A success return proves what the callback reported; a plausible classifier score does not prove that the correct samples were supplied. The useful diagnosis is the first requested element that mapped incorrectly, which identifies the adapter defect without changing a model that may already be correct.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *