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,60 @@
|
|
|
1
|
+
# Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Unit tests for :py:mod:`sigima.tools.coordinates`.
|
|
5
|
+
|
|
6
|
+
This module verifies the correctness of coordinate conversion functions.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
import pytest
|
|
11
|
+
|
|
12
|
+
from sigima.tests.helpers import check_array_result
|
|
13
|
+
from sigima.tools.coordinates import polar_to_complex
|
|
14
|
+
|
|
15
|
+
polar_to_complex_parameters = [
|
|
16
|
+
(
|
|
17
|
+
np.array([1.0, 2.0, 3.0]),
|
|
18
|
+
np.array([0.0, 90.0, 180.0]),
|
|
19
|
+
"°",
|
|
20
|
+
np.array([1 + 0j, 0 + 2j, -3 + 0j]),
|
|
21
|
+
),
|
|
22
|
+
(
|
|
23
|
+
np.array([1.0, 2.0, 3.0]),
|
|
24
|
+
np.array([0.0, np.pi / 2, np.pi]),
|
|
25
|
+
"rad",
|
|
26
|
+
np.array([1 + 0j, 0 + 2j, -3 + 0j]),
|
|
27
|
+
),
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@pytest.mark.parametrize("r, theta, unit, expected", polar_to_complex_parameters)
|
|
32
|
+
def test_polar_to_complex(r, theta, unit, expected):
|
|
33
|
+
"""Test :py:func:`sigima.tools.polar_to_complex` with valid input.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
r: The radial coordinates.
|
|
37
|
+
theta: The angular coordinates.
|
|
38
|
+
unit: The unit of the angular coordinates ("°" or "rad").
|
|
39
|
+
expected: The expected complex coordinates.
|
|
40
|
+
"""
|
|
41
|
+
z = polar_to_complex(r, theta, unit=unit)
|
|
42
|
+
check_array_result(f"polar_to_complex_{unit}", z, expected)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def test_polar_to_complex_invalid_unit():
|
|
46
|
+
"""Test :py:func:`sigima.tools.polar_to_complex` with invalid unit.
|
|
47
|
+
|
|
48
|
+
Ensure :py:func:`sigima.tools.polar_to_complex` raises ValueError for unsupported
|
|
49
|
+
unit.
|
|
50
|
+
"""
|
|
51
|
+
r = np.array([1.0])
|
|
52
|
+
theta = np.array([0.0])
|
|
53
|
+
with pytest.raises(ValueError):
|
|
54
|
+
polar_to_complex(r, theta, unit="foo")
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
if __name__ == "__main__":
|
|
58
|
+
for parameters in polar_to_complex_parameters:
|
|
59
|
+
test_polar_to_complex(*parameters)
|
|
60
|
+
test_polar_to_complex_invalid_unit()
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Unit tests for transformations module
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
|
|
11
|
+
from sigima.objects.scalar import GeometryResult, KindShape
|
|
12
|
+
from sigima.objects.shape import PointCoordinates
|
|
13
|
+
from sigima.proc.image.transformations import GeometryTransformer, transformer
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def test_geometry_transformer_singleton() -> None:
|
|
17
|
+
"""
|
|
18
|
+
Test that GeometryTransformer follows singleton pattern.
|
|
19
|
+
"""
|
|
20
|
+
t1 = GeometryTransformer()
|
|
21
|
+
t2 = GeometryTransformer()
|
|
22
|
+
assert t1 is t2
|
|
23
|
+
assert t1 is transformer
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def test_transform_geometry_result_point() -> None:
|
|
27
|
+
"""
|
|
28
|
+
Test transformation of GeometryResult with POINT coordinates.
|
|
29
|
+
"""
|
|
30
|
+
# Create a GeometryResult with point coordinates
|
|
31
|
+
coords = np.array([[1.0, 2.0], [3.0, 4.0]])
|
|
32
|
+
geometry = GeometryResult(
|
|
33
|
+
title="Test Points",
|
|
34
|
+
kind=KindShape.POINT,
|
|
35
|
+
coords=coords,
|
|
36
|
+
roi_indices=None,
|
|
37
|
+
attrs={},
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
# Test rotation
|
|
41
|
+
rotated = transformer.rotate(geometry, np.pi / 2, center=(0, 0))
|
|
42
|
+
expected_coords = np.array([[-2.0, 1.0], [-4.0, 3.0]])
|
|
43
|
+
assert np.allclose(rotated.coords, expected_coords)
|
|
44
|
+
assert rotated.title == geometry.title
|
|
45
|
+
assert rotated.kind == geometry.kind
|
|
46
|
+
|
|
47
|
+
# Test translation
|
|
48
|
+
translated = transformer.translate(geometry, 10.0, 20.0)
|
|
49
|
+
expected_coords = np.array([[11.0, 22.0], [13.0, 24.0]])
|
|
50
|
+
assert np.allclose(translated.coords, expected_coords)
|
|
51
|
+
|
|
52
|
+
# Original should be unchanged
|
|
53
|
+
assert np.allclose(geometry.coords, coords)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def test_transform_geometry_result_rectangle() -> None:
|
|
57
|
+
"""
|
|
58
|
+
Test transformation of GeometryResult with RECTANGLE coordinates.
|
|
59
|
+
"""
|
|
60
|
+
# Create a GeometryResult with rectangle coordinates (x0, y0, dx, dy)
|
|
61
|
+
coords = np.array([[0.0, 0.0, 3.0, 1.0], [10.0, 10.0, 2.0, 4.0]])
|
|
62
|
+
geometry = GeometryResult(
|
|
63
|
+
title="Test Rectangles",
|
|
64
|
+
kind=KindShape.RECTANGLE,
|
|
65
|
+
coords=coords,
|
|
66
|
+
roi_indices=None,
|
|
67
|
+
attrs={},
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
# Test horizontal flip around x=1
|
|
71
|
+
flipped = transformer.fliph(geometry, cx=1.0)
|
|
72
|
+
expected_coords = np.array([[-1.0, 0.0, 3.0, 1.0], [-10.0, 10.0, 2.0, 4.0]])
|
|
73
|
+
assert np.allclose(flipped.coords, expected_coords)
|
|
74
|
+
|
|
75
|
+
# Test transpose
|
|
76
|
+
transposed = transformer.transpose(geometry)
|
|
77
|
+
expected_coords = np.array([[0.0, 0.0, 1.0, 3.0], [10.0, 10.0, 4.0, 2.0]])
|
|
78
|
+
assert np.allclose(transposed.coords, expected_coords)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def test_transform_geometry_result_circle() -> None:
|
|
82
|
+
"""
|
|
83
|
+
Test transformation of GeometryResult with CIRCLE coordinates.
|
|
84
|
+
"""
|
|
85
|
+
# Create a GeometryResult with circle coordinates
|
|
86
|
+
coords = np.array([[1.0, 2.0, 5.0], [10.0, 20.0, 10.0]])
|
|
87
|
+
geometry = GeometryResult(
|
|
88
|
+
title="Test Circles",
|
|
89
|
+
kind=KindShape.CIRCLE,
|
|
90
|
+
coords=coords,
|
|
91
|
+
roi_indices=None,
|
|
92
|
+
attrs={},
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
# Test scaling (only center should be scaled, radius unchanged)
|
|
96
|
+
scaled = transformer.scale(geometry, 2.0, 3.0, center=(0, 0))
|
|
97
|
+
expected_coords = np.array([[2.0, 6.0, 5.0], [20.0, 60.0, 10.0]])
|
|
98
|
+
assert np.allclose(scaled.coords, expected_coords)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def test_geometry_transformer_generic_methods() -> None:
|
|
102
|
+
"""
|
|
103
|
+
Test generic transform_geometry method.
|
|
104
|
+
"""
|
|
105
|
+
coords = np.array([[1.0, 2.0]])
|
|
106
|
+
geometry = GeometryResult(
|
|
107
|
+
title="Test Point",
|
|
108
|
+
kind=KindShape.POINT,
|
|
109
|
+
coords=coords,
|
|
110
|
+
roi_indices=None,
|
|
111
|
+
attrs={},
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
# Test generic method
|
|
115
|
+
rotated = transformer.transform_geometry(
|
|
116
|
+
geometry, "rotate", angle=np.pi / 2, center=(0, 0)
|
|
117
|
+
)
|
|
118
|
+
expected_coords = np.array([[-2.0, 1.0]])
|
|
119
|
+
assert np.allclose(rotated.coords, expected_coords)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def test_unsupported_geometry_kind() -> None:
|
|
123
|
+
"""
|
|
124
|
+
Test error handling for unsupported geometry kinds.
|
|
125
|
+
"""
|
|
126
|
+
# Create invalid geometry kind (this would need to be added to KindShape enum)
|
|
127
|
+
try:
|
|
128
|
+
# This should raise an error if we had an unsupported kind
|
|
129
|
+
# For now, all defined kinds are supported
|
|
130
|
+
pass
|
|
131
|
+
except ValueError:
|
|
132
|
+
pass
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def test_unsupported_operation() -> None:
|
|
136
|
+
"""
|
|
137
|
+
Test error handling for unsupported operations.
|
|
138
|
+
"""
|
|
139
|
+
coords = np.array([[1.0, 2.0]])
|
|
140
|
+
geometry = GeometryResult(
|
|
141
|
+
title="Test Point",
|
|
142
|
+
kind=KindShape.POINT,
|
|
143
|
+
coords=coords,
|
|
144
|
+
roi_indices=None,
|
|
145
|
+
attrs={},
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
try:
|
|
149
|
+
transformer.transform_geometry(geometry, "invalid_operation")
|
|
150
|
+
assert False, "Should have raised ValueError"
|
|
151
|
+
except ValueError as e:
|
|
152
|
+
assert "Unknown operation" in str(e)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def test_direct_coordinate_transformation() -> None:
|
|
156
|
+
"""
|
|
157
|
+
Test that transformations use the shape coordinate system correctly.
|
|
158
|
+
"""
|
|
159
|
+
# Test that our transformer produces the same results as direct shape
|
|
160
|
+
# coordinate usage
|
|
161
|
+
coords = np.array([[2.0, 3.0]])
|
|
162
|
+
geometry = GeometryResult(
|
|
163
|
+
title="Test Point",
|
|
164
|
+
kind=KindShape.POINT,
|
|
165
|
+
coords=coords,
|
|
166
|
+
roi_indices=None,
|
|
167
|
+
attrs={},
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
# Transform using transformer
|
|
171
|
+
rotated_geometry = transformer.rotate(geometry, np.pi / 2, center=(1, 1))
|
|
172
|
+
|
|
173
|
+
# Transform using shape coordinates directly
|
|
174
|
+
shape_coords = PointCoordinates([2.0, 3.0])
|
|
175
|
+
shape_coords.rotate(np.pi / 2, center=(1, 1))
|
|
176
|
+
|
|
177
|
+
# Results should be identical
|
|
178
|
+
assert np.allclose(rotated_geometry.coords[0], shape_coords.data)
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
# Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Testing validation test introspection and CSV generation.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os.path as osp
|
|
8
|
+
|
|
9
|
+
import sigima.tests as tests_pkg
|
|
10
|
+
from sigima.proc.decorator import find_computation_functions
|
|
11
|
+
from sigima.proc.validation import (
|
|
12
|
+
ValidationStatistics,
|
|
13
|
+
generate_valid_test_names_for_function,
|
|
14
|
+
get_validation_tests,
|
|
15
|
+
)
|
|
16
|
+
from sigima.tests.helpers import WorkdirRestoringTempDir
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def __generate_all_valid_test_names() -> set[str]:
|
|
20
|
+
"""Generate all valid test names for all computation functions.
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
Set of all valid test names that could test computation functions
|
|
24
|
+
"""
|
|
25
|
+
computation_functions = find_computation_functions()
|
|
26
|
+
valid_test_names = set()
|
|
27
|
+
|
|
28
|
+
for module_name, func_name, _ in computation_functions:
|
|
29
|
+
names = generate_valid_test_names_for_function(module_name, func_name)
|
|
30
|
+
valid_test_names.update(names)
|
|
31
|
+
|
|
32
|
+
return valid_test_names
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def test_validation_statistics() -> None:
|
|
36
|
+
"""Test validation statistics introspection and CSV generation."""
|
|
37
|
+
stats = ValidationStatistics()
|
|
38
|
+
stats.collect_validation_status(verbose=True)
|
|
39
|
+
stats.get_validation_info()
|
|
40
|
+
with WorkdirRestoringTempDir() as tmpdir:
|
|
41
|
+
stats.generate_csv_files(tmpdir)
|
|
42
|
+
stats.generate_statistics_csv(tmpdir)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def test_validation_missing_tests() -> None:
|
|
46
|
+
"""Test that all computation functions have validation tests.
|
|
47
|
+
|
|
48
|
+
This test ensures that all computation functions (those decorated with
|
|
49
|
+
@computation_function) have corresponding validation tests
|
|
50
|
+
marked with @pytest.mark.validation.
|
|
51
|
+
"""
|
|
52
|
+
# Get all functions marked with @pytest.mark.validation
|
|
53
|
+
validation_tests = get_validation_tests(tests_pkg)
|
|
54
|
+
|
|
55
|
+
# Get all computation functions that should have validation tests
|
|
56
|
+
computation_functions = find_computation_functions()
|
|
57
|
+
required_functions = [
|
|
58
|
+
(module_name, func_name) for module_name, func_name, _ in computation_functions
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
# Check each required function to see if it has a corresponding validation test
|
|
62
|
+
missing_validation_tests = []
|
|
63
|
+
|
|
64
|
+
for module_name, func_name in required_functions:
|
|
65
|
+
valid_test_names = generate_valid_test_names_for_function(
|
|
66
|
+
module_name, func_name
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
# Check if any of the valid test names exist in validation tests
|
|
70
|
+
has_validation_test = any(
|
|
71
|
+
test_name in [vt[0] for vt in validation_tests]
|
|
72
|
+
for test_name in valid_test_names
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
if not has_validation_test:
|
|
76
|
+
missing_validation_tests.append(f"{module_name}.{func_name}")
|
|
77
|
+
|
|
78
|
+
# Report any missing validation tests
|
|
79
|
+
if missing_validation_tests:
|
|
80
|
+
error_messages = []
|
|
81
|
+
error_messages.append(
|
|
82
|
+
"The following computation functions are missing "
|
|
83
|
+
"validation tests marked with @pytest.mark.validation:"
|
|
84
|
+
)
|
|
85
|
+
for func_name in missing_validation_tests:
|
|
86
|
+
error_messages.append(f" - {func_name}")
|
|
87
|
+
error_messages.append("")
|
|
88
|
+
error_messages.append(f"Found {len(missing_validation_tests)} missing cases.")
|
|
89
|
+
error_messages.append(
|
|
90
|
+
"Please add validation tests for these computation functions."
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
raise AssertionError("\n".join(error_messages))
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def test_validation_decorator_only_on_computation_functions() -> None:
|
|
97
|
+
"""Test that @pytest.mark.validation is only used on computation function tests.
|
|
98
|
+
|
|
99
|
+
This test ensures that validation tests marked with @pytest.mark.validation
|
|
100
|
+
are only used for testing actual computation functions (those decorated with
|
|
101
|
+
@computation_function). Test functions for non-computation functions (like
|
|
102
|
+
I/O convenience functions) should not have this decorator.
|
|
103
|
+
"""
|
|
104
|
+
# Get all functions marked with @pytest.mark.validation
|
|
105
|
+
validation_tests = get_validation_tests(tests_pkg)
|
|
106
|
+
|
|
107
|
+
# Get all valid test names for computation functions
|
|
108
|
+
valid_test_names = __generate_all_valid_test_names()
|
|
109
|
+
|
|
110
|
+
# Check each validation test to see if it corresponds to a computation function
|
|
111
|
+
invalid_validation_tests = []
|
|
112
|
+
|
|
113
|
+
for test_name, test_path, line_number in validation_tests:
|
|
114
|
+
if test_name not in valid_test_names:
|
|
115
|
+
# This validation test doesn't correspond to any computation function
|
|
116
|
+
rel_path = osp.relpath(test_path, start=osp.dirname(tests_pkg.__file__))
|
|
117
|
+
module_parts = rel_path.replace(osp.sep, ".").replace(".py", "")
|
|
118
|
+
module_name = f"sigima.tests.{module_parts}"
|
|
119
|
+
invalid_validation_tests.append((test_name, module_name, line_number))
|
|
120
|
+
|
|
121
|
+
# Report any invalid validation tests
|
|
122
|
+
if invalid_validation_tests:
|
|
123
|
+
error_messages = []
|
|
124
|
+
error_messages.append(
|
|
125
|
+
"Found @pytest.mark.validation decorator on tests that don't test "
|
|
126
|
+
"computation functions:"
|
|
127
|
+
)
|
|
128
|
+
for test_name, module_name, line_number in invalid_validation_tests:
|
|
129
|
+
# Convert module path back to file path for clickable links
|
|
130
|
+
file_path = (
|
|
131
|
+
module_name.replace("sigima.tests.", "").replace(".", "\\") + ".py"
|
|
132
|
+
)
|
|
133
|
+
error_messages.append(f" - {file_path}:{line_number} ({test_name})")
|
|
134
|
+
error_messages.append("")
|
|
135
|
+
error_messages.append(f"Found {len(invalid_validation_tests)} invalid cases.")
|
|
136
|
+
error_messages.append(
|
|
137
|
+
"The @pytest.mark.validation decorator should only be used on "
|
|
138
|
+
"test functions that test computation functions (those decorated with "
|
|
139
|
+
"@computation_function). Please remove this decorator from test functions "
|
|
140
|
+
"that test non-computation functions."
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
raise AssertionError("\n".join(error_messages))
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def test_computation_functions_documented_in_features() -> None:
|
|
147
|
+
"""Test that all computation functions are documented in doc/features.rst.
|
|
148
|
+
|
|
149
|
+
This test ensures that all computation functions (those decorated with
|
|
150
|
+
@computation_function) are documented in the features.rst file with
|
|
151
|
+
proper Sphinx :func: references using simplified paths like
|
|
152
|
+
sigima.proc.image.function_name or sigima.proc.signal.function_name.
|
|
153
|
+
"""
|
|
154
|
+
# Read the features.rst file
|
|
155
|
+
doc_dir = osp.join(osp.dirname(tests_pkg.__file__), "..", "..", "doc")
|
|
156
|
+
features_rst_path = osp.join(doc_dir, "user_guide", "features.rst")
|
|
157
|
+
|
|
158
|
+
if not osp.exists(features_rst_path):
|
|
159
|
+
raise AssertionError(f"Documentation file not found: {features_rst_path}")
|
|
160
|
+
|
|
161
|
+
with open(features_rst_path, encoding="utf-8") as f:
|
|
162
|
+
features_content = f.read()
|
|
163
|
+
|
|
164
|
+
# Get all computation functions
|
|
165
|
+
computation_functions = find_computation_functions()
|
|
166
|
+
|
|
167
|
+
# Check each computation function to see if it's documented
|
|
168
|
+
missing_documentation = []
|
|
169
|
+
|
|
170
|
+
for module_name, func_name, _ in computation_functions:
|
|
171
|
+
# Build the expected documentation reference using simplified path
|
|
172
|
+
# The module_name is like "sigima.proc.image" or "sigima.proc.signal"
|
|
173
|
+
module_path = module_name.split("sigima.proc.")[-1]
|
|
174
|
+
expected_ref = f"sigima.proc.{module_path}.{func_name}"
|
|
175
|
+
|
|
176
|
+
# Check if this reference exists in the documentation
|
|
177
|
+
if expected_ref not in features_content:
|
|
178
|
+
missing_documentation.append((module_name, func_name, expected_ref))
|
|
179
|
+
|
|
180
|
+
# Report any missing documentation
|
|
181
|
+
if missing_documentation:
|
|
182
|
+
error_messages = []
|
|
183
|
+
error_messages.append(
|
|
184
|
+
"The following computation functions are missing from doc/features.rst:"
|
|
185
|
+
)
|
|
186
|
+
for module_name, func_name, expected_ref in missing_documentation:
|
|
187
|
+
error_messages.append(f" - {func_name} ({expected_ref})")
|
|
188
|
+
error_messages.append("")
|
|
189
|
+
error_messages.append(f"Found {len(missing_documentation)} missing cases.")
|
|
190
|
+
error_messages.append(
|
|
191
|
+
"Please add documentation references for these computation functions "
|
|
192
|
+
"in doc/features.rst using the format:"
|
|
193
|
+
)
|
|
194
|
+
error_messages.append(
|
|
195
|
+
" :func:`function_name <sigima.proc.module.function_name>`"
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
raise AssertionError("\n".join(error_messages))
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
if __name__ == "__main__":
|
|
202
|
+
test_validation_statistics()
|
|
203
|
+
test_validation_missing_tests()
|
|
204
|
+
test_validation_decorator_only_on_computation_functions()
|
|
205
|
+
test_computation_functions_documented_in_features()
|
sigima/tests/conftest.py
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
`sigima` pytest configuration
|
|
5
|
+
-----------------------------
|
|
6
|
+
|
|
7
|
+
This file contains the configuration for running pytest in `sigima`. It is
|
|
8
|
+
executed before running any tests.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import os
|
|
12
|
+
import os.path as osp
|
|
13
|
+
|
|
14
|
+
import guidata
|
|
15
|
+
import h5py
|
|
16
|
+
import numpy
|
|
17
|
+
import pytest
|
|
18
|
+
import scipy
|
|
19
|
+
import skimage
|
|
20
|
+
from guidata.config import ValidationMode, set_validation_mode
|
|
21
|
+
from guidata.utils.gitreport import format_git_info_for_pytest, get_git_info_for_modules
|
|
22
|
+
|
|
23
|
+
import sigima
|
|
24
|
+
from sigima.proc.validation import ValidationStatistics
|
|
25
|
+
from sigima.tests import SIGIMA_TESTS_GUI_ENV, env, helpers
|
|
26
|
+
|
|
27
|
+
# Set validation mode to STRICT for all tests
|
|
28
|
+
set_validation_mode(ValidationMode.STRICT)
|
|
29
|
+
|
|
30
|
+
# Turn on unattended mode for executing tests without user interaction
|
|
31
|
+
env.execenv.unattended = True
|
|
32
|
+
env.execenv.verbose = "quiet"
|
|
33
|
+
|
|
34
|
+
INITIAL_CWD = os.getcwd()
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def pytest_addoption(parser):
|
|
38
|
+
"""Add custom command line options to pytest."""
|
|
39
|
+
parser.addoption(
|
|
40
|
+
"--show-windows",
|
|
41
|
+
action="store_true",
|
|
42
|
+
default=False,
|
|
43
|
+
help="Display Qt windows during tests (disables QT_QPA_PLATFORM=offscreen)",
|
|
44
|
+
)
|
|
45
|
+
parser.addoption(
|
|
46
|
+
"--gui", action="store_true", default=False, help="Run tests that require a GUI"
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def pytest_report_header(config): # pylint: disable=unused-argument
|
|
51
|
+
"""Add additional information to the pytest report header."""
|
|
52
|
+
infolist = [
|
|
53
|
+
f"sigima {sigima.__version__}",
|
|
54
|
+
f" guidata {guidata.__version__},",
|
|
55
|
+
f" NumPy {numpy.__version__}, SciPy {scipy.__version__}, "
|
|
56
|
+
f"h5py {h5py.__version__}, scikit-image {skimage.__version__}",
|
|
57
|
+
]
|
|
58
|
+
try:
|
|
59
|
+
import cv2 # pylint: disable=import-outside-toplevel
|
|
60
|
+
|
|
61
|
+
infolist[-1] += f", OpenCV {cv2.__version__}"
|
|
62
|
+
except ImportError:
|
|
63
|
+
pass
|
|
64
|
+
envlist = []
|
|
65
|
+
for vname in ("SIGIMA_DATA", "PYTHONPATH", "DEBUG", "QT_API", "QT_QPA_PLATFORM"):
|
|
66
|
+
value = os.environ.get(vname, "")
|
|
67
|
+
if value:
|
|
68
|
+
if vname == "PYTHONPATH":
|
|
69
|
+
pathlist = value.split(os.pathsep)
|
|
70
|
+
envlist.append(f" {vname}:")
|
|
71
|
+
envlist.extend(f" {p}" for p in pathlist if p)
|
|
72
|
+
else:
|
|
73
|
+
envlist.append(f" {vname}: {value}")
|
|
74
|
+
if envlist:
|
|
75
|
+
infolist.append("Environment variables:")
|
|
76
|
+
infolist.extend(envlist)
|
|
77
|
+
infolist.append("Test paths:")
|
|
78
|
+
for test_path in helpers.get_test_paths():
|
|
79
|
+
test_path = osp.abspath(test_path)
|
|
80
|
+
infolist.append(f" {test_path}")
|
|
81
|
+
|
|
82
|
+
# Git information for all modules using the new gitreport module
|
|
83
|
+
modules_config = [
|
|
84
|
+
("Sigima", sigima, "."), # Sigima uses current directory
|
|
85
|
+
("guidata", guidata, None),
|
|
86
|
+
]
|
|
87
|
+
git_repos = get_git_info_for_modules(modules_config)
|
|
88
|
+
git_info_lines = format_git_info_for_pytest(git_repos, "Sigima")
|
|
89
|
+
if git_info_lines:
|
|
90
|
+
infolist.extend(git_info_lines)
|
|
91
|
+
|
|
92
|
+
infolist.extend(ValidationStatistics().get_validation_info())
|
|
93
|
+
return infolist
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def pytest_configure(config):
|
|
97
|
+
"""Add custom markers to pytest."""
|
|
98
|
+
if config.option.durations is None:
|
|
99
|
+
config.option.durations = 10 # Default to showing 10 slowest tests
|
|
100
|
+
config.addinivalue_line(
|
|
101
|
+
"markers",
|
|
102
|
+
"validation: mark a test as a validation test (ground truth or analytical)",
|
|
103
|
+
)
|
|
104
|
+
if not config.getoption("--show-windows"):
|
|
105
|
+
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
|
|
106
|
+
config.addinivalue_line("markers", "gui: mark test as requiring GUI")
|
|
107
|
+
if config.getoption("--gui"):
|
|
108
|
+
os.environ[SIGIMA_TESTS_GUI_ENV] = "1"
|
|
109
|
+
else:
|
|
110
|
+
os.environ.pop(SIGIMA_TESTS_GUI_ENV, None)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def pytest_collection_modifyitems(config, items):
|
|
114
|
+
"""Modify collected test items based on command line options."""
|
|
115
|
+
if config.getoption("--gui"):
|
|
116
|
+
return # User requested GUI tests
|
|
117
|
+
|
|
118
|
+
skip_gui = pytest.mark.skip(reason="GUI test: run with --gui")
|
|
119
|
+
|
|
120
|
+
for item in items:
|
|
121
|
+
if "gui" in item.keywords:
|
|
122
|
+
item.add_marker(skip_gui)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@pytest.fixture(autouse=True)
|
|
126
|
+
def reset_cwd(request): # pylint: disable=unused-argument
|
|
127
|
+
"""Reset the current working directory to the initial one after each test."""
|
|
128
|
+
yield
|
|
129
|
+
os.chdir(INITIAL_CWD)
|