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,628 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Perform analysis on the properties of tracked cells
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import os
|
|
7
|
+
import warnings
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
import pandas as pd
|
|
11
|
+
from iris.cube import Cube, CubeList
|
|
12
|
+
from iris.coords import AuxCoord
|
|
13
|
+
from iris import Constraint, save
|
|
14
|
+
|
|
15
|
+
from tobac.centerofgravity import calculate_cog
|
|
16
|
+
from tobac.utils.mask import mask_cell, mask_cell_surface, mask_cube_cell
|
|
17
|
+
from tobac.utils.general import get_bounding_box
|
|
18
|
+
from tobac.analysis.spatial import (
|
|
19
|
+
calculate_distance,
|
|
20
|
+
calculate_velocity,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
__all__ = (
|
|
24
|
+
"cell_statistics_all",
|
|
25
|
+
"cell_statistics",
|
|
26
|
+
"cog_cell",
|
|
27
|
+
"lifetime_histogram",
|
|
28
|
+
"velocity_histogram",
|
|
29
|
+
"histogram_cellwise",
|
|
30
|
+
"calculate_overlap",
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def cell_statistics_all(
|
|
35
|
+
input_cubes,
|
|
36
|
+
track,
|
|
37
|
+
mask,
|
|
38
|
+
aggregators,
|
|
39
|
+
output_path="./",
|
|
40
|
+
cell_selection=None,
|
|
41
|
+
output_name="Profiles",
|
|
42
|
+
width=10000,
|
|
43
|
+
z_coord="model_level_number",
|
|
44
|
+
dimensions=["x", "y"],
|
|
45
|
+
**kwargs,
|
|
46
|
+
):
|
|
47
|
+
"""
|
|
48
|
+
Parameters
|
|
49
|
+
----------
|
|
50
|
+
input_cubes : iris.cube.Cube
|
|
51
|
+
|
|
52
|
+
track : dask.dataframe.DataFrame
|
|
53
|
+
|
|
54
|
+
mask : iris.cube.Cube
|
|
55
|
+
Cube containing mask (int id for tracked volumes 0 everywhere
|
|
56
|
+
else).
|
|
57
|
+
|
|
58
|
+
aggregators : list
|
|
59
|
+
list of iris.analysis.Aggregator instances
|
|
60
|
+
|
|
61
|
+
output_path : str, optional
|
|
62
|
+
Default is './'.
|
|
63
|
+
|
|
64
|
+
cell_selection : optional
|
|
65
|
+
Default is None.
|
|
66
|
+
|
|
67
|
+
output_name : str, optional
|
|
68
|
+
Default is 'Profiles'.
|
|
69
|
+
|
|
70
|
+
width : int, optional
|
|
71
|
+
Default is 10000.
|
|
72
|
+
|
|
73
|
+
z_coord : str, optional
|
|
74
|
+
Name of the vertical coordinate in the cube. Default is
|
|
75
|
+
'model_level_number'.
|
|
76
|
+
|
|
77
|
+
dimensions : list of str, optional
|
|
78
|
+
Default is ['x', 'y'].
|
|
79
|
+
|
|
80
|
+
**kwargs
|
|
81
|
+
|
|
82
|
+
Returns
|
|
83
|
+
-------
|
|
84
|
+
None
|
|
85
|
+
"""
|
|
86
|
+
warnings.warn(
|
|
87
|
+
"cell_statistics_all is depreciated and will be removed or significantly changed in v2.0.",
|
|
88
|
+
DeprecationWarning,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
if cell_selection is None:
|
|
92
|
+
cell_selection = np.unique(track["cell"])
|
|
93
|
+
for cell in cell_selection:
|
|
94
|
+
cell_statistics(
|
|
95
|
+
input_cubes=input_cubes,
|
|
96
|
+
track=track,
|
|
97
|
+
mask=mask,
|
|
98
|
+
dimensions=dimensions,
|
|
99
|
+
aggregators=aggregators,
|
|
100
|
+
cell=cell,
|
|
101
|
+
output_path=output_path,
|
|
102
|
+
output_name=output_name,
|
|
103
|
+
width=width,
|
|
104
|
+
z_coord=z_coord,
|
|
105
|
+
**kwargs,
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def cell_statistics(
|
|
110
|
+
input_cubes,
|
|
111
|
+
track,
|
|
112
|
+
mask,
|
|
113
|
+
aggregators,
|
|
114
|
+
cell,
|
|
115
|
+
output_path="./",
|
|
116
|
+
output_name="Profiles",
|
|
117
|
+
width=10000,
|
|
118
|
+
z_coord="model_level_number",
|
|
119
|
+
dimensions=["x", "y"],
|
|
120
|
+
**kwargs,
|
|
121
|
+
):
|
|
122
|
+
"""
|
|
123
|
+
Parameters
|
|
124
|
+
----------
|
|
125
|
+
input_cubes : iris.cube.Cube
|
|
126
|
+
|
|
127
|
+
track : dask.dataframe.DataFrame
|
|
128
|
+
|
|
129
|
+
mask : iris.cube.Cube
|
|
130
|
+
Cube containing mask (int id for tracked volumes 0 everywhere
|
|
131
|
+
else).
|
|
132
|
+
|
|
133
|
+
aggregators list
|
|
134
|
+
list of iris.analysis.Aggregator instances
|
|
135
|
+
|
|
136
|
+
cell : int
|
|
137
|
+
Integer id of cell to create masked cube for output.
|
|
138
|
+
|
|
139
|
+
output_path : str, optional
|
|
140
|
+
Default is './'.
|
|
141
|
+
|
|
142
|
+
output_name : str, optional
|
|
143
|
+
Default is 'Profiles'.
|
|
144
|
+
|
|
145
|
+
width : int, optional
|
|
146
|
+
Default is 10000.
|
|
147
|
+
|
|
148
|
+
z_coord : str, optional
|
|
149
|
+
Name of the vertical coordinate in the cube. Default is
|
|
150
|
+
'model_level_number'.
|
|
151
|
+
|
|
152
|
+
dimensions : list of str, optional
|
|
153
|
+
Default is ['x', 'y'].
|
|
154
|
+
|
|
155
|
+
**kwargs
|
|
156
|
+
|
|
157
|
+
Returns
|
|
158
|
+
-------
|
|
159
|
+
None
|
|
160
|
+
"""
|
|
161
|
+
|
|
162
|
+
warnings.warn(
|
|
163
|
+
"cell_statistics is depreciated and will be removed or significantly changed in v2.0.",
|
|
164
|
+
DeprecationWarning,
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
# If input is single cube, turn into cubelist
|
|
168
|
+
if type(input_cubes) is Cube:
|
|
169
|
+
input_cubes = CubeList([input_cubes])
|
|
170
|
+
|
|
171
|
+
logging.debug("Start calculating profiles for cell " + str(cell))
|
|
172
|
+
track_i = track[track["cell"] == cell]
|
|
173
|
+
|
|
174
|
+
cubes_profile = {}
|
|
175
|
+
for aggregator in aggregators:
|
|
176
|
+
cubes_profile[aggregator.name()] = CubeList()
|
|
177
|
+
|
|
178
|
+
for time_i in track_i["time"].values:
|
|
179
|
+
constraint_time = Constraint(time=time_i)
|
|
180
|
+
|
|
181
|
+
mask_i = mask.extract(constraint_time)
|
|
182
|
+
mask_cell_i = mask_cell(mask_i, cell, track_i, masked=False)
|
|
183
|
+
mask_cell_surface_i = mask_cell_surface(
|
|
184
|
+
mask_i, cell, track_i, masked=False, z_coord=z_coord
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
x_dim = mask_cell_surface_i.coord_dims("projection_x_coordinate")[0]
|
|
188
|
+
y_dim = mask_cell_surface_i.coord_dims("projection_y_coordinate")[0]
|
|
189
|
+
x_coord = mask_cell_surface_i.coord("projection_x_coordinate")
|
|
190
|
+
y_coord = mask_cell_surface_i.coord("projection_y_coordinate")
|
|
191
|
+
|
|
192
|
+
if (mask_cell_surface_i.core_data() > 0).any():
|
|
193
|
+
box_mask_i = get_bounding_box(mask_cell_surface_i.core_data(), buffer=1)
|
|
194
|
+
|
|
195
|
+
box_mask = [
|
|
196
|
+
[
|
|
197
|
+
x_coord.points[box_mask_i[x_dim][0]],
|
|
198
|
+
x_coord.points[box_mask_i[x_dim][1]],
|
|
199
|
+
],
|
|
200
|
+
[
|
|
201
|
+
y_coord.points[box_mask_i[y_dim][0]],
|
|
202
|
+
y_coord.points[box_mask_i[y_dim][1]],
|
|
203
|
+
],
|
|
204
|
+
]
|
|
205
|
+
else:
|
|
206
|
+
box_mask = [[np.nan, np.nan], [np.nan, np.nan]]
|
|
207
|
+
|
|
208
|
+
x = track_i[track_i["time"].values == time_i]["projection_x_coordinate"].values[
|
|
209
|
+
0
|
|
210
|
+
]
|
|
211
|
+
y = track_i[track_i["time"].values == time_i]["projection_y_coordinate"].values[
|
|
212
|
+
0
|
|
213
|
+
]
|
|
214
|
+
|
|
215
|
+
box_slice = [[x - width, x + width], [y - width, y + width]]
|
|
216
|
+
|
|
217
|
+
x_min = np.nanmin([box_mask[0][0], box_slice[0][0]])
|
|
218
|
+
x_max = np.nanmax([box_mask[0][1], box_slice[0][1]])
|
|
219
|
+
y_min = np.nanmin([box_mask[1][0], box_slice[1][0]])
|
|
220
|
+
y_max = np.nanmax([box_mask[1][1], box_slice[1][1]])
|
|
221
|
+
|
|
222
|
+
constraint_x = Constraint(
|
|
223
|
+
projection_x_coordinate=lambda cell: int(x_min) < cell < int(x_max)
|
|
224
|
+
)
|
|
225
|
+
constraint_y = Constraint(
|
|
226
|
+
projection_y_coordinate=lambda cell: int(y_min) < cell < int(y_max)
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
constraint = constraint_time & constraint_x & constraint_y
|
|
230
|
+
# Mask_cell_surface_i=mask_cell_surface(Mask_w_i,cell,masked=False,z_coord='model_level_number')
|
|
231
|
+
mask_cell_i = mask_cell_i.extract(constraint)
|
|
232
|
+
mask_cell_surface_i = mask_cell_surface_i.extract(constraint)
|
|
233
|
+
|
|
234
|
+
input_cubes_i = input_cubes.extract(constraint)
|
|
235
|
+
for cube in input_cubes_i:
|
|
236
|
+
cube_masked = mask_cube_cell(cube, mask_cell_i, cell, track_i)
|
|
237
|
+
coords_remove = []
|
|
238
|
+
for coordinate in cube_masked.coords(dim_coords=False):
|
|
239
|
+
if coordinate.name() not in dimensions:
|
|
240
|
+
for dim in dimensions:
|
|
241
|
+
if set(cube_masked.coord_dims(coordinate)).intersection(
|
|
242
|
+
set(cube_masked.coord_dims(dim))
|
|
243
|
+
):
|
|
244
|
+
coords_remove.append(coordinate.name())
|
|
245
|
+
for coordinate in set(coords_remove):
|
|
246
|
+
cube_masked.remove_coord(coordinate)
|
|
247
|
+
|
|
248
|
+
for aggregator in aggregators:
|
|
249
|
+
cube_collapsed = cube_masked.collapsed(dimensions, aggregator, **kwargs)
|
|
250
|
+
# remove all collapsed coordinates (x and y dim, scalar now) and keep only time as all these coordinates are useless
|
|
251
|
+
for coordinate in cube_collapsed.coords():
|
|
252
|
+
if not cube_collapsed.coord_dims(coordinate):
|
|
253
|
+
if coordinate.name() != "time":
|
|
254
|
+
cube_collapsed.remove_coord(coordinate)
|
|
255
|
+
logging.debug(str(cube_collapsed))
|
|
256
|
+
cubes_profile[aggregator.name()].append(cube_collapsed)
|
|
257
|
+
|
|
258
|
+
minutes = (track_i["time_cell"] / pd.Timedelta(minutes=1)).values
|
|
259
|
+
latitude = track_i["latitude"].values
|
|
260
|
+
longitude = track_i["longitude"].values
|
|
261
|
+
minutes_coord = AuxCoord(minutes, long_name="cell_time", units="min")
|
|
262
|
+
latitude_coord = AuxCoord(latitude, long_name="latitude", units="degrees")
|
|
263
|
+
longitude_coord = AuxCoord(longitude, long_name="longitude", units="degrees")
|
|
264
|
+
|
|
265
|
+
for aggregator in aggregators:
|
|
266
|
+
cubes_profile[aggregator.name()] = cubes_profile[aggregator.name()].merge()
|
|
267
|
+
for cube in cubes_profile[aggregator.name()]:
|
|
268
|
+
cube.add_aux_coord(minutes_coord, data_dims=cube.coord_dims("time"))
|
|
269
|
+
cube.add_aux_coord(latitude_coord, data_dims=cube.coord_dims("time"))
|
|
270
|
+
cube.add_aux_coord(longitude_coord, data_dims=cube.coord_dims("time"))
|
|
271
|
+
os.makedirs(
|
|
272
|
+
os.path.join(output_path, output_name, aggregator.name()), exist_ok=True
|
|
273
|
+
)
|
|
274
|
+
savefile = os.path.join(
|
|
275
|
+
output_path,
|
|
276
|
+
output_name,
|
|
277
|
+
aggregator.name(),
|
|
278
|
+
output_name + "_" + aggregator.name() + "_" + str(int(cell)) + ".nc",
|
|
279
|
+
)
|
|
280
|
+
save(cubes_profile[aggregator.name()], savefile)
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def cog_cell(
|
|
284
|
+
cell,
|
|
285
|
+
Tracks=None,
|
|
286
|
+
M_total=None,
|
|
287
|
+
M_liquid=None,
|
|
288
|
+
M_frozen=None,
|
|
289
|
+
Mask=None,
|
|
290
|
+
savedir=None,
|
|
291
|
+
):
|
|
292
|
+
"""
|
|
293
|
+
Parameters
|
|
294
|
+
----------
|
|
295
|
+
cell : int
|
|
296
|
+
Integer id of cell to create masked cube for output.
|
|
297
|
+
|
|
298
|
+
Tracks : optional
|
|
299
|
+
Default is None.
|
|
300
|
+
|
|
301
|
+
M_total : subset of cube, optional
|
|
302
|
+
Default is None.
|
|
303
|
+
|
|
304
|
+
M_liquid : subset of cube, optional
|
|
305
|
+
Default is None.
|
|
306
|
+
|
|
307
|
+
M_frozen : subset of cube, optional
|
|
308
|
+
Default is None.
|
|
309
|
+
|
|
310
|
+
savedir : str
|
|
311
|
+
Default is None.
|
|
312
|
+
|
|
313
|
+
Returns
|
|
314
|
+
-------
|
|
315
|
+
None
|
|
316
|
+
"""
|
|
317
|
+
|
|
318
|
+
warnings.warn(
|
|
319
|
+
"cog_cell is depreciated and will be removed or significantly changed in v2.0.",
|
|
320
|
+
DeprecationWarning,
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
logging.debug("Start calculating COG for " + str(cell))
|
|
324
|
+
Track = Tracks[Tracks["cell"] == cell]
|
|
325
|
+
constraint_time = Constraint(
|
|
326
|
+
time=lambda cell: Track.head(1)["time"].values[0]
|
|
327
|
+
<= cell
|
|
328
|
+
<= Track.tail(1)["time"].values[0]
|
|
329
|
+
)
|
|
330
|
+
M_total_i = M_total.extract(constraint_time)
|
|
331
|
+
M_liquid_i = M_liquid.extract(constraint_time)
|
|
332
|
+
M_frozen_i = M_frozen.extract(constraint_time)
|
|
333
|
+
Mask_i = Mask.extract(constraint_time)
|
|
334
|
+
|
|
335
|
+
savedir_cell = os.path.join(savedir, "cells", str(int(cell)))
|
|
336
|
+
os.makedirs(savedir_cell, exist_ok=True)
|
|
337
|
+
savefile_COG_total_i = os.path.join(
|
|
338
|
+
savedir_cell, "COG_total" + "_" + str(int(cell)) + ".h5"
|
|
339
|
+
)
|
|
340
|
+
savefile_COG_liquid_i = os.path.join(
|
|
341
|
+
savedir_cell, "COG_liquid" + "_" + str(int(cell)) + ".h5"
|
|
342
|
+
)
|
|
343
|
+
savefile_COG_frozen_i = os.path.join(
|
|
344
|
+
savedir_cell, "COG_frozen" + "_" + str(int(cell)) + ".h5"
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
Tracks_COG_total_i = calculate_cog(Track, M_total_i, Mask_i)
|
|
348
|
+
# Tracks_COG_total_list.append(Tracks_COG_total_i)
|
|
349
|
+
logging.debug("COG total loaded for " + str(cell))
|
|
350
|
+
|
|
351
|
+
Tracks_COG_liquid_i = calculate_cog(Track, M_liquid_i, Mask_i)
|
|
352
|
+
# Tracks_COG_liquid_list.append(Tracks_COG_liquid_i)
|
|
353
|
+
logging.debug("COG liquid loaded for " + str(cell))
|
|
354
|
+
Tracks_COG_frozen_i = calculate_cog(Track, M_frozen_i, Mask_i)
|
|
355
|
+
# Tracks_COG_frozen_list.append(Tracks_COG_frozen_i)
|
|
356
|
+
logging.debug("COG frozen loaded for " + str(cell))
|
|
357
|
+
|
|
358
|
+
Tracks_COG_total_i.to_hdf(savefile_COG_total_i, "table")
|
|
359
|
+
Tracks_COG_liquid_i.to_hdf(savefile_COG_liquid_i, "table")
|
|
360
|
+
Tracks_COG_frozen_i.to_hdf(savefile_COG_frozen_i, "table")
|
|
361
|
+
logging.debug("individual COG calculated and saved to " + savedir_cell)
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
def lifetime_histogram(
|
|
365
|
+
Track, bin_edges=np.arange(0, 200, 20), density=False, return_values=False
|
|
366
|
+
):
|
|
367
|
+
"""Compute the lifetime histogram of tracked cells.
|
|
368
|
+
|
|
369
|
+
Parameters
|
|
370
|
+
----------
|
|
371
|
+
Track : pandas.DataFrame
|
|
372
|
+
Dataframe of linked features, containing the columns 'cell'
|
|
373
|
+
and 'time_cell'.
|
|
374
|
+
|
|
375
|
+
bin_edges : int or ndarray, optional
|
|
376
|
+
If bin_edges is an int, it defines the number of equal-width
|
|
377
|
+
bins in the given range. If bins is a ndarray, it defines a
|
|
378
|
+
monotonically increasing array of bin edges, including the
|
|
379
|
+
rightmost edge. The unit is minutes.
|
|
380
|
+
Default is np.arange(0, 200, 20).
|
|
381
|
+
|
|
382
|
+
density : bool, optional
|
|
383
|
+
If False, the result will contain the number of samples in
|
|
384
|
+
each bin. If True, the result is the value of the probability
|
|
385
|
+
density function at the bin, normalized such that the integral
|
|
386
|
+
over the range is 1. Default is False.
|
|
387
|
+
|
|
388
|
+
return_values : bool, optional
|
|
389
|
+
Bool determining wether the lifetimes of the features are
|
|
390
|
+
returned from this function. Default is False.
|
|
391
|
+
|
|
392
|
+
Returns
|
|
393
|
+
-------
|
|
394
|
+
hist : ndarray
|
|
395
|
+
The values of the histogram.
|
|
396
|
+
|
|
397
|
+
bin_edges : ndarray
|
|
398
|
+
The edges of the histogram.
|
|
399
|
+
|
|
400
|
+
bin_centers : ndarray
|
|
401
|
+
The centers of the histogram intervalls.
|
|
402
|
+
|
|
403
|
+
minutes, optional : ndarray
|
|
404
|
+
Numpy.array of the lifetime of each feature in minutes.
|
|
405
|
+
Returned if return_values is True.
|
|
406
|
+
|
|
407
|
+
"""
|
|
408
|
+
|
|
409
|
+
Track_cell = Track.groupby("cell")
|
|
410
|
+
minutes = (Track_cell["time_cell"].max() / pd.Timedelta(minutes=1)).values
|
|
411
|
+
hist, bin_edges = np.histogram(minutes, bin_edges, density=density)
|
|
412
|
+
bin_centers = bin_edges[:-1] + 0.5 * np.diff(bin_edges)
|
|
413
|
+
if return_values:
|
|
414
|
+
return hist, bin_edges, bin_centers, minutes
|
|
415
|
+
else:
|
|
416
|
+
return hist, bin_edges, bin_centers
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
def velocity_histogram(
|
|
420
|
+
track,
|
|
421
|
+
bin_edges=np.arange(0, 30, 1),
|
|
422
|
+
density=False,
|
|
423
|
+
method_distance=None,
|
|
424
|
+
return_values=False,
|
|
425
|
+
):
|
|
426
|
+
"""Create an velocity histogram of the tracked cells. If the DataFrame
|
|
427
|
+
does not contain a velocity column, the velocities are calculated.
|
|
428
|
+
|
|
429
|
+
Parameters
|
|
430
|
+
----------
|
|
431
|
+
track: pandas.DataFrame
|
|
432
|
+
DataFrame of the linked features, containing the columns 'cell',
|
|
433
|
+
'time' and either 'projection_x_coordinate' and
|
|
434
|
+
'projection_y_coordinate' or 'latitude' and 'longitude'.
|
|
435
|
+
|
|
436
|
+
bin_edges : int or ndarray, optional
|
|
437
|
+
If bin_edges is an int, it defines the number of equal-width
|
|
438
|
+
bins in the given range. If bins is a ndarray, it defines a
|
|
439
|
+
monotonically increasing array of bin edges, including the
|
|
440
|
+
rightmost edge. Default is np.arange(0, 30000, 500).
|
|
441
|
+
|
|
442
|
+
density : bool, optional
|
|
443
|
+
If False, the result will contain the number of samples in
|
|
444
|
+
each bin. If True, the result is the value of the probability
|
|
445
|
+
density function at the bin, normalized such that the integral
|
|
446
|
+
over the range is 1. Default is False.
|
|
447
|
+
|
|
448
|
+
methods_distance : {None, 'xy', 'latlon'}, optional
|
|
449
|
+
Method of distance calculation, used to calculate the velocity.
|
|
450
|
+
'xy' uses the length of the vector between the two features,
|
|
451
|
+
'latlon' uses the haversine distance. None checks wether the
|
|
452
|
+
required coordinates are present and starts with 'xy'.
|
|
453
|
+
Default is None.
|
|
454
|
+
|
|
455
|
+
return_values : bool, optional
|
|
456
|
+
Bool determining wether the velocities of the features are
|
|
457
|
+
returned from this function. Default is False.
|
|
458
|
+
|
|
459
|
+
Returns
|
|
460
|
+
-------
|
|
461
|
+
hist : ndarray
|
|
462
|
+
The values of the histogram.
|
|
463
|
+
|
|
464
|
+
bin_edges : ndarray
|
|
465
|
+
The edges of the histogram.
|
|
466
|
+
|
|
467
|
+
velocities , optional : ndarray
|
|
468
|
+
Numpy array with the velocities of each feature.
|
|
469
|
+
|
|
470
|
+
"""
|
|
471
|
+
|
|
472
|
+
if "v" not in track.columns:
|
|
473
|
+
logging.info("calculate velocities")
|
|
474
|
+
track = calculate_velocity(track)
|
|
475
|
+
velocities = track["v"].values
|
|
476
|
+
hist, bin_edges = np.histogram(
|
|
477
|
+
velocities[~np.isnan(velocities)], bin_edges, density=density
|
|
478
|
+
)
|
|
479
|
+
if return_values:
|
|
480
|
+
return hist, bin_edges, velocities
|
|
481
|
+
else:
|
|
482
|
+
return hist, bin_edges
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
def histogram_cellwise(
|
|
486
|
+
Track, variable=None, bin_edges=None, quantity="max", density=False
|
|
487
|
+
):
|
|
488
|
+
"""Create a histogram of the maximum, minimum or mean of
|
|
489
|
+
a variable for the cells (series of features linked together
|
|
490
|
+
over multiple timesteps) of a track. Essentially a wrapper
|
|
491
|
+
of the numpy.histogram() method.
|
|
492
|
+
|
|
493
|
+
Parameters
|
|
494
|
+
----------
|
|
495
|
+
Track : pandas.DataFrame
|
|
496
|
+
The track containing the variable to create the histogram
|
|
497
|
+
from.
|
|
498
|
+
|
|
499
|
+
variable : string, optional
|
|
500
|
+
Column of the DataFrame with the variable on which the
|
|
501
|
+
histogram is to be based on. Default is None.
|
|
502
|
+
|
|
503
|
+
bin_edges : int or ndarray, optional
|
|
504
|
+
If bin_edges is an int, it defines the number of
|
|
505
|
+
equal-width bins in the given range. If bins is a ndarray,
|
|
506
|
+
it defines a monotonically increasing array of bin edges,
|
|
507
|
+
including the rightmost edge.
|
|
508
|
+
|
|
509
|
+
quantity : {'max', 'min', 'mean'}, optional
|
|
510
|
+
Flag determining wether to use maximum, minimum or mean
|
|
511
|
+
of a variable from all timeframes the cell covers.
|
|
512
|
+
Default is 'max'.
|
|
513
|
+
|
|
514
|
+
density : bool, optional
|
|
515
|
+
If False, the result will contain the number of samples
|
|
516
|
+
in each bin. If True, the result is the value of the
|
|
517
|
+
probability density function at the bin, normalized such
|
|
518
|
+
that the integral over the range is 1.
|
|
519
|
+
Default is False.
|
|
520
|
+
|
|
521
|
+
Returns
|
|
522
|
+
-------
|
|
523
|
+
hist : ndarray
|
|
524
|
+
The values of the histogram
|
|
525
|
+
|
|
526
|
+
bin_edges : ndarray
|
|
527
|
+
The edges of the histogram
|
|
528
|
+
|
|
529
|
+
bin_centers : ndarray
|
|
530
|
+
The centers of the histogram intervalls
|
|
531
|
+
|
|
532
|
+
Raises
|
|
533
|
+
------
|
|
534
|
+
ValueError
|
|
535
|
+
If quantity is not 'max', 'min' or 'mean'.
|
|
536
|
+
|
|
537
|
+
"""
|
|
538
|
+
|
|
539
|
+
Track_cell = Track.groupby("cell")
|
|
540
|
+
if quantity == "max":
|
|
541
|
+
variable_cell = Track_cell[variable].max().values
|
|
542
|
+
elif quantity == "min":
|
|
543
|
+
variable_cell = Track_cell[variable].min().values
|
|
544
|
+
elif quantity == "mean":
|
|
545
|
+
variable_cell = Track_cell[variable].mean().values
|
|
546
|
+
else:
|
|
547
|
+
raise ValueError("quantity unknown, must be max, min or mean")
|
|
548
|
+
hist, bin_edges = np.histogram(variable_cell, bin_edges, density=density)
|
|
549
|
+
bin_centers = bin_edges[:-1] + 0.5 * np.diff(bin_edges)
|
|
550
|
+
|
|
551
|
+
return hist, bin_edges, bin_centers
|
|
552
|
+
|
|
553
|
+
|
|
554
|
+
def calculate_overlap(
|
|
555
|
+
track_1, track_2, min_sum_inv_distance=None, min_mean_inv_distance=None
|
|
556
|
+
):
|
|
557
|
+
"""Count the number of time frames in which the
|
|
558
|
+
individual cells of two tracks are present together
|
|
559
|
+
and calculate their mean and summed inverse distance.
|
|
560
|
+
|
|
561
|
+
Parameters
|
|
562
|
+
----------
|
|
563
|
+
track_1, track_2 : pandas.DataFrame
|
|
564
|
+
The tracks conaining the cells to analyze.
|
|
565
|
+
|
|
566
|
+
min_sum_inv_distance : float, optional
|
|
567
|
+
Minimum of the inverse net distance for two
|
|
568
|
+
cells to be counted as overlapping.
|
|
569
|
+
Default is None.
|
|
570
|
+
|
|
571
|
+
min_mean_inv_distance : float, optional
|
|
572
|
+
Minimum of the inverse mean distance for two cells
|
|
573
|
+
to be counted as overlapping. Default is None.
|
|
574
|
+
|
|
575
|
+
Returns
|
|
576
|
+
-------
|
|
577
|
+
overlap : pandas.DataFrame
|
|
578
|
+
DataFrame containing the columns cell_1 and cell_2
|
|
579
|
+
with the index of the cells from the tracks,
|
|
580
|
+
n_overlap with the number of frames both cells are
|
|
581
|
+
present in, mean_inv_distance with the mean inverse
|
|
582
|
+
distance and sum_inv_distance with the summed
|
|
583
|
+
inverse distance of the cells.
|
|
584
|
+
|
|
585
|
+
"""
|
|
586
|
+
|
|
587
|
+
cells_1 = track_1["cell"].unique()
|
|
588
|
+
# n_cells_1_tot=len(cells_1)
|
|
589
|
+
cells_2 = track_2["cell"].unique()
|
|
590
|
+
overlap = pd.DataFrame()
|
|
591
|
+
for i_cell_1, cell_1 in enumerate(cells_1):
|
|
592
|
+
for cell_2 in cells_2:
|
|
593
|
+
track_1_i = track_1[track_1["cell"] == cell_1]
|
|
594
|
+
track_2_i = track_2[track_2["cell"] == cell_2]
|
|
595
|
+
track_1_i = track_1_i[track_1_i["time"].isin(track_2_i["time"])]
|
|
596
|
+
track_2_i = track_2_i[track_2_i["time"].isin(track_1_i["time"])]
|
|
597
|
+
if not track_1_i.empty:
|
|
598
|
+
n_overlap = len(track_1_i)
|
|
599
|
+
distances = []
|
|
600
|
+
for i in range(len(track_1_i)):
|
|
601
|
+
distance = calculate_distance(
|
|
602
|
+
track_1_i.iloc[[i]], track_2_i.iloc[[i]], method_distance="xy"
|
|
603
|
+
)
|
|
604
|
+
distances.append(distance)
|
|
605
|
+
# mean_distance=np.mean(distances)
|
|
606
|
+
mean_inv_distance = np.mean(1 / (1 + np.array(distances) / 1000))
|
|
607
|
+
# mean_inv_squaredistance=np.mean(1/(1+(np.array(distances)/1000)**2))
|
|
608
|
+
sum_inv_distance = np.sum(1 / (1 + np.array(distances) / 1000))
|
|
609
|
+
# sum_inv_squaredistance=np.sum(1/(1+(np.array(distances)/1000)**2))
|
|
610
|
+
overlap = overlap.append(
|
|
611
|
+
{
|
|
612
|
+
"cell_1": cell_1,
|
|
613
|
+
"cell_2": cell_2,
|
|
614
|
+
"n_overlap": n_overlap,
|
|
615
|
+
# 'mean_distance':mean_distance,
|
|
616
|
+
"mean_inv_distance": mean_inv_distance,
|
|
617
|
+
# 'mean_inv_squaredistance':mean_inv_squaredistance,
|
|
618
|
+
"sum_inv_distance": sum_inv_distance,
|
|
619
|
+
# 'sum_inv_squaredistance':sum_inv_squaredistance
|
|
620
|
+
},
|
|
621
|
+
ignore_index=True,
|
|
622
|
+
)
|
|
623
|
+
if min_sum_inv_distance:
|
|
624
|
+
overlap = overlap[(overlap["sum_inv_distance"] >= min_sum_inv_distance)]
|
|
625
|
+
if min_mean_inv_distance:
|
|
626
|
+
overlap = overlap[(overlap["mean_inv_distance"] >= min_mean_inv_distance)]
|
|
627
|
+
|
|
628
|
+
return overlap
|