diff --git a/.github/workflows/test_and_deploy.yml b/.github/workflows/test_and_deploy.yml index 6d7fecd..72cfd59 100644 --- a/.github/workflows/test_and_deploy.yml +++ b/.github/workflows/test_and_deploy.yml @@ -1,9 +1,12 @@ # This workflows will upload a Python Package using Twine when a release is created # For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries +### test workflow from Talley Lambert's napari-omero! thanks! +# https://github.com/tlambert03/napari-omero/blob/main/.github/workflows/ci.yml + name: tests -on: +on: push: branches: - main @@ -16,22 +19,25 @@ on: jobs: test: - name: ${{ matrix.platform }} py${{ matrix.python-version }} + name: ${{ matrix.platform }} ${{ matrix.python-version }} runs-on: ${{ matrix.platform }} + defaults: + run: + shell: bash -l {0} strategy: fail-fast: false matrix: - platform: [ubuntu-latest, windows-latest, macos-latest] - python-version: ['3.8', '3.9', '3.10'] - exclude: # torch doesn't support windows python 3.10 https://pytorch.org/get-started/locally/#windows-python - - platform: windows-latest - python-version: '3.10' - steps: - - uses: actions/checkout@v3 + platform: [ubuntu-latest]#, macos-latest, windows-latest] + python-version: ["3.9", "3.10", "3.11"] + backend: [pyqt5] - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + steps: + - uses: actions/checkout@v4 + - uses: conda-incubator/setup-miniconda@v2 with: + miniconda-version: "latest" + channels: conda-forge + channel-priority: strict python-version: ${{ matrix.python-version }} - uses: tlambert03/setup-qt-libs@v1 @@ -41,27 +47,25 @@ jobs: run: | git clone --depth 1 https://github.com/pyvista/gl-ci-helpers.git powershell gl-ci-helpers/appveyor/install_opengl.ps1 - if (Test-Path -Path "C:\Windows\system32\opengl32.dll" -PathType Leaf) {Exit 0} else {Exit 1} - - # note: if you need dependencies from conda, considering using - # setup-miniconda: https://github.com/conda-incubator/setup-miniconda - # and - # tox-conda: https://github.com/tox-dev/tox-conda - name: Install dependencies run: | python -m pip install --upgrade pip - python -m pip install setuptools tox tox-gh-actions - # this runs the platform-specific tests declared in tox.ini + python -m pip install --upgrade setuptools tox tox-conda tox-gh-actions + - name: Test with tox - uses: GabrielBB/xvfb-action@v1 + uses: aganders3/headless-gui@v1.2 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: Coverage - uses: codecov/codecov-action@v2 + - name: Codecov + uses: codecov/codecov-action@v3 deploy: # this will run when you have tagged a commit, starting with "v*" @@ -71,9 +75,9 @@ jobs: runs-on: ubuntu-latest if: contains(github.ref, 'tags') steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: "3.x" - name: Install dependencies @@ -87,4 +91,5 @@ jobs: run: | git tag python -m build + twine check dist/* twine upload dist/* diff --git a/cellpose_napari/_dock_widget.py b/cellpose_napari/_dock_widget.py index 92621d6..cbbf013 100644 --- a/cellpose_napari/_dock_widget.py +++ b/cellpose_napari/_dock_widget.py @@ -13,6 +13,10 @@ from magicgui import magicgui import sys +CP_models = ["cyto3", "cyto2", "cyto", "nuclei", "tissuenet_cp3", + "livecell_cp3", "yeast_PhC_cp3", "yeast_BF_cp3", "bact_phase_cp3", + "bact_fluor_cp3", "deepbacs_cp3", "cyto2_cp3"] + # initialize logger # use -v or --verbose when starting napari to increase verbosity logger = logging.getLogger(__name__) @@ -57,12 +61,11 @@ def _deco(func): @thread_worker @no_grad() def run_cellpose(image, model_type, custom_model, channels, channel_axis, diameter, - net_avg, resample, cellprob_threshold, - model_match_threshold, do_3D, stitch_threshold): + resample, cellprob_threshold, + flow_threshold, do_3D, stitch_threshold): from cellpose import models - flow_threshold = (31.0 - model_match_threshold) / 10. - if model_match_threshold==0.0: + 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}') @@ -74,7 +77,6 @@ def run_cellpose(image, model_type, custom_model, channels, channel_axis, diamet channels=channels, channel_axis=channel_axis, diameter=diameter, - net_avg=net_avg, resample=resample, cellprob_threshold=cellprob_threshold, flow_threshold=flow_threshold, @@ -93,23 +95,24 @@ def run_cellpose(image, model_type, custom_model, channels, channel_axis, diamet @thread_worker def compute_diameter(image, channels, model_type): from cellpose import models + model_type0 = model_type if model_type in CP_models[:4] else "cyto3" - CP = models.Cellpose(model_type = model_type, gpu=True) + CP = models.Cellpose(model_type = model_type0, gpu=True) diam = CP.sz.eval(image, channels=channels, channel_axis=-1)[0] diam = np.around(diam, 2) del CP return diam @thread_worker - def compute_masks(masks_orig, flows_orig, cellprob_threshold, model_match_threshold): + 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 #print(flows_orig[3].shape, flows_orig[2].shape, masks_orig.shape) - flow_threshold = (31.0 - model_match_threshold) / 10. - if model_match_threshold==0.0: + 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}') @@ -123,7 +126,7 @@ def compute_masks(masks_orig, flows_orig, cellprob_threshold, model_match_thresh @magicgui( call_button='run segmentation', layout='vertical', - model_type = dict(widget_type='ComboBox', label='model type', choices=['cyto', 'nuclei', 'cyto2', 'custom'], value='cyto', tooltip='there is a cyto model, a new cyto2 model from user submissions, and a nuclei model'), + model_type = dict(widget_type='ComboBox', label='model type', choices=[*CP_models, 'custom'], value='cyto3', tooltip='there is a cyto model, a new cyto2 model from user submissions, and a nuclei model'), custom_model = dict(widget_type='FileEdit', label='custom model path: ', tooltip='if model type is custom, specify file path to it here'), main_channel = dict(widget_type='ComboBox', label='channel to segment', choices=main_channel_choices, value=0, tooltip='choose channel with cells'), optional_nuclear_channel = dict(widget_type='ComboBox', label='optional nuclear channel', choices=optional_nuclear_channel_choices, value=0, tooltip='optional, if available, choose channel with nuclei of cells'), @@ -131,9 +134,8 @@ def compute_masks(masks_orig, flows_orig, cellprob_threshold, model_match_thresh compute_diameter_shape = dict(widget_type='PushButton', text='compute diameter from shape layer', tooltip='create shape layer with circles and/or squares, select above, and diameter will be estimated from it'), compute_diameter_button = dict(widget_type='PushButton', text='compute diameter from image', tooltip='cellpose model will estimate diameter from image using specified channels'), cellprob_threshold = dict(widget_type='FloatSlider', name='cellprob_threshold', value=0.0, min=-8.0, max=8.0, step=0.2, tooltip='cell probability threshold (set lower to get more cells and larger cells)'), - model_match_threshold = dict(widget_type='FloatSlider', name='model_match_threshold', value=27.0, min=0.0, max=30.0, step=0.2, tooltip='threshold on gradient match to accept a mask (set lower to get more cells)'), + flow_threshold = dict(widget_type='FloatSlider', name='flow_threshold', value=0.4, min=0.0, max=3.0, step=0.05, tooltip='threshold on gradient match to accept a mask (set higher to get more cells, or to zero to turn off)'), compute_masks_button = dict(widget_type='PushButton', text='recompute last masks with new cellprob + model match', enabled=False), - net_average = dict(widget_type='CheckBox', text='average 4 nets', value=True, tooltip='average 4 different fit networks (default) or if not checked run only 1 network (fast)'), resample_dynamics = dict(widget_type='CheckBox', text='resample dynamics', value=False, tooltip='if False, mask estimation with dynamics run on resized image with diameter=30; if True, flows are resized to original image size before dynamics and mask estimation (turn on for more smooth masks)'), process_3D = dict(widget_type='CheckBox', text='process stack as 3D', value=False, tooltip='use default 3D processing where flows in X, Y, and Z are computed and dynamics run in 3D to create masks'), stitch_threshold_3D = dict(widget_type='LineEdit', label='stitch threshold slices', value=0, tooltip='across time or Z, stitch together masks with IoU threshold of "stitch threshold" to create 3D segmentation'), @@ -153,9 +155,8 @@ def widget(#label_logo, compute_diameter_shape, compute_diameter_button, cellprob_threshold, - model_match_threshold, + flow_threshold, compute_masks_button, - net_average, resample_dynamics, process_3D, stitch_threshold_3D, @@ -256,10 +257,9 @@ def _new_segmentation(segmentation): max(0, optional_nuclear_channel)], channel_axis=widget.channel_axis, diameter=float(diameter), - net_avg=net_average, resample=resample_dynamics, cellprob_threshold=cellprob_threshold, - model_match_threshold=model_match_threshold, + flow_threshold=flow_threshold, do_3D=(process_3D and float(stitch_threshold_3D)==0 and image_layer.ndim>2), stitch_threshold=float(stitch_threshold_3D) if image_layer.ndim>2 else 0.0) cp_worker.returned.connect(_new_segmentation) @@ -298,7 +298,7 @@ def _compute_masks(e: Any): mask_worker = compute_masks(widget.masks_orig, widget.flows_orig, widget.cellprob_threshold.value, - widget.model_match_threshold.value) + widget.flow_threshold.value) mask_worker.returned.connect(update_masks) mask_worker.start() diff --git a/tests/test_plugin.py b/tests/test_plugin.py index d3d7c36..8d236e7 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -48,7 +48,7 @@ def check_widget(): assert "cp_masks" in viewer.layers[-1].name # check that the segmentation was proper, should yield 11 cells - assert viewer.layers[-1].data.max() == 11 + 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):