Intrinsic cortical geometry is associated with individual differences in local functional organization
The 5 matches
- [1] § Methods › Shared geometry embedding ↔ src/variograd_utils/embed_utils.py, lines 78–156 · score 0.68 · Laplacian eigenmaps, Cauchy kernel, reference matrices, affinity, embedding
- [2] § Methods › Functional connectivity gradients ↔ scripts/07a.connectivity_JE.py, lines 1–68 · score 0.67 · connectivity matrices, joint embedding, functional connectivity, thresholded, mapping, cortical
- [3] § Methods › MRI data ↔ scripts/06.downsample_timeseries.py, lines 1–62 · score 0.63 · fMRI, preprocessing, split, FWHM, smoothed, downsampled
- [4] § Methods › Functional connectivity gradients ↔ src/variograd_utils/embed_utils.py, lines 78–156 · score 0.59 · Pearson correlations, joint embedding, affinity, mapping, matrices
- [5] § Methods › MRI data ↔ src/variograd_utils/brain_utils.py, lines 238–281 · score 0.54 · fMRI, cortical surfaces, HCP, mesh, hemisphere, vertex
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 · 765 lines · 26 KB · MIT · 2 matches
- import warnings
- import numpy as np
- from scipy.sparse import diags
- from scipy.linalg import orthogonal_procrustes
- from scipy.optimize import linear_sum_assignment
- from sklearn.metrics.pairwise import cosine_similarity, euclidean_distances
- from sklearn.decomposition import TruncatedSVD
- from variograd_utils.core_utils import vector_wise_corr
- class JointEmbedding:
- """
- JointEmbedding class for computing the joint embedding of two matrices.
- Parameters:
- ----------
- method : str, optional
- The embedding method to use. Options are:
- - "dme": diffusion map embedding (default)
- - "le": Laplacian eigenmap
- n_components : int, optional
- Number of components to compute (default=2).
- alignment : str, optional
- The alignment method to use:
- - "procrustes": orthogonal Procrustes rotation with scaling.
- - "rotation": orthogonal Procrustes rotation (default)
- - "sort_flip": reorder the embedding dimensions maximizing absolute
- correlation coefficients with th independent refrence. Then multiply
- dimensions with a negative coefficient by -1.
- - "dot_product": align the embedding using the dot product of the joint reference
- and independent reference embeddings.
- random_state : int, optional
- Random seed of the SVDs.
- copy : bool, optional
- Whether to copy the input matrices (default=True).
- Attributes:
- ----------
- method : str
- The embedding method to use.
- n_components : int
- Number of components to compute.
- alignment : str
- The alignment method to use.
- random_state : int
- Random seed of the SVDs.
- copy : bool
- Whether to copy the input matrices.
- vectors : np.ndarray
- The eigenvectors of the embedding.
- lambdas : np.ndarray
- The eigenvalues of the embedding.
- independent_ref : np.ndarray
- The independent reference embedding used for alignment.
- Methods:
- --------
- fit_transform(M, R, C=None, affinity="cosine", scale=None, method_kwargs=None)
- Compute the joint embedding of M and R using the specified method.
- _joint_affinity_matrix(M, R, C=None, affinity="cosine", scale=None)
- Computes the joint affinity matrix.
- _align_embeddings(embedding, joint_reference, independent_reference, method="rotation")
- Align the joint embedding with the independently computed reference embedding.
- _affinity_matrix(M, method="cosine", scale=None)
- Compute the joint affinity matrix of the input data.
- """
- def __init__(self, method="dme", n_components=2, alignment=None,
- random_state=None, copy=True):
- self.method = method
- self.n_components = n_components
- self.random_state = random_state
- self.copy = copy
- self.alignment = alignment
- self.vectors = self.lambdas = None
- self.independent_ref = None
- def fit_transform(self, M, R, C=None, affinity="cosine", scale=None, method_kwargs=None):
- """
- Compute the joint embedding of M and R using the specified method.
- Parameters:
- ----------
- M : np.ndarray
- Target matrix to embed.
- R : np.ndarray
- Reference matrix.
- C : np.ndarray, optional
- Correspondence matrix.
- If affinity is not "precomputed", C is used as is and the
- specified affinity method is applied only to while M and R.
- affinity : str, optional
- The method to compute the affinity matrices. Options are:
- - "cosine": cosine similarity (default)
- - "correlation": Pearson correlation coefficient
- - "linear": linear kernel
- - "cauchy": Cauchy kernel
- - "gauss": Gaussian kernel
- - "precomputed": precomputed affinity matrix.
- In this case, M and R are assumed to be affinity matrices
- and a correspondence matrix C must be specified.
- scale : float, optional
- The scaling parameter for the kernel methods.
- method_kwargs : dict, optional
- Additional keyword arguments for the embedding method.
- Returns:
- -------
- self : JointEmbedding
- Fitted instance of JointEmbedding.
- embedding_M : np.ndarray
- The joint embedding of M.
- embedding_R : np.ndarray
- The joint embedding of R.
- Raises:
- -------
- ValueError
- If the affinity is "precomputed" and C is not specified.
- """
- if (affinity == "precomputed") & (C is None):
- raise ValueError("Precomputed affinity assumes M and R are already affinity matrices,"
- + "so a correspondance affinity matrx C must be specified too.")
- n = M.shape[0]
- R = np.array(R, copy=self.copy)
- M = np.array(M, copy=self.copy)
- if C is not None:
- C = np.array(C, copy=self.copy)
- A = self._joint_affinity_matrix(M, R, C=C, affinity=affinity, scale=scale)
- method_kwargs = {} if method_kwargs is None else method_kwargs
- embedding_function = diffusion_map_embedding if self.method == "dme" else laplacian_eigenmap
- embedding, vectors, lambdas = embedding_function(A, n_components=self.n_components,
- random_state=self.random_state,
- **method_kwargs)
- self.vectors = vectors
- self.lambdas = lambdas
- embedding_R = embedding[:n]
- embedding_M = embedding[n:]
- if self.alignment is not None:
- A = _affinity_matrix(R,method=affinity, scale=scale) if affinity != "precomputed" else R
- embedding_R_ind, _, _ = embedding_function(A, n_components=self.n_components,
- random_state=self.random_state,
- **method_kwargs)
- embedding_M, embedding_R = self._align_embeddings(embedding_M, embedding_R,
- embedding_R_ind,method=self.alignment)
- self.independent_ref = embedding_R_ind
- return embedding_M, embedding_R
- def _joint_affinity_matrix(self, M, R, C=None, affinity="cosine", scale=None):
- """
- Computes the joint affinity matrix.
- Parameters:
- ----------
- M : np.ndarray
- Target matrix to embed.
- R : np.ndarray
- Reference matrix.
- C : np.ndarray, optional
- Correspondence matrix of shape (n_samples_M, n_samples_R).
- If affinity is not "precomputed", C is used as is and the
- specified affinity method is applied only to while M and R.
- affinity : str, optional
- The method to compute the affinity matrices. Options are:
- - "cosine": cosine similarity (default)
- - "correlation": Pearson correlation coefficient
- - "linear": linear kernel
- - "cauchy": Cauchy kernel
- - "gauss": Gaussian kernel
- - "precomputed": precomputed affinity matrix.
- scale : float, optional
- The scaling parameter for the kernel methods.
- Returns:
- -------
- A : np.ndarray
- The joint affinity matrix.
- """
- if C is None:
- if affinity in ["cosine", "correlation"]:
- A = np.vstack([R, M])
- A = _affinity_matrix(A, method=affinity, scale=scale)
- elif affinity in ["linear", "cauchy", "gauss"]:
- M_aff = _affinity_matrix(M, method=affinity, scale=scale)
- R_aff = _affinity_matrix(R, method=affinity, scale=scale)
- C = pseudo_sqrt(np.dot(M_aff, R_aff), n_components=100)
- A = np.block([[R_aff, C.T],
- [C, M_aff]])
- else:
- if affinity == "precomputed":
- A = np.block([[R, C.T],
- [C, M]])
- else:
- A = np.block([[_affinity_matrix(R, method=affinity, scale=scale), C.T],
- [C, _affinity_matrix(M, method=affinity, scale=scale)]])
- return A
- def _align_embeddings(self, embedding, joint_reference, independent_reference,
- method="rotation"):
- """
- Align the joint embedding with the independently computed reference embedding.
- Parameters:
- ----------
- embedding : np.ndarray
- The joint embedding.
- reference : np.ndarray
- The reference embedding.
- method : str, optional
- The method used to align the joint embedding to the independently
- computed reference embedding:
- - "procrustes": orthogonal Procrustes rotation with scaling.
- - "rotation": orthogonal Procrustes rotation (default)
- - "sort_flip": reorder the embedding dimensions maximizing absolute
- correlation coefficients with th independent refrence. Then multiply
- dimensions with a negative coefficient by -1.
- - "dot_product": align the embedding using the dot product of the joint reference
- and independent reference embeddings.
- Returns:
- -------
- embedding : np.ndarray
- The aligned joint embedding.
- reference : np.ndarray
- The aligned reference embedding.
- """
- s = 1
- if method == "sort_flip":
- idx = argsort_axes(joint_reference.copy(), independent_reference.copy())
- joint_reference = joint_reference[:, idx]
- embedding = embedding[:, idx]
- to_flip = vector_wise_corr(joint_reference.copy(), independent_reference.copy()) < 0
- R = np.diag([-1 if i else 1 for i in to_flip])
- elif method == "dot_product":
- R = dot_product_rotation(joint_reference.copy(), independent_reference.copy())
- elif method in ["rotation", "procrustes"]:
- R, s = procrustes_rotation(joint_reference.copy(), independent_reference.copy())
- s = 1 if method == "rotation" else s
- else:
- raise ValueError(f"Unknown alignment method: {self.alignment}")
- joint_reference = np.dot(joint_reference, R) * s
- embedding = np.dot(embedding, R) * s
- return embedding, joint_reference
- def _affinity_matrix(M, method="cosine", scale=None):
- """
- Convert a distance measure to affinity.
- Parameters
- ----------
- M : array-like
- The input matrix of shape (samples, features)
- method : str
- The method to compute the affinity matrix. Options are:
- - "cosine": cosine similarity
- - "correlation": Pearson correlation coefficient
- - "linear": linear kernel
- - "cauchy": Cauchy kernel
- - "gauss": Gaussian kernel
- scale : float
- The scaling parameter for the kernel
- Returns
- -------
- A : array-like
- The affinity matrix
- """
- if method == "cosine":
- A = cosine_similarity(M)
- elif method == "correlation":
- A = np.corrcoef(M)
- elif method in {"linear", "cauchy", "gauss"}:
- # A = M if _is_square(M) else euclidean_distances(M)
- A = kernel_affinity(A, kernel=method, scale=scale)
- else:
- raise ValueError("Unknown affinity method")
- return A
- def kernel_affinity(A, kernel="linear", scale=None):
- """
- Apply kernel to a matrix A
- Parameters
- ----------
- A : array-like
- The input matrix of shape smaples x features
- kernel : str
- The kernel to apply. Options are "cauchy", "gauss", "linear"
- scale : float
- The scaling parameter for the kernel
- Returns
- -------
- A : array-like
- The kernelized matrix
- """
- if scale is None:
- scale = 1 / A.shape[1]
- if kernel == "cauchy":
- A = 1.0 / (1.0 + (A ** 2) / (scale ** 2))
- elif kernel == "gauss":
- A = np.exp(-0.5 * (A ** 2) / (scale ** 2))
- elif kernel == "linear":
- A = 1 / (1 + A / scale)
- else:
- raise ValueError("Unknown kernel type")
- return A
- def spectral_affinity(M, R, n_components=2, random_state=None):
- """
- Compute the spectral similarity between two matrices using Laplacian Eigenmaps and Procrustes alignment.
- This function calculates the cosine similarity between Laplacian Eigenmaps of two matrices,
- `M` and `R` of shape observations x features. The embeddings are aligned using
- the Orthogonal Procrustes transformation before computing similarity.
- Parameters
- ----------
- M : numpy.ndarray
- The first input matrix.
- R : numpy.ndarray
- The second input matrix (also target of the procrustes alignment).
- n_components : int, optional
- Number of dimensions for the Laplacian Eigenmap embeddings. Default is 2.
- random_state : int, optional
- Determines random number generation for eigenmap embedding. Default is None.
- Returns
- -------
- numpy.ndarray
- A cosine similarity matrix representing the similarity between aligned
- embeddings of `M` and `R`.
- """
- embedding_M, _, _ = laplacian_eigenmap(M, n_components=n_components, normalized=True, random_state=random_state)
- embedding_R, _, _ = laplacian_eigenmap(R, n_components=n_components, normalized=True, random_state=random_state)
- embedding_M -= embedding_M.mean(axis=0)
- embedding_M /= np.linalg.norm(embedding_M)
- embedding_R -= embedding_R.mean(axis=0)
- embedding_R /= np.linalg.norm(embedding_R)
- rotation, scaling = orthogonal_procrustes(embedding_M, embedding_R)
- embedding_M = np.dot(embedding_M, rotation) * scaling
- A = cosine_similarity(embedding_M, embedding_R)
- return A
- def diffusion_map_embedding(A, n_components=2, alpha=0.5, diffusion_time=1, random_state=None):
- """
- Computes the joint diffusion map embedding of an affinity matrix.
- Parameters:
- ----------
- A : np.ndarray
- Target matrix to embed.
- n_components: int, optional
- Number of components to keep (default=2).
- alpha: float, optional
- Controls Laplacian normalization, balancing local and global structures in embeddings.
- Alpha <= 0.5 emphasize local structures, alpha >= 1 focus on global organization.
- diffusion_time: float, optional
- Determines the scale of the random walk process (default=1).
- random_state: int, optional
- Random seed of the SVD.
- Returns
- -------
- embedding : numpy.ndarray of shape (n_samples, n_components)
- The diffusion map embedding computed from the random walk Laplacian.
- vectors : numpy.ndarray of shape (n_samples, n_components)
- The corresponding singular vectors (or eigenvectors) used for the embedding.
- lambdas : numpy.ndarray of shape (n_components,)
- The singular values (or eigenvalues) associated with the embedding.
- """
- if np.any(A < 0):
- warnings.warn(f"{np.sum(A < 0)} negative values in the affinity matrix set to 0. "
- + f"Mean negative value: {np.mean(A[A < 0])}({np.std(A[A < 0])})",
- RuntimeWarning)
- A[A<0] = 0
- L = _random_walk_laplacian(A, alpha=alpha)
- embedding, vectors, lambdas = _diffusion_map(L, n_components=n_components,
- diffusion_time=diffusion_time,
- random_state=random_state)
- return embedding, vectors, lambdas
- def _diffusion_map(L, n_components=2, diffusion_time=1, random_state=None):
- """
- Computes the diffusion map of the random walk Laplacian L.
- Parameters:
- ----------
- L: np.ndarray
- Random walk Laplacian.
- n_components: int, optional
- Number of components to compute (default=2).
- diffusion_time: float, optional
- Determines the scale of the random walk process (default=1).
- random_state: int, optional
- Random seed of the SVD.
- Returns
- -------
- embedding : numpy.ndarray of shape (n_samples, n_components)
- The diffusion map embedding computed from the random walk Laplacian.
- vectors : numpy.ndarray of shape (n_samples, n_components)
- The corresponding singular vectors (or eigenvectors) used for the embedding.
- lambdas : numpy.ndarray of shape (n_components,)
- The singular values (or eigenvalues) associated with the embedding.
- """
- n_components = n_components + 1
- embedding = TruncatedSVD(n_components=n_components,
- random_state=random_state
- ).fit(L)
- vectors = embedding.components_.T
- lambdas = embedding.singular_values_
- if np.any(vectors[:, 0] == 0):
- warnings.warn("0 values found in the first eigenvector; 1e-15 was added to all vectors.",
- RuntimeWarning)
- vectors += 1e-16
- psi = vectors / np.tile(vectors[:, 0], (vectors.shape[1], 1)).T
- lambdas[1:] = np.power(lambdas[1:], diffusion_time)
- embedding = psi[:, 1:n_components] @ np.diag(lambdas[1:n_components], 0)
- lambdas = lambdas[1:]
- vectors = vectors[:, 1:]
- return embedding, vectors, lambdas
- def _random_walk_laplacian(A, alpha=0.5):
- """
- Computes the random walk Laplacian of an affinity matrix A.
- Parameters:
- ----------
- A: np.ndarray
- Affinity matrix.
- alpha: float, optional
- Controls Laplacian normalization, balancing local and global structures in embeddings.
- Alpha <= 0.5 emphasize local structures, alpha >= 1 focus on global organization.
- Returns:
- -------
- L : np.ndarray
- The random walk Laplacian of A.
- """
- if (A.shape[0] != A.shape[1]) or not np.allclose(A, A.T):
- raise ValueError("A must be a squared, symmetrical affinity matrix.")
- # Compute the normalized Laplacian
- degree = np.sum(A, axis=1)
- d_alpha = diags(np.power(degree, -alpha))
- L_alpha = d_alpha @ A @ d_alpha
- # Compute the random walk Laplacian
- degree = np.sum(L_alpha, axis=1)
- d_alpha = diags(np.power(degree, -1))
- L = d_alpha @ L_alpha
- return L
- def laplacian_eigenmap(A, n_components=2, normalized=True, random_state=None):
- """
- Computes the spectral embedding of an affinity matrix.
- Parameters:
- ----------
- A : np.ndarray
- Target matrix to embed.
- n_components: int, optional
- Number of components to compute (default=2).
- normalized: bool, optional
- Whether to normalize the Laplacian matrix (default=True).
- random_state: int, optional
- Random seed of the SVD.
- Returns
- -------
- embedding : numpy.ndarray of shape (n_samples, n_components)
- The laplaician eigenmap embedding of L.
- vectors : numpy.ndarray of shape (n_samples, n_components)
- The corresponding singular vectors (or eigenvectors) used for the embedding.
- lambdas : numpy.ndarray of shape (n_components,)
- The singular values (or eigenvalues) associated with the embedding.
- """
- if np.any(A < 0):
- warnings.warn(f"{np.sum(A < 0)} negative values in the affinity matrix set to 0. "
- + f"Mean negative value: {np.mean(A[A < 0])}({np.std(A[A < 0])})",
- RuntimeWarning)
- A[A<0] = 0
- L = _laplacian(A, normalized=normalized)
- n_components = n_components + 1
- svd = TruncatedSVD(n_components=n_components, random_state=random_state)
- embedding = svd.fit_transform(L)[:, 1:]
- vectors = svd.components_.T[:, 1:]
- lambdas = svd.singular_values_[1:]
- return embedding, vectors, lambdas
- def _laplacian(A, normalized=True):
- """
- Computes the Laplacian of an affinity matrix A.
- Parameters:
- ----------
- A : np.ndarray
- Affinity matrix.
- normalized : bool, optional
- Compute the normalized Laplacian (default=True).
- Returns:
- -------
- L : np.ndarray
- Laplacian matrix of M.
- """
- if (A.shape[0] != A.shape[1]) or not np.allclose(A, A.T):
- raise ValueError("A must be a squared, symmetrical affinity matrix.")
- # Calculate Laplacian
- D = np.sum(A, axis=1)
- L = D - A
- # Normalize Laplacian
- if normalized:
- D_inv_sqrt = 1.0 / np.sqrt(D)
- D_inv_sqrt[np.isinf(D_inv_sqrt)] = 0
- D_inv_sqrt = np.diag(D_inv_sqrt)
- L = D_inv_sqrt @ (L @ D_inv_sqrt)
- return L
- def pseudo_sqrt(X, n_components=100):
- """
- Compute the pseudo-square root of a symmetric matrix.
- This function computes a low-rank approximation of the pseudo-square root of
- a symmetric matrix using Singular Value Decomposition (SVD).
- Parameters
- ----------
- X : numpy.ndarray
- The input square matrix to compute the pseudo-square root for.
- n_components : int, optional
- The number of components to retain in the low-rank approximation.
- Default is 100.
- Returns
- -------
- numpy.ndarray
- The approximated symmetric pseudo-square root of the input matrix.
- Notes
- -----
- - If `X` is not symmetric, it is symmetrized as `(X + X.T) / 2`.
- - The pseudo-square root is computed as `U * sqrt(S) * U.T`, where `U` and `S`
- - The method assumes `X` has a valid SVD decomposition.
- Raises
- ------
- ValueError
- If the input matrix `X` is not square.
- """
- if (X.shape[0] != X.shape[1]) or (X.ndim != 2):
- raise ValueError("Input matrix X must be square.")
- if not np.allclose(X, X.T, atol=1e-10):
- X = (X + X.T) / 2
- svd = TruncatedSVD(n_components=n_components)
- svd.fit(X)
- U = svd.components_.T
- S = np.diag(svd.singular_values_)
- Ssqrt = S ** 0.5
- return np.dot(np.dot(U, Ssqrt), U.T)
- def dot_product_rotation(M, R):
- """
- Compute the rotated dot product matrix between two datasets.
- This function takes two matrices, `M` and `R`, and computes the roation matrix
- necessary to align them using their dot product.
- Parameters
- ----------
- M : numpy.ndarray
- A 2D array of shape `(n_samples, n_features)`. The matrix to be rotated.
- R : numpy.ndarray
- A 2D array of shape `(n_samples, n_features)`. The target reference matrix.
- Returns
- -------
- numpy.ndarray
- A 2D array of shape `(n_features, n_features)`. The rotation matrix from M to R.
- Notes
- -----
- - The input matrices `M` and `R` must have the same shape.
- - This function normalizes each row (sample) to have unit norm, ensuring that the dot
- product represents cosine similarity between the corresponding features.
- Raises
- ------
- ValueError
- If the shapes of `M` and `R` do not match, or if any row contains only zeros.
- """
- if M.shape != R.shape:
- raise ValueError("Input matrices M and R must have the same shape.")
- if np.any(np.linalg.norm(M, axis=1) == 0) or np.any(np.linalg.norm(R, axis=1) == 0):
- raise ValueError("Rows of M and R must not be all zeros.")
- R -= R.mean(axis=1, keepdims=True)
- M -= M.mean(axis=1, keepdims=True)
- R /= np.linalg.norm(R, axis=1, keepdims=True)
- M /= np.linalg.norm(M, axis=1, keepdims=True)
- rotation_mat = np.dot(M.T, R)
- return rotation_mat
- def procrustes_rotation(M, R):
- """
- Compute the rotation matrix and scaling factor to align a matric M to a reference R
- solving the orthogonal Procrustes problem. This is essentially a wrapper over
- scipy.linalg.orthogonal_procrustes() that includes the normalization step (see
- scipy.spatial.procrustes()).
- Parameters
- ----------
- M : numpy.ndarray
- A 2D array of shape `(n_samples, n_features)`. The matrix to be rotated.
- R : numpy.ndarray
- A 2D array of shape `(n_samples, n_features)`. The target reference matrix.
- Returns
- -------
- R : (N, N) ndarray
- A 2D array of shape `(n_features, n_features)`. The rotation matrix from M to R.
- scale : float
- Sum of the singular values of ``A.conj().T @ B``.
- Raises
- ------
- ValueError
- If the shapes of `M` and `R` do not match, or if any row contains only zeros.
- """
- if M.shape != R.shape:
- raise ValueError("Input matrices M and R must have the same shape.")
- if np.any(np.linalg.norm(M, axis=1) == 0) or np.any(np.linalg.norm(R, axis=1) == 0):
- raise ValueError("Rows of M and R must not be all zeros.")
- R -= R.mean(axis=0)
- R /= np.linalg.norm(R)
- M -= M.mean(axis=0)
- M /= np.linalg.norm(M)
- return orthogonal_procrustes(M, R)
- def argsort_axes(M, R):
- """
- Determine optimal axis reordering of a subject embedding to match a reference embedding.
- This function computes the absolute Pearson correlation between each pair of axes
- in the subject embedding `M` and reference embedding `R`, and solves the optimal
- one-to-one axis assignment using the Hungarian algorithm to maximize total correlation.
- Parameters
- ----------
- M : numpy.ndarray
- A 2D array of shape `(n_samples, n_features)`. The embedding
- dimensions to reorder.
- R : numpy.ndarray
- A 2D array of shape `(n_samples, n_features)`. The target
- reference embedding.
- Returns
- -------
- reorder_idx : ndarray of shape (n_dims,)
- Indices that reorder the axes of `M` to best match the axes of `R`
- in correlation magnitude. Use as: `M[:, reorder_idx]`.
- Notes
- -----
- - Sign alignment (i.e., flipping axes) is not handled here.
- - Axis correspondence is determined using absolute Pearson correlation.
- See Also
- --------
- vector_wise_corr : Computes vector-wise Pearson correlation.
- """
- ndims = M.shape[1]
- if R.shape[1] != ndims:
- raise ValueError("Both embeddings must have the same number of dimensions")
- S = np.array([vector_wise_corr(R, m.reshape(-1,1)) for m in M.T])
- return linear_sum_assignment(abs(S), maximize=True)[1]
embed_utils.py at commit c1a6f0b, under MIT · at the source
Overview
- Université Paris Cité, INCC UMR 8002, CNRS, Paris, France
- Centre for Integrative Neuroimaging (OxCIN), FMRIB, Nuffield Department of Clinical Neurosciences, University of Oxford
- Full Brain Picture Analytics, Leiden, Netherlands
- Wilhelm Wundt Institute for Psychology, Leipzig University, Leipzig, Germany
- Max Planck School of Cognition, Leipzig, Germany
- National Scientific and Technical Research Council (CONICET), Argentina
- Cognitive Neuroscience Center, University of San Andres, Buenos Aires, Argentina
- Department of Neuroimaging, King’s College London, UK
Abstract
It is widely accepted that the geometry of the cerebral cortex constrains its functional topography. However, how geometric properties contribute to individual differences in cortical organization has not been fully characterized. Here, we investigate whether cortical function varies with cortical geometry across individuals at the local or global scale. We characterize functional organization using the first three gradients of functional connectivity, and project individual surfaces into a shared embedding that captures their intrinsic geometry. Fitting localized spatial models within this embedding via lattice Kriging, we test whether interindividual variation of the gradients is linked to differences in spatial location. These models capture a common spatial structure underlying local gradient transitions across individuals, but do not capture differences in global gradient layout. This suggests that universal geometric properties shape the functional transitions between otherwise stable functional systems, meaningfully contributing to subject-specific functional topography.
Reproduced under the paper's license (CC BY), from the paper cited above.
Repository
Its files are read in the Code ↔ Paper reader above, with 5 matches between paragraphs and lines of code.
alberti-f/VarioGrad
c1a6f0b1ba34caeafac778cb97a6d671fbf9c7c9, 23 June 2026Availability: 1 check, the latest on 29 September 2026: the link answers
- 29 September 2026: the link answers
38 files
- LatticeKriging/
LK.analysis.R , R, 161 lines - LatticeKriging/
modules/ , R, 34 linesactivate_environment.R - LatticeKriging/
modules/ , R, 38 linescompute_local_averages.R - LatticeKriging/
modules/ , R, 65 linesevaluate_LK_model.R - LatticeKriging/
modules/ , R, 49 linesfit_LK_model.R - LatticeKriging/
modules/ , R, 52 linesload_data.R - LatticeKriging/
modules/ , R, 149 linessave_results.R - LatticeKriging/
modules/ , R, 137 linesselect_searchlight_domai n.R - LatticeKriging/
modules/ , R, 56 linessetup_args.R - LatticeKriging/
modules/ , R, 49 linessetup_environment.R - LatticeKriging/
modules/ , R, 116 linestests.R - notebooks/
Figures_cluster.ipynb , Jupyter, 365 lines - notebooks/
Figures_local.ipynb , Jupyter, 948 lines - notebooks/
stat_tests.ipynb , Jupyter, 1,055 lines - scripts/
00a.train_test_split.py , Python, 87 lines - scripts/
00b.init_dataset.py , Python, 58 lines - scripts/
01.create_cortical_mask. , Python, 104 linespy - scripts/
02a.downsample_surfaces. , Python, 32 linespy - scripts/
02b.average_downsampled_ , Python, 81 linessurface.py - scripts/
03.remove_medial_wall.py , Python, 59 lines - scripts/
04.geodesic_distance.py , Python, 68 lines - scripts/
05a.geometry_JE.py , Python, 102 lines - scripts/
05b.create_geoJE_archive , Python, 84 liness.py - scripts/
05c.create_searchlights. , Python, 94 linespy - scripts/
06.downsample_timeseries , Python, 189 lines, 1 match.py - scripts/
07a.connectivity_JE.py , Python, 133 lines, 1 match - scripts/
07b.create_gradient_arra , Python, 51 linesys.py - src/
variograd_utils/ , Python, 8 lines__init__.py - src/
variograd_utils/ , Python, 389 lines, 1 matchbrain_utils.py - src/
variograd_utils/ , Python, 1,085 linescore_utils.py - src/
variograd_utils/ , Python, 765 lines, 2 matchesembed_utils.py - src/
variograd_utils/ , Python, 105 linesloadsave_utils.py - src/
variograd_utils/ , Python, 446 linessurfplotter.py - src/
variograd_utils/ , Python, 103 linestest_utils.py - src/
variograd_utils/ , Python, 590 linesvariography_utils.py - src/
variograd_utils/ , Python, 252 linesviz_utils.py - LICENSE.txt, License, 21 lines
- README.md, Text, 48 lines
Code Availability
The HCP preprocessing pipelines can be found at https://
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:
- 1 repository of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
- 36 scripts, each with its path and the digest of its content;
- 5 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
Data were provided by the Human Connectome Project, WU-Minn Consortium (Principal Investigators: David Van Essen and Kamil Ugurbil; 1U54MH091657) funded by the 16 NIH Institutes and Centers that support the NIH Blueprint for Neuroscience Research; and by the McDonnell Center for Systems Neuroscience at Washington University. All data are obtainable from the HCP website (https://
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, 29 September 2026: the first record
Recorded: type, language, journal, dates, 11 authors, 1 keyword, 2 funders, 37 references.
Cite
This paper
Alberti, F., Bazin, P.-L., Benn, R. A., Scholz, R., Wei, W., Holmes, A., Shevchenko, V., Klatzmann, U., Pallavicini, C., Leech, R., & Margulies, D. S. (2026). Intrinsic cortical geometry is associated with individual differences in local functional organization. Research Square (preprint). https://
BibTeX
@article{alberti2026intr
author = {Alberti, Francesco and Bazin, Pierre-Louis and Benn, R. Austin and Scholz, Robert and Wei, Wei and Holmes, Alexander and Shevchenko, Victoria and Klatzmann, Ulysse and Pallavicini, Carla and Leech, Robert and Margulies, Daniel S.},
title = {{Intrinsic cortical geometry is associated with individual differences in local functional organization}},
journal = {Research Square (preprint)},
year = {2026},
month = apr,
publisher = {Research Square},
issn = {2693-5015},
doi = {10.21203/
url = {https://
}
RIS
TY - JOUR
AU - Alberti, Francesco
AU - Bazin, Pierre-Louis
AU - Benn, R. Austin
AU - Scholz, Robert
AU - Wei, Wei
AU - Holmes, Alexander
AU - Shevchenko, Victoria
AU - Klatzmann, Ulysse
AU - Pallavicini, Carla
AU - Leech, Robert
AU - Margulies, Daniel S.
TI - Intrinsic cortical geometry is associated with individual differences in local functional organization
T2 - Research Square (preprint)
J2 - Res Sq
PY - 2026
DA - 2026/
SN - 2693-5015
PB - Research Square
DO - 10.21203/
UR - https://
LA - en
ER -
CSL-JSON
{
"id": "10.21203/
"type": "article",
"title": "Intrinsic cortical geometry is associated with individual differences in local functional organization",
"container-title": "Research Square (preprint)",
"author": [
{
"family": "Alberti",
"given": "Francesco"
},
{
"family": "Bazin",
"given": "Pierre-Louis"
},
{
"family": "Benn",
"given": "R. Austin"
},
{
"family": "Scholz",
"given": "Robert"
},
{
"family": "Wei",
"given": "Wei"
},
{
"family": "Holmes",
"given": "Alexander"
},
{
"family": "Shevchenko",
"given": "Victoria"
},
{
"family": "Klatzmann",
"given": "Ulysse"
},
{
"family": "Pallavicini",
"given": "Carla"
},
{
"family": "Leech",
"given": "Robert"
},
{
"family": "Margulies",
"given": "Daniel S."
}
],
"container-title-short":
"DOI": "10.21203/
"ISSN": "2693-5015",
"publisher": "Research Square",
"URL": "https://
"language": "en",
"issued": {
"date-parts": [
[
2026,
4,
8
]
]
}
}
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/s41467-026-71270-w [code]
- Spatiotemporal dynamics of the human cortical functional hierarchy across the lifespan.Journal: Nature communicationsIn common: Connectome Workbench, h5py, NiBabel, 6 other tools, 5 references
- [2] doi:10.1038/s41467-026-76011-7 [code]
- Human cortex organizes dynamic co-fluctuations along the sensorimotor-association
axis. Journal: Nature communicationsIn common: Connectome Workbench, NiBabel, SciPy, 2 other tools, 7 references - [3] doi:10.1038/s41467-026-72931-6 [code]
- Three parsimonious spatiotemporal patterns in cerebellum reveal individual traits in function and behavior.Journal: Nature communicationsIn common: Connectome Workbench, NiBabel, statsmodels, 6 other tools, 4 references
- [4] doi:10.7554/elife.103097 [code]
- Canonical neurodevelopmental trajectories of structural and functional manifolds.Journal: eLifeIn common: reticulate, h5py, NiBabel, 5 other tools, 4 references
- [5] doi:10.21203/rs.3.rs-9326213/v1 [code]
- Multi-task fMRI outperforms resting-state fMRI for revealing task-invariant organization of the human brainJournal: Research Square (preprint)In common: Connectome Workbench, h5py, NiBabel, 6 other tools, 4 references
- [6] doi:10.64898/2026.03.09.710558 [code]
- Multi-task fMRI outperforms resting-state fMRI for revealing task-invariant organization of the human brainJournal: bioRxiv (preprint)In common: Connectome Workbench, h5py, NiBabel, 6 other tools, 4 references
- [7] doi:10.1038/s41467-026-75959-w [code]
- Charting higher-order models of brain function beyond pairwise interactions.Journal: Nature communicationsIn common: h5py, NiBabel, statsmodels, 6 other tools, 4 references
- [8] doi:10.1038/s41467-026-76452-0 [code]
- Music evokes shared neural representations of imagined narratives across sensory modalities.Journal: Nature communicationsIn common: Connectome Workbench, h5py, NiBabel, 7 other tools, 1 reference
- [9] doi:10.1162/imag.a.1222 [code]
- Network-based near-scalp personalized brain stimulation targets.Journal: Imaging neuroscience (Cambridge, Mass.)In common: Connectome Workbench, NiBabel, scikit-learn, 3 other tools, 4 references
- [10] doi:10.1038/s41467-026-73668-y [code]
- Convergent and divergent brain-cognition development in early adolescence.Journal: Nature communicationsIn common: Connectome Workbench, NiBabel, seaborn, 5 other tools, 3 references
Contribute
The authors of this paper can claim it, correct its record and validate its tracing map, and the maintainers of its code (its owner, or a public member of its organization) correct what it says of their repository; anyone signed in can ask for its removal. Every request goes to OSCR's own machine, which answers it; your account page follows them.
Sign in with ORCID to claim this paper as one of its authors, correct its record or validate its tracing map: when the paper's metadata lists your ORCID iD, you are recognized at once. Maintainers of its code: sign in with GitHub, then claim the repository on your account page.
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: 1 repository of the authors' code, each at its verified commit and with its license, 36 scripts, and 5 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:8e353de62175aef5…
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.
