OSCR

Classification of chronic pain and spinal cord stimulation response using machine learning in magnetoencephalography data

Code ↔ Paper

8 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 8 matches
  1. [1] § Methods › Feature selection and machine learning ↔ WORC/classification/construct_classifier.py, lines 168–239 · score 0.77 · linear discriminant, ridge regression, linear regression, Bayes, Gaussian, ElasticNet
  2. [2] § Methods › Evaluation ↔ WORC/classification/metrics.py, lines 32–120 · score 0.76 · R2 score, squared error, F1 score, metric, precision, sensitivity
  3. [3] § Methods › Evaluation ↔ WORC/plotting/plot_estimator_performance.py, lines 715–802 · score 0.64 · R2 score, F1 score, precision, sensitivity, AUC, Spearman
  4. [4] § Methods › Feature selection and machine learning ↔ WORC/WORC.py, lines 430–482 · score 0.61 · neighborhood cleaning rule, ADASYN, SMOTE, linear, ElasticNet, resampling
  5. [5] § Methods › Feature selection and machine learning ↔ WORC/classification/fitandscore.py, lines 912–959 · score 0.61 · principal components, dimensionality reduction, variance, Relief, model
  6. [6] § Methods › Experimental setup ↔ WORC/classification/crossval.py, lines 432–515 · score 0.58 · random split cross, hyperparameter optimization, machine, validation, training, classifier
  7. [7] § Methods › Experimental setup ↔ WORC/WORC.py, lines 484–547 · score 0.57 · hyperparameter optimization, random split, cross validation, depth, selection, training
  8. [8] § Methods › Feature selection and machine learning ↔ WORC/WORC.py, lines 381–428 · score 0.54 · Mann Whitney, feature selection, variance, threshold, Relief, model

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 · 2,493 lines · 136 KB · Apache-2.0 · 3 matches

  1. #!/usr/bin/env python
  2. # Copyright 2016-2023 Biomedical Imaging Group Rotterdam, Departments of
  3. # Medical Informatics and Radiology, Erasmus MC, Rotterdam, The Netherlands
  4. #
  5. # Licensed under the Apache License, Version 2.0 (the "License");
  6. # you may not use this file except in compliance with the License.
  7. # You may obtain a copy of the License at
  8. #
  9. # http://www.apache.org/licenses/LICENSE-2.0
  10. #
  11. # Unless required by applicable law or agreed to in writing, software
  12. # distributed under the License is distributed on an "AS IS" BASIS,
  13. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. # See the License for the specific language governing permissions and
  15. # limitations under the License.
  16. import platform
  17. import os
  18. import yaml
  19. import fastr
  20. import graphviz
  21. import configparser
  22. from pathlib import Path
  23. from random import randint
  24. import WORC.IOparser.file_io as io
  25. from fastr.api import ResourceLimit
  26. from WORC.tools.Slicer import Slicer
  27. from WORC.tools.Elastix import Elastix
  28. from WORC.tools.Evaluate import Evaluate
  29. import WORC.addexceptions as WORCexceptions
  30. import WORC.IOparser.config_WORC as config_io
  31. from WORC.detectors.detectors import DebugDetector
  32. from WORC.export.hyper_params_exporter import export_hyper_params_to_latex
  33. from urllib.parse import urlparse
  34. from urllib.request import url2pathname
  35. from WORC.tools.fingerprinting import quantitative_modalities, qualitative_modalities, all_modalities
  36. class WORC(object):
  37. """Workflow for Optimal Radiomics Classification.
  38. A Workflow for Optimal Radiomics Classification (WORC) object that
  39. serves as a pipeline spawner and manager for optimizating radiomics
  40. studies. Depending on the attributes set, the object will spawn an
  41. appropriate pipeline and manage it.
  42. Note that many attributes are lists and can therefore contain multiple
  43. instances. For example, when providing two sequences per patient,
  44. the "images" list contains two items. The type of items in the lists
  45. is described below.
  46. All objects that serve as source for your network, i.e. refer to
  47. actual files to be used, should be formatted as fastr sources suited for
  48. one of the fastr plugings, see also
  49. http://fastr.readthedocs.io/en/stable/fastr.reference.html#ioplugin-reference
  50. The objects should be lists of these fastr sources or dictionaries with the
  51. sample ID's, e.g.
  52. images_train = [{'Patient001': vfs://input/CT001.nii.gz,
  53. 'Patient002': vfs://input/CT002.nii.gz},
  54. {'Patient001': vfs://input/MR001.nii.gz,
  55. 'Patient002': vfs://input/MR002.nii.gz}]
  56. Attributes
  57. ------------------
  58. name: String, default 'WORC'
  59. name of the network.
  60. configs: list, required
  61. Configuration parameters, either ConfigParser objects
  62. created through the defaultconfig function or paths of config .ini
  63. files. (list, required)
  64. labels: list, required
  65. Paths to files containing patient labels (.txt files).
  66. network: automatically generated
  67. The FASTR network generated through the "build" function.
  68. images: list, optional
  69. Paths refering to the images used for Radiomics computation. Images
  70. should be of the ITK Image type.
  71. segmentations: list, optional
  72. Paths refering to the segmentations used for Radiomics computation.
  73. Segmentations should be of the ITK Image type.
  74. semantics: semantic features per image type (list, optional)
  75. masks: state which pixels of images are valid (list, optional)
  76. features: input Radiomics features for classification (list, optional)
  77. metadata: DICOM headers belonging to images (list, optional)
  78. Elastix_Para: parameter files for Elastix (list, optional)
  79. fastr_plugin: plugin to use for FASTR execution
  80. fastr_tempdir: temporary directory to use for FASTR execution
  81. additions: additional inputs for your network (dict, optional)
  82. source_data: data to use as sources for FASTR (dict)
  83. sink_data: data to use as sinks for FASTR (dict)
  84. CopyMetadata: Boolean, default True
  85. when using elastix, copy metadata from image to segmentation or not
  86. """
  87. def __init__(self, name='test'):
  88. """Initialize WORC object.
  89. Set the initial variables all to None, except for some defaults.
  90. Arguments:
  91. name: name of the nework (string, optional)
  92. """
  93. self.name = 'WORC_' + name
  94. # Initialize several objects
  95. self.configs = list()
  96. self.fastrconfigs = list()
  97. self.images_train = list()
  98. self.segmentations_train = list()
  99. self.semantics_train = list()
  100. self.labels_train = list()
  101. self.masks_train = list()
  102. self.masks_normalize_train = list()
  103. self.features_train = list()
  104. self.metadata_train = list()
  105. self.images_test = list()
  106. self.segmentations_test = list()
  107. self.semantics_test = list()
  108. self.labels_test = list()
  109. self.masks_test = list()
  110. self.masks_normalize_test = list()
  111. self.features_test = list()
  112. self.metadata_test = list()
  113. self.trained_model = None
  114. self.Elastix_Para = list()
  115. self.label_names = 'Label1, Label2'
  116. self.fixedsplits = list()
  117. # Set some defaults, name
  118. self.fastr_plugin = 'LinearExecution'
  119. if name == '':
  120. name = [randint(0, 9) for p in range(0, 5)]
  121. self.fastr_tmpdir = os.path.join(fastr.config.mounts['tmp'], self.name)
  122. self.additions = dict()
  123. self.CopyMetadata = True
  124. self.segmode = []
  125. self._add_evaluation = False
  126. self.TrainTest = False
  127. self.OnlyTest = False
  128. self.WindowsCharacterLimitHack = False
  129. # Memory settings for all fastr nodes
  130. self.fastr_memory_parameters = dict()
  131. self.fastr_memory_parameters['FeatureCalculator'] = '14G'
  132. self.fastr_memory_parameters['Classification'] = '6G'
  133. self.fastr_memory_parameters['WORCCastConvert'] = '4G'
  134. self.fastr_memory_parameters['Preprocessing'] = '4G'
  135. self.fastr_memory_parameters['Elastix'] = '4G'
  136. self.fastr_memory_parameters['Transformix'] = '4G'
  137. self.fastr_memory_parameters['Segmentix'] = '6G'
  138. self.fastr_memory_parameters['ComBat'] = '12G'
  139. self.fastr_memory_parameters['PlotEstimator'] = '12G'
  140. self.fastr_memory_parameters['Fingerprinter'] = '12G'
  141. if DebugDetector().do_detection():
  142. print(fastr.config)
  143. def defaultconfig(self):
  144. """Generate a configparser object holding all default configuration values.
  145. Returns:
  146. config: configparser configuration file
  147. """
  148. config = configparser.ConfigParser()
  149. config.optionxform = str
  150. # General configuration of WORC
  151. config['General'] = dict()
  152. config['General']['cross_validation'] = 'True'
  153. config['General']['Segmentix'] = 'True'
  154. config['General']['FeatureCalculators'] = '[predict/CalcFeatures:1.0, pyradiomics/Pyradiomics:1.0]'
  155. config['General']['Preprocessing'] = 'worc/PreProcess:1.0'
  156. config['General']['RegistrationNode'] = "elastix4.8/Elastix:4.8"
  157. config['General']['TransformationNode'] = "elastix4.8/Transformix:4.8"
  158. config['General']['Joblib_ncores'] = '1'
  159. config['General']['Joblib_backend'] = 'threading'
  160. config['General']['tempsave'] = 'True'
  161. config['General']['AssumeSameImageAndMaskMetadata'] = 'False'
  162. config['General']['ComBat'] = 'False'
  163. config['General']['Fingerprint'] = 'True'
  164. config['General']['DoTestNRSNEns'] = 'False'
  165. config['General']['WindowsCharacterLimitHack'] = str(self.WindowsCharacterLimitHack)
  166. # Fingerprinting
  167. config['Fingerprinting'] = dict()
  168. config['Fingerprinting']['max_num_image'] = '100'
  169. config['Fingerprinting']['inputtype'] = 'images'
  170. # Options for the object/patient labels that are used
  171. config['Labels'] = dict()
  172. config['Labels']['label_names'] = 'Label1, Label2'
  173. config['Labels']['modus'] = 'singlelabel'
  174. config['Labels']['url'] = 'WIP'
  175. config['Labels']['projectID'] = 'WIP'
  176. # Preprocessing
  177. config['Preprocessing'] = dict()
  178. config['Preprocessing']['CheckSpacing'] = 'False'
  179. config['Preprocessing']['Clipping'] = 'False'
  180. config['Preprocessing']['Clipping_Range'] = '-1000.0, 3000.0'
  181. config['Preprocessing']['Normalize'] = 'True'
  182. config['Preprocessing']['Normalize_ROI'] = 'Full'
  183. config['Preprocessing']['Method'] = 'z_score'
  184. config['Preprocessing']['ROIDetermine'] = 'Provided'
  185. config['Preprocessing']['ROIdilate'] = 'False'
  186. config['Preprocessing']['ROIdilateradius'] = '10'
  187. config['Preprocessing']['Resampling'] = 'False'
  188. config['Preprocessing']['Resampling_spacing'] = '1, 1, 1'
  189. config['Preprocessing']['BiasCorrection'] = 'False'
  190. config['Preprocessing']['BiasCorrection_Mask'] = 'False'
  191. config['Preprocessing']['CheckOrientation'] = 'False'
  192. config['Preprocessing']['OrientationPrimaryAxis'] = 'axial'
  193. config['Preprocessing']['HistogramEqualization'] = 'False'
  194. config['Preprocessing']['HistogramEqualization_Alpha'] = '0.3'
  195. config['Preprocessing']['HistogramEqualization_Beta'] = '0.3'
  196. config['Preprocessing']['HistogramEqualization_Radius'] = '5'
  197. # Segmentix
  198. config['Segmentix'] = dict()
  199. config['Segmentix']['mask'] = 'None'
  200. config['Segmentix']['segtype'] = 'None'
  201. config['Segmentix']['segradius'] = '5'
  202. config['Segmentix']['N_blobs'] = '1'
  203. config['Segmentix']['fillholes'] = 'True'
  204. config['Segmentix']['remove_small_objects'] = 'False'
  205. config['Segmentix']['min_object_size'] = '2'
  206. # PREDICT - Feature calculation
  207. # Determine which features are calculated
  208. config['ImageFeatures'] = dict()
  209. config['ImageFeatures']['shape'] = 'True'
  210. config['ImageFeatures']['histogram'] = 'True'
  211. config['ImageFeatures']['orientation'] = 'True'
  212. config['ImageFeatures']['texture_Gabor'] = 'True'
  213. config['ImageFeatures']['texture_LBP'] = 'True'
  214. config['ImageFeatures']['texture_GLCM'] = 'True'
  215. config['ImageFeatures']['texture_GLCMMS'] = 'True'
  216. config['ImageFeatures']['texture_GLRLM'] = 'False'
  217. config['ImageFeatures']['texture_GLSZM'] = 'False'
  218. config['ImageFeatures']['texture_NGTDM'] = 'False'
  219. config['ImageFeatures']['coliage'] = 'False'
  220. config['ImageFeatures']['vessel'] = 'True'
  221. config['ImageFeatures']['log'] = 'True'
  222. config['ImageFeatures']['phase'] = 'True'
  223. # Parameter settings for PREDICT feature calculation
  224. # Defines only naming of modalities
  225. config['ImageFeatures']['image_type'] = ''
  226. # How to extract the features in different dimension
  227. config['ImageFeatures']['extraction_mode'] = '2.5D'
  228. # Define frequencies for gabor filter in pixels
  229. config['ImageFeatures']['gabor_frequencies'] = '0.05, 0.2, 0.5'
  230. # Gabor, GLCM angles in degrees and radians, respectively
  231. config['ImageFeatures']['gabor_angles'] = '0, 45, 90, 135'
  232. config['ImageFeatures']['GLCM_angles'] = '0, 0.79, 1.57, 2.36'
  233. # GLCM discretization levels, distances in pixels
  234. config['ImageFeatures']['GLCM_levels'] = '16'
  235. config['ImageFeatures']['GLCM_distances'] = '1, 3'
  236. # LBP radius, number of points in pixels
  237. config['ImageFeatures']['LBP_radius'] = '3, 8, 15'
  238. config['ImageFeatures']['LBP_npoints'] = '12, 24, 36'
  239. # Phase features minimal wavelength and number of scales
  240. config['ImageFeatures']['phase_minwavelength'] = '3'
  241. config['ImageFeatures']['phase_nscale'] = '5'
  242. # Log features sigma of Gaussian in pixels
  243. config['ImageFeatures']['log_sigma'] = '1, 5, 10'
  244. # Vessel features scale range, steps for the range
  245. config['ImageFeatures']['vessel_scale_range'] = '1, 10'
  246. config['ImageFeatures']['vessel_scale_step'] = '2'
  247. # Vessel features radius for erosion to determine boudnary
  248. config['ImageFeatures']['vessel_radius'] = '5'
  249. # Tags from which to extract features, and how to name them
  250. config['ImageFeatures']['dicom_feature_tags'] = '0010 1010, 0010 0040'
  251. config['ImageFeatures']['dicom_feature_labels'] = 'age, sex'
  252. # PyRadiomics - Feature calculation
  253. # Addition to the above, specifically for PyRadiomics
  254. # Mostly based on specific MR Settings: see https://github.com/Radiomics/pyradiomics/blob/master/examples/exampleSettings/exampleMR_NoResampling.yaml
  255. config['PyRadiomics'] = dict()
  256. config['PyRadiomics']['geometryTolerance'] = '0.0001'
  257. config['PyRadiomics']['normalize'] = 'False'
  258. config['PyRadiomics']['normalizeScale'] = '100'
  259. config['PyRadiomics']['resampledPixelSpacing'] = 'None'
  260. config['PyRadiomics']['interpolator'] = 'sitkBSpline'
  261. config['PyRadiomics']['preCrop'] = 'True'
  262. config['PyRadiomics']['binCount'] = config['ImageFeatures']['GLCM_levels'] # BinWidth to sensitive for normalization, thus use binCount
  263. config['PyRadiomics']['binWidth'] = 'None'
  264. config['PyRadiomics']['force2D'] = 'False'
  265. config['PyRadiomics']['force2Ddimension'] = '0' # axial slices, for coronal slices, use dimension 1 and for sagittal, dimension 2.
  266. config['PyRadiomics']['voxelArrayShift'] = '300'
  267. config['PyRadiomics']['Original'] = 'True'
  268. config['PyRadiomics']['Wavelet'] = 'False'
  269. config['PyRadiomics']['LoG'] = 'False'
  270. if config['General']['Segmentix'] == 'True':
  271. config['PyRadiomics']['label'] = '1'
  272. else:
  273. config['PyRadiomics']['label'] = '255'
  274. # Enabled PyRadiomics features
  275. config['PyRadiomics']['extract_firstorder'] = 'False'
  276. config['PyRadiomics']['extract_shape'] = 'True'
  277. config['PyRadiomics']['texture_GLCM'] = 'False'
  278. config['PyRadiomics']['texture_GLRLM'] = 'True'
  279. config['PyRadiomics']['texture_GLSZM'] = 'True'
  280. config['PyRadiomics']['texture_GLDM'] = 'True'
  281. config['PyRadiomics']['texture_NGTDM'] = 'True'
  282. # ComBat Feature Harmonization
  283. config['ComBat'] = dict()
  284. config['ComBat']['language'] = 'python'
  285. config['ComBat']['batch'] = 'Hospital'
  286. config['ComBat']['mod'] = '[]'
  287. config['ComBat']['par'] = '1'
  288. config['ComBat']['eb'] = '1'
  289. config['ComBat']['per_feature'] = '0'
  290. config['ComBat']['excluded_features'] = 'sf_, of_, semf_, pf_'
  291. config['ComBat']['matlab'] = 'C:\\Program Files\\MATLAB\\R2015b\\bin\\matlab.exe'
  292. # Feature OneHotEncoding
  293. config['OneHotEncoding'] = dict()
  294. config['OneHotEncoding']['Use'] = 'False'
  295. config['OneHotEncoding']['feature_labels_tofit'] = ''
  296. # Feature imputation
  297. config['Imputation'] = dict()
  298. config['Imputation']['use'] = 'True'
  299. config['Imputation']['strategy'] = 'mean, median, most_frequent, constant, knn'
  300. config['Imputation']['n_neighbors'] = '5, 5'
  301. config['Imputation']['skipallNaN'] = 'True'
  302. # Feature scaling options
  303. config['FeatureScaling'] = dict()
  304. config['FeatureScaling']['scaling_method'] = 'robust_z_score'
  305. config['FeatureScaling']['skip_features'] = 'semf_, pf_'
  306. # Feature preprocessing before the whole HyperOptimization
  307. config['FeatPreProcess'] = dict()
  308. config['FeatPreProcess']['Use'] = 'False'
  309. config['FeatPreProcess']['Combine'] = 'False'
  310. config['FeatPreProcess']['Combine_method'] = 'mean'
  311. # Feature selection
  312. config['Featsel'] = dict()
  313. config['Featsel']['Variance'] = '1.0'
  314. config['Featsel']['GroupwiseSearch'] = 'True'
  315. config['Featsel']['SelectFromModel'] = '0.275'
  316. config['Featsel']['SelectFromModel_estimator'] = 'Lasso, LR, RF'
  317. config['Featsel']['SelectFromModel_lasso_alpha'] = '0.1, 1.4'
  318. config['Featsel']['SelectFromModel_n_trees'] = '10, 90'
  319. config['Featsel']['UsePCA'] = '0.275'
  320. config['Featsel']['PCAType'] = '95variance, 10, 50, 100'
  321. config['Featsel']['StatisticalTestUse'] = '0.275'
  322. config['Featsel']['StatisticalTestMetric'] = 'MannWhitneyU'
  323. config['Featsel']['StatisticalTestThreshold'] = '-3, 2.5'
  324. config['Featsel']['ReliefUse'] = '0.275'
  325. config['Featsel']['ReliefNN'] = '2, 4'
  326. config['Featsel']['ReliefSampleSize'] = '0.75, 0.2'
  327. config['Featsel']['ReliefDistanceP'] = '1, 3'
  328. config['Featsel']['ReliefNumFeatures'] = '10, 40'
  329. config['Featsel']['RFE'] = '0.0'
  330. config['Featsel']['RFE_estimator'] = config['Featsel']['SelectFromModel_estimator']
  331. config['Featsel']['RFE_lasso_alpha'] = config['Featsel']['SelectFromModel_lasso_alpha']
  332. config['Featsel']['RFE_n_trees'] = config['Featsel']['SelectFromModel_n_trees']
  333. config['Featsel']['RFE_n_features_to_select'] = '10, 90'
  334. config['Featsel']['RFE_step'] = '1, 9'
  335. # Groupwise Featureselection options
  336. config['SelectFeatGroup'] = dict()
  337. config['SelectFeatGroup']['shape_features'] = 'True, False'
  338. config['SelectFeatGroup']['histogram_features'] = 'True, False'
  339. config['SelectFeatGroup']['orientation_features'] = 'True, False'
  340. config['SelectFeatGroup']['texture_Gabor_features'] = 'True, False'
  341. config['SelectFeatGroup']['texture_GLCM_features'] = 'True, False'
  342. config['SelectFeatGroup']['texture_GLDM_features'] = 'True, False'
  343. config['SelectFeatGroup']['texture_GLCMMS_features'] = 'True, False'
  344. config['SelectFeatGroup']['texture_GLRLM_features'] = 'True, False'
  345. config['SelectFeatGroup']['texture_GLSZM_features'] = 'True, False'
  346. config['SelectFeatGroup']['texture_GLDZM_features'] = 'True, False'
  347. config['SelectFeatGroup']['texture_NGTDM_features'] = 'True, False'
  348. config['SelectFeatGroup']['texture_NGLDM_features'] = 'True, False'
  349. config['SelectFeatGroup']['texture_LBP_features'] = 'True, False'
  350. config['SelectFeatGroup']['dicom_features'] = 'False'
  351. config['SelectFeatGroup']['semantic_features'] = 'False'
  352. config['SelectFeatGroup']['coliage_features'] = 'False'
  353. config['SelectFeatGroup']['vessel_features'] = 'True, False'
  354. config['SelectFeatGroup']['phase_features'] = 'True, False'
  355. config['SelectFeatGroup']['fractal_features'] = 'True, False'
  356. config['SelectFeatGroup']['location_features'] = 'True, False'
  357. config['SelectFeatGroup']['rgrd_features'] = 'True, False'
  358. # Select features per toolbox, or simply all
  359. config['SelectFeatGroup']['toolbox'] = 'All, PREDICT, PyRadiomics'
  360. # Select original features, or after transformation of feature space
  361. config['SelectFeatGroup']['original_features'] = 'True'
  362. config['SelectFeatGroup']['wavelet_features'] = 'True, False'
  363. config['SelectFeatGroup']['log_features'] = 'True, False'
  364. # Resampling options
  365. config['Resampling'] = dict()
  366. config['Resampling']['Use'] = '0.20'
  367. config['Resampling']['Method'] =\
  368. 'RandomUnderSampling, RandomOverSampling, NearMiss, ' +\
  369. 'NeighbourhoodCleaningRule, ADASYN, BorderlineSMOTE, SMOTE, ' +\
  370. 'SMOTEENN, SMOTETomek'
  371. config['Resampling']['sampling_strategy'] = 'auto, majority, minority, not minority, not majority, all'
  372. config['Resampling']['n_neighbors'] = '3, 12'
  373. config['Resampling']['k_neighbors'] = '5, 15'
  374. config['Resampling']['threshold_cleaning'] = '0.25, 0.5'
  375. # Classification
  376. config['Classification'] = dict()
  377. config['Classification']['fastr'] = 'True'
  378. config['Classification']['fastr_plugin'] = self.fastr_plugin
  379. config['Classification']['classifiers'] =\
  380. 'SVM, RF, LR, LDA, QDA, GaussianNB, ' +\
  381. 'AdaBoostClassifier, ' +\
  382. 'XGBClassifier'
  383. config['Classification']['max_iter'] = '100000'
  384. config['Classification']['SVMKernel'] = 'linear, poly, rbf'
  385. config['Classification']['SVMC'] = '0, 6'
  386. config['Classification']['SVMdegree'] = '1, 6'
  387. config['Classification']['SVMcoef0'] = '0, 1'
  388. config['Classification']['SVMgamma'] = '-5, 5'
  389. config['Classification']['RFn_estimators'] = '10, 90'
  390. config['Classification']['RFmin_samples_split'] = '2, 3'
  391. config['Classification']['RFmax_depth'] = '5, 5'
  392. config['Classification']['LRpenalty'] = 'l1, l2, elasticnet'
  393. config['Classification']['LRC'] = '0.01, 0.99'
  394. config['Classification']['LR_solver'] = 'lbfgs, saga'
  395. config['Classification']['LR_l1_ratio'] = '0, 1'
  396. config['Classification']['LDA_solver'] = 'svd, lsqr, eigen'
  397. config['Classification']['LDA_shrinkage'] = '-5, 5'
  398. config['Classification']['QDA_reg_param'] = '-5, 5'
  399. config['Classification']['ElasticNet_alpha'] = '-5, 5'
  400. config['Classification']['ElasticNet_l1_ratio'] = '0, 1'
  401. config['Classification']['SGD_alpha'] = '-5, 5'
  402. config['Classification']['SGD_l1_ratio'] = '0, 1'
  403. config['Classification']['SGD_loss'] = 'squared_loss, huber, epsilon_insensitive, squared_epsilon_insensitive'
  404. config['Classification']['SGD_penalty'] = 'none, l2, l1'
  405. config['Classification']['CNB_alpha'] = '0, 1'
  406. config['Classification']['AdaBoost_n_estimators'] = config['Classification']['RFn_estimators']
  407. config['Classification']['AdaBoost_learning_rate'] = '0.01, 0.99'
  408. # Based on https://towardsdatascience.com/doing-xgboost-hyper-parameter-tuning-the-smart-way-part-1-of-2-f6d255a45dde
  409. # and https://www.analyticsvidhya.com/blog/2016/03/complete-guide-parameter-tuning-xgboost-with-codes-python/
  410. # and https://medium.com/data-design/xgboost-hi-im-gamma-what-can-i-do-for-you-and-the-tuning-of-regularization-a42ea17e6ab6
  411. config['Classification']['XGB_boosting_rounds'] = config['Classification']['RFn_estimators']
  412. config['Classification']['XGB_max_depth'] = '3, 12'
  413. config['Classification']['XGB_learning_rate'] = config['Classification']['AdaBoost_learning_rate']
  414. config['Classification']['XGB_gamma'] = '0.01, 9.99'
  415. config['Classification']['XGB_min_child_weight'] = '1, 6'
  416. config['Classification']['XGB_colsample_bytree'] = '0.3, 0.7'
  417. # https://lightgbm.readthedocs.io/en/latest/Parameters-Tuning.html. Mainly prevent overfitting
  418. config['Classification']['LightGBM_num_leaves'] = '5, 95' # Default 31 so search around that
  419. config['Classification']['LightGBM_max_depth'] = config['Classification']['XGB_max_depth'] # Good to limit explicitly to decrease compuytation time and limit overfitting
  420. config['Classification']['LightGBM_min_child_samples'] = '5, 45' # = min_data_in_leaf. Default 20
  421. config['Classification']['LightGBM_reg_alpha'] = config['Classification']['LRC']
  422. config['Classification']['LightGBM_reg_lambda'] = config['Classification']['LRC']
  423. config['Classification']['LightGBM_min_child_weight'] = '-7, 4' # Default 1e-3
  424. # CrossValidation
  425. config['CrossValidation'] = dict()
  426. config['CrossValidation']['Type'] = 'random_split'
  427. config['CrossValidation']['N_iterations'] = '100'
  428. config['CrossValidation']['test_size'] = '0.2'
  429. config['CrossValidation']['fixed_seed'] = 'False'
  430. # Hyperparameter optimization options
  431. config['HyperOptimization'] = dict()
  432. config['HyperOptimization']['scoring_method'] = 'f1_weighted'
  433. config['HyperOptimization']['test_size'] = '0.2'
  434. config['HyperOptimization']['n_splits'] = '5'
  435. config['HyperOptimization']['N_iterations'] = '1000' # represents either wallclock time limit or nr of evaluations when using SMAC
  436. config['HyperOptimization']['n_jobspercore'] = '200' # only relevant when using fastr in classification
  437. config['HyperOptimization']['maxlen'] = '100'
  438. config['HyperOptimization']['ranking_score'] = 'test_score'
  439. config['HyperOptimization']['memory'] = '3G'
  440. config['HyperOptimization']['refit_training_workflows'] = 'False'
  441. config['HyperOptimization']['refit_validation_workflows'] = 'False'
  442. config['HyperOptimization']['fix_random_seed'] = 'False'
  443. # SMAC options
  444. config['SMAC'] = dict()
  445. config['SMAC']['use'] = 'False'
  446. config['SMAC']['n_smac_cores'] = '1'
  447. config['SMAC']['budget_type'] = 'evals' # ['evals', 'time']
  448. config['SMAC']['budget'] = '100' # Nr of evals or time in seconds
  449. config['SMAC']['init_method'] = 'random' # ['sobol', 'random']
  450. config['SMAC']['init_budget'] = '20' # Nr of evals
  451. # Ensemble options
  452. config['Ensemble'] = dict()
  453. config['Ensemble']['Method'] = 'top_N' # ['Single', 'top_N', 'FitNumber', 'ForwardSelection', 'Caruana', 'Bagging']
  454. config['Ensemble']['Size'] = '100' # Size of ensemble in top_N, or number of bags in Bagging
  455. config['Ensemble']['Metric'] = 'Default'
  456. # Evaluation options
  457. config['Evaluation'] = dict()
  458. config['Evaluation']['OverfitScaler'] = 'False'
  459. # Bootstrap options
  460. config['Bootstrap'] = dict()
  461. config['Bootstrap']['Use'] = 'False'
  462. config['Bootstrap']['N_iterations'] = '10000'
  463. return config
  464. def add_tools(self):
  465. """Add several tools to the WORC object."""
  466. self.Tools = Tools()
  467. def build(self, buildtype='training'):
  468. """Build the network based on the given attributes.
  469. Parameters
  470. ----------
  471. buildtype: string, default 'training'
  472. Specify the WORC execution type.
  473. - inference: use if you have a trained classifier and want to
  474. train it on some new images.
  475. - training: use if you want to train a classifier from a dataset.
  476. """
  477. if buildtype == 'training':
  478. self.build_training()
  479. elif buildtype == 'inference':
  480. # raise WORCexceptions.WORCValueError("Inference workflow is still WIP and does not fully work yet.")
  481. self.TrainTest = True
  482. self.OnlyTest = True
  483. self.build_inference()
  484. def build_training(self):
  485. """Build the training network based on the given attributes."""
  486. # We either need images or features for Radiomics
  487. if self.images_test or self.features_test:
  488. if not self.labels_test:
  489. m = "You provided images and/or features for a test set, but not ground truth labels. Please also provide labels for the test set."
  490. raise WORCexceptions.WORCValueError(m)
  491. self.TrainTest = True
  492. if self.images_train or self.features_train:
  493. print('Building training network...')
  494. # We currently require labels for supervised learning
  495. if self.labels_train:
  496. if not self.configs:
  497. print("No configuration given, assuming default")
  498. if self.images_train:
  499. self.configs = [self.defaultconfig()] * len(self.images_train)
  500. else:
  501. self.configs = [self.defaultconfig()] * len(self.features_train)
  502. self.network = fastr.create_network(self.name)
  503. # NOTE: We currently use the first configuration as general config
  504. image_types = list()
  505. for c in range(len(self.configs)):
  506. image_types.append(self.configs[c]['ImageFeatures']['image_type'])
  507. if self.configs[0]['General']['Fingerprint'] == 'True' and any(imt not in all_modalities for imt in image_types):
  508. m = f'One of your image types {image_types} is not one of the valid image types {quantitative_modalities + qualitative_modalities}. This is mandatory to set when performing fingerprinting, see the WORC Documentation (https://worc.readthedocs.io/en/latest/static/configuration.html#imagefeatures).'
  509. raise WORCexceptions.WORCValueError(m)
  510. # Create config source
  511. self.source_class_config = self.network.create_source('ParameterFile', id='config_classification_source', node_group='conf', step_id='general_sources')
  512. # Classification tool and label source
  513. self.source_patientclass_train = self.network.create_source('PatientInfoFile', id='patientclass_train', node_group='pctrain', step_id='train_sources')
  514. if self.labels_test:
  515. self.source_patientclass_test = self.network.create_source('PatientInfoFile', id='patientclass_test', node_group='pctest', step_id='test_sources')
  516. # Add classification node
  517. memory = self.fastr_memory_parameters['Classification']
  518. self.classify = self.network.create_node('worc/TrainClassifier:1.0',
  519. tool_version='1.0',
  520. id='classify',
  521. resources=ResourceLimit(memory=memory),
  522. step_id='WorkflowOptimization')
  523. # Add fingerprinting
  524. if self.configs[0]['General']['Fingerprint'] == 'True':
  525. self.node_fingerprinters = dict()
  526. self.links_fingerprinting = dict()
  527. self.add_fingerprinter(id='classification', type='classification', config_source=self.source_class_config.output)
  528. # Link output of fingerprinter to classification node
  529. self.link_class_1 = self.network.create_link(self.node_fingerprinters['classification'].outputs['config'], self.classify.inputs['config'])
  530. # self.link_class_1.collapse = 'conf'
  531. else:
  532. # Directly parse config to classify node
  533. self.link_class_1 = self.network.create_link(self.source_class_config.output, self.classify.inputs['config'])
  534. self.link_class_1.collapse = 'conf'
  535. if self.fixedsplits:
  536. self.fixedsplits_node = self.network.create_source('CSVFile', id='fixedsplits_source', node_group='conf', step_id='general_sources')
  537. self.classify.inputs['fixedsplits'] = self.fixedsplits_node.output
  538. self.source_ensemble_method =\
  539. self.network.create_constant('String', [self.configs[0]['Ensemble']['Method']],
  540. id='ensemble_method',
  541. step_id='Evaluation')
  542. self.source_ensemble_size =\
  543. self.network.create_constant('String', [self.configs[0]['Ensemble']['Size']],
  544. id='ensemble_size',
  545. step_id='Evaluation')
  546. self.source_LabelType =\
  547. self.network.create_constant('String', [self.configs[0]['Labels']['label_names']],
  548. id='LabelType',
  549. step_id='Evaluation')
  550. memory = self.fastr_memory_parameters['PlotEstimator']
  551. self.plot_estimator =\
  552. self.network.create_node('worc/PlotEstimator:1.0', tool_version='1.0',
  553. id='plot_Estimator',
  554. resources=ResourceLimit(memory=memory),
  555. step_id='Evaluation')
  556. # Outputs
  557. self.sink_classification = self.network.create_sink('HDF5', id='classification', step_id='general_sinks')
  558. self.sink_performance = self.network.create_sink('JsonFile', id='performance', step_id='general_sinks')
  559. self.sink_class_config = self.network.create_sink('ParameterFile', id='config_classification_sink', node_group='conf', step_id='general_sinks')
  560. # Links
  561. if self.configs[0]['General']['Fingerprint'] == 'True':
  562. self.sink_class_config.input = self.node_fingerprinters['classification'].outputs['config']
  563. else:
  564. self.sink_class_config.input = self.source_class_config.output
  565. self.link_class_2 = self.network.create_link(self.source_patientclass_train.output, self.classify.inputs['patientclass_train'])
  566. self.link_class_2.collapse = 'pctrain'
  567. self.plot_estimator.inputs['ensemble_method'] = self.source_ensemble_method.output
  568. self.plot_estimator.inputs['ensemble_size'] = self.source_ensemble_size.output
  569. self.plot_estimator.inputs['label_type'] = self.source_LabelType.output
  570. if self.labels_test:
  571. pinfo = self.source_patientclass_test.output
  572. else:
  573. pinfo = self.source_patientclass_train.output
  574. self.plot_estimator.inputs['prediction'] = self.classify.outputs['classification']
  575. self.plot_estimator.inputs['pinfo'] = pinfo
  576. # Optional SMAC output
  577. if self.configs[0]['SMAC']['use'] == 'True':
  578. self.sink_smac_results = self.network.create_sink('JsonFile', id='smac_results',
  579. step_id='general_sinks')
  580. self.sink_smac_results.input = self.classify.outputs['smac_results']
  581. if self.TrainTest:
  582. # FIXME: the naming here is ugly
  583. self.link_class_3 = self.network.create_link(self.source_patientclass_test.output, self.classify.inputs['patientclass_test'])
  584. self.link_class_3.collapse = 'pctest'
  585. self.sink_classification.input = self.classify.outputs['classification']
  586. self.sink_performance.input = self.plot_estimator.outputs['output_json']
  587. if self.masks_normalize_train:
  588. self.sources_masks_normalize_train = dict()
  589. if self.masks_normalize_test:
  590. self.sources_masks_normalize_test = dict()
  591. # -----------------------------------------------------
  592. # Optionally, add ComBat Harmonization. Currently done
  593. # on full dataset, not in a cross-validation
  594. if self.configs[0]['General']['ComBat'] == 'True':
  595. self.add_ComBat()
  596. if not self.features_train:
  597. # Create nodes to compute features
  598. # General
  599. self.sources_parameters = dict()
  600. self.source_config_pyradiomics = dict()
  601. self.source_toolbox_name = dict()
  602. # Training only
  603. self.calcfeatures_train = dict()
  604. self.featureconverter_train = dict()
  605. self.preprocessing_train = dict()
  606. self.sources_images_train = dict()
  607. self.sinks_features_train = dict()
  608. self.sinks_configs = dict()
  609. self.converters_im_train = dict()
  610. self.converters_seg_train = dict()
  611. self.links_C1_train = dict()
  612. self.featurecalculators = dict()
  613. if self.TrainTest:
  614. # A test set is supplied, for which nodes also need to be created
  615. self.calcfeatures_test = dict()
  616. self.featureconverter_test = dict()
  617. self.preprocessing_test = dict()
  618. self.sources_images_test = dict()
  619. self.sinks_features_test = dict()
  620. self.converters_im_test = dict()
  621. self.converters_seg_test = dict()
  622. self.links_C1_test = dict()
  623. # Check which nodes are necessary
  624. if not self.segmentations_train:
  625. message = "No automatic segmentation method is yet implemented."
  626. raise WORCexceptions.WORCNotImplementedError(message)
  627. elif len(self.segmentations_train) == len(image_types):
  628. # Segmentations provided
  629. self.sources_segmentations_train = dict()
  630. self.sources_segmentations_test = dict()
  631. self.segmode = 'Provided'
  632. elif len(self.segmentations_train) == 1:
  633. # Assume segmentations need to be registered to other modalities
  634. print('\t - Adding Elastix node for image registration.')
  635. self.add_elastix_sourcesandsinks()
  636. pass
  637. else:
  638. nseg = len(self.segmentations_train)
  639. nim = len(image_types)
  640. m = f'Length of segmentations for training is ' +\
  641. f'{nseg}: should be equal to number of images' +\
  642. f' ({nim}) or 1 when using registration.'
  643. raise WORCexceptions.WORCValueError(m)
  644. # BUG: We assume that first type defines if we use segmentix
  645. if self.configs[0]['General']['Segmentix'] == 'True':
  646. # Use the segmentix toolbox for segmentation processing
  647. print('\t - Adding segmentix node for segmentation preprocessing.')
  648. self.sinks_segmentations_segmentix_train = dict()
  649. self.sources_masks_train = dict()
  650. self.converters_masks_train = dict()
  651. self.nodes_segmentix_train = dict()
  652. if self.TrainTest:
  653. # Also use segmentix on the tes set
  654. self.sinks_segmentations_segmentix_test = dict()
  655. self.sources_masks_test = dict()
  656. self.converters_masks_test = dict()
  657. self.nodes_segmentix_test = dict()
  658. if self.semantics_train:
  659. # Semantic features are supplied
  660. self.sources_semantics_train = dict()
  661. if self.metadata_train:
  662. # Metadata to extract patient features from is supplied
  663. self.sources_metadata_train = dict()
  664. if self.semantics_test:
  665. # Semantic features are supplied
  666. self.sources_semantics_test = dict()
  667. if self.metadata_test:
  668. # Metadata to extract patient features from is supplied
  669. self.sources_metadata_test = dict()
  670. # Create a part of the pipeline for each modality
  671. self.modlabels = list()
  672. for nmod, mod in enumerate(image_types):
  673. # Create label for each modality/image
  674. num = 0
  675. label = mod + '_' + str(num)
  676. while label in self.calcfeatures_train.keys():
  677. # if label already exists, add number to label
  678. num += 1
  679. label = mod + '_' + str(num)
  680. self.modlabels.append(label)
  681. # Create required sources and sinks
  682. self.sources_parameters[label] = self.network.create_source('ParameterFile', id=f'config_{label}', step_id='general_sources')
  683. self.sinks_configs[label] = self.network.create_sink('ParameterFile', id=f'config_{label}_sink', node_group='conf', step_id='general_sinks')
  684. self.sources_images_train[label] = self.network.create_source('ITKImageFile', id='images_train_' + label, node_group='train', step_id='train_sources')
  685. if self.TrainTest:
  686. self.sources_images_test[label] = self.network.create_source('ITKImageFile', id='images_test_' + label, node_group='test', step_id='test_sources')
  687. if self.metadata_train and len(self.metadata_train) >= nmod + 1:
  688. self.sources_metadata_train[label] = self.network.create_source('DicomImageFile', id='metadata_train_' + label, node_group='train', step_id='train_sources')
  689. if self.metadata_test and len(self.metadata_test) >= nmod + 1:
  690. self.sources_metadata_test[label] = self.network.create_source('DicomImageFile', id='metadata_test_' + label, node_group='test', step_id='test_sources')
  691. if self.masks_train and len(self.masks_train) >= nmod + 1:
  692. # Create mask source and convert
  693. self.sources_masks_train[label] = self.network.create_source('ITKImageFile', id='mask_train_' + label, node_group='train', step_id='train_sources')
  694. memory = self.fastr_memory_parameters['WORCCastConvert']
  695. self.converters_masks_train[label] =\
  696. self.network.create_node('worc/WORCCastConvert:0.3.2',
  697. tool_version='0.1',
  698. id='convert_mask_train_' + label,
  699. node_group='train',
  700. resources=ResourceLimit(memory=memory),
  701. step_id='FileConversion')
  702. self.converters_masks_train[label].inputs['image'] = self.sources_masks_train[label].output
  703. if self.masks_test and len(self.masks_test) >= nmod + 1:
  704. # Create mask source and convert
  705. self.sources_masks_test[label] = self.network.create_source('ITKImageFile', id='mask_test_' + label, node_group='test', step_id='test_sources')
  706. memory = self.fastr_memory_parameters['WORCCastConvert']
  707. self.converters_masks_test[label] =\
  708. self.network.create_node('worc/WORCCastConvert:0.3.2',
  709. tool_version='0.1',
  710. id='convert_mask_test_' + label,
  711. node_group='test',
  712. resources=ResourceLimit(memory=memory),
  713. step_id='FileConversion')
  714. self.converters_masks_test[label].inputs['image'] = self.sources_masks_test[label].output
  715. # First convert the images
  716. if any(modality in mod for modality in all_modalities):
  717. # Use WORC PXCastConvet for converting image formats
  718. memory = self.fastr_memory_parameters['WORCCastConvert']
  719. self.converters_im_train[label] =\
  720. self.network.create_node('worc/WORCCastConvert:0.3.2',
  721. tool_version='0.1',
  722. id='convert_im_train_' + label,
  723. resources=ResourceLimit(memory=memory),
  724. step_id='FileConversion')
  725. if self.TrainTest:
  726. self.converters_im_test[label] =\
  727. self.network.create_node('worc/WORCCastConvert:0.3.2',
  728. tool_version='0.1',
  729. id='convert_im_test_' + label,
  730. resources=ResourceLimit(memory=memory),
  731. step_id='FileConversion')
  732. else:
  733. raise WORCexceptions.WORCTypeError(('No valid image type for modality {}: {} provided.').format(str(nmod), mod))
  734. # Create required links
  735. self.converters_im_train[label].inputs['image'] = self.sources_images_train[label].output
  736. if self.TrainTest:
  737. self.converters_im_test[label].inputs['image'] = self.sources_images_test[label].output
  738. # -----------------------------------------------------
  739. # Add fingerprinting
  740. if self.configs[0]['General']['Fingerprint'] == 'True':
  741. self.add_fingerprinter(id=label, type='images', config_source=self.sources_parameters[label].output)
  742. # When applying the hack, the fingerprinter job will itself check which files it needs to read
  743. if not self.WindowsCharacterLimitHack:
  744. self.links_fingerprinting[f'{label}_images'] = self.network.create_link(self.converters_im_train[label].outputs['image'], self.node_fingerprinters[label].inputs['images_train'])
  745. self.links_fingerprinting[f'{label}_images'].collapse = 'train'
  746. self.sinks_configs[label].input = self.node_fingerprinters[label].outputs['config']
  747. if nmod == 0:
  748. # When applying the hack, the fingerprinter job will itself check which files it needs to read
  749. if not self.WindowsCharacterLimitHack:
  750. # Also add images from first modality for classification fingerprinter
  751. self.links_fingerprinting['classification'] = self.network.create_link(self.converters_im_train[label].outputs['image'], self.node_fingerprinters['classification'].inputs['images_train'])
  752. self.links_fingerprinting['classification'].collapse = 'train'
  753. else:
  754. self.sinks_configs[label].input = self.sources_parameters[label].output
  755. # -----------------------------------------------------
  756. # Preprocessing
  757. preprocess_node = str(self.configs[nmod]['General']['Preprocessing'])
  758. print('\t - Adding preprocessing node for image preprocessing.')
  759. self.add_preprocessing(preprocess_node, label, nmod)
  760. # -----------------------------------------------------
  761. # Feature calculation
  762. feature_calculators =\
  763. self.configs[nmod]['General']['FeatureCalculators']
  764. feature_calculators = feature_calculators.strip('][').split(', ')
  765. self.featurecalculators[label] = [f.split('/')[0] for f in feature_calculators]
  766. # Add lists for feature calculation and converter objects
  767. self.calcfeatures_train[label] = list()
  768. self.featureconverter_train[label] = list()
  769. if self.TrainTest:
  770. self.calcfeatures_test[label] = list()
  771. self.featureconverter_test[label] = list()
  772. for f in feature_calculators:
  773. print(f'\t - Adding feature calculation node: {f}.')
  774. self.add_feature_calculator(f, label, nmod)
  775. # -----------------------------------------------------
  776. # Create the neccesary nodes for the segmentation
  777. if self.segmode == 'Provided':
  778. # Segmentation ----------------------------------------------------
  779. # Use the provided segmantions for each modality
  780. memory = self.fastr_memory_parameters['WORCCastConvert']
  781. self.sources_segmentations_train[label] =\
  782. self.network.create_source('ITKImageFile',
  783. id='segmentations_train_' + label,
  784. node_group='train',
  785. step_id='train_sources')
  786. self.converters_seg_train[label] =\
  787. self.network.create_node('worc/WORCCastConvert:0.3.2',
  788. tool_version='0.1',
  789. id='convert_seg_train_' + label,
  790. resources=ResourceLimit(memory=memory),
  791. step_id='FileConversion')
  792. self.converters_seg_train[label].inputs['image'] =\
  793. self.sources_segmentations_train[label].output
  794. if self.TrainTest:
  795. self.sources_segmentations_test[label] =\
  796. self.network.create_source('ITKImageFile',
  797. id='segmentations_test_' + label,
  798. node_group='test',
  799. step_id='test_sources')
  800. self.converters_seg_test[label] =\
  801. self.network.create_node('worc/WORCCastConvert:0.3.2',
  802. tool_version='0.1',
  803. id='convert_seg_test_' + label,
  804. resources=ResourceLimit(memory=memory),
  805. step_id='FileConversion')
  806. self.converters_seg_test[label].inputs['image'] =\
  807. self.sources_segmentations_test[label].output
  808. # Add to fingerprinting if required
  809. if self.configs[0]['General']['Fingerprint'] == 'True':
  810. # When applying the hack, the fingerprinter job will itself check which files it needs to read
  811. if not self.WindowsCharacterLimitHack:
  812. self.links_fingerprinting[f'{label}_segmentations'] = self.network.create_link(self.converters_seg_train[label].outputs['image'], self.node_fingerprinters[label].inputs['segmentations_train'])
  813. self.links_fingerprinting[f'{label}_segmentations'].collapse = 'train'
  814. elif self.segmode == 'Register':
  815. # ---------------------------------------------
  816. # Registration nodes: Align segmentation of first
  817. # modality to others using registration with Elastix
  818. self.add_elastix(label, nmod)
  819. # Add to fingerprinting if required
  820. if self.configs[0]['General']['Fingerprint'] == 'True':
  821. # When applying the hack, the fingerprinter job will itself check which files it needs to read
  822. if not self.WindowsCharacterLimitHack:
  823. # Since there are no segmentations yet of this modality, just use those of the first, provided modality
  824. self.links_fingerprinting[f'{label}_segmentations'] = self.network.create_link(self.converters_seg_train[self.modlabels[0]].outputs['image'], self.node_fingerprinters[label].inputs['segmentations_train'])
  825. self.links_fingerprinting[f'{label}_segmentations'].collapse = 'train'
  826. # -----------------------------------------------------
  827. # Optionally, add segmentix, the in-house segmentation
  828. # processor of WORC
  829. if self.configs[nmod]['General']['Segmentix'] == 'True':
  830. self.add_segmentix(label, nmod)
  831. elif self.configs[nmod]['Preprocessing']['Resampling'] == 'True':
  832. raise WORCexceptions.WORCValueError('If you use resampling, ' +
  833. 'have to use segmentix to ' +
  834. ' make sure the mask is ' +
  835. 'also resampled. Please ' +
  836. 'set ' +
  837. 'config["General"]["Segmentix"]' +
  838. 'to "True".')
  839. else:
  840. # Provide source or elastix segmentations to
  841. # feature calculator
  842. for i_node in range(len(self.calcfeatures_train[label])):
  843. if self.segmode == 'Provided':
  844. self.calcfeatures_train[label][i_node].inputs['segmentation'] =\
  845. self.converters_seg_train[label].outputs['image']
  846. elif self.segmode == 'Register':
  847. if nmod > 0:
  848. self.calcfeatures_train[label][i_node].inputs['segmentation'] =\
  849. self.transformix_seg_nodes_train[label].outputs['image']
  850. else:
  851. self.calcfeatures_train[label][i_node].inputs['segmentation'] =\
  852. self.converters_seg_train[label].outputs['image']
  853. if self.TrainTest:
  854. if self.segmode == 'Provided':
  855. self.calcfeatures_test[label][i_node].inputs['segmentation'] =\
  856. self.converters_seg_test[label].outputs['image']
  857. elif self.segmode == 'Register':
  858. if nmod > 0:
  859. self.calcfeatures_test[label][i_node].inputs['segmentation'] =\
  860. self.transformix_seg_nodes_test[label].outputs['image']
  861. else:
  862. self.calcfeatures_test[label][i_node].inputs['segmentation'] =\
  863. self.converters_seg_test[label].outputs['image']
  864. # -----------------------------------------------------
  865. # Optionally, add ComBat Harmonization
  866. if self.configs[0]['General']['ComBat'] == 'True':
  867. # Link features to ComBat
  868. self.links_Combat1_train[label] = list()
  869. for i_node, fname in enumerate(self.featurecalculators[label]):
  870. self.links_Combat1_train[label].append(self.ComBat.inputs['features_train'][f'{label}_{self.featurecalculators[label][i_node]}'] << self.featureconverter_train[label][i_node].outputs['feat_out'])
  871. self.links_Combat1_train[label][i_node].collapse = 'train'
  872. if self.TrainTest:
  873. self.links_Combat1_test[label] = list()
  874. for i_node, fname in enumerate(self.featurecalculators[label]):
  875. self.links_Combat1_test[label].append(self.ComBat.inputs['features_test'][f'{label}_{self.featurecalculators[label][i_node]}'] << self.featureconverter_test[label][i_node].outputs['feat_out'])
  876. self.links_Combat1_test[label][i_node].collapse = 'test'
  877. # -----------------------------------------------------
  878. # Classification nodes
  879. # Add the features from this modality to the classifier node input
  880. self.links_C1_train[label] = list()
  881. self.sinks_features_train[label] = list()
  882. if self.TrainTest:
  883. self.links_C1_test[label] = list()
  884. self.sinks_features_test[label] = list()
  885. for i_node, fname in enumerate(self.featurecalculators[label]):
  886. # Create sink for feature outputs
  887. self.sinks_features_train[label].append(self.network.create_sink('HDF5', id='features_train_' + label + '_' + fname, step_id='train_sinks'))
  888. # Append features to the classification
  889. if not self.configs[0]['General']['ComBat'] == 'True':
  890. if not self.WindowsCharacterLimitHack:
  891. self.links_C1_train[label].append(self.classify.inputs['features_train'][f'{label}_{self.featurecalculators[label][i_node]}'] << self.featureconverter_train[label][i_node].outputs['feat_out'])
  892. self.links_C1_train[label][i_node].collapse = 'train'
  893. # Save output
  894. self.sinks_features_train[label][i_node].input = self.featureconverter_train[label][i_node].outputs['feat_out']
  895. # Similar for testing workflow
  896. if self.TrainTest:
  897. # Create sink for feature outputs
  898. self.sinks_features_test[label].append(self.network.create_sink('HDF5', id='features_test_' + label + '_' + fname, step_id='test_sinks'))
  899. # Append features to the classification
  900. if not self.configs[0]['General']['ComBat'] == 'True':
  901. if not self.WindowsCharacterLimitHack:
  902. self.links_C1_test[label].append(self.classify.inputs['features_test'][f'{label}_{self.featurecalculators[label][i_node]}'] << self.featureconverter_test[label][i_node].outputs['feat_out'])
  903. self.links_C1_test[label][i_node].collapse = 'test'
  904. # Save output
  905. self.sinks_features_test[label][i_node].input = self.featureconverter_test[label][i_node].outputs['feat_out']
  906. else:
  907. # Features already provided: hence we can skip numerous nodes
  908. self.sources_features_train = dict()
  909. self.links_C1_train = dict()
  910. if self.features_test:
  911. self.sources_features_test = dict()
  912. self.links_C1_test = dict()
  913. # Create label for each modality/image
  914. self.modlabels = list()
  915. for num, mod in enumerate(image_types):
  916. num = 0
  917. label = mod + str(num)
  918. while label in self.sources_features_train.keys():
  919. # if label exists, add number to label
  920. num += 1
  921. label = mod + str(num)
  922. self.modlabels.append(label)
  923. # Create a node for the feature computation
  924. self.sources_features_train[label] = self.network.create_source('HDF5', id='features_train_' + label, node_group='train', step_id='train_sources')
  925. # Add the features from this modality to the classifier node input
  926. if not self.WindowsCharacterLimitHack:
  927. self.links_C1_train[label] = self.classify.inputs['features_train'][str(label)] << self.sources_features_train[label].output
  928. self.links_C1_train[label].collapse = 'train'
  929. if self.features_test:
  930. # Create a node for the feature computation
  931. self.sources_features_test[label] = self.network.create_source('HDF5', id='features_test_' + label, node_group='test', step_id='test_sources')
  932. # Add the features from this modality to the classifier node input
  933. if not self.WindowsCharacterLimitHack:
  934. self.links_C1_test[label] = self.classify.inputs['features_test'][str(label)] << self.sources_features_test[label].output
  935. self.links_C1_test[label].collapse = 'test'
  936. # Add input to fingerprinting for classification
  937. if self.configs[0]['General']['Fingerprint'] == 'True':
  938. # When applying the hack, the fingerprinter job will itself check which files it needs to read
  939. if not self.WindowsCharacterLimitHack:
  940. if num == 0:
  941. self.links_fingerprinting['classification'] = self.network.create_link(self.sources_features_train[label].output, self.node_fingerprinters['classification'].inputs['features_train'])
  942. self.links_fingerprinting['classification'].collapse = 'train'
  943. else:
  944. raise WORCexceptions.WORCIOError("Please provide labels for training, i.e., WORC.labels_train or SimpleWORC.labels_from_this_file.")
  945. else:
  946. raise WORCexceptions.WORCIOError("Please provide either images or features.")
  947. def build_inference(self):
  948. """Build a network to test an already trained model on a test dataset based on the given attributes."""
  949. #FIXME WIP
  950. if self.images_test or self.features_test:
  951. if not self.labels_test:
  952. m = "You provided images and/or features for a test set, but not ground truth labels. Please also provide labels for the test set."
  953. raise WORCexceptions.WORCValueError(m)
  954. else:
  955. m = "Please provide either images and/or features for your test set."
  956. raise WORCexceptions.WORCValueError(m)
  957. if not self.configs:
  958. m = 'For a testing workflow, you need to provide a WORC config.ini file'
  959. raise WORCexceptions.WORCValueError(m)
  960. self.network = fastr.create_network(self.name)
  961. # Add trained model node
  962. memory = self.fastr_memory_parameters['Classification']
  963. self.source_trained_model = self.network.create_source('HDF5',
  964. id='trained_model',
  965. node_group='trained_model', step_id='general_sources')
  966. if self.images_test or self.features_test:
  967. print('Building testing network...')
  968. # We currently require labels for supervised learning
  969. if self.labels_test:
  970. # Extract some information from the configs
  971. image_types = list()
  972. for conf_it in range(len(self.configs)):
  973. if type(self.configs[conf_it]) == str:
  974. # Config is a .ini file, load
  975. temp_conf = config_io.load_config(self.configs[conf_it])
  976. else:
  977. temp_conf = self.configs[conf_it]
  978. image_type = temp_conf['ImageFeatures']['image_type']
  979. image_types.append(image_type)
  980. # NOTE: We currently use the first configuration as general config
  981. if conf_it == 0:
  982. print(temp_conf)
  983. ensemble_method = [temp_conf['Ensemble']['Method']]
  984. ensemble_size = [temp_conf['Ensemble']['Size']]
  985. label_names = [temp_conf['Labels']['label_names']]
  986. use_ComBat = temp_conf['General']['ComBat']
  987. use_segmentix = temp_conf['General']['Segmentix']
  988. # Create various input sources
  989. self.source_patientclass_test =\
  990. self.network.create_source('PatientInfoFile',
  991. id='patientclass_test',
  992. node_group='pctest', step_id='test_sources')
  993. self.source_ensemble_method =\
  994. self.network.create_constant('String', ensemble_method,
  995. id='ensemble_method',
  996. step_id='Evaluation')
  997. self.source_ensemble_size =\
  998. self.network.create_constant('String', ensemble_size,
  999. id='ensemble_size',
  1000. step_id='Evaluation')
  1001. self.source_LabelType =\
  1002. self.network.create_constant('String', label_names,
  1003. id='LabelType',
  1004. step_id='Evaluation')
  1005. memory = self.fastr_memory_parameters['PlotEstimator']
  1006. self.plot_estimator =\
  1007. self.network.create_node('worc/PlotEstimator:1.0', tool_version='1.0',
  1008. id='plot_Estimator',
  1009. resources=ResourceLimit(memory=memory),
  1010. step_id='Evaluation')
  1011. # Links to performance creator
  1012. self.plot_estimator.inputs['ensemble_method'] = self.source_ensemble_method.output
  1013. self.plot_estimator.inputs['ensemble_size'] = self.source_ensemble_size.output
  1014. self.plot_estimator.inputs['label_type'] = self.source_LabelType.output
  1015. pinfo = self.source_patientclass_test.output
  1016. self.plot_estimator.inputs['prediction'] = self.source_trained_model.output
  1017. self.plot_estimator.inputs['pinfo'] = pinfo
  1018. # Performance output
  1019. self.sink_performance = self.network.create_sink('JsonFile', id='performance', step_id='general_sinks')
  1020. self.sink_performance.input = self.plot_estimator.outputs['output_json']
  1021. if self.masks_normalize_test:
  1022. self.sources_masks_normalize_test = dict()
  1023. # -----------------------------------------------------
  1024. # Optionally, add ComBat Harmonization. Currently done
  1025. # on full dataset, not in a cross-validation
  1026. if use_ComBat == 'True':
  1027. message = '[ERROR] If you want to use ComBat, you need to provide training images or features as well.'
  1028. raise WORCexceptions.WORCNotImplementedError(message)
  1029. if not self.features_test:
  1030. # Create nodes to compute features
  1031. # General
  1032. self.sources_parameters = dict()
  1033. self.source_config_pyradiomics = dict()
  1034. self.source_toolbox_name = dict()
  1035. # testing only
  1036. self.calcfeatures_test = dict()
  1037. self.featureconverter_test = dict()
  1038. self.preprocessing_test = dict()
  1039. self.sources_images_test = dict()
  1040. self.sinks_features_test = dict()
  1041. self.sinks_configs = dict()
  1042. self.converters_im_test = dict()
  1043. self.converters_seg_test = dict()
  1044. self.links_C1_test = dict()
  1045. self.featurecalculators = dict()
  1046. # Check which nodes are necessary
  1047. if not self.segmentations_test:
  1048. message = "No automatic segmentation method is yet implemented."
  1049. raise WORCexceptions.WORCNotImplementedError(message)
  1050. elif len(self.segmentations_test) == len(image_types):
  1051. # Segmentations provided
  1052. self.sources_segmentations_test = dict()
  1053. self.segmode = 'Provided'
  1054. elif len(self.segmentations_test) == 1:
  1055. # Assume segmentations need to be registered to other modalities
  1056. print('\t - Adding Elastix node for image registration.')
  1057. self.add_elastix_sourcesandsinks()
  1058. pass
  1059. else:
  1060. nseg = len(self.segmentations_test)
  1061. nim = len(image_types)
  1062. m = f'Length of segmentations for testing is ' +\
  1063. f'{nseg}: should be equal to number of images' +\
  1064. f' ({nim}) or 1 when using registration.'
  1065. raise WORCexceptions.WORCValueError(m)
  1066. if use_segmentix == 'True':
  1067. # Use the segmentix toolbox for segmentation processing
  1068. print('\t - Adding segmentix node for segmentation preprocessing.')
  1069. self.sinks_segmentations_segmentix_test = dict()
  1070. self.sources_masks_test = dict()
  1071. self.converters_masks_test = dict()
  1072. self.nodes_segmentix_test = dict()
  1073. if self.semantics_test:
  1074. # Semantic features are supplied
  1075. self.sources_semantics_test = dict()
  1076. if self.metadata_test:
  1077. # Metadata to extract patient features from is supplied
  1078. self.sources_metadata_test = dict()
  1079. # Create a part of the pipeline for each modality
  1080. self.modlabels = list()
  1081. for nmod, mod in enumerate(image_types):
  1082. # Extract some modality specific config info
  1083. if type(self.configs[conf_it]) == str:
  1084. # Config is a .ini file, load
  1085. temp_conf = config_io.load_config(self.configs[nmod])
  1086. else:
  1087. temp_conf = self.configs[nmod]
  1088. # Create label for each modality/image
  1089. num = 0
  1090. label = mod + '_' + str(num)
  1091. while label in self.calcfeatures_test.keys():
  1092. # if label already exists, add number to label
  1093. num += 1
  1094. label = mod + '_' + str(num)
  1095. self.modlabels.append(label)
  1096. # Create required sources and sinks
  1097. self.sources_parameters[label] = self.network.create_source('ParameterFile', id=f'config_{label}', step_id='general_sources')
  1098. self.sources_images_test[label] = self.network.create_source('ITKImageFile', id='images_test_' + label, node_group='test', step_id='test_sources')
  1099. if self.metadata_test and len(self.metadata_test) >= nmod + 1:
  1100. self.sources_metadata_test[label] = self.network.create_source('DicomImageFile', id='metadata_test_' + label, node_group='test', step_id='test_sources')
  1101. if self.masks_test and len(self.masks_test) >= nmod + 1:
  1102. # Create mask source and convert
  1103. self.sources_masks_test[label] = self.network.create_source('ITKImageFile', id='mask_test_' + label, node_group='test', step_id='test_sources')
  1104. memory = self.fastr_memory_parameters['WORCCastConvert']
  1105. self.converters_masks_test[label] =\
  1106. self.network.create_node('worc/WORCCastConvert:0.3.2',
  1107. tool_version='0.1',
  1108. id='convert_mask_test_' + label,
  1109. node_group='test',
  1110. resources=ResourceLimit(memory=memory),
  1111. step_id='FileConversion')
  1112. self.converters_masks_test[label].inputs['image'] = self.sources_masks_test[label].output
  1113. # First convert the images
  1114. if any(modality in mod for modality in all_modalities):
  1115. # Use WORC PXCastConvet for converting image formats
  1116. memory = self.fastr_memory_parameters['WORCCastConvert']
  1117. self.converters_im_test[label] =\
  1118. self.network.create_node('worc/WORCCastConvert:0.3.2',
  1119. tool_version='0.1',
  1120. id='convert_im_test_' + label,
  1121. resources=ResourceLimit(memory=memory),
  1122. step_id='FileConversion')
  1123. else:
  1124. raise WORCexceptions.WORCTypeError(('No valid image type for modality {}: {} provided.').format(str(nmod), mod))
  1125. # Create required links
  1126. self.converters_im_test[label].inputs['image'] = self.sources_images_test[label].output
  1127. # -----------------------------------------------------
  1128. # Preprocessing
  1129. preprocess_node = str(temp_conf['General']['Preprocessing'])
  1130. print('\t - Adding preprocessing node for image preprocessing.')
  1131. self.add_preprocessing(preprocess_node, label, nmod)
  1132. # -----------------------------------------------------
  1133. # Feature calculation
  1134. feature_calculators =\
  1135. temp_conf['General']['FeatureCalculators']
  1136. if not isinstance(feature_calculators, list):
  1137. # Configparser object, need to split string
  1138. feature_calculators = feature_calculators.strip('][').split(', ')
  1139. self.featurecalculators[label] = [f.split('/')[0] for f in feature_calculators]
  1140. else:
  1141. self.featurecalculators[label] = feature_calculators
  1142. # Add lists for feature calculation and converter objects
  1143. self.calcfeatures_test[label] = list()
  1144. self.featureconverter_test[label] = list()
  1145. for f in feature_calculators:
  1146. print(f'\t - Adding feature calculation node: {f}.')
  1147. # remove potential leading spaces due to parsing issues
  1148. if f[0] == ' ':
  1149. f = f[1::]
  1150. self.add_feature_calculator(f, label, nmod)
  1151. # -----------------------------------------------------
  1152. # Create the neccesary nodes for the segmentation
  1153. if self.segmode == 'Provided':
  1154. # Segmentation ----------------------------------------------------
  1155. # Use the provided segmantions for each modality
  1156. memory = self.fastr_memory_parameters['WORCCastConvert']
  1157. self.sources_segmentations_test[label] =\
  1158. self.network.create_source('ITKImageFile',
  1159. id='segmentations_test_' + label,
  1160. node_group='test',
  1161. step_id='test_sources')
  1162. self.converters_seg_test[label] =\
  1163. self.network.create_node('worc/WORCCastConvert:0.3.2',
  1164. tool_version='0.1',
  1165. id='convert_seg_test_' + label,
  1166. resources=ResourceLimit(memory=memory),
  1167. step_id='FileConversion')
  1168. self.converters_seg_test[label].inputs['image'] =\
  1169. self.sources_segmentations_test[label].output
  1170. elif self.segmode == 'Register':
  1171. # ---------------------------------------------
  1172. # Registration nodes: Align segmentation of first
  1173. # modality to others using registration with Elastix
  1174. self.add_elastix(label, nmod)
  1175. # -----------------------------------------------------
  1176. # Optionally, add segmentix, the in-house segmentation
  1177. # processor of WORC
  1178. if temp_conf['General']['Segmentix'] == 'True':
  1179. self.add_segmentix(label, nmod)
  1180. elif temp_conf['Preprocessing']['Resampling'] == 'True':
  1181. raise WORCexceptions.WORCValueError('If you use resampling, ' +
  1182. 'have to use segmentix to ' +
  1183. ' make sure the mask is ' +
  1184. 'also resampled. Please ' +
  1185. 'set ' +
  1186. 'config["General"]["Segmentix"]' +
  1187. 'to "True".')
  1188. else:
  1189. # Provide source or elastix segmentations to
  1190. # feature calculator
  1191. for i_node in range(len(self.calcfeatures_test[label])):
  1192. if self.segmode == 'Provided':
  1193. self.calcfeatures_test[label][i_node].inputs['segmentation'] =\
  1194. self.converters_seg_test[label].outputs['image']
  1195. elif self.segmode == 'Register':
  1196. if nmod > 0:
  1197. self.calcfeatures_test[label][i_node].inputs['segmentation'] =\
  1198. self.transformix_seg_nodes_test[label].outputs['image']
  1199. else:
  1200. self.calcfeatures_test[label][i_node].inputs['segmentation'] =\
  1201. self.converters_seg_test[label].outputs['image']
  1202. # -----------------------------------------------------
  1203. # Optionally, add ComBat Harmonization
  1204. if use_ComBat == 'True':
  1205. # Link features to ComBat
  1206. self.links_Combat1_test[label] = list()
  1207. for i_node, fname in enumerate(self.featurecalculators[label]):
  1208. self.links_Combat1_test[label].append(self.ComBat.inputs['features_test'][f'{label}_{self.featurecalculators[label][i_node]}'] << self.featureconverter_test[label][i_node].outputs['feat_out'])
  1209. self.links_Combat1_test[label][i_node].collapse = 'test'
  1210. # -----------------------------------------------------
  1211. # Output the features
  1212. # Add the features from this modality to the classifier node input
  1213. self.links_C1_test[label] = list()
  1214. self.sinks_features_test[label] = list()
  1215. for i_node, fname in enumerate(self.featurecalculators[label]):
  1216. # Create sink for feature outputs
  1217. node_id = 'features_test_' + label + '_' + fname
  1218. node_id = node_id.replace(':', '_').replace('.', '_').replace('/', '_')
  1219. self.sinks_features_test[label].append(self.network.create_sink('HDF5', id=node_id, step_id='test_sinks'))
  1220. # Save output
  1221. self.sinks_features_test[label][i_node].input = self.featureconverter_test[label][i_node].outputs['feat_out']
  1222. else:
  1223. # Features already provided: hence we can skip numerous nodes
  1224. self.sources_features_train = dict()
  1225. self.links_C1_train = dict()
  1226. if self.features_test:
  1227. self.sources_features_test = dict()
  1228. self.links_C1_test = dict()
  1229. # Create label for each modality/image
  1230. self.modlabels = list()
  1231. for num, mod in enumerate(image_types):
  1232. num = 0
  1233. label = mod + str(num)
  1234. while label in self.sources_features_train.keys():
  1235. # if label exists, add number to label
  1236. num += 1
  1237. label = mod + str(num)
  1238. self.modlabels.append(label)
  1239. # Create a node for the features
  1240. self.sources_features_test[label] = self.network.create_source('HDF5', id='features_test_' + label, node_group='test', step_id='test_sources')
  1241. else:
  1242. raise WORCexceptions.WORCIOError("Please provide labels for training, i.e., WORC.labels_train or SimpleWORC.labels_from_this_file.")
  1243. else:
  1244. raise WORCexceptions.WORCIOError("Please provide either images or features.")
  1245. def add_fingerprinter(self, id, type, config_source):
  1246. """Add WORC Fingerprinter to the network.
  1247. Note: applied per imaging sequence, or per feature file if no
  1248. images are present.
  1249. """
  1250. # Add fingerprinting tool
  1251. memory = self.fastr_memory_parameters['Fingerprinter']
  1252. fingerprinter_node = self.network.create_node('worc/Fingerprinter:1.0',
  1253. tool_version='1.0',
  1254. id=f'fingerprinter_{id}',
  1255. resources=ResourceLimit(memory=memory),
  1256. step_id='FingerPrinting')
  1257. # Add general sources to fingerprinting node
  1258. fingerprinter_node.inputs['config'] = config_source
  1259. fingerprinter_node.inputs['patientclass_train'] = self.source_patientclass_train.output
  1260. # Add type input
  1261. valid_types = ['classification', 'images']
  1262. if type not in valid_types:
  1263. raise WORCexceptions.WORCValueError(f'Type {type} is not valid for fingeprinting. Should be one of {valid_types}.')
  1264. type_node = self.network.create_constant('String', type,
  1265. id=f'type_fingerprint_{id}',
  1266. node_group='train',
  1267. step_id='FingerPrinting')
  1268. fingerprinter_node.inputs['type'] = type_node.output
  1269. # Add to list of fingerprinting nodes
  1270. self.node_fingerprinters[id] = fingerprinter_node
  1271. def add_ComBat(self):
  1272. """Add ComBat harmonization to the network.
  1273. Note: applied on all objects, not in a train-test or cross-val setting.
  1274. """
  1275. memory = self.fastr_memory_parameters['ComBat']
  1276. self.ComBat =\
  1277. self.network.create_node('combat/ComBat:1.0',
  1278. tool_version='1.0',
  1279. id='ComBat',
  1280. resources=ResourceLimit(memory=memory),
  1281. step_id='ComBat')
  1282. # Create sink for ComBat output
  1283. self.sinks_features_train_ComBat = self.network.create_sink('HDF5', id='features_train_ComBat', step_id='ComBat')
  1284. # Create links for inputs
  1285. if self.configs[0]['General']['Fingerprint'] == 'True':
  1286. self.link_combat_1 = self.network.create_link(self.node_fingerprinters['classification'].outputs['config'], self.ComBat.inputs['config'])
  1287. else:
  1288. self.link_combat_1 = self.network.create_link(self.source_class_config.output, self.ComBat.inputs['config'])
  1289. self.link_combat_2 = self.network.create_link(self.source_patientclass_train.output, self.ComBat.inputs['patientclass_train'])
  1290. # self.link_combat_1.collapse = 'conf'
  1291. self.link_combat_2.collapse = 'pctrain'
  1292. self.links_Combat1_train = dict()
  1293. self.links_Combat1_test = dict()
  1294. # Link Combat output to both sink and classify node
  1295. self.links_Combat_out_train = self.network.create_link(self.ComBat.outputs['features_train_out'], self.classify.inputs['features_train'])
  1296. self.links_Combat_out_train.collapse = 'ComBat'
  1297. self.sinks_features_train_ComBat.input = self.ComBat.outputs['features_train_out']
  1298. if self.TrainTest or self.OnlyTest:
  1299. # Create sink for ComBat output
  1300. self.sinks_features_test_ComBat = self.network.create_sink('HDF5', id='features_test_ComBat', step_id='ComBat')
  1301. # Create links for inputs
  1302. self.link_combat_3 = self.network.create_link(self.source_patientclass_test.output, self.ComBat.inputs['patientclass_test'])
  1303. self.link_combat_3.collapse = 'pctest'
  1304. # Link Combat output to both sink and classify node
  1305. self.links_Combat_out_test = self.network.create_link(self.ComBat.outputs['features_test_out'], self.classify.inputs['features_test'])
  1306. self.links_Combat_out_test.collapse = 'ComBat'
  1307. self.sinks_features_test_ComBat.input = self.ComBat.outputs['features_test_out']
  1308. def add_preprocessing(self, preprocess_node, label, nmod):
  1309. """Add nodes required for preprocessing of images."""
  1310. # Extract some general information on the setup
  1311. if type(self.configs[0]) == str:
  1312. # Config is a .ini file, load
  1313. temp_conf = config_io.load_config(self.configs[nmod])
  1314. else:
  1315. temp_conf = self.configs[nmod]
  1316. memory = self.fastr_memory_parameters['Preprocessing']
  1317. if not self.OnlyTest:
  1318. self.preprocessing_train[label] = self.network.create_node(preprocess_node, tool_version='1.0', id='preprocessing_train_' + label, resources=ResourceLimit(memory=memory), step_id='Preprocessing')
  1319. if self.TrainTest:
  1320. self.preprocessing_test[label] = self.network.create_node(preprocess_node, tool_version='1.0', id='preprocessing_test_' + label, resources=ResourceLimit(memory=memory), step_id='Preprocessing')
  1321. # Create required links
  1322. if not self.OnlyTest:
  1323. if temp_conf['General']['Fingerprint'] == 'True':
  1324. self.preprocessing_train[label].inputs['parameters'] = self.node_fingerprinters[label].outputs['config']
  1325. else:
  1326. self.preprocessing_train[label].inputs['parameters'] = self.sources_parameters[label].output
  1327. self.preprocessing_train[label].inputs['image'] = self.converters_im_train[label].outputs['image']
  1328. if self.TrainTest:
  1329. if temp_conf['General']['Fingerprint'] == 'True' and not self.OnlyTest:
  1330. self.preprocessing_test[label].inputs['parameters'] = self.node_fingerprinters[label].outputs['config']
  1331. else:
  1332. self.preprocessing_test[label].inputs['parameters'] = self.sources_parameters[label].output
  1333. self.preprocessing_test[label].inputs['image'] = self.converters_im_test[label].outputs['image']
  1334. if self.metadata_train and len(self.metadata_train) >= nmod + 1:
  1335. self.preprocessing_train[label].inputs['metadata'] = self.sources_metadata_train[label].output
  1336. if self.metadata_test and len(self.metadata_test) >= nmod + 1:
  1337. self.preprocessing_test[label].inputs['metadata'] = self.sources_metadata_test[label].output
  1338. # If there are masks to use in normalization, add them here
  1339. if self.masks_normalize_train:
  1340. self.sources_masks_normalize_train[label] = self.network.create_source('ITKImageFile', id='masks_normalize_train_' + label, node_group='train', step_id='Preprocessing')
  1341. self.preprocessing_train[label].inputs['mask'] = self.sources_masks_normalize_train[label].output
  1342. if self.masks_normalize_test:
  1343. self.sources_masks_normalize_test[label] = self.network.create_source('ITKImageFile', id='masks_normalize_test_' + label, node_group='test', step_id='Preprocessing')
  1344. self.preprocessing_test[label].inputs['mask'] = self.sources_masks_normalize_test[label].output
  1345. def add_feature_calculator(self, calcfeat_node, label, nmod):
  1346. """Add a feature calculation node to the network."""
  1347. # Name of fastr node has to exclude some specific symbols, which
  1348. # are used in the node name
  1349. node_ID = '_'.join([calcfeat_node.replace(':', '_').replace('.', '_').replace('/', '_'),
  1350. label])
  1351. memory = self.fastr_memory_parameters['FeatureCalculator']
  1352. if not self.OnlyTest:
  1353. node_train =\
  1354. self.network.create_node(calcfeat_node,
  1355. tool_version='1.0',
  1356. id='calcfeatures_train_' + node_ID,
  1357. resources=ResourceLimit(memory=memory),
  1358. step_id='Feature_Extraction')
  1359. if self.TrainTest:
  1360. node_test =\
  1361. self.network.create_node(calcfeat_node,
  1362. tool_version='1.0',
  1363. id='calcfeatures_test_' + node_ID,
  1364. resources=ResourceLimit(memory=memory),
  1365. step_id='Feature_Extraction')
  1366. # Check if we need to add pyradiomics specific sources
  1367. if 'pyradiomics' in calcfeat_node.lower():
  1368. if self.configs[0]['General']['Fingerprint'] != 'True':
  1369. # Add a config source
  1370. self.source_config_pyradiomics[label] =\
  1371. self.network.create_source('YamlFile',
  1372. id='config_pyradiomics_' + label,
  1373. node_group='train',
  1374. step_id='Feature_Extraction')
  1375. # Add a format source, which we are going to set to a constant
  1376. # And attach to the tool node
  1377. self.source_format_pyradiomics =\
  1378. self.network.create_constant('String', 'csv',
  1379. id='format_pyradiomics_' + label,
  1380. node_group='train',
  1381. step_id='Feature_Extraction')
  1382. if not self.OnlyTest:
  1383. node_train.inputs['format'] =\
  1384. self.source_format_pyradiomics.output
  1385. if self.TrainTest:
  1386. node_test.inputs['format'] =\
  1387. self.source_format_pyradiomics.output
  1388. # Create required links
  1389. # We can have a different config for different tools
  1390. if not self.OnlyTest:
  1391. if 'pyradiomics' in calcfeat_node.lower():
  1392. if self.configs[0]['General']['Fingerprint'] != 'True':
  1393. node_train.inputs['parameters'] =\
  1394. self.source_config_pyradiomics[label].output
  1395. else:
  1396. node_train.inputs['parameters'] =\
  1397. self.node_fingerprinters[label].outputs['config_pyradiomics']
  1398. else:
  1399. if self.configs[0]['General']['Fingerprint'] == 'True':
  1400. node_train.inputs['parameters'] =\
  1401. self.node_fingerprinters[label].outputs['config']
  1402. else:
  1403. node_train.inputs['parameters'] =\
  1404. self.sources_parameters[label].output
  1405. node_train.inputs['image'] =\
  1406. self.preprocessing_train[label].outputs['image']
  1407. if self.OnlyTest:
  1408. if 'pyradiomics' in calcfeat_node.lower():
  1409. node_test.inputs['parameters'] =\
  1410. self.source_config_pyradiomics[label].output
  1411. else:
  1412. node_test.inputs['parameters'] =\
  1413. self.sources_parameters[label].output
  1414. node_test.inputs['image'] =\
  1415. self.preprocessing_test[label].outputs['image']
  1416. elif self.TrainTest:
  1417. if 'pyradiomics' in calcfeat_node.lower():
  1418. if self.configs[0]['General']['Fingerprint'] != 'True':
  1419. node_test.inputs['parameters'] =\
  1420. self.source_config_pyradiomics[label].output
  1421. else:
  1422. node_test.inputs['parameters'] =\
  1423. self.node_fingerprinters[label].outputs['config_pyradiomics']
  1424. else:
  1425. if self.configs[0]['General']['Fingerprint'] == 'True':
  1426. node_test.inputs['parameters'] =\
  1427. self.node_fingerprinters[label].outputs['config']
  1428. else:
  1429. node_test.inputs['parameters'] =\
  1430. self.sources_parameters[label].output
  1431. node_test.inputs['image'] =\
  1432. self.preprocessing_test[label].outputs['image']
  1433. # PREDICT can extract semantic and metadata features
  1434. if 'predict' in calcfeat_node.lower():
  1435. if self.metadata_train and len(self.metadata_train) >= nmod + 1:
  1436. node_train.inputs['metadata'] =\
  1437. self.sources_metadata_train[label].output
  1438. if self.metadata_test and len(self.metadata_test) >= nmod + 1:
  1439. node_test.inputs['metadata'] =\
  1440. self.sources_metadata_test[label].output
  1441. # If a semantics file is provided, connect to feature extraction tool
  1442. if self.semantics_train and len(self.semantics_train) >= nmod + 1:
  1443. self.sources_semantics_train[label] =\
  1444. self.network.create_source('CSVFile',
  1445. id='semantics_train_' + label,
  1446. step_id='train_sources')
  1447. node_train.inputs['semantics'] =\
  1448. self.sources_semantics_train[label].output
  1449. if self.semantics_test and len(self.semantics_test) >= nmod + 1:
  1450. self.sources_semantics_test[label] =\
  1451. self.network.create_source('CSVFile',
  1452. id='semantics_test_' + label,
  1453. step_id='test_sources')
  1454. node_test.inputs['semantics'] =\
  1455. self.sources_semantics_test[label].output
  1456. # Add feature converter to make features WORC compatible
  1457. if not self.OnlyTest:
  1458. conv_train =\
  1459. self.network.create_node('worc/FeatureConverter:1.0',
  1460. tool_version='1.0',
  1461. id='featureconverter_train_' + node_ID,
  1462. resources=ResourceLimit(memory='4G'),
  1463. step_id='Feature_Extraction')
  1464. conv_train.inputs['feat_in'] = node_train.outputs['features']
  1465. # Add source to tell converter which toolbox we use
  1466. if 'pyradiomics' in calcfeat_node.lower():
  1467. toolbox = 'PyRadiomics'
  1468. elif 'predict' in calcfeat_node.lower():
  1469. toolbox = 'PREDICT'
  1470. else:
  1471. message = f'Toolbox {calcfeat_node} not recognized!'
  1472. raise WORCexceptions.WORCKeyError(message)
  1473. self.source_toolbox_name[label] =\
  1474. self.network.create_constant('String', toolbox,
  1475. id=f'toolbox_name_{toolbox}_{label}',
  1476. step_id='Feature_Extraction')
  1477. if not self.OnlyTest:
  1478. conv_train.inputs['toolbox'] = self.source_toolbox_name[label].output
  1479. if self.configs[0]['General']['Fingerprint'] == 'True':
  1480. conv_train.inputs['config'] =\
  1481. self.node_fingerprinters[label].outputs['config']
  1482. else:
  1483. conv_train.inputs['config'] = self.sources_parameters[label].output
  1484. if self.TrainTest:
  1485. conv_test =\
  1486. self.network.create_node('worc/FeatureConverter:1.0',
  1487. tool_version='1.0',
  1488. id='featureconverter_test_' + node_ID,
  1489. resources=ResourceLimit(memory='4G'),
  1490. step_id='Feature_Extraction')
  1491. conv_test.inputs['feat_in'] = node_test.outputs['features']
  1492. conv_test.inputs['toolbox'] = self.source_toolbox_name[label].output
  1493. if self.OnlyTest:
  1494. conv_test.inputs['config'] =\
  1495. self.sources_parameters[label].output
  1496. elif self.configs[0]['General']['Fingerprint'] == 'True':
  1497. conv_test.inputs['config'] =\
  1498. self.node_fingerprinters[label].outputs['config']
  1499. else:
  1500. conv_test.inputs['config'] =\
  1501. self.sources_parameters[label].output
  1502. # Append to nodes to list
  1503. if not self.OnlyTest:
  1504. self.calcfeatures_train[label].append(node_train)
  1505. self.featureconverter_train[label].append(conv_train)
  1506. if self.TrainTest:
  1507. self.calcfeatures_test[label].append(node_test)
  1508. self.featureconverter_test[label].append(conv_test)
  1509. def add_elastix_sourcesandsinks(self):
  1510. """Add sources and sinks required for image registration."""
  1511. self.sources_segmentation = dict()
  1512. self.segmode = 'Register'
  1513. self.source_Elastix_Parameters = dict()
  1514. if not self.OnlyTest:
  1515. self.elastix_nodes_train = dict()
  1516. self.transformix_seg_nodes_train = dict()
  1517. self.sources_segmentations_train = dict()
  1518. self.sinks_transformations_train = dict()
  1519. self.sinks_segmentations_elastix_train = dict()
  1520. self.sinks_images_elastix_train = dict()
  1521. self.converters_seg_train = dict()
  1522. self.edittransformfile_nodes_train = dict()
  1523. self.transformix_im_nodes_train = dict()
  1524. if self.TrainTest:
  1525. self.elastix_nodes_test = dict()
  1526. self.transformix_seg_nodes_test = dict()
  1527. self.sources_segmentations_test = dict()
  1528. self.sinks_transformations_test = dict()
  1529. self.sinks_segmentations_elastix_test = dict()
  1530. self.sinks_images_elastix_test = dict()
  1531. self.converters_seg_test = dict()
  1532. self.edittransformfile_nodes_test = dict()
  1533. self.transformix_im_nodes_test = dict()
  1534. def add_elastix(self, label, nmod):
  1535. """ Add image registration through elastix to network."""
  1536. # Create sources and converter for only for the given segmentation,
  1537. # which should be on the first modality
  1538. if nmod == 0:
  1539. memory = self.fastr_memory_parameters['WORCCastConvert']
  1540. if not self.OnlyTest:
  1541. self.sources_segmentations_train[label] =\
  1542. self.network.create_source('ITKImageFile',
  1543. id='segmentations_train_' + label,
  1544. node_group='train',
  1545. step_id='train_sources')
  1546. self.converters_seg_train[label] =\
  1547. self.network.create_node('worc/WORCCastConvert:0.3.2',
  1548. tool_version='0.1',
  1549. id='convert_seg_train_' + label,
  1550. resources=ResourceLimit(memory=memory),
  1551. step_id='FileConversion')
  1552. self.converters_seg_train[label].inputs['image'] =\
  1553. self.sources_segmentations_train[label].output
  1554. if self.TrainTest:
  1555. self.sources_segmentations_test[label] =\
  1556. self.network.create_source('ITKImageFile',
  1557. id='segmentations_test_' + label,
  1558. node_group='test',
  1559. step_id='test_sources')
  1560. self.converters_seg_test[label] =\
  1561. self.network.create_node('worc/WORCCastConvert:0.3.2',
  1562. tool_version='0.1',
  1563. id='convert_seg_test_' + label,
  1564. resources=ResourceLimit(memory=memory),
  1565. step_id='FileConversion')
  1566. self.converters_seg_test[label].inputs['image'] =\
  1567. self.sources_segmentations_test[label].output
  1568. # Assume provided segmentation is on first modality
  1569. if nmod > 0:
  1570. # Use elastix and transformix for registration
  1571. # NOTE: Assume elastix node type is on first configuration
  1572. elastix_node =\
  1573. str(self.configs[0]['General']['RegistrationNode'])
  1574. transformix_node =\
  1575. str(self.configs[0]['General']['TransformationNode'])
  1576. memory_elastix = self.fastr_memory_parameters['Elastix']
  1577. if not self.OnlyTest:
  1578. self.elastix_nodes_train[label] =\
  1579. self.network.create_node(elastix_node,
  1580. tool_version='0.2',
  1581. id='elastix_train_' + label,
  1582. resources=ResourceLimit(memory=memory_elastix),
  1583. step_id='Image_Registration')
  1584. memory_transformix = self.fastr_memory_parameters['Elastix']
  1585. self.transformix_seg_nodes_train[label] =\
  1586. self.network.create_node(transformix_node,
  1587. tool_version='0.2',
  1588. id='transformix_seg_train_' + label,
  1589. resources=ResourceLimit(memory=memory_transformix),
  1590. step_id='Image_Registration')
  1591. self.transformix_im_nodes_train[label] =\
  1592. self.network.create_node(transformix_node,
  1593. tool_version='0.2',
  1594. id='transformix_im_train_' + label,
  1595. resources=ResourceLimit(memory=memory_transformix),
  1596. step_id='Image_Registration')
  1597. if self.TrainTest:
  1598. self.elastix_nodes_test[label] =\
  1599. self.network.create_node(elastix_node,
  1600. tool_version='0.2',
  1601. id='elastix_test_' + label,
  1602. resources=ResourceLimit(memory=memory_elastix),
  1603. step_id='Image_Registration')
  1604. self.transformix_seg_nodes_test[label] =\
  1605. self.network.create_node(transformix_node,
  1606. tool_version='0.2',
  1607. id='transformix_seg_test_' + label,
  1608. resources=ResourceLimit(memory=memory_transformix),
  1609. step_id='Image_Registration')
  1610. self.transformix_im_nodes_test[label] =\
  1611. self.network.create_node(transformix_node,
  1612. tool_version='0.2',
  1613. id='transformix_im_test_' + label,
  1614. resources=ResourceLimit(memory=memory_transformix),
  1615. step_id='Image_Registration')
  1616. # Create sources_segmentation
  1617. # M1 = moving, others = fixed
  1618. if not self.OnlyTest:
  1619. self.elastix_nodes_train[label].inputs['fixed_image'] =\
  1620. self.converters_im_train[label].outputs['image']
  1621. self.elastix_nodes_train[label].inputs['moving_image'] =\
  1622. self.converters_im_train[self.modlabels[0]].outputs['image']
  1623. # Add node that copies metadata from the image to the
  1624. # segmentation if required
  1625. if self.CopyMetadata and not self.OnlyTest:
  1626. # Copy metadata from the image which was registered to
  1627. # the segmentation, if it is not created yet
  1628. if not hasattr(self, "copymetadata_nodes_train"):
  1629. # NOTE: Do this for first modality, as we assume
  1630. # the segmentation is on that one
  1631. self.copymetadata_nodes_train = dict()
  1632. self.copymetadata_nodes_train[self.modlabels[0]] =\
  1633. self.network.create_node('itktools/0.3.2/CopyMetadata:1.0',
  1634. tool_version='1.0',
  1635. id='CopyMetadata_train_' + self.modlabels[0],
  1636. step_id='Image_Registration')
  1637. self.copymetadata_nodes_train[self.modlabels[0]].inputs["source"] =\
  1638. self.converters_im_train[self.modlabels[0]].outputs['image']
  1639. self.copymetadata_nodes_train[self.modlabels[0]].inputs["destination"] =\
  1640. self.converters_seg_train[self.modlabels[0]].outputs['image']
  1641. self.transformix_seg_nodes_train[label].inputs['image'] =\
  1642. self.copymetadata_nodes_train[self.modlabels[0]].outputs['output']
  1643. else:
  1644. self.transformix_seg_nodes_train[label].inputs['image'] =\
  1645. self.converters_seg_train[self.modlabels[0]].outputs['image']
  1646. if self.TrainTest:
  1647. self.elastix_nodes_test[label].inputs['fixed_image'] =\
  1648. self.converters_im_test[label].outputs['image']
  1649. self.elastix_nodes_test[label].inputs['moving_image'] =\
  1650. self.converters_im_test[self.modlabels[0]].outputs['image']
  1651. if self.CopyMetadata:
  1652. # Copy metadata from the image which was registered
  1653. # to the segmentation
  1654. if not hasattr(self, "copymetadata_nodes_test"):
  1655. # NOTE: Do this for first modality, as we assume
  1656. # the segmentation is on that one
  1657. self.copymetadata_nodes_test = dict()
  1658. self.copymetadata_nodes_test[self.modlabels[0]] =\
  1659. self.network.create_node('itktools/0.3.2/CopyMetadata:1.0',
  1660. tool_version='1.0',
  1661. id='CopyMetadata_test_' + self.modlabels[0],
  1662. step_id='Image_Registration')
  1663. self.copymetadata_nodes_test[self.modlabels[0]].inputs["source"] =\
  1664. self.converters_im_test[self.modlabels[0]].outputs['image']
  1665. self.copymetadata_nodes_test[self.modlabels[0]].inputs["destination"] =\
  1666. self.converters_seg_test[self.modlabels[0]].outputs['image']
  1667. self.transformix_seg_nodes_test[label].inputs['image'] =\
  1668. self.copymetadata_nodes_test[self.modlabels[0]].outputs['output']
  1669. else:
  1670. self.transformix_seg_nodes_test[label].inputs['image'] =\
  1671. self.converters_seg_test[self.modlabels[0]].outputs['image']
  1672. # Apply registration to input modalities
  1673. self.source_Elastix_Parameters[label] =\
  1674. self.network.create_source('ElastixParameterFile',
  1675. id='Elastix_Para_' + label,
  1676. node_group='elpara',
  1677. step_id='Image_Registration')
  1678. if not self.OnlyTest:
  1679. self.link_elparam_train =\
  1680. self.network.create_link(self.source_Elastix_Parameters[label].output,
  1681. self.elastix_nodes_train[label].inputs['parameters'])
  1682. self.link_elparam_train.collapse = 'elpara'
  1683. if self.TrainTest:
  1684. self.link_elparam_test =\
  1685. self.network.create_link(self.source_Elastix_Parameters[label].output,
  1686. self.elastix_nodes_test[label].inputs['parameters'])
  1687. self.link_elparam_test.collapse = 'elpara'
  1688. if self.masks_train:
  1689. self.elastix_nodes_train[label].inputs['fixed_mask'] =\
  1690. self.converters_masks_train[label].outputs['image']
  1691. self.elastix_nodes_train[label].inputs['moving_mask'] =\
  1692. self.converters_masks_train[self.modlabels[0]].outputs['image']
  1693. if self.TrainTest:
  1694. if self.masks_test:
  1695. self.elastix_nodes_test[label].inputs['fixed_mask'] =\
  1696. self.converters_masks_test[label].outputs['image']
  1697. self.elastix_nodes_test[label].inputs['moving_mask'] =\
  1698. self.converters_masks_test[self.modlabels[0]].outputs['image']
  1699. # Change the FinalBSpline Interpolation order to 0 as required for binarie images: see https://github.com/SuperElastix/elastix/wiki/FAQ
  1700. if not self.OnlyTest:
  1701. self.edittransformfile_nodes_train[label] =\
  1702. self.network.create_node('elastixtools/EditElastixTransformFile:0.1',
  1703. tool_version='0.1',
  1704. id='EditElastixTransformFile_train_' + label,
  1705. step_id='Image_Registration')
  1706. self.edittransformfile_nodes_train[label].inputs['set'] =\
  1707. ["FinalBSplineInterpolationOrder=0"]
  1708. self.edittransformfile_nodes_train[label].inputs['transform'] =\
  1709. self.elastix_nodes_train[label].outputs['transform'][-1]
  1710. if self.TrainTest:
  1711. self.edittransformfile_nodes_test[label] =\
  1712. self.network.create_node('elastixtools/EditElastixTransformFile:0.1',
  1713. tool_version='0.1',
  1714. id='EditElastixTransformFile_test_' + label,
  1715. step_id='Image_Registration')
  1716. self.edittransformfile_nodes_test[label].inputs['set'] =\
  1717. ["FinalBSplineInterpolationOrder=0"]
  1718. self.edittransformfile_nodes_test[label].inputs['transform'] =\
  1719. self.elastix_nodes_test[label].outputs['transform'][-1]
  1720. # Link data and transformation to transformix and source
  1721. if not self.OnlyTest:
  1722. self.transformix_seg_nodes_train[label].inputs['transform'] =\
  1723. self.edittransformfile_nodes_train[label].outputs['transform']
  1724. self.transformix_im_nodes_train[label].inputs['transform'] =\
  1725. self.elastix_nodes_train[label].outputs['transform'][-1]
  1726. self.transformix_im_nodes_train[label].inputs['image'] =\
  1727. self.converters_im_train[self.modlabels[0]].outputs['image']
  1728. if self.TrainTest:
  1729. self.transformix_seg_nodes_test[label].inputs['transform'] =\
  1730. self.edittransformfile_nodes_test[label].outputs['transform']
  1731. self.transformix_im_nodes_test[label].inputs['transform'] =\
  1732. self.elastix_nodes_test[label].outputs['transform'][-1]
  1733. self.transformix_im_nodes_test[label].inputs['image'] =\
  1734. self.converters_im_test[self.modlabels[0]].outputs['image']
  1735. if self.configs[nmod]['General']['Segmentix'] != 'True':
  1736. if not self.OnlyTest:
  1737. # These segmentations serve as input for the feature calculation
  1738. for i_node in range(len(self.calcfeatures_train[label])):
  1739. self.calcfeatures_train[label][i_node].inputs['segmentation'] =\
  1740. self.transformix_seg_nodes_train[label].outputs['image']
  1741. if self.TrainTest:
  1742. self.calcfeatures_test[label][i_node].inputs['segmentation'] =\
  1743. self.transformix_seg_nodes_test[label].outputs['image']
  1744. else:
  1745. for i_node in range(len(self.calcfeatures_test[label])):
  1746. self.calcfeatures_test[label][i_node].inputs['segmentation'] =\
  1747. self.transformix_seg_nodes_test[label].outputs['image']
  1748. # Save outputfor the training set
  1749. if not self.OnlyTest:
  1750. self.sinks_transformations_train[label] =\
  1751. self.network.create_sink('ElastixTransformFile',
  1752. id='transformations_train_' + label,
  1753. step_id='train_sinks')
  1754. self.sinks_segmentations_elastix_train[label] =\
  1755. self.network.create_sink('ITKImageFile',
  1756. id='segmentations_out_elastix_train_' + label,
  1757. step_id='train_sinks')
  1758. self.sinks_images_elastix_train[label] =\
  1759. self.network.create_sink('ITKImageFile',
  1760. id='images_out_elastix_train_' + label,
  1761. step_id='train_sinks')
  1762. self.sinks_transformations_train[label].input =\
  1763. self.elastix_nodes_train[label].outputs['transform']
  1764. self.sinks_segmentations_elastix_train[label].input =\
  1765. self.transformix_seg_nodes_train[label].outputs['image']
  1766. self.sinks_images_elastix_train[label].input =\
  1767. self.transformix_im_nodes_train[label].outputs['image']
  1768. # Save output for the test set
  1769. if self.TrainTest:
  1770. self.sinks_transformations_test[label] =\
  1771. self.network.create_sink('ElastixTransformFile',
  1772. id='transformations_test_' + label,
  1773. step_id='test_sinks')
  1774. self.sinks_segmentations_elastix_test[label] =\
  1775. self.network.create_sink('ITKImageFile',
  1776. id='segmentations_out_elastix_test_' + label,
  1777. step_id='test_sinks')
  1778. self.sinks_images_elastix_test[label] =\
  1779. self.network.create_sink('ITKImageFile',
  1780. id='images_out_elastix_test_' + label,
  1781. step_id='test_sinks')
  1782. self.sinks_transformations_test[label].input =\
  1783. self.elastix_nodes_test[label].outputs['transform']
  1784. self.sinks_segmentations_elastix_test[label].input =\
  1785. self.transformix_seg_nodes_test[label].outputs['image']
  1786. self.sinks_images_elastix_test[label].input =\
  1787. self.transformix_im_nodes_test[label].outputs['image']
  1788. def add_segmentix(self, label, nmod):
  1789. """Add segmentix to the network."""
  1790. # Segmentix nodes -------------------------------------------------
  1791. # Use segmentix node to convert input segmentation into
  1792. # correct contour
  1793. if not self.OnlyTest:
  1794. if label not in self.sinks_segmentations_segmentix_train:
  1795. self.sinks_segmentations_segmentix_train[label] =\
  1796. self.network.create_sink('ITKImageFile',
  1797. id='segmentations_out_segmentix_train_' + label,
  1798. step_id='train_sinks')
  1799. memory = self.fastr_memory_parameters['Segmentix']
  1800. self.nodes_segmentix_train[label] =\
  1801. self.network.create_node('segmentix/Segmentix:1.0',
  1802. tool_version='1.0',
  1803. id='segmentix_train_' + label,
  1804. resources=ResourceLimit(memory=memory),
  1805. step_id='Preprocessing')
  1806. # Input the image
  1807. self.nodes_segmentix_train[label].inputs['image'] =\
  1808. self.converters_im_train[label].outputs['image']
  1809. # Input the metadata
  1810. if self.metadata_train and len(self.metadata_train) >= nmod + 1:
  1811. self.nodes_segmentix_train[label].inputs['metadata'] = self.sources_metadata_train[label].output
  1812. # Input the segmentation
  1813. if not self.OnlyTest:
  1814. if hasattr(self, 'transformix_seg_nodes_train'):
  1815. if label in self.transformix_seg_nodes_train.keys():
  1816. # Use output of registration in segmentix
  1817. self.nodes_segmentix_train[label].inputs['segmentation_in'] =\
  1818. self.transformix_seg_nodes_train[label].outputs['image']
  1819. else:
  1820. # Use original segmentation
  1821. self.nodes_segmentix_train[label].inputs['segmentation_in'] =\
  1822. self.converters_seg_train[label].outputs['image']
  1823. else:
  1824. # Use original segmentation
  1825. self.nodes_segmentix_train[label].inputs['segmentation_in'] =\
  1826. self.converters_seg_train[label].outputs['image']
  1827. # Input the parameters
  1828. if not self.OnlyTest:
  1829. if self.configs[0]['General']['Fingerprint'] == 'True':
  1830. self.nodes_segmentix_train[label].inputs['parameters'] =\
  1831. self.node_fingerprinters[label].outputs['config']
  1832. else:
  1833. self.nodes_segmentix_train[label].inputs['parameters'] =\
  1834. self.sources_parameters[label].output
  1835. self.sinks_segmentations_segmentix_train[label].input =\
  1836. self.nodes_segmentix_train[label].outputs['segmentation_out']
  1837. if self.TrainTest:
  1838. self.sinks_segmentations_segmentix_test[label] =\
  1839. self.network.create_sink('ITKImageFile',
  1840. id='segmentations_out_segmentix_test_' + label,
  1841. step_id='test_sinks')
  1842. self.nodes_segmentix_test[label] =\
  1843. self.network.create_node('segmentix/Segmentix:1.0',
  1844. tool_version='1.0',
  1845. id='segmentix_test_' + label,
  1846. resources=ResourceLimit(memory=memory),
  1847. step_id='Preprocessing')
  1848. # Input the image
  1849. self.nodes_segmentix_test[label].inputs['image'] =\
  1850. self.converters_im_test[label].outputs['image']
  1851. # Input the metadata
  1852. if self.metadata_test and len(self.metadata_test) >= nmod + 1:
  1853. self.nodes_segmentix_test[label].inputs['metadata'] = self.sources_metadata_test[label].output
  1854. if hasattr(self, 'transformix_seg_nodes_test'):
  1855. if label in self.transformix_seg_nodes_test.keys():
  1856. # Use output of registration in segmentix
  1857. self.nodes_segmentix_test[label].inputs['segmentation_in'] =\
  1858. self.transformix_seg_nodes_test[label].outputs['image']
  1859. else:
  1860. # Use original segmentation
  1861. self.nodes_segmentix_test[label].inputs['segmentation_in'] =\
  1862. self.converters_seg_test[label].outputs['image']
  1863. else:
  1864. # Use original segmentation
  1865. self.nodes_segmentix_test[label].inputs['segmentation_in'] =\
  1866. self.converters_seg_test[label].outputs['image']
  1867. if self.configs[0]['General']['Fingerprint'] == 'True' and not self.OnlyTest:
  1868. self.nodes_segmentix_test[label].inputs['parameters'] =\
  1869. self.node_fingerprinters[label].outputs['config']
  1870. else:
  1871. self.nodes_segmentix_test[label].inputs['parameters'] =\
  1872. self.sources_parameters[label].output
  1873. self.sinks_segmentations_segmentix_test[label].input =\
  1874. self.nodes_segmentix_test[label].outputs['segmentation_out']
  1875. if not self.OnlyTest:
  1876. for i_node in range(len(self.calcfeatures_train[label])):
  1877. self.calcfeatures_train[label][i_node].inputs['segmentation'] =\
  1878. self.nodes_segmentix_train[label].outputs['segmentation_out']
  1879. if self.TrainTest:
  1880. self.calcfeatures_test[label][i_node].inputs['segmentation'] =\
  1881. self.nodes_segmentix_test[label].outputs['segmentation_out']
  1882. else:
  1883. for i_node in range(len(self.calcfeatures_test[label])):
  1884. self.calcfeatures_test[label][i_node].inputs['segmentation'] =\
  1885. self.nodes_segmentix_test[label].outputs['segmentation_out']
  1886. if self.masks_train and len(self.masks_train) >= nmod + 1:
  1887. # Use masks
  1888. self.nodes_segmentix_train[label].inputs['mask'] =\
  1889. self.converters_masks_train[label].outputs['image']
  1890. if self.masks_test and len(self.masks_test) >= nmod + 1:
  1891. # Use masks
  1892. self.nodes_segmentix_test[label].inputs['mask'] =\
  1893. self.converters_masks_test[label].outputs['image']
  1894. def set(self):
  1895. """Set the FASTR source and sink data based on the given attributes."""
  1896. self.fastrconfigs = list()
  1897. self.source_data = dict()
  1898. self.sink_data = dict()
  1899. # Save the configurations as files
  1900. if not self.OnlyTest:
  1901. self.save_config()
  1902. else:
  1903. self.fastrconfigs = self.configs
  1904. # fixed splits
  1905. if self.fixedsplits:
  1906. self.source_data['fixedsplits_source'] = self.fixedsplits
  1907. # Set source and sink data
  1908. self.source_data['patientclass_train'] = self.labels_train
  1909. self.source_data['patientclass_test'] = self.labels_test
  1910. self.source_data['trained_model'] = self.trained_model
  1911. self.sink_data['classification'] = ("vfs://output/{}/estimator_{{sample_id}}_{{cardinality}}{{ext}}").format(self.name)
  1912. self.sink_data['performance'] = ("vfs://output/{}/performance_{{sample_id}}_{{cardinality}}{{ext}}").format(self.name)
  1913. self.sink_data['smac_results'] = ("vfs://output/{}/smac_results_{{sample_id}}_{{cardinality}}{{ext}}").format(self.name)
  1914. self.sink_data['config_classification_sink'] = ("vfs://output/{}/config_{{sample_id}}_{{cardinality}}{{ext}}").format(self.name)
  1915. self.sink_data['features_train_ComBat'] = ("vfs://output/{}/ComBat/features_ComBat_{{sample_id}}_{{cardinality}}{{ext}}").format(self.name)
  1916. self.sink_data['features_test_ComBat'] = ("vfs://output/{}/ComBat/features_ComBat_{{sample_id}}_{{cardinality}}{{ext}}").format(self.name)
  1917. # Get info from the first config file
  1918. if type(self.configs[0]) == str:
  1919. # Config is a .ini file, load
  1920. temp_conf = config_io.load_config(self.configs[0])
  1921. else:
  1922. temp_conf = self.configs[0]
  1923. # Set the source data from the WORC objects you created
  1924. for num, label in enumerate(self.modlabels):
  1925. self.source_data['config_' + label] = self.fastrconfigs[num]
  1926. self.sink_data[f'config_{label}_sink'] = f"vfs://output/{self.name}/config_{label}_{{sample_id}}_{{cardinality}}{{ext}}"
  1927. if 'pyradiomics' in temp_conf['General']['FeatureCalculators'] and temp_conf['General']['Fingerprint'] != 'True':
  1928. self.source_data['config_pyradiomics_' + label] = self.pyradiomics_configs[num]
  1929. # Add train data sources
  1930. if self.images_train and len(self.images_train) - 1 >= num:
  1931. self.source_data['images_train_' + label] = self.images_train[num]
  1932. if self.masks_train and len(self.masks_train) - 1 >= num:
  1933. self.source_data['mask_train_' + label] = self.masks_train[num]
  1934. if self.masks_normalize_train and len(self.masks_normalize_train) - 1 >= num:
  1935. self.source_data['masks_normalize_train_' + label] = self.masks_normalize_train[num]
  1936. if self.metadata_train and len(self.metadata_train) - 1 >= num:
  1937. self.source_data['metadata_train_' + label] = self.metadata_train[num]
  1938. if self.segmentations_train and len(self.segmentations_train) - 1 >= num:
  1939. self.source_data['segmentations_train_' + label] = self.segmentations_train[num]
  1940. if self.semantics_train and len(self.semantics_train) - 1 >= num:
  1941. self.source_data['semantics_train_' + label] = self.semantics_train[num]
  1942. if self.features_train and len(self.features_train) - 1 >= num:
  1943. self.source_data['features_train_' + label] = self.features_train[num]
  1944. if self.Elastix_Para:
  1945. # First modality does not need to be registered
  1946. if num > 0:
  1947. if len(self.Elastix_Para) > 1:
  1948. # Each modality has its own registration parameters
  1949. self.source_data['Elastix_Para_' + label] = self.Elastix_Para[num]
  1950. else:
  1951. # Use one fileset for all modalities
  1952. self.source_data['Elastix_Para_' + label] = self.Elastix_Para[0]
  1953. # Add test data sources
  1954. if self.images_test and len(self.images_test) - 1 >= num:
  1955. self.source_data['images_test_' + label] = self.images_test[num]
  1956. if self.masks_test and len(self.masks_test) - 1 >= num:
  1957. self.source_data['mask_test_' + label] = self.masks_test[num]
  1958. if self.masks_normalize_test and len(self.masks_normalize_test) - 1 >= num:
  1959. self.source_data['masks_normalize_test_' + label] = self.masks_normalize_test[num]
  1960. if self.metadata_test and len(self.metadata_test) - 1 >= num:
  1961. self.source_data['metadata_test_' + label] = self.metadata_test[num]
  1962. if self.segmentations_test and len(self.segmentations_test) - 1 >= num:
  1963. self.source_data['segmentations_test_' + label] = self.segmentations_test[num]
  1964. if self.semantics_test and len(self.semantics_test) - 1 >= num:
  1965. self.source_data['semantics_test_' + label] = self.semantics_test[num]
  1966. if self.features_test and len(self.features_test) - 1 >= num:
  1967. self.source_data['features_test_' + label] = self.features_test[num]
  1968. self.sink_data['segmentations_out_segmentix_train_' + label] = ("vfs://output/{}/Segmentations/seg_{}_segmentix_{{sample_id}}_{{cardinality}}{{ext}}").format(self.name, label)
  1969. self.sink_data['segmentations_out_elastix_train_' + label] = ("vfs://output/{}/Elastix/seg_{}_elastix_{{sample_id}}_{{cardinality}}{{ext}}").format(self.name, label)
  1970. self.sink_data['images_out_elastix_train_' + label] = ("vfs://output/{}/Elastix/im_{}_elastix_{{sample_id}}_{{cardinality}}{{ext}}").format(self.name, label)
  1971. if hasattr(self, 'featurecalculators'):
  1972. for f in self.featurecalculators[label]:
  1973. self.sink_data['features_train_' + label + '_' + f] = ("vfs://output/{}/Features/features_{}_{}_{{sample_id}}_{{cardinality}}{{ext}}").format(self.name, f, label)
  1974. if self.labels_test:
  1975. self.sink_data['segmentations_out_segmentix_test_' + label] = ("vfs://output/{}/Segmentations/seg_{}_segmentix_{{sample_id}}_{{cardinality}}{{ext}}").format(self.name, label)
  1976. self.sink_data['segmentations_out_elastix_test_' + label] = ("vfs://output/{}/Elastix/seg_{}_elastix_{{sample_id}}_{{cardinality}}{{ext}}").format(self.name, label)
  1977. self.sink_data['images_out_elastix_test_' + label] = ("vfs://output/{}/Images/im_{}_elastix_{{sample_id}}_{{cardinality}}{{ext}}").format(self.name, label)
  1978. if hasattr(self, 'featurecalculators'):
  1979. for f in self.featurecalculators[label]:
  1980. f = f.replace(':', '_').replace('.', '_').replace('/', '_')
  1981. self.sink_data['features_test_' + label + '_' + f] = ("vfs://output/{}/Features/features_{}_{}_{{sample_id}}_{{cardinality}}{{ext}}").format(self.name, f, label)
  1982. # Add elastix sinks if used
  1983. if self.segmode:
  1984. # Segmode is only non-empty if segmentations are provided
  1985. if self.segmode == 'Register':
  1986. self.sink_data['transformations_train_' + label] = ("vfs://output/{}/Elastix/transformation_{}_{{sample_id}}_{{cardinality}}{{ext}}").format(self.name, label)
  1987. if self.TrainTest:
  1988. self.sink_data['transformations_test_' + label] = ("vfs://output/{}/Elastix/transformation_{}_{{sample_id}}_{{cardinality}}{{ext}}").format(self.name, label)
  1989. if self._add_evaluation:
  1990. self.Evaluate.set()
  1991. # Generate gridsearch parameter files if required
  1992. self.source_data['config_classification_source'] = self.fastrconfigs[0]
  1993. # Give configuration sources to WORC
  1994. for num, label in enumerate(self.modlabels):
  1995. self.source_data['config_' + label] = self.fastrconfigs[num]
  1996. def execute(self):
  1997. """Execute the network through the fastr.network.execute command."""
  1998. # Draw and execute nwtwork
  1999. try:
  2000. self.network.draw(file_path=self.network.id + '.svg', draw_dimensions=True)
  2001. except graphviz.backend.ExecutableNotFound:
  2002. print('[WORC WARNING] Graphviz executable not found: not drawing network diagram. Make sure the Graphviz executables are on your systems PATH.')
  2003. except graphviz.backend.CalledProcessError as e:
  2004. print(f'[WORC WARNING] Graphviz executable gave an error: not drawing network diagram. Original error: {e}')
  2005. # export hyper param. search space to LaTeX table. Only for training models.
  2006. if not self.OnlyTest:
  2007. for config in self.fastrconfigs:
  2008. config_path = Path(url2pathname(urlparse(config).path))
  2009. tex_path = f'{config_path.parent.absolute() / config_path.stem}_hyperparams_space.tex'
  2010. export_hyper_params_to_latex(config_path, tex_path)
  2011. if DebugDetector().do_detection():
  2012. print("Source Data:")
  2013. for k in self.source_data.keys():
  2014. print(f"\t {k}: {self.source_data[k]}.")
  2015. print("\n Sink Data:")
  2016. for k in self.sink_data.keys():
  2017. print(f"\t {k}: {self.sink_data[k]}.")
  2018. # # When debugging, set the tempdir to the default of fastr + name
  2019. system = platform.system()
  2020. if system != 'Darwin':
  2021. self.fastr_tmpdir = os.path.join(fastr.config.mounts['tmp'], self.name)
  2022. print(f"Setting fastr_tmpdir to: {self.fastr_tmpdir}")
  2023. else:
  2024. print("macOS detected — not overriding fastr_tmpdir")
  2025. self.network.execute(self.source_data, self.sink_data, execution_plugin=self.fastr_plugin, tmpdir=self.fastr_tmpdir)
  2026. def add_evaluation(self, label_type, modus='binary_classification'):
  2027. """Add branch for evaluation of performance to network.
  2028. Note: should be done after build, before set:
  2029. WORC.build()
  2030. WORC.add_evaluation(label_type)
  2031. WORC.set()
  2032. WORC.execute()
  2033. """
  2034. self.Evaluate =\
  2035. Evaluate(label_type=label_type, parent=self, modus=modus)
  2036. self._add_evaluation = True
  2037. def save_config(self):
  2038. """Save the config files to physical files and add to network."""
  2039. # If the configuration files are confiparse objects, write to file
  2040. self.pyradiomics_configs = list()
  2041. # Make sure we can dump blank values for PyRadiomics
  2042. yaml.SafeDumper.add_representer(type(None),
  2043. lambda dumper, value: dumper.represent_scalar(u'tag:yaml.org,2002:null', ''))
  2044. for num, c in enumerate(self.configs):
  2045. if type(c) != configparser.ConfigParser:
  2046. # A filepath (not a fastr source) is provided. Hence we read
  2047. # the config file and convert it to a configparser object
  2048. config = configparser.ConfigParser()
  2049. config.read(c)
  2050. c = config
  2051. cfile = os.path.join(self.fastr_tmpdir, f"config_{self.name}_{num}.ini")
  2052. if not os.path.exists(os.path.dirname(cfile)):
  2053. os.makedirs(os.path.dirname(cfile))
  2054. with open(cfile, 'w') as configfile:
  2055. c.write(configfile)
  2056. # If PyRadiomics is used and there is no finterprinting, also write a config for PyRadiomics
  2057. if 'pyradiomics' in c['General']['FeatureCalculators'] and self.configs[0]['General']['Fingerprint'] != 'True':
  2058. cfile_pyradiomics = os.path.join(self.fastr_tmpdir, f"config_pyradiomics_{self.name}_{num}.yaml")
  2059. config_pyradiomics = io.convert_config_pyradiomics(c)
  2060. with open(cfile_pyradiomics, 'w') as file:
  2061. yaml.safe_dump(config_pyradiomics, file)
  2062. cfile_pyradiomics = Path(self.fastr_tmpdir) / f"config_pyradiomics_{self.name}_{num}.yaml"
  2063. self.pyradiomics_configs.append(cfile_pyradiomics.as_uri().replace('%20', ' '))
  2064. # BUG: Make path with pathlib to create windows double slashes
  2065. cfile = Path(self.fastr_tmpdir) / f"config_{self.name}_{num}.ini"
  2066. self.fastrconfigs.append(cfile.as_uri().replace('%20', ' '))
  2067. class Tools(object):
  2068. """
  2069. Create other pipelines besides the default radiomics executions.
  2070. Currently includes:
  2071. 1. Registration pipeline
  2072. 2. Evaluation pipeline
  2073. 3. Slicer pipeline, to create pngs of middle slice of images.
  2074. """
  2075. def __init__(self):
  2076. """Initialize object with all pipelines."""
  2077. self.Elastix = Elastix()
  2078. self.Evaluate = Evaluate()
  2079. self.Slicer = Slicer()

WORC.py at commit bcf6a0e, under Apache-2.0 · at the source

Overview

  1. Center for pain Medicine, Department of Anesthesiology, Erasmus University Medical Center, Rotterdam, the Netherlands
  2. Department of Radiology & Nuclear Medicine, Erasmus University Medical Center, Rotterdam, the Netherlands
  3. Department of Pathology, Erasmus University Medical Center, Rotterdam, the Netherlands
Institutions: Erasmus MC (Netherlands); Erasmus University Rotterdam (Netherlands)
Journal: —, volume 20, issue 12, article e0337726
Dates: received 21 October 2024; accepted 12 November 2025; published online 5 December 2025
Type: Research article · Language: English
License: none stated
Identifiers: DOI 10.1371/journal.pone.0337726 · PMCID PMC12680202 · OpenAlex W4417091371
Open access: gold, a free copy (OpenAlex)
Status: code verified
Categories: MEG (modality), human (organism), pain (population)
Methods: Spectral & time-frequency, Preprocessing, Connectivity, Statistics, Smoothing, state filtering, decompositions, Machine learning, Physiology & signal measures
MeSH: Chronic Pain*, Machine Learning*, Magnetoencephalography*, Spinal Cord Stimulation*, Adult, Female, Humans, Male, Middle Aged, Pain Measurement (* major topic)
Topic: Pain Mechanisms and Treatments (Physiology, Medicine), according to OpenAlex
Funding: The Quebec Pain Research Network; Canadian Institutes of Health Research (CIHR 2015-2016/358832); Stichting Neurobionics Foundations
Citations: not cited yet (Europe PMC); 23 references in the paper

Abstract

The abstract is not reproduced here: the paper's license (none stated) does not allow it. Read it in the paper, at the publisher or on Europe PMC.

Repositories

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

Zenodo 3840534

License: Apache-2.0
State: the link answers, verified on 26 September 2026
Evidence: files inventoried
Size: 1 file
Software Heritage: not checked
Found in: the references
Not found: README, license file, CITATION.cff, environment file, tests, continuous integration, documentation
Tools: NumPy (51 files), pandas (31 files), scikit-learn (23 files), SimpleITK (17 files), SciPy (14 files), Matplotlib (11 files), scikit-image (3 files), imbalanced-learn (2 files), pydicom (2 files), XGBoost (2 files), LightGBM (1 file), neuroCombat (1 file), PyRadiomics (1 file)
Availability: 1 check, the latest on 26 September 2026: the link answers (HTTP 200)
  • 26 September 2026: the link answers (HTTP 200)
185 files
At the source:

mstarmans91/worc

License: Apache-2.0
State: the link answers, verified on 26 September 2026
Evidence: files inventoried
Commit: bcf6a0e49ff2ecd778086dfe54027759b42212b3, 13 February 2026
Languages: Python (169), JavaScript (11), MATLAB (1), Shell (1)
Size: 568 files, 182 scripts
Software Heritage: not archived
Found in: the Zenodo archive record
Holds: README, license file, CITATION.cff, environment (pyproject.toml, setup.cfg, setup.py, WORC/doc/requirements-pre-numpy.txt, WORC/doc/requirements.txt), tests, continuous integration, documentation
Tools: NumPy (51 files), pandas (31 files), scikit-learn (23 files), SimpleITK (17 files), SciPy (14 files), Matplotlib (11 files), scikit-image (3 files), imbalanced-learn (2 files), pydicom (2 files), XGBoost (2 files), LightGBM (1 file), neuroCombat (1 file), PyRadiomics (1 file)
Availability: 1 check, the latest on 26 September 2026: the link answers
  • 26 September 2026: the link answers
185 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:

  • 2 repositories of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
  • 364 scripts, each with its path and the digest of its content;
  • 8 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 paper has a data availability statement. Its license (none stated) does not allow reproducing it here; in short, from what the harvester recognized in it:

  • no repository, dataset or request procedure was recognized in it

Read it in the paper: doi.org/10.1371/journal.pone.0337726.

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, 4 authors, 10 MeSH terms, 3 funders, 21 references.

Cite

This paper

Witjes, B., Starmans, M. P. A., Huygen, F. J., & de Vos, C. C. (2025). Classification of chronic pain and spinal cord stimulation response using machine learning in magnetoencephalography data. PLOS One, 20(12), e0337726. https://doi.org/10.1371/journal.pone.0337726

BibTeX

@article{witjes2025classification,
author = {Witjes, Bart and Starmans, Martijn P A and Huygen, Frank JPM and de Vos, Cecile C},
title = {{Classification of chronic pain and spinal cord stimulation response using machine learning in magnetoencephalography data}},
journal = {PLOS One},
year = {2025},
volume = {20},
number = {12},
pages = {e0337726},
publisher = {PLOS},
issn = {1932-6203},
doi = {10.1371/journal.pone.0337726},
url = {https://doi.org/10.1371/journal.pone.0337726},
pmcid = {PMC12680202}
}

RIS

TY - JOUR
AU - Witjes, Bart
AU - Starmans, Martijn P A
AU - Huygen, Frank JPM
AU - de Vos, Cecile C
TI - Classification of chronic pain and spinal cord stimulation response using machine learning in magnetoencephalography data
T2 - PLOS One
J2 - PLoS One
PY - 2025
DA - 2025
VL - 20
IS - 12
SP - e0337726
SN - 1932-6203
PB - PLOS
DO - 10.1371/journal.pone.0337726
UR - https://doi.org/10.1371/journal.pone.0337726
LA - en
ER -

CSL-JSON

{
"id": "10.1371/journal.pone.0337726",
"type": "article-journal",
"title": "Classification of chronic pain and spinal cord stimulation response using machine learning in magnetoencephalography data",
"container-title": "PLOS One",
"author": [
{
"family": "Witjes",
"given": "Bart"
},
{
"family": "Starmans",
"given": "Martijn P A"
},
{
"family": "Huygen",
"given": "Frank JPM"
},
{
"family": "de Vos",
"given": "Cecile C"
}
],
"container-title-short": "PLoS One",
"volume": "20",
"issue": "12",
"page": "e0337726",
"DOI": "10.1371/journal.pone.0337726",
"PMCID": "PMC12680202",
"ISSN": "1932-6203",
"publisher": "PLOS",
"URL": "https://doi.org/10.1371/journal.pone.0337726",
"language": "en",
"issued": {
"date-parts": [
[
2025
]
]
}
}

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.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, XGBoost, 7 other tools
[2] 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, imbalanced-learn, pydicom, 6 other tools
[3] doi:10.3390/cancers18162636 [code]
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
In common: PyRadiomics, neuroCombat, pydicom, 6 other tools
[4] doi:10.7554/elife.110588 [code]
Opening the black box toward a modular approach to spike sorting.
Journal: eLife
In common: LightGBM, imbalanced-learn, XGBoost, 6 other tools
[5] doi:10.1038/s42003-026-10957-8 [code]
Brain defence by the extracellular matrix protein Cochlin.
Journal: Communications biology
In common: imbalanced-learn, pydicom, XGBoost, 6 other tools
[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: PyRadiomics, LightGBM, XGBoost, 5 other tools
[7] doi:10.3389/fonc.2026.1816015 [code]
Modality-level attribution and redundancy-aware radiomics for MRI-based differentiation of melanoma and NSCLC brain metastases.
Journal: Frontiers in oncology
In common: PyRadiomics, LightGBM, SimpleITK, 5 other tools
[8] doi:10.1016/j.isci.2026.116825 [code]
Social hierarchy shapes behavioral and transcriptional responses to chronic stress and ketamine in male mice.
Journal: iScience
In common: imbalanced-learn, XGBoost, scikit-image, 5 other tools
[9] doi:10.3389/frai.2026.1771088 [code]
Few-shot deployment of pretrained MRI transformers in brain imaging tasks.
Journal: Frontiers in artificial intelligence
In common: pydicom, SimpleITK, scikit-image, 5 other tools
[10] 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, scikit-image, 4 other tools

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.