diff --git a/echopype/convert/api.py b/echopype/convert/api.py index bb94e726b..e017df57f 100644 --- a/echopype/convert/api.py +++ b/echopype/convert/api.py @@ -2,11 +2,12 @@ from typing import TYPE_CHECKING, Dict, List, Literal, Optional, Tuple, Union import fsspec -from xarray import DataTree +from xarray import Dataset, DataTree # fmt: off # black and isort have conflicting ideas about how this should be formatted from ..core import SONAR_MODELS +from .set_groups_bi500 import SetGroupsBI500 if TYPE_CHECKING: from ..core import EngineHint, PathHint, SonarModelsHint @@ -264,7 +265,8 @@ def _check_file( Parameters ---------- raw_file : str - path to raw data file + path to raw data file, or for BI500, the directory containing + the associated BI500 file set sonar_model : str model of the sonar instrument xml_path : str @@ -279,7 +281,8 @@ def _check_file( Returns ------- raw_file : str - path to existing raw data file + path to raw data file, or for BI500, the directory containing + the associated BI500 file set xml : str path to existing xml file empty string if no xml file is required for the specified model @@ -354,16 +357,20 @@ def open_raw( use_swap: Union[bool, Literal["auto"]] = False, max_chunk_size: str = "100MB", channels: Optional[List[str]] = None, -) -> EchoData: - """Create an EchoData object containing parsed data from a single raw data file. +) -> Union[EchoData, Tuple[EchoData, Dataset]]: + """Create an EchoData object containing parsed data from a raw data source. The EchoData object can be used for adding metadata and ancillary data as well as to serialize the parsed data to zarr or netcdf. + For BI500, this function returns ``(echodata, ds_cal)``, where ``ds_cal`` + contains the calibrated products generated by the BI500 system. + Parameters ---------- raw_file : str - path to raw data file + path to raw data file, or for BI500, the directory containing + the associated BI500 file set sonar_model : str model of the sonar instrument @@ -375,6 +382,7 @@ def open_raw( - ``AZFP6``: ASL Environmental Sciences AZFP echosounder (ULS6) - ``AD2CP``: Nortek Signature series ADCP (tested with Signature 500 and Signature 1000) + - ``BI500``: Bergen Integrator BI500 data xml_path : str path to XML config file used by AZFP (ULS5 only) @@ -392,7 +400,7 @@ def open_raw( When set to ``True`` (or when set to "auto" and large memory footprint is needed, this function will create a temporary zarr store at the operating system's temporary directory. - max_mb : int + max_chunk_size : str, default "100MB" The maximum data chunk size in Megabytes (MB), when offloading variables with a large memory footprint to a temporary zarr store channels : list of str, optional @@ -404,6 +412,11 @@ def open_raw( Returns ------- EchoData object + For all sonar models except BI500. + tuple of EchoData and xarray.Dataset + For BI500, returns ``(echodata, ds_cal)``. ``ds_cal`` contains + calibrated BI500 products including ``Sv``, ``Sv_bottom``, ``depth``, + ``depth_bottom``, and single-target variables. Raises ------ @@ -519,6 +532,15 @@ def open_raw( sonar_model=sonar_model, date_created=parser.config_datagram["timestamp"], ) + elif sonar_model == "BI500": + date_created = SetGroupsBI500._build_ping_time( + [parser.parameters["start_date"][0]], + [parser.parameters["start_time"][0]], + )[0] + tree_dict["/"] = setgrouper.set_toplevel( + sonar_model=sonar_model, + date_created=date_created, + ) else: tree_dict["/"] = setgrouper.set_toplevel( sonar_model=sonar_model, date_created=parser.ping_time[0] @@ -564,4 +586,10 @@ def open_raw( echodata._set_tree(tree) echodata._load_tree() + # BI500 values are already calibrated by the EK500/BI500 system. + # Return these products separately from the EchoData metadata object. + if sonar_model == "BI500": + ds_cal = setgrouper.set_calibrated() + return echodata, ds_cal + return echodata diff --git a/echopype/convert/parse_bi500.py b/echopype/convert/parse_bi500.py new file mode 100644 index 000000000..7910b1e63 --- /dev/null +++ b/echopype/convert/parse_bi500.py @@ -0,0 +1,334 @@ +import re +from collections import defaultdict +from struct import unpack + +import fsspec +import numpy as np + +from ..utils.log import _init_logger +from ..utils.misc import camelcase2snakecase +from .parse_base import ParseBase + +logger = _init_logger(__name__) + +FILENAME_DATETIME_BI500 = ( + "?(?.*)?-?F(?P\\w+)?-?T(?P\\w+)?" + "-?D(?P\\w+)?-?T(?P