Master Thesis Code
by Simon Moser
Loading...
Searching...
No Matches
calculate_gait_parameters.m
Go to the documentation of this file.
1% =========================================================================== %
2%> @brief calculates gait parameters from the processed data
3%>
4%> this function calculates the gait parameters from the processed data. the
5%> gait parameters are calculated for each trial and then combined over all
6%> trials. the gait parameters are:
7%> - stride length
8%> - step length
9%> - step width
10%>
11%> @param basepath: (string) the basepath of the data, e.g. "D:/ETH_Data/"
12%> @param ptp: (string) the participant to process, e.g. "SonE_01"
13%> @param options the options for the processing:
14%> - RECALCULATE: (logical) recalculate the gait parameters even if they are
15%> already calculated
16%>
17%> @retval none
18%>
19%> @copyright see the file @ref LICENSE in the root directory of the repository
20% =========================================================================== %
21function calculate_gait_parameters(basepath, ptp, options)
22
23arguments
24 basepath string
25 ptp string
26 options.RECALCULATE logical = true
27 options.create_plots logical = false
28end
29
30% convert options
31RECALCULATE = options.RECALCULATE;
32
33% define the log
34diary(fullfile(basepath, ptp, ptp + ".log"))
35
36% get the participant number
37ptp_num = regexp(ptp, "SonE_(\d+)|Pilot_(\d+)", "tokens");
38ptp_num = str2double(ptp_num{1});
39
40% log the start of the processing
41log_entry("Start calculating gait parameters", ptp_num)
42
43%% load the process info
44filtering_done = false;
45num_waited = 0;
46while ~filtering_done
47 proc_info = readstruct(fullfile(basepath, ptp, "proc_info.json"));
48
49 if isfield(proc_info, "filtering_done")
50
51 if proc_info.filtering_done
52 filtering_done = proc_info.filtering_done;
53 else
54
55 log_entry("Filtering is not done yet. I'll wait for a bit.", ptp_num, 5)
56
57 num_waited = num_waited + 1;
58
59 if num_waited > 10
60 log_entry("Something seems to be wrong. I'll stop waiting.", ptp_num, 2)
61
62 error("I'm stuck in a waiting loop...")
63 end
64 pause(5) % wait for 5 seconds
65 end
66
67 else
68 error("The filtering done field is missing");
69 end
70
71end
72
73% ckeck if the gait parameters are already calculated
74if isfield(proc_info, "gait_parameters_calculated") && ~RECALCULATE
75 if proc_info.gait_parameters_calculated
76 log_entry("Gait parameters are already calculated", ptp_num, 4)
77
78 return
79 end
80end
81
82% load the processed data
83try
84 load(fullfile(basepath, ptp, "temp", "processed_data.mat"), "result");
85catch ME
86 log_entry("The processed data could not be loaded", ptp_num, 2)
87
88 % log the error
89 log_entry(ME.message, ptp_num, 2)
90 for i = 1:numel(ME.stack)
91 log_entry(ME.stack(i).name + " (Line: " + string(ME.stack(i).line) + ")", ptp_num, 2)
92 end
93
94 error("The processed data could not be loaded")
95end
96
97% redefine the gait events
98% until now, I only have the TO and HS events. Since the actual tempospatial
99% parameters should be compared, I want to ensure that I catch a midstance
100% moment. then I defiinitively have zero velocity detection in place. this
101% will also not somehow "fake" better results, since the feet are not moving
102% at this point. It will only reduce effects where the feet were already steady
103% in reality but the filter has them in a steady state only a bit later.
104%
105% the midstance (MS) will be defined as the center between the HS and TO of the
106% same stance phase. this will be done for both feet.
107%
108% yes, MS is not really an event but a period. since I only get one index per
109% midstance period, I still call this moment the MS event.
110
111% loop over all trials
112trials = string(fieldnames(result.vicon))';
113for trial = trials
114
115 % get the gait events
116 HS_l_idx = result.vicon.(trial).GaitEvents.HSleftlocs;
117 TO_l_idx = result.vicon.(trial).GaitEvents.TOleftlocs;
118 HS_r_idx = result.vicon.(trial).GaitEvents.HSrightlocs;
119 TO_r_idx = result.vicon.(trial).GaitEvents.TOrightlocs;
120
121 % make sure that the first HS is before the first TO
122 while HS_l_idx(1) > TO_l_idx(1)
123 TO_l_idx(1) = [];
124
125 log_entry("Removed first left TO because it was before the first HS", ptp_num, 5)
126 end
127 while HS_r_idx(1) > TO_r_idx(1)
128 TO_r_idx(1) = [];
129
130 log_entry("Removed first right TO because it was before the first HS", ptp_num, 5)
131 end
132
133 % make sure that the last TO is after the last HS
134 while TO_l_idx(end) < HS_l_idx(end)
135 HS_l_idx(end) = [];
136
137 log_entry("Removed last left HS because it was after the last TO", ptp_num, 5)
138 end
139 while TO_r_idx(end) < HS_r_idx(end)
140 HS_r_idx(end) = [];
141
142 log_entry("Removed last right HS because it was after the last TO", ptp_num, 5)
143 end
144
145 % make sure that the number of events is the same
146 try
147 assert(numel(HS_l_idx) == numel(TO_l_idx), "The number of left events is not the same")
148 assert(numel(HS_r_idx) == numel(TO_r_idx), "The number of right events is not the same")
149 catch ME
150 log_entry(ME.message, ptp_num, 2)
151
152 % log the error
153 log_entry(ME.message, ptp_num, 2)
154 for i = 1:numel(ME.stack)
155 log_entry(ME.stack(i).name + " (Line: " + string(ME.stack(i).line) + ")", ptp_num, 2)
156 end
157
158 continue;
159 end
160
161 % calculate the midstance moment as the rounded mean of the HS and TO
162 MS_l_idx = round((HS_l_idx + TO_l_idx) / 2);
163 MS_r_idx = round((HS_r_idx + TO_r_idx) / 2);
164
165 % store the new events
166 result.gait_events.(trial).MS_l = MS_l_idx;
167 result.gait_events.(trial).MS_r = MS_r_idx;
168 result.gait_events.(trial).num_stance_l = numel(HS_l_idx);
169 result.gait_events.(trial).num_stance_r = numel(HS_r_idx);
170
171 log_entry("Gait events redefined", ptp_num, 5, trial)
172 log_entry(sprintf("Number of stance phases: left: %d, right: %d", result.gait_events.(trial).num_stance_l, result.gait_events.(trial).num_stance_r), ptp_num, 5, trial)
173
174end
175
176log_entry("Gait events redefined", ptp_num, 4)
177
178% get the walking axes
179%> @note this is for the vison data. the axes of the jump sensor coming from
180%> the filter are defined as follows:
181%> - sagittal: x-axis, positive forwards
182%> - longitudinal: z-axis, positive downwards
183%> - lateral: y-axis, negative in sinister direction
184try
185 sag_axis = axis2num(proc_info.walking_axes.sagittal);
186 %lon_axis = axis2num(proc_info.walking_axes.longitudinal); % not used yet
187 lat_axis = axis2num(proc_info.walking_axes.lateral);
188 %upwards = direction2num(proc_info.walking_axes.upwards); % not used yet
189 forwards = direction2num(proc_info.walking_axes.forwards);
190 sinister = direction2num(proc_info.walking_axes.sinister);
191catch ME
192 log_entry("The walking axes are not defined", ptp_num, 2)
193
194 % log the error
195 log_entry(ME.message, ptp_num, 2)
196 for i = 1:numel(ME.stack)
197 log_entry(ME.stack(i).name + " (Line: " + string(ME.stack(i).line) + ")", ptp_num, 2)
198 end
199
200 error("The walking axes are not defined")
201
202end
203
204% calculate the gait parameters
205
206% variables to store total number of parameters over all trials
207num_stride_length_l = 0;
208num_stride_length_r = 0;
209num_step_length_l = 0;
210num_step_length_r = 0;
211num_step_width_l = 0;
212num_step_width_r = 0;
213
214% loop over all trials
215for trial = trials
216
217 % get the gait events
218 try
219 MS_l_idx = result.gait_events.(trial).MS_l;
220 MS_r_idx = result.gait_events.(trial).MS_r;
221 catch
222 error_filename = "error_" + ptp + "_" + trial + ".mat";
223 save(fullfile(tempdir, error_filename), "result")
224 log_entry("The gait events are not defined", ptp_num, 2)
225
226 continue
227 end
228
229 % check if the gait events are alternately left and right
230 MS_test = sort([MS_l_idx; MS_r_idx]);
231 MS_test = MS_test(1:2:end);
232
233 try
234 if numel(MS_test) == numel(MS_l_idx)
235 assert(all(MS_test == MS_l_idx), "The gait events are not alternating left and right")
236 first_stance = "left";
237 else
238 assert(all(MS_test == MS_r_idx), "The gait events are not alternating left and right")
239 first_stance = "right";
240 end
241 catch ME
242
243 % log the error
244 log_entry(ME.message, ptp_num, 2)
245 for i = 1:numel(ME.stack)
246 log_entry(ME.stack(i).name + " (Line: " + string(ME.stack(i).line) + ")", ptp_num, 2)
247 end
248
249 error("The gait events are not alternating left and right")
250 end
251
252 % last stance
253 if MS_l_idx(end) > MS_r_idx(end)
254 last_stance = "left";
255 else
256 last_stance = "right";
257 end
258
259 %% introduce sagital and lateral offsets
260 % both sides were filtered independently. the filter always initializes
261 % the position at zero. this leads to a shift in the sagital and lateral
262 % axis.
263 %
264 % the sagital offset is introduced by shifting the right foot to minimize
265 % the mean difference between the left and right foot. this is done by
266 % fitting a linear function to the right foot and shifting it.
267 %
268 % for the lateral axis, the jump sensors cannot know the walking base.
269 % therefore, the mean walking base is calculated from the VICON data and
270 % then one sensor is shifted to introduce the same walking base.
271
272 % sagital offset
273
274 % shift the right foot to minimize the mean difference
275 left_linear = fit((1:MS_l_idx(end)-MS_l_idx(1)+1)', result.jump_filtered.(trial).l(MS_l_idx(1):MS_l_idx(end),1), "poly1");
276 right_linear = fit((1:MS_l_idx(end)-MS_l_idx(1)+1)', result.jump_filtered.(trial).r(MS_l_idx(1):MS_l_idx(end),1), "poly1"); % the reference to the left foot is correct
277 shift = left_linear.p2 - right_linear.p2;
278
279 % shift the right foot
280 result.jump_filtered.(trial).r(:, 1) = result.jump_filtered.(trial).r(:, 1) + shift;
281 result.jump_filtered.(trial).right_shift_sagital = shift;
282
283 % lateral offset
284
285 % calculate the mean walking base from the VICON data
286 num_steps = min(numel(MS_l_idx), numel(MS_r_idx));
287 mean_base = mean(abs(...
288 result.vicon.(trial).KinematicData.Marker.LF(MS_l_idx(1:num_steps), lat_axis) - ...
289 result.vicon.(trial).KinematicData.Marker.RF(MS_r_idx(1:num_steps), lat_axis)), ...
290 "omitmissing");
291
292 % shift the right foot to the right
293 result.jump_filtered.(trial).r(:, 2) = result.jump_filtered.(trial).r(:, 2) + mean_base * -sinister;
294 result.jump_filtered.(trial).right_shift_lateral = mean_base;
295
296 %% stride length
297 % the stride length is defined as the distance between the same foot at
298 % two consecutive midstance events. the distance is calculated in the
299 % sagittal axis.
300 stride_length_vicon_l = abs(diff(result.vicon.(trial).KinematicData.Marker.LF(MS_l_idx, sag_axis), 1, 1));
301 stride_length_vicon_r = abs(diff(result.vicon.(trial).KinematicData.Marker.RF(MS_r_idx, sag_axis), 1, 1));
302 stride_length_imu_l = abs(diff(result.jump_filtered.(trial).l(MS_l_idx, 1), 1, 1));
303 stride_length_imu_r = abs(diff(result.jump_filtered.(trial).r(MS_r_idx, 1), 1, 1));
304
305 result.gait_parameters.(trial).stride_length.l.vicon = stride_length_vicon_l;
306 result.gait_parameters.(trial).stride_length.r.vicon = stride_length_vicon_r;
307 result.gait_parameters.(trial).stride_length.l.imu = stride_length_imu_l;
308 result.gait_parameters.(trial).stride_length.r.imu = stride_length_imu_r;
309
310 num_stride_length_l = num_stride_length_l + numel(stride_length_vicon_l);
311 num_stride_length_r = num_stride_length_r + numel(stride_length_vicon_r);
312
313 %% step length
314 % the step length is defined as the distance between two consecutive
315 % midstance events of the opposite feet. the distance is calculated in the
316 % sagittal axis.
317
318 % left step length
319 if first_stance == "left"
320 idx_start_l = 2;
321 idx_start_r = 1;
322 else
323 idx_start_l = 1;
324 idx_start_r = 1;
325 end
326 num_steps_l = min(numel(MS_l_idx(idx_start_l:end)), numel(MS_r_idx(idx_start_r:end)));
327
328 positions_vicon_l = result.vicon.(trial).KinematicData.Marker.LF(MS_l_idx(idx_start_l:idx_start_l+num_steps_l-1), sag_axis);
329 positions_vicon_r = result.vicon.(trial).KinematicData.Marker.RF(MS_r_idx(idx_start_r:idx_start_r+num_steps_l-1), sag_axis);
330 positions_imu_l = result.jump_filtered.(trial).l(MS_l_idx(idx_start_l:idx_start_l+num_steps_l-1), 1);
331 positions_imu_r = result.jump_filtered.(trial).r(MS_r_idx(idx_start_r:idx_start_r+num_steps_l-1), 1);
332
333 step_length_vicon_l = (positions_vicon_l - positions_vicon_r) * forwards;
334 step_length_imu_l = positions_imu_l - positions_imu_r;
335
336 % right step length
337 if first_stance == "right"
338 idx_start_l = 1;
339 idx_start_r = 2;
340 else
341 idx_start_l = 1;
342 idx_start_r = 1;
343 end
344 num_steps_r = min(numel(MS_l_idx(idx_start_l:end)), numel(MS_r_idx(idx_start_r:end)));
345
346 positions_vicon_l = result.vicon.(trial).KinematicData.Marker.LF(MS_l_idx(idx_start_l:idx_start_l+num_steps_r-1), sag_axis);
347 positions_vicon_r = result.vicon.(trial).KinematicData.Marker.RF(MS_r_idx(idx_start_r:idx_start_r+num_steps_r-1), sag_axis);
348 positions_imu_l = result.jump_filtered.(trial).l(MS_l_idx(idx_start_l:idx_start_l+num_steps_r-1), 1);
349 positions_imu_r = result.jump_filtered.(trial).r(MS_r_idx(idx_start_r:idx_start_r+num_steps_r-1), 1);
350
351 step_length_vicon_r = (positions_vicon_r - positions_vicon_l) * forwards;
352 step_length_imu_r = positions_imu_r - positions_imu_l;
353
354 result.gait_parameters.(trial).step_length.l.vicon = step_length_vicon_l;
355 result.gait_parameters.(trial).step_length.r.vicon = step_length_vicon_r;
356 result.gait_parameters.(trial).step_length.l.imu = step_length_imu_l;
357 result.gait_parameters.(trial).step_length.r.imu = step_length_imu_r;
358
359 num_step_length_l = num_step_length_l + numel(step_length_vicon_l);
360 num_step_length_r = num_step_length_r + numel(step_length_vicon_r);
361
362 %% step width
363 % the step width is defined as the lateral distance between the heels at
364 % two consecutive midstance events.
365
366 % get all vicon heel positions
367 positions_vicon_l = result.vicon.(trial).KinematicData.Marker.LF(MS_l_idx, lat_axis);
368 positions_vicon_r = result.vicon.(trial).KinematicData.Marker.RF(MS_r_idx, lat_axis);
369
370 % get all imu heel positions
371 positions_imu_l = result.jump_filtered.(trial).l(MS_l_idx, 2);
372 positions_imu_r = result.jump_filtered.(trial).r(MS_r_idx, 2);
373
374 % calculate the step width
375 try
376 if first_stance == "left" && last_stance == "left"
377 % if the first and last stance are left, there must be one more left
378 % stance than right stance.
379 assert(numel(MS_l_idx) == numel(MS_r_idx) + 1, "The number of left and right stances is not correct")
380
381 % vicon
382 result.gait_parameters.(trial).step_width.r.vicon = abs(positions_vicon_l(1:end-1) - positions_vicon_r) * sinister;
383 result.gait_parameters.(trial).step_width.l.vicon = abs(positions_vicon_l(2:end) - positions_vicon_r) * sinister;
384
385 % imu
386 result.gait_parameters.(trial).step_width.r.imu = abs(positions_imu_l(1:end-1) - positions_imu_r);
387 result.gait_parameters.(trial).step_width.l.imu = abs(positions_imu_l(2:end) - positions_imu_r);
388
389 elseif first_stance == "right" && last_stance == "right"
390 % if the first and last stance are right, there must be one more right
391 % stance than left stance.
392 assert(numel(MS_l_idx) + 1 == numel(MS_r_idx), "The number of left and right stances is not correct")
393
394 % vicon
395 result.gait_parameters.(trial).step_width.r.vicon = abs(positions_vicon_l - positions_vicon_r(2:end)) * sinister;
396 result.gait_parameters.(trial).step_width.l.vicon = abs(positions_vicon_l - positions_vicon_r(1:end-1)) * sinister;
397
398 % imu
399 result.gait_parameters.(trial).step_width.r.imu = abs(positions_imu_l - positions_imu_r(2:end));
400 result.gait_parameters.(trial).step_width.l.imu = abs(positions_imu_l - positions_imu_r(1:end-1));
401
402 elseif first_stance == "left" && last_stance == "right"
403 % if the first stance is left and the last stance is right, the number
404 % of stances must be the same.
405 assert(numel(MS_l_idx) == numel(MS_r_idx), "The number of left and right stances is not correct")
406
407 % vicon
408 result.gait_parameters.(trial).step_width.r.vicon = abs(positions_vicon_l - positions_vicon_r) * sinister;
409 result.gait_parameters.(trial).step_width.l.vicon = abs(positions_vicon_l(2:end) - positions_vicon_r(2:end)) * sinister;
410
411 % imu
412 result.gait_parameters.(trial).step_width.r.imu = abs(positions_imu_l - positions_imu_r);
413 result.gait_parameters.(trial).step_width.l.imu = abs(positions_imu_l(2:end) - positions_imu_r(2:end));
414
415 elseif first_stance == "right" && last_stance == "left"
416 % if the first stance is right and the last stance is left, the number
417 % of stances must be the same.
418 assert(numel(MS_l_idx) == numel(MS_r_idx), "The number of left and right stances is not correct")
419
420 % vicon
421 result.gait_parameters.(trial).step_width.r.vicon = abs(positions_vicon_l(2:end) - positions_vicon_r(2:end)) * sinister;
422 result.gait_parameters.(trial).step_width.l.vicon = abs(positions_vicon_l - positions_vicon_r) * sinister;
423
424 % imu
425 result.gait_parameters.(trial).step_width.r.imu = abs(positions_imu_l(2:end) - positions_imu_r(2:end));
426 result.gait_parameters.(trial).step_width.l.imu = abs(positions_imu_l - positions_imu_r);
427
428 else
429 error("The first and last stance are not defined")
430
431 end
432
433 num_step_width_l = num_step_width_l + numel(result.gait_parameters.(trial).step_width.l.vicon);
434 num_step_width_r = num_step_width_r + numel(result.gait_parameters.(trial).step_width.r.vicon);
435
436 catch ME
437
438 % log the error
439 log_entry(ME.message, ptp_num, 2)
440 for i = 1:numel(ME.stack)
441 log_entry(ME.stack(i).name + " (Line: " + string(ME.stack(i).line) + ")", ptp_num, 2)
442 end
443
444 continue
445
446 end
447
448 proc_info.trials.(trial).gait_parameters_calculated = true;
449
450 log_entry("Gait parameters calculated", ptp_num, 4, trial)
451
452end
453
454%% combine the gait parameters over all trials
455
456% create a table for the combined gait parameters
457% the table has the following variables:
458% - ptp: the participant (e.g. "SonE_01")
459% - trial: the trial name
460% - stride_length_l_vicon: the stride length of the left foot from the vicon data
461% - stride_length_r_vicon: the stride length of the right foot from the vicon data
462% - step_length_l_vicon: the step length of the left foot from the vicon data
463% - step_length_r_vicon: the step length of the right foot from the vicon data
464% - step_width_l_vicon: the step width of the left foot from the vicon data
465% - step_width_r_vicon: the step width of the right foot from the vicon data
466% - stride_length_l_imu: the stride length of the left foot from the imu data
467% - stride_length_r_imu: the stride length of the right foot from the imu data
468% - step_length_l_imu: the step length of the left foot from the imu data
469% - step_length_r_imu: the step length of the right foot from the imu data
470% - step_width_l_imu: the step width of the left foot from the imu data
471% - step_width_r_imu: the step width of the right foot from the imu data
472% - number: the number of the step of the trial
473combined = table('Size', [1500, 15], ...
474 'VariableTypes', ["categorical", "categorical", repmat("double", 1, 13)], ...
475 'VariableNames', ["ptp", "trial", "stride_length_l_vicon", "stride_length_r_vicon", ...
476 "step_length_l_vicon", "step_length_r_vicon", "step_width_l_vicon", "step_width_r_vicon", ...
477 "stride_length_l_imu", "stride_length_r_imu", "step_length_l_imu", "step_length_r_imu", ...
478 "step_width_l_imu", "step_width_r_imu", "number"]);
479
480% Preallocate double values with NaN
481combined.stride_length_l_vicon(:) = NaN;
482combined.stride_length_r_vicon(:) = NaN;
483combined.step_length_l_vicon(:) = NaN;
484combined.step_length_r_vicon(:) = NaN;
485combined.step_width_l_vicon(:) = NaN;
486combined.step_width_r_vicon(:) = NaN;
487combined.stride_length_l_imu(:) = NaN;
488combined.stride_length_r_imu(:) = NaN;
489combined.step_length_l_imu(:) = NaN;
490combined.step_length_r_imu(:) = NaN;
491combined.step_width_l_imu(:) = NaN;
492combined.step_width_r_imu(:) = NaN;
493
494for trial = trials
495
496 % make sure that the trial exists
497 if ~isfield(result.gait_parameters, trial)
498 continue
499 end
500
501 % get the first empty row
502 idx = find(ismissing(combined.ptp), 1, "first");
503
504 % save the highest number of samples
505 max_n_samples = 0;
506
507 % vicon
508 % stride length left
509 n_samples = numel(result.gait_parameters.(trial).stride_length.l.vicon);
510 if n_samples > max_n_samples; max_n_samples = n_samples; end
511 combined.stride_length_l_vicon(idx:idx+n_samples-1) = result.gait_parameters.(trial).stride_length.l.vicon;
512
513 % stride length right
514 n_samples = numel(result.gait_parameters.(trial).stride_length.r.vicon);
515 if n_samples > max_n_samples; max_n_samples = n_samples; end
516 combined.stride_length_r_vicon(idx:idx+n_samples-1) = result.gait_parameters.(trial).stride_length.r.vicon;
517
518 % step length left
519 n_samples = numel(result.gait_parameters.(trial).step_length.l.vicon);
520 if n_samples > max_n_samples; max_n_samples = n_samples; end
521 combined.step_length_l_vicon(idx:idx+n_samples-1) = result.gait_parameters.(trial).step_length.l.vicon;
522
523 % step length right
524 n_samples = numel(result.gait_parameters.(trial).step_length.r.vicon);
525 if n_samples > max_n_samples; max_n_samples = n_samples; end
526 combined.step_length_r_vicon(idx:idx+n_samples-1) = result.gait_parameters.(trial).step_length.r.vicon;
527
528 % step width left
529 n_samples = numel(result.gait_parameters.(trial).step_width.l.vicon);
530 if n_samples > max_n_samples; max_n_samples = n_samples; end
531 combined.step_width_l_vicon(idx:idx+n_samples-1) = result.gait_parameters.(trial).step_width.l.vicon;
532
533 % step width right
534 n_samples = numel(result.gait_parameters.(trial).step_width.r.vicon);
535 if n_samples > max_n_samples; max_n_samples = n_samples; end
536 combined.step_width_r_vicon(idx:idx+n_samples-1) = result.gait_parameters.(trial).step_width.r.vicon;
537
538 % imu
539 % stride length left
540 n_samples = numel(result.gait_parameters.(trial).stride_length.l.imu);
541 if n_samples > max_n_samples; max_n_samples = n_samples; end
542 combined.stride_length_l_imu(idx:idx+n_samples-1) = result.gait_parameters.(trial).stride_length.l.imu;
543
544 % stride length right
545 n_samples = numel(result.gait_parameters.(trial).stride_length.r.imu);
546 if n_samples > max_n_samples; max_n_samples = n_samples; end
547 combined.stride_length_r_imu(idx:idx+n_samples-1) = result.gait_parameters.(trial).stride_length.r.imu;
548
549 % step length left
550 n_samples = numel(result.gait_parameters.(trial).step_length.l.imu);
551 if n_samples > max_n_samples; max_n_samples = n_samples; end
552 combined.step_length_l_imu(idx:idx+n_samples-1) = result.gait_parameters.(trial).step_length.l.imu;
553
554 % step length right
555 n_samples = numel(result.gait_parameters.(trial).step_length.r.imu);
556 if n_samples > max_n_samples; max_n_samples = n_samples; end
557 combined.step_length_r_imu(idx:idx+n_samples-1) = result.gait_parameters.(trial).step_length.r.imu;
558
559 % step width left
560 n_samples = numel(result.gait_parameters.(trial).step_width.l.imu);
561 if n_samples > max_n_samples; max_n_samples = n_samples; end
562 combined.step_width_l_imu(idx:idx+n_samples-1) = result.gait_parameters.(trial).step_width.l.imu;
563
564 % step width right
565 n_samples = numel(result.gait_parameters.(trial).step_width.r.imu);
566 if n_samples > max_n_samples; max_n_samples = n_samples; end
567 combined.step_width_r_imu(idx:idx+n_samples-1) = result.gait_parameters.(trial).step_width.r.imu;
568
569 % participant
570 combined.ptp(idx:idx+max_n_samples-1) = categorical(ptp);
571
572 % trial name
573 combined.trial(idx:idx+max_n_samples-1) = categorical(trial);
574
575 % number
576 combined.number(idx:idx+max_n_samples-1) = 1:max_n_samples;
577
578end
579
580% remove the empty rows
581combined = rmmissing(combined, "MinNumMissing", 14);
582
583% save into the result
584result.gait_parameters.combined = combined;
585
586% save the gait parameters
587log_entry("Gait parameters combined over all trials", ptp_num, 4)
588
589% save the processed data
590save(fullfile(basepath, ptp, "processed_data.mat"), "result")
591
592%% create a bland altman plot for the gait parameters
593
594if options.create_plots
595
596 % create the folder if it does not exist
597 if ~isfolder(fullfile(basepath, ptp, "gait_parameters"))
598 mkdir(fullfile(basepath, ptp, "gait_parameters"))
599 end
600
601 % stride length
603 result.gait_parameters.combined.stride_length_l_vicon, ...
604 result.gait_parameters.combined.stride_length_l_imu, ...
605 result.gait_parameters.combined.number, ...
606 fullfile(basepath, ptp, "gait_parameters", "stride_length_l.png"), ...
607 title = "Stride Length Left of " + ptp);
609 result.gait_parameters.combined.stride_length_r_vicon, ...
610 result.gait_parameters.combined.stride_length_r_imu, ...
611 result.gait_parameters.combined.number, ...
612 fullfile(basepath, ptp, "gait_parameters", "stride_length_r.png"), ...
613 title = "Stride Length Right of " + ptp);
614
615 % step length
617 result.gait_parameters.combined.step_length_l_vicon, ...
618 result.gait_parameters.combined.step_length_l_imu, ...
619 result.gait_parameters.combined.number, ...
620 fullfile(basepath, ptp, "gait_parameters", "step_length_l.png"), ...
621 title = "Step Length Left of " + ptp);
623 result.gait_parameters.combined.step_length_r_vicon, ...
624 result.gait_parameters.combined.step_length_r_imu, ...
625 result.gait_parameters.combined.number, ...
626 fullfile(basepath, ptp, "gait_parameters", "step_length_r.png"), ...
627 title = "Step Length Right of " + ptp);
628
629 % step width
631 result.gait_parameters.combined.step_width_l_vicon, ...
632 result.gait_parameters.combined.step_width_l_imu, ...
633 result.gait_parameters.combined.number, ...
634 fullfile(basepath, ptp, "gait_parameters", "step_width_l.png"), ...
635 title = "Step Width Left of " + ptp);
637 result.gait_parameters.combined.step_width_r_vicon, ...
638 result.gait_parameters.combined.step_width_r_imu, ...
639 result.gait_parameters.combined.number, ...
640 fullfile(basepath, ptp, "gait_parameters", "step_width_r.png"), ...
641 title = "Step Width Right of " + ptp);
642
643 % log
644 log_entry("Bland Altman plots created", ptp_num, 4)
645
646end
647
648% set the status to completed
649proc_info.gait_parameters_calculated = true;
650proc_info.gait_parameters_calculated_time = datetime("now", "Format", "yyyy-MM-dd HH:mm:ss");
651writestruct(proc_info, fullfile(basepath, ptp, "proc_info.json"))
652
653end
function axis2num(in axis)
convert an axis to a number
function calculate_gait_parameters(in basepath, in ptp, in options)
calculates gait parameters from the processed data
function comparison_plot(in gt_vec, in meas_vec, in i_step, in filename, in options)
create a comparison plot
function direction2num(in dir)
convert a direction to a number
function log_entry(in msg, in ptp_num, in level, in trial)
log entry to a log file