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,484 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the terms of the BSD 3-Clause
|
|
4
|
+
# (see sigima/LICENSE for details)
|
|
5
|
+
|
|
6
|
+
"""
|
|
7
|
+
Filtering processing functions for signal objects
|
|
8
|
+
=================================================
|
|
9
|
+
|
|
10
|
+
This module provides filtering operations for signal objects:
|
|
11
|
+
|
|
12
|
+
- Gaussian filter
|
|
13
|
+
- Moving average and median filters
|
|
14
|
+
- Wiener filter
|
|
15
|
+
- Frequency filters (low-pass, high-pass, band-pass, band-stop)
|
|
16
|
+
- Noise addition functions
|
|
17
|
+
|
|
18
|
+
.. note::
|
|
19
|
+
|
|
20
|
+
Uses zero-phase filtering when possible for better phase response.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
import warnings
|
|
26
|
+
from typing import Callable
|
|
27
|
+
|
|
28
|
+
import guidata.dataset as gds
|
|
29
|
+
import numpy as np
|
|
30
|
+
import scipy.ndimage as spi
|
|
31
|
+
import scipy.signal as sps
|
|
32
|
+
|
|
33
|
+
from sigima.config import _
|
|
34
|
+
from sigima.enums import FilterType, FrequencyFilterMethod, PadLocation1D
|
|
35
|
+
from sigima.objects import (
|
|
36
|
+
NormalDistribution1DParam,
|
|
37
|
+
PoissonDistribution1DParam,
|
|
38
|
+
SignalObj,
|
|
39
|
+
UniformDistribution1DParam,
|
|
40
|
+
create_signal_from_param,
|
|
41
|
+
)
|
|
42
|
+
from sigima.objects.base import (
|
|
43
|
+
NormalDistributionParam,
|
|
44
|
+
PoissonDistributionParam,
|
|
45
|
+
UniformDistributionParam,
|
|
46
|
+
)
|
|
47
|
+
from sigima.proc.base import GaussianParam, MovingAverageParam, MovingMedianParam
|
|
48
|
+
from sigima.proc.decorator import computation_function
|
|
49
|
+
from sigima.proc.signal.arithmetic import addition
|
|
50
|
+
from sigima.proc.signal.base import Wrap1to1Func, dst_1_to_1, restore_data_outside_roi
|
|
51
|
+
from sigima.proc.signal.fourier import ZeroPadding1DParam, zero_padding
|
|
52
|
+
from sigima.tools.signal import fourier
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@computation_function()
|
|
56
|
+
def gaussian_filter(src: SignalObj, p: GaussianParam) -> SignalObj:
|
|
57
|
+
"""Compute gaussian filter with :py:func:`scipy.ndimage.gaussian_filter`
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
src: source signal
|
|
61
|
+
p: parameters
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
Result signal object
|
|
65
|
+
"""
|
|
66
|
+
return Wrap1to1Func(spi.gaussian_filter, sigma=p.sigma)(src)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@computation_function()
|
|
70
|
+
def moving_average(src: SignalObj, p: MovingAverageParam) -> SignalObj:
|
|
71
|
+
"""Compute moving average with :py:func:`scipy.ndimage.uniform_filter`
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
src: source signal
|
|
75
|
+
p: parameters
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
Result signal object
|
|
79
|
+
"""
|
|
80
|
+
return Wrap1to1Func(
|
|
81
|
+
spi.uniform_filter, size=p.n, mode=p.mode, func_name="moving_average"
|
|
82
|
+
)(src)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@computation_function()
|
|
86
|
+
def moving_median(src: SignalObj, p: MovingMedianParam) -> SignalObj:
|
|
87
|
+
"""Compute moving median with :py:func:`scipy.ndimage.median_filter`
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
src: source signal
|
|
91
|
+
p: parameters
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
Result signal object
|
|
95
|
+
"""
|
|
96
|
+
return Wrap1to1Func(
|
|
97
|
+
spi.median_filter, size=p.n, mode=p.mode, func_name="moving_median"
|
|
98
|
+
)(src)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@computation_function()
|
|
102
|
+
def wiener(src: SignalObj) -> SignalObj:
|
|
103
|
+
"""Compute Wiener filter with :py:func:`scipy.signal.wiener`
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
src: source signal
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
Result signal object
|
|
110
|
+
"""
|
|
111
|
+
return Wrap1to1Func(sps.wiener)(src)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class BaseHighLowBandParam(gds.DataSet, title=_("Filter")):
|
|
115
|
+
"""Base class for high-pass, low-pass, band-pass and band-stop filters"""
|
|
116
|
+
|
|
117
|
+
TYPE = FilterType.LOWPASS
|
|
118
|
+
_type_prop = gds.GetAttrProp("TYPE")
|
|
119
|
+
|
|
120
|
+
# Must be overwriten by the child class
|
|
121
|
+
_method_prop = gds.GetAttrProp("method")
|
|
122
|
+
method = gds.ChoiceItem(
|
|
123
|
+
_("Filter method"),
|
|
124
|
+
[
|
|
125
|
+
(FrequencyFilterMethod.BUTTERWORTH, "Butterworth"),
|
|
126
|
+
(FrequencyFilterMethod.BESSEL, "Bessel"),
|
|
127
|
+
(FrequencyFilterMethod.CHEBYSHEV1, "Chebyshev I"),
|
|
128
|
+
(FrequencyFilterMethod.CHEBYSHEV2, "Chebyshev II"),
|
|
129
|
+
(FrequencyFilterMethod.ELLIPTIC, "Elliptic"),
|
|
130
|
+
(FrequencyFilterMethod.BRICKWALL, "Brickwall"),
|
|
131
|
+
],
|
|
132
|
+
).set_prop("display", store=_method_prop)
|
|
133
|
+
|
|
134
|
+
def get_filter_func(self) -> Callable:
|
|
135
|
+
"""Get the scipy filter function corresponding to the method."""
|
|
136
|
+
filter_funcs = {
|
|
137
|
+
FrequencyFilterMethod.BESSEL: sps.bessel,
|
|
138
|
+
FrequencyFilterMethod.BUTTERWORTH: sps.butter,
|
|
139
|
+
FrequencyFilterMethod.CHEBYSHEV1: sps.cheby1,
|
|
140
|
+
FrequencyFilterMethod.CHEBYSHEV2: sps.cheby2,
|
|
141
|
+
FrequencyFilterMethod.ELLIPTIC: sps.ellip,
|
|
142
|
+
}
|
|
143
|
+
return filter_funcs.get(self.method)
|
|
144
|
+
|
|
145
|
+
order = gds.IntItem(_("Filter order"), default=3, min=1).set_prop(
|
|
146
|
+
"display",
|
|
147
|
+
active=gds.FuncProp(
|
|
148
|
+
_method_prop, lambda x: x != FrequencyFilterMethod.BRICKWALL
|
|
149
|
+
),
|
|
150
|
+
)
|
|
151
|
+
cut0 = gds.FloatItem(
|
|
152
|
+
_("Low cutoff frequency"), min=0.0, nonzero=True, unit="Hz", allow_none=True
|
|
153
|
+
)
|
|
154
|
+
cut1 = gds.FloatItem(
|
|
155
|
+
_("High cutoff frequency"), min=0.0, nonzero=True, unit="Hz", allow_none=True
|
|
156
|
+
).set_prop(
|
|
157
|
+
"display",
|
|
158
|
+
hide=gds.FuncProp(
|
|
159
|
+
_type_prop, lambda x: x in (FilterType.LOWPASS, FilterType.HIGHPASS)
|
|
160
|
+
),
|
|
161
|
+
)
|
|
162
|
+
rp = gds.FloatItem(
|
|
163
|
+
_("Passband ripple"), min=0.0, default=1.0, nonzero=True, unit="dB"
|
|
164
|
+
).set_prop(
|
|
165
|
+
"display",
|
|
166
|
+
active=gds.FuncProp(
|
|
167
|
+
_method_prop,
|
|
168
|
+
lambda x: x
|
|
169
|
+
in (FrequencyFilterMethod.CHEBYSHEV1, FrequencyFilterMethod.ELLIPTIC),
|
|
170
|
+
),
|
|
171
|
+
)
|
|
172
|
+
rs = gds.FloatItem(
|
|
173
|
+
_("Stopband attenuation"), min=0.0, default=60.0, nonzero=True, unit="dB"
|
|
174
|
+
).set_prop(
|
|
175
|
+
"display",
|
|
176
|
+
active=gds.FuncProp(
|
|
177
|
+
_method_prop,
|
|
178
|
+
lambda x: x
|
|
179
|
+
in (FrequencyFilterMethod.CHEBYSHEV2, FrequencyFilterMethod.ELLIPTIC),
|
|
180
|
+
),
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
_zp_prop = gds.GetAttrProp("zero_padding")
|
|
184
|
+
zero_padding = gds.BoolItem(
|
|
185
|
+
_("Zero padding"),
|
|
186
|
+
default=True,
|
|
187
|
+
).set_prop(
|
|
188
|
+
"display",
|
|
189
|
+
active=gds.FuncProp(
|
|
190
|
+
_method_prop, lambda x: x == FrequencyFilterMethod.BRICKWALL
|
|
191
|
+
),
|
|
192
|
+
store=_zp_prop,
|
|
193
|
+
)
|
|
194
|
+
nfft = gds.IntItem(
|
|
195
|
+
_("Minimum FFT points number"),
|
|
196
|
+
default=0,
|
|
197
|
+
).set_prop(
|
|
198
|
+
"display",
|
|
199
|
+
active=gds.FuncPropMulti(
|
|
200
|
+
[_method_prop, _zp_prop],
|
|
201
|
+
lambda x, y: x == FrequencyFilterMethod.BRICKWALL and y,
|
|
202
|
+
),
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
@staticmethod
|
|
206
|
+
def get_nyquist_frequency(obj: SignalObj) -> float:
|
|
207
|
+
"""Return the Nyquist frequency of a signal object
|
|
208
|
+
|
|
209
|
+
Args:
|
|
210
|
+
obj: signal object
|
|
211
|
+
"""
|
|
212
|
+
fs = float(obj.x.size - 1) / (obj.x[-1] - obj.x[0])
|
|
213
|
+
return fs / 2.0
|
|
214
|
+
|
|
215
|
+
def update_from_obj(self, obj: SignalObj) -> None:
|
|
216
|
+
"""Update the filter parameters from a signal object
|
|
217
|
+
|
|
218
|
+
Args:
|
|
219
|
+
obj: signal object
|
|
220
|
+
"""
|
|
221
|
+
f_nyquist = self.get_nyquist_frequency(obj)
|
|
222
|
+
if self.cut0 is None:
|
|
223
|
+
if self.TYPE == FilterType.LOWPASS:
|
|
224
|
+
self.cut0 = 0.1 * f_nyquist
|
|
225
|
+
elif self.TYPE == FilterType.HIGHPASS:
|
|
226
|
+
self.cut0 = 0.9 * f_nyquist
|
|
227
|
+
elif self.TYPE == FilterType.BANDPASS:
|
|
228
|
+
self.cut0 = 0.1 * f_nyquist
|
|
229
|
+
self.cut1 = 0.9 * f_nyquist
|
|
230
|
+
elif self.TYPE == FilterType.BANDSTOP:
|
|
231
|
+
self.cut0 = 0.4 * f_nyquist
|
|
232
|
+
self.cut1 = 0.6 * f_nyquist
|
|
233
|
+
|
|
234
|
+
def get_filter_params(self, obj: SignalObj) -> tuple[float | str, float | str]:
|
|
235
|
+
"""Return the filter parameters (a and b) as a tuple. These parameters are used
|
|
236
|
+
in the scipy.signal filter functions (eg. `scipy.signal.filtfilt`).
|
|
237
|
+
|
|
238
|
+
Args:
|
|
239
|
+
obj: signal object
|
|
240
|
+
|
|
241
|
+
Returns:
|
|
242
|
+
tuple: filter parameters
|
|
243
|
+
"""
|
|
244
|
+
f_nyquist = self.get_nyquist_frequency(obj)
|
|
245
|
+
args: list[float | str | tuple[float, ...]] = [self.order] # type: ignore
|
|
246
|
+
if self.method == FrequencyFilterMethod.CHEBYSHEV1:
|
|
247
|
+
args += [self.rp]
|
|
248
|
+
elif self.method == FrequencyFilterMethod.CHEBYSHEV2:
|
|
249
|
+
args += [self.rs]
|
|
250
|
+
elif self.method == FrequencyFilterMethod.ELLIPTIC:
|
|
251
|
+
args += [self.rp, self.rs]
|
|
252
|
+
if self.TYPE in (FilterType.HIGHPASS, FilterType.LOWPASS):
|
|
253
|
+
args += [self.cut0 / f_nyquist]
|
|
254
|
+
else:
|
|
255
|
+
args += [[self.cut0 / f_nyquist, self.cut1 / f_nyquist]]
|
|
256
|
+
args += [self.TYPE.value]
|
|
257
|
+
return self.get_filter_func()(*args)
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
class LowPassFilterParam(BaseHighLowBandParam):
|
|
261
|
+
"""Low-pass filter parameters"""
|
|
262
|
+
|
|
263
|
+
TYPE = FilterType.LOWPASS
|
|
264
|
+
|
|
265
|
+
# Redefine cut0 just to change its label (instead of "Low cutoff frequency")
|
|
266
|
+
cut0 = gds.FloatItem(
|
|
267
|
+
_("Cutoff frequency"), min=0, nonzero=True, unit="Hz", allow_none=True
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
class HighPassFilterParam(BaseHighLowBandParam):
|
|
272
|
+
"""High-pass filter parameters"""
|
|
273
|
+
|
|
274
|
+
TYPE = FilterType.HIGHPASS
|
|
275
|
+
|
|
276
|
+
# Redefine cut0 just to change its label (instead of "High cutoff frequency")
|
|
277
|
+
cut0 = gds.FloatItem(
|
|
278
|
+
_("Cutoff frequency"), min=0, nonzero=True, unit="Hz", allow_none=True
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
class BandPassFilterParam(BaseHighLowBandParam):
|
|
283
|
+
"""Band-pass filter parameters"""
|
|
284
|
+
|
|
285
|
+
TYPE = FilterType.BANDPASS
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
class BandStopFilterParam(BaseHighLowBandParam):
|
|
289
|
+
"""Band-stop filter parameters"""
|
|
290
|
+
|
|
291
|
+
TYPE = FilterType.BANDSTOP
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def frequency_filter(src: SignalObj, p: BaseHighLowBandParam) -> SignalObj:
|
|
295
|
+
"""Compute frequency filter (low-pass, high-pass, band-pass, band-stop),
|
|
296
|
+
with :py:func:`scipy.signal.filtfilt`
|
|
297
|
+
|
|
298
|
+
Args:
|
|
299
|
+
src: source signal
|
|
300
|
+
p: parameters
|
|
301
|
+
|
|
302
|
+
Returns:
|
|
303
|
+
Result signal object
|
|
304
|
+
|
|
305
|
+
.. note::
|
|
306
|
+
|
|
307
|
+
Uses zero-phase filtering (`filtfilt`) when possible for better phase response.
|
|
308
|
+
If numerical instability occurs (e.g., singular matrix errors), automatically
|
|
309
|
+
falls back to forward filtering (`lfilter`) with a warning. This ensures
|
|
310
|
+
cross-platform compatibility while maintaining optimal filtering when possible.
|
|
311
|
+
"""
|
|
312
|
+
name = f"{p.TYPE.value}"
|
|
313
|
+
suffix = ""
|
|
314
|
+
if p.method != FrequencyFilterMethod.BRICKWALL:
|
|
315
|
+
suffix = f"order={p.order:d}, "
|
|
316
|
+
if p.TYPE in (FilterType.LOWPASS, FilterType.HIGHPASS):
|
|
317
|
+
suffix += f"cutoff={p.cut0:.2f}"
|
|
318
|
+
else:
|
|
319
|
+
suffix += f"cutoff={p.cut0:.2f}:{p.cut1:.2f}"
|
|
320
|
+
dst = dst_1_to_1(src, name, suffix)
|
|
321
|
+
|
|
322
|
+
if p.method == FrequencyFilterMethod.BRICKWALL:
|
|
323
|
+
original_size = src.y.size
|
|
324
|
+
src_padded = src.copy()
|
|
325
|
+
if p.zero_padding and p.nfft is not None:
|
|
326
|
+
size_padded = ZeroPadding1DParam.next_power_of_two(max(p.nfft, src.y.size))
|
|
327
|
+
n_to_add = size_padded - src.y.size
|
|
328
|
+
if n_to_add > 0:
|
|
329
|
+
src_padded = zero_padding(
|
|
330
|
+
src_padded,
|
|
331
|
+
ZeroPadding1DParam.create(
|
|
332
|
+
location=PadLocation1D.APPEND,
|
|
333
|
+
strategy="custom",
|
|
334
|
+
n=n_to_add,
|
|
335
|
+
),
|
|
336
|
+
)
|
|
337
|
+
x_padded, y_padded = src_padded.get_data()
|
|
338
|
+
x, y = fourier.brickwall_filter(
|
|
339
|
+
x_padded, y_padded, p.TYPE.value, p.cut0, p.cut1
|
|
340
|
+
)
|
|
341
|
+
# Trim back to original size if padding was applied
|
|
342
|
+
x = x[:original_size]
|
|
343
|
+
y = y[:original_size]
|
|
344
|
+
dst.set_xydata(x, y)
|
|
345
|
+
else:
|
|
346
|
+
b, a = p.get_filter_params(dst)
|
|
347
|
+
try:
|
|
348
|
+
# Prefer zero-phase filtering
|
|
349
|
+
dst.y = sps.filtfilt(b, a, dst.y)
|
|
350
|
+
except np.linalg.LinAlgError:
|
|
351
|
+
# Fallback to forward filtering if filtfilt fails due to numerical issues
|
|
352
|
+
warnings.warn(
|
|
353
|
+
"Zero-phase filtering failed due to numerical instability. "
|
|
354
|
+
"Using forward filtering instead.",
|
|
355
|
+
UserWarning,
|
|
356
|
+
stacklevel=2,
|
|
357
|
+
)
|
|
358
|
+
dst.y = sps.lfilter(b, a, dst.y)
|
|
359
|
+
|
|
360
|
+
restore_data_outside_roi(dst, src)
|
|
361
|
+
return dst
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
@computation_function()
|
|
365
|
+
def lowpass(src: SignalObj, p: LowPassFilterParam) -> SignalObj:
|
|
366
|
+
"""Compute low-pass filter with :py:func:`scipy.signal.filtfilt`
|
|
367
|
+
|
|
368
|
+
Args:
|
|
369
|
+
src: source signal
|
|
370
|
+
p: parameters
|
|
371
|
+
|
|
372
|
+
Returns:
|
|
373
|
+
Result signal object
|
|
374
|
+
"""
|
|
375
|
+
return frequency_filter(src, p)
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
@computation_function()
|
|
379
|
+
def highpass(src: SignalObj, p: HighPassFilterParam) -> SignalObj:
|
|
380
|
+
"""Compute high-pass filter with :py:func:`scipy.signal.filtfilt`
|
|
381
|
+
|
|
382
|
+
Args:
|
|
383
|
+
src: source signal
|
|
384
|
+
p: parameters
|
|
385
|
+
|
|
386
|
+
Returns:
|
|
387
|
+
Result signal object
|
|
388
|
+
"""
|
|
389
|
+
return frequency_filter(src, p)
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
@computation_function()
|
|
393
|
+
def bandpass(src: SignalObj, p: BandPassFilterParam) -> SignalObj:
|
|
394
|
+
"""Compute band-pass filter with :py:func:`scipy.signal.filtfilt`
|
|
395
|
+
|
|
396
|
+
Args:
|
|
397
|
+
src: source signal
|
|
398
|
+
p: parameters
|
|
399
|
+
|
|
400
|
+
Returns:
|
|
401
|
+
Result signal object
|
|
402
|
+
"""
|
|
403
|
+
return frequency_filter(src, p)
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
@computation_function()
|
|
407
|
+
def bandstop(src: SignalObj, p: BandStopFilterParam) -> SignalObj:
|
|
408
|
+
"""Compute band-stop filter with :py:func:`scipy.signal.filtfilt`
|
|
409
|
+
|
|
410
|
+
Args:
|
|
411
|
+
src: source signal
|
|
412
|
+
p: parameters
|
|
413
|
+
|
|
414
|
+
Returns:
|
|
415
|
+
Result signal object
|
|
416
|
+
"""
|
|
417
|
+
return frequency_filter(src, p)
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
# Noise addition functions
|
|
421
|
+
@computation_function()
|
|
422
|
+
def add_gaussian_noise(src: SignalObj, p: NormalDistributionParam) -> SignalObj:
|
|
423
|
+
"""Add normal noise to the input signal.
|
|
424
|
+
|
|
425
|
+
Args:
|
|
426
|
+
src: Source signal.
|
|
427
|
+
p: Parameters.
|
|
428
|
+
|
|
429
|
+
Returns:
|
|
430
|
+
Result signal object.
|
|
431
|
+
"""
|
|
432
|
+
param = NormalDistribution1DParam() # Do not confuse with NormalDistributionParam
|
|
433
|
+
gds.update_dataset(param, p)
|
|
434
|
+
param.xmin = src.x[0]
|
|
435
|
+
param.xmax = src.x[-1]
|
|
436
|
+
param.size = src.x.size
|
|
437
|
+
noise = create_signal_from_param(param)
|
|
438
|
+
dst = dst_1_to_1(src, "add_gaussian_noise", f"µ={p.mu}, σ={p.sigma}")
|
|
439
|
+
dst.xydata = addition([src, noise]).xydata
|
|
440
|
+
return dst
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
@computation_function()
|
|
444
|
+
def add_poisson_noise(src: SignalObj, p: PoissonDistributionParam) -> SignalObj:
|
|
445
|
+
"""Add Poisson noise to the input signal.
|
|
446
|
+
|
|
447
|
+
Args:
|
|
448
|
+
src: Source signal.
|
|
449
|
+
p: Parameters.
|
|
450
|
+
|
|
451
|
+
Returns:
|
|
452
|
+
Result signal object.
|
|
453
|
+
"""
|
|
454
|
+
param = PoissonDistribution1DParam() # Do not confuse with PoissonDistributionParam
|
|
455
|
+
gds.update_dataset(param, p)
|
|
456
|
+
param.xmin = src.x[0]
|
|
457
|
+
param.xmax = src.x[-1]
|
|
458
|
+
param.size = src.x.size
|
|
459
|
+
noise = create_signal_from_param(param)
|
|
460
|
+
dst = dst_1_to_1(src, "add_poisson_noise", f"λ={p.lam}")
|
|
461
|
+
dst.xydata = addition([src, noise]).xydata
|
|
462
|
+
return dst
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
@computation_function()
|
|
466
|
+
def add_uniform_noise(src: SignalObj, p: UniformDistributionParam) -> SignalObj:
|
|
467
|
+
"""Add uniform noise to the input signal.
|
|
468
|
+
|
|
469
|
+
Args:
|
|
470
|
+
src: Source signal.
|
|
471
|
+
p: Parameters.
|
|
472
|
+
|
|
473
|
+
Returns:
|
|
474
|
+
Result signal object.
|
|
475
|
+
"""
|
|
476
|
+
param = UniformDistribution1DParam() # Do not confuse with UniformDistributionParam
|
|
477
|
+
gds.update_dataset(param, p)
|
|
478
|
+
param.xmin = src.x[0]
|
|
479
|
+
param.xmax = src.x[-1]
|
|
480
|
+
param.size = src.x.size
|
|
481
|
+
noise = create_signal_from_param(param)
|
|
482
|
+
dst = dst_1_to_1(src, "add_uniform_noise", f"low={p.vmin}, high={p.vmax}")
|
|
483
|
+
dst.xydata = addition([src, noise]).xydata
|
|
484
|
+
return dst
|