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,414 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+
4
+
5
+
6
+ from typing import Dict, Tuple, Union
7
+
8
+ import numpy as np
9
+
10
+
11
+ def get_matrix(roi_only:np.ndarray,
12
+ levels:np.ndarray,
13
+ dist_correction: bool=False) -> Tuple[np.ndarray, np.ndarray]:
14
+ """This function computes the Neighborhood Gray-Tone Difference Matrix
15
+ (NGTDM) of the region of interest (ROI) of an input volume. The input
16
+ volume is assumed to be isotropically resampled. The ngtdm is computed
17
+ using 26-voxel connectivity. To account for discretization length
18
+ differences, all averages around a center voxel are performed such that
19
+ the neighbours at a distance of :math:`\sqrt{3}` voxels are given a weight of
20
+ :math:`\sqrt{3}`, and the neighbours at a distance of :math:`\sqrt{2}` voxels are given a
21
+ weight of :math:`\sqrt{2}`.
22
+ This matrix refers to "Neighbourhood grey tone difference based features" (ID = IPET)
23
+ in the `IBSI1 reference manual <https://arxiv.org/pdf/1612.07003.pdf>`_.
24
+
25
+ Note:
26
+ This function is compatible with 2D analysis (language not adapted in the text)
27
+
28
+ Args:
29
+ roi_only (ndarray): Smallest box containing the ROI, with the imaging data ready
30
+ for texture analysis computations. Voxels outside the ROI are set to NaNs.
31
+ levels (ndarray): Vector containing the quantized gray-levels in the tumor region
32
+ (or reconstruction ``levels`` of quantization).
33
+ dist_correction (bool, optional): Set this variable to true in order to use
34
+ discretization length difference corrections as used by the `Institute of Physics and
35
+ Engineering in Medicine <https://doi.org/10.1088/0031-9155/60/14/5471>`_.
36
+ Set this variable to false to replicate IBSI results.
37
+
38
+ Returns:
39
+ Tuple[np.ndarray, np.ndarray]:
40
+ - ngtdm: Neighborhood Gray-Tone Difference Matrix of ``roi_only'``.
41
+ - count_valid: Array of number of valid voxels used in the ngtdm computation.
42
+
43
+ REFERENCES:
44
+ [1] Amadasun, M., & King, R. (1989). Textural Features Corresponding to
45
+ Textural Properties. IEEE Transactions on Systems Man and Cybernetics,
46
+ 19(5), 1264–1274.
47
+
48
+ """
49
+
50
+ # PARSING "dist_correction" ARGUMENT
51
+ if type(dist_correction) is not bool:
52
+ # The user did not input either "true" or "false",
53
+ # so the default behavior is used.
54
+ dist_correction = True # By default
55
+
56
+ # PRELIMINARY
57
+ if np.size(np.shape(roi_only)) == 2: # generalization to 2D inputs
58
+ two_d = 1
59
+ else:
60
+ two_d = 0
61
+
62
+ roi_only = np.pad(roi_only, [1, 1], 'constant', constant_values=np.NaN)
63
+
64
+ # # QUANTIZATION EFFECTS CORRECTION
65
+ # # In case (for example) we initially wanted to have 64 levels, but due to
66
+ # # quantization, only 60 resulted.
67
+ unique_vol = levels.astype('int')
68
+ NL = np.size(levels)
69
+ temp = roi_only.copy().astype('int')
70
+ for i in range(1, NL+1):
71
+ roi_only[temp == unique_vol[i-1]] = i
72
+
73
+ # INTIALIZATION
74
+ ngtdm = np.zeros(NL)
75
+ count_valid = np.zeros(NL)
76
+
77
+ # COMPUTATION OF ngtdm
78
+ if two_d:
79
+ indices = np.where(~np.isnan(np.reshape(
80
+ roi_only, np.size(roi_only), order='F')))[0]
81
+ pos_valid = np.unravel_index(indices, np.shape(roi_only), order='F')
82
+ n_valid_temp = np.size(pos_valid[0])
83
+ w4 = 1
84
+ if dist_correction:
85
+ # Weights given to different neighbors to correct
86
+ # for discretization length differences
87
+ w8 = 1/np.sqrt(2)
88
+ else:
89
+ w8 = 1
90
+
91
+ weights = np.array([w8, w4, w8, w4, w4, w4, w8, w4, w8])
92
+
93
+ for n in range(1, n_valid_temp+1):
94
+
95
+ neighbours = roi_only[(pos_valid[0][n-1]-1):(pos_valid[0][n-1]+2),
96
+ (pos_valid[1][n-1]-1):(pos_valid[1][n-1]+2)].copy()
97
+ neighbours = np.reshape(neighbours, 9, order='F')
98
+ neighbours = neighbours*weights
99
+ value = neighbours[4].astype('int')
100
+ neighbours[4] = np.NaN
101
+ neighbours = neighbours/np.sum(weights[~np.isnan(neighbours)])
102
+ neighbours = np.delete(neighbours, 4) # Remove the center voxel
103
+ # Thus only excluding voxels with NaNs only as neighbors.
104
+ if np.size(neighbours[~np.isnan(neighbours)]) > 0:
105
+ ngtdm[value-1] = ngtdm[value-1] + np.abs(
106
+ value-np.sum(neighbours[~np.isnan(neighbours)]))
107
+ count_valid[value-1] = count_valid[value-1] + 1
108
+ else:
109
+
110
+ indices = np.where(~np.isnan(np.reshape(
111
+ roi_only, np.size(roi_only), order='F')))[0]
112
+ pos_valid = np.unravel_index(indices, np.shape(roi_only), order='F')
113
+ n_valid_temp = np.size(pos_valid[0])
114
+ w6 = 1
115
+ if dist_correction:
116
+ # Weights given to different neighbors to correct
117
+ # for discretization length differences
118
+ w26 = 1 / np.sqrt(3)
119
+ w18 = 1 / np.sqrt(2)
120
+ else:
121
+ w26 = 1
122
+ w18 = 1
123
+
124
+ weights = np.array([w26, w18, w26, w18, w6, w18, w26, w18, w26, w18,
125
+ w6, w18, w6, w6, w6, w18, w6, w18, w26, w18,
126
+ w26, w18, w6, w18, w26, w18, w26])
127
+
128
+ for n in range(1, n_valid_temp+1):
129
+ neighbours = roi_only[(pos_valid[0][n-1]-1) : (pos_valid[0][n-1]+2),
130
+ (pos_valid[1][n-1]-1) : (pos_valid[1][n-1]+2),
131
+ (pos_valid[2][n-1]-1) : (pos_valid[2][n-1]+2)].copy()
132
+ neighbours = np.reshape(neighbours, 27, order='F')
133
+ neighbours = neighbours * weights
134
+ value = neighbours[13].astype('int')
135
+ neighbours[13] = np.NaN
136
+ neighbours = neighbours / np.sum(weights[~np.isnan(neighbours)])
137
+ neighbours = np.delete(neighbours, 13) # Remove the center voxel
138
+ # Thus only excluding voxels with NaNs only as neighbors.
139
+ if np.size(neighbours[~np.isnan(neighbours)]) > 0:
140
+ ngtdm[value-1] = ngtdm[value-1] + np.abs(value - np.sum(neighbours[~np.isnan(neighbours)]))
141
+ count_valid[value-1] = count_valid[value-1] + 1
142
+
143
+ return ngtdm, count_valid
144
+
145
+ def extract_all(vol: np.ndarray,
146
+ dist_correction :Union[bool, str]=None) -> Dict:
147
+ """Compute Neighbourhood grey tone difference based features.
148
+ These features refer to "Neighbourhood grey tone difference based features" (ID = IPET) in
149
+ the `IBSI1 reference manual <https://arxiv.org/pdf/1612.07003.pdf>`__.
150
+
151
+ Args:
152
+
153
+ vol (ndarray): 3D volume, isotropically resampled, quantized
154
+ (e.g. n_g = 32, levels = [1, ..., n_g]), with NaNs outside the region
155
+ of interest.
156
+ dist_correction (Union[bool, str], optional): Set this variable to true in order to use
157
+ discretization length difference corrections as used
158
+ by the `Institute of Physics and Engineering in
159
+ Medicine <https://doi.org/10.1088/0031-9155/60/14/5471>`__.
160
+ Set this variable to false to replicate IBSI results.
161
+ Or use string and specify the norm for distance weighting.
162
+ Weighting is only performed if this argument is
163
+ "manhattan", "euclidean" or "chebyshev".
164
+
165
+ Returns:
166
+ Dict: Dict of Neighbourhood grey tone difference based features.
167
+ """
168
+ ngtdm_features = {'Fngt_coarseness': [],
169
+ 'Fngt_contrast': [],
170
+ 'Fngt_busyness': [],
171
+ 'Fngt_complexity': [],
172
+ 'Fngt_strength': []}
173
+
174
+ # GET THE NGTDM MATRIX
175
+ # Correct definition, without any assumption
176
+ levels = np.arange(1, np.max(vol[~np.isnan(vol[:])].astype("int"))+1)
177
+ ngtdm, count_valid = get_matrix(vol, levels, dist_correction)
178
+
179
+ n_tot = np.sum(count_valid)
180
+ # Now representing the probability of gray-level occurences
181
+ count_valid = count_valid/n_tot
182
+ nl = np.size(ngtdm)
183
+ n_g = np.sum(count_valid != 0)
184
+ p_valid = np.where(np.reshape(count_valid, np.size(
185
+ count_valid), order='F') > 0)[0]+1
186
+ n_valid = np.size(p_valid)
187
+
188
+ # COMPUTING TEXTURES
189
+ # Coarseness
190
+ coarseness = 1 / np.matmul(np.transpose(count_valid), ngtdm)
191
+ coarseness = min(coarseness, 10**6)
192
+ ngtdm_features['Fngt_coarseness'] = coarseness
193
+
194
+ # Contrast
195
+ if n_g == 1:
196
+ ngtdm_features['Fngt_contrast'] = 0
197
+ else:
198
+ val = 0
199
+ for i in range(1, nl+1):
200
+ for j in range(1, nl+1):
201
+ val = val + count_valid[i-1] * count_valid[j-1] * ((i-j)**2)
202
+ ngtdm_features['Fngt_contrast'] = val * np.sum(ngtdm) / (n_g*(n_g-1)*n_tot)
203
+
204
+ # Busyness
205
+ if n_g == 1:
206
+ ngtdm_features['Fngt_busyness'] = 0
207
+ else:
208
+ denom = 0
209
+ for i in range(1, n_valid+1):
210
+ for j in range(1, n_valid+1):
211
+ denom = denom + np.abs(p_valid[i-1]*count_valid[p_valid[i-1]-1] -
212
+ p_valid[j-1]*count_valid[p_valid[j-1]-1])
213
+ ngtdm_features['Fngt_busyness'] = np.matmul(np.transpose(count_valid), ngtdm) / denom
214
+
215
+ # Complexity
216
+ val = 0
217
+ for i in range(1, n_valid+1):
218
+ for j in range(1, n_valid+1):
219
+ val = val + (np.abs(
220
+ p_valid[i-1]-p_valid[j-1]) / (n_tot*(
221
+ count_valid[p_valid[i-1]-1] +
222
+ count_valid[p_valid[j-1]-1])))*(
223
+ count_valid[p_valid[i-1]-1]*ngtdm[p_valid[i-1]-1] +
224
+ count_valid[p_valid[j-1]-1]*ngtdm[p_valid[j-1]-1])
225
+
226
+ ngtdm_features['Fngt_complexity'] = val
227
+
228
+ # Strength
229
+ if np.sum(ngtdm) == 0:
230
+ ngtdm_features['Fngt_strength'] = 0
231
+ else:
232
+ val = 0
233
+ for i in range(1, n_valid+1):
234
+ for j in range(1, n_valid+1):
235
+ val = val + (count_valid[p_valid[i-1]-1] + count_valid[p_valid[j-1]-1])*(
236
+ p_valid[i-1]-p_valid[j-1])**2
237
+
238
+ ngtdm_features['Fngt_strength'] = val/np.sum(ngtdm)
239
+
240
+ return ngtdm_features
241
+
242
+ def get_single_matrix(vol: np.ndarray,
243
+ dist_correction = None)-> Tuple[np.ndarray,
244
+ np.ndarray]:
245
+ """Compute neighbourhood grey tone difference matrix in order to compute the single features.
246
+
247
+ Args:
248
+
249
+ vol (ndarray): 3D volume, isotropically resampled, quantized
250
+ (e.g. n_g = 32, levels = [1, ..., n_g]), with NaNs outside the region of interest.
251
+ dist_correction (Union[bool, str], optional): Set this variable to true in order to use
252
+ discretization length difference corrections as used
253
+ by the `Institute of Physics and Engineering in
254
+ Medicine <https://doi.org/10.1088/0031-9155/60/14/5471>`__.
255
+ Set this variable to false to replicate IBSI results.
256
+ Or use string and specify the norm for distance weighting.
257
+ Weighting is only performed if this argument is
258
+ "manhattan", "euclidean" or "chebyshev".
259
+
260
+ Returns:
261
+ np.ndarray: array of neighbourhood grey tone difference matrix
262
+ """
263
+ # GET THE NGTDM MATRIX
264
+ # Correct definition, without any assumption
265
+ levels = np.arange(1, np.max(vol[~np.isnan(vol[:])].astype("int"))+1)
266
+
267
+ ngtdm, count_valid = get_matrix(vol, levels, dist_correction)
268
+
269
+ return ngtdm, count_valid
270
+
271
+ def coarseness(ngtdm: np.ndarray, count_valid: np.ndarray)-> float:
272
+ """
273
+ Computes coarseness feature.
274
+ This feature refers to "Coarseness" (ID = QCDE) in
275
+ the `IBSI1 reference manual <https://arxiv.org/pdf/1612.07003.pdf>`__.
276
+
277
+ Args:
278
+ ngtdm (ndarray): array of neighbourhood grey tone difference matrix
279
+
280
+ Returns:
281
+ float: coarseness value
282
+
283
+ """
284
+ n_tot = np.sum(count_valid)
285
+ count_valid = count_valid/n_tot
286
+ coarseness = 1 / np.matmul(np.transpose(count_valid), ngtdm)
287
+ coarseness = min(coarseness, 10**6)
288
+ return coarseness
289
+
290
+ def contrast(ngtdm: np.ndarray, count_valid: np.ndarray)-> float:
291
+ """
292
+ Computes contrast feature.
293
+ This feature refers to "Contrast" (ID = 65HE) in
294
+ the `IBSI1 reference manual <https://arxiv.org/pdf/1612.07003.pdf>`__.
295
+
296
+ Args:
297
+ ngtdm (ndarray): array of neighbourhood grey tone difference matrix
298
+
299
+ Returns:
300
+ float: contrast value
301
+
302
+ """
303
+ n_tot = np.sum(count_valid)
304
+ count_valid = count_valid/n_tot
305
+ nl = np.size(ngtdm)
306
+ n_g = np.sum(count_valid != 0)
307
+
308
+ if n_g == 1:
309
+ return 0
310
+ else:
311
+ val = 0
312
+ for i in range(1, nl+1):
313
+ for j in range(1, nl+1):
314
+ val = val + count_valid[i-1] * count_valid[j-1] * ((i-j)**2)
315
+ contrast = val * np.sum(ngtdm) / (n_g*(n_g-1)*n_tot)
316
+ return contrast
317
+
318
+ def busyness(ngtdm: np.ndarray, count_valid: np.ndarray)-> float:
319
+ """
320
+ Computes busyness feature.
321
+ This feature refers to "Busyness" (ID = NQ30) in
322
+ the `IBSI1 reference manual <https://arxiv.org/pdf/1612.07003.pdf>`__.
323
+
324
+ Args:
325
+ ngtdm (ndarray): array of neighbourhood grey tone difference matrix
326
+
327
+ Returns:
328
+ float: busyness value
329
+
330
+ """
331
+ n_tot = np.sum(count_valid)
332
+ count_valid = count_valid/n_tot
333
+ n_g = np.sum(count_valid != 0)
334
+ p_valid = np.where(np.reshape(count_valid, np.size(
335
+ count_valid), order='F') > 0)[0]+1
336
+ n_valid = np.size(p_valid)
337
+
338
+ if n_g == 1:
339
+ busyness = 0
340
+ return busyness
341
+ else:
342
+ denom = 0
343
+ for i in range(1, n_valid+1):
344
+ for j in range(1, n_valid+1):
345
+ denom = denom + np.abs(p_valid[i-1]*count_valid[p_valid[i-1]-1] -
346
+ p_valid[j-1]*count_valid[p_valid[j-1]-1])
347
+ busyness = np.matmul(np.transpose(count_valid), ngtdm) / denom
348
+ return busyness
349
+
350
+ def complexity(ngtdm: np.ndarray, count_valid: np.ndarray)-> float:
351
+ """
352
+ Computes complexity feature.
353
+ This feature refers to "Complexity" (ID = HDEZ) in
354
+ the `IBSI1 reference manual <https://arxiv.org/pdf/1612.07003.pdf>`__.
355
+
356
+ Args:
357
+ ngtdm (ndarray): array of neighbourhood grey tone difference matrix
358
+
359
+ Returns:
360
+ float: complexity value
361
+
362
+ """
363
+ n_tot = np.sum(count_valid)
364
+ # Now representing the probability of gray-level occurences
365
+ count_valid = count_valid/n_tot
366
+ p_valid = np.where(np.reshape(count_valid, np.size(
367
+ count_valid), order='F') > 0)[0]+1
368
+ n_valid = np.size(p_valid)
369
+
370
+ val = 0
371
+ for i in range(1, n_valid+1):
372
+ for j in range(1, n_valid+1):
373
+ val = val + (np.abs(
374
+ p_valid[i-1]-p_valid[j-1]) / (n_tot*(
375
+ count_valid[p_valid[i-1]-1] +
376
+ count_valid[p_valid[j-1]-1])))*(
377
+ count_valid[p_valid[i-1]-1]*ngtdm[p_valid[i-1]-1] +
378
+ count_valid[p_valid[j-1]-1]*ngtdm[p_valid[j-1]-1])
379
+ complexity = val
380
+ return complexity
381
+
382
+ def strength(ngtdm: np.ndarray, count_valid: np.ndarray)-> float:
383
+ """
384
+ Computes strength feature.
385
+ This feature refers to "Strength" (ID = 1X9X) in
386
+ the `IBSI1 reference manual <https://arxiv.org/pdf/1612.07003.pdf>`__.
387
+
388
+ Args:
389
+ ngtdm (ndarray): array of neighbourhood grey tone difference matrix
390
+
391
+ Returns:
392
+ float: strength value
393
+
394
+ """
395
+
396
+ n_tot = np.sum(count_valid)
397
+ # Now representing the probability of gray-level occurences
398
+ count_valid = count_valid/n_tot
399
+ p_valid = np.where(np.reshape(count_valid, np.size(
400
+ count_valid), order='F') > 0)[0]+1
401
+ n_valid = np.size(p_valid)
402
+
403
+ if np.sum(ngtdm) == 0:
404
+ strength = 0
405
+ return strength
406
+ else:
407
+ val = 0
408
+ for i in range(1, n_valid+1):
409
+ for j in range(1, n_valid+1):
410
+ val = val + (count_valid[p_valid[i-1]-1] + count_valid[p_valid[j-1]-1])*(
411
+ p_valid[i-1]-p_valid[j-1])**2
412
+
413
+ strength = val/np.sum(ngtdm)
414
+ return strength