__author__ = "Simon Nilsson; sronilsson@gmail.com"
import os
from typing import List, Optional, Union
import numpy as np
import pandas as pd
try:
from typing import Literal
except:
from typing_extensions import Literal
from itertools import combinations
from simba.feature_extractors.perimeter_jit import jitted_hull
from simba.mixins.config_reader import ConfigReader
from simba.mixins.feature_extraction_mixin import FeatureExtractionMixin
from simba.mixins.feature_extraction_supplement_mixin import \
FeatureExtractionSupplemental
from simba.mixins.train_model_mixin import TrainModelMixin
from simba.roi_tools.roi_utils import get_roi_dict_from_dfs
from simba.utils.checks import (
check_all_file_names_are_represented_in_video_log,
check_file_exist_and_readable, check_if_dir_exists,
check_same_files_exist_in_all_directories, check_valid_boolean,
check_valid_dataframe, check_valid_lst, check_video_has_rois)
from simba.utils.enums import ROI_SETTINGS, Formats
from simba.utils.errors import (DuplicationError, InvalidInputError,
NoFilesFoundError, NoROIDataError)
from simba.utils.printing import SimbaTimer, stdout_success
from simba.utils.read_write import (copy_files_in_directory,
find_files_of_filetypes_in_directory,
get_fn_ext, read_df, remove_a_folder,
remove_multiple_folders, write_df)
SHAPE_TYPE = "Shape_type"
TWO_POINT_BP_DISTANCES = 'TWO-POINT BODY-PART DISTANCES (MM)'
WITHIN_ANIMAL_THREE_POINT_ANGLES = 'WITHIN-ANIMAL THREE-POINT BODY-PART ANGLES (DEGREES)'
WITHIN_ANIMAL_THREE_POINT_HULL = "WITHIN-ANIMAL THREE-POINT CONVEX HULL PERIMETERS (MM)"
WITHIN_ANIMAL_FOUR_POINT_HULL = "WITHIN-ANIMAL FOUR-POINT CONVEX HULL PERIMETERS (MM)"
ANIMAL_CONVEX_HULL_PERIMETER = 'ENTIRE ANIMAL CONVEX HULL PERIMETERS (MM)'
ANIMAL_CONVEX_HULL_AREA = "ENTIRE ANIMAL CONVEX HULL AREA (MM2)"
FRAME_BP_MOVEMENT = "FRAME-BY-FRAME BODY-PART MOVEMENTS (MM)"
FRAME_BP_TO_ROI_CENTER = "FRAME-BY-FRAME BODY-PART DISTANCES TO ROI CENTERS (MM)"
FRAME_BP_INSIDE_ROI = "FRAME-BY-FRAME BODY-PARTS INSIDE ROIS (BOOLEAN)"
ARENA_EDGE = "BODY-PART DISTANCES TO VIDEO FRAME EDGE (MM)"
FEATURE_FAMILIES = [TWO_POINT_BP_DISTANCES,
WITHIN_ANIMAL_THREE_POINT_ANGLES,
WITHIN_ANIMAL_THREE_POINT_HULL,
WITHIN_ANIMAL_FOUR_POINT_HULL,
ANIMAL_CONVEX_HULL_PERIMETER,
ANIMAL_CONVEX_HULL_AREA,
FRAME_BP_MOVEMENT,
FRAME_BP_TO_ROI_CENTER,
FRAME_BP_INSIDE_ROI,
ARENA_EDGE]
[docs]class FeatureSubsetsCalculator(ConfigReader, TrainModelMixin):
"""
Computes a subset of features from pose for non-ML downstream purposes.
E.g., returns the size of animal convex hull in each frame.
:param str config_path: path to SimBA project config file in Configparser format
:param str save_dir: directory where to store results.
:param List[str] feature_family: List of feature subtype to calculate. E.g., ['TWO-POINT BODY-PART DISTANCES (MM)"].
:param bool file_checks: If true, checks that the files which the data is appended too contains the anticipated number of rows and no duplicate columns after appending. Default False.
:param Optional[Union[str, os.PathLike]] save_dir: Directory where to save the data. If None, then the data is only appended.
:param Optional[Union[str, os.PathLike]] data_dir: Directory of pose-estimation data to compute feature subsets for. If None, then the `/project_folder/csv/outlier_corrected_movement_locations` directory.
:param bool append_to_features_extracted: If True, appends the data to the file sin the `features_extracted` directory. Default: False.
:param bool append_to_targets_inserted: If True, appends the data to the file sin the `targets_inserted` directory. Default: False.
.. note::
`Tutorial <https://github.com/sgoldenlab/simba/blob/master/docs/feature_subsets.md>_`
.. image:: _static/img/feature_subsets.png
:alt: Feature subsets
:width: 400
:align: center
:example:
>>> test = FeatureSubsetsCalculator(config_path=r"C:/troubleshooting/mitra/project_folder/project_config.ini",
>>> feature_families=[FRAME_BP_MOVEMENT, WITHIN_ANIMAL_THREE_POINT_ANGLES],
>>> append_to_features_extracted=False,
>>> file_checks=False,
>>> append_to_targets_inserted=False,
>>> save_dir=r"C:/troubleshooting/mitra/project_folder/csv/new_features")
>>> test.run()
"""
def __init__(self,
config_path: Union[str, os.PathLike],
feature_families: List[str],
file_checks: bool = False,
save_dir: Optional[Union[str, os.PathLike]] = None,
data_dir: Optional[Union[str, os.PathLike]] = None,
append_to_features_extracted: bool = False,
append_to_targets_inserted: bool = False):
check_file_exist_and_readable(file_path=config_path)
ConfigReader.__init__(self, config_path=config_path)
TrainModelMixin.__init__(self)
check_valid_boolean(value=file_checks, source=f'{self.__class__.__name__} file_checks', raise_error=True)
check_valid_boolean(value=append_to_features_extracted, source=f'{self.__class__.__name__} append_to_features_extracted', raise_error=True)
check_valid_boolean(value=append_to_targets_inserted, source=f'{self.__class__.__name__} append_to_targets_inserted', raise_error=True)
if save_dir is not None:
check_if_dir_exists(in_dir=save_dir)
check_valid_lst(data=feature_families, source=f'{self.__class__.__name__} feature_families', valid_dtypes=(str,), valid_values=FEATURE_FAMILIES, min_len=1, raise_error=True)
self.file_checks, self.feature_families, self.save_dir = file_checks, feature_families, save_dir
self.append_to_features_extracted = append_to_features_extracted
self.append_to_targets_inserted = append_to_targets_inserted
if data_dir is None:
self.data_dir = self.outlier_corrected_dir
self.data_paths = self.outlier_corrected_paths
else:
self.data_dir = data_dir
check_if_dir_exists(in_dir=data_dir)
self.data_paths = find_files_of_filetypes_in_directory(directory=data_dir, extensions=['csv'], raise_error=True)
if self.append_to_features_extracted:
if not check_same_files_exist_in_all_directories(dirs=[self.data_dir, self.features_dir], file_type=self.file_type, raise_error=False):
raise NoFilesFoundError(msg=f'Cannot append feature subset to files in {self.features_dir} directory: To proceed, the files in the {self.features_dir} and the {self.data_dir} directories has to contain the same number of files with the same filenames.', source=self.__class__.__name__)
if self.append_to_targets_inserted:
if not check_same_files_exist_in_all_directories(dirs=[self.data_dir, self.targets_folder], file_type=self.file_type, raise_error=False):
raise NoFilesFoundError(msg=f'Cannot append feature subset to files in {self.targets_folder} directory: To proceed, the files in the {self.targets_folder} and the {self.data_dir} directories has to contain the same number of files with the same filenames.', source=self.__class__.__name__)
self.video_names = [get_fn_ext(filepath=x)[1] for x in self.data_paths]
for file_path in self.data_paths: check_file_exist_and_readable(file_path=file_path)
self.temp_dir = os.path.join(self.data_dir, f"temp_data_{self.datetime}")
if not os.path.isdir(self.temp_dir):
os.makedirs(self.temp_dir)
if (FRAME_BP_TO_ROI_CENTER in feature_families) or (FRAME_BP_INSIDE_ROI in feature_families):
if not os.path.isfile(self.roi_coordinates_path):
raise NoROIDataError(msg=f'Cannot compute ROI features: The SimBA project has no ROI data defined.')
self.read_roi_data(); check_video_has_rois(roi_dict=self.roi_dict)
self.roi_dict = get_roi_dict_from_dfs(rectangle_df=self.rectangles_df, circle_df=self.circles_df, polygon_df=self.polygon_df, video_name_nesting=True)
missing_roi_videos = [x for x in self.video_names if x not in list(self.roi_dict.keys())]
if len(missing_roi_videos) > 0:
raise NoROIDataError(msg=f'Cannot compute ROI features: The following videos have no ROIs: {missing_roi_videos}')
self.__get_bp_combinations()
def __get_bp_combinations(self):
ordered_bps = sorted(self.project_bps, key=str.lower)
self.two_point_combs = np.array(list(combinations(ordered_bps, 2)))
self.within_animal_three_point_combs = {}
self.within_animal_four_point_combs = {}
self.animal_bps = {}
for animal, animal_data in self.animal_bp_dict.items():
animal_bps = [x[:-2] for x in animal_data["X_bps"]]
self.animal_bps[animal] = animal_bps
self.within_animal_three_point_combs[animal] = np.array(list(combinations(animal_bps, 3)))
self.within_animal_four_point_combs[animal] = np.array(list(combinations(animal_bps, 4)))
def _get_two_point_bp_distances(self):
for cnt, c in enumerate(self.two_point_combs):
x1, y1, x2, y2 = list(sum([(f"{x}_x", f"{y}_y") for (x, y) in zip(c, c)], ()))
bp1 = self.data_df[[x1, y1]].values
bp2 = self.data_df[[x2, y2]].values
self.results[f"Distance (mm) {c[0]}-{c[1]}"] = FeatureExtractionMixin.bodypart_distance(bp1_coords=bp1.astype(np.int32), bp2_coords=bp2.astype(np.int32), px_per_mm=np.float64(self.px_per_mm), in_centimeters=False)
def __get_three_point_angles(self):
for animal, points in self.within_animal_three_point_combs.items():
for point in points:
col_names = list(sum([(f"{x}_x", f"{y}_y") for (x, y) in zip(point, point)], ()))
self.results[f"Angle (degrees) {point[0]}-{point[1]}-{point[2]}"] = (FeatureExtractionMixin.angle3pt_vectorized(data=self.data_df[col_names].values))
def __get_three_point_hulls(self):
for animal, points in self.within_animal_three_point_combs.items():
for point in points:
col_names = list(sum([(f"{x}_x", f"{y}_y") for (x, y) in zip(point, point)], ()))
three_point_arr = np.reshape(self.data_df[col_names].values, (len(self.data_df / 2), -1, 2)).astype(np.float32)
self.results[f"{animal} three-point convex hull perimeter (mm) {point[0]}-{point[1]}-{point[2]}"] = (jitted_hull(points=three_point_arr, target=Formats.PERIMETER.value) / self.px_per_mm)
def __get_four_point_hulls(self):
for animal, points in self.within_animal_four_point_combs.items():
for point in points:
col_names = list(sum([(f"{x}_x", f"{y}_y") for (x, y) in zip(point, point)], ()))
four_point_arr = np.reshape(self.data_df[col_names].values, (len(self.data_df / 2), -1, 2) ).astype(np.float32)
self.results[f"{animal} four-point convex perimeter (mm) {point[0]}-{point[1]}-{point[2]}-{point[3]}"] = (jitted_hull(points=four_point_arr, target=Formats.PERIMETER.value) / self.px_per_mm)
def __get_convex_hulls(self, method: str):
for animal, point in self.animal_bps.items():
col_names = list(sum([(f"{x}_x", f"{y}_y") for (x, y) in zip(point, point)], ()))
animal_point_arr = np.reshape(self.data_df[col_names].values, (len(self.data_df / 2), -1, 2)).astype(np.float32)
if method == 'perimeter':
self.results[f"{animal} convex hull perimeter (mm)"] = (jitted_hull(points=animal_point_arr, target=Formats.PERIMETER.value)/ self.px_per_mm)
else:
self.results[f"{animal} convex hull area (mm2)"] = (jitted_hull(points=animal_point_arr, target=Formats.AREA.value) / self.px_per_mm)
def __get_framewise_movement(self):
for animal, animal_bps in self.animal_bps.items():
for bp in animal_bps:
check_valid_dataframe(df=self.data_df, source=self.file_path, required_fields=[f"{bp}_x", f"{bp}_y"])
bp_arr = FeatureExtractionMixin.create_shifted_df(df=self.data_df[[f"{bp}_x", f"{bp}_y"]]).values
x, y = bp_arr[:, 0:2], bp_arr[:, 2:4]
self.results[f"{animal} movement {bp} (mm)"] = FeatureExtractionMixin.framewise_euclidean_distance(location_1=x.astype(np.float64), location_2=y.astype(np.float64), px_per_mm=np.float64(self.px_per_mm), centimeter=False)
def __get_roi_center_distances(self):
for animal, animal_bps in self.animal_bps.items():
for bp in animal_bps:
check_valid_dataframe(df=self.data_df, source=self.file_path, required_fields=[f"{bp}_x", f"{bp}_y"])
bp_arr = self.data_df[[f"{bp}_x", f"{bp}_y"]].values.astype(np.float32)
for roi_name, roi_data in self.roi_dict[self.video_name].items():
center_point = np.array([roi_data['Center_X'], roi_data['Center_Y']]).astype(np.int32)
distance = FeatureExtractionMixin.framewise_euclidean_distance_roi(location_1=bp_arr, location_2=center_point, px_per_mm=self.px_per_mm)
self.results[f"{animal} {bp} to {roi_name} center distance (mm)"] = distance
def __get_distances_to_frm_edge(self):
for animal, animal_bps in self.animal_bps.items():
for bp in animal_bps:
check_valid_dataframe(df=self.data_df, source=self.file_path, required_fields=[f"{bp}_x", f"{bp}_y"])
bp_arr = self.data_df[[f"{bp}_x", f"{bp}_y"]].values.astype(np.float32)
distance = FeatureExtractionSupplemental().border_distances(data=bp_arr, pixels_per_mm=self.px_per_mm, img_resolution=np.array([self.video_width, self.video_height], dtype=np.int32), time_window=1, fps=1)
self.results[f"{animal} {bp} to left video edge distance (mm)"] = distance[:, 0]
self.results[f"{animal} {bp} to right video edge distance (mm)"] = distance[:, 1]
self.results[f"{animal} {bp} to top video edge distance (mm)"] = distance[:, 2]
self.results[f"{animal} {bp} to bottom video edge distance (mm)"] = distance[:, 3]
def __get_inside_roi(self):
for animal, animal_bps in self.animal_bps.items():
for bp in animal_bps:
check_valid_dataframe(df=self.data_df, source=self.file_path, required_fields=[f"{bp}_x", f"{bp}_y"])
bp_arr = self.data_df[[f"{bp}_x", f"{bp}_y"]].values.astype(np.float32)
for roi_name, roi_data in self.roi_dict[self.video_name].items():
if roi_data[SHAPE_TYPE] == ROI_SETTINGS.RECTANGLE.value:
roi_coords = np.array([[roi_data['topLeftX'], roi_data['topLeftY']], [roi_data['Bottom_right_X'], roi_data['Bottom_right_Y']]])
r = FeatureExtractionMixin.framewise_inside_rectangle_roi(bp_location=bp_arr, roi_coords=roi_coords)
self.results[f"{animal} {bp} inside rectangle {roi_name} (Boolean)"] = r
elif roi_data[SHAPE_TYPE] == ROI_SETTINGS.CIRCLE.value:
circle_center = np.array([roi_data['Center_X'], roi_data['Center_Y']]).astype(np.int32)
r = FeatureExtractionMixin.is_inside_circle(bp=bp_arr, roi_center=circle_center, roi_radius=roi_data['radius'])
self.results[f"{animal} {bp} inside circle {roi_name} (Boolean)"] = r
elif roi_data[SHAPE_TYPE] == ROI_SETTINGS.POLYGON.value:
vertices = roi_data['vertices'].astype(np.int32)
r = FeatureExtractionMixin.framewise_inside_polygon_roi(bp_location=bp_arr, roi_coords=vertices)
self.results[f"{animal} {bp} inside polygon {roi_name} (Boolean)"] = r
def __check_files(self, x: pd.DataFrame, y: pd.DataFrame, path_x: str, path_y: str):
if len(x) != len(y):
remove_multiple_folders(folders=[self.temp_append_dir, self.temp_dir], raise_error=False)
raise InvalidInputError(msg=f'The files at {path_x} and {path_y} do not contain the same number of rows: {len(x)} vs {len(y)}', source=self.__class__.__name__)
duplicated_x_cols = [i for i in x.columns if i in y.columns]
if len(duplicated_x_cols) > 0:
remove_multiple_folders(folders=[self.temp_append_dir, self.temp_dir], raise_error=False)
raise DuplicationError(msg=f'Cannot append the new features to {path_y}. This file already has the following columns: {duplicated_x_cols}', source=self.__class__.__name__)
def __append_to_data_in_dir(self, dir: Union[str, os.PathLike]):
temp_files = find_files_of_filetypes_in_directory(directory=self.temp_dir, extensions=[f'.{self.file_type}'], as_dict=True)
self.temp_append_dir = os.path.join(dir, f'temp_{self.datetime}')
os.makedirs(self.temp_append_dir)
for file_cnt, (file_name, file_path) in enumerate(temp_files.items()):
print(f'Appending features to {file_name} ({file_cnt+1}/{len(list(temp_files.keys()))})')
old_df = read_df(file_path=os.path.join(dir, f'{file_name}.{self.file_type}'), file_type=self.file_type).reset_index(drop=True)
new_features_df = read_df(file_path=file_path, file_type=self.file_type).reset_index(drop=True)
if self.file_checks:
self.__check_files(x=new_features_df, y=old_df, path_x=file_path, path_y=os.path.join(dir, f'{file_name}.{self.file_type}'))
save_path = os.path.join(self.temp_append_dir, f'{file_name}.{self.file_type}')
out_df = pd.concat([old_df, new_features_df], axis=1)
write_df(df=out_df, file_type=self.file_type, save_path=save_path)
prior_dir = os.path.join(dir, f"Prior_to_feature_subset_append_{self.datetime}")
os.makedirs(prior_dir)
copy_files_in_directory(in_dir=dir, out_dir=prior_dir, filetype=self.file_type, raise_error=True)
copy_files_in_directory(in_dir=self.temp_append_dir, out_dir=dir, filetype=self.file_type, raise_error=True)
remove_a_folder(folder_dir=self.temp_append_dir, ignore_errors=False)
def __append_to_targets_inserted(self, dir: Union[str, os.PathLike]):
temp_files = find_files_of_filetypes_in_directory(directory=self.temp_dir, extensions=[f'.{self.file_type}'], as_dict=True)
self.temp_append_dir = os.path.join(dir, f'temp_{self.datetime}')
os.makedirs(self.temp_append_dir)
for file_cnt, (file_name, file_path) in enumerate(temp_files.items()):
old_df = read_df(file_path=os.path.join(dir, f'{file_name}.{self.file_type}'), file_type=self.file_type).reset_index(drop=True)
new_features_df = read_df(file_path=file_path, file_type=self.file_type).reset_index(drop=True)
if self.file_checks:
self.__check_files(x=new_features_df, y=old_df, path_x=file_path, path_y=os.path.join(dir, f'{file_name}.{self.file_type}'))
save_path = os.path.join(self.temp_append_dir, f'{file_name}.{self.file_type}')
clf_cols = [x for x in self.clf_names if x in list(old_df.columns)]
clf_df, old_df = old_df[clf_cols], old_df.drop(clf_cols, axis=1)
out_df = pd.concat([old_df, new_features_df, clf_df], axis=1)
write_df(df=out_df, file_type=self.file_type, save_path=save_path)
prior_dir = os.path.join(dir, f"Prior_to_feature_subset_append_{self.datetime}")
os.makedirs(prior_dir)
copy_files_in_directory(in_dir=dir, out_dir=prior_dir, filetype=self.file_type, raise_error=True)
copy_files_in_directory(in_dir=self.temp_append_dir, out_dir=dir, filetype=self.file_type, raise_error=True)
remove_a_folder(folder_dir=self.temp_append_dir, ignore_errors=False)
[docs] def run(self):
check_all_file_names_are_represented_in_video_log(video_info_df=self.video_info_df, data_paths=self.data_paths)
for file_cnt, file_path in enumerate(self.data_paths):
self.file_path = file_path
self.video_name = get_fn_ext(filepath=file_path)[1]
video_timer = SimbaTimer(start=True)
save_path = os.path.join(self.temp_dir, f'{self.video_name}.{self.file_type}')
self.results = pd.DataFrame()
print(f'Analyzing video {self.video_name}... ({file_cnt+1}/{len(self.data_paths)})')
self.video_info, self.px_per_mm, self.fps = self.read_video_info(video_name=self.video_name)
self.video_width, self.video_height = self.video_info['Resolution_width'].values[0], self.video_info['Resolution_height'].values[0]
self.data_df = read_df(file_path=file_path, file_type=self.file_type)
for family_cnt, feature_family in enumerate(self.feature_families):
print(f"Analyzing {self.video_name} and {feature_family} (Video {file_cnt + 1}/{len(self.outlier_corrected_paths)}, Family {family_cnt + 1}/{len(self.feature_families)})...")
if feature_family == TWO_POINT_BP_DISTANCES:
self._get_two_point_bp_distances()
elif feature_family == WITHIN_ANIMAL_THREE_POINT_ANGLES:
self.__get_three_point_angles()
elif feature_family == WITHIN_ANIMAL_THREE_POINT_HULL:
self.__get_three_point_hulls()
elif feature_family == WITHIN_ANIMAL_FOUR_POINT_HULL:
self.__get_four_point_hulls()
elif feature_family == ANIMAL_CONVEX_HULL_PERIMETER:
self.__get_convex_hulls(method='perimeter')
elif feature_family == ANIMAL_CONVEX_HULL_AREA:
self.__get_convex_hulls(method='area')
elif feature_family == FRAME_BP_MOVEMENT:
self.__get_framewise_movement()
elif feature_family == FRAME_BP_TO_ROI_CENTER:
self.__get_roi_center_distances()
elif feature_family == FRAME_BP_INSIDE_ROI:
self.__get_inside_roi()
elif feature_family == ARENA_EDGE:
self.__get_distances_to_frm_edge()
self.results = self.results.add_suffix('_FEATURE_SUBSET')
self.results = self.results[sorted(self.results.columns)]
write_df(df=self.results.fillna(-1), file_type=self.file_type, save_path=save_path)
video_timer.stop_timer()
print(f"Feature subsets computed for {self.video_name} complete (elapsed time {video_timer.elapsed_time_str}s)...")
if self.append_to_features_extracted:
print(f'Appending new feature to files in {self.features_dir}...')
self.__append_to_data_in_dir(dir=self.features_dir)
if self.append_to_targets_inserted:
print(f'Appending new feature to files in {self.targets_folder}...')
self.__append_to_targets_inserted(dir=self.targets_folder)
if self.save_dir is not None:
print(f"Storing new features in {self.save_dir}...")
copy_files_in_directory(in_dir=self.temp_dir, out_dir=self.save_dir, filetype=self.file_type, raise_error=True)
remove_a_folder(folder_dir=self.temp_dir, ignore_errors=False)
self.timer.stop_timer()
stdout_success(msg="Feature sub-sets calculations complete!", elapsed_time=self.timer.elapsed_time_str)
# test = FeatureSubsetsCalculator(config_path=r"C:\troubleshooting\srami0619\project_folder\project_config.ini",
# feature_families=[TWO_POINT_BP_DISTANCES],
# append_to_features_extracted=False,
# file_checks=True,
# append_to_targets_inserted=False)
# test.run()
# test = FeatureSubsetsCalculator(config_path=r"C:\troubleshooting\mitra\project_folder\project_config.ini",
# feature_families=[TWO_POINT_BP_DISTANCES],
# append_to_features_extracted=False,
# file_checks=True,
# append_to_targets_inserted=False)
# test.run()
#
# test = FeatureSubsetsCalculator(config_path=r"D:\Stretch\Stretch\project_folder\project_config.ini",
# feature_families=[TWO_POINT_BP_DISTANCES],
# append_to_features_extracted=True,
# file_checks=True,
# append_to_targets_inserted=True,
# save_dir=r"D:\Stretch\Stretch\project_folder\new_features")
# test.run()
# test = FeatureSubsetsCalculator(config_path=r"C:\troubleshooting\mitra\project_folder\project_config.ini",
# feature_families=[TWO_POINT_BP_DISTANCES],
# append_to_features_extracted=False,
# file_checks=False,
# append_to_targets_inserted=False,
# save_dir=r"C:\troubleshooting\mitra\project_folder\csv\new_features")
# test.run()
# test = FeatureSubsetsCalculator(config_path='/Users/simon/Desktop/envs/troubleshooting/two_black_animals_14bp/project_folder/project_config.ini',
# feature_family='Frame-by-frame body-parts inside ROIs (Boolean)',
# save_dir='/Users/simon/Desktop/envs/troubleshooting/two_black_animals_14bp/data')
# test.run()
#
#
# test = FeatureSubsetsCalculator(config_path='/Users/simon/Desktop/envs/troubleshooting/two_black_animals_14bp/project_folder/project_config.ini',
# feature_family='Frame-by-frame body-part movements (mm)',
# save_dir='/Users/simon/Desktop/envs/troubleshooting/two_black_animals_14bp/data')
# test.run()
#
#
# test = FeatureSubsetsCalculator(config_path='/Users/simon/Desktop/envs/troubleshooting/two_black_animals_14bp/project_folder/project_config.ini',
# feature_families=['Frame-by-frame body-part distances to ROI centers (mm)', 'Frame-by-frame body-parts inside ROIs (Boolean)'],
# save_dir='/Users/simon/Desktop/envs/troubleshooting/two_black_animals_14bp/data',
# include_file_checks=True,
# append_to_features_extracted=False,
# append_to_targets_inserted=True)
# test.run()