1% =========================================================================== %
2%> @brief
final analysis script
for the SonEMS data
4%>
this script filters the data of the feet and compares certain gait
5%> parameters with the VICON data. the data is from the SonEMS experiments.
9%> @note
this script is written to use the parallel processing toolbox. it
10%> works only with the
"Processes" pool. The script is very memory intensive,
11%> therefore the number of workers should be set accordingly.
13%> @copyright see the file @ref LICENSE in the root directory of the repository
14% =========================================================================== %
20pause(2); % give the system some time to close the figures
23%> here the tasks that need to be done again can be marked.
if a task is not done
24%> yet OR is marked as
true, it will be executed.
if a task is marked as
false,
25%> it will be skipped (
if it has be done before).
27%% base folder, participant list and ekf tuning parameters
28basepath =
"D:/ETH_Data/";
29info_sheet = readtable(fullfile(basepath,
"participant_info.xlsx"), ...
30 FileType =
"spreadsheet", ...
31 TextType =
"string", ...
32 DatetimeType =
"datetime", ...
33 VariableNamingRule =
"modify", ...
34 ReadVariableNames =
true, ...
38current_run =
"Test"; %
"Tuning" or
"Test"
39ptps = unique(info_sheet.ID(info_sheet.(current_run) == 1));
41% ekf tuning parameters file
42if strcmp(current_run,
"Test")
43 tuning_files = "C:\Users\moss\Code\MT_moss_IMU_sensor_fusion\experiments\ekf_tuning.json"; % Test
44elseif strcmp(current_run, "Tuning")
45 tuning_files = dir(fullfile(basepath, "Tuning", "Trial_14", "*.json"));
46 tuning_files =
string(fullfile({tuning_files.folder}, {tuning_files.name}))
';
47 tuning_files = "C:\Users\moss\Code\MT_moss_IMU_sensor_fusion\experiments\ekf_tuning.json"; % Test
49 error("Unknown Dataset.")
53diary(fullfile(basepath, "exp_SonEMS_3.log"))
54fprintf("\n\nThis is the beginning of a new log file.\n\nThe column headers are:\n")
55fprintf("Time; Log Level; Participant; Message\n\n")
57log_entry("Start processing SonEMS data")
58log_entry("Running " + current_run + " set", 0, 4)
60% Select the processes pool to use (not the thread pool)
61num_workers = 16; % change this value, if you want to use more or less workers
62num_workers = min(num_workers, numel(ptps)); % limit the number of workers to the number of participants
63currentPool = gcp("nocreate");
64if ~isempty(currentPool)
65 if ~strcmp(class(currentPool), "parallel.ProcessPool")
67 parpool("Processes", num_workers);
68 log_entry("Changed the pool to Processes", 0, 5)
69 elseif currentPool.NumWorkers ~= num_workers
71 parpool("Processes", num_workers);
72 log_entry("Changed the pool to Processes with " + num_workers + " workers", 0, 5)
74 log_entry("Using the existing pool of Processes", 0, 5)
77 parpool("Processes", num_workers);
78 log_entry("Created a new pool of Processes", 0, 5)
81% define a vartiabel to store the time of the last log entry of the RAM usage
82ram_last_logged = datetime(1970, 1, 1); % preinitialize to a long time ago
84% loop over the tuning files
85wb = waitbar(0, "Processing participants", "Name", "Processing participants");
87% allocate the grid search results table
88grid_search_results = table('Size
', [numel(tuning_files), 20], ...
89 'VariableTypes
', ["string", "datetime", repmat("double", 1, 18)], ...
94 "StrideLengthDiffMu", ...
96 "StepLengthDiffMu", ...
98 "StepWidthDiffMu", ...
99 "StrideLengthStd", ...
100 "StrideLengthDiffStd", ...
102 "StepLengthDiffStd", ...
104 "StepWidthDiffStd", ...
105 "StrideLengthMSE", ...
106 "StrideLengthDiffMSE", ...
108 "StepLengthDiffMSE", ...
113for i_tf = 1:numel(tuning_files)
115 % get the tuning file
116 tuning_file = tuning_files(i_tf);
118 % save the tuning file
119 grid_search_results.TuningFile(i_tf) = tuning_file;
121 if numel(tuning_files) > 1
122 log_entry("Start processing with tuning file " + tuning_file, 0, 4)
125 % preallocate the futures
126 f_filter(1:numel(ptps)) = parallel.FevalFuture;
127 f_gait_parameters(1:numel(ptps)) = parallel.FevalFuture;
129 % queue all participants for filter processing
130 for ptp = 1:numel(ptps)
132 f_filter(ptp) = parfeval(...
133 @process_participant, ... % function to execute
134 0, ... % number of output arguments
137 ekf_tuning_file = tuning_file, ...
139 FIND_WALKING_AXES = false, ...
140 FIND_WALKING_SPEED = false, ...
141 ACCUMULATE_DIST = false, ...
142 ALIGN_IMU_VICON = false, ...
146 log_entry("Participant " + ptps(ptp) + " queued")
150 % wait for all futures to finish, show intermediate results in waitbar
151 filter_finished = false;
153 while ~filter_finished
154 finished = sum(string({f_filter.State}) == "finished");
155 queued = sum(string({f_filter.State}) == "queued");
156 running = sum(string({f_filter.State}) == "running");
157 failed = sum(string({f_filter.State}) == "failed") + error_occured;
159 waitbar(finished / numel(f_filter), wb, sprintf("Filtering (%i/%i) ...\nFinished: %d, Queued: %d, Running: %d, Failed: %d", i_tf, numel(tuning_files), finished, queued, running, failed))
161 if finished + failed >= numel(f_filter)
162 filter_finished = true;
165 % log RAM usage every 1 minute
166 if seconds(datetime("now") - ram_last_logged) > 60
167 [~, curr_ram] = memory;
169 log_entry(sprintf("RAM: %.1f GB of %.1f GB remaining.", curr_ram.PhysicalMemory.Available / 1e9, curr_ram.PhysicalMemory.Total / 1e9), 0, 5)
171 ram_last_logged = datetime("now");
176 % check the state of the futures
177 for ptp = 1:numel(f_filter)
179 % check if future is already read
180 if f_filter(ptp).Read == 1
185 if ~isempty(f_filter(ptp).Error)
187 % get the SonEMS String
188 err_ptp = f_filter(ptp).InputArguments{2};
194 log_entry("Unhandled error in participant " + err_ptp, 0, 1)
195 log_entry(f_filter(ptp).Error.message, 0, 2)
196 log_entry("Stack trace:", 0, 2)
197 for i = 1:numel(f_filter(ptp).Error.stack)
198 log_entry(f_filter(ptp).Error.stack(i).name + " (Line: " + string(f_filter(ptp).Error.stack(i).line) + ")", 0, 2)
201 % read the future (this will throw an exeption but will mark it as read)
203 fetchOutputs(f_filter(ptp));
208 error_occured = error_occured + 1;
213 % check if the future is finished
214 if strcmp(f_filter(ptp).State, "finished")
216 % read the future (this can throw an exeption but will mark it as read)
218 fetchOutputs(f_filter(ptp));
223 % log the finished participant
224 log_entry("Participant " + ptps(ptp) + " finished with filter processing", 0, 4)
226 % start the future for the gait parameters
227 f_gait_parameters(ptp) = parfeval(...
228 @calculate_gait_parameters, ... % function to execute
229 0, ... % number of output arguments
232 RECALCULATE = true, ...
233 create_plots = true);
235 % log the queued participant
236 log_entry("Participant " + ptps(ptp) + " queued for gait parameter calculation", 0, 4)
242 log_entry("All participants processed (filtered)")
244 % wait for all gait parameters to finish, show intermediate results in waitbar
245 gait_parameters_finished = false;
246 while ~gait_parameters_finished
247 finished = sum(string({f_gait_parameters.State}) == "finished");
248 queued = sum(string({f_gait_parameters.State}) == "queued");
249 running = sum(string({f_gait_parameters.State}) == "running");
250 failed = sum(string({f_gait_parameters.State}) == "failed");
251 unavailable = sum(string({f_gait_parameters.State}) == "unavailable");
253 waitbar(finished / numel(f_filter), wb, sprintf("Gait Parameters (%i/%i) ...\nFinished: %d, Queued: %d, Running: %d, Failed: %d", i_tf, numel(tuning_files), finished, queued, running, failed))
255 if finished + failed + unavailable >= numel(f_gait_parameters)
256 gait_parameters_finished = true;
261 % check the state of the futures
262 for ptp = 1:numel(f_gait_parameters)
264 % check if future is already read
265 if f_gait_parameters(ptp).Read == 1
269 % check if the future is finished
270 if strcmp(f_gait_parameters(ptp).State, "finished")
272 % read the future (this can throw an exeption but will mark it as read)
274 fetchOutputs(f_gait_parameters(ptp));
279 % log the finished participant
280 log_entry("Participant " + ptps(ptp) + " finished with gait parameter calculation")
285 if ~isempty(f_gait_parameters(ptp).Error)
287 % get the SonEMS String
288 err_ptp = f_gait_parameters(ptp).InputArguments{2};
291 f_gait_parameters(ptp).cancel
294 log_entry("Unhandled eror in participant " + err_ptp, 0, 1)
295 log_entry(f_gait_parameters(ptp).Error.message, 0, 2)
296 log_entry("Stack trace:", 0, 2)
297 for i = 1:numel(f_gait_parameters(ptp).Error.stack)
298 log_entry(f_gait_parameters(ptp).Error.stack(i).name + " (Line: " + string(f_gait_parameters(ptp).Error.stack(i).line) + ")", 0, 2)
301 % read the future (this will throw an exeption but will mark it as read)
303 fetchOutputs(f_gait_parameters(ptp));
313 %% create a summary of the gait parameters
314 log_entry("Start creating gait parameter summary")
315 results = create_gait_parameter_summary(basepath, ptps, tuning_file, current_run);
318 grid_search_results.time(i_tf) = datetime("now");
319 grid_search_results.StrideLengthMu(i_tf) = results.mu_stride;
320 grid_search_results.StepLengthMu(i_tf) = results.mu_step;
321 grid_search_results.StepWidthMu(i_tf) = results.mu_width;
322 grid_search_results.StrideLengthDiffMu(i_tf) = results.mu_stride_d;
323 grid_search_results.StepLengthDiffMu(i_tf) = results.mu_step_d;
324 grid_search_results.StepWidthDiffMu(i_tf) = results.mu_width_d;
325 grid_search_results.StrideLengthStd(i_tf) = results.std_stride;
326 grid_search_results.StepLengthStd(i_tf) = results.std_step;
327 grid_search_results.StepWidthStd(i_tf) = results.std_width;
328 grid_search_results.StrideLengthDiffStd(i_tf) = results.std_stride_d;
329 grid_search_results.StepLengthDiffStd(i_tf) = results.std_step_d;
330 grid_search_results.StepWidthDiffStd(i_tf) = results.std_width_d;
331 grid_search_results.StrideLengthMSE(i_tf) = results.mse_stride;
332 grid_search_results.StepLengthMSE(i_tf) = results.mse_step;
333 grid_search_results.StepWidthMSE(i_tf) = results.mse_width;
334 grid_search_results.StrideLengthDiffMSE(i_tf) = results.mse_stride_d;
335 grid_search_results.StepLengthDiffMSE(i_tf) = results.mse_step_d;
336 grid_search_results.StepWidthDiffMSE(i_tf) = results.mse_width_d;
338 if current_run == "Tuning"
339 save(fullfile(basepath, "Tuning", "grid_search_results.mat"), "grid_search_results");
340 copyfile(fullfile(basepath, "Tuning", "grid_search_results.mat"), fullfile(basepath, "Tuning", string(datetime("now", "Format", "yy-MM-dd_HH-mm-ss")) + "_grid_search_results.mat"));
343 % log the end of the processing
344 log_entry("All participants processed (gait parameters calculated)")