microeye 2.3.2__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 (190) hide show
  1. microEye/__init__.py +47 -0
  2. microEye/_version.py +2 -0
  3. microEye/analysis/__init__.py +1 -0
  4. microEye/analysis/checklist_dialog.py +143 -0
  5. microEye/analysis/cmosMaps.py +228 -0
  6. microEye/analysis/filters/__init__.py +9 -0
  7. microEye/analysis/filters/base.py +21 -0
  8. microEye/analysis/filters/spatial.py +338 -0
  9. microEye/analysis/filters/temporal.py +76 -0
  10. microEye/analysis/fitting/__init__.py +0 -0
  11. microEye/analysis/fitting/fit.py +680 -0
  12. microEye/analysis/fitting/nena.py +375 -0
  13. microEye/analysis/fitting/phasor_fit.py +90 -0
  14. microEye/analysis/fitting/processing.py +317 -0
  15. microEye/analysis/fitting/psf/__init__.py +6 -0
  16. microEye/analysis/fitting/psf/extract.py +1129 -0
  17. microEye/analysis/fitting/psf/rubost_mean.py +150 -0
  18. microEye/analysis/fitting/psf/spline.py +167 -0
  19. microEye/analysis/fitting/psf/stats/__init__.py +12 -0
  20. microEye/analysis/fitting/psf/stats/core.py +295 -0
  21. microEye/analysis/fitting/psf/stats/curve_fit.py +708 -0
  22. microEye/analysis/fitting/psf/stats/io.py +104 -0
  23. microEye/analysis/fitting/psf/stats/slope_fit.py +171 -0
  24. microEye/analysis/fitting/psf/temp.py +147 -0
  25. microEye/analysis/fitting/psf/test.py +47 -0
  26. microEye/analysis/fitting/pyfit3Dcspline/CPU/CPUfunctions.py +657 -0
  27. microEye/analysis/fitting/pyfit3Dcspline/CPU/CPUmleFit_LM.py +1336 -0
  28. microEye/analysis/fitting/pyfit3Dcspline/CPU/CPUsplineLib.py +270 -0
  29. microEye/analysis/fitting/pyfit3Dcspline/CPU/__init__.py +1 -0
  30. microEye/analysis/fitting/pyfit3Dcspline/GPU/GPUfunctions.py +609 -0
  31. microEye/analysis/fitting/pyfit3Dcspline/GPU/GPUmleFit_LM_EMCCD.py +1396 -0
  32. microEye/analysis/fitting/pyfit3Dcspline/GPU/GPUmleFit_LM_sCMOS.py +1426 -0
  33. microEye/analysis/fitting/pyfit3Dcspline/GPU/GPUsplineLib.py +231 -0
  34. microEye/analysis/fitting/pyfit3Dcspline/GPU/__init__.py +2 -0
  35. microEye/analysis/fitting/pyfit3Dcspline/__init__.py +10 -0
  36. microEye/analysis/fitting/pyfit3Dcspline/constants.py +27 -0
  37. microEye/analysis/fitting/pyfit3Dcspline/mainfunctions.py +903 -0
  38. microEye/analysis/fitting/results.py +917 -0
  39. microEye/analysis/fitting/results_stats.py +251 -0
  40. microEye/analysis/fitting/tardis.py +209 -0
  41. microEye/analysis/multi_viewer.py +568 -0
  42. microEye/analysis/processing/__init__.py +1 -0
  43. microEye/analysis/processing/frc.py +120 -0
  44. microEye/analysis/rendering/__init__.py +3 -0
  45. microEye/analysis/rendering/base.py +452 -0
  46. microEye/analysis/rendering/cloud.py +215 -0
  47. microEye/analysis/rendering/core.py +47 -0
  48. microEye/analysis/rendering/volumetric.py +198 -0
  49. microEye/analysis/tools/__init__.py +0 -0
  50. microEye/analysis/tools/kymograms.py +1079 -0
  51. microEye/analysis/tools/roi_selectors.py +556 -0
  52. microEye/analysis/utils/__init__.py +3 -0
  53. microEye/analysis/utils/coordinates.py +18 -0
  54. microEye/analysis/utils/images.py +68 -0
  55. microEye/analysis/utils/windows.py +19 -0
  56. microEye/analysis/viewer/__init__.py +3 -0
  57. microEye/analysis/viewer/image_options_widget.py +615 -0
  58. microEye/analysis/viewer/images.py +1225 -0
  59. microEye/analysis/viewer/layers_widget.py +415 -0
  60. microEye/analysis/viewer/localizations.py +1304 -0
  61. microEye/analysis/viewer/psf.py +1003 -0
  62. microEye/analysis/viewer/volume.py +456 -0
  63. microEye/hardware/__init__.py +3 -0
  64. microEye/hardware/cams/__init__.py +29 -0
  65. microEye/hardware/cams/camera_calibration.py +99 -0
  66. microEye/hardware/cams/camera_list.py +528 -0
  67. microEye/hardware/cams/camera_options.py +694 -0
  68. microEye/hardware/cams/camera_panel.py +941 -0
  69. microEye/hardware/cams/dummy/__init__.py +1 -0
  70. microEye/hardware/cams/dummy/dummy_panel.py +759 -0
  71. microEye/hardware/cams/jobs.py +497 -0
  72. microEye/hardware/cams/line_profiler.py +99 -0
  73. microEye/hardware/cams/linescan/IR_Cam.py +474 -0
  74. microEye/hardware/cams/linescan/__init__.py +1 -0
  75. microEye/hardware/cams/micam.py +524 -0
  76. microEye/hardware/cams/pco/__init__.py +58 -0
  77. microEye/hardware/cams/pco/enums.py +382 -0
  78. microEye/hardware/cams/pco/pco_cam.py +761 -0
  79. microEye/hardware/cams/pco/pco_panel.py +477 -0
  80. microEye/hardware/cams/shortcuts.py +312 -0
  81. microEye/hardware/cams/thorlabs/__init__.py +1 -0
  82. microEye/hardware/cams/thorlabs/thorlabs.py +1508 -0
  83. microEye/hardware/cams/thorlabs/thorlabs_panel.py +850 -0
  84. microEye/hardware/cams/ueye/__init__.py +1 -0
  85. microEye/hardware/cams/ueye/ueye_camera.py +1023 -0
  86. microEye/hardware/cams/ueye/ueye_panel.py +861 -0
  87. microEye/hardware/cams/vimba/__init__.py +1 -0
  88. microEye/hardware/cams/vimba/vimba_cam.py +1000 -0
  89. microEye/hardware/cams/vimba/vimba_panel.py +813 -0
  90. microEye/hardware/device.py +60 -0
  91. microEye/hardware/lasers/__init__.py +13 -0
  92. microEye/hardware/lasers/io_matchbox.py +791 -0
  93. microEye/hardware/lasers/io_params.py +85 -0
  94. microEye/hardware/lasers/io_single_laser.py +742 -0
  95. microEye/hardware/lasers/laser_relay.py +594 -0
  96. microEye/hardware/mieye/__init__.py +1 -0
  97. microEye/hardware/mieye/acquisition_manager.py +467 -0
  98. microEye/hardware/mieye/devices_manager.py +533 -0
  99. microEye/hardware/mieye/miEye.py +659 -0
  100. microEye/hardware/misc/__init__.py +0 -0
  101. microEye/hardware/misc/acquisition_view.py +71 -0
  102. microEye/hardware/misc/reglo.py +761 -0
  103. microEye/hardware/misc/temp.py +188 -0
  104. microEye/hardware/port_config.py +59 -0
  105. microEye/hardware/protocols/__init__.py +2 -0
  106. microEye/hardware/protocols/actions.py +402 -0
  107. microEye/hardware/protocols/actions_items.py +703 -0
  108. microEye/hardware/protocols/designer.py +244 -0
  109. microEye/hardware/protocols/scene_manager.py +191 -0
  110. microEye/hardware/protocols/serialization.py +97 -0
  111. microEye/hardware/pycromanager/__init__.py +16 -0
  112. microEye/hardware/pycromanager/core.py +1433 -0
  113. microEye/hardware/pycromanager/devices.py +461 -0
  114. microEye/hardware/pycromanager/enums.py +107 -0
  115. microEye/hardware/pycromanager/headless.py +153 -0
  116. microEye/hardware/pycromanager/utils.py +34 -0
  117. microEye/hardware/pycromanager/widgets/__init__.py +5 -0
  118. microEye/hardware/pycromanager/widgets/bridges.py +407 -0
  119. microEye/hardware/pycromanager/widgets/headless_manager.py +258 -0
  120. microEye/hardware/pycromanager/widgets/headless_options.py +224 -0
  121. microEye/hardware/pycromanager/widgets/pycro_panel.py +455 -0
  122. microEye/hardware/stages/__init__.py +18 -0
  123. microEye/hardware/stages/elliptec/__init__.py +5 -0
  124. microEye/hardware/stages/elliptec/baseDevice.py +314 -0
  125. microEye/hardware/stages/elliptec/device.py +384 -0
  126. microEye/hardware/stages/elliptec/deviceID.py +212 -0
  127. microEye/hardware/stages/elliptec/devicePort.py +379 -0
  128. microEye/hardware/stages/elliptec/deviceStatus.py +65 -0
  129. microEye/hardware/stages/elliptec/devicesView.py +706 -0
  130. microEye/hardware/stages/elliptec/ellDevices.py +134 -0
  131. microEye/hardware/stages/elliptec/messageUpdater.py +34 -0
  132. microEye/hardware/stages/elliptec/motorInfo.py +153 -0
  133. microEye/hardware/stages/elliptec/stage.py +62 -0
  134. microEye/hardware/stages/elliptec/test.py +139 -0
  135. microEye/hardware/stages/kinesis/__init__.py +1 -0
  136. microEye/hardware/stages/kinesis/kdc101/__init__.py +1 -0
  137. microEye/hardware/stages/kinesis/kdc101/enums.py +1002 -0
  138. microEye/hardware/stages/kinesis/kdc101/factory.py +171 -0
  139. microEye/hardware/stages/kinesis/kdc101/kdc101.py +718 -0
  140. microEye/hardware/stages/kinesis/kinesis.py +776 -0
  141. microEye/hardware/stages/piezo_concept.py +607 -0
  142. microEye/hardware/stages/stabilizer.py +785 -0
  143. microEye/hardware/stages/stage.py +89 -0
  144. microEye/hardware/widgets/__init__.py +10 -0
  145. microEye/hardware/widgets/controller.py +246 -0
  146. microEye/hardware/widgets/devices.py +133 -0
  147. microEye/hardware/widgets/focusWidget.py +264 -0
  148. microEye/hardware/widgets/qlist_slider.py +113 -0
  149. microEye/hardware/widgets/scan_acquisition.py +424 -0
  150. microEye/icons/1024.png +0 -0
  151. microEye/icons/128.png +0 -0
  152. microEye/icons/16.png +0 -0
  153. microEye/icons/24.png +0 -0
  154. microEye/icons/256.png +0 -0
  155. microEye/icons/32.png +0 -0
  156. microEye/icons/48.png +0 -0
  157. microEye/icons/512.png +0 -0
  158. microEye/icons/64.png +0 -0
  159. microEye/icons/__init__.py +0 -0
  160. microEye/icons/close.svg +88 -0
  161. microEye/icons/mieye.png +0 -0
  162. microEye/icons/min.svg +83 -0
  163. microEye/icons/viewer.png +0 -0
  164. microEye/launcher.py +42 -0
  165. microEye/qt.py +181 -0
  166. microEye/utils/__init__.py +2 -0
  167. microEye/utils/enum_encoder.py +10 -0
  168. microEye/utils/expandable_groupbox.py +93 -0
  169. microEye/utils/gui_helper.py +457 -0
  170. microEye/utils/hid/__init__.py +8 -0
  171. microEye/utils/hid/controller.py +153 -0
  172. microEye/utils/hid/device.py +63 -0
  173. microEye/utils/hid/enums.py +140 -0
  174. microEye/utils/hid/utils.py +58 -0
  175. microEye/utils/labelled_slider.py +134 -0
  176. microEye/utils/metadata.py +599 -0
  177. microEye/utils/metadata_tree.py +718 -0
  178. microEye/utils/micro_launcher.py +245 -0
  179. microEye/utils/parameter_tree.py +325 -0
  180. microEye/utils/pyscripting.py +444 -0
  181. microEye/utils/retry_exec.py +35 -0
  182. microEye/utils/start_gui.py +112 -0
  183. microEye/utils/thread_worker.py +115 -0
  184. microEye/utils/uImage.py +1512 -0
  185. microeye-2.3.2.dist-info/METADATA +401 -0
  186. microeye-2.3.2.dist-info/RECORD +190 -0
  187. microeye-2.3.2.dist-info/WHEEL +5 -0
  188. microeye-2.3.2.dist-info/entry_points.txt +2 -0
  189. microeye-2.3.2.dist-info/licenses/LICENSE +674 -0
  190. microeye-2.3.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,917 @@
1
+ from enum import Enum
2
+
3
+ import numpy as np
4
+ import pandas as pd
5
+ from numpy.core._exceptions import _ArrayMemoryError
6
+ from scipy.interpolate import interp1d
7
+
8
+ from microEye.analysis.fitting.processing import *
9
+ from microEye.analysis.rendering import BaseRenderer, Projection, RenderModes
10
+
11
+ UNIQUE_COLUMNS = [
12
+ 'frame',
13
+ 'channel',
14
+ 'x',
15
+ 'y',
16
+ 'z',
17
+ 'background',
18
+ 'intensity',
19
+ 'sigmax',
20
+ 'sigmay',
21
+ 'ratio',
22
+ 'loglike',
23
+ 'iteration',
24
+ 'trackID',
25
+ 'neighbour_distance',
26
+ 'n_merged',
27
+ 'CRLB x',
28
+ 'CRLB y',
29
+ 'CRLB z',
30
+ 'CRLB background',
31
+ 'CRLB intensity',
32
+ 'CRLB sigmax',
33
+ 'CRLB sigmay',
34
+ 'CRLB sigmaz',
35
+ ]
36
+
37
+
38
+ class DataColumns(Enum):
39
+ FRAME = 1
40
+ C = 2
41
+ X = 3
42
+ Y = 4
43
+ Z = 5
44
+ BACKGROUND = 6
45
+ INTENSITY = 7
46
+ X_SIGMA = 8
47
+ Y_SIGMA = 9
48
+ XY_RATIO = 10
49
+ LOG_LIKE = 11
50
+ ITERATION = 12
51
+ TRACK_ID = 13
52
+ NEIGHBOUR_DISTANCE = 14
53
+ N_MERGED = 15
54
+ CRLB_X = 16
55
+ CRLB_Y = 17
56
+ CRLB_Z = 18
57
+ CRLB_I = 19
58
+ CRLB_BG = 20
59
+ CRLB_SIG_X = 21
60
+ CRLB_SIG_Y = 22
61
+ CRLB_SIG_Z = 23
62
+
63
+ def __str__(self):
64
+ return UNIQUE_COLUMNS[self.value - 1]
65
+
66
+ @classmethod
67
+ def from_string(cls, s):
68
+ for column in cls:
69
+ if column.name == s:
70
+ return column
71
+ raise ValueError(f'{cls.__name__} has no value matching "{s}"')
72
+
73
+ @classmethod
74
+ def values(cls):
75
+ return np.array([column.name for column in cls])
76
+
77
+
78
+ class ResultsUnits:
79
+ Pixel = 0
80
+ Nanometer = 1
81
+
82
+
83
+ class FittingMethod(Enum):
84
+ _External = -1
85
+ _2D_Phasor_CPU = 0
86
+ _2D_Gauss_MLE_fixed_sigma = 1
87
+ _2D_Gauss_MLE_free_sigma = 2
88
+ _not_used = 3
89
+ _2D_Gauss_MLE_elliptical_sigma = 4
90
+ _3D_Gauss_MLE_cspline_sigma = 5
91
+
92
+ def __str__(self):
93
+ '''
94
+ Return the string representation of method.
95
+ '''
96
+ return [
97
+ 'External',
98
+ '2D Phasor-Fit (CPU)',
99
+ '2D MLE Gauss-Fit fixed sigma (GPU/CPU)',
100
+ '2D MLE Gauss-Fit free sigma (GPU/CPU)',
101
+ 'Not Available',
102
+ '2D MLE Gauss-Fit elliptical sigma (GPU/CPU)',
103
+ '3D MLE cSpline (GPU/CPU)',
104
+ ][self.value + 1]
105
+
106
+ @staticmethod
107
+ def get_strings():
108
+ return [
109
+ '2D Phasor-Fit (CPU)',
110
+ '2D MLE Gauss-Fit free sigma (GPU/CPU)',
111
+ '2D MLE Gauss-Fit elliptical sigma (GPU/CPU)',
112
+ '3D MLE cSpline (GPU/CPU)',
113
+ ]
114
+
115
+ @staticmethod
116
+ def from_string(s: str):
117
+ '''
118
+ Return the fitting method from string representation.
119
+ '''
120
+ for method in FittingMethod:
121
+ if str(method) == s:
122
+ return method
123
+ raise ValueError(f'Fitting method "{s}" not found.')
124
+
125
+ def __lt__(self, other):
126
+ if isinstance(other, (FittingMethod, int)):
127
+ return self.value < (
128
+ other.value if isinstance(other, FittingMethod) else other
129
+ )
130
+ return NotImplemented
131
+
132
+ def __le__(self, other):
133
+ if isinstance(other, (FittingMethod, int)):
134
+ return self.value <= (
135
+ other.value if isinstance(other, FittingMethod) else other
136
+ )
137
+ return NotImplemented
138
+
139
+ def __gt__(self, other):
140
+ if isinstance(other, (FittingMethod, int)):
141
+ return self.value > (
142
+ other.value if isinstance(other, FittingMethod) else other
143
+ )
144
+ return NotImplemented
145
+
146
+ def __ge__(self, other):
147
+ if isinstance(other, (FittingMethod, int)):
148
+ return self.value >= (
149
+ other.value if isinstance(other, FittingMethod) else other
150
+ )
151
+ return NotImplemented
152
+
153
+ def __eq__(self, other):
154
+ if isinstance(other, (FittingMethod, int)):
155
+ return self.value == (
156
+ other.value if isinstance(other, FittingMethod) else other
157
+ )
158
+ return NotImplemented
159
+
160
+ def __ne__(self, other):
161
+ if isinstance(other, (FittingMethod, int)):
162
+ return self.value != (
163
+ other.value if isinstance(other, FittingMethod) else other
164
+ )
165
+ return NotImplemented
166
+
167
+
168
+ PARAMETER_HEADERS = {
169
+ 0: ['x', 'y', 'intensity', 'background', 'ratio'],
170
+ 1: ['x', 'y', 'intensity', 'background', 'iteration'],
171
+ 2: ['x', 'y', 'intensity', 'background', 'sigmax', 'iteration'],
172
+ 4: ['x', 'y', 'intensity', 'background', 'sigmax', 'sigmay', 'iteration'],
173
+ 5: ['x', 'y', 'intensity', 'background', 'z', 'iteration'],
174
+ }
175
+
176
+
177
+ def map_column_alias(alias: str) -> DataColumns:
178
+ alias_lower = alias.lower()
179
+ if alias_lower in ('frame', 'frames', 't', 'time'):
180
+ return DataColumns.FRAME
181
+ elif alias_lower in ('channel', 'c'):
182
+ return DataColumns.C
183
+ elif alias_lower in ('x', 'x [nm]', 'locx'):
184
+ return DataColumns.X
185
+ elif alias_lower in ('y', 'y [nm]', 'locy'):
186
+ return DataColumns.Y
187
+ elif alias_lower in ('z', 'z [nm]', 'locz'):
188
+ return DataColumns.Z
189
+ elif alias_lower in ('bg', 'background'):
190
+ return DataColumns.BACKGROUND
191
+ elif alias_lower in ('i', 'intensity'):
192
+ return DataColumns.INTENSITY
193
+ elif alias_lower in ('sigma', 'sigmax', 'sigma x', 'x_sigma'):
194
+ return DataColumns.X_SIGMA
195
+ elif alias_lower in ('sigmay', 'sigma y', 'y_sigma'):
196
+ return DataColumns.Y_SIGMA
197
+ elif alias_lower in ('ratio', 'xy_ratio', 'ratio x/y'):
198
+ return DataColumns.XY_RATIO
199
+ elif alias_lower in ('loglike',):
200
+ return DataColumns.LOG_LIKE
201
+ elif alias_lower in ('iteration',):
202
+ return DataColumns.ITERATION
203
+ elif alias_lower in ('trackid', 'track id'):
204
+ return DataColumns.TRACK_ID
205
+ elif alias_lower in ('neighbour_distance', 'neighbour_dist', 'neighbour distance'):
206
+ return DataColumns.NEIGHBOUR_DISTANCE
207
+ elif alias_lower in ('n_merged',):
208
+ return DataColumns.N_MERGED
209
+ elif alias_lower in ('crlb_x', 'crlb x'):
210
+ return DataColumns.CRLB_X
211
+ elif alias_lower in ('crlb_y', 'crlb y'):
212
+ return DataColumns.CRLB_Y
213
+ elif alias_lower in ('crlb_z', 'crlb z'):
214
+ return DataColumns.CRLB_Z
215
+ elif alias_lower in ('crlb_i', 'crlb i', 'crlb intensity'):
216
+ return DataColumns.CRLB_I
217
+ elif alias_lower in ('crlb_bg', 'crlb bg', 'crlb background'):
218
+ return DataColumns.CRLB_BG
219
+ elif alias_lower in ('crlb_sigx', 'crlb sigmax', 'crlb sigmax'):
220
+ return DataColumns.CRLB_SIG_X
221
+ elif alias_lower in ('crlb_sigy', 'crlb sigmay'):
222
+ return DataColumns.CRLB_SIG_Y
223
+ elif alias_lower in ('crlb_sigz', 'crlb sigmaz'):
224
+ return DataColumns.CRLB_SIG_Z
225
+ else:
226
+ # raise ValueError(f'Unrecognized column alias: {alias}')
227
+ return None
228
+
229
+
230
+ class FittingResults:
231
+ def __init__(
232
+ self,
233
+ unit=ResultsUnits.Pixel,
234
+ pixelSize=130.0,
235
+ fittingMethod=FittingMethod._2D_Phasor_CPU,
236
+ path=None,
237
+ ):
238
+ '''Fitting Results
239
+
240
+ Parameters
241
+ ----------
242
+ unit : int, optional
243
+ unit of localized points, by default ResultsUnits.Pixel
244
+ pixelSize : float, optional
245
+ pixel size in nanometers, by default 130.0
246
+ fittingMethod : FittingMethod, optional
247
+ fitting method used, by default FittingMethod._2D_Phasor_CPU
248
+ path : str, optional
249
+ path to file, by default None
250
+ '''
251
+ self.unit = unit
252
+ self.pixel_size = pixelSize
253
+ self.fitting_method = fittingMethod
254
+ self.path = path
255
+ self.colormap = None
256
+ self.intensity_range = None
257
+ if fittingMethod > -1:
258
+ self.parameterHeader = PARAMETER_HEADERS[fittingMethod.value]
259
+ else:
260
+ self.parameterHeader = []
261
+
262
+ # Initialize the dictionary with None for each key
263
+ self.data = {key: None for key in DataColumns}
264
+
265
+ def __len__(self):
266
+ '''Number of localized positions'''
267
+ return len(self.data[self.uniqueKeys()[0]])
268
+
269
+ def __getattr__(self, name):
270
+ key = map_column_alias(name)
271
+ if key in self.data:
272
+ return self.data[key]
273
+ else:
274
+ raise AttributeError(
275
+ f"'{type(self).__name__}' object has no attribute '{name}'."
276
+ )
277
+
278
+ def __setattr__(self, name, value):
279
+ key = map_column_alias(name)
280
+ if key is None:
281
+ super().__setattr__(name, value)
282
+ else:
283
+ self.data[key] = value
284
+
285
+ def uniqueKeys(self) -> list[DataColumns]:
286
+ '''Returns a list of unique keys in FittingResults'''
287
+ keys = []
288
+ for key in self.data:
289
+ if self.data[key] is not None:
290
+ keys.append(key)
291
+
292
+ return keys
293
+
294
+ def extend_column(self, key, value):
295
+ if key not in self.data or self.data[key] is None:
296
+ self.data[key] = value
297
+ else:
298
+ self.data[key] = np.concatenate((self.data[key], value))
299
+
300
+ def _scale_array(self, array: np.ndarray):
301
+ if array is not None:
302
+ array[:, :2] *= self.pixel_size
303
+ if (
304
+ self.fitting_method == FittingMethod._2D_Gauss_MLE_free_sigma
305
+ or self.fitting_method == FittingMethod._3D_Gauss_MLE_cspline_sigma
306
+ ):
307
+ array[:, 4] *= self.pixel_size
308
+ elif self.fitting_method == FittingMethod._2D_Gauss_MLE_elliptical_sigma:
309
+ array[:, 4:6] *= self.pixel_size
310
+
311
+ def get_column(self, key: DataColumns):
312
+ if not isinstance(key, DataColumns):
313
+ raise TypeError(f'Expected DataColumns type, got {type(key)}')
314
+ if self.data[key] is None:
315
+ return np.zeros(self.__len__())
316
+ else:
317
+ return self.data[key]
318
+
319
+ def extend(self, data: tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]):
320
+ '''Extend results by contents of data array
321
+
322
+ Parameters
323
+ ----------
324
+ data : tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]
325
+ tuple of arrays (frames, params, crlbs, loglike),
326
+ single-column (frames, loglike),
327
+ multi-column headers for params see ParametersHeaders
328
+ for each fitting method,
329
+ multi-column headers for crlbs same as params without iterations.
330
+
331
+ For FittingMethod._2D_Phasor_CPU use data:
332
+ (None, params, None, None).
333
+ '''
334
+ frames, params, crlbs, loglike = data
335
+
336
+ if self.unit == ResultsUnits.Pixel:
337
+ self._scale_array(params)
338
+ self._scale_array(crlbs)
339
+
340
+ for idx, var in enumerate(
341
+ [
342
+ DataColumns.X,
343
+ DataColumns.Y,
344
+ DataColumns.INTENSITY,
345
+ DataColumns.BACKGROUND,
346
+ ]
347
+ ):
348
+ self.extend_column(var, params[:, idx])
349
+
350
+ self.extend_column(DataColumns.FRAME, frames)
351
+
352
+ if self.fitting_method == FittingMethod._2D_Phasor_CPU:
353
+ self.extend_column(DataColumns.XY_RATIO, params[:, -1])
354
+ else:
355
+ self.extend_column(DataColumns.ITERATION, params[:, -1])
356
+
357
+ # Extend last column based on FittingMethod
358
+ if self.fitting_method != FittingMethod._2D_Phasor_CPU:
359
+ self.extend_column(DataColumns.LOG_LIKE, loglike)
360
+
361
+ for idx, var in enumerate(
362
+ [
363
+ DataColumns.CRLB_X,
364
+ DataColumns.CRLB_Y,
365
+ DataColumns.CRLB_I,
366
+ DataColumns.CRLB_BG,
367
+ ]
368
+ ):
369
+ self.extend_column(var, crlbs[:, idx])
370
+
371
+ if self.fitting_method == FittingMethod._2D_Gauss_MLE_free_sigma:
372
+ self.extend_column(DataColumns.X_SIGMA, params[:, 4])
373
+ self.extend_column(DataColumns.CRLB_SIG_X, crlbs[:, 4])
374
+ elif self.fitting_method == FittingMethod._2D_Gauss_MLE_elliptical_sigma:
375
+ self.extend_column(DataColumns.X_SIGMA, params[:, 4])
376
+ self.extend_column(DataColumns.Y_SIGMA, params[:, 5])
377
+ self.extend_column(DataColumns.CRLB_SIG_X, crlbs[:, 4])
378
+ self.extend_column(DataColumns.CRLB_SIG_Y, crlbs[:, 5])
379
+ elif self.fitting_method == FittingMethod._3D_Gauss_MLE_cspline_sigma:
380
+ self.extend_column(DataColumns.Z, params[:, 4])
381
+ self.extend_column(DataColumns.CRLB_Z, crlbs[:, 4])
382
+
383
+ def dataFrame(self):
384
+ '''Return fitting results as Pandas DataFrame
385
+
386
+ Returns
387
+ -------
388
+ DataFrame
389
+ fitting results DataFrame with columns FittingResults.columns
390
+ '''
391
+ keys = self.uniqueKeys()
392
+
393
+ if self.fitting_method == FittingMethod._2D_Phasor_CPU:
394
+ loc = np.c_[
395
+ self.data[DataColumns.FRAME],
396
+ self.data[DataColumns.X],
397
+ self.data[DataColumns.Y],
398
+ self.data[DataColumns.BACKGROUND],
399
+ self.data[DataColumns.INTENSITY],
400
+ self.data[DataColumns.XY_RATIO],
401
+ self.get_column(DataColumns.TRACK_ID),
402
+ self.get_column(DataColumns.NEIGHBOUR_DISTANCE),
403
+ self.get_column(DataColumns.N_MERGED),
404
+ ]
405
+
406
+ columns = (
407
+ [
408
+ 'frame',
409
+ ]
410
+ + self.parameterHeader
411
+ + [
412
+ str(DataColumns.TRACK_ID),
413
+ str(DataColumns.NEIGHBOUR_DISTANCE),
414
+ str(DataColumns.N_MERGED),
415
+ ]
416
+ )
417
+ elif self.fitting_method == -1:
418
+ loc = None
419
+ columns = []
420
+ for key in keys:
421
+ col = self.data[key]
422
+ columns.append(str(key))
423
+ loc = col if loc is None else np.c_[loc, col]
424
+ else:
425
+ loc = np.c_[
426
+ self.data[DataColumns.FRAME],
427
+ self.data[DataColumns.X],
428
+ self.data[DataColumns.Y],
429
+ self.data[DataColumns.BACKGROUND],
430
+ self.data[DataColumns.INTENSITY],
431
+ ]
432
+
433
+ if self.fitting_method == FittingMethod._2D_Gauss_MLE_fixed_sigma:
434
+ loc = np.c_[
435
+ loc,
436
+ self.data[DataColumns.ITERATION],
437
+ self.data[DataColumns.CRLB_X],
438
+ self.data[DataColumns.CRLB_Y],
439
+ self.data[DataColumns.CRLB_BG],
440
+ self.data[DataColumns.CRLB_I],
441
+ ]
442
+ elif self.fitting_method == FittingMethod._2D_Gauss_MLE_free_sigma:
443
+ loc = np.c_[
444
+ loc,
445
+ self.data[DataColumns.X_SIGMA],
446
+ self.data[DataColumns.ITERATION],
447
+ self.data[DataColumns.CRLB_X],
448
+ self.data[DataColumns.CRLB_Y],
449
+ self.data[DataColumns.CRLB_BG],
450
+ self.data[DataColumns.CRLB_I],
451
+ self.data[DataColumns.CRLB_SIG_X],
452
+ ]
453
+ elif self.fitting_method == FittingMethod._2D_Gauss_MLE_elliptical_sigma:
454
+ loc = np.c_[
455
+ loc,
456
+ self.data[DataColumns.X_SIGMA],
457
+ self.data[DataColumns.Y_SIGMA],
458
+ self.data[DataColumns.ITERATION],
459
+ self.data[DataColumns.CRLB_X],
460
+ self.data[DataColumns.CRLB_Y],
461
+ self.data[DataColumns.CRLB_BG],
462
+ self.data[DataColumns.CRLB_I],
463
+ self.data[DataColumns.CRLB_SIG_X],
464
+ self.data[DataColumns.CRLB_SIG_Y],
465
+ ]
466
+ elif self.fitting_method == FittingMethod._3D_Gauss_MLE_cspline_sigma:
467
+ loc = np.c_[
468
+ loc,
469
+ self.data[DataColumns.Z],
470
+ self.data[DataColumns.ITERATION],
471
+ self.data[DataColumns.CRLB_X],
472
+ self.data[DataColumns.CRLB_Y],
473
+ self.data[DataColumns.CRLB_BG],
474
+ self.data[DataColumns.CRLB_I],
475
+ self.data[DataColumns.CRLB_Z],
476
+ ]
477
+
478
+ loc = np.c_[
479
+ loc,
480
+ self.data[DataColumns.LOG_LIKE],
481
+ self.get_column(DataColumns.TRACK_ID),
482
+ self.get_column(DataColumns.NEIGHBOUR_DISTANCE),
483
+ self.get_column(DataColumns.N_MERGED),
484
+ ]
485
+
486
+ columns = (
487
+ [
488
+ 'frame',
489
+ ]
490
+ + self.parameterHeader
491
+ + ['CRLB ' + x for x in self.parameterHeader[:-1]]
492
+ + [
493
+ 'loglike',
494
+ str(DataColumns.TRACK_ID),
495
+ str(DataColumns.NEIGHBOUR_DISTANCE),
496
+ str(DataColumns.N_MERGED),
497
+ ]
498
+ )
499
+
500
+ return pd.DataFrame(loc, columns=columns).sort_values(by=str(DataColumns.FRAME))
501
+
502
+ def toRender(self):
503
+ '''Returns columns for rendering
504
+
505
+ Returns
506
+ -------
507
+ tuple(np.ndarray, np.ndarray, np.ndarray)
508
+ tuple contains X [nm], Y [nm], Intensity columns
509
+ '''
510
+ # TODO: update for 3D render later
511
+ return (
512
+ self.data[DataColumns.X],
513
+ self.data[DataColumns.Y],
514
+ self.data[DataColumns.INTENSITY],
515
+ )
516
+
517
+ def toAnimation(self, n_bins=10, pixelSize=10, **kwargs):
518
+ '''Returns columns for animation
519
+ Parameters
520
+ ----------
521
+ n_bins : int, optional
522
+ Number of bins, by default 10
523
+ pixelSize : int, optional
524
+ Pixel size, by default 10
525
+ **kwargs : dict
526
+ Additional keyword arguments for rendering
527
+
528
+ Returns
529
+ -------
530
+ tuple(np.ndarray, np.ndarray, np.ndarray)
531
+ tuple contains animation stack, frame bins, locs per bin
532
+ '''
533
+ # Get kwargs for rendering
534
+ renderMode: RenderModes = kwargs.get('renderMode', RenderModes.HISTOGRAM)
535
+ z_pixel_size = kwargs.get('z_pixel_size')
536
+ projection: Projection = kwargs.get('projection', Projection.XY)
537
+
538
+ # Get unique frames
539
+ unique_frames = np.unique(self.data[DataColumns.FRAME])
540
+
541
+ # Check edge cases
542
+ if len(unique_frames) < 2:
543
+ print('Animation failed: 2 or more frames are required.')
544
+ return None, None, None
545
+
546
+ if n_bins < 2:
547
+ print('Animation failed: 2 or more bins are required.')
548
+ return None, None, None
549
+
550
+ frames_per_bin = int(np.floor(np.max(unique_frames) / n_bins))
551
+
552
+ if frames_per_bin < 2:
553
+ print('Animation failed: 2 or more frames per bin are required.')
554
+ return None, None, None
555
+
556
+ renderEngine = BaseRenderer(pixelSize, z_pixel_size, renderMode)
557
+
558
+ x_max = int((np.max(self.data[DataColumns.X]) / renderEngine._pixel_size) + 4)
559
+ y_max = int((np.max(self.data[DataColumns.Y]) / renderEngine._pixel_size) + 4)
560
+
561
+ frame_ids = np.cumsum(
562
+ np.bincount(self.data[DataColumns.FRAME].astype(np.int64))
563
+ )
564
+
565
+ sub_images = []
566
+ locs_per_bin = []
567
+ frames = []
568
+
569
+ for f in range(0, n_bins):
570
+ mask = slice(
571
+ frame_ids[f * frames_per_bin - 1] if f * frames_per_bin > 0 else 0,
572
+ frame_ids[(f + 1) * frames_per_bin],
573
+ )
574
+ image = renderEngine.render(
575
+ projection,
576
+ self.data[DataColumns.X][mask],
577
+ self.data[DataColumns.Y][mask],
578
+ self.data[DataColumns.Z][mask] if self.data[DataColumns.Z] else None,
579
+ self.data[DataColumns.INTENSITY][mask],
580
+ (y_max, x_max),
581
+ )
582
+ locs_per_bin.append(mask.stop - mask.start)
583
+ frames.append(f * frames_per_bin + frames_per_bin / 2)
584
+ sub_images.append(image)
585
+ print(f'Bins: {f + 1:d}/{n_bins:d}', end='\r')
586
+
587
+ return np.array(sub_images), np.array(frames), np.array(locs_per_bin),
588
+
589
+ def drift_cross_correlation(self, n_bins=10, pixelSize=10, upsampling=100):
590
+ '''Corrects the XY drift using cross-correlation measurments
591
+
592
+ Parameters
593
+ ----------
594
+ n_bins : int, optional
595
+ Number of frame bins, by default 10
596
+ pixelSize : int, optional
597
+ Super-res image pixel size in nanometers, by default 10
598
+ upsampling : int, optional
599
+ phase_cross_correlation upsampling (check skimage.registration),
600
+ by default 100
601
+
602
+ Returns
603
+ -------
604
+ tuple(FittingResults, np.ndarray)
605
+ returns the drift corrected fittingResults and recontructed image
606
+ '''
607
+ unique_frames = np.unique(self.data[DataColumns.FRAME])
608
+ if len(unique_frames) < 2:
609
+ print('Drift cross-correlation failed: no frame info.')
610
+ return
611
+
612
+ frames_per_bin = int(np.floor(np.max(unique_frames) / n_bins))
613
+
614
+ if frames_per_bin < 2:
615
+ print('Drift cross-correlation failed: 2 or more bins are required.')
616
+ return
617
+
618
+ renderEngine = BaseRenderer(pixelSize, RenderModes.HISTOGRAM)
619
+
620
+ x_max = int((np.max(self.data[DataColumns.X]) / renderEngine._pixel_size) + 4)
621
+ y_max = int((np.max(self.data[DataColumns.Y]) / renderEngine._pixel_size) + 4)
622
+
623
+ frame_ids = np.cumsum(
624
+ np.bincount(self.data[DataColumns.FRAME].astype(np.int64))
625
+ )
626
+ max_frame = np.max(self.data[DataColumns.FRAME])
627
+
628
+ # grouped_data = []
629
+ sub_images = []
630
+ shifts = []
631
+ frames = []
632
+
633
+ for f in range(0, n_bins):
634
+ mask = slice(
635
+ frame_ids[f * frames_per_bin - 1] if f * frames_per_bin > 0 else 0,
636
+ frame_ids[(f + 1) * frames_per_bin],
637
+ )
638
+ image = renderEngine.from_array(
639
+ np.c_[
640
+ self.data[DataColumns.X][mask],
641
+ self.data[DataColumns.Y][mask],
642
+ self.data[DataColumns.INTENSITY][mask],
643
+ ],
644
+ (y_max, x_max),
645
+ )
646
+ frames.append(f * frames_per_bin + frames_per_bin / 2)
647
+ sub_images.append(image)
648
+ print(f'Bins: {f + 1:d}/{n_bins:d}', end='\r')
649
+
650
+ print('Shift Estimation ...', end='\r')
651
+ try:
652
+ shifts = shift_estimation(np.array(sub_images), pixelSize, upsampling)
653
+ except _ArrayMemoryError as e:
654
+ print(f'Drift cross-correlation failed: {e}')
655
+ return
656
+
657
+ shifts = np.c_[shifts, np.array(frames)]
658
+ print('Shift Correction ...', end='\r')
659
+
660
+ # An one-dimensional interpolation is applied
661
+ # to drift traces in X and Y dimensions separately.
662
+ interpy = interp1d(
663
+ shifts[:, -1], shifts[:, 0], kind='quadratic', fill_value='extrapolate'
664
+ )
665
+ interpx = interp1d(
666
+ shifts[:, -1], shifts[:, 1], kind='quadratic', fill_value='extrapolate'
667
+ )
668
+ # And this interpolation is used to get the shift at every frame-point
669
+ frames_new = np.arange(0, max_frame, 1)
670
+ interpx = interpx(frames_new)
671
+ interpy = interpy(frames_new)
672
+
673
+ shift_correction(
674
+ interpx,
675
+ interpy,
676
+ frame_ids,
677
+ self.data[DataColumns.X],
678
+ self.data[DataColumns.Y],
679
+ )
680
+
681
+ drift_corrected_image = renderEngine.render_xy(
682
+ self.data[DataColumns.X],
683
+ self.data[DataColumns.Y],
684
+ self.data[DataColumns.INTENSITY],
685
+ )
686
+
687
+ return self, drift_corrected_image, (frames_new, interpx, interpy)
688
+
689
+ def nn_trajectories(
690
+ self, minDistance: float = 0, maxDistance: float = 30, maxOff=1, neighbors=1
691
+ ):
692
+ '''
693
+ Perform nearest neighbor trajectory analysis.
694
+
695
+ Parameters
696
+ ----------
697
+ minDistance : float
698
+ Minimum distance between neighbors in nanometers
699
+ maxDistance : float
700
+ Maximum distance between neighbors in nanometers
701
+ maxOff : int
702
+ Maximum length of trajectory in frames
703
+ neighbors : int
704
+ Number of nearest neighbors to consider
705
+
706
+ Returns
707
+ -------
708
+ FittingResults
709
+ Updated FittingResults object with trajectory information
710
+
711
+ Raises
712
+ ------
713
+ ValueError
714
+ If parameters are invalid
715
+ '''
716
+ self.trackID = np.zeros(self.__len__(), dtype=np.int64)
717
+ self.neighbour_dist = np.zeros(self.__len__(), dtype=np.float64)
718
+ # data = self.dataFrame().to_numpy()
719
+ data = np.c_[
720
+ self.data[DataColumns.FRAME],
721
+ self.data[DataColumns.X],
722
+ self.data[DataColumns.Y],
723
+ ]
724
+
725
+ ids = np.cumsum(np.bincount(self.data[DataColumns.FRAME].astype(np.int64)))
726
+
727
+ counter = 0
728
+ min_frame = np.min(self.data[DataColumns.FRAME])
729
+ max_frame = np.max(self.data[DataColumns.FRAME])
730
+
731
+ for frameID in np.arange(min_frame, max_frame, dtype=np.int64):
732
+ for offset in np.arange(0, maxOff + 1, dtype=np.int64):
733
+ nextID = frameID + offset + 1
734
+
735
+ if nextID > max_frame:
736
+ continue
737
+
738
+ currentMask = slice(
739
+ ids[frameID - 1] if frameID > 0 else 0, ids[frameID]
740
+ )
741
+ nextMask = slice(ids[nextID - 1], ids[nextID])
742
+
743
+ (
744
+ counter,
745
+ self.trackID[currentMask],
746
+ self.trackID[nextMask],
747
+ self.neighbour_dist[nextMask],
748
+ ) = nn_trajectories(
749
+ data[currentMask, :],
750
+ data[nextMask, :],
751
+ c_trackID=self.trackID[currentMask],
752
+ n_trackID=self.trackID[nextMask],
753
+ nn_dist=self.neighbour_dist[nextMask],
754
+ counter=counter,
755
+ minDistance=minDistance,
756
+ maxDistance=maxDistance,
757
+ neighbors=neighbors,
758
+ )
759
+ print(f'NN {frameID / max_frame:.2%} ... ', end='\r')
760
+
761
+ print('Done ... ')
762
+
763
+ return self
764
+
765
+ def merge_tracks(self, maxLength=500):
766
+ self.n_merged = np.zeros(self.__len__(), dtype=np.int64)
767
+ df = self.dataFrame()
768
+ columns = list(df.columns)
769
+
770
+ column_ids = np.zeros(4, dtype=np.int32)
771
+ column_ids[0] = columns.index('frame')
772
+ column_ids[1] = columns.index('trackID')
773
+ column_ids[2] = columns.index('n_merged')
774
+ if 'I' in columns:
775
+ column_ids[3] = columns.index('I')
776
+ elif 'intensity' in columns:
777
+ column_ids[3] = columns.index('intensity')
778
+
779
+ data = df.to_numpy()
780
+
781
+ print('Merging ... ')
782
+ finalData = merge_localizations(data, column_ids, maxLength)
783
+
784
+ df = pd.DataFrame(finalData, columns=columns).sort_values(
785
+ by=str(DataColumns.FRAME)
786
+ )
787
+
788
+ print('Done ... ')
789
+
790
+ return FittingResults.fromDataFrame(df, self.pixel_size)
791
+
792
+ def nearest_neighbour_merging(
793
+ self, minDistance=0, maxDistance=30, maxOff=1, maxLength=500, neighbors=1
794
+ ):
795
+ # Call nn_trajectories
796
+ self.nn_trajectories(minDistance, maxDistance, maxOff, neighbors)
797
+
798
+ # Call merge_tracks
799
+ return self.merge_tracks(maxLength)
800
+
801
+ def drift_fiducial_marker(self):
802
+ df = self.dataFrame()
803
+ columns = df.columns
804
+ data = df.to_numpy()
805
+
806
+ unique_frames, frame_counts = np.unique(
807
+ self.data[DataColumns.FRAME], return_counts=True
808
+ )
809
+ if len(unique_frames) < 2:
810
+ print('Drift correction failed: no frame info.')
811
+ return
812
+
813
+ if self.data[DataColumns.TRACK_ID] is None:
814
+ print('Drift correction failed: please perform NN, no trackIDs.')
815
+ return
816
+ if np.max(self.data[DataColumns.TRACK_ID]) < 1:
817
+ print('Drift correction failed: please perform NN, no trackIDs.')
818
+ return
819
+
820
+ unique_tracks, track_counts = np.unique(
821
+ self.data[DataColumns.TRACK_ID], return_counts=True
822
+ )
823
+ fiducial_tracks_mask = np.logical_and(
824
+ unique_tracks > 0, track_counts == len(unique_frames)
825
+ )
826
+
827
+ fiducial_trackIDs = unique_tracks[fiducial_tracks_mask]
828
+
829
+ if len(fiducial_trackIDs) < 1:
830
+ print('Drift correction failed: no ' + 'fiducial markers tracks detected.')
831
+ return
832
+ else:
833
+ print(f'{len(fiducial_trackIDs):d} tracks detected.', end='\r')
834
+
835
+ fiducial_markers = np.zeros((len(fiducial_trackIDs), len(unique_frames), 2))
836
+
837
+ print('Fiducial markers ... ', end='\r')
838
+ for idx in np.arange(fiducial_markers.shape[0]):
839
+ mask = self.data[DataColumns.TRACK_ID] == fiducial_trackIDs[idx]
840
+ fiducial_markers[idx] = np.c_[
841
+ self.data[DataColumns.X][mask], self.data[DataColumns.Y][mask]
842
+ ]
843
+
844
+ fiducial_markers[idx, :, 0] -= fiducial_markers[idx, 0, 0]
845
+ fiducial_markers[idx, :, 1] -= fiducial_markers[idx, 0, 1]
846
+
847
+ print('Drift estimate ... ', end='\r')
848
+ drift_x = np.mean(fiducial_markers[:, :, 0], axis=0)
849
+ drift_y = np.mean(fiducial_markers[:, :, 1], axis=0)
850
+
851
+ print('Drift correction ... ', end='\r')
852
+ ids = np.cumsum(np.bincount(self.data[DataColumns.FRAME].astype(np.int64)))
853
+
854
+ for idx in np.arange(len(unique_frames)):
855
+ frame_id = int(unique_frames[idx])
856
+ mask = slice(ids[frame_id - 1] if frame_id > 0 else 0, ids[frame_id])
857
+ self.data[DataColumns.X][mask] -= drift_x[idx]
858
+ self.data[DataColumns.Y][mask] -= drift_y[idx]
859
+
860
+ # df = pd.DataFrame(
861
+ # data,
862
+ # columns=columns)
863
+
864
+ # drift_corrected = FittingResults.fromDataFrame(df, self.pixelSize)
865
+
866
+ return self, (unique_frames, drift_x, drift_y)
867
+
868
+ def zero_coordinates(self):
869
+ # Use vectorized operations with optional masking
870
+ for coord in (DataColumns.X, DataColumns.Y, DataColumns.Z):
871
+ if self.data[coord] is not None:
872
+ self.data[coord] -= np.min(self.data[coord])
873
+
874
+ @staticmethod
875
+ def fromFile(filename: str, pixelSize: float):
876
+ '''Populates fitting results from a tab seperated values
877
+ (tsv) file.
878
+
879
+ Parameters
880
+ ----------
881
+ filename : str
882
+ path to tab seperated file (.tsv)
883
+ pixelSize : float
884
+ projected pixel size in nanometers
885
+
886
+ Returns
887
+ -------
888
+ FittingResults
889
+ FittingResults with imported data
890
+ '''
891
+ if '.tsv' in filename:
892
+ dataFrame = pd.read_csv(filename, sep='\t', engine='python')
893
+ elif '.h5' in filename:
894
+ dataFrame = pd.read_hdf(filename, mode='r')
895
+ else:
896
+ raise Exception(
897
+ 'Supported file types are dataframes ' + 'stored as .tsv or .h5 files.'
898
+ )
899
+
900
+ return FittingResults.fromDataFrame(dataFrame, pixelSize, path=filename)
901
+
902
+ @staticmethod
903
+ def fromDataFrame(dataFrame: pd.DataFrame, pixelSize: float = 1, **kwargs):
904
+ fittingResults = None
905
+ fittingResults = FittingResults(
906
+ ResultsUnits.Nanometer,
907
+ pixelSize,
908
+ FittingMethod._External,
909
+ kwargs.get('path'),
910
+ )
911
+
912
+ for column in dataFrame.columns.values.tolist():
913
+ key = map_column_alias(column)
914
+ if key is not None:
915
+ fittingResults.data[key] = dataFrame[column].to_numpy()
916
+
917
+ return fittingResults