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
MEDiml/utils/mode.py ADDED
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+
4
+
5
+ from typing import Tuple, Union
6
+
7
+ import numpy as np
8
+
9
+
10
+ def mode(x: np.ndarray,
11
+ return_counts=False) -> Union[Tuple[np.ndarray, np.ndarray],
12
+ np.ndarray]:
13
+ """Implementation of mode that also returns counts, unlike the standard statistics.mode.
14
+
15
+ Args:
16
+ x (ndarray): n-dimensional array of which to find mode.
17
+ return_counts (bool): If True, also return the number of times each unique item appears in ``x``.
18
+
19
+ Returns:
20
+ 2-element tuple containing
21
+
22
+ - ndarray: Array of the modal (most common) value in the given array.
23
+ - ndarray: Array of the counts if ``return_counts`` is True.
24
+ """
25
+
26
+ unique_values, counts = np.unique(x, return_counts=True)
27
+
28
+ if return_counts:
29
+ return unique_values[np.argmax(counts)], np.max(counts)
30
+
31
+ return unique_values[np.argmax(counts)]
@@ -0,0 +1,58 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+
4
+ from typing import List, Tuple, Union
5
+
6
+ import numpy as np
7
+
8
+ from ..utils.strfind import strfind
9
+
10
+
11
+ def parse_contour_string(contour_string) -> Union[Tuple[float, List[str]],
12
+ Tuple[int, List[str]],
13
+ Tuple[List[int], List[str]]]:
14
+ """Finds the delimeters (:math:`'+'` and :math:`'-'`) and the contour indexe(s) from the given string.
15
+
16
+ Args:
17
+ contour_string (str, float or int): Index or string of indexes with
18
+ delimeters. For example: :math:`'3'` or :math:`'1-3+2'`.
19
+
20
+ Returns:
21
+ float, int: If ``contour_string`` is a an int or float we return ``contour_string``.
22
+ List[str]: List of the delimeters.
23
+ List[int]: List of the contour indexes.
24
+
25
+ Example:
26
+ >>> ``contour_string`` = '1-3+2'
27
+ >>> :function: parse_contour_string(contour_string)
28
+ [1, 2, 3], ['+', '-']
29
+ >>> ``contour_string`` = 1
30
+ >>> :function: parse_contour_string(contour_string)
31
+ 1, []
32
+ """
33
+
34
+ if isinstance(contour_string, (int, float)):
35
+ return contour_string, []
36
+
37
+ ind_plus = strfind(string=contour_string, pattern='\+')
38
+ ind_minus = strfind(string=contour_string, pattern='\-')
39
+ ind_operations = np.sort(np.hstack((ind_plus, ind_minus))).astype(int)
40
+
41
+ # Parsing operations and contour numbers
42
+ # AZ: I assume that contour_number is an integer
43
+ if ind_operations.size == 0:
44
+ operations = []
45
+ contour_number = [int(contour_string)]
46
+ else:
47
+ n_op = len(ind_operations)
48
+ operations = [contour_string[ind_operations[i]] for i in np.arange(n_op)]
49
+
50
+ contour_number = np.zeros(n_op + 1, dtype=int)
51
+ contour_number[0] = int(contour_string[0:ind_operations[0]])
52
+ for c in np.arange(start=1, stop=n_op):
53
+ contour_number[c] = int(contour_string[(ind_operations[c-1]+1) : ind_operations[c]])
54
+
55
+ contour_number[-1] = int(contour_string[(ind_operations[-1]+1):])
56
+ contour_number.tolist()
57
+
58
+ return contour_number, operations
@@ -0,0 +1,30 @@
1
+ import pickle
2
+ from pathlib import Path
3
+
4
+ from ..MEDscan import MEDscan
5
+
6
+
7
+ def save_MEDscan(medscan: MEDscan,
8
+ path_save: Path) -> str:
9
+ """Saves MEDscan class instance in a pickle object
10
+
11
+ Args:
12
+ medscan (MEDscan): MEDscan instance
13
+ path_save (Path): MEDscan instance saving paths
14
+
15
+ Returns:
16
+ None.
17
+ """
18
+
19
+ series_description = medscan.series_description.translate({ord(ch): '-' for ch in '/\\ ()&:*'})
20
+ name_id = medscan.patientID
21
+ name_id = name_id.translate({ord(ch): '-' for ch in '/\\ ()&:*'})
22
+
23
+ # final saving name
24
+ name_complete = name_id + '__' + series_description + '.' + medscan.type + '.npy'
25
+
26
+ # save
27
+ with open(path_save / name_complete,'wb') as f:
28
+ pickle.dump(medscan, f)
29
+
30
+ return name_complete
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+
4
+ from re import finditer
5
+ from typing import List
6
+
7
+
8
+ def strfind(pattern: str,
9
+ string: str) -> List[int]:
10
+ """Finds indices of ``pattern`` in ``string``. Based on regex.
11
+
12
+ Note:
13
+ Be careful with + and - symbols. Use :math:`\+` and :math:`\-` instead.
14
+
15
+ Args:
16
+ pattern (str): Substring to be searched in the ``string``.
17
+ string (str): String used to find matches.
18
+
19
+ Returns:
20
+ List[int]: List of indexes of every occurence of ``pattern`` in the passed ``string``.
21
+
22
+ Raises:
23
+ ValueError: If the ``pattern`` does not use backslash with special regex symbols
24
+ """
25
+
26
+ if pattern in ('+', '-'):
27
+ raise ValueError(
28
+ "Please use a backslash with special regex symbols in findall.")
29
+
30
+ ind = [m.start() for m in finditer(pattern, string)]
31
+
32
+ return ind
@@ -0,0 +1,188 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+
4
+
5
+ from typing import List, Union
6
+
7
+ import numpy as np
8
+
9
+
10
+ def get_neighbour_direction(d=1.8,
11
+ distance="euclidian",
12
+ centre=False,
13
+ complete=False,
14
+ dim3=True) -> np.ndarray:
15
+ """Defines transitions to neighbour voxels.
16
+
17
+ Note:
18
+ This code was adapted from the in-house radiomics software created at
19
+ OncoRay, Dresden, Germany.
20
+
21
+ Args:
22
+ d (float, optional): Max ``distance`` between voxels.
23
+ distance (str, optional): Distance norm used to compute distances. must be
24
+ "manhattan", "l1", "l_1", "euclidian", "l2", "l_2", "chebyshev", "linf" or "l_inf".
25
+ centre (bool, optional): Flags whether the [0,0,0] direction should be included
26
+ complete(bool, optional): Flags whether all directions should be computed (True)
27
+ or just the primary ones (False). For example, including [0,0,1] and [0,0,-1]
28
+ directions may lead to redundant texture matrices.
29
+ dim3(bool, optional): flags whether full 3D (True) or only in-slice (2D; False)
30
+ directions should be considered.
31
+
32
+ Returns:
33
+ ndarray: set of k neighbour direction vectors.
34
+ """
35
+
36
+ # Base transition vector
37
+ trans = np.arange(start=-np.ceil(d), stop=np.ceil(d)+1)
38
+ n = np.size(trans)
39
+
40
+ # Build transition array [x,y,z]
41
+ nbrs = np.array([rep(x=trans, each=n * n, times=1),
42
+ rep(x=trans, each=n, times=n),
43
+ rep(x=trans, each=1, times=n * n)], dtype=np.int32)
44
+
45
+ # Initiate maintenance index
46
+ index = np.zeros(np.shape(nbrs)[1], dtype=bool)
47
+
48
+ # Remove neighbours more than distance d from the center ----------------
49
+
50
+ # Manhattan distance
51
+ if distance.lower() in ["manhattan", "l1", "l_1"]:
52
+ index = np.logical_or(index, np.sum(np.abs(nbrs), axis=0) <= d)
53
+ # Eucldian distance
54
+ if distance.lower() in ["euclidian", "l2", "l_2"]:
55
+ index = np.logical_or(index, np.sqrt(
56
+ np.sum(np.multiply(nbrs, nbrs), axis=0)) <= d)
57
+ # Chebyshev distance
58
+ if distance.lower() in ["chebyshev", "linf", "l_inf"]:
59
+ index = np.logical_or(index, np.max(np.abs(nbrs), axis=0) <= d)
60
+
61
+ # Check if centre voxel [0,0,0] should be maintained; False indicates removal
62
+ if centre is False:
63
+ index = np.logical_and(index, (np.sum(np.abs(nbrs), axis=0)) > 0)
64
+
65
+ # Check if a complete neighbourhood should be returned
66
+ # False indicates that only half of the vectors are returned
67
+ if complete is False:
68
+ index[np.arange(start=0, stop=len(index)//2 + 1)] = False
69
+
70
+ # Check if neighbourhood should be 3D or 2D
71
+ if dim3 is False:
72
+ index[nbrs[2, :] != 0] = False
73
+
74
+ return nbrs[:, index]
75
+
76
+
77
+ def rep(x: np.ndarray,
78
+ each=1,
79
+ times=1) -> np.ndarray:
80
+ """Replicates the values in ``x``.
81
+ Replicates the :func:`"rep"` function found in R for tiling and repeating vectors.
82
+
83
+ Note:
84
+ Code was adapted from the in-house radiomics software created at OncoRay,
85
+ Dresden, Germany.
86
+
87
+ Args:
88
+ x (ndarray): Array to replicate.
89
+ each (int): Integer (non-negative) giving the number of times to repeat
90
+ each element of the passed array.
91
+ times (int): Integer (non-negative). Each element of ``x`` is repeated each times.
92
+
93
+ Returns:
94
+ ndarray: Array with same values but replicated.
95
+ """
96
+
97
+ each = int(each)
98
+ times = int(times)
99
+
100
+ if each > 1:
101
+ x = np.repeat(x, repeats=each)
102
+
103
+ if times > 1:
104
+ x = np.tile(x, reps=times)
105
+
106
+ return x
107
+
108
+ def get_value(x: np.ndarray,
109
+ index: int,
110
+ replace_invalid=True) -> np.ndarray:
111
+ """Retrieves intensity values from an image intensity table used for computing
112
+ texture features.
113
+
114
+ Note:
115
+ Code was adapted from the in-house radiomics software created at OncoRay,
116
+ Dresden, Germany.
117
+
118
+ Args:
119
+ x (ndarray): set of intensity values.
120
+ index (int): Index to the provided set of intensity values.
121
+ replace_invalid (bool, optional): If True, invalid indices will be replaced
122
+ by a placeholder "NaN" value.
123
+
124
+ Returns:
125
+ ndarray: Array of the intensity values found at the requested indices.
126
+
127
+ """
128
+
129
+ # Initialise placeholder
130
+ read_x = np.zeros(np.shape(x))
131
+
132
+ # Read variables for valid indices
133
+ read_x[index >= 0] = x[index[index >= 0]]
134
+
135
+ if replace_invalid:
136
+ # Set variables for invalid indices to nan
137
+ read_x[index < 0] = np.nan
138
+
139
+ # Set variables for invalid initial indices to nan
140
+ read_x[np.isnan(x)] = np.nan
141
+
142
+ return read_x
143
+
144
+
145
+ def coord2index(x: np.ndarray,
146
+ y: np.ndarray,
147
+ z: np.ndarray,
148
+ dims: Union[List, np.ndarray]) -> Union[np.ndarray,
149
+ List]:
150
+ """Translate requested coordinates to row indices in image intensity tables.
151
+
152
+ Note:
153
+ Code was adapted from the in-house radiomics software created at OncoRay,
154
+ Dresden, Germany.
155
+
156
+ Args:
157
+ x (ndarray): set of discrete x-coordinates.
158
+ y (ndarray): set of discrete y-coordinates.
159
+ z (ndarray): set of discrete z-coordinates.
160
+ dims (ndarray or List): dimensions of the image.
161
+
162
+ Returns:
163
+ ndarray or List: Array or List of indexes corresponding the requested coordinates
164
+
165
+ """
166
+
167
+ # Translate coordinates to indices
168
+ index = z + y * dims[2] + x * dims[2] * dims[1]
169
+
170
+ # Mark invalid transitions
171
+ index[np.logical_or(x < 0, x >= dims[0])] = -99999
172
+ index[np.logical_or(y < 0, y >= dims[1])] = -99999
173
+ index[np.logical_or(z < 0, z >= dims[2])] = -99999
174
+
175
+ return index
176
+
177
+
178
+ def is_list_all_none(x: List) -> bool:
179
+ """Determines if all list elements are None.
180
+
181
+ Args:
182
+ x (List): List of elements to check.
183
+
184
+ Returns:
185
+ bool: True if all elemets in `x` are None.
186
+
187
+ """
188
+ return all(y is None for y in x)
@@ -0,0 +1,115 @@
1
+ glcm_features_names = [
2
+ "Fcm_joint_max",
3
+ "Fcm_joint_avg",
4
+ "Fcm_joint_var",
5
+ "Fcm_joint_entr",
6
+ "Fcm_diff_avg",
7
+ "Fcm_diff_var",
8
+ "Fcm_diff_entr",
9
+ "Fcm_sum_avg",
10
+ "Fcm_sum_var",
11
+ "Fcm_sum_entr",
12
+ "Fcm_energy",
13
+ "Fcm_contrast",
14
+ "Fcm_dissimilarity",
15
+ "Fcm_inv_diff",
16
+ "Fcm_inv_diff_norm",
17
+ "Fcm_inv_diff_mom",
18
+ "Fcm_inv_diff_mom_norm",
19
+ "Fcm_inv_var",
20
+ "Fcm_corr",
21
+ "Fcm_auto_corr",
22
+ "Fcm_info_corr1",
23
+ "Fcm_info_corr2",
24
+ "Fcm_clust_tend",
25
+ "Fcm_clust_shade",
26
+ "Fcm_clust_prom"
27
+ ]
28
+ glrlm_features_names = [
29
+ "Frlm_sre",
30
+ "Frlm_lre",
31
+ "Frlm_lgre",
32
+ "Frlm_hgre",
33
+ "Frlm_srlge",
34
+ "Frlm_srhge",
35
+ "Frlm_lrlge",
36
+ "Frlm_lrhge",
37
+ "Frlm_glnu",
38
+ "Frlm_glnu_norm",
39
+ "Frlm_rlnu",
40
+ "Frlm_rlnu_norm",
41
+ "Frlm_r_perc",
42
+ "Frlm_gl_var",
43
+ "Frlm_rl_var",
44
+ "Frlm_rl_entr"
45
+ ]
46
+ glszm_features_names = [
47
+ "Fszm_sze",
48
+ "Fszm_lze",
49
+ "Fszm_lgze",
50
+ "Fszm_hgze",
51
+ "Fszm_szlge",
52
+ "Fszm_szhge",
53
+ "Fszm_lzlge",
54
+ "Fszm_lzhge",
55
+ "Fszm_glnu",
56
+ "Fszm_glnu_norm",
57
+ "Fszm_zsnu",
58
+ "Fszm_zsnu_norm",
59
+ "Fszm_z_perc",
60
+ "Fszm_gl_var",
61
+ "Fszm_zs_var",
62
+ "Fszm_zs_entr",
63
+ ]
64
+ gldzm_features_names = [
65
+ "Fdzm_sde",
66
+ "Fdzm_lde",
67
+ "Fdzm_lgze",
68
+ "Fdzm_hgze",
69
+ "Fdzm_sdlge",
70
+ "Fdzm_sdhge",
71
+ "Fdzm_ldlge",
72
+ "Fdzm_ldhge",
73
+ "Fdzm_glnu",
74
+ "Fdzm_glnu_norm",
75
+ "Fdzm_zdnu",
76
+ "Fdzm_zdnu_norm",
77
+ "Fdzm_z_perc",
78
+ "Fdzm_gl_var",
79
+ "Fdzm_zd_var",
80
+ "Fdzm_zd_entr"
81
+ ]
82
+ ngtdm_features_names = [
83
+ "Fngt_coarseness"
84
+ "Fngt_contrast"
85
+ "Fngt_busyness"
86
+ "Fngt_complexity"
87
+ "Fngt_strength"
88
+ ]
89
+ ngldm_features_names = [
90
+ "Fngl_lde",
91
+ "Fngl_hde",
92
+ "Fngl_lgce",
93
+ "Fngl_hgce",
94
+ "Fngl_ldlge",
95
+ "Fngl_ldhge",
96
+ "Fngl_hdlge",
97
+ "Fngl_hdhge",
98
+ "Fngl_glnu",
99
+ "Fngl_glnu_norm",
100
+ "Fngl_dcnu",
101
+ "Fngl_dcnu_norm",
102
+ "Fngl_gl_var",
103
+ "Fngl_dc_var",
104
+ "Fngl_dc_entr",
105
+ "Fngl_dc_energy"
106
+ ]
107
+ # PS: DO NOT CHANGE THE ORDER OF THE LISTS BELOW, CHANGING THE ORDER WILL BREAK THE CODE (RESULTS CLASS)
108
+ texture_features_all = [
109
+ glcm_features_names,
110
+ ngtdm_features_names,
111
+ ngldm_features_names,
112
+ glrlm_features_names,
113
+ gldzm_features_names,
114
+ glszm_features_names
115
+ ]
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+
4
+ from pathlib import Path
5
+ from typing import Union
6
+
7
+ import numpy as np
8
+
9
+
10
+ def write_radiomics_csv(path_radiomics_table: Union[Path, str]) -> None:
11
+ """
12
+ Loads a radiomics structure (dict with radiomics features) to convert it to a CSV file and save it.
13
+
14
+ Args:
15
+ path_radiomics_table(Union[Path, str]): path to the radiomics dict.
16
+
17
+ Returns:
18
+ None.
19
+ """
20
+
21
+ # INITIALIZATION
22
+ path_radiomics_table = Path(path_radiomics_table)
23
+ path_to_table = path_radiomics_table.parent
24
+ name_table = path_radiomics_table.stem
25
+
26
+ # LOAD RADIOMICS TABLE
27
+ radiomics_table_dict = np.load(path_radiomics_table, allow_pickle=True)[0]
28
+
29
+ # WRITE RADIOMICS TABLE IN CSV FORMAT
30
+ csv_name = name_table + '.csv'
31
+ csv_path = path_to_table / csv_name
32
+ radiomics_table_dict['Table'] = radiomics_table_dict['Table'].fillna(value='NaN')
33
+ radiomics_table_dict['Table'] = radiomics_table_dict['Table'].sort_index()
34
+ radiomics_table_dict['Table'].to_csv(csv_path,
35
+ sep=',',
36
+ encoding='utf-8',
37
+ index=True,
38
+ index_label=radiomics_table_dict['Properties']['DimensionNames'][0])
39
+
40
+ # WRITE DEFINITIONS.TXT
41
+ txt_name = name_table + '.txt'
42
+ txt_Path = path_to_table / txt_name
43
+
44
+ # WRITE THE CSV
45
+ fid = open(txt_Path, 'w')
46
+ fid.write(radiomics_table_dict['Properties']['UserData'])
47
+ fid.close()