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,175 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Licensed under the terms of the BSD 3-Clause
4
+ # (see sigima/LICENSE for details)
5
+
6
+ """
7
+ Stability analysis functions
8
+ ============================
9
+
10
+ This module provides stability analysis functions for signal objects:
11
+
12
+ - Allan variance and deviation
13
+ - Overlapping Allan variance
14
+ - Modified Allan variance
15
+ - Hadamard variance
16
+ - Total variance
17
+
18
+ .. note::
19
+
20
+ All operations use functions from :mod:`sigima.tools.signal.stability` for
21
+ actual computations.
22
+ """
23
+
24
+ from __future__ import annotations
25
+
26
+ import guidata.dataset as gds
27
+ import numpy as np
28
+
29
+ from sigima.config import _
30
+ from sigima.objects import SignalObj
31
+ from sigima.proc.decorator import computation_function
32
+ from sigima.tools.signal import stability
33
+
34
+ from .base import dst_1_to_1
35
+
36
+
37
+ class AllanVarianceParam(gds.DataSet, title=_("Allan variance")):
38
+ """Allan variance parameters"""
39
+
40
+ max_tau = gds.IntItem("Max τ", default=100, min=1, unit="pts")
41
+
42
+
43
+ @computation_function()
44
+ def allan_variance(src: SignalObj, p: AllanVarianceParam) -> SignalObj:
45
+ """Compute Allan variance with
46
+ :py:func:`sigima.tools.signal.stability.allan_variance`.
47
+
48
+ Args:
49
+ src: source signal
50
+ p: parameters
51
+
52
+ Returns:
53
+ Result signal object
54
+ """
55
+ dst = dst_1_to_1(src, "allan_variance", f"max_tau={p.max_tau}")
56
+ x, y = src.get_data()
57
+ tau_values = np.arange(1, p.max_tau + 1)
58
+ avar = stability.allan_variance(x, y, tau_values)
59
+ dst.set_xydata(tau_values, avar)
60
+ return dst
61
+
62
+
63
+ @computation_function()
64
+ def allan_deviation(src: SignalObj, p: AllanVarianceParam) -> SignalObj:
65
+ """Compute Allan deviation with
66
+ :py:func:`sigima.tools.signal.stability.allan_deviation`
67
+
68
+ Args:
69
+ src: source signal
70
+ p: parameters
71
+
72
+ Returns:
73
+ Result signal object
74
+ """
75
+ dst = dst_1_to_1(src, "allan_deviation", f"max_tau={p.max_tau}")
76
+ x, y = src.get_data()
77
+ tau_values = np.arange(1, p.max_tau + 1)
78
+ adev = stability.allan_deviation(x, y, tau_values)
79
+ dst.set_xydata(tau_values, adev)
80
+ return dst
81
+
82
+
83
+ @computation_function()
84
+ def overlapping_allan_variance(src: SignalObj, p: AllanVarianceParam) -> SignalObj:
85
+ """Compute Overlapping Allan variance.
86
+
87
+ Args:
88
+ src: source signal
89
+ p: parameters
90
+
91
+ Returns:
92
+ Result signal object
93
+ """
94
+ dst = dst_1_to_1(src, "overlapping_allan_variance", f"max_tau={p.max_tau}")
95
+ x, y = src.get_data()
96
+ tau_values = np.arange(1, p.max_tau + 1)
97
+ oavar = stability.overlapping_allan_variance(x, y, tau_values)
98
+ dst.set_xydata(tau_values, oavar)
99
+ return dst
100
+
101
+
102
+ @computation_function()
103
+ def modified_allan_variance(src: SignalObj, p: AllanVarianceParam) -> SignalObj:
104
+ """Compute Modified Allan variance.
105
+
106
+ Args:
107
+ src: source signal
108
+ p: parameters
109
+
110
+ Returns:
111
+ Result signal object
112
+ """
113
+ dst = dst_1_to_1(src, "modified_allan_variance", f"max_tau={p.max_tau}")
114
+ x, y = src.get_data()
115
+ tau_values = np.arange(1, p.max_tau + 1)
116
+ mavar = stability.modified_allan_variance(x, y, tau_values)
117
+ dst.set_xydata(tau_values, mavar)
118
+ return dst
119
+
120
+
121
+ @computation_function()
122
+ def hadamard_variance(src: SignalObj, p: AllanVarianceParam) -> SignalObj:
123
+ """Compute Hadamard variance.
124
+
125
+ Args:
126
+ src: source signal
127
+ p: parameters
128
+
129
+ Returns:
130
+ Result signal object
131
+ """
132
+ dst = dst_1_to_1(src, "hadamard_variance", f"max_tau={p.max_tau}")
133
+ x, y = src.get_data()
134
+ tau_values = np.arange(1, p.max_tau + 1)
135
+ hvar = stability.hadamard_variance(x, y, tau_values)
136
+ dst.set_xydata(tau_values, hvar)
137
+ return dst
138
+
139
+
140
+ @computation_function()
141
+ def total_variance(src: SignalObj, p: AllanVarianceParam) -> SignalObj:
142
+ """Compute Total variance.
143
+
144
+ Args:
145
+ src: source signal
146
+ p: parameters
147
+
148
+ Returns:
149
+ Result signal object
150
+ """
151
+ dst = dst_1_to_1(src, "total_variance", f"max_tau={p.max_tau}")
152
+ x, y = src.get_data()
153
+ tau_values = np.arange(1, p.max_tau + 1)
154
+ tvar = stability.total_variance(x, y, tau_values)
155
+ dst.set_xydata(tau_values, tvar)
156
+ return dst
157
+
158
+
159
+ @computation_function()
160
+ def time_deviation(src: SignalObj, p: AllanVarianceParam) -> SignalObj:
161
+ """Compute Time Deviation (TDEV).
162
+
163
+ Args:
164
+ src: source signal
165
+ p: parameters
166
+
167
+ Returns:
168
+ Result signal object
169
+ """
170
+ dst = dst_1_to_1(src, "time_deviation", f"max_tau={p.max_tau}")
171
+ x, y = src.get_data()
172
+ tau_values = np.arange(1, p.max_tau + 1)
173
+ tdev = stability.time_deviation(x, y, tau_values)
174
+ dst.set_xydata(tau_values, tdev)
175
+ return dst
@@ -0,0 +1,227 @@
1
+ # Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
2
+
3
+ """
4
+ Title formatting system for computation results
5
+ -----------------------------------------------
6
+
7
+ This module provides a configurable title formatting system for computation results.
8
+ It allows different applications (Sigima vs DataLab) to use different title formatting
9
+ strategies while maintaining compatibility.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ from typing import TYPE_CHECKING, Protocol, runtime_checkable
15
+
16
+ if TYPE_CHECKING:
17
+ from sigima.objects.base import BaseObj
18
+
19
+ __all__ = [
20
+ "PlaceholderTitleFormatter",
21
+ "SimpleTitleFormatter",
22
+ "TitleFormatter",
23
+ "get_default_title_formatter",
24
+ "set_default_title_formatter",
25
+ ]
26
+
27
+
28
+ @runtime_checkable
29
+ class TitleFormatter(Protocol):
30
+ """Protocol for title formatting strategies used in computation functions.
31
+
32
+ This protocol allows different title formatting approaches:
33
+ - Simple descriptive titles for Sigima-only usage
34
+ - Placeholder-based titles for DataLab integration
35
+ - Custom formatting for specific use cases
36
+ """
37
+
38
+ def format_1_to_1_title(self, name: str, suffix: str | None = None) -> str:
39
+ """Format title for 1-to-1 computation (single input → single output).
40
+
41
+ Args:
42
+ name: Name of the computation function
43
+ suffix: Optional suffix to add to the title
44
+
45
+ Returns:
46
+ Formatted title string
47
+ """
48
+
49
+ def format_n_to_1_title(
50
+ self, name: str, n_inputs: int, suffix: str | None = None
51
+ ) -> str:
52
+ """Format title for n-to-1 computation (multiple inputs → single output).
53
+
54
+ Args:
55
+ name: Name of the computation function
56
+ n_inputs: Number of input objects
57
+ suffix: Optional suffix to add to the title
58
+
59
+ Returns:
60
+ Formatted title string
61
+ """
62
+
63
+ def format_2_to_1_title(self, name: str, suffix: str | None = None) -> str:
64
+ """Format title for 2-to-1 computation (two inputs → single output).
65
+
66
+ Args:
67
+ name: Name of the computation function
68
+ suffix: Optional suffix to add to the title
69
+
70
+ Returns:
71
+ Formatted title string
72
+ """
73
+
74
+ def resolve_placeholder_title(
75
+ self,
76
+ title: str,
77
+ source_objects: list[BaseObj],
78
+ ) -> str:
79
+ """Resolve placeholder title with actual object references.
80
+
81
+ This method takes a title with placeholders (e.g., "wiener({0})")
82
+ and replaces them with actual object identifiers.
83
+
84
+ Args:
85
+ title: Title containing placeholders
86
+ source_objects: List of source objects to use for replacement
87
+
88
+ Returns:
89
+ Title with placeholders resolved
90
+ """
91
+
92
+
93
+ class SimpleTitleFormatter:
94
+ """Simple descriptive title formatter for Sigima-only usage.
95
+
96
+ Creates human-readable titles without placeholders, suitable for
97
+ standalone Sigima usage where object relationships are less critical.
98
+ """
99
+
100
+ def format_1_to_1_title(self, name: str, suffix: str | None = None) -> str:
101
+ """Format title for 1-to-1 computation."""
102
+ if len(name) == 1: # This is an operator
103
+ base_title = f"operator_{name}"
104
+ else:
105
+ # Convert function names to human-readable format
106
+ readable_name = name.replace("_", " ").title()
107
+ base_title = f"{readable_name} Result"
108
+
109
+ if suffix:
110
+ base_title += f" ({suffix})"
111
+ return base_title
112
+
113
+ def format_n_to_1_title(
114
+ self, name: str, n_inputs: int, suffix: str | None = None
115
+ ) -> str:
116
+ """Format title for n-to-1 computation."""
117
+ readable_name = name.replace("_", " ").title()
118
+ base_title = f"{readable_name} of {n_inputs} Objects"
119
+
120
+ if suffix:
121
+ base_title += f" ({suffix})"
122
+ return base_title
123
+
124
+ def format_2_to_1_title(self, name: str, suffix: str | None = None) -> str:
125
+ """Format title for 2-to-1 computation."""
126
+ if len(name) == 1: # This is an operator
127
+ base_title = f"Binary Operation {name}"
128
+ else:
129
+ readable_name = name.replace("_", " ").title()
130
+ base_title = f"{readable_name} Result"
131
+
132
+ if suffix:
133
+ base_title += f" ({suffix})"
134
+ return base_title
135
+
136
+ def resolve_placeholder_title(
137
+ self,
138
+ title: str,
139
+ source_objects: list[BaseObj], # pylint: disable=unused-argument
140
+ ) -> str:
141
+ """For simple formatter, just return the title as-is."""
142
+ return title
143
+
144
+
145
+ class PlaceholderTitleFormatter:
146
+ """Placeholder-based title formatter compatible with DataLab.
147
+
148
+ Creates titles with placeholders that can be resolved later by DataLab's
149
+ patch_title_with_ids() function.
150
+ """
151
+
152
+ def format_1_to_1_title(self, name: str, suffix: str | None = None) -> str:
153
+ """Format title for 1-to-1 computation with placeholder."""
154
+ if len(name) == 1: # This is an operator
155
+ title = f"{{0}}{name}"
156
+ else:
157
+ title = f"{name}({{0}})"
158
+ if suffix: # suffix may be None or an empty string
159
+ title += "|"
160
+ if suffix: # suffix may be None or an empty string
161
+ title += suffix
162
+ return title
163
+
164
+ def format_n_to_1_title(
165
+ self, name: str, n_inputs: int, suffix: str | None = None
166
+ ) -> str:
167
+ """Format title for n-to-1 computation with placeholders."""
168
+ placeholders = ", ".join(f"{{{i}}}" for i in range(n_inputs))
169
+ title = f"{name}({placeholders})"
170
+ if suffix:
171
+ title += "|" + suffix
172
+ return title
173
+
174
+ def format_2_to_1_title(self, name: str, suffix: str | None = None) -> str:
175
+ """Format title for 2-to-1 computation with placeholders."""
176
+ if len(name) == 1: # This is an operator
177
+ title = f"{{0}}{name}{{1}}"
178
+ else:
179
+ title = f"{name}({{0}}, {{1}})"
180
+ if suffix:
181
+ title += "|" + suffix
182
+ return title
183
+
184
+ def resolve_placeholder_title(
185
+ self,
186
+ title: str,
187
+ source_objects: list[BaseObj],
188
+ ) -> str:
189
+ """Resolve placeholder title with object short IDs (if available)."""
190
+ # For basic Sigima usage, use simple indexing
191
+ # DataLab will override this with its own patch_title_with_ids logic
192
+ try:
193
+ # Try to use short_id if available (for DataLab compatibility)
194
+ ids = []
195
+ for i, obj in enumerate(source_objects):
196
+ short_id = getattr(obj, "short_id", None)
197
+ if short_id is not None:
198
+ ids.append(short_id)
199
+ else:
200
+ ids.append(f"obj{i + 1}")
201
+ return title.format(*ids)
202
+ except (IndexError, AttributeError):
203
+ # Fallback: use simple object indices
204
+ return title.format(*[f"obj{i + 1}" for i in range(len(source_objects))])
205
+
206
+
207
+ # Global default title formatter
208
+ _default_title_formatter: TitleFormatter = SimpleTitleFormatter()
209
+
210
+
211
+ def get_default_title_formatter() -> TitleFormatter:
212
+ """Get the current default title formatter.
213
+
214
+ Returns:
215
+ Current default title formatter
216
+ """
217
+ return _default_title_formatter
218
+
219
+
220
+ def set_default_title_formatter(formatter: TitleFormatter) -> None:
221
+ """Set the default title formatter.
222
+
223
+ Args:
224
+ formatter: Title formatter to use as default
225
+ """
226
+ global _default_title_formatter # pylint: disable=global-statement
227
+ _default_title_formatter = formatter
@@ -0,0 +1,272 @@
1
+ # Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
2
+
3
+ """
4
+ .. Validation statistics module
5
+ (see parent package :mod:`sigima.computation`)
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import csv
11
+ import dataclasses
12
+ import importlib
13
+ import inspect
14
+ import os.path as osp
15
+ import pkgutil
16
+ import re
17
+
18
+ from _pytest.mark import Mark
19
+
20
+ import sigima.tests as tests_pkg
21
+ from sigima import __version__
22
+ from sigima.proc.decorator import find_computation_functions
23
+
24
+
25
+ def generate_valid_test_names_for_function(
26
+ module_name: str, func_name: str
27
+ ) -> list[str]:
28
+ """Generate all valid test names for a computation function.
29
+
30
+ Args:
31
+ module_name: Module name containing the computation function
32
+ (e.g., "sigima.proc.image")
33
+ func_name: Function name (e.g., "compute_add_gaussian_noise")
34
+
35
+ Returns:
36
+ List of valid test names that could test this computation function
37
+ """
38
+ family = module_name.split(".")[-1] # "signal" or "image"
39
+ shortname = func_name.removeprefix("compute_")
40
+ endings = [shortname, shortname + "_unit", shortname + "_validation"]
41
+ beginnings = ["test", f"test_{family}", f"test_{family[:3]}", f"test_{family[0]}"]
42
+ names = [f"{beginning}_{ending}" for beginning in beginnings for ending in endings]
43
+ return names
44
+
45
+
46
+ def check_for_validation_test(
47
+ full_function_name: str, validation_tests: list[tuple[str, str]]
48
+ ) -> str:
49
+ """Check if a validation test exists for a compute function
50
+
51
+ Args:
52
+ full_function_name: Compute function name
53
+ validation_tests: List of validation tests
54
+
55
+ Returns:
56
+ Text to be included in the CSV file or None if it doesn't exist
57
+ """
58
+ # Extract module name and function name from full function name
59
+ module_parts = full_function_name.split(".")
60
+ module_name = ".".join(module_parts[:-1]) # e.g., "sigima.proc.image"
61
+ func_name = module_parts[-1] # e.g., "compute_add_gaussian_noise"
62
+
63
+ # Generate all valid test names for this computation function
64
+ names = generate_valid_test_names_for_function(module_name, func_name)
65
+
66
+ stable_version = re.sub(r"\.?(post|dev|rc|b|a)\S*", "", __version__)
67
+ for test, path, line_number in validation_tests:
68
+ if test in names:
69
+ # Path relative to the `datalab` package:
70
+ path = osp.relpath(path, start=osp.dirname(osp.join(tests_pkg.__file__)))
71
+ name = "/".join(path.split(osp.sep))
72
+ link = (
73
+ f"https://github.com/DataLab-Platform/Sigima/blob/"
74
+ f"v{stable_version}/sigima/tests/{name}#L{line_number}"
75
+ )
76
+ return f"`{test} <{link}>`_"
77
+ return None
78
+
79
+
80
+ def get_validation_tests(package: str) -> list:
81
+ """Retrieve list of validation tests from a package and its submodules
82
+
83
+ Args:
84
+ package: Python package
85
+
86
+ Returns:
87
+ List of tuples containing the test name, module path and line number
88
+ """
89
+ validation_tests = []
90
+ package_path = package.__path__
91
+ for _, module_name, _ in pkgutil.walk_packages(
92
+ package_path, package.__name__ + "."
93
+ ):
94
+ try:
95
+ module = importlib.import_module(module_name)
96
+ except ImportError as exc:
97
+ if "vistools" in module_name:
98
+ # This is expected as vistools requires a GUI
99
+ continue
100
+ raise ImportError(
101
+ f"Failed to import module {module_name}. "
102
+ "Ensure the module is correctly installed and accessible."
103
+ ) from exc
104
+ for name, obj in inspect.getmembers(module, inspect.isfunction):
105
+ if hasattr(obj, "pytestmark"):
106
+ for mark in obj.pytestmark:
107
+ if isinstance(mark, Mark) and mark.name == "validation":
108
+ module_path = inspect.getfile(obj)
109
+ try:
110
+ line_number = inspect.getsourcelines(obj)[1]
111
+ except OSError as exc:
112
+ raise RuntimeError(
113
+ f"Failed to get source line for {name} in {module_name}"
114
+ ) from exc
115
+ validation_tests.append((name, module_path, line_number))
116
+ return validation_tests
117
+
118
+
119
+ def shorten_docstring(docstring: str) -> str:
120
+ """Shorten a docstring to a single line
121
+
122
+ Args:
123
+ docstring: Docstring
124
+
125
+ Returns:
126
+ Shortened docstring
127
+ """
128
+ shorter = docstring.split("\n")[0].strip() if docstring else "-"
129
+ for suffix in (".", ":", ",", "using", "with"):
130
+ shorter = shorter.removesuffix(suffix)
131
+ shorter = shorter.split(" with :py:func:")[0] # Remove function references
132
+ return shorter
133
+
134
+
135
+ @dataclasses.dataclass
136
+ class ValidationStatus:
137
+ """Data class to hold validation status of a compute function"""
138
+
139
+ module_name: str
140
+ function_name: str
141
+ description: str
142
+ test_script: str
143
+
144
+ def get_pyfunc_link(self) -> str:
145
+ """Get the reStructuredText link to the compute function"""
146
+ return (
147
+ f":py:func:`{self.function_name} <{self.module_name}.{self.function_name}>`"
148
+ )
149
+
150
+ def __str__(self) -> str:
151
+ """String representation of the validation status"""
152
+ return f"{self.get_pyfunc_link()} - {self.description} - {self.test_script}"
153
+
154
+ def to_csv_row(self) -> list[str]:
155
+ """Convert the validation status to a CSV row"""
156
+ return [self.get_pyfunc_link(), self.description, self.test_script]
157
+
158
+
159
+ class ValidationStatistics:
160
+ """Data class to hold validation statistics of compute functions"""
161
+
162
+ def __init__(self):
163
+ self.submodules: dict[str, list[tuple[str, str, str]]] = {}
164
+ self.t_count: dict[str, int] = {}
165
+ self.v_count: dict[str, int] = {}
166
+ self.signal_pct: int = 0
167
+ self.image_pct: int = 0
168
+ self.total_pct: int = 0
169
+ self.validations: dict[str, list[ValidationStatus]] = {}
170
+ self.validation_info_list: list[str] = []
171
+
172
+ def collect_validation_status(self, verbose: bool = False) -> None:
173
+ """Populate the statistics from the validation status"""
174
+ compute_functions = find_computation_functions()
175
+ validation_tests = get_validation_tests(tests_pkg)
176
+
177
+ self.submodules = {"signal": [], "image": []}
178
+ for modname, funcname, docstring in compute_functions:
179
+ if "signal" in modname:
180
+ self.submodules["signal"].append((modname, funcname, docstring))
181
+ elif "image" in modname:
182
+ self.submodules["image"].append((modname, funcname, docstring))
183
+
184
+ self.t_count = t_count = {"signal": 0, "image": 0, "total": 0}
185
+ self.v_count = v_count = {"signal": 0, "image": 0, "total": 0}
186
+
187
+ self.validations = {"signal": [], "image": []}
188
+ for submodule, functions in self.submodules.items():
189
+ for modname, funcname, docstring in functions:
190
+ full_funcname = f"{modname}.{funcname}"
191
+ test_link = check_for_validation_test(full_funcname, validation_tests)
192
+ if test_link:
193
+ v_count[submodule] += 1
194
+ v_count["total"] += 1
195
+ t_count[submodule] += 1
196
+ t_count["total"] += 1
197
+ status = ValidationStatus(
198
+ module_name=modname,
199
+ function_name=funcname,
200
+ description=shorten_docstring(docstring),
201
+ test_script=test_link if test_link else "N/A",
202
+ )
203
+ self.validations[submodule].append(status)
204
+
205
+ self.signal_pct = signal_pct = (
206
+ int((v_count["signal"] / t_count["signal"]) * 100)
207
+ if t_count["signal"] > 0
208
+ else 0
209
+ )
210
+ self.image_pct = image_pct = (
211
+ int((v_count["image"] / t_count["image"]) * 100)
212
+ if t_count["image"] > 0
213
+ else 0
214
+ )
215
+ self.total_pct = total_pct = (
216
+ int((v_count["total"] / t_count["total"]) * 100)
217
+ if t_count["total"] > 0
218
+ else 0
219
+ )
220
+
221
+ self.validation_info_list = [
222
+ f"Validation statistics for Sigima {__version__}:",
223
+ f" Signal: {v_count['signal']}/{t_count['signal']} ({signal_pct}%)",
224
+ f" Image : {v_count['image']}/{t_count['image']} ({image_pct}%)",
225
+ f" Total : {v_count['total']}/{t_count['total']} ({total_pct}%)",
226
+ ]
227
+
228
+ if verbose:
229
+ print("\n".join(self.validation_info_list))
230
+ print()
231
+
232
+ def get_validation_info(self) -> list[str]:
233
+ """Get the validation information as a list of strings"""
234
+ if not self.validation_info_list:
235
+ self.collect_validation_status(verbose=False)
236
+ return self.validation_info_list
237
+
238
+ def generate_csv_files(self, path: str) -> None:
239
+ """Generate CSV files for each submodule's validation status"""
240
+ for submodule, validation_status_list in self.validations.items():
241
+ rows = [status.to_csv_row() for status in validation_status_list]
242
+ fname = osp.join(path, f"validation_status_{submodule}.csv")
243
+ with open(fname, "w", newline="", encoding="utf-8") as csvfile:
244
+ writer = csv.writer(csvfile)
245
+ writer.writerows(rows)
246
+
247
+ def generate_statistics_csv(self, path: str) -> None:
248
+ """Generate a CSV file with the validation statistics"""
249
+ statistics_rows = [
250
+ [
251
+ "Number of compute functions",
252
+ self.t_count["signal"],
253
+ self.t_count["image"],
254
+ self.t_count["total"],
255
+ ],
256
+ [
257
+ "Number of validated compute functions",
258
+ self.v_count["signal"],
259
+ self.v_count["image"],
260
+ self.v_count["total"],
261
+ ],
262
+ [
263
+ "Percentage of validated compute functions",
264
+ f"{self.signal_pct}%",
265
+ f"{self.image_pct}%",
266
+ f"{self.total_pct}%",
267
+ ],
268
+ ]
269
+ fname = osp.join(path, "validation_statistics.csv")
270
+ with open(fname, "w", newline="", encoding="utf-8") as csvfile:
271
+ writer = csv.writer(csvfile)
272
+ writer.writerows(statistics_rows)
@@ -0,0 +1,7 @@
1
+ # Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
2
+
3
+ """
4
+ Sigima test suite
5
+ """
6
+
7
+ SIGIMA_TESTS_GUI_ENV = "SIGIMA_TESTS_GUI"
File without changes