funcnodes-span 0.2.5__tar.gz → 0.3.1__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.
@@ -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.2.5
4
- Summary:
5
- Home-page: https://linkdlab.de/
3
+ Version: 0.3.1
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
- Requires-Dist: Numba
13
+ Classifier: Programming Language :: Python :: 3.13
13
14
  Requires-Dist: funcnodes
14
- Requires-Dist: funcnodes-core (>=0.1.4)
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: Repository, https://github.com/kouroshrezaei/funcnodes_span
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.2.5"
10
+ __version__ = "0.3.1"
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: float = 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