pose2sim/Pose2Sim/Utilities/bodykin_from_mot_osim.py

175 lines
7.1 KiB
Python
Raw Permalink Normal View History

2023-10-19 00:53:06 +08:00
#!/usr/bin/env python
# -*- coding: utf-8 -*-
'''
##################################################
## Build csv from mot and osim files ##
##################################################
Build a csv file which stores locations and orientations of all bodies
calculated from a .mot motion file and a .osim model file.
Equivalent to OpenSim Analysis -> BodyKinematics but without the bugs in
orientations due to their use of Euler angle instead of homography matrices
2023-10-19 02:05:25 +08:00
Transforms from OpenSim's yup to Blender's zup unless you set direction = 'yup'
2023-10-19 00:53:06 +08:00
2024-01-15 18:51:02 +08:00
Beware, it can be quite slow depending on the ccomplexity
of the model and on the number of frames.
2023-10-19 00:53:06 +08:00
Usage:
2023-11-03 06:49:44 +08:00
from Pose2Sim.Utilities import bodykin_from_mot_osim; bodykin_from_mot_osim.bodykin_from_mot_osim_func(r'<input_mot_file>', r'<output_osim_file>', r'<output_csv_file>')
python -m bodykin_from_mot_osim -m input_mot_file -o input_osim_file
python -m bodykin_from_mot_osim -m input_mot_file -o input_osim_file -c output_csv_file
2023-10-19 00:53:06 +08:00
'''
## INIT
import os
import numpy as np
import opensim as osim
import argparse
2023-10-19 02:05:25 +08:00
direction = 'zup' # 'zup' or 'yup'
2023-10-19 00:53:06 +08:00
## AUTHORSHIP INFORMATION
__author__ = "David Pagnon, Jonathan Camargo"
__copyright__ = "Copyright 2023, BlendOSim & Sim2Blend"
__credits__ = ["David Pagnon", "Jonathan Camargo"]
__license__ = "MIT License"
2024-07-10 16:12:57 +08:00
__version__ = "0.9.4"
2023-10-19 00:53:06 +08:00
__maintainer__ = "David Pagnon"
__email__ = "contact@david-pagnon.com"
__status__ = "Development"
2023-11-03 06:49:44 +08:00
def bodykin_from_mot_osim_func(*args):
2023-10-19 00:53:06 +08:00
'''
Build a csv file which stores locations and orientations of all bodies
calculated from a .mot motion file and a .osim model file.
Equivalent to OpenSim Analysis -> BodyKinematics but without the bugs in
orientations due to their use of Euler angle instead of homography matrices
Usage:
2023-11-03 06:49:44 +08:00
from Pose2Sim.Utilities import bodykin_from_mot_osim; bodykin_from_mot_osim.bodykin_from_mot_osim_func(r'<input_mot_file>', r'<output_osim_file>', r'<output_csv_file>')
python -m bodykin_from_mot_osim -m input_mot_file -o input_osim_file
python -m bodykin_from_mot_osim -m input_mot_file -o input_osim_file -t output_csv_file
2023-10-19 00:53:06 +08:00
'''
try:
motion_path = args[0]['input_mot_file'] # invoked with argparse
osim_path = args[0]['input_osim_file']
if args[0]['csv_output_file'] == None:
output_csv_file = motion_path.replace('.mot', '.csv')
else:
output_csv_file = args[0]['csv_output_file']
except:
motion_path = args[0] # invoked as a function
osim_path = args[1]
try:
output_csv_file = args[2]
except:
output_csv_file = motion_path.replace('.mot', '.csv')
# Read model and motion files
2023-10-19 01:07:21 +08:00
model = osim.Model(osim_path)
motion_data = osim.TimeSeriesTable(motion_path)
2024-01-15 18:51:02 +08:00
# # Degrees or radians
# with open(motion_path) as m_p:
# while True:
# line = m_p.readline()
# if 'inDegrees' in line:
# break
# if 'yes' in line:
# in_degrees = True
# else:
# in_degrees = False
2023-10-19 00:53:06 +08:00
# Model: get model coordinates and bodies
model_coordSet = model.getCoordinateSet()
2024-01-15 18:51:02 +08:00
# coordinates = [model_coordSet.get(i) for i in range(model_coordSet.getSize())]
# coordinates = [c for c in coordinates if '_beta' not in c.getName()]
# coordinateNames = [c.getName() for c in coordinates]
coordinateNames = motion_data.getColumnLabels()
2023-10-19 00:53:06 +08:00
model_bodySet = model.getBodySet()
bodies = [model_bodySet.get(i) for i in range(model_bodySet.getSize())]
bodyNames = [b.getName() for b in bodies]
# Motion: read coordinates and convert to radians
times = motion_data.getIndependentColumn()
motion_data_np = motion_data.getMatrix().to_numpy()
2024-01-15 18:51:02 +08:00
for i, c in enumerate(coordinateNames):
if model_coordSet.get(c).getMotionType() == 1: # 1: rotation, 2: translation, 3: coupled
if motion_data.getTableMetaDataAsString('inDegrees') == 'yes':
# if in_degrees:
# for i in range(len(coordinates)):
# if coordinates[i].getMotionType() == 1: # 1: rotation, 2: translation, 3: coupled
motion_data_np[:,i] = motion_data_np[:,i] * np.pi/180 # if rotation, convert to radians
2023-10-19 00:53:06 +08:00
# Animate model
state = model.initSystem()
loc_rot_frame_all = []
H_zup = np.array([[1,0,0,0], [0,0,-1,0], [0,1,0,0], [0,0,0,1]])
2024-01-15 18:51:02 +08:00
print('Time frame:')
2023-10-19 00:53:06 +08:00
for n in range(motion_data.getNumRows()):
2024-01-15 18:51:02 +08:00
print(times[n], 's')
2023-10-19 00:53:06 +08:00
# Set model struct in each time state
2024-03-15 17:49:32 +08:00
for c, coord in enumerate(coordinateNames): ## PROBLEME QUAND HEADERS DE MOTION_DATA_NP ET COORDINATENAMES SONT PAS DANS LE MEME ORDRE
try:
model.getCoordinateSet().get(coord).setValue(state, motion_data_np[n,c], enforceContraints=False)
except:
pass
# model.assemble(state)
model.realizePosition(state) # much faster (IK already done, no need to compute it again)
2023-10-19 00:53:06 +08:00
# Use state of model to get body coordinates in ground
loc_rot_frame = []
for b in bodies:
H_swig = b.getTransformInGround(state)
T = H_swig.T().to_numpy()
R_swig = H_swig.R()
R = np.array([[R_swig.get(0,0), R_swig.get(0,1), R_swig.get(0,2)],
[R_swig.get(1,0), R_swig.get(1,1), R_swig.get(1,2)],
[R_swig.get(2,0), R_swig.get(2,1), R_swig.get(2,2)]])
H = np.block([ [R,T.reshape(3,1)], [np.zeros(3), 1] ])
# y-up to z-up
if direction=='zup':
H = H_zup @ H
# Convert matrix to loc and rot, and export to csv
loc_x, loc_y, loc_z = H[0:3,3]
R_mat = H[0:3,0:3]
sy = np.sqrt(R_mat[1,0]**2 + R_mat[0,0]**2) # singularity when y angle is +/- pi/2
if sy>1e-6:
rot_x = np.arctan2(R_mat[2,1], R_mat[2,2])
rot_y = np.arctan2(-R_mat[2,0], sy)
rot_z = np.arctan2(R_mat[1,0], R_mat[0,0])
else: # to be verified
rot_x = np.arctan2(-R_mat[1,2], R_mat[1,1])
rot_y = np.arctan2(-R[2,0], sy)
rot_z = 0
loc_rot_frame.extend([loc_x, loc_y, loc_z, rot_x, rot_y, rot_z])
loc_rot_frame_all.append(loc_rot_frame)
# Export to csv
loc_rot_frame_all_np = np.array(loc_rot_frame_all)
loc_rot_frame_all_np = np.insert(loc_rot_frame_all_np, 0, times, axis=1) # insert time column
bodyHeader = 'times, ' + ''.join([f'{b}_x, {b}_y, {b}_z, {b}_rotx, {b}_roty, {b}_rotz, ' for b in bodyNames])[:-2]
np.savetxt(os.path.splitext(output_csv_file)[0]+'.csv', loc_rot_frame_all_np, delimiter=',', header=bodyHeader)
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('-m', '--input_mot_file', required = True, help='input mot file')
parser.add_argument('-o', '--input_osim_file', required = True, help='input osim file')
parser.add_argument('-c', '--csv_output_file', required=False, help='csv output file')
args = vars(parser.parse_args())
2023-11-03 06:49:44 +08:00
bodykin_from_mot_osim_func(args)