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,444 @@
1
+ # Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
2
+
3
+ """Signal object class
4
+ ===================
5
+
6
+ This module provides the main SignalObj class for handling 1D signal data.
7
+
8
+ The module includes:
9
+
10
+ - `SignalObj`: Main class for signal data management and operations
11
+
12
+ The SignalObj class supports:
13
+ - Signal data storage with x, y coordinates
14
+ - Error bars (dx, dy) for uncertainty quantification
15
+ - Metadata and annotations
16
+ - ROI (Region of Interest) operations
17
+ - Axis labels and units management
18
+ - Copy operations with type conversion
19
+ """
20
+
21
+ # pylint: disable=invalid-name # Allows short reference names like x, y, ...
22
+ # pylint: disable=duplicate-code
23
+
24
+ from __future__ import annotations
25
+
26
+ from typing import Type
27
+
28
+ import guidata.dataset as gds
29
+ import numpy as np
30
+ import pandas as pd
31
+
32
+ from sigima.config import _
33
+ from sigima.objects import base
34
+ from sigima.objects.signal.constants import (
35
+ DATETIME_X_FORMAT_KEY,
36
+ DATETIME_X_KEY,
37
+ DEFAULT_DATETIME_FORMAT,
38
+ VALID_TIME_UNITS,
39
+ )
40
+ from sigima.objects.signal.roi import SignalROI
41
+
42
+
43
+ class SignalObj(gds.DataSet, base.BaseObj[SignalROI]):
44
+ """Signal object"""
45
+
46
+ PREFIX = "s"
47
+ VALID_DTYPES = (np.float32, np.float64, np.complex128)
48
+
49
+ _tabs = gds.BeginTabGroup("all")
50
+
51
+ _datag = gds.BeginGroup(_("Data and metadata"))
52
+ title = gds.StringItem(_("Signal title"), default=_("Untitled"))
53
+ xydata = gds.FloatArrayItem(_("Data"), transpose=True, minmax="rows")
54
+ metadata = gds.DictItem(_("Metadata"), default={}) # type: ignore[assignment]
55
+ annotations = gds.StringItem(_("Annotations"), default="").set_prop(
56
+ "display",
57
+ hide=True,
58
+ ) # Annotations as a serialized JSON string # type: ignore[assignment]
59
+ _e_datag = gds.EndGroup(_("Data and metadata"))
60
+
61
+ _unitsg = gds.BeginGroup(_("Titles / Units"))
62
+ title = gds.StringItem(_("Signal title"), default=_("Untitled"))
63
+ _tabs_u = gds.BeginTabGroup("units")
64
+ _unitsx = gds.BeginGroup(_("X-axis"))
65
+ xlabel = gds.StringItem(_("Title"), default="")
66
+ xunit = gds.StringItem(_("Unit"), default="")
67
+ _e_unitsx = gds.EndGroup(_("X-axis"))
68
+ _unitsy = gds.BeginGroup(_("Y-axis"))
69
+ ylabel = gds.StringItem(_("Title"), default="")
70
+ yunit = gds.StringItem(_("Unit"), default="")
71
+ _e_unitsy = gds.EndGroup(_("Y-axis"))
72
+ _e_tabs_u = gds.EndTabGroup("units")
73
+ _e_unitsg = gds.EndGroup(_("Titles / Units"))
74
+
75
+ _scalesg = gds.BeginGroup(_("Scales"))
76
+ _prop_autoscale = gds.GetAttrProp("autoscale")
77
+ autoscale = gds.BoolItem(_("Auto scale"), default=True).set_prop(
78
+ "display", store=_prop_autoscale
79
+ )
80
+ _tabs_b = gds.BeginTabGroup("bounds")
81
+ _boundsx = gds.BeginGroup(_("X-axis"))
82
+ xscalelog = gds.BoolItem(_("Logarithmic scale"), default=False)
83
+ xscalemin = gds.FloatItem(_("Lower bound"), check=False).set_prop(
84
+ "display", active=gds.NotProp(_prop_autoscale)
85
+ )
86
+ xscalemax = gds.FloatItem(_("Upper bound"), check=False).set_prop(
87
+ "display", active=gds.NotProp(_prop_autoscale)
88
+ )
89
+ _e_boundsx = gds.EndGroup(_("X-axis"))
90
+ _boundsy = gds.BeginGroup(_("Y-axis"))
91
+ yscalelog = gds.BoolItem(_("Logarithmic scale"), default=False)
92
+ yscalemin = gds.FloatItem(_("Lower bound"), check=False).set_prop(
93
+ "display", active=gds.NotProp(_prop_autoscale)
94
+ )
95
+ yscalemax = gds.FloatItem(_("Upper bound"), check=False).set_prop(
96
+ "display", active=gds.NotProp(_prop_autoscale)
97
+ )
98
+ _e_boundsy = gds.EndGroup(_("Y-axis"))
99
+ _e_tabs_b = gds.EndTabGroup("bounds")
100
+ _e_scalesg = gds.EndGroup(_("Scales"))
101
+
102
+ _e_tabs = gds.EndTabGroup("all")
103
+
104
+ def __init__(self, title=None, comment=None, icon=""):
105
+ """Constructor
106
+
107
+ Args:
108
+ title: title
109
+ comment: comment
110
+ icon: icon
111
+ """
112
+ gds.DataSet.__init__(self, title, comment, icon)
113
+ base.BaseObj.__init__(self)
114
+
115
+ @staticmethod
116
+ def get_roi_class() -> Type[SignalROI]:
117
+ """Return ROI class"""
118
+ return SignalROI
119
+
120
+ def copy(
121
+ self,
122
+ title: str | None = None,
123
+ dtype: np.dtype | None = None,
124
+ all_metadata: bool = False,
125
+ ) -> SignalObj:
126
+ """Copy object.
127
+
128
+ Args:
129
+ title: title
130
+ dtype: data type
131
+ all_metadata: if True, copy all metadata, otherwise only basic metadata
132
+
133
+ Returns:
134
+ Copied object
135
+ """
136
+ title = self.title if title is None else title
137
+ obj = SignalObj(title=title)
138
+ obj.title = title
139
+ obj.xlabel = self.xlabel
140
+ obj.ylabel = self.ylabel
141
+ obj.xunit = self.xunit
142
+ obj.yunit = self.yunit
143
+ if dtype not in (None, float, complex, np.complex128):
144
+ raise RuntimeError("Signal data only supports float64/complex128 dtype")
145
+ obj.metadata = base.deepcopy_metadata(self.metadata, all_metadata=all_metadata)
146
+ obj.annotations = self.annotations
147
+ obj.xydata = np.array(self.xydata, copy=True, dtype=dtype)
148
+ obj.autoscale = self.autoscale
149
+ obj.xscalelog = self.xscalelog
150
+ obj.xscalemin = self.xscalemin
151
+ obj.xscalemax = self.xscalemax
152
+ obj.yscalelog = self.yscalelog
153
+ obj.yscalemin = self.yscalemin
154
+ obj.yscalemax = self.yscalemax
155
+ return obj
156
+
157
+ def set_data_type(self, dtype: np.dtype) -> None: # pylint: disable=unused-argument
158
+ """Change data type.
159
+
160
+ Args:
161
+ Data type
162
+ """
163
+ raise RuntimeError("Setting data type is not support for signals")
164
+
165
+ def set_xydata(
166
+ self,
167
+ x: np.ndarray | list | None,
168
+ y: np.ndarray | list | None,
169
+ dx: np.ndarray | list | None = None,
170
+ dy: np.ndarray | list | None = None,
171
+ ) -> None:
172
+ """Set xy data
173
+
174
+ Args:
175
+ x: x data
176
+ y: y data
177
+ dx: dx data (optional: error bars). Use None to reset dx data to None,
178
+ or provide array to set new dx data.
179
+ dy: dy data (optional: error bars). Use None to reset dy data to None,
180
+ or provide array to set new dy data.
181
+ """
182
+ if x is None and y is None:
183
+ # Using empty arrays (this allows initialization of the object without data)
184
+ x = np.array([], dtype=np.float64)
185
+ y = np.array([], dtype=np.float64)
186
+ if x is None and y is not None:
187
+ # If x is None, we create a default x array based on the length of y
188
+ assert isinstance(y, (list, np.ndarray))
189
+ x = np.arange(len(y), dtype=np.float64)
190
+ if x is not None:
191
+ x = np.array(x)
192
+ if y is not None:
193
+ y = np.array(y)
194
+ if dx is not None:
195
+ dx = np.array(dx)
196
+ if dy is not None:
197
+ dy = np.array(dy)
198
+ if dx is None and dy is None:
199
+ self.xydata = np.vstack([x, y])
200
+ else:
201
+ if dx is None:
202
+ dx = np.full_like(x, np.nan)
203
+ if dy is None:
204
+ dy = np.full_like(y, np.nan)
205
+ assert x is not None and y is not None
206
+ self.xydata = np.vstack((x, y, dx, dy))
207
+
208
+ def __get_x(self) -> np.ndarray | None:
209
+ """Get x data"""
210
+ if self.xydata is not None:
211
+ x: np.ndarray = self.xydata[0]
212
+ # We have to ensure that x is a floating point array, because if y is
213
+ # complex, the whole xydata array will be complex, and we need to avoid
214
+ # any unintended type promotion.
215
+ return x.real.astype(float)
216
+ return None
217
+
218
+ def __set_x(self, data: np.ndarray | list[float]) -> None:
219
+ """Set x data"""
220
+ assert isinstance(self.xydata, np.ndarray)
221
+ assert isinstance(data, (list, np.ndarray))
222
+ data = np.array(data, dtype=float)
223
+ assert data.shape[0] == self.xydata.shape[1], (
224
+ "X data size must match Y data size"
225
+ )
226
+ if not np.all(np.diff(data) >= 0.0):
227
+ raise ValueError("X data must be monotonic (sorted in ascending order)")
228
+ self.xydata[0] = data
229
+
230
+ def __get_y(self) -> np.ndarray | None:
231
+ """Get y data"""
232
+ if self.xydata is not None:
233
+ return self.xydata[1]
234
+ return None
235
+
236
+ def __set_y(self, data: np.ndarray | list[float]) -> None:
237
+ """Set y data"""
238
+ assert isinstance(self.xydata, np.ndarray)
239
+ assert isinstance(data, (list, np.ndarray))
240
+ data = np.array(data)
241
+ assert data.shape[0] == self.xydata.shape[1], (
242
+ "Y data size must match X data size"
243
+ )
244
+ assert np.issubdtype(data.dtype, np.inexact), "Y data must be float or complex"
245
+ self.xydata[1] = data
246
+
247
+ def __get_dx(self) -> np.ndarray | None:
248
+ """Get dx data"""
249
+ if self.xydata is not None and len(self.xydata) == 4:
250
+ dx: np.ndarray = self.xydata[2]
251
+ if np.all(np.isnan(dx)):
252
+ return None
253
+ return dx.real.astype(float)
254
+ return None
255
+
256
+ def __set_dx(self, data: np.ndarray | list[float] | None) -> None:
257
+ """Set dx data"""
258
+ if data is None:
259
+ data = np.full_like(self.x, np.nan)
260
+ assert isinstance(data, (list, np.ndarray))
261
+ data = np.array(data)
262
+ if self.xydata is None:
263
+ raise ValueError("Signal data not initialized")
264
+ assert data.shape[0] == self.xydata.shape[1], (
265
+ "dx data size must match X data size"
266
+ )
267
+ if len(self.xydata) == 2:
268
+ self.xydata = np.vstack((self.xydata, np.zeros((2, self.xydata.shape[1]))))
269
+ self.xydata[2] = np.array(data)
270
+
271
+ def __get_dy(self) -> np.ndarray | None:
272
+ """Get dy data"""
273
+ if self.xydata is not None and len(self.xydata) == 4:
274
+ dy: np.ndarray = self.xydata[3]
275
+ if np.all(np.isnan(dy)):
276
+ return None
277
+ return dy
278
+ return None
279
+
280
+ def __set_dy(self, data: np.ndarray | list[float] | None) -> None:
281
+ """Set dy data"""
282
+ if data is None:
283
+ data = np.full_like(self.x, np.nan)
284
+ assert isinstance(data, (list, np.ndarray))
285
+ data = np.array(data)
286
+ if self.xydata is None:
287
+ raise ValueError("Signal data not initialized")
288
+ assert data.shape[0] == self.xydata.shape[1], (
289
+ "dy data size must match X data size"
290
+ )
291
+ if len(self.xydata) == 2:
292
+ self.xydata = np.vstack((self.xydata, np.zeros((2, self.xydata.shape[1]))))
293
+ self.xydata[3] = np.array(data)
294
+
295
+ x = property(__get_x, __set_x)
296
+ y = data = property(__get_y, __set_y)
297
+ dx = property(__get_dx, __set_dx)
298
+ dy = property(__get_dy, __set_dy)
299
+
300
+ def get_data(self, roi_index: int | None = None) -> tuple[np.ndarray, np.ndarray]:
301
+ """
302
+ Return original data (if ROI is not defined or `roi_index` is None),
303
+ or ROI data (if both ROI and `roi_index` are defined).
304
+
305
+ Args:
306
+ roi_index: ROI index
307
+
308
+ Returns:
309
+ Data
310
+ """
311
+ if self.roi is None or roi_index is None:
312
+ assert isinstance(self.xydata, np.ndarray)
313
+ return self.x, self.y
314
+ single_roi = self.roi.get_single_roi(roi_index)
315
+ return single_roi.get_data(self)
316
+
317
+ def physical_to_indices(self, coords: list[float]) -> list[int]:
318
+ """Convert coordinates from physical (real world) to indices (pixel)
319
+
320
+ Args:
321
+ coords: coordinates
322
+
323
+ Returns:
324
+ Indices
325
+ """
326
+ assert isinstance(self.x, np.ndarray)
327
+ return [int(np.abs(self.x - x).argmin()) for x in coords]
328
+
329
+ def indices_to_physical(self, indices: list[int]) -> list[float]:
330
+ """Convert coordinates from indices to physical (real world)
331
+
332
+ Args:
333
+ indices: indices
334
+
335
+ Returns:
336
+ Coordinates
337
+ """
338
+ # We take the real part of the x data to avoid `ComplexWarning` warnings
339
+ # when creating and manipulating the `XRangeSelection` shape (`plotpy`)
340
+ return self.x.real[indices].tolist()
341
+
342
+ def is_x_datetime(self) -> bool:
343
+ """Check if x data represents datetime values.
344
+
345
+ Returns:
346
+ True if x data represents datetime values, False otherwise
347
+ """
348
+ return self.metadata.get(DATETIME_X_KEY, False)
349
+
350
+ def set_x_from_datetime(
351
+ self,
352
+ dt_array: np.ndarray | list,
353
+ unit: str = "s",
354
+ format_str: str | None = None,
355
+ ) -> None:
356
+ """Set x values from datetime objects or strings.
357
+
358
+ This method converts datetime data to float timestamps (Unix time: seconds
359
+ since 1970-01-01) for efficient storage and computation. The datetime context
360
+ is preserved through metadata.
361
+
362
+ Note: X values are always stored as Unix timestamps (seconds since 1970-01-01)
363
+ regardless of the 'unit' parameter. The 'unit' parameter is stored in metadata
364
+ and used only for axis labeling when plotting.
365
+
366
+ Args:
367
+ dt_array: Array of datetime objects, datetime strings, or numpy datetime64
368
+ unit: Time unit label for display. Options: 's' (seconds),
369
+ 'ms' (milliseconds), 'us' (microseconds), 'ns' (nanoseconds),
370
+ 'min' (minutes), 'h' (hours). Default is 's'. This parameter only
371
+ affects the axis label, not the stored data.
372
+ format_str: Format string for datetime display. If None, uses default.
373
+
374
+ Raises:
375
+ ValueError: If unit is not valid
376
+
377
+ Example:
378
+ >>> from datetime import datetime
379
+ >>> signal = SignalObj()
380
+ >>> timestamps = [datetime(2025, 1, 1, 10, 0, 0),
381
+ ... datetime(2025, 1, 1, 10, 0, 1)]
382
+ >>> signal.set_x_from_datetime(timestamps, unit='s')
383
+ >>> signal.is_x_datetime()
384
+ True
385
+ >>> # X data is stored as Unix timestamps (seconds since 1970)
386
+ >>> signal.x[0] > 1.7e9 # Year 2025
387
+ True
388
+ """
389
+ if unit not in VALID_TIME_UNITS:
390
+ raise ValueError(
391
+ f"Invalid unit: {unit}. Must be one of: {', '.join(VALID_TIME_UNITS)}"
392
+ )
393
+
394
+ # Convert to pandas datetime (handles strings, datetime objects, etc.)
395
+ dt_series = pd.to_datetime(dt_array)
396
+
397
+ # Convert to float timestamp in seconds (pandas epoch is in nanoseconds)
398
+ # Note: We always store as Unix timestamps (seconds since 1970-01-01)
399
+ # regardless of the 'unit' parameter, which is only for display purposes
400
+ timestamp_seconds = dt_series.astype(np.int64) / 1e9
401
+
402
+ # Convert to numpy array (pandas may return Float64Index)
403
+ x_float = np.array(timestamp_seconds, dtype=np.float64)
404
+
405
+ # Check if signal already has data with matching size
406
+ if self.xydata is not None and self.xydata.shape[1] == len(x_float):
407
+ # Signal already has matching data, just update x
408
+ self.x = x_float
409
+ else:
410
+ # Initialize or reinitialize signal with x data (y will be zeros)
411
+ y_placeholder = np.zeros_like(x_float)
412
+ self.set_xydata(x_float, y_placeholder)
413
+
414
+ # Store metadata
415
+ self.metadata[DATETIME_X_KEY] = True
416
+ self.metadata[DATETIME_X_FORMAT_KEY] = (
417
+ format_str if format_str is not None else DEFAULT_DATETIME_FORMAT
418
+ )
419
+ # Store unit in xunit attribute (more intuitive than metadata)
420
+ self.xunit = unit
421
+
422
+ def get_x_as_datetime(self) -> np.ndarray:
423
+ """Get x values as datetime objects if x is datetime data.
424
+
425
+ Returns x data as numpy datetime64 array if the signal contains datetime data,
426
+ otherwise returns the regular x data as floats.
427
+
428
+ Returns:
429
+ Array of datetime64 objects if x is datetime data, otherwise regular x array
430
+
431
+ Example:
432
+ >>> signal.set_x_from_datetime([datetime(2025, 1, 1, 10, 0, 0)])
433
+ >>> dt_values = signal.get_x_as_datetime()
434
+ >>> isinstance(dt_values[0], np.datetime64)
435
+ True
436
+ """
437
+ if not self.is_x_datetime():
438
+ return self.x
439
+ # X values are always stored as Unix timestamps (seconds since 1970-01-01)
440
+ # regardless of the 'unit' parameter
441
+ x_float = self.x
442
+
443
+ # Convert seconds to datetime using pandas
444
+ return pd.to_datetime(x_float, unit="s").to_numpy()