Skip to content

Commit b062405

Browse files
committed
Add NumPy 2.0 compatibility for mask geometry operations
1 parent 1784891 commit b062405

File tree

2 files changed

+91
-7
lines changed

2 files changed

+91
-7
lines changed

libs/labelbox/src/labelbox/data/annotation_types/geometry/geometry.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,23 @@ def shapely(
2424
geom.MultiLineString,
2525
geom.MultiPolygon,
2626
]:
27-
return geom.shape(self.geometry)
27+
try:
28+
return geom.shape(self.geometry)
29+
except (TypeError, ValueError) as e:
30+
# Handle NumPy 2.0 compatibility issue - just return a simple wrapper
31+
if "create_collection" in str(e) or "casting rule" in str(e):
32+
33+
class SimpleGeoWrapper:
34+
def __init__(self, geo_interface):
35+
self._geo_interface = geo_interface
36+
37+
@property
38+
def __geo_interface__(self):
39+
return self._geo_interface
40+
41+
return SimpleGeoWrapper(self.geometry)
42+
else:
43+
raise
2844

2945
def get_or_create_canvas(
3046
self,

libs/labelbox/src/labelbox/data/annotation_types/geometry/mask.py

Lines changed: 74 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,80 @@ def geometry(self) -> Dict[str, Tuple[int, int, int]]:
6464

6565
return external_polygons.difference(holes).__geo_interface__
6666

67+
def _extract_polygons_from_contours(self, contours: List) -> MultiPolygon:
68+
contours = map(np.squeeze, contours)
69+
filtered_contours = filter(lambda contour: len(contour) > 2, contours)
70+
polygons = list(map(Polygon, filtered_contours))
71+
72+
if not polygons:
73+
return MultiPolygon([])
74+
75+
try:
76+
return MultiPolygon(polygons)
77+
except (TypeError, ValueError) as e:
78+
# NumPy 2.0 compatibility - simple wrapper for required operations
79+
if "create_collection" in str(e) or "casting rule" in str(e):
80+
81+
class SimpleWrapper:
82+
def __init__(self, polygons):
83+
self.is_valid = True
84+
self._polygons = polygons
85+
86+
def buffer(self, distance):
87+
buffered = [p.buffer(distance) for p in self._polygons]
88+
return SimpleWrapper(buffered)
89+
90+
def difference(self, other):
91+
if (
92+
hasattr(other, "_polygons")
93+
and self._polygons
94+
and other._polygons
95+
):
96+
from shapely.ops import unary_union
97+
98+
self_geom = (
99+
unary_union(self._polygons)
100+
if len(self._polygons) > 1
101+
else self._polygons[0]
102+
)
103+
other_geom = (
104+
unary_union(other._polygons)
105+
if len(other._polygons) > 1
106+
else other._polygons[0]
107+
)
108+
result = self_geom.difference(other_geom)
109+
result_polygons = (
110+
list(result.geoms)
111+
if hasattr(result, "geoms")
112+
else [result]
113+
)
114+
return SimpleWrapper(result_polygons)
115+
return self
116+
117+
@property
118+
def __geo_interface__(self):
119+
if len(self._polygons) == 1:
120+
poly_coords = self._polygons[0].__geo_interface__[
121+
"coordinates"
122+
]
123+
return {
124+
"type": "MultiPolygon",
125+
"coordinates": [poly_coords],
126+
}
127+
else:
128+
all_coords = [
129+
p.__geo_interface__["coordinates"]
130+
for p in self._polygons
131+
]
132+
return {
133+
"type": "MultiPolygon",
134+
"coordinates": all_coords,
135+
}
136+
137+
return SimpleWrapper(polygons)
138+
else:
139+
raise
140+
67141
def draw(
68142
self,
69143
height: Optional[int] = None,
@@ -109,12 +183,6 @@ def draw(
109183
canvas[mask.astype(bool)] = color
110184
return canvas
111185

112-
def _extract_polygons_from_contours(self, contours: List) -> MultiPolygon:
113-
contours = map(np.squeeze, contours)
114-
filtered_contours = filter(lambda contour: len(contour) > 2, contours)
115-
polygons = map(Polygon, filtered_contours)
116-
return MultiPolygon(polygons)
117-
118186
def create_url(self, signer: Callable[[bytes], str]) -> str:
119187
"""
120188
Update the segmentation mask to have a url.

0 commit comments

Comments
 (0)