OSCR

PyLossless: A non-destructive EEG processing pipeline.

Code ↔ Paper

18 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 18 matches · 1 of them tie a paragraph to a whole file, not to given lines: a weak match, whose lines are not tinted
  1. [1] § Materials and methods › Pipeline steps › Flag bridged sensors ↔ notebooks/pipeline_algorithms.ipynb, lines 446–527 · score 0.89 · flagging bridged sensors, high median correlation, inter quantile range, low IQR, bridging threshold, bridge indicator
  2. [2] § Materials and methods › Pipeline steps › Filtering ↔ examples/plot_0_implementation.py, lines 394–404 · score 0.84 · low frequency drifts, 1–100 Hz, notch filter, ICLabel, trained, classifier
  3. [3] § Materials and methods › Pipeline steps › Flag uncorrelated sensors ↔ notebooks/pipeline_algorithms.ipynb, lines 558–642 · score 0.77 · lower quantile range, low correlation, rejecting sensors, define epoch, sensors dimension, uncorrelated
  4. [4] § Materials and methods › Pipeline steps › Notation ↔ examples/plot_0_implementation.py, lines 1–53 · score 0.75 · lowercase letters, capital letters, subscripts, superscripts, scalars, denote
  5. [5] § Materials and methods › Pipeline steps › Flag noisy sensors ↔ pylossless/pipeline.py, lines 1034–1063 · score 0.71 · voltage variance, detect outlier, standard deviation, Flag noisy, outlying, threshold
  6. [6] § Materials and methods › Pipeline steps › Flag uncorrelated epochs ↔ pylossless/pipeline.py, lines 1418–1498 · score 0.65 · flag uncorrelated epochs, high impedance, neighboring, thresholds, EEG
  7. [7] § Materials and methods › Pipeline steps › Initial ICA and flagging of outlying independent component (IC) ↔ pylossless/pipeline.py, lines 1418–1498 · score 0.64 · flag_noisy_ics, flag noisy epochs, ICLabel, ICA, EEG, pipeline
  8. [8] § Materials and methods › Pipeline steps › Applying the PyLossless decisions ↔ pylossless/config/rejection.py, lines 13–56 · score 0.63 · RejectionPolicy, subtract, brain, confidence, EOG, class
  9. [9] § Materials and methods › Pipeline steps › Flag noisy sensors ↔ examples/plot_0_implementation.py, lines 210–225 · score 0.59 · right tail, spread, multiply, UQR, median, deviation
  10. [10] § Materials and methods › Pipeline steps › Saving the pipeline output ↔ pylossless/flagging.py, lines 252–330 · score 0.59 · MNE ICALabel, independent component, TSV, derivatives, root, preprocessing
  11. [11] § Materials and methods › Pipeline steps › Initial ICA and flagging of outlying independent component (IC) ↔ examples/plot_0_implementation.py, lines 394–404 · score 0.58 · ICLabel, ICA decompositions, flag noisy, classifier, epochs, pipeline
  12. [12] § Materials and methods › Pipeline steps › Flag bridged sensors ↔ pylossless/pipeline.py, lines 1136–1165 · score 0.56 · flagging bridged, bridged channel, IQR, median, correlation, pipeline
  13. [13] § Materials and methods › Licence and dependencies ↔ pylossless/datasets/datasets.py, the whole file · a weak match · score 0.56 · optional dependency, MNE BIDS, installation, PyLossless, EEG, pipeline
  14. [14] § Materials and methods › Pipeline steps › Saving the pipeline output ↔ pylossless/dash/qcgui.py, lines 48–87 · score 0.55 · project_root, PyLossless pipeline, derivatives, BIDS, MNE, Python
  15. [15] § Materials and methods › Pipeline steps › Final ICA and IC classification ↔ pylossless/flagging.py, lines 252–330 · score 0.55 · MNE ICALabel, independent component, classifier, ICLabel
  16. [16] § Materials and methods › Pipeline steps › Notation ↔ examples/plot_0_implementation.py, lines 1–53 · score 0.54 · single sensor, single epoch, denote, Notation, matrices, dimension
  17. [17] § Materials and methods › Pipeline steps › Flag the rank sensor ↔ notebooks/pipeline_algorithms.ipynb, lines 536–556 · score 0.54 · highest median, correlation coefficient, good sensors, rank, flags, channels
  18. [18] § Materials and methods › Pipeline steps › Flag noisy time periods ↔ examples/plot_0_implementation.py, lines 227–246 · score 0.53 · matrix indicates, standard deviation, vectors, outlier, thresholds, sensors

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 · 750 lines · 25 KB · MIT · 6 matches

  1. r"""
  2. PyLossless Algorithms
  3. =====================
  4. This tutorial explains the calculations that PyLossless performs at each step of the
  5. pipeline. We will use example EEG data to demonstrate the
  6. calculations.
  7. .. note::
  8. You can open this notebook in
  9. `Google Colab <https://colab.research.google.com/drive/1ecyNo10oFgpbVNuD7Ztgr2fs8XkpfOYo?usp=sharing>`_!
  10. .. _notation:
  11. Notation
  12. --------
  13. Before we begin, we define some notation that will be used throughout the text:
  14. - We start with a 3D matrix of EEG data,
  15. :math:`X \in \mathbb{R}^{S_\mathcal{G} \times E_\mathcal{G} \times T}`,
  16. where :math:`S_\mathcal{G}` and :math:`E_\mathcal{G}` are the sets of good sensors and
  17. epochs, respectively, and :math:`T`, is the number of samples(i.e. time-points).
  18. - ``s``, ``e``, and ``t`` are sensor, epochs, and samples, respectively.
  19. - We use superscripts to denote operations across a dimension, and we use subscripts to
  20. denote indexing a dimension.
  21. - We refer to a single sensor :math:`i` as
  22. :math:`X\big|_{s=i} \in \mathbb{R}^{E_\mathcal{G} \times T}`,
  23. with :math:`i \in S_\mathcal{G}`.
  24. - We refer to a single epoch :math:`j` as
  25. :math:`X\big|_{e=j} \in \mathbb{R}^{S_\mathcal{G} \times T}`,
  26. with :math:`j \in E_\mathcal{G}`.
  27. - We denote sensor-specific thresholds for rejecting epochs as
  28. :math:`\tau^e_i \in \mathbb{R}^{S_\mathcal{G}}`
  29. - We denote epoch-specific thresholds for rejecting sensors as
  30. :math:`\tau^s_j \in \mathbb{R}^{E_\mathcal{G}}`
  31. - We denote *quantiles* as :math:`Q\#^{dim}`: i.e. :math:`Q75^s` is the 75th *quantile*
  32. along the sensor dimension. The function :math:`Q75^s(X)` computes the 75th quantile
  33. along the :math:`s` dimension of matrix :math:`X`, resulting in a matrix noted
  34. :math:`X^{Q75^s} \in \mathbb{R}^{E \times T}`.
  35. Throughout the text, we use capital letters for matrices and lowercase letters for
  36. scalars. For example, the data point for sensor :math:`i`, epoch :math:`j`, and
  37. time :math:`k` is denoted as :math:`X\big|_{s=i; e=j; t=k} = x_{ijk} \in \mathbb{R}`,
  38. and :math:`X=\{x_{ijk}\}`.
  39. """
  40. # %%
  41. # Imports and data loading
  42. # ------------------------
  43. from pathlib import Path
  44. import numpy as np
  45. import mne
  46. from mne.datasets import sample
  47. import pylossless as ll
  48. # Load example mne data
  49. raw = ll.datasets.load_simulated_raw()
  50. # %%
  51. # Load a PyLossless configuration file
  52. # ------------------------------------
  53. # Let's load a PyLossless configuration file. This file contains the parameters that
  54. # will be used for each step of the pipeline. For example, the ``noisy_channels``
  55. # section contains the parameters for the :ref:`noisy_sensors` step. We can modify
  56. # these parameters to change the behavior of the pipeline. For example, we can change
  57. # the percent of epochs that a sensor must be noisy for it to be flagged via the
  58. # ``flag_crit`` parameter.
  59. config = ll.config.Config()
  60. config.load_default()
  61. config["noisy_channels"]["outliers_kwargs"]["lower"] = 0.25 # lower quantile
  62. config["noisy_channels"]["outliers_kwargs"]["upper"] = 0.75 # upper quantile
  63. config["noisy_channels"]["flag_crit"] = 0.30 # percent of epochs that a sensor must be noisy
  64. config.save("test_config.yaml")
  65. # %%
  66. # Create a pipeline instance
  67. # --------------------------
  68. pipeline = ll.LosslessPipeline("test_config.yaml")
  69. pipeline.raw = raw
  70. raw.plot()
  71. # %%
  72. # Input Data
  73. # ----------
  74. #
  75. # First, we epoch the data to be used for subsequent steps.
  76. # Let our 3D matrix below be defined as :math:`X \in \mathbb{R}^{S \times E \times T}`
  77. # where :math:`X` is a matrix of real numbers and of dimension :math:`S` sensors
  78. # :math:`\times$ E` epochs `\times T` times.
  79. #
  80. epochs = pipeline.get_epochs()
  81. # %%
  82. #
  83. # Let's convert our epochs object into a named :class:`xarray.DataArray` object.
  84. from pylossless.pipeline import epochs_to_xr
  85. #
  86. epochs_xr = epochs_to_xr(epochs, kind="ch")
  87. epochs_xr # 277 epochs, 50 sensors, 602 samples per epoch
  88. # %%
  89. # .. _robust_reference:
  90. #
  91. # Robust Average Reference
  92. # ------------------------
  93. #
  94. # .. figure:: https://raw.githubusercontent.com/scott-huberty/wip_pipeline-figures/main/robust_rereference.png
  95. # :align: center
  96. # :alt: Robust Average Reference graphic.
  97. #
  98. # Robust Average Reference. The figure shows the steps for robust average referencing.
  99. # See the text below for descriptions of mathematical notation.
  100. #
  101. # Before the pipeline can begin, we must average reference the data. This is because
  102. # the pipeline uses data distributions to identify noisy sensors, and For EEG data that
  103. # uses an online reference to a single electrode, sensors that are further from the
  104. # reference will have a higher voltage variance, and the pipeline will be biased to
  105. # flag these sensors as noisy. The average reference, which subtracts the average
  106. # signal across sensors from each individual sensor, will ensure an even playing field.
  107. # Howeer, we dont want to include noisy sensors in the average reference signal. So we
  108. # will identify noisy sensors and and leave them out of the average reference signal.
  109. # %%
  110. sample_std = epochs_xr.std("time")
  111. q25_ch = sample_std.quantile(0.25, dim="ch")
  112. q50_ch = sample_std.median(dim="ch")
  113. q75_ch = sample_std.quantile(0.75, dim="ch")
  114. ch_dist = sample_std - q50_ch # center the data
  115. ch_dist /= q75_ch - q25_ch # shape (chans, epoch)
  116. mean_ch_dist = ch_dist.mean(dim="epoch") # shape (chans)
  117. # find the median and 25 and 75 percentiles
  118. # of the mean of the channel distributions
  119. mdn = np.median(mean_ch_dist)
  120. deviation = np.diff(np.quantile(mean_ch_dist, [0.25, 0.75]))
  121. leave_out = mean_ch_dist.ch[mean_ch_dist > mdn + 6 * deviation].values.tolist()
  122. leave_out
  123. # %%
  124. ref_chans = [ch for ch in epochs.pick("eeg").ch_names if ch not in leave_out]
  125. pipeline.raw.set_eeg_reference(ref_channels=ref_chans)
  126. # %%
  127. #
  128. # .. _noisy_sensors:
  129. #
  130. # Flag Noisy Sensors
  131. # ------------------
  132. # .. figure:: https://raw.githubusercontent.com/scott-huberty/wip_pipeline-figures/main/Flag_noisy_sensors.png
  133. # :align: center
  134. # :alt: Flag Noisy Sensors graphic.
  135. #
  136. # Flag Noisy Sensors. The figure shows the steps for flagging noisy sensors. See the text below
  137. # for descriptions of mathematical notation.
  138. #
  139. # %%
  140. # Since we applied a robust average reference to the raw data, we will need to re-epoch
  141. # the data:
  142. epochs = pipeline.get_epochs()
  143. epochs_xr = epochs_to_xr(epochs, kind="ch")
  144. # First we take standard deviation of
  145. # :math:`X \in \mathbb{R}^{S \times E \times T}` across the samples dimension :math:`t`
  146. # resulting in a 2D matrix :math:`X^{\sigma_{t}} \in \mathbb{R}^{S \times E}`
  147. trim_ch_sd = epochs_xr.std("time")
  148. trim_ch_sd
  149. # %%
  150. # a) Take the 50th and 75th quantile across dimension sensor of :math:`X^{\sigma_{t}}`
  151. # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  152. #
  153. # This operation results in two 1D vectors of size :math:`E`:
  154. #
  155. # .. math::
  156. # X^{{\sigma}_t{Q50^s}} = Q50^s(X^{\sigma_{t}}) \in \mathbb{R}^{E}
  157. # .. math::
  158. # X^{{\sigma}_t{Q75^s}} = Q75^s(X^{\sigma_{t}}) \in \mathbb{R}^{E}
  159. # %%
  160. q50, q75 = trim_ch_sd.quantile([0.5, 0.75], dim="ch")
  161. q50 # a 1D array of median standard deviation values across channels for each epoch
  162. # %%
  163. # b) Define an Upper Quantile Range as :math:`Q75 - Q50`
  164. # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  165. #
  166. # .. math::
  167. # UQR^s = X^{{\sigma}_T{Q75}^s} - X^{{\sigma}_T{Q50}^s}
  168. #
  169. # This operation results in a 1D vector of size :math:`E`.
  170. uqr = q75 - q50
  171. uqr
  172. # %%
  173. # c) Identify outlier Indices :math:`(i, j)`
  174. # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  175. #
  176. # We multiply a constant :math:`k` by the :math:`UQR` to define a measure for the
  177. # spread of the right tail of the distribution of :math:`X^{\sigma_{t}}` values and
  178. # add it to the median of :math:`X^{\sigma_{t}}` to obtain epoch-specific standard
  179. # deviation threshold for outliers:
  180. #
  181. # .. math::
  182. # \tau^s_j = X^{{\sigma}_T{Q50}^S} + UQR^s\times k
  183. #
  184. # That is, :math:`\tau^s_j` is the epoch-specific threshold for the epoch :math:`j`
  185. k = 3
  186. upper_threshold = q50 + q75 * k
  187. upper_threshold # epoch specific thresholds
  188. # %%
  189. # Now, we compare our 2D standard deviation matrix to the threshold vector of
  190. # :math:`\tau^e_j`:
  191. #
  192. # .. math::
  193. # X^{\sigma_{t}} \big|_{e=j} > \tau^s_j
  194. #
  195. # resulting in the indicator matrix :math:`C \in \{0, 1\}^{S \times E}=\{c_{ij}\}`:
  196. #
  197. # .. math::
  198. # c_{ij} =
  199. # \begin{cases}
  200. # 0 & \text{if } x^{\sigma_{t}}_{ij} < \tau^s_j \\
  201. # 1 & \text{if } x^{\sigma_{t}}_{ij} \geq \tau^s_j
  202. # \end{cases}
  203. #
  204. # Each element of this matrix indicates whether sensor :math:`i` is an outlier at an epoch
  205. # :math:`j`.
  206. outlier_mask = trim_ch_sd > upper_threshold
  207. outlier_mask
  208. # %%
  209. # d) Identify noisy sensors part 1
  210. # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  211. #
  212. # To identify outlier sensors, we average across the epoch dimension of our indicator
  213. # matrix :math:`C` and obtain :math:`C^{\mu_e} \in \mathbb{R}^{S_\mathcal{G}}`, which
  214. # is a vector of fractional numbers :math:`c^{\mu_e}_i` representing the percentage of
  215. # epochs for which that sensor is an outlier.
  216. percent_outliers = outlier_mask.astype(float).mean("epoch")
  217. percent_outliers # percent of epochs that sensor is an outlier
  218. # %%
  219. # e) Identify noisy sensors part 2
  220. # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  221. #
  222. # Next, we define a threshold :math:`\tau^{p}` (:math:`p` for percentile;
  223. # default, ``.20``) as a cutoff point for determining if a sensor should be marked
  224. # artifactual. The sensor :math:`i` is flagged as noisy if
  225. # :math:`c^{\mu_e}_i > \tau^{p}`. That is, if the sensor is an outlier for more than
  226. # :math:`\tau^{p}` percent of the epochs, it is flagged as noisy.
  227. p_threshold = config["noisy_channels"]["flag_crit"] # 0.3, or 30%
  228. noisy_chs = percent_outliers[percent_outliers > p_threshold].coords.to_index().values
  229. noisy_chs
  230. # %%
  231. # f) Add the noisy sensors to the pipeline flags
  232. # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  233. #
  234. # Let's add the noisy sensors to the pipeline flags.
  235. pipeline.flags["ch"].add_flag_cat(kind="noisy", bad_ch_names=noisy_chs)
  236. pipeline.raw.info["bads"].extend(pipeline.flags["ch"]["noisy"].tolist())
  237. pipeline.flags["ch"]
  238. # %%
  239. #
  240. # .. _noisy_epochs:
  241. #
  242. # Flag Noisy Epochs
  243. # -----------------
  244. #
  245. # This step closely resembles the :ref:`noisy_sensors` step. For the sake of brevity
  246. # we will be more concise in the documentation.
  247. # %%
  248. #
  249. # .. figure:: https://raw.githubusercontent.com/scott-huberty/wip_pipeline-figures/main/Flag_noisy_epochs.png
  250. # :align: center
  251. #
  252. # Flag Noisy Epochs. The figure shows the steps for flagging noisy epochs. See the text below
  253. # for descriptions of mathematical notation.
  254. #
  255. # %%
  256. # a) Take standard deviation across the samples dimension :math:`t`
  257. # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  258. #
  259. # Take a moment below to notice that the sensors flagged in the prior setp are not in
  260. # ``epochs_xr`` below:
  261. epochs = pipeline.get_epochs()
  262. # Let's make our epochs array into a named Array
  263. epochs_xr = epochs_to_xr(epochs, kind="ch")
  264. trim_ch_sd = epochs_xr.std("time")
  265. trim_ch_sd.coords["ch"]
  266. # %%
  267. # b) Compute 50th and 75th quantile across epochs and the UQR
  268. # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  269. #
  270. # Like before, We Take the median and 70th quantile, but now we operate across epochs,
  271. # resulting in two 1D vector's of size ``n_good_sensors`` :math:`S_\mathcal{G}`
  272. #
  273. # .. math::
  274. # X^{{\sigma}_t{Q50^e}} = Q50^e(X^{\sigma_{t}}) \in \mathbb{R}^{S_\mathcal{G}}
  275. # .. math::
  276. # X^{{\sigma}_t{Q75^e}} = Q75^e(X^{\sigma_{t}}) \in \mathbb{R}^{S_\mathcal{G}}
  277. # .. math::
  278. # UQR^e = (X^{{\sigma}_T{Q75}^e} - X^{{\sigma}_T{Q50}^e})
  279. # %%
  280. # c) Define sensor-specific thresholds for rejecting epochs :math:`\tau^e_i`
  281. # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  282. #
  283. # Our sensor-specifc threshold for rejecting epochs is defined by:
  284. #
  285. # .. math::
  286. # \tau^e_i = X^{{\sigma}_T{Q50}^e} + UQR^e\times k
  287. q50, q75 = trim_ch_sd.quantile([0.5, 0.75], dim="epoch")
  288. uqr_epoch = q75 - q50
  289. uqr_epoch
  290. # %%
  291. k = 8
  292. upper_threshold = q50 + uqr_epoch * k
  293. upper_threshold
  294. # %%
  295. # d) Identify Outlier indices
  296. # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  297. #
  298. # The indicator matrix is defined by:
  299. #
  300. # .. math::
  301. # c_{ij} =
  302. # \begin{cases}
  303. # 0 & \text{if } x^{\sigma_{t}}_{ij} < \tau^e_i \\
  304. # 1 & \text{if } x^{\sigma_{t}}_{ij} \geq \tau^e_i
  305. # \end{cases}
  306. #
  307. #
  308. # To identify outlier **epochs**, we average across the **sensor** dimension of our
  309. # indicator matrix :math:`C` and obtain
  310. # :math:`C^{\mu_s} \in \mathbb{R}^{E_\mathcal{G}}`, which is a vector of numbers
  311. # :math:`c^{\mu_s}_j` representing the percentage of **sensors** for which that epoch
  312. # is an outlier.
  313. outlier_mask = trim_ch_sd > upper_threshold
  314. outlier_mask
  315. # %%
  316. percent_outliers = outlier_mask.astype(float).mean("ch")
  317. percent_outliers
  318. # %%
  319. # e) Identify noisy epochs
  320. # ^^^^^^^^^^^^^^^^^^^^^^^^
  321. #
  322. # Next, we define a fractional threshold :math:`\tau^{p}` as a cutoff point for
  323. # determining if a epoch should be marked artifactual. The epoch :math:`j` is flagged
  324. # as noisy if :math:`c^{\mu_s}_j > \tau^{p}`.
  325. bad_epochs = percent_outliers[percent_outliers > p_threshold].coords.to_index().values
  326. bad_epochs
  327. # %%
  328. # f) Add the noisy epochs to the pipeline flags
  329. # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  330. #
  331. # Let's add the outlier epochs to our flags
  332. # These will be added directly as :class:`mne.Annotations` to the raw data.
  333. pipeline.flags["epoch"].add_flag_cat(
  334. kind="noisy", bad_epoch_inds=bad_epochs, epochs=epochs
  335. )
  336. pipeline.raw.annotations.description
  337. # %%
  338. pipeline.raw.plot()
  339. # %%
  340. # Filtering
  341. # ---------
  342. #
  343. # After flagging noisy sensors and epochs, we filter the data. By default,
  344. # The pipeline uses a 1-100Hz bandpass filter. This is because 1), ICA decompositions
  345. # are more stable when low frequency drifts are removed, and 2) the ICLabel classifier
  346. # is trained on data that has been filtered between 1-100Hz. A notch filter can also be
  347. # optionally specified.
  348. pipeline.config["filtering"]["notch_filter_args"]["freqs"] = [50]
  349. pipeline.filter()
  350. # %%
  351. # Find Nearest Neighbours & return Maximum Correlation
  352. # ----------------------------------------------------
  353. #
  354. # .. figure:: https://raw.githubusercontent.com/scott-huberty/wip_pipeline-figures/main/Nearest_neighbors.png
  355. # :align: center
  356. # :alt: Nearest Neighbors graphic.
  357. #
  358. # Nearest Neighbors. The figure shows the steps for finding nearest neighbors. See the text below
  359. # for descriptions of mathematical notation.
  360. #
  361. # %%
  362. # Whereas :ref:`noisy_sensors` and :ref:`noisy_epochs` operated on a 2D matrix of
  363. # standard deviation values, The next few steps will operate on correlation
  364. # coefficients. Here we describe the procedure for defining the 2D matrix of correlation
  365. # coefficients.
  366. # %%
  367. from pylossless.pipeline import chan_neighbour_r
  368. # %%
  369. #
  370. # Notice that our flagged epochs are dropped.
  371. epochs = pipeline.get_epochs()
  372. # %%
  373. # a) Calculate Correlation Coefficients between each Sensor and its neighboring eighbors
  374. # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  375. #
  376. # - For each good sensor $i$ in :math:`S_{\mathcal{G}}`, we select its :math:`N` nearest
  377. # neighbors. I.e. the :math:`N` sensors that are closest to it.
  378. #
  379. # - We call the sensor :math:`i` the *origin*, and its nearest neighbors :math`\hat{s_l}`
  380. # with :math:`l \in \{1, 2, \ldots, N\}`
  381. #
  382. # - Then, for each epoch :math:`j`, we calculate the correlation coefficient
  383. # :math:`\rho^t_{(i,\hat{s_l}),j}` between origin sensor :math:`i` and each neighbor
  384. # :math:`\hat{s_l}` across dimension :math:`t` (samples), returning a 3D matrice of
  385. # correlation coefficients:
  386. #
  387. # .. math::
  388. # \mathrm{P}^t = \{\rho^t_{(i, \hat{s_l}),j}\} \in \mathbb{R}^{S_G \times E_G \times n}
  389. #
  390. # Finally, we select the maximum correlation coefficient across the neighbor dimension
  391. # :math:`n`:
  392. #
  393. # .. math::
  394. # \mathrm{P}^{t,{\text{max}}^n}= \max\limits_{\hat{s_l}} \rho^t_{(i, \hat{s_l}),j}
  395. #
  396. # Returning a 2D matrix where each value at :math:`(i, j)` is the maximum correlation
  397. # coefficient between sensor :math:`i` and its :math:`N` nearest neighbors, at each epoch
  398. # :math:`j`
  399. # %%
  400. data_r_ch = chan_neighbour_r(epochs, nneigbr=3, method="max")
  401. # maximum correlation out of correlations between ch and its 3 neighbors
  402. data_r_ch
  403. # %%
  404. # This matrix :math:`\mathrm{P}^{t,{\text{max}}^n}` will be used in the steps below.
  405. # %%
  406. # Flag Bridged Sensors
  407. # --------------------
  408. #
  409. # .. figure:: https://raw.githubusercontent.com/scott-huberty/wip_pipeline-figures/main/Flag_bridged_sensors.png
  410. # :align: center
  411. # :alt: Flag Bridged Sensors graphic.
  412. #
  413. # Flag Bridged Sensors. The figure shows the steps for flagging bridged sensors.
  414. # See the text below for descriptions of mathematical notation.
  415. #
  416. # %%
  417. # a) Calculate the 50th, 75th quantile and IQR across epochs
  418. # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  419. #
  420. # .. math::
  421. # IQR^e = \mathrm{P}^{t,{\text{max}}^nQ75^e} - \mathrm{P}^{t,{\text{max}}^nQ25^e}
  422. #
  423. # For each sensor, divide the median across epochs by the IQR across epochs. Bridged
  424. # channels should have a high median correlation but a low IQR of the correlation.
  425. # We call this measure the bridge-indicator.
  426. #
  427. # .. math::
  428. # \mathcal{B}_s = \frac{\mathrm{P}^{t,{\text{max}}^nQ50^e}}{IQR^e}
  429. #
  430. # b) Define a bridging threshold
  431. # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  432. # Now, take the 25th, 50th, and 75th quantile of :math:`\mathcal{B}_s` across sensors,
  433. # And calculate the :math:`IQR^s`. A channel :math:`i` is bridged if
  434. #
  435. # .. math::
  436. # \mathcal{B}_i > B^{Q50^s} +k \times IQR^s
  437. #
  438. # %%
  439. import scipy
  440. from functools import partial
  441. # %%
  442. msr = data_r_ch.median("epoch") / data_r_ch.reduce(scipy.stats.iqr, dim="epoch")
  443. # msr is a 1D vector of size n_sensors
  444. config_trim = 40
  445. config_bridge_z = 6
  446. #
  447. trim = config_trim
  448. if trim >= 1:
  449. trim /= 100
  450. trim /= 2 # .20 and will be used as (.20, .20)
  451. #
  452. trim_mean = partial(scipy.stats.mstats.trimmed_mean, limits=(trim, trim))
  453. trim_std = partial(scipy.stats.mstats.trimmed_std, limits=(trim, trim))
  454. #
  455. z_val = config_bridge_z # 6
  456. mask = msr > msr.reduce(trim_mean, dim="ch") + z_val * msr.reduce(
  457. trim_std, dim="ch"
  458. ) # bridged chans
  459. #
  460. bridged_ch_names = data_r_ch.ch.values[mask]
  461. bridged_ch_names
  462. # %%
  463. # Let's add the outlier channels to our flags
  464. bad_chs = bridged_ch_names
  465. pipeline.flags["ch"].add_flag_cat(kind="bridged", bad_ch_names=bad_chs)
  466. pipeline.flags["ch"]
  467. # %%
  468. # Identify the Rank Channel
  469. # -------------------------
  470. #
  471. # Because the pipeline uses an average reference before the ICA decomposition, it is
  472. # necessary to account for rank deficiency (i.e., every sensor in the montage is
  473. # linearly dependent on the other channels due to the common average reference). To
  474. # account for this, the pipeline flags the sensor (out of the remaining good sensors)
  475. # with the highest median of the max correlation coefficient with its neighbors
  476. # (across epochs):
  477. #
  478. # .. math::
  479. # \begin{equation}
  480. # i = \text{arg}\max\limits_i \rho_{i}^{t,{\text{max}}^n,median^j}
  481. # \end{equation}
  482. #
  483. # This sensor has the least unique time-series out of the remaining set of good sensors
  484. # :math:`S_\mathcal{G}` and is flagged by the pipeline as ``”rank”``. Note that this
  485. # sensor is not flagged because it contains artifact, but only because one of the
  486. # remaining sensors needs to be removed to address rank deficiency before ICA
  487. # decomposition is performed. By choosing this sensor, we are likely to lose little
  488. # information because of its high correlation with its neighbors. This sensor can be
  489. # reintroduced after the ICA has been applied for artifact corrections.
  490. # %%
  491. good_chs = [
  492. ch for ch in data_r_ch.ch.values if ch not in pipeline.flags["ch"].get_flagged()
  493. ]
  494. data_r_ch_good = data_r_ch.sel(ch=good_chs)
  495. flag_ch = [str(data_r_ch_good.median("epoch").idxmax(dim="ch").to_numpy())]
  496. pipeline.flags["ch"].add_flag_cat(kind="rank", bad_ch_names=flag_ch)
  497. pipeline.flags["ch"]
  498. # %%
  499. # Flag low correlation Epochs
  500. # ---------------------------
  501. #
  502. # This step is designed to identify time periods in which many sensors are
  503. # uncorrelated with neighboring sensors. It is similar to the :ref:`noisy_sensors` step,
  504. #
  505. # Again we calculate the 25th and 50th quantile
  506. # of :math:`\mathrm{P}^{t,{\text{max}}^n}`, across the epochs dimension, and calculate
  507. # the lower quantile range :math:`LQR^s`. This results in vectors
  508. # :math:`\mathrm{P}^{t,{\text{max}}^nQ25^e}` and
  509. # :math:`\mathrm{P}^{t,{\text{max}}^nQ50^e}` of size :math:`S_\mathcal{G}`. As for previous
  510. # steps, we define sensor-specific thresholds for flagging epochs:
  511. #
  512. # .. math::
  513. # \begin{equation}
  514. # \tau^e = \mathrm{P}^{t,{\text{max}}^nQ50^e} - LQR^e\times k
  515. # \end{equation}
  516. #
  517. # And the corresponding indicator matrix:
  518. #
  519. # .. math::
  520. # \begin{equation}
  521. # c_{ij} =
  522. # \begin{cases}
  523. # 1 & \text{if } \rho^{t,{\text{max}}^n}_{ij} < \tau^e_i \\
  524. # 0 & \text{if } \rho^{t,{\text{max}}^n}_{ij} \geq \tau^e_i
  525. # \end{cases}
  526. # \end{equation}
  527. #
  528. # We average the indicator matrix across sensors and obtain a vector :math:`C^{\mu_s}`
  529. # that we use to flag uncorrelated epochs using the following criterion:
  530. #
  531. # .. math::
  532. # c^{\mu_e}_i > \tau^{p}.
  533. #
  534. # %%
  535. # Step a
  536. q25, q50 = data_r_ch.quantile([0.25, 0.5], dim="epoch")
  537. #
  538. # Define the LQR
  539. lqr = q50 - q25
  540. #
  541. # define a threshold
  542. k = 3
  543. lower_threshold = q50 - lqr * k
  544. #
  545. outlier_mask = data_r_ch < lower_threshold
  546. #
  547. percent_outliers = outlier_mask.astype(float).mean("ch")
  548. #
  549. p_threshold = 0.2
  550. bad_epochs = percent_outliers[percent_outliers > p_threshold].coords.to_index().values
  551. #
  552. # Add the outlier epochs to our flags
  553. pipeline.flags["epoch"].add_flag_cat(
  554. kind="uncorrelated", bad_epoch_inds=bad_epochs, epochs=epochs
  555. )
  556. pipeline.raw.annotations.description
  557. # %%
  558. # in this case, no epochs were flagged as uncorrelated.
  559. #
  560. # %%
  561. # Flag low correlation Sensors
  562. # -----------------------------
  563. #
  564. # .. figure:: https://raw.githubusercontent.com/scott-huberty/wip_pipeline-figures/main/Flag_uncorrelated_sensors.png
  565. # :align: center
  566. # :alt: Flag Uncorrelated Sensors graphic.
  567. #
  568. # Flag Uncorrelated Sensors. The figure shows the steps for flagging uncorrelated
  569. # sensors. See the text below for descriptions of mathematical notation.
  570. #
  571. # This step is designed to identify sensors that have an unusually low correlation with
  572. # neighboring sensors. The operations involved by this step are similar to those of the
  573. # :ref:`noisy_sensors` step, except we use maximal nearest neighbor correlations instead
  574. # of dispersion and the left instead of the right tail of the distribution to set
  575. # the threshold for outliers.
  576. # %%
  577. # a) Take lower quantile range and defined sensor-specific thresholds
  578. # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  579. #
  580. # We get the indicator matrix as described previously, using
  581. #
  582. # .. math::
  583. # \tau^e_i = \mathrm{P}^{t,{\text{max}}^nQ50^e} - LQR^e\times k
  584. #
  585. # and
  586. #
  587. # .. math::
  588. # c_{ij} =
  589. # \begin{cases}
  590. # 1 & \text{if } \rho^{t,{\text{max}}^n}_{ij} < \tau^e_i \\
  591. # 0 & \text{if } \rho^{t,{\text{max}}^n}_{ij} \geq \tau^e_i
  592. # \end{cases}
  593. # %%
  594. # b) Identify uncorrelated sensors
  595. # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  596. #
  597. # We define a threshold as we did in the previous step and flag uncorrelated epochs
  598. # :math:`j` if :math:`c^{\mu_s}_j > \tau^{p}`.
  599. # %%
  600. q25, q50 = data_r_ch.quantile([0.25, 0.5], dim="ch")
  601. #
  602. # Define LQR
  603. lqr = q50 - q25
  604. #
  605. # define a threshold
  606. k = 3
  607. lower_threshold = q50 - lqr * k
  608. #
  609. # Identify correlations less than the threshold
  610. outlier_mask = data_r_ch < lower_threshold
  611. percent_outliers = outlier_mask.astype(float).mean("epoch")
  612. #
  613. p_threshold = 0.2
  614. bad_chs = percent_outliers[percent_outliers > p_threshold].coords.to_index().values
  615. #
  616. # Add the outlier channels to our flags
  617. pipeline.flags["ch"].add_flag_cat(kind="uncorrelated", bad_ch_names=bad_chs)
  618. pipeline.flags["ch"]
  619. # %%
  620. # In this case, no sensors were flagged as uncorrelated.
  621. # %%
  622. # Run Initial ICA
  623. # ---------------
  624. #
  625. # The pipeline by runs ICA two times. The first ICA is only used to identify
  626. # noisy periods in its IC activation time-series. For this reason, the pipeline
  627. # uses the FastICA algorithm for speed.
  628. # %%
  629. pipeline.run_ica("run1")
  630. # %%
  631. # Flag Noisy IC Activation time-periods
  632. # -------------------------------------
  633. #
  634. # This step follows the same procedure as the :ref:`noisy_sensors` step, except that
  635. # the data is now the IC activation time-series. thus we start with a 3D matrix
  636. # :math:`X_{ica} \in \mathbb{R}^{I_\mathcal{G} \times E_\mathcal{G} \times T}` of
  637. # IC time-courses rather than scalp EEG data and where :math:`I` is the set of
  638. # independent components.
  639. #
  640. # %%
  641. pipeline.flag_noisy_ics()
  642. # %%
  643. pipeline.raw.annotations.description
  644. # %%
  645. # Run Final ICA
  646. # -------------
  647. #
  648. # Now The pipeline runs the final ICA decomposition, this time using the extended
  649. # Infomax algorithm. Note that any sensors or time-periods that have been flagged
  650. # up to this point will not be passed into the ICA decomposition. For the sake of
  651. # time, we will not run the second ICA here, as there are no more pipeline calculations.
  652. # %%
  653. # Run ICLabel Classifier
  654. # ----------------------
  655. #
  656. # The pipeline will run the ICLabel classifier on the final ICA, which will produce a
  657. # label for each IC, one of ``"brain"``, ``"muscle"``, ``"eog"`` (eye), ``"ecg"``
  658. # (heart), ``line_noise``, or ``"channel_noise"``.
  659. #
  660. #
  661. # Conclusion
  662. # ----------
  663. # And that's all! See the other pylossless tutorials for brief examples on running the
  664. # pipeline on your own data, and rejecting the flagged data.
  665. #

plot_0_implementation.py at commit 0f4bccf, under MIT · at the source

Overview

Authors: Scott Huberty1, James Desjardins2, Tyler Collins2, Mayada Elsabbagh1, Christian O’Reilly3,4,5,6
  1. Montreal Neurological Institute-Hospital, McGill University,Montreal, Canada
  2. Compute Ontario,St. Catharines, Canada
  3. Department of Computer Science and Engineering, University of South Carolina,Columbia, SC USA
  4. Artificial Intelligence Institute, University of South Carolina,Columbia, SC USA
  5. Carolina Autism and Neurodevelopment Research Center, Columbia, SC USA
  6. Institute for Mind and Brain, University of South Carolina,Columbia, SC USA
Institutions: McGill University (Canada); Compute Ontario (Canada); University of South Carolina (United States)
Journal: Behavior research methods, volume 58, issue 8, article 220
Dates: received 19 November 2024; accepted 19 February 2026; published online 6 July 2026; in print 2026
Type: Review · Language: English
License: CC BY
Identifiers: DOI 10.3758/s13428-026-02997-z · PMID 42410267 · PMCID PMC13337736 · OpenAlex W4390810969
Open access: hybrid, a free copy (OpenAlex)
Status: code verified
Categories: EEG (modality), human (organism), methods / tools (subfield)
Methods: Spectral & time-frequency, Preprocessing, Smoothing, state filtering, decompositions, Machine learning, Physiology & signal measures
Keywords: Electroencephalography, Preprocessing, Artifact rejection, Non-destructive processing, Reproducible analysis
MeSH: Electroencephalography*, Signal Processing, Computer-Assisted*, Software*, Artifacts, Humans (* major topic)
Journal subjects: Software Review
Topic: EEG and Brain-Computer Interfaces (Cognitive Neuroscience, Neuroscience), according to OpenAlex
Funding: University of South Carolina
Citations: cited by 1 paper (Europe PMC); 24 references in the paper

Abstract

EEG recordings are typically long and contain large amounts of data, making manual cleaning a time-consuming and error-prone task. Automated preprocessing pipelines can facilitate the efficient and objective extraction of artifacts, enabling standardized and reproducible analyses. However, automated preprocessing pipelines typically remove data considered artifacts and return a subset of irreversibly transformed signals. This approach obfuscates preprocessing decisions and often makes it impossible to recover the original data or modify the preprocessing steps. Further, it complicates collaboration among research teams working on a common dataset, as different analyses may require specific preprocessing steps. Given the large amount of resources devoted to collecting EEG, tools that can efficiently and transparently preprocess data are greatly needed. PyLossless addresses this need by creating a non-destructive, automated preprocessing pipeline that maintains the continuous EEG structure. It offers a user-friendly API, is well documented, tested through continuous integration, easily deployable, and integrates with the popular MNE-Python environment. The pipeline also provides a browser-based quality control review (QCR) dashboard that allows researchers to visualize and edit automated artifact flags for sensors, time periods, and independent components. The end product of PyLossless is a lossless annotated data state that can be shared and used with analysis-specific artifact rejection policies, allowing for an optimal balance between flexibility and standardization.

Supplementary Information: The online version contains supplementary material available at 10.3758/s13428-026-02997-z.

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

lina-usc/pylossless

License: MIT
State: the link answers, verified on 27 September 2026
Evidence: files inventoried
Commit: 0f4bccfe2657984a5363be80ef29f00bd3c1a07a, 3 September 2026
Languages: Python (41), Jupyter (2)
Size: 149 files, 43 scripts
Software Heritage: not archived
Found in: “Code Availability”
Holds: README, license file, environment (environment.yaml, pyproject.toml, requirements.txt, requirements_qc.txt, requirements_rtd.txt, requirements_testing.txt, setup.py, docs/requirements_doc.txt), tests, continuous integration, documentation, 2 notebooks
Not found: CITATION.cff
Tools: MNE-Python (17 files), NumPy (17 files), pandas (6 files), MNE-BIDS (5 files), SciPy (4 files), Plotly (3 files), ICLabel (2 files), xarray (2 files)
Availability: 1 check, the latest on 27 September 2026: the link answers
  • 27 September 2026: the link answers
45 files

Code Availability

Materials and analysis code are available at https://github.com/lina-usc/pylossless.

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

Availability of Data and Materials

The EEG data used for this analysis is available at https://github.com/Andesha/Face13

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 2, 28 September 2026

  • Publisher: n/a → Springer Science+Business Media

Version 1, 27 September 2026: the first record

Recorded: type, language, journal, volume, issue, pages, dates, 5 authors, 5 keywords, 5 MeSH terms, 1 funder, 19 references.

Cite

This paper

Huberty, S., Desjardins, J., Collins, T., Elsabbagh, M., & O’Reilly, C. (2026). PyLossless: A non-destructive EEG processing pipeline. Behavior research methods, 58(8), 220. https://doi.org/10.3758/s13428-026-02997-z

BibTeX

@article{huberty2026pylossless,
author = {Huberty, Scott and Desjardins, James and Collins, Tyler and Elsabbagh, Mayada and O’Reilly, Christian},
title = {{PyLossless: A non-destructive EEG processing pipeline}},
journal = {Behavior research methods},
year = {2026},
month = jul,
volume = {58},
number = {8},
pages = {220},
publisher = {Springer Science+Business Media},
issn = {1554-351X},
doi = {10.3758/s13428-026-02997-z},
url = {https://doi.org/10.3758/s13428-026-02997-z},
pmid = {42410267},
pmcid = {PMC13337736}
}

RIS

TY - JOUR
AU - Huberty, Scott
AU - Desjardins, James
AU - Collins, Tyler
AU - Elsabbagh, Mayada
AU - O’Reilly, Christian
TI - PyLossless: A non-destructive EEG processing pipeline
T2 - Behavior research methods
J2 - Behav Res Methods
PY - 2026
DA - 2026/07/06
VL - 58
IS - 8
SP - 220
SN - 1554-351X
PB - Springer Science+Business Media
DO - 10.3758/s13428-026-02997-z
UR - https://doi.org/10.3758/s13428-026-02997-z
LA - en
ER -

CSL-JSON

{
"id": "10.3758/s13428-026-02997-z",
"type": "article-journal",
"title": "PyLossless: A non-destructive EEG processing pipeline",
"container-title": "Behavior research methods",
"author": [
{
"family": "Huberty",
"given": "Scott"
},
{
"family": "Desjardins",
"given": "James"
},
{
"family": "Collins",
"given": "Tyler"
},
{
"family": "Elsabbagh",
"given": "Mayada"
},
{
"family": "O’Reilly",
"given": "Christian"
}
],
"container-title-short": "Behav Res Methods",
"volume": "58",
"issue": "8",
"page": "220",
"DOI": "10.3758/s13428-026-02997-z",
"PMID": "42410267",
"PMCID": "PMC13337736",
"ISSN": "1554-351X",
"publisher": "Springer Science+Business Media",
"URL": "https://doi.org/10.3758/s13428-026-02997-z",
"language": "en",
"issued": {
"date-parts": [
[
2026,
7,
6
]
]
}
}

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/s41597-025-05174-7 [code]
A large-scale MEG and EEG dataset for object recognition in naturalistic scenes
Journal: n/a
In common: MNE-BIDS, ICLabel, MNE-Python, 4 other tools, methods / tools, EEG, 4 references
[2] doi:10.1097/j.pain.0000000000004044 [code]
No effect of rhythmic visual stimulation on experimental pain perception.
Journal: Pain
In common: MNE-BIDS, ICLabel, MNE-Python, 3 other tools, EEG, 5 references
[3] 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: xarray, ICLabel, MNE-Python, 4 other tools, EEG, 3 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: MNE-BIDS, xarray, MNE-Python, 3 other tools, EEG, 2 references
[5] doi:10.1093/cercor/bhag113 [code]
Long-term reliability and stability of parameterized resting state EEG: evidence from a five-year follow-up.
Journal: Cerebral cortex (New York, N.Y. : 1991)
In common: MNE-BIDS, ICLabel, MNE-Python, 3 other tools, methods / tools, EEG, 2 references
[6] doi:10.3390/s26134019 [code]
NeuroStat: An Open-Source EEG Connectivity Platform for Randomised Controlled Trials.
Journal: Sensors (Basel, Switzerland)
In common: ICLabel, MNE-Python, pandas, 2 other tools, EEG, 4 references
[7] 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: MNE-BIDS, xarray, MNE-Python, 3 other tools, 2 references
[8] doi:10.1038/s41597-026-07146-x [code]
Intention-Action Conflict EEG-Hand Kinematics Dataset for Unimanual Control under Congruent and Incongruent Conditions.
Journal: Scientific data
In common: ICLabel, MNE-Python, pandas, 2 other tools, methods / tools, EEG, 2 references
[9] doi:10.1016/j.dib.2026.113064 [code]
A reproducible EEG hyperscanning dataset for triadic social decision-making during an iterated 3-player Prisoner's Dilemma.
Journal: Data in brief
In common: ICLabel, MNE-Python, pandas, 2 other tools, methods / tools, EEG, 2 references
[10] doi:10.1371/journal.pcbi.1014043 [code]
EEG-Pype: An accessible MNE-Python pipeline with graphical user interface for preprocessing and analysis of resting-state electroencephalography data.
Journal: PLoS computational biology
In common: ICLabel, MNE-Python, pandas, 2 other tools, methods / tools, EEG, 2 references

Contribute

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

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

Request its removal

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

Discussion, reproductions, activity

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

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

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