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.
- tobac/__init__.py +112 -0
- tobac/analysis/__init__.py +31 -0
- tobac/analysis/cell_analysis.py +628 -0
- tobac/analysis/feature_analysis.py +212 -0
- tobac/analysis/spatial.py +619 -0
- tobac/centerofgravity.py +226 -0
- tobac/feature_detection.py +1758 -0
- tobac/merge_split.py +324 -0
- tobac/plotting.py +2321 -0
- tobac/segmentation/__init__.py +10 -0
- tobac/segmentation/watershed_segmentation.py +1316 -0
- tobac/testing.py +1179 -0
- tobac/tests/segmentation_tests/test_iris_xarray_segmentation.py +0 -0
- tobac/tests/segmentation_tests/test_segmentation.py +1183 -0
- tobac/tests/segmentation_tests/test_segmentation_time_pad.py +104 -0
- tobac/tests/test_analysis_spatial.py +1109 -0
- tobac/tests/test_convert.py +265 -0
- tobac/tests/test_datetime.py +216 -0
- tobac/tests/test_decorators.py +148 -0
- tobac/tests/test_feature_detection.py +1321 -0
- tobac/tests/test_generators.py +273 -0
- tobac/tests/test_import.py +24 -0
- tobac/tests/test_iris_xarray_match_utils.py +244 -0
- tobac/tests/test_merge_split.py +351 -0
- tobac/tests/test_pbc_utils.py +497 -0
- tobac/tests/test_sample_data.py +197 -0
- tobac/tests/test_testing.py +747 -0
- tobac/tests/test_tracking.py +714 -0
- tobac/tests/test_utils.py +650 -0
- tobac/tests/test_utils_bulk_statistics.py +789 -0
- tobac/tests/test_utils_coordinates.py +328 -0
- tobac/tests/test_utils_internal.py +97 -0
- tobac/tests/test_xarray_utils.py +232 -0
- tobac/tracking.py +613 -0
- tobac/utils/__init__.py +27 -0
- tobac/utils/bulk_statistics.py +360 -0
- tobac/utils/datetime.py +184 -0
- tobac/utils/decorators.py +540 -0
- tobac/utils/general.py +753 -0
- tobac/utils/generators.py +87 -0
- tobac/utils/internal/__init__.py +2 -0
- tobac/utils/internal/coordinates.py +430 -0
- tobac/utils/internal/iris_utils.py +462 -0
- tobac/utils/internal/label_props.py +82 -0
- tobac/utils/internal/xarray_utils.py +439 -0
- tobac/utils/mask.py +364 -0
- tobac/utils/periodic_boundaries.py +419 -0
- tobac/wrapper.py +244 -0
- tobac-1.6.2.dist-info/METADATA +154 -0
- tobac-1.6.2.dist-info/RECORD +53 -0
- tobac-1.6.2.dist-info/WHEEL +5 -0
- tobac-1.6.2.dist-info/licenses/LICENSE +29 -0
- tobac-1.6.2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
"""Internal tobac utilities for iris cubes
|
|
2
|
+
The goal will be to, ultimately, remove these when we sunset iris
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from typing import Union
|
|
8
|
+
import logging
|
|
9
|
+
|
|
10
|
+
import iris
|
|
11
|
+
import iris.cube
|
|
12
|
+
import numpy as np
|
|
13
|
+
import pandas as pd
|
|
14
|
+
|
|
15
|
+
from . import coordinates as tb_utils_gi
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def find_axis_from_coord(
|
|
19
|
+
variable_cube: iris.cube.Cube, coord_name: str
|
|
20
|
+
) -> Union[int, None]:
|
|
21
|
+
"""Finds the axis number in an iris cube given a coordinate name.
|
|
22
|
+
|
|
23
|
+
Parameters
|
|
24
|
+
----------
|
|
25
|
+
variable_cube: iris.cube
|
|
26
|
+
Input variable cube
|
|
27
|
+
coord_name: str
|
|
28
|
+
coordinate to look for
|
|
29
|
+
|
|
30
|
+
Returns
|
|
31
|
+
-------
|
|
32
|
+
axis_number: int
|
|
33
|
+
the number of the axis of the given coordinate, or None if the coordinate
|
|
34
|
+
is not found in the cube or not a dimensional coordinate
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
list_coord_names = [coord.name() for coord in variable_cube.coords()]
|
|
38
|
+
all_matching_axes = list(set(list_coord_names) & {coord_name})
|
|
39
|
+
if (
|
|
40
|
+
len(all_matching_axes) == 1
|
|
41
|
+
and len(variable_cube.coord_dims(all_matching_axes[0])) > 0
|
|
42
|
+
):
|
|
43
|
+
return variable_cube.coord_dims(all_matching_axes[0])[0]
|
|
44
|
+
if len(all_matching_axes) > 1:
|
|
45
|
+
raise ValueError("Too many axes matched.")
|
|
46
|
+
|
|
47
|
+
return None
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def find_vertical_axis_from_coord(
|
|
51
|
+
variable_cube: iris.cube.Cube, vertical_coord: Union[str, None] = None
|
|
52
|
+
) -> str:
|
|
53
|
+
"""Function to find the vertical coordinate in the iris cube
|
|
54
|
+
|
|
55
|
+
Parameters
|
|
56
|
+
----------
|
|
57
|
+
variable_cube: iris.cube
|
|
58
|
+
Input variable cube, containing a vertical coordinate.
|
|
59
|
+
vertical_coord: str
|
|
60
|
+
Vertical coordinate name. If None, this function tries to auto-detect.
|
|
61
|
+
|
|
62
|
+
Returns
|
|
63
|
+
-------
|
|
64
|
+
str
|
|
65
|
+
the vertical coordinate name
|
|
66
|
+
|
|
67
|
+
Raises
|
|
68
|
+
------
|
|
69
|
+
ValueError
|
|
70
|
+
Raised if the vertical coordinate isn't found in the cube.
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
list_coord_names = [coord.name() for coord in variable_cube.coords()]
|
|
74
|
+
|
|
75
|
+
if vertical_coord is None or vertical_coord == "auto":
|
|
76
|
+
# find the intersection
|
|
77
|
+
all_vertical_axes = list(
|
|
78
|
+
set(list_coord_names) & set(tb_utils_gi.COMMON_VERT_COORDS)
|
|
79
|
+
)
|
|
80
|
+
if len(all_vertical_axes) >= 1:
|
|
81
|
+
return all_vertical_axes[0]
|
|
82
|
+
raise ValueError(
|
|
83
|
+
"Cube lacks suitable automatic vertical coordinate (z, model_level_number, altitude, "
|
|
84
|
+
"or geopotential_height)"
|
|
85
|
+
)
|
|
86
|
+
if vertical_coord in list_coord_names:
|
|
87
|
+
return vertical_coord
|
|
88
|
+
raise ValueError("Please specify vertical coordinate found in cube")
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def find_hdim_axes_3d(
|
|
92
|
+
field_in: iris.cube.Cube,
|
|
93
|
+
vertical_coord: Union[str, None] = None,
|
|
94
|
+
vertical_axis: Union[int, None] = None,
|
|
95
|
+
) -> tuple[int, int]:
|
|
96
|
+
"""Finds what the hdim axes are given a 3D (including z) or
|
|
97
|
+
4D (including z and time) dataset.
|
|
98
|
+
|
|
99
|
+
Parameters
|
|
100
|
+
----------
|
|
101
|
+
field_in: iris cube
|
|
102
|
+
Input field, can be 3D or 4D
|
|
103
|
+
vertical_coord: str or None
|
|
104
|
+
The name of the vertical coord, or None, which will attempt to find
|
|
105
|
+
the vertical coordinate name
|
|
106
|
+
vertical_axis: int or None
|
|
107
|
+
The axis number of the vertical coordinate, or None. Note
|
|
108
|
+
that only one of vertical_axis or vertical_coord can be set.
|
|
109
|
+
|
|
110
|
+
Returns
|
|
111
|
+
-------
|
|
112
|
+
(hdim_1_axis, hdim_2_axis): (int, int)
|
|
113
|
+
The axes for hdim_1 and hdim_2
|
|
114
|
+
"""
|
|
115
|
+
|
|
116
|
+
if vertical_coord is not None and vertical_axis is not None:
|
|
117
|
+
if vertical_coord != "auto":
|
|
118
|
+
raise ValueError("Cannot set both vertical_coord and vertical_axis.")
|
|
119
|
+
|
|
120
|
+
time_axis = find_axis_from_coord(field_in, "time")
|
|
121
|
+
if vertical_axis is not None:
|
|
122
|
+
vertical_coord_axis = vertical_axis
|
|
123
|
+
vert_coord_found = True
|
|
124
|
+
else:
|
|
125
|
+
try:
|
|
126
|
+
vertical_axis = find_vertical_axis_from_coord(
|
|
127
|
+
field_in, vertical_coord=vertical_coord
|
|
128
|
+
)
|
|
129
|
+
except ValueError:
|
|
130
|
+
vert_coord_found = False
|
|
131
|
+
else:
|
|
132
|
+
vert_coord_found = True
|
|
133
|
+
ndim_vertical = field_in.coord_dims(vertical_axis)
|
|
134
|
+
if len(ndim_vertical) > 1:
|
|
135
|
+
raise ValueError(
|
|
136
|
+
"please specify 1 dimensional vertical coordinate."
|
|
137
|
+
f" Current vertical coordinates: {ndim_vertical}"
|
|
138
|
+
)
|
|
139
|
+
if len(ndim_vertical) != 0:
|
|
140
|
+
vertical_coord_axis = ndim_vertical[0]
|
|
141
|
+
else:
|
|
142
|
+
# this means the vertical coordinate is an auxiliary coordinate of some kind.
|
|
143
|
+
vert_coord_found = False
|
|
144
|
+
|
|
145
|
+
if not vert_coord_found:
|
|
146
|
+
# if we don't have a vertical coordinate, and we are 3D or lower
|
|
147
|
+
# that is okay.
|
|
148
|
+
if (field_in.ndim == 3 and time_axis is not None) or field_in.ndim < 3:
|
|
149
|
+
vertical_coord_axis = None
|
|
150
|
+
else:
|
|
151
|
+
raise ValueError("No suitable vertical coordinate found")
|
|
152
|
+
# Once we know the vertical coordinate, we can resolve the
|
|
153
|
+
# horizontal coordinates
|
|
154
|
+
|
|
155
|
+
all_axes = np.arange(0, field_in.ndim)
|
|
156
|
+
output_vals = tuple(
|
|
157
|
+
all_axes[np.logical_not(np.isin(all_axes, [time_axis, vertical_coord_axis]))]
|
|
158
|
+
)
|
|
159
|
+
return output_vals
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def add_coordinates(t: pd.DataFrame, variable_cube: iris.cube.Cube) -> pd.DataFrame:
|
|
163
|
+
"""Add coordinates from the input cube of the feature detection
|
|
164
|
+
to the trajectories/features.
|
|
165
|
+
|
|
166
|
+
Parameters
|
|
167
|
+
----------
|
|
168
|
+
t : pandas.DataFrame
|
|
169
|
+
Trajectories/features from feature detection or linking step.
|
|
170
|
+
|
|
171
|
+
variable_cube : iris.cube.Cube
|
|
172
|
+
Input data used for the tracking with coordinate information
|
|
173
|
+
to transfer to the resulting DataFrame. Needs to contain the
|
|
174
|
+
coordinate 'time'.
|
|
175
|
+
|
|
176
|
+
Returns
|
|
177
|
+
-------
|
|
178
|
+
t : pandas.DataFrame
|
|
179
|
+
Trajectories with added coordinates.
|
|
180
|
+
|
|
181
|
+
"""
|
|
182
|
+
|
|
183
|
+
from scipy.interpolate import interp1d, interpn
|
|
184
|
+
|
|
185
|
+
logging.debug("start adding coordinates from cube")
|
|
186
|
+
|
|
187
|
+
# pull time as datetime object and timestr from input data and add it to DataFrame:
|
|
188
|
+
t["time"] = None
|
|
189
|
+
t["timestr"] = None
|
|
190
|
+
|
|
191
|
+
logging.debug("adding time coordinate")
|
|
192
|
+
|
|
193
|
+
time_in = variable_cube.coord("time")
|
|
194
|
+
time_in_datetime = time_in.units.num2date(time_in.points)
|
|
195
|
+
|
|
196
|
+
t["time"] = time_in_datetime[t["frame"]]
|
|
197
|
+
t["timestr"] = [
|
|
198
|
+
x.strftime("%Y-%m-%d %H:%M:%S") for x in time_in_datetime[t["frame"]]
|
|
199
|
+
]
|
|
200
|
+
|
|
201
|
+
# Get list of all coordinates in input cube except for time (already treated):
|
|
202
|
+
coord_names = [coord.name() for coord in variable_cube.coords()]
|
|
203
|
+
coord_names.remove("time")
|
|
204
|
+
|
|
205
|
+
logging.debug("time coordinate added")
|
|
206
|
+
|
|
207
|
+
# chose right dimension for horizontal axis based on time dimension:
|
|
208
|
+
ndim_time = variable_cube.coord_dims("time")[0]
|
|
209
|
+
if ndim_time == 0:
|
|
210
|
+
hdim_1 = 1
|
|
211
|
+
hdim_2 = 2
|
|
212
|
+
elif ndim_time == 1:
|
|
213
|
+
hdim_1 = 0
|
|
214
|
+
hdim_2 = 2
|
|
215
|
+
elif ndim_time == 2:
|
|
216
|
+
hdim_1 = 0
|
|
217
|
+
hdim_2 = 1
|
|
218
|
+
|
|
219
|
+
# create vectors to use to interpolate from pixels to coordinates
|
|
220
|
+
dimvec_1 = np.arange(variable_cube.shape[hdim_1])
|
|
221
|
+
dimvec_2 = np.arange(variable_cube.shape[hdim_2])
|
|
222
|
+
|
|
223
|
+
# loop over coordinates in input data:
|
|
224
|
+
for coord in coord_names:
|
|
225
|
+
logging.debug("adding coord: %s", coord)
|
|
226
|
+
# interpolate 2D coordinates:
|
|
227
|
+
if variable_cube.coord(coord).ndim == 1:
|
|
228
|
+
if variable_cube.coord_dims(coord) == (hdim_1,):
|
|
229
|
+
f = interp1d(
|
|
230
|
+
dimvec_1,
|
|
231
|
+
variable_cube.coord(coord).points,
|
|
232
|
+
fill_value="extrapolate",
|
|
233
|
+
)
|
|
234
|
+
coordinate_points = f(t["hdim_1"])
|
|
235
|
+
|
|
236
|
+
if variable_cube.coord_dims(coord) == (hdim_2,):
|
|
237
|
+
f = interp1d(
|
|
238
|
+
dimvec_2,
|
|
239
|
+
variable_cube.coord(coord).points,
|
|
240
|
+
fill_value="extrapolate",
|
|
241
|
+
)
|
|
242
|
+
coordinate_points = f(t["hdim_2"])
|
|
243
|
+
|
|
244
|
+
# interpolate 2D coordinates:
|
|
245
|
+
elif variable_cube.coord(coord).ndim == 2:
|
|
246
|
+
if variable_cube.coord_dims(coord) == (hdim_1, hdim_2):
|
|
247
|
+
points = (dimvec_1, dimvec_2)
|
|
248
|
+
values = variable_cube.coord(coord).points
|
|
249
|
+
xi = np.column_stack((t["hdim_1"], t["hdim_2"]))
|
|
250
|
+
coordinate_points = interpn(points, values, xi)
|
|
251
|
+
|
|
252
|
+
if variable_cube.coord_dims(coord) == (hdim_2, hdim_1):
|
|
253
|
+
points = (dimvec_2, dimvec_1)
|
|
254
|
+
values = variable_cube.coord(coord).points
|
|
255
|
+
xi = np.column_stack((t["hdim_2"], t["hdim_1"]))
|
|
256
|
+
coordinate_points = interpn(points, values, xi)
|
|
257
|
+
|
|
258
|
+
# interpolate 3D coordinates:
|
|
259
|
+
# mainly workaround for wrf latitude and longitude (to be fixed in future)
|
|
260
|
+
|
|
261
|
+
elif variable_cube.coord(coord).ndim == 3:
|
|
262
|
+
if variable_cube.coord_dims(coord) == (ndim_time, hdim_1, hdim_2):
|
|
263
|
+
points = (dimvec_1, dimvec_2)
|
|
264
|
+
values = variable_cube[0, :, :].coord(coord).points
|
|
265
|
+
xi = np.column_stack((t["hdim_1"], t["hdim_2"]))
|
|
266
|
+
coordinate_points = interpn(points, values, xi)
|
|
267
|
+
|
|
268
|
+
if variable_cube.coord_dims(coord) == (ndim_time, hdim_2, hdim_1):
|
|
269
|
+
points = (dimvec_2, dimvec_1)
|
|
270
|
+
values = variable_cube[0, :, :].coord(coord).points
|
|
271
|
+
xi = np.column_stack((t["hdim_2"], t["hdim_1"]))
|
|
272
|
+
coordinate_points = interpn(points, values, xi)
|
|
273
|
+
|
|
274
|
+
if variable_cube.coord_dims(coord) == (hdim_1, ndim_time, hdim_2):
|
|
275
|
+
points = (dimvec_1, dimvec_2)
|
|
276
|
+
values = variable_cube[:, 0, :].coord(coord).points
|
|
277
|
+
xi = np.column_stack((t["hdim_1"], t["hdim_2"]))
|
|
278
|
+
coordinate_points = interpn(points, values, xi)
|
|
279
|
+
|
|
280
|
+
if variable_cube.coord_dims(coord) == (hdim_1, hdim_2, ndim_time):
|
|
281
|
+
points = (dimvec_1, dimvec_2)
|
|
282
|
+
values = variable_cube[:, :, 0].coord(coord).points
|
|
283
|
+
xi = np.column_stack((t["hdim_1"], t["hdim_2"]))
|
|
284
|
+
coordinate_points = interpn(points, values, xi)
|
|
285
|
+
|
|
286
|
+
if variable_cube.coord_dims(coord) == (hdim_2, ndim_time, hdim_1):
|
|
287
|
+
points = (dimvec_2, dimvec_1)
|
|
288
|
+
values = variable_cube[:, 0, :].coord(coord).points
|
|
289
|
+
xi = np.column_stack((t["hdim_2"], t["hdim_1"]))
|
|
290
|
+
coordinate_points = interpn(points, values, xi)
|
|
291
|
+
|
|
292
|
+
if variable_cube.coord_dims(coord) == (hdim_2, hdim_1, ndim_time):
|
|
293
|
+
points = (dimvec_2, dimvec_1)
|
|
294
|
+
values = variable_cube[:, :, 0].coord(coord).points
|
|
295
|
+
xi = np.column_stack((t["hdim_2"], t["hdim_1"]))
|
|
296
|
+
coordinate_points = interpn(points, values, xi)
|
|
297
|
+
|
|
298
|
+
# write resulting array or list into DataFrame:
|
|
299
|
+
t[coord] = coordinate_points
|
|
300
|
+
|
|
301
|
+
logging.debug("added coord: " + coord)
|
|
302
|
+
return t
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def add_coordinates_3D(
|
|
306
|
+
t: pd.DataFrame,
|
|
307
|
+
variable_cube: iris.cube.Cube,
|
|
308
|
+
vertical_coord: Union[str, int] = None,
|
|
309
|
+
vertical_axis: Union[int, None] = None,
|
|
310
|
+
assume_coords_fixed_in_time=True,
|
|
311
|
+
):
|
|
312
|
+
"""Function adding coordinates from the tracking cube to the trajectories
|
|
313
|
+
for the 3D case: time, longitude&latitude, x&y dimensions, and altitude
|
|
314
|
+
|
|
315
|
+
Parameters
|
|
316
|
+
----------
|
|
317
|
+
t: pandas DataFrame
|
|
318
|
+
trajectories/features
|
|
319
|
+
variable_cube: iris.cube.Cube
|
|
320
|
+
Cube (usually the one you are tracking on) at least conaining the dimension of 'time'.
|
|
321
|
+
Typically, 'longitude','latitude','x_projection_coordinate','y_projection_coordinate',
|
|
322
|
+
and 'altitude' (if 3D) are the coordinates that we expect, although this function
|
|
323
|
+
will happily interpolate along any dimension coordinates you give.
|
|
324
|
+
vertical_coord: str or int
|
|
325
|
+
Name or axis number of the vertical coordinate. If None, tries to auto-detect.
|
|
326
|
+
If it is a string, it looks for the coordinate or the dimension name corresponding
|
|
327
|
+
to the string. If it is an int, it assumes that it is the vertical axis.
|
|
328
|
+
Note that if you only have a 2D or 3D coordinate for altitude, you must
|
|
329
|
+
pass in an int.
|
|
330
|
+
vertical_axis: int or None
|
|
331
|
+
Axis number of the vertical.
|
|
332
|
+
assume_coords_fixed_in_time: bool
|
|
333
|
+
If true, it assumes that the coordinates are fixed in time, even if the
|
|
334
|
+
coordinates say they vary in time. This is, by default, True, to preserve
|
|
335
|
+
legacy functionality. If False, it assumes that if a coordinate says
|
|
336
|
+
it varies in time, it takes the coordinate at its word.
|
|
337
|
+
|
|
338
|
+
Returns
|
|
339
|
+
-------
|
|
340
|
+
pandas DataFrame
|
|
341
|
+
trajectories with added coordinates
|
|
342
|
+
"""
|
|
343
|
+
from scipy.interpolate import interp2d, interp1d, interpn
|
|
344
|
+
|
|
345
|
+
logging.debug("start adding coordinates from cube")
|
|
346
|
+
|
|
347
|
+
# pull time as datetime object and timestr from input data and add it to DataFrame:
|
|
348
|
+
t["time"] = None
|
|
349
|
+
t["timestr"] = None
|
|
350
|
+
|
|
351
|
+
logging.debug("adding time coordinate")
|
|
352
|
+
|
|
353
|
+
time_in = variable_cube.coord("time")
|
|
354
|
+
time_in_datetime = time_in.units.num2date(time_in.points)
|
|
355
|
+
|
|
356
|
+
t["time"] = time_in_datetime[t["frame"]]
|
|
357
|
+
t["timestr"] = [
|
|
358
|
+
x.strftime("%Y-%m-%d %H:%M:%S") for x in time_in_datetime[t["frame"]]
|
|
359
|
+
]
|
|
360
|
+
|
|
361
|
+
# Get list of all coordinates in input cube except for time (already treated):
|
|
362
|
+
coord_names = [coord.name() for coord in variable_cube.coords()]
|
|
363
|
+
coord_names.remove("time")
|
|
364
|
+
|
|
365
|
+
logging.debug("time coordinate added")
|
|
366
|
+
|
|
367
|
+
# chose right dimension for horizontal and vertical axes based on time dimension:
|
|
368
|
+
ndim_time = variable_cube.coord_dims("time")[0]
|
|
369
|
+
|
|
370
|
+
if type(vertical_coord) is int:
|
|
371
|
+
ndim_vertical = vertical_coord
|
|
372
|
+
vertical_axis = None
|
|
373
|
+
else:
|
|
374
|
+
vertical_axis = tb_utils_gi.find_vertical_coord_name(
|
|
375
|
+
variable_cube, vertical_coord=vertical_coord
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
if vertical_axis is not None:
|
|
379
|
+
ndim_vertical = tb_utils_gi.find_axis_from_coord(variable_cube, vertical_axis)
|
|
380
|
+
if ndim_vertical is None:
|
|
381
|
+
raise ValueError("Vertical Coordinate not found")
|
|
382
|
+
|
|
383
|
+
# We need to figure out the axis number of hdim_1 and hdim_2.
|
|
384
|
+
ndim_hdim_1, ndim_hdim_2 = tb_utils_gi.find_hdim_axes_3D(
|
|
385
|
+
variable_cube, vertical_axis=ndim_vertical
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
if ndim_hdim_1 is None or ndim_hdim_2 is None:
|
|
389
|
+
raise ValueError("Could not find hdim coordinates.")
|
|
390
|
+
|
|
391
|
+
# create vectors to use to interpolate from pixels to coordinates
|
|
392
|
+
dimvec_1 = np.arange(variable_cube.shape[ndim_vertical])
|
|
393
|
+
dimvec_2 = np.arange(variable_cube.shape[ndim_hdim_1])
|
|
394
|
+
dimvec_3 = np.arange(variable_cube.shape[ndim_hdim_2])
|
|
395
|
+
dimvec_time = np.arange(variable_cube.shape[ndim_time])
|
|
396
|
+
|
|
397
|
+
coord_to_ax = {
|
|
398
|
+
ndim_vertical: (dimvec_1, "vdim"),
|
|
399
|
+
ndim_time: (dimvec_time, "time"),
|
|
400
|
+
ndim_hdim_1: (dimvec_2, "hdim_1"),
|
|
401
|
+
ndim_hdim_2: (dimvec_3, "hdim_2"),
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
# loop over coordinates in input data:
|
|
405
|
+
for coord in coord_names:
|
|
406
|
+
logging.debug("adding coord: " + coord)
|
|
407
|
+
# interpolate 1D coordinates:
|
|
408
|
+
var_coord = variable_cube.coord(coord)
|
|
409
|
+
if var_coord.ndim == 1:
|
|
410
|
+
curr_dim = coord_to_ax[variable_cube.coord_dims(coord)[0]]
|
|
411
|
+
f = interp1d(curr_dim[0], var_coord.points, fill_value="extrapolate")
|
|
412
|
+
coordinate_points = f(t[curr_dim[1]])
|
|
413
|
+
|
|
414
|
+
# interpolate 2D coordinates
|
|
415
|
+
elif var_coord.ndim == 2:
|
|
416
|
+
first_dim = coord_to_ax[variable_cube.coord_dims(coord)[1]]
|
|
417
|
+
second_dim = coord_to_ax[variable_cube.coord_dims(coord)[0]]
|
|
418
|
+
points = (second_dim[0], first_dim[0])
|
|
419
|
+
values = var_coord.points
|
|
420
|
+
xi = np.column_stack((t[second_dim[1]], t[first_dim[1]]))
|
|
421
|
+
coordinate_points = interpn(points, values, xi)
|
|
422
|
+
|
|
423
|
+
# Deal with the special case where the coordinate is 3D but
|
|
424
|
+
# one of the dimensions is time and we assume the coordinates
|
|
425
|
+
# don't vary in time.
|
|
426
|
+
elif (
|
|
427
|
+
var_coord.ndim == 3
|
|
428
|
+
and ndim_time in variable_cube.coord_dims(coord)
|
|
429
|
+
and assume_coords_fixed_in_time
|
|
430
|
+
):
|
|
431
|
+
time_pos = variable_cube.coord_dims(coord).index(ndim_time)
|
|
432
|
+
hdim1_pos = 0 if time_pos != 0 else 1
|
|
433
|
+
hdim2_pos = 1 if time_pos == 2 else 2
|
|
434
|
+
first_dim = coord_to_ax[variable_cube.coord_dims(coord)[hdim2_pos]]
|
|
435
|
+
second_dim = coord_to_ax[variable_cube.coord_dims(coord)[hdim1_pos]]
|
|
436
|
+
points = (second_dim[0], first_dim[0])
|
|
437
|
+
values = var_coord.points
|
|
438
|
+
xi = np.column_stack((t[second_dim[1]], t[first_dim[1]]))
|
|
439
|
+
coordinate_points = interpn(points, values, xi)
|
|
440
|
+
|
|
441
|
+
# interpolate 3D coordinates:
|
|
442
|
+
elif var_coord.ndim == 3:
|
|
443
|
+
first_dim = coord_to_ax[variable_cube.coord_dims(coord)[0]]
|
|
444
|
+
second_dim = coord_to_ax[variable_cube.coord_dims(coord)[1]]
|
|
445
|
+
third_dim = coord_to_ax[variable_cube.coord_dims(coord)[2]]
|
|
446
|
+
coordinate_points = interpn(
|
|
447
|
+
[first_dim[0], second_dim[0], third_dim[0]],
|
|
448
|
+
var_coord.points,
|
|
449
|
+
[
|
|
450
|
+
[a, b, c]
|
|
451
|
+
for a, b, c in zip(
|
|
452
|
+
t[first_dim[1]], t[second_dim[1]], t[third_dim[1]]
|
|
453
|
+
)
|
|
454
|
+
],
|
|
455
|
+
)
|
|
456
|
+
# coordinate_points=[f(a,b) for a,b in zip(t[first_dim[1]],t[second_dim[1]])]
|
|
457
|
+
|
|
458
|
+
# write resulting array or list into DataFrame:
|
|
459
|
+
t[coord] = coordinate_points
|
|
460
|
+
|
|
461
|
+
logging.debug("added coord: " + coord)
|
|
462
|
+
return t
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""Internal tobac utilities"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
import numpy as np
|
|
5
|
+
import skimage.measure
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def get_label_props_in_dict(labels: np.array) -> dict:
|
|
9
|
+
"""Function to get the label properties into a dictionary format.
|
|
10
|
+
|
|
11
|
+
Parameters
|
|
12
|
+
----------
|
|
13
|
+
labels : 2D array-like
|
|
14
|
+
Output of the `skimage.measure.label` function.
|
|
15
|
+
|
|
16
|
+
Returns
|
|
17
|
+
-------
|
|
18
|
+
region_properties_dict: dict
|
|
19
|
+
Output from skimage.measure.regionprops in dictionary
|
|
20
|
+
format, where they key is the label number.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
region_properties_raw = skimage.measure.regionprops(labels)
|
|
24
|
+
region_properties_dict = {
|
|
25
|
+
region_prop.label: region_prop for region_prop in region_properties_raw
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return region_properties_dict
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def get_indices_of_labels_from_reg_prop_dict(region_property_dict: dict) -> tuple[dict]:
|
|
32
|
+
"""Function to get the x, y, and z indices (as well as point count) of all labeled regions.
|
|
33
|
+
Parameters
|
|
34
|
+
----------
|
|
35
|
+
region_property_dict : dict of region_property objects
|
|
36
|
+
This dict should come from the get_label_props_in_dict function.
|
|
37
|
+
Returns
|
|
38
|
+
-------
|
|
39
|
+
curr_loc_indices : dict
|
|
40
|
+
The number of points in the label number (key: label number).
|
|
41
|
+
z_indices : dict
|
|
42
|
+
The z indices in the label number. If a 2D property dict is passed, this value is not returned.
|
|
43
|
+
y_indices : dict
|
|
44
|
+
The y indices in the label number (key: label number).
|
|
45
|
+
x_indices : dict
|
|
46
|
+
The x indices in the label number (key: label number).
|
|
47
|
+
Raises
|
|
48
|
+
------
|
|
49
|
+
ValueError
|
|
50
|
+
A ValueError is raised if there are no regions in the region
|
|
51
|
+
property dict.
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
if len(region_property_dict) == 0:
|
|
55
|
+
raise ValueError("No regions!")
|
|
56
|
+
|
|
57
|
+
z_indices = dict()
|
|
58
|
+
y_indices = dict()
|
|
59
|
+
x_indices = dict()
|
|
60
|
+
curr_loc_indices = dict()
|
|
61
|
+
is_3D = False
|
|
62
|
+
|
|
63
|
+
# loop through all skimage identified regions
|
|
64
|
+
for region_prop_key in region_property_dict:
|
|
65
|
+
region_prop = region_property_dict[region_prop_key]
|
|
66
|
+
index = region_prop.label
|
|
67
|
+
if len(region_prop.coords[0]) >= 3:
|
|
68
|
+
is_3D = True
|
|
69
|
+
curr_z_ixs, curr_y_ixs, curr_x_ixs = np.transpose(region_prop.coords)
|
|
70
|
+
z_indices[index] = curr_z_ixs
|
|
71
|
+
else:
|
|
72
|
+
curr_y_ixs, curr_x_ixs = np.transpose(region_prop.coords)
|
|
73
|
+
z_indices[index] = -1
|
|
74
|
+
|
|
75
|
+
y_indices[index] = curr_y_ixs
|
|
76
|
+
x_indices[index] = curr_x_ixs
|
|
77
|
+
curr_loc_indices[index] = len(curr_y_ixs)
|
|
78
|
+
# print("indices found")
|
|
79
|
+
if is_3D:
|
|
80
|
+
return [curr_loc_indices, z_indices, y_indices, x_indices]
|
|
81
|
+
else:
|
|
82
|
+
return [curr_loc_indices, y_indices, x_indices]
|