funcnodes-span 0.2.1__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.1
3
+ Version: 0.2.2
4
4
  Summary:
5
5
  Home-page: https://linkdlab.de/
6
6
  Author: Kourosh Rezaei
@@ -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.1"
8
+ __version__ = "0.2.2"
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
+ )
@@ -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
@@ -218,13 +219,36 @@ def peak_finder(
218
219
  distance = float(distance) if distance is not None else None
219
220
  prominence = float(prominence) if prominence is not None else None
220
221
  width = float(width) if width is not None else None
221
- wlen = int(wlen) if wlen is not None else None
222
+ wlen = float(wlen) if wlen is not None else None
222
223
  rel_height = float(rel_height)
223
- 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
224
225
 
225
226
  height = 0.05 * np.max(y_array) if height is None else height
226
227
  noise_level = 5000 if noise_level is None else noise_level
227
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
+
228
252
  # Make a copy of the input array
229
253
  y_array_copy = np.copy(y_array)
230
254
 
@@ -235,8 +259,8 @@ def peak_finder(
235
259
  prominence=prominence,
236
260
  height=height,
237
261
  distance=distance,
238
- width=width,
239
- 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,
240
264
  rel_height=rel_height,
241
265
  plateau_size=plateau_size,
242
266
  )
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "funcnodes-span"
3
- version = "0.2.1"
3
+ version = "0.2.2"
4
4
  description = ""
5
5
  authors = ["Kourosh Rezaei <kouroshrezaei90@gmail.com>"]
6
6
  readme = "README.md"
@@ -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