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,1304 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import re
|
|
3
|
+
import traceback
|
|
4
|
+
|
|
5
|
+
import cv2
|
|
6
|
+
import numpy as np
|
|
7
|
+
import pandas as pd
|
|
8
|
+
import pyqtgraph as pg
|
|
9
|
+
import tifffile as tf
|
|
10
|
+
|
|
11
|
+
from microEye.analysis.checklist_dialog import ChecklistDialog
|
|
12
|
+
from microEye.analysis.fitting.nena import NeNA_Widget
|
|
13
|
+
from microEye.analysis.fitting.processing import plot_animation_stats, plot_drift
|
|
14
|
+
from microEye.analysis.fitting.psf import stats
|
|
15
|
+
from microEye.analysis.fitting.results import (
|
|
16
|
+
UNIQUE_COLUMNS,
|
|
17
|
+
DataColumns,
|
|
18
|
+
FittingResults,
|
|
19
|
+
)
|
|
20
|
+
from microEye.analysis.fitting.results_stats import resultsStatsWidget
|
|
21
|
+
from microEye.analysis.fitting.tardis import TARDIS_Widget
|
|
22
|
+
from microEye.analysis.processing import FRC_resolution_binomial, plot_frc
|
|
23
|
+
from microEye.analysis.rendering import *
|
|
24
|
+
from microEye.analysis.viewer.layers_widget import ImageItemsWidget
|
|
25
|
+
from microEye.analysis.viewer.volume import PointCloudViewer, VolumeViewerWindow
|
|
26
|
+
from microEye.qt import Qt, QtCore, QtGui, QtWidgets, getOpenFileName, getSaveFileName
|
|
27
|
+
from microEye.utils.gui_helper import *
|
|
28
|
+
from microEye.utils.thread_worker import QThreadWorker
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class LocalizationsView(QtWidgets.QWidget):
|
|
32
|
+
'''
|
|
33
|
+
A class for viewing and interacting with SMLM localizations in a PyQt5 application.
|
|
34
|
+
'''
|
|
35
|
+
|
|
36
|
+
__INSTANCES = 0
|
|
37
|
+
|
|
38
|
+
def __init__(self, fittingResults: FittingResults = None):
|
|
39
|
+
'''Initialize the StackView.
|
|
40
|
+
|
|
41
|
+
Parameters
|
|
42
|
+
----------
|
|
43
|
+
path : str
|
|
44
|
+
The path to the image stack.
|
|
45
|
+
fittingResults : FittingResults, optional
|
|
46
|
+
Fitting results, by default None.
|
|
47
|
+
'''
|
|
48
|
+
super().__init__()
|
|
49
|
+
|
|
50
|
+
LocalizationsView.__INSTANCES += 1
|
|
51
|
+
self.setWindowTitle(f'Loc Viewer {LocalizationsView.__INSTANCES:04d}')
|
|
52
|
+
|
|
53
|
+
self._threadpool = QtCore.QThreadPool.globalInstance()
|
|
54
|
+
# Initialize variables
|
|
55
|
+
self.fittingResults: list[FittingResults] = []
|
|
56
|
+
|
|
57
|
+
self.main_layout = QtWidgets.QHBoxLayout()
|
|
58
|
+
self.setLayout(self.main_layout)
|
|
59
|
+
|
|
60
|
+
# Graphics layout
|
|
61
|
+
self.init_graphics()
|
|
62
|
+
|
|
63
|
+
# Tab Widget
|
|
64
|
+
self.tab_widget = QtWidgets.QTabWidget()
|
|
65
|
+
|
|
66
|
+
self.main_layout.addWidget(self.tab_widget, 1)
|
|
67
|
+
|
|
68
|
+
# Localization / Render tab layout
|
|
69
|
+
self.setup_localization_tab()
|
|
70
|
+
|
|
71
|
+
# Results stats tab layout
|
|
72
|
+
self.setup_data_filters_tab()
|
|
73
|
+
|
|
74
|
+
# Layers tab
|
|
75
|
+
self.setup_layers_tab()
|
|
76
|
+
|
|
77
|
+
if fittingResults:
|
|
78
|
+
self.add_data_layer(fittingResults)
|
|
79
|
+
|
|
80
|
+
@property
|
|
81
|
+
def currentResults(self) -> FittingResults:
|
|
82
|
+
'''Get the current fitting results.'''
|
|
83
|
+
if self.image_layers.currentIndex < 0:
|
|
84
|
+
return None
|
|
85
|
+
|
|
86
|
+
return self.fittingResults[self.image_layers.currentIndex]
|
|
87
|
+
|
|
88
|
+
@currentResults.setter
|
|
89
|
+
def currentResults(self, value: FittingResults):
|
|
90
|
+
'''Set the current fitting results.'''
|
|
91
|
+
if self.image_layers.currentIndex < 0:
|
|
92
|
+
return
|
|
93
|
+
|
|
94
|
+
value.colormap = self.currentResults.colormap
|
|
95
|
+
value.intensity_range = self.currentResults.intensity_range
|
|
96
|
+
|
|
97
|
+
self.fittingResults[self.image_layers.currentIndex] = value
|
|
98
|
+
|
|
99
|
+
def setImage(self, image: np.ndarray, autoLevels: bool = True, **kwargs):
|
|
100
|
+
'''Set the image data.'''
|
|
101
|
+
if image is None or self.image_layers.currentLayer is None:
|
|
102
|
+
return
|
|
103
|
+
|
|
104
|
+
index = kwargs.get('index', self.image_layers.currentIndex)
|
|
105
|
+
|
|
106
|
+
colorMap = self.fittingResults[index].colormap
|
|
107
|
+
if colorMap is not None:
|
|
108
|
+
kwargs['colorMap'] = colorMap
|
|
109
|
+
|
|
110
|
+
self.image_layers.getImageItemAt(index).setImage(
|
|
111
|
+
image, autoLevels=autoLevels, **kwargs
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
def init_graphics(self):
|
|
115
|
+
'''Initialize the graphics layout for the main window.'''
|
|
116
|
+
# A plot area (ViewBox + axes) for displaying the image
|
|
117
|
+
self.image_widget = pg.GraphicsLayoutWidget()
|
|
118
|
+
self.image_widget.setMinimumWidth(600)
|
|
119
|
+
|
|
120
|
+
# Create the ViewBox
|
|
121
|
+
self.view_box: pg.ViewBox = self.image_widget.addViewBox(row=0, col=0)
|
|
122
|
+
|
|
123
|
+
self.view_box.setAspectLocked(True)
|
|
124
|
+
self.view_box.setAutoVisible(True)
|
|
125
|
+
self.view_box.enableAutoRange()
|
|
126
|
+
self.view_box.invertY(True)
|
|
127
|
+
|
|
128
|
+
self.empty_image = np.zeros((128, 128), dtype=np.uint8)
|
|
129
|
+
self.empty_alpha = np.zeros((128, 128, 4), dtype=np.uint8)
|
|
130
|
+
self.empty_image[0, 0] = 255
|
|
131
|
+
|
|
132
|
+
# histogram item
|
|
133
|
+
self.histogram_item = pg.HistogramLUTItem(
|
|
134
|
+
gradientPosition='bottom', orientation='horizontal'
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
self.image_widget.addItem(self.histogram_item, row=1, col=0)
|
|
138
|
+
|
|
139
|
+
# Add ROI
|
|
140
|
+
self.roi = pg.RectROI(
|
|
141
|
+
[-8, 14], [6, 5], scaleSnap=True, translateSnap=True, movable=False
|
|
142
|
+
)
|
|
143
|
+
self.roi.addTranslateHandle([0, 0], [0.5, 0.5])
|
|
144
|
+
self.view_box.addItem(self.roi)
|
|
145
|
+
self.roi.setZValue(1000)
|
|
146
|
+
self.roi.setVisible(False)
|
|
147
|
+
|
|
148
|
+
# Add LinePlotItem for trajectories
|
|
149
|
+
self.trajectory_item = pg.PlotDataItem(pen=pg.mkPen(color='r', width=2))
|
|
150
|
+
self.view_box.addItem(self.trajectory_item)
|
|
151
|
+
self.trajectory_item.setZValue(999) # Ensure lines are on top of image
|
|
152
|
+
|
|
153
|
+
# Add the two sub-main layouts
|
|
154
|
+
self.main_layout.addWidget(self.image_widget, 4)
|
|
155
|
+
|
|
156
|
+
def add_data_layer(self, data: FittingResults):
|
|
157
|
+
if data is None:
|
|
158
|
+
return
|
|
159
|
+
|
|
160
|
+
self.fittingResults.append(data)
|
|
161
|
+
|
|
162
|
+
self.updateStatsLabel()
|
|
163
|
+
|
|
164
|
+
count = self.image_layers.count()
|
|
165
|
+
# Get the last part of the path
|
|
166
|
+
path = (
|
|
167
|
+
data.path
|
|
168
|
+
if len(data.path.split('/')) < 3
|
|
169
|
+
else '/'.join(data.path.split('/')[-2:])
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
image_item = self.add_image_item(
|
|
173
|
+
self.empty_image if count < 1 else self.empty_alpha,
|
|
174
|
+
compMode='SourceOver' if count < 1 else 'Plus',
|
|
175
|
+
name=path,
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
self.render_loc()
|
|
179
|
+
|
|
180
|
+
def add_image_item(
|
|
181
|
+
self,
|
|
182
|
+
image: np.ndarray,
|
|
183
|
+
opacity: float = 1.0,
|
|
184
|
+
compMode='SourceOver',
|
|
185
|
+
name='Layer',
|
|
186
|
+
):
|
|
187
|
+
"""Add an image item to the view.
|
|
188
|
+
|
|
189
|
+
Parameters
|
|
190
|
+
----------
|
|
191
|
+
image : np.ndarray
|
|
192
|
+
The image data.
|
|
193
|
+
opacity : float, optional
|
|
194
|
+
The opacity of the image, by default 1.0.
|
|
195
|
+
compMode : str, optional
|
|
196
|
+
The composition mode, by default 'SourceOver'.
|
|
197
|
+
name : str, optional
|
|
198
|
+
The name of the image item layer, by default 'Layer'.
|
|
199
|
+
|
|
200
|
+
Returns
|
|
201
|
+
-------
|
|
202
|
+
pg.ImageItem
|
|
203
|
+
The added image item.
|
|
204
|
+
"""
|
|
205
|
+
# Create the ImageItem and set its view to self.view_box
|
|
206
|
+
image_item = self.image_layers.add_layer(image, opacity, compMode, name=name)
|
|
207
|
+
|
|
208
|
+
# Add the ImageItem to the ViewBox
|
|
209
|
+
self.view_box.addItem(image_item)
|
|
210
|
+
|
|
211
|
+
return image_item
|
|
212
|
+
|
|
213
|
+
def setup_layers_tab(self):
|
|
214
|
+
'''Set up the Layers tab.'''
|
|
215
|
+
# Layers tab
|
|
216
|
+
self.image_layers = ImageItemsWidget()
|
|
217
|
+
|
|
218
|
+
self.image_layers.layerChanged.connect(self.update_layer)
|
|
219
|
+
self.image_layers.layerRemoved.connect(
|
|
220
|
+
lambda index: self.fittingResults.pop(index)
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
self.tab_widget.addTab(self.image_layers, 'Layers')
|
|
224
|
+
|
|
225
|
+
def update_layer(self, current: int, previous: int, imageItem: pg.ImageItem):
|
|
226
|
+
'''Update the layer settings.
|
|
227
|
+
|
|
228
|
+
Parameters
|
|
229
|
+
----------
|
|
230
|
+
current : int
|
|
231
|
+
The current layer index.
|
|
232
|
+
previous : int
|
|
233
|
+
The previous layer index.
|
|
234
|
+
imageItem : pg.ImageItem
|
|
235
|
+
The image item.
|
|
236
|
+
'''
|
|
237
|
+
if current == -1 or imageItem is None:
|
|
238
|
+
return
|
|
239
|
+
|
|
240
|
+
if previous >= 0 and previous < len(self.fittingResults):
|
|
241
|
+
self.fittingResults[
|
|
242
|
+
previous
|
|
243
|
+
].colormap = self.histogram_item.gradient.colorMap()
|
|
244
|
+
self.fittingResults[
|
|
245
|
+
previous
|
|
246
|
+
].intensity_range = self.histogram_item.getLevels()
|
|
247
|
+
|
|
248
|
+
self.histogram_item.setImageItem(imageItem)
|
|
249
|
+
|
|
250
|
+
if self.currentResults.colormap is not None:
|
|
251
|
+
self.histogram_item.gradient.setColorMap(self.currentResults.colormap)
|
|
252
|
+
self.histogram_item.setLevels(*self.currentResults.intensity_range)
|
|
253
|
+
|
|
254
|
+
self.results_plot.clear()
|
|
255
|
+
|
|
256
|
+
def update_position(self):
|
|
257
|
+
'''Update the position label.'''
|
|
258
|
+
if self.projection_cbox.currentData() in [3, 4, 5]:
|
|
259
|
+
self.position_label.setVisible(True)
|
|
260
|
+
self.position.setVisible(True)
|
|
261
|
+
|
|
262
|
+
projection = self.projection_cbox.currentData() - 3
|
|
263
|
+
if projection == 0:
|
|
264
|
+
column = DataColumns.Z
|
|
265
|
+
elif projection == 1:
|
|
266
|
+
column = DataColumns.Y
|
|
267
|
+
else:
|
|
268
|
+
column = DataColumns.X
|
|
269
|
+
|
|
270
|
+
self.position_label.setText('Z Position [nm]:')
|
|
271
|
+
self.position.setRange(
|
|
272
|
+
self.currentResults.data[column].min(),
|
|
273
|
+
self.currentResults.data[column].max(),
|
|
274
|
+
)
|
|
275
|
+
self.position.setValue(self.position.minimum())
|
|
276
|
+
else:
|
|
277
|
+
self.position_label.setVisible(False)
|
|
278
|
+
self.position.setVisible(False)
|
|
279
|
+
|
|
280
|
+
def render_loc(self):
|
|
281
|
+
'''Update the rendered super-res image.
|
|
282
|
+
|
|
283
|
+
Returns
|
|
284
|
+
-------
|
|
285
|
+
ndarray or None
|
|
286
|
+
Rendered super-res image or None.
|
|
287
|
+
'''
|
|
288
|
+
for idx, localizations in enumerate(self.fittingResults):
|
|
289
|
+
if localizations is None:
|
|
290
|
+
continue
|
|
291
|
+
|
|
292
|
+
if len(localizations) <= 0:
|
|
293
|
+
continue
|
|
294
|
+
|
|
295
|
+
render_idx = self.render_cbox.currentData()
|
|
296
|
+
projection = self.projection_cbox.currentData()
|
|
297
|
+
|
|
298
|
+
renderClass = BaseRenderer(
|
|
299
|
+
self.xy_binsize.value(),
|
|
300
|
+
self.z_binsize.value(),
|
|
301
|
+
RenderModes(render_idx),
|
|
302
|
+
)
|
|
303
|
+
if 0 <= projection < 3:
|
|
304
|
+
img = renderClass.render(
|
|
305
|
+
Projection(projection),
|
|
306
|
+
localizations.data[DataColumns.X],
|
|
307
|
+
localizations.data[DataColumns.Y],
|
|
308
|
+
localizations.data[DataColumns.Z],
|
|
309
|
+
localizations.data[DataColumns.INTENSITY],
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
self.setImage(
|
|
313
|
+
img, index=idx, autoLevels=self.auto_level.isChecked()
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
if projection == 0:
|
|
317
|
+
self.view_box.setAspectLocked(True, 1)
|
|
318
|
+
else:
|
|
319
|
+
self.view_box.setAspectLocked(
|
|
320
|
+
True, self.xy_binsize.value() / self.z_binsize.value()
|
|
321
|
+
)
|
|
322
|
+
elif 3 <= projection < 6:
|
|
323
|
+
projection -= 3
|
|
324
|
+
|
|
325
|
+
if projection == 0:
|
|
326
|
+
self.view_box.setAspectLocked(True, 1)
|
|
327
|
+
width = self.z_binsize.value()
|
|
328
|
+
else:
|
|
329
|
+
self.view_box.setAspectLocked(
|
|
330
|
+
True, self.xy_binsize.value() / self.z_binsize.value()
|
|
331
|
+
)
|
|
332
|
+
width = self.xy_binsize.value()
|
|
333
|
+
|
|
334
|
+
img = renderClass.render_slice(
|
|
335
|
+
Projection(projection),
|
|
336
|
+
localizations.data[DataColumns.X],
|
|
337
|
+
localizations.data[DataColumns.Y],
|
|
338
|
+
localizations.data[DataColumns.Z],
|
|
339
|
+
localizations.data[DataColumns.INTENSITY],
|
|
340
|
+
self.position.value(),
|
|
341
|
+
width,
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
self.setImage(
|
|
345
|
+
img, index=idx, autoLevels=self.auto_level.isChecked()
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
def extract_z(self):
|
|
349
|
+
'''Extract Z values from the fitting results.'''
|
|
350
|
+
if self.currentResults is None:
|
|
351
|
+
return None
|
|
352
|
+
if len(self.currentResults) <= 0:
|
|
353
|
+
return
|
|
354
|
+
|
|
355
|
+
if self.cal_curve_cbox.currentData() == stats.CurveFitMethod.LINEAR:
|
|
356
|
+
self.currentResults.data[DataColumns.Z] = (
|
|
357
|
+
(
|
|
358
|
+
self.currentResults.data[DataColumns.X_SIGMA]
|
|
359
|
+
/ self.currentResults.data[DataColumns.Y_SIGMA]
|
|
360
|
+
)
|
|
361
|
+
- self.z_cal_intercept.value()
|
|
362
|
+
) / self.z_cal_slope.value()
|
|
363
|
+
self.results_plot.setData(self.currentResults.dataFrame())
|
|
364
|
+
elif self.cal_curve_cbox.currentData() == stats.CurveFitMethod.CSPLINE:
|
|
365
|
+
if self.z_cal_curve:
|
|
366
|
+
self.currentResults.data[DataColumns.Z] = (
|
|
367
|
+
stats.CurveAnalyzer.get_z_from_y_array_optimized(
|
|
368
|
+
self.currentResults.data[DataColumns.X_SIGMA]
|
|
369
|
+
/ self.currentResults.data[DataColumns.Y_SIGMA],
|
|
370
|
+
self.z_cal_curve,
|
|
371
|
+
region=[-450, 450],
|
|
372
|
+
)
|
|
373
|
+
)
|
|
374
|
+
self.results_plot.setData(self.currentResults.dataFrame())
|
|
375
|
+
|
|
376
|
+
def _load_calibration(self):
|
|
377
|
+
'''Load a calibration file.'''
|
|
378
|
+
self.z_cal_curve, path = stats.import_fit_curve(
|
|
379
|
+
self,
|
|
380
|
+
os.path.dirname(self.currentResults.path),
|
|
381
|
+
)
|
|
382
|
+
if self.z_cal_curve is not None:
|
|
383
|
+
self.cal_file_path.setText(path)
|
|
384
|
+
if self.z_cal_curve.method == stats.CurveFitMethod.LINEAR:
|
|
385
|
+
self.cal_file_path.setText(path)
|
|
386
|
+
self.cal_curve_cbox.setCurrentIndex(0)
|
|
387
|
+
self.z_cal_slope.setValue(self.z_cal_curve.slope)
|
|
388
|
+
self.z_cal_intercept.setValue(self.z_cal_curve.intercept)
|
|
389
|
+
elif self.z_cal_curve.method == stats.CurveFitMethod.CSPLINE:
|
|
390
|
+
self.cal_file_path.setText(path)
|
|
391
|
+
self.cal_curve_cbox.setCurrentIndex(1)
|
|
392
|
+
|
|
393
|
+
def _plot_calibration(self):
|
|
394
|
+
'''Plot the calibration curve.'''
|
|
395
|
+
if self.z_cal_curve is not None:
|
|
396
|
+
plt = pg.plot()
|
|
397
|
+
plt.showGrid(x=True, y=True)
|
|
398
|
+
legend = plt.addLegend()
|
|
399
|
+
legend.anchor(itemPos=(1, 0), parentPos=(1, 0))
|
|
400
|
+
plt.setWindowTitle('Z Calibration')
|
|
401
|
+
plt.setLabel('left', 'Sigma Ratio (X/Y)')
|
|
402
|
+
plt.setLabel('bottom', 'Z [nm]')
|
|
403
|
+
|
|
404
|
+
# Zero plane line
|
|
405
|
+
plt.plotItem.addLine(x=0, pen='w')
|
|
406
|
+
|
|
407
|
+
if self.z_cal_curve.method == stats.CurveFitMethod.LINEAR:
|
|
408
|
+
pen = pg.mkPen(color=(0, 0, 255, 150), width=2)
|
|
409
|
+
plt.plot(
|
|
410
|
+
self.z_cal_curve.data['z'],
|
|
411
|
+
self.z_cal_curve.data['ratio'],
|
|
412
|
+
pen=pen,
|
|
413
|
+
name='data',
|
|
414
|
+
)
|
|
415
|
+
pen = pg.mkPen(color=(0, 255, 0, 150), width=2)
|
|
416
|
+
plt.plot(
|
|
417
|
+
self.z_cal_curve.data['z'],
|
|
418
|
+
np.array(self.z_cal_curve.data['z']) * self.z_cal_curve.slope
|
|
419
|
+
+ self.z_cal_curve.intercept,
|
|
420
|
+
pen=pen,
|
|
421
|
+
name='fit',
|
|
422
|
+
)
|
|
423
|
+
|
|
424
|
+
# Confidence interval fill
|
|
425
|
+
fill = pg.FillBetweenItem(
|
|
426
|
+
pg.PlotCurveItem(
|
|
427
|
+
self.z_cal_curve.data['z'],
|
|
428
|
+
self.z_cal_curve.data['lower_bounds'],
|
|
429
|
+
),
|
|
430
|
+
pg.PlotCurveItem(
|
|
431
|
+
self.z_cal_curve.data['z'],
|
|
432
|
+
self.z_cal_curve.data['upper_bounds'],
|
|
433
|
+
),
|
|
434
|
+
brush=pg.mkBrush((0, 255, 0, 50)), # More transparent for fill
|
|
435
|
+
)
|
|
436
|
+
plt.addItem(fill)
|
|
437
|
+
|
|
438
|
+
plt.show()
|
|
439
|
+
else:
|
|
440
|
+
pen = pg.mkPen(color=(0, 0, 255, 150), width=2)
|
|
441
|
+
plt.plot(
|
|
442
|
+
self.z_cal_curve.parameters['x_data'],
|
|
443
|
+
self.z_cal_curve.parameters['y_data'],
|
|
444
|
+
pen=pen,
|
|
445
|
+
name='data',
|
|
446
|
+
)
|
|
447
|
+
pen = pg.mkPen(color=(0, 255, 0, 150), width=2)
|
|
448
|
+
plt.plot(
|
|
449
|
+
self.z_cal_curve.parameters['x_data'],
|
|
450
|
+
self.z_cal_curve.get_data(self.z_cal_curve.parameters['x_data']),
|
|
451
|
+
pen=pen,
|
|
452
|
+
name='fit',
|
|
453
|
+
)
|
|
454
|
+
|
|
455
|
+
plt.show()
|
|
456
|
+
|
|
457
|
+
def render_3D(self):
|
|
458
|
+
'''
|
|
459
|
+
Render a 3D view of the super-res image.
|
|
460
|
+
'''
|
|
461
|
+
if (
|
|
462
|
+
self.currentResults is None
|
|
463
|
+
or self.currentResults.data[DataColumns.Z] is None
|
|
464
|
+
):
|
|
465
|
+
return None
|
|
466
|
+
|
|
467
|
+
if len(self.currentResults) <= 0:
|
|
468
|
+
return
|
|
469
|
+
|
|
470
|
+
renderClass = PointCloudRenderer(
|
|
471
|
+
self.xy_binsize.value(), self.z_binsize.value()
|
|
472
|
+
)
|
|
473
|
+
# Render point cloud
|
|
474
|
+
points, intensities, metadata = renderClass.render(
|
|
475
|
+
self.currentResults.data[DataColumns.X],
|
|
476
|
+
self.currentResults.data[DataColumns.Y],
|
|
477
|
+
self.currentResults.data[DataColumns.Z],
|
|
478
|
+
self.currentResults.data[DataColumns.INTENSITY],
|
|
479
|
+
)
|
|
480
|
+
volume_viewer = PointCloudViewer(points, intensities, metadata)
|
|
481
|
+
|
|
482
|
+
def toAnimation(self):
|
|
483
|
+
'''
|
|
484
|
+
Render an animation of the super-res image.
|
|
485
|
+
'''
|
|
486
|
+
if self.currentResults is None:
|
|
487
|
+
return
|
|
488
|
+
|
|
489
|
+
if len(self.currentResults) <= 0:
|
|
490
|
+
return
|
|
491
|
+
|
|
492
|
+
filename = self.currentResults.path
|
|
493
|
+
|
|
494
|
+
if filename is None:
|
|
495
|
+
return
|
|
496
|
+
|
|
497
|
+
def work_func(**kwargs):
|
|
498
|
+
try:
|
|
499
|
+
return self.currentResults.toAnimation(
|
|
500
|
+
self.frame_bins.value(), self.xy_binsize.value(),
|
|
501
|
+
z_pixel_size=self.z_binsize.value(),
|
|
502
|
+
projection=Projection(self.projection_cbox.currentData()),
|
|
503
|
+
renderMode=RenderModes(self.render_cbox.currentData())
|
|
504
|
+
)
|
|
505
|
+
except Exception:
|
|
506
|
+
traceback.print_exc()
|
|
507
|
+
return None
|
|
508
|
+
|
|
509
|
+
def done(results):
|
|
510
|
+
self.animation_btn.setDisabled(False)
|
|
511
|
+
# results might be tuple of three nones
|
|
512
|
+
if results is not None and all([r is not None for r in results]):
|
|
513
|
+
stack, frame_bins, locs_per_bin = results
|
|
514
|
+
|
|
515
|
+
# save stack to tiff to the same directory as the fitting results
|
|
516
|
+
tf.imwrite(
|
|
517
|
+
re.sub(r'(\.h5|\.tsv)$', '_animation.tif', filename),
|
|
518
|
+
stack,
|
|
519
|
+
photometric='minisblack',
|
|
520
|
+
bigtiff=True,
|
|
521
|
+
ome=False,
|
|
522
|
+
)
|
|
523
|
+
|
|
524
|
+
plot_animation_stats(frame_bins, locs_per_bin)
|
|
525
|
+
|
|
526
|
+
self.worker = QThreadWorker(work_func)
|
|
527
|
+
self.worker.signals.result.connect(done)
|
|
528
|
+
# Execute
|
|
529
|
+
self.animation_btn.setDisabled(True)
|
|
530
|
+
self._threadpool.start(self.worker)
|
|
531
|
+
|
|
532
|
+
self.currentResults.toAnimation()
|
|
533
|
+
|
|
534
|
+
def render_tracks(self):
|
|
535
|
+
# self.trajectory_item.setData(
|
|
536
|
+
# x=np.linspace(0, 10, 100),
|
|
537
|
+
# y=np.sin(np.linspace(0, 10, 100))
|
|
538
|
+
# )
|
|
539
|
+
pass
|
|
540
|
+
|
|
541
|
+
def setup_localization_tab(self):
|
|
542
|
+
'''Set up the Localization tab.'''
|
|
543
|
+
# Render tab layout
|
|
544
|
+
self.localization_widget, self.localization_form = create_widget(
|
|
545
|
+
QtWidgets.QVBoxLayout
|
|
546
|
+
)
|
|
547
|
+
self.tab_widget.addTab(self.localization_widget, 'Localization')
|
|
548
|
+
|
|
549
|
+
# Render layout
|
|
550
|
+
|
|
551
|
+
self.render_cbox = QtWidgets.QComboBox()
|
|
552
|
+
self.render_cbox.addItem('2D Histogram (Intensity)', 0)
|
|
553
|
+
self.render_cbox.addItem('2D Histogram (Events)', 1)
|
|
554
|
+
self.render_cbox.addItem('2D Gaussian Histogram', 2)
|
|
555
|
+
|
|
556
|
+
self.projection_cbox = QtWidgets.QComboBox()
|
|
557
|
+
self.projection_cbox.addItem('XY', 0)
|
|
558
|
+
self.projection_cbox.addItem('XY (slice)', 3)
|
|
559
|
+
self.projection_cbox.addItem('XZ', 1)
|
|
560
|
+
self.projection_cbox.addItem('XZ (slice)', 4)
|
|
561
|
+
self.projection_cbox.addItem('YZ', 2)
|
|
562
|
+
self.projection_cbox.addItem('YZ (slice)', 5)
|
|
563
|
+
|
|
564
|
+
self.projection_cbox.currentIndexChanged.connect(lambda: self.update_position())
|
|
565
|
+
|
|
566
|
+
self.auto_level = QtWidgets.QCheckBox('Auto-stretch')
|
|
567
|
+
self.auto_level.setChecked(True)
|
|
568
|
+
|
|
569
|
+
self.frame_bins = create_spin_box(min_value=2, max_value=1000, initial_value=10)
|
|
570
|
+
self.xy_binsize = create_spin_box(min_value=0, max_value=250, initial_value=10)
|
|
571
|
+
self.z_binsize = create_spin_box(min_value=0, max_value=250, initial_value=50)
|
|
572
|
+
|
|
573
|
+
self.position_label = QtWidgets.QLabel('Position [nm]:')
|
|
574
|
+
self.position = create_double_spin_box(
|
|
575
|
+
min_value=0, max_value=200, single_step=10, decimals=6, initial_value=0
|
|
576
|
+
)
|
|
577
|
+
self.position_label.setVisible(False)
|
|
578
|
+
self.position.setVisible(False)
|
|
579
|
+
self.position.valueChanged.connect(lambda: self.render_loc())
|
|
580
|
+
|
|
581
|
+
self.refresh_btn = QtWidgets.QPushButton(
|
|
582
|
+
'Refresh 2D View', clicked=lambda: self.render_loc()
|
|
583
|
+
)
|
|
584
|
+
|
|
585
|
+
self.display_3d_btn = QtWidgets.QPushButton(
|
|
586
|
+
'3D View', clicked=lambda: self.render_3D()
|
|
587
|
+
)
|
|
588
|
+
|
|
589
|
+
self.animation_btn = QtWidgets.QPushButton(
|
|
590
|
+
'Animation', clicked=lambda: self.toAnimation()
|
|
591
|
+
)
|
|
592
|
+
|
|
593
|
+
# Render GroupBox
|
|
594
|
+
localization = QtWidgets.QGroupBox('Render')
|
|
595
|
+
flocalization = QtWidgets.QFormLayout()
|
|
596
|
+
localization.setLayout(flocalization)
|
|
597
|
+
|
|
598
|
+
flocalization.addRow('Rendering Method:', self.render_cbox)
|
|
599
|
+
flocalization.addRow('Projection:', self.projection_cbox)
|
|
600
|
+
flocalization.addRow('Frame bins:', self.frame_bins)
|
|
601
|
+
flocalization.addRow('XY binsize [nm]:', self.xy_binsize)
|
|
602
|
+
flocalization.addRow('Z binsize [nm]:', self.z_binsize)
|
|
603
|
+
flocalization.addRow(self.position_label, self.position)
|
|
604
|
+
flocalization.addRow(
|
|
605
|
+
create_hbox_layout(
|
|
606
|
+
self.auto_level,
|
|
607
|
+
self.refresh_btn,
|
|
608
|
+
self.display_3d_btn,
|
|
609
|
+
self.animation_btn,
|
|
610
|
+
)
|
|
611
|
+
)
|
|
612
|
+
# End Localization GroupBox
|
|
613
|
+
|
|
614
|
+
# Z Extraction
|
|
615
|
+
self.z_cal_curve = None
|
|
616
|
+
z_extract = QtWidgets.QGroupBox('Z Extraction')
|
|
617
|
+
z_extract_form = QtWidgets.QFormLayout()
|
|
618
|
+
z_extract.setLayout(z_extract_form)
|
|
619
|
+
|
|
620
|
+
self.cal_curve_cbox = QtWidgets.QComboBox()
|
|
621
|
+
for c in [stats.CurveFitMethod.LINEAR, stats.CurveFitMethod.CSPLINE]:
|
|
622
|
+
self.cal_curve_cbox.addItem(c.name, c)
|
|
623
|
+
self.cal_curve_cbox.currentIndexChanged.connect(
|
|
624
|
+
lambda: [
|
|
625
|
+
z_extract_form.setRowVisible(
|
|
626
|
+
idx, self.cal_curve_cbox.currentIndex() == 0
|
|
627
|
+
)
|
|
628
|
+
for idx in [1, 2]
|
|
629
|
+
]
|
|
630
|
+
)
|
|
631
|
+
|
|
632
|
+
self.z_cal_slope = create_double_spin_box(
|
|
633
|
+
min_value=0, max_value=200, decimals=8, initial_value=0.000800867
|
|
634
|
+
)
|
|
635
|
+
self.z_cal_intercept = create_double_spin_box(
|
|
636
|
+
min_value=0, max_value=200, decimals=8, initial_value=1.00927
|
|
637
|
+
)
|
|
638
|
+
self.load_cal_btn = QtWidgets.QPushButton(
|
|
639
|
+
'Load Z Calibration', clicked=lambda: self._load_calibration()
|
|
640
|
+
)
|
|
641
|
+
self.plot_cal_btn = QtWidgets.QPushButton(
|
|
642
|
+
'Calibration Curve', clicked=lambda: self._plot_calibration()
|
|
643
|
+
)
|
|
644
|
+
# display loaded file path in multiline readonly edit box
|
|
645
|
+
self.cal_file_path = QtWidgets.QTextEdit()
|
|
646
|
+
self.cal_file_path.setReadOnly(True)
|
|
647
|
+
self.cal_file_path.setFixedHeight(60)
|
|
648
|
+
self.cal_file_path.setLineWrapMode(QtWidgets.QTextEdit.LineWrapMode.WidgetWidth)
|
|
649
|
+
self.cal_file_path.setPlaceholderText('No file loaded')
|
|
650
|
+
|
|
651
|
+
self.extract_z_btn = QtWidgets.QPushButton(
|
|
652
|
+
'Extract Z', clicked=lambda: self.extract_z()
|
|
653
|
+
)
|
|
654
|
+
z_extract_form.addRow(
|
|
655
|
+
'Calibration Method:',
|
|
656
|
+
self.cal_curve_cbox,
|
|
657
|
+
)
|
|
658
|
+
z_extract_form.addRow(
|
|
659
|
+
'Z Calibration (slope):',
|
|
660
|
+
self.z_cal_slope,
|
|
661
|
+
)
|
|
662
|
+
z_extract_form.addRow(
|
|
663
|
+
'Z Calibration (intercept):',
|
|
664
|
+
self.z_cal_intercept,
|
|
665
|
+
)
|
|
666
|
+
z_extract_form.addRow(
|
|
667
|
+
create_hbox_layout(self.extract_z_btn, self.load_cal_btn, self.plot_cal_btn)
|
|
668
|
+
)
|
|
669
|
+
z_extract_form.addRow(self.cal_file_path)
|
|
670
|
+
|
|
671
|
+
# End Z Extraction
|
|
672
|
+
|
|
673
|
+
self.drift_cross_args = QtWidgets.QHBoxLayout()
|
|
674
|
+
|
|
675
|
+
self.drift_cross_bins = create_spin_box(initial_value=10)
|
|
676
|
+
self.drift_cross_px = create_spin_box(initial_value=10)
|
|
677
|
+
self.drift_cross_up = create_spin_box(
|
|
678
|
+
min_value=0, max_value=1000, initial_value=100
|
|
679
|
+
)
|
|
680
|
+
|
|
681
|
+
self.drift_cross_args.addWidget(self.drift_cross_bins)
|
|
682
|
+
self.drift_cross_args.addWidget(self.drift_cross_px)
|
|
683
|
+
self.drift_cross_args.addWidget(self.drift_cross_up)
|
|
684
|
+
|
|
685
|
+
self.drift_cross_btn = QtWidgets.QPushButton(
|
|
686
|
+
'Drift cross-correlation', clicked=lambda: self.drift_cross()
|
|
687
|
+
)
|
|
688
|
+
self.drift_fdm_btn = QtWidgets.QPushButton(
|
|
689
|
+
'Fiducial marker drift correction', clicked=lambda: self.drift_fdm()
|
|
690
|
+
)
|
|
691
|
+
|
|
692
|
+
# Drift GroupBox
|
|
693
|
+
drift = QtWidgets.QGroupBox('Drift Correction')
|
|
694
|
+
fdrift = QtWidgets.QFormLayout()
|
|
695
|
+
drift.setLayout(fdrift)
|
|
696
|
+
|
|
697
|
+
fdrift.addRow(QtWidgets.QLabel('Drift X-Corr. (bins, pixelSize, upsampling):'))
|
|
698
|
+
fdrift.addRow(self.drift_cross_args)
|
|
699
|
+
fdrift.addRow(create_hbox_layout(self.drift_cross_btn, self.drift_fdm_btn))
|
|
700
|
+
# End Drift GroupBox
|
|
701
|
+
|
|
702
|
+
self.frc_cbox = QtWidgets.QComboBox()
|
|
703
|
+
self.frc_cbox.addItem('Binomial')
|
|
704
|
+
self.frc_cbox.addItem('Odd/Even')
|
|
705
|
+
self.frc_cbox.addItem('Halves')
|
|
706
|
+
|
|
707
|
+
precision_btns = QtWidgets.QHBoxLayout()
|
|
708
|
+
self.frc_res_btn = QtWidgets.QPushButton(
|
|
709
|
+
'FRC Resolution', clicked=lambda: self.FRC_estimate()
|
|
710
|
+
)
|
|
711
|
+
|
|
712
|
+
self.NeNA_widget = None
|
|
713
|
+
self.NeNA_btn = QtWidgets.QPushButton(
|
|
714
|
+
'NeNA Loc. Prec. Estimate', clicked=lambda: self.NeNA_estimate()
|
|
715
|
+
)
|
|
716
|
+
self.tardis_btn = QtWidgets.QPushButton(
|
|
717
|
+
'TARDIS', clicked=lambda: self.TARDIS_analysis()
|
|
718
|
+
)
|
|
719
|
+
|
|
720
|
+
# Precision GroupBox
|
|
721
|
+
precision = QtWidgets.QGroupBox('Loc. Precision')
|
|
722
|
+
fprecision = QtWidgets.QFormLayout()
|
|
723
|
+
precision.setLayout(fprecision)
|
|
724
|
+
|
|
725
|
+
fprecision.addRow(QtWidgets.QLabel('FRC Method:'), self.frc_cbox)
|
|
726
|
+
precision_btns.addWidget(self.frc_res_btn)
|
|
727
|
+
precision_btns.addWidget(self.NeNA_btn)
|
|
728
|
+
precision_btns.addWidget(self.tardis_btn)
|
|
729
|
+
fprecision.addRow(precision_btns)
|
|
730
|
+
# End Precision GroupBox
|
|
731
|
+
|
|
732
|
+
self.nneigh_merge_args = QtWidgets.QHBoxLayout()
|
|
733
|
+
|
|
734
|
+
self.nn_neighbors = create_spin_box(max_value=20000, initial_value=1)
|
|
735
|
+
self.nn_min_distance = create_double_spin_box(max_value=20000, initial_value=0)
|
|
736
|
+
self.nn_max_distance = create_double_spin_box(max_value=20000, initial_value=30)
|
|
737
|
+
self.nn_max_off = create_spin_box(max_value=20000, initial_value=1)
|
|
738
|
+
self.nn_max_length = create_spin_box(max_value=20000, initial_value=500)
|
|
739
|
+
|
|
740
|
+
self.nneigh_merge_args.addWidget(self.nn_neighbors)
|
|
741
|
+
self.nneigh_merge_args.addWidget(self.nn_min_distance)
|
|
742
|
+
self.nneigh_merge_args.addWidget(self.nn_max_distance)
|
|
743
|
+
self.nneigh_merge_args.addWidget(self.nn_max_off)
|
|
744
|
+
self.nneigh_merge_args.addWidget(self.nn_max_length)
|
|
745
|
+
|
|
746
|
+
self.nn_layout = QtWidgets.QHBoxLayout()
|
|
747
|
+
self.nneigh_btn = QtWidgets.QPushButton(
|
|
748
|
+
'Nearest-neighbour', clicked=lambda: self.nneigh()
|
|
749
|
+
)
|
|
750
|
+
self.merge_btn = QtWidgets.QPushButton(
|
|
751
|
+
'Merge Tracks', clicked=lambda: self.merge()
|
|
752
|
+
)
|
|
753
|
+
self.nneigh_merge_btn = QtWidgets.QPushButton(
|
|
754
|
+
'NM + Merging', clicked=lambda: self.nneigh_merge()
|
|
755
|
+
)
|
|
756
|
+
|
|
757
|
+
self.nn_layout.addWidget(self.nneigh_btn)
|
|
758
|
+
self.nn_layout.addWidget(self.merge_btn)
|
|
759
|
+
self.nn_layout.addWidget(self.nneigh_merge_btn)
|
|
760
|
+
|
|
761
|
+
# Precision GroupBox
|
|
762
|
+
nearestN = QtWidgets.QGroupBox('NN Analysis')
|
|
763
|
+
fnearestN = QtWidgets.QFormLayout()
|
|
764
|
+
nearestN.setLayout(fnearestN)
|
|
765
|
+
|
|
766
|
+
fnearestN.addRow(
|
|
767
|
+
QtWidgets.QLabel('NN (n-neighbor, min, max-distance, max-off, max-len):')
|
|
768
|
+
)
|
|
769
|
+
fnearestN.addRow(self.nneigh_merge_args)
|
|
770
|
+
fnearestN.addRow(self.nn_layout)
|
|
771
|
+
# End Precision GroupBox
|
|
772
|
+
|
|
773
|
+
self.export_options = ChecklistDialog(
|
|
774
|
+
'Exported Columns',
|
|
775
|
+
[
|
|
776
|
+
'Super-res image',
|
|
777
|
+
]
|
|
778
|
+
+ UNIQUE_COLUMNS,
|
|
779
|
+
checked=True,
|
|
780
|
+
parent=self,
|
|
781
|
+
)
|
|
782
|
+
|
|
783
|
+
self.import_loc_btn = QtWidgets.QPushButton(
|
|
784
|
+
'Import', clicked=lambda: self.import_loc()
|
|
785
|
+
)
|
|
786
|
+
self.export_loc_btn = QtWidgets.QPushButton(
|
|
787
|
+
'Export', clicked=lambda: self.export_loc()
|
|
788
|
+
)
|
|
789
|
+
|
|
790
|
+
self.localization_form.addWidget(localization)
|
|
791
|
+
self.localization_form.addWidget(z_extract)
|
|
792
|
+
self.localization_form.addWidget(drift)
|
|
793
|
+
self.localization_form.addWidget(precision)
|
|
794
|
+
self.localization_form.addWidget(nearestN)
|
|
795
|
+
|
|
796
|
+
self.localization_form.addLayout(
|
|
797
|
+
create_hbox_layout(self.import_loc_btn, self.export_loc_btn)
|
|
798
|
+
)
|
|
799
|
+
self.localization_form.addStretch()
|
|
800
|
+
|
|
801
|
+
def setup_data_filters_tab(self):
|
|
802
|
+
'''Set up the Data Filters tab.'''
|
|
803
|
+
# Results stats tab layout
|
|
804
|
+
self.data_filters_widget, self.data_filters_layout = create_widget(
|
|
805
|
+
QtWidgets.QVBoxLayout
|
|
806
|
+
)
|
|
807
|
+
self.tab_widget.addTab(self.data_filters_widget, 'Data Filters')
|
|
808
|
+
|
|
809
|
+
# results stats widget
|
|
810
|
+
self.results_plot_scroll = QtWidgets.QScrollArea()
|
|
811
|
+
self.results_plot = resultsStatsWidget()
|
|
812
|
+
self.results_plot.dataFilterUpdated.connect(self.filter_updated)
|
|
813
|
+
self.results_plot_scroll.setHorizontalScrollBarPolicy(
|
|
814
|
+
Qt.ScrollBarPolicy.ScrollBarAlwaysOff
|
|
815
|
+
)
|
|
816
|
+
self.results_plot_scroll.setWidgetResizable(True)
|
|
817
|
+
self.results_plot_scroll.setWidget(self.results_plot)
|
|
818
|
+
|
|
819
|
+
self.populate_data_btn = QtWidgets.QPushButton(
|
|
820
|
+
'Populate Data', clicked=lambda: self.populate_data()
|
|
821
|
+
)
|
|
822
|
+
self.apply_filters_btn = QtWidgets.QPushButton(
|
|
823
|
+
'Apply Filters', clicked=lambda: self.apply_filters()
|
|
824
|
+
)
|
|
825
|
+
self.apply_filters_btn.setToolTip(
|
|
826
|
+
'Applies the filters permanently to fitting results.'
|
|
827
|
+
)
|
|
828
|
+
self.zero_coords_btn = QtWidgets.QPushButton(
|
|
829
|
+
'Zero Coordinates', clicked=lambda: self.zero_coordinates()
|
|
830
|
+
)
|
|
831
|
+
self.toggle_tracking_btn = QtWidgets.QPushButton(
|
|
832
|
+
'Track Plots', clicked=lambda: self.toggle_track_plots()
|
|
833
|
+
)
|
|
834
|
+
|
|
835
|
+
data_btn_layout = create_hbox_layout(
|
|
836
|
+
self.populate_data_btn,
|
|
837
|
+
self.apply_filters_btn,
|
|
838
|
+
self.zero_coords_btn,
|
|
839
|
+
self.toggle_tracking_btn,
|
|
840
|
+
)
|
|
841
|
+
|
|
842
|
+
# create x/y/z coordinate shift
|
|
843
|
+
self.data_shift = create_double_spin_box(
|
|
844
|
+
min_value=1, max_value=10000, decimals=0, initial_value=200
|
|
845
|
+
)
|
|
846
|
+
self.data_shift_label = QtWidgets.QLabel('Data Shift (nm)')
|
|
847
|
+
# axis to shift
|
|
848
|
+
self.data_shift_axis = QtWidgets.QComboBox()
|
|
849
|
+
self.data_shift_axis.addItem('X', DataColumns.X)
|
|
850
|
+
self.data_shift_axis.addItem('Y', DataColumns.Y)
|
|
851
|
+
self.data_shift_axis.addItem('Z', DataColumns.Z)
|
|
852
|
+
self.data_shift_axis.setToolTip('Select the axis to shift the data.')
|
|
853
|
+
# direction to shift
|
|
854
|
+
self.data_shift_dir = QtWidgets.QCheckBox('Negative')
|
|
855
|
+
self.data_shift_dir.setToolTip('Shift the data in the negative direction.')
|
|
856
|
+
# apply shift button
|
|
857
|
+
self.data_shift_btn = QtWidgets.QPushButton(
|
|
858
|
+
'Shift Data', clicked=lambda: self.shift_data()
|
|
859
|
+
)
|
|
860
|
+
self.data_shift_btn.setToolTip('Shift the data by the specified value.')
|
|
861
|
+
|
|
862
|
+
data_shift_layout = create_hbox_layout(
|
|
863
|
+
self.data_shift_label,
|
|
864
|
+
self.data_shift,
|
|
865
|
+
self.data_shift_axis,
|
|
866
|
+
self.data_shift_dir,
|
|
867
|
+
self.data_shift_btn,
|
|
868
|
+
)
|
|
869
|
+
|
|
870
|
+
# Create stats label with HTML formatting
|
|
871
|
+
self.stats_label = QtWidgets.QLabel()
|
|
872
|
+
|
|
873
|
+
self.data_filters_layout.addWidget(self.results_plot_scroll)
|
|
874
|
+
self.data_filters_layout.addLayout(data_btn_layout)
|
|
875
|
+
self.data_filters_layout.addLayout(data_shift_layout)
|
|
876
|
+
self.data_filters_layout.addWidget(self.stats_label)
|
|
877
|
+
|
|
878
|
+
def updateStatsLabel(self):
|
|
879
|
+
total = len(self.currentResults) if self.currentResults is not None else 0
|
|
880
|
+
filtered = (
|
|
881
|
+
len(self.results_plot.filtered)
|
|
882
|
+
if hasattr(self.results_plot, 'filtered')
|
|
883
|
+
and self.results_plot.filtered is not None
|
|
884
|
+
else total
|
|
885
|
+
)
|
|
886
|
+
|
|
887
|
+
self.stats_label.setText(
|
|
888
|
+
f'''<div style='text-align: center;'>
|
|
889
|
+
<b>Localizations:</b> {filtered:,} / {total:,}
|
|
890
|
+
</div>'''
|
|
891
|
+
)
|
|
892
|
+
|
|
893
|
+
def FRC_estimate(self):
|
|
894
|
+
'''Estimate FRC (Fourier Ring Correlation) resolution.'''
|
|
895
|
+
if self.currentResults is None:
|
|
896
|
+
return
|
|
897
|
+
|
|
898
|
+
if len(self.currentResults) <= 0:
|
|
899
|
+
return
|
|
900
|
+
|
|
901
|
+
frc_method = self.frc_cbox.currentText()
|
|
902
|
+
data = self.currentResults.toRender()
|
|
903
|
+
|
|
904
|
+
def work_func(**kwargs):
|
|
905
|
+
try:
|
|
906
|
+
return FRC_resolution_binomial(
|
|
907
|
+
np.c_[data[0], data[1], data[2]],
|
|
908
|
+
self.xy_binsize.value(),
|
|
909
|
+
frc_method,
|
|
910
|
+
)
|
|
911
|
+
except Exception:
|
|
912
|
+
traceback.print_exc()
|
|
913
|
+
return None
|
|
914
|
+
|
|
915
|
+
def done(results):
|
|
916
|
+
self.frc_res_btn.setDisabled(False)
|
|
917
|
+
if results is not None:
|
|
918
|
+
plot_frc(*results)
|
|
919
|
+
|
|
920
|
+
self.worker = QThreadWorker(work_func)
|
|
921
|
+
self.worker.signals.result.connect(done)
|
|
922
|
+
# Execute
|
|
923
|
+
self.frc_res_btn.setDisabled(True)
|
|
924
|
+
self._threadpool.start(self.worker)
|
|
925
|
+
|
|
926
|
+
def NeNA_estimate(self):
|
|
927
|
+
'''Estimate Nearest-Neighbor Analysis for localization precision.'''
|
|
928
|
+
if self.currentResults is None:
|
|
929
|
+
return
|
|
930
|
+
|
|
931
|
+
if len(self.currentResults) <= 0:
|
|
932
|
+
return
|
|
933
|
+
|
|
934
|
+
def work_func(**kwargs):
|
|
935
|
+
try:
|
|
936
|
+
return self.currentResults.nn_trajectories(0, 200, 0, 1)
|
|
937
|
+
except Exception:
|
|
938
|
+
traceback.print_exc()
|
|
939
|
+
return None
|
|
940
|
+
|
|
941
|
+
def done(results):
|
|
942
|
+
self.NeNA_btn.setDisabled(False)
|
|
943
|
+
if results is not None:
|
|
944
|
+
self.currentResults = results
|
|
945
|
+
self.results_plot.setData(self.currentResults.dataFrame())
|
|
946
|
+
|
|
947
|
+
self.NeNA_widget = NeNA_Widget(
|
|
948
|
+
self.currentResults.neighbour_dist, self.currentResults.trackID
|
|
949
|
+
)
|
|
950
|
+
|
|
951
|
+
res = self.NeNA_widget.exec()
|
|
952
|
+
|
|
953
|
+
self.worker = QThreadWorker(work_func)
|
|
954
|
+
self.worker.signals.result.connect(done)
|
|
955
|
+
# Execute
|
|
956
|
+
self.NeNA_btn.setDisabled(True)
|
|
957
|
+
self._threadpool.start(self.worker)
|
|
958
|
+
|
|
959
|
+
def TARDIS_analysis(self):
|
|
960
|
+
'''Perform TARDIS analysis.'''
|
|
961
|
+
if self.currentResults is None:
|
|
962
|
+
return
|
|
963
|
+
|
|
964
|
+
if len(self.currentResults) <= 0:
|
|
965
|
+
return
|
|
966
|
+
|
|
967
|
+
self.tardis = TARDIS_Widget(
|
|
968
|
+
self.currentResults.frames,
|
|
969
|
+
self.currentResults.locX,
|
|
970
|
+
self.currentResults.locY,
|
|
971
|
+
self.currentResults.locZ,
|
|
972
|
+
)
|
|
973
|
+
self.tardis.startWorker.connect(
|
|
974
|
+
lambda worker: self._threadpool.start(worker)
|
|
975
|
+
)
|
|
976
|
+
self.tardis.show()
|
|
977
|
+
|
|
978
|
+
def drift_cross(self):
|
|
979
|
+
'''Perform drift cross-correlation.'''
|
|
980
|
+
if self.currentResults is None:
|
|
981
|
+
return
|
|
982
|
+
|
|
983
|
+
if len(self.currentResults) <= 0:
|
|
984
|
+
return
|
|
985
|
+
|
|
986
|
+
def work_func(**kwargs):
|
|
987
|
+
try:
|
|
988
|
+
return self.currentResults.drift_cross_correlation(
|
|
989
|
+
self.drift_cross_bins.value(),
|
|
990
|
+
self.drift_cross_px.value(),
|
|
991
|
+
self.drift_cross_up.value(),
|
|
992
|
+
)
|
|
993
|
+
except Exception:
|
|
994
|
+
traceback.print_exc()
|
|
995
|
+
return None
|
|
996
|
+
|
|
997
|
+
def done(results):
|
|
998
|
+
self.drift_cross_btn.setDisabled(False)
|
|
999
|
+
if results is not None:
|
|
1000
|
+
self.render_loc()
|
|
1001
|
+
self.currentResults = results[0]
|
|
1002
|
+
self.results_plot.setData(self.currentResults.dataFrame())
|
|
1003
|
+
plot_drift(*results[2])
|
|
1004
|
+
|
|
1005
|
+
self.worker = QThreadWorker(work_func)
|
|
1006
|
+
self.worker.signals.result.connect(done)
|
|
1007
|
+
# Execute
|
|
1008
|
+
self.drift_cross_btn.setDisabled(True)
|
|
1009
|
+
self._threadpool.start(self.worker)
|
|
1010
|
+
|
|
1011
|
+
def drift_fdm(self):
|
|
1012
|
+
'''Perform drift correction using fiducial markers.'''
|
|
1013
|
+
if self.currentResults is None:
|
|
1014
|
+
return
|
|
1015
|
+
|
|
1016
|
+
if len(self.currentResults) <= 0:
|
|
1017
|
+
return
|
|
1018
|
+
|
|
1019
|
+
def work_func(**kwargs):
|
|
1020
|
+
try:
|
|
1021
|
+
return self.currentResults.drift_fiducial_marker()
|
|
1022
|
+
except Exception:
|
|
1023
|
+
traceback.print_exc()
|
|
1024
|
+
return None
|
|
1025
|
+
|
|
1026
|
+
def done(results):
|
|
1027
|
+
self.drift_fdm_btn.setDisabled(False)
|
|
1028
|
+
if results is not None:
|
|
1029
|
+
self.currentResults = results[0]
|
|
1030
|
+
self.results_plot.setData(self.currentResults.dataFrame())
|
|
1031
|
+
plot_drift(*results[1])
|
|
1032
|
+
self.render_loc()
|
|
1033
|
+
|
|
1034
|
+
self.worker = QThreadWorker(work_func)
|
|
1035
|
+
self.worker.signals.result.connect(done)
|
|
1036
|
+
# Execute
|
|
1037
|
+
self.drift_fdm_btn.setDisabled(True)
|
|
1038
|
+
self._threadpool.start(self.worker)
|
|
1039
|
+
|
|
1040
|
+
def nneigh_merge(self):
|
|
1041
|
+
'''Perform nearest-neighbor merging.'''
|
|
1042
|
+
if self.currentResults is None:
|
|
1043
|
+
return
|
|
1044
|
+
|
|
1045
|
+
if len(self.currentResults) <= 0:
|
|
1046
|
+
return
|
|
1047
|
+
|
|
1048
|
+
def work_func(**kwargs):
|
|
1049
|
+
try:
|
|
1050
|
+
return self.currentResults.nearest_neighbour_merging(
|
|
1051
|
+
self.nn_min_distance.value(),
|
|
1052
|
+
self.nn_max_distance.value(),
|
|
1053
|
+
self.nn_max_off.value(),
|
|
1054
|
+
self.nn_max_length.value(),
|
|
1055
|
+
self.nn_neighbors.value(),
|
|
1056
|
+
)
|
|
1057
|
+
except Exception:
|
|
1058
|
+
traceback.print_exc()
|
|
1059
|
+
return None
|
|
1060
|
+
|
|
1061
|
+
def done(results):
|
|
1062
|
+
self.nneigh_merge_btn.setDisabled(False)
|
|
1063
|
+
if results is not None:
|
|
1064
|
+
self.currentResults = results
|
|
1065
|
+
self.results_plot.setData(self.currentResults.dataFrame())
|
|
1066
|
+
|
|
1067
|
+
self.worker = QThreadWorker(work_func)
|
|
1068
|
+
self.worker.signals.result.connect(done)
|
|
1069
|
+
# Execute
|
|
1070
|
+
self.nneigh_merge_btn.setDisabled(True)
|
|
1071
|
+
self._threadpool.start(self.worker)
|
|
1072
|
+
|
|
1073
|
+
def nneigh(self):
|
|
1074
|
+
'''Perform nearest-neighbor analysis.'''
|
|
1075
|
+
if self.currentResults is None:
|
|
1076
|
+
return
|
|
1077
|
+
|
|
1078
|
+
if len(self.currentResults) <= 0:
|
|
1079
|
+
return
|
|
1080
|
+
|
|
1081
|
+
def work_func(**kwargs):
|
|
1082
|
+
try:
|
|
1083
|
+
return self.currentResults.nn_trajectories(
|
|
1084
|
+
self.nn_min_distance.value(),
|
|
1085
|
+
self.nn_max_distance.value(),
|
|
1086
|
+
self.nn_max_off.value(),
|
|
1087
|
+
self.nn_neighbors.value(),
|
|
1088
|
+
)
|
|
1089
|
+
except Exception:
|
|
1090
|
+
traceback.print_exc()
|
|
1091
|
+
return None
|
|
1092
|
+
|
|
1093
|
+
def done(results):
|
|
1094
|
+
self.nneigh_btn.setDisabled(False)
|
|
1095
|
+
if results is not None:
|
|
1096
|
+
self.currentResults = results
|
|
1097
|
+
self.results_plot.setData(self.currentResults.dataFrame())
|
|
1098
|
+
|
|
1099
|
+
self.worker = QThreadWorker(work_func)
|
|
1100
|
+
self.worker.signals.result.connect(done)
|
|
1101
|
+
# Execute
|
|
1102
|
+
self.nneigh_btn.setDisabled(True)
|
|
1103
|
+
self._threadpool.start(self.worker)
|
|
1104
|
+
|
|
1105
|
+
def merge(self):
|
|
1106
|
+
'''Merge tracks.'''
|
|
1107
|
+
if self.currentResults is None:
|
|
1108
|
+
return
|
|
1109
|
+
|
|
1110
|
+
if len(self.currentResults) <= 0:
|
|
1111
|
+
return
|
|
1112
|
+
|
|
1113
|
+
def work_func(**kwargs):
|
|
1114
|
+
try:
|
|
1115
|
+
return self.currentResults.merge_tracks(self.nn_max_length.value())
|
|
1116
|
+
except Exception:
|
|
1117
|
+
traceback.print_exc()
|
|
1118
|
+
return None
|
|
1119
|
+
|
|
1120
|
+
def done(results):
|
|
1121
|
+
self.merge_btn.setDisabled(False)
|
|
1122
|
+
if results is not None:
|
|
1123
|
+
self.currentResults = results
|
|
1124
|
+
self.results_plot.setData(self.currentResults.dataFrame())
|
|
1125
|
+
|
|
1126
|
+
self.worker = QThreadWorker(work_func)
|
|
1127
|
+
self.worker.signals.result.connect(done)
|
|
1128
|
+
# Execute
|
|
1129
|
+
self.merge_btn.setDisabled(True)
|
|
1130
|
+
self._threadpool.start(self.worker)
|
|
1131
|
+
|
|
1132
|
+
def populate_data(self):
|
|
1133
|
+
'''Populate the data filters.'''
|
|
1134
|
+
self.results_plot.setData(self.currentResults.dataFrame())
|
|
1135
|
+
|
|
1136
|
+
def apply_filters(self):
|
|
1137
|
+
'''Apply data filters to the fitting results.'''
|
|
1138
|
+
if (
|
|
1139
|
+
not hasattr(self.results_plot, 'filtered')
|
|
1140
|
+
or self.results_plot.filtered is None
|
|
1141
|
+
):
|
|
1142
|
+
self.results_plot.update()
|
|
1143
|
+
|
|
1144
|
+
self.currentResults = FittingResults.fromDataFrame(
|
|
1145
|
+
self.results_plot.filtered, 1, path=self.currentResults.path
|
|
1146
|
+
)
|
|
1147
|
+
self.results_plot.setData(self.currentResults.dataFrame())
|
|
1148
|
+
self.render_loc()
|
|
1149
|
+
print('Filters applied.')
|
|
1150
|
+
|
|
1151
|
+
def zero_coordinates(self):
|
|
1152
|
+
'''Zero the fitting results coordinates.'''
|
|
1153
|
+
if self.currentResults is not None:
|
|
1154
|
+
self.currentResults.zero_coordinates()
|
|
1155
|
+
self.results_plot.setData(self.currentResults.dataFrame())
|
|
1156
|
+
self.render_loc()
|
|
1157
|
+
print('Coordinates reset.')
|
|
1158
|
+
|
|
1159
|
+
def toggle_track_plots(self):
|
|
1160
|
+
'''Toggle the track plots.'''
|
|
1161
|
+
if self.currentResults is not None:
|
|
1162
|
+
self.results_plot.toggle_track_plots()
|
|
1163
|
+
|
|
1164
|
+
def shift_data(self):
|
|
1165
|
+
'''Shift the data by a specified amount.'''
|
|
1166
|
+
if self.currentResults is None:
|
|
1167
|
+
return
|
|
1168
|
+
|
|
1169
|
+
if len(self.currentResults) <= 0:
|
|
1170
|
+
return
|
|
1171
|
+
|
|
1172
|
+
axis: DataColumns = self.data_shift_axis.currentData()
|
|
1173
|
+
shift = self.data_shift.value()
|
|
1174
|
+
direction = -1 if self.data_shift_dir.isChecked() else 1
|
|
1175
|
+
|
|
1176
|
+
self.currentResults.data[axis] += shift * direction
|
|
1177
|
+
|
|
1178
|
+
self.results_plot.clear()
|
|
1179
|
+
self.render_loc()
|
|
1180
|
+
|
|
1181
|
+
def filter_updated(self, df: pd.DataFrame):
|
|
1182
|
+
'''Update the view when data filters are updated.
|
|
1183
|
+
|
|
1184
|
+
Parameters
|
|
1185
|
+
----------
|
|
1186
|
+
df : pd.DataFrame
|
|
1187
|
+
The filtered DataFrame.
|
|
1188
|
+
'''
|
|
1189
|
+
if df is not None:
|
|
1190
|
+
self.updateStatsLabel()
|
|
1191
|
+
|
|
1192
|
+
if df.count().min() > 1:
|
|
1193
|
+
render_idx = self.render_cbox.currentData()
|
|
1194
|
+
renderClass = BaseRenderer(
|
|
1195
|
+
self.xy_binsize.value(), RenderModes(render_idx)
|
|
1196
|
+
)
|
|
1197
|
+
img = renderClass.render_xy(
|
|
1198
|
+
df['x'].to_numpy(), df['y'].to_numpy(), df['intensity'].to_numpy()
|
|
1199
|
+
)
|
|
1200
|
+
self.setImage(img, autoLevels=False)
|
|
1201
|
+
else:
|
|
1202
|
+
# Create a black image
|
|
1203
|
+
img = np.zeros(
|
|
1204
|
+
(
|
|
1205
|
+
self.image_layers.currentLayer.height(),
|
|
1206
|
+
self.image_layers.currentLayer.width(),
|
|
1207
|
+
3,
|
|
1208
|
+
),
|
|
1209
|
+
np.uint8,
|
|
1210
|
+
)
|
|
1211
|
+
|
|
1212
|
+
# Write some Text
|
|
1213
|
+
font = cv2.FONT_HERSHEY_SIMPLEX
|
|
1214
|
+
bottomLeftCornerOfText = (10, 50)
|
|
1215
|
+
fontScale = 1
|
|
1216
|
+
fontColor = (255, 255, 255)
|
|
1217
|
+
thickness = 1
|
|
1218
|
+
lineType = 2
|
|
1219
|
+
|
|
1220
|
+
cv2.putText(
|
|
1221
|
+
img,
|
|
1222
|
+
'EMPTY!',
|
|
1223
|
+
bottomLeftCornerOfText,
|
|
1224
|
+
font,
|
|
1225
|
+
fontScale,
|
|
1226
|
+
fontColor,
|
|
1227
|
+
thickness,
|
|
1228
|
+
lineType,
|
|
1229
|
+
)
|
|
1230
|
+
self.setImage(img, autoLevels=False)
|
|
1231
|
+
|
|
1232
|
+
def export_loc(self, filename=None):
|
|
1233
|
+
'''Export the fitting results into a file.
|
|
1234
|
+
|
|
1235
|
+
Parameters
|
|
1236
|
+
----------
|
|
1237
|
+
filename : str, optional
|
|
1238
|
+
File path; if None, a save file dialog is shown, by default None.
|
|
1239
|
+
'''
|
|
1240
|
+
if self.currentResults is None:
|
|
1241
|
+
return
|
|
1242
|
+
|
|
1243
|
+
if filename is None:
|
|
1244
|
+
if not self.export_options.exec():
|
|
1245
|
+
return
|
|
1246
|
+
|
|
1247
|
+
filename, _ = getSaveFileName(
|
|
1248
|
+
self,
|
|
1249
|
+
'Export localizations',
|
|
1250
|
+
filter='HDF5 files (*.h5);;TSV Files (*.tsv)',
|
|
1251
|
+
directory=os.path.dirname(self.currentResults.path),
|
|
1252
|
+
)
|
|
1253
|
+
|
|
1254
|
+
if len(filename) > 0:
|
|
1255
|
+
options = self.export_options.toList()
|
|
1256
|
+
|
|
1257
|
+
dataFrame = self.currentResults.dataFrame()
|
|
1258
|
+
exp_columns = []
|
|
1259
|
+
for col in dataFrame.columns:
|
|
1260
|
+
if col in options:
|
|
1261
|
+
exp_columns.append(col)
|
|
1262
|
+
|
|
1263
|
+
if exp_columns:
|
|
1264
|
+
if '.tsv' in filename:
|
|
1265
|
+
dataFrame.to_csv(
|
|
1266
|
+
filename,
|
|
1267
|
+
index=False,
|
|
1268
|
+
columns=exp_columns,
|
|
1269
|
+
float_format=self.export_options.export_precision.text(),
|
|
1270
|
+
sep='\t',
|
|
1271
|
+
encoding='utf-8',
|
|
1272
|
+
)
|
|
1273
|
+
elif '.h5' in filename:
|
|
1274
|
+
dataFrame[exp_columns].to_hdf(
|
|
1275
|
+
filename, key='microEye', index=False, complevel=0
|
|
1276
|
+
)
|
|
1277
|
+
|
|
1278
|
+
if 'Super-res image' in options:
|
|
1279
|
+
sres_img = self.render_loc()
|
|
1280
|
+
tf.imwrite(
|
|
1281
|
+
re.sub(r'(\.h5|\.tsv)$', '_super_res.tif', filename),
|
|
1282
|
+
sres_img,
|
|
1283
|
+
photometric='minisblack',
|
|
1284
|
+
bigtiff=True,
|
|
1285
|
+
ome=False,
|
|
1286
|
+
)
|
|
1287
|
+
|
|
1288
|
+
def import_loc(self):
|
|
1289
|
+
'''Import fitting results from a file.'''
|
|
1290
|
+
filename, _ = getOpenFileName(
|
|
1291
|
+
self,
|
|
1292
|
+
'Import localizations',
|
|
1293
|
+
filter='HDF5 files (*.h5);;TSV Files (*.tsv)',
|
|
1294
|
+
directory=os.path.dirname(self.currentResults.path),
|
|
1295
|
+
)
|
|
1296
|
+
|
|
1297
|
+
if len(filename) > 0:
|
|
1298
|
+
results = FittingResults.fromFile(filename, 1)
|
|
1299
|
+
|
|
1300
|
+
if results is not None:
|
|
1301
|
+
self.add_data_layer(results)
|
|
1302
|
+
print('Done importing results.')
|
|
1303
|
+
else:
|
|
1304
|
+
print('Error importing results.')
|