Skip to content

Commit d282040

Browse files
authored
[Validate] Use Shapely for 2D polygon calculations (#279)
* Use Shapely for 2D bounding box and poly metrics as well * Adapt tests * Add poly_* function to AvailableEvalFunction
1 parent 684fe39 commit d282040

File tree

7 files changed

+315
-273
lines changed

7 files changed

+315
-273
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ All notable changes to the [Nucleus Python Client](https://github.com/scaleapi/n
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [0.10.2](https://github.com/scaleapi/nucleus-python-client/releases/tag/v0.10.2) - 2022-04-22
9+
10+
### Fixed
11+
- Polygon and bounding box matching uses Shapely again providing faster evaluations
12+
813
## [0.10.1](https://github.com/scaleapi/nucleus-python-client/releases/tag/v0.10.1) - 2022-04-21
914

1015
### Added

nucleus/metrics/filters.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,15 @@
22

33
from nucleus.prediction import PredictionList
44

5-
from .polygon_utils import (
6-
BoxOrPolygonAnnoOrPred,
7-
polygon_annotation_to_geometry,
8-
)
5+
from .polygon_utils import BoxOrPolygonAnnoOrPred, polygon_annotation_to_shape
96

107

118
def polygon_area_filter(
129
polygons: List[BoxOrPolygonAnnoOrPred], min_area: float, max_area: float
1310
) -> List[BoxOrPolygonAnnoOrPred]:
1411
filter_fn = (
1512
lambda polygon: min_area
16-
<= polygon_annotation_to_geometry(polygon).signed_area
13+
<= polygon_annotation_to_shape(polygon)
1714
<= max_area
1815
)
1916
return list(filter(filter_fn, polygons))

nucleus/metrics/geometry.py

Lines changed: 0 additions & 198 deletions
This file was deleted.

nucleus/metrics/polygon_utils.py

Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@
44

55
import numpy as np
66
from scipy.optimize import linear_sum_assignment
7+
from shapely.geometry import Polygon
78

89
from nucleus.annotation import BoxAnnotation, PolygonAnnotation
910
from nucleus.prediction import BoxPrediction, PolygonPrediction
1011

1112
from .base import ScalarResult
1213
from .errors import PolygonAnnotationTypeError
13-
from .geometry import GeometryPolygon, polygon_intersection_area
1414

1515
BoxOrPolygonPrediction = TypeVar(
1616
"BoxOrPolygonPrediction", BoxPrediction, PolygonPrediction
@@ -27,33 +27,31 @@
2727
)
2828

2929

30-
def polygon_annotation_to_geometry(
30+
def polygon_annotation_to_shape(
3131
annotation: BoxOrPolygonAnnotation,
32-
) -> GeometryPolygon:
32+
) -> Polygon:
3333
if isinstance(annotation, BoxAnnotation):
3434
xmin = annotation.x - annotation.width / 2
3535
xmax = annotation.x + annotation.width / 2
3636
ymin = annotation.y - annotation.height / 2
3737
ymax = annotation.y + annotation.height / 2
38-
points = [(xmin, ymin), (xmax, ymin), (xmax, ymax), (xmin, ymax)]
39-
return GeometryPolygon(points=points, is_rectangle=True)
40-
elif isinstance(annotation, PolygonAnnotation):
41-
return GeometryPolygon(
42-
points=[(point.x, point.y) for point in annotation.vertices],
43-
is_rectangle=False,
38+
return Polygon(
39+
[(xmin, ymin), (xmax, ymin), (xmax, ymax), (xmin, ymax)]
4440
)
41+
elif isinstance(annotation, PolygonAnnotation):
42+
return Polygon([(point.x, point.y) for point in annotation.vertices])
4543
else:
4644
raise PolygonAnnotationTypeError()
4745

4846

49-
def _iou(annotation: GeometryPolygon, prediction: GeometryPolygon) -> float:
50-
intersection = polygon_intersection_area(annotation, prediction)
47+
def _iou(annotation: Polygon, prediction: Polygon) -> float:
48+
intersection = annotation.intersection(prediction).area
5149
union = annotation.area + prediction.area - intersection
5250
return intersection / max(union, sys.float_info.epsilon)
5351

5452

5553
def _iou_matrix(
56-
annotations: List[GeometryPolygon], predictions: List[GeometryPolygon]
54+
annotations: List[Polygon], predictions: List[Polygon]
5755
) -> np.ndarray:
5856
iou_matrix = np.empty((len(predictions), len(annotations)))
5957
for i, prediction in enumerate(predictions):
@@ -80,13 +78,9 @@ def _iou_assignments_for_same_reference_id(
8078
len(reference_ids) <= 1
8179
), "Expected annotations and predictions to have same reference ID."
8280

83-
# Convert annotation and predictions to GeometryPolygon objects
84-
polygon_annotations = list(
85-
map(polygon_annotation_to_geometry, annotations)
86-
)
87-
polygon_predictions = list(
88-
map(polygon_annotation_to_geometry, predictions)
89-
)
81+
# Convert annotation and predictions to shapely.geometry.Polygon objects
82+
polygon_annotations = list(map(polygon_annotation_to_shape, annotations))
83+
polygon_predictions = list(map(polygon_annotation_to_shape, predictions))
9084

9185
# Compute IoU matrix and set IoU values below the threshold to 0.
9286
iou_matrix = _iou_matrix(polygon_annotations, polygon_predictions)

0 commit comments

Comments
 (0)