Software Setup โ ImSwitch, HIK SDK & Plugin¶
This page is the long form of the Get started list on the overview page: everything that has to be installed, in which order, and how to verify each step before moving on.
How the pieces fit together¶
The recording plugin does not talk to the camera directly. ImSwitch owns the camera and publishes every live frame into the napari viewer; the plugin reads its frames from that napari layer and drives the ESP32 (LEDs, sensors) itself over the serial port:
HIK MV-CS013-60GN โโGigEโโโถ MVS SDK โโโถ ImSwitch (HikCamManager)
โ
โผ live image
napari layer
โ layer.data
โผ
Nematostella Timelapse Recording โโโถ HDF5 / Zarr
โ
โผ USB serial (COMx)
ESP32 โโโถ IR / White LED, DHT22
Two consequences drive the whole installation:
- ImSwitch, napari and the plugin run in the same Python process, so they
must be installed into the same environment โ here the conda environment
imswitch21. - All requirements must be installed before ImSwitch is started for the first time. Installing packages into a running environment does not retrofit them into the running ImSwitch/napari session, and a half-installed environment is the most common cause of "the plugin does not show up in the Plugins menu".
Prerequisites¶
| Component | Requirement |
|---|---|
| Operating system | Windows 10/11, Linux or macOS (Windows is the reference setup) |
| Python | 3.12 in a dedicated conda environment โ the analysis plugin requires โฅ 3.12, everything else is happy there |
| Camera | Hik Robotics MV-CS013-60GN (GigE, NIR) โ see Hardware & Assembly |
| Camera driver | Hikrobot MVS SDK (provides the runtime the HikCamManager binds to) |
| Network | Gigabit NIC for the camera, camera and adapter in the same subnet |
| Controller | ESP32 DevKit with the project firmware flashed |
Step 1 โ Create the imswitch21 conda environment¶
Verify that the environment is really the active one before installing anything:
Activate before every start
conda activate imswitch21 is required for every later start of ImSwitch,
napari and for every pip install that belongs to this setup. A second
environment with a partial install is the usual reason the plugin or the HIK
camera silently disappears.
Step 2 โ Install the Hik Robotics SDK (MVS)¶
ImSwitch's HikCamManager is a thin wrapper around Hikrobot's machine-vision
runtime. Without the SDK installed, ImSwitch starts but the detector stays empty.
- Download the MVS package for your operating system from the
Hikrobot download center
(Windows installer, Linux
.tar.gz/.deb, macOS.pkg). - Install it before ImSwitch. The package provides the MVS client
application, the GenICam runtime, the GigE driver and the Python bindings
(
MvImport), and sets the environment variable the runtime is located through โMVCAM_COMMON_RUNENV, pointing at the SDK'sDevelopmentfolder. (MVCAM_SDK_PATHmay stay empty; that is not a problem.) - Reboot (Windows) or open a new shell so those variables are visible inside the conda environment.
Verify the camera before touching ImSwitch¶
Open the MVS client that ships with the SDK. The camera must be listed and must open in live view there. If it does not, ImSwitch cannot open it either โ fix it here first:
| Symptom | Check |
|---|---|
| Camera not listed | Camera IP and NIC IP in the same subnet (e.g. NIC 192.168.1.10, camera 192.168.1.101, mask 255.255.255.0) |
| Listed but unreachable | Firewall blocking GigE discovery, or a second NIC on the same subnet |
| Frames drop or tear | Enable jumbo frames (MTU 9000) on the camera NIC, use a Cat-6 cable, disable NIC power saving |
| Very dark image | Expected โ the IR imager is dark without the LEDs running; use the plugin's LED control |
macOS on Apple Silicon
The MVS SDK for macOS is x86_64-only. Run the whole stack in an x86_64 environment (Rosetta 2), otherwise the SDK libraries will not load.
Step 3 โ Install ImSwitch¶
This setup uses our own fork of ImSwitch, branch nematostella-rig. It is
openUC2's ImSwitch 2.1.191 (commit 8b424d5) plus three commits this rig needs,
and it provides both the HikCamManager detector and the UC2 ESP32Manager:
conda activate imswitch21
git clone -b nematostella-rig https://github.com/s1alknau/ImSwitch.git
cd ImSwitch
pip install -e .
Do not clone openUC2/ImSwitch directly
Three things fail with upstream:
- The Qt GUI aborts on startup. Signals are assigned in
__init__, butpyqtSignalonly binds as a class attribute:AttributeError: 'pyqtSignal' object has no attribute 'connect' fcntlis missing on Windows (imcontrol/model/io/session.py).setLaserGalvo()does not exist. napari-lsft calls this endpoint over the HTTP API to start the light-sheet galvo at acquisition start.
Every change is documented commit by commit and mirrored as patch files
under imswitch-patches/.
Choose a Qt binding and install the GUI stack¶
pip install -e . installs no GUI packages at all โ neither a Qt binding
nor the layers ImSwitch's windows are built on. Pick a binding:
pip install "PySide6==6.8.3" # Qt 6.8.3 - recommended
# or: pip install -e ".[PyQt5]" # Qt 5.15.2, pulls the GUI stack below with it
PyQt5 shears the client area diagonally on some dual-GPU machines. With PySide6,
site-packages/PyQt5 must be fully removed - pyqtgraph treats even an
emptied directory as an installed binding and then picks the wrong one.
The binding alone is not enough. On the PySide6 path, add the packages
ImSwitch imports on startup (the PyQt5 extra already contains them):
| Package | Where ImSwitch needs it |
|---|---|
qtpy |
imcommon/framework/qt.py โ the abstraction layer over the binding, imported before anything else |
napari |
imcommon/view/guitools/naparitools.py โ the camera view is an embedded napari viewer, and it is the layer this plugin reads |
pyqtgraph |
plot widgets in imcontrol/view/widgets/ |
qdarkstyle |
imcommon/view/MultiModuleWindow.py โ the main window stylesheet |
ModuleNotFoundError: No module named 'qtpy'
This is what a missing GUI stack looks like โ ImSwitch starts, prints its version and then dies during the import chain:
Starting ImSwitch using version: 2.1.191
File ".../imswitch/imcommon/framework/qt.py", line 2, in <module>
from qtpy import QtCore
ModuleNotFoundError: No module named 'qtpy'
Install the packages above (Step 5 also brings qtpy and napari in as
plugin dependencies) and start ImSwitch again.
Decompile psygnal¶
The wheel ships mypyc-compiled modules. With those active, napari's plugin discovery dies inside ImSwitch:
TypeError: 'object' object is not subscriptable
File "src\psygnal\_signal.py", line 1456, in resume
File ".../npe2/_plugin_manager.py", line 301, in discover
The tell-tale sign is the log line Could not enable signal emission for
logging: interpreted classes cannot inherit from compiled. psygnal ships an
official switch for exactly this โ it renames the .pyd/.so files to
*_BAK and is reversible with recompile():
python -c "import psygnal.utils; psygnal.utils.decompile()"
python -c "import psygnal; print(psygnal._compiled)" # False
(openUC2 recommend pip install psygnal --no-binary :all: instead; that works
too but has to build from source.)
Linux: Qt platform plugin
If the imports succeed but the GUI aborts with
qt.qpa.plugin: Could not load the Qt platform plugin "xcb", install the
system libraries the binding links against and make sure a display is
available (echo $DISPLAY):
Do not start ImSwitch yet
Install the plugin (Step 5) first โ every requirement has to be in place before the first start. ImSwitch creates its configuration folder on that first start; create it now so the setup file from Step 4 can already be placed:
~/ImSwitchConfig/ (C:\Users\<you>\ImSwitchConfig on Windows)
โโโ imcontrol_setups/ โ the setup JSON goes here (Step 4)
โโโ config/ โ modules.json lives here
โโโ ...
It is the home directory, not Documents
Older ImSwitch versions kept this folder under Documents/. This one
resolves it to the home directory
(storage_paths.py),
unless --config-folder points elsewhere. If both folders exist on your
machine, the one under Documents/ is a leftover and is ignored.
Ready-made configurations
The openUC2/ImSwitchConfig
repository contains further example setups. You can clone it into the
ImSwitchConfig folder and drop the file from Step 4 in next to the others.
Step 4 โ Add the camera setup JSON to imcontrol_setups¶
The setup file for this imager lives in the repository at
Json+cam_manager/example_uc2_ddorf_hik_imager_IR.json:
{
"rs232devices": {
"ESP32": {
"managerName": "ESP32Manager",
"managerProperties": {
"host_": "192.168.43.129",
"serialport": "COM4",
"debug": 0,
"baudrate": 115200
}
}
},
"detectors": {
"WidefieldCamera": {
"managerName": "HikCamManager",
"managerProperties": {
"isRGB": false,
"cameraListIndex": 0,
"cameraEffPixelsize": 3.45,
"hikcam": {
"exposure": 50.0,
"gain": 0.0,
"blacklevel": 0,
"trigger_source": "Continous",
"exposure_mode": "manual",
"frame_rate": 30
}
},
"forAcquisition": true
}
},
"availableWidgets": ["Settings", "View", "Recording", "Image"]
}
Copy it into the setups folder:
Then select it in ImSwitch as the active setup (Settings โ setup file) and restart ImSwitch so the setup is loaded.
What the fields mean¶
| Field | Meaning |
|---|---|
detectors.WidefieldCamera.managerName |
HikCamManager โ the MVS-SDK-backed detector. The napari layer that shows up later is named after this detector. |
cameraListIndex |
Index into the list of detected HIK cameras. 0 is correct for a single camera; increase it if several are attached. |
cameraEffPixelsize |
Effective pixel size in ยตm (3.45 ยตm for the MV-CS013), used for scale bars. |
isRGB |
false โ the NIR sensor is monochrome. |
hikcam.exposure |
Exposure in ms. The plugin reads the live value back from ImSwitch, so set exposure in ImSwitch, not in the plugin. |
hikcam.gain / blacklevel |
Keep at 0 for calibrated recordings โ LED calibration assumes a fixed camera response. |
hikcam.trigger_source |
Continous (free-running). LED/exposure synchronization is done by the ESP32 sync pulse, not by a hardware camera trigger. |
hikcam.exposure_mode |
manual โ auto exposure would defeat the brightness validation during recording. |
rs232devices.ESP32.serialport |
Serial port of the ESP32 โ COMx on Windows, /dev/ttyUSB0 on Linux. |
rs232devices.ESP32.baudrate |
115200, matching the project firmware. |
availableWidgets |
Which ImSwitch panels are shown. Settings, View, Recording and Image are enough for this workflow. |
Only one process can hold the ESP32 serial port
The plugin opens the ESP32 port itself, and serial ports are exclusive: if
ImSwitch's ESP32 rs232 device already holds COM4, the plugin's
Connect fails (and vice versa). If you drive the ESP32 exclusively from
the plugin โ the normal case for this workflow โ either point ImSwitch's
serialport at an unused port or remove the rs232devices block from your
copy of the setup file. Also close any serial monitor (Arduino IDE, PuTTY)
before connecting.
Step 5 โ Install the napari plugins¶
Before the first ImSwitch start
Do this step before starting ImSwitch for the first time. The plugins
bring qtpy and napari in as dependencies, so a missing step here is the
usual cause of the No module named 'qtpy' crash in
Step 3.
None of the three plugins is published on PyPI โ all install from source, into
the same imswitch21 environment:
Timelapse recording with the HIK camera and the ESP32 โ the plugin this page is about.
git clone https://github.com/s1alknau/Nematostella-time-series.git
cd Nematostella-time-series
pip install -e .
# optional: pip install -e ".[opencv]" # Live Analysis tab (HoughCircles ROI)
zarr (Zarr recording format, read-while-write live analysis) is already a
core dependency.
Activity and circadian analysis of the recorded HDF5/Zarr files โ napari-hdf5-activity.
git clone https://github.com/s1alknau/napari-hdf5-activity.git
cd napari-hdf5-activity
pip install -e ".[zarr]"
This is the plugin that sets the Python floor: it requires โฅ 3.12, which is why Step 1 creates a 3.12 environment.
Light-sheet control and reconstruction โ napari-lsft. It drives the galvo through ImSwitch's HTTP API, which is one of the reasons this rig needs the fork from Step 3.
git clone https://github.com/s1alknau/napari-lsft.git
cd napari-lsft
pip install -e ".[control,stream]"
control adds UC2-REST for direct ESP32 access, stream the Socket.IO live
frame stream. daheng (Galaxy SDK) is only needed for a Daheng camera.
Verify that all three are importable and registered with napari:
python -c "import timeseries_capture, napari_hdf5_activity, napari_lsft; print('ok')"
python -c "from npe2 import PluginManager; pm = PluginManager(); pm.discover(); print([w.display_name for m in pm._manifests.values() for w in (m.contributions.widgets or [])])"
Expected widgets: Nematostella Timelapse Recording, HDF5 Activity Analysis, LSFT Reconstruction, LSFT Control & Acquisition.
Step 6 โ First launch¶
-
Activate the environment and start ImSwitch:
-
Load the
example_uc2_ddorf_hik_imager_IRsetup. - Start the live view โ a napari layer with the camera image appears
(
Live: WidefieldCameraor similar). The plugin auto-detects layers whose name containsLive:,Widefield,CameraorDetector. - Open Plugins โ Nematostella Timelapse Recording in the napari viewer.
- In the ESP32 Connection tab, click Connect โ the status turns green.
- Run an LED calibration before the first recording; for light/dark experiments follow the Circadian Protocol.
- Configure duration, interval and output folder, then start the recording.
Without a HIK camera
Steps 2โ4 are only needed for the ImSwitch/HIK GigE path. The plugin also
runs in a plain napari session with the other supported camera adapters โ
only the ESP32 is mandatory.
Dry run without hardware
The installation can be checked before the imager is built: with no camera
attached, HikCamManager falls back to a mock camera, and ImSwitch starts
into its GUI with the same setup file (log: Initialized camera, model:
mock โฆ init done). Point it at a scratch configuration so your real one
stays untouched:
Optional: multi-camera configuration¶
If a camera_system.json is present, the plugin adds its multi-camera panels on
startup. It is searched for in this order:
camera_system.jsonin the current working directory~/.imswitch/camera_system.jsoncamera_system.jsonnext to the installed package
Use camera_system_example.json
as a template โ it lists per-camera IP, ESP32 port and the default recording
configuration. Without such a file the plugin runs in single-camera mode.
Troubleshooting¶
| Message / symptom | Cause and fix |
|---|---|
ModuleNotFoundError: No module named 'qtpy' while starting ImSwitch |
The GUI stack is missing โ pip install -e . installs no Qt packages. Install the binding and qtpy napari pyqtgraph qdarkstyle, see Step 3. |
ModuleNotFoundError for napari, pyqtgraph or qdarkstyle |
Same cause, same fix โ or simply run Step 5 (the plugin pulls qtpy and napari in) before starting ImSwitch. |
Fatal Python error: Segmentation fault in launchApp |
QtWebEngine without a shared GL context โ fixed in nematostella-rig, git pull the ImSwitch clone. See Segfault when the GUI starts. |
qt.qpa.plugin: Could not load the Qt platform plugin "xcb" (Linux) |
Missing system libraries or no display โ see the note in Step 3. |
Failed to load module: KeyError: / KeyError: 'WidefieldCamera' |
No camera attached: HikCamManager falls back to its mock, which used to reject the "Continous" spelling the real camera uses. Fixed in nematostella-rig โ git pull the ImSwitch clone. With the fix, the documented setup starts on the mock and the GUI comes up without hardware. |
| Modal dialog "It appears that Jupyter Notebook isn't where it usually is" | The imnotebook module wants jupyter-lab on PATH. It is off by default in nematostella-rig; an existing ~/ImSwitchConfig/config/modules.json keeps its old value, so set it to ["imcontrol"]. |
pip check: imswitchuc2 requires pydantic==2.11.4, but you have 2.13.4 |
napari pulls the newer pydantic. The exact pin is relaxed in nematostella-rig; ImSwitch runs unchanged with 2.13.4. |
Expecting value: line 1 column 1 / setup file was corrupted |
A BOM in the JSON โ see the warning in Segfault when the GUI starts. |
TypeError: 'object' object is not subscriptable in npe2 / psygnal while ImSwitch builds its window |
The compiled psygnal breaks napari's plugin discovery. Run python -c "import psygnal.utils; psygnal.utils.decompile()", see Step 3. |
| Plugin missing in the Plugins menu | Installed into a different environment. conda activate imswitch21, reinstall, restart napari/ImSwitch. |
No camera layer found (ensure live view is started in ImSwitch) |
The live view is not running, or the layer name contains none of Live:, Widefield, Camera, Detector. Start the live view or rename the layer. |
Zero frame from napari layer (n consecutive) |
The HIK acquisition buffer is in an inconsistent state. The plugin flushes the ImSwitch detector buffer automatically after 5 consecutive zero frames; if it keeps recurring, check MTU/cabling and restart the live view. |
| Camera missing in ImSwitch but visible in MVS | Wrong cameraListIndex, or the MVS client still holds the camera โ close it, only one application can open a GigE camera at a time. |
| Camera missing in MVS as well | Subnet or firewall problem โ see the table in Step 2. |
| ESP32 Connect fails, port busy | Another process holds the port (ImSwitch rs232 device, serial monitor) โ see the warning in Step 4. |
| Exposure in the plugin looks wrong | The plugin reads the exposure back from ImSwitch in ms; change it in ImSwitch's settings, not in the plugin. |
| Frames too dark or too bright | Run an LED calibration and keep gain and blacklevel at 0 so the calibration stays valid. |
Segfault when the GUI starts (Linux, PySide6)¶
ImSwitch loads, the window appears for a moment, then the process dies without a Python exception:
Fatal Python error: Segmentation fault
Current thread (most recent call first):
File ".../qtpy/_utils.py", line 53 in possibly_static_exec
File ".../imswitch/imcommon/applaunch.py", line 169 in launchApp
Extension modules: ... PySide6.QtWebEngineCore, PySide6.QtWebEngineWidgets ...
Fixed in the fork โ update your clone. QtWebEngine requires
Qt.AA_ShareOpenGLContexts, and the attribute only takes effect while no
QApplication exists yet. It used to sit in a PyQt5/PySide2-only block,
while imnotebook โ which pulls QtWebEngineWidgets in โ is imported after
the application object is created. On Qt 6 the web engine then came up without a
shared GL context and took the process down as soon as app.exec_() ran.
nematostella-rig now sets the attribute for every binding
(patch 0003):
If the clone was installed with pip install . instead of pip install -e .,
reinstall afterwards โ a plain install copies the code into site-packages and
does not follow the clone:
nematostella-rig additionally ships imnotebook off by default, so a
fresh installation never loads QtWebEngine in the first place.
Workaround without updating
In an existing configuration the module stays enabled โ modules.json is
only created once. Set ~/ImSwitchConfig/config/modules.json to:
Upstream's default is ["imcontrol", "imscripting", "imnotebook"];
imscripting is dropped by ImSwitch at startup anyway.
Windows: do not write JSON with Set-Content -Encoding utf8
PowerShell 5.1 writes a BOM, and ImSwitch's JSON parser stops at it โ
modules.json fails with Expecting value: line 1 column 1 (char 0), a
setup file is reported as corrupted and has been reset to default values.
Use an editor that saves UTF-8 without BOM, or
[System.IO.File]::WriteAllText($path, $json, (New-Object System.Text.UTF8Encoding($false))).
The copy command in Step 4 is unaffected โ it copies bytes.
If it still segfaults, the display stack is the next suspect:
echo $XDG_SESSION_TYPE # "wayland" is worth ruling out
QT_QPA_PLATFORM=xcb imswitch # force X11 instead of Wayland
LIBGL_ALWAYS_SOFTWARE=1 imswitch # software OpenGL (napari/vispy on old iGPUs)
Whichever of the two makes the crash go away identifies the cause: the first points at the Wayland platform plugin, the second at the OpenGL driver.