diff --git a/.travis.yml b/.travis.yml index 01cf77f2..cc48a3be 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,14 +1,21 @@ language: python sudo: false -python: - - 2.7 - - 3.4 +matrix: + include: + - python: "2.7" + env: DEPS="numpy=1.9*" DEPSSM="tifffile" + - python: "2.7" + env: DEPS="numpy=1.9* scipy pillow<3 matplotlib scikit-image jinja2" DEPSSM="pyav tifffile" + - python: "3.4" + env: DEPS="numpy=1.9*" DEPSSM="tifffile" + - python: "3.4" + env: DEPS="numpy=1.9* scipy pillow<3 matplotlib scikit-image jinja2" DEPSSM="pyav tifffile" install: - conda update --yes conda - - conda create -n testenv --yes numpy=1.9* scipy nose "pillow<3" matplotlib scikit-image jinja2 pip python=$TRAVIS_PYTHON_VERSION - - conda install -n testenv -c soft-matter --yes pyav tifffile + - conda create -n testenv --yes $DEPS nose pip python=$TRAVIS_PYTHON_VERSION + - conda install -n testenv -c soft-matter --yes $DEPSSM - source activate testenv - if [ ${TRAVIS_PYTHON_VERSION:0:1} == "2" ]; then pip install libtiff; fi - pip install slicerator jpype1; diff --git a/pims/api.py b/pims/api.py index 21d59cd8..69e17c4c 100644 --- a/pims/api.py +++ b/pims/api.py @@ -33,7 +33,7 @@ def raiser(*args, **kwargs): else: raise ImportError() except (ImportError, IOError): - Video = not_available("PyAV") + Video = not_available("PyAV and/or PIL/Pillow") import pims.tiff_stack from pims.tiff_stack import (TiffStack_pil, TiffStack_libtiff, diff --git a/pims/display.py b/pims/display.py index 6edb7101..105248a4 100644 --- a/pims/display.py +++ b/pims/display.py @@ -15,7 +15,6 @@ ColorConverter = None mpl = None plt = None -from PIL import Image def export(sequence, filename, rate=30, bitrate=None, @@ -244,7 +243,10 @@ def scrollable_stack(sequence, width=512, normed=True): def _as_png(arr, width, normed=True): "Create a PNG image buffer from an array." - from PIL import Image + try: + from PIL import Image + except ImportError: + raise ImportError("This feature requires PIL/Pillow.") w = width # for brevity h = arr.shape[0] * w // arr.shape[1] if normed: diff --git a/pims/frame.py b/pims/frame.py index 7d2b266d..18e2b0dc 100644 --- a/pims/frame.py +++ b/pims/frame.py @@ -6,6 +6,7 @@ from numpy import ndarray, asarray from pims.display import _scrollable_stack, _as_png, to_rgb +from warnings import warn WIDTH = 512 # width of rich display, in pixels @@ -76,6 +77,10 @@ def __setstate__(self, state): def _repr_html_(self): from jinja2 import Template + try: + from PIL import Image + except ImportError: + warn('Rich display in IPython requires PIL/Pillow.') ndim = self.ndim shape = self.shape image = self diff --git a/pims/image_sequence.py b/pims/image_sequence.py index ea11d0b6..387ae4a2 100644 --- a/pims/image_sequence.py +++ b/pims/image_sequence.py @@ -17,7 +17,6 @@ from pims.frame import Frame from pims.utils.sort import natural_keys -from PIL import Image # skimage.io.plugin_order() gives a nice hierarchy of implementations of imread. # If skimage is not available, go down our own hard-coded hierarchy. try: @@ -26,7 +25,10 @@ try: from matplotlib.pyplot import imread except ImportError: - from scipy.ndimage import imread + try: + from scipy.ndimage import imread + except: + imread = None class ImageSequence(FramesSequence): @@ -110,9 +112,13 @@ def __del__(self): self.close() def imread(self, filename, **kwargs): + if imread is None: + raise ImportError("One of the following packages are required for " + "using the ImageSequence reader: " + "scipy, matplotlib or scikit-image.") if self._is_zipfile: - img = StringIO(self._zipfile.read(filename)) - return np.array(Image.open(img)) + file_handle = StringIO(self._zipfile.read(filename)) + return imread(file_handle, **kwargs) else: return imread(filename, **kwargs) diff --git a/pims/pyav_reader.py b/pims/pyav_reader.py index 81bc3bbe..efdc9006 100644 --- a/pims/pyav_reader.py +++ b/pims/pyav_reader.py @@ -19,6 +19,7 @@ def available(): try: import av + from PIL import Image except ImportError: return False else: diff --git a/pims/tests/test_common.py b/pims/tests/test_common.py index c70305c1..8bbd028e 100644 --- a/pims/tests/test_common.py +++ b/pims/tests/test_common.py @@ -14,7 +14,6 @@ from numpy.testing import (assert_equal, assert_allclose) from nose.tools import assert_true import pims -from PIL import Image path, _ = os.path.split(os.path.abspath(__file__)) path = os.path.join(path, 'data') @@ -40,6 +39,26 @@ def _skip_if_no_tifffile(): raise nose.SkipTest('tifffile not installed. Skipping.') +def _skip_if_no_imread(): + if pims.image_sequence.imread is None: + raise nose.SkipTest('ImageSequence requires either scipy, matplotlib or' + ' scikit-image. Skipping.') + + +def _skip_if_no_skimage(): + try: + import skimage + except ImportError: + raise nose.SkipTest('skimage not installed. Skipping.') + + +def _skip_if_no_PIL(): + try: + from PIL import Image + except ImportError: + raise nose.SkipTest('PIL/Pillow not installed. Skipping.') + + def assert_image_equal(actual, expected): if np.issubdtype(actual.dtype, np.integer): assert_equal(actual, expected) @@ -50,6 +69,7 @@ def assert_image_equal(actual, expected): def save_dummy_png(filepath, filenames, shape): + from PIL import Image if not os.path.isdir(filepath): os.mkdir(filepath) frames = [] @@ -180,6 +200,7 @@ def compare_slice_to_list(actual, expected): class TestRecursiveSlicing(unittest.TestCase): def setUp(self): + _skip_if_no_imread() class DemoReader(pims.ImageSequence): def imread(self, filename, **kwargs): return np.array([[filename]]) @@ -533,6 +554,7 @@ def setUp(self): class TestImageSequenceWithPIL(_image_series, unittest.TestCase): def setUp(self): + _skip_if_no_skimage() self.filepath = os.path.join(path, 'image_sequence') self.filenames = ['T76S3F00001.png', 'T76S3F00002.png', 'T76S3F00003.png', 'T76S3F00004.png', @@ -559,6 +581,7 @@ def tearDown(self): class TestImageSequenceWithMPL(_image_series, unittest.TestCase): def setUp(self): + _skip_if_no_skimage() self.filepath = os.path.join(path, 'image_sequence') self.filenames = ['T76S3F00001.png', 'T76S3F00002.png', 'T76S3F00003.png', 'T76S3F00004.png', @@ -579,6 +602,7 @@ def tearDown(self): class TestImageSequenceAcceptsList(_image_series, unittest.TestCase): def setUp(self): + _skip_if_no_imread() self.filepath = os.path.join(path, 'image_sequence') self.filenames = ['T76S3F00001.png', 'T76S3F00002.png', 'T76S3F00003.png', 'T76S3F00004.png', @@ -601,6 +625,7 @@ def tearDown(self): class TestImageSequenceNaturalSorting(_image_series, unittest.TestCase): def setUp(self): + _skip_if_no_imread() self.filepath = os.path.join(path, 'image_sequence') self.filenames = ['T76S3F1.png', 'T76S3F20.png', 'T76S3F3.png', 'T76S3F4.png', @@ -635,6 +660,7 @@ def check_skip(self): pass def setUp(self): + _skip_if_no_PIL() self.filename = os.path.join(path, 'stuck.tif') self.frame0 = np.load(os.path.join(path, 'stuck_frame0.npy')) self.frame1 = np.load(os.path.join(path, 'stuck_frame1.npy')) @@ -693,6 +719,9 @@ def test_metadata(self): class TestOpenFiles(unittest.TestCase): + def setUp(self): + _skip_if_no_PIL() + def test_open_pngs(self): self.filepath = os.path.join(path, 'image_sequence') self.filenames = ['T76S3F00001.png', 'T76S3F00002.png', @@ -713,10 +742,8 @@ def test_open_tiff(self): class ImageSequenceND(_image_series, unittest.TestCase): - def check_skip(self): - pass - def setUp(self): + _skip_if_no_imread() self.filepath = os.path.join(path, 'image_sequence3d') self.filenames = ['file_t001_z001_c1.png', 'file_t001_z001_c2.png', @@ -777,10 +804,8 @@ def test_sizeC(self): class ImageSequenceND_RGB(_image_series, unittest.TestCase): - def check_skip(self): - pass - def setUp(self): + _skip_if_no_imread() self.filepath = os.path.join(path, 'image_sequence3d') self.filenames = ['file_t001_z001_c1.png', 'file_t001_z002_c1.png', diff --git a/pims/tests/test_frame.py b/pims/tests/test_frame.py index 9f3cc1e8..69bfd2f7 100644 --- a/pims/tests/test_frame.py +++ b/pims/tests/test_frame.py @@ -2,11 +2,19 @@ unicode_literals) import six +import nose import numpy as np from pims.frame import Frame from nose.tools import assert_true, assert_equal +def _skip_if_no_PIL(): + try: + from PIL import Image + except ImportError: + raise nose.SkipTest('PIL/Pillow not installed. Skipping.') + + def test_scalar_casting(): tt = Frame(np.ones((5, 3)), frame_no=42) sum1 = tt.sum() @@ -25,6 +33,7 @@ def test_creation_md(): def test_repr_html_(): + _skip_if_no_PIL() # This confims a bugfix, where 16-bit images would raise # an error. Frame(10000*np.ones((50, 50), dtype=np.uint16))._repr_html_()