Skip to content

Commit 5d1ffe2

Browse files
Merge pull request #1616 from roboflow/fix/image-preprocessing-dynamic-params
Fix image preprocessing block to accept dynamic parameter references
2 parents f06f3f3 + c3300a4 commit 5d1ffe2

File tree

2 files changed

+87
-8
lines changed

2 files changed

+87
-8
lines changed

inference/core/workflows/core_steps/classical_cv/image_preprocessing/v1.py

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import cv2
44
import numpy as np
5-
from pydantic import AliasChoices, ConfigDict, Field
5+
from pydantic import AliasChoices, ConfigDict, Field, PositiveInt
66

77
from inference.core.workflows.execution_engine.entities.base import (
88
OutputDefinition,
@@ -55,12 +55,11 @@ class ImagePreprocessingManifest(WorkflowBlockManifest):
5555
task_type: Literal["resize", "rotate", "flip"] = Field(
5656
description="Preprocessing task to be applied to the image.",
5757
)
58-
width: Union[int, Selector(kind=[INTEGER_KIND])] = Field( # type: ignore
58+
width: Union[PositiveInt, Selector(kind=[INTEGER_KIND])] = Field( # type: ignore
5959
title="Width",
6060
default=640,
6161
description="Width of the image to be resized to.",
6262
examples=[640, "$inputs.resize_width"],
63-
gt=0,
6463
json_schema_extra={
6564
"relevant_for": {
6665
"task_type": {
@@ -70,12 +69,11 @@ class ImagePreprocessingManifest(WorkflowBlockManifest):
7069
},
7170
},
7271
)
73-
height: Union[int, Selector(kind=[INTEGER_KIND])] = Field( # type: ignore
72+
height: Union[PositiveInt, Selector(kind=[INTEGER_KIND])] = Field( # type: ignore
7473
title="Height",
7574
default=640,
7675
description="Height of the image to be resized to.",
7776
examples=[640, "$inputs.resize_height"],
78-
gt=0,
7977
json_schema_extra={
8078
"relevant_for": {
8179
"task_type": {
@@ -90,8 +88,6 @@ class ImagePreprocessingManifest(WorkflowBlockManifest):
9088
description="Positive value to rotate clockwise, negative value to rotate counterclockwise",
9189
default=90,
9290
examples=[90, "$inputs.rotation_degrees"],
93-
ge=-360,
94-
le=360,
9591
json_schema_extra={
9692
"relevant_for": {
9793
"task_type": {
@@ -101,7 +97,9 @@ class ImagePreprocessingManifest(WorkflowBlockManifest):
10197
}
10298
},
10399
)
104-
flip_type: Union[Selector(kind=[STRING_KIND]), Literal["vertical", "horizontal", "both"]] = Field( # type: ignore
100+
flip_type: Union[
101+
Selector(kind=[STRING_KIND]), Literal["vertical", "horizontal", "both"]
102+
] = Field( # type: ignore
105103
title="Flip Type",
106104
description="Type of flip to be applied to the image.",
107105
default="vertical",
@@ -150,10 +148,24 @@ def run(
150148
response_image = None
151149

152150
if task_type == "resize":
151+
if width is not None and width <= 0:
152+
raise ValueError("Width must be greater than 0")
153+
if height is not None and height <= 0:
154+
raise ValueError("Height must be greater than 0")
153155
response_image = apply_resize_image(image.numpy_image, width, height)
154156
elif task_type == "rotate":
157+
if rotation_degrees is not None and not (-360 <= rotation_degrees <= 360):
158+
raise ValueError("Rotation degrees must be between -360 and 360")
155159
response_image = apply_rotate_image(image.numpy_image, rotation_degrees)
156160
elif task_type == "flip":
161+
if flip_type is not None and flip_type not in [
162+
"vertical",
163+
"horizontal",
164+
"both",
165+
]:
166+
raise ValueError(
167+
"Flip type must be 'vertical', 'horizontal', or 'both'"
168+
)
157169
response_image = apply_flip_image(image.numpy_image, flip_type)
158170
else:
159171
raise ValueError(f"Invalid task type: {task_type}")

tests/workflows/integration_tests/execution/test_workflow_with_image_preprocessing.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,73 @@ def test_rotate_image_workflow_when_valid_input_provided(
194194
), "Expected image to be rotated 90 degrees"
195195

196196

197+
def test_rotate_image_workflow_with_dynamic_rotation_degrees(
198+
model_manager: ModelManager,
199+
dogs_image: np.ndarray,
200+
) -> None:
201+
"""Test that rotation_degrees can accept dynamic parameter references."""
202+
# given
203+
workflow_definition = {
204+
"version": "1.0",
205+
"inputs": [
206+
{"type": "InferenceImage", "name": "image"},
207+
{"type": "WorkflowParameter", "name": "rotation_angle", "default_value": 90},
208+
],
209+
"steps": [
210+
{
211+
"type": "roboflow_core/image_preprocessing@v1",
212+
"name": "rotate",
213+
"image": "$inputs.image",
214+
"task_type": "rotate",
215+
"rotation_degrees": "$inputs.rotation_angle",
216+
}
217+
],
218+
"outputs": [
219+
{
220+
"type": "JsonField",
221+
"name": "rotated_image",
222+
"coordinates_system": "own",
223+
"selector": "$steps.rotate.image",
224+
}
225+
],
226+
}
227+
228+
workflow_init_parameters = {
229+
"workflows_core.model_manager": model_manager,
230+
"workflows_core.api_key": None,
231+
"workflows_core.step_execution_mode": StepExecutionMode.LOCAL,
232+
}
233+
execution_engine = ExecutionEngine.init(
234+
workflow_definition=workflow_definition,
235+
init_parameters=workflow_init_parameters,
236+
max_concurrent_steps=WORKFLOWS_MAX_CONCURRENT_STEPS,
237+
)
238+
239+
# when
240+
result = execution_engine.run(
241+
runtime_parameters={
242+
"image": dogs_image,
243+
"rotation_angle": 180,
244+
}
245+
)
246+
247+
# then
248+
original_image_height, original_image_width, _ = dogs_image.shape
249+
assert isinstance(result, list), "Expected list to be delivered"
250+
assert len(result) == 1, "Expected 1 element in the output for one input image"
251+
assert set(result[0].keys()) == {
252+
"rotated_image",
253+
}, "Expected all declared outputs to be delivered"
254+
np_image = result[0]["rotated_image"].numpy_image
255+
# 180 degree rotation should preserve dimensions
256+
assert (
257+
np_image.shape[0] == original_image_height
258+
), "Expected image height to be preserved after 180 degree rotation"
259+
assert (
260+
np_image.shape[1] == original_image_width
261+
), "Expected image width to be preserved after 180 degree rotation"
262+
263+
197264
def test_rotate_image_workflow_when_missing_required_parameters() -> None:
198265
# given
199266
data = {

0 commit comments

Comments
 (0)