Skip to content

fix: use qtpy instead of importing PyQt5 directly - #40

Open
hongquanli wants to merge 2 commits into
mainfrom
fix/pyqt6-qtpy-migration
Open

hongquanli wants to merge 2 commits into
mainfrom
fix/pyqt6-qtpy-migration

Conversation

@hongquanli

Copy link
Copy Markdown
Collaborator

Problem

ndviewer_light imports PyQt5 directly. Squid embeds this viewer in its own process, and since the napari 0.7 migration (Cephla-Lab/Squid#622) that process is PyQt6-only, so every import fails:

ERROR squid.NDViewerTab: Failed to import ndviewer_light: No module named 'PyQt5'

The NDViewer tab then silently degrades to NDViewer tab unavailable: ndviewer_light module not installed (gui_hcs.py:1226).

Installing PyQt5 alongside is not an option — two Qt bindings in one environment break napari/vispy OpenGL rendering, which setup_22.04.sh calls out explicitly:

Exactly ONE Qt binding may be installed — PyQt5 and PyQt6 in the same environment conflict and break napari/vispy OpenGL rendering.

This is not platform-specific: anyone following setup_22.04.sh on Ubuntu gets a PyQt6-only environment and hits it identically.

Change

Import through qtpy and let the host application supply the binding.

  • ndviewer_light/core.py, simulate_push_acquisition.py, simulate_zarr_acquisition.py, tests/test_3d_visualization.pyPyQt5.*qtpy.*
  • pyproject.toml / environment.yml — declare qtpy instead of pyqt5

Two details worth noting:

  • qtpy exposes Signal, not pyqtSignal, so the two signal declarations in core.py were renamed accordingly.
  • qtpy promotes unscoped enums under PyQt6, so Qt.AlignCenter-style access needed no changes — this is why the diff is as small as it is.

Verification

Under Python 3.12 / PyQt6 6.11.0 / napari 0.7.1:

  • import ndviewer_light succeeds with qtpy.API_NAME == "PyQt6"
  • Squid's test_widgets.py goes from 4 failures to 158 passed
  • Squid GUI starts with no ndviewer/PyQt5 errors in the log

Requires tensorstore at import time, which Squid's setup script installs as of Cephla-Lab/Squid#622.

Squid runs this viewer embedded in its own process, which is PyQt6-only since
the napari 0.7 migration, so `from PyQt5...` raised ImportError and the
NDViewer tab failed to load. Installing PyQt5 alongside is not an option: two
Qt bindings in one environment break napari/vispy OpenGL rendering.

qtpy promotes unscoped enums under PyQt6, so Qt.AlignCenter-style access is
unchanged; only pyqtSignal needed renaming to Signal. The declared dependency
is now qtpy rather than a concrete binding, which the host application supplies.
qtpy resolves a binding at import time and raises QtBindingsNotFoundError when
none is installed, so replacing the pyqt5 dependency with qtpy alone broke every
standalone use: CI collected 14 import errors, and the simulate_* scripts would
fail the same way.

Embedded in Squid the host process already supplies PyQt6, which is why a
concrete binding must not be a hard dependency here -- a second binding in that
environment breaks napari/vispy OpenGL rendering. So it is an extra,
`pip install .[pyqt6]`, mirroring napari[pyqt6], and environment.yml (used by CI
and for standalone work) installs PyQt6 explicitly.
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.

1 participant