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,474 @@
|
|
|
1
|
+
import struct
|
|
2
|
+
import weakref
|
|
3
|
+
from os import name
|
|
4
|
+
from queue import LifoQueue, Queue
|
|
5
|
+
from typing import Union
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
|
|
9
|
+
from microEye.hardware.port_config import *
|
|
10
|
+
from microEye.qt import QtCore, QtSerialPort, QtWidgets
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class IR_Cam:
|
|
14
|
+
'''An abstract class for IR cameras.'''
|
|
15
|
+
|
|
16
|
+
def __init__(self, buffer: Union[Queue, LifoQueue]) -> None:
|
|
17
|
+
self.name = 'Dummy'
|
|
18
|
+
if buffer is None:
|
|
19
|
+
self._buffer = LifoQueue()
|
|
20
|
+
else:
|
|
21
|
+
# add buffer in weakref to avoid circular reference
|
|
22
|
+
self._buffer = weakref.ref(buffer)
|
|
23
|
+
self.buffer.put(np.array([0 for i in range(128)]))
|
|
24
|
+
self._connect_btn = QtWidgets.QPushButton()
|
|
25
|
+
|
|
26
|
+
def isDummy(self) -> bool:
|
|
27
|
+
return True
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def buffer(self):
|
|
31
|
+
if isinstance(self._buffer, weakref.ref):
|
|
32
|
+
# dereference the weakref to get the actual buffer
|
|
33
|
+
return self._buffer()
|
|
34
|
+
|
|
35
|
+
return self._buffer
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def isEmpty(self) -> bool:
|
|
39
|
+
return self.buffer.empty()
|
|
40
|
+
|
|
41
|
+
def get(self) -> np.ndarray:
|
|
42
|
+
return self.buffer.get()
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def isOpen(self) -> bool:
|
|
46
|
+
return False
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class ParallaxLineScanner(IR_Cam):
|
|
50
|
+
'''A class for Parallax CCD Array (TSL1401)
|
|
51
|
+
connected via the Arduino LineScanner.'''
|
|
52
|
+
|
|
53
|
+
def __init__(self, buffer: Union[Queue, LifoQueue]) -> None:
|
|
54
|
+
super().__init__(buffer=buffer)
|
|
55
|
+
|
|
56
|
+
self.name = 'Parallax CCD Array (TSL1401) LineScanner'
|
|
57
|
+
self.buffer.put(np.array([0 for i in range(128)]))
|
|
58
|
+
self.serial = QtSerialPort.QSerialPort(None, readyRead=self.receive)
|
|
59
|
+
self.serial.setBaudRate(115200)
|
|
60
|
+
self.serial.setPortName('COM4')
|
|
61
|
+
|
|
62
|
+
def isDummy(self) -> bool:
|
|
63
|
+
return False
|
|
64
|
+
|
|
65
|
+
def open(self):
|
|
66
|
+
'''Opens the serial port.'''
|
|
67
|
+
self.serial.open(QtCore.QIODevice.OpenModeFlag.ReadWrite)
|
|
68
|
+
|
|
69
|
+
@property
|
|
70
|
+
def isOpen(self) -> bool:
|
|
71
|
+
'''Returns True if connected.'''
|
|
72
|
+
return self.serial.isOpen()
|
|
73
|
+
|
|
74
|
+
def close(self):
|
|
75
|
+
'''Closes the supplied serial port.'''
|
|
76
|
+
self.serial.close()
|
|
77
|
+
|
|
78
|
+
def setPortName(self, name: str):
|
|
79
|
+
'''Sets the serial port name.'''
|
|
80
|
+
self.serial.setPortName(name)
|
|
81
|
+
|
|
82
|
+
def setBaudRate(self, baudRate: int):
|
|
83
|
+
'''Sets the serial port baudrate.'''
|
|
84
|
+
self.serial.setBaudRate(baudRate)
|
|
85
|
+
|
|
86
|
+
def receive(self):
|
|
87
|
+
'''IR CCD array serial port data ready signal'''
|
|
88
|
+
if self.serial.bytesAvailable() >= 260:
|
|
89
|
+
barray = self.serial.read(260)
|
|
90
|
+
temp = np.array(
|
|
91
|
+
np.array(struct.unpack('h' * (len(barray) // 2), barray)) * 5.0 / 1023.0
|
|
92
|
+
)
|
|
93
|
+
# array realignment
|
|
94
|
+
if (temp[0] != 0 or temp[-1] != 0) and self.serial.bytesAvailable() >= 2:
|
|
95
|
+
self.serial.read(2)
|
|
96
|
+
# byte-wise realignment
|
|
97
|
+
if temp.max() > 5:
|
|
98
|
+
self.serial.read(1)
|
|
99
|
+
self.buffer.put(temp[1:129])
|
|
100
|
+
|
|
101
|
+
def open_dialog(self):
|
|
102
|
+
'''Opens a port config dialog
|
|
103
|
+
for the serial port.
|
|
104
|
+
'''
|
|
105
|
+
dialog = port_config()
|
|
106
|
+
if not self.isOpen:
|
|
107
|
+
if dialog.exec():
|
|
108
|
+
portname, baudrate = dialog.get_results()
|
|
109
|
+
self.setPortName(portname)
|
|
110
|
+
self.setBaudRate(baudrate)
|
|
111
|
+
|
|
112
|
+
def getQWidget(self, parent=None) -> QtWidgets.QGroupBox:
|
|
113
|
+
'''Generates a QGroupBox with
|
|
114
|
+
connect/disconnect/config buttons.'''
|
|
115
|
+
group = QtWidgets.QGroupBox('Parallax CCD Array')
|
|
116
|
+
layout = QtWidgets.QVBoxLayout()
|
|
117
|
+
group.setLayout(layout)
|
|
118
|
+
|
|
119
|
+
# IR CCD array arduino buttons
|
|
120
|
+
self._connect_btn = QtWidgets.QPushButton(
|
|
121
|
+
'Connect', parent, clicked=lambda: self.open()
|
|
122
|
+
)
|
|
123
|
+
disconnect_btn = QtWidgets.QPushButton(
|
|
124
|
+
'Disconnect', clicked=lambda: self.close()
|
|
125
|
+
)
|
|
126
|
+
config_btn = QtWidgets.QPushButton(
|
|
127
|
+
'Port Config.', clicked=lambda: self.open_dialog()
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
btns = QtWidgets.QHBoxLayout()
|
|
131
|
+
btns.addWidget(self._connect_btn)
|
|
132
|
+
btns.addWidget(disconnect_btn)
|
|
133
|
+
btns.addWidget(config_btn)
|
|
134
|
+
layout.addLayout(btns)
|
|
135
|
+
|
|
136
|
+
return group
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class DemoLineScanner(IR_Cam):
|
|
140
|
+
'''A demo camera class that generates random frames every 50 ms.
|
|
141
|
+
Useful for testing and demonstrations without hardware.'''
|
|
142
|
+
|
|
143
|
+
def __init__(self, buffer: Union[Queue, LifoQueue], array_length=128) -> None:
|
|
144
|
+
super().__init__(buffer=buffer)
|
|
145
|
+
|
|
146
|
+
self.name = 'Demo Line Scanner'
|
|
147
|
+
self._array_length = array_length # Adjustable array length
|
|
148
|
+
self.buffer.put(np.array([0 for i in range(self._array_length)]))
|
|
149
|
+
self._isOpen = False
|
|
150
|
+
self._timer = QtCore.QTimer()
|
|
151
|
+
self._timer.timeout.connect(self.generate_frame)
|
|
152
|
+
self._timer.setInterval(50) # 50 ms interval
|
|
153
|
+
self._noise_level = 0.1 # Default noise level
|
|
154
|
+
|
|
155
|
+
# Signal pattern options
|
|
156
|
+
self.PATTERN_NOISE = 0
|
|
157
|
+
self.PATTERN_STATIC_PEAK = 1
|
|
158
|
+
self.PATTERN_MOVING_PEAK = 2
|
|
159
|
+
self.PATTERN_DRIFT = 3
|
|
160
|
+
self._pattern_type = self.PATTERN_NOISE
|
|
161
|
+
|
|
162
|
+
# Parameters for peaks
|
|
163
|
+
self._peak_width = self._array_length // 12 # Width of Gaussian peak (sigma)
|
|
164
|
+
self._peak_amplitude = 0.8 # Height of peak (0-1)
|
|
165
|
+
self._peak_position = (
|
|
166
|
+
self._array_length // 2
|
|
167
|
+
) # Current position for moving peak
|
|
168
|
+
self._peak_direction = 1 # Direction of movement (1 or -1)
|
|
169
|
+
self._peak_speed = 2 # Pixels per frame
|
|
170
|
+
|
|
171
|
+
# Microscope drift simulation parameters
|
|
172
|
+
self._drift_speed = 0.1 # Base drift speed (pixels per frame)
|
|
173
|
+
self._drift_variation = 0.3 # Random variation in drift
|
|
174
|
+
self._drift_momentum = (
|
|
175
|
+
0.95 # How much previous movement affects current (inertia)
|
|
176
|
+
)
|
|
177
|
+
self._current_drift_velocity = 0.0 # Current drift velocity
|
|
178
|
+
self._vibration_amplitude = 0.3 # Amplitude of high-frequency vibrations
|
|
179
|
+
self._drift_time = 0 # Time counter for drift simulation
|
|
180
|
+
self._random_walk_factor = 0.1 # Factor for random walk component
|
|
181
|
+
|
|
182
|
+
# Initialize fake drift with some variation
|
|
183
|
+
self._drift_center = self._array_length // 2
|
|
184
|
+
self._drift_period = np.random.uniform(
|
|
185
|
+
50, 120
|
|
186
|
+
) # Period in frames for slow drift cycle
|
|
187
|
+
self._drift_phase = np.random.uniform(0, 2 * np.pi) # Random starting phase
|
|
188
|
+
|
|
189
|
+
def isDummy(self) -> bool:
|
|
190
|
+
return True
|
|
191
|
+
|
|
192
|
+
def open(self):
|
|
193
|
+
'''Starts the frame generation timer.'''
|
|
194
|
+
self._isOpen = True
|
|
195
|
+
self._timer.start()
|
|
196
|
+
if self._connect_btn is not None:
|
|
197
|
+
self._connect_btn.setEnabled(False)
|
|
198
|
+
|
|
199
|
+
@property
|
|
200
|
+
def isOpen(self) -> bool:
|
|
201
|
+
'''Returns True if the demo camera is running.'''
|
|
202
|
+
return self._isOpen
|
|
203
|
+
|
|
204
|
+
def close(self):
|
|
205
|
+
'''Stops the frame generation timer.'''
|
|
206
|
+
self._timer.stop()
|
|
207
|
+
self._isOpen = False
|
|
208
|
+
if self._connect_btn is not None:
|
|
209
|
+
self._connect_btn.setEnabled(True)
|
|
210
|
+
|
|
211
|
+
@property
|
|
212
|
+
def array_length(self) -> int:
|
|
213
|
+
'''Get the current array length'''
|
|
214
|
+
return self._array_length
|
|
215
|
+
|
|
216
|
+
def set_array_length(self, length: int) -> None:
|
|
217
|
+
'''Set a new array length'''
|
|
218
|
+
|
|
219
|
+
self._array_length = length if length > 32 else 32
|
|
220
|
+
|
|
221
|
+
# Adjust peak position and width to match new array length
|
|
222
|
+
self._peak_position = self._array_length // 2
|
|
223
|
+
self._peak_width = self._array_length // 12
|
|
224
|
+
|
|
225
|
+
def generate_frame(self):
|
|
226
|
+
'''Generates a frame based on the selected pattern type.'''
|
|
227
|
+
# Base array with zeros
|
|
228
|
+
frame = np.zeros(self._array_length)
|
|
229
|
+
x = np.arange(self._array_length)
|
|
230
|
+
|
|
231
|
+
# Add noise to all patterns
|
|
232
|
+
if self._noise_level > 0:
|
|
233
|
+
noise = np.random.random(self._array_length) * self._noise_level
|
|
234
|
+
frame += noise
|
|
235
|
+
|
|
236
|
+
# Add pattern based on selection
|
|
237
|
+
if self._pattern_type == self.PATTERN_NOISE:
|
|
238
|
+
# Just noise, already added
|
|
239
|
+
pass
|
|
240
|
+
|
|
241
|
+
elif self._pattern_type == self.PATTERN_STATIC_PEAK:
|
|
242
|
+
# Add static Gaussian peak at center
|
|
243
|
+
center = self._array_length // 2
|
|
244
|
+
frame += self._peak_amplitude * np.exp(
|
|
245
|
+
-((x - center) ** 2) / (2 * self._peak_width**2)
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
elif self._pattern_type == self.PATTERN_MOVING_PEAK:
|
|
249
|
+
# Add moving Gaussian peak
|
|
250
|
+
frame += self._peak_amplitude * np.exp(
|
|
251
|
+
-((x - self._peak_position) ** 2) / (2 * self._peak_width**2)
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
# Update peak position for next frame
|
|
255
|
+
self._peak_position += self._peak_speed * self._peak_direction
|
|
256
|
+
|
|
257
|
+
# Reverse direction when reaching edges (with margin)
|
|
258
|
+
edge_margin = max(8, self._array_length // 16)
|
|
259
|
+
if (
|
|
260
|
+
self._peak_position >= self._array_length - edge_margin
|
|
261
|
+
or self._peak_position <= edge_margin
|
|
262
|
+
):
|
|
263
|
+
self._peak_direction *= -1
|
|
264
|
+
|
|
265
|
+
elif self._pattern_type == self.PATTERN_DRIFT:
|
|
266
|
+
# Simulate microscope stage drift
|
|
267
|
+
self._drift_time += 1
|
|
268
|
+
|
|
269
|
+
# Combine multiple drift components:
|
|
270
|
+
|
|
271
|
+
# 1. Slow sinusoidal drift (thermal/mechanical creep)
|
|
272
|
+
slow_drift = np.sin(
|
|
273
|
+
2 * np.pi * self._drift_time / self._drift_period + self._drift_phase
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
# 2. Brownian motion / random walk component
|
|
277
|
+
# (use momentum to avoid jerky movement)
|
|
278
|
+
random_component = np.random.normal(0, self._random_walk_factor)
|
|
279
|
+
self._current_drift_velocity = (
|
|
280
|
+
self._drift_momentum * self._current_drift_velocity
|
|
281
|
+
+ (1 - self._drift_momentum) * random_component
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
# 3. Small high-frequency vibrations (building vibrations, etc)
|
|
285
|
+
vibration = (
|
|
286
|
+
self._vibration_amplitude
|
|
287
|
+
* np.sin(0.1 * self._drift_time)
|
|
288
|
+
* np.random.random()
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
# Combine all drift components
|
|
292
|
+
drift_amount = (
|
|
293
|
+
slow_drift * self._drift_speed
|
|
294
|
+
+ self._current_drift_velocity
|
|
295
|
+
+ vibration
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
# Update drift center with bounds checking
|
|
299
|
+
self._drift_center += drift_amount
|
|
300
|
+
edge_margin = max(10, self._array_length // 10)
|
|
301
|
+
if self._drift_center < edge_margin:
|
|
302
|
+
self._drift_center = edge_margin
|
|
303
|
+
self._current_drift_velocity *= -0.5 # Bounce back with damping
|
|
304
|
+
elif self._drift_center > self._array_length - edge_margin:
|
|
305
|
+
self._drift_center = self._array_length - edge_margin
|
|
306
|
+
self._current_drift_velocity *= -0.5 # Bounce back with damping
|
|
307
|
+
|
|
308
|
+
# Add Gaussian peak at current drift position
|
|
309
|
+
frame += self._peak_amplitude * np.exp(
|
|
310
|
+
-((x - self._drift_center) ** 2) / (2 * self._peak_width**2)
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
# Scale to 0-5V range
|
|
314
|
+
frame = frame * 5.0
|
|
315
|
+
|
|
316
|
+
# Add to buffer
|
|
317
|
+
self.buffer.put(frame)
|
|
318
|
+
|
|
319
|
+
def set_pattern_type(self, pattern_type):
|
|
320
|
+
'''Set the pattern type for the generated frames'''
|
|
321
|
+
self._pattern_type = pattern_type
|
|
322
|
+
|
|
323
|
+
def set_peak_width(self, width):
|
|
324
|
+
'''Set the width (sigma) of the Gaussian peak'''
|
|
325
|
+
self._peak_width = max(1, min(self._array_length // 4, width))
|
|
326
|
+
|
|
327
|
+
def set_peak_amplitude(self, amplitude):
|
|
328
|
+
'''Set the amplitude of the Gaussian peak'''
|
|
329
|
+
self._peak_amplitude = max(0.0, min(1.0, amplitude))
|
|
330
|
+
|
|
331
|
+
def set_peak_speed(self, speed):
|
|
332
|
+
'''Set the speed of the moving peak'''
|
|
333
|
+
self._peak_speed = max(1, min(10, speed))
|
|
334
|
+
|
|
335
|
+
def set_noise_level(self, level):
|
|
336
|
+
'''Set the noise level (0.0 to 1.0)'''
|
|
337
|
+
self._noise_level = max(0.0, min(1.0, level)) # Clamp between 0 and 1
|
|
338
|
+
|
|
339
|
+
def set_drift_speed(self, speed):
|
|
340
|
+
'''Set the base speed of the drift simulation'''
|
|
341
|
+
self._drift_speed = max(0.01, min(1.0, speed))
|
|
342
|
+
|
|
343
|
+
def set_drift_variation(self, variation):
|
|
344
|
+
'''Set the random variation amount in drift simulation'''
|
|
345
|
+
self._random_walk_factor = max(0.01, min(1.0, variation))
|
|
346
|
+
|
|
347
|
+
def set_vibration_amplitude(self, amplitude):
|
|
348
|
+
'''Set the amplitude of high-frequency vibrations'''
|
|
349
|
+
self._vibration_amplitude = max(0.0, min(1.0, amplitude))
|
|
350
|
+
|
|
351
|
+
def getQWidget(self, parent=None) -> QtWidgets.QGroupBox:
|
|
352
|
+
'''Generates a QGroupBox with controls for the demo camera.'''
|
|
353
|
+
group = QtWidgets.QGroupBox('Demo Line Scanner')
|
|
354
|
+
layout = QtWidgets.QVBoxLayout()
|
|
355
|
+
group.setLayout(layout)
|
|
356
|
+
|
|
357
|
+
# Connect/Disconnect buttons
|
|
358
|
+
self._connect_btn = QtWidgets.QPushButton(
|
|
359
|
+
'Start Demo', parent, clicked=lambda: self.open()
|
|
360
|
+
)
|
|
361
|
+
disconnect_btn = QtWidgets.QPushButton(
|
|
362
|
+
'Stop Demo', clicked=lambda: self.close()
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
# Array length control
|
|
366
|
+
array_length_layout = QtWidgets.QHBoxLayout()
|
|
367
|
+
array_length_label = QtWidgets.QLabel('Array Length:')
|
|
368
|
+
array_length_spinbox = QtWidgets.QSpinBox()
|
|
369
|
+
array_length_spinbox.setRange(16, 2048) # Reasonable range
|
|
370
|
+
array_length_spinbox.setValue(self._array_length)
|
|
371
|
+
array_length_spinbox.valueChanged.connect(self.set_array_length)
|
|
372
|
+
array_length_layout.addWidget(array_length_label)
|
|
373
|
+
array_length_layout.addWidget(array_length_spinbox)
|
|
374
|
+
|
|
375
|
+
# Pattern selection
|
|
376
|
+
pattern_layout = QtWidgets.QHBoxLayout()
|
|
377
|
+
pattern_label = QtWidgets.QLabel('Pattern:')
|
|
378
|
+
pattern_combo = QtWidgets.QComboBox()
|
|
379
|
+
pattern_combo.addItems(
|
|
380
|
+
['Noise Only', 'Static Peak', 'Moving Peak', 'Microscope Drift']
|
|
381
|
+
)
|
|
382
|
+
pattern_combo.currentIndexChanged.connect(self.set_pattern_type)
|
|
383
|
+
pattern_layout.addWidget(pattern_label)
|
|
384
|
+
pattern_layout.addWidget(pattern_combo)
|
|
385
|
+
|
|
386
|
+
# Noise level slider
|
|
387
|
+
noise_layout = QtWidgets.QHBoxLayout()
|
|
388
|
+
noise_label = QtWidgets.QLabel('Noise Level:')
|
|
389
|
+
noise_slider = QtWidgets.QSlider(QtCore.Qt.Orientation.Horizontal)
|
|
390
|
+
noise_slider.setMinimum(0)
|
|
391
|
+
noise_slider.setMaximum(100)
|
|
392
|
+
noise_slider.setValue(int(self._noise_level * 100))
|
|
393
|
+
noise_slider.valueChanged.connect(lambda v: self.set_noise_level(v / 100.0))
|
|
394
|
+
noise_layout.addWidget(noise_label)
|
|
395
|
+
noise_layout.addWidget(noise_slider)
|
|
396
|
+
|
|
397
|
+
# Peak width slider
|
|
398
|
+
width_layout = QtWidgets.QHBoxLayout()
|
|
399
|
+
width_label = QtWidgets.QLabel('Peak Width:')
|
|
400
|
+
width_slider = QtWidgets.QSlider(QtCore.Qt.Orientation.Horizontal)
|
|
401
|
+
width_slider.setMinimum(1)
|
|
402
|
+
width_slider.setMaximum(self._array_length // 4)
|
|
403
|
+
width_slider.setValue(self._peak_width)
|
|
404
|
+
width_slider.valueChanged.connect(self.set_peak_width)
|
|
405
|
+
width_layout.addWidget(width_label)
|
|
406
|
+
width_layout.addWidget(width_slider)
|
|
407
|
+
|
|
408
|
+
# Peak amplitude slider
|
|
409
|
+
amp_layout = QtWidgets.QHBoxLayout()
|
|
410
|
+
amp_label = QtWidgets.QLabel('Peak Amplitude:')
|
|
411
|
+
amp_slider = QtWidgets.QSlider(QtCore.Qt.Orientation.Horizontal)
|
|
412
|
+
amp_slider.setMinimum(0)
|
|
413
|
+
amp_slider.setMaximum(100)
|
|
414
|
+
amp_slider.setValue(int(self._peak_amplitude * 100))
|
|
415
|
+
amp_slider.valueChanged.connect(lambda v: self.set_peak_amplitude(v / 100.0))
|
|
416
|
+
amp_layout.addWidget(amp_label)
|
|
417
|
+
amp_layout.addWidget(amp_slider)
|
|
418
|
+
|
|
419
|
+
# Drift speed slider (for microscope drift simulation)
|
|
420
|
+
drift_speed_layout = QtWidgets.QHBoxLayout()
|
|
421
|
+
drift_speed_label = QtWidgets.QLabel('Drift Speed:')
|
|
422
|
+
drift_speed_slider = QtWidgets.QSlider(QtCore.Qt.Orientation.Horizontal)
|
|
423
|
+
drift_speed_slider.setMinimum(1)
|
|
424
|
+
drift_speed_slider.setMaximum(100)
|
|
425
|
+
drift_speed_slider.setValue(int(self._drift_speed * 100))
|
|
426
|
+
drift_speed_slider.valueChanged.connect(
|
|
427
|
+
lambda v: self.set_drift_speed(v / 100.0)
|
|
428
|
+
)
|
|
429
|
+
drift_speed_layout.addWidget(drift_speed_label)
|
|
430
|
+
drift_speed_layout.addWidget(drift_speed_slider)
|
|
431
|
+
|
|
432
|
+
# Drift variation slider
|
|
433
|
+
drift_var_layout = QtWidgets.QHBoxLayout()
|
|
434
|
+
drift_var_label = QtWidgets.QLabel('Drift Randomness:')
|
|
435
|
+
drift_var_slider = QtWidgets.QSlider(QtCore.Qt.Orientation.Horizontal)
|
|
436
|
+
drift_var_slider.setMinimum(1)
|
|
437
|
+
drift_var_slider.setMaximum(100)
|
|
438
|
+
drift_var_slider.setValue(int(self._random_walk_factor * 100))
|
|
439
|
+
drift_var_slider.valueChanged.connect(
|
|
440
|
+
lambda v: self.set_drift_variation(v / 100.0)
|
|
441
|
+
)
|
|
442
|
+
drift_var_layout.addWidget(drift_var_label)
|
|
443
|
+
drift_var_layout.addWidget(drift_var_slider)
|
|
444
|
+
|
|
445
|
+
# Vibration amplitude slider
|
|
446
|
+
vib_layout = QtWidgets.QHBoxLayout()
|
|
447
|
+
vib_label = QtWidgets.QLabel('Vibration:')
|
|
448
|
+
vib_slider = QtWidgets.QSlider(QtCore.Qt.Orientation.Horizontal)
|
|
449
|
+
vib_slider.setMinimum(0)
|
|
450
|
+
vib_slider.setMaximum(100)
|
|
451
|
+
vib_slider.setValue(int(self._vibration_amplitude * 100))
|
|
452
|
+
vib_slider.valueChanged.connect(
|
|
453
|
+
lambda v: self.set_vibration_amplitude(v / 100.0)
|
|
454
|
+
)
|
|
455
|
+
vib_layout.addWidget(vib_label)
|
|
456
|
+
vib_layout.addWidget(vib_slider)
|
|
457
|
+
|
|
458
|
+
# Arrange buttons
|
|
459
|
+
btns = QtWidgets.QHBoxLayout()
|
|
460
|
+
btns.addWidget(self._connect_btn)
|
|
461
|
+
btns.addWidget(disconnect_btn)
|
|
462
|
+
|
|
463
|
+
# Add all layouts
|
|
464
|
+
layout.addLayout(btns)
|
|
465
|
+
layout.addLayout(array_length_layout)
|
|
466
|
+
layout.addLayout(pattern_layout)
|
|
467
|
+
layout.addLayout(noise_layout)
|
|
468
|
+
layout.addLayout(width_layout)
|
|
469
|
+
layout.addLayout(amp_layout)
|
|
470
|
+
layout.addLayout(drift_speed_layout)
|
|
471
|
+
layout.addLayout(drift_var_layout)
|
|
472
|
+
layout.addLayout(vib_layout)
|
|
473
|
+
|
|
474
|
+
return group
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|