funcnodes-span 0.2.1__tar.gz → 0.2.3__tar.gz
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.
- {funcnodes_span-0.2.1 → funcnodes_span-0.2.3}/PKG-INFO +1 -1
- {funcnodes_span-0.2.1 → funcnodes_span-0.2.3}/funcnodes_span/__init__.py +1 -1
- funcnodes_span-0.2.3/funcnodes_span/normalization.py +161 -0
- {funcnodes_span-0.2.1 → funcnodes_span-0.2.3}/funcnodes_span/peak_analysis.py +73 -44
- {funcnodes_span-0.2.1 → funcnodes_span-0.2.3}/pyproject.toml +1 -1
- funcnodes_span-0.2.1/funcnodes_span/normalization.py +0 -54
- {funcnodes_span-0.2.1 → funcnodes_span-0.2.3}/README.md +0 -0
- {funcnodes_span-0.2.1 → funcnodes_span-0.2.3}/funcnodes_span/baseline.py +0 -0
- {funcnodes_span-0.2.1 → funcnodes_span-0.2.3}/funcnodes_span/smoothing.py +0 -0
|
@@ -5,7 +5,7 @@ from .smoothing import SMOOTH_NODE_SHELF as SMOOTH
|
|
|
5
5
|
from .peak_analysis import PEAKS_NODE_SHELF as PEAK
|
|
6
6
|
from .baseline import BASELINE_NODE_SHELF as BASELINE
|
|
7
7
|
|
|
8
|
-
__version__ = "0.2.
|
|
8
|
+
__version__ = "0.2.3"
|
|
9
9
|
|
|
10
10
|
NODE_SHELF = fn.Shelf(
|
|
11
11
|
name="Spectral Analysis",
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
from typing import Literal, Tuple
|
|
2
|
+
from funcnodes import NodeDecorator, Shelf
|
|
3
|
+
import numpy as np
|
|
4
|
+
from scipy.interpolate import interp1d
|
|
5
|
+
from exposedfunctionality import controlled_wrapper
|
|
6
|
+
import funcnodes as fn
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class NormMode(fn.DataEnum):
|
|
10
|
+
ZERO_ONE = "zero_one"
|
|
11
|
+
MINUS_ONE_ONE = "minus_one_one"
|
|
12
|
+
SUM_ABS = "sum_abs"
|
|
13
|
+
SUM = "sum"
|
|
14
|
+
EUCLIDEAN = "euclidean"
|
|
15
|
+
MEAN_STD = "mean_std"
|
|
16
|
+
MAX = "max"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@NodeDecorator(id="span.basics.norm", name="Normalization node")
|
|
20
|
+
def _norm(array: np.ndarray, mode: NormMode = NormMode.ZERO_ONE) -> np.ndarray:
|
|
21
|
+
# """
|
|
22
|
+
# Apply different normalizations to the array.
|
|
23
|
+
|
|
24
|
+
# Args:
|
|
25
|
+
# array (np.ndarray): The input array to be normalized.
|
|
26
|
+
# mode (NormMode): The normalization mode to apply. Defaults to NormMode.ZERO_ONE.
|
|
27
|
+
|
|
28
|
+
# Returns:
|
|
29
|
+
# np.ndarray: The normalized array.
|
|
30
|
+
|
|
31
|
+
# Raises:
|
|
32
|
+
# ValueError: If an unsupported normalization mode is provided.
|
|
33
|
+
# """
|
|
34
|
+
mode = NormMode.v(mode)
|
|
35
|
+
normalization_methods = {
|
|
36
|
+
NormMode.ZERO_ONE.value: lambda x: (x - np.amin(x)) / (np.amax(x) - np.amin(x)),
|
|
37
|
+
NormMode.MINUS_ONE_ONE.value: lambda x: 2
|
|
38
|
+
* ((x - np.amin(x)) / (np.amax(x) - np.amin(x)))
|
|
39
|
+
- 1,
|
|
40
|
+
NormMode.SUM_ABS.value: lambda x: x / np.abs(x).sum(),
|
|
41
|
+
NormMode.SUM.value: lambda x: x / x.sum(),
|
|
42
|
+
NormMode.EUCLIDEAN.value: lambda x: x / np.sqrt((x**2).sum()),
|
|
43
|
+
NormMode.MEAN_STD.value: lambda x: (x - x.mean()) / x.std(),
|
|
44
|
+
NormMode.MAX.value: lambda x: x / x.max(),
|
|
45
|
+
}
|
|
46
|
+
if mode not in normalization_methods.keys():
|
|
47
|
+
raise ValueError(f"Unsupported normalization mode: {mode}")
|
|
48
|
+
return normalization_methods[mode](array)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def density_normalization(
|
|
52
|
+
x: np.ndarray,
|
|
53
|
+
y: np.ndarray,
|
|
54
|
+
num_points: int = None,
|
|
55
|
+
distance_estimation: Literal["median", "mean", "min", "max"] = "median",
|
|
56
|
+
) -> Tuple[np.ndarray, np.ndarray]:
|
|
57
|
+
"""
|
|
58
|
+
Redistributes the x and y coordinates to have evenly spaced x-values while retaining original points.
|
|
59
|
+
|
|
60
|
+
Parameters:
|
|
61
|
+
x (np.ndarray): Uneven, unsorted x coordinates.
|
|
62
|
+
y (np.ndarray): Corresponding y values.
|
|
63
|
+
num_points (int, optional): Total number of points in the final grid.
|
|
64
|
+
Must be greater than or equal to the number of unique original x values.
|
|
65
|
+
If None, it is estimated based on the specified distance estimation method.
|
|
66
|
+
distance_estimation (Literal["median", "mean", "min", "max"], optional):
|
|
67
|
+
Method to estimate the spacing between points when num_points is not provided.
|
|
68
|
+
Options include "median", "mean", "min", "max". Defaults to "median".
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
Tuple[np.ndarray, np.ndarray]:
|
|
72
|
+
- x_new: Evenly spaced x coordinates including original x points.
|
|
73
|
+
- y_new: Corresponding interpolated y values."""
|
|
74
|
+
# Convert inputs to numpy arrays
|
|
75
|
+
x = np.array(x)
|
|
76
|
+
y = np.array(y)
|
|
77
|
+
|
|
78
|
+
# if x is already evenly spaced, return the input
|
|
79
|
+
if len(np.unique(np.diff(x))) == 1:
|
|
80
|
+
return x, y
|
|
81
|
+
|
|
82
|
+
# Sort the data based on x values
|
|
83
|
+
sorted_indices = np.argsort(x)
|
|
84
|
+
x_sorted = x[sorted_indices]
|
|
85
|
+
y_sorted = y[sorted_indices]
|
|
86
|
+
|
|
87
|
+
# Handle duplicate x-values by averaging their y-values
|
|
88
|
+
unique_x, indices, counts = np.unique(
|
|
89
|
+
x_sorted, return_index=True, return_counts=True
|
|
90
|
+
)
|
|
91
|
+
y_unique = np.array([y_sorted[i : i + c].mean() for i, c in zip(indices, counts)])
|
|
92
|
+
|
|
93
|
+
# Determine number of points for the evenly spaced grid
|
|
94
|
+
|
|
95
|
+
if num_points is None:
|
|
96
|
+
distance_func = {
|
|
97
|
+
"mean": np.mean,
|
|
98
|
+
"median": np.median,
|
|
99
|
+
"min": np.min,
|
|
100
|
+
"max": np.max,
|
|
101
|
+
}.get(distance_estimation, np.median) # Default to median if key not found
|
|
102
|
+
|
|
103
|
+
unique_diffs = np.diff(unique_x)
|
|
104
|
+
|
|
105
|
+
# Handle the case where there is only one unique_x point
|
|
106
|
+
if len(unique_diffs) == 0:
|
|
107
|
+
num_points = 1
|
|
108
|
+
else:
|
|
109
|
+
estimated_diff = distance_func(unique_diffs)
|
|
110
|
+
total_range = unique_x.max() - unique_x.min()
|
|
111
|
+
|
|
112
|
+
# Prevent division by zero
|
|
113
|
+
if estimated_diff == 0:
|
|
114
|
+
num_points = len(unique_x)
|
|
115
|
+
else:
|
|
116
|
+
num_points = int(total_range / estimated_diff)
|
|
117
|
+
else:
|
|
118
|
+
num_points = int(num_points)
|
|
119
|
+
# Create evenly spaced x-values
|
|
120
|
+
x_new = np.linspace(unique_x.min(), unique_x.max(), num_points)
|
|
121
|
+
|
|
122
|
+
# Create an interpolation function
|
|
123
|
+
interp_func = interp1d(unique_x, y_unique, kind="linear", fill_value="extrapolate")
|
|
124
|
+
|
|
125
|
+
# Compute the interpolated y-values
|
|
126
|
+
y_new = interp_func(x_new)
|
|
127
|
+
|
|
128
|
+
return x_new, y_new
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
@NodeDecorator(
|
|
132
|
+
id="span.norm.density",
|
|
133
|
+
name="Density normalization node",
|
|
134
|
+
description="Redistributes the x and y coordinates to have evenly spaced x-values while retaining original points.",
|
|
135
|
+
outputs=[
|
|
136
|
+
{
|
|
137
|
+
"name": "x_new",
|
|
138
|
+
"description": "Evenly spaced x coordinates including original x points.",
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
"name": "y_new",
|
|
142
|
+
"description": "Corresponding interpolated y values.",
|
|
143
|
+
},
|
|
144
|
+
],
|
|
145
|
+
)
|
|
146
|
+
@controlled_wrapper(density_normalization, wrapper_attribute="__fnwrapped__")
|
|
147
|
+
def density_normalization_node(
|
|
148
|
+
x: np.ndarray,
|
|
149
|
+
y: np.ndarray,
|
|
150
|
+
num_points: int = None,
|
|
151
|
+
distance_estimation: Literal["median", "mean", "min", "max"] = "median",
|
|
152
|
+
) -> Tuple[np.ndarray, np.ndarray]:
|
|
153
|
+
return density_normalization(x, y, num_points, distance_estimation)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
NORM_NODE_SHELF = Shelf(
|
|
157
|
+
nodes=[_norm, density_normalization_node],
|
|
158
|
+
subshelves=[],
|
|
159
|
+
name="Normalization",
|
|
160
|
+
description="Normalization of the spectra",
|
|
161
|
+
)
|
|
@@ -2,7 +2,7 @@ from funcnodes import NodeDecorator, Shelf
|
|
|
2
2
|
import funcnodes as fn
|
|
3
3
|
import numpy as np
|
|
4
4
|
from exposedfunctionality import controlled_wrapper
|
|
5
|
-
from typing import Optional, List, Tuple, Dict
|
|
5
|
+
from typing import Optional, List, Tuple, Dict, Union
|
|
6
6
|
from scipy.signal import find_peaks
|
|
7
7
|
from scipy.stats import norm
|
|
8
8
|
from scipy import signal, interpolate
|
|
@@ -13,6 +13,7 @@ import plotly.graph_objs as go
|
|
|
13
13
|
from plotly.subplots import make_subplots
|
|
14
14
|
import re
|
|
15
15
|
from dataclasses import dataclass
|
|
16
|
+
from .normalization import density_normalization
|
|
16
17
|
|
|
17
18
|
|
|
18
19
|
@dataclass
|
|
@@ -21,6 +22,8 @@ class FittinInfo:
|
|
|
21
22
|
best_values: dict
|
|
22
23
|
data: np.ndarray
|
|
23
24
|
userkws: dict
|
|
25
|
+
best_fit: np.ndarray
|
|
26
|
+
rsquared: float
|
|
24
27
|
|
|
25
28
|
|
|
26
29
|
@dataclass
|
|
@@ -197,8 +200,8 @@ def compute_peak_properties(
|
|
|
197
200
|
@NodeDecorator(id="span.basics.peaks", name="Peak finder")
|
|
198
201
|
@controlled_wrapper(find_peaks, wrapper_attribute="__fnwrapped__")
|
|
199
202
|
def peak_finder(
|
|
200
|
-
|
|
201
|
-
|
|
203
|
+
x: np.ndarray,
|
|
204
|
+
y: np.ndarray,
|
|
202
205
|
noise_level: Optional[int] = None,
|
|
203
206
|
height: Optional[float] = None,
|
|
204
207
|
threshold: Optional[float] = None,
|
|
@@ -210,23 +213,46 @@ def peak_finder(
|
|
|
210
213
|
plateau_size: Optional[int] = None,
|
|
211
214
|
) -> List[PeakProperties]:
|
|
212
215
|
peak_lst = []
|
|
213
|
-
|
|
214
|
-
|
|
216
|
+
x = np.array(x, dtype=float)
|
|
217
|
+
y = np.array(y, dtype=float)
|
|
215
218
|
noise_level = int(noise_level) if noise_level is not None else None
|
|
216
219
|
height = float(height) if height is not None else None
|
|
217
220
|
threshold = float(threshold) if threshold is not None else None
|
|
218
221
|
distance = float(distance) if distance is not None else None
|
|
219
222
|
prominence = float(prominence) if prominence is not None else None
|
|
220
223
|
width = float(width) if width is not None else None
|
|
221
|
-
wlen =
|
|
224
|
+
wlen = float(wlen) if wlen is not None else None
|
|
222
225
|
rel_height = float(rel_height)
|
|
223
|
-
plateau_size =
|
|
226
|
+
plateau_size = float(plateau_size) if plateau_size is not None else None
|
|
224
227
|
|
|
225
|
-
height = 0.05 * np.max(
|
|
228
|
+
height = 0.05 * np.max(y) if height is None else height
|
|
226
229
|
noise_level = 5000 if noise_level is None else noise_level
|
|
227
230
|
|
|
231
|
+
if x is not None:
|
|
232
|
+
x, y = density_normalization(
|
|
233
|
+
x,
|
|
234
|
+
y,
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
xdiff = x[1] - x[0]
|
|
238
|
+
# if x is given width is based on the x scale and has to be converted to index
|
|
239
|
+
if width is not None:
|
|
240
|
+
width = width / xdiff
|
|
241
|
+
|
|
242
|
+
# same for distance
|
|
243
|
+
if distance is not None:
|
|
244
|
+
distance = distance / xdiff
|
|
245
|
+
|
|
246
|
+
# same for wlen
|
|
247
|
+
if wlen is not None:
|
|
248
|
+
wlen = wlen / xdiff
|
|
249
|
+
|
|
250
|
+
# same for plateau_size
|
|
251
|
+
if plateau_size is not None:
|
|
252
|
+
plateau_size = plateau_size / xdiff
|
|
253
|
+
|
|
228
254
|
# Make a copy of the input array
|
|
229
|
-
y_array_copy = np.copy(
|
|
255
|
+
y_array_copy = np.copy(y)
|
|
230
256
|
|
|
231
257
|
# Find the peaks in the copy of the input array
|
|
232
258
|
peaks, _ = find_peaks(
|
|
@@ -235,8 +261,8 @@ def peak_finder(
|
|
|
235
261
|
prominence=prominence,
|
|
236
262
|
height=height,
|
|
237
263
|
distance=distance,
|
|
238
|
-
width=width,
|
|
239
|
-
wlen=wlen,
|
|
264
|
+
width=max(1, width) if width is not None else None,
|
|
265
|
+
wlen=int(wlen) if wlen is not None else None,
|
|
240
266
|
rel_height=rel_height,
|
|
241
267
|
plateau_size=plateau_size,
|
|
242
268
|
)
|
|
@@ -263,7 +289,7 @@ def peak_finder(
|
|
|
263
289
|
# Find the right minimum of the peak
|
|
264
290
|
right_min = mins[np.argmax(mins > peak)]
|
|
265
291
|
if right_min < peak:
|
|
266
|
-
right_min = len(
|
|
292
|
+
right_min = len(y) - 1
|
|
267
293
|
|
|
268
294
|
try:
|
|
269
295
|
# Find the left minimum of the peak
|
|
@@ -289,7 +315,7 @@ def peak_finder(
|
|
|
289
315
|
for peak in peaks:
|
|
290
316
|
right_min = mins[np.argmax(mins > peak)]
|
|
291
317
|
if right_min < peak:
|
|
292
|
-
right_min = len(
|
|
318
|
+
right_min = len(y) - 1
|
|
293
319
|
try:
|
|
294
320
|
left_min = np.array(mins)[np.where(np.array(mins) < peak)][-1]
|
|
295
321
|
except IndexError:
|
|
@@ -307,7 +333,7 @@ def peak_finder(
|
|
|
307
333
|
|
|
308
334
|
for peak_nr, peak in enumerate(peak_lst):
|
|
309
335
|
peak_properties = compute_peak_properties(
|
|
310
|
-
x_array=
|
|
336
|
+
x_array=x, y_array=y, peak_indices=peak, peak_nr=peak_nr
|
|
311
337
|
)
|
|
312
338
|
peak_properties_list.append(peak_properties)
|
|
313
339
|
|
|
@@ -382,8 +408,8 @@ class BaselineModel(fn.DataEnum):
|
|
|
382
408
|
|
|
383
409
|
@NodeDecorator(id="span.basics.fit", name="Fit 1D", separate_process=True)
|
|
384
410
|
def fit_1D(
|
|
385
|
-
|
|
386
|
-
|
|
411
|
+
x: np.ndarray,
|
|
412
|
+
y: np.ndarray,
|
|
387
413
|
basic_peaks: List[PeakProperties],
|
|
388
414
|
main_model: FittingModel = FittingModel.Gaussian,
|
|
389
415
|
baseline_model: BaselineModel = BaselineModel.Exponential,
|
|
@@ -408,15 +434,14 @@ def fit_1D(
|
|
|
408
434
|
|
|
409
435
|
# """
|
|
410
436
|
|
|
411
|
-
|
|
412
|
-
|
|
437
|
+
x = np.array(x)
|
|
438
|
+
y = np.array(y)
|
|
413
439
|
|
|
414
440
|
main_model = FittingModel.v(main_model)
|
|
415
441
|
|
|
416
442
|
baseline_model = BaselineModel.v(baseline_model)
|
|
417
443
|
peaks = copy.deepcopy(basic_peaks)
|
|
418
|
-
|
|
419
|
-
x = x_array
|
|
444
|
+
|
|
420
445
|
# if is_sturated:
|
|
421
446
|
|
|
422
447
|
lowest_index = min(dictionary.i_index for dictionary in peaks)
|
|
@@ -486,18 +511,20 @@ def fit_1D(
|
|
|
486
511
|
best_values=out.best_values,
|
|
487
512
|
data=out.data,
|
|
488
513
|
userkws=out.userkws,
|
|
514
|
+
best_fit=out.best_fit,
|
|
515
|
+
rsquared=out.rsquared,
|
|
489
516
|
)
|
|
490
517
|
|
|
491
518
|
peak_properties_list = []
|
|
492
519
|
|
|
493
520
|
for key in com.keys():
|
|
494
521
|
if key != "baseline":
|
|
495
|
-
|
|
496
|
-
peak_lst = [(0, np.argmax(
|
|
522
|
+
y = com[key]
|
|
523
|
+
peak_lst = [(0, np.argmax(y), len(y) - 1)]
|
|
497
524
|
for peak_nr, peak in enumerate(peak_lst):
|
|
498
525
|
peak_properties = compute_peak_properties(
|
|
499
|
-
x_array=
|
|
500
|
-
y_array=
|
|
526
|
+
x_array=x,
|
|
527
|
+
y_array=y,
|
|
501
528
|
peak_indices=peak,
|
|
502
529
|
peak_nr=peak_nr,
|
|
503
530
|
is_fitted=True,
|
|
@@ -516,7 +543,7 @@ def fit_1D(
|
|
|
516
543
|
def force_peak_finder(
|
|
517
544
|
x: np.array,
|
|
518
545
|
y: np.array,
|
|
519
|
-
basic_peaks: List[PeakProperties],
|
|
546
|
+
basic_peaks: Union[List[PeakProperties], PeakProperties],
|
|
520
547
|
) -> List[PeakProperties]:
|
|
521
548
|
# """
|
|
522
549
|
# Identify and return the two peaks around the main peak in the given peaks dictionary.
|
|
@@ -531,10 +558,14 @@ def force_peak_finder(
|
|
|
531
558
|
# Returns:
|
|
532
559
|
# - dict: A dictionary containing information about the two identified peaks.
|
|
533
560
|
# """
|
|
534
|
-
if
|
|
535
|
-
|
|
561
|
+
if isinstance(basic_peaks, (list, np.ndarray, tuple)):
|
|
562
|
+
if len(basic_peaks) != 1:
|
|
563
|
+
raise ValueError(
|
|
564
|
+
"This method accepts one and only one main peak as an input."
|
|
565
|
+
)
|
|
566
|
+
basic_peaks = basic_peaks[0]
|
|
536
567
|
|
|
537
|
-
peaks = copy.deepcopy(basic_peaks
|
|
568
|
+
peaks = copy.deepcopy(basic_peaks)
|
|
538
569
|
main_peak_i_index = peaks.i_index
|
|
539
570
|
main_peak_r_index = peaks.index
|
|
540
571
|
main_peak_f_index = peaks.f_index
|
|
@@ -566,7 +597,7 @@ def force_peak_finder(
|
|
|
566
597
|
): # seond peak is in the leftside of the max peak #TODO: fix this
|
|
567
598
|
common_point = max([num for num in max_pp if num < main_peak_r_index])
|
|
568
599
|
|
|
569
|
-
print("Left convoluted")
|
|
600
|
+
# print("Left convoluted")
|
|
570
601
|
peak1 = {
|
|
571
602
|
"I.Index": main_peak_i_index,
|
|
572
603
|
"R.Index": max(
|
|
@@ -581,7 +612,7 @@ def force_peak_finder(
|
|
|
581
612
|
}
|
|
582
613
|
else:
|
|
583
614
|
common_point = next((x for x in max_pp if x > main_peak_r_index), None)
|
|
584
|
-
print("Right convoluted")
|
|
615
|
+
# print("Right convoluted")
|
|
585
616
|
peak1 = {
|
|
586
617
|
"I.Index": main_peak_i_index,
|
|
587
618
|
"R.Index": main_peak_r_index,
|
|
@@ -615,13 +646,11 @@ def force_peak_finder(
|
|
|
615
646
|
default_render_options={"data": {"src": "figure"}},
|
|
616
647
|
outputs=[{"name": "figure"}],
|
|
617
648
|
)
|
|
618
|
-
def plot_peaks(
|
|
619
|
-
x_array: np.array, y_array: np.array, peaks_dict: List[PeakProperties]
|
|
620
|
-
) -> go.Figure:
|
|
649
|
+
def plot_peaks(x: np.array, y: np.array, peaks_dict: List[PeakProperties]) -> go.Figure:
|
|
621
650
|
fig = go.Figure()
|
|
622
651
|
|
|
623
652
|
# Set up line plot
|
|
624
|
-
plot_trace = {"x":
|
|
653
|
+
plot_trace = {"x": x, "y": y, "mode": "lines", "name": "data"}
|
|
625
654
|
|
|
626
655
|
fig.add_trace(go.Scatter(**plot_trace))
|
|
627
656
|
|
|
@@ -633,16 +662,16 @@ def plot_peaks(
|
|
|
633
662
|
initial_idx = peak.i_index
|
|
634
663
|
ending_idx = peak.f_index
|
|
635
664
|
peak_height = peak.y_at_index
|
|
636
|
-
plot_y_min = min(
|
|
665
|
+
plot_y_min = min(y[initial_idx], y[ending_idx])
|
|
637
666
|
|
|
638
667
|
# Create a scatter trace that simulates a rectangle
|
|
639
668
|
fig.add_trace(
|
|
640
669
|
go.Scatter(
|
|
641
670
|
x=[
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
671
|
+
x[initial_idx],
|
|
672
|
+
x[ending_idx],
|
|
673
|
+
x[ending_idx],
|
|
674
|
+
x[initial_idx],
|
|
646
675
|
],
|
|
647
676
|
y=[plot_y_min, plot_y_min, peak_height, peak_height],
|
|
648
677
|
fill="toself",
|
|
@@ -689,10 +718,10 @@ def plot_fitted_peaks(peaks: List[PeakProperties]) -> go.Figure:
|
|
|
689
718
|
if not peak._is_fitted:
|
|
690
719
|
raise ValueError("No fitting information is available.")
|
|
691
720
|
|
|
692
|
-
x = peak.fitting_info
|
|
721
|
+
x = peak.fitting_info.userkws["x"]
|
|
693
722
|
# Extract data from peaks
|
|
694
|
-
y = peak.fitting_info
|
|
695
|
-
best_fit = peak.fitting_info
|
|
723
|
+
y = peak.fitting_info.data
|
|
724
|
+
best_fit = peak.fitting_info.best_fit
|
|
696
725
|
|
|
697
726
|
# Create a subplot with 1 row, 1 column, and a secondary y-axis
|
|
698
727
|
fig = make_subplots(specs=[[{"secondary_y": True}]])
|
|
@@ -741,8 +770,8 @@ def plot_fitted_peaks(peaks: List[PeakProperties]) -> go.Figure:
|
|
|
741
770
|
fig.update_yaxes(title_text="Baseline corrected", secondary_y=True)
|
|
742
771
|
fig.update_layout(
|
|
743
772
|
title={
|
|
744
|
-
"text": f"{peak.fitting_info
|
|
745
|
-
f"score = {np.round(peak.fitting_info
|
|
773
|
+
"text": f"{peak.fitting_info.model_name} model with fitting "
|
|
774
|
+
f"score = {np.round(peak.fitting_info.rsquared, 4)}",
|
|
746
775
|
"x": 0.5, # Center the title
|
|
747
776
|
"xanchor": "center",
|
|
748
777
|
},
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
from funcnodes import NodeDecorator, Shelf
|
|
2
|
-
import numpy as np
|
|
3
|
-
|
|
4
|
-
import funcnodes as fn
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
class NormMode(fn.DataEnum):
|
|
8
|
-
ZERO_ONE = "zero_one"
|
|
9
|
-
MINUS_ONE_ONE = "minus_one_one"
|
|
10
|
-
SUM_ABS = "sum_abs"
|
|
11
|
-
SUM = "sum"
|
|
12
|
-
EUCLIDEAN = "euclidean"
|
|
13
|
-
MEAN_STD = "mean_std"
|
|
14
|
-
MAX = "max"
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
@NodeDecorator(id="span.basics.norm", name="Normalization node")
|
|
18
|
-
def _norm(array: np.ndarray, mode: NormMode = NormMode.ZERO_ONE) -> np.ndarray:
|
|
19
|
-
# """
|
|
20
|
-
# Apply different normalizations to the array.
|
|
21
|
-
|
|
22
|
-
# Args:
|
|
23
|
-
# array (np.ndarray): The input array to be normalized.
|
|
24
|
-
# mode (NormMode): The normalization mode to apply. Defaults to NormMode.ZERO_ONE.
|
|
25
|
-
|
|
26
|
-
# Returns:
|
|
27
|
-
# np.ndarray: The normalized array.
|
|
28
|
-
|
|
29
|
-
# Raises:
|
|
30
|
-
# ValueError: If an unsupported normalization mode is provided.
|
|
31
|
-
# """
|
|
32
|
-
mode = NormMode.v(mode)
|
|
33
|
-
normalization_methods = {
|
|
34
|
-
NormMode.ZERO_ONE.value: lambda x: (x - np.amin(x)) / (np.amax(x) - np.amin(x)),
|
|
35
|
-
NormMode.MINUS_ONE_ONE.value: lambda x: 2
|
|
36
|
-
* ((x - np.amin(x)) / (np.amax(x) - np.amin(x)))
|
|
37
|
-
- 1,
|
|
38
|
-
NormMode.SUM_ABS.value: lambda x: x / np.abs(x).sum(),
|
|
39
|
-
NormMode.SUM.value: lambda x: x / x.sum(),
|
|
40
|
-
NormMode.EUCLIDEAN.value: lambda x: x / np.sqrt((x**2).sum()),
|
|
41
|
-
NormMode.MEAN_STD.value: lambda x: (x - x.mean()) / x.std(),
|
|
42
|
-
NormMode.MAX.value: lambda x: x / x.max(),
|
|
43
|
-
}
|
|
44
|
-
if mode not in normalization_methods.keys():
|
|
45
|
-
raise ValueError(f"Unsupported normalization mode: {mode}")
|
|
46
|
-
return normalization_methods[mode](array)
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
NORM_NODE_SHELF = Shelf(
|
|
50
|
-
nodes=[_norm],
|
|
51
|
-
subshelves=[],
|
|
52
|
-
name="Normalization",
|
|
53
|
-
description="Normalization of the spectra",
|
|
54
|
-
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|