From c7477fedf2787faba02f03aa87aeeab16de6e8f8 Mon Sep 17 00:00:00 2001 From: sronilsson Date: Fri, 5 Jul 2024 15:20:29 -0400 Subject: [PATCH] smooth --- docs/_static/img/AdvancedInterpolator.webp | Bin 0 -> 35294 bytes docs/_static/img/AdvancedSmoother.webp | Bin 0 -> 38202 bytes docs/simba.data_processors.rst | 17 +- .../data_processors/advanced_interpolator.py | 209 ++++++++++++++++++ simba/data_processors/advanced_smoothing.py | 201 +++++++++++++++++ 5 files changed, 425 insertions(+), 2 deletions(-) create mode 100644 docs/_static/img/AdvancedInterpolator.webp create mode 100644 docs/_static/img/AdvancedSmoother.webp create mode 100644 simba/data_processors/advanced_interpolator.py create mode 100644 simba/data_processors/advanced_smoothing.py diff --git a/docs/_static/img/AdvancedInterpolator.webp b/docs/_static/img/AdvancedInterpolator.webp new file mode 100644 index 0000000000000000000000000000000000000000..682637b2fffb24ece9b972ed13c23595b4a8dfd4 GIT binary patch literal 35294 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 literal 0 HcmV?d00001 diff --git a/docs/_static/img/AdvancedSmoother.webp b/docs/_static/img/AdvancedSmoother.webp new file mode 100644 index 0000000000000000000000000000000000000000..ccb31766fdd2adf710ef9f62f4c63067d893388a GIT binary patch literal 38202 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 literal 0 HcmV?d00001 diff --git a/docs/simba.data_processors.rst b/docs/simba.data_processors.rst index bbc4aaaaf..69f88e935 100644 --- a/docs/simba.data_processors.rst +++ b/docs/simba.data_processors.rst @@ -11,7 +11,7 @@ Aggregate classifier statistics calculator :show-inheritance: -Interpolate data +Interpolate pose-estimation data -------------------------------------------------- .. automodule:: simba.data_processors.interpolate @@ -19,13 +19,26 @@ Interpolate data :show-inheritance: -Smooth data +Advanced pose-estimation interpolation +-------------------------------------------------- + +.. automodule:: simba.data_processors.advanced_interpolator + :members: + :show-inheritance: + +Smooth pose-estimation data -------------------------------------------------- .. automodule:: simba.data_processors.smoothing :members: :show-inheritance: +Advanced smooth pose-estimation data +-------------------------------------------------- + +.. automodule:: simba.data_processors.advanced_smoothing + :members: + :show-inheritance: Directing-other-animals calculator ------------------------------------------------------------------- diff --git a/simba/data_processors/advanced_interpolator.py b/simba/data_processors/advanced_interpolator.py new file mode 100644 index 000000000..e5dcc2907 --- /dev/null +++ b/simba/data_processors/advanced_interpolator.py @@ -0,0 +1,209 @@ +__author__ = "Simon Nilsson" + +import os +from typing import Any, Dict, Optional, Union + +import numpy as np +import pandas as pd +pd.options.mode.chained_assignment = None + +try: + from typing import Literal +except ImportError: + from typing_extensions import Literal + +from simba.mixins.config_reader import ConfigReader +from simba.utils.checks import (check_valid_boolean, check_instance, check_that_column_exist, check_str, check_file_exist_and_readable) +from simba.utils.errors import DataHeaderError, InvalidInputError +from simba.utils.printing import SimbaTimer, stdout_success +from simba.utils.read_write import (find_files_of_filetypes_in_directory, get_fn_ext, read_df, write_df, copy_files_to_directory) + + +BODY_PART_TYPE = 'body-part' +ANIMAL_TYPE = 'animal' +NEAREST = 'nearest' +LINEAR = 'linear' +QUADRATIC = 'quadratic' + +class AdvancedInterpolator(ConfigReader): + """ + Interpolation method that allows different interpolation parameters for different animals or body-parts. + For example, interpolate some body-parts of animals using linear interpolation, and other body-parts of animals using nearest interpolation. + + .. image:: _static/img/AdvancedInterpolator.webp + :width: 600 + :align: center + + :parameter Union[str, os.PathLike] data_dir: path to folder containing pose-estimation data or a file with pose-estimation data. + :parameter Union[str, os.PathLike] config_path: path to SimBA project config file in Configparser format. + :parameter Literal["animal", "body-part"] type: Type of interpolation: animal or body-part. + :parameter Dict settings: Interpolation rules for each animal or each animal body-part. See examples. + :parameter bool initial_import_multi_index: If True, the incoming data is multi-index columns dataframes. Use of input data is the ``project_folder/csv/input_csv`` directory. Default: False. + :parameter bool overwrite: If True, overwrites the input data. If False, then saves input data in datetime-stamped sub-directory. + + :examples: + >>> interpolator = AdvancedInterpolator(data_dir='/Users/simon/Desktop/envs/troubleshooting/two_black_animals_14bp/project_folder/csv/input_csv', + >>> config_path='/Users/simon/Desktop/envs/troubleshooting/two_black_animals_14bp/project_folder/project_config.ini', + >>> type='animal', + >>> settings={'Animal_1': 'linear', 'Animal_2': 'quadratic'}, + >>> multi_index_data=True) + >>> interpolator.run() + >>> interpolator = AdvancedInterpolator(data_dir='/Users/simon/Desktop/envs/troubleshooting/two_black_animals_14bp/project_folder/csv/input_csv', + >>> config_path='/Users/simon/Desktop/envs/troubleshooting/two_black_animals_14bp/project_folder/project_config.ini', + >>> type='animal', + >>> settings={'Simon': {'Ear_left_1': 'linear', + >>> 'Ear_right_1': 'linear', + >>> 'Nose_1': 'quadratic', + >>> 'Lat_left_1': 'quadratic', + >>> 'Lat_right_1': 'quadratic', + >>> 'Center_1': 'nearest', + >>> 'Tail_base_1': 'nearest'}, + >>> 'JJ': {'Ear_left_2': 'nearest', + >>> 'Ear_right_2': 'nearest', + >>> 'Nose_2': 'quadratic', + >>> 'Lat_left_2': 'quadratic', + >>> 'Lat_right_2': 'quadratic', + >>> 'Center_2': 'linear', + >>> 'Tail_base_2': 'linear'}}, + >>> multi_index_data=True) + >>> interpolator.run() + """ + + def __init__(self, + data_path: Union[str, os.PathLike], + config_path: Union[str, os.PathLike], + settings: Dict[str, Any], + type: Optional[Literal["animal", "body-part"]] = 'body-part', + verbose: Optional[bool] = True, + multi_index_data: Optional[bool] = False, + overwrite: Optional[bool] = True): + + ConfigReader.__init__(self, config_path=config_path, read_video_info=False) + check_str(name=f'{self.__class__.__name__} type', value=type, options=["animal", "body-part"], raise_error=True) + if os.path.isfile(data_path): + check_file_exist_and_readable(file_path=data_path) + self.file_paths = [data_path] + self.input_dir = os.path.dirname(data_path) + self.cpy_dir = os.path.join(os.path.dirname(data_path), f"Pre_Advanced_Interpolation_{self.datetime}") + elif os.path.isdir(data_path): + self.file_paths = find_files_of_filetypes_in_directory(directory=data_path, extensions=[f".{self.file_type}"], raise_warning=False,raise_error=True) + self.cpy_dir = os.path.join(data_path, f"Pre_Advanced_Interpolation_{self.datetime}") + self.input_dir = data_path + else: + raise InvalidInputError(msg=f'{data_path} is not a valid file path or file directory', source=self.__class__.__name__) + check_valid_boolean(value=[multi_index_data, overwrite], source=self.__class__.__name__, raise_error=True) + check_instance(source=self.__class__.__name__, instance=settings, accepted_types=(dict,)) + for animal, animal_data in settings.items(): + if type == BODY_PART_TYPE: + check_instance(source=self.__class__.__name__, instance=animal_data, accepted_types=(dict,)) + for bp_name, bp_data in animal_data.items(): + check_str(name='method', value=bp_name, options=self.project_bps) + check_str(name='method', value=bp_data, options=[LINEAR, NEAREST, QUADRATIC]) + else: + check_str(name='method', value=animal_data, options=[LINEAR, NEAREST, QUADRATIC]) + self.settings, self.type, self.multi_index_data, self.verbose = settings, type, multi_index_data, verbose + if type == ANIMAL_TYPE: + self.__transpose_settings() + + self.overwrite = overwrite + if not overwrite and not os.path.isdir(self.cpy_dir): os.makedirs(self.cpy_dir) + + def __transpose_settings(self): + """Helper to transpose settings dict if interpolating per animal, so the same method can be used for both animal and body-part interpolation""" + transposed_settings = {} + for animal_name, body_part_data in self.animal_bp_dict.items(): + transposed_settings[animal_name] = {} + for animal_body_part in body_part_data["X_bps"]: + transposed_settings[animal_name][animal_body_part[:-2]] = self.settings[animal_name] + self.settings = transposed_settings + + def __insert_multi_index(self, df: pd.DataFrame) -> pd.DataFrame: + multi_idx_header = [] + for i in range(len(df.columns)): + multi_idx_header.append(("IMPORTED_POSE", "IMPORTED_POSE", list(df.columns)[i])) + df.columns = pd.MultiIndex.from_tuples(multi_idx_header) + return df + + def run(self): + for file_cnt, file_path in enumerate(self.file_paths): + video_timer = SimbaTimer(start=True) + df = read_df(file_path=file_path, file_type=self.file_type, check_multiindex=self.multi_index_data).fillna(0).reset_index(drop=True) + _, video_name, _ = get_fn_ext(filepath=file_path) + if len(df.columns) != len(self.bp_col_names): + raise DataHeaderError(msg=f"The SimBA project suggest the data should have {len(self.bp_col_names)} columns, but the input data has {len(df.columns)} columns", source=self.__class__.__name__) + df.columns = self.bp_headers + df[df < 0] = 0 + for animal_name, animal_body_parts in self.settings.items(): + for bp, interpolation_setting in animal_body_parts.items(): + check_that_column_exist(df=df, column_name=[f"{bp}_x", f"{bp}_y"], file_name=file_path) + df[[f"{bp}_x", f"{bp}_y"]] = df[[f"{bp}_x", f"{bp}_y"]].astype(int) + idx = df.loc[(df[f"{bp}_x"] <= 0.0) & (df[f"{bp}_y"] <= 0.0)].index.tolist() + if self.verbose: print(f"Interpolating {len(idx)} {bp} body-parts in video {video_name}...") + df.loc[idx, [f"{bp}_x", f"{bp}_y"]] = np.nan + df[[f"{bp}_x", f"{bp}_y"]] = (df[[f"{bp}_x", f"{bp}_y"]].interpolate(method=interpolation_setting, axis=0).ffill().bfill().astype(int)) + df[[f"{bp}_x", f"{bp}_y"]][df[[f"{bp}_x", f"{bp}_y"]] < 0] = 0 + if self.multi_index_data: + df = self.__insert_multi_index(df=df) + if not self.overwrite: + copy_files_to_directory(file_paths=[file_path], dir=self.cpy_dir, verbose=False) + write_df(df=df, file_type=self.file_type, save_path=file_path, multi_idx_header=self.multi_index_data) + video_timer.stop_timer() + print(f'Video {video_name} complete. Elapsed time {video_timer.elapsed_time_str}s') + self.timer.stop_timer() + if self.overwrite: + msg = f"Advanced interpolation complete. Data saved in {self.input_dir}." + else: + msg = f"Advanced interpolation complete. Data saved in {self.input_dir}. Original data saved in {self.cpy_dir}." + stdout_success(msg=msg, elapsed_time=self.timer.elapsed_time_str, source=self.__class__.__name__) + +# SMOOTHING_SETTINGS = {'Simon': {'Ear_left_1': {'method': 'Savitzky Golay', 'time_window': 3500}, +# 'Ear_right_1': {'method': 'Gaussian', 'time_window': 500}, +# 'Nose_1': {'method': 'Savitzky Golay', 'time_window': 2000}, +# 'Lat_left_1': {'method': 'Savitzky Golay', 'time_window': 2000}, +# 'Lat_right_1': {'method': 'Gaussian', 'time_window': 2000}, +# 'Center_1': {'method': 'Savitzky Golay', 'time_window': 2000}, +# 'Tail_base_1': {'method': 'Gaussian', 'time_window': 500}}, +# 'JJ': {'Ear_left_2': {'method': 'Savitzky Golay', 'time_window': 2000}, +# 'Ear_right_2': {'method': 'Savitzky Golay', 'time_window': 500}, +# 'Nose_2': {'method': 'Gaussian', 'time_window': 3500}, +# 'Lat_left_2': {'method': 'Savitzky Golay', 'time_window': 500}, +# 'Lat_right_2': {'method': 'Gaussian', 'time_window': 3500}, +# 'Center_2': {'method': 'Gaussian', 'time_window': 2000}, +# 'Tail_base_2': {'method': 'Savitzky Golay', 'time_window': 3500}}} +# +# +# INTERPOLATION_SETTINGS = {'Simon': {'Ear_left_1': 'linear', +# 'Ear_right_1': 'linear', +# 'Nose_1': 'quadratic', +# 'Lat_left_1': 'quadratic', +# 'Lat_right_1': 'quadratic', +# 'Center_1': 'nearest', +# 'Tail_base_1': 'nearest'}, +# 'JJ': {'Ear_left_2': 'nearest', +# 'Ear_right_2': 'nearest', +# 'Nose_2': 'quadratic', +# 'Lat_left_2': 'quadratic', +# 'Lat_right_2': 'quadratic', +# 'Center_2': 'linear', +# 'Tail_base_2': 'linear'}} +# +# +# INTERPOLATION_SETTINGS = {'Animal_1': 'linear', 'Animal_2': 'quadratic'} +# +# advanced_interpolator = AdvancedInterpolator(config_path='/Users/simon/Desktop/envs/simba/troubleshooting/two_black_animals_14bp/project_folder/project_config.ini', +# data_path='/Users/simon/Desktop/envs/simba/troubleshooting/two_black_animals_14bp/project_folder/new_data', +# settings=INTERPOLATION_SETTINGS, type='animal', multi_index_data=True, overwrite=False) +# +# advanced_interpolator.run() + +# for animal, animal_data in settings.items(): +# check_instance(source=self.__class__.__name__, instance=animal_data, accepted_types=(dict,)) +# if type == BODY_PART_TYPE: +# for bp_name, bp_data in animal_data.items(): +# check_if_keys_exist_in_dict(data=bp_data, key=['method', 'time_window']) +# check_str(name='method', value=bp_data['method'], options=[GAUSSIAN, SAVITZKY_GOLAY]) +# check_int(name='time_window', value=bp_data['time_window'], min_value=1) +# else: +# check_if_keys_exist_in_dict(data=animal_data, key=['method', 'time_window']) +# check_str(name='method', value=animal_data['method'], options=[GAUSSIAN, SAVITZKY_GOLAY]) +# check_int(name='time_window', value=animal_data['time_window'], min_value=1) \ No newline at end of file diff --git a/simba/data_processors/advanced_smoothing.py b/simba/data_processors/advanced_smoothing.py new file mode 100644 index 000000000..087a7d5b6 --- /dev/null +++ b/simba/data_processors/advanced_smoothing.py @@ -0,0 +1,201 @@ +__author__ = "Simon Nilsson" + +import os +from copy import deepcopy +from typing import Any, Dict, Optional, Union +import pandas as pd + +try: + from typing import Literal +except ImportError: + from typing_extensions import Literal + +from simba.mixins.config_reader import ConfigReader +from simba.utils.checks import (check_valid_boolean, check_instance, check_str, check_int, check_that_column_exist, check_file_exist_and_readable) +from simba.utils.enums import TagNames +from simba.utils.errors import DataHeaderError, NoFilesFoundError, InvalidInputError +from simba.utils.printing import SimbaTimer, log_event, stdout_success +from simba.utils.read_write import (find_files_of_filetypes_in_directory, + find_video_of_file, get_fn_ext, + get_video_meta_data, read_df, write_df, copy_files_to_directory) +from simba.utils.data import savgol_smoother, df_smoother + +BODY_PART_TYPE = 'body-part' +ANIMAL_TYPE = 'animal' +GAUSSIAN = 'gaussian' +SAVITZKY_GOLAY = 'savitzky_golay' +TIME_WINDOW = 'time_window' + +class AdvancedSmoother(ConfigReader): + """ + Smoothing method that allows different smoothing parameters for different animals or body-parts. + For example, smooth some body-parts of animals using Savitzky-Golay smoothing, and other body-parts of animals using Gaussian smoothing. + + .. image:: _static/img/AdvancedSmoother.webp + :width: 600 + :align: center + + :parameter str data_dir: path to pose-estimation data in CSV or parquet format + :parameter str config_path: path to SimBA project config file in Configparser format. + :parameter Literal type: Level of smoothing: animal or body-part. + :parameter Dict settings: Smoothing rules for each animal or each animal body-part. + :parameter bool initial_import_multi_index: If True, the incoming data is multi-index columns dataframes. Use of input data is the ``project_folder/csv/input_csv`` directory. Default: False. + :parameter bool overwrite: If True, overwrites the input data. If False, then saves a copy input data in datetime-stamped sub-directory. + :parameter Optional[verbose] bool: If True, prints the progress. Default: True. + + :examples: + >>> smoother = AdvancedSmoother(data_dir='/Users/simon/Desktop/envs/troubleshooting/two_black_animals_14bp/project_folder/csv/input_csv', + >>> config_path='/Users/simon/Desktop/envs/troubleshooting/two_black_animals_14bp/project_folder/project_config.ini', + >>> type='animal', + >>> settings={'Simon': {'method': 'Savitzky Golay', 'time_window': 200}, + >>> 'JJ': {'method': 'Savitzky Golay', 'time_window': 200}}, + >>> initial_import_multi_index=True, + >>> overwrite=False) + >>> smoother.run() + >>> SMOOTHING_SETTINGS = {'Simon': {'Ear_left_1': {'method': 'savitzky_golay', 'time_window': 3500}, + >>> 'Ear_right_1': {'method': 'gaussian', 'time_window': 500}, + >>> 'Nose_1': {'method': 'savitzky_golay', 'time_window': 2000}, + >>> 'Lat_left_1': {'method': 'savitzky_golay', 'time_window': 2000}, + >>> 'Lat_right_1': {'method': 'gaussian', 'time_window': 2000}, + >>> 'Center_1': {'method': 'savitzky_golay', 'time_window': 2000}, + >>> 'Tail_base_1': {'method': 'gaussian', 'time_window': 500}}, + >>> 'JJ': {'Ear_left_2': {'method': 'savitzky_golay', 'time_window': 2000}, + >>> 'Ear_right_2': {'method': 'savitzky_golay', 'time_window': 500}, + >>> 'Nose_2': {'method': 'gaussian', 'time_window': 3500}, + >>> 'Lat_left_2': {'method': 'savitzky_golay', 'time_window': 500}, + >>> 'Lat_right_2': {'method': 'gaussian', 'time_window': 3500}, + >>> 'Center_2': {'method': 'gaussian', 'time_window': 2000}, + >>> 'Tail_base_2': {'method': 'savitzky_golay', 'time_window': 3500}}} + >>> advanced_smoother = AdvancedSmoother(config_path='/Users/simon/Desktop/envs/simba/troubleshooting/two_black_animals_14bp/project_folder/project_config.ini', + >>> data_path='/Users/simon/Desktop/envs/simba/troubleshooting/two_black_animals_14bp/project_folder/new_data', + >>> settings=SMOOTHING_SETTINGS, type='body-part', multi_index_data=True, overwrite=False) + >>> advanced_smoother.run() + """ + + def __init__(self, + data_path: Union[str, os.PathLike], + config_path: Union[str, os.PathLike], + settings: Dict[str, Any], + type: Optional[Literal["animal", "body-part"]] = 'body-part', + verbose: Optional[bool] = True, + multi_index_data: Optional[bool] = False, + overwrite: Optional[bool] = True): + + ConfigReader.__init__(self, config_path=config_path, read_video_info=False) + log_event(logger_name=str(self.__class__.__name__), log_type=TagNames.CLASS_INIT.value, msg=f"data_dir: {data_path}, type: {type}, settings: {settings}, initial_import_multi_index: {multi_index_data}, overwrite: {overwrite}",) + ConfigReader.__init__(self, config_path=config_path, read_video_info=False) + check_str(name=f'{self.__class__.__name__} type', value=type, options=[ANIMAL_TYPE, BODY_PART_TYPE], raise_error=True) + if os.path.isfile(data_path): + check_file_exist_and_readable(file_path=data_path) + self.file_paths = [data_path] + self.input_dir = os.path.dirname(data_path) + self.cpy_dir = os.path.join(os.path.dirname(data_path), f"Pre_Advanced_Interpolation_{self.datetime}") + elif os.path.isdir(data_path): + self.file_paths = find_files_of_filetypes_in_directory(directory=data_path, extensions=[f".{self.file_type}"], raise_warning=False,raise_error=True) + self.cpy_dir = os.path.join(data_path, f"Pre_Advanced_Interpolation_{self.datetime}") + self.input_dir = data_path + else: + raise InvalidInputError(msg=f'{data_path} is not a valid file path or file directory', source=self.__class__.__name__) + check_valid_boolean(value=[multi_index_data, overwrite], source=self.__class__.__name__, raise_error=True) + check_instance(source=self.__class__.__name__, instance=settings, accepted_types=(dict,)) + for animal, animal_data in settings.items(): + if type == BODY_PART_TYPE: + check_instance(source=self.__class__.__name__, instance=animal_data, accepted_types=(dict,)) + for bp_name, bp_data in animal_data.items(): + check_str(name='body_part', value=bp_name, options=self.project_bps) + check_str(name='method', value=bp_data['method'], options=[SAVITZKY_GOLAY, GAUSSIAN]) + check_int(name='method', value=bp_data[TIME_WINDOW], min_value=1) + else: + check_str(name='method', value=animal_data['method'], options=[SAVITZKY_GOLAY, GAUSSIAN]) + check_int(name='method', value=animal_data[TIME_WINDOW], min_value=1) + self.settings, self.type, self.multi_index_data, self.verbose = settings, type, multi_index_data, verbose + if type == ANIMAL_TYPE: + self.__transpose_settings() + + self.overwrite = overwrite + if not overwrite and not os.path.isdir(self.cpy_dir): os.makedirs(self.cpy_dir) + + def __transpose_settings(self): + """Helper to transpose settings dict if interpolating per animal, so the same method can be used for both animal and body-part interpolation""" + transposed_settings = {} + for animal_name, body_part_data in self.animal_bp_dict.items(): + transposed_settings[animal_name] = {} + for animal_body_part in body_part_data["X_bps"]: + transposed_settings[animal_name][animal_body_part[:-2]] = self.settings[animal_name] + self.settings = transposed_settings + + def __insert_multi_index(self, df: pd.DataFrame) -> pd.DataFrame: + multi_idx_header = [] + for i in range(len(df.columns)): + multi_idx_header.append(("IMPORTED_POSE", "IMPORTED_POSE", list(df.columns)[i])) + df.columns = pd.MultiIndex.from_tuples(multi_idx_header) + return df + + def run(self): + for file_cnt, file_path in enumerate(self.file_paths): + video_timer = SimbaTimer(start=True) + _, video_name, _ = get_fn_ext(filepath=file_path) + df = read_df(file_path=file_path, file_type=self.file_type, check_multiindex=self.multi_index_data).fillna(0).reset_index(drop=True) + if self.verbose: print(f"Smoothing data in video {video_name} ({file_cnt+1}/{len(self.file_paths)})...") + if len(df.columns) != len(self.bp_col_names): + raise DataHeaderError(msg=f"The SimBA project suggest the data should have {len(self.bp_col_names)} columns, but the input data has {len(df.columns)} columns", source=self.__class__.__name__) + df.columns = self.bp_headers + df[df < 0] = 0 + video_path = find_video_of_file(video_dir=self.video_dir, filename=video_name, warning=False, raise_error=False) + if video_path is None: + try: + video_meta_data = {} + self.video_info_df = self.read_video_info_csv(file_path=self.video_info_path) + _, _, fps = self.read_video_info(video_name=video_name) + video_meta_data["fps"] = fps + except: + raise NoFilesFoundError(msg=f"No video for file {video_name} found in SimBA project. Import the video before doing smoothing. To perform smoothing, SimBA needs the video fps from the video itself OR the logs/video_info.csv file in order to read the video FPS.", source=self.__class__.__name__) + else: + video_meta_data = get_video_meta_data(video_path=video_path) + out_df = deepcopy(df) + for animal_name, animal_body_parts in self.settings.items(): + for bp, smoothing_setting in animal_body_parts.items(): + if self.verbose: print(f"Smoothing body-part {bp} in video {video_name} using method {smoothing_setting['method']} (time window: {smoothing_setting[TIME_WINDOW]}ms)...") + check_that_column_exist(df=df, column_name=[f"{bp}_x", f"{bp}_y"], file_name=file_path) + bp_df = df[[f"{bp}_x", f"{bp}_y"]] + if smoothing_setting['method'] == SAVITZKY_GOLAY: + bp_df = savgol_smoother(data=bp_df, fps=video_meta_data['fps'], time_window=smoothing_setting[TIME_WINDOW], source=f'{file_path} {bp}') + else: + bp_df = df_smoother(data=bp_df, fps=video_meta_data['fps'], time_window=smoothing_setting[TIME_WINDOW], source=f'{file_path} {bp}') + out_df[[f"{bp}_x", f"{bp}_y"]] = bp_df + if self.multi_index_data: + out_df = self.__insert_multi_index(df=out_df) + if not self.overwrite: + copy_files_to_directory(file_paths=[file_path], dir=self.cpy_dir, verbose=False) + write_df(df=out_df, file_type=self.file_type, save_path=file_path, multi_idx_header=self.multi_index_data) + video_timer.stop_timer() + print(f'Smoothing video {video_name} complete ({file_cnt+1}/{len(self.file_paths)}). Elapsed time {video_timer.elapsed_time_str}s') + self.timer.stop_timer() + if self.overwrite: + msg = f"Advanced smoothing complete. Data saved in {self.input_dir}." + else: + msg = f"Advanced smoothing complete. Data saved in {self.input_dir}. Original data saved in {self.cpy_dir}." + stdout_success(msg=msg, elapsed_time=self.timer.elapsed_time_str, source=self.__class__.__name__) + +# SMOOTHING_SETTINGS = {'Simon': {'Ear_left_1': {'method': 'savitzky_golay', 'time_window': 3500}, +# 'Ear_right_1': {'method': 'gaussian', 'time_window': 500}, +# 'Nose_1': {'method': 'savitzky_golay', 'time_window': 2000}, +# 'Lat_left_1': {'method': 'savitzky_golay', 'time_window': 2000}, +# 'Lat_right_1': {'method': 'gaussian', 'time_window': 2000}, +# 'Center_1': {'method': 'savitzky_golay', 'time_window': 2000}, +# 'Tail_base_1': {'method': 'gaussian', 'time_window': 500}}, +# 'JJ': {'Ear_left_2': {'method': 'savitzky_golay', 'time_window': 2000}, +# 'Ear_right_2': {'method': 'savitzky_golay', 'time_window': 500}, +# 'Nose_2': {'method': 'gaussian', 'time_window': 3500}, +# 'Lat_left_2': {'method': 'savitzky_golay', 'time_window': 500}, +# 'Lat_right_2': {'method': 'gaussian', 'time_window': 3500}, +# 'Center_2': {'method': 'gaussian', 'time_window': 2000}, +# 'Tail_base_2': {'method': 'savitzky_golay', 'time_window': 3500}}} + + +#SMOOTHING_SETTINGS = {'Animal_1': {'method': 'savitzky_golay', 'time_window': 3500}, 'Animal_2': {'method': 'savitzky_golay', 'time_window': 3500}} +# advanced_smoother = AdvancedSmoother(config_path='/Users/simon/Desktop/envs/simba/troubleshooting/two_black_animals_14bp/project_folder/project_config.ini', +# data_path='/Users/simon/Desktop/envs/simba/troubleshooting/two_black_animals_14bp/project_folder/new_data', +# settings=SMOOTHING_SETTINGS, type='body-part', multi_index_data=True, overwrite=False) +# +# advanced_smoother.run()