OSCR

Unveiling the Distinctive Brain Functional Dynamics Between Parkinson's Disease and Progressive Supranuclear Palsy.

Code ↔ Paper

4 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 4 matches
  1. [1] § Methods › Characterization of Brain States ↔ hmmleida/clustering/_clustering.py, lines 985–1055 · score 0.79 · Davies Bouldin, state sequence, model fit, distortion, concatenated, decoding
  2. [2] § Methods › Discriminative Power of Brain State Features ↔ hmmleida/plotting/_plotting.py, lines 707–789 · score 0.71 · static functional connectivity, Pearson correlation, static FC, rows, edge, matrices
  3. [3] § Methods › Characterization of Brain States ↔ hmmleida/signal_tools/_signal_tools.py, lines 66–133 · score 0.68 · unit variance, BOLD signals, fMRI, zero, sensitivity
  4. [4] § Methods › Discriminative Power of Brain State Features ↔ hmmleida/_data_load.py, lines 396–503 · score 0.62 · static functional connectivity, static FC, diagonal, Pearson, Class, matrices

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 · 1,055 lines · 35 KB · no license · 1 match

  1. import pandas as pd
  2. import numpy as np
  3. import os
  4. import matplotlib.pyplot as plt
  5. import seaborn as sns
  6. from hmmlearn import hmm
  7. from scipy.spatial.distance import cdist
  8. from sklearn.metrics.pairwise import manhattan_distances
  9. from sklearn.metrics import (
  10. silhouette_score,
  11. davies_bouldin_score,
  12. adjusted_mutual_info_score,
  13. adjusted_rand_score
  14. )
  15. from sklearn.model_selection import (
  16. train_test_split,
  17. KFold,
  18. StratifiedKFold
  19. )
  20. from sklearn.decomposition import PCA
  21. from scipy.spatial import Voronoi,voronoi_plot_2d
  22. import pickle
  23. from ..data_utils.validation import _check_isint
  24. class KMeansLeida():
  25. """
  26. K-Means algorithm with cosine or euclidean
  27. distance-based optimization.
  28. Params:
  29. -------
  30. k : int.
  31. The number of clusters to form as well as
  32. the number of centroids to generate.
  33. metric : str.
  34. Whether to use 'cosine' or 'euclidean' distance
  35. to optimize the centroids.
  36. n_init : int.
  37. Number of time the k-means algorithm will be
  38. run with different centroid seeds. The final
  39. results will be the best output of n_init
  40. consecutive runs in terms of inertia.
  41. n_iter : int.
  42. Maximum number of iterations of the k-means
  43. algorithm for a single run.
  44. Attributes:
  45. -----------
  46. cluster_centers_ : ndarray of shape (n_centroids,n_ROIs).
  47. Coordinates of cluster centers.
  48. labels_ : ndarray of shape (n_samples,).
  49. Labels of each point.
  50. inertia_ : float.
  51. Sum of squared distances of samples to their closest
  52. cluster center (aka distortion).
  53. """
  54. def __init__(self,k=2, metric='cosine',n_init=10,n_iter=1_000):
  55. #validation of input data
  56. if not isinstance(metric,str):
  57. raise TypeError("'metric' must be a string!")
  58. if metric not in ['euclidean','cosine']:
  59. raise ValueError("'metric' must be either 'cosine' or 'euclidean'")
  60. _check_isint({
  61. 'k':k,
  62. 'n_init':n_init,
  63. 'n_iter':n_iter
  64. })
  65. if k<2:
  66. raise ValueError("'k' must be > 1")
  67. self._k_ = k
  68. self._metric_ = metric
  69. self._n_iter_ = n_iter
  70. self._n_init_ = n_init
  71. def fit(self,y,random_state=None):
  72. """
  73. Compute k-means clustering.
  74. Params:
  75. --------
  76. y : ndarray of shape (n_samples,n_features).
  77. Training instances to cluster.
  78. random_state : int or None.
  79. Determines random number generation
  80. for centroid initialization. Use an
  81. int to make the randomness deterministic.
  82. Returns:
  83. --------
  84. self : object.
  85. Fitted estimator.
  86. """
  87. for init_idx in range(self._n_init_):
  88. #print(f'\tCurrent initialization: {init_idx+1}')
  89. if random_state is not None:
  90. np.random.seed(random_state)
  91. idx = np.random.choice(len(y), self._k_, replace=False)
  92. #Step 1. Randomly assigning first centroids positions
  93. centroids = y[idx, :]
  94. #Step 2. Finding the distance between centroids and all the data labels
  95. distances = cdist(y, centroids ,self._metric_)
  96. #Step 3. Predict each observation label based on the minimum Distance
  97. labels = np.array([np.argmin(i) for i in distances]) #Step 3
  98. #Step 4.Repeating the above steps for a defined number of iterations
  99. labels_all = []
  100. for iter in range(self._n_iter_):
  101. centroids = []
  102. for centroid_idx in range(self._k_):
  103. #Updating centroids by computing the mean of the observations in each cluster
  104. temp_cent = y[labels==centroid_idx].mean(axis=0)
  105. centroids.append(temp_cent)
  106. centroids = np.vstack(centroids) #Updated centroids
  107. distances = cdist(y, centroids ,self._metric_)
  108. labels = np.array([np.argmin(i) for i in distances])
  109. if iter!=0:
  110. if np.array_equal(labels,labels_all[-1]):
  111. #print(f'\tConverged at iteration n° {iter+1}')
  112. break
  113. labels_all.append(labels)
  114. #compute distortion on final centroids of current centroids initialization
  115. distortion = self._distortion(y,centroids)
  116. #update centroids,distortion and labels keeping always the best updated
  117. if init_idx == 0:
  118. best_distortion = distortion
  119. optimum_centroids = centroids
  120. optimum_labels = labels.copy()
  121. if distortion < best_distortion:
  122. best_distortion = distortion.copy()
  123. optimum_centroids = centroids.copy()
  124. optimum_labels = labels.copy()
  125. self.cluster_centers_ = optimum_centroids
  126. self.labels_ = optimum_labels
  127. self.inertia_ = best_distortion
  128. self._remap_labels()
  129. self._is_fitted = True
  130. def _check_is_fitted(self):
  131. """
  132. Check if the k-means model had been already fitted.
  133. """
  134. if not hasattr(self,"_is_fitted"):
  135. raise Exception("You have to fit the model first by using the 'fit' method.")
  136. def transform(self,y,closest=False):
  137. """
  138. Computes distances between each observation
  139. or data point and each centroid. Differs from
  140. sklearn transform method in that here we can select
  141. if retrieve all the distances or only the distances
  142. to closest centroid. In the new space, each dimension
  143. is the distance to the cluster centers.
  144. Params:
  145. --------
  146. y : ndarray of shape (n_samples,n_features).
  147. Data to transform.
  148. Returns:
  149. --------
  150. distances : ndarray of shape (n_samples,n_centroids).
  151. y transformed in the new space.
  152. """
  153. self._check_is_fitted()
  154. distances = cdist(y, self.cluster_centers_ ,self._metric_)
  155. if not closest:
  156. return distances
  157. else:
  158. return distances.min(1)
  159. def predict(self,y):
  160. """
  161. Assign cluster label to each data point in 'y'.
  162. Predict the closest cluster each sample in y belongs to.
  163. Params:
  164. -------
  165. y : ndarray of shape (n_samples,n_features).
  166. Data to predict.
  167. Returns:
  168. --------
  169. labels : ndarray of shape (n_samples,).
  170. Index of the cluster each sample belongs to.
  171. """
  172. self._check_is_fitted()
  173. labels = np.array([np.argmin(i) for i in self.transform(y)])
  174. return labels
  175. def _distortion(self,y,centroids):
  176. """
  177. Sum of squared distances of samples to their
  178. closest cluster center.
  179. Params:
  180. ------
  181. y : ndarray of shape (n_samples,n_features).
  182. Samples clustered.
  183. centroids : ndarray of shape (n_centroids,n_features).
  184. Computed centroids/prototypes.
  185. Returns:
  186. --------
  187. sum_sqr_dist : float.
  188. Computed distortion.
  189. """
  190. #distances = self.transform(y,closest=True)
  191. distances = cdist(y, centroids ,self._metric_).min(1)
  192. sqr_dist = distances**2
  193. sum_sqr_dist = np.sum(sqr_dist)
  194. return sum_sqr_dist
  195. def _remap_labels(self):
  196. """"
  197. After fitting the k-means model and making the
  198. labelling of each sample, this function computes
  199. the frequency of each cluster label across all samples,
  200. and relabels the original labels so that centroid 1 is
  201. always the cluster with higher number of observations.
  202. """
  203. label,freq = np.unique(self.labels_,return_counts=True)
  204. sorted_idxs = np.argsort(freq)[::-1]
  205. dct = {k:v for k,v in zip(sorted_idxs,label)}
  206. new_labels = np.array([dct[n] for n in self.labels_])
  207. self.labels_ = new_labels
  208. self.cluster_centers_ = self.cluster_centers_[sorted_idxs,:]
  209. def identify_states(eigens_dataset,K_min=2,K_max=20,n_init=15,random_state=None,plot=True,save_results=False,path=None):
  210. """
  211. Perform k-means clustering for each value of 'k' to identify the
  212. different (discrete) number of clusters (i.e.,the phase-locking
  213. states). At eack 'k', a k-means model is fitted and a cluster is
  214. assign to each eigenvector. These 'predictions' are located in a
  215. new column of the provided 'eigens_dataset'.
  216. In addition, each 'k' is evaluated by means of the Dunn score or
  217. Dunn index, distortion (aka sum squared errors), silhouete score,
  218. and Davies Bouldin score.
  219. Params:
  220. -------
  221. eigens : ndarray with shape (n_eigevectors, n_ROIs).
  222. Contains the extracted eigenvectors from the dFC matrices.
  223. K_max, K_min : int.
  224. Max and min number of clusters to fit.
  225. n_init : int.
  226. Number of times the clustering algorithm
  227. will be run with different centroid seeds.
  228. random_state : None or int.
  229. Determines random number generation for
  230. centroid initialization.
  231. Use an int to make the randomness deterministic.
  232. plot : bool.
  233. Whether to create plots showing the number
  234. of k in the X axis, and the Dunn mean score,
  235. Silhoutte score, distortion, and Davis Bouldin
  236. score for each k in the Y axis.
  237. save_results : bool.
  238. Whether to save results in '{path}/clustering'.
  239. path : str.
  240. Where to create the 'clustering' folder if
  241. 'save_results' was set to True.
  242. Returns:
  243. --------
  244. eigens_predictions : pd.dataframe.
  245. Contains the metadata (subjects_ids and conditions),
  246. and a column for each K partition is added containing
  247. the predicted label for each observation (row).
  248. clustering_performance : pd.dataframe.
  249. Contains the Dunn score, Silhouette score and distorion
  250. values for each K explored.
  251. models : dict.
  252. Contain the fitted k-means models.
  253. """
  254. if save_results:
  255. try:
  256. results_path = f'{path}/clustering'
  257. if not os.path.exists(results_path):
  258. os.makedirs(results_path)
  259. print(f"-Creating folder to save results: './{results_path}'")
  260. except:
  261. print("Warning: the folder to save the results could't be created.")
  262. #Execute the k-means models fitting process
  263. eigens_predictions = eigens_dataset[['subject_id','condition']] #copy provided data (to avoid overwriting)
  264. X = np.array(eigens_dataset.iloc[:,2:],dtype=np.float32) #keep array with the eigenvectors (remove 'subject_ids' and 'condition' columns)
  265. N_samples = X.shape[0]
  266. if N_samples>30_000:
  267. random_samples_idx = np.random.choice(np.arange(N_samples),size=30_000,replace=False)
  268. clustering_performance = []
  269. models = {} #dict to save each fitted model
  270. for k in range(K_min,K_max+1):
  271. print(f'k = {k}')
  272. kmeans = KMeansLeida(k=k,n_init=n_init,n_iter=1_000)
  273. kmeans.fit(X,random_state=random_state)
  274. models[f'k_{k}'] = kmeans
  275. clustering_performance.append({
  276. 'k':k,
  277. 'Dunn_score':dunn_fast(X,kmeans.labels_) if N_samples<30_000
  278. else dunn_fast(X[random_samples_idx,:],kmeans.labels_[random_samples_idx]),
  279. 'distortion':kmeans.inertia_,
  280. 'silhouette_score':silhouette_score(X=X, labels=kmeans.labels_, metric='cosine') if N_samples<30_000
  281. else silhouette_score(X=X[random_samples_idx,:], labels=kmeans.labels_[random_samples_idx], metric='cosine'),
  282. 'Davies-Bouldin_score':davies_bouldin_score(X, kmeans.labels_)
  283. })
  284. #predict each volume label
  285. eigens_predictions.loc[:,f'k_{k}'] = kmeans.labels_ #adding column with clusters assignment
  286. #save model to disk
  287. if save_results:
  288. models_folder = f'{results_path}/models'
  289. if not os.path.exists(models_folder):
  290. os.mkdir(models_folder)
  291. model_filename = f'model_k_{k}.pkl'
  292. pickle.dump(kmeans, open(f'{models_folder}/{model_filename}', 'wb'))
  293. clustering_performance = pd.DataFrame(clustering_performance)
  294. if plot:
  295. plt.ioff()
  296. plot_clustering_scores(clustering_performance)
  297. if save_results:
  298. try:
  299. plt.savefig(f'{results_path}/clustering_performance.png',dpi=300)
  300. except:
  301. print('The figures were not saved on local folder.')
  302. plt.close()
  303. #save results
  304. if save_results:
  305. try:
  306. clustering_performance.to_csv(f'{results_path}/clustering_performance.csv',sep='\t',index=False)
  307. eigens_predictions.to_csv(f'{results_path}/predictions.csv',sep='\t',index=False)
  308. except:
  309. print('The .csv files were not saved on disk. '
  310. 'You can still save them by yourself using the returned variables.')
  311. print('\n*The clustering process has succesfully ended.')
  312. return eigens_predictions,clustering_performance,models
  313. def centroid2matrix(centroids,plot=True,cmap='jet',darkstyle=False):
  314. """
  315. Take the controids resulting from the k-means
  316. clustering (i.e., the phase-locking states) and
  317. reconstruct the connectivity pattern in matrix
  318. format.
  319. Note: see Cabral et al. 2017 [p.4 and p.6].
  320. Params:
  321. -------
  322. centroids : ndarray with shape (n_centroids,n_rois).
  323. Clusters centroids of a specific K partition.
  324. plot : bool.
  325. Whether to create plot showing
  326. the matrices.
  327. cmap : str. Default = 'jet'.
  328. Select the colormap to use.
  329. darkstyle : bool.
  330. Whether to use a black background.
  331. Returns:
  332. --------
  333. states : ndarray with shape (n_rois,n_rois,n_centroids).
  334. PL states in matrix format.
  335. """
  336. if not isinstance(centroids,np.ndarray) or centroids.ndim!=2:
  337. raise TypeError("'centroids' must be a 2D array (n_centroids,n_rois)!")
  338. N_states, N_regions = centroids.shape
  339. states = np.empty((N_regions,N_regions,N_states))
  340. for state_idx in range(N_states):
  341. #scale centroid by its maximum value and transpose the matrix
  342. centroid = centroids[state_idx,:]/np.max(np.abs(centroids[state_idx,:]))
  343. states[:,:,state_idx] = np.outer(centroid,centroid.T)
  344. if plot:
  345. plt.ion()
  346. with plt.style.context('dark_background' if darkstyle else 'default'):
  347. _, axs = plt.subplots(
  348. nrows=1,
  349. ncols=N_states,
  350. figsize=(N_states*2, 2 if N_states<=10 else 1),
  351. edgecolor='black',
  352. subplot_kw=dict(box_aspect=1)
  353. )
  354. axs = axs.ravel()
  355. ticks_size = 6 if N_states<=10 else 5 if 10<N_states<=15 else 4
  356. for state_idx in range(N_states):
  357. sns.heatmap(
  358. states[:,:,state_idx],
  359. ax=axs[state_idx],
  360. vmax=1,
  361. vmin=-1,
  362. center=0,
  363. cmap=cmap,
  364. square=True,
  365. cbar=False
  366. )
  367. axs[state_idx].set_title(f'PL state {state_idx+1}',fontsize=7 if N_states<15 else 5)
  368. #axs[state_idx].set_xlabel(f'ROIs',fontsize=6.5)
  369. #axs[state_idx].set_ylabel(f'ROIs',fontsize=6.5)
  370. axs[state_idx].set_xticks(
  371. np.arange(20,N_regions,20),
  372. np.arange(20,N_regions,20).tolist(),
  373. rotation=0
  374. )
  375. axs[state_idx].set_yticks(
  376. np.arange(20,N_regions,20),
  377. np.arange(20,N_regions,20).tolist(),
  378. )
  379. axs[state_idx].tick_params(
  380. axis='both',
  381. which='both',
  382. bottom=False,
  383. left=False,
  384. top=False,
  385. #labelsize=5 if N_states<15 else 3.5,
  386. labelsize=ticks_size,
  387. pad=0
  388. #labelbottom=False,
  389. #labelleft=False
  390. )
  391. plt.tight_layout(pad=1,w_pad=1)
  392. plt.ioff()
  393. plt.show()
  394. return states
  395. def patterns_stability(X,y=None,n_clusters=None,folds=5,metric='ari',plot=True,darkstyle=False):
  396. """
  397. Run a stratified KFold cross-validation to explore
  398. the stability of assigned clusters across folds.
  399. The provided data in 'X' and is splitted in a train
  400. set and a test set. Then, a cross-validation is
  401. performed by splitting the train set into n number
  402. of 'folds'. At each iteration, a given fold is used to
  403. fit a KMeans model, which is used to assign a cluster
  404. label to each observation of the test set. Finally,
  405. the similarity between clusters assignments of the test
  406. set across folds is evaluated by using either the ARI
  407. (adjusted Rand index) score ('ari') or the AMI (adjusted
  408. mutual information) score ('ami').
  409. Params:
  410. -------
  411. X : ndarray with shape (n_volumes,n_rois).
  412. Contains the eigenvectors.
  413. y : ndarray with shape (n_volumes,). Optional.
  414. Clusters assignment of each observation in 'X'.
  415. If 'None', you must provide 'n_clusters'.
  416. n_clusters : int. Optional.
  417. Select the numbers of clusters to fit the
  418. K-Means models. If 'y' is not None, then
  419. the number of clusters is inferred from
  420. the provided array.
  421. folds : int.
  422. Select the number of folds for the
  423. cross-validation.
  424. metric : str.
  425. Metric to compute the similarity between
  426. clusters assignments across folds.
  427. plot : bool.
  428. Whether to create a heatmap showing the
  429. scores obtained in the cross-validation.
  430. darkstyle : bool.
  431. Whether to use a dark background for plotting.
  432. Returns:
  433. --------
  434. scores : ndarray with shape (n_folds,n_folds).
  435. Array containing the computed values of the
  436. selected metric for each pair of folds.
  437. """
  438. if not isinstance(metric,str) or metric not in ['ari','ami']:
  439. raise Exception("'metric' must be either 'ari' or 'ami'")
  440. if not isinstance(X,np.ndarray):
  441. raise TypeError("'X' must be a 2D array!")
  442. if y is None and n_clusters is None:
  443. raise Exception("You must specify either predicted labels in 'y' "
  444. "or a number of clusters ('n_clusters').")
  445. elif y is not None and n_clusters is not None:
  446. print("Warning: when 'y' is provided, the 'n_clusters' "
  447. "is inferred from 'y'.")
  448. if y is not None:
  449. if not isinstance(y,np.ndarray):
  450. raise TypeError("'y' must be an array!")
  451. n_clusters = np.unique(y).size
  452. X_tr,X_ts,y_tr,_ = train_test_split(X,y,stratify=y,train_size=.5)
  453. else:
  454. X_tr,X_ts = train_test_split(X,train_size=.5)
  455. #compute centroids on different folds
  456. kfold = StratifiedKFold(n_splits=folds) if y is not None else KFold(n_splits=folds)
  457. predictions_ = np.empty((X_ts.shape[0],folds))
  458. if y is not None:
  459. for i,(tr_idx,_) in enumerate(kfold.split(X_tr,y_tr)):
  460. print(f'Current fold: {i+1}')
  461. km = KMeansLeida(k=n_clusters)
  462. km.fit(X_tr[tr_idx])
  463. predictions_[:,i] = km.predict(X_ts)
  464. else:
  465. for i,(tr_idx,_) in enumerate(kfold.split(X_tr)):
  466. print(f'Current fold: {i+1}')
  467. km = KMeansLeida(k=n_clusters)
  468. km.fit(X_tr[tr_idx])
  469. predictions_[:,i] = km.predict(X_ts)
  470. #compute scores
  471. scores = np.empty((folds,folds))
  472. for fold_idx in range(folds):
  473. for fold_idx2 in range(folds):
  474. if metric=='ari':
  475. scores[fold_idx,fold_idx2] = adjusted_rand_score(
  476. predictions_[:,fold_idx],
  477. predictions_[:,fold_idx2]
  478. )
  479. else:
  480. scores[fold_idx,fold_idx2] = adjusted_mutual_info_score(
  481. predictions_[:,fold_idx],
  482. predictions_[:,fold_idx2]
  483. )
  484. font_sizes = {'folds':np.arange(2,21,1),'fsize':np.linspace(6,15,19)[::-1]}
  485. if plot:
  486. plt.ion()
  487. with plt.style.context('dark_background' if darkstyle else 'default'):
  488. matrix_ = np.triu(scores)
  489. plt.figure()
  490. sns.heatmap(
  491. scores[1:,:-1],
  492. vmin=0,
  493. center=.5,
  494. vmax=1,
  495. cmap='viridis',
  496. square=True,
  497. linecolor='black' if darkstyle else 'white',
  498. linewidths=.4,
  499. annot=True,
  500. annot_kws={'size':font_sizes['fsize'][folds-2]},
  501. mask=matrix_[1:,:-1],
  502. yticklabels=[f'{i+2}' for i in range(folds-1)],
  503. xticklabels=[f'{i+1}' for i in range(folds-1)],
  504. fmt='.2f',
  505. cbar_kws={'label': 'ARI score' if metric=='ari' else 'AMI score','shrink': 0.5}
  506. )
  507. plt.xlabel('Fold',fontsize=16)
  508. plt.ylabel('Fold',fontsize=16)
  509. plt.yticks(rotation=0)
  510. plt.tight_layout()
  511. return scores
  512. # Plotting functions
  513. def plot_clustering_scores(data):
  514. """
  515. Create a 2x2 panel with lineplots showing
  516. the clustering evaluation metrics for each
  517. explored k value (Dunn score, distortion,
  518. silhouette score, and Davis-Bouldin score).
  519. Params:
  520. -------
  521. data : pandas.dataframe.
  522. Contain k in the 1st col, with the
  523. rest of columns containing the values
  524. of the clustering evaluation metrics.
  525. """
  526. _,axs = plt.subplots(nrows=2,ncols=2,figsize=(11,6))
  527. axs = np.ravel(axs)
  528. for fig_idx,metric in enumerate(data.columns[1:].values):
  529. sns.lineplot(x='k',y=metric,data=data,ax=axs[fig_idx],linewidth=3)
  530. axs[fig_idx].set_xticks([i+2 for i in range(np.max(data.k)-1)])
  531. axs[fig_idx].set_xlabel('Number of clusters',fontsize=15)
  532. axs[fig_idx].set_ylabel(metric.replace('_',' '),fontsize=15)
  533. axs[fig_idx].grid(False)
  534. plt.tight_layout()
  535. def barplot_eig(eig,features_list):
  536. """
  537. Create barplot showing the values of the
  538. eigenvector for each brain region. This
  539. eigenvector could be either an eigenvector
  540. of a particular time point, or a cluster
  541. centroid.
  542. Params:
  543. -------
  544. eig : ndarray with shape (N_ROIs,).
  545. Contains the eigenvector to plot.
  546. features_list : list.
  547. Contains the names of the ROIs.
  548. Must be in the same order as in 'eig'.
  549. """
  550. # generating list of colors.
  551. # Positive values get red, negative values get blue
  552. cols = ['mediumblue' if i<0 else 'firebrick' for i in eig]
  553. plt.ion()
  554. plt.figure(figsize=(4,15))
  555. sns.barplot(y=features_list,x=eig,orient='h',palette=cols)
  556. plt.axvline(0,color='black')
  557. if np.max(np.abs(eig))>0.15:
  558. plt.xlim(-1.05,1.05)
  559. else:
  560. plt.xlim(-0.11,0.11)
  561. plt.xticks(fontweight='regular')
  562. plt.yticks(fontweight='regular',fontsize=8)
  563. plt.ylabel('Brain areas',rotation='vertical',fontsize=24,labelpad=40)
  564. plt.tight_layout()
  565. def barplot_states(centroids,rois_labels,save_path=None):
  566. """
  567. Create subplots with barplots showing
  568. the values of each cluster centroid.
  569. Params:
  570. ------
  571. centroids : ndarray with shape (n_centroids,n_rois).
  572. Contains the centroids.
  573. rois_labels : list.
  574. Contains the names of each ROI.
  575. """
  576. if not isinstance(centroids,np.ndarray):
  577. raise TypeError("'centroids' must be a numpy 2D array!")
  578. if centroids.shape[1]!=len(rois_labels):
  579. raise Exception("The number of brain regions in 'centroids' and 'rois_labels' must be the same!")
  580. N_centroids = centroids.shape[0]
  581. plt.ion()
  582. _,axs = plt.subplots(
  583. ncols=N_centroids,
  584. nrows=1,
  585. sharey=True,
  586. figsize=(N_centroids if N_centroids>4 else 4,8)
  587. )
  588. axs = np.ravel(axs)
  589. for fig_idx in range(N_centroids):
  590. # generating list of colors.
  591. # Positive values get red, negative values get blue
  592. cols = ['mediumblue' if i<0 else 'firebrick' for i in centroids[fig_idx,:]]
  593. sns.barplot(
  594. y=rois_labels,
  595. x=centroids[fig_idx,:],
  596. orient='h',
  597. palette=cols,
  598. ax=axs[fig_idx]
  599. )
  600. axs[fig_idx].axvline(0,color='black')
  601. if np.max(np.abs(centroids[fig_idx,:]))>0.15:
  602. axs[fig_idx].set_xlim(-1.05,1.05)
  603. else:
  604. axs[fig_idx].set_xlim(-0.11,0.11)
  605. #axs[fig_idx].set_xlim(-0.11,0.11)
  606. axs[fig_idx].tick_params(axis='y',labelsize=5)
  607. axs[fig_idx].tick_params(axis='x',labelsize=6 if N_centroids>5 else 3)
  608. axs[fig_idx].set_title(
  609. f'PL pattern {fig_idx+1}',
  610. fontweight='regular',
  611. fontsize=4,
  612. pad=10
  613. )
  614. axs[0].set_ylabel('Brain areas',rotation='vertical',fontsize=24,labelpad=25)
  615. plt.tight_layout()
  616. # plt.ioff()
  617. plt.show()
  618. if save_path is not None:
  619. plt.savefig(save_path)
  620. print(f"Figure saved to: {save_path}")
  621. plt.close()
  622. def plot_clusters3D(eigens,labels,clusters_colors=None,grid=True,alpha=.7,dot_size=3,edgecolor=None):
  623. """
  624. Visualize the identified clusters (brain states) in a
  625. 3D scatter plot, which constitutes a low-dimensional
  626. representation of the 'state space'.
  627. Method : take the eigenvectors and extract the first
  628. three principal components to reduce the dimensionality
  629. of the data to a 3D space. Each dot in the plot thus
  630. represents a single eigenvector, and is coloured according
  631. to the cluster it belongs to.
  632. Params:
  633. -------
  634. eigens : ndarray with shape (n_samples, n_rois).
  635. Contains the eigenvectors of each
  636. subject for each time point.
  637. labels : ndarray with shape (n_samples,).
  638. Contains the predicted cluster assignment
  639. for each eigenvector in 'eigens'.
  640. clusters_colors : list [optional].
  641. Provide a list with the desired color
  642. of each cluster. If not provided, then
  643. a predefined set of colors will be used.
  644. grid : bool.
  645. Whether to show grid or not.
  646. alpha : float.
  647. Set transparency of dots.
  648. dot_size : float or int.
  649. Set dot size.
  650. edge_color : None | str.
  651. Specify an edge color to use on dots.
  652. Returns:
  653. --------
  654. fig : generated plot.
  655. """
  656. if clusters_colors is not None:
  657. if np.unique(labels).size!=len(clusters_colors):
  658. raise ValueError('The number of clusters and the number of colours provided must match')
  659. else:
  660. clusters_colors = [
  661. 'royalblue',
  662. 'grey',
  663. 'tomato',
  664. 'orange',
  665. 'cyan',
  666. 'violet',
  667. 'yellow',
  668. 'purple',
  669. 'firebrick',
  670. 'teal',
  671. 'orchid',
  672. 'red',
  673. 'green',
  674. 'steelblue',
  675. 'indigo',
  676. 'gold',
  677. 'sienna',
  678. 'coral',
  679. 'olive',
  680. 'salmon'
  681. ]
  682. pca = PCA(n_components=3)
  683. pcs = pca.fit_transform(eigens)
  684. x_pcs = pd.concat((pd.DataFrame(pcs),pd.Series(labels)),axis=1)
  685. x_pcs.columns = np.array(['PC_1','PC_2','PC_3','Cluster'])
  686. #plotting
  687. fig = plt.figure(figsize=(8,8))
  688. ax = plt.axes(projection ="3d")
  689. for i,color in zip(np.unique(labels),clusters_colors):
  690. ax.scatter3D(
  691. x_pcs[x_pcs.Cluster==i]['PC_3'],
  692. x_pcs[x_pcs.Cluster==i]['PC_2'],
  693. x_pcs[x_pcs.Cluster==i]['PC_1'],
  694. c=color,
  695. edgecolors=edgecolor,
  696. s=dot_size,
  697. alpha=alpha
  698. )
  699. ax.w_xaxis.pane.fill = False
  700. ax.w_yaxis.pane.fill = False
  701. ax.w_zaxis.pane.fill = False
  702. ax.set_xlabel('3rd PC', fontsize=15, fontweight='regular',labelpad=20)
  703. ax.set_ylabel('2nd PC', fontsize=15, fontweight='regular',labelpad=20)
  704. ax.set_zlabel('1st PC', fontsize=15, fontweight='regular',labelpad=20)
  705. if not grid:
  706. ax.grid(False)
  707. plt.tight_layout()
  708. plt.show()
  709. return fig
  710. def plot_voronoi(centroids):
  711. """
  712. Plot the clusters centroids in a 2D Voronoi
  713. cells space. Performs a PCA to reduce the
  714. dimensionality of the original centroid space
  715. to a 2D space.
  716. Params:
  717. --------
  718. centroids : ndarray with shape (n_centroids, n_regions).
  719. Centroids of a particular K partition.
  720. """
  721. pcs = PCA(n_components=2).fit_transform(centroids)
  722. vor = Voronoi(pcs)
  723. plt.ion()
  724. voronoi_plot_2d(vor,point_size=10,show_vertices=False)
  725. plt.title(f'K = {centroids.shape[0]}')
  726. plt.tight_layout()
  727. def plot_clusters_boundaries(y,n_clusters=2,alpha=0.05):
  728. """
  729. Plot cluster centroids decision boundaries in
  730. 2D space after applying PCA.
  731. Params:
  732. -------
  733. y : ndarray with shape (n_samples, n_rois).
  734. Contains the eigenvectors.
  735. n_clusters : int.
  736. Specify the k number of clusters.
  737. alpha : float.
  738. Specify the transparency of the
  739. dots (observations).
  740. """
  741. ỹ = PCA(n_components=2).fit_transform(y) #embedding
  742. kmeans = KMeansLeida(k=n_clusters, n_init=4)
  743. kmeans.fit(ỹ)
  744. # Step size of the mesh. Decrease to increase the quality of the VQ.
  745. h = 0.0007 # point in the mesh [x_min, x_max]x[y_min, y_max].
  746. # Plot the decision boundary. For that, we will assign a color to each
  747. x_min, x_max = ỹ[:, 1].min(), ỹ[:, 1].max()
  748. y_min, y_max = ỹ[:, 0].min(), ỹ[:, 0].max()
  749. xx, yy = np.meshgrid(np.arange(x_min, x_max, h), np.arange(y_min, y_max, h))
  750. # Obtain labels for each point in mesh. Use last trained model.
  751. Z = kmeans.predict(np.c_[xx.ravel(), yy.ravel()])
  752. # Put the result into a color plot
  753. Z = Z.reshape(xx.shape)
  754. plt.ion()
  755. plt.figure(figsize=(8,8))
  756. #plt.clf()
  757. plt.imshow(
  758. Z,
  759. interpolation="nearest",
  760. extent=(xx.min(), xx.max(), yy.min(), yy.max()),
  761. cmap=plt.cm.Paired,
  762. aspect="auto",
  763. origin="lower",
  764. alpha=.9
  765. )
  766. # Plot dots (eigenvectors/samples)
  767. plt.plot(
  768. ỹ[:, 1],
  769. ỹ[:, 0],
  770. 'o',
  771. markersize=4,
  772. markerfacecolor='black',
  773. markeredgecolor='grey',
  774. markeredgewidth=.5,
  775. alpha=alpha
  776. )
  777. # Plot the centroids as a white X
  778. centroids = kmeans.cluster_centers_
  779. plt.scatter(
  780. centroids[:, 1],
  781. centroids[:, 0],
  782. marker="X",
  783. s=400,
  784. linewidths=3,
  785. color="black",
  786. zorder=10,
  787. edgecolors='w'
  788. )
  789. plt.title(
  790. f"K-means clustering (K={n_clusters}) on the eigenvectors (PCA-reduced data)"
  791. )
  792. plt.xlim(x_min, x_max)
  793. plt.xlabel("Centroids are marked with white cross.\nObservations are marked with black dots")
  794. plt.ylim(y_min, y_max)
  795. plt.xticks(())
  796. plt.yticks(())
  797. plt.tight_layout()
  798. # Compute Dunn Score functions
  799. def dunn_fast(points, labels):
  800. """
  801. Compute the Dunn index.
  802. Params:
  803. ----------
  804. points : ndarray with shape (N_samples,N_features).
  805. Observations/samples.
  806. labels : ndarray with shape (N_samples).
  807. Labels of each observation in 'points'.
  808. """
  809. distances = manhattan_distances(points)
  810. ks = np.sort(np.unique(labels))
  811. deltas = np.ones([len(ks), len(ks)])*1_000_000
  812. big_deltas = np.zeros([len(ks), 1])
  813. l_range = list(range(0, len(ks)))
  814. for k in l_range:
  815. for l in (l_range[0:k]+l_range[k+1:]):
  816. deltas[k, l] = _delta_fast((labels == ks[k]), (labels == ks[l]), distances)
  817. big_deltas[k] = _big_delta_fast((labels == ks[k]), distances)
  818. di = np.min(deltas)/np.max(big_deltas)
  819. return di
  820. def _delta_fast(ck, cl, distances):
  821. values = distances[np.where(ck)][:, np.where(cl)]
  822. values = values[np.nonzero(values)]
  823. return np.min(values)
  824. def _big_delta_fast(ci, distances):
  825. values = distances[np.where(ci)][:, np.where(ci)]
  826. #values = values[np.nonzero(values)]
  827. return np.max(values)
  828. def hmm_analysis(concatenated_dataset,K_min=2,K_max=20,n_init=20,random_state=None,plot=True,save_results=False,path=None):
  829. if save_results:
  830. try:
  831. results_path = f'{path}/clustering'
  832. if not os.path.exists(results_path):
  833. os.makedirs(results_path)
  834. print(f"-Creating folder to save results: './{results_path}'")
  835. except:
  836. print("Warning: the folder to save the results could't be created.")
  837. # Execute the k-means models fitting process
  838. predictions = concatenated_dataset[['subject_id', 'condition']] # copy provided data (to avoid overwriting)
  839. X = np.array(concatenated_dataset.iloc[:, 2:],
  840. dtype=np.float32) # keep array with the eigenvectors (remove 'subject_ids' and 'condition' columns)
  841. clustering_performance = []
  842. models = {} # dict to save each fitted model
  843. for k in range(K_min, K_max + 1):
  844. print(f'k = {k}')
  845. model = hmm.GaussianHMM(n_components=k, covariance_type='diag', n_iter=1000, tol=1e-6, random_state=seed)
  846. model.fit(X)
  847. models[f'k_{k}'] = model
  848. _, state_sequences = model.decode(X)
  849. means = model.means_
  850. distances = cdist(X, means, metric='euclidean')
  851. nearest_center_idx = np.argmin(distances, axis=1)
  852. distance_sum = np.sum(distances[np.arange(len(X)), nearest_center_idx] ** 2)
  853. clustering_performance.append({
  854. 'k': k,
  855. 'Dunn_score': dunn_fast(X, state_sequences),
  856. 'distortion': distance_sum,
  857. 'silhouette_score': silhouette_score(X=X, labels=state_sequences, metric='cosine'),
  858. 'Davies-Bouldin_score': davies_bouldin_score(X, state_sequences)
  859. })
  860. # predict each volume label
  861. predictions.loc[:, f'k_{k}'] = state_sequences # adding column with clusters assignment
  862. # save model to disk
  863. if save_results:
  864. models_folder = f'{results_path}/models'
  865. if not os.path.exists(models_folder):
  866. os.mkdir(models_folder)
  867. model_filename = f'model_k_{k}.pkl'
  868. pickle.dump(model, open(f'{models_folder}/{model_filename}', 'wb'))
  869. clustering_performance = pd.DataFrame(clustering_performance)
  870. if plot:
  871. plt.ioff()
  872. plot_clustering_scores(clustering_performance)
  873. if save_results:
  874. try:
  875. plt.savefig(f'{results_path}/clustering_performance.png', dpi=300)
  876. except:
  877. print('The figures were not saved on local folder.')
  878. plt.close()
  879. # save results
  880. if save_results:
  881. try:
  882. clustering_performance.to_csv(f'{results_path}/clustering_performance.csv', sep='\t', index=False)
  883. predictions.to_csv(f'{results_path}/predictions.csv', sep='\t', index=False)
  884. except:
  885. print('The .csv files were not saved on disk. '
  886. 'You can still save them by yourself using the returned variables.')
  887. print('\n*The clustering process has succesfully ended.')
  888. return predictions, clustering_performance, models

_clustering.py at commit 09ba8f9, no license · at the source

Overview

Authors: Chenfei Ye1, Jiahui Shi2, Jiliang Fang3, Tao Wu4,5, Piu Chan6,7,8,9, Jinghong Ma10, Ting Ma1
  1. School of Biomedical Engineering Harbin Institute of Technology (Shenzhen) Shenzhen China
  2. Department of Electronic and Information Engineering Harbin Institute of Technology (Shenzhen) Shenzhen China
  3. Department of Radiology Guang'anmen Hospital, China Academy of Chinese Medical Sciences Beijing China
  4. Department of Neurology, China National Clinical Research Center for Neurological Diseases Beijing Tiantan Hospital, Capital Medical University Beijing China
  5. Center for Movement Disorders, Department of Neurology Beijing Tiantan Hospital, Capital Medical University Beijing China
  6. Department of Neurology and Neurobiology Xuanwu Hospital Capital Medical University Beijing China
  7. Key Laboratory on Neurodegenerative Disorders of Ministry of Education Xuanwu Hospital Capital Medical University Beijing China
  8. Key Laboratory on Parkinson's Disease of Beijing Xuanwu Hospital Capital Medical University Beijing China
  9. Parkinson's Disease Center of Beijing Institute of Brain Disorders, Collaborative Innovation Center for Brain Disorders Xuanwu Hospital Capital Medical University Beijing China
  10. Department of Neurology Xuanwu Hospital of Capital Medical University Beijing China
Journal: CNS neuroscience & therapeutics, volume 32, issue 9, article e71147
Dates: received 13 January 2026; accepted 31 August 2026; published online 4 September 2026; in print September 2026
Type: Research article · Language: English
License: CC BY
Identifiers: DOI 10.1002/cns.71147 · PMID 42698292 · PMCID PMC13545542 · OpenAlex W7208805461
Open access: gold, a free copy (OpenAlex)
Status: code verified
Categories: human (organism), Parkinson's (population)
Methods: Connectivity, Statistics, Machine learning, fMRI & imaging
Keywords: hidden Markov models, network dynamics, non‐motor impairment, Parkinson's disease, progressive supranuclear palsy
MeSH: Brain*, Parkinson Disease*, Supranuclear Palsy, Progressive*, Aged, Brain Mapping, Female, Hidden Markov Models, Humans, Magnetic Resonance Imaging, Male, Middle Aged (* major topic)
Topic: Neurological disorders and treatments (Neurology, Medicine), according to OpenAlex
Funding: National Key Research and Development Program of China (2025YFF0517803); National Natural Science Foundation of China (62276081); Shenzhen Science and Technology Innovation Program (GXWD20231129121139001, JCYJ20240813110522029, JCYJ20250604145427037); Basic and Applied Basic Research Foundation of Guangdong Province (2023A1515010792, 2023B1515120065); Guangdong S&T Programme (2025B0101130004)
Citations: not cited yet (Europe PMC); 38 references in the paper

Abstract

Background: Dynamic analysis of resting‐state fMRI (rs‐fMRI) offers a novel approach to differentiate Parkinson's disease (PD) from progressive supranuclear palsy (PSP) by capturing temporal features of brain network activity, which may shed light on the mechanisms underlying non‐motor symptoms.

Objectives: To characterize differences in brain dynamics between PD and PSP using dynamic brain metrics, evaluate their exploratory discriminative performance, and investigate associations with non‐motor symptoms.

Methods: Sixty‐nine healthy controls, 82 PD patients, and 29 PSP patients underwent standardized clinical assessment and rs‐fMRI. Hidden Markov models extracted temporal features including fraction occurrence (FO), dwell time, and transition probability. These metrics were used for group comparisons, correlation analyses, and machine learning classification.

Results: PD and PSP showed opposite trends in fraction occurrence of unimodal network‐dominant states. Compared to PD and controls, PSP exhibited prolonged duration in the dorsal attention and limbic network (DAN&LIM) state and increased occurrence in the dorsal attention and frontoparietal control network (DAN&FPCN) state. Reduced unimodal state occupancy correlated with cognitive decline in both groups, while increased DAN&LIM and unimodal persistence linked to worse mood and sleep disturbances in PSP. Machine learning with these metrics achieved moderate accuracy in differentiating PD from PSP.

Conclusions: Temporal features of brain network dynamics may provide candidate imaging markers for distinguishing PD and PSP while offering mechanistic insights into non‐motor symptomatology. However, their diagnostic applicability requires validation in independent external multicenter cohorts.

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

netneurolab/neuromaps

License: CC-BY-NC-SA-4.0
State: the link answers, verified on 26 September 2026
Evidence: files inventoried
Commit: ffcc2e0f657943ce00a1b6a968396f32250e495c, 4 June 2026
Languages: Python (45), Shell (3)
Size: 90 files, 48 scripts
Software Heritage: archived
Found in: “Data Availability Statement”
Holds: README, license file, environment (Dockerfile, pyproject.toml, requirements.txt, setup.py, docs/requirements.txt), tests, continuous integration, documentation
Not found: CITATION.cff
Tools: neuromaps (29 files), NumPy (19 files), NiBabel (8 files), SciPy (7 files), Nilearn (5 files), scikit-learn (3 files), Matplotlib (2 files), BrainSMASH (1 file), BrainSpace (1 file), pandas (1 file)
Availability: 1 check, the latest on 26 September 2026: the link answers
  • 26 September 2026: the link answers
50 files

Shijh123456/PD_PSP

License: none: the authors keep all their rights
State: the link answers, verified on 26 September 2026
Evidence: files inventoried
Commit: 09ba8f96ac5c2945f7dcf34e4eb8c03183d5d9b3, 30 July 2026
Languages: Python (19)
Size: 55 files, 19 scripts
Software Heritage: not archived
Found in: “Data Availability Statement”
Holds: README
Not found: license file, CITATION.cff, environment file, tests, continuous integration, documentation
Tools: NumPy (12 files), Matplotlib (9 files), pandas (9 files), seaborn (9 files), SciPy (4 files), Nilearn (3 files), imageio (2 files), scikit-learn (2 files), NiBabel (1 file)
Availability: 1 check, the latest on 26 September 2026: the link answers
  • 26 September 2026: the link answers
20 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:

  • 2 repositories of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
  • 67 scripts, each with its path and the digest of its content;
  • 4 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

The data that support the findings of this study are available on request from the corresponding author. The data are not publicly available due to privacy or ethical restrictions. The codes for dynamic brain state analyses were conducted using the hmmlearn package (https://github.com/hmmlearn/hmmlearn) and modified from pyleida (https://psychomark.github.io/leida‐python/ (https://psychomark.github.io/leida-python/)). Codes for machine learning used the scikit‐learn package (https://github.com/scikit‐learn/scikit‐learn (https://github.com/scikit-learn/scikit-learn)). Sensitivity analyses used neuromaps (https://github.com/netneurolab/neuromaps). The complete codes are publicly available at https://github.com/Shijh123456/PD_PSP.

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

Recorded: type, language, journal, volume, issue, pages, dates, 7 authors, 5 keywords, 11 MeSH terms, 5 funders, 38 references.

Cite

This paper

Ye, C., Shi, J., Fang, J., Wu, T., Chan, P., Ma, J., & Ma, T. (2026). Unveiling the Distinctive Brain Functional Dynamics Between Parkinson's Disease and Progressive Supranuclear Palsy. CNS neuroscience & therapeutics, 32(9), e71147. https://doi.org/10.1002/cns.71147

BibTeX

@article{ye2026unveiling,
author = {Ye, Chenfei and Shi, Jiahui and Fang, Jiliang and Wu, Tao and Chan, Piu and Ma, Jinghong and Ma, Ting},
title = {{Unveiling the Distinctive Brain Functional Dynamics Between Parkinson's Disease and Progressive Supranuclear Palsy}},
journal = {CNS neuroscience \& therapeutics},
year = {2026},
month = sep,
volume = {32},
number = {9},
pages = {e71147},
publisher = {Wiley},
issn = {1755-5930},
doi = {10.1002/cns.71147},
url = {https://doi.org/10.1002/cns.71147},
pmid = {42698292},
pmcid = {PMC13545542}
}

RIS

TY - JOUR
AU - Ye, Chenfei
AU - Shi, Jiahui
AU - Fang, Jiliang
AU - Wu, Tao
AU - Chan, Piu
AU - Ma, Jinghong
AU - Ma, Ting
TI - Unveiling the Distinctive Brain Functional Dynamics Between Parkinson's Disease and Progressive Supranuclear Palsy
T2 - CNS neuroscience & therapeutics
J2 - CNS Neurosci Ther
PY - 2026
DA - 2026/09/01
VL - 32
IS - 9
SP - e71147
SN - 1755-5930
PB - Wiley
DO - 10.1002/cns.71147
UR - https://doi.org/10.1002/cns.71147
LA - en
ER -

CSL-JSON

{
"id": "10.1002/cns.71147",
"type": "article-journal",
"title": "Unveiling the Distinctive Brain Functional Dynamics Between Parkinson's Disease and Progressive Supranuclear Palsy",
"container-title": "CNS neuroscience & therapeutics",
"author": [
{
"family": "Ye",
"given": "Chenfei"
},
{
"family": "Shi",
"given": "Jiahui"
},
{
"family": "Fang",
"given": "Jiliang"
},
{
"family": "Wu",
"given": "Tao"
},
{
"family": "Chan",
"given": "Piu"
},
{
"family": "Ma",
"given": "Jinghong"
},
{
"family": "Ma",
"given": "Ting"
}
],
"container-title-short": "CNS Neurosci Ther",
"volume": "32",
"issue": "9",
"page": "e71147",
"DOI": "10.1002/cns.71147",
"PMID": "42698292",
"PMCID": "PMC13545542",
"ISSN": "1755-5930",
"publisher": "Wiley",
"URL": "https://doi.org/10.1002/cns.71147",
"language": "en",
"issued": {
"date-parts": [
[
2026,
9,
1
]
]
}
}

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/s41531-026-01354-3 [code]
Neuromodulation-induced normalization of cortical metastable dynamics signatures in Parkinson's disease.
Journal: NPJ Parkinson's disease
In common: neuromaps, BrainSpace, imageio, 8 other tools, Parkinson's, 6 references
[2] doi:10.1038/s41586-026-10631-3 [code]
A prognostic human brain network for diffuse midline glioma.
Journal: Nature
In common: BrainSMASH, neuromaps, BrainSpace, 8 other tools, 3 references
[3] doi:10.1186/s12916-026-04978-7 [code]
Mapping shared and specific cortical after-effects of repetitive TMS on brain function.
Journal: BMC medicine
In common: BrainSMASH, neuromaps, BrainSpace, 7 other tools, 4 references
[4] doi:10.1038/s41467-026-75959-w [code]
Charting higher-order models of brain function beyond pairwise interactions.
Journal: Nature communications
In common: neuromaps, BrainSpace, Nilearn, 7 other tools, 5 references
[5] doi:10.1038/s41398-026-04025-2 [code]
Brain energetic landscapes shape state dysregulation in major depressive disorder: a morphological network controllability perspective.
Journal: Translational psychiatry
In common: BrainSMASH, neuromaps, BrainSpace, 8 other tools, 2 references
[6] doi:10.1002/agm2.70073 [code]
Foreign Language Learning in Older Adults Modifies Resting-State Functional Connectivity Between the Subcortical Structures and the Cortex.
Journal: Aging medicine (Milton (N.S.W))
In common: BrainSMASH, neuromaps, BrainSpace, 8 other tools, 1 reference
[7] doi:10.1038/s41467-026-74153-2 [code]
Regional, functional and transcriptomic decoding of multidimensional brain structure alterations in obsessive-compulsive disorder.
Journal: Nature communications
In common: BrainSMASH, neuromaps, BrainSpace, 6 other tools, 2 references
[8] doi:10.7554/elife.103097 [code]
Canonical neurodevelopmental trajectories of structural and functional manifolds.
Journal: eLife
In common: neuromaps, BrainSpace, Nilearn, 5 other tools, 3 references
[9] doi:10.21203/rs.3.rs-9914920/v1 [code]
Prediction of cognitive performance by demographics, sleep, and brain morphometry: machine learning findings from ENIGMA-Sleep Working Group
Journal: Research Square (preprint)
In common: neuromaps, BrainSpace, Nilearn, 7 other tools, 1 reference
[10] doi:10.64898/2026.08.13.26360304 [code]
Lifespan brain structural variation reveals shared organization across mental health conditions
Journal: medRxiv (preprint)
In common: BrainSMASH, BrainSpace, Nilearn, 7 other tools

Contribute

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

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

Request its removal

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

Discussion, reproductions, activity

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

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

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