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
16 changes: 10 additions & 6 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,17 @@ jobs:
- name: Run the GUI test in its own process
# Own invocation: the full-application GUI test leaves Qt/thread state
# behind that deterministically segfaults later Qt tests (e.g.
# test_channel_sequence) when run in the same pytest process.
# Step-scoped pyqt5 pin: without it, pytest-qt picks PySide6 for qtbot
# while the app code (qtpy) uses PyQt5, and the mixed bindings fail
# with a QObject double-init error.
# test_channel_sequence) when run in the same pytest process: pytest-qt
# deleteLater()s each window at teardown, and destroying the full GUI
# crashes once a later test's event loop flushes that delete, so this
# file must be the last thing its process does (and hard-exit).
# Step-scoped pyqt6 pin: keeps pytest-qt's qtbot on the same binding as
# the app code (qtpy, QT_API=pyqt6). Mixed bindings fail with a QObject
# double-init error, and pointing this at a binding that is not installed
# (e.g. pyqt5, removed by setup_22.04.sh) aborts pytest at startup.
run: python3 -m pytest tests/control/test_HighContentScreeningGui.py
working-directory: ./software
env:
QT_API: pyqt5
PYTEST_QT_API: pyqt5
QT_API: pyqt6
PYTEST_QT_API: pyqt6
SQUID_PYTEST_HARD_EXIT: "1"
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ See related work and applications on: www.cephla.com, www.squid-imaging.org
## How to Start
### Software
#### Setting up and run Squid software on Linux
Ubuntu 22.04 is the most tested platform. Other Linux systems should work but performance is not guaranteed.
Ubuntu 22.04 is the most tested platform; Ubuntu 24.04 is supported with its own setup script (`software/setup_24.04.sh`). Other Linux systems should work but performance is not guaranteed.

See [README](https://github.com/Cephla-Lab/Squid/blob/master/software/README.md) in `/software` directory for instructions. Toupcam and laser auto-focus camera dependencies will be installed automatically when you run the setup script.

Expand Down
67 changes: 67 additions & 0 deletions software/Python 3.14 Compatibility Check.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Python 3.14 Compatibility -- Squid Dependencies

## Supported (confirmed)
| Package | Notes |
|---------|-------|
| numpy | Wheels for 3.14 since v2.3+ |
| scipy | Supported |
| pandas | Supported |
| pydantic (v2) | Supported. (pydantic v1 is not, but nothing in our stack needs it: napari 0.5.x already ran on pydantic 2.x) |
| Pillow | Supported |
| matplotlib | Supported |
| PyYAML | Supported |
| filelock | Supported |
| platformdirs | Supported |
| scikit-image | Wheels for 3.14 since v0.26 |
| napari (latest) | Supported -- ships both `[pyqt5]` and `[pyqt6]` extras (0.7.x and 0.9.x); we use PyQt6 |
| opencv-python-headless | Uses stable ABI (cp37-abi3), works |
| opencv-contrib-python-headless | Same stable ABI, works |
| torch (PyTorch) | Supported since ~2.13 |
| dask | Active releases in 2026 |
| zarr | Supported |
| tifffile | Supported |
| imageio | Supported |
| pyqtgraph | Pure Python, likely works |
| qtpy | Pure Python abstraction layer, works |
| PyQt5 | Installs on 3.14: PyQt5 5.15.11 wheels are `cp38-abi3` and PyQt5-sip 12.19.0 ships cp314 wheels. Not a blocker -- we moved to PyQt6 for other reasons |

## Blocked / Incompatible
None confirmed in the pip stack. Earlier drafts listed PyQt5 and pydantic v1 here; both were wrong
(see the Supported table). The likely blockers are the old pins pulled in by unmaintained packages:
aicsimageio 4.14 pins `tifffile<2023.3.15`, `zarr<2.16`, `lxml<5`; basicpy pins `scipy<1.13`
(peng-lab/BaSiCPy#173). Whether those old releases have 3.14 wheels has not been checked.

## Uncertain / Likely OK but unconfirmed
| Package | Notes |
|---------|-------|
| pyserial | Pure Python, last release 2020 -- probably works but untested |
| pyvisa | Likely works |
| hidapi | Cython extension -- no 3.14 wheels confirmed |
| GitPython | Marked as NOT supporting 3.14 on pyreadiness.org |
| aicsimageio | Unclear, niche package |
| basicpy | Unclear |
| ome-zarr | Unclear |
| pydantic-xml | Likely works if pydantic v2 is used |
| pyreadline3 | Windows-specific, unclear |
| qtconsole | Depends on Qt backend |
| lxml / lxml_html_clean | C extension -- needs checking |
| mcp | Unclear |
| Hardware drivers (PySpin, pyAndorSDK3, ids_peak, pyvcam, pm16) | Vendor-specific, unlikely to have 3.14 builds yet |

## Bottom Line

No confirmed hard blocker in the pip stack. The PyQt6 + napari 0.7 migration in this PR removes the
Qt-side uncertainty; what remains before a 3.14 move is:
1. Confirming 3.14 wheels (or dropping) the pinned-back packages aicsimageio and basicpy pull in
2. Verifying all hardware vendor SDKs have 3.14 builds

**Recommendation: Stay on Python 3.10** for production use. It's the sweet spot where everything works.

## Sources
- [Python 3.14 Readiness](https://pyreadiness.org/3.14/)
- [NumPy 2.3.0 Release Notes](https://numpy.org/devdocs/release/2.3.0-notes.html)
- [PyQt5 on PyPI](https://pypi.org/project/PyQt5/)
- [napari Installation](https://napari.org/stable/getting_started/installation.html)
- [PyTorch torch.compile Python 3.14 support](https://dev-discuss.pytorch.org/t/torch-compile-support-for-python-3-14-completed/3276)
- [Pydantic v1 Python 3.14 issue](https://github.com/pydantic/pydantic/issues/12618)
- [Anaconda Python 3.14 overview](https://www.anaconda.com/blog/python-3-14-what-data-scientists-developers-need-know)
12 changes: 11 additions & 1 deletion software/README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
## Setting up the environments
Run the following script in terminal to clone the repo and set up the environment
Run the setup script for your Ubuntu release in a terminal to clone the repo and set up the environment.

Ubuntu 22.04:
```
wget https://raw.githubusercontent.com/Cephla-Lab/Squid/master/software/setup_22.04.sh
chmod +x setup_22.04.sh
./setup_22.04.sh
```

Ubuntu 24.04:
```
wget https://raw.githubusercontent.com/Cephla-Lab/Squid/master/software/setup_24.04.sh
chmod +x setup_24.04.sh
./setup_24.04.sh
```
The two scripts install the same packages; the 24.04 one only adds what Python 3.12 requires (pip's `--break-system-packages`, apt setuptools). If you pip-install extras by hand on 24.04, add `--break-system-packages` to the pip command.

Reboot the computer to finish the installation.

## Optional or Hardware-specific dependencies
Expand Down
2 changes: 1 addition & 1 deletion software/control/console.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import os

# set QT_API environment variable
os.environ["QT_API"] = "pyqt5"
os.environ["QT_API"] = "pyqt6"

import qtpy
from qtpy.QtWidgets import QApplication, QMainWindow, QWidget, QVBoxLayout, QPushButton, QLabel
Expand Down
2 changes: 1 addition & 1 deletion software/control/core/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import tempfile

# qt libraries
os.environ["QT_API"] = "pyqt5"
os.environ["QT_API"] = "pyqt6"
import qtpy
import pyqtgraph as pg
from qtpy.QtCore import *
Expand Down
15 changes: 10 additions & 5 deletions software/control/core/job_processing.py
Original file line number Diff line number Diff line change
Expand Up @@ -935,11 +935,16 @@ def shutdown(self, timeout_s=1.0):
if self.is_alive():
self.terminate()
self.join(timeout=1.0)
# Clean up multiprocessing primitives to avoid semaphore leaks
self._input_queue.close()
self._input_queue.join_thread()
self._output_queue.close()
self._output_queue.join_thread()
# Clean up multiprocessing primitives to avoid semaphore leaks. cancel_join_thread()
# first: a Queue feeds its pipe from a background thread, and if the worker was
# terminated with unread data queued that thread is stuck in a write nobody will
# read, so join_thread() would wait forever (deterministic on Windows, where the
# write blocks once the pipe buffer fills). Both queues are being dropped, so
# discarding those bytes is fine.
for queue in (self._input_queue, self._output_queue):
queue.cancel_join_thread()
queue.close()
queue.join_thread()
# Clear references to allow garbage collection of Event and Value semaphores
self._input_queue = None
self._output_queue = None
Expand Down
2 changes: 1 addition & 1 deletion software/control/core_PDAF.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# set QT_API environment variable
import os

os.environ["QT_API"] = "pyqt5"
os.environ["QT_API"] = "pyqt6"

# qt libraries
from qtpy.QtCore import *
Expand Down
2 changes: 1 addition & 1 deletion software/control/core_displacement_measurement.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# set QT_API environment variable
import os

os.environ["QT_API"] = "pyqt5"
os.environ["QT_API"] = "pyqt6"
import qtpy

# qt libraries
Expand Down
2 changes: 1 addition & 1 deletion software/control/core_usbspectrometer.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# set QT_API environment variable
import os

os.environ["QT_API"] = "pyqt5"
os.environ["QT_API"] = "pyqt6"
import qtpy

# qt libraries
Expand Down
6 changes: 3 additions & 3 deletions software/control/core_volumetric_imaging.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# set QT_API environment variable
import os

os.environ["QT_API"] = "pyqt5"
os.environ["QT_API"] = "pyqt6"
import qtpy

# qt libraries
Expand Down Expand Up @@ -173,8 +173,8 @@ def __init__(self, window_title=""):
self.setCentralWidget(self.widget)

# set window size
desktopWidget = QDesktopWidget()
width = min(desktopWidget.height() * 0.9, 1000) # @@@TO MOVE@@@#
screen_height = QApplication.primaryScreen().size().height()
width = int(min(screen_height * 0.9, 1000)) # @@@TO MOVE@@@#
height = width
self.setFixedSize(width, height)

Expand Down
Loading
Loading