OSCR

A portable solution for simultaneous human movement and mobile EEG acquisition: readiness potential for basketball free-throw shooting.

Code ↔ Paper

19 matches between paragraphs of the paper and lines of its authors' code, computed by the harvester (lexical-v1). Click a colored paragraph or line to see its counterpart.

The 19 matches
  1. [1] § Materials and methods › EEG preprocessing ↔ scripts/A07_EEG_preprocessing.m, lines 52–159 · score 0.99 · pop_icflag, pop_iclabel, pop_viewprops, pop_interp, pop_reref, channel noise
  2. [2] § Materials and methods › EEG preprocessing ↔ scripts/A08_EEG_analysis_RP.m, lines 43–138 · score 0.93 · pop_interp, pop_reref, pop eegfiltnew, Bad components, baseline corrected, pre movement
  3. [3] § Materials and methods › Onset detection ↔ scripts/A03_ACC_sensor_rev_PRE.m, lines 314–397 · score 0.82 · reverse computation, artificial onset, baseline period, standard deviation, backwards, latency
  4. [4] § Materials and methods › Onset detection ↔ scripts/A04_ACC_sensor_rev_POST.m, lines 348–367 · score 0.80 · reverse computation, artificial onset, baseline period, standard deviation, latency, algorithm
  5. [5] § Results › Relationship between human pose and performance ↔ scripts/A17_MP_features.m, lines 31–139 · score 0.79 · left shoulder, right elbow, left pinky, right shoulder, right pinky, right wrist
  6. [6] § Results › Relationship between human pose and performance ↔ scripts/B01_MP_visualization.m, lines 149–246 · score 0.79 · left shoulder, right elbow, left pinky, right shoulder, right pinky, right wrist
  7. [7] § Results › Movement onset validation ↔ scripts/A26_RP_onset_imageplot_participant.m, lines 128–175 · score 0.78 · left thumb, right thumb, left pinky, right pinky, left wrist, right wrist
  8. [8] § Results › Movement onset validation ↔ scripts/A27_RP_onset_imageplot_grand_avg.m, lines 184–257 · score 0.78 · left thumb, right thumb, left pinky, right pinky, left wrist, right wrist
  9. [9] § Materials and methods › EEG preprocessing ↔ scripts/A07_EEG_preprocessing.m, lines 52–159 · score 0.77 · pop_jointprob, pop eegfiltnew, flagged, rejected, ICA, component
  10. [10] § Materials and methods › EEG preprocessing ↔ scripts/A06_EEG_preparation.m, lines 58–181 · score 0.77 · pop_jointprob, pop eegfiltnew, attenuation, ICA, filtered, root
  11. [11] § Materials and methods › Participants ↔ scripts/A32_Basketball_profile.m, lines 29–146 · score 0.65 · basketball profile, ages, week, clubs, contacts, activity
  12. [12] § Results › Movement onset validation ↔ scripts/A27_RP_onset_imageplot_grand_avg.m, lines 958–1080 · score 0.61 · RMS acceleration, acceleration magnitude, body landmarks, Body parts, stars, binomial
  13. [13] § Results › Presence of the RP ↔ scripts/A15_Topo_ttest_RP_avg.m, lines 245–360 · score 0.59 · Cz RP, channel Cz, FDR corrected, SE, Wilcoxon, 100 ms
  14. [14] § Materials and methods › Statistical analysis › Relationship between human pose and performance ↔ scripts/A19_Biserial_MP.m, lines 44–92 · score 0.57 · correlation coefficients, explained variance, vector, squaring, landmarks, PLD
  15. [15] § Materials and methods › Statistical analysis › Relationship between RP amplitudes and performance ↔ scripts/A18_Biserial_RP.m, lines 45–89 · score 0.56 · correlation coefficients, explained variance, vector, squaring, bin, RP
  16. [16] § Materials and methods › Statistical analysis › Relationship between RP amplitudes and performance ↔ scripts/A19_Biserial_MP.m, lines 95–164 · score 0.55 · correlation coefficients, explained variance, R2, squaring, amplitudes, hits
  17. [17] § Materials and methods › Materials ↔ scripts/B01_MP_visualization.m, lines 149–246 · score 0.55 · Pose Landmark Detection, MediaPipe, customized, timestamp, PLD, body
  18. [18] § Materials and methods › EEG data analysis ↔ scripts/A35_Interpolation_demo.m, lines 19–132 · score 0.53 · load_xdf, EEG timestamps, PLD
  19. [19] § Materials and methods › Statistical analysis › Movement onset validation ↔ scripts/A27_RP_onset_imageplot_grand_avg.m, lines 958–1080 · score 0.53 · acceleration magnitude, body landmarks, body part, Binomial, validation, onset

Paper

Loaded from Europe PMC by your browser, not stored by OSCR: doi.org · Europe PMC

The paper is loaded when this pane is shown.

The authors' code

MATLAB · 1,084 lines · 46 KB · MIT · 3 matches

  1. clc, clear, close all;
  2. %% Movement onset comparison (grand average)
  3. % This code compares the time reference (eye-wrist intersection) and the
  4. % grand average detected onset of the movement across participants.
  5. % Furthermore, a binomial test is used to determine whether the proportion
  6. % of participants showing significant movement in each body part was
  7. % significantly above chance level.
  8. % Miguel Contreras-Altamirano, 2025
  9. %% EEG data preparation
  10. mainpath = 'L:\Downloads\eeglab2023.0\'; % eeglab folder
  11. path = 'L:\Downloads\basketball_RP\NCP_Basketball\MediaPipe\';
  12. outpath = 'L:\Downloads\basketball_RP\Oldenburg_University\Thesis\data_hoops\';
  13. files = dir( fullfile( path,'\*.xdf')); % listing data sets
  14. % Add EEGLAB properly
  15. if exist('eeglab','file') ~= 2
  16. addpath(mainpath);
  17. end
  18. num_conditions = 3; % (Conditions and overall: 1=hit 2=miss 3=all)
  19. % Permutation parameters
  20. bin_size = 500; % Bin size in milliseconds
  21. num_permutations = 5000; % Number of permutations
  22. %% Loading data
  23. for cond=3 : num_conditions
  24. % Loading desired data
  25. if cond == 1 % 'hit'
  26. load([outpath , 'PLD_grand_avg_hit.mat'], 'averageTimeseriesMp', 'timestamps_mp');
  27. load([outpath , 'ACC_grand_avg_rev_hit','.mat']); % Loading accelerometer data
  28. % Import EEG processed data
  29. [ALLEEG EEG CURRENTSET ALLCOM] = eeglab; % Open eeglab
  30. EEG = pop_loadset('filename',['Grand_avg_hits','.set'],'filepath', outpath); % Loading set file
  31. elseif cond == 2 % 'miss'
  32. load([outpath , 'PLD_grand_avg_miss.mat'], 'averageTimeseriesMp', 'timestamps_mp');
  33. load([outpath , 'ACC_grand_avg_rev_miss','.mat']); % Loading accelerometer data
  34. % Import EEG processed data
  35. [ALLEEG EEG CURRENTSET ALLCOM] = eeglab; % Open eeglab
  36. EEG = pop_loadset('filename',['Grand_avg_misses','.set'],'filepath', outpath); % Loading set file
  37. eeglab redraw
  38. elseif cond == 3 % % 'none'
  39. load([outpath , 'PLD_grand_avg.mat'], 'averageTimeseriesMp', 'timestamps_mp');
  40. load([outpath , 'ACC_grand_avg_rev','.mat']); % Loading accelerometer data
  41. % Import EEG processed data
  42. [ALLEEG EEG CURRENTSET ALLCOM] = eeglab; % Open eeglab
  43. EEG = pop_loadset('filename',['Grand_avg_all','.set'],'filepath', outpath); % Loading set file
  44. eeglab redraw
  45. end
  46. %timeseries_mp = averageTimeseriesMp;
  47. frequency_sampling = input('At which frequency would you like the data? (1=15Hz / 2=250Hz): ');
  48. %% Reampling PLD to its originally 15 Hz
  49. if frequency_sampling == 1
  50. % Resampling instead
  51. %[OUTEEG] = pop_resample( INEEG, freq, fc, df);
  52. % Define original and target sampling rates
  53. original_sampling_rate = EEG.srate; % EEG sampling rate (e.g., 250Hz)
  54. target_sampling_rate = 15; % Target PLD sampling rate %sampling_rate_mp
  55. % Calculate downsampling factor
  56. downsample_factor = round(original_sampling_rate / target_sampling_rate);
  57. % Downsample the time series data
  58. averageTimeseriesMp = averageTimeseriesMp(:, 1:downsample_factor:end);
  59. timeseries_mp = averageTimeseriesMp;
  60. elseif frequency_sampling == 2
  61. timeseries_mp = averageTimeseriesMp;
  62. end
  63. %% ERP Onset with Gaussian
  64. % Assuming you have EEG data in an EEGLAB structure
  65. ERP_bin = mean (EEG.data, 3);
  66. chan = find(strcmp({EEG.chanlocs.labels}, 'Cz'));
  67. timeEEG = [find(EEG.times == -200)]; % for topoplot
  68. % Step 1: Extract the data for the channel of interest
  69. latency = EEG.times;
  70. erp_data = squeeze(EEG.data(chan, :, :)); % Squeeze the data to remove singleton dimensions and transpose
  71. sortvar = zeros(1, size(erp_data, 2)); % One value per trial (876 trials in this case)
  72. % Step 3: Use erpimage
  73. figure('units', 'normalized', 'outerposition', [0 0 1 1]);
  74. %subplot(2, 1, 1);
  75. erpimage(erp_data, [], EEG.times, 'Grand Average ERP at Cz', 1, 1,...
  76. 'yerplabel', {'Grand Average Motion Tracking and ERP [N=26]'},'erp', 'off', 'cbar', 'off','cbar_title', '\muV',...
  77. 'vert', [grand_avgOnsetTime_rev], ...
  78. 'topo', {chan, EEG.chanlocs, EEG.chaninfo},...
  79. 'caxis', [-20 20],...
  80. 'vert', grand_avgOnsetTime_rev,... % 'avg_type', 'Gaussian',...
  81. 'img_trialax_label', {'Participants'}, 'img_trialax_ticks', [0 : 1 :size(erp_data,2)]);
  82. %% ERP Onset with Moving Average
  83. % Step 1: Define parameters
  84. clipping_range = [-20 20]; % Match the caxis range in erpimage
  85. % % Step 2: Apply smoothing
  86. % smooth_window = 20; % Adjust for slight smoothing if needed
  87. % smoothed_data = movmean(erp_data, smooth_window, 2); % Smooth across time
  88. smoothed_data = erp_data;
  89. % Step 3: Plot with imagesc
  90. figure('units', 'normalized', 'outerposition', [0 0 1 1]);
  91. %subtightplot(2, 1, 2);
  92. imagesc(latency, 1:size(smoothed_data, 2), smoothed_data'); % Transpose for correct orientation
  93. set(gca, 'YDir', 'normal'); % Ensure correct orientation
  94. % Step 4: Colormap and axis adjustments
  95. colormap('jet'); % Similar to 'erpimage' colormap
  96. c = colorbar('Ticks',[-20 -10 0 10 20]); % Replace minValue and maxValue with your actual min and max
  97. c.Label.String = 'Amplitude [\muV]';
  98. c.Label.FontSize = 18;
  99. caxis(clipping_range); % Set color limits to match erpimage
  100. % Step 5: Add vertical lines and labels
  101. hold on;
  102. xline(0, 'r--', 'LineWidth', 2.5); % Onset line
  103. xline(grand_avgOnsetTime_rev, 'k:', 'LineWidth', 2.5); % Additional onset lines if needed
  104. xlabel('Time [ms]', 'FontSize', 18);
  105. ylabel('Participants', 'FontSize', 18);
  106. %title('Grand Average Motion Tracking and ERP [N=26]', 'FontSize', 20);
  107. %subtitle('Event Related Potential at [Cz]', 'FontSize', 19);
  108. % Optional: Adjust Y-axis tick labels
  109. yticks(1:size(smoothed_data, 2));
  110. yticklabels(1:size(smoothed_data, 2)); % Adjust as per participant indexing
  111. % % Step 4: Add the topoplot outside the matrix plot (above)
  112. % topoplot_axes = axes('Position', [0.56, 0.79, 0.1, 0.1]); % Adjust to position above the main plot
  113. % timeEEG = -200; % Choose the time point for topoplot (adjust as necessary)
  114. % topoplot(ERP_bin(:, find(latency == timeEEG)), EEG.chanlocs, 'maplimits', clipping_range, 'electrodes', 'off');
  115. % title([num2str(timeEEG), ' ms'], 'FontSize', 11);
  116. % set(topoplot_axes, 'Color', 'none'); % Optional: Make the topoplot background transparent
  117. %
  118. % % Step 4: Add the topoplot outside the matrix plot (above)
  119. % topoplot_axes = axes('Position', [0.50, 0.79, 0.1, 0.1]); % Adjust to position above the main plot
  120. % timeEEG = -500; % Choose the time point for topoplot (adjust as necessary)
  121. % topoplot(ERP_bin(:, find(latency == timeEEG)), EEG.chanlocs, 'maplimits', clipping_range, 'electrodes', 'off');
  122. % title([num2str(timeEEG), ' ms'], 'FontSize', 11);
  123. % set(topoplot_axes, 'Color', 'none'); % Optional: Make the topoplot background transparent
  124. % Save the Plot
  125. % saveas(gcf, [outpath, '\\group_analysis\\','ERP_onset_mov_grand_avg', '.jpg']); % Save the figure as a PNG image
  126. save_fig(gcf,[outpath, '\\group_analysis\\',], 'ERP_onset_mov_grand_avg', 'fontsize', 12);
  127. %% Calculate Acceleration Magnitude for Each Body Part
  128. % Assuming `averageTimeseriesMp` has dimensions [body_parts * 3 (xyz) * time]
  129. from = -2.5; % Start time in seconds
  130. to = 1; % End time in seconds
  131. % Reshape and initialize variables
  132. num_body_parts = 33;
  133. num_frames = size(averageTimeseriesMp, 2);
  134. acceleration_magnitude = zeros(num_body_parts, num_frames);
  135. % Calculate the acceleration magnitude for each body part
  136. for i = 1:num_body_parts
  137. % Extract x, y, and z rows for the current body part
  138. x = averageTimeseriesMp((i-1)*3 + 1, :);
  139. y = averageTimeseriesMp((i-1)*3 + 2, :);
  140. z = averageTimeseriesMp((i-1)*3 + 3, :); % --> Taking out z axis
  141. % Calculate magnitude
  142. acceleration_magnitude(i, :) = sqrt(x.^2 + y.^2 + z.^2); %
  143. end
  144. if frequency_sampling == 1
  145. % RMS over time for acceleration magnitude
  146. sec = 0.264; % Time window length in seconds (0.264 = 4 samples per window at 15Hz)
  147. LeWin = target_sampling_rate * sec; % Define window length in samples
  148. idx_loop = 1:max(1, LeWin):size(acceleration_magnitude, 2); % Ensure valid step size
  149. rms_acceleration_t = zeros(size(acceleration_magnitude, 1), length(idx_loop)); % Pre-allocate matrix
  150. % Loop through and calculate RMS of acceleration magnitude in each time window
  151. row_count = 1; % Initialize counter for rows
  152. for idx = 1:LeWin:size(acceleration_magnitude, 2) - LeWin
  153. signal = acceleration_magnitude(:, idx:idx + (LeWin - 1)); % Segment of acceleration magnitude
  154. rms_acceleration_t(:, row_count) = std(signal, [], 2); % Calculate RMS (standard deviation)
  155. row_count = row_count + 1;
  156. end
  157. elseif frequency_sampling == 2
  158. % RMS over time for acceleration magnitude
  159. sec = 0.01; % Time window length in seconds (0.01 = 2 samples per window at 250Hz)
  160. LeWin = EEG.srate * sec; % Define window length in samples
  161. idx_loop = 1:LeWin:size(acceleration_magnitude, 2); % Index for loop to go through windows
  162. rms_acceleration_t = zeros(size(acceleration_magnitude, 1), length(idx_loop)); % Pre-allocate matrix
  163. % Loop through and calculate RMS of acceleration magnitude in each time window
  164. row_count = 1; % Initialize counter for rows
  165. for idx = 1:LeWin:size(acceleration_magnitude, 2) - LeWin
  166. signal = acceleration_magnitude(:, idx:idx + (LeWin - 1)); % Segment of acceleration magnitude
  167. rms_acceleration_t(:, row_count) = std(signal, [], 2); % Calculate RMS (standard deviation)
  168. row_count = row_count + 1;
  169. end
  170. end
  171. % Multiply RMS values by 1000 to convert units if necessary
  172. rms_acceleration_t = rms_acceleration_t * 1000;
  173. % Define body part labels
  174. body_parts = {
  175. 'nose', 'left eye (inner)', 'left eye', 'left eye (outer)', 'right eye (inner)', ...
  176. 'right eye', 'right eye (outer)', 'left ear', 'right ear', 'mouth (left)', ...
  177. 'mouth (right)', 'left shoulder', 'right shoulder', 'left elbow', 'right elbow', ...
  178. 'left wrist', 'right wrist', 'left pinky', 'right pinky', 'left index', 'right index', ...
  179. 'left thumb', 'right thumb', 'left hip', 'right hip', 'left knee', 'right knee', ...
  180. 'left ankle', 'right ankle', 'left heel', 'right heel', 'left foot index', 'right foot index'
  181. };
  182. %%
  183. % %% Pose Landmarks Artifacts - 3D Surface Plot with Adjusted Labels for Y Coordinates
  184. %
  185. % % RMS measures overall variability, which is helpful for identifying general movement artifacts.
  186. % time_axis = linspace(from * 1000, to * 1000, length(idx_loop)); % in milliseconds
  187. %
  188. % % We will only label every third row (corresponding to the y-coordinates)
  189. % label_indices = 0:1:length(body_parts); % Indices for the y-coordinates (every third row)
  190. %
  191. % % Define time points for onset lines
  192. % onset_times = [0, grand_avgOnsetTime_rev]; % Replace with your actual onset times
  193. %
  194. % % Plot the 3D surface with correctly aligned Y-axis labels
  195. % figure('units', 'normalized', 'outerposition', [0 0 1 1]);
  196. % [X, Y] = meshgrid(time_axis, 1:length(body_parts)); % Use body_parts length directly to match rows
  197. %
  198. % % Plot the surface
  199. % surf(X, Y, rms_acceleration_t, 'EdgeColor', 'none');
  200. % colormap("turbo");
  201. % c = colorbar;
  202. % c.Label.String = 'RMS of Acceleration Magnitude [mm/s^2]';
  203. % c.Label.FontSize = 11;
  204. % caxis([0 max(rms_acceleration_t(:))]);
  205. %
  206. % % Set axis limits and labels
  207. % xlim([min(time_axis) max(time_axis)]);
  208. % ylim([1 size(rms_acceleration_t, 1)]); % Ensure the Y-axis matches the data size
  209. %
  210. % % Adjust Y-axis labels
  211. % set(gca, 'YTick', 1:length(body_parts), 'YTickLabel', body_parts, 'YTickLabelRotation', 30, 'XTickLabelRotation', 30);
  212. %
  213. %
  214. % % Label axes
  215. % xlabel('Time [ms]', 'FontSize', 11);
  216. % ylabel('Body Landmarks', 'FontSize', 11);
  217. % zlabel('RMS', 'FontSize', 11);
  218. % title('Root Mean Square [RMS] of Acceleration Magnitude Over Time', 'FontSize', 12);
  219. % subtitle(['Windows of ', num2str(sec*1000), ' [ms]'], 'FontSize', 11.5);
  220. % view([110, 40]);
  221. %
  222. % % Recalculate and highlight top RMS body parts with correctly aligned labels
  223. % N = min(6, length(body_parts)); % Number of top body parts to highlight
  224. % [~, top_rms_indices] = maxk(mean(rms_acceleration_t, 2), N); % Get top body part indices by RMS
  225. % rms_threshold = min(max(rms_acceleration_t(label_indices(top_rms_indices), :), [], 2)); % Min of max RMS values for top body parts
  226. %
  227. %
  228. % % Draw 3D "walls" with restricted height at RMS threshold
  229. % hold on;
  230. % for i = 1:length(onset_times)
  231. % % Define the color and style for each line
  232. % if i == 1
  233. % line_color = 'r';
  234. % line_style = '--';
  235. % else
  236. % line_color = 'k';
  237. % line_style = '--';
  238. % end
  239. %
  240. % % Draw box edges at each onset time along x, y, and restricted z
  241. % plot3([onset_times(i), onset_times(i)], [1, 1], [0, rms_threshold], line_style, 'Color', line_color, 'LineWidth', 2); % Vertical edge
  242. % plot3([onset_times(i), onset_times(i)], [1, size(rms_acceleration_t, 1)], [0, 0], line_style, 'Color', line_color, 'LineWidth', 2); % Bottom horizontal edge
  243. % plot3([onset_times(i), onset_times(i)], [1, size(rms_acceleration_t, 1)], [rms_threshold, rms_threshold], line_style, 'Color', line_color, 'LineWidth', 2); % Top horizontal edge
  244. % end
  245. %
  246. % % Highlight top RMS body parts with labels
  247. % offset = 0.1; % Offset for label positioning
  248. % for i = 1:N
  249. % plot3(time_axis, repmat(top_rms_indices(i), size(time_axis)), rms_acceleration_t(top_rms_indices(i), :), 'b', 'Linestyle', ':', 'LineWidth', 2);
  250. % text(max(time_axis) + offset, top_rms_indices(i), max(rms_acceleration_t(top_rms_indices(i), :)), ...
  251. % body_parts{top_rms_indices(i)}, 'Color', 'k', 'FontSize', 8, 'FontWeight', 'bold', 'HorizontalAlignment', 'center');
  252. % end
  253. %
  254. % % Enable grid for better visualization
  255. % grid on;
  256. %
  257. % % Rotate the fig for better visualization
  258. % azimuth_angle = 3.349649635036496e+02;
  259. % elevation_angle = 81.093181818181820;
  260. %
  261. % view([azimuth_angle, elevation_angle]); % Replace with your desired values
  262. %% Plot Acceleration Magnitude as Coldmap 2D
  263. figure('units', 'normalized', 'outerposition', [0 0 1 1]);
  264. imagesc(EEG.times, 1:size(rms_acceleration_t, 1), rms_acceleration_t);
  265. colorbar;
  266. colormap("sky");
  267. set(gca, 'YTick', 1:length(body_parts), 'YTickLabel', body_parts); % Label body parts
  268. xlabel('Time [ms]', 'FontSize', 11);
  269. ylabel('Body Landmarks', 'FontSize', 11);
  270. title('Root Mean Square [RMS] of Acceleration Magnitude', 'FontSize', 12);
  271. subtitle(['Windows of ', num2str(sec*1000), ' [ms]'], 'FontSize', 11.5);
  272. c = colorbar;
  273. c.Label.String = 'RMS of Acceleration Magnitude [mm/s^2]';
  274. c.Label.FontSize = 11;
  275. xline(0, '--', 'Color', 'r', 'LineWidth', 2); % Add red dashed line at 0 ms
  276. xline(grand_avgOnsetTime_rev, ':', 'Color', 'k', 'LineWidth', 2); % Add a red dashed line at 0 ms to indicate movement onset
  277. %% Plot Acceleration Magnitude as Heatmap 2D
  278. % % Plot with labels
  279. % fig_velocity = figure('units', 'normalized', 'outerposition', [0 0 1 1]);
  280. % imagesc(EEG.times, 1:size(rms_acceleration_t, 1), rms_acceleration_t); % Plot RMS over time for MediaPipe data
  281. % colormap("turbo");
  282. % c = colorbar;
  283. % c.Label.String = 'RMS of Acceleration Magnitude [mm/s^2]';
  284. % c.Label.FontSize = 11;
  285. % set(gca, 'YTick', 1:1:33, 'YTickLabel', body_parts); % Adjust for your landmark indices
  286. % xlabel('Time [ms]', 'FontSize', 11);
  287. % ylabel('Body Landmarks', 'FontSize', 11);
  288. % title('Root Mean Square [RMS] of Acceleration Magnitude Over Time', 'FontSize', 12);
  289. % subtitle(['Windows of ', num2str(sec*1000), ' [ms]'], 'FontSize', 11.5);
  290. %
  291. % xline(0, '--', 'Color', 'r', 'LineWidth', 2); % Add a red dashed line at 0 ms to indicate movement onset
  292. % xline(grand_avgOnsetTime_rev, ':', 'Color', 'k', 'LineWidth', 2); % Add a red dashed line at 0 ms to indicate movement onset
  293. %
  294. % %saveas(gcf, [outpath, '\\group_analysis\\','RMS_motion_2D_grand_avg', '.jpg']); % Save the figure as a PNG image
  295. %% Permutation Test for Motion Significance
  296. % Parameters
  297. bin_samples = bin_size / 10; % Convert to samples (each sample is 10 ms)
  298. time_bins = -2500:bin_size:1000; % Define time bins in ms
  299. clusters = {'Head', 'Upper Body', 'Lower Body'};
  300. num_clusters = length(clusters);
  301. % Define cluster indices
  302. head_parts = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
  303. upper_body_parts = [12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23];
  304. lower_body_parts = [24, 25, 26, 27, 28, 29, 30, 31, 32, 33];
  305. cluster_parts = {head_parts, upper_body_parts, lower_body_parts};
  306. % Initialize results
  307. p_values = zeros(num_clusters, length(time_bins) - 1);
  308. % Perform Permutation Test for Each Cluster and Each Time Bin
  309. for cluster_idx = 1:length(clusters)
  310. cluster_data = rms_acceleration_t(cluster_parts{cluster_idx}, :); % Extract data for current cluster
  311. for bin_idx = 1:length(time_bins) - 1
  312. % Define start and end sample for the current bin
  313. start_sample = (bin_idx - 1) * bin_samples + 1;
  314. end_sample = min(start_sample + bin_samples - 1, size(cluster_data, 2));
  315. % Calculate the observed mean for the current bin
  316. observed_mean = mean(cluster_data(:, start_sample:end_sample), 'all');
  317. % Initialize permutation means
  318. permuted_means = zeros(1, num_permutations);
  319. % Permutation test
  320. for perm = 1:num_permutations
  321. % Permute data
  322. permuted_data = cluster_data(:, randperm(size(cluster_data, 2)));
  323. permuted_means(perm) = mean(permuted_data(:, start_sample:end_sample), 'all');
  324. end
  325. % Calculate p-value for observed mean
  326. p_values(cluster_idx, bin_idx) = sum(permuted_means >= observed_mean) / num_permutations;
  327. end
  328. end
  329. % Apply FDR Correction
  330. adjusted_p_values = mafdr(p_values(:), 'BHFDR', true);
  331. adjusted_p_values = reshape(adjusted_p_values, size(p_values));
  332. %% Visualization of Significant Time Bins
  333. figure('units', 'normalized', 'outerposition', [0 0 1 1]);
  334. time_labels = time_bins(1:end-1) + bin_size / 2; % Midpoints of time bins
  335. % Define time bins from -2500 ms to 1000 ms
  336. bin_start_times = -2500:bin_size:1000; % Start of each 100 ms bin
  337. bin_labels = arrayfun(@(x) sprintf('%d %d ms', x), bin_start_times, 'UniformOutput', false); % Create labels
  338. for cluster_idx = 1:length(clusters)
  339. subplot(length(clusters), 1, cluster_idx);
  340. hold on;
  341. % Plot p-values
  342. plot(time_labels, adjusted_p_values(cluster_idx, :), '-o', 'LineWidth', 1.5);
  343. % Highlight significant bins
  344. significant_bins = adjusted_p_values(cluster_idx, :) < 0.05;
  345. scatter(time_labels(significant_bins), adjusted_p_values(cluster_idx, significant_bins), ...
  346. 50, 'r', 'filled'); % Red dots for significant bins
  347. % Labels and formatting
  348. title(['Cluster: ', clusters{cluster_idx}], 'FontSize', 14, 'FontWeight', 'bold');
  349. xlabel('Time [ms]', 'FontSize', 12);
  350. ylabel('Adjusted p-Value', 'FontSize', 12);
  351. ylim([0 1]);
  352. yline(0.05, '--', 'FDR Threshold (α = 0.05)', 'FontSize', 10, 'Color', 'k', 'LabelHorizontalAlignment','left');
  353. xline(0, 'r--', 'LineWidth', 2.5); % Onset line
  354. xlim([-2500, 1000]);
  355. xticks(bin_start_times); % Ensure all bins are represented
  356. xticklabels(bin_labels);
  357. %xtickangle(45); % Rotate labels for readability
  358. grid on;
  359. hold off;
  360. end
  361. sgtitle('Permutation Test for Motion Significance', 'FontSize', 16, 'FontWeight', 'bold');
  362. % Save the Plot
  363. %saveas(gcf, [outpath, '\\group_analysis\\', 'Permutation_test_motion_clusters', '.jpg']);
  364. % save_fig(gcf,[outpath, '\\group_analysis\\',], 'Permutation_test_motion_clusters');
  365. if frequency_sampling == 1
  366. saveas(gcf, [outpath, '\\group_analysis\\', 'Permutation_test_motion_clusters_15Hz', '.jpg']);
  367. elseif frequency_sampling == 2
  368. saveas(gcf, [outpath, '\\group_analysis\\', 'Permutation_test_motion_clusters_250Hz', '.jpg']);
  369. end
  370. %% Combined plots
  371. % Plot RMS Background
  372. figure('units', 'normalized', 'outerposition', [0 0 1 1]);
  373. imagesc(EEG.times, 1:size(rms_acceleration_t, 1), rms_acceleration_t);
  374. colormap("turbo");
  375. c = colorbar;
  376. c.Label.String = 'RMS of Acceleration Magnitude [mm/s^2]';
  377. c.Label.FontSize = 11;
  378. set(gca, 'YTick', 1:1:33, 'YTickLabel', body_parts);
  379. xlabel('Time [ms]', 'FontSize', 11);
  380. ylabel('Body Landmarks', 'FontSize', 11, 'Position', [-2976.620246249664,16.560959874129882,1]);
  381. title('Root Mean Square [RMS] of Acceleration Magnitude Over Time', 'FontSize', 14);
  382. subtitle('Windows of 10 ms', 'FontSize', 11.5);
  383. % Add grid lines
  384. for c = [head_parts(end), upper_body_parts(end), lower_body_parts(end)]
  385. yline(c + 0.5, 'w-', 'LineWidth', 2);
  386. end
  387. hold on;
  388. % Draw vertical lines for time bins
  389. % for t = time_bins
  390. % xline(t, 'w--', 'LineWidth', 0.1);
  391. % end
  392. % Add cluster labels on the left side of the plot
  393. text(-2900, mean(head_parts), 'Head', 'FontSize', 11, 'FontWeight', 'bold', 'HorizontalAlignment', 'right', 'Rotation', 90);
  394. text(-2900, mean(upper_body_parts)-2, 'Upper Body', 'FontSize', 11, 'FontWeight', 'bold', 'HorizontalAlignment', 'right', 'Rotation', 90);
  395. text(-2900, mean(lower_body_parts)-2, 'Lower Body', 'FontSize', 11, 'FontWeight', 'bold', 'HorizontalAlignment', 'right', 'Rotation', 90);
  396. % Highlight Significant Bins with Stars
  397. for cluster_idx = 1:num_clusters
  398. for bin_idx = 1:length(time_bins) - 1
  399. if adjusted_p_values(cluster_idx, bin_idx) < 0.05
  400. % Get the mid-time of the bin
  401. bin_mid_time = (time_bins(bin_idx) + time_bins(bin_idx + 1)) / 2;
  402. % Determine y-axis position for the star (center of the cluster range)
  403. y_center = mean(cluster_parts{cluster_idx});
  404. % Plot star
  405. plot(bin_mid_time, y_center, 'w*', 'MarkerSize', 8, 'LineWidth', 1.5);
  406. end
  407. end
  408. end
  409. xline(0, '--', 'Color', 'r', 'LineWidth', 2);
  410. xline(grand_avgOnsetTime_rev, ':', 'Color', 'k', 'LineWidth', 2); % Add a red dashed line at 0 ms to indicate movement onset
  411. hold off;
  412. % Save the Plot
  413. %saveas(gcf, [outpath, '\\group_analysis\\','RMS_motion_2D_grand_avg', '.jpg']); % Save the figure as a PNG image
  414. % save_fig(gcf,[outpath, '\\group_analysis\\',], 'RMS_motion_2D_grand_avg');
  415. if frequency_sampling == 1
  416. saveas(gcf, [outpath, '\\group_analysis\\','RMS_motion_2D_grand_avg_15Hz', '.jpg']); % Save the figure as a PNG image
  417. elseif frequency_sampling == 2
  418. saveas(gcf, [outpath, '\\group_analysis\\','RMS_motion_2D_grand_avg_250Hz', '.jpg']); % Save the figure as a PNG image
  419. end
  420. %% Combined plots 3D - Permutation
  421. % RMS measures overall variability, which is helpful for identifying general movement artifacts.
  422. time_axis = linspace(from * 1000, to * 1000, length(idx_loop)); % in milliseconds
  423. % We will only label every third row (corresponding to the y-coordinates)
  424. label_indices = 0:1:length(body_parts); % Indices for the y-coordinates (every third row)
  425. % Define time points for onset lines
  426. onset_times = [0, grand_avgOnsetTime_rev]; % Replace with your actual onset times
  427. % Plot the 3D surface with correctly aligned Y-axis labels
  428. figure('units', 'normalized', 'outerposition', [0 0 1 1]);
  429. [X, Y] = meshgrid(time_axis, 1:length(body_parts)); % Use body_parts length directly to match rows
  430. % Plot the surface
  431. surf(X, Y, rms_acceleration_t, 'EdgeColor', 'none');
  432. colormap("turbo");
  433. c = colorbar;
  434. c.Label.String = 'RMS of Acceleration Magnitude [mm/s^2]';
  435. c.Label.FontSize = 11;
  436. caxis([0 max(rms_acceleration_t(:))]);
  437. % Set axis limits and labels
  438. xlim([min(time_axis) max(time_axis)]);
  439. ylim([1 size(rms_acceleration_t, 1)]); % Ensure the Y-axis matches the data size
  440. % Adjust Y-axis labels
  441. set(gca, 'YTick', 1:length(body_parts), 'YTickLabel', body_parts, 'XTickLabelRotation', 30); %'YTickLabelRotation', 10,
  442. % Label axes
  443. xlabel('Time [ms]', 'FontSize', 11);
  444. ylabel('Body Landmarks', 'FontSize', 11, 'Position', [-3438.001500791994,11.190612155547742,-0.16010789176363]);
  445. zlabel('RMS', 'FontSize', 11);
  446. %title('Root Mean Square [RMS] of Acceleration Magnitude Over Time', 'FontSize', 12);
  447. %subtitle(['Windows of ', num2str(sec*1000), ' [ms]'], 'FontSize', 11.5);
  448. view([110, 40]);
  449. % Recalculate and highlight top RMS body parts with correctly aligned labels
  450. N = min(6, length(body_parts)); % Number of top body parts to highlight
  451. [top_rms_values, top_rms_indices] = maxk(mean(rms_acceleration_t, 2), N); % Get top body part indices by RMS
  452. rms_threshold = min(max(rms_acceleration_t(label_indices(top_rms_indices), :), [], 2)); % Average of max RMS values for top body parts
  453. % Draw 3D "walls" with restricted height at RMS threshold
  454. hold on;
  455. for i = 1:length(onset_times)
  456. % Define the color and style for each line
  457. if i == 1
  458. line_color = 'r';
  459. line_style = '--';
  460. else
  461. line_color = 'k';
  462. line_style = ':';
  463. end
  464. % Draw box edges at each onset time along x, y, and restricted z
  465. plot3([onset_times(i), onset_times(i)], [1, 1], [0, rms_threshold], line_style, 'Color', line_color, 'LineWidth', 2.5); % Vertical edge
  466. plot3([onset_times(i), onset_times(i)], [1, size(rms_acceleration_t, 1)], [0, 0], line_style, 'Color', line_color, 'LineWidth', 2.5); % Bottom horizontal edge
  467. plot3([onset_times(i), onset_times(i)], [1, size(rms_acceleration_t, 1)], [rms_threshold, rms_threshold], line_style, 'Color', line_color, 'LineWidth', 2.5); % Top horizontal edge
  468. end
  469. % Highlight top RMS body parts with labels
  470. offset = 0.1; % Offset for label positioning
  471. for i = 1:N
  472. plot3(time_axis, repmat(top_rms_indices(i), size(time_axis)), rms_acceleration_t(top_rms_indices(i), :), 'b', 'Linestyle', ':', 'LineWidth', 2);
  473. %text(max(time_axis) + offset, top_rms_indices(i), max(rms_acceleration_t(top_rms_indices(i), :)), ...
  474. % body_parts{top_rms_indices(i)}, 'Color', 'k', 'FontSize', 8, 'HorizontalAlignment', 'center');
  475. end
  476. % Enable grid for better visualization
  477. grid on;
  478. % % Add grid lines
  479. % for c = [head_parts(end), upper_body_parts(end), lower_body_parts(end)]
  480. % yline(c + 0.5, 'w-', 'LineWidth', 2);
  481. % end
  482. hold on;
  483. % % Draw vertical lines for time bins
  484. % for t = time_bins
  485. % xline(t, 'w--', 'LineWidth', 0.1);
  486. % end
  487. % Add cluster labels on the left side of the plot
  488. text(-3200, mean(head_parts)+2, 'Head', 'FontSize', 11, 'FontWeight', 'bold', 'HorizontalAlignment', 'right', 'Rotation', 90); %, 'Rotation', 90
  489. text(-3100, mean(upper_body_parts)+2, 'Upper Body', 'FontSize', 11, 'FontWeight', 'bold', 'HorizontalAlignment', 'right', 'Rotation', 90);
  490. text(-3100, mean(lower_body_parts)+1, 'Lower Body', 'FontSize', 11, 'FontWeight', 'bold', 'HorizontalAlignment', 'right', 'Rotation', 90);
  491. % Determine maximum z value for positioning the stars on top
  492. max_rms_value = max(rms_acceleration_t(:));
  493. z_offset = 0.1; % Small offset to ensure the markers are clearly visible above the surface
  494. % Highlight Significant Bins with Stars on Top of the 3D Plot
  495. for cluster_idx = 1:num_clusters
  496. for bin_idx = 1:length(time_bins) - 1
  497. if adjusted_p_values(cluster_idx, bin_idx) < 0.05
  498. % Get the mid-time of the bin
  499. bin_mid_time = (time_bins(bin_idx) + time_bins(bin_idx + 1)) / 2;
  500. % Determine y-axis position for the star (center of the cluster range)
  501. y_center = mean(cluster_parts{cluster_idx});
  502. % Plot star at the top surface level
  503. plot3(bin_mid_time, y_center, max_rms_value + z_offset, ...
  504. 'Marker', "pentagram", 'Color', 'w', 'MarkerSize', 15, 'LineWidth', 1, 'MarkerEdgeColor', 'k', 'MarkerFaceColor','w'); % Place star slightly above the surface
  505. end
  506. end
  507. end
  508. % Rotate the fig for better visualization
  509. azimuth_angle = -20.695036496350380;
  510. elevation_angle = 82.462139689578750;
  511. view([azimuth_angle, elevation_angle]); % Replace with your desired values
  512. % Loop through significant bins and highlight them with transparent 3D planes
  513. for cluster_idx = 1:num_clusters
  514. for bin_idx = 1:length(time_bins) - 1
  515. if adjusted_p_values(cluster_idx, bin_idx) < 0.05
  516. % Get the time range of the bin
  517. bin_start_time = time_bins(bin_idx);
  518. bin_end_time = time_bins(bin_idx + 1);
  519. % Determine y-axis range for the cluster
  520. y_start = min(cluster_parts{cluster_idx});
  521. y_end = max(cluster_parts{cluster_idx});
  522. % Define z-axis range (RMS values) for the box
  523. z_start = 0;
  524. z_end = max_rms_value;
  525. % Create vertices for the 3D box
  526. vertices = [
  527. bin_start_time, y_start, z_start; % Bottom-front-left
  528. bin_start_time, y_end, z_start; % Bottom-front-right
  529. bin_end_time, y_end, z_start; % Bottom-back-right
  530. bin_end_time, y_start, z_start; % Bottom-back-left
  531. bin_start_time, y_start, z_end; % Top-front-left
  532. bin_start_time, y_end, z_end; % Top-front-right
  533. bin_end_time, y_end, z_end; % Top-back-right
  534. bin_end_time, y_start, z_end; % Top-back-left
  535. ];
  536. % Define faces of the box
  537. faces = [
  538. 1, 2, 6, 5; % Front
  539. 2, 3, 7, 6; % Right
  540. 3, 4, 8, 7; % Back
  541. 4, 1, 5, 8; % Left
  542. 5, 6, 7, 8; % Top
  543. 1, 2, 3, 4; % Bottom
  544. ];
  545. % Add the 3D box to the plot
  546. patch('Vertices', vertices, 'Faces', faces, ...
  547. 'FaceColor', "yellow", 'FaceAlpha', 0.08, 'EdgeColor', 'k');
  548. end
  549. end
  550. end
  551. %saveas(gcf, [outpath, '\\group_analysis\\','RMS_motion_3D_grand_avg', '.jpg']); % Save the figure as a PNG image
  552. %save_fig(gcf,[outpath, '\\group_analysis\\',], 'RMS_motion_3D_grand_avg_Permutation', 'fontsize', 10);
  553. % Save a figure with 2-column width (180 mm) as TIFF
  554. % save_fig_pro(gcf, [outpath, '\\group_analysis\\',], 'RMS_motion_3D_grand_avg', 'fontsize', 8, 'width_mm', 180, 'figtype', 'tiff', 'dpi', 600);
  555. if frequency_sampling == 1
  556. save_fig(gcf,[outpath, '\\group_analysis\\',], 'RMS_motion_3D_grand_avg_Permutation_15Hz', 'fontsize', 10);
  557. elseif frequency_sampling == 2
  558. save_fig(gcf,[outpath, '\\group_analysis\\',], 'RMS_motion_3D_grand_avg_Permutation_250Hz', 'fontsize', 10);
  559. end
  560. %% Combined plots 3D - Binomial
  561. % RMS measures overall variability, which is helpful for identifying general movement artifacts.
  562. time_axis = linspace(from * 1000, to * 1000, length(idx_loop)); % in milliseconds
  563. % We will only label every third row (corresponding to the y-coordinates)
  564. label_indices = 0:1:length(body_parts); % Indices for the y-coordinates (every third row)
  565. % Define time points for onset lines
  566. onset_times = [0, grand_avgOnsetTime_rev]; % Replace with your actual onset times
  567. % Plot the 3D surface with correctly aligned Y-axis labels
  568. figure('units', 'normalized', 'outerposition', [0 0 1 1]);
  569. [X, Y] = meshgrid(time_axis, 1:length(body_parts)); % Use body_parts length directly to match rows
  570. % Plot the surface
  571. surf(X, Y, rms_acceleration_t, 'EdgeColor', 'none');
  572. colormap("turbo");
  573. c = colorbar;
  574. c.Label.String = 'RMS of Acceleration Magnitude [mm/s^2]';
  575. c.Label.FontSize = 11;
  576. caxis([0 max(rms_acceleration_t(:))]);
  577. % Set axis limits and labels
  578. xlim([min(time_axis) max(time_axis)]);
  579. ylim([1 size(rms_acceleration_t, 1)]); % Ensure the Y-axis matches the data size
  580. % Adjust Y-axis labels
  581. set(gca, 'YTick', 1:length(body_parts), 'YTickLabel', body_parts, 'XTickLabelRotation', 30); %'YTickLabelRotation', 10,
  582. % Label axes
  583. xlabel('Time [ms]', 'FontSize', 11);
  584. ylabel('Body Landmarks', 'FontSize', 11, 'Position', [-3438.001500791994,11.190612155547742,-0.16010789176363]);
  585. zlabel('RMS', 'FontSize', 11);
  586. %title('Root Mean Square [RMS] of Acceleration Magnitude Over Time', 'FontSize', 12);
  587. %subtitle(['Windows of ', num2str(sec*1000), ' [ms]'], 'FontSize', 11.5);
  588. view([110, 40]);
  589. % Recalculate and highlight top RMS body parts with correctly aligned labels
  590. N = min(6, length(body_parts)); % Number of top body parts to highlight
  591. [top_rms_values, top_rms_indices] = maxk(mean(rms_acceleration_t, 2), N); % Get top body part indices by RMS
  592. rms_threshold = min(max(rms_acceleration_t(label_indices(top_rms_indices), :), [], 2)); % Average of max RMS values for top body parts
  593. % Draw 3D "walls" with restricted height at RMS threshold
  594. hold on;
  595. for i = 1:length(onset_times)
  596. % Define the color and style for each line
  597. if i == 1
  598. line_color = 'r';
  599. line_style = '--';
  600. else
  601. line_color = 'k';
  602. line_style = ':';
  603. end
  604. % Draw box edges at each onset time along x, y, and restricted z
  605. plot3([onset_times(i), onset_times(i)], [1, 1], [0, rms_threshold], line_style, 'Color', line_color, 'LineWidth', 2.5); % Vertical edge
  606. %plot3([onset_times(i), onset_times(i)], [1, size(rms_acceleration_t, 1)], [0, 0], line_style, 'Color', line_color, 'LineWidth', 2.5); % Bottom horizontal edge
  607. plot3([onset_times(i), onset_times(i)], [1, size(rms_acceleration_t, 1)], [rms_threshold, rms_threshold], line_style, 'Color', line_color, 'LineWidth', 2.5); % Top horizontal edge
  608. end
  609. % % Highlight top RMS body parts with labels
  610. % offset = 0.1; % Offset for label positioning
  611. % for i = 1:N
  612. % plot3(time_axis, repmat(top_rms_indices(i), size(time_axis)), rms_acceleration_t(top_rms_indices(i), :), 'b', 'Linestyle', ':', 'LineWidth', 2);
  613. % %text(max(time_axis) + offset, top_rms_indices(i), max(rms_acceleration_t(top_rms_indices(i), :)), ...
  614. % % body_parts{top_rms_indices(i)}, 'Color', 'k', 'FontSize', 8, 'HorizontalAlignment', 'center');
  615. % end
  616. % Enable grid for better visualization
  617. grid on;
  618. % % Add grid lines
  619. % for c = [head_parts(end), upper_body_parts(end), lower_body_parts(end)]
  620. % yline(c + 0.5, 'w-', 'LineWidth', 2);
  621. % end
  622. hold on;
  623. % % Draw vertical lines for time bins
  624. % for t = time_bins
  625. % xline(t, 'w--', 'LineWidth', 0.1);
  626. % end
  627. % Add cluster labels on the left side of the plot
  628. text(-3200, mean(head_parts)+2, 'Head', 'FontSize', 11, 'FontWeight', 'bold', 'HorizontalAlignment', 'right', 'Rotation', 90); %, 'Rotation', 90
  629. text(-3100, mean(upper_body_parts)+2, 'Upper Body', 'FontSize', 11, 'FontWeight', 'bold', 'HorizontalAlignment', 'right', 'Rotation', 90);
  630. text(-3100, mean(lower_body_parts)+1, 'Lower Body', 'FontSize', 11, 'FontWeight', 'bold', 'HorizontalAlignment', 'right', 'Rotation', 90);
  631. % % Determine maximum z value for positioning the stars on top
  632. % max_rms_value = max(rms_acceleration_t(:));
  633. % z_offset = 0.1; % Small offset to ensure the markers are clearly visible above the surface
  634. %
  635. % % Highlight Significant Bins with Stars on Top of the 3D Plot
  636. % for cluster_idx = 1:num_clusters
  637. % for bin_idx = 1:length(time_bins) - 1
  638. % if adjusted_p_values(cluster_idx, bin_idx) < 0.05
  639. % % Get the mid-time of the bin
  640. % bin_mid_time = (time_bins(bin_idx) + time_bins(bin_idx + 1)) / 2;
  641. %
  642. % % Determine y-axis position for the star (center of the cluster range)
  643. % y_center = mean(cluster_parts{cluster_idx});
  644. %
  645. % % Plot star at the top surface level
  646. % plot3(bin_mid_time, y_center, max_rms_value + z_offset, ...
  647. % 'Marker', "pentagram", 'Color', 'w', 'MarkerSize', 15, 'LineWidth', 1, 'MarkerEdgeColor', 'k', 'MarkerFaceColor','w'); % Place star slightly above the surface
  648. % end
  649. % end
  650. % end
  651. % Rotate the fig for better visualization
  652. azimuth_angle = -20.695036496350380;
  653. elevation_angle = 82.462139689578750;
  654. view([azimuth_angle, elevation_angle]); % Replace with your desired values
  655. % % Loop through significant bins and highlight them with transparent 3D planes
  656. % for cluster_idx = 1:num_clusters
  657. % for bin_idx = 1:length(time_bins) - 1
  658. % if adjusted_p_values(cluster_idx, bin_idx) < 0.05
  659. % % Get the time range of the bin
  660. % bin_start_time = time_bins(bin_idx);
  661. % bin_end_time = time_bins(bin_idx + 1);
  662. %
  663. % % Determine y-axis range for the cluster
  664. % y_start = min(cluster_parts{cluster_idx});
  665. % y_end = max(cluster_parts{cluster_idx});
  666. %
  667. % % Define z-axis range (RMS values) for the box
  668. % z_start = 0;
  669. % z_end = max_rms_value;
  670. %
  671. % % Create vertices for the 3D box
  672. % vertices = [
  673. % bin_start_time, y_start, z_start; % Bottom-front-left
  674. % bin_start_time, y_end, z_start; % Bottom-front-right
  675. % bin_end_time, y_end, z_start; % Bottom-back-right
  676. % bin_end_time, y_start, z_start; % Bottom-back-left
  677. % bin_start_time, y_start, z_end; % Top-front-left
  678. % bin_start_time, y_end, z_end; % Top-front-right
  679. % bin_end_time, y_end, z_end; % Top-back-right
  680. % bin_end_time, y_start, z_end; % Top-back-left
  681. % ];
  682. %
  683. % % Define faces of the box
  684. % faces = [
  685. % 1, 2, 6, 5; % Front
  686. % 2, 3, 7, 6; % Right
  687. % 3, 4, 8, 7; % Back
  688. % 4, 1, 5, 8; % Left
  689. % 5, 6, 7, 8; % Top
  690. % 1, 2, 3, 4; % Bottom
  691. % ];
  692. %
  693. % % Add the 3D box to the plot
  694. % patch('Vertices', vertices, 'Faces', faces, ...
  695. % 'FaceColor', "yellow", 'FaceAlpha', 0.08, 'EdgeColor', 'k');
  696. % end
  697. % end
  698. % end
  699. % Add Significant Movement Stars to the Plot
  700. % Define the path to the onset validation file
  701. onset_validation_file = fullfile(outpath, 'binomial_test_acceleration.xlsx');
  702. % Read the onset validation data
  703. onset_data = readtable(onset_validation_file, 'ReadVariableNames', true);
  704. % Identify significant body parts based on p-value threshold
  705. significant_parts = onset_data.pValue < 0.05;
  706. significant_body_parts = onset_data.parts(significant_parts);
  707. % Loop through the body parts and find their indices
  708. significant_indices = [];
  709. for i = 1:length(significant_body_parts)
  710. % Find the index of the significant body part in the body_parts list
  711. idx = find(strcmp(body_parts, significant_body_parts{i}));
  712. if ~isempty(idx)
  713. significant_indices = [significant_indices; idx];
  714. end
  715. end
  716. % Add stars for significant body parts at time 0
  717. hold on;
  718. for i = 1:length(significant_indices)
  719. idx = significant_indices(i);
  720. plot3(0, idx, max(rms_acceleration_t(:)) + 0.1, 'p', ...
  721. 'Marker', "pentagram", 'Color', 'w', 'MarkerSize', 15, 'LineWidth', 1, 'MarkerEdgeColor', 'k', 'MarkerFaceColor','w');
  722. end
  723. %saveas(gcf, [outpath, '\\group_analysis\\','RMS_motion_3D_grand_avg_PRO', '.jpg']); % Save the figure as a PNG image
  724. %save_fig(gcf,[outpath, '\\group_analysis\\',], 'RMS_motion_3D_grand_avg_Binomial', 'fontsize', 10);
  725. if frequency_sampling == 1
  726. save_fig(gcf,[outpath, '\\group_analysis\\',], 'RMS_motion_3D_grand_avg_Binomial_15Hz', 'fontsize', 10);
  727. elseif frequency_sampling == 2
  728. save_fig(gcf,[outpath, '\\group_analysis\\',], 'RMS_motion_3D_grand_avg_Binomial_250Hz', 'fontsize', 10);
  729. end
  730. disp('Updated plot with significant body parts saved!');
  731. %% Combined plots 2D - Binomial
  732. % RMS measures overall variability, which is helpful for identifying general movement artifacts.
  733. time_axis = linspace(from * 1000, to * 1000, length(idx_loop)); % in milliseconds
  734. % We will only label every third row (corresponding to the y-coordinates)
  735. label_indices = 0:1:length(body_parts); % Indices for the y-coordinates (every third row)
  736. % Define time points for onset lines
  737. onset_times = [0, grand_avgOnsetTime_rev]; % Replace with your actual onset times
  738. % Plot the 2D surface with correctly aligned Y-axis labels
  739. figure('units', 'normalized', 'outerposition', [0 0 1 1]);
  740. imagesc(time_axis, 1:size(rms_acceleration_t, 1), rms_acceleration_t);
  741. colormap("turbo");
  742. c = colorbar;
  743. c.Label.String = 'RMS of Acceleration Magnitude [mm/s^2]';
  744. c.Label.FontSize = 11;
  745. caxis([0 max(rms_acceleration_t(:))]);
  746. set(gca, 'YTick', 1:1:33, 'YTickLabel', body_parts);
  747. xlabel('Time [ms]', 'FontSize', 11);
  748. ylabel('Body Landmarks', 'FontSize', 11, 'Position', [-2976.620246249664,16.560959874129882,1]);
  749. % title('Root Mean Square [RMS] of Acceleration Magnitude Over Time', 'FontSize', 14);
  750. % subtitle('Windows of 10 ms', 'FontSize', 11.5);
  751. % Set axis limits and labels
  752. xlim([min(time_axis) max(time_axis)]);
  753. ylim([1 size(rms_acceleration_t, 1)]); % Ensure the Y-axis matches the data size
  754. % % Add grid lines
  755. % for c = [head_parts(end), upper_body_parts(end), lower_body_parts(end)]
  756. % yline(c + 0.5, 'w-', 'LineWidth', 2);
  757. % end
  758. hold on;
  759. % Add cluster labels on the left side of the plot
  760. text(-2900, mean(head_parts), 'Head', 'FontSize', 11, 'FontWeight', 'bold', 'HorizontalAlignment', 'right', 'Rotation', 90);
  761. text(-2900, mean(upper_body_parts)-2, 'Upper Body', 'FontSize', 11, 'FontWeight', 'bold', 'HorizontalAlignment', 'right', 'Rotation', 90);
  762. text(-2900, mean(lower_body_parts)-2, 'Lower Body', 'FontSize', 11, 'FontWeight', 'bold', 'HorizontalAlignment', 'right', 'Rotation', 90);
  763. % Recalculate and highlight top RMS body parts with correctly aligned labels
  764. N = min(6, length(body_parts)); % Number of top body parts to highlight
  765. [top_rms_values, top_rms_indices] = maxk(mean(rms_acceleration_t, 2), N); % Get top body part indices by RMS
  766. rms_threshold = min(max(rms_acceleration_t(label_indices(top_rms_indices), :), [], 2)); % Average of max RMS values for top body parts
  767. % Draw 3D "walls" with restricted height at RMS threshold
  768. hold on;
  769. for i = 1:length(onset_times)
  770. % Define the color and style for each line
  771. if i == 1
  772. line_color = 'r';
  773. line_style = '--';
  774. else
  775. line_color = 'k';
  776. line_style = ':';
  777. end
  778. % Draw box edges at each onset time along x, y, and restricted z
  779. plot3([onset_times(i), onset_times(i)], [1, 1], [0, rms_threshold], line_style, 'Color', line_color, 'LineWidth', 2.5); % Vertical edge
  780. %plot3([onset_times(i), onset_times(i)], [1, size(rms_acceleration_t, 1)], [0, 0], line_style, 'Color', line_color, 'LineWidth', 2.5); % Bottom horizontal edge
  781. plot3([onset_times(i), onset_times(i)], [1, size(rms_acceleration_t, 1)], [rms_threshold, rms_threshold], line_style, 'Color', line_color, 'LineWidth', 2.5); % Top horizontal edge
  782. end
  783. % Add Significant Movement Stars to the Plot
  784. hold on;
  785. % Define the path to the onset validation file
  786. onset_validation_file = fullfile(outpath, 'binomial_test_acceleration.xlsx');
  787. % Read the onset validation data
  788. onset_data = readtable(onset_validation_file, 'ReadVariableNames', true);
  789. % Identify significant body parts based on p-value threshold
  790. significant_parts = onset_data.pValue < 0.05;
  791. significant_body_parts = onset_data.parts(significant_parts);
  792. % Loop through the body parts and find their indices
  793. significant_indices = [];
  794. for i = 1:length(significant_body_parts)
  795. % Find the index of the significant body part in the body_parts list
  796. idx = find(strcmp(body_parts, significant_body_parts{i}));
  797. if ~isempty(idx)
  798. significant_indices = [significant_indices; idx];
  799. end
  800. end
  801. % Add stars for significant body parts at time 0
  802. hold on;
  803. for i = 1:length(significant_indices)
  804. idx = significant_indices(i);
  805. plot3(0, idx, max(rms_acceleration_t(:)) + 0.1, 'p', ...
  806. 'Marker', "pentagram", 'Color', 'w', 'MarkerSize', 15, 'LineWidth', 1, 'MarkerEdgeColor', 'k', 'MarkerFaceColor','w');
  807. end
  808. %saveas(gcf, [outpath, '\\group_analysis\\','RMS_motion_3D_grand_avg_PRO', '.jpg']); % Save the figure as a PNG image
  809. %save_fig(gcf,[outpath, '\\group_analysis\\',], 'RMS_motion_2D_grand_avg_Binomial', 'fontsize', 10);
  810. if frequency_sampling == 1
  811. save_fig(gcf,[outpath, '\\group_analysis\\',], 'RMS_motion_2D_grand_avg_Binomial_15Hz', 'fontsize', 10);
  812. acceleration_magnitude_15Hz = rms_acceleration_t;
  813. time_axis_15Hz = time_axis;
  814. save([outpath, 'Acceleration_magnitude_grand_avg_15Hz', '.mat'],'acceleration_magnitude_15Hz', 'time_axis_15Hz');
  815. elseif frequency_sampling == 2
  816. save_fig(gcf,[outpath, '\\group_analysis\\',], 'RMS_motion_2D_grand_avg_Binomial_250Hz', 'fontsize', 10);
  817. acceleration_magnitude_250Hz = rms_acceleration_t;
  818. time_axis_250Hz = time_axis;
  819. save([outpath, 'Acceleration_magnitude_grand_avg_250Hz', '.mat'],'acceleration_magnitude_250Hz', 'time_axis_250Hz');
  820. end
  821. end

A27_RP_onset_imageplot_grand_avg.m at commit 8753e5e, under MIT · at the source

Overview

Authors: Miguel Contreras-Altamirano1, Melanie Klapprott1, Nadine Jacobsen1, Paul Maanen1,2, Julius Welzel3, Stefan Debener1,2,4,5
  1. Neuropsychology Lab, Department of Psychology, School of Medicine and Health Sciences, Carl Von Ossietzky Universität Oldenburg, Oldenburg, Germany
  2. Cluster of Excellence “Hearing4All”, Carl Von Ossietzky Universität Oldenburg, Oldenburg, Germany
  3. Kiel University, Kiel, Germany
  4. Fraunhofer Institute of Digital Media Technology, Oldenburg Branch for Hearing, Oldenburg, Germany
  5. Center for Neurosensory Science and Systems, Carl Von Ossietzky University of Oldenburg, Oldenburg, Germany
Journal: Experimental brain research, volume 244, issue 8, article 153
Dates: received 24 March 2026; accepted 12 June 2026; published online 7 July 2026; in print 2026
Type: Research article · Language: English
License: CC BY
Identifiers: DOI 10.1007/s00221-026-07342-6 · PMID 42412206 · PMCID PMC13342168 · OpenAlex W4406273067
Open access: hybrid, a free copy (OpenAlex)
Status: code verified
Categories: EEG (modality), human (organism)
Methods: Connectivity, Statistics, Smoothing, state filtering, decompositions, Preprocessing, Evoked potentials, Machine learning
Keywords: Mobile EEG, Human pose, Readiness potential, MoBI, Basketball
MeSH: Basketball*, Contingent Negative Variation*, Electroencephalography*, Motor Activity*, Psychomotor Performance*, Smartphone*, Adult, Female, Humans, Male, Movement, Young Adult (* major topic)
Topic: EEG and Brain-Computer Interfaces (Cognitive Neuroscience, Neuroscience), according to OpenAlex
Funding: Carl von Ossietzky Universität Oldenburg
Citations: not cited yet (Europe PMC); 145 references in the paper
Research resources: RRID:SCR_013726

Abstract

Advances in wireless electroencephalography (EEG) technology promise to record brain-electrical activity in everyday situations. To better understand the relationship between brain activity and natural behavior, it is necessary to monitor human movement patterns. Here, we present a pocketable setup consisting of two smartphones to simultaneously capture human posture and EEG signals. We asked 26 basketball players to shoot 120 free throws each. First, we investigated whether our setup allows us to capture the readiness potential (RP) that precedes voluntary actions. Second, we investigated whether the RP differs between successful and unsuccessful free-throw attempts. The results confirmed the presence of the RP over fronto-central channels, with significant negative deflection at channel Cz, from − 400 to 0 ms before movement onset (M ± SE: − 6.54 ± 2.26 to − 13.52 ± 2.42 μV; z = − 2.53 to − 3.92; FDR-corrected p = 0.049 to 0.003; r = 0.50 to 0.77). However, the amplitude of the RP was not related to shooting success (all FDR-corrected p > 0.05; maximum mean R2 = 0.047, i.e., 4.7% explained variance). Preliminary exploratory pose analysis conducted offline indicated the presence of participant-specific variations in posture between successful and unsuccessful shots in 38.5% of participants (10/26), with 4.5% explained variance (maximum mean landmark R2 = 0.045). We conclude that a highly portable, low-cost and lightweight acquisition setup, consisting of two smartphones and a head-mounted wireless EEG amplifier, is sufficient to monitor complex human movement patterns and associated brain dynamics outside the laboratory.

Supplementary Information: The online version contains supplementary material available at 10.1007/s00221-026-07342-6.

Reproduced under the paper's license (CC BY), from the paper cited above.

Repository

Its files are read in the Code ↔ Paper reader above, with 19 matches between paragraphs and lines of code.

micufx/Pocketable-MoBI-Baskts

License: MIT
State: the link answers, verified on 27 September 2026
Evidence: files inventoried
Commit: 8753e5efbceea1d510fc1fa9c22ab3f4a0da19f5, 5 August 2026
Languages: MATLAB (48)
Size: 80 files, 48 scripts
Software Heritage: not archived
Found in: “Data availability”
Holds: README, environment (pyproject.toml)
Not found: license file, CITATION.cff, tests, continuous integration, documentation
Tools: EEGLAB (23 files), Statistics and Machine Learning Toolbox (8 files), FieldTrip (1 file), ICLabel (1 file), Signal Processing Toolbox (1 file)
Availability: 1 check, the latest on 27 September 2026: the link answers
  • 27 September 2026: the link answers
49 files

The paper's code and data availability statement is in the Data section.

Tracing map

Proposed by the machine: these links were found in the paper and verified at the source, without human review. The map will receive a Zenodo DOI once one of the paper's authors has validated it with their ORCID.

What the map holds:

  • 1 repository of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
  • 48 scripts, each with its path and the digest of its content;
  • 19 matches between paragraphs of the paper and lines of the code (method lexical-v1);
  • neither the text of the paper nor the code itself.

Its JSON (tracing-map.json) is deposited on Zenodo with its DOI once the map is validated.

Data

No dataset and no data link were found in the paper.

Data availability

The data that support the findings of this study are available on request from the corresponding author. The MATLAB code is available on GitHub (https://github.com/micufx/Pocketable-MoBI-Baskts). The smartphone LSL apps Senda and Recorda are available on GitHub (https://neuropsyol.github.io/). All information about the set-up is available at https://juliuswelzel.github.io/eeg_basketball_website/.

Reproduced under the paper's license (CC BY), from the paper cited above.

Versions

The history of this record: each version stored by the harvester or made by a correction of its authors or of the maintainers of its code, and what changed in its facts. The texts of the paper (its abstract, its availability statements) are not part of it; versions that changed only those are not listed.

Version 2, 28 September 2026

  • Publisher: n/a → Springer Science+Business Media

Version 1, 27 September 2026: the first record

Recorded: type, language, journal, volume, issue, pages, dates, 6 authors, 5 keywords, 12 MeSH terms, 1 funder, 129 references, 1 RRID.

Cite

This paper

Contreras-Altamirano, M., Klapprott, M., Jacobsen, N., Maanen, P., Welzel, J., & Debener, S. (2026). A portable solution for simultaneous human movement and mobile EEG acquisition: readiness potential for basketball free-throw shooting. Experimental brain research, 244(8), 153. https://doi.org/10.1007/s00221-026-07342-6

BibTeX

@article{contrerasaltamirano2026portable,
author = {Contreras-Altamirano, Miguel and Klapprott, Melanie and Jacobsen, Nadine and Maanen, Paul and Welzel, Julius and Debener, Stefan},
title = {{A portable solution for simultaneous human movement and mobile EEG acquisition: readiness potential for basketball free-throw shooting}},
journal = {Experimental brain research},
year = {2026},
month = jul,
volume = {244},
number = {8},
pages = {153},
publisher = {Springer Science+Business Media},
issn = {0014-4819},
doi = {10.1007/s00221-026-07342-6},
url = {https://doi.org/10.1007/s00221-026-07342-6},
pmid = {42412206},
pmcid = {PMC13342168}
}

RIS

TY - JOUR
AU - Contreras-Altamirano, Miguel
AU - Klapprott, Melanie
AU - Jacobsen, Nadine
AU - Maanen, Paul
AU - Welzel, Julius
AU - Debener, Stefan
TI - A portable solution for simultaneous human movement and mobile EEG acquisition: readiness potential for basketball free-throw shooting
T2 - Experimental brain research
J2 - Exp Brain Res
PY - 2026
DA - 2026/07/07
VL - 244
IS - 8
SP - 153
SN - 0014-4819
PB - Springer Science+Business Media
DO - 10.1007/s00221-026-07342-6
UR - https://doi.org/10.1007/s00221-026-07342-6
LA - en
ER -

CSL-JSON

{
"id": "10.1007/s00221-026-07342-6",
"type": "article-journal",
"title": "A portable solution for simultaneous human movement and mobile EEG acquisition: readiness potential for basketball free-throw shooting",
"container-title": "Experimental brain research",
"author": [
{
"family": "Contreras-Altamirano",
"given": "Miguel"
},
{
"family": "Klapprott",
"given": "Melanie"
},
{
"family": "Jacobsen",
"given": "Nadine"
},
{
"family": "Maanen",
"given": "Paul"
},
{
"family": "Welzel",
"given": "Julius"
},
{
"family": "Debener",
"given": "Stefan"
}
],
"container-title-short": "Exp Brain Res",
"volume": "244",
"issue": "8",
"page": "153",
"DOI": "10.1007/s00221-026-07342-6",
"PMID": "42412206",
"PMCID": "PMC13342168",
"ISSN": "0014-4819",
"publisher": "Springer Science+Business Media",
"URL": "https://doi.org/10.1007/s00221-026-07342-6",
"language": "en",
"issued": {
"date-parts": [
[
2026,
7,
7
]
]
}
}

The tracing map gets a citation of its own once an author has validated it and it has a DOI.

Similar papers

The papers with a page that share the most with this one: the tools found in their code, their categories, datasets, cited references and authors, the rarest counting most.

[1] doi:10.1016/j.isci.2026.116647 [code]
Enhancing mobile brain and body imaging: Open-source solutions for real-world research applications.
Journal: iScience
In common: 14 references, author Miguel Contreras-Altamirano
[2] doi:10.1111/psyp.70352 [code]
Preprocessing on the Go: Practices in Gait-Related Mobile EEG.
Journal: Psychophysiology
In common: EEG, 10 references
[3] doi:10.3390/s26082440 [code]
Suppressing Non-Stationary Motion Artefacts in Mobile EEG Using Generalized Eigenvalue Decomposition.
Journal: Sensors (Basel, Switzerland)
In common: EEG, 7 references
[4] doi:10.7554/elife.107088 [code]
Development of auditory and spontaneous movement responses to music over the first postnatal year.
Journal: eLife
In common: ICLabel, EEGLAB, FieldTrip, 2 other tools, EEG, 2 references
[5] doi:10.1162/imag.a.1201 [code]
All spectral frequencies of neural activity reveal semantic representation in the human anterior ventral temporal cortex.
Journal: Imaging neuroscience (Cambridge, Mass.)
In common: ICLabel, EEGLAB, FieldTrip, 2 other tools, 2 references
[6] doi:10.1093/pnasnexus/pgag220 [code]
Neural mechanisms of awareness of action.
Journal: PNAS nexus
In common: EEGLAB, Signal Processing Toolbox, Statistics and Machine Learning Toolbox, EEG, 3 references
[7] doi:10.1038/s41598-026-56070-y [code]
SSDLabeler: realistic semi-synthetic data generation for multi-label artifact classification in EEG.
Journal: Scientific reports
In common: ICLabel, EEGLAB, Signal Processing Toolbox, 1 other tool, EEG, 2 references
[8] doi:10.1523/eneuro.0316-25.2026 [code]
Neural Mechanisms of Self-Generated Action Sequences.
Journal: eNeuro
In common: EEGLAB, FieldTrip, Statistics and Machine Learning Toolbox, EEG, 3 references
[9] doi:10.1093/braincomms/fcag043 [code]
Linking movement-related beta oscillations to cortical excitability, structural damage, and fatigue in multiple sclerosis.
Journal: Brain communications
In common: EEGLAB, FieldTrip, Signal Processing Toolbox, 1 other tool, EEG, 2 references
[10] doi:10.1111/psyp.70271 [code]
Disentangling Respiratory Phase-Dependent and Phase-Independent Components of Anticipatory Cardiac Deceleration.
Journal: Psychophysiology
In common: EEGLAB, FieldTrip, Signal Processing Toolbox, 1 other tool, EEG, 2 references

Contribute

The authors of this paper can claim it, correct its record and validate its tracing map, and the maintainers of its code (its owner, or a public member of its organization) correct what it says of their repository; anyone signed in can ask for its removal. Every request goes to OSCR's own machine, which answers it; your account page follows them.

Sign in with ORCID to claim this paper as one of its authors, correct its record or validate its tracing map: when the paper's metadata lists your ORCID iD, you are recognized at once. Maintainers of its code: sign in with GitHub, then claim the repository on your account page.

Request its removal

To ask OSCR to remove this record, the copies of its authors' scripts or its tracing map, use the removal request page: signed in, you say who you are, what to remove and why, then review and confirm the request. Published rules decide every request (how).

Discussion, reproductions, activity

Discussion: questions and error reports about this paper and its code, from signed-in readers and its authors. It opens with sign-in.

Reproductions: reports from readers who ran the authors' code: what they reproduced, with which environment, commit and data. It opens with sign-in.

Activity: what happens around this paper: new versions of its record, its map's validation, discussions and reproductions. It opens with sign-in.