OSCR

Canonical Hidden Markov Model Networks for studying M/EEG.

Code ↔ Paper

6 matches between paragraphs of the paper and lines of its authors' code, computed by the harvester (lexical-v1). Click a colored paragraph or line to see its counterpart.

The 6 matches
  1. [1] § Materials and Methods › M/EEG preprocessing, source reconstruction, and parcellation ↔ osl/source_recon/rhino/forward_model.py, lines 285–349 · score 0.89 · boundary element model, inner skull surface, outer skull, Forward modelling, skin, brain
  2. [2] § Materials and Methods › M/EEG preprocessing, source reconstruction, and parcellation ↔ osl/source_recon/beamforming.py, lines 57–206 · score 0.84 · empty room, diagonal matrix, noise covariance, forward model, beamformer, rank
  3. [3] § Materials and Methods › M/EEG preprocessing, source reconstruction, and parcellation ↔ osl/source_recon/rhino/surfaces.py, lines 88–153 · score 0.83 · scalp surface, inner skull, brain surface, sMRI, FSL, outer
  4. [4] § Materials and Methods › M/EEG preprocessing, source reconstruction, and parcellation ↔ osl/source_recon/sign_flipping.py, lines 146–180 · score 0.81 · median subject, upper triangle, covariance matrices, Sign flipping, metric, dipole
  5. [5] § Materials and Methods › M/EEG preprocessing, source reconstruction, and parcellation ↔ osl/source_recon/beamforming.py, lines 57–206 · score 0.68 · unit noise gain, invariant, depth, inside, Variance, LCMV
  6. [6] § Materials and Methods › M/EEG preprocessing, source reconstruction, and parcellation ↔ examples/camcan/preprocess.py, lines 7–47 · score 0.51 · notch filter, 125 Hz, IIR, Cam, 0.5 Hz, preprocessing

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 · 935 lines · 33 KB · BSD-3-Clause · 2 matches

  1. #!/usr/bin/env python
  2. """Beamforming.
  3. """
  4. # Authors: Mark Woolrich <[email hidden]>
  5. # Chetan Gohil <[email hidden]>
  6. import os
  7. import os.path as op
  8. import numpy as np
  9. import matplotlib.pyplot as plt
  10. import mne
  11. from mne import (
  12. read_forward_solution,
  13. Covariance,
  14. compute_covariance,
  15. compute_raw_covariance,
  16. )
  17. from mne.io.meas_info import _simplify_info
  18. from mne.io.pick import pick_channels_cov, pick_info
  19. from mne.io.proj import make_projector
  20. from mne.rank import compute_rank
  21. from mne.minimum_norm.inverse import _check_depth, _prepare_forward, _get_vertno
  22. from mne.source_estimate import _get_src_type
  23. from mne.forward import _subject_from_forward
  24. from mne.forward.forward import is_fixed_orient
  25. from mne.beamformer._lcmv import _apply_lcmv
  26. from mne.beamformer._compute_beamformer import (
  27. _reduce_leadfield_rank,
  28. _sym_inv_sm,
  29. Beamformer,
  30. )
  31. from mne.minimum_norm.inverse import _check_reference
  32. from mne.utils import (
  33. _check_channels_spatial_filter,
  34. _check_one_ch_type,
  35. _check_info_inv,
  36. _check_option,
  37. _reg_pinv,
  38. _pl,
  39. _sym_mat_pow,
  40. _check_src_normal,
  41. check_version,
  42. logger,
  43. verbose,
  44. warn,
  45. )
  46. from osl.source_recon import rhino
  47. from osl.source_recon.rhino import utils as rhino_utils
  48. from osl.utils.logger import log_or_print
  49. def make_lcmv(
  50. subjects_dir,
  51. subject,
  52. data,
  53. chantypes,
  54. data_cov=None,
  55. noise_cov=None,
  56. reg=0,
  57. label=None,
  58. pick_ori="max-power-pre-weight-norm",
  59. rank="info",
  60. noise_rank="info",
  61. weight_norm="unit-noise-gain-invariant",
  62. reduce_rank=True,
  63. depth=None,
  64. inversion="matrix",
  65. verbose=None,
  66. logger=None,
  67. save_figs=False,
  68. ):
  69. """Compute LCMV spatial filter.
  70. Wrapper for RHINO version of mne.beamformer.make_lcmv
  71. Parameters
  72. ----------
  73. subjects_dir : string
  74. Directory to find RHINO subject dirs in.
  75. subject : string
  76. Subject name dir to find RHINO fwd model file in.
  77. data : instance of raw or epochs
  78. The measurement data to specify the channels to include.
  79. Bad channels in info['bads'] are not used.
  80. Will also be used to calculate data_cov
  81. data_cov : instance of Covariance | None
  82. The noise covariance matrix used to whiten.
  83. If None will be computed from dat.
  84. noise_cov : instance of Covariance | None
  85. The noise covariance matrix used to whiten.
  86. If None will be computed from dat as a diagonal matrix
  87. with variances set to the average of all sensors of that type.
  88. chantypes : List
  89. List of channel types to use. E.g. ['eeg'], ['mag', 'grad'],
  90. ['eeg', 'mag', 'grad'].
  91. reg : float
  92. The regularization for the whitened data covariance.
  93. label : instance of Label
  94. Restricts the LCMV solution to a given label.
  95. logger : logging.getLogger()
  96. Logger.
  97. save_figs : bool
  98. Should we save figures?
  99. Returns
  100. -------
  101. filters : instance of MNE Beamformer
  102. Dictionary containing filter weights from LCMV beamformer. See MNE docs.
  103. """
  104. log_or_print("*** RUNNING OSL MAKE LCMV ***", logger)
  105. # load forward solution
  106. fwd_fname = rhino.get_coreg_filenames(subjects_dir, subject)["forward_model_file"]
  107. fwd = read_forward_solution(fwd_fname)
  108. if data_cov is None:
  109. # Note that if chantypes are meg, eeg; and meg includes mag, grad
  110. # then compute_covariance will project data separately for meg and eeg
  111. # to reduced rank subspace (i.e. mag and grad will be combined together
  112. # inside the meg subspace, eeg will haeve a separate subspace).
  113. # I.e. data will become (ntpts x (rank_meg + rank_eeg))
  114. # and cov will be (rank_meg + rank_eeg) x (rank_meg + rank_eeg)
  115. # and include correlations between eeg and meg subspaces.
  116. # The output data_cov is cov projected back onto the indivdual
  117. # sensor types mag, grad, eeg.
  118. #
  119. # Prior to computing anything, including the subspaces each of mag, grad, eeg
  120. # are scaled so that they are on comparable scales to aid mixing in the
  121. # subspace and improve numerical stability. This is equivalent to what the
  122. # osl_normalise_sensor_data.m function in Matlab OSL is trying to do.
  123. # Note that in the output data_cov the scalings have been undone.
  124. if isinstance(data, mne.Epochs):
  125. data_cov = compute_covariance(data, method="empirical", rank=rank)
  126. else:
  127. data_cov = compute_raw_covariance(data, method="empirical", rank=rank)
  128. if noise_cov is None:
  129. # calculate noise covariance matrix
  130. #
  131. # Later this will be inverted and used to whiten the data AND the lead fields
  132. # as part of the source recon. See:
  133. # https://www.sciencedirect.com/science/article/pii/S1053811914010325?via%3Dihub
  134. #
  135. # In MNE, the noise cov is normally obtained from empty room noise recordings
  136. # or from a baseline period.
  137. # Here (if no noise cov is passed in) we mimic what the
  138. # osl_normalise_sensor_data.m function in Matlab OSL does,
  139. # by computing a diagonal noise cov with the variances set to the mean
  140. # variance of each sensor type (e.g. mag, grad, eeg.)
  141. n_channels = data_cov.data.shape[0]
  142. noise_cov_diag = np.zeros(n_channels)
  143. for type in chantypes:
  144. # Indices of this channel type
  145. type_data = data.copy().pick(type, exclude="bads")
  146. inds = []
  147. for chan in type_data.info["ch_names"]:
  148. inds.append(data_cov.ch_names.index(chan))
  149. # Mean variance of channels of this type
  150. variance = np.mean(np.diag(data_cov.data)[inds])
  151. noise_cov_diag[inds] = variance
  152. log_or_print(
  153. "variance for chantype {} is {}".format(type, variance),
  154. logger,
  155. )
  156. bads = [b for b in data.info["bads"] if b in data_cov.ch_names]
  157. noise_cov = Covariance(
  158. noise_cov_diag, data_cov.ch_names, bads, data.info["projs"], nfree=1e10
  159. )
  160. filters = _make_lcmv(
  161. data.info,
  162. fwd,
  163. data_cov,
  164. noise_cov=noise_cov,
  165. reg=reg,
  166. pick_ori=pick_ori,
  167. weight_norm=weight_norm,
  168. rank=rank,
  169. noise_rank=noise_rank,
  170. reduce_rank=reduce_rank,
  171. verbose=verbose,
  172. )
  173. if save_figs:
  174. # Plot covariances
  175. fig_cov, fig_svd = filters["data_cov"].plot(
  176. data.info, show=False, verbose=verbose
  177. )
  178. fig_cov.savefig(
  179. op.join(subjects_dir, subject, "rhino", "filter_cov.png"), dpi=150
  180. )
  181. fig_svd.savefig(
  182. op.join(subjects_dir, subject, "rhino", "filter_svd.png"), dpi=150
  183. )
  184. plt.close("all")
  185. log_or_print("*** OSL MAKE LCMV COMPLETE ***", logger)
  186. return filters
  187. def apply_lcmv_raw(raw, filters, reject_by_annotations="omit"):
  188. """Modified version of mne.beamformer.apply_lcmv_raw.
  189. This function has the option to remove bad segments
  190. (reject_by_annotations='omit') whereas the MNE function does not.
  191. """
  192. _check_reference(raw)
  193. # Get data from the mne.Raw object
  194. data, times = raw.get_data(
  195. reject_by_annotation=reject_by_annotations, return_times=True
  196. )
  197. # Select channels
  198. sel = _check_channels_spatial_filter(raw.ch_names, filters)
  199. data = data[sel]
  200. info = raw.info
  201. tmin = times[0]
  202. # Apply LCMV beamformer
  203. stc = _apply_lcmv(data=data, filters=filters, info=info, tmin=tmin)
  204. return next(stc)
  205. def get_recon_timeseries(subjects_dir, subject, coord_mni, recon_timeseries_head):
  206. """Gets the reconstructed time series nearest to the passed in coordinate
  207. in MNI space>
  208. Parameters
  209. ----------
  210. subjects_dir : string
  211. Directory to find RHINO subject dirs in.
  212. subject : string
  213. Subject name dir to find RHINO files in.
  214. coord_mni : (3,) np.array
  215. 3D coordinate in MNI space to get timeseries for
  216. recon_timeseries_head : (ndipoles, ntpts) np.array
  217. Reconstructed time courses in head (polhemus) space
  218. Assumes that the dipoles are the same (and in the same order)
  219. as those in the forward model, coreg_filenames['forward_model_file'].
  220. Returns
  221. -------
  222. recon_timeseries : numpy.ndarray
  223. The timecourse in recon_timeseries_head nearest to coord_mni
  224. """
  225. surfaces_filenames = rhino.get_surfaces_filenames(subjects_dir, subject)
  226. coreg_filenames = rhino.get_coreg_filenames(subjects_dir, subject)
  227. # get coord_mni in mri space
  228. mni_mri_t = rhino_utils.read_trans(surfaces_filenames["mni_mri_t_file"])
  229. coord_mri = rhino_utils.xform_points(mni_mri_t["trans"], coord_mni)
  230. # Get hold of coords of points reconstructed to.
  231. # Note, MNE forward model is done in head space in metres.
  232. # Rhino does everything in mm
  233. fwd = read_forward_solution(coreg_filenames["forward_model_file"])
  234. vs = fwd["src"][0]
  235. recon_coords_head = vs["rr"][vs["vertno"]] * 1000 # in mm
  236. # convert coords_head from head to mri space to get index of reconstructed
  237. # coordinate nearest to coord_mni
  238. head_scaledmri_t = rhino_utils.read_trans(coreg_filenames["head_scaledmri_t_file"])
  239. recon_coords_scaledmri = rhino_utils.xform_points(
  240. head_scaledmri_t["trans"], recon_coords_head.T
  241. ).T
  242. recon_index, d = rhino_utils._closest_node(coord_mri.T, recon_coords_scaledmri)
  243. recon_timeseries = np.abs(recon_timeseries_head[recon_index, :]).T
  244. return recon_timeseries
  245. def transform_recon_timeseries(
  246. subjects_dir,
  247. subject,
  248. recon_timeseries,
  249. spatial_resolution=None,
  250. reference_brain="mni",
  251. ):
  252. """Spatially resamples a (ndipoles x ntpts) array of reconstructed time
  253. courses (in head/polhemus space) to dipoles on the brain grid of the
  254. specified reference brain.
  255. Parameters
  256. ----------
  257. subjects_dir : string
  258. Directory to find RHINO subject dirs in.
  259. subject : string
  260. Subject name dir to find RHINO files in.
  261. recon_timeseries : numpy.ndarray
  262. (ndipoles, ntpts) or (ndipoles, ntpts, ntrials) of reconstructed time courses
  263. (in head (polhemus) space). Assumes that the dipoles are the same (and in the
  264. same order) as those in the forward model,
  265. coreg_filenames['forward_model_file']. Typically derive from the
  266. VolSourceEstimate's output by MNE source recon methods, e.g.
  267. mne.beamformer.apply_lcmv, obtained using a forward model generated by Rhino.
  268. spatial_resolution : int
  269. Resolution to use for the reference brain in mm
  270. (must be an integer, or will be cast to nearest int)
  271. If None, then the gridstep used in coreg_filenames['forward_model_file']
  272. is used.
  273. reference_brain : string
  274. 'mni' indicates that the reference_brain is the stdbrain in MNI space
  275. 'mri' indicates that the reference_brain is the subject's sMRI in
  276. the scaled native/mri space. "
  277. 'unscaled_mri' indicates that the reference_brain is the subject's sMRI in
  278. unscaled native/mri space.
  279. Note that Scaled/unscaled relates to the allow_smri_scaling option in coreg.
  280. If allow_scaling was False, then the unscaled MRI will be the same as the scaled.
  281. MRI.
  282. Returns
  283. -------
  284. recon_timeseries_out : numpy.ndarray
  285. (ndipoles, ntpts) np.array of reconstructed time courses resampled
  286. on the reference brain grid.
  287. reference_brain_fname : string
  288. File name of the requested reference brain at the requested
  289. spatial resolution, int(spatial_resolution)
  290. (with zero for background, and !=0 for brain)
  291. coords_out : numpy.ndarray
  292. (3, ndipoles) np.array of coordinates (in mm) of dipoles in
  293. recon_timeseries_out in "reference_brain" space
  294. """
  295. surfaces_filenames = rhino.get_surfaces_filenames(subjects_dir, subject)
  296. coreg_filenames = rhino.get_coreg_filenames(subjects_dir, subject)
  297. # -------------------------------------------------------
  298. # Get hold of coords of points reconstructed to.
  299. # Note, MNE forward model is done in head space in metres.
  300. # Rhino does everything in mm
  301. fwd = read_forward_solution(coreg_filenames["forward_model_file"])
  302. vs = fwd["src"][0]
  303. recon_coords_head = vs["rr"][vs["vertno"]] * 1000 # in mm
  304. # -------------------------------------------------------
  305. if spatial_resolution is None:
  306. # estimate gridstep from forward model
  307. rr = fwd["src"][0]["rr"]
  308. store = []
  309. for ii in range(rr.shape[0]):
  310. store.append(np.sqrt(np.sum(np.square(rr[ii, :] - rr[0, :]))))
  311. store = np.asarray(store)
  312. spatial_resolution = int(np.round(np.min(store[np.where(store > 0)]) * 1000))
  313. spatial_resolution = int(spatial_resolution)
  314. if reference_brain == "mni":
  315. # reference is mni stdbrain
  316. # convert recon_coords_head from head to mni space
  317. # head_mri_t_file xform is to unscaled MRI
  318. head_mri_t = rhino_utils.read_trans(coreg_filenames["head_mri_t_file"])
  319. recon_coords_mri = rhino_utils.xform_points(
  320. head_mri_t["trans"], recon_coords_head.T
  321. ).T
  322. # mni_mri_t_file xform is to unscaled MRI
  323. mni_mri_t = rhino_utils.read_trans(surfaces_filenames["mni_mri_t_file"])
  324. recon_coords_out = rhino_utils.xform_points(
  325. np.linalg.inv(mni_mri_t["trans"]), recon_coords_mri.T
  326. ).T
  327. reference_brain = (
  328. os.environ["FSLDIR"] + "/data/standard/MNI152_T1_1mm_brain.nii.gz"
  329. )
  330. # Sample reference_brain to the desired resolution
  331. reference_brain_resampled = op.join(
  332. coreg_filenames["basedir"],
  333. "MNI152_T1_{}mm_brain.nii.gz".format(spatial_resolution),
  334. )
  335. elif reference_brain == "unscaled_mri":
  336. # reference is unscaled smri
  337. # convert recon_coords_head from head to mri space
  338. head_mri_t = rhino_utils.read_trans(coreg_filenames["head_mri_t_file"])
  339. recon_coords_out = rhino_utils.xform_points(
  340. head_mri_t["trans"], recon_coords_head.T
  341. ).T
  342. reference_brain = surfaces_filenames["smri_file"]
  343. # Sample reference_brain to the desired resolution
  344. reference_brain_resampled = reference_brain.replace(
  345. ".nii.gz", "_{}mm.nii.gz".format(spatial_resolution)
  346. )
  347. elif reference_brain == "mri":
  348. # reference is scaled smri
  349. # convert recon_coords_head from head to mri space
  350. head_scaledmri_t = rhino_utils.read_trans(coreg_filenames["head_scaledmri_t_file"])
  351. recon_coords_out = rhino_utils.xform_points(
  352. head_scaledmri_t["trans"], recon_coords_head.T
  353. ).T
  354. reference_brain = coreg_filenames["smri_file"]
  355. # Sample reference_brain to the desired resolution
  356. reference_brain_resampled = reference_brain.replace(
  357. ".nii.gz", "_{}mm.nii.gz".format(spatial_resolution)
  358. )
  359. else:
  360. ValueError("Invalid out_space, should be mni or mri or scaledmri")
  361. # -------------------------------------------------------------------------
  362. # get coordinates from reference brain at resolution spatial_resolution
  363. # create std brain of the required resolution
  364. rhino_utils.system_call(
  365. "flirt -in {} -ref {} -out {} -applyisoxfm {}".format(
  366. reference_brain,
  367. reference_brain,
  368. reference_brain_resampled,
  369. spatial_resolution,
  370. )
  371. )
  372. coords_out, vals = rhino_utils.niimask2mmpointcloud(reference_brain_resampled)
  373. # -------------------------------------------------------------------------
  374. # for each mni_coords_out find nearest coord in recon_coords_out
  375. recon_timeseries_out = np.zeros(
  376. np.insert(recon_timeseries.shape[1:], 0, coords_out.shape[1])
  377. )
  378. recon_indices = np.zeros([coords_out.shape[1]])
  379. for cc in range(coords_out.shape[1]):
  380. recon_index, dist = rhino_utils._closest_node(
  381. coords_out[:, cc], recon_coords_out
  382. )
  383. if dist < spatial_resolution:
  384. recon_timeseries_out[cc, :] = recon_timeseries[recon_index, ...]
  385. recon_indices[cc] = recon_index
  386. return recon_timeseries_out, reference_brain_resampled, coords_out, recon_indices
  387. @verbose
  388. def _make_lcmv(
  389. info,
  390. forward,
  391. data_cov,
  392. reg=0.05,
  393. noise_cov=None,
  394. label=None,
  395. pick_ori=None,
  396. rank="info",
  397. noise_rank="info",
  398. weight_norm="unit-noise-gain-invariant",
  399. reduce_rank=False,
  400. depth=None,
  401. inversion="matrix",
  402. verbose=None,
  403. ):
  404. """Compute LCMV spatial filter.
  405. RHINO version of mne.beamformer.make_lcmv Code that is different to
  406. mne.beamformer.make_lcmv is labelled with MWW.
  407. """
  408. # check number of sensor types present in the data and ensure a noise cov
  409. info = _simplify_info(info)
  410. noise_cov, _, allow_mismatch = _check_one_ch_type(
  411. "lcmv", info, forward, data_cov, noise_cov
  412. )
  413. # XXX we need this extra picking step (can't just rely on minimum norm's
  414. # because there can be a mismatch. Should probably add an extra arg to
  415. # _prepare_beamformer_input at some point (later)
  416. picks = _check_info_inv(info, forward, data_cov, noise_cov)
  417. info = pick_info(info, picks)
  418. data_rank = compute_rank(data_cov, rank=rank, info=info)
  419. noise_rank = compute_rank(noise_cov, rank=noise_rank, info=info)
  420. # MWW, CG
  421. #for key in data_rank:
  422. # if (
  423. # key not in noise_rank or data_rank[key] != noise_rank[key]
  424. # ) and not allow_mismatch:
  425. # raise ValueError(
  426. # "%s data rank (%s) did not match the noise "
  427. # "rank (%s)" % (key, data_rank[key], noise_rank.get(key, None))
  428. # )
  429. # MWW
  430. # del noise_rank
  431. rank = data_rank
  432. logger.info("Making LCMV beamformer with data cov rank %s" % (rank,))
  433. # MWW added:
  434. logger.info("Making LCMV beamformer with noise cov rank %s" % (noise_rank,))
  435. del data_rank
  436. depth = _check_depth(depth, "depth_sparse")
  437. if inversion == "single":
  438. depth["combine_xyz"] = False
  439. # MWW
  440. (
  441. is_free_ori, info, proj, vertno, G, whitener, nn, orient_std
  442. ) = _prepare_beamformer_input(
  443. info, forward, label, pick_ori,
  444. noise_cov=noise_cov, rank=noise_rank, pca=False, **depth,
  445. )
  446. ch_names = list(info["ch_names"])
  447. data_cov = pick_channels_cov(data_cov, include=ch_names)
  448. Cm = data_cov._get_square()
  449. if "estimator" in data_cov:
  450. del data_cov["estimator"]
  451. rank_int = sum(rank.values())
  452. del rank
  453. # compute spatial filter
  454. n_orient = 3 if is_free_ori else 1
  455. W, max_power_ori = _compute_beamformer(
  456. G, Cm, reg, n_orient, weight_norm, pick_ori, reduce_rank, rank_int,
  457. inversion=inversion, nn=nn, orient_std=orient_std, whitener=whitener,
  458. )
  459. # get src type to store with filters for _make_stc
  460. src_type = _get_src_type(forward["src"], vertno)
  461. # get subject to store with filters
  462. subject_from = _subject_from_forward(forward)
  463. # Is the computed beamformer a scalar or vector beamformer?
  464. is_free_ori = is_free_ori if pick_ori in [None, "vector"] else False
  465. is_ssp = bool(info["projs"])
  466. filters = Beamformer(
  467. kind="LCMV",
  468. weights=W,
  469. data_cov=data_cov,
  470. noise_cov=noise_cov,
  471. whitener=whitener,
  472. weight_norm=weight_norm,
  473. pick_ori=pick_ori,
  474. ch_names=ch_names,
  475. proj=proj,
  476. is_ssp=is_ssp,
  477. vertices=vertno,
  478. is_free_ori=is_free_ori,
  479. n_sources=forward["nsource"],
  480. src_type=src_type,
  481. source_nn=forward["source_nn"].copy(),
  482. subject=subject_from,
  483. rank=rank_int,
  484. max_power_ori=max_power_ori,
  485. inversion=inversion,
  486. )
  487. return filters
  488. def _compute_beamformer(
  489. G, Cm, reg, n_orient, weight_norm, pick_ori, reduce_rank, rank, inversion, nn,
  490. orient_std, whitener
  491. ):
  492. """Compute a spatial beamformer filter (LCMV or DICS).
  493. For more detailed information on the parameters, see the docstrings of
  494. `make_lcmv` and `make_dics`.
  495. RHINO version of mne.beamformer._compute_beamformer
  496. See lines marked MWW for where code has been changed
  497. Parameters
  498. ----------
  499. G : ndarray, shape (n_dipoles, n_channels)
  500. The leadfield.
  501. Cm : ndarray, shape (n_channels, n_channels)
  502. The data covariance matrix.
  503. reg : float
  504. Regularization parameter.
  505. n_orient : int
  506. Number of dipole orientations defined at each source point
  507. weight_norm : None | 'unit-noise-gain' | 'nai'
  508. The weight normalization scheme to use.
  509. pick_ori : None | 'normal' | 'max-power' | max-power-pre-weight-norm
  510. The source orientation to compute the beamformer in.
  511. reduce_rank : bool
  512. Whether to reduce the rank by one during computation of the filter.
  513. rank : dict | None | 'full' | 'info'
  514. See compute_rank.
  515. inversion : 'matrix' | 'single'
  516. The inversion scheme to compute the weights.
  517. nn : ndarray, shape (n_dipoles, 3)
  518. The source normals.
  519. orient_std : ndarray, shape (n_dipoles,)
  520. The std of the orientation prior used in weighting the lead fields.
  521. whitener : ndarray, shape (n_channels, n_channels)
  522. The whitener.
  523. Returns
  524. -------
  525. W : ndarray, shape (n_dipoles, n_channels)
  526. The beamformer filter weights.
  527. """
  528. _check_option(
  529. "weight_norm",
  530. weight_norm,
  531. ["unit-noise-gain-invariant", "unit-noise-gain", "nai", None],
  532. )
  533. # Whiten the data covariance
  534. Cm = whitener @ Cm @ whitener.T.conj()
  535. # Restore to properly Hermitian as large whitening coefs can have bad
  536. # rounding error
  537. Cm[:] = (Cm + Cm.T.conj()) / 2.0
  538. assert Cm.shape == (G.shape[0],) * 2
  539. s, _ = np.linalg.eigh(Cm)
  540. if not (s >= -s.max() * 1e-7).all():
  541. # This shouldn't ever happen, but just in case
  542. warn(
  543. "data covariance does not appear to be positive semidefinite, "
  544. "results will likely be incorrect"
  545. )
  546. # Tikhonov regularization using reg parameter to control for
  547. # trade-off between spatial resolution and noise sensitivity
  548. # eq. 25 in Gross and Ioannides, 1999 Phys. Med. Biol. 44 2081
  549. Cm_inv, loading_factor, rank = _reg_pinv(Cm, reg, rank)
  550. assert orient_std.shape == (G.shape[1],)
  551. n_sources = G.shape[1] // n_orient
  552. assert nn.shape == (n_sources, 3)
  553. logger.info(
  554. "Computing beamformer filters for %d source%s" % (n_sources, _pl(n_sources))
  555. )
  556. n_channels = G.shape[0]
  557. assert n_orient in (3, 1)
  558. Gk = np.reshape(G.T, (n_sources, n_orient, n_channels)).transpose(0, 2, 1)
  559. assert Gk.shape == (n_sources, n_channels, n_orient)
  560. sk = np.reshape(orient_std, (n_sources, n_orient))
  561. del G, orient_std
  562. pinv_kwargs = dict()
  563. if check_version("numpy", "1.17"):
  564. pinv_kwargs["hermitian"] = True
  565. _check_option("reduce_rank", reduce_rank, (True, False))
  566. # inversion of the denominator
  567. _check_option("inversion", inversion, ("matrix", "single"))
  568. if (
  569. inversion == "single"
  570. and n_orient > 1
  571. and pick_ori == "vector"
  572. and weight_norm == "unit-noise-gain-invariant"
  573. ):
  574. raise ValueError(
  575. 'Cannot use pick_ori="vector" with inversion="single" and '
  576. 'weight_norm="unit-noise-gain-invariant"'
  577. )
  578. if reduce_rank and inversion == "single":
  579. raise ValueError(
  580. 'reduce_rank cannot be used with inversion="single"; '
  581. 'consider using inversion="matrix" if you have a '
  582. "rank-deficient forward model (i.e., from a sphere "
  583. "model with MEG channels), otherwise consider using "
  584. "reduce_rank=False"
  585. )
  586. if n_orient > 1:
  587. _, Gk_s, _ = np.linalg.svd(Gk, full_matrices=False)
  588. assert Gk_s.shape == (n_sources, n_orient)
  589. if not reduce_rank and (Gk_s[:, 0] > 1e6 * Gk_s[:, 2]).any():
  590. raise ValueError(
  591. "Singular matrix detected when estimating spatial filters. "
  592. "Consider reducing the rank of the forward operator by using "
  593. "reduce_rank=True."
  594. )
  595. del Gk_s
  596. # ------------------------------------------------------------------
  597. # 1. Reduce rank of the lead field
  598. if reduce_rank:
  599. Gk = _reduce_leadfield_rank(Gk)
  600. def _compute_bf_terms(Gk, Cm_inv):
  601. bf_numer = np.matmul(Gk.swapaxes(-2, -1).conj(), Cm_inv)
  602. bf_denom = np.matmul(bf_numer, Gk)
  603. return bf_numer, bf_denom
  604. # ------------------------------------------------------------------
  605. # 2. Reorient lead field in direction of max power or normal
  606. if pick_ori == "max-power" or pick_ori == "max-power-pre-weight-norm":
  607. assert n_orient == 3
  608. _, bf_denom = _compute_bf_terms(Gk, Cm_inv)
  609. if pick_ori == "max-power":
  610. if weight_norm is None:
  611. ori_numer = np.eye(n_orient)[np.newaxis]
  612. ori_denom = bf_denom
  613. else:
  614. # compute power, cf Sekihara & Nagarajan 2008, eq. 4.47
  615. ori_numer = bf_denom
  616. # Cm_inv should be Hermitian so no need for .T.conj()
  617. ori_denom = np.matmul(
  618. np.matmul(Gk.swapaxes(-2, -1).conj(), Cm_inv @ Cm_inv), Gk
  619. )
  620. ori_denom_inv = _sym_inv_sm(ori_denom, reduce_rank, inversion, sk)
  621. ori_pick = np.matmul(ori_denom_inv, ori_numer)
  622. # MWW
  623. else: # pick_ori == 'max-power-pre-weight-norm':
  624. # Compute power, see eq 5 in Brookes et al, Optimising experimental
  625. # design for MEG beamformer imaging, Neuroimage 2008
  626. # This optimises the orientation by maximising the power
  627. # BEFORE any weight normalisation is performed
  628. ori_pick = _sym_inv_sm(bf_denom, reduce_rank, inversion, sk)
  629. assert ori_pick.shape == (n_sources, n_orient, n_orient)
  630. # pick eigenvector that corresponds to maximum eigenvalue:
  631. eig_vals, eig_vecs = np.linalg.eig(ori_pick.real) # not Hermitian!
  632. # sort eigenvectors by eigenvalues for picking:
  633. order = np.argsort(np.abs(eig_vals), axis=-1)
  634. # eig_vals = np.take_along_axis(eig_vals, order, axis=-1)
  635. max_power_ori = eig_vecs[np.arange(len(eig_vecs)), :, order[:, -1]]
  636. assert max_power_ori.shape == (n_sources, n_orient)
  637. # set the (otherwise arbitrary) sign to match the normal
  638. signs = np.sign(np.sum(max_power_ori * nn, axis=1, keepdims=True))
  639. signs[signs == 0] = 1.0
  640. max_power_ori *= signs
  641. # Compute the lead field for the optimal orientation,
  642. # and adjust numer/denom
  643. Gk = np.matmul(Gk, max_power_ori[..., np.newaxis])
  644. n_orient = 1
  645. else:
  646. max_power_ori = None
  647. if pick_ori == "normal":
  648. Gk = Gk[..., 2:3]
  649. n_orient = 1
  650. # ----------------------------------------------------------------------
  651. # 3. Compute numerator and denominator of beamformer formula (unit-gain)
  652. bf_numer, bf_denom = _compute_bf_terms(Gk, Cm_inv)
  653. assert bf_denom.shape == (n_sources,) + (n_orient,) * 2
  654. assert bf_numer.shape == (n_sources, n_orient, n_channels)
  655. del Gk # lead field has been adjusted and should not be used anymore
  656. # ----------------------------------------------------------------------
  657. # 4. Invert the denominator
  658. # Here W is W_ug, i.e.:
  659. # G.T @ Cm_inv / (G.T @ Cm_inv @ G)
  660. bf_denom_inv = _sym_inv_sm(bf_denom, reduce_rank, inversion, sk)
  661. assert bf_denom_inv.shape == (n_sources, n_orient, n_orient)
  662. W = np.matmul(bf_denom_inv, bf_numer)
  663. assert W.shape == (n_sources, n_orient, n_channels)
  664. del bf_denom_inv, sk
  665. # ----------------------------------------------------------------------
  666. # 5. Re-scale filter weights according to the selected weight_norm
  667. # Weight normalization is done by computing, for each source::
  668. #
  669. # W_ung = W_ug / sqrt(W_ug @ W_ug.T)
  670. #
  671. # with W_ung referring to the unit-noise-gain (weight normalized) filter
  672. # and W_ug referring to the above-calculated unit-gain filter stored in W.
  673. if weight_norm is not None:
  674. # Three different ways to calculate the normalization factors here.
  675. # Only matters when in vector mode, as otherwise n_orient == 1 and
  676. # they are all equivalent. Sekihara 2008 says to use
  677. #
  678. # In MNE < 0.21, we just used the Frobenius matrix norm:
  679. #
  680. # noise_norm = np.linalg.norm(W, axis=(1, 2), keepdims=True)
  681. # assert noise_norm.shape == (n_sources, 1, 1)
  682. # W /= noise_norm
  683. #
  684. # Sekihara 2008 says to use sqrt(diag(W_ug @ W_ug.T)), which is not
  685. # rotation invariant:
  686. if weight_norm in ("unit-noise-gain", "nai"):
  687. noise_norm = np.matmul(W, W.swapaxes(-2, -1).conj()).real
  688. noise_norm = np.reshape( # np.diag operation over last two axes
  689. noise_norm, (n_sources, -1, 1)
  690. )[:, :: n_orient + 1]
  691. np.sqrt(noise_norm, out=noise_norm)
  692. noise_norm[noise_norm == 0] = np.inf
  693. assert noise_norm.shape == (n_sources, n_orient, 1)
  694. W /= noise_norm
  695. else:
  696. assert weight_norm == "unit-noise-gain-invariant"
  697. # Here we use sqrtm. The shortcut:
  698. #
  699. # use = W
  700. #
  701. # ... does not match the direct route (it is rotated!), so we'll
  702. # use the direct one to match FieldTrip:
  703. use = bf_numer
  704. inner = np.matmul(use, use.swapaxes(-2, -1).conj())
  705. W = np.matmul(_sym_mat_pow(inner, -0.5), use)
  706. noise_norm = 1.0
  707. if weight_norm == "nai":
  708. # Estimate noise level based on covariance matrix, taking the
  709. # first eigenvalue that falls outside the signal subspace or the
  710. # loading factor used during regularization, whichever is largest.
  711. if rank > len(Cm):
  712. # Covariance matrix is full rank, no noise subspace!
  713. # Use the loading factor as noise ceiling.
  714. if loading_factor == 0:
  715. raise RuntimeError(
  716. "Cannot compute noise subspace with a full-rank "
  717. "covariance matrix and no regularization. Try "
  718. "manually specifying the rank of the covariance "
  719. "matrix or using regularization."
  720. )
  721. noise = loading_factor
  722. else:
  723. noise, _ = np.linalg.eigh(Cm)
  724. noise = noise[-rank]
  725. noise = max(noise, loading_factor)
  726. W /= np.sqrt(noise)
  727. W = W.reshape(n_sources * n_orient, n_channels)
  728. logger.info("Filter computation complete")
  729. return W, max_power_ori
  730. def _prepare_beamformer_input(
  731. info,
  732. forward,
  733. label=None,
  734. pick_ori=None,
  735. noise_cov=None,
  736. rank=None,
  737. pca=False,
  738. loose=None,
  739. combine_xyz="fro",
  740. exp=None,
  741. limit=None,
  742. allow_fixed_depth=True,
  743. limit_depth_chs=False,
  744. ):
  745. """Input preparation common for LCMV, DICS, and RAP-MUSIC.
  746. RHINO version of mne.beamformer._prepare_beamformer_input.
  747. See lines marked MWW (or CG) for where code has been changed.
  748. """
  749. # MWW
  750. # _check_option('pick_ori', pick_ori, ('normal', 'max-power', 'vector', None))
  751. _check_option(
  752. "pick_ori", pick_ori,
  753. ("normal", "max-power", "vector", "max-power-pre-weight-norm", None),
  754. )
  755. # MWW, CG
  756. # Restrict forward solution to selected vertices
  757. #if label is not None:
  758. # _, src_sel = label_src_vertno_sel(label, forward["src"])
  759. # forward = _restrict_forward_to_src_sel(forward, src_sel)
  760. if loose is None:
  761. loose = 0.0 if is_fixed_orient(forward) else 1.0
  762. # MWW, CG
  763. #if noise_cov is None:
  764. # noise_cov = make_ad_hoc_cov(info, std=1.0)
  765. forward, info_picked, gain, _, orient_prior, _, trace_GRGT, noise_cov, whitener = \
  766. _prepare_forward(
  767. forward, info, noise_cov, "auto", loose, rank=rank, pca=pca, use_cps=True,
  768. exp=exp, limit_depth_chs=limit_depth_chs, combine_xyz=combine_xyz,
  769. limit=limit, allow_fixed_depth=allow_fixed_depth,
  770. )
  771. is_free_ori = not is_fixed_orient(forward) # could have been changed
  772. nn = forward["source_nn"]
  773. if is_free_ori: # take Z coordinate
  774. nn = nn[2::3]
  775. nn = nn.copy()
  776. vertno = _get_vertno(forward["src"])
  777. if forward["surf_ori"]:
  778. nn[...] = [0, 0, 1] # align to local +Z coordinate
  779. if pick_ori is not None and not is_free_ori:
  780. raise ValueError(
  781. "Normal or max-power orientation (got %r) can only be picked when "
  782. "a forward operator with free orientation is used." % (pick_ori,)
  783. )
  784. if pick_ori == "normal" and not forward["surf_ori"]:
  785. raise ValueError(
  786. "Normal orientation can only be picked when a "
  787. "forward operator oriented in surface coordinates is "
  788. "used."
  789. )
  790. _check_src_normal(pick_ori, forward["src"])
  791. del forward, info
  792. # Undo the scaling that MNE prefers
  793. scale = np.sqrt((noise_cov["eig"] > 0).sum() / trace_GRGT)
  794. gain /= scale
  795. if orient_prior is not None:
  796. orient_std = np.sqrt(orient_prior)
  797. else:
  798. orient_std = np.ones(gain.shape[1])
  799. # Get the projector
  800. proj, _, _ = make_projector(info_picked["projs"], info_picked["ch_names"])
  801. return is_free_ori, info_picked, proj, vertno, gain, whitener, nn, orient_std

beamforming.py, under BSD-3-Clause · at the source

Overview

Authors: Chetan Gohil1, Rukuang Huang1, Cameron Higgins1, Mats W.J. van Es1, Andrew J. Quinn2, Diego Vidaurre1,3, Mark W. Woolrich1
  1. Oxford Centre for Human Brain Activity, Oxford Centre for Integrative Neuroimaging, Department of Psychiatry, University of Oxford, Oxford, United Kingdom
  2. Centre for Human Brain Health, School of Psychology, University of Birmingham, Birmingham, United Kingdom
  3. Center of Functionally Integrative Neuroscience, Department of Clinical Medicine, Aarhus University, Aarhus, Denmark
Institutions: University of Oxford (United Kingdom); Wellcome Centre for Integrative Neuroimaging (United Kingdom); University of Birmingham (United Kingdom); Aarhus University (Denmark)
Journal: Imaging neuroscience (Cambridge, Mass.), volume 4, article IMAG.a.1190
Dates: received 28 October 2025; accepted 27 February 2026; published online 1 April 2026
Type: Research article · Language: English
License: CC BY
Identifiers: DOI 10.1162/imag.a.1190 · PMID 41938661 · PMCID PMC13045528 · OpenAlex W7135011771
Open access: diamond, a free copy (OpenAlex)
Status: code verified
Categories: EEG (modality), MEG (modality), human (organism)
Methods: Spectral & time-frequency, Preprocessing, Connectivity, Smoothing, state filtering, decompositions, fMRI & imaging
Keywords: electrophysiology, MEG, EEG, neuronal oscillations, Hidden Markov Model, machine learning, dynamic functional connectivity, functional networks
Journal subjects: Software Toolbox
Topic: Functional Brain Connectivity Studies (Cognitive Neuroscience, Neuroscience), according to OpenAlex
Funding: National Institute for Health Research (NIHR) Oxford Health Biomedical Research Centre; Wellcome Trust (203139/Z/16/Z, 215573/Z/19/Z, 106183/Z/14/Z); EPSRC Centre for Doctoral Training in Health Data Science (EP/S02428X/1); MRC (RG94383/RG89702); Novo Nordisk Foundation Emerging Investigator Fellowship (NNF19OC-0054895); ERC (ERC-StG-2019-850404); Independent Research Fund of Denmark (2034-00054B); NIHR Oxford Health Biomedical Research Centre (NIHR203316)
Citations: cited by 2 papers (Europe PMC); 65 references in the paper

Abstract

Dynamic brain networks identified in magneto/encephalography (M/EEG) recordings provide new insights into human brain activity. One established method uses Hidden Markov Models (HMMs) and has been shown to infer reproducible, fast-switching brain networks in a variety of cognitive and disease conditions. Often, these studies are done on small bespoke (boutique) datasets (N<100 ) and analyzed in isolation of other M/EEG datasets. Instead of training a new model for each boutique study, which is computationally expensive, we propose the use of a canonical HMM. This provides a common reference through which different studies can be described using the same set of networks. We provide HMMs for a range of model orders (4–16 states) in parcellated source space and sensor space. These HMMs were trained on 1849 MEG recordings (N = 621, 18–88 years old, 194 hours), capturing population variability in both rest and task data. We illustrate applications of this canonical HMM approach in parcellated source space using boutique MEG and EEG datasets. Applying the canonical HMM in parcel space requires the boutique dataset to be preprocessed and source reconstructed in the same way as the canonical HMM training data. Applying the canonical HMM in the sensor space requires the same sensor layout and preprocessing as the canonical HMM training data. The canonical HMMs have been made publicly available as an open-access resource, providing sets of canonical brain networks that can be used to compare individuals within and across a range of datasets.

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

Zenodo 10401793

License: CC-BY-4.0
State: the link answers, verified on 28 September 2026
Evidence: files inventoried
Size: 6 files
Software Heritage: not checked
Found in: the references
Not found: README, license file, CITATION.cff, environment file, tests, continuous integration, documentation
Availability: 1 check, the latest on 28 September 2026: the link answers (HTTP 200)
  • 28 September 2026: the link answers (HTTP 200)
At the source:

Zenodo 6875060

License: BSD-3-Clause
State: the link answers, verified on 28 September 2026
Evidence: files inventoried
Size: 1 file
Software Heritage: not checked
Found in: the references
Not found: README, license file, CITATION.cff, environment file, tests, continuous integration, documentation
Tools: NumPy (40 files), OHBA Software Library (OSL) (28 files), MNE-Python (26 files), Matplotlib (16 files), SciPy (10 files), FSL (8 files), NiBabel (8 files), pandas (5 files), scikit-learn (3 files), Nilearn (2 files), h5py (1 file), Numba (1 file), OpenCV (1 file)
Availability: 1 check, the latest on 28 September 2026: the link answers (HTTP 200)
  • 28 September 2026: the link answers (HTTP 200)
72 files
At the source:

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;
  • 69 scripts, each with its path and the digest of its content;
  • 6 matches between paragraphs of the paper and lines of the code (method lexical-v1);
  • neither the text of the paper nor the code itself.

Its JSON (tracing-map.json) is deposited on Zenodo with its DOI once the map is validated.

Data

No dataset and no data link were found in the paper.

Data and Code Availability

All the datasets used in this work are publicly available (see Section 2.1). Code and examples scripts for processing M/EEG data and applying the canonical HMM have been made publicly available here: https://github.com/OHBA-analysis/Canonical-HMM-Networks. As larger and more diverse datasets become available, this resource will be actively updated. This includes providing canonical HMMs with differing model orders, different parcellations, different sensor layouts and training on larger datasets including other modalities, such as OPM and EEG data.

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

Recorded: type, language, journal, volume, pages, dates, 7 authors, 8 keywords, 8 funders, 61 references.

Cite

This paper

Gohil, C., Huang, R., Higgins, C., van Es, M. W., Quinn, A. J., Vidaurre, D., & Woolrich, M. W. (2026). Canonical Hidden Markov Model Networks for studying M/EEG. Imaging neuroscience (Cambridge, Mass.), 4, IMAG.a.1190. https://doi.org/10.1162/imag.a.1190

BibTeX

@article{gohil2026canonical,
author = {Gohil, Chetan and Huang, Rukuang and Higgins, Cameron and van Es, Mats W.J. and Quinn, Andrew J. and Vidaurre, Diego and Woolrich, Mark W.},
title = {{Canonical Hidden Markov Model Networks for studying M/EEG}},
journal = {Imaging neuroscience (Cambridge, Mass.)},
year = {2026},
month = apr,
volume = {4},
pages = {IMAG.a.1190},
publisher = {MIT Press},
issn = {2837-6056},
doi = {10.1162/imag.a.1190},
url = {https://doi.org/10.1162/imag.a.1190},
pmid = {41938661},
pmcid = {PMC13045528}
}

RIS

TY - JOUR
AU - Gohil, Chetan
AU - Huang, Rukuang
AU - Higgins, Cameron
AU - van Es, Mats W.J.
AU - Quinn, Andrew J.
AU - Vidaurre, Diego
AU - Woolrich, Mark W.
TI - Canonical Hidden Markov Model Networks for studying M/EEG
T2 - Imaging neuroscience (Cambridge, Mass.)
J2 - Imaging Neurosci (Camb)
PY - 2026
DA - 2026/04/01
VL - 4
SP - IMAG.a.1190
SN - 2837-6056
PB - MIT Press
DO - 10.1162/imag.a.1190
UR - https://doi.org/10.1162/imag.a.1190
LA - en
ER -

CSL-JSON

{
"id": "10.1162/imag.a.1190",
"type": "article-journal",
"title": "Canonical Hidden Markov Model Networks for studying M/EEG",
"container-title": "Imaging neuroscience (Cambridge, Mass.)",
"author": [
{
"family": "Gohil",
"given": "Chetan"
},
{
"family": "Huang",
"given": "Rukuang"
},
{
"family": "Higgins",
"given": "Cameron"
},
{
"family": "van Es",
"given": "Mats W.J."
},
{
"family": "Quinn",
"given": "Andrew J."
},
{
"family": "Vidaurre",
"given": "Diego"
},
{
"family": "Woolrich",
"given": "Mark W."
}
],
"container-title-short": "Imaging Neurosci (Camb)",
"volume": "4",
"page": "IMAG.a.1190",
"DOI": "10.1162/imag.a.1190",
"PMID": "41938661",
"PMCID": "PMC13045528",
"ISSN": "2837-6056",
"publisher": "MIT Press",
"URL": "https://doi.org/10.1162/imag.a.1190",
"language": "en",
"issued": {
"date-parts": [
[
2026,
4,
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.1002/hbm.70516 [code]
Effects of Age on Resting-State Cortical Networks.
Journal: Human brain mapping
In common: OHBA Software Library (OSL), Numba, MNE-Python, 10 other tools, MEG, 18 references, 2 authors
[2] doi:10.1038/s41531-026-01372-1 [code]
Varying patterns of association between cortical large-scale networks and subthalamic nucleus activity in Parkinson's disease.
Journal: NPJ Parkinson's disease
In common: OHBA Software Library (OSL), Numba, MNE-Python, 10 other tools, 12 references
[3] doi:10.1162/imag.a.1237 [code]
Modelling discrete states and long-term dynamics in functional brain networks.
Journal: Imaging neuroscience (Cambridge, Mass.)
In common: MNE-Python, scikit-learn, pandas, 3 other tools, MEG, 16 references, author Chetan Gohil
[4] doi:10.1162/imag.a.1301 [code]
MEG-GPT: A transformer-based foundation model for magnetoencephalography data.
Journal: Imaging neuroscience (Cambridge, Mass.)
In common: MNE-Python, Nilearn, NiBabel, 5 other tools, MEG, 9 references, author Chetan Gohil
[5] doi:10.1162/imag.a.1188 [code]
Modelling variability in functional brain networks using embeddings.
Journal: Imaging neuroscience (Cambridge, Mass.)
In common: OHBA Software Library (OSL), NiBabel, scikit-learn, 4 other tools, 12 references
[6] doi:10.1093/braincomms/fcag236 [code]
Dynamic, state-dependent characteristics of cognitive fluctuations in Lewy body dementia: a magnetoencephalography study.
Journal: Brain communications
In common: OHBA Software Library (OSL), MNE-Python, pandas, 1 other tool, MEG, 9 references
[7] doi:10.1162/imag.a.1269 [code]
From early to contemporary normative modeling: Mapping individual differences in neurophysiological signals.
Journal: Imaging neuroscience (Cambridge, Mass.)
In common: MNE-Python, Nilearn, NiBabel, 5 other tools, MEG, EEG, 6 references
[8] 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: Numba, FSL, Nilearn, 7 other tools, 2 references
[9] doi:10.1162/imag.a.1276 [code]
High-resolution whole-brain magnetic resonance spectroscopic imaging in youth at risk for psychosis.
Journal: Imaging neuroscience (Cambridge, Mass.)
In common: Numba, MNE-Python, FSL, 7 other tools, 1 reference
[10] doi:10.1038/s41597-026-07350-9 [code]
An open multi-center MEG-EEG dataset for studying conscious visual perception.
Journal: Scientific data
In common: MNE-Python, FSL, Nilearn, 6 other tools, MEG, EEG, 1 reference

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.