From fedd468baefaa6f47df6142778fdeb119e6392ae Mon Sep 17 00:00:00 2001 From: jaideeppyne Date: Tue, 18 Aug 2026 11:30:12 +0530 Subject: [PATCH] Fix wrong COCO bbox area in vector object-detection export `sa_vector_to_coco_object_detection` computed the annotation `area` as `(x2 - x1) * y2 - y1`. Operator precedence makes that `((x2 - x1) * y2) - y1` instead of the COCO box area `(x2 - x1) * (y2 - y1)` (width * height). The error term is `y1 * (width - 1)`, so `area` is wrong for every exported bbox that does not sit against the top edge (`y1 == 0`). On the repo's own export golden box `{x1:437.16, y1:341.5, x2:465.23, y2:357.09}` the old code yields 9682 while the correct area is 437 (~22x off). Reuse the already-computed `bbox` width/height (`int(bbox[2] * bbox[3])`), matching the keypoint path and the instance-segmentation path. Intent is unambiguous: the shipped golden fixture encodes width*height. Adds a unit test asserting `area == int(width * height)` (and == 437 for that box). It fails on the current code (9682) and passes with the fix. The existing export tests never caught this because they only assert output filename equality (`filecmp.dircmp`), never file contents. --- .../coco_converters/sa_vector_to_coco.py | 7 +++- tests/unit/test_coco_object_detection_area.py | 39 +++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 tests/unit/test_coco_object_detection_area.py diff --git a/src/superannotate/lib/app/input_converters/converters/coco_converters/sa_vector_to_coco.py b/src/superannotate/lib/app/input_converters/converters/coco_converters/sa_vector_to_coco.py index b546d57a..29d836ed 100644 --- a/src/superannotate/lib/app/input_converters/converters/coco_converters/sa_vector_to_coco.py +++ b/src/superannotate/lib/app/input_converters/converters/coco_converters/sa_vector_to_coco.py @@ -40,7 +40,12 @@ def sa_vector_to_coco_object_detection( points["y2"] - points["y1"], ) polygons = bbox - area = int((points["x2"] - points["x1"]) * points["y2"] - points["y1"]) + # COCO bbox area is width * height. `bbox` is already + # (x, y, width, height), so reuse it — the previous expression + # `(x2 - x1) * y2 - y1` was mis-parenthesized (operator precedence made + # it `((x2 - x1) * y2) - y1`), producing a wrong area for every box not + # touching the top edge. Mirrors the keypoint path's `bbox[2] * bbox[3]`. + area = int(bbox[2] * bbox[3]) annotation = make_annotation( category_id, image_info["id"], bbox, polygons, area, anno_id diff --git a/tests/unit/test_coco_object_detection_area.py b/tests/unit/test_coco_object_detection_area.py new file mode 100644 index 00000000..10beb04b --- /dev/null +++ b/tests/unit/test_coco_object_detection_area.py @@ -0,0 +1,39 @@ +from types import SimpleNamespace + +from src.superannotate.lib.app.input_converters.converters.coco_converters.sa_vector_to_coco import ( # noqa: E501 + sa_vector_to_coco_object_detection, +) + + +def test_object_detection_area_is_width_times_height(): + """COCO bbox `area` must be width * height. + + Regression for an operator-precedence bug: `area` was computed as + `(x2 - x1) * y2 - y1`, i.e. `((x2 - x1) * y2) - y1`, instead of + `(x2 - x1) * (y2 - y1)`. For the box below (from the repo's own export + golden fixture) the old expression yields 9682 while the correct area is 437. + """ + captured = {} + + def make_annotation(category_id, image_id, bbox, segmentation, area, anno_id): + captured["bbox"] = bbox + captured["area"] = area + return {"id": anno_id, "bbox": bbox, "area": area} + + image_commons = SimpleNamespace(image_info={"id": 1}) + instances = [ + { + "type": "bbox", + "classId": 5, + "points": {"x1": 437.16, "y1": 341.5, "x2": 465.23, "y2": 357.09}, + } + ] + + _, annotations = sa_vector_to_coco_object_detection( + make_annotation, image_commons, instances, iter([1]) + ) + + assert len(annotations) == 1 + width, height = captured["bbox"][2], captured["bbox"][3] + assert captured["area"] == int(width * height) + assert captured["area"] == 437 # not the buggy 9682