Skip to content

Instantly share code, notes, and snippets.

@tlambert03
Last active March 24, 2025 13:22
Show Gist options
  • Save tlambert03/fa4bfbff612e9decb1f6356873378d98 to your computer and use it in GitHub Desktop.
Save tlambert03/fa4bfbff612e9decb1f6356873378d98 to your computer and use it in GitHub Desktop.
benchscope
# /// 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