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.
Files changed (145) hide show
  1. segment_everything/__init__.py +5 -0
  2. segment_everything/augmentation/albumentations_helper.py +0 -0
  3. segment_everything/detect_and_segment.py +131 -0
  4. segment_everything/napari_helper.py +15 -0
  5. segment_everything/prompt_generator.py +188 -0
  6. segment_everything/py.typed +5 -0
  7. segment_everything/stacked_label_dataset.py +113 -0
  8. segment_everything/stacked_labels.py +428 -0
  9. segment_everything/vendored/PromptGuidedDecoder/Prompt_guided_Mask_Decoder.pt +0 -0
  10. segment_everything/vendored/__init__.py +5 -0
  11. segment_everything/vendored/dice.py +158 -0
  12. segment_everything/vendored/efficientvit/__init__.py +0 -0
  13. segment_everything/vendored/efficientvit/apps/__init__.py +0 -0
  14. segment_everything/vendored/efficientvit/apps/data_provider/__init__.py +7 -0
  15. segment_everything/vendored/efficientvit/apps/data_provider/augment/__init__.py +6 -0
  16. segment_everything/vendored/efficientvit/apps/data_provider/augment/bbox.py +30 -0
  17. segment_everything/vendored/efficientvit/apps/data_provider/augment/color_aug.py +78 -0
  18. segment_everything/vendored/efficientvit/apps/data_provider/base.py +254 -0
  19. segment_everything/vendored/efficientvit/apps/data_provider/random_resolution/__init__.py +6 -0
  20. segment_everything/vendored/efficientvit/apps/data_provider/random_resolution/_data_loader.py +1538 -0
  21. segment_everything/vendored/efficientvit/apps/data_provider/random_resolution/_data_worker.py +357 -0
  22. segment_everything/vendored/efficientvit/apps/data_provider/random_resolution/controller.py +100 -0
  23. segment_everything/vendored/efficientvit/apps/setup.py +150 -0
  24. segment_everything/vendored/efficientvit/apps/trainer/__init__.py +6 -0
  25. segment_everything/vendored/efficientvit/apps/trainer/base.py +318 -0
  26. segment_everything/vendored/efficientvit/apps/trainer/run_config.py +129 -0
  27. segment_everything/vendored/efficientvit/apps/utils/__init__.py +12 -0
  28. segment_everything/vendored/efficientvit/apps/utils/dist.py +32 -0
  29. segment_everything/vendored/efficientvit/apps/utils/ema.py +52 -0
  30. segment_everything/vendored/efficientvit/apps/utils/export.py +45 -0
  31. segment_everything/vendored/efficientvit/apps/utils/init.py +66 -0
  32. segment_everything/vendored/efficientvit/apps/utils/lr.py +52 -0
  33. segment_everything/vendored/efficientvit/apps/utils/metric.py +43 -0
  34. segment_everything/vendored/efficientvit/apps/utils/misc.py +101 -0
  35. segment_everything/vendored/efficientvit/apps/utils/opt.py +28 -0
  36. segment_everything/vendored/efficientvit/cls_model_zoo.py +79 -0
  37. segment_everything/vendored/efficientvit/clscore/__init__.py +0 -0
  38. segment_everything/vendored/efficientvit/clscore/data_provider/__init__.py +5 -0
  39. segment_everything/vendored/efficientvit/clscore/data_provider/imagenet.py +142 -0
  40. segment_everything/vendored/efficientvit/clscore/trainer/__init__.py +6 -0
  41. segment_everything/vendored/efficientvit/clscore/trainer/cls_run_config.py +18 -0
  42. segment_everything/vendored/efficientvit/clscore/trainer/cls_trainer.py +265 -0
  43. segment_everything/vendored/efficientvit/clscore/trainer/utils/__init__.py +7 -0
  44. segment_everything/vendored/efficientvit/clscore/trainer/utils/label_smooth.py +18 -0
  45. segment_everything/vendored/efficientvit/clscore/trainer/utils/metric.py +23 -0
  46. segment_everything/vendored/efficientvit/clscore/trainer/utils/mixup.py +67 -0
  47. segment_everything/vendored/efficientvit/models/__init__.py +0 -0
  48. segment_everything/vendored/efficientvit/models/efficientvit/__init__.py +8 -0
  49. segment_everything/vendored/efficientvit/models/efficientvit/backbone.py +380 -0
  50. segment_everything/vendored/efficientvit/models/efficientvit/cls.py +188 -0
  51. segment_everything/vendored/efficientvit/models/efficientvit/sam.py +181 -0
  52. segment_everything/vendored/efficientvit/models/efficientvit/seg.py +373 -0
  53. segment_everything/vendored/efficientvit/models/nn/__init__.py +8 -0
  54. segment_everything/vendored/efficientvit/models/nn/act.py +30 -0
  55. segment_everything/vendored/efficientvit/models/nn/drop.py +104 -0
  56. segment_everything/vendored/efficientvit/models/nn/norm.py +164 -0
  57. segment_everything/vendored/efficientvit/models/nn/ops.py +597 -0
  58. segment_everything/vendored/efficientvit/models/utils/__init__.py +7 -0
  59. segment_everything/vendored/efficientvit/models/utils/list.py +53 -0
  60. segment_everything/vendored/efficientvit/models/utils/network.py +73 -0
  61. segment_everything/vendored/efficientvit/models/utils/random.py +65 -0
  62. segment_everything/vendored/efficientvit/sam_model_zoo.py +45 -0
  63. segment_everything/vendored/efficientvit/seg_model_zoo.py +70 -0
  64. segment_everything/vendored/get_object_aware.py +26 -0
  65. segment_everything/vendored/mobilesamv2/__init__.py +16 -0
  66. segment_everything/vendored/mobilesamv2/automatic_mask_generator.py +415 -0
  67. segment_everything/vendored/mobilesamv2/build_sam.py +246 -0
  68. segment_everything/vendored/mobilesamv2/modeling/__init__.py +11 -0
  69. segment_everything/vendored/mobilesamv2/modeling/common.py +43 -0
  70. segment_everything/vendored/mobilesamv2/modeling/image_encoder.py +394 -0
  71. segment_everything/vendored/mobilesamv2/modeling/mask_decoder.py +213 -0
  72. segment_everything/vendored/mobilesamv2/modeling/prompt_encoder.py +217 -0
  73. segment_everything/vendored/mobilesamv2/modeling/sam.py +203 -0
  74. segment_everything/vendored/mobilesamv2/modeling/transformer.py +240 -0
  75. segment_everything/vendored/mobilesamv2/predictor.py +384 -0
  76. segment_everything/vendored/mobilesamv2/utils/__init__.py +5 -0
  77. segment_everything/vendored/mobilesamv2/utils/amg.py +347 -0
  78. segment_everything/vendored/mobilesamv2/utils/onnx.py +144 -0
  79. segment_everything/vendored/mobilesamv2/utils/transforms.py +103 -0
  80. segment_everything/vendored/object_detection/__init__.py +0 -0
  81. segment_everything/vendored/object_detection/ultralytics/__init__.py +5 -0
  82. segment_everything/vendored/object_detection/ultralytics/nn/__init__.py +9 -0
  83. segment_everything/vendored/object_detection/ultralytics/nn/autobackend.py +658 -0
  84. segment_everything/vendored/object_detection/ultralytics/nn/autoshape.py +397 -0
  85. segment_everything/vendored/object_detection/ultralytics/nn/modules/__init__.py +110 -0
  86. segment_everything/vendored/object_detection/ultralytics/nn/modules/block.py +304 -0
  87. segment_everything/vendored/object_detection/ultralytics/nn/modules/conv.py +297 -0
  88. segment_everything/vendored/object_detection/ultralytics/nn/modules/head.py +468 -0
  89. segment_everything/vendored/object_detection/ultralytics/nn/modules/transformer.py +378 -0
  90. segment_everything/vendored/object_detection/ultralytics/nn/modules/utils.py +78 -0
  91. segment_everything/vendored/object_detection/ultralytics/nn/tasks.py +1049 -0
  92. segment_everything/vendored/object_detection/ultralytics/prompt_mobilesamv2/__init__.py +6 -0
  93. segment_everything/vendored/object_detection/ultralytics/prompt_mobilesamv2/model.py +104 -0
  94. segment_everything/vendored/object_detection/ultralytics/prompt_mobilesamv2/predict.py +95 -0
  95. segment_everything/vendored/object_detection/ultralytics/yolo/__init__.py +5 -0
  96. segment_everything/vendored/object_detection/ultralytics/yolo/cfg/__init__.py +588 -0
  97. segment_everything/vendored/object_detection/ultralytics/yolo/cfg/default.yaml +117 -0
  98. segment_everything/vendored/object_detection/ultralytics/yolo/data/__init__.py +9 -0
  99. segment_everything/vendored/object_detection/ultralytics/yolo/data/annotator.py +53 -0
  100. segment_everything/vendored/object_detection/ultralytics/yolo/data/augment.py +899 -0
  101. segment_everything/vendored/object_detection/ultralytics/yolo/data/base.py +286 -0
  102. segment_everything/vendored/object_detection/ultralytics/yolo/data/build.py +213 -0
  103. segment_everything/vendored/object_detection/ultralytics/yolo/data/converter.py +358 -0
  104. segment_everything/vendored/object_detection/ultralytics/yolo/data/dataloaders/__init__.py +0 -0
  105. segment_everything/vendored/object_detection/ultralytics/yolo/data/dataloaders/stream_loaders.py +459 -0
  106. segment_everything/vendored/object_detection/ultralytics/yolo/data/dataset.py +274 -0
  107. segment_everything/vendored/object_detection/ultralytics/yolo/data/dataset_wrappers.py +53 -0
  108. segment_everything/vendored/object_detection/ultralytics/yolo/data/scripts/download_weights.sh +18 -0
  109. segment_everything/vendored/object_detection/ultralytics/yolo/data/scripts/get_coco.sh +60 -0
  110. segment_everything/vendored/object_detection/ultralytics/yolo/data/scripts/get_coco128.sh +17 -0
  111. segment_everything/vendored/object_detection/ultralytics/yolo/data/scripts/get_imagenet.sh +51 -0
  112. segment_everything/vendored/object_detection/ultralytics/yolo/data/utils.py +716 -0
  113. segment_everything/vendored/object_detection/ultralytics/yolo/engine/__init__.py +0 -0
  114. segment_everything/vendored/object_detection/ultralytics/yolo/engine/exporter.py +1214 -0
  115. segment_everything/vendored/object_detection/ultralytics/yolo/engine/model.py +641 -0
  116. segment_everything/vendored/object_detection/ultralytics/yolo/engine/predictor.py +461 -0
  117. segment_everything/vendored/object_detection/ultralytics/yolo/engine/results.py +741 -0
  118. segment_everything/vendored/object_detection/ultralytics/yolo/utils/__init__.py +893 -0
  119. segment_everything/vendored/object_detection/ultralytics/yolo/utils/autobatch.py +108 -0
  120. segment_everything/vendored/object_detection/ultralytics/yolo/utils/callbacks/__init__.py +5 -0
  121. segment_everything/vendored/object_detection/ultralytics/yolo/utils/callbacks/base.py +212 -0
  122. segment_everything/vendored/object_detection/ultralytics/yolo/utils/checks.py +547 -0
  123. segment_everything/vendored/object_detection/ultralytics/yolo/utils/dist.py +67 -0
  124. segment_everything/vendored/object_detection/ultralytics/yolo/utils/downloads.py +353 -0
  125. segment_everything/vendored/object_detection/ultralytics/yolo/utils/errors.py +12 -0
  126. segment_everything/vendored/object_detection/ultralytics/yolo/utils/files.py +100 -0
  127. segment_everything/vendored/object_detection/ultralytics/yolo/utils/instance.py +391 -0
  128. segment_everything/vendored/object_detection/ultralytics/yolo/utils/loss.py +579 -0
  129. segment_everything/vendored/object_detection/ultralytics/yolo/utils/metrics.py +1189 -0
  130. segment_everything/vendored/object_detection/ultralytics/yolo/utils/ops.py +870 -0
  131. segment_everything/vendored/object_detection/ultralytics/yolo/utils/patches.py +45 -0
  132. segment_everything/vendored/object_detection/ultralytics/yolo/utils/plotting.py +767 -0
  133. segment_everything/vendored/object_detection/ultralytics/yolo/utils/tal.py +276 -0
  134. segment_everything/vendored/object_detection/ultralytics/yolo/utils/torch_utils.py +684 -0
  135. segment_everything/vendored/object_detection/ultralytics/yolo/utils/tuner.py +54 -0
  136. segment_everything/vendored/object_detection/ultralytics/yolo/v8/__init__.py +5 -0
  137. segment_everything/vendored/object_detection/ultralytics/yolo/v8/detect/__init__.py +5 -0
  138. segment_everything/vendored/object_detection/ultralytics/yolo/v8/detect/predict.py +69 -0
  139. segment_everything/vendored/tinyvit/__init__.py +2 -0
  140. segment_everything/vendored/tinyvit/tiny_vit.py +867 -0
  141. segment_everything/weights_helper.py +124 -0
  142. segment_everything-0.1.0.dist-info/METADATA +53 -0
  143. segment_everything-0.1.0.dist-info/RECORD +145 -0
  144. segment_everything-0.1.0.dist-info/WHEEL +4 -0
  145. 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,5 @@
1
+ """Utilities for rendering and training prompt-based and overlapping segmentation models (like SAM). """
2
+
3
+ __version__ = '0.1.0'
4
+ __author__ = "Brian Northan"
5
+ __email__ = "bnorthan@gmail.com"
@@ -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
@@ -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,6 @@
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 .bbox import *
6
+ from .color_aug 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