simba-uw-tf-dev 4.6.2__py3-none-any.whl → 4.7.1__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/assets/.recent_projects.txt +1 -0
- simba/assets/lookups/tooptips.json +6 -1
- simba/data_processors/agg_clf_counter_mp.py +52 -53
- simba/data_processors/blob_location_computer.py +1 -1
- simba/data_processors/circling_detector.py +30 -13
- simba/data_processors/cuda/geometry.py +45 -27
- simba/data_processors/cuda/image.py +1648 -1598
- simba/data_processors/cuda/statistics.py +72 -26
- simba/data_processors/cuda/timeseries.py +1 -1
- simba/data_processors/cue_light_analyzer.py +5 -9
- simba/data_processors/egocentric_aligner.py +25 -7
- simba/data_processors/freezing_detector.py +55 -47
- simba/data_processors/kleinberg_calculator.py +61 -29
- simba/feature_extractors/feature_subsets.py +14 -7
- simba/feature_extractors/mitra_feature_extractor.py +2 -2
- simba/feature_extractors/straub_tail_analyzer.py +4 -6
- simba/labelling/standard_labeller.py +1 -1
- simba/mixins/config_reader.py +5 -2
- simba/mixins/geometry_mixin.py +22 -36
- simba/mixins/image_mixin.py +24 -28
- simba/mixins/plotting_mixin.py +28 -10
- simba/mixins/statistics_mixin.py +48 -11
- simba/mixins/timeseries_features_mixin.py +1 -1
- simba/mixins/train_model_mixin.py +67 -29
- simba/model/inference_batch.py +1 -1
- simba/model/yolo_seg_inference.py +3 -3
- simba/outlier_tools/skip_outlier_correction.py +1 -1
- simba/plotting/ROI_feature_visualizer_mp.py +3 -5
- simba/plotting/clf_validator_mp.py +4 -5
- simba/plotting/cue_light_visualizer.py +6 -7
- simba/plotting/directing_animals_visualizer_mp.py +2 -3
- simba/plotting/distance_plotter_mp.py +378 -378
- simba/plotting/gantt_creator.py +29 -10
- simba/plotting/gantt_creator_mp.py +96 -33
- simba/plotting/geometry_plotter.py +270 -272
- simba/plotting/heat_mapper_clf_mp.py +4 -6
- simba/plotting/heat_mapper_location_mp.py +2 -2
- simba/plotting/light_dark_box_plotter.py +2 -2
- simba/plotting/path_plotter_mp.py +26 -29
- simba/plotting/plot_clf_results_mp.py +455 -454
- simba/plotting/pose_plotter_mp.py +28 -29
- simba/plotting/probability_plot_creator_mp.py +288 -288
- simba/plotting/roi_plotter_mp.py +31 -31
- simba/plotting/single_run_model_validation_video_mp.py +427 -427
- simba/plotting/spontaneous_alternation_plotter.py +2 -3
- simba/plotting/yolo_pose_track_visualizer.py +32 -27
- simba/plotting/yolo_pose_visualizer.py +35 -36
- simba/plotting/yolo_seg_visualizer.py +2 -3
- simba/pose_importers/simba_blob_importer.py +3 -3
- simba/roi_tools/roi_aggregate_stats_mp.py +5 -4
- simba/roi_tools/roi_clf_calculator_mp.py +4 -4
- simba/sandbox/analyze_runtimes.py +30 -0
- simba/sandbox/cuda/egocentric_rotator.py +374 -374
- simba/sandbox/get_cpu_pool.py +5 -0
- simba/sandbox/proboscis_to_tip.py +28 -0
- simba/sandbox/test_directionality.py +47 -0
- simba/sandbox/test_nonstatic_directionality.py +27 -0
- simba/sandbox/test_pycharm_cuda.py +51 -0
- simba/sandbox/test_simba_install.py +41 -0
- simba/sandbox/test_static_directionality.py +26 -0
- simba/sandbox/test_static_directionality_2d.py +26 -0
- simba/sandbox/verify_env.py +42 -0
- simba/third_party_label_appenders/transform/coco_keypoints_to_yolo.py +3 -3
- simba/third_party_label_appenders/transform/coco_keypoints_to_yolo_bbox.py +2 -2
- simba/ui/pop_ups/clf_plot_pop_up.py +2 -2
- simba/ui/pop_ups/fsttc_pop_up.py +27 -25
- simba/ui/pop_ups/gantt_pop_up.py +31 -6
- simba/ui/pop_ups/kleinberg_pop_up.py +39 -40
- simba/ui/pop_ups/video_processing_pop_up.py +37 -29
- simba/ui/tkinter_functions.py +3 -0
- simba/utils/custom_feature_extractor.py +1 -1
- simba/utils/data.py +90 -14
- simba/utils/enums.py +1 -0
- simba/utils/errors.py +441 -440
- simba/utils/lookups.py +1203 -1203
- simba/utils/printing.py +124 -124
- simba/utils/read_write.py +3769 -3721
- simba/utils/yolo.py +10 -1
- simba/video_processors/blob_tracking_executor.py +2 -2
- simba/video_processors/clahe_ui.py +1 -1
- simba/video_processors/egocentric_video_rotator.py +44 -41
- simba/video_processors/multi_cropper.py +1 -1
- simba/video_processors/video_processing.py +5264 -5222
- simba/video_processors/videos_to_frames.py +43 -33
- {simba_uw_tf_dev-4.6.2.dist-info → simba_uw_tf_dev-4.7.1.dist-info}/METADATA +4 -3
- {simba_uw_tf_dev-4.6.2.dist-info → simba_uw_tf_dev-4.7.1.dist-info}/RECORD +90 -80
- {simba_uw_tf_dev-4.6.2.dist-info → simba_uw_tf_dev-4.7.1.dist-info}/LICENSE +0 -0
- {simba_uw_tf_dev-4.6.2.dist-info → simba_uw_tf_dev-4.7.1.dist-info}/WHEEL +0 -0
- {simba_uw_tf_dev-4.6.2.dist-info → simba_uw_tf_dev-4.7.1.dist-info}/entry_points.txt +0 -0
- {simba_uw_tf_dev-4.6.2.dist-info → simba_uw_tf_dev-4.7.1.dist-info}/top_level.txt +0 -0
|
@@ -1,272 +1,270 @@
|
|
|
1
|
-
import functools
|
|
2
|
-
import multiprocessing
|
|
3
|
-
import os
|
|
4
|
-
from typing import List, Optional, Tuple, Union
|
|
5
|
-
|
|
6
|
-
import cv2
|
|
7
|
-
import numpy as np
|
|
8
|
-
import pandas as pd
|
|
9
|
-
from shapely.geometry import (LineString, MultiLineString, MultiPolygon, Point,
|
|
10
|
-
Polygon)
|
|
11
|
-
|
|
12
|
-
from simba.mixins.config_reader import ConfigReader
|
|
13
|
-
from simba.mixins.plotting_mixin import PlottingMixin
|
|
14
|
-
from simba.utils.checks import (check_float, check_if_dir_exists,
|
|
15
|
-
check_if_valid_rgb_tuple, check_instance,
|
|
16
|
-
check_int, check_iterable_length, check_str,
|
|
17
|
-
check_valid_boolean, check_valid_lst)
|
|
18
|
-
from simba.utils.data import create_color_palettes
|
|
19
|
-
from simba.utils.enums import Defaults, Formats
|
|
20
|
-
from simba.utils.errors import InvalidInputError
|
|
21
|
-
from simba.utils.lookups import get_color_dict
|
|
22
|
-
from simba.utils.printing import SimbaTimer, stdout_success
|
|
23
|
-
from simba.utils.read_write import (concatenate_videos_in_folder,
|
|
24
|
-
find_core_cnt, find_video_of_file,
|
|
25
|
-
get_fn_ext, get_video_meta_data,
|
|
26
|
-
remove_a_folder)
|
|
27
|
-
from simba.utils.warnings import FrameRangeWarning
|
|
28
|
-
|
|
29
|
-
ACCEPTED_TYPES = [Polygon, LineString, MultiPolygon, MultiLineString, Point]
|
|
30
|
-
FRAME_COUNT = "frame_count"
|
|
31
|
-
|
|
32
|
-
def geometry_visualizer(data: Tuple[int, pd.DataFrame],
|
|
33
|
-
video_path: Union[str, os.PathLike],
|
|
34
|
-
video_temp_dir: Union[str, os.PathLike],
|
|
35
|
-
video_meta_data: dict,
|
|
36
|
-
thickness: int,
|
|
37
|
-
verbose: bool,
|
|
38
|
-
intersection_clr: Optional[Tuple[int, int, int]],
|
|
39
|
-
bg_opacity: float,
|
|
40
|
-
colors: list,
|
|
41
|
-
circle_size: int,
|
|
42
|
-
shape_opacity: float):
|
|
43
|
-
|
|
44
|
-
group, idx = int(data[0]), data[1].index.tolist()
|
|
45
|
-
start_frm, end_frm = idx[0], idx[-1]
|
|
46
|
-
fourcc = cv2.VideoWriter_fourcc(*Formats.MP4_CODEC.value)
|
|
47
|
-
video_save_path = os.path.join(video_temp_dir, f"{group}.mp4")
|
|
48
|
-
video_writer = cv2.VideoWriter(video_save_path, fourcc, video_meta_data["fps"], (video_meta_data["width"], video_meta_data["height"]))
|
|
49
|
-
cap = cv2.VideoCapture(video_path)
|
|
50
|
-
batch_shapes = data[1].values.reshape(len(data[1]), -1)
|
|
51
|
-
for frm_cnt, frm_id in enumerate(range(start_frm, end_frm + 1)):
|
|
52
|
-
cap.set(1, int(frm_id))
|
|
53
|
-
ret, img = cap.read()
|
|
54
|
-
if ret:
|
|
55
|
-
img_cpy = img.copy()
|
|
56
|
-
if bg_opacity != 1.0:
|
|
57
|
-
opacity = 1 - bg_opacity
|
|
58
|
-
h, w, clr = img.shape[:3]
|
|
59
|
-
opacity_image = np.ones((h, w, clr), dtype=np.uint8) * int(255 * opacity)
|
|
60
|
-
img = cv2.addWeighted(img.astype(np.uint8), 1 - opacity, opacity_image.astype(np.uint8), opacity, 0)
|
|
61
|
-
for shape_cnt, shape in enumerate(batch_shapes[frm_cnt]):
|
|
62
|
-
shape_clr = colors[shape_cnt]
|
|
63
|
-
if intersection_clr is not None and shape is not None:
|
|
64
|
-
current_shapes = batch_shapes[frm_cnt]
|
|
65
|
-
for i in range(len(current_shapes)):
|
|
66
|
-
if i != shape_cnt and current_shapes[i] is not None and shape.intersects(current_shapes[i]):
|
|
67
|
-
shape_clr = intersection_clr
|
|
68
|
-
break
|
|
69
|
-
if isinstance(shape, Polygon):
|
|
70
|
-
img_cpy = cv2.fillPoly(img_cpy, [np.array(shape.exterior.coords).astype(np.int32)], color=shape_clr)
|
|
71
|
-
interior_coords = [np.array(interior.coords, dtype=np.int32).reshape((-1, 1, 2)) for interior in shape.interiors]
|
|
72
|
-
for interior in interior_coords:
|
|
73
|
-
img_cpy = cv2.fillPoly(img_cpy, [interior], color=(shape_clr[::-1]))
|
|
74
|
-
elif isinstance(shape, LineString):
|
|
75
|
-
img_cpy = cv2.fillPoly(img_cpy, [np.array(shape.coords, dtype=np.int32)], color=shape_clr)
|
|
76
|
-
elif isinstance(shape, MultiPolygon):
|
|
77
|
-
for polygon_cnt, polygon in enumerate(shape.geoms):
|
|
78
|
-
img_cpy = cv2.fillPoly(img_cpy, [np.array((polygon.convex_hull.exterior.coords), dtype=np.int32)], color=shape_clr)
|
|
79
|
-
elif isinstance(shape, MultiLineString):
|
|
80
|
-
for line_cnt, line in enumerate(shape.geoms):
|
|
81
|
-
img_cpy = cv2.fillPoly(img_cpy,[np.array(shape[line_cnt].coords, dtype=np.int32)], color=shape_clr)
|
|
82
|
-
elif isinstance(shape, Point):
|
|
83
|
-
arr = np.array((shape.coords)).astype(np.int32)
|
|
84
|
-
x, y = arr[0][0], arr[0][1]
|
|
85
|
-
img_cpy = cv2.circle(img_cpy,(x, y), circle_size, shape_clr, thickness)
|
|
86
|
-
if shape_opacity is not None:
|
|
87
|
-
img = cv2.addWeighted(img_cpy, shape_opacity, img, 1 - shape_opacity, 0, img)
|
|
88
|
-
else:
|
|
89
|
-
img = np.copy(img_cpy)
|
|
90
|
-
video_writer.write(img.astype(np.uint8))
|
|
91
|
-
if verbose:
|
|
92
|
-
print(f"Creating frame {frm_id} / {video_meta_data['frame_count']} (CPU core: {group}, video name: {video_meta_data['video_name']})")
|
|
93
|
-
else:
|
|
94
|
-
FrameRangeWarning(msg=f'Frame {frm_id} in video {video_meta_data["video_name"]} could not be read.')
|
|
95
|
-
pass
|
|
96
|
-
video_writer.release()
|
|
97
|
-
cap.release()
|
|
98
|
-
|
|
99
|
-
return group
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
class GeometryPlotter(ConfigReader, PlottingMixin):
|
|
103
|
-
"""
|
|
104
|
-
A class for creating overlay geometry visualization videos based on provided geometries and video name.
|
|
105
|
-
|
|
106
|
-
.. seealso::
|
|
107
|
-
To quickly create static geometries on a white background (useful for troubleshooting unexpected geometries), see :func:`simba.mixins.geometry_mixin.GeometryMixin.view_shapes`
|
|
108
|
-
and :func:`simba.mixins.geometry_mixin.GeometryMixin.geometry_video`
|
|
109
|
-
|
|
110
|
-
.. image:: _static/img/GeometryPlotter.gif
|
|
111
|
-
:width: 600
|
|
112
|
-
:align: center
|
|
113
|
-
|
|
114
|
-
.. video:: _static/img/GeometryPlotter_1.webm
|
|
115
|
-
:width: 900
|
|
116
|
-
:autoplay:
|
|
117
|
-
:loop:
|
|
118
|
-
|
|
119
|
-
.. video:: _static/img/GeometryPlotter_2.webm
|
|
120
|
-
:width: 600
|
|
121
|
-
:autoplay:
|
|
122
|
-
:loop:
|
|
123
|
-
|
|
124
|
-
:param List[List[Union[Polygon, LineString, MultiPolygon, MultiLineString, Point]]] geometries: List of lists of geometries for each frame. Each list contains as many entries as frames. Each list may represent a track or unique tracked object.
|
|
125
|
-
:param Union[str, os.PathLike] video_name: Name of the input video.
|
|
126
|
-
:param Optional[Union[str, os.PathLike]] config_path: Path to SimBA configuration file. Default: None.
|
|
127
|
-
:param Optional[int] core_cnt: Number of CPU cores to use for parallel processing. Default: -1 (all available cores).
|
|
128
|
-
:param Optional[Union[str, os.PathLike]] save_dir: Directory to save output videos. Default: None.
|
|
129
|
-
:param Optional[int] thickness: Thickness of geometry outlines in pixels. Default: None.
|
|
130
|
-
:param Optional[int] circle_size: Size of circles for Point geometries. Default: None.
|
|
131
|
-
:param Optional[float] bg_opacity: Background video opacity (0.0-1.0). Default: 1.0.
|
|
132
|
-
:param float shape_opacity: Shape fill opacity (0.0-1.0). Default: 0.3.
|
|
133
|
-
:param Optional[str] palette: Color palette name for geometries. Default: None. Provide either a `palette` name, or a list of `colors`. If both are passed, `palette` is used.
|
|
134
|
-
:param Optional[List[Union[str, Tuple[int, int, int]]]] colors: Custom colors for geometries. Default: None.
|
|
135
|
-
:param Optional[Tuple[int, int, int]] intersection_clr: Color for geometries that intersect other geometries. Default to None (which means intersecting geometries maintain the original color while intersecting.
|
|
136
|
-
:param Optional[bool] verbose: Print progress information. Default: True.
|
|
137
|
-
:raises InvalidInputError: If the provided geometries contain invalid data types or if neither palette nor colors are provided.
|
|
138
|
-
:raises CountError: If the number of shapes in the geometries does not match the number of frames in the video.
|
|
139
|
-
"""
|
|
140
|
-
|
|
141
|
-
def __init__(self,
|
|
142
|
-
geometries: List[List[Union[Polygon, LineString, MultiPolygon, MultiLineString, Point]]],
|
|
143
|
-
video_name: Union[str, os.PathLike],
|
|
144
|
-
config_path: Optional[Union[str, os.PathLike]] = None,
|
|
145
|
-
core_cnt: Optional[int] = -1,
|
|
146
|
-
save_dir: Optional[Union[str, os.PathLike]] = None,
|
|
147
|
-
thickness: Optional[int] = None,
|
|
148
|
-
circle_size: Optional[int] = None,
|
|
149
|
-
intersection_clr: Optional[Tuple[int, int, int]] = None,
|
|
150
|
-
bg_opacity: Optional[float] = 1,
|
|
151
|
-
shape_opacity: float = 0.3,
|
|
152
|
-
palette: Optional[str] = None,
|
|
153
|
-
colors: Optional[List[Union[str, Tuple[int, int, int]]]] = None,
|
|
154
|
-
verbose: Optional[bool] = True):
|
|
155
|
-
|
|
156
|
-
PlottingMixin.__init__(self)
|
|
157
|
-
check_instance(source=self.__class__.__name__, instance=geometries, accepted_types=list)
|
|
158
|
-
check_iterable_length(source=self.__class__.__name__, val=len(geometries), min=1)
|
|
159
|
-
if circle_size is not None:
|
|
160
|
-
check_int(name="circle_size", value=circle_size, min_value=1)
|
|
161
|
-
if thickness is not None:
|
|
162
|
-
check_int(name="thickness", value=thickness, min_value=1)
|
|
163
|
-
check_float(name="video_opacity", value=bg_opacity, min_value=0.0, max_value=1.0)
|
|
164
|
-
check_float(name="shape_opacity", value=shape_opacity, min_value=0.0, max_value=1.0)
|
|
165
|
-
check_valid_boolean(value=verbose, source='verbose', raise_error=True)
|
|
166
|
-
self.color_dict = get_color_dict()
|
|
167
|
-
check_int(name="CORE COUNT", value=core_cnt, min_value=-1, raise_error=True, unaccepted_vals=[0])
|
|
168
|
-
self.core_cnt = core_cnt
|
|
169
|
-
if palette is None and colors is None:
|
|
170
|
-
raise InvalidInputError(msg='Pass palette or colors', source=self.__class__.__name__)
|
|
171
|
-
if core_cnt == -1 or core_cnt > find_core_cnt()[0]:
|
|
172
|
-
self.core_cnt = find_core_cnt()[0]
|
|
173
|
-
if config_path is not None:
|
|
174
|
-
ConfigReader.__init__(self, config_path=config_path)
|
|
175
|
-
if os.path.isfile(video_name):
|
|
176
|
-
self.video_path = video_name
|
|
177
|
-
else:
|
|
178
|
-
if config_path is None:
|
|
179
|
-
raise InvalidInputError(msg=f'When providing a non-path video name, pass config_path')
|
|
180
|
-
self.video_path = find_video_of_file(video_dir=self.video_dir, filename=video_name, raise_error=True)
|
|
181
|
-
if intersection_clr is not None:
|
|
182
|
-
check_if_valid_rgb_tuple(data=intersection_clr, raise_error=True, source=f'{self.__class__.__name__} intersection_clr')
|
|
183
|
-
video_name = get_fn_ext(filepath=self.video_path)[1]
|
|
184
|
-
self.video_meta_data = get_video_meta_data(video_path=self.video_path)
|
|
185
|
-
self.shape_opacity = shape_opacity
|
|
186
|
-
if circle_size is None:
|
|
187
|
-
circle_size = self.get_optimal_circle_size(frame_size=(self.video_meta_data['width'], self.video_meta_data['height']), circle_frame_ratio=50)
|
|
188
|
-
if thickness is None:
|
|
189
|
-
thickness = circle_size
|
|
190
|
-
if palette is None:
|
|
191
|
-
self.colors = []
|
|
192
|
-
check_valid_lst(data=colors, source=f'{self.__class__.__name__} colors', valid_dtypes=(str, tuple), exact_len=len(geometries))
|
|
193
|
-
for clr in colors:
|
|
194
|
-
if isinstance(clr, str):
|
|
195
|
-
check_str(name=f'{self.__class__.__name__} colors', value=clr, options=list(self.color_dict.keys()))
|
|
196
|
-
else:
|
|
197
|
-
check_if_valid_rgb_tuple(data=clr)
|
|
198
|
-
self.colors.append(clr)
|
|
199
|
-
else:
|
|
200
|
-
colors = create_color_palettes(no_animals=1, map_size=len(geometries) + 1, cmaps=[palette])
|
|
201
|
-
self.colors = [x for xs in colors for x in xs]
|
|
202
|
-
for i in range(len(geometries)):
|
|
203
|
-
if len(geometries[i]) != self.video_meta_data[FRAME_COUNT]:
|
|
204
|
-
FrameRangeWarning(msg=f"Geometry {i+1} contains {len(geometries[i])} shapes but video has {self.video_meta_data[FRAME_COUNT]} frames")
|
|
205
|
-
self.geometries, self.video_name, self.thickness = geometries, video_name, thickness
|
|
206
|
-
if config_path is None:
|
|
207
|
-
check_if_dir_exists(in_dir=save_dir, source=f'{self.__class__.__name__} save_dir')
|
|
208
|
-
self.save_dir = save_dir
|
|
209
|
-
else:
|
|
210
|
-
self.save_dir = os.path.join(self.frames_output_dir, "geometry_visualization")
|
|
211
|
-
if not os.path.isdir(self.save_dir): os.makedirs(self.save_dir)
|
|
212
|
-
self.temp_dir = os.path.join(self.save_dir, video_name, "temp")
|
|
213
|
-
if os.path.isdir(self.temp_dir): remove_a_folder(folder_dir=self.temp_dir, ignore_errors=False)
|
|
214
|
-
os.makedirs(self.temp_dir)
|
|
215
|
-
self.save_path = os.path.join(self.save_dir, f'{video_name}.mp4')
|
|
216
|
-
self.verbose, self.bg_opacity, self.palette = verbose, bg_opacity, palette
|
|
217
|
-
self.circles_size, self.intersection_clr = circle_size, intersection_clr
|
|
218
|
-
|
|
219
|
-
def run(self):
|
|
220
|
-
video_timer = SimbaTimer(start=True)
|
|
221
|
-
data = pd.DataFrame(self.geometries).T
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
data_splits
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
#
|
|
257
|
-
#
|
|
258
|
-
#
|
|
259
|
-
#
|
|
260
|
-
#
|
|
261
|
-
#
|
|
262
|
-
#
|
|
263
|
-
#
|
|
264
|
-
#
|
|
265
|
-
#
|
|
266
|
-
#
|
|
267
|
-
#
|
|
268
|
-
#
|
|
269
|
-
#
|
|
270
|
-
#
|
|
271
|
-
# plotter.run()
|
|
272
|
-
# #max_frm = 9000
|
|
1
|
+
import functools
|
|
2
|
+
import multiprocessing
|
|
3
|
+
import os
|
|
4
|
+
from typing import List, Optional, Tuple, Union
|
|
5
|
+
|
|
6
|
+
import cv2
|
|
7
|
+
import numpy as np
|
|
8
|
+
import pandas as pd
|
|
9
|
+
from shapely.geometry import (LineString, MultiLineString, MultiPolygon, Point,
|
|
10
|
+
Polygon)
|
|
11
|
+
|
|
12
|
+
from simba.mixins.config_reader import ConfigReader
|
|
13
|
+
from simba.mixins.plotting_mixin import PlottingMixin
|
|
14
|
+
from simba.utils.checks import (check_float, check_if_dir_exists,
|
|
15
|
+
check_if_valid_rgb_tuple, check_instance,
|
|
16
|
+
check_int, check_iterable_length, check_str,
|
|
17
|
+
check_valid_boolean, check_valid_lst)
|
|
18
|
+
from simba.utils.data import create_color_palettes, terminate_cpu_pool
|
|
19
|
+
from simba.utils.enums import Defaults, Formats
|
|
20
|
+
from simba.utils.errors import InvalidInputError
|
|
21
|
+
from simba.utils.lookups import get_color_dict
|
|
22
|
+
from simba.utils.printing import SimbaTimer, stdout_success
|
|
23
|
+
from simba.utils.read_write import (concatenate_videos_in_folder,
|
|
24
|
+
find_core_cnt, find_video_of_file,
|
|
25
|
+
get_fn_ext, get_video_meta_data,
|
|
26
|
+
remove_a_folder)
|
|
27
|
+
from simba.utils.warnings import FrameRangeWarning
|
|
28
|
+
|
|
29
|
+
ACCEPTED_TYPES = [Polygon, LineString, MultiPolygon, MultiLineString, Point]
|
|
30
|
+
FRAME_COUNT = "frame_count"
|
|
31
|
+
|
|
32
|
+
def geometry_visualizer(data: Tuple[int, pd.DataFrame],
|
|
33
|
+
video_path: Union[str, os.PathLike],
|
|
34
|
+
video_temp_dir: Union[str, os.PathLike],
|
|
35
|
+
video_meta_data: dict,
|
|
36
|
+
thickness: int,
|
|
37
|
+
verbose: bool,
|
|
38
|
+
intersection_clr: Optional[Tuple[int, int, int]],
|
|
39
|
+
bg_opacity: float,
|
|
40
|
+
colors: list,
|
|
41
|
+
circle_size: int,
|
|
42
|
+
shape_opacity: float):
|
|
43
|
+
|
|
44
|
+
group, idx = int(data[0]), data[1].index.tolist()
|
|
45
|
+
start_frm, end_frm = idx[0], idx[-1]
|
|
46
|
+
fourcc = cv2.VideoWriter_fourcc(*Formats.MP4_CODEC.value)
|
|
47
|
+
video_save_path = os.path.join(video_temp_dir, f"{group}.mp4")
|
|
48
|
+
video_writer = cv2.VideoWriter(video_save_path, fourcc, video_meta_data["fps"], (video_meta_data["width"], video_meta_data["height"]))
|
|
49
|
+
cap = cv2.VideoCapture(video_path)
|
|
50
|
+
batch_shapes = data[1].values.reshape(len(data[1]), -1)
|
|
51
|
+
for frm_cnt, frm_id in enumerate(range(start_frm, end_frm + 1)):
|
|
52
|
+
cap.set(1, int(frm_id))
|
|
53
|
+
ret, img = cap.read()
|
|
54
|
+
if ret:
|
|
55
|
+
img_cpy = img.copy()
|
|
56
|
+
if bg_opacity != 1.0:
|
|
57
|
+
opacity = 1 - bg_opacity
|
|
58
|
+
h, w, clr = img.shape[:3]
|
|
59
|
+
opacity_image = np.ones((h, w, clr), dtype=np.uint8) * int(255 * opacity)
|
|
60
|
+
img = cv2.addWeighted(img.astype(np.uint8), 1 - opacity, opacity_image.astype(np.uint8), opacity, 0)
|
|
61
|
+
for shape_cnt, shape in enumerate(batch_shapes[frm_cnt]):
|
|
62
|
+
shape_clr = colors[shape_cnt]
|
|
63
|
+
if intersection_clr is not None and shape is not None:
|
|
64
|
+
current_shapes = batch_shapes[frm_cnt]
|
|
65
|
+
for i in range(len(current_shapes)):
|
|
66
|
+
if i != shape_cnt and current_shapes[i] is not None and shape.intersects(current_shapes[i]):
|
|
67
|
+
shape_clr = intersection_clr
|
|
68
|
+
break
|
|
69
|
+
if isinstance(shape, Polygon):
|
|
70
|
+
img_cpy = cv2.fillPoly(img_cpy, [np.array(shape.exterior.coords).astype(np.int32)], color=shape_clr)
|
|
71
|
+
interior_coords = [np.array(interior.coords, dtype=np.int32).reshape((-1, 1, 2)) for interior in shape.interiors]
|
|
72
|
+
for interior in interior_coords:
|
|
73
|
+
img_cpy = cv2.fillPoly(img_cpy, [interior], color=(shape_clr[::-1]))
|
|
74
|
+
elif isinstance(shape, LineString):
|
|
75
|
+
img_cpy = cv2.fillPoly(img_cpy, [np.array(shape.coords, dtype=np.int32)], color=shape_clr)
|
|
76
|
+
elif isinstance(shape, MultiPolygon):
|
|
77
|
+
for polygon_cnt, polygon in enumerate(shape.geoms):
|
|
78
|
+
img_cpy = cv2.fillPoly(img_cpy, [np.array((polygon.convex_hull.exterior.coords), dtype=np.int32)], color=shape_clr)
|
|
79
|
+
elif isinstance(shape, MultiLineString):
|
|
80
|
+
for line_cnt, line in enumerate(shape.geoms):
|
|
81
|
+
img_cpy = cv2.fillPoly(img_cpy,[np.array(shape[line_cnt].coords, dtype=np.int32)], color=shape_clr)
|
|
82
|
+
elif isinstance(shape, Point):
|
|
83
|
+
arr = np.array((shape.coords)).astype(np.int32)
|
|
84
|
+
x, y = arr[0][0], arr[0][1]
|
|
85
|
+
img_cpy = cv2.circle(img_cpy,(x, y), circle_size, shape_clr, thickness)
|
|
86
|
+
if shape_opacity is not None:
|
|
87
|
+
img = cv2.addWeighted(img_cpy, shape_opacity, img, 1 - shape_opacity, 0, img)
|
|
88
|
+
else:
|
|
89
|
+
img = np.copy(img_cpy)
|
|
90
|
+
video_writer.write(img.astype(np.uint8))
|
|
91
|
+
if verbose:
|
|
92
|
+
print(f"Creating frame {frm_id} / {video_meta_data['frame_count']} (CPU core: {group}, video name: {video_meta_data['video_name']})")
|
|
93
|
+
else:
|
|
94
|
+
FrameRangeWarning(msg=f'Frame {frm_id} in video {video_meta_data["video_name"]} could not be read.')
|
|
95
|
+
pass
|
|
96
|
+
video_writer.release()
|
|
97
|
+
cap.release()
|
|
98
|
+
|
|
99
|
+
return group
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class GeometryPlotter(ConfigReader, PlottingMixin):
|
|
103
|
+
"""
|
|
104
|
+
A class for creating overlay geometry visualization videos based on provided geometries and video name.
|
|
105
|
+
|
|
106
|
+
.. seealso::
|
|
107
|
+
To quickly create static geometries on a white background (useful for troubleshooting unexpected geometries), see :func:`simba.mixins.geometry_mixin.GeometryMixin.view_shapes`
|
|
108
|
+
and :func:`simba.mixins.geometry_mixin.GeometryMixin.geometry_video`
|
|
109
|
+
|
|
110
|
+
.. image:: _static/img/GeometryPlotter.gif
|
|
111
|
+
:width: 600
|
|
112
|
+
:align: center
|
|
113
|
+
|
|
114
|
+
.. video:: _static/img/GeometryPlotter_1.webm
|
|
115
|
+
:width: 900
|
|
116
|
+
:autoplay:
|
|
117
|
+
:loop:
|
|
118
|
+
|
|
119
|
+
.. video:: _static/img/GeometryPlotter_2.webm
|
|
120
|
+
:width: 600
|
|
121
|
+
:autoplay:
|
|
122
|
+
:loop:
|
|
123
|
+
|
|
124
|
+
:param List[List[Union[Polygon, LineString, MultiPolygon, MultiLineString, Point]]] geometries: List of lists of geometries for each frame. Each list contains as many entries as frames. Each list may represent a track or unique tracked object.
|
|
125
|
+
:param Union[str, os.PathLike] video_name: Name of the input video.
|
|
126
|
+
:param Optional[Union[str, os.PathLike]] config_path: Path to SimBA configuration file. Default: None.
|
|
127
|
+
:param Optional[int] core_cnt: Number of CPU cores to use for parallel processing. Default: -1 (all available cores).
|
|
128
|
+
:param Optional[Union[str, os.PathLike]] save_dir: Directory to save output videos. Default: None.
|
|
129
|
+
:param Optional[int] thickness: Thickness of geometry outlines in pixels. Default: None.
|
|
130
|
+
:param Optional[int] circle_size: Size of circles for Point geometries. Default: None.
|
|
131
|
+
:param Optional[float] bg_opacity: Background video opacity (0.0-1.0). Default: 1.0.
|
|
132
|
+
:param float shape_opacity: Shape fill opacity (0.0-1.0). Default: 0.3.
|
|
133
|
+
:param Optional[str] palette: Color palette name for geometries. Default: None. Provide either a `palette` name, or a list of `colors`. If both are passed, `palette` is used.
|
|
134
|
+
:param Optional[List[Union[str, Tuple[int, int, int]]]] colors: Custom colors for geometries. Default: None.
|
|
135
|
+
:param Optional[Tuple[int, int, int]] intersection_clr: Color for geometries that intersect other geometries. Default to None (which means intersecting geometries maintain the original color while intersecting.
|
|
136
|
+
:param Optional[bool] verbose: Print progress information. Default: True.
|
|
137
|
+
:raises InvalidInputError: If the provided geometries contain invalid data types or if neither palette nor colors are provided.
|
|
138
|
+
:raises CountError: If the number of shapes in the geometries does not match the number of frames in the video.
|
|
139
|
+
"""
|
|
140
|
+
|
|
141
|
+
def __init__(self,
|
|
142
|
+
geometries: List[List[Union[Polygon, LineString, MultiPolygon, MultiLineString, Point]]],
|
|
143
|
+
video_name: Union[str, os.PathLike],
|
|
144
|
+
config_path: Optional[Union[str, os.PathLike]] = None,
|
|
145
|
+
core_cnt: Optional[int] = -1,
|
|
146
|
+
save_dir: Optional[Union[str, os.PathLike]] = None,
|
|
147
|
+
thickness: Optional[int] = None,
|
|
148
|
+
circle_size: Optional[int] = None,
|
|
149
|
+
intersection_clr: Optional[Tuple[int, int, int]] = None,
|
|
150
|
+
bg_opacity: Optional[float] = 1,
|
|
151
|
+
shape_opacity: float = 0.3,
|
|
152
|
+
palette: Optional[str] = None,
|
|
153
|
+
colors: Optional[List[Union[str, Tuple[int, int, int]]]] = None,
|
|
154
|
+
verbose: Optional[bool] = True):
|
|
155
|
+
|
|
156
|
+
PlottingMixin.__init__(self)
|
|
157
|
+
check_instance(source=self.__class__.__name__, instance=geometries, accepted_types=list)
|
|
158
|
+
check_iterable_length(source=self.__class__.__name__, val=len(geometries), min=1)
|
|
159
|
+
if circle_size is not None:
|
|
160
|
+
check_int(name="circle_size", value=circle_size, min_value=1)
|
|
161
|
+
if thickness is not None:
|
|
162
|
+
check_int(name="thickness", value=thickness, min_value=1)
|
|
163
|
+
check_float(name="video_opacity", value=bg_opacity, min_value=0.0, max_value=1.0)
|
|
164
|
+
check_float(name="shape_opacity", value=shape_opacity, min_value=0.0, max_value=1.0)
|
|
165
|
+
check_valid_boolean(value=verbose, source='verbose', raise_error=True)
|
|
166
|
+
self.color_dict = get_color_dict()
|
|
167
|
+
check_int(name="CORE COUNT", value=core_cnt, min_value=-1, raise_error=True, unaccepted_vals=[0])
|
|
168
|
+
self.core_cnt = core_cnt
|
|
169
|
+
if palette is None and colors is None:
|
|
170
|
+
raise InvalidInputError(msg='Pass palette or colors', source=self.__class__.__name__)
|
|
171
|
+
if core_cnt == -1 or core_cnt > find_core_cnt()[0]:
|
|
172
|
+
self.core_cnt = find_core_cnt()[0]
|
|
173
|
+
if config_path is not None:
|
|
174
|
+
ConfigReader.__init__(self, config_path=config_path)
|
|
175
|
+
if os.path.isfile(video_name):
|
|
176
|
+
self.video_path = video_name
|
|
177
|
+
else:
|
|
178
|
+
if config_path is None:
|
|
179
|
+
raise InvalidInputError(msg=f'When providing a non-path video name, pass config_path')
|
|
180
|
+
self.video_path = find_video_of_file(video_dir=self.video_dir, filename=video_name, raise_error=True)
|
|
181
|
+
if intersection_clr is not None:
|
|
182
|
+
check_if_valid_rgb_tuple(data=intersection_clr, raise_error=True, source=f'{self.__class__.__name__} intersection_clr')
|
|
183
|
+
video_name = get_fn_ext(filepath=self.video_path)[1]
|
|
184
|
+
self.video_meta_data = get_video_meta_data(video_path=self.video_path)
|
|
185
|
+
self.shape_opacity = shape_opacity
|
|
186
|
+
if circle_size is None:
|
|
187
|
+
circle_size = self.get_optimal_circle_size(frame_size=(self.video_meta_data['width'], self.video_meta_data['height']), circle_frame_ratio=50)
|
|
188
|
+
if thickness is None:
|
|
189
|
+
thickness = circle_size
|
|
190
|
+
if palette is None:
|
|
191
|
+
self.colors = []
|
|
192
|
+
check_valid_lst(data=colors, source=f'{self.__class__.__name__} colors', valid_dtypes=(str, tuple), exact_len=len(geometries))
|
|
193
|
+
for clr in colors:
|
|
194
|
+
if isinstance(clr, str):
|
|
195
|
+
check_str(name=f'{self.__class__.__name__} colors', value=clr, options=list(self.color_dict.keys()))
|
|
196
|
+
else:
|
|
197
|
+
check_if_valid_rgb_tuple(data=clr)
|
|
198
|
+
self.colors.append(clr)
|
|
199
|
+
else:
|
|
200
|
+
colors = create_color_palettes(no_animals=1, map_size=len(geometries) + 1, cmaps=[palette])
|
|
201
|
+
self.colors = [x for xs in colors for x in xs]
|
|
202
|
+
for i in range(len(geometries)):
|
|
203
|
+
if len(geometries[i]) != self.video_meta_data[FRAME_COUNT]:
|
|
204
|
+
FrameRangeWarning(msg=f"Geometry {i+1} contains {len(geometries[i])} shapes but video has {self.video_meta_data[FRAME_COUNT]} frames")
|
|
205
|
+
self.geometries, self.video_name, self.thickness = geometries, video_name, thickness
|
|
206
|
+
if config_path is None:
|
|
207
|
+
check_if_dir_exists(in_dir=save_dir, source=f'{self.__class__.__name__} save_dir')
|
|
208
|
+
self.save_dir = save_dir
|
|
209
|
+
else:
|
|
210
|
+
self.save_dir = os.path.join(self.frames_output_dir, "geometry_visualization")
|
|
211
|
+
if not os.path.isdir(self.save_dir): os.makedirs(self.save_dir)
|
|
212
|
+
self.temp_dir = os.path.join(self.save_dir, video_name, "temp")
|
|
213
|
+
if os.path.isdir(self.temp_dir): remove_a_folder(folder_dir=self.temp_dir, ignore_errors=False)
|
|
214
|
+
os.makedirs(self.temp_dir)
|
|
215
|
+
self.save_path = os.path.join(self.save_dir, f'{video_name}.mp4')
|
|
216
|
+
self.verbose, self.bg_opacity, self.palette = verbose, bg_opacity, palette
|
|
217
|
+
self.circles_size, self.intersection_clr = circle_size, intersection_clr
|
|
218
|
+
|
|
219
|
+
def run(self):
|
|
220
|
+
video_timer = SimbaTimer(start=True)
|
|
221
|
+
data = pd.DataFrame(self.geometries).T
|
|
222
|
+
data = np.array_split(data, self.core_cnt)
|
|
223
|
+
data_splits = []
|
|
224
|
+
for i in range(len(data)): data_splits.append((i, data[i]))
|
|
225
|
+
del data
|
|
226
|
+
|
|
227
|
+
with multiprocessing.Pool(self.core_cnt, maxtasksperchild=Defaults.LARGE_MAX_TASK_PER_CHILD.value) as pool:
|
|
228
|
+
constants = functools.partial(geometry_visualizer,
|
|
229
|
+
video_path=self.video_path,
|
|
230
|
+
video_temp_dir=self.temp_dir,
|
|
231
|
+
video_meta_data=self.video_meta_data,
|
|
232
|
+
thickness=self.thickness,
|
|
233
|
+
verbose=self.verbose,
|
|
234
|
+
bg_opacity=self.bg_opacity,
|
|
235
|
+
intersection_clr=self.intersection_clr,
|
|
236
|
+
colors=self.colors,
|
|
237
|
+
circle_size=self.circles_size,
|
|
238
|
+
shape_opacity=self.shape_opacity)
|
|
239
|
+
for cnt, result in enumerate(pool.imap(constants, data_splits, chunksize=1)):
|
|
240
|
+
print(f"Section {result}/{len(data_splits)} complete...")
|
|
241
|
+
|
|
242
|
+
terminate_cpu_pool(pool=pool, force=False)
|
|
243
|
+
print(f"Joining {self.video_name} geometry video...")
|
|
244
|
+
concatenate_videos_in_folder(in_folder=self.temp_dir, save_path=self.save_path, remove_splits=True)
|
|
245
|
+
video_timer.stop_timer()
|
|
246
|
+
stdout_success(msg=f"Geometry video {self.save_path} complete!", elapsed_time=video_timer.elapsed_time_str, source=self.__class__.__name__)
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
# if __name__ == '__main__':
|
|
255
|
+
# from simba.utils.read_write import read_pickle
|
|
256
|
+
# VIDEO_PATH = r"D:\ares\data\termite_2\termite.mp4"
|
|
257
|
+
# DATA_PATH = r"D:\ares\data\termite_2\termite_2_geometries.pickle"
|
|
258
|
+
# data = read_pickle(data_path=DATA_PATH)
|
|
259
|
+
#
|
|
260
|
+
# get_video_meta_data(video_path=VIDEO_PATH)
|
|
261
|
+
#
|
|
262
|
+
# geos = []
|
|
263
|
+
#
|
|
264
|
+
# for k, v in data.items():
|
|
265
|
+
# geos.append(list(v.values()))
|
|
266
|
+
# #geos.append([subdict[i] for subdict in data.values()])
|
|
267
|
+
#
|
|
268
|
+
# plotter = GeometryPlotter(geometries=geos, video_name=VIDEO_PATH, save_dir=r"D:\ares\data\termite_2\video", palette='Set1', core_cnt=32, bg_opacity=1)
|
|
269
|
+
# plotter.run()
|
|
270
|
+
# #max_frm = 9000
|
|
@@ -8,8 +8,6 @@ from typing import List, Union
|
|
|
8
8
|
|
|
9
9
|
import cv2
|
|
10
10
|
import numpy as np
|
|
11
|
-
import pandas as pd
|
|
12
|
-
from numba import jit, prange
|
|
13
11
|
|
|
14
12
|
import simba.mixins.plotting_mixin
|
|
15
13
|
from simba.mixins.config_reader import ConfigReader
|
|
@@ -19,6 +17,7 @@ from simba.utils.checks import (
|
|
|
19
17
|
check_all_file_names_are_represented_in_video_log,
|
|
20
18
|
check_filepaths_in_iterable_exist, check_int, check_str,
|
|
21
19
|
check_valid_boolean, check_valid_dataframe, check_valid_dict)
|
|
20
|
+
from simba.utils.data import terminate_cpu_pool
|
|
22
21
|
from simba.utils.enums import Formats
|
|
23
22
|
from simba.utils.errors import InvalidInputError, NoSpecifiedOutputError
|
|
24
23
|
from simba.utils.printing import SimbaTimer, stdout_success
|
|
@@ -99,14 +98,14 @@ class HeatMapperClfMultiprocess(ConfigReader, PlottingMixin):
|
|
|
99
98
|
|
|
100
99
|
|
|
101
100
|
:example II:
|
|
102
|
-
>>> test = HeatMapperClfMultiprocess(config_path=r"C
|
|
101
|
+
>>> test = HeatMapperClfMultiprocess(config_path=r"C:/troubleshooting/RAT_NOR/project_folder/project_config.ini",
|
|
103
102
|
>>> style_attr = {'palette': 'jet', 'shading': 'gouraud', 'bin_size': 50, 'max_scale': 'auto'},
|
|
104
103
|
>>> final_img_setting=True,
|
|
105
104
|
>>> video_setting=True,
|
|
106
105
|
>>> frame_setting=True,
|
|
107
106
|
>>> bodypart='Ear_left',
|
|
108
107
|
>>> clf_name='straub_tail',
|
|
109
|
-
>>> data_paths=[r"C
|
|
108
|
+
>>> data_paths=[r"C:/troubleshooting/RAT_NOR/project_folder/csv/test/2022-06-20_NOB_DOT_4.csv"])
|
|
110
109
|
>>> test.run()
|
|
111
110
|
"""
|
|
112
111
|
|
|
@@ -215,8 +214,7 @@ class HeatMapperClfMultiprocess(ConfigReader, PlottingMixin):
|
|
|
215
214
|
|
|
216
215
|
for cnt, batch in enumerate(pool.imap(constants, frm_per_core_w_batch, chunksize=self.multiprocess_chunksize)):
|
|
217
216
|
print(f'Batch core {batch+1}/{self.core_cnt} complete (Video {self.video_name})... ')
|
|
218
|
-
pool
|
|
219
|
-
pool.join()
|
|
217
|
+
terminate_cpu_pool(pool=pool, force=False)
|
|
220
218
|
|
|
221
219
|
if self.video_setting:
|
|
222
220
|
print(f"Joining {self.video_name} multiprocessed video...")
|
|
@@ -15,6 +15,7 @@ from simba.utils.checks import (
|
|
|
15
15
|
check_all_file_names_are_represented_in_video_log,
|
|
16
16
|
check_file_exist_and_readable, check_filepaths_in_iterable_exist,
|
|
17
17
|
check_float, check_if_keys_exist_in_dict, check_int, check_valid_lst)
|
|
18
|
+
from simba.utils.data import terminate_cpu_pool
|
|
18
19
|
from simba.utils.enums import Defaults, Formats, TagNames
|
|
19
20
|
from simba.utils.errors import NoSpecifiedOutputError
|
|
20
21
|
from simba.utils.printing import SimbaTimer, log_event, stdout_success
|
|
@@ -211,8 +212,7 @@ class HeatMapperLocationMultiprocess(ConfigReader, PlottingMixin):
|
|
|
211
212
|
make_location_heatmap_plot=self.make_location_heatmap_plot)
|
|
212
213
|
for cnt, result in enumerate(pool.imap(constants,frame_arrays,chunksize=self.multiprocess_chunksize)):
|
|
213
214
|
print(f'Batch {result}/{self.core_cnt} complete... Video: {self.video_name} ({file_cnt+1}/{len(self.data_paths)})')
|
|
214
|
-
pool
|
|
215
|
-
pool.join()
|
|
215
|
+
terminate_cpu_pool(pool=pool, force=False)
|
|
216
216
|
|
|
217
217
|
if self.video_setting:
|
|
218
218
|
print(f"Joining {self.video_name} multiprocessed heatmap location video...")
|
|
@@ -13,6 +13,7 @@ from simba.data_processors.light_dark_box_analyzer import LightDarkBoxAnalyzer
|
|
|
13
13
|
from simba.mixins.plotting_mixin import PlottingMixin
|
|
14
14
|
from simba.utils.checks import (check_float, check_if_dir_exists, check_str,
|
|
15
15
|
check_video_and_data_frm_count_align)
|
|
16
|
+
from simba.utils.data import terminate_cpu_pool
|
|
16
17
|
from simba.utils.enums import Defaults, Formats, Options, TextOptions
|
|
17
18
|
from simba.utils.errors import NoDataError
|
|
18
19
|
from simba.utils.printing import SimbaTimer, stdout_success
|
|
@@ -158,8 +159,7 @@ class LightDarkBoxPlotter():
|
|
|
158
159
|
threshold=self.threshold)
|
|
159
160
|
for cnt, result in enumerate(pool.imap(constants, pose_data, chunksize=1)):
|
|
160
161
|
print(f"Section {result}/{len(pose_data)} complete...")
|
|
161
|
-
pool
|
|
162
|
-
pool.join()
|
|
162
|
+
terminate_cpu_pool(pool=pool, force=False)
|
|
163
163
|
concatenate_videos_in_folder(in_folder=self.temp_dir, save_path=self.video_save_path, gpu=False)
|
|
164
164
|
video_timer.stop_timer()
|
|
165
165
|
print(f"Video {self.video_save_path} complete (elapsed time: {video_timer.elapsed_time_str}s)...")
|