Skip to content

Commit 4824512

Browse files
fix(dynamic_crop): emit both manifest keys on zero-area bbox path (#2346)
The zero-area branch in crop_image() appended {"crops": None} while the manifest declares two outputs (crops, predictions). The execution engine's strict set-equality check on element keys then failed the step with `Expected: {'predictions', 'crops'}. Got: {'crops'}` whenever a detection's bbox rounded to zero width/height. Drift dates to commit b4e3ab9 which added the predictions output to the manifest and happy path but missed this error path. Test updates pin the two-key contract and add a regression that locks crop_image() output keys against BlockManifest.describe_outputs(). Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 566d53b commit 4824512

2 files changed

Lines changed: 55 additions & 3 deletions

File tree

  • inference/core/workflows/core_steps/transformations/dynamic_crop
  • tests/workflows/unit_tests/core_steps/transformations

inference/core/workflows/core_steps/transformations/dynamic_crop/v1.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ def crop_image(
214214
):
215215
cropped_image = image.numpy_image[y_min:y_max, x_min:x_max]
216216
if not cropped_image.size:
217-
crops.append({"crops": None})
217+
crops.append({"crops": None, "predictions": None})
218218
continue
219219
if mask_opacity > 0 and detections.mask is not None:
220220
detection_mask = detections.mask[idx]

tests/workflows/unit_tests/core_steps/transformations/test_crop.py

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -219,11 +219,63 @@ def test_crop_image_on_zero_size_detections() -> None:
219219

220220
# then
221221
assert len(result) == 3, "Expected 3 outputs"
222-
assert result[0] == {"crops": None}, "Expected first element empty"
222+
assert result[0] == {
223+
"crops": None,
224+
"predictions": None,
225+
}, "Both manifest-declared keys must be present, even on zero-area"
226+
assert set(result[1].keys()) == {
227+
"crops",
228+
"predictions",
229+
}, "Happy path must produce both manifest-declared keys"
223230
assert (
224231
result[1]["crops"].parent_metadata.parent_id == "two"
225232
), "Appropriate parent id (from detection id) must be attached"
226-
assert result[2] == {"crops": None}, "Expected last element empty"
233+
assert result[2] == {
234+
"crops": None,
235+
"predictions": None,
236+
}, "Both manifest-declared keys must be present, even on zero-area"
237+
238+
239+
def test_crop_image_output_keys_match_manifest_on_mixed_detections() -> None:
240+
# given a mix of zero-area and valid detections
241+
np_image = np.zeros((1000, 1000, 3), dtype=np.uint8)
242+
image = WorkflowImageData(
243+
parent_metadata=ImageParentMetadata(parent_id="origin_image"),
244+
numpy_image=np_image,
245+
)
246+
detections = sv.Detections(
247+
xyxy=np.array(
248+
[[0, 0, 0, 0], [80, 80, 120, 120]], dtype=np.float64
249+
),
250+
class_id=np.array([1, 1]),
251+
confidence=np.array([0.5, 0.5], dtype=np.float64),
252+
data={
253+
"detection_id": np.array(["zero", "valid"]),
254+
"class_name": np.array(["cat", "cat"]),
255+
},
256+
)
257+
258+
# when
259+
result = crop_image(
260+
image=image,
261+
detections=detections,
262+
mask_opacity=0.0,
263+
background_color=(0, 0, 0),
264+
)
265+
266+
# then — every emitted dict must carry exactly the manifest output keys
267+
expected_keys = {
268+
output.name for output in BlockManifest.describe_outputs()
269+
}
270+
assert expected_keys == {"crops", "predictions"}, (
271+
"Sanity: manifest declares the two outputs this test locks against. "
272+
"If this changes, update the assertion below accordingly."
273+
)
274+
for i, element in enumerate(result):
275+
assert set(element.keys()) == expected_keys, (
276+
f"Element {i} keys {set(element.keys())} != manifest keys "
277+
f"{expected_keys}"
278+
)
227279

228280

229281
def test_crop_image_when_detections_without_ids_provided() -> None:

0 commit comments

Comments
 (0)