A PyQt widget that displays images from a CMMCorePlus instance (pymmcore-plus).
This widget displays images from a single CMMCorePlus instance,
updating the display in real-time as new images are captured.
The image is displayed using PyQt’s QLabel and QPixmap, allowing for efficient
rendering without external dependencies like VisPy.
Parameters
parent : QWidget, optional
The parent widget. Defaults to None.
mmcore : CMMCorePlus
The CMMCorePlus instance from which images will be displayed.
Represents the microscope control core.
use_with_mda : bool, optional
If True, the widget will update during Multi-Dimensional Acquisitions (MDA).
If False, the widget will not update during MDA. Defaults to True.
Attributes
clims : Union[Tuple[float, float], Literal[“auto”]]
The contrast limits for the image display. If set to "auto", the widget will
automatically adjust the contrast limits based on the image data.
cmap : str
The colormap to use for the image display. Currently set to "grayscale".
Notes
-
Image Display: Uses a
QLabelwidget to display the image. The image is set to scale to fit the label size (setScaledContents(True)). -
Image Conversion: Converts images from the
CMMCorePlusinstance touint8and scales them appropriately for display usingQImageandQPixmap. -
Event Handling: Connects to various events emitted by the
CMMCorePlusinstance:imageSnapped: Emitted when a new image is snapped.continuousSequenceAcquisitionStartedandsequenceAcquisitionStarted: Emitted when a sequence acquisition starts.sequenceAcquisitionStopped: Emitted when a sequence acquisition stops.exposureChanged: Emitted when the exposure time changes.frameReady(MDA): Emitted when a new frame is ready during MDA.
-
Thread Safety: Uses a threading lock (
Lock) to ensure thread-safe access to shared resources, such as the current frame. UI updates are performed in the main thread using Qt’s signals and slots mechanism, ensuring thread safety. -
Timer for Updates: A
QTimeris used to periodically update the image from the core. The timer interval can be adjusted based on the exposure time, ensuring that updates occur at appropriate intervals. -
Contrast Limits and Colormap: Allows setting contrast limits (
clims) and colormap (cmap) for the image. Currently, only grayscale images are supported. Theclimscan be set to a tuple(min, max)or"auto"for automatic adjustment. -
Usage with MDA: The
use_with_mdaparameter determines whether the widget updates during Multi-Dimensional Acquisitions. If set toFalse, the widget will not update during MDA runs.
Examples
from pymmcore_plus import CMMCorePlus
from PyQt6.QtWidgets import QApplication, QVBoxLayout, QWidget
# Initialize a CMMCorePlus instance
mmc = CMMCorePlus()
# Set up the application and main window
app = QApplication([])
window = QWidget()
layout = QVBoxLayout(window)
# Create the ImagePreview widget
image_preview = ImagePreview(mmcore=mmc)
# Add the widget to the layout
layout.addWidget(image_preview)
window.show()
# Start the Qt event loop
app.exec()Private Methods
These methods handle internal functionality:
_disconnect(): Disconnects all connected signals from theCMMCorePlusinstance._on_streaming_start(): Starts the streaming timer when a sequence acquisition starts._on_streaming_stop(): Stops the streaming timer when the sequence acquisition stops._on_exposure_changed(device, value): Adjusts the timer interval when the exposure changes._on_streaming_timeout(): Called periodically by the timer to fetch and display new images._on_image_snapped(img): Handles new images snapped outside of sequences._on_frame_ready(event): Handles new frames ready during MDA._display_image(img): Converts and displays the image in the label._adjust_image_data(img): Scales image data touint8for display._convert_to_qimage(img): Converts a NumPy array to aQImagefor display.
Performance Considerations
- Frame Rate: The default timer interval is set to 10 milliseconds. Adjust the interval based on your performance needs.
- Resource Management: Disconnect signals properly by ensuring the
_disconnect()method is called when the widget is destroyed.
Converting Images to QImage
- This is the GPU intensive component of this viewer, and is where any optimization to performance might be done. Images must be scaled to uint8 for QT display.
def _convert_to_qimage(self, img: np.ndarray) -> QImage:
"""Convert a NumPy array to QImage."""
if img is None:
return None
img = self._adjust_image_data(img)
img = np.ascontiguousarray(img)
height, width = img.shape[:2]
if img.ndim == 2:
# Grayscale image
bytes_per_line = width
qimage = QImage(img.data, width, height, bytes_per_line, QImage.Format.Format_Grayscale8)
else:
# Handle other image formats if needed
return None
return qimageThis function is called during the convert_to_qimage( ) method above. Here, the Contrast Limits (Clims) are set
def _adjust_image_data(self, img: np.ndarray) -> np.ndarray:
# NOTE: This is the default implementation for grayscale images
# NOTE: This is the most processor-intensive part of this widget
# Ensure the image is in float format for scaling
img = img.astype(np.float32, copy=False)
# Apply contrast limits
if self._clims == "auto":
min_val, max_val = np.min(img), np.max(img)
else:
min_val, max_val = self._clims
# Avoid division by zero
scale = 255.0 / (max_val - min_val) if max_val != min_val else 255.0
# Scale to 0-255
img = np.clip((img - min_val) * scale, 0, 255).astype(np.uint8, copy=False)
return img