OSCR

Estimating fMRI timescale maps.

Code ↔ Paper

2 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 2 matches
  1. [1] § Methods › Estimation of standard errors › Time-domain standard error estimator ↔ fmri_timescales/timescale_utils.py, lines 133–256 · score 0.64 · Bartlett kernel, lag truncation, weighted, regression, standard errors, domain
  2. [2] § Simulations › Simulation settings ↔ fmri_timescales/sim.py, lines 9–62 · score 0.55 · multivariate normal distribution, Cholesky, matrix, Toeplitz, simulate, ACFs

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 · 256 lines · 9.2 KB · MIT · 1 match

  1. from typing import Optional
  2. import numpy as np
  3. from joblib import Parallel, delayed
  4. from scipy.optimize import curve_fit
  5. from sklearn.base import BaseEstimator
  6. from fmri_timescales import acf_utils
  7. def newey_west_omega(u: np.ndarray, n_lags: int | None = None) -> float:
  8. n_u = len(u)
  9. if n_lags is None:
  10. n_lags = int(np.floor(4 * (n_u / 100.0) ** (2 / 9)))
  11. weights = 1 - np.arange(n_lags + 1) / (n_lags + 1)
  12. omega = weights[0] * np.sum(u**2)
  13. for lag in range(1, n_lags + 1):
  14. omega += weights[lag] * (2 * np.sum(u[lag:] * u[:-lag]))
  15. return omega
  16. def _phi_to_tau(phis: np.ndarray, se_phis: np.ndarray, lag: int = 1) -> tuple:
  17. """phi to tau (timescale), and apply delta method to std err"""
  18. phis_abs = np.abs(phis) # tau undefined for negative phi
  19. taus = -lag / np.log(phis_abs)
  20. se_taus = (lag / (phis_abs * np.log(phis_abs) ** 2)) * se_phis
  21. return taus, se_taus
  22. class TD(BaseEstimator):
  23. """Time Domain (TD) Linear Model, Fit by Linear Least Squares.
  24. Parameters
  25. ----------
  26. var_estimator : str, optional
  27. The variance estimator to use. Options are "newey-west" or "non-robust", by default "newey-west"
  28. var_n_lags : int, optional
  29. The lag truncation number for the bartlett kernel, by default None
  30. copy_X : bool, optional
  31. If True X will be copied, else it may be overwritten, by default False
  32. n_jobs : int, optional
  33. The number of jobs to use for the computation, by default None
  34. Attributes
  35. ----------
  36. estimates_ : dict
  37. A dictionary containing four np.ndarray of shape (n_regions, ):
  38. - "phi": AR(1) coefficient estimates, for each region in X.
  39. - "se(phi)": Standard errors of AR(1) coefficients.
  40. - "tau": Timescale estimates, for each region in X.
  41. - "se(tau)": Standard errors of timescales.
  42. Examples
  43. --------
  44. >>> from fmri_timescales import sim, timescale_utils
  45. >>> X = sim.sim_ar(ar_coeffs=[0.8], n_timepoints=1000) # x_t = 0.8 x_{t-1} + e_t
  46. >>> td = timescale_utils.TD(var_estimator="newey-west", var_n_lags=10)
  47. >>> td.fit(X=X, n_timepoints=1000).estimates_
  48. {'phi': array([0.79789847]), 'se(phi)': array([0.02045074]), 'tau': array([4.42920958]), 'se(tau)': array([0.50282146])}
  49. """
  50. def __init__(
  51. self,
  52. var_estimator: str = "newey-west",
  53. var_n_lags: int | None = None,
  54. copy_X: bool = False,
  55. n_jobs: int | None = None,
  56. ) -> None:
  57. self.var_estimator = var_estimator
  58. self.var_n_lags = var_n_lags
  59. self.copy_X = copy_X
  60. self.n_jobs = n_jobs
  61. @delayed
  62. def _fit_td(self, x: np.ndarray) -> tuple:
  63. """fit model to a single timeseries x in X"""
  64. T = len(x) - 1
  65. # x_t = X[1:], x_{t-1} = x[:-1] (Hz=1)
  66. phi_ = np.sum(x[1:] * x[:-1]) / np.sum(x[:-1] ** 2)
  67. # variance estimators
  68. def non_robust():
  69. e_ = x[1:] - phi_ * x[:-1]
  70. q_ = np.sum(x[:-1] ** 2)
  71. sigma2_ = (1 / T) * np.sum(e_**2)
  72. return (1 / q_) * sigma2_
  73. def newey_west():
  74. e_ = x[1:] - phi_ * x[:-1]
  75. q_ = np.sum(x[:-1] ** 2)
  76. u_ = x[:-1] * e_
  77. omega_ = newey_west_omega(u_, n_lags=self.var_n_lags)
  78. return (1 / q_) * omega_ * (1 / q_)
  79. var_estimators = {"non-robust": non_robust, "newey-west": newey_west}
  80. if self.var_estimator not in var_estimators:
  81. raise ValueError("var_estimator must be either 'newey-west' or 'non-robust'")
  82. var_ = var_estimators[self.var_estimator]()
  83. return phi_, np.sqrt(var_)
  84. def fit(self, X: np.ndarray, n_timepoints: int):
  85. """Fit the TD model.
  86. Parameters
  87. ----------
  88. X : np.ndarray of shape (n_timepoints, n_regions)
  89. An array containing the timeseries of each region.
  90. n_timepoints : int
  91. The number of timepoints in X.
  92. Raises
  93. ------
  94. ValueError
  95. If `X` is not in (n_timepoints, n_regions) form.
  96. """
  97. if X.ndim != 2 or X.shape[0] != n_timepoints:
  98. raise ValueError("X should be in (n_timepoints, n_regions) form")
  99. X = X.copy() if self.copy_X else X
  100. X = (X - X.mean(axis=0)) / X.std(axis=0) # mean zero, variance 1
  101. with Parallel(n_jobs=self.n_jobs) as parallel:
  102. td_fits = parallel(self._fit_td(X[:, idx]) for idx in range(X.shape[1]))
  103. phis_, se_phis_ = map(np.array, zip(*td_fits))
  104. taus_, se_taus_ = _phi_to_tau(phis_, se_phis_)
  105. self.estimates_ = {"phi": phis_, "se(phi)": se_phis_, "tau": taus_, "se(tau)": se_taus_}
  106. return self
  107. class AD(BaseEstimator):
  108. """Autocorrelation Domain (AD) Nonlinear Model, fit by Nonlinear Least Squares.
  109. Parameters
  110. ----------
  111. var_estimator : str, optional
  112. The variance estimator to use. Options are "newey-west" or "non-robust", by default "newey-west"
  113. var_n_lags : int, optional
  114. The lag truncation number for the bartlett kernel, by default None
  115. acf_n_lags : int, optional
  116. The lag truncation number for the autocorrelation function, by default None
  117. copy_X : bool, optional
  118. If True X will be copied, else it may be overwritten, by default False
  119. n_jobs : _type_, optional
  120. The number of jobs to use for the computation, by default None
  121. Attributes
  122. ----------
  123. estimates_ : dict
  124. A dictionary containing two np.ndarray of shape (n_regions, ):
  125. - "tau": Timescale estimates, for each region in X.
  126. - "se(tau)": Standard errors of timescales.
  127. Examples
  128. --------
  129. >>> from fmri_timescales import sim, timescale_utils
  130. >>> X = sim.sim_ar(ar_coeffs=[0.8], n_timepoints=1000) # x_t = 0.8 x_{t-1} + e_t
  131. >>> ad = timescale_utils.AD(var_estimator="newey-west", var_n_lags=10, acf_n_lags=50)
  132. >>> ad.fit(X=X, n_timepoints=1000).estimates_
  133. {'phi': array([0.78021651]), 'se(phi)': array([0.02814532]), 'tau': array([4.02927146]), 'se(tau)': array([0.58565806])}
  134. """
  135. def __init__(
  136. self,
  137. var_estimator: str = "newey-west",
  138. var_n_lags: int | None = None,
  139. acf_n_lags: int | None = None,
  140. copy_X: bool = False,
  141. n_jobs: int | None = None,
  142. ) -> None:
  143. self.var_estimator = var_estimator
  144. self.var_n_lags = var_n_lags
  145. self.acf_n_lags = acf_n_lags
  146. self.copy_X = copy_X
  147. self.n_jobs = n_jobs
  148. @delayed
  149. def _fit_ad(self, x: np.ndarray) -> tuple:
  150. """fit model to a single timeseries/autocorrelation function x in X"""
  151. T = len(x)
  152. # acf estimator
  153. x_acf = acf_utils.ACF(n_lags=self.acf_n_lags + 1).fit_transform(x.reshape(-1, 1), T).squeeze()[1:]
  154. # regression function (m), and its linearized regressor (dm_dphi)
  155. ks = np.arange(1, len(x_acf) + 1)
  156. def m(ks, phi):
  157. return phi**ks
  158. def jac(ks, phi):
  159. return (ks * phi ** (ks - 1)).reshape(-1, 1)
  160. # phi estimator
  161. eps = 1e-10
  162. phi_, _ = curve_fit(f=m, xdata=ks, ydata=x_acf, p0=1e-2, bounds=(-1 + eps, +1 - eps), ftol=1e-6, jac=jac)
  163. phi_ = phi_.squeeze()
  164. # variance estimators
  165. def non_robust():
  166. e_ = x[1:] - phi_ * x[:-1]
  167. q_ = np.sum(x[:-1] ** 2)
  168. sigma2_ = (1 / T) * np.sum(e_**2)
  169. return (1 / q_) * sigma2_
  170. def newey_west():
  171. q_ = np.sum((ks * phi_ ** (ks - 1)) ** 2)
  172. weights = ks * (phi_ ** (ks - 1))
  173. weights_phi = weights * (phi_**ks)
  174. conv1 = np.convolve(x, weights, mode="full")
  175. conv2 = np.convolve(x**2, weights_phi, mode="full")
  176. u_ = x[len(ks) : T] * conv1[len(ks) - 1 : T - 1] - conv2[len(ks) - 1 : T - 1]
  177. omega_ = (1 / len(u_) ** 2) * newey_west_omega(u_, n_lags=self.var_n_lags)
  178. return (1 / q_) * omega_ * (1 / q_)
  179. var_estimators = {"non-robust": non_robust, "newey-west": newey_west}
  180. if self.var_estimator not in var_estimators:
  181. raise ValueError("var_estimator must be either 'newey-west' or 'non-robust'")
  182. var_ = var_estimators[self.var_estimator]()
  183. return phi_, np.sqrt(var_)
  184. def fit(self, X: np.ndarray, n_timepoints: int):
  185. """Fit the AD model.
  186. Parameters
  187. ----------
  188. X : np.ndarray of shape (n_timepoints, n_regions)
  189. An array containing the timeseries of each region.
  190. n_timepoints : int
  191. The number of timepoints in X.
  192. Raises
  193. ------
  194. ValueError
  195. If `X` is not in (n_timepoints, n_regions) form.
  196. """
  197. if X.ndim != 2 or X.shape[0] != n_timepoints:
  198. raise ValueError("X should be in (n_timepoints, n_regions) form")
  199. X = X.copy() if self.copy_X else X
  200. X = (X - X.mean(axis=0)) / X.std(axis=0) # mean zero, variance 1
  201. if self.acf_n_lags is None:
  202. self.acf_n_lags = n_timepoints // 100
  203. with Parallel(n_jobs=self.n_jobs) as parallel:
  204. ad_fits = parallel(self._fit_ad(X[:, idx]) for idx in range(X.shape[1]))
  205. phis_, se_phis_ = map(np.array, zip(*ad_fits))
  206. taus_, se_taus_ = _phi_to_tau(phis_, se_phis_)
  207. self.estimates_ = {"phi": phis_, "se(phi)": se_phis_, "tau": taus_, "se(tau)": se_taus_}
  208. return self

timescale_utils.py at commit cd0072e, under MIT · at the source

Overview

Authors: Gabriel Riegner1, Samuel Davenport2, Bradley Voytek1,3,4, Armin Schwartzman1,2
  1. Halicioğlu Data Science Institute, University of California San Diego, La Jolla, CA, United States
  2. Division of Biostatistics, University of California San Diego, La Jolla, CA, United States
  3. Department of Cognitive Science, University of California San Diego, La Jolla, CA, United States
  4. Neurosciences Graduate Program, University of California San Diego, La Jolla, CA, United States
Institutions: University of California San Diego (United States)
Journal: Imaging neuroscience (Cambridge, Mass.), volume 4, article IMAG.a.1248
Dates: received 23 April 2025; accepted 27 April 2026; published online 4 June 2026
Type: Research article · Language: English
License: CC BY
Identifiers: DOI 10.1162/imag.a.1248 · PMID 42253608 · PMCID PMC13237991 · OpenAlex W4409838608
Open access: diamond, a free copy (OpenAlex)
Status: code verified
Categories: fMRI (modality), human (organism)
Methods: Connectivity, Smoothing, state filtering, decompositions, Statistics, fMRI & imaging, Spectral & time-frequency
Keywords: time-domain linear model, autocorrelation-domain nonlinear model, uncertainty quantification, statistical inference, human connectome project, functional brain organization
Topic: Functional Brain Connectivity Studies (Cognitive Neuroscience, Neuroscience), according to OpenAlex
Funding: NIH (R01MH128923); Halicioglu Data Science Institute Fellowship
Citations: not cited yet (Europe PMC); 56 references in the paper

Abstract

Brain activity unfolds over hierarchical timescales that reflect how brain regions integrate and process information, linking functional and structural organization. While timescale studies are prevalent, existing estimation methods rely on the restrictive assumption of exponentially decaying temporal autocorrelation and only provide point estimates without standard errors, limiting statistical inference. In this paper, we formalize and evaluate two methods for mapping timescales in resting-state fMRI: a time-domain fit of an autoregressive (AR1) model and an autocorrelation-domain fit of an exponential decay model. Rather than assuming exponential autocorrelation decay, we define timescales by projecting the fMRI time series onto these approximating models, requiring only stationarity and mixing conditions while incorporating robust standard errors to account for model misspecification. We introduce theoretical properties of timescale estimators and show parameter recovery in realistic simulations, as well as applications to fMRI from the Human Connectome Project. Comparatively, the time-domain method produces more accurate estimates under model misspecification, remains computationally efficient for high-dimensional fMRI data, and yields maps aligned with known functional brain organization. In this work, we show valid statistical inference on fMRI timescale maps, and provide Python implementations of all methods.

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

griegner/fmri-timescales

License: MIT
State: the link answers, verified on 27 September 2026
Evidence: files inventoried
Commit: cd0072ee3b42f271e49de3682d5f7a41bb9070d2, 18 May 2026
Languages: Python (13), Jupyter (6)
Size: 115 files, 19 scripts
Software Heritage: not archived
Found in: “Data and Code Availability”
Holds: README, license file, environment (pyproject.toml), tests, continuous integration, documentation, 6 notebooks
Not found: CITATION.cff
Tools: NumPy (17 files), Matplotlib (8 files), SciPy (7 files), NiBabel (5 files), scikit-learn (4 files), statsmodels (4 files), Nilearn (3 files), neuromaps (1 file), pandas (1 file)
Availability: 1 check, the latest on 27 September 2026: the link answers
  • 27 September 2026: the link answers
21 files

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

Tracing map

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

What the map holds:

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

All simulation results and fMRI timescale maps, inclusive of the code by which they were derived, can be accessed on https://github.com/griegner/fmri-timescales. The code is under the open source MIT license, allowing access and reuse with attribution. The Human Connectome Project young adult dataset (ages 22-35; 2018 release) used in this study is publicly accessible under a data usage agreement, which describes specific terms for data use and sharing.

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, pages, dates, 4 authors, 6 keywords, 2 funders, 55 references.

Cite

This paper

Riegner, G., Davenport, S., Voytek, B., & Schwartzman, A. (2026). Estimating fMRI timescale maps. Imaging neuroscience (Cambridge, Mass.), 4, IMAG.a.1248. https://doi.org/10.1162/imag.a.1248

BibTeX

@article{riegner2026estimating,
author = {Riegner, Gabriel and Davenport, Samuel and Voytek, Bradley and Schwartzman, Armin},
title = {{Estimating fMRI timescale maps}},
journal = {Imaging neuroscience (Cambridge, Mass.)},
year = {2026},
month = jun,
volume = {4},
pages = {IMAG.a.1248},
publisher = {MIT Press},
issn = {2837-6056},
doi = {10.1162/imag.a.1248},
url = {https://doi.org/10.1162/imag.a.1248},
pmid = {42253608},
pmcid = {PMC13237991}
}

RIS

TY - JOUR
AU - Riegner, Gabriel
AU - Davenport, Samuel
AU - Voytek, Bradley
AU - Schwartzman, Armin
TI - Estimating fMRI timescale maps
T2 - Imaging neuroscience (Cambridge, Mass.)
J2 - Imaging Neurosci (Camb)
PY - 2026
DA - 2026/06/04
VL - 4
SP - IMAG.a.1248
SN - 2837-6056
PB - MIT Press
DO - 10.1162/imag.a.1248
UR - https://doi.org/10.1162/imag.a.1248
LA - en
ER -

CSL-JSON

{
"id": "10.1162/imag.a.1248",
"type": "article-journal",
"title": "Estimating fMRI timescale maps",
"container-title": "Imaging neuroscience (Cambridge, Mass.)",
"author": [
{
"family": "Riegner",
"given": "Gabriel"
},
{
"family": "Davenport",
"given": "Samuel"
},
{
"family": "Voytek",
"given": "Bradley"
},
{
"family": "Schwartzman",
"given": "Armin"
}
],
"container-title-short": "Imaging Neurosci (Camb)",
"volume": "4",
"page": "IMAG.a.1248",
"DOI": "10.1162/imag.a.1248",
"PMID": "42253608",
"PMCID": "PMC13237991",
"ISSN": "2837-6056",
"publisher": "MIT Press",
"URL": "https://doi.org/10.1162/imag.a.1248",
"language": "en",
"issued": {
"date-parts": [
[
2026,
6,
4
]
]
}
}

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-74466-2 [code]
Neuromorphic hierarchical modular reservoirs.
Journal: Nature communications
In common: neuromaps, Nilearn, NiBabel, 6 other tools, 11 references
[2] doi:10.1162/imag.a.1275 [code]
Glucose metabolism echoes long-range temporal correlations in the human brain.
Journal: Imaging neuroscience (Cambridge, Mass.)
In common: NiBabel, scikit-learn, pandas, 3 other tools, fMRI, 9 references
[3] doi:10.1038/s41540-026-00727-x [code]
Association-sensory spatiotemporal hierarchy and functional gradient-regularised recurrent neural network with implications for schizophrenia.
Journal: NPJ systems biology and applications
In common: statsmodels, pandas, SciPy, 2 other tools, 9 references
[4] doi:10.1093/cercor/bhag121 [code]
Preserved intrinsic neural timescale organization with hierarchical variation in autism spectrum disorder.
Journal: Cerebral cortex (New York, N.Y. : 1991)
In common: 10 references
[5] doi:10.1038/s41467-026-75959-w [code]
Charting higher-order models of brain function beyond pairwise interactions.
Journal: Nature communications
In common: neuromaps, Nilearn, NiBabel, 6 other tools, 4 references
[6] doi:10.7554/elife.110294 [code]
Arousal modulates functional connectivity through structured and hemispherically asymmetric community architecture during wakefulness.
Journal: eLife
In common: neuromaps, NiBabel, statsmodels, 5 other tools, fMRI, 4 references
[7] doi:10.21203/rs.3.rs-9326213/v1 [code]
Multi-task fMRI outperforms resting-state fMRI for revealing task-invariant organization of the human brain
Journal: Research Square (preprint)
In common: neuromaps, Nilearn, NiBabel, 5 other tools, fMRI, 4 references
[8] doi:10.64898/2026.03.09.710558 [code]
Multi-task fMRI outperforms resting-state fMRI for revealing task-invariant organization of the human brain
Journal: bioRxiv (preprint)
In common: neuromaps, Nilearn, NiBabel, 5 other tools, fMRI, 4 references
[9] doi:10.1038/s41467-026-72931-6 [code]
Three parsimonious spatiotemporal patterns in cerebellum reveal individual traits in function and behavior.
Journal: Nature communications
In common: Nilearn, NiBabel, statsmodels, 5 other tools, fMRI, 4 references
[10] doi:10.1162/netn.a.547 [code]
An evaluation of the efficacy of single-echo and multi-echo fMRI denoising strategies.
Journal: Network neuroscience (Cambridge, Mass.)
In common: Nilearn, NiBabel, statsmodels, 5 other tools, fMRI, 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.

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.