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,276 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the terms of the BSD 3-Clause
|
|
4
|
+
# (see sigima/LICENSE for details)
|
|
5
|
+
|
|
6
|
+
"""
|
|
7
|
+
Curve fitting operations
|
|
8
|
+
========================
|
|
9
|
+
|
|
10
|
+
This module provides curve fitting operations for signal objects:
|
|
11
|
+
|
|
12
|
+
- Linear and polynomial fits
|
|
13
|
+
- Gaussian, Lorentzian, and Voigt fits
|
|
14
|
+
- Exponential and CDF fits
|
|
15
|
+
|
|
16
|
+
.. note::
|
|
17
|
+
|
|
18
|
+
Most operations use functions from :mod:`sigima.tools.signal.fitting` for
|
|
19
|
+
actual computations.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
from typing import Callable
|
|
25
|
+
|
|
26
|
+
import guidata.dataset as gds
|
|
27
|
+
import numpy as np
|
|
28
|
+
|
|
29
|
+
from sigima.config import _
|
|
30
|
+
from sigima.objects import SignalObj
|
|
31
|
+
from sigima.proc.base import dst_2_to_1
|
|
32
|
+
from sigima.proc.decorator import computation_function
|
|
33
|
+
from sigima.tools.signal import fitting
|
|
34
|
+
|
|
35
|
+
from .base import dst_1_to_1
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def __generic_fit(
|
|
39
|
+
src: SignalObj,
|
|
40
|
+
fitfunc: Callable[[np.ndarray, np.ndarray], tuple[np.ndarray, dict[str, float]]],
|
|
41
|
+
) -> SignalObj:
|
|
42
|
+
"""Generic fitting function.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
src: source signal
|
|
46
|
+
fitfunc: fitting function
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
Fitting result signal object
|
|
50
|
+
"""
|
|
51
|
+
dst = dst_1_to_1(src, fitfunc.__name__)
|
|
52
|
+
|
|
53
|
+
# Fit only on ROI if available
|
|
54
|
+
x_roi = src.x[~src.get_masked_view().mask]
|
|
55
|
+
y_roi = src.get_masked_view().compressed()
|
|
56
|
+
_fitted_y_roi, fit_params = fitfunc(x_roi, y_roi)
|
|
57
|
+
|
|
58
|
+
# Evaluate fit on full x range
|
|
59
|
+
fitted_y = fitting.evaluate_fit(src.x, **fit_params)
|
|
60
|
+
dst.set_xydata(src.x, fitted_y)
|
|
61
|
+
|
|
62
|
+
# Store fit parameters in metadata
|
|
63
|
+
dst.metadata["fit_params"] = fit_params
|
|
64
|
+
return dst
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@computation_function()
|
|
68
|
+
def linear_fit(src: SignalObj) -> SignalObj:
|
|
69
|
+
"""Compute linear fit with :py:func:`numpy.polyfit`
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
src: source signal
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
Result signal object
|
|
76
|
+
"""
|
|
77
|
+
return __generic_fit(src, fitting.linear_fit)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class PolynomialFitParam(gds.DataSet, title=_("Polynomial fit")):
|
|
81
|
+
"""Polynomial fitting parameters"""
|
|
82
|
+
|
|
83
|
+
degree = gds.IntItem(_("Degree"), 3, min=1, max=10, slider=True)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@computation_function()
|
|
87
|
+
def polynomial_fit(src: SignalObj, p: PolynomialFitParam) -> SignalObj:
|
|
88
|
+
"""Compute polynomial fit with :py:func:`numpy.polyfit`
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
src: source signal
|
|
92
|
+
p: polynomial fitting parameters
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
Result signal object
|
|
96
|
+
"""
|
|
97
|
+
if p.degree < 1:
|
|
98
|
+
raise ValueError("The polynomial degree must be at least 1.")
|
|
99
|
+
return __generic_fit(src, lambda x, y: fitting.polynomial_fit(x, y, p.degree))
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@computation_function()
|
|
103
|
+
def gaussian_fit(src: SignalObj) -> SignalObj:
|
|
104
|
+
"""Compute Gaussian fit with :py:func:`scipy.optimize.curve_fit`
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
src: source signal
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
Result signal object
|
|
111
|
+
"""
|
|
112
|
+
return __generic_fit(src, fitting.gaussian_fit)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
@computation_function()
|
|
116
|
+
def lorentzian_fit(src: SignalObj) -> SignalObj:
|
|
117
|
+
"""Compute Lorentzian fit with :py:func:`scipy.optimize.curve_fit`
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
src: source signal
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
Result signal object
|
|
124
|
+
"""
|
|
125
|
+
return __generic_fit(src, fitting.lorentzian_fit)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
@computation_function()
|
|
129
|
+
def voigt_fit(src: SignalObj) -> SignalObj:
|
|
130
|
+
"""Compute Voigt fit with :py:func:`scipy.optimize.curve_fit`
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
src: source signal
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
Result signal object
|
|
137
|
+
"""
|
|
138
|
+
return __generic_fit(src, fitting.voigt_fit)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
@computation_function()
|
|
142
|
+
def exponential_fit(src: SignalObj) -> SignalObj:
|
|
143
|
+
"""Compute exponential fit with :py:func:`scipy.optimize.curve_fit`
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
src: source signal
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
Result signal object
|
|
150
|
+
"""
|
|
151
|
+
return __generic_fit(src, fitting.exponential_fit)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
@computation_function()
|
|
155
|
+
def cdf_fit(src: SignalObj) -> SignalObj:
|
|
156
|
+
"""Compute CDF fit with :py:func:`scipy.optimize.curve_fit`
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
src: source signal
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
Result signal object
|
|
163
|
+
"""
|
|
164
|
+
return __generic_fit(src, fitting.cdf_fit)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
@computation_function()
|
|
168
|
+
def planckian_fit(src: SignalObj) -> SignalObj:
|
|
169
|
+
"""Compute Planckian fit with :py:func:`scipy.optimize.curve_fit`
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
src: source signal
|
|
173
|
+
|
|
174
|
+
Returns:
|
|
175
|
+
Result signal object
|
|
176
|
+
"""
|
|
177
|
+
return __generic_fit(src, fitting.planckian_fit)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
@computation_function()
|
|
181
|
+
def twohalfgaussian_fit(src: SignalObj) -> SignalObj:
|
|
182
|
+
"""Compute two-half-Gaussian fit with :py:func:`scipy.optimize.curve_fit`
|
|
183
|
+
|
|
184
|
+
Args:
|
|
185
|
+
src: source signal
|
|
186
|
+
|
|
187
|
+
Returns:
|
|
188
|
+
Result signal object
|
|
189
|
+
"""
|
|
190
|
+
return __generic_fit(src, fitting.twohalfgaussian_fit)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
@computation_function()
|
|
194
|
+
def sigmoid_fit(src: SignalObj) -> SignalObj:
|
|
195
|
+
"""Compute sigmoid fit with :py:func:`scipy.optimize.curve_fit`
|
|
196
|
+
|
|
197
|
+
Args:
|
|
198
|
+
src: source signal
|
|
199
|
+
|
|
200
|
+
Returns:
|
|
201
|
+
Result signal object
|
|
202
|
+
"""
|
|
203
|
+
return __generic_fit(src, fitting.sigmoid_fit)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
@computation_function()
|
|
207
|
+
def piecewiseexponential_fit(src: SignalObj) -> SignalObj:
|
|
208
|
+
"""Compute piecewise exponential fit (raise-decay) with
|
|
209
|
+
:py:func:`scipy.optimize.curve_fit`
|
|
210
|
+
|
|
211
|
+
Args:
|
|
212
|
+
src: source signal
|
|
213
|
+
|
|
214
|
+
Returns:
|
|
215
|
+
Result signal object
|
|
216
|
+
"""
|
|
217
|
+
return __generic_fit(src, fitting.piecewiseexponential_fit)
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
@computation_function()
|
|
221
|
+
def sinusoidal_fit(src: SignalObj) -> SignalObj:
|
|
222
|
+
"""Compute sinusoidal fit with :py:func:`scipy.optimize.curve_fit`
|
|
223
|
+
|
|
224
|
+
Args:
|
|
225
|
+
src: source signal
|
|
226
|
+
|
|
227
|
+
Returns:
|
|
228
|
+
Result signal object
|
|
229
|
+
"""
|
|
230
|
+
return __generic_fit(src, fitting.sinusoidal_fit)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def extract_fit_params(signal: SignalObj) -> dict[str, float | str]:
|
|
234
|
+
"""Extract fit parameters from a fitted signal.
|
|
235
|
+
|
|
236
|
+
Args:
|
|
237
|
+
signal: Signal object containing fit metadata
|
|
238
|
+
|
|
239
|
+
Returns:
|
|
240
|
+
Fit parameters
|
|
241
|
+
"""
|
|
242
|
+
if "fit_params" not in signal.metadata:
|
|
243
|
+
raise ValueError("Signal does not contain fit parameters")
|
|
244
|
+
fit_params_dict: dict[str, float | str] = signal.metadata["fit_params"]
|
|
245
|
+
assert "fit_type" in fit_params_dict, "No valid fit parameters found"
|
|
246
|
+
return fit_params_dict
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
@computation_function()
|
|
250
|
+
def evaluate_fit(src1: SignalObj, src2: SignalObj) -> SignalObj:
|
|
251
|
+
"""Evaluate fit function from src1 on the x-axis of src2.
|
|
252
|
+
|
|
253
|
+
This function extracts fit parameters from `src1` (which must contain fit metadata
|
|
254
|
+
from a previous fitting operation) and evaluates the fit function on the x-axis
|
|
255
|
+
of `src2`.
|
|
256
|
+
|
|
257
|
+
Args:
|
|
258
|
+
src1: Signal object containing fit parameters in metadata (from a fit operation)
|
|
259
|
+
src2: Signal object whose x-axis will be used for evaluation
|
|
260
|
+
|
|
261
|
+
Returns:
|
|
262
|
+
New signal with the fit evaluated on src2's x-axis
|
|
263
|
+
"""
|
|
264
|
+
fit_params = extract_fit_params(src1)
|
|
265
|
+
dst = dst_2_to_1(src1, src2, "evaluate_fit")
|
|
266
|
+
|
|
267
|
+
# Evaluate fit on src2's x-axis
|
|
268
|
+
x = src2.x
|
|
269
|
+
y = fitting.evaluate_fit(x, **fit_params)
|
|
270
|
+
|
|
271
|
+
dst.set_xydata(x, y)
|
|
272
|
+
dst.title = f"Fitted {fit_params['fit_type']}"
|
|
273
|
+
|
|
274
|
+
# Copy fit parameters to destination metadata
|
|
275
|
+
dst.metadata["fit_params"] = fit_params
|
|
276
|
+
return dst
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the terms of the BSD 3-Clause
|
|
4
|
+
# (see sigima/LICENSE for details)
|
|
5
|
+
|
|
6
|
+
"""
|
|
7
|
+
Fourier transform and frequency domain operations
|
|
8
|
+
=================================================
|
|
9
|
+
|
|
10
|
+
This module provides Fourier transform and frequency domain operations:
|
|
11
|
+
|
|
12
|
+
- FFT and inverse FFT
|
|
13
|
+
- Magnitude and phase spectrum
|
|
14
|
+
- Power spectral density (PSD)
|
|
15
|
+
|
|
16
|
+
.. note::
|
|
17
|
+
|
|
18
|
+
Most operations use functions from :mod:`sigima.tools.signal.fourier` for actual
|
|
19
|
+
computations.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
from math import ceil, log2
|
|
25
|
+
|
|
26
|
+
import guidata.dataset as gds
|
|
27
|
+
|
|
28
|
+
from sigima.config import _
|
|
29
|
+
from sigima.enums import PadLocation1D
|
|
30
|
+
from sigima.objects import SignalObj
|
|
31
|
+
from sigima.proc.base import FFTParam, SpectrumParam
|
|
32
|
+
from sigima.proc.decorator import computation_function
|
|
33
|
+
from sigima.proc.signal.base import dst_1_to_1
|
|
34
|
+
from sigima.tools.signal import fourier
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class ZeroPadding1DParam(gds.DataSet, title=_("Zero padding")):
|
|
38
|
+
"""ZeroPadding1DParam manages the parameters for applying zero-padding to signals.
|
|
39
|
+
|
|
40
|
+
Attributes:
|
|
41
|
+
strategies: Available strategies ("next_pow2", "double", "triple", "custom").
|
|
42
|
+
strategy: Choice item for selecting the zero-padding strategy.
|
|
43
|
+
locations: Available locations for padding ("append", "prepend", "both").
|
|
44
|
+
location: Choice item for selecting where to add the padding.
|
|
45
|
+
n: Number of points to add as padding (active only for "custom" strategy).
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
def __init__(self, *args, **kwargs) -> None:
|
|
49
|
+
"""Initialize zero padding parameters.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
*args: Variable length argument list passed to the superclass.
|
|
53
|
+
**kwargs: Arbitrary keyword arguments passed to the superclass.
|
|
54
|
+
"""
|
|
55
|
+
super().__init__(*args, **kwargs)
|
|
56
|
+
self.__obj: SignalObj | None = None
|
|
57
|
+
|
|
58
|
+
def update_from_obj(self, obj: SignalObj) -> None:
|
|
59
|
+
"""Update parameters from signal.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
obj: Signal object from which to update the dataset.
|
|
63
|
+
"""
|
|
64
|
+
self.__obj = obj
|
|
65
|
+
self.strategy_callback(None, self.strategy)
|
|
66
|
+
|
|
67
|
+
@staticmethod
|
|
68
|
+
def next_power_of_two(size: int) -> int:
|
|
69
|
+
"""Compute the next power of two greater than or equal to the given size.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
size: The input integer.
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
The smallest power of two greater than or equal to 'size'.
|
|
76
|
+
"""
|
|
77
|
+
return 2 ** (ceil(log2(size)))
|
|
78
|
+
|
|
79
|
+
def strategy_callback(self, _, value):
|
|
80
|
+
"""Callback for strategy choice item.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
_: Unused argument (in this context).
|
|
84
|
+
value: The selected strategy value.
|
|
85
|
+
"""
|
|
86
|
+
if self.__obj is None:
|
|
87
|
+
return
|
|
88
|
+
assert self.__obj.x is not None
|
|
89
|
+
size = self.__obj.x.size
|
|
90
|
+
if value == "next_pow2":
|
|
91
|
+
self.n = self.next_power_of_two(size) - size
|
|
92
|
+
elif value == "double":
|
|
93
|
+
self.n = size
|
|
94
|
+
elif value == "triple":
|
|
95
|
+
self.n = 2 * size
|
|
96
|
+
|
|
97
|
+
strategies = ("next_pow2", "double", "triple", "custom")
|
|
98
|
+
_prop = gds.GetAttrProp("strategy")
|
|
99
|
+
strategy = gds.ChoiceItem(
|
|
100
|
+
_("Strategy"), zip(strategies, strategies), default=strategies[0]
|
|
101
|
+
).set_prop("display", store=_prop, callback=strategy_callback)
|
|
102
|
+
location = gds.ChoiceItem(
|
|
103
|
+
_("Location"),
|
|
104
|
+
PadLocation1D,
|
|
105
|
+
default=PadLocation1D.APPEND,
|
|
106
|
+
help=_("Where to add the padding"),
|
|
107
|
+
)
|
|
108
|
+
_func_prop = gds.FuncProp(_prop, lambda x: x == "custom")
|
|
109
|
+
n = gds.IntItem(
|
|
110
|
+
_("Number of points"), min=1, default=1, help=_("Number of points to add")
|
|
111
|
+
).set_prop("display", active=_func_prop)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
@computation_function()
|
|
115
|
+
def zero_padding(src: SignalObj, p: ZeroPadding1DParam) -> SignalObj:
|
|
116
|
+
"""Compute zero padding with :py:func:`sigima.tools.signal.fourier.zero_padding`.
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
src: Source signal.
|
|
120
|
+
p: Parameters.
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
Result signal object.
|
|
124
|
+
"""
|
|
125
|
+
if p.strategy == "custom":
|
|
126
|
+
suffix = f"n={p.n}"
|
|
127
|
+
else:
|
|
128
|
+
suffix = f"strategy={p.strategy}"
|
|
129
|
+
|
|
130
|
+
assert p.n is not None
|
|
131
|
+
if p.location == PadLocation1D.APPEND:
|
|
132
|
+
n_prepend = 0
|
|
133
|
+
n_append = p.n
|
|
134
|
+
elif p.location == PadLocation1D.PREPEND:
|
|
135
|
+
n_prepend = p.n
|
|
136
|
+
n_append = 0
|
|
137
|
+
elif p.location == PadLocation1D.BOTH:
|
|
138
|
+
n_prepend = p.n // 2
|
|
139
|
+
n_append = p.n - n_prepend
|
|
140
|
+
else:
|
|
141
|
+
raise ValueError(f"Invalid padding location: {p.location}")
|
|
142
|
+
|
|
143
|
+
dst = dst_1_to_1(src, "zero_padding", suffix)
|
|
144
|
+
x, y = src.get_data()
|
|
145
|
+
x_padded, y_padded = fourier.zero_padding(x, y, n_prepend, n_append)
|
|
146
|
+
dst.set_xydata(x_padded, y_padded)
|
|
147
|
+
|
|
148
|
+
return dst
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
@computation_function()
|
|
152
|
+
def fft(src: SignalObj, p: FFTParam | None = None) -> SignalObj:
|
|
153
|
+
"""Compute FFT with :py:func:`sigima.tools.signal.fourier.fft1d`.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
src: Source signal.
|
|
157
|
+
p: Parameters.
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
Result signal object.
|
|
161
|
+
"""
|
|
162
|
+
dst = dst_1_to_1(src, "fft")
|
|
163
|
+
x, y = src.get_data()
|
|
164
|
+
fft_x, fft_y = fourier.fft1d(x, y, shift=bool(True if p is None else p.shift))
|
|
165
|
+
dst.set_xydata(fft_x, fft_y)
|
|
166
|
+
dst.save_attr_to_metadata("xunit", "Hz" if dst.xunit == "s" else "")
|
|
167
|
+
dst.save_attr_to_metadata("yunit", "")
|
|
168
|
+
dst.save_attr_to_metadata("xlabel", _("Frequency"))
|
|
169
|
+
return dst
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
@computation_function()
|
|
173
|
+
def ifft(src: SignalObj) -> SignalObj:
|
|
174
|
+
"""Compute the inverse FFT with :py:func:`sigima.tools.signal.fourier.ifft1d`.
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
src: Source signal.
|
|
178
|
+
|
|
179
|
+
Returns:
|
|
180
|
+
Result signal object.
|
|
181
|
+
"""
|
|
182
|
+
dst = dst_1_to_1(src, "ifft")
|
|
183
|
+
f, sp = src.get_data()
|
|
184
|
+
x, y = fourier.ifft1d(f, sp)
|
|
185
|
+
dst.set_xydata(x, y)
|
|
186
|
+
dst.restore_attr_from_metadata("xunit", "s" if src.xunit == "Hz" else "")
|
|
187
|
+
dst.restore_attr_from_metadata("yunit", "")
|
|
188
|
+
dst.restore_attr_from_metadata("xlabel", "")
|
|
189
|
+
return dst
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
@computation_function()
|
|
193
|
+
def magnitude_spectrum(src: SignalObj, p: SpectrumParam | None = None) -> SignalObj:
|
|
194
|
+
"""Compute magnitude spectrum.
|
|
195
|
+
|
|
196
|
+
This function computes the magnitude spectrum of a signal using
|
|
197
|
+
:py:func:`sigima.tools.signal.fourier.magnitude_spectrum`.
|
|
198
|
+
|
|
199
|
+
Args:
|
|
200
|
+
src: Source signal.
|
|
201
|
+
p: Parameters.
|
|
202
|
+
|
|
203
|
+
Returns:
|
|
204
|
+
Result signal object.
|
|
205
|
+
"""
|
|
206
|
+
decibel = bool(p is not None and p.decibel)
|
|
207
|
+
dst = dst_1_to_1(src, "magnitude_spectrum", f"dB={decibel}")
|
|
208
|
+
x, y = src.get_data()
|
|
209
|
+
mag_x, mag_y = fourier.magnitude_spectrum(x, y, decibel=decibel)
|
|
210
|
+
dst.set_xydata(mag_x, mag_y)
|
|
211
|
+
dst.xlabel = _("Frequency")
|
|
212
|
+
dst.xunit = "Hz" if dst.xunit == "s" else ""
|
|
213
|
+
dst.yunit = "dB" if decibel else ""
|
|
214
|
+
return dst
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
@computation_function()
|
|
218
|
+
def phase_spectrum(src: SignalObj) -> SignalObj:
|
|
219
|
+
"""Compute phase spectrum.
|
|
220
|
+
|
|
221
|
+
This function computes the phase spectrum of a signal using
|
|
222
|
+
:py:func:`sigima.tools.signal.fourier.phase_spectrum`
|
|
223
|
+
|
|
224
|
+
Args:
|
|
225
|
+
src: Source signal.
|
|
226
|
+
|
|
227
|
+
Returns:
|
|
228
|
+
Result signal object.
|
|
229
|
+
"""
|
|
230
|
+
dst = dst_1_to_1(src, "phase_spectrum")
|
|
231
|
+
x, y = src.get_data()
|
|
232
|
+
phase_x, phase_y = fourier.phase_spectrum(x, y)
|
|
233
|
+
dst.set_xydata(phase_x, phase_y)
|
|
234
|
+
dst.xlabel = _("Frequency")
|
|
235
|
+
dst.xunit = "Hz" if dst.xunit == "s" else ""
|
|
236
|
+
dst.yunit = ""
|
|
237
|
+
return dst
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
@computation_function()
|
|
241
|
+
def psd(src: SignalObj, p: SpectrumParam | None = None) -> SignalObj:
|
|
242
|
+
"""Compute power spectral density with :py:func:`sigima.tools.signal.fourier.psd`.
|
|
243
|
+
|
|
244
|
+
Args:
|
|
245
|
+
src: Source signal.
|
|
246
|
+
p: Parameters.
|
|
247
|
+
|
|
248
|
+
Returns:
|
|
249
|
+
Result signal object.
|
|
250
|
+
"""
|
|
251
|
+
decibel = p is not None and p.decibel
|
|
252
|
+
dst = dst_1_to_1(src, "psd", f"dB={decibel}")
|
|
253
|
+
x, y = src.get_data()
|
|
254
|
+
psd_x, psd_y = fourier.psd(x, y, decibel=decibel)
|
|
255
|
+
dst.set_xydata(psd_x, psd_y)
|
|
256
|
+
dst.xlabel = _("Frequency")
|
|
257
|
+
dst.xunit = "Hz" if dst.xunit == "s" else ""
|
|
258
|
+
dst.yunit = "dB/Hz" if decibel else ""
|
|
259
|
+
return dst
|