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,469 @@
1
+ # Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
2
+
3
+ """
4
+ Sigima Client Remote Control
5
+ ----------------------------
6
+
7
+ This module provides utilities to control DataLab from a Python script (e.g. with
8
+ Spyder) or from a Jupyter notebook.
9
+
10
+ The :class:`SimpleRemoteProxy` class provides the main interface
11
+ to DataLab XML-RPC server.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import configparser as cp
17
+ import json
18
+ import os
19
+ import os.path as osp
20
+ import sys
21
+ import time
22
+ import warnings
23
+ from xmlrpc.client import ServerProxy
24
+
25
+ import guidata.dataset as gds
26
+ import numpy as np
27
+ from guidata.env import execenv
28
+ from guidata.io import JSONReader, JSONWriter
29
+ from guidata.userconfig import get_config_basedir
30
+ from packaging.version import Version
31
+
32
+ from sigima.client.base import SimpleBaseProxy
33
+ from sigima.client.utils import (
34
+ array_to_rpcbinary,
35
+ dataset_to_rpcjson,
36
+ rpcjson_to_dataset,
37
+ )
38
+ from sigima.objects import ImageObj, SignalObj
39
+
40
+ # pylint: disable=invalid-name # Allows short reference names like x, y, ...
41
+ # pylint: disable=duplicate-code
42
+
43
+ __required_server_version__ = "1.0.0" # Minimum required DataLab server version
44
+
45
+ XMLRPCPORT_ENV = "DATALAB_XMLRPCPORT"
46
+
47
+
48
+ def get_xmlrpcport_from_env() -> int | None:
49
+ """Get XML-RPC port number from environment variable."""
50
+ try:
51
+ return int(os.environ.get(XMLRPCPORT_ENV))
52
+ except (TypeError, ValueError):
53
+ return None
54
+
55
+
56
+ def get_cdl_xmlrpc_port():
57
+ """Return DataLab current XML-RPC port.
58
+
59
+ This function attempts to read the XML-RPC port from DataLab's configuration file.
60
+ It first tries the versioned configuration folders (starting with the latest
61
+ version), and falls back to the legacy .DataLab folder for backward compatibility
62
+ with DataLab v0.x.
63
+
64
+ Returns:
65
+ str: XML-RPC port number
66
+
67
+ Raises:
68
+ ConnectionRefusedError: DataLab has not yet been executed or configuration
69
+ file is not accessible
70
+ """
71
+ if sys.platform == "win32" and "HOME" in os.environ:
72
+ os.environ.pop("HOME") # Avoid getting old WinPython settings dir
73
+
74
+ config_basedir = get_config_basedir()
75
+
76
+ # List of configuration folders to try, in order of preference
77
+ # Try versioned folders first (v3, v2, v1), then legacy .DataLab
78
+ config_folders = [
79
+ (".DataLab_v3", "DataLab_v3.ini"), # Future versions
80
+ (".DataLab_v2", "DataLab_v2.ini"),
81
+ (".DataLab_v1", "DataLab_v1.ini"), # Current stable (v1.x)
82
+ (".DataLab", "DataLab.ini"), # Legacy (v0.x)
83
+ ]
84
+
85
+ # Try each configuration folder in order
86
+ for folder_name, ini_name in config_folders:
87
+ fname = osp.join(config_basedir, folder_name, ini_name)
88
+ if osp.exists(fname):
89
+ ini = cp.ConfigParser()
90
+ try:
91
+ ini.read(fname)
92
+ port = ini.get("main", "rpc_server_port")
93
+ # Successfully read port from this version's config
94
+ return port
95
+ except (cp.NoSectionError, cp.NoOptionError):
96
+ # Config file exists but doesn't have the port yet, try next version
97
+ continue
98
+
99
+ # No valid configuration found
100
+ raise ConnectionRefusedError(
101
+ "DataLab has not yet been executed or no valid configuration found"
102
+ )
103
+
104
+
105
+ def items_to_json(items: list) -> str | None:
106
+ """Convert plot items to JSON string.
107
+
108
+ Args:
109
+ items (list): list of plot items
110
+
111
+ Returns:
112
+ str: JSON string or None if items is empty
113
+ """
114
+ from plotpy.io import save_items # pylint: disable=import-outside-toplevel
115
+
116
+ if items:
117
+ writer = JSONWriter(None)
118
+ save_items(writer, items)
119
+ return writer.get_json(indent=4)
120
+ return None
121
+
122
+
123
+ def json_to_items(json_str: str | None) -> list:
124
+ """Convert JSON string to plot items.
125
+
126
+ Args:
127
+ json_str (str): JSON string or None
128
+
129
+ Returns:
130
+ list: list of plot items
131
+ """
132
+ from plotpy.io import load_items # pylint: disable=import-outside-toplevel
133
+
134
+ items = []
135
+ if json_str:
136
+ try:
137
+ for item in load_items(JSONReader(json_str)):
138
+ items.append(item)
139
+ except json.decoder.JSONDecodeError:
140
+ pass
141
+ return items
142
+
143
+
144
+ class SimpleRemoteProxy(SimpleBaseProxy):
145
+ """Object representing a proxy/client to DataLab XML-RPC server.
146
+ This object is used to call DataLab functions from a Python script.
147
+
148
+ This is a subset of DataLab's `RemoteClient` class, with only the methods
149
+ that do not require DataLab object model to be implemented.
150
+
151
+ Args:
152
+ autoconnect: If True, automatically connect to DataLab XML-RPC server.
153
+ Defaults to True.
154
+
155
+ Raises:
156
+ ConnectionRefusedError: DataLab is currently not running
157
+
158
+ Examples:
159
+ Here is a simple example of how to use SimpleRemoteProxy in a Python script
160
+ or in a Jupyter notebook:
161
+
162
+ >>> from sigima.client import SimpleRemoteProxy
163
+ >>> proxy = SimpleRemoteProxy() # autoconnect is on by default
164
+ Connecting to DataLab XML-RPC server...OK (port: 28867)
165
+ >>> proxy.get_version()
166
+ '1.0.0'
167
+ >>> proxy.add_signal("toto", np.array([1., 2., 3.]), np.array([4., 5., -1.]))
168
+ True
169
+ >>> proxy.get_object_titles()
170
+ ['toto']
171
+ >>> proxy["toto"]
172
+ <sigima.objects.signal.SignalObj at 0x7f7f1c0b4a90>
173
+ >>> "toto" in proxy
174
+ True
175
+ >>> proxy[1]
176
+ <sigima.objects.signal.SignalObj at 0x7f7f1c0b4a90>
177
+ >>> proxy[1].data
178
+ array([1., 2., 3.])
179
+ """
180
+
181
+ def __init__(self, autoconnect: bool = True) -> None:
182
+ super().__init__()
183
+ self.port: str = None
184
+ self._datalab: ServerProxy
185
+ if autoconnect:
186
+ self.connect()
187
+
188
+ def __connect_to_server(self, port: str | None = None) -> None:
189
+ """Connect to DataLab XML-RPC server.
190
+
191
+ Args:
192
+ port (str | None): XML-RPC port to connect to. If not specified,
193
+ the port is automatically retrieved from DataLab configuration.
194
+
195
+ Raises:
196
+ ConnectionRefusedError: DataLab is currently not running
197
+ """
198
+ if port is None:
199
+ port = get_xmlrpcport_from_env()
200
+ if port is None:
201
+ port = get_cdl_xmlrpc_port()
202
+ self.port = port
203
+ self._datalab = ServerProxy(f"http://127.0.0.1:{port}", allow_none=True)
204
+ try:
205
+ version = self.get_version()
206
+ except ConnectionRefusedError as exc:
207
+ raise ConnectionRefusedError("DataLab is currently not running") from exc
208
+
209
+ # If DataLab version is not compatible with this client, show a warning
210
+ # pylint: disable=cyclic-import
211
+ from sigima import __version__ # pylint: disable=import-outside-toplevel
212
+
213
+ server_ver = Version(version)
214
+ client_ver = Version(__version__)
215
+ required_ver = Version(__required_server_version__)
216
+
217
+ # Compare base versions (ignore pre-release/dev identifiers)
218
+ # This allows 1.0.0b1 to satisfy >= 1.0.0 requirement
219
+ if server_ver.base_version < required_ver.base_version:
220
+ warnings.warn(
221
+ f"DataLab server version ({server_ver}) may not be fully compatible "
222
+ f"with Sigima client version {client_ver} "
223
+ f"(requires DataLab >= {__required_server_version__}).\n"
224
+ f"Please upgrade DataLab to {__required_server_version__} or higher.",
225
+ UserWarning,
226
+ stacklevel=2,
227
+ )
228
+
229
+ def connect(
230
+ self,
231
+ port: str | None = None,
232
+ timeout: float | None = None,
233
+ retries: int | None = None,
234
+ ) -> None:
235
+ """Try to connect to DataLab XML-RPC server.
236
+
237
+ Args:
238
+ port (str | None): XML-RPC port to connect to. If not specified,
239
+ the port is automatically retrieved from DataLab configuration.
240
+ timeout (float | None): Maximum time to wait for connection in seconds.
241
+ Defaults to 5.0. This is the total maximum wait time, not per retry.
242
+ retries (int | None): Number of retries. Defaults to 10. This parameter
243
+ is deprecated and will be removed in a future version (kept for
244
+ backward compatibility).
245
+
246
+ Raises:
247
+ ConnectionRefusedError: Unable to connect to DataLab
248
+ ValueError: Invalid timeout (must be >= 0.0)
249
+ ValueError: Invalid number of retries (must be >= 1)
250
+ """
251
+ timeout = 5.0 if timeout is None else timeout
252
+ retries = 10 if retries is None else retries # Kept for backward compatibility
253
+ if timeout < 0.0:
254
+ raise ValueError("timeout must be >= 0.0")
255
+ if retries < 1:
256
+ raise ValueError("retries must be >= 1")
257
+
258
+ execenv.print("Connecting to DataLab XML-RPC server...", end="")
259
+
260
+ # Use exponential backoff for more efficient retrying
261
+ start_time = time.time()
262
+ poll_interval = 0.1 # Start with 100ms
263
+ max_poll_interval = 1.0 # Cap at 1 second
264
+
265
+ while True:
266
+ try:
267
+ # Try to connect - this may fail if DataLab hasn't written its
268
+ # config file yet, so we retry it in the loop
269
+ self.__connect_to_server(port=port)
270
+ elapsed = time.time() - start_time
271
+ execenv.print(f"OK (port: {self.port}, connected in {elapsed:.1f}s)")
272
+ return
273
+ except (ConnectionRefusedError, OSError) as exc:
274
+ # Catch both ConnectionRefusedError and OSError (includes socket errors)
275
+ elapsed = time.time() - start_time
276
+ if elapsed >= timeout:
277
+ execenv.print("KO")
278
+ raise ConnectionRefusedError(
279
+ f"Unable to connect to DataLab after {elapsed:.1f}s"
280
+ ) from exc
281
+ # Wait before next retry with exponential backoff
282
+ time.sleep(poll_interval)
283
+ poll_interval = min(poll_interval * 1.5, max_poll_interval)
284
+
285
+ def disconnect(self) -> None:
286
+ """Disconnect from DataLab XML-RPC server."""
287
+ # This is not mandatory with XML-RPC, but if we change protocol in the
288
+ # future, it may be useful to have a disconnect method.
289
+ self._datalab = None
290
+
291
+ def is_connected(self) -> bool:
292
+ """Return True if connected to DataLab XML-RPC server."""
293
+ if self._datalab is not None:
294
+ try:
295
+ self.get_version()
296
+ return True
297
+ except ConnectionRefusedError:
298
+ self._datalab = None
299
+ return False
300
+
301
+ def get_method_list(self) -> list[str]:
302
+ """Return list of available methods."""
303
+ return self._datalab.system.listMethods()
304
+
305
+ # === Following methods should match the register functions in XML-RPC server
306
+
307
+ def add_signal(
308
+ self,
309
+ title: str,
310
+ xdata: np.ndarray,
311
+ ydata: np.ndarray,
312
+ xunit: str | None = None,
313
+ yunit: str | None = None,
314
+ xlabel: str | None = None,
315
+ ylabel: str | None = None,
316
+ ) -> bool: # pylint: disable=too-many-arguments
317
+ """Add signal data to DataLab.
318
+
319
+ Args:
320
+ title (str): Signal title
321
+ xdata (numpy.ndarray): X data
322
+ ydata (numpy.ndarray): Y data
323
+ xunit (str | None): X unit. Defaults to None.
324
+ yunit (str | None): Y unit. Defaults to None.
325
+ xlabel (str | None): X label. Defaults to None.
326
+ ylabel (str | None): Y label. Defaults to None.
327
+
328
+ Returns:
329
+ bool: True if signal was added successfully, False otherwise
330
+
331
+ Raises:
332
+ ValueError: Invalid xdata dtype
333
+ ValueError: Invalid ydata dtype
334
+ """
335
+ xbinary = array_to_rpcbinary(xdata)
336
+ ybinary = array_to_rpcbinary(ydata)
337
+ return self._datalab.add_signal(
338
+ title, xbinary, ybinary, xunit, yunit, xlabel, ylabel
339
+ )
340
+
341
+ # pylint: disable=too-many-arguments
342
+ def add_image(
343
+ self,
344
+ title: str,
345
+ data: np.ndarray,
346
+ xunit: str | None = None,
347
+ yunit: str | None = None,
348
+ zunit: str | None = None,
349
+ xlabel: str | None = None,
350
+ ylabel: str | None = None,
351
+ zlabel: str | None = None,
352
+ ) -> bool:
353
+ """Add image data to DataLab.
354
+
355
+ Args:
356
+ title (str): Image title
357
+ data (numpy.ndarray): Image data
358
+ xunit (str | None): X unit. Defaults to None.
359
+ yunit (str | None): Y unit. Defaults to None.
360
+ zunit (str | None): Z unit. Defaults to None.
361
+ xlabel (str | None): X label. Defaults to None.
362
+ ylabel (str | None): Y label. Defaults to None.
363
+ zlabel (str | None): Z label. Defaults to None.
364
+
365
+ Returns:
366
+ bool: True if image was added successfully, False otherwise
367
+
368
+ Raises:
369
+ ValueError: Invalid data dtype
370
+ """
371
+ zbinary = array_to_rpcbinary(data)
372
+ return self._datalab.add_image(
373
+ title, zbinary, xunit, yunit, zunit, xlabel, ylabel, zlabel
374
+ )
375
+
376
+ def add_object(
377
+ self, obj: SignalObj | ImageObj, group_id: str = "", set_current: bool = True
378
+ ) -> None:
379
+ """Add object to DataLab.
380
+
381
+ Args:
382
+ obj: Signal or image object
383
+ group_id: group id in which to add the object. Defaults to ""
384
+ set_current: if True, set the added object as current
385
+ """
386
+ self._datalab.add_object(obj.serialize(), group_id, set_current)
387
+
388
+ def load_from_directory(self, path: str) -> None:
389
+ """Open objects from directory in current panel (signals/images).
390
+
391
+ Args:
392
+ path: directory path
393
+ """
394
+ self._datalab.load_from_directory(path)
395
+
396
+ def calc(self, name: str, param: gds.DataSet | None = None) -> None:
397
+ """Call compute function ``name`` in current panel's processor.
398
+
399
+ Args:
400
+ name: Compute function name
401
+ param: Compute function parameter. Defaults to None.
402
+
403
+ Raises:
404
+ ValueError: unknown function
405
+ """
406
+ if param is None:
407
+ return self._datalab.calc(name)
408
+ return self._datalab.calc(name, dataset_to_rpcjson(param))
409
+
410
+ def get_object(
411
+ self,
412
+ nb_id_title: int | str | None = None,
413
+ panel: str | None = None,
414
+ ) -> SignalObj | ImageObj:
415
+ """Get object (signal/image) from index.
416
+
417
+ Args:
418
+ nb_id_title: Object number, or object id, or object title.
419
+ Defaults to None (current object).
420
+ panel: Panel name. Defaults to None (current panel).
421
+
422
+ Returns:
423
+ Object
424
+
425
+ Raises:
426
+ KeyError: if object not found
427
+ """
428
+ param_data = self._datalab.get_object(nb_id_title, panel)
429
+ if param_data is None:
430
+ return None
431
+ return rpcjson_to_dataset(param_data)
432
+
433
+ def get_object_shapes(
434
+ self,
435
+ nb_id_title: int | str | None = None,
436
+ panel: str | None = None,
437
+ ) -> list:
438
+ """Get plot item shapes associated to object (signal/image).
439
+
440
+ Args:
441
+ nb_id_title: Object number, or object id, or object title.
442
+ Defaults to None (current object).
443
+ panel: Panel name. Defaults to None (current panel).
444
+
445
+ Returns:
446
+ List of plot item shapes
447
+ """
448
+ items_json = self._datalab.get_object_shapes(nb_id_title, panel)
449
+ return json_to_items(items_json)
450
+
451
+ def add_annotations_from_items(
452
+ self, items: list, refresh_plot: bool = True, panel: str | None = None
453
+ ) -> None:
454
+ """Add object annotations (annotation plot items).
455
+
456
+ .. note:: This method is only available if PlotPy is installed.
457
+
458
+ Args:
459
+ items (list): annotation plot items
460
+ refresh_plot (bool | None): refresh plot. Defaults to True.
461
+ panel (str | None): panel name (valid values: "signal", "image").
462
+ If None, current panel is used.
463
+ """
464
+ try:
465
+ items_json = items_to_json(items)
466
+ except ImportError as exc:
467
+ raise ImportError("PlotPy is not installed") from exc
468
+ if items_json is not None:
469
+ self._datalab.add_annotations_from_items(items_json, refresh_plot, panel)