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,654 @@
|
|
|
1
|
+
# Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Unit tests for geometry computation functions.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
import pytest
|
|
11
|
+
import scipy.ndimage as spi
|
|
12
|
+
|
|
13
|
+
import sigima.enums
|
|
14
|
+
import sigima.objects
|
|
15
|
+
import sigima.params
|
|
16
|
+
import sigima.proc.image
|
|
17
|
+
from sigima.tests.data import get_test_image, iterate_noisy_images
|
|
18
|
+
from sigima.tests.env import execenv
|
|
19
|
+
from sigima.tests.helpers import check_array_result, check_scalar_result
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@pytest.mark.validation
|
|
23
|
+
def test_image_translate() -> None:
|
|
24
|
+
"""Image translation test."""
|
|
25
|
+
for dx, dy in [(10, 0), (0, 10), (-10, -10)]:
|
|
26
|
+
compfunc = sigima.proc.image.translate
|
|
27
|
+
execenv.print(f"*** Testing image translate: {compfunc.__name__}")
|
|
28
|
+
ima1 = list(iterate_noisy_images(size=128))[0]
|
|
29
|
+
ima2: sigima.objects.ImageObj = compfunc(
|
|
30
|
+
ima1, sigima.params.TranslateParam.create(dx=dx, dy=dy)
|
|
31
|
+
)
|
|
32
|
+
check_scalar_result("Image X translation", ima2.x0, ima1.x0 + dx)
|
|
33
|
+
check_scalar_result("Image Y translation", ima2.y0, ima1.y0 + dy)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def __generic_flip_check(compfunc: callable, expfunc: callable) -> None:
|
|
37
|
+
"""Generic flip check function."""
|
|
38
|
+
execenv.print(f"*** Testing image flip: {compfunc.__name__}")
|
|
39
|
+
for ima1 in iterate_noisy_images(size=128):
|
|
40
|
+
execenv.print(f" {compfunc.__name__}({ima1.data.dtype}): ", end="")
|
|
41
|
+
ima2: sigima.objects.ImageObj = compfunc(ima1)
|
|
42
|
+
check_array_result("Image flip", ima2.data, expfunc(ima1.data))
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@pytest.mark.validation
|
|
46
|
+
def test_image_fliph() -> None:
|
|
47
|
+
"""Image horizontal flip test."""
|
|
48
|
+
__generic_flip_check(sigima.proc.image.fliph, np.fliplr)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@pytest.mark.validation
|
|
52
|
+
def test_image_flipv() -> None:
|
|
53
|
+
"""Image vertical flip test."""
|
|
54
|
+
__generic_flip_check(sigima.proc.image.flipv, np.flipud)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def __generic_rotate_check(angle: int) -> None:
|
|
58
|
+
"""Generic rotate check function."""
|
|
59
|
+
execenv.print(f"*** Testing image {angle}° rotation:")
|
|
60
|
+
for ima1 in iterate_noisy_images(size=128):
|
|
61
|
+
execenv.print(f" rotate{angle}({ima1.data.dtype}): ", end="")
|
|
62
|
+
ima2 = getattr(sigima.proc.image, f"rotate{angle}")(ima1)
|
|
63
|
+
check_array_result(
|
|
64
|
+
f"Image rotate{angle}", ima2.data, np.rot90(ima1.data, k=angle // 90)
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@pytest.mark.validation
|
|
69
|
+
def test_image_rotate90() -> None:
|
|
70
|
+
"""Image 90° rotation test."""
|
|
71
|
+
__generic_rotate_check(90)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@pytest.mark.validation
|
|
75
|
+
def test_image_rotate270() -> None:
|
|
76
|
+
"""Image 270° rotation test."""
|
|
77
|
+
__generic_rotate_check(270)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def __get_test_image_with_roi() -> sigima.objects.ImageObj:
|
|
81
|
+
"""Get a test image with a predefined ROI."""
|
|
82
|
+
ima = get_test_image("flower.npy")
|
|
83
|
+
ima.roi = sigima.objects.create_image_roi(
|
|
84
|
+
"rectangle", [10.0, 10.0, 50.0, 400.0], indices=False
|
|
85
|
+
)
|
|
86
|
+
return ima
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def __check_roi_properties(
|
|
90
|
+
ima1: sigima.objects.ImageObj, ima2: sigima.objects.ImageObj
|
|
91
|
+
) -> None:
|
|
92
|
+
"""Check that the ROI properties are preserved after transformation."""
|
|
93
|
+
assert ima2.roi.single_rois[0].title == ima1.roi.single_rois[0].title
|
|
94
|
+
assert ima2.roi.single_rois[0].indices == ima1.roi.single_rois[0].indices
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def test_roi_rotate90() -> None:
|
|
98
|
+
"""Test 90° rotation with ROI transformation."""
|
|
99
|
+
ima = __get_test_image_with_roi()
|
|
100
|
+
|
|
101
|
+
# Apply 90° rotation
|
|
102
|
+
rotated = sigima.proc.image.rotate90(ima)
|
|
103
|
+
|
|
104
|
+
# Check that ROI coordinates were transformed correctly
|
|
105
|
+
# Original: [10, 10, 50, 400] -> Expected: [10, ima.height - 10 - 50, 400, 50]
|
|
106
|
+
expected_coords = np.array([10.0, ima.height - 60.0, 400.0, 50.0])
|
|
107
|
+
actual_coords = rotated.roi.single_rois[0].coords
|
|
108
|
+
|
|
109
|
+
assert np.allclose(actual_coords, expected_coords), (
|
|
110
|
+
f"ROI coordinates not transformed correctly. "
|
|
111
|
+
f"Expected {expected_coords}, got {actual_coords}"
|
|
112
|
+
)
|
|
113
|
+
__check_roi_properties(ima, rotated)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def test_roi_rotate270() -> None:
|
|
117
|
+
"""Test 270° rotation with ROI transformation."""
|
|
118
|
+
ima = __get_test_image_with_roi()
|
|
119
|
+
|
|
120
|
+
# Apply 270° rotation
|
|
121
|
+
rotated = sigima.proc.image.rotate270(ima)
|
|
122
|
+
|
|
123
|
+
# Check that ROI coordinates were transformed correctly
|
|
124
|
+
# Original: [10, 10, 50, 400] -> Expected: [ima.width - 10 - 400, 10, 400, 50]
|
|
125
|
+
expected_coords = np.array([ima.width - 410.0, 10.0, 400.0, 50.0])
|
|
126
|
+
actual_coords = rotated.roi.single_rois[0].coords
|
|
127
|
+
|
|
128
|
+
assert np.allclose(actual_coords, expected_coords), (
|
|
129
|
+
f"ROI coordinates not transformed correctly. "
|
|
130
|
+
f"Expected {expected_coords}, got {actual_coords}"
|
|
131
|
+
)
|
|
132
|
+
__check_roi_properties(ima, rotated)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def test_roi_translation() -> None:
|
|
136
|
+
"""Test translation with ROI transformation."""
|
|
137
|
+
ima = __get_test_image_with_roi()
|
|
138
|
+
|
|
139
|
+
# Apply translation
|
|
140
|
+
translated = sigima.proc.image.translate(
|
|
141
|
+
ima, sigima.params.TranslateParam.create(dx=10, dy=10)
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
# Check that ROI coordinates were transformed correctly
|
|
145
|
+
# Original: [10, 10, 50, 400] -> Expected: [20, 20, 50, 400]
|
|
146
|
+
expected_coords = np.array([20.0, 20.0, 50.0, 400.0])
|
|
147
|
+
actual_coords = translated.roi.single_rois[0].coords
|
|
148
|
+
|
|
149
|
+
assert np.allclose(actual_coords, expected_coords), (
|
|
150
|
+
f"ROI coordinates not transformed correctly. "
|
|
151
|
+
f"Expected {expected_coords}, got {actual_coords}"
|
|
152
|
+
)
|
|
153
|
+
__check_roi_properties(ima, translated)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
@pytest.mark.validation
|
|
157
|
+
def test_image_rotate() -> None:
|
|
158
|
+
"""Image rotation test."""
|
|
159
|
+
execenv.print("*** Testing image rotation:")
|
|
160
|
+
for ima1 in iterate_noisy_images(size=128):
|
|
161
|
+
for angle in (30.0, 45.0, 60.0, 120.0):
|
|
162
|
+
execenv.print(f" rotate{angle}({ima1.data.dtype}): ", end="")
|
|
163
|
+
ima2 = sigima.proc.image.rotate(
|
|
164
|
+
ima1, sigima.params.RotateParam.create(angle=angle)
|
|
165
|
+
)
|
|
166
|
+
exp = spi.rotate(ima1.data, angle, reshape=False)
|
|
167
|
+
check_array_result(f"Image rotate{angle}", ima2.data, exp)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
@pytest.mark.validation
|
|
171
|
+
def test_image_transpose() -> None:
|
|
172
|
+
"""Validation test for the image transpose processing."""
|
|
173
|
+
src = get_test_image("flower.npy")
|
|
174
|
+
dst = sigima.proc.image.transpose(src)
|
|
175
|
+
exp = np.swapaxes(src.data, 0, 1)
|
|
176
|
+
check_array_result("Transpose", dst.data, exp)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
@pytest.mark.validation
|
|
180
|
+
def test_image_resampling() -> None:
|
|
181
|
+
"""Image resampling test."""
|
|
182
|
+
execenv.print("*** Testing image resampling")
|
|
183
|
+
|
|
184
|
+
# Create a test image
|
|
185
|
+
ima1 = get_test_image(
|
|
186
|
+
"flower.npy"
|
|
187
|
+
) # Test 1: Identity resampling (same dimensions and coordinate range)
|
|
188
|
+
p1 = sigima.params.Resampling2DParam.create(
|
|
189
|
+
mode="shape",
|
|
190
|
+
width=ima1.data.shape[1],
|
|
191
|
+
height=ima1.data.shape[0],
|
|
192
|
+
xmin=ima1.x0,
|
|
193
|
+
xmax=ima1.x0 + ima1.width,
|
|
194
|
+
ymin=ima1.y0,
|
|
195
|
+
ymax=ima1.y0 + ima1.height,
|
|
196
|
+
method=sigima.enums.Interpolation2DMethod.LINEAR,
|
|
197
|
+
)
|
|
198
|
+
dst1 = sigima.proc.image.resampling(ima1, p1)
|
|
199
|
+
|
|
200
|
+
# Should be very close to original (allowing for small interpolation differences)
|
|
201
|
+
check_scalar_result("Identity resampling X0", dst1.x0, ima1.x0)
|
|
202
|
+
check_scalar_result("Identity resampling Y0", dst1.y0, ima1.y0)
|
|
203
|
+
check_scalar_result(
|
|
204
|
+
"Identity resampling shape[0]", dst1.data.shape[0], ima1.data.shape[0]
|
|
205
|
+
)
|
|
206
|
+
check_scalar_result(
|
|
207
|
+
"Identity resampling shape[1]", dst1.data.shape[1], ima1.data.shape[1]
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
# Test 2: Downsample by factor of 2
|
|
211
|
+
p2 = sigima.params.Resampling2DParam.create(
|
|
212
|
+
mode="shape",
|
|
213
|
+
width=ima1.data.shape[1] // 2,
|
|
214
|
+
height=ima1.data.shape[0] // 2,
|
|
215
|
+
xmin=ima1.x0,
|
|
216
|
+
xmax=ima1.x0 + ima1.width,
|
|
217
|
+
ymin=ima1.y0,
|
|
218
|
+
ymax=ima1.y0 + ima1.height,
|
|
219
|
+
method=sigima.enums.Interpolation2DMethod.LINEAR,
|
|
220
|
+
)
|
|
221
|
+
dst2 = sigima.proc.image.resampling(ima1, p2)
|
|
222
|
+
|
|
223
|
+
check_scalar_result("Downsample X0", dst2.x0, ima1.x0)
|
|
224
|
+
check_scalar_result("Downsample Y0", dst2.y0, ima1.y0)
|
|
225
|
+
check_scalar_result(
|
|
226
|
+
"Downsample shape[0]", dst2.data.shape[0], ima1.data.shape[0] // 2
|
|
227
|
+
)
|
|
228
|
+
check_scalar_result(
|
|
229
|
+
"Downsample shape[1]", dst2.data.shape[1], ima1.data.shape[1] // 2
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
# Check that pixel sizes are adjusted correctly
|
|
233
|
+
expected_dx = ima1.dx * 2 if ima1.dx is not None else 2.0
|
|
234
|
+
expected_dy = ima1.dy * 2 if ima1.dy is not None else 2.0
|
|
235
|
+
check_scalar_result("Downsample dx", dst2.dx, expected_dx, rtol=1e-10)
|
|
236
|
+
check_scalar_result("Downsample dy", dst2.dy, expected_dy, rtol=1e-10)
|
|
237
|
+
|
|
238
|
+
# Test 3: Use pixel size mode
|
|
239
|
+
if ima1.dx is not None and ima1.dy is not None:
|
|
240
|
+
p3 = sigima.params.Resampling2DParam.create(
|
|
241
|
+
mode="dxy",
|
|
242
|
+
dx=ima1.dx * 1.5,
|
|
243
|
+
dy=ima1.dy * 1.5,
|
|
244
|
+
xmin=ima1.x0,
|
|
245
|
+
xmax=ima1.x0 + ima1.width,
|
|
246
|
+
ymin=ima1.y0,
|
|
247
|
+
ymax=ima1.y0 + ima1.height,
|
|
248
|
+
method=sigima.enums.Interpolation2DMethod.LINEAR,
|
|
249
|
+
)
|
|
250
|
+
dst3 = sigima.proc.image.resampling(ima1, p3)
|
|
251
|
+
|
|
252
|
+
check_scalar_result("Pixel size mode dx", dst3.dx, ima1.dx * 1.5, rtol=1e-10)
|
|
253
|
+
check_scalar_result("Pixel size mode dy", dst3.dy, ima1.dy * 1.5, rtol=1e-10)
|
|
254
|
+
|
|
255
|
+
# Test 4: Different interpolation methods
|
|
256
|
+
for method in sigima.enums.Interpolation2DMethod:
|
|
257
|
+
p4 = sigima.params.Resampling2DParam.create(
|
|
258
|
+
mode="shape",
|
|
259
|
+
width=ima1.data.shape[1] // 2,
|
|
260
|
+
height=ima1.data.shape[0] // 2,
|
|
261
|
+
xmin=ima1.x0,
|
|
262
|
+
xmax=ima1.x0 + ima1.width,
|
|
263
|
+
ymin=ima1.y0,
|
|
264
|
+
ymax=ima1.y0 + ima1.height,
|
|
265
|
+
method=method,
|
|
266
|
+
)
|
|
267
|
+
dst4 = sigima.proc.image.resampling(ima1, p4)
|
|
268
|
+
|
|
269
|
+
# Basic shape checks
|
|
270
|
+
check_scalar_result(
|
|
271
|
+
f"Method {method} shape[0]", dst4.data.shape[0], ima1.data.shape[0] // 2
|
|
272
|
+
)
|
|
273
|
+
check_scalar_result(
|
|
274
|
+
f"Method {method} shape[1]", dst4.data.shape[1], ima1.data.shape[1] // 2
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
# Test 5: fill_value parameter (out-of-bounds sampling)
|
|
278
|
+
execenv.print(" Testing fill_value parameter")
|
|
279
|
+
|
|
280
|
+
# Test 5a: Default behavior (fill_value=None should use NaN)
|
|
281
|
+
p5a = sigima.params.Resampling2DParam.create(
|
|
282
|
+
mode="shape",
|
|
283
|
+
width=20,
|
|
284
|
+
height=20,
|
|
285
|
+
xmin=600.0, # Outside image bounds
|
|
286
|
+
xmax=620.0,
|
|
287
|
+
ymin=600.0,
|
|
288
|
+
ymax=620.0,
|
|
289
|
+
method=sigima.enums.Interpolation2DMethod.LINEAR,
|
|
290
|
+
fill_value=None,
|
|
291
|
+
)
|
|
292
|
+
dst5a = sigima.proc.image.resampling(ima1, p5a)
|
|
293
|
+
|
|
294
|
+
# Should be all NaN since sampling outside image bounds
|
|
295
|
+
assert np.all(np.isnan(dst5a.data)), (
|
|
296
|
+
"Expected all NaN values for out-of-bounds sampling with fill_value=None"
|
|
297
|
+
)
|
|
298
|
+
assert dst5a.data.dtype == np.float64, "Expected float64 dtype for NaN result"
|
|
299
|
+
|
|
300
|
+
# Test 5b: Custom fill value
|
|
301
|
+
p5b = sigima.params.Resampling2DParam.create(
|
|
302
|
+
mode="shape",
|
|
303
|
+
width=20,
|
|
304
|
+
height=20,
|
|
305
|
+
xmin=600.0, # Outside image bounds
|
|
306
|
+
xmax=620.0,
|
|
307
|
+
ymin=600.0,
|
|
308
|
+
ymax=620.0,
|
|
309
|
+
method=sigima.enums.Interpolation2DMethod.LINEAR,
|
|
310
|
+
fill_value=123.0,
|
|
311
|
+
)
|
|
312
|
+
dst5b = sigima.proc.image.resampling(ima1, p5b)
|
|
313
|
+
|
|
314
|
+
# Should be all 123.0 since sampling outside image bounds
|
|
315
|
+
assert np.all(dst5b.data == 123.0), (
|
|
316
|
+
"Expected all fill values for out-of-bounds sampling"
|
|
317
|
+
)
|
|
318
|
+
assert dst5b.data.dtype == ima1.data.dtype, (
|
|
319
|
+
"Expected same dtype as input for numeric fill value"
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
# Test 5c: Partially outside (mix of real data and fill values)
|
|
323
|
+
p5c = sigima.params.Resampling2DParam.create(
|
|
324
|
+
mode="shape",
|
|
325
|
+
width=30,
|
|
326
|
+
height=30,
|
|
327
|
+
xmin=ima1.x0 + ima1.width - 10, # Partially outside
|
|
328
|
+
xmax=ima1.x0 + ima1.width + 20,
|
|
329
|
+
ymin=ima1.y0 + ima1.height - 10,
|
|
330
|
+
ymax=ima1.y0 + ima1.height + 20,
|
|
331
|
+
method=sigima.enums.Interpolation2DMethod.LINEAR,
|
|
332
|
+
fill_value=99.0,
|
|
333
|
+
)
|
|
334
|
+
dst5c = sigima.proc.image.resampling(ima1, p5c)
|
|
335
|
+
|
|
336
|
+
# Should have mix of values
|
|
337
|
+
fill_count = np.sum(dst5c.data == 99.0)
|
|
338
|
+
total_count = dst5c.data.size
|
|
339
|
+
assert fill_count > 0, "Expected some fill values for partially out-of-bounds"
|
|
340
|
+
assert fill_count < total_count, "Expected some real data values"
|
|
341
|
+
|
|
342
|
+
# Test 5d: Within bounds should not use fill value
|
|
343
|
+
p5d = sigima.params.Resampling2DParam.create(
|
|
344
|
+
mode="shape",
|
|
345
|
+
width=50,
|
|
346
|
+
height=50,
|
|
347
|
+
xmin=ima1.x0 + 50, # Within bounds
|
|
348
|
+
xmax=ima1.x0 + 100,
|
|
349
|
+
ymin=ima1.y0 + 50,
|
|
350
|
+
ymax=ima1.y0 + 100,
|
|
351
|
+
method=sigima.enums.Interpolation2DMethod.LINEAR,
|
|
352
|
+
fill_value=999.0,
|
|
353
|
+
)
|
|
354
|
+
dst5d = sigima.proc.image.resampling(ima1, p5d)
|
|
355
|
+
|
|
356
|
+
# Should not contain any fill values since all within bounds
|
|
357
|
+
assert not np.any(dst5d.data == 999.0), (
|
|
358
|
+
"No fill values expected for within-bounds sampling"
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
@pytest.mark.validation
|
|
363
|
+
def test_image_resize() -> None:
|
|
364
|
+
"""Image resize test."""
|
|
365
|
+
execenv.print("*** Testing image resize")
|
|
366
|
+
|
|
367
|
+
# Test with different zoom factors
|
|
368
|
+
zoom_factors = [0.5, 2.0, 1.5, 0.75]
|
|
369
|
+
|
|
370
|
+
for ima1 in iterate_noisy_images(size=128):
|
|
371
|
+
execenv.print(f" Testing on {ima1.data.dtype} image")
|
|
372
|
+
|
|
373
|
+
for zoom in zoom_factors:
|
|
374
|
+
execenv.print(f" zoom={zoom}: ", end="")
|
|
375
|
+
|
|
376
|
+
# Test resize with default parameters
|
|
377
|
+
p = sigima.params.ResizeParam.create(zoom=zoom)
|
|
378
|
+
ima2 = sigima.proc.image.resize(ima1, p)
|
|
379
|
+
|
|
380
|
+
# Check that scipy.ndimage.zoom produces the same result
|
|
381
|
+
expected_data = spi.zoom(
|
|
382
|
+
ima1.data, zoom, order=3, mode="constant", cval=0.0, prefilter=True
|
|
383
|
+
)
|
|
384
|
+
check_array_result(f"Resize zoom={zoom}", ima2.data, expected_data)
|
|
385
|
+
|
|
386
|
+
# Check that pixel sizes are updated correctly
|
|
387
|
+
if ima1.dx is not None and ima1.dy is not None:
|
|
388
|
+
expected_dx = ima1.dx / zoom
|
|
389
|
+
expected_dy = ima1.dy / zoom
|
|
390
|
+
check_scalar_result(
|
|
391
|
+
f"Resize dx zoom={zoom}", ima2.dx, expected_dx, rtol=1e-10
|
|
392
|
+
)
|
|
393
|
+
check_scalar_result(
|
|
394
|
+
f"Resize dy zoom={zoom}", ima2.dy, expected_dy, rtol=1e-10
|
|
395
|
+
)
|
|
396
|
+
|
|
397
|
+
# Test different border modes and parameters
|
|
398
|
+
execenv.print(" Testing different border modes and parameters")
|
|
399
|
+
ima_test = get_test_image("flower.npy")
|
|
400
|
+
|
|
401
|
+
# Test different modes
|
|
402
|
+
for mode in sigima.enums.BorderMode:
|
|
403
|
+
execenv.print(f" mode={mode.name}: ", end="")
|
|
404
|
+
p = sigima.params.ResizeParam.create(zoom=1.5, mode=mode, cval=100.0)
|
|
405
|
+
ima_resized = sigima.proc.image.resize(ima_test, p)
|
|
406
|
+
|
|
407
|
+
# Compare with scipy implementation
|
|
408
|
+
expected_data = spi.zoom(
|
|
409
|
+
ima_test.data, 1.5, order=3, mode=mode.value, cval=100.0, prefilter=True
|
|
410
|
+
)
|
|
411
|
+
check_array_result(f"Resize mode={mode.name}", ima_resized.data, expected_data)
|
|
412
|
+
|
|
413
|
+
# Test different interpolation orders
|
|
414
|
+
execenv.print(" Testing different interpolation orders")
|
|
415
|
+
for order in [0, 1, 2, 3, 4, 5]:
|
|
416
|
+
execenv.print(f" order={order}: ", end="")
|
|
417
|
+
p = sigima.params.ResizeParam.create(zoom=1.3, order=order, prefilter=False)
|
|
418
|
+
ima_resized = sigima.proc.image.resize(ima_test, p)
|
|
419
|
+
|
|
420
|
+
# Compare with scipy implementation
|
|
421
|
+
expected_data = spi.zoom(
|
|
422
|
+
ima_test.data, 1.3, order=order, mode="constant", cval=0.0, prefilter=False
|
|
423
|
+
)
|
|
424
|
+
check_array_result(f"Resize order={order}", ima_resized.data, expected_data)
|
|
425
|
+
|
|
426
|
+
# Test with prefilter disabled
|
|
427
|
+
execenv.print(" Testing prefilter parameter")
|
|
428
|
+
for prefilter in [True, False]:
|
|
429
|
+
execenv.print(f" prefilter={prefilter}: ", end="")
|
|
430
|
+
p = sigima.params.ResizeParam.create(zoom=0.8, prefilter=prefilter)
|
|
431
|
+
ima_resized = sigima.proc.image.resize(ima_test, p)
|
|
432
|
+
|
|
433
|
+
# Compare with scipy implementation
|
|
434
|
+
expected_data = spi.zoom(
|
|
435
|
+
ima_test.data, 0.8, order=3, mode="constant", cval=0.0, prefilter=prefilter
|
|
436
|
+
)
|
|
437
|
+
check_array_result(
|
|
438
|
+
f"Resize prefilter={prefilter}", ima_resized.data, expected_data
|
|
439
|
+
)
|
|
440
|
+
|
|
441
|
+
# Test edge cases
|
|
442
|
+
execenv.print(" Testing edge cases")
|
|
443
|
+
|
|
444
|
+
# Test zoom=1.0 (identity)
|
|
445
|
+
p_identity = sigima.params.ResizeParam.create(zoom=1.0)
|
|
446
|
+
ima_identity = sigima.proc.image.resize(ima_test, p_identity)
|
|
447
|
+
check_array_result("Resize identity zoom=1.0", ima_identity.data, ima_test.data)
|
|
448
|
+
|
|
449
|
+
# Test very small zoom
|
|
450
|
+
p_small = sigima.params.ResizeParam.create(zoom=0.1)
|
|
451
|
+
ima_small = sigima.proc.image.resize(ima_test, p_small)
|
|
452
|
+
expected_small = spi.zoom(
|
|
453
|
+
ima_test.data, 0.1, order=3, mode="constant", cval=0.0, prefilter=True
|
|
454
|
+
)
|
|
455
|
+
check_array_result("Resize small zoom=0.1", ima_small.data, expected_small)
|
|
456
|
+
|
|
457
|
+
# Test large zoom
|
|
458
|
+
p_large = sigima.params.ResizeParam.create(zoom=5.0)
|
|
459
|
+
ima_large = sigima.proc.image.resize(ima_test, p_large)
|
|
460
|
+
expected_large = spi.zoom(
|
|
461
|
+
ima_test.data, 5.0, order=3, mode="constant", cval=0.0, prefilter=True
|
|
462
|
+
)
|
|
463
|
+
check_array_result("Resize large zoom=5.0", ima_large.data, expected_large)
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
@pytest.mark.validation
|
|
467
|
+
def test_set_uniform_coords() -> None:
|
|
468
|
+
"""Test converting from non-uniform to uniform coordinates."""
|
|
469
|
+
execenv.print("*** Testing set_uniform_coords")
|
|
470
|
+
|
|
471
|
+
# Test 1: Create an image with non-uniform coordinates
|
|
472
|
+
execenv.print(" Testing non-uniform to uniform conversion")
|
|
473
|
+
ima1 = get_test_image("flower.npy")
|
|
474
|
+
nx, ny = ima1.data.shape[1], ima1.data.shape[0]
|
|
475
|
+
|
|
476
|
+
# Create non-uniform coordinates (e.g., quadratic spacing on y-axis)
|
|
477
|
+
xcoords = np.linspace(0.0, 10.0, nx)
|
|
478
|
+
ycoords = np.linspace(0.0, 8.0, ny) ** 2 # Non-uniform spacing
|
|
479
|
+
ima1.set_coords(xcoords, ycoords)
|
|
480
|
+
|
|
481
|
+
# Verify it's non-uniform
|
|
482
|
+
assert not ima1.is_uniform_coords, "Image should have non-uniform coordinates"
|
|
483
|
+
|
|
484
|
+
# Create parameter and update from object
|
|
485
|
+
p = sigima.params.UniformCoordsParam()
|
|
486
|
+
p.update_from_obj(ima1)
|
|
487
|
+
|
|
488
|
+
# Apply conversion
|
|
489
|
+
ima2 = sigima.proc.image.set_uniform_coords(ima1, p)
|
|
490
|
+
|
|
491
|
+
# Check that result has uniform coordinates
|
|
492
|
+
assert ima2.is_uniform_coords, "Result should have uniform coordinates"
|
|
493
|
+
|
|
494
|
+
# Check that the data is unchanged
|
|
495
|
+
check_array_result("Data preservation", ima2.data, ima1.data)
|
|
496
|
+
|
|
497
|
+
# Check that coordinate parameters were extracted correctly
|
|
498
|
+
expected_x0 = xcoords[0]
|
|
499
|
+
expected_y0 = ycoords[0]
|
|
500
|
+
expected_dx = (xcoords[-1] - xcoords[0]) / (nx - 1)
|
|
501
|
+
expected_dy = (ycoords[-1] - ycoords[0]) / (ny - 1)
|
|
502
|
+
|
|
503
|
+
check_scalar_result("X0 extraction", ima2.x0, expected_x0, atol=1e-10)
|
|
504
|
+
check_scalar_result("Y0 extraction", ima2.y0, expected_y0, atol=1e-10)
|
|
505
|
+
check_scalar_result("dx extraction", ima2.dx, expected_dx, atol=1e-10)
|
|
506
|
+
check_scalar_result("dy extraction", ima2.dy, expected_dy, atol=1e-10)
|
|
507
|
+
|
|
508
|
+
# Test 2: Converting already uniform coordinates (should preserve values)
|
|
509
|
+
execenv.print(" Testing uniform to uniform (identity)")
|
|
510
|
+
ima3 = get_test_image("flower.npy")
|
|
511
|
+
original_x0, original_y0 = ima3.x0, ima3.y0
|
|
512
|
+
original_dx, original_dy = ima3.dx, ima3.dy
|
|
513
|
+
|
|
514
|
+
p2 = sigima.params.UniformCoordsParam()
|
|
515
|
+
p2.update_from_obj(ima3)
|
|
516
|
+
ima4 = sigima.proc.image.set_uniform_coords(ima3, p2)
|
|
517
|
+
|
|
518
|
+
assert ima4.is_uniform_coords, "Result should have uniform coordinates"
|
|
519
|
+
check_array_result("Data preservation (uniform)", ima4.data, ima3.data)
|
|
520
|
+
check_scalar_result("X0 preservation", ima4.x0, original_x0, atol=1e-10)
|
|
521
|
+
check_scalar_result("Y0 preservation", ima4.y0, original_y0, atol=1e-10)
|
|
522
|
+
check_scalar_result("dx preservation", ima4.dx, original_dx, atol=1e-10)
|
|
523
|
+
check_scalar_result("dy preservation", ima4.dy, original_dy, atol=1e-10)
|
|
524
|
+
|
|
525
|
+
# Test 3: Manual parameter specification
|
|
526
|
+
execenv.print(" Testing manual parameter specification")
|
|
527
|
+
ima5 = get_test_image("flower.npy")
|
|
528
|
+
# Create non-uniform coordinates
|
|
529
|
+
ima5.set_coords(np.linspace(5.0, 15.0, nx), np.linspace(10.0, 20.0, ny))
|
|
530
|
+
|
|
531
|
+
p3 = sigima.params.UniformCoordsParam.create(x0=5.0, y0=10.0, dx=0.5, dy=0.25)
|
|
532
|
+
ima6 = sigima.proc.image.set_uniform_coords(ima5, p3)
|
|
533
|
+
|
|
534
|
+
assert ima6.is_uniform_coords, "Result should have uniform coordinates"
|
|
535
|
+
check_scalar_result("Manual X0", ima6.x0, 5.0, atol=1e-10)
|
|
536
|
+
check_scalar_result("Manual Y0", ima6.y0, 10.0, atol=1e-10)
|
|
537
|
+
check_scalar_result("Manual dx", ima6.dx, 0.5, atol=1e-10)
|
|
538
|
+
check_scalar_result("Manual dy", ima6.dy, 0.25, atol=1e-10)
|
|
539
|
+
|
|
540
|
+
|
|
541
|
+
@pytest.mark.validation
|
|
542
|
+
def test_image_calibration() -> None:
|
|
543
|
+
"""Validation test for polynomial calibration."""
|
|
544
|
+
execenv.print("*** Testing calibration (polynomial)")
|
|
545
|
+
|
|
546
|
+
# Test 1: Z-axis polynomial calibration
|
|
547
|
+
execenv.print(" Testing Z-axis polynomial calibration")
|
|
548
|
+
src = get_test_image("flower.npy")
|
|
549
|
+
# Use smaller coefficients to avoid overflow with uint8 data (0-255 range)
|
|
550
|
+
p = sigima.params.XYZCalibrateParam.create(
|
|
551
|
+
axis="z", a0=10.0, a1=2.0, a2=0.001, a3=0.0
|
|
552
|
+
)
|
|
553
|
+
dst = sigima.proc.image.calibration(src, p)
|
|
554
|
+
|
|
555
|
+
# Verify polynomial transformation on data
|
|
556
|
+
src_data_float = src.data.astype(float)
|
|
557
|
+
expected_data = (
|
|
558
|
+
p.a0
|
|
559
|
+
+ p.a1 * src_data_float
|
|
560
|
+
+ p.a2 * src_data_float**2
|
|
561
|
+
+ p.a3 * src_data_float**3
|
|
562
|
+
)
|
|
563
|
+
check_array_result("Z-axis polynomial", dst.data, expected_data)
|
|
564
|
+
|
|
565
|
+
# Coordinates should be unchanged
|
|
566
|
+
assert dst.is_uniform_coords
|
|
567
|
+
check_scalar_result("Z-calib: x0", dst.x0, src.x0)
|
|
568
|
+
check_scalar_result("Z-calib: y0", dst.y0, src.y0)
|
|
569
|
+
check_scalar_result("Z-calib: dx", dst.dx, src.dx)
|
|
570
|
+
check_scalar_result("Z-calib: dy", dst.dy, src.dy)
|
|
571
|
+
|
|
572
|
+
# Test 2: X-axis polynomial calibration (uniform → non-uniform)
|
|
573
|
+
execenv.print(" Testing X-axis polynomial (uniform → non-uniform)")
|
|
574
|
+
src2 = get_test_image("flower.npy")
|
|
575
|
+
src2.set_uniform_coords(dx=0.5, dy=0.5, x0=0.0, y0=0.0)
|
|
576
|
+
p2 = sigima.params.XYZCalibrateParam.create(
|
|
577
|
+
axis="x", a0=1.0, a1=2.0, a2=0.1, a3=0.0
|
|
578
|
+
)
|
|
579
|
+
dst2 = sigima.proc.image.calibration(src2, p2)
|
|
580
|
+
|
|
581
|
+
# After polynomial calibration on X, coordinates should become non-uniform
|
|
582
|
+
assert not dst2.is_uniform_coords, (
|
|
583
|
+
"X-axis polynomial should create non-uniform coords"
|
|
584
|
+
)
|
|
585
|
+
|
|
586
|
+
# Verify X coordinates transformation
|
|
587
|
+
x_uniform = src2.x0 + np.arange(src2.data.shape[1]) * src2.dx
|
|
588
|
+
expected_x = p2.a0 + p2.a1 * x_uniform + p2.a2 * x_uniform**2
|
|
589
|
+
check_array_result("X-axis polynomial coords", dst2.xcoords, expected_x)
|
|
590
|
+
# Check that Y coordinates were converted in non-uniform but unchanged
|
|
591
|
+
src2_ycoords = src2.y0 + np.arange(src2.data.shape[0]) * src2.dy
|
|
592
|
+
check_array_result("X-axis polynomial Y coords", dst2.ycoords, src2_ycoords)
|
|
593
|
+
|
|
594
|
+
# Data should be unchanged
|
|
595
|
+
check_array_result("X-calib: data preservation", dst2.data, src2.data)
|
|
596
|
+
|
|
597
|
+
# Test 3: Y-axis polynomial calibration (non-uniform → non-uniform)
|
|
598
|
+
execenv.print(" Testing Y-axis polynomial (non-uniform → non-uniform)")
|
|
599
|
+
src3 = get_test_image("flower.npy")
|
|
600
|
+
ny = src3.data.shape[0]
|
|
601
|
+
y_nonuniform = np.linspace(0.0, 10.0, ny)
|
|
602
|
+
src3.set_coords(None, y_nonuniform)
|
|
603
|
+
|
|
604
|
+
p3 = sigima.params.XYZCalibrateParam.create(
|
|
605
|
+
axis="y", a0=5.0, a1=1.0, a2=0.0, a3=0.05
|
|
606
|
+
)
|
|
607
|
+
dst3 = sigima.proc.image.calibration(src3, p3)
|
|
608
|
+
|
|
609
|
+
# Should still be non-uniform
|
|
610
|
+
assert not dst3.is_uniform_coords
|
|
611
|
+
|
|
612
|
+
# Verify Y coordinates transformation
|
|
613
|
+
expected_y = p3.a0 + p3.a1 * y_nonuniform + p3.a3 * y_nonuniform**3
|
|
614
|
+
check_array_result("Y-axis polynomial coords", dst3.ycoords, expected_y)
|
|
615
|
+
|
|
616
|
+
# Data should be unchanged
|
|
617
|
+
check_array_result("Y-calib: data preservation", dst3.data, src3.data)
|
|
618
|
+
|
|
619
|
+
# Test 4: Linear case (a2=a3=0, backward compatibility)
|
|
620
|
+
execenv.print(" Testing linear calibration (a2=a3=0)")
|
|
621
|
+
src4 = get_test_image("flower.npy")
|
|
622
|
+
p4 = sigima.params.XYZCalibrateParam.create(
|
|
623
|
+
axis="x", a0=0.5, a1=2.0, a2=0.0, a3=0.0
|
|
624
|
+
)
|
|
625
|
+
dst4 = sigima.proc.image.calibration(src4, p4)
|
|
626
|
+
|
|
627
|
+
# For linear case with uniform input, result should still be non-uniform
|
|
628
|
+
# because we always generate coordinate arrays
|
|
629
|
+
# Verify the transformation is correct
|
|
630
|
+
x_uniform = src4.x0 + np.arange(src4.data.shape[1]) * src4.dx
|
|
631
|
+
expected_x_linear = p4.a0 + p4.a1 * x_uniform
|
|
632
|
+
if dst4.is_uniform_coords:
|
|
633
|
+
# If implementation optimized to keep uniform coords
|
|
634
|
+
check_scalar_result("Linear x0", dst4.x0, expected_x_linear[0])
|
|
635
|
+
check_scalar_result(
|
|
636
|
+
"Linear dx", dst4.dx, expected_x_linear[1] - expected_x_linear[0]
|
|
637
|
+
)
|
|
638
|
+
else:
|
|
639
|
+
# If coordinates are non-uniform
|
|
640
|
+
check_array_result("Linear xcoords", dst4.xcoords, expected_x_linear)
|
|
641
|
+
|
|
642
|
+
|
|
643
|
+
if __name__ == "__main__":
|
|
644
|
+
test_image_fliph()
|
|
645
|
+
test_image_flipv()
|
|
646
|
+
test_image_rotate90()
|
|
647
|
+
test_image_rotate270()
|
|
648
|
+
test_image_rotate()
|
|
649
|
+
test_image_transpose()
|
|
650
|
+
test_image_resampling()
|
|
651
|
+
test_image_resize()
|
|
652
|
+
test_image_translate()
|
|
653
|
+
test_set_uniform_coords()
|
|
654
|
+
test_image_calibration()
|