funcnodes-span 0.2.2__tar.gz → 0.2.5__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.2 → funcnodes_span-0.2.5}/PKG-INFO +1 -1
- {funcnodes_span-0.2.2 → funcnodes_span-0.2.5}/funcnodes_span/__init__.py +1 -1
- {funcnodes_span-0.2.2 → funcnodes_span-0.2.5}/funcnodes_span/peak_analysis.py +63 -47
- {funcnodes_span-0.2.2 → funcnodes_span-0.2.5}/pyproject.toml +2 -1
- {funcnodes_span-0.2.2 → funcnodes_span-0.2.5}/README.md +0 -0
- {funcnodes_span-0.2.2 → funcnodes_span-0.2.5}/funcnodes_span/baseline.py +0 -0
- {funcnodes_span-0.2.2 → funcnodes_span-0.2.5}/funcnodes_span/normalization.py +0 -0
- {funcnodes_span-0.2.2 → funcnodes_span-0.2.5}/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.5"
|
|
9
9
|
|
|
10
10
|
NODE_SHELF = fn.Shelf(
|
|
11
11
|
name="Spectral Analysis",
|
|
@@ -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
|
|
@@ -22,6 +22,8 @@ class FittinInfo:
|
|
|
22
22
|
best_values: dict
|
|
23
23
|
data: np.ndarray
|
|
24
24
|
userkws: dict
|
|
25
|
+
best_fit: np.ndarray
|
|
26
|
+
rsquared: float
|
|
25
27
|
|
|
26
28
|
|
|
27
29
|
@dataclass
|
|
@@ -198,8 +200,8 @@ def compute_peak_properties(
|
|
|
198
200
|
@NodeDecorator(id="span.basics.peaks", name="Peak finder")
|
|
199
201
|
@controlled_wrapper(find_peaks, wrapper_attribute="__fnwrapped__")
|
|
200
202
|
def peak_finder(
|
|
201
|
-
|
|
202
|
-
|
|
203
|
+
x: np.ndarray,
|
|
204
|
+
y: np.ndarray,
|
|
203
205
|
noise_level: Optional[int] = None,
|
|
204
206
|
height: Optional[float] = None,
|
|
205
207
|
threshold: Optional[float] = None,
|
|
@@ -211,8 +213,8 @@ def peak_finder(
|
|
|
211
213
|
plateau_size: Optional[int] = None,
|
|
212
214
|
) -> List[PeakProperties]:
|
|
213
215
|
peak_lst = []
|
|
214
|
-
|
|
215
|
-
|
|
216
|
+
x = np.array(x, dtype=float)
|
|
217
|
+
y = np.array(y, dtype=float)
|
|
216
218
|
noise_level = int(noise_level) if noise_level is not None else None
|
|
217
219
|
height = float(height) if height is not None else None
|
|
218
220
|
threshold = float(threshold) if threshold is not None else None
|
|
@@ -223,16 +225,16 @@ def peak_finder(
|
|
|
223
225
|
rel_height = float(rel_height)
|
|
224
226
|
plateau_size = float(plateau_size) if plateau_size is not None else None
|
|
225
227
|
|
|
226
|
-
height = 0.05 * np.max(
|
|
228
|
+
height = 0.05 * np.max(y) if height is None else height
|
|
227
229
|
noise_level = 5000 if noise_level is None else noise_level
|
|
228
230
|
|
|
229
|
-
if
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
231
|
+
if x is not None:
|
|
232
|
+
x, y = density_normalization(
|
|
233
|
+
x,
|
|
234
|
+
y,
|
|
233
235
|
)
|
|
234
236
|
|
|
235
|
-
xdiff =
|
|
237
|
+
xdiff = x[1] - x[0]
|
|
236
238
|
# if x is given width is based on the x scale and has to be converted to index
|
|
237
239
|
if width is not None:
|
|
238
240
|
width = width / xdiff
|
|
@@ -250,7 +252,7 @@ def peak_finder(
|
|
|
250
252
|
plateau_size = plateau_size / xdiff
|
|
251
253
|
|
|
252
254
|
# Make a copy of the input array
|
|
253
|
-
y_array_copy = np.copy(
|
|
255
|
+
y_array_copy = np.copy(y)
|
|
254
256
|
|
|
255
257
|
# Find the peaks in the copy of the input array
|
|
256
258
|
peaks, _ = find_peaks(
|
|
@@ -287,7 +289,7 @@ def peak_finder(
|
|
|
287
289
|
# Find the right minimum of the peak
|
|
288
290
|
right_min = mins[np.argmax(mins > peak)]
|
|
289
291
|
if right_min < peak:
|
|
290
|
-
right_min = len(
|
|
292
|
+
right_min = len(y) - 1
|
|
291
293
|
|
|
292
294
|
try:
|
|
293
295
|
# Find the left minimum of the peak
|
|
@@ -313,7 +315,7 @@ def peak_finder(
|
|
|
313
315
|
for peak in peaks:
|
|
314
316
|
right_min = mins[np.argmax(mins > peak)]
|
|
315
317
|
if right_min < peak:
|
|
316
|
-
right_min = len(
|
|
318
|
+
right_min = len(y) - 1
|
|
317
319
|
try:
|
|
318
320
|
left_min = np.array(mins)[np.where(np.array(mins) < peak)][-1]
|
|
319
321
|
except IndexError:
|
|
@@ -331,7 +333,7 @@ def peak_finder(
|
|
|
331
333
|
|
|
332
334
|
for peak_nr, peak in enumerate(peak_lst):
|
|
333
335
|
peak_properties = compute_peak_properties(
|
|
334
|
-
x_array=
|
|
336
|
+
x_array=x, y_array=y, peak_indices=peak, peak_nr=peak_nr
|
|
335
337
|
)
|
|
336
338
|
peak_properties_list.append(peak_properties)
|
|
337
339
|
|
|
@@ -406,8 +408,8 @@ class BaselineModel(fn.DataEnum):
|
|
|
406
408
|
|
|
407
409
|
@NodeDecorator(id="span.basics.fit", name="Fit 1D", separate_process=True)
|
|
408
410
|
def fit_1D(
|
|
409
|
-
|
|
410
|
-
|
|
411
|
+
x: np.ndarray,
|
|
412
|
+
y: np.ndarray,
|
|
411
413
|
basic_peaks: List[PeakProperties],
|
|
412
414
|
main_model: FittingModel = FittingModel.Gaussian,
|
|
413
415
|
baseline_model: BaselineModel = BaselineModel.Exponential,
|
|
@@ -432,15 +434,14 @@ def fit_1D(
|
|
|
432
434
|
|
|
433
435
|
# """
|
|
434
436
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
+
x = np.array(x)
|
|
438
|
+
y = np.array(y)
|
|
437
439
|
|
|
438
440
|
main_model = FittingModel.v(main_model)
|
|
439
441
|
|
|
440
442
|
baseline_model = BaselineModel.v(baseline_model)
|
|
441
443
|
peaks = copy.deepcopy(basic_peaks)
|
|
442
|
-
|
|
443
|
-
x = x_array
|
|
444
|
+
|
|
444
445
|
# if is_sturated:
|
|
445
446
|
|
|
446
447
|
lowest_index = min(dictionary.i_index for dictionary in peaks)
|
|
@@ -510,18 +511,20 @@ def fit_1D(
|
|
|
510
511
|
best_values=out.best_values,
|
|
511
512
|
data=out.data,
|
|
512
513
|
userkws=out.userkws,
|
|
514
|
+
best_fit=out.best_fit,
|
|
515
|
+
rsquared=out.rsquared,
|
|
513
516
|
)
|
|
514
517
|
|
|
515
518
|
peak_properties_list = []
|
|
516
519
|
|
|
517
520
|
for key in com.keys():
|
|
518
521
|
if key != "baseline":
|
|
519
|
-
|
|
520
|
-
peak_lst = [(0, np.argmax(
|
|
522
|
+
y = com[key]
|
|
523
|
+
peak_lst = [(0, np.argmax(y), len(y) - 1)]
|
|
521
524
|
for peak_nr, peak in enumerate(peak_lst):
|
|
522
525
|
peak_properties = compute_peak_properties(
|
|
523
|
-
x_array=
|
|
524
|
-
y_array=
|
|
526
|
+
x_array=x,
|
|
527
|
+
y_array=y,
|
|
525
528
|
peak_indices=peak,
|
|
526
529
|
peak_nr=peak_nr,
|
|
527
530
|
is_fitted=True,
|
|
@@ -540,7 +543,7 @@ def fit_1D(
|
|
|
540
543
|
def force_peak_finder(
|
|
541
544
|
x: np.array,
|
|
542
545
|
y: np.array,
|
|
543
|
-
basic_peaks: List[PeakProperties],
|
|
546
|
+
basic_peaks: Union[List[PeakProperties], PeakProperties],
|
|
544
547
|
) -> List[PeakProperties]:
|
|
545
548
|
# """
|
|
546
549
|
# Identify and return the two peaks around the main peak in the given peaks dictionary.
|
|
@@ -555,10 +558,14 @@ def force_peak_finder(
|
|
|
555
558
|
# Returns:
|
|
556
559
|
# - dict: A dictionary containing information about the two identified peaks.
|
|
557
560
|
# """
|
|
558
|
-
if
|
|
559
|
-
|
|
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]
|
|
560
567
|
|
|
561
|
-
peaks = copy.deepcopy(basic_peaks
|
|
568
|
+
peaks = copy.deepcopy(basic_peaks)
|
|
562
569
|
main_peak_i_index = peaks.i_index
|
|
563
570
|
main_peak_r_index = peaks.index
|
|
564
571
|
main_peak_f_index = peaks.f_index
|
|
@@ -590,7 +597,7 @@ def force_peak_finder(
|
|
|
590
597
|
): # seond peak is in the leftside of the max peak #TODO: fix this
|
|
591
598
|
common_point = max([num for num in max_pp if num < main_peak_r_index])
|
|
592
599
|
|
|
593
|
-
print("Left convoluted")
|
|
600
|
+
# print("Left convoluted")
|
|
594
601
|
peak1 = {
|
|
595
602
|
"I.Index": main_peak_i_index,
|
|
596
603
|
"R.Index": max(
|
|
@@ -605,7 +612,7 @@ def force_peak_finder(
|
|
|
605
612
|
}
|
|
606
613
|
else:
|
|
607
614
|
common_point = next((x for x in max_pp if x > main_peak_r_index), None)
|
|
608
|
-
print("Right convoluted")
|
|
615
|
+
# print("Right convoluted")
|
|
609
616
|
peak1 = {
|
|
610
617
|
"I.Index": main_peak_i_index,
|
|
611
618
|
"R.Index": main_peak_r_index,
|
|
@@ -639,13 +646,11 @@ def force_peak_finder(
|
|
|
639
646
|
default_render_options={"data": {"src": "figure"}},
|
|
640
647
|
outputs=[{"name": "figure"}],
|
|
641
648
|
)
|
|
642
|
-
def plot_peaks(
|
|
643
|
-
x_array: np.array, y_array: np.array, peaks_dict: List[PeakProperties]
|
|
644
|
-
) -> go.Figure:
|
|
649
|
+
def plot_peaks(x: np.array, y: np.array, peaks_dict: List[PeakProperties]) -> go.Figure:
|
|
645
650
|
fig = go.Figure()
|
|
646
651
|
|
|
647
652
|
# Set up line plot
|
|
648
|
-
plot_trace = {"x":
|
|
653
|
+
plot_trace = {"x": x, "y": y, "mode": "lines", "name": "data"}
|
|
649
654
|
|
|
650
655
|
fig.add_trace(go.Scatter(**plot_trace))
|
|
651
656
|
|
|
@@ -654,19 +659,17 @@ def plot_peaks(
|
|
|
654
659
|
|
|
655
660
|
# Add rectangle shapes for each peak
|
|
656
661
|
for index, peak in enumerate(peaks_dict):
|
|
657
|
-
initial_idx = peak.i_index
|
|
658
|
-
ending_idx = peak.f_index
|
|
659
662
|
peak_height = peak.y_at_index
|
|
660
|
-
plot_y_min = min(
|
|
663
|
+
plot_y_min = min(peak.y_at_i_index, peak.y_at_f_index)
|
|
661
664
|
|
|
662
665
|
# Create a scatter trace that simulates a rectangle
|
|
663
666
|
fig.add_trace(
|
|
664
667
|
go.Scatter(
|
|
665
668
|
x=[
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
669
|
+
peak.x_at_i_index,
|
|
670
|
+
peak.x_at_f_index,
|
|
671
|
+
peak.x_at_f_index,
|
|
672
|
+
peak.x_at_i_index,
|
|
670
673
|
],
|
|
671
674
|
y=[plot_y_min, plot_y_min, peak_height, peak_height],
|
|
672
675
|
fill="toself",
|
|
@@ -675,6 +678,19 @@ def plot_peaks(
|
|
|
675
678
|
line=dict(width=0),
|
|
676
679
|
mode="lines",
|
|
677
680
|
name=f"Peak {peak.id}",
|
|
681
|
+
legendgroup=f"Peak {peak.id}", # Group by Peak id
|
|
682
|
+
showlegend=True,
|
|
683
|
+
)
|
|
684
|
+
)
|
|
685
|
+
# Add an X marker at the exact peak position
|
|
686
|
+
fig.add_trace(
|
|
687
|
+
go.Scatter(
|
|
688
|
+
x=[peak.x_at_index],
|
|
689
|
+
y=[peak.y_at_index],
|
|
690
|
+
mode="markers",
|
|
691
|
+
marker=dict(symbol="x", size=10, color="black"),
|
|
692
|
+
legendgroup=f"Peak {peak.id}", # Same group as rectangle
|
|
693
|
+
showlegend=False,
|
|
678
694
|
)
|
|
679
695
|
)
|
|
680
696
|
|
|
@@ -713,10 +729,10 @@ def plot_fitted_peaks(peaks: List[PeakProperties]) -> go.Figure:
|
|
|
713
729
|
if not peak._is_fitted:
|
|
714
730
|
raise ValueError("No fitting information is available.")
|
|
715
731
|
|
|
716
|
-
x = peak.fitting_info
|
|
732
|
+
x = peak.fitting_info.userkws["x"]
|
|
717
733
|
# Extract data from peaks
|
|
718
|
-
y = peak.fitting_info
|
|
719
|
-
best_fit = peak.fitting_info
|
|
734
|
+
y = peak.fitting_info.data
|
|
735
|
+
best_fit = peak.fitting_info.best_fit
|
|
720
736
|
|
|
721
737
|
# Create a subplot with 1 row, 1 column, and a secondary y-axis
|
|
722
738
|
fig = make_subplots(specs=[[{"secondary_y": True}]])
|
|
@@ -765,8 +781,8 @@ def plot_fitted_peaks(peaks: List[PeakProperties]) -> go.Figure:
|
|
|
765
781
|
fig.update_yaxes(title_text="Baseline corrected", secondary_y=True)
|
|
766
782
|
fig.update_layout(
|
|
767
783
|
title={
|
|
768
|
-
"text": f"{peak.fitting_info
|
|
769
|
-
f"score = {np.round(peak.fitting_info
|
|
784
|
+
"text": f"{peak.fitting_info.model_name} model with fitting "
|
|
785
|
+
f"score = {np.round(peak.fitting_info.rsquared, 4)}",
|
|
770
786
|
"x": 0.5, # Center the title
|
|
771
787
|
"xanchor": "center",
|
|
772
788
|
},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "funcnodes-span"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.5"
|
|
4
4
|
description = ""
|
|
5
5
|
authors = ["Kourosh Rezaei <kouroshrezaei90@gmail.com>"]
|
|
6
6
|
readme = "README.md"
|
|
@@ -26,6 +26,7 @@ pre-commit = "*"
|
|
|
26
26
|
funcnodes-module = "*"
|
|
27
27
|
pooch = "*"
|
|
28
28
|
|
|
29
|
+
|
|
29
30
|
[build-system]
|
|
30
31
|
requires = ["poetry-core"]
|
|
31
32
|
build-backend = "poetry.core.masonry.api"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|