cfapyx 2024.9.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cfapyx-2024.9.0/CFAPyX/__init__.py +1 -0
- cfapyx-2024.9.0/CFAPyX/backend.py +175 -0
- cfapyx-2024.9.0/CFAPyX/datastore.py +459 -0
- cfapyx-2024.9.0/CFAPyX/decoder.py +108 -0
- cfapyx-2024.9.0/CFAPyX/group.py +142 -0
- cfapyx-2024.9.0/CFAPyX/wrappers.py +381 -0
- cfapyx-2024.9.0/CFAPyX.egg-info/PKG-INFO +77 -0
- cfapyx-2024.9.0/CFAPyX.egg-info/SOURCES.txt +17 -0
- cfapyx-2024.9.0/CFAPyX.egg-info/dependency_links.txt +1 -0
- cfapyx-2024.9.0/CFAPyX.egg-info/entry_points.txt +2 -0
- cfapyx-2024.9.0/CFAPyX.egg-info/requires.txt +10 -0
- cfapyx-2024.9.0/CFAPyX.egg-info/top_level.txt +1 -0
- cfapyx-2024.9.0/LICENSE +31 -0
- cfapyx-2024.9.0/PKG-INFO +77 -0
- cfapyx-2024.9.0/README.md +25 -0
- cfapyx-2024.9.0/pyproject.toml +23 -0
- cfapyx-2024.9.0/requirements.txt +10 -0
- cfapyx-2024.9.0/setup.cfg +4 -0
- cfapyx-2024.9.0/tests/test_cfa.py +77 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .backend import CFANetCDFBackendEntrypoint
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
__author__ = "Daniel Westwood"
|
|
2
|
+
__contact__ = "daniel.westwood@stfc.ac.uk"
|
|
3
|
+
__copyright__ = "Copyright 2024 United Kingdom Research and Innovation"
|
|
4
|
+
|
|
5
|
+
from xarray.backends import StoreBackendEntrypoint, BackendEntrypoint
|
|
6
|
+
from xarray.backends.common import AbstractDataStore
|
|
7
|
+
from xarray.core.dataset import Dataset
|
|
8
|
+
from xarray import conventions
|
|
9
|
+
|
|
10
|
+
from CFAPyX.datastore import CFADataStore
|
|
11
|
+
|
|
12
|
+
def open_cfa_dataset(
|
|
13
|
+
filename_or_obj,
|
|
14
|
+
drop_variables=None,
|
|
15
|
+
mask_and_scale=None,
|
|
16
|
+
decode_times=None,
|
|
17
|
+
concat_characters=None,
|
|
18
|
+
decode_coords=None,
|
|
19
|
+
use_cftime=None,
|
|
20
|
+
decode_timedelta=None,
|
|
21
|
+
cfa_options={},
|
|
22
|
+
group=None,
|
|
23
|
+
):
|
|
24
|
+
"""
|
|
25
|
+
Top-level function which opens a CFA dataset using Xarray. Creates a CFA Datastore
|
|
26
|
+
from the ``filename_or_obj`` provided, then passes this to a CFA StoreBackendEntrypoint
|
|
27
|
+
to create an Xarray Dataset. Most parameters are not handled by CFA, so only the
|
|
28
|
+
CFA-relevant ones are described here.
|
|
29
|
+
|
|
30
|
+
:param filename_or_obj: (str) The path to a CFA-netCDF file to be opened by Xarray
|
|
31
|
+
|
|
32
|
+
:param cfa_options: (dict) A set of kwargs provided to CFA which provide additional
|
|
33
|
+
configurations. Currently implemented are: substitutions (dict),
|
|
34
|
+
decode_cfa (bool)
|
|
35
|
+
|
|
36
|
+
:param group: (str) The name or path to a NetCDF group. CFA can handle opening
|
|
37
|
+
from specific groups and will inherit both ``group`` and ``global``
|
|
38
|
+
dimensions/attributes.
|
|
39
|
+
|
|
40
|
+
:returns: An xarray.Dataset object composed of xarray.DataArray objects representing the different
|
|
41
|
+
NetCDF variables and dimensions. CFA aggregated variables are decoded unless the ``decode_cfa``
|
|
42
|
+
parameter in ``cfa_options`` is false.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
# Load the CFA datastore from the provided file (object not supported).
|
|
46
|
+
store = CFADataStore.open(filename_or_obj, group=group)
|
|
47
|
+
|
|
48
|
+
# Expands cfa_options into individual kwargs for the store.
|
|
49
|
+
store.cfa_options = cfa_options
|
|
50
|
+
|
|
51
|
+
use_active = False
|
|
52
|
+
if hasattr(store, 'use_active'):
|
|
53
|
+
use_active = store.use_active
|
|
54
|
+
|
|
55
|
+
# Xarray makes use of StoreBackendEntrypoints to provide the Dataset 'ds'
|
|
56
|
+
store_entrypoint = CFAStoreBackendEntrypoint()
|
|
57
|
+
ds = store_entrypoint.open_dataset(
|
|
58
|
+
store,
|
|
59
|
+
mask_and_scale=mask_and_scale,
|
|
60
|
+
decode_times=decode_times,
|
|
61
|
+
concat_characters=concat_characters,
|
|
62
|
+
decode_coords=decode_coords,
|
|
63
|
+
drop_variables=drop_variables,
|
|
64
|
+
use_cftime=use_cftime,
|
|
65
|
+
decode_timedelta=decode_timedelta,
|
|
66
|
+
use_active=use_active
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
return ds
|
|
70
|
+
|
|
71
|
+
class CFANetCDFBackendEntrypoint(BackendEntrypoint):
|
|
72
|
+
|
|
73
|
+
description = "Open CFA-netCDF files (.nca) using CFA-PyX in Xarray"
|
|
74
|
+
url = "https://cedadev.github.io/CFAPyX/"
|
|
75
|
+
|
|
76
|
+
def open_dataset(
|
|
77
|
+
self,
|
|
78
|
+
filename_or_obj,
|
|
79
|
+
*,
|
|
80
|
+
drop_variables=None,
|
|
81
|
+
mask_and_scale=None,
|
|
82
|
+
decode_times=None,
|
|
83
|
+
concat_characters=None,
|
|
84
|
+
decode_coords=None,
|
|
85
|
+
use_cftime=None,
|
|
86
|
+
decode_timedelta=None,
|
|
87
|
+
cfa_options={},
|
|
88
|
+
group=None,
|
|
89
|
+
# backend specific keyword arguments
|
|
90
|
+
# do not use 'chunks' or 'cache' here
|
|
91
|
+
):
|
|
92
|
+
"""
|
|
93
|
+
Returns a complete xarray representation of a CFA-netCDF dataset which includes expanding/decoding
|
|
94
|
+
CFA aggregated variables into proper arrays.
|
|
95
|
+
"""
|
|
96
|
+
|
|
97
|
+
return open_cfa_dataset(
|
|
98
|
+
filename_or_obj,
|
|
99
|
+
drop_variables=drop_variables,
|
|
100
|
+
mask_and_scale=mask_and_scale,
|
|
101
|
+
decode_times=decode_times,
|
|
102
|
+
concat_characters=concat_characters,
|
|
103
|
+
decode_coords=decode_coords,
|
|
104
|
+
use_cftime=use_cftime,
|
|
105
|
+
decode_timedelta=decode_timedelta,
|
|
106
|
+
cfa_options=cfa_options,
|
|
107
|
+
group=group)
|
|
108
|
+
|
|
109
|
+
class CFAStoreBackendEntrypoint(StoreBackendEntrypoint):
|
|
110
|
+
description = "Open CFA-based Abstract Data Store"
|
|
111
|
+
url = "https://cedadev.github.io/CFAPyX/"
|
|
112
|
+
|
|
113
|
+
def open_dataset(
|
|
114
|
+
self,
|
|
115
|
+
cfa_xarray_store,
|
|
116
|
+
*,
|
|
117
|
+
mask_and_scale=True,
|
|
118
|
+
decode_times=True,
|
|
119
|
+
concat_characters=True,
|
|
120
|
+
decode_coords=True,
|
|
121
|
+
drop_variables=None,
|
|
122
|
+
use_cftime=None,
|
|
123
|
+
decode_timedelta=None,
|
|
124
|
+
use_active=False,
|
|
125
|
+
) -> Dataset:
|
|
126
|
+
"""
|
|
127
|
+
Takes cfa_xarray_store of type AbstractDataStore and creates an xarray.Dataset object.
|
|
128
|
+
Most parameters are not handled by CFA, so only the CFA-relevant ones are described here.
|
|
129
|
+
|
|
130
|
+
:param cfa_xarray_store: (obj) The CFA Datastore object which loads and decodes CFA
|
|
131
|
+
aggregated variables and dimensions.
|
|
132
|
+
|
|
133
|
+
:returns: An xarray.Dataset object composed of xarray.DataArray objects representing the different
|
|
134
|
+
NetCDF variables and dimensions. CFA aggregated variables are decoded unless the ``decode_cfa``
|
|
135
|
+
parameter in ``cfa_options`` is false.
|
|
136
|
+
|
|
137
|
+
"""
|
|
138
|
+
assert isinstance(cfa_xarray_store, AbstractDataStore)
|
|
139
|
+
|
|
140
|
+
# Same as NetCDF4 operations, just with the CFA Datastore
|
|
141
|
+
vars, attrs = cfa_xarray_store.load()
|
|
142
|
+
encoding = cfa_xarray_store.get_encoding()
|
|
143
|
+
|
|
144
|
+
# Ensures variables/attributes comply with CF conventions.
|
|
145
|
+
vars, attrs, coord_names = conventions.decode_cf_variables(
|
|
146
|
+
vars,
|
|
147
|
+
attrs,
|
|
148
|
+
mask_and_scale=mask_and_scale,
|
|
149
|
+
decode_times=decode_times,
|
|
150
|
+
concat_characters=concat_characters,
|
|
151
|
+
decode_coords=decode_coords,
|
|
152
|
+
drop_variables=drop_variables,
|
|
153
|
+
use_cftime=use_cftime,
|
|
154
|
+
decode_timedelta=decode_timedelta,
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
# Create the xarray.Dataset object here.
|
|
158
|
+
if use_active:
|
|
159
|
+
try:
|
|
160
|
+
from XarrayActive import ActiveDataset
|
|
161
|
+
|
|
162
|
+
ds = ActiveDataset(vars, attrs=attrs)
|
|
163
|
+
except ImportError:
|
|
164
|
+
raise ImportError(
|
|
165
|
+
'"ActiveDataset" from XarrayActive failed to import - please '
|
|
166
|
+
'ensure you have the XarrayActive package installed.'
|
|
167
|
+
)
|
|
168
|
+
else:
|
|
169
|
+
ds = Dataset(vars, attrs=attrs)
|
|
170
|
+
|
|
171
|
+
ds = ds.set_coords(coord_names.intersection(vars))
|
|
172
|
+
ds.set_close(cfa_xarray_store.close)
|
|
173
|
+
ds.encoding = encoding
|
|
174
|
+
|
|
175
|
+
return ds
|
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
__author__ = "Daniel Westwood"
|
|
2
|
+
__contact__ = "daniel.westwood@stfc.ac.uk"
|
|
3
|
+
__copyright__ = "Copyright 2024 United Kingdom Research and Innovation"
|
|
4
|
+
|
|
5
|
+
from xarray.backends import (
|
|
6
|
+
NetCDF4DataStore
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
from xarray.core.utils import FrozenDict
|
|
10
|
+
from xarray.core import indexing
|
|
11
|
+
from xarray.coding.variables import pop_to
|
|
12
|
+
from xarray.core.variable import Variable
|
|
13
|
+
|
|
14
|
+
import netCDF4
|
|
15
|
+
import numpy as np
|
|
16
|
+
import re
|
|
17
|
+
|
|
18
|
+
from CFAPyX.wrappers import FragmentArrayWrapper
|
|
19
|
+
from CFAPyX.decoder import get_fragment_positions, get_fragment_extents
|
|
20
|
+
from CFAPyX.group import CFAGroupWrapper
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
xarray_subs = {
|
|
24
|
+
'file:///':'/'
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
class CFADataStore(NetCDF4DataStore):
|
|
28
|
+
|
|
29
|
+
"""
|
|
30
|
+
DataStore container for the CFA-netCDF loaded file. Contains all unpacking routines
|
|
31
|
+
directly related to the specific variables and attributes. The ``NetCDF4Datastore``
|
|
32
|
+
Xarray class from which this class inherits, has an ``__init__`` method which
|
|
33
|
+
cannot easily be overriden, so properties are used instead for specific variables
|
|
34
|
+
that may be un-set at time of use.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def chunks(self):
|
|
39
|
+
if hasattr(self,'_cfa_chunks'):
|
|
40
|
+
return self._cfa_chunks
|
|
41
|
+
return None
|
|
42
|
+
|
|
43
|
+
@chunks.setter
|
|
44
|
+
def chunks(self, value):
|
|
45
|
+
self._cfa_chunks = value
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def cfa_options(self):
|
|
49
|
+
"""
|
|
50
|
+
Property of the datastore that relates private option variables to the standard
|
|
51
|
+
``cfa_options`` parameter.
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
'substitutions': self._substitutions,
|
|
56
|
+
'decode_cfa': self._decode_cfa,
|
|
57
|
+
'chunks': self.chunks,
|
|
58
|
+
'chunk_limits': self._chunk_limits,
|
|
59
|
+
'use_active': self.use_active
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
@cfa_options.setter
|
|
63
|
+
def cfa_options(self, value):
|
|
64
|
+
self._set_cfa_options(**value)
|
|
65
|
+
|
|
66
|
+
def _set_cfa_options(
|
|
67
|
+
self,
|
|
68
|
+
substitutions=None,
|
|
69
|
+
decode_cfa=True,
|
|
70
|
+
chunks={},
|
|
71
|
+
chunk_limits=True,
|
|
72
|
+
use_active=False,
|
|
73
|
+
):
|
|
74
|
+
"""
|
|
75
|
+
Method to set cfa options.
|
|
76
|
+
|
|
77
|
+
:param substitutions: (dict) Set of provided substitutions to Xarray,
|
|
78
|
+
following the CFA conventions on substitutions.
|
|
79
|
+
|
|
80
|
+
:param decode_cfa: (bool) Optional setting to disable CFA decoding
|
|
81
|
+
in some cases, default is True.
|
|
82
|
+
|
|
83
|
+
:param use_active: (bool) Enable for use with XarrayActive.
|
|
84
|
+
|
|
85
|
+
:param chunks: (dict) Not implemented in 2024.9.0
|
|
86
|
+
|
|
87
|
+
:param chunk_limits: (dict) Not implemented in 2024.9.0
|
|
88
|
+
"""
|
|
89
|
+
|
|
90
|
+
self.chunks = chunks
|
|
91
|
+
self._substitutions = substitutions
|
|
92
|
+
self._decode_cfa = decode_cfa
|
|
93
|
+
self._chunk_limits = chunk_limits
|
|
94
|
+
self.use_active = use_active
|
|
95
|
+
|
|
96
|
+
def _acquire(self, needs_lock=True):
|
|
97
|
+
"""
|
|
98
|
+
Fetch the global or group dataset from the Datastore Caching Manager (NetCDF4)
|
|
99
|
+
"""
|
|
100
|
+
with self._manager.acquire_context(needs_lock) as root:
|
|
101
|
+
ds = CFAGroupWrapper.open(root, self._group, self._mode)
|
|
102
|
+
|
|
103
|
+
self.conventions = ds.Conventions
|
|
104
|
+
|
|
105
|
+
return ds
|
|
106
|
+
|
|
107
|
+
def _decode_feature_data(self, feature_data, readd={}):
|
|
108
|
+
"""
|
|
109
|
+
Decode the value of an object which is expected to be of the form of a
|
|
110
|
+
``feature: variable`` blank-separated element list.
|
|
111
|
+
"""
|
|
112
|
+
parts = re.split(': | ',feature_data)
|
|
113
|
+
|
|
114
|
+
# Anything that uses a ':' needs to be readded after the previous step.
|
|
115
|
+
for k, v in readd:
|
|
116
|
+
for p in parts:
|
|
117
|
+
p.replace(k,v)
|
|
118
|
+
|
|
119
|
+
return {k: v for k, v in zip(parts[0::2], parts[1::2])}
|
|
120
|
+
|
|
121
|
+
def _check_applied_conventions(self, agg_data):
|
|
122
|
+
"""
|
|
123
|
+
Check that the aggregated data complies with the conventions specified in the
|
|
124
|
+
CFA-netCDF file
|
|
125
|
+
"""
|
|
126
|
+
|
|
127
|
+
required = ('shape', 'location', 'address')
|
|
128
|
+
if 'CFA-0.6.2' in self.conventions.split(' '):
|
|
129
|
+
required = ('location', 'file', 'format')
|
|
130
|
+
|
|
131
|
+
for feature in required:
|
|
132
|
+
if feature not in agg_data:
|
|
133
|
+
raise ValueError(
|
|
134
|
+
f'CFA-netCDF file is not compliant with {self.conventions} '
|
|
135
|
+
f'Required aggregated data features: "{required}", '
|
|
136
|
+
f'Received "{tuple(agg_data.keys())}"'
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
def _perform_decoding(
|
|
140
|
+
self,
|
|
141
|
+
shape,
|
|
142
|
+
address,
|
|
143
|
+
location,
|
|
144
|
+
array_shape,
|
|
145
|
+
value=None,
|
|
146
|
+
cformat='',
|
|
147
|
+
substitutions=None):
|
|
148
|
+
"""
|
|
149
|
+
Private method for performing the decoding of the standard ``fragment array
|
|
150
|
+
variables``. Any convention version-specific adjustments should be made prior
|
|
151
|
+
to decoding with this function, namely in the public method of the same name.
|
|
152
|
+
|
|
153
|
+
:param shape: (obj) The integer-valued ``shape`` fragment array variable
|
|
154
|
+
defines the shape of each fragment's data in its canonical
|
|
155
|
+
form. CF-1.12 section 2.8.1
|
|
156
|
+
|
|
157
|
+
:param address: (obj) The ``address`` fragment array variable, that may
|
|
158
|
+
have any data type, defines how to find each fragment
|
|
159
|
+
within its fragment dataset. CF-1.12 section 2.8.1
|
|
160
|
+
|
|
161
|
+
:param location: (obj) The string-valued ``location`` fragment array
|
|
162
|
+
variable defines the locations of fragment datasets using
|
|
163
|
+
Uniform Resource Identifiers (URIs). CF-1.12 section 2.8.1
|
|
164
|
+
|
|
165
|
+
:param value: (obj) *Optional* unique data value to fill a fragment array
|
|
166
|
+
where the data values within the fragment are all the same.
|
|
167
|
+
|
|
168
|
+
:param cformat: (str) *Optional* ``format`` argument if provided by the
|
|
169
|
+
CFA-netCDF or cfa-options parameters. CFA-0.6.2
|
|
170
|
+
|
|
171
|
+
:param substitutions: (dict) Set of substitutions to apply in the form 'base':'sub'
|
|
172
|
+
|
|
173
|
+
:returns: (fragment_info) A dictionary of fragment metadata where each
|
|
174
|
+
key is the coordinates of a fragment in index space and the
|
|
175
|
+
value is a dictionary of the attributes specific to that
|
|
176
|
+
fragment.
|
|
177
|
+
|
|
178
|
+
"""
|
|
179
|
+
|
|
180
|
+
fragment_info = {}
|
|
181
|
+
|
|
182
|
+
# Extract non-padded fragment sizes per dimension.
|
|
183
|
+
fragment_size_per_dim = [i.compressed().tolist() for i in shape]
|
|
184
|
+
|
|
185
|
+
# Derive the total shape of the fragment array in all fragmented dimensions.
|
|
186
|
+
fragment_space = [len(fsize) for fsize in fragment_size_per_dim]
|
|
187
|
+
|
|
188
|
+
# Obtain the positions of each fragment in index space.
|
|
189
|
+
fragment_positions = get_fragment_positions(fragment_size_per_dim)
|
|
190
|
+
|
|
191
|
+
global_extent, extent, shapes = get_fragment_extents(
|
|
192
|
+
fragment_size_per_dim,
|
|
193
|
+
array_shape
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
if value is not None:
|
|
197
|
+
# --------------------------------------------------------
|
|
198
|
+
# This fragment contains a constant value, not file
|
|
199
|
+
# locations.
|
|
200
|
+
# --------------------------------------------------------
|
|
201
|
+
fragment_space = value.shape
|
|
202
|
+
fragment_info = {
|
|
203
|
+
frag_pos: {
|
|
204
|
+
"shape": shapes[frag_pos],
|
|
205
|
+
"fill_value": value[frag_pos].item(),
|
|
206
|
+
"global_extent": global_extent[frag_pos],
|
|
207
|
+
"extent": extent[frag_pos],
|
|
208
|
+
"format": "full",
|
|
209
|
+
}
|
|
210
|
+
for frag_pos in fragment_positions
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return fragment_info, fragment_space
|
|
214
|
+
|
|
215
|
+
constructor_shape = location.shape
|
|
216
|
+
|
|
217
|
+
if not address.ndim: # Scalar address
|
|
218
|
+
addr = address.getValue()
|
|
219
|
+
adtype = np.array(addr).dtype
|
|
220
|
+
address = np.full(constructor_shape, addr, dtype=adtype)
|
|
221
|
+
|
|
222
|
+
if cformat != '':
|
|
223
|
+
if not cformat.ndim:
|
|
224
|
+
cft = cformat.getValue()
|
|
225
|
+
npdtype = np.array(cft).dtype
|
|
226
|
+
cformat = np.full(constructor_shape, cft, dtype=npdtype)
|
|
227
|
+
|
|
228
|
+
for frag_pos in fragment_positions:
|
|
229
|
+
|
|
230
|
+
fragment_info[frag_pos] = {
|
|
231
|
+
"shape" : shapes[frag_pos],
|
|
232
|
+
"location" : location[frag_pos],
|
|
233
|
+
"address" : address[frag_pos],
|
|
234
|
+
"extent" : extent[frag_pos],
|
|
235
|
+
"global_extent": global_extent[frag_pos]
|
|
236
|
+
}
|
|
237
|
+
if hasattr(cformat, 'shape'):
|
|
238
|
+
fragment_info[frag_pos]["format"] = cformat[frag_pos]
|
|
239
|
+
|
|
240
|
+
# Apply string substitutions to the fragment filenames
|
|
241
|
+
if substitutions:
|
|
242
|
+
for value in fragment_info.values():
|
|
243
|
+
for base, sub in substitutions.items():
|
|
244
|
+
value["location"] = value["location"].replace(base, sub)
|
|
245
|
+
|
|
246
|
+
return fragment_info, fragment_space
|
|
247
|
+
|
|
248
|
+
# Public class methods
|
|
249
|
+
|
|
250
|
+
def perform_decoding(self, array_shape, agg_data):
|
|
251
|
+
"""
|
|
252
|
+
Public method ``perform_decoding`` involves extracting the aggregated
|
|
253
|
+
information parameters and assembling the required information for actual
|
|
254
|
+
decoding.
|
|
255
|
+
"""
|
|
256
|
+
|
|
257
|
+
# If not raised an error in checking, we can continue.
|
|
258
|
+
self._check_applied_conventions(agg_data)
|
|
259
|
+
|
|
260
|
+
cformat = ''
|
|
261
|
+
value = None
|
|
262
|
+
try:
|
|
263
|
+
if 'CFA-0.6.2' in self.conventions:
|
|
264
|
+
shape = self.ds.variables[agg_data['location']]
|
|
265
|
+
location = self.ds.variables[agg_data['file']]
|
|
266
|
+
cformat = self.ds.variables[agg_data['format']]
|
|
267
|
+
else: # Default to CF-1.12
|
|
268
|
+
shape = self.ds.variables[agg_data['shape']]
|
|
269
|
+
location = self.ds.variables[agg_data['location']]
|
|
270
|
+
if 'value' in agg_data:
|
|
271
|
+
value = self.ds.variables[agg_data['value']]
|
|
272
|
+
|
|
273
|
+
address = self.ds.variables[agg_data['address']]
|
|
274
|
+
except:
|
|
275
|
+
raise ValueError(
|
|
276
|
+
'One or more aggregated data features specified could not be '
|
|
277
|
+
'found in the data: '
|
|
278
|
+
f'"{tuple(agg_data.keys())}"'
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
subs = {}
|
|
282
|
+
if hasattr(location, 'substitutions'):
|
|
283
|
+
subs = location.substitutions.replace('https://', 'https@//')
|
|
284
|
+
subs = self._decode_feature_data(subs, readd={'https://':'https@//'})
|
|
285
|
+
|
|
286
|
+
return self._perform_decoding(shape, address, location, array_shape,
|
|
287
|
+
cformat=cformat, value=value,
|
|
288
|
+
substitutions = xarray_subs | subs)
|
|
289
|
+
# Combine substitutions with known defaults for using in xarray.
|
|
290
|
+
|
|
291
|
+
def get_variables(self):
|
|
292
|
+
"""
|
|
293
|
+
Fetch the netCDF4.Dataset variables and perform some CFA decoding if
|
|
294
|
+
necessary.
|
|
295
|
+
|
|
296
|
+
``ds`` is now a ``GroupedDatasetWrapper`` object from ``CFAPyX.group`` which
|
|
297
|
+
has flattened the group structure and allows fetching of variables and
|
|
298
|
+
attributes from the whole group tree from which a specific group may inherit.
|
|
299
|
+
|
|
300
|
+
:returns: A ``FrozenDict`` Xarray object of the names of all variables,
|
|
301
|
+
and methods to fetch those variables, depending on if those
|
|
302
|
+
variables are standard NetCDF4 or CFA Aggregated variables.
|
|
303
|
+
"""
|
|
304
|
+
|
|
305
|
+
if not self._decode_cfa:
|
|
306
|
+
return FrozenDict(
|
|
307
|
+
(k, self.open_variable(k, v)) for k, v in self.ds.variables.items()
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
# Determine CFA-aggregated variables
|
|
311
|
+
all_vars, real_vars = {}, {}
|
|
312
|
+
|
|
313
|
+
fragment_array_vars = []
|
|
314
|
+
|
|
315
|
+
## Ignore variables in the set of standardised terms.
|
|
316
|
+
for avar in self.ds.variables.keys():
|
|
317
|
+
cfa = False
|
|
318
|
+
## CF-Compliant method of identifying aggregated variables.
|
|
319
|
+
if hasattr(self.ds.variables[avar], 'aggregated_dimensions'):
|
|
320
|
+
cfa = True
|
|
321
|
+
|
|
322
|
+
agg_data = self.ds.variables[avar].aggregated_data.split(' ')
|
|
323
|
+
|
|
324
|
+
for vname in agg_data:
|
|
325
|
+
fragment_array_vars += re.split(': | ',vname)
|
|
326
|
+
|
|
327
|
+
all_vars[avar] = (self.ds.variables[avar], cfa)
|
|
328
|
+
|
|
329
|
+
# Ignore fragment array variables at this stage of decoding.
|
|
330
|
+
for var in all_vars.keys():
|
|
331
|
+
if var not in fragment_array_vars:
|
|
332
|
+
real_vars[var] = all_vars[var]
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
return FrozenDict(
|
|
336
|
+
(k, self.open_variable(k, v)) for k, v in real_vars.items()
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
def get_attrs(self):
|
|
340
|
+
"""
|
|
341
|
+
Produce the FrozenDict of attributes from the ``NetCDF4.Dataset`` or
|
|
342
|
+
``CFAGroupWrapper`` in the case of using a group or nested group tree.
|
|
343
|
+
"""
|
|
344
|
+
return FrozenDict((k, self.ds.getncattr(k)) for k in self.ds.ncattrs())
|
|
345
|
+
|
|
346
|
+
def open_variable(self, name: str, var):
|
|
347
|
+
"""
|
|
348
|
+
Open a CFA-netCDF variable as either a standard NetCDF4 Datastore variable
|
|
349
|
+
or as a CFA aggregated variable which requires additional decoding.
|
|
350
|
+
|
|
351
|
+
:param name: (str) A named NetCDF4 variable.
|
|
352
|
+
|
|
353
|
+
:param var: (obj) The NetCDF4.Variable object or a tuple with the contents
|
|
354
|
+
``(NetCDF4.Variable, cfa)`` where ``cfa`` is a bool that
|
|
355
|
+
determines if the variable is a CFA or standard variable.
|
|
356
|
+
|
|
357
|
+
:returns: The variable object opened as either a standard store variable
|
|
358
|
+
or CFA aggregated variable.
|
|
359
|
+
"""
|
|
360
|
+
if type(var) == tuple:
|
|
361
|
+
if var[1] and self._decode_cfa:
|
|
362
|
+
variable = self.open_cfa_variable(name, var[0])
|
|
363
|
+
else:
|
|
364
|
+
variable = self.open_store_variable(name, var[0])
|
|
365
|
+
else:
|
|
366
|
+
variable = self.open_store_variable(name, var)
|
|
367
|
+
return variable
|
|
368
|
+
|
|
369
|
+
def open_cfa_variable(self, name: str, var):
|
|
370
|
+
"""
|
|
371
|
+
Open a CFA Aggregated variable with the correct parameters to create an
|
|
372
|
+
Xarray ``Variable`` instance.
|
|
373
|
+
|
|
374
|
+
:param name: (str) A named NetCDF4 variable.
|
|
375
|
+
|
|
376
|
+
:param var: (obj) The NetCDF4.Variable object or a tuple with the
|
|
377
|
+
contents ``(NetCDF4.Variable, cfa)`` where ``cfa`` is
|
|
378
|
+
a bool that determines if the variable is a CFA or
|
|
379
|
+
standard variable.
|
|
380
|
+
|
|
381
|
+
:returns: An xarray ``Variable`` instance constructed from the
|
|
382
|
+
attributes provided here, and data provided by a
|
|
383
|
+
``FragmentArrayWrapper`` which is indexed by Xarray's
|
|
384
|
+
``LazilyIndexedArray`` class.
|
|
385
|
+
"""
|
|
386
|
+
|
|
387
|
+
real_dims = {
|
|
388
|
+
d: self.ds.dimensions[d].size for d in var.aggregated_dimensions.split(' ')
|
|
389
|
+
}
|
|
390
|
+
agg_data = self._decode_feature_data(var.aggregated_data)
|
|
391
|
+
|
|
392
|
+
## Array Metadata
|
|
393
|
+
dimensions = tuple(real_dims.keys())
|
|
394
|
+
array_shape = tuple(real_dims.values())
|
|
395
|
+
|
|
396
|
+
fragment_info, fragment_space = self.perform_decoding(array_shape, agg_data)
|
|
397
|
+
|
|
398
|
+
units = ''
|
|
399
|
+
if hasattr(var, 'units'):
|
|
400
|
+
units = getattr(var, 'units')
|
|
401
|
+
if hasattr(var, 'aggregated_units'):
|
|
402
|
+
units = getattr(var, 'aggregated_units')
|
|
403
|
+
|
|
404
|
+
## Get non-aggregated attributes.
|
|
405
|
+
attributes = {}
|
|
406
|
+
for k in var.ncattrs():
|
|
407
|
+
if 'aggregated' not in k:
|
|
408
|
+
attributes[k] = var.getncattr(k)
|
|
409
|
+
|
|
410
|
+
## Array-like object
|
|
411
|
+
data = indexing.LazilyIndexedArray(
|
|
412
|
+
FragmentArrayWrapper(
|
|
413
|
+
fragment_info,
|
|
414
|
+
fragment_space,
|
|
415
|
+
shape=array_shape,
|
|
416
|
+
units=units,
|
|
417
|
+
dtype=var.dtype,
|
|
418
|
+
cfa_options=self.cfa_options,
|
|
419
|
+
named_dims=dimensions,
|
|
420
|
+
))
|
|
421
|
+
|
|
422
|
+
encoding = {}
|
|
423
|
+
if isinstance(var.datatype, netCDF4.EnumType):
|
|
424
|
+
encoding["dtype"] = np.dtype(
|
|
425
|
+
data.dtype,
|
|
426
|
+
metadata={
|
|
427
|
+
"enum": var.datatype.enum_dict,
|
|
428
|
+
"enum_name": var.datatype.name,
|
|
429
|
+
},
|
|
430
|
+
)
|
|
431
|
+
else:
|
|
432
|
+
encoding["dtype"] = var.dtype
|
|
433
|
+
|
|
434
|
+
if data.dtype.kind == "S" and "_FillValue" in attributes:
|
|
435
|
+
attributes["_FillValue"] = np.bytes_(attributes["_FillValue"])
|
|
436
|
+
|
|
437
|
+
filters = var.filters()
|
|
438
|
+
if filters is not None:
|
|
439
|
+
encoding.update(filters)
|
|
440
|
+
chunking = var.chunking()
|
|
441
|
+
if chunking is not None:
|
|
442
|
+
if chunking == "contiguous":
|
|
443
|
+
encoding["contiguous"] = True
|
|
444
|
+
encoding["chunksizes"] = None
|
|
445
|
+
else:
|
|
446
|
+
encoding["contiguous"] = False
|
|
447
|
+
encoding["chunksizes"] = tuple(chunking)
|
|
448
|
+
encoding["preferred_chunks"] = dict(zip(var.dimensions, chunking))
|
|
449
|
+
# TODO: figure out how to round-trip "endian-ness" without raising
|
|
450
|
+
# warnings from netCDF4
|
|
451
|
+
# encoding['endian'] = var.endian()
|
|
452
|
+
pop_to(attributes, encoding, "least_significant_digit")
|
|
453
|
+
# save source so __repr__ can detect if it's local or not
|
|
454
|
+
encoding["source"] = self._filename
|
|
455
|
+
encoding["original_shape"] = data.shape
|
|
456
|
+
|
|
457
|
+
v = Variable(dimensions, data, attributes, encoding)
|
|
458
|
+
return v
|
|
459
|
+
|