From c1722c42697ba3cb6307c006364957e8d7031ecb Mon Sep 17 00:00:00 2001 From: Peter Sobolewski <76622105+psobolewskiPhD@users.noreply.github.com> Date: Mon, 23 Sep 2024 10:29:37 -0400 Subject: [PATCH 1/2] [maint] Fix CI (GitHub actions workflow and tox.ini) (#55) * Update tox.ini for py39-py312 * Update test_and_deploy.yml for py39-312 and allow mac/win * Update test_and_deploy.yml to add concurrency/cancel-in-progress * Update test_and_deploy.yml remove unused miniconda * Update test_and_deploy.yml to use setup-python action * Update test_and_deploy.yml remove unused tox-conda * Update test_and_deploy.yml remove unused backend * Update test_and_deploy.yml remove unneeded bash shell * monkeypatch torch mps on CI to be False because runners dont have it * monkeypatch assign_device to return CPU * fix lambda * Fix expected values for tests and specify cyto3 model * fix setting the model type * re-enable tests on linux CI * clean up imports and comments in test_plugin --- .github/workflows/test_and_deploy.yml | 27 ++++++++++------------- tests/test_plugin.py | 31 ++++++++++++++------------- tox.ini | 5 +++-- 3 files changed, 30 insertions(+), 33 deletions(-) diff --git a/.github/workflows/test_and_deploy.yml b/.github/workflows/test_and_deploy.yml index 72cfd59..510a4e0 100644 --- a/.github/workflows/test_and_deploy.yml +++ b/.github/workflows/test_and_deploy.yml @@ -17,27 +17,26 @@ on: - main workflow_dispatch: +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: test: name: ${{ matrix.platform }} ${{ matrix.python-version }} runs-on: ${{ matrix.platform }} - defaults: - run: - shell: bash -l {0} strategy: fail-fast: false matrix: - platform: [ubuntu-latest]#, macos-latest, windows-latest] - python-version: ["3.9", "3.10", "3.11"] - backend: [pyqt5] + platform: [ubuntu-latest, macos-latest, windows-latest] + python-version: ["3.9", "3.10", "3.11", "3.12"] steps: - uses: actions/checkout@v4 - - uses: conda-incubator/setup-miniconda@v2 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 with: - miniconda-version: "latest" - channels: conda-forge - channel-priority: strict python-version: ${{ matrix.python-version }} - uses: tlambert03/setup-qt-libs@v1 @@ -51,18 +50,14 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - python -m pip install --upgrade setuptools tox tox-conda tox-gh-actions + python -m pip install --upgrade setuptools tox tox-gh-actions - name: Test with tox - uses: aganders3/headless-gui@v1.2 + uses: aganders3/headless-gui@v2 with: - shell: bash -el {0} run: python -m tox env: PLATFORM: ${{ matrix.platform }} - PYTHON: ${{ matrix.python-version }} - PYVISTA_OFF_SCREEN: True - BACKEND: ${{ matrix.backend }} - name: Codecov uses: codecov/codecov-action@v3 diff --git a/tests/test_plugin.py b/tests/test_plugin.py index af2f2f4..748f028 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -1,14 +1,11 @@ import os -import sys from pathlib import Path from math import isclose from typing import Callable -import cellpose_napari import napari import pytest import torch # for ubuntu tests on CI, see https://github.com/pytorch/pytorch/issues/75912 -from cellpose_napari._dock_widget import widget_wrapper # this is your plugin name declared in your napari.plugins entry point PLUGIN_NAME = "cellpose-napari" @@ -17,6 +14,14 @@ SAMPLE = Path(__file__).parent / "sample.tif" +@pytest.fixture(autouse=True) +def patch_mps_on_CI(monkeypatch): + # https://github.com/actions/runner-images/issues/9918 + if os.getenv('CI'): + monkeypatch.setattr("torch.backends.mps.is_available", lambda: False) + monkeypatch.setattr("cellpose.core.assign_device", lambda **kwargs: (torch.device("cpu"), False)) + + @pytest.fixture def viewer_widget(make_napari_viewer: Callable[..., napari.Viewer]): viewer = make_napari_viewer() @@ -32,11 +37,7 @@ def test_basic_function(qtbot, viewer_widget): viewer.open_sample(PLUGIN_NAME, 'rgb_2D') viewer.layers[0].data = viewer.layers[0].data[0:128, 0:128] - #if os.getenv("CI"): - # return - # actually running cellpose like this takes too long and always timesout on CI - # need to figure out better strategy - + widget.model_type.value = "cyto3" widget() # run segmentation with all default parameters def check_widget(): @@ -47,10 +48,9 @@ def check_widget(): assert len(viewer.layers) == 5 assert "cp_masks" in viewer.layers[-1].name - # check that the segmentation was proper, should yield 11 cells + # check that the segmentation was proper, cyto3 yields 10 cells assert viewer.layers[-1].data.max() == 10 -@pytest.mark.skipif(sys.platform.startswith('linux'), reason="ubuntu stalls with two cellpose tests") def test_compute_diameter(qtbot, viewer_widget): viewer, widget = viewer_widget viewer.open_sample(PLUGIN_NAME, 'rgb_2D') @@ -63,16 +63,17 @@ def test_compute_diameter(qtbot, viewer_widget): with qtbot.waitSignal(widget.diameter.changed, timeout=60_000) as blocker: widget.compute_diameter_button.changed(None) - assert isclose(float(widget.diameter.value), 24.1, abs_tol=10**-1) + # local on macOS with MPS, get 20.37, with CPU-only it's 20.83, same as CI + # so choosing a target that works for both + assert isclose(float(widget.diameter.value), 20.6, abs_tol=0.3) -@pytest.mark.skipif(sys.platform.startswith('linux'), reason="ubuntu stalls with >1 cellpose tests") def test_3D_segmentation(qtbot, viewer_widget): viewer, widget = viewer_widget viewer.open_sample(PLUGIN_NAME, 'rgb_3D') # set 3D processing widget.process_3D.value = True - + widget.model_type.value = "cyto3" widget() # run segmentation with all default parameters def check_widget(): @@ -83,5 +84,5 @@ def check_widget(): assert len(viewer.layers) == 5 assert "cp_masks" in viewer.layers[-1].name - # check that the segmentation was proper, should yield 7 cells - assert viewer.layers[-1].data.max() == 7 + # check that the segmentation was proper, `cyto3` should yield 9 cells + assert viewer.layers[-1].data.max() == 9 diff --git a/tox.ini b/tox.ini index 1d110c7..8353db6 100644 --- a/tox.ini +++ b/tox.ini @@ -1,14 +1,15 @@ # For more information about tox, see https://tox.readthedocs.io/en/latest/ [tox] -envlist = py{38,39,310}-{linux,macos,windows} +envlist = py{39,310,311,312}-{linux,macos,windows} isolated_build=true toxworkdir = /tmp/.tox [gh-actions] python = - 3.8: py38 3.9: py39 3.10: py310 + 3.11: py311 + 3.12: py312 [gh-actions:env] PLATFORM = From c1298344e65f98022a38b536125ace25bacefc06 Mon Sep 17 00:00:00 2001 From: Peter Sobolewski <76622105+psobolewskiPhD@users.noreply.github.com> Date: Mon, 23 Sep 2024 13:35:55 -0400 Subject: [PATCH 2/2] use esize_and_compute_masks in compute_masks (#57) --- cellpose_napari/_dock_widget.py | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/cellpose_napari/_dock_widget.py b/cellpose_napari/_dock_widget.py index 37d20f5..87723f8 100644 --- a/cellpose_napari/_dock_widget.py +++ b/cellpose_napari/_dock_widget.py @@ -73,6 +73,8 @@ def run_cellpose(image, model_type, custom_model, channels, channel_axis, diamet CP = models.CellposeModel(pretrained_model=custom_model, gpu=True) else: CP = models.CellposeModel(model_type=model_type, gpu=True) + + print(cellprob_threshold, flow_threshold) masks, flows_orig, _ = CP.eval(image, channels=channels, channel_axis=channel_axis, @@ -105,22 +107,16 @@ def compute_diameter(image, channels, model_type): @thread_worker def compute_masks(masks_orig, flows_orig, cellprob_threshold, flow_threshold): - import cv2 - from cellpose.utils import fill_holes_and_remove_small_masks - from cellpose.dynamics import get_masks - from cellpose.transforms import resize_image + from cellpose.dynamics import resize_and_compute_masks - #print(flows_orig[3].shape, flows_orig[2].shape, masks_orig.shape) flow_threshold = (31.0 - flow_threshold) / 10. if flow_threshold==0.0: flow_threshold = 0.0 logger.debug('flow_threshold=0 => no masks thrown out due to model mismatch') logger.debug(f'computing masks with cellprob_threshold={cellprob_threshold}, flow_threshold={flow_threshold}') - maski = get_masks(flows_orig[3].copy(), iscell=(flows_orig[2] > cellprob_threshold), - flows=flows_orig[1], threshold=flow_threshold*(masks_orig.ndim<3)) - maski = fill_holes_and_remove_small_masks(maski) - maski = resize_image(maski, masks_orig.shape[-2], masks_orig.shape[-1], - interpolation=cv2.INTER_NEAREST) + maski, _ = resize_and_compute_masks(flows_orig[1], cellprob=flows_orig[2], cellprob_threshold=cellprob_threshold, + flow_threshold=flow_threshold, resize=(masks_orig.shape[-2], masks_orig.shape[-1])) + return maski @magicgui(