diff --git a/pims/api.py b/pims/api.py index 3ac37883..b2f1c7fb 100644 --- a/pims/api.py +++ b/pims/api.py @@ -13,7 +13,7 @@ from warnings import warn # has to be here for API stuff -from pims.image_sequence import ImageSequence, ImageSequenceND # noqa +from pims.image_sequence import ImageSequenceND # noqa from .cine import Cine # noqa from .norpix_reader import NorpixSeq # noqa from pims.tiff_stack import TiffStack_tifffile # noqa @@ -57,6 +57,33 @@ def raiser(*args, **kwargs): TiffStack = not_available("tifffile, libtiff, or PIL/Pillow") +# As above +from pims.image_sequence import (ImageSequence_skimage, ImageSequence_mpl, + ImageSequence_scipy, ImageSequence_pil, + ImageSequence_tifffile) +if not pims.image_sequence.skimage_available(): + ImageSequence_skimage = not_available('skimage') +if not pims.image_sequence.skimage_available(): + ImageSequence_mpl = not_availabe('matplotlib') +if not pims.image_sequence.scipy_available(): + ImageSequence_scipy = not_available('scipy') +if not pims.image_sequence.PIL_available(): + ImageSequence_pil = not_available('PIL/Pillow') +if not pims.image_sequence.tifffile_available(): + ImageSequence_tifffile = not_available('tifffile') + +if pims.image_sequence.skimage_available(): + ImageSequence = ImageSequence_skimage +elif pims.image_sequence.mpl_available(): + ImageSequence = ImageSequence_mpl +elif pims.iamge_sequence.scipy_avaiable(): + ImageSequence = ImageSequence_scipy +elif pims.image_sequence.PIL_avaialble(): + ImageSequence = ImageSequence_pil +# Never point ImageSequence to ImageSequence_tifffile; it only works on TIFF. +else: + ImageSequence = not_available("skimage, matplotlib, scipy, or PIL/Pillow") + try: import pims.bioformats if pims.bioformats.available(): diff --git a/pims/image_sequence.py b/pims/image_sequence.py index 24659c0a..b1301d0d 100644 --- a/pims/image_sequence.py +++ b/pims/image_sequence.py @@ -17,19 +17,52 @@ 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: - from skimage.io import imread + from skimage.io import imread as skimage_imread except ImportError: - try: - from matplotlib.pyplot import imread - except ImportError: - from scipy.ndimage import imread + skimage_imread = None +try: + from matplotlib.pyplot import imread as mpl_imread +except ImportError: + mpl_imread = None +try: + from scipy.ndimage import imread as scipy_imread +except ImportError: + scipy_imread = None +try: + from tifffile import imread as tifffile_imread +except ImportError: + tifffile_imread = None +try: + from PIL import Image +except ImportError: + pil_imread = None +else: + def pil_imread(filename): + return np.asarray(Image.open(filename)) + + +def skimage_available(): + return skimage_imread is not None + + +def mpl_available(): + return mpl_imread is not None + + +def scipy_available(): + return scipy_imread is not None + + +def tifffile_available(): + return tifffile_imread is not None + +def PIL_available(): + return pil_imread is not None -class ImageSequence(FramesSequence): + +class BaseImageSequence(FramesSequence): """Read a directory of sequentially numbered image files into an iterable that returns images as numpy arrays. @@ -48,10 +81,6 @@ class ImageSequence(FramesSequence): as_grey : boolean, optional Convert color images to greyscale. False by default. May not be used in conjection with process_func. - plugin : string - Passed on to skimage.io.imread if scikit-image is available. - If scikit-image is not available, this will be ignored and a warning - will be issued. Not available in combination with zipfiles. Examples -------- @@ -73,19 +102,8 @@ class ImageSequence(FramesSequence): >>> frame_shape = video.frame_shape # Pixel dimensions of video """ def __init__(self, path_spec, process_func=None, dtype=None, - as_grey=False, plugin=None): - try: - import skimage - except ImportError: - if plugin is not None: - warn("A plugin was specified but ignored. Plugins can only " - "be specified if scikit-image is available. Instead, " - "ImageSequence will try using matplotlib and scipy " - "in that order.") - self.kwargs = dict() - else: - self.kwargs = dict(plugin=plugin) - + as_grey=False, **kwargs): + self.kwargs = kwargs self._is_zipfile = False self._zipfile = None self._get_files(path_spec) @@ -104,17 +122,17 @@ def __init__(self, path_spec, process_func=None, dtype=None, def close(self): if self._is_zipfile: self._zipfile.close() - super(ImageSequence, self).close() + super(BaseImageSequence, self).close() def __del__(self): self.close() 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 self._imread(file_handle, **kwargs) else: - return imread(filename, **kwargs) + return self._imread(filename, **kwargs) def _get_files(self, path_spec): # deal with if input is _not_ a string @@ -132,9 +150,6 @@ def _get_files(self, path_spec): if fnmatch.fnmatch(fn, '*.*')] self._filepaths = sorted(filepaths, key=natural_keys) self._count = len(self._filepaths) - if 'plugin' in self.kwargs and self.kwargs['plugin'] is not None: - warn("A plugin cannot be combined with reading from an " - "archive. Extract it if you want to use the plugin.") return self.pathname = os.path.abspath(path_spec) # used by __repr__ @@ -193,6 +208,31 @@ def __repr__(self): dtype=self.pixel_type) +class ImageSequence_skimage(BaseImageSequence): + __doc__ = BaseImageSequence.__doc__ + _imread = skimage_imread + + +class ImageSequence_mpl(BaseImageSequence): + __doc__ = BaseImageSequence.__doc__ + _imread = mpl_imread + + +class ImageSequence_pil(BaseImageSequence): + __doc__ = BaseImageSequence.__doc__ + _imread = pil_imread + + +class ImageSequence_scipy(BaseImageSequence): + __doc__ = BaseImageSequence.__doc__ + _imread = scipy_imread + + +class ImageSequence_tifffile(BaseImageSequence): + __doc__ = BaseImageSequence.__doc__ + _imread = tifffile_imread + + def filename_to_indices(filename, identifiers='tzc'): """ Find ocurrences of dimension indices (e.g. t001, z06, c2) in a filename and returns a list of indices. @@ -225,7 +265,7 @@ def filename_to_indices(filename, identifiers='tzc'): return result -class ImageSequenceND(FramesSequenceND, ImageSequence): +class ImageSequenceND(FramesSequenceND, BaseImageSequence): """Read a directory of multi-indexed image files into an iterable that returns images as numpy arrays. By default, the extra dimensions are denoted with t, z, c. @@ -349,8 +389,8 @@ def customize_image_sequence(imread_func, name=None): >>> MyImageSequence = customize_image_sequence(my_func) >>> frames = MyImageSequence('path/to/my_weird_files*') """ - class CustomImageSequence(ImageSequence): - def imread(self, filename, **kwargs): + class CustomImageSequence(BaseImageSequence): + def _imread(self, filename, **kwargs): return imread_func(filename, **kwargs) if name is not None: CustomImageSequence.__name__ = name diff --git a/pims/tests/test_common.py b/pims/tests/test_common.py index 25b68df4..98baf297 100644 --- a/pims/tests/test_common.py +++ b/pims/tests/test_common.py @@ -510,54 +510,70 @@ def setUp(self): self.expected_len = 5 +class TestImageSequenceWithSkimage(_image_series): + def setUp(self): + self.klass = ImageSequence_skimage + try: + import skimage + except ImportError: + self.skip = True + else: + self.skip = False + super(TestImageSequenceWithPIL, self).setUp() + + class TestImageSequenceWithPIL(_image_series): def setUp(self): - self.filepath = os.path.join(path, 'image_sequence') - self.filenames = ['T76S3F00001.png', 'T76S3F00002.png', - 'T76S3F00003.png', 'T76S3F00004.png', - 'T76S3F00005.png'] - shape = (10, 11) - frames = save_dummy_png(self.filepath, self.filenames, shape) + self.klass = ImageSequence_pil + try: + from PIL import Image + except ImportError: + self.skip = 'PIL' + else: + self.skip = False + super(TestImageSequenceWithPIL, self).setUp() - self.filename = os.path.join(self.filepath, '*.png') - self.frame0 = frames[0] - self.frame1 = frames[1] - self.kwargs = dict(plugin='pil') - self.klass = pims.ImageSequence - self.v = self.klass(self.filename, **self.kwargs) - self.expected_shape = shape - self.expected_len = 5 - def test_bad_path_raises(self): - raises = lambda: pims.ImageSequence('this/path/does/not/exist/*.jpg') - self.assertRaises(IOError, raises) +class TestImageSequenceWithMPL(_image_series): + def setUp(self): + try: + import matplotlib + except ImportError: + self.skip = 'matplotlib' + else: + self.skip = False + self.klass = ImageSequence_mpl + super(TestImageSequenceWithMPL, self).setUp() - def tearDown(self): - clean_dummy_png(self.filepath, self.filenames) +class TestImageSequenceWithTifffile(_image_series): + def setUp(self): + try: + import tifffile + except ImportError: + self.skip = 'tifffile' + else: + self.skip = False + self.klass = ImageSequence_tifffile + super(TestImageSequenceWithTifffile, self).setUp() -class TestImageSequenceWithMPL(_image_series): + +class TestImageSequenceWithScipy(_image_series): def setUp(self): - self.filepath = os.path.join(path, 'image_sequence') - self.filenames = ['T76S3F00001.png', 'T76S3F00002.png', - 'T76S3F00003.png', 'T76S3F00004.png', - 'T76S3F00005.png'] - shape = (10, 11) - frames = save_dummy_png(self.filepath, self.filenames, shape) - self.filename = os.path.join(self.filepath, '*.png') - self.frame0 = frames[0] - self.frame1 = frames[1] - self.kwargs = dict(plugin='matplotlib') - self.klass = pims.ImageSequence - self.v = self.klass(self.filename, **self.kwargs) - self.expected_shape = shape - self.expected_len = 5 + try: + import scipy + except ImportError: + self.skip = 'scipy' + else: + self.skip = False + self.klass = ImageSequence_scipy + super(TestImageSequenceWithScipy, self).setUp() - def tearDown(self): - clean_dummy_png(self.filepath, self.filenames) -class TestImageSequenceAcceptsList(_image_series): +class ImageSequenceBase(_image_series): def setUp(self): + if self.skip: + raise nose.SkipTest('{0} is not installed'.format(self.skip)) self.filepath = os.path.join(path, 'image_sequence') self.filenames = ['T76S3F00001.png', 'T76S3F00002.png', 'T76S3F00003.png', 'T76S3F00004.png', @@ -569,12 +585,15 @@ def setUp(self): for fn in self.filenames] self.frame0 = frames[0] self.frame1 = frames[1] - self.kwargs = dict(plugin='matplotlib') - self.klass = pims.ImageSequence self.v = self.klass(self.filename, **self.kwargs) self.expected_shape = shape self.expected_len = len(self.filenames) + def test_bad_path_raises(self): + raises = lambda: pims.ImageSequence('this/path/does/not/exist/*.jpg') + self.assertRaises(IOError, raises) + + def tearDown(self): clean_dummy_png(self.filepath, self.filenames)