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.
- microEye/__init__.py +47 -0
- microEye/_version.py +2 -0
- microEye/analysis/__init__.py +1 -0
- microEye/analysis/checklist_dialog.py +143 -0
- microEye/analysis/cmosMaps.py +228 -0
- microEye/analysis/filters/__init__.py +9 -0
- microEye/analysis/filters/base.py +21 -0
- microEye/analysis/filters/spatial.py +338 -0
- microEye/analysis/filters/temporal.py +76 -0
- microEye/analysis/fitting/__init__.py +0 -0
- microEye/analysis/fitting/fit.py +680 -0
- microEye/analysis/fitting/nena.py +375 -0
- microEye/analysis/fitting/phasor_fit.py +90 -0
- microEye/analysis/fitting/processing.py +317 -0
- microEye/analysis/fitting/psf/__init__.py +6 -0
- microEye/analysis/fitting/psf/extract.py +1129 -0
- microEye/analysis/fitting/psf/rubost_mean.py +150 -0
- microEye/analysis/fitting/psf/spline.py +167 -0
- microEye/analysis/fitting/psf/stats/__init__.py +12 -0
- microEye/analysis/fitting/psf/stats/core.py +295 -0
- microEye/analysis/fitting/psf/stats/curve_fit.py +708 -0
- microEye/analysis/fitting/psf/stats/io.py +104 -0
- microEye/analysis/fitting/psf/stats/slope_fit.py +171 -0
- microEye/analysis/fitting/psf/temp.py +147 -0
- microEye/analysis/fitting/psf/test.py +47 -0
- microEye/analysis/fitting/pyfit3Dcspline/CPU/CPUfunctions.py +657 -0
- microEye/analysis/fitting/pyfit3Dcspline/CPU/CPUmleFit_LM.py +1336 -0
- microEye/analysis/fitting/pyfit3Dcspline/CPU/CPUsplineLib.py +270 -0
- microEye/analysis/fitting/pyfit3Dcspline/CPU/__init__.py +1 -0
- microEye/analysis/fitting/pyfit3Dcspline/GPU/GPUfunctions.py +609 -0
- microEye/analysis/fitting/pyfit3Dcspline/GPU/GPUmleFit_LM_EMCCD.py +1396 -0
- microEye/analysis/fitting/pyfit3Dcspline/GPU/GPUmleFit_LM_sCMOS.py +1426 -0
- microEye/analysis/fitting/pyfit3Dcspline/GPU/GPUsplineLib.py +231 -0
- microEye/analysis/fitting/pyfit3Dcspline/GPU/__init__.py +2 -0
- microEye/analysis/fitting/pyfit3Dcspline/__init__.py +10 -0
- microEye/analysis/fitting/pyfit3Dcspline/constants.py +27 -0
- microEye/analysis/fitting/pyfit3Dcspline/mainfunctions.py +903 -0
- microEye/analysis/fitting/results.py +917 -0
- microEye/analysis/fitting/results_stats.py +251 -0
- microEye/analysis/fitting/tardis.py +209 -0
- microEye/analysis/multi_viewer.py +568 -0
- microEye/analysis/processing/__init__.py +1 -0
- microEye/analysis/processing/frc.py +120 -0
- microEye/analysis/rendering/__init__.py +3 -0
- microEye/analysis/rendering/base.py +452 -0
- microEye/analysis/rendering/cloud.py +215 -0
- microEye/analysis/rendering/core.py +47 -0
- microEye/analysis/rendering/volumetric.py +198 -0
- microEye/analysis/tools/__init__.py +0 -0
- microEye/analysis/tools/kymograms.py +1079 -0
- microEye/analysis/tools/roi_selectors.py +556 -0
- microEye/analysis/utils/__init__.py +3 -0
- microEye/analysis/utils/coordinates.py +18 -0
- microEye/analysis/utils/images.py +68 -0
- microEye/analysis/utils/windows.py +19 -0
- microEye/analysis/viewer/__init__.py +3 -0
- microEye/analysis/viewer/image_options_widget.py +615 -0
- microEye/analysis/viewer/images.py +1225 -0
- microEye/analysis/viewer/layers_widget.py +415 -0
- microEye/analysis/viewer/localizations.py +1304 -0
- microEye/analysis/viewer/psf.py +1003 -0
- microEye/analysis/viewer/volume.py +456 -0
- microEye/hardware/__init__.py +3 -0
- microEye/hardware/cams/__init__.py +29 -0
- microEye/hardware/cams/camera_calibration.py +99 -0
- microEye/hardware/cams/camera_list.py +528 -0
- microEye/hardware/cams/camera_options.py +694 -0
- microEye/hardware/cams/camera_panel.py +941 -0
- microEye/hardware/cams/dummy/__init__.py +1 -0
- microEye/hardware/cams/dummy/dummy_panel.py +759 -0
- microEye/hardware/cams/jobs.py +497 -0
- microEye/hardware/cams/line_profiler.py +99 -0
- microEye/hardware/cams/linescan/IR_Cam.py +474 -0
- microEye/hardware/cams/linescan/__init__.py +1 -0
- microEye/hardware/cams/micam.py +524 -0
- microEye/hardware/cams/pco/__init__.py +58 -0
- microEye/hardware/cams/pco/enums.py +382 -0
- microEye/hardware/cams/pco/pco_cam.py +761 -0
- microEye/hardware/cams/pco/pco_panel.py +477 -0
- microEye/hardware/cams/shortcuts.py +312 -0
- microEye/hardware/cams/thorlabs/__init__.py +1 -0
- microEye/hardware/cams/thorlabs/thorlabs.py +1508 -0
- microEye/hardware/cams/thorlabs/thorlabs_panel.py +850 -0
- microEye/hardware/cams/ueye/__init__.py +1 -0
- microEye/hardware/cams/ueye/ueye_camera.py +1023 -0
- microEye/hardware/cams/ueye/ueye_panel.py +861 -0
- microEye/hardware/cams/vimba/__init__.py +1 -0
- microEye/hardware/cams/vimba/vimba_cam.py +1000 -0
- microEye/hardware/cams/vimba/vimba_panel.py +813 -0
- microEye/hardware/device.py +60 -0
- microEye/hardware/lasers/__init__.py +13 -0
- microEye/hardware/lasers/io_matchbox.py +791 -0
- microEye/hardware/lasers/io_params.py +85 -0
- microEye/hardware/lasers/io_single_laser.py +742 -0
- microEye/hardware/lasers/laser_relay.py +594 -0
- microEye/hardware/mieye/__init__.py +1 -0
- microEye/hardware/mieye/acquisition_manager.py +467 -0
- microEye/hardware/mieye/devices_manager.py +533 -0
- microEye/hardware/mieye/miEye.py +659 -0
- microEye/hardware/misc/__init__.py +0 -0
- microEye/hardware/misc/acquisition_view.py +71 -0
- microEye/hardware/misc/reglo.py +761 -0
- microEye/hardware/misc/temp.py +188 -0
- microEye/hardware/port_config.py +59 -0
- microEye/hardware/protocols/__init__.py +2 -0
- microEye/hardware/protocols/actions.py +402 -0
- microEye/hardware/protocols/actions_items.py +703 -0
- microEye/hardware/protocols/designer.py +244 -0
- microEye/hardware/protocols/scene_manager.py +191 -0
- microEye/hardware/protocols/serialization.py +97 -0
- microEye/hardware/pycromanager/__init__.py +16 -0
- microEye/hardware/pycromanager/core.py +1433 -0
- microEye/hardware/pycromanager/devices.py +461 -0
- microEye/hardware/pycromanager/enums.py +107 -0
- microEye/hardware/pycromanager/headless.py +153 -0
- microEye/hardware/pycromanager/utils.py +34 -0
- microEye/hardware/pycromanager/widgets/__init__.py +5 -0
- microEye/hardware/pycromanager/widgets/bridges.py +407 -0
- microEye/hardware/pycromanager/widgets/headless_manager.py +258 -0
- microEye/hardware/pycromanager/widgets/headless_options.py +224 -0
- microEye/hardware/pycromanager/widgets/pycro_panel.py +455 -0
- microEye/hardware/stages/__init__.py +18 -0
- microEye/hardware/stages/elliptec/__init__.py +5 -0
- microEye/hardware/stages/elliptec/baseDevice.py +314 -0
- microEye/hardware/stages/elliptec/device.py +384 -0
- microEye/hardware/stages/elliptec/deviceID.py +212 -0
- microEye/hardware/stages/elliptec/devicePort.py +379 -0
- microEye/hardware/stages/elliptec/deviceStatus.py +65 -0
- microEye/hardware/stages/elliptec/devicesView.py +706 -0
- microEye/hardware/stages/elliptec/ellDevices.py +134 -0
- microEye/hardware/stages/elliptec/messageUpdater.py +34 -0
- microEye/hardware/stages/elliptec/motorInfo.py +153 -0
- microEye/hardware/stages/elliptec/stage.py +62 -0
- microEye/hardware/stages/elliptec/test.py +139 -0
- microEye/hardware/stages/kinesis/__init__.py +1 -0
- microEye/hardware/stages/kinesis/kdc101/__init__.py +1 -0
- microEye/hardware/stages/kinesis/kdc101/enums.py +1002 -0
- microEye/hardware/stages/kinesis/kdc101/factory.py +171 -0
- microEye/hardware/stages/kinesis/kdc101/kdc101.py +718 -0
- microEye/hardware/stages/kinesis/kinesis.py +776 -0
- microEye/hardware/stages/piezo_concept.py +607 -0
- microEye/hardware/stages/stabilizer.py +785 -0
- microEye/hardware/stages/stage.py +89 -0
- microEye/hardware/widgets/__init__.py +10 -0
- microEye/hardware/widgets/controller.py +246 -0
- microEye/hardware/widgets/devices.py +133 -0
- microEye/hardware/widgets/focusWidget.py +264 -0
- microEye/hardware/widgets/qlist_slider.py +113 -0
- microEye/hardware/widgets/scan_acquisition.py +424 -0
- microEye/icons/1024.png +0 -0
- microEye/icons/128.png +0 -0
- microEye/icons/16.png +0 -0
- microEye/icons/24.png +0 -0
- microEye/icons/256.png +0 -0
- microEye/icons/32.png +0 -0
- microEye/icons/48.png +0 -0
- microEye/icons/512.png +0 -0
- microEye/icons/64.png +0 -0
- microEye/icons/__init__.py +0 -0
- microEye/icons/close.svg +88 -0
- microEye/icons/mieye.png +0 -0
- microEye/icons/min.svg +83 -0
- microEye/icons/viewer.png +0 -0
- microEye/launcher.py +42 -0
- microEye/qt.py +181 -0
- microEye/utils/__init__.py +2 -0
- microEye/utils/enum_encoder.py +10 -0
- microEye/utils/expandable_groupbox.py +93 -0
- microEye/utils/gui_helper.py +457 -0
- microEye/utils/hid/__init__.py +8 -0
- microEye/utils/hid/controller.py +153 -0
- microEye/utils/hid/device.py +63 -0
- microEye/utils/hid/enums.py +140 -0
- microEye/utils/hid/utils.py +58 -0
- microEye/utils/labelled_slider.py +134 -0
- microEye/utils/metadata.py +599 -0
- microEye/utils/metadata_tree.py +718 -0
- microEye/utils/micro_launcher.py +245 -0
- microEye/utils/parameter_tree.py +325 -0
- microEye/utils/pyscripting.py +444 -0
- microEye/utils/retry_exec.py +35 -0
- microEye/utils/start_gui.py +112 -0
- microEye/utils/thread_worker.py +115 -0
- microEye/utils/uImage.py +1512 -0
- microeye-2.3.2.dist-info/METADATA +401 -0
- microeye-2.3.2.dist-info/RECORD +190 -0
- microeye-2.3.2.dist-info/WHEEL +5 -0
- microeye-2.3.2.dist-info/entry_points.txt +2 -0
- microeye-2.3.2.dist-info/licenses/LICENSE +674 -0
- 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)])
|