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.
Files changed (41) hide show
  1. simba/assets/lookups/tooptips.json +6 -1
  2. simba/data_processors/cuda/geometry.py +45 -27
  3. simba/data_processors/cuda/image.py +1620 -1600
  4. simba/data_processors/cuda/statistics.py +17 -9
  5. simba/data_processors/egocentric_aligner.py +24 -6
  6. simba/data_processors/kleinberg_calculator.py +61 -29
  7. simba/feature_extractors/feature_subsets.py +12 -5
  8. simba/feature_extractors/straub_tail_analyzer.py +0 -2
  9. simba/mixins/statistics_mixin.py +9 -2
  10. simba/plotting/gantt_creator_mp.py +7 -5
  11. simba/plotting/pose_plotter_mp.py +7 -3
  12. simba/plotting/roi_plotter_mp.py +4 -3
  13. simba/plotting/yolo_pose_track_visualizer.py +3 -2
  14. simba/plotting/yolo_pose_visualizer.py +5 -4
  15. simba/sandbox/analyze_runtimes.py +30 -0
  16. simba/sandbox/cuda/egocentric_rotator.py +374 -374
  17. simba/sandbox/proboscis_to_tip.py +28 -0
  18. simba/sandbox/test_directionality.py +47 -0
  19. simba/sandbox/test_nonstatic_directionality.py +27 -0
  20. simba/sandbox/test_pycharm_cuda.py +51 -0
  21. simba/sandbox/test_simba_install.py +41 -0
  22. simba/sandbox/test_static_directionality.py +26 -0
  23. simba/sandbox/test_static_directionality_2d.py +26 -0
  24. simba/sandbox/verify_env.py +42 -0
  25. simba/ui/pop_ups/fsttc_pop_up.py +27 -25
  26. simba/ui/pop_ups/kleinberg_pop_up.py +39 -40
  27. simba/ui/tkinter_functions.py +3 -0
  28. simba/utils/data.py +0 -1
  29. simba/utils/errors.py +441 -440
  30. simba/utils/lookups.py +1203 -1203
  31. simba/utils/printing.py +124 -125
  32. simba/utils/read_write.py +43 -14
  33. simba/video_processors/egocentric_video_rotator.py +41 -36
  34. simba/video_processors/video_processing.py +5247 -5233
  35. simba/video_processors/videos_to_frames.py +41 -31
  36. {simba_uw_tf_dev-4.6.3.dist-info → simba_uw_tf_dev-4.6.6.dist-info}/METADATA +2 -2
  37. {simba_uw_tf_dev-4.6.3.dist-info → simba_uw_tf_dev-4.6.6.dist-info}/RECORD +41 -32
  38. {simba_uw_tf_dev-4.6.3.dist-info → simba_uw_tf_dev-4.6.6.dist-info}/LICENSE +0 -0
  39. {simba_uw_tf_dev-4.6.3.dist-info → simba_uw_tf_dev-4.6.6.dist-info}/WHEEL +0 -0
  40. {simba_uw_tf_dev-4.6.3.dist-info → simba_uw_tf_dev-4.6.6.dist-info}/entry_points.txt +0 -0
  41. {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, y: np.ndarray, fill_value: int = 1, bucket_method: Literal[
838
- "fd", "doane", "auto", "scott", "stone", "rice", "sturges", "sqrt"] = "scott") -> float:
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
- return kl_divergence_gpu(P=x_hist.astype(np.float32), Q=y_hist.astype(np.float32), convert_dtype=False)
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: self.core_cnt = find_core_cnt()[0]
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, check_valid_lst)
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 get_fn_ext, read_df, write_df
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: Burst detection sigma value. Higher sigma values and fewer, longer, behavioural bursts will be recognised. Default: 2.
42
- :param float gamma: Burst detection gamma value. Higher gamma values and fewer behavioural bursts will be recognised. Default: 0.3.
43
- :param int hierarchy: Burst detection hierarchy level. Higher hierarchy values and fewer behavioural bursts will to be recognised. Default: 1.
44
- :param bool hierarchical_search: See `Tutorial <https://github.com/sgoldenlab/simba/blob/master/docs/kleinberg_filter.md#hierarchical-search-example>`_ Default: False.
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: Optional[int] = 2,
73
- gamma: Optional[float] = 0.3,
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
- check_valid_lst(data=classifier_names, source=f'{self.__class__.__name__} classifier_names', valid_dtypes=(str,), min_len=1)
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), float(hierarchy), classifier_names)
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.data_paths, self.output_dir = self.machine_results_paths, self.machine_results_dir
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.data_paths = glob.glob(input_dir + f"/*.{self.file_type}")
99
- check_if_filepath_list_is_empty(filepaths=self.data_paths, error_msg=f"SIMBA ERROR: No data files found in {input_dir}. Cannot perform Kleinberg smoothing")
100
- if not os.path.isdir(output_dir):
101
- os.makedirs(output_dir)
102
- print(f"Processing Kleinberg burst detection for {len(self.data_paths)} file(s) and {len(classifier_names)} classifier(s)...")
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} (Video {file_cnt+1}/{len(self.data_paths)})...")
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.framewise_euclidean_distance(location_1=bp1.astype(np.float64), location_2=bp2.astype(np.float64), px_per_mm=np.float64(self.px_per_mm), centimeter=False)
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=[ARENA_EDGE],
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
  #
@@ -9,8 +9,6 @@ try:
9
9
  except:
10
10
  from typing_extensions import Literal
11
11
 
12
- import functools
13
- import multiprocessing
14
12
  from copy import deepcopy
15
13
 
16
14
  import numpy as np
@@ -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") -> float:
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
- return stats.entropy(pk=sample_1_hist, qk=sample_2_hist)
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
- import matplotlib
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, terminate_cpu_pool, get_cpu_pool)
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, get_current_time)
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, check_nvidea_gpu_available, check_str, check_that_column_exist, check_valid_boolean)
16
- from simba.utils.data import create_color_palette, terminate_cpu_pool, get_cpu_pool
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
- get_fn_ext, get_video_meta_data, read_df, get_current_time)
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
 
@@ -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
- slice_roi_dict_for_video, terminate_cpu_pool, get_cpu_pool)
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, get_video_meta_data,
35
- read_df, get_current_time)
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 terminate_cpu_pool, get_cpu_pool
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 get_random_color_palette, intermittent_palette, get_current_time
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, terminate_cpu_pool, get_cpu_pool
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
- get_fn_ext, get_video_meta_data,
26
- read_frm_of_video, recursive_file_search,
27
- remove_a_folder, get_current_time)
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)")