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;

Reply via email to