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,580 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the terms of the BSD 3-Clause
|
|
4
|
+
# (see sigima/LICENSE for details)
|
|
5
|
+
|
|
6
|
+
"""
|
|
7
|
+
Signal processing operations
|
|
8
|
+
============================
|
|
9
|
+
|
|
10
|
+
This module provides signal processing operations:
|
|
11
|
+
|
|
12
|
+
- Zero padding
|
|
13
|
+
- Interpolation and resampling
|
|
14
|
+
- Convolution and deconvolution
|
|
15
|
+
- Signal manipulation functions
|
|
16
|
+
|
|
17
|
+
.. note::
|
|
18
|
+
|
|
19
|
+
Most operations use functions from :mod:`sigima.tools.signal` for actual
|
|
20
|
+
computations.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
import warnings
|
|
26
|
+
|
|
27
|
+
import guidata.dataset as gds
|
|
28
|
+
import numpy as np
|
|
29
|
+
import scipy.integrate as spt
|
|
30
|
+
import scipy.signal as sps
|
|
31
|
+
from guidata.dataset import FuncProp, GetAttrProp
|
|
32
|
+
|
|
33
|
+
from sigima.config import _
|
|
34
|
+
from sigima.config import options as sigima_options
|
|
35
|
+
from sigima.enums import Interpolation1DMethod, NormalizationMethod, WindowingMethod
|
|
36
|
+
from sigima.objects import ROI1DParam, SignalObj
|
|
37
|
+
from sigima.proc.base import ClipParam, NormalizeParam, dst_2_to_1
|
|
38
|
+
from sigima.proc.decorator import computation_function
|
|
39
|
+
from sigima.tools.signal import fourier, interpolation, scaling, windowing
|
|
40
|
+
|
|
41
|
+
from .base import dst_1_to_1, is_uncertainty_data_available, restore_data_outside_roi
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class InterpolationParam(gds.DataSet, title=_("Interpolation")):
|
|
45
|
+
"""Interpolation parameters"""
|
|
46
|
+
|
|
47
|
+
method = gds.ChoiceItem(
|
|
48
|
+
_("Interpolation method"),
|
|
49
|
+
[
|
|
50
|
+
(Interpolation1DMethod.LINEAR, "Linear"),
|
|
51
|
+
(Interpolation1DMethod.SPLINE, "Spline"),
|
|
52
|
+
(Interpolation1DMethod.QUADRATIC, "Quadratic"),
|
|
53
|
+
(Interpolation1DMethod.CUBIC, "Cubic"),
|
|
54
|
+
(Interpolation1DMethod.BARYCENTRIC, "Barycentric"),
|
|
55
|
+
(Interpolation1DMethod.PCHIP, "PCHIP"),
|
|
56
|
+
],
|
|
57
|
+
default=Interpolation1DMethod.LINEAR,
|
|
58
|
+
)
|
|
59
|
+
fill_value = gds.FloatItem(
|
|
60
|
+
_("Fill value"),
|
|
61
|
+
default=None,
|
|
62
|
+
help=_(
|
|
63
|
+
"Value to use for points outside the interpolation domain "
|
|
64
|
+
"(used only with linear, cubic and pchip methods)."
|
|
65
|
+
),
|
|
66
|
+
check=False,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@computation_function()
|
|
71
|
+
def interpolate(src1: SignalObj, src2: SignalObj, p: InterpolationParam) -> SignalObj:
|
|
72
|
+
"""Interpolate data with :py:func:`sigima.tools.signal.interpolation.interpolate`.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
src1: Source signal to interpolate.
|
|
76
|
+
src2: Signal with new x-axis.
|
|
77
|
+
p: Parameters.
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
Result signal object.
|
|
81
|
+
"""
|
|
82
|
+
suffix = f"method={p.method}"
|
|
83
|
+
if p.fill_value is not None and p.method in (
|
|
84
|
+
Interpolation1DMethod.LINEAR,
|
|
85
|
+
Interpolation1DMethod.CUBIC,
|
|
86
|
+
Interpolation1DMethod.PCHIP,
|
|
87
|
+
):
|
|
88
|
+
suffix += f", fill_value={p.fill_value}"
|
|
89
|
+
dst = dst_2_to_1(src1, src2, "interpolate", suffix)
|
|
90
|
+
x1, y1 = src1.get_data()
|
|
91
|
+
xnew, _y2 = src2.get_data()
|
|
92
|
+
ynew = interpolation.interpolate(x1, y1, xnew, p.method, p.fill_value)
|
|
93
|
+
dst.set_xydata(xnew, ynew)
|
|
94
|
+
return dst
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class Resampling1DParam(InterpolationParam):
|
|
98
|
+
"""Resample parameters for 1D signals"""
|
|
99
|
+
|
|
100
|
+
xmin = gds.FloatItem(_("X<sub>min</sub>"), allow_none=True)
|
|
101
|
+
xmax = gds.FloatItem(_("X<sub>max</sub>"), allow_none=True)
|
|
102
|
+
_prop = GetAttrProp("dx_or_nbpts")
|
|
103
|
+
_modes = (("dx", "ΔX"), ("nbpts", _("Number of points")))
|
|
104
|
+
mode = gds.ChoiceItem(_("Mode"), _modes, default="nbpts", radio=True).set_prop(
|
|
105
|
+
"display", store=_prop
|
|
106
|
+
)
|
|
107
|
+
dx = gds.FloatItem("ΔX", allow_none=True).set_prop(
|
|
108
|
+
"display", active=FuncProp(_prop, lambda x: x == "dx")
|
|
109
|
+
)
|
|
110
|
+
nbpts = gds.IntItem(_("Number of points"), allow_none=True).set_prop(
|
|
111
|
+
"display", active=FuncProp(_prop, lambda x: x == "nbpts")
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
def update_from_obj(self, obj: SignalObj) -> None:
|
|
115
|
+
"""Update parameters from a signal object."""
|
|
116
|
+
if self.xmin is None:
|
|
117
|
+
self.xmin = obj.x[0]
|
|
118
|
+
if self.xmax is None:
|
|
119
|
+
self.xmax = obj.x[-1]
|
|
120
|
+
if self.dx is None:
|
|
121
|
+
self.dx = obj.x[1] - obj.x[0]
|
|
122
|
+
if self.nbpts is None:
|
|
123
|
+
self.nbpts = len(obj.x)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
@computation_function()
|
|
127
|
+
def resampling(src: SignalObj, p: Resampling1DParam) -> SignalObj:
|
|
128
|
+
"""Resample data with :py:func:`sigima.tools.signal.interpolation.interpolate`
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
src: source signal
|
|
132
|
+
p: parameters
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
Result signal object
|
|
136
|
+
"""
|
|
137
|
+
# Create new x-axis based on parameters
|
|
138
|
+
if p.mode == "dx":
|
|
139
|
+
assert p.dx is not None
|
|
140
|
+
xnew = np.arange(p.xmin, p.xmax + p.dx / 2, p.dx)
|
|
141
|
+
else:
|
|
142
|
+
assert p.nbpts is not None
|
|
143
|
+
xnew = np.linspace(p.xmin, p.xmax, p.nbpts)
|
|
144
|
+
|
|
145
|
+
method: Interpolation1DMethod = p.method
|
|
146
|
+
suffix = f"method={method.value}"
|
|
147
|
+
if p.fill_value is not None and method in (
|
|
148
|
+
Interpolation1DMethod.LINEAR,
|
|
149
|
+
Interpolation1DMethod.CUBIC,
|
|
150
|
+
Interpolation1DMethod.PCHIP,
|
|
151
|
+
):
|
|
152
|
+
suffix += f", fill_value={p.fill_value}"
|
|
153
|
+
|
|
154
|
+
dst = dst_1_to_1(src, "resampling", suffix)
|
|
155
|
+
x, y = src.get_data()
|
|
156
|
+
ynew = interpolation.interpolate(x, y, xnew, method, p.fill_value)
|
|
157
|
+
dst.set_xydata(xnew, ynew)
|
|
158
|
+
return dst
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def check_same_sample_rate(src1: SignalObj, src2: SignalObj) -> None:
|
|
162
|
+
"""Check if two signals have a constant step size *and* the same sample rate.
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
src1: First signal.
|
|
166
|
+
src2: Second signal.
|
|
167
|
+
|
|
168
|
+
Raises:
|
|
169
|
+
ValueError: If the signals do not have a constant step size
|
|
170
|
+
or the same sample rate.
|
|
171
|
+
"""
|
|
172
|
+
for sig in (src1, src2):
|
|
173
|
+
if not np.allclose(np.diff(sig.x), sig.x[1] - sig.x[0]):
|
|
174
|
+
raise ValueError(f"Signal {sig.title} must have a constant step size (dx).")
|
|
175
|
+
dx1 = src1.x[1] - src1.x[0]
|
|
176
|
+
dx2 = src2.x[1] - src2.x[0]
|
|
177
|
+
if not np.isclose(dx1, dx2):
|
|
178
|
+
raise ValueError(f"Signals must have the same sample rate (dx): {dx1} != {dx2}")
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
@computation_function()
|
|
182
|
+
def deconvolution(src1: SignalObj, src2: SignalObj) -> SignalObj:
|
|
183
|
+
"""Compute deconvolution.
|
|
184
|
+
|
|
185
|
+
The function computes the deconvolution of a signal using
|
|
186
|
+
:py:func:`sigima_.algorithms.signal.fourier.deconvolve`.
|
|
187
|
+
|
|
188
|
+
Args:
|
|
189
|
+
src1: Source signal.
|
|
190
|
+
src2: Filter signal.
|
|
191
|
+
|
|
192
|
+
Returns:
|
|
193
|
+
Result signal.
|
|
194
|
+
|
|
195
|
+
Notes:
|
|
196
|
+
The kernel normalization behavior can be configured globally using
|
|
197
|
+
``sigima.config.options.auto_normalize_kernel``.
|
|
198
|
+
"""
|
|
199
|
+
check_same_sample_rate(src1, src2)
|
|
200
|
+
dst = dst_2_to_1(src1, src2, "⊛⁻¹", f"filter={src2.title}")
|
|
201
|
+
x1, y1 = src1.get_data()
|
|
202
|
+
_x2, y2 = src2.get_data()
|
|
203
|
+
|
|
204
|
+
# Get kernel normalization option from configuration
|
|
205
|
+
normalize_kernel = sigima_options.auto_normalize_kernel.get()
|
|
206
|
+
|
|
207
|
+
result_y = fourier.deconvolve(
|
|
208
|
+
x1,
|
|
209
|
+
y1,
|
|
210
|
+
y2,
|
|
211
|
+
normalize_kernel_flag=normalize_kernel,
|
|
212
|
+
reg=2.0,
|
|
213
|
+
gain_max=None,
|
|
214
|
+
auto_scale=True,
|
|
215
|
+
)
|
|
216
|
+
dst.set_xydata(x1, result_y, None, None)
|
|
217
|
+
restore_data_outside_roi(dst, src1)
|
|
218
|
+
return dst
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
@computation_function()
|
|
222
|
+
def normalize(src: SignalObj, p: NormalizeParam) -> SignalObj:
|
|
223
|
+
"""Normalize data with :py:func:`sigima.tools.signal.level.normalize`
|
|
224
|
+
|
|
225
|
+
Args:
|
|
226
|
+
src: source signal
|
|
227
|
+
p: parameters
|
|
228
|
+
|
|
229
|
+
Returns:
|
|
230
|
+
Result signal object
|
|
231
|
+
"""
|
|
232
|
+
method: NormalizationMethod = p.method
|
|
233
|
+
dst = dst_1_to_1(src, "normalize", f"ref={method.value}")
|
|
234
|
+
x, y = src.get_data()
|
|
235
|
+
normalized_y = scaling.normalize(y, method)
|
|
236
|
+
dst.set_xydata(x, normalized_y)
|
|
237
|
+
|
|
238
|
+
# Uncertainty propagation for normalization
|
|
239
|
+
# σ(y/norm_factor) = σ(y) / norm_factor
|
|
240
|
+
if is_uncertainty_data_available(src):
|
|
241
|
+
with warnings.catch_warnings():
|
|
242
|
+
warnings.simplefilter("ignore", category=RuntimeWarning)
|
|
243
|
+
# Calculate normalization factor
|
|
244
|
+
if method == NormalizationMethod.MAXIMUM:
|
|
245
|
+
norm_factor = np.nanmax(y)
|
|
246
|
+
elif method == NormalizationMethod.AMPLITUDE:
|
|
247
|
+
norm_factor = np.nanmax(y) - np.nanmin(y)
|
|
248
|
+
elif method == NormalizationMethod.AREA:
|
|
249
|
+
norm_factor = np.nansum(y)
|
|
250
|
+
elif method == NormalizationMethod.ENERGY:
|
|
251
|
+
norm_factor = np.sqrt(np.nansum(np.abs(y) ** 2))
|
|
252
|
+
elif method == NormalizationMethod.RMS:
|
|
253
|
+
norm_factor = np.sqrt(np.nanmean(np.abs(y) ** 2))
|
|
254
|
+
else:
|
|
255
|
+
raise RuntimeError(f"Unsupported normalization method: {method}")
|
|
256
|
+
|
|
257
|
+
if norm_factor != 0:
|
|
258
|
+
dst.dy = src.dy / np.abs(norm_factor)
|
|
259
|
+
else:
|
|
260
|
+
dst.dy[:] = np.nan
|
|
261
|
+
|
|
262
|
+
restore_data_outside_roi(dst, src)
|
|
263
|
+
return dst
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
@computation_function()
|
|
267
|
+
def derivative(src: SignalObj) -> SignalObj:
|
|
268
|
+
"""Compute derivative with :py:func:`numpy.gradient`
|
|
269
|
+
|
|
270
|
+
Args:
|
|
271
|
+
src: source signal
|
|
272
|
+
|
|
273
|
+
Returns:
|
|
274
|
+
Result signal object
|
|
275
|
+
"""
|
|
276
|
+
dst = dst_1_to_1(src, "derivative")
|
|
277
|
+
x, y = src.get_data()
|
|
278
|
+
dst.set_xydata(x, np.gradient(y, x))
|
|
279
|
+
|
|
280
|
+
# Uncertainty propagation for numerical derivative
|
|
281
|
+
# For gradient using finite differences: σ(dy/dx) ≈ σ(y) / Δx
|
|
282
|
+
if is_uncertainty_data_available(src):
|
|
283
|
+
with warnings.catch_warnings():
|
|
284
|
+
warnings.simplefilter("ignore", category=RuntimeWarning)
|
|
285
|
+
# Use the same gradient approach as numpy.gradient for uncertainty
|
|
286
|
+
dst.dy = np.gradient(src.dy, x)
|
|
287
|
+
dst.dy[np.isinf(dst.dy) | np.isnan(dst.dy)] = np.nan
|
|
288
|
+
|
|
289
|
+
restore_data_outside_roi(dst, src)
|
|
290
|
+
return dst
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
@computation_function()
|
|
294
|
+
def integral(src: SignalObj) -> SignalObj:
|
|
295
|
+
"""Compute integral with :py:func:`scipy.integrate.cumulative_trapezoid`
|
|
296
|
+
|
|
297
|
+
Args:
|
|
298
|
+
src: source signal
|
|
299
|
+
|
|
300
|
+
Returns:
|
|
301
|
+
Result signal object
|
|
302
|
+
"""
|
|
303
|
+
dst = dst_1_to_1(src, "integral")
|
|
304
|
+
x, y = src.get_data()
|
|
305
|
+
dst.set_xydata(x, spt.cumulative_trapezoid(y, x, initial=0.0))
|
|
306
|
+
|
|
307
|
+
# Uncertainty propagation for numerical integration
|
|
308
|
+
# For cumulative trapezoidal integration, uncertainties accumulate
|
|
309
|
+
if is_uncertainty_data_available(src):
|
|
310
|
+
# Propagate uncertainties through cumulative trapezoidal rule
|
|
311
|
+
# σ(∫y dx) ≈ √(Σ(σ(y_i) * Δx_i)²) for independent measurements
|
|
312
|
+
dx = np.diff(x)
|
|
313
|
+
dy_squared = src.dy[:-1] ** 2 + src.dy[1:] ** 2 # Trapezoidal rule uncertainty
|
|
314
|
+
# Propagated variance for trapezoidal integration
|
|
315
|
+
dst.dy = np.zeros_like(dst.y) # Initialize uncertainty array
|
|
316
|
+
dst.dy[0] = 0.0 # Initial value has no uncertainty
|
|
317
|
+
dst.dy[1:] = np.sqrt(np.cumsum(dy_squared * (dx**2) / 4))
|
|
318
|
+
|
|
319
|
+
restore_data_outside_roi(dst, src)
|
|
320
|
+
return dst
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
class XYCalibrateParam(gds.DataSet, title=_("Calibration")):
|
|
324
|
+
"""Signal calibration parameters"""
|
|
325
|
+
|
|
326
|
+
axes = (("x", _("X-axis")), ("y", _("Y-axis")))
|
|
327
|
+
axis = gds.ChoiceItem(_("Calibrate"), axes, default="y")
|
|
328
|
+
a = gds.FloatItem("a", default=1.0)
|
|
329
|
+
b = gds.FloatItem("b", default=0.0)
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
@computation_function()
|
|
333
|
+
def calibration(src: SignalObj, p: XYCalibrateParam) -> SignalObj:
|
|
334
|
+
"""Compute linear calibration
|
|
335
|
+
|
|
336
|
+
Args:
|
|
337
|
+
src: source signal
|
|
338
|
+
p: parameters
|
|
339
|
+
|
|
340
|
+
Returns:
|
|
341
|
+
Result signal object
|
|
342
|
+
"""
|
|
343
|
+
dst = dst_1_to_1(src, "calibration", f"{p.axis}={p.a}*{p.axis}+{p.b}")
|
|
344
|
+
x, y = src.get_data()
|
|
345
|
+
if p.axis == "x":
|
|
346
|
+
dst.set_xydata(p.a * x + p.b, y, src.dx, src.dy)
|
|
347
|
+
# For X-axis calibration: uncertainties in x are scaled, y unchanged
|
|
348
|
+
if is_uncertainty_data_available(src):
|
|
349
|
+
dst.dx = np.abs(p.a) * src.dx if src.dx is not None else None
|
|
350
|
+
# Y uncertainties remain the same
|
|
351
|
+
else:
|
|
352
|
+
dst.set_xydata(x, p.a * y + p.b, src.dx, src.dy)
|
|
353
|
+
# For Y-axis calibration: σ(a*y + b) = |a| * σ(y)
|
|
354
|
+
if is_uncertainty_data_available(src):
|
|
355
|
+
if dst.dy is not None:
|
|
356
|
+
dst.dy *= np.abs(p.a)
|
|
357
|
+
restore_data_outside_roi(dst, src)
|
|
358
|
+
return dst
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
@computation_function()
|
|
362
|
+
def clip(src: SignalObj, p: ClipParam) -> SignalObj:
|
|
363
|
+
"""Compute maximum data clipping with :py:func:`numpy.clip`
|
|
364
|
+
|
|
365
|
+
Args:
|
|
366
|
+
src: source signal
|
|
367
|
+
p: parameters
|
|
368
|
+
|
|
369
|
+
Returns:
|
|
370
|
+
Result signal object
|
|
371
|
+
"""
|
|
372
|
+
dst = dst_1_to_1(src, "clip", f"[{p.lower}, {p.upper}]")
|
|
373
|
+
x, y = src.get_data()
|
|
374
|
+
|
|
375
|
+
# Compute result
|
|
376
|
+
result_y = np.clip(y, p.lower, p.upper)
|
|
377
|
+
dst.set_xydata(x, result_y, src.dx, src.dy)
|
|
378
|
+
|
|
379
|
+
# Uncertainty propagation: σ(clip(y)) = σ(y) where not clipped, 0 where clipped
|
|
380
|
+
if is_uncertainty_data_available(src):
|
|
381
|
+
dst.dy = src.dy.copy()
|
|
382
|
+
if p.lower is not None:
|
|
383
|
+
dst.dy[y <= p.lower] = 0
|
|
384
|
+
if p.upper is not None:
|
|
385
|
+
dst.dy[y >= p.upper] = 0
|
|
386
|
+
|
|
387
|
+
restore_data_outside_roi(dst, src)
|
|
388
|
+
return dst
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
@computation_function()
|
|
392
|
+
def offset_correction(src: SignalObj, p: ROI1DParam) -> SignalObj:
|
|
393
|
+
"""Correct offset: subtract the mean value of the signal in the specified range
|
|
394
|
+
(baseline correction)
|
|
395
|
+
|
|
396
|
+
Args:
|
|
397
|
+
src: source signal
|
|
398
|
+
p: parameters
|
|
399
|
+
|
|
400
|
+
Returns:
|
|
401
|
+
Result signal object
|
|
402
|
+
"""
|
|
403
|
+
dst = dst_1_to_1(src, "offset_correction", f"{p.xmin:.3g}≤x≤{p.xmax:.3g}")
|
|
404
|
+
_roi_x, roi_y = p.get_data(src)
|
|
405
|
+
dst.y -= np.mean(roi_y)
|
|
406
|
+
restore_data_outside_roi(dst, src)
|
|
407
|
+
return dst
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
class DetrendingParam(gds.DataSet, title=_("Detrending")):
|
|
411
|
+
"""Detrending parameters"""
|
|
412
|
+
|
|
413
|
+
methods = (("linear", _("Linear")), ("constant", _("Constant")))
|
|
414
|
+
method = gds.ChoiceItem(_("Detrending method"), methods, default="linear")
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
@computation_function()
|
|
418
|
+
def detrending(src: SignalObj, p: DetrendingParam) -> SignalObj:
|
|
419
|
+
"""Detrend data with :py:func:`scipy.signal.detrend`
|
|
420
|
+
|
|
421
|
+
Args:
|
|
422
|
+
src: source signal
|
|
423
|
+
p: parameters
|
|
424
|
+
|
|
425
|
+
Returns:
|
|
426
|
+
Result signal object
|
|
427
|
+
"""
|
|
428
|
+
dst = dst_1_to_1(src, "detrending", f"method={p.method}")
|
|
429
|
+
x, y = src.get_data()
|
|
430
|
+
dst.set_xydata(x, sps.detrend(y, type=p.method))
|
|
431
|
+
restore_data_outside_roi(dst, src)
|
|
432
|
+
return dst
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
class WindowingParam(gds.DataSet, title=_("Windowing")):
|
|
436
|
+
"""Windowing parameters."""
|
|
437
|
+
|
|
438
|
+
_meth_prop = gds.GetAttrProp("method")
|
|
439
|
+
method = gds.ChoiceItem(
|
|
440
|
+
_("Method"), WindowingMethod, default=WindowingMethod.HAMMING
|
|
441
|
+
).set_prop("display", store=_meth_prop)
|
|
442
|
+
alpha = gds.FloatItem(
|
|
443
|
+
"Alpha",
|
|
444
|
+
default=0.5,
|
|
445
|
+
help=_("Shape parameter of the Tukey windowing function"),
|
|
446
|
+
).set_prop(
|
|
447
|
+
"display", active=gds.FuncProp(_meth_prop, lambda x: x == WindowingMethod.TUKEY)
|
|
448
|
+
)
|
|
449
|
+
beta = gds.FloatItem(
|
|
450
|
+
"Beta",
|
|
451
|
+
default=14.0,
|
|
452
|
+
help=_("Shape parameter of the Kaiser windowing function"),
|
|
453
|
+
).set_prop(
|
|
454
|
+
"display",
|
|
455
|
+
active=gds.FuncProp(_meth_prop, lambda x: x == WindowingMethod.KAISER),
|
|
456
|
+
)
|
|
457
|
+
sigma = gds.FloatItem(
|
|
458
|
+
"Sigma",
|
|
459
|
+
default=0.5,
|
|
460
|
+
help=_("Shape parameter of the Gaussian windowing function"),
|
|
461
|
+
).set_prop(
|
|
462
|
+
"display",
|
|
463
|
+
active=gds.FuncProp(_meth_prop, lambda x: x == WindowingMethod.GAUSSIAN),
|
|
464
|
+
)
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
@computation_function()
|
|
468
|
+
def apply_window(src: SignalObj, p: WindowingParam) -> SignalObj:
|
|
469
|
+
"""Compute windowing with :py:func:`sigima.tools.signal.windowing.apply_window`.
|
|
470
|
+
|
|
471
|
+
Args:
|
|
472
|
+
src: Source signal.
|
|
473
|
+
p: Parameters for windowing.
|
|
474
|
+
|
|
475
|
+
Returns:
|
|
476
|
+
Result signal object.
|
|
477
|
+
"""
|
|
478
|
+
method: WindowingMethod = p.method
|
|
479
|
+
suffix = f"method={method.value}"
|
|
480
|
+
if method == WindowingMethod.GAUSSIAN:
|
|
481
|
+
suffix += f", sigma={p.sigma:.3f}"
|
|
482
|
+
elif method == WindowingMethod.KAISER:
|
|
483
|
+
suffix += f", beta={p.beta:.3f}"
|
|
484
|
+
elif method == WindowingMethod.TUKEY:
|
|
485
|
+
suffix += f", alpha={p.alpha:.3f}"
|
|
486
|
+
dst = dst_1_to_1(src, "apply_window", suffix)
|
|
487
|
+
assert p.alpha is not None
|
|
488
|
+
dst.y = windowing.apply_window(dst.y, method, p.alpha)
|
|
489
|
+
restore_data_outside_roi(dst, src)
|
|
490
|
+
return dst
|
|
491
|
+
|
|
492
|
+
|
|
493
|
+
@computation_function()
|
|
494
|
+
def reverse_x(src: SignalObj) -> SignalObj:
|
|
495
|
+
"""Reverse x-axis
|
|
496
|
+
|
|
497
|
+
Args:
|
|
498
|
+
src: source signal
|
|
499
|
+
|
|
500
|
+
Returns:
|
|
501
|
+
Result signal object
|
|
502
|
+
"""
|
|
503
|
+
dst = dst_1_to_1(src, "reverse_x")
|
|
504
|
+
dst.y = dst.y[::-1]
|
|
505
|
+
return dst
|
|
506
|
+
|
|
507
|
+
|
|
508
|
+
@computation_function()
|
|
509
|
+
def convolution(src1: SignalObj, src2: SignalObj) -> SignalObj:
|
|
510
|
+
"""Compute convolution of two signals with :py:func:`scipy.signal.convolve`.
|
|
511
|
+
|
|
512
|
+
Args:
|
|
513
|
+
src1: Source signal 1.
|
|
514
|
+
src2: Source signal 2.
|
|
515
|
+
|
|
516
|
+
Returns:
|
|
517
|
+
Result signal.
|
|
518
|
+
|
|
519
|
+
Notes:
|
|
520
|
+
The behavior of kernel normalization is controlled by the global configuration
|
|
521
|
+
option ``sigima.config.options.auto_normalize_kernel``.
|
|
522
|
+
"""
|
|
523
|
+
check_same_sample_rate(src1, src2)
|
|
524
|
+
dst = dst_2_to_1(src1, src2, "⊛")
|
|
525
|
+
x1, y1 = src1.get_data()
|
|
526
|
+
_x2, y2 = src2.get_data()
|
|
527
|
+
|
|
528
|
+
# Get configuration option for kernel normalization
|
|
529
|
+
normalize_kernel = sigima_options.auto_normalize_kernel.get()
|
|
530
|
+
|
|
531
|
+
ynew = fourier.convolve(
|
|
532
|
+
x1,
|
|
533
|
+
y1,
|
|
534
|
+
y2,
|
|
535
|
+
normalize_kernel_flag=normalize_kernel,
|
|
536
|
+
)
|
|
537
|
+
dst.set_xydata(x1, ynew, None, None)
|
|
538
|
+
restore_data_outside_roi(dst, src1)
|
|
539
|
+
return dst
|
|
540
|
+
|
|
541
|
+
|
|
542
|
+
def get_nyquist_frequency(obj: SignalObj) -> float:
|
|
543
|
+
"""Return the Nyquist frequency of a signal object
|
|
544
|
+
|
|
545
|
+
Args:
|
|
546
|
+
obj: signal object
|
|
547
|
+
|
|
548
|
+
Returns:
|
|
549
|
+
Nyquist frequency
|
|
550
|
+
"""
|
|
551
|
+
fs = float(obj.x.size - 1) / (obj.x[-1] - obj.x[0])
|
|
552
|
+
return fs / 2.0
|
|
553
|
+
|
|
554
|
+
|
|
555
|
+
@computation_function()
|
|
556
|
+
def xy_mode(src1: SignalObj, src2: SignalObj) -> SignalObj:
|
|
557
|
+
"""Simulate the X-Y mode of an oscilloscope.
|
|
558
|
+
|
|
559
|
+
Use the first signal as the X-axis and the second signal as the Y-axis.
|
|
560
|
+
|
|
561
|
+
Args:
|
|
562
|
+
src1: First input signal (X-axis).
|
|
563
|
+
src2: Second input signal (Y-axis).
|
|
564
|
+
|
|
565
|
+
Returns:
|
|
566
|
+
A signal object representing the X-Y mode.
|
|
567
|
+
"""
|
|
568
|
+
dst = dst_2_to_1(src1, src2, "", "X-Y Mode")
|
|
569
|
+
p = Resampling1DParam()
|
|
570
|
+
p.xmin = max(src1.x[0], src2.x[0])
|
|
571
|
+
p.xmax = min(src1.x[-1], src2.x[-1])
|
|
572
|
+
assert p.xmin < p.xmax, "X-Y mode: No overlap between signals."
|
|
573
|
+
p.mode = "nbpts"
|
|
574
|
+
p.nbpts = min(src1.x.size, src2.x.size)
|
|
575
|
+
_, y1 = resampling(src1, p).get_data()
|
|
576
|
+
_, y2 = resampling(src2, p).get_data()
|
|
577
|
+
dst.set_xydata(y1, y2)
|
|
578
|
+
dst.title = "{1} = f({0})"
|
|
579
|
+
restore_data_outside_roi(dst, src1)
|
|
580
|
+
return dst
|