Skip to content

Commit

Permalink
video tools
Browse files Browse the repository at this point in the history
  • Loading branch information
sronilsson committed May 6, 2024
1 parent 72951b5 commit 4fea0b2
Show file tree
Hide file tree
Showing 9 changed files with 366 additions and 65 deletions.
Binary file added docs/_static/img/brightness_contrast_ui.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/_static/img/interactive_clahe_ui.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
48 changes: 17 additions & 31 deletions simba/SimBA.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@
ImportFrameDirectoryPopUp, InitiateClipMultipleVideosByFrameNumbersPopUp,
InitiateClipMultipleVideosByTimestampsPopUp, MergeFrames2VideoPopUp,
MultiCropPopUp, MultiShortenPopUp, SuperImposeFrameCountPopUp,
VideoRotatorPopUp, VideoTemporalJoinPopUp)
VideoRotatorPopUp, VideoTemporalJoinPopUp, BrightnessContrastPopUp, InteractiveClahePopUp)
from simba.ui.pop_ups.visualize_pose_in_dir_pop_up import \
VisualizePoseInFolderPopUp
from simba.ui.tkinter_functions import DropDownMenu, Entry_Box, FileSelect
Expand Down Expand Up @@ -1678,31 +1678,16 @@ def __init__(self):
)

format_menu = Menu(video_process_menu)
format_menu.add_command(
label="Change image file formats", command=ChangeImageFormatPopUp
)
format_menu.add_command(
label="Change video file formats", command=ConvertVideoPopUp
)
video_process_menu.add_cascade(
label="Change formats...",
compound="left",
image=self.menu_icons["convert"]["img"],
menu=format_menu,
)
video_process_menu.add_command(
label="CLAHE enhance video",
compound="left",
image=self.menu_icons["clahe"]["img"],
command=CLAHEPopUp,
)
format_menu.add_command(label="Change image file formats", command=ChangeImageFormatPopUp)
format_menu.add_command(label="Change video file formats", command=ConvertVideoPopUp)
video_process_menu.add_cascade(label="Change formats...", compound="left", image=self.menu_icons["convert"]["img"], menu=format_menu)

video_process_menu.add_cascade(
label="Concatenate multiple videos",
compound="left",
image=self.menu_icons["concat"]["img"],
command=lambda: ConcatenatorPopUp(config_path=None),
)
clahe_menu = Menu(video_process_menu)
clahe_menu.add_command(label="CLAHE enhance videos", command=CLAHEPopUp)
clahe_menu.add_command(label="Interactively CLAHE enhance videos", command=InteractiveClahePopUp)
video_process_menu.add_cascade(label="CLAHE enhance videos...", compound="left", image=self.menu_icons["clahe"]["img"], menu=clahe_menu)

video_process_menu.add_cascade(label="Concatenate multiple videos", compound="left", image=self.menu_icons["concat"]["img"], command=lambda: ConcatenatorPopUp(config_path=None))
video_process_menu.add_cascade(
label="Concatenate two videos",
compound="left",
Expand All @@ -1722,12 +1707,9 @@ def __init__(self):
command=lambda: ConvertROIDefinitionsPopUp(),
)
convert_data_menu = Menu(video_process_menu)
convert_data_menu.add_command(
label="Convert CSV to parquet", command=Csv2ParquetPopUp
)
convert_data_menu.add_command(
label="Convert parquet o CSV", command=Parquet2CsvPopUp
)
convert_data_menu.add_command(label="Convert CSV to parquet", command=Csv2ParquetPopUp)
convert_data_menu.add_command(label="Convert parquet o CSV", command=Parquet2CsvPopUp)

video_process_menu.add_cascade(
label="Convert working file type...",
compound="left",
Expand Down Expand Up @@ -1784,6 +1766,10 @@ def __init__(self):
image=self.menu_icons["calipher"]["img"],
command=CalculatePixelsPerMMInVideoPopUp,
)

video_process_menu.add_command(label="Change video brightness / contrast", compound="left", image=self.menu_icons["brightness"]["img"], command=BrightnessContrastPopUp)


video_process_menu.add_command(
label="Merge frames to video",
compound="left",
Expand Down
Binary file added simba/assets/icons/brightness.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
35 changes: 35 additions & 0 deletions simba/mixins/statistics_mixin.py
Original file line number Diff line number Diff line change
Expand Up @@ -3186,6 +3186,41 @@ def manhattan_distance_cdist(data: np.ndarray) -> np.ndarray:
results = np.sum(differences, axis=-1)
return results

@staticmethod
@jit('(float32[:,:],)')
def mahalanobis_distance_cdist(data: np.ndarray) -> np.ndarray:
"""
Compute the Mahalanobis distance between every pair of observations in a 2D array using numba.
The Mahalanobis distance is a measure of the distance between a point and a distribution. It accounts for correlations between variables and the scales of the variables, making it suitable for datasets where features are not independent and have different variances.
.. note::
Significantly reduced runtime versus Mahalanobis scipy.cdist only with larger feature sets ( > 10-50).
However, Mahalanobis distance may not be suitable in certain scenarios, such as:
- When the dataset is small and the covariance matrix is not accurately estimated.
- When the dataset contains outliers that significantly affect the estimation of the covariance matrix.
- When the assumptions of multivariate normality are violated.
:param np.ndarray data: 2D array with feature observations. Frames on axis 0 and feature values on axis 1
:return np.ndarray: Pairwise Mahalanobis distance matrix where element (i, j) represents the Mahalanobis distance between observations i and j.
:example:
>>> data = np.random.randint(0, 50, (1000, 200)).astype(np.float32)
>>> x = mahalanobis_distance_cdist(data=data)
"""

covariance_matrix = np.cov(data, rowvar=False)
inv_covariance_matrix = np.linalg.inv(covariance_matrix).astype(np.float32)
n = data.shape[0]
distances = np.zeros((n, n))
for i in prange(n):
for j in range(n):
diff = data[i] - data[j]
diff = diff.astype(np.float32)
distances[i, j] = np.sqrt(np.dot(np.dot(diff, inv_covariance_matrix), diff.T))
return distances

@staticmethod
@njit("(int64[:], int64[:])")
def cohens_kappa(sample_1: np.ndarray, sample_2: np.ndarray):
Expand Down
172 changes: 147 additions & 25 deletions simba/ui/pop_ups/video_processing_pop_up.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import threading
from tkinter import *
from typing import Optional, Union
import subprocess
from datetime import datetime

from PIL import Image, ImageTk

Expand All @@ -14,6 +16,8 @@
from simba.mixins.config_reader import ConfigReader
from simba.mixins.pop_up_mixin import PopUpMixin
from simba.plotting.frame_mergerer_ffmpeg import FrameMergererFFmpeg
from simba.video_processors.brightness_contrast_ui import brightness_contrast_ui
from simba.video_processors.clahe_ui import interactive_clahe_ui
from simba.ui.tkinter_functions import (CreateLabelFrameWithIcon,
CreateToolTip, DropDownMenu, Entry_Box,
FileSelect, FolderSelect)
Expand All @@ -32,7 +36,7 @@
from simba.utils.read_write import (
check_if_hhmmss_timestamp_is_valid_part_of_video,
concatenate_videos_in_folder, find_all_videos_in_directory, get_fn_ext,
get_video_meta_data, seconds_to_timestamp)
get_video_meta_data, seconds_to_timestamp, find_files_of_filetypes_in_directory)
from simba.video_processors.extract_seqframes import extract_seq_frames
from simba.video_processors.multi_cropper import MultiCropper
from simba.video_processors.px_to_mm import get_coordinates_nilsson
Expand All @@ -55,33 +59,47 @@
class CLAHEPopUp(PopUpMixin):
def __init__(self):
super().__init__(title="CLAHE VIDEO CONVERSION")
clahe_frm = CreateLabelFrameWithIcon(
parent=self.main_frm,
header="Contrast Limited Adaptive Histogram Equalization",
icon_name=Keys.DOCUMENTATION.value,
icon_link=Links.VIDEO_TOOLS.value,
)
selected_video = FileSelect(
clahe_frm,
"Video path ",
title="Select a video file",
file_types=[("VIDEO", Options.ALL_VIDEO_FORMAT_STR_OPTIONS.value)],
)
button_clahe = Button(
clahe_frm,
text="Apply CLAHE",
command=lambda: threading.Thread(
target=clahe_enhance_video(file_path=selected_video.file_path)
).start(),
)
single_video_frm = CreateLabelFrameWithIcon(parent=self.main_frm,
header="SINGLE VIDEO - Contrast Limited Adaptive Histogram Equalization",
icon_name=Keys.DOCUMENTATION.value,
icon_link=Links.VIDEO_TOOLS.value)
self.selected_video = FileSelect(single_video_frm, "VIDEO PATH:", title="Select a video file", file_types=[("VIDEO", Options.ALL_VIDEO_FORMAT_STR_OPTIONS.value)], lblwidth=25)
run_single_video_btn = Button(single_video_frm, text="Apply CLAHE on VIDEO", command=lambda: self.run_single_video(), fg="blue")

multiple_videos_frm = CreateLabelFrameWithIcon(parent=self.main_frm,
header="MULTIPLE VIDEOs - Contrast Limited Adaptive Histogram Equalization",
icon_name=Keys.DOCUMENTATION.value,
icon_link=Links.VIDEO_TOOLS.value)
self.selected_dir = FolderSelect(multiple_videos_frm, "VIDEO DIRECTORY PATH:", lblwidth=25)
run_multiple_btn = Button(multiple_videos_frm, text="Apply CLAHE on DIRECTORY", command=lambda: self.run_directory(), fg="blue")

single_video_frm.grid(row=0, column=0, sticky=NW)
self.selected_video.grid(row=0, column=0, sticky=NW)
run_single_video_btn.grid(row=1, column=0, sticky=NW)

clahe_frm.grid(row=0, sticky=W)
selected_video.grid(row=0, sticky=W)
button_clahe.grid(row=1, pady=5)
# self.main_frm.mainloop()
multiple_videos_frm.grid(row=1, column=0, sticky=NW)
self.selected_dir.grid(row=0, column=0, sticky=NW)
run_multiple_btn.grid(row=1, column=0, sticky=NW)
#self.main_frm.mainloop()


# _ = CLAHEPopUp()
def run_single_video(self):
selected_video = self.selected_video.file_path
check_file_exist_and_readable(file_path=selected_video)
threading.Thread(target=clahe_enhance_video(file_path=selected_video)).start()

def run_directory(self):
timer = SimbaTimer(start=True)
video_dir = self.selected_dir.folder_path
check_if_dir_exists(in_dir=video_dir, source=self.__class__.__name__)
self.video_paths = find_files_of_filetypes_in_directory(directory=video_dir, extensions=Options.ALL_VIDEO_FORMAT_OPTIONS.value, raise_error=True)
for file_path in self.video_paths:
threading.Thread(target=clahe_enhance_video(file_path=file_path)).start()
timer.stop_timer()
stdout_success(msg=f'CLAHE enhanced {len(self.video_paths)} video(s)', elapsed_time=timer.elapsed_time_str)


#_ = CLAHEPopUp()


class CropVideoPopUp(PopUpMixin):
Expand Down Expand Up @@ -2047,6 +2065,110 @@ def run(self):
save_dir=self.output_folder.folder_path,
)

class BrightnessContrastPopUp(PopUpMixin):
def __init__(self):
super().__init__(title="CHANGE BRIGHTNESS / CONTRAST")
self.datetime = datetime.now().strftime("%Y%m%d%H%M%S")
single_video_frm = CreateLabelFrameWithIcon(parent=self.main_frm, header="CHANGE BRIGHTNESS / CONTRAST SINGLE VIDEO", icon_name=Keys.DOCUMENTATION.value, icon_link=Links.VIDEO_TOOLS.value)
self.selected_video = FileSelect(single_video_frm, "VIDEO PATH:", title="Select a video file", file_types=[("VIDEO", Options.ALL_VIDEO_FORMAT_STR_OPTIONS.value)], lblwidth=25)
run_video_btn = Button(single_video_frm, text="RUN SINGLE VIDEO", command=lambda: self.run_video(), fg="blue")

single_video_frm.grid(row=0, column=0, sticky="NW")
self.selected_video.grid(row=0, column=0, sticky="NW")
run_video_btn.grid(row=1, column=0, sticky="NW")

video_dir_frm = CreateLabelFrameWithIcon(parent=self.main_frm, header="CHANGE BRIGHTNESS / CONTRAST MULTIPLE VIDEOS", icon_name=Keys.DOCUMENTATION.value, icon_link=Links.VIDEO_TOOLS.value)
self.selected_dir = FolderSelect(video_dir_frm, "VIDEO DIRECTORY PATH:", lblwidth=25)
run_dir_btn = Button(video_dir_frm, text="RUN VIDEO DIRECTORY", command=lambda: self.run_directory(), fg="blue")

video_dir_frm.grid(row=1, column=0, sticky="NW")
self.selected_dir.grid(row=0, column=0, sticky="NW")
run_dir_btn.grid(row=1, column=0, sticky="NW")
self.main_frm.mainloop()

def run_video(self):
video_path = self.selected_video.file_path
check_file_exist_and_readable(file_path=video_path)
self.brightness, self.contrast = brightness_contrast_ui(video_path=video_path)
self.video_paths = [video_path]
self.apply()

def run_directory(self):
video_dir = self.selected_dir.folder_path
check_if_dir_exists(in_dir=video_dir, source=self.__class__.__name__)
self.video_paths = find_files_of_filetypes_in_directory(directory=video_dir, extensions=Options.ALL_VIDEO_FORMAT_OPTIONS.value, raise_error=True)
self.brightness, self.contrast = brightness_contrast_ui(video_path=self.video_paths[0])
self.apply()

def apply(self):
timer = SimbaTimer(start=True)
for file_cnt, file_path in enumerate(self.video_paths):
video_timer = SimbaTimer(start=True)
dir, video_name, ext = get_fn_ext(filepath=file_path)
print(f'Creating copy of {video_name}...')
out_path = os.path.join(dir, f'{video_name}_eq_{self.datetime}{ext}')
cmd = f'ffmpeg -i "{file_path}" -vf "eq=brightness={self.brightness}:contrast={self.contrast}" -loglevel error -stats "{out_path}" -y'
subprocess.call(cmd, shell=True, stdout=subprocess.PIPE)
video_timer.stop_timer()
stdout_success(msg=f'Video {out_path} complete!', elapsed_time=video_timer.elapsed_time_str)
timer.stop_timer()
stdout_success(f'{len(self.video_paths)} video(s) converted.', elapsed_time=timer.elapsed_time_str)


class InteractiveClahePopUp(PopUpMixin):
"""
.. image:: _static/img/interactive_clahe_ui.gif
:width: 500
:align: center
"""

def __init__(self):
super().__init__(title="INTERACTIVE CLAHE")
self.datetime = datetime.now().strftime("%Y%m%d%H%M%S")
single_video_frm = CreateLabelFrameWithIcon(parent=self.main_frm, header="INTERACTIVE CLAHE - SINGLE VIDEO", icon_name=Keys.DOCUMENTATION.value, icon_link=Links.VIDEO_TOOLS.value)
self.selected_video = FileSelect(single_video_frm, "VIDEO PATH:", title="Select a video file", file_types=[("VIDEO", Options.ALL_VIDEO_FORMAT_STR_OPTIONS.value)], lblwidth=25)
run_video_btn = Button(single_video_frm, text="RUN SINGLE VIDEO", command=lambda: self.run_video(), fg="blue")

single_video_frm.grid(row=0, column=0, sticky="NW")
self.selected_video.grid(row=0, column=0, sticky="NW")
run_video_btn.grid(row=1, column=0, sticky="NW")

video_dir_frm = CreateLabelFrameWithIcon(parent=self.main_frm, header="INTERACTIVE CLAHE - MULTIPLE VIDEOS", icon_name=Keys.DOCUMENTATION.value, icon_link=Links.VIDEO_TOOLS.value)
self.selected_dir = FolderSelect(video_dir_frm, "VIDEO DIRECTORY PATH:", lblwidth=25)
run_dir_btn = Button(video_dir_frm, text="RUN VIDEO DIRECTORY", command=lambda: self.run_directory(), fg="blue")

video_dir_frm.grid(row=1, column=0, sticky="NW")
self.selected_dir.grid(row=0, column=0, sticky="NW")
run_dir_btn.grid(row=1, column=0, sticky="NW")
self.main_frm.mainloop()

def run_video(self):
video_path = self.selected_video.file_path
check_file_exist_and_readable(file_path=video_path)
self.clip_limit, self.tile_size = interactive_clahe_ui(data=video_path)
self.video_paths = [video_path]
self.apply()

def run_directory(self):
video_dir = self.selected_dir.folder_path
check_if_dir_exists(in_dir=video_dir, source=self.__class__.__name__)
self.video_paths = find_files_of_filetypes_in_directory(directory=video_dir, extensions=Options.ALL_VIDEO_FORMAT_OPTIONS.value, raise_error=True)
self.clip_limit, self.tile_size = interactive_clahe_ui(data=self.video_paths[0])
self.apply()

def apply(self):
timer = SimbaTimer(start=True)
for file_cnt, file_path in enumerate(self.video_paths):
dir, video_name, ext = get_fn_ext(filepath=file_path)
print(f'Creating CLAHE copy of {video_name}...')
out_path = os.path.join(dir, f'{video_name}_CLAHE_CLIPLIMIT_{int(self.clip_limit)}_TILESIZE_{int(self.tile_size)}_{self.datetime}{ext}')
clahe_enhance_video(file_path=file_path, clip_limit=int(self.clip_limit), tile_grid_size=(int(self.tile_size), int(self.tile_size)), out_path=out_path)
timer.stop_timer()
stdout_success(f'{len(self.video_paths)} video(s) converted.', elapsed_time=timer.elapsed_time_str)





# ClipMultipleVideosByFrameNumbers
# ClipMultipleVideosByFrameNumbers(data_dir='/Users/simon/Desktop/envs/simba/troubleshooting/beepboop174/project_folder/videos/test', save_dir='/Users/simon/Desktop/envs/simba/troubleshooting/beepboop174/project_folder/videos/clipped')
Loading

0 comments on commit 4fea0b2

Please sign in to comment.