OSCR

EStiMapp: A practical tool for mapping clinical symptoms evoked by electrical stimulation in intracranial EEG for epilepsy surgery.

Code ↔ Paper

7 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 7 matches
  1. [1] § Methods › Workflow using the graphical user interface ↔ estimapp.py, lines 1–56 · score 0.75 · implanted electrodes, electrode scheme, electrical stimulation, duration, Optional, channel
  2. [2] § Results › The graphical user interface ↔ estimapp.py, lines 1–56 · score 0.69 · stimulated electrode pair, implanted electrodes, electrode scheme, optionally, rendering, PLY
  3. [3] § Results › Categories of evoked clinical symptoms ↔ functions/estimapp_create_stimulations_overview.py, lines 32–143 · score 0.61 · affective, auditory, vestibular, autonomic, discharge, doubt
  4. [4] § Results › Co-designing the graphical user interface ↔ functions/estimapp_create_stimulations_overview.py, lines 32–143 · score 0.60 · elementary motor, complex motor, discharge, doubt, somatosensory, seizure
  5. [5] § Results › Co-designing the graphical user interface ↔ functions/estimapp_generate_3d_plot.py, lines 29–150 · score 0.60 · elementary motor, complex motor, discharge, doubt, somatosensory, seizure
  6. [6] § Results › Categories of evoked clinical symptoms ↔ functions/estimapp_generate_3d_plot.py, lines 29–150 · score 0.58 · auditory, vestibular, autonomic, discharge, doubt, somatosensory
  7. [7] § Methods › Workflow using the graphical user interface ↔ estimapp.py, lines 69–100 · score 0.56 · medical device, UMC Utrecht, Render, installed, committee, hosted

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 · 482 lines · 21 KB · no license · 3 matches

  1. """
  2. Created on Mon May 26, 2025
  3. @author: Irene Heijink
  4. Run this dash app to host the webpage at localhost:8050/
  5. The app visualises the result of electrical stimulation during intracranial monitoring.
  6. Input:
  7. Electrodes overview: an excel file with the patient specific electrode scheme.
  8. Annotations: one or multiple csv files with the EEG annotations during stimulation.
  9. The categories of clinical symptoms should be annotated using the correct abbreviations,
  10. please find the user instructions. Column names: ['Start from:', 'Time End', 'Duration', 'Category', 'Comment']
  11. Optional, required for 3D rendering:
  12. Electrode coordinates: an excel file with the patient specific electrode names,
  13. number of channels, and entry and target coordinates of the implanted electrodes.
  14. Column names: ['electrode_name', 'nr_of_channels', 'entry_x', 'entry_y', 'entry_z', 'target_x', 'target_y', 'target_z']
  15. PLY brain rendering: a PLY file with the patient specific 3D brain rendering.
  16. Can be saved via EpiNav or other visualization software.
  17. Example data is available for all input data at DataVerseNL.
  18. Output:
  19. 2D figure: the projection of clinical symptom categories on the electrode overview.
  20. Table: category of clinical symptoms, free text annotations, and stimulation type
  21. sorted per stimulated electrode pair.
  22. 3D figure: the projection of clinical symptom categories on the implanted electrodes
  23. and brain rendering in 3D. The end-user can rotate and translate the figure and
  24. change the opacity.
  25. """
  26. import dash
  27. from dash import html, dcc, Input, Output, State, dash_table
  28. import dash_mantine_components as dmc
  29. import base64
  30. import io
  31. import pandas as pd
  32. import trimesh
  33. import csv
  34. import os
  35. from functions.estimapp_process_annotations import estimapp_process_annotations
  36. from functions.estimapp_generate_plot import estimapp_generate_plot
  37. from functions.estimapp_generate_table import estimapp_generate_table
  38. from functions.estimapp_generate_3d_plot import estimapp_generate_3d_plot
  39. from functions.estimapp_create_upload_button import estimapp_create_upload_button
  40. app = dash.Dash(__name__, suppress_callback_exceptions=True)
  41. server = app.server
  42. app.title = "EStiMapp"
  43. # Layouts
  44. app.layout = dmc.MantineProvider(
  45. children=html.Div([
  46. dcc.Location(id="main-url", refresh=False),
  47. dcc.Store(id="session-data", storage_type='memory'),
  48. html.Div(id="warning-alert"),
  49. html.Div(id="page-content")],
  50. style={"backgroundColor":"rgb(243,250,255)"}
  51. )
  52. )
  53. def layout_home():
  54. return dmc.Container(
  55. dmc.Stack([
  56. dmc.Title("EStiMapp", order=2),
  57. dmc.Text("A practical tool for mapping clinical symptoms evoked by electrical stimulation in intracranial EEG for epilepsy surgery",
  58. fw=500, ta="center", w="450px"),
  59. html.Br(),
  60. html.Br(),
  61. dmc.TextInput(
  62. id="name-input",
  63. label="Enter patient name or ID (optional)",
  64. placeholder="e.g., John Doe",
  65. required=False,
  66. style={"marginBottom": 20}),
  67. estimapp_create_upload_button("upload-electrodes", "upload-overview-electrodes", "Upload overview electrodes",
  68. "xlsx file containing electrode names and ordering. Example file: ","https://doi.org/10.34894/KMT3VI"),
  69. estimapp_create_upload_button("upload-annotations", "upload-overview-annotations", "Upload overview annotations",
  70. "csv file(s) containing annotations from iEEG software. Multiple files can be uploaded at once. \n For Micromed users: these files can be compiled automatically from a Micromed TRC using the Export Notes option in Micromed. Example files: ","https://doi.org/10.34894/KMT3VI", multiple=True),
  71. dmc.Text("Optional, required for 3D rendering:", fw=500),
  72. estimapp_create_upload_button("upload-coordinates", "upload-electrode-coordinates", "Upload electrode coordinates",
  73. "xlsx file containing electrode names, number of contacts, and entry and target coordinates. Example file: ","https://doi.org/10.34894/KMT3VI"),
  74. estimapp_create_upload_button("upload-ply", "upload-ply-rendering", "Upload PLY brain rendering",
  75. "ply file containing 3D brain rendering. Example file: ","https://doi.org/10.34894/KMT3VI"),
  76. html.Br(),
  77. dmc.Button("Create EStiMapp", id="submit-btn"),
  78. dmc.Text("EStiMapp is a visualization tool that was evaluated by the CE-committee of the UMC Utrecht and was labeled not to be a medical device. The use or reliance of any information contained on the site is solely at your own risk. Data privacy cannot be guaranteed when using the app hosted on Render. For patient data, the locally installed version is recommended.",
  79. fw=300, ta="center", w="450px"),
  80. html.Div(id="warning-alert"),
  81. ], align="center", gap="sm"),size="sm")
  82. def layout_result():
  83. print("layout_result is called")
  84. return html.Div([
  85. html.H3("Result Page", style={'font-family':'verdana'}),
  86. html.H4(id="result-name", style={'font-family':'verdana'}),
  87. dcc.Tabs(id="result-tabs", value="tab-2d", children=[
  88. dcc.Tab(label="2D visualization", value="tab-2d",
  89. style={'background':'white', 'color':'black', 'font-family':'verdana'},
  90. selected_style={'background':'blue', 'color':'white', 'font-family':'verdana'}),
  91. dcc.Tab(label="3D visualization", value="tab-3d",
  92. style={'background':'white', 'color': 'black', 'font-family': 'verdana'},
  93. selected_style={'background':'blue', 'color':'white', 'font-family':'verdana'})]),
  94. html.Div(id="result-tab-content"),
  95. html.Br(),
  96. dcc.Store(id="processed-annotations"),
  97. dcc.Store(id="edited-processed-annotations"),
  98. html.Div(id="result-table") # table is outside tab
  99. ])
  100. # Show uploaded file names
  101. @app.callback(
  102. Output("upload-overview-electrodes", "children"),
  103. Input("upload-electrodes", "filename")
  104. )
  105. def update_electrodes(filename):
  106. return f"Selected: {filename}" if filename else ""
  107. @app.callback(
  108. Output("upload-overview-annotations", "children"),
  109. Input("upload-annotations", "filename")
  110. )
  111. def update_annotations(filenames):
  112. if filenames:
  113. return "Selected: " + ", ".join(filenames)
  114. return ""
  115. @app.callback(
  116. Output("upload-electrode-coordinates", "children"),
  117. Input("upload-coordinates", "filename")
  118. )
  119. def update_coordinates(filename):
  120. if filename:
  121. return f"Selected: {filename}" if filename else ""
  122. return ""
  123. @app.callback(
  124. Output("upload-ply-rendering", "children"),
  125. Input("upload-ply", "filename")
  126. )
  127. def update_ply(filename):
  128. if filename:
  129. return f"Selected: {filename}" if filename else ""
  130. return ""
  131. # Result page
  132. def show_result(data):
  133. print("⚡ show_result called")
  134. name = data.get("name", "")
  135. electrodes = data.get("electrodes")
  136. annotations = data.get("annotations")
  137. coordinates = data.get("coordinates")
  138. ply = data.get("ply")
  139. def decode_excel(content):
  140. _, content_string = content.split(',')
  141. decoded = base64.b64decode(content_string)
  142. xls = pd.ExcelFile(io.BytesIO(decoded))
  143. sheet_names = xls.sheet_names
  144. print("sheet names", sheet_names)
  145. if len(sheet_names) > 1 and "sjabloon" in sheet_names:
  146. sheet_name = "sjabloon"
  147. elif len(sheet_names) > 1 and "Sheet 1" in sheet_names:
  148. sheet_name = "Sheet 1"
  149. elif len(sheet_names) > 1 and "Sheet1" in sheet_names:
  150. sheet_name = "Sheet1"
  151. elif len(sheet_names) > 1 and "elektroden" in sheet_names:
  152. sheet_name = "elektroden"
  153. elif len(sheet_names) > 1 and "Elektroden" in sheet_names:
  154. sheet_name = "Elektroden"
  155. else:
  156. sheet_name = 0 # Default to read first worksheet of excel file
  157. decoded_excel = pd.read_excel(io.BytesIO(decoded), sheet_name=sheet_name, keep_default_na=False)
  158. return decoded_excel
  159. # def decode_annotations(content):
  160. # _, content_string = content.split(',')
  161. # decoded = base64.b64decode(content_string)
  162. # annotations = pd.read_csv(io.BytesIO(decoded),
  163. # encoding="latin1", # handles special characters like °, é, etc.
  164. # sep="\t", # tab-delimited
  165. # engine="python", # more forgiving parser
  166. # quoting=csv.QUOTE_NONE # <-- ignore quotes completely
  167. # )
  168. # return annotations
  169. def decode_annotations(content):
  170. content_type, content_string = content.split(',')
  171. decoded = base64.b64decode(content_string)
  172. if 'csv' in content_type or 'text' in content_type:
  173. annotations = pd.read_csv(io.BytesIO(decoded),
  174. encoding="latin1",
  175. sep="\t",
  176. engine="python",
  177. quoting=csv.QUOTE_NONE
  178. )
  179. elif 'excel' in content_type or 'spreadsheetml' in content_type or 'xls' in content_type:
  180. #xls = pd.ExcelFile(io.BytesIO(decoded))
  181. #sheet_name = xls.sheet_names[0] # take first sheet
  182. annotations = pd.read_excel(io.BytesIO(decoded), keep_default_na=False)
  183. print('excel file is decoded')
  184. else:
  185. raise ValueError(f"Unsupported file type: {content_type}")
  186. return annotations
  187. def decode_ply(content):
  188. _, content_string = content.split(',')
  189. decoded = base64.b64decode(content_string)
  190. mesh = trimesh.load(io.BytesIO(decoded), file_type='ply')
  191. return mesh
  192. print("decode annotations, file type:", type(annotations), type(annotations[0])) # <class 'list'> <class 'str'>
  193. annotations_df = pd.DataFrame()
  194. for file in annotations:
  195. decoded_annotations = decode_annotations(file)
  196. print("decoded annotations type", type(decoded_annotations), decoded_annotations.shape)
  197. print("add annotations to dataframe")
  198. annotations_df = pd.concat([annotations_df, decoded_annotations], ignore_index=True)
  199. print("decoded annotations", type(annotations_df), "size df", annotations_df.shape, annotations_df['Comment']) #decoded annotations <class 'list'> <class 'pandas.core.frame.DataFrame'>
  200. print('decoding electrodes')
  201. decoded_electrodes = decode_excel(electrodes) # df
  202. stimulations_df, processed_annotations, categories_dict = estimapp_process_annotations(annotations_df)
  203. print("stimulations_df", stimulations_df.head())
  204. print("processed_annotations:", processed_annotations)
  205. # 3D
  206. coordinates_df = decode_excel(coordinates) if data.get("coordinates") else None
  207. mesh = decode_ply(ply) if data.get("ply") else None
  208. return name, decoded_electrodes, processed_annotations, categories_dict, coordinates_df, mesh
  209. # Page routing
  210. @app.callback(
  211. Output("page-content", "children"),
  212. Input("main-url", "pathname")
  213. )
  214. def display_page(pathname):
  215. print("📍 Navigated to pathname:", pathname)
  216. if pathname == "/result":
  217. return layout_result()
  218. else:
  219. return layout_home()
  220. # Handle submit
  221. @app.callback(
  222. Output("main-url", "pathname"),
  223. Output("session-data", "data"),
  224. Output("warning-alert", "children"),
  225. Input("submit-btn", "n_clicks"),
  226. State("name-input", "value"),
  227. State("upload-electrodes", "contents"),
  228. State("upload-annotations", "contents"),
  229. State("upload-coordinates", "contents"),
  230. State("upload-ply", "contents"),
  231. prevent_initial_call=True
  232. )
  233. def handle_submit(n_clicks, name, electrodes, annotations, coordinates, ply):
  234. if n_clicks is None:
  235. raise dash.exceptions.PreventUpdate
  236. print("🚨 Submit clicked")
  237. print(" ↳ Electrodes content present:", isinstance(electrodes, str))
  238. print(" ↳ Annotations content list:", isinstance(annotations, list), "Length:", len(annotations) if annotations else 0)
  239. print(" ↳ Electrode coordinates content present:", isinstance(coordinates, str))
  240. print(" ↳ PLY content present:", isinstance(ply, str)) # string?
  241. missing = []
  242. if not electrodes or not isinstance(electrodes, str):
  243. missing.append("Excel electrodes")
  244. if not annotations or not isinstance(annotations, list) or len(annotations) == 0:
  245. missing.append("Annotations")
  246. if missing:
  247. return dash.no_update, dash.no_update, dmc.Alert(
  248. title="Missing Information",
  249. color="red",
  250. radius="md",
  251. children="Please provide: " + ", ".join(missing)
  252. )
  253. data = {
  254. "name": name or "",
  255. "electrodes": electrodes,
  256. "annotations": annotations,
  257. "coordinates": coordinates,
  258. "ply": ply # Default value if missing is None
  259. }
  260. return "/result", data, None # None is default value for Alert missing data
  261. # Result Display
  262. @app.callback(
  263. Output("result-name", "children"),
  264. Output("result-table", "children"),
  265. Output("result-tab-content", "children"),
  266. Output("processed-annotations", "data"),
  267. Input("result-tabs", "value"),
  268. Input("session-data", "data"),
  269. )
  270. def update_result_tabs(tab, data):
  271. if not data:
  272. return "No data submitted", html.Div(), html.Div(), html.Div()
  273. name, decoded_electrodes, processed_annotations, categories_dict, coordinates_df, mesh = show_result(data)
  274. table, table_columns = estimapp_generate_table(processed_annotations)
  275. dropdown_individual_cat = set(categories_dict.values())
  276. dropdown_multiple_cat = set(table["Category"].unique())
  277. dropdown_menu = sorted(dropdown_individual_cat | dropdown_multiple_cat) # removes duplicates
  278. table_section = html.Div([html.Label("Overview of all annotations per stimulation pair ", style={'font-family':'verdana', 'font': 'bold'}),
  279. html.Div( html.Button("Download table", id="download-table-btn", style={
  280. "backgroundColor": "white", "border": "2px solid #228be6", "color": "#228be6", "padding": "6px 14px",
  281. "borderRadius": "6px", "cursor": "pointer", "fontSize": "14px",}),
  282. style={"display":"flex", "justifyContent":"flex-end", "marginBottom":"10px"}),
  283. dcc.Download(id="download-table"),
  284. dash_table.DataTable(id="editable-table", data=table.to_dict("records"),
  285. #columns=[{"name": col, "id": col} for col in table_columns],
  286. columns=[{"name": "Electrode 1", "id": "Electrode 1", "editable": False},
  287. {"name": "Electrode 2", "id": "Electrode 2", "editable": False},
  288. {"name": "Category", "id": "Category", "editable": True, "presentation": "dropdown"},
  289. {"name": "Free text", "id": "Free text", "editable": True},
  290. {"name": "Stim type", "id": "Stim type", "editable": False},
  291. {"name": "Settings", "id": "Settings", "editable": False},
  292. ],
  293. dropdown = {
  294. "Category": {
  295. "options": [{"label": v, "value": v} for v in dropdown_menu]
  296. }
  297. },
  298. sort_action="native", # Allow user to sort columns
  299. filter_action="native", # Optional: Allow column filtering
  300. filter_options={'case':'insensitive'},
  301. row_deletable=True,
  302. editable=True,
  303. #row_addable=True,
  304. style_table={"overflowX": "auto"},
  305. style_cell={"textAlign": "left"},#, "whiteSpace": "pre-line"},
  306. style_data={"whiteSpace": "normal", "height": "auto"}
  307. ),
  308. ])
  309. if tab == "tab-2d":
  310. print("Generating figure")
  311. fig2d = dcc.Graph(id="result-plot-2d", figure = estimapp_generate_plot(decoded_electrodes, processed_annotations))
  312. return f"{name}" if name else "No name provided", table_section, html.Div([
  313. html.Div(fig2d,
  314. style={"width": "auto", "display": "inline-block", "verticalAlign": "top", "margin": "0", "padding": "0", "backgroundColor": "rgba(0,0,0,0)"}),
  315. html.Img(src='/assets/Legend.png', style={'width': '400px', "margin": "0", "marginBottom": "75px", "padding": "5px", "alignSelf": "flex-end"}) ],
  316. style={"textAlign": "left", "whiteSpace": "nowrap", "display": "flex", "alignItems": "flex-end", "justifyContent": "flex-start"}), processed_annotations.to_json(date_format="iso", orient="split")
  317. elif tab == "tab-3d" and mesh:
  318. fig3d = estimapp_generate_3d_plot(mesh, coordinates_df, processed_annotations)
  319. return f"{name}" if name else "No name provided", table_section, html.Div([
  320. html.Div([
  321. dcc.Graph(id="result-plot-3d", figure=fig3d, clear_on_unhover=True, style={"width":"1200px","height":"800px"}),
  322. html.Div(id="hover-coords", style={
  323. "position": "absolute",
  324. "bottom": "80px",
  325. "left": "20px",
  326. "backgroundColor": "rgba(255,255,255,0.85)",
  327. "padding": "6px 12px",
  328. "borderRadius": "5px",
  329. "fontFamily": "monospace",
  330. "fontSize": "12px",
  331. "zIndex": "1000",
  332. "border": "1px solid #ccc",
  333. "boxShadow": "0px 2px 4px rgba(0,0,0,0.1)"
  334. }),
  335. html.Label("Adjust cortex opacity:"),
  336. dcc.Slider(id="opacity", min=0, max=1, step=0.1, value=0.8, marks={0: "0", 0.5: "0.5", 1: "1"}, updatemode="drag"),
  337. ], style={"position": "relative", "display": "inline-block", "verticalAlign": "top"}),
  338. html.Img(src="/assets/Legend.png", style={
  339. "width": "400px",
  340. "margin": "0 0 0 20px",
  341. "padding": "5px",
  342. "display": "inline-block",
  343. "verticalAlign": "top"
  344. })
  345. ], style={"whiteSpace": "nowrap", "textAlign": "left"}), processed_annotations.to_json(date_format="iso", orient="split")
  346. else:
  347. return f"{name}" if name else "No name provided", table_section, html.Div("No PLY data uploaded for 3D visualization.", style={'font-family':'verdana'}), processed_annotations.to_json(date_format="iso", orient="split")
  348. # Table callbacks
  349. @app.callback(
  350. Output("edited-processed-annotations", "data"),
  351. Input("editable-table", "data")
  352. )
  353. def save_edits(data):
  354. # Store the edited table in JSON format
  355. return pd.DataFrame(data).to_json(date_format="iso", orient="split")
  356. @app.callback(
  357. Output("download-table", "data"),
  358. Input("download-table-btn", "n_clicks"),
  359. State("result-name", "children"),
  360. State("edited-processed-annotations", "data"),
  361. prevent_initial_call=True
  362. )
  363. def download_table(n_clicks, name, processed_annotations_json):
  364. if not processed_annotations_json:
  365. raise dash.exceptions.PreventUpdate
  366. processed_annotations = pd.read_json(processed_annotations_json, orient="split")
  367. print("download table")
  368. # return as CSV
  369. return dcc.send_data_frame(
  370. processed_annotations.to_csv,
  371. filename=f"{name}_annotations.csv",
  372. index=False
  373. )
  374. # 3D interaction functions
  375. @app.callback(
  376. Output("result-plot-3d", "figure"),
  377. Input("opacity", "value"),
  378. State("result-plot-3d", "figure"),
  379. State("result-plot-3d","relayoutData"),
  380. prevent_initial_call=True
  381. )
  382. def update_opacity(opacity, fig, relayoutData):
  383. if not fig:
  384. raise dash.exceptions.PreventUpdate
  385. # Preserve current camera
  386. camera = None
  387. if relayoutData and "scene.camera" in relayoutData:
  388. camera = relayoutData["scene.camera"]
  389. # Update mesh opacity
  390. for trace in fig["data"]:
  391. if trace["type"] == "mesh3d":
  392. trace["opacity"] = opacity
  393. # Reapply preserved camera
  394. if camera:
  395. fig["layout"]["scene"]["camera"] = camera
  396. return fig
  397. @app.callback(
  398. Output("hover-coords", "children"),
  399. Input("result-plot-3d", "hoverData"),
  400. prevent_initial_call=True
  401. )
  402. def display_hover_coordinates(hoverData):
  403. if not hoverData or "points" not in hoverData:
  404. return ""
  405. point = hoverData["points"][0]
  406. x, y, z = point.get("x"), point.get("y"), point.get("z")
  407. customdata = point.get("customdata")
  408. electrode_label = customdata if customdata and len(customdata) > 0 else "N/A"
  409. return html.Div([
  410. html.Div(f"Electrode: {electrode_label}"),
  411. html.Div(f"x: {x:.2f}, y: {y:.2f}, z: {z:.2f}")
  412. ])
  413. # To run app on server:
  414. if __name__ == "__main__":
  415. app.run(host="0.0.0.0", port=int(os.environ.get("PORT", 8050)), debug=False)
  416. #app.run(debug=False)

estimapp.py at commit 1299b34, no license · at the source

Overview

Authors: Irene B Heijink1,2, Susanne B Jelsma1,2, Sandra MA van der Salm1, Nicole EC van Klink1, Cyrille H Ferrier1, Sem Hoogteijling1,2, Charlotte JJ van Asch2, Dorien van Blooijs1,2, Maeike Zijlmans1,2
  1. Department of Neurology and Neurosurgery, University Medical Center Utrecht Brain Center, University Medical Center Utrecht, Full Member of European Reference Network EpiCARE, P.O. box 85500, 3508, GA, Utrecht, the Netherlands
  2. Stichting Epilepsie Instellingen Nederland (SEIN), P.O. box 540, 2130, AM, Hoofddorp, the Netherlands
Journal: Clinical neurophysiology practice, volume 11, pages 492-502
Dates: received 24 February 2026; accepted 8 June 2026; published online 12 June 2026
Type: Research article · Language: English
License: CC BY
Identifiers: DOI 10.1016/j.cnp.2026.06.006 · PMID 42440465 · PMCID PMC13333301 · OpenAlex W7164552638
Open access: gold, a free copy (OpenAlex)
Status: code verified
Categories: intracranial EEG (iEEG / ECoG / SEEG) (modality), epilepsy (population), clinical / translational (subfield)
Methods: Statistics
Keywords: Focal epilepsy, Intracranial EEG, Electrical stimulation, Clinical symptoms, Evoked symptoms, Graphical user interface, Co-design
Topic: EEG and Brain-Computer Interfaces (Cognitive Neuroscience, Neuroscience), according to OpenAlex
Funding: Horizon Europe; ZonMw (09150172210057); European Research Council (803880); EpilepsyNL
Citations: not cited yet (Europe PMC); 48 references in the paper

Abstract

Objective: Electrical stimulation of intracranial electrodes can map the patient's specific functional regions and seizure related symptoms. In our clinical practice, evoked functional symptoms and electrographic responses are manually annotated and visualized in a schematic electrode overview: an error-prone and time-consuming process. We here present an open-source graphical user interface (GUI) that standardizes the workflow and automatizes the visualization of intracranial EEG (iEEG) electrical stimulation results.

Methods: We defined categories of evoked clinical symptoms based on a literature study and consensus session. A co-design team participated in brainstorm sessions to set the requirements of the GUI. We built the GUI and tested its usability with qualitative and quantitative assessments (System Usability Scales (SUS) questionnaires).

Results: The workflow included standardized annotation of categories of evoked clinical symptoms in the iEEG software. The GUI visualized these evoked clinical symptoms in a 2D schematic overview, a 3D visualization of the brain with locations of evoked symptoms, and showed additional annotations per stimulated electrodes in a table. A mean SUS score of 82.3 (SD 9.2) was reached, which is considered excellent.

Conclusions: We present an in-house developed workflow and graphical user interface (GUI) to categorize and directly visualize evoked clinical symptoms resulting from electrical stimulation on intracranial electrodes.

Significance: Our open-source GUI facilitates standardization of the intracranial stimulation workflow and directly enables straightforward and uniform interpretation of evoked clinical symptoms.

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

UMCU-EpiLAB/umcuEpi_estimapp

License: none: the authors keep all their rights
State: the link answers, verified on 27 September 2026
Evidence: files inventoried
Commit: 1299b341ada0d87aed6255a87c7b4b56b91aa91a, 26 May 2026
Languages: Python (14)
Size: 36 files, 14 scripts
Software Heritage: not archived
Found in: “Data and code availability statement”
Holds: README, environment (requirements.txt)
Not found: license file, CITATION.cff, tests, continuous integration, documentation
Tools: pandas (9 files), NumPy (5 files), Plotly (2 files), Pillow (1 file)
Availability: 1 check, the latest on 27 September 2026: the link answers
  • 27 September 2026: the link answers
15 files

estimapp.onrender.com

License: none: the authors keep all their rights
State: the link answers, verified on 27 September 2026
Evidence: the link answers
Software Heritage: not checked
Found in: “Data and code availability statement”
Not found: README, license file, CITATION.cff, environment file, tests, continuous integration, documentation
Availability: 1 check, the latest on 27 September 2026: the link answers (HTTP 200)
  • 27 September 2026: the link answers (HTTP 200)
At the source: estimapp.onrender.com

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

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

Data

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

Data and code availability statement

The source code and web application are publicly available on GitHub (https://github.com/UMCU-EpiLAB/umcuEpi_estimapp.git) and (https://estimapp.onrender.com). The data of one example patient with stereo EEG is available on DataverseNL (https://doi.org/10.34894/KMT3VI (http://dx.doi.org/10.34894/KMT3VI)). The example patient provided informed consent for data use via the Registry for Epilepsy Surgery Patients (RESPect database) of the University Medical Center Utrecht (UMCU) and Stichting Epilepsie Instellingen Nederland (SEIN). The Medical Ethical Committee of the UMC Utrecht approved collecting pseudo-anonymized data in the RESPect database.

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

Versions

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

Version 1, 27 September 2026: the first record

Recorded: type, language, journal, volume, pages, dates, 9 authors, 7 keywords, 4 funders, 45 references.

Cite

This paper

Heijink, I. B., Jelsma, S. B., van der Salm, S. M., van Klink, N. E., Ferrier, C. H., Hoogteijling, S., van Asch, C. J., van Blooijs, D., & Zijlmans, M. (2026). EStiMapp: A practical tool for mapping clinical symptoms evoked by electrical stimulation in intracranial EEG for epilepsy surgery. Clinical neurophysiology practice, 11, 492-502. https://doi.org/10.1016/j.cnp.2026.06.006

BibTeX

@article{heijink2026estimapp,
author = {Heijink, Irene B and Jelsma, Susanne B and van der Salm, Sandra MA and van Klink, Nicole EC and Ferrier, Cyrille H and Hoogteijling, Sem and van Asch, Charlotte JJ and van Blooijs, Dorien and Zijlmans, Maeike},
title = {{EStiMapp: A practical tool for mapping clinical symptoms evoked by electrical stimulation in intracranial EEG for epilepsy surgery}},
journal = {Clinical neurophysiology practice},
year = {2026},
month = jun,
volume = {11},
pages = {492--502},
publisher = {Elsevier},
issn = {2467-981X},
doi = {10.1016/j.cnp.2026.06.006},
url = {https://doi.org/10.1016/j.cnp.2026.06.006},
pmid = {42440465},
pmcid = {PMC13333301}
}

RIS

TY - JOUR
AU - Heijink, Irene B
AU - Jelsma, Susanne B
AU - van der Salm, Sandra MA
AU - van Klink, Nicole EC
AU - Ferrier, Cyrille H
AU - Hoogteijling, Sem
AU - van Asch, Charlotte JJ
AU - van Blooijs, Dorien
AU - Zijlmans, Maeike
TI - EStiMapp: A practical tool for mapping clinical symptoms evoked by electrical stimulation in intracranial EEG for epilepsy surgery
T2 - Clinical neurophysiology practice
J2 - Clin Neurophysiol Pract
PY - 2026
DA - 2026/06/12
VL - 11
SP - 492
EP - 502
SN - 2467-981X
PB - Elsevier
DO - 10.1016/j.cnp.2026.06.006
UR - https://doi.org/10.1016/j.cnp.2026.06.006
LA - en
ER -

CSL-JSON

{
"id": "10.1016/j.cnp.2026.06.006",
"type": "article-journal",
"title": "EStiMapp: A practical tool for mapping clinical symptoms evoked by electrical stimulation in intracranial EEG for epilepsy surgery",
"container-title": "Clinical neurophysiology practice",
"author": [
{
"family": "Heijink",
"given": "Irene B"
},
{
"family": "Jelsma",
"given": "Susanne B"
},
{
"family": "van der Salm",
"given": "Sandra MA"
},
{
"family": "van Klink",
"given": "Nicole EC"
},
{
"family": "Ferrier",
"given": "Cyrille H"
},
{
"family": "Hoogteijling",
"given": "Sem"
},
{
"family": "van Asch",
"given": "Charlotte JJ"
},
{
"family": "van Blooijs",
"given": "Dorien"
},
{
"family": "Zijlmans",
"given": "Maeike"
}
],
"container-title-short": "Clin Neurophysiol Pract",
"volume": "11",
"page": "492-502",
"DOI": "10.1016/j.cnp.2026.06.006",
"PMID": "42440465",
"PMCID": "PMC13333301",
"ISSN": "2467-981X",
"publisher": "Elsevier",
"URL": "https://doi.org/10.1016/j.cnp.2026.06.006",
"language": "en",
"issued": {
"date-parts": [
[
2026,
6,
12
]
]
}
}

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.3389/fncir.2026.1814667 [code]
A modular and flexible pipeline for intraoperative electrode reconstruction and localization in patients with brain lesions.
Journal: Frontiers in neural circuits
In common: pandas, NumPy, intracranial EEG (iEEG / ECoG / SEEG), epilepsy, 3 references
[2] doi:10.1002/epi4.70311 [code]
Bridging computational and clinical strategies for presurgical identification of epileptogenic networks.
Journal: Epilepsia open
In common: NumPy, intracranial EEG (iEEG / ECoG / SEEG), epilepsy, clinical / translational, 2 references
[3] doi:10.1002/mco2.70931 [code]
Unveiling Functional Architecture of Human Thalamus: AStereoelectroencephalography Study.
Journal: MedComm
In common: epilepsy, 3 references
[4] doi:10.1093/braincomms/fcag187 [code]
50 Hz Cortical stimulation increases interictal epileptiform discharges at the seizure onset zone.
Journal: Brain communications
In common: intracranial EEG (iEEG / ECoG / SEEG), epilepsy, clinical / translational, 2 references
[5] doi:10.1002/brb3.71507 [code]
Anti-Seizure Medications Alter Functional and Effective Connectivity as Measured With Intracranial Electroencephalography.
Journal: Brain and behavior
In common: intracranial EEG (iEEG / ECoG / SEEG), epilepsy, 2 references
[6] doi:10.1162/imag.a.1276 [code]
High-resolution whole-brain magnetic resonance spectroscopic imaging in youth at risk for psychosis.
Journal: Imaging neuroscience (Cambridge, Mass.)
In common: Plotly, Pillow, pandas, 1 other tool, clinical / translational
[7] doi:10.1186/s12880-026-02481-2 [code]
Deep learning-based neuroanatomical profiling reveals population-specific brain changes in multiple sclerosis: a large-scale Middle Eastern study.
Journal: BMC medical imaging
In common: Plotly, Pillow, pandas, 1 other tool, clinical / translational
[8] doi:10.1093/braincomms/fcag176 [code]
Tau topography subtypes account for clinical heterogeneity and longitudinal trajectories in early-onset Alzheimer's disease.
Journal: Brain communications
In common: Plotly, Pillow, pandas, 1 other tool, clinical / translational
[9] doi:10.1371/journal.pone.0348866 [code]
Using deep learning to identify inherited retinal diseases based on wide-field retinal imaging data.
Journal: PloS one
In common: Plotly, Pillow, pandas, 1 other tool, clinical / translational
[10] doi:10.34133/csbj.0042 [code]
Using Steady-State Visual Evoked Potentials to Characterize Wide-Ranging Retinopathy Linked to &lt;i&gt;CRB1&lt;/i&gt;: Implications for Clinical Trials.
Journal: Computational and structural biotechnology journal
In common: Plotly, Pillow, pandas, 1 other tool, clinical / translational

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.