API  4.5.1
For C++ developers
exampleMocoTrack.py

This is an example using the MocoTrack tool with a complex model to track walking.

1 # -------------------------------------------------------------------------- #
2 # OpenSim Moco: exampleMocoTrack.py #
3 # -------------------------------------------------------------------------- #
4 # Copyright (c) 2023 Stanford University and the Authors #
5 # #
6 # Author(s): Nicholas Bianco #
7 # #
8 # Licensed under the Apache License, Version 2.0 (the "License") you may #
9 # not use this file except in compliance with the License. You may obtain a #
10 # copy of the License at http://www.apache.org/licenses/LICENSE-2.0 #
11 # #
12 # Unless required by applicable law or agreed to in writing, software #
13 # distributed under the License is distributed on an "AS IS" BASIS, #
14 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
15 # See the License for the specific language governing permissions and #
16 # limitations under the License. #
17 # -------------------------------------------------------------------------- #
18 
19 # This example features two different tracking problems solved using the
20 # MocoTrack tool.
21 # - The first problem demonstrates the basic usage of the tool interface
22 # to solve a torque-driven marker tracking problem.
23 # - The second problem shows how to customize a muscle-driven state tracking
24 # problem using more advanced features of the tool interface.
25 # - The third problem demonstrates how to solve a muscle-driven joint moment
26 # tracking problem.
27 #
28 # See the README.txt next to this file for more information.
29 
30 import os
31 import opensim as osim
32 
33 def torqueDrivenMarkerTracking():
34 
35  # Create and name an instance of the MocoTrack tool.
36  track = osim.MocoTrack()
37  track.setName("torque_driven_marker_tracking")
38 
39  # Construct a ModelProcessor and add it to the tool. ModelProcessors
40  # accept a base model and allow you to easily modify the model by appending
41  # ModelOperators. Operations are performed in the order that they are
42  # appended to the model.
43  # Create the base Model by passing in the model file.
44  modelProcessor = osim.ModelProcessor("subject_walk_scaled.osim")
45  # Add ground reaction external loads in lieu of a ground-contact model.
46  modelProcessor.append(osim.ModOpAddExternalLoads("grf_walk.xml"))
47  # Remove all the muscles in the model's ForceSet.
48  modelProcessor.append(osim.ModOpRemoveMuscles())
49  # Add CoordinateActuators to the model degrees-of-freedom. This ignores the
50  # pelvis coordinates which already have residual CoordinateActuators.
51  modelProcessor.append(osim.ModOpAddReserves(250.0, 1.0))
52  track.setModel(modelProcessor)
53 
54  # Use this convenience function to set the MocoTrack markers reference
55  # directly from a TRC file. By default, the markers data is filtered at
56  # 6 Hz and if in millimeters, converted to meters.
57  track.setMarkersReferenceFromTRC("marker_trajectories.trc")
58 
59  # There is marker data in the 'marker_trajectories.trc' associated with
60  # model markers that no longer exists (i.e. markers on the arms). Set this
61  # flag to avoid an exception from being thrown.
62  track.set_allow_unused_references(True)
63 
64  # Increase the global marker tracking weight, which is the weight
65  # associated with the internal MocoMarkerTrackingCost term.
66  track.set_markers_global_tracking_weight(10)
67 
68  # Increase the tracking weights for individual markers in the data set
69  # placed on bony landmarks compared to markers located on soft tissue.
70  markerWeights = osim.MocoWeightSet()
71  markerWeights.cloneAndAppend(osim.MocoWeight("R.ASIS", 20))
72  markerWeights.cloneAndAppend(osim.MocoWeight("L.ASIS", 20))
73  markerWeights.cloneAndAppend(osim.MocoWeight("R.PSIS", 20))
74  markerWeights.cloneAndAppend(osim.MocoWeight("L.PSIS", 20))
75  markerWeights.cloneAndAppend(osim.MocoWeight("R.Knee", 10))
76  markerWeights.cloneAndAppend(osim.MocoWeight("R.Ankle", 10))
77  markerWeights.cloneAndAppend(osim.MocoWeight("R.Heel", 10))
78  markerWeights.cloneAndAppend(osim.MocoWeight("R.MT5", 5))
79  markerWeights.cloneAndAppend(osim.MocoWeight("R.Toe", 2))
80  markerWeights.cloneAndAppend(osim.MocoWeight("L.Knee", 10))
81  markerWeights.cloneAndAppend(osim.MocoWeight("L.Ankle", 10))
82  markerWeights.cloneAndAppend(osim.MocoWeight("L.Heel", 10))
83  markerWeights.cloneAndAppend(osim.MocoWeight("L.MT5", 5))
84  markerWeights.cloneAndAppend(osim.MocoWeight("L.Toe", 2))
85  track.set_markers_weight_set(markerWeights)
86 
87  # Initial time, final time, and mesh interval. The number of mesh points
88  # used to discretize the problem is computed internally using these values.
89  track.set_initial_time(0.48)
90  track.set_final_time(1.61)
91  track.set_mesh_interval(0.02)
92 
93  # Solve! Use track.solve() to skip visualizing.
94  solution = track.solveAndVisualize()
95 
96 def muscleDrivenStateTracking():
97 
98  # Create and name an instance of the MocoTrack tool.
99  track = osim.MocoTrack()
100  track.setName("muscle_driven_state_tracking")
101 
102  # Construct a ModelProcessor and set it on the tool. The default
103  # muscles in the model are replaced with optimization-friendly
104  # DeGrooteFregly2016Muscles, and adjustments are made to the default muscle
105  # parameters.
106  modelProcessor = osim.ModelProcessor("subject_walk_scaled.osim")
107  modelProcessor.append(osim.ModOpAddExternalLoads("grf_walk.xml"))
108  modelProcessor.append(osim.ModOpIgnoreTendonCompliance())
109  modelProcessor.append(osim.ModOpReplaceMusclesWithDeGrooteFregly2016())
110  # Only valid for DeGrooteFregly2016Muscles.
111  modelProcessor.append(osim.ModOpIgnorePassiveFiberForcesDGF())
112  # Only valid for DeGrooteFregly2016Muscles.
113  modelProcessor.append(osim.ModOpScaleActiveFiberForceCurveWidthDGF(1.5))
114  # Use a function-based representation for the muscle paths. This is
115  # recommended to speed up convergence, but if you would like to use
116  # the original GeometryPath muscle wrapping instead, simply comment out
117  # this line. To learn how to create a set of function-based paths for
118  # your model, see the example 'examplePolynomialPathFitter.py'.
119  modelProcessor.append(osim.ModOpReplacePathsWithFunctionBasedPaths(
120  "subject_walk_scaled_FunctionBasedPathSet.xml"))
121  track.setModel(modelProcessor)
122 
123  # Construct a TableProcessor of the coordinate data and pass it to the
124  # tracking tool. TableProcessors can be used in the same way as
125  # ModelProcessors by appending TableOperators to modify the base table.
126  # A TableProcessor with no operators, as we have here, simply returns the
127  # base table.
128  track.setStatesReference(osim.TableProcessor("coordinates.sto"))
129 
130  # This setting allows extra data columns contained in the states
131  # reference that don't correspond to model coordinates.
132  track.set_allow_unused_references(True)
133 
134  # Since there is only coordinate position data in the states references,
135  # this setting is enabled to fill in the missing coordinate speed data using
136  # the derivative of splined position data.
137  track.set_track_reference_position_derivatives(True)
138 
139  # Initial time, final time, and mesh interval.
140  track.set_initial_time(0.48)
141  track.set_final_time(1.61)
142  track.set_mesh_interval(0.02)
143 
144  # Instead of calling solve(), call initialize() to receive a pre-configured
145  # MocoStudy object based on the settings above. Use this to customize the
146  # problem beyond the MocoTrack interface.
147  study = track.initialize()
148 
149  # Get a reference to the MocoControlCost that is added to every MocoTrack
150  # problem by default.
151  problem = study.updProblem()
152  effort = osim.MocoControlGoal.safeDownCast(problem.updGoal("control_effort"))
153  effort.setWeight(0.1)
154 
155  # Put a large weight on the pelvis CoordinateActuators, which act as the
156  # residual, or 'hand-of-god', forces which we would like to keep as small
157  # as possible.
158  model = modelProcessor.process()
159  model.initSystem()
160  forceSet = model.getForceSet()
161  for i in range(forceSet.getSize()):
162  forcePath = forceSet.get(i).getAbsolutePathString()
163  if 'pelvis' in str(forcePath):
164  effort.setWeightForControl(forcePath, 10)
165 
166  # Constrain the states and controls to be periodic.
167  periodicityGoal = osim.MocoPeriodicityGoal("periodicity")
168  for i in range(model.getNumStateVariables()):
169  currentStateName = str(model.getStateVariableNames().getitem(i))
170  if 'pelvis_tx/value' not in currentStateName:
171  periodicityGoal.addStatePair(osim.MocoPeriodicityGoalPair(currentStateName))
172 
173  forceSet = model.getForceSet()
174  for i in range(forceSet.getSize()):
175  forcePath = forceSet.get(i).getAbsolutePathString()
176  periodicityGoal.addControlPair(osim.MocoPeriodicityGoalPair(forcePath))
177 
178  problem.addGoal(periodicityGoal)
179 
180  # Update the solver problem and tolerances.
181  solver = osim.MocoCasADiSolver.safeDownCast(study.updSolver())
182  solver.set_optim_convergence_tolerance(1e-3)
183  solver.set_optim_constraint_tolerance(1e-4)
184  solver.resetProblem(problem)
185 
186  # Solve!
187  solution = study.solve()
188  solution.write('exampleMocoTrack_state_tracking_solution.sto')
189 
190  # Visualize the solution.
191  study.visualize(solution)
192 
193 def muscleDrivenJointMomentTracking():
194 
195  # Create and name an instance of the MocoTrack tool.
196  track = osim.MocoTrack()
197  track.setName('muscle_driven_joint_moment_tracking')
198 
199  # Construct a ModelProcessor and set it on the tool.
200  modelProcessor = osim.ModelProcessor('subject_walk_scaled.osim')
201  modelProcessor.append(osim.ModOpAddExternalLoads('grf_walk.xml'))
202  modelProcessor.append(osim.ModOpIgnoreTendonCompliance())
203  modelProcessor.append(osim.ModOpReplaceMusclesWithDeGrooteFregly2016())
204  modelProcessor.append(osim.ModOpIgnorePassiveFiberForcesDGF())
205  modelProcessor.append(osim.ModOpScaleActiveFiberForceCurveWidthDGF(1.5))
206  modelProcessor.append(osim.ModOpReplacePathsWithFunctionBasedPaths(
207  'subject_walk_scaled_FunctionBasedPathSet.xml'))
208  track.setModel(modelProcessor)
209 
210  # We will still track the coordinates trajectory, but with a lower weight.
211  track.setStatesReference(osim.TableProcessor('coordinates.sto'))
212  track.set_states_global_tracking_weight(0.1)
213  track.set_allow_unused_references(True)
214  track.set_track_reference_position_derivatives(True)
215 
216  # Initial time, final time, and mesh interval.
217  track.set_initial_time(0.48)
218  track.set_final_time(1.61)
219  track.set_mesh_interval(0.02)
220 
221  # Set the control effort weights.
222  controlsWeightSet = osim.MocoWeightSet()
223  model = modelProcessor.process()
224  model.initSystem()
225  forceSet = model.getForceSet()
226  for i in range(forceSet.getSize()):
227  forcePath = forceSet.get(i).getAbsolutePathString()
228  if 'pelvis' in str(forcePath):
229  controlsWeightSet.cloneAndAppend(osim.MocoWeight(forcePath, 10))
230 
231  track.set_control_effort_weight(0.1)
232  track.set_controls_weight_set(controlsWeightSet)
233 
234  # Get the underlying MocoStudy.
235  study = track.initialize()
236  problem = study.updProblem()
237 
238  # Constrain the states and controls to be periodic.
239  periodicityGoal = osim.MocoPeriodicityGoal('periodicity')
240  for i in range(model.getNumStateVariables()):
241  currentStateName = str(model.getStateVariableNames().getitem(i))
242  if 'pelvis_tx/value' not in currentStateName:
243  periodicityGoal.addStatePair(osim.MocoPeriodicityGoalPair(currentStateName))
244 
245  forceSet = model.getForceSet()
246  for i in range(forceSet.getSize()):
247  forcePath = forceSet.get(i).getAbsolutePathString()
248  periodicityGoal.addControlPair(osim.MocoPeriodicityGoalPair(forcePath))
249 
250  problem.addGoal(periodicityGoal)
251 
252  # Add a joint moment tracking goal to the problem.
253  jointMomentTracking = osim.MocoGeneralizedForceTrackingGoal(
254  'joint_moment_tracking', 1e-2)
255 
256  # Set the reference joint moments from an inverse dynamics solution and
257  # low-pass filter the data at 10 Hz. The reference data should use the
258  # same column label format as the output of the Inverse Dynamics Tool.
259  jointMomentRef = osim.TableProcessor('inverse_dynamics.sto')
260  jointMomentRef.append(osim.TabOpLowPassFilter(10))
261  jointMomentTracking.setReference(jointMomentRef)
262 
263  # Set the force paths that will be applied to the model to compute the
264  # generalized forces. Usually these are the external loads and actuators
265  # (e.g., muscles) should be excluded, but any model force can be included
266  # or excluded. Gravitational force is applied by default.
267  # Regular expression are supported when setting the force paths.
268  forcePaths = osim.StdVectorString()
269  forcePaths.append('.*externalloads.*')
270  jointMomentTracking.setForcePaths(forcePaths)
271 
272  # Allow unused columns in the reference data.
273  jointMomentTracking.setAllowUnusedReferences(True)
274 
275  # Normalize the tracking error for each generalized for by the maximum
276  # absolute value in the reference data for that generalized force.
277  jointMomentTracking.setNormalizeTrackingError(True)
278 
279  # Ignore coordinates that are locked, prescribed, or coupled to other
280  # coordinates via CoordinateCouplerConstraints (true by default).
281  jointMomentTracking.setIgnoreConstrainedCoordinates(True)
282  coordinateSet = model.getCoordinateSet()
283  for i in range(coordinateSet.getSize()):
284  coordinate = coordinateSet.get(i)
285  coordName = coordinate.getName()
286  # Don't track generalized forces associated with pelvis residuals.
287  if 'pelvis' in coordName:
288  jointMomentTracking.setWeightForCoordinate(coordName, 0)
289 
290  # Encourage better tracking of the ankle joint moments.
291  if 'ankle' in coordName:
292  jointMomentTracking.setWeightForCoordinate(coordName, 100)
293 
294  problem.addGoal(jointMomentTracking)
295 
296  # Update the solver tolerances.
297  solver = osim.MocoCasADiSolver.safeDownCast(study.updSolver())
298  solver.set_optim_convergence_tolerance(1e-3)
299  solver.set_optim_constraint_tolerance(1e-4)
300  solver.resetProblem(problem)
301 
302  # Solve!
303  solution = study.solve()
304  solution.write('exampleMocoTrack_joint_moment_tracking_solution.sto')
305 
306  # Save the model to a file.
307  model.printToXML('exampleMocoTrack_model.osim')
308 
309  # Compute the joint moments and write them to a file.
310  forcePaths = osim.StdVectorString()
311  forcePaths.append('.*externalloads.*')
312  jointMoments = study.calcGeneralizedForces(solution, forcePaths)
313  osim.STOFileAdapter.write(jointMoments, 'exampleMocoTrack_joint_moments.sto')
314 
315  # Visualize the solution.
316  study.visualize(solution)
317 
318 
319 # Solve the torque-driven marker tracking problem.
320 torqueDrivenMarkerTracking()
321 
322 # Solve the muscle-driven state tracking problem.
323 muscleDrivenStateTracking()
324 
325 # Solve the muscle-driven joint moment tracking problem.
326 muscleDrivenJointMomentTracking()