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,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