Skip to content

Commit a61ed8b

Browse files
committed
fix: Fix Polygon Repair and Empty Polygon Issues, see obss#1118
1 parent efb21fe commit a61ed8b

File tree

1 file changed

+72
-0
lines changed

1 file changed

+72
-0
lines changed

sahi/postprocess/utils.py

+72
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import numpy as np
55
import torch
66
from shapely.geometry import MultiPolygon, Polygon
7+
from shapely.geometry.collection import GeometryCollection
78

89
from sahi.annotation import BoundingBox, Category, Mask
910
from sahi.prediction import ObjectPrediction
@@ -65,6 +66,71 @@ def tolist(self):
6566
return self.list
6667

6768

69+
def repair_polygon(shapely_polygon: Polygon) -> Polygon:
70+
"""
71+
Fix polygons
72+
:param shapely_polygon: Shapely polygon object
73+
:return:
74+
"""
75+
if not shapely_polygon.is_valid:
76+
fixed_polygon = shapely_polygon.buffer(0)
77+
if fixed_polygon.is_valid:
78+
if isinstance(fixed_polygon, Polygon):
79+
return fixed_polygon
80+
elif isinstance(fixed_polygon, MultiPolygon):
81+
return max(fixed_polygon.geoms, key=lambda p: p.area)
82+
elif isinstance(fixed_polygon, GeometryCollection):
83+
polygons = [geom for geom in fixed_polygon.geoms if isinstance(geom, Polygon)]
84+
return max(polygons, key=lambda p: p.area) if polygons else shapely_polygon
85+
86+
return shapely_polygon
87+
88+
89+
def repair_multipolygon(shapely_multipolygon: MultiPolygon) -> MultiPolygon:
90+
"""
91+
Fix invalid MultiPolygon objects
92+
:param shapely_multipolygon: Imported shapely MultiPolygon object
93+
:return:
94+
"""
95+
if not shapely_multipolygon.is_valid:
96+
fixed_geometry = shapely_multipolygon.buffer(0)
97+
98+
if fixed_geometry.is_valid:
99+
if isinstance(fixed_geometry, MultiPolygon):
100+
return fixed_geometry
101+
elif isinstance(fixed_geometry, Polygon):
102+
return MultiPolygon([fixed_geometry])
103+
elif isinstance(fixed_geometry, GeometryCollection):
104+
polygons = [geom for geom in fixed_geometry.geoms if isinstance(geom, Polygon)]
105+
return MultiPolygon(polygons) if polygons else shapely_multipolygon
106+
107+
return shapely_multipolygon
108+
109+
110+
def coco_segmentation_to_shapely(segmentation: Union[List, List[List]]):
111+
"""
112+
Fix segment data in COCO format
113+
:param segmentation: segment data in COCO format
114+
:return:
115+
"""
116+
if isinstance(segmentation, List) and all([not isinstance(seg, List) for seg in segmentation]):
117+
segmentation = [segmentation]
118+
elif isinstance(segmentation, List) and all([isinstance(seg, List) for seg in segmentation]):
119+
pass
120+
else:
121+
raise ValueError("segmentation must be List or List[List]")
122+
123+
polygon_list = []
124+
125+
for coco_polygon in segmentation:
126+
point_list = list(zip(coco_polygon[::2], coco_polygon[1::2]))
127+
shapely_polygon = Polygon(point_list)
128+
polygon_list.append(repair_polygon(shapely_polygon))
129+
130+
shapely_multipolygon = repair_multipolygon(MultiPolygon(polygon_list))
131+
return shapely_multipolygon
132+
133+
68134
def object_prediction_list_to_torch(object_prediction_list: ObjectPredictionList) -> torch.tensor:
69135
"""
70136
Returns:
@@ -166,6 +232,12 @@ def get_merged_mask(pred1: ObjectPrediction, pred2: ObjectPrediction) -> Mask:
166232
# buffer(0) is a quickhack to fix invalid polygons most of the time
167233
poly1 = get_shapely_multipolygon(mask1.segmentation).buffer(0)
168234
poly2 = get_shapely_multipolygon(mask2.segmentation).buffer(0)
235+
236+
if poly1.is_empty:
237+
poly1 = coco_segmentation_to_shapely(mask1.segmentation)
238+
if poly2.is_empty:
239+
poly2 = coco_segmentation_to_shapely(mask2.segmentation)
240+
169241
union_poly = poly1.union(poly2)
170242
if not hasattr(union_poly, "geoms"):
171243
union_poly = MultiPolygon([union_poly])

0 commit comments

Comments
 (0)