This is an automated email from the ASF dual-hosted git repository.
martinzink pushed a commit to branch minifi_rust_tensor
in repository https://gitbox.apache.org/repos/asf/nifi-minifi-cpp.git
The following commit(s) were added to refs/heads/minifi_rust_tensor by this
push:
new 568f89572 review changes
568f89572 is described below
commit 568f895725eb77ef470121155e99e91f91f6a621
Author: Martin Zink <[email protected]>
AuthorDate: Wed Sep 30 13:53:28 2026 +0200
review changes
---
.../ubuntu_22_04_clang_arm_manifest.json | 2 +-
PROCESSORS.md | 2 +-
.../extensions/minifi_tensor/minifi_tensor.md | 2 +-
.../low_level_processors/filter_bounding_boxes.rs | 54 ++++++++++-----------
.../src/low_level_processors/image_to_tensor.rs | 55 ++++++++++------------
.../image_to_tensor/image_to_tensor_def.rs | 5 +-
.../src/processors/draw_bounding_box.rs | 2 +-
.../minifi_tensor/src/utils/dimensions.rs | 34 +++++++++----
minifi_rust/minifi_native/src/api/property.rs | 13 +++++
9 files changed, 92 insertions(+), 77 deletions(-)
diff --git a/.github/references/ubuntu_22_04_clang_arm_manifest.json
b/.github/references/ubuntu_22_04_clang_arm_manifest.json
index f599adac8..3be2dbd08 100644
--- a/.github/references/ubuntu_22_04_clang_arm_manifest.json
+++ b/.github/references/ubuntu_22_04_clang_arm_manifest.json
@@ -13523,7 +13523,7 @@
"description": "Flowfiles are routed here after
drawing the bounding boxes"
}
],
- "typeDescription": "Decodes the image from the flow file
content, draws each bounding box supplied via the 'Bounding boxes' property
onto it, and re-encodes the annotated image as PNG. Pair with an upstream
DetectObject / FilterBoundingBoxes to visualise detections.",
+ "typeDescription": "Decodes the image from the flow file
content, draws each bounding box supplied via the 'Bounding boxes' property
onto it, and re-encodes the annotated imagewith the original image format. Pair
with an upstream DetectObject / FilterBoundingBoxes to visualise detections.",
"supportsDynamicRelationships": "false",
"supportsDynamicProperties": "false",
"type":
"minifi_tensor.processors.draw_bounding_box.DrawBoundingBox"
diff --git a/PROCESSORS.md b/PROCESSORS.md
index 082ec91aa..a98a5404c 100644
--- a/PROCESSORS.md
+++ b/PROCESSORS.md
@@ -745,7 +745,7 @@ In the list below, the names of required properties appear
in bold. Any other pr
### Description
-Decodes the image from the flow file content, draws each bounding box supplied
via the 'Bounding boxes' property onto it, and re-encodes the annotated image
as PNG. Pair with an upstream DetectObject / FilterBoundingBoxes to visualise
detections.
+Decodes the image from the flow file content, draws each bounding box supplied
via the 'Bounding boxes' property onto it, and re-encodes the annotated
imagewith the original image format. Pair with an upstream DetectObject /
FilterBoundingBoxes to visualise detections.
### Properties
diff --git a/minifi_rust/extensions/minifi_tensor/minifi_tensor.md
b/minifi_rust/extensions/minifi_tensor/minifi_tensor.md
index 1feeaca86..1301446b4 100644
--- a/minifi_rust/extensions/minifi_tensor/minifi_tensor.md
+++ b/minifi_rust/extensions/minifi_tensor/minifi_tensor.md
@@ -168,7 +168,7 @@ In the list below, the names of required properties appear
in bold. Any other pr
### Description
-Decodes the image from the flow file content, draws each bounding box supplied
via the 'Bounding boxes' property onto it, and re-encodes the annotated image
as PNG. Pair with an upstream DetectObject / FilterBoundingBoxes to visualise
detections.
+Decodes the image from the flow file content, draws each bounding box supplied
via the 'Bounding boxes' property onto it, and re-encodes the annotated
imagewith the original image format. Pair with an upstream DetectObject /
FilterBoundingBoxes to visualise detections.
### Properties
diff --git
a/minifi_rust/extensions/minifi_tensor/src/low_level_processors/filter_bounding_boxes.rs
b/minifi_rust/extensions/minifi_tensor/src/low_level_processors/filter_bounding_boxes.rs
index 3f96441f8..36004fbca 100644
---
a/minifi_rust/extensions/minifi_tensor/src/low_level_processors/filter_bounding_boxes.rs
+++
b/minifi_rust/extensions/minifi_tensor/src/low_level_processors/filter_bounding_boxes.rs
@@ -131,6 +131,16 @@ impl Schedule for FilterBoundingBoxes {
let background_class_index =
context.get_property(&BACKGROUND_CLASS_INDEX)?;
let class_output_index = context.get_property(&CLASS_OUTPUT_INDEX)?;
+ if score_output_index == box_output_index {
+ return Err(MinifiError::ValidationError(
+ format!(
+ "{} must be different from {}",
+ SCORE_OUTPUT_INDEX.name(),
+ BOX_OUTPUT_INDEX.name()
+ )
+ .into(),
+ ));
+ }
Ok(Self {
confidence_threshold,
iou_threshold,
@@ -196,8 +206,8 @@ impl FilterBoundingBoxes {
)
}
ResizeMode::Stretch => (
- target_dim.width / orig_dim.width,
- target_dim.height / orig_dim.height,
+ target_dim.width() / orig_dim.width(),
+ target_dim.height() / orig_dim.height(),
0.0,
0.0,
),
@@ -219,12 +229,14 @@ impl FilterBoundingBoxes {
let make_box = |i: usize, class_id: usize, confidence: f32| ->
BoundingBox {
let (raw_x_min, raw_y_min, raw_x_max, raw_y_max) =
decode_box(&box_floats, i * 4, self.box_format);
- let true_x_min = (((raw_x_min * target_dim.width) - pad_x) /
scale_x) / orig_dim.width;
+ let true_x_min =
+ (((raw_x_min * target_dim.width()) - pad_x) / scale_x) /
orig_dim.width();
let true_y_min =
- (((raw_y_min * target_dim.height) - pad_y) / scale_y) /
orig_dim.height;
- let true_x_max = (((raw_x_max * target_dim.width) - pad_x) /
scale_x) / orig_dim.width;
+ (((raw_y_min * target_dim.height()) - pad_y) / scale_y) /
orig_dim.height();
+ let true_x_max =
+ (((raw_x_max * target_dim.width()) - pad_x) / scale_x) /
orig_dim.width();
let true_y_max =
- (((raw_y_max * target_dim.height) - pad_y) / scale_y) /
orig_dim.height;
+ (((raw_y_max * target_dim.height()) - pad_y) / scale_y) /
orig_dim.height();
BoundingBox {
class_id,
confidence,
@@ -446,10 +458,7 @@ mod tests {
let classes = Tensor::from_slice::<i64>(&[2], &[5, 3]).unwrap();
let processor = class_index_processor();
- let dim = Dimensions {
- width: 100.0,
- height: 100.0,
- };
+ let dim = Dimensions::new(100.0, 100.0).unwrap();
let result = processor
.filter(
&MockProcessContext::new(),
@@ -484,14 +493,8 @@ mod tests {
&MockProcessContext::new(),
&MockLogger::new(),
vec![scores, boxes, classes],
- Dimensions {
- width: 200.0,
- height: 100.0,
- },
- Dimensions {
- width: 100.0,
- height: 100.0,
- },
+ Dimensions::new(200.0, 100.0).unwrap(),
+ Dimensions::new(100.0, 100.0).unwrap(),
mode,
)
.expect("filter should succeed");
@@ -535,14 +538,8 @@ mod tests {
&MockProcessContext::new(),
&MockLogger::new(),
vec![scores, boxes, classes],
- Dimensions {
- width: 1920.0,
- height: 1080.0,
- },
- Dimensions {
- width: 300.0,
- height: 300.0,
- },
+ Dimensions::new(1920.0, 1080.0).unwrap(),
+ Dimensions::new(300.0, 300.0).unwrap(),
ResizeMode::Letterbox,
)
.expect("filter should succeed");
@@ -573,10 +570,7 @@ mod tests {
Tensor::from_slice::<f32>(&[2, 4], &[0.1, 0.1, 0.2, 0.2, 0.5, 0.5,
0.9, 0.9]).unwrap();
let classes = Tensor::from_slice::<i64>(&[2], &[5, 3]).unwrap();
- let dim = Dimensions {
- width: 100.0,
- height: 100.0,
- };
+ let dim = Dimensions::new(100.0, 100.0).unwrap();
let result = class_index_processor().filter(
&MockProcessContext::new(),
&MockLogger::new(),
diff --git
a/minifi_rust/extensions/minifi_tensor/src/low_level_processors/image_to_tensor.rs
b/minifi_rust/extensions/minifi_tensor/src/low_level_processors/image_to_tensor.rs
index 65d6e4660..b24f58ca7 100644
---
a/minifi_rust/extensions/minifi_tensor/src/low_level_processors/image_to_tensor.rs
+++
b/minifi_rust/extensions/minifi_tensor/src/low_level_processors/image_to_tensor.rs
@@ -34,6 +34,7 @@ use minifi_native::{
FlowFileTransform, GetAttribute, GetControllerService, GetId, GetProperty,
InputStream, Logger,
MinifiError, ProcessError, RouteErrorExt, Schedule, TransformedFlowFile,
};
+use std::num::NonZeroU32;
use strum_macros::{Display, EnumString, IntoStaticStr, VariantNames};
use tract::Tensor;
@@ -92,8 +93,8 @@ pub(crate) enum ResizeMode {
#[derive(ComponentIdentifier)]
pub(crate) struct ImageToTensor {
- target_width: u32,
- target_height: u32,
+ target_width: NonZeroU32,
+ target_height: NonZeroU32,
resize_filter: ResizeFilter,
resize_mode: ResizeMode,
color_format: ColorFormat,
@@ -114,11 +115,6 @@ impl Schedule for ImageToTensor {
{
let target_width = context.get_property(&TARGET_WIDTH)?;
let target_height = context.get_property(&TARGET_HEIGHT)?;
- if target_width == 0 || target_height == 0 {
- return Err(MinifiError::validation(
- "Target width and Target height must be greater than zero",
- ));
- }
let resize_filter = context.get_property(&RESIZE_FILTER)?;
let resize_mode = context.get_property(&RESIZE_MODE)?;
let color_format = context.get_property(&COLOR_FORMAT)?;
@@ -158,14 +154,14 @@ struct MaskedRgbImage {
impl ImageToTensor {
fn total_pixels(&self) -> usize {
- self.target_width as usize * self.target_height as usize
+ self.target_width.get() as usize * self.target_height.get() as usize
}
fn stretch_resize(&self, img: image::DynamicImage) -> MaskedRgbImage {
let resized = img
.resize_exact(
- self.target_width,
- self.target_height,
+ self.target_width.get(),
+ self.target_height.get(),
self.resize_filter.into(),
)
.to_rgb8();
@@ -186,8 +182,8 @@ impl ImageToTensor {
.to_rgb8();
let mut canvas = image::RgbImage::from_pixel(
- self.target_width,
- self.target_height,
+ self.target_width.get(),
+ self.target_height.get(),
image::Rgb([0, 0, 0]),
);
image::imageops::overlay(&mut canvas, &scaled, pad_x as i64, pad_y as
i64);
@@ -196,7 +192,7 @@ impl ImageToTensor {
let mut mask = vec![false; self.total_pixels()];
for y in pad_y..(new_h + pad_y) {
for x in pad_x..(new_w + pad_x) {
- mask[y as usize * self.target_width as usize + x as usize] =
true;
+ mask[y as usize * self.target_width.get() as usize + x as
usize] = true;
}
}
MaskedRgbImage { img: canvas, mask }
@@ -286,20 +282,20 @@ impl ImageToTensor {
vec![
1,
1,
- self.target_height as usize,
- self.target_width as usize,
+ self.target_height.get() as usize,
+ self.target_width.get() as usize,
]
}
(_, TensorShapeFormat::Chw) => vec![
1,
num_channels,
- self.target_height as usize,
- self.target_width as usize,
+ self.target_height.get() as usize,
+ self.target_width.get() as usize,
],
(_, TensorShapeFormat::Hwc) => vec![
1,
- self.target_height as usize,
- self.target_width as usize,
+ self.target_height.get() as usize,
+ self.target_width.get() as usize,
num_channels,
],
}
@@ -313,10 +309,7 @@ impl ImageToTensor {
.join(",")
}
pub fn get_target_dim(&self) -> Dimensions {
- Dimensions {
- width: self.target_width as f32,
- height: self.target_height as f32,
- }
+ Dimensions::from_u32s(self.target_width, self.target_height)
}
pub fn get_resize_mode(&self) -> ResizeMode {
@@ -360,8 +353,8 @@ impl FlowFileTransform for ImageToTensor {
(&TENSOR_SHAPE_ATTR, self.get_shape_str()),
(&TENSOR_BYTES_ATTR, tensor_bytes_len.to_string()),
(&TENSOR_DTYPE_ATTR, MinifiDatumType::F32.to_string()),
- (&IMG_ORG_HEIGHT_ATTR, orig_dim.height.to_string()),
- (&IMG_ORG_WIDTH_ATTR, orig_dim.width.to_string()),
+ (&IMG_ORG_HEIGHT_ATTR, orig_dim.height().to_string()),
+ (&IMG_ORG_WIDTH_ATTR, orig_dim.width().to_string()),
(&IMG_TRG_HEIGHT_ATTR, self.target_height.to_string()),
(&IMG_TRG_WIDTH_ATTR, self.target_width.to_string()),
(&IMG_RESIZE_MODE_ATTR, self.resize_mode.to_string()),
@@ -393,8 +386,8 @@ mod tests {
fn default_processor() -> ImageToTensor {
ImageToTensor {
- target_width: 2,
- target_height: 2,
+ target_width: NonZeroU32::new(2).unwrap(),
+ target_height: NonZeroU32::new(2).unwrap(),
resize_filter: ResizeFilter::Nearest,
resize_mode: ResizeMode::Stretch,
color_format: ColorFormat::Rgb,
@@ -521,8 +514,8 @@ mod tests {
#[test]
fn test_invalid_image_routes_to_failure() {
let processor = ImageToTensor {
- target_width: 224,
- target_height: 224,
+ target_width: NonZeroU32::new(224).unwrap(),
+ target_height: NonZeroU32::new(224).unwrap(),
resize_filter: ResizeFilter::Bilinear,
..default_processor()
};
@@ -607,8 +600,8 @@ mod tests {
.unwrap();
let processor = ImageToTensor {
- target_width: 4,
- target_height: 4,
+ target_width: NonZeroU32::new(4).unwrap(),
+ target_height: NonZeroU32::new(4).unwrap(),
resize_mode: ResizeMode::Letterbox,
letterbox_pad_value: -1.0,
..default_processor()
diff --git
a/minifi_rust/extensions/minifi_tensor/src/low_level_processors/image_to_tensor/image_to_tensor_def.rs
b/minifi_rust/extensions/minifi_tensor/src/low_level_processors/image_to_tensor/image_to_tensor_def.rs
index 03c486fa3..f2b916962 100644
---
a/minifi_rust/extensions/minifi_tensor/src/low_level_processors/image_to_tensor/image_to_tensor_def.rs
+++
b/minifi_rust/extensions/minifi_tensor/src/low_level_processors/image_to_tensor/image_to_tensor_def.rs
@@ -21,14 +21,15 @@ use minifi_native::{
OutputAttribute, ProcessorDefinition, ProcessorInputRequirement, Property,
PropertyDefinition,
Relationship, property_definitions,
};
+use std::num::NonZeroU32;
-pub(crate) const TARGET_WIDTH: Property<u32> = Property::new(
+pub(crate) const TARGET_WIDTH: Property<NonZeroU32> = Property::new(
"Target width",
"Width in pixels the decoded image is resized to before normalisation and \
inference.",
);
-pub(crate) const TARGET_HEIGHT: Property<u32> = Property::new(
+pub(crate) const TARGET_HEIGHT: Property<NonZeroU32> = Property::new(
"Target height",
"Height in pixels the decoded image is resized to before normalisation and
\
inference.",
diff --git
a/minifi_rust/extensions/minifi_tensor/src/processors/draw_bounding_box.rs
b/minifi_rust/extensions/minifi_tensor/src/processors/draw_bounding_box.rs
index 073890c25..2b18502c3 100644
--- a/minifi_rust/extensions/minifi_tensor/src/processors/draw_bounding_box.rs
+++ b/minifi_rust/extensions/minifi_tensor/src/processors/draw_bounding_box.rs
@@ -150,7 +150,7 @@ impl FlowFileTransform for DrawBoundingBox {
impl ProcessorDefinition for DrawBoundingBox {
const DESCRIPTION: &'static str = "Decodes the image from the flow file
content, draws each bounding box supplied via the \
- 'Bounding boxes' property onto it, and re-encodes the annotated image
as PNG. Pair with an \
+ 'Bounding boxes' property onto it, and re-encodes the annotated
imagewith the original image format. Pair with an \
upstream DetectObject / FilterBoundingBoxes to visualise detections.";
const INPUT_REQUIREMENT: ProcessorInputRequirement =
ProcessorInputRequirement::Required;
const SUPPORTS_DYNAMIC_PROPERTIES: bool = false;
diff --git a/minifi_rust/extensions/minifi_tensor/src/utils/dimensions.rs
b/minifi_rust/extensions/minifi_tensor/src/utils/dimensions.rs
index aa5acbca5..4608b522f 100644
--- a/minifi_rust/extensions/minifi_tensor/src/utils/dimensions.rs
+++ b/minifi_rust/extensions/minifi_tensor/src/utils/dimensions.rs
@@ -16,6 +16,7 @@
// under the License.
use minifi_native::{GetAttribute, MinifiError};
+use std::num::NonZeroU32;
/// The exact placement of an aspect-preserving resize inside a target canvas.
///
@@ -36,11 +37,30 @@ pub(crate) struct LetterboxGeometry {
#[derive(Debug, Clone, Copy, PartialEq)]
pub(crate) struct Dimensions {
- pub(crate) width: f32,
- pub(crate) height: f32,
+ width: f32,
+ height: f32,
}
impl Dimensions {
+ pub(crate) fn from_u32s(width: NonZeroU32, height: NonZeroU32) -> Self {
+ Dimensions {
+ width: width.get() as f32,
+ height: height.get() as f32,
+ }
+ }
+ pub(crate) fn new(width: f32, height: f32) -> Result<Self, MinifiError> {
+ if width <= 0f32 || height <= 0f32 {
+ Err(MinifiError::custom("Only positive dimensions are allowed"))
+ } else {
+ Ok(Self { width, height })
+ }
+ }
+ pub(crate) fn width(&self) -> f32 {
+ self.width
+ }
+ pub(crate) fn height(&self) -> f32 {
+ self.height
+ }
/// Fit `self` into `target` preserving aspect ratio, centring the result.
///
/// Assumes both dimensions are non-zero; `ImageToTensor::schedule`
rejects a
@@ -80,10 +100,7 @@ impl Dimensions {
.get_required_attribute("image.original.height")?
.parse::<f32>()?;
- Ok(Dimensions {
- width: orig_w,
- height: orig_h,
- })
+ Dimensions::new(orig_w, orig_h)
}
pub(crate) fn target_from_attributes<Context: GetAttribute>(
@@ -97,10 +114,7 @@ impl Dimensions {
.get_required_attribute("image.target.height")?
.parse::<f32>()?;
- Ok(Dimensions {
- width: target_w,
- height: target_h,
- })
+ Dimensions::new(target_w, target_h)
}
}
diff --git a/minifi_rust/minifi_native/src/api/property.rs
b/minifi_rust/minifi_native/src/api/property.rs
index 9c70fa016..c74852a90 100644
--- a/minifi_rust/minifi_native/src/api/property.rs
+++ b/minifi_rust/minifi_native/src/api/property.rs
@@ -23,6 +23,7 @@ use crate::{
};
use minifi_native::StandardPropertyValidator::{F64Validator, I64Validator};
use std::marker::PhantomData;
+use std::num::{NonZeroU32, NonZeroU64, NonZeroUsize};
use std::str::FromStr;
use std::time::Duration;
@@ -207,6 +208,18 @@ impl_from_str_property!(bool,
Some(PropertyConstraints::Validator(BoolValidator)
impl_from_str_property!(u64,
Some(PropertyConstraints::Validator(U64Validator)));
impl_from_str_property!(u32,
Some(PropertyConstraints::Validator(U64Validator)));
impl_from_str_property!(usize,
Some(PropertyConstraints::Validator(U64Validator)));
+impl_from_str_property!(
+ NonZeroU32,
+ Some(PropertyConstraints::Validator(U64Validator))
+);
+impl_from_str_property!(
+ NonZeroU64,
+ Some(PropertyConstraints::Validator(U64Validator))
+);
+impl_from_str_property!(
+ NonZeroUsize,
+ Some(PropertyConstraints::Validator(U64Validator))
+);
impl PropertyType for Duration {
type Output = Duration;