Skip to content

Commit

Permalink
roi pre-set sizes
Browse files Browse the repository at this point in the history
  • Loading branch information
sronilsson committed Sep 9, 2024
1 parent 28c209b commit 6daa89e
Show file tree
Hide file tree
Showing 16 changed files with 1,069 additions and 62 deletions.
Binary file added simba/assets/icons/half_circle.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 simba/assets/icons/hexagon.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 simba/assets/icons/roi_green.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 simba/assets/icons/size_black.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 simba/assets/icons/square_black.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
677 changes: 677 additions & 0 deletions simba/data_processors/cuda/circular_statistics.py

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion simba/data_processors/cuda/geometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ def is_inside_rectangle(x: np.ndarray, y: np.ndarray) -> np.ndarray:
:param np.ndarray x: 2d numeric np.ndarray size (N, 2).
:param np.ndarray y: 2d numeric np.ndarray size (2, 2) (top left[x, y], bottom right[x, y])
:return np.ndarray: 2d numeric boolean (N, 1) with 1s representing the point being inside the rectangle and 0 if the point is outside the rectangle.
:return: 2d numeric boolean (N, 1) with 1s representing the point being inside the rectangle and 0 if the point is outside the rectangle.
:rtype: np.ndarray
"""

x = np.ascontiguousarray(x).astype(np.int32)
Expand Down Expand Up @@ -298,6 +299,7 @@ def poly_area(data: np.ndarray,
:param pixels_per_mm: Optional scaling factor to convert the area from pixels squared to square millimeters. Default is 1.0.
:param batch_size: Optional batch size for processing the data in chunks to fit in memory. Default is 0.5e+7.
:return: A 1D numpy array of shape (N,) containing the computed area of each polygon in square millimeters.
:rtype: np.ndarray
"""

check_valid_array(data=data, source=f'{poly_area} data', accepted_ndims=(3,), accepted_dtypes=Formats.NUMERIC_DTYPES.value)
Expand Down
1 change: 0 additions & 1 deletion simba/data_processors/cuda/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -763,7 +763,6 @@ def slice_imgs(video_path: Union[str, os.PathLike],
"""
Slice frames from a video based on given shape coordinates (rectangles or circles) and return the cropped regions using GPU acceleration.
.. video:: _static/img/slice_imgs_gpu.webm
:width: 800
:autoplay:
Expand Down
14 changes: 8 additions & 6 deletions simba/data_processors/cuda/statistics.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,14 @@ def get_3pt_angle(x: np.ndarray, y: np.ndarray, z: np.ndarray) -> np.ndarray:
:header-rows: 1
.. seealso::
For CPU function see :func:`~simba.mixins.FeatureExtractionMixin.angle3pt` and
For CPU function see :func:`~simba.mixins.FeatureExtractionMixin.angle3pt_serialized`.
For CPU function see :func:`~simba.mixins.feature_extraction_mixin.FeatureExtractionMixin.angle3pt` and
For CPU function see :func:`~simba.mixins.feature_extraction_mixin.FeatureExtractionMixin.angle3pt_serialized`.
:param x: A numpy array of shape (n, 2) representing the first point (e.g., nose) coordinates.
:param y: A numpy array of shape (n, 2) representing the second point (e.g., center) coordinates, where the angle is computed.
:param z: A numpy array of shape (n, 2) representing the second point (e.g., center) coordinates, where the angle is computed.
:return: A numpy array of shape (n, 1) containing the calculated angles (in degrees) for each row.
:rtype: np.ndarray
:example:
>>> video_path = r"/mnt/c/troubleshooting/mitra/project_folder/videos/501_MA142_Gi_CNO_0514.mp4"
Expand Down Expand Up @@ -110,11 +111,12 @@ def count_values_in_ranges(x: np.ndarray, r: np.ndarray) -> np.ndarray:
:header-rows: 1
.. seealso::
For CPU function see :func:`~simba.mixins.FeatureExtractionMixin.count_values_in_range`.
For CPU function see :func:`~simba.mixins.feature_extraction_mixin.FeatureExtractionMixin.count_values_in_range`.
:param np.ndarray x: 2d array with feature values.
:param np.ndarray r: 2d array with lower and upper boundaries.
:return np.ndarray: 2d array of size len(x) x len(r) with the counts of values in each feature range (inclusive).
:return: 2d array of size len(x) x len(r) with the counts of values in each feature range (inclusive).
:rtype: np.ndarray
:example:
>>> x = np.random.randint(1, 11, (10, 10)).astype(np.int8)
Expand Down Expand Up @@ -153,7 +155,7 @@ def get_euclidean_distance_cuda(x: np.ndarray, y: np.ndarray) -> np.ndarray:
:header-rows: 1
.. seealso::
For CPU function see :func:`~simba.mixins.FeatureExtractionMixin.framewise_euclidean_distance`.
For CPU function see :func:`~simba.mixins.feature_extraction_mixin.FeatureExtractionMixin.framewise_euclidean_distance`.
For CuPY function see :func:`~simba.data_processors.cuda.statistics.get_euclidean_distance_cupy`.
Expand Down Expand Up @@ -193,7 +195,7 @@ def get_euclidean_distance_cupy(x: np.ndarray,
.. seealso::
For CPU function see :func:`~simba.mixins.FeatureExtractionMixin.framewise_euclidean_distance`.
For CPU function see :func:`~simba.mixins.feature_extraction_mixin.FeatureExtractionMixin.framewise_euclidean_distance`.
For CUDA JIT function see :func:`~simba.data_processors.cuda.statistics.get_euclidean_distance_cuda`.
:param np.ndarray x: A 2D NumPy array with shape (n, 2), where each row represents a point in a 2D space.
Expand Down
17 changes: 7 additions & 10 deletions simba/mixins/config_reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -710,16 +710,13 @@ def read_config_entry(
source=self.__class__.__name__,
)

def read_video_info_csv(self, file_path: str) -> pd.DataFrame:
def read_video_info_csv(self, file_path: Union[str, os.PathLike]) -> pd.DataFrame:
"""
Helper to read the project_folder/logs/video_info.csv of the SimBA project in as a pd.DataFrame
Parameters
----------
file_path: str
Returns
-------
pd.DataFrame
:param Union[str, os.PathLike] file_path: Path to the project_folder/logs/video_info.csv file.
:return: Dataframe representation of the file.
:rtype: pd.DataFrame
"""

if not os.path.isfile(file_path):
Expand Down Expand Up @@ -764,16 +761,16 @@ def read_video_info_csv(self, file_path: str) -> pd.DataFrame:
return info_df

def read_video_info(
self, video_name: str, raise_error: Optional[bool] = True
) -> (pd.DataFrame, float, float):
self, video_name: str, raise_error: Optional[bool] = True) -> Tuple[pd.DataFrame, float, float]:
"""
Helper to read the meta-data (pixels per mm, resolution, fps) from the video_info.csv for a single input file.
:param str video_name: The name of the video without extension to get the metadata for
:param Optional[bool] raise_error: If True, raise error if video info for the video name cannot be found. Default: True.
:raise ParametersFileError: If ``raise_error`` and video metadata info is not found
:raise DuplicationError: If file contains multiple entries for the same video.
:return (pd.DataFrame, float, float) representing all video info, pixels per mm, and fps
:returns: Tuple representing all video info, pixels per mm, and fps
:rtype: Tuple[pd.DataFrame, float, float]
"""

video_settings = self.video_info_df.loc[
Expand Down
18 changes: 11 additions & 7 deletions simba/mixins/feature_extraction_mixin.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,8 @@ def euclidean_distance(
.. seealso::
Use :meth:`simba.mixins.feature_extraction_mixin.FeatureExtractionMixin.framewise_euclidean_distance`
for imporved run-times.
for imporved run-times. Use :func:`simba.data_processors.cuda.statistics.get_euclidean_distance_cuda`
or :func:`simba.data_processors.cuda.statistics.get_euclidean_distance_cupy` for GPU acceleration.
:param np.ndarray bp_1_x: 2D array of size len(frames) x 1 with bodypart 1 x-coordinates.
:param np.ndarray bp_2_x: 2D array of size len(frames) x 1 with bodypart 2 x-coordinates.
Expand Down Expand Up @@ -112,6 +113,11 @@ def angle3pt(ax: float, ay: float, bx: float, by: float, cx: float, cy: float) -
:width: 300
:align: center
.. seealso::
:func:`simba.mixins.feature_extraction_mixin.FeatureExtractionMixin.angle3pt_serialized`,
:func:
:example:
>>> FeatureExtractionMixin.angle3pt(ax=122.0, ay=198.0, bx=237.0, by=138.0, cx=191.0, cy=109)
>>> 59.78156901181637
Expand Down Expand Up @@ -743,12 +749,10 @@ def minimum_bounding_rectangle(points: np.ndarray) -> np.ndarray:

@staticmethod
@jit(nopython=True)
def framewise_euclidean_distance(
location_1: np.ndarray,
location_2: np.ndarray,
px_per_mm: float,
centimeter: bool = False,
) -> np.ndarray:
def framewise_euclidean_distance(location_1: np.ndarray,
location_2: np.ndarray,
px_per_mm: float,
centimeter: bool = False) -> np.ndarray:
"""
Jitted helper finding frame-wise distances between two moving locations in millimeter or centimeter.
Expand Down
25 changes: 14 additions & 11 deletions simba/roi_tools/ROI_define.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import copy
import glob
import os
from tkinter import *
from PIL import ImageTk
import PIL.Image

import cv2
import pandas as pd
Expand All @@ -23,10 +24,11 @@
from simba.utils.printing import log_event, stdout_success
from simba.utils.read_write import find_all_videos_in_directory, get_fn_ext
from simba.utils.warnings import NoDataFoundWarning
from simba.utils.lookups import get_icons_paths
from simba.ui.pop_ups.roi_fixed_size_pop_up import DrawFixedROIPopUp

WINDOW_SIZE = (800, 750)


class ROI_definitions(ConfigReader, PopUpMixin):
"""
Launch ROI user-interface for drawing user-defined shapes in a video.
Expand Down Expand Up @@ -64,6 +66,11 @@ def __init__(self, config_path: str, video_path: str):
self.other_video_file_names.append(os.path.basename(video))
self.video_info, self.curr_px_mm, self.curr_fps = self.read_video_info(video_name=self.file_name)

self.menu_icons = get_icons_paths()

for k in self.menu_icons.keys():
self.menu_icons[k]["img"] = ImageTk.PhotoImage(image=PIL.Image.open(os.path.join(os.path.dirname(__file__), self.menu_icons[k]["icon_path"])))

self.roi_root = Toplevel()
self.roi_root.minsize(WINDOW_SIZE[0], WINDOW_SIZE[1])
self.screen_width = self.roi_root.winfo_screenwidth()
Expand Down Expand Up @@ -224,9 +231,7 @@ def apply_rois_from_other_video(self):
for shape_type in ["rectangles", "circleDf", "polygons"]:
c_df = pd.read_hdf(self.roi_coordinates_path, key=shape_type)
if len(c_df) > 0:
c_df = c_df[c_df["Video"] == target_video].reset_index(
drop=True
)
c_df = c_df[c_df["Video"] == target_video].reset_index(drop=True)
c_df["Video"] = self.file_name
c_df = c_df.to_dict("records")
if shape_type == "rectangles":
Expand Down Expand Up @@ -848,9 +853,9 @@ def window_menus(self):
menu = Menu(self.roi_root)
file_menu = Menu(menu)
menu.add_cascade(label="File (ROI)", menu=file_menu)
file_menu.add_command(
label="Preferences...", command=lambda: PreferenceMenu(self.image_data)
)

file_menu.add_command(label="Preferences...", compound="left", image=self.menu_icons["settings"]["img"], command=lambda: PreferenceMenu(self.image_data))
file_menu.add_command(label="Draw ROIs of pre-defined sizes...", compound="left", image=self.menu_icons["size_black"]["img"], command=lambda: DrawFixedROIPopUp(roi_image=self.image_data))
file_menu.add_separator()
file_menu.add_command(label="Exit", command=self.Exit)
self.roi_root.config(menu=menu)
Expand Down Expand Up @@ -893,9 +898,7 @@ def __init__(self, image_data):
line_type_dropdown = OptionMenu(pref_lbl_frame, self.line_type, *line_type_list)
text_thickness_dropdown = OptionMenu(pref_lbl_frame, self.text_thickness, *text_thickness_list)
text_size_dropdown = OptionMenu(pref_lbl_frame, self.text_size, *text_size_list)
click_sens_dropdown = OptionMenu(
pref_lbl_frame, self.click_sens, *click_sensitivity_list
)
click_sens_dropdown = OptionMenu(pref_lbl_frame, self.click_sens, *click_sensitivity_list)
duplicate_jump_size_lbl = Label(pref_lbl_frame, text="DUPLICATE SHAPE JUMP: ", font=Formats.FONT_REGULAR.value)
duplicate_jump_size_list = list(range(1, 100, 5))
self.duplicate_jump_size = IntVar()
Expand Down
21 changes: 5 additions & 16 deletions simba/roi_tools/ROI_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,7 @@ def __init__(self,

config = read_config_file(config_path=config_path)
self.roi_define = ROI_define_instance
self.project_path = config.get(
ConfigKey.GENERAL_SETTINGS.value, ConfigKey.PROJECT_PATH.value
)
self.project_path = config.get(ConfigKey.GENERAL_SETTINGS.value, ConfigKey.PROJECT_PATH.value)
_, self.curr_vid_name, ext = get_fn_ext(video_path)
(
self.duplicate_jump_size,
Expand All @@ -48,10 +46,7 @@ def __init__(self,
self.colors = self.roi_define.named_shape_colors
self.select_color = (128, 128, 128)
_, self.orig_frame = self.cap.read()
self.frame_width, self.frame_height = (
self.orig_frame.shape[0],
self.orig_frame.shape[1],
)
self.frame_width, self.frame_height = (self.orig_frame.shape[0], self.orig_frame.shape[1])
self.frame_default_loc = (
int(self.roi_define.default_top_left_x - self.frame_width),
0,
Expand Down Expand Up @@ -539,19 +534,13 @@ def check_if_click_is_tag():
def remove_ROI(self, roi_to_delete):
if roi_to_delete.startswith("Rectangle"):
rectangle_name = roi_to_delete.split("Rectangle: ")[1]
self.out_rectangles[:] = [
d for d in self.out_rectangles if d.get("Name") != rectangle_name
]
self.out_rectangles[:] = [d for d in self.out_rectangles if d.get("Name") != rectangle_name]
if roi_to_delete.startswith("Circle"):
circle_name = roi_to_delete.split("Circle: ")[1]
self.out_circles[:] = [
d for d in self.out_circles if d.get("Name") != circle_name
]
self.out_circles[:] = [d for d in self.out_circles if d.get("Name") != circle_name]
if roi_to_delete.startswith("Polygon"):
polygon_name = roi_to_delete.split("Polygon: ")[1]
self.out_polygon[:] = [
d for d in self.out_polygon if d.get("Name") != polygon_name
]
self.out_polygon[:] = [d for d in self.out_polygon if d.get("Name") != polygon_name]
self.insert_all_ROIs_into_image()

def insert_all_ROIs_into_image(
Expand Down
Loading

0 comments on commit 6daa89e

Please sign in to comment.