Activity-dependent adaptive deep brain stimulation improves gait in Parkinson's disease.
The 3 matches
- [1] § Methods › Decoding algorithms of locomotor activities › Balancing of classes ↔ neurodecode/decoder/trainer.py, lines 1–62 · score 0.76 · Random Forest classifier, scikit learn, sklearn, training, errors
- [2] § Methods › Neural decoding framework ↔ neurodecode/decoder/trainer.py, lines 1–62 · score 0.66 · scikit learn, Random Forest, cross validation, training, decoder
- [3] § Methods › Decoding algorithms of locomotor activities › Balancing of classes ↔ neurodecode/decoder/trainer.py, lines 464–557 · score 0.51 · confusion matrices, cross validation, class, score
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
Python · 702 lines · 28 KB · gnu · 3 matches
- from __future__ import print_function, division
- """
- trainer.py
- Perform cross-validation and train a classifier.
- See run() to see the overall flow.
- When you add more classifiers, modify "CLASSIFIERS" variable to include your classifier.
- Kyuhwa Lee, 2018
- Swiss Federal Institute of Technology Lausanne (EPFL)
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- You should have received a copy of the GNU General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
- """
- import os
- import sys
- import mne
- import mne.io
- import platform
- import numpy as np
- import multiprocessing as mp
- import sklearn.metrics as skmetrics
- import neurodecode.utils.q_common as qc
- import neurodecode.utils.pycnbi_utils as pu
- import neurodecode.decoder.features as features
- from builtins import input
- from sklearn.ensemble import RandomForestClassifier
- from sklearn.ensemble import GradientBoostingClassifier
- from xgboost import XGBClassifier
- from lightgbm import LGBMClassifier
- from sklearn.discriminant_analysis import LinearDiscriminantAnalysis as LDA
- from neurodecode.decoder.rlda import rLDA
- from neurodecode import logger
- from neurodecode.triggers.trigger_def import trigger_def
- # supported classifiers: add this part as you add more classifiers
- CLASSIFIERS = {'RF':RandomForestClassifier, 'GB':GradientBoostingClassifier, 'XGB':XGBClassifier,
- 'LGB':LGBMClassifier, 'LDA':LDA, 'rLDA':rLDA}
- # scikit-learn old version compatibility
- try:
- from sklearn.model_selection import StratifiedShuffleSplit, LeaveOneOut
- SKLEARN_OLD = False
- except ImportError:
- from sklearn.cross_validation import StratifiedShuffleSplit, LeaveOneOut
- SKLEARN_OLD = True
- mne.set_log_level('ERROR')
- os.environ['OMP_NUM_THREADS'] = '1' # actually improves performance for multitaper
- def check_config(cfg):
- critical_vars = {
- 'COMMON': ['TRIGGER_FILE',
- 'TRIGGER_DEF',
- 'EPOCH',
- 'DATA_PATH',
- 'PICKED_CHANNELS',
- 'SP_FILTER',
- 'TP_FILTER',
- 'NOTCH_FILTER',
- 'FEATURES',
- 'CLASSIFIER',
- 'CV_PERFORM'],
- 'RF': ['n_estimators'],
- 'GB': ['n_estimators', 'learning_rate'],
- 'XGB': ['n_estimators', 'learning_rate'],
- 'LGB': ['n_estimators', 'learning_rate'],
- 'LDA': [],
- 'rLDA': ['reg_cov'],
- 'StratifiedShuffleSplit': ['test_ratio', 'folds', 'seed', 'export_result'],
- 'LeaveOneOut': ['export_result']
- }
- # optional variables with default values
- optional_vars = {
- 'MULTIPLIER': 1,
- 'EXPORT_GOOD_FEATURES': False,
- 'FEAT_TOPN': 10,
- 'EXPORT_CLS': False,
- 'REREFERENCE': None,
- 'N_JOBS': None,
- 'EXCLUDED_CHANNELS': None,
- 'LOAD_EVENTS': None,
- 'CV': {'IGNORE_THRES': None, 'DECISION_THRES': None, 'BALANCE_SAMPLES': False},
- }
- for v in critical_vars['COMMON']:
- if not hasattr(cfg, v):
- logger.error('%s not defined in config.' % v)
- raise KeyError
- for key in optional_vars:
- if not hasattr(cfg, key):
- setattr(cfg, key, optional_vars[key])
- logger.warning('Setting undefined parameter %s=%s' % (key, getattr(cfg, key)))
- if 'decim' not in cfg.FEATURES['PSD']:
- cfg.FEATURES['PSD']['decim'] = 1
- # classifier parameters check
- selected_classifier = cfg.CLASSIFIER['selected']
- if selected_classifier not in cfg.CLASSIFIER:
- logger.error('"%s" not defined in your config.' % selected_classifier)
- raise KeyError
- for v in critical_vars[selected_classifier]:
- if v not in cfg.CLASSIFIER[selected_classifier]:
- logger.error('parameter %s must be defined for %s classifier.' % (v, selected_classifier))
- raise KeyError
- cv_selected = cfg.CV_PERFORM['selected']
- if cfg.CV_PERFORM[cv_selected] is not None:
- if cv_selected not in cfg.CV_PERFORM:
- logger.error('"%s" not defined in config.' % cv_selected)
- raise KeyError
- for v in critical_vars[cv_selected]:
- if v not in cfg.CV_PERFORM[cv_selected]:
- logger.error('parameter %s must be defined for %s.' % (v, cv_selected))
- raise KeyError
- if cfg.N_JOBS is None:
- cfg.N_JOBS = mp.cpu_count()
- return cfg
- def balance_samples(X, Y, balance_type, verbose=False):
- if balance_type == 'OVER':
- """
- Oversample from classes that lack samples
- """
- label_set = np.unique(Y)
- max_set = []
- X_balanced = np.array(X)
- Y_balanced = np.array(Y)
- # find a class with maximum number of samples
- for c in label_set:
- yl = np.where(Y == c)[0]
- if len(max_set) == 0 or len(yl) > max_set[1]:
- max_set = [c, len(yl)]
- for c in label_set:
- if c == max_set[0]: continue
- yl = np.where(Y == c)[0]
- extra_samples = max_set[1] - len(yl)
- extra_idx = np.random.choice(yl, extra_samples)
- X_balanced = np.append(X_balanced, X[extra_idx], axis=0)
- Y_balanced = np.append(Y_balanced, Y[extra_idx], axis=0)
- elif balance_type == 'UNDER':
- """
- Undersample from classes that are excessive
- """
- label_set = np.unique(Y)
- min_set = []
- # find a class with minimum number of samples
- for c in label_set:
- yl = np.where(Y == c)[0]
- if len(min_set) == 0 or len(yl) < min_set[1]:
- min_set = [c, len(yl)]
- yl = np.where(Y == min_set[0])[0]
- X_balanced = np.array(X[yl])
- Y_balanced = np.array(Y[yl])
- for c in label_set:
- if c == min_set[0]: continue
- yl = np.where(Y == c)[0]
- reduced_idx = np.random.choice(yl, min_set[1])
- X_balanced = np.append(X_balanced, X[reduced_idx], axis=0)
- Y_balanced = np.append(Y_balanced, Y[reduced_idx], axis=0)
- elif balance_type is None or balance_type is False:
- return X, Y
- else:
- logger.error('Unknown balancing type %s' % balance_type)
- raise ValueError
- logger.info_green('\nNumber of samples after %ssampling' % balance_type.lower())
- for c in label_set:
- logger.info('%s: %d -> %d' % (c, len(np.where(Y == c)[0]), len(np.where(Y_balanced == c)[0])))
- return X_balanced, Y_balanced
- def crossval_epochs(cv, epochs_data, labels, cls, label_names=None, do_balance=None, n_jobs=None, ignore_thres=None, decision_thres=None):
- """
- Epoch-based cross-validation used by cross_validate().
- Params
- ======
- cv: scikit-learn cross-validation object
- epochs_data: np.array of shape [epochs x samples x features]
- labels: np.array of shape [epochs x samples]
- cls: classifier
- label_names: associated label names {0:'Left', 1:'Right', ...}
- do_balance: oversample or undersample to match the number of samples among classes
- """
- scores = []
- f1s = []
- cnum = 1
- cm_sum = 0
- label_set = np.unique(labels)
- num_labels = len(label_set)
- if label_names is None:
- label_names = {l:'%s' % l for l in label_set}
- if n_jobs is None:
- n_jobs = mp.cpu_count()
- if n_jobs > 1:
- logger.info('crossval_epochs(): Using %d cores' % n_jobs)
- pool = mp.Pool(n_jobs)
- results = []
- # for classifier itself, single core is usually faster
- cls.n_jobs = 1
- if SKLEARN_OLD:
- splits = cv
- else:
- splits = cv.split(epochs_data, labels[:, 0])
- for train, test in splits:
- X_train = np.concatenate(epochs_data[train])
- X_test = np.concatenate(epochs_data[test])
- Y_train = np.concatenate(labels[train])
- Y_test = np.concatenate(labels[test])
- if do_balance:
- X_train, Y_train = balance_samples(X_train, Y_train, do_balance)
- X_test, Y_test = balance_samples(X_test, Y_test, do_balance)
- if n_jobs > 1:
- results.append(pool.apply_async(fit_predict_thres,
- [cls, X_train, Y_train, X_test, Y_test, cnum, label_set, ignore_thres, decision_thres]))
- else:
- score, cm, f1 = fit_predict_thres(cls, X_train, Y_train, X_test, Y_test, cnum, label_set, ignore_thres, decision_thres)
- scores.append(score)
- f1s.append(f1)
- cm_sum += cm
- cnum += 1
- if n_jobs > 1:
- pool.close()
- pool.join()
- for r in results:
- score, cm, f1 = r.get()
- scores.append(score)
- f1s.append(f1)
- cm_sum += cm
- # confusion matrix
- cm_sum = cm_sum.astype('float')
- if cm_sum.shape[0] != cm_sum.shape[1]:
- # we have decision thresholding condition
- assert cm_sum.shape[0] < cm_sum.shape[1]
- cm_sum_all = cm_sum
- cm_sum = cm_sum[:, :cm_sum.shape[0]]
- underthres = np.array([r[-1] / sum(r) for r in cm_sum_all])
- else:
- underthres = None
- cm_rate = np.zeros(cm_sum.shape)
- for r_in, r_out in zip(cm_sum, cm_rate):
- rs = sum(r_in)
- if rs > 0:
- r_out[:] = r_in / rs
- else:
- assert min(r) == max(r) == 0
- if underthres is not None:
- cm_rate = np.concatenate((cm_rate, underthres[:, np.newaxis]), axis=1)
- cm_txt = 'Y: ground-truth, X: predicted\n'
- max_chars = 12
- tpl_str = '%%-%ds ' % max_chars
- tpl_float = '%%-%d.2f ' % max_chars
- for l in label_set:
- cm_txt += tpl_str % label_names[l][:max_chars]
- if underthres is not None:
- cm_txt += tpl_str % 'Ignored'
- cm_txt += '\n'
- for r in cm_rate:
- for c in r:
- cm_txt += tpl_float % c
- cm_txt += '\n'
- cm_txt += 'Average accuracy: %.2f\n' % np.mean(scores)
- cm_txt += 'Average F1 score: %.2f\n' % np.mean(f1s)
- return np.array(scores), cm_txt
- def balance_tpr(cfg, featdata):
- """
- Find the threshold of class index 0 that yields equal number of true positive samples of each class.
- Currently only available for binary classes.
- Params
- ======
- cfg: config module
- feetdata: feature data computed using compute_features()
- """
- n_jobs = cfg.N_JOBS
- if n_jobs is None:
- n_jobs = mp.cpu_count()
- if n_jobs > 1:
- logger.info('balance_tpr(): Using %d cores' % n_jobs)
- pool = mp.Pool(n_jobs)
- results = []
- # Init a classifier
- selected_classifier = cfg.CLASSIFIER[cfg.CLASSIFIER['selected']]
- if selected_classifier not in CLASSIFIERS:
- logger.error('Unsupported classifier %s' % selected_classifier)
- raise ValueError
- params = cfg.CLASSIFIER[selected_classifier]
- cls = CLASSIFIERS[selected_classifier](**params)
- # Setup features
- X_data = featdata['X_data']
- Y_data = featdata['Y_data']
- wlen = featdata['wlen']
- if cfg.CLASSIFIER['PSD']['wlen'] is None:
- cfg.CLASSIFIER['PSD']['wlen'] = wlen
- # Choose CV type
- ntrials, nsamples, fsize = X_data.shape
- selected_CV = cfg.CV_PERFORM[cfg.CV_PERFORM['selected']]
- if cselected_CV == 'LeaveOneOut':
- logger.info_green('\n%d-fold leave-one-out cross-validation' % ntrials)
- if SKLEARN_OLD:
- cv = LeaveOneOut(len(Y_data))
- else:
- cv = LeaveOneOut()
- elif selected_CV == 'StratifiedShuffleSplit':
- logger.info_green('\n%d-fold stratified cross-validation with test set ratio %.2f' % (cfg.CV_PERFORM[selected_CV]['folds'], cfg.CV_PERFORM[selected_CV]['test_ratio']))
- if SKLEARN_OLD:
- cv = StratifiedShuffleSplit(Y_data[:, 0], cfg.CV_PERFORM[selected_CV]['folds'], test_size=cfg.CV_PERFORM[selected_CV]['test_ratio'], random_state=cfg.CV_PERFORM[selected_CV]['random_seed'])
- else:
- cv = StratifiedShuffleSplit(n_splits=cfg.CV_PERFORM[selected_CV]['folds'], test_size=cfg.CV_PERFORM[selected_CV]['test_ratio'], random_state=cfg.CV_PERFORM[selected_CV]['random_seed'])
- else:
- logger.error('%s is not supported yet. Sorry.' % selected_CV)
- raise NotImplementedError
- logger.info('%d trials, %d samples per trial, %d feature dimension' % (ntrials, nsamples, fsize))
- # For classifier itself, single core is usually faster
- cls.n_jobs = 1
- Y_preds = []
- if SKLEARN_OLD:
- splits = cv
- else:
- splits = cv.split(X_data, Y_data[:, 0])
- for cnum, (train, test) in enumerate(splits):
- X_train = np.concatenate(X_data[train])
- X_test = np.concatenate(X_data[test])
- Y_train = np.concatenate(Y_data[train])
- Y_test = np.concatenate(Y_data[test])
- if n_jobs > 1:
- results.append(pool.apply_async(get_predict_proba, [cls, X_train, Y_train, X_test, Y_test, cnum+1]))
- else:
- Y_preds.append(get_predict_proba(cls, X_train, Y_train, X_test, Y_test, cnum+1))
- cnum += 1
- # Aggregate predictions
- if n_jobs > 1:
- pool.close()
- pool.join()
- for r in results:
- Y_preds.append(r.get())
- Y_preds = np.concatenate(Y_preds, axis=0)
- # Find threshold for class index 0
- Y_preds = sorted(Y_preds)
- mid_idx = int(len(Y_preds) / 2)
- if len(Y_preds) == 1:
- return 0.5 # should not reach here in normal conditions
- elif len(Y_preds) % 2 == 0:
- thres = Y_preds[mid_idx-1] + (Y_preds[mid_idx] - Y_preds[mid_idx-1]) / 2
- else:
- thres = Y_preds[mid_idx]
- return thres
- def cva_features(datadir):
- """
- (DEPRECATED FUNCTION)
- """
- for fin in qc.get_file_list(datadir, fullpath=True):
- if fin[-4:] != '.gdf': continue
- fout = fin + '.cva'
- if os.path.exists(fout):
- logger.info('Skipping', fout)
- continue
- logger.info("cva_features('%s')" % fin)
- qc.matlab("cva_features('%s')" % fin)
- def get_predict_proba(cls, X_train, Y_train, X_test, Y_test, cnum):
- """
- All likelihoods will be collected from every fold of a cross-validaiton. Based on these likelihoods,
- a threshold will be computed that will balance the true positive rate of each class.
- Available with binary classification scenario only.
- """
- timer = qc.Timer()
- cls.fit(X_train, Y_train)
- Y_pred = cls.predict_proba(X_test)
- logger.info('Cross-validation %d (%d tests) - %.1f sec' % (cnum, Y_pred.shape[0], timer.sec()))
- return Y_pred[:,0]
- def fit_predict_thres(cls, X_train, Y_train, X_test, Y_test, cnum, label_list, ignore_thres=None, decision_thres=None):
- """
- Any likelihood lower than a threshold is not counted as classification score
- Confusion matrix, accuracy and F1 score (macro average) are computed.
- Params
- ======
- ignore_thres:
- if not None or larger than 0, likelihood values lower than ignore_thres will be ignored
- while computing confusion matrix.
- """
- timer = qc.Timer()
- cls.fit(X_train, Y_train)
- assert ignore_thres is None or ignore_thres >= 0
- if ignore_thres is None or ignore_thres == 0:
- Y_pred = cls.predict(X_test)
- score = skmetrics.accuracy_score(Y_test, Y_pred)
- cm = skmetrics.confusion_matrix(Y_test, Y_pred, labels=label_list)
- f1 = skmetrics.f1_score(Y_test, Y_pred, average='macro')
- else:
- if decision_thres is not None:
- logger.error('decision threshold and ignore_thres cannot be set at the same time.')
- raise ValueError
- Y_pred = cls.predict_proba(X_test)
- Y_pred_labels = np.argmax(Y_pred, axis=1)
- Y_pred_maxes = np.array([x[i] for i, x in zip(Y_pred_labels, Y_pred)])
- Y_index_overthres = np.where(Y_pred_maxes >= ignore_thres)[0]
- Y_index_underthres = np.where(Y_pred_maxes < ignore_thres)[0]
- Y_pred_overthres = np.array([cls.classes_[x] for x in Y_pred_labels[Y_index_overthres]])
- Y_pred_underthres = np.array([cls.classes_[x] for x in Y_pred_labels[Y_index_underthres]])
- Y_pred_underthres_count = np.array([np.count_nonzero(Y_pred_underthres == c) for c in label_list])
- Y_test_overthres = Y_test[Y_index_overthres]
- score = skmetrics.accuracy_score(Y_test_overthres, Y_pred_overthres)
- cm = skmetrics.confusion_matrix(Y_test_overthres, Y_pred_overthres, labels=label_list)
- cm = np.concatenate((cm, Y_pred_underthres_count[:, np.newaxis]), axis=1)
- f1 = skmetrics.f1_score(Y_test_overthres, Y_pred_overthres, average='macro')
- logger.info('Cross-validation %d (%.3f) - %.1f sec' % (cnum, score, timer.sec()))
- return score, cm, f1
- def cross_validate(cfg, featdata, cv_file=None):
- """
- Perform cross validation
- """
- # Init a classifier
- selected_classifier = cfg.CLASSIFIER['selected']
- if selected_classifier not in CLASSIFIERS:
- logger.error('Unsupported classifier %s' % selected_classifier)
- raise ValueError
- params = cfg.CLASSIFIER[selected_classifier]
- cls = CLASSIFIERS[selected_classifier](**params)
- # Setup features
- X_data = featdata['X_data']
- Y_data = featdata['Y_data']
- wlen = featdata['wlen']
- # Choose CV type
- ntrials, nsamples, fsize = X_data.shape
- selected_cv = cfg.CV_PERFORM['selected']
- if selected_cv == 'LeaveOneOut':
- logger.info_green('%d-fold leave-one-out cross-validation' % ntrials)
- if SKLEARN_OLD:
- cv = LeaveOneOut(len(Y_data))
- else:
- cv = LeaveOneOut()
- elif selected_cv == 'StratifiedShuffleSplit':
- logger.info_green('%d-fold stratified cross-validation with test set ratio %.2f' % (cfg.CV_PERFORM[selected_cv]['folds'], cfg.CV_PERFORM[selected_cv]['test_ratio']))
- if SKLEARN_OLD:
- cv = StratifiedShuffleSplit(Y_data[:, 0], cfg.CV_PERFORM[selected_cv]['folds'], test_size=cfg.CV_PERFORM[selected_cv]['test_ratio'], random_state=cfg.CV_PERFORM[selected_cv]['seed'])
- else:
- cv = StratifiedShuffleSplit(n_splits=cfg.CV_PERFORM[selected_cv]['folds'], test_size=cfg.CV_PERFORM[selected_cv]['test_ratio'], random_state=cfg.CV_PERFORM[selected_cv]['seed'])
- else:
- logger.error('%s is not supported yet. Sorry.' % cfg.CV_PERFORM[cfg.CV_PERFORM['selected']])
- raise NotImplementedError
- logger.info('%d trials, %d samples per trial, %d feature dimension' % (ntrials, nsamples, fsize))
- # Do it!
- timer_cv = qc.Timer()
- scores, cm_txt = crossval_epochs(cv, X_data, Y_data, cls, cfg.tdef.by_value, cfg.CV['BALANCE_SAMPLES'], n_jobs=cfg.N_JOBS,
- ignore_thres=cfg.CV['IGNORE_THRES'], decision_thres=cfg.CV['DECISION_THRES'])
- t_cv = timer_cv.sec()
- # Export results
- txt = 'Cross validation took %d seconds.\n' % t_cv
- txt += '\n- Class information\n'
- txt += '%d epochs, %d samples per epoch, %d feature dimension (total %d samples)\n' %\
- (ntrials, nsamples, fsize, ntrials * nsamples)
- for ev in np.unique(Y_data):
- txt += '%s: %d trials\n' % (cfg.tdef.by_value[ev], len(np.where(Y_data[:, 0] == ev)[0]))
- if cfg.CV['BALANCE_SAMPLES']:
- txt += 'The number of samples was balanced using %ssampling.\n' % cfg.BALANCE_SAMPLES.lower()
- txt += '\n- Experiment condition\n'
- txt += 'Sampling frequency: %.3f Hz\n' % featdata['sfreq']
- txt += 'Spatial filter: %s\n' % cfg.SP_FILTER
- txt += 'Spectral filter: %s\n' % cfg.TP_FILTER[cfg.TP_FILTER['selected']]
- txt += 'Notch filter: %s\n' % cfg.NOTCH_FILTER[cfg.NOTCH_FILTER['selected']]
- #txt += 'Channels: ' + ','.join([str(featdata['ch_names'][p]) for p in featdata['picks']]) + '\n'
- txt += 'Channels: %s\n' % featdata['ch_names']
- txt += 'PSD range: %.1f - %.1f Hz\n' % (cfg.FEATURES['PSD']['fmin'], cfg.FEATURES['PSD']['fmax'])
- txt += 'Window step: %.2f msec\n' % (1000.0 * cfg.FEATURES['PSD']['wstep'] / featdata['sfreq'])
- if type(wlen) is list:
- for i, w in enumerate(wlen):
- txt += 'Window size: %.1f msec\n' % (w * 1000.0)
- txt += 'Epoch range: %s sec\n' % (cfg.EPOCH[i])
- else:
- txt += 'Window size: %.1f msec\n' % (cfg.FEATURES['PSD']['wlen'] * 1000.0)
- txt += 'Epoch range: %s sec\n' % (cfg.EPOCH)
- txt += 'Decimation factor: %d\n' % cfg.FEATURES['PSD']['decim']
- # Compute stats
- cv_mean, cv_std = np.mean(scores), np.std(scores)
- txt += '\n- Average CV accuracy over %d epochs (random seed=%s)\n' % (ntrials, cfg.CV_PERFORM[cfg.CV_PERFORM['selected']]['seed'])
- if cfg.CV_PERFORM[cfg.CV_PERFORM['selected']] in ['LeaveOneOut', 'StratifiedShuffleSplit']:
- txt += "mean %.3f, std: %.3f\n" % (cv_mean, cv_std)
- txt += 'Classifier: %s, ' % selected_classifier
- txt += ', '.join(['%s=%s' % (k, params[k]) for k in params]) + '\n'
- if cfg.CV['IGNORE_THRES'] is not None:
- txt += 'Decision threshold: %.2f\n' % cfg.CV['IGNORE_THRES']
- txt += '\n- Confusion Matrix\n' + cm_txt
- logger.info(txt)
- # Export to a file
- if 'export_result' in cfg.CV_PERFORM[selected_cv] and cfg.CV_PERFORM[selected_cv]['export_result'] is True:
- if cv_file is None:
- if cfg.EXPORT_CLS is True:
- qc.make_dirs('%s/classifier' % cfg.DATA_PATH)
- fout = open('%s/classifier/cv_result.txt' % cfg.DATA_PATH, 'w')
- else:
- fout = open('%s/cv_result.txt' % cfg.DATA_PATH, 'w')
- else:
- fout = open(cv_file, 'w')
- fout.write(txt)
- fout.close()
- def train_decoder(cfg, featdata, feat_file=None):
- """
- Train the final decoder using all data
- """
- # Init a classifier
- selected_classifier = cfg.CLASSIFIER['selected']
- if selected_classifier not in CLASSIFIERS:
- logger.error('Unsupported classifier %s' % selected_classifier)
- raise ValueError
- params = cfg.CLASSIFIER[selected_classifier]
- cls = CLASSIFIERS[selected_classifier](**params)
- # Setup features
- X_data = featdata['X_data']
- Y_data = featdata['Y_data']
- wlen = featdata['wlen']
- if cfg.FEATURES['PSD']['wlen'] is None:
- cfg.FEATURES['PSD']['wlen'] = wlen
- w_frames = featdata['w_frames']
- ch_names = featdata['ch_names']
- ch_names_raw = featdata['ch_names_raw']
- X_data_merged = np.concatenate(X_data)
- Y_data_merged = np.concatenate(Y_data)
- if cfg.CV['BALANCE_SAMPLES']:
- X_data_merged, Y_data_merged = balance_samples(X_data_merged, Y_data_merged, cfg.CV['BALANCE_SAMPLES'], verbose=True)
- # Start training the decoder
- logger.info_green('Training the decoder')
- timer = qc.Timer()
- cls.n_jobs = cfg.N_JOBS
- cls.fit(X_data_merged, Y_data_merged)
- logger.info('Trained %d samples x %d dimension in %.1f sec' %\
- (X_data_merged.shape[0], X_data_merged.shape[1], timer.sec()))
- cls.n_jobs = 1 # always set n_jobs=1 for testing
- # Export the decoder
- classes = {c:cfg.tdef.by_value[c] for c in np.unique(Y_data)}
- if cfg.FEATURES['selected'] == 'PSD':
- data = dict(cls=cls, ch_names=ch_names, ch_names_raw=ch_names_raw, psde=featdata['psde'], sfreq=featdata['sfreq'],
- picks=featdata['picks'], classes=classes, epochs=cfg.EPOCH, w_frames=w_frames,
- w_seconds=cfg.FEATURES['PSD']['wlen'], wstep=cfg.FEATURES['PSD']['wstep'], spatial=cfg.SP_FILTER,
- spatial_ch=featdata['picks'], spectral=cfg.TP_FILTER[cfg.TP_FILTER['selected']], spectral_ch=featdata['picks'],
- notch=cfg.NOTCH_FILTER[cfg.NOTCH_FILTER['selected']], notch_ch=featdata['picks'], multiplier=cfg.MULTIPLIER,
- ref_ch=cfg.REREFERENCE[cfg.REREFERENCE['selected']], decim=cfg.FEATURES['PSD']['decim'])
- clsfile = '%s/classifier/classifier-%s.pkl' % (cfg.DATA_PATH, platform.architecture()[0])
- qc.make_dirs('%s/classifier' % cfg.DATA_PATH)
- qc.save_obj(clsfile, data)
- logger.info('Decoder saved to %s' % clsfile)
- # Reverse-lookup frequency from FFT
- fq = 0
- if type(cfg.FEATURES['PSD']['wlen']) == list:
- fq_res = 1.0 / cfg.FEATURES['PSD']['wlen'][0]
- else:
- fq_res = 1.0 / cfg.FEATURES['PSD']['wlen']
- fqlist = []
- while fq <= cfg.FEATURES['PSD']['fmax']:
- if fq >= cfg.FEATURES['PSD']['fmin']:
- fqlist.append(fq)
- fq += fq_res
- # Show top distinctive features
- if cfg.FEATURES['selected'] == 'PSD':
- logger.info_green('Good features ordered by importance')
- if selected_classifier in ['RF', 'GB', 'XGB', 'LGB']:
- keys, values = qc.sort_by_value(list(cls.feature_importances_), rev=True)
- elif selected_classifier in ['LDA', 'rLDA']:
- keys, values = qc.sort_by_value(cls.w, rev=True)
- keys = np.array(keys)
- values = np.array(values)
- if cfg.EXPORT_GOOD_FEATURES:
- if feat_file is None:
- gfout = open('%s/classifier/good_features.txt' % cfg.DATA_PATH, 'w')
- else:
- gfout = open(feat_file, 'w')
- if type(wlen) is list:
- ch_names = []
- for w in range(len(wlen)):
- for c in featdata['picks']:
- ch_names.append('w%d-%s' % (w, ch_names_raw[c]))
- chlist, hzlist = features.feature2chz(keys, fqlist, ch_names=ch_names)
- valnorm = values.copy()
- valsum = np.sum(valnorm)
- if valsum > 0:
- valnorm = valnorm / valsum * 100.0
- # show top-N features
- for i, (ch, hz) in enumerate(zip(chlist, hzlist)):
- if i >= cfg.FEAT_TOPN:
- break
- txt = '%-3s %5.1f Hz normalized importance %-6s raw importance %-6s feature %-5d' %\
- (ch, hz, '%.2f%%' % valnorm[i], '%.2f' % values[i], keys[i])
- logger.info(txt)
- if cfg.EXPORT_GOOD_FEATURES:
- gfout.write('Importance(%) Channel Frequency Index\n')
- for i, (ch, hz) in enumerate(zip(chlist, hzlist)):
- gfout.write('%.3f\t%s\t%s\t%d\n' % (valnorm[i], ch, hz, keys[i]))
- gfout.close()
- # for batch scripts
- def batch_run(cfg_module):
- cfg = pu.load_config(cfg_module)
- cfg = check_config(cfg)
- run(cfg)
- def run(cfg, cv_file=None, feat_file=None, logger=logger):
- # add tdef object
- cfg.tdef = trigger_def(cfg.TRIGGER_FILE)
- # Extract features
- featdata = features.compute_features(cfg)
- # Find optimal threshold for TPR balancing
- #balance_tpr(cfg, featdata)
- # Perform cross validation
- if cfg.CV_PERFORM[cfg.CV_PERFORM['selected']] is not None:
- cross_validate(cfg, featdata, cv_file=cv_file)
- # Train a decoder
- if cfg.EXPORT_CLS is True:
- train_decoder(cfg, featdata, feat_file=feat_file)
- def main():
- """
- Invoked from console
- """
- # Load parameters
- if len(sys.argv) <= 1:
- print('Usage: %s config_module' % os.path.basename(__file__))
- return
- cfg_module = sys.argv[1]
- batch_run(cfg_module)
- logger.info('Finished.')
- if __name__ == '__main__':
- main()
trainer.py at commit 8071d09, under gnu · at the source
Overview
and 7 other authors
Julien F Bally10, Alessandro Puiatti3, Kyuhwa Lee11, Henri Lorach1,2, Antoine Collomb-Clerc4, Jocelyne Bloch1,2,4,5, Eduardo M Moraud1,2,4- Department of Clinical Neurosciences, Lausanne University Hospital (CHUV) and University of Lausanne (UNIL), Lausanne, Switzerland
- NeuroRestore, Lausanne University Hospital (CHUV) and Ecole Polytechnique Fédérale de Lausanne (EPFL), Lausanne, Switzerland
- Institute of Digital Technologies for Personalized Healthcare (MeDiTech), University of Southern Switzerland (SUPSI), Viganello, Switzerland
- Neuro-X Institute, École Polytechnique Fédérale de Lausanne (EPFL), Geneva, Switzerland
- Department of Neurosurgery, Lausanne University Hospital (CHUV) and University of Lausanne (UNIL), Lausanne, Switzerland
- Department of Neurology, Hôpitaux Universitaires de Genève (HUG), Geneva, Switzerland
- Faculty of Medicine, University of Geneva, Geneva, Switzerland
- Department of Neurology, Hôpital du Valais, Sion, Switzerland
- Department of Neurology, Hôpital Fribourgeois, Fribourg, Switzerland
- Department of Neurology, Lausanne University Hospital (CHUV) and University of Lausanne (UNIL), Lausanne, Switzerland
- Wyss Center for Bio and Neuroengineering, Geneva, Switzerland
Abstract
Parkinson’s disease leads to a spectrum of locomotor deficits that vary in severity with the nature of daily activities and the fluctuating physiology of patients. Many of these deficits remain inadequately addressed by existing deep brain stimulation therapies that rely on activity-agnostic parameters optimized for cardinal motor symptoms. By contrast, therapies embedding activity-specific parameters have the potential to better address the entire range of symptoms. Here we expose physiological principles that enable real-time decoding of ongoing locomotor activities across motor fluctuations from the neural dynamics of the subthalamic nucleus. This decoding steered activity-dependent adaptations of deep brain stimulation therapies that improved locomotor deficits while preserving efficacy for cardinal motor symptoms across activities of daily living. Our activity-dependent framework provides a blueprint for next-generation neuromodulation therapies that continuously select parameters optimized to the behavioral context and fluctuating physiology of each patient. ClinicalTrials.gov registration NCT06791902.
Reproduced under the paper's license (CC BY), from the paper cited above.
Repositories
Its files are read in the Code ↔ Paper reader above, with 3 matches between paragraphs and lines of code.
dbdq/neurodecode
8071d0975910c23b8339b2660e822c68e9cd6f1f, 24 July 2026Availability: 1 check, the latest on 27 September 2026: the link answers
- 27 September 2026: the link answers
78 files
- neurodecode/
__init__.py , Python, 122 lines - neurodecode/
analysis/ , Python, 1 line__init__.py - neurodecode/
analysis/ , Python, 181 linesfeature_importances_topo .py - neurodecode/
analysis/ , Python, 209 linesparse_features.py - neurodecode/
analysis/ , Python, 60 linessample_scripts/ config/ tfr_export_config_mi.py - neurodecode/
analysis/ , Python, 306 linestfr_export.py - neurodecode/
analysis/ , Python, 146 linestfr_export_each_file.py - neurodecode/
colorer.py , Python, 119 lines - neurodecode/
config_files/ , Python, 67 linesmi/ structure_files/ config_structure_offline .py - neurodecode/
config_files/ , Python, 106 linesmi/ structure_files/ config_structure_online. py - neurodecode/
config_files/ , Python, 99 linesmi/ structure_files/ config_structure_trainer .py - neurodecode/
config_files/ , Python, 25 linesmi/ template_files/ config_offline.py - neurodecode/
config_files/ , Python, 82 linesmi/ template_files/ config_online.py - neurodecode/
config_files/ , Python, 65 linesmi/ template_files/ config_trainer.py - neurodecode/
decoder/ , Python, 1 line__init__.py - neurodecode/
decoder/ , Python, 836 linesdecoder.py - neurodecode/
decoder/ , Python, 535 linesfeatures.py - neurodecode/
decoder/ , Python, 110 linesrlda.py - neurodecode/
decoder/ , Python, 702 lines, 3 matchestrainer.py - neurodecode/
glass/ , Python, 1 line__init__.py - neurodecode/
glass/ , Java, 270 linesbgi-cnbi/ app/ src/ main/ java/ com/ example/ rex4/ Bar.java - neurodecode/
glass/ , Java, 107 linesbgi-cnbi/ app/ src/ main/ java/ com/ example/ rex4/ MainActivity.java - neurodecode/
glass/ , Python, 81 linesbgi-cnbi/ python/ bgi_client.py - neurodecode/
glass/ , Python, 184 linesbgi_client.py - neurodecode/
protocols/ , Python, 1 line__init__.py - neurodecode/
protocols/ , Python, 443 linesfeedback.py - neurodecode/
protocols/ , Python, 488 linesfeedback_fes.py - neurodecode/
protocols/ , Python, 297 linesmi/ test_mi.py - neurodecode/
protocols/ , Python, 242 linesmi/ train_mi.py - neurodecode/
protocols/ , Python, 185 linesviz_bars.py - neurodecode/
protocols/ , Python, 233 linesviz_images.py - neurodecode/
pycnbi_config.py , Python, 44 lines - neurodecode/
stream_player/ , Python, 1 line__init__.py - neurodecode/
stream_player/ , Python, 159 linesstream_player.py - neurodecode/
stream_player/ , Python, 159 linesstream_player_auto.py - neurodecode/
stream_receiver/ , Python, 1 line__init__.py - neurodecode/
stream_receiver/ , Python, 544 linesstream_receiver.py - neurodecode/
stream_recorder/ , Python, 1 line__init__.py - neurodecode/
stream_recorder/ , Python, 183 linesstream_recorder.py - neurodecode/
stream_viewer/ , Python, 1 line__init__.py - neurodecode/
stream_viewer/ , Python, 973 linesstream_viewer.py - neurodecode/
stream_viewer/ , Python, 491 linesui_mainwindow_Viewer.py - neurodecode/
triggers/ , Python, 1 line__init__.py - neurodecode/
triggers/ , Python, 283 linespyLptControl.py - neurodecode/
triggers/ , Python, 69 linestrigger_def.py - neurodecode/
utils/ , Python, 184 linesMotionstim8.py - neurodecode/
utils/ , Python, 85 linesScalerMad.py - neurodecode/
utils/ , Python, 1 line__init__.py - neurodecode/
utils/ , Python, 78 linesadd_lsl_events.py - neurodecode/
utils/ , Python, 39 linesbenchmark_decoder.py - neurodecode/
utils/ , Python, 47 linesbenchmark_multitaper.py - neurodecode/
utils/ , Python, 558 linesconvert2fif.py - neurodecode/
utils/ , Python, 79 linesepochs2mat.py - neurodecode/
utils/ , Python, 72 linesepochs2txt.py - neurodecode/
utils/ , Python, 58 linesfif2mat.py - neurodecode/
utils/ , Python, 51 linesfif_info.py - neurodecode/
utils/ , Python, 56 linesfif_resample.py - neurodecode/
utils/ , Python, 44 linesfix_channel_names.py - neurodecode/
utils/ , Python, 93 lineshdf5_to_python.py - neurodecode/
utils/ , Python, 35 linesimages2pkl.py - neurodecode/
utils/ , Python, 30 lineslist_trigger_pins.py - neurodecode/
utils/ , Python, 56 linesmat2fif.py - neurodecode/
utils/ , Python, 71 linesmerge_events.py - neurodecode/
utils/ , Python, 97 linesnd_lsl.py - neurodecode/
utils/ , Python, 33 linesparse_online_results.py - neurodecode/
utils/ , Python, 111 linespsd_visualizer.py - neurodecode/
utils/ , Python, 666 linespycnbi_utils.py - neurodecode/
utils/ , Python, 770 linesq_common.py - neurodecode/
utils/ , Python, 112 linesraw2psd.py - neurodecode/
utils/ , Python, 24 linessamples/ lsl_client.py - neurodecode/
utils/ , Python, 17 linessamples/ lsl_server.py - neurodecode/
utils/ , Python, 23 linessamples/ run_epochs2psd.py - neurodecode/
utils/ , Python, 46 linessamples/ run_raw2psd.py - sample/
config_offline.py , Python, 27 lines - sample/
config_online.py , Python, 87 lines - sample/
config_trainer.py , Python, 67 lines - setup.py, Python, 66 lines
- README.md, Text, 219 lines
Zenodo 19371521
Availability: 1 check, the latest on 27 September 2026: the link answers (HTTP 200)
- 27 September 2026: the link answers (HTTP 200)
Code availability
The custom code used in this study, including the implementation of the decoding framework, is available via Zenodo at 10.5281/
Reproduced under the paper's license (CC BY), from the paper cited above.
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:
- 2 repositories of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
- 77 scripts, each with its path and the digest of its content;
- 3 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 Statement
All data associated with this study are present in the paper, as well as in Extended Data Figs. 1–9, Extended Data Table 1 or Supplementary Information. Anonymized datasets and scripts that support the findings of this study are available via Zenodo at 10.5281/
The custom code used in this study, including the implementation of the decoding framework, is available via Zenodo at 10.5281/
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 → Nature Portfolio
Version 1, 27 September 2026: the first record
Recorded: type, language, journal, volume, issue, pages, dates, 27 authors, 3 keywords, 6 MeSH terms, 1 funder, 78 references.
Cite
This paper
Scafa, S., de Seta, V., Wang, R., Sánchez López, P., Sánchez López, A., Varescon, C., Sakr, I., Bérard, N., Bole-Feysot, L., Deschenaux, C., Enderli, I., Thenaisie, Y., Burri, M., Merlos, F., Fleury, V., Wicki, B., Accolla, E., Tziakouri, A., Hübsch, C., . . . Moraud, E. M. (2026). Activity-dependent adaptive deep brain stimulation improves gait in Parkinson's disease. Nature medicine, 32(8), 2815-2830. https://
BibTeX
@article{scafa2026activi
author = {Scafa, Stefano and de Seta, Valeria and Wang, Ruijia and Sánchez López, Paula and Sánchez López, Andrea and Varescon, Camille and Sakr, Icare and Bérard, Nadia and Bole-Feysot, Lea and Deschenaux, Céline and Enderli, Ian and Thenaisie, Yohann and Burri, Morgane and Merlos, Frédéric and Fleury, Vanessa and Wicki, Benoit and Accolla, Ettore and Tziakouri, Andria and Hübsch, Cécile and Castro Jiménez, Mayte and Bally, Julien F and Puiatti, Alessandro and Lee, Kyuhwa and Lorach, Henri and Collomb-Clerc, Antoine and Bloch, Jocelyne and Moraud, Eduardo M},
title = {{Activity-dependent adaptive deep brain stimulation improves gait in Parkinson's disease}},
journal = {Nature medicine},
year = {2026},
month = jun,
volume = {32},
number = {8},
pages = {2815--2830},
publisher = {Nature Portfolio},
issn = {1078-8956},
doi = {10.1038/
url = {https://
pmid = {42297979},
pmcid = {PMC13472876}
}
RIS
TY - JOUR
AU - Scafa, Stefano
AU - de Seta, Valeria
AU - Wang, Ruijia
AU - Sánchez López, Paula
AU - Sánchez López, Andrea
AU - Varescon, Camille
AU - Sakr, Icare
AU - Bérard, Nadia
AU - Bole-Feysot, Lea
AU - Deschenaux, Céline
AU - Enderli, Ian
AU - Thenaisie, Yohann
AU - Burri, Morgane
AU - Merlos, Frédéric
AU - Fleury, Vanessa
AU - Wicki, Benoit
AU - Accolla, Ettore
AU - Tziakouri, Andria
AU - Hübsch, Cécile
AU - Castro Jiménez, Mayte
AU - Bally, Julien F
AU - Puiatti, Alessandro
AU - Lee, Kyuhwa
AU - Lorach, Henri
AU - Collomb-Clerc, Antoine
AU - Bloch, Jocelyne
AU - Moraud, Eduardo M
TI - Activity-dependent adaptive deep brain stimulation improves gait in Parkinson's disease
T2 - Nature medicine
J2 - Nat Med
PY - 2026
DA - 2026/
VL - 32
IS - 8
SP - 2815
EP - 2830
SN - 1078-8956
PB - Nature Portfolio
DO - 10.1038/
UR - https://
LA - en
ER -
CSL-JSON
{
"id": "10.1038/
"type": "article-journal",
"title": "Activity-dependent adaptive deep brain stimulation improves gait in Parkinson's disease",
"container-title": "Nature medicine",
"author": [
{
"family": "Scafa",
"given": "Stefano"
},
{
"family": "de Seta",
"given": "Valeria"
},
{
"family": "Wang",
"given": "Ruijia"
},
{
"family": "Sánchez López",
"given": "Paula"
},
{
"family": "Sánchez López",
"given": "Andrea"
},
{
"family": "Varescon",
"given": "Camille"
},
{
"family": "Sakr",
"given": "Icare"
},
{
"family": "Bérard",
"given": "Nadia"
},
{
"family": "Bole-Feysot",
"given": "Lea"
},
{
"family": "Deschenaux",
"given": "Céline"
},
{
"family": "Enderli",
"given": "Ian"
},
{
"family": "Thenaisie",
"given": "Yohann"
},
{
"family": "Burri",
"given": "Morgane"
},
{
"family": "Merlos",
"given": "Frédéric"
},
{
"family": "Fleury",
"given": "Vanessa"
},
{
"family": "Wicki",
"given": "Benoit"
},
{
"family": "Accolla",
"given": "Ettore"
},
{
"family": "Tziakouri",
"given": "Andria"
},
{
"family": "Hübsch",
"given": "Cécile"
},
{
"family": "Castro Jiménez",
"given": "Mayte"
},
{
"family": "Bally",
"given": "Julien F"
},
{
"family": "Puiatti",
"given": "Alessandro"
},
{
"family": "Lee",
"given": "Kyuhwa"
},
{
"family": "Lorach",
"given": "Henri"
},
{
"family": "Collomb-Clerc",
"given": "Antoine"
},
{
"family": "Bloch",
"given": "Jocelyne"
},
{
"family": "Moraud",
"given": "Eduardo M"
}
],
"container-title-short":
"volume": "32",
"issue": "8",
"page": "2815-2830",
"DOI": "10.1038/
"PMID": "42297979",
"PMCID": "PMC13472876",
"ISSN": "1078-8956",
"publisher": "Nature Portfolio",
"URL": "https://
"language": "en",
"issued": {
"date-parts": [
[
2026,
6,
15
]
]
}
}
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.1038/s41591-026-04434-2 [code]
- Adaptive deep brain stimulation for dynamic gait control in Parkinson's disease: a randomized feasibility trial.Journal: Nature medicineIn common: scikit-learn, SciPy, Matplotlib, 1 other tool, Parkinson's, clinical / translational, 10 references
- [2] doi:10.1038/s44172-026-00688-3
- N2G calibrator: a cross-subject adversarial learning framework for neural signal-driven gait tracking in Parkinson's disease.Journal: Communications engineeringIn common: Parkinson's, 11 references
- [3] doi:10.1016/j.ebiom.2026.106293 [code]
- Dynamic neural states underpin motor symptom severity in Parkinson's disease: a longitudinal analysis of chronic cortico-subthalamic nucleus recordings.Journal: EBioMedicineIn common: SciPy, Matplotlib, NumPy, Parkinson's, 7 references
- [4] doi:10.1038/s41531-026-01421-9 [code]
- Cortico-pallidal beta dynamics underlie impaired turning in Parkinson's disease.Journal: NPJ Parkinson's diseaseIn common: Parkinson's, 8 references
- [5] doi:10.1038/s41531-026-01531-4 [code]
- Inconsistent subthalamic local field potential beta activity amid in- and antiphasic neuronal bursts.Journal: NPJ Parkinson's diseaseIn common: MNE-Python, h5py, scikit-learn, 3 other tools, Parkinson's, clinical / translational, 4 references
- [6] doi:10.1038/s41531-026-01372-1 [code]
- Varying patterns of association between cortical large-scale networks and subthalamic nucleus activity in Parkinson's disease.Journal: NPJ Parkinson's diseaseIn common: MNE-Python, OpenCV, h5py, 4 other tools, Parkinson's, 3 references
- [7] doi:10.1038/s41531-026-01439-z [code]
- DBSsync: combining intracranial and multimodal data to investigate new biomarkers in Parkinson's disease.Journal: NPJ Parkinson's diseaseIn common: MNE-Python, SciPy, Matplotlib, 1 other tool, Parkinson's, clinical / translational, 4 references
- [8] doi:10.1002/ana.78206 [code]
- Multimodal Image Guidance in Subthalamic Deep Brain Stimulation for Parkinson's Disease.Journal: Annals of neurologyIn common: h5py, SciPy, Matplotlib, 1 other tool, Parkinson's, clinical / translational, 5 references
- [9] doi:10.64898/2026.08.12.26350419 [code]
- Volitional deep brain stimulation following brain-computer interface training for Parkinson’s diseaseJournal: medRxiv (preprint)In common: SciPy, Matplotlib, NumPy, Parkinson's, clinical / translational, 5 references
- [10] doi:10.21203/rs.3.rs-9901682/v1
- An Implantable Digital Bridge Enables Arm And Hand Movements After TetraplegiaJournal: Research Square (preprint)In common: 2 authors
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.
Claim this paper
Correct its record
Say what each link of this record is, remove the ones that are not the paper's, add the ones that are missing. The correction becomes a new version of the record, in its Versions section.
Validate its tracing map
You validate the map as this page shows it: 2 repositories of the authors' code, each at its verified commit and with its license, 77 scripts, and 3 matches between paragraphs and code (see the Code and Map sections). It then receives a DOI on Zenodo, with you (your ORCID iD) and OSCR as its creators; the code itself is not deposited.
The map's fingerprint: sha256:24bd3c61c12e2256…
Add the badge to its README
The badge links the code to this page. Copy one of these into the README of the paper's code: only you decide where it goes, and nothing is changed for you.
Markdown
[, paste the snippet at the top, then “Commit changes…” and, to review it first, “Create a new branch and start a pull request”. You open the pull request; OSCR asks for no permission.
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.
