Switch to side-by-side view

--- a
+++ b/MATLAB_tool/MuscleParOptTool/optimMuscleParams.m
@@ -0,0 +1,202 @@
+%-------------------------------------------------------------------------%
+% 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                                  % 
+% ----------------------------------------------------------------------- %
+%
+% This function optimizes the muscle parameters as described in Modenese L, 
+% Ceseracciu E, Reggiani M, Lloyd DG (2015). Estimation of 
+% musculotendon parameters for scaled and subject specific musculoskeletal 
+% models using an optimization technique. Journal of Biomechanics (submitted)
+% and prints the results to command window.
+% Also it stores information about the optimization in the structure SimInfo
+
+function [osimModel_opt, SimInfo] = optimMuscleParams(osimModel_ref_filepath, osimModel_targ_filepath, N_eval, log_folder)
+
+% import opensim libraries
+import org.opensim.modeling.*
+
+% results file identifier
+res_file_id_exp = ['_N',num2str(N_eval)];
+
+% import models
+osimModel_ref   = Model(osimModel_ref_filepath);
+osimModel_targ  = Model(osimModel_targ_filepath);
+
+% models details
+[~, name, ext]   = fileparts(osimModel_targ_filepath);
+
+% assigning new name to the model
+osimModel_opt_name  = [name,'_opt',res_file_id_exp,ext];
+osimModel_targ.setName(osimModel_opt_name);
+
+% initializing log file
+if ~isdir(log_folder)
+    warning([log_folder,' does not exist. It will be created']);
+    mkdir(log_folder);
+end
+log_filepath = fullfile(log_folder,[name,'_opt',res_file_id_exp,'.log']);
+% cleaning file (otherwise it appends)
+fopen(log_filepath,'w+'); fclose all;
+% set up diary, without writing at the moment
+diary(log_filepath); diary off
+
+% get muscles
+muscles = osimModel_ref.getMuscles;
+muscles_scaled = osimModel_targ.getMuscles;
+
+% init model
+% si = osimModel_ref.initSystem;
+% si_scaled = osimModel_targ.initSystem;
+
+% initialize with recognizable values
+LmOptLts_opt = ones(muscles.getSize,2)*(-1000);
+
+for n_mus = 0:muscles.getSize-1    
+    tic
+    
+    % current muscle name (here so that it is possible to choose a single
+    % muscle when developing.
+    curr_mus_name = char(muscles.get(n_mus).getName);
+    display(['processing mus ',num2str(n_mus+1),': ',char(curr_mus_name)]);
+    
+    % import muscles
+    curr_mus = muscles.get(n_mus);
+    curr_mus_scaled = muscles_scaled.get(curr_mus_name);
+    
+    % extracting the muscle parameters from reference model
+    LmOptLts = [curr_mus.getOptimalFiberLength, curr_mus.getTendonSlackLength];
+    PenAngleOpt = curr_mus.getPennationAngleAtOptimalFiberLength();
+    Mus_ref = sampleMuscleQuantities(osimModel_ref,curr_mus,'all',N_eval);
+    
+    % calculating minimum fiber length before having pennation 90 deg
+    % acos(0.1) = 1.47 red = 84 degrees, chosen as in OpenSim
+    limitPenAngle = acos(0.1);
+    % this is the minimum length the fiber can be for geometrical reasons.
+    LfibNorm_min = sin(PenAngleOpt)/sin(limitPenAngle);
+    % LfibNorm as calculated above can be shorter than the minimum length
+    % at which the fiber can generate force (taken to be 0.5 Zajac 1989)
+    if (LfibNorm_min<0.5)==1
+        LfibNorm_min = 0.5;
+    end
+    
+    % checking the muscle configuration that do not respect the condition.
+    LfibNorm_ref = Mus_ref(:,2);
+    okList = (LfibNorm_ref>LfibNorm_min);
+    
+    % keeping only acceptable values
+    LfibNorm_ref        = LfibNorm_ref(okList);
+    LtenNorm_ref        = Mus_ref(okList,3)/LmOptLts(2);
+    %Ffib               = Mus_ref(okList,4)/curr_mus.getMaxIsometricForce;
+    MTL_ref             = Mus_ref(okList,1);
+    penAngle_ref        = Mus_ref(okList,5);
+    LfibNormOnTen_ref   = LfibNorm_ref.*cos(penAngle_ref);
+    
+    % in the target only MTL is needed for all muscles
+    MTL_targ = sampleMuscleQuantities(osimModel_targ,curr_mus_scaled,'MTL',N_eval);
+    evalTotPoints = length(MTL_targ);
+    MTL_targ = MTL_targ(okList)';
+    evalOkPoints  = length(MTL_targ); 
+    
+    % The problem to be solved is: 
+    % [LmNorm*cos(penAngle) LtNorm]*[Lmopt Lts]' = MTL;
+    % written as Ax = b
+    A = [LfibNormOnTen_ref LtenNorm_ref];
+    b = MTL_targ;
+    
+    % ===== LINSOL =======
+    % solving the problem to calculate the muscle param
+    x = A\b;    
+    LmOptLts_opt(n_mus+1,:) = x;
+    
+    % checking the results
+    if min(x)<=0
+        diary on
+        % informing the user
+        display(['Negative value estimated for muscle parameter of muscle ',curr_mus_name]);
+        display( '                         Lm Opt        Lts'   );
+        display(['Template model       : ',num2str(LmOptLts)]);
+        display(['Optimized param      : ',num2str(LmOptLts_opt(n_mus+1,:))]);
+        
+        % ===== IMPLEMENTING CORRECTIONS IF ESTIMATION IS NOT CORRECT =======
+        % first try lsqnonlin
+        x = lsqnonneg(A,b);
+        LmOptLts_opt(n_mus+1,:) = x;
+        display(['Opt params (lsqnonneg): ',num2str(LmOptLts_opt(n_mus+1,:))]);
+        % In our tests, if something goes wrong is generally tendon slack 
+        % length becoming negative or zero because tendon length doesn't change
+        % throughout the range of motion, so lowering the rank of A.
+        % if x(2)==0 
+        if min(x)<=0
+            if (max(Mus_ref(okList,3))-min(Mus_ref(okList,3)))<0.0001
+                display('Tendon length not changing throughout range of motion')
+            end
+            % calculating proportion of tendon and fiber
+%            Lfib_fraction = LfibNormOnTen*LmOptLts(1)./MTL;
+            Lten_fraction = Mus_ref(okList,3)./MTL_ref;
+            Lten_targ = (Lten_fraction.*MTL_targ);
+            
+            % first round: optimizing Lopt maintaing the proportion of
+            % tendon as in the reference model
+            A_1 = LfibNormOnTen_ref;
+            b_1 = (MTL_targ-Lten_targ);
+            x(1) = A_1\b_1;
+            
+            % second round: using the optimized Lopt to recalculate Lts
+            b_2 = MTL_targ-A_1*x(1);
+            A_2 = LtenNorm_ref;
+            x(2) = A_2\b_2;
+            LmOptLts_opt(n_mus+1,:) = x;
+        end
+        diary off
+    end
+
+
+    % Here tests about '\' against optimizers were implemented.
+
+    % calculating the error (sum of squared errors)
+    fval = norm(A*x-b).^2.0;
+    
+    % update muscles from scaled model
+    curr_mus_scaled.setOptimalFiberLength(LmOptLts_opt(n_mus+1,1));
+    curr_mus_scaled.setTendonSlackLength(LmOptLts_opt(n_mus+1,2));
+    
+    % PRINT LOGS
+    diary on
+    display('  ');
+    display(['Calculated optimized muscle parameters for ', char(curr_mus),' in ',num2str(toc),' seconds.'])
+    display( '                         Lm Opt        Lts'   );
+    display(['Template model       : ',num2str(LmOptLts)]);
+    display(['Optimized param      : ',num2str(LmOptLts_opt(n_mus+1,:))]);
+    display(['Nr of eval points    : ',num2str(evalOkPoints), '/',num2str(evalTotPoints),' used'])
+    display(['fval                 : ',num2str(fval)]);
+    display(['var from template [%]: ',num2str(100*(abs(LmOptLts-LmOptLts_opt(n_mus+1,:)))./LmOptLts),'%'])
+    display('  ');
+    diary off
+    
+    % SIMULATION INFO AND RESULTS
+    SimInfo.colheader(n_mus+1)               = {char(curr_mus)};
+    SimInfo.LmOptLts_ref(1:2,n_mus+1)        = LmOptLts;
+    SimInfo.LmOptLts_opt(1:2,n_mus+1)        = LmOptLts_opt(n_mus+1,:);
+    SimInfo.varPercLmOptLts(1:2,n_mus+1)     = 100*(abs(LmOptLts-LmOptLts_opt(n_mus+1,:)))./LmOptLts;
+    SimInfo.sampledEvalPoints(n_mus+1)       = evalOkPoints;
+    SimInfo.usedEvalPoints(n_mus+1)          = evalTotPoints;
+    SimInfo.fval(n_mus+1)                    = fval;
+end
+
+% assigning optimized model as output
+osimModel_opt = osimModel_targ;
+
+end