Skip to content

Commit 5fba260

Browse files
Jammy2211Jammy2211
authored andcommitted
enforce odd x odd or even x even
1 parent daa9127 commit 5fba260

File tree

2 files changed

+51
-15
lines changed

2 files changed

+51
-15
lines changed

autoarray/dataset/grids.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,8 @@ def blurring(self):
104104
kernel_shape_native=self.psf.kernel.shape_native, allow_padding=True
105105
)
106106

107+
blurring_mask = blurring_mask.resized_from(new_shape=(120, 120))
108+
107109
self._blurring = Grid2D.from_mask(
108110
mask=blurring_mask,
109111
over_sample_size=1,

autoarray/mask/mask_2d_util.py

Lines changed: 49 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -461,13 +461,21 @@ 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(
464+
import warnings
465+
from typing import Tuple
466+
467+
import numpy as np
468+
from scipy.ndimage import binary_dilation
469+
470+
471+
def required_shape_for_kernel(
465472
mask_2d: np.ndarray,
466473
kernel_shape_native: Tuple[int, int],
467474
) -> Tuple[int, int]:
468475
"""
469476
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.
477+
footprint can be applied without sampling beyond the array edge, while preserving
478+
parity (odd->odd, even->even) in each dimension.
471479
472480
Parameters
473481
----------
@@ -480,7 +488,8 @@ def blurring_mask_required_shape_from(
480488
-------
481489
required_shape
482490
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).
491+
pixel to the array edge is at least (ky//2, kx//2), and each dimension keeps
492+
the same parity as the input mask.
484493
"""
485494
mask_2d = np.asarray(mask_2d, dtype=bool)
486495

@@ -496,7 +505,16 @@ def blurring_mask_required_shape_from(
496505
extra_y = max(0, pad_y - y_distance)
497506
extra_x = max(0, pad_x - x_distance)
498507

499-
return (mask_2d.shape[0] + 2 * extra_y, mask_2d.shape[1] + 2 * extra_x)
508+
new_y = mask_2d.shape[0] + 2 * extra_y
509+
new_x = mask_2d.shape[1] + 2 * extra_x
510+
511+
# Preserve parity per axis: odd->odd, even->even
512+
if (new_y % 2) != (mask_2d.shape[0] % 2):
513+
new_y += 1
514+
if (new_x % 2) != (mask_2d.shape[1] % 2):
515+
new_x += 1
516+
517+
return new_y, new_x
500518

501519

502520
def blurring_mask_2d_from(
@@ -511,7 +529,13 @@ def blurring_mask_2d_from(
511529
- False = unmasked (included)
512530
- True = masked (excluded)
513531
514-
The returned *blurring mask* is a mask where the blurring-region pixels are unmasked (False).
532+
The returned blurring mask is a *mask* where the blurring-region pixels are
533+
unmasked (False) and all other pixels are masked (True).
534+
535+
If the input mask is too small for the kernel footprint:
536+
- allow_padding=False (default): raises an exception.
537+
- allow_padding=True: pads the mask symmetrically with masked pixels (True) to the
538+
minimal required shape (with parity preserved) and emits a warning.
515539
516540
Parameters
517541
----------
@@ -520,17 +544,18 @@ def blurring_mask_2d_from(
520544
kernel_shape_native
521545
(ky, kx) kernel footprint.
522546
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.
547+
If False, raise if padding is required. If True, pad and warn.
548+
549+
Returns
550+
-------
551+
blurring_mask
552+
Boolean mask of the same shape as the (possibly padded) input.
527553
"""
528554
mask_2d = np.asarray(mask_2d, dtype=bool)
529555

530-
required_shape = blurring_mask_required_shape_from(mask_2d, kernel_shape_native)
556+
required_shape = required_shape_for_kernel(mask_2d, kernel_shape_native)
531557

532558
if required_shape != mask_2d.shape:
533-
534559
if not allow_padding:
535560
raise exc.MaskException(
536561
"The input mask is too small for the kernel shape. "
@@ -540,7 +565,7 @@ def blurring_mask_2d_from(
540565

541566
warnings.warn(
542567
f"Mask padded from {mask_2d.shape} to {required_shape} "
543-
f"to support kernel footprint {kernel_shape_native}.",
568+
f"(parity preserved) to support kernel footprint {kernel_shape_native}.",
544569
UserWarning,
545570
)
546571

@@ -559,36 +584,45 @@ def blurring_mask_2d_from(
559584
constant_values=True, # outside is masked
560585
)
561586

587+
# (Optional) hard invariant: parity preserved after any padding
588+
if (mask_2d.shape[0] % 2) != (required_shape[0] % 2) or (mask_2d.shape[1] % 2) != (
589+
required_shape[1] % 2
590+
):
591+
raise RuntimeError(
592+
f"Parity invariant violated: got {mask_2d.shape}, expected parity of {required_shape}."
593+
)
594+
562595
ky, kx = kernel_shape_native
563596
pad_y, pad_x = ky // 2, kx // 2
564597
structure = np.ones((ky, kx), dtype=bool)
565598

566599
# Unmasked region (True where unmasked)
567600
unmasked = ~mask_2d
568601

569-
# Pad so outside behaves as masked
602+
# Explicit padding so outside behaves as masked => outside is NOT unmasked
570603
unmasked_padded = np.pad(
571604
unmasked,
572605
pad_width=((pad_y, pad_y), (pad_x, pad_x)),
573606
mode="constant",
574607
constant_values=False,
575608
)
576609

610+
# Pixels within kernel footprint of any unmasked pixel
577611
near_unmasked_padded = binary_dilation(unmasked_padded, structure=structure)
578-
579612
near_unmasked = near_unmasked_padded[
580613
pad_y : pad_y + mask_2d.shape[0],
581614
pad_x : pad_x + mask_2d.shape[1],
582615
]
583616

617+
# Blurring region: masked pixels near unmasked pixels
584618
blurring_region = mask_2d & near_unmasked
585619

620+
# Return as a mask: blurring region is unmasked (False), everything else masked (True)
586621
blurring_mask = np.ones_like(mask_2d, dtype=bool)
587622
blurring_mask[blurring_region] = False
588623

589624
return blurring_mask
590625

591-
592626
def mask_slim_indexes_from(
593627
mask_2d: np.ndarray, return_masked_indexes: bool = True
594628
) -> np.ndarray:

0 commit comments

Comments
 (0)