beta test opensim scaling and kinematics with edits
This commit is contained in:
parent
e321228a75
commit
65e675ab64
@ -136,7 +136,6 @@ calibration_type = 'convert' # 'convert' or 'calculate'
|
|||||||
|
|
||||||
|
|
||||||
[triangulation]
|
[triangulation]
|
||||||
reorder_trc = false # only checked if multi_person analysis
|
|
||||||
reproj_error_threshold_triangulation = 15 # px
|
reproj_error_threshold_triangulation = 15 # px
|
||||||
likelihood_threshold_triangulation= 0.3
|
likelihood_threshold_triangulation= 0.3
|
||||||
min_cameras_for_triangulation = 2
|
min_cameras_for_triangulation = 2
|
||||||
@ -181,14 +180,10 @@ make_c3d = true # save triangulated data in c3d format in addition to trc
|
|||||||
|
|
||||||
|
|
||||||
[opensim]
|
[opensim]
|
||||||
static_trial = ['S00_P00_Participant/S00_P00_T00_StaticTrial']
|
use_augmentation = true # true or false (lowercase) # Set to true if you want to use the model with augmented markers
|
||||||
# # If this Config.toml file is at the Trial level, set to true or false (lowercase);
|
right_left_symmetry = true # true or false (lowercase) # Set to false only if you have good reasons to think the participant is not symmetrical (e.g. prosthetic limb)
|
||||||
# # At the Participant level, specify the name of the static trial folder name, e.g. ['S00_P00_T00_StaticTrial'];
|
remove_individual_scaling_setup = true # true or false (lowercase) # If true, the individual scaling setup files are removed to avoid cluttering
|
||||||
# # At the Session level, add participant subdirectory, e.g. ['S00_P00_Participant/S00_P00_T00_StaticTrial', 'S00_P01_Participant/S00_P00_T00_StaticTrial']
|
remove_individual_IK_setup = true # true or false (lowercase) # If true, the individual IK setup files are removed to avoid cluttering
|
||||||
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]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -204,7 +199,7 @@ IK_timeRange = [] #left empty to IK full range or eg.[0.5,1.0]
|
|||||||
# Check your model hierarchy with: for pre, _, node in RenderTree(model):
|
# Check your model hierarchy with: for pre, _, node in RenderTree(model):
|
||||||
# print(f'{pre}{node.name} id={node.id}')
|
# print(f'{pre}{node.name} id={node.id}')
|
||||||
[pose.CUSTOM]
|
[pose.CUSTOM]
|
||||||
name = "CHip"
|
name = "Hip"
|
||||||
id = "19"
|
id = "19"
|
||||||
[[pose.CUSTOM.children]]
|
[[pose.CUSTOM.children]]
|
||||||
name = "RHip"
|
name = "RHip"
|
||||||
|
@ -136,7 +136,6 @@
|
|||||||
|
|
||||||
|
|
||||||
# [triangulation]
|
# [triangulation]
|
||||||
# reorder_trc = false # only checked if multi_person analysis
|
|
||||||
# reproj_error_threshold_triangulation = 15 # px
|
# reproj_error_threshold_triangulation = 15 # px
|
||||||
# likelihood_threshold_triangulation= 0.3
|
# likelihood_threshold_triangulation= 0.3
|
||||||
# min_cameras_for_triangulation = 2
|
# min_cameras_for_triangulation = 2
|
||||||
@ -181,14 +180,10 @@
|
|||||||
|
|
||||||
|
|
||||||
# [opensim]
|
# [opensim]
|
||||||
#static_trial = ['S00_P00_Participant/S00_P00_T00_StaticTrial']
|
# use_augmentation = true # true or false (lowercase) # Set to true if you want to use the model with augmented markers
|
||||||
# # If this Config.toml file is at the Trial level, set to true or false (lowercase);
|
# right_left_symmetry = true # true or false (lowercase) # Set to false only if you have good reasons to think the participant is not symmetrical (e.g. prosthetic limb)
|
||||||
# # At the Participant level, specify the name of the static trial folder name, e.g. ['S00_P00_T00_StaticTrial'];
|
# remove_individual_scaling_setup = true # true or false (lowercase) # If true, the individual scaling setup files are removed to avoid cluttering
|
||||||
# # At the Session level, add participant subdirectory, e.g. ['S00_P00_Participant/S00_P00_T00_StaticTrial', 'S00_P01_Participant/S00_P00_T00_StaticTrial']
|
# remove_individual_IK_setup = true # true or false (lowercase) # If true, the individual IK setup files are removed to avoid cluttering
|
||||||
#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]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -204,7 +199,7 @@
|
|||||||
# # Check your model hierarchy with: for pre, _, node in RenderTree(model):
|
# # Check your model hierarchy with: for pre, _, node in RenderTree(model):
|
||||||
# # print(f'{pre}{node.name} id={node.id}')
|
# # print(f'{pre}{node.name} id={node.id}')
|
||||||
# [pose.CUSTOM]
|
# [pose.CUSTOM]
|
||||||
# name = "CHip"
|
# name = "Hip"
|
||||||
# id = "19"
|
# id = "19"
|
||||||
# [[pose.CUSTOM.children]]
|
# [[pose.CUSTOM.children]]
|
||||||
# name = "RHip"
|
# name = "RHip"
|
||||||
|
@ -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, 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_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, 88.0] # kg # Only used for marker augmentation and scaling
|
participant_mass = [70.0, 63.5] # 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.
|
||||||
@ -136,7 +136,6 @@ keypoints_to_consider = 'all' # 'all' if all points should be considered, for ex
|
|||||||
|
|
||||||
|
|
||||||
# [triangulation]
|
# [triangulation]
|
||||||
# reorder_trc = false # only checked if multi_person analysis
|
|
||||||
# reproj_error_threshold_triangulation = 15 # px
|
# reproj_error_threshold_triangulation = 15 # px
|
||||||
# likelihood_threshold_triangulation= 0.3
|
# likelihood_threshold_triangulation= 0.3
|
||||||
# min_cameras_for_triangulation = 2
|
# min_cameras_for_triangulation = 2
|
||||||
@ -181,14 +180,10 @@ keypoints_to_consider = 'all' # 'all' if all points should be considered, for ex
|
|||||||
|
|
||||||
|
|
||||||
# [opensim]
|
# [opensim]
|
||||||
#static_trial = ['S00_P00_Participant/S00_P00_T00_StaticTrial']
|
# use_augmentation = true # true or false (lowercase) # Set to true if you want to use the model with augmented markers
|
||||||
# # If this Config.toml file is at the Trial level, set to true or false (lowercase);
|
# right_left_symmetry = true # true or false (lowercase) # Set to false only if you have good reasons to think the participant is not symmetrical (e.g. prosthetic limb)
|
||||||
# # At the Participant level, specify the name of the static trial folder name, e.g. ['S00_P00_T00_StaticTrial'];
|
# remove_individual_scaling_setup = true # true or false (lowercase) # If true, the individual scaling setup files are removed to avoid cluttering
|
||||||
# # At the Session level, add participant subdirectory, e.g. ['S00_P00_Participant/S00_P00_T00_StaticTrial', 'S00_P01_Participant/S00_P00_T00_StaticTrial']
|
# remove_individual_IK_setup = true # true or false (lowercase) # If true, the individual IK setup files are removed to avoid cluttering
|
||||||
#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]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -204,7 +199,7 @@ keypoints_to_consider = 'all' # 'all' if all points should be considered, for ex
|
|||||||
# # Check your model hierarchy with: for pre, _, node in RenderTree(model):
|
# # Check your model hierarchy with: for pre, _, node in RenderTree(model):
|
||||||
# # print(f'{pre}{node.name} id={node.id}')
|
# # print(f'{pre}{node.name} id={node.id}')
|
||||||
# [pose.CUSTOM]
|
# [pose.CUSTOM]
|
||||||
# name = "CHip"
|
# name = "Hip"
|
||||||
# id = "19"
|
# id = "19"
|
||||||
# [[pose.CUSTOM.children]]
|
# [[pose.CUSTOM.children]]
|
||||||
# name = "RHip"
|
# name = "RHip"
|
||||||
|
@ -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, 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_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, 90.0] # kg # Only used for marker augmentation and scaling
|
participant_mass = [70.0, 63.5] # 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.
|
||||||
@ -136,7 +136,6 @@ calibration_type = 'convert' # 'convert' or 'calculate'
|
|||||||
|
|
||||||
|
|
||||||
[triangulation]
|
[triangulation]
|
||||||
reorder_trc = false # only checked if multi_person analysis
|
|
||||||
reproj_error_threshold_triangulation = 15 # px
|
reproj_error_threshold_triangulation = 15 # px
|
||||||
likelihood_threshold_triangulation= 0.3
|
likelihood_threshold_triangulation= 0.3
|
||||||
min_cameras_for_triangulation = 2
|
min_cameras_for_triangulation = 2
|
||||||
@ -181,14 +180,10 @@ make_c3d = true # save triangulated data in c3d format in addition to trc
|
|||||||
|
|
||||||
|
|
||||||
[opensim]
|
[opensim]
|
||||||
static_trial = ['S00_P00_Participant/S00_P00_T00_StaticTrial']
|
use_augmentation = true # true or false (lowercase) # Set to true if you want to use the model with augmented markers
|
||||||
# # If this Config.toml file is at the Trial level, set to true or false (lowercase);
|
right_left_symmetry = true # true or false (lowercase) # Set to false only if you have good reasons to think the participant is not symmetrical (e.g. prosthetic limb)
|
||||||
# # At the Participant level, specify the name of the static trial folder name, e.g. ['S00_P00_T00_StaticTrial'];
|
remove_individual_scaling_setup = true # true or false (lowercase) # If true, the individual scaling setup files are removed to avoid cluttering
|
||||||
# # At the Session level, add participant subdirectory, e.g. ['S00_P00_Participant/S00_P00_T00_StaticTrial', 'S00_P01_Participant/S00_P00_T00_StaticTrial']
|
remove_individual_IK_setup = true # true or false (lowercase) # If true, the individual IK setup files are removed to avoid cluttering
|
||||||
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]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -204,7 +199,7 @@ IK_timeRange = [] #left empty to IK full range or eg.[0.5,1.0]
|
|||||||
# Check your model hierarchy with: for pre, _, node in RenderTree(model):
|
# Check your model hierarchy with: for pre, _, node in RenderTree(model):
|
||||||
# print(f'{pre}{node.name} id={node.id}')
|
# print(f'{pre}{node.name} id={node.id}')
|
||||||
[pose.CUSTOM]
|
[pose.CUSTOM]
|
||||||
name = "CHip"
|
name = "Hip"
|
||||||
id = "19"
|
id = "19"
|
||||||
[[pose.CUSTOM.children]]
|
[[pose.CUSTOM.children]]
|
||||||
name = "RHip"
|
name = "RHip"
|
||||||
|
@ -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 the keypoints to consider.
|
# ['RWrist', 'RElbow'] list of keypoint names if you want to specify keypoints with a sharp vertical motion.
|
||||||
approx_time_maxspeed = 'auto' # 'auto' if you want to consider the whole capture (default, slower if long sequences)
|
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 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
|
# [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
|
||||||
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
|
||||||
@ -136,7 +136,6 @@ calibration_type = 'convert' # 'convert' or 'calculate'
|
|||||||
|
|
||||||
|
|
||||||
[triangulation]
|
[triangulation]
|
||||||
reorder_trc = false # only checked if multi_person analysis
|
|
||||||
reproj_error_threshold_triangulation = 15 # px
|
reproj_error_threshold_triangulation = 15 # px
|
||||||
likelihood_threshold_triangulation= 0.3
|
likelihood_threshold_triangulation= 0.3
|
||||||
min_cameras_for_triangulation = 2
|
min_cameras_for_triangulation = 2
|
||||||
@ -180,17 +179,11 @@ make_c3d = true # also save triangulated data in c3d format
|
|||||||
make_c3d = true # save triangulated data in c3d format in addition to trc
|
make_c3d = true # save triangulated data in c3d format in addition to trc
|
||||||
|
|
||||||
|
|
||||||
[opensim]
|
[kinematics]
|
||||||
static_trial = ['S00_P00_Participant/S00_P00_T00_StaticTrial']
|
use_augmentation = true # true or false (lowercase) # Set to true if you want to use the model with augmented markers
|
||||||
# # If this Config.toml file is at the Trial level, set to true or false (lowercase);
|
right_left_symmetry = true # true or false (lowercase) # Set to false only if you have good reasons to think the participant is not symmetrical (e.g. prosthetic limb)
|
||||||
# # At the Participant level, specify the name of the static trial folder name, e.g. ['S00_P00_T00_StaticTrial'];
|
remove_individual_scaling_setup = true # true or false (lowercase) # If true, the individual scaling setup files are removed to avoid cluttering
|
||||||
# # At the Session level, add participant subdirectory, e.g. ['S00_P00_Participant/S00_P00_T00_StaticTrial', 'S00_P01_Participant/S00_P00_T00_StaticTrial']
|
remove_individual_IK_setup = true # true or false (lowercase) # If true, the individual IK setup files are removed to avoid cluttering
|
||||||
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]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -206,7 +199,7 @@ IK_timeRange = [] #left empty to IK full range or eg.[0.5,1.0]
|
|||||||
# Check your model hierarchy with: for pre, _, node in RenderTree(model):
|
# Check your model hierarchy with: for pre, _, node in RenderTree(model):
|
||||||
# print(f'{pre}{node.name} id={node.id}')
|
# print(f'{pre}{node.name} id={node.id}')
|
||||||
[pose.CUSTOM]
|
[pose.CUSTOM]
|
||||||
name = "CHip"
|
name = "Hip"
|
||||||
id = "19"
|
id = "19"
|
||||||
[[pose.CUSTOM.children]]
|
[[pose.CUSTOM.children]]
|
||||||
name = "RHip"
|
name = "RHip"
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" ?>
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
<OpenSimDocument Version="40000">
|
<OpenSimDocument Version="40000">
|
||||||
<Model name="Pose2Sim_AlphaPose">
|
<Model name="Pose2Sim_RTMPose">
|
||||||
<!--The model's ground reference frame.-->
|
<!--The model's ground reference frame.-->
|
||||||
<Ground name="ground">
|
<Ground name="ground">
|
||||||
<!--The geometry used to display the axes of this Frame.-->
|
<!--The geometry used to display the axes of this Frame.-->
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" ?>
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
<OpenSimDocument Version="40000">
|
<OpenSimDocument Version="40000">
|
||||||
<Model name="Pose2Sim_AlphaPose">
|
<Model name="Pose2Sim_RTMPose">
|
||||||
<!--The model's ground reference frame.-->
|
<!--The model's ground reference frame.-->
|
||||||
<Ground name="ground">
|
<Ground name="ground">
|
||||||
<!--The geometry used to display the axes of this Frame.-->
|
<!--The geometry used to display the axes of this Frame.-->
|
||||||
@ -6808,7 +6808,7 @@
|
|||||||
<!--Path to a Component that satisfies the Socket 'parent_frame' of type PhysicalFrame (description: The frame to which this station is fixed.).-->
|
<!--Path to a Component that satisfies the Socket 'parent_frame' of type PhysicalFrame (description: The frame to which this station is fixed.).-->
|
||||||
<socket_parent_frame>/bodyset/pelvis</socket_parent_frame>
|
<socket_parent_frame>/bodyset/pelvis</socket_parent_frame>
|
||||||
<!--The fixed location of the station expressed in its parent frame.-->
|
<!--The fixed location of the station expressed in its parent frame.-->
|
||||||
<location>-0.063927399999999995 0 0</location>
|
<location>-0.063927399999999995 -0.081343112945556642 0</location>
|
||||||
</Marker>
|
</Marker>
|
||||||
<Marker name="LHip">
|
<Marker name="LHip">
|
||||||
<!--Path to a Component that satisfies the Socket 'parent_frame' of type PhysicalFrame (description: The frame to which this station is fixed.).-->
|
<!--Path to a Component that satisfies the Socket 'parent_frame' of type PhysicalFrame (description: The frame to which this station is fixed.).-->
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" ?>
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
<OpenSimDocument Version="40000">
|
<OpenSimDocument Version="40000">
|
||||||
<Model name="Pose2Sim_AlphaPose">
|
<Model name="Pose2Sim_RTMPose">
|
||||||
<!--The model's ground reference frame.-->
|
<!--The model's ground reference frame.-->
|
||||||
<Ground name="ground">
|
<Ground name="ground">
|
||||||
<!--The geometry used to display the axes of this Frame.-->
|
<!--The geometry used to display the axes of this Frame.-->
|
||||||
|
@ -539,7 +539,7 @@ def kinematics(config=None):
|
|||||||
|
|
||||||
end = time.time()
|
end = time.time()
|
||||||
elapsed = end - start
|
elapsed = end - start
|
||||||
logging.info(f'\nOpenSim scaling and inverse kinematics took {time.strftime("%Hh%Mm%Ss", time.gmtime(elapsed))}.\n')
|
logging.info(f'OpenSim scaling and inverse kinematics 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_kinematics=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_kinematics=True):
|
||||||
|
@ -219,11 +219,11 @@ def euclidean_distance(q1, q2):
|
|||||||
|
|
||||||
INPUTS:
|
INPUTS:
|
||||||
- q1: list of N_dimensional coordinates of point
|
- q1: list of N_dimensional coordinates of point
|
||||||
|
or list of N points of N_dimensional coordinates
|
||||||
- q2: idem
|
- q2: idem
|
||||||
|
|
||||||
OUTPUTS:
|
OUTPUTS:
|
||||||
- euc_dist: float. Euclidian distance between q1 and q2
|
- euc_dist: float. Euclidian distance between q1 and q2
|
||||||
|
|
||||||
'''
|
'''
|
||||||
|
|
||||||
q1 = np.array(q1)
|
q1 = np.array(q1)
|
||||||
@ -233,11 +233,40 @@ def euclidean_distance(q1, q2):
|
|||||||
dist = np.empty_like(dist)
|
dist = np.empty_like(dist)
|
||||||
dist[...] = np.inf
|
dist[...] = np.inf
|
||||||
|
|
||||||
|
if len(dist.shape)==1:
|
||||||
euc_dist = np.sqrt(np.nansum( [d**2 for d in dist]))
|
euc_dist = np.sqrt(np.nansum( [d**2 for d in dist]))
|
||||||
|
else:
|
||||||
|
euc_dist = np.sqrt(np.nansum( [d**2 for d in dist], axis=1))
|
||||||
|
|
||||||
return euc_dist
|
return euc_dist
|
||||||
|
|
||||||
|
|
||||||
|
def trimmed_mean(arr, trimmed_percent=0.5):
|
||||||
|
'''
|
||||||
|
Trimmed mean calculation for an array.
|
||||||
|
|
||||||
|
INPUTS:
|
||||||
|
- arr (np.array): The input array.
|
||||||
|
- trimmed_percent (float): The percentage of values to be trimmed from both ends.
|
||||||
|
|
||||||
|
OUTPUTS:
|
||||||
|
- float: The trimmed mean of the array.
|
||||||
|
'''
|
||||||
|
|
||||||
|
# Sort the array
|
||||||
|
sorted_arr = np.sort(arr)
|
||||||
|
|
||||||
|
# Determine the indices for the 25th and 75th percentiles (if trimmed_percent = 0.5)
|
||||||
|
lower_idx = int(len(sorted_arr) * (trimmed_percent/2))
|
||||||
|
upper_idx = int(len(sorted_arr) * (1 - trimmed_percent/2))
|
||||||
|
|
||||||
|
# Slice the array to exclude the 25% lowest and highest values
|
||||||
|
trimmed_arr = sorted_arr[lower_idx:upper_idx]
|
||||||
|
|
||||||
|
# Return the mean of the remaining values
|
||||||
|
return np.mean(trimmed_arr)
|
||||||
|
|
||||||
|
|
||||||
def world_to_camera_persp(r, t):
|
def world_to_camera_persp(r, t):
|
||||||
'''
|
'''
|
||||||
Converts rotation R and translation T
|
Converts rotation R and translation T
|
||||||
@ -364,6 +393,7 @@ def natural_sort_key(s):
|
|||||||
Sorts list of strings with numbers in natural order (alphabetical and numerical)
|
Sorts list of strings with numbers in natural order (alphabetical and numerical)
|
||||||
Example: ['item_1', 'item_2', 'item_10', 'stuff_1']
|
Example: ['item_1', 'item_2', 'item_10', 'stuff_1']
|
||||||
'''
|
'''
|
||||||
|
s=str(s)
|
||||||
return [int(c) if c.isdigit() else c.lower() for c in re.split(r'(\d+)', s)]
|
return [int(c) if c.isdigit() else c.lower() for c in re.split(r'(\d+)', s)]
|
||||||
|
|
||||||
|
|
||||||
|
@ -24,352 +24,213 @@ OUTPUT:
|
|||||||
- scaled OpenSim model files (.osim)
|
- scaled OpenSim model files (.osim)
|
||||||
- joint angle data files (.mot)
|
- joint angle data files (.mot)
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
## INIT
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from collections import defaultdict
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
import logging
|
import logging
|
||||||
|
from anytree import PreOrderIter
|
||||||
|
|
||||||
import opensim
|
import opensim
|
||||||
|
|
||||||
|
from Pose2Sim.common import natural_sort_key, euclidean_distance, trimmed_mean
|
||||||
|
from Pose2Sim.skeletons import *
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## AUTHORSHIP INFORMATION
|
||||||
|
__author__ = "Ivan Sun, David Pagnon"
|
||||||
|
__copyright__ = "Copyright 2021, Pose2Sim"
|
||||||
|
__credits__ = ["Ivan Sun", "David Pagnon"]
|
||||||
|
__license__ = "BSD 3-Clause License"
|
||||||
|
__version__ = "0.10.0"
|
||||||
|
__maintainer__ = "David Pagnon"
|
||||||
|
__email__ = "contact@david-pagnon.com"
|
||||||
|
__status__ = "Development"
|
||||||
|
|
||||||
|
|
||||||
## FUNCTIONS
|
## 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):
|
def read_trc(trc_path):
|
||||||
"""
|
'''
|
||||||
Read a TRC file and extract its contents.
|
Read a TRC file and extract its contents.
|
||||||
|
|
||||||
Args:
|
INPUTS:
|
||||||
trc_path (str): The path to the TRC file.
|
- trc_path (str): The path to the TRC file.
|
||||||
|
|
||||||
|
OUTPUTS:
|
||||||
|
- tuple: A tuple containing the Q coordinates, frames column, time column, and header.
|
||||||
|
'''
|
||||||
|
|
||||||
Returns:
|
|
||||||
tuple: A tuple containing the Q coordinates, frames column, time column, and header.
|
|
||||||
"""
|
|
||||||
try:
|
try:
|
||||||
logging.info(f"Attempting to read TRC file: {trc_path}")
|
|
||||||
with open(trc_path, 'r') as trc_file:
|
with open(trc_path, 'r') as trc_file:
|
||||||
header = [next(trc_file) for _ in range(5)]
|
header = [next(trc_file) for _ in range(5)]
|
||||||
|
markers = header[3].split('\t')[2::3][:-1]
|
||||||
|
|
||||||
trc_df = pd.read_csv(trc_path, sep="\t", skiprows=4, encoding='utf-8')
|
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]
|
frames_col, time_col = trc_df.iloc[:, 0], trc_df.iloc[:, 1]
|
||||||
Q_coords = trc_df.drop(trc_df.columns[[0, 1]], axis=1)
|
Q_coords = trc_df.drop(trc_df.columns[[0, 1, -1]], axis=1)
|
||||||
return Q_coords, frames_col, time_col, header
|
|
||||||
|
return Q_coords, frames_col, time_col, markers, header
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"Error reading TRC file at {trc_path}: {e}")
|
logging.error(f"Error reading TRC file at {trc_path}: {e}")
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
def make_trc_with_Q(Q, header, trc_path):
|
def get_opensim_setup_dir():
|
||||||
"""
|
'''
|
||||||
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.
|
Locate the OpenSim setup directory within the Pose2Sim package.
|
||||||
|
|
||||||
Returns:
|
OUTPUTS:
|
||||||
Path: The path to the OpenSim setup directory.
|
- Path: The path to the OpenSim setup directory.
|
||||||
"""
|
'''
|
||||||
|
|
||||||
pose2sim_path = Path(sys.modules['Pose2Sim'].__file__).resolve().parent
|
pose2sim_path = Path(sys.modules['Pose2Sim'].__file__).resolve().parent
|
||||||
setup_dir = pose2sim_path / 'OpenSim_Setup'
|
setup_dir = pose2sim_path / 'OpenSim_Setup'
|
||||||
return setup_dir
|
return setup_dir
|
||||||
|
|
||||||
|
|
||||||
def get_Model(config_dict):
|
def get_model_path(model_name, osim_setup_dir):
|
||||||
"""
|
'''
|
||||||
Retrieve the OpenSim model file path based on the configuration.
|
Retrieve the path of the OpenSim model file.
|
||||||
|
|
||||||
Args:
|
INPUTS:
|
||||||
config_dict (dict): The configuration dictionary.
|
- model_name (str): Name of the model
|
||||||
|
- osim_setup_dir (Path): Path to the OpenSim setup directory.
|
||||||
|
|
||||||
Returns:
|
OUTPUTS:
|
||||||
str: The path to the OpenSim model file.
|
- pose_model_path: (Path) Path to the OpenSim model file.
|
||||||
"""
|
'''
|
||||||
setup_key = get_key(config_dict)
|
|
||||||
setup_dir = get_OpenSim_Setup()
|
|
||||||
|
|
||||||
if setup_key == 'LSTM':
|
if model_name == 'BODY_25B':
|
||||||
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'
|
pose_model_file = 'Model_Setup_Pose2Sim_Body25b.osim'
|
||||||
elif setup_key == 'BODY_135':
|
elif model_name == 'BODY_25':
|
||||||
|
pose_model_file = 'Model_Pose2Sim_Body25.osim'
|
||||||
|
elif model_name == 'BODY_135':
|
||||||
pose_model_file = 'Model_Pose2Sim_Body135.osim'
|
pose_model_file = 'Model_Pose2Sim_Body135.osim'
|
||||||
elif setup_key == 'COCO_17':
|
elif model_name == 'BLAZEPOSE':
|
||||||
pose_model_file = 'Model_Pose2Sim_Coco17.osim'
|
pose_model_file = 'Model_Pose2Sim_Blazepose.osim'
|
||||||
elif setup_key == 'COCO_133':
|
elif model_name == 'HALPE_26':
|
||||||
pose_model_file = 'Model_Pose2Sim_Coco133.osim'
|
|
||||||
elif setup_key == 'HALPE_26':
|
|
||||||
pose_model_file = 'Model_Pose2Sim_Halpe26.osim'
|
pose_model_file = 'Model_Pose2Sim_Halpe26.osim'
|
||||||
elif setup_key == 'HALPE_68':
|
elif model_name == 'HALPE_68' or model_name == 'HALPE_136':
|
||||||
pose_model_file = 'Model_Pose2Sim_Halpe68_136.osim'
|
pose_model_file = 'Model_Pose2Sim_Halpe68_136.osim'
|
||||||
|
elif model_name == 'COCO_133':
|
||||||
|
pose_model_file = 'Model_Pose2Sim_Coco133.osim'
|
||||||
|
# elif model_name == 'COCO' or model_name == 'MPII':
|
||||||
|
# pose_model_file = 'Model_Pose2Sim_Coco.osim'
|
||||||
|
elif model_name == 'COCO_17':
|
||||||
|
pose_model_file = 'Model_Pose2Sim_Coco17.osim'
|
||||||
|
elif model_name == 'LSTM':
|
||||||
|
pose_model_file = 'Model_Pose2Sim_LSTM.osim'
|
||||||
else:
|
else:
|
||||||
raise ValueError(f"pose_model '{setup_key}' not found.")
|
raise ValueError(f"Pose model '{model_name}' not found.")
|
||||||
|
|
||||||
pose_model_path = os.path.join(setup_dir, pose_model_file)
|
unscaled_model_path = osim_setup_dir / pose_model_file
|
||||||
return pose_model_path
|
return unscaled_model_path
|
||||||
|
|
||||||
|
|
||||||
def get_Scale_Setup(config_dict):
|
def get_scaling_setup(model_name, osim_setup_dir):
|
||||||
"""
|
'''
|
||||||
Retrieve the OpenSim scaling setup file path based on the configuration.
|
Retrieve the OpenSim scaling setup file path.
|
||||||
|
|
||||||
Args:
|
INPUTS:
|
||||||
config_dict (dict): The configuration dictionary.
|
- model_name (str): Name of the model
|
||||||
|
- osim_setup_dir (Path): Path to the OpenSim setup directory.
|
||||||
|
|
||||||
Returns:
|
OUTPUTS:
|
||||||
str: The path to the OpenSim scaling setup file.
|
- scaling_setup_path: (Path) Path to the OpenSim scaling setup file.
|
||||||
"""
|
'''
|
||||||
setup_key = get_key(config_dict)
|
|
||||||
setup_dir = get_OpenSim_Setup()
|
|
||||||
|
|
||||||
if setup_key == 'LSTM':
|
if model_name == 'BODY_25B':
|
||||||
scale_setup_file = 'Scaling_Setup_Pose2Sim_LSTM.xml'
|
scaling_setup_file = 'Scaling_Setup_Pose2Sim_Body25b.xml'
|
||||||
elif setup_key == 'BLAZEPOSE':
|
elif model_name == 'BODY_25':
|
||||||
scale_setup_file = 'Scaling_Setup_Pose2Sim_Blazepose.xml'
|
scaling_setup_file = 'Scaling_Setup_Pose2Sim_Body25.xml'
|
||||||
elif setup_key == 'BODY_25':
|
elif model_name == 'BODY_135':
|
||||||
scale_setup_file = 'Scaling_Setup_Pose2Sim_Body25.xml'
|
scaling_setup_file = 'Scaling_Setup_Pose2Sim_Body135.xml'
|
||||||
elif setup_key == 'BODY_25B':
|
elif model_name == 'BLAZEPOSE':
|
||||||
scale_setup_file = 'Scaling_Setup_Pose2Sim_Body25b.xml'
|
scaling_setup_file = 'Scaling_Setup_Pose2Sim_Blazepose.xml'
|
||||||
elif setup_key == 'BODY_135':
|
elif model_name == 'HALPE_26':
|
||||||
scale_setup_file = 'Scaling_Setup_Pose2Sim_Body135.xml'
|
scaling_setup_file = 'Scaling_Setup_Pose2Sim_Halpe26.xml'
|
||||||
elif setup_key == 'COCO_17':
|
elif model_name == 'HALPE_68' or model_name == 'HALPE_136':
|
||||||
scale_setup_file = 'Scaling_Setup_Pose2Sim_Coco17.xml'
|
scaling_setup_file = 'Scaling_Setup_Pose2Sim_Halpe68_136.xml'
|
||||||
elif setup_key == 'COCO_133':
|
elif model_name == 'COCO_133':
|
||||||
scale_setup_file = 'Scaling_Setup_Pose2Sim_Coco133.xml'
|
scaling_setup_file = 'Scaling_Setup_Pose2Sim_Coco133.xml'
|
||||||
elif setup_key == 'HALPE_26':
|
# elif model_name == 'COCO' or model_name == 'MPII':
|
||||||
scale_setup_file = 'Scaling_Setup_Pose2Sim_Halpe26.xml'
|
# scaling_setup_file = 'Scaling_Setup_Pose2Sim_Coco.xml'
|
||||||
elif setup_key == 'HALPE_68':
|
elif model_name == 'COCO_17':
|
||||||
scale_setup_file = 'Scaling_Setup_Pose2Sim_Halpe68_136.xml'
|
scaling_setup_file = 'Scaling_Setup_Pose2Sim_Coco17.xml'
|
||||||
|
elif model_name == 'LSTM':
|
||||||
|
scaling_setup_file = 'Scaling_Setup_Pose2Sim_LSTM.xml'
|
||||||
else:
|
else:
|
||||||
raise ValueError(f"pose_model '{setup_key}' not found.")
|
raise ValueError(f"Pose model '{model_name}' not found.")
|
||||||
|
|
||||||
scale_setup_path = os.path.join(setup_dir, scale_setup_file)
|
scaling_setup_path = osim_setup_dir / scaling_setup_file
|
||||||
return scale_setup_path
|
return scaling_setup_path
|
||||||
|
|
||||||
|
|
||||||
def get_IK_Setup(config_dict):
|
def get_IK_Setup(model_name, osim_setup_dir):
|
||||||
"""
|
'''
|
||||||
Retrieve the OpenSim inverse kinematics setup file path based on the configuration.
|
Retrieve the OpenSim inverse kinematics setup file path.
|
||||||
|
|
||||||
Args:
|
INPUTS:
|
||||||
config_dict (dict): The configuration dictionary.
|
- model_name (str): Name of the model
|
||||||
|
- osim_setup_dir (Path): Path to the OpenSim setup directory.
|
||||||
|
|
||||||
Returns:
|
OUTPUTS:
|
||||||
str: The path to the OpenSim inverse kinematics setup file.
|
- ik_setup_path: (Path) Path to the OpenSim IK setup file.
|
||||||
"""
|
'''
|
||||||
setup_key = get_key(config_dict)
|
|
||||||
setup_dir = get_OpenSim_Setup()
|
|
||||||
|
|
||||||
if setup_key == 'LSTM':
|
if model_name == 'BODY_25B':
|
||||||
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'
|
ik_setup_file = 'IK_Setup_Pose2Sim_Body25b.xml'
|
||||||
elif setup_key == 'BODY_135':
|
elif model_name == 'BODY_25':
|
||||||
|
ik_setup_file = 'IK_Setup_Pose2Sim_Body25.xml'
|
||||||
|
elif model_name == 'BODY_135':
|
||||||
ik_setup_file = 'IK_Setup_Pose2Sim_Body135.xml'
|
ik_setup_file = 'IK_Setup_Pose2Sim_Body135.xml'
|
||||||
elif setup_key == 'COCO_17':
|
elif model_name == 'BLAZEPOSE':
|
||||||
ik_setup_file = 'IK_Setup_Pose2Sim_Coco17.xml'
|
ik_setup_file = 'IK_Setup_Pose2Sim_Blazepose.xml'
|
||||||
elif setup_key == 'COCO_133':
|
elif model_name == 'HALPE_26':
|
||||||
ik_setup_file = 'IK_Setup_Pose2Sim_Coco133.xml'
|
|
||||||
elif setup_key == 'HALPE_26':
|
|
||||||
ik_setup_file = 'IK_Setup_Pose2Sim_Halpe26.xml'
|
ik_setup_file = 'IK_Setup_Pose2Sim_Halpe26.xml'
|
||||||
elif setup_key == 'HALPE_68':
|
elif model_name == 'HALPE_68' or model_name == 'HALPE_136':
|
||||||
ik_setup_file = 'IK_Setup_Pose2Sim_Halpe68_136.xml'
|
ik_setup_file = 'IK_Setup_Pose2Sim_Halpe68_136.xml'
|
||||||
|
elif model_name == 'COCO_133':
|
||||||
|
ik_setup_file = 'IK_Setup_Pose2Sim_Coco133.xml'
|
||||||
|
# elif model_name == 'COCO' or model_name == 'MPII':
|
||||||
|
# ik_setup_file = 'IK_Setup_Pose2Sim_Coco.xml'
|
||||||
|
elif model_name == 'COCO_17':
|
||||||
|
ik_setup_file = 'IK_Setup_Pose2Sim_Coco17.xml'
|
||||||
|
elif model_name == 'LSTM':
|
||||||
|
ik_setup_file = 'IK_Setup_Pose2Sim_LSTM.xml'
|
||||||
else:
|
else:
|
||||||
raise ValueError(f"pose_model '{setup_key}' not found.")
|
raise ValueError(f"Pose model '{model_name}' not found.")
|
||||||
|
|
||||||
ik_setup_path = os.path.join(setup_dir, ik_setup_file)
|
ik_setup_path = osim_setup_dir / ik_setup_file
|
||||||
return ik_setup_path
|
return ik_setup_path
|
||||||
|
|
||||||
|
|
||||||
def get_output_dir(config_dir, person_id):
|
# def get_output_dir(config_dir, person_id):
|
||||||
"""
|
'''
|
||||||
Determines the correct output directory based on the configuration and the person identifier.
|
Determines the correct output directory based on the configuration and the person identifier.
|
||||||
|
|
||||||
Args:
|
INPUTS:
|
||||||
config_dir (Path): The root directory where the configuration file is located.
|
- config_dir (Path): The root directory where the configuration file is located.
|
||||||
person_id (str): Identifier for the person (e.g., 'SinglePerson', 'P1').
|
- person_id (str): Identifier for the person (e.g., 'SinglePerson', 'P1').
|
||||||
|
|
||||||
Returns:
|
OUTPUTS:
|
||||||
Path: The path where the output files should be stored.
|
- Path: The path where the output files should be stored.
|
||||||
"""
|
'''
|
||||||
output_dir = config_dir / 'opensim' # Assuming 'opensim' as the default output subdirectory
|
|
||||||
|
output_dir = config_dir / 'kinematics' # Assuming 'opensim' as the default output subdirectory
|
||||||
|
|
||||||
# Append the person_id to the output directory if it's a multi-person setup
|
# Append the person_id to the output directory if it's a multi-person setup
|
||||||
if person_id != "SinglePerson":
|
if person_id != "SinglePerson":
|
||||||
output_dir = output_dir / person_id
|
output_dir = output_dir / person_id
|
||||||
|
|
||||||
logging.debug(f"Output directory determined as: {output_dir}")
|
logging.info(f"Output directory determined as: {output_dir}")
|
||||||
|
|
||||||
# Create the directory if it does not exist
|
# Create the directory if it does not exist
|
||||||
if not output_dir.exists():
|
if not output_dir.exists():
|
||||||
@ -378,134 +239,329 @@ def get_output_dir(config_dir, person_id):
|
|||||||
return output_dir
|
return output_dir
|
||||||
|
|
||||||
|
|
||||||
def perform_scaling(config_dict, person_id, trc_files, output_dir):
|
def get_kpt_pairs_from_tree(root_node):
|
||||||
"""
|
'''
|
||||||
Perform scaling on the TRC files according to the OpenSim configuration.
|
Get name pairs for all parent-child relationships in the tree.
|
||||||
|
# Excludes the root node.
|
||||||
|
|
||||||
Args:
|
INPUTS:
|
||||||
config_dict (dict): The configuration dictionary.
|
- root_node (Node): The root node of the tree.
|
||||||
person_id (str): The person identifier (e.g., 'SinglePerson', 'P1').
|
|
||||||
trc_files (list): List of TRC files to be processed.
|
OUTPUTS:
|
||||||
output_dir (Path): The directory where the output files should be saved.
|
- list: A list of name pairs for all parent-child relationships in the tree.
|
||||||
"""
|
'''
|
||||||
geometry_path = Path(get_OpenSim_Setup()) / 'Geometry'
|
|
||||||
geometry_path_str = str(geometry_path)
|
pairs = []
|
||||||
opensim.ModelVisualizer.addDirToGeometrySearchPaths(geometry_path_str)
|
for node in PreOrderIter(root_node):
|
||||||
|
# if node.is_root:
|
||||||
|
# continue
|
||||||
|
for child in node.children:
|
||||||
|
pairs.append([node.name, child.name])
|
||||||
|
|
||||||
|
return pairs
|
||||||
|
|
||||||
|
|
||||||
|
def get_kpt_pairs_from_scaling(scaling_root):
|
||||||
|
'''
|
||||||
|
Get name pairs for all marker pairs in the scaling setup file.
|
||||||
|
'''
|
||||||
|
|
||||||
|
pairs = [pair.find('markers').text.strip().split(' ') for pair in scaling_root[0].findall(".//MarkerPair")]
|
||||||
|
|
||||||
|
return pairs
|
||||||
|
|
||||||
|
|
||||||
|
def dict_segment_marker_pairs(scaling_root, right_left_symmetry=True):
|
||||||
|
'''
|
||||||
|
|
||||||
|
'''
|
||||||
|
|
||||||
|
measurement_dict = {}
|
||||||
|
for measurement in scaling_root.findall(".//Measurement"):
|
||||||
|
# Collect all marker pairs for this measurement
|
||||||
|
marker_pairs = [pair.find('markers').text.strip().split() for pair in measurement.findall(".//MarkerPair")]
|
||||||
|
|
||||||
|
# Collect all body scales for this measurement
|
||||||
|
for body_scale in measurement.findall(".//BodyScale"):
|
||||||
|
body_name = body_scale.get('name')
|
||||||
|
if right_left_symmetry:
|
||||||
|
measurement_dict[body_name] = marker_pairs
|
||||||
|
else:
|
||||||
|
if body_name.endswith('_r'):
|
||||||
|
marker_pairs_r = [pair for pair in marker_pairs if any([pair[0].startswith('R'), pair[1].startswith('R')])]
|
||||||
|
measurement_dict[body_name] = marker_pairs_r
|
||||||
|
elif body_name.endswith('_l'):
|
||||||
|
marker_pairs_l = [pair for pair in marker_pairs if any([pair[0].startswith('L'), pair[1].startswith('L')])]
|
||||||
|
measurement_dict[body_name] = marker_pairs_l
|
||||||
|
|
||||||
|
return measurement_dict
|
||||||
|
|
||||||
|
|
||||||
|
def dict_segment_ratio(scaling_root, unscaled_model, Q_coords_scaling, markers, right_left_symmetry=True):
|
||||||
|
'''
|
||||||
|
'''
|
||||||
|
|
||||||
|
# segment_pairs = get_kpt_pairs_from_tree(eval(model_name))
|
||||||
|
segment_pairs = get_kpt_pairs_from_scaling(scaling_root)
|
||||||
|
|
||||||
|
# Get model segment lengths
|
||||||
|
model_markers_locs = [unscaled_model.getMarkerSet().get(marker).getLocationInGround(unscaled_model.getWorkingState()).to_numpy() for marker in markers]
|
||||||
|
model_segment_lengths = np.array([euclidean_distance(model_markers_locs[markers.index(pt1)],
|
||||||
|
model_markers_locs[markers.index(pt2)])
|
||||||
|
for (pt1,pt2) in segment_pairs])
|
||||||
|
|
||||||
|
# Get median segment lengths from Q_coords_scaling. Trimmed mean works better than mean or median
|
||||||
|
trc_segment_lengths = np.array([euclidean_distance(Q_coords_scaling.iloc[:,markers.index(pt1)*3:markers.index(pt1)*3+3],
|
||||||
|
Q_coords_scaling.iloc[:,markers.index(pt2)*3:markers.index(pt2)*3+3])
|
||||||
|
for (pt1,pt2) in segment_pairs])
|
||||||
|
# trc_segment_lengths = np.median(trc_segment_lengths, axis=1)
|
||||||
|
# trc_segment_lengths = np.mean(trc_segment_lengths, axis=1)
|
||||||
|
trc_segment_lengths = np.array([trimmed_mean(arr, trimmed_percent=0.5) for arr in trc_segment_lengths])
|
||||||
|
|
||||||
|
# Calculate ratio for each segment
|
||||||
|
segment_ratios = trc_segment_lengths / model_segment_lengths
|
||||||
|
segment_markers_dict = dict_segment_marker_pairs(scaling_root, right_left_symmetry=right_left_symmetry)
|
||||||
|
segment_ratio_dict = segment_markers_dict.copy()
|
||||||
|
segment_ratio_dict.update({key: np.mean([segment_ratios[segment_pairs.index(k)]
|
||||||
|
for k in segment_markers_dict[key]])
|
||||||
|
for key in segment_markers_dict.keys()})
|
||||||
|
|
||||||
|
return segment_ratio_dict
|
||||||
|
|
||||||
|
|
||||||
|
def deactivate_measurements(scaling_root):
|
||||||
|
'''
|
||||||
|
'''
|
||||||
|
|
||||||
|
measurement_set = scaling_root.find(".//MeasurementSet/objects")
|
||||||
|
for measurement in measurement_set.findall('Measurement'):
|
||||||
|
apply_elem = measurement.find('apply')
|
||||||
|
apply_elem.text = 'false'
|
||||||
|
|
||||||
|
|
||||||
|
def update_scale_values(scaling_root, segment_ratio_dict):
|
||||||
|
'''
|
||||||
|
'''
|
||||||
|
|
||||||
|
# Get the ScaleSet/objects element
|
||||||
|
scale_set = scaling_root.find(".//ScaleSet/objects")
|
||||||
|
|
||||||
|
# Remove all existing Scale elements
|
||||||
|
for scale in scale_set.findall('Scale'):
|
||||||
|
scale_set.remove(scale)
|
||||||
|
|
||||||
|
# Add new Scale elements based on scale_dict
|
||||||
|
for segment, scale in segment_ratio_dict.items():
|
||||||
|
new_scale = etree.Element('Scale')
|
||||||
|
# scales
|
||||||
|
scales_elem = etree.SubElement(new_scale, 'scales')
|
||||||
|
scales_elem.text = ' '.join([str(scale)]*3)
|
||||||
|
# segment name
|
||||||
|
segment_elem = etree.SubElement(new_scale, 'segment')
|
||||||
|
segment_elem.text = segment
|
||||||
|
# apply True
|
||||||
|
apply_elem = etree.SubElement(new_scale, 'apply')
|
||||||
|
apply_elem.text = 'true'
|
||||||
|
|
||||||
|
scale_set.append(new_scale)
|
||||||
|
|
||||||
|
|
||||||
|
def perform_scaling(trc_file, kinematics_dir, osim_setup_dir, model_name, right_left_symmetry=True, subject_height=1.75, subject_mass=70, remove_scaling_setup=True):
|
||||||
|
'''
|
||||||
|
Perform model scaling based on the (not necessarily static) TRC file:
|
||||||
|
- Retrieve the 80% slowest frames, excluding frames where the person is out of frame.
|
||||||
|
- From these frames, measure median segment lengths.
|
||||||
|
- Calculate ratio between model and measured segment lengths -> OpenSim manual scaling.
|
||||||
|
|
||||||
|
INPUTS:
|
||||||
|
- 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:
|
try:
|
||||||
athlete_config = config_dict.get('project', {})
|
# Load model
|
||||||
athlete_height = athlete_config.get('participant_height', -1)
|
opensim.ModelVisualizer.addDirToGeometrySearchPaths(str(osim_setup_dir / 'Geometry'))
|
||||||
athlete_weight = athlete_config.get('participant_mass', -1)
|
unscaled_model_path = get_model_path(model_name, osim_setup_dir)
|
||||||
|
if not unscaled_model_path:
|
||||||
|
raise ValueError(f"Unscaled OpenSim model not found at: {unscaled_model_path}")
|
||||||
|
unscaled_model = opensim.Model(str(unscaled_model_path))
|
||||||
|
unscaled_model.initSystem()
|
||||||
|
scaled_model_path = (kinematics_dir / (trc_file.stem + '.osim')).resolve()
|
||||||
|
|
||||||
if person_id == "SinglePerson":
|
# Load scaling setup
|
||||||
if not isinstance(athlete_height, float) or not isinstance(athlete_weight, float):
|
scaling_path = get_scaling_setup(model_name, osim_setup_dir)
|
||||||
raise ValueError("For a single person configuration, 'participant_height' and 'participant_mass' must be floats.")
|
scaling_tree = etree.parse(scaling_path)
|
||||||
else:
|
scaling_root = scaling_tree.getroot()
|
||||||
if person_id.startswith("P"):
|
scaling_path_temp = str(kinematics_dir / Path(scaling_path).name)
|
||||||
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}")
|
# Read trc file
|
||||||
|
Q_coords, _, _, markers, _ = read_trc(trc_file)
|
||||||
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)
|
|
||||||
|
|
||||||
|
# Using 80% slowest frames for scaling, removing frames when person is out of frame
|
||||||
Q_diff = Q_coords.diff(axis=0).sum(axis=1)
|
Q_diff = Q_coords.diff(axis=0).sum(axis=1)
|
||||||
Q_diff = Q_diff[Q_diff != 0]
|
Q_diff = Q_diff[Q_diff != 0] # remove when speed is 0 (person out of frame)
|
||||||
min_speed_indices = Q_diff.abs().nsmallest(int(len(Q_diff) * 0.1)).index
|
min_speed_indices = Q_diff.abs().nsmallest(int(len(Q_diff) * 0.8)).index
|
||||||
Q_coords_scaling = Q_coords.iloc[min_speed_indices].reset_index(drop=True)
|
Q_coords_scaling = Q_coords.iloc[min_speed_indices].reset_index(drop=True)
|
||||||
|
|
||||||
trc_scaling_path = trc_file.parent / (trc_file.stem + '_scaling.trc')
|
# Get manual scale values (scale on trimmed mean of measured segments rather than on raw keypoints)
|
||||||
make_trc_with_Q(Q_coords_scaling, header, str(trc_scaling_path))
|
segment_ratio_dict = dict_segment_ratio(scaling_root, unscaled_model, Q_coords_scaling, markers, right_left_symmetry=right_left_symmetry)
|
||||||
|
|
||||||
scaling_file_path = str(trc_file.parent / (trc_file.stem + '_' + Path(scaling_path).name))
|
# Update scaling setup file
|
||||||
scaled_model_path = (output_dir / (trc_file.stem + '_scaled.osim')).resolve()
|
scaling_root[0].find('mass').text = str(subject_mass)
|
||||||
scaling_tree = etree.parse(str(scaling_path))
|
scaling_root[0].find('height').text = str(subject_height)
|
||||||
scaling_root = scaling_tree.getroot()
|
scaling_root[0].find('GenericModelMaker').find('model_file').text = str(unscaled_model_path)
|
||||||
|
scaling_root[0].find(".//scaling_order").text = ' manualScale measurements'
|
||||||
scaling_root[0].find('mass').text = str(athlete_weight)
|
deactivate_measurements(scaling_root)
|
||||||
scaling_root[0].find('height').text = str(athlete_height)
|
update_scale_values(scaling_root, segment_ratio_dict)
|
||||||
scaling_root[0].find('GenericModelMaker').find('model_file').text = str(pose_model)
|
for mk_f in scaling_root[0].findall(".//marker_file"):
|
||||||
scaling_root[0].find('ModelScaler').find('marker_file').text = trc_scaling_path.name
|
mk_f.text = "Unassigned"
|
||||||
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('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}")
|
etree.indent(scaling_tree, space='\t', level=0)
|
||||||
opensim.ScaleTool(scaling_file_path).run()
|
scaling_tree.write(scaling_path_temp, pretty_print=True, xml_declaration=True, encoding='utf-8')
|
||||||
|
|
||||||
|
# Run scaling
|
||||||
|
opensim.ScaleTool(scaling_path_temp).run()
|
||||||
|
|
||||||
|
# Remove scaling setup
|
||||||
|
if remove_scaling_setup:
|
||||||
|
Path(scaling_path_temp).unlink()
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"Error during scaling for {person_id}: {e}")
|
logging.error(f"Error during scaling for {trc_file}: {e}")
|
||||||
raise
|
raise
|
||||||
|
|
||||||
def perform_inverse_kinematics(config_dict, person_id, trc_files, output_dir):
|
|
||||||
"""
|
def perform_IK(trc_file, kinematics_dir, osim_setup_dir, model_name, remove_IK_setup=True):
|
||||||
|
'''
|
||||||
Perform inverse kinematics on the TRC files according to the OpenSim configuration.
|
Perform inverse kinematics on the TRC files according to the OpenSim configuration.
|
||||||
|
|
||||||
Args:
|
INPUTS:
|
||||||
config_dict (dict): The configuration dictionary.
|
- config_dict (dict): The configuration dictionary.
|
||||||
person_id (str): The person identifier (e.g., 'SinglePerson', 'P1').
|
- person_id (str): The person identifier (e.g., 'SinglePerson', 'P1').
|
||||||
trc_files (list): List of TRC files to be processed.
|
- trc_files (list): List of TRC files to be processed.
|
||||||
output_dir (Path): The directory where the output files should be saved.
|
- output_dir (Path): The directory where the output files should be saved.
|
||||||
"""
|
'''
|
||||||
|
|
||||||
try:
|
try:
|
||||||
logging.debug(f"Performing inverse kinematics. Output directory: {output_dir}")
|
# Retrieve data
|
||||||
|
ik_path = get_IK_Setup(model_name, osim_setup_dir)
|
||||||
|
ik_path_temp = str(kinematics_dir / Path(ik_path).name)
|
||||||
|
scaled_model_path = (kinematics_dir / (trc_file.stem + '.osim')).resolve()
|
||||||
|
output_motion_file = Path(kinematics_dir, trc_file.stem + '.mot').resolve()
|
||||||
|
if not trc_file.exists():
|
||||||
|
raise FileNotFoundError(f"TRC file does not exist: {trc_file}")
|
||||||
|
_, _, time_col, _, _ = read_trc(trc_file)
|
||||||
|
start_time, end_time = time_col.iloc[0], time_col.iloc[-1]
|
||||||
|
|
||||||
for trc_file in trc_files:
|
# Update IK setup file
|
||||||
trc_file_path = Path(trc_file).resolve()
|
ik_tree = etree.parse(ik_path)
|
||||||
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 = ik_tree.getroot()
|
||||||
ik_root.find('.//model_file').text = str(scaled_model_path)
|
ik_root.find('.//model_file').text = str(scaled_model_path)
|
||||||
ik_root.find('.//time_range').text = f'{start_time} {end_time}'
|
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('.//output_motion_file').text = str(output_motion_file)
|
||||||
ik_root.find('.//marker_file').text = str(trc_file_path)
|
ik_root.find('.//marker_file').text = str(trc_file.resolve())
|
||||||
ik_tree.write(ik_file_path)
|
ik_tree.write(ik_path_temp)
|
||||||
|
|
||||||
logging.info(f"Running InverseKinematicsTool with TRC file: {trc_file_path}")
|
# Run IK
|
||||||
if not trc_file_path.exists():
|
opensim.InverseKinematicsTool(str(ik_path_temp)).run()
|
||||||
raise FileNotFoundError(f"TRC file does not exist: {trc_file_path}")
|
|
||||||
|
|
||||||
logging.debug(f"Running InverseKinematicsTool with IK setup file: {ik_file_path}")
|
# Remove IK setup
|
||||||
opensim.InverseKinematicsTool(str(ik_file_path)).run()
|
if remove_IK_setup:
|
||||||
|
Path(ik_path_temp).unlink()
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"Error during IK for {person_id}: {e}")
|
logging.error(f"Error during IK for {trc_file}: {e}")
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
def opensimProcessing(config_dict):
|
def kinematics(config_dict):
|
||||||
logging.info("Starting OpenSim processing...")
|
'''
|
||||||
process_all_groups(config_dict)
|
Runs OpenSim scaling and inverse kinematics on the trc files of triangulated coordinates.
|
||||||
logging.info("OpenSim processing completed successfully.")
|
|
||||||
|
INPUTS:
|
||||||
|
- config_dict (dict): Generated from a .toml calibration file
|
||||||
|
|
||||||
|
OUTPUTS:
|
||||||
|
- A scaled .osim model for each person.
|
||||||
|
- Joint angle data files (.mot) for each person.
|
||||||
|
'''
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Read config_dict
|
||||||
|
project_dir = config_dict.get('project').get('project_dir')
|
||||||
|
# if batch
|
||||||
|
session_dir = Path(project_dir) / '..'
|
||||||
|
# if single trial
|
||||||
|
session_dir = session_dir if 'Config.toml' in os.listdir(session_dir) else os.getcwd()
|
||||||
|
use_augmentation = config_dict.get('kinematics').get('use_augmentation')
|
||||||
|
if use_augmentation: model_name = 'LSTM'
|
||||||
|
else: model_name = config_dict.get('pose').get('pose_model').upper()
|
||||||
|
right_left_symmetry = config_dict.get('kinematics').get('right_left_symmetry')
|
||||||
|
remove_scaling_setup = config_dict.get('kinematics').get('remove_individual_scaling_setup')
|
||||||
|
remove_IK_setup = config_dict.get('kinematics').get('remove_individual_IK_setup')
|
||||||
|
subject_height = config_dict.get('project').get('participant_height')
|
||||||
|
subject_mass = config_dict.get('project').get('participant_mass')
|
||||||
|
|
||||||
|
pose3d_dir = Path(project_dir) / 'pose-3d'
|
||||||
|
kinematics_dir = Path(project_dir) / 'kinematics'
|
||||||
|
kinematics_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
osim_setup_dir = get_opensim_setup_dir()
|
||||||
|
|
||||||
|
# OpenSim logs saved to a different file
|
||||||
|
opensim_logs_file = kinematics_dir / 'opensim_logs.txt'
|
||||||
|
opensim.Logger.setLevelString('Info')
|
||||||
|
opensim.Logger.removeFileSink()
|
||||||
|
opensim.Logger.addFileSink(str(opensim_logs_file))
|
||||||
|
|
||||||
|
# Find all trc files
|
||||||
|
trc_files = []
|
||||||
|
if use_augmentation:
|
||||||
|
trc_files = [f for f in pose3d_dir.glob('*.trc') if '_LSTM' in f.name]
|
||||||
|
if len(trc_files) == 0:
|
||||||
|
model_name = config_dict.get('pose').get('pose_model').upper()
|
||||||
|
logging.warning("No LSTM trc files found. Using non augmented trc files instead.")
|
||||||
|
if len(trc_files) == 0: # filtered files by default
|
||||||
|
trc_files = [f for f in pose3d_dir.glob('*.trc') if '_LSTM' not in f.name and '_filt' in f.name and '_scaling' not in f.name]
|
||||||
|
if len(trc_files) == 0:
|
||||||
|
trc_files = [f for f in pose3d_dir.glob('*.trc') if '_LSTM' not in f.name and '_scaling' not in f.name]
|
||||||
|
if len(trc_files) == 0:
|
||||||
|
raise ValueError(f'No trc files found in {pose3d_dir}.')
|
||||||
|
sorted(trc_files, key=natural_sort_key)
|
||||||
|
|
||||||
|
# Get subject heights and masses
|
||||||
|
if subject_height is None or subject_height == 0:
|
||||||
|
subject_height = [1.75] * len(trc_files)
|
||||||
|
logging.warning("No subject height found in Config.toml. Using default height of 1.75m.")
|
||||||
|
elif not type(subject_height) == list: # int or float
|
||||||
|
subject_height = [subject_height]
|
||||||
|
elif len(subject_height) < len(trc_files):
|
||||||
|
logging.warning("Number of subject heights does not match number of TRC files. Missing heights are set to 1.75m.")
|
||||||
|
subject_height += [1.75] * (len(trc_files) - len(subject_height))
|
||||||
|
|
||||||
|
if subject_mass is None or subject_mass == 0:
|
||||||
|
subject_mass = [70] * len(trc_files)
|
||||||
|
logging.warning("No subject mass found in Config.toml. Using default mass of 70kg.")
|
||||||
|
elif not type(subject_mass) == list:
|
||||||
|
subject_mass = [subject_mass]
|
||||||
|
elif len(subject_mass) < len(trc_files):
|
||||||
|
logging.warning("Number of subject masses does not match number of TRC files. Missing masses are set to 70kg.")
|
||||||
|
subject_mass += [70] * (len(trc_files) - len(subject_mass))
|
||||||
|
|
||||||
|
# Perform scaling and IK for each trc file
|
||||||
|
for p, trc_file in enumerate(trc_files):
|
||||||
|
logging.info(f"Processing TRC file: {trc_file.resolve()}")
|
||||||
|
|
||||||
|
logging.info("Scaling...")
|
||||||
|
perform_scaling(trc_file, kinematics_dir, osim_setup_dir, model_name, right_left_symmetry=right_left_symmetry, subject_height=subject_height[p], subject_mass=subject_mass[p], remove_scaling_setup=remove_scaling_setup)
|
||||||
|
logging.info(f"\tDone. OpenSim logs saved to {opensim_logs_file.resolve()}.")
|
||||||
|
logging.info(f"\tScaled model saved to {(kinematics_dir / (trc_file.stem + '_scaled.osim')).resolve()}")
|
||||||
|
|
||||||
|
logging.info("\nInverse Kinematics...")
|
||||||
|
perform_IK(trc_file, kinematics_dir, osim_setup_dir, model_name, remove_IK_setup=remove_IK_setup)
|
||||||
|
logging.info(f"\tDone. OpenSim logs saved to {opensim_logs_file.resolve()}.")
|
||||||
|
logging.info(f"\tJoint angle data saved to {(kinematics_dir / (trc_file.stem + '.mot')).resolve()}\n")
|
||||||
|
except RuntimeError as e:
|
||||||
|
logging.error(f"Error occurred: {e}")
|
||||||
|
@ -49,7 +49,7 @@ __status__ = "Development"
|
|||||||
def check_midhip_data(trc_file):
|
def check_midhip_data(trc_file):
|
||||||
try:
|
try:
|
||||||
# Find MidHip data
|
# Find MidHip data
|
||||||
midhip_data = trc_file.marker("CHip")
|
midhip_data = trc_file.marker("Hip")
|
||||||
if midhip_data is None or len(midhip_data) == 0:
|
if midhip_data is None or len(midhip_data) == 0:
|
||||||
raise ValueError("MidHip data is empty")
|
raise ValueError("MidHip data is empty")
|
||||||
except (KeyError, ValueError):
|
except (KeyError, ValueError):
|
||||||
@ -57,7 +57,7 @@ def check_midhip_data(trc_file):
|
|||||||
rhip_data = trc_file.marker("RHip")
|
rhip_data = trc_file.marker("RHip")
|
||||||
lhip_data = trc_file.marker("LHip")
|
lhip_data = trc_file.marker("LHip")
|
||||||
midhip_data = (rhip_data + lhip_data) / 2
|
midhip_data = (rhip_data + lhip_data) / 2
|
||||||
trc_file.add_marker('CHip', *midhip_data.T)
|
trc_file.add_marker('Hip', *midhip_data.T)
|
||||||
|
|
||||||
return trc_file
|
return trc_file
|
||||||
|
|
||||||
@ -83,14 +83,10 @@ def augmentTRC(config_dict):
|
|||||||
project_dir = config_dict.get('project').get('project_dir')
|
project_dir = config_dict.get('project').get('project_dir')
|
||||||
pathInputTRCFile = os.path.realpath(os.path.join(project_dir, 'pose-3d'))
|
pathInputTRCFile = os.path.realpath(os.path.join(project_dir, 'pose-3d'))
|
||||||
pathOutputTRCFile = os.path.realpath(os.path.join(project_dir, 'pose-3d'))
|
pathOutputTRCFile = os.path.realpath(os.path.join(project_dir, 'pose-3d'))
|
||||||
subject_height = config_dict.get('project').get('participant_height')
|
|
||||||
if subject_height is None or subject_height == 0 or subject_height==0:
|
|
||||||
raise ValueError("Subject height is not set or is invalid.")
|
|
||||||
subject_mass = config_dict.get('project').get('participant_mass')
|
|
||||||
if not type(subject_height) == list:
|
|
||||||
subject_height = [subject_height]
|
|
||||||
subject_mass = [subject_mass]
|
|
||||||
make_c3d = config_dict.get('markerAugmentation').get('make_c3d')
|
make_c3d = config_dict.get('markerAugmentation').get('make_c3d')
|
||||||
|
subject_height = config_dict.get('project').get('participant_height')
|
||||||
|
subject_mass = config_dict.get('project').get('participant_mass')
|
||||||
|
|
||||||
augmenterDir = os.path.dirname(utilsDataman.__file__)
|
augmenterDir = os.path.dirname(utilsDataman.__file__)
|
||||||
augmenterModelName = 'LSTM'
|
augmenterModelName = 'LSTM'
|
||||||
augmenter_model = 'v0.3'
|
augmenter_model = 'v0.3'
|
||||||
@ -110,6 +106,25 @@ def augmentTRC(config_dict):
|
|||||||
trc_files = trc_no_filtering
|
trc_files = trc_no_filtering
|
||||||
sorted(trc_files, key=natural_sort_key)
|
sorted(trc_files, key=natural_sort_key)
|
||||||
|
|
||||||
|
# Get subject heights and masses
|
||||||
|
if subject_height is None or subject_height == 0:
|
||||||
|
subject_height = [1.75] * len(trc_files)
|
||||||
|
logging.warning("No subject height found in Config.toml. Using default height of 1.75m.")
|
||||||
|
elif not type(subject_height) == list: # int or float
|
||||||
|
subject_height = [subject_height]
|
||||||
|
elif len(subject_height) < len(trc_files):
|
||||||
|
logging.warning("Number of subject heights does not match number of TRC files. Missing heights are set to 1.75m.")
|
||||||
|
subject_height += [1.75] * (len(trc_files) - len(subject_height))
|
||||||
|
|
||||||
|
if subject_mass is None or subject_mass == 0:
|
||||||
|
subject_mass = [70] * len(trc_files)
|
||||||
|
logging.warning("No subject mass found in Config.toml. Using default mass of 70kg.")
|
||||||
|
elif not type(subject_mass) == list:
|
||||||
|
subject_mass = [subject_mass]
|
||||||
|
elif len(subject_mass) < len(trc_files):
|
||||||
|
logging.warning("Number of subject masses does not match number of TRC files. Missing masses are set to 70kg.")
|
||||||
|
subject_mass += [70] * (len(trc_files) - len(subject_mass))
|
||||||
|
|
||||||
for p in range(len(subject_mass)):
|
for p in range(len(subject_mass)):
|
||||||
pathInputTRCFile = trc_files[p]
|
pathInputTRCFile = trc_files[p]
|
||||||
pathOutputTRCFile = os.path.splitext(pathInputTRCFile)[0] + '_LSTM.trc'
|
pathOutputTRCFile = os.path.splitext(pathInputTRCFile)[0] + '_LSTM.trc'
|
||||||
@ -172,7 +187,7 @@ def augmentTRC(config_dict):
|
|||||||
trc_data_data = trc_data[:,1:]
|
trc_data_data = trc_data[:,1:]
|
||||||
|
|
||||||
# Step 2: Normalize with reference marker position.
|
# Step 2: Normalize with reference marker position.
|
||||||
referenceMarker_data = trc_file.marker("CHip") # instead of trc_file.marker(referenceMarker) # change by HunMin
|
referenceMarker_data = trc_file.marker("Hip") # instead of trc_file.marker(referenceMarker) # change by HunMin
|
||||||
norm_trc_data_data = np.zeros((trc_data_data.shape[0],
|
norm_trc_data_data = np.zeros((trc_data_data.shape[0],
|
||||||
trc_data_data.shape[1]))
|
trc_data_data.shape[1]))
|
||||||
for i in range(0,trc_data_data.shape[1],3):
|
for i in range(0,trc_data_data.shape[1],3):
|
||||||
|
33
README.md
33
README.md
@ -16,9 +16,9 @@
|
|||||||
|
|
||||||
##### N.B:. Please set undistort_points and handle_LR_swap to false for now since it currently leads to inaccuracies. I'll try to fix it soon.
|
##### N.B:. Please set undistort_points and handle_LR_swap to false for now since it currently leads to inaccuracies. I'll try to fix it soon.
|
||||||
|
|
||||||
> **_News_: Version 0.9:**\
|
> **_News_: Version 0.109:**\
|
||||||
> **Pose estimation with RTMPose is now included in Pose2Sim!**\
|
> **OpenSim scaling and inverse kinematics are now integrated in Pose2Sim!** No static trial needed.\
|
||||||
> **Other recently added features**: Automatic camera synchronization, multi-person analysis, Blender visualization, Marker augmentation, Batch processing.
|
> **Other recently added features**: Pose estimation, Automatic camera synchronization, Multi-person analysis, Blender visualization, Marker augmentation, Batch processing.
|
||||||
<!-- Incidentally, right/left limb swapping is now handled, which is useful if few cameras are used;\
|
<!-- Incidentally, right/left limb swapping is now handled, which is useful if few cameras are used;\
|
||||||
and lens distortions are better taken into account.\ -->
|
and lens distortions are better taken into account.\ -->
|
||||||
> To upgrade, type `pip install pose2sim --upgrade` (note that you need Python 3.9 or higher).
|
> To upgrade, type `pip install pose2sim --upgrade` (note that you need Python 3.9 or higher).
|
||||||
@ -27,6 +27,14 @@ and lens distortions are better taken into account.\ -->
|
|||||||
|
|
||||||
`Pose2Sim` provides a workflow for 3D markerless kinematics, as an alternative to marker-based motion capture methods. It aims to provide a free tool to obtain research-grade results from consumer-grade equipment. Any combination of phone, webcam, GoPro, etc. can be used.
|
`Pose2Sim` provides a workflow for 3D markerless kinematics, as an alternative to marker-based motion capture methods. It aims to provide a free tool to obtain research-grade results from consumer-grade equipment. Any combination of phone, webcam, GoPro, etc. can be used.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<!--powerfull, flexible, intuitive -->
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
**Pose2Sim** stands for "OpenPose to OpenSim", as it originally used *OpenPose* inputs (2D keypoints coordinates) from multiple videos and lead to an [OpenSim](https://opensim.stanford.edu/) result (full-body 3D joint angles). Pose estimation is now performed with more recent models from [RTMPose](https://github.com/open-mmlab/mmpose/tree/main/projects/rtmpose). OpenPose and other models are kept as legacy options.
|
**Pose2Sim** stands for "OpenPose to OpenSim", as it originally used *OpenPose* inputs (2D keypoints coordinates) from multiple videos and lead to an [OpenSim](https://opensim.stanford.edu/) result (full-body 3D joint angles). Pose estimation is now performed with more recent models from [RTMPose](https://github.com/open-mmlab/mmpose/tree/main/projects/rtmpose). OpenPose and other models are kept as legacy options.
|
||||||
|
|
||||||
For real-time analysis with a single camera, please consider **[Sports2D](https://github.com/davidpagnon/Sports2D)** (note that the motion must lie in the sagittal or frontal plane).
|
For real-time analysis with a single camera, please consider **[Sports2D](https://github.com/davidpagnon/Sports2D)** (note that the motion must lie in the sagittal or frontal plane).
|
||||||
@ -48,11 +56,12 @@ For real-time analysis with a single camera, please consider **[Sports2D](https:
|
|||||||
- [x] **v0.6** *(02/2024)*: Marker augmentation, Blender visualizer
|
- [x] **v0.6** *(02/2024)*: Marker augmentation, Blender visualizer
|
||||||
- [x] **v0.7** *(03/2024)*: Multi-person analysis
|
- [x] **v0.7** *(03/2024)*: Multi-person analysis
|
||||||
- [x] **v0.8** *(04/2024)*: New synchronization tool
|
- [x] **v0.8** *(04/2024)*: New synchronization tool
|
||||||
- [x] **v0.9: *(07/2024)*: Integration of pose estimation in the pipeline**
|
- [x] **v0.9** *(07/2024)*: Integration of pose estimation in the pipeline
|
||||||
- [ ] v0.10: Integration of OpenSim in the pipeline
|
- [x] **v0.10 *(09/2024)*: Integration of OpenSim in the pipeline**
|
||||||
- [ ] v0.11: Calibration based on keypoint detection, Handling left/right swaps, Correcting lens distortions
|
- [ ] v0.11: Migrated documentation to new github.io website
|
||||||
- [ ] v0.12: Graphical User Interface
|
- [ ] v0.12: Calibration based on keypoint detection, Handling left/right swaps, Correcting lens distortions
|
||||||
- [ ] v1.0: First accomplished release
|
- [ ] v0.13: Graphical User Interface
|
||||||
|
- [ ] v1.0: First full release
|
||||||
|
|
||||||
</br>
|
</br>
|
||||||
|
|
||||||
@ -175,7 +184,7 @@ Pose2Sim.personAssociation()
|
|||||||
Pose2Sim.triangulation()
|
Pose2Sim.triangulation()
|
||||||
Pose2Sim.filtering()
|
Pose2Sim.filtering()
|
||||||
Pose2Sim.markerAugmentation()
|
Pose2Sim.markerAugmentation()
|
||||||
Pose2Sim.opensimProcessing()
|
Pose2Sim.kinematics()
|
||||||
```
|
```
|
||||||
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.
|
||||||
|
|
||||||
@ -189,7 +198,8 @@ OpenSim results are stored as scaled model .osim and .mot in each trial folder i
|
|||||||
- You can run all stages at once:
|
- You can run all stages at once:
|
||||||
``` python
|
``` python
|
||||||
from Pose2Sim import Pose2Sim
|
from Pose2Sim import Pose2Sim
|
||||||
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_kinematics=True)
|
||||||
|
# or simply: Pose2Sim.runAll()
|
||||||
```
|
```
|
||||||
- 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.
|
- If the results are not convincing, refer to Section [OpenSim-kinematics](#OpenSim-kinematics) in the document.
|
||||||
@ -260,8 +270,7 @@ You can then run OpenSim scaling and inverse kinematics for each resulting .trc
|
|||||||
You can also visualize your results with Blender as in [Demonstration Part-3](#demonstration-part-3-optional-visualize-your-results-with-blender).
|
You can also visualize your results with Blender as in [Demonstration Part-3](#demonstration-part-3-optional-visualize-your-results-with-blender).
|
||||||
|
|
||||||
*N.B.:* Set *[project]* `multi_person = true` for each trial that contains multiple persons.\
|
*N.B.:* Set *[project]* `multi_person = true` for each trial that contains multiple persons.\
|
||||||
Set *[triangulation]* `reorder_trc = true` if you need to run OpenSim and to match the generated .trc files with the static trials.\
|
Make sure that the order of *[markerAugmentation]* `participant_height` and `participant_mass` matches the person's IDs.
|
||||||
Make sure that the order of *[markerAugmentation]* `participant_height` and `participant_mass` matches the order of the static trials.
|
|
||||||
|
|
||||||
<br/>
|
<br/>
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user