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.
- MEDiml/MEDscan.py +1696 -0
- MEDiml/__init__.py +21 -0
- MEDiml/biomarkers/BatchExtractor.py +806 -0
- MEDiml/biomarkers/BatchExtractorTexturalFilters.py +840 -0
- MEDiml/biomarkers/__init__.py +16 -0
- MEDiml/biomarkers/diagnostics.py +125 -0
- MEDiml/biomarkers/get_oriented_bound_box.py +158 -0
- MEDiml/biomarkers/glcm.py +1602 -0
- MEDiml/biomarkers/gldzm.py +523 -0
- MEDiml/biomarkers/glrlm.py +1315 -0
- MEDiml/biomarkers/glszm.py +555 -0
- MEDiml/biomarkers/int_vol_hist.py +527 -0
- MEDiml/biomarkers/intensity_histogram.py +615 -0
- MEDiml/biomarkers/local_intensity.py +89 -0
- MEDiml/biomarkers/morph.py +1756 -0
- MEDiml/biomarkers/ngldm.py +780 -0
- MEDiml/biomarkers/ngtdm.py +414 -0
- MEDiml/biomarkers/stats.py +373 -0
- MEDiml/biomarkers/utils.py +389 -0
- MEDiml/filters/TexturalFilter.py +299 -0
- MEDiml/filters/__init__.py +9 -0
- MEDiml/filters/apply_filter.py +134 -0
- MEDiml/filters/gabor.py +215 -0
- MEDiml/filters/laws.py +283 -0
- MEDiml/filters/log.py +147 -0
- MEDiml/filters/mean.py +121 -0
- MEDiml/filters/textural_filters_kernels.py +1738 -0
- MEDiml/filters/utils.py +107 -0
- MEDiml/filters/wavelet.py +237 -0
- MEDiml/learning/DataCleaner.py +198 -0
- MEDiml/learning/DesignExperiment.py +480 -0
- MEDiml/learning/FSR.py +667 -0
- MEDiml/learning/Normalization.py +112 -0
- MEDiml/learning/RadiomicsLearner.py +714 -0
- MEDiml/learning/Results.py +2237 -0
- MEDiml/learning/Stats.py +694 -0
- MEDiml/learning/__init__.py +10 -0
- MEDiml/learning/cleaning_utils.py +107 -0
- MEDiml/learning/ml_utils.py +1015 -0
- MEDiml/processing/__init__.py +6 -0
- MEDiml/processing/compute_suv_map.py +121 -0
- MEDiml/processing/discretisation.py +149 -0
- MEDiml/processing/interpolation.py +275 -0
- MEDiml/processing/resegmentation.py +66 -0
- MEDiml/processing/segmentation.py +912 -0
- MEDiml/utils/__init__.py +25 -0
- MEDiml/utils/batch_patients.py +45 -0
- MEDiml/utils/create_radiomics_table.py +131 -0
- MEDiml/utils/data_frame_export.py +42 -0
- MEDiml/utils/find_process_names.py +16 -0
- MEDiml/utils/get_file_paths.py +34 -0
- MEDiml/utils/get_full_rad_names.py +21 -0
- MEDiml/utils/get_institutions_from_ids.py +16 -0
- MEDiml/utils/get_patient_id_from_scan_name.py +22 -0
- MEDiml/utils/get_patient_names.py +26 -0
- MEDiml/utils/get_radiomic_names.py +27 -0
- MEDiml/utils/get_scan_name_from_rad_name.py +22 -0
- MEDiml/utils/image_reader_SITK.py +37 -0
- MEDiml/utils/image_volume_obj.py +22 -0
- MEDiml/utils/imref.py +340 -0
- MEDiml/utils/initialize_features_names.py +62 -0
- MEDiml/utils/inpolygon.py +159 -0
- MEDiml/utils/interp3.py +43 -0
- MEDiml/utils/json_utils.py +78 -0
- MEDiml/utils/mode.py +31 -0
- MEDiml/utils/parse_contour_string.py +58 -0
- MEDiml/utils/save_MEDscan.py +30 -0
- MEDiml/utils/strfind.py +32 -0
- MEDiml/utils/textureTools.py +188 -0
- MEDiml/utils/texture_features_names.py +115 -0
- MEDiml/utils/write_radiomics_csv.py +47 -0
- MEDiml/wrangling/DataManager.py +1724 -0
- MEDiml/wrangling/ProcessDICOM.py +512 -0
- MEDiml/wrangling/__init__.py +3 -0
- mediml-0.9.9.dist-info/LICENSE.md +674 -0
- mediml-0.9.9.dist-info/METADATA +232 -0
- mediml-0.9.9.dist-info/RECORD +78 -0
- mediml-0.9.9.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,1602 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
from copy import deepcopy
|
|
6
|
+
from typing import Dict, List, Union, List
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
import pandas as pd
|
|
10
|
+
|
|
11
|
+
from ..utils.textureTools import (coord2index, get_neighbour_direction,
|
|
12
|
+
get_value, is_list_all_none)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def get_matrix(roi_only: np.ndarray,
|
|
16
|
+
levels: Union[np.ndarray, List],
|
|
17
|
+
dist_correction=True) -> np.ndarray:
|
|
18
|
+
r"""
|
|
19
|
+
This function computes the Gray-Level Co-occurence Matrix (GLCM) of the
|
|
20
|
+
region of interest (ROI) of an input volume. The input volume is assumed
|
|
21
|
+
to be isotropically resampled. Only one GLCM is computed per scan,
|
|
22
|
+
simultaneously recording (i.e. adding up) the neighboring properties of
|
|
23
|
+
the 26-connected neighbors of all voxels in the ROI. To account for
|
|
24
|
+
discretization length differences, neighbors at a distance of :math:`\sqrt{3}`
|
|
25
|
+
voxels around a center voxel increment the GLCM by a value of :math:`\sqrt{3}`,
|
|
26
|
+
neighbors at a distance of :math:`\sqrt{2}` voxels around a center voxel increment
|
|
27
|
+
the GLCM by a value of :math:`\sqrt{2}`, and neighbors at a distance of 1 voxels
|
|
28
|
+
around a center voxel increment the GLCM by a value of 1.
|
|
29
|
+
This matrix refers to "Grey level co-occurrence based features" (ID = LFYI)
|
|
30
|
+
in the `IBSI1 reference manual <https://arxiv.org/pdf/1612.07003.pdf>`_.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
roi_only (ndarray): Smallest box containing the ROI, with the imaging data
|
|
34
|
+
ready for texture analysis computations. Voxels outside the ROI are
|
|
35
|
+
set to NaNs.
|
|
36
|
+
levels (ndarray or List): Vector containing the quantized gray-levels in the tumor region
|
|
37
|
+
(or reconstruction ``levels`` of quantization).
|
|
38
|
+
dist_correction (bool, optional): Set this variable to true in order to use
|
|
39
|
+
discretization length difference corrections as used by the `Institute of Physics and
|
|
40
|
+
Engineering in Medicine <https://doi.org/10.1088/0031-9155/60/14/5471>`_.
|
|
41
|
+
Set this variable to false to replicate IBSI results.
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
ndarray: Gray-Level Co-occurence Matrix of ``roi_only``.
|
|
45
|
+
|
|
46
|
+
References:
|
|
47
|
+
[1] Haralick, R. M., Shanmugam, K., & Dinstein, I. (1973). Textural \
|
|
48
|
+
features for image classification. IEEE Transactions on Systems, \
|
|
49
|
+
Man and Cybernetics, smc 3(6), 610–621.
|
|
50
|
+
"""
|
|
51
|
+
# PARSING "dist_correction" ARGUMENT
|
|
52
|
+
if type(dist_correction) is not bool:
|
|
53
|
+
# The user did not input either "true" or "false",
|
|
54
|
+
# so the default behavior is used.
|
|
55
|
+
dist_correction = True
|
|
56
|
+
|
|
57
|
+
# PRELIMINARY
|
|
58
|
+
roi_only = roi_only.copy()
|
|
59
|
+
level_temp = np.max(levels)+1
|
|
60
|
+
roi_only[np.isnan(roi_only)] = level_temp
|
|
61
|
+
#levels = np.append(levels, level_temp)
|
|
62
|
+
dim = np.shape(roi_only)
|
|
63
|
+
|
|
64
|
+
if np.ndim(roi_only) == 2:
|
|
65
|
+
dim[2] = 1
|
|
66
|
+
|
|
67
|
+
q2 = np.reshape(roi_only, (1, np.prod(dim)))
|
|
68
|
+
|
|
69
|
+
# QUANTIZATION EFFECTS CORRECTION (M. Vallieres)
|
|
70
|
+
# In case (for example) we initially wanted to have 64 levels, but due to
|
|
71
|
+
# quantization, only 60 resulted.
|
|
72
|
+
# qs = round(levels*adjust)/adjust;
|
|
73
|
+
# q2 = round(q2*adjust)/adjust;
|
|
74
|
+
|
|
75
|
+
#qs = levels
|
|
76
|
+
qs = levels.tolist() + [level_temp]
|
|
77
|
+
lqs = np.size(qs)
|
|
78
|
+
|
|
79
|
+
q3 = q2*0
|
|
80
|
+
for k in range(0, lqs):
|
|
81
|
+
q3[q2 == qs[k]] = k
|
|
82
|
+
|
|
83
|
+
q3 = np.reshape(q3, dim).astype(int)
|
|
84
|
+
GLCM = np.zeros((lqs, lqs))
|
|
85
|
+
|
|
86
|
+
for i in range(1, dim[0]+1):
|
|
87
|
+
i_min = max(1, i-1)
|
|
88
|
+
i_max = min(i+1, dim[0])
|
|
89
|
+
for j in range(1, dim[1]+1):
|
|
90
|
+
j_min = max(1, j-1)
|
|
91
|
+
j_max = min(j+1, dim[1])
|
|
92
|
+
for k in range(1, dim[2]+1):
|
|
93
|
+
k_min = max(1, k-1)
|
|
94
|
+
k_max = min(k+1, dim[2])
|
|
95
|
+
val_q3 = q3[i-1, j-1, k-1]
|
|
96
|
+
for I2 in range(i_min, i_max+1):
|
|
97
|
+
for J2 in range(j_min, j_max+1):
|
|
98
|
+
for K2 in range(k_min, k_max+1):
|
|
99
|
+
if (I2 == i) & (J2 == j) & (K2 == k):
|
|
100
|
+
continue
|
|
101
|
+
else:
|
|
102
|
+
val_neighbor = q3[I2-1, J2-1, K2-1]
|
|
103
|
+
if dist_correction:
|
|
104
|
+
# Discretization length correction
|
|
105
|
+
GLCM[val_q3, val_neighbor] += \
|
|
106
|
+
np.sqrt(abs(I2-i)+abs(J2-j)+abs(K2-k))
|
|
107
|
+
else:
|
|
108
|
+
GLCM[val_q3, val_neighbor] += 1
|
|
109
|
+
|
|
110
|
+
GLCM = GLCM[0:-1, 0:-1]
|
|
111
|
+
|
|
112
|
+
return GLCM
|
|
113
|
+
|
|
114
|
+
def joint_max(glcm_dict: Dict) -> Union[float, List[float]]:
|
|
115
|
+
"""Computes joint maximum features.
|
|
116
|
+
This feature refers to "Fcm_joint_max" (ID = GYBY) in the `IBSI1 reference \
|
|
117
|
+
manual <https://arxiv.org/pdf/1612.07003.pdf>`__.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
glcm_dict (Dict): dictionary with glcm matrices, generated using :func:`get_glcm_matrices`
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
Union[float, List[float]]:: List or float of the joint maximum feature(s)
|
|
124
|
+
"""
|
|
125
|
+
temp = []
|
|
126
|
+
joint_max = []
|
|
127
|
+
for key in glcm_dict.keys():
|
|
128
|
+
for glcm in glcm_dict[key]:
|
|
129
|
+
df_pij, _, _, _, _, _ = glcm.get_cm_data([np.nan, np.nan])
|
|
130
|
+
temp.append(np.max(df_pij.pij))
|
|
131
|
+
if len(glcm_dict) <= 1:
|
|
132
|
+
return sum(temp) / len(temp)
|
|
133
|
+
else:
|
|
134
|
+
print(f'Merge method: {key}, joint max: {sum(temp) / len(temp)}')
|
|
135
|
+
joint_max.append(sum(temp) / len(temp))
|
|
136
|
+
return joint_max
|
|
137
|
+
|
|
138
|
+
def joint_avg(glcm_dict: Dict) -> Union[float, List[float]]:
|
|
139
|
+
"""Computes joint average features.
|
|
140
|
+
This feature refers to "Fcm_joint_avg" (ID = 60VM) in the `IBSI1 reference \
|
|
141
|
+
manual <https://arxiv.org/pdf/1612.07003.pdf>`__.
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
glcm_dict (Dict): dictionary with glcm matrices, generated using :func:`get_glcm_matrices`
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
Union[float, List[float]]:: List or float of the joint average feature(s)
|
|
148
|
+
"""
|
|
149
|
+
temp = []
|
|
150
|
+
joint_avg = []
|
|
151
|
+
for key in glcm_dict.keys():
|
|
152
|
+
for glcm in glcm_dict[key]:
|
|
153
|
+
df_pij, _, _, _, _, _ = glcm.get_cm_data([np.nan, np.nan])
|
|
154
|
+
temp.append(np.sum(df_pij.i * df_pij.pij))
|
|
155
|
+
if len(glcm_dict) <= 1:
|
|
156
|
+
return sum(temp) / len(temp)
|
|
157
|
+
else:
|
|
158
|
+
print(f'Merge method: {key}, joint avg: {sum(temp) / len(temp)}')
|
|
159
|
+
joint_avg.append(sum(temp) / len(temp))
|
|
160
|
+
return joint_avg
|
|
161
|
+
|
|
162
|
+
def joint_var(glcm_dict: Dict) -> Union[float, List[float]]:
|
|
163
|
+
"""Computes joint variance features.
|
|
164
|
+
This feature refers to "Fcm_var" (ID = UR99) in the `IBSI1 reference \
|
|
165
|
+
manual <https://arxiv.org/pdf/1612.07003.pdf>`__.
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
glcm_dict (Dict): dictionary with glcm matrices, generated using :func:`get_glcm_matrices`
|
|
169
|
+
|
|
170
|
+
Returns:
|
|
171
|
+
Union[float, List[float]]: List or float of the joint variance feature(s)
|
|
172
|
+
"""
|
|
173
|
+
temp = []
|
|
174
|
+
joint_var = []
|
|
175
|
+
for key in glcm_dict.keys():
|
|
176
|
+
for glcm in glcm_dict[key]:
|
|
177
|
+
df_pij, _, _, _, _, _ = glcm.get_cm_data([np.nan, np.nan])
|
|
178
|
+
m_u = np.sum(df_pij.i * df_pij.pij)
|
|
179
|
+
temp.append(np.sum((df_pij.i - m_u) ** 2.0 * df_pij.pij))
|
|
180
|
+
if len(glcm_dict) <= 1:
|
|
181
|
+
return sum(temp) / len(temp)
|
|
182
|
+
else:
|
|
183
|
+
print(f'Merge method: {key}, joint var: {sum(temp) / len(temp)}')
|
|
184
|
+
joint_var.append(sum(temp) / len(temp))
|
|
185
|
+
return joint_var
|
|
186
|
+
|
|
187
|
+
def joint_entr(glcm_dict: Dict) -> Union[float, List[float]]:
|
|
188
|
+
"""Computes joint entropy features.
|
|
189
|
+
This feature refers to "Fcm_joint_entr" (ID = TU9B) in the `IBSI1 reference \
|
|
190
|
+
manual <https://arxiv.org/pdf/1612.07003.pdf>`__.
|
|
191
|
+
|
|
192
|
+
Args:
|
|
193
|
+
glcm_dict (Dict): dictionary with glcm matrices, generated using :func:`get_glcm_matrices`
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
Union[float, List[float]]: the joint entropy features
|
|
197
|
+
"""
|
|
198
|
+
temp = []
|
|
199
|
+
joint_entr = []
|
|
200
|
+
for key in glcm_dict.keys():
|
|
201
|
+
for glcm in glcm_dict[key]:
|
|
202
|
+
df_pij, _, _, _, _, _ = glcm.get_cm_data([np.nan, np.nan])
|
|
203
|
+
temp.append(-np.sum(df_pij.pij * np.log2(df_pij.pij)))
|
|
204
|
+
if len(glcm_dict) <= 1:
|
|
205
|
+
return sum(temp) / len(temp)
|
|
206
|
+
else:
|
|
207
|
+
print(f'Merge method: {key}, joint entr: {sum(temp) / len(temp)}')
|
|
208
|
+
joint_entr.append(sum(temp) / len(temp))
|
|
209
|
+
return joint_entr
|
|
210
|
+
|
|
211
|
+
def diff_avg(glcm_dict: Dict) -> Union[float, List[float]]:
|
|
212
|
+
"""Computes difference average features.
|
|
213
|
+
This feature refers to "Fcm_diff_avg" (ID = TF7R) in the `IBSI1 reference \
|
|
214
|
+
manual <https://arxiv.org/pdf/1612.07003.pdf>`__.
|
|
215
|
+
|
|
216
|
+
Args:
|
|
217
|
+
glcm_dict (Dict): dictionary with glcm matrices, generated using :func:`get_glcm_matrices`
|
|
218
|
+
|
|
219
|
+
Returns:
|
|
220
|
+
Union[float, List[float]]: the difference average features
|
|
221
|
+
"""
|
|
222
|
+
temp = []
|
|
223
|
+
diff_avg = []
|
|
224
|
+
for key in glcm_dict.keys():
|
|
225
|
+
for glcm in glcm_dict[key]:
|
|
226
|
+
_, _, _, df_pimj, _, _ = glcm.get_cm_data([np.nan, np.nan])
|
|
227
|
+
temp.append(np.sum(df_pimj.k * df_pimj.pimj))
|
|
228
|
+
if len(glcm_dict) <= 1:
|
|
229
|
+
return sum(temp) / len(temp)
|
|
230
|
+
else:
|
|
231
|
+
print(f'Merge method: {key}, diff avg: {sum(temp) / len(temp)}')
|
|
232
|
+
diff_avg.append(sum(temp) / len(temp))
|
|
233
|
+
return diff_avg
|
|
234
|
+
|
|
235
|
+
def diff_var(glcm_dict: Dict) -> Union[float, List[float]]:
|
|
236
|
+
"""Computes difference variance features.
|
|
237
|
+
This feature refers to "Fcm_diff_var" (ID = D3YU) in the `IBSI1 reference \
|
|
238
|
+
manual <https://arxiv.org/pdf/1612.07003.pdf>`__.
|
|
239
|
+
|
|
240
|
+
Args:
|
|
241
|
+
glcm_dict (Dict): dictionary with glcm matrices, generated using :func:`get_glcm_matrices`
|
|
242
|
+
|
|
243
|
+
Returns:
|
|
244
|
+
Union[float, List[float]]: the difference variance features
|
|
245
|
+
"""
|
|
246
|
+
temp = []
|
|
247
|
+
diff_var = []
|
|
248
|
+
for key in glcm_dict.keys():
|
|
249
|
+
for glcm in glcm_dict[key]:
|
|
250
|
+
_, _, _, df_pimj, _, _ = glcm.get_cm_data([np.nan, np.nan])
|
|
251
|
+
m_u = np.sum(df_pimj.k * df_pimj.pimj)
|
|
252
|
+
temp.append(np.sum((df_pimj.k - m_u) ** 2.0 * df_pimj.pimj))
|
|
253
|
+
if len(glcm_dict) <= 1:
|
|
254
|
+
return sum(temp) / len(temp)
|
|
255
|
+
else:
|
|
256
|
+
print(f'Merge method: {key}, diff var: {sum(temp) / len(temp)}')
|
|
257
|
+
diff_var.append(sum(temp) / len(temp))
|
|
258
|
+
return diff_var
|
|
259
|
+
|
|
260
|
+
def diff_entr(glcm_dict: Dict) -> Union[float, List[float]]:
|
|
261
|
+
"""Computes difference entropy features.
|
|
262
|
+
This feature refers to "Fcm_diff_entr" (ID = NTRS) in the `IBSI1 reference \
|
|
263
|
+
manual <https://arxiv.org/pdf/1612.07003.pdf>`__.
|
|
264
|
+
|
|
265
|
+
Args:
|
|
266
|
+
glcm_dict (Dict): dictionary with glcm matrices, generated using :func:`get_glcm_matrices`
|
|
267
|
+
|
|
268
|
+
Returns:
|
|
269
|
+
Union[float, List[float]]: the difference entropy features
|
|
270
|
+
"""
|
|
271
|
+
temp = []
|
|
272
|
+
diff_entr = []
|
|
273
|
+
for key in glcm_dict.keys():
|
|
274
|
+
for glcm in glcm_dict[key]:
|
|
275
|
+
_, _, _, df_pimj, _, _ = glcm.get_cm_data([np.nan, np.nan])
|
|
276
|
+
temp.append(-np.sum(df_pimj.pimj * np.log2(df_pimj.pimj)))
|
|
277
|
+
if len(glcm_dict) <= 1:
|
|
278
|
+
return sum(temp) / len(temp)
|
|
279
|
+
else:
|
|
280
|
+
print(f'Merge method: {key}, diff entr: {sum(temp) / len(temp)}')
|
|
281
|
+
diff_entr.append(sum(temp) / len(temp))
|
|
282
|
+
return diff_entr
|
|
283
|
+
|
|
284
|
+
def sum_avg(glcm_dict: Dict) -> Union[float, List[float]]:
|
|
285
|
+
"""Computes sum average features.
|
|
286
|
+
This feature refers to "Fcm_sum_avg" (ID = ZGXS) in the `IBSI1 reference \
|
|
287
|
+
manual <https://arxiv.org/pdf/1612.07003.pdf>`__.
|
|
288
|
+
|
|
289
|
+
Args:
|
|
290
|
+
glcm_dict (Dict): dictionary with glcm matrices, generated using :func:`get_glcm_matrices`
|
|
291
|
+
|
|
292
|
+
Returns:
|
|
293
|
+
Union[float, List[float]]: the sum average features
|
|
294
|
+
"""
|
|
295
|
+
temp = []
|
|
296
|
+
sum_avg = []
|
|
297
|
+
for key in glcm_dict.keys():
|
|
298
|
+
for glcm in glcm_dict[key]:
|
|
299
|
+
_, _, _, _, df_pipj, _ = glcm.get_cm_data([np.nan, np.nan])
|
|
300
|
+
temp.append(np.sum(df_pipj.k * df_pipj.pipj))
|
|
301
|
+
if len(glcm_dict) <= 1:
|
|
302
|
+
return sum(temp) / len(temp)
|
|
303
|
+
else:
|
|
304
|
+
print(f'Merge method: {key}, sum avg: {sum(temp) / len(temp)}')
|
|
305
|
+
sum_avg.append(sum(temp) / len(temp))
|
|
306
|
+
return sum_avg
|
|
307
|
+
|
|
308
|
+
def sum_var(glcm_dict: Dict) -> Union[float, List[float]]:
|
|
309
|
+
"""Computes sum variance features.
|
|
310
|
+
This feature refers to "Fcm_sum_var" (ID = OEEB) in the `IBSI1 reference \
|
|
311
|
+
manual <https://arxiv.org/pdf/1612.07003.pdf>`__.
|
|
312
|
+
|
|
313
|
+
Args:
|
|
314
|
+
glcm_dict (Dict): dictionary with glcm matrices, generated using :func:`get_glcm_matrices`
|
|
315
|
+
|
|
316
|
+
Returns:
|
|
317
|
+
Union[float, List[float]]: the sum variance features
|
|
318
|
+
"""
|
|
319
|
+
temp = []
|
|
320
|
+
sum_var = []
|
|
321
|
+
for key in glcm_dict.keys():
|
|
322
|
+
for glcm in glcm_dict[key]:
|
|
323
|
+
_, _, _, _, df_pipj, _ = glcm.get_cm_data([np.nan, np.nan])
|
|
324
|
+
m_u = np.sum(df_pipj.k * df_pipj.pipj)
|
|
325
|
+
temp.append(np.sum((df_pipj.k - m_u) ** 2.0 * df_pipj.pipj))
|
|
326
|
+
if len(glcm_dict) <= 1:
|
|
327
|
+
return sum(temp) / len(temp)
|
|
328
|
+
else:
|
|
329
|
+
print(f'Merge method: {key}, sum var: {sum(temp) / len(temp)}')
|
|
330
|
+
sum_var.append(sum(temp) / len(temp))
|
|
331
|
+
return sum_var
|
|
332
|
+
|
|
333
|
+
def sum_entr(glcm_dict: Dict) -> Union[float, List[float]]:
|
|
334
|
+
"""Computes sum entropy features.
|
|
335
|
+
This feature refers to "Fcm_sum_entr" (ID = P6QZ) in the `IBSI1 reference \
|
|
336
|
+
manual <https://arxiv.org/pdf/1612.07003.pdf>`__.
|
|
337
|
+
|
|
338
|
+
Args:
|
|
339
|
+
glcm_dict (Dict): dictionary with glcm matrices, generated using :func:`get_glcm_matrices`
|
|
340
|
+
|
|
341
|
+
Returns:
|
|
342
|
+
Union[float, List[float]]: the sum entropy features
|
|
343
|
+
"""
|
|
344
|
+
temp = []
|
|
345
|
+
sum_entr = []
|
|
346
|
+
for key in glcm_dict.keys():
|
|
347
|
+
for glcm in glcm_dict[key]:
|
|
348
|
+
_, _, _, _, df_pipj, _ = glcm.get_cm_data([np.nan, np.nan])
|
|
349
|
+
temp.append(-np.sum(df_pipj.pipj * np.log2(df_pipj.pipj)))
|
|
350
|
+
if len(glcm_dict) <= 1:
|
|
351
|
+
return sum(temp) / len(temp)
|
|
352
|
+
else:
|
|
353
|
+
print(f'Merge method: {key}, sum entr: {sum(temp) / len(temp)}')
|
|
354
|
+
sum_entr.append(sum(temp) / len(temp))
|
|
355
|
+
return sum_entr
|
|
356
|
+
|
|
357
|
+
def energy(glcm_dict: Dict) -> Union[float, List[float]]:
|
|
358
|
+
"""Computes angular second moment features.
|
|
359
|
+
This feature refers to "Fcm_energy" (ID = 8ZQL) in the `IBSI1 reference \
|
|
360
|
+
manual <https://arxiv.org/pdf/1612.07003.pdf>`__.
|
|
361
|
+
|
|
362
|
+
Args:
|
|
363
|
+
glcm_dict (Dict): dictionary with glcm matrices, generated using :func:`get_glcm_matrices`
|
|
364
|
+
|
|
365
|
+
Returns:
|
|
366
|
+
Union[float, List[float]]: the angular second moment features
|
|
367
|
+
"""
|
|
368
|
+
temp = []
|
|
369
|
+
energy = []
|
|
370
|
+
for key in glcm_dict.keys():
|
|
371
|
+
for glcm in glcm_dict[key]:
|
|
372
|
+
df_pij, _, _, _, _, _ = glcm.get_cm_data([np.nan, np.nan])
|
|
373
|
+
temp.append(np.sum(df_pij.pij ** 2.0))
|
|
374
|
+
if len(glcm_dict) <= 1:
|
|
375
|
+
return sum(temp) / len(temp)
|
|
376
|
+
else:
|
|
377
|
+
print(f'Merge method: {key}, energy: {sum(temp) / len(temp)}')
|
|
378
|
+
energy.append(sum(temp) / len(temp))
|
|
379
|
+
return energy
|
|
380
|
+
|
|
381
|
+
def contrast(glcm_dict: Dict) -> Union[float, List[float]]:
|
|
382
|
+
"""Computes constrast features.
|
|
383
|
+
This feature refers to "Fcm_contrast" (ID = ACUI) in the `IBSI1 reference \
|
|
384
|
+
manual <https://arxiv.org/pdf/1612.07003.pdf>`__.
|
|
385
|
+
|
|
386
|
+
Args:
|
|
387
|
+
glcm_dict (Dict): dictionary with glcm matrices, generated using :func:`get_glcm_matrices`
|
|
388
|
+
|
|
389
|
+
Returns:
|
|
390
|
+
Union[float, List[float]]: the contrast features
|
|
391
|
+
"""
|
|
392
|
+
temp = []
|
|
393
|
+
contrast = []
|
|
394
|
+
for key in glcm_dict.keys():
|
|
395
|
+
for glcm in glcm_dict[key]:
|
|
396
|
+
df_pij, _, _, _, _, _ = glcm.get_cm_data([np.nan, np.nan])
|
|
397
|
+
temp.append(np.sum((df_pij.i - df_pij.j) ** 2.0 * df_pij.pij))
|
|
398
|
+
if len(glcm_dict) <= 1:
|
|
399
|
+
return sum(temp) / len(temp)
|
|
400
|
+
else:
|
|
401
|
+
print(f'Merge method: {key}, contrast: {sum(temp) / len(temp)}')
|
|
402
|
+
contrast.append(sum(temp) / len(temp))
|
|
403
|
+
return contrast
|
|
404
|
+
|
|
405
|
+
def dissimilarity(glcm_dict: Dict) -> Union[float, List[float]]:
|
|
406
|
+
"""Computes dissimilarity features.
|
|
407
|
+
This feature refers to "Fcm_dissimilarity" (ID = 8S9J) in the `IBSI1 reference \
|
|
408
|
+
manual <https://arxiv.org/pdf/1612.07003.pdf>`__.
|
|
409
|
+
|
|
410
|
+
Args:
|
|
411
|
+
glcm_dict (Dict): dictionary with glcm matrices, generated using :func:`get_glcm_matrices`
|
|
412
|
+
|
|
413
|
+
Returns:
|
|
414
|
+
Union[float, List[float]]: the dissimilarity features
|
|
415
|
+
"""
|
|
416
|
+
temp = []
|
|
417
|
+
dissimilarity = []
|
|
418
|
+
for key in glcm_dict.keys():
|
|
419
|
+
for glcm in glcm_dict[key]:
|
|
420
|
+
df_pij, _, _, _, _, _ = glcm.get_cm_data([np.nan, np.nan])
|
|
421
|
+
temp.append(np.sum(np.abs(df_pij.i - df_pij.j) * df_pij.pij))
|
|
422
|
+
if len(glcm_dict) <= 1:
|
|
423
|
+
return sum(temp) / len(temp)
|
|
424
|
+
else:
|
|
425
|
+
print(f'Merge method: {key}, dissimilarity: {sum(temp) / len(temp)}')
|
|
426
|
+
dissimilarity.append(sum(temp) / len(temp))
|
|
427
|
+
return dissimilarity
|
|
428
|
+
|
|
429
|
+
def inv_diff(glcm_dict: Dict) -> Union[float, List[float]]:
|
|
430
|
+
"""Computes inverse difference features.
|
|
431
|
+
This feature refers to "Fcm_inv_diff" (ID = IB1Z) in the `IBSI1 reference \
|
|
432
|
+
manual <https://arxiv.org/pdf/1612.07003.pdf>`__.
|
|
433
|
+
|
|
434
|
+
Args:
|
|
435
|
+
glcm_dict (Dict): dictionary with glcm matrices, generated using :func:`get_glcm_matrices`
|
|
436
|
+
|
|
437
|
+
Returns:
|
|
438
|
+
Union[float, List[float]]: the inverse difference features
|
|
439
|
+
"""
|
|
440
|
+
temp = []
|
|
441
|
+
inv_diff = []
|
|
442
|
+
for key in glcm_dict.keys():
|
|
443
|
+
for glcm in glcm_dict[key]:
|
|
444
|
+
df_pij, _, _, _, _, _ = glcm.get_cm_data([np.nan, np.nan])
|
|
445
|
+
temp.append(np.sum(df_pij.pij / (1.0 + np.abs(df_pij.i - df_pij.j))))
|
|
446
|
+
if len(glcm_dict) <= 1:
|
|
447
|
+
return sum(temp) / len(temp)
|
|
448
|
+
else:
|
|
449
|
+
print(f'Merge method: {key}, inv diff: {sum(temp) / len(temp)}')
|
|
450
|
+
inv_diff.append(sum(temp) / len(temp))
|
|
451
|
+
return inv_diff
|
|
452
|
+
|
|
453
|
+
def inv_diff_norm(glcm_dict: Dict) -> Union[float, List[float]]:
|
|
454
|
+
"""Computes inverse difference normalized features.
|
|
455
|
+
This feature refers to "Fcm_inv_diff_norm" (ID = NDRX) in the `IBSI1 reference \
|
|
456
|
+
manual <https://arxiv.org/pdf/1612.07003.pdf>`__.
|
|
457
|
+
|
|
458
|
+
Args:
|
|
459
|
+
glcm_dict (Dict): dictionary with glcm matrices, generated using :func:`get_glcm_matrices`
|
|
460
|
+
|
|
461
|
+
Returns:
|
|
462
|
+
Union[float, List[float]]: the inverse difference normalized features
|
|
463
|
+
"""
|
|
464
|
+
temp = []
|
|
465
|
+
inv_diff_norm = []
|
|
466
|
+
for key in glcm_dict.keys():
|
|
467
|
+
for glcm in glcm_dict[key]:
|
|
468
|
+
df_pij, _, _, _, _, n_g = glcm.get_cm_data([np.nan, np.nan])
|
|
469
|
+
temp.append(np.sum(df_pij.pij / (1.0 + np.abs(df_pij.i - df_pij.j) / n_g)))
|
|
470
|
+
if len(glcm_dict) <= 1:
|
|
471
|
+
return sum(temp) / len(temp)
|
|
472
|
+
else:
|
|
473
|
+
print(f'Merge method: {key}, inv diff norm: {sum(temp) / len(temp)}')
|
|
474
|
+
inv_diff_norm.append(sum(temp) / len(temp))
|
|
475
|
+
return inv_diff_norm
|
|
476
|
+
|
|
477
|
+
def inv_diff_mom(glcm_dict: Dict) -> Union[float, List[float]]:
|
|
478
|
+
"""Computes inverse difference moment features.
|
|
479
|
+
This feature refers to "Fcm_inv_diff_mom" (ID = WF0Z) in the `IBSI1 reference \
|
|
480
|
+
manual <https://arxiv.org/pdf/1612.07003.pdf>`__.
|
|
481
|
+
|
|
482
|
+
Args:
|
|
483
|
+
glcm_dict (Dict): dictionary with glcm matrices, generated using :func:`get_glcm_matrices`
|
|
484
|
+
|
|
485
|
+
Returns:
|
|
486
|
+
Union[float, List[float]]: the inverse difference moment features
|
|
487
|
+
"""
|
|
488
|
+
temp = []
|
|
489
|
+
inv_diff_mom = []
|
|
490
|
+
for key in glcm_dict.keys():
|
|
491
|
+
for glcm in glcm_dict[key]:
|
|
492
|
+
df_pij, _, _, _, _, _ = glcm.get_cm_data([np.nan, np.nan])
|
|
493
|
+
temp.append(np.sum(df_pij.pij / (1.0 + (df_pij.i - df_pij.j) ** 2.0)))
|
|
494
|
+
if len(glcm_dict) <= 1:
|
|
495
|
+
return sum(temp) / len(temp)
|
|
496
|
+
else:
|
|
497
|
+
print(f'Merge method: {key}, inv diff mom: {sum(temp) / len(temp)}')
|
|
498
|
+
inv_diff_mom.append(sum(temp) / len(temp))
|
|
499
|
+
return inv_diff_mom
|
|
500
|
+
|
|
501
|
+
def inv_diff_mom_norm(glcm_dict: Dict) -> Union[float, List[float]]:
|
|
502
|
+
"""Computes inverse difference moment normalized features.
|
|
503
|
+
This feature refers to "Fcm_inv_diff_mom_norm" (ID = 1QCO) in the `IBSI1 reference \
|
|
504
|
+
manual <https://arxiv.org/pdf/1612.07003.pdf>`__.
|
|
505
|
+
|
|
506
|
+
Args:
|
|
507
|
+
glcm_dict (Dict): dictionary with glcm matrices, generated using :func:`get_glcm_matrices`
|
|
508
|
+
|
|
509
|
+
Returns:
|
|
510
|
+
Union[float, List[float]]: the inverse difference moment normalized features
|
|
511
|
+
"""
|
|
512
|
+
temp = []
|
|
513
|
+
inv_diff_mom_norm = []
|
|
514
|
+
for key in glcm_dict.keys():
|
|
515
|
+
for glcm in glcm_dict[key]:
|
|
516
|
+
df_pij, _, _, _, _, n_g = glcm.get_cm_data([np.nan, np.nan])
|
|
517
|
+
temp.append(np.sum(df_pij.pij / (1.0 + (df_pij.i - df_pij.j)** 2.0 / n_g ** 2.0)))
|
|
518
|
+
if len(glcm_dict) <= 1:
|
|
519
|
+
return sum(temp) / len(temp)
|
|
520
|
+
else:
|
|
521
|
+
print(f'Merge method: {key}, inv diff mom norm: {sum(temp) / len(temp)}')
|
|
522
|
+
inv_diff_mom_norm.append(sum(temp) / len(temp))
|
|
523
|
+
return inv_diff_mom_norm
|
|
524
|
+
|
|
525
|
+
def inv_var(glcm_dict: Dict) -> Union[float, List[float]]:
|
|
526
|
+
"""Computes inverse variance features.
|
|
527
|
+
This feature refers to "Fcm_inv_var" (ID = E8JP) in the `IBSI1 reference \
|
|
528
|
+
manual <https://arxiv.org/pdf/1612.07003.pdf>`__.
|
|
529
|
+
|
|
530
|
+
Args:
|
|
531
|
+
glcm_dict (Dict): dictionary with glcm matrices, generated using :func:`get_glcm_matrices`
|
|
532
|
+
|
|
533
|
+
Returns:
|
|
534
|
+
Union[float, List[float]]: the inverse variance features
|
|
535
|
+
"""
|
|
536
|
+
temp = []
|
|
537
|
+
inv_var = []
|
|
538
|
+
for key in glcm_dict.keys():
|
|
539
|
+
for glcm in glcm_dict[key]:
|
|
540
|
+
df_pij, df_pi, _, _, _, _ = glcm.get_cm_data([np.nan, np.nan])
|
|
541
|
+
mu_marg = np.sum(df_pi.i * df_pi.pi)
|
|
542
|
+
var_marg = np.sum((df_pi.i - mu_marg) ** 2.0 * df_pi.pi)
|
|
543
|
+
if var_marg == 0.0:
|
|
544
|
+
temp.append(1.0)
|
|
545
|
+
else:
|
|
546
|
+
temp.append(1.0 / var_marg * (np.sum(df_pij.i * df_pij.j * df_pij.pij) - mu_marg ** 2.0))
|
|
547
|
+
if len(glcm_dict) <= 1:
|
|
548
|
+
return sum(temp) / len(temp)
|
|
549
|
+
else:
|
|
550
|
+
print(f'Merge method: {key}, inv var: {sum(temp) / len(temp)}')
|
|
551
|
+
inv_var.append(sum(temp) / len(temp))
|
|
552
|
+
return inv_var
|
|
553
|
+
|
|
554
|
+
def corr(glcm_dict: Dict) -> Union[float, List[float]]:
|
|
555
|
+
"""Computes correlation features.
|
|
556
|
+
This feature refers to "Fcm_corr" (ID = NI2N) in the `IBSI1 reference \
|
|
557
|
+
manual <https://arxiv.org/pdf/1612.07003.pdf>`__.
|
|
558
|
+
|
|
559
|
+
Args:
|
|
560
|
+
glcm_dict (Dict): dictionary with glcm matrices, generated using :func:`get_glcm_matrices`
|
|
561
|
+
|
|
562
|
+
Returns:
|
|
563
|
+
Union[float, List[float]]: the correlation features
|
|
564
|
+
"""
|
|
565
|
+
temp = []
|
|
566
|
+
corr = []
|
|
567
|
+
for key in glcm_dict.keys():
|
|
568
|
+
for glcm in glcm_dict[key]:
|
|
569
|
+
df_pij, df_pi, _, _, _, _ = glcm.get_cm_data([np.nan, np.nan])
|
|
570
|
+
mu_marg = np.sum(df_pi.i * df_pi.pi)
|
|
571
|
+
var_marg = np.sum((df_pi.i - mu_marg) ** 2.0 * df_pi.pi)
|
|
572
|
+
if var_marg == 0.0:
|
|
573
|
+
temp.append(1.0)
|
|
574
|
+
else:
|
|
575
|
+
temp.append(1.0 / var_marg * (np.sum(df_pij.i * df_pij.j * df_pij.pij) - mu_marg ** 2.0))
|
|
576
|
+
if len(glcm_dict) <= 1:
|
|
577
|
+
return sum(temp) / len(temp)
|
|
578
|
+
else:
|
|
579
|
+
print(f'Merge method: {key}, corr: {sum(temp) / len(temp)}')
|
|
580
|
+
corr.append(sum(temp) / len(temp))
|
|
581
|
+
return corr
|
|
582
|
+
|
|
583
|
+
|
|
584
|
+
def auto_corr(glcm_dict: Dict) -> Union[float, List[float]]:
|
|
585
|
+
"""Computes autocorrelation features.
|
|
586
|
+
This feature refers to "Fcm_auto_corr" (ID = QWB0) in the `IBSI1 reference \
|
|
587
|
+
manual <https://arxiv.org/pdf/1612.07003.pdf>`__.
|
|
588
|
+
|
|
589
|
+
Args:
|
|
590
|
+
glcm_dict (Dict): dictionary with glcm matrices, generated using :func:`get_glcm_matrices`
|
|
591
|
+
|
|
592
|
+
Returns:
|
|
593
|
+
Union[float, List[float]]: the autocorrelation features
|
|
594
|
+
"""
|
|
595
|
+
temp = []
|
|
596
|
+
auto_corr = []
|
|
597
|
+
for key in glcm_dict.keys():
|
|
598
|
+
for glcm in glcm_dict[key]:
|
|
599
|
+
df_pij, _, _, _, _, _ = glcm.get_cm_data([np.nan, np.nan])
|
|
600
|
+
temp.append(np.sum(df_pij.i * df_pij.j * df_pij.pij))
|
|
601
|
+
if len(glcm_dict) <= 1:
|
|
602
|
+
return sum(temp) / len(temp)
|
|
603
|
+
else:
|
|
604
|
+
print(f'Merge method: {key}, auto corr: {sum(temp) / len(temp)}')
|
|
605
|
+
auto_corr.append(sum(temp) / len(temp))
|
|
606
|
+
return auto_corr
|
|
607
|
+
|
|
608
|
+
def info_corr1(glcm_dict: Dict) -> Union[float, List[float]]:
|
|
609
|
+
"""Computes information correlation 1 features.
|
|
610
|
+
This feature refers to "Fcm_info_corr1" (ID = R8DG) in the `IBSI1 reference \
|
|
611
|
+
manual <https://arxiv.org/pdf/1612.07003.pdf>`__.
|
|
612
|
+
|
|
613
|
+
Args:
|
|
614
|
+
glcm_dict (Dict): dictionary with glcm matrices, generated using :func:`get_glcm_matrices`
|
|
615
|
+
|
|
616
|
+
Returns:
|
|
617
|
+
Union[float, List[float]]: the information correlation 1 features
|
|
618
|
+
"""
|
|
619
|
+
temp = []
|
|
620
|
+
info_corr1 = []
|
|
621
|
+
for key in glcm_dict.keys():
|
|
622
|
+
for glcm in glcm_dict[key]:
|
|
623
|
+
df_pij, df_pi, _, _, _, _ = glcm.get_cm_data([np.nan, np.nan])
|
|
624
|
+
hxy = -np.sum(df_pij.pij * np.log2(df_pij.pij))
|
|
625
|
+
hxy_1 = -np.sum(df_pij.pij * np.log2(df_pij.pi * df_pij.pj))
|
|
626
|
+
hx = -np.sum(df_pi.pi * np.log2(df_pi.pi))
|
|
627
|
+
if len(df_pij) == 1 or hx == 0.0:
|
|
628
|
+
temp.append(1.0)
|
|
629
|
+
else:
|
|
630
|
+
temp.append((hxy - hxy_1) / hx)
|
|
631
|
+
if len(glcm_dict) <= 1:
|
|
632
|
+
return sum(temp) / len(temp)
|
|
633
|
+
else:
|
|
634
|
+
print(f'Merge method: {key}, info corr 1: {sum(temp) / len(temp)}')
|
|
635
|
+
info_corr1.append(sum(temp) / len(temp))
|
|
636
|
+
return info_corr1
|
|
637
|
+
|
|
638
|
+
def info_corr2(glcm_dict: Dict) -> Union[float, List[float]]:
|
|
639
|
+
"""Computes information correlation 2 features - Note: iteration over combinations of i and j
|
|
640
|
+
This feature refers to "Fcm_info_corr2" (ID = JN9H) in the `IBSI1 reference \
|
|
641
|
+
manual <https://arxiv.org/pdf/1612.07003.pdf>`__.
|
|
642
|
+
|
|
643
|
+
Args:
|
|
644
|
+
glcm_dict (Dict): dictionary with glcm matrices, generated using :func:`get_glcm_matrices`
|
|
645
|
+
|
|
646
|
+
Returns:
|
|
647
|
+
Union[float, List[float]]: the information correlation 2 features
|
|
648
|
+
"""
|
|
649
|
+
temp = []
|
|
650
|
+
info_corr2 = []
|
|
651
|
+
for key in glcm_dict.keys():
|
|
652
|
+
for glcm in glcm_dict[key]:
|
|
653
|
+
df_pij, df_pi, df_pj, _, _, _ = glcm.get_cm_data([np.nan, np.nan])
|
|
654
|
+
hxy = - np.sum(df_pij.pij * np.log2(df_pij.pij))
|
|
655
|
+
hxy_2 = - np.sum(
|
|
656
|
+
np.tile(df_pi.pi, len(df_pj)) * np.repeat(df_pj.pj, len(df_pi)) * \
|
|
657
|
+
np.log2(np.tile(df_pi.pi, len(df_pj)) * np.repeat(df_pj.pj, len(df_pi)))
|
|
658
|
+
)
|
|
659
|
+
if hxy_2 < hxy:
|
|
660
|
+
temp.append(0)
|
|
661
|
+
else:
|
|
662
|
+
temp.append(np.sqrt(1 - np.exp(-2.0 * (hxy_2 - hxy))))
|
|
663
|
+
if len(glcm_dict) <= 1:
|
|
664
|
+
return sum(temp) / len(temp)
|
|
665
|
+
else:
|
|
666
|
+
print(f'Merge method: {key}, info corr 2: {sum(temp) / len(temp)}')
|
|
667
|
+
info_corr2.append(sum(temp) / len(temp))
|
|
668
|
+
return info_corr2
|
|
669
|
+
|
|
670
|
+
def clust_tend(glcm_dict: Dict) -> Union[float, List[float]]:
|
|
671
|
+
"""Computes cluster tendency features.
|
|
672
|
+
This feature refers to "Fcm_clust_tend" (ID = DG8W) in the `IBSI1 reference \
|
|
673
|
+
manual <https://arxiv.org/pdf/1612.07003.pdf>`__.
|
|
674
|
+
|
|
675
|
+
Args:
|
|
676
|
+
glcm_dict (Dict): dictionary with glcm matrices, generated using :func:`get_glcm_matrices`
|
|
677
|
+
|
|
678
|
+
Returns:
|
|
679
|
+
Union[float, List[float]]: the cluster tendency features
|
|
680
|
+
"""
|
|
681
|
+
temp = []
|
|
682
|
+
clust_tend = []
|
|
683
|
+
for key in glcm_dict.keys():
|
|
684
|
+
for glcm in glcm_dict[key]:
|
|
685
|
+
df_pij, df_pi, _, _, _, _ = glcm.get_cm_data([np.nan, np.nan])
|
|
686
|
+
m_u = np.sum(df_pi.i * df_pi.pi)
|
|
687
|
+
temp.append(np.sum((df_pij.i + df_pij.j - 2 * m_u) ** 2.0 * df_pij.pij))
|
|
688
|
+
if len(glcm_dict) <= 1:
|
|
689
|
+
return sum(temp) / len(temp)
|
|
690
|
+
else:
|
|
691
|
+
print(f'Merge method: {key}, clust tend: {sum(temp) / len(temp)}')
|
|
692
|
+
clust_tend.append(sum(temp) / len(temp))
|
|
693
|
+
return clust_tend
|
|
694
|
+
|
|
695
|
+
def clust_shade(glcm_dict: Dict) -> Union[float, List[float]]:
|
|
696
|
+
"""Computes cluster shade features.
|
|
697
|
+
This feature refers to "Fcm_clust_shade" (ID = 7NFM) in the `IBSI1 reference \
|
|
698
|
+
manual <https://arxiv.org/pdf/1612.07003.pdf>`__.
|
|
699
|
+
|
|
700
|
+
Args:
|
|
701
|
+
glcm_dict (Dict): dictionary with glcm matrices, generated using :func:`get_glcm_matrices`
|
|
702
|
+
|
|
703
|
+
Returns:
|
|
704
|
+
Union[float, List[float]]: the cluster shade features
|
|
705
|
+
"""
|
|
706
|
+
temp = []
|
|
707
|
+
clust_shade = []
|
|
708
|
+
for key in glcm_dict.keys():
|
|
709
|
+
for glcm in glcm_dict[key]:
|
|
710
|
+
df_pij, df_pi, _, _, _, _ = glcm.get_cm_data([np.nan, np.nan])
|
|
711
|
+
m_u = np.sum(df_pi.i * df_pi.pi)
|
|
712
|
+
temp.append(np.sum((df_pij.i + df_pij.j - 2 * m_u) ** 3.0 * df_pij.pij))
|
|
713
|
+
if len(glcm_dict) <= 1:
|
|
714
|
+
return sum(temp) / len(temp)
|
|
715
|
+
else:
|
|
716
|
+
print(f'Merge method: {key}, clust shade: {sum(temp) / len(temp)}')
|
|
717
|
+
clust_shade.append(sum(temp) / len(temp))
|
|
718
|
+
return clust_shade
|
|
719
|
+
|
|
720
|
+
def clust_prom(glcm_dict: Dict) -> Union[float, List[float]]:
|
|
721
|
+
"""Computes cluster prominence features.
|
|
722
|
+
This feature refers to "Fcm_clust_prom" (ID = AE86) in the `IBSI1 reference \
|
|
723
|
+
manual <https://arxiv.org/pdf/1612.07003.pdf>`__.
|
|
724
|
+
|
|
725
|
+
Args:
|
|
726
|
+
glcm_dict (Dict): dictionary with glcm matrices, generated using :func:`get_glcm_matrices`
|
|
727
|
+
|
|
728
|
+
Returns:
|
|
729
|
+
Union[float, List[float]]: the cluster prominence features
|
|
730
|
+
"""
|
|
731
|
+
temp = []
|
|
732
|
+
clust_prom = []
|
|
733
|
+
for key in glcm_dict.keys():
|
|
734
|
+
for glcm in glcm_dict[key]:
|
|
735
|
+
df_pij, df_pi, _, _, _, _ = glcm.get_cm_data([np.nan, np.nan])
|
|
736
|
+
m_u = np.sum(df_pi.i * df_pi.pi)
|
|
737
|
+
temp.append(np.sum((df_pij.i + df_pij.j - 2 * m_u) ** 4.0 * df_pij.pij))
|
|
738
|
+
if len(glcm_dict) <= 1:
|
|
739
|
+
return sum(temp) / len(temp)
|
|
740
|
+
else:
|
|
741
|
+
print(f'Merge method: {key}, clust prom: {sum(temp) / len(temp)}')
|
|
742
|
+
clust_prom.append(sum(temp) / len(temp))
|
|
743
|
+
return clust_prom
|
|
744
|
+
|
|
745
|
+
def extract_all(vol, dist_correction=None, merge_method="vol_merge") -> Dict:
|
|
746
|
+
"""Computes glcm features.
|
|
747
|
+
This features refer to Glcm family in the `IBSI1 reference \
|
|
748
|
+
manual <https://arxiv.org/pdf/1612.07003.pdf>`__.
|
|
749
|
+
|
|
750
|
+
Args:
|
|
751
|
+
vol (ndarray): 3D volume, isotropically resampled, quantized
|
|
752
|
+
(e.g. n_g = 32, levels = [1, ..., n_g]), with NaNs outside the region
|
|
753
|
+
of interest.
|
|
754
|
+
dist_correction (Union[bool, str], optional): Set this variable to true in order to use
|
|
755
|
+
discretization length difference corrections as used by the `Institute of Physics and
|
|
756
|
+
Engineering in Medicine <https://doi.org/10.1088/0031-9155/60/14/5471>`__.
|
|
757
|
+
Set this variable to false to replicate IBSI results.
|
|
758
|
+
Or use string and specify the norm for distance weighting. Weighting is
|
|
759
|
+
only performed if this argument is "manhattan", "euclidean" or "chebyshev".
|
|
760
|
+
merge_method (str, optional): merging ``method`` which determines how features are
|
|
761
|
+
calculated. One of "average", "slice_merge", "dir_merge" and "vol_merge".
|
|
762
|
+
Note that not all combinations of spatial and merge ``method`` are valid.
|
|
763
|
+
method (str, optional): Either 'old' (deprecated) or 'new' (faster) ``method``.
|
|
764
|
+
|
|
765
|
+
Returns:
|
|
766
|
+
Dict: Dict of the glcm features.
|
|
767
|
+
|
|
768
|
+
Raises:
|
|
769
|
+
ValueError: If `method` is not 'old' or 'new'.
|
|
770
|
+
|
|
771
|
+
Todo:
|
|
772
|
+
|
|
773
|
+
- Enable calculation of CM features using different spatial methods (2d, 2.5d, 3d)
|
|
774
|
+
- Enable calculation of CM features using different CM distance settings
|
|
775
|
+
- Enable calculation of CM features for different merge methods ("average", "slice_merge", "dir_merge" and "vol_merge")
|
|
776
|
+
- Provide the range of discretised intensities from a calling function and pass to :func:`get_cm_features`.
|
|
777
|
+
- Test if dist_correction works as expected.
|
|
778
|
+
|
|
779
|
+
"""
|
|
780
|
+
glcm = get_cm_features(
|
|
781
|
+
vol=vol,
|
|
782
|
+
intensity_range=[np.nan, np.nan],
|
|
783
|
+
merge_method=merge_method,
|
|
784
|
+
dist_weight_norm=dist_correction
|
|
785
|
+
)
|
|
786
|
+
|
|
787
|
+
return glcm
|
|
788
|
+
|
|
789
|
+
def get_glcm_matrices(vol,
|
|
790
|
+
glcm_spatial_method="3d",
|
|
791
|
+
glcm_dist=1.0,
|
|
792
|
+
merge_method="vol_merge",
|
|
793
|
+
dist_weight_norm=None) -> Dict:
|
|
794
|
+
"""Extracts co-occurrence matrices from the intensity roi mask prior to features extraction.
|
|
795
|
+
|
|
796
|
+
Note:
|
|
797
|
+
This code was adapted from the in-house radiomics software created at
|
|
798
|
+
OncoRay, Dresden, Germany.
|
|
799
|
+
|
|
800
|
+
Args:
|
|
801
|
+
vol (ndarray): volume with discretised intensities as 3D numpy array (x, y, z).
|
|
802
|
+
intensity_range (ndarray): range of potential discretised intensities,provided as a list:
|
|
803
|
+
[minimal discretised intensity, maximal discretised intensity].
|
|
804
|
+
If one or both values are unknown, replace the respective values with np.nan.
|
|
805
|
+
glcm_spatial_method (str, optional): spatial method which determines the way
|
|
806
|
+
co-occurrence matrices are calculated and how features are determined.
|
|
807
|
+
Must be "2d", "2.5d" or "3d".
|
|
808
|
+
glcm_dist (float, optional): Chebyshev distance for comparison between neighboring voxels.
|
|
809
|
+
merge_method (str, optional): merging method which determines how features are
|
|
810
|
+
calculated. One of "average", "slice_merge", "dir_merge" and "vol_merge".
|
|
811
|
+
Note that not all combinations of spatial and merge method are valid.
|
|
812
|
+
dist_weight_norm (Union[bool, str], optional): norm for distance weighting. Weighting is only
|
|
813
|
+
performed if this argument is either "manhattan","euclidean", "chebyshev" or bool.
|
|
814
|
+
|
|
815
|
+
Returns:
|
|
816
|
+
Dict: Dict of co-occurrence matrices.
|
|
817
|
+
|
|
818
|
+
Raises:
|
|
819
|
+
ValueError: If `glcm_spatial_method` is not "2d", "2.5d" or "3d".
|
|
820
|
+
"""
|
|
821
|
+
if type(glcm_spatial_method) is not list:
|
|
822
|
+
glcm_spatial_method = [glcm_spatial_method]
|
|
823
|
+
|
|
824
|
+
if type(glcm_dist) is not list:
|
|
825
|
+
glcm_dist = [glcm_dist]
|
|
826
|
+
|
|
827
|
+
if type(merge_method) is not list:
|
|
828
|
+
merge_method = [merge_method]
|
|
829
|
+
|
|
830
|
+
if type(dist_weight_norm) is bool:
|
|
831
|
+
if dist_weight_norm:
|
|
832
|
+
dist_weight_norm = "euclidean"
|
|
833
|
+
|
|
834
|
+
# Get the roi in tabular format
|
|
835
|
+
img_dims = vol.shape
|
|
836
|
+
index_id = np.arange(start=0, stop=vol.size)
|
|
837
|
+
coords = np.unravel_index(indices=index_id, shape=img_dims)
|
|
838
|
+
df_img = pd.DataFrame({"index_id": index_id,
|
|
839
|
+
"g": np.ravel(vol),
|
|
840
|
+
"x": coords[0],
|
|
841
|
+
"y": coords[1],
|
|
842
|
+
"z": coords[2],
|
|
843
|
+
"roi_int_mask": np.ravel(np.isfinite(vol))})
|
|
844
|
+
|
|
845
|
+
# Iterate over spatial arrangements
|
|
846
|
+
for ii_spatial in glcm_spatial_method:
|
|
847
|
+
# Iterate over distances
|
|
848
|
+
for ii_dist in glcm_dist:
|
|
849
|
+
# Initiate list of glcm objects
|
|
850
|
+
glcm_list = []
|
|
851
|
+
# Perform 2D analysis
|
|
852
|
+
if ii_spatial.lower() in ["2d", "2.5d"]:
|
|
853
|
+
# Iterate over slices
|
|
854
|
+
for ii_slice in np.arange(0, img_dims[2]):
|
|
855
|
+
# Get neighbour direction and iterate over neighbours
|
|
856
|
+
nbrs = get_neighbour_direction(
|
|
857
|
+
d=1,
|
|
858
|
+
distance="chebyshev",
|
|
859
|
+
centre=False,
|
|
860
|
+
complete=False,
|
|
861
|
+
dim3=False) * int(ii_dist)
|
|
862
|
+
for ii_direction in np.arange(0, np.shape(nbrs)[1]):
|
|
863
|
+
# Add glcm matrices to list
|
|
864
|
+
glcm_list += [CooccurrenceMatrix(distance=int(ii_dist),
|
|
865
|
+
direction=nbrs[:, ii_direction],
|
|
866
|
+
direction_id=ii_direction,
|
|
867
|
+
spatial_method=ii_spatial.lower(),
|
|
868
|
+
img_slice=ii_slice)]
|
|
869
|
+
|
|
870
|
+
# Perform 3D analysis
|
|
871
|
+
elif ii_spatial.lower() == "3d":
|
|
872
|
+
# Get neighbour direction and iterate over neighbours
|
|
873
|
+
nbrs = get_neighbour_direction(d=1,
|
|
874
|
+
distance="chebyshev",
|
|
875
|
+
centre=False,
|
|
876
|
+
complete=False,
|
|
877
|
+
dim3=True) * int(ii_dist)
|
|
878
|
+
|
|
879
|
+
for ii_direction in np.arange(0, np.shape(nbrs)[1]):
|
|
880
|
+
# Add glcm matrices to list
|
|
881
|
+
glcm_list += [CooccurrenceMatrix(distance=int(ii_dist),
|
|
882
|
+
direction=nbrs[:, ii_direction],
|
|
883
|
+
direction_id=ii_direction,
|
|
884
|
+
spatial_method=ii_spatial.lower())]
|
|
885
|
+
|
|
886
|
+
else:
|
|
887
|
+
raise ValueError(
|
|
888
|
+
"GCLM matrices can be determined in \"2d\", \"2.5d\" and \"3d\". \
|
|
889
|
+
The requested method (%s) is not implemented.", ii_spatial)
|
|
890
|
+
|
|
891
|
+
# Calculate glcm matrices
|
|
892
|
+
for glcm in glcm_list:
|
|
893
|
+
glcm.calculate_cm_matrix(
|
|
894
|
+
df_img=df_img, img_dims=img_dims, dist_weight_norm=dist_weight_norm)
|
|
895
|
+
|
|
896
|
+
# Merge matrices according to the given method
|
|
897
|
+
upd_list = {}
|
|
898
|
+
for merge_method in merge_method:
|
|
899
|
+
upd_list[merge_method] = combine_matrices(
|
|
900
|
+
glcm_list=glcm_list, merge_method=merge_method, spatial_method=ii_spatial.lower())
|
|
901
|
+
|
|
902
|
+
# Skip if no matrices are available (due to illegal combinations of merge and spatial methods
|
|
903
|
+
if upd_list is None:
|
|
904
|
+
continue
|
|
905
|
+
return upd_list
|
|
906
|
+
|
|
907
|
+
def get_cm_features(vol,
|
|
908
|
+
intensity_range,
|
|
909
|
+
glcm_spatial_method="3d",
|
|
910
|
+
glcm_dist=1.0,
|
|
911
|
+
merge_method="vol_merge",
|
|
912
|
+
dist_weight_norm=None) -> Dict:
|
|
913
|
+
"""Extracts co-occurrence matrix-based features from the intensity roi mask.
|
|
914
|
+
|
|
915
|
+
Note:
|
|
916
|
+
This code was adapted from the in-house radiomics software created at
|
|
917
|
+
OncoRay, Dresden, Germany.
|
|
918
|
+
|
|
919
|
+
Args:
|
|
920
|
+
vol (ndarray): volume with discretised intensities as 3D numpy array (x, y, z).
|
|
921
|
+
intensity_range (ndarray): range of potential discretised intensities,
|
|
922
|
+
provided as a list: [minimal discretised intensity, maximal discretised
|
|
923
|
+
intensity]. If one or both values are unknown, replace the respective values
|
|
924
|
+
with np.nan.
|
|
925
|
+
glcm_spatial_method (str, optional): spatial method which determines the way
|
|
926
|
+
co-occurrence matrices are calculated and how features are determined.
|
|
927
|
+
MUST BE "2d", "2.5d" or "3d".
|
|
928
|
+
glcm_dist (float, optional): chebyshev distance for comparison between neighbouring
|
|
929
|
+
voxels.
|
|
930
|
+
merge_method (str, optional): merging method which determines how features are
|
|
931
|
+
calculated. One of "average", "slice_merge", "dir_merge" and "vol_merge".
|
|
932
|
+
Note that not all combinations of spatial and merge method are valid.
|
|
933
|
+
dist_weight_norm (Union[bool, str], optional): norm for distance weighting. Weighting is only
|
|
934
|
+
performed if this argument is either "manhattan",
|
|
935
|
+
"euclidean", "chebyshev" or bool.
|
|
936
|
+
|
|
937
|
+
Returns:
|
|
938
|
+
Dict: Dict of the glcm features.
|
|
939
|
+
|
|
940
|
+
Raises:
|
|
941
|
+
ValueError: If `glcm_spatial_method` is not "2d", "2.5d" or "3d".
|
|
942
|
+
"""
|
|
943
|
+
if type(glcm_spatial_method) is not list:
|
|
944
|
+
glcm_spatial_method = [glcm_spatial_method]
|
|
945
|
+
|
|
946
|
+
if type(glcm_dist) is not list:
|
|
947
|
+
glcm_dist = [glcm_dist]
|
|
948
|
+
|
|
949
|
+
if type(merge_method) is not list:
|
|
950
|
+
merge_method = [merge_method]
|
|
951
|
+
|
|
952
|
+
if type(dist_weight_norm) is bool:
|
|
953
|
+
if dist_weight_norm:
|
|
954
|
+
dist_weight_norm = "euclidean"
|
|
955
|
+
|
|
956
|
+
# Get the roi in tabular format
|
|
957
|
+
img_dims = vol.shape
|
|
958
|
+
index_id = np.arange(start=0, stop=vol.size)
|
|
959
|
+
coords = np.unravel_index(indices=index_id, shape=img_dims)
|
|
960
|
+
df_img = pd.DataFrame({"index_id": index_id,
|
|
961
|
+
"g": np.ravel(vol),
|
|
962
|
+
"x": coords[0],
|
|
963
|
+
"y": coords[1],
|
|
964
|
+
"z": coords[2],
|
|
965
|
+
"roi_int_mask": np.ravel(np.isfinite(vol))})
|
|
966
|
+
|
|
967
|
+
# Generate an empty feature list
|
|
968
|
+
feat_list = []
|
|
969
|
+
|
|
970
|
+
# Iterate over spatial arrangements
|
|
971
|
+
for ii_spatial in glcm_spatial_method:
|
|
972
|
+
# Iterate over distances
|
|
973
|
+
for ii_dist in glcm_dist:
|
|
974
|
+
# Initiate list of glcm objects
|
|
975
|
+
glcm_list = []
|
|
976
|
+
# Perform 2D analysis
|
|
977
|
+
if ii_spatial.lower() in ["2d", "2.5d"]:
|
|
978
|
+
# Iterate over slices
|
|
979
|
+
for ii_slice in np.arange(0, img_dims[2]):
|
|
980
|
+
# Get neighbour direction and iterate over neighbours
|
|
981
|
+
nbrs = get_neighbour_direction(
|
|
982
|
+
d=1,
|
|
983
|
+
distance="chebyshev",
|
|
984
|
+
centre=False,
|
|
985
|
+
complete=False,
|
|
986
|
+
dim3=False) * int(ii_dist)
|
|
987
|
+
for ii_direction in np.arange(0, np.shape(nbrs)[1]):
|
|
988
|
+
# Add glcm matrices to list
|
|
989
|
+
glcm_list += [CooccurrenceMatrix(distance=int(ii_dist),
|
|
990
|
+
direction=nbrs[:, ii_direction],
|
|
991
|
+
direction_id=ii_direction,
|
|
992
|
+
spatial_method=ii_spatial.lower(),
|
|
993
|
+
img_slice=ii_slice)]
|
|
994
|
+
|
|
995
|
+
# Perform 3D analysis
|
|
996
|
+
elif ii_spatial.lower() == "3d":
|
|
997
|
+
# Get neighbour direction and iterate over neighbours
|
|
998
|
+
nbrs = get_neighbour_direction(d=1,
|
|
999
|
+
distance="chebyshev",
|
|
1000
|
+
centre=False,
|
|
1001
|
+
complete=False,
|
|
1002
|
+
dim3=True) * int(ii_dist)
|
|
1003
|
+
|
|
1004
|
+
for ii_direction in np.arange(0, np.shape(nbrs)[1]):
|
|
1005
|
+
# Add glcm matrices to list
|
|
1006
|
+
glcm_list += [CooccurrenceMatrix(distance=int(ii_dist),
|
|
1007
|
+
direction=nbrs[:, ii_direction],
|
|
1008
|
+
direction_id=ii_direction,
|
|
1009
|
+
spatial_method=ii_spatial.lower())]
|
|
1010
|
+
|
|
1011
|
+
else:
|
|
1012
|
+
raise ValueError(
|
|
1013
|
+
"GCLM matrices can be determined in \"2d\", \"2.5d\" and \"3d\". \
|
|
1014
|
+
The requested method (%s) is not implemented.", ii_spatial)
|
|
1015
|
+
|
|
1016
|
+
# Calculate glcm matrices
|
|
1017
|
+
for glcm in glcm_list:
|
|
1018
|
+
glcm.calculate_cm_matrix(
|
|
1019
|
+
df_img=df_img, img_dims=img_dims, dist_weight_norm=dist_weight_norm)
|
|
1020
|
+
|
|
1021
|
+
# Merge matrices according to the given method
|
|
1022
|
+
for merge_method in merge_method:
|
|
1023
|
+
upd_list = combine_matrices(
|
|
1024
|
+
glcm_list=glcm_list, merge_method=merge_method, spatial_method=ii_spatial.lower())
|
|
1025
|
+
|
|
1026
|
+
# Skip if no matrices are available (due to illegal combinations of merge and spatial methods
|
|
1027
|
+
if upd_list is None:
|
|
1028
|
+
continue
|
|
1029
|
+
|
|
1030
|
+
# Calculate features
|
|
1031
|
+
feat_run_list = []
|
|
1032
|
+
for glcm in upd_list:
|
|
1033
|
+
feat_run_list += [glcm.calculate_cm_features(
|
|
1034
|
+
intensity_range=intensity_range)]
|
|
1035
|
+
|
|
1036
|
+
# Average feature values
|
|
1037
|
+
feat_list += [pd.concat(feat_run_list, axis=0).mean(axis=0, skipna=True).to_frame().transpose()]
|
|
1038
|
+
|
|
1039
|
+
# Merge feature tables into a single table and return as a dictionary
|
|
1040
|
+
df_feat = pd.concat(feat_list, axis=1).to_dict(orient="records")[0]
|
|
1041
|
+
|
|
1042
|
+
return df_feat
|
|
1043
|
+
|
|
1044
|
+
def combine_matrices(glcm_list: List, merge_method: str, spatial_method: str) -> List:
|
|
1045
|
+
"""Merges co-occurrence matrices prior to feature calculation.
|
|
1046
|
+
|
|
1047
|
+
Note:
|
|
1048
|
+
This code was adapted from the in-house radiomics software created at
|
|
1049
|
+
OncoRay, Dresden, Germany.
|
|
1050
|
+
|
|
1051
|
+
Args:
|
|
1052
|
+
glcm_list (List): List of CooccurrenceMatrix objects.
|
|
1053
|
+
merge_method (str): Merging method which determines how features are calculated.
|
|
1054
|
+
One of "average", "slice_merge", "dir_merge" and "vol_merge". Note that not all
|
|
1055
|
+
combinations of spatial and merge method are valid.
|
|
1056
|
+
spatial_method (str): spatial method which determines the way co-occurrence
|
|
1057
|
+
matrices are calculated and how features are determined. One of "2d", "2.5d"
|
|
1058
|
+
or "3d".
|
|
1059
|
+
|
|
1060
|
+
Returns:
|
|
1061
|
+
List[CooccurrenceMatrix]: list of one or more merged CooccurrenceMatrix objects.
|
|
1062
|
+
"""
|
|
1063
|
+
# Initiate empty list
|
|
1064
|
+
use_list = []
|
|
1065
|
+
|
|
1066
|
+
# For average features over direction, maintain original glcms
|
|
1067
|
+
if merge_method == "average" and spatial_method in ["2d", "3d"]:
|
|
1068
|
+
# Make copy of glcm_list
|
|
1069
|
+
for glcm in glcm_list:
|
|
1070
|
+
use_list += [glcm._copy()]
|
|
1071
|
+
|
|
1072
|
+
# Set merge method to average
|
|
1073
|
+
for glcm in use_list:
|
|
1074
|
+
glcm.merge_method = "average"
|
|
1075
|
+
|
|
1076
|
+
# Merge glcms by slice
|
|
1077
|
+
elif merge_method == "slice_merge" and spatial_method == "2d":
|
|
1078
|
+
# Find slice_ids
|
|
1079
|
+
slice_id = []
|
|
1080
|
+
for glcm in glcm_list:
|
|
1081
|
+
slice_id += [glcm.slice]
|
|
1082
|
+
|
|
1083
|
+
# Iterate over unique slice_ids
|
|
1084
|
+
for ii_slice in np.unique(slice_id):
|
|
1085
|
+
slice_glcm_id = np.squeeze(np.where(slice_id == ii_slice))
|
|
1086
|
+
|
|
1087
|
+
# Select all matrices within the slice
|
|
1088
|
+
sel_matrix_list = []
|
|
1089
|
+
for glcm_id in slice_glcm_id:
|
|
1090
|
+
sel_matrix_list += [glcm_list[glcm_id].matrix]
|
|
1091
|
+
|
|
1092
|
+
# Check if any matrix has been created for the currently selected slice
|
|
1093
|
+
if is_list_all_none(sel_matrix_list):
|
|
1094
|
+
# No matrix was created
|
|
1095
|
+
use_list += [CooccurrenceMatrix(distance=glcm_list[slice_glcm_id[0]].distance,
|
|
1096
|
+
direction=None,
|
|
1097
|
+
direction_id=None,
|
|
1098
|
+
spatial_method=spatial_method,
|
|
1099
|
+
img_slice=ii_slice,
|
|
1100
|
+
merge_method=merge_method,
|
|
1101
|
+
matrix=None,
|
|
1102
|
+
n_v=0.0)]
|
|
1103
|
+
else:
|
|
1104
|
+
# Merge matrices within the slice
|
|
1105
|
+
merge_cm = pd.concat(sel_matrix_list, axis=0)
|
|
1106
|
+
merge_cm = merge_cm.groupby(by=["i", "j"]).sum().reset_index()
|
|
1107
|
+
|
|
1108
|
+
# Update the number of voxels within the merged slice
|
|
1109
|
+
merge_n_v = 0.0
|
|
1110
|
+
for glcm_id in slice_glcm_id:
|
|
1111
|
+
merge_n_v += glcm_list[glcm_id].n_v
|
|
1112
|
+
|
|
1113
|
+
# Create new cooccurrence matrix
|
|
1114
|
+
use_list += [CooccurrenceMatrix(distance=glcm_list[slice_glcm_id[0]].distance,
|
|
1115
|
+
direction=None,
|
|
1116
|
+
direction_id=None,
|
|
1117
|
+
spatial_method=spatial_method,
|
|
1118
|
+
img_slice=ii_slice,
|
|
1119
|
+
merge_method=merge_method,
|
|
1120
|
+
matrix=merge_cm,
|
|
1121
|
+
n_v=merge_n_v)]
|
|
1122
|
+
|
|
1123
|
+
# Merge glcms by direction
|
|
1124
|
+
elif merge_method == "dir_merge" and spatial_method == "2.5d":
|
|
1125
|
+
# Find slice_ids
|
|
1126
|
+
dir_id = []
|
|
1127
|
+
for glcm in glcm_list:
|
|
1128
|
+
dir_id += [glcm.direction_id]
|
|
1129
|
+
|
|
1130
|
+
# Iterate over unique directions
|
|
1131
|
+
for ii_dir in np.unique(dir_id):
|
|
1132
|
+
dir_glcm_id = np.squeeze(np.where(dir_id == ii_dir))
|
|
1133
|
+
|
|
1134
|
+
# Select all matrices with the same direction
|
|
1135
|
+
sel_matrix_list = []
|
|
1136
|
+
for glcm_id in dir_glcm_id:
|
|
1137
|
+
sel_matrix_list += [glcm_list[glcm_id].matrix]
|
|
1138
|
+
|
|
1139
|
+
# Check if any matrix has been created for the currently selected direction
|
|
1140
|
+
if is_list_all_none(sel_matrix_list):
|
|
1141
|
+
# No matrix was created
|
|
1142
|
+
use_list += [CooccurrenceMatrix(distance=glcm_list[dir_glcm_id[0]].distance,
|
|
1143
|
+
direction=glcm_list[dir_glcm_id[0]].direction,
|
|
1144
|
+
direction_id=ii_dir,
|
|
1145
|
+
spatial_method=spatial_method,
|
|
1146
|
+
img_slice=None,
|
|
1147
|
+
merge_method=merge_method,
|
|
1148
|
+
matrix=None, n_v=0.0)]
|
|
1149
|
+
else:
|
|
1150
|
+
# Merge matrices with the same direction
|
|
1151
|
+
merge_cm = pd.concat(sel_matrix_list, axis=0)
|
|
1152
|
+
merge_cm = merge_cm.groupby(by=["i", "j"]).sum().reset_index()
|
|
1153
|
+
|
|
1154
|
+
# Update the number of voxels for the merged matrices with the same direction
|
|
1155
|
+
merge_n_v = 0.0
|
|
1156
|
+
for glcm_id in dir_glcm_id:
|
|
1157
|
+
merge_n_v += glcm_list[glcm_id].n_v
|
|
1158
|
+
|
|
1159
|
+
# Create new co-occurrence matrix
|
|
1160
|
+
use_list += [CooccurrenceMatrix(distance=glcm_list[dir_glcm_id[0]].distance,
|
|
1161
|
+
direction=glcm_list[dir_glcm_id[0]].direction,
|
|
1162
|
+
direction_id=ii_dir,
|
|
1163
|
+
spatial_method=spatial_method,
|
|
1164
|
+
img_slice=None,
|
|
1165
|
+
merge_method=merge_method,
|
|
1166
|
+
matrix=merge_cm,
|
|
1167
|
+
n_v=merge_n_v)]
|
|
1168
|
+
|
|
1169
|
+
# Merge all glcms into a single representation
|
|
1170
|
+
elif merge_method == "vol_merge" and spatial_method in ["2.5d", "3d"]:
|
|
1171
|
+
# Select all matrices within the slice
|
|
1172
|
+
sel_matrix_list = []
|
|
1173
|
+
for glcm_id in np.arange(len(glcm_list)):
|
|
1174
|
+
sel_matrix_list += [glcm_list[glcm_id].matrix]
|
|
1175
|
+
|
|
1176
|
+
# Check if any matrix was created
|
|
1177
|
+
if is_list_all_none(sel_matrix_list):
|
|
1178
|
+
# In case no matrix was created
|
|
1179
|
+
use_list += [CooccurrenceMatrix(distance=glcm_list[0].distance,
|
|
1180
|
+
direction=None,
|
|
1181
|
+
direction_id=None,
|
|
1182
|
+
spatial_method=spatial_method,
|
|
1183
|
+
img_slice=None,
|
|
1184
|
+
merge_method=merge_method,
|
|
1185
|
+
matrix=None,
|
|
1186
|
+
n_v=0.0)]
|
|
1187
|
+
else:
|
|
1188
|
+
# Merge co-occurrence matrices
|
|
1189
|
+
merge_cm = pd.concat(sel_matrix_list, axis=0)
|
|
1190
|
+
merge_cm = merge_cm.groupby(by=["i", "j"]).sum().reset_index()
|
|
1191
|
+
|
|
1192
|
+
# Update the number of voxels
|
|
1193
|
+
merge_n_v = 0.0
|
|
1194
|
+
for glcm_id in np.arange(len(glcm_list)):
|
|
1195
|
+
merge_n_v += glcm_list[glcm_id].n_v
|
|
1196
|
+
|
|
1197
|
+
# Create new co-occurrence matrix
|
|
1198
|
+
use_list += [CooccurrenceMatrix(distance=glcm_list[0].distance,
|
|
1199
|
+
direction=None,
|
|
1200
|
+
direction_id=None,
|
|
1201
|
+
spatial_method=spatial_method,
|
|
1202
|
+
img_slice=None,
|
|
1203
|
+
merge_method=merge_method,
|
|
1204
|
+
matrix=merge_cm,
|
|
1205
|
+
n_v=merge_n_v)]
|
|
1206
|
+
else:
|
|
1207
|
+
use_list = None
|
|
1208
|
+
|
|
1209
|
+
return use_list
|
|
1210
|
+
|
|
1211
|
+
|
|
1212
|
+
class CooccurrenceMatrix:
|
|
1213
|
+
""" Class that contains a single co-occurrence ``matrix``.
|
|
1214
|
+
|
|
1215
|
+
Note:
|
|
1216
|
+
Code was adapted from the in-house radiomics software created at
|
|
1217
|
+
OncoRay, Dresden, Germany.
|
|
1218
|
+
|
|
1219
|
+
Attributes:
|
|
1220
|
+
distance (int): Chebyshev ``distance``.
|
|
1221
|
+
direction (ndarray): Direction along which neighbouring voxels are found.
|
|
1222
|
+
direction_id (int): Direction index to identify unique ``direction`` vectors.
|
|
1223
|
+
spatial_method (str): Spatial method used to calculate the co-occurrence
|
|
1224
|
+
``matrix``: "2d", "2.5d" or "3d".
|
|
1225
|
+
img_slice (ndarray): Corresponding slice index (only if the co-occurrence
|
|
1226
|
+
``matrix`` corresponds to a 2d image slice).
|
|
1227
|
+
merge_method (str): Method for merging the co-occurrence ``matrix`` with other
|
|
1228
|
+
co-occurrence matrices.
|
|
1229
|
+
matrix (pandas.DataFrame): The actual co-occurrence ``matrix`` in sparse format
|
|
1230
|
+
(row, column, count).
|
|
1231
|
+
n_v (int): The number of voxels in the volume.
|
|
1232
|
+
"""
|
|
1233
|
+
|
|
1234
|
+
def __init__(self,
|
|
1235
|
+
distance: int,
|
|
1236
|
+
direction: np.ndarray,
|
|
1237
|
+
direction_id: int,
|
|
1238
|
+
spatial_method: str,
|
|
1239
|
+
img_slice: np.ndarray=None,
|
|
1240
|
+
merge_method: str=None,
|
|
1241
|
+
matrix: pd.DataFrame=None,
|
|
1242
|
+
n_v: int=None) -> None:
|
|
1243
|
+
"""Constructor of the CooccurrenceMatrix class
|
|
1244
|
+
|
|
1245
|
+
Args:
|
|
1246
|
+
distance (int): Chebyshev ``distance``.
|
|
1247
|
+
direction (ndarray): Direction along which neighbouring voxels are found.
|
|
1248
|
+
direction_id (int): Direction index to identify unique ``direction`` vectors.
|
|
1249
|
+
spatial_method (str): Spatial method used to calculate the co-occurrence
|
|
1250
|
+
``matrix``: "2d", "2.5d" or "3d".
|
|
1251
|
+
img_slice (ndarray, optional): Corresponding slice index (only if the
|
|
1252
|
+
co-occurrence ``matrix`` corresponds to a 2d image slice).
|
|
1253
|
+
merge_method (str, optional): Method for merging the co-occurrence ``matrix``
|
|
1254
|
+
with other co-occurrence matrices.
|
|
1255
|
+
matrix (pandas.DataFrame, optional): The actual co-occurrence ``matrix`` in
|
|
1256
|
+
sparse format (row, column, count).
|
|
1257
|
+
n_v (int, optional): The number of voxels in the volume.
|
|
1258
|
+
|
|
1259
|
+
Returns:
|
|
1260
|
+
None.
|
|
1261
|
+
"""
|
|
1262
|
+
# Distance used
|
|
1263
|
+
self.distance = distance
|
|
1264
|
+
|
|
1265
|
+
# Direction and slice for which the current matrix is extracted
|
|
1266
|
+
self.direction = direction
|
|
1267
|
+
self.direction_id = direction_id
|
|
1268
|
+
self.img_slice = img_slice
|
|
1269
|
+
|
|
1270
|
+
# Spatial analysis method (2d, 2.5d, 3d) and merge method (average, slice_merge, dir_merge, vol_merge)
|
|
1271
|
+
self.spatial_method = spatial_method
|
|
1272
|
+
self.merge_method = merge_method
|
|
1273
|
+
|
|
1274
|
+
# Place holders
|
|
1275
|
+
self.matrix = matrix
|
|
1276
|
+
self.n_v = n_v
|
|
1277
|
+
|
|
1278
|
+
def _copy(self):
|
|
1279
|
+
"""
|
|
1280
|
+
Returns a copy of the co-occurrence matrix object.
|
|
1281
|
+
"""
|
|
1282
|
+
return deepcopy(self)
|
|
1283
|
+
|
|
1284
|
+
def calculate_cm_matrix(self, df_img: pd.DataFrame, img_dims: np.ndarray, dist_weight_norm: str) -> None:
|
|
1285
|
+
"""Function that calculates a co-occurrence matrix for the settings provided during
|
|
1286
|
+
initialisation and the input image.
|
|
1287
|
+
|
|
1288
|
+
Args:
|
|
1289
|
+
df_img (pandas.DataFrame): Data table containing image intensities, x, y and z coordinates,
|
|
1290
|
+
and mask labels corresponding to voxels in the volume.
|
|
1291
|
+
img_dims (ndarray, List[float]): Dimensions of the image volume.
|
|
1292
|
+
dist_weight_norm (str): Norm for distance weighting. Weighting is only
|
|
1293
|
+
performed if this parameter is either "manhattan", "euclidean" or "chebyshev".
|
|
1294
|
+
|
|
1295
|
+
Returns:
|
|
1296
|
+
None. Assigns the created image table (cm matrix) to the `matrix` attribute.
|
|
1297
|
+
|
|
1298
|
+
Raises:
|
|
1299
|
+
ValueError:
|
|
1300
|
+
If `self.spatial_method` is not "2d", "2.5d" or "3d".
|
|
1301
|
+
Also, if ``dist_weight_norm`` is not "manhattan", "euclidean" or "chebyshev".
|
|
1302
|
+
|
|
1303
|
+
"""
|
|
1304
|
+
# Check if the roi contains any masked voxels. If this is not the case, don't construct the glcm.
|
|
1305
|
+
if not np.any(df_img.roi_int_mask):
|
|
1306
|
+
self.n_v = 0
|
|
1307
|
+
self.matrix = None
|
|
1308
|
+
|
|
1309
|
+
return None
|
|
1310
|
+
|
|
1311
|
+
# Create local copies of the image table
|
|
1312
|
+
if self.spatial_method == "3d":
|
|
1313
|
+
df_cm = deepcopy(df_img)
|
|
1314
|
+
elif self.spatial_method in ["2d", "2.5d"]:
|
|
1315
|
+
df_cm = deepcopy(df_img[df_img.z == self.img_slice])
|
|
1316
|
+
df_cm["index_id"] = np.arange(0, len(df_cm))
|
|
1317
|
+
df_cm["z"] = 0
|
|
1318
|
+
df_cm = df_cm.reset_index(drop=True)
|
|
1319
|
+
else:
|
|
1320
|
+
raise ValueError(
|
|
1321
|
+
"The spatial method for grey level co-occurrence matrices should be one of \"2d\", \"2.5d\" or \"3d\".")
|
|
1322
|
+
|
|
1323
|
+
# Set grey level of voxels outside ROI to NaN
|
|
1324
|
+
df_cm.loc[df_cm.roi_int_mask == False, "g"] = np.nan
|
|
1325
|
+
|
|
1326
|
+
# Determine potential transitions
|
|
1327
|
+
df_cm["to_index"] = coord2index(x=df_cm.x.values + self.direction[0],
|
|
1328
|
+
y=df_cm.y.values + self.direction[1],
|
|
1329
|
+
z=df_cm.z.values + self.direction[2],
|
|
1330
|
+
dims=img_dims)
|
|
1331
|
+
|
|
1332
|
+
# Get grey levels from transitions
|
|
1333
|
+
df_cm["to_g"] = get_value(x=df_cm.g.values, index=df_cm.to_index.values)
|
|
1334
|
+
|
|
1335
|
+
# Check if any transitions exist.
|
|
1336
|
+
if np.all(np.isnan(df_cm[["to_g"]])):
|
|
1337
|
+
self.n_v = 0
|
|
1338
|
+
self.matrix = None
|
|
1339
|
+
|
|
1340
|
+
return None
|
|
1341
|
+
|
|
1342
|
+
# Count occurrences of grey level transitions
|
|
1343
|
+
df_cm = df_cm.groupby(by=["g", "to_g"]).size().reset_index(name="n")
|
|
1344
|
+
|
|
1345
|
+
# Append grey level transitions in opposite direction
|
|
1346
|
+
df_cm_inv = pd.DataFrame({"g": df_cm.to_g, "to_g": df_cm.g, "n": df_cm.n})
|
|
1347
|
+
df_cm = df_cm.append(df_cm_inv, ignore_index=True)
|
|
1348
|
+
|
|
1349
|
+
# Sum occurrences of grey level transitions
|
|
1350
|
+
df_cm = df_cm.groupby(by=["g", "to_g"]).sum().reset_index()
|
|
1351
|
+
|
|
1352
|
+
# Rename columns
|
|
1353
|
+
df_cm.columns = ["i", "j", "n"]
|
|
1354
|
+
|
|
1355
|
+
if dist_weight_norm in ["manhattan", "euclidean", "chebyshev"]:
|
|
1356
|
+
if dist_weight_norm == "manhattan":
|
|
1357
|
+
weight = sum(abs(self.direction))
|
|
1358
|
+
elif dist_weight_norm == "euclidean":
|
|
1359
|
+
weight = np.sqrt(sum(np.power(self.direction, 2.0)))
|
|
1360
|
+
elif dist_weight_norm == "chebyshev":
|
|
1361
|
+
weight = np.max(abs(self.direction))
|
|
1362
|
+
df_cm.n /= weight
|
|
1363
|
+
|
|
1364
|
+
# Set the number of voxels
|
|
1365
|
+
self.n_v = np.sum(df_cm.n)
|
|
1366
|
+
|
|
1367
|
+
# Add matrix and number of voxels to object
|
|
1368
|
+
self.matrix = df_cm
|
|
1369
|
+
|
|
1370
|
+
def get_cm_data(self, intensity_range: np.ndarray):
|
|
1371
|
+
"""Computes the probability distribution for the elements of the GLCM
|
|
1372
|
+
(diagonal probability, cross-diagonal probability...) and number of gray-levels.
|
|
1373
|
+
|
|
1374
|
+
Args:
|
|
1375
|
+
intensity_range (ndarray): Range of potential discretised intensities, provided as a list:
|
|
1376
|
+
[minimal discretised intensity, maximal discretised intensity].
|
|
1377
|
+
If one or both values are unknown,replace the respective values with np.nan.
|
|
1378
|
+
|
|
1379
|
+
Returns:
|
|
1380
|
+
Typle[pd.DataFrame, pd.DataFrame, pd.DataFrame, float]:
|
|
1381
|
+
- Occurence data frame
|
|
1382
|
+
- Diagonal probabilty
|
|
1383
|
+
- Cross-diagonal probabilty
|
|
1384
|
+
- Number of gray levels
|
|
1385
|
+
"""
|
|
1386
|
+
# Occurrence data frames
|
|
1387
|
+
df_pij = deepcopy(self.matrix)
|
|
1388
|
+
df_pij["pij"] = df_pij.n / sum(df_pij.n)
|
|
1389
|
+
df_pi = df_pij.groupby(by="i")["pij"].agg(np.sum).reset_index().rename(columns={"pij": "pi"})
|
|
1390
|
+
df_pj = df_pij.groupby(by="j")["pij"].agg(np.sum).reset_index().rename(columns={"pij": "pj"})
|
|
1391
|
+
|
|
1392
|
+
# Diagonal probilities p(i-j)
|
|
1393
|
+
df_pimj = deepcopy(df_pij)
|
|
1394
|
+
df_pimj["k"] = np.abs(df_pimj.i - df_pimj.j)
|
|
1395
|
+
df_pimj = df_pimj.groupby(by="k")["pij"].agg(np.sum).reset_index().rename(columns={"pij": "pimj"})
|
|
1396
|
+
|
|
1397
|
+
# Cross-diagonal probabilities p(i+j)
|
|
1398
|
+
df_pipj = deepcopy(df_pij)
|
|
1399
|
+
df_pipj["k"] = df_pipj.i + df_pipj.j
|
|
1400
|
+
df_pipj = df_pipj.groupby(by="k")["pij"].agg(np.sum).reset_index().rename(columns={"pij": "pipj"})
|
|
1401
|
+
|
|
1402
|
+
# Merger of df.p_ij, df.p_i and df.p_j
|
|
1403
|
+
df_pij = pd.merge(df_pij, df_pi, on="i")
|
|
1404
|
+
df_pij = pd.merge(df_pij, df_pj, on="j")
|
|
1405
|
+
|
|
1406
|
+
# Constant definitions
|
|
1407
|
+
intensity_range_loc = deepcopy(intensity_range)
|
|
1408
|
+
if np.isnan(intensity_range[0]):
|
|
1409
|
+
intensity_range_loc[0] = np.min(df_pi.i) * 1.0
|
|
1410
|
+
if np.isnan(intensity_range[1]):
|
|
1411
|
+
intensity_range_loc[1] = np.max(df_pi.i) * 1.0
|
|
1412
|
+
# Number of grey levels
|
|
1413
|
+
n_g = intensity_range_loc[1] - intensity_range_loc[0] + 1.0
|
|
1414
|
+
|
|
1415
|
+
return df_pij, df_pi, df_pj, df_pimj, df_pipj, n_g
|
|
1416
|
+
|
|
1417
|
+
def calculate_cm_features(self, intensity_range: np.ndarray) -> pd.DataFrame:
|
|
1418
|
+
"""Wrapper to json.dump function.
|
|
1419
|
+
|
|
1420
|
+
Args:
|
|
1421
|
+
intensity_range (np.ndarray): Range of potential discretised intensities,
|
|
1422
|
+
provided as a list: [minimal discretised intensity, maximal discretised intensity].
|
|
1423
|
+
If one or both values are unknown,replace the respective values with np.nan.
|
|
1424
|
+
|
|
1425
|
+
Returns:
|
|
1426
|
+
pandas.DataFrame: Data frame with values for each feature.
|
|
1427
|
+
"""
|
|
1428
|
+
# Create feature table
|
|
1429
|
+
feat_names = ["Fcm_joint_max", "Fcm_joint_avg", "Fcm_joint_var", "Fcm_joint_entr",
|
|
1430
|
+
"Fcm_diff_avg", "Fcm_diff_var", "Fcm_diff_entr",
|
|
1431
|
+
"Fcm_sum_avg", "Fcm_sum_var", "Fcm_sum_entr",
|
|
1432
|
+
"Fcm_energy", "Fcm_contrast", "Fcm_dissimilarity",
|
|
1433
|
+
"Fcm_inv_diff", "Fcm_inv_diff_norm", "Fcm_inv_diff_mom",
|
|
1434
|
+
"Fcm_inv_diff_mom_norm", "Fcm_inv_var", "Fcm_corr",
|
|
1435
|
+
"Fcm_auto_corr", "Fcm_clust_tend", "Fcm_clust_shade",
|
|
1436
|
+
"Fcm_clust_prom", "Fcm_info_corr1", "Fcm_info_corr2"]
|
|
1437
|
+
|
|
1438
|
+
df_feat = pd.DataFrame(np.full(shape=(1, len(feat_names)), fill_value=np.nan))
|
|
1439
|
+
df_feat.columns = feat_names
|
|
1440
|
+
|
|
1441
|
+
# Don't return data for empty slices or slices without a good matrix
|
|
1442
|
+
if self.matrix is None:
|
|
1443
|
+
# Update names
|
|
1444
|
+
#df_feat.columns += self._parse_names()
|
|
1445
|
+
return df_feat
|
|
1446
|
+
elif len(self.matrix) == 0:
|
|
1447
|
+
# Update names
|
|
1448
|
+
#df_feat.columns += self._parse_names()
|
|
1449
|
+
return df_feat
|
|
1450
|
+
|
|
1451
|
+
df_pij, df_pi, df_pj, df_pimj, df_pipj, n_g = self.get_cm_data(intensity_range)
|
|
1452
|
+
|
|
1453
|
+
###############################################
|
|
1454
|
+
###### glcm features ######
|
|
1455
|
+
###############################################
|
|
1456
|
+
# Joint maximum
|
|
1457
|
+
df_feat.loc[0, "Fcm_joint_max"] = np.max(df_pij.pij)
|
|
1458
|
+
|
|
1459
|
+
# Joint average
|
|
1460
|
+
df_feat.loc[0, "Fcm_joint_avg"] = np.sum(df_pij.i * df_pij.pij)
|
|
1461
|
+
|
|
1462
|
+
# Joint variance
|
|
1463
|
+
m_u = np.sum(df_pij.i * df_pij.pij)
|
|
1464
|
+
df_feat.loc[0, "Fcm_joint_var"] = np.sum((df_pij.i - m_u) ** 2.0 * df_pij.pij)
|
|
1465
|
+
|
|
1466
|
+
# Joint entropy
|
|
1467
|
+
df_feat.loc[0, "Fcm_joint_entr"] = -np.sum(df_pij.pij * np.log2(df_pij.pij))
|
|
1468
|
+
|
|
1469
|
+
# Difference average
|
|
1470
|
+
df_feat.loc[0, "Fcm_diff_avg"] = np.sum(df_pimj.k * df_pimj.pimj)
|
|
1471
|
+
|
|
1472
|
+
# Difference variance
|
|
1473
|
+
m_u = np.sum(df_pimj.k * df_pimj.pimj)
|
|
1474
|
+
df_feat.loc[0, "Fcm_diff_var"] = np.sum((df_pimj.k - m_u) ** 2.0 * df_pimj.pimj)
|
|
1475
|
+
|
|
1476
|
+
# Difference entropy
|
|
1477
|
+
df_feat.loc[0, "Fcm_diff_entr"] = -np.sum(df_pimj.pimj * np.log2(df_pimj.pimj))
|
|
1478
|
+
|
|
1479
|
+
# Sum average
|
|
1480
|
+
df_feat.loc[0, "Fcm_sum_avg"] = np.sum(df_pipj.k * df_pipj.pipj)
|
|
1481
|
+
|
|
1482
|
+
# Sum variance
|
|
1483
|
+
m_u = np.sum(df_pipj.k * df_pipj.pipj)
|
|
1484
|
+
df_feat.loc[0, "Fcm_sum_var"] = np.sum((df_pipj.k - m_u) ** 2.0 * df_pipj.pipj)
|
|
1485
|
+
|
|
1486
|
+
# Sum entropy
|
|
1487
|
+
df_feat.loc[0, "Fcm_sum_entr"] = -np.sum(df_pipj.pipj * np.log2(df_pipj.pipj))
|
|
1488
|
+
|
|
1489
|
+
# Angular second moment
|
|
1490
|
+
df_feat.loc[0, "Fcm_energy"] = np.sum(df_pij.pij ** 2.0)
|
|
1491
|
+
|
|
1492
|
+
# Contrast
|
|
1493
|
+
df_feat.loc[0, "Fcm_contrast"] = np.sum((df_pij.i - df_pij.j) ** 2.0 * df_pij.pij)
|
|
1494
|
+
|
|
1495
|
+
# Dissimilarity
|
|
1496
|
+
df_feat.loc[0, "Fcm_dissimilarity"] = np.sum(np.abs(df_pij.i - df_pij.j) * df_pij.pij)
|
|
1497
|
+
|
|
1498
|
+
# Inverse difference
|
|
1499
|
+
df_feat.loc[0, "Fcm_inv_diff"] = np.sum(df_pij.pij / (1.0 + np.abs(df_pij.i - df_pij.j)))
|
|
1500
|
+
|
|
1501
|
+
# Inverse difference normalised
|
|
1502
|
+
df_feat.loc[0, "Fcm_inv_diff_norm"] = np.sum(df_pij.pij / (1.0 + np.abs(df_pij.i - df_pij.j) / n_g))
|
|
1503
|
+
|
|
1504
|
+
# Inverse difference moment
|
|
1505
|
+
df_feat.loc[0, "Fcm_inv_diff_mom"] = np.sum(df_pij.pij / (1.0 + (df_pij.i - df_pij.j) ** 2.0))
|
|
1506
|
+
|
|
1507
|
+
# Inverse difference moment normalised
|
|
1508
|
+
df_feat.loc[0, "Fcm_inv_diff_mom_norm"] = np.sum(df_pij.pij / (1.0 + (df_pij.i - df_pij.j)
|
|
1509
|
+
** 2.0 / n_g ** 2.0))
|
|
1510
|
+
|
|
1511
|
+
# Inverse variance
|
|
1512
|
+
df_sel = df_pij[df_pij.i != df_pij.j]
|
|
1513
|
+
df_feat.loc[0, "Fcm_inv_var"] = np.sum(df_sel.pij / (df_sel.i - df_sel.j) ** 2.0)
|
|
1514
|
+
del df_sel
|
|
1515
|
+
|
|
1516
|
+
# Correlation
|
|
1517
|
+
mu_marg = np.sum(df_pi.i * df_pi.pi)
|
|
1518
|
+
var_marg = np.sum((df_pi.i - mu_marg) ** 2.0 * df_pi.pi)
|
|
1519
|
+
|
|
1520
|
+
if var_marg == 0.0:
|
|
1521
|
+
df_feat.loc[0, "Fcm_corr"] = 1.0
|
|
1522
|
+
else:
|
|
1523
|
+
df_feat.loc[0, "Fcm_corr"] = 1.0 / var_marg * (np.sum(df_pij.i * df_pij.j * df_pij.pij) - mu_marg ** 2.0)
|
|
1524
|
+
|
|
1525
|
+
del mu_marg, var_marg
|
|
1526
|
+
|
|
1527
|
+
# Autocorrelation
|
|
1528
|
+
df_feat.loc[0, "Fcm_auto_corr"] = np.sum(df_pij.i * df_pij.j * df_pij.pij)
|
|
1529
|
+
|
|
1530
|
+
# Information correlation 1
|
|
1531
|
+
hxy = -np.sum(df_pij.pij * np.log2(df_pij.pij))
|
|
1532
|
+
hxy_1 = -np.sum(df_pij.pij * np.log2(df_pij.pi * df_pij.pj))
|
|
1533
|
+
hx = -np.sum(df_pi.pi * np.log2(df_pi.pi))
|
|
1534
|
+
if len(df_pij) == 1 or hx == 0.0:
|
|
1535
|
+
df_feat.loc[0, "Fcm_info_corr1"] = 1.0
|
|
1536
|
+
else:
|
|
1537
|
+
df_feat.loc[0, "Fcm_info_corr1"] = (hxy - hxy_1) / hx
|
|
1538
|
+
del hxy, hxy_1, hx
|
|
1539
|
+
|
|
1540
|
+
# Information correlation 2 - Note: iteration over combinations of i and j
|
|
1541
|
+
hxy = - np.sum(df_pij.pij * np.log2(df_pij.pij))
|
|
1542
|
+
hxy_2 = - np.sum(
|
|
1543
|
+
np.tile(df_pi.pi, len(df_pj)) * np.repeat(df_pj.pj, len(df_pi)) * \
|
|
1544
|
+
np.log2(np.tile(df_pi.pi, len(df_pj)) * np.repeat(df_pj.pj, len(df_pi)))
|
|
1545
|
+
)
|
|
1546
|
+
|
|
1547
|
+
if hxy_2 < hxy:
|
|
1548
|
+
df_feat.loc[0, "Fcm_info_corr2"] = 0
|
|
1549
|
+
else:
|
|
1550
|
+
df_feat.loc[0, "Fcm_info_corr2"] = np.sqrt(1 - np.exp(-2.0 * (hxy_2 - hxy)))
|
|
1551
|
+
del hxy, hxy_2
|
|
1552
|
+
|
|
1553
|
+
# Cluster tendency
|
|
1554
|
+
m_u = np.sum(df_pi.i * df_pi.pi)
|
|
1555
|
+
df_feat.loc[0, "Fcm_clust_tend"] = np.sum((df_pij.i + df_pij.j - 2 * m_u) ** 2.0 * df_pij.pij)
|
|
1556
|
+
del m_u
|
|
1557
|
+
|
|
1558
|
+
# Cluster shade
|
|
1559
|
+
m_u = np.sum(df_pi.i * df_pi.pi)
|
|
1560
|
+
df_feat.loc[0, "Fcm_clust_shade"] = np.sum((df_pij.i + df_pij.j - 2 * m_u) ** 3.0 * df_pij.pij)
|
|
1561
|
+
del m_u
|
|
1562
|
+
|
|
1563
|
+
# Cluster prominence
|
|
1564
|
+
m_u = np.sum(df_pi.i * df_pi.pi)
|
|
1565
|
+
df_feat.loc[0, "Fcm_clust_prom"] = np.sum((df_pij.i + df_pij.j - 2 * m_u) ** 4.0 * df_pij.pij)
|
|
1566
|
+
|
|
1567
|
+
del df_pi, df_pj, df_pij, df_pimj, df_pipj, n_g
|
|
1568
|
+
|
|
1569
|
+
# Update names
|
|
1570
|
+
# df_feat.columns += self._parse_names()
|
|
1571
|
+
|
|
1572
|
+
return df_feat
|
|
1573
|
+
|
|
1574
|
+
def _parse_names(self) -> str:
|
|
1575
|
+
""""Adds additional settings-related identifiers to each feature.
|
|
1576
|
+
Not used currently, as the use of different settings for the
|
|
1577
|
+
co-occurrence matrix is not supported.
|
|
1578
|
+
|
|
1579
|
+
Returns:
|
|
1580
|
+
str: String of the features indetifier.
|
|
1581
|
+
"""
|
|
1582
|
+
parse_str = ""
|
|
1583
|
+
|
|
1584
|
+
# Add distance
|
|
1585
|
+
parse_str += "_d" + str(np.round(self.distance, 1))
|
|
1586
|
+
|
|
1587
|
+
# Add spatial method
|
|
1588
|
+
if self.spatial_method is not None:
|
|
1589
|
+
parse_str += "_" + self.spatial_method
|
|
1590
|
+
|
|
1591
|
+
# Add merge method
|
|
1592
|
+
if self.merge_method is not None:
|
|
1593
|
+
if self.merge_method == "average":
|
|
1594
|
+
parse_str += "_avg"
|
|
1595
|
+
if self.merge_method == "slice_merge":
|
|
1596
|
+
parse_str += "_s_mrg"
|
|
1597
|
+
if self.merge_method == "dir_merge":
|
|
1598
|
+
parse_str += "_d_mrg"
|
|
1599
|
+
if self.merge_method == "vol_merge":
|
|
1600
|
+
parse_str += "_v_mrg"
|
|
1601
|
+
|
|
1602
|
+
return parse_str
|