Source code for core.utils

from pathlib import Path, PosixPath, WindowsPath
from typing import List, Tuple, Dict, Optional, Union

import imageio as iio
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import yaml


[docs]def convert_to_path(attribute: Union[str, Path]) -> Path: """Convert strings to Path and returns them.""" if type(attribute) == PosixPath or type(attribute) == WindowsPath: return attribute elif type(attribute) == str: return Path(attribute)
[docs]def check_keys(dictionary: Dict, list_of_keys: List[str]) -> List: """ Check, whether list_of_keys are in a dictionary and returns a list of keys missing in the dictionary. """ missing_keys = [] for key in list_of_keys: if key not in dictionary: missing_keys.append(key) return missing_keys
[docs]def read_config(path: Path) -> Dict: """Read structured config file defining a project.""" path = convert_to_path(path) if path.exists() and path.suffix == ".yaml": with open(path, "r") as ymlfile: cfg = yaml.load(ymlfile, Loader=yaml.SafeLoader) else: raise FileNotFoundError( f"Could not open the yaml file at {path}\n" f"Please make sure the path is correct and the file exists!" ) return cfg
[docs]class Coordinates: """ Class to store xyz coordinates. Parameters ---------- y_or_row: int x_or_column: int z: int Attributes __________ x: int, column: int Represent x coordinates in a system and columns in an image. y, row: int Represent y coordinates in a system and rows in an image. z: int Represent z coordinates in a system. """ def __init__( self, y_or_row: int, x_or_column: int, z: Optional[int] = None ) -> None: """ Construct attributes for Class Coordinates. Parameters ---------- y_or_row: int x_or_column: int z: int """ self.y = y_or_row self.row = y_or_row self.x = x_or_column self.column = x_or_column self.z = z
[docs]def get_3D_df_keys(key: str) -> Tuple[str, str, str]: """ Construct a tuple of strings representing keys in a aniposelib created 3 dimensional DataFrame. """ return key + "_x", key + "_y", key + "_z"
[docs]def create_calibration_key( videos: List[str], recording_date: str, calibration_index: int, iteration: Optional[int] = None, ) -> str: key = "" videos.sort() for elem in videos: key = key + "_" + elem if iteration is None: calibration_key = recording_date + "_" + str(calibration_index) + key else: calibration_key = recording_date + "_" + str(calibration_index) + key + "_" + str(iteration) return calibration_key
[docs]def get_3D_array(df: pd.DataFrame, key: str, index: Optional[int] = None) -> np.array: """ Construct a ndarray of shape (3, N) representing one marker in a 3 dimensional DataFrame with N number of indices. """ x, y, z = get_3D_df_keys(key) if index is None: return np.array([df[x], df[y], df[z]]) else: return np.array([df[x][index], df[y][index], df[z][index]])
[docs]def get_multi_index(markers: List) -> pd.MultiIndex: multi_index_column_names = [[], [], []] for marker_id in markers: for column_name in ("x", "y", "likelihood"): multi_index_column_names[0].append("annotated_markers") multi_index_column_names[1].append(marker_id) multi_index_column_names[2].append(column_name) return pd.MultiIndex.from_arrays( multi_index_column_names, names=("scorer", "bodyparts", "coords") )
[docs]def construct_dlc_output_style_df_from_dictionary(marker_predictions: Dict) -> pd.DataFrame: """ Create a DataFrame from dictionary with DLC-like multi-index. Parameters ---------- marker_predictions: {str: {str: list of int}} Dictionary containing markers as keys and dictionaries as values with x, y and z as keys and lists of int as values. The length of the lists is equivalent to the number of frames annotated. Returns ------- df: pd.DataFrame The dataframe with DLC-like multiindex and data from input dictionary. """ multi_index = get_multi_index( markers=marker_predictions.keys() ) df = pd.DataFrame(data={}, columns=multi_index) for scorer, marker_id, key in df.columns: df[(scorer, marker_id, key)] = marker_predictions[ marker_id ][key] return df
[docs]def load_image(filepath: Path, idx: int = 0) -> np.ndarray: iio_reader = iio.get_reader(filepath) return np.asarray(iio_reader.get_data(idx))
[docs]def load_single_frame_of_video(filepath: Path, frame_idx: int = 0) -> np.ndarray: return load_image(filepath=filepath, idx=frame_idx)
[docs]def plot_image( filepath: Path, idx: int = 0, plot_size: Tuple[int, int] = (9, 6) ) -> None: fig = plt.figure(figsize=plot_size, facecolor="white") image = load_image(filepath=filepath, idx=idx) plt.imshow(image)
[docs]def plot_single_frame_of_video( filepath: Path, frame_idx: int = 0, plot_size: Tuple[int, int] = (9, 6) ) -> None: plot_image(filepath=filepath, idx=frame_idx, plot_size=plot_size)
KEYS_TO_CHECK_PROJECT = [ "valid_cam_ids", "paradigms", "animal_lines", "led_extraction_type", "led_extraction_filepath", "max_calibration_frames", "max_cpu_cores_to_pool", "max_ram_digestible_frames", "rapid_aligner_path", "use_gpu", "load_calibration", "calibration_tag", "calibration_validation_tag", "allowed_num_diverging_frames", "handle_synchro_fails", "default_offset_ms", "start_pattern_match_ms", "end_pattern_match_ms", "synchro_error_threshold", "synchro_marker", "led_box_size", "use_2D_filter", "score_threshold", 'num_frames_to_pick', 'triangulation_type' ] """ Keys ____ calibration_evaluation_filepath: {str: str} Dictionary with keys for all valid cam_ids, defining the filepath to the file to use for marker detection for calibration_validation per cam. calibration_evaluation_type: {str: str} Dictionary with keys for all valid cam_ids, defining the type of marker detection to use per cam for calibration_evaluation files. Values: DLC, manual processing_filepath: {str: str} Dictionary with keys for all valid cam_ids, defining the filepath to the file to use for marker detection for recordings per cam. processing_type: {str: str} Dictionary with keys for all valid cam_ids, defining the type of marker detection to use per cam for recordings. Values: DLC, manual, exclude led_extraction_filepath: {str: str} Dictionary with keys for all valid cam_ids, defining the filepath to the file to use for marker detection for synchronization per cam. led_extraction_type: {str: str} Dictionary with keys for all valid cam_ids, defining the type of marker detection to use per cam for synchronization. Values: DLC, manual animal_lines: list of str List of all animal_lines to search for in recording filenames. If the lines are numbers, you need to set the lines as str. paradigms: list of str List of all paradigms to search for in filenames and directories. valid_cam_ids: list of str List of all cam_ids to search for in filenames. calibration_tag: str Filename tag to search for in the calibration files. "Calvin" is an invalid value. calibration_validation_tag: str Filename tag to search for in the calibration_validation files. "Calvin" is an invalid value. use_gpu: str, default "" Whether to restrict the usage of GPU for DLC analyses. Values: "", "prevent", "low", "full" "prevent" disables GPU usage "low" restricts GPU memory for synchronization "full" uses entire GPU capacity, equivalent to "" intrinsic_calibration_directory: str The directory, in which the intrinsic calibration pickle .p files or the intrinsic calibration checkerboard videos are stored. Intrinsic calibration videos have to have "checkerboard" and a valid cam_id in their filename. They have to be recorded in same resolution as the recording/calibration videos without cropping, using a 6x6 checkerboard. load_calibration: bool If True, then use this package to set the intrinsics of the cameras instead of using the aniposelib function to calibrate intrinsics. Requires checkerboard videos or .p pickle files for all cameras in intrinsic_calibration_directory. triangulation_type: str, default "triangulate" Specify the method of aniposelib triangulation to use. Values: "triangulate", "triangulate_optim_ransac_True", "triangulate_optim_ransac_False" allowed_num_diverging_frames: int Specify how many frames a synchronized file can differ from the median of all synchronized files before it will be excluded from analysis. handle_synchro_fails: str, default error How to proceed if the first synchronisation try exceeds synchro_error_threshold. Values: "repeat", "default", "manual", "error" "repeat" run the same method of synchronisation again. "default" using default value default_offset_ms as synchro offset. "manual" using manual marker detection for synchronisation "error" raises an error and breaks the analysis default_offset_ms: int Synchro offset to use if first synchro try fails and handle_synchro_fails is "default". In milliseconds. start_pattern_match_ms: int, default 0 Start of time range in which to search for matching synchro pattern. In milliseconds. end_pattern_match_ms: int End of time range in which to search for matching synchro pattern. In milliseconds. synchro_error_threshold: int, default 100 Below this threshold, a synchro patter alignment error will be considered as good synchro, above, as failed synchro. synchro_marker: str The marker to use for synchronisation. Has to be detectable by the led_extraction method. led_box_size: int Pixel range around predicted synchro marker position to calculate mean pixel intensity for blinking pattern from. use_2D_filter: bool, default True Whether to use filtering on 2D marker detection predictions in recordings. At the moment only available for DLC. score_threshold: float, default 0.9 Only predictions with likelihood above this threshold will be taken into account for triangulation. num_frames_to_pick: int, default 5 The number of frames to use to find optimal prediction for the synchro_marker. max_ram_digestible_frames: int, default 3000 Maximum number of frames to keep in RAM during writing of synchronised videos. Increase to speed up analysis, reduce to adapt to available RAM. max_cpu_cores_to_pool: int, default 0 If 0, then no multiprocessing will be used for writing of synchronised videos. Set to as many CPU cores, as you would like to use for multiprocessing. max_calibration_frames: int Number of frames to take into account for intrinsic calibration. 300 works well, depending on CPU speed, it can be necessary to reduce. rapid_aligner_path: str, default "" If "", then no rapid_aligner (GPU based pattern alignment) will be used. Insert path to locally installed clone of the rapid_aligner package to use GPU for pattern synchronisation. """ KEYS_TO_CHECK_RECORDING = [ "led_pattern", "target_fps", "calibration_index", "recording_date", ] """ led_pattern: dict Blinking pattern to use for temporal synchronisation. target_fps: int Fps rate, to which the videos should be synchronized. calibration_index: int, default 0 Index of a calibration. Together with recording_date, it creates a unique calibration key. recording_date: str Date at which the calibration was done. """ # ToDo: default values # fps not necessary if all cams have the same fps # offsets not necessary if no cropping was performed or use_intrinsic_calibration False # fisheye not necessary if no camera is fisheye KEYS_TO_CHECK_CAMERA_RECORDING = ["fps", "offset_row_idx", "offset_col_idx", "flip_h", "flip_v", "fisheye"] """ fps: int The framerate of the camera. offset_row_idx: int, default 0 If cropping was performed, specify the row or y index initial here. offset_col_idx: int, default 0 If cropping was performed, specify the col or x index initial here. flip_h: bool, default False Based on the size from uncropped intrinsic calibration videos, the row or y index end is calculated and used instead of offset_row_idx. flip_v: bool, default False Based on the size from uncropped intrinsic calibration videos, the col or x index end is calculated and used instead of offset_col_idx. fisheye: bool, default False If a fisheye lens was used, set True. """ KEYS_TO_CHECK_CAMERA_PROJECT = [ "processing_type", "calibration_evaluation_type", "processing_filepath", "calibration_evaluation_filepath", "led_extraction_type", "led_extraction_filepath", ] STANDARD_ATTRIBUTES_TRIANGULATION = ["all_cameras", "markers_excluded_manually", "calibration_toml_filepath", "csv_output_filepath", "markers", "triangulation_dlc_cams_filepaths", "project_config_filepath", "output_directory", "normalised_dataframe", "anipose_io", "video_plotting_config", "rotated_filepath", "rotation_error", "synchronization_individuals", "led_detection_individuals", "ground_truth_config", "triangulation_visualization", "video_start_s", "excluded_videos"] STANDARD_ATTRIBUTES_CALIBRATION = ["camera_group", "report_filepath", "reprojerr", "valid_videos", "synchronized_charuco_videofiles", "camera_objects", "synchronization_individuals", "led_detection_individuals", "project_config_filepath", "output_directory", "excluded_videos"] SYNCHRO_METADATA_KEYS = ["handle_synchro_fails", "default_offset_ms", "start_pattern_match_ms", "end_pattern_match_ms", "synchro_error_threshold", "synchro_marker", "use_2D_filter", 'num_frames_to_pick', "rapid_aligner_path", "use_gpu", "led_box_size"] KEYS_PER_CAM_PROJECT = ["processing_type", "calibration_evaluation_type", "processing_filepath", "calibration_evaluation_filepath", "led_extraction_type", "led_extraction_filepath"] KEYS_VIDEOMETADATA_PROJECT = ["valid_cam_ids", "paradigms", "load_calibration", "max_calibration_frames", "max_ram_digestible_frames", "max_cpu_cores_to_pool", "animal_lines"] # ToDo # led pattern not necessary if no synchronisation necessary KEYS_VIDEOMETADATA_RECORDING = ["led_pattern", "target_fps", "calibration_index"]