Skip to content

Commit daa9127

Browse files
Jammy2211Jammy2211
authored andcommitted
now havce option to pad blurring mask
1 parent ddbec19 commit daa9127

File tree

4 files changed

+97
-23
lines changed

4 files changed

+97
-23
lines changed

autoarray/dataset/grids.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ def blurring(self):
101101
else:
102102

103103
blurring_mask = self.mask.derive_mask.blurring_from(
104-
kernel_shape_native=self.psf.kernel.shape_native
104+
kernel_shape_native=self.psf.kernel.shape_native, allow_padding=True
105105
)
106106

107107
self._blurring = Grid2D.from_mask(

autoarray/inversion/mesh/interpolator/knn.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,10 @@ def _mappings_sizes_weights(self):
149149
try:
150150
query_points = self.data_grid.over_sampled.array
151151
except AttributeError:
152-
query_points = self.data_grid.array
152+
try:
153+
query_points = self.data_grid.array
154+
except AttributeError:
155+
query_points = self.data_grid
153156

154157
mappings, weights, _ = get_interpolation_weights(
155158
points=self.mesh_grid_xy,

autoarray/mask/derive/mask_2d.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,9 @@ def all_false(self) -> Mask2D:
139139
origin=self.mask.origin,
140140
)
141141

142-
def blurring_from(self, kernel_shape_native: Tuple[int, int]) -> Mask2D:
142+
def blurring_from(
143+
self, kernel_shape_native: Tuple[int, int], allow_padding: bool = False
144+
) -> Mask2D:
143145
"""
144146
Returns a blurring ``Mask2D``, representing all masked pixels (given by ``True``) whose values are blurred
145147
into unmasked pixels (given by ``False``) when a 2D convolution is performed.
@@ -201,6 +203,7 @@ def blurring_from(self, kernel_shape_native: Tuple[int, int]) -> Mask2D:
201203
blurring_mask = mask_2d_util.blurring_mask_2d_from(
202204
mask_2d=self.mask,
203205
kernel_shape_native=kernel_shape_native,
206+
allow_padding=allow_padding,
204207
)
205208

206209
return Mask2D(

autoarray/mask/mask_2d_util.py

Lines changed: 88 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -461,8 +461,48 @@ def min_false_distance_to_edge(mask: np.ndarray) -> Tuple[int, int]:
461461
return min(top_dist, bottom_dist), min(left_dist, right_dist)
462462

463463

464+
def blurring_mask_required_shape_from(
465+
mask_2d: np.ndarray,
466+
kernel_shape_native: Tuple[int, int],
467+
) -> Tuple[int, int]:
468+
"""
469+
Return the minimal shape the mask must be padded to so that a kernel with the given
470+
footprint can be applied without sampling beyond the array edge.
471+
472+
Parameters
473+
----------
474+
mask_2d
475+
2D boolean array where False is unmasked and True is masked.
476+
kernel_shape_native
477+
(ky, kx) footprint of the convolution kernel.
478+
479+
Returns
480+
-------
481+
required_shape
482+
The minimal (ny, nx) shape such that the minimum distance from any unmasked
483+
pixel to the array edge is at least (ky//2, kx//2).
484+
"""
485+
mask_2d = np.asarray(mask_2d, dtype=bool)
486+
487+
ky, kx = kernel_shape_native
488+
if ky <= 0 or kx <= 0:
489+
raise ValueError(
490+
f"kernel_shape_native must be positive, got {kernel_shape_native}."
491+
)
492+
493+
pad_y, pad_x = ky // 2, kx // 2
494+
y_distance, x_distance = min_false_distance_to_edge(mask_2d)
495+
496+
extra_y = max(0, pad_y - y_distance)
497+
extra_x = max(0, pad_x - x_distance)
498+
499+
return (mask_2d.shape[0] + 2 * extra_y, mask_2d.shape[1] + 2 * extra_x)
500+
501+
464502
def blurring_mask_2d_from(
465-
mask_2d: np.ndarray, kernel_shape_native: Tuple[int, int]
503+
mask_2d: np.ndarray,
504+
kernel_shape_native: Tuple[int, int],
505+
allow_padding: bool = False,
466506
) -> np.ndarray:
467507
"""
468508
Return the blurring mask for a 2D mask and kernel footprint.
@@ -472,51 +512,79 @@ def blurring_mask_2d_from(
472512
- True = masked (excluded)
473513
474514
The returned *blurring mask* is a mask where the blurring-region pixels are unmasked (False).
515+
516+
Parameters
517+
----------
518+
mask_2d
519+
2D boolean mask.
520+
kernel_shape_native
521+
(ky, kx) kernel footprint.
522+
allow_padding
523+
If False (default), raises an exception when the mask is too small
524+
for the kernel footprint.
525+
If True, pads the mask symmetrically with masked pixels (True) to the
526+
minimal required shape and emits a warning.
475527
"""
476528
mask_2d = np.asarray(mask_2d, dtype=bool)
477529

478-
ky, kx = kernel_shape_native
479-
if ky <= 0 or kx <= 0:
480-
raise ValueError(
481-
f"kernel_shape_native must be positive, got {kernel_shape_native}."
530+
required_shape = blurring_mask_required_shape_from(mask_2d, kernel_shape_native)
531+
532+
if required_shape != mask_2d.shape:
533+
534+
if not allow_padding:
535+
raise exc.MaskException(
536+
"The input mask is too small for the kernel shape. "
537+
f"Current shape: {mask_2d.shape}, required shape: {required_shape}. "
538+
"Set allow_padding=True to pad automatically."
539+
)
540+
541+
warnings.warn(
542+
f"Mask padded from {mask_2d.shape} to {required_shape} "
543+
f"to support kernel footprint {kernel_shape_native}.",
544+
UserWarning,
482545
)
483546

484-
# Keep your existing guard (optional)
485-
y_distance, x_distance = min_false_distance_to_edge(mask_2d)
486-
if (y_distance < ky // 2) or (x_distance < kx // 2):
487-
raise exc.MaskException(
488-
"The input mask is too small for the kernel shape. "
489-
"Please pad the mask before computing the blurring mask."
547+
dy = required_shape[0] - mask_2d.shape[0]
548+
dx = required_shape[1] - mask_2d.shape[1]
549+
550+
pad_top = dy // 2
551+
pad_bottom = dy - pad_top
552+
pad_left = dx // 2
553+
pad_right = dx - pad_left
554+
555+
mask_2d = np.pad(
556+
mask_2d,
557+
pad_width=((pad_top, pad_bottom), (pad_left, pad_right)),
558+
mode="constant",
559+
constant_values=True, # outside is masked
490560
)
491561

492-
# Kernel footprint (support only)
562+
ky, kx = kernel_shape_native
563+
pad_y, pad_x = ky // 2, kx // 2
493564
structure = np.ones((ky, kx), dtype=bool)
494565

495566
# Unmasked region (True where unmasked)
496567
unmasked = ~mask_2d
497568

498-
# Explicit padding: outside-of-array is masked => outside is NOT unmasked (False)
499-
pad_y, pad_x = ky // 2, kx // 2
569+
# Pad so outside behaves as masked
500570
unmasked_padded = np.pad(
501571
unmasked,
502572
pad_width=((pad_y, pad_y), (pad_x, pad_x)),
503573
mode="constant",
504574
constant_values=False,
505575
)
506576

507-
# Pixels within kernel footprint of any unmasked pixel
508577
near_unmasked_padded = binary_dilation(unmasked_padded, structure=structure)
578+
509579
near_unmasked = near_unmasked_padded[
510580
pad_y : pad_y + mask_2d.shape[0],
511581
pad_x : pad_x + mask_2d.shape[1],
512582
]
513583

514-
# Blurring REGION: masked pixels that are near unmasked pixels
515-
blurring_region = mask_2d & near_unmasked # True on the ring (in region-space)
584+
blurring_region = mask_2d & near_unmasked
516585

517-
# Convert region -> mask semantics: ring should be unmasked (False)
518-
blurring_mask = np.ones_like(mask_2d, dtype=bool) # start fully masked
519-
blurring_mask[blurring_region] = False # unmask the ring
586+
blurring_mask = np.ones_like(mask_2d, dtype=bool)
587+
blurring_mask[blurring_region] = False
520588

521589
return blurring_mask
522590

0 commit comments

Comments
 (0)