Finish merging multiimage capability into collections

This commit is contained in:
Steven Silvester
2014-11-07 15:22:19 +02:00
committed by Stefan van der Walt
parent b4838c3bd9
commit 76fc909b62
+106 -167
View File
@@ -75,167 +75,6 @@ def alphanumeric_key(s):
return k
class MultiImage(object):
"""A class containing a single multi-frame image.
Parameters
----------
filename : str
The complete path to the image file.
conserve_memory : bool, optional
Whether to conserve memory by only caching a single frame. Default is
True.
Notes
-----
If ``conserve_memory=True`` the memory footprint can be reduced, however
the performance can be affected because frames have to be read from file
more often.
The last accessed frame is cached, all other frames will have to be read
from file.
The current implementation makes use of PIL.
Examples
--------
>>> from skimage import data_dir
>>> img = MultiImage(data_dir + '/multipage.tif') # doctest: +SKIP
>>> len(img) # doctest: +SKIP
2
>>> for frame in img: # doctest: +SKIP
... print(frame.shape) # doctest: +SKIP
(15, 10)
(15, 10)
"""
def __init__(self, filename, conserve_memory=True, dtype=None):
"""Load a multi-img."""
self._filename = filename
self._conserve_memory = conserve_memory
self._dtype = dtype
self._cached = None
if filename.lower().endswith(('.tiff', '.tif')):
self.tif_img = img = TiffFile(self._filename)
else:
img = Image.open(self._filename)
self.tif_img = None
if self._conserve_memory:
self._numframes = self._find_numframes(img)
else:
self._frames = self._getallframes()
# TODO: this is not correct
self._numframes = self._frames.shape[0]
if not self.tif_img:
img.fp.close()
@property
def filename(self):
return self._filename
@property
def conserve_memory(self):
return self._conserve_memory
def _find_numframes(self, img):
"""Find the number of frames in the multi-img."""
if self.tif_img:
return len(img.pages)
else:
i = 0
while True:
i += 1
try:
img.seek(i)
except EOFError:
break
return i
def _getframe(self, framenum):
"""Open the image and extract the frame."""
if self.tif_img:
return self.tif_img[framenum].asarray()
else:
from ._io import imread
return imread(self.filename, self._dtype, img_num=framenum)
def _getallframes(self):
"""Extract all frames from the multi-img."""
if self.tif_img:
return self.tif_img.asarray()
else:
from ._io import imread
return imread(self.filename, self._dtype)
def __getitem__(self, n):
"""Return the n-th frame as an array.
Parameters
----------
n : int
Number of the required frame.
Returns
-------
frame : ndarray
The n-th frame.
"""
numframes = self._numframes
if -numframes <= n < numframes:
n = n % numframes
else:
raise IndexError("There are only %s frames in the image"
% numframes)
if self.conserve_memory:
if not self._cached == n:
frame = self._getframe(n)
self._cached = n
self._cachedframe = frame
return self._cachedframe
else:
return self._frames[n]
def __iter__(self):
"""Iterate over the frames."""
for i in range(len(self)):
yield self[i]
def __len__(self):
"""Number of images in collection."""
return self._numframes
def __str__(self):
return str(self.filename) + ' [%s frames]' % self._numframes
def concatenate(self):
"""Concatenate all images in the multi-image into an array.
Returns
-------
ar : np.ndarray
An array having one more dimension than the images in `self`.
See Also
--------
concatenate_images
Raises
------
ValueError
If images in the `MultiImage` don't have identical shapes.
"""
return concatenate_images(self)
class ImageCollection(object):
"""Load and manage a collection of image files.
@@ -295,6 +134,9 @@ class ImageCollection(object):
ic = ImageCollection('/tmp/*.png', load_func=imread_convert)
For files with multiple images, the images will be flattened into a list
and added to the list of available images.
Examples
--------
>>> import skimage.io as io
@@ -317,13 +159,16 @@ class ImageCollection(object):
for pattern in load_pattern:
self._files.extend(glob(pattern))
self._files = sorted(self._files, key=alphanumeric_key)
self._numframes = self._find_images()
else:
self._files = load_pattern
self._numframes = len(load_pattern)
self._frame_index = None
if conserve_memory:
memory_slots = 1
else:
memory_slots = len(self._files)
memory_slots = self._numframes
self._conserve_memory = conserve_memory
self._cached = None
@@ -344,6 +189,32 @@ class ImageCollection(object):
def conserve_memory(self):
return self._conserve_memory
def _find_images(self):
index = []
for fname in self._files:
if fname.lower().endswith(('.tiff', '.tif')):
img = TiffFile(fname)
index += [(fname, i) for i in range(len(img.pages))]
else:
im = Image.open(fname)
try:
# this will raise an IOError if the file is not readable
im.getdata()[0]
except IOError:
site = "http://pillow.readthedocs.org/en/latest/installation.html#external-libraries"
raise ValueError('Could not load "%s"\nPlease see documentation at: %s' % (fname, site))
else:
i = 0
while True:
i += 1
index.append((fname, i))
try:
im.seek(i)
except EOFError:
break
self._frame_index = index
return len(index)
def __getitem__(self, n):
"""Return selected image(s) in the collection.
@@ -374,7 +245,12 @@ class ImageCollection(object):
if (self.conserve_memory and n != self._cached) or \
(self.data[idx] is None):
self.data[idx] = self.load_func(self.files[n])
if self._frame_index:
fname, img_num = self._frame_index[idx]
print('loading function')
self.data[idx] = self.load_func(fname, img_num=img_num)
else:
self.data[idx] = self.load_func(self.files[n])
self._cached = n
return self.data[idx]
@@ -383,9 +259,17 @@ class ImageCollection(object):
# object. Any loaded image data in the original ImageCollection
# will be copied by reference to the new object. Image data
# loaded after this creation is not linked.
fidx = range(len(self.files))[n]
fidx = range(self._numframes)[n]
new_ic = copy(self)
new_ic._files = [self.files[i] for i in fidx]
if self._frame_index:
new_ic._files = [self._frame_index[i][0] for i in fidx]
new_ic._frame_index = [self._frame_index[i] for i in fidx]
else:
new_ic._files = [self._files[i] for i in fidx]
new_ic._numframes = len(fidx)
if self.conserve_memory:
if self._cached in fidx:
new_ic._cached = fidx.index(self._cached)
@@ -398,7 +282,7 @@ class ImageCollection(object):
def _check_imgnum(self, n):
"""Check that the given image number is valid."""
num = len(self.files)
num = self._numframes
if -num <= n < num:
n = n % num
else:
@@ -413,7 +297,7 @@ class ImageCollection(object):
def __len__(self):
"""Number of images in collection."""
return len(self.files)
return self._numframes
def __str__(self):
return str(self.files)
@@ -474,3 +358,58 @@ def imread_collection_wrapper(imread):
return ImageCollection(load_pattern, conserve_memory=conserve_memory,
load_func=imread)
return imread_collection
class MultiImage(ImageCollection):
"""A class containing a single multi-frame image.
Parameters
----------
filename : str
The complete path to the image file.
conserve_memory : bool, optional
Whether to conserve memory by only caching a single frame. Default is
True.
Notes
-----
If ``conserve_memory=True`` the memory footprint can be reduced, however
the performance can be affected because frames have to be read from file
more often.
The last accessed frame is cached, all other frames will have to be read
from file.
The current implementation makes use of ``tifffile`` for Tiff files and
PIL otherwise.
Examples
--------
>>> from skimage import data_dir
>>> img = MultiImage(data_dir + '/multipage.tif') # doctest: +SKIP
>>> len(img) # doctest: +SKIP
2
>>> for frame in img: # doctest: +SKIP
... print(frame.shape) # doctest: +SKIP
(15, 10)
(15, 10)
"""
def __init__(self, filename, conserve_memory=True, dtype=None):
"""Load a multi-img."""
from ._io import imread
def load_func(fname, **kwargs):
kwargs.setdefault('dtype', dtype)
return imread(fname, **kwargs)
self._filename = filename
super(MultiImage, self).__init__(filename, conserve_memory,
load_func=load_func)
@property
def filename(self):
return self._filename