simba-uw-tf-dev 4.6.2__py3-none-any.whl → 4.7.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. simba/assets/.recent_projects.txt +1 -0
  2. simba/assets/lookups/tooptips.json +6 -1
  3. simba/data_processors/agg_clf_counter_mp.py +52 -53
  4. simba/data_processors/blob_location_computer.py +1 -1
  5. simba/data_processors/circling_detector.py +30 -13
  6. simba/data_processors/cuda/geometry.py +45 -27
  7. simba/data_processors/cuda/image.py +1648 -1598
  8. simba/data_processors/cuda/statistics.py +72 -26
  9. simba/data_processors/cuda/timeseries.py +1 -1
  10. simba/data_processors/cue_light_analyzer.py +5 -9
  11. simba/data_processors/egocentric_aligner.py +25 -7
  12. simba/data_processors/freezing_detector.py +55 -47
  13. simba/data_processors/kleinberg_calculator.py +61 -29
  14. simba/feature_extractors/feature_subsets.py +14 -7
  15. simba/feature_extractors/mitra_feature_extractor.py +2 -2
  16. simba/feature_extractors/straub_tail_analyzer.py +4 -6
  17. simba/labelling/standard_labeller.py +1 -1
  18. simba/mixins/config_reader.py +5 -2
  19. simba/mixins/geometry_mixin.py +22 -36
  20. simba/mixins/image_mixin.py +24 -28
  21. simba/mixins/plotting_mixin.py +28 -10
  22. simba/mixins/statistics_mixin.py +48 -11
  23. simba/mixins/timeseries_features_mixin.py +1 -1
  24. simba/mixins/train_model_mixin.py +67 -29
  25. simba/model/inference_batch.py +1 -1
  26. simba/model/yolo_seg_inference.py +3 -3
  27. simba/outlier_tools/skip_outlier_correction.py +1 -1
  28. simba/plotting/ROI_feature_visualizer_mp.py +3 -5
  29. simba/plotting/clf_validator_mp.py +4 -5
  30. simba/plotting/cue_light_visualizer.py +6 -7
  31. simba/plotting/directing_animals_visualizer_mp.py +2 -3
  32. simba/plotting/distance_plotter_mp.py +378 -378
  33. simba/plotting/gantt_creator.py +29 -10
  34. simba/plotting/gantt_creator_mp.py +96 -33
  35. simba/plotting/geometry_plotter.py +270 -272
  36. simba/plotting/heat_mapper_clf_mp.py +4 -6
  37. simba/plotting/heat_mapper_location_mp.py +2 -2
  38. simba/plotting/light_dark_box_plotter.py +2 -2
  39. simba/plotting/path_plotter_mp.py +26 -29
  40. simba/plotting/plot_clf_results_mp.py +455 -454
  41. simba/plotting/pose_plotter_mp.py +28 -29
  42. simba/plotting/probability_plot_creator_mp.py +288 -288
  43. simba/plotting/roi_plotter_mp.py +31 -31
  44. simba/plotting/single_run_model_validation_video_mp.py +427 -427
  45. simba/plotting/spontaneous_alternation_plotter.py +2 -3
  46. simba/plotting/yolo_pose_track_visualizer.py +32 -27
  47. simba/plotting/yolo_pose_visualizer.py +35 -36
  48. simba/plotting/yolo_seg_visualizer.py +2 -3
  49. simba/pose_importers/simba_blob_importer.py +3 -3
  50. simba/roi_tools/roi_aggregate_stats_mp.py +5 -4
  51. simba/roi_tools/roi_clf_calculator_mp.py +4 -4
  52. simba/sandbox/analyze_runtimes.py +30 -0
  53. simba/sandbox/cuda/egocentric_rotator.py +374 -374
  54. simba/sandbox/get_cpu_pool.py +5 -0
  55. simba/sandbox/proboscis_to_tip.py +28 -0
  56. simba/sandbox/test_directionality.py +47 -0
  57. simba/sandbox/test_nonstatic_directionality.py +27 -0
  58. simba/sandbox/test_pycharm_cuda.py +51 -0
  59. simba/sandbox/test_simba_install.py +41 -0
  60. simba/sandbox/test_static_directionality.py +26 -0
  61. simba/sandbox/test_static_directionality_2d.py +26 -0
  62. simba/sandbox/verify_env.py +42 -0
  63. simba/third_party_label_appenders/transform/coco_keypoints_to_yolo.py +3 -3
  64. simba/third_party_label_appenders/transform/coco_keypoints_to_yolo_bbox.py +2 -2
  65. simba/ui/pop_ups/clf_plot_pop_up.py +2 -2
  66. simba/ui/pop_ups/fsttc_pop_up.py +27 -25
  67. simba/ui/pop_ups/gantt_pop_up.py +31 -6
  68. simba/ui/pop_ups/kleinberg_pop_up.py +39 -40
  69. simba/ui/pop_ups/video_processing_pop_up.py +37 -29
  70. simba/ui/tkinter_functions.py +3 -0
  71. simba/utils/custom_feature_extractor.py +1 -1
  72. simba/utils/data.py +90 -14
  73. simba/utils/enums.py +1 -0
  74. simba/utils/errors.py +441 -440
  75. simba/utils/lookups.py +1203 -1203
  76. simba/utils/printing.py +124 -124
  77. simba/utils/read_write.py +3769 -3721
  78. simba/utils/yolo.py +10 -1
  79. simba/video_processors/blob_tracking_executor.py +2 -2
  80. simba/video_processors/clahe_ui.py +1 -1
  81. simba/video_processors/egocentric_video_rotator.py +44 -41
  82. simba/video_processors/multi_cropper.py +1 -1
  83. simba/video_processors/video_processing.py +5264 -5222
  84. simba/video_processors/videos_to_frames.py +43 -33
  85. {simba_uw_tf_dev-4.6.2.dist-info → simba_uw_tf_dev-4.7.1.dist-info}/METADATA +4 -3
  86. {simba_uw_tf_dev-4.6.2.dist-info → simba_uw_tf_dev-4.7.1.dist-info}/RECORD +90 -80
  87. {simba_uw_tf_dev-4.6.2.dist-info → simba_uw_tf_dev-4.7.1.dist-info}/LICENSE +0 -0
  88. {simba_uw_tf_dev-4.6.2.dist-info → simba_uw_tf_dev-4.7.1.dist-info}/WHEEL +0 -0
  89. {simba_uw_tf_dev-4.6.2.dist-info → simba_uw_tf_dev-4.7.1.dist-info}/entry_points.txt +0 -0
  90. {simba_uw_tf_dev-4.6.2.dist-info → simba_uw_tf_dev-4.7.1.dist-info}/top_level.txt +0 -0
@@ -8,12 +8,13 @@ from simba.data_processors.kleinberg_calculator import KleinbergCalculator
8
8
  from simba.mixins.config_reader import ConfigReader
9
9
  from simba.mixins.pop_up_mixin import PopUpMixin
10
10
  from simba.ui.tkinter_functions import (CreateLabelFrameWithIcon, Entry_Box,
11
- SimbaButton, SimBADropDown)
11
+ SimbaButton, SimBADropDown, SimBALabel)
12
12
  from simba.utils.checks import check_float, check_int
13
13
  from simba.utils.enums import Formats, Keys, Links
14
14
  from simba.utils.errors import NoChoosenClassifierError, NoDataError
15
- from simba.utils.read_write import str_2_bool
15
+ from simba.utils.read_write import get_current_time, str_2_bool
16
16
 
17
+ INSTRUCTIONS_TXT = 'Results in the project_folder/csv/machine_results folder are overwritten.\n If saving the originals, the original un-smoothened data is saved in a subdirectory of \nthe project_folder/csv/machine_results folder'
17
18
 
18
19
  class KleinbergPopUp(PopUpMixin, ConfigReader):
19
20
  def __init__(self,
@@ -24,61 +25,59 @@ class KleinbergPopUp(PopUpMixin, ConfigReader):
24
25
  raise NoDataError(msg=f'Cannot perform Kleinberg smoothing: No data files found in {self.machine_results_dir} directory', source=self.__class__.__name__)
25
26
  PopUpMixin.__init__(self, title="APPLY KLEINBERG BEHAVIOR CLASSIFICATION SMOOTHING", icon='smooth')
26
27
  kleinberg_settings_frm = CreateLabelFrameWithIcon(parent=self.main_frm, header="KLEINBERG SETTINGS", icon_name=Keys.DOCUMENTATION.value, icon_link=Links.KLEINBERG.value)
27
- self.k_sigma = Entry_Box(kleinberg_settings_frm, fileDescription="SIGMA", img='sigma', value='2', justify='center', labelwidth=35, entry_box_width=35)
28
- self.k_gamma = Entry_Box(kleinberg_settings_frm, fileDescription="GAMMA", img='gamma', value='0.3', justify='center', labelwidth=35, entry_box_width=35)
29
- self.k_hierarchy = Entry_Box(kleinberg_settings_frm, fileDescription="HIERARCHY", value=1, img='hierarchy_2', justify='center', labelwidth=35, entry_box_width=35)
30
- self.h_search_dropdown = SimBADropDown(parent=kleinberg_settings_frm, dropdown_options=['TRUE', 'FALSE'], label="HIERACHICAL SEARCH", value='FALSE', img='hierarchy', label_width=35, dropdown_width=35)
28
+ self.k_sigma = Entry_Box(kleinberg_settings_frm, fileDescription="SIGMA", img='sigma', value='2', justify='center', labelwidth=35, entry_box_width=35, tooltip_key='KLEINBERG_SIGMA')
29
+ self.k_gamma = Entry_Box(kleinberg_settings_frm, fileDescription="GAMMA", img='gamma', value='0.3', justify='center', labelwidth=35, entry_box_width=35, tooltip_key='KLEINBERG_GAMMA')
30
+ self.k_hierarchy = Entry_Box(kleinberg_settings_frm, fileDescription="HIERARCHY", value=1, img='hierarchy_2', justify='center', labelwidth=35, entry_box_width=35, validation='numeric', tooltip_key='KLEINBERG_HIERARCHY')
31
+ self.h_search_dropdown = SimBADropDown(parent=kleinberg_settings_frm, dropdown_options=['TRUE', 'FALSE'], label="HIERARCHICAL SEARCH", value='FALSE', img='hierarchy', label_width=35, dropdown_width=35, tooltip_key='KLEINBERG_HIERARCHY_SEARCH')
32
+ self.save_originals_dropdown = SimBADropDown(parent=kleinberg_settings_frm, dropdown_options=['TRUE', 'FALSE'], label="SAVE ORIGINAL DATA:", value='TRUE', img='save', label_width=35, dropdown_width=35, tooltip_key='KLEINBERG_SAVE_ORIGINALS')
33
+ self.instructions_lbl = SimBALabel(parent=kleinberg_settings_frm, txt=INSTRUCTIONS_TXT, justify='center', txt_clr='blue', font=Formats.FONT_REGULAR_ITALICS.value)
31
34
 
32
35
 
33
- kleinberg_table_frame = LabelFrame(self.main_frm, text="CHOOSE CLASSIFIER(S) FOR KLEINBERG SMOOTHING", font=Formats.FONT_HEADER.value)
36
+ kleinberg_table_frame = CreateLabelFrameWithIcon(parent=self.main_frm, header="CHOOSE CLASSIFIER(S) FOR KLEINBERG SMOOTHING", icon_name=Keys.DOCUMENTATION.value, icon_link=Links.KLEINBERG.value)
34
37
  clf_var_dict, clf_cb_dict = {}, {}
35
38
  for clf_cnt, clf in enumerate(self.clf_names):
36
39
  clf_var_dict[clf] = BooleanVar()
37
40
  clf_cb_dict[clf] = Checkbutton(kleinberg_table_frame, text=clf, font=Formats.FONT_REGULAR.value, variable=clf_var_dict[clf])
38
- clf_cb_dict[clf].grid(row=clf_cnt, sticky=NW)
41
+ clf_cb_dict[clf].grid(row=clf_cnt, column=0, sticky=NW)
39
42
 
40
43
  run_kleinberg_btn = SimbaButton(parent=self.main_frm, txt="APPLY KLEINBERG SMOOTHER", img='rocket', txt_clr="blue", font=Formats.FONT_REGULAR.value, cmd=self.run_kleinberg, cmd_kwargs={'behaviors_dict': lambda: clf_var_dict, 'hierarchical_search': lambda: str_2_bool(self.h_search_dropdown.get_value())})
41
- kleinberg_settings_frm.grid(row=0, sticky=W, padx=10)
42
- self.k_sigma.grid(row=0, sticky=W)
43
- self.k_gamma.grid(row=1, sticky=W)
44
- self.k_hierarchy.grid(row=2, sticky=W)
45
- self.h_search_dropdown.grid(row=3, column=0, sticky=W)
46
- kleinberg_table_frame.grid(row=1, pady=10, padx=10)
47
- run_kleinberg_btn.grid(row=2)
44
+ kleinberg_settings_frm.grid(row=0, sticky=W, pady=(15, 0))
45
+ self.instructions_lbl.grid(row=0, sticky=W)
46
+ self.k_sigma.grid(row=1, sticky=W)
47
+ self.k_gamma.grid(row=2, sticky=W)
48
+ self.k_hierarchy.grid(row=3, sticky=W)
49
+ self.h_search_dropdown.grid(row=4, column=0, sticky=W)
50
+ self.save_originals_dropdown.grid(row=5, column=0, sticky=W)
51
+ kleinberg_table_frame.grid(row=1, column=0, sticky=NW, pady=(15, 0))
52
+ run_kleinberg_btn.grid(row=2, column=0, sticky=NW, pady=(15, 0))
48
53
  self.main_frm.mainloop()
49
54
 
50
55
  def run_kleinberg(self, behaviors_dict: dict, hierarchical_search: bool):
51
56
  targets = []
52
57
  for behaviour, behavior_val in behaviors_dict.items():
53
- if behavior_val.get():
54
- targets.append(behaviour)
58
+ if behavior_val.get(): targets.append(behaviour)
55
59
 
56
60
  if len(targets) == 0:
57
61
  raise NoChoosenClassifierError(source=self.__class__.__name__)
58
62
 
59
- check_int(name="Hierarchy", value=self.k_hierarchy.entry_get)
60
- check_float(name="Sigma", value=self.k_sigma.entry_get)
61
- check_float(name="Gamma", value=self.k_gamma.entry_get)
62
-
63
- try:
64
- print(
65
- "Applying kleinberg hyperparameter Setting: Sigma: {}, Gamma: {}, Hierarchy: {}".format(
66
- str(self.k_sigma.entry_get),
67
- str(self.k_gamma.entry_get),
68
- str(self.k_hierarchy.entry_get),
69
- )
70
- )
71
- except:
72
- print("Please insert accurate values for all hyperparameters.")
73
-
74
- kleinberg_analyzer = KleinbergCalculator(
75
- config_path=self.config_path,
76
- classifier_names=targets,
77
- sigma=self.k_sigma.entry_get,
78
- gamma=self.k_gamma.entry_get,
79
- hierarchy=self.k_hierarchy.entry_get,
80
- hierarchical_search=hierarchical_search,
81
- )
63
+ k_hierarchy = self.k_hierarchy.entry_get
64
+ k_sigma = self.k_sigma.entry_get
65
+ k_gamma = self.k_gamma.entry_get
66
+ save_originals = str_2_bool(self.save_originals_dropdown.get_value())
67
+
68
+ check_int(name="Hierarchy", value=k_hierarchy, min_value=1, allow_negative=False, allow_zero=False, raise_error=True)
69
+ check_float(name="Sigma", value=k_sigma, allow_negative=False, allow_zero=False, raise_error=True)
70
+ check_float(name="Gamma", value=k_gamma, allow_negative=False, allow_zero=False, raise_error=True)
71
+
72
+ print(f"[{get_current_time()}] Applying kleinberg hyperparameter Setting: Sigma: {k_sigma}, Gamma: {k_gamma}, Hierarchy: {k_hierarchy}")
73
+
74
+ kleinberg_analyzer = KleinbergCalculator(config_path=self.config_path,
75
+ classifier_names=targets,
76
+ sigma=float(k_sigma),
77
+ gamma=float(k_gamma),
78
+ hierarchy=int(k_hierarchy),
79
+ hierarchical_search=hierarchical_search,
80
+ save_originals=save_originals)
82
81
  kleinberg_analyzer.run()
83
82
 
84
83
 
@@ -24,7 +24,7 @@ from simba.ui.tkinter_functions import (CreateLabelFrameWithIcon,
24
24
  FileSelect, FolderSelect, SimbaButton,
25
25
  SimbaCheckbox, SimBADropDown,
26
26
  SimBALabel, SimBARadioButton,
27
- SimBAScaleBar)
27
+ SimBAScaleBar, SimBASeperator)
28
28
  from simba.utils.checks import (check_ffmpeg_available,
29
29
  check_file_exist_and_readable,
30
30
  check_if_dir_exists,
@@ -1505,23 +1505,23 @@ class ClipMultipleVideosByFrameNumbersPopUp(PopUpMixin):
1505
1505
  max_video_name_len = len(max(list(self.video_paths.keys())))
1506
1506
  super().__init__(title="CLIP MULTIPLE VIDEOS BY FRAME NUMBERS", icon='clip')
1507
1507
  self.save_dir = save_dir
1508
+ padx = (0, 25)
1508
1509
  data_frm = CreateLabelFrameWithIcon(parent=self.main_frm, header="VIDEO SETTINGS", icon_name=Keys.DOCUMENTATION.value, icon_link=Links.VIDEO_TOOLS.value)
1509
- data_frm.grid(row=2, column=0, sticky=NW)
1510
-
1511
-
1510
+ SimBALabel(parent=data_frm, font=Formats.FONT_REGULAR.value, txt="VIDEO NAME", justify='center', img='video').grid(row=0, column=0, padx=padx, sticky=NW)
1511
+ SimBALabel(parent=data_frm, font=Formats.FONT_REGULAR.value, txt="START FRAME", justify='center', img='play').grid(row=0, column=2, padx=padx)
1512
+ SimBALabel(parent=data_frm, font=Formats.FONT_REGULAR.value, txt="END FRAME", justify='center', img='stop').grid(row=0, column=3, padx=padx)
1512
1513
 
1513
- SimBALabel(parent=data_frm, width=max_video_name_len+20, font=Formats.FONT_REGULAR.value, txt="VIDEO NAME", justify='center').grid(row=0, column=0)
1514
- SimBALabel(parent=data_frm, width=max_video_name_len+20, font=Formats.FONT_REGULAR.value, txt="START FRAME", justify='center').grid(row=0, column=2)
1515
- SimBALabel(parent=data_frm, width=max_video_name_len+20, font=Formats.FONT_REGULAR.value, txt="END FRAME", justify='center').grid(row=0, column=3)
1514
+ seperator = SimBASeperator(parent=data_frm, color=None, orient='horizontal', borderwidth=1)
1515
+ seperator.grid(row=1, column=0, columnspan=4, rowspan=1, sticky="ew")
1516
1516
 
1517
1517
  self.entry_boxes = {}
1518
1518
  for cnt, video_name in enumerate(self.video_paths.keys()):
1519
1519
  self.entry_boxes[video_name] = {}
1520
- SimBALabel(parent=data_frm, width=max_video_name_len + 20, font=Formats.FONT_REGULAR.value, txt=video_name + ' ' + f'({ self.video_meta_data[cnt]})', justify='center').grid(row=cnt + 1, column=0)
1521
- self.entry_boxes[video_name]["start"] = Entry_Box(data_frm, "", 0, validation="numeric")
1522
- self.entry_boxes[video_name]["end"] = Entry_Box(data_frm, "", 0, validation="numeric")
1523
- self.entry_boxes[video_name]["start"].grid(row=cnt + 1, column=2, sticky=NW)
1524
- self.entry_boxes[video_name]["end"].grid(row=cnt + 1, column=3, sticky=NW)
1520
+ SimBALabel(parent=data_frm, font=Formats.FONT_REGULAR.value, txt=video_name + f' (frames: { self.video_meta_data[cnt]})', justify='left').grid(row=cnt + 2, column=0, padx=padx, sticky=NW)
1521
+ self.entry_boxes[video_name]["start"] = Entry_Box(data_frm, fileDescription="", labelwidth=0, validation="numeric", justify='center')
1522
+ self.entry_boxes[video_name]["end"] = Entry_Box(data_frm, fileDescription="", labelwidth=0, validation="numeric", justify='center')
1523
+ self.entry_boxes[video_name]["start"].grid(row=cnt + 2, column=2, sticky=NW, padx=padx)
1524
+ self.entry_boxes[video_name]["end"].grid(row=cnt + 2, column=3, sticky=NW, padx=padx)
1525
1525
 
1526
1526
  gpu_state = NORMAL if check_nvidea_gpu_available(raise_error=False) else DISABLED
1527
1527
  batch_settings_frm = CreateLabelFrameWithIcon(parent=self.main_frm, header="BATCH SETTINGS", icon_name=Keys.DOCUMENTATION.value, icon_link=Links.VIDEO_TOOLS.value)
@@ -1531,6 +1531,7 @@ class ClipMultipleVideosByFrameNumbersPopUp(PopUpMixin):
1531
1531
  batch_end_btn = SimbaButton(parent=batch_settings_frm, txt='SET', img='tick', cmd=self._batch_set_val, cmd_kwargs={'text': lambda: batch_end_entry.entry_get.strip(), 'box_type': lambda: 'end'})
1532
1532
  self.gpu_dropdown = SimBADropDown(parent=batch_settings_frm, dropdown_options=['TRUE', 'FALSE'], label='USE GPU:', label_width=25, dropdown_width=12, tooltip_key='USE_GPU', img='gpu_3', state=gpu_state, value='FALSE')
1533
1533
  self.quality_dropdown = SimBADropDown(parent=batch_settings_frm, dropdown_options=list(range(10, 110, 10)), label='OUT VIDEO QUALITY:', label_width=25, dropdown_width=12, tooltip_key='OUTPUT_VIDEO_QUALITY', img='pct_2', value=60)
1534
+ data_frm.grid(row=2, column=0, sticky=NW)
1534
1535
  batch_settings_frm.grid(row=1, column=0, sticky=NW)
1535
1536
  batch_start_entry.grid(row=0, column=0, sticky=NW)
1536
1537
  batch_start_btn.grid(row=0, column=1, sticky=NW)
@@ -1590,6 +1591,9 @@ class ClipMultipleVideosByFrameNumbersPopUp(PopUpMixin):
1590
1591
 
1591
1592
 
1592
1593
  #ClipMultipleVideosByFrameNumbersPopUp(data_dir=r'E:\netholabs_videos\terry\mp4s\4_02_001_exp_2025_12_02_15_22_00\videos\Camera2', save_dir=r'E:\netholabs_videos\terry\mp4s\4_02_001_exp_2025_12_02_15_22_00\videos\Camera2\test')
1594
+ #ClipMultipleVideosByFrameNumbersPopUp(data_dir=r"E:\maplight_videos\test_0126", save_dir=r"E:\maplight_videos\clip_test")
1595
+
1596
+
1593
1597
 
1594
1598
  class InitiateClipMultipleVideosByFrameNumbersPopUp(PopUpMixin):
1595
1599
  def __init__(self):
@@ -1636,6 +1640,8 @@ class InitiateClipMultipleVideosByFrameNumbersPopUp(PopUpMixin):
1636
1640
  )
1637
1641
 
1638
1642
 
1643
+
1644
+
1639
1645
  class ClipMultipleVideosByTimestamps(PopUpMixin):
1640
1646
  """
1641
1647
  :example:
@@ -1648,13 +1654,12 @@ class ClipMultipleVideosByTimestamps(PopUpMixin):
1648
1654
  check_if_dir_exists(in_dir=save_dir, source=self.__class__.__name__, create_if_not_exist=True)
1649
1655
  self.video_paths = find_all_videos_in_directory(directory=data_dir, as_dict=True, raise_error=True)
1650
1656
  self.video_meta_data = [get_video_meta_data(video_path=x) for x in list(self.video_paths.values())]
1651
- max_video_name_len = len(max(list(self.video_paths.keys()))) + 25
1652
1657
  super().__init__(title="CLIP MULTIPLE VIDEOS BY TIME-STAMPS", icon='clip')
1653
1658
  self.save_dir = save_dir
1654
1659
  batch_settings_frm = CreateLabelFrameWithIcon(parent=self.main_frm, header="BATCH SETTINGS", icon_name=Keys.DOCUMENTATION.value, icon_link=Links.VIDEO_TOOLS.value)
1655
- batch_start_entry = Entry_Box(parent=batch_settings_frm, fileDescription='START TIME (HH:MM:SS):', labelwidth=25, entry_box_width=12, img='play')
1660
+ batch_start_entry = Entry_Box(parent=batch_settings_frm, fileDescription='START TIME (HH:MM:SS):', labelwidth=25, entry_box_width=12, img='play', justify='center')
1656
1661
  batch_start_btn = SimbaButton(parent=batch_settings_frm, txt='SET', img='tick', cmd=self._batch_set_val, cmd_kwargs={'text': lambda: batch_start_entry.entry_get.strip(), 'box_type': lambda: 'start'})
1657
- batch_end_entry = Entry_Box(parent=batch_settings_frm, fileDescription='END TIME (HH:MM:SS):', labelwidth=25, entry_box_width=12, img='stop')
1662
+ batch_end_entry = Entry_Box(parent=batch_settings_frm, fileDescription='END TIME (HH:MM:SS):', labelwidth=25, entry_box_width=12, img='stop', justify='center')
1658
1663
  batch_end_btn = SimbaButton(parent=batch_settings_frm, txt='SET', img='tick', cmd=self._batch_set_val, cmd_kwargs={'text': lambda: batch_end_entry.entry_get.strip(), 'box_type': lambda: 'end'})
1659
1664
  batch_settings_frm.grid(row=0, column=0, sticky=NW)
1660
1665
  batch_start_entry.grid(row=0, column=0, sticky=NW)
@@ -1667,25 +1672,28 @@ class ClipMultipleVideosByTimestamps(PopUpMixin):
1667
1672
  self.gpu_dropdown.grid(row=2, column=0, sticky=NW)
1668
1673
  self.quality_dropdown.grid(row=3, column=0, sticky=NW)
1669
1674
  self.save_dir = save_dir
1675
+ padx = (0, 30)
1670
1676
  data_frm = CreateLabelFrameWithIcon(parent=self.main_frm, header="VIDEO SETTINGS", icon_name='video', icon_link=Links.VIDEO_TOOLS.value)
1671
- data_frm.grid(row=1, column=0, sticky=NW)
1672
- Label(data_frm, text="VIDEO NAME", width=max_video_name_len).grid(row=0, column=0, sticky=NW)
1673
- Label(data_frm, text="VIDEO LENGTH", width=12).grid(row=0, column=1)
1674
- Label(data_frm, text="START TIME (HH:MM:SS)", width=18).grid(row=0, column=2)
1675
- Label(data_frm, text="END TIME (HH:MM:SS)", width=18).grid(row=0, column=3)
1677
+
1678
+ SimBALabel(data_frm, txt="VIDEO NAME", justify='center', font=Formats.FONT_REGULAR_BOLD.value, img='video').grid(row=0, column=0, sticky=NW, padx=padx)
1679
+ SimBALabel(data_frm, txt="VIDEO LENGTH", justify='center', font=Formats.FONT_REGULAR_BOLD.value, img='timer_2').grid(row=0, column=1, sticky=NW, padx=padx)
1680
+ SimBALabel(data_frm, txt="START TIME (HH:MM:SS)", justify='center', font=Formats.FONT_REGULAR_BOLD.value, img='play').grid(row=0, column=2, sticky=NW, padx=padx)
1681
+ SimBALabel(data_frm, txt="END TIME (HH:MM:SS)", justify='center', font=Formats.FONT_REGULAR_BOLD.value, img='stop').grid(row=0, column=3, sticky=NW, padx=padx)
1682
+ seperator = SimBASeperator(parent=data_frm, color=None, orient='horizontal', borderwidth=1)
1683
+ seperator.grid(row=1, column=0, columnspan=4, rowspan=1, sticky="ew")
1676
1684
 
1677
1685
  self.entry_boxes = {}
1678
1686
  for cnt, video_name in enumerate(self.video_paths.keys()):
1679
1687
  self.entry_boxes[video_name] = {}
1680
- SimBALabel(parent=data_frm, txt=video_name, width=max_video_name_len, justify='center').grid(row=cnt + 1, column=0, sticky=NW)
1688
+ SimBALabel(parent=data_frm, txt=video_name, justify='center').grid(row=cnt + 2, column=0, sticky=NW, padx=padx)
1681
1689
  video_length = self.video_meta_data[cnt]["video_length_s"]
1682
1690
  video_length_hhmmss = seconds_to_timestamp(seconds=video_length)
1683
- Label(data_frm, text=video_length_hhmmss, width=max_video_name_len).grid(row=cnt + 1, column=1, sticky=NW)
1684
- self.entry_boxes[video_name]["start"] = Entry_Box(data_frm, "", 5)
1685
- self.entry_boxes[video_name]["end"] = Entry_Box(data_frm, "", 5)
1686
- self.entry_boxes[video_name]["start"].grid(row=cnt + 1, column=2, sticky=NW)
1687
- self.entry_boxes[video_name]["end"].grid(row=cnt + 1, column=3, sticky=NW)
1688
-
1691
+ SimBALabel(data_frm, txt=video_length_hhmmss, justify='center').grid(row=cnt + 2, column=1, sticky=NW, padx=padx)
1692
+ self.entry_boxes[video_name]["start"] = Entry_Box(data_frm, fileDescription="", labelwidth=0, justify='center')
1693
+ self.entry_boxes[video_name]["end"] = Entry_Box(data_frm, fileDescription="", labelwidth=0, justify='center')
1694
+ self.entry_boxes[video_name]["start"].grid(row=cnt + 2, column=2, sticky=NW, padx=padx)
1695
+ self.entry_boxes[video_name]["end"].grid(row=cnt + 2, column=3, sticky=NW, padx=padx)
1696
+ data_frm.grid(row=1, column=0, sticky=NW)
1689
1697
  self.create_run_frm(run_function=self.run, btn_txt_clr="blue")
1690
1698
  self.main_frm.mainloop()
1691
1699
 
@@ -1708,11 +1716,11 @@ class ClipMultipleVideosByTimestamps(PopUpMixin):
1708
1716
  check_that_hhmmss_start_is_before_end(start_time=start, end_time=end, name=video_name)
1709
1717
  check_if_hhmmss_timestamp_is_valid_part_of_video(timestamp=start, video_path=self.video_paths[video_name])
1710
1718
  check_if_hhmmss_timestamp_is_valid_part_of_video(timestamp=end, video_path=self.video_paths[video_name])
1711
- clip_video_in_range(file_path=self.video_paths[video_name], start_time=start, end_time=end, out_dir=self.save_dir, overwrite=True, include_clip_time_in_filename=False, gpu=gpu, quality=quality_pct)
1719
+ clip_video_in_range(file_path=self.video_paths[video_name], start_time=start, end_time=end, out_dir=self.save_dir, overwrite=True, include_clip_time_in_filename=False, gpu=gpu, quality=quality_pct, codec='libx264', verbose=True)
1712
1720
  timer.stop_timer()
1713
1721
  stdout_success(msg=f"{len(self.entry_boxes)} videos clipped by time-stamps and saved in {self.save_dir}", elapsed_time=timer.elapsed_time_str,)
1714
1722
 
1715
- #ClipMultipleVideosByTimestamps(data_dir=r"C:\troubleshooting\mitra\project_folder\videos", save_dir=r"C:\troubleshooting\mitra\project_folder\videos\temp_3")
1723
+ #ClipMultipleVideosByTimestamps(data_dir=r"E:\maplight_videos\test_0126", save_dir=r"E:\maplight_videos\clip_test")
1716
1724
 
1717
1725
 
1718
1726
  class InitiateClipMultipleVideosByTimestampsPopUp(PopUpMixin):
@@ -229,6 +229,7 @@ class Entry_Box(Frame):
229
229
  value: Optional[Any] = None,
230
230
  label_font: tuple = Formats.FONT_REGULAR.value,
231
231
  entry_font: tuple = Formats.FONT_REGULAR.value,
232
+ tooltip_key: Optional[str] = None,
232
233
  justify: Literal["left", "center", "right"] = 'left',
233
234
  cmd: Optional[Callable] = None,
234
235
  **kw):
@@ -249,6 +250,8 @@ class Entry_Box(Frame):
249
250
  self.filePath = StringVar()
250
251
  self.lblName = Label(self, text=fileDescription, width=labelwidth, anchor=W, font=label_font, bg=label_bg_clr)
251
252
  self.lblName.grid(row=0, column=1)
253
+ if tooltip_key in TOOLTIPS.keys():
254
+ CreateToolTip(widget=self.lblName, text=TOOLTIPS[tooltip_key])
252
255
  if not entry_box_width:
253
256
  self.entPath = Entry(self, textvariable=self.filePath, state=self.status, validate="key", validatecommand=self.validation_methods.get(validation, None), font=entry_font, justify=justify, bg=entry_box_clr)
254
257
  else:
@@ -30,7 +30,7 @@ class CustomFeatureExtractor(ConfigReader):
30
30
  4. Handle cases of multiple classes and missing configuration arguments.
31
31
  5. Invokes the feature extraction process if conditions are met.
32
32
 
33
- .. notes::
33
+ .. note::
34
34
 
35
35
  `Tutorial <https://github.com/sgoldenlab/simba/blob/master/docs/extractFeatures.md>`_.
36
36
 
simba/utils/data.py CHANGED
@@ -5,6 +5,7 @@ import configparser
5
5
  import gc
6
6
  import io
7
7
  import os
8
+ import platform
8
9
  import subprocess
9
10
  from copy import deepcopy
10
11
  from datetime import datetime
@@ -38,15 +39,18 @@ from simba.utils.checks import (check_file_exist_and_readable, check_float,
38
39
  check_if_valid_rgb_tuple, check_instance,
39
40
  check_int, check_str, check_that_column_exist,
40
41
  check_that_hhmmss_start_is_before_end,
41
- check_valid_array, check_valid_cpu_pool,
42
- check_valid_dataframe, check_valid_lst)
43
- from simba.utils.enums import ConfigKey, Dtypes, Formats, Keys, Options
42
+ check_valid_array, check_valid_boolean,
43
+ check_valid_cpu_pool, check_valid_dataframe,
44
+ check_valid_lst)
45
+ from simba.utils.enums import (OS, ConfigKey, Defaults, Dtypes, Formats, Keys,
46
+ Options)
44
47
  from simba.utils.errors import (BodypartColumnNotFoundError, CountError,
45
48
  InvalidFileTypeError, InvalidInputError,
46
49
  NoFilesFoundError, NoROIDataError,
47
50
  SimBAModuleNotFoundError)
48
51
  from simba.utils.printing import stdout_success, stdout_warning
49
- from simba.utils.read_write import (find_video_of_file, get_fn_ext,
52
+ from simba.utils.read_write import (find_core_cnt, find_video_of_file,
53
+ get_current_time, get_fn_ext,
50
54
  get_video_meta_data, read_config_entry,
51
55
  read_config_file, read_df,
52
56
  read_project_path_and_file_type,
@@ -1781,8 +1785,8 @@ def fft_lowpass_filter(data: np.ndarray, cut_off: float = 0.1) -> np.ndarray:
1781
1785
 
1782
1786
  :example:
1783
1787
  >>> from simba.utils.read_write import read_df
1784
- >>> IN_PATH = r"C:\troubleshooting\RAT_NOR\project_folder\csv\outlier_corrected_movement_location\2022-06-20_NOB_DOT_4.csv"
1785
- >>> OUT_PATH = r"C:\troubleshooting\RAT_NOR\project_folder\csv\outlier_corrected_movement_location\2022-06-20_NOB_DOT_4_filtered.csv"
1788
+ >>> IN_PATH = r"C:/troubleshooting/RAT_NOR/project_folder/csv/outlier_corrected_movement_location/2022-06-20_NOB_DOT_4.csv"
1789
+ >>> OUT_PATH = r"C:/troubleshooting/RAT_NOR/project_folder/csv/outlier_corrected_movement_location/2022-06-20_NOB_DOT_4_filtered.csv"
1786
1790
  >>> df = read_df(file_path=IN_PATH)
1787
1791
  >>> data = df.values
1788
1792
  >>> x = fft_lowpass_filter(data=data, cut_off=0.1)
@@ -1813,33 +1817,105 @@ def fft_lowpass_filter(data: np.ndarray, cut_off: float = 0.1) -> np.ndarray:
1813
1817
  return results.astype(data.dtype)
1814
1818
 
1815
1819
 
1816
- def terminate_cpu_pool(pool: Optional[multiprocessing.pool.Pool],
1817
- force: bool = False) -> None:
1820
+ def terminate_cpu_pool(pool: multiprocessing.pool.Pool,
1821
+ force: bool = False,
1822
+ verbose: bool = True,
1823
+ source: Optional[str] = None) -> None:
1818
1824
  """
1819
- Safely terminates a multiprocessing.Pool instance.
1825
+ Safely terminates a multiprocessing.Pool instance with optional graceful shutdown.
1820
1826
 
1821
- :param Optional[multiprocessing.pool.Pool] pool: The pool to terminate. If None, function returns without action.
1822
- :param bool force: If True, skips join() and immediately terminates. Default: False.
1823
- :raises InvalidInputError: If pool is not a valid Pool instance.
1827
+ .. note::
1828
+ If pool is None or invalid, function returns without action. Exceptions during termination are silently caught.
1829
+
1830
+ :param multiprocessing.pool.Pool pool: The multiprocessing pool to terminate. If None, function returns without action.
1831
+ :param bool force: If True, skips graceful shutdown (close/join) and immediately terminates. Default: False.
1832
+ :param bool verbose: If True, prints termination message with timestamp. Default: True.
1833
+ :param Optional[str] source: Optional identifier string for logging purposes (e.g., 'VideoProcessor'). Default: None.
1824
1834
 
1825
1835
  :example:
1826
1836
  >>> import multiprocessing
1827
1837
  >>> pool = multiprocessing.Pool(4)
1828
- >>> terminate_cpu_pool(pool)
1838
+ >>> terminate_cpu_pool(pool=pool, force=False, verbose=True, source='FeatureExtractor')
1829
1839
  """
1830
1840
  if pool is None:
1831
1841
  return
1832
- check_valid_cpu_pool(value=pool, source=terminate_cpu_pool.__name__, raise_error=True)
1842
+ if not check_valid_cpu_pool(value=pool, source=terminate_cpu_pool.__name__, raise_error=False):
1843
+ return
1833
1844
  try:
1845
+ core_cnt = pool._processes if hasattr(pool, '_processes') else None
1834
1846
  if not force:
1835
1847
  pool.close()
1836
1848
  pool.join()
1837
1849
  pool.terminate()
1850
+ if verbose: print(f'[{get_current_time()}] {"" if source is None else f"{core_cnt} core"} SimBA CPU pool {"" if source is None else source} terminated.')
1838
1851
  except (ValueError, AssertionError, AttributeError):
1839
1852
  pass
1840
1853
  gc.collect()
1841
1854
 
1842
1855
 
1856
+
1857
+ def get_cpu_pool(core_cnt: int = -1,
1858
+ maxtasksperchild: int = Defaults.MAXIMUM_MAX_TASK_PER_CHILD.value,
1859
+ context: Literal['fork', 'spawn', 'forkserver'] = None,
1860
+ verbose: bool = True,
1861
+ source: Optional[str] = None) -> multiprocessing.Pool:
1862
+ """
1863
+ Creates and returns a multiprocessing.Pool instance with platform-appropriate defaults and validation.
1864
+
1865
+ :param int core_cnt: Number of worker processes. -1 uses all available cores. Default: -1.
1866
+ :param int maxtasksperchild: Maximum number of tasks a worker process can complete before being replaced. Default: From Defaults.MAXIMUM_MAX_TASK_PER_CHILD.
1867
+ :param Optional[Literal['fork', 'spawn', 'forkserver']] context: Multiprocessing start method. None uses platform default. Default: None.
1868
+ :param bool verbose: If True, prints pool creation message with timestamp. Default: True.
1869
+ :param Optional[str] source: Optional identifier string for logging purposes (e.g., 'VideoProcessor'). Default: None.
1870
+ :return: Configured multiprocessing.Pool instance.
1871
+ :rtype: multiprocessing.Pool
1872
+
1873
+ :example:
1874
+ >>> pool = get_cpu_pool(core_cnt=4, source='FeatureExtractor')
1875
+ >>> pool = get_cpu_pool(core_cnt=-1, context='spawn', verbose=True)
1876
+ >>> pool = get_cpu_pool(core_cnt=8, maxtasksperchild=100, source='VideoProcessor')
1877
+ """
1878
+
1879
+ check_int(name=f'{get_cpu_pool.__name__} core_cnt', min_value=-1, unaccepted_vals=[0], value=core_cnt, raise_error=True)
1880
+ check_int(name=f'{get_cpu_pool.__name__} maxtasksperchild', min_value=1, value=maxtasksperchild, raise_error=True)
1881
+ check_valid_boolean(value=verbose, source=f'{get_cpu_pool.__name__} verbose', raise_error=True)
1882
+ if source is not None: check_str(name=f'{get_cpu_pool.__name__} source', value=source, raise_error=True, allow_blank=True)
1883
+ current_process = multiprocessing.current_process()
1884
+ if current_process.name != 'MainProcess': core_cnt = 1
1885
+ core_cnt = find_core_cnt()[0] if core_cnt == -1 or core_cnt > find_core_cnt()[0] else core_cnt
1886
+ if verbose: print(f'[{get_current_time()}] {core_cnt} core SimBA CPU pool {"" if source is None else source} started.')
1887
+ if context is not None:
1888
+ check_str(name=f'{get_cpu_pool.__name__} context', value=context, options=('fork', 'spawn', 'forkserver'), raise_error=True)
1889
+ else:
1890
+ existing_method = multiprocessing.get_start_method(allow_none=True)
1891
+ if existing_method is not None:
1892
+ context = existing_method
1893
+ else:
1894
+ system = platform.system()
1895
+ if system == OS.WINDOWS.value: context = OS.SPAWN.value
1896
+ elif system == OS.MAC.value: context = OS.SPAWN.value
1897
+ else: context = OS.FORK.value
1898
+
1899
+ if context is not None:
1900
+ try:
1901
+ ctx = multiprocessing.get_context(context)
1902
+ except ValueError:
1903
+ system = platform.system()
1904
+ if system == OS.WINDOWS.value: fallback_context = OS.SPAWN.value
1905
+ elif system == OS.MAC.value: fallback_context = OS.SPAWN.value
1906
+ else: fallback_context = OS.FORK.value
1907
+ try:
1908
+ ctx = multiprocessing.get_context(fallback_context)
1909
+ except ValueError:
1910
+ pool = multiprocessing.Pool(processes=core_cnt, maxtasksperchild=maxtasksperchild)
1911
+ return pool
1912
+ pool = ctx.Pool(processes=core_cnt, maxtasksperchild=maxtasksperchild)
1913
+ else:
1914
+ pool = multiprocessing.Pool(processes=core_cnt, maxtasksperchild=maxtasksperchild)
1915
+ return pool
1916
+
1917
+
1918
+ #get_cpu_pool()
1843
1919
  # run_user_defined_feature_extraction_class(config_path='/Users/simon/Desktop/envs/troubleshooting/circular_features_zebrafish/project_folder/project_config.ini', file_path='/Users/simon/Desktop/fish_feature_extractor_2023_version_5.py')
1844
1920
 
1845
1921
 
simba/utils/enums.py CHANGED
@@ -127,6 +127,7 @@ class OS(Enum):
127
127
  LINUX = "Linux"
128
128
  MAC = "Darwin"
129
129
  SPAWN = 'spawn'
130
+ FORK = 'fork'
130
131
  PYTHON_VER = str(f"{sys.version_info.major}.{sys.version_info.minor}")
131
132
  try:
132
133
  SIMBA_VERSION = pkg_resources.get_distribution("simba-uw-tf-dev").version