Issues with IK (API)
Posted: Wed Apr 19, 2023 3:28 pm
Hello,
I am trying to use inverse kinematics on our lab's neck model (Vasavada) with data collected from subjects with 5 static poses each. I'm using template settings files for scaling and inverse kinematics so that I can verify that the API methods match what I expect from using the GUI. The only things really changing between them is the paths for in/out files.
My plan is:
1) scale the generic model using the neutral position
2) lock the ground translation/rotations (so that movement during IK must come from the neck joints) & add back vertebrae markerset that got deleted from scaling
3) do inverse kinematics for all the trials for that subject
4) get vertebral marker locations in ground frame to compare the spinal pose to our x-ray data.
This works fine for me using the GUI, but since we have so many trials I am trying to using the API through Python. I'm having a lot of issues with using the API for this.
My top issues are:
a) ScaleTool doesn't save the model with the registered markers.
When I use the scale settings file in the GUI, I see that it changes the coordinates (static pose). When I save this model, close it, and re-open it, the coordinates default back to zero. Correct me if I'm wrong, but this is because the coordinates are in the state, which isn't saved when I save the model. This is fine for the GUI. Not good for my API workflow which requires loading in models saved from ScaleTool/IkTool.
My workflow works for the GUI because I maintain state continuity, and can do my whole workflow without closing the model. I don't know if there is a simple way to maintain this state continuity between scaling, locking the markers, and inverse kinematics through the API.
b) I haven't seen a straightforward way to load a .mot file to a model using the API.
The Spline method inside prescribeMotionInModel.m works for me to be able to load a model, and have it maintain the state of the .mot from the inverse kinematics trial I'm looking at when I load it into the GUI. This is fine when I load my inverse kinematics .mot to it's own model to look at marker error, but it doesn't work when I want to load in the static pose coordinates to my model, then lock the ground movements, then do inverse kinematics.
Is there an easier way to load .mot into a model using the API?
This is an abdridged version of the Python code I have been using to run the simulations. The issue I am having with it is described in a). I just don't know how to get OpenSim to scale the model, keep using the static pose coordinate configuration, lock the ground translation/rotations (with the static pose configuration still present), then do inverse kinematics and save a model with that motion as the default values. Not sure if helpful, but since we are using static postures, I have the .trc file I load in for IK as going from the neutral posture @ t=1 to one of the four other postures @t=2.
Can anyone help me? I'd really appreciate it.
Nick Ozanich | Washington State University | Vasavada Lab
Python code: (OpenSim version 4.3-2021-08-27-4bc7ad9)
def write_model_with_motion(model, ikmot, new_filename):
# Adapted from prescribeMotionInModel.m
# Some extra variables for referencing
nCoordinates = model.getCoordinateSet().getSize()
coordinate_names = [i.getName() for i in model.getCoordinateSet()]
#coordinate_vals = [i.get_location().to_numpy() for i in model.getCoordinateSet()]
modelCoordSet = model.getCoordinateSet()
# Read IK post data
dataStorage = osim.Storage(ikmot)
# Some variables to store data
time = osim.ArrayDouble()
# need this to update time with time column from .mot file
size = dataStorage.getTimeColumn(time)
# Loop over each coordinate
# I only care about locking
for i in range(nCoordinates):
print(coordinate_names)
# Initialize OpenSim's ArrayDouble
coordvalue = osim.ArrayDouble()
# Get OpenSim's DataColumn
dataStorage.getDataColumn(coordinate_names, coordvalue)
# print(coordvalue.getSize()) # Number of time points
currentcoord = modelCoordSet.get(i)
# Get the type of motion for this coordinate
motType = model.getCoordinateSet().get(
coordinate_names).getMotionType()
Spline = osim.SimmSpline()
# Check unit for "Rotational" or "Coupled" coordinate in your case: rad or degree
# if translation (2)
if motType == 2:
for j in range(coordvalue.getSize()):
# data_array.append(coordvect.to_numpy())
Spline.addPoint(time.getitem(j), coordvalue.getitem(j))
### I'm not sure exactly if coupled is degrees or rads... Will need to confirm to make sure ###
# if rotational/coupled (1,3)
else:
for j in range(coordvalue.getSize()):
# data_array.append(np.deg2rad(coordvect.to_numpy()))
Spline.addPoint(time.getitem(
j), np.deg2rad(coordvalue.getitem(j)))
currentcoord.setPrescribedFunction(Spline)
currentcoord.setDefaultIsPrescribed(True)
model.printToXML(new_filename)
return model
#########################################
# Start of script
# Initialize our model and get the state
model = osim.Model(model_orig)
state = model.initSystem()
# Normally this is a loop over all subjects, but we are looking at subject 1 for this
# For this script, i refers to subject, j refers to which pose (1 through 5)
i = 1
# Read scale setup file
scaleSetupFile = f'subject{i}_scale_settings.xml'
# Run ScaleTool
scaleTool = osim.ScaleTool(scaleSetupFile)
scaleTool.run()
print('ScaleTool finished!')
# Get path for our newly made model
scaled_model_path = os.path.join('scaled_models', f'subject{i}_scaled.osim')
origin_markers = osim.MarkerSet('c_origin_markers.xml')
scale_mot_path = os.path.join('scale_mot_files', f'subject{i}_scale_mot.mot')
# Edits the .osim file to lock the ground translations/rotations
# These are all custom Python functions. All they do is change the locked value inside the model to True from False.
root = get_xml_format(scaled_model_path)
root = lock_osim_ground_movement(root)
write_opensim_xml_format(root, scaled_model_path)
# Iterate over our postures for IK
for j in range(1, 5):
# Read IK setup file
ikSetupFile = f'subject{i}_ik{j}_settings.xml'
# Load in new model
scaled_model = osim.Model(scaled_model_path)
state = scaled_model.initSystem()
# Run IKTool
print('Running IKTool')
ikTool = osim.InverseKinematicsTool(ikSetupFile)
ikTool.setModel(scaled_model)
ikTool.run()
print('IKTool finished!')
for j in range(1, 5):
# Our paths for this Ik Trial
# Saving this result as its own .osim model
scaled_model = osim.Model(scaled_model_path)
state = scaled_model.initSystem()
model.setName(f'subject{i}_ik{j}')
# Define the path to this trial's model
new_posture_model_path = os.path.join('scaled_models',
'models_with_posture',
f'subject{i}_trial{j}_model.osim')
# Add our vertebrae markers
model.updateMarkerSet(origin_markers)
ikmot = os.path.join('ik_mot_files',
f'subject{i}_ik{j}_mot.mot')
write_model_with_motion(model, ikmot, new_posture_model_path)
I am trying to use inverse kinematics on our lab's neck model (Vasavada) with data collected from subjects with 5 static poses each. I'm using template settings files for scaling and inverse kinematics so that I can verify that the API methods match what I expect from using the GUI. The only things really changing between them is the paths for in/out files.
My plan is:
1) scale the generic model using the neutral position
2) lock the ground translation/rotations (so that movement during IK must come from the neck joints) & add back vertebrae markerset that got deleted from scaling
3) do inverse kinematics for all the trials for that subject
4) get vertebral marker locations in ground frame to compare the spinal pose to our x-ray data.
This works fine for me using the GUI, but since we have so many trials I am trying to using the API through Python. I'm having a lot of issues with using the API for this.
My top issues are:
a) ScaleTool doesn't save the model with the registered markers.
When I use the scale settings file in the GUI, I see that it changes the coordinates (static pose). When I save this model, close it, and re-open it, the coordinates default back to zero. Correct me if I'm wrong, but this is because the coordinates are in the state, which isn't saved when I save the model. This is fine for the GUI. Not good for my API workflow which requires loading in models saved from ScaleTool/IkTool.
My workflow works for the GUI because I maintain state continuity, and can do my whole workflow without closing the model. I don't know if there is a simple way to maintain this state continuity between scaling, locking the markers, and inverse kinematics through the API.
b) I haven't seen a straightforward way to load a .mot file to a model using the API.
The Spline method inside prescribeMotionInModel.m works for me to be able to load a model, and have it maintain the state of the .mot from the inverse kinematics trial I'm looking at when I load it into the GUI. This is fine when I load my inverse kinematics .mot to it's own model to look at marker error, but it doesn't work when I want to load in the static pose coordinates to my model, then lock the ground movements, then do inverse kinematics.
Is there an easier way to load .mot into a model using the API?
This is an abdridged version of the Python code I have been using to run the simulations. The issue I am having with it is described in a). I just don't know how to get OpenSim to scale the model, keep using the static pose coordinate configuration, lock the ground translation/rotations (with the static pose configuration still present), then do inverse kinematics and save a model with that motion as the default values. Not sure if helpful, but since we are using static postures, I have the .trc file I load in for IK as going from the neutral posture @ t=1 to one of the four other postures @t=2.
Can anyone help me? I'd really appreciate it.
Nick Ozanich | Washington State University | Vasavada Lab
Python code: (OpenSim version 4.3-2021-08-27-4bc7ad9)
def write_model_with_motion(model, ikmot, new_filename):
# Adapted from prescribeMotionInModel.m
# Some extra variables for referencing
nCoordinates = model.getCoordinateSet().getSize()
coordinate_names = [i.getName() for i in model.getCoordinateSet()]
#coordinate_vals = [i.get_location().to_numpy() for i in model.getCoordinateSet()]
modelCoordSet = model.getCoordinateSet()
# Read IK post data
dataStorage = osim.Storage(ikmot)
# Some variables to store data
time = osim.ArrayDouble()
# need this to update time with time column from .mot file
size = dataStorage.getTimeColumn(time)
# Loop over each coordinate
# I only care about locking
for i in range(nCoordinates):
print(coordinate_names)
# Initialize OpenSim's ArrayDouble
coordvalue = osim.ArrayDouble()
# Get OpenSim's DataColumn
dataStorage.getDataColumn(coordinate_names, coordvalue)
# print(coordvalue.getSize()) # Number of time points
currentcoord = modelCoordSet.get(i)
# Get the type of motion for this coordinate
motType = model.getCoordinateSet().get(
coordinate_names).getMotionType()
Spline = osim.SimmSpline()
# Check unit for "Rotational" or "Coupled" coordinate in your case: rad or degree
# if translation (2)
if motType == 2:
for j in range(coordvalue.getSize()):
# data_array.append(coordvect.to_numpy())
Spline.addPoint(time.getitem(j), coordvalue.getitem(j))
### I'm not sure exactly if coupled is degrees or rads... Will need to confirm to make sure ###
# if rotational/coupled (1,3)
else:
for j in range(coordvalue.getSize()):
# data_array.append(np.deg2rad(coordvect.to_numpy()))
Spline.addPoint(time.getitem(
j), np.deg2rad(coordvalue.getitem(j)))
currentcoord.setPrescribedFunction(Spline)
currentcoord.setDefaultIsPrescribed(True)
model.printToXML(new_filename)
return model
#########################################
# Start of script
# Initialize our model and get the state
model = osim.Model(model_orig)
state = model.initSystem()
# Normally this is a loop over all subjects, but we are looking at subject 1 for this
# For this script, i refers to subject, j refers to which pose (1 through 5)
i = 1
# Read scale setup file
scaleSetupFile = f'subject{i}_scale_settings.xml'
# Run ScaleTool
scaleTool = osim.ScaleTool(scaleSetupFile)
scaleTool.run()
print('ScaleTool finished!')
# Get path for our newly made model
scaled_model_path = os.path.join('scaled_models', f'subject{i}_scaled.osim')
origin_markers = osim.MarkerSet('c_origin_markers.xml')
scale_mot_path = os.path.join('scale_mot_files', f'subject{i}_scale_mot.mot')
# Edits the .osim file to lock the ground translations/rotations
# These are all custom Python functions. All they do is change the locked value inside the model to True from False.
root = get_xml_format(scaled_model_path)
root = lock_osim_ground_movement(root)
write_opensim_xml_format(root, scaled_model_path)
# Iterate over our postures for IK
for j in range(1, 5):
# Read IK setup file
ikSetupFile = f'subject{i}_ik{j}_settings.xml'
# Load in new model
scaled_model = osim.Model(scaled_model_path)
state = scaled_model.initSystem()
# Run IKTool
print('Running IKTool')
ikTool = osim.InverseKinematicsTool(ikSetupFile)
ikTool.setModel(scaled_model)
ikTool.run()
print('IKTool finished!')
for j in range(1, 5):
# Our paths for this Ik Trial
# Saving this result as its own .osim model
scaled_model = osim.Model(scaled_model_path)
state = scaled_model.initSystem()
model.setName(f'subject{i}_ik{j}')
# Define the path to this trial's model
new_posture_model_path = os.path.join('scaled_models',
'models_with_posture',
f'subject{i}_trial{j}_model.osim')
# Add our vertebrae markers
model.updateMarkerSet(origin_markers)
ikmot = os.path.join('ik_mot_files',
f'subject{i}_ik{j}_mot.mot')
write_model_with_motion(model, ikmot, new_posture_model_path)