diff --git a/.travis.yml b/.travis.yml index 197a37ccf..e508941c6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -47,9 +47,19 @@ jobs: - make install-dev slow-test after_success: - bash <(curl -s https://codecov.io/bash) - - name: Install Napari + - name: Install Napari & Test napari if: type = push and (branch = master or branch =~ /-alltest/) - script: pip install .[napari] + dist: xenial + before_install: + - sudo apt-get install -y libgl1-mesa-glx libqt5x11extras5 xvfb + - make install-dev + - pip install .[napari] + - export DISPLAY=:99 + - Xvfb $DISPLAY -ac -screen 0 1024x768x24 & + - sleep 10 + script: + - python -c "import vispy; print(vispy.sys_info())" + - make napari-test - name: Docker if: type = push and (branch = master or branch =~ /-alltest/) script: make docker diff --git a/Makefile b/Makefile index 3c4c76589..24daca43a 100644 --- a/Makefile +++ b/Makefile @@ -39,14 +39,20 @@ lint-non-init: lint-init: flake8 --ignore 'E252, E301, E302, E305, E401, F401, W503, E731, F811' --filename='*__init__.py' $(MODULES) +# note that napari tests shouldn't be run in parallel because Qt seems to intermittently fail when multiple QApplications are spawned on threads. test: - pytest -v -n 8 --cov starfish + pytest -v -n 8 --cov starfish -m 'not napari' + pytest -v --cov starfish -m 'napari' fast-test: - pytest -v -n 8 --cov starfish -m 'not slow' + pytest -v -n 8 --cov starfish -m 'not (slow or napari)' slow-test: - pytest -v -n 8 --cov starfish -m 'slow' + pytest -v -n 8 --cov starfish -m 'slow and (not napari)' + +# note that this shouldn't be run in parallel because Qt seems to intermittently fail when multiple QApplications are spawned on threads. +napari-test: + pytest -v --cov starfish -m 'napari' mypy: mypy --ignore-missing-imports $(MODULES) diff --git a/REQUIREMENTS-CI.txt b/REQUIREMENTS-CI.txt index 9bf9560e3..be6944c5f 100644 --- a/REQUIREMENTS-CI.txt +++ b/REQUIREMENTS-CI.txt @@ -5,7 +5,7 @@ mypy numpydoc nbencdec >= 0.0.5 pycodestyle==2.5.0 -pytest>=3.6.3 +pytest>=4.4.0 pytest-cov>=2.5.1 pytest-xdist recommonmark diff --git a/setup.py b/setup.py index ce7483388..0a39777c4 100644 --- a/setup.py +++ b/setup.py @@ -19,7 +19,7 @@ packages=setuptools.find_packages(), install_requires=install_requires, extras_require={ - 'napari': [f"napari=={napari_version}"], + 'napari': [f"napari>={napari_version}"], }, entry_points={ 'console_scripts': [ diff --git a/starfish/core/_display.py b/starfish/core/_display.py index a04472b5f..4f7bd2150 100644 --- a/starfish/core/_display.py +++ b/starfish/core/_display.py @@ -4,6 +4,7 @@ from typing import Iterable, List, Optional, Set, Tuple, Union import numpy as np +from packaging.version import parse as parse_version from starfish.core.imagestack.imagestack import ImageStack from starfish.core.intensity_table.intensity_table import IntensityTable @@ -16,7 +17,7 @@ Viewer = None -NAPARI_VERSION = "0.1.3" # when changing this, update docs in display +NAPARI_VERSION = "0.1.5" # when changing this, update docs in display INTERACTIVE = not hasattr(__main__, "__file__") @@ -127,7 +128,8 @@ def display( z_multiplier: float = 1 ): """ - Displays an image stack and/or detected spots using napari (https://github.com/napari/napari). + Display an image stack, detected spots, and masks using + `napari `. Parameters ---------- @@ -193,20 +195,19 @@ def display( Notes ----- - - To use in ipython, use the `%gui qt5` magic. + - To use in ipython, use the `%gui qt` magic. - napari axes currently cannot be labeled. Until such a time that they can, this function will order them by Round, Channel, and Z. - - Requires napari 0.1.3: install starfish using `pip install starfish[napari]` to install all - necessary requirements + - Requires at least napari 0.1.5: use `pip install starfish[napari]` + to install all necessary requirements """ - if stack is None and spots is None: - # masks without stack or spots have no context so don't check for that - raise TypeError("expected a stack and/or spots; got nothing") + if stack is None and spots is None and masks is None: + raise TypeError("expected a stack, spots, or masks; got nothing") try: import napari except ImportError: - raise ImportError(f"Requires napari {NAPARI_VERSION}. " + raise ImportError(f"Requires at least napari {NAPARI_VERSION}. " "Run `pip install starfish[napari]` " "to install the necessary requirements.") @@ -215,7 +216,7 @@ def display( except Exception as e: raise RuntimeError("Could not identify napari version") from e - if version != NAPARI_VERSION: + if parse_version(version) < parse_version(NAPARI_VERSION): raise ValueError(f"Incorrect version {version} of napari installed." "Run `pip install starfish[napari]` " "to install the necessary requirements.") diff --git a/starfish/core/test/test_display.py b/starfish/core/test/test_display.py new file mode 100644 index 000000000..e1d977f94 --- /dev/null +++ b/starfish/core/test/test_display.py @@ -0,0 +1,54 @@ +import numpy as np +import pytest + +from starfish import display, SegmentationMaskCollection +from starfish.core.test.factories import SyntheticData +from starfish.types import Coordinates + + +sd = SyntheticData( + n_ch=2, + n_round=3, + n_spots=1, + n_codes=4, + n_photons_background=0, + background_electrons=0, + camera_detection_efficiency=1.0, + gray_level=1, + ad_conversion_bits=16, + point_spread_function=(2, 2, 2), +) + +stack = sd.spots() +spots = sd.intensities() +masks = SegmentationMaskCollection.from_label_image( + np.random.rand(128, 128).astype(np.uint8), + {Coordinates.Y: np.arange(128), Coordinates.X: np.arange(128)} +) + + +@pytest.mark.napari +@pytest.mark.parametrize('masks', [masks, None], ids=['masks', ' ']) +@pytest.mark.parametrize('spots', [spots, None], ids=['spots', ' ']) +@pytest.mark.parametrize('stack', [stack, None], ids=['stack', ' ']) +def test_display(stack, spots, masks): + import napari + from qtpy.QtCore import QTimer + from qtpy.QtWidgets import QApplication + + def run(): + app = QApplication.instance() or QApplication([]) + viewer = napari.Viewer() + timer = QTimer() + timer.setInterval(500) + timer.timeout.connect(viewer.window.close) + timer.timeout.connect(app.quit) + timer.start() + display(stack, spots, masks, viewer=viewer) + app.exec_() + + if stack is None and spots is None and masks is None: + with pytest.raises(TypeError): + run() + else: + run()