Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/api/roi/pixelmaskcollection.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
::: eitprocessing.roi.pixelmaskcollection
4 changes: 4 additions & 0 deletions docs/api/roi/roi.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
::: eitprocessing.roi
options:
members:
- PixelMask
36 changes: 27 additions & 9 deletions eitprocessing/roi/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
import dataclasses
import sys
import warnings
from dataclasses import InitVar, dataclass, field
from dataclasses import InitVar, dataclass, field, replace
from dataclasses import replace as dataclass_replace
from typing import TypeVar, overload

Expand Down Expand Up @@ -76,16 +76,21 @@ class PixelMask:
"""

mask: np.ndarray
label: str | None = None
keep_zeros: InitVar[bool] = field(default=False, kw_only=True)
suppress_value_range_error: InitVar[bool] = field(default=False, kw_only=True)
suppress_zero_conversion_warning: InitVar[bool] = field(default=False, kw_only=True)
suppress_all_nan_warning: InitVar[bool] = field(default=False, kw_only=True)

def __init__(
self,
mask: list | np.ndarray,
*,
label: str | None = None,
keep_zeros: bool = False,
suppress_value_range_error: bool = False,
suppress_zero_conversion_warning: bool = False,
suppress_all_nan_warning: bool = False,
):
is_boolean_mask = np.array(mask).dtype == bool
mask = np.array(mask, dtype=float)
Expand All @@ -94,6 +99,13 @@ def __init__(
msg = f"Mask should be a 2D array, not {mask.ndim}D."
raise ValueError(msg)

if (not suppress_all_nan_warning) and np.all(np.isnan(mask)):
warnings.warn(
"Mask contains only NaN values. This will create in all-NaN results when applied.",
UserWarning,
stacklevel=2,
)

if (not suppress_value_range_error) and (np.nanmax(mask) > 1 or np.nanmin(mask) < 0):
msg = "One or more mask values fall outside the range 0 to 1."
exc = ValueError(msg)
Expand All @@ -113,12 +125,16 @@ def __init__(
"If you want to suppress this warning, provide `suppress_value_range_warning=True` "
"or provide boolean values as input (only for non-weighted masks)."
)
warnings.warn(msg, UserWarning)
warnings.warn(msg, UserWarning, stacklevel=2)

mask[mask == 0] = np.nan

mask.flags["WRITEABLE"] = False
object.__setattr__(self, "mask", mask)
object.__setattr__(self, "label", label)

update = replace
# TODO: add tests for update

@overload
def apply(self, data: np.ndarray) -> np.ndarray: ...
Expand Down Expand Up @@ -189,19 +205,21 @@ def __add__(self, other: Self) -> Self:

Values are clipped at 1, so that the resulting mask does not contain values higher than 1.
"""
return dataclasses.replace(self, mask=np.clip(np.nansum([self.mask, other.mask], axis=0), a_min=None, a_max=1))
new_mask = np.clip(np.nansum([self.mask, other.mask], axis=0), a_min=None, a_max=1)
new_mask[new_mask == 0] = np.nan
return dataclasses.replace(self, mask=new_mask)


LAYER_1_MASK = PixelMask(np.concat([np.ones((8, 32)), np.zeros((24, 32))], axis=0))
LAYER_2_MASK = PixelMask(np.concat([np.zeros((8, 32)), np.ones((8, 32)), np.zeros((16, 32))], axis=0))
LAYER_3_MASK = PixelMask(np.concat([np.zeros((16, 32)), np.ones((8, 32)), np.zeros((8, 32))], axis=0))
LAYER_4_MASK = PixelMask(np.concat([np.zeros((24, 32)), np.ones((8, 32))], axis=0))
LAYER_1_MASK = PixelMask(np.concat([np.ones((8, 32)), np.full((24, 32), np.nan)], axis=0))
LAYER_2_MASK = PixelMask(np.concat([np.full((8, 32), np.nan), np.ones((8, 32)), np.full((16, 32), np.nan)], axis=0))
LAYER_3_MASK = PixelMask(np.concat([np.full((16, 32), np.nan), np.ones((8, 32)), np.full((8, 32), np.nan)], axis=0))
LAYER_4_MASK = PixelMask(np.concat([np.full((24, 32), np.nan), np.ones((8, 32))], axis=0))

VENTRAL_MASK = LAYER_1_MASK + LAYER_2_MASK
DORSAL_MASK = LAYER_3_MASK + LAYER_4_MASK

ANATOMICAL_RIGHT_MASK = PixelMask(np.concat([np.ones((32, 16)), np.zeros((32, 16))], axis=1))
ANATOMICAL_LEFT_MASK = PixelMask(np.concat([np.zeros((32, 16)), np.ones((32, 16))], axis=1))
ANATOMICAL_RIGHT_MASK = PixelMask(np.concat([np.ones((32, 16)), np.full((32, 16), np.nan)], axis=1))
ANATOMICAL_LEFT_MASK = PixelMask(np.concat([np.full((32, 16), np.nan), np.ones((32, 16))], axis=1))

QUADRANT_1_MASK = VENTRAL_MASK * ANATOMICAL_RIGHT_MASK
QUADRANT_2_MASK = VENTRAL_MASK * ANATOMICAL_LEFT_MASK
Expand Down
Loading