OSCR

Determining hemispheric language dominance from MEG beta-power modulations: Concordance with fMRI.

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] § Material and methods › MEG Data acquisition and analysis › Preprocessing ↔ MEGnet/prep_inputs/ICA.py, lines 366–464 · score 0.72 · cross talk, bad channel, calibration, compensation, Elekta, SSS
  2. [2] § Material and methods › Predictors of MEG–fMRI discordance ↔ MEGnet/megnet_qc_plots.py, lines 1–22 · score 0.50 · raw tSSS, MEGnet, quality

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 · 938 lines · 35 KB · BSD-3-Clause · 1 match

  1. #!/usr/bin/env python
  2. # coding: utf-8
  3. # @author: Allison Nugent
  4. # @author: Jeff Stout
  5. import warnings
  6. warnings.simplefilter(action='ignore', category=FutureWarning)
  7. import os, os.path as op
  8. from pathlib import PosixPath
  9. from copy import deepcopy
  10. import numpy as np
  11. import matplotlib.pyplot as plt
  12. import mne
  13. from mne.preprocessing import ICA
  14. import scipy as sp
  15. from scipy.spatial import ConvexHull, convex_hull_plot_2d
  16. from scipy import interpolate
  17. from scipy.io import savemat
  18. from scipy.stats import zscore
  19. from matplotlib.backends.backend_agg import FigureCanvas
  20. from matplotlib.pyplot import show
  21. import matplotlib
  22. matplotlib.use('agg')
  23. import logging
  24. import unittest.mock
  25. from mne.defaults import _INTERPOLATION_DEFAULT, _EXTRAPOLATE_DEFAULT, _BORDER_DEFAULT, _handle_default
  26. from mne.utils import logger
  27. import mne.viz.topomap
  28. import mne.viz
  29. from mne.viz.topomap import _check_extrapolate, _make_head_outlines, _prepare_topomap, _setup_interp, _get_patch, _draw_outlines
  30. from mne.viz.utils import _setup_vmin_vmax, _get_cmap, plt_show
  31. from scipy.io import savemat
  32. import PIL.Image
  33. import MEGnet
  34. from MEGnet.megnet_utilities import fPredictChunkAndVoting_parrallel
  35. import functools
  36. from mne.io.ctf.ctf import RawCTF
  37. from mne.io.kit.kit import RawKIT
  38. from mne.io.bti.bti import RawBTi
  39. from mne.io.fiff.raw import Raw
  40. raw_typelist = [RawCTF, RawKIT, RawBTi, Raw]
  41. # =============================================================================
  42. # Helper Functions
  43. # =============================================================================
  44. # function to transform Cartesian coordinates to spherical coordinates
  45. # theta = azimuth
  46. # phi = elevation
  47. def cart2sph(x, y, z):
  48. xy = np.sqrt(x*x + y*y)
  49. r = np.sqrt(x*x + y*y + z*z)
  50. theta = np.arctan2(y,x)
  51. phi = np.arctan2(z,xy)
  52. return r, theta, phi
  53. # function to transform 2d polar coordinates to Cartesian
  54. def pol2cart(rho,phi):
  55. x = rho * np.cos(phi)
  56. y = rho * np.sin(phi)
  57. return x,y
  58. # function to transform 2d Cartesian coodinates to polar coordinates
  59. def cart2pol(x,y):
  60. r = np.sqrt(x*x + y*y)
  61. theta = np.arctan2(y,x)
  62. return r,theta
  63. # re-write of the MNE python function make_head_outlines() without the nose and ears, with expansion
  64. # of the outline to 1.01
  65. def make_head_outlines_new(sphere, pos, outlines, clip_origin):
  66. """Check or create outlines for topoplot."""
  67. assert isinstance(sphere, np.ndarray)
  68. x, y, _, radius = sphere
  69. del sphere
  70. ll = np.linspace(0, 2 * np.pi, 101)
  71. head_x = np.cos(ll) * radius*1.01 + x
  72. head_y = np.sin(ll) * radius*1.01 + y
  73. dx = np.exp(np.arccos(np.deg2rad(12)) * 1j)
  74. dx, dy = dx.real, dx.imag
  75. outlines_dict = dict(head=(head_x, head_y))
  76. # Make the figure encompass slightly more than all points
  77. mask_scale = 1.
  78. # We probably want to ensure it always contains our most
  79. # extremely positioned channels, so we do:
  80. mask_scale = max(
  81. mask_scale, np.linalg.norm(pos, axis=1).max() * 1.01 / radius)
  82. outlines_dict['mask_pos'] = (mask_scale * head_x, mask_scale * head_y)
  83. clip_radius = radius * mask_scale
  84. outlines_dict['clip_radius'] = (clip_radius,) * 2
  85. outlines_dict['clip_origin'] = clip_origin
  86. return outlines_dict
  87. def _mod_plot_topomap(
  88. data,
  89. pos,
  90. axes,
  91. *,
  92. ch_type="eeg",
  93. sensors=True,
  94. names=None,
  95. mask=None,
  96. mask_params=None,
  97. contours=6,
  98. outlines="head",
  99. sphere=None,
  100. image_interp=_INTERPOLATION_DEFAULT,
  101. extrapolate=_EXTRAPOLATE_DEFAULT,
  102. border=_BORDER_DEFAULT,
  103. res=64,
  104. cmap=None,
  105. vmin=None,
  106. vmax=None,
  107. cnorm=None,
  108. show=True,
  109. onselect=None,
  110. ):
  111. from matplotlib.colors import Normalize
  112. from matplotlib.widgets import RectangleSelector
  113. data = np.asarray(data)
  114. logger.debug(f"Plotting topomap for {ch_type} data shape {data.shape}")
  115. if isinstance(pos, mne.Info): # infer pos from Info object
  116. picks = _pick_data_channels(pos, exclude=()) # pick only data channels
  117. pos = pick_info(pos, picks)
  118. # check if there is only 1 channel type, and n_chans matches the data
  119. ch_type = _get_channel_types(pos, unique=True)
  120. info_help = (
  121. "Pick Info with e.g. mne.pick_info and "
  122. "mne.io.pick.channel_indices_by_type."
  123. )
  124. if len(ch_type) > 1:
  125. raise ValueError("Multiple channel types in Info structure. " + info_help)
  126. elif len(pos["chs"]) != data.shape[0]:
  127. raise ValueError(
  128. "Number of channels in the Info object (%s) and "
  129. "the data array (%s) do not match. " % (len(pos["chs"]), data.shape[0])
  130. + info_help
  131. )
  132. else:
  133. ch_type = ch_type.pop()
  134. if any(type_ in ch_type for type_ in ("planar", "grad")):
  135. # deal with grad pairs
  136. picks = _pair_grad_sensors(pos, topomap_coords=False)
  137. pos = _find_topomap_coords(pos, picks=picks[::2], sphere=sphere)
  138. data, _ = _merge_ch_data(data[picks], ch_type, [])
  139. data = data.reshape(-1)
  140. else:
  141. picks = list(range(data.shape[0]))
  142. pos = _find_topomap_coords(pos, picks=picks, sphere=sphere)
  143. extrapolate = _check_extrapolate(extrapolate, ch_type)
  144. if data.ndim > 1:
  145. raise ValueError(
  146. "Data needs to be array of shape (n_sensors,); got "
  147. "shape %s." % str(data.shape)
  148. )
  149. # Give a helpful error message for common mistakes regarding the position
  150. # matrix.
  151. pos_help = (
  152. "Electrode positions should be specified as a 2D array with "
  153. "shape (n_channels, 2). Each row in this matrix contains the "
  154. "(x, y) position of an electrode."
  155. )
  156. if pos.ndim != 2:
  157. error = (
  158. "{ndim}D array supplied as electrode positions, where a 2D "
  159. "array was expected"
  160. ).format(ndim=pos.ndim)
  161. raise ValueError(error + " " + pos_help)
  162. elif pos.shape[1] == 3:
  163. error = (
  164. "The supplied electrode positions matrix contains 3 columns. "
  165. "Are you trying to specify XYZ coordinates? Perhaps the "
  166. "mne.channels.create_eeg_layout function is useful for you."
  167. )
  168. raise ValueError(error + " " + pos_help)
  169. # No error is raised in case of pos.shape[1] == 4. In this case, it is
  170. # assumed the position matrix contains both (x, y) and (width, height)
  171. # values, such as Layout.pos.
  172. elif pos.shape[1] == 1 or pos.shape[1] > 4:
  173. raise ValueError(pos_help)
  174. pos = pos[:, :2]
  175. if len(data) != len(pos):
  176. raise ValueError(
  177. "Data and pos need to be of same length. Got data of "
  178. "length %s, pos of length %s" % (len(data), len(pos))
  179. )
  180. norm = min(data) >= 0
  181. vmin, vmax = _setup_vmin_vmax(data, vmin, vmax, norm)
  182. if cmap is None:
  183. cmap = "Reds" if norm else "RdBu_r"
  184. cmap = _get_cmap(cmap)
  185. outlines = _make_head_outlines(sphere, pos, outlines, (0.0, 0.0))
  186. assert isinstance(outlines, dict)
  187. _prepare_topomap(pos, axes)
  188. mask_params = _handle_default("mask_params", mask_params)
  189. # find mask limits and setup interpolation
  190. extent, Xi, Yi, interp = _setup_interp(
  191. pos, res, image_interp, extrapolate, outlines, border
  192. )
  193. interp.set_values(data)
  194. Zi = interp.set_locations(Xi, Yi)()
  195. # plot outline
  196. patch_ = _get_patch(outlines, extrapolate, interp, axes)
  197. # get colormap normalization
  198. if cnorm is None:
  199. cnorm = Normalize(vmin=vmin, vmax=vmax)
  200. # plot interpolated map
  201. if image_interp == "nearest": # plot over with Voronoi, more accurate
  202. im = _voronoi_topomap(
  203. data,
  204. pos=pos,
  205. outlines=outlines,
  206. ax=axes,
  207. cmap=cmap,
  208. norm=cnorm,
  209. extent=extent,
  210. res=res,
  211. )
  212. else:
  213. im = axes.imshow(
  214. Zi,
  215. cmap=cmap,
  216. origin="lower",
  217. aspect="equal",
  218. extent=extent,
  219. interpolation="bilinear",
  220. norm=cnorm,
  221. alpha=0.9
  222. )
  223. # gh-1432 had a workaround for no contours here, but we'll remove it
  224. # because mpl has probably fixed it
  225. linewidth = mask_params["markeredgewidth"]
  226. cont = True
  227. if isinstance(contours, (np.ndarray, list)):
  228. pass
  229. elif contours == 0 or ((Zi == Zi[0, 0]) | np.isnan(Zi)).all():
  230. cont = None # can't make contours for constant-valued functions
  231. if cont:
  232. with warnings.catch_warnings(record=True):
  233. warnings.simplefilter("ignore")
  234. cont = axes.contour(
  235. Xi, Yi, Zi, contours, colors="w", linewidths=linewidth / 2.0, negative_linestyles='solid', alpha=0.5, antialiased=True
  236. )
  237. if patch_ is not None:
  238. im.set_clip_path(patch_)
  239. if cont is not None:
  240. for col in cont.collections:
  241. col.set_clip_path(patch_)
  242. pos_x, pos_y = pos.T
  243. mask = mask.astype(bool, copy=False) if mask is not None else None
  244. if sensors is not False and mask is None:
  245. _topomap_plot_sensors(pos_x, pos_y, sensors=sensors, ax=axes)
  246. elif sensors and mask is not None:
  247. idx = np.where(mask)[0]
  248. axes.plot(pos_x[idx], pos_y[idx], **mask_params)
  249. idx = np.where(~mask)[0]
  250. _topomap_plot_sensors(pos_x[idx], pos_y[idx], sensors=sensors, ax=axes)
  251. elif not sensors and mask is not None:
  252. idx = np.where(mask)[0]
  253. axes.plot(pos_x[idx], pos_y[idx], **mask_params)
  254. if isinstance(outlines, dict):
  255. _draw_outlines(axes, outlines)
  256. if names is not None:
  257. show_idx = np.arange(len(names)) if mask is None else np.where(mask)[0]
  258. for ii, (_pos, _name) in enumerate(zip(pos, names)):
  259. if ii not in show_idx:
  260. continue
  261. axes.text(
  262. _pos[0],
  263. _pos[1],
  264. _name,
  265. horizontalalignment="center",
  266. verticalalignment="center",
  267. size="x-small",
  268. )
  269. if not axes.figure.get_constrained_layout():
  270. axes.figure.subplots_adjust(top=0.95)
  271. if onselect is not None:
  272. lim = axes.dataLim
  273. x0, y0, width, height = lim.x0, lim.y0, lim.width, lim.height
  274. axes.RS = RectangleSelector(axes, onselect=onselect)
  275. axes.set(xlim=[x0, x0 + width], ylim=[y0, y0 + height])
  276. plt_show(show)
  277. return im, cont, interp
  278. # =============================================================================
  279. # >>> Monkey patch the new topography output using the code above <<<
  280. # Required to match the brainstorm style outputs used for training orig MEGnet
  281. # =============================================================================
  282. mne.viz.topomap._plot_topomap=_mod_plot_topomap
  283. # =============================================================================
  284. def read_raw(filename, do_assess_bads=False):
  285. '''
  286. Use the appropriate MNE io reader for the MEG type
  287. For CTF/.ds datasets, gradient compensation will be checked and applied if
  288. needed.
  289. Parameters
  290. ----------
  291. filename : Path or PathStr
  292. Path to file
  293. Returns
  294. -------
  295. Raw MNE instance
  296. '''
  297. #Case of BTI in folder
  298. if (op.isdir(str(filename))) & (not str(filename).endswith('.ds')):
  299. return mne.io.read_raw_bti(filename, preload=True,
  300. head_shape_fname=None)
  301. ext = os.path.splitext(filename)[-1]
  302. if ext == '.fif':
  303. raw = mne.io.read_raw_fif(filename, preload=True, allow_maxshield=True)
  304. if do_assess_bads==True:
  305. _bads=assess_bads(filename)
  306. raw.info['bads'] = _bads['noisy'] + _bads['flat']
  307. elif ext == '.ds':
  308. raw = mne.io.read_raw_ctf(filename, preload=True,
  309. system_clock='ignore', clean_names=True)
  310. if raw.compensation_grade != 3:
  311. raw.apply_gradient_compensation(3)
  312. #XXX Hack -- figure out the correct way to identify 4D/BTI data
  313. elif (filename[-4:]=='rfDC') | ('c,rfhp' in filename[-14:]):
  314. raw = mne.io.read_raw_bti(filename, preload=True,
  315. head_shape_fname=None)
  316. #XXX Hack - Confirm KIT assignment - sqd or con file
  317. elif ext == '.sqd':
  318. raw = mne.io.read_raw_kit(filename, preload=True)
  319. return raw
  320. def assess_bads(raw_fname, is_eroom=False): # assess MEG data for bad channels
  321. '''Code sampled from MNE python website
  322. https://mne.tools/dev/auto_tutorials/preprocessing/\
  323. plot_60_maxwell_filtering_sss.html'''
  324. from mne.preprocessing import find_bad_channels_maxwell
  325. # load data with load_data to ensure correct function is chosen
  326. raw = read_raw(raw_fname, do_assess_bads=False)
  327. #if raw.times[-1] > 60.0:
  328. # raw.crop(tmax=60)
  329. raw.info['bads'] = []
  330. raw_check = raw.copy()
  331. vendor = mne.channels.channels._get_meg_system(raw.info)
  332. if vendor == '306m' or vendor == '122m':
  333. if is_eroom==False:
  334. auto_noisy_chs, auto_flat_chs, auto_scores = find_bad_channels_maxwell(
  335. raw_check, cross_talk=None, calibration=None,
  336. return_scores=True, verbose=True)
  337. else:
  338. auto_noisy_chs, auto_flat_chs, auto_scores = find_bad_channels_maxwell(
  339. raw_check, cross_talk=None, calibration=None,
  340. return_scores=True, verbose=True, coord_frame="meg")
  341. # find_bad_channels_maxwell is actually pretty bad at finding flat channels -
  342. # it uses a much too stringent threshold. So, we need some supplementary code
  343. # This is extra complicated for Elekta/MEGIN, because there are both mags and
  344. # grads, which will be on a different scale
  345. mags = mne.pick_types(raw_check.info, meg='mag')
  346. grads = mne.pick_types(raw_check.info, meg='grad')
  347. # get the standard deviation for each channel, and the trimmed mean of the stds
  348. # have to do this separately for mags and grads
  349. stdraw_mags = np.std(raw_check._data[mags,:],axis=1)
  350. stdraw_grads = np.std(raw_check._data[grads,:],axis=1)
  351. stdraw_trimmedmean_mags = sp.stats.trim_mean(stdraw_mags,0.1)
  352. stdraw_trimmedmean_grads = sp.stats.trim_mean(stdraw_grads,0.1)
  353. # we can't use the same threshold here, because grads have a much greater
  354. # variance in the variances
  355. flat_mags = np.where(stdraw_mags < stdraw_trimmedmean_mags/100)[0]
  356. flat_grads = np.where(stdraw_grads < stdraw_trimmedmean_grads/1000)[0]
  357. # need to use list comprehensions
  358. flat_idx_mags = [flat_mags[i] for i in flat_mags.tolist()]
  359. flat_idx_grads = [flat_grads[i] for i in flat_grads.tolist()]
  360. flats = []
  361. for flat in flat_idx_mags:
  362. flats.append(raw_check.info['ch_names'][mags[flat_idx_mags]])
  363. for flat in flat_idx_grads:
  364. flats.append(raw_check.info['ch_names'][grads[flat_idx_grads]])
  365. # ignore references and use 'meg' coordinate frame for CTF and KIT
  366. if vendor == 'CTF_275':
  367. raw_check.apply_gradient_compensation(0)
  368. auto_noisy_chs, auto_flat_chs, auto_scores = find_bad_channels_maxwell(
  369. raw_check, cross_talk=None, calibration=None, coord_frame='meg',
  370. return_scores=True, verbose=True, ignore_ref=True)
  371. # again, finding flat/bad channels is not great, so we add another algorithm
  372. # since other vendors don't mix grads and mags, we only need to do this for
  373. # a single channel type
  374. megs = mne.pick_types(raw_check.info, meg=True)
  375. # get the standard deviation for each channel, and the trimmed mean of the stds
  376. stdraw_megs = np.std(raw_check._data[megs,:],axis=1)
  377. stdraw_trimmedmean_megs = sp.stats.trim_mean(stdraw_megs,0.1)
  378. flat_megs = np.where(stdraw_megs < stdraw_trimmedmean_megs/100)[0]
  379. # need to use list comprehensions
  380. flat_idx_megs = [flat_megs[i] for i in flat_megs.tolist()]
  381. flats = []
  382. for flat in flat_idx_megs:
  383. flats.append(raw_check.info['ch_names'][megs[flat_idx_mags]])
  384. else:
  385. auto_noisy_chs, auto_flat_chs, auto_scores = find_bad_channels_maxwell(
  386. raw_check, cross_talk=None, calibration=None, coord_frame='meg',
  387. return_scores=True, verbose=True, ignore_ref=True)
  388. # again, finding flat/bad channels is not great, so we add another algorithm
  389. # since other vendors don't mix grads and mags, we only need to do this for
  390. # a single channel type
  391. megs = mne.pick_types(raw_check.info, meg=True)
  392. # get the standard deviation for each channel, and the trimmed mean of the stds
  393. stdraw_megs = np.std(raw_check._data[megs,:],axis=1)
  394. stdraw_trimmedmean_megs = sp.stats.trim_mean(stdraw_megs,0.1)
  395. flat_megs = np.where(stdraw_megs < stdraw_trimmedmean_megs/100)[0]
  396. # need to use list comprehensions
  397. flat_idx_megs = [flat_megs[i] for i in flat_megs.tolist()]
  398. flats = []
  399. for flat in flat_idx_megs:
  400. flats.append(raw_check.info['ch_names'][megs[flat_idx_mags]])
  401. auto_flat_chs = auto_flat_chs + flats
  402. auto_flat_chs = list(set(auto_flat_chs))
  403. return {'noisy':auto_noisy_chs, 'flat':auto_flat_chs}
  404. def raw_preprocess(raw, mains_freq=None):
  405. '''
  406. Preprocess data with 250Hz resampling, notch_filter, and 1-100Hz bp
  407. Returns instance of mne raw
  408. '''
  409. resample_freq = 250
  410. mains_freq = int(mains_freq)
  411. notch_freqs = np.arange(mains_freq, resample_freq * 2/3, mains_freq)
  412. raw.notch_filter(notch_freqs)
  413. raw.resample(resample_freq)
  414. raw.filter(1.0, 100)
  415. return raw
  416. def thresh_get_good_segments(raw):
  417. magthresh = 5000e-15
  418. gradthresh = 5000e-13
  419. flatmagthresh = 10e-15
  420. flatgradthresh = 10e-13
  421. evts = mne.make_fixed_length_events(raw, duration=5.0)
  422. chtypes=raw.get_channel_types()
  423. if 'grad' in chtypes:
  424. if 'mag' in chtypes:
  425. reject_dict = dict(mag=magthresh, grad=gradthresh)
  426. flat_dict = dict(mag=flatmagthresh, grad=flatgradthresh)
  427. else:
  428. reject_dict = dict(grad=gradthresh)
  429. flat_dict = dict(grad=flatgradthresh)
  430. else:
  431. reject_dict = dict(mag=magthresh)
  432. flat_dict = dict(mag=flatmagthresh)
  433. epochs = mne.Epochs(raw, evts, reject=reject_dict, flat=flat_dict, preload=True, baseline=None)
  434. return epochs
  435. def z_get_good_segments(epochs, std_thresh=6):
  436. '''Identify bad channels using standard deviation'''
  437. epochs = epochs.copy()
  438. z = zscore(np.std(epochs._data, axis=2), axis=0)
  439. bad_epochs = np.where(z>std_thresh)[0]
  440. epochs.drop(indices=bad_epochs)
  441. return epochs
  442. def calc_ica(raw, file_base=None, save=False, results_dir=None, seedval=0):
  443. '''Straightforward MNE ICA with MEGnet article specifications:
  444. infomax, 20 components'''
  445. epochs = thresh_get_good_segments(raw)
  446. epochs = z_get_good_segments(epochs)
  447. ica = ICA(n_components=20, random_state=seedval, method='infomax')
  448. ica.fit(epochs)
  449. if save==True:
  450. out_filename = file_base + '_{}-ica.fif'.format(str(seedval))
  451. out_filename = os.path.join(results_dir, out_filename)
  452. ica.save(out_filename, overwrite=True)
  453. return ica
  454. # =============================================================================
  455. # Neighborhood Correlation
  456. # =============================================================================
  457. from sklearn.neighbors import NearestNeighbors
  458. def get_sensor_locs(raw):
  459. '''Return sensor coordinates'''
  460. locs = np.array([i['loc'][0:3] for i in raw.info['chs']])
  461. return locs
  462. def get_neighbors(raw=None, n_neighbors=6):
  463. '''Enter the MNE raw object
  464. Returns neighborhood index matrix with the first column being the index
  465. of the input channel - and the rest of the columns being the neighbor
  466. indices. n_neighbors defines the number of neighbors found'''
  467. if raw!=None:
  468. locs=get_sensor_locs(raw)
  469. n_neighbors+=1 #Add 1 - because input chan is one of hte "neighbors"
  470. nbrs = NearestNeighbors(n_neighbors=n_neighbors, algorithm='ball_tree')
  471. nbrs.fit(locs)
  472. distances, neighbor_mat = nbrs.kneighbors(locs)
  473. return distances, neighbor_mat
  474. # def return_index(val, idx):
  475. # return val[idx]
  476. def neighborhood_corr(raw, n_neighbors=6):
  477. dists, neighbor_mat = get_neighbors(raw)
  478. corr_vec=np.zeros(neighbor_mat.shape[0])
  479. for idx,row in enumerate(neighbor_mat):
  480. tmp = (np.corrcoef(raw._data[row])[0,1:] / dists[idx][1:]) * dists[idx][1:].mean()
  481. corr_vec[idx] = np.abs(tmp.mean())
  482. # =============================================================================
  483. #
  484. # =============================================================================
  485. def sensor_pos2circle(raw, ica):
  486. '''
  487. Project the sensor positions to a unit circle and return positions
  488. Currently works with MNE chan_type == mag (includes CTF ax gradiometers)
  489. Parameters
  490. ----------
  491. raw : mne.io.{fiff,ds}.raw.Raw
  492. Mne format dataset.
  493. ica : mne.preprocessing.ica.ICA
  494. MNE ICA instance
  495. Returns
  496. -------
  497. pos_new : numpy.ndarray
  498. Position of channels projected to the unit circle. (#chans X 2)
  499. '''
  500. num_chans = len(raw.ch_names)
  501. # extract magnetometer positions
  502. data_picks, pos, merge_channels, names, ch_type, sphere, clip_origin = \
  503. mne.viz.topomap._prepare_topomap_plot(ica, 'mag')
  504. #Extract channel locations
  505. # 'loc' has 12 elements, the location plus a 3x3 orientation matrix
  506. tmp_ = [i['loc'][0:3] for i in raw.info['chs']]
  507. channel_locations3d = np.stack(tmp_)
  508. tmp_ = np.array([cart2sph(*i) for i in channel_locations3d])
  509. channel_locations_3d_spherical = tmp_ #np.transpose(tmp_)
  510. TH=channel_locations_3d_spherical[:,1]
  511. PHI=channel_locations_3d_spherical[:,2]
  512. # project the spherical locations to a plane
  513. # this calculates a new R for each coordinate, based on PHI
  514. # then transform the projection to Cartesian coordinates
  515. channel_locations_2d=np.zeros([num_chans,2])
  516. newR=np.zeros((num_chans,))
  517. newR = 1 - PHI/np.pi*2
  518. channel_locations_2d=np.transpose(pol2cart(newR,TH))
  519. # use ConvexHull to get the sensor indices around the edges,
  520. # and scale their radii to a unit circle
  521. hull = ConvexHull(channel_locations_2d)
  522. Border=hull.vertices
  523. Dborder = 1/newR[Border]
  524. # Define an interpolation function of the TH coordinate to define a scaling
  525. # factor for R
  526. FuncTh=np.hstack([TH[Border]-2*np.pi, TH[Border], TH[Border]+2*np.pi])
  527. funcD=np.hstack((Dborder,Dborder,Dborder))
  528. finterp = interpolate.interp1d(FuncTh,funcD);
  529. D = finterp(TH)
  530. # Apply the scaling to every radii coordinate and transform back to
  531. # Cartesian coordinates
  532. newerR=np.zeros((num_chans,))
  533. for i in np.arange(0,num_chans):
  534. newerR[i] = min(newR[i]*D[i],1)
  535. [Xnew,Ynew]=pol2cart(newerR,TH)
  536. pos_new=np.transpose(np.vstack((Xnew,Ynew)))
  537. return pos_new
  538. def circle_plot(circle_pos=None, data=None, out_fname=None):
  539. '''Generate the plot and save'''
  540. # create a circular outline without nose and ears, and get coordinates
  541. outlines_new = make_head_outlines_new(np.array([0,0,0,1]),
  542. circle_pos,
  543. 'head',
  544. (0,0))
  545. fig = plt.figure(figsize=(1.5, 1.8), dpi=100)
  546. ax = fig.add_subplot(111)
  547. ax.set_facecolor('k')
  548. mnefig, contour = mne.viz.plot_topomap(data,circle_pos,
  549. sensors=False,
  550. outlines=outlines_new,
  551. extrapolate='head',
  552. sphere=[0,0,0,1.0],
  553. contours=9,res=120,
  554. show=False,
  555. axes=ax,
  556. )
  557. contour.colors=['white'] #This is not working currently
  558. mnefig.set_cmap(plt.get_cmap('bwr'))
  559. # plt.show()
  560. mnefig.figure.savefig(out_fname)
  561. plt.close(fig)
  562. mat_fname = os.path.splitext(out_fname)[0]+'.mat'
  563. matrix_out = np.frombuffer(mnefig.figure.canvas.tostring_rgb(), dtype=np.uint8)
  564. matrix_out = matrix_out.reshape(mnefig.figure.canvas.get_width_height()[::-1] + (3,))
  565. savemat(mat_fname,
  566. {'array':matrix_out})
  567. del mnefig
  568. #return matrix_out
  569. def main(filename, outbasename=None, mains_freq=60.0,
  570. save_preproc=False, save_ica=False, seedval=0,
  571. results_dir=None, filename_raw=None, do_assess_bads=False,
  572. bad_channels=[]):
  573. '''
  574. Perform all of the steps to preprocess the ica maps:
  575. Read raw data
  576. Filter / notch filter / resample (250Hz)
  577. Calculate 20 component ICA using infomax
  578. Warp sensors to circle plot
  579. Save ICA to output dir
  580. Save ICA topoplots to output dir
  581. Parameters
  582. ----------
  583. filename : str or Raw MNE data object
  584. Path to file
  585. filename_raw : str
  586. Required for MEGIN datasets
  587. Path to file
  588. outbasename : str
  589. Required for 4D/BTI datasets
  590. If none defaults to basename(filename)
  591. mains_freq : float
  592. Line frequency 50 or 60 Hz
  593. save_preproc : Bool
  594. Save the preprocessed data
  595. save_ica : Bool
  596. Save the ica output
  597. seedval : Int
  598. Set the numpy random seed
  599. results_dir : str / path
  600. Path to output directory
  601. do_assess_bads : Bool
  602. Assess bad channels if not already done
  603. '''
  604. if (type(filename) == str) | (type(filename) == PosixPath):
  605. raw = read_raw(filename)
  606. elif type(filename) in raw_typelist:
  607. raw = deepcopy(filename)
  608. else:
  609. raise BaseException('Could not interpret input variable "filename"')
  610. if len(bad_channels) > 0:
  611. print('dropping bad channels\n')
  612. print(bad_channels)
  613. raw.drop_channels(bad_channels)
  614. raw = raw_preprocess(raw, mains_freq)
  615. if filename_raw is not None:
  616. tmp_raw = read_raw(filename_raw, do_assess_bads=do_assess_bads)
  617. tmp_raw = raw_preprocess(tmp_raw, mains_freq)
  618. raw.info['bads'] = tmp_raw.info['bads']
  619. del tmp_raw
  620. #Set output names
  621. if outbasename != None:
  622. file_base = outbasename #Necessary for 4D datasets
  623. else:
  624. file_base = os.path.basename(filename)
  625. file_base = os.path.splitext(file_base)[0]
  626. results_dir = os.path.join(results_dir, file_base)
  627. if not os.path.exists(results_dir): os.makedirs(results_dir)
  628. if save_preproc==True:
  629. out_fname = os.path.join(results_dir, file_base+'_250srate_meg.fif')
  630. raw.save(out_fname, overwrite=True) #Save with EEG
  631. # pick only the MEG channels, and then grab the indices of the channels we want to use
  632. raw.pick_types(meg=True, eeg=False, ref_meg=False)
  633. mag_idxs = mne.pick_types(raw.info, ref_meg=False, meg='mag')
  634. ica = calc_ica(raw, file_base=file_base, results_dir=results_dir,
  635. save=save_ica, seedval=seedval)
  636. # generate the sensor positions on the circle
  637. circle_pos = sensor_pos2circle(raw, ica)
  638. for comp in range(0,20):
  639. # get the ICA component data
  640. data=np.dot(ica.mixing_matrix_[:,comp].T, ica.pca_components_[:ica.n_components_])
  641. data = data[mag_idxs]
  642. '''Generate the plot and save'''
  643. # create a circular outline without nose and ears, and get coordinates
  644. outlines_new = make_head_outlines_new(np.array([0,0,0,1]),
  645. circle_pos,
  646. 'head',
  647. (0,0))
  648. # set up the figure canvas
  649. fig = plt.figure(figsize=(1.3, 1.3), dpi=100, facecolor='black')
  650. canvas=FigureCanvas(fig)
  651. ax = fig.add_subplot(111)
  652. # plot the figure using the monkey patched mne.viz.plot_topomap
  653. mnefig, contour = mne.viz.plot_topomap(data,
  654. circle_pos[mag_idxs],
  655. sensors=False,
  656. outlines=outlines_new,
  657. extrapolate='head',
  658. sphere=[0,0,0,1.],
  659. contours=10,res=120,
  660. show=True,
  661. axes=ax,
  662. cmap='bwr'
  663. )
  664. print(comp)
  665. # plt.show()
  666. # So matplotlib did strange things to the canvas and background image size when I tried to grab
  667. # the image from the canvas and convert directly to RGB. This ridiculous (but functional) hack is to
  668. # write the image out as a png then reopen it to convert to RGB.
  669. outpng = f'{results_dir}/component{str(int(comp)+1)}.png'
  670. mnefig.figure.savefig(outpng,dpi=120,bbox_inches='tight',pad_inches=0)
  671. rgba_image=PIL.Image.open(outpng)
  672. rgb_image=rgba_image.convert('RGB')
  673. # save the RGB image as a .mat file
  674. mat_fname = f'{results_dir}/component{str(int(comp)+1)}.mat'
  675. savemat(mat_fname, {'array':np.array(rgb_image)})
  676. del mnefig
  677. # Save ICA timeseries as input for classification
  678. # Currently inputs to classification are matlab arrays
  679. ica_ts = ica.get_sources(raw)._data.T
  680. outfname = f'{results_dir}/ICATimeSeries.mat' #'{file_base}-ica-ts.mat'
  681. savemat(outfname, {'arrICATimeSeries':ica_ts})
  682. def classify_ica(results_dir=None, outbasename=None, filename=None):
  683. '''
  684. Run the ICA timeseries and spatial maps generated during the main processing
  685. through the MEGNET tensorflow model (using the CPU)
  686. Parameters
  687. ----------
  688. results_dir : str, optional
  689. Path to the output directory from the main processing
  690. Contains the spatial maps and time series
  691. Returns
  692. -------
  693. dict
  694. dictionary with keys: (classes, bads_idx)
  695. '''
  696. from scipy.io import loadmat
  697. os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
  698. os.environ["CUDA_DEVICE_ORDER"] = "PCI_BUS_ID" # see issue #152
  699. os.environ["CUDA_VISIBLE_DEVICES"] = ""
  700. from tensorflow import keras
  701. model_path = op.join(MEGnet.__path__[0] , 'model_v2')
  702. # This is set to use CPU in initial import
  703. kModel=keras.models.load_model(model_path, compile=False)
  704. #Set output names
  705. if outbasename != None:
  706. file_base = outbasename #Necessary for 4D datasets
  707. else:
  708. file_base = os.path.basename(filename)
  709. file_base = os.path.splitext(file_base)[0]
  710. results_dir = os.path.join(results_dir, file_base)
  711. outfname = f'{results_dir}/ICATimeSeries.mat'
  712. arrSP_fnames = [op.join(results_dir, f'component{i}.mat') for i in range(1,21)]
  713. arrTS = loadmat(op.join(results_dir, 'ICATimeSeries.mat'))['arrICATimeSeries'].T
  714. arrSP = np.stack([loadmat(i)['array'] for i in arrSP_fnames])
  715. preds, probs = fPredictChunkAndVoting_parrallel(kModel, arrTS, arrSP)
  716. meg_rest_ica_classes = preds.argmax(axis=1)
  717. ica_comps_toremove = [index for index, value in enumerate(meg_rest_ica_classes) if value in [1, 2, 3]]
  718. return {'classes':meg_rest_ica_classes,
  719. 'bads_idx': ica_comps_toremove}
  720. def clean_ica(bad_comps=None, results_dir=None, outbasename=None,
  721. raw_dataset=None): # Remove identified ICA components
  722. print("removing ica components")
  723. #Set output names
  724. if outbasename != None:
  725. file_base = outbasename #Necessary for 4D datasets
  726. else:
  727. file_base = os.path.basename(filename)
  728. file_base = os.path.splitext(file_base)[0]
  729. results_dir = os.path.join(results_dir, file_base)
  730. ica_fname = op.join(results_dir, file_base +'_0-ica.fif')
  731. ica=mne.preprocessing.read_ica(ica_fname)
  732. ica.exclude = bad_comps
  733. raw=load_data(raw_dataset)
  734. ica.apply(raw)
  735. outfname = op.join(results_dir, 'ica_clean.fif')
  736. raw.save(outfname)
  737. ica.save(ica_fname.replace('_0-ica.fif', '_0-ica_applied.fif'))
  738. def check_datatype(filename): # function to determine the file format of MEG data
  739. '''Check datatype based on the vendor naming convention to choose best loader'''
  740. if os.path.splitext(filename)[-1] == '.ds':
  741. return 'ctf'
  742. elif os.path.splitext(filename)[-1] == '.fif':
  743. return 'fif'
  744. elif os.path.splitext(filename)[-1] == '.4d' or ',' in str(filename):
  745. return '4d'
  746. elif os.path.splitext(filename)[-1] == '.sqd':
  747. return 'kit'
  748. elif os.path.splitext(filename)[-1] == 'con':
  749. return 'kit'
  750. else:
  751. raise ValueError('Could not detect datatype')
  752. def return_dataloader(datatype): # function to return a data loader based on file format
  753. '''Return the dataset loader for this dataset'''
  754. if datatype == 'ctf':
  755. return functools.partial(mne.io.read_raw_ctf, system_clock='ignore',
  756. clean_names=True)
  757. if datatype == 'fif':
  758. return functools.partial(mne.io.read_raw_fif, allow_maxshield=True)
  759. if datatype == '4d':
  760. return mne.io.read_raw_bti
  761. if datatype == 'kit':
  762. return mne.io.read_raw_kit
  763. def load_data(filename): # simple function to load raw MEG data
  764. datatype = check_datatype(filename)
  765. dataloader = return_dataloader(datatype)
  766. raw = dataloader(filename, preload=True)
  767. return raw
  768. #%%
  769. if __name__ == '__main__':
  770. import argparse
  771. parser = argparse.ArgumentParser()
  772. # bids_args = parser.add_argument_group('BIDS')
  773. # bids_args.add_argument('-bids_root', help='foo help')
  774. # bids_args.add_argument('-bids_id', help='BIDS ID')
  775. # bids_args.add_argument('-run', help='Run Number')
  776. # bids_args.add_argument('-task', help='Task name')
  777. parser.add_argument('-filename', help='Path to MEG dataset')
  778. parser.add_argument('-filename_raw', help='''Required for MEGIN data.
  779. Path to the non-SSS data. Do not use this data for
  780. non-MEGIN data.
  781. ''')
  782. parser.add_argument('-outbasename',
  783. help='''Basename for output directory. If none is
  784. provided, the basename up to the suffix of the file
  785. will be used as the folder name inside of the results_dir.
  786. NOTE: this is a required flag for 4D/BTI data - since the
  787. standard filenames are not unique''',
  788. required=False)
  789. parser.add_argument('-results_dir', help='Path to save the results')
  790. parser.add_argument('-line_freq', help='{60,50} Hz - AC electric frequency')
  791. # parser.add_argument('-clean_data',
  792. # help='''Perform classification of the ICA components and
  793. # generate the ICA cleaned dataset''',
  794. # default=True)
  795. args = parser.parse_args()
  796. filename = args.filename
  797. mains_freq = float(args.line_freq)
  798. main(filename, outbasename=args.outbasename, mains_freq=mains_freq,
  799. save_preproc=True, save_ica=True, seedval=0, filename_raw=args.filename_raw,
  800. results_dir=args.results_dir)
  801. ica_dict = classify_ica(results_dir=args.results_dir, outbasename=args.outbasename,
  802. filename=filename)
  803. clean_ica(bad_comps=ica_dict['bads_idx'], results_dir=args.results_dir,
  804. raw_dataset=args.filename, outbasename=args.outbasename)

ICA.py at commit 0a72b95, under BSD-3-Clause · at the source

Overview

Authors: Vahab Youssofzadeh1, Jeffrey R Binder1,2, Joseph Heffernan1, Elizabeth Bock3, Amit Jaiswal3,4, Jeffrey Stout1, Candida Ustine1, Rupesh Kumar Chikara1, Priyanka Shah-Basak1, Colin Humphries1, Jed Mathis1, Lisa L Conant1, William L Gross5, Chad Carlson1, Christopher T Anderson1, Bruce Hermann6, Beth Meyerand7,8,9, Manoj Raghavan1
  1. Neurology, Medical College of Wisconsin, Milwaukee, WI, USA
  2. Biophysics, Medical College of Wisconsin, Milwaukee, WI, USA
  3. MEGIN Oy, Espoo, Finland
  4. Department of Neuroscience and Biomedical Engineering, Aalto University School of Science, Espoo, Finland
  5. Anesthesiology, Medical College of Wisconsin, Milwaukee, WI, USA
  6. Neurology, University of Wisconsin-Madison, Madison, WI, USA
  7. Radiology, University of Wisconsin-Madison, Madison, WI, USA
  8. Medical Physics, University of Wisconsin-Madison, Madison, WI, USA
  9. Biomedical Engineering, University of Wisconsin-Madison, Madison, WI, USA
Institutions: Medical College of Wisconsin (United States); Aalto University (Finland); University of Wisconsin–Madison (United States)
Journal: NeuroImage, volume 338, article 122051
Dates: published online 11 June 2026; in print September 2026
Type: Research article · Language: English
License: CC BY
Identifiers: DOI 10.1016/j.neuroimage.2026.122051 · PMID 42276436 · PMCID PMC13373884 · OpenAlex W7164358761
Open access: gold, a free copy (OpenAlex)
Status: code verified
Categories: fMRI (modality), MEG (modality), human (organism), epilepsy (population), cognitive (subfield)
Methods: Spectral & time-frequency, Connectivity, Statistics, Smoothing, state filtering, decompositions, Machine learning, Preprocessing, Source localization, fMRI & imaging, Physiology & signal measures
Keywords: Magnetoencephalography (MEG), Beta band power suppression, Language lateralization, Temporal lobe epilepsy (TLE), Laterality index, MEG–fMRI concordance, Participant/ROI-specific timing
MeSH: Beta Rhythm*, Epilepsy, Temporal Lobe*, Functional Laterality*, Language*, Magnetic Resonance Imaging*, Magnetoencephalography*, Adult, Brain Mapping, Female, Humans, Male, Middle Aged, Young Adult (* major topic)
Topic: Epilepsy research and treatment (Psychiatry and Mental health, Medicine), according to OpenAlex
Funding: NINDS NIH HHS (U01 NS093650); National Institutes of Health (U01NS093650)
Citations: not cited yet (Europe PMC); 89 references in the paper

Abstract

Accurate identification of the language-dominant hemisphere is essential for surgical planning in drug-resistant focal epilepsy. Functional MRI is widely used as a noninvasive alternative to the Wada test but may be contraindicated or unreliable in some patients, motivating complementary approaches such as magnetoencephalography (MEG). We tested whether beta-band MEG power suppression can provide reliable language lateralization when combined with a task-matched nonlinguistic control, participant/ROI-specific timing, and a multi-threshold weighted-bootstrap laterality index (LI). Seventy-four adults with temporal lobe epilepsy completed visual semantic-decision and false-font control tasks during MEG. Beta-band (17–25 Hz) source power was estimated with DICS beamforming, and LIs were computed in frontal, temporal, angular, and composite lateral ROIs using 300-ms sliding windows. For comparison, participants also completed an auditory semantic-decision versus tone-decision fMRI protocol, with fMRI LIs derived from matched ROIs. MEG showed a left-lateralized spatiotemporal sequence in which frontal LI emerged earlier than temporal–parietal LI, following early bilateral temporo-parieto-occipital engagement. Compared with a prestimulus baseline, the false-font control contrast increased LI magnitude and improved MEG–fMRI correspondence, with a peak MEG–fMRI correlation of r = 0.65. Participant/ROI-specific timing produced only small and mixed changes in concordance. Using the optimized MEG pipeline, MEG and fMRI agreed in 67/74 patients (90.5%; 95% CI: 83–96%; κ = 0.81) Bootstrap LIs performed similarly to magnitude- and vertex-count-based variants while providing confidence intervals. These findings suggest that beta-band MEG suppression during semantic decision, especially when contrasted with a nonlinguistic control task, provides a temporally resolved and clinically feasible marker of hemispheric language dominance that is highly concordant with fMRI.

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.

nih-megcore/MegNET_2020

License: BSD-3-Clause
State: the link answers, verified on 27 September 2026
Evidence: files inventoried
Commit: 0a72b9577ce650012fe2eecef4fea0f0a9c53883, 25 August 2026
Languages: Python (25), Jupyter (1)
Size: 71 files, 26 scripts
Software Heritage: not archived
Found in: “Code and software availability”
Holds: README, license file, environment (conda_environment.yml, conda_environment_update.yml, setup.py), tests, 1 notebook
Not found: CITATION.cff, continuous integration, documentation
Tools: NumPy (21 files), SciPy (17 files), pandas (16 files), TensorFlow (15 files), Keras (14 files), scikit-learn (12 files), Matplotlib (7 files), MNE-Python (6 files), Numba (1 file), Pillow (1 file)
Availability: 1 check, the latest on 27 September 2026: the link answers
  • 27 September 2026: the link answers
28 files

Code and software availability

Analyses used MATLAB R2019a, FieldTrip (https://www.fieldtriptoolbox.org), and Brainstorm (https://neuroimage.usc.edu/brainstorm); anatomical processing used FreeSurfer (https://surfer.nmr.mgh.harvard.edu). MEGnet is available at https://github.com/nih-megcore/MegNET_2020. The Brainstorm plug-in for source magnitude, counting, and weighted-bootstrap LIs is at https://github.com/vyoussofzadeh/meg-laterality-for-Brainstorm. ECP data are publicly available at https://osf.io/exbt4/.

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;
  • 26 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

Datasets cited

Versions

The history of this record: each version stored by the harvester or made by a correction of its authors or of the maintainers of its code, and what changed in its facts. The texts of the paper (its abstract, its availability statements) are not part of it; versions that changed only those are not listed.

Version 2, 28 September 2026

  • Publisher: n/a → Elsevier BV

Version 1, 27 September 2026: the first record

Recorded: type, language, journal, volume, pages, dates, 18 authors, 7 keywords, 13 MeSH terms, 2 funders, 89 references.

Cite

This paper

Youssofzadeh, V., Binder, J. R., Heffernan, J., Bock, E., Jaiswal, A., Stout, J., Ustine, C., Chikara, R. K., Shah-Basak, P., Humphries, C., Mathis, J., Conant, L. L., Gross, W. L., Carlson, C., Anderson, C. T., Hermann, B., Meyerand, B., & Raghavan, M. (2026). Determining hemispheric language dominance from MEG beta-power modulations: Concordance with fMRI. NeuroImage, 338, 122051. https://doi.org/10.1016/j.neuroimage.2026.122051

BibTeX

@article{youssofzadeh2026determining,
author = {Youssofzadeh, Vahab and Binder, Jeffrey R and Heffernan, Joseph and Bock, Elizabeth and Jaiswal, Amit and Stout, Jeffrey and Ustine, Candida and Chikara, Rupesh Kumar and Shah-Basak, Priyanka and Humphries, Colin and Mathis, Jed and Conant, Lisa L and Gross, William L and Carlson, Chad and Anderson, Christopher T and Hermann, Bruce and Meyerand, Beth and Raghavan, Manoj},
title = {{Determining hemispheric language dominance from MEG beta-power modulations: Concordance with fMRI}},
journal = {NeuroImage},
year = {2026},
month = jun,
volume = {338},
pages = {122051},
publisher = {Elsevier BV},
issn = {1053-8119},
doi = {10.1016/j.neuroimage.2026.122051},
url = {https://doi.org/10.1016/j.neuroimage.2026.122051},
pmid = {42276436},
pmcid = {PMC13373884}
}

RIS

TY - JOUR
AU - Youssofzadeh, Vahab
AU - Binder, Jeffrey R
AU - Heffernan, Joseph
AU - Bock, Elizabeth
AU - Jaiswal, Amit
AU - Stout, Jeffrey
AU - Ustine, Candida
AU - Chikara, Rupesh Kumar
AU - Shah-Basak, Priyanka
AU - Humphries, Colin
AU - Mathis, Jed
AU - Conant, Lisa L
AU - Gross, William L
AU - Carlson, Chad
AU - Anderson, Christopher T
AU - Hermann, Bruce
AU - Meyerand, Beth
AU - Raghavan, Manoj
TI - Determining hemispheric language dominance from MEG beta-power modulations: Concordance with fMRI
T2 - NeuroImage
J2 - Neuroimage
PY - 2026
DA - 2026/06/11
VL - 338
SP - 122051
SN - 1053-8119
PB - Elsevier BV
DO - 10.1016/j.neuroimage.2026.122051
UR - https://doi.org/10.1016/j.neuroimage.2026.122051
LA - en
ER -

CSL-JSON

{
"id": "10.1016/j.neuroimage.2026.122051",
"type": "article-journal",
"title": "Determining hemispheric language dominance from MEG beta-power modulations: Concordance with fMRI",
"container-title": "NeuroImage",
"author": [
{
"family": "Youssofzadeh",
"given": "Vahab"
},
{
"family": "Binder",
"given": "Jeffrey R"
},
{
"family": "Heffernan",
"given": "Joseph"
},
{
"family": "Bock",
"given": "Elizabeth"
},
{
"family": "Jaiswal",
"given": "Amit"
},
{
"family": "Stout",
"given": "Jeffrey"
},
{
"family": "Ustine",
"given": "Candida"
},
{
"family": "Chikara",
"given": "Rupesh Kumar"
},
{
"family": "Shah-Basak",
"given": "Priyanka"
},
{
"family": "Humphries",
"given": "Colin"
},
{
"family": "Mathis",
"given": "Jed"
},
{
"family": "Conant",
"given": "Lisa L"
},
{
"family": "Gross",
"given": "William L"
},
{
"family": "Carlson",
"given": "Chad"
},
{
"family": "Anderson",
"given": "Christopher T"
},
{
"family": "Hermann",
"given": "Bruce"
},
{
"family": "Meyerand",
"given": "Beth"
},
{
"family": "Raghavan",
"given": "Manoj"
}
],
"container-title-short": "Neuroimage",
"volume": "338",
"page": "122051",
"DOI": "10.1016/j.neuroimage.2026.122051",
"PMID": "42276436",
"PMCID": "PMC13373884",
"ISSN": "1053-8119",
"publisher": "Elsevier BV",
"URL": "https://doi.org/10.1016/j.neuroimage.2026.122051",
"language": "en",
"issued": {
"date-parts": [
[
2026,
6,
11
]
]
}
}

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.1016/j.cnp.2026.06.005
Reliability of magnetoencephalography beta desynchronization for language lateralization in a subsequent memory effect paradigm.
Journal: Clinical neurophysiology practice
In common: MEG, 10 references
[2] doi:10.1038/s41467-026-71918-7 [code]
Developmental disinhibition gates language lateralization in childhood.
Journal: Nature communications
In common: MNE-Python, scikit-learn, pandas, 3 other tools, 7 references
[3] doi:10.1038/s41597-025-05174-7 [code]
A large-scale MEG and EEG dataset for object recognition in naturalistic scenes
Journal: n/a
In common: Keras, TensorFlow, MNE-Python, 6 other tools, MEG, fMRI, 2 references
[4] doi:10.1093/cercor/bhag075 [code]
Cortical dynamics of icon perception: effects of concreteness and attractiveness.
Journal: Cerebral cortex (New York, N.Y. : 1991)
In common: MNE-Python, Pillow, scikit-learn, 4 other tools, MEG, cognitive, 4 references
[5] doi:10.1038/s41598-026-41532-0 [code]
Prediction, syntax and semantic grounding in the brain and large language models.
Journal: Scientific reports
In common: MNE-Python, scikit-learn, pandas, 3 other tools, MEG, cognitive, 4 references
[6] doi:10.1523/eneuro.0023-26.2026 [code]
Real-Time Segmentation and Classification of Birdsong Syllables for Learning Experiments.
Journal: eNeuro
In common: Keras, Numba, TensorFlow, 6 other tools, cognitive
[7] doi:10.7554/elife.100605 [code]
Age-related changes in ‘cortical’ 1/f dynamics are linked to cardiac activity
Journal: n/a
In common: MNE-Python, scikit-learn, pandas, 3 other tools, MEG, 3 references
[8] doi:10.1038/s42003-026-10957-8 [code]
Brain defence by the extracellular matrix protein Cochlin.
Journal: Communications biology
In common: Keras, Numba, TensorFlow, 6 other tools
[9] doi:10.1371/journal.pcbi.1014571 [code]
SynAPSeg: A novel dataset and image analysis framework for deep learning-based synapse detection and quantification.
Journal: PLoS computational biology
In common: Keras, Numba, TensorFlow, 6 other tools
[10] doi:10.1016/j.xcrm.2026.102766 [code]
A longitudinal single-cell and spatial multiomic atlas of pediatric high-grade glioma.
Journal: Cell reports. Medicine
In common: Keras, Numba, TensorFlow, 6 other tools

Contribute

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

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

Request its removal

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

Discussion, reproductions, activity

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

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

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