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,737 @@
|
|
|
1
|
+
# Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Unit tests around the `ImageObj` 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.image import ImageIORegistry
|
|
20
|
+
from sigima.objects.image import Gauss2DParam, Ramp2DParam
|
|
21
|
+
from sigima.tests import guiutils
|
|
22
|
+
from sigima.tests.data import (
|
|
23
|
+
create_annotated_image,
|
|
24
|
+
create_test_image_with_metadata,
|
|
25
|
+
iterate_image_creation,
|
|
26
|
+
)
|
|
27
|
+
from sigima.tests.env import execenv
|
|
28
|
+
from sigima.tests.helpers import (
|
|
29
|
+
WorkdirRestoringTempDir,
|
|
30
|
+
check_scalar_result,
|
|
31
|
+
compare_metadata,
|
|
32
|
+
read_test_objects,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def preprocess_image_parameters(param: sigima.objects.NewImageParam) -> None:
|
|
37
|
+
"""Preprocess image parameters before creating the image.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
param: The image parameters to preprocess.
|
|
41
|
+
"""
|
|
42
|
+
if isinstance(param, Ramp2DParam):
|
|
43
|
+
param.a = 1.0
|
|
44
|
+
param.b = 2.0
|
|
45
|
+
param.c = 3.0
|
|
46
|
+
param.xmin = -1.0
|
|
47
|
+
param.xmax = 2.0
|
|
48
|
+
param.ymin = -5.0
|
|
49
|
+
param.ymax = 4.0
|
|
50
|
+
elif isinstance(param, Gauss2DParam):
|
|
51
|
+
param.x0 = param.y0 = 3.0
|
|
52
|
+
param.sigma = 5.0
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def postprocess_image_object(
|
|
56
|
+
obj: sigima.objects.ImageObj, itype: sigima.objects.ImageTypes
|
|
57
|
+
) -> None:
|
|
58
|
+
"""Postprocess the image object after creation.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
obj: The image object to postprocess.
|
|
62
|
+
itype: The type of the image.
|
|
63
|
+
"""
|
|
64
|
+
if itype == sigima.objects.ImageTypes.ZEROS:
|
|
65
|
+
assert (obj.data == 0).all()
|
|
66
|
+
elif itype == sigima.objects.ImageTypes.RAMP:
|
|
67
|
+
assert obj.data is not None
|
|
68
|
+
check_scalar_result("Top-left corner", obj.data[0][0], -8.0)
|
|
69
|
+
check_scalar_result("Top-right corner", obj.data[0][-1], -5.0)
|
|
70
|
+
check_scalar_result("Bottom-left corner", obj.data[-1][0], 10.0)
|
|
71
|
+
check_scalar_result("Bottom-right", obj.data[-1][-1], 13.0)
|
|
72
|
+
else:
|
|
73
|
+
assert obj.data is not None
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def test_all_image_types() -> None:
|
|
77
|
+
"""Testing image creation from parameters"""
|
|
78
|
+
execenv.print(f"{test_all_image_types.__doc__}:")
|
|
79
|
+
for image in iterate_image_creation(
|
|
80
|
+
preproc=preprocess_image_parameters,
|
|
81
|
+
postproc=postprocess_image_object,
|
|
82
|
+
):
|
|
83
|
+
assert image.data is not None
|
|
84
|
+
execenv.print(f"{test_all_image_types.__doc__}: OK")
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def __get_filenames_and_images() -> list[tuple[str, sigima.objects.ImageObj]]:
|
|
88
|
+
"""Get test filenames and images from the registry"""
|
|
89
|
+
fi_list = [
|
|
90
|
+
(fname, obj)
|
|
91
|
+
for fname, obj in read_test_objects(ImageIORegistry)
|
|
92
|
+
if obj is not None
|
|
93
|
+
]
|
|
94
|
+
fi_list.append(("test_image_with_metadata", create_test_image_with_metadata()))
|
|
95
|
+
fi_list.append(("annotated_image", create_annotated_image()))
|
|
96
|
+
return fi_list
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def test_hdf5_image_io() -> None:
|
|
100
|
+
"""Test HDF5 I/O for image objects with uniform and non-uniform coordinates"""
|
|
101
|
+
execenv.print(f"{test_hdf5_image_io.__doc__}:")
|
|
102
|
+
with WorkdirRestoringTempDir() as tmpdir:
|
|
103
|
+
for fname, orig_image in __get_filenames_and_images():
|
|
104
|
+
if orig_image is None:
|
|
105
|
+
execenv.print(f" Skipping {fname} (not implemented)")
|
|
106
|
+
continue
|
|
107
|
+
|
|
108
|
+
# Test Case 1: Original image with uniform coordinates (default)
|
|
109
|
+
filename = osp.join(tmpdir, f"test_{osp.basename(fname)}_uniform.h5ima")
|
|
110
|
+
sigima.io.write_image(filename, orig_image)
|
|
111
|
+
execenv.print(f" Saved {filename} (uniform coords)")
|
|
112
|
+
|
|
113
|
+
# Read back
|
|
114
|
+
fetch_image = sigima.io.read_image(filename)
|
|
115
|
+
execenv.print(f" Read {filename}")
|
|
116
|
+
|
|
117
|
+
# Verify data
|
|
118
|
+
data = fetch_image.data
|
|
119
|
+
orig_data = orig_image.data
|
|
120
|
+
assert isinstance(data, np.ndarray)
|
|
121
|
+
assert isinstance(orig_data, np.ndarray)
|
|
122
|
+
assert data.shape == orig_data.shape
|
|
123
|
+
assert data.dtype == orig_data.dtype
|
|
124
|
+
assert fetch_image.annotations == orig_image.annotations
|
|
125
|
+
assert np.allclose(data, orig_data, atol=0.0, equal_nan=True)
|
|
126
|
+
compare_metadata(
|
|
127
|
+
fetch_image.metadata, orig_image.metadata.copy(), raise_on_diff=True
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
# Verify uniform coordinate attributes are preserved
|
|
131
|
+
if orig_image.is_uniform_coords:
|
|
132
|
+
assert fetch_image.is_uniform_coords
|
|
133
|
+
assert fetch_image.dx == orig_image.dx
|
|
134
|
+
assert fetch_image.dy == orig_image.dy
|
|
135
|
+
assert fetch_image.x0 == orig_image.x0
|
|
136
|
+
assert fetch_image.y0 == orig_image.y0
|
|
137
|
+
execenv.print(" ✓ Uniform coordinates preserved")
|
|
138
|
+
|
|
139
|
+
# Test Case 2: Same image with non-uniform coordinates
|
|
140
|
+
# Create a modified version with non-uniform coordinates
|
|
141
|
+
nonuniform_image = sigima.objects.create_image(
|
|
142
|
+
title=orig_image.title + " (non-uniform)",
|
|
143
|
+
data=orig_image.data.copy(),
|
|
144
|
+
metadata=orig_image.metadata.copy(),
|
|
145
|
+
units=(orig_image.xunit, orig_image.yunit, orig_image.zunit),
|
|
146
|
+
labels=(orig_image.xlabel, orig_image.ylabel, orig_image.zlabel),
|
|
147
|
+
)
|
|
148
|
+
# Set non-uniform coordinates
|
|
149
|
+
ny, nx = nonuniform_image.data.shape
|
|
150
|
+
xcoords = np.linspace(0, 1, nx)
|
|
151
|
+
ycoords = np.linspace(0, 1, ny) ** 2 # Quadratic spacing
|
|
152
|
+
nonuniform_image.set_coords(xcoords=xcoords, ycoords=ycoords)
|
|
153
|
+
|
|
154
|
+
# Save non-uniform version
|
|
155
|
+
filename_nu = osp.join(
|
|
156
|
+
tmpdir, f"test_{osp.basename(fname)}_nonuniform.h5ima"
|
|
157
|
+
)
|
|
158
|
+
sigima.io.write_image(filename_nu, nonuniform_image)
|
|
159
|
+
execenv.print(f" Saved {filename_nu} (non-uniform coords)")
|
|
160
|
+
|
|
161
|
+
# Read back
|
|
162
|
+
fetch_image_nu = sigima.io.read_image(filename_nu)
|
|
163
|
+
execenv.print(f" Read {filename_nu}")
|
|
164
|
+
|
|
165
|
+
# Verify data
|
|
166
|
+
assert np.allclose(
|
|
167
|
+
fetch_image_nu.data, nonuniform_image.data, atol=0.0, equal_nan=True
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
# Verify non-uniform coordinate attributes are preserved
|
|
171
|
+
assert not fetch_image_nu.is_uniform_coords
|
|
172
|
+
assert np.array_equal(fetch_image_nu.xcoords, xcoords)
|
|
173
|
+
assert np.array_equal(fetch_image_nu.ycoords, ycoords)
|
|
174
|
+
execenv.print(" ✓ Non-uniform coordinates preserved")
|
|
175
|
+
|
|
176
|
+
execenv.print(f"{test_hdf5_image_io.__doc__}: OK")
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
@pytest.mark.gui
|
|
180
|
+
def test_image_parameters_interactive() -> None:
|
|
181
|
+
"""Test interactive creation of image parameters"""
|
|
182
|
+
execenv.print(f"{test_image_parameters_interactive.__doc__}:")
|
|
183
|
+
with guiutils.lazy_qt_app_context(force=True):
|
|
184
|
+
for itype in sigima.objects.ImageTypes:
|
|
185
|
+
param = sigima.objects.create_image_parameters(itype)
|
|
186
|
+
if param.edit():
|
|
187
|
+
execenv.print(f" Edited parameters for {itype.value}:")
|
|
188
|
+
execenv.print(f" {param}")
|
|
189
|
+
else:
|
|
190
|
+
execenv.print(f" Skipped editing parameters for {itype.value}")
|
|
191
|
+
execenv.print(f"{test_image_parameters_interactive.__doc__}: OK")
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def test_create_image() -> None:
|
|
195
|
+
"""Test creation of an image object using `create_image` function"""
|
|
196
|
+
execenv.print(f"{test_create_image.__doc__}:")
|
|
197
|
+
# pylint: disable=import-outside-toplevel
|
|
198
|
+
|
|
199
|
+
# Test all combinations of input parameters
|
|
200
|
+
title = "Some Image"
|
|
201
|
+
data = np.random.rand(10, 10)
|
|
202
|
+
metadata = {"key": "value"}
|
|
203
|
+
units = ("x unit", "y unit", "z unit")
|
|
204
|
+
labels = ("x label", "y label", "z label")
|
|
205
|
+
|
|
206
|
+
# 1. Create image with all parameters, and uniform coordinates
|
|
207
|
+
image = sigima.objects.create_image(
|
|
208
|
+
title=title,
|
|
209
|
+
data=data,
|
|
210
|
+
metadata=metadata,
|
|
211
|
+
units=units,
|
|
212
|
+
labels=labels,
|
|
213
|
+
)
|
|
214
|
+
assert isinstance(image, sigima.objects.ImageObj)
|
|
215
|
+
assert image.title == title
|
|
216
|
+
assert image.data is data # Data should be the same object (not a copy)
|
|
217
|
+
assert image.metadata == metadata
|
|
218
|
+
assert (image.xunit, image.yunit, image.zunit) == units
|
|
219
|
+
assert (image.xlabel, image.ylabel, image.zlabel) == labels
|
|
220
|
+
dx, dy, x0, y0 = 0.1, 0.2, 50.0, 100.0
|
|
221
|
+
image.set_uniform_coords(dx, dy, x0=x0, y0=y0)
|
|
222
|
+
assert image.is_uniform_coords
|
|
223
|
+
assert image.dx == dx
|
|
224
|
+
assert image.dy == dy
|
|
225
|
+
assert image.x0 == x0
|
|
226
|
+
assert image.y0 == y0
|
|
227
|
+
|
|
228
|
+
guiutils.view_images_if_gui(image, title=title)
|
|
229
|
+
|
|
230
|
+
# 2. Create image with non-uniform coordinates
|
|
231
|
+
xcoords = np.linspace(0, 1, 10)
|
|
232
|
+
ycoords = np.linspace(0, 1, 10) ** 2
|
|
233
|
+
image.set_coords(xcoords=xcoords, ycoords=ycoords)
|
|
234
|
+
assert not image.is_uniform_coords
|
|
235
|
+
assert np.array_equal(image.xcoords, xcoords)
|
|
236
|
+
assert np.array_equal(image.ycoords, ycoords)
|
|
237
|
+
|
|
238
|
+
guiutils.view_images_if_gui(image, title=title + " (non-uniform coords)")
|
|
239
|
+
|
|
240
|
+
# 3. Create image with only data
|
|
241
|
+
image = sigima.objects.create_image("", data=data)
|
|
242
|
+
assert isinstance(image, sigima.objects.ImageObj)
|
|
243
|
+
assert np.array_equal(image.data, data)
|
|
244
|
+
assert not image.metadata
|
|
245
|
+
assert (image.xunit, image.yunit, image.zunit) == ("", "", "")
|
|
246
|
+
assert (image.xlabel, image.ylabel, image.zlabel) == ("", "", "")
|
|
247
|
+
|
|
248
|
+
execenv.print(f"{test_create_image.__doc__}: OK")
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def test_create_image_from_param() -> None:
|
|
252
|
+
"""Test creation of an image object using `create_image_from_param` function"""
|
|
253
|
+
execenv.print(f"{test_create_image_from_param.__doc__}:")
|
|
254
|
+
|
|
255
|
+
# Test 1: Basic parameter with defaults
|
|
256
|
+
param = sigima.objects.NewImageParam()
|
|
257
|
+
param.title = "Test Image"
|
|
258
|
+
param.height = 100
|
|
259
|
+
param.width = 200
|
|
260
|
+
param.dtype = sigima.objects.ImageDatatypes.UINT16
|
|
261
|
+
|
|
262
|
+
image = sigima.objects.create_image_from_param(param)
|
|
263
|
+
assert isinstance(image, sigima.objects.ImageObj)
|
|
264
|
+
assert image.title == "Test Image"
|
|
265
|
+
assert image.data is not None
|
|
266
|
+
assert image.data.shape == (100, 200)
|
|
267
|
+
assert image.data.dtype == np.uint16
|
|
268
|
+
assert (image.data == 0).all() # NewImageParam generates zeros by default
|
|
269
|
+
|
|
270
|
+
# Test 2: Parameter with default values (no explicit setting)
|
|
271
|
+
param_defaults = sigima.objects.NewImageParam()
|
|
272
|
+
# Don't set any values, use defaults
|
|
273
|
+
|
|
274
|
+
image_defaults = sigima.objects.create_image_from_param(param_defaults)
|
|
275
|
+
assert isinstance(image_defaults, sigima.objects.ImageObj)
|
|
276
|
+
assert image_defaults.data is not None
|
|
277
|
+
assert image_defaults.data.shape == (1024, 1024) # Default dimensions
|
|
278
|
+
assert image_defaults.data.dtype == np.float64 # Default dtype from NewImageParam
|
|
279
|
+
|
|
280
|
+
# Test 3: Different image types using create_image_parameters
|
|
281
|
+
test_cases = [
|
|
282
|
+
(sigima.objects.ImageTypes.ZEROS, sigima.objects.ImageDatatypes.UINT8),
|
|
283
|
+
(
|
|
284
|
+
sigima.objects.ImageTypes.UNIFORM_DISTRIBUTION,
|
|
285
|
+
sigima.objects.ImageDatatypes.FLOAT32,
|
|
286
|
+
),
|
|
287
|
+
(
|
|
288
|
+
sigima.objects.ImageTypes.NORMAL_DISTRIBUTION,
|
|
289
|
+
sigima.objects.ImageDatatypes.FLOAT64,
|
|
290
|
+
),
|
|
291
|
+
(sigima.objects.ImageTypes.GAUSS, sigima.objects.ImageDatatypes.UINT16),
|
|
292
|
+
(sigima.objects.ImageTypes.RAMP, sigima.objects.ImageDatatypes.FLOAT64),
|
|
293
|
+
]
|
|
294
|
+
|
|
295
|
+
for img_type, dtype in test_cases:
|
|
296
|
+
param_type = sigima.objects.create_image_parameters(
|
|
297
|
+
img_type,
|
|
298
|
+
title=f"Test {img_type.value}",
|
|
299
|
+
height=50,
|
|
300
|
+
width=60,
|
|
301
|
+
idtype=dtype,
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
# Preprocess parameters for specific types
|
|
305
|
+
preprocess_image_parameters(param_type)
|
|
306
|
+
|
|
307
|
+
image_type = sigima.objects.create_image_from_param(param_type)
|
|
308
|
+
assert isinstance(image_type, sigima.objects.ImageObj)
|
|
309
|
+
assert image_type.data is not None
|
|
310
|
+
assert image_type.data.shape == (50, 60)
|
|
311
|
+
assert image_type.data.dtype == dtype.value
|
|
312
|
+
|
|
313
|
+
# Validate image type-specific properties
|
|
314
|
+
if img_type == sigima.objects.ImageTypes.ZEROS:
|
|
315
|
+
assert (image_type.data == 0).all()
|
|
316
|
+
elif img_type == sigima.objects.ImageTypes.UNIFORM_DISTRIBUTION:
|
|
317
|
+
# Uniform distribution should have varying values
|
|
318
|
+
assert not (image_type.data == image_type.data[0, 0]).all()
|
|
319
|
+
assert np.isfinite(image_type.data).all()
|
|
320
|
+
elif img_type == sigima.objects.ImageTypes.NORMAL_DISTRIBUTION:
|
|
321
|
+
# Normal distribution should have reasonable values
|
|
322
|
+
assert not (image_type.data == 0).all()
|
|
323
|
+
assert np.isfinite(image_type.data).all()
|
|
324
|
+
elif img_type == sigima.objects.ImageTypes.GAUSS:
|
|
325
|
+
# 2D Gaussian should have non-zero values
|
|
326
|
+
assert not (image_type.data == 0).all()
|
|
327
|
+
assert np.isfinite(image_type.data).all()
|
|
328
|
+
elif img_type == sigima.objects.ImageTypes.RAMP:
|
|
329
|
+
# Ramp should have varying values
|
|
330
|
+
assert not (image_type.data == image_type.data[0, 0]).all()
|
|
331
|
+
assert np.isfinite(image_type.data).all()
|
|
332
|
+
|
|
333
|
+
# Test automatic title generation for distribution types
|
|
334
|
+
if "DISTRIBUTION" in img_type.name:
|
|
335
|
+
param_autotitle = sigima.objects.create_image_parameters(
|
|
336
|
+
img_type, title="", height=50, width=60, idtype=dtype
|
|
337
|
+
)
|
|
338
|
+
image_autotitle = sigima.objects.create_image_from_param(param_autotitle)
|
|
339
|
+
assert "Random" in image_autotitle.title, (
|
|
340
|
+
f"Auto-generated title should contain 'Random' for {img_type.value}"
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
# Test 4: Gaussian parameters with specific values
|
|
344
|
+
gauss_param = sigima.objects.Gauss2DParam()
|
|
345
|
+
gauss_param.title = "Custom Gauss"
|
|
346
|
+
gauss_param.height = 80
|
|
347
|
+
gauss_param.width = 80
|
|
348
|
+
gauss_param.dtype = sigima.objects.ImageDatatypes.FLOAT32
|
|
349
|
+
|
|
350
|
+
gauss_image = sigima.objects.create_image_from_param(gauss_param)
|
|
351
|
+
assert isinstance(gauss_image, sigima.objects.ImageObj)
|
|
352
|
+
assert gauss_image.title == "Custom Gauss"
|
|
353
|
+
assert gauss_image.data.shape == (80, 80)
|
|
354
|
+
assert gauss_image.data.dtype == np.float32
|
|
355
|
+
# Center should have highest value for Gaussian
|
|
356
|
+
center_val = gauss_image.data[40, 40]
|
|
357
|
+
corner_val = gauss_image.data[0, 0]
|
|
358
|
+
assert center_val > corner_val
|
|
359
|
+
|
|
360
|
+
# Test 5: Ramp parameters with specific values
|
|
361
|
+
ramp_param = sigima.objects.Ramp2DParam()
|
|
362
|
+
ramp_param.title = "Custom Ramp"
|
|
363
|
+
ramp_param.height = 60
|
|
364
|
+
ramp_param.width = 40
|
|
365
|
+
ramp_param.dtype = sigima.objects.ImageDatatypes.FLOAT64
|
|
366
|
+
|
|
367
|
+
ramp_image = sigima.objects.create_image_from_param(ramp_param)
|
|
368
|
+
assert isinstance(ramp_image, sigima.objects.ImageObj)
|
|
369
|
+
assert ramp_image.title == "Custom Ramp"
|
|
370
|
+
assert ramp_image.data.shape == (60, 40)
|
|
371
|
+
assert ramp_image.data.dtype == np.float64
|
|
372
|
+
# Ramp should have different values at different positions
|
|
373
|
+
assert ramp_image.data[0, 0] != ramp_image.data[-1, -1]
|
|
374
|
+
|
|
375
|
+
execenv.print(f"{test_create_image_from_param.__doc__}: OK")
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
def test_image_copy() -> None:
|
|
379
|
+
"""Test copying image objects with uniform and non-uniform coordinates"""
|
|
380
|
+
execenv.print(f"{test_image_copy.__doc__}:")
|
|
381
|
+
|
|
382
|
+
# Create a base image with some data
|
|
383
|
+
data = np.random.rand(50, 60)
|
|
384
|
+
title = "Original Image"
|
|
385
|
+
metadata = {"key1": "value1", "key2": 42}
|
|
386
|
+
units = ("mm", "mm", "intensity")
|
|
387
|
+
labels = ("X axis", "Y axis", "Intensity")
|
|
388
|
+
|
|
389
|
+
# Test 1: Copy image with uniform coordinates
|
|
390
|
+
execenv.print(" Test 1: Copy image with uniform coordinates")
|
|
391
|
+
image_uniform = sigima.objects.create_image(
|
|
392
|
+
title=title,
|
|
393
|
+
data=data.copy(),
|
|
394
|
+
metadata=metadata.copy(),
|
|
395
|
+
units=units,
|
|
396
|
+
labels=labels,
|
|
397
|
+
)
|
|
398
|
+
dx, dy, x0, y0 = 0.5, 0.8, 10.0, 20.0
|
|
399
|
+
image_uniform.set_uniform_coords(dx, dy, x0=x0, y0=y0)
|
|
400
|
+
# Set some scale attributes
|
|
401
|
+
image_uniform.autoscale = False
|
|
402
|
+
image_uniform.xscalelog = True
|
|
403
|
+
image_uniform.xscalemin = 5.0
|
|
404
|
+
image_uniform.xscalemax = 25.0
|
|
405
|
+
image_uniform.yscalelog = False
|
|
406
|
+
image_uniform.yscalemin = 15.0
|
|
407
|
+
image_uniform.yscalemax = 35.0
|
|
408
|
+
image_uniform.zscalemin = 0.0
|
|
409
|
+
image_uniform.zscalemax = 1.0
|
|
410
|
+
|
|
411
|
+
# Copy the image
|
|
412
|
+
copied_uniform = image_uniform.copy()
|
|
413
|
+
|
|
414
|
+
# Verify the copy
|
|
415
|
+
assert copied_uniform is not image_uniform
|
|
416
|
+
assert copied_uniform.title == image_uniform.title
|
|
417
|
+
assert np.array_equal(copied_uniform.data, image_uniform.data)
|
|
418
|
+
assert copied_uniform.data is not image_uniform.data # Different array objects
|
|
419
|
+
assert copied_uniform.metadata == image_uniform.metadata
|
|
420
|
+
assert copied_uniform.metadata is not image_uniform.metadata
|
|
421
|
+
assert (copied_uniform.xunit, copied_uniform.yunit, copied_uniform.zunit) == units
|
|
422
|
+
assert (
|
|
423
|
+
copied_uniform.xlabel,
|
|
424
|
+
copied_uniform.ylabel,
|
|
425
|
+
copied_uniform.zlabel,
|
|
426
|
+
) == labels
|
|
427
|
+
|
|
428
|
+
# Verify uniform coordinates are preserved
|
|
429
|
+
assert copied_uniform.is_uniform_coords == image_uniform.is_uniform_coords
|
|
430
|
+
assert copied_uniform.is_uniform_coords is True
|
|
431
|
+
assert copied_uniform.dx == dx
|
|
432
|
+
assert copied_uniform.dy == dy
|
|
433
|
+
assert copied_uniform.x0 == x0
|
|
434
|
+
assert copied_uniform.y0 == y0
|
|
435
|
+
execenv.print(" ✓ Uniform coordinates correctly copied")
|
|
436
|
+
|
|
437
|
+
# Verify scale attributes are preserved
|
|
438
|
+
assert copied_uniform.autoscale == image_uniform.autoscale
|
|
439
|
+
assert copied_uniform.xscalelog == image_uniform.xscalelog
|
|
440
|
+
assert copied_uniform.xscalemin == image_uniform.xscalemin
|
|
441
|
+
assert copied_uniform.xscalemax == image_uniform.xscalemax
|
|
442
|
+
assert copied_uniform.yscalelog == image_uniform.yscalelog
|
|
443
|
+
assert copied_uniform.yscalemin == image_uniform.yscalemin
|
|
444
|
+
assert copied_uniform.yscalemax == image_uniform.yscalemax
|
|
445
|
+
assert copied_uniform.zscalemin == image_uniform.zscalemin
|
|
446
|
+
assert copied_uniform.zscalemax == image_uniform.zscalemax
|
|
447
|
+
execenv.print(" ✓ Scale attributes correctly copied")
|
|
448
|
+
|
|
449
|
+
# Test 2: Copy image with non-uniform coordinates
|
|
450
|
+
execenv.print(" Test 2: Copy image with non-uniform coordinates")
|
|
451
|
+
image_nonuniform = sigima.objects.create_image(
|
|
452
|
+
title=title + " (non-uniform)",
|
|
453
|
+
data=data.copy(),
|
|
454
|
+
metadata=metadata.copy(),
|
|
455
|
+
units=units,
|
|
456
|
+
labels=labels,
|
|
457
|
+
)
|
|
458
|
+
# Create non-uniform coordinates (quadratic spacing)
|
|
459
|
+
ny, nx = data.shape
|
|
460
|
+
xcoords = np.linspace(0, 10, nx) ** 1.5
|
|
461
|
+
ycoords = np.linspace(0, 20, ny) ** 2
|
|
462
|
+
image_nonuniform.set_coords(xcoords=xcoords, ycoords=ycoords)
|
|
463
|
+
|
|
464
|
+
# Copy the image
|
|
465
|
+
copied_nonuniform = image_nonuniform.copy()
|
|
466
|
+
|
|
467
|
+
# Verify the copy
|
|
468
|
+
assert copied_nonuniform is not image_nonuniform
|
|
469
|
+
assert copied_nonuniform.title == image_nonuniform.title
|
|
470
|
+
assert np.array_equal(copied_nonuniform.data, image_nonuniform.data)
|
|
471
|
+
assert copied_nonuniform.data is not image_nonuniform.data
|
|
472
|
+
assert copied_nonuniform.metadata == image_nonuniform.metadata
|
|
473
|
+
assert copied_nonuniform.metadata is not image_nonuniform.metadata
|
|
474
|
+
|
|
475
|
+
# Verify non-uniform coordinates are preserved
|
|
476
|
+
assert copied_nonuniform.is_uniform_coords == image_nonuniform.is_uniform_coords
|
|
477
|
+
assert copied_nonuniform.is_uniform_coords is False
|
|
478
|
+
assert np.array_equal(copied_nonuniform.xcoords, xcoords)
|
|
479
|
+
assert np.array_equal(copied_nonuniform.ycoords, ycoords)
|
|
480
|
+
assert copied_nonuniform.xcoords is not image_nonuniform.xcoords
|
|
481
|
+
assert copied_nonuniform.ycoords is not image_nonuniform.ycoords
|
|
482
|
+
execenv.print(" ✓ Non-uniform coordinates correctly copied")
|
|
483
|
+
|
|
484
|
+
# Test 3: Copy with title override
|
|
485
|
+
execenv.print(" Test 3: Copy with custom title")
|
|
486
|
+
new_title = "Copied Image"
|
|
487
|
+
copied_with_title = image_uniform.copy(title=new_title)
|
|
488
|
+
assert copied_with_title.title == new_title
|
|
489
|
+
assert copied_with_title.is_uniform_coords is True
|
|
490
|
+
assert copied_with_title.dx == dx
|
|
491
|
+
execenv.print(" ✓ Title override works correctly")
|
|
492
|
+
|
|
493
|
+
# Test 4: Copy with dtype conversion
|
|
494
|
+
execenv.print(" Test 4: Copy with dtype conversion")
|
|
495
|
+
copied_uint16 = image_uniform.copy(dtype=np.uint16)
|
|
496
|
+
assert copied_uint16.data.dtype == np.uint16
|
|
497
|
+
assert copied_uint16.is_uniform_coords is True
|
|
498
|
+
assert copied_uint16.dx == dx
|
|
499
|
+
execenv.print(" ✓ Dtype conversion works correctly")
|
|
500
|
+
|
|
501
|
+
# Test 5: Copy with metadata filtering
|
|
502
|
+
execenv.print(" Test 5: Copy with metadata filtering")
|
|
503
|
+
copied_basic_meta = image_uniform.copy(all_metadata=False)
|
|
504
|
+
assert copied_basic_meta.is_uniform_coords is True
|
|
505
|
+
assert copied_basic_meta.dx == dx
|
|
506
|
+
execenv.print(" ✓ Metadata filtering works correctly")
|
|
507
|
+
|
|
508
|
+
execenv.print(f"{test_image_copy.__doc__}: OK")
|
|
509
|
+
|
|
510
|
+
|
|
511
|
+
def test_coordinate_conversion() -> None:
|
|
512
|
+
"""Test physical_to_indices and indices_to_physical methods"""
|
|
513
|
+
execenv.print(f"{test_coordinate_conversion.__doc__}:")
|
|
514
|
+
|
|
515
|
+
# Create a test image
|
|
516
|
+
data = np.random.rand(100, 150)
|
|
517
|
+
|
|
518
|
+
# ==================== Test 1: Uniform coordinates ====================
|
|
519
|
+
execenv.print(" Test 1: Uniform coordinates - basic conversion")
|
|
520
|
+
image_uniform = sigima.objects.create_image(
|
|
521
|
+
title="Uniform Coordinates Test", data=data.copy()
|
|
522
|
+
)
|
|
523
|
+
dx, dy, x0, y0 = 0.5, 0.8, 10.0, 20.0
|
|
524
|
+
image_uniform.set_uniform_coords(dx, dy, x0=x0, y0=y0)
|
|
525
|
+
|
|
526
|
+
# Test basic forward conversion (physical → indices)
|
|
527
|
+
physical_coords = [10.0, 20.0, 15.0, 30.0] # Two points
|
|
528
|
+
indices = image_uniform.physical_to_indices(physical_coords)
|
|
529
|
+
assert len(indices) == 4
|
|
530
|
+
assert indices[0] == 0 # (10.0 - 10.0) / 0.5 = 0
|
|
531
|
+
assert indices[1] == 0 # (20.0 - 20.0) / 0.8 = 0
|
|
532
|
+
assert indices[2] == 10 # (15.0 - 10.0) / 0.5 = 10
|
|
533
|
+
assert indices[3] == 13 # (30.0 - 20.0) / 0.8 = 12.5 → 13 (floor(12.5 + 0.5))
|
|
534
|
+
execenv.print(" ✓ Forward conversion (physical → indices) correct")
|
|
535
|
+
|
|
536
|
+
# Test basic backward conversion (indices → physical)
|
|
537
|
+
indices_input = [0, 0, 10, 12]
|
|
538
|
+
coords = image_uniform.indices_to_physical(indices_input)
|
|
539
|
+
assert len(coords) == 4
|
|
540
|
+
assert coords[0] == 10.0 # 0 * 0.5 + 10.0 = 10.0
|
|
541
|
+
assert coords[1] == 20.0 # 0 * 0.8 + 20.0 = 20.0
|
|
542
|
+
assert coords[2] == 15.0 # 10 * 0.5 + 10.0 = 15.0
|
|
543
|
+
assert coords[3] == 29.6 # 12 * 0.8 + 20.0 = 29.6
|
|
544
|
+
execenv.print(" ✓ Backward conversion (indices → physical) correct")
|
|
545
|
+
|
|
546
|
+
# Test round-trip accuracy
|
|
547
|
+
execenv.print(" Test 2: Uniform coordinates - round-trip accuracy")
|
|
548
|
+
original_physical = [12.5, 25.6, 18.3, 35.2]
|
|
549
|
+
indices_rt = image_uniform.physical_to_indices(
|
|
550
|
+
original_physical, as_float=True
|
|
551
|
+
) # Use float to preserve precision
|
|
552
|
+
recovered_physical = image_uniform.indices_to_physical(indices_rt)
|
|
553
|
+
np.testing.assert_allclose(recovered_physical, original_physical, rtol=1e-10)
|
|
554
|
+
execenv.print(" ✓ Round-trip (physical → indices → physical) preserves values")
|
|
555
|
+
|
|
556
|
+
# Test with origin offset and different pixel spacing
|
|
557
|
+
execenv.print(" Test 3: Uniform coordinates - with non-zero origin")
|
|
558
|
+
image_offset = sigima.objects.create_image(
|
|
559
|
+
title="Offset Origin Test", data=data.copy()
|
|
560
|
+
)
|
|
561
|
+
image_offset.set_uniform_coords(dx=2.0, dy=3.0, x0=-5.0, y0=-10.0)
|
|
562
|
+
phys = [-5.0, -10.0, 5.0, 20.0]
|
|
563
|
+
idx = image_offset.physical_to_indices(phys)
|
|
564
|
+
assert idx[0] == 0 # (-5.0 - (-5.0)) / 2.0 = 0
|
|
565
|
+
assert idx[1] == 0 # (-10.0 - (-10.0)) / 3.0 = 0
|
|
566
|
+
assert idx[2] == 5 # (5.0 - (-5.0)) / 2.0 = 5
|
|
567
|
+
assert idx[3] == 10 # (20.0 - (-10.0)) / 3.0 = 10
|
|
568
|
+
execenv.print(" ✓ Non-zero origin handled correctly")
|
|
569
|
+
|
|
570
|
+
# Test clipping to image boundaries
|
|
571
|
+
execenv.print(" Test 4: Uniform coordinates - clipping to boundaries")
|
|
572
|
+
out_of_bounds = [-100.0, -100.0, 1000.0, 1000.0]
|
|
573
|
+
clipped = image_uniform.physical_to_indices(out_of_bounds, clip=True)
|
|
574
|
+
assert clipped[0] == 0 # Clipped to minimum X index
|
|
575
|
+
assert clipped[1] == 0 # Clipped to minimum Y index
|
|
576
|
+
assert clipped[2] == data.shape[1] - 1 # Clipped to maximum X index (149)
|
|
577
|
+
assert clipped[3] == data.shape[0] - 1 # Clipped to maximum Y index (99)
|
|
578
|
+
execenv.print(" ✓ Clipping to image boundaries works correctly")
|
|
579
|
+
|
|
580
|
+
# Test as_float option
|
|
581
|
+
execenv.print(" Test 5: Uniform coordinates - float indices")
|
|
582
|
+
float_coords = [10.25, 20.4]
|
|
583
|
+
float_indices = image_uniform.physical_to_indices(float_coords, as_float=True)
|
|
584
|
+
int_indices = image_uniform.physical_to_indices(float_coords, as_float=False)
|
|
585
|
+
assert isinstance(float_indices[0], float)
|
|
586
|
+
assert isinstance(int_indices[0], (int, np.integer))
|
|
587
|
+
assert float_indices[0] == 0.5 # (10.25 - 10.0) / 0.5 = 0.5
|
|
588
|
+
assert int_indices[0] == 1 # floor(0.5 + 0.5) = 1
|
|
589
|
+
execenv.print(" ✓ as_float option works correctly")
|
|
590
|
+
|
|
591
|
+
# ==================== Test 6: Uniform to non-uniform conversion ==========
|
|
592
|
+
execenv.print(" Test 6: Converting uniform to non-uniform coordinates")
|
|
593
|
+
# Create a uniform image and test conversions
|
|
594
|
+
image_to_convert = sigima.objects.create_image(
|
|
595
|
+
title="Uniform to Non-uniform Test", data=data.copy()
|
|
596
|
+
)
|
|
597
|
+
dx_conv, dy_conv, x0_conv, y0_conv = 0.5, 0.8, 10.0, 20.0
|
|
598
|
+
image_to_convert.set_uniform_coords(dx_conv, dy_conv, x0=x0_conv, y0=y0_conv)
|
|
599
|
+
|
|
600
|
+
# Test conversions with uniform coordinates
|
|
601
|
+
test_phys = [12.5, 25.6, 18.3, 35.2]
|
|
602
|
+
indices_before = image_to_convert.physical_to_indices(test_phys, as_float=True)
|
|
603
|
+
physical_before = image_to_convert.indices_to_physical([10.0, 20.0, 50.0, 60.0])
|
|
604
|
+
|
|
605
|
+
# Convert to non-uniform coordinates
|
|
606
|
+
image_to_convert.switch_coords_to("non-uniform")
|
|
607
|
+
assert not image_to_convert.is_uniform_coords
|
|
608
|
+
assert len(image_to_convert.xcoords) == data.shape[1]
|
|
609
|
+
assert len(image_to_convert.ycoords) == data.shape[0]
|
|
610
|
+
|
|
611
|
+
# Verify the generated xcoords and ycoords match the uniform grid
|
|
612
|
+
expected_xcoords = np.linspace(
|
|
613
|
+
x0_conv, x0_conv + dx_conv * (data.shape[1] - 1), data.shape[1]
|
|
614
|
+
)
|
|
615
|
+
expected_ycoords = np.linspace(
|
|
616
|
+
y0_conv, y0_conv + dy_conv * (data.shape[0] - 1), data.shape[0]
|
|
617
|
+
)
|
|
618
|
+
np.testing.assert_allclose(image_to_convert.xcoords, expected_xcoords, rtol=1e-10)
|
|
619
|
+
np.testing.assert_allclose(image_to_convert.ycoords, expected_ycoords, rtol=1e-10)
|
|
620
|
+
execenv.print(" ✓ Generated non-uniform coords match uniform grid")
|
|
621
|
+
|
|
622
|
+
# Test that conversions give the same results after switching to non-uniform
|
|
623
|
+
indices_after = image_to_convert.physical_to_indices(test_phys, as_float=True)
|
|
624
|
+
physical_after = image_to_convert.indices_to_physical([10.0, 20.0, 50.0, 60.0])
|
|
625
|
+
np.testing.assert_allclose(indices_after, indices_before, rtol=1e-10)
|
|
626
|
+
np.testing.assert_allclose(physical_after, physical_before, rtol=1e-10)
|
|
627
|
+
execenv.print(" ✓ Coordinate conversions consistent after switch to non-uniform")
|
|
628
|
+
|
|
629
|
+
# ==================== Test 7: Non-uniform coordinates ====================
|
|
630
|
+
execenv.print(" Test 7: Non-uniform coordinates - basic conversion")
|
|
631
|
+
image_nonuniform = sigima.objects.create_image(
|
|
632
|
+
title="Non-Uniform Coordinates Test", data=data.copy()
|
|
633
|
+
)
|
|
634
|
+
|
|
635
|
+
# Create non-uniform coordinates with logarithmic spacing
|
|
636
|
+
ny, nx = data.shape
|
|
637
|
+
xcoords = np.logspace(0, 2, nx) # 1 to 100, logarithmic spacing
|
|
638
|
+
ycoords = np.linspace(0, 50, ny) ** 2 # 0 to 2500, quadratic spacing
|
|
639
|
+
image_nonuniform.set_coords(xcoords=xcoords, ycoords=ycoords)
|
|
640
|
+
|
|
641
|
+
# Test forward conversion with interpolation
|
|
642
|
+
phys_nu = [xcoords[0], ycoords[0], xcoords[10], ycoords[20]]
|
|
643
|
+
idx_nu = image_nonuniform.physical_to_indices(phys_nu, as_float=True)
|
|
644
|
+
assert abs(idx_nu[0] - 0.0) < 1e-10 # First X coord → index 0
|
|
645
|
+
assert abs(idx_nu[1] - 0.0) < 1e-10 # First Y coord → index 0
|
|
646
|
+
assert abs(idx_nu[2] - 10.0) < 1e-10 # 10th X coord → index 10
|
|
647
|
+
assert abs(idx_nu[3] - 20.0) < 1e-10 # 20th Y coord → index 20
|
|
648
|
+
execenv.print(" ✓ Non-uniform forward conversion correct")
|
|
649
|
+
|
|
650
|
+
# Test backward conversion with interpolation
|
|
651
|
+
idx_back = [0.0, 0.0, 10.0, 20.0]
|
|
652
|
+
coords_back = image_nonuniform.indices_to_physical(idx_back)
|
|
653
|
+
assert abs(coords_back[0] - xcoords[0]) < 1e-10
|
|
654
|
+
assert abs(coords_back[1] - ycoords[0]) < 1e-10
|
|
655
|
+
assert abs(coords_back[2] - xcoords[10]) < 1e-10
|
|
656
|
+
assert abs(coords_back[3] - ycoords[20]) < 1e-10
|
|
657
|
+
execenv.print(" ✓ Non-uniform backward conversion correct")
|
|
658
|
+
|
|
659
|
+
# Test round-trip for non-uniform coordinates
|
|
660
|
+
execenv.print(" Test 8: Non-uniform coordinates - round-trip accuracy")
|
|
661
|
+
original_nu = [xcoords[5], ycoords[15], xcoords[50], ycoords[75]]
|
|
662
|
+
indices_nu_rt = image_nonuniform.physical_to_indices(original_nu, as_float=True)
|
|
663
|
+
recovered_nu = image_nonuniform.indices_to_physical(indices_nu_rt)
|
|
664
|
+
np.testing.assert_allclose(recovered_nu, original_nu, rtol=1e-10)
|
|
665
|
+
execenv.print(" ✓ Round-trip for non-uniform coordinates preserves values")
|
|
666
|
+
|
|
667
|
+
# Test interpolation between grid points
|
|
668
|
+
execenv.print(" Test 9: Non-uniform coordinates - interpolation")
|
|
669
|
+
# Test a coordinate between grid points
|
|
670
|
+
mid_x = (xcoords[5] + xcoords[6]) / 2
|
|
671
|
+
mid_y = (ycoords[10] + ycoords[11]) / 2
|
|
672
|
+
mid_coords = [mid_x, mid_y]
|
|
673
|
+
mid_indices = image_nonuniform.physical_to_indices(mid_coords, as_float=True)
|
|
674
|
+
# Should be close to 5.5 and 10.5
|
|
675
|
+
assert 5.4 < mid_indices[0] < 5.6
|
|
676
|
+
assert 10.4 < mid_indices[1] < 10.6
|
|
677
|
+
execenv.print(" ✓ Interpolation between grid points works")
|
|
678
|
+
|
|
679
|
+
# ==================== Test 10: Edge cases ====================
|
|
680
|
+
execenv.print(" Test 10: Edge cases")
|
|
681
|
+
|
|
682
|
+
# Empty coordinate list
|
|
683
|
+
empty_coords = []
|
|
684
|
+
empty_indices = image_uniform.physical_to_indices(empty_coords)
|
|
685
|
+
assert len(empty_indices) == 0
|
|
686
|
+
execenv.print(" ✓ Empty coordinate list handled")
|
|
687
|
+
|
|
688
|
+
# Single point
|
|
689
|
+
single_point = [12.0, 25.0]
|
|
690
|
+
single_idx = image_uniform.physical_to_indices(single_point)
|
|
691
|
+
assert len(single_idx) == 2
|
|
692
|
+
execenv.print(" ✓ Single point conversion works")
|
|
693
|
+
|
|
694
|
+
# Multiple points
|
|
695
|
+
multi_points = [10.0, 20.0, 15.0, 30.0, 20.0, 40.0, 25.0, 50.0]
|
|
696
|
+
multi_idx = image_uniform.physical_to_indices(multi_points)
|
|
697
|
+
assert len(multi_idx) == 8
|
|
698
|
+
execenv.print(" ✓ Multiple points conversion works")
|
|
699
|
+
|
|
700
|
+
# Odd number of coordinates should raise ValueError
|
|
701
|
+
execenv.print(" Test 11: Error handling")
|
|
702
|
+
try:
|
|
703
|
+
image_uniform.physical_to_indices([10.0, 20.0, 15.0]) # Odd number
|
|
704
|
+
assert False, "Should have raised ValueError for odd number of coords"
|
|
705
|
+
except ValueError as e:
|
|
706
|
+
assert "even number" in str(e)
|
|
707
|
+
execenv.print(" ✓ ValueError raised for odd number of coordinates")
|
|
708
|
+
|
|
709
|
+
try:
|
|
710
|
+
image_uniform.indices_to_physical([0, 0, 5]) # Odd number
|
|
711
|
+
assert False, "Should have raised ValueError for odd number of indices"
|
|
712
|
+
except ValueError as e:
|
|
713
|
+
assert "even number" in str(e)
|
|
714
|
+
execenv.print(" ✓ ValueError raised for odd number of indices")
|
|
715
|
+
|
|
716
|
+
# Test clipping with non-uniform coordinates
|
|
717
|
+
execenv.print(" Test 12: Non-uniform coordinates - clipping")
|
|
718
|
+
out_of_bounds_nu = [-1000.0, -1000.0, 10000.0, 10000.0]
|
|
719
|
+
clipped_nu = image_nonuniform.physical_to_indices(out_of_bounds_nu, clip=True)
|
|
720
|
+
assert clipped_nu[0] == 0
|
|
721
|
+
assert clipped_nu[1] == 0
|
|
722
|
+
assert clipped_nu[2] == data.shape[1] - 1
|
|
723
|
+
assert clipped_nu[3] == data.shape[0] - 1
|
|
724
|
+
execenv.print(" ✓ Clipping works for non-uniform coordinates")
|
|
725
|
+
|
|
726
|
+
execenv.print(f"{test_coordinate_conversion.__doc__}: OK")
|
|
727
|
+
|
|
728
|
+
|
|
729
|
+
if __name__ == "__main__":
|
|
730
|
+
guiutils.enable_gui()
|
|
731
|
+
test_create_image()
|
|
732
|
+
test_image_parameters_interactive()
|
|
733
|
+
test_all_image_types()
|
|
734
|
+
test_hdf5_image_io()
|
|
735
|
+
test_create_image_from_param()
|
|
736
|
+
test_image_copy()
|
|
737
|
+
test_coordinate_conversion()
|