napari-musa 1.0.0__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.
- napari_musa/Widget_DataManager.py +864 -0
- napari_musa/Widgets_DataVisualization.py +257 -0
- napari_musa/Widgets_EndmembersExtraction.py +382 -0
- napari_musa/Widgets_Fusion.py +458 -0
- napari_musa/Widgets_NMF.py +265 -0
- napari_musa/Widgets_PCA.py +212 -0
- napari_musa/Widgets_UMAP.py +463 -0
- napari_musa/_version.py +34 -0
- napari_musa/main.py +150 -0
- napari_musa/modules/D_illuminants.mat +0 -0
- napari_musa/modules/data.py +48 -0
- napari_musa/modules/functions.py +1331 -0
- napari_musa/modules/plot.py +581 -0
- napari_musa/napari.yaml +15 -0
- napari_musa-1.0.0.dist-info/METADATA +156 -0
- napari_musa-1.0.0.dist-info/RECORD +20 -0
- napari_musa-1.0.0.dist-info/WHEEL +5 -0
- napari_musa-1.0.0.dist-info/entry_points.txt +2 -0
- napari_musa-1.0.0.dist-info/licenses/LICENSE +28 -0
- napari_musa-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1331 @@
|
|
|
1
|
+
""" """
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
import time
|
|
6
|
+
from bisect import bisect # RGB
|
|
7
|
+
|
|
8
|
+
import h5py
|
|
9
|
+
import numpy as np
|
|
10
|
+
import plotly.graph_objects as go
|
|
11
|
+
import pysptools.eea as eea
|
|
12
|
+
import pywt # DIMENSIONALITY REDUCTION
|
|
13
|
+
import scipy.io as spio # RGB
|
|
14
|
+
import spectral
|
|
15
|
+
import umap
|
|
16
|
+
from numpy.linalg import svd
|
|
17
|
+
from pybaselines import Baseline
|
|
18
|
+
from scipy import interpolate
|
|
19
|
+
from scipy.interpolate import PchipInterpolator # RGB
|
|
20
|
+
from scipy.io import loadmat
|
|
21
|
+
from scipy.ndimage import gaussian_filter, median_filter
|
|
22
|
+
from scipy.optimize import nnls
|
|
23
|
+
from scipy.signal import find_peaks, peak_widths, savgol_filter
|
|
24
|
+
from scipy.sparse import csc_matrix, issparse
|
|
25
|
+
from sklearn.decomposition import NMF, PCA
|
|
26
|
+
|
|
27
|
+
# import ramanspy as rp
|
|
28
|
+
from sklearn.metrics.pairwise import distance_metrics
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def open_file(filepath, hsi_cube_var=None, wl_var=None):
|
|
32
|
+
""" """
|
|
33
|
+
file_extension = filepath.split(".")[-1].lower()
|
|
34
|
+
if file_extension not in ["mat", "h5"]:
|
|
35
|
+
print(
|
|
36
|
+
f"Error: file format not supported. Expected .mat o .h5, found .{file_extension}."
|
|
37
|
+
)
|
|
38
|
+
return None, None
|
|
39
|
+
|
|
40
|
+
hypercube_names = [
|
|
41
|
+
"data",
|
|
42
|
+
"data_RIFLE",
|
|
43
|
+
"Y",
|
|
44
|
+
"Hyperspectrum_cube",
|
|
45
|
+
"XRFdata",
|
|
46
|
+
"spectra",
|
|
47
|
+
"HyperMatrix",
|
|
48
|
+
"H",
|
|
49
|
+
]
|
|
50
|
+
wls_names = [
|
|
51
|
+
"WL",
|
|
52
|
+
"WL_RIFLE",
|
|
53
|
+
"X",
|
|
54
|
+
"fr_real",
|
|
55
|
+
"spectra",
|
|
56
|
+
"wavelength",
|
|
57
|
+
"ENERGY",
|
|
58
|
+
"t",
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
data = None
|
|
62
|
+
wl = None
|
|
63
|
+
|
|
64
|
+
if hsi_cube_var is not None and wl_var is not None:
|
|
65
|
+
hypercube_names.append(hsi_cube_var)
|
|
66
|
+
wls_names.append(wl_var)
|
|
67
|
+
|
|
68
|
+
# if .mat file
|
|
69
|
+
if file_extension == "mat":
|
|
70
|
+
f = loadmat(filepath)
|
|
71
|
+
print("Dataset presents (MATLAB file):")
|
|
72
|
+
for dataset_name in f:
|
|
73
|
+
print(dataset_name)
|
|
74
|
+
if dataset_name in hypercube_names:
|
|
75
|
+
data = np.array(f[dataset_name])
|
|
76
|
+
if dataset_name == "Hyperspectrum_cube":
|
|
77
|
+
data = data[:, :, ::-1]
|
|
78
|
+
data = np.rot90(data, k=1, axes=(0, 1))
|
|
79
|
+
# data = data[::-1, ::-1, :]
|
|
80
|
+
if dataset_name == "H":
|
|
81
|
+
wl = np.linspace(0, 1, data.shape[2])
|
|
82
|
+
if dataset_name in wls_names:
|
|
83
|
+
wl = np.array(f[dataset_name]).flatten()
|
|
84
|
+
if dataset_name == "fr_real":
|
|
85
|
+
wl = 3 * 10**5 / wl
|
|
86
|
+
wl = wl[::-1]
|
|
87
|
+
if data is not None and wl is not None:
|
|
88
|
+
data = np.rot90(data, k=3)
|
|
89
|
+
print("Data shape:", data.shape, "\nWL shape:", wl.shape)
|
|
90
|
+
return data, wl
|
|
91
|
+
else:
|
|
92
|
+
print("ERROR: the .mat file does not contain correct data names.")
|
|
93
|
+
return 1, 1
|
|
94
|
+
|
|
95
|
+
# If .h5 file
|
|
96
|
+
elif file_extension == "h5":
|
|
97
|
+
with h5py.File(filepath, "r") as f:
|
|
98
|
+
print("Dataset presents (HDF5 file):")
|
|
99
|
+
for dataset_name in f:
|
|
100
|
+
print(dataset_name)
|
|
101
|
+
if dataset_name in hypercube_names:
|
|
102
|
+
data = np.array(f[dataset_name])
|
|
103
|
+
if dataset_name == "Hyperspectrum_cube":
|
|
104
|
+
data = data[:, :, ::-1]
|
|
105
|
+
if dataset_name in wls_names:
|
|
106
|
+
wl = np.array(f[dataset_name]).flatten()
|
|
107
|
+
if dataset_name == "fr_real":
|
|
108
|
+
wl = 3 * 10**5 / wl
|
|
109
|
+
wl = wl[::-1]
|
|
110
|
+
if data is not None and wl is not None:
|
|
111
|
+
data = np.rot90(data, k=3)
|
|
112
|
+
print("Data shape:", data.shape, "\nWL shape:", wl.shape)
|
|
113
|
+
return data, wl
|
|
114
|
+
else:
|
|
115
|
+
print("ERROR: the .h5 file does not contain correct data names.")
|
|
116
|
+
return 1, 1
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
# %% CROP XY
|
|
120
|
+
def crop_xy(data, shape, rgb=None):
|
|
121
|
+
min_y, max_y = int(np.min(shape[:, 2])), int(np.max(shape[:, 2]))
|
|
122
|
+
min_x, max_x = int(np.min(shape[:, 1])), int(np.max(shape[:, 1]))
|
|
123
|
+
|
|
124
|
+
print("Points selected for cropping: ", min_x, max_x, min_y, max_y)
|
|
125
|
+
crop_array = [min_x, max_x, min_y, max_y]
|
|
126
|
+
data = data[
|
|
127
|
+
crop_array[0] : crop_array[1], crop_array[2] : crop_array[3], :
|
|
128
|
+
]
|
|
129
|
+
print(f"Cropped shape: {data.shape}")
|
|
130
|
+
|
|
131
|
+
if rgb is not None:
|
|
132
|
+
rgb = rgb[
|
|
133
|
+
crop_array[0] : crop_array[1], crop_array[2] : crop_array[3], :
|
|
134
|
+
]
|
|
135
|
+
return data, rgb
|
|
136
|
+
else:
|
|
137
|
+
return data
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
# %% CREATE MASK
|
|
141
|
+
def create_mask(data, rgb, mask):
|
|
142
|
+
data = data * mask[: data.shape[0], : data.shape[1], np.newaxis]
|
|
143
|
+
rgb = rgb * mask[: data.shape[0], : data.shape[1], np.newaxis]
|
|
144
|
+
return data, rgb
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
# WE ARE USING IT?
|
|
148
|
+
def plotSpectra(data, label, wl):
|
|
149
|
+
""" """
|
|
150
|
+
dataMasked = np.einsum("ijk,jk->ijk", data, label)
|
|
151
|
+
dataSum = np.sum(
|
|
152
|
+
dataMasked.reshape(
|
|
153
|
+
dataMasked.shape[0], dataMasked.shape[1] * dataMasked.shape[2]
|
|
154
|
+
),
|
|
155
|
+
1,
|
|
156
|
+
)
|
|
157
|
+
print(dataSum)
|
|
158
|
+
return dataSum
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
# %% NORMALIZATION
|
|
162
|
+
def normalize(channel):
|
|
163
|
+
""" """
|
|
164
|
+
return (channel - np.min(channel)) / (np.max(channel) - np.min(channel))
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
# %% CREATE RGB
|
|
168
|
+
def HSI2RGB(wY, HSI_cube, ydim, xdim, d, threshold):
|
|
169
|
+
""" """
|
|
170
|
+
# wY: wavelengths in nm
|
|
171
|
+
# Y : HSI as a (#pixels x #bands) matrix,
|
|
172
|
+
# dims: x & y dimension of image
|
|
173
|
+
# d: 50, 55, 65, 75, determines the illuminant used, if in doubt use d65
|
|
174
|
+
# thresholdRGB : True if thesholding should be done to increase contrast
|
|
175
|
+
#
|
|
176
|
+
#
|
|
177
|
+
# If you use this method, please cite the following paper:
|
|
178
|
+
# M. Magnusson, J. Sigurdsson, S. E. Armansson, M. O. Ulfarsson,
|
|
179
|
+
# H. Deborah and J. R. Sveinsson,
|
|
180
|
+
# "Creating RGB Images from Hyperspectral Images Using a Color Matching Function",
|
|
181
|
+
# IEEE International Geoscience and Remote Sensing Symposium, Virtual Symposium, 2020
|
|
182
|
+
#
|
|
183
|
+
# @INPROCEEDINGS{hsi2rgb,
|
|
184
|
+
# author={M. {Magnusson} and J. {Sigurdsson} and S. E. {Armansson}
|
|
185
|
+
# and M. O. {Ulfarsson} and H. {Deborah} and J. R. {Sveinsson}},
|
|
186
|
+
# booktitle={IEEE International Geoscience and Remote Sensing Symposium},
|
|
187
|
+
# title={Creating {RGB} Images from Hyperspectral Images using a Color Matching Function},
|
|
188
|
+
# year={2020}, volume={}, number={}, pages={}}
|
|
189
|
+
#
|
|
190
|
+
# Paper is available at
|
|
191
|
+
# https://www.researchgate.net/profile/Jakob_Sigurdsson
|
|
192
|
+
|
|
193
|
+
HSI = np.reshape(HSI_cube, [-1, HSI_cube.shape[2]]) / HSI_cube.max()
|
|
194
|
+
|
|
195
|
+
# Load reference illuminant
|
|
196
|
+
file_path = os.path.join(os.path.dirname(__file__), "D_illuminants.mat")
|
|
197
|
+
D = spio.loadmat(file_path)
|
|
198
|
+
# D = spio.loadmat(
|
|
199
|
+
# r"C:\Users\User\OneDrive - Politecnico di Milano\PhD\Programmi\Pyhton\ANALISI\D_illuminants.mat"
|
|
200
|
+
# )
|
|
201
|
+
w = D["wxyz"][:, 0]
|
|
202
|
+
x = D["wxyz"][:, 1]
|
|
203
|
+
y = D["wxyz"][:, 2]
|
|
204
|
+
z = D["wxyz"][:, 3]
|
|
205
|
+
D = D["D"]
|
|
206
|
+
|
|
207
|
+
i = {50: 2, 55: 3, 65: 1, 75: 4}
|
|
208
|
+
wI = D[:, 0]
|
|
209
|
+
I_matrix = D[:, i[d]]
|
|
210
|
+
|
|
211
|
+
# Interpolate to image wavelengths
|
|
212
|
+
I_matrix = PchipInterpolator(wI, I_matrix, extrapolate=True)(
|
|
213
|
+
wY
|
|
214
|
+
) # interp1(wI,I,wY,'pchip','extrap')';
|
|
215
|
+
x = PchipInterpolator(w, x, extrapolate=True)(
|
|
216
|
+
wY
|
|
217
|
+
) # interp1(w,x,wY,'pchip','extrap')';
|
|
218
|
+
y = PchipInterpolator(w, y, extrapolate=True)(
|
|
219
|
+
wY
|
|
220
|
+
) # interp1(w,y,wY,'pchip','extrap')';
|
|
221
|
+
z = PchipInterpolator(w, z, extrapolate=True)(
|
|
222
|
+
wY
|
|
223
|
+
) # interp1(w,z,wY,'pchip','extrap')';
|
|
224
|
+
|
|
225
|
+
# Truncate at 780nm
|
|
226
|
+
i = bisect(wY, 780)
|
|
227
|
+
HSI = HSI[:, 0:i] / HSI.max()
|
|
228
|
+
wY = wY[:i]
|
|
229
|
+
I_matrix = I_matrix[:i]
|
|
230
|
+
x = x[:i]
|
|
231
|
+
y = y[:i]
|
|
232
|
+
z = z[:i]
|
|
233
|
+
|
|
234
|
+
# Compute k
|
|
235
|
+
k = 1 / np.trapz(y * I_matrix, wY)
|
|
236
|
+
|
|
237
|
+
# Compute X,Y & Z for image
|
|
238
|
+
X = k * np.trapz(HSI @ np.diag(I_matrix * x), wY, axis=1)
|
|
239
|
+
Z = k * np.trapz(HSI @ np.diag(I_matrix * z), wY, axis=1)
|
|
240
|
+
Y = k * np.trapz(HSI @ np.diag(I_matrix * y), wY, axis=1)
|
|
241
|
+
|
|
242
|
+
XYZ = np.array([X, Y, Z])
|
|
243
|
+
|
|
244
|
+
# Convert to RGB
|
|
245
|
+
M = np.array(
|
|
246
|
+
[
|
|
247
|
+
[3.2404542, -1.5371385, -0.4985314],
|
|
248
|
+
[-0.9692660, 1.8760108, 0.0415560],
|
|
249
|
+
[0.0556434, -0.2040259, 1.0572252],
|
|
250
|
+
]
|
|
251
|
+
)
|
|
252
|
+
sRGB = M @ XYZ
|
|
253
|
+
|
|
254
|
+
# Gamma correction
|
|
255
|
+
gamma_map = sRGB > 0.0031308
|
|
256
|
+
sRGB[gamma_map] = 1.055 * np.power(sRGB[gamma_map], (1.0 / 2.4)) - 0.055
|
|
257
|
+
sRGB[np.invert(gamma_map)] = 12.92 * sRGB[np.invert(gamma_map)]
|
|
258
|
+
# Note: RL, GL or BL values less than 0 or greater than 1 are clipped to 0 and 1.
|
|
259
|
+
sRGB[sRGB > 1] = 1
|
|
260
|
+
sRGB[sRGB < 0] = 0
|
|
261
|
+
|
|
262
|
+
if threshold:
|
|
263
|
+
for idx in range(3):
|
|
264
|
+
y = sRGB[idx, :]
|
|
265
|
+
a, b = np.histogram(y, 100)
|
|
266
|
+
b = b[:-1] + np.diff(b) / 2
|
|
267
|
+
a = np.cumsum(a) / np.sum(a)
|
|
268
|
+
th = b[0]
|
|
269
|
+
i = a < threshold
|
|
270
|
+
if i.any():
|
|
271
|
+
th = b[i][-1]
|
|
272
|
+
y = y - th
|
|
273
|
+
y[y < 0] = 0
|
|
274
|
+
|
|
275
|
+
a, b = np.histogram(y, 100)
|
|
276
|
+
b = b[:-1] + np.diff(b) / 2
|
|
277
|
+
a = np.cumsum(a) / np.sum(a)
|
|
278
|
+
i = a > 1 - threshold
|
|
279
|
+
th = b[i][0]
|
|
280
|
+
y[y > th] = th
|
|
281
|
+
y = y / th
|
|
282
|
+
sRGB[idx, :] = y
|
|
283
|
+
|
|
284
|
+
R = np.reshape(sRGB[0, :], [ydim, xdim])
|
|
285
|
+
G = np.reshape(sRGB[1, :], [ydim, xdim])
|
|
286
|
+
B = np.reshape(sRGB[2, :], [ydim, xdim])
|
|
287
|
+
return np.transpose(np.array([R, G, B]), [1, 2, 0])
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
# %% RGB TO HEX: create a matrix with hex strings of rgb in that pixel
|
|
291
|
+
def RGB_to_hex(RGB_image, brightness_factor=1.1):
|
|
292
|
+
""" """
|
|
293
|
+
RGB_image = np.clip(RGB_image * brightness_factor, 0, 1)
|
|
294
|
+
image_scaled = (RGB_image * 255).astype(int)
|
|
295
|
+
hex_matrix = np.apply_along_axis(
|
|
296
|
+
lambda rgb: "#{:02x}{:02x}{:02x}".format(*rgb),
|
|
297
|
+
axis=2,
|
|
298
|
+
arr=image_scaled,
|
|
299
|
+
)
|
|
300
|
+
return hex_matrix
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
# %% FALSE RGB:
|
|
304
|
+
def falseRGB(data, wl, R, G, B):
|
|
305
|
+
""" """
|
|
306
|
+
R = np.array(R)
|
|
307
|
+
G = np.array(G)
|
|
308
|
+
B = np.array(B)
|
|
309
|
+
R_image = np.mean(
|
|
310
|
+
data[
|
|
311
|
+
:,
|
|
312
|
+
:,
|
|
313
|
+
(np.abs(wl - R[0])).argmin() : (np.abs(wl - R[1])).argmin() + 1,
|
|
314
|
+
],
|
|
315
|
+
axis=2,
|
|
316
|
+
)
|
|
317
|
+
G_image = np.mean(
|
|
318
|
+
data[
|
|
319
|
+
:,
|
|
320
|
+
:,
|
|
321
|
+
(np.abs(wl - G[0])).argmin() : (np.abs(wl - G[1])).argmin() + 1,
|
|
322
|
+
],
|
|
323
|
+
axis=2,
|
|
324
|
+
)
|
|
325
|
+
B_image = np.mean(
|
|
326
|
+
data[
|
|
327
|
+
:,
|
|
328
|
+
:,
|
|
329
|
+
(np.abs(wl - B[0])).argmin() : (np.abs(wl - B[1])).argmin() + 1,
|
|
330
|
+
],
|
|
331
|
+
axis=2,
|
|
332
|
+
)
|
|
333
|
+
R_image = normalize(R_image)
|
|
334
|
+
G_image = normalize(G_image)
|
|
335
|
+
B_image = normalize(B_image)
|
|
336
|
+
rgb_image = np.stack([R_image, G_image, B_image], axis=-1)
|
|
337
|
+
rgb_uint8 = (rgb_image * 255).astype(np.uint8)
|
|
338
|
+
return rgb_uint8
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
# %% SVD DENOISING
|
|
342
|
+
def SVD_denoise(dataset, components, matrices=None):
|
|
343
|
+
if matrices is None:
|
|
344
|
+
data_reshaped = dataset.reshape(-1, dataset.shape[2])
|
|
345
|
+
U, S, VT = svd(data_reshaped, full_matrices=False)
|
|
346
|
+
else:
|
|
347
|
+
U, S, VT = matrices
|
|
348
|
+
|
|
349
|
+
data_approx = np.dot(
|
|
350
|
+
U[:, :components], np.dot(np.diag(S[:components]), VT[:components, :])
|
|
351
|
+
)
|
|
352
|
+
data_denoised = data_approx.reshape(dataset.shape)
|
|
353
|
+
return data_denoised, [U, S, VT]
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
# %% PREPROCESSING
|
|
357
|
+
def preprocessing(
|
|
358
|
+
data,
|
|
359
|
+
medfilt_w,
|
|
360
|
+
gaussian_s,
|
|
361
|
+
savgol_w,
|
|
362
|
+
savgol_pol,
|
|
363
|
+
bkg_w,
|
|
364
|
+
medfilt_checkbox=True,
|
|
365
|
+
gaussian_checkbox=True,
|
|
366
|
+
savgol_checkbox=True,
|
|
367
|
+
bkg_checkbox=True,
|
|
368
|
+
):
|
|
369
|
+
""" """
|
|
370
|
+
data_processed = data
|
|
371
|
+
print("Data is now data_processed")
|
|
372
|
+
|
|
373
|
+
if savgol_checkbox:
|
|
374
|
+
print(
|
|
375
|
+
"Doing Savitzki-Golay filter: Window=",
|
|
376
|
+
str(savgol_w),
|
|
377
|
+
" Polynomial: ",
|
|
378
|
+
str(savgol_pol),
|
|
379
|
+
)
|
|
380
|
+
data_processed = savgol_filter(
|
|
381
|
+
data_processed, savgol_w, savgol_pol, axis=2
|
|
382
|
+
)
|
|
383
|
+
|
|
384
|
+
if bkg_checkbox:
|
|
385
|
+
baseline_fitter = Baseline(x_data=np.arange(data_processed.shape[2]))
|
|
386
|
+
data_reshaped = data_processed.reshape(-1, data_processed.shape[2])
|
|
387
|
+
for i in range(data_processed.shape[0] * data_processed.shape[1]):
|
|
388
|
+
# baseline = baseline_fitter.asls(data_reshaped[i,:], lam=1e8, p=0.2)[0]
|
|
389
|
+
# baseline = baseline_fitter.beads(data_reshaped[i,:], lam_0=0.06, lam_1=0.8, lam_2=0.5, tol=1e-6,
|
|
390
|
+
# freq_cutoff=0.04, asymmetry=3)[0]
|
|
391
|
+
|
|
392
|
+
baseline = baseline_fitter.snip(
|
|
393
|
+
data_reshaped[i, :],
|
|
394
|
+
max_half_window=bkg_w,
|
|
395
|
+
decreasing=True,
|
|
396
|
+
smooth_half_window=2,
|
|
397
|
+
)[0]
|
|
398
|
+
|
|
399
|
+
data_reshaped[i, :] = np.clip(
|
|
400
|
+
data_reshaped[i, :] - baseline, 0, max(data_reshaped[i, :])
|
|
401
|
+
)
|
|
402
|
+
data_processed = data_reshaped.reshape(data_processed.shape)
|
|
403
|
+
|
|
404
|
+
if medfilt_checkbox:
|
|
405
|
+
print("Doing medfilt with window: " + str(medfilt_w))
|
|
406
|
+
for i in range(data_processed.shape[2]):
|
|
407
|
+
data_processed[:, :, i] = abs(
|
|
408
|
+
# medfilt2d(data_processed[:, :, i], medfilt_w)
|
|
409
|
+
median_filter(data_processed[:, :, i], size=medfilt_w)
|
|
410
|
+
# gaussian_filter(data_processed[:, :, i], sigma=medfilt_w)
|
|
411
|
+
)
|
|
412
|
+
|
|
413
|
+
if gaussian_checkbox:
|
|
414
|
+
print("Doing gaussian filter with sigma: " + str(gaussian_s))
|
|
415
|
+
for i in range(data_processed.shape[2]):
|
|
416
|
+
data_processed[:, :, i] = gaussian_filter(
|
|
417
|
+
data_processed[:, :, i], sigma=gaussian_s
|
|
418
|
+
)
|
|
419
|
+
|
|
420
|
+
return data_processed
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
# %% DESPIKE DATA
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
def spike_removal(
|
|
427
|
+
y,
|
|
428
|
+
width_threshold,
|
|
429
|
+
prominence_threshold=None,
|
|
430
|
+
moving_average_window=10,
|
|
431
|
+
width_param_rel=0.8,
|
|
432
|
+
interp_type="linear",
|
|
433
|
+
):
|
|
434
|
+
"""
|
|
435
|
+
Detects and replaces spikes in the input spectrum with interpolated values. Algorithm first
|
|
436
|
+
published by N. Coca-Lopez in Analytica Chimica Acta. https://doi.org/10.1016/j.aca.2024.342312
|
|
437
|
+
|
|
438
|
+
Parameters:
|
|
439
|
+
y (numpy.ndarray): Input spectrum intensity.
|
|
440
|
+
width_threshold (float): Threshold for peak width.
|
|
441
|
+
prominence_threshold (float): Threshold for peak prominence.
|
|
442
|
+
moving_average_window (int): Number of points in moving average window.
|
|
443
|
+
width_param_rel (float): Relative height parameter for peak width.
|
|
444
|
+
tipo: type of interpolation (linear, quadratic, cubic)
|
|
445
|
+
|
|
446
|
+
Returns:
|
|
447
|
+
numpy.ndarray: Signal with spikes replaced by interpolated values.
|
|
448
|
+
"""
|
|
449
|
+
|
|
450
|
+
# First, we find all peaks showing a prominence above prominence_threshold on the spectra
|
|
451
|
+
peaks, _ = find_peaks(y, prominence=prominence_threshold)
|
|
452
|
+
|
|
453
|
+
# Create a vector where spikes will be flag: no spike = 0, spike = 1.
|
|
454
|
+
spikes = np.zeros(len(y))
|
|
455
|
+
|
|
456
|
+
# Calculation of the widths of the found peaks
|
|
457
|
+
widths = peak_widths(y, peaks)[0]
|
|
458
|
+
|
|
459
|
+
# Calculation of the range where the spectral points are asumed to be corrupted
|
|
460
|
+
widths_ext_a = peak_widths(y, peaks, rel_height=width_param_rel)[2]
|
|
461
|
+
widths_ext_b = peak_widths(y, peaks, rel_height=width_param_rel)[3]
|
|
462
|
+
|
|
463
|
+
# Flagging the area previously defined if the peak is considered a spike (width below width_threshold)
|
|
464
|
+
for _a, width, ext_a, ext_b in zip(
|
|
465
|
+
range(len(widths)), widths, widths_ext_a, widths_ext_b, strict=False
|
|
466
|
+
):
|
|
467
|
+
if width < width_threshold:
|
|
468
|
+
spikes[int(ext_a) - 1 : int(ext_b) + 2] = 1
|
|
469
|
+
|
|
470
|
+
y_out = y.copy()
|
|
471
|
+
|
|
472
|
+
# Interpolation of corrupted points
|
|
473
|
+
for i, spike in enumerate(spikes):
|
|
474
|
+
if spike != 0: # If we have a spike in position i
|
|
475
|
+
window = np.arange(
|
|
476
|
+
i - moving_average_window, i + moving_average_window + 1
|
|
477
|
+
)
|
|
478
|
+
window = window[
|
|
479
|
+
(window >= 0) & (window < len(y))
|
|
480
|
+
] # evita out-of-bounds
|
|
481
|
+
|
|
482
|
+
window_exclude_spikes = window[spikes[window] == 0]
|
|
483
|
+
if len(window_exclude_spikes) >= 2 and np.min(
|
|
484
|
+
window_exclude_spikes
|
|
485
|
+
) <= i <= np.max(window_exclude_spikes):
|
|
486
|
+
interpolator = interpolate.interp1d(
|
|
487
|
+
window_exclude_spikes,
|
|
488
|
+
y[window_exclude_spikes],
|
|
489
|
+
kind=interp_type,
|
|
490
|
+
bounds_error=False,
|
|
491
|
+
fill_value="extrapolate",
|
|
492
|
+
)
|
|
493
|
+
y_out[i] = interpolator(i)
|
|
494
|
+
elif len(window_exclude_spikes) > 0:
|
|
495
|
+
y_out[i] = np.mean(y[window_exclude_spikes])
|
|
496
|
+
|
|
497
|
+
return y_out
|
|
498
|
+
|
|
499
|
+
|
|
500
|
+
def despike(cube, window_size=3, threshold=0.5):
|
|
501
|
+
"""
|
|
502
|
+
Per ogni banda, calcola l'autocorrelazione locale e sostituisce i pixel con valori bassi (< threshold)
|
|
503
|
+
con la media dei vicini, *escludendo* i pixel marcati.
|
|
504
|
+
"""
|
|
505
|
+
h, w, b = cube.shape
|
|
506
|
+
corrected = cube.copy()
|
|
507
|
+
corrected_reshaped = corrected.reshape(-1, b)
|
|
508
|
+
|
|
509
|
+
for k in range(h * w):
|
|
510
|
+
spectrum = corrected_reshaped[k, :]
|
|
511
|
+
intensity_despiked = spike_removal(
|
|
512
|
+
spectrum,
|
|
513
|
+
width_threshold=3,
|
|
514
|
+
prominence_threshold=20,
|
|
515
|
+
moving_average_window=10,
|
|
516
|
+
width_param_rel=0.8,
|
|
517
|
+
interp_type="linear",
|
|
518
|
+
)
|
|
519
|
+
|
|
520
|
+
corrected_reshaped[k, :] = intensity_despiked
|
|
521
|
+
corrected = corrected_reshaped.reshape(h, w, b)
|
|
522
|
+
|
|
523
|
+
return corrected
|
|
524
|
+
|
|
525
|
+
|
|
526
|
+
# %% DIMENSIONALITY REDUCTION
|
|
527
|
+
# SPATIAL DIMENSION WITH DWT
|
|
528
|
+
def reduce_spatial_dimension_dwt(hsi_cube, wavelet="haar", level=1):
|
|
529
|
+
""" """
|
|
530
|
+
H, W, B = hsi_cube.shape
|
|
531
|
+
reduced_cube = []
|
|
532
|
+
LH_cube = []
|
|
533
|
+
HL_cube = []
|
|
534
|
+
HH_cube = []
|
|
535
|
+
|
|
536
|
+
for b in range(B): # Iterations on spectral bands
|
|
537
|
+
# 2D DWT ats each band
|
|
538
|
+
coeffs2 = pywt.wavedec2(
|
|
539
|
+
hsi_cube[:, :, b], wavelet=wavelet, level=level
|
|
540
|
+
)
|
|
541
|
+
LL, (LH, HL, HH) = coeffs2
|
|
542
|
+
reduced_cube.append(LL)
|
|
543
|
+
LH_cube.append(LH)
|
|
544
|
+
HL_cube.append(HL)
|
|
545
|
+
HH_cube.append(HH)
|
|
546
|
+
|
|
547
|
+
# The list converted in a cube
|
|
548
|
+
reduced_cube = np.stack(reduced_cube, axis=-1)
|
|
549
|
+
LH_cube = np.stack(LH_cube, axis=-1)
|
|
550
|
+
HL_cube = np.stack(HL_cube, axis=-1)
|
|
551
|
+
HH_cube = np.stack(HH_cube, axis=-1)
|
|
552
|
+
|
|
553
|
+
scaling_factor = np.mean(hsi_cube) / np.mean(reduced_cube)
|
|
554
|
+
reduced_cube = reduced_cube * scaling_factor
|
|
555
|
+
|
|
556
|
+
return reduced_cube, (LH_cube, HL_cube, HH_cube, scaling_factor)
|
|
557
|
+
|
|
558
|
+
|
|
559
|
+
def reduce_spatial_dimension_dwt_inverse(
|
|
560
|
+
hsi_cube, params, wavelet="haar", level=1
|
|
561
|
+
):
|
|
562
|
+
""" """
|
|
563
|
+
H, W, B = hsi_cube.shape
|
|
564
|
+
reconstructed_cube = []
|
|
565
|
+
LH, HL, HH, scaling_factor = params
|
|
566
|
+
hsi_cube = hsi_cube / scaling_factor
|
|
567
|
+
for b in range(B): # Iterations on spectral bands
|
|
568
|
+
# 2D DWT ats each band
|
|
569
|
+
coeffs = (hsi_cube[:, :, b], (LH[:, :, b], HL[:, :, b], HH[:, :, b]))
|
|
570
|
+
rec = pywt.waverec2(coeffs, wavelet=wavelet)
|
|
571
|
+
reconstructed_cube.append(rec)
|
|
572
|
+
|
|
573
|
+
# The list converted in a cube
|
|
574
|
+
reconstructed_cube = np.stack(reconstructed_cube, axis=-1)
|
|
575
|
+
|
|
576
|
+
return reconstructed_cube
|
|
577
|
+
|
|
578
|
+
|
|
579
|
+
# SPECTRAL DIMENSION WITH DWT
|
|
580
|
+
def reduce_bands_with_dwt(hsi_data, wavelet="db1", level=2):
|
|
581
|
+
""" """
|
|
582
|
+
h, w, b = hsi_data.shape
|
|
583
|
+
approx_bands = []
|
|
584
|
+
|
|
585
|
+
# Iteration on spatial pixels
|
|
586
|
+
for i in range(h):
|
|
587
|
+
for j in range(w):
|
|
588
|
+
# DWT along spectral bands
|
|
589
|
+
coeffs = pywt.wavedec(
|
|
590
|
+
hsi_data[i, j, :], wavelet=wavelet, level=level
|
|
591
|
+
)
|
|
592
|
+
approx = coeffs[0]
|
|
593
|
+
approx_bands.append(approx)
|
|
594
|
+
|
|
595
|
+
# The list converted in a cube
|
|
596
|
+
approx_bands = np.array(approx_bands)
|
|
597
|
+
b_reduced = approx_bands.shape[1]
|
|
598
|
+
reduced_hsi = approx_bands.reshape(h, w, b_reduced) / level
|
|
599
|
+
|
|
600
|
+
scaling_factor = np.mean(hsi_data) / np.mean(reduced_hsi)
|
|
601
|
+
reduced_hsi = reduced_hsi * scaling_factor
|
|
602
|
+
|
|
603
|
+
return reduced_hsi
|
|
604
|
+
|
|
605
|
+
|
|
606
|
+
# TOTAL DIMENSIONALITY REDUCTION
|
|
607
|
+
def dimensionality_reduction(
|
|
608
|
+
data, spectral_dimred_checkbox, spatial_dimred_checkbox, wl
|
|
609
|
+
):
|
|
610
|
+
""" """
|
|
611
|
+
reduced_data = data
|
|
612
|
+
if spatial_dimred_checkbox:
|
|
613
|
+
reduced_data, (LH, HL, HH, scaling) = reduce_spatial_dimension_dwt(
|
|
614
|
+
reduced_data
|
|
615
|
+
)
|
|
616
|
+
reduced_data = reduced_data / 2
|
|
617
|
+
# dataset_reshaped = (
|
|
618
|
+
# np.reshape(reduced_data, [-1, reduced_data.shape[2]])
|
|
619
|
+
# / reduced_data.max()
|
|
620
|
+
# )
|
|
621
|
+
|
|
622
|
+
reduced_rgb = HSI2RGB(
|
|
623
|
+
wl,
|
|
624
|
+
reduced_data,
|
|
625
|
+
reduced_data.shape[0],
|
|
626
|
+
reduced_data.shape[1],
|
|
627
|
+
65,
|
|
628
|
+
False,
|
|
629
|
+
)
|
|
630
|
+
if spectral_dimred_checkbox:
|
|
631
|
+
reduced_data = reduce_bands_with_dwt(reduced_data)
|
|
632
|
+
if (
|
|
633
|
+
not spatial_dimred_checkbox
|
|
634
|
+
): # if just spectral reduction, RGB is the RGB of the whole HS cube
|
|
635
|
+
reduced_rgb = HSI2RGB(
|
|
636
|
+
wl,
|
|
637
|
+
data,
|
|
638
|
+
data.shape[0],
|
|
639
|
+
data.shape[1],
|
|
640
|
+
65,
|
|
641
|
+
False,
|
|
642
|
+
)
|
|
643
|
+
|
|
644
|
+
print("Original dimensions of the hypercube:", data.shape)
|
|
645
|
+
print("Reduced dimensions of the reduced hypercube:", reduced_data.shape)
|
|
646
|
+
reduced_wl = np.arange(reduced_data.shape[2])
|
|
647
|
+
return reduced_data, reduced_wl, reduced_rgb
|
|
648
|
+
|
|
649
|
+
|
|
650
|
+
# %% DERIVATIVE
|
|
651
|
+
def derivative(data, savgol_w=9, savgol_pol=3, deriv=1):
|
|
652
|
+
""" """
|
|
653
|
+
data_firstDev = np.zeros_like(data)
|
|
654
|
+
print(
|
|
655
|
+
"Doing Savitzki-Golay filter: Window=",
|
|
656
|
+
str(savgol_w),
|
|
657
|
+
" Polynomial: ",
|
|
658
|
+
str(savgol_pol),
|
|
659
|
+
" Derivarive: ",
|
|
660
|
+
str(deriv),
|
|
661
|
+
)
|
|
662
|
+
data_firstDev = savgol_filter(
|
|
663
|
+
data, savgol_w, savgol_pol, deriv=deriv, axis=2
|
|
664
|
+
)
|
|
665
|
+
|
|
666
|
+
return data_firstDev
|
|
667
|
+
|
|
668
|
+
|
|
669
|
+
# %% METRICS
|
|
670
|
+
def metrics(data, metric):
|
|
671
|
+
params = {}
|
|
672
|
+
if metric == "Frobenius norm":
|
|
673
|
+
norm = np.linalg.norm(data)
|
|
674
|
+
print(f"Frobenius norm for the dataset: {norm}")
|
|
675
|
+
data_norm = data / norm
|
|
676
|
+
params["norm"] = norm
|
|
677
|
+
|
|
678
|
+
if metric == "Z score":
|
|
679
|
+
data_reshaped = data.reshape(-1, data.shape[2])
|
|
680
|
+
mu, sigma = np.mean(data_reshaped), np.std(data_reshaped)
|
|
681
|
+
print(f"Mean of the dataset: {mu}")
|
|
682
|
+
print(f"Std of the dataset: {sigma}")
|
|
683
|
+
data_reshaped_norm = (data_reshaped - mu) / sigma
|
|
684
|
+
data_reshaped_norm = data_reshaped_norm - data_reshaped_norm.min()
|
|
685
|
+
data_norm = data_reshaped_norm.reshape(data.shape)
|
|
686
|
+
params["mu"], params["sigma"] = mu, sigma
|
|
687
|
+
|
|
688
|
+
if metric == "Z score - dataset":
|
|
689
|
+
mu = data.mean(axis=(0, 1))
|
|
690
|
+
sigma = data.var(axis=(0, 1))
|
|
691
|
+
print(mu.shape, sigma.shape)
|
|
692
|
+
sigma[sigma == 0] = 1
|
|
693
|
+
sigma_tot = np.sum(np.sqrt(sigma))
|
|
694
|
+
data_norm = (data - mu[None, None, ...]) / sigma_tot
|
|
695
|
+
data_norm = data_norm - data_norm.min(axis=(0, 1))
|
|
696
|
+
params["mu"], params["sigma"] = mu, sigma_tot
|
|
697
|
+
|
|
698
|
+
if metric == "Z score - spectrum":
|
|
699
|
+
mu = data.mean(axis=(0, 1))
|
|
700
|
+
sigma = data.std(axis=(0, 1))
|
|
701
|
+
print(mu.shape)
|
|
702
|
+
sigma[sigma == 0] = 1
|
|
703
|
+
data_norm = (data - mu[None, None, ...]) / sigma[None, None, ...]
|
|
704
|
+
data_norm = data_norm - data_norm.min(axis=(0, 1))
|
|
705
|
+
params["mu"], params["sigma"] = mu, sigma
|
|
706
|
+
|
|
707
|
+
if metric == "SNV":
|
|
708
|
+
mu = data.mean(axis=2)
|
|
709
|
+
sigma = data.std(axis=2)
|
|
710
|
+
sigma[sigma == 0] = 1
|
|
711
|
+
data_norm = (data - mu[..., None]) / sigma[..., None]
|
|
712
|
+
print(f"Mean of the dataset shape: {mu.shape}")
|
|
713
|
+
params["mu"], params["sigma"] = mu, sigma
|
|
714
|
+
|
|
715
|
+
if metric == "Sum to one":
|
|
716
|
+
sum_data = data.sum(axis=2)
|
|
717
|
+
print("Dimension of sum: ", sum_data.shape)
|
|
718
|
+
sum_data[sum_data == 0] = 1
|
|
719
|
+
data_norm = data / sum_data[..., None]
|
|
720
|
+
params["sum"] = sum_data
|
|
721
|
+
|
|
722
|
+
if metric == "Global min-max":
|
|
723
|
+
min_data, max_data = data.min(axis=(0, 1)), data.max(axis=(0, 1))
|
|
724
|
+
print(
|
|
725
|
+
f"Dimension of min matrix of the dataset: {min_data.shape}, Dimension of max matrix of the dataset: {max_data.shape}"
|
|
726
|
+
)
|
|
727
|
+
data_norm = (data - min_data[None, None, ...]) / (max_data - min_data)[
|
|
728
|
+
None, None, ...
|
|
729
|
+
]
|
|
730
|
+
params["min"], params["max"] = min_data, max_data
|
|
731
|
+
|
|
732
|
+
if metric == "Robust min-max":
|
|
733
|
+
out = np.empty_like(data)
|
|
734
|
+
for b in range(data.shape[2]):
|
|
735
|
+
band = data[:, :, b]
|
|
736
|
+
p_low, p_high = np.percentile(band, (2, 98))
|
|
737
|
+
out[:, :, b] = (band - p_low) / (p_high - p_low)
|
|
738
|
+
data_norm = np.clip(out, 0, 1)
|
|
739
|
+
print("Robust min-max performed")
|
|
740
|
+
params["p_low"], params["p_high"] = np.array(p_low), np.array(p_high)
|
|
741
|
+
|
|
742
|
+
if metric == "Pixel min-max":
|
|
743
|
+
min_data = data.min(axis=2)
|
|
744
|
+
max_data = data.max(axis=2)
|
|
745
|
+
print(
|
|
746
|
+
f"Dimension of min matrix of the dataset: {min_data.shape}, Dimension of max matrix of the dataset: {max_data.shape}"
|
|
747
|
+
)
|
|
748
|
+
diff = max_data - min_data
|
|
749
|
+
diff[diff == 0] = 1
|
|
750
|
+
data_norm = (data - min_data[..., None]) / diff[..., None]
|
|
751
|
+
params["min"], params["diff"] = min_data, diff
|
|
752
|
+
|
|
753
|
+
print(f"Dimension of the normalized dataset: {data_norm.shape}")
|
|
754
|
+
return data_norm, params
|
|
755
|
+
|
|
756
|
+
|
|
757
|
+
# %% INVERSE METRICS
|
|
758
|
+
def inverse_metrics(spectrum_norm, metric, params):
|
|
759
|
+
if metric == "Frobenius norm":
|
|
760
|
+
return spectrum_norm * params["norm"]
|
|
761
|
+
|
|
762
|
+
if metric == "Z score":
|
|
763
|
+
return spectrum_norm * params["sigma"] + params["mu"]
|
|
764
|
+
|
|
765
|
+
if metric == "Z score - dataset":
|
|
766
|
+
return spectrum_norm * params["sigma"] + params["mu"][..., None]
|
|
767
|
+
|
|
768
|
+
if metric == "Z score - spectrum":
|
|
769
|
+
return (
|
|
770
|
+
spectrum_norm * params["sigma"][..., None]
|
|
771
|
+
+ params["mu"][..., None]
|
|
772
|
+
)
|
|
773
|
+
|
|
774
|
+
if metric == "SNV":
|
|
775
|
+
mu_all = spectrum_norm.mean(axis=0)
|
|
776
|
+
sigma_all = spectrum_norm.std(axis=0)
|
|
777
|
+
sigma_all[sigma_all == 0] = 1 # evita divisione per zero
|
|
778
|
+
# mu, sigma = params["mu_pixel"], params["sigma_pixel"]
|
|
779
|
+
return spectrum_norm * sigma_all[None, ...] + mu_all[None, ...]
|
|
780
|
+
|
|
781
|
+
if metric == "Sum to one":
|
|
782
|
+
sum_all = spectrum_norm.sum(axis=0)
|
|
783
|
+
return spectrum_norm * sum_all[..., None]
|
|
784
|
+
|
|
785
|
+
if metric == "Global min-max":
|
|
786
|
+
return (
|
|
787
|
+
spectrum_norm * (params["max"] - params["min"])[..., None]
|
|
788
|
+
+ params["min"][..., None]
|
|
789
|
+
)
|
|
790
|
+
|
|
791
|
+
if metric == "Robust min-max":
|
|
792
|
+
return (
|
|
793
|
+
spectrum_norm * (params["p_high"] - params["p_low"])[..., None]
|
|
794
|
+
+ params["p_low"][..., None]
|
|
795
|
+
)
|
|
796
|
+
|
|
797
|
+
if metric == "Pixel min-max":
|
|
798
|
+
min_all = spectrum_norm.min(axis=0)
|
|
799
|
+
max_all = spectrum_norm.max(axis=0)
|
|
800
|
+
diff_all = max_all - min_all
|
|
801
|
+
return spectrum_norm * diff_all[None, ...] + min_all[None, :]
|
|
802
|
+
|
|
803
|
+
|
|
804
|
+
# %% FUSION
|
|
805
|
+
def datasets_fusion(data1, data2, wl1, wl2, norm="Z score"):
|
|
806
|
+
""" """
|
|
807
|
+
print(
|
|
808
|
+
f"Dimensions of dataset 1 and 2: \nData1: {data1.shape} \nData2: {data2.shape} \n\n"
|
|
809
|
+
)
|
|
810
|
+
|
|
811
|
+
data1_norm, params_1 = metrics(data1, metric=norm)
|
|
812
|
+
data2_norm, params_2 = metrics(data2, metric=norm)
|
|
813
|
+
|
|
814
|
+
wl_fused = np.concatenate((wl1, wl2))
|
|
815
|
+
data_fused = np.concatenate((data1_norm, data2_norm), axis=2)
|
|
816
|
+
fusion_point = data1_norm.shape[2]
|
|
817
|
+
print(
|
|
818
|
+
f"The new dataset has the shape: {data_fused.shape} \nThe fusion point is: {fusion_point}"
|
|
819
|
+
)
|
|
820
|
+
|
|
821
|
+
return data_fused, wl_fused, [params_1, params_2]
|
|
822
|
+
|
|
823
|
+
|
|
824
|
+
# %% ----- ----- ----- ----- ANALYSIS ----- ----- ----- -----
|
|
825
|
+
|
|
826
|
+
|
|
827
|
+
# %% PCA
|
|
828
|
+
def PCA_analysis(data, n_components, points=None, variance=False):
|
|
829
|
+
""" """
|
|
830
|
+
if points is None:
|
|
831
|
+
points = []
|
|
832
|
+
|
|
833
|
+
data_reshaped = data.reshape(data.shape[0] * data.shape[1], -1)
|
|
834
|
+
pca = PCA(n_components)
|
|
835
|
+
|
|
836
|
+
if len(points) > 0:
|
|
837
|
+
pca.fit(data_reshaped[points, :])
|
|
838
|
+
H = np.zeros((data.shape[0] * data.shape[1], n_components))
|
|
839
|
+
H_reduced = pca.transform(data_reshaped[points, :])
|
|
840
|
+
for i in range(n_components):
|
|
841
|
+
H[points, i] = H_reduced[:, i]
|
|
842
|
+
H = H.reshape(data.shape[0], data.shape[1], n_components)
|
|
843
|
+
else:
|
|
844
|
+
pca.fit(data_reshaped)
|
|
845
|
+
H = pca.transform(data_reshaped).reshape(
|
|
846
|
+
data.shape[0], data.shape[1], n_components
|
|
847
|
+
)
|
|
848
|
+
W = pca.components_ # EIGENVECTORS
|
|
849
|
+
print("W shape: ", W.shape, "H shape: ", H.shape)
|
|
850
|
+
print("Variance: ", pca.explained_variance_)
|
|
851
|
+
|
|
852
|
+
if variance:
|
|
853
|
+
cum_explained_var = []
|
|
854
|
+
for i in range(len(pca.explained_variance_ratio_)):
|
|
855
|
+
if i == 0:
|
|
856
|
+
cum_explained_var.append(pca.explained_variance_ratio_[i])
|
|
857
|
+
else:
|
|
858
|
+
cum_explained_var.append(
|
|
859
|
+
pca.explained_variance_ratio_[i] + cum_explained_var[i - 1]
|
|
860
|
+
)
|
|
861
|
+
print(cum_explained_var)
|
|
862
|
+
|
|
863
|
+
wl = np.arange(n_components)
|
|
864
|
+
line = np.zeros_like(wl)
|
|
865
|
+
line = np.full(n_components, 0.95)
|
|
866
|
+
|
|
867
|
+
plot = go.Figure()
|
|
868
|
+
plot.add_trace(
|
|
869
|
+
go.Scatter(
|
|
870
|
+
x=wl,
|
|
871
|
+
y=cum_explained_var,
|
|
872
|
+
marker={"size": 5},
|
|
873
|
+
mode="markers",
|
|
874
|
+
showlegend=False,
|
|
875
|
+
)
|
|
876
|
+
)
|
|
877
|
+
plot.add_trace(
|
|
878
|
+
go.Scatter(
|
|
879
|
+
x=wl,
|
|
880
|
+
y=line,
|
|
881
|
+
line={"width": 1, "color": "red"},
|
|
882
|
+
marker={"size": 5},
|
|
883
|
+
mode="lines",
|
|
884
|
+
name="95%",
|
|
885
|
+
)
|
|
886
|
+
)
|
|
887
|
+
plot.update_layout(
|
|
888
|
+
{
|
|
889
|
+
"plot_bgcolor": "rgba(0, 0, 0, 0)",
|
|
890
|
+
"paper_bgcolor": "rgba(0, 0, 0, 0)",
|
|
891
|
+
},
|
|
892
|
+
width=1000,
|
|
893
|
+
height=600,
|
|
894
|
+
xaxis_title="Number of components",
|
|
895
|
+
yaxis_title="Contribution to toal variance",
|
|
896
|
+
yaxis_range=[0.8, 1.01],
|
|
897
|
+
)
|
|
898
|
+
|
|
899
|
+
plot.show()
|
|
900
|
+
return H, W, cum_explained_var
|
|
901
|
+
else:
|
|
902
|
+
return H, W
|
|
903
|
+
|
|
904
|
+
|
|
905
|
+
# %% UMAP
|
|
906
|
+
def UMAP_analysis(
|
|
907
|
+
data,
|
|
908
|
+
downsampling=1,
|
|
909
|
+
points=None,
|
|
910
|
+
metric="euclidean",
|
|
911
|
+
n_neighbors=20,
|
|
912
|
+
min_dist=0.0,
|
|
913
|
+
spread=1.0,
|
|
914
|
+
init="spectral",
|
|
915
|
+
densmap=False,
|
|
916
|
+
random_state=42,
|
|
917
|
+
):
|
|
918
|
+
""" """
|
|
919
|
+
if points is None:
|
|
920
|
+
points = []
|
|
921
|
+
start_time = time.time() # Start of the timer
|
|
922
|
+
|
|
923
|
+
if downsampling != 1:
|
|
924
|
+
data = data[0::downsampling, 0::downsampling, :]
|
|
925
|
+
print("Data downsampled dimesnion: ", data.shape)
|
|
926
|
+
|
|
927
|
+
data_reshaped = data.reshape(data.shape[0] * data.shape[1], -1)
|
|
928
|
+
print("Data reshaped dimension: ", data_reshaped.shape)
|
|
929
|
+
|
|
930
|
+
fit = umap.UMAP(
|
|
931
|
+
n_neighbors=n_neighbors,
|
|
932
|
+
min_dist=min_dist,
|
|
933
|
+
n_components=2,
|
|
934
|
+
metric=metric,
|
|
935
|
+
n_jobs=-1,
|
|
936
|
+
spread=spread,
|
|
937
|
+
init=init,
|
|
938
|
+
densmap=densmap,
|
|
939
|
+
)
|
|
940
|
+
# output_metric='hyperboloid',)
|
|
941
|
+
if len(points) > 0:
|
|
942
|
+
umap_result = fit.fit_transform(data_reshaped[points, :])
|
|
943
|
+
else:
|
|
944
|
+
umap_result = fit.fit_transform(data_reshaped)
|
|
945
|
+
print("UMAP result dimension: ", umap_result.shape)
|
|
946
|
+
elapsed_time = time.time() - start_time
|
|
947
|
+
print(f"Time: {elapsed_time:.2f} seconds")
|
|
948
|
+
|
|
949
|
+
return umap_result
|
|
950
|
+
|
|
951
|
+
|
|
952
|
+
# %% NMF
|
|
953
|
+
# %% UMAP
|
|
954
|
+
def NMF_analysis(
|
|
955
|
+
data,
|
|
956
|
+
points,
|
|
957
|
+
n_components,
|
|
958
|
+
init,
|
|
959
|
+
):
|
|
960
|
+
""" """
|
|
961
|
+
if points is None:
|
|
962
|
+
points = []
|
|
963
|
+
start_time = time.time() # Start of the timer
|
|
964
|
+
|
|
965
|
+
data_reshaped = data.reshape(data.shape[0] * data.shape[1], -1)
|
|
966
|
+
print("Data reshaped dimension: ", data_reshaped.shape)
|
|
967
|
+
|
|
968
|
+
fit = NMF(n_components=n_components, init=init, tol=5e-8, max_iter=10000)
|
|
969
|
+
|
|
970
|
+
if len(points) > 0:
|
|
971
|
+
H = fit.fit_transform(data_reshaped[points, :])
|
|
972
|
+
W = fit.components_.T
|
|
973
|
+
H_temp = np.zeros((data.shape[0] * data.shape[1], n_components))
|
|
974
|
+
H_temp[points, :] = H
|
|
975
|
+
H = H_temp.reshape(data.shape[0], data.shape[1], n_components)
|
|
976
|
+
else:
|
|
977
|
+
H = fit.fit_transform(data_reshaped)
|
|
978
|
+
W = fit.components_.T
|
|
979
|
+
H = H.reshape(data.shape[0], data.shape[1], W.shape[1])
|
|
980
|
+
|
|
981
|
+
print(f"NMF result dimension: W shape {W.shape}, H shape {H.shape}")
|
|
982
|
+
elapsed_time = time.time() - start_time
|
|
983
|
+
print(f"Time: {elapsed_time:.2f} seconds")
|
|
984
|
+
|
|
985
|
+
return H, W
|
|
986
|
+
|
|
987
|
+
|
|
988
|
+
# %% SiVM
|
|
989
|
+
def SiVM(
|
|
990
|
+
data,
|
|
991
|
+
n_bases=10,
|
|
992
|
+
init="origin",
|
|
993
|
+
metric="euclidean",
|
|
994
|
+
silent=True,
|
|
995
|
+
points=None,
|
|
996
|
+
):
|
|
997
|
+
W = []
|
|
998
|
+
# H_labels = []
|
|
999
|
+
W_indices = []
|
|
1000
|
+
select = []
|
|
1001
|
+
# UPDATE W
|
|
1002
|
+
EPS = 10**-8
|
|
1003
|
+
|
|
1004
|
+
# Reshape of the data
|
|
1005
|
+
data_reshaped = data.reshape(np.prod(data.shape[:2]), data.shape[2]).T
|
|
1006
|
+
data_shape = data.shape
|
|
1007
|
+
|
|
1008
|
+
if len(points) > 0:
|
|
1009
|
+
data_reshaped = data_reshaped[points, :]
|
|
1010
|
+
|
|
1011
|
+
# DIST FUNC
|
|
1012
|
+
def distfunc(data, vec):
|
|
1013
|
+
dist = distance_metrics()[metric](data.T, vec.T)[:, 0]
|
|
1014
|
+
return dist
|
|
1015
|
+
|
|
1016
|
+
# DISTANCE
|
|
1017
|
+
def distance(data_reshaped, idx):
|
|
1018
|
+
"""compute distances of a specific data point to all other samples"""
|
|
1019
|
+
print(
|
|
1020
|
+
"Compute the distances of a specific data point to all other samples"
|
|
1021
|
+
)
|
|
1022
|
+
if issparse(data_reshaped):
|
|
1023
|
+
print("The matrix is sparse")
|
|
1024
|
+
step = data_reshaped.shape[1]
|
|
1025
|
+
else:
|
|
1026
|
+
step = 50000
|
|
1027
|
+
print("The matrix is not sparse. 50000 steps are used.")
|
|
1028
|
+
|
|
1029
|
+
d = np.zeros(
|
|
1030
|
+
data_reshaped.shape[1]
|
|
1031
|
+
) # Creation of d, has the dimension of number of points
|
|
1032
|
+
|
|
1033
|
+
if idx == -1:
|
|
1034
|
+
# If idx =-1, set vec to origin
|
|
1035
|
+
print("First cycle. Calulate the distances from the origin.")
|
|
1036
|
+
vec = np.zeros(
|
|
1037
|
+
(data_reshaped.shape[0], 1)
|
|
1038
|
+
) # Creation of vec, has the dimension of a spectrum
|
|
1039
|
+
if issparse(data_reshaped):
|
|
1040
|
+
vec = csc_matrix(vec)
|
|
1041
|
+
else:
|
|
1042
|
+
print("Compute distance to node: ", str(idx))
|
|
1043
|
+
vec = data_reshaped[
|
|
1044
|
+
:, idx : idx + 1
|
|
1045
|
+
] # cur_p = 0 --> take the first element
|
|
1046
|
+
|
|
1047
|
+
# slice data into smaller chunks
|
|
1048
|
+
for idx_start in range(
|
|
1049
|
+
0, data_reshaped.shape[1], step
|
|
1050
|
+
): # From 0 to all the pixels, qith 50 000 step
|
|
1051
|
+
if idx_start + step > data_reshaped.shape[1]:
|
|
1052
|
+
idx_end = data_reshaped.shape[1]
|
|
1053
|
+
else:
|
|
1054
|
+
idx_end = idx_start + step
|
|
1055
|
+
d[idx_start:idx_end] = distfunc(
|
|
1056
|
+
data_reshaped[:, idx_start:idx_end], vec
|
|
1057
|
+
) # Calculate distance of each point of the chunk from the vector vec
|
|
1058
|
+
# print('Completed:' + str(idx_end/(data_reshaped.shape[1]/100.0)) + "%")
|
|
1059
|
+
return d
|
|
1060
|
+
|
|
1061
|
+
# INIT_SIV
|
|
1062
|
+
if init == "fastmap":
|
|
1063
|
+
cur_p = 0 # set the starting index for fastmap initialization
|
|
1064
|
+
|
|
1065
|
+
# after 3 iterations the first "real" index is found
|
|
1066
|
+
for _ in range(3):
|
|
1067
|
+
d = distance(data_reshaped, cur_p)
|
|
1068
|
+
cur_p = np.argmax(d)
|
|
1069
|
+
print(d)
|
|
1070
|
+
maxd = np.max(d)
|
|
1071
|
+
select.append(cur_p)
|
|
1072
|
+
elif init == "origin":
|
|
1073
|
+
cur_p = -1
|
|
1074
|
+
d = distance(data_reshaped, cur_p)
|
|
1075
|
+
maxd = np.max(d)
|
|
1076
|
+
select.append(cur_p)
|
|
1077
|
+
# ---
|
|
1078
|
+
|
|
1079
|
+
d_square = np.zeros(data_reshaped.shape[1])
|
|
1080
|
+
d_sum = np.zeros(data_reshaped.shape[1])
|
|
1081
|
+
d_i_times_d_j = np.zeros(data_reshaped.shape[1])
|
|
1082
|
+
distiter = np.zeros(data_reshaped.shape[1])
|
|
1083
|
+
a = np.log(maxd)
|
|
1084
|
+
# a_inc = a.copy()
|
|
1085
|
+
|
|
1086
|
+
for l_index in range(1, n_bases):
|
|
1087
|
+
d = distance(data_reshaped, select[l_index - 1])
|
|
1088
|
+
|
|
1089
|
+
# take the log of d (sually more stable that d)
|
|
1090
|
+
d = np.log(d + EPS)
|
|
1091
|
+
|
|
1092
|
+
d_i_times_d_j += d * d_sum
|
|
1093
|
+
d_sum += d
|
|
1094
|
+
d_square += d**2
|
|
1095
|
+
distiter = d_i_times_d_j + a * d_sum - (l_index / 2.0) * d_square
|
|
1096
|
+
|
|
1097
|
+
# detect the next best data point
|
|
1098
|
+
select.append(np.argmax(distiter))
|
|
1099
|
+
|
|
1100
|
+
if not silent:
|
|
1101
|
+
print("cur_nodes: " + str(select))
|
|
1102
|
+
# sort indices, otherwise h5py won't work
|
|
1103
|
+
W_calc = data_reshaped[:, np.sort(select)]
|
|
1104
|
+
|
|
1105
|
+
# "unsort" it again to keep the correct order
|
|
1106
|
+
W_calc = W_calc[:, np.argsort(np.argsort(select))]
|
|
1107
|
+
# ----
|
|
1108
|
+
|
|
1109
|
+
data = data_reshaped.T.reshape(data_shape)
|
|
1110
|
+
W.append(W_calc)
|
|
1111
|
+
W_indices.append(select)
|
|
1112
|
+
# H_labels.append(['Archetype '+str(i) for i in range(n_bases['value'])])
|
|
1113
|
+
|
|
1114
|
+
W = np.array(W)
|
|
1115
|
+
W = W.reshape(W.shape[1], W.shape[2])
|
|
1116
|
+
print(W.shape)
|
|
1117
|
+
|
|
1118
|
+
return W
|
|
1119
|
+
|
|
1120
|
+
|
|
1121
|
+
# %% N-FINDR
|
|
1122
|
+
def NFINDR(data, n_bases=10):
|
|
1123
|
+
W_calc = eea.NFINDR().extract(data, n_bases)
|
|
1124
|
+
W = np.array(W_calc).transpose
|
|
1125
|
+
print(W.shape)
|
|
1126
|
+
return W
|
|
1127
|
+
|
|
1128
|
+
|
|
1129
|
+
# %% PPI
|
|
1130
|
+
def PPI(data, n_bases=10):
|
|
1131
|
+
W_calc = eea.PPI().extract(data, n_bases)
|
|
1132
|
+
W = np.array(W_calc).transpose
|
|
1133
|
+
print(W.shape)
|
|
1134
|
+
return W
|
|
1135
|
+
|
|
1136
|
+
|
|
1137
|
+
# %% NNLS
|
|
1138
|
+
def nnls_analysis(data, W):
|
|
1139
|
+
data_reshaped = data.reshape(data.shape[0] * data.shape[1], -1)
|
|
1140
|
+
|
|
1141
|
+
result = np.zeros((data_reshaped.shape[0], W.shape[1]))
|
|
1142
|
+
print(
|
|
1143
|
+
"Data shape: ",
|
|
1144
|
+
data_reshaped.shape,
|
|
1145
|
+
"\nEndmember matrix shape: ",
|
|
1146
|
+
W.shape,
|
|
1147
|
+
)
|
|
1148
|
+
|
|
1149
|
+
for i in range(data_reshaped.shape[0]):
|
|
1150
|
+
result[i, :] = nnls(W, data_reshaped.T[:, i])[0]
|
|
1151
|
+
|
|
1152
|
+
result = result.reshape(data.shape[0], data.shape[1], W.shape[1])
|
|
1153
|
+
return result
|
|
1154
|
+
|
|
1155
|
+
|
|
1156
|
+
def sam_analysis(data, W, angle):
|
|
1157
|
+
print("Data shape: ", data.shape)
|
|
1158
|
+
print("Reference spectrum shape: ", W.shape)
|
|
1159
|
+
angles = np.zeros((data.shape[0], data.shape[1], W.shape[1]))
|
|
1160
|
+
print("Angles shape: ", angles.shape)
|
|
1161
|
+
for d in range(W.shape[1]):
|
|
1162
|
+
angles[:, :, d] = spectral.spectral_angles(
|
|
1163
|
+
data, W[:, d].reshape(1, W.shape[0])
|
|
1164
|
+
).reshape(data.shape[0], data.shape[1])
|
|
1165
|
+
print("Angles shape: ", angles.shape)
|
|
1166
|
+
|
|
1167
|
+
for i in range(angles.shape[0]):
|
|
1168
|
+
for j in range(angles.shape[1]):
|
|
1169
|
+
if angles[i, j, d] >= angle or np.isnan(angles[i, j, d]):
|
|
1170
|
+
angles[i, j, d] = angle + 0.1
|
|
1171
|
+
print("Angles shape: ", angles.shape)
|
|
1172
|
+
return angles
|
|
1173
|
+
|
|
1174
|
+
|
|
1175
|
+
# %% VCA
|
|
1176
|
+
|
|
1177
|
+
|
|
1178
|
+
def estimate_snr(Y, r_m, x):
|
|
1179
|
+
|
|
1180
|
+
[L, N] = Y.shape # L number of bands (channels), N number of pixels
|
|
1181
|
+
[p, N] = x.shape # p number of endmembers (reduced dimension)
|
|
1182
|
+
|
|
1183
|
+
P_y = np.sum(Y**2) / float(N)
|
|
1184
|
+
P_x = np.sum(x**2) / float(N) + np.sum(r_m**2)
|
|
1185
|
+
snr_est = 10 * np.log10((P_x - p / L * P_y) / (P_y - P_x))
|
|
1186
|
+
|
|
1187
|
+
return snr_est
|
|
1188
|
+
|
|
1189
|
+
|
|
1190
|
+
def vca(Y, R, verbose=True, snr_input=0):
|
|
1191
|
+
# Vertex Component Analysis
|
|
1192
|
+
#
|
|
1193
|
+
# Ae, indice, Yp = vca(Y,R,verbose = True,snr_input = 0)
|
|
1194
|
+
#
|
|
1195
|
+
# ------- Input variables -------------
|
|
1196
|
+
# Y - matrix with dimensions L(channels) x N(pixels)
|
|
1197
|
+
# each pixel is a linear mixture of R endmembers
|
|
1198
|
+
# signatures Y = M x s, where s = gamma x alfa
|
|
1199
|
+
# gamma is a illumination perturbation factor and
|
|
1200
|
+
# alfa are the abundance fractions of each endmember.
|
|
1201
|
+
# R - positive integer number of endmembers in the scene
|
|
1202
|
+
#
|
|
1203
|
+
# ------- Output variables -----------
|
|
1204
|
+
# Ae - estimated mixing matrix (endmembers signatures)
|
|
1205
|
+
# indice - pixels that were chosen to be the most pure
|
|
1206
|
+
# Yp - Data matrix Y projected.
|
|
1207
|
+
#
|
|
1208
|
+
# ------- Optional parameters---------
|
|
1209
|
+
# snr_input - (float) signal to noise ratio (dB)
|
|
1210
|
+
# v - [True | False]
|
|
1211
|
+
# ------------------------------------
|
|
1212
|
+
#
|
|
1213
|
+
# Author: Adrien Lagrange (adrien.lagrange@enseeiht.fr)
|
|
1214
|
+
# This code is a translation of a matlab code provided by
|
|
1215
|
+
# Jose Nascimento (zen@isel.pt) and Jose Bioucas Dias (bioucas@lx.it.pt)
|
|
1216
|
+
# available at http://www.lx.it.pt/~bioucas/code.htm under a non-specified Copyright (c)
|
|
1217
|
+
# Translation of last version at 22-February-2018 (Matlab version 2.1 (7-May-2004))
|
|
1218
|
+
#
|
|
1219
|
+
# more details on:
|
|
1220
|
+
# Jose M. P. Nascimento and Jose M. B. Dias
|
|
1221
|
+
# "Vertex Component Analysis: A Fast Algorithm to Unmix Hyperspectral Data"
|
|
1222
|
+
# submited to IEEE Trans. Geosci. Remote Sensing, vol. .., no. .., pp. .-., 2004
|
|
1223
|
+
#
|
|
1224
|
+
#
|
|
1225
|
+
|
|
1226
|
+
#############################################
|
|
1227
|
+
# Initializations
|
|
1228
|
+
#############################################
|
|
1229
|
+
if len(Y.shape) != 2:
|
|
1230
|
+
sys.exit(
|
|
1231
|
+
"Input data must be of size L (number of bands i.e. channels) by N (number of pixels)"
|
|
1232
|
+
)
|
|
1233
|
+
|
|
1234
|
+
[L, N] = Y.shape # L number of bands (channels), N number of pixels
|
|
1235
|
+
|
|
1236
|
+
R = int(R)
|
|
1237
|
+
if R < 0 or R > L:
|
|
1238
|
+
sys.exit("ENDMEMBER parameter must be integer between 1 and L")
|
|
1239
|
+
|
|
1240
|
+
#############################################
|
|
1241
|
+
# SNR Estimates
|
|
1242
|
+
#############################################
|
|
1243
|
+
|
|
1244
|
+
if snr_input == 0:
|
|
1245
|
+
y_m = np.mean(Y, axis=1, keepdims=True)
|
|
1246
|
+
Y_o = Y - y_m # data with zero-mean
|
|
1247
|
+
Ud = np.linalg.svd(np.dot(Y_o, Y_o.T) / float(N))[0][
|
|
1248
|
+
:, :R
|
|
1249
|
+
] # computes the R-projection matrix
|
|
1250
|
+
x_p = np.dot(Ud.T, Y_o) # project the zero-mean data onto p-subspace
|
|
1251
|
+
|
|
1252
|
+
SNR = estimate_snr(Y, y_m, x_p)
|
|
1253
|
+
|
|
1254
|
+
if verbose:
|
|
1255
|
+
print(f"SNR estimated = {SNR}[dB]")
|
|
1256
|
+
else:
|
|
1257
|
+
SNR = snr_input
|
|
1258
|
+
if verbose:
|
|
1259
|
+
print(f"input SNR = {SNR}[dB]\n")
|
|
1260
|
+
|
|
1261
|
+
SNR_th = 15 + 10 * np.log10(R)
|
|
1262
|
+
|
|
1263
|
+
#############################################
|
|
1264
|
+
# Choosing Projective Projection or
|
|
1265
|
+
# projection to p-1 subspace
|
|
1266
|
+
#############################################
|
|
1267
|
+
|
|
1268
|
+
if SNR_th > SNR:
|
|
1269
|
+
if verbose:
|
|
1270
|
+
print("... Select proj. to R-1")
|
|
1271
|
+
|
|
1272
|
+
d = R - 1
|
|
1273
|
+
if (
|
|
1274
|
+
snr_input == 0
|
|
1275
|
+
): # it means that the projection is already computed
|
|
1276
|
+
Ud = Ud[:, :d]
|
|
1277
|
+
else:
|
|
1278
|
+
y_m = np.mean(Y, axis=1, keepdims=True)
|
|
1279
|
+
Y_o = Y - y_m # data with zero-mean
|
|
1280
|
+
|
|
1281
|
+
Ud = np.linalg.svd(np.dot(Y_o, Y_o.T) / float(N))[0][
|
|
1282
|
+
:, :d
|
|
1283
|
+
] # computes the p-projection matrix
|
|
1284
|
+
x_p = np.dot(
|
|
1285
|
+
Ud.T, Y_o
|
|
1286
|
+
) # project thezeros mean data onto p-subspace
|
|
1287
|
+
|
|
1288
|
+
Yp = np.dot(Ud, x_p[:d, :]) + y_m # again in dimension L
|
|
1289
|
+
|
|
1290
|
+
x = x_p[:d, :] # x_p = Ud.T * Y_o is on a R-dim subspace
|
|
1291
|
+
c = np.amax(np.sum(x**2, axis=0)) ** 0.5
|
|
1292
|
+
y = np.vstack((x, c * np.ones((1, N))))
|
|
1293
|
+
else:
|
|
1294
|
+
if verbose:
|
|
1295
|
+
print("... Select the projective proj.")
|
|
1296
|
+
|
|
1297
|
+
d = R
|
|
1298
|
+
Ud = np.linalg.svd(np.dot(Y, Y.T) / float(N))[0][
|
|
1299
|
+
:, :d
|
|
1300
|
+
] # computes the p-projection matrix
|
|
1301
|
+
|
|
1302
|
+
x_p = np.dot(Ud.T, Y)
|
|
1303
|
+
Yp = np.dot(
|
|
1304
|
+
Ud, x_p[:d, :]
|
|
1305
|
+
) # again in dimension L (note that x_p has no null mean)
|
|
1306
|
+
|
|
1307
|
+
x = np.dot(Ud.T, Y)
|
|
1308
|
+
u = np.mean(x, axis=1, keepdims=True) # equivalent to u = Ud.T * r_m
|
|
1309
|
+
y = x / (np.dot(u.T, x) + 1e-7)
|
|
1310
|
+
|
|
1311
|
+
#############################################
|
|
1312
|
+
# VCA algorithm
|
|
1313
|
+
#############################################
|
|
1314
|
+
|
|
1315
|
+
indice = np.zeros((R), dtype=int)
|
|
1316
|
+
A = np.zeros((R, R))
|
|
1317
|
+
A[-1, 0] = 1
|
|
1318
|
+
|
|
1319
|
+
for i in range(R):
|
|
1320
|
+
w = np.random.rand(R, 1)
|
|
1321
|
+
f = w - np.dot(A, np.dot(np.linalg.pinv(A), w))
|
|
1322
|
+
f = f / np.linalg.norm(f)
|
|
1323
|
+
|
|
1324
|
+
v = np.dot(f.T, y)
|
|
1325
|
+
|
|
1326
|
+
indice[i] = np.argmax(np.absolute(v))
|
|
1327
|
+
A[:, i] = y[:, indice[i]] # same as x(:,indice(i))
|
|
1328
|
+
|
|
1329
|
+
Ae = Yp[:, indice]
|
|
1330
|
+
|
|
1331
|
+
return Ae, indice, Yp
|