diff --git a/Pose2Sim/Demo_Batch/Config.toml b/Pose2Sim/Demo_Batch/Config.toml index e5063d89..8dabb75d 100644 --- a/Pose2Sim/Demo_Batch/Config.toml +++ b/Pose2Sim/Demo_Batch/Config.toml @@ -18,9 +18,9 @@ [project] -multi_person = false # true for trials with multiple participants. If false, only the person with lowest reprojection error is analyzed. -participant_height = 1.72 # m # float if single person, list of float if multi-person (same order as the Static trials) # Only used for marker augmentation -participant_mass = 70.0 # kg # Only used for marker augmentation and scaling +multi_person = false # true for trials with multiple participants. If false, only the person with lowest reprojection error is analyzed. +participant_height = 'auto' # 'auto', float (eg 1.72), or list of floats (eg [1.72, 1.40]) # meters # Only used for marker augmentation +participant_mass = 70.0 # float (eg 70.0), or list of floats (eg [70.0, 63.5]) # kg # Only used for marker augmentation and scaling, no impact on results unless you need to further compute forces rather than only kinematics frame_rate = 'auto' # fps # int or 'auto'. If 'auto', finds from video (or defaults to 60 fps if you work with images) frame_range = [] # For example [10,300], or [] for all frames. @@ -183,6 +183,10 @@ make_c3d = true # also save triangulated data in c3d format ## "RAnkle", "LAnkle", "RHeel", "LHeel", "RSmallToe", "LSmallToe", ## "RBigToe", "LBigToe", "RElbow", "LElbow", "RWrist", "LWrist"] make_c3d = true # save triangulated data in c3d format in addition to trc +fastest_frames_to_remove_percent = 0.1 # Frames with high speed are considered as outliers +close_to_zero_speed_m = 0.2 # Sum for all keypoints: about 50 px/frame or 0.2 m/frame +large_hip_knee_angles = 45 # Hip and knee angles below this value are considered as imprecise +trimmed_extrema_percent = 0.5 # Proportion of the most extreme segment values to remove before calculating their mean) [kinematics] @@ -191,10 +195,6 @@ right_left_symmetry = true # true or false (lowercase) # Set to false only if yo remove_individual_scaling_setup = true # true or false (lowercase) # If true, the individual scaling setup files are removed to avoid cluttering remove_individual_IK_setup = true # true or false (lowercase) # If true, the individual IK setup files are removed to avoid cluttering -fastest_frames_to_remove_percent = 0.1 # Frames with high speed are considered as outliers -close_to_zero_speed_m = 0.2 # Sum for all keypoints: about 50 px/frame or 0.2 m/frame -large_hip_knee_angles = 45 # Hip and knee angles below this value are considered as imprecise -trimmed_extrema_percent = 0.5 # Proportion of the most extreme segment values to remove before calculating their mean) # CUSTOM skeleton, if you trained your own model from DeepLabCut or MMPose for example. diff --git a/Pose2Sim/Demo_Batch/Trial_1/Config.toml b/Pose2Sim/Demo_Batch/Trial_1/Config.toml index 10b32f08..46372f01 100644 --- a/Pose2Sim/Demo_Batch/Trial_1/Config.toml +++ b/Pose2Sim/Demo_Batch/Trial_1/Config.toml @@ -18,9 +18,9 @@ # [project] -# multi_person = false # true for trials with multiple participants. If false, only the person with lowest reprojection error is analyzed. -# participant_height = 1.72 # m # float if single person, list of float if multi-person (same order as the Static trials) # Only used for marker augmentation -# participant_mass = 70.0 # kg # Only used for marker augmentation and scaling +# multi_person = false # true for trials with multiple participants. If false, only the person with lowest reprojection error is analyzed. +# participant_height = 'auto' # 'auto', float (eg 1.72), or list of floats (eg [1.72, 1.40]) # meters # Only used for marker augmentation +# participant_mass = 70.0 # float (eg 70.0), or list of floats (eg [70.0, 63.5]) # kg # Only used for marker augmentation and scaling, no impact on results unless you need to further compute forces rather than only kinematics # frame_rate = 'auto' # fps # int or 'auto'. If 'auto', finds from video (or defaults to 60 fps if you work with images) # frame_range = [] # For example [10,300], or [] for all frames. @@ -183,7 +183,10 @@ # ## "RAnkle", "LAnkle", "RHeel", "LHeel", "RSmallToe", "LSmallToe", # ## "RBigToe", "LBigToe", "RElbow", "LElbow", "RWrist", "LWrist"] # make_c3d = false # save triangulated data in c3d format in addition to trc - +# fastest_frames_to_remove_percent = 0.1 # Frames with high speed are considered as outliers +# close_to_zero_speed_m = 0.2 # Sum for all keypoints: about 50 px/frame or 0.2 m/frame +# large_hip_knee_angles = 45 # Hip and knee angles below this value are considered as imprecise +# trimmed_extrema_percent = 0.5 # Proportion of the most extreme segment values to remove before calculating their mean) # [kinematics] # use_augmentation = true # true or false (lowercase) # Set to true if you want to use the model with augmented markers @@ -191,10 +194,7 @@ # remove_individual_scaling_setup = true # true or false (lowercase) # If true, the individual scaling setup files are removed to avoid cluttering # remove_individual_IK_setup = true # true or false (lowercase) # If true, the individual IK setup files are removed to avoid cluttering -# fastest_frames_to_remove_percent = 0.1 # Frames with high speed are considered as outliers -# close_to_zero_speed_m = 0.2 # Sum for all keypoints: about 50 px/frame or 0.2 m/frame -# large_hip_knee_angles = 45 # Hip and knee angles below this value are considered as imprecise -# trimmed_extrema_percent = 0.5 # Proportion of the most extreme segment values to remove before calculating their mean) + # # CUSTOM skeleton, if you trained your own model from DeepLabCut or MMPose for example. diff --git a/Pose2Sim/Demo_Batch/Trial_2/Config.toml b/Pose2Sim/Demo_Batch/Trial_2/Config.toml index 5da6a2dd..7781e4d8 100644 --- a/Pose2Sim/Demo_Batch/Trial_2/Config.toml +++ b/Pose2Sim/Demo_Batch/Trial_2/Config.toml @@ -18,9 +18,9 @@ [project] -multi_person = true # true for trials with multiple participants. If false, only the person with lowest reprojection error is analyzed. -participant_height = [1.72, 1.40] # m # float if single person, list of float if multi-person (same order as the Static trials) # Only used for marker augmentation -participant_mass = [70.0, 63.5] # kg # Only used for marker augmentation and scaling +multi_person = true # true for trials with multiple participants. If false, only the person with lowest reprojection error is analyzed. +participant_height = 'auto' # 'auto', float (eg 1.72), or list of floats (eg [1.72, 1.40]) # meters # Only used for marker augmentation +participant_mass = [70.0, 63.5] # float (eg 70.0), or list of floats (eg [70.0, 63.5]) # kg # Only used for marker augmentation and scaling, no impact on results unless you need to further compute forces rather than only kinematics # frame_rate = 'auto' # fps # int or 'auto'. If 'auto', finds from video (or defaults to 60 fps if you work with images) # frame_range = [] # For example [10,300], or [] for all frames. @@ -56,6 +56,7 @@ participant_mass = [70.0, 63.5] # kg # Only used for marker augmentation and sca [synchronization] # display_sync_plots = true # true or false (lowercase) + keypoints_to_consider = ['RWrist'] # 'all' if all points should be considered, for example if the participant did not perform any particicular sharp movement. In this case, the capture needs to be 5-10 seconds long at least # # ['RWrist', 'RElbow'] list of keypoint names if you want to specify keypoints with a sharp vertical motion. # approx_time_maxspeed = 'auto' # 'auto' if you want to consider the whole capture (default, slower if long sequences) @@ -147,17 +148,17 @@ keypoints_to_consider = ['RWrist'] # 'all' if all points should be considered, f # interpolation = 'linear' #linear, slinear, quadratic, cubic, or none # # 'none' if you don't want to interpolate missing points # interp_if_gap_smaller_than = 10 # do not interpolate bigger gaps -# fill_large_gaps_with = 'last_value' # 'last_value', 'nan', or 'zeros' # show_interp_indices = true # true or false (lowercase). For each keypoint, return the frames that need to be interpolated +# fill_large_gaps_with = 'last_value' # 'last_value', 'nan', or 'zeros' # handle_LR_swap = false # Better if few cameras (eg less than 4) with risk of limb swapping (eg camera facing sagittal plane), otherwise slightly less accurate and slower # undistort_points = false # Better if distorted image (parallel lines curvy on the edge or at least one param > 10^-2), but unnecessary (and slightly slower) if distortions are low -# make_c3d = false # save triangulated data in c3d format in addition to trc +# make_c3d = true # save triangulated data in c3d format in addition to trc # [filtering] # type = 'butterworth' # butterworth, kalman, gaussian, LOESS, median, butterworth_on_speed # display_figures = false # true or false (lowercase) -# make_c3d = false # also save triangulated data in c3d format +# make_c3d = true # also save triangulated data in c3d format # [filtering.butterworth] # order = 4 @@ -182,7 +183,10 @@ keypoints_to_consider = ['RWrist'] # 'all' if all points should be considered, f # ## "RAnkle", "LAnkle", "RHeel", "LHeel", "RSmallToe", "LSmallToe", # ## "RBigToe", "LBigToe", "RElbow", "LElbow", "RWrist", "LWrist"] # make_c3d = false # save triangulated data in c3d format in addition to trc - +# fastest_frames_to_remove_percent = 0.1 # Frames with high speed are considered as outliers +# close_to_zero_speed_m = 0.2 # Sum for all keypoints: about 50 px/frame or 0.2 m/frame +# large_hip_knee_angles = 45 # Hip and knee angles below this value are considered as imprecise +# trimmed_extrema_percent = 0.5 # Proportion of the most extreme segment values to remove before calculating their mean) # [kinematics] # use_augmentation = true # true or false (lowercase) # Set to true if you want to use the model with augmented markers @@ -190,10 +194,7 @@ keypoints_to_consider = ['RWrist'] # 'all' if all points should be considered, f # remove_individual_scaling_setup = true # true or false (lowercase) # If true, the individual scaling setup files are removed to avoid cluttering # remove_individual_IK_setup = true # true or false (lowercase) # If true, the individual IK setup files are removed to avoid cluttering -# fastest_frames_to_remove_percent = 0.1 # Frames with high speed are considered as outliers -# close_to_zero_speed_m = 0.2 # Sum for all keypoints: about 50 px/frame or 0.2 m/frame -# large_hip_knee_angles = 45 # Hip and knee angles below this value are considered as imprecise -# trimmed_extrema_percent = 0.5 # Proportion of the most extreme segment values to remove before calculating their mean) + # # CUSTOM skeleton, if you trained your own model from DeepLabCut or MMPose for example. diff --git a/Pose2Sim/Demo_MultiPerson/Config.toml b/Pose2Sim/Demo_MultiPerson/Config.toml index 6e119d9a..2afdd1da 100644 --- a/Pose2Sim/Demo_MultiPerson/Config.toml +++ b/Pose2Sim/Demo_MultiPerson/Config.toml @@ -19,8 +19,8 @@ [project] multi_person = true # true for trials with multiple participants. If false, only the person with lowest reprojection error is analyzed. -participant_height = [1.72, 1.40] # m # float if single person, list of float if multi-person (same order as the Static trials) # Only used for marker augmentation -participant_mass = [70.0, 63.5] # kg # Only used for marker augmentation and scaling +participant_height = 'auto' # 'auto', float (eg 1.72), or list of floats (eg [1.72, 1.40]) # meters # Only used for marker augmentation +participant_mass = [70.0, 63.5] # float (eg 70.0), or list of floats (eg [70.0, 63.5]) # kg # Only used for marker augmentation and scaling, no impact on results unless you need to further compute forces rather than only kinematics frame_rate = 'auto' # fps # int or 'auto'. If 'auto', finds from video (or defaults to 60 fps if you work with images) frame_range = [] # For example [10,300], or [] for all frames. @@ -183,7 +183,10 @@ make_c3d = false # also save triangulated data in c3d format ## "RAnkle", "LAnkle", "RHeel", "LHeel", "RSmallToe", "LSmallToe", ## "RBigToe", "LBigToe", "RElbow", "LElbow", "RWrist", "LWrist"] make_c3d = true # save triangulated data in c3d format in addition to trc - +fastest_frames_to_remove_percent = 0.1 # Frames with high speed are considered as outliers +close_to_zero_speed_m = 0.05 # Sum for all keypoints: about 50 px/frame or 0.2 m/frame +large_hip_knee_angles = 45 # Hip and knee angles below this value are considered as imprecise +trimmed_extrema_percent = 0.5 # Proportion of the most extreme segment values to remove before calculating their mean) [kinematics] use_augmentation = true # true or false (lowercase) # Set to true if you want to use the model with augmented markers @@ -191,10 +194,7 @@ right_left_symmetry = true # true or false (lowercase) # Set to false only if yo remove_individual_scaling_setup = true # true or false (lowercase) # If true, the individual scaling setup files are removed to avoid cluttering remove_individual_IK_setup = true # true or false (lowercase) # If true, the individual IK setup files are removed to avoid cluttering -fastest_frames_to_remove_percent = 0.1 # Frames with high speed are considered as outliers -close_to_zero_speed_m = 0.2 # Sum for all keypoints: about 50 px/frame or 0.2 m/frame -large_hip_knee_angles = 45 # Hip and knee angles below this value are considered as imprecise -trimmed_extrema_percent = 0.5 # Proportion of the most extreme segment values to remove before calculating their mean) + # CUSTOM skeleton, if you trained your own model from DeepLabCut or MMPose for example. diff --git a/Pose2Sim/Demo_SinglePerson/Config.toml b/Pose2Sim/Demo_SinglePerson/Config.toml index bd4b339c..fb5ff64d 100644 --- a/Pose2Sim/Demo_SinglePerson/Config.toml +++ b/Pose2Sim/Demo_SinglePerson/Config.toml @@ -19,8 +19,8 @@ [project] multi_person = false # true for trials with multiple participants. If false, only the person with lowest reprojection error is analyzed. -participant_height = 1.72 # m # float if single person, list of float if multi-person (same order as the Static trials) # Only used for marker augmentation -participant_mass = 70.0 # kg # Only used for marker augmentation and scaling +participant_height = 'auto' # 'auto', float (eg 1.72), or list of floats (eg [1.72, 1.40]) # meters # Only used for marker augmentation +participant_mass = 70.0 # float (eg 70.0), or list of floats (eg [70.0, 63.5]) # kg # Only used for marker augmentation and scaling, no impact on results unless you need to further compute forces rather than only kinematics frame_rate = 'auto' # fps # int or 'auto'. If 'auto', finds from video (or defaults to 60 fps if you work with images) frame_range = [] # For example [10,300], or [] for all frames. @@ -183,7 +183,10 @@ make_c3d = true # also save triangulated data in c3d format ## "RAnkle", "LAnkle", "RHeel", "LHeel", "RSmallToe", "LSmallToe", ## "RBigToe", "LBigToe", "RElbow", "LElbow", "RWrist", "LWrist"] make_c3d = true # save triangulated data in c3d format in addition to trc - +fastest_frames_to_remove_percent = 0.1 # Frames with high speed are considered as outliers +close_to_zero_speed_m = 0.2 # Sum for all keypoints: about 50 px/frame or 0.2 m/frame +large_hip_knee_angles = 45 # Hip and knee angles below this value are considered as imprecise +trimmed_extrema_percent = 0.5 # Proportion of the most extreme segment values to remove before calculating their mean) [kinematics] use_augmentation = true # true or false (lowercase) # Set to true if you want to use the model with augmented markers @@ -191,10 +194,7 @@ right_left_symmetry = true # true or false (lowercase) # Set to false only if yo remove_individual_scaling_setup = true # true or false (lowercase) # If true, the individual scaling setup files are removed to avoid cluttering remove_individual_IK_setup = true # true or false (lowercase) # If true, the individual IK setup files are removed to avoid cluttering -fastest_frames_to_remove_percent = 0.1 # Frames with high speed are considered as outliers -close_to_zero_speed_m = 0.2 # Sum for all keypoints: about 50 px/frame or 0.2 m/frame -large_hip_knee_angles = 45 # Hip and knee angles below this value are considered as imprecise -trimmed_extrema_percent = 0.5 # Proportion of the most extreme segment values to remove before calculating their mean) + # CUSTOM skeleton, if you trained your own model from DeepLabCut or MMPose for example. diff --git a/Pose2Sim/common.py b/Pose2Sim/common.py index 8e491d69..b2bd57af 100644 --- a/Pose2Sim/common.py +++ b/Pose2Sim/common.py @@ -14,6 +14,7 @@ import toml import json import numpy as np +import pandas as pd import re import cv2 import c3d @@ -54,6 +55,32 @@ def common_items_in_list(list1, list2): return False +def read_trc(trc_path): + ''' + Read a TRC file and extract its contents. + + INPUTS: + - trc_path (str): The path to the TRC file. + + OUTPUTS: + - tuple: A tuple containing the Q coordinates, frames column, time column, marker names, and header. + ''' + + try: + with open(trc_path, 'r') as trc_file: + header = [next(trc_file) for _ in range(5)] + markers = header[3].split('\t')[2::3] + + trc_df = pd.read_csv(trc_path, sep="\t", skiprows=4, encoding='utf-8') + frames_col, time_col = trc_df.iloc[:, 0], trc_df.iloc[:, 1] + Q_coords = trc_df.drop(trc_df.columns[[0, 1]], axis=1) + + return Q_coords, frames_col, time_col, markers, header + + except Exception as e: + raise ValueError(f"Error reading TRC file at {trc_path}: {e}") + + def bounding_boxes(js_file, margin_percent=0.1, around='extremities'): ''' Compute the bounding boxes of the people in the json file. @@ -678,6 +705,107 @@ def points_to_angles(points_list): return ang_deg +def best_coords_for_measurements(Q_coords, keypoints_names, fastest_frames_to_remove_percent=0.2, close_to_zero_speed=0.2, large_hip_knee_angles=45): + ''' + Compute the best coordinates for measurements, after removing: + - 20% fastest frames (may be outliers) + - frames when speed is close to zero (person is out of frame): 0.2 m/frame, or 50 px/frame + - frames when hip and knee angle below 45° (imprecise coordinates when person is crouching) + + INPUTS: + - Q_coords: pd.DataFrame. The XYZ coordinates of each marker + - keypoints_names: list. The list of marker names + - fastest_frames_to_remove_percent: float + - close_to_zero_speed: float (sum for all keypoints: about 50 px/frame or 0.2 m/frame) + - large_hip_knee_angles: int + - trimmed_extrema_percent + + OUTPUT: + - Q_coords_low_speeds_low_angles: pd.DataFrame. The best coordinates for measurements + ''' + + # Import here to avoid circular import error (kinematics <-> common) + from Pose2Sim.kinematics import mean_angles + + # Add Hip column if not present + n_markers_init = len(keypoints_names) + if 'Hip' not in keypoints_names: + RHip_df = Q_coords.iloc[:,keypoints_names.index('RHip')*3:keypoints_names.index('RHip')*3+3] + LHip_df = Q_coords.iloc[:,keypoints_names.index('LHip')*3:keypoints_names.index('LHip')*3+3] + #Hip_df = RHip_df.add(LHip_df, fill_value=0) /2 # .add function would make 6 columns due to RHip and LHip have different name of columns + Hip_df = pd.DataFrame((RHip_df.values + LHip_df.values) / 2, columns=['X','Y','Z']) + Hip_df.columns = [col+ str(int(Q_coords.columns[-1][1:])+1) for col in ['X','Y','Z']] + keypoints_names += ['Hip'] + Q_coords = pd.concat([Q_coords, Hip_df], axis=1) + n_markers = len(keypoints_names) + + # Using 80% slowest frames + sum_speeds = pd.Series(np.nansum([np.linalg.norm(Q_coords.iloc[:,kpt:kpt+3].diff(), axis=1) for kpt in range(n_markers)], axis=0)) + sum_speeds = sum_speeds[sum_speeds>close_to_zero_speed] # Removing when speeds close to zero (out of frame) + min_speed_indices = sum_speeds.abs().nsmallest(int(len(sum_speeds) * (1-fastest_frames_to_remove_percent))).index + Q_coords_low_speeds = Q_coords.iloc[min_speed_indices].reset_index(drop=True) + + # Only keep frames with hip and knee flexion angles below 45% + # (if more than 50 of them, else take 50 smallest values) + ang_mean = mean_angles(Q_coords_low_speeds, keypoints_names, ang_to_consider = ['right knee', 'left knee', 'right hip', 'left hip']) + Q_coords_low_speeds_low_angles = Q_coords_low_speeds[ang_mean < large_hip_knee_angles] + if len(Q_coords_low_speeds_low_angles) < 50: + Q_coords_low_speeds_low_angles = Q_coords_low_speeds.iloc[pd.Series(ang_mean).nsmallest(50).index] + + if n_markers_init < n_markers: + Q_coords_low_speeds_low_angles = Q_coords_low_speeds_low_angles.iloc[:,:-3] + keypoints_names.remove('Hip') # prevent length mismatch + + return Q_coords_low_speeds_low_angles + + +def compute_height(Q_coords, keypoints_names, fastest_frames_to_remove_percent=0.1, close_to_zero_speed=0.2, large_hip_knee_angles=45, trimmed_extrema_percent=0.5): + ''' + Compute the height of the person from the trc data. + + INPUTS: + - Q_coords: pd.DataFrame. The XYZ coordinates of each marker + - keypoints_names: list. The list of marker names + - fastest_frames_to_remove_percent: float. Frames with high speed are considered as outliers + - close_to_zero_speed: float. Sum for all keypoints: about 50 px/frame or 0.2 m/frame + - large_hip_knee_angles5: float. Hip and knee angles below this value are considered as imprecise + - trimmed_extrema_percent: float. Proportion of the most extreme segment values to remove before calculating their mean) + + OUTPUT: + - height: float. The estimated height of the person + ''' + + # Retrieve most reliable coordinates + Q_coords_low_speeds_low_angles = best_coords_for_measurements(Q_coords, keypoints_names, + fastest_frames_to_remove_percent=fastest_frames_to_remove_percent, close_to_zero_speed=close_to_zero_speed, large_hip_knee_angles=large_hip_knee_angles) + Q_coords_low_speeds_low_angles.columns = np.array([[m]*3 for m in keypoints_names]).flatten() + + # Add MidShoulder column + df_MidShoulder = pd.DataFrame((Q_coords_low_speeds_low_angles['RShoulder'].values + Q_coords_low_speeds_low_angles['LShoulder'].values) /2) + df_MidShoulder.columns = ['MidShoulder']*3 + Q_coords_low_speeds_low_angles = pd.concat((Q_coords_low_speeds_low_angles.reset_index(drop=True), df_MidShoulder), axis=1) + + # Automatically compute the height of the person + pairs_up_to_shoulders = [['RHeel', 'RAnkle'], ['RAnkle', 'RKnee'], ['RKnee', 'RHip'], ['RHip', 'RShoulder'], + ['LHeel', 'LAnkle'], ['LAnkle', 'LKnee'], ['LKnee', 'LHip'], ['LHip', 'LShoulder']] + try: + rfoot, rshank, rfemur, rback, lfoot, lshank, lfemur, lback = [euclidean_distance(Q_coords_low_speeds_low_angles[pair[0]],Q_coords_low_speeds_low_angles[pair[1]]) for pair in pairs_up_to_shoulders] + except: + raise ValueError('At least one of the following markers is missing for computing the height of the person:\ + RHeel, RAnkle, RKnee, RHip, RShoulder, LHeel, LAnkle, LKnee, LHip, LShoulder.\ + Make sure that the person is entirely visible, or use a calibration file instead, or set "to_meters=false".') + if 'Head' in keypoints_names: + head = euclidean_distance(Q_coords_low_speeds_low_angles['MidShoulder'], Q_coords_low_speeds_low_angles['Head']) + else: + head = euclidean_distance(Q_coords_low_speeds_low_angles['MidShoulder'], Q_coords_low_speeds_low_angles['Nose'])*1.33 + heights = (rfoot + lfoot)/2 + (rshank + lshank)/2 + (rfemur + lfemur)/2 + (rback + lback)/2 + head + + # Remove the 20% most extreme values + height = trimmed_mean(heights, trimmed_extrema_percent=trimmed_extrema_percent) + + return height + + ## CLASSES class plotWindow(): ''' diff --git a/Pose2Sim/kinematics.py b/Pose2Sim/kinematics.py index 8e1fa26f..d3786b6a 100644 --- a/Pose2Sim/kinematics.py +++ b/Pose2Sim/kinematics.py @@ -44,7 +44,7 @@ import opensim -from Pose2Sim.common import natural_sort_key, euclidean_distance, trimmed_mean, points_to_angles +from Pose2Sim.common import natural_sort_key, euclidean_distance, trimmed_mean, points_to_angles, read_trc, best_coords_for_measurements from Pose2Sim.skeletons import * @@ -98,32 +98,6 @@ ## FUNCTIONS -def read_trc(trc_path): - ''' - Read a TRC file and extract its contents. - - INPUTS: - - trc_path (str): The path to the TRC file. - - OUTPUTS: - - tuple: A tuple containing the Q coordinates, frames column, time column, marker names, and header. - ''' - - try: - with open(trc_path, 'r') as trc_file: - header = [next(trc_file) for _ in range(5)] - markers = header[3].split('\t')[2::3] - - trc_df = pd.read_csv(trc_path, sep="\t", skiprows=4, encoding='utf-8') - frames_col, time_col = trc_df.iloc[:, 0], trc_df.iloc[:, 1] - Q_coords = trc_df.drop(trc_df.columns[[0, 1]], axis=1) - - return Q_coords, frames_col, time_col, markers, header - - except Exception as e: - raise ValueError(f"Error reading TRC file at {trc_path}: {e}") - - def get_opensim_setup_dir(): ''' Locate the OpenSim setup directory within the Pose2Sim package. @@ -731,9 +705,6 @@ def kinematics_all(config_dict): - Pose2Sim and OpenSim logs saved to files ''' - print(config_dict, '\n\n\n\n\n') - - # Read config_dict project_dir = config_dict.get('project').get('project_dir') # if batch @@ -755,12 +726,14 @@ def kinematics_all(config_dict): subject_height = config_dict.get('project').get('participant_height') subject_mass = config_dict.get('project').get('participant_mass') + fastest_frames_to_remove_percent = config_dict.get('markerAugmentation').get('fastest_frames_to_remove_percent') + large_hip_knee_angles = config_dict.get('markerAugmentation').get('large_hip_knee_angles') + trimmed_extrema_percent = config_dict.get('markerAugmentation').get('trimmed_extrema_percent') + close_to_zero_speed_m = config_dict.get('markerAugmentation').get('close_to_zero_speed_m') + remove_scaling_setup = config_dict.get('kinematics').get('remove_individual_scaling_setup') remove_IK_setup = config_dict.get('kinematics').get('remove_individual_IK_setup') - fastest_frames_to_remove_percent = config_dict.get('kinematics').get('fastest_frames_to_remove_percent') - large_hip_knee_angles = config_dict.get('kinematics').get('large_hip_knee_angles') - trimmed_extrema_percent = config_dict.get('kinematics').get('trimmed_extrema_percent') - close_to_zero_speed_m = config_dict.get('kinematics').get('close_to_zero_speed_m') + pose3d_dir = Path(project_dir) / 'pose-3d' kinematics_dir = Path(project_dir) / 'kinematics' @@ -792,6 +765,28 @@ def kinematics_all(config_dict): if subject_height is None or subject_height == 0: subject_height = [1.75] * len(trc_files) logging.warning("No subject height found in Config.toml. Using default height of 1.75m.") + elif subject_height == 'auto'.lower(): + subject_height = [] + for trc_file in trc_files: + try: + Q_coords, _, _, markers, _ = read_trc(trc_file) + Q_coords = Q_coords.loc[:, ~Q_coords.columns.str.startswith('Unnamed')] # remove unnamed columns + markers = [m.strip() for m in markers if m.strip()] # remove last \n character + + # Compute height + height = compute_height( + Q_coords, + markers, + fastest_frames_to_remove_percent=fastest_frames_to_remove_percent, + close_to_zero_speed=close_to_zero_speed, + large_hip_knee_angles=large_hip_knee_angles, + trimmed_extrema_percent=trimmed_extrema_percent + ) + logging.info(f"Subject height automatically calculated for {os.path.basename(trc_file)}: {height} m") + subject_height.append(height) + except Exception as e: + subject_height.append(1.75) + logging.warning(f"Could not compute height from {os.path.basename(trc_file)}. Error: {str(e)}. Using default height of 1.75m.") elif not type(subject_height) == list: # int or float subject_height = [subject_height] elif len(subject_height) < len(trc_files): diff --git a/Pose2Sim/markerAugmentation.py b/Pose2Sim/markerAugmentation.py index f33bd0dd..5c86b217 100644 --- a/Pose2Sim/markerAugmentation.py +++ b/Pose2Sim/markerAugmentation.py @@ -1,7 +1,6 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- - ''' ########################################################################### ## AUGMENT MARKER DATA ## @@ -30,7 +29,7 @@ from Pose2Sim.MarkerAugmenter import utilsDataman from Pose2Sim.MarkerAugmenter.utils import TRC2numpy -from Pose2Sim.common import convert_to_c3d, natural_sort_key +from Pose2Sim.common import convert_to_c3d, natural_sort_key, read_trc, compute_height ## AUTHORSHIP INFORMATION @@ -87,6 +86,11 @@ def augmentTRC(config_dict): subject_height = config_dict.get('project').get('participant_height') subject_mass = config_dict.get('project').get('participant_mass') + fastest_frames_to_remove_percent = config_dict.get('markerAugmentation').get('fastest_frames_to_remove_percent') + close_to_zero_speed = config_dict.get('markerAugmentation').get('close_to_zero_speed_m') + large_hip_knee_angles = config_dict.get('markerAugmentation').get('large_hip_knee_angles') + trimmed_extrema_percent = config_dict.get('markerAugmentation').get('trimmed_extrema_percent') + augmenterDir = os.path.dirname(utilsDataman.__file__) augmenterModelName = 'LSTM' augmenter_model = 'v0.3' @@ -106,16 +110,39 @@ def augmentTRC(config_dict): trc_files = trc_no_filtering sorted(trc_files, key=natural_sort_key) - # Get subject heights and masses + # calculate subject heights if subject_height is None or subject_height == 0: subject_height = [1.75] * len(trc_files) logging.warning("No subject height found in Config.toml. Using default height of 1.75m.") + elif subject_height == 'auto'.lower(): + subject_height = [] + for trc_file in trc_files: + try: + Q_coords, _, _, markers, _ = read_trc(trc_file) + Q_coords = Q_coords.loc[:, ~Q_coords.columns.str.startswith('Unnamed')] # remove unnamed columns + markers = [m.strip() for m in markers if m.strip()] # remove last \n character + + # Compute height + height = compute_height( + Q_coords, + markers, + fastest_frames_to_remove_percent=fastest_frames_to_remove_percent, + close_to_zero_speed=close_to_zero_speed, + large_hip_knee_angles=large_hip_knee_angles, + trimmed_extrema_percent=trimmed_extrema_percent + ) + logging.info(f"Subject height automatically calculated for {os.path.basename(trc_file)}: {height} m") + subject_height.append(height) + except Exception as e: + subject_height.append(1.75) + logging.warning(f"Could not compute height from {os.path.basename(trc_file)}. Error: {str(e)}. Using default height of 1.75m.") elif not type(subject_height) == list: # int or float subject_height = [subject_height] elif len(subject_height) < len(trc_files): logging.warning("Number of subject heights does not match number of TRC files. Missing heights are set to 1.75m.") subject_height += [1.75] * (len(trc_files) - len(subject_height)) + # Get subject masses if subject_mass is None or subject_mass == 0: subject_mass = [70] * len(trc_files) logging.warning("No subject mass found in Config.toml. Using default mass of 70kg.") @@ -290,4 +317,3 @@ def augmentTRC(config_dict): logging.info(f'Augmented trc files have been converted to c3d.') return min_y_pos -