simba-uw-tf-dev 4.7.4__py3-none-any.whl → 4.7.6__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.
Potentially problematic release.
This version of simba-uw-tf-dev might be problematic. Click here for more details.
- simba/SimBA.py +1180 -1178
- simba/assets/.recent_projects.txt +1 -0
- simba/mixins/feature_extraction_mixin.py +0 -2
- simba/mixins/geometry_mixin.py +357 -302
- simba/outlier_tools/skip_outlier_correction.py +2 -2
- simba/plotting/heat_mapper_clf_mp.py +45 -23
- simba/plotting/plot_clf_results.py +2 -1
- simba/plotting/plot_clf_results_mp.py +456 -455
- simba/sandbox/av1.py +5 -0
- simba/sandbox/denoise_hqdn3d.py +266 -0
- simba/sandbox/extract_random_frames.py +126 -0
- simba/sandbox/remove_end_of_video.py +80 -80
- simba/ui/import_pose_frame.py +13 -13
- simba/ui/pop_ups/clf_plot_pop_up.py +1 -1
- simba/ui/pop_ups/video_processing_pop_up.py +11 -10
- simba/ui/video_timelaps.py +158 -36
- simba/video_processors/video_processing.py +20 -13
- {simba_uw_tf_dev-4.7.4.dist-info → simba_uw_tf_dev-4.7.6.dist-info}/METADATA +1 -1
- {simba_uw_tf_dev-4.7.4.dist-info → simba_uw_tf_dev-4.7.6.dist-info}/RECORD +23 -20
- {simba_uw_tf_dev-4.7.4.dist-info → simba_uw_tf_dev-4.7.6.dist-info}/LICENSE +0 -0
- {simba_uw_tf_dev-4.7.4.dist-info → simba_uw_tf_dev-4.7.6.dist-info}/WHEEL +0 -0
- {simba_uw_tf_dev-4.7.4.dist-info → simba_uw_tf_dev-4.7.6.dist-info}/entry_points.txt +0 -0
- {simba_uw_tf_dev-4.7.4.dist-info → simba_uw_tf_dev-4.7.6.dist-info}/top_level.txt +0 -0
simba/mixins/geometry_mixin.py
CHANGED
|
@@ -42,7 +42,7 @@ from simba.utils.checks import (check_float,
|
|
|
42
42
|
check_valid_cpu_pool, check_valid_dict,
|
|
43
43
|
check_valid_lst, check_valid_tuple)
|
|
44
44
|
from simba.utils.data import (create_color_palette, create_color_palettes,
|
|
45
|
-
terminate_cpu_pool)
|
|
45
|
+
get_cpu_pool, terminate_cpu_pool)
|
|
46
46
|
from simba.utils.enums import Defaults, Formats, GeometryEnum, Options
|
|
47
47
|
from simba.utils.errors import CountError, InvalidInputError
|
|
48
48
|
from simba.utils.read_write import (SimbaTimer, find_core_cnt,
|
|
@@ -1225,9 +1225,16 @@ class GeometryMixin(object):
|
|
|
1225
1225
|
To convert single frame animal body-part coordinates to polygon, use single core method :func:`simba.mixins.geometry_mixin.GeometryMixin.bodyparts_to_polygon`
|
|
1226
1226
|
|
|
1227
1227
|
:param np.ndarray data: NumPy array of body part coordinates. Each subarray represents the coordinates of a geometry in a frame.
|
|
1228
|
+
:param Optional[str] video_name: Optional video name for progress messages.
|
|
1229
|
+
:param Optional[str] animal_name: Optional animal name for progress messages.
|
|
1230
|
+
:param Optional[bool] verbose: If True, prints progress messages. Default False.
|
|
1228
1231
|
:param Literal['round', 'square', 'flat'] cap_style: Style of line cap for parallel offset. Options: 'round', 'square', 'flat'.
|
|
1229
1232
|
:param int parallel_offset: Offset distance for parallel lines. Default is 1.
|
|
1233
|
+
:param Optional[float] pixels_per_mm: Pixels per millimeter conversion factor.
|
|
1230
1234
|
:param float simplify_tolerance: Tolerance parameter for simplifying geometries. Default is 2.
|
|
1235
|
+
:param bool preserve_topology: If True, preserves topology during simplification. Default True.
|
|
1236
|
+
:param int core_cnt: Number of CPU cores to use for parallel processing. Default is -1, which uses all available cores. Ignored if pool is provided.
|
|
1237
|
+
:param Optional[multiprocessing.Pool] pool: Optional multiprocessing pool to reuse. If None, creates a new pool. Default None.
|
|
1231
1238
|
:returns: A list of polygons with length data.shape[0]
|
|
1232
1239
|
:rtype: List[Polygon]
|
|
1233
1240
|
|
|
@@ -1266,7 +1273,7 @@ class GeometryMixin(object):
|
|
|
1266
1273
|
|
|
1267
1274
|
pool_terminate_flag = False if pool is not None else True
|
|
1268
1275
|
if pool is not None:
|
|
1269
|
-
check_valid_cpu_pool(value=pool, source=f'{
|
|
1276
|
+
check_valid_cpu_pool(value=pool, source=f'{GeometryMixin().multiframe_bodyparts_to_polygon.__name__} pool', raise_error=True, accepted_cores=core_cnt)
|
|
1270
1277
|
else:
|
|
1271
1278
|
pool = multiprocessing.Pool(core_cnt, maxtasksperchild=Defaults.MAXIMUM_MAX_TASK_PER_CHILD.value)
|
|
1272
1279
|
constants = functools.partial(GeometryMixin.bodyparts_to_polygon,
|
|
@@ -1289,14 +1296,15 @@ class GeometryMixin(object):
|
|
|
1289
1296
|
timer.stop_timer()
|
|
1290
1297
|
if verbose:
|
|
1291
1298
|
stdout_success(msg="Polygons complete.", elapsed_time=timer.elapsed_time_str)
|
|
1292
|
-
if pool_terminate_flag: terminate_cpu_pool(pool=pool)
|
|
1299
|
+
if pool_terminate_flag: terminate_cpu_pool(pool=pool, source=GeometryMixin().multiframe_bodyparts_to_polygon.__name__)
|
|
1293
1300
|
return [l for ll in results for l in ll]
|
|
1294
1301
|
|
|
1295
1302
|
@staticmethod
|
|
1296
1303
|
def multiframe_bodypart_to_point(data: np.ndarray,
|
|
1297
1304
|
core_cnt: Optional[int] = -1,
|
|
1298
1305
|
buffer: Optional[int] = None,
|
|
1299
|
-
px_per_mm: Optional[int] = None
|
|
1306
|
+
px_per_mm: Optional[int] = None,
|
|
1307
|
+
pool: Optional[multiprocessing.Pool] = None) -> Union[List[Point], List[List[Point]]]:
|
|
1300
1308
|
"""
|
|
1301
1309
|
Process multiple frames of body part data in parallel and convert them to shapely Points.
|
|
1302
1310
|
|
|
@@ -1306,10 +1314,10 @@ class GeometryMixin(object):
|
|
|
1306
1314
|
For non-parallized call, use :func:`simba.mixins.geometry_mixin.GeometryMixin.bodyparts_to_points`
|
|
1307
1315
|
|
|
1308
1316
|
:param np.ndarray data: 2D or 3D array with body-part coordinates where rows are frames and columns are x and y coordinates.
|
|
1309
|
-
:param Optional[int] core_cnt: The number of cores to use. If -1, then all available cores.
|
|
1310
|
-
:param Optional[int] px_per_mm: Pixels ro millimeter convertion factor. Required if buffer is not None.
|
|
1317
|
+
:param Optional[int] core_cnt: The number of cores to use. If -1, then all available cores. Ignored if pool is provided.
|
|
1311
1318
|
:param Optional[int] buffer: If not None, then the area of the Point. Thus, if not None, then returns Polygons representing the Points.
|
|
1312
1319
|
:param Optional[int] px_per_mm: Pixels to millimeter convertion factor. Required if buffer is not None.
|
|
1320
|
+
:param Optional[multiprocessing.Pool] pool: Optional multiprocessing pool to reuse. If None, creates a new pool. Default None.
|
|
1313
1321
|
:returns Union[List[Point], List[List[Point]]]: If input is a 2D array, then list of Points. If 3D array, then list of list of Points.
|
|
1314
1322
|
|
|
1315
1323
|
.. note::
|
|
@@ -1331,15 +1339,13 @@ class GeometryMixin(object):
|
|
|
1331
1339
|
data_ndim = data.ndim
|
|
1332
1340
|
if data_ndim == 2:
|
|
1333
1341
|
data = np.array_split(data, core_cnt)
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
)
|
|
1340
|
-
|
|
1341
|
-
results.append(result)
|
|
1342
|
-
terminate_cpu_pool(pool=pool, force=False)
|
|
1342
|
+
pool_terminate_flag = False if pool is not None else True
|
|
1343
|
+
if pool is not None: check_valid_cpu_pool(value=pool, source=f'{GeometryMixin.multiframe_bodypart_to_point.__name__} pool', raise_error=True, accepted_cores=core_cnt)
|
|
1344
|
+
else: pool = get_cpu_pool(core_cnt=core_cnt, source=GeometryMixin.multiframe_bodypart_to_point.__name__)
|
|
1345
|
+
constants = functools.partial(GeometryMixin.bodyparts_to_points, buffer=buffer, px_per_mm=px_per_mm)
|
|
1346
|
+
for cnt, result in enumerate(pool.imap(constants, data, chunksize=1)):
|
|
1347
|
+
results.append(result)
|
|
1348
|
+
if pool_terminate_flag: terminate_cpu_pool(pool=pool, source=GeometryMixin.multiframe_bodypart_to_point.__name__)
|
|
1343
1349
|
if data_ndim == 2:
|
|
1344
1350
|
return [i for s in results for i in s]
|
|
1345
1351
|
else:
|
|
@@ -1350,26 +1356,38 @@ class GeometryMixin(object):
|
|
|
1350
1356
|
size_mm: int,
|
|
1351
1357
|
pixels_per_mm: float,
|
|
1352
1358
|
core_cnt: int = -1,
|
|
1353
|
-
cap_style: Literal["round", "square", "flat"] = "round"
|
|
1359
|
+
cap_style: Literal["round", "square", "flat"] = "round",
|
|
1360
|
+
pool: Optional[multiprocessing.Pool] = None) -> List[Polygon]:
|
|
1361
|
+
"""
|
|
1362
|
+
Buffer shapes by a specified size using multiprocessing.
|
|
1354
1363
|
|
|
1355
|
-
|
|
1356
|
-
|
|
1364
|
+
.. seealso::
|
|
1365
|
+
For single core method, see :func:`simba.mixins.geometry_mixin.GeometryMixin.buffer_shape`
|
|
1366
|
+
|
|
1367
|
+
:param List[Union[Polygon, LineString]] geometries: List of geometries to buffer.
|
|
1368
|
+
:param int size_mm: Buffer size in millimeters.
|
|
1369
|
+
:param float pixels_per_mm: Pixels per millimeter conversion factor.
|
|
1370
|
+
:param int core_cnt: Number of CPU cores to use for parallel processing. Default is -1, which uses all available cores. Ignored if pool is provided.
|
|
1371
|
+
:param Literal["round", "square", "flat"] cap_style: Style of line cap for buffering. Options: 'round', 'square', 'flat'. Default 'round'.
|
|
1372
|
+
:param Optional[multiprocessing.Pool] pool: Optional multiprocessing pool to reuse. If None, creates a new pool. Default None.
|
|
1373
|
+
:return: List of buffered polygons.
|
|
1374
|
+
:rtype: List[Polygon]
|
|
1375
|
+
"""
|
|
1376
|
+
|
|
1377
|
+
check_valid_lst(data=geometries, source=f'{GeometryMixin.multiframe_buffer_shapes.__name__} geometries', valid_dtypes=(Polygon, LineString,), min_len=1, raise_error=True)
|
|
1357
1378
|
check_int(name=f'{GeometryMixin.multiframe_buffer_shapes.__name__} size_mm', value=size_mm, min_value=1)
|
|
1358
|
-
check_float(name=f'{GeometryMixin.multiframe_buffer_shapes.__name__} pixels_per_mm', value=pixels_per_mm,
|
|
1359
|
-
|
|
1360
|
-
check_int(name=f'{GeometryMixin.multiframe_buffer_shapes.__name__} core_cnt', value=core_cnt, min_value=-1,
|
|
1361
|
-
unaccepted_vals=[0])
|
|
1379
|
+
check_float(name=f'{GeometryMixin.multiframe_buffer_shapes.__name__} pixels_per_mm', value=pixels_per_mm, allow_zero=False, allow_negative=False)
|
|
1380
|
+
check_int(name=f'{GeometryMixin.multiframe_buffer_shapes.__name__} core_cnt', value=core_cnt, min_value=-1, unaccepted_vals=[0])
|
|
1362
1381
|
core_cnt = find_core_cnt()[0] if core_cnt == -1 or core_cnt > find_core_cnt()[0] else core_cnt
|
|
1363
1382
|
geomety_lst = lambda lst, core_cnt: [lst[i::core_cnt] for i in range(core_cnt)]
|
|
1364
1383
|
results = []
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
terminate_cpu_pool(pool=pool, force=False)
|
|
1384
|
+
pool_terminate_flag = False if pool is not None else True
|
|
1385
|
+
if pool is not None: check_valid_cpu_pool(value=pool, source=f'{GeometryMixin.multiframe_buffer_shapes.__name__} pool', raise_error=True, accepted_cores=core_cnt)
|
|
1386
|
+
else: pool = get_cpu_pool(core_cnt=core_cnt, source=GeometryMixin.multiframe_buffer_shapes.__name__)
|
|
1387
|
+
constants = functools.partial(GeometryMixin.buffer_shape, size_mm=size_mm, pixels_per_mm=pixels_per_mm, cap_style=cap_style)
|
|
1388
|
+
for cnt, mp_return in enumerate(pool.imap(constants, geomety_lst, chunksize=1)):
|
|
1389
|
+
results.append(mp_return)
|
|
1390
|
+
if pool_terminate_flag: terminate_cpu_pool(pool=pool, source=GeometryMixin.multiframe_buffer_shapes.__name__)
|
|
1373
1391
|
return [l for ll in results for l in ll]
|
|
1374
1392
|
|
|
1375
1393
|
def multiframe_bodyparts_to_circle(self,
|
|
@@ -1386,11 +1404,13 @@ class GeometryMixin(object):
|
|
|
1386
1404
|
For non-parallized call, use :func:`simba.mixins.geometry_mixin.GeometryMixin.bodyparts_to_circle`
|
|
1387
1405
|
|
|
1388
1406
|
:param np.ndarray data: The body-part coordinates xy as a 2d array where rows are frames and columns represent x and y coordinates . E.g., np.array([[364, 308], [369, 309]])
|
|
1389
|
-
:param int
|
|
1390
|
-
:param int core_cnt: Number of CPU cores to use. Defaults to -1 meaning all available cores will be used.
|
|
1407
|
+
:param int parallel_offset: The radius of the resultant circle in millimeters.
|
|
1408
|
+
:param int core_cnt: Number of CPU cores to use. Defaults to -1 meaning all available cores will be used. Ignored if pool is provided.
|
|
1409
|
+
:param bool verbose: If True, prints progress messages. Default True.
|
|
1391
1410
|
:param int pixels_per_mm: The pixels per millimeter of the video. If not passed, 1 will be used meaning revert to radius in pixels rather than millimeters.
|
|
1411
|
+
:param Optional[multiprocessing.Pool] pool: Optional multiprocessing pool to reuse. If None, creates a new pool. Default None.
|
|
1392
1412
|
:returns: List of shapely Polygons of circular shape of size data.shape[0].
|
|
1393
|
-
:rtype: Polygon
|
|
1413
|
+
:rtype: List[Polygon]
|
|
1394
1414
|
|
|
1395
1415
|
:example:
|
|
1396
1416
|
>>> data = np.random.randint(0, 100, (100, 2))
|
|
@@ -1405,7 +1425,7 @@ class GeometryMixin(object):
|
|
|
1405
1425
|
core_cnt = find_core_cnt()[0] if core_cnt == -1 or core_cnt > find_core_cnt()[0] else core_cnt
|
|
1406
1426
|
pool_terminate_flag = False if pool is not None else True
|
|
1407
1427
|
if pool is not None:
|
|
1408
|
-
check_valid_cpu_pool(value=pool, source=f'{
|
|
1428
|
+
check_valid_cpu_pool(value=pool, source=f'{GeometryMixin().multiframe_bodyparts_to_circle.__name__} pool', raise_error=True, accepted_cores=core_cnt)
|
|
1409
1429
|
else:
|
|
1410
1430
|
pool = multiprocessing.Pool(core_cnt, maxtasksperchild=Defaults.MAXIMUM_MAX_TASK_PER_CHILD.value)
|
|
1411
1431
|
constants = functools.partial(GeometryMixin.bodyparts_to_circle, parallel_offset=parallel_offset, pixels_per_mm=pixels_per_mm, verbose=verbose)
|
|
@@ -1413,7 +1433,7 @@ class GeometryMixin(object):
|
|
|
1413
1433
|
data = np.array_split(data, core_cnt)
|
|
1414
1434
|
for cnt, mp_return in enumerate(pool.imap(constants, data, chunksize=1)):
|
|
1415
1435
|
results.extend((mp_return))
|
|
1416
|
-
if pool_terminate_flag: terminate_cpu_pool(pool=pool)
|
|
1436
|
+
if pool_terminate_flag: terminate_cpu_pool(pool=pool, source=GeometryMixin().multiframe_bodyparts_to_circle.__name__)
|
|
1417
1437
|
timer.stop_timer()
|
|
1418
1438
|
if verbose: stdout_success(msg="Multiframe body-parts to circle complete", source=GeometryMixin.multiframe_bodyparts_to_circle.__name__, elapsed_time=timer.elapsed_time_str )
|
|
1419
1439
|
return results
|
|
@@ -1468,7 +1488,8 @@ class GeometryMixin(object):
|
|
|
1468
1488
|
data: np.ndarray,
|
|
1469
1489
|
buffer: Optional[int] = None,
|
|
1470
1490
|
px_per_mm: Optional[float] = None,
|
|
1471
|
-
core_cnt: Optional[int] = -1
|
|
1491
|
+
core_cnt: Optional[int] = -1,
|
|
1492
|
+
pool: Optional[multiprocessing.Pool] = None) -> List[LineString]:
|
|
1472
1493
|
"""
|
|
1473
1494
|
Convert multiframe body-parts data to a list of LineString objects using multiprocessing.
|
|
1474
1495
|
|
|
@@ -1477,8 +1498,9 @@ class GeometryMixin(object):
|
|
|
1477
1498
|
|
|
1478
1499
|
:param np.ndarray data: Input array representing multiframe body-parts data. It should be a 3D array with dimensions (frames, points, coordinates).
|
|
1479
1500
|
:param Optional[int] buffer: If not None, then the linestring will be expanded into a 2D geometry polygon with area ``buffer``.
|
|
1480
|
-
:param Optional[
|
|
1481
|
-
:param Optional[int] core_cnt: Number of CPU cores to use for parallel processing. If set to -1, the function will automatically determine the available core count.
|
|
1501
|
+
:param Optional[float] px_per_mm: If ``buffer`` if not None, then provide the pixels to millimeter conversion factor.
|
|
1502
|
+
:param Optional[int] core_cnt: Number of CPU cores to use for parallel processing. If set to -1, the function will automatically determine the available core count. Ignored if pool is provided.
|
|
1503
|
+
:param Optional[multiprocessing.Pool] pool: Optional multiprocessing pool to reuse. If None, creates a new pool. Default None.
|
|
1482
1504
|
:return: A list of LineString objects representing the body-parts trajectories.
|
|
1483
1505
|
:rtype: List[LineString]
|
|
1484
1506
|
|
|
@@ -1514,15 +1536,13 @@ class GeometryMixin(object):
|
|
|
1514
1536
|
min_value=1,
|
|
1515
1537
|
)
|
|
1516
1538
|
results = []
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
)
|
|
1523
|
-
|
|
1524
|
-
results.append(result)
|
|
1525
|
-
terminate_cpu_pool(pool=pool, force=False)
|
|
1539
|
+
pool_terminate_flag = False if pool is not None else True
|
|
1540
|
+
if pool is not None: check_valid_cpu_pool(value=pool, source=f'{GeometryMixin().multiframe_bodyparts_to_line.__name__} pool', raise_error=True, accepted_cores=core_cnt)
|
|
1541
|
+
else: pool = get_cpu_pool(core_cnt=core_cnt, source=GeometryMixin().multiframe_bodyparts_to_line.__name__)
|
|
1542
|
+
constants = functools.partial(GeometryMixin.bodyparts_to_line, buffer=buffer, px_per_mm=px_per_mm)
|
|
1543
|
+
for cnt, result in enumerate(pool.imap(constants, data, chunksize=1)):
|
|
1544
|
+
results.append(result)
|
|
1545
|
+
if pool_terminate_flag: terminate_cpu_pool(pool=pool, source=GeometryMixin().multiframe_bodyparts_to_line.__name__)
|
|
1526
1546
|
return results
|
|
1527
1547
|
|
|
1528
1548
|
def multiframe_compute_pct_shape_overlap(self,
|
|
@@ -1547,13 +1567,14 @@ class GeometryMixin(object):
|
|
|
1547
1567
|
|
|
1548
1568
|
:param List[Polygon] shape_1: List of Polygons.
|
|
1549
1569
|
:param List[Polygon] shape_2: List of Polygons with the same length as shape_1.
|
|
1550
|
-
:param int core_cnt: Number of CPU cores to use for parallel processing. Default is -1, which uses all available cores.
|
|
1570
|
+
:param int core_cnt: Number of CPU cores to use for parallel processing. Default is -1, which uses all available cores. Ignored if pool is provided.
|
|
1551
1571
|
:param Optional[str] video_name: If not None, then the name of the video being processed for interpretable progress msgs.
|
|
1552
|
-
:param Optional[bool]
|
|
1572
|
+
:param Optional[bool] verbose: If True, then prints interpretable progress msgs.
|
|
1553
1573
|
:param Optional[Tuple[str]] animal_names: If not None, then a two-tuple of animal names (or alternative shape names) interpretable progress msgs.
|
|
1554
1574
|
:param Optional[Literal["difference", "shape_1", "shape_2"]] denominator: Denominator for percentage calculation. "difference" uses union minus intersection, "shape_1" uses shape_1 area, "shape_2" uses shape_2 area. Default: "difference".
|
|
1575
|
+
:param Optional[multiprocessing.Pool] pool: Optional multiprocessing pool to reuse. If None, creates a new pool. Default None.
|
|
1555
1576
|
:return: List of length ``shape_1`` with percentage overlap between corresponding Polygons.
|
|
1556
|
-
:rtype:
|
|
1577
|
+
:rtype: np.ndarray
|
|
1557
1578
|
|
|
1558
1579
|
:example:
|
|
1559
1580
|
>>> df = read_df(file_path=r"C:/troubleshooting/two_black_animals_14bp/project_folder/csv/outlier_corrected_movement_location/Together_2.csv", file_type='csv').astype(int)
|
|
@@ -1578,7 +1599,7 @@ class GeometryMixin(object):
|
|
|
1578
1599
|
data = np.array_split(data, core_cnt)
|
|
1579
1600
|
pool_terminate_flag = False if pool is not None else True
|
|
1580
1601
|
if pool is not None:
|
|
1581
|
-
check_valid_cpu_pool(value=pool, source=f'{
|
|
1602
|
+
check_valid_cpu_pool(value=pool, source=f'{GeometryMixin().multiframe_compute_pct_shape_overlap.__name__} pool', raise_error=True, accepted_cores=core_cnt)
|
|
1582
1603
|
else:
|
|
1583
1604
|
pool = multiprocessing.Pool(core_cnt, maxtasksperchild=Defaults.MAXIMUM_MAX_TASK_PER_CHILD.value)
|
|
1584
1605
|
constants = functools.partial(GeometryMixin.compute_pct_shape_overlap, denominator=denominator)
|
|
@@ -1594,7 +1615,7 @@ class GeometryMixin(object):
|
|
|
1594
1615
|
results.append(result)
|
|
1595
1616
|
timer.stop_timer()
|
|
1596
1617
|
stdout_success(msg="Compute overlap complete.", elapsed_time=timer.elapsed_time_str)
|
|
1597
|
-
if pool_terminate_flag: terminate_cpu_pool(pool=pool)
|
|
1618
|
+
if pool_terminate_flag: terminate_cpu_pool(pool=pool, source=GeometryMixin().multiframe_compute_pct_shape_overlap.__name__)
|
|
1598
1619
|
return np.hstack(results).astype(np.float32)
|
|
1599
1620
|
|
|
1600
1621
|
def multiframe_compute_shape_overlap(self,
|
|
@@ -1616,10 +1637,13 @@ class GeometryMixin(object):
|
|
|
1616
1637
|
.. seealso:
|
|
1617
1638
|
:func:`simba.mixins.geometry_mixin.GeometryMixin.compute_shape_overlap`, :func:`simba.mixins.geometry_mixin.GeometryMixin.compute_pct_shape_overlap`, :func:`simba.mixins.geometry_mixin.GeometryMixin.multiframe_compute_pct_shape_overlap`
|
|
1618
1639
|
|
|
1619
|
-
:param List[Polygon] shape_1: List of Polygons.
|
|
1620
|
-
:param List[Polygon] shape_2: List of Polygons with the same length as shape_1.
|
|
1621
|
-
:param int core_cnt: Number of CPU cores to use for parallel processing. Default is -1, which uses all available cores.
|
|
1622
|
-
:
|
|
1640
|
+
:param List[Union[Polygon, LineString, None]] shape_1: List of Polygons, LineStrings, or None.
|
|
1641
|
+
:param List[Union[Polygon, LineString, None]] shape_2: List of Polygons, LineStrings, or None with the same length as shape_1.
|
|
1642
|
+
:param int core_cnt: Number of CPU cores to use for parallel processing. Default is -1, which uses all available cores. Ignored if pool is provided.
|
|
1643
|
+
:param Optional[bool] verbose: If True, prints progress messages. Default False.
|
|
1644
|
+
:param Optional[Tuple[str]] names: Optional tuple of names for progress messages.
|
|
1645
|
+
:param Optional[multiprocessing.Pool] pool: Optional multiprocessing pool to reuse. If None, creates a new pool. Default None.
|
|
1646
|
+
:return List[int]: List of overlap between corresponding Polygons. If overlap 1, else 0.
|
|
1623
1647
|
|
|
1624
1648
|
:example:
|
|
1625
1649
|
>>> df = read_df(file_path=r"C:/troubleshooting/two_black_animals_14bp/project_folder/csv/outlier_corrected_movement_location/Together_2.csv", file_type='csv').astype(int)
|
|
@@ -1645,7 +1669,7 @@ class GeometryMixin(object):
|
|
|
1645
1669
|
data = np.array_split(data, core_cnt)
|
|
1646
1670
|
pool_terminate_flag = False if pool is not None else True
|
|
1647
1671
|
if pool is not None:
|
|
1648
|
-
check_valid_cpu_pool(value=pool, source=f'{
|
|
1672
|
+
check_valid_cpu_pool(value=pool, source=f'{GeometryMixin().multiframe_compute_shape_overlap.__name__} pool', raise_error=True, accepted_cores=core_cnt)
|
|
1649
1673
|
else:
|
|
1650
1674
|
pool = multiprocessing.Pool(core_cnt, maxtasksperchild=Defaults.MAXIMUM_MAX_TASK_PER_CHILD.value)
|
|
1651
1675
|
for cnt, result in enumerate(pool.imap(GeometryMixin.compute_shape_overlap, data, chunksize=1)):
|
|
@@ -1654,7 +1678,7 @@ class GeometryMixin(object):
|
|
|
1654
1678
|
else:
|
|
1655
1679
|
print(f"Computing overlap {cnt + 1}/{len(data)} (Shape 1: {names[0]}, Shape 2: {names[1]}, Video: {names[2]}...)")
|
|
1656
1680
|
results.extend((result))
|
|
1657
|
-
if pool_terminate_flag: terminate_cpu_pool(pool=pool)
|
|
1681
|
+
if pool_terminate_flag: terminate_cpu_pool(pool=pool, source=GeometryMixin().multiframe_compute_shape_overlap.__name__)
|
|
1658
1682
|
return results
|
|
1659
1683
|
|
|
1660
1684
|
def multiframe_shape_distance(self,
|
|
@@ -1682,7 +1706,7 @@ class GeometryMixin(object):
|
|
|
1682
1706
|
|
|
1683
1707
|
:param List[Union[LineString, Polygon]] shapes_a: List of LineString or Polygon geometries.
|
|
1684
1708
|
:param List[Union[LineString, Polygon]] shapes_b: List of LineString or Polygon geometries with the same length as shapes_a.
|
|
1685
|
-
:param float pixels_per_mm: Conversion factor from pixels to millimeters. Default 1.
|
|
1709
|
+
:param Optional[float] pixels_per_mm: Conversion factor from pixels to millimeters. Default 1.
|
|
1686
1710
|
:param Literal['mm', 'cm', 'dm', 'm'] unit: Unit of measurement for the result. Options: 'mm', 'cm', 'dm', 'm'. Default: 'mm'.
|
|
1687
1711
|
:param bool verbose: If True, prints progress information during computation. Default False.
|
|
1688
1712
|
:param int core_cnt: Number of CPU cores to use for parallel processing. Default is -1, which uses all available cores. Ignored if pool is provided.
|
|
@@ -1719,7 +1743,7 @@ class GeometryMixin(object):
|
|
|
1719
1743
|
data = np.array_split(data, core_cnt)
|
|
1720
1744
|
pool_terminate_flag = False if pool is not None else True
|
|
1721
1745
|
if pool is not None:
|
|
1722
|
-
check_valid_cpu_pool(value=pool, source=f'{
|
|
1746
|
+
check_valid_cpu_pool(value=pool, source=f'{GeometryMixin().multiframe_shape_distance.__name__} pool', raise_error=True, accepted_cores=core_cnt)
|
|
1723
1747
|
else:
|
|
1724
1748
|
pool = multiprocessing.Pool(core_cnt, maxtasksperchild=maxchildpertask)
|
|
1725
1749
|
results = []
|
|
@@ -1738,7 +1762,7 @@ class GeometryMixin(object):
|
|
|
1738
1762
|
if verbose and shape_names is None:
|
|
1739
1763
|
print(f'Shape distances computed for {len(shapes_a)} comparisons (elapsed time: {timer.elapsed_time_str}s)')
|
|
1740
1764
|
if pool_terminate_flag:
|
|
1741
|
-
terminate_cpu_pool(pool=pool)
|
|
1765
|
+
terminate_cpu_pool(pool=pool, source=GeometryMixin().multiframe_shape_distance.__name__)
|
|
1742
1766
|
return results
|
|
1743
1767
|
|
|
1744
1768
|
def multiframe_minimum_rotated_rectangle(self,
|
|
@@ -1746,7 +1770,8 @@ class GeometryMixin(object):
|
|
|
1746
1770
|
video_name: Optional[str] = None,
|
|
1747
1771
|
verbose: Optional[bool] = False,
|
|
1748
1772
|
animal_name: Optional[bool] = None,
|
|
1749
|
-
core_cnt: int = -1
|
|
1773
|
+
core_cnt: int = -1,
|
|
1774
|
+
pool: Optional[multiprocessing.Pool] = None) -> List[Polygon]:
|
|
1750
1775
|
|
|
1751
1776
|
"""
|
|
1752
1777
|
Compute the minimum rotated rectangle for each Polygon in a list using multiprocessing.
|
|
@@ -1758,8 +1783,9 @@ class GeometryMixin(object):
|
|
|
1758
1783
|
:param Optional[str] video_name: Optional video name to print (if verbose is True).
|
|
1759
1784
|
:param Optional[str] animal_name: Optional animal name to print (if verbose is True).
|
|
1760
1785
|
:param Optional[bool] verbose: If True, prints progress.
|
|
1761
|
-
:param core_cnt: Number of CPU cores to use for parallel processing. Default is -1, which uses all available cores.
|
|
1762
|
-
:
|
|
1786
|
+
:param int core_cnt: Number of CPU cores to use for parallel processing. Default is -1, which uses all available cores. Ignored if pool is provided.
|
|
1787
|
+
:param Optional[multiprocessing.Pool] pool: Optional multiprocessing pool to reuse. If None, creates a new pool. Default None.
|
|
1788
|
+
:returns: A list of rotated rectangles of size len(shapes).
|
|
1763
1789
|
:rtype: List[Polygon]
|
|
1764
1790
|
|
|
1765
1791
|
:example:
|
|
@@ -1773,29 +1799,31 @@ class GeometryMixin(object):
|
|
|
1773
1799
|
check_int(name="CORE COUNT", value=core_cnt, min_value=-1, max_value=find_core_cnt()[0], raise_error=True)
|
|
1774
1800
|
if core_cnt == -1: core_cnt = find_core_cnt()[0]
|
|
1775
1801
|
results, timer = [], SimbaTimer(start=True)
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1802
|
+
pool_terminate_flag = False if pool is not None else True
|
|
1803
|
+
if pool is not None: check_valid_cpu_pool(value=pool, source=f'{GeometryMixin().multiframe_minimum_rotated_rectangle.__name__} pool', raise_error=True, accepted_cores=core_cnt)
|
|
1804
|
+
else: pool = get_cpu_pool(core_cnt=core_cnt, source=GeometryMixin().multiframe_minimum_rotated_rectangle.__name__)
|
|
1805
|
+
for cnt, result in enumerate(pool.imap(GeometryMixin.minimum_rotated_rectangle, shapes, chunksize=1)):
|
|
1806
|
+
if verbose:
|
|
1807
|
+
if not video_name and not animal_name:
|
|
1808
|
+
print(f"Rotating polygon {cnt + 1}/{len(shapes)}...")
|
|
1809
|
+
elif not video_name and animal_name:
|
|
1810
|
+
print(
|
|
1811
|
+
f"Rotating polygon {cnt + 1}/{len(shapes)} (Animal: {animal_name})..."
|
|
1812
|
+
)
|
|
1813
|
+
elif video_name and not animal_name:
|
|
1814
|
+
print(
|
|
1815
|
+
f"Rotating polygon {cnt + 1}/{len(shapes)} (Video: {video_name})..."
|
|
1816
|
+
)
|
|
1817
|
+
else:
|
|
1818
|
+
print(
|
|
1819
|
+
f"Rotating polygon {cnt + 1}/{len(shapes)} (Video: {video_name}, Animal: {animal_name})..."
|
|
1820
|
+
)
|
|
1821
|
+
results.append(result)
|
|
1794
1822
|
|
|
1795
1823
|
timer.stop_timer()
|
|
1824
|
+
if pool_terminate_flag: terminate_cpu_pool(pool=pool, source=GeometryMixin().multiframe_minimum_rotated_rectangle.__name__)
|
|
1796
1825
|
if verbose:
|
|
1797
1826
|
stdout_success(msg="Rotated rectangles complete.", elapsed_time=timer.elapsed_time_str)
|
|
1798
|
-
terminate_cpu_pool(pool=pool, force=False)
|
|
1799
1827
|
return results
|
|
1800
1828
|
|
|
1801
1829
|
@staticmethod
|
|
@@ -1965,13 +1993,22 @@ class GeometryMixin(object):
|
|
|
1965
1993
|
shapes: List[Union[LineString, MultiLineString]],
|
|
1966
1994
|
pixels_per_mm: float,
|
|
1967
1995
|
core_cnt: int = -1,
|
|
1968
|
-
unit: Literal["mm", "cm", "dm", "m"] = "mm"
|
|
1996
|
+
unit: Literal["mm", "cm", "dm", "m"] = "mm",
|
|
1997
|
+
pool: Optional[multiprocessing.Pool] = None) -> List[float]:
|
|
1969
1998
|
"""
|
|
1970
1999
|
Calculate the length of LineStrings using multiprocessing.
|
|
1971
2000
|
|
|
1972
2001
|
.. seealso::
|
|
1973
2002
|
For single core process, see :func:`simba.mixins.geometry_mixin.GeometryMixin.length`
|
|
1974
2003
|
|
|
2004
|
+
:param List[Union[LineString, MultiLineString]] shapes: List of LineString or MultiLineString geometries.
|
|
2005
|
+
:param float pixels_per_mm: Pixels per millimeter conversion factor.
|
|
2006
|
+
:param int core_cnt: Number of CPU cores to use for parallel processing. Default is -1, which uses all available cores. Ignored if pool is provided.
|
|
2007
|
+
:param Literal["mm", "cm", "dm", "m"] unit: Unit of measurement for the result. Options: 'mm', 'cm', 'dm', 'm'. Default: 'mm'.
|
|
2008
|
+
:param Optional[multiprocessing.Pool] pool: Optional multiprocessing pool to reuse. If None, creates a new pool. Default None.
|
|
2009
|
+
:return: List of lengths in the specified unit.
|
|
2010
|
+
:rtype: List[float]
|
|
2011
|
+
|
|
1975
2012
|
:example:
|
|
1976
2013
|
>>> data = np.random.randint(0, 100, (5000, 2))
|
|
1977
2014
|
>>> data = data.reshape(2500,-1, data.shape[1])
|
|
@@ -1991,19 +2028,19 @@ class GeometryMixin(object):
|
|
|
1991
2028
|
check_float(name="PIXELS PER MM", value=pixels_per_mm, min_value=0.0)
|
|
1992
2029
|
check_if_valid_input(name="UNIT", input=unit, options=["mm", "cm", "dm", "m"])
|
|
1993
2030
|
results = []
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
)
|
|
2000
|
-
|
|
2001
|
-
results.append(result)
|
|
2002
|
-
terminate_cpu_pool(pool=pool, force=False)
|
|
2031
|
+
pool_terminate_flag = False if pool is not None else True
|
|
2032
|
+
if pool is not None: check_valid_cpu_pool(value=pool, source=f'{GeometryMixin().multiframe_length.__name__} pool', raise_error=True, accepted_cores=core_cnt)
|
|
2033
|
+
else: pool = get_cpu_pool(core_cnt=core_cnt, source=GeometryMixin().multiframe_length.__name__)
|
|
2034
|
+
constants = functools.partial(GeometryMixin.length, pixels_per_mm=pixels_per_mm, unit=unit)
|
|
2035
|
+
for cnt, result in enumerate(pool.imap(constants, shapes, chunksize=1)):
|
|
2036
|
+
results.append(result)
|
|
2037
|
+
if pool_terminate_flag: terminate_cpu_pool(pool=pool, source=GeometryMixin().multiframe_length.__name__)
|
|
2003
2038
|
return results
|
|
2004
2039
|
|
|
2005
|
-
def multiframe_union(self,
|
|
2006
|
-
|
|
2040
|
+
def multiframe_union(self,
|
|
2041
|
+
shapes: Iterable[Union[LineString, MultiLineString, Polygon]],
|
|
2042
|
+
core_cnt: int = -1,
|
|
2043
|
+
pool: Optional[multiprocessing.Pool] = None) -> Iterable[Union[LineString, MultiLineString, Polygon]]:
|
|
2007
2044
|
"""
|
|
2008
2045
|
Join multiple shapes frame-wise into a single shape/
|
|
2009
2046
|
|
|
@@ -2017,8 +2054,9 @@ class GeometryMixin(object):
|
|
|
2017
2054
|
.. seealso::
|
|
2018
2055
|
For single core method, see :func:`simba.mixins.geometry_mixin.GeometryMixin.union`
|
|
2019
2056
|
|
|
2020
|
-
:param shapes: Iterable collection of shapes (`LineString`, `MultiLineString`, or `Polygon`) to be merged. E.g, of size NxM where N is the number of frames and M is the number of shapes in each frame.
|
|
2021
|
-
:param core_cnt: The number of CPU cores to use for parallel processing; defaults to -1, which uses all available cores.
|
|
2057
|
+
:param Iterable[Union[LineString, MultiLineString, Polygon]] shapes: Iterable collection of shapes (`LineString`, `MultiLineString`, or `Polygon`) to be merged. E.g, of size NxM where N is the number of frames and M is the number of shapes in each frame.
|
|
2058
|
+
:param int core_cnt: The number of CPU cores to use for parallel processing; defaults to -1, which uses all available cores. Ignored if pool is provided.
|
|
2059
|
+
:param Optional[multiprocessing.Pool] pool: Optional multiprocessing pool to reuse. If None, creates a new pool. Default None.
|
|
2022
2060
|
:return: An iterable of merged shapes, where each frame is combined into a single shape.
|
|
2023
2061
|
:rtype: List[Union[LineString, MultiLineString, Polygon]]
|
|
2024
2062
|
|
|
@@ -2035,16 +2073,19 @@ class GeometryMixin(object):
|
|
|
2035
2073
|
if core_cnt == -1:
|
|
2036
2074
|
core_cnt = find_core_cnt()[0]
|
|
2037
2075
|
results = []
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2076
|
+
pool_terminate_flag = False if pool is not None else True
|
|
2077
|
+
if pool is not None: check_valid_cpu_pool(value=pool, source=f'{GeometryMixin().multiframe_union.__name__} pool', raise_error=True, accepted_cores=core_cnt)
|
|
2078
|
+
else: pool = get_cpu_pool(core_cnt=core_cnt, source=GeometryMixin().multiframe_union.__name__)
|
|
2079
|
+
for cnt, result in enumerate(pool.imap(GeometryMixin().union, shapes, chunksize=1)):
|
|
2080
|
+
results.append(result)
|
|
2081
|
+
if pool_terminate_flag: terminate_cpu_pool(pool=pool, source=GeometryMixin().multiframe_union.__name__)
|
|
2042
2082
|
return results
|
|
2043
2083
|
|
|
2044
2084
|
def multiframe_symmetric_difference(self, shapes: Iterable[Union[LineString, MultiLineString, Polygon]],
|
|
2045
|
-
core_cnt: int = -1
|
|
2085
|
+
core_cnt: int = -1,
|
|
2086
|
+
pool: Optional[multiprocessing.Pool] = None):
|
|
2046
2087
|
"""
|
|
2047
|
-
Compute the symmetric differences between corresponding LineString or MultiLineString geometries
|
|
2088
|
+
Compute the symmetric differences between corresponding LineString or MultiLineString geometries using multiprocessing.
|
|
2048
2089
|
|
|
2049
2090
|
Computes a new geometry consisting of the parts that are exclusive to each input geometry.
|
|
2050
2091
|
|
|
@@ -2053,6 +2094,12 @@ class GeometryMixin(object):
|
|
|
2053
2094
|
.. seealso::
|
|
2054
2095
|
For single core method, see :func:`simba.mixins.geometry_mixin.GeometryMixin.symmetric_difference`
|
|
2055
2096
|
|
|
2097
|
+
:param Iterable[Union[LineString, MultiLineString, Polygon]] shapes: Iterable collection of shapes where each element is a list containing two geometries.
|
|
2098
|
+
:param int core_cnt: Number of CPU cores to use for parallel processing. Default is -1, which uses all available cores. Ignored if pool is provided.
|
|
2099
|
+
:param Optional[multiprocessing.Pool] pool: Optional multiprocessing pool to reuse. If None, creates a new pool. Default None.
|
|
2100
|
+
:return: List of symmetric difference geometries.
|
|
2101
|
+
:rtype: List[Union[LineString, MultiLineString, Polygon]]
|
|
2102
|
+
|
|
2056
2103
|
:example:
|
|
2057
2104
|
>>> data_1 = np.random.randint(0, 100, (5000, 2)).reshape(1000,-1, 2)
|
|
2058
2105
|
>>> data_2 = np.random.randint(0, 100, (5000, 2)).reshape(1000,-1, 2)
|
|
@@ -2071,23 +2118,31 @@ class GeometryMixin(object):
|
|
|
2071
2118
|
if core_cnt == -1:
|
|
2072
2119
|
core_cnt = find_core_cnt()[0]
|
|
2073
2120
|
results = []
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
results.append(result)
|
|
2081
|
-
terminate_cpu_pool(pool=pool, force=False)
|
|
2121
|
+
pool_terminate_flag = False if pool is not None else True
|
|
2122
|
+
if pool is not None: check_valid_cpu_pool(value=pool, source=f'{GeometryMixin().multiframe_symmetric_difference.__name__} pool', raise_error=True, accepted_cores=core_cnt)
|
|
2123
|
+
else: pool = get_cpu_pool(core_cnt=core_cnt, source=GeometryMixin().multiframe_symmetric_difference.__name__)
|
|
2124
|
+
for cnt, result in enumerate(pool.imap(GeometryMixin().symmetric_difference, shapes, chunksize=1)):
|
|
2125
|
+
results.append(result)
|
|
2126
|
+
if pool_terminate_flag: terminate_cpu_pool(pool=pool, source=GeometryMixin().multiframe_symmetric_difference.__name__)
|
|
2082
2127
|
return results
|
|
2083
2128
|
|
|
2084
|
-
def multiframe_delaunay_triangulate_keypoints(self,
|
|
2129
|
+
def multiframe_delaunay_triangulate_keypoints(self,
|
|
2130
|
+
data: np.ndarray,
|
|
2131
|
+
core_cnt: int = -1,
|
|
2132
|
+
pool: Optional[multiprocessing.Pool] = None) -> List[List[Polygon]]:
|
|
2085
2133
|
"""
|
|
2086
|
-
Triangulates a set of 2D keypoints. E.g., can be used to polygonize animal hull, or triangulate a gridpoint
|
|
2134
|
+
Triangulates a set of 2D keypoints. E.g., can be used to polygonize animal hull, or triangulate a gridpoint arena.
|
|
2087
2135
|
|
|
2088
2136
|
.. seealso::
|
|
2089
2137
|
For single core process, see :func:`simba.mixins.geometry_mixin.GeometryMixin.delaunay_triangulate_keypoints`
|
|
2090
2138
|
|
|
2139
|
+
:param np.ndarray data: 3D array of keypoints where shape is (frames, keypoints, coordinates).
|
|
2140
|
+
:param int core_cnt: Number of CPU cores to use for parallel processing. Default is -1, which uses all available cores. Ignored if pool is provided.
|
|
2141
|
+
:param Optional[multiprocessing.Pool] pool: Optional multiprocessing pool to reuse. If None, creates a new pool. Default None.
|
|
2142
|
+
:return: List of lists of Polygon objects representing triangles.
|
|
2143
|
+
:rtype: List[List[Polygon]]
|
|
2144
|
+
|
|
2145
|
+
:example:
|
|
2091
2146
|
>>> data_path = '/Users/simon/Desktop/envs/troubleshooting/Rat_NOR/project_folder/csv/machine_results/08102021_DOT_Rat7_8(2).csv'
|
|
2092
2147
|
>>> data = pd.read_csv(data_path, index_col=0).head(1000).iloc[:, 0:21]
|
|
2093
2148
|
>>> data = data[data.columns.drop(list(data.filter(regex='_p')))]
|
|
@@ -2115,17 +2170,12 @@ class GeometryMixin(object):
|
|
|
2115
2170
|
source=GeometryMixin.multiframe_delaunay_triangulate_keypoints.__name__,
|
|
2116
2171
|
)
|
|
2117
2172
|
results = []
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
)
|
|
2125
|
-
):
|
|
2126
|
-
results.append(result)
|
|
2127
|
-
|
|
2128
|
-
terminate_cpu_pool(pool=pool, force=False)
|
|
2173
|
+
pool_terminate_flag = False if pool is not None else True
|
|
2174
|
+
if pool is not None: check_valid_cpu_pool(value=pool, source=f'{GeometryMixin().multiframe_delaunay_triangulate_keypoints.__name__} pool', raise_error=True, accepted_cores=core_cnt)
|
|
2175
|
+
else: pool = get_cpu_pool(core_cnt=core_cnt, source=GeometryMixin().multiframe_delaunay_triangulate_keypoints.__name__)
|
|
2176
|
+
for cnt, result in enumerate(pool.imap(GeometryMixin().delaunay_triangulate_keypoints, data, chunksize=1)):
|
|
2177
|
+
results.append(result)
|
|
2178
|
+
if pool_terminate_flag: terminate_cpu_pool(pool=pool, source=GeometryMixin().multiframe_delaunay_triangulate_keypoints.__name__)
|
|
2129
2179
|
return results
|
|
2130
2180
|
|
|
2131
2181
|
def multiframe_difference(
|
|
@@ -2135,7 +2185,7 @@ class GeometryMixin(object):
|
|
|
2135
2185
|
verbose: Optional[bool] = False,
|
|
2136
2186
|
animal_names: Optional[str] = None,
|
|
2137
2187
|
video_name: Optional[str] = None,
|
|
2138
|
-
|
|
2188
|
+
pool: Optional[multiprocessing.Pool] = None) -> List[Union[Polygon, MultiPolygon]]:
|
|
2139
2189
|
"""
|
|
2140
2190
|
Compute the multi-frame difference for a collection of shapes using parallel processing.
|
|
2141
2191
|
|
|
@@ -2143,10 +2193,11 @@ class GeometryMixin(object):
|
|
|
2143
2193
|
For single core method, see :func:`simba.mixins.geometry_mixin.GeometryMixin.difference`
|
|
2144
2194
|
|
|
2145
2195
|
:param Iterable[Union[LineString, Polygon, MultiPolygon]] shapes: A collection of shapes, where each shape is a list containing two geometries.
|
|
2146
|
-
:param int core_cnt: The number of CPU cores to use for parallel processing. Default is -1, which automatically detects the available cores.
|
|
2196
|
+
:param int core_cnt: The number of CPU cores to use for parallel processing. Default is -1, which automatically detects the available cores. Ignored if pool is provided.
|
|
2147
2197
|
:param Optional[bool] verbose: If True, print progress messages during computation. Default is False.
|
|
2148
2198
|
:param Optional[str] animal_names: Optional string representing the names of animals for informative messages.
|
|
2149
|
-
:param Optional[str]video_name: Optional string representing the name of the video for informative messages.
|
|
2199
|
+
:param Optional[str] video_name: Optional string representing the name of the video for informative messages.
|
|
2200
|
+
:param Optional[multiprocessing.Pool] pool: Optional multiprocessing pool to reuse. If None, creates a new pool. Default None.
|
|
2150
2201
|
:return: A list of geometries representing the multi-frame difference.
|
|
2151
2202
|
:rtype: List[Union[Polygon, MultiPolygon]]
|
|
2152
2203
|
"""
|
|
@@ -2183,37 +2234,32 @@ class GeometryMixin(object):
|
|
|
2183
2234
|
if core_cnt == -1:
|
|
2184
2235
|
core_cnt = find_core_cnt()[0]
|
|
2185
2236
|
results, timer = [], SimbaTimer(start=True)
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
)
|
|
2209
|
-
results.append(result)
|
|
2237
|
+
pool_terminate_flag = False if pool is not None else True
|
|
2238
|
+
if pool is not None: check_valid_cpu_pool(value=pool, source=f'{GeometryMixin().multiframe_difference.__name__} pool', raise_error=True, accepted_cores=core_cnt)
|
|
2239
|
+
else: pool = get_cpu_pool(core_cnt=core_cnt, source=GeometryMixin().multiframe_difference.__name__)
|
|
2240
|
+
for cnt, result in enumerate(pool.imap(GeometryMixin().difference, shapes, chunksize=1)):
|
|
2241
|
+
if verbose:
|
|
2242
|
+
if not video_name and not animal_names:
|
|
2243
|
+
print(
|
|
2244
|
+
f"Computing geometry difference {cnt + 1}/{len(shapes)}..."
|
|
2245
|
+
)
|
|
2246
|
+
elif not video_name and animal_names:
|
|
2247
|
+
print(
|
|
2248
|
+
f"Computing geometry difference {cnt + 1}/{len(shapes)} (Animals: {animal_names})..."
|
|
2249
|
+
)
|
|
2250
|
+
elif video_name and not animal_names:
|
|
2251
|
+
print(
|
|
2252
|
+
f"Computing geometry difference {cnt + 1}/{len(shapes)} (Video: {video_name})..."
|
|
2253
|
+
)
|
|
2254
|
+
else:
|
|
2255
|
+
print(
|
|
2256
|
+
f"Computing geometry difference {cnt + 1}/{len(shapes)} (Video: {video_name}, Animals: {animal_names})..."
|
|
2257
|
+
)
|
|
2258
|
+
results.append(result)
|
|
2210
2259
|
|
|
2211
2260
|
timer.stop_timer()
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
elapsed_time=timer.elapsed_time_str,
|
|
2215
|
-
)
|
|
2216
|
-
terminate_cpu_pool(pool=pool, force=False)
|
|
2261
|
+
if pool_terminate_flag: terminate_cpu_pool(pool=pool, source=GeometryMixin().multiframe_difference.__name__)
|
|
2262
|
+
stdout_success(msg="Multi-frame difference compute complete", elapsed_time=timer.elapsed_time_str)
|
|
2217
2263
|
return results
|
|
2218
2264
|
|
|
2219
2265
|
def multiframe_area(self,
|
|
@@ -2222,7 +2268,8 @@ class GeometryMixin(object):
|
|
|
2222
2268
|
core_cnt: Optional[int] = -1,
|
|
2223
2269
|
verbose: Optional[bool] = False,
|
|
2224
2270
|
video_name: Optional[bool] = False,
|
|
2225
|
-
animal_names: Optional[bool] = False
|
|
2271
|
+
animal_names: Optional[bool] = False,
|
|
2272
|
+
pool: Optional[multiprocessing.Pool] = None) -> List[float]:
|
|
2226
2273
|
|
|
2227
2274
|
"""
|
|
2228
2275
|
Calculate the area of geometries in square millimeters using multiprocessing.
|
|
@@ -2232,10 +2279,11 @@ class GeometryMixin(object):
|
|
|
2232
2279
|
|
|
2233
2280
|
:param List[Union[MultiPolygon, Polygon]] shapes: List of polygons of Multipolygons.
|
|
2234
2281
|
:param float pixels_per_mm: Pixel per millimeter conversion factor. Default: 1.0.
|
|
2235
|
-
:param Optional[int] core_cnt: The number of CPU cores to use for parallel processing. Default is -1, which automatically detects the available cores.
|
|
2282
|
+
:param Optional[int] core_cnt: The number of CPU cores to use for parallel processing. Default is -1, which automatically detects the available cores. Ignored if pool is provided.
|
|
2236
2283
|
:param Optional[bool] verbose: If True, prints progress.
|
|
2237
|
-
:param Optional[
|
|
2238
|
-
:param Optional[
|
|
2284
|
+
:param Optional[str] video_name: If string, prints video name string during progress if verbose.
|
|
2285
|
+
:param Optional[str] animal_names: If string, prints animal name during progress if verbose.
|
|
2286
|
+
:param Optional[multiprocessing.Pool] pool: Optional multiprocessing pool to reuse. If None, creates a new pool. Default None.
|
|
2239
2287
|
:return: List of length ``len(shapes)`` with area values.
|
|
2240
2288
|
:rtype: List[float]
|
|
2241
2289
|
"""
|
|
@@ -2250,24 +2298,26 @@ class GeometryMixin(object):
|
|
|
2250
2298
|
if core_cnt == -1:
|
|
2251
2299
|
core_cnt = find_core_cnt()[0]
|
|
2252
2300
|
results, timer = [], SimbaTimer(start=True)
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2301
|
+
pool_terminate_flag = False if pool is not None else True
|
|
2302
|
+
if pool is not None: check_valid_cpu_pool(value=pool, source=f'{GeometryMixin().multiframe_area.__name__} pool', raise_error=True, accepted_cores=core_cnt)
|
|
2303
|
+
else: pool = get_cpu_pool(core_cnt=core_cnt, source=GeometryMixin().multiframe_area.__name__)
|
|
2304
|
+
constants = functools.partial(GeometryMixin.area, pixels_per_mm=pixels_per_mm)
|
|
2305
|
+
for cnt, result in enumerate(pool.imap(constants, shapes, chunksize=1)):
|
|
2306
|
+
if verbose:
|
|
2307
|
+
if not video_name and not animal_names:
|
|
2308
|
+
print(f"Computing area {cnt + 1}/{len(shapes)}...")
|
|
2309
|
+
elif not video_name and animal_names:
|
|
2310
|
+
print(f"Computing % area {cnt + 1}/{len(shapes)} (Animals: {animal_names})...")
|
|
2311
|
+
elif video_name and not animal_names:
|
|
2312
|
+
print(f"Computing % area {cnt + 1}/{len(shapes)} (Video: {video_name})...")
|
|
2313
|
+
else:
|
|
2314
|
+
print(
|
|
2315
|
+
f"Computing % area {cnt + 1}/{len(shapes)} (Video: {video_name}, Animals: {animal_names})...")
|
|
2316
|
+
results.append(result)
|
|
2267
2317
|
|
|
2268
2318
|
timer.stop_timer()
|
|
2319
|
+
if pool_terminate_flag: terminate_cpu_pool(pool=pool, source=GeometryMixin().multiframe_area.__name__)
|
|
2269
2320
|
stdout_success(msg="Multi-frame area compute complete", elapsed_time=timer.elapsed_time_str)
|
|
2270
|
-
terminate_cpu_pool(pool=pool, force=False)
|
|
2271
2321
|
return results
|
|
2272
2322
|
|
|
2273
2323
|
def multiframe_bodyparts_to_multistring_skeleton(
|
|
@@ -2278,7 +2328,8 @@ class GeometryMixin(object):
|
|
|
2278
2328
|
verbose: Optional[bool] = False,
|
|
2279
2329
|
video_name: Optional[bool] = False,
|
|
2280
2330
|
animal_names: Optional[bool] = False,
|
|
2281
|
-
|
|
2331
|
+
pool: Optional[multiprocessing.Pool] = None) -> List[Union[LineString, MultiLineString]]:
|
|
2332
|
+
|
|
2282
2333
|
"""
|
|
2283
2334
|
Convert body parts to LineString skeleton representations in a videos using multiprocessing.
|
|
2284
2335
|
|
|
@@ -2287,10 +2338,11 @@ class GeometryMixin(object):
|
|
|
2287
2338
|
|
|
2288
2339
|
:param pd.DataFrame data_df: Pose-estimation data.
|
|
2289
2340
|
:param Iterable[str] skeleton: Iterable of body part pairs defining the skeleton structure. Eg., [['Center', 'Lat_left'], ['Center', 'Lat_right'], ['Center', 'Nose'], ['Center', 'Tail_base']]
|
|
2290
|
-
:param Optional[int] core_cnt: Number of CPU cores to use for parallel processing. Default is -1, which uses all available cores.
|
|
2341
|
+
:param Optional[int] core_cnt: Number of CPU cores to use for parallel processing. Default is -1, which uses all available cores. Ignored if pool is provided.
|
|
2291
2342
|
:param Optional[bool] verbose: If True, print progress information during computation. Default is False.
|
|
2292
|
-
:param Optional[
|
|
2293
|
-
:param Optional[
|
|
2343
|
+
:param Optional[str] video_name: If string, include video name in progress information.
|
|
2344
|
+
:param Optional[str] animal_names: If string, include animal names in progress information.
|
|
2345
|
+
:param Optional[multiprocessing.Pool] pool: Optional multiprocessing pool to reuse. If None, creates a new pool. Default None.
|
|
2294
2346
|
:return: List of LineString or MultiLineString objects representing the computed skeletons.
|
|
2295
2347
|
:rtype: List[Union[LineString, MultiLineString]]
|
|
2296
2348
|
|
|
@@ -2341,33 +2393,27 @@ class GeometryMixin(object):
|
|
|
2341
2393
|
else:
|
|
2342
2394
|
skeleton_data = np.concatenate((skeleton_data, line), axis=1)
|
|
2343
2395
|
skeleton_data = skeleton_data.reshape(len(data_df), len(skeleton), 2, -1)
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2396
|
+
pool_terminate_flag = False if pool is not None else True
|
|
2397
|
+
if pool is not None: check_valid_cpu_pool(value=pool, source=f'{GeometryMixin().multiframe_bodyparts_to_multistring_skeleton.__name__} pool', raise_error=True, accepted_cores=core_cnt)
|
|
2398
|
+
else: pool = get_cpu_pool(core_cnt=core_cnt, source=GeometryMixin().multiframe_bodyparts_to_multistring_skeleton.__name__)
|
|
2399
|
+
for cnt, result in enumerate(pool.imap(GeometryMixin.bodyparts_to_multistring_skeleton, skeleton_data, chunksize=1)):
|
|
2400
|
+
if verbose:
|
|
2401
|
+
if not video_name and not animal_names:
|
|
2402
|
+
print(f"Computing skeleton {cnt + 1}/{len(data_df)}...")
|
|
2403
|
+
elif not video_name and animal_names:
|
|
2404
|
+
print(
|
|
2405
|
+
f"Computing skeleton {cnt + 1}/{len(data_df)} (Animals: {animal_names})..."
|
|
2352
2406
|
)
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
f"Computing skeleton {cnt + 1}/{len(data_df)} (Video: {video_name})..."
|
|
2364
|
-
)
|
|
2365
|
-
else:
|
|
2366
|
-
print(
|
|
2367
|
-
f"Computing skeleton {cnt + 1}/{len(data_df)} (Video: {video_name}, Animals: {animal_names})..."
|
|
2368
|
-
)
|
|
2369
|
-
results.append(result)
|
|
2370
|
-
|
|
2407
|
+
elif video_name and not animal_names:
|
|
2408
|
+
print(
|
|
2409
|
+
f"Computing skeleton {cnt + 1}/{len(data_df)} (Video: {video_name})..."
|
|
2410
|
+
)
|
|
2411
|
+
else:
|
|
2412
|
+
print(
|
|
2413
|
+
f"Computing skeleton {cnt + 1}/{len(data_df)} (Video: {video_name}, Animals: {animal_names})..."
|
|
2414
|
+
)
|
|
2415
|
+
results.append(result)
|
|
2416
|
+
if pool_terminate_flag: terminate_cpu_pool(pool=pool, source=GeometryMixin().multiframe_bodyparts_to_multistring_skeleton.__name__)
|
|
2371
2417
|
timer.stop_timer()
|
|
2372
2418
|
stdout_success(
|
|
2373
2419
|
msg="Multistring skeleton complete.",
|
|
@@ -2551,7 +2597,8 @@ class GeometryMixin(object):
|
|
|
2551
2597
|
def multiframe_is_shape_covered(self,
|
|
2552
2598
|
shape_1: List[Polygon],
|
|
2553
2599
|
shape_2: List[Polygon],
|
|
2554
|
-
core_cnt: Optional[int] = -1
|
|
2600
|
+
core_cnt: Optional[int] = -1,
|
|
2601
|
+
pool: Optional[multiprocessing.Pool] = None) -> List[bool]:
|
|
2555
2602
|
"""
|
|
2556
2603
|
For each shape in time-series of shapes, check if another shape in the same time-series fully covers the
|
|
2557
2604
|
first shape.
|
|
@@ -2563,6 +2610,13 @@ class GeometryMixin(object):
|
|
|
2563
2610
|
.. seealso::
|
|
2564
2611
|
For single core method, see :func:`simba.mixins.geometry_mixin.GeometryMixin.is_shape_covered`
|
|
2565
2612
|
|
|
2613
|
+
:param List[Union[LineString, Polygon, MultiPolygon]] shape_1: List of geometries to check if covered.
|
|
2614
|
+
:param List[Union[LineString, Polygon, MultiPolygon]] shape_2: List of geometries that may cover shape_1.
|
|
2615
|
+
:param Optional[int] core_cnt: Number of CPU cores to use for parallel processing. Default is -1, which uses all available cores. Ignored if pool is provided.
|
|
2616
|
+
:param Optional[multiprocessing.Pool] pool: Optional multiprocessing pool to reuse. If None, creates a new pool. Default None.
|
|
2617
|
+
:return: List of booleans indicating if each shape_1 is covered by corresponding shape_2.
|
|
2618
|
+
:rtype: List[bool]
|
|
2619
|
+
|
|
2566
2620
|
:example:
|
|
2567
2621
|
>>> shape_1 = GeometryMixin().multiframe_bodyparts_to_polygon(data=np.random.randint(0, 200, (100, 6, 2)))
|
|
2568
2622
|
>>> shape_2 = [Polygon([[0, 0], [20, 20], [20, 10], [10, 20]]) for x in range(len(shape_1))]
|
|
@@ -2602,14 +2656,12 @@ class GeometryMixin(object):
|
|
|
2602
2656
|
core_cnt = find_core_cnt()[0]
|
|
2603
2657
|
shapes = [list(x) for x in zip(shape_1, shape_2)]
|
|
2604
2658
|
results = []
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
results.append(mp_return)
|
|
2612
|
-
terminate_cpu_pool(pool=pool, force=False)
|
|
2659
|
+
pool_terminate_flag = False if pool is not None else True
|
|
2660
|
+
if pool is not None: check_valid_cpu_pool(value=pool, source=f'{GeometryMixin().multiframe_is_shape_covered.__name__} pool', raise_error=True, accepted_cores=core_cnt)
|
|
2661
|
+
else: pool = get_cpu_pool(core_cnt=core_cnt, source=GeometryMixin().multiframe_is_shape_covered.__name__)
|
|
2662
|
+
for cnt, mp_return in enumerate(pool.imap(GeometryMixin.is_shape_covered, shapes, chunksize=1)):
|
|
2663
|
+
results.append(mp_return)
|
|
2664
|
+
if pool_terminate_flag: terminate_cpu_pool(pool=pool, source=GeometryMixin().multiframe_is_shape_covered.__name__)
|
|
2613
2665
|
return results
|
|
2614
2666
|
|
|
2615
2667
|
@staticmethod
|
|
@@ -2774,7 +2826,8 @@ class GeometryMixin(object):
|
|
|
2774
2826
|
lag: Optional[int] = 2,
|
|
2775
2827
|
core_cnt: Optional[int] = -1,
|
|
2776
2828
|
pixels_per_mm: int = 1,
|
|
2777
|
-
parallel_offset: int = 1
|
|
2829
|
+
parallel_offset: int = 1,
|
|
2830
|
+
pool: Optional[multiprocessing.Pool] = None) -> np.ndarray:
|
|
2778
2831
|
|
|
2779
2832
|
"""
|
|
2780
2833
|
Perform geometry histocomparison on multiple video frames using multiprocessing.
|
|
@@ -2788,11 +2841,12 @@ class GeometryMixin(object):
|
|
|
2788
2841
|
|
|
2789
2842
|
:param Union[str, os.PathLike] video_path: Path to the video file.
|
|
2790
2843
|
:param np.ndarray data: Input data, typically containing coordinates of one or several body-parts.
|
|
2791
|
-
:param Literal['rectangle', 'circle'] shape_type: Type of shape for comparison.
|
|
2844
|
+
:param Literal['rectangle', 'circle', 'line'] shape_type: Type of shape for comparison.
|
|
2792
2845
|
:param Optional[int] lag: Number of frames to lag between comparisons. Default is 2.
|
|
2793
|
-
:param Optional[int] core_cnt: Number of CPU cores to use for parallel processing. Default is -1 which is all available cores.
|
|
2794
|
-
:param
|
|
2795
|
-
:param
|
|
2846
|
+
:param Optional[int] core_cnt: Number of CPU cores to use for parallel processing. Default is -1 which is all available cores. Ignored if pool is provided.
|
|
2847
|
+
:param int pixels_per_mm: Pixels per millimeter for conversion. Default is 1.
|
|
2848
|
+
:param int parallel_offset: Size of the geometry ROI in millimeters. Default 1.
|
|
2849
|
+
:param Optional[multiprocessing.Pool] pool: Optional multiprocessing pool to reuse. If None, creates a new pool. Default None.
|
|
2796
2850
|
:returns: The difference between the successive geometry histograms.
|
|
2797
2851
|
:rtype: np.ndarray
|
|
2798
2852
|
|
|
@@ -2821,22 +2875,13 @@ class GeometryMixin(object):
|
|
|
2821
2875
|
for i in range(core_cnt)
|
|
2822
2876
|
]
|
|
2823
2877
|
results = [[0] * lag]
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
shape_type=shape_type,
|
|
2832
|
-
pixels_per_mm=pixels_per_mm,
|
|
2833
|
-
parallel_offset=parallel_offset,
|
|
2834
|
-
)
|
|
2835
|
-
for cnt, result in enumerate(
|
|
2836
|
-
pool.imap(constants, split_frm_idx, chunksize=1)
|
|
2837
|
-
):
|
|
2838
|
-
results.append(result)
|
|
2839
|
-
|
|
2878
|
+
pool_terminate_flag = False if pool is not None else True
|
|
2879
|
+
if pool is not None: check_valid_cpu_pool(value=pool, source=f'{GeometryMixin().multifrm_geometry_histocomparison.__name__} pool', raise_error=True, accepted_cores=core_cnt)
|
|
2880
|
+
else: pool = get_cpu_pool(core_cnt=core_cnt, source=GeometryMixin().multifrm_geometry_histocomparison.__name__)
|
|
2881
|
+
constants = functools.partial(GeometryMixin()._multifrm_geometry_histocomparison_helper,video_path=video_path,data=data,shape_type=shape_type,pixels_per_mm=pixels_per_mm,parallel_offset=parallel_offset)
|
|
2882
|
+
for cnt, result in enumerate(pool.imap(constants, split_frm_idx, chunksize=1)):
|
|
2883
|
+
results.append(result)
|
|
2884
|
+
if pool_terminate_flag: terminate_cpu_pool(pool=pool, source=GeometryMixin().multifrm_geometry_histocomparison.__name__)
|
|
2840
2885
|
return [item for sublist in results for item in sublist]
|
|
2841
2886
|
|
|
2842
2887
|
@staticmethod
|
|
@@ -3258,7 +3303,8 @@ class GeometryMixin(object):
|
|
|
3258
3303
|
geometries: Dict[Tuple[int, int], Polygon],
|
|
3259
3304
|
fps: Optional[int] = None,
|
|
3260
3305
|
core_cnt: Optional[int] = -1,
|
|
3261
|
-
verbose: Optional[bool] = True
|
|
3306
|
+
verbose: Optional[bool] = True,
|
|
3307
|
+
pool: Optional[multiprocessing.Pool] = None) -> np.ndarray:
|
|
3262
3308
|
|
|
3263
3309
|
"""
|
|
3264
3310
|
Compute the cumulative time a body-part has spent inside a grid of geometries using multiprocessing.
|
|
@@ -3269,9 +3315,11 @@ class GeometryMixin(object):
|
|
|
3269
3315
|
:param np.ndarray data: Input data array where rows represent frames and columns represent body-part x and y coordinates.
|
|
3270
3316
|
:param Dict[Tuple[int, int], Polygon] geometries: Dictionary of polygons representing spatial regions. E.g., created by :func:`simba.mixins.geometry_mixin.GeometryMixin.bucket_img_into_grid_square` or :func:`simba.mixins.geometry_mixin.GeometryMixin.bucket_img_into_grid_hexagon`.
|
|
3271
3317
|
:param Optional[int] fps: Frames per second (fps) for time normalization. If None, cumulative sum of frame count is returned.
|
|
3272
|
-
:param Optional[int] core_cnt: Number of CPU cores to use for parallel processing. Default is -1 which is all available cores.
|
|
3318
|
+
:param Optional[int] core_cnt: Number of CPU cores to use for parallel processing. Default is -1 which is all available cores. Ignored if pool is provided.
|
|
3273
3319
|
:param Optional[bool] verbose: If True, prints progress.
|
|
3274
|
-
:
|
|
3320
|
+
:param Optional[multiprocessing.Pool] pool: Optional multiprocessing pool to reuse. If None, creates a new pool. Default None.
|
|
3321
|
+
:returns: Matrix of size (frames x horizontal bins x vertical bins) with cumulative time values.
|
|
3322
|
+
:rtype: np.ndarray
|
|
3275
3323
|
|
|
3276
3324
|
:example:
|
|
3277
3325
|
>>> img_geometries = GeometryMixin.bucket_img_into_grid_square(img_size=(640, 640), bucket_grid_size=(10, 10), px_per_mm=1)
|
|
@@ -3299,18 +3347,14 @@ class GeometryMixin(object):
|
|
|
3299
3347
|
frm_id = np.arange(0, data.shape[0]).reshape(-1, 1)
|
|
3300
3348
|
data = np.hstack((frm_id, data))
|
|
3301
3349
|
img_arr = np.zeros((data.shape[0], h + 1, w + 1))
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
for cnt, result in enumerate(pool.imap(constants, data, chunksize=1)):
|
|
3311
|
-
if result[1] != -1:
|
|
3312
|
-
img_arr[result[0], result[2] - 1, result[1] - 1] = 1
|
|
3313
|
-
terminate_cpu_pool(pool=pool, force=False)
|
|
3350
|
+
pool_terminate_flag = False if pool is not None else True
|
|
3351
|
+
if pool is not None: check_valid_cpu_pool(value=pool, source=f'{GeometryMixin().cumsum_coord_geometries.__name__} pool', raise_error=True, accepted_cores=core_cnt)
|
|
3352
|
+
else: pool = get_cpu_pool(core_cnt=core_cnt, source=GeometryMixin().cumsum_coord_geometries.__name__)
|
|
3353
|
+
constants = functools.partial(self._cumsum_coord_geometries_helper, geometries=geometries, verbose=verbose)
|
|
3354
|
+
for cnt, result in enumerate(pool.imap(constants, data, chunksize=1)):
|
|
3355
|
+
if result[1] != -1:
|
|
3356
|
+
img_arr[result[0], result[2] - 1, result[1] - 1] = 1
|
|
3357
|
+
if pool_terminate_flag: terminate_cpu_pool(pool=pool, source=GeometryMixin().cumsum_coord_geometries.__name__)
|
|
3314
3358
|
timer.stop_timer()
|
|
3315
3359
|
stdout_success(
|
|
3316
3360
|
msg="Cumulative coordinates in geometries complete",
|
|
@@ -3340,7 +3384,8 @@ class GeometryMixin(object):
|
|
|
3340
3384
|
bool_data: np.ndarray,
|
|
3341
3385
|
fps: Optional[float] = None,
|
|
3342
3386
|
verbose: bool = True,
|
|
3343
|
-
core_cnt: Optional[int] = -1
|
|
3387
|
+
core_cnt: Optional[int] = -1,
|
|
3388
|
+
pool: Optional[multiprocessing.Pool] = None) -> np.ndarray:
|
|
3344
3389
|
"""
|
|
3345
3390
|
Compute the cumulative sums of boolean events within polygon geometries over time using multiprocessing. For example, compute the cumulative bout count of classified events within spatial locations at all time-points of the video.
|
|
3346
3391
|
|
|
@@ -3359,8 +3404,10 @@ class GeometryMixin(object):
|
|
|
3359
3404
|
:param np.ndarray bool_data: Boolean array with shape (data.shape[0],) or (data.shape[0], 1) indicating the presence or absence in each frame.
|
|
3360
3405
|
:param Optional[float] fps: Frames per second. If provided, the result is normalized by the frame rate.
|
|
3361
3406
|
:param bool verbose: If true, prints progress. Default: True.
|
|
3362
|
-
:param Optional[
|
|
3363
|
-
:
|
|
3407
|
+
:param Optional[int] core_cnt: Number of CPU cores to use for parallel processing. Default is -1, which means using all available cores. Ignored if pool is provided.
|
|
3408
|
+
:param Optional[multiprocessing.Pool] pool: Optional multiprocessing pool to reuse. If None, creates a new pool. Default None.
|
|
3409
|
+
:returns: Matrix of size (frames x horizontal bins x vertical bins) with times in seconds (if fps passed) or frames (if fps not passed)
|
|
3410
|
+
:rtype: np.ndarray
|
|
3364
3411
|
:rtype: np.ndarray
|
|
3365
3412
|
|
|
3366
3413
|
:example:
|
|
@@ -3396,14 +3443,14 @@ class GeometryMixin(object):
|
|
|
3396
3443
|
data = np.hstack((frm_id, data))
|
|
3397
3444
|
img_arr = np.zeros((data.shape[0], h + 1, w + 1))
|
|
3398
3445
|
data = data[np.argwhere((data[:, 3] == 1))].reshape(-1, 4)
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
terminate_cpu_pool(pool=pool,
|
|
3446
|
+
pool_terminate_flag = False if pool is not None else True
|
|
3447
|
+
if pool is not None: check_valid_cpu_pool(value=pool, source=f'{GeometryMixin().cumsum_bool_geometries.__name__} pool', raise_error=True, accepted_cores=core_cnt)
|
|
3448
|
+
else: pool = get_cpu_pool(core_cnt=core_cnt, source=GeometryMixin().cumsum_bool_geometries.__name__)
|
|
3449
|
+
constants = functools.partial(self._cumsum_bool_helper, geometries=geometries, verbose=verbose)
|
|
3450
|
+
for cnt, result in enumerate(pool.imap(constants, data, chunksize=1)):
|
|
3451
|
+
if result[1] != -1:
|
|
3452
|
+
img_arr[result[0], result[2] - 1, result[1] - 1] = 1
|
|
3453
|
+
if pool_terminate_flag: terminate_cpu_pool(pool=pool, source=GeometryMixin().cumsum_bool_geometries.__name__)
|
|
3407
3454
|
if fps is None:
|
|
3408
3455
|
return np.cumsum(img_arr, axis=0)
|
|
3409
3456
|
else:
|
|
@@ -3430,7 +3477,8 @@ class GeometryMixin(object):
|
|
|
3430
3477
|
grid: Dict[Tuple[int, int], Polygon],
|
|
3431
3478
|
fps: Optional[int] = None,
|
|
3432
3479
|
core_cnt: Optional[int] = -1,
|
|
3433
|
-
verbose: Optional[bool] = True
|
|
3480
|
+
verbose: Optional[bool] = True,
|
|
3481
|
+
pool: Optional[multiprocessing.Pool] = None) -> np.ndarray:
|
|
3434
3482
|
"""
|
|
3435
3483
|
Compute the cumulative time the animal has spent in each geometry.
|
|
3436
3484
|
|
|
@@ -3447,10 +3495,12 @@ class GeometryMixin(object):
|
|
|
3447
3495
|
|
|
3448
3496
|
:param List[Polygon] data: List of polygons where every index represent a frame and every value the animal convex hull
|
|
3449
3497
|
:param Dict[Tuple[int, int], Polygon] grid: Dictionary of polygons representing spatial regions. E.g., created by :func:`simba.mixins.geometry_mixin.GeometryMixin.bucket_img_into_grid_square` or :func:`simba.mixins.geometry_mixin.GeometryMixin.bucket_img_into_grid_hexagon`.
|
|
3450
|
-
:param Optional[
|
|
3451
|
-
:param Optional[
|
|
3498
|
+
:param Optional[int] fps: Frames per second. If provided, the result is normalized by the frame rate.
|
|
3499
|
+
:param Optional[int] core_cnt: Number of CPU cores to use for parallel processing. Default is -1, which means using all available cores. Ignored if pool is provided.
|
|
3452
3500
|
:param Optional[bool] verbose: If True, then prints progress.
|
|
3453
|
-
:
|
|
3501
|
+
:param Optional[multiprocessing.Pool] pool: Optional multiprocessing pool to reuse. If None, creates a new pool. Default None.
|
|
3502
|
+
:returns: Matrix of size (frames x horizontal bins x vertical bins) with values representing time in seconds (if fps passed) or frames (if fps not passed)
|
|
3503
|
+
:rtype: np.ndarray
|
|
3454
3504
|
:rtype: np.ndarray
|
|
3455
3505
|
"""
|
|
3456
3506
|
|
|
@@ -3467,12 +3517,13 @@ class GeometryMixin(object):
|
|
|
3467
3517
|
frm_id = np.arange(0, len(data)).reshape(-1, 1)
|
|
3468
3518
|
data = np.hstack((frm_id, np.array(data).reshape(-1, 1)))
|
|
3469
3519
|
img_arr = np.zeros((data.shape[0], h + 1, w + 1))
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3520
|
+
pool_terminate_flag = False if pool is not None else True
|
|
3521
|
+
if pool is not None: check_valid_cpu_pool(value=pool, source=f'{GeometryMixin().cumsum_animal_geometries_grid.__name__} pool', raise_error=True, accepted_cores=core_cnt)
|
|
3522
|
+
else: pool = get_cpu_pool(core_cnt=core_cnt, source=GeometryMixin().cumsum_animal_geometries_grid.__name__)
|
|
3523
|
+
constants = functools.partial(self._cumsum_animal_geometries_grid_helper, grid=grid, size=(h, w), erbose=verbose)
|
|
3524
|
+
for cnt, result in enumerate(pool.imap(constants, data, chunksize=1)):
|
|
3525
|
+
img_arr[cnt] = result
|
|
3526
|
+
if pool_terminate_flag: terminate_cpu_pool(pool=pool, source=GeometryMixin().cumsum_animal_geometries_grid.__name__)
|
|
3476
3527
|
timer.stop_timer()
|
|
3477
3528
|
stdout_success(msg="Cumulative animal geometries in grid complete", elapsed_time=timer.elapsed_time_str, )
|
|
3478
3529
|
if fps is None:
|
|
@@ -3501,8 +3552,8 @@ class GeometryMixin(object):
|
|
|
3501
3552
|
def geometry_transition_probabilities(data: np.ndarray,
|
|
3502
3553
|
grid: Dict[Tuple[int, int], Polygon],
|
|
3503
3554
|
core_cnt: Optional[int] = -1,
|
|
3504
|
-
verbose: Optional[bool] = False
|
|
3505
|
-
|
|
3555
|
+
verbose: Optional[bool] = False,
|
|
3556
|
+
pool: Optional[multiprocessing.Pool] = None) -> (Dict[Tuple[int, int], float], Dict[Tuple[int, int], int]):
|
|
3506
3557
|
"""
|
|
3507
3558
|
Calculate geometry transition probabilities based on spatial transitions between grid cells.
|
|
3508
3559
|
|
|
@@ -3515,8 +3566,9 @@ class GeometryMixin(object):
|
|
|
3515
3566
|
:param np.ndarray data: A 2D array where each row represents a point in space with two coordinates [x, y].
|
|
3516
3567
|
:param Dict[Tuple[int, int], Polygon] grid: A dictionary mapping grid cell identifiers (tuple of int, int) to their corresponding polygon objects.
|
|
3517
3568
|
Each grid cell is represented by a tuple key (e.g., (row, col)) and its spatial boundaries as a `Polygon`. Can be computed with E.g., created by :func:`simba.mixins.geometry_mixin.GeometryMixin.bucket_img_into_grid_square` or :func:`simba.mixins.geometry_mixin.GeometryMixin.bucket_img_into_grid_hexagon`.
|
|
3518
|
-
:param Optional[int] core_cnt: The number of cores to use for parallel processing. Default is -1, which uses the maximum available cores.
|
|
3569
|
+
:param Optional[int] core_cnt: The number of cores to use for parallel processing. Default is -1, which uses the maximum available cores. Ignored if pool is provided.
|
|
3519
3570
|
:param Optional[bool] verbose: If True, the function will print additional information, including the elapsed time for processing.
|
|
3571
|
+
:param Optional[multiprocessing.Pool] pool: Optional multiprocessing pool to reuse. If None, creates a new pool. Default None.
|
|
3520
3572
|
:return: A tuple containing two dictionaries:
|
|
3521
3573
|
- A dictionary of transition probabilities between grid cells, where each key is a grid cell tuple (row, col),
|
|
3522
3574
|
and each value is another dictionary representing the transition probabilities to other cells.
|
|
@@ -3533,20 +3585,20 @@ class GeometryMixin(object):
|
|
|
3533
3585
|
"""
|
|
3534
3586
|
|
|
3535
3587
|
timer = SimbaTimer(start=True)
|
|
3536
|
-
check_valid_array(data=data, source=GeometryMixin.geometry_transition_probabilities.__name__,
|
|
3537
|
-
accepted_ndims=(2,), accepted_axis_1_shape=[2, ],
|
|
3538
|
-
accepted_dtypes=Formats.NUMERIC_DTYPES.value)
|
|
3588
|
+
check_valid_array(data=data, source=GeometryMixin.geometry_transition_probabilities.__name__, accepted_ndims=(2,), accepted_axis_1_shape=[2, ], accepted_dtypes=Formats.NUMERIC_DTYPES.value)
|
|
3539
3589
|
check_valid_dict(x=grid, valid_key_dtypes=(tuple,), valid_values_dtypes=(Polygon,))
|
|
3540
3590
|
check_int(name="core_cnt", value=core_cnt, min_value=-1, unaccepted_vals=[0])
|
|
3541
3591
|
if core_cnt == -1 or core_cnt > find_core_cnt()[0]: core_cnt = find_core_cnt()[0]
|
|
3542
3592
|
frm_id = np.arange(0, data.shape[0]).reshape(-1, 1)
|
|
3543
3593
|
data = np.hstack((frm_id, data)).reshape(-1, 3).astype(np.int32)
|
|
3544
3594
|
data, results = np.array_split(data, core_cnt), []
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
|
|
3595
|
+
pool_terminate_flag = False if pool is not None else True
|
|
3596
|
+
if pool is not None: check_valid_cpu_pool(value=pool, source=f'{GeometryMixin.geometry_transition_probabilities.__name__} pool', raise_error=True, accepted_cores=core_cnt)
|
|
3597
|
+
else: pool = get_cpu_pool(core_cnt=core_cnt, source=GeometryMixin.geometry_transition_probabilities.__name__)
|
|
3598
|
+
constants = functools.partial(GeometryMixin._compute_framewise_geometry_idx, grid=grid, verbose=verbose)
|
|
3599
|
+
for cnt, result in enumerate(pool.imap(constants, data, chunksize=1)):
|
|
3600
|
+
results.append(result)
|
|
3601
|
+
if pool_terminate_flag: terminate_cpu_pool(pool=pool, source=GeometryMixin.geometry_transition_probabilities.__name__)
|
|
3550
3602
|
del data
|
|
3551
3603
|
|
|
3552
3604
|
results = np.vstack(results)[:, 1:].astype(np.int32)
|
|
@@ -3618,7 +3670,8 @@ class GeometryMixin(object):
|
|
|
3618
3670
|
geometries: List[Union[Polygon, LineString]],
|
|
3619
3671
|
lag: Optional[Union[float, int]] = 1,
|
|
3620
3672
|
sample_rate: Optional[Union[float, int]] = 1,
|
|
3621
|
-
core_cnt: Optional[int] = -1
|
|
3673
|
+
core_cnt: Optional[int] = -1,
|
|
3674
|
+
pool: Optional[multiprocessing.Pool] = None) -> List[float]:
|
|
3622
3675
|
"""
|
|
3623
3676
|
The Hausdorff distance measure of the similarity between sequential time-series geometries.
|
|
3624
3677
|
|
|
@@ -3628,9 +3681,10 @@ class GeometryMixin(object):
|
|
|
3628
3681
|
:param List[Union[Polygon, LineString]] geometries: List of geometries.
|
|
3629
3682
|
:param Optional[Union[float, int]] lag: If int, then the number of frames preceeding the current frame to compare the geometry with. Eg., 1 compares the geometry to the immediately preceeding geometry. If float, then evaluated as seconds. E.g., 1 compares the geometry to the geometry 1s prior in the geometries list.
|
|
3630
3683
|
:param Optional[Union[float, int]] sample_rate: The FPS of the recording. Used as conversion factor if lag is a float.
|
|
3631
|
-
:param Optional[int] core_cnt: The number of cores to use for parallel processing. Default is -1, which uses the maximum available cores.
|
|
3684
|
+
:param Optional[int] core_cnt: The number of cores to use for parallel processing. Default is -1, which uses the maximum available cores. Ignored if pool is provided.
|
|
3685
|
+
:param Optional[multiprocessing.Pool] pool: Optional multiprocessing pool to reuse. If None, creates a new pool. Default None.
|
|
3632
3686
|
:returns: List of Hausdorff distance measures.
|
|
3633
|
-
:rtype: List[float]
|
|
3687
|
+
:rtype: List[float]
|
|
3634
3688
|
|
|
3635
3689
|
:example:
|
|
3636
3690
|
>>> df = read_df(file_path='/Users/simon/Desktop/envs/simba/troubleshooting/mouse_open_field/project_folder/csv/outlier_corrected_movement_location/SI_DAY3_308_CD1_PRESENT.csv', file_type='csv')
|
|
@@ -3669,15 +3723,12 @@ class GeometryMixin(object):
|
|
|
3669
3723
|
for i in range(lag, len(geometries)):
|
|
3670
3724
|
reshaped_geometries.append([[geometries[i - lag], geometries[i]]])
|
|
3671
3725
|
results = []
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
)
|
|
3679
|
-
):
|
|
3680
|
-
results.append(mp_return[0])
|
|
3726
|
+
pool_terminate_flag = False if pool is not None else True
|
|
3727
|
+
if pool is not None: check_valid_cpu_pool(value=pool, source=f'{GeometryMixin().multiframe_hausdorff_distance.__name__} pool', raise_error=True, accepted_cores=core_cnt)
|
|
3728
|
+
else: pool = get_cpu_pool(core_cnt=core_cnt, source=GeometryMixin().multiframe_hausdorff_distance.__name__)
|
|
3729
|
+
for cnt, mp_return in enumerate(pool.imap(GeometryMixin.hausdorff_distance, reshaped_geometries, chunksize=1)):
|
|
3730
|
+
results.append(mp_return[0])
|
|
3731
|
+
if pool_terminate_flag: terminate_cpu_pool(pool=pool, source=GeometryMixin().multiframe_hausdorff_distance.__name__)
|
|
3681
3732
|
return results
|
|
3682
3733
|
|
|
3683
3734
|
@staticmethod
|
|
@@ -3976,7 +4027,9 @@ class GeometryMixin(object):
|
|
|
3976
4027
|
return results
|
|
3977
4028
|
|
|
3978
4029
|
@staticmethod
|
|
3979
|
-
def geometries_to_exterior_keypoints(geometries: List[Polygon],
|
|
4030
|
+
def geometries_to_exterior_keypoints(geometries: List[Polygon],
|
|
4031
|
+
core_cnt: Optional[int] = -1,
|
|
4032
|
+
pool: Optional[multiprocessing.Pool] = None) -> np.ndarray:
|
|
3980
4033
|
"""
|
|
3981
4034
|
Extract exterior keypoints from a list of Polygon geometries in parallel, with optional core count specification for multiprocessing.
|
|
3982
4035
|
|
|
@@ -3985,7 +4038,8 @@ class GeometryMixin(object):
|
|
|
3985
4038
|
:align: center
|
|
3986
4039
|
|
|
3987
4040
|
:param List[Polygon] geometries: A list of Shapely `Polygon` objects representing geometries whose exterior keypoints will be extracted.
|
|
3988
|
-
:param Optional[int] core_cnt: The number of CPU cores to use for multiprocessing. If -1, it uses the maximum number of available cores.
|
|
4041
|
+
:param Optional[int] core_cnt: The number of CPU cores to use for multiprocessing. If -1, it uses the maximum number of available cores. Ignored if pool is provided.
|
|
4042
|
+
:param Optional[multiprocessing.Pool] pool: Optional multiprocessing pool to reuse. If None, creates a new pool. Default None.
|
|
3989
4043
|
:return: A numpy array of exterior keypoints extracted from the input geometries.
|
|
3990
4044
|
:rtype: np.ndarray
|
|
3991
4045
|
|
|
@@ -3997,15 +4051,16 @@ class GeometryMixin(object):
|
|
|
3997
4051
|
"""
|
|
3998
4052
|
check_int(name="CORE COUNT", value=core_cnt, min_value=-1, max_value=find_core_cnt()[0], raise_error=True)
|
|
3999
4053
|
if core_cnt == -1: core_cnt = find_core_cnt()[0]
|
|
4000
|
-
check_valid_lst(data=geometries, source=GeometryMixin.geometries_to_exterior_keypoints.__name__,
|
|
4001
|
-
valid_dtypes=(Polygon,), min_len=1)
|
|
4054
|
+
check_valid_lst(data=geometries, source=GeometryMixin.geometries_to_exterior_keypoints.__name__, valid_dtypes=(Polygon,), min_len=1)
|
|
4002
4055
|
results = []
|
|
4003
4056
|
geometries = np.array_split(geometries, 3)
|
|
4004
|
-
|
|
4005
|
-
|
|
4006
|
-
|
|
4007
|
-
|
|
4057
|
+
pool_terminate_flag = False if pool is not None else True
|
|
4058
|
+
if pool is not None: check_valid_cpu_pool(value=pool, source=f'{GeometryMixin.geometries_to_exterior_keypoints.__name__} pool', raise_error=True, accepted_cores=core_cnt)
|
|
4059
|
+
else: pool = get_cpu_pool(core_cnt=core_cnt, source=GeometryMixin.geometries_to_exterior_keypoints.__name__)
|
|
4060
|
+
for cnt, mp_return in enumerate(pool.imap(GeometryMixin._geometries_to_exterior_keypoints_helper, geometries, chunksize=1)):
|
|
4061
|
+
results.append(mp_return)
|
|
4008
4062
|
results = [i for xs in results for i in xs]
|
|
4063
|
+
if pool_terminate_flag: terminate_cpu_pool(pool=pool, source=GeometryMixin.geometries_to_exterior_keypoints.__name__)
|
|
4009
4064
|
return np.ascontiguousarray(np.array(results)).astype(np.int32)
|
|
4010
4065
|
|
|
4011
4066
|
@staticmethod
|