simba-uw-tf-dev 4.6.4__py3-none-any.whl → 4.6.7__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.

Files changed (53) hide show
  1. simba/data_processors/blob_location_computer.py +1 -1
  2. simba/data_processors/cuda/geometry.py +45 -27
  3. simba/data_processors/cuda/image.py +1624 -1600
  4. simba/data_processors/cuda/statistics.py +72 -25
  5. simba/data_processors/cuda/timeseries.py +1 -1
  6. simba/data_processors/egocentric_aligner.py +25 -7
  7. simba/data_processors/kleinberg_calculator.py +6 -2
  8. simba/feature_extractors/feature_subsets.py +14 -7
  9. simba/feature_extractors/straub_tail_analyzer.py +4 -6
  10. simba/labelling/standard_labeller.py +1 -1
  11. simba/mixins/geometry_mixin.py +8 -8
  12. simba/mixins/image_mixin.py +14 -14
  13. simba/mixins/statistics_mixin.py +48 -11
  14. simba/mixins/timeseries_features_mixin.py +1 -1
  15. simba/mixins/train_model_mixin.py +65 -27
  16. simba/model/inference_batch.py +1 -1
  17. simba/model/yolo_seg_inference.py +3 -3
  18. simba/plotting/heat_mapper_clf_mp.py +2 -2
  19. simba/pose_importers/simba_blob_importer.py +3 -3
  20. simba/roi_tools/roi_aggregate_stats_mp.py +1 -1
  21. simba/roi_tools/roi_clf_calculator_mp.py +1 -1
  22. simba/sandbox/analyze_runtimes.py +30 -0
  23. simba/sandbox/cuda/egocentric_rotator.py +374 -374
  24. simba/sandbox/proboscis_to_tip.py +28 -0
  25. simba/sandbox/test_directionality.py +47 -0
  26. simba/sandbox/test_nonstatic_directionality.py +27 -0
  27. simba/sandbox/test_pycharm_cuda.py +51 -0
  28. simba/sandbox/test_simba_install.py +41 -0
  29. simba/sandbox/test_static_directionality.py +26 -0
  30. simba/sandbox/test_static_directionality_2d.py +26 -0
  31. simba/sandbox/verify_env.py +42 -0
  32. simba/third_party_label_appenders/transform/coco_keypoints_to_yolo.py +3 -3
  33. simba/third_party_label_appenders/transform/coco_keypoints_to_yolo_bbox.py +2 -2
  34. simba/ui/pop_ups/fsttc_pop_up.py +27 -25
  35. simba/ui/pop_ups/kleinberg_pop_up.py +3 -2
  36. simba/utils/custom_feature_extractor.py +1 -1
  37. simba/utils/data.py +2 -3
  38. simba/utils/errors.py +441 -440
  39. simba/utils/lookups.py +1203 -1203
  40. simba/utils/read_write.py +70 -31
  41. simba/utils/yolo.py +10 -1
  42. simba/video_processors/blob_tracking_executor.py +2 -2
  43. simba/video_processors/clahe_ui.py +1 -1
  44. simba/video_processors/egocentric_video_rotator.py +44 -39
  45. simba/video_processors/multi_cropper.py +1 -1
  46. simba/video_processors/video_processing.py +5264 -5233
  47. simba/video_processors/videos_to_frames.py +43 -33
  48. {simba_uw_tf_dev-4.6.4.dist-info → simba_uw_tf_dev-4.6.7.dist-info}/METADATA +4 -3
  49. {simba_uw_tf_dev-4.6.4.dist-info → simba_uw_tf_dev-4.6.7.dist-info}/RECORD +53 -44
  50. {simba_uw_tf_dev-4.6.4.dist-info → simba_uw_tf_dev-4.6.7.dist-info}/LICENSE +0 -0
  51. {simba_uw_tf_dev-4.6.4.dist-info → simba_uw_tf_dev-4.6.7.dist-info}/WHEEL +0 -0
  52. {simba_uw_tf_dev-4.6.4.dist-info → simba_uw_tf_dev-4.6.7.dist-info}/entry_points.txt +0 -0
  53. {simba_uw_tf_dev-4.6.4.dist-info → simba_uw_tf_dev-4.6.7.dist-info}/top_level.txt +0 -0
@@ -3,7 +3,9 @@ __author__ = "Simon Nilsson; sronilsson@gmail.com"
3
3
 
4
4
  import math
5
5
  from itertools import combinations
6
- from typing import Optional, Tuple
6
+ from typing import Optional, Tuple, Union
7
+
8
+ from simba.utils.printing import SimbaTimer
7
9
 
8
10
  try:
9
11
  from typing import Literal
@@ -17,16 +19,20 @@ from scipy.spatial import ConvexHull
17
19
  from simba.utils.read_write import get_unique_values_in_iterable, read_df
18
20
  from simba.utils.warnings import GPUToolsWarning
19
21
 
22
+
20
23
  try:
21
24
  import cupy as cp
22
- 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
25
  from cupyx.scipy.spatial.distance import cdist
26
- except:
27
- GPUToolsWarning(msg='GPU tools not detected, reverting to CPU')
26
+ except Exception as e:
27
+ GPUToolsWarning(msg=f'GPU tools not detected, reverting to CPU: {e.args}')
28
28
  import numpy as cp
29
29
  from scipy.spatial.distance import cdist
30
+ try:
31
+ from cuml.metrics import kl_divergence as kl_divergence_gpu
32
+ from cuml.metrics.cluster.adjusted_rand_index import adjusted_rand_score
33
+ from cuml.metrics.cluster.silhouette_score import cython_silhouette_score
34
+ except Exception as e:
35
+ GPUToolsWarning(msg=f'GPU tools not detected, reverting to CPU: {e.args}')
30
36
  from scipy.stats import entropy as kl_divergence_gpu
31
37
  from sklearn.metrics import adjusted_rand_score
32
38
  from sklearn.metrics import silhouette_score as cython_silhouette_score
@@ -39,7 +45,7 @@ except:
39
45
  from simba.data_processors.cuda.utils import _cuda_are_rows_equal
40
46
  from simba.mixins.statistics_mixin import Statistics
41
47
  from simba.utils.checks import (check_int, check_str, check_valid_array,
42
- check_valid_tuple)
48
+ check_valid_tuple, check_float)
43
49
  from simba.utils.data import bucket_data
44
50
  from simba.utils.enums import Formats
45
51
 
@@ -227,7 +233,6 @@ def get_euclidean_distance_cupy(x: np.ndarray,
227
233
  using CuPy for GPU acceleration. The computation is performed in batches to handle large
228
234
  datasets efficiently.
229
235
 
230
-
231
236
  .. seealso::
232
237
  For CPU function see :func:`~simba.mixins.feature_extraction_mixin.FeatureExtractionMixin.framewise_euclidean_distance`.
233
238
  For CUDA JIT function see :func:`~simba.data_processors.cuda.statistics.get_euclidean_distance_cuda`.
@@ -380,9 +385,10 @@ def sliding_min(x: np.ndarray, time_window: float, sample_rate: int) -> np.ndarr
380
385
 
381
386
  def sliding_spearmans_rank(x: np.ndarray,
382
387
  y: np.ndarray,
383
- time_window: float,
384
- sample_rate: int,
385
- batch_size: Optional[int] = int(1.6e+7)) -> np.ndarray:
388
+ time_window: Union[float, int],
389
+ sample_rate: Union[float, int],
390
+ batch_size: Optional[int] = int(1.6e+7),
391
+ verbose: bool = False) -> np.ndarray:
386
392
  """
387
393
  Computes the Spearman's rank correlation coefficient between two 1D arrays `x` and `y`
388
394
  over sliding windows of size `time_window * sample_rate`. The computation is performed
@@ -413,7 +419,13 @@ def sliding_spearmans_rank(x: np.ndarray,
413
419
  >>> sliding_spearmans_rank(x, y, time_window=0.5, sample_rate=2)
414
420
  """
415
421
 
416
- window_size = int(np.ceil(time_window * sample_rate))
422
+ timer = SimbaTimer(start=True)
423
+ check_valid_array(data=x, source=f'{sliding_spearmans_rank.__name__} x', accepted_ndims=(1,), accepted_dtypes=Formats.NUMERIC_DTYPES.value)
424
+ check_valid_array(data=y, source=f'{sliding_spearmans_rank.__name__} y', accepted_ndims=(1,), accepted_axis_0_shape=(x.shape[0],), accepted_dtypes=Formats.NUMERIC_DTYPES.value)
425
+ check_float(name=f'{sliding_spearmans_rank.__name__} time_window', value=time_window, allow_zero=False, allow_negative=False, raise_error=True)
426
+ check_float(name=f'{sliding_spearmans_rank.__name__} sample_rate', value=sample_rate, allow_zero=False, allow_negative=False, raise_error=True)
427
+ check_int(name=f'{sliding_spearmans_rank.__name__} batch_size', value=batch_size, allow_zero=False, allow_negative=False, raise_error=True)
428
+ window_size = np.int32(np.ceil(time_window * sample_rate))
417
429
  n = x.shape[0]
418
430
  results = cp.full(n, -1, dtype=cp.float32)
419
431
 
@@ -433,7 +445,11 @@ def sliding_spearmans_rank(x: np.ndarray,
433
445
 
434
446
  results[left + window_size - 1:right] = s
435
447
 
436
- return cp.asnumpy(results)
448
+ r = cp.asnumpy(results)
449
+ timer.stop_timer()
450
+ if verbose: print(f'Sliding Spearmans rank for {x.shape[0]} observations computed (elapsed time: {timer.elapsed_time_str}s)')
451
+ return r
452
+
437
453
 
438
454
 
439
455
 
@@ -538,6 +554,12 @@ def euclidean_distance_to_static_point(data: np.ndarray,
538
554
  """
539
555
  Computes the Euclidean distance between each point in a given 2D array `data` and a static point using GPU acceleration.
540
556
 
557
+ .. seealso::
558
+ For CPU-based distance to static point (ROI center), see :func:`simba.mixins.feature_extraction_mixin.FeatureExtractionMixin.framewise_euclidean_distance_roi`
559
+ For CPU-based framewise Euclidean distance, see :func:`simba.mixins.feature_extraction_mixin.FeatureExtractionMixin.framewise_euclidean_distance`
560
+ For GPU CuPy solution for distance between two sets of points, see :func:`simba.data_processors.cuda.statistics.get_euclidean_distance_cupy`
561
+ For GPU numba CUDA solution for distance between two sets of points, see :func:`simba.data_processors.cuda.statistics.get_euclidean_distance_cuda`
562
+
541
563
  :param data: A 2D array of shape (N, 2), where N is the number of points, and each point is represented by its (x, y) coordinates. The array can represent pixel coordinates.
542
564
  :param point: A tuple of two integers representing the static point (x, y) in the same space as `data`.
543
565
  :param pixels_per_millimeter: A scaling factor that indicates how many pixels correspond to one millimeter. Defaults to 1 if no scaling is necessary.
@@ -789,13 +811,31 @@ def xie_beni(x: np.ndarray, y: np.ndarray) -> float:
789
811
  return xb
790
812
 
791
813
 
792
- def i_index(x: np.ndarray, y: np.ndarray):
814
+ def i_index(x: np.ndarray, y: np.ndarray, verbose: bool = False) -> float:
793
815
  """
794
816
  Calculate the I-Index for evaluating clustering quality.
795
817
 
796
818
  The I-Index is a metric that measures the compactness and separation of clusters.
797
819
  A higher I-Index indicates better clustering with compact and well-separated clusters.
798
820
 
821
+ .. csv-table::
822
+ :header: EXPECTED RUNTIMES
823
+ :file: ../../../docs/tables/i_index_cuda.csv
824
+ :widths: 10, 45, 45
825
+ :align: center
826
+ :header-rows: 1
827
+
828
+ The I-Index is calculated as:
829
+
830
+ .. math::
831
+ I = \frac{SST}{k \times SWC}
832
+
833
+ where:
834
+
835
+ - :math:`SST = \sum_{i=1}^{n} \|x_i - \mu\|^2` is the total sum of squares (sum of squared distances from all points to the global centroid)
836
+ - :math:`k` is the number of clusters
837
+ - :math:`SWC = \sum_{c=1}^{k} \sum_{i \in c} \|x_i - \mu_c\|^2` is the within-cluster sum of squares (sum of squared distances from points to their cluster centroids)
838
+
799
839
  .. seealso::
800
840
  To compute Xie-Beni on the CPU, use :func:`~simba.mixins.statistics_mixin.Statistics.i_index`
801
841
 
@@ -806,17 +846,16 @@ def i_index(x: np.ndarray, y: np.ndarray):
806
846
 
807
847
  :references:
808
848
  .. [1] Zhao, Q., Xu, M., Fränti, P. (2009). Sum-of-Squares Based Cluster Validity Index and Significance Analysis.
809
- In: Kolehmainen, M., Toivanen, P., Beliczynski, B. (eds) Adaptive and Natural Computing Algorithms. ICANNGA 2009.
810
- Lecture Notes in Computer Science, vol 5495. Springer, Berlin, Heidelberg. https://doi.org/10.1007/978-3-642-04921-7_32
849
+ In: Kolehmainen, M., Toivanen, P., Beliczynski, B. (eds) Adaptive and Natural Computing Algorithms. ICANNGA 2009. Lecture Notes in Computer Science, vol 5495. Springer, Berlin, Heidelberg. https://doi.org/10.1007/978-3-642-04921-7_32
811
850
 
812
851
  :example:
813
852
  >>> X, y = make_blobs(n_samples=5000, centers=20, n_features=3, random_state=0, cluster_std=0.1)
814
853
  >>> i_index(x=X, y=y)
815
854
  """
855
+ timer = SimbaTimer(start=True)
816
856
  check_valid_array(data=x, accepted_ndims=(2,), accepted_dtypes=Formats.NUMERIC_DTYPES.value)
817
- check_valid_array(data=y, accepted_ndims=(1,), accepted_dtypes=Formats.NUMERIC_DTYPES.value,
818
- accepted_axis_0_shape=[x.shape[0], ])
819
- _ = get_unique_values_in_iterable(data=y, name=i_index.__name__, min=2)
857
+ check_valid_array(data=y, accepted_ndims=(1,), accepted_dtypes=Formats.NUMERIC_DTYPES.value, accepted_axis_0_shape=[x.shape[0], ])
858
+ cluster_cnt = get_unique_values_in_iterable(data=y, name=i_index.__name__, min=2)
820
859
  x, y = cp.array(x), cp.array(y)
821
860
  unique_y = cp.unique(y)
822
861
  n_y = unique_y.shape[0]
@@ -830,12 +869,16 @@ def i_index(x: np.ndarray, y: np.ndarray):
830
869
  swc += cp.sum(cp.linalg.norm(cluster_obs - cluster_centroid, axis=1) ** 2)
831
870
 
832
871
  i_idx = sst / (n_y * swc)
833
-
872
+ i_idx = np.float32(i_idx.get()) if hasattr(i_idx, 'get') else np.float32(i_idx)
873
+ timer.stop_timer()
874
+ if verbose: print(f'I-index for {x.shape[0]} observations in {cluster_cnt} clusters computed (elapsed time: {timer.elapsed_time_str}s)')
834
875
  return i_idx
835
876
 
836
-
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:
877
+ def kullback_leibler_divergence_gpu(x: np.ndarray,
878
+ y: np.ndarray,
879
+ fill_value: int = 1,
880
+ bucket_method: Literal["fd", "doane", "auto", "scott", "stone", "rice", "sturges", "sqrt"] = "scott",
881
+ verbose: bool = False) -> float:
839
882
  """
840
883
  Compute Kullback-Leibler divergence between two distributions.
841
884
 
@@ -847,7 +890,6 @@ def kullback_leibler_divergence_gpu(x: np.ndarray, y: np.ndarray, fill_value: in
847
890
  .. seealso::
848
891
  For CPU implementation, see :func:`simba.mixins.statistics_mixin.Statistics.kullback_leibler_divergence`.
849
892
 
850
-
851
893
  :param ndarray x: First 1d array representing feature values.
852
894
  :param ndarray y: Second 1d array representing feature values.
853
895
  :param Optional[int] fill_value: Optional pseudo-value to use to fill empty buckets in ``y`` histogram
@@ -860,13 +902,18 @@ def kullback_leibler_divergence_gpu(x: np.ndarray, y: np.ndarray, fill_value: in
860
902
  >>> kl = kullback_leibler_divergence_gpu(x=x, y=y)
861
903
  """
862
904
 
905
+ timer = SimbaTimer(start=True)
906
+
863
907
  bin_width, bin_count = bucket_data(data=x, method=bucket_method)
864
908
  r = np.array([np.min(x), np.max(x)])
865
909
  x_hist = Statistics._hist_1d(data=x, bin_count=bin_count, range=r)
866
910
  y_hist = Statistics._hist_1d(data=y, bin_count=bin_count, range=r)
867
911
  y_hist[y_hist == 0] = fill_value
868
912
  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)
913
+ r = kl_divergence_gpu(P=x_hist.astype(np.float32), Q=y_hist.astype(np.float32), convert_dtype=False)
914
+ timer.stop_timer()
915
+ if verbose: print(f'KL divergence performed on {x.shape[0]} observations (elapsed time: {timer.elapsed_time_str}s)')
916
+ return r
870
917
 
871
918
 
872
919
  @cuda.jit()
@@ -307,7 +307,7 @@ def sliding_hjort_parameters_gpu(data: np.ndarray, window_sizes: np.ndarray, sam
307
307
  """
308
308
  Compute Hjorth parameters over sliding windows on the GPU.
309
309
 
310
- .. seelalso::
310
+ .. seealso::
311
311
  For CPU implementation, see :`simba.mixins.timeseries_features_mixin.TimeseriesFeatureMixin.hjort_parameters`
312
312
 
313
313
  :param np.ndarray data: 1D numeric array of signal data.
@@ -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
@@ -51,7 +52,7 @@ class EgocentricalAligner():
51
52
  :param Optional[int] core_cnt: Number of CPU cores to use for video rotation; `-1` uses all available cores.
52
53
 
53
54
  :example:
54
- >>> aligner = EgocentricalAligner(rotate_video=True, anchor_1='tail_base', anchor_2='nose', data_dir=r"/data_dir", videos_dir=r'/videos_dir', save_dir=r"/save_dir", video_info=r"C:\troubleshooting\mitra\project_folder\logs\video_info.csv", direction=0, anchor_location=(250, 250), fill_clr=(0, 0, 0))
55
+ >>> aligner = EgocentricalAligner(rotate_video=True, anchor_1='tail_base', anchor_2='nose', data_dir=r"/data_dir", videos_dir=r'/videos_dir', save_dir=r"/save_dir", video_info=r"C:/troubleshooting/mitra/project_folder/logs/video_info.csv", direction=0, anchor_location=(250, 250), fill_clr=(0, 0, 0))
55
56
  >>> aligner.run()
56
57
  """
57
58
 
@@ -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, check_valid_boolean)
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, get_current_time, find_files_of_filetypes_in_directory, remove_a_folder, copy_files_to_directory
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
 
@@ -83,12 +83,12 @@ class FeatureSubsetsCalculator(ConfigReader, TrainModelMixin):
83
83
  :align: center
84
84
 
85
85
  :example:
86
- >>> test = FeatureSubsetsCalculator(config_path=r"C:\troubleshooting\mitra\project_folder\project_config.ini",
86
+ >>> test = FeatureSubsetsCalculator(config_path=r"C:/troubleshooting/mitra/project_folder/project_config.ini",
87
87
  >>> feature_families=[FRAME_BP_MOVEMENT, WITHIN_ANIMAL_THREE_POINT_ANGLES],
88
88
  >>> append_to_features_extracted=False,
89
89
  >>> file_checks=False,
90
90
  >>> append_to_targets_inserted=False,
91
- >>> save_dir=r"C:\troubleshooting\mitra\project_folder\csv\new_features")
91
+ >>> save_dir=r"C:/troubleshooting/mitra/project_folder/csv/new_features")
92
92
  >>> test.run()
93
93
  """
94
94
 
@@ -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
@@ -46,10 +44,10 @@ class StraubTailAnalyzer(ConfigReader):
46
44
  .. [1] Lazaro et al., Brainwide Genetic Capture for Conscious State Transitions, `biorxiv`, doi: https://doi.org/10.1101/2025.03.28.646066
47
45
 
48
46
  :example:
49
- >>> runner = StraubTailAnalyzer(config_path=r"C:\troubleshooting\mitra\project_folder\project_config.ini",
50
- >>> data_dir=r'C:\troubleshooting\mitra\project_folder\videos\additional\bg_removed\rotated',
51
- >>> video_dir=r'C:\troubleshooting\mitra\project_folder\videos\additional\bg_removed\rotated',
52
- >>> save_dir=r'C:\troubleshooting\mitra\project_folder\videos\additional\bg_removed\rotated\tail_features_additional',
47
+ >>> runner = StraubTailAnalyzer(config_path=r"C:/troubleshooting/mitra/project_folder/project_config.ini",
48
+ >>> data_dir=r'C:/troubleshooting/mitra/project_folder/videos/additional/bg_removed/rotated',
49
+ >>> video_dir=r'C:/troubleshooting/mitra/project_folder/videos/additional/bg_removed/rotated',
50
+ >>> save_dir=r'C:/troubleshooting/mitra/project_folder/videos/additional/bg_removed/rotated/tail_features_additional',
53
51
  >>> anchor_points=('tail_base', 'tail_center', 'tail_tip'),
54
52
  >>> body_parts=('nose', 'left_ear', 'right_ear', 'right_side', 'left_side', 'tail_base'))
55
53
  >>> runner.run()
@@ -64,7 +64,7 @@ class LabellingInterface(ConfigReader):
64
64
  :param bool continuing: Set True to resume annotations from an existing targets file. Defaults to False.
65
65
 
66
66
  :example:
67
- >>> _ = LabellingInterface(config_path=r"C:\troubleshooting\mitra\project_folder\project_config.ini", file_path=r"C:\troubleshooting\mitra\project_folder\videos\501_MA142_Gi_CNO_0521.mp4", thresholds=None, continuing=False)
67
+ >>> _ = LabellingInterface(config_path=r"C:/troubleshooting/mitra/project_folder/project_config.ini", file_path=r"C:/troubleshooting/mitra/project_folder/videos/501_MA142_Gi_CNO_0521.mp4", thresholds=None, continuing=False)
68
68
  """
69
69
 
70
70
  def __init__(self,
@@ -1556,7 +1556,7 @@ class GeometryMixin(object):
1556
1556
  :rtype: List[float]
1557
1557
 
1558
1558
  :example:
1559
- >>> df = read_df(file_path=r"C:\troubleshooting\two_black_animals_14bp\project_folder\csv\outlier_corrected_movement_location\Together_2.csv", file_type='csv').astype(int)
1559
+ >>> df = read_df(file_path=r"C:/troubleshooting/two_black_animals_14bp/project_folder/csv/outlier_corrected_movement_location/Together_2.csv", file_type='csv').astype(int)
1560
1560
  >>> animal_1_cols = [x for x in df.columns if '_1_' in x and not '_p' in x]
1561
1561
  >>> animal_2_cols = [x for x in df.columns if '_2_' in x and not '_p' in x]
1562
1562
  >>> animal_1_arr = df[animal_1_cols].values.reshape(len(df), int(len(animal_1_cols)/ 2), 2)
@@ -1622,7 +1622,7 @@ class GeometryMixin(object):
1622
1622
  :return List[float]: List of overlap between corresponding Polygons. If overlap 1, else 0.
1623
1623
 
1624
1624
  :example:
1625
- >>> df = read_df(file_path=r"C:\troubleshooting\two_black_animals_14bp\project_folder\csv\outlier_corrected_movement_location\Together_2.csv", file_type='csv').astype(int)
1625
+ >>> df = read_df(file_path=r"C:/troubleshooting/two_black_animals_14bp/project_folder/csv/outlier_corrected_movement_location/Together_2.csv", file_type='csv').astype(int)
1626
1626
  >>> animal_1_cols = [x for x in df.columns if '_1_' in x and not '_p' in x]
1627
1627
  >>> animal_2_cols = [x for x in df.columns if '_2_' in x and not '_p' in x]
1628
1628
  >>> animal_1_arr = df[animal_1_cols].values.reshape(len(df), int(len(animal_1_cols)/ 2), 2)
@@ -1693,7 +1693,7 @@ class GeometryMixin(object):
1693
1693
  :rtype: List[float]
1694
1694
 
1695
1695
  :example:
1696
- >>> df = read_df(file_path=r"C:\troubleshooting\two_black_animals_14bp\project_folder\csv\outlier_corrected_movement_location\Together_2.csv", file_type='csv').astype(int)
1696
+ >>> df = read_df(file_path=r"C:/troubleshooting/two_black_animals_14bp/project_folder/csv/outlier_corrected_movement_location/Together_2.csv", file_type='csv').astype(int)
1697
1697
  >>> animal_1_cols = [x for x in df.columns if '_1_' in x and not '_p' in x]
1698
1698
  >>> animal_2_cols = [x for x in df.columns if '_2_' in x and not '_p' in x]
1699
1699
  >>> animal_1_arr = df[animal_1_cols].values.reshape(len(df), int(len(animal_1_cols)/ 2), 2)
@@ -1763,7 +1763,7 @@ class GeometryMixin(object):
1763
1763
  :rtype: List[Polygon]
1764
1764
 
1765
1765
  :example:
1766
- >>> df = read_df(file_path=r"C:\troubleshooting\two_black_animals_14bp\project_folder\csv\outlier_corrected_movement_location\Together_2.csv", file_type='csv').astype(int)
1766
+ >>> df = read_df(file_path=r"C:/troubleshooting/two_black_animals_14bp/project_folder/csv/outlier_corrected_movement_location/Together_2.csv", file_type='csv').astype(int)
1767
1767
  >>> animal_1_cols = [x for x in df.columns if '_1_' in x and not '_p' in x]
1768
1768
  >>> animal_1_arr = df[animal_1_cols].values.reshape(len(df), int(len(animal_1_cols)/ 2), 2)
1769
1769
  >>> animal_1_geo = GeometryMixin.bodyparts_to_polygon(data=animal_1_arr)
@@ -3525,10 +3525,10 @@ class GeometryMixin(object):
3525
3525
  :rtype: Tuple[Dict[Tuple[int, int], Dict[Tuple[int, int], float]], Dict[Tuple[int, int], Dict[Tuple[int, int], int]]]
3526
3526
 
3527
3527
  :example:
3528
- >>> video_meta_data = get_video_meta_data(video_path=r"C:\troubleshooting\mitra\project_folder\videos\708_MA149_Gq_CNO_0515.mp4")
3528
+ >>> video_meta_data = get_video_meta_data(video_path=r"C:/troubleshooting/mitra/project_folder/videos/708_MA149_Gq_CNO_0515.mp4")
3529
3529
  >>> w, h = video_meta_data['width'], video_meta_data['height']
3530
3530
  >>> grid = GeometryMixin().bucket_img_into_grid_square(bucket_grid_size=(5, 5), bucket_grid_size_mm=None, img_size=(h, w), verbose=False)[0]
3531
- >>> data = read_df(file_path=r'C:\troubleshooting\mitra\project_folder\csv\outlier_corrected_movement_location\708_MA149_Gq_CNO_0515.csv', file_type='csv')[['Nose_x', 'Nose_y']].values
3531
+ >>> data = read_df(file_path=r'C:/troubleshooting/mitra/project_folder/csv/outlier_corrected_movement_location/708_MA149_Gq_CNO_0515.csv', file_type='csv')[['Nose_x', 'Nose_y']].values
3532
3532
  >>> transition_probabilities, _ = geometry_transition_probabilities(data=data, grid=grid)
3533
3533
  """
3534
3534
 
@@ -3990,7 +3990,7 @@ class GeometryMixin(object):
3990
3990
  :rtype: np.ndarray
3991
3991
 
3992
3992
  :example:
3993
- >>> data_path = r"C:\troubleshooting\mitra\project_folder\csv\outlier_corrected_movement_location\FRR_gq_Saline_0624.csv"
3993
+ >>> data_path = r"C:/troubleshooting/mitra/project_folder/csv/outlier_corrected_movement_location/FRR_gq_Saline_0624.csv"
3994
3994
  >>> animal_data = read_df(file_path=data_path, file_type='csv', usecols=['Nose_x', 'Nose_y', 'Tail_base_x', 'Tail_base_y', 'Left_side_x', 'Left_side_y', 'Right_side_x', 'Right_side_y']).values.reshape(-1, 4, 2)[0:20].astype(np.int32)
3995
3995
  >>> animal_polygons = GeometryMixin().bodyparts_to_polygon(data=animal_data)
3996
3996
  >>> GeometryMixin.geometries_to_exterior_keypoints(geometries=animal_polygons)
@@ -4160,7 +4160,7 @@ class GeometryMixin(object):
4160
4160
  :rtype: Union[None, Dict[Any, dict]]
4161
4161
 
4162
4162
  :example I:
4163
- >>> results = GeometryMixin.sleap_csv_to_geometries(data=r"C:\troubleshooting\ants\pose_data\ant.csv")
4163
+ >>> results = GeometryMixin.sleap_csv_to_geometries(data=r"C:/troubleshooting/ants/pose_data/ant.csv")
4164
4164
  >>> # Results structure: {track_id: {frame_idx: Polygon, ...}, ...}
4165
4165
 
4166
4166
  :example II
@@ -57,17 +57,16 @@ class ImageMixin(object):
57
57
  pass
58
58
 
59
59
  @staticmethod
60
- def brightness_intensity(imgs: List[np.ndarray], ignore_black: Optional[bool] = True) -> List[float]:
60
+ def brightness_intensity(imgs: Union[List[np.ndarray], np.ndarray], ignore_black: bool = True, verbose: bool = False) -> np.ndarray:
61
61
  """
62
62
  Compute the average brightness intensity within each image within a list.
63
63
 
64
64
  For example, (i) create a list of images containing a light cue ROI, (ii) compute brightness in each image, (iii) perform kmeans on brightness, and get the frames when the light cue is on vs off.
65
65
 
66
66
  .. seealso::
67
- For GPU acceleration, see :func:`simba.data_processors.cuda.image.img_stack_brightness`.
68
- For geometry based brightness, see :func:`simba.mixins.geometry_mixin.GeometryMixin.get_geometry_brightness_intensity`
67
+ For GPU acceleration, see :func:`simba.data_processors.cuda.image.img_stack_brightness`. For geometry based brightness, see :func:`simba.mixins.geometry_mixin.GeometryMixin.get_geometry_brightness_intensity`
69
68
 
70
- :param List[np.ndarray] imgs: List of images as arrays to calculate average brightness intensity within.
69
+ :param Union[List[np.ndarray], np.ndarray] imgs: List of images as arrays or 3/4d array of images to calculate average brightness intensity within.
71
70
  :param Optional[bool] ignore_black: If True, ignores black pixels. If the images are sliced non-rectangular geometric shapes created by ``slice_shapes_in_img``, then pixels that don't belong to the shape has been masked in black.
72
71
  :returns: List of floats of size len(imgs) with brightness intensities.
73
72
  :rtype: List[float]
@@ -77,14 +76,12 @@ class ImageMixin(object):
77
76
  >>> ImageMixin.brightness_intensity(imgs=[img], ignore_black=False)
78
77
  >>> [159.0]
79
78
  """
80
- results = []
81
- check_instance(source=f"{ImageMixin().brightness_intensity.__name__} imgs", instance=imgs, accepted_types=list)
82
- for cnt, img in enumerate(imgs):
83
- check_instance(
84
- source=f"{ImageMixin().brightness_intensity.__name__} img {cnt}",
85
- instance=img,
86
- accepted_types=np.ndarray,
87
- )
79
+ results, timer = [], SimbaTimer(start=True)
80
+ check_instance(source=f"{ImageMixin().brightness_intensity.__name__} imgs", instance=imgs, accepted_types=(list, np.ndarray,))
81
+ if isinstance(imgs, np.ndarray): imgs = np.array(imgs)
82
+ for img_cnt in range(imgs.shape[0]):
83
+ img = imgs[img_cnt]
84
+ check_instance(source=f"{ImageMixin().brightness_intensity.__name__} img {img_cnt}", instance=img, accepted_types=np.ndarray)
88
85
  if len(img) == 0:
89
86
  results.append(0)
90
87
  else:
@@ -92,7 +89,10 @@ class ImageMixin(object):
92
89
  results.append(np.ceil(np.average(img[img != 0])))
93
90
  else:
94
91
  results.append(np.ceil(np.average(img)))
95
- return results
92
+ b = np.array(results).astype(np.float32)
93
+ timer.stop_timer()
94
+ if verbose: print(f'Brightness computed in {b.shape[0]} images (elapsed time {timer.elapsed_time_str}s)')
95
+
96
96
 
97
97
  @staticmethod
98
98
  def gaussian_blur(img: np.ndarray, kernel_size: Optional[Tuple] = (9, 9)) -> np.ndarray:
@@ -1898,7 +1898,7 @@ class ImageMixin(object):
1898
1898
  :rtype: np.ndarray
1899
1899
 
1900
1900
  :example:
1901
- >>> VIDEO_PATH = r"D:\EPM_2\EPM_1.mp4"
1901
+ >>> VIDEO_PATH = r"D:/EPM_2/EPM_1.mp4"
1902
1902
  >>> img = read_img_batch_from_video(video_path=VIDEO_PATH, greyscale=True, start_frm=0, end_frm=15, core_cnt=1)
1903
1903
  >>> imgs = np.stack(list(img.values()))
1904
1904
  >>> resized_img = resize_img_stack(imgs=imgs)