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,414 @@
1
+ # Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
2
+
3
+ """
4
+ Sigima I/O image functions
5
+ """
6
+
7
+ # pylint: disable=invalid-name # Allows short reference names like x, y, ...
8
+
9
+ from __future__ import annotations
10
+
11
+ import os
12
+ import re
13
+ import sys
14
+ import time
15
+
16
+ import numpy as np
17
+ from guidata.utils.misc import to_string
18
+
19
+
20
+ # MARK: SIF I/O functions
21
+ # ==============================================================================
22
+ # Original code:
23
+ # --------------
24
+ # Zhenpeng Zhou <zhenp3ngzhou cir{a} gmail dot com>
25
+ # Copyright 2017 Zhenpeng Zhou
26
+ # Licensed under MIT License Terms
27
+ #
28
+ # Changes:
29
+ # -------
30
+ # * Calculating header length using the line beginning with "Counts"
31
+ # * Calculating wavelenght info line number using line starting with "65538 "
32
+ # * Handling wavelenght info line ending with "NM"
33
+ # * Calculating data offset by detecting the first line containing NUL character after
34
+ # header
35
+ #
36
+ class SIFFile:
37
+ """
38
+ A class that reads the contents and metadata of an Andor .sif file.
39
+ Compatible with images as well as spectra.
40
+ Exports data as numpy array or xarray.DataArray.
41
+
42
+ Example: SIFFile('my_spectrum.sif').read_all()
43
+
44
+ In addition to the raw data, SIFFile objects provide a number of meta
45
+ data variables:
46
+ :ivar x_axis: the horizontal axis (can be pixel numbers or wvlgth in nm)
47
+ :ivar original_filename: the original file name of the .sif file
48
+ :ivar date: the date the file was recorded
49
+ :ivar model: camera model
50
+ :ivar temperature: sensor temperature in degrees Celsius
51
+ :ivar exposuretime: exposure time in seconds
52
+ :ivar cycletime: cycle time in seconds
53
+ :ivar accumulations: number of accumulations
54
+ :ivar readout: pixel readout rate in MHz
55
+ :ivar xres: horizontal resolution
56
+ :ivar yres: vertical resolution
57
+ :ivar width: image width
58
+ :ivar height: image height
59
+ :ivar xbin: horizontal binning
60
+ :ivar ybin: vertical binning
61
+ :ivar gain: EM gain level
62
+ :ivar vertical_shift_speed: vertical shift speed
63
+ :ivar pre_amp_gain: pre-amplifier gain
64
+ :ivar stacksize: number of frames
65
+ :ivar filesize: size of the file in bytes
66
+ :ivar m_offset: offset in the .sif file to the actual data
67
+ """
68
+
69
+ # pylint: disable=too-many-instance-attributes
70
+ # pylint: disable=too-many-statements
71
+
72
+ def __init__(self, filepath: str) -> None:
73
+ self.filepath = filepath
74
+ self.original_filename = None
75
+ self.filesize = None
76
+ self.left = None
77
+ self.right = None
78
+ self.top = None
79
+ self.bottom = None
80
+ self.width = None
81
+ self.height = None
82
+ self.grating = None
83
+ self.stacksize = None
84
+ self.datasize = None
85
+ self.xres = None
86
+ self.yres = None
87
+ self.xbin = None
88
+ self.ybin = None
89
+ self.cycletime = None
90
+ self.pre_amp_gain = None
91
+ self.temperature = None
92
+ self.center_wavelength = None
93
+ self.readout = None
94
+ self.gain = None
95
+ self.date = None
96
+ self.exposuretime = None
97
+ self.m_offset = None
98
+ self.accumulations = None
99
+ self.vertical_shift_speed = None
100
+ self.model = None
101
+ self.grating_blaze = None
102
+ self._read_header(filepath)
103
+
104
+ def __repr__(self) -> str:
105
+ """Return a string representation of the SIFFile object"""
106
+ info = (
107
+ ("Original Filename", self.original_filename),
108
+ ("Date", self.date),
109
+ ("Camera Model", self.model),
110
+ ("Temperature (deg.C)", f"{self.temperature:f}"),
111
+ ("Exposure Time", f"{self.exposuretime:f}"),
112
+ ("Cycle Time", f"{self.cycletime:f}"),
113
+ ("Number of accumulations", f"{self.accumulations:d}"),
114
+ ("Pixel Readout Rate (MHz)", f"{self.readout:f}"),
115
+ ("Horizontal Camera Resolution", f"{self.xres:d}"),
116
+ ("Vertical Camera Resolution", f"{self.yres:d}"),
117
+ ("Image width", f"{self.width:d}"),
118
+ ("Image Height", f"{self.height:d}"),
119
+ ("Horizontal Binning", f"{self.xbin:d}"),
120
+ ("Vertical Binning", f"{self.ybin:d}"),
121
+ ("EM Gain level", f"{self.gain:f}"),
122
+ ("Vertical Shift Speed", f"{self.vertical_shift_speed:f}"),
123
+ ("Pre-Amplifier Gain", f"{self.pre_amp_gain:f}"),
124
+ ("Stacksize", f"{self.stacksize:d}"),
125
+ ("Filesize", f"{self.filesize:d}"),
126
+ ("Offset to Image Data", f"{self.m_offset:f}"),
127
+ )
128
+ desc_len = max(len(d) for d in list(zip(*info))[0]) + 3
129
+ res = ""
130
+ for description, value in info:
131
+ res += ("{:" + str(desc_len) + "}{}\n").format(description + ": ", value)
132
+
133
+ res = object.__repr__(self) + "\n" + res
134
+ return res
135
+
136
+ def _read_header(self, filepath: str) -> None:
137
+ """Read SIF file header
138
+
139
+ Args:
140
+ filepath: path to SIF file
141
+ """
142
+ with open(filepath, "rb") as sif_file:
143
+ i_wavelength_info = None
144
+ headerlen = None
145
+ i = 0
146
+ self.m_offset = 0
147
+ while True:
148
+ raw_line = sif_file.readline()
149
+ line = raw_line.strip()
150
+ if i == 0:
151
+ if line != b"Andor Technology Multi-Channel File":
152
+ sif_file.close()
153
+ raise ValueError(f"{filepath} is not an Andor SIF file")
154
+ elif i == 2:
155
+ tokens = line.split()
156
+ self.temperature = float(tokens[5])
157
+ self.date = time.strftime("%c", time.localtime(float(tokens[4])))
158
+ self.exposuretime = float(tokens[12])
159
+ self.cycletime = float(tokens[13])
160
+ self.accumulations = int(tokens[15])
161
+ self.readout = 1 / float(tokens[18]) / 1e6
162
+ self.gain = float(tokens[21])
163
+ self.vertical_shift_speed = float(tokens[41])
164
+ self.pre_amp_gain = float(tokens[43])
165
+ elif i == 3:
166
+ self.model = to_string(line)
167
+ elif i == 5:
168
+ self.original_filename = to_string(line)
169
+ if i_wavelength_info is None and i > 7:
170
+ if line.startswith(b"65538 ") and len(line) == 17:
171
+ i_wavelength_info = i + 1
172
+ if i_wavelength_info is not None and i == i_wavelength_info:
173
+ wavelength_info = line.split()
174
+ self.center_wavelength = float(wavelength_info[3])
175
+ self.grating = float(wavelength_info[6])
176
+ blaze = wavelength_info[7]
177
+ if blaze.endswith(b"NM"):
178
+ blaze = blaze[:-2]
179
+ self.grating_blaze = float(blaze)
180
+ if headerlen is None:
181
+ if line.startswith(b"Counts"):
182
+ headerlen = i + 3
183
+ else:
184
+ if i == headerlen - 2:
185
+ if line[:12] == b"Pixel number":
186
+ line = line[12:]
187
+ tokens = line.split()
188
+ if len(tokens) < 6:
189
+ raise ValueError("Not able to read stacksize.")
190
+ self.yres = int(tokens[2])
191
+ self.xres = int(tokens[3])
192
+ self.stacksize = int(tokens[5])
193
+ elif i == headerlen - 1:
194
+ tokens = line.split()
195
+ if len(tokens) < 7:
196
+ raise ValueError("Not able to read Image dimensions.")
197
+ self.left = int(tokens[1])
198
+ self.top = int(tokens[2])
199
+ self.right = int(tokens[3])
200
+ self.bottom = int(tokens[4])
201
+ self.xbin = int(tokens[5])
202
+ self.ybin = int(tokens[6])
203
+ elif i >= headerlen:
204
+ if b"\x00" in line:
205
+ break
206
+ i += 1
207
+ self.m_offset += len(raw_line)
208
+
209
+ width = self.right - self.left + 1
210
+ mod = width % self.xbin
211
+ self.width = int((width - mod) / self.ybin)
212
+ height = self.top - self.bottom + 1
213
+ mod = height % self.ybin
214
+ self.height = int((height - mod) / self.xbin)
215
+
216
+ self.filesize = os.path.getsize(filepath)
217
+ self.datasize = self.width * self.height * 4 * self.stacksize
218
+
219
+ def read(self) -> np.ndarray:
220
+ """Read the data from the SIF file
221
+
222
+ Returns:
223
+ The image data. The shape of the array is (stacksize, height, width)
224
+ """
225
+ with open(self.filepath, "rb") as sif_file:
226
+ sif_file.seek(self.m_offset)
227
+ block = sif_file.read(self.width * self.height * self.stacksize * 4)
228
+ data = np.frombuffer(block, dtype=np.float32)
229
+ # If there is a background image, it will be stored just after the signal
230
+ # data. The background image is the same size as the signal data.
231
+ # To read the background image, we need to search for the next line starting
232
+ # with "Counts" and read the data from there.
233
+ while True:
234
+ line = sif_file.readline()
235
+ if not line:
236
+ break
237
+ if line.startswith(b"Counts"):
238
+ # Data starts 4 lines after the "Counts" line
239
+ for _ in range(4):
240
+ line = sif_file.readline()
241
+ # Read the background image data
242
+ background_data = sif_file.read(self.width * self.height * 4)
243
+ background = np.frombuffer(background_data, dtype=np.float32)
244
+ # Check if the background data is the same size as the signal data
245
+ if background.size != data.size:
246
+ # This is not a background image: not supported format
247
+ break
248
+ # Add the background data to the signal data, as an additional frame
249
+ data = np.concatenate((data, background))
250
+ # Update the stack size to include the background image
251
+ self.stacksize += 1
252
+ break
253
+ return data.reshape(self.stacksize, self.height, self.width)
254
+
255
+
256
+ def imread_sif(filename: str) -> np.ndarray:
257
+ """Open a SIF image
258
+
259
+ Args:
260
+ filename: path to SIF file
261
+
262
+ Returns:
263
+ Image data
264
+ """
265
+ sif_file = SIFFile(filename)
266
+ return sif_file.read()
267
+
268
+
269
+ # MARK: SPIRICON I/O functions
270
+ # ==============================================================================
271
+
272
+
273
+ class SCORFile:
274
+ """Object representing a SPIRICON .scor-data file
275
+
276
+ Args:
277
+ filepath: path to .scor-data file
278
+ """
279
+
280
+ def __init__(self, filepath: str) -> None:
281
+ self.filepath = filepath
282
+ self.metadata = None
283
+ self.width = None
284
+ self.height = None
285
+ self.m_offset = None
286
+ self.filesize = None
287
+ self.datasize = None
288
+ self._read_header()
289
+
290
+ def __repr__(self) -> str:
291
+ """Return a string representation of the object"""
292
+ info = (
293
+ ("Image width", f"{self.width:d}"),
294
+ ("Image Height", f"{self.height:d}"),
295
+ ("Filesize", f"{self.filesize:d}"),
296
+ ("Datasize", f"{self.datasize:d}"),
297
+ ("Offset to Image Data", f"{self.m_offset:f}"),
298
+ )
299
+ desc_len = max(len(d) for d in list(zip(*info))[0]) + 3
300
+ res = ""
301
+ for description, value in info:
302
+ res += ("{:" + str(desc_len) + "}{}\n").format(description + ": ", value)
303
+
304
+ res = object.__repr__(self) + "\n" + res
305
+ return res
306
+
307
+ def _read_header(self) -> None:
308
+ """Read file header"""
309
+ with open(self.filepath, "rb") as data_file:
310
+ metadata = {}
311
+ key1 = None
312
+ while True:
313
+ bline = data_file.readline().strip()
314
+ key1_match = re.match(b"\\[(\\S*)\\]", bline)
315
+ if key1_match is not None:
316
+ key1 = key1_match.groups()[0].decode()
317
+ metadata[key1] = {}
318
+ elif b"=" in bline:
319
+ key2, value = bline.decode().split("=")
320
+ metadata[key1][key2] = value
321
+ else:
322
+ break
323
+
324
+ capture_size = metadata["Capture"]["CaptureSize"]
325
+ self.width, self.height = [int(val) for val in capture_size.split(",")]
326
+
327
+ self.filesize = os.path.getsize(self.filepath)
328
+ self.datasize = self.width * self.height * 2
329
+ self.m_offset = self.filesize - self.datasize - 8
330
+
331
+ def read(self) -> np.ndarray:
332
+ """Read the data from the SPIRICON file
333
+
334
+ Returns:
335
+ The image data as a NumPy array with shape (height, width)
336
+ """
337
+ with open(self.filepath, "rb") as data_file:
338
+ data_file.seek(self.m_offset)
339
+ block = data_file.read(self.datasize)
340
+ data = np.frombuffer(block, dtype=np.int16)
341
+ return data.reshape(self.height, self.width)
342
+
343
+
344
+ def imread_scor(filename: str) -> np.ndarray:
345
+ """Open a SPIRICON image
346
+
347
+ Args:
348
+ filename: path to SPIRICON file
349
+
350
+ Returns:
351
+ Image data
352
+ """
353
+ scor_file = SCORFile(filename)
354
+ return scor_file.read()
355
+
356
+
357
+ # MARK: DICOM I/O functions
358
+ # ==============================================================================
359
+
360
+
361
+ # Original code: see PlotPy package (BSD 3-Clause license)
362
+ def imread_dicom(filename: str) -> np.ndarray:
363
+ """Open DICOM image with pydicom and return a NumPy array
364
+
365
+ Args:
366
+ filename: path to DICOM file
367
+
368
+ Returns:
369
+ Image data as a NumPy array
370
+ """
371
+ # pylint: disable=import-outside-toplevel
372
+ # pylint: disable=import-error
373
+ from pydicom import dcmread # type:ignore
374
+
375
+ dcm = dcmread(filename, force=True)
376
+ # **********************************************************************
377
+ # The following is necessary until pydicom numpy support is improved:
378
+ # (after that, a simple: 'arr = dcm.PixelArray' will work the same)
379
+ format_str = f"{'u' if dcm.PixelRepresentation == 0 else ''}int{dcm.BitsAllocated}"
380
+ try:
381
+ dtype = np.dtype(format_str)
382
+ except TypeError as exc:
383
+ raise TypeError(
384
+ f"Data type not understood by NumPy: "
385
+ f"PixelRepresentation={dcm.PixelRepresentation}, "
386
+ f"BitsAllocated={dcm.BitsAllocated}"
387
+ ) from exc
388
+ arr = np.frombuffer(dcm.PixelData, dtype)
389
+ try:
390
+ # pydicom 0.9.3:
391
+ dcm_is_little_endian = dcm.isLittleEndian
392
+ except AttributeError:
393
+ # pydicom 0.9.4:
394
+ dcm_is_little_endian = dcm.is_little_endian
395
+ if dcm_is_little_endian != (sys.byteorder == "little"):
396
+ arr.byteswap(True)
397
+ spp = getattr(dcm, "SamplesperPixel", 1)
398
+ if hasattr(dcm, "NumberOfFrames") and dcm.NumberOfFrames > 1:
399
+ if spp > 1:
400
+ arr = arr.reshape(spp, dcm.NumberofFrames, dcm.Rows, dcm.Columns)
401
+ else:
402
+ arr = arr.reshape(dcm.NumberOfFrames, dcm.Rows, dcm.Columns)
403
+ else:
404
+ if spp > 1:
405
+ if dcm.BitsAllocated == 8:
406
+ arr = arr.reshape(spp, dcm.Rows, dcm.Columns)
407
+ else:
408
+ raise NotImplementedError(
409
+ "This code only handles SamplesPerPixel > 1 if Bits Allocated = 8"
410
+ )
411
+ else:
412
+ arr = arr.reshape(dcm.Rows, dcm.Columns)
413
+ # **********************************************************************
414
+ return arr
@@ -0,0 +1,9 @@
1
+ # Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
2
+
3
+ """
4
+ I/O features
5
+ """
6
+
7
+ # pylint: disable=unused-import
8
+ import sigima.io.signal.formats # noqa: F401
9
+ from sigima.io.signal.base import SignalIORegistry # noqa: F401
@@ -0,0 +1,129 @@
1
+ # Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
2
+
3
+ """
4
+ Base I/O registry
5
+ """
6
+
7
+ # pylint: disable=invalid-name # Allows short reference names like x, y, ...
8
+
9
+ from __future__ import annotations
10
+
11
+ import abc
12
+ import os.path as osp
13
+ from typing import Sequence
14
+
15
+ import numpy as np
16
+
17
+ from sigima.config import _
18
+ from sigima.io.base import BaseIORegistry, FormatBase
19
+ from sigima.objects.signal import SignalObj, create_signal
20
+ from sigima.worker import CallbackWorkerProtocol
21
+
22
+
23
+ class SignalIORegistry(BaseIORegistry):
24
+ """Metaclass for registering signal I/O handler classes"""
25
+
26
+ REGISTRY_INFO: str = _("Signal I/O formats")
27
+
28
+ _io_format_instances: list[SignalFormatBase] = []
29
+
30
+
31
+ class SignalFormatBaseMeta(SignalIORegistry, abc.ABCMeta):
32
+ """Mixed metaclass to avoid conflicts"""
33
+
34
+
35
+ class SignalFormatBase(abc.ABC, FormatBase, metaclass=SignalFormatBaseMeta):
36
+ """Class representing a signal file type"""
37
+
38
+ HEADER_KEY = "header"
39
+
40
+ @staticmethod
41
+ def create_object(filename: str, index: int | None = None) -> SignalObj:
42
+ """Create empty object
43
+
44
+ Args:
45
+ filename: File name
46
+ index: Index of object in file
47
+
48
+ Returns:
49
+ Signal object
50
+ """
51
+ name = osp.basename(filename)
52
+ if index is not None:
53
+ name += f" {index:02d}"
54
+ return create_signal(name, metadata={"source": filename})
55
+
56
+ def create_signal(
57
+ self, xydata: np.ndarray, filename: str, index: int | None = None
58
+ ) -> SignalObj:
59
+ """Create signal object from xydata and filename.
60
+
61
+ Args:
62
+ xydata: XY data
63
+ filename: File name
64
+ index: Index of object in file
65
+
66
+ Returns:
67
+ Signal object
68
+ """
69
+ obj = self.create_object(filename, index=index)
70
+ obj.set_xydata(xydata[:, 0], xydata[:, index or 1])
71
+ return obj
72
+
73
+ def create_signals_from(self, xydata: np.ndarray, filename: str) -> list[SignalObj]:
74
+ """Create signal objects from xydata and filename
75
+
76
+ Args:
77
+ xydata: XY data
78
+ filename: File name
79
+
80
+ Returns:
81
+ List of signal objects
82
+ """
83
+ assert isinstance(xydata, np.ndarray), "Data type not supported"
84
+ assert len(xydata.shape) in (1, 2), "Data not supported"
85
+ if len(xydata.shape) == 1:
86
+ # 1D data
87
+ obj = self.create_object(filename)
88
+ obj.set_xydata(np.arange(xydata.size), xydata)
89
+ return [obj]
90
+ # 2D data: x, y1, y2, ...
91
+ # Eventually transpose data:
92
+ if xydata.shape[1] > xydata.shape[0]:
93
+ xydata = xydata.T
94
+ # If only data contains one x and y columns, return single object without index
95
+ # in title
96
+ if xydata.shape[1] == 2:
97
+ return [self.create_signal(xydata, filename)]
98
+
99
+ objs = []
100
+ # Create objects for each y column
101
+ for i in range(1, xydata.shape[1]):
102
+ objs.append(self.create_signal(xydata, filename, i))
103
+ return objs
104
+
105
+ def read(
106
+ self, filename: str, worker: CallbackWorkerProtocol | None = None
107
+ ) -> Sequence[SignalObj]:
108
+ """Read list of signal objects from file
109
+
110
+ Args:
111
+ filename: File name
112
+ worker: Callback worker object
113
+
114
+ Returns:
115
+ List of signal objects
116
+ """
117
+ xydata = self.read_xydata(filename)
118
+ return self.create_signals_from(xydata, filename)
119
+
120
+ def read_xydata(self, filename: str) -> np.ndarray:
121
+ """Read data and metadata from file, write metadata to object, return xydata
122
+
123
+ Args:
124
+ filename: File name
125
+
126
+ Returns:
127
+ XY data
128
+ """
129
+ raise NotImplementedError(f"Reading from {self.info.name} is not supported")