Skip to content

Commit

Permalink
Merge/add shoal mask (#88)
Browse files Browse the repository at this point in the history
* Mask utils

* rename

* Mask utils

* Weill shoal mask

* Decided against implementing the echoview method

* Turned out not to need these

* Add wrapper function

* Refactoring and adding channel selection capability

* Import fix

* ci: Bump docker/setup-qemu-action from 2 to 3 (OSOceanAcoustics#1172)

Bumps [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) from 2 to 3.
- [Release notes](https://github.com/docker/setup-qemu-action/releases)
- [Commits](docker/setup-qemu-action@v2...v3)

---
updated-dependencies:
- dependency-name: docker/setup-qemu-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* ci: Bump docker/login-action from 2 to 3 (OSOceanAcoustics#1175)

Bumps [docker/login-action](https://github.com/docker/login-action) from 2 to 3.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](docker/login-action@v2...v3)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* ci: Bump docker/build-push-action from 4 to 5 (OSOceanAcoustics#1173)

Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 4 to 5.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](docker/build-push-action@v4...v5)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* ci: Bump docker/setup-buildx-action from 2 to 3 (OSOceanAcoustics#1174)

Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 2 to 3.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](docker/setup-buildx-action@v2...v3)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Delinting

* Requirements consistency

* Refactoring

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
  • Loading branch information
ruxandra-valcu and dependabot[bot] authored Oct 26, 2023
1 parent c3e532b commit a8d6354
Show file tree
Hide file tree
Showing 7 changed files with 170 additions and 139 deletions.
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

0 comments on commit a8d6354

Please sign in to comment.