funcnodes-span 0.2.0__tar.gz → 0.2.2__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: funcnodes-span
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary:
5
5
  Home-page: https://linkdlab.de/
6
6
  Author: Kourosh Rezaei
@@ -11,7 +11,7 @@ Classifier: Programming Language :: Python :: 3.11
11
11
  Classifier: Programming Language :: Python :: 3.12
12
12
  Requires-Dist: Numba
13
13
  Requires-Dist: funcnodes
14
- Requires-Dist: funcnodes-core (>=0.1.3)
14
+ Requires-Dist: funcnodes-core (>=0.1.4)
15
15
  Requires-Dist: funcnodes_numpy
16
16
  Requires-Dist: funcnodes_pandas
17
17
  Requires-Dist: funcnodes_plotly
@@ -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.0"
8
+ __version__ = "0.2.2"
9
9
 
10
10
  NODE_SHELF = fn.Shelf(
11
11
  name="Spectral Analysis",
@@ -1,20 +1,16 @@
1
+ import funcnodes as fn
1
2
  from funcnodes import NodeDecorator, Shelf
2
3
  from exposedfunctionality import controlled_wrapper
3
4
  import numpy as np
4
- from enum import Enum
5
5
  from typing import Optional, Tuple, Union, List
6
6
  import pybaselines
7
7
 
8
8
 
9
- class CostFunction(Enum):
9
+ class CostFunction(fn.DataEnum):
10
10
  asymmetric_indec = "asymmetric_indec"
11
11
  asymmetric_truncated_quadratic = "asymmetric_truncated_quadratic"
12
12
  asymmetric_huber = "asymmetric_huber"
13
13
 
14
- @classmethod
15
- def default(cls):
16
- return cls.asymmetric_indec.value
17
-
18
14
 
19
15
  @NodeDecorator(
20
16
  "pybaselines.polynomial.goldindec",
@@ -33,7 +29,7 @@ def _goldindec(
33
29
  tol: float = 0.001,
34
30
  max_iter: int = 250,
35
31
  weights: Optional[np.ndarray] = None,
36
- cost_function: CostFunction = CostFunction.default(),
32
+ cost_function: CostFunction = CostFunction.asymmetric_indec,
37
33
  peak_ratio: float = 0.5,
38
34
  alpha_factor: float = 0.99,
39
35
  tol_2: float = 0.001,
@@ -41,8 +37,7 @@ def _goldindec(
41
37
  max_iter_2: int = 100,
42
38
  return_coef: bool = False,
43
39
  ) -> Tuple[np.ndarray, np.ndarray, dict]:
44
- if isinstance(cost_function, CostFunction):
45
- cost_function = cost_function.value
40
+ cost_function = CostFunction.v(cost_function)
46
41
  baseline, params = pybaselines.polynomial.goldindec(
47
42
  data,
48
43
  x_data=x_data,
@@ -186,7 +181,7 @@ def _modpoly(
186
181
  return baseline_corrected, baseline, params
187
182
 
188
183
 
189
- class PenalizedPolyCostFunction(Enum):
184
+ class PenalizedPolyCostFunction(fn.DataEnum):
190
185
  asymmetric_truncated_quadratic = "asymmetric_truncated_quadratic"
191
186
  symmetric_truncated_quadratic = "symmetric_truncated_quadratic"
192
187
  asymmetric_huber = "asymmetric_huber"
@@ -194,10 +189,6 @@ class PenalizedPolyCostFunction(Enum):
194
189
  asymmetric_indec = "asymmetric_indec"
195
190
  symmetric_indec = "symmetric_indec"
196
191
 
197
- @classmethod
198
- def default(cls):
199
- return cls.asymmetric_truncated_quadratic.value
200
-
201
192
 
202
193
  @NodeDecorator(
203
194
  "pybaselines.polynomial.penalized_poly",
@@ -218,13 +209,12 @@ def _penalized_poly(
218
209
  max_iter: int = 250,
219
210
  tol: float = 1e-3,
220
211
  weights: Optional[np.ndarray] = None,
221
- cost_function: PenalizedPolyCostFunction = PenalizedPolyCostFunction.default(),
212
+ cost_function: PenalizedPolyCostFunction = PenalizedPolyCostFunction.asymmetric_truncated_quadratic,
222
213
  threshold: Optional[float] = None,
223
214
  alpha_factor: float = 0.99,
224
215
  return_coef: bool = False,
225
216
  ) -> Tuple[np.ndarray, np.ndarray, dict]:
226
- if isinstance(cost_function, PenalizedPolyCostFunction):
227
- cost_function = cost_function.value
217
+ cost_function = PenalizedPolyCostFunction.v(cost_function)
228
218
  baseline, params = pybaselines.polynomial.penalized_poly(
229
219
  data,
230
220
  x_data=x_data,
@@ -1540,15 +1530,11 @@ def _noise_median(
1540
1530
  return baseline_corrected, baseline, params
1541
1531
 
1542
1532
 
1543
- class Side(Enum):
1533
+ class Side(fn.DataEnum):
1544
1534
  both = "both"
1545
1535
  left = "left"
1546
1536
  right = "right"
1547
1537
 
1548
- @classmethod
1549
- def default(cls):
1550
- return cls.both.value
1551
-
1552
1538
 
1553
1539
  @NodeDecorator(
1554
1540
  "pybaselines.smooth.ria",
@@ -1566,13 +1552,12 @@ def _ria(
1566
1552
  half_window: Optional[int] = None,
1567
1553
  max_iter: int = 500,
1568
1554
  tol: float = 0.01,
1569
- side: Side = Side.default(),
1555
+ side: Side = Side.both,
1570
1556
  width_scale: float = 0.1,
1571
1557
  height_scale: float = 1.0,
1572
1558
  sigma_scale: float = 1.0 / 12.0,
1573
1559
  ) -> Tuple[np.ndarray, np.ndarray, dict]:
1574
- if isinstance(side, Side):
1575
- side = side.value
1560
+ side = Side.v(side)
1576
1561
  baseline, params = pybaselines.smooth.ria(
1577
1562
  data,
1578
1563
  x_data=x_data,
@@ -1936,14 +1921,10 @@ CLASSIFICATION_NODE_SHELF = Shelf(
1936
1921
  )
1937
1922
 
1938
1923
 
1939
- class Method(Enum):
1924
+ class Method(fn.DataEnum):
1940
1925
  modpoly = "modpoly"
1941
1926
  imodpoly = "imodpoly"
1942
1927
 
1943
- @classmethod
1944
- def default(cls):
1945
- return cls.modpoly.value
1946
-
1947
1928
 
1948
1929
  @NodeDecorator(
1949
1930
  "pybaselines.optimizers.adaptive_minmax",
@@ -1961,14 +1942,13 @@ def _adaptive_minmax(
1961
1942
  data: np.ndarray,
1962
1943
  x_data: Optional[np.ndarray] = None,
1963
1944
  poly_order: Optional[Union[int, List[int]]] = None,
1964
- method: Method = Method.default(),
1945
+ method: Method = Method.modpoly,
1965
1946
  constrained_fraction: Union[float, List[float]] = 0.01,
1966
1947
  constrained_weight: Union[float, List[float]] = 100000.0,
1967
1948
  estimation_poly_order: int = 2,
1968
1949
  weights: Optional[np.ndarray] = None,
1969
1950
  ) -> Tuple[np.ndarray, np.ndarray, dict]:
1970
- if isinstance(method, Method):
1971
- method = method.value
1951
+ method = Method.v(method)
1972
1952
  baseline, params = pybaselines.optimizers.adaptive_minmax(
1973
1953
  data,
1974
1954
  x_data=x_data,
@@ -1983,7 +1963,7 @@ def _adaptive_minmax(
1983
1963
  return baseline_corrected, baseline, params
1984
1964
 
1985
1965
 
1986
- class MethodColab(Enum):
1966
+ class MethodColab(fn.DataEnum):
1987
1967
  airpls = "airpls"
1988
1968
  arpls = "arpls"
1989
1969
  asls = "asls"
@@ -1994,10 +1974,6 @@ class MethodColab(Enum):
1994
1974
  iasls = "iasls"
1995
1975
  psalsa = "psalsa"
1996
1976
 
1997
- @classmethod
1998
- def default(cls):
1999
- return cls.asls.value
2000
-
2001
1977
 
2002
1978
  @NodeDecorator(
2003
1979
  "pybaselines.optimizers.collab_pls",
@@ -2015,10 +1991,9 @@ def _collab_pls(
2015
1991
  data: np.ndarray,
2016
1992
  x_data: Optional[np.ndarray] = None,
2017
1993
  average_dataset: bool = True,
2018
- method: MethodColab = MethodColab.default(),
1994
+ method: MethodColab = MethodColab.asls,
2019
1995
  ) -> Tuple[np.ndarray, np.ndarray, dict]:
2020
- if isinstance(method, MethodColab):
2021
- method = method.value
1996
+ method = MethodColab.v(method)
2022
1997
  baseline, params = pybaselines.optimizers.collab_pls(
2023
1998
  data,
2024
1999
  x_data=x_data,
@@ -2029,7 +2004,7 @@ def _collab_pls(
2029
2004
  return baseline_corrected, baseline, params
2030
2005
 
2031
2006
 
2032
- class MethodAll(Enum):
2007
+ class MethodAll(fn.DataEnum):
2033
2008
  goldindec = "goldindec"
2034
2009
  imodpoly = "imodpoly"
2035
2010
  loess = "loess"
@@ -2076,10 +2051,6 @@ class MethodAll(Enum):
2076
2051
  rubberband = "rubberband"
2077
2052
  std_distribution = "std_distribution"
2078
2053
 
2079
- @classmethod
2080
- def default(cls):
2081
- return cls.asls.value
2082
-
2083
2054
 
2084
2055
  @NodeDecorator(
2085
2056
  "pybaselines.optimizers.custom_bc",
@@ -2097,10 +2068,9 @@ def _custom_bc(
2097
2068
  sampling: Union[int, np.ndarray] = 1,
2098
2069
  lam: Optional[float] = None,
2099
2070
  diff_order: int = 2,
2100
- method: MethodAll = MethodAll.default(),
2071
+ method: MethodAll = MethodAll.asls,
2101
2072
  ) -> Tuple[np.ndarray, np.ndarray, dict]:
2102
- if isinstance(method, MethodAll):
2103
- method = method.value
2073
+ method = MethodAll.v(method)
2104
2074
  baseline, params = pybaselines.optimizers.custom_bc(
2105
2075
  data,
2106
2076
  x_data=x_data,
@@ -2128,19 +2098,17 @@ def _custom_bc(
2128
2098
  def _optimize_extended_range(
2129
2099
  data: np.ndarray,
2130
2100
  x_data: Optional[np.ndarray] = None,
2131
- side: Side = Side.default(),
2101
+ side: Side = Side.both,
2132
2102
  width_scale: float = 0.1,
2133
2103
  height_scale: float = 1.0,
2134
2104
  sigma_scale: float = 1.0 / 12.0,
2135
2105
  min_value: float = 2.0,
2136
2106
  max_value: float = 8.0,
2137
2107
  step: int = 1,
2138
- method: MethodAll = MethodAll.default(),
2108
+ method: MethodAll = MethodAll.asls,
2139
2109
  ) -> Tuple[np.ndarray, np.ndarray, dict]:
2140
- if isinstance(method, MethodAll):
2141
- method = method.value
2142
- if isinstance(side, Side):
2143
- side = side.value
2110
+ method = MethodAll.v(method)
2111
+ side = Side.v(side)
2144
2112
  baseline, params = pybaselines.optimizers.optimize_extended_range(
2145
2113
  data,
2146
2114
  x_data=x_data,
@@ -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
+ )
@@ -1,8 +1,8 @@
1
1
  from funcnodes import NodeDecorator, Shelf
2
+ import funcnodes as fn
2
3
  import numpy as np
3
- from enum import Enum
4
4
  from exposedfunctionality import controlled_wrapper
5
- from typing import Optional, List, Tuple
5
+ from typing import Optional, List, Tuple, Dict
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,15 @@ 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
17
+
18
+
19
+ @dataclass
20
+ class FittinInfo:
21
+ model_name: str
22
+ best_values: dict
23
+ data: np.ndarray
24
+ userkws: dict
16
25
 
17
26
 
18
27
  @dataclass
@@ -35,8 +44,8 @@ class PeakProperties:
35
44
  width: float
36
45
  _is_fitted: bool = False
37
46
  _is_force_fitted: bool = False
38
- fitting_data: Optional[dict] = None
39
- fitting_info: Optional[dict] = None
47
+ fitting_data: Optional[Dict[str, np.ndarray]] = None
48
+ fitting_info: Optional[FittinInfo] = None
40
49
 
41
50
 
42
51
  def compute_peak_properties(
@@ -46,25 +55,27 @@ def compute_peak_properties(
46
55
  peak_nr: int,
47
56
  is_fitted: bool = False,
48
57
  is_force_fitted: bool = False,
49
- fitting_data: Optional[dict] = None,
50
- fitting_info: Optional[dict] = None,
58
+ fitting_data: Optional[Dict[str, np.ndarray]] = None,
59
+ fitting_info: Optional[FittinInfo] = None,
51
60
  ) -> PeakProperties:
52
- # """
53
- # Compute various properties of a given peak.
54
-
55
- # Parameters:
56
- # - x_array: np.ndarray - The array of x-values (e.g., time or wavelength).
57
- # - y_array: np.ndarray - The array of y-values (e.g., intensity).
58
- # - peak_indices: List[int] - A list containing the start index, peak index, and end index of the peak.
59
- # - peak_nr: int - The identifier number of the peak.
60
- # - is_fitted: bool = False - A flag indicating whether the peak is fitted or not.
61
- # - is_force_fitted: bool = False - A flag indicating whether the peak is forced fitted or not.
62
- # - fitting_data: Optional[dict] = None - A dictionary containing the fitting data if the peak is fitted.
63
- # - fitting_info: Optional[dict] = None - A dictionary containing the fitting information if the peak is fitted.
64
-
65
- # Returns:
66
- # - peak_properties: PeakProperties - A dictionary containing various properties of the peak.
67
- # """
61
+ """
62
+ Compute various properties of a given peak.
63
+
64
+ Parameters:
65
+ - x_array: np.ndarray - The array of x-values (e.g., time or wavelength).
66
+ - y_array: np.ndarray - The array of y-values (e.g., intensity).
67
+ - peak_indices: List[int] - A list containing the start index, peak index, and end index of the peak.
68
+ - peak_nr: int - The identifier number of the peak.
69
+ - is_fitted: bool = False - A flag indicating whether the peak is fitted or not.
70
+ - is_force_fitted: bool = False - A flag indicating whether the peak is forced fitted or not.
71
+ - fitting_data: Optional[Dict[str, np.ndarray]] = None - A dictionary containing the fitting
72
+ data if the peak is fitted.
73
+ - fitting_info: Optional[FittinInfo] = None - A dictionary containing the fitting information
74
+ if the peak is fitted.
75
+
76
+ Returns:
77
+ - peak_properties: PeakProperties - A dictionary containing various properties of the peak.
78
+ """
68
79
 
69
80
  i_index, index, f_index = peak_indices
70
81
 
@@ -198,7 +209,7 @@ def peak_finder(
198
209
  wlen: Optional[int] = None,
199
210
  rel_height: float = 0.5,
200
211
  plateau_size: Optional[int] = None,
201
- ) -> dict:
212
+ ) -> List[PeakProperties]:
202
213
  peak_lst = []
203
214
  x_array = np.array(x_array, dtype=float)
204
215
  y_array = np.array(y_array, dtype=float)
@@ -208,13 +219,36 @@ def peak_finder(
208
219
  distance = float(distance) if distance is not None else None
209
220
  prominence = float(prominence) if prominence is not None else None
210
221
  width = float(width) if width is not None else None
211
- wlen = int(wlen) if wlen is not None else None
222
+ wlen = float(wlen) if wlen is not None else None
212
223
  rel_height = float(rel_height)
213
- plateau_size = int(plateau_size) if plateau_size is not None else None
224
+ plateau_size = float(plateau_size) if plateau_size is not None else None
214
225
 
215
226
  height = 0.05 * np.max(y_array) if height is None else height
216
227
  noise_level = 5000 if noise_level is None else noise_level
217
228
 
229
+ if x_array is not None:
230
+ x_array, y_array = density_normalization(
231
+ x_array,
232
+ y_array,
233
+ )
234
+
235
+ xdiff = x_array[1] - x_array[0]
236
+ # if x is given width is based on the x scale and has to be converted to index
237
+ if width is not None:
238
+ width = width / xdiff
239
+
240
+ # same for distance
241
+ if distance is not None:
242
+ distance = distance / xdiff
243
+
244
+ # same for wlen
245
+ if wlen is not None:
246
+ wlen = wlen / xdiff
247
+
248
+ # same for plateau_size
249
+ if plateau_size is not None:
250
+ plateau_size = plateau_size / xdiff
251
+
218
252
  # Make a copy of the input array
219
253
  y_array_copy = np.copy(y_array)
220
254
 
@@ -225,8 +259,8 @@ def peak_finder(
225
259
  prominence=prominence,
226
260
  height=height,
227
261
  distance=distance,
228
- width=width,
229
- wlen=wlen,
262
+ width=max(1, width) if width is not None else None,
263
+ wlen=int(wlen) if wlen is not None else None,
230
264
  rel_height=rel_height,
231
265
  plateau_size=plateau_size,
232
266
  )
@@ -331,7 +365,7 @@ def interpolation_1d(
331
365
  return x_interpolated, y_interpolated
332
366
 
333
367
 
334
- class FittingModel(Enum):
368
+ class FittingModel(fn.DataEnum):
335
369
  ComplexConstant = "Complex Constant"
336
370
  Gaussian = "Gaussian"
337
371
  Gaussian2D = "Gaussian-2D"
@@ -356,12 +390,8 @@ class FittingModel(Enum):
356
390
  Rectangle = "Rectangle"
357
391
  Expression = "Expression"
358
392
 
359
- @classmethod
360
- def default(cls):
361
- return cls.Gaussian.value
362
-
363
393
 
364
- class BaselineModel(Enum):
394
+ class BaselineModel(fn.DataEnum):
365
395
  # Polynomial = "Polynomial"
366
396
  Linear = "Linear"
367
397
  Spline = "Spline"
@@ -374,13 +404,13 @@ class BaselineModel(Enum):
374
404
  return cls.Exponential.value
375
405
 
376
406
 
377
- @NodeDecorator(id="span.basics.fit", name="Fit 1D")
407
+ @NodeDecorator(id="span.basics.fit", name="Fit 1D", separate_process=True)
378
408
  def fit_1D(
379
409
  x_array: np.ndarray,
380
410
  y_array: np.ndarray,
381
411
  basic_peaks: List[PeakProperties],
382
- main_model: FittingModel = FittingModel.default(),
383
- baseline_model: BaselineModel = BaselineModel.default(),
412
+ main_model: FittingModel = FittingModel.Gaussian,
413
+ baseline_model: BaselineModel = BaselineModel.Exponential,
384
414
  ) -> List[PeakProperties]:
385
415
  # """
386
416
  # Fit a 1D model to the given data.
@@ -405,10 +435,9 @@ def fit_1D(
405
435
  x_array = np.array(x_array)
406
436
  y_array = np.array(y_array)
407
437
 
408
- if isinstance(main_model, FittingModel):
409
- main_model = main_model.value
410
- if isinstance(baseline_model, BaselineModel):
411
- baseline_model = baseline_model.value
438
+ main_model = FittingModel.v(main_model)
439
+
440
+ baseline_model = BaselineModel.v(baseline_model)
412
441
  peaks = copy.deepcopy(basic_peaks)
413
442
  y = y_array
414
443
  x = x_array
@@ -473,8 +502,15 @@ def fit_1D(
473
502
 
474
503
  out = f.fit(y, pars, x=x)
475
504
  com = out.eval_components(x=x)
476
- info_dict = out.__dict__
477
- info_dict["model_name"] = main_model
505
+ # info_dict = out.__dict__
506
+ # info_dict["model_name"] = main_model
507
+
508
+ info_dict = FittinInfo(
509
+ model_name=main_model,
510
+ best_values=out.best_values,
511
+ data=out.data,
512
+ userkws=out.userkws,
513
+ )
478
514
 
479
515
  peak_properties_list = []
480
516
 
@@ -1,28 +1,70 @@
1
+ from typing import Dict, Callable
1
2
  from funcnodes import NodeDecorator, Shelf
2
3
  import numpy as np
3
4
  import pandas as pd
4
- from enum import Enum
5
5
  from scipy.signal import savgol_filter, medfilt
6
6
  from scipy.ndimage import gaussian_filter1d
7
7
  import warnings
8
+ import funcnodes as fn
8
9
 
9
10
  warnings.filterwarnings("ignore")
10
11
 
11
- class SmoothMode(Enum):
12
+
13
+ class SmoothMode(fn.DataEnum):
12
14
  SAVITZKY_GOLAY = "savgol"
13
15
  GAUSSIAN = "gaussian"
14
16
  MOVING_AVERAGE = "ma"
15
17
  EXPONENTIAL_MOVING_AVERAGE = "ema"
16
18
  MEDIAN = "median"
17
-
18
- @classmethod
19
- def default(cls) -> 'SmoothMode':
20
- """Returns the default smoothing mode."""
21
- return cls.SAVITZKY_GOLAY.value
22
19
 
23
- @NodeDecorator("span.basics.smooth", name="Smoothing")
24
20
 
25
- def _smooth(array: np.ndarray, mode: SmoothMode = SmoothMode.default(), window: int = 5) -> np.ndarray:
21
+ def smooth_savgol(x: np.ndarray, window: int) -> np.ndarray:
22
+ return savgol_filter(x, window, 2)
23
+
24
+
25
+ def smooth_gaussian(x: np.ndarray, window: int) -> np.ndarray:
26
+ return gaussian_filter1d(x, window)
27
+
28
+
29
+ def smooth_ma(x: np.ndarray, window: int) -> np.ndarray:
30
+ if x.ndim > 1:
31
+ n, m = x.shape
32
+ result = np.zeros((n, m))
33
+ for i in range(n):
34
+ result[i, :] = np.convolve(x[i, :], np.ones(window) / window, mode="same")
35
+ return result
36
+ else:
37
+ return np.convolve(x, np.ones(window) / window, mode="same")
38
+
39
+
40
+ def smooth_ema(x: np.ndarray, window: int) -> np.ndarray:
41
+ if x.ndim > 1:
42
+ n, m = x.shape
43
+ result = np.zeros((n, m))
44
+ for i in range(n):
45
+ result[i, :] = pd.Series(x[i, :]).ewm(span=window).mean().values
46
+ return result
47
+ else:
48
+ return pd.Series(x).ewm(span=window).mean().values
49
+
50
+
51
+ def smooth_median(x: np.ndarray, window: int) -> np.ndarray:
52
+ return medfilt(x, window)
53
+
54
+
55
+ _SMOOTHING_MAPPER: Dict[str, Callable[[np.ndarray, int], np.ndarray]] = {
56
+ SmoothMode.SAVITZKY_GOLAY.value: smooth_savgol,
57
+ SmoothMode.GAUSSIAN.value: smooth_gaussian,
58
+ SmoothMode.MOVING_AVERAGE.value: smooth_ma,
59
+ SmoothMode.EXPONENTIAL_MOVING_AVERAGE.value: smooth_ema,
60
+ SmoothMode.MEDIAN.value: smooth_median,
61
+ }
62
+
63
+
64
+ @NodeDecorator("span.basics.smooth", name="Smoothing")
65
+ def _smooth(
66
+ array: np.ndarray, mode: SmoothMode = SmoothMode.SAVITZKY_GOLAY, window: int = 5
67
+ ) -> np.ndarray:
26
68
  # """
27
69
  # Apply different smoothing techniques to the input array.
28
70
 
@@ -37,49 +79,12 @@ def _smooth(array: np.ndarray, mode: SmoothMode = SmoothMode.default(), window:
37
79
  # Raises:
38
80
  # ValueError: If an unsupported smoothing mode is provided.
39
81
  # """
40
- if isinstance(mode, SmoothMode):
41
- mode = mode.value
42
- def smooth_savgol(x: np.ndarray) -> np.ndarray:
43
- return savgol_filter(x, window, 2)
44
-
45
- def smooth_gaussian(x: np.ndarray) -> np.ndarray:
46
- return gaussian_filter1d(x, window)
47
-
48
- def smooth_ma(x: np.ndarray) -> np.ndarray:
49
- if x.ndim > 1:
50
- n, m = x.shape
51
- result = np.zeros((n, m))
52
- for i in range(n):
53
- result[i, :] = np.convolve(x[i, :], np.ones(window) / window, mode="same")
54
- return result
55
- else:
56
- return np.convolve(x, np.ones(window) / window, mode="same")
57
-
58
- def smooth_ema(x: np.ndarray) -> np.ndarray:
59
- if x.ndim > 1:
60
- n, m = x.shape
61
- result = np.zeros((n, m))
62
- for i in range(n):
63
- result[i, :] = pd.Series(x[i, :]).ewm(span=window).mean().values
64
- return result
65
- else:
66
- return pd.Series(x).ewm(span=window).mean().values
67
-
68
- def smooth_median(x: np.ndarray) -> np.ndarray:
69
- return medfilt(x, window)
70
-
71
- smoothing_methods = {
72
- SmoothMode.SAVITZKY_GOLAY.value: smooth_savgol,
73
- SmoothMode.GAUSSIAN.value: smooth_gaussian,
74
- SmoothMode.MOVING_AVERAGE.value: smooth_ma,
75
- SmoothMode.EXPONENTIAL_MOVING_AVERAGE.value: smooth_ema,
76
- SmoothMode.MEDIAN.value: smooth_median
77
- }
78
-
79
- if mode not in smoothing_methods.keys():
82
+ mode = SmoothMode.v(mode)
83
+
84
+ if mode not in _SMOOTHING_MAPPER.keys():
80
85
  raise ValueError(f"Unsupported smoothing mode: {mode}")
81
-
82
- return smoothing_methods[mode](array)
86
+
87
+ return _SMOOTHING_MAPPER[mode](array, window)
83
88
 
84
89
 
85
90
  # @NodeDecorator("span.basics.smooth.savgol", name="Savgol")
@@ -187,4 +192,4 @@ SMOOTH_NODE_SHELF = Shelf(
187
192
  subshelves=[],
188
193
  name="Smoothing",
189
194
  description="Smoothing of the spectra",
190
- )
195
+ )
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "funcnodes-span"
3
- version = "0.2.0"
3
+ version = "0.2.2"
4
4
  description = ""
5
5
  authors = ["Kourosh Rezaei <kouroshrezaei90@gmail.com>"]
6
6
  readme = "README.md"
@@ -15,7 +15,7 @@ funcnodes_numpy = "*"
15
15
  funcnodes_pandas = "*"
16
16
  funcnodes_plotly = "*"
17
17
 
18
- funcnodes-core = ">=0.1.3"
18
+ funcnodes-core = ">=0.1.4"
19
19
  pybaselines = "*"
20
20
  Numba = "*"
21
21
  pentapy = "*"
@@ -1,56 +0,0 @@
1
- from funcnodes import NodeDecorator, Shelf
2
- import numpy as np
3
- from enum import Enum
4
-
5
-
6
- class NormMode(Enum):
7
- ZERO_ONE = "zero_one"
8
- MINUS_ONE_ONE = "minus_one_one"
9
- SUM_ABS = "sum_abs"
10
- SUM = "sum"
11
- EUCLIDEAN = "euclidean"
12
- MEAN_STD = "mean_std"
13
- MAX = "max"
14
-
15
- @classmethod
16
- def default(cls) -> "NormMode":
17
- """Returns the default normalization mode."""
18
- return cls.ZERO_ONE.value
19
-
20
-
21
- @NodeDecorator(id="span.basics.norm", name="Normalization node")
22
- def _norm(array: np.ndarray, mode: NormMode = NormMode.default()) -> np.ndarray:
23
- # """
24
- # Apply different normalizations to the array.
25
-
26
- # Args:
27
- # array (np.ndarray): The input array to be normalized.
28
- # mode (NormMode): The normalization mode to apply. Defaults to NormMode.ZERO_ONE.
29
-
30
- # Returns:
31
- # np.ndarray: The normalized array.
32
-
33
- # Raises:
34
- # ValueError: If an unsupported normalization mode is provided.
35
- # """
36
- if isinstance(mode, NormMode):
37
- mode = mode.value
38
- normalization_methods = {
39
- NormMode.ZERO_ONE.value: lambda x: (x - np.amin(x)) / (np.amax(x) - np.amin(x)),
40
- NormMode.MINUS_ONE_ONE.value: lambda x: 2 * ((x - np.amin(x)) / (np.amax(x) - np.amin(x))) - 1,
41
- NormMode.SUM_ABS.value: lambda x: x / np.abs(x).sum(),
42
- NormMode.SUM.value: lambda x: x / x.sum(),
43
- NormMode.EUCLIDEAN.value: lambda x: x / np.sqrt((x**2).sum()),
44
- NormMode.MEAN_STD.value: lambda x: (x - x.mean()) / x.std(),
45
- NormMode.MAX.value: lambda x: x / x.max()
46
- }
47
- if mode not in normalization_methods.keys():
48
- raise ValueError(f"Unsupported normalization mode: {mode}")
49
- return normalization_methods[mode](array)
50
-
51
- NORM_NODE_SHELF = Shelf(
52
- nodes=[_norm],
53
- subshelves=[],
54
- name="Normalization",
55
- description="Normalization of the spectra",
56
- )
File without changes