acvl-utils 0.2.2__tar.gz → 0.2.4__tar.gz
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.
- {acvl_utils-0.2.2 → acvl_utils-0.2.4}/PKG-INFO +7 -2
- {acvl_utils-0.2.2 → acvl_utils-0.2.4}/acvl_utils/cropping_and_padding/bounding_boxes.py +135 -17
- {acvl_utils-0.2.2 → acvl_utils-0.2.4}/acvl_utils/morphology/morphology_helper.py +28 -4
- {acvl_utils-0.2.2 → acvl_utils-0.2.4}/acvl_utils.egg-info/PKG-INFO +7 -2
- {acvl_utils-0.2.2 → acvl_utils-0.2.4}/acvl_utils.egg-info/SOURCES.txt +1 -0
- {acvl_utils-0.2.2 → acvl_utils-0.2.4}/setup.py +1 -1
- {acvl_utils-0.2.2 → acvl_utils-0.2.4}/tests/test_crop_and_pad.py +20 -7
- acvl_utils-0.2.4/tests/test_pad_bbox.py +46 -0
- {acvl_utils-0.2.2 → acvl_utils-0.2.4}/acvl_utils/__init__.py +0 -0
- {acvl_utils-0.2.2 → acvl_utils-0.2.4}/acvl_utils/array_manipulation/__init__.py +0 -0
- {acvl_utils-0.2.2 → acvl_utils-0.2.4}/acvl_utils/array_manipulation/resampling.py +0 -0
- {acvl_utils-0.2.2 → acvl_utils-0.2.4}/acvl_utils/array_manipulation/slicer.py +0 -0
- {acvl_utils-0.2.2 → acvl_utils-0.2.4}/acvl_utils/cropping_and_padding/__init__.py +0 -0
- {acvl_utils-0.2.2 → acvl_utils-0.2.4}/acvl_utils/cropping_and_padding/padding.py +0 -0
- {acvl_utils-0.2.2 → acvl_utils-0.2.4}/acvl_utils/instance_segmentation/__init__.py +0 -0
- {acvl_utils-0.2.2 → acvl_utils-0.2.4}/acvl_utils/instance_segmentation/instance_as_semantic_seg.py +0 -0
- {acvl_utils-0.2.2 → acvl_utils-0.2.4}/acvl_utils/instance_segmentation/instance_matching.py +0 -0
- {acvl_utils-0.2.2 → acvl_utils-0.2.4}/acvl_utils/miscellaneous/__init__.py +0 -0
- {acvl_utils-0.2.2 → acvl_utils-0.2.4}/acvl_utils/miscellaneous/ptqdm.py +0 -0
- {acvl_utils-0.2.2 → acvl_utils-0.2.4}/acvl_utils/miscellaneous/test_all_images_same.py +0 -0
- {acvl_utils-0.2.2 → acvl_utils-0.2.4}/acvl_utils/morphology/__init__.py +0 -0
- {acvl_utils-0.2.2 → acvl_utils-0.2.4}/acvl_utils/morphology/gpu_binary_morphology.py +0 -0
- {acvl_utils-0.2.2 → acvl_utils-0.2.4}/acvl_utils/morphology/slice_interpolation.py +0 -0
- {acvl_utils-0.2.2 → acvl_utils-0.2.4}/acvl_utils.egg-info/dependency_links.txt +0 -0
- {acvl_utils-0.2.2 → acvl_utils-0.2.4}/acvl_utils.egg-info/requires.txt +0 -0
- {acvl_utils-0.2.2 → acvl_utils-0.2.4}/acvl_utils.egg-info/top_level.txt +0 -0
- {acvl_utils-0.2.2 → acvl_utils-0.2.4}/setup.cfg +0 -0
- {acvl_utils-0.2.2 → acvl_utils-0.2.4}/tests/test_component_removal.py +0 -0
- {acvl_utils-0.2.2 → acvl_utils-0.2.4}/tests/test_insert_crop.py +0 -0
- {acvl_utils-0.2.2 → acvl_utils-0.2.4}/tests/test_instance_matching.py +0 -0
- {acvl_utils-0.2.2 → acvl_utils-0.2.4}/tests/test_padding.py +0 -0
- {acvl_utils-0.2.2 → acvl_utils-0.2.4}/tests/test_ptqdm.py +0 -0
- {acvl_utils-0.2.2 → acvl_utils-0.2.4}/tests/test_slicer.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
2
|
Name: acvl_utils
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.4
|
|
4
4
|
Summary: Super cool utilities that we just love to use
|
|
5
5
|
Author: Applied Computer Vision Lab, Helmholtz Imaging & Division of Medical Image Computing, German Cancer Research Center
|
|
6
6
|
Author-email: f.isensee@dkfz.de
|
|
@@ -12,3 +12,8 @@ Requires-Dist: SimpleITK
|
|
|
12
12
|
Requires-Dist: scikit-image
|
|
13
13
|
Requires-Dist: connected-components-3d
|
|
14
14
|
Requires-Dist: blosc2>=3.0.0b4
|
|
15
|
+
Dynamic: author
|
|
16
|
+
Dynamic: author-email
|
|
17
|
+
Dynamic: license
|
|
18
|
+
Dynamic: requires-dist
|
|
19
|
+
Dynamic: summary
|
|
@@ -11,7 +11,57 @@ import torch.nn.functional as F
|
|
|
11
11
|
def pad_bbox(bounding_box: Union[List[List[int]], Tuple[Tuple[int, int]]], pad_amount: Union[int, List[int]],
|
|
12
12
|
array_shape: Tuple[int, ...] = None) -> List[List[int]]:
|
|
13
13
|
"""
|
|
14
|
+
Pads a bounding box by a specified amount and optionally ensures that it stays within the bounds of an array.
|
|
14
15
|
|
|
16
|
+
Parameters:
|
|
17
|
+
-----------
|
|
18
|
+
bounding_box : Union[List[List[int]], Tuple[Tuple[int, int]]]
|
|
19
|
+
The input bounding box, represented as a list or tuple of coordinate pairs. Each pair corresponds to
|
|
20
|
+
one dimension and should have the form `[start, end]`, where `start` is inclusive and `end` is exclusive.
|
|
21
|
+
|
|
22
|
+
pad_amount : Union[int, List[int]]
|
|
23
|
+
The amount of padding to apply to each dimension of the bounding box.
|
|
24
|
+
- If an integer is provided, the same padding is applied to all dimensions.
|
|
25
|
+
- If a list is provided, each element specifies the padding for the corresponding dimension.
|
|
26
|
+
|
|
27
|
+
array_shape : Tuple[int, ...], optional
|
|
28
|
+
The shape of the array to which the bounding box is applied.
|
|
29
|
+
If provided, the padded bounding box will be clipped to stay within these bounds.
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
--------
|
|
33
|
+
List[List[int]]
|
|
34
|
+
A new bounding box with the specified padding applied. Each dimension will be represented as a pair `[start, end]`.
|
|
35
|
+
|
|
36
|
+
Notes:
|
|
37
|
+
------
|
|
38
|
+
- The input `bounding_box` is converted to a list to ensure it can be modified without affecting the original data.
|
|
39
|
+
- The padding is applied symmetrically, reducing the `start` coordinate and increasing the `end` coordinate.
|
|
40
|
+
- If `array_shape` is provided, the padded bounding box will not exceed the valid indices of the array.
|
|
41
|
+
|
|
42
|
+
Examples:
|
|
43
|
+
---------
|
|
44
|
+
1. Padding a bounding box without array shape constraints:
|
|
45
|
+
```python
|
|
46
|
+
bbox = [[2, 10], [5, 15]]
|
|
47
|
+
padded_bbox = pad_bbox(bbox, pad_amount=2)
|
|
48
|
+
# Result: [[0, 12], [3, 17]]
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
2. Padding with array shape constraints:
|
|
52
|
+
```python
|
|
53
|
+
bbox = [[2, 10], [5, 15]]
|
|
54
|
+
array_shape = (12, 20)
|
|
55
|
+
padded_bbox = pad_bbox(bbox, pad_amount=2, array_shape=array_shape)
|
|
56
|
+
# Result: [[0, 12], [3, 17]]
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
3. Using dimension-specific padding:
|
|
60
|
+
```python
|
|
61
|
+
bbox = [[2, 10], [5, 15]]
|
|
62
|
+
padded_bbox = pad_bbox(bbox, pad_amount=[3, 1])
|
|
63
|
+
# Result: [[0, 13], [4, 16]]
|
|
64
|
+
```
|
|
15
65
|
"""
|
|
16
66
|
if isinstance(bounding_box, tuple):
|
|
17
67
|
# convert to list
|
|
@@ -41,10 +91,26 @@ def regionprops_bbox_to_proper_bbox(regionprops_bbox: Tuple[int, ...]) -> List[L
|
|
|
41
91
|
|
|
42
92
|
|
|
43
93
|
def bounding_box_to_slice(bounding_box: List[List[int]]):
|
|
94
|
+
"""
|
|
95
|
+
ALL bounding boxes in acvl_utils and nnU-Netv2 are half open interval [start, end)!
|
|
96
|
+
- Alignment with Python Slicing
|
|
97
|
+
- Ease of Subdivision
|
|
98
|
+
- Consistency in Multi-Dimensional Arrays
|
|
99
|
+
- Precedent in Computer Graphics
|
|
100
|
+
https://chatgpt.com/share/679203ec-3fbc-8013-a003-13a7adfb1e73
|
|
101
|
+
"""
|
|
44
102
|
return tuple([slice(*i) for i in bounding_box])
|
|
45
103
|
|
|
46
104
|
|
|
47
105
|
def crop_to_bbox(array: np.ndarray, bounding_box: List[List[int]]):
|
|
106
|
+
"""
|
|
107
|
+
ALL bounding boxes in acvl_utils and nnU-Netv2 are half open interval [start, end)!
|
|
108
|
+
- Alignment with Python Slicing
|
|
109
|
+
- Ease of Subdivision
|
|
110
|
+
- Consistency in Multi-Dimensional Arrays
|
|
111
|
+
- Precedent in Computer Graphics
|
|
112
|
+
https://chatgpt.com/share/679203ec-3fbc-8013-a003-13a7adfb1e73
|
|
113
|
+
"""
|
|
48
114
|
assert len(bounding_box) == len(array.shape), f"Dimensionality of bbox and array do not match. bbox has length " \
|
|
49
115
|
f"{len(bounding_box)} while array has dimension {len(array.shape)}"
|
|
50
116
|
slicer = bounding_box_to_slice(bounding_box)
|
|
@@ -53,10 +119,15 @@ def crop_to_bbox(array: np.ndarray, bounding_box: List[List[int]]):
|
|
|
53
119
|
|
|
54
120
|
def get_bbox_from_mask(mask: np.ndarray) -> List[List[int]]:
|
|
55
121
|
"""
|
|
122
|
+
ALL bounding boxes in acvl_utils and nnU-Netv2 are half open interval [start, end)!
|
|
123
|
+
- Alignment with Python Slicing
|
|
124
|
+
- Ease of Subdivision
|
|
125
|
+
- Consistency in Multi-Dimensional Arrays
|
|
126
|
+
- Precedent in Computer Graphics
|
|
127
|
+
https://chatgpt.com/share/679203ec-3fbc-8013-a003-13a7adfb1e73
|
|
128
|
+
|
|
56
129
|
this implementation uses less ram than the np.where one and is faster as well IF we expect the bounding box to
|
|
57
130
|
be close to the image size. If it's not it's likely slower!
|
|
58
|
-
|
|
59
|
-
bbox is returned so that you can just do slice(minzidx, maxzidx) to retrieve the object of interest with nothing cut off
|
|
60
131
|
|
|
61
132
|
:param mask:
|
|
62
133
|
:param outside_value:
|
|
@@ -96,7 +167,22 @@ def get_bbox_from_mask(mask: np.ndarray) -> List[List[int]]:
|
|
|
96
167
|
return [[minzidx, maxzidx], [minxidx, maxxidx], [minyidx, maxyidx]]
|
|
97
168
|
|
|
98
169
|
|
|
170
|
+
def int_bbox(bbox):
|
|
171
|
+
bbox2 = [[int(i) for i in row] for row in bbox] # Convert all values to int
|
|
172
|
+
if bbox2 != bbox: # Directly compare the original and casted versions
|
|
173
|
+
raise RuntimeError(f"Invalid bbox encountered. Cannot be safely cast to int: {bbox}. Casting result: {bbox2}")
|
|
174
|
+
return bbox2
|
|
175
|
+
|
|
176
|
+
|
|
99
177
|
def get_bbox_from_mask_npwhere(mask: np.ndarray) -> List[List[int]]:
|
|
178
|
+
"""
|
|
179
|
+
ALL bounding boxes in acvl_utils and nnU-Netv2 are half open interval [start, end)!
|
|
180
|
+
- Alignment with Python Slicing
|
|
181
|
+
- Ease of Subdivision
|
|
182
|
+
- Consistency in Multi-Dimensional Arrays
|
|
183
|
+
- Precedent in Computer Graphics
|
|
184
|
+
https://chatgpt.com/share/679203ec-3fbc-8013-a003-13a7adfb1e73
|
|
185
|
+
"""
|
|
100
186
|
where = np.array(np.where(mask))
|
|
101
187
|
mins = np.min(where, 1)
|
|
102
188
|
maxs = np.max(where, 1) + 1
|
|
@@ -106,21 +192,37 @@ def get_bbox_from_mask_npwhere(mask: np.ndarray) -> List[List[int]]:
|
|
|
106
192
|
def crop_and_pad_nd(
|
|
107
193
|
image: Union[torch.Tensor, np.ndarray, blosc2.ndarray.NDArray],
|
|
108
194
|
bbox: List[List[int]],
|
|
109
|
-
pad_value
|
|
195
|
+
pad_value=0,
|
|
196
|
+
pad_mode: str = 'constant'
|
|
110
197
|
) -> Union[torch.Tensor, np.ndarray]:
|
|
111
198
|
"""
|
|
112
|
-
Crops a bounding box directly specified by bbox,
|
|
199
|
+
Crops a bounding box directly specified by bbox, adhering to the half-open interval [start, end).
|
|
113
200
|
If the bounding box extends beyond the image boundaries, the cropped area is padded
|
|
114
201
|
to maintain the desired size. Initial dimensions not included in bbox remain unaffected.
|
|
115
202
|
|
|
203
|
+
ALL bounding boxes in acvl_utils and nnU-Netv2 are half open interval [start, end)!
|
|
204
|
+
- Alignment with Python Slicing
|
|
205
|
+
- Ease of Subdivision
|
|
206
|
+
- Consistency in Multi-Dimensional Arrays
|
|
207
|
+
- Precedent in Computer Graphics
|
|
208
|
+
https://chatgpt.com/share/679203ec-3fbc-8013-a003-13a7adfb1e73
|
|
209
|
+
|
|
116
210
|
Parameters:
|
|
117
|
-
- image: N-dimensional torch.Tensor or
|
|
211
|
+
- image: N-dimensional torch.Tensor, np.ndarray, or blosc2.ndarray.NDArray representing the image.
|
|
118
212
|
- bbox: List of [[dim_min, dim_max], ...] defining the bounding box for the last dimensions.
|
|
213
|
+
Each dimension follows the half-open interval [start, end).
|
|
214
|
+
- pad_value: Value used for padding when bbox extends beyond image boundaries.
|
|
215
|
+
- pad_mode: Padding mode, one of 'constant', 'reflect', or 'replicate' (alias for 'edge').
|
|
119
216
|
|
|
120
217
|
Returns:
|
|
121
218
|
- Cropped and padded patch of the requested bounding box size, as the same type as `image`.
|
|
122
219
|
"""
|
|
123
220
|
|
|
221
|
+
assert pad_mode in ['constant', 'reflect', 'replicate', 'edge'], "Unsupported pad_mode."
|
|
222
|
+
|
|
223
|
+
# make sure bounding boxes are int and not uint. Otherwise we may get underflow
|
|
224
|
+
bbox = int_bbox(bbox)
|
|
225
|
+
|
|
124
226
|
# Determine the number of dimensions to crop based on bbox
|
|
125
227
|
crop_dims = len(bbox)
|
|
126
228
|
img_shape = image.shape
|
|
@@ -130,7 +232,7 @@ def crop_and_pad_nd(
|
|
|
130
232
|
slices = []
|
|
131
233
|
padding = []
|
|
132
234
|
output_shape = list(img_shape[:num_dims - crop_dims]) # Initial dimensions remain as in the original image
|
|
133
|
-
target_shape = output_shape + [max_val - min_val for min_val, max_val in bbox]
|
|
235
|
+
target_shape = output_shape + [max_val - min_val for min_val, max_val in bbox] # Half-open interval
|
|
134
236
|
|
|
135
237
|
# Iterate through dimensions, applying bbox to the last `crop_dims` dimensions
|
|
136
238
|
for i in range(num_dims):
|
|
@@ -138,13 +240,13 @@ def crop_and_pad_nd(
|
|
|
138
240
|
# For initial dimensions not covered by bbox, include the entire dimension
|
|
139
241
|
slices.append(slice(None))
|
|
140
242
|
padding.append([0, 0])
|
|
141
|
-
output_shape.append(img_shape[i])
|
|
243
|
+
output_shape.append(img_shape[i])
|
|
142
244
|
else:
|
|
143
245
|
# For dimensions specified in bbox, directly use the min and max bounds
|
|
144
246
|
dim_idx = i - (num_dims - crop_dims) # Index within bbox
|
|
145
247
|
|
|
146
248
|
min_val = bbox[dim_idx][0]
|
|
147
|
-
max_val = bbox[dim_idx][1]
|
|
249
|
+
max_val = bbox[dim_idx][1] # This is exclusive by definition
|
|
148
250
|
|
|
149
251
|
# Check if the bounding box is completely outside the image bounds
|
|
150
252
|
if max_val <= 0 or min_val >= img_shape[i]:
|
|
@@ -154,9 +256,9 @@ def crop_and_pad_nd(
|
|
|
154
256
|
elif isinstance(image, (np.ndarray, blosc2.ndarray.NDArray)):
|
|
155
257
|
return np.zeros(target_shape, dtype=image.dtype)
|
|
156
258
|
|
|
157
|
-
# Calculate valid cropping ranges within image bounds
|
|
259
|
+
# Calculate valid cropping ranges within image bounds (half-open interval)
|
|
158
260
|
valid_min = max(min_val, 0)
|
|
159
|
-
valid_max = min(max_val, img_shape[i]) #
|
|
261
|
+
valid_max = min(max_val, img_shape[i]) # Exclusive upper bound
|
|
160
262
|
slices.append(slice(valid_min, valid_max))
|
|
161
263
|
|
|
162
264
|
# Calculate padding needed for this dimension
|
|
@@ -171,14 +273,28 @@ def crop_and_pad_nd(
|
|
|
171
273
|
cropped = image[tuple(slices)]
|
|
172
274
|
|
|
173
275
|
# Apply padding to the cropped patch
|
|
174
|
-
if
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
276
|
+
if np.any(padding):
|
|
277
|
+
if isinstance(image, torch.Tensor):
|
|
278
|
+
if pad_mode == 'edge':
|
|
279
|
+
pad_mode = 'replicate'
|
|
280
|
+
cropped = cropped[None, None] # Adjust shape for PyTorch padding requirements
|
|
281
|
+
flattened_padding = [p for sublist in reversed(padding) for p in sublist] # Flatten in reverse order for PyTorch
|
|
282
|
+
padded_cropped = F.pad(cropped, flattened_padding, mode=pad_mode, value=pad_value)
|
|
283
|
+
if pad_mode == 'replicate':
|
|
284
|
+
padded_cropped = padded_cropped[0, 0]
|
|
285
|
+
elif isinstance(image, (np.ndarray, blosc2.ndarray.NDArray)):
|
|
286
|
+
if pad_mode == 'replicate':
|
|
287
|
+
pad_mode = 'edge'
|
|
288
|
+
if pad_mode == 'edge' or pad_mode == 'reflect':
|
|
289
|
+
kwargs = {}
|
|
290
|
+
else:
|
|
291
|
+
kwargs = {'constant_values': pad_value}
|
|
292
|
+
pad_width = [(p[0], p[1]) for p in padding]
|
|
293
|
+
padded_cropped = np.pad(cropped, pad_width=pad_width, mode=pad_mode, **kwargs)
|
|
294
|
+
else:
|
|
295
|
+
raise ValueError(f'Unsupported image type {type(image)}')
|
|
180
296
|
else:
|
|
181
|
-
|
|
297
|
+
padded_cropped = cropped
|
|
182
298
|
|
|
183
299
|
return padded_cropped
|
|
184
300
|
|
|
@@ -201,6 +317,8 @@ def insert_crop_into_image(
|
|
|
201
317
|
Returns:
|
|
202
318
|
- image: The original image with the crop reinserted at the specified location (modified in-place).
|
|
203
319
|
"""
|
|
320
|
+
# make sure bounding boxes are int and not uint. Otherwise we may get underflow
|
|
321
|
+
bbox = int_bbox(bbox)
|
|
204
322
|
|
|
205
323
|
# Ensure that bbox only applies to the last len(bbox) dimensions of crop and image
|
|
206
324
|
num_dims = len(image.shape)
|
|
@@ -2,20 +2,24 @@ from typing import Union, Tuple, List, Callable, Dict
|
|
|
2
2
|
|
|
3
3
|
import numpy as np
|
|
4
4
|
from skimage.measure import label
|
|
5
|
-
from skimage.morphology import ball
|
|
5
|
+
from skimage.morphology import ball, disk
|
|
6
6
|
from skimage.transform import resize
|
|
7
7
|
import cc3d
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
def generate_ball(radius: Union[Tuple, List], spacing: Union[Tuple, List] =
|
|
10
|
+
def generate_ball(radius: Union[Tuple, List], spacing: Union[Tuple, List] = None, dtype=np.uint8) -> np.ndarray:
|
|
11
11
|
"""
|
|
12
12
|
Returns a ball/ellipsoid corresponding to the specified size (radius = list/tuple of len 3 with one radius per axis)
|
|
13
13
|
If you use spacing, both radius and spacing will be interpreted relative to each other, so a radius of 10 with a
|
|
14
14
|
spacing of 5 will result in a ball with radius 2 pixels.
|
|
15
15
|
"""
|
|
16
|
-
|
|
16
|
+
if spacing is None:
|
|
17
|
+
spacing = [1] * len(radius)
|
|
18
|
+
radius_in_voxels = np.array([round(i) for i in radius]) / np.array(spacing)
|
|
19
|
+
if all([i == radius_in_voxels[0] for i in radius_in_voxels[1:]]):
|
|
20
|
+
return ball(radius_in_voxels[0]) if len(radius) == 3 else disk(radius_in_voxels[0])
|
|
17
21
|
n = 2 * radius_in_voxels + 1
|
|
18
|
-
ball_iso = ball(max(n) * 2, dtype=np.float64)
|
|
22
|
+
ball_iso = ball(max(n) * 2, dtype=np.float64) if len(radius) == 3 else disk(max(n) * 2, dtype=np.float64)
|
|
19
23
|
ball_resampled = resize(ball_iso, n, 1, 'constant', 0, clip=True, anti_aliasing=False, preserve_range=True)
|
|
20
24
|
ball_resampled[ball_resampled > 0.5] = 1
|
|
21
25
|
ball_resampled[ball_resampled <= 0.5] = 0
|
|
@@ -59,6 +63,14 @@ def remove_all_but_largest_component(binary_image: np.ndarray, connectivity: int
|
|
|
59
63
|
return generic_filter_components(binary_image, filter_fn, connectivity)
|
|
60
64
|
|
|
61
65
|
|
|
66
|
+
def remove_all_but_largest_component_cc3d(binary_image: np.ndarray, connectivity: int = None) -> np.ndarray:
|
|
67
|
+
"""
|
|
68
|
+
Removes all but the largest component in binary_image. Replaces pixels that don't belong to it with background_label
|
|
69
|
+
"""
|
|
70
|
+
filter_fn = lambda x, y: [i for i, j in zip(x, y) if j == max(y)]
|
|
71
|
+
return generic_filter_components_cc3d(binary_image, filter_fn, connectivity)
|
|
72
|
+
|
|
73
|
+
|
|
62
74
|
def generic_filter_components(binary_image: np.ndarray, filter_fn: Callable[[List[int], List[int]], List[int]],
|
|
63
75
|
connectivity: int = None):
|
|
64
76
|
"""
|
|
@@ -74,6 +86,17 @@ def generic_filter_components(binary_image: np.ndarray, filter_fn: Callable[[Lis
|
|
|
74
86
|
return np.isin(labeled_image.ravel(), keep).reshape(labeled_image.shape)
|
|
75
87
|
|
|
76
88
|
|
|
89
|
+
def generic_filter_components_cc3d(binary_image: np.ndarray, filter_fn: Callable[[List[int], List[int]], List[int]],
|
|
90
|
+
connectivity: int = None):
|
|
91
|
+
"""
|
|
92
|
+
"""
|
|
93
|
+
labeled_image, component_sizes = cc3d_label_with_component_sizes(binary_image, connectivity)
|
|
94
|
+
component_ids = list(component_sizes.keys())
|
|
95
|
+
component_sizes = list(component_sizes.values())
|
|
96
|
+
keep = filter_fn(component_ids, component_sizes)
|
|
97
|
+
return np.isin(labeled_image.ravel(), keep).reshape(labeled_image.shape)
|
|
98
|
+
|
|
99
|
+
|
|
77
100
|
def remove_components(binary_image: np.ndarray, threshold_size_in_pixels: int, threshold_type: str = 'min',
|
|
78
101
|
connectivity=None, verbose: bool = False):
|
|
79
102
|
"""
|
|
@@ -165,6 +188,7 @@ def remove_components_cc3d(binary_image: np.ndarray, threshold_size_in_pixels: i
|
|
|
165
188
|
return keep
|
|
166
189
|
|
|
167
190
|
|
|
191
|
+
|
|
168
192
|
if __name__ == '__main__':
|
|
169
193
|
print(generate_ball((10, 10, 10), (5, 5, 5)).shape)
|
|
170
194
|
print(generate_ball((10, 5, 15), (1, 1, 1)).shape)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
2
|
Name: acvl_utils
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.4
|
|
4
4
|
Summary: Super cool utilities that we just love to use
|
|
5
5
|
Author: Applied Computer Vision Lab, Helmholtz Imaging & Division of Medical Image Computing, German Cancer Research Center
|
|
6
6
|
Author-email: f.isensee@dkfz.de
|
|
@@ -12,3 +12,8 @@ Requires-Dist: SimpleITK
|
|
|
12
12
|
Requires-Dist: scikit-image
|
|
13
13
|
Requires-Dist: connected-components-3d
|
|
14
14
|
Requires-Dist: blosc2>=3.0.0b4
|
|
15
|
+
Dynamic: author
|
|
16
|
+
Dynamic: author-email
|
|
17
|
+
Dynamic: license
|
|
18
|
+
Dynamic: requires-dist
|
|
19
|
+
Dynamic: summary
|
|
@@ -2,7 +2,7 @@ from setuptools import setup, find_namespace_packages
|
|
|
2
2
|
|
|
3
3
|
setup(name='acvl_utils',
|
|
4
4
|
packages=find_namespace_packages(include=["acvl_utils", "acvl_utils.*"]),
|
|
5
|
-
version='0.2.
|
|
5
|
+
version='0.2.4',
|
|
6
6
|
description='Super cool utilities that we just love to use',
|
|
7
7
|
# url='',
|
|
8
8
|
author='Applied Computer Vision Lab, Helmholtz Imaging & Division of Medical Image Computing, German Cancer Research Center',
|
|
@@ -264,7 +264,6 @@ class TestCropAndPadND(unittest.TestCase):
|
|
|
264
264
|
self.assertEqual(cropped_patch.shape, (5, 10, 10, 5))
|
|
265
265
|
np.testing.assert_array_equal(cropped_patch, self.image_np[:, :, :, 3:8])
|
|
266
266
|
|
|
267
|
-
|
|
268
267
|
def test_two_dimensions_crop_np(self):
|
|
269
268
|
"""Test cropping the last two dimensions of a 4D np.ndarray."""
|
|
270
269
|
bbox = [[3, 8], [1, 7]]
|
|
@@ -274,7 +273,6 @@ class TestCropAndPadND(unittest.TestCase):
|
|
|
274
273
|
self.assertEqual(cropped_patch.shape, (5, 10, 5, 6))
|
|
275
274
|
np.testing.assert_array_equal(cropped_patch, self.image_np[:, :, 3:8, 1:7])
|
|
276
275
|
|
|
277
|
-
|
|
278
276
|
def test_three_dimensions_crop_np(self):
|
|
279
277
|
"""Test cropping the last three dimensions of a 4D np.ndarray."""
|
|
280
278
|
bbox = [[1, 6], [2, 9], [3, 8]]
|
|
@@ -284,7 +282,6 @@ class TestCropAndPadND(unittest.TestCase):
|
|
|
284
282
|
self.assertEqual(cropped_patch.shape, (5, 5, 7, 5))
|
|
285
283
|
np.testing.assert_array_equal(cropped_patch, self.image_np[:, 1:6, 2:9, 3:8])
|
|
286
284
|
|
|
287
|
-
|
|
288
285
|
def test_all_dimensions_crop_np(self):
|
|
289
286
|
"""Test cropping all dimensions of a 4D np.ndarray."""
|
|
290
287
|
bbox = [[1, 4], [2, 7], [0, 6], [4, 9]]
|
|
@@ -294,7 +291,6 @@ class TestCropAndPadND(unittest.TestCase):
|
|
|
294
291
|
self.assertEqual(cropped_patch.shape, (3, 5, 6, 5))
|
|
295
292
|
np.testing.assert_array_equal(cropped_patch, self.image_np[1:4, 2:7, 0:6, 4:9])
|
|
296
293
|
|
|
297
|
-
|
|
298
294
|
def test_single_dimension_crop_torch(self):
|
|
299
295
|
"""Test cropping only the last dimension of a 4D torch.Tensor."""
|
|
300
296
|
bbox = [[3, 8]]
|
|
@@ -304,7 +300,6 @@ class TestCropAndPadND(unittest.TestCase):
|
|
|
304
300
|
self.assertEqual(cropped_patch.shape, (5, 10, 10, 5))
|
|
305
301
|
torch.testing.assert_close(cropped_patch, self.image_torch[:, :, :, 3:8])
|
|
306
302
|
|
|
307
|
-
|
|
308
303
|
def test_two_dimensions_crop_torch(self):
|
|
309
304
|
"""Test cropping the last two dimensions of a 4D torch.Tensor."""
|
|
310
305
|
bbox = [[3, 8], [1, 7]]
|
|
@@ -314,7 +309,6 @@ class TestCropAndPadND(unittest.TestCase):
|
|
|
314
309
|
self.assertEqual(cropped_patch.shape, (5, 10, 5, 6))
|
|
315
310
|
torch.testing.assert_close(cropped_patch, self.image_torch[:, :, 3:8, 1:7])
|
|
316
311
|
|
|
317
|
-
|
|
318
312
|
def test_three_dimensions_crop_torch(self):
|
|
319
313
|
"""Test cropping the last three dimensions of a 4D torch.Tensor."""
|
|
320
314
|
bbox = [[1, 6], [2, 9], [3, 8]]
|
|
@@ -324,7 +318,6 @@ class TestCropAndPadND(unittest.TestCase):
|
|
|
324
318
|
self.assertEqual(cropped_patch.shape, (5, 5, 7, 5))
|
|
325
319
|
torch.testing.assert_close(cropped_patch, self.image_torch[:, 1:6, 2:9, 3:8])
|
|
326
320
|
|
|
327
|
-
|
|
328
321
|
def test_all_dimensions_crop_torch(self):
|
|
329
322
|
"""Test cropping all dimensions of a 4D torch.Tensor."""
|
|
330
323
|
bbox = [[1, 4], [2, 7], [0, 6], [4, 9]]
|
|
@@ -334,6 +327,26 @@ class TestCropAndPadND(unittest.TestCase):
|
|
|
334
327
|
self.assertEqual(cropped_patch.shape, (3, 5, 6, 5))
|
|
335
328
|
torch.testing.assert_close(cropped_patch, self.image_torch[1:4, 2:7, 0:6, 4:9])
|
|
336
329
|
|
|
330
|
+
def test_pad_all_dims(self):
|
|
331
|
+
seg = np.ones((128, 128))
|
|
332
|
+
bbox = [[-5, 133], [-5, 133]]
|
|
333
|
+
out = crop_and_pad_nd(seg, bbox)
|
|
334
|
+
self.assertEqual(out.shape, (138, 138))
|
|
335
|
+
bbox_reverse = [[5, 133], [5, 133]]
|
|
336
|
+
out2 = crop_and_pad_nd(out, bbox_reverse)
|
|
337
|
+
self.assertEqual(out2.shape, (128, 128))
|
|
338
|
+
self.assertTrue(np.all(out2==1))
|
|
339
|
+
|
|
340
|
+
def test_pad_all_dims_torch(self):
|
|
341
|
+
seg = torch.ones((128, 128))
|
|
342
|
+
bbox = [[-5, 133], [-5, 133]]
|
|
343
|
+
out = crop_and_pad_nd(seg, bbox)
|
|
344
|
+
self.assertEqual(out.shape, (138, 138))
|
|
345
|
+
bbox_reverse = [[5, 133], [5, 133]]
|
|
346
|
+
out2 = crop_and_pad_nd(out, bbox_reverse)
|
|
347
|
+
self.assertEqual(out2.shape, (128, 128))
|
|
348
|
+
self.assertTrue(torch.all(out2==1))
|
|
349
|
+
|
|
337
350
|
|
|
338
351
|
if __name__ == "__main__":
|
|
339
352
|
unittest.main()
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
|
|
3
|
+
from acvl_utils.cropping_and_padding.bounding_boxes import pad_bbox
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class TestPadBBox(unittest.TestCase):
|
|
7
|
+
|
|
8
|
+
def test_no_array_shape(self):
|
|
9
|
+
bbox = [[2, 10], [5, 15]]
|
|
10
|
+
pad_amount = 2
|
|
11
|
+
expected = [[0, 12], [3, 17]]
|
|
12
|
+
result = pad_bbox(bbox, pad_amount)
|
|
13
|
+
self.assertEqual(result, expected)
|
|
14
|
+
|
|
15
|
+
def test_with_array_shape(self):
|
|
16
|
+
bbox = [[2, 10], [5, 15]]
|
|
17
|
+
pad_amount = 2
|
|
18
|
+
array_shape = (12, 20)
|
|
19
|
+
expected = [[0, 12], [3, 17]]
|
|
20
|
+
result = pad_bbox(bbox, pad_amount, array_shape)
|
|
21
|
+
self.assertEqual(result, expected)
|
|
22
|
+
|
|
23
|
+
def test_dimension_specific_padding(self):
|
|
24
|
+
bbox = [[2, 10], [5, 15]]
|
|
25
|
+
pad_amount = [3, 1]
|
|
26
|
+
expected = [[0, 13], [4, 16]]
|
|
27
|
+
result = pad_bbox(bbox, pad_amount)
|
|
28
|
+
self.assertEqual(result, expected)
|
|
29
|
+
|
|
30
|
+
def test_clipping_with_array_shape(self):
|
|
31
|
+
bbox = [[2, 10], [5, 15]]
|
|
32
|
+
pad_amount = 5
|
|
33
|
+
array_shape = (12, 18)
|
|
34
|
+
expected = [[0, 12], [0, 18]]
|
|
35
|
+
result = pad_bbox(bbox, pad_amount, array_shape)
|
|
36
|
+
self.assertEqual(result, expected)
|
|
37
|
+
|
|
38
|
+
def test_no_padding(self):
|
|
39
|
+
bbox = [[2, 10], [5, 15]]
|
|
40
|
+
pad_amount = 0
|
|
41
|
+
expected = [[2, 10], [5, 15]]
|
|
42
|
+
result = pad_bbox(bbox, pad_amount)
|
|
43
|
+
self.assertEqual(result, expected)
|
|
44
|
+
|
|
45
|
+
if __name__ == "__main__":
|
|
46
|
+
unittest.main()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{acvl_utils-0.2.2 → acvl_utils-0.2.4}/acvl_utils/instance_segmentation/instance_as_semantic_seg.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|