Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 108 additions & 0 deletions .github/workflows/build-mm-arm64.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
name: Build Micro-Manager adapters (arm64) & test pymmcore-plus integration

on:
push:
branches: [feat/pymmcore-integration]
tags: ["mm-v*"]
workflow_dispatch:

jobs:
# ---------------------------------------------------------------------
# Run the Python integration tests on x86_64 with the DemoCamera adapter
# downloaded by `mmcore install`.
# ---------------------------------------------------------------------
test-x86:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: "3.11"

- name: Install ImSwitch (no heavy extras) + pymmcore-plus
run: |
python -m pip install --upgrade pip
# Install only what the MMCore tests touch – avoids pulling the full
# ImSwitch dependency chain on CI.
pip install pytest numpy "pymmcore-plus[cli]>=0.10"

- name: Download Micro-Manager DemoCamera adapter
run: |
mmcore install
mmcore list

- name: Make repo importable as a package
run: |
pip install -e . --no-deps || true

- name: Run MMCore manager tests
env:
MICROMANAGER_PATH: ""
run: |
pytest imswitch/imcontrol/_test/unit/test_mmcore_managers.py -v

# ---------------------------------------------------------------------
# Build mmCoreAndDevices for arm64 in an emulated container, then
# publish the resulting .so files as a workflow artifact / GH release.
# ---------------------------------------------------------------------
build-arm64:
runs-on: ubuntu-24.04
permissions:
contents: write
steps:
- uses: actions/checkout@v4

- name: Set up QEMU for arm64 emulation
uses: docker/setup-qemu-action@v3
with:
platforms: arm64

- name: Determine Micro-Manager source ref
id: mm
run: |
# Pin to a known-good tag so device interface version stays stable.
echo "ref=main" >> "$GITHUB_OUTPUT"

- name: Build inside arm64 container
uses: uraimo/run-on-arch-action@v3
with:
arch: aarch64
distro: ubuntu22.04
githubToken: ${{ secrets.GITHUB_TOKEN }}
dockerRunArgs: |
--volume "${{ github.workspace }}:/work"
install: |
apt-get update -qq
DEBIAN_FRONTEND=noninteractive apt-get install -qq -y \
git build-essential autoconf automake libtool pkg-config \
swig python3-dev libboost-all-dev curl ca-certificates
run: |
set -euxo pipefail
cd /work
git clone --depth 1 --branch ${{ steps.mm.outputs.ref }} \
https://github.com/micro-manager/mmCoreAndDevices.git mm-src
cd mm-src
./autogen.sh
./configure --prefix=/work/mm-out --without-java
make -j"$(nproc)"
make install
cd /work
mkdir -p artifact/micro-manager/lib/micro-manager
cp -a /work/mm-out/lib/micro-manager/. artifact/micro-manager/lib/micro-manager/
tar -C artifact -czf micro-manager-arm64.tar.gz micro-manager
ls -lh micro-manager-arm64.tar.gz

- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: micro-manager-arm64
path: micro-manager-arm64.tar.gz
if-no-files-found: error

- name: Publish as GitHub Release
if: startsWith(github.ref, 'refs/tags/mm-v')
uses: softprops/action-gh-release@v2
with:
files: micro-manager-arm64.tar.gz
fail_on_unmatched_files: true
172 changes: 172 additions & 0 deletions docs/pymmcore-integration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
# pymmcore-plus integration

ImSwitch can drive any camera, stage, or laser that has a Micro-Manager
device adapter — Andor, Hamamatsu, Basler, ASI, Prior, Thorlabs, Coherent
and ~250 others — through the [`pymmcore-plus`][pymmcore-plus] Python
bindings to the Micro-Manager `MMCore` C++ library.

There is **no Java**, **no MMStudio**, and **no separate server process**:
`pymmcore-plus` loads the same C++ adapters that MMStudio uses directly
into the ImSwitch Python process.

## What you get

Three new device managers, picked up by the standard `managerName`
mechanism in your setup JSON:

| Manager class | Replaces | Wraps |
|----------------------------|--------------------------|------------------------------------|
| `MMCoreDetectorManager` | Camera-specific manager | `core.snap()`, sequence acquisition |
| `MMCorePositionerManager` | Stage-specific manager | XY + Z stage devices |
| `MMCoreLaserManager` | Laser-specific manager | Shutter or DA property device |

All three share a process-wide `CMMCorePlus` singleton (see
[`MMCoreManager.py`](../imswitch/imcontrol/model/managers/MMCoreManager.py))
so a single `.cfg` file drives every device without USB conflicts.

## Installation

> **Micro-Manager 2.0 is required** (Device API ≥ 70). The `pymmcore` /
> `pymmcore-plus` Python wheels are built against the MM 2.0 device
> interface; MM 1.4 adapters will fail with an *“interface version
> mismatch”* error on load.

```bash
pip install "ImSwitchUC2[pymmcore]"
# or, in a dev checkout:
pip install -e ".[pymmcore]"
```

To install the Micro-Manager 2.0 device adapters themselves, choose the
option that matches your platform:

| Platform | Recommended install |
|----------|--------------------------------------------------------------------------------------|
| Windows | Download MM 2.0 from <https://micro-manager.org/Micro-Manager_Nightly_Builds> – the installer drops adapters in `C:\Program Files\Micro-Manager-2.0` and ImSwitch picks them up automatically. |
| macOS | Download the MM 2.0 nightly `.dmg`; drag to `/Applications/Micro-Manager-2.0`. |
| Linux x86_64 | `pip install "pymmcore-plus[cli]"` then `mmcore install` – downloads the official MM 2.0 adapters into pymmcore-plus' managed directory. |
| Raspberry Pi (arm64) | Build from source via [`install_micromanager_raspi.sh`](../install_micromanager_raspi.sh) or use the prebuilt tarball from the [`build-mm-arm64`](../.github/workflows/build-mm-arm64.yml) workflow. |

### Adapter path discovery

`MMCoreManager.discover_adapter_paths()` resolves adapter directories in
the following order:

1. `MICROMANAGER_PATH` environment variable (override).
2. `pymmcore_plus.find_micromanager()` – knows about `mmcore install`
managed installs and any system installs it can find.
3. Platform-specific MM 2.0 install locations:
* **Windows:** `C:\Program Files\Micro-Manager-2.0*`,
`C:\Program Files (x86)\Micro-Manager-2.0*`
* **macOS:** `/Applications/Micro-Manager-2.0*`,
`/Applications/Micro-Manager.app/Contents/Resources`
* **Linux:** `/opt/micro-manager/lib/micro-manager`,
`/opt/Micro-Manager-2.0*`, `/usr/local/lib/micro-manager`
4. pymmcore-plus' managed install dir on every platform
(`~/.local/share/pymmcore-plus/mm/Micro-Manager-*` and OS-specific
equivalents).

On **Windows**, every resolved directory is also added to the Python
DLL search path via `os.add_dll_directory`, so vendor SDK DLLs co-located
with the adapter (e.g. Andor, Hamamatsu) are found automatically.

You can override the search at any time by setting `MICROMANAGER_PATH`,
or per-device via the `adapterPath` key in the setup JSON.

## Quick start: the DemoCamera setup

The [`example_mmcore_demo.json`](../imswitch/_data/user_defaults/imcontrol_setups/example_mmcore_demo.json)
setup uses the `DemoCamera` adapter that ships with every Micro-Manager
install — no `.cfg` file required.

```bash
imswitch --setup example_mmcore_demo.json
```

You should see a `MMCamera` detector, a `MMStage` XYZ positioner, and
the `MMShutter` laser show up in the UI immediately.

## Using a real camera

Two configuration modes are supported.

### Mode A — write a Micro-Manager `.cfg` file

Configure your hardware once with `MMConfig.exe` (or by hand) and point
ImSwitch at the resulting file. Multiple managers can share the same
`.cfg`; it is loaded only once per process:

```json
{
"detectors": {
"AndorCamera": {
"managerName": "MMCoreDetectorManager",
"managerProperties": {
"cfgPath": "/home/pi/configs/Andor_ASI.cfg",
"deviceLabel": "Andor sCMOS Camera"
},
"forAcquisition": true
}
}
}
```

A complete example lives in
[`example_mmcore_andor.json`](../imswitch/_data/user_defaults/imcontrol_setups/example_mmcore_andor.json).

### Mode B — declare the device inline

Skip `.cfg` files entirely by listing the adapter and device name in the
manager properties — handy for quick demos:

```json
{
"detectors": {
"Cam": {
"managerName": "MMCoreDetectorManager",
"managerProperties": {
"adapterName": "HamamatsuHam",
"deviceName": "HamamatsuHam_DCAM",
"deviceLabel": "Hamamatsu"
},
"forAcquisition": true
}
}
}
```

## Discovering adapters and devices

Use the helpers on `MMCoreManager` to introspect what is installed:

```python
from imswitch.imcontrol.model.managers import MMCoreManager

# All adapters present on disk
print(MMCoreManager.get_available_adapters("/opt/micro-manager/lib/micro-manager"))

# Devices a specific adapter exposes (requires the singleton to be alive)
core = MMCoreManager.get_core()
print(MMCoreManager.get_available_devices_for_adapter("HamamatsuHam"))
```

## Raspberry Pi notes

* Use a Pi 5 with **8 GB RAM** for anything beyond the demo adapter.
* Building `mmCoreAndDevices` from source on the Pi takes ~30 minutes;
prefer the prebuilt tarball from CI.
* Adapters that depend on Windows-only vendor DLLs (e.g. some
AndorSDK3 builds) cannot be used on Linux — check the vendor's docs.

## Known limitations

* No Java, no MMStudio integration.
* `MMCorePositionerManager.moveForever` is a no-op — Micro-Manager has no
generic jog primitive.
* Laser power calibration is the user's responsibility: `mode: "property"`
writes a raw value (typically volts).
* Some adapter properties expose values that don't fit our number/list
parameter widgets; those properties are silently skipped in the UI but
remain settable via `setProperty` calls.

[pymmcore-plus]: https://pymmcore-plus.github.io/pymmcore-plus/
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
{
"detectors": {
"AndorCamera": {
"analogChannel": null,
"digitalLine": null,
"managerName": "MMCoreDetectorManager",
"managerProperties": {
"cfgPath_": "/home/pi/micro-manager-configs/Andor_ASI.cfg",
"cfgPath": "C:\\Users\\benir\\Desktop\\andor.cfg",
"deviceLabel": "Andor sCMOS Camera"
},
Comment on lines +7 to +11
"forAcquisition": true,
"forFocusLock": false
}
},
"lasers": {},
"LEDs": {},
"LEDMatrixs": {},
"positioners": {
"ASIStage": {
"analogChannel": null,
"digitalLine": null,
"managerName": "MMCorePositionerManager",
"managerProperties": {
"cfgPath_": "/home/pi/micro-manager-configs/Andor_ASI.cfg",
"cfgPath": "C:\\Users\\benir\\Desktop\\andor.cfg",
"xyDeviceLabel": "XYStage:XY:31",
"zDeviceLabel": "ZStage:Z:32"
},
"axes": ["X", "Y", "Z"],
"isPositiveDirection": true,
"forPositioning": true,
"forScanning": true,
"resetOnClose": false,
"stageOffsets": {
"stageOffsetPositionX": 0.0,
"stageOffsetPositionY": 0.0,
"stageOffsetPositionZ": 0.0
}
}
},
"rs232devices": {},
"slm": null,
"sim": null,
"dpc": null,
"objective": null,
"mct": null,
"nidaq": {
"timerCounterChannel": null,
"startTrigger": false
},
"roiscan": null,
"lightsheet": null,
"webrtc": null,
"hypha": null,
"Stresstest": {},
"HistoScan": null,
"Workflow": null,
"FlowStop": null,
"Lepmon": null,
"Flatfield": null,
"PixelCalibration": null,
"experiment": null,
"uc2Config": null,
"ism": null,
"focusLock": null,
"fovLock": null,
"autofocus": null,
"scan": null,
"etSTED": null,
"rotators": null,
"microscopeStand": null,
"storage": null,
"pulseStreamer": {
"ipAddress": null
},
"pyroServerInfo": null,
"rois": {},
"ledPresets": {},
"defaultLEDPresetForScan": null,
"laserPresets": {},
"stageOffsets": {},
"defaultLaserPresetForScan": null,
"availableWidgets": [
"Settings",
"View",
"Recording",
"Image",
"Positioner"
],
"nonAvailableWidgets": [],
"designerId": null
}
Loading
Loading