eem-python 1.2__py3-none-any.whl → 1.3__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.
- {eem_python-1.2.dist-info → eem_python-1.3.dist-info}/METADATA +5 -1
- eem_python-1.3.dist-info/RECORD +14 -0
- {eem_python-1.2.dist-info → eem_python-1.3.dist-info}/WHEEL +1 -1
- eempy/eem_processing/eem_processing.py +609 -172
- eempy/plot/plot.py +359 -86
- eempy/read_data/read_data.py +93 -22
- eempy/utils/utils.py +20 -1
- eem_python-1.2.dist-info/RECORD +0 -14
- {eem_python-1.2.dist-info → eem_python-1.3.dist-info}/LICENSE +0 -0
- {eem_python-1.2.dist-info → eem_python-1.3.dist-info}/top_level.txt +0 -0
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Functions for EEM preprocessing and post-processing
|
|
3
3
|
Author: Yongmin Hu (yongmin.hu@eawag.ch, yongminhu@outlook.com)
|
|
4
|
-
Last update: 2024-
|
|
4
|
+
Last update: 2024-07-03
|
|
5
5
|
"""
|
|
6
|
+
from matplotlib import pyplot as plt
|
|
6
7
|
|
|
7
|
-
from eempy.read_data import *
|
|
8
8
|
from eempy.utils import *
|
|
9
9
|
import scipy.stats as stats
|
|
10
10
|
import random
|
|
@@ -13,10 +13,12 @@ import numpy as np
|
|
|
13
13
|
import itertools
|
|
14
14
|
import string
|
|
15
15
|
import warnings
|
|
16
|
+
import json
|
|
16
17
|
from math import sqrt
|
|
17
18
|
from sklearn.metrics import mean_squared_error, explained_variance_score, r2_score
|
|
18
19
|
# from sklearn.ensemble import IsolationForest
|
|
19
20
|
# from sklearn import svm
|
|
21
|
+
from sklearn.decomposition import PCA, NMF
|
|
20
22
|
from sklearn.linear_model import LinearRegression
|
|
21
23
|
from scipy.ndimage import gaussian_filter
|
|
22
24
|
from scipy.interpolate import RegularGridInterpolator, interp1d, griddata
|
|
@@ -54,12 +56,12 @@ def process_eem_stack(eem_stack, f, **kwargs):
|
|
|
54
56
|
The processed EEM stack.
|
|
55
57
|
other_outputs: tuple, optional
|
|
56
58
|
If the EEM processing function have more than 1 returns, the rest of the returns will be stored in a tuple,
|
|
57
|
-
where each element is the return of EEM processing function applied
|
|
59
|
+
where each element is the return of EEM processing function applied to every EEM.
|
|
58
60
|
"""
|
|
59
61
|
processed_eem_stack = []
|
|
60
62
|
other_outputs = []
|
|
61
63
|
|
|
62
|
-
|
|
64
|
+
# ------Absorbance and blank are two parameters that are potentially sample-specific--------
|
|
63
65
|
if "absorbance" in kwargs:
|
|
64
66
|
abs = kwargs.pop('absorbance')
|
|
65
67
|
abs_passed = True
|
|
@@ -192,7 +194,7 @@ def eem_gaussian_filter(intensity, sigma=1, truncate=3):
|
|
|
192
194
|
return intensity_filtered
|
|
193
195
|
|
|
194
196
|
|
|
195
|
-
def eem_cutting(intensity,
|
|
197
|
+
def eem_cutting(intensity, ex_range_old, em_range_old, ex_min_new, ex_max_new, em_min_new, em_max_new):
|
|
196
198
|
"""
|
|
197
199
|
To cut the EEM.
|
|
198
200
|
|
|
@@ -200,17 +202,17 @@ def eem_cutting(intensity, ex_range, em_range, em_min, em_max, ex_min, ex_max):
|
|
|
200
202
|
----------
|
|
201
203
|
intensity: np.ndarray (2d)
|
|
202
204
|
The EEM.
|
|
203
|
-
|
|
205
|
+
ex_range_old: np.ndarray (1d)
|
|
204
206
|
The excitation wavelengths.
|
|
205
|
-
|
|
207
|
+
em_range_old: np.ndarray (1d)
|
|
206
208
|
The emission wavelengths.
|
|
207
|
-
|
|
209
|
+
ex_min_new: float
|
|
208
210
|
The lower boundary of excitation wavelength of the EEM after cutting.
|
|
209
|
-
|
|
211
|
+
ex_max_new: float
|
|
210
212
|
The upper boundary of excitation wavelength of the EEM after cutting.
|
|
211
|
-
|
|
213
|
+
em_min_new: float
|
|
212
214
|
The lower boundary of emission wavelength of the EEM after cutting.
|
|
213
|
-
|
|
215
|
+
em_max_new: float
|
|
214
216
|
The upper boundary of emission wavelength of the EEM after cutting.
|
|
215
217
|
|
|
216
218
|
Returns
|
|
@@ -222,14 +224,14 @@ def eem_cutting(intensity, ex_range, em_range, em_min, em_max, ex_min, ex_max):
|
|
|
222
224
|
em_range_cut:np.ndarray
|
|
223
225
|
The cut em wavelengths.
|
|
224
226
|
"""
|
|
225
|
-
em_min_idx = dichotomy_search(
|
|
226
|
-
em_max_idx = dichotomy_search(
|
|
227
|
-
ex_min_idx = dichotomy_search(
|
|
228
|
-
ex_max_idx = dichotomy_search(
|
|
229
|
-
intensity_cut = intensity[
|
|
230
|
-
|
|
231
|
-
em_range_cut =
|
|
232
|
-
ex_range_cut =
|
|
227
|
+
em_min_idx = dichotomy_search(em_range_old, em_min_new)
|
|
228
|
+
em_max_idx = dichotomy_search(em_range_old, em_max_new)
|
|
229
|
+
ex_min_idx = dichotomy_search(ex_range_old, ex_min_new)
|
|
230
|
+
ex_max_idx = dichotomy_search(ex_range_old, ex_max_new)
|
|
231
|
+
intensity_cut = intensity[ex_range_old.shape[0] - ex_max_idx - 1:ex_range_old.shape[0] - ex_min_idx,
|
|
232
|
+
em_min_idx:em_max_idx + 1]
|
|
233
|
+
em_range_cut = em_range_old[em_min_idx:em_max_idx + 1]
|
|
234
|
+
ex_range_cut = ex_range_old[ex_min_idx:ex_max_idx + 1]
|
|
233
235
|
return intensity_cut, ex_range_cut, em_range_cut
|
|
234
236
|
|
|
235
237
|
|
|
@@ -259,7 +261,6 @@ def eem_nan_imputing(intensity, ex_range, em_range, method: str = 'linear', fill
|
|
|
259
261
|
xx = x[~np.isnan(intensity)].flatten()
|
|
260
262
|
yy = y[~np.isnan(intensity)].flatten()
|
|
261
263
|
zz = intensity[~np.isnan(intensity)].flatten()
|
|
262
|
-
intensity_imputed = None
|
|
263
264
|
if isinstance(fill_value, float):
|
|
264
265
|
intensity_imputed = griddata((xx, yy), zz, (x, y), method=method, fill_value=fill_value)
|
|
265
266
|
elif fill_value == 'linear_ex':
|
|
@@ -285,8 +286,8 @@ def eem_nan_imputing(intensity, ex_range, em_range, method: str = 'linear', fill
|
|
|
285
286
|
return intensity_imputed
|
|
286
287
|
|
|
287
288
|
|
|
288
|
-
def eem_raman_normalization(intensity, blank=None, ex_range_blank=None, em_range_blank=None, from_blank=
|
|
289
|
-
integration_time=1,
|
|
289
|
+
def eem_raman_normalization(intensity, blank=None, ex_range_blank=None, em_range_blank=None, from_blank=True,
|
|
290
|
+
integration_time=1, ex_target=350, bandwidth=5,
|
|
290
291
|
rsu_standard=20000, manual_rsu: Optional[float] = 1):
|
|
291
292
|
"""
|
|
292
293
|
Normalize the EEM using the Raman scattering unit (RSU) given directly or calculated from a blank EEM.
|
|
@@ -307,14 +308,10 @@ def eem_raman_normalization(intensity, blank=None, ex_range_blank=None, em_range
|
|
|
307
308
|
Whether to calculate the RSU from a blank. If False, manual_rsu will be used.
|
|
308
309
|
integration_time: float
|
|
309
310
|
The integration time of the blank measurement.
|
|
310
|
-
|
|
311
|
-
The
|
|
312
|
-
ex_ub: float
|
|
313
|
-
The upper boundary of excitation wavelength range within which the RSU is calculated.
|
|
311
|
+
ex_target: float
|
|
312
|
+
The excitation wavelength at which the RSU is calculated.
|
|
314
313
|
bandwidth: float
|
|
315
314
|
The bandwidth of Raman scattering peak.
|
|
316
|
-
bandwidth_type: str, {"wavenumber", "wavelength"}
|
|
317
|
-
The type of bandwidth. "wavenumber": (1/nm); "wavelength": (nm).
|
|
318
315
|
rsu_standard: float
|
|
319
316
|
A factor used to divide the raw RSU. This is used to control the magnitude of RSU so that the normalized
|
|
320
317
|
intensity of EEM would not be numerically too high or too low.
|
|
@@ -331,28 +328,24 @@ def eem_raman_normalization(intensity, blank=None, ex_range_blank=None, em_range
|
|
|
331
328
|
if not from_blank:
|
|
332
329
|
return intensity / manual_rsu, manual_rsu
|
|
333
330
|
else:
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
ex_min=ex, ex_max=ex, em_min=ex - bandwidth, em_max=ex + bandwidth)
|
|
347
|
-
else:
|
|
348
|
-
raise ValueError("'bandwidth_type' should be either 'wavenumber' or 'wavelength'.")
|
|
349
|
-
rsu_tot += rsu
|
|
350
|
-
rsu_final = rsu_tot / (integration_time * rsu_standard)
|
|
331
|
+
em_target = -ex_target / (0.00036 * ex_target - 1)
|
|
332
|
+
rsu, _ = eem_regional_integration(blank, ex_range_blank, em_range_blank,
|
|
333
|
+
ex_min=ex_target, ex_max=ex_target, em_min=em_target - bandwidth / 2,
|
|
334
|
+
em_max=em_target + bandwidth / 2)
|
|
335
|
+
# elif bandwidth_type == 'wavenumber':
|
|
336
|
+
# em_target = -ex / (0.00036 * ex - 1)
|
|
337
|
+
# wn_target = 10000000 / em_target
|
|
338
|
+
# em_lb = 10000000 / (wn_target + bandwidth)
|
|
339
|
+
# em_rb = 10000000 / (wn_target - bandwidth)
|
|
340
|
+
# rsu, _ = eem_regional_integration(blank, ex_range_blank, em_range_blank,
|
|
341
|
+
# ex_min=ex, ex_max=ex, em_min=em_lb, em_max=em_rb)
|
|
342
|
+
rsu_final = rsu / (integration_time * rsu_standard)
|
|
351
343
|
intensity_normalized = intensity / rsu_final
|
|
352
344
|
return intensity_normalized, rsu_final
|
|
353
345
|
|
|
354
346
|
|
|
355
|
-
def
|
|
347
|
+
def eem_raman_scattering_removal(intensity, ex_range, em_range, width=5, interpolation_method='linear',
|
|
348
|
+
interpolation_dimension='2d'):
|
|
356
349
|
"""
|
|
357
350
|
Remove and interpolate the Raman scattering.
|
|
358
351
|
|
|
@@ -368,9 +361,9 @@ def eem_raman_masking(intensity, ex_range, em_range, width=5, interpolation_meth
|
|
|
368
361
|
The width of Raman scattering.
|
|
369
362
|
interpolation_method: str, {"linear", "cubic", "nan"}
|
|
370
363
|
The method used to interpolate the Raman scattering.
|
|
371
|
-
|
|
372
|
-
The axis along which the Raman scattering is interpolated. "ex": interpolation is conducted along the excitation
|
|
373
|
-
wavelength; "em": interpolation is conducted along the emission wavelength; "
|
|
364
|
+
interpolation_dimension: str, {"1d-ex", "1d-em", "2d"}
|
|
365
|
+
The axis along which the Raman scattering is interpolated. "1d-ex": interpolation is conducted along the excitation
|
|
366
|
+
wavelength; "1d-em": interpolation is conducted along the emission wavelength; "2d": interpolation is conducted
|
|
374
367
|
on the 2D grid of both excitation and emission wavelengths.
|
|
375
368
|
|
|
376
369
|
Returns
|
|
@@ -381,6 +374,7 @@ def eem_raman_masking(intensity, ex_range, em_range, width=5, interpolation_meth
|
|
|
381
374
|
Indicate the pixels that are interpolated. 0: pixel is interpolated; 1: pixel is not interpolated.
|
|
382
375
|
"""
|
|
383
376
|
intensity_masked = np.array(intensity)
|
|
377
|
+
width = width / 2
|
|
384
378
|
raman_mask = np.ones(intensity.shape)
|
|
385
379
|
lambda_em = -ex_range / (0.00036 * ex_range - 1)
|
|
386
380
|
tol_emidx = int(np.round(width / (em_range[1] - em_range[0])))
|
|
@@ -389,17 +383,17 @@ def eem_raman_masking(intensity, ex_range, em_range, width=5, interpolation_meth
|
|
|
389
383
|
if lambda_em[s] <= em_range[0] <= lambda_em[s] + width:
|
|
390
384
|
emidx = dichotomy_search(em_range, lambda_em[s] + width)
|
|
391
385
|
raman_mask[exidx, 0: emidx + 1] = 0
|
|
392
|
-
elif lambda_em[s] - width <= em_range[0]:
|
|
386
|
+
elif lambda_em[s] - width <= em_range[0] < lambda_em[s]:
|
|
393
387
|
emidx = dichotomy_search(em_range, lambda_em[s])
|
|
394
388
|
raman_mask[exidx, 0: emidx + tol_emidx + 1] = 0
|
|
395
|
-
|
|
389
|
+
elif em_range[0] < lambda_em[s] - width < em_range[-1]:
|
|
396
390
|
emidx = dichotomy_search(em_range, lambda_em[s] - width)
|
|
397
|
-
raman_mask[exidx, emidx: emidx + 2 * tol_emidx + 1] = 0
|
|
391
|
+
raman_mask[exidx, emidx: min(em_range.shape[0], emidx + 2 * tol_emidx + 1)] = 0
|
|
398
392
|
|
|
399
393
|
if interpolation_method == 'nan':
|
|
400
394
|
intensity_masked[np.where(raman_mask == 0)] = np.nan
|
|
401
395
|
else:
|
|
402
|
-
if
|
|
396
|
+
if interpolation_dimension == '1d-ex':
|
|
403
397
|
for j in range(0, intensity.shape[1]):
|
|
404
398
|
try:
|
|
405
399
|
x = np.flipud(ex_range)[np.where(raman_mask[:, j] == 1)]
|
|
@@ -410,7 +404,7 @@ def eem_raman_masking(intensity, ex_range, em_range, width=5, interpolation_meth
|
|
|
410
404
|
except ValueError:
|
|
411
405
|
continue
|
|
412
406
|
|
|
413
|
-
if
|
|
407
|
+
if interpolation_dimension == '1d-em':
|
|
414
408
|
for i in range(0, intensity.shape[0]):
|
|
415
409
|
try:
|
|
416
410
|
x = em_range[np.where(raman_mask[i, :] == 1)]
|
|
@@ -421,7 +415,7 @@ def eem_raman_masking(intensity, ex_range, em_range, width=5, interpolation_meth
|
|
|
421
415
|
except ValueError:
|
|
422
416
|
continue
|
|
423
417
|
|
|
424
|
-
if
|
|
418
|
+
if interpolation_dimension == '2d':
|
|
425
419
|
old_nan = np.isnan(intensity)
|
|
426
420
|
intensity_masked[np.where(raman_mask == 0)] = np.nan
|
|
427
421
|
intensity_masked = eem_nan_imputing(intensity_masked, ex_range, em_range, method=interpolation_method)
|
|
@@ -430,9 +424,10 @@ def eem_raman_masking(intensity, ex_range, em_range, width=5, interpolation_meth
|
|
|
430
424
|
return intensity_masked, raman_mask
|
|
431
425
|
|
|
432
426
|
|
|
433
|
-
def
|
|
434
|
-
|
|
435
|
-
|
|
427
|
+
def eem_rayleigh_scattering_removal(intensity, ex_range, em_range, width_o1=15, width_o2=15,
|
|
428
|
+
interpolation_dimension_o1='2d', interpolation_dimension_o2='2d',
|
|
429
|
+
interpolation_method_o1='zero',
|
|
430
|
+
interpolation_method_o2='linear'):
|
|
436
431
|
"""
|
|
437
432
|
Remove and interpolate the Rayleigh scattering.
|
|
438
433
|
|
|
@@ -448,11 +443,11 @@ def eem_rayleigh_masking(intensity, ex_range, em_range, width_o1=15, width_o2=15
|
|
|
448
443
|
The width or 1st order Rayleigh scattering.
|
|
449
444
|
width_o2: float
|
|
450
445
|
The width or 2nd order Rayleigh scattering.
|
|
451
|
-
|
|
446
|
+
interpolation_dimension_o1: str, {"1d-ex", "1d-em", "2d"}
|
|
452
447
|
The axis along which the 1st order Rayleigh scattering is interpolated. "ex": interpolation is conducted along
|
|
453
|
-
the excitation wavelength; "em": interpolation is conducted along the emission wavelength; "
|
|
448
|
+
the excitation wavelength; "em": interpolation is conducted along the emission wavelength; "2d": interpolation
|
|
454
449
|
is conducted on the 2D grid of both excitation and emission wavelengths.
|
|
455
|
-
|
|
450
|
+
interpolation_dimension_o2: str, {"1d-ex", "1d-em", "2d"}
|
|
456
451
|
The axis along which the 2nd order Rayleigh scattering is interpolated.
|
|
457
452
|
interpolation_method_o1: str, {"linear", "cubic", "nan"}
|
|
458
453
|
The method used to interpolate the 1st order Rayleigh scattering.
|
|
@@ -473,6 +468,9 @@ def eem_rayleigh_masking(intensity, ex_range, em_range, width_o1=15, width_o2=15
|
|
|
473
468
|
intensity_masked = np.array(intensity)
|
|
474
469
|
rayleigh_mask_o1 = np.ones(intensity.shape)
|
|
475
470
|
rayleigh_mask_o2 = np.ones(intensity.shape)
|
|
471
|
+
# convert the entire width to half-width
|
|
472
|
+
width_o1 = width_o1 / 2
|
|
473
|
+
width_o2 = width_o2 / 2
|
|
476
474
|
lambda_em_o1 = ex_range
|
|
477
475
|
tol_emidx_o1 = int(np.round(width_o1 / (em_range[1] - em_range[0])))
|
|
478
476
|
for s in range(0, intensity_masked.shape[0]):
|
|
@@ -480,13 +478,14 @@ def eem_rayleigh_masking(intensity, ex_range, em_range, width_o1=15, width_o2=15
|
|
|
480
478
|
if lambda_em_o1[s] <= em_range[0] <= lambda_em_o1[s] + width_o1:
|
|
481
479
|
emidx = dichotomy_search(em_range, lambda_em_o1[s] + width_o1)
|
|
482
480
|
rayleigh_mask_o1[exidx, 0:emidx + 1] = 0
|
|
483
|
-
elif lambda_em_o1[s] - width_o1 <= em_range[0]:
|
|
481
|
+
elif lambda_em_o1[s] - width_o1 <= em_range[0] < lambda_em_o1[s]:
|
|
484
482
|
emidx = dichotomy_search(em_range, lambda_em_o1[s])
|
|
485
483
|
rayleigh_mask_o1[exidx, 0: emidx + tol_emidx_o1 + 1] = 0
|
|
486
|
-
|
|
487
|
-
emidx = dichotomy_search(em_range, lambda_em_o1[s])
|
|
488
|
-
rayleigh_mask_o1[exidx, emidx: emidx + tol_emidx_o1 + 1] = 0
|
|
484
|
+
elif em_range[0] < lambda_em_o1[s] - width_o1 < em_range[-1]:
|
|
485
|
+
emidx = dichotomy_search(em_range, lambda_em_o1[s] - width_o1)
|
|
486
|
+
rayleigh_mask_o1[exidx, emidx: min(em_range.shape[0], emidx + 2 * tol_emidx_o1 + 1)] = 0
|
|
489
487
|
intensity_masked[exidx, 0: emidx] = 0
|
|
488
|
+
|
|
490
489
|
lambda_em_o2 = ex_range * 2
|
|
491
490
|
tol_emidx_o2 = int(np.round(width_o2 / (em_range[1] - em_range[0])))
|
|
492
491
|
for s in range(0, intensity_masked.shape[0]):
|
|
@@ -494,22 +493,24 @@ def eem_rayleigh_masking(intensity, ex_range, em_range, width_o1=15, width_o2=15
|
|
|
494
493
|
if lambda_em_o2[s] <= em_range[0] <= lambda_em_o2[s] + width_o2:
|
|
495
494
|
emidx = dichotomy_search(em_range, lambda_em_o2[s] + width_o2)
|
|
496
495
|
rayleigh_mask_o2[exidx, 0:emidx + 1] = 0
|
|
497
|
-
elif lambda_em_o2[s] - width_o2 <= em_range[0]:
|
|
496
|
+
elif lambda_em_o2[s] - width_o2 <= em_range[0] < lambda_em_o2[s]:
|
|
498
497
|
emidx = dichotomy_search(em_range, lambda_em_o2[s])
|
|
499
498
|
rayleigh_mask_o2[exidx, 0: emidx + tol_emidx_o2 + 1] = 0
|
|
500
|
-
|
|
499
|
+
elif em_range[0] < lambda_em_o2[s] - width_o2 <= em_range[-1]:
|
|
501
500
|
emidx = dichotomy_search(em_range, lambda_em_o2[s] - width_o2)
|
|
502
|
-
rayleigh_mask_o2[exidx, emidx: emidx + 2 * tol_emidx_o2 + 1] = 0
|
|
501
|
+
rayleigh_mask_o2[exidx, emidx: min(em_range.shape[0], emidx + 2 * tol_emidx_o2 + 1)] = 0
|
|
503
502
|
|
|
504
|
-
for axis, itp, mask in zip([
|
|
503
|
+
for axis, itp, mask in zip([interpolation_dimension_o1, interpolation_dimension_o2],
|
|
505
504
|
[interpolation_method_o1, interpolation_method_o2],
|
|
506
505
|
[rayleigh_mask_o1, rayleigh_mask_o2]):
|
|
507
506
|
if itp == 'zero':
|
|
508
507
|
intensity_masked[np.where(mask == 0)] = 0
|
|
509
508
|
elif itp == 'nan':
|
|
510
509
|
intensity_masked[np.where(mask == 0)] = np.nan
|
|
510
|
+
elif itp == 'none':
|
|
511
|
+
pass
|
|
511
512
|
else:
|
|
512
|
-
if axis == 'ex':
|
|
513
|
+
if axis == '1d-ex':
|
|
513
514
|
for j in range(0, intensity.shape[1]):
|
|
514
515
|
try:
|
|
515
516
|
x = np.flipud(ex_range)[np.where(mask[:, j] == 1)]
|
|
@@ -519,7 +520,7 @@ def eem_rayleigh_masking(intensity, ex_range, em_range, width_o1=15, width_o2=15
|
|
|
519
520
|
intensity_masked[:, j] = y_predict
|
|
520
521
|
except ValueError:
|
|
521
522
|
continue
|
|
522
|
-
if axis == 'em':
|
|
523
|
+
if axis == '1d-em':
|
|
523
524
|
for i in range(0, intensity.shape[0]):
|
|
524
525
|
try:
|
|
525
526
|
x = em_range[np.where(mask[i, :] == 1)]
|
|
@@ -529,18 +530,19 @@ def eem_rayleigh_masking(intensity, ex_range, em_range, width_o1=15, width_o2=15
|
|
|
529
530
|
intensity_masked[i, :] = y_predict
|
|
530
531
|
except ValueError:
|
|
531
532
|
continue
|
|
532
|
-
if axis == '
|
|
533
|
+
if axis == '2d':
|
|
533
534
|
old_nan = np.isnan(intensity)
|
|
534
535
|
old_nan_o1 = np.isnan(intensity_masked)
|
|
535
536
|
intensity_masked[np.where(mask == 0)] = np.nan
|
|
536
|
-
intensity_masked = eem_nan_imputing(intensity_masked, ex_range, em_range, method=itp,
|
|
537
|
+
intensity_masked = eem_nan_imputing(intensity_masked, ex_range, em_range, method=itp,
|
|
538
|
+
fill_value='linear_ex')
|
|
537
539
|
# restore the nan values in non-raman-scattering region
|
|
538
540
|
intensity_masked[old_nan] = np.nan
|
|
539
541
|
intensity_masked[old_nan_o1] = np.nan
|
|
540
542
|
return intensity_masked, rayleigh_mask_o1, rayleigh_mask_o2
|
|
541
543
|
|
|
542
544
|
|
|
543
|
-
def eem_ife_correction(intensity,
|
|
545
|
+
def eem_ife_correction(intensity, ex_range_eem, em_range_eem, absorbance, ex_range_abs):
|
|
544
546
|
"""
|
|
545
547
|
Correct the inner filter effect (IFE).
|
|
546
548
|
|
|
@@ -548,31 +550,29 @@ def eem_ife_correction(intensity, ex_range, em_range, absorbance, ex_range_abs,
|
|
|
548
550
|
----------
|
|
549
551
|
intensity: np.ndarray (2d)
|
|
550
552
|
The EEM.
|
|
551
|
-
|
|
553
|
+
ex_range_eem: np.ndarray (1d)
|
|
552
554
|
The excitation wavelengths of EEM.
|
|
553
|
-
|
|
555
|
+
em_range_eem: np.ndarray (1d)
|
|
554
556
|
The emission wavelengths of EEM.
|
|
555
557
|
absorbance: np.ndarray
|
|
556
|
-
The absorbance. If this function is called
|
|
557
|
-
length of the absorbance spectrum. If this function is called
|
|
558
|
+
The absorbance. If this function is called by itself, an array of shape (i, ) should be passed, where i is the
|
|
559
|
+
length of the absorbance spectrum. If this function is called by process_eem_stack(), an array of shape (n, i)
|
|
558
560
|
should be passed, where n is the number samples, and i is the length of the absorbance spectrum.
|
|
559
561
|
ex_range_abs: np.ndarray (1d)
|
|
560
562
|
The excitation wavelengths of absorbance.
|
|
561
|
-
cuvette_length: float
|
|
562
|
-
The length of cuvette in measurement.
|
|
563
563
|
|
|
564
564
|
Returns
|
|
565
565
|
-------
|
|
566
566
|
intensity_corrected: np.ndarray
|
|
567
567
|
The corrected EEM.
|
|
568
568
|
"""
|
|
569
|
-
if absorbance.ndim == 1:
|
|
570
|
-
|
|
569
|
+
# if absorbance.ndim == 1:
|
|
570
|
+
# absorbance_reshape = absorbance.reshape((1, absorbance.shape[0]))
|
|
571
571
|
f1 = interp1d(ex_range_abs, absorbance, kind='linear', bounds_error=False, fill_value='extrapolate')
|
|
572
|
-
absorbance_ex = np.fliplr(np.array([f1(
|
|
573
|
-
absorbance_em = np.array([f1(
|
|
574
|
-
ife_factors = 10 ** (
|
|
575
|
-
|
|
572
|
+
absorbance_ex = np.fliplr(np.array([f1(ex_range_eem)]))
|
|
573
|
+
absorbance_em = np.array([f1(em_range_eem)])
|
|
574
|
+
ife_factors = 10 ** ((absorbance_ex.T.dot(np.ones(absorbance_em.shape)) +
|
|
575
|
+
np.ones(absorbance_ex.shape).T.dot(absorbance_em)) / 2)
|
|
576
576
|
intensity_corrected = intensity * ife_factors
|
|
577
577
|
return intensity_corrected
|
|
578
578
|
|
|
@@ -605,9 +605,15 @@ def eem_regional_integration(intensity, ex_range, em_range, ex_min, ex_max, em_m
|
|
|
605
605
|
avg_regional_intensity:
|
|
606
606
|
The average fluorescence intensity in the region.
|
|
607
607
|
"""
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
608
|
+
|
|
609
|
+
ex_range_interpolated = np.sort(np.unique(np.concatenate([ex_range, [ex_min, ex_max]])))
|
|
610
|
+
em_range_interpolated = np.sort(np.unique(np.concatenate([em_range, [em_min, em_max]])))
|
|
611
|
+
intensity_interpolated = eem_interpolation(intensity, ex_range, em_range, ex_range_interpolated,
|
|
612
|
+
em_range_interpolated, method='linear')
|
|
613
|
+
intensity_cut, ex_range_cut, em_range_cut = eem_cutting(intensity_interpolated, ex_range_interpolated,
|
|
614
|
+
em_range_interpolated,
|
|
615
|
+
ex_min_new=ex_min, ex_max_new=ex_max,
|
|
616
|
+
em_min_new=ex_min, em_max_new=em_max)
|
|
611
617
|
if intensity_cut.shape[0] == 1:
|
|
612
618
|
integration = np.trapz(intensity_cut, em_range_cut, axis=1)
|
|
613
619
|
elif intensity_cut.shape[1] == 1:
|
|
@@ -647,12 +653,9 @@ def eem_interpolation(intensity, ex_range_old, em_range_old, ex_range_new, em_ra
|
|
|
647
653
|
intensity_interpolated: np.ndarray
|
|
648
654
|
The interpolated EEM.
|
|
649
655
|
"""
|
|
650
|
-
interp = RegularGridInterpolator((ex_range_old[::-1], em_range_old), intensity, method=method)
|
|
651
|
-
x, y = np.meshgrid(
|
|
652
|
-
|
|
653
|
-
yy = y.flatten()
|
|
654
|
-
coordinates_new = np.concatenate([xx[:, np.newaxis], yy[:, np.newaxis]], axis=1)
|
|
655
|
-
intensity_interpolated = interp(coordinates_new).reshape(ex_range_new.shape[0], em_range_new.shape[0])
|
|
656
|
+
interp = RegularGridInterpolator((ex_range_old[::-1], em_range_old), intensity, method=method, bounds_error=False)
|
|
657
|
+
x, y = np.meshgrid(ex_range_new[::-1], em_range_new, indexing='ij')
|
|
658
|
+
intensity_interpolated = interp((x, y)).reshape(ex_range_new.shape[0], em_range_new.shape[0])
|
|
656
659
|
return intensity_interpolated
|
|
657
660
|
|
|
658
661
|
|
|
@@ -740,8 +743,8 @@ def eems_tf_normalization(intensity):
|
|
|
740
743
|
# return label
|
|
741
744
|
|
|
742
745
|
|
|
743
|
-
def eems_fit_components(eem_stack, component_stack, fit_intercept=False):
|
|
744
|
-
assert eem_stack.shape[1:] == component_stack.shape, "EEM and component have different shapes"
|
|
746
|
+
def eems_fit_components(eem_stack, component_stack, fit_intercept=False, positive=False):
|
|
747
|
+
assert eem_stack.shape[1:] == component_stack.shape[1:], "EEM and component have different shapes"
|
|
745
748
|
score_sample = []
|
|
746
749
|
fmax_sample = []
|
|
747
750
|
max_values = np.amax(component_stack, axis=(1, 2))
|
|
@@ -749,7 +752,7 @@ def eems_fit_components(eem_stack, component_stack, fit_intercept=False):
|
|
|
749
752
|
for i in range(eem_stack.shape[0]):
|
|
750
753
|
y_true = eem_stack[i].reshape([-1])
|
|
751
754
|
x = component_stack.reshape([component_stack.shape[0], -1]).T
|
|
752
|
-
reg = LinearRegression(fit_intercept=fit_intercept)
|
|
755
|
+
reg = LinearRegression(fit_intercept=fit_intercept, positive=positive)
|
|
753
756
|
reg.fit(x, y_true)
|
|
754
757
|
y_pred = reg.predict(x)
|
|
755
758
|
eem_stack_pred[i, :, :] = y_pred.reshape((eem_stack.shape[1], eem_stack.shape[2]))
|
|
@@ -808,6 +811,12 @@ class EEMDataset:
|
|
|
808
811
|
self.index = index
|
|
809
812
|
self.extent = (self.em_range.min(), self.em_range.max(), self.ex_range.min(), self.ex_range.max())
|
|
810
813
|
|
|
814
|
+
def to_json_serializable(self):
|
|
815
|
+
self.eem_stack = self.eem_stack.tolist()
|
|
816
|
+
self.ex_range = self.ex_range.tolist()
|
|
817
|
+
self.em_range = self.em_range.tolist()
|
|
818
|
+
self.ref = self.ref.tolist() if self.ref else None
|
|
819
|
+
|
|
811
820
|
# --------------------EEM dataset features--------------------
|
|
812
821
|
def zscore(self):
|
|
813
822
|
"""
|
|
@@ -919,10 +928,15 @@ class EEMDataset:
|
|
|
919
928
|
em_actual = self.em_range[em_idx]
|
|
920
929
|
return fi, ex_actual, em_actual
|
|
921
930
|
|
|
922
|
-
def correlation(self):
|
|
931
|
+
def correlation(self, fit_intercept=True):
|
|
923
932
|
"""
|
|
924
933
|
Analyze the correlation between reference and fluorescence intensity at each pair of ex/em.
|
|
925
934
|
|
|
935
|
+
Params
|
|
936
|
+
-------
|
|
937
|
+
fit_intercept: bool, optional
|
|
938
|
+
Whether to fit the intercept for linear regression.
|
|
939
|
+
|
|
926
940
|
Returns
|
|
927
941
|
-------
|
|
928
942
|
corr_dict: dict
|
|
@@ -931,20 +945,28 @@ class EEMDataset:
|
|
|
931
945
|
m = self.eem_stack
|
|
932
946
|
x = self.ref
|
|
933
947
|
x = x.reshape(m.shape[0], 1)
|
|
934
|
-
w
|
|
948
|
+
w = np.full((m.shape[1], m.shape[2]), fill_value=np.nan)
|
|
949
|
+
b = np.full((m.shape[1], m.shape[2]), fill_value=np.nan)
|
|
950
|
+
r2 = np.full((m.shape[1], m.shape[2]), fill_value=np.nan)
|
|
951
|
+
pc = np.full((m.shape[1], m.shape[2]), fill_value=np.nan)
|
|
952
|
+
pc_p = np.full((m.shape[1], m.shape[2]), fill_value=np.nan)
|
|
953
|
+
sc = np.full((m.shape[1], m.shape[2]), fill_value=np.nan)
|
|
954
|
+
sc_p = np.full((m.shape[1], m.shape[2]), fill_value=np.nan)
|
|
935
955
|
e = np.full(m.shape, fill_value=np.nan)
|
|
936
956
|
for i in range(m.shape[1]):
|
|
937
957
|
for j in range(m.shape[2]):
|
|
938
958
|
try:
|
|
939
959
|
y = (m[:, i, j])
|
|
940
|
-
reg = LinearRegression()
|
|
960
|
+
reg = LinearRegression(fit_intercept=fit_intercept)
|
|
941
961
|
reg.fit(x, y)
|
|
942
962
|
w[i, j] = reg.coef_
|
|
943
963
|
b[i, j] = reg.intercept_
|
|
944
964
|
r2[i, j] = reg.score(x, y)
|
|
945
965
|
e[:, i, j] = reg.predict(x) - y
|
|
946
|
-
pc[i, j]
|
|
947
|
-
|
|
966
|
+
pc[i, j] = stats.pearsonr(x.reshape(-1), y).statistic
|
|
967
|
+
pc_p[i, j] = stats.pearsonr(x.reshape(-1), y).pvalue
|
|
968
|
+
sc[i, j] = stats.spearmanr(x.reshape(-1), y).statistic
|
|
969
|
+
sc_p[i, j] = stats.spearmanr(x.reshape(-1), y).pvalue
|
|
948
970
|
except ValueError:
|
|
949
971
|
pass
|
|
950
972
|
corr_dict = {'slope': w, 'intercept': b, 'r_square': r2, 'linear regression residual': e,
|
|
@@ -984,7 +1006,7 @@ class EEMDataset:
|
|
|
984
1006
|
|
|
985
1007
|
Parameters
|
|
986
1008
|
----------
|
|
987
|
-
sigma, truncate
|
|
1009
|
+
sigma, truncate:
|
|
988
1010
|
See eempy.eem_processing.eem_gaussian_filter
|
|
989
1011
|
copy: bool
|
|
990
1012
|
if False, overwrite the EEMDataset object with the processed EEMs.
|
|
@@ -1005,7 +1027,7 @@ class EEMDataset:
|
|
|
1005
1027
|
|
|
1006
1028
|
Parameters
|
|
1007
1029
|
----------
|
|
1008
|
-
ex_min, ex_max, em_min, em_max, fill_value
|
|
1030
|
+
ex_min, ex_max, em_min, em_max, fill_value:
|
|
1009
1031
|
See eempy.eem_processing.eem_region_masking
|
|
1010
1032
|
copy: bool
|
|
1011
1033
|
if False, overwrite the EEMDataset object with the processed EEMs.
|
|
@@ -1015,9 +1037,11 @@ class EEMDataset:
|
|
|
1015
1037
|
eem_stack_masked: np.ndarray
|
|
1016
1038
|
The masked EEM.
|
|
1017
1039
|
"""
|
|
1018
|
-
eem_stack_masked, _ = process_eem_stack(
|
|
1019
|
-
|
|
1020
|
-
|
|
1040
|
+
eem_stack_masked, _ = process_eem_stack(
|
|
1041
|
+
self.eem_stack, eem_region_masking, ex_range=self.ex_range,
|
|
1042
|
+
em_range=self.em_range, ex_min=ex_min, ex_max=ex_max, em_min=em_min,
|
|
1043
|
+
em_max=em_max, fill_value=fill_value
|
|
1044
|
+
)
|
|
1021
1045
|
if not copy:
|
|
1022
1046
|
self.eem_stack = eem_stack_masked
|
|
1023
1047
|
return eem_stack_masked
|
|
@@ -1028,7 +1052,7 @@ class EEMDataset:
|
|
|
1028
1052
|
|
|
1029
1053
|
Parameters
|
|
1030
1054
|
----------
|
|
1031
|
-
ex_min, ex_max, em_min, em_max
|
|
1055
|
+
ex_min, ex_max, em_min, em_max:
|
|
1032
1056
|
See eempy.eem_processing.eem_cutting
|
|
1033
1057
|
copy: bool
|
|
1034
1058
|
if False, overwrite the EEMDataset object with the processed EEMs.
|
|
@@ -1042,9 +1066,12 @@ class EEMDataset:
|
|
|
1042
1066
|
em_range_cut:np.ndarray
|
|
1043
1067
|
The cut em wavelengths.
|
|
1044
1068
|
"""
|
|
1045
|
-
eem_stack_cut, new_ranges = process_eem_stack(
|
|
1046
|
-
|
|
1047
|
-
|
|
1069
|
+
eem_stack_cut, new_ranges = process_eem_stack(
|
|
1070
|
+
self.eem_stack, eem_cutting, ex_range_old=self.ex_range,
|
|
1071
|
+
em_range_old=self.em_range,
|
|
1072
|
+
ex_min_new=ex_min, ex_max_new=ex_max, em_min_new=em_min,
|
|
1073
|
+
em_max_new=em_max
|
|
1074
|
+
)
|
|
1048
1075
|
if not copy:
|
|
1049
1076
|
self.eem_stack = eem_stack_cut
|
|
1050
1077
|
self.ex_range = new_ranges[0][0]
|
|
@@ -1074,7 +1101,7 @@ class EEMDataset:
|
|
|
1074
1101
|
return eem_stack_imputed
|
|
1075
1102
|
|
|
1076
1103
|
def raman_normalization(self, ex_range_blank=None, em_range_blank=None, blank=None, from_blank=False,
|
|
1077
|
-
integration_time=1,
|
|
1104
|
+
integration_time=1, ex_target=350, bandwidth=5,
|
|
1078
1105
|
rsu_standard=20000, manual_rsu=1, copy=True):
|
|
1079
1106
|
"""
|
|
1080
1107
|
Normalize the EEM using the Raman scattering unit (RSU) given directly or calculated from a blank EEM.
|
|
@@ -1082,7 +1109,7 @@ class EEMDataset:
|
|
|
1082
1109
|
|
|
1083
1110
|
Parameters
|
|
1084
1111
|
----------
|
|
1085
|
-
blank, ex_range_blank, em_range_blank, from_blank, integration_time,
|
|
1112
|
+
blank, ex_range_blank, em_range_blank, from_blank, integration_time, ex_target, bandwidth
|
|
1086
1113
|
See eempy.eem_processing.eem_raman_normalization
|
|
1087
1114
|
rsu_standard, manual_rsu
|
|
1088
1115
|
See eempy.eem_processing.eem_raman_normalization
|
|
@@ -1094,14 +1121,15 @@ class EEMDataset:
|
|
|
1094
1121
|
eem_stack_normalized: np.ndarray
|
|
1095
1122
|
The normalized EEM.
|
|
1096
1123
|
"""
|
|
1097
|
-
eem_stack_normalized = process_eem_stack(
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1124
|
+
eem_stack_normalized, rsu = process_eem_stack(
|
|
1125
|
+
self.eem_stack, eem_raman_normalization, ex_range_blank=ex_range_blank,
|
|
1126
|
+
em_range_blank=em_range_blank, blank=blank, from_blank=from_blank,
|
|
1127
|
+
integration_time=integration_time, ex_target=ex_target,
|
|
1128
|
+
bandwidth=bandwidth, rsu_standard=rsu_standard, manual_rsu=manual_rsu
|
|
1129
|
+
)
|
|
1102
1130
|
if not copy:
|
|
1103
1131
|
self.eem_stack = eem_stack_normalized
|
|
1104
|
-
return eem_stack_normalized
|
|
1132
|
+
return eem_stack_normalized, rsu
|
|
1105
1133
|
|
|
1106
1134
|
def tf_normalization(self, copy=True):
|
|
1107
1135
|
"""
|
|
@@ -1124,14 +1152,14 @@ class EEMDataset:
|
|
|
1124
1152
|
self.eem_stack = eem_stack_normalized
|
|
1125
1153
|
return eem_stack_normalized, weights
|
|
1126
1154
|
|
|
1127
|
-
def
|
|
1155
|
+
def raman_scattering_removal(self, width=5, interpolation_method='linear', interpolation_dimension='2d', copy=True):
|
|
1128
1156
|
"""
|
|
1129
1157
|
Remove and interpolate the Raman scattering.
|
|
1130
1158
|
|
|
1131
1159
|
Parameters
|
|
1132
1160
|
----------
|
|
1133
|
-
width, interpolation_method,
|
|
1134
|
-
See eempy.eem_processing.
|
|
1161
|
+
width, interpolation_method, interpolation_dimension:
|
|
1162
|
+
See eempy.eem_processing.eem_raman_scattering_removal
|
|
1135
1163
|
copy: bool
|
|
1136
1164
|
if False, overwrite the EEMDataset object with the processed EEMs.
|
|
1137
1165
|
|
|
@@ -1140,23 +1168,26 @@ class EEMDataset:
|
|
|
1140
1168
|
eem_stack_masked: np.ndarray
|
|
1141
1169
|
The EEM with Raman scattering interpolated.
|
|
1142
1170
|
"""
|
|
1143
|
-
eem_stack_masked, _ = process_eem_stack(
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1171
|
+
eem_stack_masked, _ = process_eem_stack(
|
|
1172
|
+
self.eem_stack, eem_raman_scattering_removal, ex_range=self.ex_range,
|
|
1173
|
+
em_range=self.em_range, width=width,
|
|
1174
|
+
interpolation_method=interpolation_method,
|
|
1175
|
+
interpolation_dimension=interpolation_dimension
|
|
1176
|
+
)
|
|
1147
1177
|
if not copy:
|
|
1148
1178
|
self.eem_stack = eem_stack_masked
|
|
1149
1179
|
return eem_stack_masked
|
|
1150
1180
|
|
|
1151
|
-
def
|
|
1152
|
-
|
|
1181
|
+
def rayleigh_scattering_removal(self, width_o1=15, width_o2=15, interpolation_dimension_o1='2d',
|
|
1182
|
+
interpolation_dimension_o2='2d', interpolation_method_o1='zero',
|
|
1183
|
+
interpolation_method_o2='linear', copy=True):
|
|
1153
1184
|
"""
|
|
1154
1185
|
Remove and interpolate the Rayleigh scattering.
|
|
1155
1186
|
|
|
1156
1187
|
Parameters
|
|
1157
1188
|
----------
|
|
1158
|
-
width_o1, width_o2,
|
|
1159
|
-
See eempy.eem_processing.
|
|
1189
|
+
width_o1, width_o2, interpolation_dimension_o1, interpolation_dimension_o2, interpolation_method_o1, interpolation_method_o2:
|
|
1190
|
+
See eempy.eem_processing.eem_rayleigh_scattering_removal
|
|
1160
1191
|
copy: bool
|
|
1161
1192
|
if False, overwrite the EEMDataset object with the processed EEMs.
|
|
1162
1193
|
|
|
@@ -1165,23 +1196,26 @@ class EEMDataset:
|
|
|
1165
1196
|
eem_stack_masked: np.ndarray
|
|
1166
1197
|
The EEM with Rayleigh scattering interpolated.
|
|
1167
1198
|
"""
|
|
1168
|
-
eem_stack_masked, _ = process_eem_stack(
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1199
|
+
eem_stack_masked, _ = process_eem_stack(
|
|
1200
|
+
self.eem_stack, eem_rayleigh_scattering_removal, ex_range=self.ex_range,
|
|
1201
|
+
em_range=self.em_range, width_o1=width_o1,
|
|
1202
|
+
width_o2=width_o2,
|
|
1203
|
+
interpolation_dimension_o1=interpolation_dimension_o1,
|
|
1204
|
+
interpolation_dimension_o2=interpolation_dimension_o2,
|
|
1205
|
+
interpolation_method_o1=interpolation_method_o1,
|
|
1206
|
+
interpolation_method_o2=interpolation_method_o2
|
|
1207
|
+
)
|
|
1174
1208
|
if not copy:
|
|
1175
1209
|
self.eem_stack = eem_stack_masked
|
|
1176
1210
|
return eem_stack_masked
|
|
1177
1211
|
|
|
1178
|
-
def ife_correction(self, absorbance, ex_range_abs,
|
|
1212
|
+
def ife_correction(self, absorbance, ex_range_abs, copy=True):
|
|
1179
1213
|
"""
|
|
1180
1214
|
Correct the inner filter effect (IFE).
|
|
1181
1215
|
|
|
1182
1216
|
Parameters
|
|
1183
1217
|
----------
|
|
1184
|
-
absorbance, ex_range_abs
|
|
1218
|
+
absorbance, ex_range_abs:
|
|
1185
1219
|
See eempy.eem_processing.eem_ife_correction
|
|
1186
1220
|
copy: bool
|
|
1187
1221
|
if False, overwrite the EEMDataset object with the processed EEMs.
|
|
@@ -1191,9 +1225,11 @@ class EEMDataset:
|
|
|
1191
1225
|
eem_stack_corrected: np.ndarray
|
|
1192
1226
|
The corrected EEM.
|
|
1193
1227
|
"""
|
|
1194
|
-
eem_stack_corrected = process_eem_stack(
|
|
1195
|
-
|
|
1196
|
-
|
|
1228
|
+
eem_stack_corrected = process_eem_stack(
|
|
1229
|
+
self.eem_stack, eem_ife_correction, ex_range_eem=self.ex_range,
|
|
1230
|
+
em_range_eem=self.em_range, absorbance=absorbance,
|
|
1231
|
+
ex_range_abs=ex_range_abs
|
|
1232
|
+
)
|
|
1197
1233
|
if not copy:
|
|
1198
1234
|
self.eem_stack = eem_stack_corrected
|
|
1199
1235
|
return eem_stack_corrected
|
|
@@ -1206,7 +1242,7 @@ class EEMDataset:
|
|
|
1206
1242
|
|
|
1207
1243
|
Parameters
|
|
1208
1244
|
----------
|
|
1209
|
-
ex_range_new, em_range_new, method
|
|
1245
|
+
ex_range_new, em_range_new, method:
|
|
1210
1246
|
See eempy.eem_processing.eem_interpolation
|
|
1211
1247
|
copy: bool
|
|
1212
1248
|
if False, overwrite the EEMDataset object with the processed EEMs.
|
|
@@ -1216,9 +1252,11 @@ class EEMDataset:
|
|
|
1216
1252
|
eem_stack_interpolated: np.ndarray
|
|
1217
1253
|
The interpolated EEM.
|
|
1218
1254
|
"""
|
|
1219
|
-
eem_stack_interpolated = process_eem_stack(
|
|
1220
|
-
|
|
1221
|
-
|
|
1255
|
+
eem_stack_interpolated = process_eem_stack(
|
|
1256
|
+
self.eem_stack, eem_interpolation, ex_range_old=self.ex_range,
|
|
1257
|
+
em_range_old=self.em_range, ex_range_new=ex_range_new,
|
|
1258
|
+
em_range_new=em_range_new, method=method
|
|
1259
|
+
)
|
|
1222
1260
|
if not copy:
|
|
1223
1261
|
self.eem_stack = eem_stack_interpolated
|
|
1224
1262
|
self.ex_range = ex_range_new
|
|
@@ -1326,6 +1364,14 @@ class EEMDataset:
|
|
|
1326
1364
|
return eem_stack_new, index_new, ref_new, selected_indices
|
|
1327
1365
|
|
|
1328
1366
|
def sort_by_index(self):
|
|
1367
|
+
"""
|
|
1368
|
+
Sort the sample order of eem_stack, index and reference (if exists) by the index.
|
|
1369
|
+
|
|
1370
|
+
Returns
|
|
1371
|
+
-------
|
|
1372
|
+
sorted_indices: np.ndarray
|
|
1373
|
+
The sorted sample order
|
|
1374
|
+
"""
|
|
1329
1375
|
sorted_indices = np.argsort(self.index)
|
|
1330
1376
|
self.eem_stack = self.eem_stack[sorted_indices]
|
|
1331
1377
|
if self.ref:
|
|
@@ -1334,13 +1380,31 @@ class EEMDataset:
|
|
|
1334
1380
|
return sorted_indices
|
|
1335
1381
|
|
|
1336
1382
|
def sort_by_ref(self):
|
|
1383
|
+
"""
|
|
1384
|
+
Sort the sample order of eem_stack, reference and index (if exists) by the reference.
|
|
1385
|
+
|
|
1386
|
+
Returns
|
|
1387
|
+
-------
|
|
1388
|
+
sorted_indices: np.ndarray
|
|
1389
|
+
The sorted sample order
|
|
1390
|
+
"""
|
|
1337
1391
|
sorted_indices = np.argsort(self.ref)
|
|
1338
1392
|
self.eem_stack = self.eem_stack[sorted_indices]
|
|
1339
1393
|
if self.index:
|
|
1340
1394
|
self.index = self.index[sorted_indices]
|
|
1341
|
-
self.ref = sorted(self.ref)
|
|
1395
|
+
self.ref = np.array(sorted(self.ref))
|
|
1342
1396
|
return sorted_indices
|
|
1343
1397
|
|
|
1398
|
+
def filter_by_index(self, keyword):
|
|
1399
|
+
"""
|
|
1400
|
+
Select the samples whose indexes contain the given keyword.
|
|
1401
|
+
|
|
1402
|
+
Returns
|
|
1403
|
+
-------
|
|
1404
|
+
|
|
1405
|
+
"""
|
|
1406
|
+
return
|
|
1407
|
+
|
|
1344
1408
|
|
|
1345
1409
|
def combine_eem_datasets(list_eem_datasets):
|
|
1346
1410
|
"""
|
|
@@ -1363,8 +1427,11 @@ def combine_eem_datasets(list_eem_datasets):
|
|
|
1363
1427
|
em_range_0 = list_eem_datasets[0].em_range
|
|
1364
1428
|
for d in list_eem_datasets:
|
|
1365
1429
|
eem_stack_combined.append(d.eem_stack)
|
|
1366
|
-
ref_combined.append(d.ref)
|
|
1367
|
-
|
|
1430
|
+
ref_combined.append(d.ref if d.ref else np.array(d.eem_stack.shape[0] * [np.nan]))
|
|
1431
|
+
if d.index:
|
|
1432
|
+
index_combined = index_combined + d.index
|
|
1433
|
+
else:
|
|
1434
|
+
index_combined = index_combined + ['N/A' for i in range(d.eem_stack.shape[0])]
|
|
1368
1435
|
if not np.array_equal(d.ex_range, ex_range_0) or not np.array_equal(d.em_range, em_range_0):
|
|
1369
1436
|
warnings.warn(
|
|
1370
1437
|
'ex_range and em_range of the datasets must be identical. If you want to combine EEM datasets '
|
|
@@ -1409,7 +1476,7 @@ class PARAFAC:
|
|
|
1409
1476
|
fmax: pandas.DataFrame
|
|
1410
1477
|
Fmax table.
|
|
1411
1478
|
component_stack: np.ndarray
|
|
1412
|
-
PARAFAC
|
|
1479
|
+
PARAFAC components.
|
|
1413
1480
|
cptensors: tensorly CPTensor
|
|
1414
1481
|
The output of PARAFAC in the form of tensorly CPTensor.
|
|
1415
1482
|
eem_stack_train: np.ndarray
|
|
@@ -1466,9 +1533,9 @@ class PARAFAC:
|
|
|
1466
1533
|
if not self.non_negativity:
|
|
1467
1534
|
if np.isnan(eem_dataset.eem_stack).any():
|
|
1468
1535
|
mask = np.where(np.isnan(eem_dataset.eem_stack), 0, 1)
|
|
1469
|
-
cptensors
|
|
1536
|
+
cptensors = parafac(eem_dataset.eem_stack, rank=self.rank, mask=mask, init=self.init)
|
|
1470
1537
|
else:
|
|
1471
|
-
cptensors
|
|
1538
|
+
cptensors = parafac(eem_dataset.eem_stack, rank=self.rank, init=self.init)
|
|
1472
1539
|
else:
|
|
1473
1540
|
if np.isnan(eem_dataset.eem_stack).any():
|
|
1474
1541
|
mask = np.where(np.isnan(eem_dataset.eem_stack), 0, 1)
|
|
@@ -1517,7 +1584,7 @@ class PARAFAC:
|
|
|
1517
1584
|
ex_loadings = pd.DataFrame(np.flipud(b), index=eem_dataset.ex_range)
|
|
1518
1585
|
em_loadings = pd.DataFrame(c, index=eem_dataset.em_range)
|
|
1519
1586
|
if self.sort_em:
|
|
1520
|
-
em_peaks = [c
|
|
1587
|
+
em_peaks = [c for c in em_loadings.idxmax()]
|
|
1521
1588
|
peak_rank = list(enumerate(stats.rankdata(em_peaks)))
|
|
1522
1589
|
order = [i[0] for i in sorted(peak_rank, key=lambda x: x[1])]
|
|
1523
1590
|
component_stack = component_stack[order]
|
|
@@ -1633,7 +1700,7 @@ class PARAFAC:
|
|
|
1633
1700
|
|
|
1634
1701
|
Returns
|
|
1635
1702
|
-------
|
|
1636
|
-
|
|
1703
|
+
cc: float
|
|
1637
1704
|
core consistency
|
|
1638
1705
|
"""
|
|
1639
1706
|
cc = core_consistency(self.cptensors, self.eem_stack_train)
|
|
@@ -1656,13 +1723,16 @@ class PARAFAC:
|
|
|
1656
1723
|
"""
|
|
1657
1724
|
if mode == 'ex':
|
|
1658
1725
|
lvr = compute_leverage(self.ex_loadings)
|
|
1726
|
+
lvr.columns = ['leverage-ex']
|
|
1659
1727
|
elif mode == 'em':
|
|
1660
1728
|
lvr = compute_leverage(self.em_loadings)
|
|
1729
|
+
lvr.columns = ['leverage-em']
|
|
1661
1730
|
elif mode == 'sample':
|
|
1662
1731
|
lvr = compute_leverage(self.score)
|
|
1732
|
+
lvr.columns = ['leverage-sample']
|
|
1663
1733
|
else:
|
|
1664
1734
|
raise ValueError("'mode' should be 'ex' or 'em' or 'sample'.")
|
|
1665
|
-
lvr.index = lvr.index.set_levels(['leverage of {m}'.format(m=mode)] * len(lvr.index.levels[0]), level=0)
|
|
1735
|
+
# lvr.index = lvr.index.set_levels(['leverage of {m}'.format(m=mode)] * len(lvr.index.levels[0]), level=0)
|
|
1666
1736
|
return lvr
|
|
1667
1737
|
|
|
1668
1738
|
def sample_rmse(self):
|
|
@@ -1836,27 +1906,27 @@ def align_parafac_components(models_dict: dict, ex_ref: pd.DataFrame, em_ref: pd
|
|
|
1836
1906
|
component_labels_ref = ex_ref.columns
|
|
1837
1907
|
models_dict_new = {}
|
|
1838
1908
|
for model_label, model in models_dict.items():
|
|
1839
|
-
m_sim_ex = loadings_similarity(model, ex_ref, wavelength_alignment=wavelength_alignment)
|
|
1840
|
-
m_sim_em = loadings_similarity(model, em_ref, wavelength_alignment=wavelength_alignment)
|
|
1909
|
+
m_sim_ex = loadings_similarity(model.ex_loadings, ex_ref, wavelength_alignment=wavelength_alignment)
|
|
1910
|
+
m_sim_em = loadings_similarity(model.em_loadings, em_ref, wavelength_alignment=wavelength_alignment)
|
|
1841
1911
|
m_sim = (m_sim_ex + m_sim_em) / 2
|
|
1842
1912
|
ex_var, em_var = (model.ex_loadings, model.em_loadings)
|
|
1843
1913
|
matched_index = []
|
|
1844
1914
|
m_sim_copy = m_sim.copy()
|
|
1845
1915
|
if ex_var.shape[1] <= ex_ref.shape[1]:
|
|
1846
1916
|
for n_var in range(ex_var.shape[1]):
|
|
1847
|
-
max_index = np.argmax(m_sim[n_var, :])
|
|
1917
|
+
max_index = np.argmax(m_sim.iloc[n_var, :])
|
|
1848
1918
|
while max_index in matched_index:
|
|
1849
|
-
m_sim_copy[n_var, max_index] = 0
|
|
1850
|
-
max_index = np.argmax(m_sim_copy[n_var, :])
|
|
1919
|
+
m_sim_copy.iloc[n_var, max_index] = 0
|
|
1920
|
+
max_index = np.argmax(m_sim_copy.iloc[n_var, :])
|
|
1851
1921
|
matched_index.append(max_index)
|
|
1852
1922
|
component_labels_var = [component_labels_ref[i] for i in matched_index]
|
|
1853
1923
|
permutation = get_indices_smallest_to_largest(matched_index)
|
|
1854
1924
|
else:
|
|
1855
1925
|
for n_ref in range(ex_ref.shape[1]):
|
|
1856
|
-
max_index = np.argmax(m_sim[:, n_ref])
|
|
1926
|
+
max_index = np.argmax(m_sim.iloc[:, n_ref])
|
|
1857
1927
|
while max_index in matched_index:
|
|
1858
|
-
m_sim_copy[max_index, n_ref] = 0
|
|
1859
|
-
max_index = np.argmax(m_sim_copy[:, n_ref])
|
|
1928
|
+
m_sim_copy.iloc[max_index, n_ref] = 0
|
|
1929
|
+
max_index = np.argmax(m_sim_copy.iloc[:, n_ref])
|
|
1860
1930
|
matched_index.append(max_index)
|
|
1861
1931
|
non_ordered_index = list(set([i for i in range(ex_var.shape[1])]) - set(matched_index))
|
|
1862
1932
|
permutation = matched_index + non_ordered_index
|
|
@@ -1872,9 +1942,9 @@ def align_parafac_components(models_dict: dict, ex_ref: pd.DataFrame, em_ref: pd
|
|
|
1872
1942
|
model.em_loadings = model.em_loadings.iloc[:, permutation]
|
|
1873
1943
|
model.fmax = model.fmax.iloc[:, permutation]
|
|
1874
1944
|
model.component_stack = model.component_stack[permutation, :, :]
|
|
1875
|
-
model.cptensor = permute_cp_tensor(model.
|
|
1945
|
+
model.cptensor = permute_cp_tensor(model.cptensors, permutation)
|
|
1876
1946
|
models_dict_new[model_label] = model
|
|
1877
|
-
|
|
1947
|
+
return models_dict_new
|
|
1878
1948
|
|
|
1879
1949
|
|
|
1880
1950
|
class SplitValidation:
|
|
@@ -1906,8 +1976,8 @@ class SplitValidation:
|
|
|
1906
1976
|
Dictionary of PARAFAC models established on sub-datasets.
|
|
1907
1977
|
"""
|
|
1908
1978
|
|
|
1909
|
-
def __init__(self, rank, n_split, combination_size, rule, similarity_metric='TCC',
|
|
1910
|
-
tf_normalization=True):
|
|
1979
|
+
def __init__(self, rank, n_split=4, combination_size='half', rule='random', similarity_metric='TCC',
|
|
1980
|
+
non_negativity=True, tf_normalization=True):
|
|
1911
1981
|
# ---------------Parameters-------------------
|
|
1912
1982
|
self.rank = rank
|
|
1913
1983
|
self.n_split = n_split
|
|
@@ -1944,7 +2014,6 @@ class SplitValidation:
|
|
|
1944
2014
|
models[label] = model_subdataset
|
|
1945
2015
|
subsets[label] = subdataset
|
|
1946
2016
|
models = align_parafac_components(models, model_complete.ex_loadings, model_complete.em_loadings)
|
|
1947
|
-
|
|
1948
2017
|
self.eem_subsets = subsets
|
|
1949
2018
|
self.subset_specific_models = models
|
|
1950
2019
|
return self
|
|
@@ -1971,8 +2040,16 @@ class SplitValidation:
|
|
|
1971
2040
|
pair_labels = '{m1} vs. {m2}'.format(m1=labels[k], m2=labels[-1 - k])
|
|
1972
2041
|
similarities_ex[pair_labels] = sims_ex
|
|
1973
2042
|
similarities_em[pair_labels] = sims_em
|
|
1974
|
-
similarities_ex = pd.DataFrame
|
|
1975
|
-
|
|
2043
|
+
similarities_ex = pd.DataFrame.from_dict(
|
|
2044
|
+
similarities_ex, orient='index',
|
|
2045
|
+
columns=['Similarities in C{i}-ex'.format(i=i + 1) for i in range(self.rank)]
|
|
2046
|
+
)
|
|
2047
|
+
similarities_ex.index.name = 'Test'
|
|
2048
|
+
similarities_em = pd.DataFrame.from_dict(
|
|
2049
|
+
similarities_em, orient='index',
|
|
2050
|
+
columns=['Similarities in C{i}-em'.format(i=i + 1) for i in range(self.rank)]
|
|
2051
|
+
)
|
|
2052
|
+
similarities_em.index.name = 'Test'
|
|
1976
2053
|
return similarities_ex, similarities_em
|
|
1977
2054
|
|
|
1978
2055
|
|
|
@@ -2046,7 +2123,7 @@ class KPARAFACs:
|
|
|
2046
2123
|
self.tf_normalization = tf_normalization
|
|
2047
2124
|
self.loadings_normalization = loadings_normalization
|
|
2048
2125
|
self.sort_em = sort_em
|
|
2049
|
-
self.
|
|
2126
|
+
self.subsampling_portion = None
|
|
2050
2127
|
self.n_runs = None
|
|
2051
2128
|
self.consensus_conversion_power = None
|
|
2052
2129
|
|
|
@@ -2273,7 +2350,7 @@ class KPARAFACs:
|
|
|
2273
2350
|
cluster_specific_models[j] = model
|
|
2274
2351
|
|
|
2275
2352
|
self.n_runs = n_runs
|
|
2276
|
-
self.
|
|
2353
|
+
self.subsampling_portion = subsampling_portion
|
|
2277
2354
|
self.consensus_conversion_power = consensus_conversion_power
|
|
2278
2355
|
self.label_history = label_history
|
|
2279
2356
|
self.error_history = error_history
|
|
@@ -2282,3 +2359,363 @@ class KPARAFACs:
|
|
|
2282
2359
|
self.cluster_specific_models = cluster_specific_models
|
|
2283
2360
|
self.consensus_matrix = consensus_matrix
|
|
2284
2361
|
self.consensus_matrix_sorted = consensus_matrix_sorted
|
|
2362
|
+
|
|
2363
|
+
def predict(self, eem_dataset: EEMDataset):
|
|
2364
|
+
"""
|
|
2365
|
+
Fit the cluster-specific models to a given EEM dataset. Each EEM in the EEM dataset is fitted to the model that
|
|
2366
|
+
produce the least RMSE.
|
|
2367
|
+
|
|
2368
|
+
Parameters
|
|
2369
|
+
----------
|
|
2370
|
+
eem_dataset: EEMDataset
|
|
2371
|
+
EEM dataset.
|
|
2372
|
+
|
|
2373
|
+
Returns
|
|
2374
|
+
-------
|
|
2375
|
+
best_model_label: pd.DataFrame
|
|
2376
|
+
The best-fit model for every EEM.
|
|
2377
|
+
score_all: pd.DataFrame
|
|
2378
|
+
The score fitted with each cluster-specific model.
|
|
2379
|
+
fmax_all: pd.DataFrame
|
|
2380
|
+
The fmax fitted with each cluster-specific model.
|
|
2381
|
+
sample_error: pd.DataFrame
|
|
2382
|
+
The RMSE fitted with each cluster-specific model.
|
|
2383
|
+
"""
|
|
2384
|
+
|
|
2385
|
+
sample_error = []
|
|
2386
|
+
score_all = []
|
|
2387
|
+
fmax_all = []
|
|
2388
|
+
|
|
2389
|
+
for label, m in self.cluster_specific_models.items():
|
|
2390
|
+
score_m, fmax_m, eem_stack_re_m = m.predict(eem_dataset)
|
|
2391
|
+
res = m.eem_stack_train - eem_stack_re_m
|
|
2392
|
+
n_pixels = m.eem_stack_train.shape[1] * m.eem_stack_train.shape[2]
|
|
2393
|
+
rmse = sqrt(np.sum(res ** 2, axis=(1, 2)) / n_pixels)
|
|
2394
|
+
sample_error.append(rmse)
|
|
2395
|
+
score_all.append(score_m)
|
|
2396
|
+
fmax_all.append(fmax_m)
|
|
2397
|
+
|
|
2398
|
+
score_all = pd.DataFrame(np.array(score_all), index=eem_dataset.index,
|
|
2399
|
+
columns=list(self.cluster_specific_models.keys()))
|
|
2400
|
+
fmax_all = pd.DataFrame(np.array(fmax_all), index=eem_dataset.index,
|
|
2401
|
+
columns=list(self.cluster_specific_models.keys()))
|
|
2402
|
+
best_model_idx = np.argmin(sample_error, axis=0)
|
|
2403
|
+
# least_model_errors = np.min(sample_error, axis=0)
|
|
2404
|
+
# score_opt = np.array([score_all[i, j] for j, i in enumerate(best_model_idx)])
|
|
2405
|
+
# fmax_opt = np.array([fmax_all[i, j] for j, i in enumerate(best_model_idx)])
|
|
2406
|
+
best_model_label = np.array([list(self.cluster_specific_models.keys())[idx] for idx in best_model_idx])
|
|
2407
|
+
best_model_label = pd.DataFrame(best_model_label, index=eem_dataset.index, columns=['best-fit model'])
|
|
2408
|
+
sample_error = pd.DataFrame(np.array(sample_error), index=eem_dataset.index,
|
|
2409
|
+
columns=list(self.cluster_specific_models.keys()))
|
|
2410
|
+
|
|
2411
|
+
return best_model_label, score_all, fmax_all, sample_error
|
|
2412
|
+
|
|
2413
|
+
|
|
2414
|
+
# class EEMPCA:
|
|
2415
|
+
#
|
|
2416
|
+
# def __init__(self, n_components):
|
|
2417
|
+
# self.n_components = n_components
|
|
2418
|
+
# self.score = None
|
|
2419
|
+
# self.components = None
|
|
2420
|
+
#
|
|
2421
|
+
# def fit(self, eem_dataset: EEMDataset):
|
|
2422
|
+
# decomposer = PCA(n_components=self.n_components)
|
|
2423
|
+
# n_samples = eem_dataset.eem_stack.shape[0]
|
|
2424
|
+
# X = eem_dataset.eem_stack.reshape([n_samples, -1])
|
|
2425
|
+
# score = decomposer.fit_transform(X)
|
|
2426
|
+
# score = pd.DataFrame(score, index=eem_dataset.index,
|
|
2427
|
+
# columns=["component {i}".format(i=i + 1) for i in range(self.n_components)])
|
|
2428
|
+
# components = decomposer.components_.reshape([self.n_components, eem_dataset.eem_stack.shape[1],
|
|
2429
|
+
# eem_dataset.eem_stack.shape[2]])
|
|
2430
|
+
# self.score = score
|
|
2431
|
+
# self.components = components
|
|
2432
|
+
#
|
|
2433
|
+
# return self
|
|
2434
|
+
#
|
|
2435
|
+
#
|
|
2436
|
+
# class EEMNMF:
|
|
2437
|
+
#
|
|
2438
|
+
# def __init__(self, n_components, alpha_W, alpha_H, l1_ratio):
|
|
2439
|
+
# self.n_components = n_components
|
|
2440
|
+
# self.alpha_W = alpha_W
|
|
2441
|
+
# self.alpha_H = alpha_H
|
|
2442
|
+
# self.l1_ratio = l1_ratio
|
|
2443
|
+
# self.score = None
|
|
2444
|
+
# self.components = None
|
|
2445
|
+
#
|
|
2446
|
+
# def fit(self, eem_dataset: EEMDataset):
|
|
2447
|
+
# decomposer = NMF(n_components=self.n_components, alpha_W=self.alpha_W, alpha_H=self.alpha_H,
|
|
2448
|
+
# l1_ratio=self.l1_ratio)
|
|
2449
|
+
# n_samples = eem_dataset.eem_stack.shape[0]
|
|
2450
|
+
# X = eem_dataset.eem_stack.reshape([n_samples, -1])
|
|
2451
|
+
# score = decomposer.fit_transform(X)
|
|
2452
|
+
# score = pd.DataFrame(score, index=eem_dataset.index,
|
|
2453
|
+
# columns=["component {i}".format(i=i + 1) for i in range(self.n_components)])
|
|
2454
|
+
# components = decomposer.components_.reshape([self.n_components, eem_dataset.eem_stack.shape[1],
|
|
2455
|
+
# eem_dataset.eem_stack.shape[2]])
|
|
2456
|
+
# self.score = score
|
|
2457
|
+
# self.components = components
|
|
2458
|
+
#
|
|
2459
|
+
# return self
|
|
2460
|
+
|
|
2461
|
+
|
|
2462
|
+
class EEMNMF:
|
|
2463
|
+
|
|
2464
|
+
def __init__(self, n_components, solver='cd', beta_loss='frobenius', alpha_W=0, alpha_H=0, l1_ratio=1,
|
|
2465
|
+
normalization='pixel_std'):
|
|
2466
|
+
self.n_components = n_components
|
|
2467
|
+
self.solver = solver
|
|
2468
|
+
self.beta_loss = beta_loss
|
|
2469
|
+
self.alpha_W = alpha_W
|
|
2470
|
+
self.alpha_H = alpha_H
|
|
2471
|
+
self.l1_ratio = l1_ratio
|
|
2472
|
+
self.normalization = normalization
|
|
2473
|
+
self.eem_stack_unfolded = None
|
|
2474
|
+
self.nmf_score = None
|
|
2475
|
+
self.nnls_score = None
|
|
2476
|
+
self.components = None
|
|
2477
|
+
self.decomposer = None
|
|
2478
|
+
self.residual = None
|
|
2479
|
+
self.normalization_factor_std = None
|
|
2480
|
+
self.normalization_factor_max = None
|
|
2481
|
+
self.reconstruction_error = None
|
|
2482
|
+
self.eem_dataset_train = None
|
|
2483
|
+
|
|
2484
|
+
def fit(self, eem_dataset, sort_em=True):
|
|
2485
|
+
decomposer = NMF(n_components=self.n_components, solver=self.solver, beta_loss=self.beta_loss,
|
|
2486
|
+
alpha_W=self.alpha_W, alpha_H=self.alpha_H,
|
|
2487
|
+
l1_ratio=self.l1_ratio)
|
|
2488
|
+
n_samples = eem_dataset.eem_stack.shape[0]
|
|
2489
|
+
X = eem_dataset.eem_stack.reshape([n_samples, -1])
|
|
2490
|
+
# if self.normalization == 'intensity_max':
|
|
2491
|
+
# factor = np.max(X, axis=1)[:, np.newaxis]
|
|
2492
|
+
# X = X / factor
|
|
2493
|
+
# score = decomposer.fit_transform(X) * factor
|
|
2494
|
+
if self.normalization == 'pixel_std':
|
|
2495
|
+
factor_std = np.std(X, axis=0)
|
|
2496
|
+
X = X / factor_std
|
|
2497
|
+
X[np.isnan(X)] = 0
|
|
2498
|
+
nmf_score = decomposer.fit_transform(X)
|
|
2499
|
+
else:
|
|
2500
|
+
nmf_score = decomposer.fit_transform(X)
|
|
2501
|
+
factor_std = None
|
|
2502
|
+
factor_max = None
|
|
2503
|
+
nmf_score = pd.DataFrame(nmf_score, index=eem_dataset.index,
|
|
2504
|
+
columns=["component {i}".format(i=i + 1) for i in range(self.n_components)])
|
|
2505
|
+
if self.normalization == 'pixel_std':
|
|
2506
|
+
components = decomposer.components_ * factor_std
|
|
2507
|
+
else:
|
|
2508
|
+
components = decomposer.components_
|
|
2509
|
+
factor_max = np.max(components, axis=1)
|
|
2510
|
+
components = components / factor_max[:, None]
|
|
2511
|
+
components = components.reshape([self.n_components, eem_dataset.eem_stack.shape[1],
|
|
2512
|
+
eem_dataset.eem_stack.shape[2]])
|
|
2513
|
+
nmf_score = nmf_score.mul(factor_max, axis=1)
|
|
2514
|
+
_, nnls_score, _ = eems_fit_components(eem_dataset.eem_stack, components,
|
|
2515
|
+
fit_intercept=False, positive=True)
|
|
2516
|
+
nnls_score = pd.DataFrame(nnls_score, index=eem_dataset.index,
|
|
2517
|
+
columns=["component {i}".format(i=i + 1) for i in range(self.n_components)])
|
|
2518
|
+
if sort_em:
|
|
2519
|
+
em_peaks = []
|
|
2520
|
+
for i in range(self.n_components):
|
|
2521
|
+
flat_max_index = components[i].argmax()
|
|
2522
|
+
row_index, col_index = np.unravel_index(flat_max_index, components[i].shape)
|
|
2523
|
+
em_peaks.append(col_index)
|
|
2524
|
+
peak_rank = list(enumerate(stats.rankdata(em_peaks)))
|
|
2525
|
+
order = [i[0] for i in sorted(peak_rank, key=lambda x: x[1])]
|
|
2526
|
+
components = components[order]
|
|
2527
|
+
nmf_score = pd.DataFrame({'component {r} score'.format(r=i + 1): nmf_score.iloc[:, order[i]]
|
|
2528
|
+
for i in range(self.n_components)})
|
|
2529
|
+
nnls_score = pd.DataFrame({'component {r} score'.format(r=i + 1): nnls_score.iloc[:, order[i]]
|
|
2530
|
+
for i in range(self.n_components)})
|
|
2531
|
+
self.nmf_score = nmf_score
|
|
2532
|
+
self.nnls_score = nnls_score
|
|
2533
|
+
self.components = components
|
|
2534
|
+
self.decomposer = decomposer
|
|
2535
|
+
self.eem_stack_unfolded = X
|
|
2536
|
+
self.normalization_factor_std = factor_std
|
|
2537
|
+
self.normalization_factor_max = factor_max
|
|
2538
|
+
self.reconstruction_error = decomposer.reconstruction_err_
|
|
2539
|
+
self.eem_dataset_train = eem_dataset
|
|
2540
|
+
return self
|
|
2541
|
+
|
|
2542
|
+
def calculate_residual(self, score_type='nmf'):
|
|
2543
|
+
if score_type == 'nmf':
|
|
2544
|
+
X_new = self.decomposer.fit_transform(self.eem_stack_unfolded)
|
|
2545
|
+
X_reversed = self.decomposer.inverse_transform(X_new) * self.normalization_factor
|
|
2546
|
+
n_samples = self.eem_dataset_train.eem_stack.shape[0]
|
|
2547
|
+
residual = X_reversed - self.eem_stack_unfolded
|
|
2548
|
+
residual = residual.reshape([n_samples, self.eem_dataset_train.eem_stack.shape[1],
|
|
2549
|
+
self.eem_dataset_train.eem_stack.shape[2]])
|
|
2550
|
+
self.residual = residual
|
|
2551
|
+
# elif score_type=='nnls':
|
|
2552
|
+
# X_new = self.decomposer.fit_transform(self.eem_stack_unfolded)
|
|
2553
|
+
# X_reversed = self.decomposer.inverse_transform(X_new)*self.normalization_factor
|
|
2554
|
+
# n_samples = eem_dataset.eem_stack.shape[0]
|
|
2555
|
+
# residual = X_reversed - self.eem_stack_unfolded
|
|
2556
|
+
# residual = residual.reshape([n_samples, eem_dataset.eem_stack.shape[1], eem_dataset.eem_stack.shape[2]])
|
|
2557
|
+
# self.residual = residual
|
|
2558
|
+
return residual
|
|
2559
|
+
|
|
2560
|
+
def greedy_selection(self, eem_dataset_train, eem_dataset_test, direction='backwards',
|
|
2561
|
+
criteria: str = 'reconstruction_error', true_values=None, axis=0, n_steps='max',
|
|
2562
|
+
index_groups=None):
|
|
2563
|
+
eem_stack = eem_dataset_train.eem_stack
|
|
2564
|
+
if index_groups == None:
|
|
2565
|
+
index_groups = [[i] for i in range(eem_stack.shape[axis])]
|
|
2566
|
+
if n_steps == 'max':
|
|
2567
|
+
n_steps = len(index_groups)
|
|
2568
|
+
eem_datasets_sequence = []
|
|
2569
|
+
err_sequence = []
|
|
2570
|
+
fmax_sequence = []
|
|
2571
|
+
g_tot = []
|
|
2572
|
+
|
|
2573
|
+
for step in range(n_steps):
|
|
2574
|
+
err_list = []
|
|
2575
|
+
eem_dataset_sub_list = []
|
|
2576
|
+
fmax_list = []
|
|
2577
|
+
if direction == 'forwards':
|
|
2578
|
+
eem_stack_sub = []
|
|
2579
|
+
elif direction == 'backwards':
|
|
2580
|
+
eem_stack_sub = eem_stack
|
|
2581
|
+
if direction == 'backwards' and step == 0:
|
|
2582
|
+
eem_dataset_sub = eem_dataset_train
|
|
2583
|
+
self.fit(eem_dataset_sub)
|
|
2584
|
+
score, fmax, eem_stack_pred = eems_fit_components(eem_dataset_test.eem_stack, self.components,
|
|
2585
|
+
fit_intercept=False, positive=True)
|
|
2586
|
+
if criteria == 'reconstruction_error':
|
|
2587
|
+
residual = eem_stack_pred - eem_dataset_test.eem_stack
|
|
2588
|
+
|
|
2589
|
+
elif criteria == 'fmax_error':
|
|
2590
|
+
assert true_values.shape == (eem_dataset_test.eem_stack.shape[0], self.n_components), \
|
|
2591
|
+
"True values should have a shape of (n_test_samples, n_components)"
|
|
2592
|
+
residual = fmax - np.array(true_values)
|
|
2593
|
+
|
|
2594
|
+
elif criteria == 'component_error':
|
|
2595
|
+
assert true_values.shape == (self.n_components, eem_dataset_test.eem_stack.shape[1],
|
|
2596
|
+
eem_dataset_test.eem_stack.shape[2]), \
|
|
2597
|
+
"True values should have a shape of (n_components, n_ex, n_em)"
|
|
2598
|
+
residual = self.components - np.array(true_values)
|
|
2599
|
+
|
|
2600
|
+
error = (np.sum(residual ** 2) / np.size(residual)) ** 0.5
|
|
2601
|
+
err_list.append(error)
|
|
2602
|
+
fmax_list.append(fmax)
|
|
2603
|
+
eem_dataset_sub_list.append(eem_dataset_sub)
|
|
2604
|
+
|
|
2605
|
+
else:
|
|
2606
|
+
for g in index_groups:
|
|
2607
|
+
if direction == 'forwards':
|
|
2608
|
+
eem_stack_g = np.take(eem_stack, g, axis=axis)
|
|
2609
|
+
eem_stack_sub.append(eem_stack_g.tolist())
|
|
2610
|
+
if axis == 0:
|
|
2611
|
+
if eem_dataset_train.index:
|
|
2612
|
+
index_sub = [eem_dataset_train.index[i] for i in g_tot + g]
|
|
2613
|
+
else:
|
|
2614
|
+
index_sub = None
|
|
2615
|
+
if eem_dataset_train.ref:
|
|
2616
|
+
ref_sub = eem_dataset_train.ref[g_tot + g]
|
|
2617
|
+
else:
|
|
2618
|
+
ref_sub = None
|
|
2619
|
+
else:
|
|
2620
|
+
index_sub = eem_dataset_train.index
|
|
2621
|
+
ref_sub = eem_dataset_train.ref
|
|
2622
|
+
eem_dataset_sub = EEMDataset(eem_stack=np.array(eem_stack_sub),
|
|
2623
|
+
ex_range=eem_dataset_train.ex_range,
|
|
2624
|
+
em_range=eem_dataset_train.em_range, index=index_sub, ref=ref_sub)
|
|
2625
|
+
|
|
2626
|
+
if direction == 'backwards':
|
|
2627
|
+
|
|
2628
|
+
eem_stack_sub = np.delete(eem_stack, g_tot + g, axis=axis)
|
|
2629
|
+
if axis == 0:
|
|
2630
|
+
if eem_dataset_train.index:
|
|
2631
|
+
index_sub = [eem_dataset_train.index[i] for i in range(eem_stack.shape[0]) if
|
|
2632
|
+
i not in g_tot + g]
|
|
2633
|
+
else:
|
|
2634
|
+
index_sub = None
|
|
2635
|
+
if eem_dataset_train.ref:
|
|
2636
|
+
ref_sub = np.delete(eem_dataset_train.ref, g_tot + g)
|
|
2637
|
+
else:
|
|
2638
|
+
ref_sub = None
|
|
2639
|
+
else:
|
|
2640
|
+
index_sub = eem_dataset_train.index
|
|
2641
|
+
ref_sub = eem_dataset_train.ref
|
|
2642
|
+
eem_dataset_sub = EEMDataset(eem_stack=eem_stack_sub, ex_range=eem_dataset_train.ex_range,
|
|
2643
|
+
em_range=eem_dataset_train.em_range, index=index_sub, ref=ref_sub)
|
|
2644
|
+
self.fit(eem_dataset_sub)
|
|
2645
|
+
# plot_eem(self.components[0], eem_dataset_test.ex_range, eem_dataset_test.em_range, auto_intensity_range=False,
|
|
2646
|
+
# vmin=0, vmax=1)
|
|
2647
|
+
score, fmax, eem_stack_pred = eems_fit_components(eem_dataset_test.eem_stack, self.components,
|
|
2648
|
+
fit_intercept=False, positive=True)
|
|
2649
|
+
if criteria == 'reconstruction_error':
|
|
2650
|
+
residual = eem_stack_pred - eem_dataset_test.eem_stack
|
|
2651
|
+
|
|
2652
|
+
elif criteria == 'fmax_error':
|
|
2653
|
+
assert true_values.shape == (eem_dataset_test.eem_stack.shape[0], self.n_components), \
|
|
2654
|
+
"True values should have a shape of (n_test_samples, n_components)"
|
|
2655
|
+
residual = fmax - np.array(true_values)
|
|
2656
|
+
|
|
2657
|
+
elif criteria == 'component_error':
|
|
2658
|
+
assert true_values.shape == (self.n_components, eem_dataset_test.eem_stack.shape[1],
|
|
2659
|
+
eem_dataset_test.eem_stack.shape[2]), \
|
|
2660
|
+
"True values should have a shape of (n_components, n_ex, n_em)"
|
|
2661
|
+
residual = self.components - np.array(true_values)
|
|
2662
|
+
|
|
2663
|
+
error = (np.sum(residual ** 2) / np.size(residual)) ** 0.5
|
|
2664
|
+
err_list.append(error)
|
|
2665
|
+
fmax_list.append(fmax)
|
|
2666
|
+
eem_dataset_sub_list.append(eem_dataset_sub)
|
|
2667
|
+
|
|
2668
|
+
least_err_idx = err_list.index(min(err_list))
|
|
2669
|
+
err_sequence.append(min(err_list))
|
|
2670
|
+
fmax_sequence.append(pd.DataFrame(fmax_list[least_err_idx], index=eem_dataset_test.index,
|
|
2671
|
+
columns=["component {i}".format(i=i + 1) for i in
|
|
2672
|
+
range(self.n_components)]))
|
|
2673
|
+
eem_datasets_sequence.append(eem_dataset_sub_list[least_err_idx])
|
|
2674
|
+
if direction == 'backwards' and step == 0:
|
|
2675
|
+
continue
|
|
2676
|
+
else:
|
|
2677
|
+
g_tot += index_groups[least_err_idx]
|
|
2678
|
+
index_groups.pop(least_err_idx)
|
|
2679
|
+
|
|
2680
|
+
return eem_datasets_sequence, err_sequence, fmax_sequence
|
|
2681
|
+
|
|
2682
|
+
|
|
2683
|
+
class EEMPCA:
|
|
2684
|
+
|
|
2685
|
+
def __init__(self, n_components):
|
|
2686
|
+
self.n_components = n_components
|
|
2687
|
+
self.eem_stack_unfolded = None
|
|
2688
|
+
self.score = None
|
|
2689
|
+
self.components = None
|
|
2690
|
+
self.decomposer = None
|
|
2691
|
+
self.residual = None
|
|
2692
|
+
self.normalization_factor = None
|
|
2693
|
+
self.eem_stack_train = None
|
|
2694
|
+
|
|
2695
|
+
def fit(self, eem_dataset: EEMDataset, normalization=None):
|
|
2696
|
+
decomposer = PCA(n_components=self.n_components)
|
|
2697
|
+
n_samples = eem_dataset.eem_stack.shape[0]
|
|
2698
|
+
X = eem_dataset.eem_stack.reshape([n_samples, -1])
|
|
2699
|
+
score = decomposer.fit_transform(X)
|
|
2700
|
+
score = pd.DataFrame(score, index=eem_dataset.index,
|
|
2701
|
+
columns=["component {i}".format(i=i + 1) for i in range(self.n_components)])
|
|
2702
|
+
components = decomposer.components_.reshape([self.n_components, eem_dataset.eem_stack.shape[1],
|
|
2703
|
+
eem_dataset.eem_stack.shape[2]])
|
|
2704
|
+
self.score = score
|
|
2705
|
+
self.components = components
|
|
2706
|
+
self.decomposer = decomposer
|
|
2707
|
+
self.eem_stack_unfolded = X
|
|
2708
|
+
self.eem_stack_train = eem_dataset
|
|
2709
|
+
return self
|
|
2710
|
+
|
|
2711
|
+
def calculate_residual(self):
|
|
2712
|
+
X_new = self.decomposer.fit_transform(self.eem_stack_unfolded)
|
|
2713
|
+
X_reversed = self.decomposer.inverse_transform(X_new)
|
|
2714
|
+
n_samples = self.eem_stack_train.eem_stack.shape[0]
|
|
2715
|
+
residual = X_reversed - self.eem_stack_unfolded
|
|
2716
|
+
residual = residual.reshape([n_samples, self.eem_stack_train.eem_stack.shape[1],
|
|
2717
|
+
self.eem_stack_train.eem_stack.shape[2]])
|
|
2718
|
+
self.residual = residual
|
|
2719
|
+
return residual
|
|
2720
|
+
|
|
2721
|
+
|