Skip to content

Feat/pymmcore integration#264

Open
beniroquai wants to merge 57 commits into
masterfrom
feat/pymmcore-integration
Open

Feat/pymmcore integration#264
beniroquai wants to merge 57 commits into
masterfrom
feat/pymmcore-integration

Conversation

@beniroquai

Copy link
Copy Markdown
Collaborator

This pull request introduces comprehensive support for integrating Micro-Manager device adapters into ImSwitch via the pymmcore-plus Python bindings. It adds detailed documentation, provides example configuration files for various hardware setups, and establishes a new GitHub Actions workflow to build and test Micro-Manager adapters (including on arm64). These changes make it significantly easier to use a wide range of hardware with ImSwitch, both in development and deployment environments.

Micro-Manager integration and documentation:

  • Added a new documentation file, docs/pymmcore-integration.md, that explains how to use ImSwitch with Micro-Manager adapters via pymmcore-plus, including installation steps, configuration modes, adapter discovery, and platform-specific notes.

Example configuration files:

  • Added example_mmcore_demo.json showing a minimal DemoCamera setup, and example_mmcore_andor_cfg.json for inline device declaration, both demonstrating how to configure detectors, lasers, and positioners using the new MMCore manager classes. [1] [2]
  • Added example_mmcore_andor.json showing a setup using a Micro-Manager .cfg file for real hardware (Andor camera and ASI stage), illustrating configuration for both Windows and Linux paths.

CI and build improvements:

  • Introduced a new GitHub Actions workflow (.github/workflows/build-mm-arm64.yml) to build Micro-Manager adapters for arm64, test pymmcore-plus integration, and publish prebuilt binaries as workflow artifacts or GitHub Releases. This facilitates using Micro-Manager on platforms like Raspberry Pi.

Known LImitations:

  • Exposure/Gain changes not working since we do not have a unified interface for camera-related parameters. Frontend writes exposure, MM provides Exposure
  • Builds are currently not working

beniroquai added 2 commits May 1, 2026 10:53
Introduce optional pymmcore-plus integration: add a process-wide MMCoreManager singleton and three MMCore* device managers (detector, positioner, laser) that wrap Micro-Manager adapters. Include unit tests that exercise the DemoCamera adapter, example setup JSON files for demo and Andor configurations, and user documentation for pymmcore-plus integration. Add a GitHub Actions workflow to run MMCore tests and to build/publish arm64 mmCoreAndDevices artifacts, plus a Raspberry Pi install script for building Micro-Manager on arm64. Minor controller comment and pyproject update included.
Improve Micro-Manager (pymmcore) integration and fix several device manager behaviors. Docs: require MM 2.0, add detailed install/discovery guidance and adapter-path resolution. Add new example_mmcore_andor_cfg.json and update example_mmcore_andor.json. Tests: use MMCoreManager.discover_adapter_paths() for adapter availability and clearer skip reason. MMCoreManager: implement discover_adapter_paths(), platform globs, Windows DLL dir handling, adapter-path resolution, MM2 verification, and better logging. LaserController: return [0,1] for binary lasers. DetectorManager: fix parameter name mapping (gain) and avoid raising on unknown parameters. MMCoreDetectorManager: track running/frame number, make getLatestFrame optionally return frame number and handle errors, and update running flag on start/stop. MMCorePositionerManager.move: extend signature to support absolute moves, optional blocking, and emit position update signal after moves.
Copilot AI review requested due to automatic review settings May 2, 2026 05:59

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a new integration layer that lets ImSwitch control Micro-Manager device adapters (camera/stage/laser) via pymmcore-plus, along with example setups, docs, tests, and a CI workflow for arm64 adapter builds.

Changes:

  • Introduces MMCoreManager (shared singleton) plus MMCore*Manager device managers for detectors, positioners, and lasers.
  • Adds documentation, example setup JSONs, and Raspberry Pi install guidance for Micro-Manager adapters.
  • Adds unit tests for the new managers and a GitHub Actions workflow to test on x86_64 and build adapters for arm64.

Reviewed changes

Copilot reviewed 18 out of 18 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
pyproject.toml Adds pymmcore-plus as an optional dependency extra (pymmcore).
imswitch/imcontrol/model/managers/MMCoreManager.py Implements lazy, shared CMMCorePlus singleton + adapter path discovery helpers.
imswitch/imcontrol/model/managers/__init__.py Exposes MMCoreManager from the managers package for imports/tests.
imswitch/imcontrol/model/managers/detectors/MMCoreDetectorManager.py New detector manager wrapping MMCore camera devices, including dynamic property discovery.
imswitch/imcontrol/model/managers/positioners/MMCorePositionerManager.py New positioner manager wrapping MMCore XY/Z stages with cfg/manual loading modes.
imswitch/imcontrol/model/managers/lasers/MMCoreLaserManager.py New laser manager supporting shutter mode and property mode.
imswitch/imcontrol/model/managers/detectors/DetectorManager.py Alters parameter-setting behavior (exposure/gain name mapping).
imswitch/imcontrol/controller/controllers/LaserController.py Changes binary laser range reporting to return [0, 1].
imswitch/imcontrol/controller/controllers/experiment_controller/experiment_normal_mode.py Adds a TODO comment.
imswitch/imcontrol/_test/unit/test_mmcore_managers.py Adds pytest coverage for MMCore managers using DemoCamera (skipped if unavailable).
imswitch/_data/user_defaults/imcontrol_setups/example_mmcore_demo.json Adds DemoCamera-based example setup (manual mode).
imswitch/_data/user_defaults/imcontrol_setups/example_mmcore_andor_cfg.json Adds an Andor inline-declaration example setup.
imswitch/_data/user_defaults/imcontrol_setups/example_mmcore_andor.json Adds cfg-based “real hardware” example setup.
docs/pymmcore-integration.md Adds end-user documentation for installing and using the integration.
.github/workflows/build-mm-arm64.yml Adds CI workflow to test integration on x86 and build arm64 adapters.
install_micromanager_raspi.sh Adds Raspberry Pi build/install script for Micro-Manager + pymmcore stack.
micromanager-userguide.md Adds a standalone usage guide for Micro-Manager + pymmcore-plus.
pymmcore-feature-integraion.md Adds a long implementation-plan style document (currently reads like an internal prompt).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +210 to +221
def getLatestFrame(self, returnFrameNumber=False) -> np.ndarray:
try:
if self._core.getRemainingImageCount() > 0:
return self._core.getLastImage()
except Exception:
pass
try:
self._core.snap()
self._frameNunber += 1
if returnFrameNumber:
return self._core.getImage(), self._frameNunber
return self._core.getImage()
Comment on lines +133 to +136
new_pos = self.getPosition(axis)
self._position[axis] = new_pos
self._commChannel.sigUpdateMotorPosition.emit() # TODO: This is a hacky workaround to force Imswitch to update the motor positions in the gui..

Comment on lines +3 to +7
# install_micromanager_rpi.sh
#
# Build and install Micro-Manager (MMCore + device adapters) and pymmcore-plus
# on a Raspberry Pi running Pi OS Bookworm (64-bit / arm64).
#
Comment on lines +1 to +49
# CLAUDE.md — pymmcore-plus Integration into ImSwitch

You are implementing Micro-Manager hardware support in the openUC2/ImSwitch
microscopy platform via `pymmcore-plus`. This adds support for any camera,
stage, or laser that has a Micro-Manager device adapter (Andor, Hamamatsu,
Basler, ASI, Prior, Thorlabs, Coherent, etc.) alongside the existing
ESP32/UC2-REST managers.


## STEP 0 — Reconnaissance (do this FIRST, before writing any code)

Run these commands and summarize what you find. Paste the summary as a
comment in the first commit.

```bash
# Check for any existing pymmcore/MMCore work
rg -n -S "pymmcore|MMCore|micromanager|micro.manager" --type py

# Map the manager directory structure
find imswitch/imcontrol/model/managers -name "*.py" | head -60

# Read the base classes — you MUST match these signatures exactly
cat imswitch/imcontrol/model/managers/detectors/DetectorManager.py
cat imswitch/imcontrol/model/managers/positioners/PositionerManager.py
cat imswitch/imcontrol/model/managers/lasers/LaserManager.py

# Read one concrete example of each to understand the pattern
cat imswitch/imcontrol/model/managers/detectors/HIKCamManager.py 2>/dev/null || \
ls imswitch/imcontrol/model/managers/detectors/
cat imswitch/imcontrol/model/managers/positioners/ESP32StageManager.py 2>/dev/null || \
ls imswitch/imcontrol/model/managers/positioners/
cat imswitch/imcontrol/model/managers/lasers/ESP32LEDLaserManager.py 2>/dev/null || \
ls imswitch/imcontrol/model/managers/lasers/

# Check how managers are registered / discovered
cat imswitch/imcontrol/model/managers/detectors/__init__.py
cat imswitch/imcontrol/model/managers/positioners/__init__.py
cat imswitch/imcontrol/model/managers/lasers/__init__.py

# Check the setup JSON schema / info classes
rg -n "class DetectorInfo" --type py
rg -n "class LaserInfo" --type py
rg -n "class PositionerInfo" --type py

# Check how lowLevelManagers work
rg -n "lowLevelManagers" imswitch/imcontrol/model/managers/ --type py | head -20

# Look at an existing setup JSON for the structure
find . -name "*.json" -path "*/imcontrol_setups/*" | head -10
Comment on lines +119 to +124
parameters["Exposure"] = DetectorNumberParameter(
group="Acquisition",
value=current_exposure,
editable=True,
valueUnits="ms",
)
Comment on lines +129 to +132
else:
self._logger.warning(f"Ignoring move on unsupported axis '{axis}'")
return self._position[axis]

Comment on lines 229 to 232
if lManager.isBinary:
return None
return [0, 1]
else:
return (lManager.valueRangeMin, lManager.valueRangeMax)
Comment on lines +7 to +11
"managerProperties": {
"cfgPath_": "/home/pi/micro-manager-configs/Andor_ASI.cfg",
"cfgPath": "C:\\Users\\benir\\Desktop\\andor.cfg",
"deviceLabel": "Andor sCMOS Camera"
},
Comment on lines 159 to 162
if name.find("posure")>0:name = "exposure" # TODO: Hacky fix for inconsistent naming
if name.find("ain")>0:name = "gain" # TODO: Hacky fix for inconsistent naming
self.__parameters[name].value = value
return self.parameters
beniroquai and others added 25 commits May 27, 2026 16:36
Introduce a new server API (testDeviceAction) to send device-specific validation commands over serial (motor, ledarray, laser) after flashing. The controller builds JSON commands, writes to the specified serial port, reads a short response with timeout, and returns success/warning/error with the command and response snippet. Add a frontend axios wrapper (apiUC2ConfigControllerTestDeviceAction.js) and integrate a hardware validation panel into UsbFlashWizard.jsx: new UI controls and state for baud, motor/LED/laser params, a runDeviceTest flow that calls the API, and result display with success/warning/error handling and dispatch of user messages. This enables quick verification of slave firmware functionality (movement, LEDs, laser) post-flash.
UsbFlashWizard: include nodeId and canMotorAxis in the CAN activation payload so the sent /can_act message contains address, nodeId and canMotorAxis. Hologram notebook: large overhaul of imswitch/_data/user_defaults/imnotebook/hologram_education_notebook.ipynb — reorganized content and headings, clarified theory, added imswitchclient connection examples, REST helper wrappers for InLineHoloController, local Fresnel reconstruction code, interactive controls and MJPEG stream integration, plus many minor cleanup and documentation improvements. Added a German localized notebook imswitch/_data/user_defaults/imnotebook/hologram_education_notebook_de.ipynb. Also applied a minor update to imswitch/imcontrol/controller/controllers/UC2ConfigController.py.
Add comprehensive streaming review notes and migration/session planning docs, rename featurelist file, and implement multiple streaming-related improvements. New docs: ISSUES_SESSION_PLAN.md, MIGRATION.md, ReviewCameraStreamingIssues.md, STREAMING_4B_NOTES.md, STREAMING_REVIEW.md, and NEWSWITCH_FEATURE_MINDMAP.svg; renamed docs/ImSwitch_Featurelist.md -> docs/NEWSWITCH_FEATURE_INVENTORY.md. Code changes touch frontend WebSocket/LiveView components and backend streaming/recording paths to reduce allocations and improve throughput: preallocated u8/u16 buffers and PyTurboJPEG fallback in LiveViewController; WebRTC producer-side resize; avoid per-client metadata copies in noqt; recording dispatch thread and bounded queue to offload synchronous writes; keep TiffWriter open and add compression/compression_level support in OMETiffWriter; safe HIK SDK flip and robust binning flow; small config hook in UC2ConfigController for enabling qid-done handling. Also update pyproject.toml. These changes aim to improve live-preview latency and recording reliability on large sensors (9MP+) and document migration to the new newswitch stack.
Introduce a full MMCore (Micro-Manager) integration: frontend UI, backend controller, and API helpers to browse/set device parameters and perform long-exposure snap-to-disk jobs.

Frontend: add MMCoreController React component, add API wrappers (getDetectors, getParameters, setParameter, snapToDisk, getSnapStatus), and wire the plugin into App.jsx and appRegistry. UI supports grouped parameter editing, exposure-in-seconds editing, snap-to-disk submission and polled job status display.

Backend: implement imswitch.imcontrol.controller.controllers.MMCoreController exposing REST methods for detector discovery, parameter read/write, background snap jobs (writes TIFFs, reports progress) and job listing/status. Snap jobs run in background threads and emit updates; TimeOut is temporarily raised for long exposures.

Model: update MMCoreDetectorManager.setParameter to handle exposure name variants case-insensitively, set core exposure properly, and echo back actual device-accepted values for UI consistency.

Also update example setup JSONs to enable the MMCore widget.
Introduce frontend and backend changes to support manual ESP32 serial port/baudrate overrides and cancelling USB flashing runs. Adds two API clients (setSerialConfig, cancelUSBFlash), a new ESP32 Serial override panel in ConnectionSettings (list ports, apply session-only or persist), and USB flash UI improvements in UsbFlashWizard: auto-detect master/slave firmware to set disconnect/reconnect defaults, and an action to cancel an ongoing flash with optimistic UI updates. On the backend UC2ConfigManager: add ping(), accept port in initSerial(), implement setSerialConfig() which reconnects and optionally persists the port/baudrate to the setup JSON (with fallbacks for older UC2-REST reconnect signatures) and error handling/logging.
Co-authored-by: Copilot <copilot@github.com>
Frontend: use createImageBitmap (when available) in LiveViewComponent for off-thread JPEG decode, add cancellation for in-flight frames, and keep a legacy img fallback; normalize data URLs and improve error handling.

WebSocketHandler: handle binary msgpack JPEG frames by re-encoding or passing through base64 as needed, and treat "cancelled" as a terminal usb-flash state (surface a cancelled result to the UI).

Backend (noqt.py): avoid per-client metadata copies when packing MessagePack frames, send raw JPEG bytes as binary (msgpack bin) to reduce CPU and wire cost, and keep metadata updates per-client minimal.

LiveViewController / StreamWorker & workers: add StreamParams.broadcast_frames flag to control per-frame fan-out; introduce _JpegEncoder that prefers PyTurboJPEG and falls back to OpenCV; preallocate buffers and optimize uint16→uint8 conversion without allocs; reorder crop/subsample/dtype conversion to perform expensive ops on smaller frames; emit raw JPEG bytes (not base64) to save CPU and bandwidth; move heavy preprocessing for WebRTC frames onto the worker thread (resize, dtype cast, channel layout, even-dim alignment) to reduce aiortc loop work and improve latency.

UC2ConfigController: add reconnect port/baud handling and setSerialConfig API, add strict uc2_board_is_connected ping mode, implement cancellable esptool subprocess management (cancelUSBFlash), cooperative cancel flag and process termination, and reentrant flash handling that cancels a previous run if a new request starts. Emits appropriate cancelled/failed statuses so UI unblocks.

Overall: reduces per-frame CPU and allocations, moves expensive work off hot event loops/main thread, prefers faster JPEG backends, and adds robust cancellable USB flashing for better responsiveness and resource usage.
Multiple frontend and backend changes to add synthetic LED-matrix channels, tiling behaviours, and grouped scan areas:

Frontend:
- CoordinateCalculator: group points by groupId and merge them into single scan areas (buildMergedScanArea) so freehand / multi-FOV selections behave as one area and preserve scan ordering.
- WellSelector & Tiling tabs: keep XY scan speed controls in sync; add UI toggles for "Return to origin" and "Override per-group Z with current Z" (override is disabled while a focus map is active).
- ChannelsDimension & ExperimentDesigner: support syntheticChannels (ring/DPC) advertised separately from conventional illuSources; merge synthetic defs into the UI list but split them back into conventional illumination + syntheticChannels payload when starting experiments; zero out disabled channels and build syntheticChannels payload including RGB/radius/exposure/gain.
- FocusMapDimension: improved "Clear All" to fully disable focus mapping, clear manual points and prevent stale Z re-application.
- Middleware & state: ParameterRangeSlice gains syntheticChannels state + actions; middleware populates syntheticChannels from backend.
- ExperimentSlice: add tiling toggles (returnToOrigin, overrideZWithCurrentZ) and related setters.

Backend:
- models: add SyntheticChannel and SyntheticKind; expose syntheticChannels in ExperimentWorkflowParams and ParameterValue.
- ExperimentController: accept syntheticChannels list (separate from conventional illumination), merge enabled synthetic channels into internal parallel arrays (intensity/gains/exposures) in one place instead of RGB→intensity promotion hacks; add per-experiment flags for focus-map activation and tiling toggles (_focus_map_active, _return_to_origin, _override_z_with_current_z) and honor their precedence (focus map wins over manual override); use experiment-configured XY/Z speeds for stage moves; optionally return to initial XYZ at end of scan.

IO fixes:
- OME writer: do not eagerly create empty tile directories; provide ensure_tiff_dir and ensure base_dir exists before writers/stitcher create files to avoid littering empty folders.

Overall these changes formalise synthetic LED channels as a separate model, add useful tiling options, improve focus-map and grouping behaviour, and tidy filesystem handling for OME outputs.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
beniroquai and others added 30 commits June 4, 2026 22:32
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Introduce true freehand/region-style scan support and move the "override per-group Z with current Z" behavior to the frontend. CoordinateCalculator now consumes neighborPointList for region points, maps neighbors into rawPositions and derives iX/iY when missing so snake/raster ordering works. WellSelectorComponent stores freehand regions as a single point with neighborPointList (one logical scan area) and triggers an objective status refresh on mount to pick up current pixel/FOV. ExperimentSlice persists neighborPointList. ChannelsDimension memoizes merged illumination arrays (illuSources, kinds and min/max) to prevent a re-render loop. ExperimentDesigner applies the Z-override by rewriting position Zs before sending; backend controller/models were simplified to remove server-side override handling and related fields, and minor cleanup/fixes were applied (AutofocusController fetch URL cleanup and user-facing message wording).
Backend: add getLastSnapPreview endpoint that returns a contrast-stretched, downsampled PNG for a finished snap; cache the most recent snaps in memory (bounded) for fast preview rendering; implement percentile stretch and max-dimension downsampling to keep preview generation cheap; introduce Pydantic request models and switch several API methods (setMMCoreParameter, setMMCoreParameters, snapMMCoreToDisk) to use them and return HTTPExceptions on bad input.

Frontend: add apiMMCoreControllerGetLastSnapPreview URL helper, show a passive live preview card (reads live state only) and render the captured frame as an <img> when a job finishes (uses finishedAt as cache-bust). Also import LiveViewControlWrapper and use connection settings from redux.

Misc: update FRAME_6_ANDOR.json defaults (various device/feature additions and reordering) and add a .bak of the file.
Introduce a first-run onboarding tour using intro.js: add OnboardingTour component, CSS, onboardingTour constants, and a Redux OnboardingSlice (with tests). Wire tour anchors by adding data-tour attributes across UI components and expose a "Start Intro Tour" entry in Settings. Persist onboarding state in the store and add intro.js as a frontend dependency. Also improve Windows streaming reliability by setting WindowsSelectorEventLoopPolicy when running on win32 and forcing the websockets backend in the server Uvicorn config to reduce websocket latency and increase FPS on Windows.
Introduce an HTTP MJPEG viewer and UI controls, plus server-side improvements to live parameter handling and Windows websocket behavior.

- Add frontend MJPEGViewer component and wire it into LiveViewControlWrapper to support plain HTTP multipart MJPEG streams (bypassing socket.io). Update StreamControlOverlay and StreamPresets to expose MJPEG settings and selection alongside existing JPEG/WebRTC/Binary options.
- Change LiveViewController to apply stream/detector parameter changes in-place on running workers (mutate worker._params and saved params) instead of stopping/restarting streams to avoid thrash and dropped WebRTC sessions; responses now report updated_detectors/updated_live.
- On server startup (ImSwitchServer), set WindowsSelectorEventLoopPolicy on Windows and force uvicorn ws="websockets" to avoid websocket upgrade/latency issues on Windows clients.
- Loosen python-socketio dependency constraint in pyproject.toml to "python-socketio[asyncio]>=5.11,<6".

These changes improve Windows streaming performance, provide an alternative transport for flaky socket.io websocket upgrades, and reduce restart churn when adjusting stream parameters.
Centralize stream parameter construction and simplify submit flow in StreamControlOverlay: build protocol-specific params from a single draft, push defaults to the backend, atomically swap workers when format changes (stop/start), then update Redux (format, settings, crop/min/max). Mirror backend capabilities, save per-detector settings and perform fire-and-forget backend persist. Improve error messaging formatting.

Increase MAX_FRAME_LAG in noqt.py from 1 to 3 and document rationale to allow more pipelining (reducing Windows latency issues at the cost of a few extra in-flight frames).

Add a diagnostic print in ImSwitchServer to log the actual server event loop type at startup to detect uvicorn overrides of the chosen Windows event loop policy.
Frontend: make parameter inputs use a local draft state and only commit on blur/Enter (or immediate for lists), add helper text, and prevent server refreshes from clobbering mid-edit. Add an "Apply" button for exposure separate from Snap, implement onApplyExposure, and provide a download link for the full-resolution TIFF plus file path display; adjust layout and button sizing. imcommon/framework/noqt.py: relax frame lag allowance (MAX_FRAME_LAG increased to 3). Controller: before snapping, clear the circular buffer and use core.snap() (which returns the image) to avoid double getImage() calls and adapter errors. ArkitektManager: defer optional imports into the constructor (avoid importing arkitekt_next/koil/mikro_next at module import time) so the module can be used when those packages are absent.
Log full exception stacktraces when controller creation fails, and refactor MMCoreDetectorManager.getLatestFrame to robustly handle live sequence vs idle modes. When a sequence is running, read from the circular buffer (getLastImage) and avoid snap(); return a zero placeholder (and -1 frame number) if no frame is available to prevent blocking. When no sequence is running, prefer the buffered last image, otherwise call snap(), using np.asarray to normalize returned images and avoid extra getImage() calls (fixes errors with some adapters like Andor). Add debug/error logging and maintain the frame number handling.
Install required system libraries and mirror test.yml by installing ImSwitch with full deps so runtime imports (Options/ViewSetupInfo) succeed. Force a pure-Python psygnal (uninstall/reinstall specific version and run psygnal.utils.decompile()), install pymmcore-plus[cli], and remove the editable pip install step. Adjust pytest invocation to disable the arkitekt_next plugin (-p no:arkitekt_next) and shorten tracebacks (--tb=short). These changes ensure CI has the native libs and dependency layout needed for the MMCore manager tests.
Expand pymmcore integration docs with architecture diagrams (simplified and detailed Mermaid flowcharts) explaining the MMCoreManager singleton, managerName routing, and how pymmcore-plus/pymmcore/adapters interact. Add a comprehensive Raspberry Pi (arm64) section describing the prebuilt micro-manager-arm64.tar.gz artifact, device-interface-version compatibility rules, where to obtain the tarball, and how ImSwitch discovers adapters. Provide two reproducible deployment recipes: Recipe A (bake adapters into Docker image with a build-micromanager.sh script and Dockerfile snippet, build pymmcore from source) and Recipe B (bind-mount adapters at runtime), plus a short sanity-check and usage examples for headless Docker runs.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants