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,603 @@
|
|
|
1
|
+
# Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Unit tests around the `SignalObj` class and its creation from parameters.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
# pylint: disable=invalid-name # Allows short reference names like x, y, ...
|
|
8
|
+
# pylint: disable=duplicate-code
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import os.path as osp
|
|
13
|
+
|
|
14
|
+
import numpy as np
|
|
15
|
+
import pytest
|
|
16
|
+
|
|
17
|
+
import sigima.io
|
|
18
|
+
import sigima.objects
|
|
19
|
+
from sigima.io.signal import SignalIORegistry
|
|
20
|
+
from sigima.tests import guiutils
|
|
21
|
+
from sigima.tests.data import iterate_signal_creation
|
|
22
|
+
from sigima.tests.env import execenv
|
|
23
|
+
from sigima.tests.helpers import (
|
|
24
|
+
WorkdirRestoringTempDir,
|
|
25
|
+
compare_metadata,
|
|
26
|
+
read_test_objects,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# pylint: disable=unused-argument
|
|
31
|
+
def preprocess_signal_parameters(param: sigima.objects.NewSignalParam) -> None:
|
|
32
|
+
"""Preprocess signal parameters before creating the signal.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
param: The signal parameters to preprocess.
|
|
36
|
+
"""
|
|
37
|
+
# Add here specific preprocessing for signal parameters if needed
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def postprocess_signal_object(
|
|
41
|
+
obj: sigima.objects.SignalObj, stype: sigima.objects.SignalTypes
|
|
42
|
+
) -> None:
|
|
43
|
+
"""Postprocess signal object after creation.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
obj: The signal object to postprocess.
|
|
47
|
+
stype: The type of the signal.
|
|
48
|
+
"""
|
|
49
|
+
if stype == sigima.objects.SignalTypes.ZERO:
|
|
50
|
+
assert (obj.y == 0).all()
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def test_all_signal_types() -> None:
|
|
54
|
+
"""Test all combinations of signal types and data sizes"""
|
|
55
|
+
execenv.print(f"{test_all_signal_types.__doc__}:")
|
|
56
|
+
for signal in iterate_signal_creation(
|
|
57
|
+
preproc=preprocess_signal_parameters, postproc=postprocess_signal_object
|
|
58
|
+
):
|
|
59
|
+
assert signal.x is not None and signal.y is not None
|
|
60
|
+
execenv.print(f"{test_all_signal_types.__doc__}: OK")
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@pytest.mark.parametrize(
|
|
64
|
+
"fname, orig_signal", list(read_test_objects(SignalIORegistry))
|
|
65
|
+
)
|
|
66
|
+
def test_hdf5_signal_io(fname: str, orig_signal: sigima.objects.SignalObj) -> None:
|
|
67
|
+
"""Test HDF5 I/O for signal objects"""
|
|
68
|
+
if orig_signal is None:
|
|
69
|
+
pytest.skip(f"Skipping {fname} (not implemented)")
|
|
70
|
+
execenv.print(f"{test_hdf5_signal_io.__doc__}:")
|
|
71
|
+
with WorkdirRestoringTempDir() as tmpdir:
|
|
72
|
+
# Save to HDF5
|
|
73
|
+
filename = osp.join(tmpdir, f"test_{osp.basename(fname)}.h5sig")
|
|
74
|
+
sigima.io.write_signal(filename, orig_signal)
|
|
75
|
+
execenv.print(f" Saved {filename}")
|
|
76
|
+
# Read back
|
|
77
|
+
fetch_signal = sigima.io.read_signal(filename)
|
|
78
|
+
execenv.print(f" Read {filename}")
|
|
79
|
+
orig_x, orig_y = orig_signal.x, orig_signal.y
|
|
80
|
+
orig_x: np.ndarray
|
|
81
|
+
orig_y: np.ndarray
|
|
82
|
+
x, y = fetch_signal.x, fetch_signal.y
|
|
83
|
+
assert isinstance(x, np.ndarray)
|
|
84
|
+
assert isinstance(y, np.ndarray)
|
|
85
|
+
assert x.shape == orig_x.shape
|
|
86
|
+
assert y.shape == orig_y.shape
|
|
87
|
+
assert x.dtype == orig_x.dtype
|
|
88
|
+
assert y.dtype == orig_y.dtype
|
|
89
|
+
assert np.isclose(x, orig_x, atol=0.0).all()
|
|
90
|
+
assert np.isclose(y, orig_y, atol=0.0).all()
|
|
91
|
+
try:
|
|
92
|
+
compare_metadata(
|
|
93
|
+
fetch_signal.metadata, orig_signal.metadata.copy(), raise_on_diff=True
|
|
94
|
+
)
|
|
95
|
+
except AssertionError as exc:
|
|
96
|
+
raise AssertionError(
|
|
97
|
+
f"Signal metadata read from file does not match original ({fname})"
|
|
98
|
+
) from exc
|
|
99
|
+
execenv.print(f"{test_hdf5_signal_io.__doc__}: OK")
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@pytest.mark.gui
|
|
103
|
+
def test_signal_parameters_interactive() -> None:
|
|
104
|
+
"""Test interactive creation of signal parameters"""
|
|
105
|
+
execenv.print(f"{test_signal_parameters_interactive.__doc__}:")
|
|
106
|
+
with guiutils.lazy_qt_app_context(force=True):
|
|
107
|
+
for stype in sigima.objects.SignalTypes:
|
|
108
|
+
param = sigima.objects.create_signal_parameters(stype)
|
|
109
|
+
if isinstance(param, sigima.objects.CustomSignalParam):
|
|
110
|
+
param.setup_array()
|
|
111
|
+
if param.edit():
|
|
112
|
+
execenv.print(f" Edited parameters for {stype.value}:")
|
|
113
|
+
execenv.print(f" {param}")
|
|
114
|
+
else:
|
|
115
|
+
execenv.print(f" Skipped editing parameters for {stype.value}")
|
|
116
|
+
execenv.print(f"{test_signal_parameters_interactive.__doc__}: OK")
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def test_create_signal() -> None:
|
|
120
|
+
"""Test creation of a signal object using `create_signal` function"""
|
|
121
|
+
execenv.print(f"{test_create_signal.__doc__}:")
|
|
122
|
+
# pylint: disable=import-outside-toplevel
|
|
123
|
+
|
|
124
|
+
# Test all combinations of input parameters
|
|
125
|
+
x = np.linspace(0, 10, 100)
|
|
126
|
+
y = np.sin(x)
|
|
127
|
+
dx = np.full_like(x, 0.1)
|
|
128
|
+
dy = np.full_like(y, 0.01)
|
|
129
|
+
metadata = {"source": "test", "description": "Test signal"}
|
|
130
|
+
units = ("s", "V")
|
|
131
|
+
labels = ("Time", "Amplitude")
|
|
132
|
+
|
|
133
|
+
# 1. Create signal with all parameters
|
|
134
|
+
title = "Some Signal"
|
|
135
|
+
signal = sigima.objects.create_signal(
|
|
136
|
+
title=title,
|
|
137
|
+
x=x,
|
|
138
|
+
y=y,
|
|
139
|
+
dx=dx,
|
|
140
|
+
dy=dy,
|
|
141
|
+
metadata=metadata,
|
|
142
|
+
units=units,
|
|
143
|
+
labels=labels,
|
|
144
|
+
)
|
|
145
|
+
assert isinstance(signal, sigima.objects.SignalObj)
|
|
146
|
+
assert signal.title == title
|
|
147
|
+
assert np.array_equal(signal.x, x)
|
|
148
|
+
assert np.array_equal(signal.y, y)
|
|
149
|
+
assert np.array_equal(signal.dx, dx)
|
|
150
|
+
assert np.array_equal(signal.dy, dy)
|
|
151
|
+
assert signal.metadata == metadata
|
|
152
|
+
assert (signal.xunit, signal.yunit) == units
|
|
153
|
+
assert (signal.xlabel, signal.ylabel) == labels
|
|
154
|
+
|
|
155
|
+
# 2. Create signal with only x and y
|
|
156
|
+
signal = sigima.objects.create_signal("", x=x, y=y)
|
|
157
|
+
assert isinstance(signal, sigima.objects.SignalObj)
|
|
158
|
+
assert np.array_equal(signal.x, x)
|
|
159
|
+
assert np.array_equal(signal.y, y)
|
|
160
|
+
assert signal.dx is None
|
|
161
|
+
assert signal.dy is None
|
|
162
|
+
assert not signal.metadata
|
|
163
|
+
assert (signal.xunit, signal.yunit) == ("", "")
|
|
164
|
+
assert (signal.xlabel, signal.ylabel) == ("", "")
|
|
165
|
+
|
|
166
|
+
# 3. Create signal with only x, y, and dx
|
|
167
|
+
signal = sigima.objects.create_signal("", x=x, y=y, dx=dx)
|
|
168
|
+
assert isinstance(signal, sigima.objects.SignalObj)
|
|
169
|
+
assert np.array_equal(signal.x, x)
|
|
170
|
+
assert np.array_equal(signal.y, y)
|
|
171
|
+
assert np.array_equal(signal.dx, dx)
|
|
172
|
+
assert signal.dy is None
|
|
173
|
+
|
|
174
|
+
# 4. Create signal with only x, y, and dy
|
|
175
|
+
signal = sigima.objects.create_signal("", x=x, y=y, dy=dy)
|
|
176
|
+
assert isinstance(signal, sigima.objects.SignalObj)
|
|
177
|
+
assert np.array_equal(signal.x, x)
|
|
178
|
+
assert np.array_equal(signal.y, y)
|
|
179
|
+
assert signal.dx is None
|
|
180
|
+
assert np.array_equal(signal.dy, dy)
|
|
181
|
+
|
|
182
|
+
execenv.print(f"{test_create_signal.__doc__}: OK")
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def test_create_signal_from_param() -> None:
|
|
186
|
+
"""Test creation of a signal object using `create_signal_from_param` function"""
|
|
187
|
+
execenv.print(f"{test_create_signal_from_param.__doc__}:")
|
|
188
|
+
|
|
189
|
+
# Test with different signal parameter types
|
|
190
|
+
test_cases = [
|
|
191
|
+
# Basic periodic functions
|
|
192
|
+
(sigima.objects.SineParam, "sine"),
|
|
193
|
+
(sigima.objects.CosineParam, "cosine"),
|
|
194
|
+
(sigima.objects.SawtoothParam, "sawtooth"),
|
|
195
|
+
(sigima.objects.TriangleParam, "triangle"),
|
|
196
|
+
(sigima.objects.SquareParam, "square"),
|
|
197
|
+
(sigima.objects.SincParam, "sinc"),
|
|
198
|
+
# Mathematical functions
|
|
199
|
+
(sigima.objects.GaussParam, "gaussian"),
|
|
200
|
+
(sigima.objects.LorentzParam, "lorentzian"),
|
|
201
|
+
(sigima.objects.ExponentialParam, "exponential"),
|
|
202
|
+
(sigima.objects.LogisticParam, "logistic"),
|
|
203
|
+
(sigima.objects.LinearChirpParam, "linear_chirp"),
|
|
204
|
+
(sigima.objects.StepParam, "step"),
|
|
205
|
+
(sigima.objects.PulseParam, "pulse"),
|
|
206
|
+
(sigima.objects.SquarePulseParam, "square_pulse"),
|
|
207
|
+
(sigima.objects.StepPulseParam, "step_pulse"),
|
|
208
|
+
(sigima.objects.PolyParam, "polynomial"),
|
|
209
|
+
# Noise and random signals
|
|
210
|
+
(sigima.objects.NormalDistribution1DParam, "normal_noise"),
|
|
211
|
+
(sigima.objects.PoissonDistribution1DParam, "poisson_noise"),
|
|
212
|
+
(sigima.objects.UniformDistribution1DParam, "uniform_noise"),
|
|
213
|
+
(sigima.objects.ZeroParam, "zero"),
|
|
214
|
+
# Other signals
|
|
215
|
+
(sigima.objects.CustomSignalParam, "custom"),
|
|
216
|
+
(sigima.objects.VoigtParam, "voigt"),
|
|
217
|
+
(sigima.objects.PlanckParam, "planck"),
|
|
218
|
+
]
|
|
219
|
+
|
|
220
|
+
# Raise an exception if sigima.objects.signal contain *Param classes not listed here
|
|
221
|
+
param_classes = dict(test_cases)
|
|
222
|
+
for attr_name in dir(sigima.objects):
|
|
223
|
+
attr = getattr(sigima.objects, attr_name)
|
|
224
|
+
if (
|
|
225
|
+
isinstance(attr, type)
|
|
226
|
+
and issubclass(attr, sigima.objects.NewSignalParam)
|
|
227
|
+
and attr is not sigima.objects.NewSignalParam
|
|
228
|
+
and attr is not sigima.objects.CustomSignalParam
|
|
229
|
+
and attr not in param_classes
|
|
230
|
+
):
|
|
231
|
+
raise AssertionError(f"Missing test case for {attr.__name__}")
|
|
232
|
+
|
|
233
|
+
for param_class, name in test_cases:
|
|
234
|
+
# Create parameter instance with default values
|
|
235
|
+
param = param_class.create(size=100, xmin=1.0, xmax=10.0)
|
|
236
|
+
param.title = f"Test {name} signal"
|
|
237
|
+
|
|
238
|
+
# Test the function
|
|
239
|
+
signal = sigima.objects.create_signal_from_param(param)
|
|
240
|
+
|
|
241
|
+
# Verify the returned object
|
|
242
|
+
assert isinstance(signal, sigima.objects.SignalObj), (
|
|
243
|
+
f"Expected SignalObj, got {type(signal)} for {name}"
|
|
244
|
+
)
|
|
245
|
+
assert signal.title == f"Test {name} signal", (
|
|
246
|
+
f"Title mismatch for {name}: expected 'Test {name} signal', "
|
|
247
|
+
f"got '{signal.title}'"
|
|
248
|
+
)
|
|
249
|
+
assert signal.x is not None, f"X data is None for {name}"
|
|
250
|
+
assert signal.y is not None, f"Y data is None for {name}"
|
|
251
|
+
assert len(signal.x) == 100, f"X length mismatch for {name}"
|
|
252
|
+
assert len(signal.y) == 100, f"Y length mismatch for {name}"
|
|
253
|
+
assert isinstance(signal.x, np.ndarray), f"X is not ndarray for {name}"
|
|
254
|
+
assert isinstance(signal.y, np.ndarray), f"Y is not ndarray for {name}"
|
|
255
|
+
|
|
256
|
+
# Test automatic title generation for parameters that support it
|
|
257
|
+
param_autotitle = param_class.create(size=100, xmin=1.0, xmax=10.0)
|
|
258
|
+
param_autotitle.title = "" # Empty title to trigger auto-generation
|
|
259
|
+
signal_autotitle = sigima.objects.create_signal_from_param(param_autotitle)
|
|
260
|
+
# Distribution params should generate descriptive titles
|
|
261
|
+
if "Distribution" in param_class.__name__:
|
|
262
|
+
assert signal_autotitle.title != "", (
|
|
263
|
+
f"Title should be auto-generated for {name}"
|
|
264
|
+
)
|
|
265
|
+
assert "Random" in signal_autotitle.title, (
|
|
266
|
+
f"Auto-generated title should contain 'Random' for {name}"
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
execenv.print(f" Created {name} signal: OK")
|
|
270
|
+
|
|
271
|
+
# Test with custom parameters and title generation
|
|
272
|
+
param = sigima.objects.GaussParam.create(size=50, xmin=-5.0, xmax=5.0)
|
|
273
|
+
param.title = "" # Empty title should trigger automatic numbering
|
|
274
|
+
signal = sigima.objects.create_signal_from_param(param)
|
|
275
|
+
|
|
276
|
+
assert signal.title != "", "Empty title should be replaced"
|
|
277
|
+
|
|
278
|
+
# Test parameter validation with units and labels
|
|
279
|
+
param = sigima.objects.SineParam()
|
|
280
|
+
param.title = "Sine wave test"
|
|
281
|
+
# xunit is set by default to "s" in SineParam
|
|
282
|
+
assert param.xunit == "s"
|
|
283
|
+
param.yunit = "V"
|
|
284
|
+
param.xlabel = "Time"
|
|
285
|
+
param.ylabel = "Amplitude"
|
|
286
|
+
|
|
287
|
+
signal = sigima.objects.create_signal_from_param(param)
|
|
288
|
+
|
|
289
|
+
expected_xunit = "s"
|
|
290
|
+
assert signal.xunit == expected_xunit, (
|
|
291
|
+
f"X unit mismatch: expected '{expected_xunit}', got '{signal.xunit}'"
|
|
292
|
+
)
|
|
293
|
+
expected_yunit = "V"
|
|
294
|
+
assert signal.yunit == expected_yunit, (
|
|
295
|
+
f"Y unit mismatch: expected '{expected_yunit}', got '{signal.yunit}'"
|
|
296
|
+
)
|
|
297
|
+
expected_xlabel = "Time"
|
|
298
|
+
assert signal.xlabel == expected_xlabel, (
|
|
299
|
+
f"X label mismatch: expected '{expected_xlabel}', got '{signal.xlabel}'"
|
|
300
|
+
)
|
|
301
|
+
expected_ylabel = "Amplitude"
|
|
302
|
+
assert signal.ylabel == expected_ylabel, (
|
|
303
|
+
f"Y label mismatch: expected '{expected_ylabel}', got '{signal.ylabel}'"
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
execenv.print(f"{test_create_signal_from_param.__doc__}: OK")
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def test_signal_copy() -> None:
|
|
310
|
+
"""Test copying signal objects with all attributes"""
|
|
311
|
+
execenv.print(f"{test_signal_copy.__doc__}:")
|
|
312
|
+
|
|
313
|
+
# Create a base signal with some data
|
|
314
|
+
x = np.linspace(0, 10, 100)
|
|
315
|
+
y = np.sin(x)
|
|
316
|
+
dx = np.full_like(x, 0.1)
|
|
317
|
+
dy = np.full_like(y, 0.01)
|
|
318
|
+
title = "Original Signal"
|
|
319
|
+
metadata = {"key1": "value1", "key2": 42}
|
|
320
|
+
units = ("s", "V")
|
|
321
|
+
labels = ("Time", "Voltage")
|
|
322
|
+
|
|
323
|
+
# Test 1: Copy signal with all attributes
|
|
324
|
+
execenv.print(" Test 1: Copy signal with all attributes")
|
|
325
|
+
signal = sigima.objects.create_signal(
|
|
326
|
+
title=title,
|
|
327
|
+
x=x,
|
|
328
|
+
y=y,
|
|
329
|
+
dx=dx,
|
|
330
|
+
dy=dy,
|
|
331
|
+
metadata=metadata.copy(),
|
|
332
|
+
units=units,
|
|
333
|
+
labels=labels,
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
# Set scale attributes
|
|
337
|
+
signal.autoscale = False
|
|
338
|
+
signal.xscalelog = True
|
|
339
|
+
signal.xscalemin = 1.0
|
|
340
|
+
signal.xscalemax = 9.0
|
|
341
|
+
signal.yscalelog = False
|
|
342
|
+
signal.yscalemin = -1.5
|
|
343
|
+
signal.yscalemax = 1.5
|
|
344
|
+
|
|
345
|
+
# Copy the signal
|
|
346
|
+
copied = signal.copy()
|
|
347
|
+
|
|
348
|
+
# Verify the copy
|
|
349
|
+
assert copied is not signal
|
|
350
|
+
assert copied.title == signal.title
|
|
351
|
+
assert np.array_equal(copied.x, signal.x)
|
|
352
|
+
assert np.array_equal(copied.y, signal.y)
|
|
353
|
+
assert np.array_equal(copied.dx, signal.dx)
|
|
354
|
+
assert np.array_equal(copied.dy, signal.dy)
|
|
355
|
+
assert copied.xydata is not signal.xydata # Different array objects
|
|
356
|
+
assert copied.metadata == signal.metadata
|
|
357
|
+
assert copied.metadata is not signal.metadata
|
|
358
|
+
assert (copied.xunit, copied.yunit) == units
|
|
359
|
+
assert (copied.xlabel, copied.ylabel) == labels
|
|
360
|
+
|
|
361
|
+
# Verify scale attributes are preserved
|
|
362
|
+
assert copied.autoscale == signal.autoscale
|
|
363
|
+
assert copied.xscalelog == signal.xscalelog
|
|
364
|
+
assert copied.xscalemin == signal.xscalemin
|
|
365
|
+
assert copied.xscalemax == signal.xscalemax
|
|
366
|
+
assert copied.yscalelog == signal.yscalelog
|
|
367
|
+
assert copied.yscalemin == signal.yscalemin
|
|
368
|
+
assert copied.yscalemax == signal.yscalemax
|
|
369
|
+
execenv.print(" ✓ All attributes correctly copied")
|
|
370
|
+
|
|
371
|
+
# Test 2: Copy with title override
|
|
372
|
+
execenv.print(" Test 2: Copy with custom title")
|
|
373
|
+
new_title = "Copied Signal"
|
|
374
|
+
copied_with_title = signal.copy(title=new_title)
|
|
375
|
+
assert copied_with_title.title == new_title
|
|
376
|
+
assert copied_with_title.autoscale == signal.autoscale
|
|
377
|
+
assert np.array_equal(copied_with_title.x, signal.x)
|
|
378
|
+
execenv.print(" ✓ Title override works correctly")
|
|
379
|
+
|
|
380
|
+
# Test 3: Copy with metadata filtering
|
|
381
|
+
execenv.print(" Test 3: Copy with metadata filtering")
|
|
382
|
+
copied_basic_meta = signal.copy(all_metadata=False)
|
|
383
|
+
assert copied_basic_meta.autoscale == signal.autoscale
|
|
384
|
+
assert copied_basic_meta.xscalelog == signal.xscalelog
|
|
385
|
+
execenv.print(" ✓ Metadata filtering works correctly")
|
|
386
|
+
|
|
387
|
+
# Test 4: Copy signal without error bars
|
|
388
|
+
execenv.print(" Test 4: Copy signal without error bars")
|
|
389
|
+
signal_no_err = sigima.objects.create_signal(
|
|
390
|
+
title="Signal without error bars",
|
|
391
|
+
x=x,
|
|
392
|
+
y=y,
|
|
393
|
+
units=units,
|
|
394
|
+
labels=labels,
|
|
395
|
+
)
|
|
396
|
+
signal_no_err.autoscale = True
|
|
397
|
+
signal_no_err.yscalelog = True
|
|
398
|
+
|
|
399
|
+
copied_no_err = signal_no_err.copy()
|
|
400
|
+
assert copied_no_err.dx is None
|
|
401
|
+
assert copied_no_err.dy is None
|
|
402
|
+
assert copied_no_err.autoscale is True
|
|
403
|
+
assert copied_no_err.yscalelog is True
|
|
404
|
+
execenv.print(" ✓ Signal without error bars copied correctly")
|
|
405
|
+
|
|
406
|
+
execenv.print(f"{test_signal_copy.__doc__}: OK")
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
def test_coordinate_conversion() -> None:
|
|
410
|
+
"""Test physical_to_indices and indices_to_physical methods"""
|
|
411
|
+
execenv.print(f"{test_coordinate_conversion.__doc__}:")
|
|
412
|
+
|
|
413
|
+
# Create test signals with different x-coordinate patterns
|
|
414
|
+
n = 100
|
|
415
|
+
|
|
416
|
+
# ==================== Test 1: Uniform spacing ====================
|
|
417
|
+
execenv.print(" Test 1: Uniform spacing - basic conversion")
|
|
418
|
+
x_uniform = np.linspace(0.0, 10.0, n)
|
|
419
|
+
y_uniform = np.sin(x_uniform)
|
|
420
|
+
signal_uniform = sigima.objects.create_signal(
|
|
421
|
+
title="Uniform Spacing Test", x=x_uniform, y=y_uniform
|
|
422
|
+
)
|
|
423
|
+
|
|
424
|
+
# Test forward conversion (physical → indices)
|
|
425
|
+
# Since SignalObj uses argmin to find closest x, we test with exact x values
|
|
426
|
+
test_coords = [0.0, 5.0, 10.0]
|
|
427
|
+
indices = signal_uniform.physical_to_indices(test_coords)
|
|
428
|
+
assert len(indices) == 3
|
|
429
|
+
assert indices[0] == 0 # Closest to x[0] = 0.0
|
|
430
|
+
assert indices[1] == 49 # Closest to x[49] ≈ 5.0 (for n=100, linspace 0-10)
|
|
431
|
+
assert indices[2] == 99 # Closest to x[99] = 10.0
|
|
432
|
+
execenv.print(" ✓ Forward conversion (physical → indices) correct")
|
|
433
|
+
|
|
434
|
+
# Test backward conversion (indices → physical)
|
|
435
|
+
test_indices = [0, 49, 99]
|
|
436
|
+
coords = signal_uniform.indices_to_physical(test_indices)
|
|
437
|
+
assert len(coords) == 3
|
|
438
|
+
np.testing.assert_allclose(coords[0], 0.0, rtol=1e-10)
|
|
439
|
+
np.testing.assert_allclose(coords[1], 5.0, rtol=0.02) # ~1% tolerance
|
|
440
|
+
np.testing.assert_allclose(coords[2], 10.0, rtol=1e-10)
|
|
441
|
+
execenv.print(" ✓ Backward conversion (indices → physical) correct")
|
|
442
|
+
|
|
443
|
+
# Test round-trip accuracy
|
|
444
|
+
execenv.print(" Test 2: Uniform spacing - round-trip accuracy")
|
|
445
|
+
# Use exact x values for perfect round-trip
|
|
446
|
+
original_coords = [x_uniform[10], x_uniform[50], x_uniform[80]]
|
|
447
|
+
indices_rt = signal_uniform.physical_to_indices(original_coords)
|
|
448
|
+
recovered_coords = signal_uniform.indices_to_physical(indices_rt)
|
|
449
|
+
np.testing.assert_allclose(recovered_coords, original_coords, rtol=1e-10)
|
|
450
|
+
execenv.print(" ✓ Round-trip (physical → indices → physical) preserves values")
|
|
451
|
+
|
|
452
|
+
# ==================== Test 3: Non-uniform spacing ====================
|
|
453
|
+
execenv.print(" Test 3: Non-uniform spacing - logarithmic")
|
|
454
|
+
x_log = np.logspace(0, 2, n) # 1 to 100, logarithmic spacing
|
|
455
|
+
y_log = np.sin(x_log)
|
|
456
|
+
signal_log = sigima.objects.create_signal(
|
|
457
|
+
title="Logarithmic Spacing Test", x=x_log, y=y_log
|
|
458
|
+
)
|
|
459
|
+
|
|
460
|
+
# Test with exact x values
|
|
461
|
+
test_coords_log = [x_log[0], x_log[50], x_log[99]]
|
|
462
|
+
indices_log = signal_log.physical_to_indices(test_coords_log)
|
|
463
|
+
assert indices_log[0] == 0
|
|
464
|
+
assert indices_log[1] == 50
|
|
465
|
+
assert indices_log[2] == 99
|
|
466
|
+
execenv.print(" ✓ Non-uniform forward conversion correct")
|
|
467
|
+
|
|
468
|
+
# Test backward conversion
|
|
469
|
+
coords_log = signal_log.indices_to_physical([0, 50, 99])
|
|
470
|
+
np.testing.assert_allclose(coords_log[0], x_log[0], rtol=1e-10)
|
|
471
|
+
np.testing.assert_allclose(coords_log[1], x_log[50], rtol=1e-10)
|
|
472
|
+
np.testing.assert_allclose(coords_log[2], x_log[99], rtol=1e-10)
|
|
473
|
+
execenv.print(" ✓ Non-uniform backward conversion correct")
|
|
474
|
+
|
|
475
|
+
# ==================== Test 4: Finding closest value ====================
|
|
476
|
+
execenv.print(" Test 4: Finding closest value (argmin behavior)")
|
|
477
|
+
# Test that physical_to_indices finds the closest x value
|
|
478
|
+
# For uniform spacing, test a value between grid points
|
|
479
|
+
test_val = 5.05 # Between x[49] and x[50]
|
|
480
|
+
idx = signal_uniform.physical_to_indices([test_val])
|
|
481
|
+
# Should return index of closest value
|
|
482
|
+
expected_idx = np.abs(x_uniform - test_val).argmin()
|
|
483
|
+
assert idx[0] == expected_idx
|
|
484
|
+
execenv.print(" ✓ Finds closest x value correctly (argmin)")
|
|
485
|
+
|
|
486
|
+
# Test with multiple values not on grid
|
|
487
|
+
test_vals = [1.23, 4.56, 7.89]
|
|
488
|
+
indices_approx = signal_uniform.physical_to_indices(test_vals)
|
|
489
|
+
for i, val in enumerate(test_vals):
|
|
490
|
+
expected = np.abs(x_uniform - val).argmin()
|
|
491
|
+
assert indices_approx[i] == expected
|
|
492
|
+
execenv.print(" ✓ Multiple approximate values handled correctly")
|
|
493
|
+
|
|
494
|
+
# ==================== Test 5: Quadratic spacing ====================
|
|
495
|
+
execenv.print(" Test 5: Non-uniform spacing - quadratic")
|
|
496
|
+
x_quad = np.linspace(0, 1, n) ** 2 * 100 # Quadratic spacing, denser near 0
|
|
497
|
+
y_quad = np.exp(-x_quad / 10)
|
|
498
|
+
signal_quad = sigima.objects.create_signal(
|
|
499
|
+
title="Quadratic Spacing Test", x=x_quad, y=y_quad
|
|
500
|
+
)
|
|
501
|
+
|
|
502
|
+
# Round-trip test with exact values
|
|
503
|
+
test_indices_quad = [0, 25, 50, 75, 99]
|
|
504
|
+
coords_quad = signal_quad.indices_to_physical(test_indices_quad)
|
|
505
|
+
indices_back = signal_quad.physical_to_indices(coords_quad)
|
|
506
|
+
assert indices_back == test_indices_quad
|
|
507
|
+
execenv.print(" ✓ Round-trip for quadratic spacing preserves indices")
|
|
508
|
+
|
|
509
|
+
# ==================== Test 6: Edge cases ====================
|
|
510
|
+
execenv.print(" Test 6: Edge cases")
|
|
511
|
+
|
|
512
|
+
# Empty coordinate list
|
|
513
|
+
empty_coords = []
|
|
514
|
+
empty_indices = signal_uniform.physical_to_indices(empty_coords)
|
|
515
|
+
assert len(empty_indices) == 0
|
|
516
|
+
execenv.print(" ✓ Empty coordinate list handled")
|
|
517
|
+
|
|
518
|
+
# Single point
|
|
519
|
+
single_coord = [5.0]
|
|
520
|
+
single_idx = signal_uniform.physical_to_indices(single_coord)
|
|
521
|
+
assert len(single_idx) == 1
|
|
522
|
+
execenv.print(" ✓ Single point conversion works")
|
|
523
|
+
|
|
524
|
+
# Multiple points
|
|
525
|
+
multi_coords = [0.0, 2.5, 5.0, 7.5, 10.0]
|
|
526
|
+
multi_idx = signal_uniform.physical_to_indices(multi_coords)
|
|
527
|
+
assert len(multi_idx) == 5
|
|
528
|
+
execenv.print(" ✓ Multiple points conversion works")
|
|
529
|
+
|
|
530
|
+
# Boundary values
|
|
531
|
+
boundary_coords = [x_uniform[0], x_uniform[-1]]
|
|
532
|
+
boundary_idx = signal_uniform.physical_to_indices(boundary_coords)
|
|
533
|
+
assert boundary_idx[0] == 0
|
|
534
|
+
assert boundary_idx[1] == n - 1
|
|
535
|
+
execenv.print(" ✓ Boundary values handled correctly")
|
|
536
|
+
|
|
537
|
+
# ==================== Test 7: Out-of-range values ====================
|
|
538
|
+
execenv.print(" Test 7: Out-of-range values")
|
|
539
|
+
# Values outside the x range should map to closest endpoint
|
|
540
|
+
out_of_range = [-100.0, 200.0]
|
|
541
|
+
out_idx = signal_uniform.physical_to_indices(out_of_range)
|
|
542
|
+
assert out_idx[0] == 0 # Closest to minimum x
|
|
543
|
+
assert out_idx[1] == n - 1 # Closest to maximum x
|
|
544
|
+
execenv.print(" ✓ Out-of-range values map to closest endpoint")
|
|
545
|
+
|
|
546
|
+
# ==================== Test 8: Complex y data ====================
|
|
547
|
+
execenv.print(" Test 8: Complex y data")
|
|
548
|
+
# SignalObj can have complex y values, but x is always real
|
|
549
|
+
x_complex = np.linspace(0, 2 * np.pi, n)
|
|
550
|
+
y_complex = np.exp(1j * x_complex) # Complex exponential
|
|
551
|
+
signal_complex = sigima.objects.create_signal(
|
|
552
|
+
title="Complex Signal Test", x=x_complex, y=y_complex
|
|
553
|
+
)
|
|
554
|
+
|
|
555
|
+
# Test that coordinate conversion still works with complex y
|
|
556
|
+
test_coords_complex = [0.0, np.pi, 2 * np.pi]
|
|
557
|
+
indices_complex = signal_complex.physical_to_indices(test_coords_complex)
|
|
558
|
+
assert len(indices_complex) == 3
|
|
559
|
+
# Verify we can recover coordinates
|
|
560
|
+
coords_complex = signal_complex.indices_to_physical(indices_complex)
|
|
561
|
+
np.testing.assert_allclose(coords_complex, test_coords_complex, rtol=0.02)
|
|
562
|
+
execenv.print(" ✓ Complex y data doesn't affect coordinate conversion")
|
|
563
|
+
|
|
564
|
+
# ==================== Test 9: Dense data near specific region ====================
|
|
565
|
+
execenv.print(" Test 9: Non-uniform spacing - dense region")
|
|
566
|
+
# Create a signal with very dense sampling in middle region
|
|
567
|
+
x_dense = np.concatenate(
|
|
568
|
+
[
|
|
569
|
+
np.linspace(0, 1, 10), # Sparse
|
|
570
|
+
np.linspace(1, 2, 70), # Dense
|
|
571
|
+
np.linspace(2, 3, 10), # Sparse
|
|
572
|
+
]
|
|
573
|
+
)
|
|
574
|
+
y_dense = np.sin(x_dense * 2 * np.pi)
|
|
575
|
+
signal_dense = sigima.objects.create_signal(
|
|
576
|
+
title="Dense Region Test", x=x_dense, y=y_dense
|
|
577
|
+
)
|
|
578
|
+
|
|
579
|
+
# Test conversion in dense region
|
|
580
|
+
dense_coords = [1.5] # Middle of dense region
|
|
581
|
+
dense_idx = signal_dense.physical_to_indices(dense_coords)
|
|
582
|
+
recovered = signal_dense.indices_to_physical(dense_idx)
|
|
583
|
+
# Should find a very close match due to dense sampling
|
|
584
|
+
assert abs(recovered[0] - 1.5) < 0.02
|
|
585
|
+
execenv.print(" ✓ Dense sampling region handled correctly")
|
|
586
|
+
|
|
587
|
+
execenv.print(f"{test_coordinate_conversion.__doc__}: OK")
|
|
588
|
+
|
|
589
|
+
|
|
590
|
+
def run_all_tests() -> None:
|
|
591
|
+
"""Run all tests in this module"""
|
|
592
|
+
test_signal_parameters_interactive()
|
|
593
|
+
test_all_signal_types()
|
|
594
|
+
for fname, orig_signal in read_test_objects(SignalIORegistry):
|
|
595
|
+
test_hdf5_signal_io(fname, orig_signal)
|
|
596
|
+
test_create_signal()
|
|
597
|
+
test_create_signal_from_param()
|
|
598
|
+
test_signal_copy()
|
|
599
|
+
test_coordinate_conversion()
|
|
600
|
+
|
|
601
|
+
|
|
602
|
+
if __name__ == "__main__":
|
|
603
|
+
run_all_tests()
|