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,659 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Utilities for creating xarray dataset variables in specified forms
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import string
|
|
6
|
+
from copy import deepcopy
|
|
7
|
+
import xarray
|
|
8
|
+
import numpy
|
|
9
|
+
from typing import Union, Optional, List, Dict, Tuple
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
__author__ = "Sam Hunt <sam.hunt@npl.co.uk>"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
DEFAULT_DIM_NAMES = list(string.ascii_lowercase[-3:]) + list(
|
|
16
|
+
string.ascii_lowercase[:-3]
|
|
17
|
+
)
|
|
18
|
+
DEFAULT_DIM_NAMES.reverse()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
ERR_CORR_DEFS = {
|
|
22
|
+
"random": {"n_params": 0},
|
|
23
|
+
"rectangle_absolute": {"n_params": 2},
|
|
24
|
+
"rectangular_relative": {"n_params": 2},
|
|
25
|
+
"triangular_relative": {"n_params": 2},
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class DatasetUtil:
|
|
30
|
+
"""
|
|
31
|
+
Class to provide utilities for generating standard xarray DataArrays and Variables
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
@staticmethod
|
|
35
|
+
def create_default_array(
|
|
36
|
+
dim_sizes: List[int],
|
|
37
|
+
dtype: numpy.typecodes,
|
|
38
|
+
dim_names: Optional[List[str]] = None,
|
|
39
|
+
fill_value: Optional[Union[int, float]] = None,
|
|
40
|
+
) -> xarray.DataArray:
|
|
41
|
+
"""
|
|
42
|
+
Return default empty xarray DataArray
|
|
43
|
+
|
|
44
|
+
:param dim_sizes: dimension sizes, i.e. ``[dim1_size, dim2_size, dim3_size]`` (e.g. ``[2,3,5]``)
|
|
45
|
+
:param dtype: numpy data type
|
|
46
|
+
:param dim_names: dimension names, i.e. ``["dim1_name", "dim2_name", "dim3_name"]``
|
|
47
|
+
:param fill_value: fill value (if None CF compliant value used)
|
|
48
|
+
|
|
49
|
+
:returns: Default empty array
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
if fill_value is None:
|
|
53
|
+
fill_value = DatasetUtil.get_default_fill_value(dtype)
|
|
54
|
+
|
|
55
|
+
empty_array = numpy.full(dim_sizes, fill_value, dtype)
|
|
56
|
+
|
|
57
|
+
if dim_names is not None:
|
|
58
|
+
default_array = xarray.DataArray(empty_array, dims=dim_names)
|
|
59
|
+
else:
|
|
60
|
+
default_array = xarray.DataArray(
|
|
61
|
+
empty_array, dims=DEFAULT_DIM_NAMES[-len(dim_sizes) :]
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
return default_array
|
|
65
|
+
|
|
66
|
+
@staticmethod
|
|
67
|
+
def create_variable(
|
|
68
|
+
dim_sizes: List[int],
|
|
69
|
+
dtype: numpy.typecodes,
|
|
70
|
+
dim_names: Optional[List[str]] = None,
|
|
71
|
+
attributes: Dict = None,
|
|
72
|
+
fill_value: Optional[Union[int, float]] = None,
|
|
73
|
+
) -> xarray.Variable:
|
|
74
|
+
"""
|
|
75
|
+
Return default empty xarray Variable
|
|
76
|
+
|
|
77
|
+
:param dim_sizes: dimension sizes, i.e. ``[dim1_size, dim2_size, dim3_size]`` (e.g. ``[2,3,5]``)
|
|
78
|
+
:param dtype: numpy data type
|
|
79
|
+
:param dim_names: dimension names as strings, i.e. ``["dim1_name", "dim2_name", "dim3_size"]``
|
|
80
|
+
:param attributes: dictionary of variable attributes, e.g. standard_name
|
|
81
|
+
:param fill_value: fill value (if None CF compliant value used)
|
|
82
|
+
|
|
83
|
+
:returns: Default empty variable
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
if fill_value is None:
|
|
87
|
+
fill_value = DatasetUtil.get_default_fill_value(dtype)
|
|
88
|
+
|
|
89
|
+
default_array = DatasetUtil.create_default_array(
|
|
90
|
+
dim_sizes, dtype, dim_names, fill_value=fill_value
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
if dim_names is None:
|
|
94
|
+
variable = xarray.Variable(
|
|
95
|
+
DEFAULT_DIM_NAMES[-len(dim_sizes) :], default_array
|
|
96
|
+
)
|
|
97
|
+
else:
|
|
98
|
+
variable = xarray.Variable(dim_names, default_array)
|
|
99
|
+
|
|
100
|
+
variable.attrs["_FillValue"] = fill_value
|
|
101
|
+
|
|
102
|
+
if attributes is not None:
|
|
103
|
+
variable.attrs = {**variable.attrs, **attributes}
|
|
104
|
+
|
|
105
|
+
return variable
|
|
106
|
+
|
|
107
|
+
@staticmethod
|
|
108
|
+
def create_unc_variable(
|
|
109
|
+
dim_sizes: List[int],
|
|
110
|
+
dtype: numpy.typecodes,
|
|
111
|
+
dim_names: List[str],
|
|
112
|
+
attributes: Optional[Dict] = None,
|
|
113
|
+
pdf_shape: str = "gaussian",
|
|
114
|
+
err_corr: Optional[List[Dict[str, Union[str, List]]]] = None,
|
|
115
|
+
) -> xarray.Variable:
|
|
116
|
+
"""
|
|
117
|
+
Return default empty 1d xarray uncertainty Variable
|
|
118
|
+
|
|
119
|
+
:param dim_sizes: dimension sizes, i.e. ``[dim1_size, dim2_size, dim3_size]`` (e.g. ``[2,3,5]``)
|
|
120
|
+
:param dtype: data type
|
|
121
|
+
:param dim_names: dimension names, i.e. ``["dim1_name", "dim2_name", "dim3_size"]``
|
|
122
|
+
:param attributes: dictionary of variable attributes, e.g. standard_name
|
|
123
|
+
:param pdf_shape: (default: `"gaussian"`) pdf shape of uncertainties
|
|
124
|
+
:param err_corr: uncertainty error-correlation structure definition, defined as below.
|
|
125
|
+
|
|
126
|
+
:returns: Default empty flag vector variable
|
|
127
|
+
|
|
128
|
+
Each element of ``err_corr`` is a dictionary that defines the error-correlation along one or more dimensions,
|
|
129
|
+
which should include the following entries:
|
|
130
|
+
|
|
131
|
+
* ``dim`` (*str*/*list*) - name of the dimension(s) as a ``str`` or list of ``str``'s (i.e. from ``dim_names``)
|
|
132
|
+
* ``form`` (*str*) - error-correlation form name, functional form of error-correlation structure for
|
|
133
|
+
dimension(s)
|
|
134
|
+
* ``params`` (*list*) - (optional) parameters of the error-correlation structure defining function for dimension
|
|
135
|
+
if required. The number of parameters required depends on the particular form.
|
|
136
|
+
* ``units`` (*list*) - (optional) units of the error-correlation function parameters for dimension
|
|
137
|
+
(ordered as the parameters)
|
|
138
|
+
|
|
139
|
+
For more information on the required form of these entries, see the :ref:`uncertainties section <err corr>` of
|
|
140
|
+
the user guide.
|
|
141
|
+
|
|
142
|
+
.. note::
|
|
143
|
+
If the error-correlation structure is not defined along a particular dimension (i.e. it is not
|
|
144
|
+
included in ``err_corr``), the error-correlation is assumed random. Variable attributes are
|
|
145
|
+
populated to the effect of this assumption.
|
|
146
|
+
"""
|
|
147
|
+
|
|
148
|
+
# define uncertainty variable attributes, based on FIDUCEO Full FCDR definition (if required)
|
|
149
|
+
attributes = {} if attributes is None else attributes
|
|
150
|
+
|
|
151
|
+
if err_corr is None:
|
|
152
|
+
err_corr = []
|
|
153
|
+
|
|
154
|
+
# set undefined dims as random
|
|
155
|
+
defined_err_corr_dims = []
|
|
156
|
+
for erd in err_corr:
|
|
157
|
+
if isinstance(erd["dim"], str):
|
|
158
|
+
defined_err_corr_dims.append(erd["dim"])
|
|
159
|
+
else:
|
|
160
|
+
defined_err_corr_dims.extend(erd["dim"])
|
|
161
|
+
|
|
162
|
+
missing_err_corr_dims = [
|
|
163
|
+
dim for dim in dim_names if dim not in defined_err_corr_dims
|
|
164
|
+
]
|
|
165
|
+
for missing_err_corr_dim in missing_err_corr_dims:
|
|
166
|
+
err_corr.append({"dim": missing_err_corr_dim, "form": "random"})
|
|
167
|
+
|
|
168
|
+
for i, ecdef in enumerate(err_corr):
|
|
169
|
+
idx = str(i + 1)
|
|
170
|
+
|
|
171
|
+
dim_str = DatasetUtil.return_err_corr_dim_str(idx)
|
|
172
|
+
form_str = DatasetUtil.return_err_corr_form_str(idx)
|
|
173
|
+
params_str = DatasetUtil.return_err_corr_params_str(idx)
|
|
174
|
+
units_str = DatasetUtil.return_err_corr_units_str(idx)
|
|
175
|
+
|
|
176
|
+
form = ecdef["form"]
|
|
177
|
+
if isinstance(ecdef["dim"], list) and len(ecdef["dim"]) == 1:
|
|
178
|
+
attributes[dim_str] = ecdef["dim"]
|
|
179
|
+
else:
|
|
180
|
+
attributes[dim_str] = ecdef["dim"]
|
|
181
|
+
attributes[form_str] = ecdef["form"]
|
|
182
|
+
attributes[units_str] = ecdef["units"] if "units" in ecdef else []
|
|
183
|
+
|
|
184
|
+
# if defined form, check number of params valid
|
|
185
|
+
if "params" in ecdef:
|
|
186
|
+
if form in ERR_CORR_DEFS.keys():
|
|
187
|
+
n_params = len(ecdef["params"])
|
|
188
|
+
req_n_params = ERR_CORR_DEFS[form]["n_params"]
|
|
189
|
+
if n_params != req_n_params:
|
|
190
|
+
raise ValueError(
|
|
191
|
+
"Must define "
|
|
192
|
+
+ str(req_n_params)
|
|
193
|
+
+ " for correlation form"
|
|
194
|
+
+ form
|
|
195
|
+
+ "(not "
|
|
196
|
+
+ str(n_params)
|
|
197
|
+
+ ")"
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
attributes[params_str] = ecdef["params"]
|
|
201
|
+
|
|
202
|
+
else:
|
|
203
|
+
attributes[params_str] = []
|
|
204
|
+
|
|
205
|
+
attributes["pdf_shape"] = pdf_shape
|
|
206
|
+
|
|
207
|
+
# Create variable
|
|
208
|
+
variable = DatasetUtil.create_variable(
|
|
209
|
+
dim_sizes,
|
|
210
|
+
dtype,
|
|
211
|
+
dim_names=dim_names,
|
|
212
|
+
attributes=attributes,
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
return variable
|
|
216
|
+
|
|
217
|
+
@staticmethod
|
|
218
|
+
def return_err_corr_dim_str(idx):
|
|
219
|
+
return "_".join(["err", "corr", idx, "dim"])
|
|
220
|
+
|
|
221
|
+
@staticmethod
|
|
222
|
+
def return_err_corr_form_str(idx):
|
|
223
|
+
return "_".join(["err", "corr", idx, "form"])
|
|
224
|
+
|
|
225
|
+
@staticmethod
|
|
226
|
+
def return_err_corr_params_str(idx):
|
|
227
|
+
return "_".join(["err", "corr", idx, "params"])
|
|
228
|
+
|
|
229
|
+
@staticmethod
|
|
230
|
+
def return_err_corr_units_str(idx):
|
|
231
|
+
return "_".join(["err", "corr", idx, "units"])
|
|
232
|
+
|
|
233
|
+
@staticmethod
|
|
234
|
+
def create_flags_variable(
|
|
235
|
+
dim_sizes: List[int],
|
|
236
|
+
meanings: List[str],
|
|
237
|
+
dim_names: Optional[List[str]] = None,
|
|
238
|
+
attributes: Optional[Dict] = None,
|
|
239
|
+
) -> xarray.Variable:
|
|
240
|
+
"""
|
|
241
|
+
Return default empty 1d xarray flag Variable
|
|
242
|
+
|
|
243
|
+
:param dim_sizes: dimension sizes, i.e. ``[dim1_size, dim2_size, dim3_size]`` (e.g. ``[2,3,5]``)
|
|
244
|
+
:param meanings: flag meanings by bit
|
|
245
|
+
:param dim_names: dimension names, i.e. ``["dim1_name", "dim2_name", "dim3_size"]``
|
|
246
|
+
:param attributes: dictionary of variable attributes, e.g. standard_name
|
|
247
|
+
|
|
248
|
+
:returns: Default empty flag vector variable
|
|
249
|
+
"""
|
|
250
|
+
|
|
251
|
+
n_masks = len(meanings)
|
|
252
|
+
|
|
253
|
+
data_type = DatasetUtil.return_flags_dtype(n_masks)
|
|
254
|
+
|
|
255
|
+
variable = DatasetUtil.create_variable(
|
|
256
|
+
dim_sizes,
|
|
257
|
+
data_type,
|
|
258
|
+
dim_names=dim_names,
|
|
259
|
+
attributes=attributes,
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
# initialise flags to zero (instead of fillvalue)
|
|
263
|
+
variable.values = 0 * variable.values
|
|
264
|
+
|
|
265
|
+
# add flag attributes
|
|
266
|
+
variable.attrs.update(DatasetUtil.pack_flag_attrs(meanings))
|
|
267
|
+
|
|
268
|
+
# todo - make sure flags can't have units
|
|
269
|
+
|
|
270
|
+
return variable
|
|
271
|
+
|
|
272
|
+
@staticmethod
|
|
273
|
+
def pack_flag_attrs(
|
|
274
|
+
flag_meanings: List[str], flag_masks: Optional[list] = None
|
|
275
|
+
) -> dict:
|
|
276
|
+
"""
|
|
277
|
+
Derive flag related dataset attributes
|
|
278
|
+
|
|
279
|
+
:param flag_meanings: flag meanings
|
|
280
|
+
:param flag_masks: pre-defined flag masks, generated if not provided
|
|
281
|
+
:return: set of derived flag related attributes
|
|
282
|
+
"""
|
|
283
|
+
|
|
284
|
+
n_masks = len(flag_meanings)
|
|
285
|
+
|
|
286
|
+
flag_attrs = dict()
|
|
287
|
+
|
|
288
|
+
flag_attrs["flag_meanings"] = (
|
|
289
|
+
str(flag_meanings)[1:-1].replace("'", "").replace(",", "")
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
if flag_masks is None:
|
|
293
|
+
flag_masks = [2**i for i in range(0, n_masks)]
|
|
294
|
+
|
|
295
|
+
flag_attrs["flag_masks"] = str(flag_masks)[1:-1]
|
|
296
|
+
|
|
297
|
+
return flag_attrs
|
|
298
|
+
|
|
299
|
+
@staticmethod
|
|
300
|
+
def unpack_flag_attrs(attrs: dict) -> Tuple[list, list]:
|
|
301
|
+
"""
|
|
302
|
+
Extract flag related metadata from dataset attributes
|
|
303
|
+
|
|
304
|
+
:param attrs: flag variable attributes
|
|
305
|
+
:return: flag meanings, flag masks lists
|
|
306
|
+
"""
|
|
307
|
+
|
|
308
|
+
flag_meanings = (
|
|
309
|
+
attrs["flag_meanings"].split() if "flag_meanings" in attrs else []
|
|
310
|
+
)
|
|
311
|
+
flag_mask = attrs["flag_masks"].split(",") if "flag_masks" in attrs else []
|
|
312
|
+
flag_mask = [int(m) for m in flag_mask] if flag_mask != [""] else []
|
|
313
|
+
|
|
314
|
+
return flag_meanings, flag_mask
|
|
315
|
+
|
|
316
|
+
@staticmethod
|
|
317
|
+
def add_flag_meaning_to_attrs(
|
|
318
|
+
attrs: dict, flag_meaning: str, dtype: numpy.typecodes
|
|
319
|
+
) -> dict:
|
|
320
|
+
"""
|
|
321
|
+
Add new meaning to flag variable attributes
|
|
322
|
+
|
|
323
|
+
:param attrs: flag variable attributes
|
|
324
|
+
:param flag_meaning: new flag name
|
|
325
|
+
:param dtype: flag variable dtype
|
|
326
|
+
|
|
327
|
+
:return: updated variable attributes
|
|
328
|
+
"""
|
|
329
|
+
|
|
330
|
+
updated_attrs = deepcopy(attrs)
|
|
331
|
+
|
|
332
|
+
flag_meanings, flag_masks = DatasetUtil.unpack_flag_attrs(attrs)
|
|
333
|
+
|
|
334
|
+
# check if variable for available flags
|
|
335
|
+
max_n_flags = numpy.iinfo(dtype).bits
|
|
336
|
+
all_flag_masks = [2**i for i in range(0, max_n_flags)]
|
|
337
|
+
available_flag_masks = list(set(all_flag_masks) - set(flag_masks))
|
|
338
|
+
|
|
339
|
+
if not available_flag_masks:
|
|
340
|
+
raise ValueError(
|
|
341
|
+
"cannot assign any more masks to variable with dtype" + str(dtype)
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
flag_mask = min(available_flag_masks)
|
|
345
|
+
|
|
346
|
+
updated_attrs["flag_meanings"] = (
|
|
347
|
+
updated_attrs["flag_meanings"] + " " + flag_meaning
|
|
348
|
+
if "flag_meanings" in updated_attrs
|
|
349
|
+
else flag_meaning
|
|
350
|
+
)
|
|
351
|
+
updated_attrs["flag_masks"] = (
|
|
352
|
+
updated_attrs["flag_masks"] + ", " + str(flag_mask)
|
|
353
|
+
if "flag_masks" in updated_attrs
|
|
354
|
+
else str(flag_mask)
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
return updated_attrs
|
|
358
|
+
|
|
359
|
+
@staticmethod
|
|
360
|
+
def rm_flag_meaning_from_attrs(attrs: dict, flag_meaning: str) -> dict:
|
|
361
|
+
"""
|
|
362
|
+
Remove flag meaning from flag variable attributes
|
|
363
|
+
|
|
364
|
+
:param attrs: flag variable attributes
|
|
365
|
+
:param flag_meaning: new flag name
|
|
366
|
+
|
|
367
|
+
:return: updated variable attributes
|
|
368
|
+
"""
|
|
369
|
+
|
|
370
|
+
updated_attrs = deepcopy(attrs)
|
|
371
|
+
|
|
372
|
+
flag_meanings, flag_masks = DatasetUtil.unpack_flag_attrs(attrs)
|
|
373
|
+
|
|
374
|
+
if flag_meaning in flag_meanings:
|
|
375
|
+
i_meaning = flag_meanings.index(flag_meaning)
|
|
376
|
+
else:
|
|
377
|
+
raise ValueError("no flag " + flag_meaning)
|
|
378
|
+
|
|
379
|
+
del flag_meanings[i_meaning]
|
|
380
|
+
del flag_masks[i_meaning]
|
|
381
|
+
|
|
382
|
+
updated_attrs.update(DatasetUtil.pack_flag_attrs(flag_meanings, flag_masks))
|
|
383
|
+
|
|
384
|
+
return updated_attrs
|
|
385
|
+
|
|
386
|
+
@staticmethod
|
|
387
|
+
def return_flags_dtype(n_masks: int) -> numpy.typecodes:
|
|
388
|
+
"""
|
|
389
|
+
Return required flags array data type
|
|
390
|
+
|
|
391
|
+
:param n_masks: number of masks required in flag array
|
|
392
|
+
:return: data type
|
|
393
|
+
"""
|
|
394
|
+
|
|
395
|
+
if n_masks <= 8:
|
|
396
|
+
return numpy.uint8
|
|
397
|
+
elif n_masks <= 16:
|
|
398
|
+
return numpy.uint16
|
|
399
|
+
elif n_masks <= 32:
|
|
400
|
+
return numpy.uint32
|
|
401
|
+
else:
|
|
402
|
+
return numpy.uint64
|
|
403
|
+
|
|
404
|
+
@staticmethod
|
|
405
|
+
def add_encoding(
|
|
406
|
+
variable: xarray.Variable,
|
|
407
|
+
dtype: numpy.typecodes,
|
|
408
|
+
scale_factor: Optional[float] = 1.0,
|
|
409
|
+
offset: Optional[float] = 0.0,
|
|
410
|
+
fill_value: Optional[Union[int, float]] = None,
|
|
411
|
+
chunksizes: Optional[float] = None,
|
|
412
|
+
):
|
|
413
|
+
"""
|
|
414
|
+
Add encoding to xarray Variable to apply when writing netCDF files
|
|
415
|
+
|
|
416
|
+
:param variable: data variable
|
|
417
|
+
:param dtype: numpy data type
|
|
418
|
+
:param scale_factor: variable scale factor
|
|
419
|
+
:param offset: variable offset value
|
|
420
|
+
:param fill_value: fill value
|
|
421
|
+
:param chunksizes: chucksizes
|
|
422
|
+
"""
|
|
423
|
+
|
|
424
|
+
# todo - make sure flags can't have encoding added
|
|
425
|
+
|
|
426
|
+
encoding_dict = {
|
|
427
|
+
"dtype": dtype,
|
|
428
|
+
"scale_factor": scale_factor,
|
|
429
|
+
"add_offset": offset,
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
if chunksizes is not None:
|
|
433
|
+
encoding_dict.update({"chunksizes": chunksizes})
|
|
434
|
+
|
|
435
|
+
if fill_value is not None:
|
|
436
|
+
encoding_dict.update({"_FillValue": fill_value})
|
|
437
|
+
else:
|
|
438
|
+
encoding_dict.update(
|
|
439
|
+
{"_FillValue": DatasetUtil.get_default_fill_value(dtype)}
|
|
440
|
+
)
|
|
441
|
+
|
|
442
|
+
variable.encoding = encoding_dict
|
|
443
|
+
|
|
444
|
+
@staticmethod
|
|
445
|
+
def get_default_fill_value(dtype: numpy.typecodes) -> Union[int, float]:
|
|
446
|
+
"""
|
|
447
|
+
Returns default fill_value for given data type
|
|
448
|
+
|
|
449
|
+
:param dtype: numpy data type
|
|
450
|
+
|
|
451
|
+
:return: CF-conforming fill value
|
|
452
|
+
"""
|
|
453
|
+
|
|
454
|
+
if dtype == numpy.int8:
|
|
455
|
+
return numpy.int8(-127)
|
|
456
|
+
if dtype == numpy.uint8:
|
|
457
|
+
return numpy.iinfo(numpy.uint8).max
|
|
458
|
+
elif dtype == numpy.int16:
|
|
459
|
+
return numpy.int16(-32767)
|
|
460
|
+
elif dtype == numpy.uint16:
|
|
461
|
+
return numpy.iinfo(numpy.uint16).max
|
|
462
|
+
elif dtype == numpy.int32:
|
|
463
|
+
return numpy.int32(-2147483647)
|
|
464
|
+
elif dtype == numpy.uint32:
|
|
465
|
+
return numpy.iinfo(numpy.uint32).max
|
|
466
|
+
elif dtype == numpy.int64:
|
|
467
|
+
return numpy.int64(-9223372036854775806)
|
|
468
|
+
elif dtype == numpy.float32:
|
|
469
|
+
return numpy.float32(9.96921e36)
|
|
470
|
+
elif dtype == numpy.float64:
|
|
471
|
+
return numpy.float64(9.969209968386869e36)
|
|
472
|
+
|
|
473
|
+
@staticmethod
|
|
474
|
+
def _get_flag_encoding(da):
|
|
475
|
+
"""
|
|
476
|
+
Returns flag encoding for flag type data array
|
|
477
|
+
:type da: xarray.DataArray
|
|
478
|
+
:param da: data array
|
|
479
|
+
:return: flag meanings
|
|
480
|
+
:rtype: list
|
|
481
|
+
:return: flag masks
|
|
482
|
+
:rtype: list
|
|
483
|
+
"""
|
|
484
|
+
|
|
485
|
+
try:
|
|
486
|
+
flag_meanings = da.attrs["flag_meanings"].split()
|
|
487
|
+
flag_masks = [int(fm) for fm in da.attrs["flag_masks"].split(",")]
|
|
488
|
+
except KeyError:
|
|
489
|
+
raise KeyError(da.name + " not a flag variable")
|
|
490
|
+
|
|
491
|
+
return flag_meanings, flag_masks
|
|
492
|
+
|
|
493
|
+
@staticmethod
|
|
494
|
+
def unpack_flags(da):
|
|
495
|
+
"""
|
|
496
|
+
Breaks down flag data array into dataset of boolean masks for each flag
|
|
497
|
+
:type da: xarray.DataArray
|
|
498
|
+
:param da: dataset
|
|
499
|
+
:return: flag masks
|
|
500
|
+
:rtype: xarray.Dataset
|
|
501
|
+
"""
|
|
502
|
+
|
|
503
|
+
flag_meanings, flag_masks = DatasetUtil._get_flag_encoding(da)
|
|
504
|
+
|
|
505
|
+
ds = xarray.Dataset()
|
|
506
|
+
for flag_meaning, flag_mask in zip(flag_meanings, flag_masks):
|
|
507
|
+
ds[flag_meaning] = DatasetUtil.create_variable(
|
|
508
|
+
list(da.shape), bool, dim_names=list(da.dims)
|
|
509
|
+
)
|
|
510
|
+
ds[flag_meaning] = (da & flag_mask).astype(bool)
|
|
511
|
+
|
|
512
|
+
return ds
|
|
513
|
+
|
|
514
|
+
@staticmethod
|
|
515
|
+
def get_flags_mask_or(da, flags=None):
|
|
516
|
+
"""
|
|
517
|
+
Returns boolean mask for set of flags, defined as logical or of flags
|
|
518
|
+
|
|
519
|
+
:type da: xarray.DataArray
|
|
520
|
+
:param da: dataset
|
|
521
|
+
|
|
522
|
+
:type flags: list
|
|
523
|
+
:param flags: list of flags (if unset all data flags selected)
|
|
524
|
+
|
|
525
|
+
:return: flag masks
|
|
526
|
+
:rtype: numpy.ndarray
|
|
527
|
+
"""
|
|
528
|
+
|
|
529
|
+
flags_ds = DatasetUtil.unpack_flags(da)
|
|
530
|
+
|
|
531
|
+
flags = flags if flags is not None else flags_ds.data_vars
|
|
532
|
+
mask_flags = numpy.array([flags_ds[flag].values for flag in flags])
|
|
533
|
+
|
|
534
|
+
return numpy.logical_or.reduce(mask_flags)
|
|
535
|
+
|
|
536
|
+
@staticmethod
|
|
537
|
+
def get_flags_mask_and(da, flags=None):
|
|
538
|
+
"""
|
|
539
|
+
Returns boolean mask for set of flags, defined as logical and of flags
|
|
540
|
+
|
|
541
|
+
:type da: xarray.DataArray
|
|
542
|
+
:param da: dataset
|
|
543
|
+
|
|
544
|
+
:type flags: list
|
|
545
|
+
:param flags: list of flags (if unset all data flags selected)
|
|
546
|
+
|
|
547
|
+
:return: flag masks
|
|
548
|
+
:rtype: numpy.ndarray
|
|
549
|
+
"""
|
|
550
|
+
|
|
551
|
+
flags_ds = DatasetUtil.unpack_flags(da)
|
|
552
|
+
|
|
553
|
+
flags = flags if flags is not None else flags_ds.data_vars
|
|
554
|
+
mask_flags = numpy.array([flags_ds[flag].values for flag in flags])
|
|
555
|
+
|
|
556
|
+
return numpy.logical_and.reduce(mask_flags)
|
|
557
|
+
|
|
558
|
+
@staticmethod
|
|
559
|
+
def set_flag(da, flag_name, error_if_set=False):
|
|
560
|
+
"""
|
|
561
|
+
Sets named flag for elements in data array
|
|
562
|
+
:type da: xarray.DataArray
|
|
563
|
+
:param da: dataset
|
|
564
|
+
:type flag_name: str
|
|
565
|
+
:param flag_name: name of flag to set
|
|
566
|
+
:type error_if_set: bool
|
|
567
|
+
:param error_if_set: raises error if chosen flag is already set for any element
|
|
568
|
+
"""
|
|
569
|
+
|
|
570
|
+
set_flags = DatasetUtil.unpack_flags(da)[flag_name]
|
|
571
|
+
|
|
572
|
+
if numpy.any(set_flags == True) and error_if_set:
|
|
573
|
+
raise ValueError(
|
|
574
|
+
"Flag " + flag_name + " already set for variable " + da.name
|
|
575
|
+
)
|
|
576
|
+
|
|
577
|
+
# Find flag mask
|
|
578
|
+
flag_meanings, flag_masks = DatasetUtil._get_flag_encoding(da)
|
|
579
|
+
flag_bit = flag_meanings.index(flag_name)
|
|
580
|
+
flag_mask = flag_masks[flag_bit]
|
|
581
|
+
|
|
582
|
+
da.values = da.values | flag_mask
|
|
583
|
+
|
|
584
|
+
return da
|
|
585
|
+
|
|
586
|
+
@staticmethod
|
|
587
|
+
def unset_flag(da, flag_name, error_if_unset=False):
|
|
588
|
+
"""
|
|
589
|
+
Unsets named flag for specified index of dataset variable
|
|
590
|
+
:type da: xarray.DataArray
|
|
591
|
+
:param da: data array
|
|
592
|
+
:type flag_name: str
|
|
593
|
+
:param flag_name: name of flag to unset
|
|
594
|
+
:type error_if_unset: bool
|
|
595
|
+
:param error_if_unset: raises error if chosen flag is already set at specified index
|
|
596
|
+
"""
|
|
597
|
+
|
|
598
|
+
set_flags = DatasetUtil.unpack_flags(da)[flag_name]
|
|
599
|
+
|
|
600
|
+
if numpy.any(set_flags == False) and error_if_unset:
|
|
601
|
+
raise ValueError(
|
|
602
|
+
"Flag " + flag_name + " already set for variable " + da.name
|
|
603
|
+
)
|
|
604
|
+
|
|
605
|
+
# Find flag mask
|
|
606
|
+
flag_meanings, flag_masks = DatasetUtil._get_flag_encoding(da)
|
|
607
|
+
flag_bit = flag_meanings.index(flag_name)
|
|
608
|
+
flag_mask = flag_masks[flag_bit]
|
|
609
|
+
|
|
610
|
+
da.values = da.values & ~flag_mask
|
|
611
|
+
|
|
612
|
+
return da
|
|
613
|
+
|
|
614
|
+
@staticmethod
|
|
615
|
+
def get_set_flags(da):
|
|
616
|
+
"""
|
|
617
|
+
Return list of set flags for single element data array
|
|
618
|
+
:type da: xarray.DataArray
|
|
619
|
+
:param da: single element data array
|
|
620
|
+
:return: set flags
|
|
621
|
+
:rtype: list
|
|
622
|
+
"""
|
|
623
|
+
|
|
624
|
+
if da.shape != ():
|
|
625
|
+
raise ValueError("Must pass single element data array")
|
|
626
|
+
|
|
627
|
+
flag_meanings, flag_masks = DatasetUtil._get_flag_encoding(da)
|
|
628
|
+
|
|
629
|
+
set_flags = []
|
|
630
|
+
for flag_meaning, flag_mask in zip(flag_meanings, flag_masks):
|
|
631
|
+
if da & flag_mask:
|
|
632
|
+
set_flags.append(flag_meaning)
|
|
633
|
+
|
|
634
|
+
return set_flags
|
|
635
|
+
|
|
636
|
+
@staticmethod
|
|
637
|
+
def check_flag_set(da, flag_name):
|
|
638
|
+
"""
|
|
639
|
+
Returns if flag for single element data array
|
|
640
|
+
:type da: xarray.DataArray
|
|
641
|
+
:param da: single element data array
|
|
642
|
+
:type flag_name: str
|
|
643
|
+
:param flag_name: name of flag to set
|
|
644
|
+
:return: set flags
|
|
645
|
+
:rtype: list
|
|
646
|
+
"""
|
|
647
|
+
|
|
648
|
+
if da.shape != ():
|
|
649
|
+
raise ValueError("Must pass single element data array")
|
|
650
|
+
|
|
651
|
+
set_flags = DatasetUtil.get_set_flags(da)
|
|
652
|
+
|
|
653
|
+
if flag_name in set_flags:
|
|
654
|
+
return True
|
|
655
|
+
return False
|
|
656
|
+
|
|
657
|
+
|
|
658
|
+
if __name__ == "__main__":
|
|
659
|
+
pass
|