simba-uw-tf-dev 4.7.5__py3-none-any.whl → 4.7.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.
Files changed (29) hide show
  1. simba/assets/.recent_projects.txt +2 -0
  2. simba/assets/icons/folder_2.png +0 -0
  3. simba/assets/icons/folder_video.png +0 -0
  4. simba/assets/lookups/tooptips.json +24 -2
  5. simba/mixins/feature_extraction_mixin.py +0 -2
  6. simba/model/yolo_fit.py +42 -9
  7. simba/sandbox/av1.py +5 -0
  8. simba/sandbox/clean_sleap.py +4 -0
  9. simba/sandbox/denoise_hqdn3d.py +266 -0
  10. simba/sandbox/extract_random_frames.py +126 -0
  11. simba/third_party_label_appenders/transform/coco_keypoints_to_yolo.py +1 -2
  12. simba/third_party_label_appenders/transform/sleap_csv_to_yolo.py +18 -12
  13. simba/ui/create_project_ui.py +1 -1
  14. simba/ui/pop_ups/batch_preprocess_pop_up.py +1 -1
  15. simba/ui/pop_ups/simba_to_yolo_keypoints_popup.py +96 -96
  16. simba/ui/pop_ups/sleap_annotations_to_yolo_popup.py +32 -18
  17. simba/ui/pop_ups/sleap_csv_predictions_to_yolo_popup.py +15 -14
  18. simba/ui/pop_ups/video_processing_pop_up.py +1 -1
  19. simba/ui/pop_ups/yolo_plot_results.py +146 -153
  20. simba/ui/pop_ups/yolo_pose_train_popup.py +69 -23
  21. simba/utils/checks.py +2414 -2401
  22. simba/utils/read_write.py +22 -20
  23. simba/video_processors/video_processing.py +21 -13
  24. {simba_uw_tf_dev-4.7.5.dist-info → simba_uw_tf_dev-4.7.7.dist-info}/METADATA +1 -1
  25. {simba_uw_tf_dev-4.7.5.dist-info → simba_uw_tf_dev-4.7.7.dist-info}/RECORD +29 -23
  26. {simba_uw_tf_dev-4.7.5.dist-info → simba_uw_tf_dev-4.7.7.dist-info}/LICENSE +0 -0
  27. {simba_uw_tf_dev-4.7.5.dist-info → simba_uw_tf_dev-4.7.7.dist-info}/WHEEL +0 -0
  28. {simba_uw_tf_dev-4.7.5.dist-info → simba_uw_tf_dev-4.7.7.dist-info}/entry_points.txt +0 -0
  29. {simba_uw_tf_dev-4.7.5.dist-info → simba_uw_tf_dev-4.7.7.dist-info}/top_level.txt +0 -0
@@ -1,3 +1,5 @@
1
+ E:/troubleshooting/mitra_pbn/mitra_pbn/project_folder/project_config.ini
2
+ C:/troubleshooting/sleap_two_animals/project_folder/project_config.ini
1
3
  E:/troubleshooting/mitra_emergence/project_folder/project_config.ini
2
4
  C:/troubleshooting/meberled/project_folder/project_config.ini
3
5
  C:/troubleshooting/mitra/project_folder/project_config.ini
Binary file
Binary file
@@ -5,7 +5,7 @@
5
5
  "batch_dropdown": "Number of frames processed at once. Larger batches speed up inference but require more GPU RAM.",
6
6
  "verbose_dropdown": "Toggle console printouts for progress and timings. Keep TRUE while tuning, FALSE for quiet runs.",
7
7
  "workers_dropdown": "How many CPU worker threads to use for pre/post-processing. Set <= available cores.",
8
- "format_dropdown": "Model serialization format (None = auto-detect from file extension). Match the format used when exporting the weights.",
8
+ "format_dropdown": "Export/serialization format. Training: export the trained model to this format after training (None = PyTorch .pt only). Inference: match the format of your weights file, or None to auto-detect from file extension. Options: onnx, engine, torchscript, onnxsimplify, coreml, openvino, pb, tf, tflite, torch.",
9
9
  "img_size_dropdown": "Resize shorter image side to this many pixels before inference. Larger sizes improve accuracy but slow down processing.",
10
10
  "devices_dropdown": "Compute device to run on. Select CUDA device ID for GPU or CPU.",
11
11
  "interpolate_dropdown": "Fill missing detections by interpolating coordinates over time. Recommended for cleaner trajectories.",
@@ -43,5 +43,27 @@
43
43
  "KLEINBERG_GAMMA": "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",
44
44
  "KLEINBERG_HIERARCHY": "Hierarchy level to extract bursts from (0=lowest, higher=more selective).\n Level 0 captures all bursts; level 1-2 typically filters noise; level 3+ selects only the most prominent, sustained bursts.\nHigher levels yield fewer but more confident detections",
45
45
  "KLEINBERG_HIERARCHY_SEARCH": "If True, searches for target hierarchy level within detected burst periods,\n falling back to lower levels if target not found. If False, extracts only bursts at the exact specified hierarchy level.\n Recommended when target hierarchy may be sparse.",
46
- "KLEINBERG_SAVE_ORIGINALS": "If True, saves the original data in a new sub-directory of \nthe project_folder/csv/machine_results directory"
46
+ "KLEINBERG_SAVE_ORIGINALS": "If True, saves the original data in a new sub-directory of \nthe project_folder/csv/machine_results directory",
47
+ "yolo_map_path": "Path to the YOLO dataset YAML file. Defines class names, paths to train/val images and labels, and number of keypoints.",
48
+ "yolo_initial_weights_path": "Optional path to pretrained weights (.pt) to start training from (e.g. yolo11n-pose.pt). Leave blank to train from scratch.",
49
+ "epochs_dropdown": "Number of training epochs. More epochs can improve accuracy but increase overfitting risk and training time.",
50
+ "plots_dropdown": "If TRUE, generate and save training curves (loss, mAP, etc.) in the save directory.",
51
+ "patience_dropdown": "Early-stopping patience: training stops if validation metric does not improve for this many epochs.",
52
+ "simba2yolo_config": "Path to the SimBA project configuration file (.ini). Defines project paths, body-parts, and animals.",
53
+ "simba2yolo_train_size": "Percentage of sampled frames to use for the YOLO training set. The remainder is used for validation. E.g. 70 means 70% train, 30% val.",
54
+ "simba2yolo_padding": "Extra margin (as a fraction of image size) added around the keypoint bounding box. Use a small value (e.g. 0.05–0.2) if the tight box cuts off body parts or you want more context in each crop; None or 0 = no padding.",
55
+ "simba2yolo_sample_size": "Maximum number of frames to sample per video for creating YOLO images and labels. Higher values give more data but increase processing time.",
56
+ "simba2yolo_grey": "If TRUE, extracted video frames are saved in greyscale. If FALSE, frames are saved in color.",
57
+ "simba2yolo_clahe": "If TRUE, apply CLAHE (Contrast Limited Adaptive Histogram Equalization) to frames before saving. Can improve keypoint visibility in low-contrast videos.",
58
+ "yolo_plot_line_thickness": "Thickness of the lines drawn between keypoints (skeleton). AUTO lets the plotter choose based on video size; or set 1–20 pixels.",
59
+ "yolo_plot_circle_size": "Radius of the circles drawn at each keypoint. AUTO lets the plotter choose based on video size; or set 1–20 pixels.",
60
+ "yolo_plot_tracks": "If TRUE, draw trajectory paths (tracks) for each detected instance over time. If FALSE, draw only keypoints and skeleton per frame.",
61
+ "yolo_plot_data_path": "Path to a single YOLO pose result CSV (output from YOLO pose inference). Must match the video you select.",
62
+ "yolo_plot_video_path": "Path to the video file to overlay pose results onto. Filename should match the data CSV (without extension).",
63
+ "yolo_plot_data_dir": "Directory containing YOLO pose result CSV files. Used for batch plotting; each CSV is matched to a video of the same name in the video directory.",
64
+ "SLEAP_DATA_DIR": "Directory containing SLEAP CSV prediction files. Each CSV should match a video filename (without extension).",
65
+ "ANIMAL_COUNT": "Number of animals (tracks) in the videos. Used to name classes (e.g. animal_1, animal_2) in the YOLO dataset.",
66
+ "sleap_remove_animal_ids": "If TRUE, merge all tracks into a single identity (animal_1). Use when animal IDs are not meaningful or for single-animal data.",
67
+ "sleap_threshold": "Minimum SLEAP instance confidence (the instance.score column in the CSV). Only pose predictions with score ≥ this value are used when building the YOLO dataset. E.g. 90 means keep instances where instance.score ≥ 0.9; lower values include more frames but may add noisy predictions.",
68
+ "SLEAP_SLP_DATA_DIR": "Directory containing SLEAP .SLP project/annotation files. Each .SLP file is converted to YOLO pose format."
47
69
  }
@@ -603,8 +603,6 @@ class FeatureExtractionMixin(object):
603
603
  else:
604
604
  pass
605
605
 
606
-
607
-
608
606
  for cord in [nose_cords, ear_left_cords, ear_right_cords]:
609
607
  if len(cord) != len(self.animal_bp_dict.keys()) * 2:
610
608
  direction_viable = False
simba/model/yolo_fit.py CHANGED
@@ -1,5 +1,6 @@
1
1
  import os
2
2
  import sys
3
+ import urllib.request
3
4
  from contextlib import redirect_stderr, redirect_stdout
4
5
 
5
6
  os.environ["KMP_DUPLICATE_LIB_OK"]="TRUE"
@@ -19,13 +20,19 @@ except ModuleNotFoundError:
19
20
  from simba.data_processors.cuda.utils import _is_cuda_available
20
21
  from simba.utils.checks import (check_file_exist_and_readable,
21
22
  check_if_dir_exists, check_int, check_str,
22
- check_valid_boolean, check_valid_device)
23
+ check_valid_boolean, check_valid_device,
24
+ check_valid_url)
23
25
  from simba.utils.enums import Options
24
26
  from simba.utils.errors import SimBAGPUError, SimBAPAckageVersionError
25
27
  from simba.utils.printing import stdout_information
26
28
  from simba.utils.read_write import find_core_cnt, get_current_time
27
29
  from simba.utils.yolo import load_yolo_model
28
30
 
31
+ #YOLO_X_PATH = "https://huggingface.co/Ultralytics/YOLO11/resolve/main/yolo11x-pose.pt"
32
+
33
+ YOLO_M_PATH = "https://huggingface.co/Ultralytics/YOLO11/resolve/main/yolo11m-pose.pt"
34
+
35
+
29
36
 
30
37
  class FitYolo():
31
38
 
@@ -74,9 +81,9 @@ class FitYolo():
74
81
  """
75
82
 
76
83
  def __init__(self,
77
- weights_path: Union[str, os.PathLike],
78
84
  model_yaml: Union[str, os.PathLike],
79
85
  save_path: Union[str, os.PathLike],
86
+ weights_path: Optional[Union[str, os.PathLike]] = None,
80
87
  epochs: int = 200,
81
88
  batch: Union[int, float] = 16,
82
89
  plots: bool = True,
@@ -92,7 +99,11 @@ class FitYolo():
92
99
  raise SimBAGPUError(msg='No GPU detected.', source=self.__class__.__name__)
93
100
  if YOLO is None:
94
101
  raise SimBAPAckageVersionError(msg='Ultralytics package not detected.', source=self.__class__.__name__)
95
- check_file_exist_and_readable(file_path=weights_path)
102
+ if weights_path is not None:
103
+ check_file_exist_and_readable(file_path=weights_path)
104
+ self.weights_path = weights_path
105
+ else:
106
+ self._download_start_weights()
96
107
  check_file_exist_and_readable(file_path=model_yaml)
97
108
  check_valid_boolean(value=verbose, source=f'{__class__.__name__} verbose', raise_error=True)
98
109
  check_valid_boolean(value=plots, source=f'{__class__.__name__} plots', raise_error=True)
@@ -106,12 +117,19 @@ class FitYolo():
106
117
  check_valid_device(device=device)
107
118
  self.model_yaml, self.epochs, self.batch = model_yaml, epochs, batch
108
119
  self.imgsz, self.device, self.workers, self.format = imgsz, device, workers, format
109
- self.plots, self.save_path, self.verbose, self.weights_path, self.patience = plots, save_path, verbose, weights_path, patience
120
+ self.plots, self.save_path, self.verbose, self.patience = plots, save_path, verbose, patience
121
+
122
+ def _download_start_weights(self, url: str = YOLO_M_PATH, save_path: Union[str, os.PathLike] = "yolo11m-pose.pt"):
123
+ print(f'No start weights provided, downloading {save_path} from {url}...')
124
+ check_valid_url(url=url, raise_error=True, source=self.__class__.__name__)
125
+ if not os.path.isfile(save_path):
126
+ urllib.request.urlretrieve(url, save_path)
127
+ stdout_information(msg=f'Downloaded initial weights from {url}', source=self.__class__.__name__)
128
+ self.weights_path = save_path
129
+ print(self.weights_path)
110
130
 
111
131
 
112
132
  def run(self):
113
- # Temporarily redirect stdout/stderr to terminal to ensure ultralytics output goes to terminal
114
- # sys.__stdout__ and sys.__stderr__ are the original terminal streams
115
133
  stdout_information(msg=f'[{get_current_time()}] Please follow the YOLO pose model training in the terminal from where SimBA was launched ...', source=self.__class__.__name__)
116
134
  stdout_information(msg=f'[{get_current_time()}] Results will be stored in the {self.save_path} directory ..', source=self.__class__.__name__)
117
135
  with redirect_stdout(sys.__stdout__), redirect_stderr(sys.__stderr__):
@@ -133,7 +151,7 @@ class FitYolo():
133
151
 
134
152
  if __name__ == "__main__" and not hasattr(sys, 'ps1'):
135
153
  parser = argparse.ArgumentParser(description="Fit YOLO model using ultralytics package.")
136
- parser.add_argument('--weights_path', type=str, required=True, help='Path to the trained YOLO model weights (e.g., yolo11n-pose.pt)')
154
+ parser.add_argument('--weights_path', type=str, default=None, help='Path to the trained YOLO model weights (e.g., yolo11n-pose.pt). Omit to download default starter weights.')
137
155
  parser.add_argument('--model_yaml', type=str, required=True, help='Path to map.yaml (model structure and label definitions)')
138
156
  parser.add_argument('--save_path', type=str, required=True, help='Directory where trained model and logs will be saved')
139
157
  parser.add_argument('--epochs', type=int, default=25, help='Number of epochs to train the model. Default is 25')
@@ -146,7 +164,6 @@ if __name__ == "__main__" and not hasattr(sys, 'ps1'):
146
164
  parser.add_argument('--workers', type=int, default=8, help='Number of data loader workers. Default is 8. Use -1 for max cores')
147
165
  parser.add_argument('--patience', type=int, default=100, help='Number of epochs to wait without improvement in validation metrics before early stopping the training. Default is 100')
148
166
 
149
-
150
167
  args = parser.parse_args()
151
168
 
152
169
  yolo_fitter = FitYolo(weights_path=args.weights_path,
@@ -159,11 +176,27 @@ if __name__ == "__main__" and not hasattr(sys, 'ps1'):
159
176
  format=args.format,
160
177
  device=int(args.device) if args.device != 'cpu' else 'cpu',
161
178
  verbose=args.verbose,
162
- workers=args.workers)
179
+ workers=args.workers,
180
+ patience=args.patience)
163
181
  yolo_fitter.run()
164
182
 
165
183
 
166
184
 
185
+
186
+
187
+ # fitter = FitYolo(weights_path=r"D:\maplight_tg2576_yolo\yolo_mdl\original_weight_oct\best.pt",
188
+ # model_yaml=r"D:\maplight_tg2576_yolo\yolo_mdl\map.yaml",
189
+ # save_path=r"D:\maplight_tg2576_yolo\yolo_mdl\mdl",
190
+ # epochs=1500,
191
+ # batch=22,
192
+ # format=None,
193
+ # device=0,
194
+ # imgsz=640)
195
+ # fitter.run()
196
+
197
+
198
+
199
+
167
200
  # fitter = FitYolo(weights_path=r"E:\yolo_resident_intruder\mdl\train3\weights\best.pt",
168
201
  # model_yaml=r"E:\maplight_videos\yolo_mdl\map.yaml",
169
202
  # save_path=r"E:\maplight_videos\yolo_mdl\mdl",
simba/sandbox/av1.py ADDED
@@ -0,0 +1,5 @@
1
+ from simba.video_processors.video_processing import convert_to_webm
2
+
3
+
4
+
5
+ convert_to_webm(path=r"D:\troubleshooting\batch_fps\2025-09-08_13-06-38-AloneH-1.mp4", codec='vp9')
@@ -0,0 +1,4 @@
1
+ from simba.utils.read_write import clean_sleap_file_name
2
+
3
+
4
+ clean_sleap_file_name(filename=f'2026-01-09 11-53-23 box1_1143_0_Gq_5cno.predictions.000_2026-01-09 11-53-23 box1_1143_0_Gq_5cno.analysis.csv')
@@ -0,0 +1,266 @@
1
+ """
2
+ Function to apply hqdn3d (high-quality denoise 3D) filter to a video file using ffmpeg.
3
+
4
+ The hqdn3d filter is a spatial-temporal denoise filter that reduces noise while preserving
5
+ video quality and details.
6
+ """
7
+
8
+ import os
9
+ import subprocess
10
+ from typing import Union, Optional
11
+
12
+ from simba.utils.checks import (
13
+ check_ffmpeg_available,
14
+ check_file_exist_and_readable,
15
+ check_if_dir_exists,
16
+ check_nvidea_gpu_available
17
+ )
18
+ from simba.utils.errors import FFMPEGCodecGPUError
19
+ from simba.utils.printing import SimbaTimer, stdout_success
20
+ from simba.utils.read_write import get_fn_ext
21
+
22
+
23
+ def denoise_bm3d(file_path: Union[str, os.PathLike],
24
+ save_path: Optional[Union[str, os.PathLike]] = None,
25
+ gpu: Optional[bool] = False,
26
+ quality: int = 60,
27
+ sigma: Optional[float] = None,
28
+ block: Optional[int] = None,
29
+ bstep: Optional[int] = None,
30
+ group: Optional[int] = None) -> None:
31
+ """
32
+ Apply bm3d (Block-Matching 3D) denoise filter to a video file.
33
+
34
+ BM3D is a more advanced denoising algorithm than hqdn3d, often better at removing texture
35
+ and background noise while preserving details. It's slower but produces better results.
36
+
37
+ **For removing background texture (e.g., sawdust pellets):**
38
+ - **sigma** is MOST IMPORTANT - controls noise level/denoising strength (higher = more denoising)
39
+ - **block** - block size (default: 4, larger = more smoothing but slower)
40
+ - **bstep** - block step (default: 4, smaller = better quality but slower)
41
+ - **group** - group size (default: 1, larger = better denoising but slower)
42
+
43
+ :param Union[str, os.PathLike] file_path: Path to input video file.
44
+ :param Optional[Union[str, os.PathLike]] save_path: Optional save location for the denoised video. If None, then the new video is saved in the same directory as the input video with the ``_bm3d_denoised`` suffix.
45
+ :param Optional[bool] gpu: If True, use NVIDEA GPU codecs. Default False.
46
+ :param int quality: Video quality percentage (1-100). Higher values = higher quality. Default 60.
47
+ :param Optional[float] sigma: Noise level/denoising strength (default: 1.0). **MOST IMPORTANT** - higher values = more denoising. For background texture removal, try 5-20.
48
+ :param Optional[int] block: Block size (default: 4). Larger values = more smoothing but slower processing.
49
+ :param Optional[int] bstep: Block step (default: 4). Smaller values = better quality but slower.
50
+ :param Optional[int] group: Group size (default: 1). Larger values = better denoising but slower.
51
+ :returns: None. If save_path is not passed, the result is stored in the same directory as the input file with the ``_bm3d_denoised.mp4`` suffix.
52
+
53
+ .. note::
54
+ Codec is automatically selected: libx264 for CPU encoding (ignored if gpu=True).
55
+ BM3D is slower than hqdn3d but often produces better results for texture removal.
56
+ For background texture removal, start with sigma=10-15 and adjust from there.
57
+
58
+ :example:
59
+ >>> denoise_bm3d(file_path='project_folder/videos/Video_1.avi', sigma=10)
60
+ >>> denoise_bm3d(file_path='/Users/simon/Desktop/test/noisy_video.mp4', sigma=15, block=8, quality=80)
61
+ """
62
+
63
+ check_ffmpeg_available(raise_error=True)
64
+ if gpu and not check_nvidea_gpu_available():
65
+ raise FFMPEGCodecGPUError(
66
+ msg="No GPU found (as evaluated by nvidea-smi returning None)",
67
+ source=denoise_bm3d.__name__
68
+ )
69
+
70
+ timer = SimbaTimer(start=True)
71
+ check_file_exist_and_readable(file_path=file_path)
72
+
73
+ dir, file_name, ext = get_fn_ext(filepath=file_path)
74
+
75
+ if save_path is None:
76
+ save_name = os.path.join(dir, f"{file_name}_bm3d_denoised.mp4")
77
+ else:
78
+ check_if_dir_exists(
79
+ in_dir=os.path.dirname(save_path),
80
+ source=f'{denoise_bm3d.__name__} save_path',
81
+ create_if_not_exist=True
82
+ )
83
+ save_name = save_path
84
+
85
+ # Set default bm3d parameters if not provided
86
+ sigma_val = sigma if sigma is not None else 1.0
87
+ block_val = block if block is not None else 4
88
+ bstep_val = bstep if bstep is not None else 4
89
+ group_val = group if group is not None else 1
90
+
91
+ # Check if bm3d filter is available first (may not be compiled into all ffmpeg builds)
92
+ check_cmd = 'ffmpeg -filters 2>&1 | findstr /i "bm3d"'
93
+ result = subprocess.run(check_cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)
94
+ if not result.stdout.strip():
95
+ raise FFMPEGCodecGPUError(
96
+ msg="BM3D filter not available in your ffmpeg build. BM3D may not be compiled into your ffmpeg installation. Try using hqdn3d or nlmeans instead.",
97
+ source=denoise_bm3d.__name__
98
+ )
99
+
100
+ # Build bm3d filter string with named parameters
101
+ # Format: bm3d=sigma=value:block=value:bstep=value:group=value
102
+ filter_str = f'bm3d=sigma={sigma_val}:block={block_val}:bstep={bstep_val}:group={group_val}'
103
+
104
+ # Build ffmpeg command with bm3d filter
105
+ if gpu:
106
+ # GPU encoding with bm3d filter
107
+ from simba.utils.lookups import quality_pct_to_crf
108
+ quality_crf = quality_pct_to_crf(pct=int(quality))
109
+ cmd = f'ffmpeg -hwaccel auto -c:v h264_cuvid -i "{file_path}" -vf {filter_str} -rc vbr -cq {quality_crf} -c:v h264_nvenc -c:a copy "{save_name}" -loglevel error -stats -hide_banner -y'
110
+ else:
111
+ # CPU encoding with bm3d filter
112
+ from simba.utils.lookups import quality_pct_to_crf
113
+ quality_crf = quality_pct_to_crf(pct=int(quality))
114
+ cmd = f'ffmpeg -i "{file_path}" -vf {filter_str} -c:v libx264 -crf {quality_crf} -c:a copy "{save_name}" -loglevel error -stats -hide_banner -y'
115
+
116
+ print(f"Applying bm3d denoise filter (sigma={sigma_val}, block={block_val}, bstep={bstep_val}, group={group_val}) to {file_name}...")
117
+ print(f"Command: {cmd}")
118
+
119
+ process = subprocess.run(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)
120
+ if process.returncode != 0:
121
+ error_msg = process.stderr if process.stderr else (process.stdout if process.stdout else "Unknown error")
122
+ print(f"Error output: {error_msg}")
123
+ raise FFMPEGCodecGPUError(
124
+ msg=f"FFmpeg bm3d filter failed: {error_msg}",
125
+ source=denoise_bm3d.__name__
126
+ )
127
+ timer.stop_timer()
128
+ stdout_success(
129
+ msg=f"SIMBA COMPLETE: Video denoised with BM3D! {save_name} generated!",
130
+ elapsed_time=timer.elapsed_time_str,
131
+ source=denoise_bm3d.__name__
132
+ )
133
+
134
+
135
+ def denoise_hqdn3d(file_path: Union[str, os.PathLike],
136
+ save_path: Optional[Union[str, os.PathLike]] = None,
137
+ gpu: Optional[bool] = False,
138
+ quality: int = 60,
139
+ luma_spatial: Optional[float] = None,
140
+ luma_temporal: Optional[float] = None,
141
+ chroma_spatial: Optional[float] = None,
142
+ chroma_temporal: Optional[float] = None) -> None:
143
+ """
144
+ Apply hqdn3d (high-quality denoise 3D) filter to a video file.
145
+
146
+ The hqdn3d filter has 4 parameters that control denoising strength:
147
+ - Higher values = more denoising but may blur details
148
+ - Lower values = less denoising but preserves more detail
149
+
150
+ **For removing background texture (e.g., sawdust pellets):**
151
+ - **luma_spatial** and **chroma_spatial** are MOST IMPORTANT - these control spatial smoothing within each frame
152
+ - **luma_temporal** and **chroma_temporal** are less critical - these smooth across frames (helpful for motion noise, not static background)
153
+ - For background texture removal, focus on high spatial values (50-200+) and moderate temporal values (10-20)
154
+
155
+ .. note::
156
+ hqdn3d may not be ideal for removing static background texture. Consider background subtraction or blur filters instead.
157
+ For background texture removal, try: luma_spatial=100-200, chroma_spatial=100-200, luma_temporal=10-20, chroma_temporal=10-20
158
+
159
+ :param Union[str, os.PathLike] file_path: Path to input video file.
160
+ :param Optional[Union[str, os.PathLike]] save_path: Optional save location for the denoised video. If None, then the new video is saved in the same directory as the input video with the ``_denoised`` suffix.
161
+ :param Optional[bool] gpu: If True, use NVIDEA GPU codecs. Default False.
162
+ :param int quality: Video quality percentage (1-100). Higher values = higher quality. Default 60.
163
+ :param Optional[float] luma_spatial: Spatial luma strength (default: 4.0). Controls detail preservation in luma channel. **MOST IMPORTANT for background texture removal.**
164
+ :param Optional[float] luma_temporal: Temporal luma strength (default: 3.0). Controls motion blur artifacts in luma channel. Less critical for static background.
165
+ :param Optional[float] chroma_spatial: Spatial chroma strength (default: 3.0). Controls detail preservation in chroma channel. **MOST IMPORTANT for background texture removal.**
166
+ :param Optional[float] chroma_temporal: Temporal chroma strength (default: 6.0). Controls motion blur artifacts in chroma channel. Less critical for static background.
167
+ :returns: None. If save_path is not passed, the result is stored in the same directory as the input file with the ``_denoised.mp4`` suffix.
168
+
169
+ .. note::
170
+ Codec is automatically selected: libx264 for CPU encoding (ignored if gpu=True).
171
+ Default parameters are conservative. For stronger denoising, try: luma_spatial=8, luma_temporal=6, chroma_spatial=6, chroma_temporal=9
172
+ For very noisy videos, you may need values of 10-15 or higher.
173
+
174
+ :example:
175
+ >>> denoise_hqdn3d(file_path='project_folder/videos/Video_1.avi')
176
+ >>> denoise_hqdn3d(file_path='/Users/simon/Desktop/test/noisy_video.mp4', luma_spatial=8, luma_temporal=6, quality=80)
177
+ >>> # For removing background texture (sawdust pellets):
178
+ >>> denoise_hqdn3d(file_path='video.mp4', luma_spatial=150, chroma_spatial=150, luma_temporal=15, chroma_temporal=15)
179
+ """
180
+
181
+ check_ffmpeg_available(raise_error=True)
182
+ if gpu and not check_nvidea_gpu_available():
183
+ raise FFMPEGCodecGPUError(
184
+ msg="No GPU found (as evaluated by nvidea-smi returning None)",
185
+ source=denoise_hqdn3d.__name__
186
+ )
187
+
188
+ timer = SimbaTimer(start=True)
189
+ check_file_exist_and_readable(file_path=file_path)
190
+
191
+ dir, file_name, ext = get_fn_ext(filepath=file_path)
192
+
193
+ if save_path is None:
194
+ save_name = os.path.join(dir, f"{file_name}_denoised.mp4")
195
+ else:
196
+ check_if_dir_exists(
197
+ in_dir=os.path.dirname(save_path),
198
+ source=f'{denoise_hqdn3d.__name__} save_path',
199
+ create_if_not_exist=True
200
+ )
201
+ save_name = save_path
202
+
203
+ # Set default hqdn3d parameters if not provided
204
+ luma_sp = luma_spatial if luma_spatial is not None else 4.0
205
+ luma_tmp = luma_temporal if luma_temporal is not None else 3.0
206
+ chroma_sp = chroma_spatial if chroma_spatial is not None else 3.0
207
+ chroma_tmp = chroma_temporal if chroma_temporal is not None else 6.0
208
+
209
+ # Build hqdn3d filter string with parameters
210
+ # Format: hqdn3d=luma_spatial:chroma_spatial:luma_temporal:chroma_temporal
211
+ filter_str = f'hqdn3d={luma_sp}:{chroma_sp}:{luma_tmp}:{chroma_tmp}'
212
+
213
+ # Build ffmpeg command with hqdn3d filter
214
+ if gpu:
215
+ # GPU encoding with hqdn3d filter
216
+ from simba.utils.lookups import quality_pct_to_crf
217
+ quality_crf = quality_pct_to_crf(pct=int(quality))
218
+ cmd = f'ffmpeg -hwaccel auto -c:v h264_cuvid -i "{file_path}" -vf {filter_str} -rc vbr -cq {quality_crf} -c:v h264_nvenc -c:a copy "{save_name}" -loglevel error -stats -hide_banner -y'
219
+ else:
220
+ # CPU encoding with hqdn3d filter
221
+ from simba.utils.lookups import quality_pct_to_crf
222
+ quality_crf = quality_pct_to_crf(pct=int(quality))
223
+ cmd = f'ffmpeg -i "{file_path}" -vf {filter_str} -c:v libx264 -crf {quality_crf} -c:a copy "{save_name}" -loglevel error -stats -hide_banner -y'
224
+
225
+ print(f"Applying hqdn3d denoise filter (luma_spatial={luma_sp}, chroma_spatial={chroma_sp}, luma_temporal={luma_tmp}, chroma_temporal={chroma_tmp}) to {file_name}...")
226
+
227
+ subprocess.call(cmd, shell=True, stdout=subprocess.PIPE)
228
+ timer.stop_timer()
229
+ stdout_success(
230
+ msg=f"SIMBA COMPLETE: Video denoised! {save_name} generated!",
231
+ elapsed_time=timer.elapsed_time_str,
232
+ source=denoise_hqdn3d.__name__
233
+ )
234
+
235
+ #
236
+ # For removing background texture (sawdust pellets):
237
+ # Focus on HIGH spatial values (most important) and moderate temporal values
238
+ # Spatial parameters blur within each frame - critical for static background texture
239
+ # Temporal parameters smooth across frames - less critical for static background
240
+ # denoise_hqdn3d(
241
+ # file_path=r"E:\open_video\open_field_4\2.mp4",
242
+ # luma_spatial=200, # VERY HIGH - most important for background texture removal
243
+ # luma_temporal=40, # Moderate - 200 is overkill for static background
244
+ # chroma_spatial=200, # VERY HIGH - most important for background texture removal
245
+ # chroma_temporal=40 # Moderate - 200 is overkill for static background
246
+ # )
247
+
248
+ # BM3D - Better for texture removal, slower but often produces better results
249
+ # sigma is the most important parameter - controls denoising strength
250
+ # denoise_bm3d(
251
+ # file_path=r"E:\open_video\open_field_4\2.mp4",
252
+ # sigma=15, # High denoising strength for background texture removal
253
+ # block=8, # Larger blocks = more smoothing
254
+ # bstep=2, # Smaller step = better quality
255
+ # group=1 # Default group size
256
+ # )
257
+ #
258
+ # # BM3D - Better for texture removal, slower but often produces better results
259
+ # # sigma is the most important parameter - controls denoising strength
260
+ # denoise_bm3d(
261
+ # file_path=r"E:\open_video\open_field_4\2.mp4",
262
+ # sigma=15, # High denoising strength for background texture removal
263
+ # block=8, # Larger blocks = more smoothing
264
+ # bstep=2, # Smaller step = better quality
265
+ # group=1 # Default group size
266
+ # )
@@ -0,0 +1,126 @@
1
+ """
2
+ Function to extract random N frames from all videos in a directory.
3
+
4
+ Each frame is saved as videoname_framenumber.png in the same directory as the video.
5
+ """
6
+
7
+ import os
8
+ import random
9
+ from typing import Union, Optional
10
+ import numpy as np
11
+ import cv2
12
+
13
+ from simba.utils.checks import (
14
+ check_if_dir_exists,
15
+ check_int,
16
+ check_file_exist_and_readable
17
+ )
18
+ from simba.utils.read_write import (
19
+ get_video_meta_data,
20
+ find_all_videos_in_directory,
21
+ read_frm_of_video,
22
+ get_fn_ext
23
+ )
24
+ from simba.utils.printing import SimbaTimer, stdout_success
25
+
26
+
27
+ def extract_random_frames_from_directory(
28
+ directory: Union[str, os.PathLike],
29
+ n_frames: int = 10,
30
+ save_dir: Optional[Union[str, os.PathLike]] = None,
31
+ verbose: Optional[bool] = True
32
+ ) -> None:
33
+ """
34
+ Extract random N frames from all videos in a directory.
35
+
36
+ For each video, randomly samples N frames and saves them as individual PNG files.
37
+ Frames are saved with the naming convention: videoname_framenumber.png
38
+
39
+ :param Union[str, os.PathLike] directory: Path to directory containing video files.
40
+ :param int n_frames: Number of random frames to extract from each video. Default: 10.
41
+ :param Optional[Union[str, os.PathLike]] save_dir: Optional directory where extracted frames will be saved.
42
+ If None, frames are saved in the same directory as each video file. Default: None.
43
+ :param Optional[bool] verbose: If True, prints progress messages during extraction. Default: True.
44
+ :return: None. Frames are saved to disk.
45
+
46
+ :example:
47
+ >>> extract_random_frames_from_directory(directory='project_folder/videos', n_frames=20)
48
+ >>> extract_random_frames_from_directory(directory='/Users/simon/Desktop/videos', n_frames=5, save_dir='/Users/simon/Desktop/frames')
49
+ """
50
+
51
+ timer = SimbaTimer(start=True)
52
+ check_if_dir_exists(in_dir=directory, source=extract_random_frames_from_directory.__name__)
53
+ check_int(name="n_frames", value=n_frames, min_value=1)
54
+
55
+ # Find all videos in directory
56
+ video_paths = find_all_videos_in_directory(
57
+ directory=directory,
58
+ as_dict=False,
59
+ raise_error=True
60
+ )
61
+
62
+ if not video_paths or video_paths == ["No videos found"]:
63
+ raise ValueError(f"No videos found in directory: {directory}")
64
+
65
+ total_frames_extracted = 0
66
+
67
+ for video_cnt, video_name in enumerate(video_paths):
68
+ video_path = os.path.join(directory, video_name)
69
+ check_file_exist_and_readable(file_path=video_path)
70
+
71
+ # Get video metadata
72
+ video_meta_data = get_video_meta_data(video_path=video_path)
73
+ total_frames = video_meta_data["frame_count"]
74
+ _, video_name_only, _ = get_fn_ext(filepath=video_path) # Returns (directory, filename, extension)
75
+
76
+ # Determine save directory
77
+ if save_dir is None:
78
+ video_save_dir = directory # Save to the same directory as the videos
79
+ else:
80
+ video_save_dir = save_dir
81
+
82
+ # Create save directory if it doesn't exist
83
+ if not os.path.exists(video_save_dir):
84
+ os.makedirs(video_save_dir)
85
+
86
+ # Randomly sample N frames (or all frames if video has fewer than N frames)
87
+ n_samples = min(n_frames, total_frames)
88
+ if total_frames < n_frames:
89
+ if verbose:
90
+ print(f"Video {video_name_only} has only {total_frames} frames. Extracting all {total_frames} frames.")
91
+ selected_frames = list(range(total_frames))
92
+ else:
93
+ selected_frames = sorted(random.sample(range(total_frames), n_samples))
94
+
95
+ # Extract and save frames
96
+ cap = cv2.VideoCapture(video_path)
97
+ for frame_idx, frame_number in enumerate(selected_frames):
98
+ # Seek to the correct frame
99
+ cap.set(cv2.CAP_PROP_POS_FRAMES, frame_number)
100
+ ret, frame = cap.read()
101
+
102
+ if not ret:
103
+ if verbose:
104
+ print(f"Warning: Could not read frame {frame_number} from {video_name_only}. Skipping...")
105
+ continue
106
+
107
+ # Save frame with naming convention: videoname_framenumber.png
108
+ # video_name_only is the video filename without extension
109
+ save_path = os.path.join(video_save_dir, f"{video_name_only}_{frame_number}.png")
110
+ cv2.imwrite(save_path, frame, [cv2.IMWRITE_PNG_COMPRESSION, 3])
111
+ total_frames_extracted += 1
112
+
113
+ if verbose:
114
+ print(f"Video {video_cnt + 1}/{len(video_paths)}: Frame {frame_number} saved from {video_name_only} ({frame_idx + 1}/{len(selected_frames)})")
115
+
116
+ cap.release()
117
+
118
+ timer.stop_timer()
119
+ stdout_success(
120
+ msg=f"SIMBA COMPLETE: Extracted {total_frames_extracted} random frames from {len(video_paths)} video(s)!",
121
+ elapsed_time=timer.elapsed_time_str,
122
+ source=extract_random_frames_from_directory.__name__
123
+ )
124
+
125
+
126
+ extract_random_frames_from_directory(directory=r"D:\maplight_tg2576_yolo\videos", n_frames=35, save_dir=r'D:\maplight_tg2576_yolo\frames')
@@ -75,7 +75,7 @@ class COCOKeypoints2Yolo:
75
75
  img_dir: Union[str, os.PathLike],
76
76
  save_dir: Union[str, os.PathLike],
77
77
  train_size: float = 0.7,
78
- flip_idx: Tuple[int, ...] = (0, 2, 1, 3, 5, 4, 6, 7, 8),
78
+ flip_idx: Tuple[int, ...] = (0, 2, 1, 5, 4, 3, 6),
79
79
  verbose: bool = True,
80
80
  greyscale: bool = False,
81
81
  clahe: bool = False,
@@ -175,7 +175,6 @@ class COCOKeypoints2Yolo:
175
175
  missing = [x for x in list(range(shapes[0])) if x not in self.flip_idx]
176
176
  if len(missing) > 0:
177
177
  raise InvalidInputError(msg=f'keypoints contains index values not in flip_idx ({missing}).', source=self.__class__.__name__)
178
-
179
178
  create_yolo_keypoint_yaml(path=self.save_dir, train_path=self.train_img_dir, val_path=self.val_img_dir, names=self.map_dict, save_path=self.map_path, kpt_shape=(int(shapes[0]), 3), flip_idx=self.flip_idx)
180
179
  timer.stop_timer()
181
180
  if self.verbose: stdout_success(msg=f'COCO keypoints to YOLO conversion complete. Data saved in directory {self.save_dir}.', elapsed_time=timer.elapsed_time_str)