__author__ = "Simon Nilsson; sronilsson@gmail.com"
import os
from tkinter import *
from tkinter import ttk
from typing import Dict, Optional, Union
try:
from typing import Literal
except:
from typing_extensions import Literal
from simba.mixins.config_reader import ConfigReader
from simba.mixins.pop_up_mixin import PopUpMixin
from simba.pose_importers.dlc_importer_csv import import_dlc_csv_data
from simba.pose_importers.facemap_h5_importer import FaceMapImporter
from simba.pose_importers.import_mars import MarsImporter
from simba.pose_importers.madlc_importer import MADLCImporterH5
from simba.pose_importers.read_DANNCE_mat import (import_DANNCE_file,
import_DANNCE_folder)
from simba.pose_importers.simba_blob_importer import SimBABlobImporter
from simba.pose_importers.simba_yolo_importer import SimBAYoloImporter
from simba.pose_importers.sleap_csv_importer import SLEAPImporterCSV
from simba.pose_importers.sleap_h5_importer import SLEAPImporterH5
from simba.pose_importers.sleap_slp_importer import SLEAPImporterSLP
from simba.pose_importers.superanimal_import import SuperAnimalTopViewImporter
from simba.pose_importers.trk_importer import TRKImporter
from simba.ui.tkinter_functions import (CreateLabelFrameWithIcon, DropDownMenu,
Entry_Box, FileSelect, FolderSelect,
SimBADropDown)
from simba.utils.checks import check_instance, check_int, check_str
from simba.utils.enums import ConfigKey, Dtypes, Formats, Options
from simba.utils.errors import InvalidInputError
from simba.utils.read_write import read_config_file
GAUSSIAN = 'Gaussian'
SAVITZKY_GOLAY = 'Savitzky Golay'
INTERPOLATION_MAP = {'Animal(s)': 'animals', 'Body-parts': 'body-parts'}
SMOOTHING_MAP = {'Savitzky Golay': 'savitzky-golay', 'Gaussian': 'gaussian'}
FRAME_DIR_IMPORT_TITLES = {'CSV (DLC/DeepPoseKit)': 'IMPORT DLC CSV DIRECTORY', 'MAT (DANNCE 3D)': 'IMPORT DANNCE MAT DIRECTORY', 'JSON (BENTO)': 'IMPORT MARS JSON DIRECTORY', 'CSV (SimBA BLOB)': 'IMPORT SIMBA BLOB CSV DIRECTORY', 'H5 (FaceMap)': 'IMPORT FaceMap H5 DIRECTORY', 'CSV (SimBA YOLO)': 'IMPORT YOLO CSV DIRECTORY'}
FRAME_FILE_IMPORT_TITLES = {'CSV (DLC/DeepPoseKit)': 'IMPORT DLC CSV FILE', 'MAT (DANNCE 3D)': 'IMPORT DANNCE MAT FILE', 'JSON (BENTO)': 'IMPORT MARS JSON FILE', 'CSV (SimBA BLOB)': 'IMPORT SIMBA BLOB CSV FILE', 'H5 (FaceMap)': 'IMPORT FaceMap H5 FILE', 'CSV (SimBA YOLO)': 'IMPORT YOLO CSV FILE'}
FILE_TYPES = {'CSV (DLC/DeepPoseKit)': '*.csv', 'MAT (DANNCE 3D)': '*.mat', 'JSON (BENTO)': '*.json', 'CSV (SimBA BLOB)': '*.csv', 'H5 (FaceMap)': '*.h5', 'CSV (SimBA YOLO)': '*.csv'}
[docs]class ImportPoseFrame(ConfigReader, PopUpMixin):
"""
.. image:: _static/img/ImportPoseFrame.webp
:alt: Import Pose Frame
:width: 500
:align: center
:param Optional[Union[Frame, Canvas, LabelFrame, ttk.Frame]] parent_frm: Parent frame to insert the Import pose frame into. If None, one is created.
:param Optional[Union[str, os.PathLike]] config_path:
:param Optional[int] idx_row: The row in parent_frm to insert the Import pose frame into. Default: 0.
:param Optional[int] idx_column: The column in parent_frm to insert the Import pose frame into. Default: 0.
:example:
>>> _ = ImportPoseFrame(config_path='/Users/simon/Desktop/envs/simba/troubleshooting/two_black_animals_14bp/project_folder/project_config.ini')
>>> _ = ImportPoseFrame(config_path=r"C:\troubleshooting\simba_blob_project\project_folder\project_config.ini")
"""
def __init__(self,
parent_frm: Optional[Union[Frame, Canvas, LabelFrame, ttk.Frame]] = None,
config_path: Optional[Union[str, os.PathLike]] = None,
idx_row: Optional[int] = 0,
idx_column: Optional[int] = 0):
if parent_frm is None and config_path is None:
raise InvalidInputError(msg='If parent_frm is None, please pass config_path', source=self.__class__.__name__)
elif parent_frm is None and config_path is not None:
PopUpMixin.__init__(self, config_path=config_path, title='IMPORT POSE ESTIMATION')
parent_frm = self.main_frm
check_instance(source=f'{self.__class__.__name__} parent_frm', accepted_types=(Frame, Canvas, LabelFrame, ttk.Frame), instance=parent_frm)
check_int(name=f'{self.__class__.__name__} idx_row', value=idx_row, min_value=0)
check_int(name=f'{self.__class__.__name__} idx_column', value=idx_column, min_value=0)
self.import_tracking_frm = CreateLabelFrameWithIcon(parent=parent_frm, header="IMPORT TRACKING DATA", relief='solid', icon_name='pose')
self.import_tracking_frm.grid(row=0, column=0, sticky=NW)
if config_path is None:
Label(self.import_tracking_frm, text="Please CREATE PROJECT CONFIG before importing tracking data \n", font=Formats.FONT_REGULAR.value).grid(row=0, column=0, sticky=NW)
else:
ConfigReader.__init__(self, config_path=config_path, read_video_info=False)
self.data_type_dropdown = SimBADropDown(parent=self.import_tracking_frm, dropdown_options=Options.IMPORT_TYPE_OPTIONS.value, label="DATA TYPE: ", label_width=25, command=self.create_import_menu, dropdown_width=25, value=Options.IMPORT_TYPE_OPTIONS.value[0], img='file_type', tooltip_key='IMPORT_POSE_DATA_TYPE')
self.data_type_dropdown.grid(row=0, column=0, sticky=NW)
self.create_import_menu(data_type_choice=Options.IMPORT_TYPE_OPTIONS.value[0])
self.import_tracking_frm.grid(row=idx_row, column=idx_column, sticky=NW)
#parent_frm.mainloop()
def __show_smoothing_entry_box_from_dropdown(self, choice: str):
if (choice == GAUSSIAN) or (choice == SAVITZKY_GOLAY):
self.smoothing_time_eb.grid(row=1, column=0, sticky=NW)
else:
self.smoothing_time_eb.grid_forget()
def __get_smooth_interpolation_settings(self,
interpolation_settings: str,
smoothing_setting: str,
smoothing_time: Union[str, int]):
METHODS_MAP = {'Gaussian': 'gaussian', 'Savitzky Golay': 'savitzky-golay'}
if interpolation_settings not in [Dtypes.NONE.value, None]:
interpolation_settings = interpolation_settings.split(':')
interpolation_settings = {'type': INTERPOLATION_MAP[interpolation_settings[0]].lower().strip(), 'method': interpolation_settings[1].lower().strip()}
else:
interpolation_settings = None
if smoothing_setting not in [Dtypes.NONE.value, None]:
check_int(name='SMOOTHING TIME', value=smoothing_time, min_value=1)
smoothing_setting = {'method': METHODS_MAP[smoothing_setting], 'time_window': int(smoothing_time)}
else:
smoothing_setting = None
return smoothing_setting, interpolation_settings
def __import_dlc_csv_data(self,
interpolation_settings: str,
smoothing_time: Union[str, int],
data_path: Union[str, os.PathLike],
smoothing_setting: Literal['Gaussian', 'None', 'Savitzky Golay']):
if not os.path.isfile(data_path) and not os.path.isdir(data_path):
raise InvalidInputError(msg=f'{data_path} is NOT a valid path', source=self.__class__.__name__)
smoothing_settings, interpolation_settings = self.__get_smooth_interpolation_settings(interpolation_settings, smoothing_setting, smoothing_time)
import_dlc_csv_data(config_path=self.config_path,
data_path=data_path,
interpolation_settings=interpolation_settings,
smoothing_settings=smoothing_settings)
def __import_simba_blob_data(self,
interpolation_settings: str,
smoothing_time: Union[str, int],
data_path: Union[str, os.PathLike],
smoothing_setting: Literal['Gaussian', 'None', 'Savitzky Golay']):
if not os.path.isfile(data_path) and not os.path.isdir(data_path):
raise InvalidInputError(msg=f'{data_path} is NOT a valid path', source=self.__class__.__name__)
smoothing_settings, interpolation_settings = self.__get_smooth_interpolation_settings(interpolation_settings, smoothing_setting, smoothing_time)
blob_importer = SimBABlobImporter(config_path=self.config_path, data_path=data_path, interpolation_settings=interpolation_settings, smoothing_settings=smoothing_settings, verbose=True)
blob_importer.run()
def __import_yolo_data(self,
interpolation_settings: str,
smoothing_time: Union[str, int],
data_path: Union[str, os.PathLike],
smoothing_setting: Literal['Gaussian', 'None', 'Savitzky Golay']):
if not os.path.isfile(data_path) and not os.path.isdir(data_path):
raise InvalidInputError(msg=f'{data_path} is NOT a valid path', source=self.__class__.__name__)
smoothing_settings, interpolation_settings = self.__get_smooth_interpolation_settings(interpolation_settings, smoothing_setting, smoothing_time)
importer = SimBAYoloImporter(config_path=self.config_path, data_dir=data_path, verbose=True, add_to_video_info=False, smoothing_settings=smoothing_settings, interpolation_settings=interpolation_settings)
importer.run()
def __import_facemap_data(self,
interpolation_settings: str,
smoothing_time: Union[str, int],
data_path: Union[str, os.PathLike],
smoothing_setting: Literal['Gaussian', 'None', 'Savitzky Golay']):
if not os.path.isfile(data_path) and not os.path.isdir(data_path):
raise InvalidInputError(msg=f'{data_path} is NOT a valid path', source=self.__class__.__name__)
smoothing_settings, interpolation_settings = self.__get_smooth_interpolation_settings(interpolation_settings, smoothing_setting, smoothing_time)
facemap_importer = FaceMapImporter(config_path=self.config_path, data_path=data_path, interpolation_settings=interpolation_settings, smoothing_settings=smoothing_settings, verbose=True)
facemap_importer.run()
def __multi_animal_run_call(self,
pose_estimation_tool: str,
interpolation_settings: str,
smoothing_settings: str,
smoothing_window: int,
animal_names: Dict[int, Entry_Box],
data_path: Union[str, os.PathLike],
tracking_data_type: Optional[str] = None):
if not os.path.isfile(data_path) and not os.path.isdir(data_path):
raise InvalidInputError(msg=f'{data_path} is NOT a valid path', source=self.__class__.__name__)
smoothing_settings, interpolation_settings = self.__get_smooth_interpolation_settings(interpolation_settings, smoothing_settings, smoothing_window)
animal_ids = []
if len(list(animal_names.items())) == 1: animal_ids.append("Animal_1")
else:
for animal_cnt, animal_entry_box in animal_names.items():
check_str(name=f"ANIMAL {str(animal_cnt)} NAME", value=animal_entry_box.entry_get, allow_blank=False)
animal_ids.append(animal_entry_box.entry_get)
config = read_config_file(config_path=self.config_path)
config.set(ConfigKey.MULTI_ANIMAL_ID_SETTING.value, ConfigKey.MULTI_ANIMAL_IDS.value, ",".join(animal_ids))
with open(self.config_path, "w") as f: config.write(f)
if pose_estimation_tool == "H5 (multi-animal DLC)":
data_importer = MADLCImporterH5(config_path=self.config_path,
data_folder=data_path,
file_type=tracking_data_type,
id_lst=animal_ids,
interpolation_settings=interpolation_settings,
smoothing_settings=smoothing_settings)
elif pose_estimation_tool == "SLP (SLEAP)":
data_importer = SLEAPImporterSLP(project_path=self.config_path,
data_folder=data_path,
id_lst=animal_ids,
interpolation_settings=interpolation_settings,
smoothing_settings=smoothing_settings)
elif pose_estimation_tool == "TRK (multi-animal APT)":
data_importer = TRKImporter(config_path=self.config_path,
data_path=data_path,
animal_id_lst=animal_ids,
interpolation_method=interpolation_settings,
smoothing_settings=smoothing_settings)
elif pose_estimation_tool == "CSV (SLEAP)":
data_importer = SLEAPImporterCSV(config_path=self.config_path,
data_folder=data_path,
id_lst=animal_ids,
interpolation_settings=interpolation_settings,
smoothing_settings=smoothing_settings)
elif pose_estimation_tool == "H5 (SLEAP)":
data_importer = SLEAPImporterH5(config_path=self.config_path,
data_folder=data_path,
id_lst=animal_ids,
interpolation_settings=interpolation_settings,
smoothing_settings=smoothing_settings)
elif pose_estimation_tool == "H5 (SuperAnimal-TopView)":
data_importer = SuperAnimalTopViewImporter(config_path=self.config_path,
data_folder=data_path,
id_lst=animal_ids,
interpolation_settings=interpolation_settings,
smoothing_settings=smoothing_settings)
else:
raise InvalidInputError(msg=f'pose estimation tool {pose_estimation_tool} not recognized', source=self.__class__.__name__)
data_importer.run()
def __create_animal_names_entry_boxes(self,
animal_cnt: str) -> None:
check_int(name="NUMBER OF ANIMALS", value=animal_cnt, min_value=0)
if hasattr(self, "animal_names_frm"):
self.animal_names_frm.destroy()
if not hasattr(self, "multi_animal_id_list"):
self.multi_animal_id_list = []
for i in range(int(animal_cnt)):
self.multi_animal_id_list.append(f"Animal {i+1}")
self.animal_names_frm = Frame(self.animal_settings_frm, pady=5, padx=5)
self.animal_name_entry_boxes = {}
for i in range(int(animal_cnt)):
self.animal_name_entry_boxes[i + 1] = Entry_Box(self.animal_names_frm, f"Animal {str(i+1)} name: ", "25", tooltip_key='IMPORT_POSE_ANIMAL_NAME', justify='center')
if i <= len(self.multi_animal_id_list) - 1:
self.animal_name_entry_boxes[i + 1].entry_set(self.multi_animal_id_list[i])
self.animal_name_entry_boxes[i + 1].grid(row=i, column=0, sticky=NW)
self.animal_names_frm.grid(row=1, column=0, sticky=NW)
#_ = ImportPoseFrame(config_path=r"E:\troubleshooting\mitra_pbn\mitra_pbn\project_folder\project_config.ini")