obsarray 1.0.2__py3-none-any.whl
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.
- docs/conf.py +181 -0
- notebook/test_register_err_corr_form.py +45 -0
- obsarray/__init__.py +14 -0
- obsarray/_version.py +21 -0
- obsarray/err_corr.py +298 -0
- obsarray/flag_accessor.py +442 -0
- obsarray/templater/__init__.py +0 -0
- obsarray/templater/dataset_util.py +659 -0
- obsarray/templater/dstemplater.py +133 -0
- obsarray/templater/dswriter.py +113 -0
- obsarray/templater/template_util.py +257 -0
- obsarray/templater/tests/__init__.py +0 -0
- obsarray/templater/tests/test_dataset_util.py +488 -0
- obsarray/templater/tests/test_dswriter.py +31 -0
- obsarray/templater/tests/test_template_util.py +334 -0
- obsarray/test/__init__.py +0 -0
- obsarray/test/test_err_corr_forms.py +251 -0
- obsarray/test/test_flag_accessor.py +373 -0
- obsarray/test/test_unc_accessor.py +745 -0
- obsarray/unc_accessor.py +899 -0
- obsarray/utils.py +31 -0
- obsarray-1.0.2.dist-info/METADATA +57 -0
- obsarray-1.0.2.dist-info/RECORD +26 -0
- obsarray-1.0.2.dist-info/WHEEL +5 -0
- obsarray-1.0.2.dist-info/licenses/LICENSE +165 -0
- obsarray-1.0.2.dist-info/top_level.txt +3 -0
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
"""flag_accessor - xarray extensions with accessor objects for flag handling"""
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import xarray as xr
|
|
5
|
+
from typing import List, Optional, Union, Tuple
|
|
6
|
+
from obsarray.templater.template_util import create_var
|
|
7
|
+
from obsarray.templater.dataset_util import DatasetUtil
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
__author__ = "Sam Hunt <sam.hunt@npl.co.uk>"
|
|
11
|
+
__all__ = []
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Flag:
|
|
15
|
+
"""
|
|
16
|
+
Interface for handling ``xarray.Dataset`` flag variable flags
|
|
17
|
+
|
|
18
|
+
:param xarray_obj: dataset
|
|
19
|
+
:param flag_var_name: name of flag variable
|
|
20
|
+
:param flag_meaning: specific flag from flag variable
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(
|
|
24
|
+
self,
|
|
25
|
+
xarray_obj: xr.Dataset,
|
|
26
|
+
flag_var_name: str,
|
|
27
|
+
flag_meaning: str,
|
|
28
|
+
):
|
|
29
|
+
|
|
30
|
+
# initialise attributes
|
|
31
|
+
|
|
32
|
+
self._obj = xarray_obj
|
|
33
|
+
self._flag_var_name = flag_var_name
|
|
34
|
+
self._flag_meaning = flag_meaning
|
|
35
|
+
self._sli = tuple([slice(None)] * self._obj[self._flag_var_name].ndim)
|
|
36
|
+
|
|
37
|
+
def __str__(self):
|
|
38
|
+
"""Custom __str__"""
|
|
39
|
+
return "<{}> \n{}".format(
|
|
40
|
+
self.__class__.__name__,
|
|
41
|
+
self.value.__repr__()[18:],
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
def __repr__(self):
|
|
45
|
+
"""Custom __repr__"""
|
|
46
|
+
return str(self)
|
|
47
|
+
|
|
48
|
+
def __getitem__(self, sli: tuple):
|
|
49
|
+
"""
|
|
50
|
+
Defines variable slice
|
|
51
|
+
|
|
52
|
+
:param sli: slice of variable
|
|
53
|
+
:return: self
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
# update slice
|
|
57
|
+
self._sli = self._expand_sli(sli)
|
|
58
|
+
|
|
59
|
+
return self
|
|
60
|
+
|
|
61
|
+
def __setitem__(self, sli: tuple, flag_value):
|
|
62
|
+
"""
|
|
63
|
+
Sets flag values
|
|
64
|
+
|
|
65
|
+
:param sli: slice of variable
|
|
66
|
+
:param flag_value: flag value as a single value boolean to apply to all data or boolean mask array
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
self._sli = self._expand_sli(sli)
|
|
70
|
+
|
|
71
|
+
flag_meanings, flag_masks = DatasetUtil.unpack_flag_attrs(
|
|
72
|
+
self._obj[self._flag_var_name].attrs
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
flag_bit = flag_meanings.index(self._flag_meaning)
|
|
76
|
+
flag_mask = flag_masks[flag_bit]
|
|
77
|
+
|
|
78
|
+
# if boolean to apply to all data
|
|
79
|
+
if isinstance(flag_value, bool):
|
|
80
|
+
if flag_value:
|
|
81
|
+
self._obj[self._flag_var_name].values[self._sli] = (
|
|
82
|
+
self._obj[self._flag_var_name][self._sli].values | flag_mask
|
|
83
|
+
)
|
|
84
|
+
else:
|
|
85
|
+
self._obj[self._flag_var_name].values[self._sli] = (
|
|
86
|
+
self._obj[self._flag_var_name][self._sli].values & ~flag_mask
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
# else apply mask
|
|
90
|
+
else:
|
|
91
|
+
if flag_value.dtype is not np.dtype(bool):
|
|
92
|
+
TypeError("Flag mask must of boolean type")
|
|
93
|
+
|
|
94
|
+
i_true = np.where(flag_value == True)
|
|
95
|
+
i_false = np.where(flag_value == False)
|
|
96
|
+
|
|
97
|
+
self._obj[self._flag_var_name][self._sli].values[i_true] = np.array(
|
|
98
|
+
self._obj[self._flag_var_name][self._sli].values[i_true] | flag_mask
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
self._obj[self._flag_var_name][self._sli].values[i_false] = np.array(
|
|
102
|
+
self._obj[self._flag_var_name][self._sli].values[i_false] & ~flag_mask
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
def _expand_sli(self, sli: Optional[tuple] = None) -> tuple:
|
|
106
|
+
"""
|
|
107
|
+
Function to expand the provided sli so that it always has the right number of dimensions
|
|
108
|
+
|
|
109
|
+
:param sli: input sli tuple. This one can have fewer dimensions than the total if e.g. only providing the first index
|
|
110
|
+
:return: output sli tuple
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
# if no slice provided, define as slice for full array
|
|
114
|
+
if sli is None:
|
|
115
|
+
out_sli = tuple([slice(None)] * self._obj[self._flag_var_name].ndim)
|
|
116
|
+
|
|
117
|
+
# Otherwise, set each : dimension to slice(None)
|
|
118
|
+
# E.g. if providing [0] for a variable with 3 dimensions, this becomes
|
|
119
|
+
# [0,slice(None),slice(None)]
|
|
120
|
+
else:
|
|
121
|
+
out_sli = list([slice(None)] * self._obj[self._flag_var_name].ndim)
|
|
122
|
+
sli_list = list(sli) if isinstance(sli, tuple) else [sli]
|
|
123
|
+
for i in range(len(sli_list)):
|
|
124
|
+
if not sli_list[i] == ":":
|
|
125
|
+
out_sli[i] = sli_list[i]
|
|
126
|
+
out_sli = tuple(out_sli)
|
|
127
|
+
|
|
128
|
+
return out_sli
|
|
129
|
+
|
|
130
|
+
@property
|
|
131
|
+
def value(self) -> xr.DataArray:
|
|
132
|
+
"""
|
|
133
|
+
Return flag variable flag value
|
|
134
|
+
|
|
135
|
+
:return: flag variable flag value
|
|
136
|
+
"""
|
|
137
|
+
|
|
138
|
+
flag_meanings, flag_masks = DatasetUtil.unpack_flag_attrs(
|
|
139
|
+
self._obj[self._flag_var_name].attrs
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
flag_bit = flag_meanings.index(self._flag_meaning)
|
|
143
|
+
flag_mask = flag_masks[flag_bit]
|
|
144
|
+
|
|
145
|
+
value = xr.DataArray(
|
|
146
|
+
np.zeros(self._obj[self._flag_var_name][self._sli].shape, dtype=bool),
|
|
147
|
+
dims=list(self._obj[self._flag_var_name][self._sli].dims),
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
value.values[:] = (
|
|
151
|
+
self._obj[self._flag_var_name][self._sli] & flag_mask
|
|
152
|
+
).astype(bool)
|
|
153
|
+
|
|
154
|
+
return value
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class FlagVariable:
|
|
158
|
+
"""
|
|
159
|
+
Interface for handling ``xarray.Dataset`` flag variables
|
|
160
|
+
|
|
161
|
+
:param xarray_obj: dataset
|
|
162
|
+
:param flag_var_name: name of flag variable
|
|
163
|
+
"""
|
|
164
|
+
|
|
165
|
+
def __init__(self, xarray_obj: xr.Dataset, flag_var_name: str):
|
|
166
|
+
|
|
167
|
+
# Initialise attributes
|
|
168
|
+
self._obj = xarray_obj
|
|
169
|
+
self._flag_var_name = flag_var_name
|
|
170
|
+
|
|
171
|
+
def __getitem__(self, key: str) -> "Flag":
|
|
172
|
+
"""
|
|
173
|
+
Returns flag variable flag interface
|
|
174
|
+
|
|
175
|
+
:param key: flag meaning
|
|
176
|
+
:return: flag interface
|
|
177
|
+
"""
|
|
178
|
+
|
|
179
|
+
return Flag(self._obj, self._flag_var_name, key)
|
|
180
|
+
|
|
181
|
+
def __setitem__(self, flag_meaning: str, flag_value: Union[bool, np.ndarray]):
|
|
182
|
+
"""
|
|
183
|
+
Sets defined flag variable flag, if flag is:
|
|
184
|
+
|
|
185
|
+
* an existing flag (i.e., in variable ``flag_meanings`` list), sets data for that flag
|
|
186
|
+
* not an existing flag variable, that flag is added to ``flag_meanings`` and has its data set. Note this fails if max number of flags for flag variable dtype already defined (e.g. 8 flags defined for ``int8`` variable)
|
|
187
|
+
|
|
188
|
+
:param flag_meaning: flag meaning name
|
|
189
|
+
:param flag_value: flag value as a single value boolean to apply to all data or boolean mask array
|
|
190
|
+
"""
|
|
191
|
+
|
|
192
|
+
flag_meanings, flag_masks = DatasetUtil.unpack_flag_attrs(
|
|
193
|
+
self._obj[self._flag_var_name].attrs
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
if flag_meaning not in flag_meanings:
|
|
197
|
+
self._obj[self._flag_var_name].attrs = (
|
|
198
|
+
DatasetUtil.add_flag_meaning_to_attrs(
|
|
199
|
+
self._obj[self._flag_var_name].attrs,
|
|
200
|
+
flag_meaning,
|
|
201
|
+
self._obj[self._flag_var_name].dtype,
|
|
202
|
+
)
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
self[flag_meaning][:] = flag_value
|
|
206
|
+
|
|
207
|
+
def __delitem__(self, flag_meaning):
|
|
208
|
+
"""
|
|
209
|
+
Removes defined flag variable flag
|
|
210
|
+
|
|
211
|
+
:param flag_meaning: flag meaning name
|
|
212
|
+
"""
|
|
213
|
+
|
|
214
|
+
self[flag_meaning][:] = False
|
|
215
|
+
|
|
216
|
+
self._obj[self._flag_var_name].attrs = DatasetUtil.rm_flag_meaning_from_attrs(
|
|
217
|
+
self._obj[self._flag_var_name].attrs,
|
|
218
|
+
flag_meaning,
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
def __str__(self):
|
|
222
|
+
"""Custom __str__"""
|
|
223
|
+
return "<{}>\nFlagVariable: '{}'\n{}".format(
|
|
224
|
+
self.__class__.__name__,
|
|
225
|
+
self._flag_var_name,
|
|
226
|
+
self.keys().__repr__(),
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
def __repr__(self):
|
|
230
|
+
"""Custom __repr__"""
|
|
231
|
+
return str(self)
|
|
232
|
+
|
|
233
|
+
def __len__(self) -> int:
|
|
234
|
+
"""
|
|
235
|
+
Returns number of flag variable flags
|
|
236
|
+
|
|
237
|
+
:returns: number of flag variable flags
|
|
238
|
+
"""
|
|
239
|
+
return len(self.keys())
|
|
240
|
+
|
|
241
|
+
def __iter__(self):
|
|
242
|
+
"""Custom __iter__"""
|
|
243
|
+
|
|
244
|
+
self.i = 0 # Define counter
|
|
245
|
+
return self
|
|
246
|
+
|
|
247
|
+
def __next__(self) -> Flag:
|
|
248
|
+
"""
|
|
249
|
+
Returns ith flag in flag variable
|
|
250
|
+
|
|
251
|
+
:return: flag variable flag
|
|
252
|
+
"""
|
|
253
|
+
|
|
254
|
+
# Iterate through flag variable flags
|
|
255
|
+
if self.i < len(self.keys()):
|
|
256
|
+
self.i += 1 # Update counter
|
|
257
|
+
return self[self.keys()[self.i - 1]]
|
|
258
|
+
|
|
259
|
+
else:
|
|
260
|
+
raise StopIteration
|
|
261
|
+
|
|
262
|
+
def keys(self) -> List[str]:
|
|
263
|
+
"""
|
|
264
|
+
Returns flag variable flag names
|
|
265
|
+
|
|
266
|
+
:return: flag variable flag names
|
|
267
|
+
"""
|
|
268
|
+
flag_meanings, _ = DatasetUtil.unpack_flag_attrs(
|
|
269
|
+
self._obj[self._flag_var_name].attrs
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
return flag_meanings
|
|
273
|
+
|
|
274
|
+
# todo - add return set flags - array of objects, where object is list of set flags
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
@xr.register_dataset_accessor("flag")
|
|
278
|
+
class FlagAccessor(object):
|
|
279
|
+
"""
|
|
280
|
+
``xarray.Dataset`` accesssor object for handling dataset variable flags
|
|
281
|
+
|
|
282
|
+
:param xarray_obj: xarray dataset
|
|
283
|
+
"""
|
|
284
|
+
|
|
285
|
+
def __init__(self, xarray_obj: xr.Dataset):
|
|
286
|
+
|
|
287
|
+
# Initialise attributes
|
|
288
|
+
self._obj = xarray_obj
|
|
289
|
+
|
|
290
|
+
def __str__(self) -> str:
|
|
291
|
+
"""Custom __str__"""
|
|
292
|
+
|
|
293
|
+
string = "<{}>\nDataset Flags:\n".format(self.__class__.__name__)
|
|
294
|
+
|
|
295
|
+
for var_name in self.keys():
|
|
296
|
+
string += "* {}\n".format(self[var_name].__repr__())
|
|
297
|
+
|
|
298
|
+
return string
|
|
299
|
+
|
|
300
|
+
def __repr__(self) -> str:
|
|
301
|
+
"""Custom __repr__"""
|
|
302
|
+
return str(self)
|
|
303
|
+
|
|
304
|
+
def __getitem__(self, flag_var_name: str) -> FlagVariable:
|
|
305
|
+
"""Custom __repr__"""
|
|
306
|
+
return FlagVariable(self._obj, flag_var_name)
|
|
307
|
+
|
|
308
|
+
def __setitem__(
|
|
309
|
+
self,
|
|
310
|
+
flag_var_name: str,
|
|
311
|
+
flag_def: Union[xr.DataArray, xr.Variable, Tuple[List[str], dict]],
|
|
312
|
+
):
|
|
313
|
+
"""
|
|
314
|
+
Adds defined flag variable to dataset
|
|
315
|
+
|
|
316
|
+
:param flag_var_name: flag variable name
|
|
317
|
+
:param flag_def: either xarray DataArray/Variable, or definition through tuple as ``(dims, attrs)``. ``dims`` is a list of variable dimension names, and ``attrs`` is a dictionary of variable attributes. ``attrs`` should include an element ``flag_meanings`` which is a list defining the flag variable flags.
|
|
318
|
+
"""
|
|
319
|
+
|
|
320
|
+
# add uncertainty variable
|
|
321
|
+
if type(flag_def) == xr.DataArray:
|
|
322
|
+
|
|
323
|
+
# add necessary flag metadata if missing from provided data array
|
|
324
|
+
flag_meanings, flag_masks = DatasetUtil.unpack_flag_attrs(flag_def.attrs)
|
|
325
|
+
flag_def.attrs.update(DatasetUtil.pack_flag_attrs(flag_meanings))
|
|
326
|
+
|
|
327
|
+
self._obj[flag_var_name] = flag_def
|
|
328
|
+
|
|
329
|
+
# use templater functionality if var defined with tuple
|
|
330
|
+
elif type(flag_def) == tuple:
|
|
331
|
+
|
|
332
|
+
attrs = flag_def[1]
|
|
333
|
+
|
|
334
|
+
if "flag_meanings" not in attrs:
|
|
335
|
+
attrs["flag_meanings"] = []
|
|
336
|
+
|
|
337
|
+
var_attrs = {
|
|
338
|
+
"dtype": "flag",
|
|
339
|
+
"dim": flag_def[0],
|
|
340
|
+
"attributes": attrs,
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
size = {d: len(self._obj[d]) for d in flag_def[0]}
|
|
344
|
+
|
|
345
|
+
var = create_var(flag_var_name, var_attrs, size)
|
|
346
|
+
|
|
347
|
+
self._obj[flag_var_name] = var
|
|
348
|
+
|
|
349
|
+
def __len__(self):
|
|
350
|
+
"""
|
|
351
|
+
Returns number of flag variables
|
|
352
|
+
|
|
353
|
+
:return: number of flag variables
|
|
354
|
+
"""
|
|
355
|
+
|
|
356
|
+
return len(self.keys())
|
|
357
|
+
|
|
358
|
+
def __iter__(self) -> "FlagAccessor":
|
|
359
|
+
"""
|
|
360
|
+
Initialises iterator
|
|
361
|
+
|
|
362
|
+
:return: self
|
|
363
|
+
"""
|
|
364
|
+
self.i = 0 # Define counter
|
|
365
|
+
return self
|
|
366
|
+
|
|
367
|
+
def __next__(self):
|
|
368
|
+
"""
|
|
369
|
+
Returns ith flag variable
|
|
370
|
+
|
|
371
|
+
:return: ith flag variable
|
|
372
|
+
"""
|
|
373
|
+
|
|
374
|
+
# Iterate through flag variables
|
|
375
|
+
if self.i < len(self.keys()):
|
|
376
|
+
self.i += 1 # Update counter
|
|
377
|
+
return self[self.keys()[self.i - 1]]
|
|
378
|
+
|
|
379
|
+
else:
|
|
380
|
+
raise StopIteration
|
|
381
|
+
|
|
382
|
+
def keys(self):
|
|
383
|
+
"""
|
|
384
|
+
Returns flag variable names
|
|
385
|
+
|
|
386
|
+
:return: flag variable names
|
|
387
|
+
"""
|
|
388
|
+
|
|
389
|
+
return list(self._obj.flag.flag_vars.keys())
|
|
390
|
+
|
|
391
|
+
@property
|
|
392
|
+
def data_vars(self) -> xr.core.dataset.DataVariables:
|
|
393
|
+
"""
|
|
394
|
+
Returns dataset data variables (defined as dataset variables that are not themselves flags)
|
|
395
|
+
|
|
396
|
+
:return: data variables
|
|
397
|
+
"""
|
|
398
|
+
data_var_names = []
|
|
399
|
+
for var_name in self._obj.variables:
|
|
400
|
+
if self._is_data_var(var_name):
|
|
401
|
+
data_var_names.append(var_name)
|
|
402
|
+
return self._obj[data_var_names].data_vars
|
|
403
|
+
|
|
404
|
+
@property
|
|
405
|
+
def flag_vars(self) -> xr.core.dataset.DataVariables:
|
|
406
|
+
"""
|
|
407
|
+
Returns dataset flag variables (defined as flags associated with data variables)
|
|
408
|
+
|
|
409
|
+
:return: flag variables
|
|
410
|
+
"""
|
|
411
|
+
flag_var_names = []
|
|
412
|
+
for var_name in self._obj.variables:
|
|
413
|
+
if self._is_flag_var(var_name):
|
|
414
|
+
flag_var_names.append(var_name)
|
|
415
|
+
|
|
416
|
+
return self._obj[list(flag_var_names)].data_vars
|
|
417
|
+
|
|
418
|
+
def _is_data_var(self, var_name: str) -> bool:
|
|
419
|
+
"""
|
|
420
|
+
Returns true if named dataset variable is an data variable (i.e. not a flag variable)
|
|
421
|
+
|
|
422
|
+
:return: data variable bool
|
|
423
|
+
"""
|
|
424
|
+
|
|
425
|
+
if not self._is_flag_var(var_name):
|
|
426
|
+
return True
|
|
427
|
+
return False
|
|
428
|
+
|
|
429
|
+
def _is_flag_var(self, var_name: str) -> bool:
|
|
430
|
+
"""
|
|
431
|
+
Returns true if named dataset variable is an flag variable
|
|
432
|
+
|
|
433
|
+
:return: flag variable bool
|
|
434
|
+
"""
|
|
435
|
+
|
|
436
|
+
if "flag_meanings" in self._obj[var_name].attrs:
|
|
437
|
+
return True
|
|
438
|
+
return False
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
if __name__ == "__main__":
|
|
442
|
+
pass
|
|
File without changes
|