[717926]: / MATLAB / getMuscleForceDirection.m

Download this file

326 lines (271 with data), 14.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
%-------------------------------------------------------------------------%
% Copyright (c) 2019 Modenese L. %
% %
% 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 %
% email: l.modenese@imperial.ac.uk %
% ----------------------------------------------------------------------- %
% This function replicates in MATLAB the functionalities of the
% MuscleForceDirection OpenSim plugin available at
% https://simtk.org/projects/force_direction
%
% Given an OpenSim model, a body of interest and a pre-computed kinematics
% the script will return the lines of action of the muscles.
%
% The output a matrix with frames as rows
%
% TODO: comment
% TODO: deal with joints
% TODO: test script
%
% created:
% 29 Nov 2018: created based on functions for other purposes and added to
% MFD Matlab toolbox.
% ------------------------------------------------------------------------%
function [MFD, MFDSumStruct] = getMuscleForceDirection(osimModel_name,...
IK_mot_file,...
MFD_sto_file,...
bodyOfInterest_name,...
bodyExpressResultsIn_name,...
effective_attachm,...
visualise,...
test_input)
% Load Library
import org.opensim.modeling.*;
% read model
osimModel = Model(osimModel_name);
% import kinematics as mat structure
IKStruct = sto2Mat(IK_mot_file);
% check feasibility of the requested operation
isBodyInModel(osimModel, bodyOfInterest_name);
% TODO: deal with multiple body expressed in distinct reference systems by
% creating a loop here and using cell arrays.
% get body of interest
bodyOfInterest = osimModel.getBodySet.get(bodyOfInterest_name);
% get body where results will be expressed
% (if different from body of interest)
if strcmp(bodyExpressResultsIn_name, bodyOfInterest_name)==1
bodyExpressResultsIn = bodyOfInterest;
else
if isempty(bodyExpressResultsIn_name)==1
bodyExpressResultsIn_name = bodyOfInterest_name;
bodyExpressResultsIn = bodyOfInterest;
else
isBodyInModel(osimModel, bodyExpressResultsIn_name)
bodyExpressResultsIn = osimModel.getBodySet.get(bodyExpressResultsIn_name);
end
end
% define muscles to include in the analysis
% get muscles
muscleSet = osimModel.getMuscles();
n_keep = 1;
for n_m = 0:muscleSet.getSize()-1
% extract muscle
curr_mus = muscleSet.get(n_m);
curr_mus_name = char(muscleSet.get(n_m).getName());
% check if muscle is attached (1) or not (0) to bone of interest
% left in internal loop to handle conditional viapoints, if
% necessary. They might be active only at certain states, therefore
% it is risky to exclude muscles a priori.
% NO! CANNOT CHANGE ORIGIN OR INSERTION
musc_attach = isMuscleConnectedToBody(curr_mus, bodyOfInterest_name);
if musc_attach == 1
target_mus_names{n_keep} = curr_mus_name;
% defining headers as well
rowheaders{n_keep,1} = curr_mus_name;
store_range = 1+3*(n_keep-1):3+3*(n_keep-1);
colheaders_MFD_vec(store_range) = { [curr_mus_name,'_vx_in_',bodyExpressResultsIn_name],...
[curr_mus_name,'_vy_in_',bodyExpressResultsIn_name],...
[curr_mus_name,'_vz_in_',bodyExpressResultsIn_name]};
colheaders_MFD_attach(store_range) = { [curr_mus_name,'_px_in_',bodyExpressResultsIn_name],...
[curr_mus_name,'_py_in_',bodyExpressResultsIn_name],...
[curr_mus_name,'_pz_in_',bodyExpressResultsIn_name]};
n_keep = n_keep +1;
end
end
N_target_muscles = length(target_mus_names);
% current state. It will change according to kinematics
if strcmp(visualise, 'true')
osimModel.setUseVisualizer(true);
end
curr_state = osimModel.initSystem();
% number of frames
N_frames = size(IKStruct.data,1);
% this is a testing option to reduce processed kinematics frames when
% developing related functions
if ~isempty(test_input)
N_frames = test_input;
end
% Variables could be preallocated for speed, but I didn't like to have all
% zeros, which might be confused with actual values from the analysis.
% mus_info_mat = zeros(muscleSet.getSize(),12, N_frames);
for n_frame = 1:N_frames
% realize kinematics
state_new = realizeMatStructKinematics(osimModel, curr_state, IKStruct, n_frame);
if strcmp(visualise, 'true')
osimModel.updVisualizer().show(state_new);
end
% counter for muscles to be kept at each frame
n_keep = 1;
% display time progression
%disp('--------------------');
disp(['FRAME: ',num2str(n_frame),'/',num2str(N_frames)]);
%disp('--------------------');
for n_m = 0:N_target_muscles-1
% extract muscle
curr_mus_name = target_mus_names{n_m+1};
curr_mus = muscleSet.get(curr_mus_name);
% loop through the points
% this print is too fast to be useful apart from debugging
% disp(['Processing muscle (',num2str(n_m+1),'/',num2str(N_target_muscles),'): ', curr_mus_name]);
% make available info about the muscle pointSet (body names
% and points coordinates)
[mus_bodyset_list, mus_pointset_mat] = getCurrentMusclePathAsMat(curr_mus, state_new);
% the vector allows 3 cases:
% 1) muscle STARTS from bone if interest,
% 2) muscle has via point through the bone of interest but no attachments
% 3) muscle ENDS at the bone of interest
% ALL VECTORS FROM BONE OUT!
% vector of muscle attachments in the bone of interest
attach_vec = strcmp(mus_bodyset_list, bodyOfInterest_name);
ind_attachms = find(attach_vec);
% proximal and distal attachments on body of interest
% NB ASSUMPTION THAT THE MUSCLE POINTS ARE NUMBERED FROM
% PROXIMAL TO DISTAL
prox_attach = min(ind_attachms);
dist_attach = max(ind_attachms);
% setting for transform operation later on
otherBodyAttach = Vec3(0);
% CASE 1
if attach_vec(1)==0 && attach_vec(end)==0
% This case is for multi-articular muscles that jump the
% body and can be attached with a viapoint.
display(['Muscle has only viapoints (no bone attachments) on ',bodyOfInterest_name,'. Skipping...']);
continue
end
% CASE 2: muscle that starts at bone of interest
if attach_vec(1)==1 && attach_vec(end)==0 % muscle starts here
ori = mus_pointset_mat(1, :);
lastAttach = mus_pointset_mat(dist_attach, :);
bone_attachment = ori;
% first body outside the body of interest
bodyFrom = osimModel.getBodySet.get(mus_bodyset_list{dist_attach+1});
% vector in bodyFrom ref system
p = Vec3(mus_pointset_mat(dist_attach+1, 1),...
mus_pointset_mat(dist_attach+1, 2),...
mus_pointset_mat(dist_attach+1, 3));
end
% CASE 3: muscle that ends at bone of interest
if attach_vec(1)==0 && attach_vec(end)==1 % muscle starts here
ins = mus_pointset_mat(end, :);
lastAttach = mus_pointset_mat(prox_attach,:);
bone_attachment = ins;
% first body outside the body of interest
bodyFrom = osimModel.getBodySet.get(mus_bodyset_list{prox_attach-1});
% vector in bodyFrom ref system
p = Vec3( mus_pointset_mat(prox_attach-1, 1),...
mus_pointset_mat(prox_attach-1, 2),...
mus_pointset_mat(prox_attach-1, 3));
end
% transformating everything in body of interest ref system
osimModel.getSimbodyEngine.transformPosition(state_new, bodyFrom, p, bodyOfInterest, otherBodyAttach)
otherBodyAttach_vec = [otherBodyAttach.get(0), otherBodyAttach.get(1), otherBodyAttach.get(2)];
% normalized force direction
force_dir_norm = (otherBodyAttach_vec-lastAttach)/norm(otherBodyAttach_vec-lastAttach);
% computing transport moment at bone attachment
bone_to_lastAttach_vec = lastAttach-bone_attachment;
transp_mom_norm = cross(force_dir_norm', bone_to_lastAttach_vec');
% transforming force directions in body where results should be
% expressed
transf_force_dir_norm = Vec3(0);
force_dir_norm_Vec3 = Vec3(force_dir_norm(1), force_dir_norm(2), force_dir_norm(3));
osimModel.getSimbodyEngine.transform(state_new, bodyOfInterest, force_dir_norm_Vec3, bodyExpressResultsIn, transf_force_dir_norm);
force_dir_res = [transf_force_dir_norm.get(0), transf_force_dir_norm.get(1), transf_force_dir_norm.get(2)];
% fill the matrix with the calculated values
col_ind = 1+3*(n_keep-1):3+3*(n_keep-1);
mus_info_mat(n_frame, col_ind, 1) = bone_attachment;
mus_info_mat(n_frame, col_ind, 2) = lastAttach;
mus_info_mat(n_frame, col_ind, 3) = force_dir_res;
mus_info_mat(n_frame, col_ind, 4) = transp_mom_norm;
n_keep = n_keep + 1;
% clear variables to avoid surprises
clear attachment force_dir_norm transp_mom_norm lastAttach otherBodyAttach_vec force_dir_res
% end
end
end
% time vector
time_v = getMatStructColumn(IKStruct, 'time');
%----------- OUTPUT FILES ------------
% Output files are the same as the C++ plugin.
% Names and descriptions are taken from the manual
[p, n, e] = fileparts(MFD_sto_file);
MFD_sto_file_att = fullfile(p, [n,'_MuscleForceDirection_attachments', e]);
MFD_sto_file_vec = fullfile(p, [n,'_MuscleForceDirection_vectors', e]);
%-----------------------------------
% MuscleForceDirection_vectors.sto |
%-----------------------------------
% this file contains the normalized vectors representing the directions
% of the muscle lines of action. The vector is always pointing from the
% selected body where the attachment is located outwards. The body in whose
% reference system the vector is expressed is always reported as the final
% part of the column header of each muscle.
MFD.vectors.colheaders = [{'time'} colheaders_MFD_vec];
MFD.vectors.data = [time_v, mus_info_mat(:, :, 3)];
MFD_vec_descr = ['the normalized muscle lines of action expressed in ',...
bodyExpressResultsIn_name, ...
' reference system'];
% write file
Mat2sto(MFD.vectors, MFD_sto_file_vec, MFD_vec_descr)
%---------------------------------------
% MuscleForceDirection_attachments.sto |
%---------------------------------------
% this file is optional and prints the coordinates of the muscle
% attachments. If the user choice is to express the anatomical muscle
% attachments in the local reference system, the file will contain the
% first and last muscle points specified for that muscle in the original model file.
MFD.attach.colheaders = [{'time'}, colheaders_MFD_attach];
% check if anatomical or effective attachments are required
if strcmp(effective_attachm, 'true')
% effective attachment if required
MFD.attach.data = [time_v, mus_info_mat(:, :, 2)];
descr = 'effective';
else
% anatomical attachments by default
MFD.attach.data = [time_v, mus_info_mat(:, :, 1)];
descr = 'amatomical';
end
MFD_attach_descr = ['the ',descr ,' position of the muscle attachments expressed in ',...
bodyExpressResultsIn_name, ...
' reference system'];
Mat2sto(MFD.attach, MFD_sto_file_att, MFD_attach_descr)
%----------------------------------------
% MuscleForceDirection_transp_moment.sto |
%----------------------------------------
% this will write a sto file for transport moment.
% MFD.transp_mom.colheaders = colheaders_MFD_vec;
% MFD.transp_mom.data = mus_info_mat(:, :, 4);
%--------------------------
% Advanced MATLAB summary |
%--------------------------
% The idea is to have a matrix with frames as rows and in column bone
% attachments, effective attachments, lines of action and transport
% moments.
colheaders = {'bone_attach_X', 'bone_attach_Y', 'bone_attach_Z', 'effect_attach_X', 'effect_attach_Y', 'effect_attach_Z', ...
'act_line_X', 'act_line_Y', 'act_line_Z', 'trans_mom_X', 'trans_mom_Y', 'trans_mom_Z'};
% this is an advanced summary for Matlab use
MFDSumStruct.colheaders = colheaders;
MFDSumStruct.rowheaders = rowheaders;
MFDSumStruct.data = mus_info_mat;
% free the memory
osimModel.disownAllComponents();
end