@@ -37,6 +37,7 @@ class Mask(Geometry):
37
37
38
38
@property
39
39
def geometry (self ) -> Dict [str , Tuple [int , int , int ]]:
40
+ # Extract mask contours and build geometry
40
41
mask = self .draw (color = 1 )
41
42
contours , hierarchy = cv2 .findContours (
42
43
image = mask , mode = cv2 .RETR_TREE , method = cv2 .CHAIN_APPROX_NONE
@@ -62,7 +63,42 @@ def geometry(self) -> Dict[str, Tuple[int, int, int]]:
62
63
if not holes .is_valid :
63
64
holes = holes .buffer (0 )
64
65
65
- return external_polygons .difference (holes ).__geo_interface__
66
+ # Get geometry result
67
+ result_geometry = external_polygons .difference (holes )
68
+
69
+ # Ensure consistent MultiPolygon format across shapely versions
70
+ if (
71
+ hasattr (result_geometry , "geom_type" )
72
+ and result_geometry .geom_type == "Polygon"
73
+ ):
74
+ result_geometry = MultiPolygon ([result_geometry ])
75
+
76
+ geometry_dict = result_geometry .__geo_interface__
77
+ return self ._normalize_coordinates (geometry_dict )
78
+
79
+ def _normalize_coordinates (self , geometry_dict ):
80
+ """Normalize coordinates to ensure consistent tuple format across shapely versions"""
81
+
82
+ def ensure_tuple_coords (obj ):
83
+ """Recursively ensure coordinate pairs are tuples"""
84
+ if isinstance (obj , (list , tuple )):
85
+ # Check if this is a coordinate pair [x, y]
86
+ if len (obj ) == 2 and all (
87
+ isinstance (x , (int , float )) for x in obj
88
+ ):
89
+ return (float (obj [0 ]), float (obj [1 ]))
90
+ else :
91
+ # Recursively process nested structures, preserving list/tuple types
92
+ if isinstance (obj , list ):
93
+ return [ensure_tuple_coords (item ) for item in obj ]
94
+ else :
95
+ return tuple (ensure_tuple_coords (item ) for item in obj )
96
+ return obj
97
+
98
+ result = geometry_dict .copy ()
99
+ if "coordinates" in result :
100
+ result ["coordinates" ] = ensure_tuple_coords (result ["coordinates" ])
101
+ return result
66
102
67
103
def draw (
68
104
self ,
0 commit comments