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,279 @@
|
|
|
1
|
+
# Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
|
|
2
|
+
|
|
3
|
+
"""Image grid ROI unit tests"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import os.path as osp
|
|
8
|
+
from copy import deepcopy
|
|
9
|
+
|
|
10
|
+
import guidata.dataset as gds
|
|
11
|
+
import numpy as np
|
|
12
|
+
from numpy.testing import assert_array_equal
|
|
13
|
+
from pytest import approx
|
|
14
|
+
|
|
15
|
+
from sigima.io import read_roi_grid, write_roi_grid
|
|
16
|
+
from sigima.objects import ImageObj, ImageROI, create_image
|
|
17
|
+
from sigima.proc.image.extraction import (
|
|
18
|
+
Direction,
|
|
19
|
+
ROIGridParam,
|
|
20
|
+
extract_roi,
|
|
21
|
+
generate_image_grid_roi,
|
|
22
|
+
)
|
|
23
|
+
from sigima.tests.data import create_grid_of_gaussian_images
|
|
24
|
+
from sigima.tests.helpers import WorkdirRestoringTempDir
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _roi_by_title(roi: list[ImageROI], title: str) -> ImageROI:
|
|
28
|
+
"""Get ROI by title."""
|
|
29
|
+
for r in roi:
|
|
30
|
+
if getattr(r, "title", None) == title:
|
|
31
|
+
return r
|
|
32
|
+
raise KeyError(title)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def test_roi_grid_basic_geometry() -> None:
|
|
36
|
+
"""2x2 grid, 50% size, centered (50% translations)."""
|
|
37
|
+
img = create_grid_of_gaussian_images() # synthetic image with known geometry
|
|
38
|
+
p = ROIGridParam()
|
|
39
|
+
p.nx = p.ny = 2
|
|
40
|
+
p.xsize = p.ysize = 50
|
|
41
|
+
p.xtranslation = p.ytranslation = 50
|
|
42
|
+
p.xdirection = p.ydirection = Direction.INCREASING
|
|
43
|
+
p.base_name = "ROI"
|
|
44
|
+
p.name_pattern = "{base}({r},{c})"
|
|
45
|
+
|
|
46
|
+
# src.roi must stay untouched (pure builder)
|
|
47
|
+
assert img.roi is None
|
|
48
|
+
|
|
49
|
+
roi = generate_image_grid_roi(img, p)
|
|
50
|
+
|
|
51
|
+
# 4 rectangles created
|
|
52
|
+
items = list(roi)
|
|
53
|
+
assert len(items) == 4
|
|
54
|
+
|
|
55
|
+
# Titles present
|
|
56
|
+
titles = {r.title for r in items}
|
|
57
|
+
assert {"ROI(1,1)", "ROI(1,2)", "ROI(2,1)", "ROI(2,2)"} <= titles
|
|
58
|
+
|
|
59
|
+
# Check one rectangle’s geometry (top-left label)
|
|
60
|
+
r11 = _roi_by_title(roi, "ROI(1,1)")
|
|
61
|
+
_x0, _y0, dx, dy = r11.get_physical_coords(img) # uses indices=False path
|
|
62
|
+
# Each cell: width/2 by height/2; ROI takes 50% of that
|
|
63
|
+
assert dx == approx((img.width / 2) * 0.5)
|
|
64
|
+
assert dy == approx((img.height / 2) * 0.5)
|
|
65
|
+
|
|
66
|
+
# Source image must still be unmodified
|
|
67
|
+
assert img.roi is None
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def test_labeling_changes_with_direction_but_geometry_set_is_invariant() -> None:
|
|
71
|
+
"""Flipping directions relabels cells
|
|
72
|
+
but the set of rectangles (geometry) stays the same."""
|
|
73
|
+
img = create_grid_of_gaussian_images()
|
|
74
|
+
base = ROIGridParam()
|
|
75
|
+
base.nx = base.ny = 2
|
|
76
|
+
base.xsize = base.ysize = 50
|
|
77
|
+
base.xtranslation = base.ytranslation = 50
|
|
78
|
+
base.base_name = "ROI"
|
|
79
|
+
base.name_pattern = "{base}({r},{c})"
|
|
80
|
+
|
|
81
|
+
# Increasing both
|
|
82
|
+
p_inc = deepcopy(base)
|
|
83
|
+
p_inc.xdirection = p_inc.ydirection = Direction.INCREASING
|
|
84
|
+
roi_inc = generate_image_grid_roi(img, p_inc)
|
|
85
|
+
geoms_inc = sorted(
|
|
86
|
+
(r.get_physical_coords(img) for r in roi_inc), key=lambda t: (t[0], t[1])
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
# Decreasing both
|
|
90
|
+
p_dec = deepcopy(base)
|
|
91
|
+
p_dec.xdirection = p_dec.ydirection = Direction.DECREASING
|
|
92
|
+
roi_dec = generate_image_grid_roi(img, p_dec)
|
|
93
|
+
geoms_dec = sorted(
|
|
94
|
+
(r.get_physical_coords(img) for r in roi_dec), key=lambda t: (t[0], t[1])
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
# Same rectangles, just different titles
|
|
98
|
+
for (x0a, y0a, dxa, dya), (x0b, y0b, dxb, dyb) in zip(geoms_inc, geoms_dec):
|
|
99
|
+
assert x0a == approx(x0b)
|
|
100
|
+
assert y0a == approx(y0b)
|
|
101
|
+
assert dxa == approx(dxb)
|
|
102
|
+
assert dya == approx(dyb)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def test_translation_semantics_delta() -> None:
|
|
106
|
+
"""Changing translation by +10% moves rectangles by 10% of image size."""
|
|
107
|
+
img = create_grid_of_gaussian_images()
|
|
108
|
+
p1 = ROIGridParam()
|
|
109
|
+
p1.nx = p1.ny = 2
|
|
110
|
+
p1.xsize = p1.ysize = 50
|
|
111
|
+
p1.xtranslation = p1.ytranslation = 50 # centered
|
|
112
|
+
p1.xdirection = p1.ydirection = Direction.INCREASING
|
|
113
|
+
|
|
114
|
+
p2 = deepcopy(p1)
|
|
115
|
+
p2.xtranslation = 60 # +10% shift in X
|
|
116
|
+
|
|
117
|
+
roi1 = generate_image_grid_roi(img, p1)
|
|
118
|
+
roi2 = generate_image_grid_roi(img, p2)
|
|
119
|
+
|
|
120
|
+
r11_1 = _roi_by_title(roi1, "ROI(1,1)")
|
|
121
|
+
r11_2 = _roi_by_title(roi2, "ROI(1,1)")
|
|
122
|
+
|
|
123
|
+
x0_1, y0_1, dx1, dy1 = r11_1.get_physical_coords(img)
|
|
124
|
+
x0_2, y0_2, dx2, dy2 = r11_2.get_physical_coords(img)
|
|
125
|
+
|
|
126
|
+
# Width should be unchanged; position should shift by exactly 10% of image width
|
|
127
|
+
assert dx1 == approx(dx2)
|
|
128
|
+
assert dy1 == approx(dy2)
|
|
129
|
+
assert (x0_2 - x0_1) == approx(0.10 * img.width)
|
|
130
|
+
assert (y0_2 - y0_1) == approx(0.00 * img.height)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def test_invalid_name_pattern_falls_back() -> None:
|
|
134
|
+
"""Malformed pattern should not break: titles fall back to 'ROI(r,c)'."""
|
|
135
|
+
img = create_grid_of_gaussian_images()
|
|
136
|
+
p = ROIGridParam()
|
|
137
|
+
p.nx = p.ny = 1
|
|
138
|
+
p.xsize = p.ysize = 50
|
|
139
|
+
p.xtranslation = p.ytranslation = 50
|
|
140
|
+
p.xdirection = p.ydirection = Direction.INCREASING
|
|
141
|
+
p.base_name = "ANY"
|
|
142
|
+
p.name_pattern = "{this_will_raise}" # invalid placeholders
|
|
143
|
+
|
|
144
|
+
roi = generate_image_grid_roi(img, p)
|
|
145
|
+
titles = [r.title for r in roi]
|
|
146
|
+
assert titles == ["ROI(1,1)"] # see fallback in implementation
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def test_zero_size_is_allowed_currently() -> None:
|
|
150
|
+
"""Current behavior: 0% sizes produce degenerate rectangles (dx==0 or dy==0)."""
|
|
151
|
+
img = create_grid_of_gaussian_images()
|
|
152
|
+
p = ROIGridParam()
|
|
153
|
+
p.nx = p.ny = 2
|
|
154
|
+
p.xsize = 0
|
|
155
|
+
p.ysize = 50
|
|
156
|
+
p.xtranslation = p.ytranslation = 50
|
|
157
|
+
|
|
158
|
+
roi = generate_image_grid_roi(img, p)
|
|
159
|
+
# All ROIs exist; all have dx == 0
|
|
160
|
+
for r in roi:
|
|
161
|
+
_x0, _y0, dx, dy = r.get_physical_coords(img)
|
|
162
|
+
assert dx == approx(0.0)
|
|
163
|
+
assert dy > 0.0
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _make_positional_image(h=6, w=9, dx=1.0, dy=1.0, x0=0.0, y0=0.0) -> ImageObj:
|
|
167
|
+
"""
|
|
168
|
+
pixel(y, x) = 1000*y + x (strictly monotone in both axes)
|
|
169
|
+
Choosing H=6, W=9 allows clean 2x3 tiling (cell=3x3).
|
|
170
|
+
"""
|
|
171
|
+
data = np.add.outer(
|
|
172
|
+
1000 * np.arange(h, dtype=np.int32), np.arange(w, dtype=np.int32)
|
|
173
|
+
)
|
|
174
|
+
img = create_image("positional", data)
|
|
175
|
+
img.set_uniform_coords(dx, dy, x0, y0)
|
|
176
|
+
img.roi = None
|
|
177
|
+
return img
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def test_roi_grid_extract_matches_pattern() -> None:
|
|
181
|
+
"""Test that the extracted ROIs match the expected pattern."""
|
|
182
|
+
img = _make_positional_image(h=6, w=9) # 6x9 → ny=2, nx=3 → cells are 3x3
|
|
183
|
+
|
|
184
|
+
# Build a full-coverage grid: each ROI == cell (no gaps/overlaps)
|
|
185
|
+
p = ROIGridParam()
|
|
186
|
+
p.nx, p.ny = 3, 2
|
|
187
|
+
p.xsize = p.ysize = 100 # ROI size == cell size
|
|
188
|
+
p.xtranslation = p.ytranslation = 50 # centered on each cell
|
|
189
|
+
p.xdirection = p.ydirection = Direction.INCREASING
|
|
190
|
+
p.base_name = "ROI"
|
|
191
|
+
p.name_pattern = "{base}({r},{c})"
|
|
192
|
+
|
|
193
|
+
roi = generate_image_grid_roi(img, p) # pure builder, no mutation
|
|
194
|
+
# Sanity: full coverage, 3*2 cells, all 3x3
|
|
195
|
+
|
|
196
|
+
params = roi.to_params(img)
|
|
197
|
+
for rparam in params:
|
|
198
|
+
# Reference window from indices
|
|
199
|
+
x0, y0, x1, y1 = rparam.get_bounding_box_indices(img)
|
|
200
|
+
ref = img.data[y0:y1, x0:x1]
|
|
201
|
+
|
|
202
|
+
# Extract via computation function
|
|
203
|
+
extracted = extract_roi(img, rparam)
|
|
204
|
+
out = extracted.data
|
|
205
|
+
|
|
206
|
+
# 1) Pixel-exact equality
|
|
207
|
+
assert_array_equal(out, ref)
|
|
208
|
+
|
|
209
|
+
# 2) Dimensions are the expected 3x3
|
|
210
|
+
assert out.shape == (3, 3)
|
|
211
|
+
|
|
212
|
+
# 3) Physical origin is consistent with bounding box (dx=dy=1, x0=y0=0)
|
|
213
|
+
px0, py0, _px1, _py1 = rparam.get_bounding_box_physical()
|
|
214
|
+
assert extracted.x0 == px0
|
|
215
|
+
assert extracted.y0 == py0
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def test_roi_grid_extract_with_translation() -> None:
|
|
219
|
+
"""Shift the grid by +10% in X (of full image width) and verify that each ROI
|
|
220
|
+
moves accordingly — the extracted content matches the shifted reference.
|
|
221
|
+
"""
|
|
222
|
+
img = _make_positional_image(h=6, w=9) # width=9 → +10% shift = 0.9 pixel
|
|
223
|
+
|
|
224
|
+
# Base centered grid (2x3)
|
|
225
|
+
p1 = ROIGridParam()
|
|
226
|
+
p1.nx, p1.ny = 3, 2
|
|
227
|
+
p1.xsize = p1.ysize = 100
|
|
228
|
+
p1.xtranslation = p1.ytranslation = 50
|
|
229
|
+
p1.xdirection = p1.ydirection = Direction.INCREASING
|
|
230
|
+
p1.base_name = "ROI"
|
|
231
|
+
p1.name_pattern = "{base}({r},{c})"
|
|
232
|
+
|
|
233
|
+
# Shifted grid (+10% in X)
|
|
234
|
+
p2 = deepcopy(p1)
|
|
235
|
+
p2.xtranslation = 60
|
|
236
|
+
|
|
237
|
+
roi1 = generate_image_grid_roi(img, p1)
|
|
238
|
+
roi2 = generate_image_grid_roi(img, p2)
|
|
239
|
+
|
|
240
|
+
# Compare first ROI of each row/col "logically" (same label), but expect
|
|
241
|
+
# a one-pixel shift when rounding indices. To avoid rounding heuristics,
|
|
242
|
+
# we compare against each ROI's own bounding box-derived slice.
|
|
243
|
+
roiparams1, roiparams2 = roi1.to_params(img), roi2.to_params(img)
|
|
244
|
+
for rp1, rp2 in zip(roiparams1, roiparams2):
|
|
245
|
+
ref1 = img.data[
|
|
246
|
+
rp1.get_bounding_box_indices(img)[1] : rp1.get_bounding_box_indices(img)[3],
|
|
247
|
+
rp1.get_bounding_box_indices(img)[0] : rp1.get_bounding_box_indices(img)[2],
|
|
248
|
+
]
|
|
249
|
+
ref2 = img.data[
|
|
250
|
+
rp2.get_bounding_box_indices(img)[1] : rp2.get_bounding_box_indices(img)[3],
|
|
251
|
+
rp2.get_bounding_box_indices(img)[0] : rp2.get_bounding_box_indices(img)[2],
|
|
252
|
+
]
|
|
253
|
+
out1 = extract_roi(img, rp1).data
|
|
254
|
+
out2 = extract_roi(img, rp2).data
|
|
255
|
+
# Both extractions must match their own references exactly
|
|
256
|
+
assert_array_equal(out1, ref1)
|
|
257
|
+
assert_array_equal(out2, ref2)
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def test_roi_grid_import_export() -> None:
|
|
261
|
+
"""Test the import and export of ROI grids."""
|
|
262
|
+
p = ROIGridParam()
|
|
263
|
+
p.nx, p.ny = 3, 2
|
|
264
|
+
p.xsize = p.ysize = 100
|
|
265
|
+
p.xtranslation = p.ytranslation = 50
|
|
266
|
+
p.xdirection = p.ydirection = Direction.INCREASING
|
|
267
|
+
p.base_name = "ROI"
|
|
268
|
+
p.name_pattern = "{base}({r},{c})"
|
|
269
|
+
|
|
270
|
+
with WorkdirRestoringTempDir() as temp_dir:
|
|
271
|
+
path = osp.join(temp_dir, "test_roi_grid.json")
|
|
272
|
+
write_roi_grid(path, p)
|
|
273
|
+
new_p = read_roi_grid(path)
|
|
274
|
+
|
|
275
|
+
gds.assert_datasets_equal(new_p, p, "Imported ROI grid does not match original")
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
if __name__ == "__main__":
|
|
279
|
+
test_roi_grid_import_export()
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Image spectrum unit test.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
# pylint: disable=invalid-name # Allows short reference names like x, y, ...
|
|
8
|
+
# pylint: disable=duplicate-code
|
|
9
|
+
|
|
10
|
+
import pytest
|
|
11
|
+
|
|
12
|
+
import sigima.tools.image
|
|
13
|
+
from sigima.tests import guiutils
|
|
14
|
+
from sigima.tests.data import get_test_image
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@pytest.mark.gui
|
|
18
|
+
def test_image_spectrum_interactive():
|
|
19
|
+
"""Interactive test of the magnitude/phase/power spectrum of an image."""
|
|
20
|
+
with guiutils.lazy_qt_app_context(force=True):
|
|
21
|
+
# pylint: disable=import-outside-toplevel
|
|
22
|
+
from sigima.tests.vistools import view_images_side_by_side
|
|
23
|
+
|
|
24
|
+
obj = get_test_image("NF 180338201.scor-data")
|
|
25
|
+
data = obj.data
|
|
26
|
+
ms = sigima.tools.image.magnitude_spectrum(data, log_scale=True)
|
|
27
|
+
ps = sigima.tools.image.phase_spectrum(data)
|
|
28
|
+
psd = sigima.tools.image.psd(data, log_scale=True)
|
|
29
|
+
images = [data, ms, ps, psd]
|
|
30
|
+
titles = [
|
|
31
|
+
"Original",
|
|
32
|
+
"Magnitude spectrum",
|
|
33
|
+
"Phase spectrum",
|
|
34
|
+
"Power spectral density",
|
|
35
|
+
]
|
|
36
|
+
view_images_side_by_side(images, titles, rows=2, title="Image spectrum")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
if __name__ == "__main__":
|
|
40
|
+
test_image_spectrum_interactive()
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Unit tests for thresholding computation functions.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import pytest
|
|
10
|
+
from skimage import filters, util
|
|
11
|
+
|
|
12
|
+
import sigima.params
|
|
13
|
+
import sigima.proc.image
|
|
14
|
+
from sigima.tests.data import get_test_image
|
|
15
|
+
from sigima.tests.helpers import check_array_result
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@pytest.mark.validation
|
|
19
|
+
def test_threshold() -> None:
|
|
20
|
+
"""Validation test for the image threshold processing."""
|
|
21
|
+
src = get_test_image("flower.npy")
|
|
22
|
+
p = sigima.params.ThresholdParam.create(value=100.0)
|
|
23
|
+
dst = sigima.proc.image.threshold(src, p)
|
|
24
|
+
exp = util.img_as_ubyte(src.data > p.value)
|
|
25
|
+
check_array_result(f"Threshold[{p.value}]", dst.data, exp)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def __generic_threshold_validation(method: str) -> None:
|
|
29
|
+
"""Generic test for thresholding methods."""
|
|
30
|
+
# See [1] in sigima\tests\image\__init__.py for more details about the validation.
|
|
31
|
+
src = get_test_image("flower.npy")
|
|
32
|
+
dst = sigima.proc.image.threshold(
|
|
33
|
+
src, sigima.params.ThresholdParam.create(method=method)
|
|
34
|
+
)
|
|
35
|
+
exp = util.img_as_ubyte(
|
|
36
|
+
src.data > getattr(filters, f"threshold_{method}")(src.data)
|
|
37
|
+
)
|
|
38
|
+
check_array_result(f"Threshold{method.capitalize()}", dst.data, exp)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@pytest.mark.validation
|
|
42
|
+
def test_threshold_isodata() -> None:
|
|
43
|
+
"""Validation test for the image threshold Isodata processing."""
|
|
44
|
+
__generic_threshold_validation("isodata")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@pytest.mark.validation
|
|
48
|
+
def test_threshold_li() -> None:
|
|
49
|
+
"""Validation test for the image threshold Li processing."""
|
|
50
|
+
__generic_threshold_validation("li")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@pytest.mark.validation
|
|
54
|
+
def test_threshold_mean() -> None:
|
|
55
|
+
"""Validation test for the image threshold Mean processing."""
|
|
56
|
+
__generic_threshold_validation("mean")
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@pytest.mark.validation
|
|
60
|
+
def test_threshold_minimum() -> None:
|
|
61
|
+
"""Validation test for the image threshold Minimum processing."""
|
|
62
|
+
__generic_threshold_validation("minimum")
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@pytest.mark.validation
|
|
66
|
+
def test_threshold_otsu() -> None:
|
|
67
|
+
"""Validation test for the image threshold Otsu processing."""
|
|
68
|
+
__generic_threshold_validation("otsu")
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@pytest.mark.validation
|
|
72
|
+
def test_threshold_triangle() -> None:
|
|
73
|
+
"""Validation test for the image threshold Triangle processing."""
|
|
74
|
+
__generic_threshold_validation("triangle")
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@pytest.mark.validation
|
|
78
|
+
def test_threshold_yen() -> None:
|
|
79
|
+
"""Validation test for the image threshold Yen processing."""
|
|
80
|
+
__generic_threshold_validation("yen")
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
if __name__ == "__main__":
|
|
84
|
+
test_threshold()
|
|
85
|
+
test_threshold_isodata()
|
|
86
|
+
test_threshold_li()
|
|
87
|
+
test_threshold_mean()
|
|
88
|
+
test_threshold_minimum()
|
|
89
|
+
test_threshold_otsu()
|
|
90
|
+
test_threshold_triangle()
|
|
91
|
+
test_threshold_yen()
|
|
File without changes
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Test adding new I/O formats
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Type
|
|
10
|
+
|
|
11
|
+
import numpy as np
|
|
12
|
+
|
|
13
|
+
from sigima.io import ImageIORegistry, SignalIORegistry
|
|
14
|
+
from sigima.io.base import FormatInfo
|
|
15
|
+
from sigima.io.image.base import SingleImageFormatBase
|
|
16
|
+
from sigima.io.signal.base import SignalFormatBase
|
|
17
|
+
from sigima.tests.env import execenv
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _get_image_format_number() -> int:
|
|
21
|
+
"""Get the number of standard image formats"""
|
|
22
|
+
return len(ImageIORegistry.get_formats())
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _add_image_format() -> Type[SingleImageFormatBase]:
|
|
26
|
+
"""Add a new image format to the registry"""
|
|
27
|
+
|
|
28
|
+
class MyImageFormat(SingleImageFormatBase):
|
|
29
|
+
"""Object representing MyImageFormat image file type"""
|
|
30
|
+
|
|
31
|
+
FORMAT_INFO = FormatInfo(
|
|
32
|
+
name="MyImageFormat",
|
|
33
|
+
extensions="*.myimg",
|
|
34
|
+
readable=True,
|
|
35
|
+
writeable=False,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
@staticmethod
|
|
39
|
+
def read_data(filename: str) -> np.ndarray:
|
|
40
|
+
"""Read data and return it
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
filename (str): path to MyImageFormat file
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
np.ndarray: image data
|
|
47
|
+
"""
|
|
48
|
+
# Implement reading logic here
|
|
49
|
+
|
|
50
|
+
return MyImageFormat
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def test_add_image_format() -> None:
|
|
54
|
+
"""Test adding a new image format"""
|
|
55
|
+
n1 = _get_image_format_number()
|
|
56
|
+
execenv.print(f"Number of standard image formats: {n1}")
|
|
57
|
+
execenv.print("Adding MyImageFormat... ", end="")
|
|
58
|
+
image_class = _add_image_format()
|
|
59
|
+
n2 = _get_image_format_number()
|
|
60
|
+
assert n2 == n1 + 1, "Image format was not added correctly"
|
|
61
|
+
execenv.print("OK")
|
|
62
|
+
execenv.print(f"New number of image formats: {n2}")
|
|
63
|
+
assert (
|
|
64
|
+
sum(isinstance(fmt, image_class) for fmt in ImageIORegistry.get_formats()) == 1
|
|
65
|
+
)
|
|
66
|
+
finfo = image_class.FORMAT_INFO
|
|
67
|
+
finfo_str = "\n".join([(" " * 4) + line for line in str(finfo).splitlines()])
|
|
68
|
+
assert finfo_str in ImageIORegistry.get_format_info(mode="text")
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _get_signal_format_number() -> int:
|
|
72
|
+
"""Get the number of standard signal formats"""
|
|
73
|
+
return len(SignalIORegistry.get_formats())
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _add_signal_format() -> Type[SignalFormatBase]:
|
|
77
|
+
"""Add a new signal format to the registry"""
|
|
78
|
+
|
|
79
|
+
class MySignalFormat(SignalFormatBase):
|
|
80
|
+
"""Object representing MySignalFormat signal file type"""
|
|
81
|
+
|
|
82
|
+
FORMAT_INFO = FormatInfo(
|
|
83
|
+
name="MySignalFormat",
|
|
84
|
+
extensions="*.mysig",
|
|
85
|
+
readable=True,
|
|
86
|
+
writeable=False,
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
def read_xydata(self, filename: str) -> np.ndarray:
|
|
90
|
+
"""Read data and metadata from file, write metadata to object, return xydata
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
filename: Name of file to read
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
NumPy array xydata
|
|
97
|
+
"""
|
|
98
|
+
# Implement reading logic here
|
|
99
|
+
print(f"Reading data from {filename}")
|
|
100
|
+
|
|
101
|
+
return MySignalFormat
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def test_add_signal_format() -> None:
|
|
105
|
+
"""Test adding a new signal format"""
|
|
106
|
+
n1 = _get_signal_format_number()
|
|
107
|
+
execenv.print(f"Number of standard signal formats: {n1}")
|
|
108
|
+
execenv.print("Adding MySignalFormat... ", end="")
|
|
109
|
+
signal_class = _add_signal_format()
|
|
110
|
+
n2 = _get_signal_format_number()
|
|
111
|
+
assert n2 == n1 + 1, "Signal format was not added correctly"
|
|
112
|
+
execenv.print("OK")
|
|
113
|
+
execenv.print(f"New number of signal formats: {n2}")
|
|
114
|
+
assert (
|
|
115
|
+
sum(isinstance(fmt, signal_class) for fmt in SignalIORegistry.get_formats())
|
|
116
|
+
== 1
|
|
117
|
+
)
|
|
118
|
+
finfo = signal_class.FORMAT_INFO
|
|
119
|
+
finfo_str = "\n".join([(" " * 4) + line for line in str(finfo).splitlines()])
|
|
120
|
+
assert finfo_str in SignalIORegistry.get_format_info(mode="text")
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
if __name__ == "__main__":
|
|
124
|
+
test_add_image_format()
|
|
125
|
+
test_add_signal_format()
|