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.
@@ -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