acvl-utils 0.2.2__tar.gz → 0.2.3__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.
Files changed (33) hide show
  1. {acvl_utils-0.2.2 → acvl_utils-0.2.3}/PKG-INFO +7 -2
  2. {acvl_utils-0.2.2 → acvl_utils-0.2.3}/acvl_utils/cropping_and_padding/bounding_boxes.py +115 -12
  3. {acvl_utils-0.2.2 → acvl_utils-0.2.3}/acvl_utils.egg-info/PKG-INFO +7 -2
  4. {acvl_utils-0.2.2 → acvl_utils-0.2.3}/acvl_utils.egg-info/SOURCES.txt +1 -0
  5. {acvl_utils-0.2.2 → acvl_utils-0.2.3}/setup.py +1 -1
  6. {acvl_utils-0.2.2 → acvl_utils-0.2.3}/tests/test_crop_and_pad.py +20 -7
  7. acvl_utils-0.2.3/tests/test_pad_bbox.py +46 -0
  8. {acvl_utils-0.2.2 → acvl_utils-0.2.3}/acvl_utils/__init__.py +0 -0
  9. {acvl_utils-0.2.2 → acvl_utils-0.2.3}/acvl_utils/array_manipulation/__init__.py +0 -0
  10. {acvl_utils-0.2.2 → acvl_utils-0.2.3}/acvl_utils/array_manipulation/resampling.py +0 -0
  11. {acvl_utils-0.2.2 → acvl_utils-0.2.3}/acvl_utils/array_manipulation/slicer.py +0 -0
  12. {acvl_utils-0.2.2 → acvl_utils-0.2.3}/acvl_utils/cropping_and_padding/__init__.py +0 -0
  13. {acvl_utils-0.2.2 → acvl_utils-0.2.3}/acvl_utils/cropping_and_padding/padding.py +0 -0
  14. {acvl_utils-0.2.2 → acvl_utils-0.2.3}/acvl_utils/instance_segmentation/__init__.py +0 -0
  15. {acvl_utils-0.2.2 → acvl_utils-0.2.3}/acvl_utils/instance_segmentation/instance_as_semantic_seg.py +0 -0
  16. {acvl_utils-0.2.2 → acvl_utils-0.2.3}/acvl_utils/instance_segmentation/instance_matching.py +0 -0
  17. {acvl_utils-0.2.2 → acvl_utils-0.2.3}/acvl_utils/miscellaneous/__init__.py +0 -0
  18. {acvl_utils-0.2.2 → acvl_utils-0.2.3}/acvl_utils/miscellaneous/ptqdm.py +0 -0
  19. {acvl_utils-0.2.2 → acvl_utils-0.2.3}/acvl_utils/miscellaneous/test_all_images_same.py +0 -0
  20. {acvl_utils-0.2.2 → acvl_utils-0.2.3}/acvl_utils/morphology/__init__.py +0 -0
  21. {acvl_utils-0.2.2 → acvl_utils-0.2.3}/acvl_utils/morphology/gpu_binary_morphology.py +0 -0
  22. {acvl_utils-0.2.2 → acvl_utils-0.2.3}/acvl_utils/morphology/morphology_helper.py +0 -0
  23. {acvl_utils-0.2.2 → acvl_utils-0.2.3}/acvl_utils/morphology/slice_interpolation.py +0 -0
  24. {acvl_utils-0.2.2 → acvl_utils-0.2.3}/acvl_utils.egg-info/dependency_links.txt +0 -0
  25. {acvl_utils-0.2.2 → acvl_utils-0.2.3}/acvl_utils.egg-info/requires.txt +0 -0
  26. {acvl_utils-0.2.2 → acvl_utils-0.2.3}/acvl_utils.egg-info/top_level.txt +0 -0
  27. {acvl_utils-0.2.2 → acvl_utils-0.2.3}/setup.cfg +0 -0
  28. {acvl_utils-0.2.2 → acvl_utils-0.2.3}/tests/test_component_removal.py +0 -0
  29. {acvl_utils-0.2.2 → acvl_utils-0.2.3}/tests/test_insert_crop.py +0 -0
  30. {acvl_utils-0.2.2 → acvl_utils-0.2.3}/tests/test_instance_matching.py +0 -0
  31. {acvl_utils-0.2.2 → acvl_utils-0.2.3}/tests/test_padding.py +0 -0
  32. {acvl_utils-0.2.2 → acvl_utils-0.2.3}/tests/test_ptqdm.py +0 -0
  33. {acvl_utils-0.2.2 → acvl_utils-0.2.3}/tests/test_slicer.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: acvl_utils
3
- Version: 0.2.2
3
+ Version: 0.2.3
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:
@@ -97,6 +168,14 @@ def get_bbox_from_mask(mask: np.ndarray) -> List[List[int]]:
97
168
 
98
169
 
99
170
  def get_bbox_from_mask_npwhere(mask: np.ndarray) -> List[List[int]]:
171
+ """
172
+ ALL bounding boxes in acvl_utils and nnU-Netv2 are half open interval [start, end)!
173
+ - Alignment with Python Slicing
174
+ - Ease of Subdivision
175
+ - Consistency in Multi-Dimensional Arrays
176
+ - Precedent in Computer Graphics
177
+ https://chatgpt.com/share/679203ec-3fbc-8013-a003-13a7adfb1e73
178
+ """
100
179
  where = np.array(np.where(mask))
101
180
  mins = np.min(where, 1)
102
181
  maxs = np.max(where, 1) + 1
@@ -106,21 +185,34 @@ def get_bbox_from_mask_npwhere(mask: np.ndarray) -> List[List[int]]:
106
185
  def crop_and_pad_nd(
107
186
  image: Union[torch.Tensor, np.ndarray, blosc2.ndarray.NDArray],
108
187
  bbox: List[List[int]],
109
- pad_value = 0
188
+ pad_value=0,
189
+ pad_mode: str = 'constant'
110
190
  ) -> Union[torch.Tensor, np.ndarray]:
111
191
  """
112
- Crops a bounding box directly specified by bbox, excluding the upper bound.
192
+ Crops a bounding box directly specified by bbox, adhering to the half-open interval [start, end).
113
193
  If the bounding box extends beyond the image boundaries, the cropped area is padded
114
194
  to maintain the desired size. Initial dimensions not included in bbox remain unaffected.
115
195
 
196
+ ALL bounding boxes in acvl_utils and nnU-Netv2 are half open interval [start, end)!
197
+ - Alignment with Python Slicing
198
+ - Ease of Subdivision
199
+ - Consistency in Multi-Dimensional Arrays
200
+ - Precedent in Computer Graphics
201
+ https://chatgpt.com/share/679203ec-3fbc-8013-a003-13a7adfb1e73
202
+
116
203
  Parameters:
117
- - image: N-dimensional torch.Tensor or np.ndarray representing the image
204
+ - image: N-dimensional torch.Tensor, np.ndarray, or blosc2.ndarray.NDArray representing the image.
118
205
  - bbox: List of [[dim_min, dim_max], ...] defining the bounding box for the last dimensions.
206
+ Each dimension follows the half-open interval [start, end).
207
+ - pad_value: Value used for padding when bbox extends beyond image boundaries.
208
+ - pad_mode: Padding mode, one of 'constant', 'reflect', or 'replicate' (alias for 'edge').
119
209
 
120
210
  Returns:
121
211
  - Cropped and padded patch of the requested bounding box size, as the same type as `image`.
122
212
  """
123
213
 
214
+ assert pad_mode in ['constant', 'reflect', 'replicate', 'edge'], "Unsupported pad_mode."
215
+
124
216
  # Determine the number of dimensions to crop based on bbox
125
217
  crop_dims = len(bbox)
126
218
  img_shape = image.shape
@@ -130,7 +222,7 @@ def crop_and_pad_nd(
130
222
  slices = []
131
223
  padding = []
132
224
  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]
225
+ target_shape = output_shape + [max_val - min_val for min_val, max_val in bbox] # Half-open interval
134
226
 
135
227
  # Iterate through dimensions, applying bbox to the last `crop_dims` dimensions
136
228
  for i in range(num_dims):
@@ -138,13 +230,13 @@ def crop_and_pad_nd(
138
230
  # For initial dimensions not covered by bbox, include the entire dimension
139
231
  slices.append(slice(None))
140
232
  padding.append([0, 0])
141
- output_shape.append(img_shape[i]) # Keep the initial dimensions as they are
233
+ output_shape.append(img_shape[i])
142
234
  else:
143
235
  # For dimensions specified in bbox, directly use the min and max bounds
144
236
  dim_idx = i - (num_dims - crop_dims) # Index within bbox
145
237
 
146
238
  min_val = bbox[dim_idx][0]
147
- max_val = bbox[dim_idx][1]
239
+ max_val = bbox[dim_idx][1] # This is exclusive by definition
148
240
 
149
241
  # Check if the bounding box is completely outside the image bounds
150
242
  if max_val <= 0 or min_val >= img_shape[i]:
@@ -154,9 +246,9 @@ def crop_and_pad_nd(
154
246
  elif isinstance(image, (np.ndarray, blosc2.ndarray.NDArray)):
155
247
  return np.zeros(target_shape, dtype=image.dtype)
156
248
 
157
- # Calculate valid cropping ranges within image bounds, excluding the upper bound
249
+ # Calculate valid cropping ranges within image bounds (half-open interval)
158
250
  valid_min = max(min_val, 0)
159
- valid_max = min(max_val, img_shape[i]) # Exclude upper bound by using max_val directly
251
+ valid_max = min(max_val, img_shape[i]) # Exclusive upper bound
160
252
  slices.append(slice(valid_min, valid_max))
161
253
 
162
254
  # Calculate padding needed for this dimension
@@ -172,11 +264,22 @@ def crop_and_pad_nd(
172
264
 
173
265
  # Apply padding to the cropped patch
174
266
  if isinstance(image, torch.Tensor):
267
+ if pad_mode == 'edge':
268
+ pad_mode = 'replicate'
269
+ cropped = cropped[None, None] # Adjust shape for PyTorch padding requirements
175
270
  flattened_padding = [p for sublist in reversed(padding) for p in sublist] # Flatten in reverse order for PyTorch
176
- padded_cropped = F.pad(cropped, flattened_padding, mode="constant", value=pad_value)
271
+ padded_cropped = F.pad(cropped, flattened_padding, mode=pad_mode, value=pad_value)
272
+ if pad_mode == 'replicate':
273
+ padded_cropped = padded_cropped[0, 0]
177
274
  elif isinstance(image, (np.ndarray, blosc2.ndarray.NDArray)):
275
+ if pad_mode == 'replicate':
276
+ pad_mode = 'edge'
277
+ if pad_mode == 'edge':
278
+ kwargs = {}
279
+ else:
280
+ kwargs = {'constant_values': pad_value}
178
281
  pad_width = [(p[0], p[1]) for p in padding]
179
- padded_cropped = np.pad(cropped, pad_width=pad_width, mode='constant', constant_values=pad_value)
282
+ padded_cropped = np.pad(cropped, pad_width=pad_width, mode=pad_mode, **kwargs)
180
283
  else:
181
284
  raise ValueError(f'Unsupported image type {type(image)}')
182
285
 
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: acvl_utils
3
- Version: 0.2.2
3
+ Version: 0.2.3
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
@@ -25,6 +25,7 @@ tests/test_component_removal.py
25
25
  tests/test_crop_and_pad.py
26
26
  tests/test_insert_crop.py
27
27
  tests/test_instance_matching.py
28
+ tests/test_pad_bbox.py
28
29
  tests/test_padding.py
29
30
  tests/test_ptqdm.py
30
31
  tests/test_slicer.py
@@ -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.2',
5
+ version='0.2.3',
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