OSCR

Enhanced detection of subtle cortical abnormalities in focal epilepsy using 7 T MRI surface-based models and graph neural networks.

Code ↔ Paper

6 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 6 matches
  1. [1] § Materials and methods › 7 T and 3 T MRI surface-based feature assessment ↔ scripts/manage_results/plot_prediction_report.py, lines 400–436 · score 0.66 · intrinsic curvature, sulcal depth, cortical thickness, FLAIR, MELD, surface
  2. [2] § Materials and methods › 7 T and 3 T MRI surface-based feature assessment ↔ notebooks/plot_examples_reports.ipynb, lines 234–259 · score 0.66 · intrinsic curvature, sulcal depth, cortical thickness, FLAIR, MELD
  3. [3] § Results › 7 T classifier concordance with clinical and electrophysiological data ↔ scripts/manage_results/plot_prediction_report.py, lines 400–436 · score 0.56 · intrinsic curvature, sulcal depth, cortical thickness, pial, FLAIR, surface
  4. [4] § Results › 7 T classifier concordance with clinical and electrophysiological data ↔ notebooks/analysis_predictions.ipynb, lines 76–98 · score 0.56 · intrinsic curvature, sulcal depth, cortical thickness, pial, FLAIR
  5. [5] § Results › 7 T classifier concordance with clinical and electrophysiological data ↔ notebooks/analysis_predictions.ipynb, lines 76–98 · score 0.55 · intrinsic curvature, sulcal depth, cortical thickness, pial, FLAIR
  6. [6] § Materials and methods › 7 T and 3 T MRI surface-based cortical reconstruction ↔ scripts/new_patient_pipeline/run_script_segmentation.py, lines 148–205 · score 0.52 · FLAIRpial, recon, FreeSurfer, segmentation, command, cortical

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 · 873 lines · 37 KB · other · 2 matches

  1. from meld_graph.evaluation import Evaluator
  2. from meld_graph.experiment import Experiment
  3. from meld_graph.meld_cohort import MeldCohort, MeldSubject
  4. from meld_graph.meld_plotting import trim
  5. from meld_graph.paths import (
  6. NVERT,
  7. MELD_PARAMS_PATH,
  8. MELD_DATA_PATH,
  9. DK_ATLAS_FILE,
  10. EXPERIMENT_PATH,
  11. MODEL_PATH,
  12. SURFACE_PARTIAL,
  13. DEFAULT_HDF5_FILE_ROOT,
  14. SCRIPTS_DIR,
  15. )
  16. import os
  17. import tempfile
  18. import json
  19. import glob
  20. import h5py
  21. import argparse
  22. import numpy as np
  23. import nibabel as nb
  24. from nilearn import plotting, image
  25. from nilearn.image import new_img_like
  26. from nilearn._utils.numpy_conversions import as_ndarray
  27. from nilearn._utils.param_validation import check_threshold
  28. from nilearn._utils.extmath import fast_abs_percentile
  29. import pandas as pd
  30. import matplotlib_surface_plotting as msp
  31. import matplotlib.pyplot as plt
  32. from matplotlib.gridspec import GridSpec
  33. import matplotlib as mpl
  34. import matplotlib.cm as cm
  35. from PIL import Image
  36. import meld_graph.mesh_tools as mt
  37. from datetime import date
  38. from fpdf import FPDF
  39. from meld_graph.tools_pipeline import get_m, get_anat_files
  40. from meld_graph.hdf5_utils import open_hdf5_file
  41. class PDF(FPDF):
  42. def lines(self):
  43. self.set_line_width(0.0)
  44. self.line(5.0,5.0,205.0,5.0) # top one
  45. self.line(5.0,292.0,205.0,292.0) # bottom one
  46. self.line(5.0,5.0,5.0,292.0) # left one
  47. self.line(205.0,5.0,205.0,292.0) # right one
  48. def custom_header(self, logo, txt1, txt2=None):
  49. # Log
  50. self.image(logo, 10, 8, 33)
  51. # Arial bold
  52. self.set_font('Arial', 'B', 30)
  53. # Move to the right
  54. self.cell(80)
  55. # Title
  56. self.cell(w=30, h=10, txt=txt1, border=0, ln=0, align='C')
  57. if txt2 != None:
  58. # Arial bold 15
  59. self.ln(20)
  60. self.cell(80)
  61. self.set_font('Arial', 'B', 20)
  62. self.cell(w=30, h=5, txt=txt2, border=0, ln=0, align='C')
  63. # Line break
  64. self.ln(20)
  65. def custom_footer(self, txt):
  66. # Arial italic 8
  67. self.set_font('Arial', 'I', 8)
  68. # Position at 1.5 cm from bottom
  69. self.set_y(-30)
  70. # add text
  71. self.cell(w=0, h=0, txt=txt, border=0, ln=2, align='C')
  72. # Date
  73. today = date.today()
  74. today = today.strftime("%d/%m/%Y")
  75. self.cell(w=5, h=8, txt=str(today) , border=0, ln=0, align='L')
  76. # Page number
  77. self.cell(w=180, h=8, txt='Page ' + str(self.page_no()), border=0, ln=2, align='R')
  78. def info_box(self, txt):
  79. # set font
  80. self.set_font('Arial', 'I', 10)
  81. #set box color
  82. self.set_fill_color(160,214,190)
  83. # add text box info
  84. self.multi_cell(w=190, h=5, txt=txt , border=1, align='L', fill=True)
  85. def info_box_links(self, segments):
  86. """Render info text with inline clickable hyperlinks.
  87. segments: list of (text, link) tuples. link='' -> plain text."""
  88. self.set_fill_color(160, 214, 190)
  89. for text, link in segments:
  90. if link:
  91. self.set_text_color(0, 0, 238) # link blue
  92. self.set_font('Arial', 'IU', 10) # italic + underline
  93. self.write(5, text, link)
  94. self.set_text_color(0, 0, 0)
  95. self.set_font('Arial', 'I', 10)
  96. else:
  97. self.set_font('Arial', 'I', 10)
  98. self.write(5, text)
  99. self.ln()
  100. def info_box_clust(self, txt):
  101. self.ln(30)
  102. # set font
  103. self.set_font('Arial', 'I', 10)
  104. #set box color
  105. self.set_fill_color(160,214,190)
  106. # add text box info
  107. self.multi_cell(w=160, h=5, txt=txt , border=1, align='L', fill=True)
  108. def disclaimer_box(self, txt):
  109. # set font
  110. self.set_font('Arial', 'I', 9)
  111. #set box color
  112. self.set_fill_color(240,128,128)
  113. # add texte box info
  114. self.multi_cell(w=190, h=5, txt=txt , border=1, align='L', fill=True)
  115. def subtitle_inflat(self):
  116. # Arial bold 15
  117. self.set_font('Arial', 'B', 20)
  118. # Title
  119. self.cell(w=10, h=80, txt='Overview clusters on inflated brain', border=0, ln=2, align='L')
  120. def imagey(self,im, y):
  121. self.image(im, 5, y, link='', type='', w=190, h=297/3)
  122. def load_prediction(subject,hdf5):
  123. results={}
  124. with open_hdf5_file(hdf5, mode="r") as f:
  125. for hemi in ['lh','rh']:
  126. results[hemi] = f[subject][hemi]['prediction'][:]
  127. return results
  128. def create_surface_plots(surf,prediction,c, base_size=20):
  129. """plot and reload surface images"""
  130. cmap, colors = load_cmap()
  131. with tempfile.TemporaryDirectory() as tmpdir:
  132. tmp_file = os.path.join(tmpdir,'tmp.png')
  133. msp.plot_surf(surf['coords'],
  134. surf['faces'],prediction,
  135. rotate=[90],
  136. mask=prediction==0,pvals=np.ones_like(c.cortex_mask),
  137. colorbar=False,vmin=1,vmax=len(colors) ,cmap=cmap,
  138. base_size=base_size,
  139. filename=tmp_file)
  140. im = Image.open(tmp_file)
  141. im = trim(im)
  142. im = im.convert("RGBA")
  143. im1 = np.array(im)
  144. msp.plot_surf(surf['coords'],
  145. surf['faces'],prediction,
  146. rotate=[270],
  147. mask=prediction==0,pvals=np.ones_like(c.cortex_mask),
  148. colorbar=False,vmin=1,vmax=len(colors),cmap=cmap,
  149. base_size=base_size,
  150. filename=tmp_file)
  151. im = Image.open(tmp_file)
  152. im = trim(im)
  153. im = im.convert("RGBA")
  154. im2 = np.array(im)
  155. plt.close('all')
  156. os.remove(tmp_file)
  157. return im1,im2
  158. def load_cluster(file, subject):
  159. df=pd.read_csv(file,index_col=False)
  160. n_clusters = df[df['ID']==subject]['n_clusters']
  161. return np.array(n_clusters)[0]
  162. def get_key(dic, val):
  163. # function to return key for any value in dictionnary
  164. for key, value in dic.items():
  165. if val == value:
  166. return key
  167. return "No key for value {}".format(val)
  168. def define_atlas():
  169. atlas = nb.freesurfer.io.read_annot(os.path.join(MELD_PARAMS_PATH, DK_ATLAS_FILE))
  170. vertex_i = np.array(atlas[0]) - 1000 # subtract 1000 to line up vertex
  171. rois_prop = [
  172. np.count_nonzero(vertex_i == x) for x in set(vertex_i)
  173. ] # proportion of vertex per rois
  174. rois = [x.decode("utf8") for x in atlas[2]] # extract rois label from the atlas
  175. rois = dict(zip(rois, range(len(rois)))) # extract rois label from the atlas
  176. rois.pop("unknown") # roi not part of the cortex
  177. rois.pop("corpuscallosum") # roi not part of the cortex
  178. return rois, vertex_i, rois_prop
  179. def get_cluster_location(cluster_array):
  180. ''' Find centre of mass of prediction
  181. Return ROI from desikan kiliany atlas'''
  182. verts,faces=nb.freesurfer.io.read_geometry(os.path.join(MELD_PARAMS_PATH,
  183. 'fsaverage_sym','surf','lh.sphere'))
  184. cluster_array = np.array(cluster_array).astype(bool)
  185. center_coords= np.mean(verts[cluster_array],axis=0)
  186. center_vert = np.argsort((np.abs(verts-center_coords).mean(axis=1)))[0]
  187. rois, vertex_i, rois_prop = define_atlas()
  188. ind = vertex_i[center_vert]
  189. location = get_key(rois,ind)
  190. return location
  191. def save_mgh(filename, array, demo):
  192. """save mgh file using nibabel and imported demo mgh file"""
  193. with tempfile.NamedTemporaryFile() as mmap_file:
  194. mmap = np.memmap(mmap_file.name, dtype="float32", mode="w+", shape=demo.get_data().shape)
  195. mmap[:, 0, 0] = array[:]
  196. output = nb.MGHImage(mmap, demo.affine, demo.header)
  197. nb.save(output, filename)
  198. def load_cmap():
  199. """ create the colors dictionarry for the clusters"""
  200. from matplotlib.colors import ListedColormap
  201. import numpy as np
  202. colors = [
  203. [255,0,0], #red
  204. [255,215,0], #gold
  205. [0,0,255], #blue
  206. [0,128,0], #green
  207. [148,0,211], #darkviolet
  208. [255,0,255], #fuchsia
  209. [255,165,0], #orange
  210. [0,255,255], #cyan
  211. [0,255,0], #lime
  212. [106,90,205], #slateblue
  213. [240,128,128], #lightcoral
  214. [184,134,11], #darkgoldenrod
  215. [100,149,237], #cornflowerblue
  216. [102,205,170], #mediumaquamarine
  217. [75,0,130], #indigo
  218. [250,128,114], #salmon
  219. [240,230,140], #khaki
  220. [176,224,230], #powderblue
  221. [128,128,0], #olive
  222. [221,160,221], #plum
  223. [255,127,80], #coral
  224. [255,250,205], #lemonchiffon
  225. [240,255,255], #azure
  226. [152,251,152], #palegreen
  227. [255,192,203], #pink
  228. ]
  229. colors=np.array(colors)/255
  230. dict_c = dict(zip(np.arange(1, len(colors)+1), colors))
  231. cmap = ListedColormap(colors)
  232. return cmap, dict_c
  233. def get_subj_data(subject_id, eva):
  234. #load data for that subject
  235. data_dictionary = eva.load_data_from_file(subject_id, keys=['result','cluster_thresholded','input_features'],
  236. split_hemis=True, )
  237. features_vals = data_dictionary['input_features']
  238. predictions = data_dictionary['cluster_thresholded']
  239. #find thresholds used if two thresholds
  240. if isinstance(eva.threshold, np.ndarray):
  241. if max(data_dictionary['result']['left'].max(), data_dictionary['result']['right'].max()) > eva.threshold[1]:
  242. threshold_text = "high confidence cluster"
  243. else :
  244. threshold_text = "No high confidence cluster\nLow confidence cluster given instead"
  245. else:
  246. threshold_text = ""
  247. #find clusters and load saliencies and confidence
  248. list_clust = {}
  249. confidences = {}
  250. saliencies = {}
  251. for hemi in ['left','right']:
  252. list_clust[hemi] = set(predictions[hemi])
  253. list_clust[hemi].remove(0.0)
  254. keys = [f'saliencies_{cl}' for cl in list_clust[hemi]] + [f'mask_salient_{cl}' for cl in list_clust[hemi]]
  255. saliencies.update(eva.load_data_from_file(subject_id,
  256. keys=keys,
  257. split_hemis=True))
  258. for cl in list_clust[hemi]:
  259. mask_salient = saliencies[f'mask_salient_{cl}'][hemi].astype(bool)
  260. confidence_cl_salient = data_dictionary['result'][hemi][mask_salient].max()
  261. confidences[f'confidence_{cl}'] = confidence_cl_salient
  262. return list_clust, features_vals, predictions, threshold_text, saliencies, confidences
  263. def get_info_soft( subject_id, harmo_code, exp):
  264. ''' Report information of software (e.g Freesurfer) '''
  265. from meld_graph import __version__
  266. #find MELD version
  267. if __version__ != None:
  268. meld_version = __version__
  269. else:
  270. meld_version = "Unknown"
  271. #find Freesurfer and Fastsurfer version
  272. fs_scripts = os.path.join(MELD_DATA_PATH, 'output','fs_outputs', subject_id, 'scripts')
  273. if os.path.isfile(os.path.join(fs_scripts,'build-stamp.txt')):
  274. with open(os.path.join(fs_scripts,'build-stamp.txt')) as f:
  275. FS_version = f.readlines()[0].strip()
  276. if os.path.isfile(os.path.join(fs_scripts,'deep-seg.log')):
  277. Fastsurfer_use = 'True'
  278. else:
  279. Fastsurfer_use = 'False'
  280. else:
  281. FS_version = 'Unknown'
  282. Fastsurfer_use = 'Unknown'
  283. #find model used
  284. model_name = exp.network_parameters['name']
  285. #use harmonisation
  286. if harmo_code == 'noHarmo':
  287. harmo = "No"
  288. else:
  289. harmo = "Yes"
  290. text = "\n".join((
  291. "Information about MELD software:",
  292. f"MELD package version: {meld_version}",
  293. f"MELD model used: {model_name}",
  294. "",
  295. "Information about segmentation software:",
  296. f"Freesurfer version: {FS_version}",
  297. f"Use of FastSurfer: {Fastsurfer_use}",
  298. "",
  299. "Information about features preprocessing:",
  300. f"Harmonisation of the feature: {harmo}",
  301. f"Harmonisation code: {harmo_code}",
  302. "",
  303. "Information about MELD project:",
  304. f"Harmonisation of the feature: {harmo}",
  305. f"Harmonisation code: {harmo_code}",
  306. ))
  307. return text
  308. def get_info_meld():
  309. ''' Report information on MELD'''
  310. nl = "\n\n"
  311. citation = ("Ripart Mathilde, Hannah Spitzer et al. 2025. " "Detection of Epileptogenic Focal Cortical Dysplasia Using Graph Neural Networks: A MELD Study. "
  312. "JAMA Neurology, February. ")
  313. return [
  314. (nl + nl + "MELD Graph is available on Github: ", ""),
  315. ("https://github.com/MELDProject/meld_graph", "https://github.com/MELDProject/meld_graph"),
  316. (nl + "For instructions on how to interpret MELD Graph results please read ", ""),
  317. ("https://meld-graph.readthedocs.io/en/latest/interpret_results.html",
  318. "https://meld-graph.readthedocs.io/en/latest/interpret_results.html"),
  319. (nl + "For more information about how MELD Graph was developed, read our paper: ", ""),
  320. ("https://jamanetwork.com/journals/jamaneurology/fullarticle/2830410",
  321. "https://jamanetwork.com/journals/jamaneurology/fullarticle/2830410"),
  322. (nl + "For more information about the MELD project, have a look at our website: ", ""),
  323. ("https://meldproject.github.io/", "https://meldproject.github.io/"),
  324. (nl + "If using MELD Graph please cite: " + citation, ""),
  325. ("https://doi.org/10.1001/jamaneurol.2024.5406", "https://doi.org/10.1001/jamaneurol.2024.5406"),
  326. (nl + "For any questions regarding the MELD project please contact:", ""), ("[email hidden]", "[email hidden]"),
  327. ]
  328. def get_t1_file(subject_id, subject_dir):
  329. '''
  330. return path of T1 if BIDs format or MELD format
  331. TODO : improve flexibility of BIDS
  332. '''
  333. t1_files_MELD = glob.glob(os.path.join(subject_dir, "T1", "*.nii*"))
  334. t1_files_bids = glob.glob(os.path.join(subject_dir, "anat", "*T1*.nii*"))
  335. if len(t1_files_MELD)==1:
  336. t1_path= t1_files_MELD[0]
  337. print(get_m(f'T1 file used : {t1_path} ', subject_id, 'INFO'))
  338. elif len(t1_files_MELD)>1:
  339. print(get_m(f'Find too much volumes for T1. Check and remove the additional volumes with same key name', subject_id, 'WARNING'))
  340. return None
  341. elif len(t1_files_bids)==1:
  342. t1_path = t1_files_bids[0]
  343. print(get_m(f'T1 file used : {t1_path} ', subject_id, 'INFO'))
  344. elif len(t1_files_bids)>1:
  345. print(get_m(f'Find too much volumes for T1. Check and remove the additional volumes with same key name', subject_id, 'WARNING'))
  346. return None
  347. else:
  348. print(get_m(f'Could not find any T1w nifti file. Please ensure your data are in MELD or BIDS format', subject_id, 'ERROR'))
  349. return None
  350. return t1_path
  351. def return_ith(num):
  352. if num > 9:
  353. secondToLastDigit = str(num)[-2]
  354. if secondToLastDigit == '1':
  355. return f'{int(num)}th'
  356. lastDigit = num % 10
  357. if (lastDigit == 1):
  358. return f'{int(num)}st'
  359. elif (lastDigit == 2):
  360. return f'{int(num)}nd'
  361. elif (lastDigit == 3):
  362. return f'{int(num)}rd'
  363. else:
  364. return f'{int(num)}th'
  365. def generate_prediction_report(
  366. subject_ids, data_dir, prediction_path, output_dir, harmo_code="noHarmo",
  367. experiment_path=EXPERIMENT_PATH, hdf5_file_root=DEFAULT_HDF5_FILE_ROOT, dataset=None):
  368. ''' Create images and report of predictions on inflated brain, on native T1 accompanied with saliencies explaining the predictions
  369. inputs:
  370. subject_ids: subjects ID
  371. data_dir: data directory containing the T1. Should be "input" in MELD structure
  372. hdf_predictions: hdf5 containing the MELD predictions
  373. exp: an experiment initialised
  374. output_dir: directory to save final reports
  375. '''
  376. # setup parameters
  377. base_feature_sets = [
  378. ".on_lh.gm_FLAIR_0.5.sm3.mgh",
  379. ".on_lh.wm_FLAIR_1.sm3.mgh",
  380. ".on_lh.curv.sm3.mgh",
  381. ".on_lh.pial.K_filtered.sm20.mgh",
  382. ".on_lh.sulc.sm3.mgh",
  383. ".on_lh.thickness_regression.sm3.mgh",
  384. ".on_lh.w-g.pct.sm3.mgh",
  385. ]
  386. feature_names_sets = [
  387. "GM FLAIR (50%)",
  388. "WM FLAIR (1mm)",
  389. "Mean curvature",
  390. "Intrinsic Curvature",
  391. "Sulcal depth",
  392. "Cortical thickness",
  393. "Grey-white contrast",
  394. ]
  395. c = MeldCohort(hdf5_file_root=hdf5_file_root, dataset=dataset)
  396. surf = mt.load_mesh_geometry(os.path.join(MELD_PARAMS_PATH, SURFACE_PARTIAL))
  397. # load cmap and colors
  398. cmap, colors = load_cmap()
  399. # create evaluator
  400. exp = Experiment.from_folder(experiment_path)
  401. features = exp.data_parameters['features']
  402. eva = Evaluator(
  403. experiment=exp,
  404. save_dir = prediction_path,
  405. make_images=False,
  406. cohort=c,
  407. subject_ids=subject_ids,
  408. mode="test",
  409. thresh_and_clust=True,
  410. )
  411. for subject_id in subject_ids:
  412. # find subject directory containing T1
  413. subject = MeldSubject(subject_id, cohort=c)
  414. # create output directory
  415. output_dir_sub = os.path.join(output_dir, subject_id, "reports")
  416. os.makedirs(os.path.join(output_dir_sub), exist_ok=True)
  417. # Open their MRI data if available
  418. t1_file = get_anat_files(subject_id)['T1_path']
  419. prediction_file = glob.glob(os.path.join(output_dir, subject_id, "predictions", "prediction*"))[0]
  420. # load image
  421. imgs = {
  422. "anat": nb.load(t1_file),
  423. "pred": nb.load(prediction_file),
  424. }
  425. if len(imgs["anat"].shape) > 3:
  426. # if the input image has a 4th (time/frame) dimension of length 1,
  427. # remove it (otherwise resampling and possibly other steps will fail)
  428. imgs["anat"] = nb.funcs.squeeze_image(imgs["anat"])
  429. # # Resample and move to same shape and affine than t1
  430. imgs["pred"] = image.resample_img(
  431. imgs["pred"],
  432. target_affine=imgs["anat"].affine,
  433. target_shape=imgs["anat"].shape,
  434. interpolation="nearest",
  435. copy=True,
  436. order="F",
  437. clip=False,
  438. fill_value=0,
  439. force_resample=False,
  440. )
  441. # initialise parameter for plot
  442. fig = plt.figure(figsize=(15, 8), constrained_layout=True)
  443. if subject.has_flair:
  444. base_features = base_feature_sets
  445. feature_names = feature_names_sets
  446. else:
  447. base_features = base_feature_sets[2:]
  448. feature_names = feature_names_sets[2:]
  449. # load predictions and data subject
  450. list_clust, features_vals, predictions, threshold_text, saliencies, confidences = get_subj_data(subject_id, eva)
  451. #save info cluster
  452. df=pd.DataFrame()
  453. info_cl={}
  454. # Loop over hemi
  455. for i, hemi in enumerate(["left", "right"]):
  456. # prepare grid plot
  457. gs1 = GridSpec(2, 4, width_ratios=[1, 0.2, 1, 1], wspace=0.1, hspace=0.1)
  458. gs2 = GridSpec(2, 4, height_ratios=[1, 3], width_ratios=[1, 1, 0.8, 2], wspace=0.1)
  459. gs3 = GridSpec(2, 1, hspace=0)
  460. # plot predictions on inflated brain
  461. im1, im2 = create_surface_plots(surf, prediction=predictions[hemi], c=c)
  462. if hemi == "right":
  463. im1 = im1[:, ::-1]
  464. im2 = im2[:, ::-1]
  465. ax = fig.add_subplot(gs1[i, 2])
  466. ax.imshow(im1)
  467. ax.axis("off")
  468. title = 'Left hemisphere' if hemi=='left' else 'Right hemisphere'
  469. ax.set_title(title, loc="left", fontsize=20)
  470. ax = fig.add_subplot(gs1[i, 3])
  471. ax.imshow(im2)
  472. ax.axis("off")
  473. # initiate params for saliencies
  474. prefixes = [".combat", ".inter_z.intra_z.combat", ".inter_z.asym.intra_z.combat"]
  475. # lims = 50
  476. # norm = mpl.colors.Normalize(vmin=-lims, vmax=lims)
  477. cmap = mpl.colors.LinearSegmentedColormap.from_list(
  478. "grpr",
  479. colors=[
  480. "#276419",
  481. "#FFFFFF",
  482. "#8E0152",
  483. ],
  484. )
  485. if harmo_code is 'noHarmo':
  486. labels = ["Smoothed", "Normalised", "Asymmetry"]
  487. else:
  488. labels = ["Harmonised", "Normalised", "Asymmetry"]
  489. hatching = ["\\\\", "//", "--"]
  490. # loop over clusters
  491. for cluster in list_clust[hemi]:
  492. fig2 = plt.figure(figsize=(17, 9))
  493. info_cl['cluster']=cluster
  494. # get and plot saliencies
  495. saliencies_cl = saliencies[f'saliencies_{cluster}'][hemi]
  496. saliencies_cl = saliencies_cl * (NVERT/2)
  497. # plot prediction and salient vertices
  498. mask = np.array([predictions[hemi] == cluster])[0]
  499. mask_salient = saliencies[f'mask_salient_{cluster}'][hemi].astype(bool)
  500. mask_comb = mask.astype(int)+mask_salient.astype(int)
  501. #get max and mean saliencies
  502. lims_saliencies_cl = 1.1*np.max([np.max(np.mean(saliencies_cl[mask_salient], axis=0)),-np.min(np.mean(saliencies_cl[mask_salient], axis=0))])
  503. norm = mpl.colors.Normalize(vmin=-lims_saliencies_cl, vmax=lims_saliencies_cl)
  504. m = cm.ScalarMappable(norm=norm, cmap=cmap)
  505. im1, im2 = create_surface_plots(surf, prediction=mask_comb, c=c, base_size=10)
  506. if hemi == "right":
  507. im1 = im1[:, ::-1]
  508. im2 = im2[:, ::-1]
  509. ax2 = fig2.add_subplot(gs2[1, 0])
  510. ax2.imshow(im1)
  511. ax2.axis("off")
  512. ax2 = fig2.add_subplot(gs2[1, 1])
  513. ax2.imshow(im2)
  514. ax2.axis("off")
  515. # ax2.set_title('Predicted cluster and\n20% most salient vertices', loc="left", fontsize=15)
  516. ax2 = fig2.add_subplot(gs2[:, 3],)
  517. for pr, prefix in enumerate(prefixes):
  518. cur_data = np.zeros(len(base_features))
  519. cur_err = np.zeros(len(base_features))
  520. saliency_data = np.zeros(len(base_features))
  521. for b, bf in enumerate(base_features):
  522. cur_data[b] = np.mean(
  523. features_vals[hemi][mask_salient, features.index(prefix + bf)]
  524. )
  525. cur_err[b] = np.std(
  526. features_vals[hemi][mask_salient, features.index(prefix + bf)]
  527. )
  528. saliency_data[b] = np.mean(
  529. saliencies_cl[mask_salient ,features.index(prefix + bf)]
  530. )
  531. #add fingerprints
  532. info_cl[labels[pr] + ' ' + feature_names[b] +' mean'] = cur_data[b]
  533. info_cl[labels[pr] + ' ' + feature_names[b] +' std'] = cur_err[b]
  534. info_cl[labels[pr] + ' ' + feature_names[b] +' saliency'] = saliency_data[b]
  535. ax2.barh(
  536. y=np.array(range(len(base_features))) - pr * 0.3,
  537. width=cur_data,
  538. hatch=hatching[pr],
  539. height=0.3,
  540. edgecolor="k",
  541. xerr=cur_err,
  542. label=labels[pr],
  543. color=m.to_rgba(saliency_data),
  544. )
  545. limvals = np.max([np.max(cur_data+cur_err),-np.min(cur_data-cur_err)])+0.5
  546. ax2.set_xlim([-limvals, limvals])
  547. # add z-scores = 2 dash lines
  548. # ax2.plot([-2,-2],[-1,len(base_features)], '--', color='red')
  549. # ax2.plot([2,2],[-1,len(base_features)], '--', color='red')
  550. # ax2.set_xlim([-8, 8])
  551. # ax2.set_xticks([])
  552. ax2.set_yticks(np.array(range(len(base_features))) - 0.23)
  553. ax2.set_yticklabels(feature_names, fontsize=16)
  554. ax2.set_xlabel("Z score", fontsize=16)
  555. ax2.legend(loc="upper center", bbox_to_anchor=(0.5, 1.17), fontsize=16)
  556. fig2.colorbar(m, ax=ax2,).set_label(label='Saliency',size=18,weight='bold')
  557. ax2.set_autoscale_on(True)
  558. ## display info cluster
  559. # get size
  560. size_clust = np.sum(c.surf_area[predictions[hemi] == cluster]) / 100
  561. size_clust = round(size_clust, 3)
  562. info_cl['size'] = size_clust
  563. # get location
  564. location = get_cluster_location(predictions[hemi] == cluster)
  565. info_cl['hemi'] = hemi
  566. info_cl['location'] = location
  567. # get confidence
  568. confidence = round(confidences[f'confidence_{cluster}']* 100,2)
  569. info_cl['confidence'] = confidence
  570. info_cl['high_low_threshold']=threshold_text
  571. # plot info in text box in upper left in axes coords
  572. textstr = "\n".join(
  573. (
  574. f" Cluster on the {hemi} hemisphere",
  575. " ",
  576. f" Cluster size = {size_clust} cm2",
  577. " ",
  578. f" Cortical region = {location}",
  579. " ",
  580. f" Confidence score = {confidence}%",
  581. " ",
  582. f" Voxel value on NIfTI = {int(cluster)} & {int(cluster)*100} (salient)",
  583. )
  584. )
  585. props = dict(boxstyle="round", facecolor=colors[int(cluster)], alpha=0.5)
  586. ax2 = fig2.add_subplot(gs2[0, 0:2])
  587. ax2.text(0.05, 0.95, textstr, transform=ax2.transAxes, fontsize=18, verticalalignment="top", bbox=props)
  588. ax2.axis("off")
  589. #fig2.tight_layout()
  590. fig2.savefig(f"{output_dir_sub}/saliency_{subject.subject_id}_{hemi}_c{int(cluster)}.png")
  591. # display MRI images
  592. fig3 = plt.figure(figsize=(12, 6))
  593. ax3 = fig3.add_subplot(gs3[0])
  594. min_v = cluster*100 - 1
  595. max_v = cluster*100 + 1
  596. mask = image.math_img(f"(img < {max_v}) & (img > {min_v})", img=imgs[f"pred"])
  597. coords = plotting.find_xyz_cut_coords(mask)
  598. vmax = np.percentile(imgs["anat"].get_fdata(), 99)
  599. # display cluster on MRI volume
  600. display3 = plotting.plot_anat(
  601. t1_file, colorbar=False, cut_coords=coords,
  602. draw_cross=True, radiological=True, annotate=True,
  603. figure=fig3, axes=ax3, vmax=vmax
  604. )
  605. data = imgs["pred"].get_fdata()
  606. map_img = new_img_like(imgs["pred"], as_ndarray((data==cluster) | (data==cluster*100)).astype(float), imgs["pred"].affine)
  607. display3.add_contours(
  608. map_img,
  609. levels=[0.5],
  610. colors=["red"],
  611. filled=True,
  612. alpha=0.7,
  613. linestyles="solid",
  614. )
  615. # display cluster salient vertices
  616. map_img = new_img_like(imgs["pred"], as_ndarray(data==cluster*100).astype(float), imgs["pred"].affine)
  617. display3.add_contours(
  618. map_img,
  619. levels=[0.5],
  620. colors=["yellow"],
  621. filled=True,
  622. alpha=0.7,
  623. linestyles="solid",
  624. )
  625. # display just raw MRI
  626. ax4 = fig3.add_subplot(gs3[1])
  627. display4 = plotting.plot_anat(
  628. t1_file, colorbar=False, cut_coords=coords,
  629. draw_cross=False, radiological=True, annotate=True,
  630. figure=fig3, axes=ax4, vmax=vmax
  631. )
  632. for display in [display3, display4]:
  633. for cut_ax in display.axes.values():
  634. slices_x = np.linspace(cut_ax.ax.get_xlim()[0], cut_ax.ax.get_xlim()[1],100)
  635. cut_ax.ax.set_xlim(slices_x[12], slices_x[-12])
  636. slices_y = np.linspace(cut_ax.ax.get_ylim()[0], cut_ax.ax.get_ylim()[1],100)
  637. cut_ax.ax.set_ylim(slices_y[15], slices_y[-15])
  638. fig3.tight_layout()
  639. fig3.savefig(f"{output_dir_sub}/mri_{subject.subject_id}_{hemi}_c{int(cluster)}.png")
  640. im = Image.open(f"{output_dir_sub}/mri_{subject.subject_id}_{hemi}_c{int(cluster)}.png")
  641. im1 = trim(im)
  642. im2 = im1.convert("RGBA")
  643. im2.save(f"{output_dir_sub}/mri_{subject.subject_id}_{hemi}_c{int(cluster)}.png")
  644. # Add info to df
  645. df = pd.concat([df,pd.DataFrame([info_cl])])
  646. # Add information subject in text box
  647. n_clusters = len(list_clust["left"]) + len(list_clust["right"])
  648. ax = fig.add_subplot(gs1[0, 0])
  649. textstr = "\n".join((f"Patient {subject.subject_id}", " ", f"Number of predicted clusters = {n_clusters}"))
  650. # place a text box in upper left in axes coords
  651. props = dict(boxstyle="round", facecolor="gray", alpha=0.5)
  652. ax.text(0.05, 0.95, textstr, transform=ax.transAxes, fontsize=16, verticalalignment="top", bbox=props)
  653. ax.axis("off")
  654. # save overview figure
  655. fig.savefig(f"{output_dir_sub}/inflatbrain_{subject.subject_id}.png")
  656. # save info subject
  657. df.to_csv(f"{output_dir_sub}/info_clusters_{subject.subject_id}.csv")
  658. # create PDF overview
  659. pdf = PDF() # pdf object
  660. pdf = PDF(orientation="P") # landscape
  661. pdf = PDF(unit="mm") # unit of measurement
  662. pdf = PDF(format="A4") # page format. A4 is the default value of the format, you don't have to specify it.
  663. logo = os.path.join(SCRIPTS_DIR, "docs", "images", "MELD_logo.png")
  664. text_info_1 = "Information: \n The MRI data of this patient has been processed through the MELD Graph surface-based FCD detection algorithm. \n Page 1 of this report will show all detected clusters on an inflated view of the brain. \n Subsequent pages characterise individual predicted clusters sorted in descending confidence. \n The last page summarises the software version used to create this report."
  665. text_info_2 = "The following pages characterise each cluster according to: \n -The hemisphere the cluster is on \n -The cortical surface area of the cluster \n -The cortical region in which the cluster-centre is located \n -MELD Graph's confidence in the cluster prediction \n -The average abnormality score - Z-score - of cortical morphological features within the cluster. \n -The saliency of each feature to the network - if a feature is brighter pink, that feature was more important to the network. \n \n For more information, please read the Guide to using the MELD Graph surface-based FCD detection."
  666. disclaimer = "Disclaimer: The MELD Graph surface-based FCD detection algorithm is intended for research purposes only and has not been reviewed or approved by the Medicines and Healthcare products Regulatory Agency (MHRA),European Medicine Agency (EMA) or by any other agency. Any clinical application of the software is at the sole risk of the party engaged in such application. There is no warranty of any kind that the software will produce useful results in any way. Use of the software is at the recipient's own risk."
  667. footer_txt = "This report was automatically generated by software by Mathilde Ripart, Hannah Spitzer, Sophie Adler and Konrad Wagstyl on behalf of the MELD Project"
  668. text_info_3 = get_info_soft(subject.subject_id, harmo_code, exp)
  669. text_info_4 = get_info_meld()
  670. #### create first page with disclaimer and intended use
  671. # add page
  672. pdf.add_page()
  673. # add line contours
  674. pdf.lines()
  675. # add header
  676. pdf.custom_header(logo, txt1="MELD report", txt2=f"")
  677. # add 1st page report image
  678. im_1stpage = os.path.join(SCRIPTS_DIR, "docs", "images", f"1st_page_report.jpg")
  679. pdf.image(im_1stpage, 10, 40, link='', type='', w=190, h=220)
  680. # add footer date
  681. pdf.custom_footer(footer_txt)
  682. #### create main page with overview on inflated brain
  683. # add page
  684. pdf.add_page()
  685. # add line contours
  686. pdf.lines()
  687. # add header
  688. pdf.custom_header(logo, txt1="MELD report", txt2=f"Patient ID: {subject.subject_id}")
  689. # add info box
  690. pdf.info_box(text_info_1)
  691. # add disclaimer box
  692. pdf.disclaimer_box(disclaimer)
  693. # add image
  694. pdf.subtitle_inflat()
  695. im_inflat = os.path.join(output_dir_sub, f"inflatbrain_{subject.subject_id}.png")
  696. pdf.image(im_inflat, 5, 100, link='', type='', w=190, h=297/3)
  697. # add info cluster analysis
  698. pdf.info_box_clust(text_info_2)
  699. # add footer date
  700. pdf.custom_footer(footer_txt)
  701. #### order display in function of confidence
  702. clusters = np.array(range(1, n_clusters + 1))
  703. confidences_order = np.array(np.argsort([confidences[f'confidence_{float(cl)}'] for cl in clusters]))
  704. clusters = clusters[confidences_order[::-1]]
  705. #### Create page for each cluster with MRI view and saliencies
  706. for i, cluster in enumerate(clusters):
  707. # add page
  708. pdf.add_page()
  709. # add line contours
  710. pdf.lines()
  711. # add header
  712. pdf.custom_header(logo, txt1="MRI view & saliencies", txt2=f"{return_ith(i+1)} cluster")
  713. # add image
  714. im_mri = glob.glob(os.path.join(output_dir_sub, f"mri_{subject.subject_id}_*_c{cluster}.png"))[0]
  715. # pdf.imagey(im_mri, 50)
  716. #add segmentation figure left
  717. pdf.image(im_mri, 5, 50, link='', type='', w=190, h=297/3)
  718. # add image
  719. im_sal = glob.glob(os.path.join(output_dir_sub, f"saliency_{subject.subject_id}_*_c{cluster}.png"))[0]
  720. # pdf.imagey(im_sal, 150)
  721. pdf.image(im_sal, 5, 150, link='', type='', w=190, h=297/3)
  722. # add footer date
  723. pdf.custom_footer(footer_txt)
  724. #### create last page with info for reproducibility
  725. # add page
  726. pdf.add_page()
  727. # add line contours
  728. pdf.lines()
  729. # add header
  730. pdf.custom_header(logo, txt1="MELD report", txt2=f"Patient ID: {subject.subject_id}")
  731. # add info box
  732. pdf.info_box(text_info_3)
  733. # add info box
  734. pdf.info_box_links(text_info_4)
  735. # add footer date
  736. pdf.custom_footer(footer_txt)
  737. # save pdf
  738. file_path = os.path.join(output_dir_sub, f"MELD_report_{subject.subject_id}.pdf")
  739. pdf.output(file_path, "F")
  740. print(get_m(f'MELD prediction report ready at {file_path}', subject_id, 'INFO'))
  741. if __name__ == "__main__":
  742. # Set up experiment
  743. parser = argparse.ArgumentParser(description="create mgh file with predictions from hdf5 arrays")
  744. parser.add_argument(
  745. "--experiment_folder",
  746. help="Experiments folder",
  747. )
  748. parser.add_argument(
  749. "--experiment_name",
  750. help="subfolder to use, typically the ensemble model",
  751. default="ensemble_iteration",
  752. )
  753. parser.add_argument("--fold", default=None, help="fold number to use (by default all)")
  754. parser.add_argument(
  755. "--data_dir", default="", help="folder containing the input data T1 and FLAIR"
  756. )
  757. parser.add_argument(
  758. "--output_dir", default="", help="folder containing the output prediction and reports"
  759. )
  760. parser.add_argument("-ids","--subject_list",
  761. default="",
  762. help="Relative path to subject List containing id and site_code.",
  763. required=False,
  764. )
  765. parser.add_argument('-id','--id',
  766. help='Subjects ID',
  767. required=False,
  768. default=None)
  769. parser.add_argument("-harmo_code","--harmo_code",
  770. default="noHarmo",
  771. help="Harmonisation code",
  772. required=False,
  773. )
  774. args = parser.parse_args()
  775. fold=args.fold
  776. data_dir=args.data_dir
  777. output_dir=args.output_dir
  778. subject_ids = np.loadtxt(args.list_ids, dtype="str", ndmin=1)
  779. harmo_code = str(args.harmo_code)
  780. experiment_path = os.path.join(MELD_DATA_PATH, args.experiment_folder)
  781. experiment_name=args.experiment_name
  782. # initialise variables
  783. if harmo_code == "noHarmo":
  784. experiment_path = os.path.join(EXPERIMENT_PATH, MODEL_PATH.format('nocombat'))
  785. else:
  786. experiment_path = os.path.join(EXPERIMENT_PATH, MODEL_PATH.format('combat'))
  787. if args.list_ids:
  788. try:
  789. sub_list_df = pd.read_csv(args.list_ids)
  790. subject_ids=np.array(sub_list_df.participant_id.values)
  791. except:
  792. subject_ids=np.array(np.loadtxt(args.list_ids, dtype='str', ndmin=1))
  793. elif args.id:
  794. subject_ids=np.array([args.id])
  795. else:
  796. print('No ids were provided')
  797. subject_ids=None
  798. # select predictions files
  799. if fold == None:
  800. hdf_predictions = os.path.join(experiment_path, "results", f"predictions_{experiment_name}.hdf5")
  801. else:
  802. hdf_predictions = os.path.join(experiment_path, f"fold_{fold}", "results", f"predictions_{experiment_name}.hdf5")
  803. # Provide models parameter
  804. exp = Experiment(experiment_path= experiment_path, experiment_name=experiment_name)
  805. generate_prediction_report(
  806. subject_ids,
  807. data_dir=data_dir,
  808. hdf_predictions=hdf_predictions,
  809. exp=exp,
  810. output_dir=output_dir,
  811. )

plot_prediction_report.py at commit 45f332d, under other · at the source

Overview

  1. Neuroscience and Human Genetics Department, Meyer Children‘s Hospital IRCCS, Florence, Italy
  2. University of Florence, Florence, Italy
  3. IMAGO7 Foundation, Pisa, Italy
  4. Neuroradiology Unit, Department of Translational Research on New Technologies in Medicine and Surgery, University of Pisa, Pisa, Italy
  5. Laboratory of Medical Physics and Magnetic Resonance, IRCCS Fondazione Stella Maris, Pisa, Italy
Journal: Neuroradiology, volume 68, issue 8, pages 2241-2256
Dates: received 11 February 2026; accepted 29 June 2026; published online 6 July 2026; in print 2026
Type: Research article · Language: English
License: CC BY
Identifiers: DOI 10.1007/s00234-026-04103-8 · PMID 42406029 · PMCID PMC13577987 · OpenAlex W7167502836
Open access: hybrid, a free copy (OpenAlex)
Status: code verified
Categories: structural MRI / diffusion (modality), EEG (modality), human (organism), epilepsy (population)
Methods: Statistics, Preprocessing, fMRI & imaging
Keywords: Focal epilepsy, Focal cortical dysplasia, MRI, Computational MRI, Artificial intelligence
MeSH: Epilepsies, Partial*, Focal Cortical Dysplasia*, Image Interpretation, Computer-Assisted*, Magnetic Resonance Imaging*, Adolescent, Adult, Child, Child, Preschool, Electroencephalography, Female, Graph Neural Networks, Humans, Male (* major topic)
Topic: Epilepsy research and treatment (Psychiatry and Mental health, Medicine), according to OpenAlex
Funding: Università degli Studi di Firenze
Citations: not cited yet (Europe PMC); 51 references in the paper

Abstract

Purpose: MRI detection of subtle focal cortical dysplasia (FCD)-like abnormalities remains challenging in focal epilepsy. Higher signal-to-noise ratio and spatial resolution offered by ultra-high-field 7T MRI and surface-based graph-neural-network (GNN) analysis may improve detection of subtle cortical abnormalities. We evaluated whether combining 7T MRI with a surface-based GNN classifier improves lesion detection in focal epilepsy of suspected structural origin.

Methods: We analyzed paired 7T and 3T MRI datasets from 87 patients with focal epilepsy (78.1% pediatric) and 10 internal healthy control individuals. We processed T1-weighted and Fluid-Attenuated-Inversion-Recovery MRI using a surface-based framework and a pre-trained GNN classifier developed within the Multi-centre-Epilepsy-Lesion-Detection project. We compared classifier outputs with expert visual MRI assessment, clinical and surface electroencephalography (EEG) localization (all patients), stereo-EEG (ten patients) and histopathological (17 patients) findings. We evaluated diagnostic yield and lesion conspicuity, and performed within-subject comparisons between 7T and 3T.

Results: Following quality controls, we included 70 patients. The 7T MRI-based classifier identified lesion clusters concordant with visual 3T MRI and electroclinical localization in 25/37 (67.6%) MRI-positive patients, electroclinical-concordant clusters in 15/33 (45.4%) 3T MRI-negatives, stereo-EEG-concordant clusters in 7/10 (70.0%) patients and surgically-concordant clusters in 11/17 (64.7%). Among classifier-positive patients (40/70, 57.1%), 7T allowed detection of previously hidden lesions in 15/40 (37.5%) patients, and improved detection of known lesions in 11/40 (27.5%).

Conclusion: Combining 7T MRI with surface-based GNN analysis improves detection and characterization of FCD-like abnormalities in focal epilepsy, particularly in patients with unrevealing 3T MRI, supporting the adoption of advanced neuroimaging in presurgical epilepsy assessment.

Supplementary Information: The online version contains supplementary material available at https://doi.org/10.1007/s00234-026-04103-8.

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

MELDProject/meld_graph

License: other
State: the link answers, verified on 27 September 2026
Evidence: files inventoried
Commit: 45f332d87d609a93cc965492cd684ab38df73a62, 23 September 2026
Languages: Python (93), Jupyter (10), Shell (3)
Size: 195 files, 106 scripts
Software Heritage: not archived
Found in: the text, “7 T and 3 T MRI surface-based feature assessment”
Holds: README, license file, environment (compose.yml, Dockerfile, environment-mac.yml, environment.yml, pyproject.toml, setup.py, docs/requirements.txt), tests, continuous integration, documentation, 10 notebooks
Not found: CITATION.cff
Tools: NumPy (68 files), pandas (29 files), NiBabel (26 files), h5py (19 files), SciPy (19 files), Matplotlib (18 files), PyTorch (15 files), seaborn (8 files), Pillow (6 files), FreeSurfer (5 files), PyTorch Geometric (5 files), scikit-learn (5 files), statsmodels (3 files), neuroCombat (2 files), Nilearn (2 files), PyBIDS (1 file), statannotations (1 file)
Availability: 1 check, the latest on 27 September 2026: the link answers
  • 27 September 2026: the link answers
108 files

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

The data are available from the authors upon reasonable request.

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, 11 authors, 5 keywords, 13 MeSH terms, 1 funder, 51 references.

Cite

This paper

Lenge, M., Fiori, S., Cappelletto, P., Droghini, A., Barbi, E., Buccoliero, A. M., Donatelli, G., Tosetti, M., Giordano, F., Barba, C., & Guerrini, R. (2026). Enhanced detection of subtle cortical abnormalities in focal epilepsy using 7 T MRI surface-based models and graph neural networks. Neuroradiology, 68(8), 2241-2256. https://doi.org/10.1007/s00234-026-04103-8

BibTeX

@article{lenge2026enhanced,
author = {Lenge, Matteo and Fiori, Simona and Cappelletto, Pietro and Droghini, Andrea and Barbi, Elisa and Buccoliero, Anna Maria and Donatelli, Graziella and Tosetti, Michela and Giordano, Flavio and Barba, Carmen and Guerrini, Renzo},
title = {{Enhanced detection of subtle cortical abnormalities in focal epilepsy using 7 T MRI surface-based models and graph neural networks}},
journal = {Neuroradiology},
year = {2026},
month = jul,
volume = {68},
number = {8},
pages = {2241--2256},
publisher = {Springer Science+Business Media},
issn = {0028-3940},
doi = {10.1007/s00234-026-04103-8},
url = {https://doi.org/10.1007/s00234-026-04103-8},
pmid = {42406029},
pmcid = {PMC13577987}
}

RIS

TY - JOUR
AU - Lenge, Matteo
AU - Fiori, Simona
AU - Cappelletto, Pietro
AU - Droghini, Andrea
AU - Barbi, Elisa
AU - Buccoliero, Anna Maria
AU - Donatelli, Graziella
AU - Tosetti, Michela
AU - Giordano, Flavio
AU - Barba, Carmen
AU - Guerrini, Renzo
TI - Enhanced detection of subtle cortical abnormalities in focal epilepsy using 7 T MRI surface-based models and graph neural networks
T2 - Neuroradiology
J2 - Neuroradiology
PY - 2026
DA - 2026/07/06
VL - 68
IS - 8
SP - 2241
EP - 2256
SN - 0028-3940
PB - Springer Science+Business Media
DO - 10.1007/s00234-026-04103-8
UR - https://doi.org/10.1007/s00234-026-04103-8
LA - en
ER -

CSL-JSON

{
"id": "10.1007/s00234-026-04103-8",
"type": "article-journal",
"title": "Enhanced detection of subtle cortical abnormalities in focal epilepsy using 7 T MRI surface-based models and graph neural networks",
"container-title": "Neuroradiology",
"author": [
{
"family": "Lenge",
"given": "Matteo"
},
{
"family": "Fiori",
"given": "Simona"
},
{
"family": "Cappelletto",
"given": "Pietro"
},
{
"family": "Droghini",
"given": "Andrea"
},
{
"family": "Barbi",
"given": "Elisa"
},
{
"family": "Buccoliero",
"given": "Anna Maria"
},
{
"family": "Donatelli",
"given": "Graziella"
},
{
"family": "Tosetti",
"given": "Michela"
},
{
"family": "Giordano",
"given": "Flavio"
},
{
"family": "Barba",
"given": "Carmen"
},
{
"family": "Guerrini",
"given": "Renzo"
}
],
"container-title-short": "Neuroradiology",
"volume": "68",
"issue": "8",
"page": "2241-2256",
"DOI": "10.1007/s00234-026-04103-8",
"PMID": "42406029",
"PMCID": "PMC13577987",
"ISSN": "0028-3940",
"publisher": "Springer Science+Business Media",
"URL": "https://doi.org/10.1007/s00234-026-04103-8",
"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.64898/2026.08.18.26360725 [code]
Temporal pole blurring in hippocampal sclerosis reflects seizure-disrupted myelination
Journal: medRxiv (preprint)
In common: neuroCombat, PyBIDS, statannotations, 14 other tools, epilepsy, structural MRI / diffusion, 5 references
[2] doi:10.64898/2026.04.02.26349812 [code]
9.4 Tesla MRI in focal epilepsy patients with high-resolution surface-based profiling of focal cortical dysplasias
Journal: medRxiv (preprint)
In common: FreeSurfer, Nilearn, NiBabel, 5 other tools, epilepsy, structural MRI / diffusion, 9 references
[3] doi:10.1002/epi4.70336 [code]
Quantifying extra-lesional interhemispheric cortical asymmetry in focal cortical dysplasia type II.
Journal: Epilepsia open
In common: statannotations, Nilearn, NiBabel, 7 other tools, epilepsy, structural MRI / diffusion, 7 references
[4] doi:10.1016/j.isci.2026.117446 [code]
Volume-inflation registration (INFREG) for morphometric analysis of human focal cortical dysplasia type II.
Journal: iScience
In common: PyTorch Geometric, FreeSurfer, NiBabel, 5 other tools, epilepsy, structural MRI / diffusion, 7 references
[5] doi:
Structural and Quantitative MRI Signal Features in Pediatric Focal Cortical Dysplasia: Clinical Relevance.
Journal: Physiological research
In common: epilepsy, structural MRI / diffusion, 10 references
[6] doi:10.1093/braincomms/fcag253 [code]
Disease detection and classification in temporal lobe epilepsy: step-wise versus simultaneous AI decision models in a multisite neuroimaging study.
Journal: Brain communications
In common: Pillow, PyTorch, seaborn, 5 other tools, epilepsy, structural MRI / diffusion, 2 references, author Matteo Lenge
[7] doi:10.21203/rs.3.rs-9326213/v1 [code]
Multi-task fMRI outperforms resting-state fMRI for revealing task-invariant organization of the human brain
Journal: Research Square (preprint)
In common: statannotations, FreeSurfer, Nilearn, 10 other tools, 1 reference
[8] doi:10.64898/2026.03.09.710558 [code]
Multi-task fMRI outperforms resting-state fMRI for revealing task-invariant organization of the human brain
Journal: bioRxiv (preprint)
In common: statannotations, FreeSurfer, Nilearn, 10 other tools, 1 reference
[9] doi:10.7554/elife.107933 [code]
Modality-agnostic decoding of vision and language from fMRI.
Journal: eLife
In common: FreeSurfer, Nilearn, h5py, 10 other tools, 2 references
[10] doi:10.1002/alz.71649 [code]
Postmortem brain MRI reveals differential associations of subcortical and limbic volumes with cortical thinning and neurodegenerative pathologies.
Journal: Alzheimer's & dementia : the journal of the Alzheimer's Association
In common: statannotations, FreeSurfer, Pillow, 9 other tools, structural MRI / diffusion, 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.