Skip to content

Commit 669277e

Browse files
josselinbuilsJosselin BUILS
andauthored
fix(lab-2558): fix area property of COCO export and ignore empty bboxes (#1735)
Co-authored-by: Josselin BUILS <josselin.buils@kili-technology.com>
1 parent 00ee448 commit 669277e

2 files changed

Lines changed: 38 additions & 3 deletions

File tree

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ dependencies = [
4949
"filelock >= 3.0.0, < 4.0.0",
5050
"pip-system-certs >= 4.0.0, < 5.0.0; platform_system=='Windows'",
5151
"pyrate-limiter >= 3, < 4",
52+
"shapely >= 1.8, < 3",
5253
]
5354
urls = { homepage = "https://github.com/kili-technology/kili-python-sdk" }
5455

src/kili/services/export/format/coco/__init__.py

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@
66
from pathlib import Path
77
from typing import Any, Dict, List, Optional, Tuple
88

9+
import numpy as np
10+
from shapely.geometry import Polygon
11+
from shapely.ops import polygonize
12+
from shapely.validation import make_valid
13+
914
from kili.domain.ontology import JobMLTask, JobTool
1015
from kili.services.export.exceptions import (
1116
NoCompatibleJobError,
@@ -412,12 +417,15 @@ def _get_coco_image_annotations(
412417
print("continue")
413418
continue
414419
bounding_poly = annotation["boundingPoly"]
415-
bbox, poly = _get_coco_geometry_from_kili_bpoly(
420+
area, bbox, poly = _get_coco_geometry_from_kili_bpoly(
416421
bounding_poly, coco_image["width"], coco_image["height"]
417422
)
418423
if len(poly) < 6: # twice the number of vertices
419424
print("A polygon must contain more than 2 points. Skipping this polygon...")
420425
continue
426+
if bbox[2] == 0 and bbox[3] == 0:
427+
print("An annotation with zero dimensions has been ignored.")
428+
continue
421429

422430
categories = annotation["categories"]
423431
coco_annotation = CocoAnnotation(
@@ -429,7 +437,7 @@ def _get_coco_image_annotations(
429437
# But a type of object can appear several times on the same image.
430438
# The limitation of the single connected part comes from Kili.
431439
segmentation=[poly],
432-
area=coco_image["height"] * coco_image["width"],
440+
area=area,
433441
iscrowd=0,
434442
)
435443

@@ -442,6 +450,23 @@ def _get_coco_image_annotations(
442450
return coco_annotations, annotation_j
443451

444452

453+
# Shoelace formula, implementation from https://stackoverflow.com/a/30408825.
454+
def _get_shoelace_area(x: List[float], y: List[float]):
455+
# Split self intersecting polygon into multiple polygons to compute area safely.
456+
polygon = Polygon(np.c_[x, y])
457+
polygons = polygonize(make_valid(polygon))
458+
459+
area = 0
460+
461+
for poly in polygons:
462+
p_xx, p_yy = poly.exterior.coords.xy
463+
p_x = p_xx.tolist()
464+
p_y = p_yy.tolist()
465+
area += 0.5 * np.abs(np.dot(p_x, np.roll(p_y, 1)) - np.dot(p_y, np.roll(p_x, 1)))
466+
467+
return area
468+
469+
445470
def _get_coco_geometry_from_kili_bpoly(
446471
bounding_poly: List[Dict], asset_width: int, asset_height: int
447472
):
@@ -452,9 +477,18 @@ def _get_coco_geometry_from_kili_bpoly(
452477
x_min, y_min = min(p_x), min(p_y)
453478
x_max, y_max = max(p_x), max(p_y)
454479
bbox_width, bbox_height = x_max - x_min, y_max - y_min
480+
area = _get_shoelace_area(p_x, p_y)
481+
482+
# Compute and remove negative area
483+
if len(bounding_poly) > 1:
484+
negative_normalized_vertices = bounding_poly[1]["normalizedVertices"]
485+
np_x = [float(vertice["x"]) * asset_width for vertice in negative_normalized_vertices]
486+
np_y = [float(vertice["y"]) * asset_height for vertice in negative_normalized_vertices]
487+
area -= _get_shoelace_area(np_x, np_y)
488+
455489
bbox = [int(x_min), int(y_min), int(bbox_width), int(bbox_height)]
456490
poly = [p for vertice in poly_vertices for p in vertice]
457-
return bbox, poly
491+
return area, bbox, poly
458492

459493

460494
def _get_coco_categories(cat_kili_id_to_coco_id, merged) -> List[CocoCategory]:

0 commit comments

Comments
 (0)