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.
Files changed (264) hide show
  1. sigima/__init__.py +142 -2
  2. sigima/client/__init__.py +105 -0
  3. sigima/client/base.py +780 -0
  4. sigima/client/remote.py +469 -0
  5. sigima/client/stub.py +814 -0
  6. sigima/client/utils.py +90 -0
  7. sigima/config.py +444 -0
  8. sigima/data/logo/Sigima.svg +135 -0
  9. sigima/data/tests/annotations.json +798 -0
  10. sigima/data/tests/curve_fitting/exponential_fit.txt +511 -0
  11. sigima/data/tests/curve_fitting/gaussian_fit.txt +100 -0
  12. sigima/data/tests/curve_fitting/piecewiseexponential_fit.txt +1022 -0
  13. sigima/data/tests/curve_fitting/polynomial_fit.txt +100 -0
  14. sigima/data/tests/curve_fitting/twohalfgaussian_fit.txt +1000 -0
  15. sigima/data/tests/curve_formats/bandwidth.txt +201 -0
  16. sigima/data/tests/curve_formats/boxcar.npy +0 -0
  17. sigima/data/tests/curve_formats/datetime.txt +1001 -0
  18. sigima/data/tests/curve_formats/dynamic_parameters.txt +4000 -0
  19. sigima/data/tests/curve_formats/fw1e2.txt +301 -0
  20. sigima/data/tests/curve_formats/fwhm.txt +319 -0
  21. sigima/data/tests/curve_formats/multiple_curves.csv +29 -0
  22. sigima/data/tests/curve_formats/noised_saw.mat +0 -0
  23. sigima/data/tests/curve_formats/oscilloscope.csv +111 -0
  24. sigima/data/tests/curve_formats/other/other2/recursive2.txt +5 -0
  25. sigima/data/tests/curve_formats/other/recursive1.txt +5 -0
  26. sigima/data/tests/curve_formats/paracetamol.npy +0 -0
  27. sigima/data/tests/curve_formats/paracetamol.txt +1010 -0
  28. sigima/data/tests/curve_formats/paracetamol_dx_dy.csv +1000 -0
  29. sigima/data/tests/curve_formats/paracetamol_dy.csv +1001 -0
  30. sigima/data/tests/curve_formats/pulse1.npy +0 -0
  31. sigima/data/tests/curve_formats/pulse2.npy +0 -0
  32. sigima/data/tests/curve_formats/simple.txt +5 -0
  33. sigima/data/tests/curve_formats/spectrum.mca +2139 -0
  34. sigima/data/tests/curve_formats/square2.npy +0 -0
  35. sigima/data/tests/curve_formats/step.npy +0 -0
  36. sigima/data/tests/fabry-perot1.jpg +0 -0
  37. sigima/data/tests/fabry-perot2.jpg +0 -0
  38. sigima/data/tests/flower.npy +0 -0
  39. sigima/data/tests/image_formats/NF 180338201.scor-data +11003 -0
  40. sigima/data/tests/image_formats/binary_image.npy +0 -0
  41. sigima/data/tests/image_formats/binary_image.png +0 -0
  42. sigima/data/tests/image_formats/centroid_test.npy +0 -0
  43. sigima/data/tests/image_formats/coordinated_text/complex_image.txt +10011 -0
  44. sigima/data/tests/image_formats/coordinated_text/complex_ref_image.txt +10010 -0
  45. sigima/data/tests/image_formats/coordinated_text/image.txt +15 -0
  46. sigima/data/tests/image_formats/coordinated_text/image2.txt +14 -0
  47. sigima/data/tests/image_formats/coordinated_text/image_no_unit_no_label.txt +14 -0
  48. sigima/data/tests/image_formats/coordinated_text/image_with_nan.txt +15 -0
  49. sigima/data/tests/image_formats/coordinated_text/image_with_unit.txt +14 -0
  50. sigima/data/tests/image_formats/fiber.csv +480 -0
  51. sigima/data/tests/image_formats/fiber.jpg +0 -0
  52. sigima/data/tests/image_formats/fiber.png +0 -0
  53. sigima/data/tests/image_formats/fiber.txt +480 -0
  54. sigima/data/tests/image_formats/gaussian_spot_with_noise.npy +0 -0
  55. sigima/data/tests/image_formats/mr-brain.dcm +0 -0
  56. sigima/data/tests/image_formats/noised_gaussian.mat +0 -0
  57. sigima/data/tests/image_formats/sif_reader/nd_lum_image_no_glue.sif +0 -0
  58. sigima/data/tests/image_formats/sif_reader/raman1.sif +0 -0
  59. sigima/data/tests/image_formats/tiling.txt +10 -0
  60. sigima/data/tests/image_formats/uint16.tiff +0 -0
  61. sigima/data/tests/image_formats/uint8.tiff +0 -0
  62. sigima/data/tests/laser_beam/TEM00_z_13.jpg +0 -0
  63. sigima/data/tests/laser_beam/TEM00_z_18.jpg +0 -0
  64. sigima/data/tests/laser_beam/TEM00_z_23.jpg +0 -0
  65. sigima/data/tests/laser_beam/TEM00_z_30.jpg +0 -0
  66. sigima/data/tests/laser_beam/TEM00_z_35.jpg +0 -0
  67. sigima/data/tests/laser_beam/TEM00_z_40.jpg +0 -0
  68. sigima/data/tests/laser_beam/TEM00_z_45.jpg +0 -0
  69. sigima/data/tests/laser_beam/TEM00_z_50.jpg +0 -0
  70. sigima/data/tests/laser_beam/TEM00_z_55.jpg +0 -0
  71. sigima/data/tests/laser_beam/TEM00_z_60.jpg +0 -0
  72. sigima/data/tests/laser_beam/TEM00_z_65.jpg +0 -0
  73. sigima/data/tests/laser_beam/TEM00_z_70.jpg +0 -0
  74. sigima/data/tests/laser_beam/TEM00_z_75.jpg +0 -0
  75. sigima/data/tests/laser_beam/TEM00_z_80.jpg +0 -0
  76. sigima/enums.py +195 -0
  77. sigima/io/__init__.py +123 -0
  78. sigima/io/base.py +311 -0
  79. sigima/io/common/__init__.py +5 -0
  80. sigima/io/common/basename.py +164 -0
  81. sigima/io/common/converters.py +189 -0
  82. sigima/io/common/objmeta.py +181 -0
  83. sigima/io/common/textreader.py +58 -0
  84. sigima/io/convenience.py +157 -0
  85. sigima/io/enums.py +17 -0
  86. sigima/io/ftlab.py +395 -0
  87. sigima/io/image/__init__.py +9 -0
  88. sigima/io/image/base.py +177 -0
  89. sigima/io/image/formats.py +1016 -0
  90. sigima/io/image/funcs.py +414 -0
  91. sigima/io/signal/__init__.py +9 -0
  92. sigima/io/signal/base.py +129 -0
  93. sigima/io/signal/formats.py +290 -0
  94. sigima/io/signal/funcs.py +723 -0
  95. sigima/objects/__init__.py +260 -0
  96. sigima/objects/base.py +937 -0
  97. sigima/objects/image/__init__.py +88 -0
  98. sigima/objects/image/creation.py +556 -0
  99. sigima/objects/image/object.py +524 -0
  100. sigima/objects/image/roi.py +904 -0
  101. sigima/objects/scalar/__init__.py +57 -0
  102. sigima/objects/scalar/common.py +215 -0
  103. sigima/objects/scalar/geometry.py +502 -0
  104. sigima/objects/scalar/table.py +784 -0
  105. sigima/objects/shape.py +290 -0
  106. sigima/objects/signal/__init__.py +133 -0
  107. sigima/objects/signal/constants.py +27 -0
  108. sigima/objects/signal/creation.py +1428 -0
  109. sigima/objects/signal/object.py +444 -0
  110. sigima/objects/signal/roi.py +274 -0
  111. sigima/params.py +405 -0
  112. sigima/proc/__init__.py +96 -0
  113. sigima/proc/base.py +381 -0
  114. sigima/proc/decorator.py +330 -0
  115. sigima/proc/image/__init__.py +513 -0
  116. sigima/proc/image/arithmetic.py +335 -0
  117. sigima/proc/image/base.py +260 -0
  118. sigima/proc/image/detection.py +519 -0
  119. sigima/proc/image/edges.py +329 -0
  120. sigima/proc/image/exposure.py +406 -0
  121. sigima/proc/image/extraction.py +458 -0
  122. sigima/proc/image/filtering.py +219 -0
  123. sigima/proc/image/fourier.py +147 -0
  124. sigima/proc/image/geometry.py +661 -0
  125. sigima/proc/image/mathops.py +340 -0
  126. sigima/proc/image/measurement.py +195 -0
  127. sigima/proc/image/morphology.py +155 -0
  128. sigima/proc/image/noise.py +107 -0
  129. sigima/proc/image/preprocessing.py +182 -0
  130. sigima/proc/image/restoration.py +235 -0
  131. sigima/proc/image/threshold.py +217 -0
  132. sigima/proc/image/transformations.py +393 -0
  133. sigima/proc/signal/__init__.py +376 -0
  134. sigima/proc/signal/analysis.py +206 -0
  135. sigima/proc/signal/arithmetic.py +551 -0
  136. sigima/proc/signal/base.py +262 -0
  137. sigima/proc/signal/extraction.py +60 -0
  138. sigima/proc/signal/features.py +310 -0
  139. sigima/proc/signal/filtering.py +484 -0
  140. sigima/proc/signal/fitting.py +276 -0
  141. sigima/proc/signal/fourier.py +259 -0
  142. sigima/proc/signal/mathops.py +420 -0
  143. sigima/proc/signal/processing.py +580 -0
  144. sigima/proc/signal/stability.py +175 -0
  145. sigima/proc/title_formatting.py +227 -0
  146. sigima/proc/validation.py +272 -0
  147. sigima/tests/__init__.py +7 -0
  148. sigima/tests/common/__init__.py +0 -0
  149. sigima/tests/common/arithmeticparam_unit_test.py +26 -0
  150. sigima/tests/common/basename_unit_test.py +126 -0
  151. sigima/tests/common/client_unit_test.py +412 -0
  152. sigima/tests/common/converters_unit_test.py +77 -0
  153. sigima/tests/common/decorator_unit_test.py +176 -0
  154. sigima/tests/common/examples_unit_test.py +104 -0
  155. sigima/tests/common/kernel_normalization_unit_test.py +242 -0
  156. sigima/tests/common/roi_basic_unit_test.py +73 -0
  157. sigima/tests/common/roi_geometry_unit_test.py +171 -0
  158. sigima/tests/common/scalar_builder_unit_test.py +142 -0
  159. sigima/tests/common/scalar_unit_test.py +991 -0
  160. sigima/tests/common/shape_unit_test.py +183 -0
  161. sigima/tests/common/stat_unit_test.py +138 -0
  162. sigima/tests/common/title_formatting_unit_test.py +338 -0
  163. sigima/tests/common/tools_coordinates_unit_test.py +60 -0
  164. sigima/tests/common/transformations_unit_test.py +178 -0
  165. sigima/tests/common/validation_unit_test.py +205 -0
  166. sigima/tests/conftest.py +129 -0
  167. sigima/tests/data.py +998 -0
  168. sigima/tests/env.py +280 -0
  169. sigima/tests/guiutils.py +163 -0
  170. sigima/tests/helpers.py +532 -0
  171. sigima/tests/image/__init__.py +28 -0
  172. sigima/tests/image/binning_unit_test.py +128 -0
  173. sigima/tests/image/blob_detection_unit_test.py +312 -0
  174. sigima/tests/image/centroid_unit_test.py +170 -0
  175. sigima/tests/image/check_2d_array_unit_test.py +63 -0
  176. sigima/tests/image/contour_unit_test.py +172 -0
  177. sigima/tests/image/convolution_unit_test.py +178 -0
  178. sigima/tests/image/datatype_unit_test.py +67 -0
  179. sigima/tests/image/edges_unit_test.py +155 -0
  180. sigima/tests/image/enclosingcircle_unit_test.py +88 -0
  181. sigima/tests/image/exposure_unit_test.py +223 -0
  182. sigima/tests/image/fft2d_unit_test.py +189 -0
  183. sigima/tests/image/filtering_unit_test.py +166 -0
  184. sigima/tests/image/geometry_unit_test.py +654 -0
  185. sigima/tests/image/hough_circle_unit_test.py +147 -0
  186. sigima/tests/image/imageobj_unit_test.py +737 -0
  187. sigima/tests/image/morphology_unit_test.py +71 -0
  188. sigima/tests/image/noise_unit_test.py +57 -0
  189. sigima/tests/image/offset_correction_unit_test.py +72 -0
  190. sigima/tests/image/operation_unit_test.py +518 -0
  191. sigima/tests/image/peak2d_limits_unit_test.py +41 -0
  192. sigima/tests/image/peak2d_unit_test.py +133 -0
  193. sigima/tests/image/profile_unit_test.py +159 -0
  194. sigima/tests/image/projections_unit_test.py +121 -0
  195. sigima/tests/image/restoration_unit_test.py +141 -0
  196. sigima/tests/image/roi2dparam_unit_test.py +53 -0
  197. sigima/tests/image/roi_advanced_unit_test.py +588 -0
  198. sigima/tests/image/roi_grid_unit_test.py +279 -0
  199. sigima/tests/image/spectrum2d_unit_test.py +40 -0
  200. sigima/tests/image/threshold_unit_test.py +91 -0
  201. sigima/tests/io/__init__.py +0 -0
  202. sigima/tests/io/addnewformat_unit_test.py +125 -0
  203. sigima/tests/io/convenience_funcs_unit_test.py +470 -0
  204. sigima/tests/io/coordinated_text_format_unit_test.py +495 -0
  205. sigima/tests/io/datetime_csv_unit_test.py +198 -0
  206. sigima/tests/io/imageio_formats_test.py +41 -0
  207. sigima/tests/io/ioregistry_unit_test.py +69 -0
  208. sigima/tests/io/objmeta_unit_test.py +87 -0
  209. sigima/tests/io/readobj_unit_test.py +130 -0
  210. sigima/tests/io/readwriteobj_unit_test.py +67 -0
  211. sigima/tests/signal/__init__.py +0 -0
  212. sigima/tests/signal/analysis_unit_test.py +135 -0
  213. sigima/tests/signal/check_1d_arrays_unit_test.py +169 -0
  214. sigima/tests/signal/convolution_unit_test.py +404 -0
  215. sigima/tests/signal/datetime_unit_test.py +176 -0
  216. sigima/tests/signal/fft1d_unit_test.py +303 -0
  217. sigima/tests/signal/filters_unit_test.py +403 -0
  218. sigima/tests/signal/fitting_unit_test.py +929 -0
  219. sigima/tests/signal/fwhm_unit_test.py +111 -0
  220. sigima/tests/signal/noise_unit_test.py +128 -0
  221. sigima/tests/signal/offset_correction_unit_test.py +34 -0
  222. sigima/tests/signal/operation_unit_test.py +489 -0
  223. sigima/tests/signal/peakdetection_unit_test.py +145 -0
  224. sigima/tests/signal/processing_unit_test.py +657 -0
  225. sigima/tests/signal/pulse/__init__.py +112 -0
  226. sigima/tests/signal/pulse/crossing_times_unit_test.py +123 -0
  227. sigima/tests/signal/pulse/plateau_detection_unit_test.py +102 -0
  228. sigima/tests/signal/pulse/pulse_unit_test.py +1824 -0
  229. sigima/tests/signal/roi_advanced_unit_test.py +392 -0
  230. sigima/tests/signal/signalobj_unit_test.py +603 -0
  231. sigima/tests/signal/stability_unit_test.py +431 -0
  232. sigima/tests/signal/uncertainty_unit_test.py +611 -0
  233. sigima/tests/vistools.py +1030 -0
  234. sigima/tools/__init__.py +59 -0
  235. sigima/tools/checks.py +290 -0
  236. sigima/tools/coordinates.py +308 -0
  237. sigima/tools/datatypes.py +26 -0
  238. sigima/tools/image/__init__.py +97 -0
  239. sigima/tools/image/detection.py +451 -0
  240. sigima/tools/image/exposure.py +77 -0
  241. sigima/tools/image/extraction.py +48 -0
  242. sigima/tools/image/fourier.py +260 -0
  243. sigima/tools/image/geometry.py +190 -0
  244. sigima/tools/image/preprocessing.py +165 -0
  245. sigima/tools/signal/__init__.py +86 -0
  246. sigima/tools/signal/dynamic.py +254 -0
  247. sigima/tools/signal/features.py +135 -0
  248. sigima/tools/signal/filtering.py +171 -0
  249. sigima/tools/signal/fitting.py +1171 -0
  250. sigima/tools/signal/fourier.py +466 -0
  251. sigima/tools/signal/interpolation.py +70 -0
  252. sigima/tools/signal/peakdetection.py +126 -0
  253. sigima/tools/signal/pulse.py +1626 -0
  254. sigima/tools/signal/scaling.py +50 -0
  255. sigima/tools/signal/stability.py +258 -0
  256. sigima/tools/signal/windowing.py +90 -0
  257. sigima/worker.py +79 -0
  258. sigima-1.0.0.dist-info/METADATA +233 -0
  259. sigima-1.0.0.dist-info/RECORD +262 -0
  260. {sigima-0.0.1.dev0.dist-info → sigima-1.0.0.dist-info}/licenses/LICENSE +29 -29
  261. sigima-0.0.1.dev0.dist-info/METADATA +0 -60
  262. sigima-0.0.1.dev0.dist-info/RECORD +0 -6
  263. {sigima-0.0.1.dev0.dist-info → sigima-1.0.0.dist-info}/WHEEL +0 -0
  264. {sigima-0.0.1.dev0.dist-info → sigima-1.0.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,26 @@
1
+ # Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
2
+
3
+ """
4
+ Arithmetic parameters unit test.
5
+ """
6
+
7
+ # pylint: disable=invalid-name # Allows short reference names like x, y, ...
8
+
9
+ import pytest
10
+
11
+ from sigima.params import ArithmeticParam
12
+ from sigima.tests import guiutils
13
+ from sigima.tests.env import execenv
14
+
15
+
16
+ @pytest.mark.gui
17
+ def test_arithmetic_param_interactive():
18
+ """Arithmetic parameters interactive test."""
19
+ with guiutils.lazy_qt_app_context(force=True):
20
+ param = ArithmeticParam()
21
+ if param.edit():
22
+ execenv.print(param)
23
+
24
+
25
+ if __name__ == "__main__":
26
+ test_arithmetic_param_interactive()
@@ -0,0 +1,126 @@
1
+ # Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
2
+
3
+ """Unit tests for :py:func:`sigima.io.common.basename.format_basenames`."""
4
+
5
+ from __future__ import annotations
6
+
7
+ import pytest
8
+
9
+ from sigima.io.common.basename import format_basenames
10
+ from sigima.objects.image import ImageObj
11
+ from sigima.objects.signal import SignalObj
12
+
13
+
14
+ def make_signal(
15
+ title: str = "",
16
+ xlabel: str = "",
17
+ xunit: str = "",
18
+ ylabel: str = "",
19
+ yunit: str = "",
20
+ metadata: dict | None = None,
21
+ ) -> SignalObj:
22
+ """Create a SignalObj with specified attributes for testing.
23
+
24
+ Args:
25
+ title: Title of the signal.
26
+ xlabel: Label for the x-axis.
27
+ xunit: Unit for the x-axis.
28
+ ylabel: Label for the y-axis.
29
+ yunit: Unit for the y-axis.
30
+ metadata: Metadata dictionary.
31
+
32
+ Returns:
33
+ Configured SignalObj instance.
34
+ """
35
+ sig = SignalObj()
36
+ sig.title = title
37
+ sig.xlabel = xlabel
38
+ sig.xunit = xunit
39
+ sig.ylabel = ylabel
40
+ sig.yunit = yunit
41
+ sig.metadata = {} if metadata is None else metadata
42
+ return sig
43
+
44
+
45
+ def make_image(title: str = "", metadata: dict | None = None) -> ImageObj:
46
+ """Create an ImageObj with specified attributes for testing.
47
+
48
+ Args:
49
+ title: Title of the image.
50
+ metadata: Metadata dictionary.
51
+
52
+ Returns:
53
+ Configured ImageObj instance.
54
+ """
55
+ img = ImageObj()
56
+ img.title = title
57
+ img.metadata = {} if metadata is None else metadata
58
+ return img
59
+
60
+
61
+ def test_format_basenames_with_indices_and_total_count():
62
+ """Test with indexing and total count placeholders."""
63
+ objs = [make_signal("sig1"), make_signal("sig2"), make_signal("sig3")]
64
+ names = format_basenames(objs, fmt="{index:02d}_of_{count:02d}")
65
+ assert names == ["01_of_03", "02_of_03", "03_of_03"]
66
+
67
+
68
+ def test_format_basenames_with_metadata_and_axes_placeholders():
69
+ """Test with metadata and axis placeholders."""
70
+ sig = make_signal(
71
+ title="My/Signal",
72
+ xlabel="Time",
73
+ xunit="s",
74
+ ylabel="Amp",
75
+ yunit="V",
76
+ metadata={"id": 42},
77
+ )
78
+ names = format_basenames(
79
+ [sig], fmt="{title}_{xlabel}[{xunit}]_{ylabel}[{yunit}]_{metadata[id]}"
80
+ )
81
+ assert names == ["My_Signal_Time[s]_Amp[V]_42"]
82
+
83
+
84
+ def test_format_basenames_sanitization():
85
+ """Test with sanitization for titles."""
86
+ objs = [make_signal("A/B"), make_image("C/D")] # '/' must be sanitized on all OSes
87
+ names = format_basenames(objs, fmt="{title}")
88
+ assert names == ["A_B", "C_D"]
89
+
90
+
91
+ def test_format_basenames_sanitization_with_custom_replacement():
92
+ """Test with custom replacement character."""
93
+ objs = [make_signal("a/b"), make_image("c/d")]
94
+ names = format_basenames(objs, fmt="{title}", replacement="-")
95
+ assert names == ["a-b", "c-d"]
96
+
97
+
98
+ def test_format_basenames_with_unknown_metadata_key():
99
+ """Test that requesting a missing metadata key raises a KeyError."""
100
+ sig = make_signal(title="T", metadata={"other": 1})
101
+ with pytest.raises(KeyError):
102
+ format_basenames([sig], fmt="{metadata[id]}")
103
+
104
+
105
+ def test_format_basenames_with_unknown_placeholder():
106
+ """Test with unknown placeholder should raise a KeyError."""
107
+ with pytest.raises(KeyError):
108
+ format_basenames([make_signal("x")], fmt="{unknown}")
109
+
110
+
111
+ def test_format_basenames_with_direct_metadata_use():
112
+ """Test that direct {metadata} use returns empty string (silently ignored)."""
113
+ sig = make_signal(title="Test", metadata={"key1": "value1", "key2": "value2"})
114
+ names = format_basenames([sig], fmt="{title} {metadata}")
115
+ # The {metadata} placeholder should be replaced with empty string
116
+ # The trailing space gets sanitized away
117
+ assert names == ["Test"]
118
+
119
+
120
+ if __name__ == "__main__":
121
+ test_format_basenames_with_indices_and_total_count()
122
+ test_format_basenames_with_metadata_and_axes_placeholders()
123
+ test_format_basenames_sanitization()
124
+ test_format_basenames_sanitization_with_custom_replacement()
125
+ test_format_basenames_with_unknown_placeholder()
126
+ test_format_basenames_with_direct_metadata_use()
@@ -0,0 +1,412 @@
1
+ # Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
2
+
3
+ """
4
+ Sigima Client Comprehensive Headless Test
5
+
6
+ This test uses a stub XML-RPC server to emulate DataLab, allowing the test
7
+ to run without requiring a real DataLab instance.
8
+ """
9
+
10
+ # pylint: disable=invalid-name # Allows short reference names like x, y, ...
11
+ # pylint: disable=duplicate-code
12
+ # guitest: skip
13
+
14
+ from __future__ import annotations
15
+
16
+ import os.path as osp
17
+ import tempfile
18
+ from collections.abc import Generator
19
+ from contextlib import contextmanager
20
+
21
+ import numpy as np
22
+ import pytest
23
+ from guidata.env import execenv
24
+
25
+ from sigima.client.remote import SimpleRemoteProxy
26
+ from sigima.client.stub import datalab_stub_server
27
+
28
+
29
+ @contextmanager
30
+ def temporary_directory() -> Generator[str, None, None]:
31
+ """Create a temporary directory and clean-up afterwards"""
32
+ tmp = tempfile.TemporaryDirectory() # pylint: disable=consider-using-with
33
+ try:
34
+ yield tmp.name
35
+ finally:
36
+ try:
37
+ tmp.cleanup()
38
+ except (PermissionError, RecursionError):
39
+ pass
40
+
41
+
42
+ class RemoteClientTester:
43
+ """Headless remote client tester class"""
44
+
45
+ SIG_TITLES = ("Oscilloscope", "Digitizer", "Radiometer", "Voltmeter", "Sensor")
46
+ IMA_TITLES = (
47
+ "Camera",
48
+ "Streak Camera",
49
+ "Image Scanner",
50
+ "Laser Beam Profiler",
51
+ "Gated Imaging Camera",
52
+ )
53
+
54
+ def __init__(self):
55
+ """Initialize the tester"""
56
+ self.datalab = None
57
+ self.log_messages = []
58
+ self.stub_server_port = None
59
+
60
+ def log(self, message: str) -> None:
61
+ """Log message for debugging"""
62
+ self.log_messages.append(message)
63
+ execenv.print(f"[CLIENT] {message}")
64
+
65
+ def init_cdl_with_stub(self, port: int) -> bool:
66
+ """Initialize DataLab connection with stub server"""
67
+ try:
68
+ self.stub_server_port = port
69
+ self.datalab = SimpleRemoteProxy(autoconnect=False)
70
+ self.datalab.connect(port=str(port), timeout=1.0, retries=1)
71
+ self.log("✨ Initialized DataLab connection with stub server ✨")
72
+ self.log(f" Communication port: {self.datalab.port}")
73
+
74
+ # Test getting method list
75
+ methods = self.datalab.get_method_list()
76
+ self.log(f" Available methods: {len(methods)} found")
77
+ return True
78
+
79
+ except ConnectionRefusedError:
80
+ self.log("🔥 Connection refused 🔥 (Stub server is not ready)")
81
+ return False
82
+
83
+ def init_cdl(self, port: str | None = None) -> bool:
84
+ """Initialize DataLab connection
85
+
86
+ Args:
87
+ port: Port to connect to (if None, uses default)
88
+ """
89
+ try:
90
+ self.datalab = SimpleRemoteProxy(autoconnect=False)
91
+ self.datalab.connect(port=port, timeout=1.0, retries=1)
92
+ self.log("✨ Initialized DataLab connection ✨")
93
+ self.log(f" Communication port: {self.datalab.port}")
94
+
95
+ # Test getting method list
96
+ methods = self.datalab.get_method_list()
97
+ self.log(f" Available methods: {len(methods)} found")
98
+ return True
99
+
100
+ except ConnectionRefusedError:
101
+ self.log("🔥 Connection refused 🔥 (DataLab server is not ready)")
102
+ return False
103
+
104
+ def close_datalab(self) -> None:
105
+ """Close DataLab connection"""
106
+ if self.datalab is not None:
107
+ try:
108
+ self.datalab.close_application()
109
+ self.log("🎬 Closed DataLab!")
110
+ except ConnectionRefusedError:
111
+ self.log("Connection lost while closing DataLab")
112
+ finally:
113
+ self.datalab = None
114
+
115
+ def test_connection_management(self) -> None:
116
+ """Test connection initialization and method listing"""
117
+ # If we already have a connection (from stub server), skip init
118
+ if self.datalab is None:
119
+ assert self.init_cdl(), "Failed to initialize DataLab connection"
120
+
121
+ # Test method listing
122
+ methods = self.datalab.get_method_list()
123
+ assert isinstance(methods, list), "Method list should be a list"
124
+ assert len(methods) > 0, "Should have at least some methods available"
125
+
126
+ # Test basic server info
127
+ version = self.datalab.get_version()
128
+ assert isinstance(version, str), "Version should be a string"
129
+ self.log(f"DataLab version: {version}")
130
+
131
+ def add_test_signals(self) -> None:
132
+ """Add test signals to DataLab"""
133
+ if self.datalab is None:
134
+ return
135
+
136
+ x = np.linspace(0, 10, 1000)
137
+ signals_data = [
138
+ ("Sine", np.sin(x)),
139
+ ("Cosine", np.cos(x)),
140
+ ("Exponential", np.exp(-x / 5)),
141
+ ]
142
+
143
+ for title, y in signals_data:
144
+ success = self.datalab.add_signal(title, x, y)
145
+ assert success, f"Failed to add signal: {title}"
146
+ self.log(f"Added signal: {title}")
147
+
148
+ def add_test_images(self) -> None:
149
+ """Add test images to DataLab"""
150
+ if self.datalab is None:
151
+ return
152
+
153
+ images_data = [
154
+ ("Zeros", np.zeros((100, 100))),
155
+ ("Ones", np.ones((100, 100))),
156
+ ("Random", np.random.random((100, 100))),
157
+ ]
158
+
159
+ for title, z in images_data:
160
+ success = self.datalab.add_image(title, z)
161
+ assert success, f"Failed to add image: {title}"
162
+ self.log(f"Added image: {title}")
163
+
164
+ def test_object_management(self) -> None:
165
+ """Test object listing, retrieval, and manipulation"""
166
+ if self.datalab is None:
167
+ return
168
+
169
+ # Test with signals
170
+ self.datalab.set_current_panel("signal")
171
+ assert self.datalab.get_current_panel() == "signal"
172
+
173
+ titles = self.datalab.get_object_titles()
174
+ self.log(f"Signal titles: {titles}")
175
+ assert isinstance(titles, list), "Titles should be a list"
176
+
177
+ uuids = self.datalab.get_object_uuids()
178
+ self.log(f"Signal UUIDs: {uuids}")
179
+ assert isinstance(uuids, list), "UUIDs should be a list"
180
+ assert len(titles) == len(uuids), "Should have equal number of titles and UUIDs"
181
+
182
+ if titles:
183
+ # Test getting first object
184
+ obj = self.datalab.get_object(1) # Get first object
185
+ assert obj is not None, "Should be able to retrieve first object"
186
+ assert hasattr(obj, "title"), "Object should have title attribute"
187
+ self.log(f"Retrieved object: {obj.title}")
188
+
189
+ # Test getting object by UUID
190
+ first_uuid = uuids[0]
191
+ obj_by_uuid = self.datalab.get_object(first_uuid)
192
+ assert obj_by_uuid is not None, "Should retrieve object by UUID"
193
+ assert obj_by_uuid.title == obj.title, "Objects should be the same"
194
+
195
+ # Test with images
196
+ self.datalab.set_current_panel("image")
197
+ assert self.datalab.get_current_panel() == "image"
198
+
199
+ img_titles = self.datalab.get_object_titles()
200
+ self.log(f"Image titles: {img_titles}")
201
+
202
+ if img_titles:
203
+ img_obj = self.datalab.get_object(1)
204
+ assert img_obj is not None, "Should retrieve first image"
205
+ self.log(f"Retrieved image: {img_obj.title}")
206
+
207
+ def test_selection_operations(self) -> None:
208
+ """Test object selection operations"""
209
+ if self.datalab is None:
210
+ return
211
+
212
+ # Test selecting objects
213
+ self.datalab.set_current_panel("signal")
214
+ uuids = self.datalab.get_object_uuids()
215
+
216
+ if uuids:
217
+ # Select first object
218
+ self.datalab.select_objects([uuids[0]])
219
+ selected = self.datalab.get_sel_object_uuids()
220
+ assert uuids[0] in selected, "Should have selected the object"
221
+ self.log(f"Selected object: {uuids[0]}")
222
+
223
+ # Test selecting multiple objects if available
224
+ if len(uuids) > 1:
225
+ self.datalab.select_objects([uuids[0], uuids[1]])
226
+ selected = self.datalab.get_sel_object_uuids()
227
+ assert len(selected) == 2, "Should have selected 2 objects"
228
+
229
+ def test_annotations_and_shapes(self) -> None:
230
+ """Test annotation and shape operations"""
231
+ if self.datalab is None:
232
+ return
233
+
234
+ # pylint: disable=import-outside-toplevel
235
+ from plotpy.builder import make
236
+
237
+ # Test with images (annotations are more meaningful for images)
238
+ self.datalab.set_current_panel("image")
239
+ uuids = self.datalab.get_object_uuids()
240
+
241
+ if uuids:
242
+ # Add an annotation
243
+ rect = make.annotated_rectangle(10, 10, 50, 50, title="Test Rectangle")
244
+ self.datalab.add_annotations_from_items([rect])
245
+ self.log("Added annotation rectangle")
246
+
247
+ # Retrieve shapes
248
+ shapes = self.datalab.get_object_shapes()
249
+ assert isinstance(shapes, list), "Shapes should be a list"
250
+ self.log(f"Retrieved {len(shapes)} shapes")
251
+
252
+ # Add label
253
+ self.datalab.add_label_with_title("Test Label")
254
+ self.log("Added label with title")
255
+
256
+ def test_file_operations(self) -> None:
257
+ """Test file save/load operations"""
258
+ if self.datalab is None:
259
+ return
260
+
261
+ with temporary_directory() as tmpdir:
262
+ # Save to HDF5 file
263
+ fname = osp.join(tmpdir, "test_remote.h5")
264
+ self.datalab.save_to_h5_file(fname)
265
+ self.log(f"Saved data to: {fname}")
266
+ assert osp.exists(fname), "HDF5 file should exist"
267
+
268
+ # Clear all data
269
+ self.datalab.reset_all()
270
+ self.log("Reset all data")
271
+
272
+ # Verify data is cleared
273
+ titles = self.datalab.get_object_titles("signal")
274
+ assert len(titles) == 0, "Signal panel should be empty after reset"
275
+
276
+ # Reload from file
277
+ self.datalab.open_h5_files([fname], import_all=True, reset_all=False)
278
+ self.log("Reloaded data from HDF5 file")
279
+
280
+ # Verify data is restored
281
+ titles = self.datalab.get_object_titles("signal")
282
+ assert len(titles) > 0, "Should have signals after reload"
283
+
284
+ def test_computation_operations(self) -> None:
285
+ """Test computation operations"""
286
+ if self.datalab is None:
287
+ return
288
+
289
+ # Test signal computations
290
+ self.datalab.set_current_panel("signal")
291
+ uuids = self.datalab.get_object_uuids()
292
+
293
+ if uuids:
294
+ # Select first signal
295
+ self.datalab.select_objects([uuids[0]])
296
+
297
+ # Test some basic computations
298
+ try:
299
+ self.datalab.calc("log10")
300
+ self.log("Applied log10 computation")
301
+ except Exception as exc: # pylint: disable=broad-except
302
+ self.log(f"log10 computation failed: {exc}")
303
+
304
+ try:
305
+ self.datalab.calc("fft")
306
+ self.log("Applied FFT computation")
307
+ except Exception as exc: # pylint: disable=broad-except
308
+ self.log(f"FFT computation failed: {exc}")
309
+
310
+ def test_group_operations(self) -> None:
311
+ """Test group operations"""
312
+ if self.datalab is None:
313
+ return
314
+
315
+ # Add a group
316
+ self.datalab.add_group("Test Group", panel="signal")
317
+ self.log("Added test group")
318
+
319
+ # Get group information
320
+ group_info = self.datalab.get_group_titles_with_object_info()
321
+ assert isinstance(group_info, (list, tuple)), (
322
+ "Group info should be a list or tuple"
323
+ )
324
+ assert len(group_info) == 3, "Should have 3 elements (titles, uuids, titles)"
325
+ self.log(f"Groups: {group_info[0]}")
326
+
327
+ def test_metadata_operations(self) -> None:
328
+ """Test metadata operations"""
329
+ if self.datalab is None:
330
+ return
331
+
332
+ uuids = self.datalab.get_object_uuids()
333
+ if uuids:
334
+ # Select an object
335
+ self.datalab.select_objects([uuids[0]])
336
+
337
+ # Delete metadata
338
+ self.datalab.delete_metadata(refresh_plot=False, keep_roi=False)
339
+ self.log("Deleted metadata")
340
+
341
+ def run_comprehensive_test(self) -> None:
342
+ """Run all tests in sequence"""
343
+ self.log("Starting comprehensive remote client test")
344
+
345
+ try:
346
+ # Basic connection test
347
+ self.test_connection_management()
348
+
349
+ # Add test data
350
+ self.add_test_signals()
351
+ self.add_test_images()
352
+
353
+ # Test object management
354
+ self.test_object_management()
355
+
356
+ # Test selection operations
357
+ self.test_selection_operations()
358
+
359
+ # Test annotations and shapes
360
+ try:
361
+ self.test_annotations_and_shapes()
362
+ except ImportError:
363
+ self.log("PlotPy not available, skipping annotations and shapes test")
364
+
365
+ # Test computations
366
+ self.test_computation_operations()
367
+
368
+ # Test group operations
369
+ self.test_group_operations()
370
+
371
+ # Test metadata operations
372
+ self.test_metadata_operations()
373
+
374
+ # Test file operations (this will reset data)
375
+ self.test_file_operations()
376
+
377
+ self.log("✅ All tests completed successfully!")
378
+
379
+ finally:
380
+ # Always try to clean up
381
+ try:
382
+ self.datalab.reset_all()
383
+ self.log("Final cleanup: reset all data")
384
+ except Exception: # pylint: disable=broad-except
385
+ pass
386
+
387
+
388
+ def test_comprehensive_remote_client():
389
+ """Comprehensive remote client test (pytest version)"""
390
+ # First try with stub server (always available)
391
+ with datalab_stub_server() as port:
392
+ tester = RemoteClientTester()
393
+ if tester.init_cdl_with_stub(port):
394
+ try:
395
+ tester.run_comprehensive_test()
396
+ return # Test passed with stub server
397
+ finally:
398
+ tester.close_datalab()
399
+
400
+ # If stub server test failed, try with real DataLab
401
+ tester = RemoteClientTester()
402
+ if not tester.init_cdl():
403
+ pytest.skip("Neither stub server nor real DataLab server is available")
404
+
405
+ try:
406
+ tester.run_comprehensive_test()
407
+ finally:
408
+ tester.close_datalab()
409
+
410
+
411
+ if __name__ == "__main__":
412
+ test_comprehensive_remote_client()
@@ -0,0 +1,77 @@
1
+ # Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
2
+
3
+ """
4
+ Unit tests for I/O conversion functions
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import numpy as np
10
+ import pytest
11
+
12
+ from sigima.io.common import converters
13
+ from sigima.objects.image import ImageObj
14
+ from sigima.objects.signal import SignalObj
15
+
16
+
17
+ class TestConvertArrayToValidDType:
18
+ """Test suite for convert_array_to_valid_dtype function."""
19
+
20
+ def test_int_arrays(self) -> None:
21
+ """Test conversion of integer numpy arrays."""
22
+ arr = np.array([1.0, 2.0, 3.0], dtype=np.int32)
23
+ result = converters.convert_array_to_valid_dtype(arr, SignalObj.VALID_DTYPES)
24
+ assert isinstance(result, np.ndarray)
25
+ assert result.dtype == np.float32
26
+ np.testing.assert_array_almost_equal(result, arr, decimal=6)
27
+
28
+ arr = np.array([[1, 2, 3], [1.1, 2, 3]], dtype=np.uint32)
29
+ result = converters.convert_array_to_valid_dtype(arr, ImageObj.VALID_DTYPES)
30
+ assert isinstance(result, np.ndarray)
31
+ assert result.dtype == np.uint8
32
+ np.testing.assert_array_almost_equal(result, arr, decimal=6)
33
+
34
+ arr = np.array([[1, 2, 3], [1.1, 2, 1e8]], dtype=np.uint32)
35
+ result = converters.convert_array_to_valid_dtype(arr, ImageObj.VALID_DTYPES)
36
+ assert isinstance(result, np.ndarray)
37
+ assert result.dtype == np.int32
38
+ np.testing.assert_array_almost_equal(result, arr, decimal=6)
39
+
40
+ def test_float_arrays(self) -> None:
41
+ """Test conversion of float numpy arrays."""
42
+ arr = np.array([1.1, 2.2, 3.3], dtype=np.float32)
43
+ result = converters.convert_array_to_valid_dtype(arr, SignalObj.VALID_DTYPES)
44
+ assert isinstance(result, np.ndarray)
45
+ assert result.dtype == np.float32
46
+ np.testing.assert_array_almost_equal(result, arr, decimal=6)
47
+
48
+ arr = np.array([[1.1, 2.2, 3.3], [1.1, 2.2, 3.3]], dtype=np.float64)
49
+ result = converters.convert_array_to_valid_dtype(arr, ImageObj.VALID_DTYPES)
50
+ assert isinstance(result, np.ndarray)
51
+ assert result.dtype == np.float64
52
+ np.testing.assert_array_almost_equal(result, arr, decimal=6)
53
+
54
+ def test_bool_arrays(self) -> None:
55
+ """Test conversion of boolean numpy arrays."""
56
+ arr = np.array([True, False, True], dtype=np.bool_)
57
+ result = converters.convert_array_to_valid_dtype(arr, SignalObj.VALID_DTYPES)
58
+ assert isinstance(result, np.ndarray)
59
+ assert result.dtype == np.uint8
60
+
61
+ def test_empty_arrays(self) -> None:
62
+ """Test conversion of empty numpy arrays."""
63
+ arr = np.array([], dtype=np.float32)
64
+ result = converters.convert_array_to_valid_dtype(arr, SignalObj.VALID_DTYPES)
65
+ assert isinstance(result, np.ndarray)
66
+ assert result.size == 0
67
+
68
+ def test_invalid_input_type(self) -> None:
69
+ """Test that invalid input types raise TypeError."""
70
+ with pytest.raises(TypeError):
71
+ converters.convert_array_to_valid_dtype(
72
+ "not an array", SignalObj.VALID_DTYPES
73
+ )
74
+
75
+
76
+ if __name__ == "__main__":
77
+ pytest.main([__file__])