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.
@@ -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