funcnodes-span 0.2.2__tar.gz → 0.2.5__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.2
3
+ Version: 0.2.5
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.2"
8
+ __version__ = "0.2.5"
9
9
 
10
10
  NODE_SHELF = fn.Shelf(
11
11
  name="Spectral Analysis",
@@ -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, Dict
5
+ from typing import Optional, List, Tuple, Dict, Union
6
6
  from scipy.signal import find_peaks
7
7
  from scipy.stats import norm
8
8
  from scipy import signal, interpolate
@@ -22,6 +22,8 @@ class FittinInfo:
22
22
  best_values: dict
23
23
  data: np.ndarray
24
24
  userkws: dict
25
+ best_fit: np.ndarray
26
+ rsquared: float
25
27
 
26
28
 
27
29
  @dataclass
@@ -198,8 +200,8 @@ def compute_peak_properties(
198
200
  @NodeDecorator(id="span.basics.peaks", name="Peak finder")
199
201
  @controlled_wrapper(find_peaks, wrapper_attribute="__fnwrapped__")
200
202
  def peak_finder(
201
- x_array: np.ndarray,
202
- y_array: np.ndarray,
203
+ x: np.ndarray,
204
+ y: np.ndarray,
203
205
  noise_level: Optional[int] = None,
204
206
  height: Optional[float] = None,
205
207
  threshold: Optional[float] = None,
@@ -211,8 +213,8 @@ def peak_finder(
211
213
  plateau_size: Optional[int] = None,
212
214
  ) -> List[PeakProperties]:
213
215
  peak_lst = []
214
- x_array = np.array(x_array, dtype=float)
215
- y_array = np.array(y_array, dtype=float)
216
+ x = np.array(x, dtype=float)
217
+ y = np.array(y, dtype=float)
216
218
  noise_level = int(noise_level) if noise_level is not None else None
217
219
  height = float(height) if height is not None else None
218
220
  threshold = float(threshold) if threshold is not None else None
@@ -223,16 +225,16 @@ def peak_finder(
223
225
  rel_height = float(rel_height)
224
226
  plateau_size = float(plateau_size) if plateau_size is not None else None
225
227
 
226
- height = 0.05 * np.max(y_array) if height is None else height
228
+ height = 0.05 * np.max(y) if height is None else height
227
229
  noise_level = 5000 if noise_level is None else noise_level
228
230
 
229
- if x_array is not None:
230
- x_array, y_array = density_normalization(
231
- x_array,
232
- y_array,
231
+ if x is not None:
232
+ x, y = density_normalization(
233
+ x,
234
+ y,
233
235
  )
234
236
 
235
- xdiff = x_array[1] - x_array[0]
237
+ xdiff = x[1] - x[0]
236
238
  # if x is given width is based on the x scale and has to be converted to index
237
239
  if width is not None:
238
240
  width = width / xdiff
@@ -250,7 +252,7 @@ def peak_finder(
250
252
  plateau_size = plateau_size / xdiff
251
253
 
252
254
  # Make a copy of the input array
253
- y_array_copy = np.copy(y_array)
255
+ y_array_copy = np.copy(y)
254
256
 
255
257
  # Find the peaks in the copy of the input array
256
258
  peaks, _ = find_peaks(
@@ -287,7 +289,7 @@ def peak_finder(
287
289
  # Find the right minimum of the peak
288
290
  right_min = mins[np.argmax(mins > peak)]
289
291
  if right_min < peak:
290
- right_min = len(y_array) - 1
292
+ right_min = len(y) - 1
291
293
 
292
294
  try:
293
295
  # Find the left minimum of the peak
@@ -313,7 +315,7 @@ def peak_finder(
313
315
  for peak in peaks:
314
316
  right_min = mins[np.argmax(mins > peak)]
315
317
  if right_min < peak:
316
- right_min = len(y_array) - 1
318
+ right_min = len(y) - 1
317
319
  try:
318
320
  left_min = np.array(mins)[np.where(np.array(mins) < peak)][-1]
319
321
  except IndexError:
@@ -331,7 +333,7 @@ def peak_finder(
331
333
 
332
334
  for peak_nr, peak in enumerate(peak_lst):
333
335
  peak_properties = compute_peak_properties(
334
- x_array=x_array, y_array=y_array, peak_indices=peak, peak_nr=peak_nr
336
+ x_array=x, y_array=y, peak_indices=peak, peak_nr=peak_nr
335
337
  )
336
338
  peak_properties_list.append(peak_properties)
337
339
 
@@ -406,8 +408,8 @@ class BaselineModel(fn.DataEnum):
406
408
 
407
409
  @NodeDecorator(id="span.basics.fit", name="Fit 1D", separate_process=True)
408
410
  def fit_1D(
409
- x_array: np.ndarray,
410
- y_array: np.ndarray,
411
+ x: np.ndarray,
412
+ y: np.ndarray,
411
413
  basic_peaks: List[PeakProperties],
412
414
  main_model: FittingModel = FittingModel.Gaussian,
413
415
  baseline_model: BaselineModel = BaselineModel.Exponential,
@@ -432,15 +434,14 @@ def fit_1D(
432
434
 
433
435
  # """
434
436
 
435
- x_array = np.array(x_array)
436
- y_array = np.array(y_array)
437
+ x = np.array(x)
438
+ y = np.array(y)
437
439
 
438
440
  main_model = FittingModel.v(main_model)
439
441
 
440
442
  baseline_model = BaselineModel.v(baseline_model)
441
443
  peaks = copy.deepcopy(basic_peaks)
442
- y = y_array
443
- x = x_array
444
+
444
445
  # if is_sturated:
445
446
 
446
447
  lowest_index = min(dictionary.i_index for dictionary in peaks)
@@ -510,18 +511,20 @@ def fit_1D(
510
511
  best_values=out.best_values,
511
512
  data=out.data,
512
513
  userkws=out.userkws,
514
+ best_fit=out.best_fit,
515
+ rsquared=out.rsquared,
513
516
  )
514
517
 
515
518
  peak_properties_list = []
516
519
 
517
520
  for key in com.keys():
518
521
  if key != "baseline":
519
- y_array = com[key]
520
- peak_lst = [(0, np.argmax(y_array), len(y_array) - 1)]
522
+ y = com[key]
523
+ peak_lst = [(0, np.argmax(y), len(y) - 1)]
521
524
  for peak_nr, peak in enumerate(peak_lst):
522
525
  peak_properties = compute_peak_properties(
523
- x_array=x_array,
524
- y_array=y_array,
526
+ x_array=x,
527
+ y_array=y,
525
528
  peak_indices=peak,
526
529
  peak_nr=peak_nr,
527
530
  is_fitted=True,
@@ -540,7 +543,7 @@ def fit_1D(
540
543
  def force_peak_finder(
541
544
  x: np.array,
542
545
  y: np.array,
543
- basic_peaks: List[PeakProperties],
546
+ basic_peaks: Union[List[PeakProperties], PeakProperties],
544
547
  ) -> List[PeakProperties]:
545
548
  # """
546
549
  # Identify and return the two peaks around the main peak in the given peaks dictionary.
@@ -555,10 +558,14 @@ def force_peak_finder(
555
558
  # Returns:
556
559
  # - dict: A dictionary containing information about the two identified peaks.
557
560
  # """
558
- if len(basic_peaks) != 1:
559
- raise ValueError("This method accepts one and only one main peak as an input.")
561
+ if isinstance(basic_peaks, (list, np.ndarray, tuple)):
562
+ if len(basic_peaks) != 1:
563
+ raise ValueError(
564
+ "This method accepts one and only one main peak as an input."
565
+ )
566
+ basic_peaks = basic_peaks[0]
560
567
 
561
- peaks = copy.deepcopy(basic_peaks[0])
568
+ peaks = copy.deepcopy(basic_peaks)
562
569
  main_peak_i_index = peaks.i_index
563
570
  main_peak_r_index = peaks.index
564
571
  main_peak_f_index = peaks.f_index
@@ -590,7 +597,7 @@ def force_peak_finder(
590
597
  ): # seond peak is in the leftside of the max peak #TODO: fix this
591
598
  common_point = max([num for num in max_pp if num < main_peak_r_index])
592
599
 
593
- print("Left convoluted")
600
+ # print("Left convoluted")
594
601
  peak1 = {
595
602
  "I.Index": main_peak_i_index,
596
603
  "R.Index": max(
@@ -605,7 +612,7 @@ def force_peak_finder(
605
612
  }
606
613
  else:
607
614
  common_point = next((x for x in max_pp if x > main_peak_r_index), None)
608
- print("Right convoluted")
615
+ # print("Right convoluted")
609
616
  peak1 = {
610
617
  "I.Index": main_peak_i_index,
611
618
  "R.Index": main_peak_r_index,
@@ -639,13 +646,11 @@ def force_peak_finder(
639
646
  default_render_options={"data": {"src": "figure"}},
640
647
  outputs=[{"name": "figure"}],
641
648
  )
642
- def plot_peaks(
643
- x_array: np.array, y_array: np.array, peaks_dict: List[PeakProperties]
644
- ) -> go.Figure:
649
+ def plot_peaks(x: np.array, y: np.array, peaks_dict: List[PeakProperties]) -> go.Figure:
645
650
  fig = go.Figure()
646
651
 
647
652
  # Set up line plot
648
- plot_trace = {"x": x_array, "y": y_array, "mode": "lines", "name": "data"}
653
+ plot_trace = {"x": x, "y": y, "mode": "lines", "name": "data"}
649
654
 
650
655
  fig.add_trace(go.Scatter(**plot_trace))
651
656
 
@@ -654,19 +659,17 @@ def plot_peaks(
654
659
 
655
660
  # Add rectangle shapes for each peak
656
661
  for index, peak in enumerate(peaks_dict):
657
- initial_idx = peak.i_index
658
- ending_idx = peak.f_index
659
662
  peak_height = peak.y_at_index
660
- plot_y_min = min(y_array[initial_idx], y_array[ending_idx])
663
+ plot_y_min = min(peak.y_at_i_index, peak.y_at_f_index)
661
664
 
662
665
  # Create a scatter trace that simulates a rectangle
663
666
  fig.add_trace(
664
667
  go.Scatter(
665
668
  x=[
666
- x_array[initial_idx],
667
- x_array[ending_idx],
668
- x_array[ending_idx],
669
- x_array[initial_idx],
669
+ peak.x_at_i_index,
670
+ peak.x_at_f_index,
671
+ peak.x_at_f_index,
672
+ peak.x_at_i_index,
670
673
  ],
671
674
  y=[plot_y_min, plot_y_min, peak_height, peak_height],
672
675
  fill="toself",
@@ -675,6 +678,19 @@ def plot_peaks(
675
678
  line=dict(width=0),
676
679
  mode="lines",
677
680
  name=f"Peak {peak.id}",
681
+ legendgroup=f"Peak {peak.id}", # Group by Peak id
682
+ showlegend=True,
683
+ )
684
+ )
685
+ # Add an X marker at the exact peak position
686
+ fig.add_trace(
687
+ go.Scatter(
688
+ x=[peak.x_at_index],
689
+ y=[peak.y_at_index],
690
+ mode="markers",
691
+ marker=dict(symbol="x", size=10, color="black"),
692
+ legendgroup=f"Peak {peak.id}", # Same group as rectangle
693
+ showlegend=False,
678
694
  )
679
695
  )
680
696
 
@@ -713,10 +729,10 @@ def plot_fitted_peaks(peaks: List[PeakProperties]) -> go.Figure:
713
729
  if not peak._is_fitted:
714
730
  raise ValueError("No fitting information is available.")
715
731
 
716
- x = peak.fitting_info["userkws"]["x"]
732
+ x = peak.fitting_info.userkws["x"]
717
733
  # Extract data from peaks
718
- y = peak.fitting_info["data"]
719
- best_fit = peak.fitting_info["best_fit"]
734
+ y = peak.fitting_info.data
735
+ best_fit = peak.fitting_info.best_fit
720
736
 
721
737
  # Create a subplot with 1 row, 1 column, and a secondary y-axis
722
738
  fig = make_subplots(specs=[[{"secondary_y": True}]])
@@ -765,8 +781,8 @@ def plot_fitted_peaks(peaks: List[PeakProperties]) -> go.Figure:
765
781
  fig.update_yaxes(title_text="Baseline corrected", secondary_y=True)
766
782
  fig.update_layout(
767
783
  title={
768
- "text": f"{peak.fitting_info['model_name']} model with fitting "
769
- f"score = {np.round(peak.fitting_info['rsquared'], 4)}",
784
+ "text": f"{peak.fitting_info.model_name} model with fitting "
785
+ f"score = {np.round(peak.fitting_info.rsquared, 4)}",
770
786
  "x": 0.5, # Center the title
771
787
  "xanchor": "center",
772
788
  },
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "funcnodes-span"
3
- version = "0.2.2"
3
+ version = "0.2.5"
4
4
  description = ""
5
5
  authors = ["Kourosh Rezaei <kouroshrezaei90@gmail.com>"]
6
6
  readme = "README.md"
@@ -26,6 +26,7 @@ pre-commit = "*"
26
26
  funcnodes-module = "*"
27
27
  pooch = "*"
28
28
 
29
+
29
30
  [build-system]
30
31
  requires = ["poetry-core"]
31
32
  build-backend = "poetry.core.masonry.api"
File without changes