funcnodes-span 0.1.22__tar.gz → 0.2.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: funcnodes-span
3
- Version: 0.1.22
3
+ Version: 0.2.1
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.1.22"
8
+ __version__ = "0.2.1"
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
- from typing import Optional, Tuple
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,
@@ -1480,6 +1470,667 @@ SPLINE_NODE_SHELF = Shelf(
1480
1470
  )
1481
1471
 
1482
1472
 
1473
+ @NodeDecorator(
1474
+ "pybaselines.smooth.ipsa",
1475
+ name="ipsa",
1476
+ outputs=[
1477
+ {"name": "baseline_corrected"},
1478
+ {"name": "baseline"},
1479
+ {"name": "params"},
1480
+ ],
1481
+ )
1482
+ @controlled_wrapper(pybaselines.smooth.ipsa, wrapper_attribute="__fnwrapped__")
1483
+ def _ipsa(
1484
+ data: np.ndarray,
1485
+ x_data: Optional[np.ndarray] = None,
1486
+ half_window: Optional[int] = None,
1487
+ max_iter: int = 500,
1488
+ tol: Optional[float] = None,
1489
+ roi: Optional[np.ndarray] = None,
1490
+ original_criteria: bool = False,
1491
+ ) -> Tuple[np.ndarray, np.ndarray, dict]:
1492
+ baseline, params = pybaselines.smooth.ipsa(
1493
+ data,
1494
+ x_data=x_data,
1495
+ max_iter=max_iter,
1496
+ half_window=half_window,
1497
+ roi=roi,
1498
+ tol=tol,
1499
+ original_criteria=original_criteria,
1500
+ )
1501
+ baseline_corrected = data - baseline
1502
+ return baseline_corrected, baseline, params
1503
+
1504
+
1505
+ @NodeDecorator(
1506
+ "pybaselines.smooth.noise_median",
1507
+ name="noise_median",
1508
+ outputs=[
1509
+ {"name": "baseline_corrected"},
1510
+ {"name": "baseline"},
1511
+ {"name": "params"},
1512
+ ],
1513
+ )
1514
+ @controlled_wrapper(pybaselines.smooth.noise_median, wrapper_attribute="__fnwrapped__")
1515
+ def _noise_median(
1516
+ data: np.ndarray,
1517
+ x_data: Optional[np.ndarray] = None,
1518
+ half_window: Optional[int] = None,
1519
+ smooth_half_window: Optional[int] = None,
1520
+ sigma: Optional[float] = None,
1521
+ ) -> Tuple[np.ndarray, np.ndarray, dict]:
1522
+ baseline, params = pybaselines.smooth.noise_median(
1523
+ data,
1524
+ x_data=x_data,
1525
+ smooth_half_window=smooth_half_window,
1526
+ half_window=half_window,
1527
+ sigma=sigma,
1528
+ )
1529
+ baseline_corrected = data - baseline
1530
+ return baseline_corrected, baseline, params
1531
+
1532
+
1533
+ class Side(fn.DataEnum):
1534
+ both = "both"
1535
+ left = "left"
1536
+ right = "right"
1537
+
1538
+
1539
+ @NodeDecorator(
1540
+ "pybaselines.smooth.ria",
1541
+ name="ria",
1542
+ outputs=[
1543
+ {"name": "baseline_corrected"},
1544
+ {"name": "baseline"},
1545
+ {"name": "params"},
1546
+ ],
1547
+ )
1548
+ @controlled_wrapper(pybaselines.smooth.ria, wrapper_attribute="__fnwrapped__")
1549
+ def _ria(
1550
+ data: np.ndarray,
1551
+ x_data: Optional[np.ndarray] = None,
1552
+ half_window: Optional[int] = None,
1553
+ max_iter: int = 500,
1554
+ tol: float = 0.01,
1555
+ side: Side = Side.both,
1556
+ width_scale: float = 0.1,
1557
+ height_scale: float = 1.0,
1558
+ sigma_scale: float = 1.0 / 12.0,
1559
+ ) -> Tuple[np.ndarray, np.ndarray, dict]:
1560
+ side = Side.v(side)
1561
+ baseline, params = pybaselines.smooth.ria(
1562
+ data,
1563
+ x_data=x_data,
1564
+ max_iter=max_iter,
1565
+ tol=tol,
1566
+ half_window=half_window,
1567
+ side=side,
1568
+ width_scale=width_scale,
1569
+ height_scale=height_scale,
1570
+ sigma_scale=sigma_scale,
1571
+ )
1572
+ baseline_corrected = data - baseline
1573
+ return baseline_corrected, baseline, params
1574
+
1575
+
1576
+ @NodeDecorator(
1577
+ "pybaselines.smooth.snip",
1578
+ name="snip",
1579
+ outputs=[
1580
+ {"name": "baseline_corrected"},
1581
+ {"name": "baseline"},
1582
+ {"name": "params"},
1583
+ ],
1584
+ )
1585
+ @controlled_wrapper(pybaselines.smooth.snip, wrapper_attribute="__fnwrapped__")
1586
+ def _snip(
1587
+ data: np.ndarray,
1588
+ x_data: Optional[np.ndarray] = None,
1589
+ decreasing: bool = False,
1590
+ smooth_half_window: Optional[int] = None,
1591
+ filter_order: int = 2,
1592
+ ) -> Tuple[np.ndarray, np.ndarray, dict]:
1593
+ baseline, params = pybaselines.smooth.snip(
1594
+ data,
1595
+ x_data=x_data,
1596
+ decreasing=decreasing,
1597
+ smooth_half_window=smooth_half_window,
1598
+ filter_order=filter_order,
1599
+ )
1600
+ baseline_corrected = data - baseline
1601
+ return baseline_corrected, baseline, params
1602
+
1603
+
1604
+ @NodeDecorator(
1605
+ "pybaselines.smooth.swima",
1606
+ name="swima",
1607
+ outputs=[
1608
+ {"name": "baseline_corrected"},
1609
+ {"name": "baseline"},
1610
+ {"name": "params"},
1611
+ ],
1612
+ )
1613
+ @controlled_wrapper(pybaselines.smooth.swima, wrapper_attribute="__fnwrapped__")
1614
+ def _swima(
1615
+ data: np.ndarray,
1616
+ x_data: Optional[np.ndarray] = None,
1617
+ min_half_window: int = 3,
1618
+ max_half_window: Optional[int] = None,
1619
+ smooth_half_window: Optional[int] = None,
1620
+ ) -> Tuple[np.ndarray, np.ndarray, dict]:
1621
+ baseline, params = pybaselines.smooth.swima(
1622
+ data,
1623
+ x_data=x_data,
1624
+ min_half_window=min_half_window,
1625
+ smooth_half_window=smooth_half_window,
1626
+ max_half_window=max_half_window,
1627
+ )
1628
+ baseline_corrected = data - baseline
1629
+ return baseline_corrected, baseline, params
1630
+
1631
+
1632
+ SMOOTH_NODE_SHELF = Shelf(
1633
+ nodes=[_ipsa, _noise_median, _ria, _snip, _swima],
1634
+ subshelves=[],
1635
+ name="Smooth",
1636
+ description="Fits a smooth baseline",
1637
+ )
1638
+
1639
+
1640
+ @NodeDecorator(
1641
+ "pybaselines.classification.cwt_br",
1642
+ name="cwt_br",
1643
+ outputs=[
1644
+ {"name": "baseline_corrected"},
1645
+ {"name": "baseline"},
1646
+ {"name": "params"},
1647
+ ],
1648
+ )
1649
+ @controlled_wrapper(
1650
+ pybaselines.classification.cwt_br, wrapper_attribute="__fnwrapped__"
1651
+ )
1652
+ def _cwt_br(
1653
+ data: np.ndarray,
1654
+ x_data: Optional[np.ndarray] = None,
1655
+ poly_order: int = 5,
1656
+ scale: Optional[np.ndarray] = None,
1657
+ num_std: float = 1.0,
1658
+ min_length: int = 2,
1659
+ max_iter: int = 50,
1660
+ tol: float = 0.001,
1661
+ symmetric: bool = False,
1662
+ weights: Optional[np.ndarray] = None,
1663
+ ) -> Tuple[np.ndarray, np.ndarray, dict]:
1664
+ baseline, params = pybaselines.classification.cwt_br(
1665
+ data,
1666
+ x_data=x_data,
1667
+ poly_order=poly_order,
1668
+ scale=scale,
1669
+ num_std=num_std,
1670
+ min_length=min_length,
1671
+ max_iter=max_iter,
1672
+ tol=tol,
1673
+ symmetric=symmetric,
1674
+ weights=weights,
1675
+ )
1676
+ baseline_corrected = data - baseline
1677
+ return baseline_corrected, baseline, params
1678
+
1679
+
1680
+ @NodeDecorator(
1681
+ "pybaselines.classification.dietrich",
1682
+ name="dietrich",
1683
+ outputs=[
1684
+ {"name": "baseline_corrected"},
1685
+ {"name": "baseline"},
1686
+ {"name": "params"},
1687
+ ],
1688
+ )
1689
+ @controlled_wrapper(
1690
+ pybaselines.classification.dietrich, wrapper_attribute="__fnwrapped__"
1691
+ )
1692
+ def _dietrich(
1693
+ data: np.ndarray,
1694
+ x_data: Optional[np.ndarray] = None,
1695
+ smooth_half_window: Optional[int] = None,
1696
+ interp_half_window: int = 5,
1697
+ poly_order: int = 5,
1698
+ max_iter: int = 50,
1699
+ tol: float = 0.001,
1700
+ num_std: float = 1.0,
1701
+ min_length: int = 2,
1702
+ return_coef: bool = False,
1703
+ weights: Optional[np.ndarray] = None,
1704
+ ) -> Tuple[np.ndarray, np.ndarray, dict]:
1705
+ baseline, params = pybaselines.classification.dietrich(
1706
+ data,
1707
+ x_data=x_data,
1708
+ poly_order=poly_order,
1709
+ smooth_half_window=smooth_half_window,
1710
+ num_std=num_std,
1711
+ min_length=min_length,
1712
+ max_iter=max_iter,
1713
+ tol=tol,
1714
+ interp_half_window=interp_half_window,
1715
+ return_coef=return_coef,
1716
+ weights=weights,
1717
+ )
1718
+ baseline_corrected = data - baseline
1719
+ return baseline_corrected, baseline, params
1720
+
1721
+
1722
+ @NodeDecorator(
1723
+ "pybaselines.classification.fabc",
1724
+ name="fabc",
1725
+ outputs=[
1726
+ {"name": "baseline_corrected"},
1727
+ {"name": "baseline"},
1728
+ {"name": "params"},
1729
+ ],
1730
+ )
1731
+ @controlled_wrapper(pybaselines.classification.fabc, wrapper_attribute="__fnwrapped__")
1732
+ def _fabc(
1733
+ data: np.ndarray,
1734
+ x_data: Optional[np.ndarray] = None,
1735
+ lam: float = 1000000.0,
1736
+ scale: Optional[np.ndarray] = None,
1737
+ num_std: float = 3.0,
1738
+ diff_order: int = 2,
1739
+ min_length: int = 2,
1740
+ weights_as_mask: bool = False,
1741
+ weights: Optional[np.ndarray] = None,
1742
+ ) -> Tuple[np.ndarray, np.ndarray, dict]:
1743
+ baseline, params = pybaselines.classification.fabc(
1744
+ data,
1745
+ x_data=x_data,
1746
+ lam=lam,
1747
+ scale=scale,
1748
+ num_std=num_std,
1749
+ min_length=min_length,
1750
+ diff_order=diff_order,
1751
+ weights_as_mask=weights_as_mask,
1752
+ weights=weights,
1753
+ )
1754
+ baseline_corrected = data - baseline
1755
+ return baseline_corrected, baseline, params
1756
+
1757
+
1758
+ @NodeDecorator(
1759
+ "pybaselines.classification.fastchrom",
1760
+ name="fastchrom",
1761
+ outputs=[
1762
+ {"name": "baseline_corrected"},
1763
+ {"name": "baseline"},
1764
+ {"name": "params"},
1765
+ ],
1766
+ )
1767
+ @controlled_wrapper(
1768
+ pybaselines.classification.fastchrom, wrapper_attribute="__fnwrapped__"
1769
+ )
1770
+ def _fastchrom(
1771
+ data: np.ndarray,
1772
+ x_data: Optional[np.ndarray] = None,
1773
+ half_window: Optional[int] = None,
1774
+ threshold: Optional[float] = None,
1775
+ min_fwhm: Optional[int] = None,
1776
+ interp_half_window: int = 5,
1777
+ smooth_half_window: Optional[int] = None,
1778
+ max_iter: int = 100,
1779
+ min_length: int = 2,
1780
+ weights: Optional[np.ndarray] = None,
1781
+ ) -> Tuple[np.ndarray, np.ndarray, dict]:
1782
+ baseline, params = pybaselines.classification.fastchrom(
1783
+ data,
1784
+ x_data=x_data,
1785
+ half_window=half_window,
1786
+ threshold=threshold,
1787
+ min_fwhm=min_fwhm,
1788
+ min_length=min_length,
1789
+ max_iter=max_iter,
1790
+ smooth_half_window=smooth_half_window,
1791
+ interp_half_window=interp_half_window,
1792
+ weights=weights,
1793
+ )
1794
+ baseline_corrected = data - baseline
1795
+ return baseline_corrected, baseline, params
1796
+
1797
+
1798
+ @NodeDecorator(
1799
+ "pybaselines.classification.golotvin",
1800
+ name="golotvin",
1801
+ outputs=[
1802
+ {"name": "baseline_corrected"},
1803
+ {"name": "baseline"},
1804
+ {"name": "params"},
1805
+ ],
1806
+ )
1807
+ @controlled_wrapper(
1808
+ pybaselines.classification.golotvin, wrapper_attribute="__fnwrapped__"
1809
+ )
1810
+ def _golotvin(
1811
+ data: np.ndarray,
1812
+ x_data: Optional[np.ndarray] = None,
1813
+ half_window: Optional[int] = None,
1814
+ num_std: float = 2.0,
1815
+ sections: int = 32,
1816
+ threshold: Optional[float] = None,
1817
+ interp_half_window: int = 5,
1818
+ smooth_half_window: Optional[int] = None,
1819
+ min_length: int = 2,
1820
+ weights: Optional[np.ndarray] = None,
1821
+ ) -> Tuple[np.ndarray, np.ndarray, dict]:
1822
+ baseline, params = pybaselines.classification.golotvin(
1823
+ data,
1824
+ x_data=x_data,
1825
+ half_window=half_window,
1826
+ threshold=threshold,
1827
+ num_std=num_std,
1828
+ min_length=min_length,
1829
+ sections=sections,
1830
+ smooth_half_window=smooth_half_window,
1831
+ interp_half_window=interp_half_window,
1832
+ weights=weights,
1833
+ )
1834
+ baseline_corrected = data - baseline
1835
+ return baseline_corrected, baseline, params
1836
+
1837
+
1838
+ @NodeDecorator(
1839
+ "pybaselines.classification.rubberband",
1840
+ name="rubberband",
1841
+ outputs=[
1842
+ {"name": "baseline_corrected"},
1843
+ {"name": "baseline"},
1844
+ {"name": "params"},
1845
+ ],
1846
+ )
1847
+ @controlled_wrapper(
1848
+ pybaselines.classification.rubberband, wrapper_attribute="__fnwrapped__"
1849
+ )
1850
+ def _rubberband(
1851
+ data: np.ndarray,
1852
+ x_data: Optional[np.ndarray] = None,
1853
+ segments: Union[int, np.ndarray] = 1,
1854
+ lam: Optional[float] = None,
1855
+ diff_order: int = 2,
1856
+ smooth_half_window: Optional[int] = None,
1857
+ weights: Optional[np.ndarray] = None,
1858
+ ) -> Tuple[np.ndarray, np.ndarray, dict]:
1859
+ baseline, params = pybaselines.classification.rubberband(
1860
+ data,
1861
+ x_data=x_data,
1862
+ segments=segments,
1863
+ lam=lam,
1864
+ diff_order=diff_order,
1865
+ smooth_half_window=smooth_half_window,
1866
+ weights=weights,
1867
+ )
1868
+ baseline_corrected = data - baseline
1869
+ return baseline_corrected, baseline, params
1870
+
1871
+
1872
+ @NodeDecorator(
1873
+ "pybaselines.classification.std_distribution",
1874
+ name="std_distribution",
1875
+ outputs=[
1876
+ {"name": "baseline_corrected"},
1877
+ {"name": "baseline"},
1878
+ {"name": "params"},
1879
+ ],
1880
+ )
1881
+ @controlled_wrapper(
1882
+ pybaselines.classification.std_distribution, wrapper_attribute="__fnwrapped__"
1883
+ )
1884
+ def _std_distribution(
1885
+ data: np.ndarray,
1886
+ x_data: Optional[np.ndarray] = None,
1887
+ half_window: Optional[int] = None,
1888
+ interp_half_window: int = 5,
1889
+ fill_half_window: int = 3,
1890
+ num_std: float = 1.1,
1891
+ smooth_half_window: Optional[int] = None,
1892
+ weights: Optional[np.ndarray] = None,
1893
+ ) -> Tuple[np.ndarray, np.ndarray, dict]:
1894
+ baseline, params = pybaselines.classification.std_distribution(
1895
+ data,
1896
+ x_data=x_data,
1897
+ half_window=half_window,
1898
+ interp_half_window=interp_half_window,
1899
+ fill_half_window=fill_half_window,
1900
+ smooth_half_window=smooth_half_window,
1901
+ num_std=num_std,
1902
+ weights=weights,
1903
+ )
1904
+ baseline_corrected = data - baseline
1905
+ return baseline_corrected, baseline, params
1906
+
1907
+
1908
+ CLASSIFICATION_NODE_SHELF = Shelf(
1909
+ nodes=[
1910
+ _cwt_br,
1911
+ _dietrich,
1912
+ _fabc,
1913
+ _fastchrom,
1914
+ _golotvin,
1915
+ _rubberband,
1916
+ _std_distribution,
1917
+ ],
1918
+ subshelves=[],
1919
+ name="Classification",
1920
+ description="Fits a classification baseline",
1921
+ )
1922
+
1923
+
1924
+ class Method(fn.DataEnum):
1925
+ modpoly = "modpoly"
1926
+ imodpoly = "imodpoly"
1927
+
1928
+
1929
+ @NodeDecorator(
1930
+ "pybaselines.optimizers.adaptive_minmax",
1931
+ name="adaptive_minmax",
1932
+ outputs=[
1933
+ {"name": "baseline_corrected"},
1934
+ {"name": "baseline"},
1935
+ {"name": "params"},
1936
+ ],
1937
+ )
1938
+ @controlled_wrapper(
1939
+ pybaselines.optimizers.adaptive_minmax, wrapper_attribute="__fnwrapped__"
1940
+ )
1941
+ def _adaptive_minmax(
1942
+ data: np.ndarray,
1943
+ x_data: Optional[np.ndarray] = None,
1944
+ poly_order: Optional[Union[int, List[int]]] = None,
1945
+ method: Method = Method.modpoly,
1946
+ constrained_fraction: Union[float, List[float]] = 0.01,
1947
+ constrained_weight: Union[float, List[float]] = 100000.0,
1948
+ estimation_poly_order: int = 2,
1949
+ weights: Optional[np.ndarray] = None,
1950
+ ) -> Tuple[np.ndarray, np.ndarray, dict]:
1951
+ method = Method.v(method)
1952
+ baseline, params = pybaselines.optimizers.adaptive_minmax(
1953
+ data,
1954
+ x_data=x_data,
1955
+ poly_order=poly_order,
1956
+ constrained_fraction=constrained_fraction,
1957
+ constrained_weight=constrained_weight,
1958
+ method=method,
1959
+ estimation_poly_order=estimation_poly_order,
1960
+ weights=weights,
1961
+ )
1962
+ baseline_corrected = data - baseline
1963
+ return baseline_corrected, baseline, params
1964
+
1965
+
1966
+ class MethodColab(fn.DataEnum):
1967
+ airpls = "airpls"
1968
+ arpls = "arpls"
1969
+ asls = "asls"
1970
+ aspls = "aspls"
1971
+ derpsalsa = "derpsalsa"
1972
+ drpls = "drpls"
1973
+ iarpls = "iarpls"
1974
+ iasls = "iasls"
1975
+ psalsa = "psalsa"
1976
+
1977
+
1978
+ @NodeDecorator(
1979
+ "pybaselines.optimizers.collab_pls",
1980
+ name="collab_pls",
1981
+ outputs=[
1982
+ {"name": "baseline_corrected"},
1983
+ {"name": "baseline"},
1984
+ {"name": "params"},
1985
+ ],
1986
+ )
1987
+ @controlled_wrapper(
1988
+ pybaselines.optimizers.collab_pls, wrapper_attribute="__fnwrapped__"
1989
+ )
1990
+ def _collab_pls(
1991
+ data: np.ndarray,
1992
+ x_data: Optional[np.ndarray] = None,
1993
+ average_dataset: bool = True,
1994
+ method: MethodColab = MethodColab.asls,
1995
+ ) -> Tuple[np.ndarray, np.ndarray, dict]:
1996
+ method = MethodColab.v(method)
1997
+ baseline, params = pybaselines.optimizers.collab_pls(
1998
+ data,
1999
+ x_data=x_data,
2000
+ method=method,
2001
+ average_dataset=average_dataset,
2002
+ )
2003
+ baseline_corrected = data - baseline
2004
+ return baseline_corrected, baseline, params
2005
+
2006
+
2007
+ class MethodAll(fn.DataEnum):
2008
+ goldindec = "goldindec"
2009
+ imodpoly = "imodpoly"
2010
+ loess = "loess"
2011
+ modpoly = "modpoly"
2012
+ penalizedpoly = "penalizedpoly"
2013
+ poly = "poly"
2014
+ quant_reg = "quant_reg"
2015
+ airpls = "airpls"
2016
+ arpls = "arpls"
2017
+ asls = "asls"
2018
+ aspls = "aspls"
2019
+ derpsalsa = "derpsalsa"
2020
+ drpls = "drpls"
2021
+ iarpls = "iarpls"
2022
+ iasls = "iasls"
2023
+ psalsa = "psalsa"
2024
+ amormol = "amormol"
2025
+ imor = "imor"
2026
+ jbcd = "jbcd"
2027
+ mor = "mor"
2028
+ mormol = "mormol"
2029
+ mpls = "mpls"
2030
+ mpspline = "mpspline"
2031
+ mwmv = "mwmv"
2032
+ rolling_ball = "rolling_ball"
2033
+ tophat = "tophat"
2034
+ corner_cutting = "corner_cutting"
2035
+ irsqr = "irsqr"
2036
+ mixture_model = "mixture_model"
2037
+ pspline_airpls = "pspline_airpls"
2038
+ pspline_asls = "pspline_asls"
2039
+ pspline_aspls = "pspline_aspls"
2040
+ pspline_derpsalsa = "pspline_derpsalsa"
2041
+ pspline_drpls = "pspline_drpls"
2042
+ pspline_iarpls = "pspline_iarpls"
2043
+ pspline_iasls = "pspline_iasls"
2044
+ pspline_mpls = "pspline_mpls"
2045
+ pspline_psalsa = "pspline_psalsa"
2046
+ cwt_br = "cwt_br"
2047
+ dietrich = "dietrich"
2048
+ fabc = "fabc"
2049
+ fastchrom = "fastchrom"
2050
+ golotvin = "golotvin"
2051
+ rubberband = "rubberband"
2052
+ std_distribution = "std_distribution"
2053
+
2054
+
2055
+ @NodeDecorator(
2056
+ "pybaselines.optimizers.custom_bc",
2057
+ name="custom_bc",
2058
+ outputs=[
2059
+ {"name": "baseline_corrected"},
2060
+ {"name": "baseline"},
2061
+ {"name": "params"},
2062
+ ],
2063
+ )
2064
+ @controlled_wrapper(pybaselines.optimizers.custom_bc, wrapper_attribute="__fnwrapped__")
2065
+ def _custom_bc(
2066
+ data: np.ndarray,
2067
+ x_data: Optional[np.ndarray] = None,
2068
+ sampling: Union[int, np.ndarray] = 1,
2069
+ lam: Optional[float] = None,
2070
+ diff_order: int = 2,
2071
+ method: MethodAll = MethodAll.asls,
2072
+ ) -> Tuple[np.ndarray, np.ndarray, dict]:
2073
+ method = MethodAll.v(method)
2074
+ baseline, params = pybaselines.optimizers.custom_bc(
2075
+ data,
2076
+ x_data=x_data,
2077
+ diff_order=diff_order,
2078
+ lam=lam,
2079
+ method=method,
2080
+ sampling=sampling,
2081
+ )
2082
+ baseline_corrected = data - baseline
2083
+ return baseline_corrected, baseline, params
2084
+
2085
+
2086
+ @NodeDecorator(
2087
+ "pybaselines.optimizers.optimize_extended_range",
2088
+ name="optimize_extended_range",
2089
+ outputs=[
2090
+ {"name": "baseline_corrected"},
2091
+ {"name": "baseline"},
2092
+ {"name": "params"},
2093
+ ],
2094
+ )
2095
+ @controlled_wrapper(
2096
+ pybaselines.optimizers.optimize_extended_range, wrapper_attribute="__fnwrapped__"
2097
+ )
2098
+ def _optimize_extended_range(
2099
+ data: np.ndarray,
2100
+ x_data: Optional[np.ndarray] = None,
2101
+ side: Side = Side.both,
2102
+ width_scale: float = 0.1,
2103
+ height_scale: float = 1.0,
2104
+ sigma_scale: float = 1.0 / 12.0,
2105
+ min_value: float = 2.0,
2106
+ max_value: float = 8.0,
2107
+ step: int = 1,
2108
+ method: MethodAll = MethodAll.asls,
2109
+ ) -> Tuple[np.ndarray, np.ndarray, dict]:
2110
+ method = MethodAll.v(method)
2111
+ side = Side.v(side)
2112
+ baseline, params = pybaselines.optimizers.optimize_extended_range(
2113
+ data,
2114
+ x_data=x_data,
2115
+ width_scale=width_scale,
2116
+ height_scale=height_scale,
2117
+ method=method,
2118
+ min_value=min_value,
2119
+ sigma_scale=sigma_scale,
2120
+ max_value=max_value,
2121
+ step=step,
2122
+ )
2123
+ baseline_corrected = data - baseline
2124
+ return baseline_corrected, baseline, params
2125
+
2126
+
2127
+ OPTIMIZERS_NODE_SHELF = Shelf(
2128
+ nodes=[_adaptive_minmax, _collab_pls, _custom_bc, _optimize_extended_range],
2129
+ subshelves=[],
2130
+ name="Optimizers",
2131
+ description="Fits a optimizers baseline",
2132
+ )
2133
+
1483
2134
  BASELINE_NODE_SHELF = Shelf(
1484
2135
  nodes=[],
1485
2136
  subshelves=[
@@ -1487,6 +2138,9 @@ BASELINE_NODE_SHELF = Shelf(
1487
2138
  WHITTAKER_NODE_SHELF,
1488
2139
  MORPHOLOGICAL_NODE_SHELF,
1489
2140
  SPLINE_NODE_SHELF,
2141
+ SMOOTH_NODE_SHELF,
2142
+ CLASSIFICATION_NODE_SHELF,
2143
+ OPTIMIZERS_NODE_SHELF,
1490
2144
  ],
1491
2145
  name="Baseline correction",
1492
2146
  description="Provides different techniques for fitting baselines to experimental data using pybaselines.",
@@ -1,9 +1,10 @@
1
1
  from funcnodes import NodeDecorator, Shelf
2
2
  import numpy as np
3
- from enum import Enum
4
3
 
4
+ import funcnodes as fn
5
5
 
6
- class NormMode(Enum):
6
+
7
+ class NormMode(fn.DataEnum):
7
8
  ZERO_ONE = "zero_one"
8
9
  MINUS_ONE_ONE = "minus_one_one"
9
10
  SUM_ABS = "sum_abs"
@@ -12,14 +13,9 @@ class NormMode(Enum):
12
13
  MEAN_STD = "mean_std"
13
14
  MAX = "max"
14
15
 
15
- @classmethod
16
- def default(cls) -> "NormMode":
17
- """Returns the default normalization mode."""
18
- return cls.ZERO_ONE.value
19
-
20
16
 
21
17
  @NodeDecorator(id="span.basics.norm", name="Normalization node")
22
- def _norm(array: np.ndarray, mode: NormMode = NormMode.default()) -> np.ndarray:
18
+ def _norm(array: np.ndarray, mode: NormMode = NormMode.ZERO_ONE) -> np.ndarray:
23
19
  # """
24
20
  # Apply different normalizations to the array.
25
21
 
@@ -33,21 +29,23 @@ def _norm(array: np.ndarray, mode: NormMode = NormMode.default()) -> np.ndarray:
33
29
  # Raises:
34
30
  # ValueError: If an unsupported normalization mode is provided.
35
31
  # """
36
- if isinstance(mode, NormMode):
37
- mode = mode.value
32
+ mode = NormMode.v(mode)
38
33
  normalization_methods = {
39
34
  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,
35
+ NormMode.MINUS_ONE_ONE.value: lambda x: 2
36
+ * ((x - np.amin(x)) / (np.amax(x) - np.amin(x)))
37
+ - 1,
41
38
  NormMode.SUM_ABS.value: lambda x: x / np.abs(x).sum(),
42
39
  NormMode.SUM.value: lambda x: x / x.sum(),
43
40
  NormMode.EUCLIDEAN.value: lambda x: x / np.sqrt((x**2).sum()),
44
41
  NormMode.MEAN_STD.value: lambda x: (x - x.mean()) / x.std(),
45
- NormMode.MAX.value: lambda x: x / x.max()
42
+ NormMode.MAX.value: lambda x: x / x.max(),
46
43
  }
47
44
  if mode not in normalization_methods.keys():
48
45
  raise ValueError(f"Unsupported normalization mode: {mode}")
49
46
  return normalization_methods[mode](array)
50
47
 
48
+
51
49
  NORM_NODE_SHELF = Shelf(
52
50
  nodes=[_norm],
53
51
  subshelves=[],
@@ -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
@@ -15,6 +15,14 @@ import re
15
15
  from dataclasses import dataclass
16
16
 
17
17
 
18
+ @dataclass
19
+ class FittinInfo:
20
+ model_name: str
21
+ best_values: dict
22
+ data: np.ndarray
23
+ userkws: dict
24
+
25
+
18
26
  @dataclass
19
27
  class PeakProperties:
20
28
  id: str
@@ -35,8 +43,8 @@ class PeakProperties:
35
43
  width: float
36
44
  _is_fitted: bool = False
37
45
  _is_force_fitted: bool = False
38
- fitting_data: Optional[dict] = None
39
- fitting_info: Optional[dict] = None
46
+ fitting_data: Optional[Dict[str, np.ndarray]] = None
47
+ fitting_info: Optional[FittinInfo] = None
40
48
 
41
49
 
42
50
  def compute_peak_properties(
@@ -46,25 +54,27 @@ def compute_peak_properties(
46
54
  peak_nr: int,
47
55
  is_fitted: bool = False,
48
56
  is_force_fitted: bool = False,
49
- fitting_data: Optional[dict] = None,
50
- fitting_info: Optional[dict] = None,
57
+ fitting_data: Optional[Dict[str, np.ndarray]] = None,
58
+ fitting_info: Optional[FittinInfo] = None,
51
59
  ) -> 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
- # """
60
+ """
61
+ Compute various properties of a given peak.
62
+
63
+ Parameters:
64
+ - x_array: np.ndarray - The array of x-values (e.g., time or wavelength).
65
+ - y_array: np.ndarray - The array of y-values (e.g., intensity).
66
+ - peak_indices: List[int] - A list containing the start index, peak index, and end index of the peak.
67
+ - peak_nr: int - The identifier number of the peak.
68
+ - is_fitted: bool = False - A flag indicating whether the peak is fitted or not.
69
+ - is_force_fitted: bool = False - A flag indicating whether the peak is forced fitted or not.
70
+ - fitting_data: Optional[Dict[str, np.ndarray]] = None - A dictionary containing the fitting
71
+ data if the peak is fitted.
72
+ - fitting_info: Optional[FittinInfo] = None - A dictionary containing the fitting information
73
+ if the peak is fitted.
74
+
75
+ Returns:
76
+ - peak_properties: PeakProperties - A dictionary containing various properties of the peak.
77
+ """
68
78
 
69
79
  i_index, index, f_index = peak_indices
70
80
 
@@ -198,7 +208,7 @@ def peak_finder(
198
208
  wlen: Optional[int] = None,
199
209
  rel_height: float = 0.5,
200
210
  plateau_size: Optional[int] = None,
201
- ) -> dict:
211
+ ) -> List[PeakProperties]:
202
212
  peak_lst = []
203
213
  x_array = np.array(x_array, dtype=float)
204
214
  y_array = np.array(y_array, dtype=float)
@@ -331,7 +341,7 @@ def interpolation_1d(
331
341
  return x_interpolated, y_interpolated
332
342
 
333
343
 
334
- class FittingModel(Enum):
344
+ class FittingModel(fn.DataEnum):
335
345
  ComplexConstant = "Complex Constant"
336
346
  Gaussian = "Gaussian"
337
347
  Gaussian2D = "Gaussian-2D"
@@ -356,12 +366,8 @@ class FittingModel(Enum):
356
366
  Rectangle = "Rectangle"
357
367
  Expression = "Expression"
358
368
 
359
- @classmethod
360
- def default(cls):
361
- return cls.Gaussian.value
362
369
 
363
-
364
- class BaselineModel(Enum):
370
+ class BaselineModel(fn.DataEnum):
365
371
  # Polynomial = "Polynomial"
366
372
  Linear = "Linear"
367
373
  Spline = "Spline"
@@ -374,13 +380,13 @@ class BaselineModel(Enum):
374
380
  return cls.Exponential.value
375
381
 
376
382
 
377
- @NodeDecorator(id="span.basics.fit", name="Fit 1D")
383
+ @NodeDecorator(id="span.basics.fit", name="Fit 1D", separate_process=True)
378
384
  def fit_1D(
379
385
  x_array: np.ndarray,
380
386
  y_array: np.ndarray,
381
387
  basic_peaks: List[PeakProperties],
382
- main_model: FittingModel = FittingModel.default(),
383
- baseline_model: BaselineModel = BaselineModel.default(),
388
+ main_model: FittingModel = FittingModel.Gaussian,
389
+ baseline_model: BaselineModel = BaselineModel.Exponential,
384
390
  ) -> List[PeakProperties]:
385
391
  # """
386
392
  # Fit a 1D model to the given data.
@@ -405,10 +411,9 @@ def fit_1D(
405
411
  x_array = np.array(x_array)
406
412
  y_array = np.array(y_array)
407
413
 
408
- if isinstance(main_model, FittingModel):
409
- main_model = main_model.value
410
- if isinstance(baseline_model, BaselineModel):
411
- baseline_model = baseline_model.value
414
+ main_model = FittingModel.v(main_model)
415
+
416
+ baseline_model = BaselineModel.v(baseline_model)
412
417
  peaks = copy.deepcopy(basic_peaks)
413
418
  y = y_array
414
419
  x = x_array
@@ -473,8 +478,15 @@ def fit_1D(
473
478
 
474
479
  out = f.fit(y, pars, x=x)
475
480
  com = out.eval_components(x=x)
476
- info_dict = out.__dict__
477
- info_dict["model_name"] = main_model
481
+ # info_dict = out.__dict__
482
+ # info_dict["model_name"] = main_model
483
+
484
+ info_dict = FittinInfo(
485
+ model_name=main_model,
486
+ best_values=out.best_values,
487
+ data=out.data,
488
+ userkws=out.userkws,
489
+ )
478
490
 
479
491
  peak_properties_list = []
480
492
 
@@ -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.1.22"
3
+ version = "0.2.1"
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 = "*"