2
\$\begingroup\$

Could someone kindly help me review and fix any errors or inefficiencies in this MATLAB script? I’m trying to make sure it’s clean, robust, and optimized.

Brief description of what the code does:

This MATLAB script processes biomechanical data from different subject groups (e.g., Healthy, PD, Stroke, TBI) during gait tasks. It loads and combines motion capture data, filters for valid entries, computes spatiotemporal gait metrics (e.g., stride length, speed, duration), calculates variability measures like coefficient of variation (CV), phase coordination index (PCI), and gait asymmetry (GA), and conducts statistical analysis (ANOVA and Tukey HSD) to compare gait characteristics across groups. It also includes pre-processing for sensor signals and attempts to correlate gait events with signal features (e.g., heel strikes via accelerometer peaks).

clear

path = 'C:\Users\franc\Desktop\BIOMECCANICA';

pop = dir(path); pop = {pop(3:end).name};

[indx,~] = listdlg('ListString',pop);
pop_true = pop(indx);


file_to_load = {'ROOT' 'INDIP' 'LWRIST' 'RWRIST' 'LFOOT' 'RFOOT' 'LBACK' 'STERN' 'FHEAD'};
[indx,~] = listdlg('ListString',file_to_load);
file_true = file_to_load(indx);
TAB = table;
TAB1 = table;

%Carico tutta la tabella TAB
for po = 1:length(pop_true)

    files = dir([path '\' char(pop_true(po))]); files = {files(3:end).name};
    files_true = files(contains(files,file_true));

    for fi = 1:length(files_true)

        temp = load([path '\' char(pop_true(po)) '\' char(files_true(fi))]);
        fld = char(fieldnames(temp)); ind = strfind(fld,'_');
        if contains(fld,'INDIP') == 1 || contains(fld,'ROOT') == 1
            temp = temp.(fld);
        else
            temp = table(temp.(fld));
            temp.Properties.VariableNames = {fld(ind+1:end)};
        end
        TAB1 = [TAB1 temp];

    end

    TAB = [TAB; TAB1];

    TAB1 = table;
end

TAB = removevars(TAB,'b2');
varnames = TAB.Properties.VariableNames;
varnames(contains(varnames,'b1')) = {'INDIP'};

TAB.Properties.VariableNames = varnames;

TAB =  movevars(TAB,'GROUP','Before',1); TAB = movevars(TAB,{'SUBJECT' 'TASK' 'TRIAL' 'ORIGINAL PATH' 'AGE' 'MASS' 'HEIGHT' 'INDIP'},'After','GROUP');

for ii = 1:size(TAB,1)
    TAB.HEIGHT{ii} = cell2mat(TAB.HEIGHT(ii))*0.53;
end


c = contains(TAB.("ORIGINAL PATH"),'FAST');
TAB = TAB(c,:);

timing = repmat({'T0'},size(TAB,1),1);

TAB.TIMING = timing;
TAB = movevars(TAB,'TIMING','Before','TASK');

%% CLEANING
for ii = 1:size(TAB,1)

    mwb = TAB.INDIP(ii).MicroWB;

    ind(ii) =   isempty(mwb);
end

ind = logical(ind);
TAB(ind,:) = [];
ind = [];

for ii = 1:size(TAB,1)

    mwb = TAB.FHEAD(ii).acc;

    ind(ii) =   isempty(mwb);
end

ind = logical(ind);
TAB(ind,:) = [];
ind = [];


for ii = 1:size(TAB,1)

    mwb = TAB.LBACK(ii).acc;
    ind(ii) =   isempty(mwb);
end

ind = logical(ind);
TAB(ind,:) = [];
ind = [];


for ii = 1:size(TAB,1)

    mwb = TAB.STERN(ii).acc;

    ind(ii) =   isempty(mwb);
end

ind = logical(ind);
TAB(ind,:) = [];
ind = [];


% %% GET SPATIOTEMPORAL
for ii = 1:size(TAB,1)

    mwb = TAB.INDIP(ii).MicroWB;

    TAB.STRIDE_FQ(ii) = {nanmean(cell2mat({mwb.StrideFrequency}))};


    spa = [];

    for jj = 1:size(mwb,2)
        temp = mwb(jj).Stride_Length;
        spa = [spa temp];
    end

    TAB.STRIDE_LENGTH(ii) = {(spa)};


    spa = [];

    for jj = 1:size(mwb,2)
        temp = mwb(jj).Stride_Speed;
        spa = [spa temp];
    end


    TAB.STRIDE_SPEED(ii) = {(spa)};


    spa = [];

    for jj = 1:size(mwb,2)
        temp = mwb(jj).Stride_Duration;
        spa = [spa temp];
    end

    TAB.STRIDE_DURATION(ii) = {(spa)};

    spa = [];

    for jj = 1:size(mwb,2)
        temp = mwb(jj).Stride_Length;
        spa = [spa temp];
    end

    TAB.STRIDE_LENGTH(ii) = {(spa)};

    spa = [];

    for jj = 1:size(mwb,2)
        temp = mwb(jj).Stance_Duration;
        spa = [spa temp];
    end

    TAB.STANCE_DURATION(ii) = {(spa)};

    spa = [];

    for jj = 1:size(mwb,2)
        temp = mwb(jj).Stance_Length;
        spa = [spa temp];
    end

    TAB.STANCE_LENGTH(ii) = {(spa)};

    spa = [];

    for jj = 1:size(mwb,2)
        temp = mwb(jj).Stance_Speed;
        spa = [spa temp];
    end

    TAB.STANCE_SPEED(ii) = {(spa)};


    spa = [];

    for jj = 1:size(mwb,2)
        temp = mwb(jj).Swing_Duration;
        spa = [spa temp];
    end

    TAB.SWING_DURATION(ii) = {(spa)};

    spa = [];

    for jj = 1:size(mwb,2)
        temp = mwb(jj).Swing_Length;
        spa = [spa temp];
    end

    TAB.SWING_LENGTH(ii) = {(spa)};

    spa = [];

    for jj = 1:size(mwb,2)
        temp = mwb(jj).Swing_Speed;
        spa = [spa temp];
    end

    TAB.SWING_SPEED(ii) = {(spa)};

    spa = [];

    for jj = 1:size(mwb,2)
        temp = mwb(jj).SingleSupport_Duration;
        spa = [spa temp];
    end

    TAB.SS_DURATION(ii) = {(spa)};

    spa = [];

    for jj = 1:size(mwb,2)
        temp = mwb(jj).DoubleSupport_Duration;
        spa = [spa temp];
    end

    TAB.DS_DURATION(ii) = {(spa)};

    spa = [];

    for jj = 1:size(mwb,2)
        temp = mwb(jj).Stride_InitialContacts;
        spa = [spa; temp];
    end

        [r,c] = find(isnan(spa)); r = unique(r); spa(r,:) = [];
    TAB.CONTANCTS(ii) = {(spa)};

    spa = [];

    for jj = 1:size(mwb,2)
        temp = mwb(jj).FinalContact_LeftRight;
        spa = [spa temp];
    end

    %     spa(:,r) = [];
    TAB.SIDE(ii) = {(spa)};


end

Dividiamo in TAB10 e TAB8
% Selezioniamo le righe in cui TASK è '10MWT' (cammino rettilineo)
TAB10 = TAB(strcmp(TAB.TASK, '10MWT'), :)

% Selezioniamo le righe in cui TASK è '8MWT' (cammino a 8)
TAB8 = TAB(strcmp(TAB.TASK, 'Fo8WT'), :)

Creo una tabella ridotta (TABreduced), rispetto al task '10MTW' (TAB10), contenente le prime 20 righe per ogni malattia
TABreduced = table; % Inizializzo una tabella vuota per il risultato

% Filtro i gruppi per ciascun tipo di malattia solo per TASK = '10MWT'+
groups = {'Healthy_Control', 'PD', 'STROKE', 'TBI'};
start_rows = [1, 254, 413, 602]; % Indici di inizio per ogni gruppo
end_rows = [253, 412, 601, 680]; % Indici di fine per ogni gruppo

for i = 1:length(groups)
    % Seleziono le righe per il gruppo corrente da TAB10
    group_data = TAB10(strcmp(TAB10.GROUP, groups{i}), :);

    % Prendo solo le prime 15 righe 
    num_rows = min(15, size(group_data, 1));   %prendo al massimo 15 righe del gruppo, ma se il gruppo ha meno di 15 righe, allora prenderai tutte le righe disponibili. Quindi, se il gruppo ha meno di 15 righe, il codice prende tutte le righe, altrimenti prende solo le prime 15 righe.
    group_data_reduced = group_data(1:num_rows, :);    %Seleziona le righe dalla 1 alla num_rows (ad esempio, se ci sono 10 righe, prende le righe da 1 a 10).

    % Aggiungo i dati ridotti a TABreduced
    TABreduced = [TABreduced; group_data_reduced];

    end


Calcolo, per tutte le malattie, il CV.
CV = 100SD∕mean where mean is the mean step length and SD is the standard deviation over the entire step length for each subject. The higher the CV, the higher is the variability of the step length. Un valore basso del CV indica una maggiore regolarità (i passi sono più simili tra loro), mentre un valore alto indica una maggiore variabilità tra gli stride.
stride=l'intervallo di movimento compreso tra due appoggi consecutivi dello stesso piede.

CV_individuale = zeros(height(TABreduced),1); 

for ii = 1:height(TABreduced)
    stride = TABreduced.STRIDE_LENGTH{ii}; 
    CV_ind = 100 * std(stride,'omitmissing') / mean(stride,'omitmissing');
    CV_individuale(ii) = CV_ind;
end

% Aggiungo la colonna alla tabella
TABreduced.CV_individuale = CV_individuale;

CV della stride_length medio per gruppo
CV_Healthy = mean(TABreduced.CV_individuale(1:15))
CV_PD = mean(TABreduced.CV_individuale(16:30))
CV_ST = mean(TABreduced.CV_individuale(31:45))
CV_TBI= mean(TABreduced.CV_individuale(46:60))

PCI individuale per tutti i pazienti

PCI_individuale = zeros(height(TABreduced),1); %Creo nuova colonna

for ii = 1:height(TABreduced)


ic_times = TABreduced.INDIP(ii).ContinuousWalkingPeriod.InitialContact_Event;
ic_side  = TABreduced.INDIP(ii).ContinuousWalkingPeriod.InitialContact_LeftRight;

ic_times = ic_times(:);
ic_side = ic_side(:);

right_idx = [];
left_idx = [];

for c = 1:length(ic_side)
% Separazione in destra e sinistra
    if ic_side(c) == "Right"
    right_idx = [right_idx c];
else
    left_idx = [left_idx c];
    end
end
% Assumo A = destro, B = sinistro
% Calcolo fasi φi tra un heel strike sinistro tra due destri (o viceversa)
phi = [];

for i = 1:length(right_idx)-1
    A1 = ic_times(right_idx(i));
    A2 = ic_times(right_idx(i+1));
    
    % Cerco heel strike sinistro che cade tra A1 e A2
    mid_L_idx = find(ic_times(left_idx) > A1 & ic_times(left_idx) < A2);
    
    if ~isempty(mid_L_idx)
        B = ic_times(left_idx(mid_L_idx(1)));
        phi_i = ((B - A1) / (A2 - A1)) * 360;
        phi(end+1) = phi_i;
    end
% PCI = CV_phi + ME_phi
mean_phi = mean(phi);
std_phi = std(phi);
CV_phi = (std_phi / mean_phi) * 100;
ME_phi = mean(abs(phi - 180));

PCI = CV_phi + ME_phi;

PCI_individuale(ii) = PCI;

    end
end

% Aggiungo la colonna alla tabella
TABreduced.PCI_individuale = PCI_individuale;


% Crea il vettore dei gruppi (20 per ciascuno)
%analisi statistica
n = 15;
gruppi = [repmat({'Healthy_Control'}, n, 1); ...
          repmat({'PD'}, n, 1); ...
          repmat({'STROKE'}, n, 1); ...
          repmat({'TBI'}, n, 1)];
gruppi = categorical(gruppi);

% Esempio: PCI = dati a caso (solo per test)
PCI = TABreduced.PCI_individuale;  

% ANOVA a una via
[p, tbl, stats] = anova1(PCI, gruppi);
% Test post-hoc Tukey HSD
figure;
[c,~,~,gnames] = multcompare(stats);
%p-value per confermare differenze viste nel multcompare

tbl = array2table(c, 'VariableNames', ...
    ["GroupA", "GroupB", "LowerLimit", "A_B", "UpperLimit", "P_value"]);

tbl.GroupA = gnames(tbl.GroupA);
tbl.GroupB = gnames(tbl.GroupB)

GAIT ASIMMETRY (GA)
GA_individuale = zeros(height(TABreduced),1); % create new column

for ii = 1:height(TABreduced)
    swing_times = TABreduced.INDIP(ii).ContinuousWalkingPeriod.Swing_Duration;
    swing_side  = TABreduced.INDIP(ii).ContinuousWalkingPeriod.InitialContact_LeftRight;

    % Ensure these are columns
    swing_times = swing_times(:);
    swing_side = swing_side(:);

    right_idx_swing = [];
    left_idx_swing = [];

    % Separate right and left swings
    for c = 1:length(swing_side)
        if swing_side(c) == "Right"
            right_idx_swing = [right_idx_swing c];
        else
            left_idx_swing = [left_idx_swing c];
        end
    end

    % Assumptions: C = right, D = left
    GA = []; % Initialize GA for this individual

 if length(right_idx_swing) && ~isempty(left_idx_swing)
        for i = 1:(length(right_idx_swing)-1)
            C1 = swing_times(right_idx_swing(i));    
            C2 = swing_times(right_idx_swing(i+1));

    mid_L_idx_swing = find(swing_times(left_idx_swing) > C1 & swing_times(left_idx_swing) < C2);
        if ~isempty(mid_L_idx_swing) && all(left_idx_swing(mid_L_idx_swing) <= length(swing_times))
    D = swing_times(left_idx_swing(mid_L_idx_swing(1))); 
        end
            % Calculate GA for this pair (use log to calculate asymmetry)
            GA = 100 * log(mean(C1) / mean(D));

        end
    end
end

GA_individuale(ii) = GA; 
TABreduced.GA_individuale = GA_individuale;
    

HEALTHY
%% calcolo Healthy solo per TABreduced, prendendo i dati da 1 a 20 (tutti gli Healthy per il cammino rettilineo nella tabella ridotta)

for ii = 1:15
    lb_acc_Healthy10 = TABreduced.LBACK(ii).acc; lb_acc_Healthy10 = fillmissing(lb_acc_Healthy10,'spline'); lb_acc_Healthy10 = bwfilt(lb_acc_Healthy10,2,128,10,'low');
    lb_gyr_Healthy10 = TABreduced.LBACK(ii).gyro; lb_gyr_Healthy10 = fillmissing(lb_gyr_Healthy10,'spline'); lb_gyr_Healthy10 = bwfilt(lb_gyr_Healthy10,2,128,6,'low');
    % 
    % st_acc = TAB10.STERN(ii).acc; st_acc = fillmissing(st_acc,'spline'); st_acc = bwfilt(st_acc,2,128,10,'low');
    % st_gyr = TAB10.STERN(ii).gyro; st_gyr = fillmissing(st_gyr,'spline'); st_gyr = bwfilt(st_gyr,2,128,6,'low');
    % 
    % fh_acc = TAB10.FHEAD(ii).acc; fh_acc = fillmissing(fh_acc,'spline'); fh_acc = bwfilt(fh_acc,2,128,10,'low');
    % fh_gyr = TAB10.FHEAD(ii).gyro; fh_gyr = fillmissing(fh_gyr,'spline'); fh_gyr = bwfilt(fh_gyr,2,128,6,'low');
    % 

    contacts = TABreduced.CONTANCTS{ii};
    side = TABreduced.SIDE{ii};

    [r,c] = (find(isnan(contacts))); r = unique(r); contacts(r,:) = []; %TAB.CONTANCTS{ii} = contacts;
    side(:,r) = []; %TAB.SIDE{ii} = side;
       

    for co = 1:length(contacts)
% if

    end
end

DST (Double support Time) ->è un indice temporale che permette di discriminare la gravità del parkinson, non è un indice di variabilità motoria. pazienti con patologie neurologiche tipo parkinson hanno un DST piu lungo perche per paura di cadere rimangono piu tempo con i piedi a terra a differenza degli Healthy che sono piu sicuri durante la camminata
IL CoV di DST POTREBBE ESSERE CALCOLATO
% DefiniscO un threshold per rilevare i picchi nei segnali di accelerazione
threshold = 0.5;

% TrovO i picchi nel segnale di accelerazione
acc_z = lb_acc_Healthy10(:,3); % Prendo solo la componente Z
[peaks, locs] = findpeaks(abs((acc_z)), 'MinPeakHeight', threshold, 'MinPeakDistance', 200); 

% Aggiungo un ciclo per associare i picchi con i contatti
heel_strikes = {}; % Inizializzo una lista per i momenti di heel strike

% Associo i picchi ai momenti di contatto
for i = 1:length(ic_times)
    contact_time = ic_times(i); % Tempo del contatto
    side = ic_side(i);          % Sinistro o destro

    % Trova i picchi che avvengono vicino al tempo del contatto (0.8s)
    %dal grafico abbiamo visto la distanza tra una linea tratteggiata, che rappresenta il contact time, el'altra
 idx = find(locs/128 >= contact_time - 1 & locs/128 <= contact_time + 1);
t = (1:length(acc_z)) / 128; % asse dei tempi
plot(t, acc_z); hold on;
plot(locs/128, acc_z(locs), 'rv'); % picchi
xline(contact_time, 'g--'); % tempo del contatto
legend('acc_z','picchi','contact time');
    if ~isempty(idx)
        for j = 1:length(idx)
             
            heel_strikes{end+1,1} = contact_time;
            heel_strikes{end,  2} = side; % stringa 'Right' o 'Left'
            heel_strikes{end,  3} = locs(idx(j))/128; % tempo del picco
            heel_strikes{end,  4} = peaks(idx(j));
        end
    end
end
%nel workspace, in heel_strikes troviamo: prima colonna è il contact time
%(quando il piede tocca terra), la seconda (il piede), la terza (istante in
%cui si rileva un picco, che corrisponde a quando uno dei due piedi tocca
%terra), la quarta rappresenta il valore del picco dell'accelerazione del
%piede che tocca terra, vedi foto gruppo


rs = []; % Right side
ls = []; % Left side
% Scorri tutte le righe di heel_strikes
for i = 1:size(heel_strikes, 1)
    side = heel_strikes{i, 2};       % 'Right' o 'Left'
    contact_time = heel_strikes{i, 1}; % tempo di contatto

    % Aggiungi il contact_time al vettore corrispondente
    if strcmp(side, 'Right')
        rs(end+1, 1) = contact_time;
    elseif strcmp(side, 'Left')
        ls(end+1, 1) = contact_time;
    end
end
% STEP 2: CALCOLO DST PER OGNI CICLO
numCycles = min(length(rs), length(ls));
DST = zeros(numCycles-1,1);
for k = 2:numCycles

    % sovrapposizione: da max dei due contatti precedenti a min dei due successivi
    DST(k-1) = min(rs(k), ls(k)) - max(rs(k-1), ls(k-1));
end

% STEP 3: AGGIUNTA ALLA TABELLA
TABreduced.DST{ii}     = DST;                       % vettore DST per trial
TABreduced.MeanDST(ii) = mean(DST,'omitnan');      % media DST
TABreduced.CVDST(ii)   = 100 * std(DST,'omitnan') / TABreduced.MeanDST(ii);  % coeff. di variazione



PD
%% calcolo PD solo per TAB10

for ii = 16:30
    lb_acc_PD10 = TAB10.LBACK(ii).acc; lb_acc_PD10 = fillmissing(lb_acc_PD10,'spline'); lb_acc_PD10 = bwfilt(lb_acc_PD10,2,128,10,'low');
    lb_gyr_PD10 = TAB10.LBACK(ii).gyro; lb_gyr_PD10 = fillmissing(lb_gyr_PD10,'spline'); lb_gyr_PD10 = bwfilt(lb_gyr_PD10,2,128,6,'low');

    % st_acc = TAB10.STERN(ii).acc; st_acc = fillmissing(st_acc,'spline'); st_acc = bwfilt(st_acc,2,128,10,'low');
    % st_gyr = TAB10.STERN(ii).gyro; st_gyr = fillmissing(st_gyr,'spline'); st_gyr = bwfilt(st_gyr,2,128,6,'low');
    % 
    % fh_acc = TAB10.FHEAD(ii).acc; fh_acc = fillmissing(fh_acc,'spline'); fh_acc = bwfilt(fh_acc,2,128,10,'low');
    % fh_gyr = TAB10.FHEAD(ii).gyro; fh_gyr = fillmissing(fh_gyr,'spline'); fh_gyr = bwfilt(fh_gyr,2,128,6,'low');


    contacts = TAB10.CONTANCTS{ii};
    side = TAB10.SIDE{ii};

    [r,c] = (find(isnan(contacts))); r = unique(r); contacts(r,:) = []; %TAB.CONTANCTS{ii} = contacts;
    side(:,r) = []; %TAB.SIDE{ii} = side;


    for co = 1:length(contacts)

%if
    end 
end

 ST
for ii = 31:45
    lb_acc_ST10 = TAB10.LBACK(ii).acc; lb_acc_ST10 = fillmissing(lb_acc_ST10,'spline'); lb_acc_ST10 = bwfilt(lb_acc_ST10,2,128,10,'low');
    lb_gyr_ST10 = TAB10.LBACK(ii).gyro; lb_gyr_ST10 = fillmissing(lb_gyr_ST10,'spline'); lb_gyr_ST10 = bwfilt(lb_gyr_ST10,2,128,6,'low');

    % st_acc = TAB10.STERN(ii).acc; st_acc = fillmissing(st_acc,'spline'); st_acc = bwfilt(st_acc,2,128,10,'low');
    % st_gyr = TAB10.STERN(ii).gyro; st_gyr = fillmissing(st_gyr,'spline'); st_gyr = bwfilt(st_gyr,2,128,6,'low');
    % 
    % fh_acc = TAB10.FHEAD(ii).acc; fh_acc = fillmissing(fh_acc,'spline'); fh_acc = bwfilt(fh_acc,2,128,10,'low');
    % fh_gyr = TAB10.FHEAD(ii).gyro; fh_gyr = fillmissing(fh_gyr,'spline'); fh_gyr = bwfilt(fh_gyr,2,128,6,'low');


    contacts = TAB10.CONTANCTS{ii};
    side = TAB10.SIDE{ii};

    [r,c] = (find(isnan(contacts))); r = unique(r); contacts(r,:) = []; %TAB.CONTANCTS{ii} = contacts;
    side(:,r) = []; %TAB.SIDE{ii} = side;


    for co = 1:length(contacts)

%if
       
        end
end

% TBI
    %% For TBI (Traumatic Brain Injury)
    for ii = 46:60   
    lb_acc_TBI10 = TAB10.LBACK(ii).acc; lb_acc_TBI10 = fillmissing(lb_acc_TBI10,'spline'); lb_acc_TBI10 = bwfilt(lb_acc_TBI10,2,128,10,'low');
    lb_gyr_TBI10 = TAB10.LBACK(ii).gyro; lb_gyr_TBI10 = fillmissing(lb_gyr_TBI10,'spline'); lb_gyr_TBI10 = bwfilt(lb_gyr_TBI10,2,128,6,'low');

    % st_acc = TAB10.STERN(ii).acc; st_acc = fillmissing(st_acc,'spline'); st_acc = bwfilt(st_acc,2,128,10,'low');
    % st_gyr = TAB10.STERN(ii).gyro; st_gyr = fillmissing(st_gyr,'spline'); st_gyr = bwfilt(st_gyr,2,128,6,'low');
    % 
    % fh_acc = TAB10.FHEAD(ii).acc; fh_acc = fillmissing(fh_acc,'spline'); fh_acc = bwfilt(fh_acc,2,128,10,'low');
    % fh_gyr = TAB10.FHEAD(ii).gyro; fh_gyr = fillmissing(fh_gyr,'spline'); fh_gyr = bwfilt(fh_gyr,2,128,6,'low');


    contacts = TAB10.CONTANCTS{ii};
    side = TAB10.SIDE{ii};

    [r,c] = (find(isnan(contacts))); r = unique(r); contacts(r,:) = []; %TAB.CONTANCTS{ii} = contacts;
    side(:,r) = []; %TAB.SIDE{ii} = side;


    for co = 1:length(contacts)

%if
    end
    end



   
Estrae il segnale Z da ogni soggetto.
Riempie i dati mancanti e applica un filtro passa basso.
Rimuove il trend dal segnale.
Costruisce lo spazio delle fasi (embedding) con:
Dimensione m = 5
Ritardo τ = 10 (≈ 80 ms se fs = 128 Hz)
Trova per ogni punto il vicino più vicino non temporale.
Calcola la divergenza logaritmica nel tempo per 20 passi.
Usa una regressione lineare per stimare la pendenza = Lyapunov exponent.
    %% Calcolo dell’Esponente di Lyapunov per ogni soggetto in TABreduced
LyE_individuale = zeros(height(TABreduced), 1); % inizializzazione

for ii = 1:height(TABreduced)
    % Dati accelerometrici: scegli asse verticale (Z)
    acc_data = TABreduced.LBACK(ii).acc;
    
    if isempty(acc_data)
        LyE_individuale(ii) = NaN;
        continue;
    end

    acc_data = fillmissing(acc_data,'spline');
    acc_data = bwfilt(acc_data,2,128,10,'low'); % filtro passa basso

    signal = acc_data(:,3); % asse Z
    signal = detrend(signal); % rimuove trend lineare

    % Parametri per embedding
    m = 5; % dimensione dell'embedding
    tau = 10; % ritardo in punti (≈80ms con 128Hz)
    fs = 128; % frequenza di campionamento

    % Ricostruzione dello spazio delle fasi
    N = length(signal) - (m-1)*tau;
    X = zeros(N, m);
    for k = 1:m
        X(:,k) = signal(1+(k-1)*tau : N+(k-1)*tau);
    end

    % Calcolo delle distanze iniziali
    d0 = zeros(N,1);
    for k = 1:N
        % Trova punto vicino escludendo vicini temporali
        dist = vecnorm(X - X(k,:), 2, 2);
        dist(abs((1:N)' - k) < 30) = Inf; % escludi 30 campioni vicini
        [d0(k), j(k)] = min(dist);
    end
% Calcolo divergenza media nel tempo
    max_iter = 20; % numero di passi temporali
    divergence = zeros(max_iter,1);

    for n = 1:max_iter
        valid = (j+n <= N) & (1:N-n)';
        dist_n = vecnorm(X(valid + n,:) - X(j(valid) + n,:), 2, 2);
        divergence(n) = mean(log(dist_n ./ d0(valid)), 'omitnan');
    end

    % Regressione lineare per stimare la Lyapunov exponent
    t = (1:max_iter) / fs;
    p = polyfit(t, divergence(1:max_iter)', 1);
    LyE_individuale(ii) = p(1); % coefficiente angolare = esponente di Lyapunov
end

% Aggiungo alla tabella
TABreduced.LyE = LyE_individuale;

% Visualizzazione
figure;
boxplot(LyE_individuale, gruppi);
ylabel('Lyapunov Exponent');
title('LyE per Gruppo (Healthy vs Patologici)');

% ANOVA
[p_lye, tbl_lye, stats_lye] = anova1(LyE_individuale, gruppi);
[c_lye,gnames_lye] = multcompare(stats_lye);
\$\endgroup\$
0

1 Answer 1

3
\$\begingroup\$

use functions

make sure it's clean ...

The Review Context introduces this as a single matlab "script", and that's certainly accurate. It would be much nicer if it was organized as, say, a 4-line script,

  • load()
  • clean()
  • analyze()
  • display()

with the meat of it buried in helper functions.

As written, the OP code does not lend itself to interactive debugging, nor to automated unit tests. In fact, it appears that we seldom run it all the way through, since we see lines like "GAIT ASIMMETRY (GA)" and "HEALTHY" that don't correspond to valid matlab syntax.

There's nearly 700 lines of source code presented. That's too much to place in just a single unit. Define helper functions to impose some structure.

helpful comments

There's already a bunch of very nice comments calling out different processing sections. Use them to invent new function names for those sections.

coupling

Putting all identifiers at top level, in matlab or in any other language, leads to excessive coupling, with an unwieldy number of globals in scope. No one will be able to remember the details of all of those, such as whether t currently has one meaning or if at this point in the code it has been redefined to have a different meaning.

The great thing about functions, especially "short" functions, is that temp vars go out of scope upon return. And typically there's just a single return value to worry about and reason about.

long lines

This is a very nice comment. But by putting it all on a single line, requiring lots of horizontal scrolling, you're essentially telling folks "don't read this!".

DST (Double support Time) ->è un indice temporale che permette di discriminare la gravità del parkinson, non è un indice di variabilità motoria. pazienti con patologie neurologiche tipo parkinson hanno un DST piu lungo perche per paura di cadere rimangono piu tempo con i piedi a terra a differenza degli Healthy che sono piu sicuri durante la camminata

Prefer to wrap it, as seen in the subsequent "%nel workspace .. foto gruppo" comment.

Also, many of your comments oddly omit "%". This seems to suggest that large portions of the script are seldom tested / executed.

commented code

    lb_gyr_Healthy10 = TABreduced.LBACK(ii).gyro; lb_gyr_Healthy10 = fillmissing(lb_gyr_Healthy10,'spline'); lb_gyr_Healthy10 = bwfilt(lb_gyr_Healthy10,2,128,6,'low');
    % 
    % st_acc = TAB10.STERN(ii).acc; st_acc = fillmissing(st_acc,'spline'); st_acc = bwfilt(st_acc,2,128,10,'low');
    % st_gyr = TAB10.STERN(ii).gyro; st_gyr = fillmissing(st_gyr,'spline'); st_gyr = bwfilt(st_gyr,2,128,6,'low');
    % 
    % fh_acc = TAB10.FHEAD(ii).acc; fh_acc = fillmissing(fh_acc,'spline'); fh_acc = bwfilt(fh_acc,2,128,10,'low');
    % fh_gyr = TAB10.FHEAD(ii).gyro; fh_gyr = fillmissing(fh_gyr,'spline'); fh_gyr = bwfilt(fh_gyr,2,128,6,'low');

Take care to remove such statements before requesting review.

% if

I imagine you sometimes write if false in order to prevent a section of code from executing. This is a symptom, a code smell. The code is telling you that it really wants to be broken up into smaller functions, so you may more easily work with it.

Rather than putting adjacent end statements at the identical indent level, prefer to use indenting to reveal how deeply nested a block of code is.

copy-n-paste

    spa = [];

    for jj = 1:size(mwb,2)
        temp = mwb(jj).Stance_Duration;
        spa = [spa temp];
    end

    TAB.STANCE_DURATION(ii) = {(spa)};


    spa = [];

    for jj = 1:size(mwb,2)
        temp = mwb(jj).Stance_Length;
        spa = [spa temp];
    end

    TAB.STANCE_LENGTH(ii) = {(spa)};


    spa = [];

    for jj = 1:size(mwb,2)
        temp = mwb(jj).Stance_Speed;
        spa = [spa temp];
    end

    TAB.STANCE_SPEED(ii) = {(spa)};

When you see yourself repeatedly pasting the "same" code into the editor, and then tweaking it a bit, stop. Take a step back. See if you can generalize what's happening here, and capture that in a suitably parameterized helper function.

\$\endgroup\$
0

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.