Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master'
Browse files Browse the repository at this point in the history
# Conflicts:
#	simba/plotting/single_run_model_validation_video_mp.py
#	simba/video_processors/video_processing.py
  • Loading branch information
sronilsson committed Apr 11, 2024
2 parents 6893ad2 + 0bdfb99 commit d3b0d3b
Show file tree
Hide file tree
Showing 13 changed files with 182 additions and 81 deletions.
6 changes: 4 additions & 2 deletions simba/SimBA.py
Original file line number Diff line number Diff line change
Expand Up @@ -1462,8 +1462,10 @@ def trainmachinemodelsetting(self):
_ = MachineModelSettingsPopUp(config_path=self.config_path)

def run_feature_extraction(self):
print('Running feature extraction...')
print(f"Pose-estimation body part setting for feature extraction: {str(self.animal_cnt)} animals {str(self.pose_setting)} body-parts...")
print("Running feature extraction...")
print(
f"Pose-estimation body part setting for feature extraction: {str(self.animal_cnt)} animals {str(self.pose_setting)} body-parts..."
)
feature_extractor_classes = get_bp_config_code_class_pairs()
if self.user_defined_var.get():
custom_feature_extractor = CustomFeatureExtractor(
Expand Down
11 changes: 7 additions & 4 deletions simba/mixins/circular_statistics.py
Original file line number Diff line number Diff line change
Expand Up @@ -583,17 +583,20 @@ def sliding_circular_correlation(
for j in prange(win_size, sample_1.shape[0] + 1):
data_1_window = sample_1[j - win_size : j]
data_2_window = sample_2[j - win_size : j]
m1 = np.arctan2(np.mean(np.sin(data_1_window)), np.mean(np.cos(data_1_window)))
m2 = np.arctan2(np.mean(np.sin(data_2_window)), np.mean(np.cos(data_2_window)))
m1 = np.arctan2(
np.mean(np.sin(data_1_window)), np.mean(np.cos(data_1_window))
)
m2 = np.arctan2(
np.mean(np.sin(data_2_window)), np.mean(np.cos(data_2_window))
)
sin_1, sin_2 = np.sin(data_1_window - m1), np.sin(data_2_window - m2)
denominator = np.sqrt(np.sum(sin_1 * sin_1) * np.sum(sin_2 * sin_2))
numerator = np.sum(sin_1 * sin_2)
if (denominator == 0):
if denominator == 0:
results[j - 1][i] = 0.0
else:
results[j - 1][i] = np.abs(numerator / denominator)


return results.astype(np.float32)

@staticmethod
Expand Down
14 changes: 10 additions & 4 deletions simba/mixins/statistics_mixin.py
Original file line number Diff line number Diff line change
Expand Up @@ -1046,7 +1046,9 @@ def pct_in_top_n(x: np.ndarray, n: float) -> float:
>>> Statistics.pct_in_top_n(x=x, n=5)
"""

check_valid_array(data=x, accepted_ndims=(1,), source=Statistics.pct_in_top_n.__name__)
check_valid_array(
data=x, accepted_ndims=(1,), source=Statistics.pct_in_top_n.__name__
)
check_int(name=Statistics.pct_in_top_n.__name__, value=n, max_value=x.shape[0])
cnts = np.sort(np.unique(x, return_counts=True)[1])[-n:]
return np.sum(cnts) / x.shape[0]
Expand Down Expand Up @@ -1560,7 +1562,9 @@ def sliding_spearman_rank_correlation(

@staticmethod
@njit("(float32[:], float64, float64, float64)")
def sliding_autocorrelation(data: np.ndarray, max_lag: float, time_window: float, fps: float):
def sliding_autocorrelation(
data: np.ndarray, max_lag: float, time_window: float, fps: float
):
"""
Jitted compute of sliding auto-correlations (the correlation of a feature with itself using lagged windows).
Expand All @@ -1574,7 +1578,7 @@ def sliding_autocorrelation(data: np.ndarray, max_lag: float, time_window: float
results = np.full((data.shape[0]), -1.0)
for right in prange(time_window_frms - 1, data.shape[0]):
left = right - time_window_frms + 1
w_data = data[left: right + 1]
w_data = data[left : right + 1]
corrcfs = np.full((max_frm_lag), np.nan)
corrcfs[0] = 1
for shift in range(1, max_frm_lag):
Expand All @@ -1587,7 +1591,9 @@ def sliding_autocorrelation(data: np.ndarray, max_lag: float, time_window: float
const = np.ones_like(corrcfs)
mat_[:, 0] = const
mat_[:, 1] = corrcfs
det_ = np.linalg.lstsq(mat_.astype(np.float32), np.arange(0, max_frm_lag).astype(np.float32))[0]
det_ = np.linalg.lstsq(
mat_.astype(np.float32), np.arange(0, max_frm_lag).astype(np.float32)
)[0]
results[right] = det_[::-1][0]
return results

Expand Down
77 changes: 55 additions & 22 deletions simba/mixins/timeseries_features_mixin.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@
import typing
from typing import get_type_hints

from simba.utils.checks import (check_instance, check_int, check_str, check_that_column_exist, check_valid_lst, check_valid_array, check_float)
from simba.utils.checks import (check_float, check_instance, check_int,
check_str, check_that_column_exist,
check_valid_array, check_valid_lst)
from simba.utils.read_write import find_core_cnt


Expand Down Expand Up @@ -403,12 +405,15 @@ def sliding_percent_beyond_n_std(
[
(float32[:], float64[:], int64),
(int64[:], float64[:], int64),
])
]
)
def sliding_unique(x: np.ndarray, time_windows: np.ndarray, fps: int):
results = np.full((x.shape[0], time_windows.shape[0]), -1)
for i in prange(time_windows.shape[0]):
window_size = int(time_windows[i] * fps)
for l, r in zip(range(0, x.shape[0] + 1), range(window_size, x.shape[0] + 1)):
for l, r in zip(
range(0, x.shape[0] + 1), range(window_size, x.shape[0] + 1)
):
sample = x[l:r]
unique_cnt = np.unique(sample)
results[r - 1, i] = unique_cnt.shape[0]
Expand Down Expand Up @@ -770,7 +775,9 @@ def sliding_line_length(
results = np.full((data.shape[0], window_sizes.shape[0]), -1.0)
for i in prange(window_sizes.shape[0]):
window_size = int(window_sizes[i] * sample_rate)
for l, r in zip(prange(0, data.shape[0] + 1), prange(window_size, data.shape[0] + 1)):
for l, r in zip(
prange(0, data.shape[0] + 1), prange(window_size, data.shape[0] + 1)
):
sample = data[l:r]
results[r - 1, i] = np.sum(np.abs(np.diff(sample.astype(np.float64))))
return results.astype(np.float32)
Expand Down Expand Up @@ -1678,13 +1685,15 @@ def sliding_displacement(
return results.astype(np.float32)

@staticmethod
@njit('(float64[:], float64[:], float64[:], float64, boolean, float64)')
def sliding_two_signal_crosscorrelation(x: np.ndarray,
y: np.ndarray,
windows: np.ndarray,
sample_rate: float,
normalize: bool,
lag: float) -> np.ndarray:
@njit("(float64[:], float64[:], float64[:], float64, boolean, float64)")
def sliding_two_signal_crosscorrelation(
x: np.ndarray,
y: np.ndarray,
windows: np.ndarray,
sample_rate: float,
normalize: bool,
lag: float,
) -> np.ndarray:
"""
Calculate sliding (lagged) cross-correlation between two signals, e.g., the movement and velocity of two animals.
Expand All @@ -1710,11 +1719,15 @@ def sliding_two_signal_crosscorrelation(x: np.ndarray,
lag = int(sample_rate * lag)
for i in prange(windows.shape[0]):
W_s = int(windows[i] * sample_rate)
for cnt, (l1, r1) in enumerate(zip(range(0, x.shape[0] + 1), range(W_s, x.shape[0] + 1))):
for cnt, (l1, r1) in enumerate(
zip(range(0, x.shape[0] + 1), range(W_s, x.shape[0] + 1))
):
l2 = l1 - lag
if l2 < 0: l2 = 0
if l2 < 0:
l2 = 0
r2 = r1 - lag
if r2 - l2 < W_s: r2 = l2 + W_s
if r2 - l2 < W_s:
r2 = l2 + W_s
X_w = x[l1:r1]
Y_w = y[l2:r2]
if normalize:
Expand All @@ -1728,7 +1741,9 @@ def sliding_two_signal_crosscorrelation(x: np.ndarray,
return results.astype(np.float32)

@staticmethod
def sliding_pct_in_top_n(x: np.ndarray, windows: np.ndarray, n: int, fps: float) -> np.ndarray:
def sliding_pct_in_top_n(
x: np.ndarray, windows: np.ndarray, n: int, fps: float
) -> np.ndarray:
"""
Compute the percentage of elements in the top 'n' frequencies in sliding windows of the input array.
Expand All @@ -1746,17 +1761,35 @@ def sliding_pct_in_top_n(x: np.ndarray, windows: np.ndarray, n: int, fps: float)
>>> results = TimeseriesFeatureMixin.sliding_pct_in_top_n(x=x, windows=np.array([1.0]), n=4, fps=10)
"""

check_valid_array(data=x, source=f'{TimeseriesFeatureMixin.sliding_pct_in_top_n.__name__} x', accepted_ndims=(1,), accepted_dtypes=(np.float32, np.float64, np.int64, np.int32, int, float))
check_valid_array(data=windows, source=f'{TimeseriesFeatureMixin.sliding_pct_in_top_n.__name__} windows', accepted_ndims=(1,), accepted_dtypes=(np.float32, np.float64, np.int64, np.int32, int, float))
check_int(name=f'{TimeseriesFeatureMixin.sliding_pct_in_top_n.__name__} n', value=n, min_value=1)
check_float(name=f'{TimeseriesFeatureMixin.sliding_pct_in_top_n.__name__} fps', value=n, min_value=10e-6)
check_valid_array(
data=x,
source=f"{TimeseriesFeatureMixin.sliding_pct_in_top_n.__name__} x",
accepted_ndims=(1,),
accepted_dtypes=(np.float32, np.float64, np.int64, np.int32, int, float),
)
check_valid_array(
data=windows,
source=f"{TimeseriesFeatureMixin.sliding_pct_in_top_n.__name__} windows",
accepted_ndims=(1,),
accepted_dtypes=(np.float32, np.float64, np.int64, np.int32, int, float),
)
check_int(
name=f"{TimeseriesFeatureMixin.sliding_pct_in_top_n.__name__} n",
value=n,
min_value=1,
)
check_float(
name=f"{TimeseriesFeatureMixin.sliding_pct_in_top_n.__name__} fps",
value=n,
min_value=10e-6,
)
results = np.full((x.shape[0], windows.shape[0]), -1.0)
for i in range(windows.shape[0]):
W_s = int(windows[i] * fps)
for cnt, (l, r) in enumerate(zip(range(0, x.shape[0] + 1), range(W_s, x.shape[0] + 1))):
for cnt, (l, r) in enumerate(
zip(range(0, x.shape[0] + 1), range(W_s, x.shape[0] + 1))
):
sample = x[l:r]
cnts = np.sort(np.unique(sample, return_counts=True)[1])[-n:]
results[int(r - 1), i] = np.sum(cnts) / sample.shape[0]
return results


3 changes: 2 additions & 1 deletion simba/model/train_rf.py
Original file line number Diff line number Diff line change
Expand Up @@ -503,7 +503,8 @@ def save(self) -> None:

self.timer.stop_timer()
print(self.model_dir_out)
if not os.listdir(self.model_dir_out): os.makedirs(self.model_dir_out)
if not os.listdir(self.model_dir_out):
os.makedirs(self.model_dir_out)
self.save_rf_model(self.rf_clf, self.clf_name, self.model_dir_out)
stdout_success(
msg=f"Classifier {self.clf_name} saved in models/generated_models directory",
Expand Down
1 change: 0 additions & 1 deletion simba/plotting/single_run_model_validation_video.py
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,6 @@ def run(self):
)



# test = ValidateModelOneVideo(config_path=r'/Users/simon/Desktop/envs/simba/troubleshooting/mouse_open_field/project_folder/project_config.ini',
# feature_file_path='/Users/simon/Desktop/envs/simba/troubleshooting/mouse_open_field/project_folder/csv/features_extracted/SI_DAY3_308_CD1_PRESENT.csv',
# model_path='/Users/simon/Desktop/envs/simba/troubleshooting/mouse_open_field/models/generated_models/Running.sav',
Expand Down
15 changes: 2 additions & 13 deletions simba/plotting/single_run_model_validation_video_mp.py
Original file line number Diff line number Diff line change
Expand Up @@ -323,8 +323,8 @@ def __init__(
settings,
feature_file_path,
)
# if platform.system() == "Darwin":
# multiprocessing.set_start_method("spawn", force=True)
if platform.system() == "Darwin":
multiprocessing.set_start_method("spawn", force=True)

def __index_df_for_multiprocessing(
self, data: List[np.ndarray]
Expand Down Expand Up @@ -428,17 +428,6 @@ def run(self):
)


# test = ValidateModelOneVideoMultiprocess(config_path=r'/Users/simon/Desktop/envs/simba/troubleshooting/beepboop174/project_folder/project_config.ini',
# feature_file_path='/Users/simon/Desktop/envs/simba/troubleshooting/beepboop174/project_folder/csv/features_extracted/Trial 10.csv',
# model_path='/Users/simon/Desktop/envs/simba/troubleshooting/beepboop174/models/generated_models/Nose to Nose.sav',
# discrimination_threshold=0.6,
# shortest_bout=50,
# cores=6,
# settings={'pose': True, 'animal_names': True, 'styles': None},
# create_gantt=None)
# test.run()


# test = ValidateModelOneVideoMultiprocess(config_path=r'/Users/simon/Desktop/envs/simba/troubleshooting/mouse_open_field/project_folder/project_config.ini',
# feature_file_path='/Users/simon/Desktop/envs/simba/troubleshooting/mouse_open_field/project_folder/csv/features_extracted/SI_DAY3_308_CD1_PRESENT.csv',
# model_path='/Users/simon/Desktop/envs/simba/troubleshooting/mouse_open_field/models/generated_models/Running.sav',
Expand Down
64 changes: 49 additions & 15 deletions simba/ui/pop_ups/video_processing_pop_up.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,15 @@
from simba.video_processors.px_to_mm import get_coordinates_nilsson
from simba.video_processors.video_processing import (
VideoRotator, batch_convert_video_format, batch_create_frames,
change_fps_of_multiple_videos, change_img_format, change_single_video_fps,
clahe_enhance_video, clip_video_in_range, convert_to_mp4,
convert_video_powerpoint_compatible_format, copy_img_folder,
crop_multiple_videos, crop_multiple_videos_circles,
batch_video_to_greyscale, change_fps_of_multiple_videos, change_img_format,
change_single_video_fps, clahe_enhance_video, clip_video_in_range,
convert_to_mp4, convert_video_powerpoint_compatible_format,
copy_img_folder, crop_multiple_videos, crop_multiple_videos_circles,
crop_multiple_videos_polygons, crop_single_video, crop_single_video_circle,
crop_single_video_polygon, downsample_video, extract_frame_range,
extract_frames_single_video, frames_to_movie, gif_creator,
multi_split_video, remove_beginning_of_video, superimpose_frame_count,
video_concatenator, video_to_greyscale, batch_video_to_greyscale)
video_concatenator, video_to_greyscale)

sys.setrecursionlimit(10**7)

Expand Down Expand Up @@ -236,17 +236,46 @@ def __init__(self):
class GreyscaleSingleVideoPopUp(PopUpMixin):
def __init__(self):
super().__init__(title="GREYSCALE VIDEO")
video_frm = CreateLabelFrameWithIcon(parent=self.main_frm, header="GREYSCALE VIDEO", icon_name=Keys.DOCUMENTATION.value, icon_link=Links.VIDEO_TOOLS.value)
self.selected_video = FileSelect(video_frm, "VIDEO FILE PATH", title="Select a video file", lblwidth=20, file_types=[("VIDEO FILE", Options.ALL_VIDEO_FORMAT_STR_OPTIONS.value)])
video_frm = CreateLabelFrameWithIcon(
parent=self.main_frm,
header="GREYSCALE VIDEO",
icon_name=Keys.DOCUMENTATION.value,
icon_link=Links.VIDEO_TOOLS.value,
)
self.selected_video = FileSelect(
video_frm,
"VIDEO FILE PATH",
title="Select a video file",
lblwidth=20,
file_types=[("VIDEO FILE", Options.ALL_VIDEO_FORMAT_STR_OPTIONS.value)],
)
self.use_gpu_var_video = BooleanVar(value=False)
use_gpu_video_cb = Checkbutton(video_frm, text="Use GPU (reduced runtime)", variable=self.use_gpu_var_video)
run_video_btn = Button(video_frm, text="RUN", command=lambda: self.run_video(), fg='blue')
use_gpu_video_cb = Checkbutton(
video_frm, text="Use GPU (reduced runtime)", variable=self.use_gpu_var_video
)
run_video_btn = Button(
video_frm, text="RUN", command=lambda: self.run_video(), fg="blue"
)

dir_frm = CreateLabelFrameWithIcon(parent=self.main_frm, header="GREYSCALE VIDEO DIRECTORY", icon_name=Keys.DOCUMENTATION.value, icon_link=Links.VIDEO_TOOLS.value)
self.dir_selected = FolderSelect(dir_frm, "VIDEO DIRECTORY PATH", title="Select folder with videos", lblwidth=20)
dir_frm = CreateLabelFrameWithIcon(
parent=self.main_frm,
header="GREYSCALE VIDEO DIRECTORY",
icon_name=Keys.DOCUMENTATION.value,
icon_link=Links.VIDEO_TOOLS.value,
)
self.dir_selected = FolderSelect(
dir_frm,
"VIDEO DIRECTORY PATH",
title="Select folder with videos",
lblwidth=20,
)
self.use_gpu_var_dir = BooleanVar(value=False)
use_gpu_dir_cb = Checkbutton(dir_frm, text="Use GPU (reduced runtime)", variable=self.use_gpu_var_dir)
run_dir_btn = Button(dir_frm, text="RUN", command=lambda: self.run_dir(), fg='blue')
use_gpu_dir_cb = Checkbutton(
dir_frm, text="Use GPU (reduced runtime)", variable=self.use_gpu_var_dir
)
run_dir_btn = Button(
dir_frm, text="RUN", command=lambda: self.run_dir(), fg="blue"
)

video_frm.grid(row=0, column=0, sticky="NW")
self.selected_video.grid(row=0, column=0, sticky="NW")
Expand All @@ -262,11 +291,16 @@ def __init__(self):

def run_video(self):
check_file_exist_and_readable(file_path=self.selected_video.file_path)
video_to_greyscale(file_path=self.selected_video.file_path, gpu=self.use_gpu_var_video.get())
video_to_greyscale(
file_path=self.selected_video.file_path, gpu=self.use_gpu_var_video.get()
)

def run_dir(self):
check_if_dir_exists(in_dir=self.dir_selected.folder_path)
batch_video_to_greyscale(directory=self.dir_selected.folder_path, gpu=self.use_gpu_var_dir.get())
batch_video_to_greyscale(
directory=self.dir_selected.folder_path, gpu=self.use_gpu_var_dir.get()
)


class SuperImposeFrameCountPopUp(PopUpMixin):
def __init__(self):
Expand Down
5 changes: 2 additions & 3 deletions simba/ui/pop_ups/visualize_pose_in_dir_pop_up.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
__author__ = "Simon Nilsson"

from tkinter import *
import threading
from tkinter import *

from simba.mixins.pop_up_mixin import PopUpMixin
from simba.plotting.pose_plotter_mp import PosePlotter
Expand Down Expand Up @@ -174,5 +174,4 @@ def launch_clr_menu(self):
self.color_table_frme.grid(row=1, column=0, sticky=NW)



#test = VisualizePoseInFolderPopUp()
# test = VisualizePoseInFolderPopUp()
1 change: 1 addition & 0 deletions simba/ui/user_defined_pose_creator.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from simba.utils.enums import Paths
from simba.utils.errors import InvalidFileTypeError


class PoseConfigCreator(PlottingMixin):
"""
Expand Down

0 comments on commit d3b0d3b

Please sign in to comment.