OSCR

Transcranial focused ultrasound induces source localizable cortical activation in resting state humans when applied concurrently with transcranial electric stimulation.

Code ↔ Paper

9 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 9 matches · 2 of them tie a paragraph to a whole file, not to given lines: weak matches, whose lines are not tinted
  1. [1] § Methods › EEG analysis › Linear mixed effect modeling ↔ lmer.R, the whole file · a weak match · score 0.98 · square root transformed, natural log transformed, classification metric, strictly positive, inclusively bound, ROI activity
  2. [2] § Methods › EEG analysis › EEG preprocessing ↔ python_utils/preprocessing_utils.py, lines 15–100 · score 0.97 · bandpass filtered, 1–40 Hz, muscle artifacts, peak amplitude, Bad channels, tDCS
  3. [3] § Methods › EEG analysis › EEG source imaging ↔ python_utils/mne_utils.py, lines 441–509 · score 0.89 · noise covariance matrix, head models, dSPM, EEG electrodes, EEG source imaging, MNE Python
  4. [4] § Methods › EEG analysis › Connectivity analysis ↔ python_utils/stats_utils.py, lines 252–310 · score 0.81 · power spectral density, Imaginary coherence, ImCoh, Welch, 1 second, window
  5. [5] § Methods › EEG analysis › EEG source imaging ↔ python_utils/mne_utils.py, lines 625–747 · score 0.65 · bounding box, projecting electrodes, inside, cortex, space, modeling
  6. [6] § Methods › EEG analysis › Linear mixed effect modeling ↔ lmer.R, the whole file · a weak match · score 0.61 · lme4, ROI activity, recall, variables, linear, precision
  7. [7] § Results › itFUS, but not tEAS, induces endogenous dissociations between the ROI and deep brain ↔ python_utils/stats_utils.py, lines 252–310 · score 0.55 · imaginary coherence, ImCoh, spectral
  8. [8] § Methods › EEG analysis › Connectivity analysis ↔ python_utils/stats_utils.py, lines 480–548 · score 0.54 · Granger causality, Toda, Yamamoto, Wald, lag
  9. [9] § Results › Increased tFUS parameter searches continued to yield no evidence of ROI suprathreshold stimulation ↔ python_utils/mne_utils.py, lines 399–423 · score 0.52 · FSAverage, source morphed, brain model, spaces

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 · 747 lines · 27 KB · MIT · 3 matches

  1. # -*- coding: utf-8 -*-
  2. """
  3. Helper functions for interacting with MNE Python objects.
  4. Written by Joshua Kosnoff
  5. [email hidden]
  6. """
  7. import mne
  8. import numpy as np
  9. import nibabel.freesurfer as fs
  10. import os
  11. import platform
  12. import datetime
  13. import typing
  14. import shutil
  15. mne.set_log_level('CRITICAL')
  16. def evoked_to_epoch(mne_evoked):
  17. """
  18. Convert an instance of an MNE evoked into an MNE Epoch.
  19. It is common practice to take the average (or evoked) response over
  20. several trials for EEG analysis. This can be done with Epoch.average() in
  21. standard MNE usage. However, once averaged into an Evoked structure,
  22. the data have limited functionality. This will convert an Evoked object
  23. into an Epoched object to maintain functionality.
  24. Parameters
  25. ----------
  26. mne_evoked : mne.Evoked
  27. The evoked data structure.
  28. Returns
  29. -------
  30. epoch : mne.Epochs
  31. The data now within an epoched format.
  32. """
  33. data_shape = mne_evoked.get_data().shape
  34. epoch = mne.EpochsArray(mne_evoked.get_data().reshape(1, data_shape[0], data_shape[1]),
  35. mne_evoked.info, tmin=mne_evoked.times[0])
  36. return epoch
  37. def plot_joint_zscored(mne_epoch, times=[],
  38. title=None,
  39. ylims=[None, None],
  40. topomap_args={}):
  41. """
  42. A wrapper function for MNE's plot_joint that correctly scales the axes.
  43. MNE Python's data structure assumes data are stored in V and wants to plot
  44. in uV, so plotting functions default to multiplying by 1e6. If data were
  45. z-scored, they do not need this adjustment. This will pre-emptively
  46. multiply data by 1e-6 and re-label the y-axis.
  47. Parameters
  48. ----------
  49. mne_epoch : mne.Epochs
  50. The epoched data.
  51. times : list
  52. A list of times to show the spatial topomap distribution. Optional.
  53. ylims : list.
  54. A list of [ymin, ymax] for imposing on the final plot. Optional.
  55. topomap_args : dict
  56. A dictionary of kwargs to pass to the plot_joint function. Optional.
  57. Returns
  58. -------
  59. None.
  60. """
  61. _epochs = mne_epoch.copy()
  62. _epochs._data *= 1e-6
  63. fig = _epochs.copy().average().plot_joint(times=times, show=False, title=title,
  64. topomap_args=topomap_args)
  65. fig.axes[-3].set_ylabel('Z-Score')
  66. fig.axes[-3].set_ylim(ylims[0], ylims[1])
  67. fig.delaxes(fig.axes[-1])
  68. return fig
  69. def plot_butterfly_zscored(mne_epoch, ylims=[None, None], title='',
  70. ax=None):
  71. """
  72. A wrapper function for MNE's plot that correctly scales the axes.
  73. MNE Python's data structure assumes data are stored in V and wants to plot
  74. in uV, so plotting functions default to multiplying by 1e6. If data were
  75. z-scored, they do not need this adjustment. This will pre-emptively
  76. multiply data by 1e-6 and re-label the y-axis.
  77. Parameters
  78. ----------
  79. mne_epoch : mne.Epochs
  80. The epoched data.
  81. ylims : list.
  82. A list of [ymin, ymax] for imposing on the final plot. Optional.
  83. title : str
  84. Title for the plot. Optional.
  85. ax : matplotlib.axes.Axes
  86. An existing axis to plot the data. Optional.
  87. Returns
  88. -------
  89. None.
  90. """
  91. _epochs = mne_epoch.copy()
  92. _epochs._data *= 1e-6
  93. fig = _epochs.copy().average().plot(show=False, axes=ax)
  94. fig.axes[0].set_title(title)
  95. fig.axes[0].set_ylabel('Z-Score')
  96. fig.axes[0].set_ylim(ylims[0], ylims[1])
  97. return
  98. def get_loc_error(stc, label, src):
  99. """
  100. Calculate the localization error between the highest amplitude voxel and the target label.
  101. Parameters
  102. ----------
  103. stc : mne.SourceEstimate
  104. The subject's source estimate
  105. label : mne.Label
  106. The 'ground truth'
  107. src : mne.SourceSpace
  108. The subject's source space.
  109. Returns
  110. -------
  111. localization error : np.float
  112. The minimum distance from the maximum voxel to the ground truth. Units are in m.
  113. """
  114. vertices_lh, vertices_rh = stc.vertices
  115. coords_lh = src[0]["rr"][vertices_lh] # Left hemisphere
  116. coords_rh = src[1]["rr"][vertices_rh] # Right hemisphere
  117. # Max Voxel x/y/z
  118. max_voxel_loc = np.vstack((coords_lh, coords_rh))[
  119. stc._data.argmax(axis=0)[0], :]
  120. # Target x/y/z (Take the geometric mean)
  121. label_verts = label.vertices
  122. if label.hemi == 'lh':
  123. target_loc = src[0]["rr"][label_verts]
  124. elif label.hemi == 'rh':
  125. target_loc = src[1]["rr"][label_verts]
  126. return np.linalg.norm(max_voxel_loc - target_loc, axis=1).min()
  127. def zscore_evoked(mne_epoch, baseline_win=[-0.500, -0.100]):
  128. """
  129. Z-score baseline correct an epoch based on the trial average.
  130. Steps:
  131. 1. Average all trials into an evoked waveform.
  132. 2. Z-score the evoked waveform w.r.t. baseline period
  133. Parameters
  134. ----------
  135. mne_epoch : mne.Epochs
  136. The epoched data.
  137. baseline_win : list
  138. The list of times for the start and of the baseline period. Used for
  139. determine z-score mu and std.
  140. Returns
  141. -------
  142. mne_epoch : mne.Epochs
  143. The trial_averaged and z-scored data.
  144. """
  145. mne_epoch = evoked_to_epoch(mne_epoch.average())
  146. mne_epoch = mne_epoch.apply_function(
  147. fun=mne.baseline.rescale,
  148. times=mne_epoch.times,
  149. baseline=(baseline_win[0], baseline_win[1]),
  150. mode='zscore')
  151. return mne_epoch
  152. class SourceImage:
  153. """
  154. A comprehensive class for EEG source imaging using MNE Python.
  155. This class provides methods for coregistration, source space setup,
  156. and source imaging for EEG data.
  157. """
  158. def __init__(
  159. self,
  160. subject: str,
  161. subjects_dir: str,
  162. nasion: str = "NAS",
  163. surface='pial',
  164. rpa: str = "RPA",
  165. lpa: str = "LPA",
  166. bem_solver: str = "mne",
  167. src_spacing: str = "oct6",
  168. n_jobs: int = -1,
  169. ):
  170. """
  171. Initialize the SourceImage object.
  172. Parameters
  173. ----------
  174. subject : str
  175. The subject's ID.
  176. subjects_dir : str
  177. Path to the FreeSurfer processed MRI directory.
  178. nasion : str, optional
  179. Landmark key for nasion in digitization file.
  180. rpa : str, optional
  181. Landmark key for right preauricular point.
  182. lpa : str, optional
  183. Landmark key for left preauricular point.
  184. bem_solver : str, optional
  185. BEM algorithm to use ('mne' or 'openmeeg').
  186. src_spacing : str, optional
  187. Source space spacing/downsampling.
  188. n_jobs : int, optional
  189. Number of parallel processes to use.
  190. """
  191. self.subject = subject
  192. self.subjects_dir = subjects_dir
  193. self.save_folder = fr'{self.subjects_dir}/{subject}/mne_fifs'
  194. if not os.path.exists(self.save_folder):
  195. try:
  196. os.makedirs(self.save_folder)
  197. except FileExistsError:
  198. pass
  199. self.nasion = nasion
  200. self.rpa = rpa
  201. self.lpa = lpa
  202. self.bem_solver = bem_solver
  203. self.src_spacing = src_spacing
  204. self.n_jobs = n_jobs
  205. # Additional attributes to be set during processing
  206. self.info = None
  207. self.src = None
  208. self.fwd = None
  209. self.inv = None
  210. self.surface = surface
  211. def auto_coregister(
  212. self,
  213. save_path: str = "",
  214. session_id: str = "",
  215. fiducials: str = "auto",
  216. ):
  217. """
  218. Create a coregistration file for the subject's fiducial points.
  219. Parameters
  220. ----------
  221. save_path : str, optional
  222. Path to save the coregistration file.
  223. session_id : str, optional
  224. Session identifier for file naming.
  225. fiducials : str, optional
  226. Fiducial point specification.
  227. Returns
  228. -------
  229. mne.transforms.Transform or None
  230. The transformation if successful, None otherwise.
  231. """
  232. try:
  233. if not self.info:
  234. raise ValueError("EEG info must be set before coregistration")
  235. coreg = mne.coreg.Coregistration(
  236. self.info,
  237. self.subject,
  238. os.path.join(self.subjects_dir, ""),
  239. fiducials=fiducials,
  240. )
  241. coreg = coreg.omit_head_shape_points(
  242. distance=5.0 / 1000
  243. ) # distance in meters
  244. coreg = coreg.fit_icp(n_iterations=100, nasion_weight=100.0,
  245. rpa_weight=25.0,
  246. lpa_weight=25.0, verbose=True)
  247. mne.write_trans(save_path, coreg.trans, overwrite=True)
  248. except Exception as e:
  249. print(f"Coregistration error: {e}")
  250. def setup_source_space(
  251. self,
  252. head_model: str,
  253. session_id: str,
  254. conductivity: tuple = (0.3, 0.006, 0.3),
  255. ) -> None:
  256. """
  257. Set up source space and forward solution for source imaging.
  258. Parameters
  259. ----------
  260. head_model : str
  261. MRI file ID to use for the subject.
  262. session_id : str
  263. Session identifier.
  264. conductivity : tuple, optional
  265. Conductivity values for head model layers.
  266. """
  267. # BEM file handling
  268. # BEM is based on MRI --> don't need to resolve BEM for every session
  269. bem_fif = os.path.join(self.save_folder, f"{head_model}-bem.fif")
  270. if os.path.isfile(bem_fif):
  271. bem = mne.read_bem_solution(bem_fif)
  272. else:
  273. print(
  274. f"Writing BEM file for {os.path.basename(bem_fif).split('-')[0]}"
  275. )
  276. model = mne.make_bem_model(
  277. subject=head_model,
  278. ico=4,
  279. conductivity=conductivity,
  280. subjects_dir=self.subjects_dir,
  281. )
  282. bem = mne.make_bem_solution(model, solver=self.bem_solver)
  283. mne.write_bem_solution(bem_fif, bem)
  284. # Coregistration file handling
  285. subject_trans_fif = os.path.join(
  286. self.save_folder, f"{head_model}_{session_id}-trans.fif"
  287. )
  288. if not os.path.isfile(subject_trans_fif):
  289. self.auto_coregister(
  290. save_path=subject_trans_fif, session_id=session_id
  291. )
  292. # If accessing files via Box, Windows os seem to need assistance in
  293. # pre-downloading certain files.
  294. # Download the necessary file from Box drive in order to run
  295. # This step is only necessary if trying to access models on Box
  296. p1 = self.subjects_dir + r"/" + head_model + r"/surf/lh.pial"
  297. p2 = self.subjects_dir + r"/" + head_model + r"/surf/lh.pial.T1"
  298. try:
  299. # Try opening the correctly named file
  300. with open(p1, "r") as _:
  301. pass
  302. except FileNotFoundError:
  303. # If the correct file doesn't exist, check if the incorrectly named file exists
  304. if os.path.exists(p2):
  305. # Copy the incorrectly named file to the correct name
  306. shutil.copy(p2, p1)
  307. p1 = self.subjects_dir + r"/" + head_model + r"/surf/rh.pial"
  308. p2 = self.subjects_dir + r"/" + head_model + r"/surf/rh.pial.T1"
  309. try:
  310. # Try opening the correctly named file
  311. with open(p1, "r") as _:
  312. pass
  313. except FileNotFoundError:
  314. # If the correct file doesn't exist, check if the incorrectly named file exists
  315. if os.path.exists(p2):
  316. # Copy the incorrectly named file to the correct name
  317. shutil.copy(p2, p1)
  318. # Setup source space and forward solution
  319. self.src = mne.setup_source_space(
  320. subject=head_model,
  321. spacing=self.src_spacing,
  322. surface=self.surface, # formerly inflated?
  323. add_dist="patch",
  324. subjects_dir=self.subjects_dir,
  325. )
  326. self.fwd = mne.make_forward_solution(
  327. self.info,
  328. trans=subject_trans_fif,
  329. src=self.src,
  330. bem=bem_fif,
  331. eeg=True,
  332. meg=False,
  333. mindist=5.0,
  334. n_jobs=self.n_jobs,
  335. )
  336. def morph_to_FS(
  337. self, stc: mne.SourceEstimate, spacing=5
  338. ) -> mne.SourceEstimate:
  339. """
  340. Compute a source-morph of a source estimate to the fsaverage brain model.
  341. Parameters
  342. ----------
  343. stc : mne.SourceEstimate
  344. The source estimate to source-morph
  345. Returns
  346. -------
  347. fs_morph : mne.SourceEstimate
  348. The source estimate morphed to the FSAverage brain model.
  349. """
  350. fs_morph = mne.compute_source_morph(
  351. self.inv["src"],
  352. subject_from=self.subject,
  353. subjects_dir=self.subjects_dir,
  354. subject_to="fsaverage",
  355. verbose="error",
  356. spacing=spacing,
  357. ).apply(stc)
  358. return fs_morph
  359. def _spatial_smooth_stc(self, stc, smooth):
  360. """
  361. Spatially smooth the stc by morphing it to itself.
  362. """
  363. stc = mne.compute_source_morph(
  364. self.inv["src"],
  365. subject_from=self.subject,
  366. subjects_dir=self.subjects_dir,
  367. subject_to=self.subject,
  368. verbose="error",
  369. src_to=self.inv["src"],
  370. smooth=smooth,
  371. ).apply(stc)
  372. return stc
  373. def basic_esi_from_epoch(
  374. self,
  375. epoch: mne.Epochs,
  376. montage: typing.Union[str, mne.channels.DigMontage],
  377. method: str = "MNE",
  378. head_model: str = "auto",
  379. baseline: typing.List[float] = [None, 0],
  380. lambda2: float = 1 / 9,
  381. label: typing.Optional[mne.Label] = None,
  382. zscore_prestim: bool = False,
  383. noise_cov: typing.Optional[mne.Covariance] = None,
  384. average: typing.Union[bool, str] = 'mean',
  385. conductivity: tuple = (0.3, 0.006, 0.3),
  386. smooth: int = 15,
  387. return_generator: bool = False,
  388. pick_ori=None,
  389. ) -> mne.SourceEstimate:
  390. """
  391. Perform EEG Source Imaging on epoched data.
  392. Cribbed heavily from various MNE Python tutorials. Functionalized by Joshua Kosnoff.
  393. Parameters
  394. ----------
  395. epoch : mne.Epoch
  396. The epoched data to source image.
  397. montage : path-like \ str
  398. The montage to use for the EEG electrodes in leadfield modeling. If using a custom montage, provide the the path to
  399. the EEG digitization file. Otherwise, indicate the string of the MNE Python premade montage to use. See
  400. mne.channels.get_builtin_montages() for all the current predefined options. Please note that, currently, this function
  401. only accepts custom Localite RAS digitization files. However, MNE Python does have additional support for other localization
  402. software. Please check their documentation and edit the read_dig_localite function accordingly.
  403. method : str
  404. The ESI method. MNE Python Currently supports 'MNE', 'dSPM', 'sLORETA', and 'eLORETA'.
  405. head_model : str
  406. The name of the MRI file ID to use for the subject. 'auto' will use the subject ID defined in the class initialization. In most
  407. cases, this will be correct. In some cases, some subjects may not have MRI files, in which case other head models may be specified.
  408. Defaults to 'auto'.
  409. baseline : list[float, float]
  410. The time points (in seconds) to use as the start and end of the epoch baseline period. Example: [0, 10.8]
  411. Default is [None, 0], which will consider all the prestimulus activity.
  412. lambda2 : float
  413. The regularization parameter. Defaults to 1/9 based on the MNE Python tutorials.
  414. label : mne.Label \ None
  415. If not None, restrict the source estimate to only the voxels within the label. Default is None, which returns the whole brain.
  416. zscore_prestim : bool
  417. Whether to zscore the EEG data based on prestimulus period (up to time 0) before performing ESI calculations.
  418. Default is False.
  419. noise_cov: mne.Covariance / None
  420. The noise covariance of the data (if precomputed). If None, it will calculate a noise covariance matrix based on the
  421. prestimulus baseline. Default is None.
  422. average : bool
  423. If True, will compute the source image on the average of the epochs (aka the evoked waveform), as opposed to on each
  424. epoch seperately. Defaults to True.
  425. conductivity : Tuple(float)
  426. The conductivities to use for each layer of the head model. Defaults are from MNE Python tutorials.
  427. smooth :
  428. return_generator : bool
  429. Only used if average is False. Returns a generator object of stcs and not a list of stcs to preserve memory.
  430. Returns
  431. -------
  432. stc : mne.SourceEstimate
  433. The source estimate for the subject's experimental condition.
  434. """
  435. assert montage in mne.channels.get_builtin_montages() or os.path.exists(
  436. montage
  437. ), "Error! Expected 'montage' to either be a path to digitization file or a built-in MNE Python montage."
  438. # Similar implementation to the original method
  439. # ... (with added type checking and error handling)
  440. epoch = epoch.copy()
  441. if head_model == "auto":
  442. head_model = self.subject
  443. # If using a custom head model, the BEM modeling needs to be
  444. if montage not in mne.channels.get_builtin_montages():
  445. # If there is a measurement date in the MNE Epoch's meta date, use
  446. # that for the session ID. If not, use the file creation date for the
  447. # RAS file.
  448. # Note that this session ID naming convention does assuming that
  449. # each subject will only have 1 digitization per day. If this is not
  450. # the case, will probably want to do some kind of additional constraint.
  451. dt = epoch.info["meas_date"]
  452. if dt is None:
  453. system = platform.system()
  454. if system == "Windows":
  455. c_time = os.path.getctime(montage)
  456. else:
  457. c_time = os.stat(montage).st_birthtime
  458. dt = datetime.datetime.fromtimestamp(c_time)
  459. session_id = dt.strftime("%Y_%m_%d")
  460. else:
  461. session_id = montage
  462. # Load the RAS montage
  463. # In our lab, we save RPA, LPA, and NAS with those keys. We need to explicitly pass those
  464. # into the read_dig_localite function.
  465. if montage not in mne.channels.get_builtin_montages():
  466. mont = mne.channels.read_dig_localite(
  467. montage, nasion=self.nasion, lpa=self.lpa, rpa=self.rpa
  468. )
  469. epoch.set_montage(mont, match_case=False, on_missing="warn")
  470. else:
  471. epoch.set_montage(montage, match_case=False, on_missing="warn")
  472. self.info = epoch.info
  473. self.setup_source_space(head_model=head_model, session_id=session_id)
  474. if zscore_prestim:
  475. epoch_prestim = epoch.copy().crop(baseline[0], baseline[1])._data
  476. epoch._data = (
  477. epoch._data - epoch_prestim.mean(axis=-1, keepdims=True)
  478. ) / epoch_prestim.std(axis=-1, keepdims=True)
  479. # Create Common Average Reference (reference is required for ESI)
  480. epoch.set_eeg_reference("average", projection=True)
  481. # Compuse noise matrix based on the the stimulus baseline
  482. # Only do this if there is no passed noise covariance
  483. if type(noise_cov) == type(None):
  484. # print("Computing noise covariance . . . ")
  485. noise = epoch.copy().crop(
  486. baseline[0], baseline[1], include_tmax=False
  487. )
  488. noise_cov = mne.compute_covariance(
  489. noise, method=["shrunk", "empirical"], rank=None
  490. )
  491. # Make the inverse operator
  492. self.inv = mne.minimum_norm.make_inverse_operator(
  493. self.info, self.fwd, noise_cov
  494. )
  495. # Calculate the time series
  496. if average:
  497. if method in ['dSPM', 'MNE', 'sLORETA', 'eLORETA']:
  498. stc = mne.minimum_norm.apply_inverse(
  499. epoch.average(method=average),
  500. self.inv,
  501. lambda2=lambda2,
  502. method=method,
  503. label=label,
  504. pick_ori=pick_ori,
  505. )
  506. elif method == 'gamma':
  507. stc = mne.inverse_sparse.gamma_map(
  508. epoch.average(method=average),
  509. forward=self.fwd,
  510. noise_cov=noise_cov,
  511. alpha=lambda2,
  512. )
  513. elif method == 'TF-MxNE':
  514. stc = mne.inverse_sparse.tf_mixed_norm(
  515. epoch.average(method=average),
  516. forward=self.fwd,
  517. noise_cov=noise_cov,
  518. alpha=40, # lambda2,
  519. l1_ratio=0.05, # ??
  520. )
  521. elif method == 'MxNE':
  522. stc = mne.inverse_sparse.mixed_norm(
  523. epoch.average(method=average),
  524. forward=self.fwd,
  525. noise_cov=noise_cov,
  526. alpha=lambda2,
  527. )
  528. else:
  529. stc = mne.minimum_norm.apply_inverse_epochs(
  530. epoch, self.inv, lambda2=lambda2, method=method, label=label,
  531. return_generator=return_generator, pick_ori=pick_ori,
  532. )
  533. return stc
  534. def elec_to_inf_label(sensor_info, src, subjects_dir, transfile):
  535. """
  536. Creates a label on the inflated brain model based on an electrode bounding box.
  537. Parameters
  538. ----------
  539. sensor_info : mne.Info
  540. subjects_dir : str
  541. src : mne.SourceSpace
  542. transfile: str
  543. The path to the subject's EEG-MRI coregistration -trans.fif
  544. """
  545. subject = src[0].get('subject_his_id', None)
  546. # Get coordinates in use for passed SRC
  547. passed_coords = [] # x/y/z spatial info
  548. passed_verts = [] # corresponding vertex numbers
  549. for i, hemi in enumerate(['lh', 'rh']):
  550. # Left hemisphere (array of shape [n_vertices_lh, 3])
  551. passed_coords.append(
  552. src[i]['rr'][np.array(src[i]['inuse'], dtype=bool), :])
  553. passed_verts.append(src[i]['vertno'])
  554. # Get coordinates in use for the inflated brain model
  555. inflated_coords = []
  556. for i, hemi in enumerate(['lh', 'rh']):
  557. inflated_fname = f'{subjects_dir}/{subject}/surf/{hemi}.inflated'
  558. _coords = fs.read_geometry(inflated_fname)[0][src[i]['vertno'], :]
  559. # Inflated brain is a mess and needs to be altered.
  560. # Right X needs to be fully positive
  561. # Left X needs to be fully negative
  562. if i == 0:
  563. _coords = 1 + _coords - ([_coords.max(axis=0)[0], 0, 0])
  564. else:
  565. _coords = -1 + _coords - ([_coords.min(axis=0)[0], 0, 0])
  566. inflated_coords.append(_coords)
  567. # project electrodes to the cortex of passed SRC
  568. proj_info = mne.preprocessing.ieeg.project_sensors_onto_brain(sensor_info,
  569. trans=mne.read_trans(
  570. transfile),
  571. subject=subject,
  572. subjects_dir=subjects_dir,
  573. picks=[
  574. 'eeg'],
  575. copy=True, verbose=None)
  576. pos = np.array([proj_info['chs'][proj_info['ch_names'].index(
  577. ch)]['loc'][:3] for ch in proj_info.ch_names])
  578. # Compute nearest vertex
  579. pos_verts = []
  580. pos_inf_coords = []
  581. for p in pos:
  582. pos_verts.append(np.linalg.norm(
  583. np.vstack(passed_coords) - p, axis=1).argmin())
  584. pos_inf_coords.append(np.vstack(inflated_coords)[pos_verts[-1], :])
  585. all_coords = np.vstack(inflated_coords)
  586. pos_inf_coords = np.vstack(pos_inf_coords)
  587. # Determine the bounding box by finding min and max for each dimension
  588. bbox_min = pos_inf_coords.min(axis=0) # [min_x, min_y, min_z]
  589. bbox_max = pos_inf_coords.max(axis=0) # [max_x, max_y, max_z]
  590. # We downsampled it later, which might lose some info, so grow it a bit first.
  591. for i in range(3):
  592. range_ = bbox_max[i] - bbox_min[i]
  593. bbox_min[i] -= 0.025 * range_
  594. bbox_max[i] += 0.025 * range_
  595. # print(bbox_min)
  596. # print(bbox_max)
  597. # print(bbox_min, bbox_max)
  598. # Find vertices inside the bounding box
  599. inside_bbox = np.all((all_coords >= bbox_min) &
  600. (all_coords <= bbox_max), axis=1)
  601. all_data = np.zeros((all_coords.shape[0], 1))
  602. all_data[inside_bbox] = 1
  603. label_data = mne.SourceEstimate(data=all_data,
  604. vertices=passed_verts,
  605. subject=subject,
  606. tmin=0, tstep=1)
  607. lh_labels, rh_labels = mne.stc_to_label(
  608. label_data,
  609. src=src,
  610. smooth=True,
  611. subjects_dir=subjects_dir,
  612. connected=True,
  613. verbose="error",
  614. )
  615. # print(len(src[0]['vertno']), src[0]['vertno'])
  616. # print(len(np.sum(lh_labels).vertices), np.sum(lh_labels).vertices)
  617. # Annoyingly, we have to spatially downsample this for the fill() function to work. Not really sure
  618. # why that is, but unfortunately, we're stuck with it.
  619. _src = mne.setup_source_space(
  620. subject=subject, spacing='ico2', subjects_dir=subjects_dir, add_dist=False)
  621. # lh_labels = np.sum(mne.grow_labels(subject,
  622. # seeds = np.sum(lh_labels).vertices[::10],
  623. # extents = 1, hemis = 0, subjects_dir=subjects_dir,
  624. # surface='inflated', colors = np.array([[1, 1, 1, 1]]),
  625. # )).restrict(src=_src).fill(src=_src) if lh_labels else None
  626. lh_labels = np.sum(lh_labels).restrict(
  627. src=_src).fill(src=_src) if lh_labels else None
  628. rh_labels = np.sum(rh_labels).restrict(
  629. src=_src).fill(src=_src) if rh_labels else None
  630. # all_labels = np.sum([l for l in [lh_labels, rh_labels] if l])
  631. # all_labels = all_labels.fill(src=src)
  632. return lh_labels if lh_labels else rh_labels

mne_utils.py at commit 8d48b12, under MIT · at the source

Overview

Authors: Joshua Kosnoff1, Colton Gonsisko1, Kai Yu1, Joseph Zhang1, Yidan Ding1, Yisha Zhang1, Bin He1,2
  1. Department of Biomedical Engineering, Carnegie Mellon University, Pittsburgh, PA USA
  2. Neuroscience Institute, Carnegie Mellon University, Pittsburgh, PA USA
Institutions: Carnegie Mellon University (United States)
Journal: Nature communications, volume 17, issue 1, article 2023
Dates: received 19 August 2025; accepted 29 January 2026; published online 10 March 2026
Type: Research article · Language: English
License: CC BY-NC-ND
Identifiers: DOI 10.1038/s41467-026-69853-8 · PMID 41807379 · PMCID PMC12976073 · OpenAlex W7134967028
Open access: gold, a free copy (OpenAlex)
Status: code verified
Categories: EEG (modality), other (modality), human (organism), computational (subfield)
Methods: Spectral & time-frequency, Statistics, Smoothing, state filtering, decompositions, Connectivity, Preprocessing, Evoked potentials, fMRI & imaging, Physiology & signal measures
Keywords: Brain-machine interface, Biophysical models, Biomedical engineering, Neurophysiology, Computational biophysics
MeSH: Cerebral Cortex*, Rest*, Transcranial Direct Current Stimulation*, Acoustic Stimulation, Adult, Electroencephalography, Evoked Potentials, Female, Humans, Male, Ultrasonic Waves, Young Adult (* major topic)
Topic: Ultrasound and Hyperthermia Applications (Biomedical Engineering, Engineering), according to OpenAlex
Funding: U.S. Department of Health &amp; Human Services | NIH | National Institute of Biomedical Imaging and Bioengineering (EB029365); NINDS NIH HHS (RF1 NS124564, RF1 NS131069, R01 NS124564, R01 NS127849, R01 NS096761); U.S. Department of Health &amp; Human Services | NIH | National Institute of Neurological Disorders and Stroke (NS124564, NS131069, NS127849, NS096761); NIBIB NIH HHS (T32 EB029365); National Science Foundation (DGE2140739)
Citations: cited by 4 papers (Europe PMC); 84 references in the paper
Research resources: RRID:SCR_023356

Abstract

The abstract is not reproduced here: the paper's license (CC BY-NC-ND) does not allow it. Read it in the paper, at the publisher or on Europe PMC.

Repository

Its files are read in the Code ↔ Paper reader above, with 9 matches between paragraphs and lines of code.

bfinl/tFUS-ESI

License: MIT
State: the link answers, verified on 30 September 2026
Evidence: files inventoried
Commit: 8d48b1257d2a331ba89e4320b09df1c30bff3d52, 9 January 2026
Languages: Python (5), R (1)
Size: 8 files, 6 scripts
Software Heritage: not archived
Found in: “Code availability”
Holds: README, license file
Not found: CITATION.cff, environment file, tests, continuous integration, documentation
Tools: NumPy (4 files), MNE-Python (2 files), BayesFactor (1 file), car (1 file), ggplot2 (1 file), lme4 (1 file), multcomp (1 file), NiBabel (1 file), pandas (1 file), Pingouin (1 file), rpy2 (1 file), SciPy (1 file), statsmodels (1 file), tidyverse (1 file)
Availability: 1 check, the latest on 30 September 2026: the link answers
  • 30 September 2026: the link answers
8 files

Code availability statement

The paper has a code availability statement. Its license (CC BY-NC-ND) does not allow reproducing it here; in short, from what the harvester recognized in it:

Read it in the paper: doi.org/10.1038/s41467-026-69853-8.

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;
  • 6 scripts, each with its path and the digest of its content;
  • 9 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

Data availability statement

The paper has a data availability statement. Its license (CC BY-NC-ND) does not allow reproducing it here; in short, from what the harvester recognized in it:

Read it in the paper: doi.org/10.1038/s41467-026-69853-8.

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

Recorded: type, language, journal, volume, issue, pages, dates, 7 authors, 5 keywords, 12 MeSH terms, 5 funders, 79 references, 1 RRID.

Cite

This paper

Kosnoff, J., Gonsisko, C., Yu, K., Zhang, J., Ding, Y., Zhang, Y., & He, B. (2026). Transcranial focused ultrasound induces source localizable cortical activation in resting state humans when applied concurrently with transcranial electric stimulation. Nature communications, 17(1), 2023. https://doi.org/10.1038/s41467-026-69853-8

BibTeX

@article{kosnoff2026transcranial,
author = {Kosnoff, Joshua and Gonsisko, Colton and Yu, Kai and Zhang, Joseph and Ding, Yidan and Zhang, Yisha and He, Bin},
title = {{Transcranial focused ultrasound induces source localizable cortical activation in resting state humans when applied concurrently with transcranial electric stimulation}},
journal = {Nature communications},
year = {2026},
month = mar,
volume = {17},
number = {1},
pages = {2023},
publisher = {Nature Publishing Group},
issn = {2041-1723},
doi = {10.1038/s41467-026-69853-8},
url = {https://doi.org/10.1038/s41467-026-69853-8},
pmid = {41807379},
pmcid = {PMC12976073}
}

RIS

TY - JOUR
AU - Kosnoff, Joshua
AU - Gonsisko, Colton
AU - Yu, Kai
AU - Zhang, Joseph
AU - Ding, Yidan
AU - Zhang, Yisha
AU - He, Bin
TI - Transcranial focused ultrasound induces source localizable cortical activation in resting state humans when applied concurrently with transcranial electric stimulation
T2 - Nature communications
J2 - Nat Commun
PY - 2026
DA - 2026/03/10
VL - 17
IS - 1
SP - 2023
SN - 2041-1723
PB - Nature Publishing Group
DO - 10.1038/s41467-026-69853-8
UR - https://doi.org/10.1038/s41467-026-69853-8
LA - en
ER -

CSL-JSON

{
"id": "10.1038/s41467-026-69853-8",
"type": "article-journal",
"title": "Transcranial focused ultrasound induces source localizable cortical activation in resting state humans when applied concurrently with transcranial electric stimulation",
"container-title": "Nature communications",
"author": [
{
"family": "Kosnoff",
"given": "Joshua"
},
{
"family": "Gonsisko",
"given": "Colton"
},
{
"family": "Yu",
"given": "Kai"
},
{
"family": "Zhang",
"given": "Joseph"
},
{
"family": "Ding",
"given": "Yidan"
},
{
"family": "Zhang",
"given": "Yisha"
},
{
"family": "He",
"given": "Bin"
}
],
"container-title-short": "Nat Commun",
"volume": "17",
"issue": "1",
"page": "2023",
"DOI": "10.1038/s41467-026-69853-8",
"PMID": "41807379",
"PMCID": "PMC12976073",
"ISSN": "2041-1723",
"publisher": "Nature Publishing Group",
"URL": "https://doi.org/10.1038/s41467-026-69853-8",
"language": "en",
"issued": {
"date-parts": [
[
2026,
3,
10
]
]
}
}

The tracing map gets a citation of its own once an author has validated it and it has a DOI.

Similar papers

The papers with a page that share the most with this one: the tools found in their code, their categories, datasets, cited references and authors, the rarest counting most.

[1] doi:10.1038/s41467-026-75826-8
Transcranial focused ultrasound modulates spiking, LFP, and BOLD activity in the primate thalamus.
Journal: Nature communications
In common: other, 11 references
[2] doi:10.1038/s41378-026-01391-1
High pressure transcranial focused ultrasound stimulation induces parameter-dependent cell-type specific effects.
Journal: Microsystems & nanoengineering
In common: other, 7 references, author Bin He
[3] doi:10.1162/imag.a.1245 [code]
Towards precision EEG connectomics: Evaluating the benefits of dense sampling.
Journal: Imaging neuroscience (Cambridge, Mass.)
In common: Pingouin, MNE-Python, lme4, 7 other tools, EEG, 4 references
[4] 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: BayesFactor, Pingouin, car, 8 other tools, EEG, 1 reference
[5] doi:10.1093/nc/niag029 [code]
A data-driven approach to identifying and evaluating connectivity-based neural correlates of conscious visual perception.
Journal: Neuroscience of consciousness
In common: BayesFactor, Pingouin, car, 8 other tools, 1 reference
[6] doi:10.1073/pnas.2510015122 [code]
Mapping epileptogenic brain using a unified spatial–temporal–spectral source imaging framework
Journal: n/a
In common: EEG, 2 references, 2 authors
[7] doi:10.1038/s41597-026-07377-y [code]
An open-access multi-site fMRI dataset for investigating conscious visual perception.
Journal: Scientific data
In common: BayesFactor, Pingouin, car, 8 other tools
[8] doi:10.1016/j.neuroimage.2026.121861
Experimental evaluation of an integrated focused ultrasound and electroencephalography approach for developing activation-informed neuroimaging.
Journal: NeuroImage
In common: other, EEG, 8 references
[9] doi:10.1162/imag.a.105 [code]
Right posterior theta reflects human parahippocampal phase resetting by salient cues during goal-directed navigation
Journal: n/a
In common: Pingouin, car, MNE-Python, 8 other tools, EEG
[10] doi:10.1038/s41467-026-75435-5 [code]
Sensory-guided human-machine joint learning accelerates the acquisition of motor imagery brain computer interface control.
Journal: Nature communications
In common: MNE-Python, SciPy, NumPy, EEG, 2 authors

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.