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.
- sigima/__init__.py +142 -2
- sigima/client/__init__.py +105 -0
- sigima/client/base.py +780 -0
- sigima/client/remote.py +469 -0
- sigima/client/stub.py +814 -0
- sigima/client/utils.py +90 -0
- sigima/config.py +444 -0
- sigima/data/logo/Sigima.svg +135 -0
- sigima/data/tests/annotations.json +798 -0
- sigima/data/tests/curve_fitting/exponential_fit.txt +511 -0
- sigima/data/tests/curve_fitting/gaussian_fit.txt +100 -0
- sigima/data/tests/curve_fitting/piecewiseexponential_fit.txt +1022 -0
- sigima/data/tests/curve_fitting/polynomial_fit.txt +100 -0
- sigima/data/tests/curve_fitting/twohalfgaussian_fit.txt +1000 -0
- sigima/data/tests/curve_formats/bandwidth.txt +201 -0
- sigima/data/tests/curve_formats/boxcar.npy +0 -0
- sigima/data/tests/curve_formats/datetime.txt +1001 -0
- sigima/data/tests/curve_formats/dynamic_parameters.txt +4000 -0
- sigima/data/tests/curve_formats/fw1e2.txt +301 -0
- sigima/data/tests/curve_formats/fwhm.txt +319 -0
- sigima/data/tests/curve_formats/multiple_curves.csv +29 -0
- sigima/data/tests/curve_formats/noised_saw.mat +0 -0
- sigima/data/tests/curve_formats/oscilloscope.csv +111 -0
- sigima/data/tests/curve_formats/other/other2/recursive2.txt +5 -0
- sigima/data/tests/curve_formats/other/recursive1.txt +5 -0
- sigima/data/tests/curve_formats/paracetamol.npy +0 -0
- sigima/data/tests/curve_formats/paracetamol.txt +1010 -0
- sigima/data/tests/curve_formats/paracetamol_dx_dy.csv +1000 -0
- sigima/data/tests/curve_formats/paracetamol_dy.csv +1001 -0
- sigima/data/tests/curve_formats/pulse1.npy +0 -0
- sigima/data/tests/curve_formats/pulse2.npy +0 -0
- sigima/data/tests/curve_formats/simple.txt +5 -0
- sigima/data/tests/curve_formats/spectrum.mca +2139 -0
- sigima/data/tests/curve_formats/square2.npy +0 -0
- sigima/data/tests/curve_formats/step.npy +0 -0
- sigima/data/tests/fabry-perot1.jpg +0 -0
- sigima/data/tests/fabry-perot2.jpg +0 -0
- sigima/data/tests/flower.npy +0 -0
- sigima/data/tests/image_formats/NF 180338201.scor-data +11003 -0
- sigima/data/tests/image_formats/binary_image.npy +0 -0
- sigima/data/tests/image_formats/binary_image.png +0 -0
- sigima/data/tests/image_formats/centroid_test.npy +0 -0
- sigima/data/tests/image_formats/coordinated_text/complex_image.txt +10011 -0
- sigima/data/tests/image_formats/coordinated_text/complex_ref_image.txt +10010 -0
- sigima/data/tests/image_formats/coordinated_text/image.txt +15 -0
- sigima/data/tests/image_formats/coordinated_text/image2.txt +14 -0
- sigima/data/tests/image_formats/coordinated_text/image_no_unit_no_label.txt +14 -0
- sigima/data/tests/image_formats/coordinated_text/image_with_nan.txt +15 -0
- sigima/data/tests/image_formats/coordinated_text/image_with_unit.txt +14 -0
- sigima/data/tests/image_formats/fiber.csv +480 -0
- sigima/data/tests/image_formats/fiber.jpg +0 -0
- sigima/data/tests/image_formats/fiber.png +0 -0
- sigima/data/tests/image_formats/fiber.txt +480 -0
- sigima/data/tests/image_formats/gaussian_spot_with_noise.npy +0 -0
- sigima/data/tests/image_formats/mr-brain.dcm +0 -0
- sigima/data/tests/image_formats/noised_gaussian.mat +0 -0
- sigima/data/tests/image_formats/sif_reader/nd_lum_image_no_glue.sif +0 -0
- sigima/data/tests/image_formats/sif_reader/raman1.sif +0 -0
- sigima/data/tests/image_formats/tiling.txt +10 -0
- sigima/data/tests/image_formats/uint16.tiff +0 -0
- sigima/data/tests/image_formats/uint8.tiff +0 -0
- sigima/data/tests/laser_beam/TEM00_z_13.jpg +0 -0
- sigima/data/tests/laser_beam/TEM00_z_18.jpg +0 -0
- sigima/data/tests/laser_beam/TEM00_z_23.jpg +0 -0
- sigima/data/tests/laser_beam/TEM00_z_30.jpg +0 -0
- sigima/data/tests/laser_beam/TEM00_z_35.jpg +0 -0
- sigima/data/tests/laser_beam/TEM00_z_40.jpg +0 -0
- sigima/data/tests/laser_beam/TEM00_z_45.jpg +0 -0
- sigima/data/tests/laser_beam/TEM00_z_50.jpg +0 -0
- sigima/data/tests/laser_beam/TEM00_z_55.jpg +0 -0
- sigima/data/tests/laser_beam/TEM00_z_60.jpg +0 -0
- sigima/data/tests/laser_beam/TEM00_z_65.jpg +0 -0
- sigima/data/tests/laser_beam/TEM00_z_70.jpg +0 -0
- sigima/data/tests/laser_beam/TEM00_z_75.jpg +0 -0
- sigima/data/tests/laser_beam/TEM00_z_80.jpg +0 -0
- sigima/enums.py +195 -0
- sigima/io/__init__.py +123 -0
- sigima/io/base.py +311 -0
- sigima/io/common/__init__.py +5 -0
- sigima/io/common/basename.py +164 -0
- sigima/io/common/converters.py +189 -0
- sigima/io/common/objmeta.py +181 -0
- sigima/io/common/textreader.py +58 -0
- sigima/io/convenience.py +157 -0
- sigima/io/enums.py +17 -0
- sigima/io/ftlab.py +395 -0
- sigima/io/image/__init__.py +9 -0
- sigima/io/image/base.py +177 -0
- sigima/io/image/formats.py +1016 -0
- sigima/io/image/funcs.py +414 -0
- sigima/io/signal/__init__.py +9 -0
- sigima/io/signal/base.py +129 -0
- sigima/io/signal/formats.py +290 -0
- sigima/io/signal/funcs.py +723 -0
- sigima/objects/__init__.py +260 -0
- sigima/objects/base.py +937 -0
- sigima/objects/image/__init__.py +88 -0
- sigima/objects/image/creation.py +556 -0
- sigima/objects/image/object.py +524 -0
- sigima/objects/image/roi.py +904 -0
- sigima/objects/scalar/__init__.py +57 -0
- sigima/objects/scalar/common.py +215 -0
- sigima/objects/scalar/geometry.py +502 -0
- sigima/objects/scalar/table.py +784 -0
- sigima/objects/shape.py +290 -0
- sigima/objects/signal/__init__.py +133 -0
- sigima/objects/signal/constants.py +27 -0
- sigima/objects/signal/creation.py +1428 -0
- sigima/objects/signal/object.py +444 -0
- sigima/objects/signal/roi.py +274 -0
- sigima/params.py +405 -0
- sigima/proc/__init__.py +96 -0
- sigima/proc/base.py +381 -0
- sigima/proc/decorator.py +330 -0
- sigima/proc/image/__init__.py +513 -0
- sigima/proc/image/arithmetic.py +335 -0
- sigima/proc/image/base.py +260 -0
- sigima/proc/image/detection.py +519 -0
- sigima/proc/image/edges.py +329 -0
- sigima/proc/image/exposure.py +406 -0
- sigima/proc/image/extraction.py +458 -0
- sigima/proc/image/filtering.py +219 -0
- sigima/proc/image/fourier.py +147 -0
- sigima/proc/image/geometry.py +661 -0
- sigima/proc/image/mathops.py +340 -0
- sigima/proc/image/measurement.py +195 -0
- sigima/proc/image/morphology.py +155 -0
- sigima/proc/image/noise.py +107 -0
- sigima/proc/image/preprocessing.py +182 -0
- sigima/proc/image/restoration.py +235 -0
- sigima/proc/image/threshold.py +217 -0
- sigima/proc/image/transformations.py +393 -0
- sigima/proc/signal/__init__.py +376 -0
- sigima/proc/signal/analysis.py +206 -0
- sigima/proc/signal/arithmetic.py +551 -0
- sigima/proc/signal/base.py +262 -0
- sigima/proc/signal/extraction.py +60 -0
- sigima/proc/signal/features.py +310 -0
- sigima/proc/signal/filtering.py +484 -0
- sigima/proc/signal/fitting.py +276 -0
- sigima/proc/signal/fourier.py +259 -0
- sigima/proc/signal/mathops.py +420 -0
- sigima/proc/signal/processing.py +580 -0
- sigima/proc/signal/stability.py +175 -0
- sigima/proc/title_formatting.py +227 -0
- sigima/proc/validation.py +272 -0
- sigima/tests/__init__.py +7 -0
- sigima/tests/common/__init__.py +0 -0
- sigima/tests/common/arithmeticparam_unit_test.py +26 -0
- sigima/tests/common/basename_unit_test.py +126 -0
- sigima/tests/common/client_unit_test.py +412 -0
- sigima/tests/common/converters_unit_test.py +77 -0
- sigima/tests/common/decorator_unit_test.py +176 -0
- sigima/tests/common/examples_unit_test.py +104 -0
- sigima/tests/common/kernel_normalization_unit_test.py +242 -0
- sigima/tests/common/roi_basic_unit_test.py +73 -0
- sigima/tests/common/roi_geometry_unit_test.py +171 -0
- sigima/tests/common/scalar_builder_unit_test.py +142 -0
- sigima/tests/common/scalar_unit_test.py +991 -0
- sigima/tests/common/shape_unit_test.py +183 -0
- sigima/tests/common/stat_unit_test.py +138 -0
- sigima/tests/common/title_formatting_unit_test.py +338 -0
- sigima/tests/common/tools_coordinates_unit_test.py +60 -0
- sigima/tests/common/transformations_unit_test.py +178 -0
- sigima/tests/common/validation_unit_test.py +205 -0
- sigima/tests/conftest.py +129 -0
- sigima/tests/data.py +998 -0
- sigima/tests/env.py +280 -0
- sigima/tests/guiutils.py +163 -0
- sigima/tests/helpers.py +532 -0
- sigima/tests/image/__init__.py +28 -0
- sigima/tests/image/binning_unit_test.py +128 -0
- sigima/tests/image/blob_detection_unit_test.py +312 -0
- sigima/tests/image/centroid_unit_test.py +170 -0
- sigima/tests/image/check_2d_array_unit_test.py +63 -0
- sigima/tests/image/contour_unit_test.py +172 -0
- sigima/tests/image/convolution_unit_test.py +178 -0
- sigima/tests/image/datatype_unit_test.py +67 -0
- sigima/tests/image/edges_unit_test.py +155 -0
- sigima/tests/image/enclosingcircle_unit_test.py +88 -0
- sigima/tests/image/exposure_unit_test.py +223 -0
- sigima/tests/image/fft2d_unit_test.py +189 -0
- sigima/tests/image/filtering_unit_test.py +166 -0
- sigima/tests/image/geometry_unit_test.py +654 -0
- sigima/tests/image/hough_circle_unit_test.py +147 -0
- sigima/tests/image/imageobj_unit_test.py +737 -0
- sigima/tests/image/morphology_unit_test.py +71 -0
- sigima/tests/image/noise_unit_test.py +57 -0
- sigima/tests/image/offset_correction_unit_test.py +72 -0
- sigima/tests/image/operation_unit_test.py +518 -0
- sigima/tests/image/peak2d_limits_unit_test.py +41 -0
- sigima/tests/image/peak2d_unit_test.py +133 -0
- sigima/tests/image/profile_unit_test.py +159 -0
- sigima/tests/image/projections_unit_test.py +121 -0
- sigima/tests/image/restoration_unit_test.py +141 -0
- sigima/tests/image/roi2dparam_unit_test.py +53 -0
- sigima/tests/image/roi_advanced_unit_test.py +588 -0
- sigima/tests/image/roi_grid_unit_test.py +279 -0
- sigima/tests/image/spectrum2d_unit_test.py +40 -0
- sigima/tests/image/threshold_unit_test.py +91 -0
- sigima/tests/io/__init__.py +0 -0
- sigima/tests/io/addnewformat_unit_test.py +125 -0
- sigima/tests/io/convenience_funcs_unit_test.py +470 -0
- sigima/tests/io/coordinated_text_format_unit_test.py +495 -0
- sigima/tests/io/datetime_csv_unit_test.py +198 -0
- sigima/tests/io/imageio_formats_test.py +41 -0
- sigima/tests/io/ioregistry_unit_test.py +69 -0
- sigima/tests/io/objmeta_unit_test.py +87 -0
- sigima/tests/io/readobj_unit_test.py +130 -0
- sigima/tests/io/readwriteobj_unit_test.py +67 -0
- sigima/tests/signal/__init__.py +0 -0
- sigima/tests/signal/analysis_unit_test.py +135 -0
- sigima/tests/signal/check_1d_arrays_unit_test.py +169 -0
- sigima/tests/signal/convolution_unit_test.py +404 -0
- sigima/tests/signal/datetime_unit_test.py +176 -0
- sigima/tests/signal/fft1d_unit_test.py +303 -0
- sigima/tests/signal/filters_unit_test.py +403 -0
- sigima/tests/signal/fitting_unit_test.py +929 -0
- sigima/tests/signal/fwhm_unit_test.py +111 -0
- sigima/tests/signal/noise_unit_test.py +128 -0
- sigima/tests/signal/offset_correction_unit_test.py +34 -0
- sigima/tests/signal/operation_unit_test.py +489 -0
- sigima/tests/signal/peakdetection_unit_test.py +145 -0
- sigima/tests/signal/processing_unit_test.py +657 -0
- sigima/tests/signal/pulse/__init__.py +112 -0
- sigima/tests/signal/pulse/crossing_times_unit_test.py +123 -0
- sigima/tests/signal/pulse/plateau_detection_unit_test.py +102 -0
- sigima/tests/signal/pulse/pulse_unit_test.py +1824 -0
- sigima/tests/signal/roi_advanced_unit_test.py +392 -0
- sigima/tests/signal/signalobj_unit_test.py +603 -0
- sigima/tests/signal/stability_unit_test.py +431 -0
- sigima/tests/signal/uncertainty_unit_test.py +611 -0
- sigima/tests/vistools.py +1030 -0
- sigima/tools/__init__.py +59 -0
- sigima/tools/checks.py +290 -0
- sigima/tools/coordinates.py +308 -0
- sigima/tools/datatypes.py +26 -0
- sigima/tools/image/__init__.py +97 -0
- sigima/tools/image/detection.py +451 -0
- sigima/tools/image/exposure.py +77 -0
- sigima/tools/image/extraction.py +48 -0
- sigima/tools/image/fourier.py +260 -0
- sigima/tools/image/geometry.py +190 -0
- sigima/tools/image/preprocessing.py +165 -0
- sigima/tools/signal/__init__.py +86 -0
- sigima/tools/signal/dynamic.py +254 -0
- sigima/tools/signal/features.py +135 -0
- sigima/tools/signal/filtering.py +171 -0
- sigima/tools/signal/fitting.py +1171 -0
- sigima/tools/signal/fourier.py +466 -0
- sigima/tools/signal/interpolation.py +70 -0
- sigima/tools/signal/peakdetection.py +126 -0
- sigima/tools/signal/pulse.py +1626 -0
- sigima/tools/signal/scaling.py +50 -0
- sigima/tools/signal/stability.py +258 -0
- sigima/tools/signal/windowing.py +90 -0
- sigima/worker.py +79 -0
- sigima-1.0.0.dist-info/METADATA +233 -0
- sigima-1.0.0.dist-info/RECORD +262 -0
- {sigima-0.0.1.dev0.dist-info → sigima-1.0.0.dist-info}/licenses/LICENSE +29 -29
- sigima-0.0.1.dev0.dist-info/METADATA +0 -60
- sigima-0.0.1.dev0.dist-info/RECORD +0 -6
- {sigima-0.0.1.dev0.dist-info → sigima-1.0.0.dist-info}/WHEEL +0 -0
- {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)
|