diff --git a/docs/nb/define_rois.ipynb b/docs/nb/define_rois.ipynb new file mode 100644 index 000000000..f52b31b92 --- /dev/null +++ b/docs/nb/define_rois.ipynb @@ -0,0 +1,152 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "e741f10a", + "metadata": {}, + "outputs": [], + "source": [ + "from simba.utils.read_write import find_files_of_filetypes_in_directory\n", + "from simba.utils.enums import Options\n", + "from simba.roi_tools.ROI_define import ROI_definitions\n", + "from simba.roi_tools.ROI_multiply import multiply_ROIs" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "cd6d77d7", + "metadata": {}, + "outputs": [], + "source": [ + "# DEFINE THE PATH TO THE SIMBA PROJECT CONFIG, AND THE PATH TO THE DIRECTORY IN SIMBA WHERE THE VIDEOS ARE STORED.\n", + "PROJECT_CONFIG_PATH = r\"C:\\troubleshooting\\mitra\\project_folder\\project_config.ini\"\n", + "VIDEO_DIR_PATH = r'C:\\troubleshooting\\mitra\\project_folder\\videos'" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "6efef9a3", + "metadata": {}, + "outputs": [], + "source": [ + "#CREATE A LIST OF PATHS TO THE VIDEO FILES THAT EXIST IN THE SIMBA PROJECT\n", + "video_file_paths = find_files_of_filetypes_in_directory(directory=VIDEO_DIR_PATH, extensions=Options.ALL_VIDEO_FORMAT_OPTIONS.value)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "c9d03e3c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "['C:\\\\troubleshooting\\\\mitra\\\\project_folder\\\\videos\\\\501_MA142_Gi_CNO_0514.mp4', 'C:\\\\troubleshooting\\\\mitra\\\\project_folder\\\\videos\\\\501_MA142_Gi_CNO_0516.mp4', 'C:\\\\troubleshooting\\\\mitra\\\\project_folder\\\\videos\\\\501_MA142_Gi_CNO_0521.mp4', 'C:\\\\troubleshooting\\\\mitra\\\\project_folder\\\\videos\\\\501_MA142_Gi_DCZ_0603.mp4', 'C:\\\\troubleshooting\\\\mitra\\\\project_folder\\\\videos\\\\501_MA142_Gi_Saline_0513.mp4']\n" + ] + } + ], + "source": [ + "#WE CAN PRINT IT OUT THE FIRST 5 VIDEO PATHS IN THIS LIST TO SEE HOW IT LOOKS.\n", + "print(video_file_paths[:5])" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "aa4ad831", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "C:\\Users\\sroni\\.conda\\envs\\simba\\lib\\_collections_abc.py:666: MatplotlibDeprecationWarning:\n", + "\n", + "The global colormaps dictionary is no longer considered public API.\n", + "\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "SIMBA COMPLETE: ROI definitions saved for video: 501_MA142_Gi_CNO_0514 \tcomplete\n" + ] + } + ], + "source": [ + "# WE RUN THE ROI DRAWING INTERFACE AND DRAW ROIs ON THE FIRST VIDEO IN THE LIST.\n", + "# ONCE THE ROIs ARE DRAWN ON THIS VIDEO, REMEMBER TO CLICK \"SAVE ROI DATA\", AND THEN CLOSE ALL OPEN THE INTERFACE WINDOWS.\n", + "_ = ROI_definitions(config_path=PROJECT_CONFIG_PATH, video_path=video_file_paths[0])" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "bc51d1d4", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "SIMBA COMPLETE: ROIs for 501_MA142_Gi_CNO_0514 applied to a further 99 videos (Duplicated rectangles count: 1, circles: 0, polygons: 0). \tcomplete\n", + "\n", + "Next, click on \"draw\" to modify ROI location(s) or click on \"reset\" to remove ROI drawing(s)\n" + ] + } + ], + "source": [ + "#NEXT, WE MULTIPLY ALL THE ROIs ON THE FIRST VIDEO ON THE LIST ON ALL OTHE VIDEOS IN THE SIMBA PROJECT (THIS PROJECT CONTAINS A TOTAL OF 100 VIDEOS)\n", + "multiply_ROIs(config_path=PROJECT_CONFIG_PATH, filename=video_file_paths[0])" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "a065f176", + "metadata": {}, + "outputs": [], + "source": [ + "#FINALLY, WE START TO ITERATE OVER ALL OTHER VIDEOS IN THE PROJECT (OMITTING THE FIRST VIDEO), AND CORRECT THE ROIs.\n", + "# I DON'T HAVE A GOOD WAY OF AUTMATICALLY OPENING THE NEXT VIDEO ONCE A VIDEO IS CLOSED AT THE MOMENT, \n", + "# SO WILL HAVE TO MANUALLY CHANGE `video_file_paths[1]` TO `video_file_paths[2]` etc.\n", + "_ = ROI_definitions(config_path=PROJECT_CONFIG_PATH, video_path=video_file_paths[1])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8b2a1d89", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.13" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/notebooks.rst b/docs/notebooks.rst index 3daa05ca5..fb389f4f8 100644 --- a/docs/notebooks.rst +++ b/docs/notebooks.rst @@ -59,6 +59,7 @@ Miscellaneous nb/import_sleap_h5 nb/multiclass + nb/define_rois diff --git a/setup.py b/setup.py index a99612582..5b9561748 100644 --- a/setup.py +++ b/setup.py @@ -29,7 +29,7 @@ # Setup configuration setuptools.setup( name="simba-uw-tf-dev", - version="2.5.2", + version="2.5.4", author="Simon Nilsson, Jia Jie Choong, Sophia Hwang", author_email="sronilsson@gmail.com", description="Toolkit for computer classification and analysis of behaviors in experimental animals", diff --git a/simba/data_processors/cuda/statistics.py b/simba/data_processors/cuda/statistics.py index 200cd016b..fe4a2abd9 100644 --- a/simba/data_processors/cuda/statistics.py +++ b/simba/data_processors/cuda/statistics.py @@ -532,17 +532,16 @@ def dunn_index(x: np.ndarray, y: np.ndarray) -> float: .. seelalso: For CPU-based method, use :func:`simba.mixins.statistics_mixin.Statistics.dunn_index` - .. math:: + The Dunn Index is given by: - Dunn\ Index = \frac{\\min_{i \\neq j} \\delta(c_i, c_j)}{\\max_k \\Delta(c_k)} + .. math:: + D = \frac{\min_{i \neq j} \{ \delta(C_i, C_j) \}}{\max_k \{ \Delta(C_k) \}} - Where: - - :math:`\\delta(c_i, c_j)` is the distance between clusters :math:`c_i` and :math:`c_j`. - - :math:`\\Delta(c_k)` is the diameter (i.e., maximum intra-cluster distance) of cluster :math:`c_k`. + where :math:`\delta(C_i, C_j)` is the distance between clusters :math:`C_i` and :math:`C_j`, and + :math:`\Delta(C_k)` is the diameter of cluster :math:`C_k`. The higher the Dunn Index, the better the clustering, as a higher value indicates that the clusters are well-separated relative to their internal cohesion. - .. csv-table:: :header: EXPECTED RUNTIMES :file: ../../../docs/tables/dunn_index_cuda.csv diff --git a/simba/mixins/feature_extraction_supplement_mixin.py b/simba/mixins/feature_extraction_supplement_mixin.py index a17f00a3d..f4d4c44bf 100644 --- a/simba/mixins/feature_extraction_supplement_mixin.py +++ b/simba/mixins/feature_extraction_supplement_mixin.py @@ -750,13 +750,16 @@ def distance_and_velocity(x: np.ndarray, :param x: Array containing movement data. For example, created by ``simba.mixins.FeatureExtractionMixin.framewise_euclidean_distance``. If its a 2-dimensional array, then we assume its pixel coordinates. If it's a 1d array, we assume its frame-wise euclidean distances. :param fps: Frames per second of the data. :param pixels_per_mm: Conversion factor from pixels to millimeters. - :param Optional[bool] centimeters: If True, results are returned in centimeters and centimeters per second. Defaults to True. + :param Optional[bool] centimeters: If True, results are returned in centimeters and centimeters per second. Defaults to True. If false, then milimeters and millimeters per second. :return: A tuple containing total movement and mean velocity. :rtype: Tuple[float, float] :example: >>> x = np.random.randint(0, 100, (100,)) >>> sum_movement, avg_velocity = FeatureExtractionSupplemental.distance_and_velocity(x=x, fps=10, pixels_per_mm=10, centimeters=True) + + >>> x = np.random.randint(0, 100, (100, 2)) + >>> sum_movement, avg_velocity = FeatureExtractionSupplemental.distance_and_velocity(x=x, fps=10, pixels_per_mm=10, centimeters=True) """ check_valid_array(data=x, source=FeatureExtractionSupplemental.distance_and_velocity.__name__, accepted_ndims=(1, 2), accepted_dtypes=Formats.NUMERIC_DTYPES.value) diff --git a/simba/mixins/statistics_mixin.py b/simba/mixins/statistics_mixin.py index 639d8f5a8..9c58b1f89 100644 --- a/simba/mixins/statistics_mixin.py +++ b/simba/mixins/statistics_mixin.py @@ -3756,7 +3756,7 @@ def dunn_index(x: np.ndarray, y: np.ndarray, sample: Optional[float] = None) -> The Dunn Index is given by: .. math:: - D = \\frac{\min_{i \neq j} \{ \delta(C_i, C_j) \}}{\max_k \{ \Delta(C_k) \}} + D = \frac{\min_{i \neq j} \{ \delta(C_i, C_j) \}}{\max_k \{ \Delta(C_k) \}} where :math:`\delta(C_i, C_j)` is the distance between clusters :math:`C_i` and :math:`C_j`, and :math:`\Delta(C_k)` is the diameter of cluster :math:`C_k`. @@ -3983,8 +3983,13 @@ def xie_beni(x: np.ndarray, y: np.ndarray) -> float: A lower Xie-Beni index indicates better clustering quality, signifying well-separated and compact clusters. .. seealso:: - To compute Xie-Beni on the GPU, use :func:`~simba.mixins.statistics_mixin.Statistics.xie_beni` + To compute Xie-Beni on the GPU, use :func:`~simba.mixins.statistics_mixin.Statistics.xie_beni`. + Significant GPU savings detected at about 1m features, 25 clusters + .. math:: + \\text{XB} = \\frac{\\frac{1}{n} \\sum_{i=1}^{n} \\| x_i - c_{y_i} \\|^2}{\\min_{i \\neq j} \\| c_i - c_j \\|^2} + + where :math:`n` is the total number of points in the dataset, :math:`x_i` is the :math:`i`-th data point, :math:`c_{y_i}` is the centroid of the cluster to which :math:`x_i` belongs, and :math:`\\| \\cdot \\|` denotes the Euclidean norm. :param np.ndarray x: The dataset as a 2D NumPy array of shape (n_samples, n_features). :param np.ndarray y: Cluster labels for each data point as a 1D NumPy array of shape (n_samples,). @@ -4864,7 +4869,6 @@ def kumar_hassebrook_similarity(self, x: np.ndarray, y: np.ndarray) -> float: def wave_hedges_distance(self, x: np.ndarray, y: np.ndarray) -> float: """ - Computes the Wave-Hedges distance between two 1-dimensional arrays `x` and `y`. The Wave-Hedges distance is a measure of dissimilarity between arrays. .. note:: @@ -4879,6 +4883,9 @@ def wave_hedges_distance(self, x: np.ndarray, y: np.ndarray) -> float: >>> x = np.random.randint(0, 500, (1000,)) >>> y = np.random.randint(0, 500, (1000,)) >>> Statistics().wave_hedges_distance(x=x, y=y) + + :references: + .. [1] Hedges, T. S. (1976). An empirical modification to linear wave theory. Proceedings of the Institution of Civil Engineers, Part 2, 61(3), 575–579. https://doi.org/10.1680/iicep.1976.3408 """ check_valid_array(data=x, source=f'{Statistics.wave_hedges_distance.__name__} x', accepted_ndims=(1,), accepted_dtypes=Formats.NUMERIC_DTYPES.value) @@ -4907,6 +4914,11 @@ def gower_distance(x: np.ndarray, y: np.ndarray) -> np.ndarray: >>> x, y = np.random.randint(0, 500, (1000, 6000)), np.random.randint(0, 500, (1000, 6000)) >>> Statistics.gower_distance(x=x, y=y) + + :references: + .. [1] Gower, J. C. (1971). A general coefficient of similarity and some of its properties. Biometrics, 27(4), 857–874. https://doi.org/10.2307/2528823 + + """ check_valid_array(data=x, source=f'{Statistics.gower_distance.__name__} x', accepted_ndims=(1, 2), accepted_dtypes=Formats.NUMERIC_DTYPES.value) check_valid_array(data=y, source=f'{Statistics.gower_distance.__name__} y', accepted_ndims=(x.ndim,), accepted_shapes=(x.shape,), accepted_dtypes=Formats.NUMERIC_DTYPES.value) @@ -4952,6 +4964,10 @@ def normalized_google_distance(x: np.ndarray, y: np.ndarray) -> float: :example: >>> x, y = np.random.randint(0, 500, (1000,200)), np.random.randint(0, 500, (1000,200)) >>> Statistics.normalized_google_distance(x=y, y=x) + + :references: + .. [1] Cilibrasi, R., & Vitányi, P. (2007). Clustering by compression. IEEE Transactions on Information Theory, 51(4), 1523-1545. https://doi.org/10.1109/TIT.2005.862080 + """ check_valid_array(data=x, source=f'{Statistics.normalized_google_distance.__name__} x', accepted_ndims=(1, 2), accepted_dtypes=Formats.NUMERIC_DTYPES.value) check_valid_array(data=y, source=f'{Statistics.normalized_google_distance.__name__} y', accepted_ndims=(x.ndim,), accepted_shapes=(x.shape,), accepted_dtypes=Formats.NUMERIC_DTYPES.value) diff --git a/simba/roi_tools/ROI_define.py b/simba/roi_tools/ROI_define.py index ecd6d594c..7eaea3bcc 100644 --- a/simba/roi_tools/ROI_define.py +++ b/simba/roi_tools/ROI_define.py @@ -1,9 +1,12 @@ +from typing import Union import copy import os from tkinter import * import cv2 import pandas as pd +import warnings +warnings.simplefilter(action='ignore', category=pd.errors.PerformanceWarning) import PIL.Image from PIL import ImageTk @@ -39,17 +42,17 @@ class ROI_definitions(ConfigReader, PopUpMixin): video_path: str path to video file for which ROIs should be defined. - Notes - ---------- - `ROI tutorials `__. + .. note:: + `ROI tutorials `__. - Examples - ---------- + :example: >>> _ = ROI_definitions(config_path='MyProjectConfig', video_path='MyVideoPath') """ - def __init__(self, config_path: str, video_path: str): + def __init__(self, + config_path: Union[str, os.PathLike], + video_path: Union[str, os.PathLike]): check_file_exist_and_readable(file_path=config_path) check_file_exist_and_readable(file_path=video_path) @@ -67,9 +70,6 @@ def __init__(self, config_path: str, video_path: str): 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() @@ -79,6 +79,9 @@ def __init__(self, config_path: str, video_path: str): self.roi_root.wm_title("Region of Interest Settings") self.roi_root.protocol("WM_DELETE_WINDOW", self._terminate) + 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.shape_thickness_list = list(range(1, 26)) self.ear_tag_size_list = list(range(1, 26)) self.select_color = "red" @@ -113,7 +116,7 @@ def __init__(self, config_path: str, video_path: str): if len(self.video_ROIs) > 0: self.update_delete_ROI_menu() - self.master.mainloop() + #self.master.mainloop() def _terminate(self): self.Exit() diff --git a/simba/roi_tools/ROI_multiply.py b/simba/roi_tools/ROI_multiply.py index 8a6ded2e8..89a4dbac1 100644 --- a/simba/roi_tools/ROI_multiply.py +++ b/simba/roi_tools/ROI_multiply.py @@ -1,17 +1,19 @@ __author__ = "Simon Nilsson" __email__ = "sronilsson@gmail.com" - - -import glob +from typing import Union import os - +from copy import deepcopy import pandas as pd +import warnings +warnings.simplefilter(action='ignore', category=pd.errors.PerformanceWarning) + -from simba.utils.enums import ConfigKey, Keys, Paths -from simba.utils.errors import NoROIDataError +from simba.utils.enums import ConfigKey, Keys, Paths, Options +from simba.utils.errors import NoROIDataError, NotDirectoryError from simba.utils.printing import stdout_success -from simba.utils.read_write import get_fn_ext, read_config_file +from simba.utils.read_write import get_fn_ext, read_config_file, find_files_of_filetypes_in_directory +from simba.utils.checks import check_file_exist_and_readable, check_valid_dataframe def create_emty_df(shape_type): @@ -64,74 +66,84 @@ def create_emty_df(shape_type): return pd.DataFrame(columns=col_list) -def multiply_ROIs(config_path, filename): - _, CurrVidName, ext = get_fn_ext(filename) +def multiply_ROIs(config_path: Union[str, os.PathLike], + filename: Union[str, os.PathLike]) -> None: + + """ + Reproduce ROIs in one video to all other videos in SimBA project. + + :param Union[str, os.PathLike] config_path: Path to SimBA project config file. + :param Union[str, os.PathLike] filename: Path to video in project for which ROIs should be duplicated in the other videos in the project + :return: None + + :example: + >>> multiply_ROIs(config_path=r"C:\troubleshooting\mitra\project_folder\project_config.ini", filename=r"C:\troubleshooting\mitra\project_folder\videos\501_MA142_Gi_CNO_0514.mp4") + """ + + check_file_exist_and_readable(file_path=config_path) + check_file_exist_and_readable(file_path=filename) + _, video_name, video_ext = get_fn_ext(filename) config = read_config_file(config_path=config_path) - projectPath = config.get( - ConfigKey.GENERAL_SETTINGS.value, ConfigKey.PROJECT_PATH.value - ) - videoPath = os.path.join(projectPath, "videos") - ROIcoordinatesPath = os.path.join(projectPath, "logs", Paths.ROI_DEFINITIONS.value) - - if not os.path.isfile(ROIcoordinatesPath): - raise NoROIDataError( - msg="Cannot multiply ROI definitions: no ROI definitions exist in SimBA project" - ) - rectanglesInfo = pd.read_hdf(ROIcoordinatesPath, key=Keys.ROI_RECTANGLES.value) - circleInfo = pd.read_hdf(ROIcoordinatesPath, key=Keys.ROI_CIRCLES.value) - polygonInfo = pd.read_hdf(ROIcoordinatesPath, key=Keys.ROI_POLYGONS.value) - - try: - r_df = rectanglesInfo[rectanglesInfo["Video"] == CurrVidName] - except KeyError: - r_df = create_emty_df("rectangle") - - try: - c_df = circleInfo.loc[circleInfo["Video"] == str(CurrVidName)] - except KeyError: - c_df = create_emty_df("circle") - - try: - p_df = polygonInfo.loc[polygonInfo["Video"] == str(CurrVidName)] - except KeyError: - p_df = create_emty_df("polygon") - - if len(r_df) == 0 and len(c_df) == 0 and len(p_df) == 0: - print( - "Cannot replicate ROIs to all videos: no ROI records exist for " - + str(CurrVidName) - ) - - else: - videofilesFound = glob.glob(videoPath + "/*.mp4") + glob.glob( - videoPath + "/*.avi" - ) - duplicatedRec, duplicatedCirc, duplicatedPoly = ( - r_df.copy(), - c_df.copy(), - p_df.copy(), - ) - for vids in videofilesFound: - _, vid_name, ext = get_fn_ext(vids) - duplicatedRec["Video"], duplicatedCirc["Video"], duplicatedPoly["Video"] = ( - vid_name, - vid_name, - vid_name, - ) - r_df = r_df.append(duplicatedRec, ignore_index=True) - c_df = c_df.append(duplicatedCirc, ignore_index=True) - p_df = p_df.append(duplicatedPoly, ignore_index=True) - r_df = r_df.drop_duplicates(subset=["Video", "Name"], keep="first") - c_df = c_df.drop_duplicates(subset=["Video", "Name"], keep="first") - p_df = p_df.drop_duplicates(subset=["Video", "Name"], keep="first") - - store = pd.HDFStore(ROIcoordinatesPath, mode="w") - store["rectangles"] = r_df - store["circleDf"] = c_df - store["polygons"] = p_df - store.close() - stdout_success(msg=f"ROI(s) for {CurrVidName} applied to all videos") - print() - print( - 'Next, click on "draw" to modify ROI location(s) or click on "reset" to remove ROI drawing(s)' - ) + project_path = config.get(ConfigKey.GENERAL_SETTINGS.value, ConfigKey.PROJECT_PATH.value) + videos_dir = os.path.join(project_path, "videos") + roi_coordinates_path = os.path.join(project_path, "logs", Paths.ROI_DEFINITIONS.value) + if not os.path.isdir(videos_dir): + raise NotDirectoryError(msg=f'Could not find the videos directory in the SimBA project. SimBA expected a directory at location: {videos_dir}') + if not os.path.isfile(roi_coordinates_path): + raise NoROIDataError(msg=f"Cannot multiply ROI definitions: no ROI definitions exist in SimBA project. Could find find a file at expected location {roi_coordinates_path}", source=multiply_ROIs.__name__) + + with pd.HDFStore(roi_coordinates_path) as hdf: roi_data_keys = [x[1:] for x in hdf.keys()] + missing_keys = [x for x in roi_data_keys if x not in [Keys.ROI_RECTANGLES.value, Keys.ROI_CIRCLES.value, Keys.ROI_POLYGONS.value]] + if len(missing_keys) > 0: + raise NoROIDataError(msg=f'The ROI data file {roi_coordinates_path} is corrupted. Missing the following keys: {missing_keys}', source=multiply_ROIs.__name__) + + rectangles_df = pd.read_hdf(path_or_buf=roi_coordinates_path, key=Keys.ROI_RECTANGLES.value) + circles_df = pd.read_hdf(path_or_buf=roi_coordinates_path, key=Keys.ROI_CIRCLES.value) + polygon_df = pd.read_hdf(path_or_buf=roi_coordinates_path, key=Keys.ROI_POLYGONS.value) + + check_valid_dataframe(df=rectangles_df, source=f'{multiply_ROIs.__name__} rectangles_df', required_fields=['Video', 'Name']) + check_valid_dataframe(df=circles_df, source=f'{multiply_ROIs.__name__} circles_df', required_fields=['Video', 'Name']) + check_valid_dataframe(df=polygon_df, source=f'{multiply_ROIs.__name__} polygon_df', required_fields=['Video', 'Name']) + + videos_w_rectangles = list(rectangles_df["Video"].unique()) + videos_w_circles = list(circles_df["Video"].unique()) + videos_w_polygons = list(polygon_df["Video"].unique()) + videos_w_shapes = list(set(videos_w_rectangles + videos_w_circles + videos_w_polygons)) + if video_name not in videos_w_shapes: + raise NoROIDataError(msg=f"Cannot replicate ROIs to all other videos: no ROI records exist for {video_name}. Create ROIs for for video {video_name}", source=multiply_ROIs.__name__) + + other_video_file_paths = find_files_of_filetypes_in_directory(directory=videos_dir, extensions=Options.ALL_VIDEO_FORMAT_OPTIONS.value) + other_video_file_paths = [x for x in other_video_file_paths if x != filename] + if len(other_video_file_paths) == 0: + raise NoROIDataError(msg=f"Cannot replicate ROIs to other videos. No other videos exist in project {videos_dir} directory.", source=multiply_ROIs.__name__) + + r_df = [create_emty_df(Keys.ROI_RECTANGLES.value) if video_name not in videos_w_rectangles else rectangles_df[rectangles_df["Video"] == video_name]][0] + c_df = [create_emty_df(Keys.ROI_CIRCLES.value) if video_name not in videos_w_circles else circles_df[circles_df["Video"] == video_name]][0] + p_df = [create_emty_df(Keys.ROI_POLYGONS.value) if video_name not in videos_w_polygons else polygon_df[polygon_df["Video"] == video_name]][0] + + rectangle_results, circle_results, polygon_results = deepcopy(r_df), deepcopy(c_df), deepcopy(p_df) + for other_video_file_name in other_video_file_paths: + _, other_vid_name, ext = get_fn_ext(other_video_file_name) + if len(r_df) > 0: + x = deepcopy(r_df); x['Video'] = other_vid_name + rectangle_results = pd.concat([rectangle_results, x], axis=0) + if len(circle_results) > 0: + x = deepcopy(c_df); x['Video'] = other_vid_name + circle_results = pd.concat([circle_results, x], axis=0) + if len(polygon_results) > 0: + x = deepcopy(p_df); x['Video'] = other_vid_name + polygon_results = pd.concat([polygon_results, x], axis=0) + + rectangle_results = rectangle_results.drop_duplicates(subset=["Video", "Name"], keep="first") + circle_results = circle_results.drop_duplicates(subset=["Video", "Name"], keep="first") + polygon_results = polygon_results.drop_duplicates(subset=["Video", "Name"], keep="first") + + store = pd.HDFStore(roi_coordinates_path, mode="w") + store[Keys.ROI_RECTANGLES.value] = rectangle_results + store[Keys.ROI_CIRCLES.value] = circle_results + store[Keys.ROI_POLYGONS.value] = polygon_results + store.close() + stdout_success(msg=f"ROIs for {video_name} applied to a further {len(other_video_file_paths)} videos (Duplicated rectangles count: {len(r_df)}, circles: {len(c_df)}, polygons: {len(p_df)}).") + print('\nNext, click on "draw" to modify ROI location(s) or click on "reset" to remove ROI drawing(s)') + +#multiply_ROIs(config_path=r"C:\troubleshooting\mitra\project_folder\project_config.ini", filename=r"C:\troubleshooting\mitra\project_folder\videos\501_MA142_Gi_CNO_0514.mp4") \ No newline at end of file diff --git a/simba/utils/read_write.py b/simba/utils/read_write.py index c0c23457b..fb5dbee91 100644 --- a/simba/utils/read_write.py +++ b/simba/utils/read_write.py @@ -835,15 +835,10 @@ def find_files_of_filetypes_in_directory(directory: Union[str, os.PathLike], """ try: - all_files_in_folder = [ - f for f in next(os.walk(directory))[2] if not f[0] == "." - ] + all_files_in_folder = [f for f in next(os.walk(directory))[2] if not f[0] == "."] except StopIteration: if raise_warning: - raise NoFilesFoundError( - msg=f"No files found in the {directory} directory with accepted extensions {str(extensions)}", - source=find_files_of_filetypes_in_directory.__name__, - ) + raise NoFilesFoundError(msg=f"No files found in the {directory} directory with accepted extensions {str(extensions)}", source=find_files_of_filetypes_in_directory.__name__) else: all_files_in_folder = [] pass