mediml 0.9.9__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. MEDiml/MEDscan.py +1696 -0
  2. MEDiml/__init__.py +21 -0
  3. MEDiml/biomarkers/BatchExtractor.py +806 -0
  4. MEDiml/biomarkers/BatchExtractorTexturalFilters.py +840 -0
  5. MEDiml/biomarkers/__init__.py +16 -0
  6. MEDiml/biomarkers/diagnostics.py +125 -0
  7. MEDiml/biomarkers/get_oriented_bound_box.py +158 -0
  8. MEDiml/biomarkers/glcm.py +1602 -0
  9. MEDiml/biomarkers/gldzm.py +523 -0
  10. MEDiml/biomarkers/glrlm.py +1315 -0
  11. MEDiml/biomarkers/glszm.py +555 -0
  12. MEDiml/biomarkers/int_vol_hist.py +527 -0
  13. MEDiml/biomarkers/intensity_histogram.py +615 -0
  14. MEDiml/biomarkers/local_intensity.py +89 -0
  15. MEDiml/biomarkers/morph.py +1756 -0
  16. MEDiml/biomarkers/ngldm.py +780 -0
  17. MEDiml/biomarkers/ngtdm.py +414 -0
  18. MEDiml/biomarkers/stats.py +373 -0
  19. MEDiml/biomarkers/utils.py +389 -0
  20. MEDiml/filters/TexturalFilter.py +299 -0
  21. MEDiml/filters/__init__.py +9 -0
  22. MEDiml/filters/apply_filter.py +134 -0
  23. MEDiml/filters/gabor.py +215 -0
  24. MEDiml/filters/laws.py +283 -0
  25. MEDiml/filters/log.py +147 -0
  26. MEDiml/filters/mean.py +121 -0
  27. MEDiml/filters/textural_filters_kernels.py +1738 -0
  28. MEDiml/filters/utils.py +107 -0
  29. MEDiml/filters/wavelet.py +237 -0
  30. MEDiml/learning/DataCleaner.py +198 -0
  31. MEDiml/learning/DesignExperiment.py +480 -0
  32. MEDiml/learning/FSR.py +667 -0
  33. MEDiml/learning/Normalization.py +112 -0
  34. MEDiml/learning/RadiomicsLearner.py +714 -0
  35. MEDiml/learning/Results.py +2237 -0
  36. MEDiml/learning/Stats.py +694 -0
  37. MEDiml/learning/__init__.py +10 -0
  38. MEDiml/learning/cleaning_utils.py +107 -0
  39. MEDiml/learning/ml_utils.py +1015 -0
  40. MEDiml/processing/__init__.py +6 -0
  41. MEDiml/processing/compute_suv_map.py +121 -0
  42. MEDiml/processing/discretisation.py +149 -0
  43. MEDiml/processing/interpolation.py +275 -0
  44. MEDiml/processing/resegmentation.py +66 -0
  45. MEDiml/processing/segmentation.py +912 -0
  46. MEDiml/utils/__init__.py +25 -0
  47. MEDiml/utils/batch_patients.py +45 -0
  48. MEDiml/utils/create_radiomics_table.py +131 -0
  49. MEDiml/utils/data_frame_export.py +42 -0
  50. MEDiml/utils/find_process_names.py +16 -0
  51. MEDiml/utils/get_file_paths.py +34 -0
  52. MEDiml/utils/get_full_rad_names.py +21 -0
  53. MEDiml/utils/get_institutions_from_ids.py +16 -0
  54. MEDiml/utils/get_patient_id_from_scan_name.py +22 -0
  55. MEDiml/utils/get_patient_names.py +26 -0
  56. MEDiml/utils/get_radiomic_names.py +27 -0
  57. MEDiml/utils/get_scan_name_from_rad_name.py +22 -0
  58. MEDiml/utils/image_reader_SITK.py +37 -0
  59. MEDiml/utils/image_volume_obj.py +22 -0
  60. MEDiml/utils/imref.py +340 -0
  61. MEDiml/utils/initialize_features_names.py +62 -0
  62. MEDiml/utils/inpolygon.py +159 -0
  63. MEDiml/utils/interp3.py +43 -0
  64. MEDiml/utils/json_utils.py +78 -0
  65. MEDiml/utils/mode.py +31 -0
  66. MEDiml/utils/parse_contour_string.py +58 -0
  67. MEDiml/utils/save_MEDscan.py +30 -0
  68. MEDiml/utils/strfind.py +32 -0
  69. MEDiml/utils/textureTools.py +188 -0
  70. MEDiml/utils/texture_features_names.py +115 -0
  71. MEDiml/utils/write_radiomics_csv.py +47 -0
  72. MEDiml/wrangling/DataManager.py +1724 -0
  73. MEDiml/wrangling/ProcessDICOM.py +512 -0
  74. MEDiml/wrangling/__init__.py +3 -0
  75. mediml-0.9.9.dist-info/LICENSE.md +674 -0
  76. mediml-0.9.9.dist-info/METADATA +232 -0
  77. mediml-0.9.9.dist-info/RECORD +78 -0
  78. mediml-0.9.9.dist-info/WHEEL +4 -0
@@ -0,0 +1,806 @@
1
+ import logging
2
+ import math
3
+ import os
4
+ import pickle
5
+ import sys
6
+ from copy import deepcopy
7
+ from datetime import datetime
8
+ from itertools import product
9
+ from pathlib import Path
10
+ from time import time
11
+ from typing import Dict, List, Union
12
+
13
+ import numpy as np
14
+ import pandas as pd
15
+ import ray
16
+ from tqdm import trange
17
+
18
+ import MEDiml
19
+
20
+
21
+ class BatchExtractor(object):
22
+ """
23
+ Organizes all the patients/scans in batches to extract all the radiomic features
24
+ """
25
+
26
+ def __init__(
27
+ self,
28
+ path_read: Union[str, Path],
29
+ path_csv: Union[str, Path],
30
+ path_params: Union[str, Path],
31
+ path_save: Union[str, Path],
32
+ n_batch: int = 4,
33
+ skip_existing: bool = False
34
+ ) -> None:
35
+ """
36
+ constructor of the BatchExtractor class
37
+ """
38
+ self._path_csv = Path(path_csv)
39
+ self._path_params = Path(path_params)
40
+ self._path_read = Path(path_read)
41
+ self._path_save = Path(path_save)
42
+ self.roi_types = []
43
+ self.roi_type_labels = []
44
+ self.n_bacth = n_batch
45
+ self.skip_existing = skip_existing
46
+
47
+ def __load_and_process_params(self) -> Dict:
48
+ """Load and process the computing & batch parameters from JSON file"""
49
+ # Load json parameters
50
+ im_params = MEDiml.utils.json_utils.load_json(self._path_params)
51
+
52
+ # Update class attributes
53
+ self.roi_types.extend(im_params['roi_types'])
54
+ self.roi_type_labels.extend(im_params['roi_type_labels'])
55
+ self.n_bacth = im_params['n_batch'] if 'n_batch' in im_params else self.n_bacth
56
+
57
+ return im_params
58
+
59
+ @ray.remote
60
+ def __compute_radiomics_one_patient(
61
+ self,
62
+ name_patient: str,
63
+ roi_name: str,
64
+ im_params: Dict,
65
+ roi_type: str,
66
+ roi_type_label: str,
67
+ log_file: Union[Path, str]
68
+ ) -> str:
69
+ """
70
+ Computes all radiomics features (Texture & Non-texture) for one patient/scan
71
+
72
+ Args:
73
+ name_patient(str): scan or patient full name. It has to respect the MEDiml naming convention:
74
+ PatientID__ImagingScanName.ImagingModality.npy
75
+ roi_name(str): name of the ROI that will be used in computation.
76
+ im_params(Dict): Dict of parameters/settings that will be used in the processing and computation.
77
+ roi_type(str): Type of ROI used in the processing and computation (for identification purposes)
78
+ roi_type_label(str): Label of the ROI used, to make it identifiable from other ROIs.
79
+ log_file(Union[Path, str]): Path to the logging file.
80
+
81
+ Returns:
82
+ Union[Path, str]: Path to the updated logging file.
83
+ """
84
+ # Setting up logging settings
85
+ logging.basicConfig(filename=log_file, level=logging.DEBUG, force=True)
86
+
87
+ # Check if features are already computed for the current scan
88
+ if self.skip_existing:
89
+ modality = name_patient.split('.')[1]
90
+ name_save = name_patient.split('.')[0] + f'({roi_type_label})' + f'.{modality}.json'
91
+ if Path(self._path_save / f'features({roi_type})' / name_save).exists():
92
+ logging.info("Skipping existing features for scan: {name_patient}")
93
+ return log_file
94
+
95
+ # start timer
96
+ t_start = time()
97
+
98
+ # Initialization
99
+ message = f"\n***************** COMPUTING FEATURES: {name_patient} *****************"
100
+ logging.info(message)
101
+
102
+ # Load MEDscan instance
103
+ try:
104
+ with open(self._path_read / name_patient, 'rb') as f: medscan = pickle.load(f)
105
+ medscan = MEDiml.MEDscan(medscan)
106
+ except Exception as e:
107
+ print(f"\n ERROR LOADING PATIENT {name_patient}:\n {e}")
108
+ return None
109
+
110
+ # Init processing & computation parameters
111
+ medscan.init_params(im_params)
112
+ logging.debug('Parameters parsed, json file is valid.')
113
+
114
+ # Get ROI (region of interest)
115
+ logging.info("\n--> Extraction of ROI mask:")
116
+ try:
117
+ vol_obj_init, roi_obj_init = MEDiml.processing.get_roi_from_indexes(
118
+ medscan,
119
+ name_roi=roi_name,
120
+ box_string=medscan.params.process.box_string
121
+ )
122
+ except:
123
+ # if for the current scan ROI is not found, computation is aborted.
124
+ return log_file
125
+
126
+ start = time()
127
+ message = '--> Non-texture features pre-processing (interp + re-seg) for "Scale={}"'.\
128
+ format(str(medscan.params.process.scale_non_text[0]))
129
+ logging.info(message)
130
+
131
+ # Interpolation
132
+ # Intensity Mask
133
+ vol_obj = MEDiml.processing.interp_volume(
134
+ medscan=medscan,
135
+ vol_obj_s=vol_obj_init,
136
+ vox_dim=medscan.params.process.scale_non_text,
137
+ interp_met=medscan.params.process.vol_interp,
138
+ round_val=medscan.params.process.gl_round,
139
+ image_type='image',
140
+ roi_obj_s=roi_obj_init,
141
+ box_string=medscan.params.process.box_string
142
+ )
143
+ # Morphological Mask
144
+ roi_obj_morph = MEDiml.processing.interp_volume(
145
+ medscan=medscan,
146
+ vol_obj_s=roi_obj_init,
147
+ vox_dim=medscan.params.process.scale_non_text,
148
+ interp_met=medscan.params.process.roi_interp,
149
+ round_val=medscan.params.process.roi_pv,
150
+ image_type='roi',
151
+ roi_obj_s=roi_obj_init,
152
+ box_string=medscan.params.process.box_string
153
+ )
154
+
155
+ # Re-segmentation
156
+ # Intensity mask range re-segmentation
157
+ roi_obj_int = deepcopy(roi_obj_morph)
158
+ roi_obj_int.data = MEDiml.processing.range_re_seg(
159
+ vol=vol_obj.data,
160
+ roi=roi_obj_int.data,
161
+ im_range=medscan.params.process.im_range
162
+ )
163
+ # Intensity mask outlier re-segmentation
164
+ roi_obj_int.data = np.logical_and(
165
+ MEDiml.processing.outlier_re_seg(
166
+ vol=vol_obj.data,
167
+ roi=roi_obj_int.data,
168
+ outliers=medscan.params.process.outliers
169
+ ),
170
+ roi_obj_int.data
171
+ ).astype(int)
172
+ logging.info(f"{time() - start}\n")
173
+
174
+ # Reset timer
175
+ start = time()
176
+
177
+ # Preparation of computation :
178
+ medscan.init_ntf_calculation(vol_obj)
179
+
180
+ # Image filtering: linear
181
+ if medscan.params.filter.filter_type:
182
+ if medscan.params.filter.filter_type.lower() == 'textural':
183
+ raise ValueError('For textural filtering, please use the BatchExtractorTexturalFilters class.')
184
+ try:
185
+ vol_obj = MEDiml.filters.apply_filter(medscan, vol_obj)
186
+ except Exception as e:
187
+ logging.error(f'PROBLEM WITH LINEAR FILTERING: {e}')
188
+ return log_file
189
+
190
+ # ROI Extraction :
191
+ try:
192
+ vol_int_re = MEDiml.processing.roi_extract(
193
+ vol=vol_obj.data,
194
+ roi=roi_obj_int.data
195
+ )
196
+ except Exception as e:
197
+ print(name_patient, e)
198
+ return log_file
199
+
200
+ # check if ROI is empty
201
+ if math.isnan(np.nanmax(vol_int_re)) and math.isnan(np.nanmin(vol_int_re)):
202
+ logging.error(f'PROBLEM WITH INTENSITY MASK. ROI {roi_name} IS EMPTY.')
203
+ return log_file
204
+
205
+ # Computation of non-texture features
206
+ logging.info("--> Computation of non-texture features:")
207
+
208
+ # Morphological features extraction
209
+ try:
210
+ if medscan.params.radiomics.extract['Morph']:
211
+ morph = MEDiml.biomarkers.morph.extract_all(
212
+ vol=vol_obj.data,
213
+ mask_int=roi_obj_int.data,
214
+ mask_morph=roi_obj_morph.data,
215
+ res=medscan.params.process.scale_non_text,
216
+ intensity_type=medscan.params.process.intensity_type
217
+ )
218
+ else:
219
+ morph = None
220
+ except Exception as e:
221
+ logging.error(f'PROBLEM WITH COMPUTATION OF MORPHOLOGICAL FEATURES {e}')
222
+ morph = None
223
+
224
+ # Local intensity features extraction
225
+ try:
226
+ if medscan.params.radiomics.extract['LocalIntensity']:
227
+ local_intensity = MEDiml.biomarkers.local_intensity.extract_all(
228
+ img_obj=vol_obj.data,
229
+ roi_obj=roi_obj_int.data,
230
+ res=medscan.params.process.scale_non_text,
231
+ intensity_type=medscan.params.process.intensity_type
232
+ )
233
+ else:
234
+ local_intensity = None
235
+ except Exception as e:
236
+ logging.error(f'PROBLEM WITH COMPUTATION OF LOCAL INTENSITY FEATURES {e}')
237
+ local_intensity = None
238
+
239
+ # statistical features extraction
240
+ try:
241
+ if medscan.params.radiomics.extract['Stats']:
242
+ stats = MEDiml.biomarkers.stats.extract_all(
243
+ vol=vol_int_re,
244
+ intensity_type=medscan.params.process.intensity_type
245
+ )
246
+ else:
247
+ stats = None
248
+ except Exception as e:
249
+ logging.error(f'PROBLEM WITH COMPUTATION OF STATISTICAL FEATURES {e}')
250
+ stats = None
251
+
252
+ # Intensity histogram equalization of the imaging volume
253
+ vol_quant_re, _ = MEDiml.processing.discretize(
254
+ vol_re=vol_int_re,
255
+ discr_type=medscan.params.process.ih['type'],
256
+ n_q=medscan.params.process.ih['val'],
257
+ user_set_min_val=medscan.params.process.user_set_min_value
258
+ )
259
+
260
+ # Intensity histogram features extraction
261
+ try:
262
+ if medscan.params.radiomics.extract['IntensityHistogram']:
263
+ int_hist = MEDiml.biomarkers.intensity_histogram.extract_all(
264
+ vol=vol_quant_re
265
+ )
266
+ else:
267
+ int_hist = None
268
+ except Exception as e:
269
+ logging.error(f'PROBLEM WITH COMPUTATION OF INTENSITY HISTOGRAM FEATURES {e}')
270
+ int_hist = None
271
+
272
+ # Intensity histogram equalization of the imaging volume
273
+ if medscan.params.process.ivh and 'type' in medscan.params.process.ivh and 'val' in medscan.params.process.ivh:
274
+ if medscan.params.process.ivh['type'] and medscan.params.process.ivh['val']:
275
+ vol_quant_re, wd = MEDiml.processing.discretize(
276
+ vol_re=vol_int_re,
277
+ discr_type=medscan.params.process.ivh['type'],
278
+ n_q=medscan.params.process.ivh['val'],
279
+ user_set_min_val=medscan.params.process.user_set_min_value,
280
+ ivh=True
281
+ )
282
+ else:
283
+ vol_quant_re = vol_int_re
284
+ wd = 1
285
+
286
+ # Intensity volume histogram features extraction
287
+ if medscan.params.radiomics.extract['IntensityVolumeHistogram']:
288
+ int_vol_hist = MEDiml.biomarkers.int_vol_hist.extract_all(
289
+ medscan=medscan,
290
+ vol=vol_quant_re,
291
+ vol_int_re=vol_int_re,
292
+ wd=wd
293
+ )
294
+ else:
295
+ int_vol_hist = None
296
+
297
+ # End of Non-Texture features extraction
298
+ logging.info(f"End of non-texture features extraction: {time() - start}\n")
299
+
300
+ # Computation of texture features
301
+ logging.info("--> Computation of texture features:")
302
+
303
+ # Compute radiomics features for each scale text
304
+ count = 0
305
+ for s in range(medscan.params.process.n_scale):
306
+ start = time()
307
+ message = '--> Texture features: pre-processing (interp + ' \
308
+ f'reSeg) for "Scale={str(medscan.params.process.scale_text[s][0])}": '
309
+ logging.info(message)
310
+
311
+ # Interpolation
312
+ # Intensity Mask
313
+ vol_obj = MEDiml.processing.interp_volume(
314
+ medscan=medscan,
315
+ vol_obj_s=vol_obj_init,
316
+ vox_dim=medscan.params.process.scale_text[s],
317
+ interp_met=medscan.params.process.vol_interp,
318
+ round_val=medscan.params.process.gl_round,
319
+ image_type='image',
320
+ roi_obj_s=roi_obj_init,
321
+ box_string=medscan.params.process.box_string
322
+ )
323
+ # Morphological Mask
324
+ roi_obj_morph = MEDiml.processing.interp_volume(
325
+ medscan=medscan,
326
+ vol_obj_s=roi_obj_init,
327
+ vox_dim=medscan.params.process.scale_text[s],
328
+ interp_met=medscan.params.process.roi_interp,
329
+ round_val=medscan.params.process.roi_pv,
330
+ image_type='roi',
331
+ roi_obj_s=roi_obj_init,
332
+ box_string=medscan.params.process.box_string
333
+ )
334
+
335
+ # Re-segmentation
336
+ # Intensity mask range re-segmentation
337
+ roi_obj_int = deepcopy(roi_obj_morph)
338
+ roi_obj_int.data = MEDiml.processing.range_re_seg(
339
+ vol=vol_obj.data,
340
+ roi=roi_obj_int.data,
341
+ im_range=medscan.params.process.im_range
342
+ )
343
+ # Intensity mask outlier re-segmentation
344
+ roi_obj_int.data = np.logical_and(
345
+ MEDiml.processing.outlier_re_seg(
346
+ vol=vol_obj.data,
347
+ roi=roi_obj_int.data,
348
+ outliers=medscan.params.process.outliers
349
+ ),
350
+ roi_obj_int.data
351
+ ).astype(int)
352
+
353
+ # Image filtering: linear
354
+ if medscan.params.filter.filter_type:
355
+ if medscan.params.filter.filter_type.lower() == 'textural':
356
+ raise ValueError('For textural filtering, please use the BatchExtractorTexturalFilters class.')
357
+ try:
358
+ vol_obj = MEDiml.filters.apply_filter(medscan, vol_obj)
359
+ except Exception as e:
360
+ logging.error(f'PROBLEM WITH LINEAR FILTERING: {e}')
361
+ return log_file
362
+
363
+ logging.info(f"{time() - start}\n")
364
+
365
+ # Compute features for each discretisation algorithm and for each grey-level
366
+ for a, n in product(range(medscan.params.process.n_algo), range(medscan.params.process.n_gl)):
367
+ count += 1
368
+ start = time()
369
+ message = '--> Computation of texture features in image ' \
370
+ 'space for "Scale= {}", "Algo={}", "GL={}" ({}):'.format(
371
+ str(medscan.params.process.scale_text[s][1]),
372
+ medscan.params.process.algo[a],
373
+ str(medscan.params.process.gray_levels[a][n]),
374
+ str(count) + '/' + str(medscan.params.process.n_exp)
375
+ )
376
+ logging.info(message)
377
+
378
+ # Preparation of computation :
379
+ medscan.init_tf_calculation(algo=a, gl=n, scale=s)
380
+
381
+ # ROI Extraction :
382
+ vol_int_re = MEDiml.processing.roi_extract(
383
+ vol=vol_obj.data,
384
+ roi=roi_obj_int.data)
385
+
386
+ # Discretisation :
387
+ try:
388
+ vol_quant_re, _ = MEDiml.processing.discretize(
389
+ vol_re=vol_int_re,
390
+ discr_type=medscan.params.process.algo[a],
391
+ n_q=medscan.params.process.gray_levels[a][n],
392
+ user_set_min_val=medscan.params.process.user_set_min_value
393
+ )
394
+ except Exception as e:
395
+ logging.error(f'PROBLEM WITH DISCRETIZATION: {e}')
396
+ vol_quant_re = None
397
+
398
+ # GLCM features extraction
399
+ try:
400
+ if medscan.params.radiomics.extract['GLCM']:
401
+ glcm = MEDiml.biomarkers.glcm.extract_all(
402
+ vol=vol_quant_re,
403
+ dist_correction=medscan.params.radiomics.glcm.dist_correction)
404
+ else:
405
+ glcm = None
406
+ except Exception as e:
407
+ logging.error(f'PROBLEM WITH COMPUTATION OF GLCM FEATURES {e}')
408
+ glcm = None
409
+
410
+ # GLRLM features extraction
411
+ try:
412
+ if medscan.params.radiomics.extract['GLRLM']:
413
+ glrlm = MEDiml.biomarkers.glrlm.extract_all(
414
+ vol=vol_quant_re,
415
+ dist_correction=medscan.params.radiomics.glrlm.dist_correction)
416
+ else:
417
+ glrlm = None
418
+ except Exception as e:
419
+ logging.error(f'PROBLEM WITH COMPUTATION OF GLRLM FEATURES {e}')
420
+ glrlm = None
421
+
422
+ # GLSZM features extraction
423
+ try:
424
+ if medscan.params.radiomics.extract['GLSZM']:
425
+ glszm = MEDiml.biomarkers.glszm.extract_all(
426
+ vol=vol_quant_re)
427
+ else:
428
+ glszm = None
429
+ except Exception as e:
430
+ logging.error(f'PROBLEM WITH COMPUTATION OF GLSZM FEATURES {e}')
431
+ glszm = None
432
+
433
+ # GLDZM features extraction
434
+ try:
435
+ if medscan.params.radiomics.extract['GLDZM']:
436
+ gldzm = MEDiml.biomarkers.gldzm.extract_all(
437
+ vol_int=vol_quant_re,
438
+ mask_morph=roi_obj_morph.data)
439
+ else:
440
+ gldzm = None
441
+ except Exception as e:
442
+ logging.error(f'PROBLEM WITH COMPUTATION OF GLDZM FEATURES {e}')
443
+ gldzm = None
444
+
445
+ # NGTDM features extraction
446
+ try:
447
+ if medscan.params.radiomics.extract['NGTDM']:
448
+ ngtdm = MEDiml.biomarkers.ngtdm.extract_all(
449
+ vol=vol_quant_re,
450
+ dist_correction=medscan.params.radiomics.ngtdm.dist_correction)
451
+ else:
452
+ ngtdm = None
453
+ except Exception as e:
454
+ logging.error(f'PROBLEM WITH COMPUTATION OF NGTDM FEATURES {e}')
455
+ ngtdm = None
456
+
457
+ # NGLDM features extraction
458
+ try:
459
+ if medscan.params.radiomics.extract['NGLDM']:
460
+ ngldm = MEDiml.biomarkers.ngldm.extract_all(
461
+ vol=vol_quant_re)
462
+ else:
463
+ ngldm = None
464
+ except Exception as e:
465
+ logging.error(f'PROBLEM WITH COMPUTATION OF NGLDM FEATURES {e}')
466
+ ngldm = None
467
+
468
+ # Update radiomics results class
469
+ medscan.update_radiomics(
470
+ int_vol_hist_features=int_vol_hist,
471
+ morph_features=morph,
472
+ loc_int_features=local_intensity,
473
+ stats_features=stats,
474
+ int_hist_features=int_hist,
475
+ glcm_features=glcm,
476
+ glrlm_features=glrlm,
477
+ glszm_features=glszm,
478
+ gldzm_features=gldzm,
479
+ ngtdm_features=ngtdm,
480
+ ngldm_features=ngldm
481
+ )
482
+
483
+ # End of texture features extraction
484
+ logging.info(f"End of texture features extraction: {time() - start}\n")
485
+
486
+ # Saving radiomics results
487
+ medscan.save_radiomics(
488
+ scan_file_name=name_patient,
489
+ path_save=self._path_save,
490
+ roi_type=roi_type,
491
+ roi_type_label=roi_type_label,
492
+ )
493
+
494
+ logging.info(f"TOTAL TIME:{time() - t_start} seconds\n\n")
495
+
496
+ return log_file
497
+
498
+ @ray.remote
499
+ def __compute_radiomics_tables(
500
+ self,
501
+ table_tags: List,
502
+ log_file: Union[str, Path],
503
+ im_params: Dict
504
+ ) -> None:
505
+ """
506
+ Creates radiomic tables off of the saved dicts with the computed features and save it as CSV files
507
+
508
+ Args:
509
+ table_tags(List): Lists of information about scans, roi type and imaging space (or filter space)
510
+ log_file(Union[str, Path]): Path to logging file.
511
+ im_params(Dict): Dictionary of parameters.
512
+
513
+ Returns:
514
+ None.
515
+ """
516
+ n_tables = len(table_tags)
517
+
518
+ for t in range(0, n_tables):
519
+ scan = table_tags[t][0]
520
+ roi_type = table_tags[t][1]
521
+ roi_label = table_tags[t][2]
522
+ im_space = table_tags[t][3]
523
+ modality = table_tags[t][4]
524
+
525
+ # extract parameters for the current modality
526
+ if modality == 'CTscan' and 'imParamCT' in im_params:
527
+ im_params_mod = im_params['imParamCT']
528
+ elif modality== 'MRscan' and 'imParamMR' in im_params:
529
+ im_params_mod = im_params['imParamMR']
530
+ elif modality == 'PTscan' and 'imParamPET' in im_params:
531
+ im_params_mod = im_params['imParamPET']
532
+ # extract name save of the used filter
533
+ if 'filter_type' in im_params_mod:
534
+ filter_type = im_params_mod['filter_type']
535
+ if filter_type in im_params['imParamFilter'] and 'name_save' in im_params['imParamFilter'][filter_type]:
536
+ name_save = im_params['imParamFilter'][filter_type]['name_save']
537
+ else:
538
+ name_save= ''
539
+ else:
540
+ name_save= ''
541
+
542
+ # set up table name
543
+ if name_save:
544
+ name_table = 'radiomics__' + scan + \
545
+ '(' + roi_type + ')__' + name_save + '.npy'
546
+ else:
547
+ name_table = 'radiomics__' + scan + \
548
+ '(' + roi_type + ')__' + im_space + '.npy'
549
+
550
+ # Start timer
551
+ start = time()
552
+ logging.info("\n --> Computing radiomics table: {name_table}...")
553
+
554
+ # Wildcard used to look only in the parent folder (save path),
555
+ # no need to recursively look into sub-folders using '**/'.
556
+ wildcard = '*_' + scan + '(' + roi_type + ')*.json'
557
+
558
+ # Create radiomics table
559
+ radiomics_table_dict = MEDiml.utils.create_radiomics_table(
560
+ MEDiml.utils.get_file_paths(self._path_save / f'features({roi_label})', wildcard),
561
+ im_space,
562
+ log_file
563
+ )
564
+ radiomics_table_dict['Properties']['Description'] = name_table
565
+
566
+ # Save radiomics table
567
+ save_path = self._path_save / f'features({roi_label})' / name_table
568
+ np.save(save_path, [radiomics_table_dict])
569
+
570
+ # Create CSV table and Definitions
571
+ MEDiml.utils.write_radiomics_csv(save_path)
572
+
573
+ logging.info(f"DONE\n {time() - start}\n")
574
+
575
+ return log_file
576
+
577
+ def __batch_all_patients(self, im_params: Dict) -> None:
578
+ """
579
+ Create batches of scans to process and compute radiomics features for every single scan.
580
+
581
+ Args:
582
+ im_params(Dict): Dict of the processing & computation parameters.
583
+
584
+ Returns:
585
+ None
586
+ """
587
+ # create a batch for each roi type
588
+ n_roi_types = len(self.roi_type_labels)
589
+ for r in range(0, n_roi_types):
590
+ roi_type = self.roi_types[r]
591
+ roi_type_label = self.roi_type_labels[r]
592
+ print(f'\n --> Computing features for the "{roi_type_label}" roi type ...', end = '')
593
+
594
+ # READING CSV EXPERIMENT TABLE
595
+ tabel_roi = pd.read_csv(self._path_csv / ('roiNames_' + roi_type_label + '.csv'))
596
+ tabel_roi['under'] = '_'
597
+ tabel_roi['dot'] = '.'
598
+ tabel_roi['npy'] = '.npy'
599
+ name_patients = (pd.Series(
600
+ tabel_roi[['PatientID', 'under', 'under',
601
+ 'ImagingScanName',
602
+ 'dot',
603
+ 'ImagingModality',
604
+ 'npy']].fillna('').values.tolist()).str.join('')).tolist()
605
+ tabel_roi = tabel_roi.drop(columns=['under', 'under', 'dot', 'npy'])
606
+ roi_names = tabel_roi.ROIname.tolist()
607
+
608
+ # INITIALIZATION
609
+ os.chdir(self._path_save)
610
+ name_bacth_log = 'batchLog_' + roi_type_label
611
+ p = Path.cwd().glob('*')
612
+ files = [x for x in p if x.is_dir()]
613
+ n_files = len(files)
614
+ exist_file = name_bacth_log in [x.name for x in files]
615
+ if exist_file and (n_files > 0):
616
+ for i in range(0, n_files):
617
+ if (files[i].name == name_bacth_log):
618
+ mod_timestamp = datetime.fromtimestamp(
619
+ Path(files[i]).stat().st_mtime)
620
+ date = mod_timestamp.strftime("%d-%b-%Y_%HH%MM%SS")
621
+ new_name = name_bacth_log+'_'+date
622
+ if sys.platform == 'win32':
623
+ os.system('move ' + name_bacth_log + ' ' + new_name)
624
+ else:
625
+ os.system('mv ' + name_bacth_log + ' ' + new_name)
626
+
627
+ os.makedirs(name_bacth_log, 0o777, True)
628
+ path_batch = Path.cwd() / name_bacth_log
629
+
630
+ # PRODUCE BATCH COMPUTATIONS
631
+ n_patients = len(name_patients)
632
+ n_batch = self.n_bacth
633
+ if n_batch is None or n_batch < 0:
634
+ n_batch = 1
635
+ elif n_patients < n_batch:
636
+ n_batch = n_patients
637
+
638
+ # Produce a list log_file path.
639
+ log_files = [path_batch / ('log_file_' + str(i) + '.log') for i in range(n_batch)]
640
+
641
+ # Distribute the first tasks to all workers
642
+ ids = [self.__compute_radiomics_one_patient.remote(
643
+ self,
644
+ name_patient=name_patients[i],
645
+ roi_name=roi_names[i],
646
+ im_params=im_params,
647
+ roi_type=roi_type,
648
+ roi_type_label=roi_type_label,
649
+ log_file=log_files[i])
650
+ for i in range(n_batch)]
651
+
652
+ # Distribute the remaining tasks
653
+ nb_job_left = n_patients - n_batch
654
+ for _ in trange(n_patients):
655
+ ready, not_ready = ray.wait(ids, num_returns=1)
656
+ ids = not_ready
657
+ log_file = ray.get(ready)[0]
658
+ if nb_job_left > 0:
659
+ idx = n_patients - nb_job_left
660
+ ids.extend([self.__compute_radiomics_one_patient.remote(
661
+ self,
662
+ name_patients[idx],
663
+ roi_names[idx],
664
+ im_params,
665
+ roi_type,
666
+ roi_type_label,
667
+ log_file)
668
+ ])
669
+ nb_job_left -= 1
670
+
671
+ print('DONE')
672
+
673
+ def __batch_all_tables(self, im_params: Dict):
674
+ """
675
+ Create batches of tables of the extracted features for every imaging scan type (CT, PET...).
676
+
677
+ Args:
678
+ im_params(Dict): Dictionary of parameters.
679
+
680
+ Returns:
681
+ None
682
+ """
683
+ # GETTING COMBINATIONS OF scan, roi_type and imageSpaces
684
+ n_roi_types = len(self.roi_type_labels)
685
+ table_tags = []
686
+ # Get all scan names present for the given roi_type_label
687
+ for r in range(0, n_roi_types):
688
+ label = self.roi_type_labels[r]
689
+ wildcard = '*' + label + '*.json'
690
+ file_paths = MEDiml.utils.get_file_paths(self._path_save / f'features({self.roi_types[r]})', wildcard)
691
+ n_files = len(file_paths)
692
+ scans = [0] * n_files
693
+ modalities = [0] * n_files
694
+ for f in range(0, n_files):
695
+ rad_file_name = file_paths[f].stem
696
+ scans[f] = MEDiml.utils.get_scan_name_from_rad_name(rad_file_name)
697
+ modalities[f] = rad_file_name.split('.')[1]
698
+ scans = s = (np.unique(np.array(scans))).tolist()
699
+ n_scans = len(scans)
700
+ # Get all scan names present for the given roi_type_label and scans
701
+ for s in range(0, n_scans):
702
+ scan = scans[s]
703
+ modality = modalities[s]
704
+ wildcard = '*' + scan + '(' + label + ')*.json'
705
+ file_paths = MEDiml.utils.get_file_paths(self._path_save / f'features({self.roi_types[r]})', wildcard)
706
+ n_files = len(file_paths)
707
+
708
+ # Finding the images spaces for a test file (assuming that all
709
+ # files for a given scan and roi_type_label have the same image spaces
710
+ radiomics = MEDiml.utils.json_utils.load_json(file_paths[0])
711
+ im_spaces = [key for key in radiomics.keys()]
712
+ im_spaces = im_spaces[:-1]
713
+ n_im_spaces = len(im_spaces)
714
+ # Constructing the table_tags variable
715
+ for i in range(0, n_im_spaces):
716
+ im_space = im_spaces[i]
717
+ table_tags = table_tags + [[scan, label, self.roi_types[r], im_space, modality]]
718
+
719
+ # INITIALIZATION
720
+ os.chdir(self._path_save)
721
+ name_batch_log = 'batchLog_tables'
722
+ p = Path.cwd().glob('*')
723
+ files = [x for x in p if x.is_dir()]
724
+ n_files = len(files)
725
+ exist_file = name_batch_log in [x.name for x in files]
726
+ if exist_file and (n_files > 0):
727
+ for i in range(0, n_files):
728
+ if files[i].name == name_batch_log:
729
+ mod_timestamp = datetime.fromtimestamp(
730
+ Path(files[i]).stat().st_mtime)
731
+ date = mod_timestamp.strftime("%d-%b-%Y_%H:%M:%S")
732
+ new_name = name_batch_log+'_'+date
733
+ if sys.platform == 'win32':
734
+ os.system('move ' + name_batch_log + ' ' + new_name)
735
+ else:
736
+ os.system('mv ' + name_batch_log + ' ' + new_name)
737
+
738
+ os.makedirs(name_batch_log, 0o777, True)
739
+ path_batch = Path.cwd()
740
+
741
+ # PRODUCE BATCH COMPUTATIONS
742
+ n_tables = len(table_tags)
743
+ self.n_bacth = self.n_bacth
744
+ if self.n_bacth is None or self.n_bacth < 0:
745
+ self.n_bacth = 1
746
+ elif n_tables < self.n_bacth:
747
+ self.n_bacth = n_tables
748
+
749
+ # Produce a list log_file path.
750
+ log_files = [path_batch / ('log_file_' + str(i) + '.txt') for i in range(self.n_bacth)]
751
+
752
+ # Distribute the first tasks to all workers
753
+ ids = [self.__compute_radiomics_tables.remote(
754
+ self,
755
+ [table_tags[i]],
756
+ log_files[i],
757
+ im_params)
758
+ for i in range(self.n_bacth)]
759
+
760
+ nb_job_left = n_tables - self.n_bacth
761
+
762
+ for _ in trange(n_tables):
763
+ ready, not_ready = ray.wait(ids, num_returns=1)
764
+ ids = not_ready
765
+
766
+ # We verify if error has occur during the process
767
+ log_file = ray.get(ready)[0]
768
+
769
+ # Distribute the remaining tasks
770
+ if nb_job_left > 0:
771
+ idx = n_tables - nb_job_left
772
+ ids.extend([self.__compute_radiomics_tables.remote(
773
+ self,
774
+ [table_tags[idx]],
775
+ log_file,
776
+ im_params)])
777
+ nb_job_left -= 1
778
+
779
+ print('DONE')
780
+
781
+ def compute_radiomics(self, create_tables: bool = True) -> None:
782
+ """Compute all radiomic features for all scans in the CSV file (set in initialization) and organize it
783
+ in JSON and CSV files
784
+
785
+ Args:
786
+ create_tables(bool) : True to create CSV tables for the extracted features and not save it in JSON only.
787
+
788
+ Returns:
789
+ None.
790
+ """
791
+
792
+ # Load and process computing parameters
793
+ im_params = self.__load_and_process_params()
794
+
795
+ # Initialize ray
796
+ if ray.is_initialized():
797
+ ray.shutdown()
798
+
799
+ ray.init(local_mode=True, include_dashboard=True, num_cpus=self.n_bacth)
800
+
801
+ # Batch all scans from CSV file and compute radiomics for each scan
802
+ self.__batch_all_patients(im_params)
803
+
804
+ # Create a CSV file off of the computed features for all the scans
805
+ if create_tables:
806
+ self.__batch_all_tables(im_params)