[592feb]: / Python_tool / MuscleParOptTool / Private_functions.py

Download this file

394 lines (300 with data), 17.6 kB

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Thu Mar 25 15:54:09 2021
@author: emi
"""
# ------------------------------------------------------------------------ #
# Copyright (c) 2019 Modenese L., Ceseracciu, E., Reggiani M., Lloyd, D.G. #
# #
# Licensed under the Apache License, Version 2.0 (the "License"); #
# you may not use this file except in compliance with the License. #
# You may obtain a copy of the License at #
# http://www.apache.org/licenses/LICENSE-2.0. #
# #
# Unless required by applicable law or agreed to in writing, software #
# distributed under the License is distributed on an "AS IS" BASIS, #
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or #
# implied. See the License for the specific language governing #
# permissions and limitations under the License. #
# #
# Author: Luca Modenese, January 2015 #
# email: l.modenese@imperial.ac.uk #
# ------------------------------------------------------------------------ #
#------ import packages ---------
import numpy as np
from lxml import etree
from itertools import product
import logging
# adding OpenSim to python script
import sys
sys.path.append('/opt/opensim-core/lib/python3.6/site-packages/')
import opensim
# adding tool function to python script
from Public_functions import getModelJointDefinitions, \
getChildBodyJoint, \
getParentBodyJoint, \
getMuscleAttachBody
#--------------------------------
def getJointsSpannedByMuscle(osimModel, OSMuscleName):
# Given as INPUT a muscle OSMuscleName from an OpenSim model, this function
# returns the OUTPUT list jointNameSet containing the OpenSim jointNames
# crossed by the OSMuscle.
#
# It works through the following steps:
# 1) extracts the GeometryPath
# 2) loops through the single points, determining the body they belong to
# 3) stores the bodies to which the muscle points are attached to
# 4) determines the nr of joints based on body indexes
# 5) stores the crossed OpenSim joints in the output list named jointNameSet
#
# NB this function return the crossed joints independently on the
# constraints applied to the coordinates. Eg patello-femoral is considered as a
# joint, although in Arnold's model it does not have independent
# coordinates, but it is moved in dependency of the knee flexion angle.
# Written by Emiliano Ravera emiliano.ravera@uner.edu.ar as part of the
# Python version of work by Luca Modenese in the parameterisation of muscle
# tendon properties.
# useful initializations
BodySet = osimModel.getBodySet()
muscle = osimModel.getMuscles().get(OSMuscleName)
# additions BAK -> adapted by ER
# load a jointStrucute detailing bone and joint configurations
# osimModel_file = osimModel.getInputFileName()
jointStructure = getModelJointDefinitions(osimModel)
# Extracting the PathPointSet via GeometryPath
musclePath = muscle.getGeometryPath()
musclePathPointSet = musclePath.getPathPointSet()
# for loops to get the attachment bodies
muscleAttachBodies = []
muscleAttachIndex = []
for n_point in range(0, musclePathPointSet.getSize()):
# get the current muscle point
muscelPathPoint_name = musclePathPointSet.get(n_point).getName()
currentAttachBody = getMuscleAttachBody(osimModel, muscelPathPoint_name)
# Initialize
if n_point == 0:
previousAttachBody = currentAttachBody
muscleAttachBodies.append(currentAttachBody)
muscleAttachIndex.append(BodySet.getIndex(currentAttachBody))
# building a list of the bodies attached to the muscles
if currentAttachBody != previousAttachBody:
muscleAttachBodies.append(currentAttachBody)
muscleAttachIndex.append(BodySet.getIndex(currentAttachBody))
previousAttachBody = currentAttachBody
# end of loops to get the attacement bodies
# From distal body checking the joint names going up until the desired
# OSJointName is found or the proximal body is reached as parent body.
DistalBodyName = muscleAttachBodies[-1]
bodyName = DistalBodyName
ProximalBodyName = muscleAttachBodies[0]
body = BodySet.get(DistalBodyName)
spannedJointNameOld = ''
NoDofjointNameSet = []
jointNameSet = []
while bodyName != ProximalBodyName:
# BAK implementation -> adapted by ER
spannedJointName = getChildBodyJoint(jointStructure, body.getName())
spannedJoint = osimModel.getJointSet().get(spannedJointName[0])
if spannedJointName == spannedJointNameOld:
# BAK implementation -> adapted by ER
body = osimModel.getBodySet().get(bodyName)
spannedJointNameOld = spannedJointName[0]
else:
if spannedJoint.numCoordinates() != 0:
jointNameSet.append(spannedJointName[0])
else:
NoDofjointNameSet.append(spannedJointName[0])
spannedJointNameOld = spannedJointName[0]
bodyName = jointStructure[spannedJointName[0]]['parentBody']
body = osimModel.getBodySet().get(bodyName)
bodyName = body.getName()
if not jointNameSet:
print('ERORR: ' + 'No joint detected for muscle ' + OSMuscleName)
if NoDofjointNameSet:
for value in NoDofjointNameSet:
print('Joint ' + value + ' has no dof.')
varargout = NoDofjointNameSet
return jointNameSet, varargout
# ----------------------------------------------------------------------------
def getIndipCoordAndJoint(osimModel, constraint_coord_name):
# Function that given a dependent coordinate finds the independent
# coordinate and the associated joint. The function assumes that the
# constraint is a CoordinateCoupleConstraint as used by Arnold, Delp and
# LLLM. The function can be useful to manage the patellar joint for instance.
# Input: OpenSim model objects
# Output: ind_coord_name and ind_coord_joint_name - the joint with specific constraint
# Written by Emiliano Ravera emiliano.ravera@uner.edu.ar as part of the
# Python version of work by Luca Modenese in the parameterisation of muscle
# tendon properties.
ind_coord_name = ''
ind_coord_joint_name = ''
# load model XML file
osimModel_filepath = osimModel.getInputFileName()
osimModel_file = etree.parse(osimModel_filepath)
model = osimModel_file.getroot()
# double check: if not constrained then function returns
flag = [1 for constraint in model.findall('.//CoordinateCouplerConstraint') if constraint.get('name').find(constraint_coord_name)]
if flag == []:
# print(constraint_coord_name + ' is not a constrained coordinate.')
logging.error(' ' + constraint_coord_name + ' is not a constrained coordinate.')
return ind_coord_name, ind_coord_joint_name
# otherwise search through the constraints
for constraint in model.findall('.//CoordinateCouplerConstraint'):
# this function assumes that the constraint will be a coordinate
# coupler contraint ( Arnold's model and LLLM uses this)
# get dep coordinate and check if it is the coord of interest
dep_coord_name = constraint.find('dependent_coordinate_name').text
if dep_coord_name in constraint_coord_name:
# print('WARNING: Only one indipendent coordinate is managed by the "getIndipCoordAndJoint" function yet.')
logging.warning(' Only one indipendent coordinate is managed by the "getIndipCoordAndJoint" function yet.')
ind_coord_name = constraint.find('independent_coordinate_names').text
ind_coord_joint_name = constraint.find('independent_coordinate_names').text # assume the same name for coordinate and joint
return ind_coord_name, ind_coord_joint_name
# ----------------------------------------------------------------------------
def sampleMuscleQuantities(osimModel,OSMuscle,muscleQuant, N_EvalPoints):
# Given as INPUT an OpenSim muscle OSModel, OSMuscle, a muscle variable and a nr
# of evaluation points this function returns as
# musOutput a vector of the muscle variable of interest
# obtained by sampling the ROM of the joint spanned by the muscle in
# N_EvalPoints evaluation points.
# For multidof joint the combinations of ROMs are considered.
# For multiarticular muscles the combination of ROM are considered.
# The script is totally general because based on generating strings of code
# correspondent to the encountered code. The strings are evaluated at the end.
# IMPORTANT 1
# The function can decrease the N_EvalPoints if there are too many dof
# involved (ASSUMPTION is that a better sampling will be vanified by the
# huge amount of data generated). This is an option that can be controlled
# by the user by deciding if to use it or not (setting limit_discr = 0/1)
# and, in case the sampling is limited, by setting a lower limit to the
# discretization.
# IMPORTANT 2
# Another check is done on the dofs: only INDEPENDENT coordinate are
# considered. This is fundamental for patellofemoral joint that both in
# LLLM and Arnold's model are constrained dof, dependent on the knee
# flexion angle. This function assumes to be used with Arnold's model.
# currentState is initialState;
# IMPORTANT 3
# At the purposes of the muscle optimizer it is important that here the
# model is initialize every time. So there are no risk of working with an
# old state (important for Schutte muscles for instance, where we observed
# that it was necessary to re-initialize the muscle after updating the
# muscle parameters).
# Written by Emiliano Ravera emiliano.ravera@uner.edu.ar as part of the
# Python version of work by Luca Modenese in the parameterisation of muscle
# tendon properties.
# ======= SETTINGS ======
# limit (1) or not (0) the discretization of the joint space sampling
limit_discr = 0
# minimum angular discretization
min_increm_in_deg = 1
# =======================
# initialize the model
currentState = osimModel.initSystem()
# getting the joint crossed by a muscle
muscleCrossedJointSet, _ = getJointsSpannedByMuscle(osimModel, OSMuscle.getName())
# index for effective dofs
DOF_Index = []
CoordinateBoundaries = []
degIncrem = []
for _, curr_joint in enumerate(muscleCrossedJointSet):
# Initial estimation of the nr of Dof of the CoordinateSet for that
# joint before checking for locked and constraint dofs.
nDOF = osimModel.getJointSet().get(curr_joint).numCoordinates()
# skip welded joint and removes welded joint from muscleCrossedJointSet
if nDOF == 0:
continue
# calculating effective dof for that joint
effect_DOF = nDOF
for n_coord in range(0,nDOF):
# get coordinate
curr_coord = osimModel.getJointSet().get(curr_joint).get_coordinates(n_coord)
curr_coord_name = curr_coord.getName()
# skip dof if locked
if curr_coord.getLocked(currentState):
continue
# if coordinate is constrained then the independent coordinate and
# associated joint will be listed in the sampling "map"
if curr_coord.isConstrained(currentState) and not curr_coord.getLocked(currentState):
constraint_coord_name = curr_coord_name
# finding the independent coordinate
ind_coord_name, ind_coord_joint_name = getIndipCoordAndJoint(osimModel, constraint_coord_name)
# updating the coordinate name to be saved in the list
curr_coord_name = ind_coord_name
effect_DOF -= 1
# ignoring constrained dof if they point to an independent
# coordinate that has already been stored
if osimModel.getCoordinateSet().getIndex(curr_coord_name) in DOF_Index:
continue
# skip dof if independent coordinate locked (the coord
# correspondent to the name needs to be extracted)
if osimModel.getCoordinateSet().get(curr_coord_name).getLocked(currentState):
continue
# NB: DOF_Index is used later in the string generated code.
# CRUCIAL: the index of dof now is model based ("global") and
# different from the joint based used until now.
DOF_Index.append(osimModel.getCoordinateSet().getIndex(curr_coord_name))
# necessary update/reload the curr_coord to avoid problems with
# dependent coordinates
curr_coord = osimModel.getCoordinateSet().get(DOF_Index[-1])
# Getting the values defining the range
jointRange = np.zeros(2)
jointRange[0] = curr_coord.getRangeMin()
jointRange[1] = curr_coord.getRangeMax()
# Storing range of motion conveniently
CoordinateBoundaries.append(jointRange)
# increments in the variables when sampling the mtl space.
# Increments are different for each dof and based on N_eval.
# Defining the increments
degIncrem.append((jointRange[1] - jointRange[0]) / (N_EvalPoints-1))
# limit or not the discretization of the joint space sampling
# a limit to the increase can be set though
if limit_discr == 1 and degIncrem[-1] < np.radians(min_increm_in_deg):
degIncrem[-1] = np.radians(min_increm_in_deg)
# assigns an interval of variation following the initial and final value
# for each dof X
# setting up for loops in order to explore all the possible combination of
# joint angles (looping on all the dofs of each joint for all the joint
# crossed by the muscle).
# The model pose is updated via: " coordToUpd.setValue(currentState,setAngleDof)"
# The right dof to update is chosen via: "coordToUpd = osimModel.getCoordinateSet.get(n_instr)"
# generate a dictionary with CoordinateRange for each dof X.
# The dictionary keys are the DOF_Index in the model
CoordinateRange = {}
for pos, dof in enumerate(DOF_Index):
CoordinateRange[str(dof)] = np.linspace(CoordinateBoundaries[pos][0] , CoordinateBoundaries[pos][1], N_EvalPoints)
# generate a list of dictionaries to explore all the possible combination of
# joit angle
CoordinateCombinations = [dict(zip(CoordinateRange.keys(), element)) for element in product(*CoordinateRange.values())]
# looping on all the dofs combinations
musOutput = [None] * len(CoordinateCombinations)
for iteration, DOF_comb in enumerate(CoordinateCombinations):
# Set the model pose
for dof_ind in DOF_comb.keys():
coordToUpd = osimModel.getCoordinateSet().get(int(dof_ind))
coordToUpd.setValue(currentState, CoordinateCombinations[iteration][dof_ind])
# calculating muscle length for the muscle
if muscleQuant == 'MTL':
musOutput[iteration] = OSMuscle.getGeometryPath().getLength(currentState)
if muscleQuant == 'LfibNorm':
OSMuscle.setActivation(currentState,1.0)
osimModel.equilibrateMuscles(currentState)
musOutput[iteration] = OSMuscle.getNormalizedFiberLength(currentState)
if muscleQuant == 'Lten':
OSMuscle.setActivation(currentState,1.0)
osimModel.equilibrateMuscles(currentState)
musOutput[iteration] = OSMuscle.getTendonLength(currentState)
if muscleQuant == 'Ffib':
OSMuscle.setActivation(currentState,1.0)
osimModel.equilibrateMuscles(currentState)
musOutput[iteration] = OSMuscle.getActiveFiberForce(currentState)
if muscleQuant == 'all':
OSMuscle.setActivation(currentState,1.0)
osimModel.equilibrateMuscles(currentState)
musOutput[iteration] = [ OSMuscle.getGeometryPath().getLength(currentState), \
OSMuscle.getNormalizedFiberLength(currentState), \
OSMuscle.getTendonLength(currentState), \
OSMuscle.getActiveFiberForce(currentState), \
OSMuscle.getPennationAngle(currentState) ]
return musOutput