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,251 @@
1
+ import sys
2
+ import traceback
3
+ import typing
4
+
5
+ import numpy as np
6
+ import pandas as pd
7
+ import pyqtgraph as pg
8
+
9
+ from microEye.qt import QApplication, QtWidgets, Signal
10
+
11
+
12
+ class resultsStatsWidget(QtWidgets.QWidget):
13
+ dataFilterUpdated = Signal(pd.DataFrame)
14
+
15
+ def __init__(self, parent: typing.Optional['QtWidgets.QWidget'] = None):
16
+ super().__init__(parent=parent)
17
+
18
+ self._minHeight = 125
19
+
20
+ self._layout = QtWidgets.QVBoxLayout()
21
+ self.setLayout(self._layout)
22
+
23
+ self.plot_widgets = []
24
+ self.plot_lr = []
25
+ self.filtered: pd.DataFrame = None
26
+
27
+ self._extended = False
28
+
29
+ def toggle_track_plots(self):
30
+ self._extended = not self._extended
31
+ self.setData(self._df)
32
+
33
+ def setData(self, df: pd.DataFrame):
34
+ self.clear()
35
+
36
+ if df is None:
37
+ return
38
+
39
+ self._df = df
40
+
41
+ for _idx, column in enumerate(df.columns):
42
+ pw, lr = self._create_plot_widget(column, df)
43
+ self.plot_widgets.append(pw)
44
+ self.plot_lr.append(lr)
45
+ self._layout.addWidget(pw)
46
+ lr.sigRegionChangeFinished.connect(self.update)
47
+
48
+ self._derive_tracking_df()
49
+
50
+ if self._tracking_df is not None:
51
+ for column in self._tracking_df.columns:
52
+ pw, lr = self._create_plot_widget(column, self._tracking_df)
53
+ self.plot_widgets.append(pw)
54
+ self.plot_lr.append(lr)
55
+ self._layout.addWidget(pw)
56
+ lr.sigRegionChangeFinished.connect(self.update)
57
+
58
+ def _derive_tracking_df(self):
59
+ if self._df['trackID'].max() > 0 and self._extended:
60
+ track_ids = self._df[self._df['trackID'] > 0]['trackID'].to_numpy()
61
+ distances = self._df[self._df['trackID'] > 0][
62
+ 'neighbour_distance'
63
+ ].to_numpy()
64
+
65
+ # Get unique track IDs and their counts
66
+ unique_ids, counts = np.unique(track_ids, return_counts=True)
67
+
68
+ # Calculate sum of distances per track using bincount
69
+ sums = np.bincount(track_ids.astype(int), weights=distances)[
70
+ unique_ids.astype(int)
71
+ ]
72
+
73
+ # Calculate MSD using bincount for sum of squares
74
+ msd = (
75
+ np.bincount(track_ids.astype(int), weights=distances**2)[
76
+ unique_ids.astype(int)
77
+ ]
78
+ / counts
79
+ )
80
+
81
+ self._tracking_df = pd.DataFrame(
82
+ {'track_length': counts, 'track_distance': sums, 'track_msd': msd},
83
+ index=unique_ids,
84
+ )
85
+ else:
86
+ self._tracking_df = None
87
+
88
+ def _create_plot_widget(
89
+ self, column: str, df: pd.DataFrame
90
+ ) -> tuple[pg.PlotWidget, pg.LinearRegionItem]:
91
+ '''
92
+ Create a plot widget with appropriate visualization for a dataframe column.
93
+ '''
94
+ # Create basic plot widget
95
+ pw = pg.PlotWidget(self)
96
+ pw.setMinimumHeight(self._minHeight)
97
+ pw.setLabel('left', 'Counts', units='')
98
+ pw.setLabel('bottom', column, units='')
99
+
100
+ # Create linear region item
101
+ bounds = [df[column].min(), df[column].max()]
102
+ lr = pg.LinearRegionItem(bounds, bounds=bounds, movable=True)
103
+ pw.addItem(lr)
104
+
105
+ # Add visualization based on column type
106
+ if column == 'trackID':
107
+ self._add_trackID_visualization(pw, df[column])
108
+ else:
109
+ self._add_histogram_visualization(pw, lr, df[column], column)
110
+
111
+ return pw, lr
112
+
113
+ def _add_trackID_visualization(
114
+ self, plot_widget: pg.PlotWidget, data_series: pd.Series
115
+ ):
116
+ '''Add bar graph visualization for trackID column.'''
117
+ data = data_series.to_numpy()
118
+ data = data[data.nonzero()]
119
+
120
+ if len(data) > 0:
121
+ uniq, counts = np.unique(data, return_counts=True)
122
+ bars = pg.BarGraphItem(x=uniq, height=counts, width=0.9, brush='b')
123
+ plot_widget.addItem(bars)
124
+ else:
125
+ plot_widget.plot(
126
+ [0], [1], fillLevel=0, fillOutline=True, brush=(0, 0, 255, 150)
127
+ )
128
+
129
+ def _add_histogram_visualization(
130
+ self,
131
+ plot_widget: pg.PlotWidget,
132
+ lr: pg.LinearRegionItem,
133
+ data_series: pd.Series,
134
+ column_name: str,
135
+ ):
136
+ '''Add histogram visualization for numeric columns.'''
137
+ try:
138
+ min_val, max_val = self._calculate_column_range(data_series, column_name)
139
+
140
+ counts = data_series.count()
141
+ hist, bins = np.histogram(
142
+ data_series.to_numpy(),
143
+ bins=min(counts + 1, 1024),
144
+ range=(min_val, max_val),
145
+ )
146
+
147
+ if column_name != 'neighbour_distance':
148
+ lr.setRegion((min_val, max_val))
149
+
150
+ plot_widget.plot(
151
+ bins,
152
+ hist / np.max(hist),
153
+ stepMode='center',
154
+ fillLevel=0,
155
+ fillOutline=True,
156
+ brush=(0, 0, 255, 150),
157
+ )
158
+ except Exception:
159
+ traceback.print_exc()
160
+ print(column_name)
161
+
162
+ def _calculate_column_range(
163
+ self, data_series: pd.Series, column_name: str
164
+ ) -> tuple[float, float]:
165
+ '''Calculate appropriate min and max values for column visualization.'''
166
+ if column_name in [
167
+ 'frame',
168
+ 'x',
169
+ 'y',
170
+ 'z',
171
+ 'iteration',
172
+ 'track_length',
173
+ 'track_distance',
174
+ 'track_msd',
175
+ ]:
176
+ min_val = data_series.min()
177
+ max_val = data_series.max()
178
+ elif column_name == 'neighbour_distance':
179
+ filtered_data = data_series[data_series > 0]
180
+ min_val = filtered_data.min() if not pd.isna(filtered_data.min()) else 0
181
+ max_val = filtered_data.max() if not pd.isna(filtered_data.max()) else 1
182
+ else:
183
+ q1, q3 = np.nanpercentile(data_series.to_numpy(), [10, 90])
184
+ iqr = q3 - q1
185
+ min_val = (
186
+ q1 - 1.5 * iqr if column_name == 'loglike' else max(q1 - 1.6 * iqr, 0)
187
+ )
188
+ max_val = q3 + 1.6 * iqr
189
+
190
+ return min_val, max_val
191
+
192
+ def _clearLayout(self):
193
+ for i in reversed(range(self._layout.count())):
194
+ widgetToRemove = self._layout.itemAt(i).widget()
195
+ # remove it from the layout list
196
+ self._layout.removeWidget(widgetToRemove)
197
+ # remove it from the gui
198
+ widgetToRemove.setParent(None)
199
+
200
+ def clear(self):
201
+ self.plot_widgets = []
202
+ self.plot_lr: list[pg.LinearRegionItem] = []
203
+
204
+ self._df = None
205
+ self._tracking_df = None
206
+
207
+ self._clearLayout()
208
+
209
+ def update(self):
210
+ mask = np.ones(self._df.count().max(), dtype=bool)
211
+ for idx, column in enumerate(self._df.columns):
212
+ Rmin, Rmax = self.plot_lr[idx].getRegion()
213
+
214
+ if Rmin >= Rmax:
215
+ continue
216
+
217
+ column_data = self._df[column].to_numpy()
218
+ mask &= (column_data >= Rmin) & (column_data <= Rmax)
219
+
220
+ if self._tracking_df is not None and self._extended:
221
+ tr_mask = np.ones(self._tracking_df.count().max(), dtype=bool)
222
+ for idx, column in enumerate(self._tracking_df.columns):
223
+ Rmin, Rmax = self.plot_lr[idx + len(self._df.columns)].getRegion()
224
+
225
+ if Rmin >= Rmax:
226
+ continue
227
+
228
+ column_data = self._tracking_df[column].to_numpy()
229
+ tr_mask &= (column_data >= Rmin) & (column_data <= Rmax)
230
+
231
+ # Create lookup array for track filtering
232
+ track_lookup = np.zeros(int(self._df['trackID'].max()) + 1, dtype=bool)
233
+ track_lookup[self._tracking_df.index[tr_mask].astype(int)] = True
234
+
235
+ # Apply track filter to main mask
236
+ track_ids = self._df['trackID'].to_numpy()
237
+ valid_tracks = (track_ids > 0) & track_lookup[track_ids.astype(int)]
238
+ mask &= valid_tracks
239
+
240
+ self.filtered = self._df[mask]
241
+
242
+ self.dataFilterUpdated.emit(self.filtered)
243
+
244
+
245
+ if __name__ == '__main__':
246
+ app = QApplication(sys.argv)
247
+ win = resultsStatsWidget()
248
+ win.show()
249
+ win.setData(5)
250
+
251
+ app.exec()
@@ -0,0 +1,209 @@
1
+ import time
2
+ import traceback
3
+
4
+ import numba as nb
5
+ import numpy as np
6
+ import pyqtgraph as pg
7
+
8
+ from microEye.analysis.fitting.processing import tardis_data
9
+ from microEye.qt import QDateTime, Qt, QtWidgets, Signal
10
+ from microEye.utils.thread_worker import QThreadWorker
11
+
12
+
13
+ class TARDIS_Widget(QtWidgets.QWidget):
14
+ startWorker = Signal(QThreadWorker)
15
+
16
+ def __init__(
17
+ self,
18
+ frames: np.ndarray,
19
+ locX: np.ndarray,
20
+ locY: np.ndarray,
21
+ locZ: np.ndarray=None,
22
+ parent=None,
23
+ ):
24
+ super().__init__(parent=parent)
25
+
26
+ self.setWindowTitle('TARDIS Analysis')
27
+ self.frames = frames
28
+ self.locX = locX
29
+ self.locY = locY
30
+ self.locZ = locZ
31
+ self.res = None
32
+
33
+ self.hlayout = QtWidgets.QHBoxLayout()
34
+ self.setLayout(self.hlayout)
35
+
36
+ self.histogram = pg.PlotWidget()
37
+ self.hlayout.addWidget(self.histogram)
38
+
39
+ self.vlayout = QtWidgets.QVBoxLayout()
40
+ self.hlayout.addLayout(self.vlayout)
41
+
42
+ self.tardis_group = QtWidgets.QGroupBox('TARDIS parameters')
43
+ self.vlayout.addWidget(self.tardis_group)
44
+ self.tardis_lay = QtWidgets.QFormLayout()
45
+ self.tardis_group.setLayout(self.tardis_lay)
46
+
47
+ self.dt = QtWidgets.QComboBox()
48
+ self.dt.setEditable(True)
49
+ self.dt.addItem('[1, 30, 100, 300, 1000]')
50
+ self.dt.addItem('np.linspace(1, 10, 10, dtype=np.int32)')
51
+ self.dt.setToolTip(
52
+ 'Enter here a Python expression that provides a list|array of dt values.')
53
+
54
+ self.tardis_lay.addRow(
55
+ QtWidgets.QLabel('Δt [frames]:'), self.dt)
56
+
57
+ self.exposure = QtWidgets.QDoubleSpinBox()
58
+ self.exposure.setDecimals(4)
59
+ self.exposure.setMinimum(1)
60
+ self.exposure.setMaximum(1e10)
61
+ self.exposure.setValue(50)
62
+
63
+ self.tardis_lay.addRow(
64
+ QtWidgets.QLabel('Exposure [ms]:'), self.exposure)
65
+
66
+ self.bins = QtWidgets.QSpinBox()
67
+ self.bins.setMinimum(5)
68
+ self.bins.setMaximum(10000)
69
+ self.bins.setValue(1200)
70
+
71
+ self.tardis_lay.addRow(
72
+ QtWidgets.QLabel('N of bins:'), self.bins)
73
+
74
+
75
+ self.maxDist = QtWidgets.QSpinBox()
76
+ self.maxDist.setMinimum(5)
77
+ self.maxDist.setMaximum(10000)
78
+ self.maxDist.setValue(1200)
79
+
80
+ self.tardis_lay.addRow(
81
+ QtWidgets.QLabel('Max Distance:'), self.maxDist)
82
+
83
+ self.clear_plot = QtWidgets.QCheckBox('Clear plot?')
84
+ self.clear_plot.setChecked(True)
85
+ self.tardis_lay.addWidget(self.clear_plot)
86
+
87
+ self.compute_btn = QtWidgets.QPushButton('Compute', clicked=self.compute)
88
+ self.tardis_lay.addWidget(self.compute_btn)
89
+
90
+ self.fitgroup = QtWidgets.QGroupBox('Fitting parameters')
91
+ self.vlayout.addWidget(self.fitgroup)
92
+
93
+ self.fitlay = QtWidgets.QFormLayout()
94
+ self.fitgroup.setLayout(self.fitlay)
95
+
96
+
97
+ self.res_guess = QtWidgets.QCheckBox('Result as initial guess.')
98
+ self.fitlay.addWidget(self.res_guess)
99
+
100
+ self.fit_btn = QtWidgets.QPushButton('Fit', clicked=self.fit_data)
101
+ self.fitlay.addWidget(self.fit_btn)
102
+
103
+ self.log = QtWidgets.QPlainTextEdit()
104
+ self.log.setReadOnly(True)
105
+ self.log.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOn)
106
+ self.log.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
107
+ self.fitlay.addRow(self.log)
108
+
109
+ label_style = {'color': '#EEE', 'font-size': '10pt'}
110
+
111
+ legend = self.histogram.addLegend()
112
+ legend.anchor(itemPos=(1, 0), parentPos=(1, 0))
113
+ self.histogram.setLabel('bottom', 'Distance [nm]', **label_style)
114
+ self.histogram.setLabel('left', 'PDF', **label_style)
115
+ # self._fit_ref = self.histogram.plot(
116
+ # _bins, np.zeros_like(_bins), pen=greenP, name='Fit')
117
+
118
+ def fit_data(self):
119
+ self.log.appendPlainText(
120
+ QDateTime.currentDateTime().toString(
121
+ '>>> yyyy/MM/dd hh:mm:ss') + ' TARDIS fit started \n'
122
+ )
123
+
124
+ def compute(self):
125
+ self.compute_btn.setDisabled(True)
126
+ self.log.appendPlainText(
127
+ QDateTime.currentDateTime().toString(
128
+ '>>> yyyy/MM/dd hh:mm:ss') + ' TARDIS compute started \n'
129
+ )
130
+
131
+ start = time.perf_counter_ns()
132
+
133
+ def work_func(**kwargs):
134
+ try:
135
+ return tardis_data(
136
+ self.frames, self.locX, self.locY, self.locZ,
137
+ dts=self.get_dts(),
138
+ bins=self.bins.value(), range=self.maxDist.value())
139
+ except Exception:
140
+ traceback.print_exc()
141
+ return None
142
+
143
+ def done(results):
144
+ self.compute_btn.setDisabled(False)
145
+ if results is not None:
146
+ self.distances, self.edges = results
147
+ stop = time.perf_counter_ns()
148
+ calc = stop - start
149
+ message = f'Computed in {calc/1e9:.5f}s.\n'
150
+ print(message)
151
+ self.log.appendPlainText(' ' + message)
152
+ self.plot_hist()
153
+ else:
154
+ self.log.appendPlainText(' Something went wrong.')
155
+
156
+
157
+ worker = QThreadWorker(
158
+ work_func)
159
+ worker.signals.result.connect(done)
160
+ self.startWorker.emit(worker)
161
+
162
+ def get_dts(self):
163
+ try:
164
+ ldict = {}
165
+ exec(
166
+ 'dts = ' + self.dt.currentText(),
167
+ globals(), ldict)
168
+ return ldict['dts']
169
+ except Exception:
170
+ return []
171
+
172
+ def plot_hist(self):
173
+ # Clear plot widget
174
+ if self.clear_plot.isChecked():
175
+ self.histogram.clear()
176
+
177
+ greenP = pg.mkPen(color='g')
178
+ redP = pg.mkPen(color='r')
179
+ yellowP = pg.mkPen(color='y')
180
+ whiteP = pg.mkPen(color='w')
181
+
182
+ dts=self.get_dts()
183
+ # Update histogram
184
+ for idx in range(self.distances.shape[0]):
185
+ self.histogram.plot(
186
+ self.edges, self.distances[idx], stepMode='center',
187
+ fillLevel=0, fillOutline=True,
188
+ pen=pg.mkPen(
189
+ color=tuple(np.random.choice(range(127, 255),size=3))),
190
+ name=f'Δt = {dts[idx]:d}')
191
+ # TBA: Update fitting curve
192
+ # self._fit_ref = self.histogram.plot(
193
+ # _bins, np.zeros_like(_bins), pen=greenP, name='Fit')
194
+
195
+
196
+ def get_bincenters(edges: np.ndarray):
197
+ '''Bin centers of histogram bin edges.
198
+
199
+ Parameters
200
+ ----------
201
+ edges : np.ndarray
202
+ array of histogram bin edges.
203
+
204
+ Returns
205
+ -------
206
+ np.ndarray
207
+ array of bin centers of histogram bin edges.
208
+ '''
209
+ return np.array([(edges[i]+edges[i+1])/2. for i in range(len(edges)-1)])