From 471f6112683f897bc93a5c7a610192b3a6a97a7a Mon Sep 17 00:00:00 2001 From: Praneeth Date: Sat, 17 May 2025 14:01:39 +0530 Subject: [PATCH 1/3] enh: add support for use frequency_nominal/channel as dimension --- echopype/consolidate/api.py | 33 ++++++++++++++++-------- echopype/consolidate/split_beam_angle.py | 11 +++++--- 2 files changed, 29 insertions(+), 15 deletions(-) diff --git a/echopype/consolidate/api.py b/echopype/consolidate/api.py index cf5f2c50a..ea9d15fe5 100644 --- a/echopype/consolidate/api.py +++ b/echopype/consolidate/api.py @@ -30,7 +30,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. @@ -349,6 +349,7 @@ def add_splitbeam_angle( pulse_compression: bool = False, storage_options: dict = {}, to_disk: bool = True, + use_frequency_nominal: bool = False, ) -> xr.Dataset: """ Add split-beam (alongship/athwartship) angles into the Sv dataset. @@ -390,6 +391,9 @@ def add_splitbeam_angle( If ``False``, ``to_disk`` with split-beam angles added will be returned. ``to_disk=True`` is useful when ``source_Sv`` is a path and users only want to write the split-beam angle data to this path. + use_frequency_nominal: bool, default=False + If ``True``, the split-beam angles will be calculated using the + ``frequency_nominal`` variable in ``source_Sv``. Returns ------- @@ -455,12 +459,17 @@ 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!") + # Set the dataset_dimension based on use_frequency_nominal. + dataset_dimension = "frequency_nominal" if use_frequency_nominal else "channel" + if dataset_dimension not in source_Sv.variables: + raise ValueError(f"The input source_Sv Dataset must have a {dataset_dimension} dimension!") - # Select ds_beam channels from source_Sv - ds_beam = echodata[ed_beam_group].sel(channel=source_Sv["channel"].values) + # Swap the dimension to frequency_nominal if use_frequency_nominal is True + if use_frequency_nominal: + source_Sv = swap_dims_channel_frequency(source_Sv) + + # Select ds_beam dimension from source_Sv + ds_beam = echodata[ed_beam_group].sel(channel=source_Sv[dataset_dimension].values) # Assemble angle param dict angle_param_list = [ @@ -480,7 +489,7 @@ def add_splitbeam_angle( # for ping_time, range_sample, and channel same_size_lens = [ ds_beam.sizes[dim] == source_Sv.sizes[dim] - for dim in ["channel", "ping_time", "range_sample"] + for dim in [dataset_dimension, "ping_time", "range_sample"] ] if not same_size_lens: raise ValueError( @@ -494,19 +503,21 @@ def add_splitbeam_angle( theta, phi = get_angle_power_samples(ds_beam, angle_params) else: # complex data # operation is identical with BB complex data - theta, phi = get_angle_complex_samples(ds_beam, angle_params) + theta, phi = get_angle_complex_samples(ds_beam, angle_params, dataset_dimension) # BB mode data 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) + echodata["Vendor_specific"].sel(channel=source_Sv[dataset_dimension].values) ) pc_params["receiver_sampling_frequency"] = source_Sv["receiver_sampling_frequency"] - theta, phi = get_angle_complex_samples(ds_beam, angle_params, pc_params) + theta, phi = get_angle_complex_samples( + ds_beam, angle_params, pc_params, dataset_dimension + ) else: # without pulse compression # operation is identical with CW complex data - theta, phi = get_angle_complex_samples(ds_beam, angle_params) + theta, phi = get_angle_complex_samples(ds_beam, angle_params, dataset_dimension) # add theta and phi to source_Sv input theta.attrs["long_name"] = "split-beam alongship angle" diff --git a/echopype/consolidate/split_beam_angle.py b/echopype/consolidate/split_beam_angle.py index a269d70c4..be10d0298 100644 --- a/echopype/consolidate/split_beam_angle.py +++ b/echopype/consolidate/split_beam_angle.py @@ -151,7 +151,10 @@ def _e2f(angle_type: str) -> xr.Dataset: def get_angle_complex_samples( - ds_beam: xr.Dataset, angle_params: dict, pc_params: dict = None + ds_beam: xr.Dataset, + angle_params: dict, + pc_params: dict = None, + dataset_dimension: str = "channel", ) -> Tuple[xr.DataArray, xr.DataArray]: """ Obtain split-beam angle from CW or BB mode complex samples. @@ -210,7 +213,7 @@ def get_angle_complex_samples( else: # beam_type different for some channels, process each channel separately theta, phi = [], [] - for ch_id in bs["channel"].data: + for ch_id in bs[dataset_dimension].data: theta_ch, phi_ch = _compute_angle_from_complex( bs=bs.sel(channel=ch_id), # beam_type is not time-varying @@ -231,7 +234,7 @@ def get_angle_complex_samples( theta = xr.DataArray( data=theta, coords={ - "channel": bs["channel"], + dataset_dimension: bs[dataset_dimension], "ping_time": bs["ping_time"], "range_sample": bs["range_sample"], }, @@ -239,7 +242,7 @@ def get_angle_complex_samples( phi = xr.DataArray( data=phi, coords={ - "channel": bs["channel"], + dataset_dimension: bs[dataset_dimension], "ping_time": bs["ping_time"], "range_sample": bs["range_sample"], }, From bb128fd3ebb6006fbf3b171708c8de00bddb43b6 Mon Sep 17 00:00:00 2001 From: Praneeth Date: Sat, 17 May 2025 14:42:06 +0530 Subject: [PATCH 2/3] fix tests --- echopype/consolidate/api.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/echopype/consolidate/api.py b/echopype/consolidate/api.py index ea9d15fe5..e4fdd89d7 100644 --- a/echopype/consolidate/api.py +++ b/echopype/consolidate/api.py @@ -503,7 +503,9 @@ def add_splitbeam_angle( theta, phi = get_angle_power_samples(ds_beam, angle_params) else: # complex data # operation is identical with BB complex data - theta, phi = get_angle_complex_samples(ds_beam, angle_params, dataset_dimension) + theta, phi = get_angle_complex_samples( + ds_beam, angle_params, dataset_dimension=dataset_dimension + ) # BB mode data else: if pulse_compression: # with pulse compression @@ -517,7 +519,9 @@ def add_splitbeam_angle( ) else: # without pulse compression # operation is identical with CW complex data - theta, phi = get_angle_complex_samples(ds_beam, angle_params, dataset_dimension) + theta, phi = get_angle_complex_samples( + ds_beam, angle_params, dataset_dimension=dataset_dimension + ) # add theta and phi to source_Sv input theta.attrs["long_name"] = "split-beam alongship angle" From cb17006858e290a5e7ce22cafd2126e6275891d9 Mon Sep 17 00:00:00 2001 From: Praneeth Date: Sat, 17 May 2025 23:38:33 +0530 Subject: [PATCH 3/3] minor fix --- echopype/consolidate/api.py | 25 +++++++++--------------- echopype/consolidate/split_beam_angle.py | 7 +++---- echopype/test_data/README.md | 3 --- 3 files changed, 12 insertions(+), 23 deletions(-) diff --git a/echopype/consolidate/api.py b/echopype/consolidate/api.py index e4fdd89d7..2388b0ead 100644 --- a/echopype/consolidate/api.py +++ b/echopype/consolidate/api.py @@ -459,17 +459,16 @@ 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) - # Set the dataset_dimension based on use_frequency_nominal. - dataset_dimension = "frequency_nominal" if use_frequency_nominal else "channel" - if dataset_dimension not in source_Sv.variables: - raise ValueError(f"The input source_Sv Dataset must have a {dataset_dimension} dimension!") + # 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!") # Swap the dimension to frequency_nominal if use_frequency_nominal is True if use_frequency_nominal: source_Sv = swap_dims_channel_frequency(source_Sv) # Select ds_beam dimension from source_Sv - ds_beam = echodata[ed_beam_group].sel(channel=source_Sv[dataset_dimension].values) + ds_beam = echodata[ed_beam_group].sel(channel=source_Sv["channel"].values) # Assemble angle param dict angle_param_list = [ @@ -489,7 +488,7 @@ def add_splitbeam_angle( # for ping_time, range_sample, and channel same_size_lens = [ ds_beam.sizes[dim] == source_Sv.sizes[dim] - for dim in [dataset_dimension, "ping_time", "range_sample"] + for dim in ["channel", "ping_time", "range_sample"] ] if not same_size_lens: raise ValueError( @@ -503,25 +502,19 @@ def add_splitbeam_angle( theta, phi = get_angle_power_samples(ds_beam, angle_params) else: # complex data # operation is identical with BB complex data - theta, phi = get_angle_complex_samples( - ds_beam, angle_params, dataset_dimension=dataset_dimension - ) + theta, phi = get_angle_complex_samples(ds_beam, angle_params) # BB mode data 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[dataset_dimension].values) + echodata["Vendor_specific"].sel(channel=source_Sv["channel"].values) ) pc_params["receiver_sampling_frequency"] = source_Sv["receiver_sampling_frequency"] - theta, phi = get_angle_complex_samples( - ds_beam, angle_params, pc_params, dataset_dimension - ) + theta, phi = get_angle_complex_samples(ds_beam, angle_params, pc_params) else: # without pulse compression # operation is identical with CW complex data - theta, phi = get_angle_complex_samples( - ds_beam, angle_params, dataset_dimension=dataset_dimension - ) + theta, phi = get_angle_complex_samples(ds_beam, angle_params) # add theta and phi to source_Sv input theta.attrs["long_name"] = "split-beam alongship angle" diff --git a/echopype/consolidate/split_beam_angle.py b/echopype/consolidate/split_beam_angle.py index be10d0298..5e68d49a4 100644 --- a/echopype/consolidate/split_beam_angle.py +++ b/echopype/consolidate/split_beam_angle.py @@ -154,7 +154,6 @@ def get_angle_complex_samples( ds_beam: xr.Dataset, angle_params: dict, pc_params: dict = None, - dataset_dimension: str = "channel", ) -> Tuple[xr.DataArray, xr.DataArray]: """ Obtain split-beam angle from CW or BB mode complex samples. @@ -213,7 +212,7 @@ def get_angle_complex_samples( else: # beam_type different for some channels, process each channel separately theta, phi = [], [] - for ch_id in bs[dataset_dimension].data: + for ch_id in bs["channel"].data: theta_ch, phi_ch = _compute_angle_from_complex( bs=bs.sel(channel=ch_id), # beam_type is not time-varying @@ -234,7 +233,7 @@ def get_angle_complex_samples( theta = xr.DataArray( data=theta, coords={ - dataset_dimension: bs[dataset_dimension], + "channel": bs["channel"], "ping_time": bs["ping_time"], "range_sample": bs["range_sample"], }, @@ -242,7 +241,7 @@ def get_angle_complex_samples( phi = xr.DataArray( data=phi, coords={ - dataset_dimension: bs[dataset_dimension], + "channel": bs["channel"], "ping_time": bs["ping_time"], "range_sample": bs["range_sample"], }, diff --git a/echopype/test_data/README.md b/echopype/test_data/README.md index d3295604e..c79ad71f3 100644 --- a/echopype/test_data/README.md +++ b/echopype/test_data/README.md @@ -11,8 +11,6 @@ Most of these files are stored on Git LFS but the ones that aren't (due to file - 2019118 group2survey-D20191214-T081342.raw: Contains 6 channels but only 2 of those channels collect ping data - D20200528-T125932.raw: Data collected from WBT mini (instead of WBT), from @emlynjdavies - Green2.Survey2.FM.short.slow.-D20191004-T211557.raw: Contains 2-in-1 transducer, from @FletcherFT (reduced from 104.9 MB to 765 KB in test data updates) -- raw4-D20220514-T172704.raw: Contains RAW4 datagram, 1 channel only, from @cornejotux -- D20210330-T123857.raw: do not contain filter coefficients ### EA640 @@ -24,7 +22,6 @@ Most of these files are stored on Git LFS but the ones that aren't (due to file - Winter2017-D20170115-T150122.raw: Contains a change of recording length in the middle of the file - 2015843-D20151023-T190636.raw: Not used in tests but contains ranges are not constant across ping times - SH1701_consecutive_files_w_range_change: Not used in tests. [Folder](https://drive.google.com/drive/u/1/folders/1PaDtL-xnG5EK3N3P1kGlXa5ub16Yic0f) on shared drive that contains sequential files with ranges that are not constant across ping times. -- NBP_B050N-D20180118-T090228.raw: split-beam setup without angle data ### AZFP