Skip to content

Commit 3723876

Browse files
authored
Merge branch 'main' into docs/changelog-update-0.0.9
2 parents 120493b + 5c71aee commit 3723876

22 files changed

+424
-38
lines changed

.pre-commit-config.yaml

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,6 @@ repos:
5959
hooks:
6060
- id: rstfmt
6161
exclude: 'cli/.*' # Because we use argparse
62-
- repo: https://github.com/b8raoult/pre-commit-docconvert
63-
rev: "0.1.5"
64-
hooks:
65-
- id: docconvert
66-
args: ["numpy"]
6762
- repo: https://github.com/tox-dev/pyproject-fmt
6863
rev: "2.2.3"
6964
hooks:

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
88
Please add your functional changes to the appropriate section in the PR.
99
Keep it human-readable, your future self will thank you!
1010

11+
1112
## [Unreleased](https://github.com/ecmwf/anemoi-utils/transform/0.0.5...HEAD/compare/0.0.9...HEAD)
1213

1314
## [0.0.9](https://github.com/ecmwf/anemoi-utils/transform/0.0.5...HEAD/compare/0.0.8...0.0.9) - 2024-11-01

pyproject.toml

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
1-
#!/usr/bin/env python
2-
# (C) Copyright 2024 ECMWF.
1+
# (C) Copyright 2024 Anemoi contributors.
32
#
43
# This software is licensed under the terms of the Apache Licence Version 2.0
54
# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
5+
#
66
# In applying this licence, ECMWF does not waive the privileges and immunities
77
# granted to it by virtue of its status as an intergovernmental organisation
88
# nor does it submit to any jurisdiction.
99

10-
# https://packaging.python.org/en/latest/guides/writing-pyproject-toml/
11-
1210
[build-system]
1311
requires = [ "setuptools>=60", "setuptools-scm>=8" ]
1412

src/anemoi/transform/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
# (C) Copyright 2024 European Centre for Medium-Range Weather Forecasts.
1+
# (C) Copyright 2024 Anemoi contributors.
2+
#
23
# This software is licensed under the terms of the Apache Licence Version 2.0
34
# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
5+
#
46
# In applying this licence, ECMWF does not waive the privileges and immunities
57
# granted to it by virtue of its status as an intergovernmental organisation
68
# nor does it submit to any jurisdiction.

src/anemoi/transform/__main__.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
1-
#!/usr/bin/env python
2-
# (C) Copyright 2024 ECMWF.
1+
# (C) Copyright 2024 Anemoi contributors.
32
#
43
# This software is licensed under the terms of the Apache Licence Version 2.0
54
# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
5+
#
66
# In applying this licence, ECMWF does not waive the privileges and immunities
77
# granted to it by virtue of its status as an intergovernmental organisation
88
# nor does it submit to any jurisdiction.
9-
#
109

1110
from anemoi.utils.cli import cli_main
1211
from anemoi.utils.cli import make_parser

src/anemoi/transform/commands/__init__.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
1-
#!/usr/bin/env python
2-
# (C) Copyright 2024 ECMWF.
1+
# (C) Copyright 2024 Anemoi contributors.
32
#
43
# This software is licensed under the terms of the Apache Licence Version 2.0
54
# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
5+
#
66
# In applying this licence, ECMWF does not waive the privileges and immunities
77
# granted to it by virtue of its status as an intergovernmental organisation
88
# nor does it submit to any jurisdiction.
9-
#
109

1110
import os
1211

src/anemoi/transform/data/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
# (C) Copyright 2024 European Centre for Medium-Range Weather Forecasts.
1+
# (C) Copyright 2024 Anemoi contributors.
2+
#
23
# This software is licensed under the terms of the Apache Licence Version 2.0
34
# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
5+
#
46
# In applying this licence, ECMWF does not waive the privileges and immunities
57
# granted to it by virtue of its status as an intergovernmental organisation
68
# nor does it submit to any jurisdiction.

src/anemoi/transform/fields.py

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
# (C) Copyright 2024 Anemoi contributors.
2+
#
3+
# This software is licensed under the terms of the Apache Licence Version 2.0
4+
# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
5+
#
6+
# In applying this licence, ECMWF does not waive the privileges and immunities
7+
# granted to it by virtue of its status as an intergovernmental organisation
8+
# nor does it submit to any jurisdiction.
9+
10+
import logging
11+
12+
from earthkit.data.indexing.fieldlist import FieldArray
13+
14+
LOG = logging.getLogger(__name__)
15+
16+
17+
def new_fieldlist_from_list(fields):
18+
return FieldArray(fields)
19+
20+
21+
def new_empty_fieldlist():
22+
return FieldArray([])
23+
24+
25+
class WrappedField:
26+
"""A wrapper around a earthkit-data field object."""
27+
28+
def __init__(self, field):
29+
self._field = field
30+
31+
def __getattr__(self, name):
32+
if name not in (
33+
"mars_area",
34+
"mars_grid",
35+
"to_numpy",
36+
"metadata",
37+
):
38+
LOG.warning(f"NewField: forwarding `{name}`")
39+
return getattr(self._field, name)
40+
41+
def __repr__(self) -> str:
42+
return repr(self._field)
43+
44+
45+
class NewDataField(WrappedField):
46+
"""Change the data of a field."""
47+
48+
def __init__(self, field, data):
49+
super().__init__(field)
50+
self._data = data
51+
self.shape = data.shape
52+
53+
def to_numpy(self, flatten=False, dtype=None, index=None):
54+
data = self._data
55+
if dtype is not None:
56+
data = data.astype(dtype)
57+
if flatten:
58+
data = data.flatten()
59+
if index is not None:
60+
data = data[index]
61+
return data
62+
63+
64+
class NewMetadataField(WrappedField):
65+
"""Change the metadata of a field."""
66+
67+
def __init__(self, field, **kwargs):
68+
super().__init__(field)
69+
self._metadata = kwargs
70+
71+
def metadata(self, *args, **kwargs):
72+
73+
if kwargs.get("namespace"):
74+
assert kwargs.get("namespace") == "mars", kwargs
75+
assert len(args) == 0, (args, kwargs)
76+
mars = self._field.metadata(**kwargs).copy()
77+
for k in list(mars.keys()):
78+
if k in self._metadata:
79+
mars[k] = self._metadata[k]
80+
return mars
81+
82+
if len(args) == 1 and args[0] in self._metadata:
83+
return self._metadata[args[0]]
84+
85+
return self._field.metadata(*args, **kwargs)
86+
87+
88+
class NewValidDateTimeField(NewMetadataField):
89+
"""Change the valid_datetime of a field."""
90+
91+
def __init__(self, field, valid_datetime):
92+
date = int(valid_datetime.date().strftime("%Y%m%d"))
93+
assert valid_datetime.time().minute == 0, valid_datetime.time()
94+
time = valid_datetime.time().hour
95+
96+
self.valid_datetime = valid_datetime
97+
98+
super().__init__(field, date=date, time=time, step=0, valid_datetime=valid_datetime.isoformat())
99+
100+
101+
def new_field_from_numpy(array, *, template, **metadata):
102+
return NewMetadataField(NewDataField(template, array), **metadata)
103+
104+
105+
def new_field_with_valid_datetime(template, date):
106+
return NewValidDateTimeField(template, date)

src/anemoi/transform/filters/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
# (C) Copyright 2024 European Centre for Medium-Range Weather Forecasts.
1+
# (C) Copyright 2024 Anemoi contributors.
2+
#
23
# This software is licensed under the terms of the Apache Licence Version 2.0
34
# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
5+
#
46
# In applying this licence, ECMWF does not waive the privileges and immunities
57
# granted to it by virtue of its status as an intergovernmental organisation
68
# nor does it submit to any jurisdiction.

src/anemoi/transform/filters/base.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,16 @@
88
# nor does it submit to any jurisdiction.
99

1010

11+
import logging
1112
from abc import abstractmethod
1213

13-
import earthkit.data as ekd
14-
14+
from ..fields import new_field_from_numpy
15+
from ..fields import new_fieldlist_from_list
1516
from ..filter import Filter
1617
from ..grouping import GroupByMarsParam
1718

19+
LOG = logging.getLogger(__name__)
20+
1821

1922
class SimpleFilter(Filter):
2023
"""A filter to convert only some fields.
@@ -35,14 +38,10 @@ def _transform(self, data, transform, *group_by):
3538

3639
def new_field_from_numpy(self, array, *, template, param):
3740
"""Create a new field from a numpy array."""
38-
md = template.metadata().override(shortName=param)
39-
# return ekd.ArrayField(array, md)
40-
return ekd.FieldList.from_array(array, md)[0]
41+
return new_field_from_numpy(array, template=template, param=param)
4142

4243
def new_fieldlist_from_list(self, fields):
43-
from earthkit.data.indexing.fieldlist import FieldArray
44-
45-
return FieldArray(fields)
44+
return new_fieldlist_from_list(fields)
4645

4746
@abstractmethod
4847
def forward_transform(self, *fields):
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# (C) Copyright 2024 Anemoi contributors.
2+
#
3+
# This software is licensed under the terms of the Apache Licence Version 2.0
4+
# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
5+
#
6+
# In applying this licence, ECMWF does not waive the privileges and immunities
7+
# granted to it by virtue of its status as an intergovernmental organisation
8+
# nor does it submit to any jurisdiction.
9+
10+
import earthkit.data as ekd
11+
import numpy as np
12+
13+
from . import filter_registry
14+
from .base import SimpleFilter
15+
16+
17+
def mask_glaciers(snow_depth, glacier_mask):
18+
snow_depth[glacier_mask] = np.nan
19+
return snow_depth
20+
21+
22+
@filter_registry.register("glacier_mask")
23+
class SnowDepthMasked(SimpleFilter):
24+
"""A filter to mask about glacier in snow depth."""
25+
26+
def __init__(
27+
self,
28+
*,
29+
glacier_mask,
30+
snow_depth="sd",
31+
snow_depth_masked="sd_masked",
32+
):
33+
self.glacier_mask = ekd.from_source("file", glacier_mask)[0].to_numpy().astype(bool)
34+
self.snow_depth = snow_depth
35+
self.snow_depth_masked = snow_depth_masked
36+
37+
def forward(self, data):
38+
return self._transform(
39+
data,
40+
self.forward_transform,
41+
self.snow_depth,
42+
)
43+
44+
def backward(self, data):
45+
raise NotImplementedError("SnowDepthMasked is not reversible")
46+
47+
def forward_transform(self, sd):
48+
"""Mask out glaciers in snow depth"""
49+
50+
snow_depth_masked = mask_glaciers(sd.to_numpy(), self.glacier_mask)
51+
52+
yield self.new_field_from_numpy(snow_depth_masked, template=sd, param=self.snow_depth_masked)
53+
54+
def backward_transform(self, sd):
55+
raise NotImplementedError("SnowDepthMasked is not reversible")

0 commit comments

Comments
 (0)