OSCR

Enhancing spontaneous recovery after stroke: a randomized controlled trial.

Code ↔ Paper

6 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 6 matches
  1. [1] § Materials and methods › Data processing and dependent measures › TMS-derived stimulus-response curves ↔ Neurophys/Grand_Average_SR_current.m, lines 278 to 362 · score 0.63 · TMS intensity, MEP amplitude, sigmoid, threshold, fitting, muscle · Report a mismatch
  2. [2] § Materials and methods › Data processing and dependent measures › TMS-derived stimulus-response curves ↔ Neurophys/ESPRESSo_SRcurve_current.m, lines 327 to 367 · score 0.62 · eRMT, sigmoid function, background, threshold, amplitude, fitting · Report a mismatch
  3. [3] § Materials and methods › Blinding and assessments › Stimulus-response curves ↔ Neurophys/ESPRESSo_SRcurve_current.m, lines 369 to 407 · score 0.59 · 30–100 %, MSO, muscles, intensities · Report a mismatch
  4. [4] § Materials and methods › Data processing and dependent measures › Reaching kinematics ↔ Kinematics/Script4_ConcatenationTimePoints.m, lines 302 to 409 · score 0.56 · normalized wrist velocity, wrist velocity profile, locations, height, SPARC, uniformly · Report a mismatch
  5. [5] § Materials and methods › Data processing and dependent measures › Reaching kinematics ↔ Kinematics/Script1_DataProcessingLeft.m, lines 110 to 236 · score 0.55 · Trunk tilt angles, velocity profile, height, wrist, kinematics · Report a mismatch
  6. [6] § Results › Secondary outcomes › Stimulus-response curves ↔ Neurophys/ESPRESSo_SRcurve_current.m, lines 539 to 594 · score 0.54 · stimulus response curves, MEPsum, RMT, ADM, ECR, FCR · Report a mismatch

Paper

Loaded from Europe PMC by your browser; OSCR does not store it: doi.org · Europe PMC

The paper is loaded when this pane is shown.

The authors' code

MATLAB · 680 lines · 37 KB · no license · 3 matches

  1. % All-in-one pipeline that integrates signal processing, data analysis, data formatting, and S-R curve fitting.
  2. % A Matlab sigmoid function is fitted which returns 4 parameters [minimum y value, maximum y value, S50, slope parameter k]
  3. % This code has adjustable constraints to improve the fit of the curve (e.g., y values cannot be negative).
  4. % There are also adjustable inital parameters (search 'initialparams') to give the algorithm somewhere to start.
  5. %% LOADING THE FILES
  6. % Currently this code runs one participants data at one time point.
  7. % All variables need to be cleared in between running data, the code does this in the very first line. If there's just one section you want to run
  8. % again (e.g., SR curve section with altered parameters) clear all and run again from the start.
  9. %
  10. % The code requires a signal file in .mat format. All of the matlab files are on the ESPRESSo research drive and are ready to be inputted to this code and processed.
  11. % The name of the file gets entered in the file prefix section in General Info.
  12. % The required input is the file prefix. Please enter the ESPRESSo alphanumeric code
  13. % Please make sure to exclude the three zeros at the end.
  14. % E.g., '#########_SR_L_XX##ESP'
  15. %
  16. %% MODIFIABLE ASPECTS OF THE CODE
  17. % Enter your initials in the assessor variable in the General Info section (search 'assessor').
  18. %
  19. % The second argument asks for the side that was analysed (specify as either 'P/NP').
  20. %
  21. % If the SR fit is not great the first time around, change the initial parameters (search 'initialparams' within the script to find it) to give a more accurate prediction of S50 and/or k.
  22. %
  23. % The title of the S-R curve will need to change between P/NP (search 'title')
  24. %
  25. %% SAVING THE OUTPUT FILES
  26. % The output file and SR curve image will automatically be saved in an Output File folder within the folder that you are using the data from.
  27. % The folder will have the initials of the assessor at the start of it.
  28. % Enter your initials in the assessor variable in the General Info section.
  29. %
  30. % When you press the Run button you will have the SR curve in a separate figure window and an Output structure and SlopeAtS50 (in mV/1%MSO) variable in the
  31. % workplace.
  32. % SR CURVE FIGURE
  33. % The SR curve will pop up in a separate figure. It will the plots for the 4 muscles in different colours and a legend to label the muscles.
  34. % It will have shaded regions at the bottom of the plot showing the background RMS lower and upper 95% confidence interval. The shaded regions are colour coded to match the muscle.
  35. % The x-axis is fixed from 0 to 100 and the y-axis is auto scaled.
  36. %
  37. % OUTPUT STRUCTURE IN WORKSPACE
  38. % Inside the output structure are all the variables calculated including the slope variables, I've just put a copy outside the structure
  39. % too for easy access. The output structure is what get saved for each participant.
  40. % The conventional reporting for the outputs that do not have the muscle names explicitly displayed is:
  41. % 1 = FDI
  42. % 2 = ADM
  43. % 3 = ECR
  44. % 4 = FCR
  45. % The output structure contains:
  46. % FDI, ADM, ECR, FCR structures - all the information pertaining to just the specfic muscle
  47. % Data table
  48. % State and stimulus intensity (linked i.e., state 1 = 30% MSO, state 2 = 40% MSO etc.)
  49. % BGamp = background amplitude calculated in the prestimulus window
  50. % defined in the script (-55 to -5 ms unless changed)
  51. % MEPamp = average amplitude for each state/intensity. Note the state 0
  52. % value is the average BGamplitude value. This is to help with curve
  53. % fitting to anchor the curve to a value at x = 0.
  54. % StD = standard deviation of the MEP amplitudes
  55. % RMS = background RMS calculated in the prestimulus window defined in
  56. % the script (-55 to -5 ms unless changed)
  57. % Norm2Max = MEP amplitude values normalised to the maximum MEP
  58. % amplitude value. Not being used for analysis currently but retained
  59. % it in the output just in case
  60. % SI = array of the stimulus intensities used for SR curve (i.e.,
  61. % including 0% MSO)
  62. % MEPvalues table = actual MEP amplitude values used for SR curve
  63. % SigX = array of x values plotted for the actual sigmoid function
  64. % SigY = array of y values plotted for the sigmoid function
  65. % S50 = S50 value (i.e. x value when y value is half it's maximum)
  66. % k = slope parameter value k. Used to calculate slope at S50 with the
  67. % formula Max/4*k
  68. % Slope_at_S50 = slope of the curve at S50 in mV per 10% MSO
  69. % The following tables in the Output are contain the same information as what is described in the individual muscle structures. It is just displayed in a table showing all 4 muscles at once.
  70. % MEPamplitude table
  71. % StD table
  72. % BGamp table
  73. % RMS table
  74. % RMSlowerCI = used as the lower boundary of the shaded area of RMS on
  75. % the SR curve
  76. % RMSupperCI = used as the upper boundary of the shaded area of RMS on
  77. % the SR curve
  78. % AverageRMS = one average background RMS value for each muscle across
  79. % all trials
  80. % RMS_thresholds = the lowest background rms threshold (starting at 10
  81. % microvolts) for each muscle to ensure at least 5 frames at each intensity
  82. % k = slope parameter k value for the 4 muscles
  83. % S50 = S50 value for all four muscles (i.e. x value when y value is half it's maximum)
  84. % SlopeAtS50_mV1MSO = the slope of the SR curve at S50 for all 4
  85. % muscles in mV per 1%MSO
  86. % SlopeAtS50_mV10MSO = the slope of the SR curve at S50 for all 4
  87. % muscles in mV per 10%MSO
  88. % GoodnessofFit = Goodness of fit of the curve to the data points using
  89. % total sums squared and r squared values
  90. % FrameNumber table = showing the number of frames used for each muscle
  91. % at each intensity after excluding based on background RMS. This will
  92. % always be at least 5.
  93. % TotalTrials table = showing the total number of frames for each
  94. % muscles/intensity combination before any cleaning/trimming of the
  95. % data
  96. % MEPsum table = the sum of the MEP values used to create the S-R curve
  97. % for each muscle (i.e., the sum of the average MEP amplitude at each
  98. % stimulus intensity)
  99. % Code created by Maxine Shanks and Gene Tang
  100. % Last update: Maxine Shanks 29-08-2024 with the update of checking that the
  101. % estimated RMT matches the actualy datapoints and adjusting accordingly if not
  102. %% General Info
  103. clear all;
  104. close all;
  105. assessor = 'MS' % Enter your initials
  106. n_channels = 4; % set number of channels/muscles recorded from
  107. fileprefix = '180324_SR_L_xxxxESP' % enter the name of the file to analyse (excluding the part of the filename that is in line 31 e.g., '000.mat')
  108. side = 'NP' % enter the label for the side to be analysed (e.g., L/R, D/ND, P/NP)
  109. Target_muscles_labels = {'FDI' 'ADM' 'ECR' 'FCR'}; % enter the names for the 4 muscle labels
  110. channelNames = {[side Target_muscles_labels{1}],[side Target_muscles_labels{2}],[side Target_muscles_labels{3}],[side Target_muscles_labels{4}]}; % the channel names will then become the side+channel name e.g., PFDI for paretic FDI
  111. fs = 2000; % sampling rate of the signal
  112. %% Loading data and assigning time
  113. r_data = load([fileprefix '000.mat']); % load in rest data using the fileprefix set above in General Info + the suffix i.e., '000.mat'
  114. fnames = fieldnames(r_data); % extract relevant data from imported .cfs file
  115. r_data = r_data.(fnames{1});
  116. % Create table to store relevant information in
  117. restData = struct2table(r_data.frameinfo); % put frameinfo into a table
  118. restData.points = []; % remove unwanted points column
  119. restData.sweeps = []; % remove unwanted sweeps column
  120. % Create list of times from -0.5 to 0.5 to correspond with the samples
  121. times = [];
  122. for i = 1:length(r_data.values)
  123. times(i) = i *(1/fs)-0.5; % convert times to -0.5 to 0.5 pre/post trigger
  124. end
  125. % Set background analysis window
  126. baseline_time = [-0.055, -0.005]; % use time point -50ms to -5ms to calculate background rms
  127. baseline_s = find(times >= baseline_time(1), 1):find(times <= baseline_time(2), 1, 'last'); % find times that are within the background window
  128. % Set p2p time
  129. MEP_time = [0.015, 0.06]; % setting MEP window to 15-60 ms
  130. MEP_s = find(times >= MEP_time(1), 1):find(times <= MEP_time(2), 1, 'last'); % find times that are within the p2p window
  131. % Create temporary data to work on
  132. temp_signal_data = r_data;
  133. temp_data = restData;
  134. % compute RMS/RMS amplitude/MEP amplitude/MEP latency
  135. for c = 1:n_channels % iterate through the number of channels there are (e.g., 4)
  136. for f = 1:size(temp_data.number) % iterate through the number of frames (e.g., 90)
  137. % Background RMS
  138. temp_data.rms(f,:) = rms(r_data.values(baseline_s(1,1):baseline_s(1,end),c,f)); % calculate rms in the background window set above (e.g., -50 to -5 ms)
  139. % Background amplitude
  140. temp_data.BGamp(f,:) = peak2peak(r_data.values(baseline_s(1,1):baseline_s(1,end),c,f)); % caluculate the peak2peak amplitude in the background window
  141. % MEP amplitude
  142. temp_data.MEPamp(f,:) = peak2peak(r_data.values(MEP_s(1,1):MEP_s(1,end),c,f)); % calculate the peak2peak amplitude in the p2p window set above (e.g., 0.01 to 0.06)
  143. end
  144. % Update variable names in data table
  145. temp_data.Properties.VariableNames{'rms'} = [channelNames{c} '_rms']; % creates a rms column with the data calculated above
  146. temp_data.Properties.VariableNames{'BGamp'} = [channelNames{c} '_BGamp']; % creates a Bgamp column with the data calculated above
  147. temp_data.Properties.VariableNames{'MEPamp'} = [channelNames{c} '_MEPamp']; % creates a MEPamp column with the data calculated above
  148. end
  149. % Store temporary data back into relevant variables
  150. raw_data = temp_data;
  151. raw_data.start = [];
  152. raw_data = sortrows(raw_data,'state','ascend'); % sort the 90 frames by state in ascending order (e.g., 30% - 100% MSO)
  153. rest_signal_data = temp_signal_data;
  154. n_frames = size(raw_data,1);
  155. clear baseline_s f i MEP_s rest_signal_data restData temp_data temp_signal_data % clears all the unnecessary variables from the workspace
  156. %% Cleaning the data
  157. %this section takes BGamp MEPamp and rms of each muscle and assigns them
  158. %into respective variable.
  159. % Splits the background rms, background amplitude and MEP amplitude data into the individual muscles
  160. State_index = raw_data(:,"state");
  161. FDI_temp= raw_data(:,[{'state'}, {[side 'FDI_BGamp']}, {[side 'FDI_MEPamp']}, {[side 'FDI_rms']}]);
  162. ADM_temp = raw_data(:,[{'state'}, {[side 'ADM_BGamp']}, {[side 'ADM_MEPamp']}, {[side 'ADM_rms']}]);
  163. ECR_temp = raw_data(:,[{'state'}, {[side 'ECR_BGamp']}, {[side 'ECR_MEPamp']}, {[side 'ECR_rms']}]);
  164. FCR_temp = raw_data(:,[{'state'}, {[side 'FCR_BGamp']}, {[side 'FCR_MEPamp']}, {[side 'FCR_rms']}]);
  165. var_names = {'FDI_temp', 'ADM_temp', 'ECR_temp', 'FCR_temp'}; % sets the names of the 4 muscles
  166. outliers_flags = []; % creates an array to store outlier flags. %matrix contains 4 columns each for each muscle. 1 represents the row that an outlier has been removed
  167. var_names = {'FDI_temp', 'ADM_temp', 'ECR_temp', 'FCR_temp'};
  168. muscle_names = {'FDI', 'ADM', 'ECR', 'FCR'};
  169. remaining_trials_percentage = 0.3;
  170. threshold = 0.5;
  171. rms_threshold_default = 0.010; % Initial RMS threshold in mV
  172. rms_thresholds = rms_threshold_default * ones(1, numel(var_names)); % Initialize RMS thresholds for each muscle
  173. frames_per_intensity = zeros(9, numel(var_names)); % Initialize the array to store frame counts
  174. total_trials = ones(9, numel(var_names));
  175. outliers_flags = false(size(eval(var_names{1}), 1), numel(var_names)); % Initialize the array to document outliers
  176. original_data = struct(); % Create a structure to store the original data
  177. for vx = 1:numel(var_names) % cycle through the muscles
  178. original_data.(var_names{vx}) = eval(var_names{vx}); % grab the data for the muscle
  179. end
  180. % Loop through all states and make sure there are at least the minimum number of frames remaining after the exclusion based on background RMS.
  181. % The RMS criterion increases by 0.005 mV each iteration if there's not enough frames across all intensities. This process works for each muscle
  182. % separately
  183. while any(frames_per_intensity < threshold*total_trials, 'all')%remaining_trials_percentage < 0.5)
  184. for vx = 1:numel(var_names) % For each muscle
  185. var_value = original_data.(var_names{vx}); % Restore the original _temp data;
  186. rms_threshold = rms_thresholds(vx);
  187. for RMS_col = 4 % For the RMS column
  188. outlier_index = (var_value{:,RMS_col}) > rms_threshold;
  189. outliers_flags(:, vx) = outliers_flags(:, vx) | outlier_index; % Store the outlier index into a column corresponding to the muscle
  190. var_value{outlier_index, 2:end} = NaN; % Turn all the rows with outliers into NaNs
  191. eval([var_names{vx} ' = var_value']);
  192. end
  193. end
  194. for vx = 1:numel(var_names) % For each muscle
  195. var_value = eval(var_names{vx}); % Get the muscle's _temp table
  196. increase_threshold = true; % Flag to indicate whether threshold needs to be increased
  197. for state = 1:9 % For each state
  198. valid_rows = sum(~isnan(var_value{var_value.state == state, 2}), 2); % Count valid rows for the state and store as 1's. Non valid frames are 0.
  199. total_frames = sum(valid_rows); % total number of valid frames
  200. frames_per_intensity(state,vx) = total_frames;
  201. total_trials(state, vx) = sum(var_value.state == state);
  202. min_trials_required = ceil(total_trials(state,vx)*0.5);
  203. remaining_trials_percentage = total_frames/total_trials(state,vx);
  204. if remaining_trials_percentage < 0.5 && increase_threshold
  205. rms_thresholds(vx) = rms_thresholds(vx) + 0.005;
  206. increase_threshold = false;
  207. end
  208. end
  209. end
  210. end
  211. avg_rms_values = zeros(1, numel(var_names)); % Initialize the array to store average RMS values
  212. for vx = 1:numel(var_names) % For each muscle
  213. var_value = eval(var_names{vx}); % Get the muscle's _temp table
  214. rms_column = var_value{:, 4}; % Get the RMS values from column 4
  215. % Calculate the average RMS value excluding NaNs
  216. avg_rms_values(vx) = mean(rms_column, 'omitnan');
  217. end
  218. outliers_flags = array2table(outliers_flags, 'VariableNames', {[side 'FDI'], [side 'ADM'], [side 'ECR'], [side 'FCR']}); % create a table for outliers_flags
  219. data = horzcat(State_index, FDI_temp(:,2:end), ADM_temp(:,2:end), ECR_temp(:,2:end), FCR_temp(:,2:end)); % concatenate all tables back into 1 file called rest data
  220. clear outlier_index outliers_flags RMS_col State_index vx var_value var_names increase_threshold rms_column rms_threshold_default rms_threhsold total_frames valid_rows state min_frames_per_intensity
  221. %% Calculate percentage of frames retained
  222. frames_retained = sum(sum(frames_per_intensity)); %add all elements of the array
  223. percent_retained = (frames_retained/(n_frames*4))*100 %calculte percentage of frames retained out of 360 total frames
  224. %% Calculate averages of BGamp/MEPamp/RMS
  225. BGamp = table; %create table for each values
  226. MEPamp = table;
  227. RMS = table;
  228. StD = table;
  229. SI = [30;40;50;60;65;70;80;90;100]; % set the intensities
  230. BGamp.Intensities = SI; % assign the above intensities to the following tables
  231. MEPamp.Intensities = SI;
  232. RMS.Intensities = SI;
  233. StD.Intensities = SI;
  234. n_states = 9; % number of states
  235. n_muscles = 4; % number of muscles
  236. for c = 1:n_channels % cycle through each of the muscles/channels
  237. var = [channelNames{c} '_BGamp']; % the variable is set as the BGamp column
  238. tidx = find(string(data.Properties.VariableNames) == var); % find the BGamp column
  239. for s = 1:n_states % for each of the states
  240. for f = 1:size(data(data.state==s,:),1) % for each of the frames
  241. state_data = data(data.state==s,:);
  242. end
  243. BGav(s) = mean(table2array(data(data.state==s, tidx)), 'omitnan'); % calculate the mean of the BGamplitude for each state, omitting any NaN values
  244. MEPav(s) = mean(table2array(data(data.state==s, tidx+1)), 'omitnan'); % calculate the mean of the MEPamplitude for each state, omitting any NaN values
  245. RMSav(s) = mean(table2array(data(data.state==s, tidx+2)), 'omitnan'); % calculate the mean of the RMS for each state, omitting any NaN values
  246. Stdev(s) = std(table2array(data(data.state==s, tidx+1)), 'omitnan'); % calculate the standard deviation of the MEPamplitude values for each state, omitting any NaN values
  247. end
  248. BGamp.([channelNames{c}]) = BGav([1:9]).'; % create a table for the background amplitude with the average value for each muscle at each intensity
  249. MEPamp.([channelNames{c}]) = MEPav([1:9]).'; % create a table for the MEP amplitude with the average value for each muscle at each intensity
  250. RMS.([channelNames{c}]) = RMSav([1:9]).'; % create a table for the RMS with the average value for each muscle at each intensity
  251. StD.([channelNames{c}]) = Stdev([1:9]).'; % create a table for the St Dev with the average value for each muscle at each intensity
  252. end
  253. clear BGav c f MEP_time MEPav n_channels n_muscles n_states RMSav s state_data Stdev tidx var
  254. %% Reformat the data for S-R curve plotting
  255. %this section takes BGamp MEPamp and RMS values from the previous section
  256. %and assign them into their respective muscles (table format)
  257. FDI = table; ADM = table; ECR = table; FCR = table; % create a table for each muscle
  258. Target_muscles_table = {FDI ADM ECR FCR};
  259. State = [1;2;3;4;5;6;7;8;9]; % set the state names
  260. SI = [30;40;50;60;65;70;80;90;100]; % set the SI column
  261. for mx = 1:length(Target_muscles_labels) % for each of the 4 muscles
  262. Target_muscles_table{mx}.State = State; % assigning state array to the state column
  263. Target_muscles_table{mx}.SI = SI; % assigning intensity to the SI column
  264. Target_muscles_table{mx}.BGamp = table2array(BGamp(:,[side,Target_muscles_labels{mx}])); % assinging BGamp to BGamp column
  265. Target_muscles_table{mx}.MEPamp = table2array(MEPamp(:,[side,Target_muscles_labels{mx}])); % assinging MEPamp to MEPamp column
  266. Target_muscles_table{mx}.StD = table2array(StD(:,[side,Target_muscles_labels{mx}])); % assigning StDev to StDev column
  267. Target_muscles_table{mx}.RMS = table2array(RMS(:,[side,Target_muscles_labels{mx}])); % assinging RMS to RMS column
  268. norm2max = [];
  269. for sx = 1:9
  270. norm2max(sx,1) = table2array(Target_muscles_table{mx}(sx,4))/(max(Target_muscles_table{mx}{:,4})); % creating a normalised to max value (currently not using)
  271. end
  272. Target_muscles_table{mx}.Norm2Max = norm2max; % adding normalised values to the table (currently not using)
  273. end
  274. clear sx State norm2max mx
  275. %% Plot the points and add function stats to the table
  276. %This section plots x = SI, y = MEPamp and fit a sigmoid function.
  277. colors = {'#A2142F', '#0072BD', '#77AC30', '#D95319'}; % specify colour of the plot
  278. Target_muscles_labels = {'FDI' 'ADM' 'ECR' 'FCR'};
  279. % Temporarily storing S50 and slope of all 4 muscles
  280. SIarray = zeros(4,10); % array to store the SI data points
  281. MEPampArray = zeros(4,10); % array to store the MEPamp data points
  282. SigXarray = zeros(4,101); % array to store the sigmoid x values
  283. SigYarray = zeros(4,101); % array to store the sigmoid y values
  284. S50 = []; % array to store S50
  285. k = []; % array to store the slope parameter
  286. dy = {}; % cell to contain the derivative of the sigmoid functions
  287. Max = []; % storing max slope from the derivative
  288. RMSmean = zeros(10,4); % array to store the RMS mean
  289. RMSupperCI = zeros(10,4); % array to store the RMS upper confidence interval
  290. RMSlowerCI = zeros(10,4); % array to store the RMS lower confidence interval
  291. GoodnessofFit = zeros(1,4); % array to store the goodness of fit of the SR curve
  292. estimated_threshold = zeros(1,4); % array to store the estimated RMT from the sigmoid curve
  293. adj_estimated_threshold = zeros(1,4); % array to store the adjusted estimated RMT from the sigmoid curve
  294. new_adj_estimated_threshold = zeros(1,4); % array to store the new adjusted estimated RMT from the actual datapoints if the eRMT from the sigmoid doesn't make sense
  295. fdi_bgampstd = std(table2array(BGamp(:,2))); % finding the standard deviation of the background amplitude for FDI
  296. adm_bgampstd = std(table2array(BGamp(:,3))); % finding the standard deviation of the background amplitude for ADM
  297. ecr_bgampstd = std(table2array(BGamp(:,4))); % finding the standard deviation of the background amplitude for ECR
  298. fcr_bgampstd = std(table2array(BGamp(:,5))); % finding the standard deviation of the background amplitude for FCR
  299. newRowStD = [0,fdi_bgampstd,adm_bgampstd,ecr_bgampstd,fcr_bgampstd]; % creating a new array with the 4 st deviation values
  300. newRowStD = array2table(newRowStD); % convert the new array to a table
  301. newRowStD.Properties.VariableNames = {'Intensities',channelNames{1,1},channelNames{1,2},channelNames{1,3},channelNames{1,4}}; % Naming the variables in the table
  302. StD = [newRowStD; StD]; % adding the new standard deviation table to the existing (i.e., adding in a 0% MSO row)
  303. for ii = 1:numel(Target_muscles_labels) %loop through each muscle
  304. values = Target_muscles_table{ii};
  305. bgampav = mean(table2array(values(:,3))); % finding the mean background amplitude value
  306. newRowAmp = [0,0,0,bgampav,table2array(StD(1,ii+1)),0,0]; % creating a new array with the mean background amplitude
  307. newRowAmp = array2table(newRowAmp); % convert the new array to a table
  308. newRowAmp.Properties.VariableNames = {'State', 'SI', 'BGamp', 'MEPamp', 'StD', 'RMS', 'Norm2Max'}; % Naming the variables in the table
  309. values = [newRowAmp; values]; % adding the new background amplitude row to the existing table (i.e., 0% MSO row)
  310. % Calculating the 95% CI for RMS from the state 1 (i.e., 30% MSO)
  311. % for the values to use at 0% MSO on the graph
  312. muscle_name = Target_muscles_labels{ii}; % Get muscle name
  313. muscle_temp_table_name = [muscle_name '_temp']; % Construct the table name
  314. muscle_temp_table = eval(muscle_temp_table_name); % access the specific muscle's _temp table
  315. alpha = 0.05; % significance level for 95% confidence level
  316. valid_rows = sum(~isnan(muscle_temp_table{muscle_temp_table.state == 1, 4}), 2); % Get the valid rows of the RMS column for state 1 (30% MSO)
  317. n = sum(valid_rows); % number of valid rows
  318. mean_RMS_state1 = mean(muscle_temp_table{muscle_temp_table.state == 1,4}, 'omitnan'); % find the mean, omitting NaN values
  319. std_RMS_state1 = std(muscle_temp_table{muscle_temp_table.state == 1,4}, 'omitnan'); % find the standard deviation, excluding NaN values
  320. t_score = tinv(1-alpha/2, n-1);
  321. margin_of_error = t_score*(std_RMS_state1/sqrt(n));
  322. CI_lower_BGamp = mean_RMS_state1 - margin_of_error;
  323. CI_upper_BGamp = mean_RMS_state1 + margin_of_error;
  324. RMSmean(1,ii) = mean_RMS_state1; % storing the mean RMS value
  325. RMSlowerCI(1,ii) = CI_lower_BGamp; % storing the RMS lower CI
  326. RMSupperCI(1,ii) = CI_upper_BGamp; % storing the RMS upper CI
  327. % Calculating the 95% Confidence Interval for RMS for the states
  328. % (i.e., 30 - 100% MSO). Same process as above but cycling through
  329. % the states
  330. for s = 1:numel(unique(muscle_temp_table.state))
  331. rows_of_interest = muscle_temp_table.state == s;
  332. valid_rows = sum(~isnan(muscle_temp_table{rows_of_interest, 4}), 2);
  333. n = sum(valid_rows);
  334. mean_rms_for_state = mean(muscle_temp_table{rows_of_interest, 4}, 'omitnan');
  335. std_rms_for_state = std(muscle_temp_table{rows_of_interest, 4}, 'omitnan');
  336. t_score = tinv(1-alpha/2, n-1);
  337. margin_of_error = t_score*(std_rms_for_state/sqrt(n));
  338. CI_lower = mean_rms_for_state - margin_of_error;
  339. CI_upper = mean_rms_for_state + margin_of_error;
  340. RMSmean(s+1,ii) = mean_rms_for_state;
  341. RMSlowerCI(s+1,ii) = CI_lower;
  342. RMSupperCI(s+1,ii) = CI_upper;
  343. end
  344. % Assigning the x and y values
  345. x = table2array(values(:,"SI")); %assiging stim intensity as X
  346. y = table2array(values(:,"MEPamp")); %assigning MEPamp as y
  347. % Define sigmoid function
  348. sigmoid = @(params, x) params(1) + (params(2) - params(1)) ./ (1 + exp(-(x - params(3)) / params(4))); % sigmoid curve equation
  349. % Initial parameter guess [min y value, max y value, S50, k] A good
  350. % starting value for S50 is 65 and k of 5
  351. initialParams = [min(y),max(y),65,5]; % Having the 0 for min(y) is ensure min(y) is non-negative. These initial parameters can be changed to refine the fit.
  352. % Fit the sigmoid function to the data using lsqcurvefit
  353. fitParams = lsqcurvefit(sigmoid, initialParams, x, y);
  354. % Additional fmincon optimization for parameter constraints
  355. lb = [0,0,20, 1]; % lower bounds for parameters
  356. ub = [min(y),15,90,20]; % upper bounds for parameters
  357. options = optimoptions('fmincon', 'Display', 'off'); % Set optimization options
  358. fitParams_constrained = fmincon(@(params)sum((y - sigmoid(params, x)).^2), fitParams, [], [], [], [], lb, ub, [], options);
  359. % Generate curve using the fitted parameters
  360. curveX = 0:100;
  361. curveY_constrained = sigmoid(fitParams_constrained, curveX);
  362. % Plot data and fitted curve for the current muscle
  363. plot(x, y, 'Color', colors{1,ii}, 'Marker', '.', 'LineStyle', 'none', 'MarkerSize', 10,'HandleVisibility','off'); % Plot data for the current muscle
  364. hold on;
  365. plot(curveX, curveY_constrained, 'Color', colors{1,ii}, 'DisplayName', Target_muscles_labels{ii}, 'LineWidth', 2);
  366. hold off;
  367. Target_muscles_table{ii} = values;
  368. %Store S50 and slope for each muscle in a 1x4 array
  369. SIarray(ii,:) = x;
  370. MEPampArray(ii,:) = y;
  371. SigXarray(ii,:) = curveX;
  372. SigYarray(ii,:) = curveY_constrained;
  373. S50(1,ii) = fitParams_constrained(1,3); % S50 the 3rd value from the params output
  374. k(1,ii) = (fitParams_constrained(1,4)); %divide by 10 to adjust for magnitude scaling %no longer used
  375. SlopeAtS50(1,ii) = fitParams_constrained(1,2)/(4*fitParams_constrained(1,4)); % equation for getting slope parameter (k) into slope at S50. Equation = Max/4*k
  376. %plot error bars
  377. % hold on
  378. % errorbar(x,y,table2array(StD(:,ii+1)));
  379. % eb = get(gca, 'Children');
  380. % set(eb(1), 'Color', colors{ii}, 'DisplayName', Target_muscles_labels{ii},'Linewidth', 1,'HandleVisibility','off','Linestyle','none');
  381. % Convert hexidecimal colour to RGB triplet
  382. hex_color = colors{ii};
  383. color_rgb = sscanf(hex_color(2:end), '%2x%2x%2x', [1 3])/255;
  384. % Plot the shaded region for the 95% CI for RMS
  385. hold on;
  386. fill([x; flipud(x)], [RMSlowerCI(:,ii); flipud(RMSupperCI(:,ii))], color_rgb, 'FaceAlpha', 0.5, 'EdgeColor', 'none','DisplayName', "Background RMS");
  387. % Calculating TSS
  388. % Calculate the mean of the y values (actual data points)
  389. meanY = mean(y);
  390. % Calculate the total sum of squares
  391. tss = sum((y-meanY).^2);
  392. % Matching the SigYarray values to the actual data points
  393. indices = [1, 30, 40, 50, 60, 65, 70, 80, 90, 100];
  394. fittedValues = SigYarray(ii,indices);
  395. % Calculate the residuals
  396. residuals = y.' - fittedValues;
  397. % Calculate the sum of squares of the residuals (sum of squared differences between fitted curve and data points)
  398. ssr = sum((residuals).^2);
  399. % Calculate the coefficient of determination (R-squared value)
  400. rSquared = 1 - (ssr/tss);
  401. GoodnessofFit(1,ii) = rSquared;
  402. % Identify the x value when the y value first goes above 0.05 for 3 data points
  403. y_threshold = 0.05; % set the MEP amplitude value in mV for the threshold
  404. consecutive_count = 0;
  405. for j = 1:numel(SigXarray(1,:)) % cycle through the stimulus intensities in the Sigmoid curve fit
  406. if SigYarray(ii,j) > y_threshold % if the MEP amplitude value from the sigmoid curve data points is above the threshold add one value to the consecutive count
  407. consecutive_count = consecutive_count + 1;
  408. if consecutive_count == 3 % once the consecutive count = 3
  409. estimated_threshold(1,ii) = SigXarray(ii,j-(consecutive_count-1)); % go back to the first x value when y > 0.05 and put that in the estimated_threshold variable
  410. break; % Stop searching when you find the x value
  411. end
  412. else
  413. consecutive_count = 0; % Reset consecutive count if y is below the threshold
  414. end
  415. end
  416. % Check if the threshold was not reached in the loop
  417. if consecutive_count < 3
  418. adj_estimated_threshold(1,ii) = 101; % Set to 101 if threshold is not reached
  419. end
  420. % Identify the x value when the y value first goes above 0.05 for 3 data points
  421. adj_y_threshold = mean(SigYarray(ii,1:5)) + 0.05; % set the MEP amplitude value in mV for the threshold
  422. consecutive_count2 = 0;
  423. for j = 1:numel(SigXarray(1,:)) % cycle through the stimulus intensities in the Sigmoid curve fit
  424. if SigYarray(ii,j) > adj_y_threshold % if the MEP amplitude value from the sigmoid curve data points is above the threshold add one value to the consecutive count
  425. consecutive_count2 = consecutive_count2 + 1;
  426. if consecutive_count2 == 3 % once the consecutive count = 3
  427. adj_estimated_threshold(1,ii) = SigXarray(ii,j-(consecutive_count2-1)); % go back to the first x value when y > 0.05 and put that in the estimated_threshold variable
  428. break; % Stop searching when you find the x value
  429. end
  430. else
  431. consecutive_count2 = 0; % Reset consecutive count if y is below the threshold
  432. end
  433. end
  434. % Check if the threshold was not reached in the loop
  435. if consecutive_count2 < 3
  436. adj_estimated_threshold(1,ii) = 101; % Set to 101 if threshold is not reached
  437. end
  438. % Find the closest x value upwards
  439. closest_x_idx = find(x >= adj_estimated_threshold(1,ii),1);
  440. closest_x = x(closest_x_idx);
  441. % Get the corresponding y value
  442. corresponding_y = y(closest_x_idx);
  443. % Compare y value with adj_y_threshold
  444. if corresponding_y < adj_y_threshold
  445. % Move upwards until a y value is found that meets the criteria
  446. for t = closest_x_idx+1:length(x)
  447. if y(t) >= adj_y_threshold
  448. closest_x_idx = t;
  449. closest_x = x(t); % update closest x value to the new one
  450. break; % stop the loop once the condition is met
  451. end
  452. end
  453. % Take the value one lower than closest_x
  454. new_eRMT = x(closest_x_idx - 1);
  455. new_adj_estimated_threshold(1,ii) = new_eRMT;
  456. else
  457. new_adj_estimated_threshold(1,ii) = adj_estimated_threshold(1,ii);
  458. end
  459. % Ensure the minimum value of new_eRMT is not < 20% MSO
  460. new_adj_estimated_threshold(1,ii) = max(new_adj_estimated_threshold(1,ii), 20);
  461. end
  462. % Creating the RMS CI values from S-R curve into a table
  463. RMSlowerCI = array2table(RMSlowerCI); % convert the new array to a table
  464. Intensities = [0;30;40;50;60;65;70;80;90;100];
  465. RMSlowerCI = addvars(RMSlowerCI, Intensities, 'Before', 1);
  466. RMSlowerCI.Properties.VariableNames = {'Intensities',channelNames{1,1},channelNames{1,2},channelNames{1,3},channelNames{1,4}}; % Naming the variables in the table
  467. RMSupperCI = array2table(RMSupperCI);
  468. RMSupperCI = addvars(RMSupperCI, Intensities, 'Before', 1);
  469. RMSupperCI.Properties.VariableNames = {'Intensities',channelNames{1,1},channelNames{1,2},channelNames{1,3},channelNames{1,4}}; % Naming the variables in the table
  470. RMSmean = array2table(RMSmean);
  471. RMSmean = addvars(RMSmean, Intensities, 'Before', 1);
  472. frames_per_intensity = array2table(frames_per_intensity);
  473. frames_per_intensity = addvars(frames_per_intensity, SI, 'Before', 1);
  474. frames_per_intensity.Properties.VariableNames = {'Intensities',channelNames{1,1},channelNames{1,2},channelNames{1,3},channelNames{1,4}}; % Naming the variables in the table
  475. total_trials = array2table(total_trials);
  476. total_trials = addvars(total_trials, SI, 'Before', 1);
  477. total_trials.Properties.VariableNames = {'Intensities',channelNames{1,1},channelNames{1,2},channelNames{1,3},channelNames{1,4}};
  478. % Add labels and title for the plot
  479. xlim([0 100]); %set x-axis limit from 0 - 100
  480. ylim([0,6]); %auto set the y-axis limit
  481. xticks(0:10:100);
  482. yticks(0:1:6);
  483. xlabel('Stimulus intensity (%MSO)');
  484. ylabel('Evoked Motor Response (mV)');
  485. %title('Non-Paretic Stimulus-Response Curve'); %label this NP/P as required
  486. title(side); %NP/P
  487. legend('show','Location','northwest'); % determining the location of the legend
  488. box off; % removing the box around the graph
  489. %As all tables are stored in one array, here we breaks them back into 4 separate variables
  490. FDI = Target_muscles_table{1}; ADM = Target_muscles_table{2}; ECR = Target_muscles_table{3}; FCR = Target_muscles_table{4};
  491. % Combining the MEP data from the S-R curve into a table
  492. MEPamplitude = table;
  493. MEPamplitude.Intensities = Intensities;
  494. MEPamplitude.FDI = FDI.MEPamp
  495. MEPamplitude.ADM = ADM.MEPamp
  496. MEPamplitude.ECR = ECR.MEPamp
  497. MEPamplitude.FCR = FCR.MEPamp
  498. MEPsum = zeros(1,4);
  499. MEPadjusted = MEPamplitude;
  500. for muscle = 1:4;
  501. subvalue = MEPadjusted(1,muscle+1)
  502. m = table2array(MEPadjusted(:,muscle+1))
  503. m = m-table2array(subvalue(1,1))
  504. MEPadjusted(:,muscle+1) = array2table(m)
  505. MEPsum(1,muscle) = sum(table2array(MEPadjusted(:,muscle+1)))
  506. end
  507. clear adm_bgampstd bgampav colors curveX curveY dy eb ecr_bgampstd fcr_bgampstd fdi_bgampstd fitParams ii initialParams newRowAmp newRowStD sigmoid ADM_temp ECR_temp FCR_temp FDI_temp alpha CI_lower CI_lower_BGamp CI_upper CI_upper_BGamp color_rgb hex_color margin_of_error mean_rms_for_state mean_RMS_state1 MEPamp MEPampArray muscle_name muscle_temp_table muscle_temp_table_name n rows_of_interest s SI std_rms_for_state std_RMS_state1 t_score valid_rows
  508. %% Calculating other stats and put them into on struct
  509. %this section take the data from the tables and calculate k, meany, df,
  510. %SEy, TSS, Critical T and CI. It groups these statistics together with the
  511. %corresponding table (for each muscle), storing them in structs.
  512. FDI_All = []; ADM_All = []; ECR_All = []; FCR_All = []; %create structs
  513. All = {FDI_All, ADM_All, ECR_All, FCR_All}; %store structs in one cell for looping
  514. variables = {FDI, ADM, ECR, FCR};
  515. for i = 1:length(variables)
  516. current_variable = variables{i};
  517. current_all = All{i};
  518. current_all.Data = current_variable;
  519. current_all.SI = SIarray(i,:);
  520. current_all.MEPvalues = MEPamplitude(:,i+1);
  521. current_all.SigX = SigXarray(i,:); % X values
  522. current_all.SigY = SigYarray(i,:); % Y values from the sigmoid fit
  523. current_all.S50 = S50(1,i); % assigning S50
  524. current_all.k = k(1,i); % k parameter is the 4th value in the parameter output from the sigmoid function
  525. current_all.Slope_at_S50 =SlopeAtS50(1,i)*10; %assigning slope at maximum in mV per 10% MSO
  526. All{i} = current_all; %assigning the modified struct back into the cell
  527. end
  528. %breaks the cell back into 4 separate variables
  529. FDI = All{1};
  530. ADM = All{2};
  531. ECR = All{3};
  532. FCR = All{4};
  533. % Adding the variables you want saved to the Output file
  534. Output.FDI = FDI; % All FDI related info
  535. Output.ADM = ADM; % All ADM related info
  536. Output.ECR = ECR; % All ECR related info
  537. Output.FCR = FCR; % All FCR related info
  538. Output.MEPamplitude = MEPamplitude; % MEPamp table across intensities and muscles
  539. Output.StD = StD; % StDev table across intensities and muscles
  540. Output.BGamp = BGamp; % BGamp table across intensities and muscles
  541. Output.RMS = RMSmean; % RMS table across intensities and muscles
  542. Output.RMSlowerCI = RMSlowerCI; % RMS confidence interval table across intensities and muscles
  543. Output.RMSupperCI = RMSupperCI; % RMS confidence interval table across intensities and muscles
  544. Output.AverageRMS = avg_rms_values; % average rms per muscle
  545. Output.RMS_thresholds = rms_thresholds.'; % the lowesr rms cut off (in 5 microvolt increments) value required for each muscle to include at least 5 frames per intensity
  546. Output.k = k.' % adding the k values to the output
  547. Output.S50 = S50.'; % S50 values for the four muscles
  548. Output.SlopeAtS50_mV1MSO = SlopeAtS50.'; % Slope at S50 in mV/1%MSO for the 4 muscles
  549. Output.SlopeAtS50_mV10MSO = SlopeAtS50*10.'; % Slope at S50 in mV/10%MSO for the 4 muscles
  550. Output.GoodnessofFit = GoodnessofFit.'; % Goodness of fit of the SR curve for the 4 muscles
  551. Output.FrameNumber = frames_per_intensity; % Number of frames per intensity per muscle included for analysis
  552. Output.TotalTrials = total_trials;
  553. Output.MEPsum = MEPsum;
  554. Output.Adjusted_MEPamplitude = MEPadjusted;
  555. Output.estimated_threshold = estimated_threshold;
  556. Output.adj_estimated_threshold = adj_estimated_threshold;
  557. Output.new_adj_estimated_threshold = new_adj_estimated_threshold;
  558. Output.percent_retained = percent_retained;
  559. clear ADM_All baseline_time current_variable ECR_All FCR_All FDI_All i
  560. %% Saving the output file and SR curve figure
  561. % Get the current working directory (where the script is located)
  562. scriptFolder = pwd;
  563. % Define the subfolder for output files
  564. outputSubfolder = strcat(assessor, '_Output Files')
  565. % Create the full path for the output subfolder
  566. outputFolder = fullfile(scriptFolder, outputSubfolder);
  567. % Make sure the output subfolder exists
  568. if ~exist(outputFolder, 'dir')
  569. mkdir(outputFolder);
  570. end
  571. % Save the analysis Output
  572. filename = strcat(fileprefix, 'output', '.mat');
  573. fullpath = fullfile(outputFolder, filename);
  574. save(fullpath, 'Output');
  575. % Save the figure
  576. figureFilename = strcat(fileprefix, '_SR Curve.png');
  577. figureFullPath = fullfile(outputFolder, figureFilename);
  578. saveas(gcf, figureFullPath);
  579. % % clearvars -except Output SlopeAtS50 MEPsum

ESPRESSo_SRcurve_current.m at commit e50e60b, no license · at the source

Overview

Authors: Winston D Byblow1,2, Maxine J Shanks1,2, Benjamin Scrivener2,3, Laura Duval1,2, Cathy M Stinear2,3, Arier Lee4, P Alan Barber2,3, Patricia Colle3, April Ren1, John Cirillo5, Ahmet Arac6, Naveed Ejaz7, Michelle Chan-Cortes7, Gangadhar Garipelli7, Tomoko Kitago8, John W Krakauer9,10,11,12
  1. School of Exercise, Sport and Rehabilitation Sciences, University of Auckland, Auckland 1142, New Zealand
  2. Centre for Brain Research, University of Auckland, Auckland 1142, New Zealand
  3. Department of Medicine, University of Auckland, Auckland 1142, New Zealand
  4. Department of Epidemiology and Biostatistics, University of Auckland, Auckland 1142, New Zealand
  5. Discipline of Physiology, School of Biomedicine, University of Adelaide, Adelaide 5000, Australia
  6. Department of Neurology, David Geffen School of Medicine, UCLA, Los Angeles, CA 90095, USA
  7. MindMaze SA, Lausanne 1006, Switzerland
  8. Department of Neurology, Westchester Medical Center, NewYork Medical College, Valhalla, NY 10595, USA
  9. Department of Neurology, Johns Hopkins University, Baltimore, MD 21287, USA
  10. Department of Neuroscience, Johns Hopkins University, Baltimore, MD 21287, USA
  11. Department of Physical Medicine and Rehabilitation, Johns Hopkins University, Baltimore, MD 21287, USA
  12. The Santa Fe Institute, Santa Fe, NM 87501, USA
Institutions: University of Auckland (New Zealand); The University of Adelaide (Australia); University of California, Los Angeles (United States); Westchester Medical Center (United States); New York Medical College (United States); Santa Fe Institute (United States); Johns Hopkins University (United States)
Journal: Brain communications, volume 8, issue 2, article fcag057
Dates: received 15 October 2025; accepted 21 February 2026; published online 28 March 2026
Type: Research article · Language: English
License: CC BY
Identifiers: DOI 10.1093/braincomms/fcag057 · PMID 42022291 · PMCID PMC13098183 · OpenAlex W7142311016
Open access: gold, a free copy (OpenAlex)
Status: code verified (how the code is found and verified)
Categories: other (modality), human (organism), stroke (population), clinical / translational (subfield)
Methods: Statistics, Machine learning, Connectivity, Physiology & signal measures
Keywords: stroke rehabilitation, spontaneous biological recovery, human, upper limb, transcranial magnetic stimulation
Journal subjects: Clinical Trial
Topic: Stroke Rehabilitation and Recovery (Rehabilitation, Medicine), according to OpenAlex
Citations: cited by 1 paper (Europe PMC); 67 references in the paper

Abstract

This Phase IIa biomarker-guided stroke rehabilitation trial aimed to determine if a 3-week programme of high-dose, high-intensity (HDHI) virtual exploratory movement (VEM) using MindPod Dolphin could improve upper limb recovery and outcomes early after stroke. Sixty-four participants were randomized into VEM (n = 31) or conventional therapy (CoT) groups (n = 33) and began intervention within 2 weeks of stroke. Participants were given 90 min of therapist time per weekday for 3 weeks to undertake intensive upper limb therapy over and above their usual customary care. Outcomes were obtained immediately post-intervention, 3 and 6 months post-stroke. The primary endpoint was change in Action Research Arm Test (ARAT) score between baseline and 3 months post-stroke. Secondary outcomes were Fugl-Meyer assessment (FM-UE), hand dexterity, reaching kinematics and transcranial magnetic stimulation-derived measures across post-intervention time points up to 6 months. Data from all participants were analysed for intention to treat (ITT), while 54 met the weekly target of active therapy minutes, permitting inclusion in a per protocol (PP) analysis. The average weekly time on task increased over the intervention period, with progressively greater distances of arm movements (VEM) or task repetitions (CoT) made each week. Still, during the final week of intervention, participants analysed PP spent an average of only 50 (VEM)—66% (CoT) of the extra time available on task. For the ITT analysis, there was no effect of group for the primary endpoint. For the PP analysis, there was no effect of group or group by time interaction for any secondary outcome measure. An expected effect of time was observed indicative of recovery from impairment (mean ΔFM at 6 months = 23 points), increased activity capacity (mean ΔARAT at 6 months = 31 points) and improved secondary neuroscientific measures. PP participants from both groups were compared with an historical cohort matched for baseline age, stroke severity and impairment who only received usual customary care. Despite a 3-fold difference in active upper limb therapy minutes there were no differences in the 3-month ARAT or ΔFM. Indeed, the recovery in FM-UE at 3 months was proportional to baseline impairment, suggesting that the gains could be largely attributable to spontaneous biological recovery. While patient-related factors limited the dose of therapy achievable in this trial, clinician and service-related factors would also need to be overcome in routine clinical practice, to deliver equivalent doses at this early phase. It may be necessary to forego HDHI therapy until the late sub-acute phase.

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 6 matches between paragraphs and lines of code.

wbyblow/ESPRESSo

License: none: the authors keep all their rights
State: the link answers, verified on 30 September 2026
Evidence: files inventoried
Commit: e50e60ba814b5ec36b498e42642b5332857287a1, 28 January 2026
Languages: MATLAB (10), Python (1)
Size: 13 files, 11 scripts
Software Heritage: not archived
Found in: “Data availability”
Holds: README
Not found: license file, CITATION.cff, environment file, tests, continuous integration, documentation
Tools: Statistics and Machine Learning Toolbox (4 files), Optimization Toolbox (2 files), NumPy (1 file), pandas (1 file)
Availability: 1 check, the latest on 30 September 2026: the link answers
  • 30 September 2026: the link answers
12 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;
  • 11 scripts, each with its path and the digest of its content;
  • 6 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 from the corresponding author, upon reasonable request. The code used to analyse neuroscientific data can be found here https://github.com/wbyblow/ESPRESSo.git.

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 1, 30 September 2026: the first record

Recorded: type, language, journal, volume, issue, pages, dates, 16 authors, 5 keywords, 1 funder, 64 references.

Cite

This paper

Byblow, W. D., Shanks, M. J., Scrivener, B., Duval, L., Stinear, C. M., Lee, A., Barber, P. A., Colle, P., Ren, A., Cirillo, J., Arac, A., Ejaz, N., Chan-Cortes, M., Garipelli, G., Kitago, T., & Krakauer, J. W. (2026). Enhancing spontaneous recovery after stroke: a randomized controlled trial. Brain communications, 8(2), fcag057. https://doi.org/10.1093/braincomms/fcag057

BibTeX

@article{byblow2026enhancing,
author = {Byblow, Winston D and Shanks, Maxine J and Scrivener, Benjamin and Duval, Laura and Stinear, Cathy M and Lee, Arier and Barber, P Alan and Colle, Patricia and Ren, April and Cirillo, John and Arac, Ahmet and Ejaz, Naveed and Chan-Cortes, Michelle and Garipelli, Gangadhar and Kitago, Tomoko and Krakauer, John W},
title = {{Enhancing spontaneous recovery after stroke: a randomized controlled trial}},
journal = {Brain communications},
year = {2026},
month = mar,
volume = {8},
number = {2},
pages = {fcag057},
publisher = {Oxford University Press},
issn = {2632-1297},
doi = {10.1093/braincomms/fcag057},
url = {https://doi.org/10.1093/braincomms/fcag057},
pmid = {42022291},
pmcid = {PMC13098183}
}

RIS

TY - JOUR
AU - Byblow, Winston D
AU - Shanks, Maxine J
AU - Scrivener, Benjamin
AU - Duval, Laura
AU - Stinear, Cathy M
AU - Lee, Arier
AU - Barber, P Alan
AU - Colle, Patricia
AU - Ren, April
AU - Cirillo, John
AU - Arac, Ahmet
AU - Ejaz, Naveed
AU - Chan-Cortes, Michelle
AU - Garipelli, Gangadhar
AU - Kitago, Tomoko
AU - Krakauer, John W
TI - Enhancing spontaneous recovery after stroke: a randomized controlled trial
T2 - Brain communications
J2 - Brain Commun
PY - 2026
DA - 2026/03/28
VL - 8
IS - 2
SP - fcag057
SN - 2632-1297
PB - Oxford University Press
DO - 10.1093/braincomms/fcag057
UR - https://doi.org/10.1093/braincomms/fcag057
LA - en
ER -

CSL-JSON

{
"id": "10.1093/braincomms/fcag057",
"type": "article-journal",
"title": "Enhancing spontaneous recovery after stroke: a randomized controlled trial",
"container-title": "Brain communications",
"author": [
{
"family": "Byblow",
"given": "Winston D"
},
{
"family": "Shanks",
"given": "Maxine J"
},
{
"family": "Scrivener",
"given": "Benjamin"
},
{
"family": "Duval",
"given": "Laura"
},
{
"family": "Stinear",
"given": "Cathy M"
},
{
"family": "Lee",
"given": "Arier"
},
{
"family": "Barber",
"given": "P Alan"
},
{
"family": "Colle",
"given": "Patricia"
},
{
"family": "Ren",
"given": "April"
},
{
"family": "Cirillo",
"given": "John"
},
{
"family": "Arac",
"given": "Ahmet"
},
{
"family": "Ejaz",
"given": "Naveed"
},
{
"family": "Chan-Cortes",
"given": "Michelle"
},
{
"family": "Garipelli",
"given": "Gangadhar"
},
{
"family": "Kitago",
"given": "Tomoko"
},
{
"family": "Krakauer",
"given": "John W"
}
],
"container-title-short": "Brain Commun",
"volume": "8",
"issue": "2",
"page": "fcag057",
"DOI": "10.1093/braincomms/fcag057",
"PMID": "42022291",
"PMCID": "PMC13098183",
"ISSN": "2632-1297",
"publisher": "Oxford University Press",
"URL": "https://doi.org/10.1093/braincomms/fcag057",
"language": "en",
"issued": {
"date-parts": [
[
2026,
3,
28
]
]
}
}

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.1186/s12984-025-01743-4 [code]
Can machine learning improve on the early prediction of upper limb recovery after stroke?
Journal: Journal of neuroengineering and rehabilitation
In common: stroke, clinical / translational, 8 references
[2] doi:10.1002/hbm.26105 [code]
Use of multi-perturbation Shapley analysis in lesion studies of functional networks: The case of upper limb paresis.
Journal: Human brain mapping
In common: Statistics and Machine Learning Toolbox, stroke, clinical / translational, 5 references
[3] doi:10.1080/07853890.2025.2598930
Predictive value of motor-evoked potentials for upper limb functional outcomes in acute ischemic stroke.
Journal: Annals of medicine
In common: stroke, other, clinical / translational, 5 references
[4] doi:10.1063/5.0263191 [code]
Post-stroke spontaneous motor recovery in mice can be predicted from acute-phase local field potential using machine learning.
Journal: APL bioengineering
In common: pandas, NumPy, stroke, clinical / translational, 5 references
[5] doi:10.1177/0271678x241254718 [code]
Refined movement analysis in the staircase test reveals differential motor deficits in mouse models of stroke.
Journal: Journal of cerebral blood flow and metabolism : official journal of the International Society of Cerebral Blood Flow and Metabolism
In common: Optimization Toolbox, Statistics and Machine Learning Toolbox, pandas, 1 other tool, stroke, clinical / translational, 3 references
[6] doi:10.1038/s41467-025-57860-0 [code]
Parvalbumin interneurons regulate rehabilitation-induced functional recovery after stroke and identify a rehabilitation drug.
Journal: Nature communications
In common: Statistics and Machine Learning Toolbox, stroke, 4 references
[7] doi:10.1093/ptj/pzae148
Modeling Upper Limb Rehabilitation-Induced Recovery After Stroke: The Role of Attention as a Clinical Confounder.
Journal: Physical therapy
In common: stroke, clinical / translational, 4 references
[8] doi:10.3389/fnhum.2022.1023246
Home-based portable fNIRS-derived cortical laterality correlates with impairment and function in chronic stroke.
Journal: Frontiers in human neuroscience
In common: stroke, 4 references
[9] doi:10.1093/braincomms/fcad055
Prediction of post-stroke motor recovery benefits from measures of sub-acute widespread network damages.
Journal: Brain communications
In common: stroke, 4 references
[10] doi:10.1126/sciadv.abo3505 [code]
Dissecting motor skill acquisition: Spatial coordinates take precedence.
Journal: Science advances
In common: pandas, NumPy, author John W Krakauer

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.

Add the badge to the code's README

OSCR: the badge of this paper

If you are an author or a maintainer of this paper's code, you can add this badge. It says what OSCR knows of the paper (its code, its validated tracing map) and links the code to this page. Paste one of these snippets under the README's title. Keep the two marks around it, so that the badge can be replaced in place and is never added twice. OSCR changes nothing in your repository.

Markdown
<!-- oscr-badge:start -->
[![OSCR](https://openscicode.org/badge/doi/10.1093/braincomms/fcag057.svg)](https://openscicode.org/badge/doi/10.1093/braincomms/fcag057)
<!-- oscr-badge:end -->
reStructuredText
.. oscr-badge:start

.. image:: https://openscicode.org/badge/doi/10.1093/braincomms/fcag057.svg
   :target: https://openscicode.org/badge/doi/10.1093/braincomms/fcag057
   :alt: OSCR

.. oscr-badge:end
HTML
<!-- oscr-badge:start -->
<a href="https://openscicode.org/badge/doi/10.1093/braincomms/fcag057"><img src="https://openscicode.org/badge/doi/10.1093/braincomms/fcag057.svg" alt="OSCR"></a>
<!-- oscr-badge:end -->

On GitHub, open the README, choose Edit (the pencil), paste the snippet under the title, then "Commit changes".

If you choose to, you can have the badge kept up to date for you, with oscr badge --write from the command line or with the GitHub Action in your repository's workflows. Every other form and option (a card, AsciiDoc, LaTeX, shields.io) is on the badge 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: code errors and mismatches between the code and the paper about this paper and its code, from readers and its authors, beside the paper and its tracing map.

Report a mismatch between the code and the paper · Report a code error (signed in)

Reproductions: reports from readers who ran the authors' code and did not get the paper's result, what they ran, with which environment, commit and data.

Report a reproduction failure (signed in)

Versions of its code: the releases of its code tied to a version of this paper (the preprint, the accepted manuscript, the version of record…), each with the tracing map versioned with it.

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