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,470 @@
|
|
|
1
|
+
# Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Unit tests for convenience I/O functions with SaveToDirectoryParam.
|
|
5
|
+
|
|
6
|
+
This module tests the `write_signals` and `write_images` functions from
|
|
7
|
+
`sigima.io.convenience`, showcasing the usage of `SaveToDirectoryParam`
|
|
8
|
+
for saving multiple objects to a directory with various configuration options.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
# pylint: disable=invalid-name # Allows short reference names like x, y, ...
|
|
12
|
+
# pylint: disable=duplicate-code
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import os
|
|
17
|
+
import os.path as osp
|
|
18
|
+
|
|
19
|
+
import numpy as np
|
|
20
|
+
|
|
21
|
+
from sigima.io import ImageIORegistry, SignalIORegistry, write_images, write_signals
|
|
22
|
+
from sigima.objects import create_image, create_signal
|
|
23
|
+
from sigima.params import SaveToDirectoryParam
|
|
24
|
+
from sigima.tests.env import execenv
|
|
25
|
+
from sigima.tests.helpers import WorkdirRestoringTempDir
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def create_test_signals() -> list:
|
|
29
|
+
"""Create a list of test signals for testing."""
|
|
30
|
+
signals = []
|
|
31
|
+
|
|
32
|
+
# Create signals with different titles and properties
|
|
33
|
+
x = np.linspace(0, 10, 100)
|
|
34
|
+
|
|
35
|
+
# Signal 1: Sine wave
|
|
36
|
+
y1 = np.sin(x)
|
|
37
|
+
signal1 = create_signal(
|
|
38
|
+
title="Sine Wave",
|
|
39
|
+
x=x,
|
|
40
|
+
y=y1,
|
|
41
|
+
metadata={"type": "sine", "frequency": "1 Hz"},
|
|
42
|
+
units=("s", "V"),
|
|
43
|
+
)
|
|
44
|
+
signals.append(signal1)
|
|
45
|
+
|
|
46
|
+
# Signal 2: Cosine wave
|
|
47
|
+
y2 = np.cos(x * 2)
|
|
48
|
+
signal2 = create_signal(
|
|
49
|
+
title="Cosine Wave",
|
|
50
|
+
x=x,
|
|
51
|
+
y=y2,
|
|
52
|
+
metadata={"type": "cosine", "frequency": "2 Hz"},
|
|
53
|
+
units=("s", "A"),
|
|
54
|
+
)
|
|
55
|
+
signals.append(signal2)
|
|
56
|
+
|
|
57
|
+
# Signal 3: Exponential decay
|
|
58
|
+
y3 = np.exp(-x / 3)
|
|
59
|
+
signal3 = create_signal(
|
|
60
|
+
title="Exponential Decay",
|
|
61
|
+
x=x,
|
|
62
|
+
y=y3,
|
|
63
|
+
metadata={"type": "exponential", "time_constant": "3 s"},
|
|
64
|
+
units=("s", "V"),
|
|
65
|
+
)
|
|
66
|
+
signals.append(signal3)
|
|
67
|
+
|
|
68
|
+
return signals
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def create_test_images() -> list:
|
|
72
|
+
"""Create a list of test images for testing."""
|
|
73
|
+
images = []
|
|
74
|
+
|
|
75
|
+
# Create images with different properties
|
|
76
|
+
|
|
77
|
+
# Image 1: Random noise
|
|
78
|
+
data1 = np.random.rand(50, 50)
|
|
79
|
+
image1 = create_image(
|
|
80
|
+
title="Random Noise",
|
|
81
|
+
data=data1,
|
|
82
|
+
metadata={"type": "noise", "distribution": "uniform"},
|
|
83
|
+
units=("px", "px", "intensity"),
|
|
84
|
+
)
|
|
85
|
+
images.append(image1)
|
|
86
|
+
|
|
87
|
+
# Image 2: Gaussian pattern
|
|
88
|
+
x, y = np.meshgrid(np.linspace(-5, 5, 50), np.linspace(-5, 5, 50))
|
|
89
|
+
data2 = np.exp(-(x**2 + y**2) / 2)
|
|
90
|
+
image2 = create_image(
|
|
91
|
+
title="Gaussian Pattern",
|
|
92
|
+
data=data2,
|
|
93
|
+
metadata={"type": "gaussian", "sigma": "1.0"},
|
|
94
|
+
units=("mm", "mm", "intensity"),
|
|
95
|
+
)
|
|
96
|
+
images.append(image2)
|
|
97
|
+
|
|
98
|
+
# Image 3: Checkerboard pattern
|
|
99
|
+
data3 = np.zeros((50, 50))
|
|
100
|
+
data3[::10, ::10] = 1
|
|
101
|
+
data3[5::10, 5::10] = 1
|
|
102
|
+
image3 = create_image(
|
|
103
|
+
title="Checkerboard",
|
|
104
|
+
data=data3,
|
|
105
|
+
metadata={"type": "pattern", "period": "10 px"},
|
|
106
|
+
units=("px", "px", "binary"),
|
|
107
|
+
)
|
|
108
|
+
images.append(image3)
|
|
109
|
+
|
|
110
|
+
return images
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def test_write_signals_basic() -> None:
|
|
114
|
+
"""Test basic functionality of write_signals with SaveToDirectoryParam."""
|
|
115
|
+
execenv.print(f"{test_write_signals_basic.__doc__}:")
|
|
116
|
+
|
|
117
|
+
with WorkdirRestoringTempDir() as tmpdir:
|
|
118
|
+
# Create test signals
|
|
119
|
+
signals = create_test_signals()
|
|
120
|
+
execenv.print(f" Created {len(signals)} test signals")
|
|
121
|
+
|
|
122
|
+
# Configure SaveToDirectoryParam
|
|
123
|
+
param = SaveToDirectoryParam()
|
|
124
|
+
param.directory = tmpdir
|
|
125
|
+
param.basename = "{title}"
|
|
126
|
+
param.extension = ".h5sig"
|
|
127
|
+
param.overwrite = False
|
|
128
|
+
|
|
129
|
+
# Write signals to directory
|
|
130
|
+
write_signals(param, signals)
|
|
131
|
+
execenv.print(f" Wrote signals to {tmpdir}")
|
|
132
|
+
|
|
133
|
+
# Verify files were created
|
|
134
|
+
expected_files = [
|
|
135
|
+
"Sine Wave.h5sig",
|
|
136
|
+
"Cosine Wave.h5sig",
|
|
137
|
+
"Exponential Decay.h5sig",
|
|
138
|
+
]
|
|
139
|
+
|
|
140
|
+
for expected_file in expected_files:
|
|
141
|
+
filepath = osp.join(tmpdir, expected_file)
|
|
142
|
+
assert osp.exists(filepath), f"Expected file {expected_file} not found"
|
|
143
|
+
execenv.print(f" ✓ Created: {expected_file}")
|
|
144
|
+
|
|
145
|
+
# Verify we can read the signal back
|
|
146
|
+
loaded_signal = SignalIORegistry.read(filepath)[0]
|
|
147
|
+
assert loaded_signal is not None
|
|
148
|
+
execenv.print(f" ✓ Verified: {expected_file}")
|
|
149
|
+
|
|
150
|
+
execenv.print(f"{test_write_signals_basic.__doc__}: OK")
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def test_write_images_basic() -> None:
|
|
154
|
+
"""Test basic functionality of write_images with SaveToDirectoryParam."""
|
|
155
|
+
execenv.print(f"{test_write_images_basic.__doc__}:")
|
|
156
|
+
|
|
157
|
+
with WorkdirRestoringTempDir() as tmpdir:
|
|
158
|
+
# Create test images
|
|
159
|
+
images = create_test_images()
|
|
160
|
+
execenv.print(f" Created {len(images)} test images")
|
|
161
|
+
|
|
162
|
+
# Configure SaveToDirectoryParam
|
|
163
|
+
param = SaveToDirectoryParam()
|
|
164
|
+
param.directory = tmpdir
|
|
165
|
+
param.basename = "{title}"
|
|
166
|
+
param.extension = ".h5ima"
|
|
167
|
+
param.overwrite = False
|
|
168
|
+
|
|
169
|
+
# Write images to directory
|
|
170
|
+
write_images(param, images)
|
|
171
|
+
execenv.print(f" Wrote images to {tmpdir}")
|
|
172
|
+
|
|
173
|
+
# Verify files were created
|
|
174
|
+
expected_files = [
|
|
175
|
+
"Random Noise.h5ima",
|
|
176
|
+
"Gaussian Pattern.h5ima",
|
|
177
|
+
"Checkerboard.h5ima",
|
|
178
|
+
]
|
|
179
|
+
|
|
180
|
+
for expected_file in expected_files:
|
|
181
|
+
filepath = osp.join(tmpdir, expected_file)
|
|
182
|
+
assert osp.exists(filepath), f"Expected file {expected_file} not found"
|
|
183
|
+
execenv.print(f" ✓ Created: {expected_file}")
|
|
184
|
+
|
|
185
|
+
# Verify we can read the image back
|
|
186
|
+
loaded_image = ImageIORegistry.read(filepath)[0]
|
|
187
|
+
assert loaded_image is not None
|
|
188
|
+
execenv.print(f" ✓ Verified: {expected_file}")
|
|
189
|
+
|
|
190
|
+
execenv.print(f"{test_write_images_basic.__doc__}: OK")
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def test_savetodir_param_formatting() -> None:
|
|
194
|
+
"""Test SaveToDirectoryParam formatting options and basename patterns."""
|
|
195
|
+
execenv.print(f"{test_savetodir_param_formatting.__doc__}:")
|
|
196
|
+
|
|
197
|
+
with WorkdirRestoringTempDir() as tmpdir:
|
|
198
|
+
# Create test signals with specific metadata
|
|
199
|
+
signals = create_test_signals()
|
|
200
|
+
|
|
201
|
+
# Test different basename patterns
|
|
202
|
+
test_cases = [
|
|
203
|
+
("{title}", ["Sine Wave.csv", "Cosine Wave.csv", "Exponential Decay.csv"]),
|
|
204
|
+
(
|
|
205
|
+
"{index:03d}_{title}",
|
|
206
|
+
[
|
|
207
|
+
"001_Sine Wave.csv",
|
|
208
|
+
"002_Cosine Wave.csv",
|
|
209
|
+
"003_Exponential Decay.csv",
|
|
210
|
+
],
|
|
211
|
+
),
|
|
212
|
+
(
|
|
213
|
+
"signal_{count}_{index}",
|
|
214
|
+
["signal_3_1.csv", "signal_3_2.csv", "signal_3_3.csv"],
|
|
215
|
+
),
|
|
216
|
+
(
|
|
217
|
+
"{metadata[type]}_signal",
|
|
218
|
+
["sine_signal.csv", "cosine_signal.csv", "exponential_signal.csv"],
|
|
219
|
+
),
|
|
220
|
+
]
|
|
221
|
+
|
|
222
|
+
for basename_pattern, expected_files in test_cases:
|
|
223
|
+
execenv.print(f" Testing pattern: {basename_pattern}")
|
|
224
|
+
|
|
225
|
+
# Configure SaveToDirectoryParam
|
|
226
|
+
param = SaveToDirectoryParam()
|
|
227
|
+
param.directory = tmpdir
|
|
228
|
+
param.basename = basename_pattern
|
|
229
|
+
param.extension = ".csv"
|
|
230
|
+
param.overwrite = True # Allow overwriting for multiple test cases
|
|
231
|
+
|
|
232
|
+
# Build filenames to verify pattern
|
|
233
|
+
filenames = param.build_filenames(signals)
|
|
234
|
+
execenv.print(f" Generated filenames: {filenames}")
|
|
235
|
+
|
|
236
|
+
# Verify expected filenames
|
|
237
|
+
assert filenames == expected_files, (
|
|
238
|
+
f"Expected {expected_files}, got {filenames}"
|
|
239
|
+
)
|
|
240
|
+
execenv.print(" ✓ Pattern matched expected filenames")
|
|
241
|
+
|
|
242
|
+
execenv.print(f"{test_savetodir_param_formatting.__doc__}: OK")
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def test_savetodir_param_collision_handling() -> None:
|
|
246
|
+
"""Test SaveToDirectoryParam collision handling and overwrite behavior."""
|
|
247
|
+
execenv.print(f"{test_savetodir_param_collision_handling.__doc__}:")
|
|
248
|
+
|
|
249
|
+
with WorkdirRestoringTempDir() as tmpdir:
|
|
250
|
+
# Create test signals with duplicate titles to force collision
|
|
251
|
+
signals = []
|
|
252
|
+
x = np.linspace(0, 10, 100)
|
|
253
|
+
|
|
254
|
+
for i in range(3):
|
|
255
|
+
y = np.sin(x + i)
|
|
256
|
+
signal = create_signal(
|
|
257
|
+
title="Test Signal", # Same title for all
|
|
258
|
+
x=x,
|
|
259
|
+
y=y,
|
|
260
|
+
metadata={"index": i},
|
|
261
|
+
)
|
|
262
|
+
signals.append(signal)
|
|
263
|
+
|
|
264
|
+
# Test collision handling without overwrite
|
|
265
|
+
param = SaveToDirectoryParam()
|
|
266
|
+
param.directory = tmpdir
|
|
267
|
+
param.basename = "{title}"
|
|
268
|
+
param.extension = ".h5sig"
|
|
269
|
+
param.overwrite = False
|
|
270
|
+
|
|
271
|
+
# Build filenames - should generate unique names
|
|
272
|
+
filenames = param.build_filenames(signals)
|
|
273
|
+
expected_files = [
|
|
274
|
+
"Test Signal.h5sig",
|
|
275
|
+
"Test Signal_1.h5sig",
|
|
276
|
+
"Test Signal_2.h5sig",
|
|
277
|
+
]
|
|
278
|
+
assert filenames == expected_files, (
|
|
279
|
+
f"Expected {expected_files}, got {filenames}"
|
|
280
|
+
)
|
|
281
|
+
execenv.print(f" ✓ Collision handling generated unique filenames: {filenames}")
|
|
282
|
+
|
|
283
|
+
# Write signals and verify files exist
|
|
284
|
+
write_signals(param, signals)
|
|
285
|
+
|
|
286
|
+
for filename in expected_files:
|
|
287
|
+
filepath = osp.join(tmpdir, filename)
|
|
288
|
+
assert osp.exists(filepath), f"File {filename} was not created"
|
|
289
|
+
execenv.print(f" ✓ Created: {filename}")
|
|
290
|
+
|
|
291
|
+
# Test overwrite behavior
|
|
292
|
+
param.overwrite = True
|
|
293
|
+
write_signals(param, signals[:1]) # Write only first signal
|
|
294
|
+
|
|
295
|
+
# With overwrite=True, should only create the first file
|
|
296
|
+
filepath = osp.join(tmpdir, "Test Signal.h5sig")
|
|
297
|
+
assert osp.exists(filepath), "File should exist after overwrite"
|
|
298
|
+
execenv.print(" ✓ Overwrite test passed")
|
|
299
|
+
|
|
300
|
+
execenv.print(f"{test_savetodir_param_collision_handling.__doc__}: OK")
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def test_savetodir_param_metadata_access() -> None:
|
|
304
|
+
"""Test SaveToDirectoryParam accessing metadata fields in basename patterns."""
|
|
305
|
+
execenv.print(f"{test_savetodir_param_metadata_access.__doc__}:")
|
|
306
|
+
|
|
307
|
+
with WorkdirRestoringTempDir() as tmpdir:
|
|
308
|
+
# Create signals with rich metadata
|
|
309
|
+
signals = []
|
|
310
|
+
x = np.linspace(0, 10, 100)
|
|
311
|
+
|
|
312
|
+
metadata_list = [
|
|
313
|
+
{"experiment": "exp001", "sensor": "A", "temperature": "25C"},
|
|
314
|
+
{"experiment": "exp002", "sensor": "B", "temperature": "30C"},
|
|
315
|
+
{"experiment": "exp003", "sensor": "C", "temperature": "35C"},
|
|
316
|
+
]
|
|
317
|
+
|
|
318
|
+
for i, metadata in enumerate(metadata_list):
|
|
319
|
+
y = np.sin(x + i)
|
|
320
|
+
signal = create_signal(title=f"Signal {i + 1}", x=x, y=y, metadata=metadata)
|
|
321
|
+
signals.append(signal)
|
|
322
|
+
|
|
323
|
+
# Test accessing nested metadata in basename pattern
|
|
324
|
+
param = SaveToDirectoryParam()
|
|
325
|
+
param.directory = tmpdir
|
|
326
|
+
param.basename = (
|
|
327
|
+
"{metadata[experiment]}_{metadata[sensor]}_{metadata[temperature]}"
|
|
328
|
+
)
|
|
329
|
+
param.extension = ".csv"
|
|
330
|
+
param.overwrite = False
|
|
331
|
+
|
|
332
|
+
# Build and verify filenames
|
|
333
|
+
filenames = param.build_filenames(signals)
|
|
334
|
+
expected_files = ["exp001_A_25C.csv", "exp002_B_30C.csv", "exp003_C_35C.csv"]
|
|
335
|
+
|
|
336
|
+
assert filenames == expected_files, (
|
|
337
|
+
f"Expected {expected_files}, got {filenames}"
|
|
338
|
+
)
|
|
339
|
+
execenv.print(f" ✓ Metadata access in basename patterns: {filenames}")
|
|
340
|
+
|
|
341
|
+
# Write signals to verify the full workflow
|
|
342
|
+
write_signals(param, signals)
|
|
343
|
+
|
|
344
|
+
for filename in expected_files:
|
|
345
|
+
filepath = osp.join(tmpdir, filename)
|
|
346
|
+
assert osp.exists(filepath), f"File {filename} was not created"
|
|
347
|
+
execenv.print(f" ✓ Created: {filename}")
|
|
348
|
+
|
|
349
|
+
execenv.print(f"{test_savetodir_param_metadata_access.__doc__}: OK")
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def test_savetodir_param_units_formatting() -> None:
|
|
353
|
+
"""Test SaveToDirectoryParam accessing units in basename patterns."""
|
|
354
|
+
execenv.print(f"{test_savetodir_param_units_formatting.__doc__}:")
|
|
355
|
+
|
|
356
|
+
with WorkdirRestoringTempDir() as tmpdir:
|
|
357
|
+
# Create signals with different units
|
|
358
|
+
signals = []
|
|
359
|
+
x = np.linspace(0, 10, 100)
|
|
360
|
+
|
|
361
|
+
units_list = [("s", "V"), ("ms", "mV"), ("us", "uV")]
|
|
362
|
+
|
|
363
|
+
for i, (xunit, yunit) in enumerate(units_list):
|
|
364
|
+
y = np.sin(x + i)
|
|
365
|
+
signal = create_signal(
|
|
366
|
+
title=f"Signal {i + 1}", x=x, y=y, units=(xunit, yunit)
|
|
367
|
+
)
|
|
368
|
+
signals.append(signal)
|
|
369
|
+
|
|
370
|
+
# Test accessing units in basename pattern
|
|
371
|
+
param = SaveToDirectoryParam()
|
|
372
|
+
param.directory = tmpdir
|
|
373
|
+
param.basename = "{title}_{xunit}_{yunit}"
|
|
374
|
+
param.extension = ".csv"
|
|
375
|
+
param.overwrite = False
|
|
376
|
+
|
|
377
|
+
# Build and verify filenames
|
|
378
|
+
filenames = param.build_filenames(signals)
|
|
379
|
+
expected_files = [
|
|
380
|
+
"Signal 1_s_V.csv",
|
|
381
|
+
"Signal 2_ms_mV.csv",
|
|
382
|
+
"Signal 3_us_uV.csv",
|
|
383
|
+
]
|
|
384
|
+
|
|
385
|
+
assert filenames == expected_files, (
|
|
386
|
+
f"Expected {expected_files}, got {filenames}"
|
|
387
|
+
)
|
|
388
|
+
execenv.print(f" ✓ Units access in basename patterns: {filenames}")
|
|
389
|
+
|
|
390
|
+
# Write signals to verify the full workflow
|
|
391
|
+
write_signals(param, signals)
|
|
392
|
+
|
|
393
|
+
for filename in expected_files:
|
|
394
|
+
filepath = osp.join(tmpdir, filename)
|
|
395
|
+
assert osp.exists(filepath), f"File {filename} was not created"
|
|
396
|
+
execenv.print(f" ✓ Created: {filename}")
|
|
397
|
+
|
|
398
|
+
execenv.print(f"{test_savetodir_param_units_formatting.__doc__}: OK")
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
def test_savetodir_param_edge_cases() -> None:
|
|
402
|
+
"""Test SaveToDirectoryParam edge cases and error handling."""
|
|
403
|
+
execenv.print(f"{test_savetodir_param_edge_cases.__doc__}:")
|
|
404
|
+
|
|
405
|
+
with WorkdirRestoringTempDir() as tmpdir:
|
|
406
|
+
# Test with empty signals list
|
|
407
|
+
param = SaveToDirectoryParam()
|
|
408
|
+
param.directory = tmpdir
|
|
409
|
+
param.basename = "{title}"
|
|
410
|
+
param.extension = ".h5sig"
|
|
411
|
+
param.overwrite = False
|
|
412
|
+
|
|
413
|
+
# Should handle empty list gracefully
|
|
414
|
+
write_signals(param, [])
|
|
415
|
+
execenv.print(" ✓ Handled empty signals list")
|
|
416
|
+
|
|
417
|
+
# Test with signals having special characters in titles
|
|
418
|
+
signals = []
|
|
419
|
+
x = np.linspace(0, 10, 100)
|
|
420
|
+
y = np.sin(x)
|
|
421
|
+
|
|
422
|
+
special_titles = [
|
|
423
|
+
"Signal/with/slashes",
|
|
424
|
+
"Signal:with:colons",
|
|
425
|
+
"Signal with spaces",
|
|
426
|
+
"Signal_with_underscores",
|
|
427
|
+
]
|
|
428
|
+
|
|
429
|
+
for title in special_titles:
|
|
430
|
+
signal = create_signal(title=title, x=x, y=y)
|
|
431
|
+
signals.append(signal)
|
|
432
|
+
|
|
433
|
+
# Test filename generation with special characters
|
|
434
|
+
filenames = param.build_filenames(signals)
|
|
435
|
+
execenv.print(f" Generated filenames with special chars: {filenames}")
|
|
436
|
+
|
|
437
|
+
# All filenames should be valid (no slashes/colons in actual filenames)
|
|
438
|
+
for filename in filenames:
|
|
439
|
+
assert "/" not in filename or "\\" not in filename, (
|
|
440
|
+
f"Invalid filename: {filename}"
|
|
441
|
+
)
|
|
442
|
+
|
|
443
|
+
execenv.print(" ✓ Handled special characters in titles")
|
|
444
|
+
|
|
445
|
+
# Test with very long directory path
|
|
446
|
+
long_subdir = "a" * 50 # Create a long subdirectory name
|
|
447
|
+
long_dir = osp.join(tmpdir, long_subdir)
|
|
448
|
+
os.makedirs(long_dir, exist_ok=True)
|
|
449
|
+
|
|
450
|
+
param.directory = long_dir
|
|
451
|
+
param.basename = "test"
|
|
452
|
+
param.extension = ".h5sig"
|
|
453
|
+
|
|
454
|
+
# Should handle long paths
|
|
455
|
+
write_signals(param, signals[:1])
|
|
456
|
+
expected_path = osp.join(long_dir, "test.h5sig")
|
|
457
|
+
assert osp.exists(expected_path), "File should be created in long path"
|
|
458
|
+
execenv.print(" ✓ Handled long directory path")
|
|
459
|
+
|
|
460
|
+
execenv.print(f"{test_savetodir_param_edge_cases.__doc__}: OK")
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
if __name__ == "__main__":
|
|
464
|
+
test_write_signals_basic()
|
|
465
|
+
test_write_images_basic()
|
|
466
|
+
test_savetodir_param_formatting()
|
|
467
|
+
test_savetodir_param_collision_handling()
|
|
468
|
+
test_savetodir_param_metadata_access()
|
|
469
|
+
test_savetodir_param_units_formatting()
|
|
470
|
+
test_savetodir_param_edge_cases()
|