API  4.5
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 % 5 != 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 # By default, coordinate values are sample around the nominal coordinate
86 # values using bounds of [-10, 10] degrees. You can set custom bounds for
87 # individual coordinates using the appendCoordinateSamplingBounds() method.
88 fitter.appendCoordinateSamplingBounds(
89  '/jointset/hip_r/hip_flexion_r', osim.Vec2(-15, 15))
90 fitter.appendCoordinateSamplingBounds(
91  '/jointset/hip_l/hip_flexion_l', osim.Vec2(-15, 15))
92 
93 # Run the fitter
94 # --------------
95 # Information about each step fitting process will be printed to the
96 # console including the path length and moment arm RMS error for
97 # each force object and averaged across all force objects.
98 fitter.run()
99 
100 # Plot the results
101 # ----------------
102 # Use the plotting helper functions to visualize the results of the
103 # fitting process and determine if the fits are good enough for your needs,
104 # or if the model or fitting settings need to be modified.
105 
106 # Plot the sampled coordinate values used to generate the path lengths
107 # and moment arms.
108 plot_coordinate_samples(results_dir, model.getName())
109 
110 # Plot the path lengths and moment arms computed from the original model
111 # paths (blue) and the fitted polynomial paths (orange).
112 #
113 # For most muscles the fit is very good, but there are noticeable fitting
114 # errors in a few muscles (e.g., /forceset/gaslat_r and /forceset/glmax1_r).
115 # Errors like these usually arise from the fitting process struggling with
116 # discontinuities due to wrapping geometry issues in the original model.
117 # Depending on size of the errors, you may want to adjust the wrapping
118 # geometry in the original model and re-run the fitter.
119 plot_path_lengths(results_dir, model.getName())
120 plot_moment_arms(results_dir, model.getName())
121 
122 # Evaluate the fitted functions on a 'new' trajectory
123 # ---------------------------------------------------
124 # You can use PolynomialPathFitter to evaluate a set of previously fitted
125 # FunctionBasedPaths on a new trajectory. This can be useful if you want to
126 # evaluate the path lengths and moment arms of a model on a trajectory that
127 # was not used to fit the functions. This example uses the same trajectory
128 # used to fit the functions, but you can replace it with any trajectory
129 # that has a set of coordinate values that are consistent with the model.
130 # Note that we do not need to use the 'fitter' object to call this function
131 # (i.e., it is a static function).
132 functionBasedPathsFile = os.path.join(
133  results_dir, f'{model.getName()}_FunctionBasedPathSet.xml')
134 osim.PolynomialPathFitter.evaluateFunctionBasedPaths(
135  model, osim.TableProcessor(values), functionBasedPathsFile)
136 
137 # Replacing the original paths with the fitted paths
138 # --------------------------------------------------
139 # You can use a ModelProcessor to replace the original paths in the model
140 # with the fitted paths. This can be useful if you want to use the fitted
141 # paths in a simulation or analysis tool but keep the original paths in the
142 # model file.
143 modelProcessor = osim.ModelProcessor('subject_walk_scaled.osim')
144 modelProcessor.append(osim.ModOpReplacePathsWithFunctionBasedPaths(
145  functionBasedPathsFile))
146 model = modelProcessor.process()
147 model.initSystem()
148 model.printToXML('subject_walk_scaled_fitted_paths.osim')