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 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']
|
# # 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'
|
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]
|
# [opensim]
|
||||||
# static_trial = ['S00_P00_Participant/S00_P00_T00_StaticTrial']
|
#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);
|
# # 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 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']
|
# # 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'
|
#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]
|
[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).
|
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_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] # kg # Only used for marker augmentation and scaling
|
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_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.
|
# 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]
|
# [opensim]
|
||||||
# static_trial = ['S00_P00_Participant/S00_P00_T00_StaticTrial']
|
#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);
|
# # 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 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']
|
# # 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'
|
#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]
|
[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).
|
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_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] # kg # Only used for marker augmentation and scaling
|
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_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.
|
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 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']
|
# # 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'
|
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]
|
[synchronization]
|
||||||
display_sync_plots = true # true or false (lowercase)
|
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
|
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)
|
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]
|
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
|
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
|
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 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']
|
# # 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'
|
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]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
## POSE2SIM ##
|
## POSE2SIM ##
|
||||||
###########################################################################
|
###########################################################################
|
||||||
|
|
||||||
This repository offers a way to perform markerless kinematics, and gives an
|
This repository offers a way to perform markerless kinematics, and gives an
|
||||||
example workflow from an Openpose input to an OpenSim result.
|
example workflow from an Openpose input to an OpenSim result.
|
||||||
|
|
||||||
It offers tools for:
|
It offers tools for:
|
||||||
@ -16,20 +16,20 @@ It offers tools for:
|
|||||||
- Camera synchronization,
|
- Camera synchronization,
|
||||||
- Tracking the person of interest,
|
- Tracking the person of interest,
|
||||||
- Robust triangulation,
|
- Robust triangulation,
|
||||||
- Filtration,
|
- Filtration,
|
||||||
- Marker augmentation,
|
- Marker augmentation,
|
||||||
- OpenSim scaling and inverse kinematics
|
- OpenSim scaling and inverse kinematics
|
||||||
|
|
||||||
It has been tested on Windows, Linux and MacOS, and works for any Python version >= 3.9
|
It has been tested on Windows, Linux and MacOS, and works for any Python version >= 3.9
|
||||||
|
|
||||||
Installation:
|
Installation:
|
||||||
# Open Anaconda prompt. Type:
|
# Open Anaconda prompt. Type:
|
||||||
# - conda create -n Pose2Sim python=3.9
|
# - conda create -n Pose2Sim python=3.9
|
||||||
# - conda activate Pose2Sim
|
# - conda activate Pose2Sim
|
||||||
# - conda install -c opensim-org opensim -y
|
# - conda install -c opensim-org opensim -y
|
||||||
# - pip install Pose2Sim
|
# - pip install Pose2Sim
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
# First run Pose estimation and organize your directories (see Readme.md)
|
# First run Pose estimation and organize your directories (see Readme.md)
|
||||||
from Pose2Sim import Pose2Sim
|
from Pose2Sim import Pose2Sim
|
||||||
Pose2Sim.calibration()
|
Pose2Sim.calibration()
|
||||||
@ -39,6 +39,7 @@ Pose2Sim.personAssociation()
|
|||||||
Pose2Sim.triangulation()
|
Pose2Sim.triangulation()
|
||||||
Pose2Sim.filtering()
|
Pose2Sim.filtering()
|
||||||
Pose2Sim.markerAugmentation()
|
Pose2Sim.markerAugmentation()
|
||||||
|
Pose2Sim.opensimProcessing()
|
||||||
# Then run OpenSim (see Readme.md)
|
# Then run OpenSim (see Readme.md)
|
||||||
'''
|
'''
|
||||||
|
|
||||||
@ -69,15 +70,15 @@ def setup_logging(session_dir):
|
|||||||
Create logging file and stream handlers
|
Create logging file and stream handlers
|
||||||
'''
|
'''
|
||||||
|
|
||||||
logging.basicConfig(format='%(message)s', level=logging.INFO,
|
logging.basicConfig(format='%(message)s', level=logging.INFO,
|
||||||
handlers = [logging.handlers.TimedRotatingFileHandler(os.path.join(session_dir, 'logs.txt'), when='D', interval=7), logging.StreamHandler()])
|
handlers = [logging.handlers.TimedRotatingFileHandler(os.path.join(session_dir, 'logs.txt'), when='D', interval=7), logging.StreamHandler()])
|
||||||
|
|
||||||
|
|
||||||
def recursive_update(dict_to_update, dict_with_new_values):
|
def recursive_update(dict_to_update, dict_with_new_values):
|
||||||
'''
|
'''
|
||||||
Update nested dictionaries without overwriting existing keys in any level of nesting
|
Update nested dictionaries without overwriting existing keys in any level of nesting
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
dict_to_update = {'key': {'key_1': 'val_1', 'key_2': 'val_2'}}
|
dict_to_update = {'key': {'key_1': 'val_1', 'key_2': 'val_2'}}
|
||||||
dict_with_new_values = {'key': {'key_1': 'val_1_new'}}
|
dict_with_new_values = {'key': {'key_1': 'val_1_new'}}
|
||||||
returns {'key': {'key_1': 'val_1_new', 'key_2': 'val_2'}}
|
returns {'key': {'key_1': 'val_1_new', 'key_2': 'val_2'}}
|
||||||
@ -111,7 +112,7 @@ def determine_level(config_dir):
|
|||||||
|
|
||||||
def read_config_files(config):
|
def read_config_files(config):
|
||||||
'''
|
'''
|
||||||
Read Root and Trial configuration files,
|
Read Root and Trial configuration files,
|
||||||
and output a dictionary with all the parameters.
|
and output a dictionary with all the parameters.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
@ -123,9 +124,9 @@ def read_config_files(config):
|
|||||||
config_dict.get("project").update({"project_dir":"<YOUR_PROJECT_DIRECTORY>"})')
|
config_dict.get("project").update({"project_dir":"<YOUR_PROJECT_DIRECTORY>"})')
|
||||||
else:
|
else:
|
||||||
# if launched without an argument, config == None, else it is the path to the config directory
|
# if launched without an argument, config == None, else it is the path to the config directory
|
||||||
config_dir = ['.' if config == None else config][0]
|
config_dir = ['.' if config == None else config][0]
|
||||||
level = determine_level(config_dir)
|
level = determine_level(config_dir)
|
||||||
|
|
||||||
# Trial level
|
# Trial level
|
||||||
if level == 1: # Trial
|
if level == 1: # Trial
|
||||||
try:
|
try:
|
||||||
@ -138,7 +139,7 @@ def read_config_files(config):
|
|||||||
session_config_dict = toml.load(os.path.join(config_dir, 'Config.toml'))
|
session_config_dict = toml.load(os.path.join(config_dir, 'Config.toml'))
|
||||||
session_config_dict.get("project").update({"project_dir":config_dir})
|
session_config_dict.get("project").update({"project_dir":config_dir})
|
||||||
config_dicts = [session_config_dict]
|
config_dicts = [session_config_dict]
|
||||||
|
|
||||||
# Root level
|
# Root level
|
||||||
if level == 2:
|
if level == 2:
|
||||||
session_config_dict = toml.load(os.path.join(config_dir, 'Config.toml'))
|
session_config_dict = toml.load(os.path.join(config_dir, 'Config.toml'))
|
||||||
@ -157,10 +158,11 @@ def read_config_files(config):
|
|||||||
return level, config_dicts
|
return level, config_dicts
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def calibration(config=None):
|
def calibration(config=None):
|
||||||
'''
|
'''
|
||||||
Cameras calibration from checkerboards or from qualisys files.
|
Cameras calibration from checkerboards or from qualisys files.
|
||||||
|
|
||||||
config can be a dictionary,
|
config can be a dictionary,
|
||||||
or a the directory path of a trial, participant, or session,
|
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.
|
or the function can be called without an argument, in which case it the config directory is the current one.
|
||||||
@ -178,9 +180,9 @@ def calibration(config=None):
|
|||||||
config_dict.get("project").update({"project_dir":session_dir})
|
config_dict.get("project").update({"project_dir":session_dir})
|
||||||
|
|
||||||
# Set up logging
|
# Set up logging
|
||||||
setup_logging(session_dir)
|
setup_logging(session_dir)
|
||||||
currentDateAndTime = datetime.now()
|
currentDateAndTime = datetime.now()
|
||||||
|
|
||||||
# Run calibration
|
# Run calibration
|
||||||
calib_dir = [os.path.join(session_dir, c) for c in os.listdir(session_dir) if os.path.isdir(os.path.join(session_dir, c)) and 'calib' in c.lower()][0]
|
calib_dir = [os.path.join(session_dir, c) for c in os.listdir(session_dir) if os.path.isdir(os.path.join(session_dir, c)) and 'calib' in c.lower()][0]
|
||||||
logging.info("\n---------------------------------------------------------------------")
|
logging.info("\n---------------------------------------------------------------------")
|
||||||
@ -189,23 +191,22 @@ def calibration(config=None):
|
|||||||
logging.info(f"Calibration directory: {calib_dir}")
|
logging.info(f"Calibration directory: {calib_dir}")
|
||||||
logging.info("---------------------------------------------------------------------\n")
|
logging.info("---------------------------------------------------------------------\n")
|
||||||
start = time.time()
|
start = time.time()
|
||||||
|
|
||||||
calibrate_cams_all(config_dict)
|
calibrate_cams_all(config_dict)
|
||||||
|
|
||||||
end = time.time()
|
end = time.time()
|
||||||
logging.info(f'\nCalibration took {end-start:.2f} s.\n')
|
logging.info(f'\nCalibration took {end-start:.2f} s.\n')
|
||||||
logging.shutdown()
|
|
||||||
|
|
||||||
|
|
||||||
def poseEstimation(config=None):
|
def poseEstimation(config=None):
|
||||||
'''
|
'''
|
||||||
Estimate pose using RTMLib
|
Estimate pose using RTMLib
|
||||||
|
|
||||||
config can be a dictionary,
|
config can be a dictionary,
|
||||||
or a the directory path of a trial, participant, or session,
|
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.
|
or the function can be called without an argument, in which case it the config directory is the current one.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
from Pose2Sim.poseEstimation import rtm_estimator # The name of the function might change
|
from Pose2Sim.poseEstimation import rtm_estimator # The name of the function might change
|
||||||
|
|
||||||
level, config_dicts = read_config_files(config)
|
level, config_dicts = read_config_files(config)
|
||||||
@ -234,25 +235,23 @@ def poseEstimation(config=None):
|
|||||||
logging.info(f"On {currentDateAndTime.strftime('%A %d. %B %Y, %H:%M:%S')}")
|
logging.info(f"On {currentDateAndTime.strftime('%A %d. %B %Y, %H:%M:%S')}")
|
||||||
logging.info(f"Project directory: {project_dir}")
|
logging.info(f"Project directory: {project_dir}")
|
||||||
logging.info("---------------------------------------------------------------------\n")
|
logging.info("---------------------------------------------------------------------\n")
|
||||||
|
|
||||||
rtm_estimator(config_dict)
|
rtm_estimator(config_dict)
|
||||||
|
|
||||||
end = time.time()
|
end = time.time()
|
||||||
elapsed = end - start
|
elapsed = end - start
|
||||||
logging.info(f'\nPose estimation took {time.strftime("%Hh%Mm%Ss", time.gmtime(elapsed))}.\n')
|
logging.info(f'\nPose estimation took {time.strftime("%Hh%Mm%Ss", time.gmtime(elapsed))}.\n')
|
||||||
|
|
||||||
logging.shutdown()
|
|
||||||
|
|
||||||
|
|
||||||
def synchronization(config=None):
|
def synchronization(config=None):
|
||||||
'''
|
'''
|
||||||
Synchronize cameras if needed.
|
Synchronize cameras if needed.
|
||||||
|
|
||||||
config can be a dictionary,
|
config can be a dictionary,
|
||||||
or a the directory path of a trial, participant, or session,
|
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.
|
or the function can be called without an argument, in which case it the config directory is the current one.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
# Import the function
|
# Import the function
|
||||||
from Pose2Sim.synchronization import synchronize_cams_all
|
from Pose2Sim.synchronization import synchronize_cams_all
|
||||||
|
|
||||||
@ -267,7 +266,7 @@ def synchronization(config=None):
|
|||||||
|
|
||||||
# Set up logging
|
# Set up logging
|
||||||
session_dir = os.path.realpath(os.path.join(config_dicts[0].get('project').get('project_dir'), '..'))
|
session_dir = os.path.realpath(os.path.join(config_dicts[0].get('project').get('project_dir'), '..'))
|
||||||
setup_logging(session_dir)
|
setup_logging(session_dir)
|
||||||
|
|
||||||
# Batch process all trials
|
# Batch process all trials
|
||||||
for config_dict in config_dicts:
|
for config_dict in config_dicts:
|
||||||
@ -280,26 +279,24 @@ def synchronization(config=None):
|
|||||||
logging.info(f"On {currentDateAndTime.strftime('%A %d. %B %Y, %H:%M:%S')}")
|
logging.info(f"On {currentDateAndTime.strftime('%A %d. %B %Y, %H:%M:%S')}")
|
||||||
logging.info(f"Project directory: {project_dir}")
|
logging.info(f"Project directory: {project_dir}")
|
||||||
logging.info("---------------------------------------------------------------------\n")
|
logging.info("---------------------------------------------------------------------\n")
|
||||||
|
|
||||||
synchronize_cams_all(config_dict)
|
|
||||||
|
|
||||||
end = time.time()
|
|
||||||
elapsed = end-start
|
|
||||||
logging.info(f'\nSynchronization took {time.strftime("%Hh%Mm%Ss", time.gmtime(elapsed))}.\n')
|
|
||||||
|
|
||||||
logging.shutdown()
|
synchronize_cams_all(config_dict)
|
||||||
|
|
||||||
|
end = time.time()
|
||||||
|
elapsed = end-start
|
||||||
|
logging.info(f'\nSynchronization took {time.strftime("%Hh%Mm%Ss", time.gmtime(elapsed))}.\n')
|
||||||
|
|
||||||
|
|
||||||
def personAssociation(config=None):
|
def personAssociation(config=None):
|
||||||
'''
|
'''
|
||||||
Tracking one or several persons of interest.
|
Tracking one or several persons of interest.
|
||||||
Needs a calibration file.
|
Needs a calibration file.
|
||||||
|
|
||||||
config can be a dictionary,
|
config can be a dictionary,
|
||||||
or a the directory path of a trial, participant, or session,
|
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.
|
or the function can be called without an argument, in which case it the config directory is the current one.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
from Pose2Sim.personAssociation import track_2d_all
|
from Pose2Sim.personAssociation import track_2d_all
|
||||||
|
|
||||||
# Determine the level at which the function is called (root:2, trial:1)
|
# Determine the level at which the function is called (root:2, trial:1)
|
||||||
@ -313,7 +310,7 @@ def personAssociation(config=None):
|
|||||||
|
|
||||||
# Set up logging
|
# Set up logging
|
||||||
session_dir = os.path.realpath(os.path.join(config_dicts[0].get('project').get('project_dir'), '..'))
|
session_dir = os.path.realpath(os.path.join(config_dicts[0].get('project').get('project_dir'), '..'))
|
||||||
setup_logging(session_dir)
|
setup_logging(session_dir)
|
||||||
|
|
||||||
# Batch process all trials
|
# Batch process all trials
|
||||||
for config_dict in config_dicts:
|
for config_dict in config_dicts:
|
||||||
@ -329,20 +326,18 @@ def personAssociation(config=None):
|
|||||||
logging.info(f"On {currentDateAndTime.strftime('%A %d. %B %Y, %H:%M:%S')}")
|
logging.info(f"On {currentDateAndTime.strftime('%A %d. %B %Y, %H:%M:%S')}")
|
||||||
logging.info(f"Project directory: {project_dir}")
|
logging.info(f"Project directory: {project_dir}")
|
||||||
logging.info("---------------------------------------------------------------------\n")
|
logging.info("---------------------------------------------------------------------\n")
|
||||||
|
|
||||||
track_2d_all(config_dict)
|
track_2d_all(config_dict)
|
||||||
|
|
||||||
end = time.time()
|
end = time.time()
|
||||||
elapsed = end-start
|
elapsed = end-start
|
||||||
logging.info(f'\nAssociating persons took {time.strftime("%Hh%Mm%Ss", time.gmtime(elapsed))}.\n')
|
logging.info(f'\nAssociating persons took {time.strftime("%Hh%Mm%Ss", time.gmtime(elapsed))}.\n')
|
||||||
|
|
||||||
logging.shutdown()
|
|
||||||
|
|
||||||
|
|
||||||
def triangulation(config=None):
|
def triangulation(config=None):
|
||||||
'''
|
'''
|
||||||
Robust triangulation of 2D points coordinates.
|
Robust triangulation of 2D points coordinates.
|
||||||
|
|
||||||
config can be a dictionary,
|
config can be a dictionary,
|
||||||
or a the directory path of a trial, participant, or session,
|
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.
|
or the function can be called without an argument, in which case it the config directory is the current one.
|
||||||
@ -361,7 +356,7 @@ def triangulation(config=None):
|
|||||||
|
|
||||||
# Set up logging
|
# Set up logging
|
||||||
session_dir = os.path.realpath(os.path.join(config_dicts[0].get('project').get('project_dir'), '..'))
|
session_dir = os.path.realpath(os.path.join(config_dicts[0].get('project').get('project_dir'), '..'))
|
||||||
setup_logging(session_dir)
|
setup_logging(session_dir)
|
||||||
|
|
||||||
# Batch process all trials
|
# Batch process all trials
|
||||||
for config_dict in config_dicts:
|
for config_dict in config_dicts:
|
||||||
@ -377,20 +372,18 @@ def triangulation(config=None):
|
|||||||
logging.info(f"On {currentDateAndTime.strftime('%A %d. %B %Y, %H:%M:%S')}")
|
logging.info(f"On {currentDateAndTime.strftime('%A %d. %B %Y, %H:%M:%S')}")
|
||||||
logging.info(f"Project directory: {project_dir}")
|
logging.info(f"Project directory: {project_dir}")
|
||||||
logging.info("---------------------------------------------------------------------\n")
|
logging.info("---------------------------------------------------------------------\n")
|
||||||
|
|
||||||
triangulate_all(config_dict)
|
triangulate_all(config_dict)
|
||||||
|
|
||||||
end = time.time()
|
end = time.time()
|
||||||
elapsed = end-start
|
elapsed = end-start
|
||||||
logging.info(f'\nTriangulation took {time.strftime("%Hh%Mm%Ss", time.gmtime(elapsed))}.\n')
|
logging.info(f'\nTriangulation took {time.strftime("%Hh%Mm%Ss", time.gmtime(elapsed))}.\n')
|
||||||
|
|
||||||
logging.shutdown()
|
|
||||||
|
|
||||||
|
|
||||||
def filtering(config=None):
|
def filtering(config=None):
|
||||||
'''
|
'''
|
||||||
Filter trc 3D coordinates.
|
Filter trc 3D coordinates.
|
||||||
|
|
||||||
config can be a dictionary,
|
config can be a dictionary,
|
||||||
or a the directory path of a trial, participant, or session,
|
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.
|
or the function can be called without an argument, in which case it the config directory is the current one.
|
||||||
@ -418,25 +411,23 @@ def filtering(config=None):
|
|||||||
seq_name = os.path.basename(project_dir)
|
seq_name = os.path.basename(project_dir)
|
||||||
frame_range = config_dict.get('project').get('frame_range')
|
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]
|
frames = ["all frames" if frame_range == [] else f"frames {frame_range[0]} to {frame_range[1]}"][0]
|
||||||
|
|
||||||
logging.info("\n---------------------------------------------------------------------")
|
logging.info("\n---------------------------------------------------------------------")
|
||||||
logging.info(f"Filtering 3D coordinates for {seq_name}, for {frames}.")
|
logging.info(f"Filtering 3D coordinates for {seq_name}, for {frames}.")
|
||||||
logging.info(f"On {currentDateAndTime.strftime('%A %d. %B %Y, %H:%M:%S')}")
|
logging.info(f"On {currentDateAndTime.strftime('%A %d. %B %Y, %H:%M:%S')}")
|
||||||
logging.info(f"Project directory: {project_dir}\n")
|
logging.info(f"Project directory: {project_dir}\n")
|
||||||
logging.info("---------------------------------------------------------------------\n")
|
logging.info("---------------------------------------------------------------------\n")
|
||||||
|
|
||||||
filter_all(config_dict)
|
filter_all(config_dict)
|
||||||
|
|
||||||
logging.info('\n')
|
logging.info('\n')
|
||||||
|
|
||||||
logging.shutdown()
|
|
||||||
|
|
||||||
|
|
||||||
def markerAugmentation(config=None):
|
def markerAugmentation(config=None):
|
||||||
'''
|
'''
|
||||||
Augment trc 3D coordinates.
|
Augment trc 3D coordinates.
|
||||||
Estimate the position of 43 additional markers.
|
Estimate the position of 43 additional markers.
|
||||||
|
|
||||||
config can be a dictionary,
|
config can be a dictionary,
|
||||||
or a the directory path of a trial, participant, or session,
|
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.
|
or the function can be called without an argument, in which case it the config directory is the current one.
|
||||||
@ -471,73 +462,66 @@ def markerAugmentation(config=None):
|
|||||||
augmentTRC(config_dict)
|
augmentTRC(config_dict)
|
||||||
|
|
||||||
end = time.time()
|
end = time.time()
|
||||||
elapsed = end-start
|
elapsed = end-start
|
||||||
logging.info(f'\nMarker augmentation took {time.strftime("%Hh%Mm%Ss", time.gmtime(elapsed))}.\n')
|
logging.info(f'\nMarker augmentation took {time.strftime("%Hh%Mm%Ss", time.gmtime(elapsed))}.\n')
|
||||||
|
|
||||||
logging.shutdown()
|
|
||||||
|
|
||||||
|
|
||||||
def opensimProcessing(config=None):
|
def opensimProcessing(config=None):
|
||||||
'''
|
'''
|
||||||
Uses OpenSim to run scaling based on a static trc pose
|
Performing OpenSim scaling and inverse kinematics.
|
||||||
and inverse kinematics based on a trc motion file.
|
Selected the 10% slowest frames from trc for scaling
|
||||||
|
Saved as .osim and .mot
|
||||||
|
|
||||||
config can be a dictionary,
|
config can be a dictionary,
|
||||||
or a the directory path of a trial, participant, or session,
|
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.
|
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')
|
|
||||||
|
|
||||||
# # TODO
|
|
||||||
# from Pose2Sim.opensimProcessing import opensim_processing_all
|
|
||||||
|
|
||||||
# # Determine the level at which the function is called (root:2, trial:1)
|
|
||||||
# level, config_dicts = read_config_files(config)
|
|
||||||
|
|
||||||
# if type(config)==dict:
|
from Pose2Sim.kinematics import opensimProcessing
|
||||||
# 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>"})')
|
|
||||||
|
|
||||||
# # Set up logging
|
# Read the configuration files
|
||||||
# session_dir = os.path.realpath(os.path.join(config_dicts[0].get('project').get('project_dir'), '..'))
|
level, config_dicts = read_config_files(config)
|
||||||
# setup_logging(session_dir)
|
|
||||||
|
|
||||||
# # Batch process all trials
|
# Ensure the configuration is properly structured
|
||||||
# for config_dict in config_dicts:
|
if isinstance(config, dict):
|
||||||
# currentDateAndTime = datetime.now()
|
config_dict = config_dicts[0]
|
||||||
# start = time.time()
|
if config_dict.get('project').get('project_dir') is None:
|
||||||
# project_dir = os.path.realpath(config_dict.get('project').get('project_dir'))
|
raise ValueError('Please specify the project directory in config_dict:\n \
|
||||||
# seq_name = os.path.basename(project_dir)
|
config_dict.get("project").update({"project_dir":"<YOUR_TRIAL_DIRECTORY>"})')
|
||||||
# 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---------------------------------------------------------------------")
|
session_dir = os.path.realpath(os.path.join(config_dicts[0].get('project').get('project_dir'), '..'))
|
||||||
# # if static_file in project_dir:
|
setup_logging(session_dir)
|
||||||
# # logging.info(f"Scaling model with <STATIC TRC FILE>.")
|
|
||||||
# # else:
|
# Process each configuration dictionary
|
||||||
# # logging.info(f"Running inverse kinematics <MOTION TRC FILE>.")
|
for config_dict in config_dicts:
|
||||||
# logging.info(f"On {currentDateAndTime.strftime('%A %d. %B %Y, %H:%M:%S')}")
|
start = time.time()
|
||||||
# logging.info(f"OpenSim output directory: {project_dir}")
|
currentDateAndTime = datetime.now()
|
||||||
# logging.info("---------------------------------------------------------------------\n")
|
project_dir = os.path.realpath(config_dict.get('project').get('project_dir'))
|
||||||
|
seq_name = os.path.basename(project_dir)
|
||||||
# opensim_processing_all(config_dict)
|
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]
|
||||||
# end = time.time()
|
|
||||||
# elapsed = end-start
|
logging.info("\n---------------------------------------------------------------------")
|
||||||
# # if static_file in project_dir:
|
logging.info(f"OpenSim processing for {seq_name}, for {frames}.")
|
||||||
# # logging.info(f'Model scaling took {time.strftime("%Hh%Mm%Ss", time.gmtime(elapsed))}.\n')
|
logging.info(f"On {currentDateAndTime.strftime('%A %d. %B %Y, %H:%M:%S')}")
|
||||||
# # else:
|
logging.info(f"Project directory: {project_dir}")
|
||||||
# # logging.info(f'Inverse kinematics took {time.strftime("%Hh%Mm%Ss", time.gmtime(elapsed))}.\n')
|
logging.info("---------------------------------------------------------------------\n")
|
||||||
|
|
||||||
# logging.shutdown()
|
try:
|
||||||
|
opensimProcessing(config_dict)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Error during OpenSim processing: {e}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
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):
|
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):
|
||||||
'''
|
'''
|
||||||
Run all functions at once. Beware that Synchronization, personAssociation, and markerAugmentation are not always necessary,
|
Run all functions at once. Beware that Synchronization, personAssociation, and markerAugmentation are not always necessary,
|
||||||
and may even lead to worse results. Think carefully before running all.
|
and may even lead to worse results. Think carefully before running all.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
@ -545,7 +529,7 @@ def runAll(config=None, do_calibration=True, do_poseEstimation=True, do_synchron
|
|||||||
# Set up logging
|
# Set up logging
|
||||||
level, config_dicts = read_config_files(config)
|
level, config_dicts = read_config_files(config)
|
||||||
session_dir = os.path.realpath(os.path.join(config_dicts[0].get('project').get('project_dir'), '..'))
|
session_dir = os.path.realpath(os.path.join(config_dicts[0].get('project').get('project_dir'), '..'))
|
||||||
setup_logging(session_dir)
|
setup_logging(session_dir)
|
||||||
|
|
||||||
currentDateAndTime = datetime.now()
|
currentDateAndTime = datetime.now()
|
||||||
start = time.time()
|
start = time.time()
|
||||||
@ -561,7 +545,7 @@ def runAll(config=None, do_calibration=True, do_poseEstimation=True, do_synchron
|
|||||||
logging.info('Running calibration...')
|
logging.info('Running calibration...')
|
||||||
logging.info("=====================================================================")
|
logging.info("=====================================================================")
|
||||||
calibration(config)
|
calibration(config)
|
||||||
else:
|
else:
|
||||||
logging.info("\n\n=====================================================================")
|
logging.info("\n\n=====================================================================")
|
||||||
logging.info('Skipping calibration.')
|
logging.info('Skipping calibration.')
|
||||||
logging.info("=====================================================================")
|
logging.info("=====================================================================")
|
||||||
@ -605,7 +589,7 @@ def runAll(config=None, do_calibration=True, do_poseEstimation=True, do_synchron
|
|||||||
logging.info("\n\n=====================================================================")
|
logging.info("\n\n=====================================================================")
|
||||||
logging.info('Skipping triangulation.')
|
logging.info('Skipping triangulation.')
|
||||||
logging.info("=====================================================================")
|
logging.info("=====================================================================")
|
||||||
|
|
||||||
if do_filtering:
|
if do_filtering:
|
||||||
logging.info("\n\n=====================================================================")
|
logging.info("\n\n=====================================================================")
|
||||||
logging.info('Running filtering...')
|
logging.info('Running filtering...')
|
||||||
@ -625,18 +609,18 @@ def runAll(config=None, do_calibration=True, do_poseEstimation=True, do_synchron
|
|||||||
logging.info("\n\n=====================================================================")
|
logging.info("\n\n=====================================================================")
|
||||||
logging.info('Skipping marker augmentation.')
|
logging.info('Skipping marker augmentation.')
|
||||||
logging.info("\n\n=====================================================================")
|
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()
|
end = time.time()
|
||||||
elapsed = end-start
|
elapsed = end-start
|
||||||
logging.info(f'\nRUNNING ALL FUNCTIONS TOOK {time.strftime("%Hh%Mm%Ss", time.gmtime(elapsed))}.\n')
|
logging.info(f'\nRUNNING ALL FUNCTIONS TOOK {time.strftime("%Hh%Mm%Ss", time.gmtime(elapsed))}.\n')
|
||||||
logging.shutdown()
|
|
||||||
|
@ -15,12 +15,14 @@
|
|||||||
- triangulation
|
- triangulation
|
||||||
- filtering
|
- filtering
|
||||||
- marker augmentation
|
- marker augmentation
|
||||||
|
- opensim processing
|
||||||
- Multi-person:
|
- Multi-person:
|
||||||
- synchronization
|
- synchronization
|
||||||
- person association
|
- person association
|
||||||
- triangulation
|
- triangulation
|
||||||
- filtering
|
- filtering
|
||||||
- marker augmentation
|
- marker augmentation
|
||||||
|
- opensim processing
|
||||||
|
|
||||||
- SINGLE TRIAL:
|
- SINGLE TRIAL:
|
||||||
- calibration
|
- calibration
|
||||||
@ -29,6 +31,7 @@
|
|||||||
- triangulation
|
- triangulation
|
||||||
- filtering
|
- filtering
|
||||||
- marker augmentation
|
- marker augmentation
|
||||||
|
- opensim processing
|
||||||
|
|
||||||
N.B.:
|
N.B.:
|
||||||
1. Calibration from scene dimensions is not tested, as it requires the
|
1. Calibration from scene dimensions is not tested, as it requires the
|
||||||
@ -74,6 +77,7 @@ class TestWorkflow(unittest.TestCase):
|
|||||||
- triangulation
|
- triangulation
|
||||||
- filtering
|
- filtering
|
||||||
- marker augmentation
|
- marker augmentation
|
||||||
|
- OpenSim processing
|
||||||
- run all
|
- run all
|
||||||
|
|
||||||
N.B.: Calibration from scene dimensions is not tested, as it requires the
|
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.triangulation(config_dict)
|
||||||
Pose2Sim.filtering(config_dict)
|
Pose2Sim.filtering(config_dict)
|
||||||
Pose2Sim.markerAugmentation(config_dict)
|
Pose2Sim.markerAugmentation(config_dict)
|
||||||
# Pose2Sim.kinematics(config_dict)
|
Pose2Sim.opensimProcessing(config_dict)
|
||||||
|
|
||||||
config_dict.get("pose").update({"overwrite_pose":False})
|
config_dict.get("pose").update({"overwrite_pose":False})
|
||||||
Pose2Sim.runAll(config_dict)
|
Pose2Sim.runAll(config_dict)
|
||||||
@ -137,7 +141,7 @@ class TestWorkflow(unittest.TestCase):
|
|||||||
Pose2Sim.triangulation(config_dict)
|
Pose2Sim.triangulation(config_dict)
|
||||||
Pose2Sim.filtering(config_dict)
|
Pose2Sim.filtering(config_dict)
|
||||||
Pose2Sim.markerAugmentation(config_dict)
|
Pose2Sim.markerAugmentation(config_dict)
|
||||||
# Pose2Sim.kinematics(config_dict)
|
Pose2Sim.opensimProcessing(config_dict)
|
||||||
|
|
||||||
# Run all
|
# Run all
|
||||||
config_dict.get("pose").update({"overwrite_pose":False})
|
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.triangulation()
|
||||||
Pose2Sim.filtering()
|
Pose2Sim.filtering()
|
||||||
Pose2Sim.markerAugmentation()
|
Pose2Sim.markerAugmentation()
|
||||||
|
Pose2Sim.opensimProcessing()
|
||||||
```
|
```
|
||||||
3D results are stored as .trc files in each trial folder in the `pose-3d` directory.
|
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>
|
</br>
|
||||||
|
|
||||||
**Note:**
|
**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)
|
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)).
|
- 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>
|
</br>
|
||||||
|
|
||||||
|
|
||||||
@ -652,6 +657,15 @@ Pose2Sim.markerAugmentation()
|
|||||||
|
|
||||||
</br>
|
</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
|
### Command line
|
||||||
Alternatively, you can use command-line tools:
|
Alternatively, you can use command-line tools:
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user