[717926]: / MATLAB / matlab_tool_v4.0 / getMuscleForceDirection.m

Download this file

422 lines (367 with data), 20.5 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
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
%-------------------------------------------------------------------------%
% 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.
% ------------------------------------------------------------------------%
% Edition by Ke Song (Washington University), email: ksong23@wustl.edu
% last modified:
% 18 Sep 2019: modified and updated for OpenSim 4.0 Matlab API (KS).
% ------------------------------------------------------------------------%
function [MFD, MFDSumStruct] = getMuscleForceDirection(osimModel_name,...
IK_mot_file,...
results_directory,...
bodyOfInterest_name,...
bodyExpressResultsIn_name,...
effective_attachm,...
print_attachm,...
visualise,...
step_interval)
% Load Library
import org.opensim.modeling.*;
% read model
osimModel = Model(osimModel_name);
% current state. It will change according to kinematics
if strcmpi(visualise, 'true')
osimModel.setUseVisualizer(true);
end
curr_state = osimModel.initSystem();
% import kinematics as mat structure
IKStruct = sto2Mat(IK_mot_file);
% check feasibility of the requested operation
isBodyInModel(osimModel, bodyOfInterest_name);
% get body (or ground) of interest
bodyOfInterest = getBodyorGround(osimModel,bodyOfInterest_name);
% get body used to express results (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 = getBodyorGround(osimModel,bodyExpressResultsIn_name);
end
end
% define muscles to include in the analysis
% get muscles
muscleSet = osimModel.getMuscles();
n_keep = 1;
target_mus_names = {};
colheaders_MFD_vec = {}; colheaders_MFD_attach = {};
colheaders_MFDSumStruct = {};
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
store_range = 1+3*(n_keep-1):3+3*(n_keep-1);
% vectors: muscle_vxyz_from_body1_expressed_in_body2 ('from' = start from bone out)
colheaders_MFD_vec(store_range) = { ...
[curr_mus_name,'_vx_from_',bodyOfInterest_name,'_in_',bodyExpressResultsIn_name],...
[curr_mus_name,'_vy_from_',bodyOfInterest_name,'_in_',bodyExpressResultsIn_name],...
[curr_mus_name,'_vz_from_',bodyOfInterest_name,'_in_',bodyExpressResultsIn_name]};
% attachments: muscle_pxyz_on_body1_expressed_in_body2
colheaders_MFD_attach(store_range) = { ...
[curr_mus_name,'_px_on_', bodyOfInterest_name,'_in_',bodyExpressResultsIn_name],...
[curr_mus_name,'_py_on_', bodyOfInterest_name,'_in_',bodyExpressResultsIn_name],...
[curr_mus_name,'_pz_on_', bodyOfInterest_name,'_in_',bodyExpressResultsIn_name]};
% advanced MATLAB summary containing all outputs
colheaders_MFDSumStruct(store_range) = { ...
[curr_mus_name,'_x_', bodyOfInterest_name,'_in_',bodyExpressResultsIn_name],...
[curr_mus_name,'_y_', bodyOfInterest_name,'_in_',bodyExpressResultsIn_name],...
[curr_mus_name,'_z_', bodyOfInterest_name,'_in_',bodyExpressResultsIn_name]};
n_keep = n_keep +1;
end
end
N_target_muscles = length(target_mus_names);
% reduce processed kinematics frames when developing related functions
if isempty(step_interval)
% number of frames
N_frames = size(IKStruct.data,1);
% frames (rows of data) in sto file to process
frames = 1:size(IKStruct.data,1);
elseif step_interval <= 0 || mod(step_interval,1) ~= 0
% check if step_interval is correctly given, throw error if not. it
% must be a positive integer.
error('input step_interval must be a positive integer.')
else % if step_interval is correctly specified:
% skip every specified steps of frames
N_frames = ceil(size(IKStruct.data,1)/step_interval);
frames = 1:step_interval:size(IKStruct.data,1);
% make sure last frame is included
if frames(end) ~= size(IKStruct.data,1)
frames = [frames, size(IKStruct.data,1)];
N_frames = N_frames+1;
end
end
% pre-allocate results storage matrix
mus_info_mat = zeros(N_frames,3*N_target_muscles,5);
% extract time stamps if found in IKStruct
if any(strcmp('time',IKStruct.colheaders))
IKStruct.time = IKStruct.data(:,strcmp('time',IKStruct.colheaders));
% add 'time' to colheaders as the first entry
colheaders_MFD_vec = [{'time'},colheaders_MFD_vec];
colheaders_MFD_attach = [{'time'},colheaders_MFD_attach];
colheaders_MFDSumStruct = [{'time'},colheaders_MFDSumStruct];
% add a column to results matrix
mus_info_mat = [zeros(N_frames,1,5) , mus_info_mat];
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.colheaders, IKStruct.data(frames(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 (also display time if found in sto)
disp('--------------------');
if any(strcmp('time',IKStruct.colheaders))
disp(['FRAME: ',num2str(frames(n_frame)),'/',num2str(frames(N_frames)), ...
' ( time = ',num2str(IKStruct.time(frames(n_frame))),'s ).']);
% fill time stamp for processed frame into first columns of results matrix
mus_info_mat(n_frame, 1, 1:5) = IKStruct.time(frames(n_frame));
else
disp(['FRAME: ',num2str(frames(n_frame)),'/',num2str(frames(N_frames)),'.']);
end
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
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);
% 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.
disp(['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
% anatomical
ana_bodyFrom = getBodyorGround(osimModel,mus_bodyset_list{2});
% effective
eff_bodyFrom = getBodyorGround(osimModel,mus_bodyset_list{dist_attach+1});
% vector in BodyFrom ref system
% anatomical
ana_p_bFrom = mus_pointset_mat(2,:);
% effective
eff_p_bFrom = mus_pointset_mat(dist_attach+1,:);
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
% anatomical
ana_bodyFrom = getBodyorGround(osimModel,mus_bodyset_list{end-1});
% effective
eff_bodyFrom = getBodyorGround(osimModel,mus_bodyset_list{prox_attach-1});
% vector in BodyFrom ref system
% anatomical
ana_p_bFrom = mus_pointset_mat(end-1,:);
% effective
eff_p_bFrom = mus_pointset_mat(prox_attach-1,:);
end
% transforming everything in body of interest ref system
% anatomical
ana_p_bOI = transformVec(osimModel, state_new, ana_p_bFrom, ana_bodyFrom, bodyOfInterest);
% effective
eff_p_bOI = transformVec(osimModel, state_new, eff_p_bFrom, eff_bodyFrom, bodyOfInterest);
% normalized force direction
% anatomical
ana_f_dir_norm = (ana_p_bOI-bone_attachment)/norm(ana_p_bOI-bone_attachment);
% effective
eff_f_dir_norm = (eff_p_bOI-lastAttach)/norm(eff_p_bOI-lastAttach);
% computing transport moment at bone attachment
% (anatomical force_dir always from bone_attachment, so there
% is no 'anatomical' transport moment)
bone_to_lastAttach_vec = lastAttach-bone_attachment;
transp_mom_norm = cross(eff_f_dir_norm', bone_to_lastAttach_vec')';
% transforming in body where results should be expressed
% anatomical attachments:
ana_attach_res = transformVec(osimModel, state_new, bone_attachment, bodyOfInterest, bodyExpressResultsIn);
% effective attachments:
eff_attach_res = transformVec(osimModel, state_new, lastAttach, bodyOfInterest, bodyExpressResultsIn);
% anatomical force directions:
ana_f_dir_res = transformVec(osimModel, state_new, ana_f_dir_norm, bodyOfInterest, bodyExpressResultsIn);
% effective force directions:
eff_f_dir_res = transformVec(osimModel, state_new, eff_f_dir_norm, bodyOfInterest, bodyExpressResultsIn);
% transport moments:
transp_mom_res = transformVec(osimModel, state_new, transp_mom_norm, bodyOfInterest, bodyExpressResultsIn);
% fill the matrix with the calculated values
col_ind = 1+3*(n_keep-1):3+3*(n_keep-1);
% shift 1 column if 'time' exists
if any(strcmp('time',IKStruct.colheaders))
col_ind = col_ind+1;
end
mus_info_mat(n_frame, col_ind, 1) = ana_attach_res;
mus_info_mat(n_frame, col_ind, 2) = eff_attach_res;
mus_info_mat(n_frame, col_ind, 3) = ana_f_dir_res ;
mus_info_mat(n_frame, col_ind, 4) = eff_f_dir_res ;
mus_info_mat(n_frame, col_ind, 5) = transp_mom_res;
n_keep = n_keep + 1;
% clear variables to avoid surprises
clear bone_attachment ana_attach_res ana_bodyFrom ana_p_bFrom ana_p_bOI ana_f_dir_norm ana_f_dir_res
clear lastAttach eff_attach_res eff_bodyFrom eff_p_bFrom eff_p_bOI eff_f_dir_norm eff_f_dir_res
clear transp_mom_norm transp_mom_res
end
end
%----------- OUTPUT FILES ------------
% Output files are the same as the C++ plugin.
% Names and descriptions are taken from the manual
MFD=struct;
%
%-----------------------------------
% 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 (reported as the middle part of column header) 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.anatom_vectors.colheaders = colheaders_MFD_vec;
MFD.anatom_vectors.data = mus_info_mat(:, :, 3);
MFD.effect_vectors.colheaders = colheaders_MFD_vec;
MFD.effect_vectors.data = mus_info_mat(:, :, 4);
% construct output filename: get prefix from IK file
[~,OUT_prefix,~] = fileparts(IK_mot_file);
% print MFD ANATOMICAL or EFFECTIVE force vectors into a sto file based on
% specified input. default is ANATOMICAL.
OUTvectors_file = [results_directory,'/',OUT_prefix,'_MuscleForceDirection_vectors.sto'];
disp('Printing MuscleForceDirection_vectors file:');
if strcmpi(effective_attachm, 'true')
% printing EFFECTIVE:
OUTvectors_header = 'Muscle Force Directions (EFFECTIVE)';
OUTvectors_info = [' This file contains normalized EFFECTIVE muscle lines of actions expressed in ',...
bodyExpressResultsIn_name,' reference system.',sprintf('\r\n'),...
' The muscle attachments can be printed in a separate file if desired.'];
printtoSTO(MFD.effect_vectors,OUTvectors_file,OUTvectors_header,OUTvectors_info);
disp(['MFD EFFECTIVE force directions results printed to file ',OUTvectors_file,'.']);
else % printing ANATOMICAL:
OUTvectors_header = 'Muscle Force Directions (ANATOMICAL)';
OUTvectors_info = [' This file contains normalized ANATOMICAL muscle lines of actions expressed in ',...
bodyExpressResultsIn_name,' reference system.',sprintf('\r\n'),...
' The muscle attachments can be printed in a separate file if desired.'];
printtoSTO(MFD.anatom_vectors,OUTvectors_file,OUTvectors_header,OUTvectors_info);
disp(['MFD ANATOMICAL force directions results printed to file ',OUTvectors_file,'.']);
end
%---------------------------------------
% 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.anatom_attach.colheaders = colheaders_MFD_attach;
MFD.anatom_attach.data = mus_info_mat(:, :, 1);
MFD.effect_attach.colheaders = colheaders_MFD_attach;
MFD.effect_attach.data = mus_info_mat(:, :, 2);
MFD.transp_mom.colheaders = colheaders_MFD_vec;
MFD.transp_mom.data = mus_info_mat(:, :, 5);
% print MFD attachments into a sto file only if print_attachm specified as
% 'true'. default is false (only print vectors results).
if strcmpi(print_attachm, 'true')
% print ANATOMICAL or EFFECTIVE attachments based on specified input.
% default is ANATOMICAL.
OUTattach_file = [results_directory,'/',OUT_prefix,'_MuscleForceDirection_attachments.sto'];
disp('Printing MuscleForceDirection_attachments file:');
if strcmpi(effective_attachm, 'true')
% printing EFFECTIVE:
OUTattach_header = 'Muscle Attachment Positions (EFFECTIVE)';
OUTattach_info = [' This file contains the positions of EFFECTIVE muscle attachments.',sprintf('\r\n'),...
' The positions are expressed in the ',bodyExpressResultsIn_name,' reference system.'];
printtoSTO(MFD.effect_attach,OUTattach_file,OUTattach_header,OUTattach_info);
disp(['MFD EFFECTIVE attachments results printed to file ',OUTattach_file,'.']);
else % printing ANATOMICAL:
OUTattach_header = 'Muscle Attachment Positions (ANATOMICAL)';
OUTattach_info = [' This file contains the positions of ANATOMICAL muscle attachments.',sprintf('\r\n'),...
' The positions are expressed in the ',bodyExpressResultsIn_name,' reference system.'];
printtoSTO(MFD.anatom_attach,OUTattach_file,OUTattach_header,OUTattach_info);
disp(['MFD ANATOMICAL attachments results printed to file ',OUTattach_file,'.']);
end
else
disp('MFD attachments results not printed to file.')
end
%--------------------------
% 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.
MFDSumStruct=struct;
% this is an advanced summary for Matlab use
MFDSumStruct.colheaders = colheaders_MFDSumStruct;
MFDSumStruct.rowheaders = arrayfun(@(r) sprintf('Frame %d/%d', frames(r),frames(N_frames)), 1:N_frames, 'un',0)';
MFDSumStruct.layerheaders(1,1,1:5) = {'bone_attach','effect_attach','anatom_act_line','effect_act_line','trans_mom'};
MFDSumStruct.data = mus_info_mat;
% to free the memory
osimModel.disownAllComponents();
end