implementing OpensimProcessing (#130)
* implementing opensimProcessing * edited in cooperating with opensimProcessing * Update scaling2IK.py * implementing opensimProcessing * Update and rename scaling2IK.py to kinematics.py * Add files via upload * Add files via upload * Update Config.toml * Update Config.toml * Update Config.toml * Update Config.toml * Update Config.toml * Update Config.toml * implementing opensim processing * OpenSim part addition * OpenSim Processing function signature adjustment * code logic and layout adjustments * opensimProcessing test enabled * Update Pose2Sim.py * Update Pose2Sim.py * docstring for opensimProcessing updated * saved folder name changed to opensim * opensim processing tests enabled * Deleted a repetitive line at opensim kinematics section
This commit is contained in:
parent
7af1584e01
commit
215c8a6e6c
@ -186,6 +186,9 @@ static_trial = ['S00_P00_Participant/S00_P00_T00_StaticTrial']
|
||||
# # At the Participant level, specify the name of the static trial folder name, e.g. ['S00_P00_T00_StaticTrial'];
|
||||
# # At the Session level, add participant subdirectory, e.g. ['S00_P00_Participant/S00_P00_T00_StaticTrial', 'S00_P01_Participant/S00_P00_T00_StaticTrial']
|
||||
opensim_bin_path = 'C:\OpenSim 4.4\bin'
|
||||
use_augmentation = false # If using augmented measurements then set it true
|
||||
load_trc_name = 'filtered' # 'default' or 'filtered', if use_augmentation = true, this line will be ignored instead using __LSTM.trc
|
||||
IK_timeRange = [] #left empty to IK full range or eg.[0.5,1.0]
|
||||
|
||||
|
||||
|
||||
|
@ -181,11 +181,14 @@
|
||||
|
||||
|
||||
# [opensim]
|
||||
# static_trial = ['S00_P00_Participant/S00_P00_T00_StaticTrial']
|
||||
# # # If this Config.toml file is at the Trial level, set to true or false (lowercase);
|
||||
# # # At the Participant level, specify the name of the static trial folder name, e.g. ['S00_P00_T00_StaticTrial'];
|
||||
# # # At the Session level, add participant subdirectory, e.g. ['S00_P00_Participant/S00_P00_T00_StaticTrial', 'S00_P01_Participant/S00_P00_T00_StaticTrial']
|
||||
# opensim_bin_path = 'C:\OpenSim 4.4\bin'
|
||||
#static_trial = ['S00_P00_Participant/S00_P00_T00_StaticTrial']
|
||||
# # If this Config.toml file is at the Trial level, set to true or false (lowercase);
|
||||
# # At the Participant level, specify the name of the static trial folder name, e.g. ['S00_P00_T00_StaticTrial'];
|
||||
# # At the Session level, add participant subdirectory, e.g. ['S00_P00_Participant/S00_P00_T00_StaticTrial', 'S00_P01_Participant/S00_P00_T00_StaticTrial']
|
||||
#opensim_bin_path = 'C:\OpenSim 4.4\bin'
|
||||
#use_augmentation = false # If using augmented measurements then set it true
|
||||
#load_trc_name = 'filtered' # 'default' or 'filtered', if use_augmentation = true, this line will be ignored instead using __LSTM.trc
|
||||
#IK_timeRange = [] #left empty to IK full range or eg.[0.5,1.0]
|
||||
|
||||
|
||||
|
||||
|
@ -19,8 +19,8 @@
|
||||
|
||||
[project]
|
||||
multi_person = true # true for trials with multiple participants. If false, only the main person in scene is analyzed (and it run much faster).
|
||||
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 = [1.72, 1.40, 1.86] # 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, 88.0] # kg # Only used for marker augmentation and scaling
|
||||
|
||||
# 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.
|
||||
@ -181,11 +181,14 @@ keypoints_to_consider = 'all' # 'all' if all points should be considered, for ex
|
||||
|
||||
|
||||
# [opensim]
|
||||
# static_trial = ['S00_P00_Participant/S00_P00_T00_StaticTrial']
|
||||
# # # If this Config.toml file is at the Trial level, set to true or false (lowercase);
|
||||
# # # At the Participant level, specify the name of the static trial folder name, e.g. ['S00_P00_T00_StaticTrial'];
|
||||
# # # At the Session level, add participant subdirectory, e.g. ['S00_P00_Participant/S00_P00_T00_StaticTrial', 'S00_P01_Participant/S00_P00_T00_StaticTrial']
|
||||
# opensim_bin_path = 'C:\OpenSim 4.4\bin'
|
||||
#static_trial = ['S00_P00_Participant/S00_P00_T00_StaticTrial']
|
||||
# # If this Config.toml file is at the Trial level, set to true or false (lowercase);
|
||||
# # At the Participant level, specify the name of the static trial folder name, e.g. ['S00_P00_T00_StaticTrial'];
|
||||
# # At the Session level, add participant subdirectory, e.g. ['S00_P00_Participant/S00_P00_T00_StaticTrial', 'S00_P01_Participant/S00_P00_T00_StaticTrial']
|
||||
#opensim_bin_path = 'C:\OpenSim 4.4\bin'
|
||||
#use_augmentation = false # If using augmented measurements then set it true
|
||||
#load_trc_name = 'filtered' # 'default' or 'filtered', if use_augmentation = true, this line will be ignored instead using __LSTM.trc
|
||||
#IK_timeRange = [] #left empty to IK full range or eg.[0.5,1.0]
|
||||
|
||||
|
||||
|
||||
|
@ -19,8 +19,8 @@
|
||||
|
||||
[project]
|
||||
multi_person = true # true for trials with multiple participants. If false, only the main person in scene is analyzed (and it run much faster).
|
||||
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 = [1.72, 1.40, 1.90] # 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, 90.0] # kg # Only used for marker augmentation and scaling
|
||||
|
||||
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.
|
||||
@ -186,6 +186,9 @@ static_trial = ['S00_P00_Participant/S00_P00_T00_StaticTrial']
|
||||
# # At the Participant level, specify the name of the static trial folder name, e.g. ['S00_P00_T00_StaticTrial'];
|
||||
# # At the Session level, add participant subdirectory, e.g. ['S00_P00_Participant/S00_P00_T00_StaticTrial', 'S00_P01_Participant/S00_P00_T00_StaticTrial']
|
||||
opensim_bin_path = 'C:\OpenSim 4.4\bin'
|
||||
use_augmentation = false # If using augmented measurements then set it true
|
||||
load_trc_name = 'filtered' # 'default' or 'filtered', if use_augmentation = true, this line will be ignored instead using __LSTM.trc
|
||||
IK_timeRange = [] #left empty to IK full range or eg.[0.5,1.0]
|
||||
|
||||
|
||||
|
||||
|
@ -53,9 +53,9 @@ output_format = 'openpose' # 'openpose', 'mmpose', 'deeplabcut', 'none' or a lis
|
||||
[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.
|
||||
# ['RWrist', 'RElbow'] list of keypoint names if you want to specify the keypoints to consider.
|
||||
approx_time_maxspeed = 'auto' # 'auto' if you want to consider the whole capture (default, slower if long sequences)
|
||||
# [10.0, 2.0, 8.0, 11.0] list of times (seconds) if you want to specify the approximate time of a clear vertical event for each camera
|
||||
# [10.0, 2.0, 8.0, 11.0] list of times in seconds, one value per camera if you want to specify the approximate time of a clear vertical event by one person standing alone in the scene
|
||||
time_range_around_maxspeed = 2.0 # Search for best correlation in the range [approx_time_maxspeed - time_range_around_maxspeed, approx_time_maxspeed + time_range_around_maxspeed]
|
||||
likelihood_threshold = 0.4 # Keypoints whose likelihood is below likelihood_threshold are filtered out
|
||||
filter_cutoff = 6 # time series are smoothed to get coherent time-lagged correlation
|
||||
@ -186,6 +186,11 @@ static_trial = ['S00_P00_Participant/S00_P00_T00_StaticTrial']
|
||||
# # At the Participant level, specify the name of the static trial folder name, e.g. ['S00_P00_T00_StaticTrial'];
|
||||
# # At the Session level, add participant subdirectory, e.g. ['S00_P00_Participant/S00_P00_T00_StaticTrial', 'S00_P01_Participant/S00_P00_T00_StaticTrial']
|
||||
opensim_bin_path = 'C:\OpenSim 4.4\bin'
|
||||
use_augmentation = false # If using augmented measurements then set it true
|
||||
load_trc_name = 'filtered' # 'default' or 'filtered', if use_augmentation = true, this line will be ignored instead using __LSTM.trc
|
||||
IK_timeRange = [] #left empty to IK full range or eg.[0.5,1.0]
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -39,6 +39,7 @@ Pose2Sim.personAssociation()
|
||||
Pose2Sim.triangulation()
|
||||
Pose2Sim.filtering()
|
||||
Pose2Sim.markerAugmentation()
|
||||
Pose2Sim.opensimProcessing()
|
||||
# Then run OpenSim (see Readme.md)
|
||||
'''
|
||||
|
||||
@ -157,6 +158,7 @@ def read_config_files(config):
|
||||
return level, config_dicts
|
||||
|
||||
|
||||
|
||||
def calibration(config=None):
|
||||
'''
|
||||
Cameras calibration from checkerboards or from qualisys files.
|
||||
@ -194,7 +196,6 @@ def calibration(config=None):
|
||||
|
||||
end = time.time()
|
||||
logging.info(f'\nCalibration took {end-start:.2f} s.\n')
|
||||
logging.shutdown()
|
||||
|
||||
|
||||
def poseEstimation(config=None):
|
||||
@ -241,8 +242,6 @@ def poseEstimation(config=None):
|
||||
elapsed = end - start
|
||||
logging.info(f'\nPose estimation took {time.strftime("%Hh%Mm%Ss", time.gmtime(elapsed))}.\n')
|
||||
|
||||
logging.shutdown()
|
||||
|
||||
|
||||
def synchronization(config=None):
|
||||
'''
|
||||
@ -287,8 +286,6 @@ def synchronization(config=None):
|
||||
elapsed = end-start
|
||||
logging.info(f'\nSynchronization took {time.strftime("%Hh%Mm%Ss", time.gmtime(elapsed))}.\n')
|
||||
|
||||
logging.shutdown()
|
||||
|
||||
|
||||
def personAssociation(config=None):
|
||||
'''
|
||||
@ -336,8 +333,6 @@ def personAssociation(config=None):
|
||||
elapsed = end-start
|
||||
logging.info(f'\nAssociating persons took {time.strftime("%Hh%Mm%Ss", time.gmtime(elapsed))}.\n')
|
||||
|
||||
logging.shutdown()
|
||||
|
||||
|
||||
def triangulation(config=None):
|
||||
'''
|
||||
@ -384,8 +379,6 @@ def triangulation(config=None):
|
||||
elapsed = end-start
|
||||
logging.info(f'\nTriangulation took {time.strftime("%Hh%Mm%Ss", time.gmtime(elapsed))}.\n')
|
||||
|
||||
logging.shutdown()
|
||||
|
||||
|
||||
def filtering(config=None):
|
||||
'''
|
||||
@ -429,8 +422,6 @@ def filtering(config=None):
|
||||
|
||||
logging.info('\n')
|
||||
|
||||
logging.shutdown()
|
||||
|
||||
|
||||
def markerAugmentation(config=None):
|
||||
'''
|
||||
@ -474,65 +465,58 @@ def markerAugmentation(config=None):
|
||||
elapsed = end-start
|
||||
logging.info(f'\nMarker augmentation took {time.strftime("%Hh%Mm%Ss", time.gmtime(elapsed))}.\n')
|
||||
|
||||
logging.shutdown()
|
||||
|
||||
|
||||
def opensimProcessing(config=None):
|
||||
'''
|
||||
Uses OpenSim to run scaling based on a static trc pose
|
||||
and inverse kinematics based on a trc motion file.
|
||||
Performing OpenSim scaling and inverse kinematics.
|
||||
Selected the 10% slowest frames from trc for scaling
|
||||
Saved as .osim and .mot
|
||||
|
||||
config can be a dictionary,
|
||||
or a the directory path of a trial, participant, or session,
|
||||
or the function can be called without an argument, in which case it the config directory is the current one.
|
||||
'''
|
||||
|
||||
raise NotImplementedError('This has not been implemented yet. \nPlease see README.md for further explanation')
|
||||
from Pose2Sim.kinematics import opensimProcessing
|
||||
|
||||
# # TODO
|
||||
# from Pose2Sim.opensimProcessing import opensim_processing_all
|
||||
# Read the configuration files
|
||||
level, config_dicts = read_config_files(config)
|
||||
|
||||
# # Determine the level at which the function is called (root:2, trial:1)
|
||||
# level, config_dicts = read_config_files(config)
|
||||
# Ensure the configuration is properly structured
|
||||
if isinstance(config, dict):
|
||||
config_dict = config_dicts[0]
|
||||
if config_dict.get('project').get('project_dir') is None:
|
||||
raise ValueError('Please specify the project directory in config_dict:\n \
|
||||
config_dict.get("project").update({"project_dir":"<YOUR_TRIAL_DIRECTORY>"})')
|
||||
|
||||
# if type(config)==dict:
|
||||
# config_dict = config_dicts[0]
|
||||
# if config_dict.get('project').get('project_dir') == None:
|
||||
# raise ValueError('Please specify the project directory in config_dict:\n \
|
||||
# config_dict.get("project").update({"project_dir":"<YOUR_TRIAL_DIRECTORY>"})')
|
||||
session_dir = os.path.realpath(os.path.join(config_dicts[0].get('project').get('project_dir'), '..'))
|
||||
setup_logging(session_dir)
|
||||
|
||||
# # Set up logging
|
||||
# session_dir = os.path.realpath(os.path.join(config_dicts[0].get('project').get('project_dir'), '..'))
|
||||
# setup_logging(session_dir)
|
||||
# Process each configuration dictionary
|
||||
for config_dict in config_dicts:
|
||||
start = time.time()
|
||||
currentDateAndTime = datetime.now()
|
||||
project_dir = os.path.realpath(config_dict.get('project').get('project_dir'))
|
||||
seq_name = os.path.basename(project_dir)
|
||||
frame_range = config_dict.get('project').get('frame_range')
|
||||
frames = ["all frames" if frame_range == [] else f"frames {frame_range[0]} to {frame_range[1]}"][0]
|
||||
|
||||
# # Batch process all trials
|
||||
# for config_dict in config_dicts:
|
||||
# currentDateAndTime = datetime.now()
|
||||
# start = time.time()
|
||||
# project_dir = os.path.realpath(config_dict.get('project').get('project_dir'))
|
||||
# seq_name = os.path.basename(project_dir)
|
||||
# frame_range = config_dict.get('project').get('frame_range')
|
||||
# frames = ["all frames" if frame_range == [] else f"frames {frame_range[0]} to {frame_range[1]}"][0]
|
||||
logging.info("\n---------------------------------------------------------------------")
|
||||
logging.info(f"OpenSim processing for {seq_name}, for {frames}.")
|
||||
logging.info(f"On {currentDateAndTime.strftime('%A %d. %B %Y, %H:%M:%S')}")
|
||||
logging.info(f"Project directory: {project_dir}")
|
||||
logging.info("---------------------------------------------------------------------\n")
|
||||
|
||||
# logging.info("\n---------------------------------------------------------------------")
|
||||
# # if static_file in project_dir:
|
||||
# # logging.info(f"Scaling model with <STATIC TRC FILE>.")
|
||||
# # else:
|
||||
# # logging.info(f"Running inverse kinematics <MOTION TRC FILE>.")
|
||||
# logging.info(f"On {currentDateAndTime.strftime('%A %d. %B %Y, %H:%M:%S')}")
|
||||
# logging.info(f"OpenSim output directory: {project_dir}")
|
||||
# logging.info("---------------------------------------------------------------------\n")
|
||||
try:
|
||||
opensimProcessing(config_dict)
|
||||
|
||||
# opensim_processing_all(config_dict)
|
||||
except Exception as e:
|
||||
logging.error(f"Error during OpenSim processing: {e}")
|
||||
continue
|
||||
|
||||
# end = time.time()
|
||||
# elapsed = end-start
|
||||
# # if static_file in project_dir:
|
||||
# # logging.info(f'Model scaling took {time.strftime("%Hh%Mm%Ss", time.gmtime(elapsed))}.\n')
|
||||
# # else:
|
||||
# # logging.info(f'Inverse kinematics took {time.strftime("%Hh%Mm%Ss", time.gmtime(elapsed))}.\n')
|
||||
|
||||
# logging.shutdown()
|
||||
end = time.time()
|
||||
elapsed = end - start
|
||||
logging.info(f'\nOpenSim processing took {time.strftime("%Hh%Mm%Ss", time.gmtime(elapsed))}.\n')
|
||||
|
||||
|
||||
def runAll(config=None, do_calibration=True, do_poseEstimation=True, do_synchronization=True, do_personAssociation=True, do_triangulation=True, do_filtering=True, do_markerAugmentation=True, do_opensimProcessing=True):
|
||||
@ -626,17 +610,17 @@ def runAll(config=None, do_calibration=True, do_poseEstimation=True, do_synchron
|
||||
logging.info('Skipping marker augmentation.')
|
||||
logging.info("\n\n=====================================================================")
|
||||
|
||||
# if do_opensimProcessing:
|
||||
# logging.info("\n\n=====================================================================")
|
||||
# logging.info('Running opensim processing.')
|
||||
# logging.info("=====================================================================")
|
||||
# opensimProcessing(config)
|
||||
# else:
|
||||
# logging.info("\n\n=====================================================================")
|
||||
# logging.info('Skipping opensim processing.')
|
||||
# logging.info("=====================================================================")
|
||||
if do_opensimProcessing:
|
||||
logging.info("\n\n=====================================================================")
|
||||
logging.info("Running OpenSim processing.")
|
||||
logging.info("=====================================================================")
|
||||
opensimProcessing(config)
|
||||
else:
|
||||
logging.info("\n\n=====================================================================")
|
||||
logging.info('Skipping OpenSim processing.')
|
||||
logging.info("\n\n=====================================================================")
|
||||
|
||||
logging.info("Pose2Sim pipeline completed.")
|
||||
end = time.time()
|
||||
elapsed = end-start
|
||||
logging.info(f'\nRUNNING ALL FUNCTIONS TOOK {time.strftime("%Hh%Mm%Ss", time.gmtime(elapsed))}.\n')
|
||||
logging.shutdown()
|
||||
|
@ -15,12 +15,14 @@
|
||||
- triangulation
|
||||
- filtering
|
||||
- marker augmentation
|
||||
- opensim processing
|
||||
- Multi-person:
|
||||
- synchronization
|
||||
- person association
|
||||
- triangulation
|
||||
- filtering
|
||||
- marker augmentation
|
||||
- opensim processing
|
||||
|
||||
- SINGLE TRIAL:
|
||||
- calibration
|
||||
@ -29,6 +31,7 @@
|
||||
- triangulation
|
||||
- filtering
|
||||
- marker augmentation
|
||||
- opensim processing
|
||||
|
||||
N.B.:
|
||||
1. Calibration from scene dimensions is not tested, as it requires the
|
||||
@ -74,6 +77,7 @@ class TestWorkflow(unittest.TestCase):
|
||||
- triangulation
|
||||
- filtering
|
||||
- marker augmentation
|
||||
- OpenSim processing
|
||||
- run all
|
||||
|
||||
N.B.: Calibration from scene dimensions is not tested, as it requires the
|
||||
@ -109,7 +113,7 @@ class TestWorkflow(unittest.TestCase):
|
||||
Pose2Sim.triangulation(config_dict)
|
||||
Pose2Sim.filtering(config_dict)
|
||||
Pose2Sim.markerAugmentation(config_dict)
|
||||
# Pose2Sim.kinematics(config_dict)
|
||||
Pose2Sim.opensimProcessing(config_dict)
|
||||
|
||||
config_dict.get("pose").update({"overwrite_pose":False})
|
||||
Pose2Sim.runAll(config_dict)
|
||||
@ -137,7 +141,7 @@ class TestWorkflow(unittest.TestCase):
|
||||
Pose2Sim.triangulation(config_dict)
|
||||
Pose2Sim.filtering(config_dict)
|
||||
Pose2Sim.markerAugmentation(config_dict)
|
||||
# Pose2Sim.kinematics(config_dict)
|
||||
Pose2Sim.opensimProcessing(config_dict)
|
||||
|
||||
# Run all
|
||||
config_dict.get("pose").update({"overwrite_pose":False})
|
||||
|
511
Pose2Sim/kinematics.py
Normal file
511
Pose2Sim/kinematics.py
Normal file
@ -0,0 +1,511 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
'''
|
||||
###########################################################################
|
||||
## KINEMATICS PROCESSING ##
|
||||
###########################################################################
|
||||
|
||||
Process kinematic data using OpenSim tools.
|
||||
|
||||
This script performs scaling, inverse kinematics, and related processing
|
||||
on 3D motion capture data (TRC files). The scaling process adjusts the
|
||||
generic model to match the subject's physical dimensions, while inverse
|
||||
kinematics computes the joint angles based on the motion data.
|
||||
|
||||
Set your parameters in Config.toml.
|
||||
|
||||
INPUTS:
|
||||
- a directory containing TRC files
|
||||
- kinematic processing parameters in Config.toml
|
||||
|
||||
OUTPUT:
|
||||
- scaled OpenSim model files (.osim)
|
||||
- joint angle data files (.mot)
|
||||
'''
|
||||
import os
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from pathlib import Path
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
from lxml import etree
|
||||
import logging
|
||||
import opensim
|
||||
|
||||
|
||||
## FUNCTIONS
|
||||
def find_config_and_pose3d(project_dir):
|
||||
"""
|
||||
Find configuration files and associated pose-3d directories in the project directory.
|
||||
|
||||
Args:
|
||||
project_dir (str): The root directory of the project.
|
||||
|
||||
Returns:
|
||||
list: A list of tuples containing the config path and the corresponding pose-3d directory.
|
||||
"""
|
||||
config_paths = []
|
||||
for root, dirs, files in os.walk(project_dir):
|
||||
if 'Config.toml' in files:
|
||||
config_path = Path(root) / 'Config.toml'
|
||||
possible_pose3d_dir = Path(root) / 'pose-3d'
|
||||
if not possible_pose3d_dir.exists():
|
||||
possible_pose3d_dir = Path(root).parent / 'pose-3d'
|
||||
if possible_pose3d_dir.exists():
|
||||
config_paths.append((config_path, possible_pose3d_dir))
|
||||
else:
|
||||
logging.warning(f"No pose-3d directory found for config: {config_path}")
|
||||
return config_paths
|
||||
|
||||
|
||||
def get_grouped_files(directory, pattern='*.trc'):
|
||||
"""
|
||||
Group TRC files by person ID or treat them as single-person if no ID is found.
|
||||
|
||||
Args:
|
||||
directory (str): The directory containing TRC files.
|
||||
pattern (str): The file pattern to search for.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary grouping TRC files by person ID.
|
||||
"""
|
||||
files = list(Path(directory).glob(pattern))
|
||||
grouped_files = defaultdict(list)
|
||||
|
||||
for file in files:
|
||||
parts = file.stem.split('_')
|
||||
if len(parts) > 2 and 'P' in parts[2]: # Multi-person file naming convention
|
||||
person_id = parts[2]
|
||||
else:
|
||||
person_id = "SinglePerson"
|
||||
grouped_files[person_id].append(file)
|
||||
|
||||
return grouped_files
|
||||
|
||||
|
||||
def process_all_groups(config_dict):
|
||||
"""
|
||||
Process all groups (single or multi-person) based on the configuration.
|
||||
|
||||
Args:
|
||||
config_dict (dict): The configuration dictionary containing project details.
|
||||
"""
|
||||
logging.info("Processing all groups in the project.")
|
||||
project_dir = config_dict.get('project', {}).get('project_dir')
|
||||
config_and_pose3d_paths = find_config_and_pose3d(project_dir)
|
||||
|
||||
for config_path, pose3d_dir in config_and_pose3d_paths:
|
||||
logging.info(f"Processing setup with config: {config_path}")
|
||||
|
||||
trc_groups = get_grouped_files(pose3d_dir)
|
||||
trial_name = Path(pose3d_dir).parent.name # Use the parent directory name as the trial name
|
||||
|
||||
for person_id, trc_files in trc_groups.items():
|
||||
filtered_trc_files = load_trc(config_dict, trc_files)
|
||||
|
||||
# Ensure output directory includes the trial name
|
||||
trial_output_dir = get_output_dir(Path(config_dict['project']['project_dir']).parent / trial_name, person_id)
|
||||
perform_scaling(config_dict, person_id, filtered_trc_files, trial_output_dir)
|
||||
perform_inverse_kinematics(config_dict, person_id, filtered_trc_files, trial_output_dir)
|
||||
|
||||
def load_trc(config_dict, trc_files):
|
||||
"""
|
||||
Load and filter TRC files according to the configuration.
|
||||
|
||||
Args:
|
||||
config_dict (dict): The configuration dictionary.
|
||||
trc_files (list): A list of TRC file paths.
|
||||
|
||||
Returns:
|
||||
list: A list of filtered TRC files based on the criteria specified in the configuration.
|
||||
"""
|
||||
opensim_config = config_dict.get('opensim', {})
|
||||
use_lstm = opensim_config.get('use_augmentation', False)
|
||||
load_trc_name = opensim_config.get('load_trc_name', 'default')
|
||||
|
||||
# Filter out any scaled TRC files
|
||||
unscaled_trc_files = [file for file in trc_files if '_scaling' not in str(file)]
|
||||
|
||||
logging.info(f"Starting TRC file filtering with criteria: use_lstm = {use_lstm}, load_trc_name = {load_trc_name}")
|
||||
logging.info(f"Initial list of TRC files: {unscaled_trc_files}")
|
||||
|
||||
# Initialize the list to store filtered TRC files
|
||||
trc_files = []
|
||||
|
||||
# Check for LSTM files if LSTM is being used
|
||||
if use_lstm:
|
||||
lstm_files = [file for file in unscaled_trc_files if '_LSTM.trc' in str(file)]
|
||||
if not lstm_files:
|
||||
raise FileNotFoundError("No LSTM TRC file found in the provided list.")
|
||||
trc_files.extend(lstm_files)
|
||||
|
||||
# Check for default or filtered TRC files
|
||||
if load_trc_name == 'default':
|
||||
default_files = [file for file in unscaled_trc_files if '_LSTM' not in str(file) and '_filt_butterworth' not in str(file)]
|
||||
trc_files.extend(default_files)
|
||||
elif load_trc_name == 'filtered':
|
||||
filtered_files = [file for file in unscaled_trc_files if '_filt_butterworth' in str(file) and '_LSTM' not in str(file)]
|
||||
trc_files.extend(filtered_files)
|
||||
|
||||
# If no TRC files are found after filtering, raise an error
|
||||
if not trc_files:
|
||||
logging.error(f"No suitable TRC files found with the specified criteria: use_lstm = {use_lstm}, load_trc_name = {load_trc_name}")
|
||||
raise FileNotFoundError(f"No suitable TRC files found in the provided list with the specified criteria: use_lstm = {use_lstm}, load_trc_name = {load_trc_name}")
|
||||
|
||||
logging.info(f"Filtered TRC files: {trc_files}")
|
||||
|
||||
return trc_files
|
||||
|
||||
|
||||
def read_trc(trc_path):
|
||||
"""
|
||||
Read a TRC file and extract its contents.
|
||||
|
||||
Args:
|
||||
trc_path (str): The path to the TRC file.
|
||||
|
||||
Returns:
|
||||
tuple: A tuple containing the Q coordinates, frames column, time column, and header.
|
||||
"""
|
||||
try:
|
||||
logging.info(f"Attempting to read TRC file: {trc_path}")
|
||||
with open(trc_path, 'r') as trc_file:
|
||||
header = [next(trc_file) for _ in range(5)]
|
||||
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, header
|
||||
except Exception as e:
|
||||
logging.error(f"Error reading TRC file at {trc_path}: {e}")
|
||||
raise
|
||||
|
||||
|
||||
def make_trc_with_Q(Q, header, trc_path):
|
||||
"""
|
||||
Write the processed Q coordinates back to a TRC file.
|
||||
|
||||
Args:
|
||||
Q (pd.DataFrame): The Q coordinates data.
|
||||
header (list): The header of the original TRC file.
|
||||
trc_path (str): Path to save the new TRC file.
|
||||
"""
|
||||
header_2_split = header[2].split('\t')
|
||||
header_2_split[2] = str(len(Q))
|
||||
header_2_split[-1] = str(len(Q))
|
||||
header[2] = '\t'.join(header_2_split) + '\n'
|
||||
|
||||
time = pd.Series(np.arange(len(Q)) / float(header_2_split[0]), name='t')
|
||||
Q.insert(0, 't', time)
|
||||
|
||||
with open(trc_path, 'w') as trc_o:
|
||||
[trc_o.write(line) for line in header]
|
||||
Q.to_csv(trc_o, sep='\t', index=True, header=None, lineterminator='\n')
|
||||
|
||||
|
||||
def get_key(config_dict):
|
||||
"""
|
||||
Determine the key for the OpenSim model and setup files based on the configuration.
|
||||
|
||||
Args:
|
||||
config_dict (dict): The configuration dictionary.
|
||||
|
||||
Returns:
|
||||
str: The key used to select the model and setup files.
|
||||
"""
|
||||
use_augmentation = config_dict.get('opensim', {}).get('use_augmentation', False)
|
||||
|
||||
if use_augmentation:
|
||||
return 'LSTM'
|
||||
|
||||
pose_model = config_dict.get('pose', {}).get('pose_model', '').upper()
|
||||
if not pose_model:
|
||||
raise ValueError(f"Invalid or missing 'pose_model' in config: {pose_model}")
|
||||
|
||||
return pose_model
|
||||
|
||||
|
||||
def get_OpenSim_Setup():
|
||||
"""
|
||||
Locate the OpenSim setup directory within the Pose2Sim package.
|
||||
|
||||
Returns:
|
||||
Path: The path to the OpenSim setup directory.
|
||||
"""
|
||||
pose2sim_path = Path(sys.modules['Pose2Sim'].__file__).resolve().parent
|
||||
setup_dir = pose2sim_path / 'OpenSim_Setup'
|
||||
return setup_dir
|
||||
|
||||
|
||||
def get_Model(config_dict):
|
||||
"""
|
||||
Retrieve the OpenSim model file path based on the configuration.
|
||||
|
||||
Args:
|
||||
config_dict (dict): The configuration dictionary.
|
||||
|
||||
Returns:
|
||||
str: The path to the OpenSim model file.
|
||||
"""
|
||||
setup_key = get_key(config_dict)
|
||||
setup_dir = get_OpenSim_Setup()
|
||||
|
||||
if setup_key == 'LSTM':
|
||||
pose_model_file = 'Model_Pose2Sim_LSTM.osim'
|
||||
elif setup_key == 'BLAZEPOSE':
|
||||
pose_model_file = 'Model_Pose2Sim_Blazepose.osim'
|
||||
elif setup_key == 'BODY_25':
|
||||
pose_model_file = 'Model_Pose2Sim_Body25.osim'
|
||||
elif setup_key == 'BODY_25B':
|
||||
pose_model_file = 'Model_Setup_Pose2Sim_Body25b.osim'
|
||||
elif setup_key == 'BODY_135':
|
||||
pose_model_file = 'Model_Pose2Sim_Body135.osim'
|
||||
elif setup_key == 'COCO_17':
|
||||
pose_model_file = 'Model_Pose2Sim_Coco17.osim'
|
||||
elif setup_key == 'COCO_133':
|
||||
pose_model_file = 'Model_Pose2Sim_Coco133.osim'
|
||||
elif setup_key == 'HALPE_26':
|
||||
pose_model_file = 'Model_Pose2Sim_Halpe26.osim'
|
||||
elif setup_key == 'HALPE_68':
|
||||
pose_model_file = 'Model_Pose2Sim_Halpe68_136.osim'
|
||||
else:
|
||||
raise ValueError(f"pose_model '{setup_key}' not found.")
|
||||
|
||||
pose_model_path = os.path.join(setup_dir, pose_model_file)
|
||||
return pose_model_path
|
||||
|
||||
|
||||
def get_Scale_Setup(config_dict):
|
||||
"""
|
||||
Retrieve the OpenSim scaling setup file path based on the configuration.
|
||||
|
||||
Args:
|
||||
config_dict (dict): The configuration dictionary.
|
||||
|
||||
Returns:
|
||||
str: The path to the OpenSim scaling setup file.
|
||||
"""
|
||||
setup_key = get_key(config_dict)
|
||||
setup_dir = get_OpenSim_Setup()
|
||||
|
||||
if setup_key == 'LSTM':
|
||||
scale_setup_file = 'Scaling_Setup_Pose2Sim_LSTM.xml'
|
||||
elif setup_key == 'BLAZEPOSE':
|
||||
scale_setup_file = 'Scaling_Setup_Pose2Sim_Blazepose.xml'
|
||||
elif setup_key == 'BODY_25':
|
||||
scale_setup_file = 'Scaling_Setup_Pose2Sim_Body25.xml'
|
||||
elif setup_key == 'BODY_25B':
|
||||
scale_setup_file = 'Scaling_Setup_Pose2Sim_Body25b.xml'
|
||||
elif setup_key == 'BODY_135':
|
||||
scale_setup_file = 'Scaling_Setup_Pose2Sim_Body135.xml'
|
||||
elif setup_key == 'COCO_17':
|
||||
scale_setup_file = 'Scaling_Setup_Pose2Sim_Coco17.xml'
|
||||
elif setup_key == 'COCO_133':
|
||||
scale_setup_file = 'Scaling_Setup_Pose2Sim_Coco133.xml'
|
||||
elif setup_key == 'HALPE_26':
|
||||
scale_setup_file = 'Scaling_Setup_Pose2Sim_Halpe26.xml'
|
||||
elif setup_key == 'HALPE_68':
|
||||
scale_setup_file = 'Scaling_Setup_Pose2Sim_Halpe68_136.xml'
|
||||
else:
|
||||
raise ValueError(f"pose_model '{setup_key}' not found.")
|
||||
|
||||
scale_setup_path = os.path.join(setup_dir, scale_setup_file)
|
||||
return scale_setup_path
|
||||
|
||||
|
||||
def get_IK_Setup(config_dict):
|
||||
"""
|
||||
Retrieve the OpenSim inverse kinematics setup file path based on the configuration.
|
||||
|
||||
Args:
|
||||
config_dict (dict): The configuration dictionary.
|
||||
|
||||
Returns:
|
||||
str: The path to the OpenSim inverse kinematics setup file.
|
||||
"""
|
||||
setup_key = get_key(config_dict)
|
||||
setup_dir = get_OpenSim_Setup()
|
||||
|
||||
if setup_key == 'LSTM':
|
||||
ik_setup_file = 'IK_Setup_Pose2Sim_LSTM.xml'
|
||||
elif setup_key == 'BLAZEPOSE':
|
||||
ik_setup_file = 'IK_Setup_Pose2Sim_Blazepose.xml'
|
||||
elif setup_key == 'BODY_25':
|
||||
ik_setup_file = 'IK_Setup_Pose2Sim_Body25.xml'
|
||||
elif setup_key == 'BODY_25B':
|
||||
ik_setup_file = 'IK_Setup_Pose2Sim_Body25b.xml'
|
||||
elif setup_key == 'BODY_135':
|
||||
ik_setup_file = 'IK_Setup_Pose2Sim_Body135.xml'
|
||||
elif setup_key == 'COCO_17':
|
||||
ik_setup_file = 'IK_Setup_Pose2Sim_Coco17.xml'
|
||||
elif setup_key == 'COCO_133':
|
||||
ik_setup_file = 'IK_Setup_Pose2Sim_Coco133.xml'
|
||||
elif setup_key == 'HALPE_26':
|
||||
ik_setup_file = 'IK_Setup_Pose2Sim_Halpe26.xml'
|
||||
elif setup_key == 'HALPE_68':
|
||||
ik_setup_file = 'IK_Setup_Pose2Sim_Halpe68_136.xml'
|
||||
else:
|
||||
raise ValueError(f"pose_model '{setup_key}' not found.")
|
||||
|
||||
ik_setup_path = os.path.join(setup_dir, ik_setup_file)
|
||||
return ik_setup_path
|
||||
|
||||
|
||||
def get_output_dir(config_dir, person_id):
|
||||
"""
|
||||
Determines the correct output directory based on the configuration and the person identifier.
|
||||
|
||||
Args:
|
||||
config_dir (Path): The root directory where the configuration file is located.
|
||||
person_id (str): Identifier for the person (e.g., 'SinglePerson', 'P1').
|
||||
|
||||
Returns:
|
||||
Path: The path where the output files should be stored.
|
||||
"""
|
||||
output_dir = config_dir / 'opensim' # Assuming 'opensim' as the default output subdirectory
|
||||
|
||||
# Append the person_id to the output directory if it's a multi-person setup
|
||||
if person_id != "SinglePerson":
|
||||
output_dir = output_dir / person_id
|
||||
|
||||
logging.debug(f"Output directory determined as: {output_dir}")
|
||||
|
||||
# Create the directory if it does not exist
|
||||
if not output_dir.exists():
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
return output_dir
|
||||
|
||||
|
||||
def perform_scaling(config_dict, person_id, trc_files, output_dir):
|
||||
"""
|
||||
Perform scaling on the TRC files according to the OpenSim configuration.
|
||||
|
||||
Args:
|
||||
config_dict (dict): The configuration dictionary.
|
||||
person_id (str): The person identifier (e.g., 'SinglePerson', 'P1').
|
||||
trc_files (list): List of TRC files to be processed.
|
||||
output_dir (Path): The directory where the output files should be saved.
|
||||
"""
|
||||
geometry_path = Path(get_OpenSim_Setup()) / 'Geometry'
|
||||
geometry_path_str = str(geometry_path)
|
||||
opensim.ModelVisualizer.addDirToGeometrySearchPaths(geometry_path_str)
|
||||
|
||||
try:
|
||||
athlete_config = config_dict.get('project', {})
|
||||
athlete_height = athlete_config.get('participant_height', -1)
|
||||
athlete_weight = athlete_config.get('participant_mass', -1)
|
||||
|
||||
if person_id == "SinglePerson":
|
||||
if not isinstance(athlete_height, float) or not isinstance(athlete_weight, float):
|
||||
raise ValueError("For a single person configuration, 'participant_height' and 'participant_mass' must be floats.")
|
||||
else:
|
||||
if person_id.startswith("P"):
|
||||
try:
|
||||
person_idx = int(person_id.replace('P', '')) - 1
|
||||
athlete_height = athlete_height[person_idx]
|
||||
athlete_weight = athlete_weight[person_idx]
|
||||
except (ValueError, IndexError) as e:
|
||||
raise ValueError(f"Error processing multi-person data for '{person_id}': {e}")
|
||||
else:
|
||||
raise ValueError(f"Unexpected person_id format: '{person_id}'")
|
||||
|
||||
logging.debug(f"Performing scaling. Output directory: {output_dir}")
|
||||
|
||||
pose_model = get_Model(config_dict)
|
||||
if not pose_model:
|
||||
raise ValueError(f"Model path not found for pose_model: {pose_model}")
|
||||
|
||||
for trc_file in trc_files:
|
||||
trc_file = Path(trc_file)
|
||||
scaling_path = get_Scale_Setup(config_dict)
|
||||
|
||||
Q_coords, _, _, header = read_trc(trc_file)
|
||||
|
||||
Q_diff = Q_coords.diff(axis=0).sum(axis=1)
|
||||
Q_diff = Q_diff[Q_diff != 0]
|
||||
min_speed_indices = Q_diff.abs().nsmallest(int(len(Q_diff) * 0.1)).index
|
||||
Q_coords_scaling = Q_coords.iloc[min_speed_indices].reset_index(drop=True)
|
||||
|
||||
trc_scaling_path = trc_file.parent / (trc_file.stem + '_scaling.trc')
|
||||
make_trc_with_Q(Q_coords_scaling, header, str(trc_scaling_path))
|
||||
|
||||
scaling_file_path = str(trc_file.parent / (trc_file.stem + '_' + Path(scaling_path).name))
|
||||
scaled_model_path = (output_dir / (trc_file.stem + '_scaled.osim')).resolve()
|
||||
scaling_tree = etree.parse(str(scaling_path))
|
||||
scaling_root = scaling_tree.getroot()
|
||||
|
||||
scaling_root[0].find('mass').text = str(athlete_weight)
|
||||
scaling_root[0].find('height').text = str(athlete_height)
|
||||
scaling_root[0].find('GenericModelMaker').find('model_file').text = str(pose_model)
|
||||
scaling_root[0].find('ModelScaler').find('marker_file').text = trc_scaling_path.name
|
||||
scaling_root[0].find('ModelScaler').find('time_range').text = '0 ' + str(Q_coords_scaling['t'].iloc[-1])
|
||||
scaling_root[0].find('ModelScaler').find('output_model_file').text = str(scaled_model_path)
|
||||
scaling_root[0].find('MarkerPlacer').find('marker_file').text = trc_scaling_path.name
|
||||
scaling_root[0].find('MarkerPlacer').find('time_range').text = '0 ' + str(Q_coords_scaling['t'].iloc[-1])
|
||||
scaling_root[0].find('MarkerPlacer').find('output_model_file').text = str(scaled_model_path)
|
||||
scaling_tree.write(scaling_file_path)
|
||||
|
||||
logging.debug(f"Running ScaleTool with scaling file: {scaling_file_path}")
|
||||
opensim.ScaleTool(scaling_file_path).run()
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"Error during scaling for {person_id}: {e}")
|
||||
raise
|
||||
|
||||
def perform_inverse_kinematics(config_dict, person_id, trc_files, output_dir):
|
||||
"""
|
||||
Perform inverse kinematics on the TRC files according to the OpenSim configuration.
|
||||
|
||||
Args:
|
||||
config_dict (dict): The configuration dictionary.
|
||||
person_id (str): The person identifier (e.g., 'SinglePerson', 'P1').
|
||||
trc_files (list): List of TRC files to be processed.
|
||||
output_dir (Path): The directory where the output files should be saved.
|
||||
"""
|
||||
try:
|
||||
logging.debug(f"Performing inverse kinematics. Output directory: {output_dir}")
|
||||
|
||||
for trc_file in trc_files:
|
||||
trc_file_path = Path(trc_file).resolve()
|
||||
scaled_model_path = Path(output_dir) / (trc_file_path.stem + '_scaled.osim')
|
||||
|
||||
ik_setup_path = get_IK_Setup(config_dict)
|
||||
Q_coords, frames_col, time_col, header = read_trc(trc_file_path)
|
||||
ik_time_range = config_dict.get('opensim', {}).get('IK_timeRange', [])
|
||||
|
||||
if not ik_time_range:
|
||||
start_time = time_col.iloc[0]
|
||||
end_time = time_col.iloc[-1]
|
||||
else:
|
||||
start_time, end_time = ik_time_range[0], ik_time_range[1]
|
||||
|
||||
ik_file_path = Path(trc_file_path.parent / (trc_file_path.stem + '_' + Path(ik_setup_path).name)).resolve()
|
||||
scaled_model_path = scaled_model_path.resolve()
|
||||
output_motion_file = Path(output_dir, trc_file_path.stem + '.mot').resolve()
|
||||
|
||||
ik_tree = etree.parse(ik_setup_path)
|
||||
ik_root = ik_tree.getroot()
|
||||
ik_root.find('.//model_file').text = str(scaled_model_path)
|
||||
ik_root.find('.//time_range').text = f'{start_time} {end_time}'
|
||||
ik_root.find('.//output_motion_file').text = str(output_motion_file)
|
||||
ik_root.find('.//marker_file').text = str(trc_file_path)
|
||||
ik_tree.write(ik_file_path)
|
||||
|
||||
logging.info(f"Running InverseKinematicsTool with TRC file: {trc_file_path}")
|
||||
if not trc_file_path.exists():
|
||||
raise FileNotFoundError(f"TRC file does not exist: {trc_file_path}")
|
||||
|
||||
logging.debug(f"Running InverseKinematicsTool with IK setup file: {ik_file_path}")
|
||||
opensim.InverseKinematicsTool(str(ik_file_path)).run()
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"Error during IK for {person_id}: {e}")
|
||||
raise
|
||||
|
||||
|
||||
def opensimProcessing(config_dict):
|
||||
logging.info("Starting OpenSim processing...")
|
||||
process_all_groups(config_dict)
|
||||
logging.info("OpenSim processing completed successfully.")
|
14
README.md
14
README.md
@ -175,9 +175,13 @@ Pose2Sim.personAssociation()
|
||||
Pose2Sim.triangulation()
|
||||
Pose2Sim.filtering()
|
||||
Pose2Sim.markerAugmentation()
|
||||
Pose2Sim.opensimProcessing()
|
||||
```
|
||||
3D results are stored as .trc files in each trial folder in the `pose-3d` directory.
|
||||
|
||||
OpenSim results are stored as scaled model .osim and .mot in each trial folder in the `opensim` directory.
|
||||
|
||||
|
||||
</br>
|
||||
|
||||
**Note:**
|
||||
@ -188,6 +192,7 @@ Pose2Sim.markerAugmentation()
|
||||
Pose2Sim.runAll(do_calibration=True, do_poseEstimation=True, do_synchronization=True, do_personAssociation=True, do_triangulation=True, do_filtering=True, do_markerAugmentation=True, do_opensimProcessing=True)
|
||||
```
|
||||
- Try the calibration tool by changing `calibration_type` to `calculate` instead of `convert` in [Config.toml](https://github.com/perfanalytics/pose2sim/blob/main/Pose2Sim/Demo_SinglePerson/Config.toml) (more info [there](#calculate-from-scratch)).
|
||||
- If the results are not convincing, refer to Section [OpenSim-kinematics](#OpenSim-kinematics) in the document.
|
||||
</br>
|
||||
|
||||
|
||||
@ -652,6 +657,15 @@ Pose2Sim.markerAugmentation()
|
||||
|
||||
</br>
|
||||
|
||||
### If kinematics results are not convicing:
|
||||
|
||||
> _***Explanation on choosing the best frames for scaling (L437-448):***_
|
||||
>
|
||||
> On difficult trials, some points are not well triangulated, which can lead to bad scaling. For example, if a point of the foot is very far from the rest of the body on some frames, scaling will consider that the foot is very large. Consequently, we need to scale only on the frames that are best triangulated. Now, how to find these best frames?
|
||||
>
|
||||
>
|
||||
> My reasoning was that the points of badly triangulated frames would go all over the place, and thus that their speeds would be fast. So I only selected the 10% slowest frames for scaling. I think that in addition, we should take the median scale factor for these frames, because we might have slow frames that are still bad. -> This last step has not been done.
|
||||
|
||||
### Command line
|
||||
Alternatively, you can use command-line tools:
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user