Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Another self-intersection corner case handling #982

Merged
merged 20 commits into from
Aug 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions sahi/models/detectron2.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,9 +149,9 @@ def _create_object_prediction_list_from_original_predictions(
object_prediction_list = [
ObjectPrediction(
bbox=box.tolist() if mask is None else None,
segmentation=get_coco_segmentation_from_bool_mask(mask.detach().cpu().numpy())
if mask is not None
else None,
segmentation=(
get_coco_segmentation_from_bool_mask(mask.detach().cpu().numpy()) if mask is not None else None
),
category_id=category_id.item(),
category_name=self.category_mapping[str(category_id.item())],
shift_amount=shift_amount,
Expand Down
21 changes: 0 additions & 21 deletions sahi/utils/coco.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,28 +224,7 @@ def __init__(
self._shapely_annotation = shapely_annotation

def get_sliced_coco_annotation(self, slice_bbox: List[int]):
def filter_polygons(geometry):
"""
Filters out and returns only Polygon or MultiPolygon components of a geometry.
If geometry is a Polygon, it converts it into a MultiPolygon.
If it's a GeometryCollection, it filters to create a MultiPolygon from any Polygons in the collection.
Returns an empty MultiPolygon if no Polygon or MultiPolygon components are found.
"""
if isinstance(geometry, Polygon):
return MultiPolygon([geometry])
elif isinstance(geometry, MultiPolygon):
return geometry
elif isinstance(geometry, GeometryCollection):
polygons = [geom for geom in geometry.geoms if isinstance(geom, Polygon)]
return MultiPolygon(polygons) if polygons else MultiPolygon()
return MultiPolygon()

shapely_polygon = box(slice_bbox[0], slice_bbox[1], slice_bbox[2], slice_bbox[3])
samp = self._shapely_annotation.multipolygon
if not samp.is_valid:
valid = make_valid(samp)
valid = filter_polygons(valid)
self._shapely_annotation.multipolygon = valid
intersection_shapely_annotation = self._shapely_annotation.get_intersection(shapely_polygon)
return CocoAnnotation.from_shapely_annotation(
intersection_shapely_annotation,
Expand Down
26 changes: 24 additions & 2 deletions sahi/utils/shapely.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@

from typing import List

from shapely.geometry import CAP_STYLE, JOIN_STYLE, MultiPolygon, Polygon, box
from shapely.geometry import CAP_STYLE, JOIN_STYLE, GeometryCollection, MultiPolygon, Polygon, box
from shapely.validation import make_valid


def get_shapely_box(x: int, y: int, width: int, height: int) -> Polygon:
Expand All @@ -21,15 +22,36 @@ def get_shapely_box(x: int, y: int, width: int, height: int) -> Polygon:

def get_shapely_multipolygon(coco_segmentation: List[List]) -> MultiPolygon:
"""
Accepts coco style polygon coords and converts it to shapely multipolygon object
Accepts coco style polygon coords and converts it to valid shapely multipolygon object
"""

def filter_polygons(geometry):
"""
Filters out and returns only Polygon or MultiPolygon components of a geometry.
If geometry is a Polygon, it converts it into a MultiPolygon.
If it's a GeometryCollection, it filters
to create a MultiPolygon from any Polygons in the collection.
Returns an empty MultiPolygon if no Polygon or MultiPolygon components are found.
"""
if isinstance(geometry, Polygon):
return MultiPolygon([geometry])
elif isinstance(geometry, MultiPolygon):
return geometry
elif isinstance(geometry, GeometryCollection):
polygons = [geom for geom in geometry.geoms if isinstance(geom, Polygon)]
return MultiPolygon(polygons) if polygons else MultiPolygon()
return MultiPolygon()

polygon_list = []
for coco_polygon in coco_segmentation:
point_list = list(zip(coco_polygon[0::2], coco_polygon[1::2]))
shapely_polygon = Polygon(point_list)
polygon_list.append(shapely_polygon)
shapely_multipolygon = MultiPolygon(polygon_list)

if not shapely_multipolygon.is_valid:
shapely_multipolygon = filter_polygons(make_valid(shapely_multipolygon))

return shapely_multipolygon


Expand Down
6 changes: 6 additions & 0 deletions tests/test_shapelyutils.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ def test_get_shapely_multipolygon(self):
self.assertEqual(shapely_multipolygon.area, 41177.5)
self.assertTupleEqual(shapely_multipolygon.bounds, (1, 1, 325, 200))

def test_get_shapely_multipolygon_naughty(self):
# self-intersection case
coco_segmentation = [[3559.0, 2046.86, 3.49, 2060.0, 3540.9, 3249.7, 2060.0, 3239.61, 2052.87]]
shapely_multipolygon = get_shapely_multipolygon(coco_segmentation)
self.assertTrue(shapely_multipolygon.is_valid)

def test_shapely_annotation(self):
# init shapely_annotation from coco segmentation
segmentation = [[1, 1, 325, 125.2, 250, 200, 5, 200]]
Expand Down