Issues with IK (API)

Provide easy-to-use, extensible software for modeling, simulating, controlling, and analyzing the neuromusculoskeletal system.
POST REPLY
User avatar
Nicholas Ozanich
Posts: 4
Joined: Wed Jun 08, 2022 9:48 am

Issues with IK (API)

Post by Nicholas Ozanich » 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)

Tags:

User avatar
Mohammadreza Rezaie
Posts: 393
Joined: Fri Nov 24, 2017 12:48 am

Re: Issues with IK (API)

Post by Mohammadreza Rezaie » Thu Apr 20, 2023 5:54 am

Hi,
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.
When the model is scaled followed by markers registration using the Scale tool, the static pose doesn't matter. However, you can set the static pose as the default motion in GUI using: Coordinates > Poses > Set Default and then save the model. Or in Python:

Code: Select all

model = osim.Model('subject01_simbody.osim') # scaled model
static = osim.TimeSeriesTable('subject01_static_output.mot') # static position output
for i in model.getCoordinateSet():
	i.set_default_value(static.getDependentColumn(i.getAbsolutePathString()+'/value').getElt(0,0))
model.printToXML('modified.osim')
lock the ground translation/rotations
This is an example of that:

Code: Select all

for coordinate in model.getCoordinateSet():
	if 'pelvis' in coordinate.getName():
		coordinate.set_locked(True)
add back vertebrae markerset that got deleted from scaling
You are adding unregistered markers from the generic model to your scaled model, and hence, these markers are no longer useful. If any of the model's markers do not exist in your experimental data, they will be ignored (deleted) from the scaled model. Try to include all your desired markers in your experimental data. A workaround might be to set the back vertebrae markers fixed in your generic model, add equivalent virtual markers with e.g. zero coordinates to your static TRC file, un-checked these markers in the "Static Pose Weight" tab of the Scale tool, and then run the Scale tool. In this case, these markers remain in your scaled model but in the appropriate location.
Here, I'm fixing a specific marker of the generic model:

Code: Select all

model.getMarkerSet().get('Top.Head').set_fixed(True)
And here I'm adding an equivalent virtual marker to the static TRC file:

Code: Select all

m = osim.TimeSeriesTableVec3('subject01_static.trc')
time = m.getIndependentColumn()
newColumn = osim.VectorVec3([osim.Vec3(0,0,0) for i in range(len(time))])
m.appendColumn('Top.Head', newColumn)
osim.TRCFileAdapter().write(m, 'new.trc')
get vertebral marker locations in ground frame
This is an example of getting markers' location in the ground at every time step:

Code: Select all

state = model.initSystem()
ik = osim.TimeSeriesTable('subject01_walk1_ik.mot') # IK output
model.getSimbodyEngine().convertDegreesToRadians(ik) # convert to Radians
time = ik.getIndependentColumn()
q = dict() # joint angles values
for i in ik.getColumnLabels():
	q[i] = ik.getDependentColumn(i)

markerData = dict()
for i,ii in enumerate(time):
	for coordinate in model.getCoordinateSet():
		name = coordinate.getName()
		coordinate.setValue(state, q[name].getElt(0,I), False)
	model.assemble(state)
	
	for marker in model.getMarkerSet():
		name = marker.getName()
		if marker.getName() not in markerData.keys():
			markerData[name] = np.zeros((len(time),3))
		markerData[name][i,:] = marker.getLocationInGround(state).to_numpy()
Hope this helps,
Mohammadreza

User avatar
Nicholas Ozanich
Posts: 4
Joined: Wed Jun 08, 2022 9:48 am

Re: Issues with IK (API)

Post by Nicholas Ozanich » Fri Apr 21, 2023 10:13 am

Mohammadreza,

Thank you so much for your help. Your feedback was very insightful on utilizing the OpenSim API. I was able to implement all of your suggestions and I was able to get the API scripting to behave how I was doing it with the GUI.
Best,
Nick Ozanich

POST REPLY