Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 28 additions & 1 deletion pims/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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():
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would opt for an (optional) classmethod pkg_available() for every reader class.

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():
Expand Down
112 changes: 76 additions & 36 deletions pims/image_sequence.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you move this to utils then we can make use of this in display.py too



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.

Expand All @@ -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
--------
Expand All @@ -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,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this works:

@classmethod
def pkg_available(cls):
    return cls._imread is not 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)
Expand All @@ -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
Expand All @@ -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__
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -225,7 +265,7 @@ def filename_to_indices(filename, identifiers='tzc'):
return result


class ImageSequenceND(FramesSequenceND, ImageSequence):
class ImageSequenceND(FramesSequenceND, BaseImageSequence):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it makes sense to make this BaseImageSequenceND

"""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.
Expand Down Expand Up @@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

__doc__ = BaseImageSequence.__doc__

Expand Down
97 changes: 58 additions & 39 deletions pims/tests/test_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -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)

Expand Down