OSCR

Preprocessing on the Go: Practices in Gait-Related Mobile EEG.

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
  1. [1] § Methods › Literature Search ↔ utils/article_fetcher.py, lines 6–39 · score 0.72 · Medical Subject Headings, MeSH, retrieved, query, articles, keywords
  2. [2] § Results › Overview of Preprocessing Steps ↔ scripts/fig3_stepsnetwork.py, lines 34–43 · score 0.71 · IC decomposition, IC rejection, post ICA, pass filtering, artifact rejection, ERS
  3. [3] § Results › Overview of Preprocessing Steps ↔ scripts/figB_outcome_stepsnetwork.py, lines 53–68 · score 0.71 · IC decomposition, IC rejection, post ICA, pass filtering, artifact rejection, ERS
  4. [4] § Methods › Defining Preprocessing Steps ↔ dataframe_plots.ipynb, lines 860–895 · score 0.67 · highpass_filter, Post ICA, Pre ICA Signal, Raw, keywords, Preprocessing
  5. [5] § Results › Overview of Preprocessing Steps ↔ scripts/fig3_stepsnetwork.py, lines 34–43 · score 0.63 · notch filter, IC rejection, low pass filtering, Artifact rejection, ICA, preprocessing
  6. [6] § Results › Overview of Preprocessing Steps ↔ dataframe_plots.ipynb, lines 469–486 · score 0.63 · notch filter, IC rejection, low pass filtering, Artifact rejection, ICA, preprocessing
  7. [7] § Results › Study Cohorts and Gait Tasks ↔ dataframe_plots.ipynb, lines 324–382 · score 0.63 · overground walking, Treadmill walking, healthy adults, patients, paradigm, cohorts
  8. [8] § Results › Study Cohorts and Gait Tasks ↔ scripts/fig1_cohort_task.py, lines 13–20 · score 0.57 · PD clinical cohorts, PwPD
  9. [9] § Results › Study Cohorts and Gait Tasks ↔ scripts/fig1_cohort_task.py, lines 13–20 · score 0.53 · PD clinical cohorts, PwPD, HA

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

Jupyter notebook · 974 lines · 34 KB · no license · 3 matches

  1. # %% [markdown]
  2. # # Review of gait-related mobile EEG preprocessing steps — Data Preparation & Visualization
  3. #
  4. # This notebook organizes and visualizes data extracted from the literature via **Elicit**, focusing on experimental and preprocessing parameters relevant to mobile EEG during gait.
  5. #
  6. # Our goals here are to:
  7. # - Structure all study-level data into a clean DataFrame
  8. # - Explore distributions of cohorts, gait measurement systems, and artifact rejection methods
  9. # - Generate simple, clear, and reproducible plots for the manuscript and supplementary materials
  10. # %% [markdown]
  11. # ## Setup
  12. #
  13. # We'll start by importing necessary packages and defining paths.
  14. # The notebook assumes the CSV file exported from Elicit is available in the `../data/` directory.
  15. # %%
  16. import pandas as pd
  17. import numpy as np
  18. import ast
  19. import os
  20. import itertools
  21. from pathlib import Path
  22. from collections import Counter, defaultdict
  23. import matplotlib.pyplot as plt
  24. import seaborn as sns
  25. import networkx as nx
  26. from matplotlib.patches import Patch, FancyArrowPatch
  27. from matplotlib.lines import Line2D
  28. from math import sqrt
  29. from upsetplot import UpSet, from_indicators
  30. from utils.config import define_dir, dir_processed
  31. # %% [markdown]
  32. # ## Configure the directories
  33. # %%
  34. # Get the current directory where the script is executed
  35. dir_proj = Path.cwd()
  36. # Define the paths for 'logs' and 'results' directories
  37. dir_log_results = define_dir(dir_proj, "logs") # Logs directory path
  38. dir_results = define_dir(dir_proj, "results") # Results directory path
  39. dir_fulltexts = define_dir(dir_results, "fulltexts") # Full-text articles directory path
  40. dir_researcharticles = define_dir(dir_results, "researcharticles") # Research articles directory path
  41. dir_methods = define_dir(dir_results, "methods") # Methods sections directory path
  42. dir_data = define_dir(dir_proj, "data") # Data directory path
  43. dir_processed = define_dir(dir_results, "cleanresults") # Processed data directory
  44. # %% [markdown]
  45. # ## 1. Load the dataset
  46. #
  47. # We’ll start by loading the extracted dataset (`Elicitrevised.csv`), which contains:
  48. # - Metadata (Title, Citation, Filename)
  49. # - Experimental details (Cohort, Gait Task, EEG Type)
  50. # - Preprocessing details and artifact rejection methods
  51. #
  52. # Since some fields include multiple values (e.g., multiple gait systems or methods),
  53. # we’ll clean those for easy downstream use.
  54. # %%
  55. data_path = Path("data/20251003_Elicitrevised.csv")
  56. df = pd.read_csv(data_path, sep=";")
  57. print(f"Loaded {len(df)} studies")
  58. df.head(3)
  59. # %% [markdown]
  60. # ## Step 2: Organize Data into Structured DataFrames
  61. #
  62. # In this section, we will:
  63. # - Clean and standardize text fields.
  64. # - Separate parameters with multiple values (e.g., gait systems, artifact rejection methods).
  65. # - Create easy-to-analyze DataFrames for:
  66. # - Cohort
  67. # - Gait Task
  68. # - EEG Electrode Type
  69. # - Gait Measurement System
  70. # - Artifact Rejection Methods
  71. # - Preprocessing Steps
  72. # - Outcomes
  73. #
  74. # Each DataFrame will have one row per study per method/system, in order to plot them.
  75. # Some columns (e.g., `Gait_measurement_system`) contain multiple entries separated by `;` or `,`.
  76. # We will:
  77. # - Split them into lists.
  78. # - Explode these lists into separate rows.
  79. # This will make it possible to count and visualize occurrences of each system or method across studies.
  80. # %%
  81. # Standardize column names
  82. df.columns = (
  83. df.columns.str.strip()
  84. .str.replace(" ", "_")
  85. .str.replace("-", "_")
  86. )
  87. # Clean text fields
  88. text_cols = [
  89. "Cohort", "Gait_Task", "Dual_layer_cap",
  90. "Type_of_EEG_electrodes", "Gait_measurement_system",
  91. "Artifactrej_methods", "step_keywords", "outcome_keywords_script"
  92. ]
  93. for col in text_cols:
  94. if col in df.columns:
  95. df[col] = (
  96. df[col]
  97. .astype(str)
  98. .str.replace(r"[\r\n]+", " ", regex=True)
  99. .str.strip()
  100. )
  101. # --- Convert invalid values to NaN ---
  102. df.replace(["", "nan", "None", "NaN"], np.nan, inplace=True)
  103. # --- Drop duplicates and empty rows ---
  104. df = df.drop_duplicates().dropna(how="all")
  105. # --- Utility to clean and explode multi-value columns ---
  106. def split_and_clean(df, col):
  107. """Split semicolon/list-like column into multiple rows."""
  108. if col not in df.columns:
  109. print(f"Column '{col}' not found.")
  110. return pd.DataFrame(columns=["Title", "Citation", col])
  111. temp = df[["Title", "Citation", col]].copy()
  112. temp[col] = temp[col].astype(str).str.replace(r"[\[\]']", "", regex=True)
  113. temp[col] = temp[col].str.split(";|,")
  114. temp = temp.explode(col)
  115. temp[col] = temp[col].str.strip()
  116. temp = temp[temp[col].notna() & (temp[col] != "")]
  117. return temp
  118. print("Data cleaned and normalized.")
  119. df.head(3)
  120. # %%
  121. # --- Split all relevant columns ---
  122. df_cohort = split_and_clean(df, "Cohort")
  123. df_gait_task = split_and_clean(df, "Gait_Task")
  124. df_gait_system = split_and_clean(df, "Gait_measurement_system")
  125. df_artifact = split_and_clean(df, "Artifactrej_methods")
  126. df_eeg_electrodes = split_and_clean(df, "Type_of_EEG_electrodes")
  127. df_step_keywords = split_and_clean(df, "step_keywords")
  128. df_outcomes = split_and_clean(df, "outcome_keywords_script")
  129. # --- Summary ---
  130. print("Summary of entries:")
  131. print(f"Cohort entries: {len(df_cohort)}")
  132. print(f"Gait task entries: {len(df_gait_task)}")
  133. print(f"Gait systems entries: {len(df_gait_system)}")
  134. print(f"Artifact rejection entries: {len(df_artifact)}")
  135. print(f"EEG electrode entries: {len(df_eeg_electrodes)}")
  136. print(f"Step keyword entries: {len(df_step_keywords)}")
  137. print(f"Outcome keyword entries: {len(df_outcomes)}")
  138. # %%
  139. export_tables = {
  140. "Cohort": df_cohort,
  141. "Gait_Task": df_gait_task,
  142. "Gait_System": df_gait_system,
  143. "Artifact_Methods": df_artifact,
  144. "EEG_Electrodes": df_eeg_electrodes,
  145. "Step_Keywords": df_step_keywords,
  146. "Outcome_Keywords": df_outcomes
  147. }
  148. for name, table in export_tables.items():
  149. out_path = os.path.join(dir_processed, f"{name}_cleaned.csv")
  150. table.to_csv(out_path, index=False)
  151. print(f"Saved: {out_path}")
  152. # %%
  153. files = {
  154. "Cohort": "results/cleanresults/Cohort_cleaned.csv",
  155. "Gait_Task": "results/cleanresults/Gait_Task_cleaned.csv",
  156. "Gait_System": "results/cleanresults/Gait_System_cleaned.csv",
  157. "EEG_Electrodes": "results/cleanresults/EEG_Electrodes_cleaned.csv",
  158. "Step_Keywords": "results/cleanresults/Step_Keywords_cleaned.csv",
  159. "Artifact_Methods": "results/cleanresults/Artifact_Methods_cleaned.csv",
  160. "Outcome_Keywords": "results/cleanresults/Outcome_Keywords_cleaned.csv"
  161. }
  162. dfs = {name: pd.read_csv(path, dtype=str).fillna("") for name, path in files.items()}
  163. # Check sizes
  164. for name, df in dfs.items():
  165. print(f"{name}: {df.shape[0]} rows × {df.shape[1]} cols")
  166. # %%
  167. display(df_cohort.head(3))
  168. display(df_artifact.head(3))
  169. display(df_step_keywords.head(3))
  170. # %%
  171. # Check for missing values
  172. master_titles = set(dfs["Cohort"]["Title"])
  173. for name, df in dfs.items():
  174. missing = master_titles - set(df["Title"])
  175. extra = set(df["Title"]) - master_titles
  176. print(f"{name}: missing {len(missing)}, extra {len(extra)}")
  177. # %%
  178. # Aggregate steps
  179. steps_grouped = (
  180. dfs["Step_Keywords"]
  181. .groupby(["Title", "Citation"])["step_keywords"]
  182. .apply(lambda x: list(set(x.dropna())))
  183. .reset_index()
  184. )
  185. # Aggregate outcomes
  186. outcomes_grouped = (
  187. dfs["Outcome_Keywords"]
  188. .groupby(["Title", "Citation"])["outcome_keywords_script"]
  189. .apply(lambda x: list(set(x.dropna())))
  190. .reset_index()
  191. )
  192. # Merge core datasets
  193. meta = dfs["Cohort"][["Title", "Citation", "Cohort"]].merge(
  194. dfs["Gait_Task"][["Title", "Gait_Task"]], on="Title", how="left"
  195. )
  196. meta = meta.merge(dfs["Gait_System"][["Title", "Gait_measurement_system"]], on="Title", how="left")
  197. meta = meta.merge(dfs["EEG_Electrodes"][["Title", "Type_of_EEG_electrodes"]], on="Title", how="left")
  198. meta = meta.merge(steps_grouped, on="Title", how="left")
  199. meta = meta.merge(outcomes_grouped, on="Title", how="left")
  200. print(f"✅ Combined dataset: {meta.shape[0]} studies")
  201. # %% [markdown]
  202. # ### Creating Clean DataFrames for Each Parameter
  203. #
  204. # Now we’ll create structured DataFrames for:
  205. # - `Cohort`
  206. # - `Gait_Task`
  207. # - `Gait_measurement_system` (exploded)
  208. # - `Type_of_EEG_electrodes` (exploded)
  209. # - `Artifactrej_methods` (exploded)
  210. #
  211. # These will be stored in a dictionary for easy iteration during plotting or export.
  212. # %%
  213. available_files = list(dir_processed.glob("*.csv"))
  214. print("Available processed CSV files:")
  215. for f in available_files:
  216. print("-", f.name)
  217. processed_data = {f.stem: pd.read_csv(f) for f in available_files}
  218. for name, df in processed_data.items():
  219. print(f"\n{name.upper()}: {df.shape[0]} rows × {df.shape[1]} cols")
  220. print(df.head(3))
  221. # %%
  222. # Rebuild pipelines per study
  223. grouped_steps = df_step_keywords.groupby("Title")["step_keywords"].apply(list).reset_index()
  224. grouped_outcomes = df_outcomes.groupby("Title")["outcome_keywords_script"].apply(list).reset_index()
  225. merged = pd.merge(grouped_steps, grouped_outcomes, on="Title", how="outer", suffixes=("_steps", "_outcomes"))
  226. merged["step_keywords"] = merged["step_keywords"].apply(lambda x: x if isinstance(x, list) else [])
  227. merged["outcome_keywords_script"] = merged["outcome_keywords_script"].apply(lambda x: x if isinstance(x, list) else [])
  228. print(f"Reconstructed {len(merged)} study pipelines.")
  229. display(merged.head(3))
  230. # %% [markdown]
  231. # ### Exporting Structured Data
  232. #
  233. # We’ll now save each structured DataFrame into the project’s `data/processed_data` directory.
  234. #
  235. # The base data directory (`dir_data`) is already defined in `utils.config`.
  236. # This ensures that file paths remain consistent across scripts, notebooks, and collaborators.
  237. #
  238. # Each structured dataset will be exported as a CSV file and versioned for reproducibility.
  239. #
  240. # %% [markdown]
  241. # ## Load Processed Data for Visualization
  242. #
  243. # Now that the preprocessing and organization are done,
  244. # we’ll load the processed CSV files (e.g., cohort, gait task, electrode type, etc.)
  245. # from the `results/cleanresults` folder for visualization.
  246. # %%
  247. # --- Data verification cell ---
  248. import os
  249. import pandas as pd
  250. processed_files = [
  251. "Cohort_cleaned.csv",
  252. "Gait_Task_cleaned.csv",
  253. "Gait_System_cleaned.csv",
  254. "EEG_Electrodes_cleaned.csv",
  255. "Step_Keywords_cleaned.csv",
  256. "Artifact_Methods_cleaned.csv",
  257. "Outcome_Keywords_cleaned.csv"
  258. ]
  259. print("✅ Checking processed data structure...\n")
  260. for f in processed_files:
  261. path = os.path.join(dir_processed, f)
  262. if os.path.exists(path):
  263. df_temp = pd.read_csv(path)
  264. print(f"{f}: {df_temp.shape[0]} rows × {df_temp.shape[1]} cols")
  265. print(" Columns:", list(df_temp.columns))
  266. print(" Sample:\n", df_temp.head(2), "\n")
  267. else:
  268. print(f"⚠️ Missing file: {f}")
  269. # %% [markdown]
  270. # ## Prepare Aggregated Data for Visualization
  271. #
  272. # Next, let’s prepare frequency tables and summary data.
  273. # For example, how many studies used each gait measurement system,
  274. # EEG electrode type, and artifact rejection method.
  275. # %% [markdown]
  276. # ## Visualize Key Parameters
  277. #
  278. # We’ll now create simple, publication-ready bar plots
  279. # to visualize how frequently each parameter occurs across studies.
  280. #
  281. # Libraries: `matplotlib` and `seaborn`.
  282. # %% [markdown]
  283. # ## Distribution of Studies by Cohort and Gait Task
  284. #
  285. # To understand the experimental landscape,
  286. # we’ll visualize how different **cohorts** (e.g., healthy adults, patients)
  287. # are distributed across various **gait tasks** (e.g., treadmill, overground, obstacle walking).
  288. #
  289. # This gives a quick view of which populations are most commonly studied
  290. # under which walking paradigms.
  291. # %%
  292. # Load cleaned data
  293. df_cohort = pd.read_csv(os.path.join(dir_processed, "Cohort_cleaned.csv"))
  294. df_gait_task = pd.read_csv(os.path.join(dir_processed, "Gait_Task_cleaned.csv"))
  295. # Merge on Citation to align cohorts and gait tasks per study
  296. df_cohort_task = pd.merge(df_cohort, df_gait_task, on="Citation", suffixes=("_Cohort", "_Task"))
  297. # Count occurrences
  298. pivot = df_cohort_task.pivot_table(index="Cohort", columns="Gait_Task", values="Citation", aggfunc="count", fill_value=0)
  299. # Plot
  300. plt.figure(figsize=(10, 6))
  301. ax = pivot.plot(kind="barh", stacked=True, colormap="tab20", edgecolor="none")
  302. plt.title("Cohort vs Gait Task Distribution")
  303. plt.xlabel("Cohort")
  304. plt.ylabel("Number of Studies")
  305. # --- Modify legend labels ---
  306. handles, labels = ax.get_legend_handles_labels()
  307. label_map = {
  308. "Overground walking": "Only Overground walking",
  309. "Treadmill walking": "Only Treadmill walking"
  310. }
  311. # Replace only matching labels
  312. new_labels = [label_map.get(lbl, lbl) for lbl in labels]
  313. plt.legend(title="Gait Task", bbox_to_anchor=(0.95, 0.95), loc='upper left',
  314. facecolor="white",
  315. framealpha=0.9,
  316. fontsize=9,
  317. title_fontsize=10)
  318. plt.tight_layout()
  319. plt.show()
  320. # %%
  321. plt.figure(figsize=(10, 6))
  322. ax = pivot.plot(kind="barh", stacked=True, colormap="tab20", edgecolor="none")
  323. plt.title("Cohort vs Gait Task Distribution")
  324. plt.xlabel("Number of Studies")
  325. plt.ylabel("Cohort")
  326. # --- Modify legend labels ---
  327. handles, labels = ax.get_legend_handles_labels()
  328. label_map = {
  329. "Overground walking": "Only Overground walking",
  330. "Treadmill walking": "Only Treadmill walking"
  331. }
  332. # Replace only matching labels
  333. new_labels = [label_map.get(lbl, lbl) for lbl in labels]
  334. # --- Place legend inside plot ---
  335. plt.legend(
  336. handles,
  337. new_labels,
  338. title="Gait Task",
  339. loc="upper left", # you can try 'upper right' or 'center left' depending on aesthetics
  340. bbox_to_anchor=(0.95, 0.95),
  341. frameon=True,
  342. facecolor="white",
  343. framealpha=0.9,
  344. fontsize=10,
  345. title_fontsize=11
  346. )
  347. plt.tight_layout()
  348. plt.show()
  349. # %% [markdown]
  350. # ## Heatmap: EEG Electrode Type vs Gait Measurement System
  351. #
  352. # We want to visualize how many studies used each combination of EEG electrode type and gait measurement system.
  353. # The heatmap shows counts for each combination.
  354. # %%
  355. plt.figure(figsize=(10, 6))
  356. heat_data = meta.groupby(["Type_of_EEG_electrodes", "Gait_measurement_system"]).size().unstack(fill_value=0)
  357. sns.heatmap(heat_data, annot=True, cmap="YlGnBu", fmt="d")
  358. plt.title("EEG Electrode Types vs Gait Measurement Systems")
  359. plt.ylabel("Type of EEG Electrodes")
  360. plt.xlabel("Gait Measurement System")
  361. plt.tight_layout()
  362. plt.show()
  363. # %% [markdown]
  364. # # EEG Preprocessing Network Plot
  365. # We will visualize the flow of preprocessing steps as a layered network.
  366. # Each node represents a step, colored by stage, and arrows show transitions between steps across studies.
  367. #
  368. # Lets start with importing all required libraries
  369. # %%
  370. # Load the two separate datasets
  371. steps_df = pd.read_csv(os.path.join(dir_processed, "Step_Keywords_cleaned.csv"))
  372. outcomes_df = pd.read_csv(os.path.join(dir_processed, "Outcome_Keywords_cleaned.csv"))
  373. # Group keywords by 'Citation' into a semicolon-separated string for each file
  374. steps_grouped = steps_df.groupby('Citation')['step_keywords'].apply(lambda x: ';'.join(x.dropna())).reset_index()
  375. outcomes_grouped = outcomes_df.groupby('Citation')['outcome_keywords_script'].apply(lambda x: ';'.join(x.dropna())).reset_index()
  376. # Rename the outcome column to match the original script's expectation
  377. outcomes_grouped.rename(columns={'outcome_keywords_script': 'outcome_keywords'}, inplace=True)
  378. # Merge the two dataframes on 'Citation'
  379. # An outer merge ensures that articles with only steps or only outcomes are included
  380. df = pd.merge(steps_grouped, outcomes_grouped, on='Citation', how='outer')
  381. # Fill any missing values (NaN) with empty strings, as in the original code
  382. df.fillna("", inplace=True)
  383. # --- End of Data Loading and Merging Block ---
  384. # Stage map
  385. stage_map = {
  386. "Raw data": ["Raw data"],
  387. "Pre ICA - Signal Cleaning": [
  388. "Channel removal",
  389. "High-pass filter", "Low-pass filter", "Bandpass filter", "Notch filter",
  390. "Downsample"
  391. ],
  392. "Pre ICA - Data Preprocessing": [
  393. "Artifact rejection", "Bad channel detection","Re-reference", "Epoching"
  394. ],
  395. "ICA": ["IC decomposition", "IC rejection"],
  396. "Post ICA": [
  397. "Clustering", "Baseline correction",
  398. "Dipole fitting", "Normalization" ,"Despiking"
  399. ],
  400. "Outcome": ["PSD", "ERD/ERS", "ERSP", "CMC"]
  401. }
  402. # Build article-wise step tracking (unchanged)
  403. article_steps = {}
  404. for idx, row in df.iterrows():
  405. paper_id = row["Citation"] if "Citation" in df.columns else idx
  406. steps = row["step_keywords"].split(";") if row["step_keywords"] else []
  407. outcomes = row["outcome_keywords"].split(";") if row["outcome_keywords"] else []
  408. # Filter out empty strings that might result from splitting
  409. steps = [s for s in steps if s]
  410. outcomes = [o for o in outcomes if o]
  411. full_sequence = steps + outcomes
  412. article_steps[paper_id] = full_sequence
  413. # Calculate transition counts (unchanged, but now works with the merged data)
  414. transition_counts = Counter()
  415. for _, row in df.iterrows():
  416. steps = row["step_keywords"].split(";") if row["step_keywords"] else []
  417. outcomes = row["outcome_keywords"].split(";") if row["outcome_keywords"] else []
  418. # Filter out empty strings
  419. steps = [s for s in steps if s]
  420. outcomes = [o for o in outcomes if o]
  421. for i in range(len(steps) - 1):
  422. transition_counts[(steps[i], steps[i + 1])] += 1
  423. if steps and outcomes:
  424. last_step = steps[-1]
  425. for outcome in outcomes:
  426. transition_counts[(last_step, outcome)] += 1
  427. # Node stage mapping (unchanged)
  428. node_stage = {"raw_data": "Raw data"}
  429. for stage, keys in stage_map.items():
  430. for key in keys:
  431. node_stage[key] = stage
  432. # Setting layout (unchanged)
  433. layer_order = [
  434. "Raw data",
  435. "Pre ICA - Signal Cleaning",
  436. "Pre ICA - Data Preprocessing",
  437. "ICA",
  438. "Post ICA",
  439. "Outcome"
  440. ]
  441. stage_y = {stage: -i for i, stage in enumerate(layer_order)}
  442. # Function to get node positions (unchanged)
  443. def get_node_positions(G, node_stage, stage_map):
  444. positions = {}
  445. x_coords = defaultdict(int)
  446. for node in G.nodes():
  447. stage = node_stage.get(node, "Raw data")
  448. y = stage_y.get(stage, -10)
  449. x = x_coords[y]
  450. positions[node] = (x, y)
  451. x_coords[y] += 1
  452. # Center the nodes in each layer
  453. for y_val in x_coords:
  454. num_nodes = x_coords[y_val]
  455. nodes_at_y = [node for node, pos in positions.items() if pos[1] == y_val]
  456. for i, node in enumerate(nodes_at_y):
  457. positions[node] = (i - (num_nodes - 1) / 2.0, y_val)
  458. return positions
  459. # Plotting function (unchanged)
  460. def plot_layered_flowchart(transition_counts, node_stage_map, stage_map, title="Flow of EEG Preprocessing Steps across multiple studies"):
  461. G = nx.DiGraph()
  462. for (src, dst), weight in transition_counts.items():
  463. G.add_edge(src, dst, weight=weight)
  464. # Add 'raw_data' if it's a source node but not in the graph yet
  465. if 'raw_data' not in G.nodes() and any(u == 'raw_data' for u, v in transition_counts.keys()):
  466. G.add_node('raw_data')
  467. # Color map by node stage
  468. color_map = {
  469. "Raw data": "#A9A9A9", # dark gray
  470. "Pre ICA - Signal Cleaning": "#FF8C42", # deep orange
  471. "Pre ICA - Data Preprocessing": "#20B2AA", # teal
  472. "ICA": "#9370DB", # medium purple
  473. "Post ICA": "#D9534F", # red / crimson
  474. "Outcome": "#3CB371", # medium sea green
  475. }
  476. node_colors = [color_map.get(node_stage_map.get(node, "Raw data"), "gray") for node in G.nodes()]
  477. node_sizes = [300 + 200 * G.degree(n) for n in G.nodes()]
  478. pos = get_node_positions(G, node_stage_map, stage_map)
  479. fig, ax = plt.subplots(figsize=(18, 12))
  480. nx.draw_networkx_nodes(G, pos, node_size=node_sizes, node_color=node_colors, ax=ax)
  481. nx.draw_networkx_labels(G, pos, font_size=9, ax=ax)
  482. # Edge color and width rules
  483. def get_edge_style(weight):
  484. if weight >= 46:
  485. return "black", 4 + weight * 0.15
  486. elif weight >= 30:
  487. return "#023e8a", 3.8 + weight * 0.13
  488. elif weight >= 15:
  489. return "brown", 3 + weight * 0.12
  490. elif weight >= 5:
  491. return "gray", 2 + weight * 0.1
  492. else:
  493. return "#cccccc", 1.0
  494. # Draw arrows with arrowheads using FancyArrowPatch
  495. for u, v in G.edges():
  496. weight = G[u][v]['weight']
  497. color, width = get_edge_style(weight)
  498. start = pos[u]
  499. end = pos[v]
  500. node_radius = 0.3 # Adjust this based on node sizes
  501. # Offset calculation
  502. dx, dy = end[0] - start[0], end[1] - start[1]
  503. dist = sqrt(dx**2 + dy**2)
  504. if dist == 0: continue
  505. arrow_start = (start[0] + dx * node_radius / dist, start[1] + dy * node_radius / dist)
  506. arrow_end = (end[0] - dx * node_radius / dist, end[1] - dy * node_radius / dist)
  507. arrow = FancyArrowPatch(
  508. posA=arrow_start,
  509. posB=arrow_end,
  510. connectionstyle="arc3,rad=0.2",
  511. arrowstyle="->",
  512. mutation_scale=20,
  513. color=color,
  514. linewidth=width,
  515. alpha=0.8
  516. )
  517. ax.add_patch(arrow)
  518. # Legend 1: Node colors (stage types)
  519. node_legend_elements = [
  520. Patch(facecolor=color, edgecolor="black", label=stage)
  521. for stage, color in color_map.items()
  522. ]
  523. # Legend 2: Arrow colors (article frequency)
  524. arrow_legend_elements = [
  525. Line2D([0], [0], color="#cccccc", lw=2, label="1-4 articles"),
  526. Line2D([0], [0], color="gray", lw=2, label="5–14 articles"),
  527. Line2D([0], [0], color="brown", lw=2, label="15–29 articles"),
  528. Line2D([0], [0], color="#023e8a", lw=2, label="30+ articles")
  529. ]
  530. # Place the legends
  531. first_legend = ax.legend(handles=node_legend_elements, title="Preprocessing Stages", loc="upper left", bbox_to_anchor=(1.02, 1), borderaxespad=0.)
  532. ax.add_artist(first_legend)
  533. ax.legend(handles=arrow_legend_elements, title="Transition Frequency", loc="upper left", bbox_to_anchor=(1.10, 0.7), borderaxespad=0.)
  534. plt.title(title, fontsize=16)
  535. plt.axis("off")
  536. plt.tight_layout(rect=[0.1, 0.25, 0.75, 1])
  537. plt.show()
  538. # Run plot
  539. plot_layered_flowchart(transition_counts, node_stage, stage_map)
  540. # %%
  541. import pandas as pd
  542. import networkx as nx
  543. import matplotlib.pyplot as plt
  544. from matplotlib.patches import FancyArrowPatch, Patch
  545. from matplotlib.lines import Line2D
  546. from collections import Counter, defaultdict
  547. from math import sqrt
  548. import os
  549. # --- Data Loading and Merging Block ---
  550. # Assumes the CSV files are in the same directory as the script.
  551. # If not, provide the full path to the files.
  552. try:
  553. steps_df = pd.read_csv(os.path.join(dir_processed, "Step_Keywords_cleaned.csv"))
  554. outcomes_df = pd.read_csv(os.path.join(dir_processed, "Outcome_Keywords_cleaned.csv"))
  555. except FileNotFoundError:
  556. print("Error: Make sure 'Step_Keywords_cleaned.csv' and 'Outcome_Keywords_cleaned.csv' are in the correct directory.")
  557. exit()
  558. # Group keywords by 'Citation' into a semicolon-separated string for each file
  559. steps_grouped = steps_df.groupby('Citation')['step_keywords'].apply(lambda x: ';'.join(x.dropna())).reset_index()
  560. outcomes_grouped = outcomes_df.groupby('Citation')['outcome_keywords_script'].apply(lambda x: ';'.join(x.dropna())).reset_index()
  561. # Rename the outcome column to match the original script's expectation
  562. outcomes_grouped.rename(columns={'outcome_keywords_script': 'outcome_keywords'}, inplace=True)
  563. # Merge the two dataframes on 'Citation'
  564. df = pd.merge(steps_grouped, outcomes_grouped, on='Citation', how='outer')
  565. df.fillna("", inplace=True)
  566. # --- End of Data Loading and Merging Block ---
  567. # Stage map
  568. stage_map = {
  569. "Raw data": ["raw_data"],
  570. "Pre ICA - Signal Cleaning": [
  571. "channel_removal", "highpass_filter", "lowpass_filter", "bandpass_filter",
  572. "notch_filter", "downsample"
  573. ],
  574. "Pre ICA - Data Preprocessing": [
  575. "artifact_rejection", "bad_channel_detection", "re_reference", "epoching"
  576. ],
  577. "ICA": ["IC_decomposition", "IC_rejection"],
  578. "Post ICA": [
  579. "clustering", "baseline_correction", "dipole_fitting", "normalization",
  580. "despiking", "CA_rejection"
  581. ],
  582. "Outcome": ["PSD", "ERD/ERS", "ERSP", "CMC"]
  583. }
  584. # --- Descriptive Statistics Calculation ---
  585. total_papers = len(df['Citation'].unique())
  586. step_counts = Counter()
  587. # Get all unique steps and outcomes from the stage map to ensure all are counted
  588. all_possible_steps = [step for sublist in stage_map.values() for step in sublist]
  589. # Calculate how many papers mention each step
  590. for step in all_possible_steps:
  591. for _, row in df.iterrows():
  592. full_keyword_list = (row['step_keywords'] + ';' + row['outcome_keywords']).split(';')
  593. if step in full_keyword_list:
  594. step_counts[step] += 1
  595. print("--- Descriptive Statistics of Preprocessing Steps ---")
  596. print(f"Total number of unique papers analyzed: {total_papers}\n")
  597. for step, count in sorted(step_counts.items(), key=lambda item: item[1], reverse=True):
  598. percentage = (count / total_papers) * 100
  599. print(f"{step}: {count} out of {total_papers} papers ({percentage:.2f}%)")
  600. print("-" * 50)
  601. # Calculate transition counts
  602. transition_counts = Counter()
  603. for _, row in df.iterrows():
  604. steps = [s for s in row["step_keywords"].split(";") if s]
  605. outcomes = [o for o in row["outcome_keywords"].split(";") if o]
  606. full_sequence = steps + outcomes
  607. for i in range(len(full_sequence) - 1):
  608. transition_counts[(full_sequence[i], full_sequence[i+1])] += 1
  609. print("\n--- Statistics of Step Transitions ---")
  610. for (src, dst), count in sorted(transition_counts.items(), key=lambda item: item[1], reverse=True):
  611. print(f"Transition from '{src}' to '{dst}': {count} times")
  612. print("-" * 50)
  613. # --- End of Statistics Calculation ---
  614. # Node stage mapping
  615. node_stage = {"raw_data": "Raw data"}
  616. for stage, keys in stage_map.items():
  617. for key in keys:
  618. node_stage[key] = stage
  619. # Setting layout
  620. layer_order = [
  621. "Raw data", "Pre ICA - Signal Cleaning", "Pre ICA - Data Preprocessing",
  622. "ICA", "Post ICA", "Outcome"
  623. ]
  624. stage_y = {stage: -i for i, stage in enumerate(layer_order)}
  625. def get_node_positions(G, node_stage_map):
  626. positions = {}
  627. nodes_by_stage = defaultdict(list)
  628. for node in G.nodes():
  629. stage = node_stage_map.get(node, "Raw data")
  630. nodes_by_stage[stage].append(node)
  631. for stage in layer_order:
  632. # Sort nodes alphabetically for consistent layout
  633. nodes = sorted(nodes_by_stage[stage])
  634. y = stage_y.get(stage)
  635. num_nodes = len(nodes)
  636. for i, node in enumerate(nodes):
  637. # Center the nodes horizontally
  638. x = i - (num_nodes - 1) / 2.0
  639. positions[node] = (x, y)
  640. return positions
  641. def plot_layered_flowchart(transition_counts, node_stage_map, title="Flow of EEG Preprocessing Steps across multiple studies"):
  642. G = nx.DiGraph()
  643. for (src, dst), weight in transition_counts.items():
  644. G.add_edge(src, dst, weight=weight)
  645. if 'raw_data' not in G.nodes() and any(u == 'raw_data' for u, v in transition_counts.keys()):
  646. G.add_node('raw_data')
  647. color_map = {
  648. "Raw data": "#A9A9A9",
  649. "Pre ICA - Signal Cleaning": "#FF8C42",
  650. "Pre ICA - Data Preprocessing": "#20B2AA",
  651. "ICA": "#9370DB",
  652. "Post ICA": "#D9534F",
  653. "Outcome": "#3CB371",
  654. }
  655. node_colors = [color_map.get(node_stage_map.get(node, "Raw data"), "gray") for node in G.nodes()]
  656. node_sizes = [400 + 250 * G.degree(n) for n in G.nodes()]
  657. pos = get_node_positions(G, node_stage_map)
  658. fig, ax = plt.subplots(figsize=(22, 16))
  659. nx.draw_networkx_nodes(G, pos, node_size=node_sizes, node_color=node_colors, ax=ax, edgecolors='black')
  660. # Increased font size for better readability
  661. nx.draw_networkx_labels(G, pos, font_size=11, font_weight='bold', ax=ax)
  662. def get_edge_style(weight):
  663. if weight >= 46: return "black", 4 + weight * 0.15
  664. elif weight >= 30: return "#023e8a", 3.8 + weight * 0.13
  665. elif weight >= 15: return "brown", 3 + weight * 0.12
  666. elif weight >= 5: return "gray", 2 + weight * 0.1
  667. else: return "#cccccc", 1.5
  668. for u, v in G.edges():
  669. weight = G[u][v]['weight']
  670. color, width = get_edge_style(weight)
  671. start, end = pos[u], pos[v]
  672. # A simple approximation for radius based on node_size to offset the arrow
  673. start_node_index = list(G.nodes()).index(u)
  674. end_node_index = list(G.nodes()).index(v)
  675. start_radius = (node_sizes[start_node_index] ** 0.5) / 50.0
  676. end_radius = (node_sizes[end_node_index] ** 0.5) / 50.0
  677. dx, dy = end[0] - start[0], end[1] - start[1]
  678. dist = sqrt(dx**2 + dy**2)
  679. if dist == 0: continue
  680. arrow_start = (start[0] + dx * start_radius / dist, start[1] + dy * start_radius / dist)
  681. arrow_end = (end[0] - dx * end_radius / dist, end[1] - dy * end_radius / dist)
  682. arrow = FancyArrowPatch(
  683. posA=arrow_start, posB=arrow_end, connectionstyle="arc3,rad=0.2",
  684. arrowstyle="-|>,head_length=0.8,head_width=0.4", mutation_scale=25,
  685. color=color, linewidth=width, alpha=0.9
  686. )
  687. ax.add_patch(arrow)
  688. # Legends
  689. node_legend_elements = [Patch(facecolor=color, edgecolor="black", label=stage) for stage, color in color_map.items()]
  690. arrow_legend_elements = [
  691. Line2D([0], [0], color="#cccccc", lw=2, label="1-4 articles"),
  692. Line2D([0], [0], color="gray", lw=2, label="5–14 articles"),
  693. Line2D([0], [0], color="brown", lw=2, label="15–29 articles"),
  694. Line2D([0], [0], color="#023e8a", lw=2, label="30+ articles")
  695. ]
  696. first_legend = ax.legend(handles=node_legend_elements, title="Preprocessing Stages", fontsize=12, title_fontsize=14, loc="upper left", bbox_to_anchor=(1.02, 1))
  697. ax.add_artist(first_legend)
  698. ax.legend(handles=arrow_legend_elements, title="Transition Frequency", fontsize=12, title_fontsize=14, loc="upper left", bbox_to_anchor=(1.02, 0.7))
  699. plt.title(title, fontsize=20, fontweight='bold')
  700. plt.axis("off")
  701. plt.tight_layout(rect=[0, 0, 0.85, 1]) # Adjust for legend
  702. plt.savefig("flowchart_with_stats.png", dpi=300, bbox_inches='tight') # Save the figure
  703. plt.show()
  704. # Run plot
  705. plot_layered_flowchart(transition_counts, node_stage)
  706. # %%
  707. # =========================================
  708. # 6️⃣ Define Stage Mapping and Dynamic Assignment
  709. # =========================================
  710. stage_map = {
  711. "Raw data": ["raw_data"],
  712. "Pre ICA - Signal Cleaning": [
  713. "channel_removal",
  714. "highpass_filter", "lowpass_filter", "bandpass_filter", "notch_filter",
  715. "downsample"
  716. ],
  717. "Pre ICA - Data Preprocessing": [
  718. "artifact_rejection", "bad_channel_detection","re_reference", "epoching"
  719. ],
  720. "ICA": ["IC_decomposition", "IC_rejection"],
  721. "Post ICA": [
  722. "clustering", "baseline_correction",
  723. "dipole_fitting", "normalization" ,"despiking", "CA_rejection"
  724. ],
  725. "Outcome": ["PSD", "ERD/ERS", "ERSP", "CMC"]
  726. }
  727. def assign_stage(step, idx, steps):
  728. """Assign preprocessing stage based on position and context."""
  729. if step in ["bandpass_filter", "lowpass_filter", "highpass_filter", "notch_filter", "artifact_rejection"]:
  730. if "IC_decomposition" in steps[idx + 1:]:
  731. if "filter" in step:
  732. return "Pre ICA - Signal Cleaning"
  733. else:
  734. return "Pre ICA - Data Preprocessing"
  735. else:
  736. return "Post ICA"
  737. for stage, keywords in stage_map.items():
  738. if step in keywords:
  739. return stage
  740. return "Other"
  741. # %%
  742. steps_all = sorted(set([s for sublist in steps_grouped["step_keywords"] for s in sublist]))
  743. pivot = pd.DataFrame(False, index=steps_grouped["Citation"], columns=steps_all)
  744. for _, row in steps_grouped.iterrows():
  745. for step in row["step_keywords"]:
  746. pivot.loc[row["Title"], step] = True
  747. upset_data = from_indicators(pivot.columns, data=pivot)
  748. plt.figure(figsize=(10, 6))
  749. UpSet(upset_data, show_counts=True, sort_by='degree').plot()
  750. plt.suptitle("Overlap of EEG Preprocessing Steps Across Studies", fontsize=14)
  751. plt.show()
  752. # %%
  753. df_artifact = pd.read_csv(os.path.join(dir_processed, "Artifact_Methods_cleaned.csv"))
  754. df_artifact["Citation"] = df_artifact["Citation"].fillna("Unknown Study")
  755. methods = df_artifact["Artifactrej_methods"].unique()
  756. pivot = pd.DataFrame(0, index=df_artifact["Citation"].unique(), columns=methods)
  757. for title, group in df_artifact.groupby("Citation"):
  758. for m in group["Artifactrej_methods"]:
  759. pivot.loc[title, m] = 1
  760. # Horizontal stacked bars
  761. plt.figure(figsize=(10, len(pivot) * 0.25 + 3))
  762. bottoms = np.zeros(len(pivot))
  763. colors = sns.color_palette("tab20", n_colors=len(methods))
  764. for i, method in enumerate(pivot.columns):
  765. plt.barh(pivot.index, pivot[method], left=bottoms, color=colors[i], label=method)
  766. bottoms += pivot[method].values
  767. plt.xlabel("Method Presence (1 = used)")
  768. plt.ylabel("Study")
  769. plt.title("Artifact Rejection Methods Across Studies")
  770. plt.legend(bbox_to_anchor=(1.05, 1), loc='upper left', title="Methods")
  771. plt.tight_layout()
  772. plt.show()
  773. # %%
  774. df_artifact = pd.read_csv(os.path.join(dir_processed, "Artifact_Methods_cleaned.csv"))
  775. df_artifact["Citation"] = df_artifact["Citation"].fillna("Unknown Study")
  776. methods = df_artifact["Artifactrej_methods"].unique()
  777. pivot = pd.DataFrame(0, index=df_artifact["Citation"].unique(), columns=methods)
  778. for title, group in df_artifact.groupby("Citation"):
  779. for m in group["Artifactrej_methods"]:
  780. pivot.loc[title, m] = 1
  781. # Horizontal stacked bars
  782. plt.figure(figsize=(10, len(pivot) * 0.25 + 3))
  783. bottoms = np.zeros(len(pivot))
  784. colors = sns.color_palette("tab20", n_colors=len(methods))
  785. for i, method in enumerate(pivot.columns):
  786. plt.barh(pivot.index, pivot[method], left=bottoms, color=colors[i], label=method)
  787. bottoms += pivot[method].values
  788. plt.xlabel("Method Presence (1 = used)")
  789. plt.ylabel("Study")
  790. plt.title("Artifact Rejection Methods Across Studies")
  791. plt.legend(bbox_to_anchor=(1.05, 1), loc='upper left', title="Methods")
  792. plt.tight_layout()
  793. plt.show()
  794. # %%
  795. meta.to_csv("Combined_Metadata.csv", index=False)
  796. print("Combined dataset saved as Combined_Metadata.csv")

dataframe_plots.ipynb at commit 7508375, no license · at the source

Overview

Authors: Vaishali Vinod1, Lara Johanna Papin2, Robbin Romijnders1, Walter Maetzler1, Julius Welzel1,2
  1. Department of Neurology, University Hospital Schleswig‐Holstein Campus Kiel and Kiel University, Kiel, Germany
  2. Neuropsychology Lab, Department of Psychology, Carl von Ossietzky University of Oldenburg, Oldenburg, Germany
Journal: Psychophysiology, volume 63, issue 6, article e70352
Dates: received 21 January 2026; accepted 20 June 2026; published online 25 June 2026; in print June 2026
Type: Review · Language: English
License: CC BY
Identifiers: DOI 10.1111/psyp.70352 · PMID 42347764 · PMCID PMC13296838 · OpenAlex W7165860782
Open access: hybrid, a free copy (OpenAlex)
Status: code verified
Categories: EEG (modality), human (organism), methods / tools (subfield)
Methods: Spectral & time-frequency, Preprocessing, Connectivity, Smoothing, state filtering, decompositions, Source localization, Physiology & signal measures, Statistics
Keywords: artifact rejection, gait, mobile EEG, preprocessing, walking
MeSH: Brain*, Electroencephalography*, Gait*, Signal Processing, Computer-Assisted*, Humans (* major topic)
Topic: EEG and Brain-Computer Interfaces (Cognitive Neuroscience, Neuroscience), according to OpenAlex
Funding: Deutsche Forschungsgemeinschaft (464552782)
Citations: cited by 1 paper (Europe PMC); 75 references in the paper

Abstract

Mobile EEG has become popular in investigating brain dynamics during gait in recent years. Within this development, new preprocessing pipelines have been introduced and refined. The diversity of approaches, however, complicates comparisons across studies. To provide clarity, we reviewed studies that combined mobile EEG with gait measurements to map the preprocessing pipelines used in the field. Our review identified substantial heterogeneity in pipeline steps, their order, combinations, and the level of reporting detail. We visualized this heterogeneity as a map, tracing pathways from raw data to outcomes such as Power spectral density (PSD), Event‐related spectral perturbations (ERSP), Event‐related (de‐) synchronization (ERD/ERS), and Corticomuscular coherence (CMC), along with a subsequent analysis highlighting unique pipelines. Notably, artifact rejection varied across studies in both the tools used and reporting practices. While differences in hardware, setup, and experimental paradigms can justify this variability, they also challenge comparability across findings. These results emphasize the need for transparent reporting standards and provide a foundation for future efforts toward developing shared standards in the mobile EEG community.

Reproduced under the paper's license (CC BY), from the paper cited above.

Repositories

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

vaishalivinod/LitExtract

License: none: the authors keep all their rights
State: the link answers, verified on 27 September 2026
Evidence: files inventoried
Commit: 7508375060633e3369520e33f2affb2b6e6411e0, 10 April 2026
Languages: Python (16), Jupyter (1)
Size: 36 files, 17 scripts
Software Heritage: not archived
Found in: the text, “Literature Search”
Holds: README, environment (poetry.lock, pyproject.toml), 1 notebook
Not found: license file, CITATION.cff, tests, continuous integration, documentation
Tools: pandas (9 files), Matplotlib (6 files), NetworkX (3 files), NumPy (3 files), seaborn (3 files)
Availability: 1 check, the latest on 27 September 2026: the link answers
  • 27 September 2026: the link answers
18 files

neurogeriatricskiel/LitExtract

License: none: the authors keep all their rights
State: the link answers, verified on 27 September 2026
Evidence: files inventoried
Commit: 7508375060633e3369520e33f2affb2b6e6411e0, 10 April 2026
Languages: Python (16), Jupyter (1)
Size: 36 files, 17 scripts
Software Heritage: not archived
Found in: “Data Availability Statement”
Holds: README, environment (poetry.lock, pyproject.toml), 1 notebook
Not found: license file, CITATION.cff, tests, continuous integration, documentation
Tools: pandas (9 files), Matplotlib (6 files), NetworkX (3 files), NumPy (3 files), seaborn (3 files)
Availability: 1 check, the latest on 27 September 2026: the link answers
  • 27 September 2026: the link answers
18 files

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

Tracing map

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

What the map holds:

  • 2 repositories of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
  • 34 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

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

Data Availability Statement

The data that support the findings of this study are openly available in LitExtract at https://github.com/neurogeriatricskiel/LitExtract.

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 → Wiley

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, 74 references.

Cite

This paper

Vinod, V., Papin, L. J., Romijnders, R., Maetzler, W., & Welzel, J. (2026). Preprocessing on the Go: Practices in Gait-Related Mobile EEG. Psychophysiology, 63(6), e70352. https://doi.org/10.1111/psyp.70352

BibTeX

@article{vinod2026preprocessing,
author = {Vinod, Vaishali and Papin, Lara Johanna and Romijnders, Robbin and Maetzler, Walter and Welzel, Julius},
title = {{Preprocessing on the Go: Practices in Gait-Related Mobile EEG}},
journal = {Psychophysiology},
year = {2026},
month = jun,
volume = {63},
number = {6},
pages = {e70352},
publisher = {Wiley},
issn = {0048-5772},
doi = {10.1111/psyp.70352},
url = {https://doi.org/10.1111/psyp.70352},
pmid = {42347764},
pmcid = {PMC13296838}
}

RIS

TY - JOUR
AU - Vinod, Vaishali
AU - Papin, Lara Johanna
AU - Romijnders, Robbin
AU - Maetzler, Walter
AU - Welzel, Julius
TI - Preprocessing on the Go: Practices in Gait-Related Mobile EEG
T2 - Psychophysiology
J2 - Psychophysiology
PY - 2026
DA - 2026/06/01
VL - 63
IS - 6
SP - e70352
SN - 0048-5772
PB - Wiley
DO - 10.1111/psyp.70352
UR - https://doi.org/10.1111/psyp.70352
LA - en
ER -

CSL-JSON

{
"id": "10.1111/psyp.70352",
"type": "article-journal",
"title": "Preprocessing on the Go: Practices in Gait-Related Mobile EEG",
"container-title": "Psychophysiology",
"author": [
{
"family": "Vinod",
"given": "Vaishali"
},
{
"family": "Papin",
"given": "Lara Johanna"
},
{
"family": "Romijnders",
"given": "Robbin"
},
{
"family": "Maetzler",
"given": "Walter"
},
{
"family": "Welzel",
"given": "Julius"
}
],
"container-title-short": "Psychophysiology",
"volume": "63",
"issue": "6",
"page": "e70352",
"DOI": "10.1111/psyp.70352",
"PMID": "42347764",
"PMCID": "PMC13296838",
"ISSN": "0048-5772",
"publisher": "Wiley",
"URL": "https://doi.org/10.1111/psyp.70352",
"language": "en",
"issued": {
"date-parts": [
[
2026,
6,
1
]
]
}
}

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

Similar papers

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

[1] doi:10.1111/ejn.70428
Modulation of Corticomuscular Coupling With Split-Belt Locomotor Adaptation in Healthy Young Males.
Journal: The European journal of neuroscience
In common: EEG, 12 references
[2] doi:10.1007/s00221-026-07342-6 [code]
A portable solution for simultaneous human movement and mobile EEG acquisition: readiness potential for basketball free-throw shooting.
Journal: Experimental brain research
In common: EEG, 10 references
[3] doi:10.1371/journal.pone.0348957 [code]
Steps against the burden of Parkinson's disease (StepuP): Protocol of a randomized controlled trial elucidating the biomechanical and neurophysiological mechanisms of a speed dependent treadmill training intervention.
Journal: PloS one
In common: Matplotlib, NumPy, EEG, 5 references, author Vaishali Vinod
[4] doi:10.3390/s26082440 [code]
Suppressing Non-Stationary Motion Artefacts in Mobile EEG Using Generalized Eigenvalue Decomposition.
Journal: Sensors (Basel, Switzerland)
In common: methods / tools, EEG, 8 references
[5] doi:10.1016/j.isci.2026.116647 [code]
Enhancing mobile brain and body imaging: Open-source solutions for real-world research applications.
Journal: iScience
In common: methods / tools, 6 references
[6] doi:10.1111/psyp.70265 [code]
Neurocognitive Dynamics of Translating Information From a Spatial Map Into Action.
Journal: Psychophysiology
In common: EEG, 5 references
[7] doi:10.1111/psyp.70297 [code]
Heartbeat-Evoked Responses in M/EEG: A Systematic Review of Methods With Suggestions for Analysis and Reporting.
Journal: Psychophysiology
In common: pandas, NumPy, methods / tools, EEG, 3 references
[8] doi:10.1002/hbm.70469 [code]
VarCoNet: A Variability-Aware Self-Supervised Framework for Functional Connectome Extraction From Resting-State fMRI.
Journal: Human brain mapping
In common: NetworkX, seaborn, pandas, 2 other tools, methods / tools, 1 reference
[9] doi:10.1002/mds.70348 [code]
Electroencephalography-Based Clustering Reveals Robust Neurophysiological Subtypes in Parkinson's Disease.
Journal: Movement disorders : official journal of the Movement Disorder Society
In common: seaborn, pandas, Matplotlib, 1 other tool, EEG, 2 references
[10] doi:10.1038/s41598-026-56070-y [code]
SSDLabeler: realistic semi-synthetic data generation for multi-label artifact classification in EEG.
Journal: Scientific reports
In common: seaborn, pandas, Matplotlib, 1 other tool, 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.