python-wml 3.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of python-wml might be problematic. Click here for more details.

Files changed (164) hide show
  1. python_wml-3.0.0.dist-info/LICENSE +23 -0
  2. python_wml-3.0.0.dist-info/METADATA +51 -0
  3. python_wml-3.0.0.dist-info/RECORD +164 -0
  4. python_wml-3.0.0.dist-info/WHEEL +5 -0
  5. python_wml-3.0.0.dist-info/top_level.txt +1 -0
  6. wml/__init__.py +0 -0
  7. wml/basic_data_def/__init__.py +2 -0
  8. wml/basic_data_def/detection_data_def.py +279 -0
  9. wml/basic_data_def/io_data_def.py +2 -0
  10. wml/basic_img_utils.py +816 -0
  11. wml/img_patch.py +92 -0
  12. wml/img_utils.py +571 -0
  13. wml/iotoolkit/__init__.py +17 -0
  14. wml/iotoolkit/aic_keypoint.py +115 -0
  15. wml/iotoolkit/baidu_mask_toolkit.py +244 -0
  16. wml/iotoolkit/base_dataset.py +210 -0
  17. wml/iotoolkit/bboxes_statistics.py +515 -0
  18. wml/iotoolkit/build.py +0 -0
  19. wml/iotoolkit/cityscapes_toolkit.py +183 -0
  20. wml/iotoolkit/classification_data_statistics.py +25 -0
  21. wml/iotoolkit/coco_data_fwd.py +225 -0
  22. wml/iotoolkit/coco_keypoints.py +118 -0
  23. wml/iotoolkit/coco_keypoints_fmt2.py +103 -0
  24. wml/iotoolkit/coco_toolkit.py +397 -0
  25. wml/iotoolkit/coco_wholebody.py +269 -0
  26. wml/iotoolkit/common.py +108 -0
  27. wml/iotoolkit/crowd_pose.py +146 -0
  28. wml/iotoolkit/fast_labelme.py +110 -0
  29. wml/iotoolkit/image_folder.py +95 -0
  30. wml/iotoolkit/imgs_cache.py +58 -0
  31. wml/iotoolkit/imgs_reader_mt.py +73 -0
  32. wml/iotoolkit/labelme_base.py +102 -0
  33. wml/iotoolkit/labelme_json_to_img.py +49 -0
  34. wml/iotoolkit/labelme_toolkit.py +117 -0
  35. wml/iotoolkit/labelme_toolkit_fwd.py +733 -0
  36. wml/iotoolkit/labelmemckeypoints_dataset.py +169 -0
  37. wml/iotoolkit/lspet.py +48 -0
  38. wml/iotoolkit/mapillary_vistas_toolkit.py +269 -0
  39. wml/iotoolkit/mat_data.py +90 -0
  40. wml/iotoolkit/mckeypoints_statistics.py +28 -0
  41. wml/iotoolkit/mot_datasets.py +62 -0
  42. wml/iotoolkit/mpii.py +108 -0
  43. wml/iotoolkit/npmckeypoints_dataset.py +164 -0
  44. wml/iotoolkit/o365_to_coco.py +136 -0
  45. wml/iotoolkit/object365_toolkit.py +156 -0
  46. wml/iotoolkit/object365v2_toolkit.py +71 -0
  47. wml/iotoolkit/pascal_voc_data.py +51 -0
  48. wml/iotoolkit/pascal_voc_toolkit.py +194 -0
  49. wml/iotoolkit/pascal_voc_toolkit_fwd.py +473 -0
  50. wml/iotoolkit/penn_action.py +57 -0
  51. wml/iotoolkit/rawframe_dataset.py +129 -0
  52. wml/iotoolkit/rewrite_pascal_voc.py +28 -0
  53. wml/iotoolkit/semantic_data.py +49 -0
  54. wml/iotoolkit/split_file_by_type.py +29 -0
  55. wml/iotoolkit/sports_mot_datasets.py +78 -0
  56. wml/iotoolkit/vis_objectdetection_dataset.py +70 -0
  57. wml/iotoolkit/vis_torch_data.py +39 -0
  58. wml/iotoolkit/yolo_toolkit.py +38 -0
  59. wml/object_detection2/__init__.py +4 -0
  60. wml/object_detection2/basic_visualization.py +37 -0
  61. wml/object_detection2/bboxes.py +812 -0
  62. wml/object_detection2/data_process_toolkit.py +146 -0
  63. wml/object_detection2/keypoints.py +292 -0
  64. wml/object_detection2/mask.py +120 -0
  65. wml/object_detection2/metrics/__init__.py +3 -0
  66. wml/object_detection2/metrics/build.py +15 -0
  67. wml/object_detection2/metrics/classifier_toolkit.py +440 -0
  68. wml/object_detection2/metrics/common.py +71 -0
  69. wml/object_detection2/metrics/mckps_toolkit.py +338 -0
  70. wml/object_detection2/metrics/toolkit.py +1953 -0
  71. wml/object_detection2/npod_toolkit.py +361 -0
  72. wml/object_detection2/odtools.py +243 -0
  73. wml/object_detection2/standard_names.py +75 -0
  74. wml/object_detection2/visualization.py +956 -0
  75. wml/object_detection2/wmath.py +34 -0
  76. wml/semantic/__init__.py +0 -0
  77. wml/semantic/basic_toolkit.py +65 -0
  78. wml/semantic/mask_utils.py +156 -0
  79. wml/semantic/semantic_test.py +21 -0
  80. wml/semantic/structures.py +1 -0
  81. wml/semantic/toolkit.py +105 -0
  82. wml/semantic/visualization_utils.py +658 -0
  83. wml/threadtoolkit.py +50 -0
  84. wml/walgorithm.py +228 -0
  85. wml/wcollections.py +212 -0
  86. wml/wfilesystem.py +487 -0
  87. wml/wml_utils.py +657 -0
  88. wml/wstructures/__init__.py +4 -0
  89. wml/wstructures/common.py +9 -0
  90. wml/wstructures/keypoints_train_toolkit.py +149 -0
  91. wml/wstructures/kps_structures.py +579 -0
  92. wml/wstructures/mask_structures.py +1161 -0
  93. wml/wtorch/__init__.py +8 -0
  94. wml/wtorch/bboxes.py +104 -0
  95. wml/wtorch/classes_suppression.py +24 -0
  96. wml/wtorch/conv_module.py +181 -0
  97. wml/wtorch/conv_ws.py +144 -0
  98. wml/wtorch/data/__init__.py +16 -0
  99. wml/wtorch/data/_utils/__init__.py +45 -0
  100. wml/wtorch/data/_utils/collate.py +183 -0
  101. wml/wtorch/data/_utils/fetch.py +47 -0
  102. wml/wtorch/data/_utils/pin_memory.py +121 -0
  103. wml/wtorch/data/_utils/signal_handling.py +72 -0
  104. wml/wtorch/data/_utils/worker.py +227 -0
  105. wml/wtorch/data/base_data_loader_iter.py +93 -0
  106. wml/wtorch/data/dataloader.py +501 -0
  107. wml/wtorch/data/datapipes/__init__.py +1 -0
  108. wml/wtorch/data/datapipes/iter/__init__.py +12 -0
  109. wml/wtorch/data/datapipes/iter/batch.py +126 -0
  110. wml/wtorch/data/datapipes/iter/callable.py +92 -0
  111. wml/wtorch/data/datapipes/iter/listdirfiles.py +37 -0
  112. wml/wtorch/data/datapipes/iter/loadfilesfromdisk.py +30 -0
  113. wml/wtorch/data/datapipes/iter/readfilesfromtar.py +60 -0
  114. wml/wtorch/data/datapipes/iter/readfilesfromzip.py +63 -0
  115. wml/wtorch/data/datapipes/iter/sampler.py +94 -0
  116. wml/wtorch/data/datapipes/utils/__init__.py +0 -0
  117. wml/wtorch/data/datapipes/utils/common.py +65 -0
  118. wml/wtorch/data/dataset.py +354 -0
  119. wml/wtorch/data/datasets/__init__.py +4 -0
  120. wml/wtorch/data/datasets/common.py +53 -0
  121. wml/wtorch/data/datasets/listdirfilesdataset.py +36 -0
  122. wml/wtorch/data/datasets/loadfilesfromdiskdataset.py +30 -0
  123. wml/wtorch/data/distributed.py +135 -0
  124. wml/wtorch/data/multi_processing_data_loader_iter.py +866 -0
  125. wml/wtorch/data/sampler.py +267 -0
  126. wml/wtorch/data/single_process_data_loader_iter.py +24 -0
  127. wml/wtorch/data/test_data_loader.py +26 -0
  128. wml/wtorch/dataset_toolkit.py +67 -0
  129. wml/wtorch/depthwise_separable_conv_module.py +98 -0
  130. wml/wtorch/dist.py +591 -0
  131. wml/wtorch/dropblock/__init__.py +6 -0
  132. wml/wtorch/dropblock/dropblock.py +228 -0
  133. wml/wtorch/dropblock/dropout.py +40 -0
  134. wml/wtorch/dropblock/scheduler.py +48 -0
  135. wml/wtorch/ema.py +61 -0
  136. wml/wtorch/fc_module.py +73 -0
  137. wml/wtorch/functional.py +34 -0
  138. wml/wtorch/iter_dataset.py +26 -0
  139. wml/wtorch/loss.py +69 -0
  140. wml/wtorch/nets/__init__.py +0 -0
  141. wml/wtorch/nets/ckpt_toolkit.py +219 -0
  142. wml/wtorch/nets/fpn.py +276 -0
  143. wml/wtorch/nets/hrnet/__init__.py +0 -0
  144. wml/wtorch/nets/hrnet/config.py +2 -0
  145. wml/wtorch/nets/hrnet/hrnet.py +494 -0
  146. wml/wtorch/nets/misc.py +249 -0
  147. wml/wtorch/nets/resnet/__init__.py +0 -0
  148. wml/wtorch/nets/resnet/layers/__init__.py +17 -0
  149. wml/wtorch/nets/resnet/layers/aspp.py +144 -0
  150. wml/wtorch/nets/resnet/layers/batch_norm.py +231 -0
  151. wml/wtorch/nets/resnet/layers/blocks.py +111 -0
  152. wml/wtorch/nets/resnet/layers/wrappers.py +110 -0
  153. wml/wtorch/nets/resnet/r50_config.py +38 -0
  154. wml/wtorch/nets/resnet/resnet.py +691 -0
  155. wml/wtorch/nets/shape_spec.py +20 -0
  156. wml/wtorch/nets/simple_fpn.py +101 -0
  157. wml/wtorch/nms.py +109 -0
  158. wml/wtorch/nn.py +896 -0
  159. wml/wtorch/ocr_block.py +193 -0
  160. wml/wtorch/summary.py +331 -0
  161. wml/wtorch/train_toolkit.py +603 -0
  162. wml/wtorch/transformer_blocks.py +266 -0
  163. wml/wtorch/utils.py +719 -0
  164. wml/wtorch/wlr_scheduler.py +100 -0
@@ -0,0 +1,1161 @@
1
+ import numpy as np
2
+ import copy
3
+ import cv2
4
+ from wml.semantic import basic_toolkit as bmt
5
+ from wml.semantic.mask_utils import get_bboxes_by_contours,npresize_mask
6
+ import wml.object_detection2.bboxes as odb
7
+ import wml.basic_img_utils as bwmli
8
+ import wml.object_detection2.bboxes as odb
9
+ from wml.semantic.basic_toolkit import findContours
10
+ from .common import WBaseMaskLike
11
+ import traceback
12
+ import sys
13
+
14
+ WBaseMask = WBaseMaskLike
15
+
16
+
17
+ class WPolygonMaskItem:
18
+ def __init__(self,points,*,width=None,height=None):
19
+ '''
20
+ points: list[[N,2]],
21
+ example:
22
+ [np.zeros(3,2),np.zeros(11,2)]
23
+ '''
24
+ for p in points:
25
+ if len(p.shape)!=2 or p.shape[1]!=2:
26
+ raise RuntimeError(f"ERROR: error polygon mask item points, p shape {p.shape}, expected [N,2]")
27
+ self.points = [p.copy().astype(np.int) for p in points] # shape of p is [Ni,2]
28
+ self.width = width
29
+ self.height = height
30
+
31
+ def copy(self):
32
+ return WPolygonMaskItem(self.points,width=self.width,height=self.height)
33
+
34
+ def bitmap(self,width=None,height=None):
35
+ '''
36
+ return: [H,W]
37
+ '''
38
+ if width is None:
39
+ width = self.width
40
+ if height is None:
41
+ height = self.height
42
+ mask = np.zeros(shape=[height,width],dtype=np.uint8)
43
+ if len(self.points)>0 and len(self.points[0])>0:
44
+ mask = cv2.drawContours(mask,self.points,-1,color=(1),thickness=cv2.FILLED)
45
+ return mask
46
+
47
+ def resize(self,size,width=None,height=None):
48
+ '''
49
+ size:[w,h]
50
+ '''
51
+ if len(self.points)==0:
52
+ return self
53
+ if width is None:
54
+ width = self.width
55
+ if height is None:
56
+ height = self.height
57
+
58
+ w_scale = size[0]/width
59
+ h_scale = size[1]/height
60
+ scale = np.array([[w_scale,h_scale]],dtype=np.float32)
61
+ ori_type = self.points[0].dtype
62
+ points = [p.astype(np.float32)*scale for p in self.points]
63
+ self.points = [p.astype(ori_type) for p in points]
64
+ self.width = size[0]
65
+ self.height = size[1]
66
+
67
+ return self
68
+
69
+ def flip(self,direction,width=None,height=None):
70
+ if len(self.points)==0:
71
+ return self
72
+ if width is None:
73
+ width = self.width
74
+ if height is None:
75
+ height = self.height
76
+
77
+ if direction == WBaseMask.HORIZONTAL:
78
+ new_points = []
79
+ for p in self.points:
80
+ p[:,0] = width-p[:,0]
81
+ p = p[::-1,:]
82
+ new_points.append(p)
83
+ elif direction == WBaseMask.VERTICAL:
84
+ new_points = []
85
+ for p in self.points:
86
+ p = p[::-1,:]
87
+ p[:,1] = height-p[:,1]
88
+ new_points.append(p)
89
+ elif direction == WBaseMask.DIAGONAL:
90
+ new_points = []
91
+ for p in self.points:
92
+ p[:,0] = width-p[:,0]
93
+ p[:,1] = height-p[:,1]
94
+ new_points.append(p)
95
+ else:
96
+ info = f"unknow flip direction {direction}"
97
+ print(f"ERROR: {info}")
98
+ raise RuntimeError(info)
99
+
100
+ self.points = new_points
101
+
102
+ return self
103
+
104
+ def crop(self, bbox):
105
+ '''
106
+ bbox: [x0,y0,x1,y1]
107
+ offset: [xoffset,yoffset]
108
+ 如果offset is not None, 先offset再用bbox crop
109
+ '''
110
+ if not isinstance(bbox,np.ndarray):
111
+ bbox = np.array(bbox)
112
+ assert bbox.ndim == 1
113
+
114
+ # clip the boundary
115
+ bbox = bbox.astype(np.int32).copy()
116
+ bbox[0::2] = np.clip(bbox[0::2], 0, self.width-1)
117
+ bbox[1::2] = np.clip(bbox[1::2], 0, self.height-1)
118
+ x1, y1, x2, y2 = bbox
119
+ w = np.maximum(x2 - x1+1, 1)
120
+ h = np.maximum(y2 - y1+1, 1)
121
+
122
+ if len(self.points) == 0:
123
+ cropped_masks = WPolygonMaskItem([], height=h, width=w)
124
+ else:
125
+ cropped_masks = []
126
+ for ps in self.points:
127
+ x0 = np.min(ps[:,0])
128
+ y0 = np.min(ps[:,1])
129
+ x1 = np.max(ps[:,0])
130
+ y1 = np.max(ps[:,1])
131
+ obbox = np.array([x0,y0,x1,y1])
132
+ iou = odb.npbboxes_intersection_of_box0(obbox[None,:],bbox[None,:])
133
+ if iou<=1e-3: #如果剪切框与当前多边形的外包框没有交集,那么剪切的结果为空
134
+ continue
135
+
136
+ e_bbox = odb.bbox_of_boxes(np.stack([bbox,obbox],axis=0)).astype(np.int32)
137
+
138
+ if 0==bbox[0] and 0==bbox[1] and x1<=bbox[2] and y2<=bbox[3]:
139
+ f_poly_masks = [ps.copy()]
140
+ elif odb.equal_bboxes(bbox,e_bbox):
141
+ sub_cropped_masks = WPolygonMaskItem([ps.copy()],width=self.width,height=self.height)
142
+ f_cropped_masks = sub_cropped_masks.simple_crop(e_bbox)
143
+ f_poly_masks = f_cropped_masks.points
144
+ else:
145
+ sub_cropped_masks = WPolygonMaskItem([ps.copy()],width=self.width,height=self.height)
146
+ f_cropped_masks = sub_cropped_masks.simple_crop(e_bbox)
147
+ f_bitmap_masks = WBitmapMasks(f_cropped_masks.bitmap()[None])
148
+ n_crop_bbox = bbox-np.array([e_bbox[0],e_bbox[1],e_bbox[0],e_bbox[1]])
149
+ f_bitmap_masks = f_bitmap_masks.crop(n_crop_bbox)
150
+ t_masks,res_bboxes,keep = f_bitmap_masks.polygon()
151
+ if not keep[0]:
152
+ continue
153
+ f_poly_masks = t_masks[0]
154
+
155
+ cropped_masks.extend(f_poly_masks)
156
+ cropped_masks = WPolygonMaskItem(cropped_masks, width=w,height=h)
157
+ return cropped_masks
158
+
159
+ def simple_crop(self, bbox):
160
+ '''
161
+ 只处理mask完全保留或者masp完全不保留的情况
162
+ bbox: [x0,y0,x1,y1]
163
+ bbox的值可能为负值,如一个大的mask旋转后
164
+ offset: [xoffset,yoffset]
165
+ 如果offset is not None, 先offset再用bbox crop
166
+ '''
167
+ if not isinstance(bbox,np.ndarray):
168
+ bbox = np.array(bbox)
169
+ assert bbox.ndim == 1
170
+
171
+ # clip the boundary
172
+ bbox = bbox.copy()
173
+ x1, y1, x2, y2 = bbox
174
+ w = np.maximum(x2 - x1+1, 1)
175
+ h = np.maximum(y2 - y1+1, 1)
176
+
177
+ if len(self.points) == 0:
178
+ cropped_masks = WPolygonMaskItem([], height=h, width=w)
179
+ else:
180
+ cropped_masks = []
181
+ for ps in self.points:
182
+ x0 = np.min(ps[:,0])
183
+ y0 = np.min(ps[:,1])
184
+ x1 = np.max(ps[:,0])
185
+ y1 = np.max(ps[:,1])
186
+ obbox = np.array([x0,y0,x1,y1])
187
+ iou = odb.npbboxes_intersection_of_box0(obbox[None,:],bbox[None,:])
188
+ if iou<=1e-3: #如果剪切框与当前多边形的外包框没有交集,那么剪切的结果为空
189
+ continue
190
+ ps = ps.copy()
191
+ ps[:,0] = ps[:,0] - bbox[0]
192
+ ps[:,1] = ps[:,1] - bbox[1]
193
+ ps[:,0] = np.clip(ps[:,0],0,bbox[2]-bbox[0]+1)
194
+ ps[:,1] = np.clip(ps[:,1],0,bbox[3]-bbox[1]+1)
195
+
196
+ x0 = np.min(ps[:,0])
197
+ y0 = np.min(ps[:,1])
198
+ x1 = np.max(ps[:,0])
199
+ y1 = np.max(ps[:,1])
200
+ obbox = np.array([x0,y0,x1,y1])
201
+ if odb.area(obbox)<=1:
202
+ continue
203
+
204
+ cropped_masks.append(ps)
205
+ cropped_masks = WPolygonMaskItem(cropped_masks, width=w,height=h)
206
+ return cropped_masks
207
+
208
+ def rotate(self, out_shape, angle, center=None, scale=1.0, fill_val=0):
209
+ #out_shape: [h,w]
210
+ """See :func:`BaseInstanceMasks.rotate`."""
211
+ if len(self.points) == 0:
212
+ rotated_masks = WPolygonMaskItem([], width=out_shape[1],height=out_shape[0])
213
+ else:
214
+ rotated_masks = []
215
+ rotate_matrix = cv2.getRotationMatrix2D(center, -angle, scale)
216
+ for p in self.points:
217
+ coords = p.copy()
218
+ # pad 1 to convert from format [x, y] to homogeneous
219
+ # coordinates format [x, y, 1]
220
+ coords = np.concatenate(
221
+ (coords, np.ones((coords.shape[0], 1), coords.dtype)),
222
+ axis=1) # [n, 3]
223
+ rotated_coords = np.matmul(
224
+ rotate_matrix[None, :, :],
225
+ coords[:, :, None])[..., 0] # [n, 2, 1] -> [n, 2]
226
+ rotated_masks.append(rotated_coords[:,:2])
227
+ rotated_masks = WPolygonMaskItem(rotated_masks, width=out_shape[1],height=out_shape[0])
228
+ rotated_masks = rotated_masks.crop(np.array([0,0,out_shape[1]-1,out_shape[0]-1]))
229
+ return rotated_masks
230
+
231
+ def warp_affine(self,M,out_shape,fill_val=0):
232
+ #out_shape: [h,w]
233
+ """See :func:`BaseInstanceMasks.rotate`."""
234
+ if len(self.points) == 0:
235
+ affined_masks = WPolygonMaskItem([], width=out_shape[1],height=out_shape[0])
236
+ else:
237
+ affined_masks = []
238
+ for p in self.points:
239
+ coords = p.copy()
240
+ # pad 1 to convert from format [x, y] to homogeneous
241
+ # coordinates format [x, y, 1]
242
+ coords = np.concatenate(
243
+ (coords, np.ones((coords.shape[0], 1), coords.dtype)),
244
+ axis=1) # [n, 3]
245
+ affined_coords = np.matmul(
246
+ M[None, :, :],
247
+ coords[:, :, None])[..., 0] # [n, 2, 1] -> [n, 2]
248
+ affined_masks.append(affined_coords[:,:2])
249
+ affined_masks = WPolygonMaskItem(affined_masks, width=out_shape[1],height=out_shape[0])
250
+ affined_masks = affined_masks.crop(np.array([0,0,out_shape[1]-1,out_shape[0]-1]))
251
+ return affined_masks
252
+
253
+ def shear(self,
254
+ out_shape,
255
+ magnitude,
256
+ direction='horizontal',
257
+ border_value=0,
258
+ interpolation='bilinear'):
259
+ #out_shape: [h,w]
260
+ if len(self.points) == 0:
261
+ sheared_masks = WPolygonMaskItem([], width=out_shape[1],height=out_shape[0])
262
+ else:
263
+ sheared_masks = []
264
+ if direction == 'horizontal':
265
+ shear_matrix = np.stack([[1, magnitude],
266
+ [0, 1]]).astype(np.float32)
267
+ elif direction == 'vertical':
268
+ shear_matrix = np.stack([[1, 0], [magnitude,
269
+ 1]]).astype(np.float32)
270
+ for p in self.points():
271
+ p = p.copy()
272
+ new_coords = np.matmul(shear_matrix, p.T) # [2, n]
273
+ new_coords[0, :] = np.clip(new_coords[0, :], 0,
274
+ out_shape[1])
275
+ new_coords[1, :] = np.clip(new_coords[1, :], 0,
276
+ out_shape[0])
277
+ sheared_masks.append(new_coords.transpose((1, 0)))
278
+ sheared_masks = WPolygonMaskItem(sheared_masks, width=out_shape[1],height=out_shape[0])
279
+ return sheared_masks
280
+
281
+ def translate(self,
282
+ out_shape,
283
+ offset,
284
+ direction='horizontal',
285
+ fill_val=None,
286
+ interpolation=None):
287
+ """Translate the PolygonMasks.
288
+
289
+ Example:
290
+ >>> self = PolygonMasks.random(dtype=np.int)
291
+ >>> out_shape = (self.height, self.width)
292
+ >>> new = self.translate(out_shape, 4., direction='horizontal')
293
+ >>> assert np.all(new.masks[0][0][1::2] == self.masks[0][0][1::2])
294
+ >>> assert np.all(new.masks[0][0][0::2] == self.masks[0][0][0::2] + 4) # noqa: E501
295
+ """
296
+ if len(self.points) == 0:
297
+ res_masks = WPolygonMaskItem([], width=out_shape[1],height=out_shape[0])
298
+ else:
299
+ translated_masks = []
300
+ for p in self.points:
301
+ p = p.copy()
302
+ if direction == WBaseMask.HORIZONTAL:
303
+ p[:,0] = np.clip(p[:,0] + offset, 0, out_shape[1])
304
+ elif direction == WBaseMask.VERTICAL:
305
+ p[:,1] = np.clip(p[:,1] + offset, 0, out_shape[0])
306
+ else:
307
+ info = f"error direction {direction}"
308
+ print(f"ERROR: {type(self).__name__} {info}")
309
+ raise RuntimeError(info)
310
+ translated_masks.append(p)
311
+ res_masks = WPolygonMaskItem(translated_masks, width=out_shape[1],height=out_shape[0])
312
+ return res_masks
313
+
314
+ def offset(self,
315
+ offset):
316
+ '''
317
+ offset: [xoffset,yoffset]
318
+ '''
319
+ w = self.width+offset[0] if self.width is not None else None
320
+ h = self.height+offset[1] if self.height is not None else None
321
+ offset = np.reshape(np.array(offset),[1,2])
322
+ if len(self.points) == 0:
323
+ res_masks = WPolygonMaskItem([], width=w,height=h)
324
+ else:
325
+ translated_masks = []
326
+ for p in self.points:
327
+ p = p.copy()+offset
328
+ translated_masks.append(p)
329
+ res_masks = WPolygonMaskItem(translated_masks, width=w,height=h)
330
+ return res_masks
331
+
332
+ def get_bbox(self):
333
+ if len(self.points) == 0:
334
+ return np.zeros([4],dtype=np.float32)
335
+
336
+ points = np.concatenate(self.points,axis=0)
337
+
338
+ if len(points) == 0: #len(points)不为0,cocat的结果可能为零
339
+ return np.zeros([4],dtype=np.float32)
340
+
341
+ if len(points)==0:
342
+ gt_bbox = np.zeros([4],dtype=np.float32)
343
+ else:
344
+ xy_min = np.min(points,axis=0)
345
+ xy_max = np.max(points,axis=0)
346
+ x0,y0 = xy_min[0],xy_min[1]
347
+ x1,y1 = xy_max[0],xy_max[1]
348
+ gt_bbox = np.array([x0,y0,x1,y1],dtype=np.float32)
349
+
350
+
351
+ return gt_bbox
352
+
353
+ def get_bbox(self):
354
+ if len(self.points) == 0:
355
+ return np.zeros([4],dtype=np.float32)
356
+
357
+ points = np.concatenate(self.points,axis=0)
358
+
359
+ if len(points) == 0: #len(points)不为0,cocat的结果可能为零
360
+ return np.zeros([4],dtype=np.float32)
361
+
362
+ if len(points)==0:
363
+ gt_bbox = np.zeros([4],dtype=np.float32)
364
+ else:
365
+ xy_min = np.min(points,axis=0)
366
+ xy_max = np.max(points,axis=0)
367
+ x0,y0 = xy_min[0],xy_min[1]
368
+ x1,y1 = xy_max[0],xy_max[1]
369
+ gt_bbox = np.array([x0,y0,x1,y1],dtype=np.float32)
370
+
371
+
372
+ return gt_bbox
373
+
374
+
375
+ def __repr__(self):
376
+ s = self.__class__.__name__ + '('
377
+ s += f'height={self.height}, '
378
+ s += f'width={self.width})'
379
+ return s
380
+
381
+ @property
382
+ def shape(self):
383
+ return [len(self.points),self.height,self.width]
384
+
385
+ def _update_shape(self,*,width=None,height=None):
386
+ if width is not None:
387
+ self.width = width
388
+ if height is not None:
389
+ self.height = height
390
+
391
+ class WPolygonMasks(WBaseMask):
392
+ def __init__(self,masks,*,width=None,height=None,exclusion=None) -> None:
393
+ super().__init__()
394
+ self.masks = copy.deepcopy(masks)
395
+ self.width = width
396
+ self.height = height
397
+ self.exclusion = exclusion
398
+ if self.width<5 or self.height<5:
399
+ print(f"WARNING: {self.__class__.__name__}: unnormal mask size, width={self.width}, height={self.height}")
400
+
401
+ @classmethod
402
+ def zeros(cls,*,width=None,height=None,shape=None):
403
+ '''
404
+ shape: [masks_nr,H,W]
405
+ '''
406
+ if shape is not None:
407
+ width = shape[-1]
408
+ height = shape[-2]
409
+ return cls([],width=width,height=height)
410
+
411
+ @classmethod
412
+ def from_ndarray(cls,masks,*,width=None,height=None):
413
+ _masks,bboxes,keep = WBitmapMasks.ndarray2polygon(masks)
414
+ masks = []
415
+ for i,k in enumerate(keep):
416
+ if k:
417
+ masks.append(_masks[k])
418
+
419
+ return cls(masks=masks,width=width,height=height)
420
+
421
+
422
+ @classmethod
423
+ def from_bboxes_masks(cls,bboxes,masks,*,width=None,height=None):
424
+ '''
425
+ bboxes: [N,4](x0,y0,x1,y1)
426
+ masks: [N,h,w] 仅包含bboxes内的部分
427
+ '''
428
+ items = []
429
+ for bbox,mask in zip(bboxes,masks):
430
+ x0,y0,x1,y1 = bbox
431
+ scale = np.reshape(np.array([(x1-x0)/mask.shape[1],(y1-y0)/mask.shape[0]],dtype=np.float32),[1,2])
432
+ offset = np.reshape(np.array([x0,y0],dtype=np.float32),[1,2])
433
+
434
+ contours, hierarchy = findContours(mask, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
435
+
436
+ shapes = []
437
+ for cont in contours:
438
+ points = cont
439
+ if len(cont.shape)==3 and cont.shape[1]==1:
440
+ points = np.squeeze(points,axis=1)
441
+ points = points*scale+offset
442
+ points = points.astype(np.int32)
443
+ if len(points)<=2:
444
+ continue
445
+ shapes.append(points)
446
+ items.append(WPolygonMaskItem(shapes,width=width,height=height))
447
+
448
+ return cls(masks=items,width=width,height=height)
449
+
450
+ def copy(self):
451
+ return WPolygonMasks(self.masks,width=self.width,height=self.height,exclusion=self.exclusion)
452
+
453
+ def __getitem__(self,idxs):
454
+ if isinstance(idxs,(list,tuple)) and (len(idxs)==2 or len(idxs)==3) and isinstance(idxs[0],slice):
455
+ sx = idxs[-1]
456
+ sy = idxs[-2]
457
+ if self.is_none_slice(sy) and self.is_flip_slice(sx):
458
+ return self.flip(WBaseMask.HORIZONTAL)
459
+ elif self.is_none_slice(sx) and self.is_flip_slice(sy):
460
+ return self.flip(WBaseMask.VERTICAL)
461
+ elif self.is_flip_slice(sx) and self.is_flip_slice(sy):
462
+ return self.flip(WBaseMask.DIAGONAL)
463
+ bbox = self.slice2bbox(sx=sx,sy=sy)
464
+ return self.crop(bbox)
465
+ elif isinstance(idxs,(list,np.ndarray,tuple)):
466
+ idxs = np.array(idxs)
467
+ if idxs.dtype == np.bool:
468
+ idxs = np.where(idxs)[0]
469
+ try:
470
+ masks = [self.masks[idx] for idx in idxs]
471
+ except Exception as e:
472
+ print(e)
473
+ pass
474
+ return WPolygonMasks(masks,width=self.width,height=self.height,exclusion=self.exclusion)
475
+ else:
476
+ return self.masks[idxs]
477
+
478
+ def __setitem__(self,idxs,value):
479
+ if isinstance(idxs,(list,tuple)) and (len(idxs)==2 or len(idxs)==3) and isinstance(idxs[0],slice):
480
+ sx = idxs[-1]
481
+ sy = idxs[-2]
482
+ bbox = self.slice2bbox(sx=sx,sy=sy)
483
+ self.copy_from(value,bbox)
484
+ elif isinstance(idxs,(list,np.ndarray,tuple)):
485
+ idxs = np.array(idxs)
486
+ if idxs.dtype == np.bool:
487
+ idxs = np.where(idxs)[0]
488
+ if len(value) != len(idxs):
489
+ info = f"idxs size not equal value's size {len(idxs)} vs {len(value)}"
490
+ print(f"ERROR: {type(self).__name__}: {info}")
491
+ raise RuntimeError(info)
492
+ for i in idxs:
493
+ self.masks[i] = value[i]
494
+ else:
495
+ info = f"unknow idxs type {type(idxs)}"
496
+ print(f"ERROR: {type(self).__name__}: {info}")
497
+ raise RuntimeError(info)
498
+
499
+ def __len__(self):
500
+ return len(self.masks)
501
+
502
+ def __repr__(self):
503
+ s = self.__class__.__name__ + '('
504
+ s += f'num_masks={len(self.masks)}, '
505
+ s += f'height={self.height}, '
506
+ s += f'width={self.width})'
507
+ return s
508
+
509
+ def bitmap(self,exclusion=None):
510
+ '''
511
+ exclusion: 如果exclusion那么一个像素只能为一个类别,索引大的优先级高,即masks[i+1]会覆盖masks[i]的重叠区域
512
+ '''
513
+ if len(self.masks) == 0:
514
+ return np.zeros(shape=[0,self.height,self.width],dtype=np.uint8)
515
+
516
+ masks = [m.bitmap(width=self.width,height=self.height) for m in self.masks]
517
+
518
+ if exclusion is None:
519
+ exclusion = self.exclusion
520
+ if exclusion is None:
521
+ exclusion = False
522
+ if exclusion and len(masks)>1:
523
+ mask = 1 - masks[-1]
524
+ for i in reversed(range(len(masks) - 1)):
525
+ masks[i] = np.logical_and(masks[i], mask)
526
+ mask = np.logical_and(mask, 1 - masks[i])
527
+
528
+ return np.stack(masks,axis=0)
529
+
530
+ def resize(self,size):
531
+ '''
532
+ size:[w,h]
533
+ '''
534
+ self.masks = [m.resize(size,width=self.width,height=self.height) for m in self.masks]
535
+ self.width = size[0]
536
+ self.height = size[1]
537
+ return self
538
+
539
+ def flip(self,direction=WBaseMask.HORIZONTAL):
540
+ [m.flip(direction,width=self.width,height=self.height) for m in self.masks]
541
+ return self
542
+
543
+ def crop(self,bbox):
544
+ '''
545
+ bbox: [x0,y0,x1,y1]
546
+ '''
547
+ bbox = np.array(bbox)
548
+ bbox = bbox.copy()
549
+ bbox[0::2] = np.clip(bbox[0::2], 0, self.width)
550
+ bbox[1::2] = np.clip(bbox[1::2], 0, self.height)
551
+ x1, y1, x2, y2 = bbox
552
+ w = np.maximum(x2 - x1+1, 1)
553
+ h = np.maximum(y2 - y1+1, 1)
554
+ masks = [m.crop(bbox) for m in self.masks]
555
+ return WPolygonMasks(masks,width=w,height=h)
556
+
557
+ def slice2bbox(self,*,sx:slice,sy:slice):
558
+ x0 = sx.start if sx.start is not None else 0
559
+ x1 = sx.stop-1 if sx.stop is not None else self.width-1
560
+ y0 = sy.start if sy.start is not None else 0
561
+ y1 = sy.stop-1 if sy.stop is not None else self.height-1
562
+ return np.array([x0,y0,x1,y1],dtype=np.int)
563
+
564
+ @staticmethod
565
+ def is_flip_slice(s:slice):
566
+ return s.start is None and s.stop is None and s.step==-1
567
+
568
+ @staticmethod
569
+ def is_none_slice(s:slice):
570
+ return s.start is None and s.stop is None and s.step is None
571
+
572
+ @staticmethod
573
+ def get_bbox_size(bbox):
574
+ x1, y1, x2, y2 = bbox
575
+ w = np.maximum(x2 - x1, 1)
576
+ h = np.maximum(y2 - y1, 1)
577
+ return w,h
578
+
579
+ def copy_from(self,masks,dst_bbox=None,src_bbox=None,update_size=False):
580
+ if src_bbox is not None:
581
+ masks = masks.crop(src_bbox)
582
+ if dst_bbox is not None:
583
+ w0,h0 = self.get_bbox_size(src_bbox)
584
+ w1,h1 = self.get_bbox_size(dst_bbox)
585
+ if w0!=w1 or h0!=h1:
586
+ info = f"dst_bbox and src_bbox expected to be equal."
587
+ print(f"ERROR: {info}")
588
+ raise RuntimeError(info)
589
+
590
+ if dst_bbox is not None:
591
+ if update_size:
592
+ w,h = self.get_bbox_size(dst_bbox)
593
+ self.width = w
594
+ self.height = h
595
+ self.masks = [m.offset(dst_bbox[:2]) for m in masks]
596
+ else:
597
+ self.masks = masks
598
+ if update_size:
599
+ self.width = masks.width
600
+ self.height = masks.height
601
+
602
+ return self
603
+
604
+
605
+ @classmethod
606
+ def from_bitmap_masks(cls,bitmap_masks):
607
+ masks,bboxes,keep = bitmap_masks.polygon()
608
+ items = [WPolygonMaskItem(points=m,width=bitmap_masks.width,height=bitmap_masks.height) for m in masks]
609
+ return cls(items,width=bitmap_masks.width,height=bitmap_masks.height)
610
+
611
+ @staticmethod
612
+ def concatenate(masks):
613
+ ws = [m.width for m in masks]
614
+ hs = [m.height for m in masks]
615
+ ws = list(filter(lambda x:x is not None,ws))
616
+ hs = list(filter(lambda x:x is not None,hs))
617
+ if len(ws)>0:
618
+ nw = np.max(ws)
619
+ else:
620
+ nw = None
621
+
622
+ if len(hs)>0:
623
+ nh = np.max(hs)
624
+ else:
625
+ nh = None
626
+ new_masks = []
627
+ for m in masks:
628
+ new_masks.extend(m.masks)
629
+ return WPolygonMasks(new_masks,width=nw,height=nh)
630
+
631
+ def rotate(self, out_shape, angle, center=None, scale=1.0, fill_val=0):
632
+ #out_shape: [h,w]
633
+ masks = [m.rotate(out_shape, angle, center, scale, fill_val) for m in self.masks]
634
+ width = out_shape[1]
635
+ height = out_shape[0]
636
+ return WPolygonMasks(masks,width=width,height=height)
637
+
638
+ def warp_affine(self,M,out_shape,fill_val=0):
639
+ #out_shape: [h,w]
640
+ masks = [m.warp_affine(M,out_shape,fill_val=fill_val) for m in self.masks]
641
+ width = out_shape[1]
642
+ height = out_shape[0]
643
+ return WPolygonMasks(masks,width=width,height=height)
644
+
645
+
646
+ def shear(self,
647
+ out_shape,
648
+ magnitude,
649
+ direction='horizontal',
650
+ border_value=0,
651
+ interpolation='bilinear'):
652
+ #out_shape: [h,w]
653
+ masks = [m.shear(out_shape, magnitude, direction, border_value, interpolation) for m in self.masks]
654
+ width = out_shape[1]
655
+ height = out_shape[0]
656
+ return WPolygonMasks(masks,width=width,height=height)
657
+
658
+ def translate(self,
659
+ out_shape,
660
+ offset,
661
+ direction=WBaseMask.HORIZONTAL,
662
+ fill_val=None,
663
+ interpolation=None):
664
+ '''
665
+ out_shape: [H,W]
666
+ '''
667
+ masks = [m.translate(out_shape, offset, direction, fill_val,interpolation) for m in self.masks]
668
+ width = out_shape[1]
669
+ height = out_shape[0]
670
+ return WPolygonMasks(masks,width=width,height=height)
671
+
672
+ def pad(self, out_shape, pad_val=0):
673
+ '''
674
+ out_shape: [H,W]
675
+ '''
676
+ """padding has no effect on polygons`"""
677
+ return WPolygonMasks(self.masks, height=out_shape[0],width=out_shape[1])
678
+
679
+ @property
680
+ def shape(self):
681
+ return (len(self.masks),self.height,self.width)
682
+
683
+ def resize_mask_in_bboxes(self,bboxes,size=None,r=None):
684
+ '''
685
+ mask: [N,H,W]
686
+ bboxes: [N,4](x0,y0,x1,y1)
687
+ size: (new_w,new_h)
688
+ '''
689
+ return self.resize(size),None
690
+
691
+ def to_ndarray(self):
692
+ """See :func:`BaseInstanceMasks.to_ndarray`."""
693
+ return self.bitmap()
694
+
695
+ def get_bboxes(self):
696
+ gt_bboxes = [m.get_bbox() for m in self.masks]
697
+ if len(gt_bboxes) == 0:
698
+ return np.zeros([0,4],dtype=np.float32)
699
+ gt_bboxes = np.stack(gt_bboxes,axis=0)
700
+
701
+ return gt_bboxes
702
+
703
+ def check_consistency(self):
704
+ for mask in self.masks:
705
+ if mask.width != self.width or mask.height != self.height:
706
+ info = f"Unmatch size WPolygonMasks shape {self.shape} vs WPolygonMakskItem shape {mask.shape}"
707
+ print(info)
708
+ raise RuntimeError(info)
709
+
710
+ def update_shape(self,*,width=None,height=None):
711
+ if width is not None:
712
+ self.width = width
713
+ if height is not None:
714
+ self.height = height
715
+ for mask in self.masks:
716
+ mask._update_shape(width=width,height=height)
717
+
718
+
719
+ class WBitmapMasks(WBaseMask):
720
+ def __init__(self,masks,*,width=None,height=None):
721
+ '''
722
+ masks: [N,H,W]
723
+ '''
724
+ self.reinit(masks=masks,width=width,height=height)
725
+
726
+ def reinit(self,masks,*,width=None,height=None):
727
+ if len(masks.shape)==2:
728
+ print(masks.shape)
729
+ pass
730
+
731
+ assert len(masks.shape)==3 and masks.shape[1]>0 and masks.shape[2]>0, f"ERROR: error points shape {masks.shape}"
732
+ super().__init__()
733
+ self.width = width if width is not None else masks.shape[2]
734
+ self.height = height if height is not None else masks.shape[1]
735
+
736
+ if self.width<5 or self.height<5:
737
+ traceback.print_exc(file=sys.stdout)
738
+ sys.stdout.flush()
739
+ print(f"WARNING: {self.__class__.__name__}: unnormal mask size, width={self.width}, height={self.height}, mask shape={masks.shape}")
740
+
741
+ if len(masks) == 0:
742
+ self.masks = np.zeros((0, self.height, self.width), dtype=np.uint8)
743
+ else:
744
+ assert isinstance(masks, (list, tuple,np.ndarray))
745
+ if isinstance(masks, (list,tuple)):
746
+ assert isinstance(masks[0], np.ndarray)
747
+ assert masks[0].ndim == 2 # (H, W)
748
+ else:
749
+ assert masks.ndim == 3 # (N, H, W)
750
+ if isinstance(masks,np.ndarray):
751
+ self.masks = masks
752
+ else:
753
+ self.masks = np.stack(masks).reshape(-1, height, width)
754
+ if self.masks.dtype != np.uint8:
755
+ print("WARNING: masks dtype is not uint8")
756
+ self.masks = self.masks.astype(np.uint8)
757
+
758
+ self.masks = np.ascontiguousarray(self.masks)
759
+
760
+ @classmethod
761
+ def new(cls,masks,*,width=None,height=None):
762
+ return cls(masks=masks,width=width,height=height)
763
+
764
+ @classmethod
765
+ def from_ndarray(cls,masks,*,width=None,height=None):
766
+ return cls(masks=masks,width=width,height=height)
767
+
768
+ @classmethod
769
+ def from_bboxes_masks(cls,bboxes,masks,*,width=None,height=None):
770
+ '''
771
+ bboxes: [N,4](x0,y0,x1,y1)
772
+ masks: [N,h,w] 仅包含bboxes内的部分
773
+ '''
774
+ masks = masks.astype(np.uint8)
775
+
776
+ bboxes[:,0::2] = np.clip(bboxes[:,0::2],a_min=0,a_max=width)
777
+ bboxes[:,1::2] = np.clip(bboxes[:,1::2],a_min=0,a_max=height)
778
+ bitmap = np.zeros([bboxes.shape[0],height,width],dtype=np.uint8)
779
+ for i,bbox in enumerate(bboxes):
780
+ x = int(bbox[1])
781
+ y = int(bbox[0])
782
+ w = int((bbox[3]-bbox[1]))
783
+ h = int((bbox[2]-bbox[0]))
784
+ if w<=0 or h<=0:
785
+ continue
786
+ mask = masks[i]
787
+ mask = cv2.resize(mask,(w,h),interpolation=cv2.INTER_NEAREST)
788
+ try:
789
+ bitmap[i,y:y+h,x:x+w] = mask
790
+ except Exception as e:
791
+ print(f"ERROR WBitmapMasks: {e}")
792
+ pass
793
+
794
+ return cls(masks=bitmap,width=width,height=height)
795
+
796
+ def __getitem__(self, index):
797
+ """Index the BitmapMask.
798
+
799
+ Args:
800
+ index (int | ndarray): Indices in the format of integer or ndarray.
801
+
802
+ Returns:
803
+ :obj:`BitmapMasks`: Indexed bitmap masks.
804
+ """
805
+ try:
806
+ masks = self.masks[index]
807
+ return self.new(masks)
808
+ except Exception as e:
809
+ print(f"ERROR WBitmapMasks: {e} index={index}, masks shape={self.masks.shape}, new masks shape {masks.shape}")
810
+ raise e
811
+
812
+ def __setitem__(self,idxs,value):
813
+ if isinstance(value,WBitmapMasks):
814
+ value = value.masks
815
+ if isinstance(idxs,(list,tuple)) and (len(idxs)==2 or len(idxs)==3) and isinstance(idxs[0],slice):
816
+ self.masks[idxs] = value
817
+ elif isinstance(idxs,(list,np.ndarray,tuple)):
818
+ idxs = np.array(idxs)
819
+ if idxs.dtype == np.bool:
820
+ idxs = np.where(idxs)[0]
821
+ if len(value) != len(idxs) and not isinstance(value,(int,float)):
822
+ info = f"idxs size not equal value's size {len(idxs)} vs {len(value)}"
823
+ print(f"ERROR: {type(self).__name__}: {info}")
824
+ raise RuntimeError(info)
825
+ if isinstance(value,(float,int)):
826
+ for i in idxs:
827
+ self.masks[i] = value
828
+ else:
829
+ for i in idxs:
830
+ self.masks[i] = value[i]
831
+ else:
832
+ info = f"unknow idxs type {type(idxs)}"
833
+ print(f"ERROR: {type(self).__name__}: {info}")
834
+ raise RuntimeError(info)
835
+
836
+ def __iter__(self):
837
+ return iter(self.masks)
838
+
839
+ def __repr__(self):
840
+ s = self.__class__.__name__ + '('
841
+ s += f'num_masks={len(self.masks)}, '
842
+ s += f'height={self.height}, '
843
+ s += f'width={self.width})'
844
+ return s
845
+
846
+ def __len__(self):
847
+ """Number of masks."""
848
+ return len(self.masks)
849
+
850
+
851
+ def polygon(self,bboxes=None):
852
+ return self.ndarray2polygon(self.masks,bboxes=bboxes)
853
+
854
+
855
+ @staticmethod
856
+ def ndarray2polygon(masks,bboxes=None):
857
+ '''
858
+ masks: [N,H,W]
859
+ return:
860
+ t_masks: list of list [N,2] points
861
+ '''
862
+ t_masks = []
863
+ keep = np.ones([masks.shape[0]],dtype=np.bool)
864
+ res_bboxes = []
865
+ for i in range(masks.shape[0]):
866
+ if bboxes is not None:
867
+ contours = bmt.find_contours_in_bbox(masks[i],bboxes[i])
868
+ else:
869
+ contours,hierarchy = findContours(masks[i],cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE)
870
+ t_bbox = get_bboxes_by_contours(contours)
871
+ if len(contours)==0 or not np.all(t_bbox[2:]-t_bbox[:2]>1):
872
+ keep[i] = False
873
+ t_masks.append(contours)
874
+ res_bboxes.append(t_bbox)
875
+
876
+ if len(res_bboxes) == 0:
877
+ res_bboxes = np.zeros([0,4],dtype=np.float32)
878
+ else:
879
+ res_bboxes = np.array(res_bboxes,dtype=np.float32)
880
+
881
+ return t_masks,res_bboxes,keep
882
+
883
+ @classmethod
884
+ def from_polygon_masks(cls,polygon_masks):
885
+ return cls(masks=polygon_masks.bitmap())
886
+
887
+ def resize(self, size):
888
+ '''
889
+ size:[w,h]
890
+ '''
891
+ if len(self.masks) == 0:
892
+ resized_masks = np.empty((0, size[1],size[0]), dtype=np.uint8)
893
+ else:
894
+ resized_masks = npresize_mask(self.masks,size)
895
+ return self.new(resized_masks)
896
+
897
+ def resize_mask_in_bboxes(self,bboxes,size=None,r=None):
898
+ '''
899
+ mask: [N,H,W]
900
+ bboxes: [N,4](x0,y0,x1,y1)
901
+ size: (new_w,new_h)
902
+ '''
903
+ if bboxes is None or self.masks.shape[0]==0:
904
+ return self.resize(size),np.zeros([0,4],dtype=bboxes.dtype)
905
+ mask = self.masks.copy()
906
+ x_scale = size[0]/mask.shape[2]
907
+ y_scale = size[1]/mask.shape[1]
908
+ bboxes = odb.correct_bboxes(bboxes,size=[mask.shape[-1],mask.shape[-2]])
909
+ resized_bboxes = (bboxes*np.array([[x_scale,y_scale,x_scale,y_scale]])).astype(np.int32)
910
+ resized_bboxes = odb.correct_bboxes(resized_bboxes,size=size)
911
+ bboxes = np.array(bboxes).astype(np.int32)
912
+
913
+ res_mask = np.zeros([mask.shape[0],size[1],size[0]],dtype=mask.dtype)
914
+ for i in range(mask.shape[0]):
915
+ dbbox = resized_bboxes[i]
916
+ dsize = (dbbox[2]-dbbox[0],dbbox[3]-dbbox[1])
917
+ if dsize[0]<=1 or dsize[1]<=1:
918
+ continue
919
+ sub_mask = bwmli.crop_img_absolute_xy(mask[i],bboxes[i])
920
+ sub_mask = np.ascontiguousarray(sub_mask)
921
+ cur_m = cv2.resize(sub_mask,dsize=dsize,interpolation=cv2.INTER_NEAREST)
922
+ bwmli.set_subimg(res_mask[i],cur_m,dbbox[:2])
923
+
924
+ return self.new(res_mask),resized_bboxes
925
+
926
+ @property
927
+ def shape(self):
928
+ return self.masks.shape
929
+
930
+ def flip(self,direction=WBaseMask.HORIZONTAL):
931
+ if len(self.masks)==0:
932
+ return self
933
+
934
+ if direction == WBaseMask.HORIZONTAL:
935
+ masks = self.masks[:,:,::-1]
936
+ elif direction == WBaseMask.VERTICAL:
937
+ masks = self.masks[:,::-1,:]
938
+ elif direction == WBaseMask.DIAGONAL:
939
+ masks = self.masks[:,::-1,::-1]
940
+ else:
941
+ info = f"unknow flip direction {direction}"
942
+ print(f"ERROR: {info}")
943
+ raise RuntimeError(info)
944
+
945
+ return self.new(masks)
946
+
947
+ def pad(self, out_shape, pad_val=0):
948
+ '''
949
+ out_shape: [H,W]
950
+ '''
951
+ """padding has no effect on polygons`"""
952
+ hp = max(out_shape[0]-self.height,0)
953
+ wp = max(out_shape[1]-self.width,0)
954
+ masks = np.pad(self.masks, [[0, 0], [0, hp], [0, wp]], constant_values=pad_val)
955
+
956
+ return self.new(masks)
957
+
958
+ def crop(self, bbox):
959
+ '''
960
+ bbox: [x0,y0,x1,y1]
961
+ '''
962
+ # clip the boundary
963
+ bbox = bbox.copy()
964
+ cropped_masks = bwmli.crop_masks_absolute_xy(self.masks,bbox)
965
+ return self.new(cropped_masks)
966
+
967
+ def to_ndarray(self):
968
+ """See :func:`BaseInstanceMasks.to_ndarray`."""
969
+ return self.masks
970
+
971
+ def rotate(self, out_shape, angle, center=None, scale=1.0, fill_val=0):
972
+ """Rotate the BitmapMasks.
973
+
974
+ Args:
975
+ out_shape (tuple[int]): Shape for output mask, format (h, w).
976
+ angle (int | float): Rotation angle in degrees. Positive values
977
+ mean counter-clockwise rotation.
978
+ center (tuple[float], optional): Center point (w, h) of the
979
+ rotation in source image. If not specified, the center of
980
+ the image will be used.
981
+ scale (int | float): Isotropic scale factor.
982
+ fill_val (int | float): Border value. Default 0 for masks.
983
+
984
+ Returns:
985
+ BitmapMasks: Rotated BitmapMasks.
986
+ """
987
+ if len(self.masks) == 0:
988
+ rotated_masks = np.empty((0, *out_shape), dtype=self.masks.dtype)
989
+ else:
990
+ rotated_masks = bwmli.imrotate(
991
+ self.masks.transpose((1, 2, 0)),
992
+ angle,
993
+ center=center,
994
+ scale=scale,
995
+ border_value=fill_val)
996
+ if rotated_masks.ndim == 2:
997
+ # case when only one mask, (h, w)
998
+ rotated_masks = rotated_masks[:, :, None] # (h, w, 1)
999
+ rotated_masks = rotated_masks.transpose(
1000
+ (2, 0, 1)).astype(self.masks.dtype)
1001
+ return self.new(rotated_masks, height=out_shape[0],width=out_shape[1])
1002
+
1003
+ def warp_affine(self,M,out_shape,fill_val=0):
1004
+ '''
1005
+ out_shape:[H,W]
1006
+ '''
1007
+ if len(self.masks) == 0:
1008
+ rotated_masks = np.empty((0, *out_shape), dtype=self.masks.dtype)
1009
+ else:
1010
+ rotated_masks = bwmli.im_warp_affine(
1011
+ self.masks.transpose((1, 2, 0)),
1012
+ M=M,
1013
+ out_shape=out_shape[:2][::-1],
1014
+ border_value=fill_val)
1015
+ if rotated_masks.ndim == 2:
1016
+ # case when only one mask, (h, w)
1017
+ rotated_masks = rotated_masks[:, :, None] # (h, w, 1)
1018
+ rotated_masks = rotated_masks.transpose(
1019
+ (2, 0, 1)).astype(self.masks.dtype)
1020
+ return self.new(rotated_masks, height=out_shape[0],width=out_shape[1])
1021
+
1022
+ def shear(self,
1023
+ out_shape,
1024
+ magnitude,
1025
+ direction='horizontal',
1026
+ border_value=0,
1027
+ interpolation='bilinear'):
1028
+ """Shear the BitmapMasks.
1029
+
1030
+ Args:
1031
+ out_shape (tuple[int]): Shape for output mask, format (h, w).
1032
+ magnitude (int | float): The magnitude used for shear.
1033
+ direction (str): The shear direction, either "horizontal"
1034
+ or "vertical".
1035
+ border_value (int | tuple[int]): Value used in case of a
1036
+ constant border.
1037
+ interpolation (str): Same as in :func:`mmcv.imshear`.
1038
+
1039
+ Returns:
1040
+ BitmapMasks: The sheared masks.
1041
+ """
1042
+ if len(self.masks) == 0:
1043
+ sheared_masks = np.empty((0, *out_shape), dtype=self.masks.dtype)
1044
+ else:
1045
+ sheared_masks = bwmli.imshear(
1046
+ self.masks.transpose((1, 2, 0)),
1047
+ magnitude,
1048
+ direction,
1049
+ border_value=border_value,
1050
+ interpolation=interpolation)
1051
+ if sheared_masks.ndim == 2:
1052
+ sheared_masks = sheared_masks[:, :, None]
1053
+ sheared_masks = sheared_masks.transpose(
1054
+ (2, 0, 1)).astype(self.masks.dtype)
1055
+ return self.new(sheared_masks, height=out_shape[0],width=out_shape[1])
1056
+
1057
+ def translate(self,
1058
+ out_shape,
1059
+ offset,
1060
+ direction='horizontal',
1061
+ fill_val=0,
1062
+ interpolation='bilinear'):
1063
+ """Translate the BitmapMasks.
1064
+
1065
+ Args:
1066
+ out_shape (tuple[int]): Shape for output mask, format (h, w).
1067
+ offset (int | float): The offset for translate.
1068
+ direction (str): The translate direction, either "horizontal"
1069
+ or "vertical".
1070
+ fill_val (int | float): Border value. Default 0 for masks.
1071
+ interpolation (str): Same as :func:`mmcv.imtranslate`.
1072
+
1073
+ Returns:
1074
+ BitmapMasks: Translated BitmapMasks.
1075
+
1076
+ Example:
1077
+ >>> from mmdet.core.mask.structures import BitmapMasks
1078
+ >>> self = BitmapMasks.random(dtype=np.uint8)
1079
+ >>> out_shape = (32, 32)
1080
+ >>> offset = 4
1081
+ >>> direction = 'horizontal'
1082
+ >>> fill_val = 0
1083
+ >>> interpolation = 'bilinear'
1084
+ >>> # Note, There seem to be issues when:
1085
+ >>> # * out_shape is different than self's shape
1086
+ >>> # * the mask dtype is not supported by cv2.AffineWarp
1087
+ >>> new = self.translate(out_shape, offset, direction, fill_val,
1088
+ >>> interpolation)
1089
+ >>> assert len(new) == len(self)
1090
+ >>> assert new.height, new.width == out_shape
1091
+ """
1092
+ if len(self.masks) == 0:
1093
+ translated_masks = np.empty((0, *out_shape), dtype=np.uint8)
1094
+ else:
1095
+ translated_masks = bwmli.imtranslate(
1096
+ self.masks.transpose((1, 2, 0)),
1097
+ offset,
1098
+ direction,
1099
+ border_value=fill_val,
1100
+ interpolation=interpolation)
1101
+ if translated_masks.ndim == 2:
1102
+ translated_masks = translated_masks[:, :, None]
1103
+ translated_masks = translated_masks.transpose(
1104
+ (2, 0, 1)).astype(self.masks.dtype)
1105
+ return self.new(translated_masks, height=out_shape[0],width=out_shape[1])
1106
+
1107
+ @classmethod
1108
+ def concatenate(cls,masks):
1109
+ masks = np.concatenate([m.masks for m in masks],axis=0)
1110
+ return cls(masks)
1111
+
1112
+ @classmethod
1113
+ def zeros(cls,*,width=None,height=None,shape=None):
1114
+ '''
1115
+ shape: [masks_nr,H,W]
1116
+ '''
1117
+ if shape is not None:
1118
+ masks = np.zeros(shape,dtype=np.uint8)
1119
+ else:
1120
+ masks = np.zeros([1,height,width],dtype=np.uint8)
1121
+ return cls(masks)
1122
+
1123
+ def get_bboxes(self):
1124
+ masks = masks
1125
+ if len(masks) == 0:
1126
+ return np.zeros([0,4],dtype=np.float32)
1127
+
1128
+ gtbboxes = []
1129
+ for i in range(masks.shape[0]):
1130
+ cur_mask = masks[i]
1131
+ idx = np.nonzero(cur_mask)
1132
+ xs = idx[1]
1133
+ ys = idx[0]
1134
+ if len(xs)==0:
1135
+ gtbboxes.append(np.zeros([4],dtype=np.float32))
1136
+ else:
1137
+ x0 = np.min(xs)
1138
+ y0 = np.min(ys)
1139
+ x1 = np.max(xs)
1140
+ y1 = np.max(ys)
1141
+ gtbboxes.append(np.array([x0,y0,x1,y1],dtype=np.float32))
1142
+
1143
+ gtbboxes = np.array(gtbboxes)
1144
+
1145
+ return gtbboxes
1146
+
1147
+ def copy(self):
1148
+ return WBitmapMasks(self.masks.copy(),width=self.width,height=self.height)
1149
+
1150
+
1151
+ def check_consistency(self):
1152
+ pass
1153
+
1154
+ def update_shape(self,*,width=None,height=None):
1155
+ return
1156
+ if height is not None:
1157
+ if self.shape[1] != height:
1158
+ print(f"Update WBitmapMasks height faild, {self.shape[1]} vs new {height}")
1159
+ if width is not None:
1160
+ if self.shape[2] != width:
1161
+ print(f"Update WBitmapMasks width faild, {self.shape[2]} vs new {width}")