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.
- python_wml-3.0.0.dist-info/LICENSE +23 -0
- python_wml-3.0.0.dist-info/METADATA +51 -0
- python_wml-3.0.0.dist-info/RECORD +164 -0
- python_wml-3.0.0.dist-info/WHEEL +5 -0
- python_wml-3.0.0.dist-info/top_level.txt +1 -0
- wml/__init__.py +0 -0
- wml/basic_data_def/__init__.py +2 -0
- wml/basic_data_def/detection_data_def.py +279 -0
- wml/basic_data_def/io_data_def.py +2 -0
- wml/basic_img_utils.py +816 -0
- wml/img_patch.py +92 -0
- wml/img_utils.py +571 -0
- wml/iotoolkit/__init__.py +17 -0
- wml/iotoolkit/aic_keypoint.py +115 -0
- wml/iotoolkit/baidu_mask_toolkit.py +244 -0
- wml/iotoolkit/base_dataset.py +210 -0
- wml/iotoolkit/bboxes_statistics.py +515 -0
- wml/iotoolkit/build.py +0 -0
- wml/iotoolkit/cityscapes_toolkit.py +183 -0
- wml/iotoolkit/classification_data_statistics.py +25 -0
- wml/iotoolkit/coco_data_fwd.py +225 -0
- wml/iotoolkit/coco_keypoints.py +118 -0
- wml/iotoolkit/coco_keypoints_fmt2.py +103 -0
- wml/iotoolkit/coco_toolkit.py +397 -0
- wml/iotoolkit/coco_wholebody.py +269 -0
- wml/iotoolkit/common.py +108 -0
- wml/iotoolkit/crowd_pose.py +146 -0
- wml/iotoolkit/fast_labelme.py +110 -0
- wml/iotoolkit/image_folder.py +95 -0
- wml/iotoolkit/imgs_cache.py +58 -0
- wml/iotoolkit/imgs_reader_mt.py +73 -0
- wml/iotoolkit/labelme_base.py +102 -0
- wml/iotoolkit/labelme_json_to_img.py +49 -0
- wml/iotoolkit/labelme_toolkit.py +117 -0
- wml/iotoolkit/labelme_toolkit_fwd.py +733 -0
- wml/iotoolkit/labelmemckeypoints_dataset.py +169 -0
- wml/iotoolkit/lspet.py +48 -0
- wml/iotoolkit/mapillary_vistas_toolkit.py +269 -0
- wml/iotoolkit/mat_data.py +90 -0
- wml/iotoolkit/mckeypoints_statistics.py +28 -0
- wml/iotoolkit/mot_datasets.py +62 -0
- wml/iotoolkit/mpii.py +108 -0
- wml/iotoolkit/npmckeypoints_dataset.py +164 -0
- wml/iotoolkit/o365_to_coco.py +136 -0
- wml/iotoolkit/object365_toolkit.py +156 -0
- wml/iotoolkit/object365v2_toolkit.py +71 -0
- wml/iotoolkit/pascal_voc_data.py +51 -0
- wml/iotoolkit/pascal_voc_toolkit.py +194 -0
- wml/iotoolkit/pascal_voc_toolkit_fwd.py +473 -0
- wml/iotoolkit/penn_action.py +57 -0
- wml/iotoolkit/rawframe_dataset.py +129 -0
- wml/iotoolkit/rewrite_pascal_voc.py +28 -0
- wml/iotoolkit/semantic_data.py +49 -0
- wml/iotoolkit/split_file_by_type.py +29 -0
- wml/iotoolkit/sports_mot_datasets.py +78 -0
- wml/iotoolkit/vis_objectdetection_dataset.py +70 -0
- wml/iotoolkit/vis_torch_data.py +39 -0
- wml/iotoolkit/yolo_toolkit.py +38 -0
- wml/object_detection2/__init__.py +4 -0
- wml/object_detection2/basic_visualization.py +37 -0
- wml/object_detection2/bboxes.py +812 -0
- wml/object_detection2/data_process_toolkit.py +146 -0
- wml/object_detection2/keypoints.py +292 -0
- wml/object_detection2/mask.py +120 -0
- wml/object_detection2/metrics/__init__.py +3 -0
- wml/object_detection2/metrics/build.py +15 -0
- wml/object_detection2/metrics/classifier_toolkit.py +440 -0
- wml/object_detection2/metrics/common.py +71 -0
- wml/object_detection2/metrics/mckps_toolkit.py +338 -0
- wml/object_detection2/metrics/toolkit.py +1953 -0
- wml/object_detection2/npod_toolkit.py +361 -0
- wml/object_detection2/odtools.py +243 -0
- wml/object_detection2/standard_names.py +75 -0
- wml/object_detection2/visualization.py +956 -0
- wml/object_detection2/wmath.py +34 -0
- wml/semantic/__init__.py +0 -0
- wml/semantic/basic_toolkit.py +65 -0
- wml/semantic/mask_utils.py +156 -0
- wml/semantic/semantic_test.py +21 -0
- wml/semantic/structures.py +1 -0
- wml/semantic/toolkit.py +105 -0
- wml/semantic/visualization_utils.py +658 -0
- wml/threadtoolkit.py +50 -0
- wml/walgorithm.py +228 -0
- wml/wcollections.py +212 -0
- wml/wfilesystem.py +487 -0
- wml/wml_utils.py +657 -0
- wml/wstructures/__init__.py +4 -0
- wml/wstructures/common.py +9 -0
- wml/wstructures/keypoints_train_toolkit.py +149 -0
- wml/wstructures/kps_structures.py +579 -0
- wml/wstructures/mask_structures.py +1161 -0
- wml/wtorch/__init__.py +8 -0
- wml/wtorch/bboxes.py +104 -0
- wml/wtorch/classes_suppression.py +24 -0
- wml/wtorch/conv_module.py +181 -0
- wml/wtorch/conv_ws.py +144 -0
- wml/wtorch/data/__init__.py +16 -0
- wml/wtorch/data/_utils/__init__.py +45 -0
- wml/wtorch/data/_utils/collate.py +183 -0
- wml/wtorch/data/_utils/fetch.py +47 -0
- wml/wtorch/data/_utils/pin_memory.py +121 -0
- wml/wtorch/data/_utils/signal_handling.py +72 -0
- wml/wtorch/data/_utils/worker.py +227 -0
- wml/wtorch/data/base_data_loader_iter.py +93 -0
- wml/wtorch/data/dataloader.py +501 -0
- wml/wtorch/data/datapipes/__init__.py +1 -0
- wml/wtorch/data/datapipes/iter/__init__.py +12 -0
- wml/wtorch/data/datapipes/iter/batch.py +126 -0
- wml/wtorch/data/datapipes/iter/callable.py +92 -0
- wml/wtorch/data/datapipes/iter/listdirfiles.py +37 -0
- wml/wtorch/data/datapipes/iter/loadfilesfromdisk.py +30 -0
- wml/wtorch/data/datapipes/iter/readfilesfromtar.py +60 -0
- wml/wtorch/data/datapipes/iter/readfilesfromzip.py +63 -0
- wml/wtorch/data/datapipes/iter/sampler.py +94 -0
- wml/wtorch/data/datapipes/utils/__init__.py +0 -0
- wml/wtorch/data/datapipes/utils/common.py +65 -0
- wml/wtorch/data/dataset.py +354 -0
- wml/wtorch/data/datasets/__init__.py +4 -0
- wml/wtorch/data/datasets/common.py +53 -0
- wml/wtorch/data/datasets/listdirfilesdataset.py +36 -0
- wml/wtorch/data/datasets/loadfilesfromdiskdataset.py +30 -0
- wml/wtorch/data/distributed.py +135 -0
- wml/wtorch/data/multi_processing_data_loader_iter.py +866 -0
- wml/wtorch/data/sampler.py +267 -0
- wml/wtorch/data/single_process_data_loader_iter.py +24 -0
- wml/wtorch/data/test_data_loader.py +26 -0
- wml/wtorch/dataset_toolkit.py +67 -0
- wml/wtorch/depthwise_separable_conv_module.py +98 -0
- wml/wtorch/dist.py +591 -0
- wml/wtorch/dropblock/__init__.py +6 -0
- wml/wtorch/dropblock/dropblock.py +228 -0
- wml/wtorch/dropblock/dropout.py +40 -0
- wml/wtorch/dropblock/scheduler.py +48 -0
- wml/wtorch/ema.py +61 -0
- wml/wtorch/fc_module.py +73 -0
- wml/wtorch/functional.py +34 -0
- wml/wtorch/iter_dataset.py +26 -0
- wml/wtorch/loss.py +69 -0
- wml/wtorch/nets/__init__.py +0 -0
- wml/wtorch/nets/ckpt_toolkit.py +219 -0
- wml/wtorch/nets/fpn.py +276 -0
- wml/wtorch/nets/hrnet/__init__.py +0 -0
- wml/wtorch/nets/hrnet/config.py +2 -0
- wml/wtorch/nets/hrnet/hrnet.py +494 -0
- wml/wtorch/nets/misc.py +249 -0
- wml/wtorch/nets/resnet/__init__.py +0 -0
- wml/wtorch/nets/resnet/layers/__init__.py +17 -0
- wml/wtorch/nets/resnet/layers/aspp.py +144 -0
- wml/wtorch/nets/resnet/layers/batch_norm.py +231 -0
- wml/wtorch/nets/resnet/layers/blocks.py +111 -0
- wml/wtorch/nets/resnet/layers/wrappers.py +110 -0
- wml/wtorch/nets/resnet/r50_config.py +38 -0
- wml/wtorch/nets/resnet/resnet.py +691 -0
- wml/wtorch/nets/shape_spec.py +20 -0
- wml/wtorch/nets/simple_fpn.py +101 -0
- wml/wtorch/nms.py +109 -0
- wml/wtorch/nn.py +896 -0
- wml/wtorch/ocr_block.py +193 -0
- wml/wtorch/summary.py +331 -0
- wml/wtorch/train_toolkit.py +603 -0
- wml/wtorch/transformer_blocks.py +266 -0
- wml/wtorch/utils.py +719 -0
- 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}")
|