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

Ensure env_params intake for calibration #985

Merged
merged 35 commits into from
Mar 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
60346b3
add get_env_params_EK60 to CalibrateEK60.__init__, make sure Calibrat…
leewujung Mar 14, 2023
33307d4
add a narrow integration test to ensure env_params intake for Calibra…
leewujung Mar 14, 2023
634ea09
add a narrow integration test to ensure env_params intake for Calibra…
leewujung Mar 14, 2023
acc2aac
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Mar 14, 2023
15b9427
add sanitize_user_env_dict, revise get_env_params_AZFP to use new func
leewujung Mar 14, 2023
ce39657
revise get_env_params_EK60, add sound speed and absorption formula so…
leewujung Mar 14, 2023
e9be4aa
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Mar 14, 2023
0152581
fix EK60 env params intake and tests
leewujung Mar 14, 2023
616b56f
Merge branch 'fix-ek60-env' of https://github.com/leewujung/echopype …
leewujung Mar 14, 2023
8e1b5a2
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Mar 14, 2023
934b32e
use get_env_params_EK for EK60 intake, add EK60 integration test with…
leewujung Mar 15, 2023
9c03464
use get_env_params_EK in CalibrateEK80, add test_env_params_intake_EK…
leewujung Mar 15, 2023
ea6c58c
add test_env_params_intake_EK80_with_input, fix condition to use valu…
leewujung Mar 15, 2023
2e13f52
add sound speed to FG absorption input in get_env_params_EK, shorten …
leewujung Mar 15, 2023
06ccd9e
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Mar 15, 2023
cb3602b
fix pre-commit errors
leewujung Mar 15, 2023
fd23ce1
change to include pH in terms of whether to recompute sound speed/abs…
leewujung Mar 15, 2023
48aac37
remove get_env_params_EK80 completely from codebase
leewujung Mar 15, 2023
0dae327
add test_sanitize_user_env_dict, fix typing for sanitize_user_*_dict
leewujung Mar 15, 2023
072bd60
add test_get_env_params_AZFP
leewujung Mar 15, 2023
789f576
add test_get_env_params_EK60_calculate/from_data
leewujung Mar 16, 2023
7cf2b35
add partial test_get_env_params_EK80_from_data
leewujung Mar 16, 2023
2a6ee93
fix test_get_env_params_EK80_from_data, revise get_env_params_EK comm…
leewujung Mar 16, 2023
cf58b70
Merge branch 'dev' into fix-ek60-env
leewujung Mar 16, 2023
5ef97c2
Apply suggestions from code review
leewujung Mar 18, 2023
ef099c5
revert wrong suggestion
leewujung Mar 18, 2023
e3d7c20
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Mar 18, 2023
11a08f9
change ed_group to ed_beam_group
leewujung Mar 18, 2023
b5b8043
fix accidental bug in reverted change
leewujung Mar 18, 2023
d9be8b2
fix azfp tests involving mean temperature
leewujung Mar 18, 2023
7aed2ca
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Mar 18, 2023
7e3d683
remove redundancy in passing in extra ed_beam_group in _cal_power_sam…
leewujung Mar 18, 2023
31ec336
Merge branch 'fix-ek60-env' of https://github.com/leewujung/echopype …
leewujung Mar 18, 2023
e132a96
add missed test cases in test_sanitize_user_env_dict
leewujung Mar 18, 2023
ad0551c
change list to List in typing
leewujung Mar 18, 2023
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: 2 additions & 6 deletions echopype/calibrate/cal_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,6 @@
}


# TODO: need a function (something like "_check_param_freq_dep")
# to check user input cal_params and env_params


def param2da(p_val: Union[int, float, list], channel: Union[list, xr.DataArray]) -> xr.DataArray:
"""
Organize individual parameter in scalar or list to xr.DataArray with channel coordinate.
Expand Down Expand Up @@ -88,7 +84,7 @@ def param2da(p_val: Union[int, float, list], channel: Union[list, xr.DataArray])

def sanitize_user_cal_dict(
sonar_type: Literal["EK60", "EK80", "AZFP"],
user_dict: Dict[str, Union[int, float, xr.DataArray]],
user_dict: Dict[str, Union[int, float, list, xr.DataArray]],
channel: Union[List, xr.DataArray],
) -> Dict[str, Union[int, float, xr.DataArray]]:
"""
Expand Down Expand Up @@ -126,7 +122,7 @@ def sanitize_user_cal_dict(

# Screen parameters: only retain those defined in CAL_PARAMS
# -- transform params in scalar or list to xr.DataArray
# -- directly pass through those that are xr.DataArray
# -- directly pass through those that are xr.DataArray and pass the check for coordinates
out_dict = dict.fromkeys(CAL_PARAMS[sonar_type])
for p_name, p_val in user_dict.items():
if p_name in out_dict:
Expand Down
2 changes: 1 addition & 1 deletion echopype/calibrate/calibrate_azfp.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ def __init__(self, echodata: EchoData, env_params=None, cal_params=None, **kwarg
self.sonar_type = "AZFP"

# load env and cal parameters
self.env_params = get_env_params_AZFP(echodata=self.echodata, user_env_dict=self.env_params)
self.env_params = get_env_params_AZFP(echodata=self.echodata, user_dict=self.env_params)
self.cal_params = get_cal_params_AZFP(
beam=self.echodata["Sonar/Beam_group1"],
vend=self.echodata["Vendor_specific"],
Expand Down
76 changes: 41 additions & 35 deletions echopype/calibrate/calibrate_ek.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from .cal_params import get_cal_params_EK
from .calibrate_base import CalibrateBase
from .ek80_complex import compress_pulse, get_filter_coeff, get_tau_effective, get_transmit_signal
from .env_params import get_env_params_EK60, get_env_params_EK80
from .env_params import get_env_params_EK
from .range import compute_range_EK, range_mod_TVG_EK

logger = _init_logger(__name__)
Expand All @@ -19,6 +19,8 @@ class CalibrateEK(CalibrateBase):
def __init__(self, echodata: EchoData, env_params, cal_params):
super().__init__(echodata, env_params, cal_params)

self.ed_beam_group = None # will be assigned in child class

def compute_echo_range(self, chan_sel: xr.DataArray = None):
"""
Compute echo range for EK echosounders.
Expand All @@ -36,24 +38,22 @@ def compute_echo_range(self, chan_sel: xr.DataArray = None):
chan_sel=chan_sel,
)

def _cal_power_samples(self, cal_type: str, power_ed_group: str = None) -> xr.Dataset:
def _cal_power_samples(self, cal_type: str) -> xr.Dataset:
"""Calibrate power data from EK60 and EK80.

Parameters
----------
cal_type: str
'Sv' for calculating volume backscattering strength, or
'TS' for calculating target strength
power_ed_group:
The ``EchoData`` beam group path containing the power data

Returns
-------
xr.Dataset
The calibrated dataset containing Sv or TS
"""
# Select source of backscatter data
beam = self.echodata[power_ed_group]
beam = self.echodata[self.ed_beam_group]

# Derived params
wavelength = self.env_params["sound_speed"] / beam["frequency_nominal"] # wavelength
Expand All @@ -63,7 +63,7 @@ def _cal_power_samples(self, cal_type: str, power_ed_group: str = None) -> xr.Da
sound_speed = self.env_params["sound_speed"]
absorption = self.env_params["sound_absorption"]
tvg_mod_range = range_mod_TVG_EK(
self.echodata, self.ed_group, self.range_meter, sound_speed
self.echodata, self.ed_beam_group, self.range_meter, sound_speed
)
tvg_mod_range = tvg_mod_range.where(tvg_mod_range > 0, np.nan)

Expand Down Expand Up @@ -130,26 +130,31 @@ class CalibrateEK60(CalibrateEK):
def __init__(self, echodata, env_params, cal_params, **kwargs):
super().__init__(echodata, env_params, cal_params)

# Set sonar_type
# Set sonar_type and waveform/encode mode
self.sonar_type = "EK60"
emiliom marked this conversation as resolved.
Show resolved Hide resolved

# Get env_params
self.env_params = get_env_params_EK60(echodata=echodata, user_env_dict=self.env_params)
self.waveform_mode = "CW"
self.encode_mode = "power"

# Compute range
self.compute_echo_range()

# Get the right ed_group for CW power samples
self.ed_group = retrieve_correct_beam_group(
# Get the right ed_beam_group for CW power samples
self.ed_beam_group = retrieve_correct_beam_group(
echodata=self.echodata, waveform_mode=self.waveform_mode, encode_mode=self.encode_mode
)

# Get env_params
self.env_params = get_env_params_EK(
sonar_type=self.sonar_type,
beam=self.echodata[self.ed_beam_group],
env=self.echodata["Environment"],
user_dict=self.env_params,
)

# Compute range
self.compute_echo_range()

# Set the channels to calibrate
# For EK60 this is all channels
self.chan_sel = self.echodata[self.ed_group]["channel"]
beam = self.echodata[self.ed_group]
self.chan_sel = self.echodata[self.ed_beam_group]["channel"]
beam = self.echodata[self.ed_beam_group]

# Get cal_params
self.cal_params = get_cal_params_EK(
Expand All @@ -162,10 +167,10 @@ def __init__(self, echodata, env_params, cal_params, **kwargs):
)

def compute_Sv(self, **kwargs):
return self._cal_power_samples(cal_type="Sv", power_ed_group=self.ed_group)
return self._cal_power_samples(cal_type="Sv")

def compute_TS(self, **kwargs):
return self._cal_power_samples(cal_type="TS", power_ed_group=self.ed_group)
return self._cal_power_samples(cal_type="TS")


class CalibrateEK80(CalibrateEK):
Expand Down Expand Up @@ -197,23 +202,23 @@ def __init__(self, echodata, env_params, cal_params, waveform_mode, encode_mode)
self.encode_mode = encode_mode
self.echodata = echodata

# Get the right ed_group given waveform and encode mode
self.ed_group = retrieve_correct_beam_group(
# Get the right ed_beam_group given waveform and encode mode
self.ed_beam_group = retrieve_correct_beam_group(
echodata=self.echodata, waveform_mode=self.waveform_mode, encode_mode=self.encode_mode
)

# Select the channels to calibrate
if self.encode_mode == "power":
# Power sample only possible under CW mode,
# and all power samples will live in the same group
self.chan_sel = self.echodata[self.ed_group]["channel"]
self.chan_sel = self.echodata[self.ed_beam_group]["channel"]
else:
# Complex samples can be CW or BB, so select based on waveform mode
chan_dict = self._get_chan_dict(self.echodata[self.ed_group])
chan_dict = self._get_chan_dict(self.echodata[self.ed_beam_group])
self.chan_sel = chan_dict[self.waveform_mode]

# Subset of the right Sonar/Beam_groupX group given the selected channels
beam = self.echodata[self.ed_group].sel(channel=self.chan_sel)
beam = self.echodata[self.ed_beam_group].sel(channel=self.chan_sel)

# Use center frequency if in BB mode, else use nominal channel frequency
if self.waveform_mode == "BB":
Expand All @@ -226,11 +231,12 @@ def __init__(self, echodata, env_params, cal_params, waveform_mode, encode_mode)
self.freq_center = beam["frequency_nominal"].sel(channel=self.chan_sel)

# Get env_params: depends on waveform mode
self.env_params = get_env_params_EK80(
echodata=echodata,
self.env_params = get_env_params_EK(
sonar_type=self.sonar_type,
beam=beam,
env=self.echodata["Environment"],
user_dict=self.env_params,
freq=self.freq_center,
user_env_dict=env_params,
ed_group=self.ed_group,
)

# Get cal_params: depends on waveform and encode mode
Expand Down Expand Up @@ -333,24 +339,22 @@ def _get_B_theta_phi_m(self):

return B_theta_phi_m

def _cal_complex_samples(self, cal_type: str, complex_ed_group: str) -> xr.Dataset:
def _cal_complex_samples(self, cal_type: str) -> xr.Dataset:
"""Calibrate complex data from EK80.

Parameters
----------
cal_type : str
'Sv' for calculating volume backscattering strength, or
'TS' for calculating target strength
complex_ed_group : str
The ``EchoData`` beam group path containing complex data

Returns
-------
xr.Dataset
The calibrated dataset containing Sv or TS
"""
# Select source of backscatter data
beam = self.echodata[complex_ed_group].sel(channel=self.chan_sel)
beam = self.echodata[self.ed_beam_group].sel(channel=self.chan_sel)
vend = self.echodata["Vendor_specific"].sel(channel=self.chan_sel)

# Get transmit signal
Expand All @@ -376,7 +380,9 @@ def _cal_complex_samples(self, cal_type: str, complex_ed_group: str) -> xr.Datas
transmit_power = beam["transmit_power"]

# TVG compensation with modified range
tvg_mod_range = range_mod_TVG_EK(self.echodata, self.ed_group, range_meter, sound_speed)
tvg_mod_range = range_mod_TVG_EK(
self.echodata, self.ed_beam_group, range_meter, sound_speed
)
tvg_mod_range = tvg_mod_range.where(tvg_mod_range > 0, np.nan)

spreading_loss = 20 * np.log10(tvg_mod_range)
Expand Down Expand Up @@ -476,10 +482,10 @@ def _compute_cal(self, cal_type) -> xr.Dataset:

if flag_complex:
# Complex samples can be BB or CW
ds_cal = self._cal_complex_samples(cal_type=cal_type, complex_ed_group=self.ed_group)
ds_cal = self._cal_complex_samples(cal_type=cal_type)
else:
# Power samples only make sense for CW mode data
ds_cal = self._cal_power_samples(cal_type=cal_type, power_ed_group=self.ed_group)
ds_cal = self._cal_power_samples(cal_type=cal_type)

return ds_cal

Expand Down
Loading