sigima 0.0.1.dev0__py3-none-any.whl → 1.0.0__py3-none-any.whl

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.
Files changed (264) hide show
  1. sigima/__init__.py +142 -2
  2. sigima/client/__init__.py +105 -0
  3. sigima/client/base.py +780 -0
  4. sigima/client/remote.py +469 -0
  5. sigima/client/stub.py +814 -0
  6. sigima/client/utils.py +90 -0
  7. sigima/config.py +444 -0
  8. sigima/data/logo/Sigima.svg +135 -0
  9. sigima/data/tests/annotations.json +798 -0
  10. sigima/data/tests/curve_fitting/exponential_fit.txt +511 -0
  11. sigima/data/tests/curve_fitting/gaussian_fit.txt +100 -0
  12. sigima/data/tests/curve_fitting/piecewiseexponential_fit.txt +1022 -0
  13. sigima/data/tests/curve_fitting/polynomial_fit.txt +100 -0
  14. sigima/data/tests/curve_fitting/twohalfgaussian_fit.txt +1000 -0
  15. sigima/data/tests/curve_formats/bandwidth.txt +201 -0
  16. sigima/data/tests/curve_formats/boxcar.npy +0 -0
  17. sigima/data/tests/curve_formats/datetime.txt +1001 -0
  18. sigima/data/tests/curve_formats/dynamic_parameters.txt +4000 -0
  19. sigima/data/tests/curve_formats/fw1e2.txt +301 -0
  20. sigima/data/tests/curve_formats/fwhm.txt +319 -0
  21. sigima/data/tests/curve_formats/multiple_curves.csv +29 -0
  22. sigima/data/tests/curve_formats/noised_saw.mat +0 -0
  23. sigima/data/tests/curve_formats/oscilloscope.csv +111 -0
  24. sigima/data/tests/curve_formats/other/other2/recursive2.txt +5 -0
  25. sigima/data/tests/curve_formats/other/recursive1.txt +5 -0
  26. sigima/data/tests/curve_formats/paracetamol.npy +0 -0
  27. sigima/data/tests/curve_formats/paracetamol.txt +1010 -0
  28. sigima/data/tests/curve_formats/paracetamol_dx_dy.csv +1000 -0
  29. sigima/data/tests/curve_formats/paracetamol_dy.csv +1001 -0
  30. sigima/data/tests/curve_formats/pulse1.npy +0 -0
  31. sigima/data/tests/curve_formats/pulse2.npy +0 -0
  32. sigima/data/tests/curve_formats/simple.txt +5 -0
  33. sigima/data/tests/curve_formats/spectrum.mca +2139 -0
  34. sigima/data/tests/curve_formats/square2.npy +0 -0
  35. sigima/data/tests/curve_formats/step.npy +0 -0
  36. sigima/data/tests/fabry-perot1.jpg +0 -0
  37. sigima/data/tests/fabry-perot2.jpg +0 -0
  38. sigima/data/tests/flower.npy +0 -0
  39. sigima/data/tests/image_formats/NF 180338201.scor-data +11003 -0
  40. sigima/data/tests/image_formats/binary_image.npy +0 -0
  41. sigima/data/tests/image_formats/binary_image.png +0 -0
  42. sigima/data/tests/image_formats/centroid_test.npy +0 -0
  43. sigima/data/tests/image_formats/coordinated_text/complex_image.txt +10011 -0
  44. sigima/data/tests/image_formats/coordinated_text/complex_ref_image.txt +10010 -0
  45. sigima/data/tests/image_formats/coordinated_text/image.txt +15 -0
  46. sigima/data/tests/image_formats/coordinated_text/image2.txt +14 -0
  47. sigima/data/tests/image_formats/coordinated_text/image_no_unit_no_label.txt +14 -0
  48. sigima/data/tests/image_formats/coordinated_text/image_with_nan.txt +15 -0
  49. sigima/data/tests/image_formats/coordinated_text/image_with_unit.txt +14 -0
  50. sigima/data/tests/image_formats/fiber.csv +480 -0
  51. sigima/data/tests/image_formats/fiber.jpg +0 -0
  52. sigima/data/tests/image_formats/fiber.png +0 -0
  53. sigima/data/tests/image_formats/fiber.txt +480 -0
  54. sigima/data/tests/image_formats/gaussian_spot_with_noise.npy +0 -0
  55. sigima/data/tests/image_formats/mr-brain.dcm +0 -0
  56. sigima/data/tests/image_formats/noised_gaussian.mat +0 -0
  57. sigima/data/tests/image_formats/sif_reader/nd_lum_image_no_glue.sif +0 -0
  58. sigima/data/tests/image_formats/sif_reader/raman1.sif +0 -0
  59. sigima/data/tests/image_formats/tiling.txt +10 -0
  60. sigima/data/tests/image_formats/uint16.tiff +0 -0
  61. sigima/data/tests/image_formats/uint8.tiff +0 -0
  62. sigima/data/tests/laser_beam/TEM00_z_13.jpg +0 -0
  63. sigima/data/tests/laser_beam/TEM00_z_18.jpg +0 -0
  64. sigima/data/tests/laser_beam/TEM00_z_23.jpg +0 -0
  65. sigima/data/tests/laser_beam/TEM00_z_30.jpg +0 -0
  66. sigima/data/tests/laser_beam/TEM00_z_35.jpg +0 -0
  67. sigima/data/tests/laser_beam/TEM00_z_40.jpg +0 -0
  68. sigima/data/tests/laser_beam/TEM00_z_45.jpg +0 -0
  69. sigima/data/tests/laser_beam/TEM00_z_50.jpg +0 -0
  70. sigima/data/tests/laser_beam/TEM00_z_55.jpg +0 -0
  71. sigima/data/tests/laser_beam/TEM00_z_60.jpg +0 -0
  72. sigima/data/tests/laser_beam/TEM00_z_65.jpg +0 -0
  73. sigima/data/tests/laser_beam/TEM00_z_70.jpg +0 -0
  74. sigima/data/tests/laser_beam/TEM00_z_75.jpg +0 -0
  75. sigima/data/tests/laser_beam/TEM00_z_80.jpg +0 -0
  76. sigima/enums.py +195 -0
  77. sigima/io/__init__.py +123 -0
  78. sigima/io/base.py +311 -0
  79. sigima/io/common/__init__.py +5 -0
  80. sigima/io/common/basename.py +164 -0
  81. sigima/io/common/converters.py +189 -0
  82. sigima/io/common/objmeta.py +181 -0
  83. sigima/io/common/textreader.py +58 -0
  84. sigima/io/convenience.py +157 -0
  85. sigima/io/enums.py +17 -0
  86. sigima/io/ftlab.py +395 -0
  87. sigima/io/image/__init__.py +9 -0
  88. sigima/io/image/base.py +177 -0
  89. sigima/io/image/formats.py +1016 -0
  90. sigima/io/image/funcs.py +414 -0
  91. sigima/io/signal/__init__.py +9 -0
  92. sigima/io/signal/base.py +129 -0
  93. sigima/io/signal/formats.py +290 -0
  94. sigima/io/signal/funcs.py +723 -0
  95. sigima/objects/__init__.py +260 -0
  96. sigima/objects/base.py +937 -0
  97. sigima/objects/image/__init__.py +88 -0
  98. sigima/objects/image/creation.py +556 -0
  99. sigima/objects/image/object.py +524 -0
  100. sigima/objects/image/roi.py +904 -0
  101. sigima/objects/scalar/__init__.py +57 -0
  102. sigima/objects/scalar/common.py +215 -0
  103. sigima/objects/scalar/geometry.py +502 -0
  104. sigima/objects/scalar/table.py +784 -0
  105. sigima/objects/shape.py +290 -0
  106. sigima/objects/signal/__init__.py +133 -0
  107. sigima/objects/signal/constants.py +27 -0
  108. sigima/objects/signal/creation.py +1428 -0
  109. sigima/objects/signal/object.py +444 -0
  110. sigima/objects/signal/roi.py +274 -0
  111. sigima/params.py +405 -0
  112. sigima/proc/__init__.py +96 -0
  113. sigima/proc/base.py +381 -0
  114. sigima/proc/decorator.py +330 -0
  115. sigima/proc/image/__init__.py +513 -0
  116. sigima/proc/image/arithmetic.py +335 -0
  117. sigima/proc/image/base.py +260 -0
  118. sigima/proc/image/detection.py +519 -0
  119. sigima/proc/image/edges.py +329 -0
  120. sigima/proc/image/exposure.py +406 -0
  121. sigima/proc/image/extraction.py +458 -0
  122. sigima/proc/image/filtering.py +219 -0
  123. sigima/proc/image/fourier.py +147 -0
  124. sigima/proc/image/geometry.py +661 -0
  125. sigima/proc/image/mathops.py +340 -0
  126. sigima/proc/image/measurement.py +195 -0
  127. sigima/proc/image/morphology.py +155 -0
  128. sigima/proc/image/noise.py +107 -0
  129. sigima/proc/image/preprocessing.py +182 -0
  130. sigima/proc/image/restoration.py +235 -0
  131. sigima/proc/image/threshold.py +217 -0
  132. sigima/proc/image/transformations.py +393 -0
  133. sigima/proc/signal/__init__.py +376 -0
  134. sigima/proc/signal/analysis.py +206 -0
  135. sigima/proc/signal/arithmetic.py +551 -0
  136. sigima/proc/signal/base.py +262 -0
  137. sigima/proc/signal/extraction.py +60 -0
  138. sigima/proc/signal/features.py +310 -0
  139. sigima/proc/signal/filtering.py +484 -0
  140. sigima/proc/signal/fitting.py +276 -0
  141. sigima/proc/signal/fourier.py +259 -0
  142. sigima/proc/signal/mathops.py +420 -0
  143. sigima/proc/signal/processing.py +580 -0
  144. sigima/proc/signal/stability.py +175 -0
  145. sigima/proc/title_formatting.py +227 -0
  146. sigima/proc/validation.py +272 -0
  147. sigima/tests/__init__.py +7 -0
  148. sigima/tests/common/__init__.py +0 -0
  149. sigima/tests/common/arithmeticparam_unit_test.py +26 -0
  150. sigima/tests/common/basename_unit_test.py +126 -0
  151. sigima/tests/common/client_unit_test.py +412 -0
  152. sigima/tests/common/converters_unit_test.py +77 -0
  153. sigima/tests/common/decorator_unit_test.py +176 -0
  154. sigima/tests/common/examples_unit_test.py +104 -0
  155. sigima/tests/common/kernel_normalization_unit_test.py +242 -0
  156. sigima/tests/common/roi_basic_unit_test.py +73 -0
  157. sigima/tests/common/roi_geometry_unit_test.py +171 -0
  158. sigima/tests/common/scalar_builder_unit_test.py +142 -0
  159. sigima/tests/common/scalar_unit_test.py +991 -0
  160. sigima/tests/common/shape_unit_test.py +183 -0
  161. sigima/tests/common/stat_unit_test.py +138 -0
  162. sigima/tests/common/title_formatting_unit_test.py +338 -0
  163. sigima/tests/common/tools_coordinates_unit_test.py +60 -0
  164. sigima/tests/common/transformations_unit_test.py +178 -0
  165. sigima/tests/common/validation_unit_test.py +205 -0
  166. sigima/tests/conftest.py +129 -0
  167. sigima/tests/data.py +998 -0
  168. sigima/tests/env.py +280 -0
  169. sigima/tests/guiutils.py +163 -0
  170. sigima/tests/helpers.py +532 -0
  171. sigima/tests/image/__init__.py +28 -0
  172. sigima/tests/image/binning_unit_test.py +128 -0
  173. sigima/tests/image/blob_detection_unit_test.py +312 -0
  174. sigima/tests/image/centroid_unit_test.py +170 -0
  175. sigima/tests/image/check_2d_array_unit_test.py +63 -0
  176. sigima/tests/image/contour_unit_test.py +172 -0
  177. sigima/tests/image/convolution_unit_test.py +178 -0
  178. sigima/tests/image/datatype_unit_test.py +67 -0
  179. sigima/tests/image/edges_unit_test.py +155 -0
  180. sigima/tests/image/enclosingcircle_unit_test.py +88 -0
  181. sigima/tests/image/exposure_unit_test.py +223 -0
  182. sigima/tests/image/fft2d_unit_test.py +189 -0
  183. sigima/tests/image/filtering_unit_test.py +166 -0
  184. sigima/tests/image/geometry_unit_test.py +654 -0
  185. sigima/tests/image/hough_circle_unit_test.py +147 -0
  186. sigima/tests/image/imageobj_unit_test.py +737 -0
  187. sigima/tests/image/morphology_unit_test.py +71 -0
  188. sigima/tests/image/noise_unit_test.py +57 -0
  189. sigima/tests/image/offset_correction_unit_test.py +72 -0
  190. sigima/tests/image/operation_unit_test.py +518 -0
  191. sigima/tests/image/peak2d_limits_unit_test.py +41 -0
  192. sigima/tests/image/peak2d_unit_test.py +133 -0
  193. sigima/tests/image/profile_unit_test.py +159 -0
  194. sigima/tests/image/projections_unit_test.py +121 -0
  195. sigima/tests/image/restoration_unit_test.py +141 -0
  196. sigima/tests/image/roi2dparam_unit_test.py +53 -0
  197. sigima/tests/image/roi_advanced_unit_test.py +588 -0
  198. sigima/tests/image/roi_grid_unit_test.py +279 -0
  199. sigima/tests/image/spectrum2d_unit_test.py +40 -0
  200. sigima/tests/image/threshold_unit_test.py +91 -0
  201. sigima/tests/io/__init__.py +0 -0
  202. sigima/tests/io/addnewformat_unit_test.py +125 -0
  203. sigima/tests/io/convenience_funcs_unit_test.py +470 -0
  204. sigima/tests/io/coordinated_text_format_unit_test.py +495 -0
  205. sigima/tests/io/datetime_csv_unit_test.py +198 -0
  206. sigima/tests/io/imageio_formats_test.py +41 -0
  207. sigima/tests/io/ioregistry_unit_test.py +69 -0
  208. sigima/tests/io/objmeta_unit_test.py +87 -0
  209. sigima/tests/io/readobj_unit_test.py +130 -0
  210. sigima/tests/io/readwriteobj_unit_test.py +67 -0
  211. sigima/tests/signal/__init__.py +0 -0
  212. sigima/tests/signal/analysis_unit_test.py +135 -0
  213. sigima/tests/signal/check_1d_arrays_unit_test.py +169 -0
  214. sigima/tests/signal/convolution_unit_test.py +404 -0
  215. sigima/tests/signal/datetime_unit_test.py +176 -0
  216. sigima/tests/signal/fft1d_unit_test.py +303 -0
  217. sigima/tests/signal/filters_unit_test.py +403 -0
  218. sigima/tests/signal/fitting_unit_test.py +929 -0
  219. sigima/tests/signal/fwhm_unit_test.py +111 -0
  220. sigima/tests/signal/noise_unit_test.py +128 -0
  221. sigima/tests/signal/offset_correction_unit_test.py +34 -0
  222. sigima/tests/signal/operation_unit_test.py +489 -0
  223. sigima/tests/signal/peakdetection_unit_test.py +145 -0
  224. sigima/tests/signal/processing_unit_test.py +657 -0
  225. sigima/tests/signal/pulse/__init__.py +112 -0
  226. sigima/tests/signal/pulse/crossing_times_unit_test.py +123 -0
  227. sigima/tests/signal/pulse/plateau_detection_unit_test.py +102 -0
  228. sigima/tests/signal/pulse/pulse_unit_test.py +1824 -0
  229. sigima/tests/signal/roi_advanced_unit_test.py +392 -0
  230. sigima/tests/signal/signalobj_unit_test.py +603 -0
  231. sigima/tests/signal/stability_unit_test.py +431 -0
  232. sigima/tests/signal/uncertainty_unit_test.py +611 -0
  233. sigima/tests/vistools.py +1030 -0
  234. sigima/tools/__init__.py +59 -0
  235. sigima/tools/checks.py +290 -0
  236. sigima/tools/coordinates.py +308 -0
  237. sigima/tools/datatypes.py +26 -0
  238. sigima/tools/image/__init__.py +97 -0
  239. sigima/tools/image/detection.py +451 -0
  240. sigima/tools/image/exposure.py +77 -0
  241. sigima/tools/image/extraction.py +48 -0
  242. sigima/tools/image/fourier.py +260 -0
  243. sigima/tools/image/geometry.py +190 -0
  244. sigima/tools/image/preprocessing.py +165 -0
  245. sigima/tools/signal/__init__.py +86 -0
  246. sigima/tools/signal/dynamic.py +254 -0
  247. sigima/tools/signal/features.py +135 -0
  248. sigima/tools/signal/filtering.py +171 -0
  249. sigima/tools/signal/fitting.py +1171 -0
  250. sigima/tools/signal/fourier.py +466 -0
  251. sigima/tools/signal/interpolation.py +70 -0
  252. sigima/tools/signal/peakdetection.py +126 -0
  253. sigima/tools/signal/pulse.py +1626 -0
  254. sigima/tools/signal/scaling.py +50 -0
  255. sigima/tools/signal/stability.py +258 -0
  256. sigima/tools/signal/windowing.py +90 -0
  257. sigima/worker.py +79 -0
  258. sigima-1.0.0.dist-info/METADATA +233 -0
  259. sigima-1.0.0.dist-info/RECORD +262 -0
  260. {sigima-0.0.1.dev0.dist-info → sigima-1.0.0.dist-info}/licenses/LICENSE +29 -29
  261. sigima-0.0.1.dev0.dist-info/METADATA +0 -60
  262. sigima-0.0.1.dev0.dist-info/RECORD +0 -6
  263. {sigima-0.0.1.dev0.dist-info → sigima-1.0.0.dist-info}/WHEEL +0 -0
  264. {sigima-0.0.1.dev0.dist-info → sigima-1.0.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,262 @@
1
+ # Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
2
+
3
+ """
4
+ Base signal processing functions and utilities
5
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from collections.abc import Callable
11
+ from typing import Any
12
+
13
+ import numpy as np
14
+
15
+ from sigima.objects import NO_ROI, GeometryResult, KindShape, SignalObj
16
+ from sigima.proc.base import dst_1_to_1
17
+
18
+
19
+ def restore_data_outside_roi(dst: SignalObj, src: SignalObj) -> None:
20
+ """Restore data outside the Region Of Interest (ROI) of the input signal
21
+ after a computation, only if the input signal has a ROI,
22
+ and if the output signal has the same ROI as the input signal,
23
+ and if the data types are the same,
24
+ and if the shapes are the same.
25
+ Otherwise, do nothing.
26
+
27
+ Args:
28
+ dst: destination signal object
29
+ src: source signal object
30
+ """
31
+ if src.maskdata is not None and dst.maskdata is not None:
32
+ if (
33
+ np.array_equal(src.maskdata, dst.maskdata)
34
+ and dst.xydata.dtype == src.xydata.dtype
35
+ and dst.xydata.shape == src.xydata.shape
36
+ ):
37
+ dst.xydata[src.maskdata] = src.xydata[src.maskdata]
38
+
39
+
40
+ def is_uncertainty_data_available(signals: SignalObj | list[SignalObj]) -> bool:
41
+ """Check if all signals have uncertainty data.
42
+
43
+ This functions is used to determine whether enough information is available to
44
+ propagate uncertainty.
45
+
46
+ Args:
47
+ signals: Signal object or list of signal objects.
48
+
49
+ Returns:
50
+ True if all signals have uncertainty data, False otherwise.
51
+ """
52
+ if isinstance(signals, SignalObj):
53
+ signals = [signals]
54
+ return all(sig.dy is not None for sig in signals)
55
+
56
+
57
+ class Wrap1to1Func:
58
+ """Wrap a 1 array → 1 array function (the simple case of y1 = f(y0)) to produce
59
+ a 1 signal → 1 signal function, which can be used as a Sigima computation function
60
+ and inside DataLab's infrastructure to perform computations with the Signal
61
+ Processor object.
62
+
63
+ This wrapping mechanism using a class is necessary for the resulted function to be
64
+ pickable by the ``multiprocessing`` module.
65
+
66
+ The instance of this wrapper is callable and returns
67
+ a :class:`sigima.objects.SignalObj` object.
68
+
69
+ Example:
70
+
71
+ >>> import numpy as np
72
+ >>> from sigima.proc.signal import Wrap1to1Func
73
+ >>> import sigima.objects
74
+ >>> def square(y):
75
+ ... return y**2
76
+ >>> compute_square = Wrap1to1Func(square)
77
+ >>> x = np.linspace(0, 10, 100)
78
+ >>> y = np.sin(x)
79
+ >>> sig0 = sigima.objects.create_signal("Example", x, y)
80
+ >>> sig1 = compute_square(sig0)
81
+
82
+ Args:
83
+ func: 1 array → 1 array function
84
+ *args: Additional positional arguments to pass to the function
85
+ **kwargs: Additional keyword arguments to pass to the function
86
+
87
+ .. note::
88
+
89
+ If `func_name` is provided in the keyword arguments, it will be used as the
90
+ function name instead of the default name derived from the function itself.
91
+
92
+ .. note::
93
+
94
+ This wrapper is suitable for functions that don't require custom uncertainty
95
+ propagation. For mathematical functions with specific uncertainty formulas
96
+ (sqrt, log10, exp, etc.), implement uncertainty propagation directly in the
97
+ computation function.
98
+ """
99
+
100
+ def __init__(self, func: Callable, *args: Any, **kwargs: Any) -> None:
101
+ self.func = func
102
+ self.args = args
103
+ self.kwargs = kwargs
104
+ self.__name__ = self.kwargs.pop("func_name", func.__name__)
105
+ self.__doc__ = func.__doc__
106
+ self.__call__.__func__.__doc__ = self.func.__doc__
107
+
108
+ def __call__(self, src: SignalObj) -> SignalObj:
109
+ """Compute the function on the input signal and return the result signal
110
+
111
+ Args:
112
+ src: input signal object
113
+
114
+ Returns:
115
+ Result signal object
116
+ """
117
+ suffix = ", ".join(
118
+ [str(arg) for arg in self.args]
119
+ + [f"{k}={v}" for k, v in self.kwargs.items() if v is not None]
120
+ )
121
+ dst = dst_1_to_1(src, self.__name__, suffix)
122
+ x, y = src.get_data()
123
+ # Apply function and propagate uncertainty unchanged
124
+ dst.set_xydata(x, self.func(y, *self.args, **self.kwargs), src.dx, src.dy)
125
+
126
+ restore_data_outside_roi(dst, src)
127
+ return dst
128
+
129
+
130
+ def signals_to_array(
131
+ signals: list[SignalObj], attr: str = "y", dtype: np.dtype | None = None
132
+ ) -> np.ndarray:
133
+ """Create an array from a list of signals.
134
+
135
+ Args:
136
+ signals: List of signal objects.
137
+ attr: Name of the attribute to extract ("y", "dy", etc.). Defaults to "y".
138
+ dtype: Desired type for the output array. If None, use the first signal's dtype.
139
+
140
+ Returns:
141
+ A NumPy array stacking the specified attribute from all signals.
142
+
143
+ Raises:
144
+ ValueError: If the signals list is empty.
145
+ """
146
+ if not signals:
147
+ raise ValueError("The signal list is empty.")
148
+ if dtype is None:
149
+ dtype = getattr(signals[0], attr).dtype
150
+ arr = np.array([getattr(sig, attr) for sig in signals], dtype=dtype)
151
+ return arr
152
+
153
+
154
+ def signals_y_to_array(
155
+ signals: SignalObj | list[SignalObj], dtype: np.dtype | None = None
156
+ ) -> np.ndarray:
157
+ """Create an array from a list of signals, extracting the `y` attribute.
158
+
159
+ Args:
160
+ signals: List of signal objects.
161
+ dtype: Desired type for the output array. If None, use the first signal's dtype.
162
+
163
+ Returns:
164
+ A NumPy array stacking the `y` attribute from all signals.
165
+ """
166
+ if isinstance(signals, SignalObj):
167
+ signals = [signals]
168
+ return signals_to_array(signals, attr="y", dtype=dtype)
169
+
170
+
171
+ def signals_dy_to_array(
172
+ signals: SignalObj | list[SignalObj], dtype: np.dtype | None = None
173
+ ) -> np.ndarray:
174
+ """Create an array from a list of signals, extracting the `dy` attribute.
175
+
176
+ Args:
177
+ signals: List of signal objects.
178
+ dtype: Desired type for the output array. If None, use the first signal's dtype.
179
+
180
+ Returns:
181
+ A NumPy array stacking the `dy` attribute from all signals.
182
+ """
183
+ if isinstance(signals, SignalObj):
184
+ signals = [signals]
185
+ return signals_to_array(signals, attr="dy", dtype=dtype)
186
+
187
+
188
+ def compute_geometry_from_obj(
189
+ title: str,
190
+ shape: str,
191
+ obj: SignalObj,
192
+ func: Callable,
193
+ *args: Any,
194
+ ) -> GeometryResult | None:
195
+ """Calculate result geometry by executing a computation function on a signal object.
196
+
197
+ Args:
198
+ title: Result title
199
+ shape: Result shape kind (e.g., "segment", "point")
200
+ obj: Input signal object
201
+ func: Computation function that takes (x, y, ``*args``) and returns coordinates
202
+ *args: Additional computation function arguments
203
+
204
+ Returns:
205
+ Result geometry object or None if no result is found
206
+
207
+ .. note::
208
+
209
+ The computation function must take x and y arrays as the first two arguments,
210
+ followed by any additional arguments, and return a NumPy array containing
211
+ coordinate pairs in the form ``[[x0, y0], [x1, y1], ...]``.
212
+ """
213
+ rows: list[np.ndarray] = []
214
+ roi_idx: list[int] = []
215
+
216
+ for i_roi in obj.iterate_roi_indices():
217
+ x, y = obj.get_data(i_roi)
218
+ if args:
219
+ results: np.ndarray = func(x, y, *args)
220
+ else:
221
+ results: np.ndarray = func(x, y)
222
+
223
+ if results is None:
224
+ continue
225
+
226
+ results = np.array(results, dtype=float)
227
+ if results.size == 0:
228
+ continue
229
+
230
+ # Ensure results are in the correct 2D format
231
+ if results.ndim == 1:
232
+ # For segment shapes, expect 4 coordinates: [x0, y0, x1, y1]
233
+ if shape == "segment" and len(results) == 4:
234
+ results = results.reshape(1, 4)
235
+ elif len(results) % 2 == 0:
236
+ # Reshape flat coordinate array to pairs for points
237
+ results = results.reshape(-1, 2)
238
+ else:
239
+ continue # Skip malformed results
240
+ elif results.ndim != 2 or results.shape[1] < 2:
241
+ continue # Skip malformed results
242
+
243
+ rows.append(results)
244
+ roi_idx.extend([NO_ROI if i_roi is None else int(i_roi)] * results.shape[0])
245
+
246
+ if not rows:
247
+ return None
248
+
249
+ coords = np.vstack(rows)
250
+ if shape == "segment":
251
+ shape_kind = KindShape.SEGMENT
252
+ elif shape == "point":
253
+ shape_kind = KindShape.POINT
254
+ else:
255
+ shape_kind = KindShape.POINT # Default fallback
256
+
257
+ return GeometryResult(
258
+ title=title,
259
+ kind=shape_kind,
260
+ coords=coords,
261
+ roi_indices=np.array(roi_idx, dtype=int),
262
+ )
@@ -0,0 +1,60 @@
1
+ # Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
2
+
3
+ """
4
+ Signal extraction and ROI operations
5
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import numpy as np
11
+
12
+ from sigima.objects import ROI1DParam, SignalObj
13
+ from sigima.proc.base import dst_1_to_1
14
+ from sigima.proc.decorator import computation_function
15
+
16
+
17
+ @computation_function()
18
+ def extract_rois(src: SignalObj, params: list[ROI1DParam]) -> SignalObj:
19
+ """Extract multiple regions of interest from data
20
+
21
+ Args:
22
+ src: source signal
23
+ params: list of ROI parameters
24
+
25
+ Returns:
26
+ Signal with multiple regions of interest
27
+ """
28
+ suffix = None
29
+ if len(params) == 1:
30
+ p: ROI1DParam = params[0]
31
+ suffix = f"{p.xmin:.3g}≤x≤{p.xmax:.3g}"
32
+ dst = dst_1_to_1(src, "extract_rois", suffix)
33
+ x, y = src.get_data()
34
+ xout, yout = np.ones_like(x) * np.nan, np.ones_like(y) * np.nan
35
+ for p in params:
36
+ idx1, idx2 = np.searchsorted(x, p.xmin), np.searchsorted(x, p.xmax)
37
+ slice0 = slice(idx1, idx2)
38
+ xout[slice0], yout[slice0] = x[slice0], y[slice0]
39
+ nans = np.isnan(xout) | np.isnan(yout)
40
+ # TODO: Handle uncertainty data
41
+ dst.set_xydata(xout[~nans], yout[~nans])
42
+ return dst
43
+
44
+
45
+ @computation_function()
46
+ def extract_roi(src: SignalObj, p: ROI1DParam) -> SignalObj:
47
+ """Extract single region of interest from data
48
+
49
+ Args:
50
+ src: source signal
51
+ p: ROI parameters
52
+
53
+ Returns:
54
+ Signal with single region of interest
55
+ """
56
+ dst = dst_1_to_1(src, "extract_roi", f"{p.xmin:.3g}≤x≤{p.xmax:.3g}")
57
+ x, y = p.get_data(src).copy()
58
+ # TODO: Handle uncertainty data
59
+ dst.set_xydata(x, y)
60
+ return dst
@@ -0,0 +1,310 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Licensed under the terms of the BSD 3-Clause
4
+ # (see sigima/LICENSE for details)
5
+
6
+ """
7
+ Feature extraction and analysis functions
8
+ =========================================
9
+
10
+ This module provides feature extraction and analysis functions for signal objects:
11
+
12
+ - Peak detection
13
+ - Full Width at Half Maximum (FWHM) and related measurements
14
+ - Statistical analysis
15
+ - Bandwidth calculations
16
+ - Dynamic parameters (ENOB, SNR, SINAD, THD, SFDR)
17
+
18
+ .. note::
19
+
20
+ Most operations use functions from :mod:`sigima.tools.signal` for actual
21
+ computations.
22
+ """
23
+
24
+ from __future__ import annotations
25
+
26
+ import guidata.dataset as gds
27
+ import numpy as np
28
+ import scipy.integrate as spt
29
+
30
+ from sigima.config import _
31
+ from sigima.enums import PowerUnit
32
+ from sigima.objects import (
33
+ GeometryResult,
34
+ SignalObj,
35
+ TableKind,
36
+ TableResult,
37
+ TableResultBuilder,
38
+ )
39
+ from sigima.proc.base import dst_1_to_1
40
+ from sigima.proc.decorator import computation_function
41
+ from sigima.proc.signal.base import compute_geometry_from_obj
42
+ from sigima.tools.signal import dynamic, features, peakdetection, pulse
43
+
44
+
45
+ class PeakDetectionParam(gds.DataSet, title=_("Peak detection")):
46
+ """Peak detection parameters"""
47
+
48
+ threshold = gds.FloatItem(_("Threshold"), default=0.1, min=0.0)
49
+ min_dist = gds.IntItem(_("Minimum distance"), default=1, min=1)
50
+
51
+
52
+ @computation_function()
53
+ def peak_detection(src: SignalObj, p: PeakDetectionParam) -> SignalObj:
54
+ """Peak detection with
55
+ :py:func:`sigima.tools.signal.peakdetection.peak_indices`
56
+
57
+ Args:
58
+ src: source signal
59
+ p: parameters
60
+
61
+ Returns:
62
+ Result signal object
63
+ """
64
+ dst = dst_1_to_1(
65
+ src, "peak_detection", f"threshold={p.threshold}%, min_dist={p.min_dist}pts"
66
+ )
67
+ x, y = src.get_data()
68
+ indices = peakdetection.peak_indices(
69
+ y, thres=p.threshold * 0.01, min_dist=p.min_dist
70
+ )
71
+ dst.set_xydata(x[indices], y[indices])
72
+ dst.set_metadata_option("curvestyle", "Sticks")
73
+ return dst
74
+
75
+
76
+ class FWHMParam(gds.DataSet, title=_("FWHM")):
77
+ """FWHM parameters"""
78
+
79
+ methods = (
80
+ ("zero-crossing", _("Zero-crossing")),
81
+ ("gauss", _("Gaussian fit")),
82
+ ("lorentz", _("Lorentzian fit")),
83
+ ("voigt", _("Voigt fit")),
84
+ )
85
+ method = gds.ChoiceItem(_("Method"), methods, default="zero-crossing")
86
+ xmin = gds.FloatItem(
87
+ "X<sub>MIN</sub>",
88
+ default=None,
89
+ check=False,
90
+ help=_("Lower X boundary (empty for no limit, i.e. start of the signal)"),
91
+ )
92
+ xmax = gds.FloatItem(
93
+ "X<sub>MAX</sub>",
94
+ default=None,
95
+ check=False,
96
+ help=_("Upper X boundary (empty for no limit, i.e. end of the signal)"),
97
+ )
98
+
99
+
100
+ @computation_function()
101
+ def fwhm(obj: SignalObj, param: FWHMParam) -> GeometryResult | None:
102
+ """Compute FWHM with :py:func:`sigima.tools.signal.pulse.fwhm`
103
+
104
+ Args:
105
+ obj: source signal
106
+ param: parameters
107
+
108
+ Returns:
109
+ Segment coordinates
110
+ """
111
+ return compute_geometry_from_obj(
112
+ "fwhm",
113
+ "segment",
114
+ obj,
115
+ pulse.fwhm,
116
+ param.method,
117
+ param.xmin,
118
+ param.xmax,
119
+ )
120
+
121
+
122
+ @computation_function()
123
+ def fw1e2(obj: SignalObj) -> GeometryResult | None:
124
+ """Compute FW at 1/e² with :py:func:`sigima.tools.signal.pulse.fw1e2`
125
+
126
+ Args:
127
+ obj: source signal
128
+
129
+ Returns:
130
+ Segment coordinates
131
+ """
132
+ return compute_geometry_from_obj("fw1e2", "segment", obj, pulse.fw1e2)
133
+
134
+
135
+ class OrdinateParam(gds.DataSet, title=_("Ordinate")):
136
+ """Ordinate parameter."""
137
+
138
+ y = gds.FloatItem(_("Ordinate"), default=0.0)
139
+
140
+
141
+ @computation_function()
142
+ def full_width_at_y(obj: SignalObj, p: OrdinateParam) -> GeometryResult | None:
143
+ """
144
+ Compute full width at a given y value for a signal object.
145
+
146
+ Args:
147
+ obj: The signal object containing x and y data.
148
+ p: The ordinate parameter dataset
149
+
150
+ Returns:
151
+ Segment coordinates
152
+ """
153
+ return compute_geometry_from_obj("∆X", "segment", obj, pulse.full_width_at_y, p.y)
154
+
155
+
156
+ def _find_first_x_at_y_value(xy: np.ndarray, y_target: float) -> float:
157
+ """Find the first x value where :math:`y = f(x)` equals the value :math:`y_target`.
158
+
159
+ Args:
160
+ xy: Tuple of (x, y) data arrays.
161
+ y_target: Target y value.
162
+
163
+ Returns:
164
+ The first interpolated x value at the given :math:`y_target`,
165
+ or `nan` if none found.
166
+ """
167
+ x_values = features.find_x_values_at_y(xy[0], xy[1], y_target)
168
+ return x_values[0] if len(x_values) > 0 else np.nan
169
+
170
+
171
+ @computation_function()
172
+ def x_at_y(obj: SignalObj, p: OrdinateParam) -> TableResult:
173
+ """
174
+ Compute the smallest x-value at a given y-value for a signal object.
175
+
176
+ Args:
177
+ obj: The signal object containing x and y data.
178
+ p: The parameter dataset for finding the abscissa.
179
+
180
+ Returns:
181
+ An object containing the x-value.
182
+ """
183
+ table = TableResultBuilder(f"x|y={p.y}")
184
+ table.add(lambda xy: _find_first_x_at_y_value(xy, p.y), "x@y")
185
+ return table.compute(obj)
186
+
187
+
188
+ class AbscissaParam(gds.DataSet, title=_("Abscissa")):
189
+ """Abscissa parameter."""
190
+
191
+ x = gds.FloatItem(_("Abscissa"), default=0.0)
192
+
193
+
194
+ @computation_function()
195
+ def y_at_x(obj: SignalObj, p: AbscissaParam) -> TableResult:
196
+ """
197
+ Compute the smallest y-value at a given x-value for a signal object.
198
+
199
+ Args:
200
+ obj: The signal object containing x and y data.
201
+ p: The parameter dataset for finding the ordinate.
202
+
203
+ Returns:
204
+ An object containing the y-value.
205
+ """
206
+ table = TableResultBuilder(f"y|x={p.x}")
207
+ table.add(lambda xy: features.find_y_at_x_value(xy[0], xy[1], p.x), "y@x")
208
+ return table.compute(obj)
209
+
210
+
211
+ @computation_function()
212
+ def stats(obj: SignalObj) -> TableResult:
213
+ """Compute statistics on a signal
214
+
215
+ Args:
216
+ obj: source signal
217
+
218
+ Returns:
219
+ Result properties object
220
+ """
221
+ table = TableResultBuilder(_("Signal statistics"), kind=TableKind.STATISTICS)
222
+ table.add(lambda xy: np.nanmin(xy[1]), "min")
223
+ table.add(lambda xy: np.nanmax(xy[1]), "max")
224
+ table.add(lambda xy: np.nanmean(xy[1]), "mean")
225
+ table.add(lambda xy: np.nanmedian(xy[1]), "median")
226
+ table.add(lambda xy: np.nanstd(xy[1]), "std")
227
+ table.add(lambda xy: np.nanmean(xy[1]) / np.nanstd(xy[1]), "snr")
228
+ table.add(lambda xy: np.nanmax(xy[1]) - np.nanmin(xy[1]), "ptp")
229
+ table.add(lambda xy: np.nansum(xy[1]), "sum")
230
+ table.add(lambda xy: spt.trapezoid(xy[1], xy[0]), "trapz")
231
+ return table.compute(obj)
232
+
233
+
234
+ @computation_function()
235
+ def bandwidth_3db(obj: SignalObj) -> GeometryResult | None:
236
+ """Compute bandwidth at -3 dB with
237
+ :py:func:`sigima.tools.signal.misc.bandwidth`
238
+
239
+ .. note::
240
+
241
+ The bandwidth is defined as the range of frequencies over which the signal
242
+ maintains a certain level relative to its peak.
243
+
244
+ .. warning::
245
+
246
+ The signal is assumed to be smooth enough for the bandwidth calculation to be
247
+ meaningful. If the signal contains excessive noise, multiple peaks, or is not
248
+ sufficiently continuous, the computed bandwidth may not accurately represent the
249
+ true -3dB range. It is recommended to preprocess the signal to ensure reliable
250
+ results.
251
+
252
+ Args:
253
+ obj: Source signal.
254
+
255
+ Returns:
256
+ Result shape with bandwidth.
257
+ """
258
+ return compute_geometry_from_obj(
259
+ "bandwidth", "segment", obj, features.find_bandwidth_coordinates, -3.0
260
+ )
261
+
262
+
263
+ class DynamicParam(gds.DataSet, title=_("Dynamic parameters")):
264
+ """Parameters for dynamic range computation (ENOB, SNR, SINAD, THD, SFDR)"""
265
+
266
+ full_scale = gds.FloatItem(_("Full scale"), default=0.16, min=0.0, unit="V")
267
+ unit = gds.ChoiceItem(
268
+ _("Unit"),
269
+ [(PowerUnit.DBC, "dBc"), (PowerUnit.DBFS, "dBFS")],
270
+ default=PowerUnit.DBC,
271
+ help=_("Unit for SINAD"),
272
+ )
273
+ nb_harm = gds.IntItem(
274
+ _("Number of harmonics"),
275
+ default=5,
276
+ min=1,
277
+ help=_("Number of harmonics to consider for THD"),
278
+ )
279
+
280
+
281
+ @computation_function()
282
+ def dynamic_parameters(src: SignalObj, p: DynamicParam) -> TableResult:
283
+ """Compute Dynamic parameters
284
+ using the following functions:
285
+
286
+ - Freq: :py:func:`sigima.tools.signal.dynamic.sinus_frequency`
287
+ - ENOB: :py:func:`sigima.tools.signal.dynamic.enob`
288
+ - SNR: :py:func:`sigima.tools.signal.dynamic.snr`
289
+ - SINAD: :py:func:`sigima.tools.signal.dynamic.sinad`
290
+ - THD: :py:func:`sigima.tools.signal.dynamic.thd`
291
+ - SFDR: :py:func:`sigima.tools.signal.dynamic.sfdr`
292
+
293
+ Args:
294
+ src: source signal
295
+ p: parameters
296
+
297
+ Returns:
298
+ Result properties with ENOB, SNR, SINAD, THD, SFDR
299
+ """
300
+ unit: PowerUnit = p.unit
301
+ table = TableResultBuilder(_("Dynamic parameters"))
302
+ table.add(lambda xy: dynamic.sinus_frequency(xy[0], xy[1]), "freq")
303
+ table.add(lambda xy: dynamic.enob(xy[0], xy[1], p.full_scale), "enob")
304
+ table.add(lambda xy: dynamic.snr(xy[0], xy[1], unit), "snr")
305
+ table.add(lambda xy: dynamic.sinad(xy[0], xy[1], unit), "sinad")
306
+ table.add(
307
+ lambda xy: dynamic.thd(xy[0], xy[1], p.full_scale, unit, p.nb_harm), "thd"
308
+ )
309
+ table.add(lambda xy: dynamic.sfdr(xy[0], xy[1], p.full_scale, unit), "sfdr")
310
+ return table.compute(src)