Skip to content

Commit 39fc173

Browse files
committed
border_slim_indexes_from
1 parent 06a9ec3 commit 39fc173

1 file changed

Lines changed: 63 additions & 122 deletions

File tree

autoarray/mask/mask_2d_util.py

Lines changed: 63 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -584,27 +584,44 @@ def edge_1d_indexes_from(mask_2d: np.ndarray) -> np.ndarray:
584584
An edge pixel is defined as a pixel on the mask which is unmasked (has a `False`) value and at least one of its 8
585585
direct neighbors is masked (is `True`).
586586
587+
For example, for the following ``Mask2D``:
588+
589+
::
590+
[[True, True, True, True, True],
591+
[True, False, False, False, True],
592+
[True, False, False, False, True],
593+
[True, False, False, False, True],
594+
[True, True, True, True, True]]
595+
596+
The `edge_slim` indexes (given via ``mask_2d.derive_indexes.edge_slim``) is given by:
597+
598+
::
599+
[0, 1, 2, 3, 5, 6, 7, 8]
600+
601+
Note that index 4 is skipped, which corresponds to the ``False`` value in the centre of the mask, because it
602+
does not neighbor a ``True`` value in any one of the eight neighboring directions and is therefore not at
603+
an edge.
604+
587605
Parameters
588606
----------
589607
mask_2d
590608
A 2D boolean array where `False` values indicate unmasked pixels.
591609
592610
Returns
593611
-------
594-
np.ndarray
595-
A 1D array of indexes of all edge pixels on the mask.
612+
A 1D array of indexes of all edge pixels on the mask.
596613
597614
Examples
598615
--------
599616
>>> mask = np.array([
600617
... [True, True, True, True, True],
601-
... [True, False, False, True, True],
602618
... [True, False, False, False, True],
603-
... [True, True, False, True, True],
619+
... [True, False, False, False, True],
620+
... [True, False, False, False, True],
604621
... [True, True, True, True, True]
605622
... ])
606623
>>> edge_1d_indexes_from(mask)
607-
array([1, 2, 5, 7, 8, 9])
624+
array([0, 1, 2, 3, 5, 6, 7, 8])
608625
"""
609626
# Pad the mask to handle edge cases without index errors
610627
padded_mask = np.pad(mask_2d, pad_width=1, mode='constant', constant_values=True)
@@ -629,102 +646,12 @@ def edge_1d_indexes_from(mask_2d: np.ndarray) -> np.ndarray:
629646
return index_array[edge_mask]
630647

631648

632-
@numba_util.jit()
633-
def check_if_border_pixel(
634-
mask_2d: np.ndarray, edge_pixel_slim: int, native_to_slim: np.ndarray
635-
) -> bool:
636-
"""
637-
Checks if an input [y,x] pixel on the input `mask` is a border-pixel.
638-
639-
A borders pixel is a pixel which:
640-
641-
1) is not fully surrounding by `False` mask values.
642-
2) Can reach the edge of the array without hitting a masked pixel in one of four directions (upwards, downwards,
643-
left, right).
644-
645-
The borders pixels are thus pixels which are on the exterior edge of the mask. For example, the inner ring of edge
646-
pixels in an annular mask are edge pixels but not borders pixels.
647-
648-
Parameters
649-
----------
650-
mask_2d
651-
The mask for which the input pixel is checked if it is a border pixel.
652-
edge_pixel_slim
653-
The edge pixel index in 1D that is checked if it is a border pixel (this 1D index is mapped to 2d via the
654-
array `native_index_for_slim_index_2d`).
655-
native_to_slim
656-
An array describing the native 2D array index that every slimmed array index maps too.
657-
658-
Returns
659-
-------
660-
bool
661-
If `True` the pixel on the mask is a border pixel, else a `False` is returned because it is not.
662-
"""
663-
edge_pixel_index = int(edge_pixel_slim)
664-
665-
y = int(native_to_slim[edge_pixel_index, 0])
666-
x = int(native_to_slim[edge_pixel_index, 1])
667-
668-
if (
669-
np.sum(mask_2d[0:y, x]) == y
670-
or np.sum(mask_2d[y, x : mask_2d.shape[1]]) == mask_2d.shape[1] - x - 1
671-
or np.sum(mask_2d[y : mask_2d.shape[0], x]) == mask_2d.shape[0] - y - 1
672-
or np.sum(mask_2d[y, 0:x]) == x
673-
):
674-
return True
675-
else:
676-
return False
677-
678-
679-
@numba_util.jit()
680-
def total_border_pixels_from(mask_2d, edge_pixels, native_to_slim):
681-
"""
682-
Returns the total number of border-pixels in a mask.
683-
684-
A borders pixel is a pixel which:
685-
686-
1) is not fully surrounding by `False` mask values.
687-
2) Can reach the edge of the array without hitting a masked pixel in one of four directions (upwards, downwards,
688-
left, right).
689-
690-
The borders pixels are thus pixels which are on the exterior edge of the mask. For example, the inner ring of edge
691-
pixels in an annular mask are edge pixels but not borders pixels.
692-
693-
Parameters
694-
----------
695-
mask_2d
696-
The mask for which the total number of border pixels is computed.
697-
edge_pixel_1d
698-
The edge pixel index in 1D that is checked if it is a border pixel (this 1D index is mapped to 2d via the
699-
array `native_index_for_slim_index_2d`).
700-
native_to_slim
701-
An array describing the 2D array index that every 1D array index maps too.
702-
703-
Returns
704-
-------
705-
int
706-
The total number of border pixels.
707-
"""
708-
709-
border_pixel_total = 0
710-
711-
for i in range(edge_pixels.shape[0]):
712-
if check_if_border_pixel(mask_2d, edge_pixels[i], native_to_slim):
713-
border_pixel_total += 1
714-
715-
return border_pixel_total
716-
717-
718-
@numba_util.jit()
719649
def border_slim_indexes_from(mask_2d: np.ndarray) -> np.ndarray:
720650
"""
721-
Returns a slim array of shape [total_unmasked_border_pixels] listing all borders pixel indexes in the mask.
722-
723-
A borders pixel is a pixel which:
651+
Returns a 1D array listing all border pixel indexes in the mask.
724652
725-
1) is not fully surrounding by `False` mask values.
726-
2) Can reach the edge of the array without hitting a masked pixel in one of four directions (upwards, downwards,
727-
left, right).
653+
A border pixel is an unmasked pixel (`False` value) that can reach the edge of the mask without encountering
654+
a masked (`True`) pixel in any of the four cardinal directions (up, down, left, right).
728655
729656
The borders pixels are thus pixels which are on the exterior edge of the mask. For example, the inner ring of edge
730657
pixels in an annular mask are edge pixels but not borders pixels.
@@ -753,39 +680,53 @@ def border_slim_indexes_from(mask_2d: np.ndarray) -> np.ndarray:
753680
Parameters
754681
----------
755682
mask_2d
756-
The mask for which the slimmed border pixel indexes are calculated.
683+
A 2D boolean array where `False` values indicate unmasked pixels.
757684
758685
Returns
759686
-------
760-
np.ndarray
761-
The slimmed indexes of all border pixels on the mask.
762-
"""
687+
A 1D array of indexes of all border pixels on the mask.
763688
764-
edge_pixels = edge_1d_indexes_from(mask_2d=mask_2d)
765-
native_index_for_slim_index_2d = native_index_for_slim_index_2d_from(
766-
mask_2d=mask_2d,
767-
)
689+
Examples
690+
--------
691+
>>> mask = np.array([
692+
... [True, True, True, True, True, True, True, True, True],
693+
... [True, False, False, False, False, False, False, False, True],
694+
... [True, False, True, True, True, True, True, False, True],
695+
... [True, False, True, False, False, False, True, False, True],
696+
... [True, False, True, False, True, False, True, False, True],
697+
... [True, False, True, False, False, False, True, False, True],
698+
... [True, False, True, True, True, True, True, False, True],
699+
... [True, False, False, False, False, False, False, False, True],
700+
... [True, True, True, True, True, True, True, True, True]
701+
... ])
702+
>>> border_slim_indexes_from(mask)
703+
array([0, 1, 2, 3, 5, 6, 7, 11, 12, 15, 16, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29])
704+
"""
768705

769-
border_pixel_total = total_border_pixels_from(
770-
mask_2d=mask_2d,
771-
edge_pixels=edge_pixels,
772-
native_to_slim=native_index_for_slim_index_2d,
773-
)
706+
# Compute cumulative sums along each direction
707+
up_sums = np.cumsum(mask_2d, axis=0)
708+
down_sums = np.cumsum(mask_2d[::-1, :], axis=0)[::-1, :]
709+
left_sums = np.cumsum(mask_2d, axis=1)
710+
right_sums = np.cumsum(mask_2d[:, ::-1], axis=1)[:, ::-1]
774711

775-
border_pixels = np.zeros(border_pixel_total)
712+
# Get mask dimensions
713+
height, width = mask_2d.shape
776714

777-
border_pixel_index = 0
715+
# Identify border pixels: where the full length in any direction is True
716+
border_mask = (
717+
(up_sums == np.arange(height)[:, None]) |
718+
(down_sums == np.arange(height - 1, -1, -1)[:, None]) |
719+
(left_sums == np.arange(width)[None, :]) |
720+
(right_sums == np.arange(width - 1, -1, -1)[None, :])
721+
) & ~mask_2d
778722

779-
for edge_pixel_index in range(edge_pixels.shape[0]):
780-
if check_if_border_pixel(
781-
mask_2d=mask_2d,
782-
edge_pixel_slim=edge_pixels[edge_pixel_index],
783-
native_to_slim=native_index_for_slim_index_2d,
784-
):
785-
border_pixels[border_pixel_index] = edge_pixels[edge_pixel_index]
786-
border_pixel_index += 1
723+
# Create an index array where False entries get sequential 1D indices
724+
index_array = np.full(mask_2d.shape, fill_value=-1, dtype=int)
725+
false_indices = np.flatnonzero(~mask_2d)
726+
index_array[~mask_2d] = np.arange(len(false_indices))
787727

788-
return border_pixels
728+
# Return the 1D indexes of the border pixels
729+
return index_array[border_mask]
789730

790731

791732
@numba_util.jit()

0 commit comments

Comments
 (0)