Skip to content

Commit

Permalink
agg clf counts
Browse files Browse the repository at this point in the history
  • Loading branch information
sronilsson committed Feb 11, 2025
1 parent 742e85d commit 235ca2e
Show file tree
Hide file tree
Showing 4 changed files with 107 additions and 41 deletions.
14 changes: 10 additions & 4 deletions simba/roi_tools/roi_ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ def __init__(self,
self.video_ext = get_fn_ext(filepath=video_path)[2][1:]
self.img, self.img_idx = ImageMixin.find_first_non_uniform_clr_frm(video_path=video_path)
self.define_ui = PopUpMixin(title="REGION OF INTEREST (ROI) SETTINGS", size=WINDOW_SIZE)
self.roi_mixin = ROI_mixin.__init__(self, video_path=video_path, config_path=config_path, img_idx=self.img_idx)
ROI_mixin.__init__(self, video_path=video_path, config_path=config_path, img_idx=self.img_idx, main_frm=self.define_ui.root)
self.other_project_video_paths = find_all_videos_in_directory(directory=self.video_dir, as_dict=True).values()
self.other_project_video_paths = [x for x in self.other_project_video_paths if x != video_path]
self.other_project_video_names = [get_fn_ext(x)[1] for x in self.other_project_video_paths]
Expand All @@ -53,15 +53,21 @@ def __init__(self,
self.get_save_roi_panel(parent_frame=self.define_ui.main_frm, row_idx=8)
self.get_status_bar_panel(parent_frame=self.define_ui.main_frm, row_idx=9)
self.get_file_menu(root=self.define_ui.root)
self.set_selected_shape_type(shape_type='rectangle')
self.define_ui.root.protocol("WM_DELETE_WINDOW", self._close)
self.define_ui.main_frm.mainloop()

def _close(self):
self.define_ui.root.destroy()
try:
self.roi_mixin.close_img()
self.close_img()
except:
pass
self.define_ui.root.destroy()
self.define_ui.root.quit()

# ROI_ui(config_path=r"C:\troubleshooting\mouse_open_field\project_folder\project_config.ini",
# video_path=r'C:\troubleshooting\mouse_open_field\project_folder\videos\Video2.mp4')

#ROI_ui(config_path=r"C:\troubleshooting\mouse_open_field\project_folder\project_config.ini", video_path=r"C:\troubleshooting\mouse_open_field\project_folder\videos\Video1.mp4")
# ROI_ui(config_path=r"C:\troubleshooting\mitra\project_folder\project_config.ini",
# video_path=r"C:\troubleshooting\mitra\project_folder\videos\501_MA142_Gi_CNO_0514.mp4")

46 changes: 31 additions & 15 deletions simba/roi_tools/roi_ui_mixin.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
import ctypes
import math
import os
import threading
from copy import copy, deepcopy
from copy import copy
from tkinter import *
from typing import Optional, Union

import cv2
import numpy as np
import pandas as pd
from PIL import Image, ImageTk
from pynput import keyboard
from scipy.spatial.distance import cdist
from shapely.geometry import Polygon

Expand All @@ -34,7 +32,8 @@
get_triangle_vertices,
get_vertices_hexagon,
get_video_roi_data_from_dict,
set_roi_metric_sizes)
set_roi_metric_sizes,
change_roi_dict_video_name)
from simba.sandbox.roi_tkinter.interactive_roi_modifier_tkinter import \
InteractiveROIModifier
from simba.ui.tkinter_functions import (CreateLabelFrameWithIcon, DropDownMenu,
Expand All @@ -60,14 +59,17 @@ class ROI_mixin(ConfigReader):
def __init__(self,
video_path: Union[str, os.PathLike],
config_path: Union[str, os.PathLike],
img_idx: int):
img_idx: int,
main_frm: Toplevel):

ConfigReader.__init__(self, config_path=config_path, read_video_info=True, create_logger=False)
self.video_meta = get_video_meta_data(video_path=video_path)
self.img_center = (int(self.video_meta['height'] / 2), int(self.video_meta['height'] / 2))
_, self.px_per_mm, _ = self.read_video_info(video_name=self.video_meta['video_name'], video_info_df_path=self.video_info_path)
self.video_path = video_path
self.img_idx = img_idx
self.main_frm = main_frm

self.selected_shape_type = None
self.color_option_dict = get_color_dict()
self.menu_icons = get_menu_icons()
Expand All @@ -79,6 +81,7 @@ def __init__(self,
self.img_window.title(DRAW_FRAME_NAME)
self.img_lbl = Label(self.img_window, name='img_lbl')
self.img_lbl.pack()
self.img_window.protocol("WM_DELETE_WINDOW", self.close_img)
self.settings = {item.name: item.value for item in ROI_SETTINGS}
self.rectangles_df, self.circles_df, self.polygon_df, self.roi_dict, self.roi_names, self.other_roi_dict, self.other_video_names_w_rois = get_roi_data(roi_path=self.roi_coordinates_path, video_name=self.video_meta['video_name'])
self.overlay_rois_on_image(show_ear_tags=False, show_roi_info=False)
Expand Down Expand Up @@ -166,14 +169,12 @@ def get_select_shape_type_panel(self,
self.rectangle_button.grid(row=0, sticky=NW, pady=10, padx=(0, 10))
self.circle_button.grid(row=0, column=1, sticky=NW, pady=10, padx=(0, 10))
self.polygon_button.grid(row=0, column=2, sticky=NW, pady=10, padx=(0, 10))
self.set_selected_shape_type(shape_type=RECTANGLE)

def get_shape_attr_panel(self,
parent_frame: Union[Frame, Canvas, LabelFrame, Toplevel],
row_idx: int):

self.shape_attr_panel = CreateLabelFrameWithIcon(parent=parent_frame, header="SHAPE ATTRIBUTES", font=Formats.FONT_HEADER.value, padx=5, pady=5, icon_name='attributes_large')
#self.shape_attr_panel = LabelFrame(parent_frame, text="SHAPE ATTRIBUTES", font=Formats.FONT_HEADER.value, padx=5, pady=5)
self.thickness_dropdown = DropDownMenu(self.shape_attr_panel, "SHAPE THICKNESS: ", ROI_SETTINGS.SHAPE_THICKNESS_OPTIONS.value, 17)
self.thickness_dropdown.setChoices(5)
self.color_dropdown = DropDownMenu(self.shape_attr_panel, "SHAPE COLOR: ", list(self.color_option_dict.keys()), 17)
Expand Down Expand Up @@ -259,11 +260,11 @@ def get_shapes_from_other_video_panel(self,
self.apply_other_video_btn.grid(row=0, column=1, sticky=W, pady=10, padx=(0, 10))

def draw(self, parent_frame):

def on_click(event):
self.click_event.set(True)
self.got_attributes = self.selector.get_attributes()
self.root.unbind("<Button-1>"); self.root.unbind("<Escape>"); self.img_window.unbind("<Escape>");
self.set_btn_clrs()

self.shape_info_btn.configure(text="SHOW SHAPE INFO")
shape_name = self.shape_name_eb.entry_get.strip()
Expand Down Expand Up @@ -305,9 +306,10 @@ def on_click(event):
del self.selector; del self.root
self.overlay_rois_on_image()

def set_btn_clrs(self, btn: SimbaButton):
btn.configure(fg=ROI_SETTINGS.SELECT_COLOR.value)
for other_btns in [self.delete_all_btn, self.draw_btn, self.chg_attr_btn, self.delete_selected_btn, self.duplicate_selected_btn, self.move_shape_btn, self.shape_info_btn, self.save_data_btn]:
def set_btn_clrs(self, btn: Optional[SimbaButton] = None):
if btn is not None:
btn.configure(fg=ROI_SETTINGS.SELECT_COLOR.value)
for other_btns in [self.delete_all_btn, self.draw_btn, self.chg_attr_btn, self.delete_selected_btn, self.duplicate_selected_btn, self.move_shape_btn, self.shape_info_btn, self.save_data_btn, self.apply_other_video_btn]:
if btn != other_btns:
other_btns.configure(fg=ROI_SETTINGS.UNSELECT_COLOR.value)

Expand All @@ -322,6 +324,7 @@ def on_click(event):
self.click_event.set(True)
self.root.unbind("<Button-1>"); self.root.unbind("<Escape>"); self.img_window.unbind("<Escape>");
self.interactive_modifier.unbind_keys()
self.set_btn_clrs()
self.set_status_bar_panel(text="ROI MOVE MODE EXITED", fg="blue")

self.set_btn_clrs(btn=self.move_shape_btn)
Expand All @@ -335,10 +338,10 @@ def on_click(event):
self.root.wait_variable(self.click_event)

self.img_window = self.interactive_modifier.img_window
self.roi_dict = copy(self.interactive_modifier.roi_dict)
self.roi_dict = self.interactive_modifier.roi_dict
self.rectangles_df, self.circles_df, self.polygon_df = get_roi_df_from_dict(roi_dict=self.roi_dict)
del self.interactive_modifier; del self.root
self.overlay_rois_on_image()
self.overlay_rois_on_image(show_ear_tags=False, show_roi_info=False)


def change_img(self, stride: Union[int, str]):
Expand Down Expand Up @@ -389,6 +392,7 @@ def set_selected_shape_type(self, shape_type: Optional[str] = None):
self.circle_button.configure(fg=ROI_SETTINGS.UNSELECT_COLOR.value)
self.polygon_button.configure(fg=ROI_SETTINGS.SELECT_COLOR.value)
self.selected_shape_type = copy(shape_type)
self.set_btn_clrs()


def show_shape_info(self):
Expand Down Expand Up @@ -550,6 +554,7 @@ def save_attr_changes(self):
self.update_dropdown_menu(dropdown=self.roi_dropdown, new_options=self.roi_names, set_index=None, set_str=new_name)
if hasattr(self, 'change_attr_frm'):
self.change_attr_frm.destroy()
self.set_btn_clrs()
self.set_status_bar_panel(text=f'Changed attributes for shape {name}', fg='blue')


Expand All @@ -560,19 +565,21 @@ def apply_different_video(self, video_name: str):
raise InvalidInputError(msg=error_txt, source=self.__class__.__name__)
video_roi_dict = get_video_roi_data_from_dict(roi_dict=self.other_roi_dict, video_name=video_name)
if len(video_roi_dict.keys()) > 0:
video_roi_dict = change_roi_dict_video_name(roi_dict=video_roi_dict, video_name=self.video_meta['video_name'])
self.reset_img_shape_memory()
self.roi_names = list(video_roi_dict.keys())
self.roi_dict = copy(video_roi_dict)
self.roi_dict = video_roi_dict
self.rectangles_df, self.circles_df, self.polygon_df = get_roi_df_from_dict(roi_dict=self.roi_dict)
self.update_dropdown_menu(dropdown=self.roi_dropdown, new_options=self.roi_names, set_index=0)
self.overlay_rois_on_image(show_roi_info=False, show_ear_tags=False)
self.set_status_bar_panel(text=f'COPIED {len(self.roi_names)} ROI(s) FROM VIDEO {video_name}', fg='blue')
else:
self.set_status_bar_panel(text=f'NO ROIs FOUND FOR VIDEO {video_name}', fg='darkred')
self.set_btn_clrs(btn=self.apply_other_video_btn)

def save_video_rois(self):
self.set_btn_clrs(btn=self.save_data_btn)
other_rectangles_df, other_circles_df, other_polygons_df = get_roi_df_from_dict(roi_dict=self.other_roi_dict)
other_rectangles_df, other_circles_df, other_polygons_df = get_roi_df_from_dict(roi_dict=self.other_roi_dict, video_name_nesting=True)
out_rectangles = pd.concat([self.rectangles_df, other_rectangles_df], axis=0).reset_index(drop=True)
out_circles = pd.concat([self.circles_df, other_circles_df], axis=0).reset_index(drop=True)
out_polygons = pd.concat([self.polygon_df, other_polygons_df], axis=0).reset_index(drop=True)
Expand All @@ -583,6 +590,8 @@ def save_video_rois(self):
store.close()
msg = f"ROI definitions saved for video: {self.video_meta['video_name']} ({len(list(self.roi_dict.keys()))} ROI(s))"
self.set_status_bar_panel(text=msg, fg='blue')
#self.rectangles_df, self.circles_df, self.polygon_df, self.roi_dict, self.roi_names, self.other_roi_dict, self.other_video_names_w_rois = get_roi_data(roi_path=self.roi_coordinates_path, video_name=self.video_meta['video_name'])
#self.overlay_rois_on_image(show_ear_tags=False, show_roi_info=False)
stdout_success(msg=f"ROI definitions saved for video: {self.video_meta['video_name']}", source=self.__class__.__name__)


Expand Down Expand Up @@ -724,6 +733,7 @@ def _fixed_roi_draw(self):
self.overlay_rois_on_image(show_ear_tags=False, show_roi_info=False)
self.fixed_roi_status_bar['text'] = self.txt
stdout_success(msg=self.txt)
self.set_btn_clrs()


def fixed_roi_rectangle(self):
Expand Down Expand Up @@ -758,6 +768,7 @@ def fixed_roi_rectangle(self):
self._fixed_roi_draw()



def fixed_roi_circle(self):
self._fixed_roi_checks()
valid, error_msg = check_int(name='RADIUS', value=self.fixed_roi_circle_radius_eb.entry_get, min_value=1)
Expand Down Expand Up @@ -866,5 +877,10 @@ def fixed_roi_triangle(self):
def close_img(self):
try:
self.img_window.destroy()
except:
pass
try:
self.main_frm.destroy()
self.main_frm.quit()
except:
pass
77 changes: 61 additions & 16 deletions simba/roi_tools/roi_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,32 +160,62 @@ def set_roi_metric_sizes(roi_dict: dict, px_conversion_factor: Union[int, float]
return out


def get_roi_df_from_dict(roi_dict: dict) -> Tuple[pd.DataFrame, pd.DataFrame, pd.DataFrame]:
def get_roi_df_from_dict(roi_dict: dict, video_name_nesting: Optional[bool] = False) -> Tuple[pd.DataFrame, pd.DataFrame, pd.DataFrame]:
"""
Helper create DataFrames from a shape dictionary.
"""
rectangles_df, circles_df, polygon_df = pd.DataFrame(columns=get_rectangle_df_headers()), pd.DataFrame(columns=get_circle_df_headers()), pd.DataFrame(columns=get_polygon_df_headers())
for name, data in roi_dict.items():
if data['Shape_type'].lower() == ROI_SETTINGS.RECTANGLE.value:
rectangles_df = pd.concat([rectangles_df, pd.DataFrame([data])], ignore_index=True)
elif data['Shape_type'].lower() == ROI_SETTINGS.CIRCLE.value:
circles_df = pd.concat([circles_df, pd.DataFrame([data])], ignore_index=True)
elif data['Shape_type'].lower() == ROI_SETTINGS.POLYGON.value:
polygon_df = pd.concat([polygon_df, pd.DataFrame([data])], ignore_index=True)
if not video_name_nesting:
for shape_name, shape_data in roi_dict.items():
if shape_data['Shape_type'].lower() == ROI_SETTINGS.RECTANGLE.value:
rectangles_df = pd.concat([rectangles_df, pd.DataFrame([shape_data])], ignore_index=True)
elif shape_data['Shape_type'].lower() == ROI_SETTINGS.CIRCLE.value:
circles_df = pd.concat([circles_df, pd.DataFrame([shape_data])], ignore_index=True)
elif shape_data['Shape_type'].lower() == ROI_SETTINGS.POLYGON.value:
polygon_df = pd.concat([polygon_df, pd.DataFrame([shape_data])], ignore_index=True)
else:
for video_name, video_data in roi_dict.items():
for shape_name, shape_data in video_data.items():
if shape_data['Shape_type'].lower() == ROI_SETTINGS.RECTANGLE.value:
rectangles_df = pd.concat([rectangles_df, pd.DataFrame([shape_data])], ignore_index=True)
elif shape_data['Shape_type'].lower() == ROI_SETTINGS.CIRCLE.value:
circles_df = pd.concat([circles_df, pd.DataFrame([shape_data])], ignore_index=True)
elif shape_data['Shape_type'].lower() == ROI_SETTINGS.POLYGON.value:
polygon_df = pd.concat([polygon_df, pd.DataFrame([shape_data])], ignore_index=True)

return (rectangles_df, circles_df, polygon_df)


def get_roi_dict_from_dfs(rectangle_df: pd.DataFrame, circle_df: pd.DataFrame, polygon_df: pd.DataFrame) -> dict:
def get_roi_dict_from_dfs(rectangle_df: pd.DataFrame,
circle_df: pd.DataFrame,
polygon_df: pd.DataFrame,
video_name_nesting: Optional[bool] = False) -> dict:
"""
Helper create dict from a shape dataframes.
"""

out = {}
for idx, row in rectangle_df.iterrows():
out[row['Name']] = row.to_dict()
if not video_name_nesting:
out[row['Name']] = row.to_dict()
else:
if row['Video'] not in list(out.keys()):
out[row['Video']] = {}
out[row['Video']][row['Name']] = row.to_dict()
for idx, row in circle_df.iterrows():
out[row['Name']] = row.to_dict()
if not video_name_nesting:
out[row['Name']] = row.to_dict()
else:
if row['Video'] not in list(out.keys()):
out[row['Video']] = {}
out[row['Video']][row['Name']] = row.to_dict()
for idx, row in polygon_df.iterrows():
out[row['Name']] = row.to_dict()
if not video_name_nesting:
out[row['Name']] = row.to_dict()
else:
if row['Video'] not in list(out.keys()):
out[row['Video']] = {}
out[row['Video']][row['Name']] = row.to_dict()
return out


Expand All @@ -208,21 +238,36 @@ def get_roi_data(roi_path: Union[str, os.PathLike], video_name: str) -> tuple:
other_rectangles_df = in_rectangles_df[in_rectangles_df['Video'] != video_name].reset_index(drop=True)
other_circles_df = in_circles_df[in_circles_df['Video'] != video_name].reset_index(drop=True)
other_polygon_df = in_polygon_df[in_polygon_df['Video'] != video_name].reset_index(drop=True)
other_roi_dict = get_roi_dict_from_dfs(rectangle_df=other_rectangles_df, circle_df=other_circles_df, polygon_df=other_polygon_df)
other_roi_dict = get_roi_dict_from_dfs(rectangle_df=other_rectangles_df,
circle_df=other_circles_df,
polygon_df=other_polygon_df,
video_name_nesting=True)
if len(rectangles_df) + len(circles_df) + len(polygon_df) > 0:
roi_names = list(set(list(rectangles_df['Name'].unique()) + list(circles_df['Name'].unique()) + list(polygon_df['Name'].unique())))
roi_dict = get_roi_dict_from_dfs(rectangle_df=rectangles_df, circle_df=circles_df, polygon_df=polygon_df)
roi_dict = get_roi_dict_from_dfs(rectangle_df=rectangles_df, circle_df=circles_df, polygon_df=polygon_df, video_name_nesting=False)

return (rectangles_df, circles_df, polygon_df, roi_dict, roi_names, other_roi_dict, other_video_names_w_rois)

def get_video_roi_data_from_dict(roi_dict: dict, video_name: str) -> dict:
out = {}
for video_key, data in roi_dict.items():
if video_key == video_name:
for shape_name, shape_data in data.items():
out[shape_name] = shape_data
return out


def change_roi_dict_video_name(roi_dict: dict, video_name: str) -> dict:
out = {}
for shape_name, shape_data in roi_dict.items():
if shape_data['Video'] == video_name:
out[shape_name] = shape_data
new_shape_data = copy(shape_data)
new_shape_data['Video'] = video_name
out[shape_name] = new_shape_data
return out




def get_ear_tags_for_rectangle(center: Tuple[int, int], width: int, height: int) -> Dict[str, Union[int, Tuple[int, int]]]:
"""
Knowing the center, width, and height of rectangle, return its vertices.
Expand Down
Loading

0 comments on commit 235ca2e

Please sign in to comment.