From 7406e0d5407a03b610da0e5992012de6d778dad8 Mon Sep 17 00:00:00 2001 From: Raluca Simedroni <92971445+simedroniraluca@users.noreply.github.com> Date: Tue, 28 Nov 2023 16:21:59 +0200 Subject: [PATCH] Add TS (#144) --- oceanstream/L2_calibrated_data/__init__.py | 1 + .../L2_calibrated_data/sv_computation.py | 3 +- .../target_strength_computation.py | 109 ++++++++++++++++++ tests/conftest.py | 34 +++--- tests/test_target_strength_computation.py | 10 ++ 5 files changed, 138 insertions(+), 19 deletions(-) create mode 100644 oceanstream/L2_calibrated_data/target_strength_computation.py create mode 100644 tests/test_target_strength_computation.py diff --git a/oceanstream/L2_calibrated_data/__init__.py b/oceanstream/L2_calibrated_data/__init__.py index 42f4abe..a81a87f 100644 --- a/oceanstream/L2_calibrated_data/__init__.py +++ b/oceanstream/L2_calibrated_data/__init__.py @@ -12,3 +12,4 @@ from .sv_computation import compute_sv from .sv_dataset_extension import enrich_sv_dataset from .sv_interpolation import interpolate_sv +from .target_strength_computation import compute_target_strength diff --git a/oceanstream/L2_calibrated_data/sv_computation.py b/oceanstream/L2_calibrated_data/sv_computation.py index 0c0c45f..eff19e3 100644 --- a/oceanstream/L2_calibrated_data/sv_computation.py +++ b/oceanstream/L2_calibrated_data/sv_computation.py @@ -15,7 +15,7 @@ - `ComputeSVParams`: Class to validate and structure the parameters passed to the Sv computation function. - `compute_sv`: Main function to calculate Sv given an EchoData object -and other optional parameters. +and other optional parameters. This function is based on the `echopype.calibrate.compute_Sv()` function. Usage: @@ -84,6 +84,7 @@ def compute_sv(echodata: EchoData, **kwargs) -> xr.Dataset: - Uses the `ComputeSVParams` pydantic model to validate parameters. - Checks if the computed Sv is empty. - Returns Sv only if it is not empty. + - Is based on the `echopype.calibrate.compute_Sv()` function. """ # Validate parameters using the pydantic model diff --git a/oceanstream/L2_calibrated_data/target_strength_computation.py b/oceanstream/L2_calibrated_data/target_strength_computation.py new file mode 100644 index 0000000..1600951 --- /dev/null +++ b/oceanstream/L2_calibrated_data/target_strength_computation.py @@ -0,0 +1,109 @@ +""" +target_strength_computation.py +------------------------------- + +Module for computing the target strength (TS) from raw data. + +Supported Sonar Models: +- EK60 +- AZFP +- EK80 + +Functions and Classes: +- `SupportedSonarModelsForTS`: An Enum containing the sonar models supported +for TS computation. +- `WaveformMode`: Enum specifying the waveform mode ("CW" or "BB"). +- `EncodeMode`: Enum indicating the encoding mode ("complex" or "power"). +- `ComputeTSParams`: Class to validate and structure the parameters passed +to the TS computation function. +- `compute_target_strength`: Main function to calculate TS given an EchoData object +and other optional parameters. This function is based on the `echopype.calibrate.compute_TS()` function. + +Usage: + +To compute TS for a given EchoData object, `ed`, simply call: +`compute_target_strength(ed)` +""" + +from enum import Enum +from typing import Any, Optional + +import echopype as ep +import xarray as xr +from echopype.echodata.echodata import EchoData +from pydantic import BaseModel, ValidationError, field_validator + + +class SupportedSonarModelsForTS(str, Enum): + EK60 = "EK60" + AZFP = "AZFP" + EK80 = "EK80" + + +class WaveformMode(str, Enum): + CW = "CW" + BB = "BB" + + +class EncodeMode(str, Enum): + COMPLEX = "complex" + POWER = "power" + + +class ComputeTSParams(BaseModel): + echodata: Any + env_params: Optional[dict] = None + cal_params: Optional[dict] = None + waveform_mode: Optional[WaveformMode] = None + encode_mode: Optional[EncodeMode] = None + + @field_validator("echodata") + def check_echodata_type(cls, value): + if not isinstance(value, EchoData): + raise ValueError("Invalid type for echodata. Expected an instance of EchoData.") + return value + + +def compute_target_strength(echodata: EchoData, **kwargs) -> xr.Dataset: + """ + Compute target strength (TS) from raw data. + + Parameters: + - echodata (EchoData): The EchoData object containing + sonar data for computation. + - **kwargs: Additional keyword arguments passed to the TS computation. + + Returns: + - xr.Dataset: A Dataset containing the computed TS values. + + Notes: + This function: + - Validates the `echodata`'s sonar model against supported models. + - Uses the `ComputeTSParams` pydantic model to validate parameters. + - Checks if the computed TS is empty. + - Returns TS only if it is not empty. + - Is based on the `echopype.calibrate.compute_TS()` function. + + """ + # Validate parameters using the pydantic model + try: + ComputeTSParams(echodata=echodata, **kwargs) + except ValidationError as e: + raise ValueError(str(e)) + # Check if the sonar model is supported + sonar_model = echodata.sonar_model + try: + SupportedSonarModelsForTS(sonar_model) + except ValueError: + raise ValueError( + f"Sonar model '{sonar_model}'\ + is not supported for TS computation.\ + Supported models are \ + {list(SupportedSonarModelsForTS)}." + ) + # Compute TS + TS = ep.calibrate.compute_TS(echodata, **kwargs) + # Check if the computed TS is empty + if TS["TS"].values.size == 0: + raise ValueError("Computed TS is empty!") + return TS diff --git a/tests/conftest.py b/tests/conftest.py index 0742360..2280321 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -7,13 +7,11 @@ import pytest from xarray import Dataset +from oceanstream.L2_calibrated_data import create_default_noise_masks_oceanstream, sv_interpolation from oceanstream.L2_calibrated_data.background_noise_remover import apply_remove_background_noise from oceanstream.L2_calibrated_data.sv_computation import compute_sv from oceanstream.L2_calibrated_data.sv_dataset_extension import enrich_sv_dataset -from oceanstream.L2_calibrated_data import sv_interpolation, \ - create_default_noise_masks_oceanstream -from oceanstream.L3_regridded_data import applying_masks_handler, \ - attach_shoal_mask_to_ds +from oceanstream.L3_regridded_data import applying_masks_handler, attach_shoal_mask_to_ds current_directory = os.path.dirname(os.path.abspath(__file__)) TEST_DATA_FOLDER = os.path.join(current_directory, "..", "test_data") @@ -199,16 +197,16 @@ def ed_ek_60_for_Sv(): local_path = os.path.join(TEST_DATA_FOLDER, filename) if os.path.isfile(local_path): ed = ep.open_raw( - local_path, - sonar_model="EK60", - ) + local_path, + sonar_model="EK60", + ) else: rawdirpath = base_path + filename s3raw_fpath = f"s3://{bucket}/{rawdirpath}" storage_opts = {"anon": True} ed = ep.open_raw( - s3raw_fpath, - sonar_model="EK60",storage_options=storage_opts) # type: ignore + s3raw_fpath, sonar_model="EK60", storage_options=storage_opts + ) # type: ignore return ed @@ -226,22 +224,22 @@ def enriched_ek60_Sv(ed_ek_60_for_Sv): def ed_ek_80_for_Sv(): base_url = "noaa-wcsd-pds.s3.amazonaws.com/" path = "data/raw/Sally_Ride/SR1611/EK80/" - filename = "D20161109-T163350.raw" + file_name = "D20161109-T163350.raw" - local_path = os.path.join(TEST_DATA_FOLDER, filename) + local_path = os.path.join(TEST_DATA_FOLDER, file_name) if os.path.isfile(local_path): ed_EK80 = ep.open_raw( - local_path, - sonar_model="EK80", - ) + local_path, + sonar_model="EK80", + ) else: raw_file_address = base_url + path + file_name rf = raw_file_address # Path(raw_file_address) ed_EK80 = ep.open_raw( - f"https://{rf}", - sonar_model="EK80", - ) - + f"https://{rf}", + sonar_model="EK80", + ) + return ed_EK80 diff --git a/tests/test_target_strength_computation.py b/tests/test_target_strength_computation.py new file mode 100644 index 0000000..a932116 --- /dev/null +++ b/tests/test_target_strength_computation.py @@ -0,0 +1,10 @@ +import numpy as np +import pytest + +from oceanstream.L2_calibrated_data import target_strength_computation + + +def test_ctarget_strength_computation(ed_ek_60_for_Sv): + TS = target_strength_computation.compute_target_strength(ed_ek_60_for_Sv, encode_mode="power") + val = np.nanmean(TS["TS"].values) + assert val == pytest.approx(-68.06057158474684, 0.0001)