batchgeneratorsv2 0.3.4__tar.gz → 0.3.6__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.
- {batchgeneratorsv2-0.3.4 → batchgeneratorsv2-0.3.6}/PKG-INFO +1 -1
- {batchgeneratorsv2-0.3.4 → batchgeneratorsv2-0.3.6}/batchgeneratorsv2/transforms/intensity/brightness.py +1 -1
- {batchgeneratorsv2-0.3.4 → batchgeneratorsv2-0.3.6}/batchgeneratorsv2/transforms/intensity/contrast.py +1 -2
- {batchgeneratorsv2-0.3.4 → batchgeneratorsv2-0.3.6}/batchgeneratorsv2/transforms/intensity/gamma.py +1 -2
- {batchgeneratorsv2-0.3.4 → batchgeneratorsv2-0.3.6}/batchgeneratorsv2/transforms/intensity/gaussian_noise.py +4 -3
- {batchgeneratorsv2-0.3.4 → batchgeneratorsv2-0.3.6}/batchgeneratorsv2/transforms/intensity/inversion.py +5 -4
- {batchgeneratorsv2-0.3.4 → batchgeneratorsv2-0.3.6}/batchgeneratorsv2/transforms/intensity/random_clip.py +6 -6
- {batchgeneratorsv2-0.3.4 → batchgeneratorsv2-0.3.6}/batchgeneratorsv2/transforms/local/local_contrast.py +7 -5
- {batchgeneratorsv2-0.3.4 → batchgeneratorsv2-0.3.6}/batchgeneratorsv2/transforms/local/local_gamma.py +9 -9
- {batchgeneratorsv2-0.3.4 → batchgeneratorsv2-0.3.6}/batchgeneratorsv2/transforms/local/local_smoothing.py +4 -4
- {batchgeneratorsv2-0.3.4 → batchgeneratorsv2-0.3.6}/batchgeneratorsv2/transforms/local/local_transform.py +27 -1
- {batchgeneratorsv2-0.3.4 → batchgeneratorsv2-0.3.6}/batchgeneratorsv2/transforms/nnunet/seg_to_onehot.py +10 -3
- {batchgeneratorsv2-0.3.4 → batchgeneratorsv2-0.3.6}/batchgeneratorsv2/transforms/noise/gaussian_blur.py +13 -6
- {batchgeneratorsv2-0.3.4 → batchgeneratorsv2-0.3.6}/batchgeneratorsv2/transforms/noise/rician.py +9 -6
- {batchgeneratorsv2-0.3.4 → batchgeneratorsv2-0.3.6}/batchgeneratorsv2/transforms/noise/sharpen.py +4 -3
- {batchgeneratorsv2-0.3.4 → batchgeneratorsv2-0.3.6}/batchgeneratorsv2/transforms/spatial/channel_misalignment.py +58 -11
- {batchgeneratorsv2-0.3.4 → batchgeneratorsv2-0.3.6}/batchgeneratorsv2/transforms/spatial/spatial.py +203 -67
- {batchgeneratorsv2-0.3.4 → batchgeneratorsv2-0.3.6}/batchgeneratorsv2/transforms/utils/deep_supervision_downsampling.py +5 -2
- {batchgeneratorsv2-0.3.4 → batchgeneratorsv2-0.3.6}/batchgeneratorsv2.egg-info/PKG-INFO +1 -1
- {batchgeneratorsv2-0.3.4 → batchgeneratorsv2-0.3.6}/batchgeneratorsv2.egg-info/SOURCES.txt +2 -1
- {batchgeneratorsv2-0.3.4 → batchgeneratorsv2-0.3.6}/pyproject.toml +1 -1
- batchgeneratorsv2-0.3.6/tests/test_seg_sampling.py +383 -0
- {batchgeneratorsv2-0.3.4 → batchgeneratorsv2-0.3.6}/LICENSE +0 -0
- {batchgeneratorsv2-0.3.4 → batchgeneratorsv2-0.3.6}/batchgeneratorsv2/benchmarks/__init__.py +0 -0
- {batchgeneratorsv2-0.3.4 → batchgeneratorsv2-0.3.6}/batchgeneratorsv2/benchmarks/bg_comparison/__init__.py +0 -0
- {batchgeneratorsv2-0.3.4 → batchgeneratorsv2-0.3.6}/batchgeneratorsv2/benchmarks/bg_comparison/nnUNet_pipeline_bg.py +0 -0
- {batchgeneratorsv2-0.3.4 → batchgeneratorsv2-0.3.6}/batchgeneratorsv2/benchmarks/bg_comparison/nnUNet_pipeline_here.py +0 -0
- {batchgeneratorsv2-0.3.4 → batchgeneratorsv2-0.3.6}/batchgeneratorsv2/benchmarks/unique_values.py +0 -0
- {batchgeneratorsv2-0.3.4 → batchgeneratorsv2-0.3.6}/batchgeneratorsv2/dataloading/__init__.py +0 -0
- {batchgeneratorsv2-0.3.4 → batchgeneratorsv2-0.3.6}/batchgeneratorsv2/helpers/__init__.py +0 -0
- {batchgeneratorsv2-0.3.4 → batchgeneratorsv2-0.3.6}/batchgeneratorsv2/helpers/fft_conv.py +0 -0
- {batchgeneratorsv2-0.3.4 → batchgeneratorsv2-0.3.6}/batchgeneratorsv2/helpers/scalar_type.py +0 -0
- {batchgeneratorsv2-0.3.4 → batchgeneratorsv2-0.3.6}/batchgeneratorsv2/transforms/__init__.py +0 -0
- {batchgeneratorsv2-0.3.4 → batchgeneratorsv2-0.3.6}/batchgeneratorsv2/transforms/base/__init__.py +0 -0
- {batchgeneratorsv2-0.3.4 → batchgeneratorsv2-0.3.6}/batchgeneratorsv2/transforms/base/basic_transform.py +0 -0
- {batchgeneratorsv2-0.3.4 → batchgeneratorsv2-0.3.6}/batchgeneratorsv2/transforms/intensity/__init__.py +0 -0
- {batchgeneratorsv2-0.3.4 → batchgeneratorsv2-0.3.6}/batchgeneratorsv2/transforms/local/__init__.py +0 -0
- {batchgeneratorsv2-0.3.4 → batchgeneratorsv2-0.3.6}/batchgeneratorsv2/transforms/local/brightness_gradient.py +0 -0
- {batchgeneratorsv2-0.3.4 → batchgeneratorsv2-0.3.6}/batchgeneratorsv2/transforms/nnunet/__init__.py +0 -0
- {batchgeneratorsv2-0.3.4 → batchgeneratorsv2-0.3.6}/batchgeneratorsv2/transforms/nnunet/random_binary_operator.py +0 -0
- {batchgeneratorsv2-0.3.4 → batchgeneratorsv2-0.3.6}/batchgeneratorsv2/transforms/nnunet/remove_connected_components.py +0 -0
- {batchgeneratorsv2-0.3.4 → batchgeneratorsv2-0.3.6}/batchgeneratorsv2/transforms/noise/__init__.py +0 -0
- {batchgeneratorsv2-0.3.4 → batchgeneratorsv2-0.3.6}/batchgeneratorsv2/transforms/noise/blank_rectangle.py +0 -0
- {batchgeneratorsv2-0.3.4 → batchgeneratorsv2-0.3.6}/batchgeneratorsv2/transforms/noise/median_filter.py +0 -0
- {batchgeneratorsv2-0.3.4 → batchgeneratorsv2-0.3.6}/batchgeneratorsv2/transforms/spatial/__init__.py +0 -0
- {batchgeneratorsv2-0.3.4 → batchgeneratorsv2-0.3.6}/batchgeneratorsv2/transforms/spatial/low_resolution.py +0 -0
- {batchgeneratorsv2-0.3.4 → batchgeneratorsv2-0.3.6}/batchgeneratorsv2/transforms/spatial/mirroring.py +0 -0
- {batchgeneratorsv2-0.3.4 → batchgeneratorsv2-0.3.6}/batchgeneratorsv2/transforms/spatial/rot90.py +0 -0
- {batchgeneratorsv2-0.3.4 → batchgeneratorsv2-0.3.6}/batchgeneratorsv2/transforms/spatial/transpose.py +0 -0
- {batchgeneratorsv2-0.3.4 → batchgeneratorsv2-0.3.6}/batchgeneratorsv2/transforms/utils/__init__.py +0 -0
- {batchgeneratorsv2-0.3.4 → batchgeneratorsv2-0.3.6}/batchgeneratorsv2/transforms/utils/compose.py +0 -0
- {batchgeneratorsv2-0.3.4 → batchgeneratorsv2-0.3.6}/batchgeneratorsv2/transforms/utils/cropping.py +0 -0
- {batchgeneratorsv2-0.3.4 → batchgeneratorsv2-0.3.6}/batchgeneratorsv2/transforms/utils/move_channels.py +0 -0
- {batchgeneratorsv2-0.3.4 → batchgeneratorsv2-0.3.6}/batchgeneratorsv2/transforms/utils/nnunet_masking.py +0 -0
- {batchgeneratorsv2-0.3.4 → batchgeneratorsv2-0.3.6}/batchgeneratorsv2/transforms/utils/pseudo2d.py +0 -0
- {batchgeneratorsv2-0.3.4 → batchgeneratorsv2-0.3.6}/batchgeneratorsv2/transforms/utils/random.py +0 -0
- {batchgeneratorsv2-0.3.4 → batchgeneratorsv2-0.3.6}/batchgeneratorsv2/transforms/utils/remove_label.py +0 -0
- {batchgeneratorsv2-0.3.4 → batchgeneratorsv2-0.3.6}/batchgeneratorsv2/transforms/utils/seg_to_regions.py +0 -0
- {batchgeneratorsv2-0.3.4 → batchgeneratorsv2-0.3.6}/batchgeneratorsv2.egg-info/dependency_links.txt +0 -0
- {batchgeneratorsv2-0.3.4 → batchgeneratorsv2-0.3.6}/batchgeneratorsv2.egg-info/requires.txt +0 -0
- {batchgeneratorsv2-0.3.4 → batchgeneratorsv2-0.3.6}/batchgeneratorsv2.egg-info/top_level.txt +0 -0
- {batchgeneratorsv2-0.3.4 → batchgeneratorsv2-0.3.6}/readme.md +0 -0
- {batchgeneratorsv2-0.3.4 → batchgeneratorsv2-0.3.6}/setup.cfg +0 -0
- {batchgeneratorsv2-0.3.4 → batchgeneratorsv2-0.3.6}/setup.py +0 -0
|
@@ -30,7 +30,7 @@ class MultiplicativeBrightnessTransform(ImageOnlyTransform):
|
|
|
30
30
|
# even though this is array notation it's a lot slower. Shame shame
|
|
31
31
|
# img[params['apply_to_channel']] *= params['multipliers'].view(-1, *[1]*(img.ndim - 1))
|
|
32
32
|
for c, m in zip(params['apply_to_channel'], params['multipliers']):
|
|
33
|
-
img[c] *= m
|
|
33
|
+
img[c].mul_(m) # in-place on the channel view; avoids the __setitem__ write-back copy of `img[c] *= m`
|
|
34
34
|
return img
|
|
35
35
|
|
|
36
36
|
|
{batchgeneratorsv2-0.3.4 → batchgeneratorsv2-0.3.6}/batchgeneratorsv2/transforms/intensity/gamma.py
RENAMED
|
@@ -79,8 +79,7 @@ class GammaTransform(ImageOnlyTransform):
|
|
|
79
79
|
mean = x.mean()
|
|
80
80
|
std = x.std()
|
|
81
81
|
|
|
82
|
-
minm = x
|
|
83
|
-
maxm = x.max()
|
|
82
|
+
minm, maxm = torch.aminmax(x) # single reduction pass instead of separate min()/max()
|
|
84
83
|
rnge = maxm - minm
|
|
85
84
|
denom = torch.clamp(rnge, min=eps)
|
|
86
85
|
|
|
@@ -66,9 +66,10 @@ class GaussianNoiseTransform(ImageOnlyTransform):
|
|
|
66
66
|
sigma = sigmas
|
|
67
67
|
noise = torch.empty((n, *spatial), device=device, dtype=dtype).normal_(mean=0.0, std=float(sigma))
|
|
68
68
|
|
|
69
|
-
#
|
|
70
|
-
#
|
|
71
|
-
img[idx] += noise
|
|
69
|
+
# index_add_ adds noise[i] into channel img[idx[i]] fully in place. idx is unique (from nonzero),
|
|
70
|
+
# so there is no accumulation ambiguity. This avoids the full (n, *spatial) gather copy + scatter that
|
|
71
|
+
# advanced-indexed `img[idx] += noise` would allocate.
|
|
72
|
+
img.index_add_(0, idx, noise)
|
|
72
73
|
return img
|
|
73
74
|
|
|
74
75
|
|
|
@@ -31,10 +31,11 @@ class InvertImageTransform(ImageOnlyTransform):
|
|
|
31
31
|
return img
|
|
32
32
|
else:
|
|
33
33
|
for ch in params['apply_to_channel']:
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
x = img[ch] # channel view; operate in place to drop the per-op __setitem__ write-backs
|
|
35
|
+
mn = x.mean()
|
|
36
|
+
x.sub_(mn)
|
|
37
|
+
x.mul_(-1)
|
|
38
|
+
x.add_(mn)
|
|
38
39
|
return img
|
|
39
40
|
|
|
40
41
|
|
|
@@ -67,18 +67,18 @@ class CutOffOutliersTransform(ImageOnlyTransform):
|
|
|
67
67
|
if retain_std[c]:
|
|
68
68
|
orig_std = img_c.std()
|
|
69
69
|
|
|
70
|
-
# Percentiles in torch to avoid numpy roundtrip
|
|
70
|
+
# Percentiles in torch to avoid numpy roundtrip; must run on the pre-clamp values
|
|
71
71
|
q = torch.tensor([perc[0] / 100.0, perc[1] / 100.0], device=img_c.device, dtype=torch.float32)
|
|
72
72
|
lower_val, upper_val = torch.quantile(img_c.float(), q)
|
|
73
73
|
|
|
74
|
-
|
|
74
|
+
# clamp in place on the channel view
|
|
75
|
+
img_c.clamp_(min=lower_val.item(), max=upper_val.item())
|
|
75
76
|
|
|
76
77
|
if retain_std[c]:
|
|
77
|
-
clipped_std =
|
|
78
|
+
clipped_std = img_c.std()
|
|
78
79
|
if clipped_std > 1e-8:
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
img[c] = img_c_clipped
|
|
80
|
+
m = img_c.mean() # reused for both centering steps
|
|
81
|
+
img[c] = (img_c - m) / clipped_std * orig_std + m
|
|
82
82
|
|
|
83
83
|
return img
|
|
84
84
|
|
|
@@ -40,7 +40,7 @@ class LocalContrastTransform(ImageOnlyTransform, LocalTransform):
|
|
|
40
40
|
return {'kernels': [None] * C, 'contrasts': [None] * C}
|
|
41
41
|
|
|
42
42
|
if self.same_for_all_channels:
|
|
43
|
-
kernel = self._generate_kernel(spatial)
|
|
43
|
+
kernel = self._generate_kernel(spatial)
|
|
44
44
|
contrast = sample_scalar(self.new_contrast)
|
|
45
45
|
|
|
46
46
|
kernels = [kernel if apply else None for apply in apply_channel]
|
|
@@ -52,7 +52,7 @@ class LocalContrastTransform(ImageOnlyTransform, LocalTransform):
|
|
|
52
52
|
kernels.append(None)
|
|
53
53
|
contrasts.append(None)
|
|
54
54
|
continue
|
|
55
|
-
kernel = self._generate_kernel(spatial)
|
|
55
|
+
kernel = self._generate_kernel(spatial)
|
|
56
56
|
contrast = sample_scalar(self.new_contrast)
|
|
57
57
|
kernels.append(kernel)
|
|
58
58
|
contrasts.append(contrast)
|
|
@@ -67,9 +67,11 @@ class LocalContrastTransform(ImageOnlyTransform, LocalTransform):
|
|
|
67
67
|
continue
|
|
68
68
|
|
|
69
69
|
channel = img_np[c]
|
|
70
|
-
mean = (channel * kernel).sum() / (kernel.sum() + 1e-8)
|
|
71
|
-
modified =
|
|
72
|
-
|
|
70
|
+
mean = (channel * kernel).sum() / (kernel.sum() + 1e-8) # keep exact summation order
|
|
71
|
+
modified = channel - mean # fresh owned array
|
|
72
|
+
modified *= contrast
|
|
73
|
+
modified += mean # (channel - mean) * contrast + mean
|
|
74
|
+
img_np[c] = self.run_interpolation_inplace(channel, modified, kernel)
|
|
73
75
|
|
|
74
76
|
return torch.from_numpy(img_np).to(img.device, dtype=img.dtype)
|
|
75
77
|
|
|
@@ -42,7 +42,7 @@ class LocalGammaTransform(ImageOnlyTransform, LocalTransform):
|
|
|
42
42
|
return {'kernels': [None] * C, 'gammas': [None] * C}
|
|
43
43
|
|
|
44
44
|
if self.same_for_all_channels:
|
|
45
|
-
kernel = self._generate_kernel(spatial)
|
|
45
|
+
kernel = self._generate_kernel(spatial)
|
|
46
46
|
gamma = sample_scalar(self.gamma)
|
|
47
47
|
|
|
48
48
|
kernels = [kernel if apply else None for apply in apply_channel]
|
|
@@ -54,7 +54,7 @@ class LocalGammaTransform(ImageOnlyTransform, LocalTransform):
|
|
|
54
54
|
kernels.append(None)
|
|
55
55
|
gammas.append(None)
|
|
56
56
|
continue
|
|
57
|
-
kernel = self._generate_kernel(spatial)
|
|
57
|
+
kernel = self._generate_kernel(spatial)
|
|
58
58
|
gamma = sample_scalar(self.gamma)
|
|
59
59
|
kernels.append(kernel)
|
|
60
60
|
gammas.append(gamma)
|
|
@@ -72,15 +72,15 @@ class LocalGammaTransform(ImageOnlyTransform, LocalTransform):
|
|
|
72
72
|
min_val, max_val = channel.min(), channel.max()
|
|
73
73
|
denom = max(max_val - min_val, 1e-8)
|
|
74
74
|
|
|
75
|
-
# Normalize to [0, 1]
|
|
76
|
-
norm =
|
|
75
|
+
# Normalize to [0, 1] (norm is a fresh owned array; channel/kernel are never mutated)
|
|
76
|
+
norm = channel - min_val
|
|
77
|
+
norm /= denom
|
|
77
78
|
gamma_corrected = np.power(norm, gamma)
|
|
78
79
|
|
|
79
|
-
|
|
80
|
-
blended
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
img_np[c] = blended * denom + min_val
|
|
80
|
+
blended = self.run_interpolation_inplace(norm, gamma_corrected, kernel)
|
|
81
|
+
blended *= denom # rescale to original range
|
|
82
|
+
blended += min_val
|
|
83
|
+
img_np[c] = blended
|
|
84
84
|
|
|
85
85
|
return torch.from_numpy(img_np).to(img.device, dtype=img.dtype)
|
|
86
86
|
|
|
@@ -47,7 +47,7 @@ class LocalSmoothingTransform(ImageOnlyTransform, LocalTransform):
|
|
|
47
47
|
sigma = sample_scalar(self.kernel_size)
|
|
48
48
|
|
|
49
49
|
if self.same_for_all_channels:
|
|
50
|
-
kernel = self._generate_kernel(spatial)
|
|
50
|
+
kernel = self._generate_kernel(spatial)
|
|
51
51
|
strength = sample_scalar(self.smoothing_strength)
|
|
52
52
|
|
|
53
53
|
kernels = [kernel if apply else None for apply in apply_channel]
|
|
@@ -59,7 +59,7 @@ class LocalSmoothingTransform(ImageOnlyTransform, LocalTransform):
|
|
|
59
59
|
kernels.append(None)
|
|
60
60
|
strengths.append(None)
|
|
61
61
|
continue
|
|
62
|
-
kernel = self._generate_kernel(spatial)
|
|
62
|
+
kernel = self._generate_kernel(spatial)
|
|
63
63
|
strength = sample_scalar(self.smoothing_strength)
|
|
64
64
|
kernels.append(kernel)
|
|
65
65
|
strengths.append(strength)
|
|
@@ -74,9 +74,9 @@ class LocalSmoothingTransform(ImageOnlyTransform, LocalTransform):
|
|
|
74
74
|
if kernel is None:
|
|
75
75
|
continue
|
|
76
76
|
|
|
77
|
-
kernel = kernel * strength #
|
|
77
|
+
kernel = kernel * strength # fresh owned array (shared param kernel is never mutated)
|
|
78
78
|
smoothed = gaussian_filter(img_np[c], sigma=sigma)
|
|
79
|
-
img_np[c] = self.
|
|
79
|
+
img_np[c] = self.run_interpolation_inplace(img_np[c], smoothed, kernel)
|
|
80
80
|
|
|
81
81
|
return torch.from_numpy(img_np).to(img.device, dtype=img.dtype)
|
|
82
82
|
|
|
@@ -11,6 +11,9 @@ class LocalTransform(ABC):
|
|
|
11
11
|
self.scale = scale
|
|
12
12
|
|
|
13
13
|
def _generate_kernel(self, img_shp: Tuple[int, ...]) -> np.ndarray:
|
|
14
|
+
"""
|
|
15
|
+
Always returns a float32 array in [0, 1]; callers rely on this and do not re-cast.
|
|
16
|
+
"""
|
|
14
17
|
ndim = len(img_shp)
|
|
15
18
|
x_grids = [np.arange(-0.5, s + 0.5, dtype=np.float32) for s in img_shp]
|
|
16
19
|
kernels = []
|
|
@@ -75,6 +78,8 @@ class LocalTransform(ABC):
|
|
|
75
78
|
"""
|
|
76
79
|
Blends original and modified images using the given kernel as a per-pixel weight map.
|
|
77
80
|
|
|
81
|
+
Reference implementation; prefer run_interpolation_inplace in per-sample code paths.
|
|
82
|
+
|
|
78
83
|
Parameters:
|
|
79
84
|
original_image (np.ndarray): Unmodified input image
|
|
80
85
|
modified_image (np.ndarray): Modified version (e.g., gamma-corrected)
|
|
@@ -83,4 +88,25 @@ class LocalTransform(ABC):
|
|
|
83
88
|
Returns:
|
|
84
89
|
np.ndarray: Blended result
|
|
85
90
|
"""
|
|
86
|
-
return original_image * (1.0 - kernel_image) + modified_image * kernel_image
|
|
91
|
+
return original_image * (1.0 - kernel_image) + modified_image * kernel_image
|
|
92
|
+
|
|
93
|
+
@staticmethod
|
|
94
|
+
def run_interpolation_inplace(original_image: np.ndarray,
|
|
95
|
+
modified_image: np.ndarray,
|
|
96
|
+
kernel_image: np.ndarray) -> np.ndarray:
|
|
97
|
+
"""
|
|
98
|
+
Same blend as run_interpolation, computed into modified_image's buffer with a single extra
|
|
99
|
+
temporary. Uses the identical individual * and + ops with only IEEE-commutative operand swaps
|
|
100
|
+
(no reassociation), so the result is bit-identical to run_interpolation.
|
|
101
|
+
|
|
102
|
+
Contract: modified_image must be a fresh buffer owned by the caller (it is overwritten);
|
|
103
|
+
original_image and kernel_image are only read. original_image may alias the eventual output
|
|
104
|
+
slot as long as that slot is written only after this call returns.
|
|
105
|
+
|
|
106
|
+
Returns modified_image (now holding the blended result).
|
|
107
|
+
"""
|
|
108
|
+
tmp = 1.0 - kernel_image
|
|
109
|
+
tmp *= original_image # original * (1 - kernel)
|
|
110
|
+
modified_image *= kernel_image
|
|
111
|
+
modified_image += tmp
|
|
112
|
+
return modified_image
|
|
@@ -22,10 +22,17 @@ class MoveSegAsOneHotToDataTransform(BasicTransform):
|
|
|
22
22
|
|
|
23
23
|
def apply(self, data_dict, **params):
|
|
24
24
|
seg = data_dict['segmentation'][self.source_channel_idx]
|
|
25
|
-
|
|
25
|
+
img = data_dict['image']
|
|
26
|
+
C = img.shape[0]
|
|
27
|
+
# Allocate the concatenated result once and write the one-hot channels straight into their final slots,
|
|
28
|
+
# instead of building a separate seg_onehot tensor and then torch.cat-copying everything again. Only the
|
|
29
|
+
# one-hot tail needs zeroing; the image channels are fully overwritten anyway.
|
|
30
|
+
out = torch.empty((C + len(self.all_labels), *seg.shape), dtype=img.dtype)
|
|
31
|
+
out[:C] = img
|
|
32
|
+
out[C:].zero_()
|
|
26
33
|
for i, l in enumerate(self.all_labels):
|
|
27
|
-
|
|
28
|
-
data_dict['image'] =
|
|
34
|
+
out[C + i][seg == l] = 1
|
|
35
|
+
data_dict['image'] = out
|
|
29
36
|
if self.remove_channel_from_source:
|
|
30
37
|
remaining_channels = [i for i in range(data_dict['segmentation'].shape[0]) if i != self.source_channel_idx]
|
|
31
38
|
data_dict['segmentation'] = data_dict['segmentation'][remaining_channels]
|
|
@@ -20,7 +20,8 @@ def blur_dimension(img: torch.Tensor, sigma: float, dim_to_blur: int, force_use_
|
|
|
20
20
|
where C is the channel dimension and X, Y, Z are spatial dimensions.
|
|
21
21
|
:param sigma: The standard deviation of the Gaussian kernel.
|
|
22
22
|
:param dim_to_blur: The dimension along which to apply the Gaussian blur (0 for X, 1 for Y, 2 for Z).
|
|
23
|
-
:return: The blurred image tensor.
|
|
23
|
+
:return: The blurred image tensor. `img` is never modified; a new tensor is returned (callers such as
|
|
24
|
+
GaussianBlurTransform chain these calls and rely on this non-mutation contract).
|
|
24
25
|
"""
|
|
25
26
|
assert img.ndim - 1 > dim_to_blur, "dim_to_blur must be a valid spatial dimension of the input image."
|
|
26
27
|
# Adjustments for kernel based on image dimensions
|
|
@@ -122,22 +123,28 @@ class GaussianBlurTransform(ImageOnlyTransform):
|
|
|
122
123
|
# print(params['sigmas'])
|
|
123
124
|
if self.synchronize_channels:
|
|
124
125
|
# we can compute that in one go as the conv implementation supports arbitrary input channels (with expanded kernel)
|
|
126
|
+
# Gather the selected channels once (advanced indexing copies), chain the per-dim blurs, scatter back once.
|
|
127
|
+
# blur_dimension never mutates its input, so this is the same composition as writing back each dim.
|
|
128
|
+
mask = params['apply_to_channel']
|
|
129
|
+
sub = img[mask]
|
|
125
130
|
for d in range(dim):
|
|
126
|
-
# print(d, params['sigmas'][d])
|
|
127
131
|
if not self.benchmark:
|
|
128
|
-
|
|
132
|
+
sub = blur_dimension(sub, params['sigmas'][d], d)
|
|
129
133
|
else:
|
|
130
|
-
|
|
134
|
+
sub = self._benchmark_wrapper(sub, params['sigmas'][d], d)
|
|
135
|
+
img[mask] = sub
|
|
131
136
|
else:
|
|
132
137
|
# we have to go through all the channels, build the kernel for each channel etc
|
|
133
138
|
idx = np.where(params['apply_to_channel'])[0]
|
|
134
139
|
for j, i in enumerate(idx):
|
|
140
|
+
sub = img[i:i+1] # view for the first read; blur_dimension returns fresh tensors thereafter
|
|
135
141
|
for d in range(dim):
|
|
136
142
|
# print(i, d, params['sigmas'][i][d])
|
|
137
143
|
if not self.benchmark:
|
|
138
|
-
|
|
144
|
+
sub = blur_dimension(sub, params['sigmas'][j][d], d)
|
|
139
145
|
else:
|
|
140
|
-
|
|
146
|
+
sub = self._benchmark_wrapper(sub, params['sigmas'][j][d], d)
|
|
147
|
+
img[i:i+1] = sub
|
|
141
148
|
return img
|
|
142
149
|
|
|
143
150
|
def _benchmark_wrapper(self, img: torch.Tensor, sigma: float, dim_to_blur: int):
|
{batchgeneratorsv2-0.3.4 → batchgeneratorsv2-0.3.6}/batchgeneratorsv2/transforms/noise/rician.py
RENAMED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import numpy as np
|
|
1
2
|
import torch
|
|
2
3
|
from typing import Tuple
|
|
3
4
|
from batchgeneratorsv2.transforms.base.basic_transform import ImageOnlyTransform
|
|
@@ -25,19 +26,21 @@ class RicianNoiseTransform(ImageOnlyTransform):
|
|
|
25
26
|
noise_imag = torch.empty_like(img).normal_(mean=0.0, std=var)
|
|
26
27
|
|
|
27
28
|
min_val = img.min()
|
|
28
|
-
shifted = img - min_val
|
|
29
|
+
shifted = img - min_val # fresh owned buffer (img itself is never mutated below)
|
|
29
30
|
|
|
30
|
-
|
|
31
|
-
|
|
31
|
+
# Run the whole Rician pipeline in `shifted`'s buffer. Same elementwise ops in the same order as
|
|
32
|
+
# sqrt((shifted+noise_real)^2 + noise_imag^2) + min_val, just in place -> bit-identical.
|
|
33
|
+
rician = shifted.add_(noise_real).pow_(2).add_(noise_imag.pow_(2)).sqrt_()
|
|
34
|
+
rician.add_(min_val)
|
|
32
35
|
|
|
33
|
-
# Normalize to match original mean and std
|
|
36
|
+
# Normalize to match original mean and std (scalars captured before the in-place normalization)
|
|
34
37
|
input_mean, input_std = img.mean(), img.std()
|
|
35
38
|
rician_mean, rician_std = rician.mean(), rician.std()
|
|
36
39
|
|
|
37
40
|
if rician_std > 0:
|
|
38
|
-
rician
|
|
41
|
+
rician.sub_(rician_mean).div_(rician_std).mul_(input_std).add_(input_mean)
|
|
39
42
|
else:
|
|
40
|
-
rician
|
|
43
|
+
rician.mul_(0).add_(input_mean) # keep mul_(0) (not fill_) so inf/nan propagate as before
|
|
41
44
|
|
|
42
45
|
return rician
|
|
43
46
|
|
{batchgeneratorsv2-0.3.4 → batchgeneratorsv2-0.3.6}/batchgeneratorsv2/transforms/noise/sharpen.py
RENAMED
|
@@ -81,7 +81,7 @@ class SharpeningTransform(ImageOnlyTransform):
|
|
|
81
81
|
continue
|
|
82
82
|
|
|
83
83
|
if clamp:
|
|
84
|
-
mn, mx = torch.
|
|
84
|
+
mn, mx = torch.aminmax(img[c]) # single reduction pass instead of separate min()/max()
|
|
85
85
|
|
|
86
86
|
x = img[c].unsqueeze(0).unsqueeze(0) # (1, 1, H, W) or (1, 1, D, H, W)
|
|
87
87
|
padded = F.pad(x, pad, mode='replicate')
|
|
@@ -91,8 +91,9 @@ class SharpeningTransform(ImageOnlyTransform):
|
|
|
91
91
|
else:
|
|
92
92
|
laplace = F.conv3d(padded, kernel)
|
|
93
93
|
|
|
94
|
-
|
|
95
|
-
out[c]
|
|
94
|
+
# laplace is a fresh conv output (owned, not reused). Combine in place: strength*laplace + x == x +
|
|
95
|
+
# strength*laplace (IEEE add commutes). x is read here before out[c] (== img[c]) is written -> no hazard.
|
|
96
|
+
out[c] = laplace.mul_(strength).add_(x).squeeze()
|
|
96
97
|
|
|
97
98
|
if clamp:
|
|
98
99
|
out[c].clamp_(mn, mx)
|
|
@@ -206,22 +206,69 @@ class ChannelMisalignmentTransform(ImageOnlyTransform):
|
|
|
206
206
|
# transformed by affine rather than its transpose (see issue #24).
|
|
207
207
|
grid = torch.matmul(grid, torch.from_numpy(params['affine'].T).float())
|
|
208
208
|
|
|
209
|
-
#
|
|
210
|
-
#
|
|
209
|
+
# re-center the mean of the grid (not the center position). new_center is all-zeros here (translation is
|
|
210
|
+
# handled via the shifted crop center below, not via the grid), so in the non-deform case mn is also 0 and
|
|
211
|
+
# `grid += (0 - 0)` would be a wasted full-grid pass -> skipped. This skip is only valid while new_center
|
|
212
|
+
# stays all-zeros; if a nonzero new_center is ever reintroduced, the shift must apply in both paths again.
|
|
211
213
|
if params['elastic_offsets'] is not None:
|
|
212
214
|
mn = grid.mean(dim=list(range(img.ndim - 1)))
|
|
213
|
-
|
|
214
|
-
|
|
215
|
+
# new_center = torch.Tensor([c - s / 2 for c, s in zip(params['center_location_in_pixels'], img.shape[1:])])
|
|
216
|
+
new_center = torch.zeros(len(im_shape))
|
|
217
|
+
grid += (new_center - mn)
|
|
215
218
|
|
|
216
|
-
#
|
|
217
|
-
|
|
218
|
-
grid += (new_center - mn)
|
|
219
|
+
# compute the grid_sample grid once instead of once per channel
|
|
220
|
+
grid_sample_grid = _convert_my_grid_to_grid_sample_grid(grid, img.shape[1:])[None]
|
|
219
221
|
|
|
220
222
|
for ch in self.im_channels_2_misalign:
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
223
|
+
# crop the grid_sample result directly instead of writing it into img[ch] and reading it back
|
|
224
|
+
resampled = grid_sample(img[ch, ...].unsqueeze(0).unsqueeze(0),
|
|
225
|
+
grid_sample_grid,
|
|
226
|
+
mode='bilinear', padding_mode="zeros", align_corners=False)[0]
|
|
227
|
+
img[ch, ...] = crop_tensor(resampled,
|
|
225
228
|
[math.floor(i) for i in params['center_location_in_pixels']], im_shape,
|
|
226
229
|
pad_mode='constant', pad_kwargs={'value': 0})
|
|
227
230
|
return img
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
# from copy import deepcopy
|
|
234
|
+
# import SimpleITK as sitk
|
|
235
|
+
# from pathlib import Path
|
|
236
|
+
|
|
237
|
+
# if __name__ == '__main__':
|
|
238
|
+
|
|
239
|
+
# source_folder = Path('./test_images/')
|
|
240
|
+
|
|
241
|
+
# img0 = sitk.GetArrayFromImage(sitk.ReadImage(source_folder / 'case001_0000.nii.gz'))
|
|
242
|
+
# img1 = sitk.GetArrayFromImage(sitk.ReadImage(source_folder / 'case001_0001.nii.gz'))
|
|
243
|
+
# img2 = sitk.GetArrayFromImage(sitk.ReadImage(source_folder / 'case001_0002.nii.gz'))
|
|
244
|
+
# seg = sitk.GetArrayFromImage(sitk.ReadImage(source_folder / 'case001.nii.gz'))
|
|
245
|
+
|
|
246
|
+
# sp = ChannelMisalignmentTransform(
|
|
247
|
+
# im_channels_2_misalign=(0,),
|
|
248
|
+
|
|
249
|
+
# squeezing_zyx=(0.0, 0.1, 0.0),
|
|
250
|
+
# p_squeeze=1.0,
|
|
251
|
+
|
|
252
|
+
# rotation_ax_cor_sag=(30, 0, 0),
|
|
253
|
+
# rad_or_deg="deg",
|
|
254
|
+
# p_rotation=1.0,
|
|
255
|
+
|
|
256
|
+
# shift_zyx=(5, 100, 20),
|
|
257
|
+
# p_shift = 1.0,
|
|
258
|
+
|
|
259
|
+
# )
|
|
260
|
+
|
|
261
|
+
# data_dict = {'image': torch.cat([torch.from_numpy(deepcopy(img0[None])).float(),
|
|
262
|
+
# torch.from_numpy(deepcopy(img1[None])).float(),
|
|
263
|
+
# torch.from_numpy(deepcopy(img2[None])).float()], dim=0)}
|
|
264
|
+
# if seg is not None:
|
|
265
|
+
# data_dict['segmentation'] = torch.from_numpy(deepcopy(seg[None]))
|
|
266
|
+
|
|
267
|
+
# out = sp(**data_dict)
|
|
268
|
+
|
|
269
|
+
# sitk.WriteImage(sitk.GetImageFromArray(img0), source_folder / 'original_ch0.nii.gz')
|
|
270
|
+
# sitk.WriteImage(sitk.GetImageFromArray(img1), source_folder / 'original_ch1.nii.gz')
|
|
271
|
+
# sitk.WriteImage(sitk.GetImageFromArray(img2), source_folder / 'original_ch2.nii.gz')
|
|
272
|
+
# sitk.WriteImage(sitk.GetImageFromArray(out['image'][0].numpy()), source_folder / 'transformed_ch0.nii.gz')
|
|
273
|
+
# sitk.WriteImage(sitk.GetImageFromArray(out['image'][1].numpy()), source_folder / 'transformed_ch1.nii.gz')
|
|
274
|
+
# sitk.WriteImage(sitk.GetImageFromArray(out['image'][2].numpy()), source_folder / 'transformed_ch2.nii.gz')
|