simba-uw-tf-dev 4.6.3__py3-none-any.whl → 4.6.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.
- simba/assets/lookups/tooptips.json +6 -1
- simba/data_processors/cuda/geometry.py +45 -27
- simba/data_processors/cuda/image.py +1620 -1600
- simba/data_processors/cuda/statistics.py +17 -9
- simba/data_processors/egocentric_aligner.py +24 -6
- simba/data_processors/kleinberg_calculator.py +61 -29
- simba/feature_extractors/feature_subsets.py +12 -5
- simba/feature_extractors/straub_tail_analyzer.py +0 -2
- simba/mixins/statistics_mixin.py +9 -2
- simba/plotting/gantt_creator_mp.py +7 -5
- simba/plotting/pose_plotter_mp.py +7 -3
- simba/plotting/roi_plotter_mp.py +4 -3
- simba/plotting/yolo_pose_track_visualizer.py +3 -2
- simba/plotting/yolo_pose_visualizer.py +5 -4
- simba/sandbox/analyze_runtimes.py +30 -0
- simba/sandbox/cuda/egocentric_rotator.py +374 -374
- 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/ui/pop_ups/fsttc_pop_up.py +27 -25
- simba/ui/pop_ups/kleinberg_pop_up.py +39 -40
- simba/ui/tkinter_functions.py +3 -0
- simba/utils/data.py +0 -1
- simba/utils/errors.py +441 -440
- simba/utils/lookups.py +1203 -1203
- simba/utils/printing.py +124 -125
- simba/utils/read_write.py +43 -14
- simba/video_processors/egocentric_video_rotator.py +41 -36
- simba/video_processors/video_processing.py +5247 -5233
- simba/video_processors/videos_to_frames.py +41 -31
- {simba_uw_tf_dev-4.6.3.dist-info → simba_uw_tf_dev-4.6.6.dist-info}/METADATA +2 -2
- {simba_uw_tf_dev-4.6.3.dist-info → simba_uw_tf_dev-4.6.6.dist-info}/RECORD +41 -32
- {simba_uw_tf_dev-4.6.3.dist-info → simba_uw_tf_dev-4.6.6.dist-info}/LICENSE +0 -0
- {simba_uw_tf_dev-4.6.3.dist-info → simba_uw_tf_dev-4.6.6.dist-info}/WHEEL +0 -0
- {simba_uw_tf_dev-4.6.3.dist-info → simba_uw_tf_dev-4.6.6.dist-info}/entry_points.txt +0 -0
- {simba_uw_tf_dev-4.6.3.dist-info → simba_uw_tf_dev-4.6.6.dist-info}/top_level.txt +0 -0
|
@@ -5,6 +5,8 @@ import math
|
|
|
5
5
|
from itertools import combinations
|
|
6
6
|
from typing import Optional, Tuple
|
|
7
7
|
|
|
8
|
+
from simba.utils.printing import SimbaTimer
|
|
9
|
+
|
|
8
10
|
try:
|
|
9
11
|
from typing import Literal
|
|
10
12
|
except:
|
|
@@ -20,11 +22,11 @@ from simba.utils.warnings import GPUToolsWarning
|
|
|
20
22
|
try:
|
|
21
23
|
import cupy as cp
|
|
22
24
|
from cuml.metrics import kl_divergence as kl_divergence_gpu
|
|
23
|
-
from cuml.metrics.cluster.adjusted_rand_index import adjusted_rand_score
|
|
24
|
-
from cuml.metrics.cluster.silhouette_score import cython_silhouette_score
|
|
25
|
+
#from cuml.metrics.cluster.adjusted_rand_index import adjusted_rand_score
|
|
26
|
+
#from cuml.metrics.cluster.silhouette_score import cython_silhouette_score
|
|
25
27
|
from cupyx.scipy.spatial.distance import cdist
|
|
26
|
-
except:
|
|
27
|
-
GPUToolsWarning(msg='GPU tools not detected, reverting to CPU')
|
|
28
|
+
except Exception as e:
|
|
29
|
+
GPUToolsWarning(msg=f'GPU tools not detected, reverting to CPU: {e.args}')
|
|
28
30
|
import numpy as cp
|
|
29
31
|
from scipy.spatial.distance import cdist
|
|
30
32
|
from scipy.stats import entropy as kl_divergence_gpu
|
|
@@ -227,7 +229,6 @@ def get_euclidean_distance_cupy(x: np.ndarray,
|
|
|
227
229
|
using CuPy for GPU acceleration. The computation is performed in batches to handle large
|
|
228
230
|
datasets efficiently.
|
|
229
231
|
|
|
230
|
-
|
|
231
232
|
.. seealso::
|
|
232
233
|
For CPU function see :func:`~simba.mixins.feature_extraction_mixin.FeatureExtractionMixin.framewise_euclidean_distance`.
|
|
233
234
|
For CUDA JIT function see :func:`~simba.data_processors.cuda.statistics.get_euclidean_distance_cuda`.
|
|
@@ -834,8 +835,11 @@ def i_index(x: np.ndarray, y: np.ndarray):
|
|
|
834
835
|
return i_idx
|
|
835
836
|
|
|
836
837
|
|
|
837
|
-
def kullback_leibler_divergence_gpu(x: np.ndarray,
|
|
838
|
-
|
|
838
|
+
def kullback_leibler_divergence_gpu(x: np.ndarray,
|
|
839
|
+
y: np.ndarray,
|
|
840
|
+
fill_value: int = 1,
|
|
841
|
+
bucket_method: Literal["fd", "doane", "auto", "scott", "stone", "rice", "sturges", "sqrt"] = "scott",
|
|
842
|
+
verbose: bool = False) -> float:
|
|
839
843
|
"""
|
|
840
844
|
Compute Kullback-Leibler divergence between two distributions.
|
|
841
845
|
|
|
@@ -847,7 +851,6 @@ def kullback_leibler_divergence_gpu(x: np.ndarray, y: np.ndarray, fill_value: in
|
|
|
847
851
|
.. seealso::
|
|
848
852
|
For CPU implementation, see :func:`simba.mixins.statistics_mixin.Statistics.kullback_leibler_divergence`.
|
|
849
853
|
|
|
850
|
-
|
|
851
854
|
:param ndarray x: First 1d array representing feature values.
|
|
852
855
|
:param ndarray y: Second 1d array representing feature values.
|
|
853
856
|
:param Optional[int] fill_value: Optional pseudo-value to use to fill empty buckets in ``y`` histogram
|
|
@@ -860,13 +863,18 @@ def kullback_leibler_divergence_gpu(x: np.ndarray, y: np.ndarray, fill_value: in
|
|
|
860
863
|
>>> kl = kullback_leibler_divergence_gpu(x=x, y=y)
|
|
861
864
|
"""
|
|
862
865
|
|
|
866
|
+
timer = SimbaTimer(start=True)
|
|
867
|
+
|
|
863
868
|
bin_width, bin_count = bucket_data(data=x, method=bucket_method)
|
|
864
869
|
r = np.array([np.min(x), np.max(x)])
|
|
865
870
|
x_hist = Statistics._hist_1d(data=x, bin_count=bin_count, range=r)
|
|
866
871
|
y_hist = Statistics._hist_1d(data=y, bin_count=bin_count, range=r)
|
|
867
872
|
y_hist[y_hist == 0] = fill_value
|
|
868
873
|
x_hist, y_hist = x_hist / np.sum(x_hist), y_hist / np.sum(y_hist)
|
|
869
|
-
|
|
874
|
+
r = kl_divergence_gpu(P=x_hist.astype(np.float32), Q=y_hist.astype(np.float32), convert_dtype=False)
|
|
875
|
+
timer.stop_timer()
|
|
876
|
+
if verbose: print(f'KL divergence performed on {x.shape[0]} observations (elapsed time: {timer.elapsed_time_str}s)')
|
|
877
|
+
return r
|
|
870
878
|
|
|
871
879
|
|
|
872
880
|
@cuda.jit()
|
|
@@ -7,7 +7,8 @@ import pandas as pd
|
|
|
7
7
|
from simba.utils.checks import (check_if_dir_exists, check_if_valid_rgb_tuple,
|
|
8
8
|
check_int, check_str, check_valid_boolean,
|
|
9
9
|
check_valid_dataframe, check_valid_tuple)
|
|
10
|
-
from simba.utils.data import egocentrically_align_pose_numba
|
|
10
|
+
from simba.utils.data import (egocentrically_align_pose_numba, get_cpu_pool,
|
|
11
|
+
terminate_cpu_pool)
|
|
11
12
|
from simba.utils.enums import Formats, Options
|
|
12
13
|
from simba.utils.errors import InvalidInputError
|
|
13
14
|
from simba.utils.printing import SimbaTimer, stdout_success
|
|
@@ -73,7 +74,7 @@ class EgocentricalAligner():
|
|
|
73
74
|
check_str(name=f'{self.__class__.__name__} anchor_1', value=anchor_1, allow_blank=False)
|
|
74
75
|
check_str(name=f'{self.__class__.__name__} anchor_2', value=anchor_2, allow_blank=False)
|
|
75
76
|
check_int(name=f'{self.__class__.__name__} core_cnt', value=core_cnt, min_value=-1, max_value=find_core_cnt()[0], unaccepted_vals=[0])
|
|
76
|
-
if core_cnt == -1
|
|
77
|
+
self.core_cnt = find_core_cnt()[0] if core_cnt == -1 or core_cnt > find_core_cnt()[0] else core_cnt
|
|
77
78
|
check_int(name=f'{self.__class__.__name__} direction', value=direction, min_value=0, max_value=360)
|
|
78
79
|
if isinstance(anchor_location, tuple):
|
|
79
80
|
check_valid_tuple(x=anchor_location, source=f'{self.__class__.__name__} anchor_location', accepted_lengths=(2,), valid_dtypes=(int,))
|
|
@@ -98,6 +99,7 @@ class EgocentricalAligner():
|
|
|
98
99
|
|
|
99
100
|
def run(self):
|
|
100
101
|
timer = SimbaTimer(start=True)
|
|
102
|
+
self.pool = None if not self.rotate_video else get_cpu_pool(core_cnt=self.core_cnt, source=self.__class__.__name__)
|
|
101
103
|
for file_cnt, file_path in enumerate(self.data_paths):
|
|
102
104
|
video_timer = SimbaTimer(start=True)
|
|
103
105
|
_, self.video_name, _ = get_fn_ext(filepath=file_path)
|
|
@@ -127,8 +129,7 @@ class EgocentricalAligner():
|
|
|
127
129
|
if self.verbose:
|
|
128
130
|
print(f'{self.video_name} complete, saved at {save_path} (elapsed time: {video_timer.elapsed_time_str}s)')
|
|
129
131
|
if self.rotate_video:
|
|
130
|
-
if self.verbose:
|
|
131
|
-
print(f'Rotating video {self.video_name}...')
|
|
132
|
+
if self.verbose: print(f'Rotating video {self.video_name}...')
|
|
132
133
|
video_path = find_video_of_file(video_dir=self.videos_dir, filename=self.video_name, raise_error=False)
|
|
133
134
|
save_path = os.path.join(self.save_dir, f'{self.video_name}.mp4')
|
|
134
135
|
video_rotator = EgocentricVideoRotator(video_path=video_path,
|
|
@@ -139,11 +140,13 @@ class EgocentricalAligner():
|
|
|
139
140
|
gpu=self.gpu,
|
|
140
141
|
fill_clr=self.fill_clr,
|
|
141
142
|
core_cnt=self.core_cnt,
|
|
142
|
-
save_path=save_path
|
|
143
|
+
save_path=save_path,
|
|
144
|
+
pool=self.pool)
|
|
143
145
|
video_rotator.run()
|
|
144
146
|
if self.verbose:
|
|
145
147
|
print(f'Rotated data for video {self.video_name} ({file_cnt+1}/{len(self.data_paths)}) saved in {self.save_dir}.')
|
|
146
148
|
timer.stop_timer()
|
|
149
|
+
terminate_cpu_pool(pool=self.pool, source=self.__class__.__name__)
|
|
147
150
|
stdout_success(msg=f'Egocentrically aligned data for {len(self.data_paths)} files saved in {self.save_dir}', elapsed_time=timer.elapsed_time_str)
|
|
148
151
|
|
|
149
152
|
|
|
@@ -156,9 +159,24 @@ class EgocentricalAligner():
|
|
|
156
159
|
# direction=0,
|
|
157
160
|
# gpu=True,
|
|
158
161
|
# anchor_location=(600, 300),
|
|
159
|
-
# fill_clr=(128,128,128)
|
|
162
|
+
# fill_clr=(128,128,128),
|
|
163
|
+
# core_cnt=18)
|
|
160
164
|
# aligner.run()
|
|
161
165
|
|
|
166
|
+
|
|
167
|
+
if __name__ == "__main__":
|
|
168
|
+
aligner = EgocentricalAligner(anchor_1='butt/proximal tail',
|
|
169
|
+
anchor_2='snout',
|
|
170
|
+
data_dir=r'C:\troubleshooting\open_field_below\project_folder\csv\outlier_corrected_movement_location',
|
|
171
|
+
videos_dir=r'C:\troubleshooting\open_field_below\project_folder\videos',
|
|
172
|
+
save_dir=r"C:\troubleshooting\open_field_below\project_folder\videos\rotated",
|
|
173
|
+
direction=0,
|
|
174
|
+
gpu=True,
|
|
175
|
+
anchor_location=(600, 300),
|
|
176
|
+
fill_clr=(128,128,128),
|
|
177
|
+
core_cnt=18)
|
|
178
|
+
aligner.run()
|
|
179
|
+
|
|
162
180
|
# aligner = EgocentricalAligner(anchor_1='tail_base',
|
|
163
181
|
# anchor_2='nose',
|
|
164
182
|
# data_dir=r'C:\Users\sroni\OneDrive\Desktop\rotate_ex\data',
|
|
@@ -13,10 +13,14 @@ from simba.data_processors.pybursts_calculator import kleinberg_burst_detection
|
|
|
13
13
|
from simba.mixins.config_reader import ConfigReader
|
|
14
14
|
from simba.utils.checks import (check_float, check_if_dir_exists,
|
|
15
15
|
check_if_filepath_list_is_empty, check_int,
|
|
16
|
-
check_that_column_exist,
|
|
16
|
+
check_that_column_exist, check_valid_boolean,
|
|
17
|
+
check_valid_lst)
|
|
17
18
|
from simba.utils.enums import Paths, TagNames
|
|
18
19
|
from simba.utils.printing import SimbaTimer, log_event, stdout_success
|
|
19
|
-
from simba.utils.read_write import
|
|
20
|
+
from simba.utils.read_write import (copy_files_to_directory,
|
|
21
|
+
find_files_of_filetypes_in_directory,
|
|
22
|
+
get_current_time, get_fn_ext, read_df,
|
|
23
|
+
remove_a_folder, write_df)
|
|
20
24
|
from simba.utils.warnings import KleinbergWarning
|
|
21
25
|
|
|
22
26
|
|
|
@@ -38,12 +42,13 @@ class KleinbergCalculator(ConfigReader):
|
|
|
38
42
|
|
|
39
43
|
:param str config_path: path to SimBA project config file in Configparser format
|
|
40
44
|
:param List[str] classifier_names: Classifier names to apply Kleinberg smoothing to.
|
|
41
|
-
:param float sigma:
|
|
42
|
-
:param float gamma:
|
|
43
|
-
:param int hierarchy:
|
|
44
|
-
:param bool hierarchical_search:
|
|
45
|
+
:param float sigma: State transition cost for moving to higher burst levels. Higher values (e.g., 2-3) produce fewer but longer bursts; lower values (e.g., 1.1-1.5) detect more frequent, shorter bursts. Must be > 1.01. Default: 2.
|
|
46
|
+
:param float gamma: State transition cost for moving to lower burst levels. Higher values (e.g., 0.5-1.0) reduce total burst count by making downward transitions costly; lower values (e.g., 0.1-0.3) allow more flexible state changes. Must be >= 0. Default: 0.3.
|
|
47
|
+
:param int hierarchy: Hierarchy level to extract bursts from (0=lowest, higher=more selective). Level 0 captures all bursts; level 1-2 typically filters noise; level 3+ selects only the most prominent, sustained bursts. Higher levels yield fewer but more confident detections. Must be >= 0. Default: 1.
|
|
48
|
+
:param bool hierarchical_search: If True, searches for target hierarchy level within detected burst periods, falling back to lower levels if target not found. If False, extracts only bursts at the exact specified hierarchy level. Recommended when target hierarchy may be sparse. Default: False.
|
|
45
49
|
:param Optional[Union[str, os.PathLike]] input_dir: The directory with files to perform kleinberg smoothing on. If None, defaults to `project_folder/csv/machine_results`
|
|
46
50
|
:param Optional[Union[str, os.PathLike]] output_dir: Location to save smoothened data in. If None, defaults to `project_folder/csv/machine_results`
|
|
51
|
+
:param Optional[bool] save_originals: If True, saves the original data in sub-directory of the ouput directory.`
|
|
47
52
|
|
|
48
53
|
:example I:
|
|
49
54
|
>>> kleinberg_calculator = KleinbergCalculator(config_path='MySimBAConfigPath', classifier_names=['Attack'], sigma=2, gamma=0.3, hierarchy=2, hierarchical_search=False)
|
|
@@ -68,10 +73,12 @@ class KleinbergCalculator(ConfigReader):
|
|
|
68
73
|
|
|
69
74
|
def __init__(self,
|
|
70
75
|
config_path: Union[str, os.PathLike],
|
|
71
|
-
classifier_names: List[str],
|
|
72
|
-
sigma:
|
|
73
|
-
gamma:
|
|
76
|
+
classifier_names: Optional[List[str]] = None,
|
|
77
|
+
sigma: float = 2,
|
|
78
|
+
gamma: float = 0.3,
|
|
74
79
|
hierarchy: Optional[int] = 1,
|
|
80
|
+
verbose: bool = True,
|
|
81
|
+
save_originals: bool = True,
|
|
75
82
|
hierarchical_search: Optional[bool] = False,
|
|
76
83
|
input_dir: Optional[Union[str, os.PathLike]] = None,
|
|
77
84
|
output_dir: Optional[Union[str, os.PathLike]] = None):
|
|
@@ -81,25 +88,31 @@ class KleinbergCalculator(ConfigReader):
|
|
|
81
88
|
check_float(value=sigma, name=f'{self.__class__.__name__} sigma', min_value=1.01)
|
|
82
89
|
check_float(value=gamma, name=f'{self.__class__.__name__} gamma', min_value=0)
|
|
83
90
|
check_int(value=hierarchy, name=f'{self.__class__.__name__} hierarchy', min_value=0)
|
|
84
|
-
|
|
91
|
+
if isinstance(classifier_names, list):
|
|
92
|
+
check_valid_lst(data=classifier_names, source=f'{self.__class__.__name__} classifier_names', valid_dtypes=(str,), min_len=1)
|
|
93
|
+
else:
|
|
94
|
+
classifier_names = deepcopy(self.clf_names)
|
|
95
|
+
check_valid_boolean(value=verbose, source=f'{self.__class__.__name__} verbose', raise_error=True)
|
|
96
|
+
check_valid_boolean(value=save_originals, source=f'{self.__class__.__name__} save_originals', raise_error=True)
|
|
85
97
|
self.hierarchical_search, sigma, gamma, hierarchy, self.output_dir = (hierarchical_search, float(sigma), float(gamma), int(hierarchy), output_dir)
|
|
86
|
-
self.sigma, self.gamma, self.hierarchy, self.clfs = ( float(sigma), float(gamma),
|
|
98
|
+
self.sigma, self.gamma, self.hierarchy, self.clfs = ( float(sigma), float(gamma), int(hierarchy), classifier_names)
|
|
99
|
+
self.verbose, self.save_originals = verbose, save_originals
|
|
87
100
|
if input_dir is None:
|
|
88
|
-
self.
|
|
89
|
-
check_if_filepath_list_is_empty(filepaths=self.machine_results_paths, error_msg=f"SIMBA ERROR: No data files found in {self.machine_results_dir}. Cannot perform Kleinberg smoothing")
|
|
90
|
-
original_data_files_folder = os.path.join(self.project_path, Paths.MACHINE_RESULTS_DIR.value, f"Pre_Kleinberg_{self.datetime}")
|
|
91
|
-
if not os.path.exists(original_data_files_folder):
|
|
92
|
-
os.makedirs(original_data_files_folder)
|
|
93
|
-
for file_path in self.machine_results_paths:
|
|
94
|
-
_, file_name, ext = get_fn_ext(file_path)
|
|
95
|
-
shutil.copyfile(file_path, os.path.join(original_data_files_folder, file_name + ext))
|
|
101
|
+
self.input_dir = os.path.join(self.project_path, Paths.MACHINE_RESULTS_DIR.value)
|
|
96
102
|
else:
|
|
97
103
|
check_if_dir_exists(in_dir=input_dir)
|
|
98
|
-
self.
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
104
|
+
self.input_dir = deepcopy(input_dir)
|
|
105
|
+
self.data_paths = find_files_of_filetypes_in_directory(directory=self.input_dir, extensions=[f'.{self.file_type}'], sort_alphabetically=True, raise_error=True)
|
|
106
|
+
if output_dir is None:
|
|
107
|
+
self.output_dir = deepcopy(self.input_dir)
|
|
108
|
+
else:
|
|
109
|
+
check_if_dir_exists(in_dir=output_dir)
|
|
110
|
+
self.output_dir = deepcopy(output_dir)
|
|
111
|
+
self.original_data_files_folder = os.path.join(self.output_dir, f"Pre_Kleinberg_{self.datetime}")
|
|
112
|
+
remove_a_folder(folder_dir=self.original_data_files_folder, ignore_errors=True)
|
|
113
|
+
os.makedirs(self.original_data_files_folder)
|
|
114
|
+
copy_files_to_directory(file_paths=self.data_paths, dir=self.original_data_files_folder, verbose=False, integer_save_names=False)
|
|
115
|
+
if self.verbose: print(f"Processing Kleinberg burst detection for {len(self.data_paths)} file(s) and {len(classifier_names)} classifier(s)...")
|
|
103
116
|
|
|
104
117
|
def hierarchical_searcher(self):
|
|
105
118
|
if (len(self.kleinberg_bouts["Hierarchy"]) == 1) and (int(self.kleinberg_bouts.at[0, "Hierarchy"]) == 0):
|
|
@@ -135,7 +148,7 @@ class KleinbergCalculator(ConfigReader):
|
|
|
135
148
|
for file_cnt, file_path in enumerate(self.data_paths):
|
|
136
149
|
_, video_name, _ = get_fn_ext(file_path)
|
|
137
150
|
video_timer = SimbaTimer(start=True)
|
|
138
|
-
print(f"Performing Kleinberg burst detection for video {video_name}
|
|
151
|
+
if self.verbose: print(f"[{get_current_time()}] Performing Kleinberg burst detection for video {video_name} (Video {file_cnt+1}/{len(self.data_paths)})...")
|
|
139
152
|
data_df = read_df(file_path, self.file_type).reset_index(drop=True)
|
|
140
153
|
video_out_df = deepcopy(data_df)
|
|
141
154
|
check_that_column_exist(df=data_df, column_name=self.clfs, file_name=video_name)
|
|
@@ -150,7 +163,7 @@ class KleinbergCalculator(ConfigReader):
|
|
|
150
163
|
self.kleinberg_bouts.insert(loc=0, column="Video", value=video_name)
|
|
151
164
|
detailed_df_lst.append(self.kleinberg_bouts)
|
|
152
165
|
if self.hierarchical_search:
|
|
153
|
-
print(f"Applying hierarchical search for video {video_name}...")
|
|
166
|
+
if self.verbose: print(f"[{get_current_time()}] Applying hierarchical search for video {video_name}...")
|
|
154
167
|
self.hierarchical_searcher()
|
|
155
168
|
else:
|
|
156
169
|
self.clf_bouts_in_hierarchy = self.kleinberg_bouts[self.kleinberg_bouts["Hierarchy"] == self.hierarchy]
|
|
@@ -160,19 +173,38 @@ class KleinbergCalculator(ConfigReader):
|
|
|
160
173
|
video_out_df.loc[hierarchy_idx, clf] = 1
|
|
161
174
|
write_df(video_out_df, self.file_type, save_path)
|
|
162
175
|
video_timer.stop_timer()
|
|
163
|
-
print(f'Kleinberg analysis complete for video {video_name} (saved at {save_path}), elapsed time: {video_timer.elapsed_time_str}s.')
|
|
176
|
+
if self.verbose: print(f'[{get_current_time()}] Kleinberg analysis complete for video {video_name} (saved at {save_path}), elapsed time: {video_timer.elapsed_time_str}s.')
|
|
164
177
|
|
|
165
178
|
self.timer.stop_timer()
|
|
179
|
+
if not self.save_originals:
|
|
180
|
+
remove_a_folder(folder_dir=self.original_data_files_folder, ignore_errors=False)
|
|
181
|
+
else:
|
|
182
|
+
if self.verbose: stdout_success(msg=f"Original, un-smoothened data, saved in {self.original_data_files_folder} directory", elapsed_time=self.timer.elapsed_time_str, source=self.__class__.__name__)
|
|
166
183
|
if len(detailed_df_lst) > 0:
|
|
167
184
|
self.detailed_df = pd.concat(detailed_df_lst, axis=0)
|
|
168
185
|
detailed_save_path = os.path.join(self.logs_path, f"Kleinberg_detailed_log_{self.datetime}.csv")
|
|
169
186
|
self.detailed_df.to_csv(detailed_save_path)
|
|
170
|
-
stdout_success(msg=f"Kleinberg analysis complete. See {detailed_save_path} for details of detected bouts of all classifiers in all hierarchies", elapsed_time=self.timer.elapsed_time_str, source=self.__class__.__name__)
|
|
187
|
+
if self.verbose: stdout_success(msg=f"Kleinberg analysis complete for {len(self.data_paths)} files. Results stored in {self.output_dir} directory. See {detailed_save_path} for details of detected bouts of all classifiers in all hierarchies", elapsed_time=self.timer.elapsed_time_str, source=self.__class__.__name__)
|
|
171
188
|
else:
|
|
172
|
-
print("Kleinberg analysis complete.")
|
|
189
|
+
if self.verbose: print(f"[{get_current_time()}] Kleinberg analysis complete for {len(self.data_paths)} files. Results stored in {self.output_dir} directory.")
|
|
173
190
|
KleinbergWarning(msg="All behavior bouts removed following kleinberg smoothing", source=self.__class__.__name__)
|
|
174
191
|
|
|
175
192
|
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
# test = KleinbergCalculator(config_path=r"C:\troubleshooting\mitra\project_folder\project_config.ini",
|
|
196
|
+
# classifier_names=['straub_tail'],
|
|
197
|
+
# sigma=1.1,
|
|
198
|
+
# gamma=0.1,
|
|
199
|
+
# hierarchy=1,
|
|
200
|
+
# save_originals=False,
|
|
201
|
+
# hierarchical_search=False)
|
|
202
|
+
#
|
|
203
|
+
# test.run()
|
|
204
|
+
#
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
|
|
176
208
|
# test = KleinbergCalculator(config_path='/Users/simon/Desktop/envs/simba/troubleshooting/levi/project_folder/project_config.ini',
|
|
177
209
|
# classifier_names=['No_Seizure_(0)'],
|
|
178
210
|
# sigma=1.1,
|
|
@@ -154,11 +154,11 @@ class FeatureSubsetsCalculator(ConfigReader, TrainModelMixin):
|
|
|
154
154
|
self.within_animal_four_point_combs[animal] = np.array(list(combinations(animal_bps, 4)))
|
|
155
155
|
|
|
156
156
|
def _get_two_point_bp_distances(self):
|
|
157
|
-
for c in self.two_point_combs:
|
|
157
|
+
for cnt, c in enumerate(self.two_point_combs):
|
|
158
158
|
x1, y1, x2, y2 = list(sum([(f"{x}_x", f"{y}_y") for (x, y) in zip(c, c)], ()))
|
|
159
159
|
bp1 = self.data_df[[x1, y1]].values
|
|
160
160
|
bp2 = self.data_df[[x2, y2]].values
|
|
161
|
-
self.results[f"Distance (mm) {c[0]}-{c[1]}"] = FeatureExtractionMixin.
|
|
161
|
+
self.results[f"Distance (mm) {c[0]}-{c[1]}"] = FeatureExtractionMixin.bodypart_distance(bp1_coords=bp1.astype(np.int32), bp2_coords=bp2.astype(np.int32), px_per_mm=np.float64(self.px_per_mm), in_centimeters=False)
|
|
162
162
|
|
|
163
163
|
def __get_three_point_angles(self):
|
|
164
164
|
for animal, points in self.within_animal_three_point_combs.items():
|
|
@@ -342,13 +342,20 @@ class FeatureSubsetsCalculator(ConfigReader, TrainModelMixin):
|
|
|
342
342
|
|
|
343
343
|
|
|
344
344
|
|
|
345
|
+
# test = FeatureSubsetsCalculator(config_path=r"C:\troubleshooting\srami0619\project_folder\project_config.ini",
|
|
346
|
+
# feature_families=[TWO_POINT_BP_DISTANCES],
|
|
347
|
+
# append_to_features_extracted=False,
|
|
348
|
+
# file_checks=True,
|
|
349
|
+
# append_to_targets_inserted=False)
|
|
350
|
+
# test.run()
|
|
351
|
+
|
|
352
|
+
|
|
345
353
|
|
|
346
354
|
# test = FeatureSubsetsCalculator(config_path=r"C:\troubleshooting\mitra\project_folder\project_config.ini",
|
|
347
|
-
# feature_families=[
|
|
355
|
+
# feature_families=[TWO_POINT_BP_DISTANCES],
|
|
348
356
|
# append_to_features_extracted=False,
|
|
349
357
|
# file_checks=True,
|
|
350
|
-
# append_to_targets_inserted=False
|
|
351
|
-
# save_dir=r"C:\troubleshooting\mitra\project_folder\csv\feature_subset")
|
|
358
|
+
# append_to_targets_inserted=False)
|
|
352
359
|
# test.run()
|
|
353
360
|
|
|
354
361
|
#
|
simba/mixins/statistics_mixin.py
CHANGED
|
@@ -8,6 +8,8 @@ from sklearn.metrics import (adjusted_mutual_info_score, adjusted_rand_score,
|
|
|
8
8
|
fowlkes_mallows_score)
|
|
9
9
|
from sklearn.neighbors import LocalOutlierFactor
|
|
10
10
|
|
|
11
|
+
from simba.utils.printing import SimbaTimer
|
|
12
|
+
|
|
11
13
|
try:
|
|
12
14
|
from typing import Literal
|
|
13
15
|
except:
|
|
@@ -538,7 +540,8 @@ class Statistics(FeatureExtractionMixin):
|
|
|
538
540
|
sample_1: np.ndarray,
|
|
539
541
|
sample_2: np.ndarray,
|
|
540
542
|
fill_value: Optional[int] = 1,
|
|
541
|
-
bucket_method: Literal["fd", "doane", "auto", "scott", "stone", "rice", "sturges", "sqrt"] = "auto"
|
|
543
|
+
bucket_method: Literal["fd", "doane", "auto", "scott", "stone", "rice", "sturges", "sqrt"] = "auto",
|
|
544
|
+
verbose: bool = False) -> float:
|
|
542
545
|
|
|
543
546
|
r"""
|
|
544
547
|
Compute Kullback-Leibler divergence between two distributions.
|
|
@@ -562,6 +565,7 @@ class Statistics(FeatureExtractionMixin):
|
|
|
562
565
|
:returns: Kullback-Leibler divergence between ``sample_1`` and ``sample_2``
|
|
563
566
|
:rtype: float
|
|
564
567
|
"""
|
|
568
|
+
timer = SimbaTimer(start=True)
|
|
565
569
|
check_valid_array(data=sample_1, source=Statistics.kullback_leibler_divergence.__name__, accepted_ndims=(1,), accepted_dtypes=Formats.NUMERIC_DTYPES.value)
|
|
566
570
|
check_valid_array(data=sample_2, source=Statistics.kullback_leibler_divergence.__name__, accepted_ndims=(1,), accepted_dtypes=Formats.NUMERIC_DTYPES.value)
|
|
567
571
|
check_str(name=f"{self.__class__.__name__} bucket_method", value=bucket_method, options=Options.BUCKET_METHODS.value)
|
|
@@ -573,7 +577,10 @@ class Statistics(FeatureExtractionMixin):
|
|
|
573
577
|
sample_1_hist[sample_1_hist == 0] = fill_value
|
|
574
578
|
sample_2_hist[sample_2_hist == 0] = fill_value
|
|
575
579
|
sample_1_hist, sample_2_hist = sample_1_hist / np.sum(sample_1_hist), sample_2_hist / np.sum(sample_2_hist)
|
|
576
|
-
|
|
580
|
+
kl = stats.entropy(pk=sample_1_hist, qk=sample_2_hist)
|
|
581
|
+
timer.stop_timer()
|
|
582
|
+
if verbose: print(f'KL divergence performed on {sample_1.shape[0]} observations (elapsed time: {timer.elapsed_time_str}s)')
|
|
583
|
+
return kl
|
|
577
584
|
|
|
578
585
|
def rolling_kullback_leibler_divergence(
|
|
579
586
|
self,
|
|
@@ -4,17 +4,18 @@ import warnings
|
|
|
4
4
|
|
|
5
5
|
warnings.simplefilter(action="ignore", category=FutureWarning)
|
|
6
6
|
import functools
|
|
7
|
+
import gc
|
|
7
8
|
import multiprocessing
|
|
8
9
|
import os
|
|
9
10
|
import platform
|
|
10
|
-
from typing import List, Optional, Union
|
|
11
11
|
from copy import deepcopy
|
|
12
|
+
from typing import List, Optional, Union
|
|
12
13
|
|
|
13
|
-
import gc
|
|
14
14
|
import cv2
|
|
15
|
+
import matplotlib
|
|
15
16
|
import numpy as np
|
|
16
17
|
import pandas as pd
|
|
17
|
-
|
|
18
|
+
|
|
18
19
|
matplotlib.use('Agg')
|
|
19
20
|
|
|
20
21
|
from simba.mixins.config_reader import ConfigReader
|
|
@@ -23,13 +24,14 @@ from simba.utils.checks import (
|
|
|
23
24
|
check_all_file_names_are_represented_in_video_log,
|
|
24
25
|
check_file_exist_and_readable, check_int, check_str,
|
|
25
26
|
check_that_column_exist, check_valid_boolean, check_valid_lst)
|
|
26
|
-
from simba.utils.data import (create_color_palette, detect_bouts,
|
|
27
|
+
from simba.utils.data import (create_color_palette, detect_bouts, get_cpu_pool,
|
|
28
|
+
terminate_cpu_pool)
|
|
27
29
|
from simba.utils.enums import Formats, Options
|
|
28
30
|
from simba.utils.errors import NoSpecifiedOutputError
|
|
29
31
|
from simba.utils.printing import SimbaTimer, stdout_success
|
|
30
32
|
from simba.utils.read_write import (concatenate_videos_in_folder,
|
|
31
33
|
create_directory, find_core_cnt,
|
|
32
|
-
get_fn_ext, read_df
|
|
34
|
+
get_current_time, get_fn_ext, read_df)
|
|
33
35
|
|
|
34
36
|
HEIGHT = "height"
|
|
35
37
|
WIDTH = "width"
|
|
@@ -12,15 +12,19 @@ import pandas as pd
|
|
|
12
12
|
from simba.mixins.config_reader import ConfigReader
|
|
13
13
|
from simba.mixins.geometry_mixin import GeometryMixin
|
|
14
14
|
from simba.mixins.plotting_mixin import PlottingMixin
|
|
15
|
-
from simba.utils.checks import (check_instance, check_int,
|
|
16
|
-
|
|
15
|
+
from simba.utils.checks import (check_instance, check_int,
|
|
16
|
+
check_nvidea_gpu_available, check_str,
|
|
17
|
+
check_that_column_exist, check_valid_boolean)
|
|
18
|
+
from simba.utils.data import (create_color_palette, get_cpu_pool,
|
|
19
|
+
terminate_cpu_pool)
|
|
17
20
|
from simba.utils.enums import OS, Formats, Options
|
|
18
21
|
from simba.utils.errors import CountError, InvalidFilepathError
|
|
19
22
|
from simba.utils.printing import SimbaTimer, stdout_success
|
|
20
23
|
from simba.utils.read_write import (concatenate_videos_in_folder,
|
|
21
24
|
find_core_cnt,
|
|
22
25
|
find_files_of_filetypes_in_directory,
|
|
23
|
-
|
|
26
|
+
get_current_time, get_fn_ext,
|
|
27
|
+
get_video_meta_data, read_df)
|
|
24
28
|
from simba.utils.warnings import FrameRangeWarning
|
|
25
29
|
|
|
26
30
|
|
simba/plotting/roi_plotter_mp.py
CHANGED
|
@@ -24,15 +24,16 @@ from simba.utils.checks import (check_file_exist_and_readable, check_float,
|
|
|
24
24
|
check_valid_boolean, check_valid_lst,
|
|
25
25
|
check_video_and_data_frm_count_align)
|
|
26
26
|
from simba.utils.data import (create_color_palettes, detect_bouts,
|
|
27
|
-
|
|
27
|
+
get_cpu_pool, slice_roi_dict_for_video,
|
|
28
|
+
terminate_cpu_pool)
|
|
28
29
|
from simba.utils.enums import ROI_SETTINGS, Formats, Keys, Paths, TextOptions
|
|
29
30
|
from simba.utils.errors import (BodypartColumnNotFoundError, DuplicationError,
|
|
30
31
|
NoFilesFoundError, NoROIDataError,
|
|
31
32
|
ROICoordinatesNotFoundError)
|
|
32
33
|
from simba.utils.printing import SimbaTimer, stdout_success
|
|
33
34
|
from simba.utils.read_write import (concatenate_videos_in_folder,
|
|
34
|
-
find_core_cnt,
|
|
35
|
-
|
|
35
|
+
find_core_cnt, get_current_time,
|
|
36
|
+
get_video_meta_data, read_df)
|
|
36
37
|
from simba.utils.warnings import (DuplicateNamesWarning, FrameRangeWarning,
|
|
37
38
|
GPUToolsWarning)
|
|
38
39
|
|
|
@@ -13,10 +13,11 @@ from simba.mixins.plotting_mixin import PlottingMixin
|
|
|
13
13
|
from simba.utils.checks import (check_file_exist_and_readable, check_float,
|
|
14
14
|
check_if_dir_exists, check_int,
|
|
15
15
|
check_valid_boolean, check_valid_dataframe)
|
|
16
|
-
from simba.utils.data import
|
|
16
|
+
from simba.utils.data import get_cpu_pool, terminate_cpu_pool
|
|
17
17
|
from simba.utils.enums import Defaults, Formats
|
|
18
18
|
from simba.utils.errors import InvalidFilepathError, NoFilesFoundError
|
|
19
|
-
from simba.utils.lookups import
|
|
19
|
+
from simba.utils.lookups import (get_current_time, get_random_color_palette,
|
|
20
|
+
intermittent_palette)
|
|
20
21
|
from simba.utils.printing import SimbaTimer, stdout_success
|
|
21
22
|
from simba.utils.read_write import (concatenate_videos_in_folder,
|
|
22
23
|
create_directory,
|
|
@@ -14,7 +14,8 @@ from simba.utils.checks import (check_file_exist_and_readable, check_float,
|
|
|
14
14
|
check_if_dir_exists, check_int,
|
|
15
15
|
check_valid_boolean, check_valid_dataframe,
|
|
16
16
|
check_valid_lst, check_valid_tuple)
|
|
17
|
-
from simba.utils.data import create_color_palette,
|
|
17
|
+
from simba.utils.data import (create_color_palette, get_cpu_pool,
|
|
18
|
+
terminate_cpu_pool)
|
|
18
19
|
from simba.utils.enums import Defaults, Options
|
|
19
20
|
from simba.utils.errors import (CountError, DataHeaderError, FrameRangeError,
|
|
20
21
|
InvalidInputError, NoDataError)
|
|
@@ -22,9 +23,9 @@ from simba.utils.printing import SimbaTimer, stdout_success
|
|
|
22
23
|
from simba.utils.read_write import (concatenate_videos_in_folder,
|
|
23
24
|
create_directory, find_core_cnt,
|
|
24
25
|
find_files_of_filetypes_in_directory,
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
get_current_time, get_fn_ext,
|
|
27
|
+
get_video_meta_data, read_frm_of_video,
|
|
28
|
+
recursive_file_search, remove_a_folder)
|
|
28
29
|
|
|
29
30
|
FRAME = 'FRAME'
|
|
30
31
|
CLASS_ID = 'CLASS_ID'
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""Analyze runtime statistics for directionality_to_nonstatic_target"""
|
|
2
|
+
import numpy as np
|
|
3
|
+
from collections import defaultdict
|
|
4
|
+
|
|
5
|
+
# Parse the runtime data
|
|
6
|
+
data = {
|
|
7
|
+
10000: [0.4389, 0.0008, 0.0012],
|
|
8
|
+
100000: [0.0063, 0.0052, 0.0052],
|
|
9
|
+
1000000: [0.0768, 0.0306, 0.0239],
|
|
10
|
+
10000000: [0.2195, 0.2122, 0.2083],
|
|
11
|
+
50000000: [1.8936, 1.5664, 1.2548]
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
# Calculate statistics
|
|
15
|
+
print("=" * 80)
|
|
16
|
+
print(f"{'Observations':<15} {'Mean (s)':<12} {'Std (s)':<12} {'Min (s)':<12} {'Max (s)':<12} {'Throughput (M obs/s)':<20}")
|
|
17
|
+
print("=" * 80)
|
|
18
|
+
|
|
19
|
+
for obs_count in sorted(data.keys()):
|
|
20
|
+
times = np.array(data[obs_count])
|
|
21
|
+
mean_time = np.mean(times)
|
|
22
|
+
std_time = np.std(times)
|
|
23
|
+
min_time = np.min(times)
|
|
24
|
+
max_time = np.max(times)
|
|
25
|
+
throughput = obs_count / (mean_time * 1_000_000) # Million observations per second
|
|
26
|
+
|
|
27
|
+
print(f"{obs_count:<15,} {mean_time:<12.4f} {std_time:<12.4f} {min_time:<12.4f} {max_time:<12.4f} {throughput:<20.2f}")
|
|
28
|
+
|
|
29
|
+
print("=" * 80)
|
|
30
|
+
print("\nNote: First run typically includes JIT compilation overhead (especially for 10k observations)")
|