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