Skip to content
Open
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
14 changes: 12 additions & 2 deletions echopype/consolidate/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
from .api import add_depth, add_location, add_splitbeam_angle, swap_dims_channel_frequency
from .api import (
add_depth,
add_location,
add_splitbeam_angle,
swap_dims_channel_frequency,
)

__all__ = ["swap_dims_channel_frequency", "add_depth", "add_location", "add_splitbeam_angle"]
__all__ = [
"swap_dims_channel_frequency",
"add_depth",
"add_location",
"add_splitbeam_angle",
]
80 changes: 56 additions & 24 deletions echopype/consolidate/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,21 @@
from ..utils.io import get_file_format, open_source
from ..utils.log import _init_logger
from ..utils.prov import add_processing_level
from .ek_depth_utils import (
from .split_beam_angle import (
get_angle_complex_samples,
get_angle_power_samples,
)
from .utils_ek_depth import (
ek_use_beam_angles,
ek_use_platform_angles,
ek_use_platform_vertical_offsets,
)
from .loc_utils import check_and_drop_loc_time_dim_duplicates, check_loc_vars_validity, sel_nmea
from .split_beam_angle import get_angle_complex_samples, get_angle_power_samples
from .utils_loc import (
check_and_drop_loc_time_dim_duplicates,
check_loc_vars_validity,
get_dim_0,
sel_nmea,
)

logger = _init_logger(__name__)

Expand All @@ -30,7 +38,7 @@

def swap_dims_channel_frequency(ds: Union[xr.Dataset, str, pathlib.Path]) -> xr.Dataset:
"""
Use frequency_nominal in place of channel to be dataset dimension and coorindate.
Use frequency_nominal in place of channel to be dataset dimension and coordinate.

This is useful because the nominal transducer frequencies are commonly used to
refer to data collected from a specific transducer.
Expand Down Expand Up @@ -74,6 +82,8 @@ def add_depth(
use_platform_vertical_offsets: bool = False,
use_platform_angles: bool = False,
use_beam_angles: bool = False,
waveform_mode: Optional[str] = None,
encode_mode: Optional[str] = None,
) -> xr.Dataset:
"""
Create a depth data variable based on data in Sv dataset, Echodata object, and/or
Expand Down Expand Up @@ -110,6 +120,10 @@ def add_depth(
Currently only implemented for EK60/EK80 sonar models.
If `tilt` is specified, Beam group angle values will not be used.
In the current implementation cannot be used in tandem with `use_platform_angles`.
waveform_mode : Optional[str], default None
Type of transmit waveform. Must be specified when `use_beam_angles` is True.
encode_mode : Optional[str], default None
Type of encode mode. Must be specified when `use_beam_angles` is True.

Returns
-------
Expand All @@ -118,6 +132,9 @@ def add_depth(
# Open Sv dataset
ds = open_source(ds, "dataset", {})

# Check that `channel` or `frequency_nominal` is the first dimension in the Sv
_ = get_dim_0(ds["Sv"]) # return nothing since it's not used

# Raise `ValueError` if `echodata` is needed but not passed in
if (not echodata) and (use_platform_vertical_offsets or use_platform_angles or use_beam_angles):
raise ValueError(
Expand Down Expand Up @@ -160,6 +177,18 @@ def add_depth(
f"`use_platform/beam_...` not implemented yet for `{sonar_model}`."
)

# Raise error if sonar model EK80 and use_beam_angles is true but wave form and encode mode
# is not provided
if (
use_beam_angles
and sonar_model == "EK80"
and (waveform_mode is None or encode_mode is None)
):
raise ValueError(
"When `use_beam_angles` is True and sonar model is EK80, "
"both `waveform_mode` and `encode_mode` must be specified."
)

# Initialize transducer depth to 0.0 (no effect on depth)
transducer_depth = 0.0
if isinstance(depth_offset, Number):
Expand Down Expand Up @@ -205,14 +234,12 @@ def add_depth(
# Compute echo range scaling in EK systems using platform angle data
echo_range_scaling = ek_use_platform_angles(echodata["Platform"], ds["ping_time"])
elif use_beam_angles:
# Identify beam group name by checking channel values of `ds`
if echodata["Sonar/Beam_group1"]["channel"].equals(ds["channel"]):
beam_group_name = "Beam_group1"
else:
beam_group_name = "Beam_group2"
# check that the appropriate waveform and encode mode have been given
# and obtain the echodata group path corresponding to encode_mode
ed_beam_group = retrieve_correct_beam_group(echodata, waveform_mode, encode_mode)

# Compute echo range scaling in EK systems using beam angle data
echo_range_scaling = ek_use_beam_angles(echodata[f"Sonar/{beam_group_name}"])
echo_range_scaling = ek_use_beam_angles(echodata[ed_beam_group])

# Set orientation multiplier. 1 if facing downwards, -1 if facing upwards
orientation_mult = 1 if downward else -1
Expand All @@ -233,7 +260,7 @@ def add_depth(
history_attr + f" Sv `echo_range`"
f"{', Echodata `Platform` Vertical Offsets' if (used_platform_vertical_offsets) else ''}"
f"{', Echodata `Platform` Angles' if (used_platform_angles) else ''}"
f"{', Echodata `%s` Angles' % (beam_group_name) if (used_beam_angles) else ''}"
f"{', Echodata `%s` Angles' % (ed_beam_group.replace('Sonar/', '')) if (used_beam_angles) else ''}" # noqa
"."
)
ds["depth"] = ds["depth"].assign_attrs({"history": history_attr})
Expand Down Expand Up @@ -287,6 +314,9 @@ def add_location(
ds = open_source(ds, "dataset", {})
echodata = open_source(echodata, "echodata", {})

# Check that `channel` or `frequency_nominal` is the first dimension in the Sv
_ = get_dim_0(ds["Sv"]) # return nothing since it's not used

# Grab lat lon names
if echodata.sonar_model.startswith("EK") and datagram_type in ["MRU1", "IDX"]:
lat_name = f"latitude_{datagram_type.lower()}"
Expand Down Expand Up @@ -475,6 +505,12 @@ def add_splitbeam_angle(
source_Sv = open_source(source_Sv, "dataset", storage_options)
echodata = open_source(echodata, "echodata", storage_options)

# Check that `channel` or `frequency_nominal` is the first dimension in the Sv
dim_0 = get_dim_0(source_Sv["Sv"]) # return nothing since it's not used

# Grab corresponding channel values
Sv_channels = source_Sv["channel"].values

# ensure that echodata was produced by EK60 or EK80-like sensors
if echodata.sonar_model not in ["EK60", "ES70", "EK80", "ES80", "EA640"]:
raise ValueError(
Expand All @@ -490,12 +526,12 @@ def add_splitbeam_angle(
# and obtain the echodata group path corresponding to encode_mode
ed_beam_group = retrieve_correct_beam_group(echodata, waveform_mode, encode_mode)

# check that source_Sv at least has a channel dimension
if "channel" not in source_Sv.variables:
raise ValueError("The input source_Sv Dataset must have a channel dimension!")
# Select channels in selected beam group
ds_beam = echodata[ed_beam_group].sel({"channel": Sv_channels})

# Select ds_beam channels from source_Sv
ds_beam = echodata[ed_beam_group].sel(channel=source_Sv["channel"].values)
# Swap dim for ds_beam if dim_0 is frequency_nominal
if dim_0 == "frequency_nominal":
ds_beam = swap_dims_channel_frequency(ds_beam)

# Assemble angle param dict
angle_param_list = [
Expand All @@ -512,10 +548,9 @@ def add_splitbeam_angle(
raise ValueError(f"source_Sv does not contain the necessary parameter {p_name}!")

# fail if source_Sv and ds_beam do not have the same lengths
# for ping_time, range_sample, and channel
# for dim_0, ping_time, range_sample
same_size_lens = [
ds_beam.sizes[dim] == source_Sv.sizes[dim]
for dim in ["channel", "ping_time", "range_sample"]
ds_beam.sizes[dim] == source_Sv.sizes[dim] for dim in [dim_0, "ping_time", "range_sample"]
]
if not same_size_lens:
raise ValueError(
Expand All @@ -534,9 +569,7 @@ def add_splitbeam_angle(
else:
if pulse_compression: # with pulse compression
# put receiver fs into the same dict for simplicity
pc_params = get_filter_coeff(
echodata["Vendor_specific"].sel(channel=source_Sv["channel"].values)
)
pc_params = get_filter_coeff(echodata["Vendor_specific"].sel({"channel": Sv_channels}))
pc_params["receiver_sampling_frequency"] = source_Sv["receiver_sampling_frequency"]

# Add dictionary entry to keep/drop last hanning window's zero value
Expand Down Expand Up @@ -568,8 +601,7 @@ def add_splitbeam_angle(
else f"{datetime.datetime.now(datetime.UTC)}. `depth` calculated using:"
)
history_attr = (
history_attr
+ "Calculated using data stored in the Beam groups of the echodata object." # noqa
history_attr + "Calculated using data stored in the Beam groups of the echodata object."
)
for da_name in ["angle_alongship", "angle_athwartship"]:
source_Sv[da_name] = source_Sv[da_name].assign_attrs({"history": history_attr})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,32 @@
logger = _init_logger(__name__)


SUPPORTED_DIM_0_NAMES = ["channel", "frequency_nominal"]


def get_dim_0(da_Sv: xr.DataArray) -> str:
"""
Get the name of the first dimension of the Sv data array.

Parameters
----------
ds : xr.Dataset
The input dataset.

Returns
-------
str
The name of the first dimension.
"""
dim_0 = list(da_Sv.dims)[0]
if dim_0 in SUPPORTED_DIM_0_NAMES:
return dim_0
else:
raise ValueError(
f"The first dimension of the the source Sv must be one of {SUPPORTED_DIM_0_NAMES}."
)


def compute_invalid_check(lat_var: xr.DataArray, lon_var: xr.DataArray, validity_check: str):
"""Helper function to check if loc vars are invalid in 4 separate ways."""
if validity_check == "missing":
Expand Down
2 changes: 1 addition & 1 deletion echopype/tests/commongrid/test_commongrid_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -1282,7 +1282,7 @@ def test_resample_matches_echoview_match_geometry(test_path):
atol=0.003,
rtol=0,
)

@pytest.mark.integration
def test_resample_shared_depth_and_range_geometry(test_path):
"""
Expand Down
Loading
Loading