1% =========================================================================== %
2%> @brief calculates gait parameters from the processed data
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:
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
19%> @copyright see the file @ref LICENSE in the root directory of the repository
20% =========================================================================== %
26 options.RECALCULATE logical = true
27 options.create_plots logical = false
31RECALCULATE = options.RECALCULATE;
34diary(fullfile(basepath, ptp, ptp +
".log"))
36% get the participant number
37ptp_num = regexp(ptp,
"SonE_(\d+)|Pilot_(\d+)",
"tokens");
38ptp_num = str2double(ptp_num{1});
40% log the start of the processing
41log_entry(
"Start calculating gait parameters", ptp_num)
43%% load the process info
44filtering_done =
false;
47 proc_info = readstruct(fullfile(basepath, ptp,
"proc_info.json"));
49 if isfield(proc_info,
"filtering_done")
51 if proc_info.filtering_done
52 filtering_done = proc_info.filtering_done;
55 log_entry("Filtering is not done yet. I'll wait for a bit.", ptp_num, 5)
57 num_waited = num_waited + 1;
60 log_entry("Something seems to be wrong. I'll stop waiting.", ptp_num, 2)
62 error("I'm stuck in a waiting loop...")
64 pause(5) % wait for 5 seconds
68 error("The filtering done field is missing");
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)
82% load the processed data
84 load(fullfile(basepath, ptp, "temp", "processed_data.mat"), "result");
86 log_entry("The processed data could not be loaded", 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)
94 error("The processed data could not be loaded")
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.
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.
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.
111% loop over all trials
112trials =
string(fieldnames(result.vicon))';
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;
121 % make sure that the first HS is before the first TO
122 while HS_l_idx(1) > TO_l_idx(1)
125 log_entry("Removed first left TO because it was before the first HS", ptp_num, 5)
127 while HS_r_idx(1) > TO_r_idx(1)
130 log_entry("Removed first right TO because it was before the first HS", ptp_num, 5)
133 % make sure that the last TO is after the last HS
134 while TO_l_idx(end) < HS_l_idx(end)
137 log_entry("Removed last left HS because it was after the last TO", ptp_num, 5)
139 while TO_r_idx(end) < HS_r_idx(end)
142 log_entry("Removed last right HS because it was after the last TO", ptp_num, 5)
145 % make sure that the number of events is the same
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")
154 for i = 1:numel(ME.stack)
155 log_entry(ME.stack(i).name + " (Line: " +
string(ME.stack(i).line) + ")", ptp_num, 2)
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);
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);
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)
176log_entry("Gait events redefined", ptp_num, 4)
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
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
192 log_entry("The walking axes are not defined", 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)
200 error("The walking axes are not defined")
204% calculate the gait parameters
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;
214% loop over all trials
217 % get the gait events
219 MS_l_idx = result.gait_events.(trial).MS_l;
220 MS_r_idx = result.gait_events.(trial).MS_r;
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)
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);
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";
238 assert(all(MS_test == MS_r_idx), "The gait events are not alternating left and right")
239 first_stance = "right";
245 for i = 1:numel(ME.stack)
246 log_entry(ME.stack(i).name + " (Line: " +
string(ME.stack(i).line) + ")", ptp_num, 2)
249 error("The gait events are not alternating left and right")
253 if MS_l_idx(end) > MS_r_idx(end)
254 last_stance = "left";
256 last_stance = "right";
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
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.
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.
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;
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;
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)), ...
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;
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
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));
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;
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);
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
319 if first_stance == "left"
326 num_steps_l = min(numel(MS_l_idx(idx_start_l:end)), numel(MS_r_idx(idx_start_r:end)));
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);
333 step_length_vicon_l = (positions_vicon_l - positions_vicon_r) * forwards;
334 step_length_imu_l = positions_imu_l - positions_imu_r;
337 if first_stance == "right"
344 num_steps_r = min(numel(MS_l_idx(idx_start_l:end)), numel(MS_r_idx(idx_start_r:end)));
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);
351 step_length_vicon_r = (positions_vicon_r - positions_vicon_l) * forwards;
352 step_length_imu_r = positions_imu_r - positions_imu_l;
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;
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);
363 % the step width is defined as the lateral distance between the heels at
364 % two consecutive midstance events.
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);
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);
374 % calculate the step width
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")
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;
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);
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")
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;
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));
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")
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;
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));
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")
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;
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);
429 error("The first and last stance are not defined")
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);
440 for i = 1:numel(ME.stack)
441 log_entry(ME.stack(i).name + " (Line: " +
string(ME.stack(i).line) + ")", ptp_num, 2)
448 proc_info.trials.(trial).gait_parameters_calculated = true;
450 log_entry("Gait parameters calculated", ptp_num, 4, trial)
454%% combine the gait parameters over all trials
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"]);
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;
496 % make sure that the trial exists
497 if ~isfield(result.gait_parameters, trial)
501 % get the first empty row
502 idx = find(ismissing(combined.ptp), 1, "first");
504 % save the highest number of samples
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
570 combined.ptp(idx:idx+max_n_samples-1) = categorical(ptp);
573 combined.trial(idx:idx+max_n_samples-1) = categorical(trial);
576 combined.number(idx:idx+max_n_samples-1) = 1:max_n_samples;
580% remove the empty rows
581combined = rmmissing(combined, "MinNumMissing", 14);
583% save into the result
584result.gait_parameters.combined = combined;
586% save the gait parameters
587log_entry("Gait parameters combined over all trials", ptp_num, 4)
589% save the processed data
590save(fullfile(basepath, ptp, "processed_data.mat"), "result")
592%% create a bland altman plot for the gait parameters
594if options.create_plots
596 % create the folder if it does not exist
597 if ~isfolder(fullfile(basepath, ptp, "gait_parameters"))
598 mkdir(fullfile(basepath, ptp, "gait_parameters"))
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);
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);
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);
644 log_entry("Bland Altman plots created", ptp_num, 4)
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"))
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