Tracking IK instead of states

OpenSim Moco is a software toolkit to solve optimal control problems with musculoskeletal models defined in OpenSim using the direct collocation method.
POST REPLY
User avatar
Grace M
Posts: 27
Joined: Thu Jul 28, 2016 11:11 am

Tracking IK instead of states

Post by Grace M » Fri Sep 03, 2021 10:03 pm

Hi there,

I am wondering if I can use an IK/.mot file instead of states/.sto file as a tracking goal?

Thanks

User avatar
Ross Miller
Posts: 371
Joined: Tue Sep 22, 2009 2:02 pm

Re: Tracking IK instead of states

Post by Ross Miller » Sat Sep 04, 2021 4:16 am

Hi Grace,

For kinematics, MocoTrack can track marker coordinates and/or "processed" kinematic data, e.g. joint angles.

Anecdotally, I've had better results (more consistent convergence) tracking processed data. It also avoids the step of having to match your model's marker set to your data, which can be laborious if the model doesn't already have a marker set or if you data's set doesn't match the model's.

Ross

User avatar
Aaron Fox
Posts: 272
Joined: Sun Aug 06, 2017 10:54 pm

Re: Tracking IK instead of states

Post by Aaron Fox » Sun Sep 05, 2021 4:48 pm

Hi Grace,

Agree with Ross - I've found tracking the 'processed' kinematic data to work better. I think I can see one issue you might have been querying, in that IK results come in the .mot format and report the data as 'coordinates' rather than 'states'. I wrote a brief Python function into some of my pipelines that converted an IK .mot file to a states .sto file - so it may be helpful to see this process:

Code: Select all

def kinematicsToStates(kinematicsFileName = None, osimModelFileName = None,
                       outputFileName = 'coordinates.sto',
                       inDegrees = True, outDegrees = False):
    
    # Convenience function for converting IK results to a states storage.
    #
    # Input:    kinematicsFileName - file containing kinematic data. Header should only be coordinates name, rather than path to state
    #           osimModelFileName - opensim model filename that corresponds to kinematic data
    #           outputFileName - optional filename to output to (defaults to coordinates.sto)
    #           inDegrees - set to true if kinematics file is in degrees (defaults to True)
    #           outDegrees - set to true if desired output is in degrees (defaults to False)

    if kinematicsFileName is None:
        raise ValueError('Filename for kinematics is required')
    if osimModelFileName is None:
        raise ValueError('OpenSim model filename is required')

    #Load in the kinematic data
    kinematicsStorage = osim.Storage(kinematicsFileName)
    
    #Create a copy of the kinematics data to alter the column labels in
    statesStorage = osim.Storage(kinematicsFileName)
    
    #Resample the data points linearly to avoid any later issues with matching
    #time points. Use a time stamp for 250 Hz
    kinematicsStorage.resampleLinear(1/250)
    statesStorage.resampleLinear(1/250)
    
    #Get the column headers for the storage file
    angleNames = kinematicsStorage.getColumnLabels()
    
    #Get the corresponding full paths from the model to rename the
    #angles in the kinematics file
    kinematicModel = osim.Model(osimModelFileName)
    for ii in range(0,angleNames.getSize()):
        currAngle = angleNames.get(ii)
        if currAngle != 'time':
            #Get full path to coordinate
            fullPath = kinematicModel.updCoordinateSet().get(currAngle).getAbsolutePathString()+'/value'
            #Set angle name appropriately using full path
            angleNames.set(ii,fullPath)
    
    #Set the states storage object to have the updated column labels
    statesStorage.setColumnLabels(angleNames)
    
    #Appropriately set output in degrees or radians
    if inDegrees and not outDegrees:
        #Convert degrees values to radians for consistency with the current
        #file label (defaults back to inDegrees=no). Radians seem to work
        #better with the Moco process as well.
        kinematicModel.initSystem()
        kinematicModel.getSimbodyEngine().convertDegreesToRadians(statesStorage)
    elif inDegrees and outDegrees:
        #Change the storage label back to specifying indegrees=yes
        statesStorage.setInDegrees(True)
    elif not inDegrees and outDegrees:
        #Convert radians to degrees
        kinematicModel.initSystem()
        kinematicModel.getSimbodyEngine().convertRadiansToDegrees(statesStorage)
        #Reset labeling for degrees
        statesStorage.setInDegrees(True)
    
    #Write the states storage object to file
    statesStorage.printToXML(outputFileName)
Aaron

User avatar
Grace M
Posts: 27
Joined: Thu Jul 28, 2016 11:11 am

Re: Tracking IK instead of states

Post by Grace M » Sun Sep 05, 2021 10:24 pm

Thanks Aaron and Ross that is very helpful! I will try out the code and try to convert it for use in MATLAB.

Cheers,
Grace

User avatar
Grace M
Posts: 27
Joined: Thu Jul 28, 2016 11:11 am

Re: Tracking IK instead of states

Post by Grace M » Tue Sep 07, 2021 7:47 pm

Hi again,

I have converted your code for use in MATLAB - see below. However when I write the file the first line of data is indented which I think is causing an issue when I try to solve a MocoInverse problem. I am getting the error :
Java exception occurred:
java.lang.RuntimeException: SimTK Exception thrown at FactorQTZ.cpp:325:
Bad call to Simbody API method FactorQTZ::factor(): Can't factor a matrix that has a zero dimension -- got 8 X 0.
(Required condition 'mat.nelt() > 0' was not met.)

at org.opensim.modeling.opensimMocoJNI.MocoInverse_solve(Native Method)

at org.opensim.modeling.MocoInverse.solve(MocoInverse.java:451)
Is there a workaround for this? Or would this error be due to some other issue?

Thanks


Code: Select all

function ConvertIKtoSTO(kinematicsFileName, osimModelFileName, ...
                       outputFileName,    inDegrees , outDegrees )
    
    % Convenience function for converting IK results to a states storage.
    %
    % Input:    kinematicsFileName - file containing kinematic data. Header should only be coordinates name, rather than path to state
    %           osimModelFileName - opensim model filename that corresponds to kinematic data
    %           outputFileName - optional filename to output to (defaults to coordinates.sto)
    %           inDegrees - set to true if kinematics file is in degrees (defaults to True)
    %           outDegrees - set to true if desired output is in degrees (defaults to False)

import org.opensim.modeling.*;


    %Load in the kinematic data
    kinematicsStorage = Storage(kinematicsFileName);
    
    %Create a copy of the kinematics data to alter the column labels in
    statesStorage = Storage(kinematicsFileName);
    
    %Resample the data points linearly to avoid any later issues with matching
    %time points. Use a time stamp for 250 Hz
    kinematicsStorage.resampleLinear(1/250)
    statesStorage.resampleLinear(1/250)
    
    %Get the column headers for the storage file
    angleNames = kinematicsStorage.getColumnLabels();
    
    %Get the corresponding full paths from the model to rename the
    %angles in the kinematics file
    kinematicModel = Model(osimModelFileName);
    for ii=1:size(angleNames.getSize(),1)
        currAngle = angleNames.get(ii);
        if currAngle ~= 'time'
            %Get full path to coordinate
            fullPath = [char(kinematicModel.updCoordinateSet().get(currAngle).getAbsolutePathString()) '/value'];
            %Set angle name appropriately using full path
            angleNames.set(ii,fullPath)
        end
    end
    
    
    %Set the states storage object to have the updated column labels
    statesStorage.setColumnLabels(angleNames)
    
    %Appropriately set output in degrees or radians
    if inDegrees ==1 && outDegrees~=1
        %Convert degrees values to radians for consistency with the current
        %file label (defaults back to inDegrees=no). Radians seem to work
        %better with the Moco process as well.
        kinematicModel.initSystem()
        kinematicModel.getSimbodyEngine().convertDegreesToRadians(statesStorage)
    elseif inDegrees ==1 &&  outDegrees ==1 
        %Change the storage label back to specifying indegrees=yes
        statesStorage.setInDegrees(true)
    elseif inDegrees ~=1 &&  outDegrees ~=1 
        %Convert radians to degrees
        kinematicModel.initSystem()
        kinematicModel.getSimbodyEngine().convertRadiansToDegrees(statesStorage)
        %Reset labeling for degrees
        statesStorage.setInDegrees(True)
    end
   %Write the states storage object to file
    statesStorage.print(outputFileName)
        end

User avatar
Evan Dooley
Posts: 25
Joined: Fri Sep 27, 2019 8:36 am

Re: Tracking IK instead of states

Post by Evan Dooley » Tue Oct 05, 2021 2:02 pm

Hi Grace,

I also am working on setting up a MocoInverse problem and have been running into issues. I got a similar error you have shown trying to put this together.
Error using MocoInverse_TorqueDrive_CoordTrack_v02 (line 109)
Java exception occurred:
java.lang.RuntimeException: SimTK Exception thrown at FactorQTZ.cpp:325:
Bad call to Simbody API method FactorQTZ::factor(): Can't factor a matrix that has a zero dimension -- got 4 X 0.
(Required condition 'mat.nelt() > 0' was not met.)

at org.opensim.modeling.opensimMocoJNI.MocoInverse_solve(Native Method)

at org.opensim.modeling.MocoInverse.solve(MocoInverse.java:451)
I tried using the code you put up to convert my MOT file and when I ran it initially it only updated the column name for the first coordinate. I modified the loop to this section and now it converts my MOT file to one similar to the "coorditnates.sto" given in the 3Dwalking example.

Code: Select all

for ii=1:angleNames.getSize()-1 % time col was skipped
        currAngle = angleNames.get(ii);
        %Get full path to coordinate
        fullPath = [char(kinematicModel.updCoordinateSet().get(currAngle).getAbsolutePathString()) '/value'];
        %Set angle name appropriately using full path
        angleNames.set(ii,fullPath)
end
Even with this adjustment I am still getting the same error when I run inverse.solve().

Any chance you have figured out how to debug this error? I am guessing that my model is creating a singularity, but I am unsure how I can go about tracking this down in Moco.

Thanks for sharing your work so far.

Best,
Evan

User avatar
Nicholas Bianco
Posts: 972
Joined: Thu Oct 04, 2012 8:09 pm

Re: Tracking IK instead of states

Post by Nicholas Bianco » Tue Oct 05, 2021 2:43 pm

Hi Grace and Evan,

Have you made any modifications to this model for your MocoInverse problems (e.g., changed joint definitions or modified kinematic constraints)? This error seems model specific, not related to Moco.

-Nick

User avatar
Evan Dooley
Posts: 25
Joined: Fri Sep 27, 2019 8:36 am

Re: Tracking IK instead of states

Post by Evan Dooley » Tue Oct 05, 2021 2:58 pm

Hello,

I have not changed the joint definitions, but I think it is a model specific thing.

I believe it is due to the subtalar joints still being locked in the model I was trying to simulate. Reading more on the forum this seems to cause various issues, so I tried unlocking these joints.

After releasing the subtalar joints, Moco attempts to solve.

(at the moment it fails, but I'm still digging into that)

Thanks for your quick response Nick!

-Evan

User avatar
Nicholas Bianco
Posts: 972
Joined: Thu Oct 04, 2012 8:09 pm

Re: Tracking IK instead of states

Post by Nicholas Bianco » Tue Oct 05, 2021 3:06 pm

Hi Evan,

You're welcome! The recommended practice for locked joints (if you wish to ignore them in your simulations), is to replace these joints with a WeldJoint. Moco has a ModelOperator called ModOpReplaceJointsWithWelds that you can use to do this.

-Nick

User avatar
Evan Dooley
Posts: 25
Joined: Fri Sep 27, 2019 8:36 am

Re: Tracking IK instead of states

Post by Evan Dooley » Tue Oct 05, 2021 4:52 pm

Awesome!

I have made a note and will give that function a try.

For completeness, my simulation was not converging because I had the max/min forces on the controllers set way too low. I opened them up to -inf to +inf and Moco quickly converged to a reasonable solution.

Thanks for the tips!

Best,
Evan

POST REPLY