segment-everything 0.1.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.
- segment_everything/__init__.py +5 -0
- segment_everything/augmentation/albumentations_helper.py +0 -0
- segment_everything/detect_and_segment.py +131 -0
- segment_everything/napari_helper.py +15 -0
- segment_everything/prompt_generator.py +188 -0
- segment_everything/py.typed +5 -0
- segment_everything/stacked_label_dataset.py +113 -0
- segment_everything/stacked_labels.py +428 -0
- segment_everything/vendored/PromptGuidedDecoder/Prompt_guided_Mask_Decoder.pt +0 -0
- segment_everything/vendored/__init__.py +5 -0
- segment_everything/vendored/dice.py +158 -0
- segment_everything/vendored/efficientvit/__init__.py +0 -0
- segment_everything/vendored/efficientvit/apps/__init__.py +0 -0
- segment_everything/vendored/efficientvit/apps/data_provider/__init__.py +7 -0
- segment_everything/vendored/efficientvit/apps/data_provider/augment/__init__.py +6 -0
- segment_everything/vendored/efficientvit/apps/data_provider/augment/bbox.py +30 -0
- segment_everything/vendored/efficientvit/apps/data_provider/augment/color_aug.py +78 -0
- segment_everything/vendored/efficientvit/apps/data_provider/base.py +254 -0
- segment_everything/vendored/efficientvit/apps/data_provider/random_resolution/__init__.py +6 -0
- segment_everything/vendored/efficientvit/apps/data_provider/random_resolution/_data_loader.py +1538 -0
- segment_everything/vendored/efficientvit/apps/data_provider/random_resolution/_data_worker.py +357 -0
- segment_everything/vendored/efficientvit/apps/data_provider/random_resolution/controller.py +100 -0
- segment_everything/vendored/efficientvit/apps/setup.py +150 -0
- segment_everything/vendored/efficientvit/apps/trainer/__init__.py +6 -0
- segment_everything/vendored/efficientvit/apps/trainer/base.py +318 -0
- segment_everything/vendored/efficientvit/apps/trainer/run_config.py +129 -0
- segment_everything/vendored/efficientvit/apps/utils/__init__.py +12 -0
- segment_everything/vendored/efficientvit/apps/utils/dist.py +32 -0
- segment_everything/vendored/efficientvit/apps/utils/ema.py +52 -0
- segment_everything/vendored/efficientvit/apps/utils/export.py +45 -0
- segment_everything/vendored/efficientvit/apps/utils/init.py +66 -0
- segment_everything/vendored/efficientvit/apps/utils/lr.py +52 -0
- segment_everything/vendored/efficientvit/apps/utils/metric.py +43 -0
- segment_everything/vendored/efficientvit/apps/utils/misc.py +101 -0
- segment_everything/vendored/efficientvit/apps/utils/opt.py +28 -0
- segment_everything/vendored/efficientvit/cls_model_zoo.py +79 -0
- segment_everything/vendored/efficientvit/clscore/__init__.py +0 -0
- segment_everything/vendored/efficientvit/clscore/data_provider/__init__.py +5 -0
- segment_everything/vendored/efficientvit/clscore/data_provider/imagenet.py +142 -0
- segment_everything/vendored/efficientvit/clscore/trainer/__init__.py +6 -0
- segment_everything/vendored/efficientvit/clscore/trainer/cls_run_config.py +18 -0
- segment_everything/vendored/efficientvit/clscore/trainer/cls_trainer.py +265 -0
- segment_everything/vendored/efficientvit/clscore/trainer/utils/__init__.py +7 -0
- segment_everything/vendored/efficientvit/clscore/trainer/utils/label_smooth.py +18 -0
- segment_everything/vendored/efficientvit/clscore/trainer/utils/metric.py +23 -0
- segment_everything/vendored/efficientvit/clscore/trainer/utils/mixup.py +67 -0
- segment_everything/vendored/efficientvit/models/__init__.py +0 -0
- segment_everything/vendored/efficientvit/models/efficientvit/__init__.py +8 -0
- segment_everything/vendored/efficientvit/models/efficientvit/backbone.py +380 -0
- segment_everything/vendored/efficientvit/models/efficientvit/cls.py +188 -0
- segment_everything/vendored/efficientvit/models/efficientvit/sam.py +181 -0
- segment_everything/vendored/efficientvit/models/efficientvit/seg.py +373 -0
- segment_everything/vendored/efficientvit/models/nn/__init__.py +8 -0
- segment_everything/vendored/efficientvit/models/nn/act.py +30 -0
- segment_everything/vendored/efficientvit/models/nn/drop.py +104 -0
- segment_everything/vendored/efficientvit/models/nn/norm.py +164 -0
- segment_everything/vendored/efficientvit/models/nn/ops.py +597 -0
- segment_everything/vendored/efficientvit/models/utils/__init__.py +7 -0
- segment_everything/vendored/efficientvit/models/utils/list.py +53 -0
- segment_everything/vendored/efficientvit/models/utils/network.py +73 -0
- segment_everything/vendored/efficientvit/models/utils/random.py +65 -0
- segment_everything/vendored/efficientvit/sam_model_zoo.py +45 -0
- segment_everything/vendored/efficientvit/seg_model_zoo.py +70 -0
- segment_everything/vendored/get_object_aware.py +26 -0
- segment_everything/vendored/mobilesamv2/__init__.py +16 -0
- segment_everything/vendored/mobilesamv2/automatic_mask_generator.py +415 -0
- segment_everything/vendored/mobilesamv2/build_sam.py +246 -0
- segment_everything/vendored/mobilesamv2/modeling/__init__.py +11 -0
- segment_everything/vendored/mobilesamv2/modeling/common.py +43 -0
- segment_everything/vendored/mobilesamv2/modeling/image_encoder.py +394 -0
- segment_everything/vendored/mobilesamv2/modeling/mask_decoder.py +213 -0
- segment_everything/vendored/mobilesamv2/modeling/prompt_encoder.py +217 -0
- segment_everything/vendored/mobilesamv2/modeling/sam.py +203 -0
- segment_everything/vendored/mobilesamv2/modeling/transformer.py +240 -0
- segment_everything/vendored/mobilesamv2/predictor.py +384 -0
- segment_everything/vendored/mobilesamv2/utils/__init__.py +5 -0
- segment_everything/vendored/mobilesamv2/utils/amg.py +347 -0
- segment_everything/vendored/mobilesamv2/utils/onnx.py +144 -0
- segment_everything/vendored/mobilesamv2/utils/transforms.py +103 -0
- segment_everything/vendored/object_detection/__init__.py +0 -0
- segment_everything/vendored/object_detection/ultralytics/__init__.py +5 -0
- segment_everything/vendored/object_detection/ultralytics/nn/__init__.py +9 -0
- segment_everything/vendored/object_detection/ultralytics/nn/autobackend.py +658 -0
- segment_everything/vendored/object_detection/ultralytics/nn/autoshape.py +397 -0
- segment_everything/vendored/object_detection/ultralytics/nn/modules/__init__.py +110 -0
- segment_everything/vendored/object_detection/ultralytics/nn/modules/block.py +304 -0
- segment_everything/vendored/object_detection/ultralytics/nn/modules/conv.py +297 -0
- segment_everything/vendored/object_detection/ultralytics/nn/modules/head.py +468 -0
- segment_everything/vendored/object_detection/ultralytics/nn/modules/transformer.py +378 -0
- segment_everything/vendored/object_detection/ultralytics/nn/modules/utils.py +78 -0
- segment_everything/vendored/object_detection/ultralytics/nn/tasks.py +1049 -0
- segment_everything/vendored/object_detection/ultralytics/prompt_mobilesamv2/__init__.py +6 -0
- segment_everything/vendored/object_detection/ultralytics/prompt_mobilesamv2/model.py +104 -0
- segment_everything/vendored/object_detection/ultralytics/prompt_mobilesamv2/predict.py +95 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/__init__.py +5 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/cfg/__init__.py +588 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/cfg/default.yaml +117 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/data/__init__.py +9 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/data/annotator.py +53 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/data/augment.py +899 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/data/base.py +286 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/data/build.py +213 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/data/converter.py +358 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/data/dataloaders/__init__.py +0 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/data/dataloaders/stream_loaders.py +459 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/data/dataset.py +274 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/data/dataset_wrappers.py +53 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/data/scripts/download_weights.sh +18 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/data/scripts/get_coco.sh +60 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/data/scripts/get_coco128.sh +17 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/data/scripts/get_imagenet.sh +51 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/data/utils.py +716 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/engine/__init__.py +0 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/engine/exporter.py +1214 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/engine/model.py +641 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/engine/predictor.py +461 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/engine/results.py +741 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/utils/__init__.py +893 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/utils/autobatch.py +108 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/utils/callbacks/__init__.py +5 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/utils/callbacks/base.py +212 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/utils/checks.py +547 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/utils/dist.py +67 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/utils/downloads.py +353 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/utils/errors.py +12 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/utils/files.py +100 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/utils/instance.py +391 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/utils/loss.py +579 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/utils/metrics.py +1189 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/utils/ops.py +870 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/utils/patches.py +45 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/utils/plotting.py +767 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/utils/tal.py +276 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/utils/torch_utils.py +684 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/utils/tuner.py +54 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/v8/__init__.py +5 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/v8/detect/__init__.py +5 -0
- segment_everything/vendored/object_detection/ultralytics/yolo/v8/detect/predict.py +69 -0
- segment_everything/vendored/tinyvit/__init__.py +2 -0
- segment_everything/vendored/tinyvit/tiny_vit.py +867 -0
- segment_everything/weights_helper.py +124 -0
- segment_everything-0.1.0.dist-info/METADATA +53 -0
- segment_everything-0.1.0.dist-info/RECORD +145 -0
- segment_everything-0.1.0.dist-info/WHEEL +4 -0
- segment_everything-0.1.0.dist-info/licenses/LICENSE +28 -0
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import numpy as np
|
|
3
|
+
|
|
4
|
+
from skimage.measure import label, regionprops
|
|
5
|
+
|
|
6
|
+
class StackedLabels:
|
|
7
|
+
"""
|
|
8
|
+
A class to manage and manipulate a list of masks
|
|
9
|
+
|
|
10
|
+
Attributes:
|
|
11
|
+
-----------
|
|
12
|
+
image : numpy.ndarray
|
|
13
|
+
The input image.
|
|
14
|
+
label_image : numpy.ndarray
|
|
15
|
+
The label image where each unique integer represents a different object.
|
|
16
|
+
mask_list : list
|
|
17
|
+
A list of masks, each represented as a dictionary containing various attributes such as
|
|
18
|
+
segmentation, bounding box, point coordinates, area, etc. The segmentation attribute contains the
|
|
19
|
+
binary representation of the mask.
|
|
20
|
+
|
|
21
|
+
Methods:
|
|
22
|
+
--------
|
|
23
|
+
__init__(self, mask_list=None, image=None, label_image=None)
|
|
24
|
+
Initializes the StackedLabels object with optional mask list, image, and label image.
|
|
25
|
+
|
|
26
|
+
create_mask_from_segmentation(segmentation, image=None)
|
|
27
|
+
Static method to create a mask dictionary from a segmentation array and optionally an image.
|
|
28
|
+
|
|
29
|
+
create_mask_from_yolo_bbox(bbox_yolo, image)
|
|
30
|
+
Static method to create a mask dictionary from a YOLO bounding box and an image.
|
|
31
|
+
|
|
32
|
+
get_bbox(segmentation)
|
|
33
|
+
Static method to calculate the bounding box of a given segmentation array.
|
|
34
|
+
|
|
35
|
+
read_yolo_txt(file_path)
|
|
36
|
+
Static method to read YOLO format results from a text file.
|
|
37
|
+
|
|
38
|
+
from_2d_label_image(cls, label_image, image, relabel=True)
|
|
39
|
+
Class method to create a StackedLabels object from a 2D label image and an image.
|
|
40
|
+
|
|
41
|
+
from_yolo_results(cls, results, image, relabel=True)
|
|
42
|
+
Class method to create a StackedLabels object from YOLO results and an image.
|
|
43
|
+
|
|
44
|
+
add_segmentation(self, segmentation)
|
|
45
|
+
Adds a new segmentation to the mask list.
|
|
46
|
+
|
|
47
|
+
add_background_results(self, num_background_results=1)
|
|
48
|
+
Adds empty masks for background regions to the mask list.
|
|
49
|
+
|
|
50
|
+
make_3d_label_image(self)
|
|
51
|
+
Constructs a 3D label image from the mask list.
|
|
52
|
+
|
|
53
|
+
make_2d_labels(self, type="min")
|
|
54
|
+
Creates a 2D label image by performing a min or max projection of the 3D label image.
|
|
55
|
+
|
|
56
|
+
get_bbox_np(self)
|
|
57
|
+
Returns a numpy array of bounding boxes for all masks in the mask list.
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
def __init__(self, mask_list=None, image = None, label_image=None):
|
|
61
|
+
self.image = image
|
|
62
|
+
|
|
63
|
+
if image is not None and image.ndim == 2:
|
|
64
|
+
self.image = np.stack([self.image, self.image, self.image], axis=-1)
|
|
65
|
+
|
|
66
|
+
self.label_image = label_image
|
|
67
|
+
|
|
68
|
+
if mask_list is None:
|
|
69
|
+
self.mask_list = []
|
|
70
|
+
else:
|
|
71
|
+
self.mask_list = mask_list
|
|
72
|
+
|
|
73
|
+
self.mask_list = sorted(self.mask_list, key=lambda x: x['area'], reverse=False)
|
|
74
|
+
|
|
75
|
+
@staticmethod
|
|
76
|
+
def create_mask_from_segmentation(segmentation, image=None):
|
|
77
|
+
"""
|
|
78
|
+
Initializes the StackedLabels object.
|
|
79
|
+
|
|
80
|
+
Parameters:
|
|
81
|
+
-----------
|
|
82
|
+
mask_list : list, optional
|
|
83
|
+
A list of mask dictionaries to initialize with. Each dictionary represents a mask and contains
|
|
84
|
+
various attributes such as segmentation, bounding box, point coordinates, area, etc.
|
|
85
|
+
Defaults to an empty list if not provided.
|
|
86
|
+
image : numpy.ndarray, optional
|
|
87
|
+
The input image. If provided and is a 2D array, it is converted to a 3-channel image by stacking
|
|
88
|
+
the 2D array along the last axis. This is done to ensure the image has three channels, which is
|
|
89
|
+
often required for further processing by SAM models.
|
|
90
|
+
label_image : numpy.ndarray, optional
|
|
91
|
+
The label image to initialize with. The label image is a 2D array where each unique integer
|
|
92
|
+
represents a different object or region. This can be useful for creating masks and further
|
|
93
|
+
segmenting the image.
|
|
94
|
+
"""
|
|
95
|
+
mask = {}
|
|
96
|
+
mask['segmentation'] = segmentation
|
|
97
|
+
y, x = np.where(segmentation)
|
|
98
|
+
mask['indexes'] = [y, x]
|
|
99
|
+
mask['point_coords'] = [[np.mean(x), np.mean(y)]]
|
|
100
|
+
mask['prompt_bbox'] = StackedLabels.get_bbox(segmentation)
|
|
101
|
+
mask['area'] = np.sum(segmentation)
|
|
102
|
+
mask['predicted_iou'] = 1
|
|
103
|
+
mask['stability_score'] = 1
|
|
104
|
+
if image is not None:
|
|
105
|
+
mask['image'] = image
|
|
106
|
+
return mask
|
|
107
|
+
|
|
108
|
+
@staticmethod
|
|
109
|
+
def create_mask_from_xywhn_bbox(bbox_yolo, image):
|
|
110
|
+
"""
|
|
111
|
+
Creates a mask from a YOLO bounding box.
|
|
112
|
+
|
|
113
|
+
Parameters:
|
|
114
|
+
-----------
|
|
115
|
+
bbox_yolo : list or tuple
|
|
116
|
+
The YOLO bounding box, typically in the format [x_center, y_center, width, height].
|
|
117
|
+
The coordinates and dimensions are normalized to the range [0, 1].
|
|
118
|
+
image : numpy.ndarray
|
|
119
|
+
The image on which the bounding box is defined. The shape of the image is used to convert
|
|
120
|
+
the normalized coordinates and dimensions of the bounding box to pixel values.
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
--------
|
|
124
|
+
dict
|
|
125
|
+
A dictionary representing the mask created from the YOLO bounding box. The dictionary contains
|
|
126
|
+
various attributes such as the segmentation mask, bounding box, point coordinates, area, and
|
|
127
|
+
other relevant information.
|
|
128
|
+
|
|
129
|
+
Notes:
|
|
130
|
+
------
|
|
131
|
+
- The method converts the YOLO bounding box coordinates from normalized values to pixel values
|
|
132
|
+
based on the dimensions of the input image.
|
|
133
|
+
- The segmentation mask is created by setting the pixels within the bounding box to True.
|
|
134
|
+
"""
|
|
135
|
+
# need to convert bbox to pixels
|
|
136
|
+
x, y, w, h = bbox_yolo
|
|
137
|
+
w = abs(w)
|
|
138
|
+
h = abs(h)
|
|
139
|
+
x_min, y_min = (x-w/2, y-h/2)
|
|
140
|
+
x_max, y_max = x+w/2, y+h/2
|
|
141
|
+
x_min = x_min * image.shape[1]
|
|
142
|
+
x_max = x_max * image.shape[1]
|
|
143
|
+
y_min = y_min * image.shape[0]
|
|
144
|
+
y_max = y_max * image.shape[0]
|
|
145
|
+
|
|
146
|
+
return StackedLabels.create_mask_from_xyxy_bbox([x_min, y_min, x_max, y_max], image)
|
|
147
|
+
|
|
148
|
+
@staticmethod
|
|
149
|
+
def create_mask_from_xyxy_bbox(bbox_yolo, image):
|
|
150
|
+
"""
|
|
151
|
+
Creates a mask from a YOLO xyxy bounding box.
|
|
152
|
+
|
|
153
|
+
Parameters:
|
|
154
|
+
-----------
|
|
155
|
+
bbox_yolo : list or tuple
|
|
156
|
+
The YOLO bounding box
|
|
157
|
+
image : numpy.ndarray
|
|
158
|
+
The image on which the bounding box is defined.
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
--------
|
|
162
|
+
dict
|
|
163
|
+
A dictionary representing the mask created from the YOLO bounding box. The dictionary contains
|
|
164
|
+
various attributes such as the segmentation mask, bounding box, point coordinates, area, and
|
|
165
|
+
other relevant information.
|
|
166
|
+
|
|
167
|
+
Notes:
|
|
168
|
+
------
|
|
169
|
+
- The method converts the YOLO bounding box coordinates from normalized values to pixel values
|
|
170
|
+
based on the dimensions of the input image.
|
|
171
|
+
- The segmentation mask is created by setting the pixels within the bounding box to True.
|
|
172
|
+
"""
|
|
173
|
+
# need to convert bbox to pixels
|
|
174
|
+
x_min, y_min, x_max, y_max = bbox_yolo
|
|
175
|
+
segmentation = np.zeros(image.shape[:2], dtype=bool)
|
|
176
|
+
segmentation[int(y_min):int(y_max)+1, int(x_min):int(x_max)+1] = True
|
|
177
|
+
if segmentation.sum() == 0:
|
|
178
|
+
print('empty')
|
|
179
|
+
|
|
180
|
+
return StackedLabels.create_mask_from_segmentation(segmentation, image)
|
|
181
|
+
|
|
182
|
+
@staticmethod
|
|
183
|
+
def get_bbox(segmentation):
|
|
184
|
+
"""
|
|
185
|
+
Computes the bounding box of a segmentation mask.
|
|
186
|
+
|
|
187
|
+
Parameters:
|
|
188
|
+
-----------
|
|
189
|
+
segmentation : numpy.ndarray
|
|
190
|
+
A 2D binary array representing the segmentation mask. Pixels belonging to the segmented
|
|
191
|
+
object are marked with True (or 1), and the background pixels are marked with False (or 0).
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
--------
|
|
195
|
+
list
|
|
196
|
+
A list of four integers representing the bounding box of the segmented object in the format
|
|
197
|
+
[x_min, y_min, x_max, y_max]. These coordinates define the smallest rectangle that can
|
|
198
|
+
contain all the True pixels in the segmentation mask.
|
|
199
|
+
"""
|
|
200
|
+
y, x = np.where(segmentation > 0)
|
|
201
|
+
x_min, x_max = np.min(x), np.max(x)
|
|
202
|
+
y_min, y_max = np.min(y), np.max(y)
|
|
203
|
+
return [x_min, y_min, x_max, y_max]
|
|
204
|
+
|
|
205
|
+
@staticmethod
|
|
206
|
+
def read_yolo_txt(file_path):
|
|
207
|
+
"""
|
|
208
|
+
Reads a YOLO format text file and extracts bounding box information.
|
|
209
|
+
|
|
210
|
+
Parameters:
|
|
211
|
+
-----------
|
|
212
|
+
file_path : str
|
|
213
|
+
The path to the YOLO format text file. Each line in the file represents one bounding box and
|
|
214
|
+
contains the class ID followed by the normalized coordinates and dimensions of the bounding
|
|
215
|
+
box (x_center, y_center, width, height).
|
|
216
|
+
|
|
217
|
+
Returns:
|
|
218
|
+
--------
|
|
219
|
+
list
|
|
220
|
+
A list of dictionaries, each containing the following keys:
|
|
221
|
+
- 'class_id': int, the class ID of the object.
|
|
222
|
+
- 'bbox': list of floats, the bounding box coordinates and dimensions in the format
|
|
223
|
+
[x_center, y_center, width, height], with values normalized to the range [0, 1].
|
|
224
|
+
|
|
225
|
+
"""
|
|
226
|
+
with open(file_path, 'r') as file:
|
|
227
|
+
lines = file.readlines()
|
|
228
|
+
|
|
229
|
+
results = []
|
|
230
|
+
for line in lines:
|
|
231
|
+
parts = line.strip().split()
|
|
232
|
+
class_id = int(parts[0])
|
|
233
|
+
bbox = [float(x) for x in parts[1:5]]
|
|
234
|
+
results.append({'class_id': class_id, 'bbox': bbox})
|
|
235
|
+
|
|
236
|
+
return results
|
|
237
|
+
|
|
238
|
+
@classmethod
|
|
239
|
+
def from_2d_label_image(cls, label_image, image, relabel=True):
|
|
240
|
+
"""
|
|
241
|
+
Creates an instance of StackedLabels from a 2D label image.
|
|
242
|
+
|
|
243
|
+
Parameters:
|
|
244
|
+
-----------
|
|
245
|
+
label_image : numpy.ndarray
|
|
246
|
+
A 2D array where each unique integer represents a different object or region. This label image
|
|
247
|
+
is used to create segmentation masks for each unique region.
|
|
248
|
+
image : numpy.ndarray
|
|
249
|
+
The input image corresponding to the label image.
|
|
250
|
+
to each mask.
|
|
251
|
+
relabel : bool, optional
|
|
252
|
+
If True, the label image is relabeled to ensure that the labels are consecutive integers starting
|
|
253
|
+
from 1. This can be useful if the label image has gaps, repeat labels or non-sequential labels. Default is True.
|
|
254
|
+
|
|
255
|
+
Returns:
|
|
256
|
+
--------
|
|
257
|
+
StackedLabels
|
|
258
|
+
An instance of the StackedLabels class, initialized with masks created from the label image and
|
|
259
|
+
the input image.
|
|
260
|
+
"""
|
|
261
|
+
if (relabel):
|
|
262
|
+
label_image = label(label_image)
|
|
263
|
+
|
|
264
|
+
mask_list = []
|
|
265
|
+
for region in regionprops(label_image):
|
|
266
|
+
segmentation = np.zeros_like(label_image, dtype=bool)
|
|
267
|
+
segmentation[label_image == region.label] = True
|
|
268
|
+
mask = cls.create_mask_from_segmentation(segmentation, image)
|
|
269
|
+
mask_list.append(mask)
|
|
270
|
+
|
|
271
|
+
return cls(mask_list, image, label_image)
|
|
272
|
+
|
|
273
|
+
@classmethod
|
|
274
|
+
def from_yolo_results(cls, bboxes, classes, image):
|
|
275
|
+
"""
|
|
276
|
+
Creates an instance of StackedLabels from YOLO detection results.
|
|
277
|
+
|
|
278
|
+
Parameters:
|
|
279
|
+
-----------
|
|
280
|
+
results : list
|
|
281
|
+
A list of dictionaries, each containing the following keys:
|
|
282
|
+
- 'class_id': int, the class ID of the object.
|
|
283
|
+
- 'bbox': list of floats, the bounding box coordinates and dimensions in the format
|
|
284
|
+
[x_center, y_center, width, height], with values normalized to the range [0, 1].
|
|
285
|
+
image : numpy.ndarray
|
|
286
|
+
The input image corresponding to the YOLO detection results. This image is used to create
|
|
287
|
+
segmentation masks from the bounding boxes.
|
|
288
|
+
|
|
289
|
+
Returns:
|
|
290
|
+
--------
|
|
291
|
+
StackedLabels
|
|
292
|
+
An instance of the StackedLabels class, initialized with masks created from the YOLO detection
|
|
293
|
+
results and the input image.
|
|
294
|
+
"""
|
|
295
|
+
mask_list = []
|
|
296
|
+
for i in range(bboxes.shape[0]):
|
|
297
|
+
#class_id = classes[i]
|
|
298
|
+
bbox = bboxes[i,:]
|
|
299
|
+
|
|
300
|
+
mask = cls.create_mask_from_xyxy_bbox(bbox, image)
|
|
301
|
+
mask_list.append(mask)
|
|
302
|
+
|
|
303
|
+
return cls(mask_list, image)
|
|
304
|
+
|
|
305
|
+
@classmethod
|
|
306
|
+
def from_yolo_dictionary(cls, results, image, format='xyxy'):
|
|
307
|
+
"""
|
|
308
|
+
Creates an instance of StackedLabels from YOLO format dictionary that is often generated when labelling
|
|
309
|
+
(ie with napari or other bounding box drawing tool)
|
|
310
|
+
|
|
311
|
+
Parameters:
|
|
312
|
+
-----------
|
|
313
|
+
results : list
|
|
314
|
+
A list of dictionaries, each containing the following keys:
|
|
315
|
+
- 'class_id': int, the class ID of the object.
|
|
316
|
+
- 'bbox': list of floats, the bounding box coordinates and dimensions in the format
|
|
317
|
+
[x_center, y_center, width, height], with values normalized to the range [0, 1].
|
|
318
|
+
image : numpy.ndarray
|
|
319
|
+
The input image corresponding to the YOLO detection results. This image is used to create
|
|
320
|
+
segmentation masks from the bounding boxes.
|
|
321
|
+
|
|
322
|
+
Returns:
|
|
323
|
+
--------
|
|
324
|
+
StackedLabels
|
|
325
|
+
An instance of the StackedLabels class, initialized with masks created from the YOLO detection
|
|
326
|
+
results and the input image.
|
|
327
|
+
"""
|
|
328
|
+
mask_list = []
|
|
329
|
+
for result in results:
|
|
330
|
+
class_id = result['class_id']
|
|
331
|
+
bbox = result['bbox']
|
|
332
|
+
|
|
333
|
+
if format == 'xyxy':
|
|
334
|
+
mask = cls.create_mask_from_xyxy_bbox(bbox, image)
|
|
335
|
+
else:
|
|
336
|
+
mask = cls.create_mask_from_xywhn_bbox(bbox, image)
|
|
337
|
+
mask_list.append(mask)
|
|
338
|
+
|
|
339
|
+
return cls(mask_list, image)
|
|
340
|
+
|
|
341
|
+
def add_segmentation(self, segmentation):
|
|
342
|
+
"""
|
|
343
|
+
Adds a new dictionary to the mask list.
|
|
344
|
+
|
|
345
|
+
Parameters:
|
|
346
|
+
-----------
|
|
347
|
+
segmentation : numpy.ndarray
|
|
348
|
+
A boolean array where True indicates the presence of an object.
|
|
349
|
+
"""
|
|
350
|
+
stacked_label = self.create_mask_from_segmentation(segmentation)
|
|
351
|
+
self.mask_list.append(stacked_label)
|
|
352
|
+
|
|
353
|
+
def add_background_results(self, num_background_results=1):
|
|
354
|
+
"""
|
|
355
|
+
Adds empty masks for background regions to the mask list. this is useful if training a model and we
|
|
356
|
+
want to avoid false positives. Thus we add empty masks to represent the background (or objects which should
|
|
357
|
+
not be detected).
|
|
358
|
+
|
|
359
|
+
Parameters:
|
|
360
|
+
-----------
|
|
361
|
+
num_background_results : int, optional
|
|
362
|
+
The number of background results to add. Defaults to 1.
|
|
363
|
+
"""
|
|
364
|
+
|
|
365
|
+
for i in range(num_background_results):
|
|
366
|
+
background = (self.label_image == 0)
|
|
367
|
+
x,y = np.where(background)
|
|
368
|
+
empty_mask = {}
|
|
369
|
+
empty_mask['segmentation'] = np.zeros_like(self.label_image, dtype=bool)
|
|
370
|
+
empty_mask['indexes'] = [x,y]
|
|
371
|
+
|
|
372
|
+
# choose random index for point_coords
|
|
373
|
+
idx = np.random.randint(len(x))
|
|
374
|
+
empty_mask['point_coords'] = [[x[idx], y[idx]]]
|
|
375
|
+
|
|
376
|
+
# add pointer to image
|
|
377
|
+
if self.image is not None:
|
|
378
|
+
empty_mask['image'] = self.image
|
|
379
|
+
|
|
380
|
+
self.mask_list.append(empty_mask)
|
|
381
|
+
|
|
382
|
+
def make_3d_label_image(self):
|
|
383
|
+
"""
|
|
384
|
+
Make a 3D label image from the mask list, each layer of the 3D label image will contain a single mask
|
|
385
|
+
this is useful for visualizing the mask collection
|
|
386
|
+
"""
|
|
387
|
+
num_masks = len(self.mask_list)
|
|
388
|
+
mask_shape = [num_masks, *self.mask_list[0]['segmentation'].shape]
|
|
389
|
+
self.label_image = np.zeros(mask_shape, dtype=np.uint16)
|
|
390
|
+
for i, mask in enumerate(self.mask_list):
|
|
391
|
+
self.label_image[i] = mask['segmentation']*(i+1)
|
|
392
|
+
|
|
393
|
+
def make_2d_labels(self, type="min"):
|
|
394
|
+
"""
|
|
395
|
+
Make a 2D label image by performing a min or max projection of the 3D label image.
|
|
396
|
+
|
|
397
|
+
Returns:
|
|
398
|
+
--------
|
|
399
|
+
numpy.ndarray
|
|
400
|
+
A 2D label image where each unique integer represents a different object or region.
|
|
401
|
+
"""
|
|
402
|
+
self.make_3d_label_image()
|
|
403
|
+
|
|
404
|
+
if type == "min":
|
|
405
|
+
# Create a masked array where zeros are masked
|
|
406
|
+
masked_label_image = np.ma.masked_equal(self.label_image, 0)
|
|
407
|
+
# Perform the min projection on the masked array
|
|
408
|
+
_2d_labels = np.ma.min(masked_label_image, axis=0).filled(0)
|
|
409
|
+
else:
|
|
410
|
+
_2d_labels = np.max(self.label_image, axis=0)
|
|
411
|
+
|
|
412
|
+
return _2d_labels
|
|
413
|
+
|
|
414
|
+
def get_bbox_np(self):
|
|
415
|
+
"""
|
|
416
|
+
Get the bounding boxes for all masks in the mask list as a numpy array"
|
|
417
|
+
|
|
418
|
+
Returns:
|
|
419
|
+
--------
|
|
420
|
+
numpy.ndarray
|
|
421
|
+
A numpy array of bounding boxes for all masks in the mask list.
|
|
422
|
+
"""
|
|
423
|
+
bboxes = []
|
|
424
|
+
for mask in self.mask_list:
|
|
425
|
+
bboxes.append(mask['prompt_bbox'])
|
|
426
|
+
return np.array(bboxes)
|
|
427
|
+
|
|
428
|
+
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import torch
|
|
2
|
+
import torch.nn as nn
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
# TODO refactor
|
|
6
|
+
def flatten_samples(input_):
|
|
7
|
+
"""
|
|
8
|
+
Flattens a tensor or a variable such that the channel axis is first and the sample axis
|
|
9
|
+
is second. The shapes are transformed as follows:
|
|
10
|
+
(N, C, H, W) --> (C, N * H * W)
|
|
11
|
+
(N, C, D, H, W) --> (C, N * D * H * W)
|
|
12
|
+
(N, C) --> (C, N)
|
|
13
|
+
The input must be atleast 2d.
|
|
14
|
+
"""
|
|
15
|
+
# Get number of channels
|
|
16
|
+
num_channels = input_.size(1)
|
|
17
|
+
# Permute the channel axis to first
|
|
18
|
+
permute_axes = list(range(input_.dim()))
|
|
19
|
+
permute_axes[0], permute_axes[1] = permute_axes[1], permute_axes[0]
|
|
20
|
+
# For input shape (say) NCHW, this should have the shape CNHW
|
|
21
|
+
permuted = input_.permute(*permute_axes).contiguous()
|
|
22
|
+
# Now flatten out all but the first axis and return
|
|
23
|
+
flattened = permuted.view(num_channels, -1)
|
|
24
|
+
return flattened
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def dice_score(input_, target, invert=False, channelwise=True, reduce_channel="sum", eps=1e-7):
|
|
28
|
+
if input_.shape != target.shape:
|
|
29
|
+
raise ValueError(f"Expect input and target of same shape, got: {input_.shape}, {target.shape}.")
|
|
30
|
+
|
|
31
|
+
if channelwise:
|
|
32
|
+
# Flatten input and target to have the shape (C, N),
|
|
33
|
+
# where N is the number of samples
|
|
34
|
+
input_ = flatten_samples(input_)
|
|
35
|
+
target = flatten_samples(target)
|
|
36
|
+
# Compute numerator and denominator (by summing over samples and
|
|
37
|
+
# leaving the channels intact)
|
|
38
|
+
numerator = (input_ * target).sum(-1)
|
|
39
|
+
denominator = (input_ * input_).sum(-1) + (target * target).sum(-1)
|
|
40
|
+
channelwise_score = 2 * (numerator / denominator.clamp(min=eps))
|
|
41
|
+
if invert:
|
|
42
|
+
channelwise_score = 1. - channelwise_score
|
|
43
|
+
|
|
44
|
+
# Reduce the dice score over the channels to compute the overall dice score.
|
|
45
|
+
# (default is to use the sum)
|
|
46
|
+
if reduce_channel is None:
|
|
47
|
+
score = channelwise_score
|
|
48
|
+
elif reduce_channel == "sum":
|
|
49
|
+
score = channelwise_score.sum()
|
|
50
|
+
elif reduce_channel == "mean":
|
|
51
|
+
score = channelwise_score.mean()
|
|
52
|
+
elif reduce_channel == "max":
|
|
53
|
+
score = channelwise_score.max()
|
|
54
|
+
elif reduce_channel == "min":
|
|
55
|
+
score = channelwise_score.min()
|
|
56
|
+
else:
|
|
57
|
+
raise ValueError(f"Unsupported channel reduction {reduce_channel}")
|
|
58
|
+
|
|
59
|
+
else:
|
|
60
|
+
numerator = (input_ * target).sum()
|
|
61
|
+
denominator = (input_ * input_).sum() + (target * target).sum()
|
|
62
|
+
score = 2. * (numerator / denominator.clamp(min=eps))
|
|
63
|
+
if invert:
|
|
64
|
+
score = 1. - score
|
|
65
|
+
|
|
66
|
+
return score
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class DiceLoss(nn.Module):
|
|
70
|
+
def __init__(self, channelwise=True, eps=1e-7, reduce_channel="sum"):
|
|
71
|
+
if reduce_channel not in ("sum", "mean", "max", "min", None):
|
|
72
|
+
raise ValueError(f"Unsupported channel reduction {reduce_channel}")
|
|
73
|
+
super().__init__()
|
|
74
|
+
self.channelwise = channelwise
|
|
75
|
+
self.eps = eps
|
|
76
|
+
self.reduce_channel = reduce_channel
|
|
77
|
+
|
|
78
|
+
# all torch_em classes should store init kwargs to easily recreate the init call
|
|
79
|
+
self.init_kwargs = {"channelwise": channelwise, "eps": self.eps, "reduce_channel": self.reduce_channel}
|
|
80
|
+
|
|
81
|
+
def forward(self, input_, target):
|
|
82
|
+
return dice_score(input_, target,
|
|
83
|
+
invert=True, channelwise=self.channelwise,
|
|
84
|
+
eps=self.eps, reduce_channel=self.reduce_channel)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class DiceLossWithLogits(nn.Module):
|
|
88
|
+
def __init__(self, channelwise=True, eps=1e-7, reduce_channel="sum"):
|
|
89
|
+
super().__init__()
|
|
90
|
+
self.channelwise = channelwise
|
|
91
|
+
self.eps = eps
|
|
92
|
+
self.reduce_channel = reduce_channel
|
|
93
|
+
|
|
94
|
+
# all torch_em classes should store init kwargs to easily recreate the init call
|
|
95
|
+
self.init_kwargs = {"channelwise": channelwise, "eps": self.eps, "reduce_channel": self.reduce_channel}
|
|
96
|
+
|
|
97
|
+
def forward(self, input_, target):
|
|
98
|
+
return dice_score(
|
|
99
|
+
nn.functional.sigmoid(input_),
|
|
100
|
+
target,
|
|
101
|
+
invert=True,
|
|
102
|
+
channelwise=self.channelwise,
|
|
103
|
+
eps=self.eps,
|
|
104
|
+
reduce_channel=self.reduce_channel,
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class BCEDiceLoss(nn.Module):
|
|
109
|
+
|
|
110
|
+
def __init__(self, alpha=1., beta=1., channelwise=True, eps=1e-7):
|
|
111
|
+
super().__init__()
|
|
112
|
+
self.alpha = alpha
|
|
113
|
+
self.beta = beta
|
|
114
|
+
self.channelwise = channelwise
|
|
115
|
+
self.eps = eps
|
|
116
|
+
|
|
117
|
+
# all torch_em classes should store init kwargs to easily recreate the init call
|
|
118
|
+
self.init_kwargs = {"alpha": alpha, "beta": beta, "channelwise": channelwise, "eps": self.eps}
|
|
119
|
+
|
|
120
|
+
def forward(self, input_, target):
|
|
121
|
+
loss_dice = dice_score(
|
|
122
|
+
input_,
|
|
123
|
+
target,
|
|
124
|
+
invert=True,
|
|
125
|
+
channelwise=self.channelwise,
|
|
126
|
+
eps=self.eps
|
|
127
|
+
)
|
|
128
|
+
loss_bce = nn.functional.binary_cross_entropy(
|
|
129
|
+
input_, target
|
|
130
|
+
)
|
|
131
|
+
return self.alpha * loss_dice + self.beta * loss_bce
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
# TODO think about how to handle combined losses like this for mixed precision training
|
|
135
|
+
class BCEDiceLossWithLogits(nn.Module):
|
|
136
|
+
|
|
137
|
+
def __init__(self, alpha=1., beta=1., channelwise=True, eps=1e-7):
|
|
138
|
+
super().__init__()
|
|
139
|
+
self.alpha = alpha
|
|
140
|
+
self.beta = beta
|
|
141
|
+
self.channelwise = channelwise
|
|
142
|
+
self.eps = eps
|
|
143
|
+
|
|
144
|
+
# all torch_em classes should store init kwargs to easily recreate the init call
|
|
145
|
+
self.init_kwargs = {"alpha": alpha, "beta": beta, "channelwise": channelwise, "eps": self.eps}
|
|
146
|
+
|
|
147
|
+
def forward(self, input_, target):
|
|
148
|
+
loss_dice = dice_score(
|
|
149
|
+
torch.sigmoid(input_),
|
|
150
|
+
target,
|
|
151
|
+
invert=True,
|
|
152
|
+
channelwise=self.channelwise,
|
|
153
|
+
eps=self.eps
|
|
154
|
+
)
|
|
155
|
+
loss_bce = nn.functional.binary_cross_entropy_with_logits(
|
|
156
|
+
input_, target
|
|
157
|
+
)
|
|
158
|
+
return self.alpha * loss_dice + self.beta * loss_bce
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# EfficientViT: Multi-Scale Linear Attention for High-Resolution Dense Prediction
|
|
2
|
+
# Han Cai, Junyan Li, Muyan Hu, Chuang Gan, Song Han
|
|
3
|
+
# International Conference on Computer Vision (ICCV), 2023
|
|
4
|
+
|
|
5
|
+
from .augment import *
|
|
6
|
+
from .base import *
|
|
7
|
+
from .random_resolution import *
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# EfficientViT: Multi-Scale Linear Attention for High-Resolution Dense Prediction
|
|
2
|
+
# Han Cai, Junyan Li, Muyan Hu, Chuang Gan, Song Han
|
|
3
|
+
# International Conference on Computer Vision (ICCV), 2023
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
__all__ = ["rand_bbox"]
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def rand_bbox(
|
|
11
|
+
h: int,
|
|
12
|
+
w: int,
|
|
13
|
+
lam: float,
|
|
14
|
+
rand_func: callable = np.random.uniform,
|
|
15
|
+
) -> tuple[int, int, int, int]:
|
|
16
|
+
"""randomly sample bbox, used in cutmix"""
|
|
17
|
+
cut_rat = np.sqrt(1.0 - lam)
|
|
18
|
+
cut_w = w * cut_rat
|
|
19
|
+
cut_h = h * cut_rat
|
|
20
|
+
|
|
21
|
+
# uniform
|
|
22
|
+
cx = rand_func(0, w)
|
|
23
|
+
cy = rand_func(0, h)
|
|
24
|
+
|
|
25
|
+
bbx1 = int(np.clip(cx - cut_w / 2, 0, w))
|
|
26
|
+
bby1 = int(np.clip(cy - cut_h / 2, 0, h))
|
|
27
|
+
bbx2 = int(np.clip(cx + cut_w / 2, 0, w))
|
|
28
|
+
bby2 = int(np.clip(cy + cut_h / 2, 0, h))
|
|
29
|
+
|
|
30
|
+
return bbx1, bby1, bbx2, bby2
|