redsun components for Mimir,
a portable interferometric scattering microscope
(iSCAT) in development, with a hardware controller by openUC2.
Each piece of hardware runs in its own process, as a redsun service served
over PVAccess by fastcs:
pymmcore-plus drives the
camera and stages, oxiserial the
openUC2 board.
Note
The bundle is a staging ground for redsun: some components may move into
the framework. Expect breaking changes.
Warning
The youseetoo module (openUC2 board) is tested only against a loop://
serial port that answers nothing; the board itself is untested in CI. Test it
locally before relying on it.
In a virtual environment:
uv venv --python 3.11 # or: python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
uv pip install redsun-mimir # or: pip install redsun-mimirFrom source, with uv:
git clone https://github.com/redsun-acquisition/redsun-mimir
cd redsun-mimir
uv syncuv pip install "redsun-mimir[sim]" # or pip
mmcore install --test-adapters # Micro-Manager demo adapters
mimir simOther ways to get the adapters are in the
pymmcore-plus docs.
mimir uc2 runs the session for the real board.
mimir sim running live_stream on the Micro-Manager
demo camera: acquisition controls on the left, the napari viewer in the
centre, detector, light and stage controls on the right.
A session starts and stops each service; its device reaches the service over PVAccess at the service's prefix.
| service | owns | arguments |
|---|---|---|
mmcore-camera |
one Micro-Manager camera | --adapter, --device, --properties |
mmcore-stage |
one Micro-Manager stage | --adapter, --device, --axes |
youseetoo-controller |
the openUC2 board's serial port | --port, --baudrate |
properties lists, comma-separated, the camera properties to publish
besides exposure and roi; none by default.
services:
transport: pv-access
camera1_ioc:
plugin_name: redsun-mimir
plugin_id: mmcore-camera
prefix: "MIMIR-CAM1:"
args:
adapter: DemoCamera
device: DCam
properties: Binning
storage:
base_dir: "D:/mimir-data" # optional; the user data directory otherwiseA device names its service, so the prefix is written once:
mmcamera: Annotated[AsDevice[MMCamera], Declare(service="camera1_ioc")]MimirApp, a QtSession, declares the presenters, views and napari hook and
links them in wire(). The two examples subclass it with their devices and
session file:
from pathlib import Path
from typing import Annotated
from redsun import AsDevice, Declare
from redsun_mimir.device import MockLightDevice
from redsun_mimir.device.mmcore import MMCamera, MMStage
class MimirSimulator(MimirApp):
config = Path(__file__).parent / "full_configuration.yaml"
mmcamera: Annotated[AsDevice[MMCamera], Declare(service="camera1_ioc")]
XY: Annotated[AsDevice[MMStage], Declare(service="xy_stage")]
laser: AsDevice[MockLightDevice]The session reads these annotations at run time, so import the classes they
name normally, not under if TYPE_CHECKING:.
Components never name each other's classes:
- A view asks in
setupfor a protocol fromredsun_mimir.protocols:DetectorViewandImageViewforDescribesDetectors.DetectorPresenterasks forDeferrals, whichredsun'sAcquisitionPresentershares. - A presenter asks for
DevicesOf[P]and gets only the devices satisfyingP, such asDetectorProtocolforDetectorPresenter. - The acquisition controls are
redsun's built-in ones:acq_ctrlis anAcquisitionPresenterandacq_widgetanAcquisitionView.DetectorPresenterofferslive_stream,MedianPresenterofferslive_median_scan, andacq_ctrlruns both. - The stage controls are
redsun's built-in positioner:motor_ctrlis aPositionerPresenterandmotor_widgetaPositionerView. It finds the axes of every stage by what they can do, so Mimir declares no motor component of its own. - The light controls are
redsun's built-in ones too:light_ctrlis aLightPresenterandlight_widgetaLightView. A light is a device whoseenabledsignal can be written; one with anintensitysignal also gets a slider.
ImageView embeds a napari viewer and takes its stylesheet from the
application. Two hook providers give it one. NapariApplication creates the
application napari would build (create_application), and redsun's
NapariStyle puts napari's stylesheet on it (configure_application), dark
or light as the session's colour scheme reads. The layer list and layer
controls change with it, and the canvas stays black. The shipped sessions
declare both. A session built only from a file needs a hooks: section:
hooks:
create_application:
provider: "redsun_mimir.hooks:NapariApplication"
configure_application:
provider: napari
kwargs:
font_size: 9Without the second one the viewer works, but the layer list, layer controls and other Qt widgets keep the default look.
The session classes make their links in wire(). A session built only from a
file lists them under wiring:, each signal mapped to one slot or a list.
Without it the components link to nothing. Never add it to a file that backs a
session class with wire(): every link would be made twice.
wiring:
det_ctrl.sig_new_data: img_widget.update_layers
median_ctrl.median: img_widget.update_layers
median_ctrl.filtered: img_widget.update_layers
det_widget.sig_property_changed: det_ctrl.set
det_ctrl.sig_new_configuration:
- det_widget.on_new_configuration
- img_widget.on_new_configuration
img_widget.sig_roi_drawn: det_widget.on_roi_drawn
det_widget.sig_roi_selection: img_widget.set_roi_selection
det_widget.sig_roi_edited: img_widget.set_roi_box
acq_ctrl.sig_plan_started: median_ctrl.clear_medians
acq_ctrl.sig_locks_changed:
- det_widget.set_locked
- motor_ctrl.set_locked
- motor_widget.set_locked
- light_ctrl.set_locked
- light_widget.set_locked
pairs:
- [motor_widget, motor_ctrl]
- [light_widget, light_ctrl]
- [acq_widget, acq_ctrl]Names are the keys under devices:, presenters: and views:, then the
signal or slot. Each pairs: entry makes the links between a built-in view
and its presenter: nine for the positioner, which redsun's guide to moving
devices by hand lists, seven for the lights and twelve for the acquisition.
acq_ctrl names the files of each run through the session's path provider,
and passes the actions of DetectorPresenter and MedianPresenter to the
view itself, so neither needs a link.
- Live acquisition, shown with
napari. - Region of interest: Select ROI opens an editor and a box over the detector's layer. Drag the box or type the numbers; Full selects the whole sensor, OK applies. A change asked for during a plan lands between two of its messages.
- Background removal by the median of a square scan, following this
paper:
MedianPresenterofferslive_median_scan. The scan's frames are written beside the next capture as<detector>_scan; the median stays in memory. - Progress bars on the acquisition page, for a scan and for a capture of a set number of frames or of frames until stopped.
- Each capture and scan is a run of its own, nested in the live run; its start document names the run it serves and the scan it follows.
- A run gets the document callbacks its plan lists plus those the user leaves ticked, and no others.
- Zarr v3 storage with
acquire-zarr, written by the camera's service. - Manual control of light sources, and of the stages through
redsun's positioner: steps, go-to and saved positions, stop. - The session log in its own window, from View > Logs.
- Extensible with further
redsuncomponents.
Contributions are welcome. Run the tests with pytest and keep coverage at least where it is.
Apache Software License 2.0.
redsun-mimir is free and open source software.
File an issue with a detailed description.