Skip to content

Commit b11cb0e

Browse files
Support Python 3.12 and Migrate to Xarray DataTree (#1419)
* initial commit to support python 3.12 and migrate to xarray DataTree * remove additional py3.11 file * update imports * add todo note to search through pr history to learn how to correctly work with rendertree * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix data tree render import * pin xarray to later 2024 november version * bump minimum python testing version and supported version to 3.10 * set time3 in nmea to time_nmea * resolve merge conflicts * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * use channel all for ek80 sonar group * add support for nmea_time * import xarray import open_datatree test change * fix datatree import pt 2 * support 3.12 in setup cfg * update dim check * resolve merge conflict * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * remove test notebook * fix import * comment out TestEchoData and save it for issue 1420 * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * set max python to <= 3.12 * set to less than ython 3.13 * set python 3.12 in setup cfg * add ek80 channel test with two beam groups * remove unnecessary comment --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 82416b0 commit b11cb0e

23 files changed

+203
-187
lines changed

.ci_helpers/py3.9.yaml renamed to .ci_helpers/py3.12.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: echopype
22
channels:
33
- conda-forge
44
dependencies:
5-
- python=3.9
5+
- python=3.12
66
- pip
77
- pip:
88
- -r ../requirements.txt

.github/workflows/build.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ jobs:
2020
strategy:
2121
fail-fast: false
2222
matrix:
23-
python-version: ["3.9", "3.10", "3.11"]
23+
python-version: ["3.10", "3.11", "3.12"]
2424
runs-on: [ubuntu-latest]
2525
experimental: [false]
2626
services:

.github/workflows/pr.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ jobs:
1616
strategy:
1717
fail-fast: false
1818
matrix:
19-
python-version: ["3.9", "3.10", "3.11"]
19+
python-version: ["3.10", "3.11", "3.12"]
2020
runs-on: [ubuntu-latest]
2121
experimental: [false]
2222
services:

.github/workflows/windows-utils.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ jobs:
2121
fail-fast: false
2222
matrix:
2323
include:
24-
- python-version: 3.9
24+
- python-version: 3.12
2525
experimental: false
2626
defaults:
2727
run:

.github/workflows/windows.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ jobs:
2020
fail-fast: false
2121
matrix:
2222
include:
23-
- python-version: 3.9
23+
- python-version: 3.12
2424
experimental: false
2525
defaults:
2626
run:

docs/source/contributing.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ To create an environment for developing Echopype, we recommend the following ste
5050
```shell
5151
# Create a conda environment using the supplied requirements files
5252
# Note the last one docs/requirements.txt is only required for building docs
53-
conda create -c conda-forge -n echopype --yes python=3.9 --file requirements.txt --file requirements-dev.txt --file docs/requirements.txt
53+
conda create -c conda-forge -n echopype --yes python=3.12 --file requirements.txt --file requirements-dev.txt --file docs/requirements.txt
5454
5555
# Switch to the newly built environment
5656
conda activate echopype

docs/source/installation.md

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
## Installation
44

5-
Echopype is available and tested for Python 3.9-3.11. The latest release can be installed through conda (or mamba, see below) via the [conda-forge channel](https://anaconda.org/conda-forge/echopype):
5+
Echopype is available and tested for Python 3.10-3.12. The latest release can be installed through conda (or mamba, see below) via the [conda-forge channel](https://anaconda.org/conda-forge/echopype):
66
```shell
77
# Install via conda-forge
88
$ conda install -c conda-forge echopype
@@ -14,10 +14,6 @@ It is available via [PyPI](https://pypi.org/project/echopype):
1414
$ pip install echopype
1515
```
1616

17-
:::{note}
18-
We are working on adding support for Python 3.12 soon!
19-
:::
20-
2117
:::{attention}
2218
It's common to encounter the situation that installing packages using Conda is slow or fails,
2319
because Conda is unable to resolve dependencies.

echopype/convert/api.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from typing import TYPE_CHECKING, Dict, Literal, Optional, Tuple, Union
33

44
import fsspec
5-
from datatree import DataTree
5+
from xarray import DataTree
66

77
# fmt: off
88
# black and isort have conflicting ideas about how this should be formatted

echopype/convert/set_groups_base.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -149,14 +149,14 @@ def set_nmea(self) -> xr.Dataset:
149149
ds = xr.Dataset(
150150
{
151151
"NMEA_datagram": (
152-
["time1"],
152+
["nmea_time"],
153153
raw_nmea,
154154
{"long_name": "NMEA datagram"},
155155
)
156156
},
157157
coords={
158-
"time1": (
159-
["time1"],
158+
"nmea_time": (
159+
["nmea_time"],
160160
time,
161161
{
162162
"axis": "T",

echopype/convert/set_groups_ek80.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ def set_sonar(self, beam_group_type: list = ["power", None]) -> xr.Dataset:
227227
# Create dataset
228228
sonar_vars = {
229229
"frequency_nominal": (
230-
["channel"],
230+
["channel_all"],
231231
var["transducer_frequency"],
232232
{
233233
"units": "Hz",
@@ -237,21 +237,21 @@ def set_sonar(self, beam_group_type: list = ["power", None]) -> xr.Dataset:
237237
},
238238
),
239239
"transceiver_serial_number": (
240-
["channel"],
240+
["channel_all"],
241241
var["serial_number"],
242242
{
243243
"long_name": "Transceiver serial number",
244244
},
245245
),
246246
"transducer_name": (
247-
["channel"],
247+
["channel_all"],
248248
var["transducer_name"],
249249
{
250250
"long_name": "Transducer name",
251251
},
252252
),
253253
"transducer_serial_number": (
254-
["channel"],
254+
["channel_all"],
255255
var["transducer_serial_number"],
256256
{
257257
"long_name": "Transducer serial number",
@@ -261,8 +261,8 @@ def set_sonar(self, beam_group_type: list = ["power", None]) -> xr.Dataset:
261261
ds = xr.Dataset(
262262
{**sonar_vars, **beam_groups_vars},
263263
coords={
264-
"channel": (
265-
["channel"],
264+
"channel_all": (
265+
["channel_all"],
266266
self.sorted_channel["all"],
267267
self._varattrs["beam_coord_default"]["channel"],
268268
),

echopype/echodata/combine.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import numpy as np
1010
import pandas as pd
1111
import xarray as xr
12-
from datatree import DataTree
12+
from xarray import DataTree
1313

1414
from ..utils.io import validate_output_path
1515
from ..utils.log import _init_logger
@@ -18,7 +18,7 @@
1818

1919
logger = _init_logger(__name__)
2020

21-
POSSIBLE_TIME_DIMS = {"time1", "time2", "time3", "time4", "ping_time"}
21+
POSSIBLE_TIME_DIMS = {"time1", "time2", "time3", "time4", "nmea_time", "ping_time"}
2222
APPEND_DIMS = {"filenames"}.union(POSSIBLE_TIME_DIMS)
2323
DATE_CREATED_ATTR = "date_created"
2424
CONVERSION_TIME_ATTR = "conversion_time"

echopype/echodata/echodata.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import fsspec
99
import numpy as np
1010
import xarray as xr
11-
from datatree import DataTree, open_datatree
11+
from xarray import DataTree, open_datatree
1212
from zarr.errors import GroupNotFoundError, PathNotFoundError
1313

1414
if TYPE_CHECKING:
@@ -242,7 +242,7 @@ def group_paths(self) -> Set[str]:
242242
def __get_dataset(node: DataTree) -> Optional[xr.Dataset]:
243243
if node.has_data or node.has_attrs:
244244
# validate and clean dtypes
245-
return sanitize_dtypes(node.ds)
245+
return sanitize_dtypes(node.to_dataset())
246246
return None
247247

248248
def __get_node(self, key: Optional[str]) -> DataTree:
@@ -265,7 +265,7 @@ def __setitem__(self, __key: Optional[str], __newvalue: Any) -> Optional[xr.Data
265265
if self._tree:
266266
try:
267267
node = self.__get_node(__key)
268-
node.ds = __newvalue
268+
node.dataset = __newvalue
269269
return self.__get_dataset(node)
270270
except KeyError:
271271
raise GroupNotFoundError(__key)
@@ -283,9 +283,9 @@ def __setattr__(self, __name: str, __value: Any) -> None:
283283
group_path = group["ep_group"]
284284
if self._tree:
285285
if __name == "top":
286-
self._tree.ds = __value
286+
self._tree.dataset = __value
287287
else:
288-
self._tree[group_path].ds = __value
288+
self._tree[group_path].dataset = __value
289289
super().__setattr__(__name, attr_value)
290290

291291
@add_processing_level("L1A")

echopype/echodata/widgets/utils.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import uuid
22
from hashlib import md5
33

4-
from datatree import DataTree
5-
from datatree.render import RenderTree
4+
from xarray import DataTree
5+
from xarray.core.datatree_render import RenderDataTree
66

77
from ..convention.utils import _get_sonar_groups
88

@@ -63,7 +63,7 @@ def _single_node_repr(node: DataTree) -> str:
6363

6464

6565
def tree_repr(tree: DataTree) -> str:
66-
renderer = RenderTree(tree)
66+
renderer = RenderDataTree(tree)
6767
lines = []
6868
for pre, _, node in renderer:
6969
if node.has_data or node.has_attrs:

echopype/tests/convert/test_convert_ek60.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ def test_convert_ek60_different_num_channel_mode_values(file_path, ek60_path):
238238
np.float32
239239
)
240240

241-
@pytest.mark.test
241+
242242
@pytest.mark.integration
243243
def test_converting_ek60_raw_with_missing_channel_power():
244244
"""

echopype/tests/convert/test_convert_ek80.py

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -446,7 +446,6 @@ def test_convert_ek80_mru1(ek80_path):
446446
np.all(echodata["Platform"]["vertical_offset"].data == np.array(parser.mru1["heave"]))
447447
np.all(echodata["Platform"]["heading"].data == np.array(parser.mru1["heading"]))
448448

449-
450449
@pytest.mark.unit
451450
def test_skip_ec150(ek80_path):
452451
"""Make sure we skip EC150 datagrams correctly."""
@@ -457,7 +456,7 @@ def test_skip_ec150(ek80_path):
457456
assert "backscatter_i" in echodata["Sonar/Beam_group1"].data_vars
458457
assert (
459458
echodata["Sonar/Beam_group1"].dims
460-
== {'channel': 1, 'ping_time': 2, 'range_sample': 115352, 'beam': 4}
459+
== {'channel_all': 1, 'beam_group': 1, 'channel': 1, 'ping_time': 2, 'range_sample': 115352, 'beam': 4}
461460
)
462461

463462

@@ -531,3 +530,25 @@ def test_parse_ek80_with_invalid_env_datagrams():
531530
env_var = ed["Environment"][var]
532531
assert env_var.notnull().all() and env_var.dtype == np.float64
533532

533+
534+
@pytest.mark.unit
535+
def test_ek80_sonar_all_channel():
536+
"""
537+
Checks that when an EK80 raw file has 2 beam groups, the converted Echodata object contains
538+
all channels in the 'channel_all' dimension of the Sonar group.
539+
"""
540+
# Convert EK80 Raw File
541+
ed = open_raw(
542+
raw_file="echopype/test_data/ek80_new/echopype-test-D20211004-T235714.raw",
543+
sonar_model="EK80"
544+
)
545+
546+
# Grab channels from Sonar group
547+
channel_set = set(ed["Sonar"]["channel_all"].data)
548+
549+
# Grab channels from both beam groups
550+
target_channel_set = set(ed["Sonar/Beam_group1"]["channel"].data)
551+
target_channel_set.update(set(ed["Sonar/Beam_group2"]["channel"].data))
552+
553+
# Check that channel sets are equal
554+
assert channel_set == target_channel_set

echopype/tests/convert/test_convert_source_target_locs.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
import fsspec
1313
import xarray as xr
1414
import pytest
15-
from datatree import open_datatree
15+
from xarray import open_datatree
1616
from tempfile import TemporaryDirectory
1717
from echopype import open_raw
1818
from echopype.utils.coding import DEFAULT_ENCODINGS

0 commit comments

Comments
 (0)