Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge/add shoal mask #88

Merged
merged 18 commits into from
Oct 26, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
8 changes: 4 additions & 4 deletions .github/workflows/docker.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,17 @@ jobs:
echo "IMAGE_SPEC=${IMAGE_SPEC}" >> $GITHUB_ENV
echo "DATE_TAG=${DATE_TAG}" >> $GITHUB_ENV
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
uses: docker/setup-buildx-action@v3
- name: Login to DockerHub
uses: docker/login-action@v2
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_TOKEN }}
- name: Build and push
id: docker_build_push
uses: docker/build-push-action@v4
uses: docker/build-push-action@v5
with:
context: ./
file: ./.ci_helpers/docker/${{ matrix.image_name }}.dockerfile
Expand Down
5 changes: 3 additions & 2 deletions echopype/mask/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from .api import apply_mask, frequency_differencing, get_seabed_mask, shoal_weill
from .api import apply_mask, frequency_differencing, get_seabed_mask, get_shoal_mask

__all__ = ["frequency_differencing", "apply_mask", "get_seabed_mask", "get_shoal_mask"]

__all__ = ["frequency_differencing", "apply_mask", "get_seabed_mask", "shoal_weill"]
214 changes: 107 additions & 107 deletions echopype/mask/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@
from ..utils.io import get_dataset, validate_source_ds_da
from ..utils.misc import frequency_nominal_to_channel
from ..utils.prov import add_processing_level, echopype_prov_attrs, insert_input_processing_level
from . import seabed

from . import shoal, seabed
from .freq_diff import _check_freq_diff_source_Sv, _parse_freq_diff_eq
from .shoal import _weill as shoal_weill


# lookup table with key string operator and value as corresponding Python operator
str2ops = {
Expand Down Expand Up @@ -552,7 +553,110 @@ def create_multichannel_mask(masks: [xr.Dataset], channels: [str]) -> xr.Dataset
)
return result


def get_shoal_mask(
source_Sv: Union[xr.Dataset, str, pathlib.Path],
parameters: dict,
desired_channel: str = None,
desired_frequency: int = None,
method: str = "will",
**kwargs,
):
"""
Wrapper function for (future) multiple shoal masking algorithms
(currently, only MOVIES-B (Will) is implemented)

Args:
source_Sv: xr.Dataset or str or pathlib.Path
If a Dataset this value contains the Sv data to create a mask for,
else it specifies the path to a zarr or netcdf file containing
a Dataset. This input must correspond to a Dataset that has the
coordinate ``channel`` and variables ``frequency_nominal`` and ``Sv``.
desired_channel: str specifying the channel to generate the mask on
method: string specifying the algorithm to use
currently, 'weill' is the only one implemented

Returns
-------
mask: xr.DataArray
A DataArray containing the mask for the Sv data. Regions satisfying the thresholding
criteria are filled with ``True``, else the regions are filled with ``False``.
mask_: xr.DataArray
A DataArray containing the mask for areas in which shoals were searched.
Edge regions are filled with 'False', whereas the portion
in which shoals could be detected is 'True'


Raises
------
ValueError
If 'weill' is not given
"""
source_Sv = get_dataset(source_Sv)
mask_map = {
"will": shoal._weill,
}

if method not in mask_map.keys():
raise ValueError(f"Unsupported method: {method}")
if desired_channel is None:
if desired_frequency is None:
raise ValueError("Must specify either desired channel or desired frequency")
else:
desired_channel = frequency_nominal_to_channel(source_Sv, desired_frequency)
mask, mask_ = mask_map[method](source_Sv, desired_channel, parameters)
return mask, mask_


def get_shoal_mask_multichannel(
source_Sv: Union[xr.Dataset, str, pathlib.Path],
parameters: dict,
method: str = "will",
):
"""
Wrapper function for (future) multiple shoal masking algorithms
(currently, only MOVIES-B (Will) is implemented)

Args:
source_Sv: xr.Dataset or str or pathlib.Path
If a Dataset this value contains the Sv data to create a mask for,
else it specifies the path to a zarr or netcdf file containing
a Dataset. This input must correspond to a Dataset that has the
coordinate ``channel`` and variables ``frequency_nominal`` and ``Sv``.
mask_type: string specifying the algorithm to use
currently, 'weill' is the only one implemented

Returns
-------
mask: xr.DataArray
A DataArray containing the multichannel mask for the Sv data.
Regions satisfying the thresholding criteria are filled with ``True``,
else the regions are filled with ``False``.
mask_: xr.DataArray
A DataArray containing the multichannel mask for areas in which shoals were searched.
Edge regions are filled with 'False', whereas the portion
in which shoals could be detected is 'True'


Raises
------
ValueError
If 'weill' is not given
"""
channel_list = source_Sv["channel"].values
mask_list = []
_mask_list = []
for channel in channel_list:
mask, _mask = get_shoal_mask(
source_Sv, desired_channel=channel, method=method, parameters=parameters
)
mask_list.append(mask)
_mask_list.append(_mask)
mask = create_multichannel_mask(mask_list, channel_list)
_mask = create_multichannel_mask(_mask_list, channel_list)
return mask, _mask


def get_seabed_mask(
source_Sv: Union[xr.Dataset, str, pathlib.Path],
parameters: dict,
Expand Down Expand Up @@ -666,108 +770,4 @@ def get_seabed_mask_multichannel(
mask_list.append(mask)
mask = create_multichannel_mask(mask_list, channel_list)
return mask


def get_shoal_mask(
source_Sv: Union[xr.Dataset, str, pathlib.Path],
desired_channel: str,
mask_type: str = "will",
**kwargs,
):
"""
Wrapper function for (future) multiple shoal masking algorithms
(currently, only MOVIES-B (Will) is implemented)

Args:
source_Sv: xr.Dataset or str or pathlib.Path
If a Dataset this value contains the Sv data to create a mask for,
else it specifies the path to a zarr or netcdf file containing
a Dataset. This input must correspond to a Dataset that has the
coordinate ``channel`` and variables ``frequency_nominal`` and ``Sv``.
desired_channel: str specifying the channel to generate the mask on
mask_type: string specifying the algorithm to use
currently, 'weill' is the only one implemented

Returns
-------
mask: xr.DataArray
A DataArray containing the mask for the Sv data. Regions satisfying the thresholding
criteria are filled with ``True``, else the regions are filled with ``False``.
mask_: xr.DataArray
A DataArray containing the mask for areas in which shoals were searched.
Edge regions are filled with 'False', whereas the portion
in which shoals could be detected is 'True'


Raises
------
ValueError
If 'weill' is not given
"""
assert mask_type in ["will"]
if mask_type == "will":
# Define a list of the keyword arguments your function can handle
valid_args = {"thr", "maxvgap", "maxhgap", "minvlen", "minhlen"}
# Filter out any kwargs not in your list
filtered_kwargs = {k: v for k, v in kwargs.items() if k in valid_args}
mask, mask_ = shoal_weill(source_Sv, desired_channel, **filtered_kwargs)
else:
raise ValueError("The provided mask type must be Will")
return_mask = xr.DataArray(
mask,
dims=("ping_time", "range_sample"),
coords={"ping_time": source_Sv.ping_time, "range_sample": source_Sv.range_sample},
)
return_mask_ = xr.DataArray(
mask_,
dims=("ping_time", "range_sample"),
coords={"ping_time": source_Sv.ping_time, "range_sample": source_Sv.range_sample},
)
return return_mask, return_mask_


def get_shoal_mask_multichannel(
source_Sv: Union[xr.Dataset, str, pathlib.Path],
mask_type: str = "will",
**kwargs,
):
"""
Wrapper function for (future) multiple shoal masking algorithms
(currently, only MOVIES-B (Will) is implemented)

Args:
source_Sv: xr.Dataset or str or pathlib.Path
If a Dataset this value contains the Sv data to create a mask for,
else it specifies the path to a zarr or netcdf file containing
a Dataset. This input must correspond to a Dataset that has the
coordinate ``channel`` and variables ``frequency_nominal`` and ``Sv``.
mask_type: string specifying the algorithm to use
currently, 'weill' is the only one implemented

Returns
-------
mask: xr.DataArray
A DataArray containing the multichannel mask for the Sv data.
Regions satisfying the thresholding criteria are filled with ``True``,
else the regions are filled with ``False``.
mask_: xr.DataArray
A DataArray containing the multichannel mask for areas in which shoals were searched.
Edge regions are filled with 'False', whereas the portion
in which shoals could be detected is 'True'


Raises
------
ValueError
If 'weill' is not given
"""
channel_list = source_Sv["channel"].values
mask_list = []
_mask_list = []
for channel in channel_list:
mask, _mask = get_shoal_mask(source_Sv, channel, mask_type, **kwargs)
mask_list.append(mask)
_mask_list.append(_mask)
mask = create_multichannel_mask(mask_list, channel_list)
_mask = create_multichannel_mask(_mask_list, channel_list)
return mask, _mask

57 changes: 39 additions & 18 deletions echopype/mask/shoal.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,13 @@
import scipy.ndimage as nd_img
import xarray as xr

WEILL_DEFAULT_PARAMETERS = {"thr": -70, "maxvgap": -5, "maxhgap": 0, "minvlen": 0, "minhlen": 0}


def _weill(
source_Sv: Union[xr.Dataset, str, pathlib.Path],
desired_channel: str,
thr=-70,
maxvgap=5,
maxhgap=0,
minvlen=0,
minhlen=0,
parameters: dict = WEILL_DEFAULT_PARAMETERS,
):
"""
Detects and masks shoals following the algorithm described in:
Expand Down Expand Up @@ -77,16 +75,17 @@ def _weill(
a Dataset. This input must correspond to a Dataset that has the
coordinate ``channel`` and variables ``frequency_nominal`` and ``Sv``.
desired_channel (str): channel to generate the mask on
thr (int): Sv threshold (dB).
maxvgap (int): maximum vertical gap allowed (n samples).
maxhgap (int): maximum horizontal gap allowed (n pings).
minvlen (int): minimum vertical length for a shoal to be eligible
(n samples).
minhlen (int): minimum horizontal length for a shoal to be eligible
(n pings).
start (int): ping index to start processing. If greater than zero, it
means that Sv carries data from a preceding file and
the algorithm needs to know where to start processing.
parameters (dict): containing the required parameters
thr (int): Sv threshold (dB).
maxvgap (int): maximum vertical gap allowed (n samples).
maxhgap (int): maximum horizontal gap allowed (n pings).
minvlen (int): minimum vertical length for a shoal to be eligible
(n samples).
minhlen (int): minimum horizontal length for a shoal to be eligible
(n pings).
start (int): ping index to start processing. If greater than zero, it
means that Sv carries data from a preceding file and
the algorithm needs to know where to start processing.

Returns
-------
Expand All @@ -98,7 +97,20 @@ def _weill(
Edge regions are filled with 'False', whereas the portion in which shoals
could be detected is 'True'
"""
# Sv = source_Sv["Sv"].values[0]
parameter_names = ["thr", "maxvgap", "maxhgap", "minvlen", "minhlen"]
if not all(name in parameters.keys() for name in parameter_names):
raise ValueError(
"Missing parameters - should be: "
+ str(parameter_names)
+ ", are: "
+ str(parameters.keys())
)
thr = parameters["thr"]
maxvgap = parameters["maxvgap"]
maxhgap = parameters["maxhgap"]
minvlen = parameters["minvlen"]
minhlen = parameters["minhlen"]

channel_Sv = source_Sv.sel(channel=desired_channel)
Sv = channel_Sv["Sv"].values

Expand Down Expand Up @@ -165,5 +177,14 @@ def _weill(
mask_ = np.zeros_like(mask, dtype=bool)
mask_[minvlen : len(mask_) - minvlen, minhlen : len(mask_[0]) - minhlen] = True

# return masks, from the start ping onwards
return mask, mask_
return_mask = xr.DataArray(
mask,
dims=("ping_time", "range_sample"),
coords={"ping_time": source_Sv.ping_time, "range_sample": source_Sv.range_sample},
)
return_mask_ = xr.DataArray(
mask_,
dims=("ping_time", "range_sample"),
coords={"ping_time": source_Sv.ping_time, "range_sample": source_Sv.range_sample},
)
return return_mask, return_mask_
1 change: 0 additions & 1 deletion echopype/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,4 +129,3 @@ def complete_dataset_jr179(setup_test_data_jr179):
def raw_dataset_jr179(setup_test_data_jr179):
ed = _get_raw_dataset(setup_test_data_jr179)
return ed

5 changes: 4 additions & 1 deletion echopype/tests/mask/test_mask.py
Original file line number Diff line number Diff line change
Expand Up @@ -1017,7 +1017,9 @@ def test_channel_mask(var_name="var2"):

def test_shoal_mask_all(sv_dataset_jr161):
source_Sv = sv_dataset_jr161
ml, _ml = echopype.mask.api.get_shoal_mask_multichannel(source_Sv)
ml, _ml = echopype.mask.api.get_shoal_mask_multichannel(
source_Sv, method="will", parameters=ep.mask.shoal.WEILL_DEFAULT_PARAMETERS
)
assert np.all(ml["channel"] == source_Sv["channel"])
assert np.all(_ml["channel"] == source_Sv["channel"])

Expand All @@ -1028,3 +1030,4 @@ def test_seabed_mask_all(complete_dataset_jr179):
source_Sv, method="ariza", parameters=ep.mask.seabed.ARIZA_DEFAULT_PARAMS
)
assert np.all(ml["channel"] == source_Sv["channel"])

Loading
Loading