Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
7a6067b
MINIFICPP-2892 Tensor extensions based on sonos/tract
martinzink Jul 8, 2026
48e5fbd
review changes
martinzink Sep 9, 2026
85d6e00
ruff fix
martinzink Sep 9, 2026
ab8d20b
fix cargo clippy
martinzink Sep 9, 2026
0c40fc4
review changes
martinzink Sep 9, 2026
a26fdf4
fix
martinzink Sep 9, 2026
8236c16
fix panic on malformed tensor payload in tensor_helpers.rs
martinzink Sep 11, 2026
79b5e42
fix doc drift in descriptions
martinzink Sep 11, 2026
8888fe5
fix 0/0 in IoU
martinzink Sep 11, 2026
fd12f44
classify_image.rs Failure description fix
martinzink Sep 11, 2026
6e74545
remove code duplication from InvokeTract model and add result_via_out…
martinzink Sep 11, 2026
e71ff9d
rebase fix
martinzink Sep 11, 2026
e2ee40b
Cargo.toml doesnt need explicit all features
martinzink Sep 11, 2026
a11126f
fix image_to_tensor.rs
martinzink Sep 14, 2026
24faa3a
respect original image format in draw_bounding_box.rs
martinzink Sep 14, 2026
86e4c40
rebase fix
martinzink Sep 14, 2026
8869f6d
fixing opus 5's findings
martinzink Sep 23, 2026
a91ba3f
trim labels both direction
martinzink Sep 23, 2026
30ceaba
adding missing TENSOR_BYTES_ATTR to image_to_tensor_def.rs
martinzink Sep 24, 2026
7778930
"certifi>=2026.7.22"
martinzink Sep 25, 2026
e8eb801
Feature: Face detection with UltraFace Single Shot MultiBox Detector …
martinzink Sep 25, 2026
c870664
added missing apache license
martinzink Sep 25, 2026
b7c7b0a
output attrs changes,
martinzink Sep 25, 2026
31dd8a1
LineColor changes
martinzink Sep 25, 2026
39c6aed
labelsproperty
martinzink Sep 25, 2026
6388a2c
Apply batched suggestions from code review
martinzink Sep 25, 2026
eacaf7b
#[strum(serialize = "Grayscale")]
martinzink Sep 25, 2026
22c099c
sigmoid tests in classify_output.rs
martinzink Sep 25, 2026
18e44c1
move test_resources into target folder
martinzink Sep 25, 2026
101a217
folded label_index_offset into labels
martinzink Sep 25, 2026
cd55887
docs update
martinzink Sep 25, 2026
2b6d198
refresh manifest
martinzink Sep 25, 2026
d0e8d73
fix flaky docker tests
martinzink Sep 28, 2026
3c66a56
Apply batched suggestions from code review
martinzink Sep 28, 2026
a610e5f
review changes
martinzink Sep 28, 2026
9fbcfe6
rebase fix
martinzink Sep 29, 2026
568f895
review changes
martinzink Sep 30, 2026
041c432
detection.feature fixes
martinzink Sep 30, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
625 changes: 625 additions & 0 deletions .github/references/ubuntu_22_04_clang_arm_manifest.json

Large diffs are not rendered by default.

17 changes: 17 additions & 0 deletions CONTROLLERS.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ limitations under the License.
- [RocksDbStateStorage](#RocksDbStateStorage)
- [SmbConnectionControllerService](#SmbConnectionControllerService)
- [SSLContextService](#SSLContextService)
- [TractModelService](#TractModelService)
- [UpdatePolicyControllerService](#UpdatePolicyControllerService)
- [VolatileMapStateStorage](#VolatileMapStateStorage)
- [XMLReader](#XMLReader)
Expand Down Expand Up @@ -359,6 +360,22 @@ In the list below, the names of required properties appear in bold. Any other pr
| Use System Cert Store | false | true<br/>false | Whether to use the certificates in the OS's certificate store |


## TractModelService

### Description

Provides a shared, CPU-optimized neural network for inference. Supports ONNX (`.onnx`) and NNEF (directory or tarball) models; the format can be auto-detected from the file extension or set explicitly.

### Properties

In the list below, the names of required properties appear in bold. Any other properties (not in bold) are considered optional. The table also indicates any default values, and whether a property supports the NiFi Expression Language.

| Name | Default Value | Allowable Values | Description |
|---------------------|---------------|------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **Model File Path** | | | Absolute path to the model on the edge device. For ONNX this is a `.onnx` file; for NNEF this is a `.nnef.tgz` archive, a `.nnef` tarball, or the root directory of an unpacked NNEF model. The model is loaded, parsed, and compiled for the host CPU once when the controller service is enabled; subsequent inference calls reuse the compiled runnable. |
| **Model format** | Auto | Auto<br/>Onnx<br/>Nnef | Format of the file/directory referenced by 'Model File Path'. 'Auto' picks Onnx when the path ends in `.onnx` and Nnef when it ends in `.nnef`, `.nnef.tgz`, `.nnef.tar`, `.nnef.tar.gz`, or points at a directory. Set explicitly when the path uses a non-standard extension. |


## UpdatePolicyControllerService

### Description
Expand Down
190 changes: 190 additions & 0 deletions PROCESSORS.md

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion behave_framework/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ dependencies = [
"PyYAML==6.0.3",
"humanfriendly==10.0",
"cryptography==50.0.0",
"pyjks==20.0.0"
"pyjks==20.0.0",
"certifi>=2026.7.22"
]

[tool.setuptools]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use crate::processors::asciify_german::relationships::FAILURE;
use minifi_native::macros::ComponentIdentifier;
use minifi_native::{
FlowFileStreamTransform, GetProperty, InputStream, Logger, MinifiError, OutputStream,
ProcessError, RouteErrorExt, Schedule, TransformStreamResult,
ProcessError, Schedule, TransformStreamResult,
};

mod relationships;
Expand Down Expand Up @@ -56,8 +56,9 @@ impl FlowFileStreamTransform for AsciifyGerman {
0xC3 => {
let mut next = [0u8; 1];
if input_stream.read(&mut next)? == 0 {
Err(MinifiError::custom("Truncated multi-byte sequence at EOF"))
.route_err_to_failure()?
return Err(ProcessError::route_to_failure(
"Truncated multi-byte sequence at EOF",
));
}
match next[0] {
0xA4 => output_stream.write_all(b"ae")?, // ä
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ use crate::processors::get_file::properties::{
use minifi_native::macros::ComponentIdentifier;
use minifi_native::{
GetProperty, IoState, Logger, MinifiError, OnTriggerResult, ProcessContext, ProcessError,
ProcessSession, Schedule, Trigger, debug, info, trace, warn,
ProcessSession, Schedule, Trigger, debug, trace, warn,
};
use std::collections::VecDeque;
use std::error;
Expand Down Expand Up @@ -179,7 +179,7 @@ impl GetFileRs {
logger: &L,
path: &Path,
) -> Result<(), MinifiError> {
info!(logger, "GetFile process {:?}", path);
trace!(logger, "GetFile process {:?}", path);
let mut ff = session
.create()
.expect("Successful FlowFile creation is expected");
Expand Down
25 changes: 25 additions & 0 deletions minifi_rust/extensions/minifi_tensor/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
[package]
name = "minifi_tensor"
version = "0.1.0"
edition = "2024"

[lib]
crate-type = ["cdylib"]

[features]
low-level-processors = []

[dependencies]
minifi_native = { path = "../../minifi_native" }
strum = "0.28.0"
strum_macros = "0.28.0"
image = "0.25.10"
imageproc = "0.27.0"
tract = "0.23.4"
ndarray = "0.17.2"
serde = { version = "1.0.229", features = ["derive"] }
serde_json = "1.0.151"

[dev-dependencies]
minifi_native = { path = "../../minifi_native", features = ["test-utils"] }
tempfile = "3.27.0"
33 changes: 33 additions & 0 deletions minifi_rust/extensions/minifi_tensor/features/basic.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

@SUPPORTS_WINDOWS
Feature: minifi_tensor extension loads

Scenario: All processors and the TractModelService register cleanly
Given log property "logger.org::apache::nifi::minifi::core::extension::ExtensionManager" is set to "TRACE,stderr"
And log property "logger.org::apache::nifi::minifi::core::ClassLoader" is set to "TRACE,stderr"

When the MiNiFi instance starts up

Then the Minifi logs contain the following message: "Registering class 'ImageToTensor' at '/minifi_tensor'" in less than 10 seconds
And the Minifi logs contain the following message: "Registering class 'InvokeTractModel' at '/minifi_tensor'" in less than 1 seconds
And the Minifi logs contain the following message: "Registering class 'FilterBoundingBoxes' at '/minifi_tensor'" in less than 1 seconds
And the Minifi logs contain the following message: "Registering class 'ClassifyOutput' at '/minifi_tensor'" in less than 1 seconds
And the Minifi logs contain the following message: "Registering class 'TractModelService' at '/minifi_tensor'" in less than 1 seconds
And the Minifi logs contain the following message: "Registering class 'ClassifyImage' at '/minifi_tensor'" in less than 1 seconds
And the Minifi logs contain the following message: "Registering class 'DetectObject' at '/minifi_tensor'" in less than 1 seconds
And the Minifi logs do not contain errors
And the Minifi logs do not contain warnings
128 changes: 128 additions & 0 deletions minifi_rust/extensions/minifi_tensor/features/classification.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

@SUPPORTS_WINDOWS
Feature: Image classification with MobileNetV2

# Based on https://github.com/sonos/tract/tree/main/examples/onnx-mobilenet-v2
Scenario: Grace Hopper image is classified using MobileNetV2 ONNX and imagenet labels (ImageToTensor + InvokeTract + ClassifyOutput)
Given a host resource file "grace_hopper.jpg" is copied to the "/tmp/input/grace_hopper.jpg" path in the MiNiFi container
And a host resource file "mobilenetv2-7.onnx" is copied to the "/tmp/models/mobilenetv2-7.onnx" path in the MiNiFi container
And a host resource file "imagenet_slim_labels.txt" is copied to the "/tmp/models/imagenet_slim_labels.txt" path in the MiNiFi container

And a TractModelService controller service named "MobileNet" is set up and the "Model File Path" property set to "/tmp/models/mobilenetv2-7.onnx"
And the "Model format" property of the MobileNet controller service is set to "Onnx"

And a GetFile processor with the "Input Directory" property set to "/tmp/input"
And the "Keep Source File" property of the GetFile processor is set to "false"

And an ImageToTensor processor with the "Target width" property set to "224"
And the "Target height" property of the ImageToTensor processor is set to "224"
And the "Resize filter" property of the ImageToTensor processor is set to "Bilinear"
And the "Resize mode" property of the ImageToTensor processor is set to "Stretch"
And the "Color format" property of the ImageToTensor processor is set to "RGB"
And the "Tensor shape format" property of the ImageToTensor processor is set to "CHW"
And the "Mean" property of the ImageToTensor processor is set to "0.485, 0.456, 0.406"
And the "Standard Deviation" property of the ImageToTensor processor is set to "0.229, 0.224, 0.225"
And the "Pixel divisor" property of the ImageToTensor processor is set to "255"

And an InvokeTractModel processor with the "Tract model service" property set to "MobileNet"

And a ClassifyOutput processor with the "Top K" property set to "3"
And the "Score activation" property of the ClassifyOutput processor is set to "Softmax"
And the "Labels file path" property of the ClassifyOutput processor is set to "/tmp/models/imagenet_slim_labels.txt"
# ImageNet slim labels start with a dummy entry on line 0
And the "Label index offset" property of the ClassifyOutput processor is set to "1"
And the "Confidence Threshold" property of the ClassifyOutput processor is set to "0.0"

And a PutFile processor with the "Directory" property set to "/tmp/output"

And a LogAttribute processor with the "FlowFiles To Log" property set to "0"
And LogAttribute is EVENT_DRIVEN

And the "success" relationship of the GetFile processor is connected to the ImageToTensor
And the "success" relationship of the ImageToTensor processor is connected to the InvokeTractModel
And the "success" relationship of the InvokeTractModel processor is connected to the ClassifyOutput
And the "success" relationship of the ClassifyOutput processor is connected to the LogAttribute
And the "success" relationship of the LogAttribute processor is connected to the PutFile
And ImageToTensor's failure relationship is auto-terminated
And InvokeTractModel's failure relationship is auto-terminated
And ClassifyOutput's failure relationship is auto-terminated
And PutFile's success relationship is auto-terminated
And PutFile's failure relationship is auto-terminated

When the MiNiFi instance starts up

# Grace Hopper wears a US Navy uniform; MobileNetV2 on ImageNet consistently
# picks "military uniform" (occasionally "suit", "Windsor tie", or
# "bulletproof vest" as close runner-ups). Match any of those to keep the
# test resilient to small numeric differences across tract versions.
Then the Minifi logs match the following regex: "key:class.top1.name value:(military uniform|bulletproof vest|suit|Windsor tie)" in less than 60 seconds
And the Minifi logs match the following regex: "key:class.count value:[1-3]" in less than 1 seconds
And the Minifi logs contain the following message: "key:mime.type value:application/json" in less than 1 seconds
And at least one file in "/tmp/output" content match the following regex: "\"class_name\":\"(military uniform|bulletproof vest|suit|Windsor tie)\"" in less than 30 seconds
And the Minifi logs do not contain errors

# Based on https://github.com/sonos/tract/tree/main/examples/onnx-mobilenet-v2
Scenario: Grace Hopper image is classified using MobileNetV2 ONNX and imagenet labels (ClassifyImage)
Given a host resource file "grace_hopper.jpg" is copied to the "/tmp/input/grace_hopper.jpg" path in the MiNiFi container
And a host resource file "mobilenetv2-7.onnx" is copied to the "/tmp/models/mobilenetv2-7.onnx" path in the MiNiFi container
And a host resource file "imagenet_slim_labels.txt" is copied to the "/tmp/models/imagenet_slim_labels.txt" path in the MiNiFi container

And a TractModelService controller service named "MobileNet" is set up and the "Model File Path" property set to "/tmp/models/mobilenetv2-7.onnx"
And the "Model format" property of the MobileNet controller service is set to "Onnx"

And a GetFile processor with the "Input Directory" property set to "/tmp/input"
And the "Keep Source File" property of the GetFile processor is set to "false"

And a ClassifyImage processor with the "Target width" property set to "224"
And the "Target height" property of the ClassifyImage processor is set to "224"
And the "Resize filter" property of the ClassifyImage processor is set to "Bilinear"
And the "Resize mode" property of the ClassifyImage processor is set to "Stretch"
And the "Color format" property of the ClassifyImage processor is set to "RGB"
And the "Tensor shape format" property of the ClassifyImage processor is set to "CHW"
And the "Mean" property of the ClassifyImage processor is set to "0.485, 0.456, 0.406"
And the "Standard Deviation" property of the ClassifyImage processor is set to "0.229, 0.224, 0.225"
And the "Pixel divisor" property of the ClassifyImage processor is set to "255"
And the "Tract model service" property of the ClassifyImage processor is set to "MobileNet"
And the "Top K" property of the ClassifyImage processor is set to "3"
And the "Score activation" property of the ClassifyImage processor is set to "Softmax"
And the "Labels file path" property of the ClassifyImage processor is set to "/tmp/models/imagenet_slim_labels.txt"
And the "Label index offset" property of the ClassifyImage processor is set to "1"
And the "Confidence Threshold" property of the ClassifyImage processor is set to "0.0"

And a PutFile processor with the "Directory" property set to "/tmp/output"

And a LogAttribute processor with the "FlowFiles To Log" property set to "0"
And LogAttribute is EVENT_DRIVEN

And the "success" relationship of the GetFile processor is connected to the ClassifyImage
And the "success" relationship of the ClassifyImage processor is connected to the LogAttribute
And the "success" relationship of the LogAttribute processor is connected to the PutFile
And ClassifyImage's failure relationship is auto-terminated
And PutFile's success relationship is auto-terminated
And PutFile's failure relationship is auto-terminated

When the MiNiFi instance starts up

# Grace Hopper wears a US Navy uniform; MobileNetV2 on ImageNet consistently
# picks "military uniform" (occasionally "suit", "Windsor tie", or
# "bulletproof vest" as close runner-ups). Match any of those to keep the
# test resilient to small numeric differences across tract versions.
Then the Minifi logs match the following regex: "key:class.top1.name value:(military uniform|bulletproof vest|suit|Windsor tie)" in less than 60 seconds
And the Minifi logs match the following regex: "key:class.count value:[1-3]" in less than 1 seconds
And the Minifi logs contain the following message: "key:mime.type value:application/json" in less than 1 seconds
And at least one file in "/tmp/output" content match the following regex: "\"class_name\":\"(military uniform|bulletproof vest|suit|Windsor tie)\"" in less than 30 seconds
And the Minifi logs do not contain errors
Loading
Loading