funcnodes-span 0.2.3__tar.gz → 0.3.0__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.3.0/LICENSE +21 -0
- {funcnodes_span-0.2.3 → funcnodes_span-0.3.0}/PKG-INFO +10 -7
- {funcnodes_span-0.2.3 → funcnodes_span-0.3.0}/funcnodes_span/__init__.py +4 -2
- funcnodes_span-0.3.0/funcnodes_span/_baseline.py +93 -0
- funcnodes_span-0.3.0/funcnodes_span/_curves.py +104 -0
- funcnodes_span-0.3.0/funcnodes_span/_smoothing.py +0 -0
- {funcnodes_span-0.2.3 → funcnodes_span-0.3.0}/funcnodes_span/baseline.py +478 -167
- funcnodes_span-0.3.0/funcnodes_span/curves.py +21 -0
- funcnodes_span-0.3.0/funcnodes_span/fitting.py +383 -0
- funcnodes_span-0.3.0/funcnodes_span/peak_analysis.py +577 -0
- funcnodes_span-0.3.0/funcnodes_span/peaks.py +416 -0
- {funcnodes_span-0.2.3 → funcnodes_span-0.3.0}/funcnodes_span/smoothing.py +20 -5
- {funcnodes_span-0.2.3 → funcnodes_span-0.3.0}/pyproject.toml +14 -8
- funcnodes_span-0.2.3/funcnodes_span/peak_analysis.py +0 -795
- {funcnodes_span-0.2.3 → funcnodes_span-0.3.0}/README.md +0 -0
- {funcnodes_span-0.2.3 → funcnodes_span-0.3.0}/funcnodes_span/normalization.py +0 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Linkdlab GmbH
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -1,25 +1,28 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: funcnodes-span
|
|
3
|
-
Version: 0.
|
|
4
|
-
Summary:
|
|
5
|
-
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Spectral Peak ANalysis (SPAN) for funcnodes
|
|
5
|
+
License: MIT
|
|
6
6
|
Author: Kourosh Rezaei
|
|
7
7
|
Author-email: kouroshrezaei90@gmail.com
|
|
8
8
|
Requires-Python: >=3.11
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
10
|
Classifier: Programming Language :: Python :: 3
|
|
10
11
|
Classifier: Programming Language :: Python :: 3.11
|
|
11
12
|
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
-
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
13
14
|
Requires-Dist: funcnodes
|
|
14
|
-
Requires-Dist: funcnodes-
|
|
15
|
+
Requires-Dist: funcnodes-lmfit
|
|
15
16
|
Requires-Dist: funcnodes_numpy
|
|
16
17
|
Requires-Dist: funcnodes_pandas
|
|
17
18
|
Requires-Dist: funcnodes_plotly
|
|
18
19
|
Requires-Dist: lmfit
|
|
19
|
-
Requires-Dist: pentapy
|
|
20
20
|
Requires-Dist: pybaselines
|
|
21
21
|
Requires-Dist: scipy
|
|
22
|
-
Project-URL:
|
|
22
|
+
Project-URL: download, https://pypi.org/project/funcnodes-span/#files
|
|
23
|
+
Project-URL: homepage, https://github.com/Linkdlab/funcnodes_span
|
|
24
|
+
Project-URL: source, https://github.com/Linkdlab/funcnodes_span
|
|
25
|
+
Project-URL: tracker, https://github.com/Linkdlab/funcnodes_span/issues
|
|
23
26
|
Description-Content-Type: text/markdown
|
|
24
27
|
|
|
25
28
|
Spectral Peak ANalysis (SPAN) for funcnodes
|
|
@@ -4,12 +4,14 @@ from .normalization import NORM_NODE_SHELF as NORM
|
|
|
4
4
|
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
|
+
from funcnodes_lmfit import NODE_SHELF as LMFIT_NODE_SHELF
|
|
8
|
+
from .curves import CURVES_NODE_SHELF
|
|
7
9
|
|
|
8
|
-
__version__ = "0.
|
|
10
|
+
__version__ = "0.3.0"
|
|
9
11
|
|
|
10
12
|
NODE_SHELF = fn.Shelf(
|
|
11
13
|
name="Spectral Analysis",
|
|
12
14
|
description="Spectral analysis for funcnodes",
|
|
13
15
|
nodes=[],
|
|
14
|
-
subshelves=[NORM, SMOOTH, BASELINE, PEAK],
|
|
16
|
+
subshelves=[NORM, SMOOTH, BASELINE, PEAK, CURVES_NODE_SHELF, LMFIT_NODE_SHELF],
|
|
15
17
|
)
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
from typing import Tuple, Optional
|
|
2
|
+
import numpy as np
|
|
3
|
+
from scipy.ndimage import gaussian_filter1d
|
|
4
|
+
from ._curves import knee_point_detection, outlier_detection_std
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def estimate_baseline_regions(
|
|
8
|
+
x: np.ndarray,
|
|
9
|
+
y: np.ndarray,
|
|
10
|
+
pre_flatted_y: Optional[np.ndarray] = None,
|
|
11
|
+
window: Optional[float] = None,
|
|
12
|
+
) -> Tuple[np.ndarray, np.ndarray]:
|
|
13
|
+
"""
|
|
14
|
+
Estimate the baseline regions of a curve.
|
|
15
|
+
|
|
16
|
+
Parameters
|
|
17
|
+
----------
|
|
18
|
+
x : np.ndarray
|
|
19
|
+
The x values of the curve.
|
|
20
|
+
y : np.ndarray
|
|
21
|
+
The y values of the curve.
|
|
22
|
+
pre_flatted_y : np.ndarray, optional
|
|
23
|
+
The y values of the curve when flattet.
|
|
24
|
+
window : float, optional
|
|
25
|
+
The window size for the gaussian filter.relative to the x values. Default is 1% of the x values.
|
|
26
|
+
|
|
27
|
+
Returns
|
|
28
|
+
-------
|
|
29
|
+
y_bl : np.ndarray
|
|
30
|
+
The baseline of the curve, linearly interpolated between the baseline regions.
|
|
31
|
+
is_baseline_region : np.ndarray
|
|
32
|
+
A boolean array indicating the baseline region.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
if window is None:
|
|
36
|
+
window = len(y) / 100 # default value as absolute number of points
|
|
37
|
+
else:
|
|
38
|
+
# if window is a float, the number of points is calculated as as if window is in x units
|
|
39
|
+
window = int(window / (np.median(np.diff(x)) if x is not None else 1) + 0.5)
|
|
40
|
+
|
|
41
|
+
window = max(1, window)
|
|
42
|
+
|
|
43
|
+
if pre_flatted_y is None:
|
|
44
|
+
pre_flatted_y = y
|
|
45
|
+
|
|
46
|
+
smoothed_baseline_corrected_y = gaussian_filter1d(pre_flatted_y, window)
|
|
47
|
+
|
|
48
|
+
percentage_cutoff = np.arange(0, 1, 0.0001)
|
|
49
|
+
|
|
50
|
+
abs_smoothed_baseline_corrected_y = (
|
|
51
|
+
smoothed_baseline_corrected_y + smoothed_baseline_corrected_y.min()
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
norm_smoothed_y = abs_smoothed_baseline_corrected_y / np.max(
|
|
55
|
+
abs_smoothed_baseline_corrected_y
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
norm_y = np.abs(pre_flatted_y)
|
|
59
|
+
norm_y = norm_y / np.max(norm_y)
|
|
60
|
+
|
|
61
|
+
cumulative_n_points = np.sum(norm_smoothed_y[:, None] < percentage_cutoff, axis=0)
|
|
62
|
+
cumulative_n_points = cumulative_n_points / np.max(cumulative_n_points)
|
|
63
|
+
|
|
64
|
+
elbow_idx, elbow_x, elbow_y = knee_point_detection(
|
|
65
|
+
percentage_cutoff, cumulative_n_points
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
is_baseline_region = norm_y <= elbow_x
|
|
69
|
+
|
|
70
|
+
outliers = outlier_detection_std(
|
|
71
|
+
x[is_baseline_region],
|
|
72
|
+
norm_y[is_baseline_region],
|
|
73
|
+
threshold=3,
|
|
74
|
+
max_iterations=9,
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
is_baseline_region[is_baseline_region] &= ~outliers
|
|
78
|
+
|
|
79
|
+
is_baseline_region = is_baseline_region.astype(float)
|
|
80
|
+
|
|
81
|
+
smoothed_is_baseline_region = gaussian_filter1d(is_baseline_region, window)
|
|
82
|
+
smoothed_is_baseline_region = (
|
|
83
|
+
smoothed_is_baseline_region >= 0.99 * smoothed_is_baseline_region.max()
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
if np.any(smoothed_is_baseline_region):
|
|
87
|
+
y_bl = np.interp(
|
|
88
|
+
x, x[smoothed_is_baseline_region], y[smoothed_is_baseline_region]
|
|
89
|
+
)
|
|
90
|
+
else:
|
|
91
|
+
y_bl = np.zeros_like(y)
|
|
92
|
+
|
|
93
|
+
return y_bl, smoothed_is_baseline_region
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from typing import Optional, Tuple
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def knee_point_detection(x, y) -> Tuple[int, float, float]:
|
|
6
|
+
"""
|
|
7
|
+
Detect the knee (or elbow) point in a curve.
|
|
8
|
+
|
|
9
|
+
Parameters:
|
|
10
|
+
x (numpy array): X-values of the curve.
|
|
11
|
+
y (numpy array): Y-values of the curve.
|
|
12
|
+
|
|
13
|
+
Returns:
|
|
14
|
+
int: Index of the knee point in the input arrays.
|
|
15
|
+
float: X-value of the knee point.
|
|
16
|
+
float: Y-value of the knee point.
|
|
17
|
+
|
|
18
|
+
"""
|
|
19
|
+
# Normalize x and y to have values between 0 and 1
|
|
20
|
+
x_normalized = (x - np.min(x)) / (np.max(x) - np.min(x))
|
|
21
|
+
y_normalized = (y - np.min(y)) / (np.max(y) - np.min(y))
|
|
22
|
+
|
|
23
|
+
# Line connecting the first and last points
|
|
24
|
+
line_vec = np.array(
|
|
25
|
+
[x_normalized[-1] - x_normalized[0], y_normalized[-1] - y_normalized[0]]
|
|
26
|
+
)
|
|
27
|
+
line_vec_norm = np.sqrt(
|
|
28
|
+
line_vec[0] ** 2 + line_vec[1] ** 2
|
|
29
|
+
) # Length of the line vector
|
|
30
|
+
line_vec = line_vec / line_vec_norm # Normalize the line vector
|
|
31
|
+
|
|
32
|
+
# Calculate the distances from all points to the line
|
|
33
|
+
vec_from_first = np.column_stack(
|
|
34
|
+
[x_normalized - x_normalized[0], y_normalized - y_normalized[0]]
|
|
35
|
+
)
|
|
36
|
+
scalar_proj = np.dot(vec_from_first, line_vec)
|
|
37
|
+
vec_along_line = np.outer(scalar_proj, line_vec)
|
|
38
|
+
vec_to_line = vec_from_first - vec_along_line
|
|
39
|
+
|
|
40
|
+
# Compute the distances
|
|
41
|
+
distances = np.sqrt(np.sum(vec_to_line**2, axis=1))
|
|
42
|
+
|
|
43
|
+
# Find the index of the maximum distance (knee point)
|
|
44
|
+
knee_index = np.argmax(distances)
|
|
45
|
+
|
|
46
|
+
return knee_index, x[knee_index], y[knee_index]
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def estimate_noise(
|
|
50
|
+
x, y, is_baseline_region: Optional[np.ndarray] = None, std_factor=3
|
|
51
|
+
) -> float:
|
|
52
|
+
"""
|
|
53
|
+
Estimate the noise level in a signal.
|
|
54
|
+
x (numpy array): X-values of the signal.
|
|
55
|
+
y (numpy array): Y-values of the signal, assumed to be baseline corrected
|
|
56
|
+
baseline_region (numpy array): Boolean array indicating the baseline regions,
|
|
57
|
+
here the noise will be estamated. Calcualted automatically if not provided.
|
|
58
|
+
std_factor (float): Factor to multiply the standard deviation of the baseline region to estimate the noise level.
|
|
59
|
+
"""
|
|
60
|
+
if is_baseline_region is None:
|
|
61
|
+
from ._baseline import estimate_baseline_regions
|
|
62
|
+
|
|
63
|
+
ybl, is_baseline_region = estimate_baseline_regions(x, y)
|
|
64
|
+
if is_baseline_region.sum() == 0:
|
|
65
|
+
raise ValueError("No baseline region found in the input data.")
|
|
66
|
+
|
|
67
|
+
std = np.std(y[is_baseline_region])
|
|
68
|
+
noise = std_factor * std
|
|
69
|
+
return noise
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def outlier_detection_std(
|
|
73
|
+
x: np.ndarray, y: np.ndarray, threshold: float = 3, max_iterations: int = 1
|
|
74
|
+
) -> np.ndarray:
|
|
75
|
+
"""
|
|
76
|
+
Detect outliers in a signal based on the standard deviation of the signal.
|
|
77
|
+
|
|
78
|
+
Parameters:
|
|
79
|
+
x (numpy array): X-values of the signal.
|
|
80
|
+
y (numpy array): Y-values of the signal.
|
|
81
|
+
threshold (float): Threshold for the standard deviation to detect outliers.
|
|
82
|
+
max_iterations (int): Maximum number of iterations to detect outliers.
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
numpy array: A boolean array where True indicates an outlier in the signal.
|
|
86
|
+
"""
|
|
87
|
+
|
|
88
|
+
is_outlier = np.zeros_like(y, dtype=bool)
|
|
89
|
+
n_outliers = 0
|
|
90
|
+
for _ in range(max_iterations):
|
|
91
|
+
new_outliers = y[~is_outlier]
|
|
92
|
+
if new_outliers.size == 0: # Handle the case of no non-outliers left
|
|
93
|
+
break
|
|
94
|
+
|
|
95
|
+
std = np.nanstd(new_outliers)
|
|
96
|
+
mean = np.nanmean(new_outliers)
|
|
97
|
+
is_outlier[~is_outlier] = np.abs(y[~is_outlier] - mean) > threshold * std
|
|
98
|
+
|
|
99
|
+
current_outliers = is_outlier.sum()
|
|
100
|
+
if current_outliers == n_outliers:
|
|
101
|
+
break
|
|
102
|
+
n_outliers = current_outliers
|
|
103
|
+
|
|
104
|
+
return is_outlier
|
|
File without changes
|