OSCR

Exploring the Impact of T2-Weighted MRI Fat Saturation on Radiomics Stability for Brain Radionecrosis Prediction After Skull-Base Proton Therapy: A Pilot Study.

Code ↔ Paper

10 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 10 matches
  1. [1] § 2. Materials and Methods › 2.2. Brain Tissue Segmentation ↔ src/data_curation/tissue_segmentation_methods.ipynb, lines 145–197 · score 0.68 · tissue segmentation, brain extraction, atropos, ANTsPyNet, synthstrip, mri
  2. [2] § 2. Materials and Methods › 2.6. Radiomics Modeling ↔ src/brn_prediction/analysis.py, lines 174–259 · score 0.67 · near zero, CCC thresholds, variance, subsets, filtering, predictive
  3. [3] § 3. Results › 3.2. Radiomics Modeling ↔ src/brn_prediction/analysis.py, lines 263–322 · score 0.64 · homogeneous subgroups, high correlations, CCC thresholded, subsets, signature, sequence
  4. [4] § 3. Results › 3.2. Radiomics Modeling ↔ src/brn_prediction/analysis.py, lines 174–259 · score 0.62 · CCC thresholds, excellent features, subgroups, subsets, Delong, CI
  5. [5] § 2. Materials and Methods › 2.3. Image-Level Processing ↔ src/data_curation/tissue_segmentation_methods.ipynb, lines 145–197 · score 0.62 · brain mask, bias correction, ANTs, segmented, intensity
  6. [6] § 2. Materials and Methods › 2.3. Image-Level Processing ↔ src/stability_analysis/feature_extraction3D.ipynb, lines 314–357 · score 0.62 · binary mask, bias correction, radiomics features, gm, wm, thresholding
  7. [7] § 2. Materials and Methods › 2.6. Radiomics Modeling ↔ src/brn_prediction/analysis.py, lines 146–170 · score 0.59 · repeated stratified, CV, fold, training, cross, prediction
  8. [8] § 2. Materials and Methods › 2.6. Radiomics Modeling ↔ src/brn_prediction/analysis.py, lines 324–363 · score 0.52 · weighted matching, Spearman, DeLong, CI, AUC, signatures
  9. [9] § 2. Materials and Methods › 2.4. Radiomics Feature Extraction and Harmonization ↔ src/stability_analysis/stability_analysis.ipynb, lines 123–168 · score 0.52 · neuroCombat, Combat harmonization, batches, sequences
  10. [10] § 2. Materials and Methods › 2.2. Brain Tissue Segmentation ↔ src/brn_prediction/feature_gen.py, lines 2–25 · score 0.51 · FSL, FAST, csf, wm, segmentation, 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

Python · 384 lines · 17 KB · GPL-3.0 · 5 matches

  1. #%%
  2. from sklearn.model_selection import RepeatedStratifiedKFold
  3. from sklearn.linear_model import LogisticRegression
  4. from sklearn.ensemble import RandomForestClassifier
  5. from sklearn.base import clone
  6. from sklearn.model_selection import cross_val_score, cross_val_predict
  7. from sklearn.metrics import roc_auc_score
  8. from sklearn.model_selection import RepeatedStratifiedKFold, LeaveOneOut
  9. from sklearn.feature_selection import SequentialFeatureSelector
  10. from boruta import BorutaPy
  11. from scipy.stats import mannwhitneyu
  12. from sklearn.preprocessing import StandardScaler
  13. from sklearn.pipeline import make_pipeline
  14. from sklearn.decomposition import PCA
  15. # import neuroCombat as neuroCombat
  16. from neurocombat_sklearn import CombatModel
  17. import matplotlib.pyplot as plt
  18. from scipy.optimize import linear_sum_assignment
  19. import seaborn as sns
  20. import os
  21. import pandas as pd
  22. import numpy as np
  23. from MLstatkit import Delong_test
  24. #%%
  25. FUP_RADIOMICS_FILE = r"/home/sithints/research/projects/t2w_stability/outputs/stability/radiomicsFeatures3D copy.csv"
  26. FUP_STABILITY_FILE = r"/home/sithints/research/projects/t2w_stability/outputs/stability/stability_df copy.csv"
  27. BRN_RADIOMICS_FILE = r"/home/sithints/research/projects/t2w_stability/outputs/brn_prediction/radiomicsFeatures3D copy.csv"
  28. BRN_DB_FILE = r"/home/sithints/research/projects/t2w_stability/outputs/brn_prediction/db copy.xlsx"
  29. OUTDIR = r"/home/sithints/research/projects/t2w_stability/outputs/brn_prediction/analysis_5CV"
  30. os.makedirs(OUTDIR, exist_ok=True)
  31. FEAT_FAMILIES = ["firstorder", "glcm", "glrlm", "glszm", "ngtdm", "gldm"]
  32. BIAS = "none"
  33. ZNORM_ROI = "none"
  34. TISSUE = "gwm"
  35. TYPE = "combat"
  36. CCC_THRESHOLDS = {"baseline":-np.inf, ">=good":0.70, "excellent":0.85}
  37. TARGET_LABEL = "CTCAE_GRADE_NECROSIS>=1"
  38. BATCH_COL = "sequence"
  39. N_SPLITS = 5
  40. RANDOM_STATE = 42
  41. def filter_near_zero(df, threshold = 1e-6, verbose=False): #1e-6 and 1e-3 works
  42. feats = df.columns.to_list()
  43. feats_var = df.var()
  44. mask_feats = feats_var[feats_var<=threshold].index.to_list()
  45. selected_feats = [feat for feat in feats if feat not in mask_feats]
  46. if verbose:
  47. print(f"Deleted {len(mask_feats)}/{len(feats)} near zero features, remaining {len(selected_feats)} features")
  48. return selected_feats
  49. def filter_high_corr(df, threshold=0.85, verbose=False):
  50. corr_matrix = df.corr(method='spearman').abs()
  51. mean_corr = corr_matrix.mean()
  52. ordered_feats = mean_corr.sort_values(ascending=True).index.to_list()
  53. corr_matrix = df[ordered_feats].corr(method='spearman').abs()
  54. up_tri = corr_matrix.where(np.triu(np.ones(corr_matrix.shape), k=1).astype(bool))
  55. mask_feats = [column for column in up_tri.columns if any(up_tri[column]>=threshold)]
  56. selected_feats = [feat for feat in ordered_feats if feat not in mask_feats]
  57. if verbose:
  58. print(f"Deleted {len(mask_feats)}/{len(ordered_feats)} highly corr features, remaining {len(selected_feats)} features")
  59. return selected_feats
  60. def select_topk_utest(X, y, k=5):
  61. scores = []
  62. for i in range(X.shape[1]):
  63. pvalue = mannwhitneyu(X[y==0, i], X[y==1, i], alternative = 'two-sided').pvalue
  64. scores.append(pvalue)
  65. selected_idxs = np.argsort(scores)[:k]
  66. return selected_idxs
  67. def select_topk_boruta(X, y, k=5):
  68. rf = RandomForestClassifier(n_jobs=-1, max_depth=5, class_weight="balanced")
  69. boruta = BorutaPy(estimator=rf, n_estimators='auto', verbose=0, random_state=42)
  70. boruta.fit(X, y)
  71. importance_history = boruta.importance_history_
  72. importance_history = np.nan_to_num(importance_history, 0.0)
  73. importance = np.mean(importance_history, axis=0)
  74. selected_idxs = np.argsort(importance)[::-1][:k]
  75. return selected_idxs
  76. def mean_weighted_matching_correlation(df_A, df_B, method = 'pearson'):
  77. """
  78. Computes the Mean Weighted Matching Correlation (MWMC) between two signatures (feature sets + predictions).
  79. It solves the linear assignment problem on the cost matrix C = 1 - |corr(A_i, B_j)|
  80. and returns the mean absolute correlation of the optimal matching.
  81. """
  82. # Calculate the pairwise absolute correlation matrix
  83. combined = pd.concat([df_A, df_B], axis=1)
  84. corr = combined.corr(method=method).abs()
  85. # Extract the cross-correlation submatrix (A vs B)
  86. cols_A = df_A.columns
  87. cols_B = df_B.columns
  88. cross_corr = corr.loc[cols_A, cols_B].values
  89. # Cost matrix is 1 - absolute correlation
  90. cost_matrix = 1.0 - cross_corr
  91. # Find the optimal bipartite matching
  92. row_ind, col_ind = linear_sum_assignment(cost_matrix)
  93. # Return the mean matched correlation (1 - cost)
  94. return cross_corr[row_ind, col_ind].mean()
  95. def visualize_corr_matrix(corr_matrix, title, figsize=(10,8)):
  96. plt.figure(figsize=figsize)
  97. sns.heatmap(
  98. corr_matrix,
  99. annot=True,
  100. fmt=".2f",
  101. cmap="coolwarm",
  102. vmin=0,
  103. vmax=1,
  104. square=True,
  105. cbar_kws={"shrink": .8}
  106. )
  107. plt.title(title, fontsize=12, pad=15)
  108. plt.xticks(rotation=45, ha='right', fontsize=9)
  109. plt.yticks(fontsize=9)
  110. plt.tight_layout()
  111. os.makedirs(OUTDIR, exist_ok=True)
  112. plt.savefig(os.path.join(OUTDIR, f"{title}.tiff"), dpi=600, bbox_inches='tight')
  113. plt.show()
  114. def cross_val(model, fs_method, num_folds = 5, n_repeats=5, random_state = 42):
  115. def call_fn(X_df, y_df):
  116. cv = RepeatedStratifiedKFold(n_splits=num_folds, n_repeats=n_repeats, random_state=random_state) # LeaveOneOut()
  117. y_preds, y_trues = [], []
  118. X, y = X_df.values, y_df.values
  119. for train_idx, test_idx in cv.split(X, y):
  120. X_train, X_test = X[train_idx], X[test_idx]
  121. y_train, y_test = y[train_idx], y[test_idx]
  122. train_selected_idxs = fs_method(X_train, y_train, k=k)
  123. X_train_selected = X_train[:, train_selected_idxs]
  124. X_test_selected = X_test[:, train_selected_idxs]
  125. y_pred = clone(model).fit(X_train_selected, y_train).predict_proba(X_test_selected)[:, 1]
  126. y_preds.append(y_pred)
  127. y_trues.append(y_test)
  128. y_true = np.concatenate(y_trues)
  129. y_pred = np.concatenate(y_preds)
  130. return y_true, y_pred
  131. return call_fn
  132. combined_signatures = {}
  133. # %%
  134. # Heterogeneous (Mixed-FS) Subgroups Analysis
  135. if __name__=="__main__":
  136. fs_method = select_topk_utest
  137. fup_radiomics_df = pd.read_csv(FUP_RADIOMICS_FILE, index_col=0)
  138. fup_radiomics_df = fup_radiomics_df[(fup_radiomics_df.tissue==TISSUE)&(fup_radiomics_df.bias_correction==BIAS)&(fup_radiomics_df.norm_roi==ZNORM_ROI)].copy().reset_index(drop=True)
  139. features = [feat for feat in fup_radiomics_df.columns for family in FEAT_FAMILIES if family in feat.lower()]
  140. # Combat parameters are learned from follow-up data used for stability study
  141. combat = CombatModel()
  142. variances = fup_radiomics_df[features].copy().var()
  143. zero_var_feats = variances[variances == 0].index
  144. nzvar_features = [feat for feat in features if feat not in zero_var_feats]
  145. combat.fit(data = fup_radiomics_df[nzvar_features].values, sites = fup_radiomics_df[[BATCH_COL]].apply(lambda x: x.astype('category').cat.codes).values)
  146. radiomics_df = pd.read_csv(BRN_RADIOMICS_FILE)[["pid", "sequence", "exclude"]+features]
  147. db = pd.read_excel(BRN_DB_FILE)[["ID", "CTCAE GRADE NECROSIS2"]]
  148. db = db.rename(columns={"ID":"pid"})
  149. radiomics_df = radiomics_df.merge(db, on="pid").reset_index(drop=True)
  150. radiomics_df["CTCAE_GRADE_NECROSIS"] = radiomics_df["CTCAE GRADE NECROSIS2"].fillna(0)
  151. radiomics_df[TARGET_LABEL] = (radiomics_df["CTCAE_GRADE_NECROSIS"]>=1).astype(int)
  152. radiomics_df = radiomics_df.dropna(subset=features, inplace=False)
  153. radiomics_df = radiomics_df[radiomics_df.exclude==0].reset_index(drop=True)
  154. stability_df = pd.read_csv(FUP_STABILITY_FILE)
  155. stability_df = stability_df[(stability_df.bias_correction==BIAS)&(stability_df.normalization==ZNORM_ROI)&(stability_df.tissue==TISSUE)&(stability_df.type==TYPE)]
  156. ## Performance Analysis on the heterogenous data
  157. estimator = make_pipeline(StandardScaler(), LogisticRegression(C=np.inf, random_state = 42)) #no penalty
  158. k = 4
  159. outputs = {}
  160. print(f"\nTarget label: {TARGET_LABEL} (prevalance= {len(radiomics_df[radiomics_df[TARGET_LABEL]==1])} / {len(radiomics_df)} = {radiomics_df[TARGET_LABEL].mean():.3f})")
  161. for ccc_stability, ccc_threshold in CCC_THRESHOLDS.items():
  162. print(f"\tCCC threshold: {ccc_stability}")
  163. combat_data_df = radiomics_df.copy().reset_index(drop=True)
  164. # transforming mixed-FS baseline features using Combat parameters learned from the stability study
  165. combat_data_df[nzvar_features] = combat.transform(data = combat_data_df[nzvar_features].values, sites = combat_data_df[[BATCH_COL]].apply(lambda x: x.astype('category').cat.codes).values)
  166. columns = stability_df[stability_df.ccc>=ccc_threshold].feature.to_list()
  167. stable_features = [feat for feat in columns if feat in features]
  168. print(f"\t\t# stable features = {len(stable_features)}")
  169. filtered_features = filter_near_zero(combat_data_df[stable_features])
  170. filtered_features = filter_high_corr(combat_data_df[filtered_features])
  171. print(f"\t\t# filtered features = {len(filtered_features)}")
  172. X_df = combat_data_df[filtered_features].copy().reset_index(drop=True)
  173. y_df = combat_data_df[TARGET_LABEL].copy().reset_index(drop=True)
  174. selected_idxs = fs_method(X_df.values, y_df.values, k=k)
  175. selected_features = X_df.iloc[:, selected_idxs].columns.to_list()
  176. combined_signatures[("fs + non-fs", ccc_stability)] = selected_features
  177. y_trues, y_preds = cross_val(estimator, fs_method, N_SPLITS, RANDOM_STATE)(X_df, y_df)
  178. _, _, ci, _, auc, _, info = Delong_test(y_trues, y_preds, y_preds, return_ci=True, return_auc=True, verbose = 0)
  179. utest_p = mannwhitneyu(y_preds[y_trues==0], y_preds[y_trues==1], alternative = 'two-sided').pvalue
  180. print(f"\t\t\t {estimator[-1].__class__.__name__}: AUC={auc:.3f} [{ci[0]:.3f}-{ci[1]:.3f}], p = {utest_p:.3f}")
  181. outputs[ccc_stability] = {"y_trues":y_trues, "y_preds":y_preds}
  182. # Delong's test between baseline vs. excellent features; good vs. excellent features
  183. y_trues = outputs[">=good"]["y_trues"]
  184. y_preds_baseline = outputs["baseline"]["y_preds"]
  185. y_preds_good = outputs[">=good"]["y_preds"]
  186. y_preds_excellent = outputs["excellent"]["y_preds"]
  187. _, p_value = Delong_test(y_trues, y_preds_baseline, y_preds_excellent, return_ci=False, return_auc=False, verbose = 0)
  188. print(f"Delong's test between baseline and excellent features: p_value={p_value:.3f}")
  189. _, p_value = Delong_test(y_trues, y_preds_good, y_preds_excellent, return_ci=False, return_auc=False, verbose = 0)
  190. print(f"Delong's test between >=good and excellent features: p_value={p_value:.3f}")
  191. #%%
  192. # Homogeneous subgroups (FS-only vs. non-FS only) analysis
  193. if __name__=="__main__":
  194. fs_method = select_topk_utest
  195. fup_radiomics_df = pd.read_csv(FUP_RADIOMICS_FILE, index_col=0)
  196. fup_radiomics_df = fup_radiomics_df[(fup_radiomics_df.tissue==TISSUE)&(fup_radiomics_df.bias_correction==BIAS)&(fup_radiomics_df.norm_roi==ZNORM_ROI)].reset_index(drop=True)
  197. features = [feat for feat in fup_radiomics_df.columns for family in FEAT_FAMILIES if family in feat.lower()]
  198. radiomics_df = pd.read_csv(BRN_RADIOMICS_FILE)[["pid", "sequence", "exclude"]+features]
  199. db = pd.read_excel(BRN_DB_FILE)[["ID", "CTCAE GRADE NECROSIS2"]]
  200. db = db.rename(columns={"ID":"pid"})
  201. radiomics_df = radiomics_df.merge(db, on="pid").reset_index(drop=True)
  202. radiomics_df["CTCAE_GRADE_NECROSIS"] = radiomics_df["CTCAE GRADE NECROSIS2"].fillna(0)
  203. radiomics_df[TARGET_LABEL] = (radiomics_df["CTCAE_GRADE_NECROSIS"]>=1).astype(int)
  204. radiomics_df = radiomics_df.dropna(subset=features, inplace=False)
  205. grp_radiomics_df = radiomics_df.groupby(by=["pid"]).agg(list).reset_index()
  206. pids_with_both_sequences = grp_radiomics_df[grp_radiomics_df.sequence.apply(lambda x: len(x)==2)].pid.to_list()
  207. radiomics_df = radiomics_df[radiomics_df.pid.isin(pids_with_both_sequences)].reset_index(drop=True)
  208. stability_df = pd.read_csv(FUP_STABILITY_FILE)
  209. stability_df = stability_df[(stability_df.bias_correction==BIAS)&(stability_df.normalization==ZNORM_ROI)&(stability_df.tissue==TISSUE)&(stability_df.type==TYPE)]
  210. estimator = make_pipeline(StandardScaler(), LogisticRegression(C=np.inf, random_state = 42)) #no penalty
  211. k = 2
  212. outputs = {}
  213. for sequence in ["t2w_fs", "t2w_nfs"]:
  214. print(f"\n**Homogeneous Subgroups: Sequence = {sequence}**")
  215. data_df = radiomics_df[(radiomics_df["sequence"]==sequence)].copy().reset_index(drop=True)
  216. print(f"\nTarget label: {TARGET_LABEL} (prevalance= {len(data_df[data_df[TARGET_LABEL]==1])} / {len(data_df)} = {data_df[TARGET_LABEL].mean():.3f})")
  217. for ccc_stability, ccc_threshold in CCC_THRESHOLDS.items():
  218. print(f"\tCCC threshold: {ccc_stability}")
  219. columns = stability_df[stability_df.ccc>=ccc_threshold].feature.to_list()
  220. stable_features = [feat for feat in columns if feat in features]
  221. print(f"\t\t# stable features = {len(stable_features)}")
  222. filtered_features = filter_near_zero(data_df[stable_features])
  223. filtered_features = filter_high_corr(data_df[filtered_features])
  224. print(f"\t\t# filtered features = {len(filtered_features)}")
  225. X_df = data_df[filtered_features].copy().reset_index(drop=True)
  226. y_df = data_df[TARGET_LABEL].copy().reset_index(drop=True)
  227. selected_idxs = fs_method(X_df.values, y_df.values, k=k)
  228. selected_features = X_df.iloc[:, selected_idxs].columns.to_list()
  229. combined_signatures[(sequence, ccc_stability)] = selected_features
  230. y_trues, y_preds = cross_val(estimator, fs_method, N_SPLITS, RANDOM_STATE)(X_df, y_df)
  231. _, _, ci, _, auc, _, info = Delong_test(y_trues, y_preds, y_preds, return_ci=True, return_auc=True, verbose = 0)
  232. utest_p = mannwhitneyu(y_preds[y_trues==0], y_preds[y_trues==1], alternative = 'two-sided').pvalue
  233. print(f"\t\t\t {estimator[-1].__class__.__name__}: AUC={auc:.3f} [{ci[0]:.3f}-{ci[1]:.3f}], p = {utest_p:.3f}")
  234. outputs[(sequence, ccc_stability)] = {"y_trues":y_trues, "y_preds":y_preds}
  235. y_trues = outputs[(sequence, ">=good")]["y_trues"]
  236. y_preds_baseline = outputs[(sequence, "baseline")]["y_preds"]
  237. y_preds_good = outputs[(sequence, ">=good")]["y_preds"]
  238. y_preds_excellent = outputs[(sequence, "excellent")]["y_preds"]
  239. _, p_value = Delong_test(y_trues, y_preds_baseline, y_preds_excellent, return_ci=False, return_auc=False, verbose = 0)
  240. print(f"\t\tDelong's test between baseline and excellent features: p_value={p_value:.3f}")
  241. _, p_value = Delong_test(y_trues, y_preds_good, y_preds_excellent, return_ci=False, return_auc=False, verbose = 0)
  242. print(f"\t\tDelong's test between >=good and excellent features: p_value={p_value:.3f}")
  243. y_trues = outputs[("t2w_fs", "excellent")]["y_trues"]
  244. y_preds_fs = outputs[("t2w_fs", "excellent")]["y_preds"]
  245. y_preds_nfs = outputs[("t2w_nfs", "excellent")]["y_preds"]
  246. _, p_value = Delong_test(y_trues, y_preds_fs, y_preds_nfs, return_ci=False, return_auc=False, verbose = 0)
  247. print(f"Delong's test between excellent fs and nfs features: p_value={p_value:.3f}")
  248. config_names = list(combined_signatures.keys())
  249. n_configs = len(config_names)
  250. pearson_mwmc_matrix = pd.DataFrame(np.zeros((n_configs, n_configs)), index=config_names, columns=config_names)
  251. spearman_mwmc_matrix = pd.DataFrame(np.zeros((n_configs, n_configs)), index=config_names, columns=config_names)
  252. for i in range(n_configs):
  253. for j in range(n_configs):
  254. name_A = config_names[i]
  255. name_B = config_names[j]
  256. pearson_mwmc_matrix.iloc[i, j] = mean_weighted_matching_correlation(radiomics_df[combined_signatures[name_A]], radiomics_df[combined_signatures[name_B]], method='pearson')
  257. spearman_mwmc_matrix.iloc[i, j] = mean_weighted_matching_correlation(radiomics_df[combined_signatures[name_A]], radiomics_df[combined_signatures[name_B]], method='spearman')
  258. for method, matrix in {"pearson":pearson_mwmc_matrix, "spearman":spearman_mwmc_matrix}.items():
  259. visualize_corr_matrix(matrix, method.capitalize() + " Correlation Matrix")
  260. # %%
  261. print(combined_signatures)
  262. '''
  263. {('t2w_fs', 'baseline'): ['wavelet-HLH_glcm_InverseVariance',
  264. 'lbp-3D-k_firstorder_10Percentile'],
  265. ('t2w_fs', '>=good'): ['lbp-3D-k_firstorder_10Percentile',
  266. 'wavelet-LHL_ngtdm_Strength'],
  267. ('t2w_fs', 'excellent'): ['lbp-3D-k_firstorder_10Percentile',
  268. 'wavelet-LHL_ngtdm_Strength'],
  269. ('t2w_nfs', 'baseline'): ['wavelet-LLL_glszm_LargeAreaHighGrayLevelEmphasis',
  270. 'lbp-3D-k_firstorder_10Percentile'],
  271. ('t2w_nfs', '>=good'): ['original_glszm_LargeAreaHighGrayLevelEmphasis',
  272. 'lbp-3D-k_firstorder_10Percentile'],
  273. ('t2w_nfs', 'excellent'): ['wavelet-LLH_gldm_GrayLevelNonUniformity',
  274. 'original_glszm_ZoneVariance']}
  275. '''

analysis.py at commit db60fad, under GPL-3.0 · at the source

Overview

Authors: Sithin Thulasi Seetha1, Giulia Fontana1, Sara Imparato2, Sara Lillo3,4, Lucia Pia Ciccone3, Marina Francesca Achilli2,5, Chiara Paganelli6, Silvia Molinelli7, Alberto Iannalfi3, Guido Baroni6,8, Lorenzo Preda5,9, Ester Orlandi3,5
  1. Clinical and Scientific Department, CNAO National Center for Oncological Hadrontherapy, 27100 Pavia, Italy
  2. Radiology Unit, Clinical and Scientific Department, CNAO National Center for Oncological Hadrontherapy, 27100 Pavia, Italy
  3. Radiation Oncology Unit, Clinical and Scientific Department, CNAO National Center for Oncological Hadrontherapy, 27100 Pavia, Italy
  4. Department of Internal Medicine and Therapeutics, University of Pavia, 27100 Pavia, Italy
  5. Department of Clinical, Surgical, Diagnostic, and Pediatric Sciences, University of Pavia, 27100 Pavia, Italy
  6. Department of Electronics, Information and Bioengineering (DEIB), Politecnico di Milano, 20133 Milan, Italy
  7. Medical Physics Unit, Clinical and Scientific Department, CNAO National Center for Oncological Hadrontherapy, 27100 Pavia, Italy
  8. Bioengineering Unit, Clinical and Scientific Department, CNAO National Center for Oncological Hadrontherapy, 27100 Pavia, Italy
  9. Radiology Institute, Fondazione IRCCS Policlinico San Matteo, 27100 Pavia, Italy
Journal: Cancers, volume 18, issue 16, article 2636
Dates: received 10 July 2026; accepted 11 August 2026; published online 15 August 2026
Type: Research article · Language: English
License: CC BY
Identifiers: DOI 10.3390/cancers18162636 · PMID 42649948 · PMCID PMC13511151 · OpenAlex W7203643199
Open access: gold, a free copy (OpenAlex)
Status: code verified
Categories: structural MRI / diffusion (modality), clinical / translational (subfield)
Methods: Connectivity, Statistics, Machine learning, Preprocessing
Keywords: chordoma, radiomics, brain radionecrosis, magnetic resonance imaging, fat saturation, reproducibility
Topic: Radiomics and Machine Learning in Medical Imaging (Radiology, Nuclear Medicine and Imaging, Medicine), according to OpenAlex
Funding: Italian Association for Cancer Research (AIRC IG 2020 - n.24946)
Citations: not cited yet (Europe PMC); 62 references in the paper

Abstract

Background/Objectives: In skull-base proton therapy, T2-weighted brain magnetic resonance (MR) imaging may be acquired with or without fat saturation (FS). The present study investigated the impact of this protocol variation on radiomics feature stability and brain radionecrosis (BRN) prediction. Methods: Paired T2-weighted FS and non-FS follow-up MR scans of proton-treated skull-base chordoma patients (n = 52) were used to assess feature stability. For BRN prediction (CTCAEv5 grade ≥ 1), baseline planning scans of chordoma and chondrosarcoma patients (n = 80) with mixed FS protocols were used. Following automated brain tissue segmentation, 1911 radiomics features were extracted from cerebrospinal fluid, gray, and/or white matter using PyRadiomics (v3.1.0). Feature stability was quantified using Lin’s concordance correlation coefficient (CCC). Several MR image- and feature-level processing configurations were explored, and the stability of combined gray and white matter features served as the reference for selecting the optimal configuration. Features were stratified based on CCC thresholds and were subjected to univariable feature selection using a Mann–Whitney U-test. Logistic regression was used for predictive modeling and its performance was measured using micro-averaged AUC within a repeated stratified cross-validation framework. Results: Radiomics features were highly sensitive to FS variations (median CCC range: 0.21–0.58). Combat harmonization without image-level processing was selected as the optimal configuration, under which 30% of features achieved CCC ≥ 0.70, and 10.7% demonstrated a CCC ≥ 0.85. Restricting modeling to a highly stable subset eliminated nearly 90% of the baseline features while significantly improving predictive performance (ΔAUC = 0.05, p < 0.001). Conclusions: A subset of radiomics features robust to FS variations was identified. Use of these stable features may mitigate the impact of protocol-induced variability in mixed-FS T2-weighted MR data while simultaneously improving BRN prediction performance. These findings are preliminary and should be interpreted cautiously, given the pilot nature of the study.

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

sithin-cnao/t2w_stability

License: GPL-3.0
State: the link answers, verified on 27 September 2026
Evidence: files inventoried
Commit: db60fadafcc63a6005565731eebea9dc32b1c299, 21 August 2026
Languages: Python (11), Jupyter (4)
Size: 22 files, 15 scripts
Software Heritage: not archived
Found in: the text, “2.5. Stability Analysis”
Holds: README, license file, 4 notebooks
Not found: CITATION.cff, environment file, tests, continuous integration, documentation
Tools: NumPy (11 files), pandas (10 files), SimpleITK (9 files), Matplotlib (7 files), SciPy (5 files), pydicom (4 files), FreeSurfer (3 files), seaborn (3 files), ANTs (2 files), neuroCombat (2 files), PyRadiomics (2 files), FSL (1 file), scikit-learn (1 file), statsmodels (1 file)
Availability: 1 check, the latest on 27 September 2026: the link answers
  • 27 September 2026: the link answers
17 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;
  • 15 scripts, each with its path and the digest of its content;
  • 10 matches between paragraphs of the paper and lines of the code (method lexical-v1);
  • neither the text of the paper nor the code itself.

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

Data

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

Data Availability Statement

The raw data supporting the conclusions of this article will be made available by 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 1, 27 September 2026: the first record

Recorded: type, language, journal, volume, issue, pages, dates, 12 authors, 6 keywords, 1 funder, 54 references.

Cite

This paper

Thulasi Seetha, S., Fontana, G., Imparato, S., Lillo, S., Ciccone, L. P., Achilli, M. F., Paganelli, C., Molinelli, S., Iannalfi, A., Baroni, G., Preda, L., & Orlandi, E. (2026). Exploring the Impact of T2-Weighted MRI Fat Saturation on Radiomics Stability for Brain Radionecrosis Prediction After Skull-Base Proton Therapy: A Pilot Study. Cancers, 18(16), 2636. https://doi.org/10.3390/cancers18162636

BibTeX

@article{thulasiseetha2026exploring,
author = {Thulasi Seetha, Sithin and Fontana, Giulia and Imparato, Sara and Lillo, Sara and Ciccone, Lucia Pia and Achilli, Marina Francesca and Paganelli, Chiara and Molinelli, Silvia and Iannalfi, Alberto and Baroni, Guido and Preda, Lorenzo and Orlandi, Ester},
title = {{Exploring the Impact of T2-Weighted MRI Fat Saturation on Radiomics Stability for Brain Radionecrosis Prediction After Skull-Base Proton Therapy: A Pilot Study}},
journal = {Cancers},
year = {2026},
month = aug,
volume = {18},
number = {16},
pages = {2636},
publisher = {Multidisciplinary Digital Publishing Institute (MDPI)},
issn = {2072-6694},
doi = {10.3390/cancers18162636},
url = {https://doi.org/10.3390/cancers18162636},
pmid = {42649948},
pmcid = {PMC13511151}
}

RIS

TY - JOUR
AU - Thulasi Seetha, Sithin
AU - Fontana, Giulia
AU - Imparato, Sara
AU - Lillo, Sara
AU - Ciccone, Lucia Pia
AU - Achilli, Marina Francesca
AU - Paganelli, Chiara
AU - Molinelli, Silvia
AU - Iannalfi, Alberto
AU - Baroni, Guido
AU - Preda, Lorenzo
AU - Orlandi, Ester
TI - Exploring the Impact of T2-Weighted MRI Fat Saturation on Radiomics Stability for Brain Radionecrosis Prediction After Skull-Base Proton Therapy: A Pilot Study
T2 - Cancers
J2 - Cancers (Basel)
PY - 2026
DA - 2026/08/15
VL - 18
IS - 16
SP - 2636
SN - 2072-6694
PB - Multidisciplinary Digital Publishing Institute (MDPI)
DO - 10.3390/cancers18162636
UR - https://doi.org/10.3390/cancers18162636
LA - en
ER -

CSL-JSON

{
"id": "10.3390/cancers18162636",
"type": "article-journal",
"title": "Exploring the Impact of T2-Weighted MRI Fat Saturation on Radiomics Stability for Brain Radionecrosis Prediction After Skull-Base Proton Therapy: A Pilot Study",
"container-title": "Cancers",
"author": [
{
"family": "Thulasi Seetha",
"given": "Sithin"
},
{
"family": "Fontana",
"given": "Giulia"
},
{
"family": "Imparato",
"given": "Sara"
},
{
"family": "Lillo",
"given": "Sara"
},
{
"family": "Ciccone",
"given": "Lucia Pia"
},
{
"family": "Achilli",
"given": "Marina Francesca"
},
{
"family": "Paganelli",
"given": "Chiara"
},
{
"family": "Molinelli",
"given": "Silvia"
},
{
"family": "Iannalfi",
"given": "Alberto"
},
{
"family": "Baroni",
"given": "Guido"
},
{
"family": "Preda",
"given": "Lorenzo"
},
{
"family": "Orlandi",
"given": "Ester"
}
],
"container-title-short": "Cancers (Basel)",
"volume": "18",
"issue": "16",
"page": "2636",
"DOI": "10.3390/cancers18162636",
"PMID": "42649948",
"PMCID": "PMC13511151",
"ISSN": "2072-6694",
"publisher": "Multidisciplinary Digital Publishing Institute (MDPI)",
"URL": "https://doi.org/10.3390/cancers18162636",
"language": "en",
"issued": {
"date-parts": [
[
2026,
8,
15
]
]
}
}

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/s41598-026-56688-y [code]
On the value of radiomics in addition to clinical measures in emotional conflict fMRI for predicting sertraline response in major depressive disorder.
Journal: Scientific reports
In common: PyRadiomics, pydicom, FSL, 7 other tools, 1 reference
[2] 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: PyRadiomics, pydicom, SimpleITK, 7 other tools, structural MRI / diffusion
[3] 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: SimpleITK, ANTs, FreeSurfer, 8 other tools, structural MRI / diffusion
[4] doi:10.1038/s41586-026-10631-3 [code]
A prognostic human brain network for diffuse midline glioma.
Journal: Nature
In common: pydicom, ANTs, FreeSurfer, 7 other tools, clinical / translational, 1 reference
[5] doi:10.1371/journal.pone.0337726 [code]
Classification of chronic pain and spinal cord stimulation response using machine learning in magnetoencephalography data
Journal: n/a
In common: PyRadiomics, neuroCombat, pydicom, 6 other tools
[6] doi:10.1038/s41598-026-55397-w [code]
Fast surface reconstruction of human brain MRI: benchmarking deep-learning based morphometry tools.
Journal: Scientific reports
In common: PyRadiomics, SimpleITK, ANTs, 5 other tools, structural MRI / diffusion, 1 reference
[7] doi:10.1162/netn.a.547 [code]
An evaluation of the efficacy of single-echo and multi-echo fMRI denoising strategies.
Journal: Network neuroscience (Cambridge, Mass.)
In common: ANTs, FreeSurfer, FSL, 7 other tools, 1 reference
[8] doi:10.1371/journal.pbio.3003856 [code]
Aging and metabolism contribute separately to brain-body health.
Journal: PLoS biology
In common: ANTs, FreeSurfer, FSL, 7 other tools, structural MRI / diffusion, clinical / translational
[9] 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: ANTs, FreeSurfer, FSL, 6 other tools, structural MRI / diffusion, clinical / translational, 1 reference
[10] doi:10.64898/2026.08.18.26360725 [code]
Temporal pole blurring in hippocampal sclerosis reflects seizure-disrupted myelination
Journal: medRxiv (preprint)
In common: neuroCombat, FreeSurfer, statsmodels, 6 other tools, structural MRI / diffusion, 1 reference

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.