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,461 @@
|
|
|
1
|
+
import time
|
|
2
|
+
from queue import Queue
|
|
3
|
+
from typing import Union
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
from pycromanager import (
|
|
7
|
+
Acquisition,
|
|
8
|
+
multi_d_acquisition_events,
|
|
9
|
+
start_headless,
|
|
10
|
+
stop_headless,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
from microEye.hardware.cams.micam import miCamera
|
|
14
|
+
from microEye.hardware.pycromanager.core import DEFAULT_BRIDGE_PORT, PycroCore
|
|
15
|
+
from microEye.hardware.pycromanager.enums import DeviceType, PropertyType
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class PycroDevice:
|
|
19
|
+
def __init__(
|
|
20
|
+
self, label: str, type: DeviceType, port: int = DEFAULT_BRIDGE_PORT
|
|
21
|
+
) -> None:
|
|
22
|
+
if not isinstance(type, DeviceType):
|
|
23
|
+
raise ValueError(f'Invalid device type: {type}')
|
|
24
|
+
|
|
25
|
+
self._port = port
|
|
26
|
+
|
|
27
|
+
if label is None or not isinstance(label, str):
|
|
28
|
+
if type == DeviceType.CameraDevice:
|
|
29
|
+
label = self._core.get_camera_device()
|
|
30
|
+
elif type == DeviceType.StageDevice:
|
|
31
|
+
label = self._core.get_focus_device()
|
|
32
|
+
elif type == DeviceType.XYStageDevice:
|
|
33
|
+
label = self._core.get_xy_stage_device()
|
|
34
|
+
elif type == DeviceType.AutoFocusDevice:
|
|
35
|
+
label = self._core.get_auto_focus_device()
|
|
36
|
+
elif type == DeviceType.GalvoDevice:
|
|
37
|
+
label = self._core.get_galvo_device()
|
|
38
|
+
elif type == DeviceType.SLMDevice:
|
|
39
|
+
label = self._core.get_slm_device()
|
|
40
|
+
elif type == DeviceType.ShutterDevice:
|
|
41
|
+
label = self._core.get_shutter_device()
|
|
42
|
+
else:
|
|
43
|
+
raise ValueError(
|
|
44
|
+
f'Unsupported device type: {type}. Please provide a valid label.'
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
if self._core.get_device_type(label) != type:
|
|
48
|
+
raise ValueError(f'Label "{label}" is not a {type.name} device')
|
|
49
|
+
|
|
50
|
+
self._label = label
|
|
51
|
+
self._type = type
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def _core(self) -> PycroCore:
|
|
55
|
+
return PycroCore.instance(self._port)
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def device_type(self) -> DeviceType:
|
|
59
|
+
return self._type
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def label(self) -> str:
|
|
63
|
+
return self._label
|
|
64
|
+
|
|
65
|
+
@property
|
|
66
|
+
def busy(self) -> bool:
|
|
67
|
+
return self._core.device_busy(self._label)
|
|
68
|
+
|
|
69
|
+
@property
|
|
70
|
+
def name(self) -> float:
|
|
71
|
+
return self._core.get_device_name(self._label)
|
|
72
|
+
|
|
73
|
+
@name.setter
|
|
74
|
+
def name(self, value: str):
|
|
75
|
+
return
|
|
76
|
+
print('Setting PycroDevice name is not supported')
|
|
77
|
+
|
|
78
|
+
def wait_for_device(self):
|
|
79
|
+
self._core.wait_for_device(self._label)
|
|
80
|
+
|
|
81
|
+
def get_prop_names(self) -> list[str]:
|
|
82
|
+
return self._core.get_device_property_names(self._label)
|
|
83
|
+
|
|
84
|
+
def get_prop_values(self) -> dict:
|
|
85
|
+
return self._core.get_system_state()[self._label]
|
|
86
|
+
|
|
87
|
+
def has_property(self, prop_name: str) -> bool:
|
|
88
|
+
return self._core.has_property(self._label, prop_name)
|
|
89
|
+
|
|
90
|
+
def get_property(self, prop_name: str):
|
|
91
|
+
prop_value = self._core.get_property(self.label, prop_name)
|
|
92
|
+
prop_type = self.get_property_type(prop_name)
|
|
93
|
+
|
|
94
|
+
if prop_type == PropertyType.Undef:
|
|
95
|
+
return prop_value
|
|
96
|
+
|
|
97
|
+
python_type = prop_type.to_python()
|
|
98
|
+
|
|
99
|
+
return python_type(prop_value)
|
|
100
|
+
|
|
101
|
+
def get_property_type(self, prop_name: str) -> PropertyType:
|
|
102
|
+
return self._core.get_property_type(self.label, prop_name)
|
|
103
|
+
|
|
104
|
+
def set_property(self, prop_name: str, prop_value):
|
|
105
|
+
self._core.set_property(self.label, prop_name, prop_value)
|
|
106
|
+
|
|
107
|
+
def is_property_read_only(self, prop_name: str) -> bool:
|
|
108
|
+
return self._core.is_property_read_only(self.label, prop_name)
|
|
109
|
+
|
|
110
|
+
def get_allowed_property_values(self, prop_name: str) -> list:
|
|
111
|
+
return self._core.get_allowed_property_values(self.label, prop_name)
|
|
112
|
+
|
|
113
|
+
def get_property_limits(self, prop_name: str) -> tuple:
|
|
114
|
+
return self._core.get_property_limits(self.label, prop_name)
|
|
115
|
+
|
|
116
|
+
def property_tree(self):
|
|
117
|
+
tree = []
|
|
118
|
+
for prop in self.get_prop_names():
|
|
119
|
+
prop_type = self.get_property_type(prop)
|
|
120
|
+
|
|
121
|
+
if prop_type == PropertyType.Undef:
|
|
122
|
+
continue
|
|
123
|
+
|
|
124
|
+
python_type = prop_type.to_python()
|
|
125
|
+
|
|
126
|
+
prop_value = python_type(self.get_property(prop))
|
|
127
|
+
|
|
128
|
+
prop_read_only = self.is_property_read_only(prop)
|
|
129
|
+
|
|
130
|
+
prop_limits = self.get_property_limits(prop)
|
|
131
|
+
|
|
132
|
+
prop_allowed_values = self.get_allowed_property_values(prop)
|
|
133
|
+
|
|
134
|
+
# if any prop_limits is None, it means that the property has no limits
|
|
135
|
+
if any(limit is None for limit in prop_limits):
|
|
136
|
+
prop_limits = None
|
|
137
|
+
|
|
138
|
+
if prop_allowed_values:
|
|
139
|
+
prop_limits = [python_type(value) for value in prop_allowed_values]
|
|
140
|
+
python_type = list
|
|
141
|
+
|
|
142
|
+
tree.append(
|
|
143
|
+
{
|
|
144
|
+
'name': prop,
|
|
145
|
+
'type': python_type.__name__,
|
|
146
|
+
'value': prop_value,
|
|
147
|
+
'enabled': not prop_read_only,
|
|
148
|
+
'limits': prop_limits,
|
|
149
|
+
}
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
return tree
|
|
153
|
+
|
|
154
|
+
def __str__(self) -> str:
|
|
155
|
+
return (
|
|
156
|
+
f'Device Type: {self.device_type.name}, '
|
|
157
|
+
f'Label: {self.label}, Name: {self.name}'
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
class PycroCamera(PycroDevice, miCamera):
|
|
162
|
+
def __init__(self, label: str = None, port: int = DEFAULT_BRIDGE_PORT) -> None:
|
|
163
|
+
PycroDevice.__init__(self, label, DeviceType.CameraDevice, port)
|
|
164
|
+
miCamera.__init__(self)
|
|
165
|
+
# self._last_image = np.zeros((self.height, self.width))
|
|
166
|
+
# self._buffer = buffer if buffer is not None else Queue()
|
|
167
|
+
|
|
168
|
+
@property
|
|
169
|
+
def image_count(self) -> int:
|
|
170
|
+
return self._core.get_remaining_image_count()
|
|
171
|
+
|
|
172
|
+
@property
|
|
173
|
+
def width(self) -> int:
|
|
174
|
+
return self._core.get_image_width()
|
|
175
|
+
|
|
176
|
+
@width.setter
|
|
177
|
+
def width(self, value: int):
|
|
178
|
+
raise NotImplementedError('Cannot set camera width')
|
|
179
|
+
|
|
180
|
+
@property
|
|
181
|
+
def height(self) -> int:
|
|
182
|
+
return self._core.get_image_height()
|
|
183
|
+
|
|
184
|
+
@height.setter
|
|
185
|
+
def height(self, value: int):
|
|
186
|
+
raise NotImplementedError('Cannot set camera height')
|
|
187
|
+
|
|
188
|
+
@property
|
|
189
|
+
def ROI(self) -> tuple[int, int, int, int]:
|
|
190
|
+
return self._core.get_roi()
|
|
191
|
+
|
|
192
|
+
def set_roi(self, roi_x, roi_y, roi_width, roi_height):
|
|
193
|
+
self._core.set_roi(roi_x, roi_y, roi_width, roi_height, self.label)
|
|
194
|
+
|
|
195
|
+
def reset_roi(self):
|
|
196
|
+
self._core.clear_roi()
|
|
197
|
+
|
|
198
|
+
@property
|
|
199
|
+
def bit_depth(self) -> int:
|
|
200
|
+
return self._core.get_image_bit_depth()
|
|
201
|
+
|
|
202
|
+
@property
|
|
203
|
+
def bytes_per_pixel(self) -> int:
|
|
204
|
+
return int(np.ceil(self.bit_depth / 8))
|
|
205
|
+
|
|
206
|
+
@bytes_per_pixel.setter
|
|
207
|
+
def bytes_per_pixel(self, value: int):
|
|
208
|
+
pass
|
|
209
|
+
|
|
210
|
+
@property
|
|
211
|
+
def exposure_current(self) -> float:
|
|
212
|
+
return self._core.get_exposure(self._label)
|
|
213
|
+
|
|
214
|
+
@exposure_current.setter
|
|
215
|
+
def exposure_current(self, value: float):
|
|
216
|
+
self._core.set_exposure(value, self.label)
|
|
217
|
+
|
|
218
|
+
def setExposure(self, exposure: float):
|
|
219
|
+
self._core.set_exposure(exposure, self.label)
|
|
220
|
+
|
|
221
|
+
@property
|
|
222
|
+
def exposure_range(self) -> tuple[float, float]:
|
|
223
|
+
try:
|
|
224
|
+
return self.get_property_limits('Exposure')
|
|
225
|
+
except Exception as e:
|
|
226
|
+
return (0.1, 10000)
|
|
227
|
+
|
|
228
|
+
@exposure_range.setter
|
|
229
|
+
def exposure_range(self, value: tuple[float, float]):
|
|
230
|
+
return
|
|
231
|
+
print('PycroCamera: Setting exposure range is not supported')
|
|
232
|
+
|
|
233
|
+
def get_temperature(self) -> float:
|
|
234
|
+
try:
|
|
235
|
+
for prop in self.get_prop_names():
|
|
236
|
+
if 'temperature' in prop.lower():
|
|
237
|
+
return float(self.get_property(prop))
|
|
238
|
+
except Exception as e:
|
|
239
|
+
print(f'Error getting temperature: {e}')
|
|
240
|
+
finally:
|
|
241
|
+
return -127.0 # noqa: B012
|
|
242
|
+
|
|
243
|
+
@property
|
|
244
|
+
def pixel_size_um(self) -> float:
|
|
245
|
+
return self._core.get_pixel_size_um()
|
|
246
|
+
|
|
247
|
+
def get_property(self, prop_name: str):
|
|
248
|
+
return self._core.get_property(self.label, prop_name)
|
|
249
|
+
|
|
250
|
+
def set_property(self, prop_name: str, prop_value):
|
|
251
|
+
self._core.set_property(self.label, prop_name, prop_value)
|
|
252
|
+
|
|
253
|
+
def get_image(self) -> np.ndarray:
|
|
254
|
+
return self._core.get_image().reshape((self.height, self.width))
|
|
255
|
+
|
|
256
|
+
# def get_image_buffer(self) -> np.ndarray:
|
|
257
|
+
# return self._buffer.get().reshape((cam.height, cam.width))
|
|
258
|
+
|
|
259
|
+
def pop_image(self) -> np.ndarray:
|
|
260
|
+
return self._core.pop_next_image().reshape((self.height, self.width))
|
|
261
|
+
|
|
262
|
+
def get_tagged_image(self) -> tuple[np.ndarray, dict[str, str]]:
|
|
263
|
+
tagged = self._core.get_tagged_image()
|
|
264
|
+
return tagged.pix, tagged.tags
|
|
265
|
+
|
|
266
|
+
def snap_image(self, buffered=False):
|
|
267
|
+
if self.is_sequence_running():
|
|
268
|
+
print('Sequence already running')
|
|
269
|
+
return None
|
|
270
|
+
|
|
271
|
+
self.start_sequence_acquisition(0, 1)
|
|
272
|
+
|
|
273
|
+
# add timeout to avoid infinite loop
|
|
274
|
+
start = time.time()
|
|
275
|
+
while self.image_count == 0:
|
|
276
|
+
time.sleep(0.001)
|
|
277
|
+
|
|
278
|
+
if time.time() - start > 5:
|
|
279
|
+
print('Timeout: Could not snap image')
|
|
280
|
+
return None
|
|
281
|
+
|
|
282
|
+
img: np.ndarray = self.pop_image()
|
|
283
|
+
# if self._last_image is None or not np.allclose(self._last_image, img):
|
|
284
|
+
# self._last_image = img.copy()
|
|
285
|
+
# if buffered:
|
|
286
|
+
# self._buffer.put(img)
|
|
287
|
+
|
|
288
|
+
return img
|
|
289
|
+
|
|
290
|
+
def start_continuous_acquisition(self, interval_ms: int = 0):
|
|
291
|
+
if self._core.is_sequence_running():
|
|
292
|
+
print('Sequence already running')
|
|
293
|
+
return
|
|
294
|
+
|
|
295
|
+
self._core.start_continuous_sequence_acquisition(interval_ms)
|
|
296
|
+
|
|
297
|
+
def is_sequence_running(self) -> bool:
|
|
298
|
+
return self._core.is_sequence_running(self._label)
|
|
299
|
+
|
|
300
|
+
def start_sequence_acquisition(self, interval_ms: float, num_time_points: int):
|
|
301
|
+
if self.is_sequence_running():
|
|
302
|
+
print('Sequence already running')
|
|
303
|
+
return
|
|
304
|
+
|
|
305
|
+
self._core.prepare_sequence_acquisition(self._label)
|
|
306
|
+
self._core.start_sequence_acquisition(
|
|
307
|
+
num_time_points, interval_ms, True, self._label
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
def stop_sequence_acquisition(self):
|
|
311
|
+
self._core.stop_sequence_acquisition(self._label)
|
|
312
|
+
|
|
313
|
+
def set_as_camera_device(self):
|
|
314
|
+
self._core.set_camera_device(self._label)
|
|
315
|
+
|
|
316
|
+
def populate_status(self):
|
|
317
|
+
self.status['Camera'] = {
|
|
318
|
+
'Class': self.__class__.__name__,
|
|
319
|
+
'Name': self._label,
|
|
320
|
+
'Type': self._type.name,
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
self.status['Exposure'] = {
|
|
324
|
+
'Value': self.exposure_current,
|
|
325
|
+
'Unit': 'ms',
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
class PycroStage(PycroDevice):
|
|
330
|
+
def __init__(self, label: str = None, port: int = DEFAULT_BRIDGE_PORT) -> None:
|
|
331
|
+
super().__init__(label, DeviceType.StageDevice, port=port)
|
|
332
|
+
|
|
333
|
+
@property
|
|
334
|
+
def position(self) -> float:
|
|
335
|
+
return self._core.get_position(self.label)
|
|
336
|
+
|
|
337
|
+
@position.setter
|
|
338
|
+
def position(self, value: float):
|
|
339
|
+
self._core.set_position(value, self.label)
|
|
340
|
+
|
|
341
|
+
def move_rel(self, step: float):
|
|
342
|
+
self._core.set_relative_position(step, self.label)
|
|
343
|
+
|
|
344
|
+
def move_abs(self, position: float):
|
|
345
|
+
self._core.set_position(position, self.label)
|
|
346
|
+
|
|
347
|
+
def home(self):
|
|
348
|
+
return self._core.home(self.label)
|
|
349
|
+
|
|
350
|
+
def stop(self):
|
|
351
|
+
return self._core.stop(self.label)
|
|
352
|
+
|
|
353
|
+
def set_adapter_origin_z(self, new_z_um: float):
|
|
354
|
+
self._core.set_adapter_origin(new_z_um, self.label)
|
|
355
|
+
|
|
356
|
+
def set_origin(self):
|
|
357
|
+
self._core.set_origin(self.label)
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
class PycroXYStage(PycroDevice):
|
|
361
|
+
def __init__(self, label: str = None, port: int = DEFAULT_BRIDGE_PORT) -> None:
|
|
362
|
+
super().__init__(label, DeviceType.XYStageDevice, port=port)
|
|
363
|
+
|
|
364
|
+
@property
|
|
365
|
+
def X(self) -> float:
|
|
366
|
+
return self._core.get_x_position(self.label)
|
|
367
|
+
|
|
368
|
+
@property
|
|
369
|
+
def Y(self) -> float:
|
|
370
|
+
return self._core.get_y_position(self.label)
|
|
371
|
+
|
|
372
|
+
@X.setter
|
|
373
|
+
def X(self, value: float):
|
|
374
|
+
self._core.set_xy_position(value, self.Y, self.label)
|
|
375
|
+
|
|
376
|
+
@Y.setter
|
|
377
|
+
def Y(self, value: float):
|
|
378
|
+
self._core.set_xy_position(self.X, value, self.label)
|
|
379
|
+
|
|
380
|
+
def move_rel(self, x: float, y: float):
|
|
381
|
+
self._core.set_relative_xy_position(x, y, self.label)
|
|
382
|
+
|
|
383
|
+
def move_abs(self, x: float, y: float):
|
|
384
|
+
self._core.set_xy_position(x, y, self.label)
|
|
385
|
+
|
|
386
|
+
def home(self):
|
|
387
|
+
return self._core.home(self.label)
|
|
388
|
+
|
|
389
|
+
def stop(self):
|
|
390
|
+
return self._core.stop(self.label)
|
|
391
|
+
|
|
392
|
+
def set_adapter_origin(self, new_x_um: float, new_y_um: float):
|
|
393
|
+
self._core.set_adapter_origin_xy(new_x_um, new_y_um, self.label)
|
|
394
|
+
|
|
395
|
+
def set_origin(self):
|
|
396
|
+
self._core.set_origin_xy(self.label)
|
|
397
|
+
|
|
398
|
+
def set_as_xy_stage_device(self):
|
|
399
|
+
self._core.set_xy_stage_device(self.label)
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
if __name__ == '__main__':
|
|
403
|
+
import os
|
|
404
|
+
import time
|
|
405
|
+
|
|
406
|
+
import cv2
|
|
407
|
+
|
|
408
|
+
mm_path = 'C:/Program Files/Micro-Manager-2.0'
|
|
409
|
+
config = 'MMConfig_demo.cfg'
|
|
410
|
+
port = 1234
|
|
411
|
+
|
|
412
|
+
try:
|
|
413
|
+
start_headless(
|
|
414
|
+
mm_path,
|
|
415
|
+
config,
|
|
416
|
+
port=port,
|
|
417
|
+
# python_backend=True,
|
|
418
|
+
)
|
|
419
|
+
|
|
420
|
+
cam = PycroCamera('Camera', port=port)
|
|
421
|
+
cam.exposure_current = 50
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
cam.start_sequence_acquisition(0, 500) # 0 ms interval for fastest acquisition
|
|
425
|
+
|
|
426
|
+
# def show_image(image, metadata):
|
|
427
|
+
# cv2.imshow('Image', image)
|
|
428
|
+
# cv2.waitKey(1)
|
|
429
|
+
|
|
430
|
+
# with Acquisition(show_display=False, image_process_fn=show_image) as acq:
|
|
431
|
+
# events = multi_d_acquisition_events(
|
|
432
|
+
# 2000,
|
|
433
|
+
# ) # events for 2000 time points
|
|
434
|
+
# for dic in events:
|
|
435
|
+
# dic['camera'] = 'DCam_1'
|
|
436
|
+
# acq.acquire(events)
|
|
437
|
+
|
|
438
|
+
i = 0
|
|
439
|
+
while cam.is_sequence_running() or cam.image_count > 0:
|
|
440
|
+
# Check if there are images before trying to get one
|
|
441
|
+
if cam.image_count > 0:
|
|
442
|
+
img = cam.pop_image()
|
|
443
|
+
i += 1
|
|
444
|
+
cv2.imshow('', img)
|
|
445
|
+
|
|
446
|
+
key = cv2.waitKey(1)
|
|
447
|
+
if key == ord('q'):
|
|
448
|
+
cam.stop_sequence_acquisition()
|
|
449
|
+
|
|
450
|
+
print(
|
|
451
|
+
f'Remaining images: {cam.image_count:04d}',
|
|
452
|
+
f'| Iterations : {i:04d}',
|
|
453
|
+
end='\r',
|
|
454
|
+
)
|
|
455
|
+
|
|
456
|
+
cv2.destroyAllWindows()
|
|
457
|
+
except Exception as e:
|
|
458
|
+
import traceback
|
|
459
|
+
traceback.print_exc()
|
|
460
|
+
finally:
|
|
461
|
+
PycroCore._instances[port].close()
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
# ActionType
|
|
5
|
+
class ActionType(Enum):
|
|
6
|
+
NoAction = 0
|
|
7
|
+
BeforeGet = 1
|
|
8
|
+
AfterSet = 2
|
|
9
|
+
IsSequenceable = 3
|
|
10
|
+
AfterLoadSequence = 4
|
|
11
|
+
StartSequence = 5
|
|
12
|
+
StopSequence = 6
|
|
13
|
+
|
|
14
|
+
# DeviceType
|
|
15
|
+
class DeviceType(Enum):
|
|
16
|
+
UnknownType = 0
|
|
17
|
+
AnyType = 1
|
|
18
|
+
CameraDevice = 2
|
|
19
|
+
ShutterDevice = 3
|
|
20
|
+
StateDevice = 4
|
|
21
|
+
StageDevice = 5
|
|
22
|
+
XYStageDevice = 6
|
|
23
|
+
SerialDevice = 7
|
|
24
|
+
GenericDevice = 8
|
|
25
|
+
AutoFocusDevice = 9
|
|
26
|
+
CoreDevice = 10
|
|
27
|
+
ImageProcessorDevice = 11
|
|
28
|
+
SignalIODevice = 12
|
|
29
|
+
MagnifierDevice = 13
|
|
30
|
+
SLMDevice = 14
|
|
31
|
+
HubDevice = 15
|
|
32
|
+
GalvoDevice = 16
|
|
33
|
+
|
|
34
|
+
# PropertyType
|
|
35
|
+
class PropertyType(Enum):
|
|
36
|
+
Undef = 0
|
|
37
|
+
String = 1
|
|
38
|
+
Float = 2
|
|
39
|
+
Integer = 3
|
|
40
|
+
|
|
41
|
+
def to_python(self):
|
|
42
|
+
if self == PropertyType.String:
|
|
43
|
+
return str
|
|
44
|
+
elif self == PropertyType.Float:
|
|
45
|
+
return float
|
|
46
|
+
elif self == PropertyType.Integer:
|
|
47
|
+
return int
|
|
48
|
+
else:
|
|
49
|
+
return None
|
|
50
|
+
|
|
51
|
+
@classmethod
|
|
52
|
+
def to_dict(cls):
|
|
53
|
+
return {member.value: member for member in cls}
|
|
54
|
+
|
|
55
|
+
@classmethod
|
|
56
|
+
def from_java(cls, prop_jv):
|
|
57
|
+
value = prop_jv.swig_value() if hasattr(prop_jv, 'swig_value') else int(prop_jv)
|
|
58
|
+
members = cls.to_dict()
|
|
59
|
+
if value in members:
|
|
60
|
+
return members[value]
|
|
61
|
+
else:
|
|
62
|
+
raise ValueError('Invalid PropertyType value')
|
|
63
|
+
|
|
64
|
+
# PortType
|
|
65
|
+
class PortType(Enum):
|
|
66
|
+
InvalidPort = 0
|
|
67
|
+
SerialPort = 1
|
|
68
|
+
USBPort = 2
|
|
69
|
+
HIDPort = 3
|
|
70
|
+
|
|
71
|
+
# FocusDirection
|
|
72
|
+
class FocusDirection(Enum):
|
|
73
|
+
Unknown = 0
|
|
74
|
+
TowardSample = 1
|
|
75
|
+
AwayFromSample = 2
|
|
76
|
+
|
|
77
|
+
# DeviceNotification
|
|
78
|
+
class DeviceNotification(Enum):
|
|
79
|
+
Attention = 0
|
|
80
|
+
Done = 1
|
|
81
|
+
StatusChanged = 2
|
|
82
|
+
|
|
83
|
+
# DeviceDetectionStatus
|
|
84
|
+
class DeviceDetectionStatus(Enum):
|
|
85
|
+
Unimplemented = -2
|
|
86
|
+
Misconfigured = -1
|
|
87
|
+
CanNotCommunicate = 0
|
|
88
|
+
CanCommunicate = 1
|
|
89
|
+
|
|
90
|
+
# DeviceInitializationState
|
|
91
|
+
class DeviceInitializationState(Enum):
|
|
92
|
+
Uninitialized = 0
|
|
93
|
+
InitializedSuccessfully = 1
|
|
94
|
+
InitializationFailed = 2
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
# MetadataProfiles
|
|
98
|
+
class MetadataProfile(Enum):
|
|
99
|
+
Essential = 0
|
|
100
|
+
CameraLabel = 1
|
|
101
|
+
BitDepth = 2
|
|
102
|
+
ROIAndBinning = 3
|
|
103
|
+
Timing = 4
|
|
104
|
+
CameraSpecificTags = 5
|
|
105
|
+
PixelSize = 6
|
|
106
|
+
LegacyAcqEngMetadata = 7
|
|
107
|
+
SystemStateCache = 8
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
import yaml
|
|
6
|
+
from mmpycorex.launcher import (
|
|
7
|
+
_JAVA_HEADLESS_SUBPROCESSES,
|
|
8
|
+
_PYMMCORES,
|
|
9
|
+
active_java_ports,
|
|
10
|
+
is_java_port_allocated,
|
|
11
|
+
logger,
|
|
12
|
+
server_terminated,
|
|
13
|
+
terminate_java_instances,
|
|
14
|
+
)
|
|
15
|
+
from pycromanager.headless import (
|
|
16
|
+
DEFAULT_BRIDGE_PORT,
|
|
17
|
+
start_headless,
|
|
18
|
+
stop_headless,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class HeadlessInstance:
|
|
24
|
+
'''Data class to store information about a headless instance.'''
|
|
25
|
+
|
|
26
|
+
port: int
|
|
27
|
+
mm_app_path: str
|
|
28
|
+
config_file: Optional[str] = None
|
|
29
|
+
java_loc: Optional[str] = None
|
|
30
|
+
python_backend: bool = False
|
|
31
|
+
core_log_path: str = ''
|
|
32
|
+
buffer_size_mb: int = 1024
|
|
33
|
+
max_memory_mb: int = 2000
|
|
34
|
+
debug: bool = False
|
|
35
|
+
name: str = 'Unnamed Instance'
|
|
36
|
+
|
|
37
|
+
def __post_init__(self):
|
|
38
|
+
'''Generate a name if none was provided.'''
|
|
39
|
+
if self.name == 'Unnamed Instance':
|
|
40
|
+
backend = 'Python' if self.python_backend else 'Java'
|
|
41
|
+
self.name = f'{backend}_Backend_{self.port}'
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class HeadlessManager:
|
|
45
|
+
'''Manager class for headless Micro-Manager instances.'''
|
|
46
|
+
|
|
47
|
+
# Singleton instance
|
|
48
|
+
_instance = None
|
|
49
|
+
|
|
50
|
+
def __new__(cls):
|
|
51
|
+
if cls._instance is None:
|
|
52
|
+
cls._instance = super().__new__(cls)
|
|
53
|
+
cls._instance._initialized = False
|
|
54
|
+
return cls._instance
|
|
55
|
+
|
|
56
|
+
def __init__(self):
|
|
57
|
+
if self._initialized:
|
|
58
|
+
return
|
|
59
|
+
self._initialized = True
|
|
60
|
+
self._instances: dict[int, HeadlessInstance] = {}
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def instances(self) -> dict[int, HeadlessInstance]:
|
|
64
|
+
'''Get a dictionary of all running instances.'''
|
|
65
|
+
return self._instances.copy()
|
|
66
|
+
|
|
67
|
+
@property
|
|
68
|
+
def ports(self) -> list[int]:
|
|
69
|
+
'''Get a list of all ports in use.'''
|
|
70
|
+
return list(self._instances.keys())
|
|
71
|
+
|
|
72
|
+
def is_port_used(self, port: int) -> bool:
|
|
73
|
+
'''Check if a port is already in use.'''
|
|
74
|
+
return port in self._instances
|
|
75
|
+
|
|
76
|
+
def start_instance(self, instance_config: HeadlessInstance) -> bool:
|
|
77
|
+
'''Start a new headless instance with the given configuration.'''
|
|
78
|
+
if self.is_port_used(instance_config.port):
|
|
79
|
+
return False
|
|
80
|
+
|
|
81
|
+
try:
|
|
82
|
+
start_headless(
|
|
83
|
+
mm_app_path=instance_config.mm_app_path,
|
|
84
|
+
config_file=instance_config.config_file,
|
|
85
|
+
java_loc=instance_config.java_loc,
|
|
86
|
+
python_backend=False, # instance_config.python_backend,
|
|
87
|
+
core_log_path=instance_config.core_log_path,
|
|
88
|
+
buffer_size_mb=instance_config.buffer_size_mb,
|
|
89
|
+
max_memory_mb=instance_config.max_memory_mb,
|
|
90
|
+
port=instance_config.port,
|
|
91
|
+
debug=instance_config.debug,
|
|
92
|
+
)
|
|
93
|
+
self._instances[instance_config.port] = instance_config
|
|
94
|
+
return True
|
|
95
|
+
except Exception as e:
|
|
96
|
+
print(f'Failed to start headless instance: {e}')
|
|
97
|
+
return False
|
|
98
|
+
|
|
99
|
+
def stop_instance(self, port: int) -> bool:
|
|
100
|
+
'''Stop a specific headless instance by port.'''
|
|
101
|
+
if not self.is_port_used(port):
|
|
102
|
+
return False
|
|
103
|
+
|
|
104
|
+
try:
|
|
105
|
+
terminate_java_instances(debug=self._instances[port].debug, port=port)
|
|
106
|
+
del self._instances[port]
|
|
107
|
+
return True
|
|
108
|
+
except Exception as e:
|
|
109
|
+
print(f'Failed to stop headless instance: {e}')
|
|
110
|
+
return False
|
|
111
|
+
|
|
112
|
+
def stop_all_instances(self, debug=False) -> None:
|
|
113
|
+
'''Stop all running headless instances.'''
|
|
114
|
+
try:
|
|
115
|
+
terminate_java_instances(debug=debug)
|
|
116
|
+
self._instances.clear()
|
|
117
|
+
return True
|
|
118
|
+
except Exception as e:
|
|
119
|
+
print(f'Failed to stop headless instances: {e}')
|
|
120
|
+
return False
|
|
121
|
+
|
|
122
|
+
def get_instance(self, port: int) -> Optional[HeadlessInstance]:
|
|
123
|
+
'''Get instance information for a specific port.'''
|
|
124
|
+
return self._instances.get(port)
|
|
125
|
+
|
|
126
|
+
def save_config(self, filepath: str) -> bool:
|
|
127
|
+
'''Save the current configuration to a file.'''
|
|
128
|
+
# Implementation for saving configs to JSON/YAML
|
|
129
|
+
try:
|
|
130
|
+
with open(filepath, 'w') as f:
|
|
131
|
+
yaml.dump(self._instances, f)
|
|
132
|
+
return True
|
|
133
|
+
except Exception as e:
|
|
134
|
+
print(f'Failed to save configuration: {e}')
|
|
135
|
+
return False
|
|
136
|
+
|
|
137
|
+
def load_config(self, filepath: str) -> bool:
|
|
138
|
+
'''Load configuration from a file.'''
|
|
139
|
+
# Implementation for loading configs from JSON/YAML
|
|
140
|
+
# Each instance will be a dictionary with the port as the key
|
|
141
|
+
# They should be started and added if not confilicting with existing ones
|
|
142
|
+
try:
|
|
143
|
+
with open(filepath) as f:
|
|
144
|
+
_instances = yaml.load(f, Loader=yaml.FullLoader)
|
|
145
|
+
|
|
146
|
+
for port, instance in _instances.items():
|
|
147
|
+
if not self.is_port_used(port):
|
|
148
|
+
self.start_instance(HeadlessInstance(**instance))
|
|
149
|
+
|
|
150
|
+
return True
|
|
151
|
+
except Exception as e:
|
|
152
|
+
print(f'Failed to load configuration: {e}')
|
|
153
|
+
return False
|