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,112 @@
|
|
|
1
|
+
# Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Unit tests for pulse analysis functions.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Literal
|
|
10
|
+
|
|
11
|
+
import numpy as np
|
|
12
|
+
|
|
13
|
+
from sigima.tools.signal import pulse
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def view_baseline_plateau_and_curve(
|
|
17
|
+
x: np.ndarray,
|
|
18
|
+
y: np.ndarray,
|
|
19
|
+
title: str,
|
|
20
|
+
signal_type: Literal["step", "square"],
|
|
21
|
+
start_range: tuple[float, float],
|
|
22
|
+
end_range: tuple[float, float],
|
|
23
|
+
plateau_range: tuple[float, float] | None = None,
|
|
24
|
+
vcursors: dict[str, float] | None = None,
|
|
25
|
+
other_items: list | None = None,
|
|
26
|
+
) -> None:
|
|
27
|
+
"""Helper function to visualize signal with baselines and plateau.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
x: X data.
|
|
31
|
+
y: Y data.
|
|
32
|
+
title: Title for the plot.
|
|
33
|
+
signal_type: Signal shape type
|
|
34
|
+
start_range: Start baseline range.
|
|
35
|
+
end_range: End baseline range.
|
|
36
|
+
plateau_range: Plateau range for square signals (optional).
|
|
37
|
+
vcursors: Dictionary of vertical cursors to display (optional).
|
|
38
|
+
other_items: Additional items to display (optional).
|
|
39
|
+
"""
|
|
40
|
+
# pylint: disable=import-outside-toplevel
|
|
41
|
+
from plotpy.builder import make
|
|
42
|
+
|
|
43
|
+
from sigima.tests import vistools
|
|
44
|
+
|
|
45
|
+
ys = pulse.get_range_mean_y(x, y, start_range)
|
|
46
|
+
ye = pulse.get_range_mean_y(x, y, end_range)
|
|
47
|
+
xs0, xs1 = start_range
|
|
48
|
+
xe0, xe1 = end_range
|
|
49
|
+
items = [
|
|
50
|
+
make.mcurve(x, y, label="Noisy signal"),
|
|
51
|
+
vistools.create_signal_segment(xs0, ys, xs1, ys, "Start baseline"),
|
|
52
|
+
vistools.create_signal_segment(xe0, ye, xe1, ye, "End baseline"),
|
|
53
|
+
]
|
|
54
|
+
if signal_type == "square":
|
|
55
|
+
if plateau_range is None:
|
|
56
|
+
polarity = pulse.detect_polarity(x, y, start_range, end_range)
|
|
57
|
+
plateau_range = pulse.get_plateau_range(x, y, polarity)
|
|
58
|
+
xp0, xp1 = plateau_range
|
|
59
|
+
yp = pulse.get_range_mean_y(x, y, plateau_range)
|
|
60
|
+
items.append(vistools.create_signal_segment(xp0, yp, xp1, yp, "Plateau"))
|
|
61
|
+
if vcursors is not None:
|
|
62
|
+
for label, xt in vcursors.items():
|
|
63
|
+
items.append(vistools.create_cursor("v", xt, label))
|
|
64
|
+
if other_items is not None:
|
|
65
|
+
items.extend(other_items)
|
|
66
|
+
|
|
67
|
+
vistools.view_curve_items(items, title=title)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def view_pulse_features(
|
|
71
|
+
x: np.ndarray,
|
|
72
|
+
y: np.ndarray,
|
|
73
|
+
title: str,
|
|
74
|
+
signal_type: Literal["step", "square"],
|
|
75
|
+
features: pulse.PulseFeatures,
|
|
76
|
+
) -> None:
|
|
77
|
+
"""Helper function to visualize pulse features.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
x: X data.
|
|
81
|
+
y: Y data.
|
|
82
|
+
title: Title for the plot.
|
|
83
|
+
signal_type: Signal shape type
|
|
84
|
+
features: Extracted pulse features.
|
|
85
|
+
"""
|
|
86
|
+
# pylint: disable=import-outside-toplevel
|
|
87
|
+
from sigima.tests import vistools
|
|
88
|
+
|
|
89
|
+
params_text = "<br>".join(
|
|
90
|
+
[
|
|
91
|
+
f"<b>Extracted {signal_type} parameters:</b>",
|
|
92
|
+
f"Polarity: {features.polarity}",
|
|
93
|
+
f"Amplitude: {features.amplitude}",
|
|
94
|
+
f"Rise time: {features.rise_time}",
|
|
95
|
+
f"Fall time: {features.fall_time}",
|
|
96
|
+
f"FWHM: {features.fwhm}",
|
|
97
|
+
f"Offset: {features.offset}",
|
|
98
|
+
f"T50: {features.x50}",
|
|
99
|
+
f"X100: {features.x100}",
|
|
100
|
+
f"Foot duration: {features.foot_duration}",
|
|
101
|
+
]
|
|
102
|
+
)
|
|
103
|
+
view_baseline_plateau_and_curve(
|
|
104
|
+
x,
|
|
105
|
+
y,
|
|
106
|
+
title,
|
|
107
|
+
signal_type,
|
|
108
|
+
[features.xstartmin, features.xstartmax],
|
|
109
|
+
[features.xendmin, features.xendmax],
|
|
110
|
+
plateau_range=None,
|
|
111
|
+
other_items=[vistools.create_label(params_text)],
|
|
112
|
+
)
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Unit tests for crossing time detection (x0, x50, x100) with real square pulse data.
|
|
5
|
+
|
|
6
|
+
This test suite validates that the crossing time detection algorithm correctly
|
|
7
|
+
identifies the x0 (0%), x50 (50%), and x100 (100%) crossing points in real-world
|
|
8
|
+
square pulse signals.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import pytest
|
|
14
|
+
|
|
15
|
+
from sigima.tests import guiutils
|
|
16
|
+
from sigima.tests.data import get_test_signal
|
|
17
|
+
from sigima.tests.helpers import check_scalar_result
|
|
18
|
+
from sigima.tests.signal.pulse import view_baseline_plateau_and_curve
|
|
19
|
+
from sigima.tools.signal import pulse
|
|
20
|
+
|
|
21
|
+
# Test data configuration: (basename, (x0, x50, x100), (rtol0, rtol50, rtol100))
|
|
22
|
+
# Note: x0 values have known issues (detecting crossings too early in the signal)
|
|
23
|
+
# so we use very high tolerances. x50 is generally reliable, x100 varies by signal.
|
|
24
|
+
CROSSING_TEST_DATA = [
|
|
25
|
+
("boxcar.npy", (-1e-9, 2.5e-10, 2e-9), (20.0, 0.15, 0.5)),
|
|
26
|
+
("square2.npy", (-3e-6, 6.5e-6, 1.65e-5), (20.0, 0.1, 5.0)),
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@pytest.mark.parametrize("basename,expected_values,tolerances", CROSSING_TEST_DATA)
|
|
31
|
+
def test_crossing_times(
|
|
32
|
+
basename: str,
|
|
33
|
+
expected_values: tuple[float, float, float],
|
|
34
|
+
tolerances: tuple[float, float, float],
|
|
35
|
+
) -> None:
|
|
36
|
+
"""Parametric test for crossing time detection on real square pulse data.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
basename: Name of the test data file
|
|
40
|
+
expected_values: Expected (x0, x50, x100) crossing times
|
|
41
|
+
tolerances: Relative tolerances (rtol0, rtol50, rtol100) for each crossing
|
|
42
|
+
|
|
43
|
+
This test ensures that the crossing time detection algorithm correctly identifies
|
|
44
|
+
the 0%, 50%, and 100% crossing points within acceptable tolerances.
|
|
45
|
+
The test includes optional visualization when run with GUI enabled.
|
|
46
|
+
|
|
47
|
+
Note:
|
|
48
|
+
x0 detection has known issues and uses very high tolerances. This test
|
|
49
|
+
primarily validates that the algorithm doesn't crash and returns values
|
|
50
|
+
in the ballpark of expected results.
|
|
51
|
+
"""
|
|
52
|
+
# Load real data
|
|
53
|
+
obj = get_test_signal(basename)
|
|
54
|
+
x, y = obj.x, obj.y
|
|
55
|
+
|
|
56
|
+
# Auto-detect ranges and polarity
|
|
57
|
+
start_range = pulse.get_start_range(x)
|
|
58
|
+
end_range = pulse.get_end_range(x)
|
|
59
|
+
polarity = pulse.detect_polarity(x, y, start_range, end_range)
|
|
60
|
+
plateau_range = pulse.get_plateau_range(x, y, polarity)
|
|
61
|
+
|
|
62
|
+
# Find crossing times
|
|
63
|
+
x0 = pulse.find_crossing_at_ratio(x, y, 0.0, start_range, end_range)
|
|
64
|
+
x50 = pulse.find_crossing_at_ratio(x, y, 0.5, start_range, end_range)
|
|
65
|
+
x100 = pulse.find_crossing_at_ratio(x, y, 1.0, start_range, end_range)
|
|
66
|
+
|
|
67
|
+
exp_x0, exp_x50, exp_x100 = expected_values
|
|
68
|
+
rtol0, rtol50, rtol100 = tolerances
|
|
69
|
+
|
|
70
|
+
with guiutils.lazy_qt_app_context() as qt_app:
|
|
71
|
+
title = (
|
|
72
|
+
f"{basename} - Crossing Times Test\n"
|
|
73
|
+
f"x0={x0:.3e} (exp: {exp_x0:.3e}), "
|
|
74
|
+
f"x50={x50:.3e} (exp: {exp_x50:.3e}), "
|
|
75
|
+
f"x100={x100:.3e} (exp: {exp_x100:.3e})"
|
|
76
|
+
)
|
|
77
|
+
if qt_app is not None:
|
|
78
|
+
# pylint: disable=import-outside-toplevel
|
|
79
|
+
from sigima.tests import vistools
|
|
80
|
+
|
|
81
|
+
# Create vertical markers for crossing times
|
|
82
|
+
items = []
|
|
83
|
+
items.append(vistools.create_cursor("v", x0, "x0 (detected)"))
|
|
84
|
+
items.append(vistools.create_cursor("v", exp_x0, "x0 (expected)"))
|
|
85
|
+
items.append(vistools.create_cursor("v", x50, "x50 (detected)"))
|
|
86
|
+
items.append(vistools.create_cursor("v", exp_x50, "x50 (expected)"))
|
|
87
|
+
items.append(vistools.create_cursor("v", x100, "x100 (detected)"))
|
|
88
|
+
items.append(vistools.create_cursor("v", exp_x100, "x100 (expected)"))
|
|
89
|
+
|
|
90
|
+
view_baseline_plateau_and_curve(
|
|
91
|
+
x,
|
|
92
|
+
y,
|
|
93
|
+
title,
|
|
94
|
+
"square",
|
|
95
|
+
start_range,
|
|
96
|
+
end_range,
|
|
97
|
+
plateau_range,
|
|
98
|
+
other_items=items,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
# Validate crossing times with appropriate tolerances
|
|
102
|
+
# Note: We use very high tolerances for x0 due to known detection issues
|
|
103
|
+
assert x0 is not None, f"{basename}: x0 crossing not found"
|
|
104
|
+
assert x50 is not None, f"{basename}: x50 crossing not found"
|
|
105
|
+
assert x100 is not None, f"{basename}: x100 crossing not found"
|
|
106
|
+
|
|
107
|
+
# Check results
|
|
108
|
+
check_scalar_result("x0", x0, exp_x0, rtol0)
|
|
109
|
+
check_scalar_result("x50", x50, exp_x50, rtol50)
|
|
110
|
+
check_scalar_result("x100", x100, exp_x100, rtol100)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def all_tests() -> None:
|
|
114
|
+
"""Run all crossing time tests."""
|
|
115
|
+
for basename, expected_values, tolerances in CROSSING_TEST_DATA:
|
|
116
|
+
print(f"Testing {basename}...")
|
|
117
|
+
test_crossing_times(basename, expected_values, tolerances)
|
|
118
|
+
print(f"✓ {basename} passed\n")
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
if __name__ == "__main__":
|
|
122
|
+
guiutils.enable_gui()
|
|
123
|
+
all_tests()
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Unit tests for plateau detection algorithm with real square pulse data.
|
|
5
|
+
|
|
6
|
+
This test suite validates that the plateau detection algorithm correctly identifies
|
|
7
|
+
the plateau region in real-world square pulse signals.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import pytest
|
|
13
|
+
|
|
14
|
+
from sigima.tests import guiutils
|
|
15
|
+
from sigima.tests.data import get_test_signal
|
|
16
|
+
from sigima.tests.signal.pulse import view_baseline_plateau_and_curve
|
|
17
|
+
from sigima.tools.signal import pulse
|
|
18
|
+
|
|
19
|
+
# Test data configuration: (basename, expected_interval)
|
|
20
|
+
PLATEAU_TEST_DATA = [
|
|
21
|
+
("boxcar.npy", (0.0, 1.4e-7)), # Slightly wider to accommodate full plateau
|
|
22
|
+
("square2.npy", (0.0, 2.4e-4)), # Slightly wider to accommodate full plateau
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@pytest.mark.parametrize("basename,expected_interval", PLATEAU_TEST_DATA)
|
|
27
|
+
def test_plateau_detection(
|
|
28
|
+
basename: str, expected_interval: tuple[float, float]
|
|
29
|
+
) -> None:
|
|
30
|
+
"""Parametric test for plateau detection on real square pulse data.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
basename: Name of the test data file
|
|
34
|
+
expected_interval: Expected (min, max) interval containing the plateau
|
|
35
|
+
|
|
36
|
+
This test ensures that the plateau detection algorithm correctly identifies
|
|
37
|
+
the plateau region within the expected interval for various real signals.
|
|
38
|
+
The test also includes optional visualization when run with GUI enabled.
|
|
39
|
+
"""
|
|
40
|
+
# Load real data
|
|
41
|
+
obj = get_test_signal(basename)
|
|
42
|
+
x, y = obj.x, obj.y
|
|
43
|
+
|
|
44
|
+
# Auto-detect polarity and plateau
|
|
45
|
+
start_range = pulse.get_start_range(x)
|
|
46
|
+
end_range = pulse.get_end_range(x)
|
|
47
|
+
polarity = pulse.detect_polarity(x, y, start_range, end_range)
|
|
48
|
+
plateau_min, plateau_max = pulse.get_plateau_range(x, y, polarity)
|
|
49
|
+
|
|
50
|
+
expected_min, expected_max = expected_interval
|
|
51
|
+
|
|
52
|
+
with guiutils.lazy_qt_app_context() as qt_app:
|
|
53
|
+
title = (
|
|
54
|
+
f"{basename} - Plateau Detection Test\n"
|
|
55
|
+
f"Detected: [{plateau_min:.3e}, {plateau_max:.3e}]"
|
|
56
|
+
)
|
|
57
|
+
if qt_app is not None:
|
|
58
|
+
# pylint: disable=import-outside-toplevel
|
|
59
|
+
from sigima.tests import vistools
|
|
60
|
+
|
|
61
|
+
item = vistools.create_range(
|
|
62
|
+
"h",
|
|
63
|
+
expected_min,
|
|
64
|
+
expected_max,
|
|
65
|
+
"Expected plateau interval",
|
|
66
|
+
)
|
|
67
|
+
view_baseline_plateau_and_curve(
|
|
68
|
+
x,
|
|
69
|
+
y,
|
|
70
|
+
title,
|
|
71
|
+
"square",
|
|
72
|
+
start_range or pulse.get_start_range(x),
|
|
73
|
+
end_range or pulse.get_end_range(x),
|
|
74
|
+
(plateau_min, plateau_max),
|
|
75
|
+
other_items=[item],
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
# Validate plateau is within expected interval
|
|
79
|
+
assert plateau_min >= expected_min, (
|
|
80
|
+
f"{basename}: Plateau start {plateau_min:.3e} is before expected "
|
|
81
|
+
f"interval [{expected_min:.3e}, {expected_max:.3e}]"
|
|
82
|
+
)
|
|
83
|
+
assert plateau_max <= expected_max, (
|
|
84
|
+
f"{basename}: Plateau end {plateau_max:.3e} is after expected "
|
|
85
|
+
f"interval [{expected_min:.3e}, {expected_max:.3e}]"
|
|
86
|
+
)
|
|
87
|
+
assert plateau_min < plateau_max, (
|
|
88
|
+
f"{basename}: Invalid plateau range [{plateau_min:.3e}, {plateau_max:.3e}]"
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def all_tests() -> None:
|
|
93
|
+
"""Run all plateau detection tests."""
|
|
94
|
+
for basename, expected_interval in PLATEAU_TEST_DATA:
|
|
95
|
+
print(f"Testing {basename}...")
|
|
96
|
+
test_plateau_detection(basename, expected_interval)
|
|
97
|
+
print(f"✓ {basename} passed\n")
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
if __name__ == "__main__":
|
|
101
|
+
guiutils.enable_gui()
|
|
102
|
+
all_tests()
|