From 0a91ad20249ac47a4ea38dbbf61b355eb6ad92d2 Mon Sep 17 00:00:00 2001 From: caspervdw Date: Thu, 22 Oct 2015 21:31:47 +0200 Subject: [PATCH 1/4] Dependencies; make PIL optional Additionally, test with only packages numpy and scipy --- .travis.yml | 15 +++++++++++---- pims/display.py | 6 ++++-- pims/frame.py | 5 +++++ pims/image_sequence.py | 5 ++--- 4 files changed, 22 insertions(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index 01cf77f2..50c8f89e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,13 +1,20 @@ language: python sudo: false -python: - - 2.7 - - 3.4 +matrix: + include: + - python: "2.7" + env: DEPS="numpy=1.9* scipy" + - python: "2.7" + env: DEPS="numpy=1.9* scipy pillow<3 matplotlib scikit-image jinja2" + - python: "3.4" + env: DEPS="numpy=1.9* scipy" + - python: "3.4" + env: DEPS="numpy=1.9* scipy pillow<3 matplotlib scikit-image jinja2" 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 create -n testenv --yes $DEPS nose pip python=$TRAVIS_PYTHON_VERSION - conda install -n testenv -c soft-matter --yes pyav tifffile - source activate testenv - if [ ${TRAVIS_PYTHON_VERSION:0:1} == "2" ]; then pip install libtiff; fi 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..98fae7bf 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: @@ -111,8 +110,8 @@ def __del__(self): def imread(self, filename, **kwargs): 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) From c6d64dab1fcfd47f8a3bbe68612b79144498edc4 Mon Sep 17 00:00:00 2001 From: caspervdw Date: Fri, 23 Oct 2015 11:07:05 +0200 Subject: [PATCH 2/4] Make scipy optional --- .travis.yml | 4 ++-- pims/image_sequence.py | 10 +++++++++- pims/tests/test_common.py | 26 ++++++++++++++++++++------ 3 files changed, 31 insertions(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index 50c8f89e..496a9801 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,11 +4,11 @@ sudo: false matrix: include: - python: "2.7" - env: DEPS="numpy=1.9* scipy" + env: DEPS="numpy=1.9*" - python: "2.7" env: DEPS="numpy=1.9* scipy pillow<3 matplotlib scikit-image jinja2" - python: "3.4" - env: DEPS="numpy=1.9* scipy" + env: DEPS="numpy=1.9*" - python: "3.4" env: DEPS="numpy=1.9* scipy pillow<3 matplotlib scikit-image jinja2" diff --git a/pims/image_sequence.py b/pims/image_sequence.py index 98fae7bf..cd2a13ce 100644 --- a/pims/image_sequence.py +++ b/pims/image_sequence.py @@ -25,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): @@ -85,6 +88,11 @@ def __init__(self, path_spec, process_func=None, dtype=None, else: self.kwargs = dict(plugin=plugin) + if imread is None: + raise ImportError("One of the following packages are required for " + "using the ImageSequence reader: " + "scipy, matplotlib or scikit-image.") + self._is_zipfile = False self._zipfile = None self._get_files(path_spec) diff --git a/pims/tests/test_common.py b/pims/tests/test_common.py index c70305c1..0e2ad85b 100644 --- a/pims/tests/test_common.py +++ b/pims/tests/test_common.py @@ -40,6 +40,19 @@ def _skip_if_no_tifffile(): raise nose.SkipTest('tifffile not installed. Skipping.') +def _skip_if_no_imread(): + if pims.imagesequence.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 assert_image_equal(actual, expected): if np.issubdtype(actual.dtype, np.integer): assert_equal(actual, expected) @@ -180,6 +193,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 +547,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 +574,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 +595,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 +618,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', @@ -713,10 +731,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 +793,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', From 7df97c658d80b5a0f03403a037cb1b380536f9ea Mon Sep 17 00:00:00 2001 From: caspervdw Date: Fri, 23 Oct 2015 11:14:46 +0200 Subject: [PATCH 3/4] TST Fixes for PIL deps --- pims/image_sequence.py | 9 ++++----- pims/tests/test_common.py | 6 +++--- pims/tests/test_frame.py | 9 +++++++++ 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/pims/image_sequence.py b/pims/image_sequence.py index cd2a13ce..387ae4a2 100644 --- a/pims/image_sequence.py +++ b/pims/image_sequence.py @@ -88,11 +88,6 @@ def __init__(self, path_spec, process_func=None, dtype=None, else: self.kwargs = dict(plugin=plugin) - if imread is None: - raise ImportError("One of the following packages are required for " - "using the ImageSequence reader: " - "scipy, matplotlib or scikit-image.") - self._is_zipfile = False self._zipfile = None self._get_files(path_spec) @@ -117,6 +112,10 @@ 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: file_handle = StringIO(self._zipfile.read(filename)) return imread(file_handle, **kwargs) diff --git a/pims/tests/test_common.py b/pims/tests/test_common.py index 0e2ad85b..4cf68916 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') @@ -41,9 +40,9 @@ def _skip_if_no_tifffile(): def _skip_if_no_imread(): - if pims.imagesequence.imread is None: + if pims.image_sequence.imread is None: raise nose.SkipTest('ImageSequence requires either scipy, matplotlib or' - 'scikit-image. Skipping.') + ' scikit-image. Skipping.') def _skip_if_no_skimage(): @@ -63,6 +62,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 = [] 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_() From 7e612ebb3cbaa2f3e25a6c622faf7148b142a5c2 Mon Sep 17 00:00:00 2001 From: caspervdw Date: Fri, 23 Oct 2015 11:41:25 +0200 Subject: [PATCH 4/4] TST More PIL conditionals PyAv reader requires PIL. --- .travis.yml | 10 +++++----- pims/api.py | 2 +- pims/pyav_reader.py | 1 + pims/tests/test_common.py | 11 +++++++++++ 4 files changed, 18 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 496a9801..cc48a3be 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,18 +4,18 @@ sudo: false matrix: include: - python: "2.7" - env: DEPS="numpy=1.9*" + env: DEPS="numpy=1.9*" DEPSSM="tifffile" - python: "2.7" - env: DEPS="numpy=1.9* scipy pillow<3 matplotlib scikit-image jinja2" + env: DEPS="numpy=1.9* scipy pillow<3 matplotlib scikit-image jinja2" DEPSSM="pyav tifffile" - python: "3.4" - env: DEPS="numpy=1.9*" + env: DEPS="numpy=1.9*" DEPSSM="tifffile" - python: "3.4" - env: DEPS="numpy=1.9* scipy pillow<3 matplotlib scikit-image jinja2" + 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 $DEPS nose pip python=$TRAVIS_PYTHON_VERSION - - conda install -n testenv -c soft-matter --yes pyav tifffile + - 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/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 4cf68916..8bbd028e 100644 --- a/pims/tests/test_common.py +++ b/pims/tests/test_common.py @@ -52,6 +52,13 @@ def _skip_if_no_skimage(): 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) @@ -653,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')) @@ -711,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',