Skip to content

Commit

Permalink
Add attributes for Sp and MVBS from index binning (#615)
Browse files Browse the repository at this point in the history
* Sp attributes and simplified _compute_cal attr assignment

* Add attributes for Sp and MVBS from index binning

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Correct _set_MVBS spelling error

* Change Sp to TS, first steps

* Replace Sp with TS. Update dB reference levels and symbology. Add MVBS_index_binning cell_methods and other attributes

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Add water_level to compute_TS calibration

* Add #noqa to a couple of long lines

* mispelled noqa

* add TS details in docstring

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: leewujung <[email protected]>
  • Loading branch information
3 people authored Apr 14, 2022
1 parent 5033dc7 commit a33edb7
Show file tree
Hide file tree
Showing 9 changed files with 156 additions and 116 deletions.
4 changes: 2 additions & 2 deletions echopype/calibrate/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
from .api import compute_Sp, compute_Sv
from .api import compute_Sv, compute_TS

__all__ = ["compute_Sv", "compute_Sp"]
__all__ = ["compute_Sv", "compute_TS"]
63 changes: 40 additions & 23 deletions echopype/calibrate/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,30 +49,37 @@ def _compute_cal(
waveform_mode=waveform_mode,
encode_mode=encode_mode,
)
# Perform calibration
if cal_type == "Sv":
sv_dataset = cal_obj.compute_Sv(waveform_mode=waveform_mode, encode_mode=encode_mode)
# Add attributes
sv_dataset["Sv"].attrs = {
"long_name": "Volume backscattering strength (Sv)",
"units": "dB re 1 m-1",

def add_attrs(cal_type, ds):
"""Add attributes to backscattering strength dataset.
cal_type: Sv or TS
"""
ds["range_sample"].attrs = {"long_name": "Along-range sample number, base 0"}
ds["echo_range"].attrs = {"long_name": "Range distance", "units": "m"}
ds[cal_type].attrs = {
"long_name": {
"Sv": "Volume backscattering strength (Sv re 1 m-1)",
"TS": "Target strength (TS re 1 m^2)",
}[cal_type],
"units": "dB",
"actual_range": [
round(float(sv_dataset["Sv"].min().values), 2),
round(float(sv_dataset["Sv"].max().values), 2),
round(float(ds[cal_type].min().values), 2),
round(float(ds[cal_type].max().values), 2),
],
}
sv_dataset["range_sample"].attrs = {"long_name": "Along-range bin (sample) number, base 0"}
sv_dataset["echo_range"].attrs = {"long_name": "Range distance", "units": "m"}

if "water_level" in echodata.platform.data_vars.keys():
# add water_level to the created xr.Dataset
sv_dataset["water_level"] = echodata.platform.water_level

return sv_dataset
# Perform calibration
if cal_type == "Sv":
cal_ds = cal_obj.compute_Sv(waveform_mode=waveform_mode, encode_mode=encode_mode)
else:
sp_dataset = cal_obj.compute_Sp(waveform_mode=waveform_mode, encode_mode=encode_mode)
cal_ds = cal_obj.compute_TS(waveform_mode=waveform_mode, encode_mode=encode_mode)

add_attrs(cal_type, cal_ds)
if "water_level" in echodata.platform.data_vars.keys():
# add water_level to the created xr.Dataset
cal_ds["water_level"] = echodata.platform.water_level

return sp_dataset
return cal_ds


def compute_Sv(echodata: EchoData, **kwargs) -> xr.Dataset:
Expand Down Expand Up @@ -164,9 +171,9 @@ def compute_Sv(echodata: EchoData, **kwargs) -> xr.Dataset:
return _compute_cal(cal_type="Sv", echodata=echodata, **kwargs)


def compute_Sp(echodata: EchoData, **kwargs):
def compute_TS(echodata: EchoData, **kwargs):
"""
Compute point backscattering strength (Sp) from raw data.
Compute target strength (TS) from raw data.
The calibration routine varies depending on the sonar type.
Currently this operation is supported for the following ``sonar_model``:
Expand Down Expand Up @@ -227,7 +234,7 @@ def compute_Sp(echodata: EchoData, **kwargs):
Returns
-------
xr.Dataset
The calibrated Sp dataset, including calibration parameters
The calibrated TS dataset, including calibration parameters
and environmental variables used in the calibration operations.
Notes
Expand All @@ -243,7 +250,17 @@ def compute_Sp(echodata: EchoData, **kwargs):
similar to those recorded by EK60 echosounders.
The current calibration implemented for EK80 broadband complex data
uses band-integrated Sp with the gain computed at the center frequency
uses band-integrated TS with the gain computed at the center frequency
of the transmit signal.
Note that in the fisheries acoustics context, it is customary to
associate TS to a single scatterer.
TS is defined as: TS = 10 * np.log10 (sigma_bs), where sigma_bs
is the backscattering cross-section.
For details, see:
MacLennan et al. 2002. A consistent approach to definitions and
symbols in fisheries acoustics. ICES J. Mar. Sci. 59: 365-369.
https://doi.org/10.1006/jmsc.2001.1158
"""
return _compute_cal(cal_type="Sp", echodata=echodata, **kwargs)
return _compute_cal(cal_type="TS", echodata=echodata, **kwargs)
16 changes: 8 additions & 8 deletions echopype/calibrate/calibrate_azfp.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def __init__(self, echodata, env_params, cal_params, **kwargs):
self.get_cal_params(cal_params)

# self.range_meter computed under self._cal_power()
# because the implementation is different for Sv and Sp
# because the implementation is different for Sv and TS

def get_cal_params(self, cal_params):
"""Get cal params using user inputs or values from data file.
Expand Down Expand Up @@ -72,13 +72,13 @@ def get_env_params(self):
def compute_range_meter(self, cal_type):
"""Calculate range (``echo_range``) in meter using AZFP formula.
Note the range calculation differs for Sv and Sp per AZFP matlab code.
Note the range calculation differs for Sv and TS per AZFP matlab code.
Parameters
----------
cal_type : str
'Sv' for calculating volume backscattering strength, or
'Sp' for calculating point backscattering strength
'TS' for calculating target strength
"""
self.range_meter = self.echodata.compute_range(self.env_params, azfp_cal_type=cal_type)

Expand All @@ -94,7 +94,7 @@ def _cal_power(self, cal_type, **kwargs):
# Compute range in meters
self.compute_range_meter(
cal_type=cal_type
) # range computation different for Sv and Sp per AZFP matlab code
) # range computation different for Sv and TS per AZFP matlab code

# Compute various params

Expand Down Expand Up @@ -127,10 +127,10 @@ def _cal_power(self, cal_type, **kwargs):
) # see p.90-91 for this correction to Sv
out.name = "Sv"

elif cal_type == "Sp":
elif cal_type == "TS":
# eq.(10)
out = EL - SL + 2 * spreading_loss + absorption_loss
out.name = "Sp"
out.name = "TS"
else:
raise ValueError("cal_type not recognized!")

Expand All @@ -146,5 +146,5 @@ def _cal_power(self, cal_type, **kwargs):
def compute_Sv(self, **kwargs):
return self._cal_power(cal_type="Sv")

def compute_Sp(self, **kwargs):
return self._cal_power(cal_type="Sp")
def compute_TS(self, **kwargs):
return self._cal_power(cal_type="TS")
8 changes: 4 additions & 4 deletions echopype/calibrate/calibrate_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ def __init__(
Class to hold and interpolate external environmental data for calibration purposes.
This class can be used as the `env_params` parameter in `echopype.calibrate.compute_Sv`
or `echopype.calibrate.compute_Sp`. It is intended to be used with environmental parameters
or `echopype.calibrate.compute_TS`. It is intended to be used with environmental parameters
indexed by time. Environmental parameters will be interpolated onto dimensions within
the Platform group of the `EchoData` object being used for calibration.
Expand Down Expand Up @@ -213,7 +213,7 @@ def __init__(self, echodata, env_params=None):
self.env_params = env_params # env_params are set in child class
self.cal_params = None # cal_params are set in child class

# range_meter is computed in compute_Sv/Sp in child class
# range_meter is computed in compute_Sv/TS in child class
self.range_meter = None

@abc.abstractmethod
Expand Down Expand Up @@ -243,7 +243,7 @@ def _cal_power(self, cal_type, **kwargs):
----------
cal_type : str
'Sv' for calculating volume backscattering strength, or
'Sp' for calculating point backscattering strength
'TS' for calculating target strength
"""
pass

Expand All @@ -252,7 +252,7 @@ def compute_Sv(self, **kwargs):
pass

@abc.abstractmethod
def compute_Sp(self, **kwargs):
def compute_TS(self, **kwargs):
pass

def _add_params_to_output(self, ds_out):
Expand Down
38 changes: 19 additions & 19 deletions echopype/calibrate/calibrate_ek.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ def _cal_power(self, cal_type, use_beam_power=False) -> xr.Dataset:
----------
cal_type : str
'Sv' for calculating volume backscattering strength, or
'Sp' for calculating point backscattering strength
'TS' for calculating target strength
use_beam_power : bool
whether to use beam_power.
If ``True`` use ``echodata.beam_power``; if ``False`` use ``echodata.beam``.
Expand All @@ -159,7 +159,7 @@ def _cal_power(self, cal_type, use_beam_power=False) -> xr.Dataset:
Returns
-------
xr.Dataset
The calibrated dataset containing Sv or Sp
The calibrated dataset containing Sv or TS
"""
# Select source of backscatter data
if use_beam_power:
Expand Down Expand Up @@ -200,7 +200,7 @@ def _cal_power(self, cal_type, use_beam_power=False) -> xr.Dataset:
)
out.name = "Sv"

elif cal_type == "Sp":
elif cal_type == "TS":
# Calc gain
CSp = (
10 * np.log10(beam["transmit_power"])
Expand All @@ -210,7 +210,7 @@ def _cal_power(self, cal_type, use_beam_power=False) -> xr.Dataset:

# Calibration and echo integration
out = beam["backscatter_r"] + spreading_loss * 2 + absorption_loss - CSp
out.name = "Sp"
out.name = "TS"

# Attach calculated range (with units meter) into data set
out = out.to_dataset()
Expand Down Expand Up @@ -279,8 +279,8 @@ def get_env_params(self, **kwargs):
def compute_Sv(self, **kwargs):
return self._cal_power(cal_type="Sv")

def compute_Sp(self, **kwargs):
return self._cal_power(cal_type="Sp")
def compute_TS(self, **kwargs):
return self._cal_power(cal_type="TS")


class CalibrateEK80(CalibrateEK):
Expand Down Expand Up @@ -663,7 +663,7 @@ def _cal_complex(self, cal_type, waveform_mode) -> xr.Dataset:
----------
cal_type : str
'Sv' for calculating volume backscattering strength, or
'Sp' for calculating point backscattering strength
'TS' for calculating target strength
waveform_mode : {"CW", "BB"}
Type of transmit waveform.
Expand All @@ -675,7 +675,7 @@ def _cal_complex(self, cal_type, waveform_mode) -> xr.Dataset:
Returns
-------
xr.Dataset
The calibrated dataset containing Sv or Sp
The calibrated dataset containing Sv or TS
"""
# Transmit replica and effective pulse length
chirp, _, tau_effective = self.get_transmit_chirp(waveform_mode=waveform_mode)
Expand Down Expand Up @@ -753,7 +753,7 @@ def _cal_complex(self, cal_type, waveform_mode) -> xr.Dataset:
spreading_loss = 20 * np.log10(range_meter.where(range_meter >= 1, other=1))
absorption_loss = 2 * absorption * range_meter

# TODO: both Sv and Sp are off by ~<0.5 dB from matlab outputs.
# TODO: both Sv and TS are off by ~<0.5 dB from matlab outputs.
# Is this due to the use of 'single' in matlab code?
if cal_type == "Sv":
# effective pulse length
Expand Down Expand Up @@ -785,7 +785,7 @@ def _cal_complex(self, cal_type, waveform_mode) -> xr.Dataset:
)
out = out.rename_vars({list(out.data_vars.keys())[0]: "Sv"})

elif cal_type == "Sp":
elif cal_type == "TS":
transmit_power = self.echodata.beam["transmit_power"].sel(frequency=freq_sel.frequency)

out = (
Expand All @@ -795,7 +795,7 @@ def _cal_complex(self, cal_type, waveform_mode) -> xr.Dataset:
- 10 * np.log10(wavelength**2 * transmit_power / (16 * np.pi**2))
- 2 * gain
)
out = out.rename_vars({list(out.data_vars.keys())[0]: "Sp"})
out = out.rename_vars({list(out.data_vars.keys())[0]: "TS"})

# Attach calculated range (with units meter) into data set
out = out.merge(range_meter)
Expand All @@ -807,13 +807,13 @@ def _cal_complex(self, cal_type, waveform_mode) -> xr.Dataset:

def _compute_cal(self, cal_type, waveform_mode, encode_mode) -> xr.Dataset:
"""
Private method to compute Sv or Sp from EK80 data, called by compute_Sv or compute_Sp.
Private method to compute Sv or TS from EK80 data, called by compute_Sv or compute_TS.
Parameters
----------
cal_type : str
'Sv' for calculating volume backscattering strength, or
'Sp' for calculating point backscattering strength
'TS' for calculating target strength
waveform_mode : {"CW", "BB"}
Type of transmit waveform.
Expand All @@ -833,7 +833,7 @@ def _compute_cal(self, cal_type, waveform_mode, encode_mode) -> xr.Dataset:
Returns
-------
xr.Dataset
An xarray Dataset containing either Sv or Sp.
An xarray Dataset containing either Sv or TS.
"""
# Raise error for wrong inputs
if waveform_mode not in ("BB", "CW"):
Expand Down Expand Up @@ -948,8 +948,8 @@ def compute_Sv(self, waveform_mode="BB", encode_mode="complex"):
cal_type="Sv", waveform_mode=waveform_mode, encode_mode=encode_mode
)

def compute_Sp(self, waveform_mode="BB", encode_mode="complex"):
"""Compute point backscattering strength (Sp).
def compute_TS(self, waveform_mode="BB", encode_mode="complex"):
"""Compute target strength (TS).
Parameters
----------
Expand All @@ -970,10 +970,10 @@ def compute_Sp(self, waveform_mode="BB", encode_mode="complex"):
Returns
-------
Sp : xr.DataSet
A DataSet containing point backscattering strength (``Sp``)
TS : xr.DataSet
A DataSet containing target strength (``TS``)
and the corresponding range (``echo_range``) in units meter.
"""
return self._compute_cal(
cal_type="Sp", waveform_mode=waveform_mode, encode_mode=encode_mode
cal_type="TS", waveform_mode=waveform_mode, encode_mode=encode_mode
)
8 changes: 4 additions & 4 deletions echopype/echodata/echodata.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,13 +249,13 @@ def compute_range(
sensor, and some are equipped with a pressure sensor, but automatically
using these pressure data is not currently supported.
azfp_cal_type : {"Sv", "Sp"}, optional
azfp_cal_type : {"Sv", "TS"}, optional
- `"Sv"` for calculating volume backscattering strength
- `"Sp"` for calculating point backscattering strength.
- `"TS"` for calculating target strength.
This parameter needs to be specified for data from the AZFP echosounder,
due to a difference in computing range (``echo_range``) for Sv and Sp.
due to a difference in computing range (``echo_range``) for Sv and TS.
ek_waveform_mode : {"CW", "BB"}, optional
Type of transmit waveform.
Expand Down Expand Up @@ -373,7 +373,7 @@ def squeeze_non_scalar(n):

# duplicate range for all ping_times for consistency with EK case
# if it is not indexed by ping_time then echopype.preprocess.compute_MVBS will fail
# because it expects the range included in the Sv/Sp dataset
# because it expects the range included in the Sv/TS dataset
# to be indexed by ping_time
range_meter = range_meter.expand_dims({"ping_time": self.beam["ping_time"]}, axis=1)

Expand Down
Loading

0 comments on commit a33edb7

Please sign in to comment.