tobac 1.6.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.
Files changed (53) hide show
  1. tobac/__init__.py +112 -0
  2. tobac/analysis/__init__.py +31 -0
  3. tobac/analysis/cell_analysis.py +628 -0
  4. tobac/analysis/feature_analysis.py +212 -0
  5. tobac/analysis/spatial.py +619 -0
  6. tobac/centerofgravity.py +226 -0
  7. tobac/feature_detection.py +1758 -0
  8. tobac/merge_split.py +324 -0
  9. tobac/plotting.py +2321 -0
  10. tobac/segmentation/__init__.py +10 -0
  11. tobac/segmentation/watershed_segmentation.py +1316 -0
  12. tobac/testing.py +1179 -0
  13. tobac/tests/segmentation_tests/test_iris_xarray_segmentation.py +0 -0
  14. tobac/tests/segmentation_tests/test_segmentation.py +1183 -0
  15. tobac/tests/segmentation_tests/test_segmentation_time_pad.py +104 -0
  16. tobac/tests/test_analysis_spatial.py +1109 -0
  17. tobac/tests/test_convert.py +265 -0
  18. tobac/tests/test_datetime.py +216 -0
  19. tobac/tests/test_decorators.py +148 -0
  20. tobac/tests/test_feature_detection.py +1321 -0
  21. tobac/tests/test_generators.py +273 -0
  22. tobac/tests/test_import.py +24 -0
  23. tobac/tests/test_iris_xarray_match_utils.py +244 -0
  24. tobac/tests/test_merge_split.py +351 -0
  25. tobac/tests/test_pbc_utils.py +497 -0
  26. tobac/tests/test_sample_data.py +197 -0
  27. tobac/tests/test_testing.py +747 -0
  28. tobac/tests/test_tracking.py +714 -0
  29. tobac/tests/test_utils.py +650 -0
  30. tobac/tests/test_utils_bulk_statistics.py +789 -0
  31. tobac/tests/test_utils_coordinates.py +328 -0
  32. tobac/tests/test_utils_internal.py +97 -0
  33. tobac/tests/test_xarray_utils.py +232 -0
  34. tobac/tracking.py +613 -0
  35. tobac/utils/__init__.py +27 -0
  36. tobac/utils/bulk_statistics.py +360 -0
  37. tobac/utils/datetime.py +184 -0
  38. tobac/utils/decorators.py +540 -0
  39. tobac/utils/general.py +753 -0
  40. tobac/utils/generators.py +87 -0
  41. tobac/utils/internal/__init__.py +2 -0
  42. tobac/utils/internal/coordinates.py +430 -0
  43. tobac/utils/internal/iris_utils.py +462 -0
  44. tobac/utils/internal/label_props.py +82 -0
  45. tobac/utils/internal/xarray_utils.py +439 -0
  46. tobac/utils/mask.py +364 -0
  47. tobac/utils/periodic_boundaries.py +419 -0
  48. tobac/wrapper.py +244 -0
  49. tobac-1.6.2.dist-info/METADATA +154 -0
  50. tobac-1.6.2.dist-info/RECORD +53 -0
  51. tobac-1.6.2.dist-info/WHEEL +5 -0
  52. tobac-1.6.2.dist-info/licenses/LICENSE +29 -0
  53. tobac-1.6.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,540 @@
1
+ """Decorators for use with other tobac functions"""
2
+
3
+ from __future__ import annotations
4
+ import functools
5
+ import warnings
6
+
7
+ import numpy as np
8
+ from numpy import ma
9
+ import pandas as pd
10
+ import xarray as xr
11
+ import iris.cube
12
+
13
+
14
+ def convert_cube_to_dataarray(
15
+ cube: iris.cube.Cube, preserve_iris_datetime_types: bool = True
16
+ ) -> xr.DataArray:
17
+ """
18
+ Convert an iris cube to an xarray dataarray, averting error for integer dtype cubes in xarray<v2023.06
19
+
20
+ Parameters
21
+ ----------
22
+ cube : iris.cube.Cube
23
+ Iris data cube
24
+
25
+ preserve_iris_datetime_types : bool, optional (default: True)
26
+ If True, ensure that the time coordinate of the output (if present) uses the same cftime type as the
27
+ input cube.
28
+
29
+ Returns
30
+ -------
31
+ dataarray : xr.DataArray
32
+ dataarray converted from cube. If the cube's core data is a masked array and has integer dtype,
33
+ the returned datarray will have a numpy array with masked values filled with the minimum value for
34
+ that integer dtype. Otherwise the data will be identical to that produced using xr.DataArray.from_iris
35
+ """
36
+ if isinstance(cube.core_data(), ma.core.MaskedArray) and np.issubdtype(
37
+ cube.core_data().dtype, np.integer
38
+ ):
39
+ da = xr.DataArray.from_iris(
40
+ cube.copy(cube.core_data().filled(np.iinfo(cube.core_data().dtype).min))
41
+ )
42
+ else:
43
+ da = xr.DataArray.from_iris(cube)
44
+
45
+ if preserve_iris_datetime_types & ("time" in da.coords):
46
+ da = da.convert_calendar(cube.coord("time").units.calendar, use_cftime=True)
47
+
48
+ return da
49
+
50
+
51
+ def _conv_kwargs_iris_to_xarray(
52
+ conv_kwargs: dict, preserve_iris_datetime_types: bool = True
53
+ ) -> dict:
54
+ """
55
+ Internal function to convert iris cube kwargs to xarray dataarrays
56
+
57
+ Parameters
58
+ ----------
59
+ conv_kwargs : dict
60
+ Input kwargs to convert
61
+
62
+ Returns
63
+ -------
64
+ dict
65
+ Output keyword arguments without any Iris Cubes
66
+ """
67
+ return {
68
+ key: (
69
+ convert_cube_to_dataarray(
70
+ arg, preserve_iris_datetime_types=preserve_iris_datetime_types
71
+ )
72
+ if isinstance(arg, iris.cube.Cube)
73
+ else arg
74
+ )
75
+ for key, arg in zip(conv_kwargs.keys(), conv_kwargs.values())
76
+ }
77
+
78
+
79
+ def _conv_kwargs_irispandas_to_xarray(
80
+ conv_kwargs: dict, preserve_iris_datetime_types: bool = True
81
+ ) -> dict:
82
+ """
83
+ Internal function to convert iris cube and pandas dataframe kwargs to xarray dataarrays
84
+
85
+ Parameters
86
+ ----------
87
+ conv_kwargs : dict
88
+ Input kwargs to convert
89
+
90
+ Returns
91
+ -------
92
+ dict
93
+ Output keyword arguments without any Iris Cubes or pandas dataframes
94
+
95
+ """
96
+ return {
97
+ key: (
98
+ convert_cube_to_dataarray(
99
+ arg, preserve_iris_datetime_types=preserve_iris_datetime_types
100
+ )
101
+ if isinstance(arg, iris.cube.Cube)
102
+ else arg.to_xarray() if isinstance(arg, pd.DataFrame) else arg
103
+ )
104
+ for key, arg in zip(conv_kwargs.keys(), conv_kwargs.values())
105
+ }
106
+
107
+
108
+ def _conv_kwargs_xarray_to_iris(conv_kwargs: dict):
109
+ """
110
+ Internal function to convert xarray dataarray kwargs back to iris cubes
111
+
112
+ Parameters
113
+ ----------
114
+ conv_kwargs : dict
115
+ Input kwargs to convert
116
+
117
+ Returns
118
+ -------
119
+ dict
120
+ Output keyword arguments with all xarray dataarrays converted back to
121
+ iris cubes
122
+ """
123
+ return {
124
+ key: (
125
+ xr.DataArray.to_iris(arg).copy(arg.data)
126
+ if isinstance(arg, xr.DataArray)
127
+ else arg
128
+ )
129
+ for key, arg in zip(conv_kwargs.keys(), conv_kwargs.values())
130
+ }
131
+
132
+
133
+ def _conv_kwargs_xarray_to_irispandas(conv_kwargs: dict):
134
+ """
135
+ Internal function to convert xarray dataarrays back to iris cubes/pandas dataframes
136
+
137
+ Parameters
138
+ ----------
139
+ conv_kwargs : dict
140
+ Input kwargs to convert
141
+
142
+ Returns
143
+ -------
144
+ dict
145
+ Output keyword arguments with all xarray dataarrays converted back to
146
+ iris cubes
147
+ """
148
+ return {
149
+ key: (
150
+ xr.DataArray.to_iris(arg).copy(arg.data)
151
+ if isinstance(arg, xr.DataArray)
152
+ else arg.to_dataframe() if isinstance(arg, xr.Dataset) else arg
153
+ )
154
+ for key, arg in zip(conv_kwargs.keys(), conv_kwargs.values())
155
+ }
156
+
157
+
158
+ def iris_to_xarray(save_iris_info: bool = False):
159
+ def iris_to_xarray_i(func):
160
+ """Decorator that converts all input of a function that is in the form of
161
+ Iris cubes into xarray DataArrays and converts all outputs with type
162
+ xarray DataArrays back into Iris cubes.
163
+
164
+ Parameters
165
+ ----------
166
+ func : function
167
+ Function to be decorated
168
+
169
+ Returns
170
+ -------
171
+ wrapper : function
172
+ Function including decorator
173
+ """
174
+
175
+ import iris
176
+ import iris.cube
177
+ import xarray
178
+
179
+ @functools.wraps(func)
180
+ def wrapper(*args, preserve_iris_datetime_types: bool = True, **kwargs):
181
+ # print(kwargs)
182
+
183
+ if save_iris_info:
184
+ if any([(type(arg) == iris.cube.Cube) for arg in args]) or any(
185
+ [(type(arg) == iris.cube.Cube) for arg in kwargs.values()]
186
+ ):
187
+ kwargs["converted_from_iris"] = True
188
+ else:
189
+ kwargs["converted_from_iris"] = False
190
+
191
+ if any([type(arg) == iris.cube.Cube for arg in args]) or any(
192
+ [type(arg) == iris.cube.Cube for arg in kwargs.values()]
193
+ ):
194
+ # print("converting iris to xarray and back")
195
+ args = tuple(
196
+ [
197
+ (
198
+ convert_cube_to_dataarray(
199
+ arg,
200
+ preserve_iris_datetime_types=preserve_iris_datetime_types,
201
+ )
202
+ if type(arg) == iris.cube.Cube
203
+ else arg
204
+ )
205
+ for arg in args
206
+ ]
207
+ )
208
+ kwargs_new = _conv_kwargs_iris_to_xarray(
209
+ kwargs, preserve_iris_datetime_types=preserve_iris_datetime_types
210
+ )
211
+ # print(args)
212
+ # print(kwargs)
213
+ output = func(*args, **kwargs_new)
214
+ if type(output) == tuple:
215
+ output = tuple(
216
+ [
217
+ (
218
+ output_item.to_iris().copy(output_item.data)
219
+ if type(output_item) == xarray.DataArray
220
+ else output_item
221
+ )
222
+ for output_item in output
223
+ ]
224
+ )
225
+ elif type(output) == xarray.DataArray:
226
+ output = output.to_iris().copy(output.data)
227
+ # if output is neither tuple nor an xr.DataArray
228
+
229
+ else:
230
+ output = func(*args, **kwargs)
231
+ return output
232
+
233
+ return wrapper
234
+
235
+ return iris_to_xarray_i
236
+
237
+
238
+ def xarray_to_iris():
239
+ def xarray_to_iris_i(func):
240
+ """Decorator that converts all input of a function that is in the form of
241
+ xarray DataArrays into Iris cubes and converts all outputs with type
242
+ Iris cubes back into xarray DataArrays.
243
+
244
+ Parameters
245
+ ----------
246
+ func : function
247
+ Function to be decorated.
248
+
249
+ Returns
250
+ -------
251
+ wrapper : function
252
+ Function including decorator.
253
+
254
+ Examples
255
+ --------
256
+ >>> segmentation_xarray_conv = xarray_to_iris()
257
+ >>> segmentation_xarray = segmentation_xarray_conv(segmentation)
258
+
259
+ This line creates a new function that can process xarray fields and
260
+ also outputs fields in xarray format, but otherwise works just like
261
+ the original function:
262
+
263
+ >>> mask_xarray, features = segmentation_xarray(
264
+ features, data_xarray, dxy, threshold
265
+ )
266
+ """
267
+
268
+ import iris
269
+ import xarray
270
+
271
+ @functools.wraps(func)
272
+ def wrapper(*args, **kwargs):
273
+ # print(args)
274
+ # print(kwargs)
275
+ if any([type(arg) == xarray.DataArray for arg in args]) or any(
276
+ [type(arg) == xarray.DataArray for arg in kwargs.values()]
277
+ ):
278
+ # print("converting xarray to iris and back")
279
+ args = tuple(
280
+ [
281
+ (
282
+ arg.to_iris().copy(arg.data)
283
+ if type(arg) == xarray.DataArray
284
+ else arg
285
+ )
286
+ for arg in args
287
+ ]
288
+ )
289
+ if kwargs:
290
+ kwargs_new = _conv_kwargs_xarray_to_iris(kwargs)
291
+ else:
292
+ kwargs_new = kwargs
293
+ # print(args)
294
+ # print(kwargs)
295
+ output = func(*args, **kwargs_new)
296
+ if type(output) == tuple:
297
+ output = tuple(
298
+ [
299
+ (
300
+ xarray.DataArray.from_iris(output_item)
301
+ if type(output_item) == iris.cube.Cube
302
+ else output_item
303
+ )
304
+ for output_item in output
305
+ ]
306
+ )
307
+ else:
308
+ if type(output) == iris.cube.Cube:
309
+ output = xarray.DataArray.from_iris(output)
310
+
311
+ else:
312
+ output = func(*args, **kwargs)
313
+ # print(output)
314
+ return output
315
+
316
+ return wrapper
317
+
318
+ return xarray_to_iris_i
319
+
320
+
321
+ def irispandas_to_xarray(save_iris_info: bool = False):
322
+ def irispandas_to_xarray_i(func):
323
+ """Decorator that converts all input of a function that is in the form of
324
+ Iris cubes/pandas Dataframes into xarray DataArrays/xarray Datasets and
325
+ converts all outputs with the type xarray DataArray/xarray Dataset
326
+ back into Iris cubes/pandas Dataframes.
327
+
328
+ Parameters
329
+ ----------
330
+ func : function
331
+ Function to be decorated.
332
+
333
+ Returns
334
+ -------
335
+ wrapper : function
336
+ Function including decorator.
337
+ """
338
+ import iris
339
+ import iris.cube
340
+ import xarray
341
+ import pandas as pd
342
+
343
+ @functools.wraps(func)
344
+ def wrapper(*args, preserve_iris_datetime_types: bool = True, **kwargs):
345
+ # pass if we did an iris conversion.
346
+ if save_iris_info:
347
+ if any([(type(arg) == iris.cube.Cube) for arg in args]) or any(
348
+ [(type(arg) == iris.cube.Cube) for arg in kwargs.values()]
349
+ ):
350
+ kwargs["converted_from_iris"] = True
351
+ else:
352
+ kwargs["converted_from_iris"] = False
353
+
354
+ # print(kwargs)
355
+ if any(
356
+ [
357
+ (type(arg) == iris.cube.Cube or type(arg) == pd.DataFrame)
358
+ for arg in args
359
+ ]
360
+ ) or any(
361
+ [
362
+ (type(arg) == iris.cube.Cube or type(arg) == pd.DataFrame)
363
+ for arg in kwargs.values()
364
+ ]
365
+ ):
366
+ # print("converting iris to xarray and back")
367
+ args = tuple(
368
+ [
369
+ (
370
+ convert_cube_to_dataarray(
371
+ arg,
372
+ preserve_iris_datetime_types=preserve_iris_datetime_types,
373
+ )
374
+ if type(arg) == iris.cube.Cube
375
+ else arg.to_xarray() if type(arg) == pd.DataFrame else arg
376
+ )
377
+ for arg in args
378
+ ]
379
+ )
380
+ kwargs = _conv_kwargs_irispandas_to_xarray(
381
+ kwargs, preserve_iris_datetime_types=preserve_iris_datetime_types
382
+ )
383
+
384
+ output = func(*args, **kwargs)
385
+ if type(output) == tuple:
386
+ output = tuple(
387
+ [
388
+ (
389
+ output_item.to_iris().copy(output_item.data)
390
+ if type(output_item) == xarray.DataArray
391
+ else (
392
+ output_item.to_dataframe()
393
+ if type(output_item) == xarray.Dataset
394
+ else output_item
395
+ )
396
+ )
397
+ for output_item in output
398
+ ]
399
+ )
400
+ else:
401
+ if type(output) == xarray.DataArray:
402
+ output = output.to_iris().copy(output.data)
403
+ elif type(output) == xarray.Dataset:
404
+ output = output.to_dataframe()
405
+
406
+ else:
407
+ output = func(*args, **kwargs)
408
+ return output
409
+
410
+ return wrapper
411
+
412
+ return irispandas_to_xarray_i
413
+
414
+
415
+ def xarray_to_irispandas():
416
+ def xarray_to_irispandas_i(func):
417
+ """Decorator that converts all input of a function that is in the form of
418
+ DataArrays/xarray Datasets into xarray Iris cubes/pandas Dataframes and
419
+ converts all outputs with the type Iris cubes/pandas Dataframes back into
420
+ xarray DataArray/xarray Dataset.
421
+
422
+ Parameters
423
+ ----------
424
+ func : function
425
+ Function to be decorated.
426
+
427
+ Returns
428
+ -------
429
+ wrapper : function
430
+ Function including decorator.
431
+
432
+ Examples
433
+ --------
434
+ >>> linking_trackpy_xarray = xarray_to_irispandas(
435
+ linking_trackpy
436
+ )
437
+
438
+ This line creates a new function that can process xarray inputs and
439
+ also outputs in xarray formats, but otherwise works just like the
440
+ original function:
441
+
442
+ >>> track_xarray = linking_trackpy_xarray(
443
+ features_xarray, field_xarray, dt, dx
444
+ )
445
+ """
446
+ import iris
447
+ import xarray
448
+ import pandas as pd
449
+
450
+ @functools.wraps(func)
451
+ def wrapper(*args, **kwargs):
452
+ # print(args)
453
+ # print(kwargs)
454
+ if any(
455
+ [
456
+ (type(arg) == xarray.DataArray or type(arg) == xarray.Dataset)
457
+ for arg in args
458
+ ]
459
+ ) or any(
460
+ [
461
+ (type(arg) == xarray.DataArray or type(arg) == xarray.Dataset)
462
+ for arg in kwargs.values()
463
+ ]
464
+ ):
465
+ # print("converting xarray to iris and back")
466
+ args = tuple(
467
+ [
468
+ (
469
+ xarray.DataArray.to_iris(arg).copy(arg.data)
470
+ if type(arg) == xarray.DataArray
471
+ else (
472
+ arg.to_dataframe()
473
+ if type(arg) == xarray.Dataset
474
+ else arg
475
+ )
476
+ )
477
+ for arg in args
478
+ ]
479
+ )
480
+ if kwargs:
481
+ kwargs_new = _conv_kwargs_xarray_to_irispandas(kwargs)
482
+ else:
483
+ kwargs_new = kwargs
484
+ # print(args)
485
+ # print(kwargs)
486
+ output = func(*args, **kwargs_new)
487
+ if type(output) == tuple:
488
+ output = tuple(
489
+ [
490
+ (
491
+ xarray.DataArray.from_iris(output_item)
492
+ if type(output_item) == iris.cube.Cube
493
+ else (
494
+ output_item.to_xarray()
495
+ if type(output_item) == pd.DataFrame
496
+ else output_item
497
+ )
498
+ )
499
+ for output_item in output
500
+ ]
501
+ )
502
+ else:
503
+ if type(output) == iris.cube.Cube:
504
+ output = xarray.DataArray.from_iris(output)
505
+ elif type(output) == pd.DataFrame:
506
+ output = output.to_xarray()
507
+
508
+ else:
509
+ output = func(*args, **kwargs)
510
+ # print(output)
511
+ return output
512
+
513
+ return wrapper
514
+
515
+ return xarray_to_irispandas_i
516
+
517
+
518
+ def njit_if_available(func, **kwargs):
519
+ """Decorator to wrap a function with numba.njit if available.
520
+ If numba isn't available, it just returns the function.
521
+
522
+ Parameters
523
+ ----------
524
+ func: function object
525
+ Function to wrap with njit
526
+ kwargs:
527
+ Keyword arguments to pass to numba njit
528
+ """
529
+ try:
530
+ from numba import njit
531
+
532
+ return njit(func, kwargs)
533
+ except KeyboardInterrupt as kie:
534
+ raise
535
+ except Exception as exc:
536
+ warnings.warn(
537
+ "Numba not able to be imported; periodic boundary calculations will be slower."
538
+ "Exception raised: " + repr(exc)
539
+ )
540
+ return func