Last active
March 24, 2025 13:22
-
-
Save tlambert03/fa4bfbff612e9decb1f6356873378d98 to your computer and use it in GitHub Desktop.
benchscope
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# /// script | |
# requires-python = "==3.12" | |
# dependencies = [ | |
# "pymmcore-plus", | |
# "pymmcore-widgets", | |
# "ndv[vispy,pyqt]", | |
# ] | |
# /// | |
import numpy as np | |
from pymmcore_plus import CMMCorePlus | |
from ndv import ArrayViewer | |
from pymmcore_widgets import PropertiesWidget, SnapButton, LiveButton | |
from qtpy.QtWidgets import QApplication, QGridLayout, QWidget, QScrollArea, QPushButton | |
from qtpy.QtCore import QTimer, Qt | |
app = QApplication([]) | |
core = CMMCorePlus() | |
try: | |
core.loadDevice("Cam1", "ThorlabsUSBCamera", "ThorCam") | |
core.loadDevice("Cam2", "ThorlabsUSBCamera", "ThorCam") | |
thor = True | |
except RuntimeError: | |
print("No ThorLabs camera found, using demo camera instead.") | |
core.loadDevice("Cam1", "DemoCamera", "DCam") | |
core.loadDevice("Cam2", "DemoCamera", "DCam") | |
thor = False | |
core.loadDevice("MultiCam", "Utilities", "Multi Camera") | |
core.initializeAllDevices() | |
core.setProperty("MultiCam", "Physical Camera 1", "Cam1") | |
core.setProperty("MultiCam", "Physical Camera 2", "Cam2") | |
core.setProperty("Cam1", "Exposure", 15) | |
core.setProperty("Cam2", "Exposure", 15) | |
if thor: | |
core.setProperty("Cam1", "PixelClockMHz", 18) | |
core.setProperty("Cam2", "PixelClockMHz", 18) | |
core.setProperty("Cam1", "HardwareGain", 15) | |
core.setProperty("Cam2", "HardwareGain", 15) | |
core.setProperty("Cam1", "FPS", 10.4) | |
core.setProperty("Cam2", "FPS", 10.4) | |
else: | |
core.setProperty("Cam1", "Mode", "Color Test Pattern") | |
core.setCameraDevice("MultiCam") | |
class ImgPreview(ArrayViewer): | |
def __init__(self, channel: int = 0): | |
self._mmc = CMMCorePlus.instance() | |
imwidth = self._mmc.getImageWidth() | |
imheight = self._mmc.getImageHeight() | |
bpp = self._mmc.getBytesPerPixel() | |
if bpp == 1: | |
dtype = np.uint8 | |
elif bpp == 2: | |
dtype = np.uint16 | |
else: | |
raise ValueError("Unsupported bit depth") | |
self._data = np.zeros((imheight, imwidth), dtype=dtype) | |
super().__init__(self._data) | |
self._viewer_model.show_roi_button = False | |
self._viewer_model.show_3d_button = False | |
self._viewer_model.show_channel_mode_selector = False | |
self._async = False | |
self._channel = channel | |
self.streaming_timer = QTimer() | |
self.streaming_timer.setTimerType(Qt.TimerType.PreciseTimer) | |
self.streaming_timer.setInterval(int(self._mmc.getExposure()) or 100) | |
self.streaming_timer.timeout.connect(self._on_streaming_timeout) | |
ev = self._mmc.events | |
ev.imageSnapped.connect(self._on_image_snapped) | |
ev.continuousSequenceAcquisitionStarted.connect(self._on_streaming_start) | |
ev.sequenceAcquisitionStopped.connect(self._on_streaming_stop) | |
ev.exposureChanged.connect(self._on_exposure_changed) | |
def _on_streaming_start(self) -> None: | |
self.streaming_timer.start() | |
def _on_streaming_stop(self) -> None: | |
self.streaming_timer.stop() | |
def _on_exposure_changed(self, device: str, value: str) -> None: | |
self.streaming_timer.setInterval(int(value)) | |
def _on_image_snapped(self) -> None: | |
self._update_image(self._mmc.getImage(self._channel)) | |
def _on_streaming_timeout(self) -> None: | |
for i in range(2): | |
if self._mmc.getRemainingImageCount() > i: | |
img, md = self._mmc.getNBeforeLastImageAndMD(i) | |
if md["MultiCam-CameraChannelIndex"] == str(self._channel): | |
self._update_image(img) | |
break | |
def _update_image(self, img: np.ndarray) -> None: | |
self._data[:] = img | |
self._request_data() | |
class Main(QWidget): | |
def __init__(self): | |
super().__init__() | |
self.setWindowTitle("Image Preview Example") | |
grid = QGridLayout(self) | |
self._img1 = ImgPreview(0) | |
self._img2 = ImgPreview(1) | |
props = "Gain|Exposure|FPS|MHz" | |
self._prop1 = PropertiesWidget(device_label="Cam1", property_name_pattern=props) | |
self._prop2 = PropertiesWidget(device_label="Cam2", property_name_pattern=props) | |
scroll1 = QScrollArea() | |
scroll1.setWidget(self._prop1) | |
policy = scroll1.sizePolicy() | |
scroll1.setWidgetResizable(True) | |
scroll2 = QScrollArea() | |
scroll2.setWidget(self._prop2) | |
scroll2.setWidgetResizable(True) | |
scroll1.setSizePolicy(policy.Policy.Expanding, policy.Policy.Preferred) | |
scroll2.setSizePolicy(policy.Policy.Expanding, policy.Policy.Preferred) | |
grid.addWidget(SnapButton(), 0, 1) | |
grid.addWidget(LiveButton(), 0, 2) | |
grid.addWidget(self._img1.widget(), 1, 0, 1, 2) | |
grid.addWidget(self._img2.widget(), 1, 2, 1, 2) | |
grid.addWidget(scroll1, 2, 0, 1, 2) | |
grid.addWidget(scroll2, 2, 2, 1, 2) | |
def _reset(self): | |
if self.sender() == self._reset_left: | |
self._img1.view.camera.set_range(margin=0) | |
elif self.sender() == self._reset_right: | |
self._img2.view.camera.set_range(margin=0) | |
main = Main() | |
main.show() | |
core.snapImage() | |
app.exec() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment