API  4.5.1
For C++ developers
examplePolynomialPathFitter.py

This example demonstrates how to use PolynomialPathFitter to create a set of FunctionBasedPaths for a model.

1 # -------------------------------------------------------------------------- #
2 # OpenSim: examplePolynomialPathFitter.py #
3 # -------------------------------------------------------------------------- #
4 # The OpenSim API is a toolkit for musculoskeletal modeling and simulation. #
5 # See http://opensim.stanford.edu and the NOTICE file for more information. #
6 # OpenSim is developed at Stanford University and supported by the US #
7 # National Institutes of Health (U54 GM072970, R24 HD065690) and by DARPA #
8 # through the Warrior Web program. #
9 # #
10 # Copyright (c) 2005-2023 Stanford University and the Authors #
11 # Author(s): Nicholas Bianco #
12 # #
13 # Licensed under the Apache License, Version 2.0 (the "License"); you may #
14 # not use this file except in compliance with the License. You may obtain a #
15 # copy of the License at http://www.apache.org/licenses/LICENSE-2.0. #
16 # #
17 # Unless required by applicable law or agreed to in writing, software #
18 # distributed under the License is distributed on an "AS IS" BASIS, #
19 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
20 # See the License for the specific language governing permissions and #
21 # limitations under the License. #
22 # -------------------------------------------------------------------------- #
23 
24 import os
25 import opensim as osim
26 from examplePolynomialPathFitter_plotting import (plot_coordinate_samples,
27  plot_path_lengths,
28  plot_moment_arms)
29 
30 # This example demonstrates how to use the PolynomialPathFitter class to create
31 # function-based representations of muscle-tendon lengths and moment arms using
32 # multivariate polynomial functions.
33 
34 # Create the PolynomialPathFitter
35 # -------------------------------
36 fitter = osim.PolynomialPathFitter()
37 
38 # Set the model.
39 #
40 # The model should contain path-based force objects (e.g., Muscles) that use
41 # geometry-based paths (e.g., GeometryPath) to model path lengths and moment
42 # arms. The fitter will create a set of FunctionBasedPaths that use
43 # MultivariatePolynomialFunctions to model the path lengths and moment arms
44 # of the original model.
45 model = osim.Model('subject_walk_scaled.osim')
46 model.initSystem()
47 fitter.setModel(osim.ModelProcessor(model))
48 
49 # Set the coordinate values table.
50 #
51 # The fitter will randomly sample around the coordinate values provided in the
52 # table to generate model configurations for which to compute path lengths and
53 # moment arms. This table has many more rows than are needed for the fitter to
54 # generate a good fit, so we will remove some of the rows to speed up the
55 # fitting process.
56 values = osim.TimeSeriesTable('coordinates.sto')
57 times = values.getIndependentColumn()
58 for i in range(len(times)):
59  if i % 10 != 0:
60  values.removeRow(times[i])
61 
62 fitter.setCoordinateValues(osim.TableProcessor(values))
63 
64 # Configure optional settings
65 # ---------------------------
66 # Use these settings to modify the default settings to tailor the fitting
67 # process to your model and motion. See the documentation for
68 # PolynomialPathFitter for more options.
69 
70 # Set a (relative) directory to where the fitting results will be saved.
71 # Files printed to this directory include the set of FunctionBasedPaths
72 # created by the fitter, the path lengths and moment arms computed for each
73 # model configuration, and the path lengths and moment arms fitted to the
74 # polynomial functions. File names will be prepended with the name of the
75 # model.
76 results_dir = 'results'
77 fitter.setOutputDirectory(results_dir)
78 
79 # Set the maximum order of the polynomials used to fit the path lengths
80 # and moment arms. Higher order polynomials might lead to a better fit,
81 # but could increase the computational time required to evaluate the
82 # path length functions.
83 fitter.setMaximumPolynomialOrder(5)
84 
85 # Set the number of random samples taken at each frame around the nominal
86 # coordinate values.
87 fitter.setNumSamplesPerFrame(10)
88 
89 # By default, coordinate values are sample around the nominal coordinate
90 # values using bounds of [-10, 10] degrees. You can set custom bounds for
91 # individual coordinates using the appendCoordinateSamplingBounds() method.
92 fitter.appendCoordinateSamplingBounds(
93  '/jointset/hip_r/hip_flexion_r', osim.Vec2(-15, 15))
94 fitter.appendCoordinateSamplingBounds(
95  '/jointset/hip_l/hip_flexion_l', osim.Vec2(-15, 15))
96 
97 # Set the global coordinate sampling bounds. This will be used for any
98 # coordinates that do not have custom bounds set. We use reasonably
99 # large bounds here to sample a wide range of the model's coordinate space
100 # around the reference trajectory.
101 fitter.setGlobalCoordinateSamplingBounds(osim.Vec2(-30, 30))
102 
103 # Use stepwise regression to fit the path lengths and moment arms. This
104 # setting evaluates the fit after adding polynomial terms one at a time
105 # to determine the minimum number of coefficients needed to achieve the
106 # path length and moment arm tolerances. Stepwise regression includes
107 # polynomial terms up to the maximum order set by
108 # setMaximumPolynomialOrder().
109 fitter.setUseStepwiseRegression(True)
110 
111 # Set the path length and moment arm tolerances. When the RMS errors
112 # between the original path lengths and moment arms and the fitted
113 # polynomial paths are below these tolerances, the fitting process will
114 # stop for a given path. Tighter tolerances may result in a better fit,
115 # but at the expense of higher polynomial orders (or more polynomial
116 # terms, if using stepwise regression).
117 fitter.setPathLengthTolerance(1e-3)
118 fitter.setMomentArmTolerance(1e-3)
119 
120 # Run the fitter
121 # --------------
122 # Information about each step fitting process will be printed to the
123 # console including the path length and moment arm RMS error for
124 # each force object and averaged across all force objects.
125 fitter.run()
126 
127 # Plot the results
128 # ----------------
129 # Use the plotting helper functions to visualize the results of the
130 # fitting process and determine if the fits are good enough for your needs,
131 # or if the model or fitting settings need to be modified.
132 
133 # Plot the sampled coordinate values used to generate the path lengths
134 # and moment arms.
135 plot_coordinate_samples(results_dir, model.getName())
136 
137 # Plot the path lengths and moment arms computed from the original model
138 # paths (blue) and the fitted polynomial paths (orange).
139 #
140 # For most muscles the fit is very good, but there are noticeable fitting
141 # errors in a few muscles (e.g., /forceset/gaslat_r and /forceset/glmax1_r).
142 # Errors like these usually arise from the fitting process struggling with
143 # discontinuities due to wrapping geometry issues in the original model.
144 # Depending on size of the errors, you may want to adjust the wrapping
145 # geometry in the original model and re-run the fitter.
146 plot_path_lengths(results_dir, model.getName())
147 plot_moment_arms(results_dir, model.getName())
148 
149 # Evaluate the fitted functions on a 'new' trajectory
150 # ---------------------------------------------------
151 # You can use PolynomialPathFitter to evaluate a set of previously fitted
152 # FunctionBasedPaths on a new trajectory. This can be useful if you want to
153 # evaluate the path lengths and moment arms of a model on a trajectory that
154 # was not used to fit the functions. This example uses the same trajectory
155 # used to fit the functions, but you can replace it with any trajectory
156 # that has a set of coordinate values that are consistent with the model.
157 # Note that we do not need to use the 'fitter' object to call this function
158 # (i.e., it is a static function).
159 functionBasedPathsFile = os.path.join(
160  results_dir, f'{model.getName()}_FunctionBasedPathSet.xml')
161 osim.PolynomialPathFitter.evaluateFunctionBasedPaths(
162  model, osim.TableProcessor(values), functionBasedPathsFile)
163 
164 # Replacing the original paths with the fitted paths
165 # --------------------------------------------------
166 # You can use a ModelProcessor to replace the original paths in the model
167 # with the fitted paths. This can be useful if you want to use the fitted
168 # paths in a simulation or analysis tool but keep the original paths in the
169 # model file.
170 modelProcessor = osim.ModelProcessor('subject_walk_scaled.osim')
171 modelProcessor.append(osim.ModOpReplacePathsWithFunctionBasedPaths(
172  functionBasedPathsFile))
173 model = modelProcessor.process()
174 model.initSystem()
175 model.printToXML('subject_walk_scaled_fitted_paths.osim')