OSCR

MRI Deep Learning for Differentiating Glioblastoma, IDH Wild-type from Central Nervous System Diffuse Large B-cell Lymphoma.

Code ↔ Paper

3 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 3 matches
  1. [1] § Materials and Methods › Three-dimensional deep learning model architecture ↔ Brain_Lesion_Classifier.ipynb, lines 562–598 · score 0.90 · cosine annealing learning, AdamW, rate scheduler, cross entropy, loss function, DenseNet121
  2. [2] § Materials and Methods › Three-dimensional deep learning model architecture ↔ Code_To_Share_Brain_Lesion_Classifier.ipynb, lines 688–724 · score 0.90 · cosine annealing learning, AdamW, rate scheduler, cross entropy, loss function, DenseNet121
  3. [3] § Materials and Methods › MRI preprocessing ↔ Code_To_Share_Brain_Lesion_Classifier.ipynb, lines 171–222 · score 0.67 · Federated Tumor Segmentation, preprocessing pipeline, MRI, Brain

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 · 1,057 lines · 43 KB · no license · 2 matches

  1. # %% [markdown]
  2. # This notebook includes only the pipeline used for training the model. Please note that additional support will not be provided.
  3. #
  4. # %%
  5. # Importing necessary libraries and packages for the project
  6. # Set the CUDA device for GPU computation (ensures reproducibility in multi-GPU settings)
  7. import os
  8. os.environ['CUDA_VISIBLE_DEVICES'] = 'x'
  9. # Core Python libraries for mathematical operations, statistics, and file handling
  10. import math # Provides mathematical functions
  11. import statistics # Provides functions for statistical calculations
  12. import json # To handle JSON data for input/output operations
  13. import itertools # Tools for iterating over combinations and permutations
  14. import shutil # For high-level file operations
  15. import random # Random number generation for data sampling and shuffling
  16. # Scientific computing and data handling libraries
  17. import numpy as np # Numerical operations, arrays, and linear algebra
  18. import pandas as pd # Data manipulation and analysis using DataFrames
  19. # Visualization libraries
  20. import matplotlib.pyplot as plt # Visualization of data, metrics, and results
  21. # Progress bar visualization for loops and computations
  22. from tqdm.auto import tqdm # Provides a progress bar for loops
  23. # Libraries for machine learning model development
  24. from sklearn.model_selection import train_test_split, StratifiedKFold # Data splitting and cross-validation
  25. from sklearn.metrics import (
  26. roc_auc_score, # ROC-AUC metric for classification performance
  27. f1_score, # F1-score for classification evaluation
  28. matthews_corrcoef, # Matthews Correlation Coefficient for binary classification
  29. average_precision_score # Average precision metric for precision-recall curves
  30. )
  31. from sklearn.utils.class_weight import compute_class_weight # Computes class weights for imbalanced datasets
  32. # MONAI: A specialized framework for medical imaging AI
  33. from monai.metrics import ROCAUCMetric, ConfusionMatrixMetric # Evaluation metrics tailored for medical imaging
  34. from monai.transforms import * # Data augmentation, preprocessing, and transformation utilities
  35. # Additional MONAI imports for deep learning and pipeline setup
  36. import monai as mn # Core MONAI library
  37. from monai.config import print_config # Prints MONAI configuration details
  38. from monai.data import DataLoader, decollate_batch # Data loaders and batch handling
  39. from monai.transforms import (
  40. Activations, Activationsd, AsDiscrete, AsDiscreted, Compose, # Activation and postprocessing transformations
  41. LoadImaged, EnsureTyped, EnsureChannelFirstd, ToTensord, # Data loading and tensor conversion
  42. NormalizeIntensityd, Resized, Spacingd, Orientationd, # Preprocessing steps: normalization and resizing
  43. RandAffineD, RandFlipd, RandSpatialCropd, RandScaleIntensityd, RandShiftIntensityd # Data augmentation
  44. )
  45. from monai.utils import set_determinism # Ensures reproducibility
  46. # Libraries for handling medical imaging formats
  47. import nibabel as nib # For reading and writing NIfTI medical image files
  48. # PyTorch: A machine learning library for building and training deep learning models
  49. import torch # Core PyTorch library
  50. from torch.utils.data import Dataset, DataLoader, WeightedRandomSampler # Dataset handling and samplers
  51. # W&B: Weights & Biases for experiment tracking and logging
  52. import wandb # Tracks model performance and logs experiments
  53. # Temporary file management, benchmarking, and timing tools
  54. import tempfile # Temporary file creation for caching data
  55. import time # Timing functions for performance benchmarking
  56. # Printing MONAI configuration to check versions and dependencies
  57. print_config()
  58. # %% [markdown]
  59. # MONAI version: 1.3.0
  60. #
  61. # Numpy version: 1.26.2
  62. #
  63. # Pytorch version: 2.1.2+cu121
  64. #
  65. # MONAI flags: HAS_EXT = False, USE_COMPILED = False, USE_META_DICT = False
  66. # MONAI rev id: 865972f7a791bf7b42efbcd87c8402bd865b329e
  67. # MONAI __file__: /home/<username>/anaconda3/envs/map_env/lib/python3.10/site-packages/monai/__init__.py
  68. #
  69. # Other dependencies:
  70. #
  71. # Pytorch Ignite version: 0.4.11
  72. #
  73. # ITK version: 5.3.0
  74. #
  75. # Nibabel version: 5.2.0
  76. #
  77. # scikit-image version: 0.22.0
  78. #
  79. # scipy version: 1.11.4
  80. #
  81. # Pillow version: 10.2.0
  82. #
  83. # Tensorboard version: 2.15.1
  84. #
  85. # gdown version: 4.7.1
  86. #
  87. # TorchVision version: 0.16.2+cu121
  88. #
  89. # tqdm version: 4.66.1
  90. #
  91. # lmdb version: 1.4.1
  92. #
  93. # psutil version: 5.9.7
  94. #
  95. # pandas version: 2.1.4
  96. #
  97. # einops version: 0.7.0
  98. #
  99. # transformers version: 4.36.2
  100. #
  101. # mlflow version: 2.9.2
  102. #
  103. # pynrrd version: 1.0.0
  104. #
  105. # clearml version: 1.13.3rc0
  106. #
  107. # %%
  108. # Setting environment variables for Weights & Biases (W&B) API and logging behavior
  109. # We set the W&B API key for authentication
  110. # Replace this with your own API key to enable Weights & Biases experiment tracking
  111. os.environ['WANDB_API_KEY'] = 'Your API Key code from your W&B'
  112. # Suppressing W&B output logs to make the notebook output cleaner
  113. os.environ['WANDB_SILENT'] = 'true'
  114. # Function to set random seeds for reproducibility across all libraries and environments
  115. def seed_all(seed: int) -> None:
  116. """
  117. Sets the seed for random number generators to ensure reproducibility across:
  118. - Python's `random` library
  119. - NumPy's random number generator
  120. - PyTorch (CPU and GPU)
  121. - MONAI for medical imaging workflows
  122. Args:
  123. seed (int): The seed value to set for all libraries.
  124. """
  125. # Set seed for Python's built-in random module
  126. random.seed(seed)
  127. # Set PYTHONHASHSEED environment variable for Python hash randomization
  128. os.environ['PYTHONHASHSEED'] = str(seed)
  129. # Set seed for NumPy random number generator
  130. np.random.seed(seed)
  131. # Set seed for PyTorch's random number generator (CPU)
  132. torch.manual_seed(seed)
  133. # Set seed for PyTorch's random number generator (GPU)
  134. torch.cuda.manual_seed(seed)
  135. # Ensure deterministic behavior in PyTorch's CUDA backend
  136. torch.backends.cudnn.deterministic = True # Ensures deterministic results
  137. torch.backends.cudnn.benchmark = False # Disables auto-optimization for reproducibility
  138. # Set deterministic behavior for MONAI (for transformations, etc.)
  139. mn.utils.misc.set_determinism(seed=seed)
  140. # Call the function to set the seed for reproducibility
  141. seed_all(3366) # We used 3366 as the fixed seed value
  142. # %%
  143. # Experiment Configuration
  144. # ==============================
  145. # Experiment series: A name for the current experiment
  146. experiment = "Choose_your _own_name"
  147. # This name helps organize and identify experiments, particularly for logging and result tracking.
  148. # ==============================
  149. # Neural Network Architecture
  150. # ==============================
  151. # Specify the neural network architecture to be used
  152. nn_architecture = "DenseNet121"
  153. # for efficient feature propagation and gradient flow.
  154. # ==============================
  155. # Preprocessing Pipeline
  156. # ==============================
  157. # We define a preprocessing mode for data.
  158. preprocessing_mode = 'FETS'
  159. # 'FETS' refers to a specific preprocessing pipeline, from the Federated Tumor Segmentation (FETS) Challenge,
  160. # ensuring standard input preprocessing for MRI data.
  161. # ==============================
  162. # MRI Sequence Selection
  163. # ==============================
  164. # Specify which MRI sequences to use as inputs
  165. # Possible options: ['CT1_path', 'T2_path'] or combinations of these.
  166. # Uncomment to choose the appropriate MRI sequence(s)
  167. # mri_to_use = ['CT1_path','T2_path'] # Use both CT1 and T2 images
  168. # mri_to_use = ['CT1_path'] # Use only CT1 images
  169. # mri_to_use = ['T2_path'] # Use only T2 images
  170. # Extract the MRI sequence names dynamically for tracking and file naming
  171. mrisq = [mri.split('_')[0] for mri in mri_to_use]
  172. # This line extracts the first part (e.g., "CT1" or "T2") from each string in `mri_to_use`.
  173. # Join the selected MRI sequences into a single string for easier tracking
  174. mri_sequences = '_'.join(mrisq)
  175. # Example result: 'CT1' or 'CT1_T2'
  176. # ==============================
  177. # Results Directory
  178. # ==============================
  179. # Define the folder where experiment results will be saved
  180. results_folder = 'the_path_to_the_director_that_you_want_to_save_your_models'
  181. # Ensure that this directory path exists and has the appropriate write permissions.
  182. # %%
  183. # ==============================
  184. # Hyperparameter Configuration
  185. # ==============================
  186. #We added our selected hyperparameters.
  187. # Batch size: Number of samples processed in one training iteration
  188. bs = 16
  189. # A smaller batch size like 16 is often used for medical imaging tasks, where models process high-resolution inputs
  190. # and GPU memory can be a limiting factor.
  191. # Learning rate: Step size for updating model parameters during training
  192. lr = 1e-3
  193. # A learning rate of 0.001 (1e-3) is a commonly used default. Adjust based on convergence behavior or fine-tuning needs.
  194. # Device selection: Automatically use GPU if available, otherwise default to CPU
  195. device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
  196. # This line checks for GPU availability:
  197. # - 'cuda': Use GPU for faster computation if CUDA is available.
  198. # - 'cpu': Fallback to CPU if no GPU is available.
  199. # Number of training epochs: Total number of passes through the entire training dataset
  200. epochs = 650
  201. # 650 epochs indicate a long training duration, which may be required for deep learning on medical imaging datasets.
  202. # It allows the model to converge effectively, especially for complex data or small datasets.
  203. # %% [markdown]
  204. # **Data**
  205. # %%
  206. # =====================================
  207. # Training Data: All Data vs Cross-Validation
  208. # =====================================
  209. import os
  210. import pandas as pd
  211. from sklearn.model_selection import StratifiedKFold
  212. # =====================================
  213. # Case 1: Training on All Data
  214. # =====================================
  215. # Load your full dataset into a pandas DataFrame
  216. # Replace '<your_training_csv_path>' with the path to your training CSV file
  217. df_train = pd.read_csv('<your_training_csv_path>')
  218. # Convert the training DataFrame to a list of dictionaries (row-wise)
  219. train_data_list = df_train.to_dict('records')
  220. # Extract relevant columns for training
  221. train_list = [
  222. {k: v for k, v in d.items() if k in ['subject_id_column_name', 'T2_path_column_name','CT1_path_column_name', 'label_column_name']}
  223. for d in train_data_list
  224. ]
  225. # Instructions:
  226. # - If you are training on all the data without validation, remove or ignore any validation loading code.
  227. # - You can directly proceed with `train_list` for training.
  228. # =====================================
  229. # Case 2: Five-Fold Cross-Validation
  230. # =====================================
  231. # If you plan to perform five-fold cross-validation, use the following code to split and save the data.
  232. # Load the full dataset
  233. df = pd.read_csv('<your_full_csv_path>') # Replace with your dataset path
  234. # Set parameters for cross-validation
  235. n_splits = 5 # Number of folds
  236. seed = 6630 # Random seed for reproducibility
  237. # Define the StratifiedKFold object
  238. skf = StratifiedKFold(n_splits=n_splits, shuffle=True, random_state=seed)
  239. # Define the directory where the fold CSV files will be saved
  240. output_dir = '<your_output_directory>' # Replace with desired output path
  241. os.makedirs(output_dir, exist_ok=True) # Create the directory if it doesn't exist
  242. # Perform Stratified K-Fold and save train/validation splits
  243. for i, (train_index, val_index) in enumerate(skf.split(df, df['label_column_name'])):
  244. # Create train and validation DataFrames for this fold
  245. train_df = df.iloc[train_index]
  246. val_df = df.iloc[val_index]
  247. # Define file names for this fold
  248. train_filename = os.path.join(output_dir, f'train_fold_{i}.csv')
  249. val_filename = os.path.join(output_dir, f'val_fold_{i}.csv')
  250. # Save the train and validation splits to CSV
  251. train_df.to_csv(train_filename, index=False)
  252. val_df.to_csv(val_filename, index=False)
  253. # =====================================
  254. # Instructions:
  255. # - Replace `<your_full_csv_path>` with the path to your complete dataset.
  256. # - Replace `<your_output_directory>` with the folder where you want to save the CSV files.
  257. # - If you are training on all data (no cross-validation), you don't need this code.
  258. # - For five-fold cross-validation, this code generates 5 pairs of train and validation CSV files,
  259. # named `train_fold_0.csv`, `val_fold_0.csv`, ..., `train_fold_4.csv`, `val_fold_4.csv`.
  260. # %%
  261. # =====================================
  262. # Loading Training and Validation Data
  263. # =====================================
  264. import pandas as pd
  265. # Load your training CSV file
  266. # Replace '<your_training_csv_path>' with the path to your training dataset
  267. df_train = pd.read_csv('<your_training_csv_path>')
  268. # Convert the training DataFrame to a list of dictionaries (row-wise)
  269. train_data_list = df_train.to_dict('records')
  270. # If you are using validation data, uncomment and replace '<your_validation_csv_path>'
  271. # with the path to your validation dataset
  272. # df_val = pd.read_csv('<your_validation_csv_path>')
  273. # val_data_list = df_val.to_dict('records')
  274. # =====================================
  275. # Extract Relevant Columns for Training
  276. # =====================================
  277. # Create a new list of dictionaries containing only the desired columns
  278. train_list = [
  279. {k: v for k, v in d.items() if k in ['subject_id_column_name', 'T2_path_column_name','CT1_path_column_name', 'label_column_name']}
  280. for d in train_data_list
  281. ]
  282. # If using validation data, extract relevant columns in a similar manner
  283. # val_list = [
  284. # {k: v for k, v in d.items() if k in ['subject_id_column_name', 'T2_path_column_name','CT1_path_column_name', 'label_column_name']} for d in val_data_list
  285. # ]
  286. # =====================================
  287. # Instructions:
  288. # - Replace '<your_training_csv_path>' with the path to your training CSV file.
  289. # - If you are training on all available data (no validation split), you can ignore or remove
  290. # the validation CSV file loading and associated code (`val_data_list` and `val_list`).
  291. # - Ensure your CSV file contains the necessary columns: 'subject_id_column_name', 'T2_path_column_name','CT1_path_column_name', 'label_column_name'.
  292. # %%
  293. # =========================================
  294. # Preprocessing and Data Augmentation
  295. # =========================================
  296. # Check preprocessing mode and apply corresponding transformations
  297. if preprocessing_mode == 'FETS-std':
  298. print('Using transforms for data preprocessed using FETS')
  299. # ---------------------------------
  300. # Training Data Transformations
  301. # ---------------------------------
  302. train_transforms = mn.transforms.Compose([
  303. # 1. Load MRI images
  304. mn.transforms.LoadImageD(keys=mri_to_use),
  305. # Loads the image paths specified in 'mri_to_use' into memory.
  306. # 2. Ensure Channel First Format
  307. mn.transforms.EnsureChannelFirstd(keys=mri_to_use),
  308. # Converts images to channel-first format (C, H, W, D), as required by deep learning models.
  309. # 3. Normalize Intensity
  310. mn.transforms.NormalizeIntensityD(keys=mri_to_use, channel_wise=True),
  311. # Performs intensity normalization per channel to ensure consistent input ranges.
  312. # Helps stabilize training by centering pixel values.
  313. # 4. Concatenate MRI Sequences [If you are training only with T2 or CT1 you can skip this part]
  314. mn.transforms.ConcatItemsd(keys=mri_to_use, name='concat_mri'),
  315. # Combines multiple MRI sequences (e.g., 'CT1', 'T2') into a single tensor with multiple channels.
  316. # 5. Random Flipping for Data Augmentation
  317. mn.transforms.RandFlipd(keys='concat_mri', prob=0.5, spatial_axis=[0, 1, 2]),
  318. # Randomly flips the input image along spatial axes (x, y, z) with a 50% probability.
  319. # 6. Random Affine Transformations for Augmentation
  320. mn.transforms.RandAffineD(
  321. keys='concat_mri',
  322. translate_range=(15, 15, 10), # Random translations in voxel space
  323. scale_range=(0.05, 0.05, 0.05), # Random scaling with a ±5% range
  324. rotate_range=(math.pi / 8, math.pi / 8, math.pi / 8), # Random rotations up to 22.5 degrees
  325. padding_mode='border', # Border padding for transformations
  326. prob=0.5), # 50% probability of applying the affine transform
  327. # 7. Convert to Tensor
  328. mn.transforms.ToTensord(keys=['concat_mri', 'label_column_name']),
  329. # Converts the input image and labels into PyTorch tensors for model input.
  330. ])
  331. # ---------------------------------
  332. # Validation Data Transformations
  333. # ---------------------------------
  334. val_transforms = mn.transforms.Compose([
  335. # 1. Load MRI images
  336. mn.transforms.LoadImageD(keys=mri_to_use),
  337. # 2. Ensure Channel First Format
  338. mn.transforms.EnsureChannelFirstd(keys=mri_to_use),
  339. # 3. Normalize Intensity
  340. mn.transforms.NormalizeIntensityD(keys=mri_to_use, channel_wise=True),
  341. # 4. Concatenate MRI Sequences
  342. mn.transforms.ConcatItemsd(keys=mri_to_use, name='concat_mri'),
  343. # 5. Convert to Tensor
  344. mn.transforms.ToTensord(keys=['concat_mri', 'label_column_name']),
  345. ])
  346. # %%
  347. # =========================================
  348. # Creating Training and Validation Datasets
  349. # =========================================
  350. # Create the training dataset
  351. train_ds = mn.data.Dataset(data=train_list, transform=train_transforms)
  352. # Explanation:
  353. # - `data=train_list`: The training data, a list of dictionaries where each dictionary contains
  354. # the input MRI paths and corresponding labels.
  355. # - `transform=train_transforms`: The preprocessing and augmentation pipeline to be applied to each sample
  356. # in the training dataset.
  357. # Create the validation dataset
  358. val_ds = mn.data.Dataset(data=val_list, transform=val_transforms)
  359. # Explanation:
  360. # - `data=val_list`: The validation data, structured similarly to the training data but used for evaluation.
  361. # - `transform=val_transforms`: The preprocessing pipeline applied to validation data
  362. # (no augmentations, only standard transformations).
  363. # %%
  364. # =========================================
  365. # Inspecting the Shape of Processed Data
  366. # =========================================
  367. # Print the shape of the first sample's MRI tensor in the training and validation datasets
  368. print(train_ds[0]['concat_mri'].shape, val_ds[0]['concat_mri'].shape)
  369. # Explanation:
  370. # - `train_ds[0]`: Retrieves the first sample from the training dataset.
  371. # - `val_ds[0]`: Retrieves the first sample from the validation dataset.
  372. # - `['concat_mri']`: Accesses the concatenated MRI tensor (created by `ConcatItemsd` transform).
  373. # - `.shape`: Returns the dimensions of the MRI tensor, typically in the format (C, H, W, D):
  374. # - `C`: Number of channels (e.g., 1 for a single MRI sequence, or more for multiple sequences like CT1/T2).
  375. # - `H, W, D`: Height, width, and depth of the 3D MRI volume.
  376. # Expected Output:
  377. # - The printed shapes will help confirm that the transformations have been applied correctly.
  378. # - Example output for single-channel MRIs:
  379. # torch.Size([1, 128, 128, 128]) torch.Size([1, 128, 128, 128])
  380. # Here:
  381. # - `1`: Single MRI channel (e.g., CT1).
  382. # - `128, 128, 128`: Spatial dimensions of the 3D volume after preprocessing.
  383. # %% [markdown]
  384. # **Plot some examples from the training and validation folds**
  385. # %% [markdown]
  386. # **Training fold**
  387. # %%
  388. # =========================================
  389. # Visualizing MRI Slices from the Dataset
  390. # =========================================
  391. # If both MRI sequences (e.g., CT1 and T2) are being used
  392. if len(mri_to_use) == 2:
  393. # Select the subject and slice number for visualization
  394. train_subject_number = 0 # Index of the subject to visualize
  395. train_z_slice_img = 80 # Slice index to visualize along the z-axis
  396. train_subj_id = train_ds[train_subject_number]['subject_id_column_name'] # Subject ID
  397. train_sample_img = train_ds[train_subject_number]['concat_mri'] # Loaded and transformed MRI tensor
  398. # Plot the figure
  399. fig = plt.figure(figsize=(10, 10))
  400. # Adjust spacing between rows and main title
  401. fig.subplots_adjust(hspace=0.4, top=0.90)
  402. # Add the main title displaying the subject and slice information
  403. fig.suptitle(f"Slice {train_z_slice_img} from subject: {train_subj_id}", fontsize=15)
  404. # Add subplots to visualize each MRI sequence
  405. ax1 = fig.add_subplot(2, 2, 1)
  406. ax1.imshow(train_sample_img[0, :, :, train_z_slice_img], cmap='gray') # First channel (e.g., CT1)
  407. ax2 = fig.add_subplot(2, 2, 2)
  408. ax2.imshow(train_sample_img[1, :, :, train_z_slice_img], cmap='gray') # Second channel (e.g., T2)
  409. # Add titles for each subplot
  410. ax1.title.set_text("T1c") # Title for the first channel
  411. ax2.title.set_text("T2") # Title for the second channel
  412. plt.show()
  413. # If only the 'T2' sequence is being used
  414. elif "T2_path" in mri_to_use:
  415. # Select the subject and slice number for visualization
  416. train_subject_number = 0
  417. train_z_slice_img = 80
  418. train_subj_id = train_ds[train_subject_number]['subject_id_column_name']
  419. train_sample_img = train_ds[train_subject_number]['concat_mri']
  420. # Plot the figure
  421. fig = plt.figure(figsize=(10, 10))
  422. # Adjust spacing between rows and main title
  423. fig.subplots_adjust(hspace=0.4, top=0.90)
  424. # Add the main title displaying the subject and slice information
  425. fig.suptitle(f"Slice {train_z_slice_img} from subject: {train_subj_id}", fontsize=15)
  426. # Visualize the T2 image
  427. ax2 = fig.add_subplot(2, 2, 2)
  428. ax2.imshow(train_sample_img[0, :, :, train_z_slice_img], cmap='gray') # Single channel (T2)
  429. # Add title for the subplot
  430. ax2.title.set_text("T2")
  431. plt.show()
  432. # If only the 'CT1' sequence is being used
  433. elif "CT1_path" in mri_to_use:
  434. # Select the subject and slice number for visualization
  435. train_subject_number = 0
  436. train_z_slice_img = 80
  437. train_subj_id = train_ds[train_subject_number]['subject_id_column_name']
  438. train_sample_img = train_ds[train_subject_number]['concat_mri']
  439. # Plot the figure
  440. fig = plt.figure(figsize=(10, 10))
  441. # Adjust spacing between rows and main title
  442. fig.subplots_adjust(hspace=0.4, top=0.90)
  443. # Add the main title displaying the subject and slice information
  444. fig.suptitle(f"Slice {train_z_slice_img} from subject: {train_subj_id}", fontsize=15)
  445. # Visualize the CT1 image
  446. ax1 = fig.add_subplot(2, 2, 1)
  447. ax1.imshow(train_sample_img[0, :, :, train_z_slice_img], cmap='gray') # Single channel (CT1)
  448. # Add title for the subplot
  449. ax1.title.set_text("T1c")
  450. plt.show()
  451. # %% [markdown]
  452. # **Validation fold**
  453. # %%
  454. # =========================================
  455. # Visualizing MRI Slices from the Validation Dataset
  456. # =========================================
  457. # If both MRI sequences (e.g., CT1 and T2) are being used
  458. if len(mri_to_use) == 2:
  459. # Select the subject and slice number for visualization
  460. val_subject_number = 6 # Index of the subject to visualize
  461. val_z_slice_img = 80 # Slice index to visualize along the z-axis
  462. val_subj_id = val_ds[val_subject_number]['subject_id_column_name'] # Subject ID
  463. val_sample_img = val_ds[val_subject_number]['concat_mri'] # Loaded and transformed MRI tensor
  464. # Plot the figure
  465. fig = plt.figure(figsize=(10, 10))
  466. # Adjust spacing between rows and main title
  467. fig.subplots_adjust(hspace=0.4, top=0.90)
  468. # Add the main title displaying the subject and slice information
  469. fig.suptitle(f"Slice {val_z_slice_img} from subject: {val_subj_id}", fontsize=15)
  470. # Add subplots to visualize each MRI sequence
  471. ax1 = fig.add_subplot(2, 2, 1)
  472. ax1.imshow(val_sample_img[0, :, :, val_z_slice_img], cmap='gray') # First channel (e.g., CT1)
  473. ax2 = fig.add_subplot(2, 2, 2)
  474. ax2.imshow(val_sample_img[1, :, :, val_z_slice_img], cmap='gray') # Second channel (e.g., T2)
  475. # Add titles for each subplot
  476. ax1.title.set_text("T1c") # Title for the first channel
  477. ax2.title.set_text("T2") # Title for the second channel
  478. plt.show()
  479. # If only the 'T2' sequence is being used
  480. elif "T2_path" in mri_to_use:
  481. # Select the subject and slice number for visualization
  482. val_subject_number = 6
  483. val_z_slice_img = 80
  484. val_subj_id = val_ds[val_subject_number]['subject_id_column_name']
  485. val_sample_img = val_ds[val_subject_number]['concat_mri']
  486. # Plot the figure
  487. fig = plt.figure(figsize=(10, 10))
  488. # Adjust spacing between rows and main title
  489. fig.subplots_adjust(hspace=0.4, top=0.90)
  490. # Add the main title displaying the subject and slice information
  491. fig.suptitle(f"Slice {val_z_slice_img} from subject: {val_subj_id}", fontsize=15)
  492. # Visualize the T2 image
  493. ax2 = fig.add_subplot(2, 2, 2)
  494. ax2.imshow(val_sample_img[0, :, :, val_z_slice_img], cmap='gray') # Single channel (T2)
  495. # Add title for the subplot
  496. ax2.title.set_text("T2")
  497. plt.show()
  498. # If only the 'CT1' sequence is being used
  499. elif "CT1_path" in mri_to_use:
  500. # Select the subject and slice number for visualization
  501. val_subject_number = 6
  502. val_z_slice_img = 80
  503. val_subj_id = val_ds[val_subject_number]['subject_id_column_name']
  504. val_sample_img = val_ds[val_subject_number]['concat_mri']
  505. # Plot the figure
  506. fig = plt.figure(figsize=(10, 10))
  507. # Adjust spacing between rows and main title
  508. fig.subplots_adjust(hspace=0.4, top=0.90)
  509. # Add the
  510. # %% [markdown]
  511. # **Model**
  512. # %%
  513. # =========================================
  514. # Creating Data Loaders for Training and Validation
  515. # =========================================
  516. # Training DataLoader
  517. train_loader = DataLoader(
  518. train_ds, # Dataset: The training dataset
  519. batch_size=bs, # Number of samples per batch
  520. num_workers=4, # Number of subprocesses to use for data loading
  521. pin_memory=torch.cuda.is_available(), # Enables fast data transfer to GPU memory if CUDA is available
  522. prefetch_factor=1, # Number of samples preloaded by each worker into the buffer
  523. shuffle=True # Randomly shuffles the data at each epoch for better generalization
  524. )
  525. # Validation DataLoader
  526. val_loader = DataLoader(
  527. val_ds, # Dataset: The validation dataset
  528. batch_size=bs, # Number of samples per batch
  529. num_workers=4, # Number of subprocesses to use for data loading
  530. pin_memory=torch.cuda.is_available(), # Enables fast data transfer to GPU memory if CUDA is available
  531. prefetch_factor=1 # Number of samples preloaded by each worker into the buffer
  532. # Note: No shuffling for validation data to ensure consistent evaluation
  533. )
  534. # %%
  535. # =========================================
  536. # Model, Loss Function, Optimizer, and Scheduler Setup
  537. # =========================================
  538. # 1. Model: Creating the DenseNet121 architecture
  539. model = mn.networks.nets.DenseNet121(
  540. spatial_dims=3, # Specifies the input data is 3D (e.g., MRI volumes)
  541. in_channels=len(mri_to_use), # Number of input channels (e.g., 1 for CT1 or T2, 2 for both)
  542. out_channels=2 # Number of output classes (binary classification: e.g., 0 or 1)
  543. ).to(device) # Move the model to the appropriate device (GPU or CPU)
  544. # Explanation:
  545. # - DenseNet121: A pre-built dense convolutional neural network from MONAI.
  546. # - `spatial_dims=3`: Handles volumetric medical imaging data (3D spatial inputs).
  547. # - `in_channels`: Dynamically set based on the number of MRI sequences being used.
  548. # - `out_channels=2`: Outputs logits for two classes; suitable for binary classification.
  549. # 2. Loss Function: Cross-Entropy Loss
  550. loss_function = torch.nn.CrossEntropyLoss()
  551. # Explanation:
  552. # - CrossEntropyLoss is a commonly used loss function for classification tasks.
  553. # - It combines a softmax operation with a negative log-likelihood loss, making it ideal for multi-class or binary classification.
  554. # 3. Optimizer: AdamW Optimizer
  555. optimizer = torch.optim.AdamW(model.parameters(), lr)
  556. # Explanation:
  557. # - AdamW: A variant of the Adam optimizer with weight decay (regularization) to prevent overfitting.
  558. # - `model.parameters()`: The model's learnable parameters to optimize.
  559. # - `lr`: The learning rate specified earlier (1e-3).
  560. # 4. Learning Rate Scheduler: Cosine Annealing
  561. lr_scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=epochs)
  562. # Explanation:
  563. # - CosineAnnealingLR adjusts the learning rate following a cosine curve.
  564. # - `T_max=epochs`: The maximum number of epochs, ensuring the scheduler completes one full cycle.
  565. # - Benefit: Starts with a high learning rate and gradually reduces it, helping the model converge smoothly.
  566. # %%
  567. # =========================================
  568. # Calculating the Total and Trainable Parameters of the Model
  569. # =========================================
  570. # Calculate the total number of parameters in the model
  571. total_params = sum(param.numel() for param in model.parameters())
  572. # Explanation:
  573. # - `model.parameters()`: Retrieves all the parameters of the model.
  574. # - `param.numel()`: Counts the total number of elements (parameters) in each tensor.
  575. # - `sum()`: Adds up all the parameters to get the total count.
  576. # Calculate the number of trainable parameters (parameters requiring gradients)
  577. trainable_params = sum(p.numel() for p in model.parameters() if p.requires_grad)
  578. # Explanation:
  579. # - `if p.requires_grad`: Filters parameters that require gradients (trainable during backpropagation).
  580. # - Non-trainable parameters (e.g., frozen layers) are excluded.
  581. # Print the total and trainable parameters
  582. print(total_params, trainable_params)
  583. # =========================================
  584. # Expected Outcome:
  585. # =========================================
  586. # - Output: 11266626 11266626
  587. # - `11266626`: Total number of parameters in DenseNet121.
  588. # - `11266626`: Trainable parameters, indicating all layers are trainable.
  589. # %% [markdown]
  590. # **Metrics**
  591. # %%
  592. # =========================================
  593. # Defining Evaluation Metrics for the Model
  594. # =========================================
  595. # Comprehensive list of metrics for evaluating classification performance
  596. all_metrics = [
  597. "sensitivity", # True Positive Rate (TPR): Proportion of actual positives correctly identified
  598. "specificity", # True Negative Rate (TNR): Proportion of actual negatives correctly identified
  599. "precision", # Positive Predictive Value (PPV): Proportion of predicted positives that are correct
  600. "negative predictive value", # NPV: Proportion of predicted negatives that are correct
  601. "miss rate", # False Negative Rate (FNR): Proportion of actual positives missed
  602. "fall out", # False Positive Rate (FPR): Proportion of actual negatives incorrectly classified
  603. "false discovery rate", # FDR: Proportion of predicted positives that are incorrect
  604. "false omission rate", # FOR: Proportion of predicted negatives that are incorrect
  605. "prevalence threshold", # Threshold where PPV = NPV given prevalence
  606. "threat score", # Intersection over Union (IoU) for positives
  607. "accuracy", # Overall proportion of correct predictions
  608. "balanced accuracy", # Average of sensitivity and specificity (useful for imbalanced datasets)
  609. "f1", # Harmonic mean of precision and recall
  610. "matthews correlation coefficient", # MCC: Correlation between predicted and actual values (-1 to 1)
  611. "fowlkes mallows index", # Geometric mean of precision and recall
  612. "informedness", # Bookmaker Informedness (sensitivity + specificity - 1)
  613. "markedness" # Proportion of correct predictions based on prediction confidence
  614. ]
  615. # Subset of common metrics from sklearn's classification report
  616. sklearn_classification_report_metrics = [
  617. "precision", # Positive Predictive Value: How many predicted positives are correct
  618. "recall", # Sensitivity or True Positive Rate: Correctly identified positives
  619. "f1", # F1-Score: Balance between precision and recall
  620. "accuracy" # Overall classification accuracy
  621. ]
  622. # %%
  623. # =========================================
  624. # MONAI Metrics Function for Logging and Evaluation
  625. # =========================================
  626. # Function to compute and log performance metrics, save the model, and track the best metrics
  627. def monai_metrics(y_true, y_predictions, metrics_list, split, epoch, best_metrics):
  628. """
  629. Computes AUROCC and confusion matrix metrics, logs them to WandB,
  630. and saves the model based on the best performance for specific metrics.
  631. Args:
  632. y_true (torch.Tensor): Ground truth labels.
  633. y_predictions (torch.Tensor): Model's raw output predictions (logits).
  634. metrics_list (list): List of metrics to compute using the confusion matrix.
  635. split (str): 'train' or 'validation' - indicates the dataset split.
  636. epoch (int): Current training epoch.
  637. best_metrics (dict): Dictionary to store the best metrics and corresponding epochs.
  638. Returns:
  639. best_metrics (dict): Updated dictionary with the best metrics and epochs.
  640. """
  641. # 1. Define MONAI Metrics
  642. aurocc_metric = ROCAUCMetric()
  643. confusion_matrix_metric = ConfusionMatrixMetric(
  644. metric_name=metrics_list, # List of confusion matrix metrics to calculate
  645. reduction="none", # No reduction across batches to calculate per-batch metrics
  646. include_background=True, # Includes background class (if applicable)
  647. get_not_nans=False # Excludes invalid (NaN) values
  648. )
  649. # Metrics to track for saving model checkpoints
  650. metrics_to_track = [
  651. "balanced accuracy",
  652. "f1",
  653. "matthews correlation coefficient"
  654. ]
  655. # 2. Postprocessing: Transform labels and predictions for metrics computation
  656. post_labels = Compose([AsDiscrete(to_onehot=2)]) # Convert labels to one-hot encoding (2 classes)
  657. post_predictions_aurocc = Compose([Activations(softmax=True)]) # Apply softmax to get probabilities
  658. post_predictions_confusionmatrix = Compose([AsDiscrete(argmax=True, to_onehot=2)]) # Get predicted classes
  659. # Apply transformations to the labels and predictions
  660. y_onehot = [post_labels(i) for i in decollate_batch(y_true, detach=False)]
  661. y_pred_aurocc = [post_predictions_aurocc(i) for i in decollate_batch(y_predictions, detach=False)]
  662. y_pred_confusionmatrix = [post_predictions_confusionmatrix(i) for i in decollate_batch(y_predictions, detach=False)]
  663. # 3. Compute AUROCC Metric
  664. aurocc_metric(y_pred_aurocc, y_onehot) # Calculate AUROCC
  665. aurocc_result = aurocc_metric.aggregate() # Aggregate the results
  666. print(f"{split.capitalize()} AUROCC: {aurocc_result:.4f}")
  667. # Log AUROCC to WandB
  668. wandb.log({f"{split.capitalize()} AUROCC": aurocc_result}, step=global_step)
  669. # Save model if AUROCC is the best so far (validation split only)
  670. if split == "validation":
  671. if "AUROCC" not in best_metrics or aurocc_result > best_metrics["AUROCC"]["value"]:
  672. best_metrics["AUROCC"] = {"value": aurocc_result, "epoch": epoch}
  673. torch.save(model.state_dict(), f'{results_folder}/{nn_architecture}_{preprocessing_mode}_{mri_sequences}_best_AUROCC_fold{fold_number}.pth')
  674. # 4. Compute Confusion Matrix Metrics
  675. confusion_matrix_metric(y_pred_confusionmatrix, y_onehot)
  676. confusion_matrix_results = confusion_matrix_metric.aggregate(reduction="mean_batch")
  677. metrics_dict = {}
  678. # Loop through the list of metrics and calculate values
  679. for i, name in enumerate(metrics_list):
  680. mean_metric_class_zero = confusion_matrix_results[i][0].item() # Metric for class 0
  681. mean_metric_class_one = confusion_matrix_results[i][1].item() # Metric for class 1
  682. if name == "accuracy":
  683. # Report accuracy as a percentage
  684. metric_value = mean_metric_class_zero * 100
  685. print(f"{split.capitalize()} {name.capitalize()}: {metric_value:.2f}%")
  686. else:
  687. # Report other metrics as the average for class 0 and class 1
  688. metric_value = (mean_metric_class_zero + mean_metric_class_one) / 2
  689. print(f"{split.capitalize()} {name.capitalize()}: {metric_value:.4f}")
  690. # Add the metric to the dictionary for logging
  691. metrics_dict[f"{split.capitalize()} {name.capitalize()}"] = metric_value
  692. # Log confusion matrix metrics to WandB
  693. wandb.log(metrics_dict, step=global_step)
  694. # 5. Save Model Based on Best Confusion Matrix Metrics (Validation Only)
  695. if split == "validation":
  696. for i, name in enumerate(metrics_list):
  697. mean_metric_class_zero = confusion_matrix_results[i][0].item()
  698. mean_metric_class_one = confusion_matrix_results[i][1].item()
  699. mean_metric = (mean_metric_class_zero + mean_metric_class_one) / 2
  700. if name == "accuracy":
  701. mean_metric *= 100 # Convert accuracy to percentage
  702. # Update best metrics dictionary and save model checkpoint
  703. if name not in best_metrics or mean_metric > best_metrics[name]["value"]:
  704. best_metrics[name] = {"value": mean_metric, "epoch": epoch}
  705. if name in metrics_to_track:
  706. name = name.replace(' ', '_') # Replace spaces with underscores for filenames
  707. torch.save(model.state_dict(), f'{results_folder}/{nn_architecture}_{preprocessing_mode}_{mri_sequences}_best_{name}_fold{fold_number}.pth')
  708. # 6. Reset Metrics for the Next Round
  709. aurocc_metric.reset()
  710. confusion_matrix_metric.reset()
  711. # Clear memory for transformed predictions
  712. del y_onehot, y_pred_aurocc, y_pred_confusionmatrix
  713. # Return updated best metrics (validation only)
  714. if split == "validation":
  715. return best_metrics
  716. # %% [markdown]
  717. # **Training**
  718. #
  719. # We used W&B for logging our metrics and monitoring the training.
  720. # %%
  721. # =========================================
  722. # Sanity Check: Confirm Results Directory
  723. # =========================================
  724. # Print a message to confirm where the results will be stored
  725. print(f'ATTENTION: results will be stored in {results_folder}')
  726. # %%
  727. # =========================================
  728. # W&B Initialization and Training Loop
  729. # =========================================
  730. # Naming convention for MRI sequences used for training
  731. mrisq = [mri.split('_')[0] for mri in mri_to_use] # Extract MRI names (e.g., 'CT1', 'T2') without suffix
  732. mri_sequences = '_'.join(mrisq) # Join MRI names into a single string
  733. # Initialize a Weights & Biases (W&B) project to log metrics and configurations
  734. wandb.init(project='Choose_your_project_ID_name')
  735. # =========================================
  736. # W&B Configuration
  737. # =========================================
  738. # Log configuration settings for better experiment tracking
  739. config = wandb.config
  740. config.preprocessing = preprocessing_mode # Preprocessing mode (e.g., 'FETS-std')
  741. config.MRI_Sequences = mri_sequences # MRI sequences used (e.g., 'CT1_T2')
  742. config.learning_rate = lr # Learning rate
  743. config.batch_size = bs # Batch size
  744. config.mode = '3D' # Model input mode (3D data)
  745. config.backbone = nn_architecture # Model backbone architecture (e.g., DenseNet121)
  746. config.total_parameters = total_params # Total model parameters
  747. config.trainable_parameters = trainable_params # Trainable model parameters
  748. config.optimizer = 'AdamW' # Optimizer used for training
  749. config.normalization = 'Per patient' # Normalization method
  750. config.epochs = epochs # Total number of training epochs
  751. config.augmentation = 'Affine' # Data augmentation applied
  752. config.RandAffine_tran_scale_rotate_prob = [(15,15,10),(0.05,0.05,0.05),(math.pi/8,math.pi/8,math.pi/8),0.5]
  753. config.RandGaussianNoise_prob_mean_std = [0.5, 0.0, 0.2]
  754. config.RandFlip_prob_axis = [0.5, (0,1,2)]
  755. # Conditional config for Spatial Padding
  756. if preprocessing_mode == 'FETS-std':
  757. config.SpatialPad = 'NA'
  758. else:
  759. config.SpatialPad = (168, 196, 168)
  760. # Set a unique name for the W&B run
  761. wandb.run.name = f'name_of_your_choice_for_your_run_{mri_sequences}'
  762. # Initialize metrics tracking and global step counter
  763. best_metrics = {} # Dictionary to store the best performance metrics
  764. training_results = [] # List to store results for each epoch
  765. global_step = 0 # Counter for logging steps to W&B
  766. # =========================================
  767. # Training and Validation Loop
  768. # =========================================
  769. for i, epoch in enumerate(tqdm(range(epochs))):
  770. print("-" * 10)
  771. print(f"epoch {epoch + 1}/{epochs}")
  772. model.train()
  773. epoch_loss = 0
  774. step = 0
  775. step_loss_list = []
  776. y_pred = torch.tensor([], dtype=torch.float32, device=device)
  777. y = torch.tensor([], dtype=torch.long, device=device)
  778. # Training Loop
  779. for batch_data in train_loader:
  780. step += 1
  781. global_step += 1
  782. # Move input and labels to the specified device (GPU/CPU)
  783. inputs, labels = batch_data['concat_mri'].to(device), batch_data['label_column_name'].to(device)
  784. optimizer.zero_grad()
  785. outputs = model(inputs)
  786. loss = loss_function(outputs, labels) # Compute loss
  787. loss.backward()
  788. optimizer.step()
  789. epoch_loss += loss.item()
  790. # Log step loss to W&B
  791. wandb.log({"Training step loss": loss.item()}, step=global_step)
  792. step_loss_list.append(loss.item())
  793. # Accumulate predictions and labels for metrics computation
  794. y = torch.cat([y, labels], dim=0)
  795. y_pred = torch.cat([y_pred, outputs], dim=0)
  796. # Learning Rate Scheduler Step
  797. wandb.log({'lr': lr_scheduler.get_lr()[0]}, step=global_step)
  798. lr_scheduler.step()
  799. # Log epoch-level metrics
  800. median_epoch_loss = statistics.median(step_loss_list)
  801. wandb.log({'Training median epoch loss': median_epoch_loss}, step=global_step)
  802. epoch_loss /= step
  803. wandb.log({'Training epoch loss': epoch_loss}, step=global_step)
  804. print(f"Training epoch {epoch + 1} average loss: {epoch_loss:.4f}")
  805. # Compute and log metrics using MONAI metrics function
  806. best_metrics_dict = monai_metrics(y, y_pred, all_metrics, epoch, best_metrics)
  807. # Store predictions, probabilities, and labels
  808. y_dict = y.detach().cpu().numpy()
  809. y_pred_classes = y_pred.detach().cpu().argmax(dim=1).numpy()
  810. y_pred_prob = torch.softmax(y_pred.detach().cpu(), dim=1).numpy()
  811. training_results_dict = {
  812. "epoch": epoch,
  813. "predictions": y_pred_classes.tolist(),
  814. "probabilities": y_pred_prob.tolist(),
  815. "labels": y_dict.tolist()
  816. }
  817. training_results.append(training_results_dict)
  818. # Clean up predictions and labels to free memory
  819. del y, y_pred
  820. # Save the model checkpoint for the current epoch
  821. torch.save(model.state_dict(), f'{results_folder}/{nn_architecture}_{preprocessing_mode}_{mri_sequences}_epoch{epoch}.pth')
  822. # Finalize W&B run
  823. wandb.finish()
  824. # Save the final model after all epochs
  825. torch.save(model.state_dict(), f'{results_folder}/{nn_architecture}_{preprocessing_mode}_{mri_sequences}_last_epoch.pth')
  826. # Save training results to a JSON file for all epochs
  827. with open(f'{results_folder}/{nn_architecture}_{preprocessing_mode}_{mri_sequences}_training_results_.json', "w") as jsonfile:
  828. json.dump(training_results, jsonfile)
  829. # Save the best metrics to a CSV file
  830. df_best_metrics = pd.DataFrame.from_dict(best_metrics_dict, orient="index")
  831. df_best_metrics.index.name = "Metric"
  832. df_best_metrics.to_csv((f'{results_folder}/{nn_architecture}_{preprocessing_mode}_{mri_sequences}_best_metrics.csv'))
  833. print('ALL DONE!')

Code_To_Share_Brain_Lesion_Classifier.ipynb at commit b13e356, no license · at the source

Overview

  1. Department of Radiology, Mayo Clinic, Rochester, Minnesota
  2. Department of Quantitative Health Sciences, Mayo Clinic, Rochester, Minnesota
  3. Department of Neurologic Surgery, University of San Francisco, San Francisco, California
  4. Department of Neurology, Mayo Clinic, Rochester, Minnesota
  5. Department of Neurosurgery, Mayo Clinic, Rochester, Minnesota
  6. Division of Hematology, Oncology, and Blood & Marrow Transplantation, University of Iowa, Iowa City, Iowa
  7. Department of Radiology and Neurology, University of Iowa, Iowa City, Iowa
  8. Department of Internal Medicine, Mayo Clinic, Rochester, Minnesota
  9. Department of Neurosurgery, University of Iowa, Iowa City, Iowa
  10. Department of Laboratory Medicine and Pathology, Mayo Clinic, Rochester, Minnesota
  11. Center for Multiple Sclerosis and Autoimmune Neurology, Mayo Clinic, Rochester, Minnesota
Institutions: Mayo Clinic (United States); University of San Francisco (United States); University of Iowa (United States)
Journal: Cancer research communications, volume 6, issue 5, pages 1168-1179
Dates: received 13 November 2025; accepted 1 May 2026; published online 20 May 2026; in print May 2026
Type: Research article · Language: English
License: CC BY
Identifiers: DOI 10.1158/2767-9764.crc-25-0710 · PMID 42081255 · PMCID PMC13188832 · OpenAlex W7160186225
Open access: gold, a free copy (OpenAlex)
Status: code verified
Categories: structural MRI / diffusion (modality), human (organism), other condition (population), clinical / translational (subfield)
Methods: Connectivity, Machine learning, Statistics
MeSH: Brain Neoplasms*, Central Nervous System Neoplasms*, Deep Learning*, Glioblastoma*, Lymphoma, Large B-Cell, Diffuse*, Magnetic Resonance Imaging*, Adult, Aged, Diagnosis, Differential, Female, Humans, Isocitrate Dehydrogenase, Male, Middle Aged, Prospective Studies (* major topic)
Journal subjects: Diagnostic Biomarkers, Gliomas, Glioblastomas, Hematological Cancers, Lymphomas, Imaging, Magnetic resonance imaging (MRI), Methodology And Modeling
Topic: Glioma Diagnosis and Treatment (Genetics, Medicine), according to OpenAlex
Funding: NIH (NS113803, CA97274)
Citations: not cited yet (Europe PMC); 48 references in the paper
Research resources: executed in Python RRID:SCR_00834, built on top of PyTorch RRID:SCR_018536

Abstract

Glioblastoma (GBM), isocitrate dehydrogenase wild-type (IDHwt) and central nervous system diffuse large B-cell lymphoma (CNS-DLBCL) are aggressive brain tumors with overlapping MRI features, yet distinct treatment approaches. Noninvasive tools are needed to aid in differential diagnosis. Deep learning on T1 postcontrast and T2-weighted MRI sequences were used to differentiate GBM and CNS-DLBCL. A three-stage temporal study design was utilized. Model development was performed on 146 patients with CNS-DLBCL and 146 age-matched, sex-matched, and MRI year–matched patients with GBM diagnosed at Mayo Clinic between 1998 and 2019. Models were tested on independent temporal test cohorts. Initial testing included 240 independent GBM diagnosed at Mayo Clinic between 1998 and 2019. The prospective test cohort included 37 patients with CNS-DLBCL and 256 patients with GBM diagnosed at Mayo Clinic after January 1, 2020, and 36 patients with CNS-DLBCL diagnosed at an external institution. Of the patients diagnosed at Mayo Clinic, 47% had MRIs generated from non-Mayo institutions. Two different model approaches were compared: (i) ensemble approach using area under the receiver operating characteristic curve (AUC) and cross-validation for model selection and (ii) loss approach minimizing cross-entropy loss and cross-validation to evaluate prediction performance. The AUCs on the prospective test cohort were 0.84 [95% confidence interval (CI), 0.78–0.90] and 0.83 (95% CI, 0.77–0.88) for the ensemble and loss approaches, respectively. Stability of ensemble prediction improved with the increasing number of models. Stratified AUC analysis demonstrated consistent performance across sex and age. We utilized a robust temporal study design and applied 2 different analytic approaches to develop a classification model. The findings confirm the feasibility of using MRI-based deep learning models to differentiate GBM from CNS-DLBCL.

Significance: GBM, IDHwt and CNS-DLBCL are aggressive brain tumors with overlapping MRI features, yet distinct treatment approaches. Noninvasive tools are needed to aid in differential diagnosis. We developed MRI-based deep learning models to differentiate GBM, IDHwt from CNS-DLBCL using a rigorous three-stage temporal design that included prospective validation. The model AUC on a prospective cohort was 0.84.

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

slowvak/BrainLesionClassifier

License: none: the authors keep all their rights
State: the link answers, verified on 27 September 2026
Evidence: files inventoried
Commit: b13e356ed9ce6dd377c779187a1a85239fc81960, 22 April 2026
Languages: Jupyter (2)
Size: 3 files, 2 scripts
Software Heritage: not archived
Found in: “Data Availability”
Holds: README, 2 notebooks
Not found: license file, CITATION.cff, environment file, tests, continuous integration, documentation
Tools: Matplotlib (2 files), MONAI (2 files), NiBabel (2 files), NumPy (2 files), pandas (2 files), PyTorch (2 files), scikit-learn (2 files)
Availability: 1 check, the latest on 27 September 2026: the link answers
  • 27 September 2026: the link answers
3 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:

  • 1 repository of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
  • 2 scripts, each with its path and the digest of its content;
  • 3 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 MRI data generated in this study are not publicly available because of patient privacy requirements. The MRI deep learning code is available at https://github.com/slowvak/BrainLesionClassifier.git.

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, issue, pages, dates, 22 authors, 15 MeSH terms, 1 funder, 45 references, 2 RRIDs.

Cite

This paper

Moassefi, M., Decker, P. A., Conte, G. M., Kosel, M. L., Molinaro, A. M., Niederschweiberer, M. A., Nikanpour, Y., Ruff, M. W., Burns, T. C., Farooq, U., Derdeyn, C., Habermann, T. M., Cerhan, J. R., Greenlee, J. D., Howard, M. A., Slager, S. L., Vaubel, R. A., Jenkins, R. B., Lachance, D. H., . . . Eckel-Passow, J. E. (2026). MRI Deep Learning for Differentiating Glioblastoma, IDH Wild-type from Central Nervous System Diffuse Large B-cell Lymphoma. Cancer research communications, 6(5), 1168-1179. https://doi.org/10.1158/2767-9764.crc-25-0710

BibTeX

@article{moassefi2026mri,
author = {Moassefi, Mana and Decker, Paul A. and Conte, Gian Marco and Kosel, Matthew L. and Molinaro, Annette M. and Niederschweiberer, Moritz A. and Nikanpour, Yalda and Ruff, Michael W. and Burns, Terry C. and Farooq, Umar and Derdeyn, Colin and Habermann, Thomas M. and Cerhan, James R. and Greenlee, Jeremy D.W. and Howard, Matthew A. and Slager, Susan L. and Vaubel, Rachael A. and Jenkins, Robert B. and Lachance, Daniel H. and Erickson, Bradley J. and Tobin, W. Oliver and Eckel-Passow, Jeanette E.},
title = {{MRI Deep Learning for Differentiating Glioblastoma, IDH Wild-type from Central Nervous System Diffuse Large B-cell Lymphoma}},
journal = {Cancer research communications},
year = {2026},
month = may,
volume = {6},
number = {5},
pages = {1168--1179},
publisher = {American Association for Cancer Research},
issn = {2767-9764},
doi = {10.1158/2767-9764.crc-25-0710},
url = {https://doi.org/10.1158/2767-9764.crc-25-0710},
pmid = {42081255},
pmcid = {PMC13188832}
}

RIS

TY - JOUR
AU - Moassefi, Mana
AU - Decker, Paul A.
AU - Conte, Gian Marco
AU - Kosel, Matthew L.
AU - Molinaro, Annette M.
AU - Niederschweiberer, Moritz A.
AU - Nikanpour, Yalda
AU - Ruff, Michael W.
AU - Burns, Terry C.
AU - Farooq, Umar
AU - Derdeyn, Colin
AU - Habermann, Thomas M.
AU - Cerhan, James R.
AU - Greenlee, Jeremy D.W.
AU - Howard, Matthew A.
AU - Slager, Susan L.
AU - Vaubel, Rachael A.
AU - Jenkins, Robert B.
AU - Lachance, Daniel H.
AU - Erickson, Bradley J.
AU - Tobin, W. Oliver
AU - Eckel-Passow, Jeanette E.
TI - MRI Deep Learning for Differentiating Glioblastoma, IDH Wild-type from Central Nervous System Diffuse Large B-cell Lymphoma
T2 - Cancer research communications
J2 - Cancer Res Commun
PY - 2026
DA - 2026/05/01
VL - 6
IS - 5
SP - 1168
EP - 1179
SN - 2767-9764
PB - American Association for Cancer Research
DO - 10.1158/2767-9764.crc-25-0710
UR - https://doi.org/10.1158/2767-9764.crc-25-0710
LA - en
ER -

CSL-JSON

{
"id": "10.1158/2767-9764.crc-25-0710",
"type": "article-journal",
"title": "MRI Deep Learning for Differentiating Glioblastoma, IDH Wild-type from Central Nervous System Diffuse Large B-cell Lymphoma",
"container-title": "Cancer research communications",
"author": [
{
"family": "Moassefi",
"given": "Mana"
},
{
"family": "Decker",
"given": "Paul A."
},
{
"family": "Conte",
"given": "Gian Marco"
},
{
"family": "Kosel",
"given": "Matthew L."
},
{
"family": "Molinaro",
"given": "Annette M."
},
{
"family": "Niederschweiberer",
"given": "Moritz A."
},
{
"family": "Nikanpour",
"given": "Yalda"
},
{
"family": "Ruff",
"given": "Michael W."
},
{
"family": "Burns",
"given": "Terry C."
},
{
"family": "Farooq",
"given": "Umar"
},
{
"family": "Derdeyn",
"given": "Colin"
},
{
"family": "Habermann",
"given": "Thomas M."
},
{
"family": "Cerhan",
"given": "James R."
},
{
"family": "Greenlee",
"given": "Jeremy D.W."
},
{
"family": "Howard",
"given": "Matthew A."
},
{
"family": "Slager",
"given": "Susan L."
},
{
"family": "Vaubel",
"given": "Rachael A."
},
{
"family": "Jenkins",
"given": "Robert B."
},
{
"family": "Lachance",
"given": "Daniel H."
},
{
"family": "Erickson",
"given": "Bradley J."
},
{
"family": "Tobin",
"given": "W. Oliver"
},
{
"family": "Eckel-Passow",
"given": "Jeanette E."
}
],
"container-title-short": "Cancer Res Commun",
"volume": "6",
"issue": "5",
"page": "1168-1179",
"DOI": "10.1158/2767-9764.crc-25-0710",
"PMID": "42081255",
"PMCID": "PMC13188832",
"ISSN": "2767-9764",
"publisher": "American Association for Cancer Research",
"URL": "https://doi.org/10.1158/2767-9764.crc-25-0710",
"language": "en",
"issued": {
"date-parts": [
[
2026,
5,
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.1038/s41467-026-76587-0 [code]
Uncovering the signaling networks of disseminated glioblastoma cells in vivo with INSIGHT.
Journal: Nature communications
In common: other condition, 2 authors
[2] doi:10.3389/fnins.2026.1870124 [code]
An end-to-end pipeline for automated fetal brain segmentation and biometry from 3D SSFP MRI.
Journal: Frontiers in neuroscience
In common: MONAI, NiBabel, PyTorch, 4 other tools, structural MRI / diffusion, 2 references
[3] doi:10.3389/fdgth.2026.1691088 [code]
Real-world federated learning for brain imaging scientists.
Journal: Frontiers in digital health
In common: MONAI, NiBabel, PyTorch, 4 other tools, structural MRI / diffusion, 1 reference
[4] doi:10.1371/journal.pone.0354511 [code]
TokenUNet: A new case for transformers integration in efficient and interpretable 3D UNets for brain imaging segmentation.
Journal: PloS one
In common: MONAI, NiBabel, PyTorch, 4 other tools, other condition, 1 reference
[5] doi:10.1038/s41467-026-71555-0 [code]
A deep representation learning model to predict response to vagus nerve stimulation.
Journal: Nature communications
In common: MONAI, NiBabel, PyTorch, 4 other tools, structural MRI / diffusion, clinical / translational
[6] doi:10.1186/s13244-026-02365-7 [code]
Super-resolution MRI and 2.5D deep learning for intratumoral-peritumoral radiomics in preoperative prediction of rectal cancer perineural invasion.
Journal: Insights into imaging
In common: MONAI, NiBabel, PyTorch, 4 other tools, structural MRI / diffusion, other condition
[7] doi:10.1186/s13244-026-02296-3 [code]
A pre-trained foundation model framework for multiplanar MRI classification of extramural vascular invasion and mesorectal fascia invasion in rectal cancer.
Journal: Insights into imaging
In common: MONAI, NiBabel, PyTorch, 4 other tools, structural MRI / diffusion, other condition
[8] doi:10.1186/s40708-026-00316-y [code]
Generalizable and explainable deep learning for brain MRI: a multi-cohort evaluation of 3D architectures for age and sex prediction.
Journal: Brain informatics
In common: MONAI, PyTorch, scikit-learn, 3 other tools, structural MRI / diffusion, 1 reference
[9] doi:10.1371/journal.pdig.0001316 [code]
Reliability of a convolutional neural network in segmenting multiple sclerosis lesions from MRI: Impact of data augmentation, image modality and tolerance with U-Net architecture.
Journal: PLOS digital health
In common: MONAI, NiBabel, PyTorch, 3 other tools, structural MRI / diffusion, 1 reference
[10] doi:10.1371/journal.pcbi.1014555 [code]
Body surface potential driven personalisation of electrophysiological digital twins in hypertrophic cardiomyopathy.
Journal: PLoS computational biology
In common: MONAI, NiBabel, PyTorch, 4 other tools, structural MRI / diffusion

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.