funcnodes-span 0.3.1__tar.gz → 0.3.3__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,7 +1,7 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.3
2
2
  Name: funcnodes-span
3
- Version: 0.3.1
4
- Summary: Spectral Peak ANalysis (SPAN) for funcnodes
3
+ Version: 0.3.3
4
+ Summary: SPectral ANalysis (SPAN) for funcnodes
5
5
  License: MIT
6
6
  Author: Kourosh Rezaei
7
7
  Author-email: kouroshrezaei90@gmail.com
@@ -25,5 +25,5 @@ Project-URL: source, https://github.com/Linkdlab/funcnodes_span
25
25
  Project-URL: tracker, https://github.com/Linkdlab/funcnodes_span/issues
26
26
  Description-Content-Type: text/markdown
27
27
 
28
- Spectral Peak ANalysis (SPAN) for funcnodes
28
+ SPectral ANalysis (SPAN) for funcnodes
29
29
 
@@ -0,0 +1 @@
1
+ SPectral ANalysis (SPAN) for funcnodes
@@ -7,7 +7,7 @@ from .baseline import BASELINE_NODE_SHELF as BASELINE
7
7
  from funcnodes_lmfit import NODE_SHELF as LMFIT_NODE_SHELF
8
8
  from .curves import CURVES_NODE_SHELF
9
9
 
10
- __version__ = "0.3.1"
10
+ __version__ = "0.3.3"
11
11
 
12
12
  NODE_SHELF = fn.Shelf(
13
13
  name="Spectral Analysis",
@@ -44,6 +44,7 @@ def group_signals(
44
44
  # Assign each peak to its respective group based on start and end indices
45
45
  for pi, p in enumerate(peaks):
46
46
  ingroup = (baseline_cut >= p.i_index) & (baseline_cut <= p.f_index)
47
+
47
48
  if ingroup.sum() == 0:
48
49
  ingroup[(baseline_cut >= p.i_index).argmax()] = True
49
50
 
@@ -63,6 +64,9 @@ def group_signals(
63
64
  else:
64
65
  current_peak_group.update(pg)
65
66
 
67
+ if len(current_peak_group) > 0:
68
+ connected_peaks.append(list(current_peak_group))
69
+
66
70
  # Return a list of grouped PeakProperties
67
71
  return [[peaks[pi] for pi in cp] for cp in connected_peaks if len(cp) > 0]
68
72
 
@@ -111,7 +115,6 @@ def fit_local_peak(
111
115
  Returns:
112
116
  Model: The fitted model for the peak.
113
117
  """
114
-
115
118
  x = np.asarray(x)
116
119
  y = np.asarray(y)
117
120
 
@@ -135,13 +138,11 @@ def fit_local_peak(
135
138
  if peak.yfull is None:
136
139
  peak.yfull = y
137
140
 
138
- model_class = AutoModelEnum.v(model_class)
139
- incomplete_peak_model_class = AutoModelEnum.v(incomplete_peak_model_class)
140
-
141
141
  if np.abs(yf[-1] - yf[0]) > incomplete_threshold * (local_max - local_min):
142
142
  # Handle incomplete peak by extending the range and filling gaps
143
143
  m_complete = incomplete_peak_model_class()
144
144
  guess = m_complete.guess(data=yf, x=xf)
145
+ guess["center"].set(min=min(xf), max=max(xf))
145
146
  fr_complete = m_complete.fit(data=yf, x=xf, params=guess, iter_cb=iter_cb)
146
147
 
147
148
  fwhm = 2 * np.sqrt(2 * np.log(2)) * fr_complete.params["sigma"].value
@@ -350,10 +351,14 @@ def fit_peaks(
350
351
 
351
352
  # Group peaks based on the baseline factor
352
353
  connected_peaks = group_signals(x, y, peaks, baseline_factor=baseline_factor)
354
+ if len(connected_peaks) == 0:
355
+ raise ValueError("No peaks after grouping.")
353
356
 
354
357
  global_model = None
355
358
  # Fit each peak group
356
359
  for pg in connected_peaks:
360
+ if len(pg) == 0:
361
+ continue
357
362
  m = fit_peak_group(
358
363
  x,
359
364
  y,
@@ -371,6 +376,9 @@ def fit_peaks(
371
376
  # Combine models for all peak groups
372
377
  global_model += m
373
378
 
379
+ if global_model is None:
380
+ raise ValueError("No model created")
381
+
374
382
  # Fit the global model to the full data
375
383
  global_fit = global_model.fit(data=y, x=x, iter_cb=iter_cb)
376
384
 
@@ -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, Union
5
+ from typing import Optional, List, Tuple
6
6
  from scipy.signal import find_peaks
7
7
  from scipy.stats import norm
8
8
  from scipy import signal, interpolate
@@ -223,32 +223,32 @@ def interpolation_1d(
223
223
  def force_peak_finder(
224
224
  x: np.array,
225
225
  y: np.array,
226
- basic_peaks: Union[List[PeakProperties], PeakProperties],
226
+ basic_peak: PeakProperties,
227
227
  ) -> List[PeakProperties]:
228
- # """
229
- # Identify and return the two peaks around the main peak in the given peaks dictionary.
230
-
231
- # Parameters:
232
- # - peaks (dict): A dictionary containing peak information.
233
- # It should have the keys 'peaks' and 'data'.
234
- # 'peaks' should contain a list of dictionaries with keys 'Initial index', 'Index',
235
- # and 'Ending index'.
236
- # 'data' should contain arrays 'x' and 'y'.
237
-
238
- # Returns:
239
- # - dict: A dictionary containing information about the two identified peaks.
240
- # """
241
- if isinstance(basic_peaks, (list, np.ndarray, tuple)):
242
- if len(basic_peaks) != 1:
243
- raise ValueError(
244
- "This method accepts one and only one main peak as an input."
245
- )
246
- basic_peaks = basic_peaks[0]
228
+ """
229
+ Breaks down a given main peak into two individual peaks.
230
+
231
+ The function works by calculating the first and second derivatives of the signal and
232
+ finding the local maxima and minima of the derivatives. It then determines which peak
233
+ is on the left and right side of the main peak by comparing the distance between the
234
+ main peak and the closest peak on either side.
235
+
236
+ Parameters:
237
+ - x (np.array): The x-values of the signal.
238
+ - y (np.array): The y-values of the signal.
239
+ - basic_peak (PeakProperties): The main peak.
240
+
241
+ Returns:
242
+ - List[PeakProperties]: A list of two PeakProperties objects, one for each of the
243
+ individual peaks.
244
+ """
245
+ if not isinstance(basic_peak, PeakProperties):
246
+ raise TypeError("The basic peak must be a single PeakProperties object.")
247
247
 
248
- peaks = copy.deepcopy(basic_peaks)
249
- main_peak_i_index = peaks.i_index
250
- main_peak_r_index = peaks.index
251
- main_peak_f_index = peaks.f_index
248
+ peak = copy.deepcopy(basic_peak)
249
+ main_peak_i_index = peak.i_index
250
+ main_peak_r_index = peak.index
251
+ main_peak_f_index = peak.f_index
252
252
  y_array = y
253
253
  x_array = x
254
254
  # Calculate first and second derivatives
@@ -266,9 +266,9 @@ def force_peak_finder(
266
266
  max_pp = signal.argrelmax(y_array_pp)[0]
267
267
  # min_pp = signal.argrelmin(y_array_pp)[0]
268
268
 
269
- # main_peak_i_index = peaks.i_index
270
- # main_peak_r_index = peaks.index
271
- # main_peak_f_index = peaks.f_index
269
+ # main_peak_i_index = peak.i_index
270
+ # main_peak_r_index = peak.index
271
+ # main_peak_f_index = peak.f_index
272
272
 
273
273
  # Determine which peak is on the left and right side of the main peak
274
274
  if (
@@ -306,10 +306,11 @@ def force_peak_finder(
306
306
  peak_lst = []
307
307
  peak_lst.append([peak1["I.Index"], peak1["R.Index"], peak1["F.Index"]])
308
308
  peak_lst.append([peak2["I.Index"], peak2["R.Index"], peak2["F.Index"]])
309
+
309
310
  peak_properties_list = []
310
311
  for peak_nr, peak in enumerate(peak_lst):
311
312
  peak_properties = PeakProperties(
312
- id=basic_peaks.id + f"_{peak_nr + 1}",
313
+ id=basic_peak.id + f"_{peak_nr + 1}",
313
314
  i_index=peak[0],
314
315
  index=peak[1],
315
316
  f_index=peak[2],
@@ -328,7 +329,16 @@ def force_peak_finder(
328
329
  default_render_options={"data": {"src": "figure"}},
329
330
  outputs=[{"name": "figure"}],
330
331
  )
331
- def plot_peaks(x: np.array, y: np.array, peaks: List[PeakProperties]) -> go.Figure:
332
+ def plot_peaks(
333
+ x: np.array,
334
+ y: np.array,
335
+ peaks: List[PeakProperties],
336
+ fill_rectangles: bool = True,
337
+ xaxis_title: str = "x",
338
+ yaxis_title: str = "y",
339
+ title: str = "",
340
+ show_legend: bool = True,
341
+ ) -> go.Figure:
332
342
  fig = go.Figure()
333
343
 
334
344
  # Set up line plot
@@ -354,14 +364,16 @@ def plot_peaks(x: np.array, y: np.array, peaks: List[PeakProperties]) -> go.Figu
354
364
  peak.x_at_i_index,
355
365
  ],
356
366
  y=[plot_y_min, plot_y_min, peak_height, peak_height],
357
- fill="toself",
358
- fillcolor=peaks_colors[index % len(peaks_colors)],
359
- opacity=0.3,
360
- line=dict(width=0),
367
+ fill="toself" if fill_rectangles else None,
368
+ fillcolor=peaks_colors[index % len(peaks_colors)]
369
+ if fill_rectangles
370
+ else None,
371
+ opacity=0.3 if fill_rectangles else 1,
372
+ line=dict(width=0 if fill_rectangles else 2),
361
373
  mode="lines",
362
374
  name=f"Peak {peak.id}",
363
375
  legendgroup=f"Peak {peak.id}", # Group by Peak id
364
- showlegend=True,
376
+ showlegend=show_legend,
365
377
  )
366
378
  )
367
379
  # Add an X marker at the exact peak position
@@ -389,14 +401,18 @@ def plot_peaks(x: np.array, y: np.array, peaks: List[PeakProperties]) -> go.Figu
389
401
  dash="dash", color=peaks_colors[index % len(peaks_colors)]
390
402
  ),
391
403
  legendgroup=f"Peak {peak.id}",
404
+ showlegend=show_legend,
392
405
  ),
393
406
  )
394
407
 
395
408
  # Customize layout (axes labels and title can be added here if needed)
396
409
  fig.update_layout(
397
- xaxis_title="x",
398
- yaxis_title="y",
399
- template="plotly_white",
410
+ xaxis_title=xaxis_title,
411
+ yaxis_title=yaxis_title,
412
+ title=title,
413
+ plot_bgcolor="white",
414
+ paper_bgcolor="white",
415
+ showlegend=show_legend,
400
416
  )
401
417
 
402
418
  return fig
@@ -1,7 +1,7 @@
1
1
  [tool.poetry]
2
2
  name = "funcnodes-span"
3
- version = "0.3.1"
4
- description = "Spectral Peak ANalysis (SPAN) for funcnodes"
3
+ version = "0.3.3"
4
+ description = "SPectral ANalysis (SPAN) for funcnodes"
5
5
  authors = ["Kourosh Rezaei <kouroshrezaei90@gmail.com>"]
6
6
  readme = "README.md"
7
7
  license = "MIT"
@@ -1 +0,0 @@
1
- Spectral Peak ANalysis (SPAN) for funcnodes
File without changes