nrtk-albumentations 2.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.
Potentially problematic release.
This version of nrtk-albumentations might be problematic. Click here for more details.
- albumentations/__init__.py +21 -0
- albumentations/augmentations/__init__.py +23 -0
- albumentations/augmentations/blur/__init__.py +0 -0
- albumentations/augmentations/blur/functional.py +438 -0
- albumentations/augmentations/blur/transforms.py +1633 -0
- albumentations/augmentations/crops/__init__.py +0 -0
- albumentations/augmentations/crops/functional.py +494 -0
- albumentations/augmentations/crops/transforms.py +3647 -0
- albumentations/augmentations/dropout/__init__.py +0 -0
- albumentations/augmentations/dropout/channel_dropout.py +134 -0
- albumentations/augmentations/dropout/coarse_dropout.py +567 -0
- albumentations/augmentations/dropout/functional.py +1017 -0
- albumentations/augmentations/dropout/grid_dropout.py +166 -0
- albumentations/augmentations/dropout/mask_dropout.py +274 -0
- albumentations/augmentations/dropout/transforms.py +461 -0
- albumentations/augmentations/dropout/xy_masking.py +186 -0
- albumentations/augmentations/geometric/__init__.py +0 -0
- albumentations/augmentations/geometric/distortion.py +1238 -0
- albumentations/augmentations/geometric/flip.py +752 -0
- albumentations/augmentations/geometric/functional.py +4151 -0
- albumentations/augmentations/geometric/pad.py +676 -0
- albumentations/augmentations/geometric/resize.py +956 -0
- albumentations/augmentations/geometric/rotate.py +864 -0
- albumentations/augmentations/geometric/transforms.py +1962 -0
- albumentations/augmentations/mixing/__init__.py +0 -0
- albumentations/augmentations/mixing/domain_adaptation.py +787 -0
- albumentations/augmentations/mixing/domain_adaptation_functional.py +453 -0
- albumentations/augmentations/mixing/functional.py +878 -0
- albumentations/augmentations/mixing/transforms.py +832 -0
- albumentations/augmentations/other/__init__.py +0 -0
- albumentations/augmentations/other/lambda_transform.py +180 -0
- albumentations/augmentations/other/type_transform.py +261 -0
- albumentations/augmentations/pixel/__init__.py +0 -0
- albumentations/augmentations/pixel/functional.py +4226 -0
- albumentations/augmentations/pixel/transforms.py +7556 -0
- albumentations/augmentations/spectrogram/__init__.py +0 -0
- albumentations/augmentations/spectrogram/transform.py +220 -0
- albumentations/augmentations/text/__init__.py +0 -0
- albumentations/augmentations/text/functional.py +272 -0
- albumentations/augmentations/text/transforms.py +299 -0
- albumentations/augmentations/transforms3d/__init__.py +0 -0
- albumentations/augmentations/transforms3d/functional.py +393 -0
- albumentations/augmentations/transforms3d/transforms.py +1422 -0
- albumentations/augmentations/utils.py +249 -0
- albumentations/core/__init__.py +0 -0
- albumentations/core/bbox_utils.py +920 -0
- albumentations/core/composition.py +1885 -0
- albumentations/core/hub_mixin.py +299 -0
- albumentations/core/keypoints_utils.py +521 -0
- albumentations/core/label_manager.py +339 -0
- albumentations/core/pydantic.py +239 -0
- albumentations/core/serialization.py +352 -0
- albumentations/core/transforms_interface.py +976 -0
- albumentations/core/type_definitions.py +127 -0
- albumentations/core/utils.py +605 -0
- albumentations/core/validation.py +129 -0
- albumentations/pytorch/__init__.py +1 -0
- albumentations/pytorch/transforms.py +189 -0
- nrtk_albumentations-2.1.0.dist-info/METADATA +196 -0
- nrtk_albumentations-2.1.0.dist-info/RECORD +62 -0
- nrtk_albumentations-2.1.0.dist-info/WHEEL +4 -0
- nrtk_albumentations-2.1.0.dist-info/licenses/LICENSE +21 -0
|
File without changes
|
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
"""Functional implementations of image cropping operations.
|
|
2
|
+
|
|
3
|
+
This module provides utility functions for performing various cropping operations on images,
|
|
4
|
+
bounding boxes, and keypoints. It includes functions to calculate crop coordinates, crop images,
|
|
5
|
+
and handle the corresponding transformations for bounding boxes and keypoints to maintain
|
|
6
|
+
consistency between different data types during cropping operations.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from collections.abc import Sequence
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
import cv2
|
|
15
|
+
import numpy as np
|
|
16
|
+
from albucore import maybe_process_in_chunks, preserve_channel_dim
|
|
17
|
+
|
|
18
|
+
from albumentations.augmentations.geometric import functional as fgeometric
|
|
19
|
+
from albumentations.augmentations.utils import handle_empty_array
|
|
20
|
+
from albumentations.core.bbox_utils import denormalize_bboxes, normalize_bboxes
|
|
21
|
+
|
|
22
|
+
__all__ = [
|
|
23
|
+
"crop",
|
|
24
|
+
"crop_and_pad",
|
|
25
|
+
"crop_and_pad_bboxes",
|
|
26
|
+
"crop_and_pad_keypoints",
|
|
27
|
+
"crop_bboxes_by_coords",
|
|
28
|
+
"crop_keypoints_by_coords",
|
|
29
|
+
"get_center_crop_coords",
|
|
30
|
+
"get_crop_coords",
|
|
31
|
+
"pad_along_axes",
|
|
32
|
+
"volume_crop_yx",
|
|
33
|
+
"volumes_crop_yx",
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def get_crop_coords(
|
|
38
|
+
image_shape: tuple[int, int],
|
|
39
|
+
crop_shape: tuple[int, int],
|
|
40
|
+
h_start: float,
|
|
41
|
+
w_start: float,
|
|
42
|
+
) -> tuple[int, int, int, int]:
|
|
43
|
+
"""Get crop coordinates.
|
|
44
|
+
|
|
45
|
+
This function gets the crop coordinates.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
image_shape (tuple[int, int]): Original image shape.
|
|
49
|
+
crop_shape (tuple[int, int]): Crop shape.
|
|
50
|
+
h_start (float): Start height.
|
|
51
|
+
w_start (float): Start width.
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
tuple[int, int, int, int]: Crop coordinates.
|
|
55
|
+
|
|
56
|
+
"""
|
|
57
|
+
# h_start is [0, 1) and should map to [0, (height - crop_height)] (note inclusive)
|
|
58
|
+
# This is conceptually equivalent to mapping onto `range(0, (height - crop_height + 1))`
|
|
59
|
+
# See: https://github.com/albumentations-team/albumentations/pull/1080
|
|
60
|
+
# We want range for coordinated to be [0, image_size], right side is included
|
|
61
|
+
|
|
62
|
+
height, width = image_shape[:2]
|
|
63
|
+
|
|
64
|
+
# Clip crop dimensions to image dimensions
|
|
65
|
+
crop_height = min(crop_shape[0], height)
|
|
66
|
+
crop_width = min(crop_shape[1], width)
|
|
67
|
+
|
|
68
|
+
y_min = int((height - crop_height + 1) * h_start)
|
|
69
|
+
y_max = y_min + crop_height
|
|
70
|
+
x_min = int((width - crop_width + 1) * w_start)
|
|
71
|
+
x_max = x_min + crop_width
|
|
72
|
+
return x_min, y_min, x_max, y_max
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def crop_bboxes_by_coords(
|
|
76
|
+
bboxes: np.ndarray,
|
|
77
|
+
crop_coords: tuple[int, int, int, int],
|
|
78
|
+
image_shape: tuple[int, int],
|
|
79
|
+
normalized_input: bool = True,
|
|
80
|
+
) -> np.ndarray:
|
|
81
|
+
"""Crop bounding boxes based on given crop coordinates.
|
|
82
|
+
|
|
83
|
+
This function adjusts bounding boxes to fit within a cropped image.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
bboxes (np.ndarray): Array of bounding boxes with shape (N, 4+) where each row is
|
|
87
|
+
[x_min, y_min, x_max, y_max, ...]. The bounding box coordinates
|
|
88
|
+
can be either normalized (in [0, 1]) if normalized_input=True or
|
|
89
|
+
absolute pixel values if normalized_input=False.
|
|
90
|
+
crop_coords (tuple[int, int, int, int]): Crop coordinates (x_min, y_min, x_max, y_max)
|
|
91
|
+
in absolute pixel values.
|
|
92
|
+
image_shape (tuple[int, int]): Original image shape (height, width).
|
|
93
|
+
normalized_input (bool): Whether input boxes are in normalized coordinates.
|
|
94
|
+
If True, assumes input is normalized [0,1] and returns normalized coordinates.
|
|
95
|
+
If False, assumes input is in absolute pixels and returns absolute coordinates.
|
|
96
|
+
Default: True for backward compatibility.
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
np.ndarray: Array of cropped bounding boxes. Coordinates will be in the same format as input
|
|
100
|
+
(normalized if normalized_input=True, absolute pixels if normalized_input=False).
|
|
101
|
+
|
|
102
|
+
Note:
|
|
103
|
+
Bounding boxes that fall completely outside the crop area will be removed.
|
|
104
|
+
Bounding boxes that partially overlap with the crop area will be adjusted to fit within it.
|
|
105
|
+
|
|
106
|
+
"""
|
|
107
|
+
if not bboxes.size:
|
|
108
|
+
return bboxes
|
|
109
|
+
|
|
110
|
+
# Convert to absolute coordinates if needed
|
|
111
|
+
if normalized_input:
|
|
112
|
+
cropped_bboxes = denormalize_bboxes(bboxes.copy().astype(np.float32), image_shape)
|
|
113
|
+
else:
|
|
114
|
+
cropped_bboxes = bboxes.copy().astype(np.float32)
|
|
115
|
+
|
|
116
|
+
x_min, y_min = crop_coords[:2]
|
|
117
|
+
|
|
118
|
+
# Subtract crop coordinates
|
|
119
|
+
cropped_bboxes[:, [0, 2]] -= x_min
|
|
120
|
+
cropped_bboxes[:, [1, 3]] -= y_min
|
|
121
|
+
|
|
122
|
+
# Calculate crop shape
|
|
123
|
+
crop_height = crop_coords[3] - crop_coords[1]
|
|
124
|
+
crop_width = crop_coords[2] - crop_coords[0]
|
|
125
|
+
crop_shape = (crop_height, crop_width)
|
|
126
|
+
|
|
127
|
+
# Return in same format as input
|
|
128
|
+
return normalize_bboxes(cropped_bboxes, crop_shape) if normalized_input else cropped_bboxes
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
@handle_empty_array("keypoints")
|
|
132
|
+
def crop_keypoints_by_coords(
|
|
133
|
+
keypoints: np.ndarray,
|
|
134
|
+
crop_coords: tuple[int, int, int, int],
|
|
135
|
+
) -> np.ndarray:
|
|
136
|
+
"""Crop keypoints using the provided coordinates of bottom-left and top-right corners in pixels.
|
|
137
|
+
|
|
138
|
+
Args:
|
|
139
|
+
keypoints (np.ndarray): An array of keypoints with shape (N, 4+) where each row is (x, y, angle, scale, ...).
|
|
140
|
+
crop_coords (tuple): Crop box coords (x1, y1, x2, y2).
|
|
141
|
+
|
|
142
|
+
Returns:
|
|
143
|
+
np.ndarray: An array of cropped keypoints with the same shape as the input.
|
|
144
|
+
|
|
145
|
+
"""
|
|
146
|
+
x1, y1 = crop_coords[:2]
|
|
147
|
+
|
|
148
|
+
cropped_keypoints = keypoints.copy()
|
|
149
|
+
cropped_keypoints[:, 0] -= x1 # Adjust x coordinates
|
|
150
|
+
cropped_keypoints[:, 1] -= y1 # Adjust y coordinates
|
|
151
|
+
|
|
152
|
+
return cropped_keypoints
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def get_center_crop_coords(image_shape: tuple[int, int], crop_shape: tuple[int, int]) -> tuple[int, int, int, int]:
|
|
156
|
+
"""Get center crop coordinates.
|
|
157
|
+
|
|
158
|
+
This function gets the center crop coordinates.
|
|
159
|
+
|
|
160
|
+
Args:
|
|
161
|
+
image_shape (tuple[int, int]): Original image shape.
|
|
162
|
+
crop_shape (tuple[int, int]): Crop shape.
|
|
163
|
+
|
|
164
|
+
Returns:
|
|
165
|
+
tuple[int, int, int, int]: Center crop coordinates.
|
|
166
|
+
|
|
167
|
+
"""
|
|
168
|
+
height, width = image_shape[:2]
|
|
169
|
+
crop_height, crop_width = crop_shape[:2]
|
|
170
|
+
|
|
171
|
+
y_min = (height - crop_height) // 2
|
|
172
|
+
y_max = y_min + crop_height
|
|
173
|
+
x_min = (width - crop_width) // 2
|
|
174
|
+
x_max = x_min + crop_width
|
|
175
|
+
return x_min, y_min, x_max, y_max
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def crop(img: np.ndarray, x_min: int, y_min: int, x_max: int, y_max: int) -> np.ndarray:
|
|
179
|
+
"""Crop an image.
|
|
180
|
+
|
|
181
|
+
This function crops an image.
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
img (np.ndarray): Input image.
|
|
185
|
+
x_min (int): Minimum x coordinate.
|
|
186
|
+
y_min (int): Minimum y coordinate.
|
|
187
|
+
x_max (int): Maximum x coordinate.
|
|
188
|
+
y_max (int): Maximum y coordinate.
|
|
189
|
+
|
|
190
|
+
Returns:
|
|
191
|
+
np.ndarray: Cropped image.
|
|
192
|
+
|
|
193
|
+
"""
|
|
194
|
+
height, width = img.shape[:2]
|
|
195
|
+
if x_max <= x_min or y_max <= y_min:
|
|
196
|
+
raise ValueError(
|
|
197
|
+
"We should have x_min < x_max and y_min < y_max. But we got"
|
|
198
|
+
f" (x_min = {x_min}, y_min = {y_min}, x_max = {x_max}, y_max = {y_max})",
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
if x_min < 0 or x_max > width or y_min < 0 or y_max > height:
|
|
202
|
+
raise ValueError(
|
|
203
|
+
"Values for crop should be non negative and equal or smaller than image sizes"
|
|
204
|
+
f"(x_min = {x_min}, y_min = {y_min}, x_max = {x_max}, y_max = {y_max}, "
|
|
205
|
+
f"height = {height}, width = {width})",
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
return img[y_min:y_max, x_min:x_max]
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
@preserve_channel_dim
|
|
212
|
+
def crop_and_pad(
|
|
213
|
+
img: np.ndarray,
|
|
214
|
+
crop_params: tuple[int, int, int, int] | None,
|
|
215
|
+
pad_params: tuple[int, int, int, int] | None,
|
|
216
|
+
pad_value: tuple[float, ...] | float | None,
|
|
217
|
+
image_shape: tuple[int, int],
|
|
218
|
+
interpolation: int,
|
|
219
|
+
pad_mode: int,
|
|
220
|
+
keep_size: bool,
|
|
221
|
+
) -> np.ndarray:
|
|
222
|
+
"""Crop and pad an image.
|
|
223
|
+
|
|
224
|
+
This function crops and pads an image.
|
|
225
|
+
|
|
226
|
+
Args:
|
|
227
|
+
img (np.ndarray): Input image.
|
|
228
|
+
crop_params (tuple[int, int, int, int] | None): Crop parameters.
|
|
229
|
+
pad_params (tuple[int, int, int, int] | None): Pad parameters.
|
|
230
|
+
pad_value (tuple[float, ...] | float | None): Pad value.
|
|
231
|
+
image_shape (tuple[int, int]): Original image shape.
|
|
232
|
+
interpolation (int): Interpolation method.
|
|
233
|
+
pad_mode (int): Pad mode.
|
|
234
|
+
keep_size (bool): Whether to keep the original size.
|
|
235
|
+
|
|
236
|
+
Returns:
|
|
237
|
+
np.ndarray: Cropped and padded image.
|
|
238
|
+
|
|
239
|
+
"""
|
|
240
|
+
if crop_params is not None and any(i != 0 for i in crop_params):
|
|
241
|
+
img = crop(img, *crop_params)
|
|
242
|
+
if pad_params is not None and any(i != 0 for i in pad_params):
|
|
243
|
+
img = fgeometric.pad_with_params(
|
|
244
|
+
img,
|
|
245
|
+
pad_params[0],
|
|
246
|
+
pad_params[1],
|
|
247
|
+
pad_params[2],
|
|
248
|
+
pad_params[3],
|
|
249
|
+
border_mode=pad_mode,
|
|
250
|
+
value=pad_value,
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
if keep_size:
|
|
254
|
+
rows, cols = image_shape[:2]
|
|
255
|
+
resize_fn = maybe_process_in_chunks(cv2.resize, dsize=(cols, rows), interpolation=interpolation)
|
|
256
|
+
return resize_fn(img)
|
|
257
|
+
|
|
258
|
+
return img
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def crop_and_pad_bboxes(
|
|
262
|
+
bboxes: np.ndarray,
|
|
263
|
+
crop_params: tuple[int, int, int, int] | None,
|
|
264
|
+
pad_params: tuple[int, int, int, int] | None,
|
|
265
|
+
image_shape: tuple[int, int],
|
|
266
|
+
result_shape: tuple[int, int],
|
|
267
|
+
) -> np.ndarray:
|
|
268
|
+
"""Crop and pad bounding boxes.
|
|
269
|
+
|
|
270
|
+
This function crops and pads bounding boxes.
|
|
271
|
+
|
|
272
|
+
Args:
|
|
273
|
+
bboxes (np.ndarray): Array of bounding boxes.
|
|
274
|
+
crop_params (tuple[int, int, int, int] | None): Crop parameters.
|
|
275
|
+
pad_params (tuple[int, int, int, int] | None): Pad parameters.
|
|
276
|
+
image_shape (tuple[int, int]): Original image shape.
|
|
277
|
+
result_shape (tuple[int, int]): Result image shape.
|
|
278
|
+
|
|
279
|
+
Returns:
|
|
280
|
+
np.ndarray: Array of cropped and padded bounding boxes.
|
|
281
|
+
|
|
282
|
+
"""
|
|
283
|
+
if len(bboxes) == 0:
|
|
284
|
+
return bboxes
|
|
285
|
+
|
|
286
|
+
# Denormalize bboxes
|
|
287
|
+
denormalized_bboxes = denormalize_bboxes(bboxes, image_shape)
|
|
288
|
+
|
|
289
|
+
if crop_params is not None:
|
|
290
|
+
crop_x, crop_y = crop_params[:2]
|
|
291
|
+
# Subtract crop values from x and y coordinates
|
|
292
|
+
denormalized_bboxes[:, [0, 2]] -= crop_x
|
|
293
|
+
denormalized_bboxes[:, [1, 3]] -= crop_y
|
|
294
|
+
|
|
295
|
+
if pad_params is not None:
|
|
296
|
+
top, _, left, _ = pad_params
|
|
297
|
+
# Add pad values to x and y coordinates
|
|
298
|
+
denormalized_bboxes[:, [0, 2]] += left
|
|
299
|
+
denormalized_bboxes[:, [1, 3]] += top
|
|
300
|
+
|
|
301
|
+
# Normalize bboxes to the result shape
|
|
302
|
+
return normalize_bboxes(denormalized_bboxes, result_shape)
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
@handle_empty_array("keypoints")
|
|
306
|
+
def crop_and_pad_keypoints(
|
|
307
|
+
keypoints: np.ndarray,
|
|
308
|
+
crop_params: tuple[int, int, int, int] | None = None,
|
|
309
|
+
pad_params: tuple[int, int, int, int] | None = None,
|
|
310
|
+
image_shape: tuple[int, int] = (0, 0),
|
|
311
|
+
result_shape: tuple[int, int] = (0, 0),
|
|
312
|
+
keep_size: bool = False,
|
|
313
|
+
) -> np.ndarray:
|
|
314
|
+
"""Crop and pad multiple keypoints simultaneously.
|
|
315
|
+
|
|
316
|
+
Args:
|
|
317
|
+
keypoints (np.ndarray): Array of keypoints with shape (N, 4+) where each row is (x, y, angle, scale, ...).
|
|
318
|
+
crop_params (Sequence[int], optional): Crop parameters [crop_x1, crop_y1, ...].
|
|
319
|
+
pad_params (Sequence[int], optional): Pad parameters [top, bottom, left, right].
|
|
320
|
+
image_shape (Tuple[int, int]): Original image shape (rows, cols).
|
|
321
|
+
result_shape (Tuple[int, int]): Result image shape (rows, cols).
|
|
322
|
+
keep_size (bool): Whether to keep the original size.
|
|
323
|
+
|
|
324
|
+
Returns:
|
|
325
|
+
np.ndarray: Array of transformed keypoints with the same shape as input.
|
|
326
|
+
|
|
327
|
+
"""
|
|
328
|
+
transformed_keypoints = keypoints.copy()
|
|
329
|
+
|
|
330
|
+
if crop_params is not None:
|
|
331
|
+
crop_x1, crop_y1 = crop_params[:2]
|
|
332
|
+
transformed_keypoints[:, 0] -= crop_x1
|
|
333
|
+
transformed_keypoints[:, 1] -= crop_y1
|
|
334
|
+
|
|
335
|
+
if pad_params is not None:
|
|
336
|
+
top, _, left, _ = pad_params
|
|
337
|
+
transformed_keypoints[:, 0] += left
|
|
338
|
+
transformed_keypoints[:, 1] += top
|
|
339
|
+
|
|
340
|
+
rows, cols = image_shape[:2]
|
|
341
|
+
result_rows, result_cols = result_shape[:2]
|
|
342
|
+
|
|
343
|
+
if keep_size and (result_cols != cols or result_rows != rows):
|
|
344
|
+
scale_x = cols / result_cols
|
|
345
|
+
scale_y = rows / result_rows
|
|
346
|
+
return fgeometric.keypoints_scale(transformed_keypoints, scale_x, scale_y)
|
|
347
|
+
|
|
348
|
+
return transformed_keypoints
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
def volume_crop_yx(
|
|
352
|
+
volume: np.ndarray,
|
|
353
|
+
x_min: int,
|
|
354
|
+
y_min: int,
|
|
355
|
+
x_max: int,
|
|
356
|
+
y_max: int,
|
|
357
|
+
) -> np.ndarray:
|
|
358
|
+
"""Crop a single volume along Y (height) and X (width) axes only.
|
|
359
|
+
|
|
360
|
+
Args:
|
|
361
|
+
volume (np.ndarray): Input volume with shape (D, H, W) or (D, H, W, C).
|
|
362
|
+
x_min (int): Minimum width coordinate.
|
|
363
|
+
y_min (int): Minimum height coordinate.
|
|
364
|
+
x_max (int): Maximum width coordinate.
|
|
365
|
+
y_max (int): Maximum height coordinate.
|
|
366
|
+
|
|
367
|
+
Returns:
|
|
368
|
+
np.ndarray: Cropped volume (D, H_new, W_new, [C]).
|
|
369
|
+
|
|
370
|
+
Raises:
|
|
371
|
+
ValueError: If crop coordinates are invalid.
|
|
372
|
+
|
|
373
|
+
"""
|
|
374
|
+
_, height, width = volume.shape[:3]
|
|
375
|
+
if x_max <= x_min or y_max <= y_min:
|
|
376
|
+
raise ValueError(
|
|
377
|
+
"Crop coordinates must satisfy min < max. Got: "
|
|
378
|
+
f"(x_min={x_min}, y_min={y_min}, x_max={x_max}, y_max={y_max})",
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
if x_min < 0 or y_min < 0 or x_max > width or y_max > height:
|
|
382
|
+
raise ValueError(
|
|
383
|
+
"Crop coordinates must be within image dimensions (H, W). Got: "
|
|
384
|
+
f"(x_min={x_min}, y_min={y_min}, x_max={x_max}, y_max={y_max}) "
|
|
385
|
+
f"for volume shape {volume.shape[:3]}",
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
# Crop along H (axis 1) and W (axis 2)
|
|
389
|
+
return volume[:, y_min:y_max, x_min:x_max]
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
def volumes_crop_yx(
|
|
393
|
+
volumes: np.ndarray,
|
|
394
|
+
x_min: int,
|
|
395
|
+
y_min: int,
|
|
396
|
+
x_max: int,
|
|
397
|
+
y_max: int,
|
|
398
|
+
) -> np.ndarray:
|
|
399
|
+
"""Crop a batch of volumes along Y (height) and X (width) axes only.
|
|
400
|
+
|
|
401
|
+
Args:
|
|
402
|
+
volumes (np.ndarray): Input batch of volumes with shape (B, D, H, W) or (B, D, H, W, C).
|
|
403
|
+
x_min (int): Minimum width coordinate.
|
|
404
|
+
y_min (int): Minimum height coordinate.
|
|
405
|
+
x_max (int): Maximum width coordinate.
|
|
406
|
+
y_max (int): Maximum height coordinate.
|
|
407
|
+
|
|
408
|
+
Returns:
|
|
409
|
+
np.ndarray: Cropped batch of volumes (B, D, H_new, W_new, [C]).
|
|
410
|
+
|
|
411
|
+
Raises:
|
|
412
|
+
ValueError: If crop coordinates are invalid or volumes shape is incorrect.
|
|
413
|
+
|
|
414
|
+
"""
|
|
415
|
+
if not 4 <= volumes.ndim <= 5:
|
|
416
|
+
raise ValueError(f"Input volumes should have 4 or 5 dimensions, got {volumes.ndim}")
|
|
417
|
+
|
|
418
|
+
depth, height, width = volumes.shape[1:4]
|
|
419
|
+
if x_max <= x_min or y_max <= y_min:
|
|
420
|
+
raise ValueError(
|
|
421
|
+
"Crop coordinates must satisfy min < max. Got: "
|
|
422
|
+
f"(x_min={x_min}, y_min={y_min}, x_max={x_max}, y_max={y_max})",
|
|
423
|
+
)
|
|
424
|
+
|
|
425
|
+
if x_min < 0 or y_min < 0 or x_max > width or y_max > height:
|
|
426
|
+
raise ValueError(
|
|
427
|
+
"Crop coordinates must be within image dimensions (H, W). Got: "
|
|
428
|
+
f"(x_min={x_min}, y_min={y_min}, x_max={x_max}, y_max={y_max}) "
|
|
429
|
+
f"for volume shape {(depth, height, width)}",
|
|
430
|
+
)
|
|
431
|
+
|
|
432
|
+
# Crop along H (axis 2) and W (axis 3)
|
|
433
|
+
return volumes[:, :, y_min:y_max, x_min:x_max]
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
def pad_along_axes(
|
|
437
|
+
arr: np.ndarray,
|
|
438
|
+
pad_top: int,
|
|
439
|
+
pad_bottom: int,
|
|
440
|
+
pad_left: int,
|
|
441
|
+
pad_right: int,
|
|
442
|
+
h_axis: int,
|
|
443
|
+
w_axis: int,
|
|
444
|
+
border_mode: int,
|
|
445
|
+
pad_value: float | Sequence[float] = 0,
|
|
446
|
+
) -> np.ndarray:
|
|
447
|
+
"""Pad an array along specified height (H) and width (W) axes using np.pad.
|
|
448
|
+
|
|
449
|
+
Args:
|
|
450
|
+
arr (np.ndarray): Input array.
|
|
451
|
+
pad_top (int): Padding added to the top (start of H axis).
|
|
452
|
+
pad_bottom (int): Padding added to the bottom (end of H axis).
|
|
453
|
+
pad_left (int): Padding added to the left (start of W axis).
|
|
454
|
+
pad_right (int): Padding added to the right (end of W axis).
|
|
455
|
+
h_axis (int): Index of the height axis (Y).
|
|
456
|
+
w_axis (int): Index of the width axis (X).
|
|
457
|
+
border_mode (int): OpenCV border mode.
|
|
458
|
+
pad_value (float | Sequence[float]): Value for constant padding.
|
|
459
|
+
|
|
460
|
+
Returns:
|
|
461
|
+
np.ndarray: Padded array.
|
|
462
|
+
|
|
463
|
+
Raises:
|
|
464
|
+
ValueError: If border_mode is unsupported or axis indices are out of bounds.
|
|
465
|
+
|
|
466
|
+
"""
|
|
467
|
+
ndim = arr.ndim
|
|
468
|
+
if not (0 <= h_axis < ndim and 0 <= w_axis < ndim):
|
|
469
|
+
raise ValueError(f"Axis indices {h_axis}, {w_axis} are out of bounds for array with {ndim} dimensions.")
|
|
470
|
+
if h_axis == w_axis:
|
|
471
|
+
raise ValueError(f"Height axis {h_axis} and width axis {w_axis} cannot be the same.")
|
|
472
|
+
|
|
473
|
+
mode_map = {
|
|
474
|
+
cv2.BORDER_CONSTANT: "constant",
|
|
475
|
+
cv2.BORDER_REPLICATE: "edge",
|
|
476
|
+
cv2.BORDER_REFLECT: "reflect",
|
|
477
|
+
cv2.BORDER_REFLECT_101: "symmetric",
|
|
478
|
+
cv2.BORDER_WRAP: "wrap",
|
|
479
|
+
}
|
|
480
|
+
if border_mode not in mode_map:
|
|
481
|
+
raise ValueError(f"Unsupported border_mode: {border_mode}")
|
|
482
|
+
np_mode = mode_map[border_mode]
|
|
483
|
+
|
|
484
|
+
pad_width = [(0, 0)] * ndim # Initialize padding for all dimensions
|
|
485
|
+
pad_width[h_axis] = (pad_top, pad_bottom)
|
|
486
|
+
pad_width[w_axis] = (pad_left, pad_right)
|
|
487
|
+
|
|
488
|
+
# Initialize kwargs with mode
|
|
489
|
+
kwargs: dict[str, Any] = {"mode": np_mode}
|
|
490
|
+
# Add constant_values only if mode is constant
|
|
491
|
+
if np_mode == "constant":
|
|
492
|
+
kwargs["constant_values"] = pad_value
|
|
493
|
+
|
|
494
|
+
return np.pad(arr, pad_width, **kwargs)
|