diff --git a/conda/meta.yaml b/conda/meta.yaml index a900b753e..f59319e14 100644 --- a/conda/meta.yaml +++ b/conda/meta.yaml @@ -15,8 +15,9 @@ requirements: # Scientific Python Stack - numpy >=1.10,<1.11 - - scipy 0.16.* + - scipy 0.17.* - pillow 3.0.* + - imageio 1.5.* # Features - cyvlfeat >=0.4.2,<0.5 diff --git a/menpo/io/__init__.py b/menpo/io/__init__.py index b9347e790..7d96a07c1 100644 --- a/menpo/io/__init__.py +++ b/menpo/io/__init__.py @@ -1,7 +1,8 @@ -from .input import (import_image, import_images, +from .input import (import_image, import_images, import_video, import_videos, import_landmark_file, import_landmark_files, import_pickle, import_pickles, import_builtin_asset, - image_paths, landmark_file_paths, + image_paths, landmark_file_paths, video_paths, data_path_to, data_dir_path, ls_builtin_assets) -from .output import export_image, export_landmark_file, export_pickle +from .output import (export_image, export_landmark_file, export_pickle, + export_video) diff --git a/menpo/io/input/__init__.py b/menpo/io/input/__init__.py index 09cd38dcb..874703fe6 100644 --- a/menpo/io/input/__init__.py +++ b/menpo/io/input/__init__.py @@ -1,6 +1,6 @@ -from .base import (import_image, import_images, +from .base import (import_image, import_images, import_video, import_videos, import_builtin_asset, import_landmark_file, import_landmark_files, data_path_to, data_dir_path, ls_builtin_assets, - image_paths, landmark_file_paths, + image_paths, landmark_file_paths, video_paths, import_pickle, import_pickles) diff --git a/menpo/io/input/base.py b/menpo/io/input/base.py index 3dac4d8c6..91aafc540 100644 --- a/menpo/io/input/base.py +++ b/menpo/io/input/base.py @@ -57,6 +57,17 @@ def same_name(path): return {p.suffix[1:].upper(): p for p in landmark_file_paths(pattern)} +def same_name_video(path, frame_number): + r""" + Menpo's default video landmark resolver. Returns all landmarks found to have + the same stem as the asset. + """ + # pattern finding all landmarks with the same stem + pattern = path.with_name('{}_{}.*'.format(path.stem, frame_number)) + # find all the landmarks we can with this name. Key is ext (without '.') + return {p.suffix[1:].upper(): p for p in landmark_file_paths(pattern)} + + def import_image(filepath, landmark_resolver=same_name, normalise=True): r"""Single image (and associated landmarks) importer. @@ -98,6 +109,59 @@ def import_image(filepath, landmark_resolver=same_name, normalise=True): importer_kwargs=kwargs) +def import_video(filepath, landmark_resolver=same_name_video, normalise=True, + importer_method='ffmpeg'): + r"""Single video (and associated landmarks) importer. + + If a video file is found at `filepath`, returns an :map:`LazyList` wrapping + all the frames of the video. By default, landmark files sharing the same + filename stem will be imported and attached with a group name based on the + extension of the landmark file appended with the frame number, although this + behavior can be customised (see `landmark_resolver`). + + Parameters + ---------- + filepath : `pathlib.Path` or `str` + A relative or absolute filepath to a video file. + landmark_resolver : `function`, optional + This function will be used to find landmarks for the + video. The function should take two arguments (the path to the video and + the frame number) and return a dictionary of the form ``{'group_name': + 'landmark_filepath'}`` Default finds landmarks with the same name as the + video file, appended with '_{frame_number}'. + normalise : `bool`, optional + If ``True``, normalise the frame pixels between 0 and 1 and convert + to floating point. If ``False``, the native datatype of the image will + be maintained (commonly `uint8`). Note that in general Menpo assumes + :map:`Image` instances contain floating point data - if you disable this + flag you will have to manually convert the farmes you import to floating + point before doing most Menpo operations. This however can be useful to + save on memory usage if you only wish to view or crop the frames. + importer_method : {'ffmpeg', 'avconv'}, optional + A string representing the type of importer to use, by default ffmpeg + is used. + + Returns + ------- + frames : :map:`LazyList` + An lazy list of :map:`Image` or subclass thereof which wraps the frames + of the video. This list can be treated as a normal list, but the frame + is only read when the video is indexed or iterated. + """ + kwargs = {'normalise': normalise} + + video_importer_methods = {'ffmpeg': ffmpeg_video_types} + if importer_method not in video_importer_methods: + raise ValueError('Unsupported importer method requested. Valid values ' + 'are: {}'.format(video_importer_methods.keys())) + + return _import(filepath, video_importer_methods[importer_method], + landmark_ext_map=image_landmark_types, + landmark_resolver=landmark_resolver, + landmark_attach_func=_import_lazylist_attach_landmarks, + importer_kwargs=kwargs) + + def import_landmark_file(filepath, asset=None): r"""Single landmark group importer. @@ -223,6 +287,99 @@ def import_images(pattern, max_images=None, shuffle=False, ) +def import_videos(pattern, max_videos=None, shuffle=False, + landmark_resolver=same_name_video, normalise=True, + importer_method='ffmpeg', as_generator=False, verbose=False): + r"""Multiple video (and associated landmarks) importer. + + For each video found yields a :map:`LazyList`. By default, landmark files + sharing the same filename stem will be imported and attached with a group + name based on the extension of the landmark file appended with the frame + number, although this behavior can be customised (see `landmark_resolver`). + + Note that this is a function returns a :map:`LazyList`. Therefore, the + function will return immediately and indexing into the returned list + will load an image at run time. If all images should be loaded, then simply + wrap the returned :map:`LazyList` in a Python `list`. + + Parameters + ---------- + pattern : `str` + A glob path pattern to search for videos. Every video found to match + the glob will be imported one by one. See :map:`video_paths` for more + details of what videos will be found. + max_videos : positive `int`, optional + If not ``None``, only import the first ``max_videos`` found. Else, + import all. + shuffle : `bool`, optional + If ``True``, the order of the returned videos will be randomised. If + ``False``, the order of the returned videos will be alphanumerically + ordered. + landmark_resolver : `function`, optional + This function will be used to find landmarks for the + video. The function should take two arguments (the path to the video and + the frame number) and return a dictionary of the form ``{'group_name': + 'landmark_filepath'}`` Default finds landmarks with the same name as the + video file, appended with '_{frame_number}'. + normalise : `bool`, optional + If ``True``, normalise the frame pixels between 0 and 1 and convert + to floating point. If ``False``, the native datatype of the image will + be maintained (commonly `uint8`). Note that in general Menpo assumes + :map:`Image` instances contain floating point data - if you disable this + flag you will have to manually convert the farmes you import to floating + point before doing most Menpo operations. This however can be useful to + save on memory usage if you only wish to view or crop the frames. + importer_method : {'ffmpeg', 'avconv'}, optional + A string representing the type of importer to use, by default ffmpeg + is used. + as_generator : `bool`, optional + If ``True``, the function returns a generator and assets will be yielded + one after another when the generator is iterated over. + verbose : `bool`, optional + If ``True`` progress of the importing will be dynamically reported with + a progress bar. + + Returns + ------- + lazy_list : :map:`LazyList` or generator of :map:`LazyList` + A :map:`LazyList` or generator yielding :map:`LazyList` instances that + wrap the video object. + + Raises + ------ + ValueError + If no videos are found at the provided glob. + + Examples + -------- + Import videos at and rescale every frame of each video: + + >>> videos = [] + >>> for video in menpo.io.import_videos('./set_of_videos/*'): + >>> frames = [] + >>> for frame in video: + >>> # rescale to a sensible size as we go + >>> frames.append(frame.rescale(0.2)) + >>> videos.append(frames) + """ + kwargs = {'normalise': normalise} + video_importer_methods = {'ffmpeg': ffmpeg_video_types} + if importer_method not in video_importer_methods: + raise ValueError('Unsupported importer method requested. Valid values ' + 'are: {}'.format(video_importer_methods.keys())) + + return _import_glob_lazy_list( + pattern, video_importer_methods[importer_method], + max_assets=max_videos, shuffle=shuffle, + landmark_resolver=landmark_resolver, + landmark_ext_map=image_landmark_types, + landmark_attach_func=_import_lazylist_attach_landmarks, + as_generator=as_generator, + verbose=verbose, + importer_kwargs=kwargs + ) + + def import_landmark_files(pattern, max_landmarks=None, shuffle=False, as_generator=False, verbose=False): r"""Import Multiple landmark files. @@ -387,6 +544,13 @@ def image_paths(pattern): return glob_with_suffix(pattern, image_types) +def video_paths(pattern): + r""" + Return video filepaths that Menpo can import that match the glob pattern. + """ + return glob_with_suffix(pattern, ffmpeg_video_types) + + def landmark_file_paths(pattern): r""" Return landmark file filepaths that Menpo can import that match the glob @@ -446,6 +610,30 @@ def _import_object_attach_landmarks(built_objects, landmark_resolver, x.landmarks[group_name] = lms +def _import_lazylist_attach_landmarks(built_objects, landmark_resolver, + landmark_ext_map=None): + # handle landmarks + if landmark_ext_map is not None: + for k in range(len(built_objects)): + x = built_objects[k] + # Use the users function to find landmarks + lm_paths = partial(landmark_resolver, x.path) + + # Do a little trick where we compose the landmark resolution onto + # the lazy list indexing - after the item has been indexed. + def wrap_landmarks(f, index): + obj = f() + for group_name, lm_path in lm_paths(index).items(): + lms = _import(lm_path, landmark_ext_map, asset=obj) + if obj.n_dims == lms.n_dims: + obj.landmarks[group_name] = lms + return obj + + new_ll = LazyList([partial(wrap_landmarks, c, i) + for i, c in enumerate(x._callables)]) + built_objects[k] = new_ll + + def _import(filepath, extensions_map, landmark_resolver=same_name, landmark_ext_map=None, landmark_attach_func=None, asset=None, importer_kwargs=None): @@ -495,7 +683,7 @@ def _import(filepath, extensions_map, landmark_resolver=same_name, built_objects = importer.build() # landmarks are iterable so check for list precisely # enforce a list to make processing consistent - if not isinstance(built_objects, Sequence): + if not isinstance(built_objects, list): built_objects = [built_objects] # attach path if there is no x.path already. @@ -675,12 +863,13 @@ def build(self): object : object or list An instantiated class of the expected type. For example, for an `.obj` importer, this would be a - :class:`menpo.shape.mesh.base.Trimesh`. If multiple objects need - to be returned from one importer, a list must be returned. + :class:`menpo.shape.Trimesh`. If multiple objects need + to be returned from one importer, a list must be returned (and + not a subclass of list - explicitly a list). """ raise NotImplementedError() # Avoid circular imports from menpo.io.input.extensions import (image_landmark_types, image_types, - pickle_types) + pickle_types, ffmpeg_video_types) diff --git a/menpo/io/input/extensions.py b/menpo/io/input/extensions.py index adc9ce4db..cc1fd70a9 100644 --- a/menpo/io/input/extensions.py +++ b/menpo/io/input/extensions.py @@ -1,24 +1,26 @@ -# A list of extensions that different importers support. from .landmark import LM2Importer, LJSONImporter -from .image import PILImporter, PILGIFImporter, ABSImporter, FLOImporter +from .image import (PILImporter, PILGIFImporter, ImageioImporter, ABSImporter, + FLOImporter) +from .video import ImageioFFMPEGImporter from .landmark_image import ImageASFImporter, ImagePTSImporter from .pickle import PickleImporter, GZipPickleImporter -image_types = {'.bmp': PILImporter, + +image_types = {'.bmp': ImageioImporter, '.dib': PILImporter, '.dcx': PILImporter, '.eps': PILImporter, '.ps': PILImporter, '.gif': PILGIFImporter, '.im': PILImporter, - '.jpg': PILImporter, - '.jpg2': PILImporter, + '.jpg': ImageioImporter, + '.jpg2': ImageioImporter, '.jpx': PILImporter, - '.jpe': PILImporter, - '.jpeg': PILImporter, + '.jpe': ImageioImporter, + '.jpeg': ImageioImporter, '.pcd': PILImporter, '.pcx': PILImporter, - '.png': PILImporter, + '.png': ImageioImporter, '.pbm': PILImporter, '.pgm': PILImporter, '.ppm': PILImporter, @@ -26,11 +28,13 @@ '.tif': PILImporter, '.tiff': PILImporter, '.xbm': PILImporter, - # '.pdf': PILImporter, '.xpm': PILImporter, '.abs': ABSImporter, '.flo': FLOImporter} + +ffmpeg_video_types = ImageioFFMPEGImporter.ffmpeg_types() + image_landmark_types = {'.asf': ImageASFImporter, '.lm2': LM2Importer, '.pts': ImagePTSImporter, diff --git a/menpo/io/input/image.py b/menpo/io/input/image.py index e06ecf1bf..f010b5af2 100644 --- a/menpo/io/input/image.py +++ b/menpo/io/input/image.py @@ -1,8 +1,9 @@ import numpy as np - +from pathlib import Path from .base import Importer from menpo.image.base import normalise_rolled_pixels from menpo.image import Image, MaskedImage, BooleanImage +from menpo.image.base import normalise_rolled_pixels class PILImporter(Importer): @@ -45,9 +46,9 @@ def build(self): self._pil_image = PILImage.open(self.filepath) mode = self._pil_image.mode if mode == 'RGBA': - # RGB with Alpha Channel - # If we normalise it then we convert to floating point - # and set the alpha channel to the mask + # If normalise is False, then we return the alpha as an extra + # channel, which can be useful if the alpha channel has semantic + # meanings! if self.normalise: alpha = np.array(self._pil_image)[..., 3].astype(np.bool) image_pixels = self._pil_to_numpy(True, @@ -202,3 +203,42 @@ def build(self): return Image(uv, copy=False) + +class ImageioImporter(Importer): + r""" + Imports images using the imageio library - which is actually fairly similar + to our importing logic - but contains the necessary plugins to import lots + of interesting image types like RAW images. + + Parameters + ---------- + filepath : string + Absolute filepath of the image. + normalise : `bool`, optional + If ``True``, normalise between 0.0 and 1.0 and convert to float. If + ``False`` just return whatever imageio imports. + """ + + def __init__(self, filepath, normalise=True): + super(ImageioImporter, self).__init__(filepath) + self._pil_image = None + self.normalise = normalise + + def build(self): + import imageio + + pixels = imageio.imread(self.filepath) + + transparent_types = {'.png'} + filepath = Path(self.filepath) + if pixels.shape[-1] == 4 and filepath.suffix in transparent_types: + # If normalise is False, then we return the alpha as an extra + # channel, which can be useful if the alpha channel has semantic + # meanings! + if self.normalise: + p = normalise_rolled_pixels(pixels[..., :3], True) + return MaskedImage(p, mask=pixels[..., -1].astype(np.bool)) + else: + return Image(normalise_rolled_pixels(pixels, False)) + else: + return Image(normalise_rolled_pixels(pixels, self.normalise)) diff --git a/menpo/io/input/video.py b/menpo/io/input/video.py new file mode 100644 index 000000000..19debeec2 --- /dev/null +++ b/menpo/io/input/video.py @@ -0,0 +1,53 @@ +from functools import partial + +from menpo.image.base import normalise_rolled_pixels +from menpo.image import Image +from menpo.base import LazyList + +from .base import Importer + + +class ImageioFFMPEGImporter(Importer): + r""" + Imports videos using the FFMPEG plugin from the imageio library. Returns a + generator that yields on a per-frame basis. + + Parameters + ---------- + filepath : string + Absolute filepath of the video. + normalise : `bool`, optional + If ``True``, normalise between 0.0 and 1.0 and convert to float. If + ``False`` just return whatever imageio imports. + """ + + def __init__(self, filepath, normalise=True): + super(ImageioFFMPEGImporter, self).__init__(filepath) + self.normalise = normalise + + @classmethod + def ffmpeg_types(cls): + try: + import imageio + + # Lazy way to get all the extensions supported by imageio/FFMPEG + ffmpeg_exts = imageio.formats['ffmpeg'].extensions + return dict(zip(ffmpeg_exts, + [ImageioFFMPEGImporter] * len(ffmpeg_exts))) + except ImportError: + return {} + + def build(self): + import imageio + + reader = imageio.get_reader(self.filepath, format='ffmpeg', mode='I') + + def imageio_to_menpo(imio_reader, index): + norm_pix = normalise_rolled_pixels(imio_reader.get_data(index), + self.normalise) + return Image(norm_pix) + + index_callable = partial(imageio_to_menpo, reader) + ll = LazyList.init_from_index_callable(index_callable, reader.get_length()) + ll.fps = reader.get_meta_data()['fps'] + return ll diff --git a/menpo/io/output/__init__.py b/menpo/io/output/__init__.py index 262f5f743..68e54e440 100644 --- a/menpo/io/output/__init__.py +++ b/menpo/io/output/__init__.py @@ -1 +1,2 @@ -from .base import export_landmark_file, export_image, export_pickle +from .base import (export_landmark_file, export_image, export_pickle, + export_video) diff --git a/menpo/io/output/base.py b/menpo/io/output/base.py index 4bd56fbee..a5837ee05 100644 --- a/menpo/io/output/base.py +++ b/menpo/io/output/base.py @@ -3,7 +3,7 @@ from pathlib import Path from menpo.compatibility import basestring, str -from .extensions import landmark_types, image_types, pickle_types +from .extensions import landmark_types, image_types, pickle_types, video_types from ..utils import _norm_path # an open file handle that uses a small fast level of compression @@ -90,6 +90,50 @@ def export_image(image, fp, extension=None, overwrite=False): _export(image, fp, image_types, extension, overwrite) +def export_video(images, filepath, overwrite=False, fps=30, **kwargs): + r""" + Exports a given list of images as a video. The ``filepath`` argument is + a `str` representing the path to save the video to. If a file is provided, + the ``extension`` kwarg **must** be provided. The export type is calculated + based on the filepath extension. + + Due to the mix of string and file types, an explicit overwrite argument is + used which is ``False`` by default. + + Note that exporting of GIF images is also supported. + + Parameters + ---------- + images : list of :map:`Image` + The images to export as a video. + filepath : `str` + The string path to save the video at. + overwrite : `bool`, optional + Whether or not to overwrite a file if it already exists. + fps : `int`, optional + The number of frames per second. + **kwargs : `dict`, optional + Extra parameters that are passed through directly to the exporter. + Please see the documentation in the ``menpo.io.output.video`` package + for information about the supported arguments. + + Raises + ------ + ValueError + File already exists and ``overwrite`` != ``True`` + ValueError + The provided extension does not match to an existing exporter type + (the output type is not supported). + """ + exporter_kwargs = {'fps': fps} + exporter_kwargs.update(kwargs) + path_filepath = _validate_filepath(str(filepath), None, overwrite) + + export_function = _extension_to_export_function( + path_filepath.suffix, video_types) + export_function(images, path_filepath, **exporter_kwargs) + + def export_pickle(obj, fp, overwrite=False, protocol=2): r""" Exports a given collection of Python objects with Pickle. diff --git a/menpo/io/output/extensions.py b/menpo/io/output/extensions.py index 1e2fb68d1..92ffa2d5f 100644 --- a/menpo/io/output/extensions.py +++ b/menpo/io/output/extensions.py @@ -1,12 +1,15 @@ from .landmark import LJSONExporter, PTSExporter from .image import PILExporter +from .video import ImageioVideoExporter, ImageioGifExporter from .pickle import pickle_export + landmark_types = { '.ljson': LJSONExporter, '.pts': PTSExporter } + image_types = { '.bmp': PILExporter, '.dib': PILExporter, @@ -31,6 +34,19 @@ '.xpm': PILExporter } + pickle_types = { '.pkl': pickle_export, } + + +video_types = { + '.mov': ImageioVideoExporter, + '.avi': ImageioVideoExporter, + '.mpg': ImageioVideoExporter, + '.mpeg': ImageioVideoExporter, + '.mp4': ImageioVideoExporter, + '.mkv': ImageioVideoExporter, + '.wmv': ImageioVideoExporter, + '.gif': ImageioGifExporter +} diff --git a/menpo/io/output/video.py b/menpo/io/output/video.py new file mode 100644 index 000000000..ef53984be --- /dev/null +++ b/menpo/io/output/video.py @@ -0,0 +1,67 @@ +import numpy as np + + +def ImageioVideoExporter(images, out_path, fps=30, codec='libx264', + quality=None, bitrate=None, pixelformat='yuv420p'): + r""" + Uses imageio to export the images using FFMPEG. Please see the imageio + documentation for more information. + + Parameters + ---------- + fps : `int`, optional + The number of frames per second. + codec : `str`, optional + The video codec to use. Default 'libx264', which represents the + widely available mpeg4. Except when saving .wmv files, then the + defaults is 'msmpeg4' which is more commonly supported for windows + quality : `float` or `None` + Video output quality. Uses variable bit rate. Highest + quality is 10, lowest is 0. Specifying a fixed bitrate using ``bitrate`` + disables this parameter. + bitrate : `int` or `None`, optional + Set a constant bitrate for the video encoding. Default is ``None`` + causing ``quality` parameter to be used instead. Better quality videos + with smaller file sizes will result from using the ``quality`` variable + bitrate parameter rather than specifying a fixed bitrate with this + parameter. + pixelformat: `str`, optional + The output video pixel format. + """ + import imageio + + writer = imageio.get_writer(str(out_path), mode='I', fps=fps, + codec=codec, quality=quality, bitrate=bitrate, + pixelformat=pixelformat) + + for v in images: + v = np.array(v.as_PILImage()) + writer.append_data(v) + writer.close() + + +def ImageioGifExporter(images, out_path, fps=30, loop=0, duration=None): + r""" + Uses imageio to export the images to a gIF. Please see the imageio + documentation for more information. + + Parameters + ---------- + fps : `float`, optional + The number of frames per second. If ``duration`` is not given, the + duration for each frame is set to 1/fps. + loop : `int`, optional + The number of iterations. 0 means loop indefinitely + duration : `float` or list of `float`, optional + The duration (in seconds) of each frame. Either specify one value + that is used for all frames, or one value for each frame. + """ + import imageio + + writer = imageio.get_writer(str(out_path), mode='I', fps=fps, + loop=loop, duration=duration) + + for v in images: + v = np.array(v.as_PILImage()) + writer.append_data(v) + writer.close() diff --git a/menpo/io/test/io_import_test.py b/menpo/io/test/io_import_test.py index ece0efef9..17ac96610 100644 --- a/menpo/io/test/io_import_test.py +++ b/menpo/io/test/io_import_test.py @@ -167,28 +167,68 @@ def test_import_images_wrong_path_raises_value_error(): def test_import_landmark_files_wrong_path_raises_value_error(): list(mio.import_landmark_files('asldfjalkgjlaknglkajlekjaltknlaekstjlakj')) +@patch('imageio.imread') +@patch('menpo.io.input.base.Path.is_file') +def test_importing_imageio_RGBA_no_normalise(is_file, mock_image): + mock_image.return_value = np.zeros([10, 10, 4], dtype=np.uint8) + is_file.return_value = True -@patch('PIL.Image.open') + im = mio.import_image('fake_image_being_mocked.png', normalise=False) + assert im.shape == (10, 10) + assert im.n_channels == 4 + assert im.pixels.dtype == np.uint8 + + +@patch('imageio.imread') @patch('menpo.io.input.base.Path.is_file') -def test_importing_RGBA_no_normalise(is_file, mock_image): - mock_image.return_value = PILImage.new('RGBA', (10, 10)) +def test_importing_imageio_RGBA_normalise(is_file, mock_image): + from menpo.image import MaskedImage + + mock_image.return_value = np.zeros([10, 10, 4], dtype=np.uint8) + is_file.return_value = True + + im = mio.import_image('fake_image_being_mocked.png', normalise=True) + assert im.shape == (10, 10) + assert im.n_channels == 3 + assert im.pixels.dtype == np.float + assert type(im) == MaskedImage + + +@patch('imageio.imread') +@patch('menpo.io.input.base.Path.is_file') +def test_importing_imageio_RGB_normalise(is_file, mock_image): + + mock_image.return_value = np.zeros([10, 10, 3], dtype=np.uint8) + is_file.return_value = True + + im = mio.import_image('fake_image_being_mocked.jpg', normalise=True) + assert im.shape == (10, 10) + assert im.n_channels == 3 + assert im.pixels.dtype == np.float + + +@patch('imageio.imread') +@patch('menpo.io.input.base.Path.is_file') +def test_importing_imageio_RGB_no_normalise(is_file, mock_image): + + mock_image.return_value = np.zeros([10, 10, 3], dtype=np.uint8) is_file.return_value = True im = mio.import_image('fake_image_being_mocked.jpg', normalise=False) assert im.shape == (10, 10) - assert im.n_channels == 4 + assert im.n_channels == 3 assert im.pixels.dtype == np.uint8 @patch('PIL.Image.open') @patch('menpo.io.input.base.Path.is_file') -def test_importing_RGBA_normalise(is_file, mock_image): +def test_importing_PIL_RGBA_normalise(is_file, mock_image): from menpo.image import MaskedImage mock_image.return_value = PILImage.new('RGBA', (10, 10)) is_file.return_value = True - im = mio.import_image('fake_image_being_mocked.jpg', normalise=True) + im = mio.import_image('fake_image_being_mocked.ppm', normalise=True) assert im.shape == (10, 10) assert im.n_channels == 3 assert im.pixels.dtype == np.float @@ -197,11 +237,24 @@ def test_importing_RGBA_normalise(is_file, mock_image): @patch('PIL.Image.open') @patch('menpo.io.input.base.Path.is_file') -def test_importing_L_no_normalise(is_file, mock_image): +def test_importing_PIL_RGBA_no_normalise(is_file, mock_image): + + mock_image.return_value = PILImage.new('RGBA', (10, 10)) + is_file.return_value = True + + im = mio.import_image('fake_image_being_mocked.ppm', normalise=False) + assert im.shape == (10, 10) + assert im.n_channels == 4 + assert im.pixels.dtype == np.uint8 + + +@patch('PIL.Image.open') +@patch('menpo.io.input.base.Path.is_file') +def test_importing_PIL_L_no_normalise(is_file, mock_image): mock_image.return_value = PILImage.new('L', (10, 10)) is_file.return_value = True - im = mio.import_image('fake_image_being_mocked.jpg', normalise=False) + im = mio.import_image('fake_image_being_mocked.ppm', normalise=False) assert im.shape == (10, 10) assert im.n_channels == 1 assert im.pixels.dtype == np.uint8 @@ -209,11 +262,11 @@ def test_importing_L_no_normalise(is_file, mock_image): @patch('PIL.Image.open') @patch('menpo.io.input.base.Path.is_file') -def test_importing_L_normalise(is_file, mock_image): +def test_importing_PIL_L_normalise(is_file, mock_image): mock_image.return_value = PILImage.new('L', (10, 10)) is_file.return_value = True - im = mio.import_image('fake_image_being_mocked.jpg', normalise=True) + im = mio.import_image('fake_image_being_mocked.ppm', normalise=True) assert im.shape == (10, 10) assert im.n_channels == 1 assert im.pixels.dtype == np.float @@ -221,21 +274,21 @@ def test_importing_L_normalise(is_file, mock_image): @patch('PIL.Image.open') @patch('menpo.io.input.base.Path.is_file') -@raises(ValueError) -def test_importing_I_normalise(is_file, mock_image): +@raises +def test_importing_PIL_I_normalise(is_file, mock_image): mock_image.return_value = PILImage.new('I', (10, 10)) is_file.return_value = True - mio.import_image('fake_image_being_mocked.jpg', normalise=True) + im = mio.import_image('fake_image_being_mocked.ppm', normalise=True) @patch('PIL.Image.open') @patch('menpo.io.input.base.Path.is_file') -def test_importing_I_no_normalise(is_file, mock_image): +def test_importing_PIL_I_no_normalise(is_file, mock_image): mock_image.return_value = PILImage.new('I', (10, 10)) is_file.return_value = True - im = mio.import_image('fake_image_being_mocked.jpg', normalise=False) + im = mio.import_image('fake_image_being_mocked.ppm', normalise=False) assert im.shape == (10, 10) assert im.n_channels == 1 assert im.pixels.dtype == np.int32 @@ -243,13 +296,13 @@ def test_importing_I_no_normalise(is_file, mock_image): @patch('PIL.Image.open') @patch('menpo.io.input.base.Path.is_file') -def test_importing_1_normalise(is_file, mock_image): +def test_importing_PIL_1_normalise(is_file, mock_image): from menpo.image import BooleanImage mock_image.return_value = PILImage.new('1', (10, 10)) is_file.return_value = True - im = mio.import_image('fake_image_being_mocked.jpg', normalise=True) + im = mio.import_image('fake_image_being_mocked.ppm', normalise=True) assert im.shape == (10, 10) assert im.n_channels == 1 assert im.pixels.dtype == np.bool @@ -258,13 +311,13 @@ def test_importing_1_normalise(is_file, mock_image): @patch('PIL.Image.open') @patch('menpo.io.input.base.Path.is_file') -def test_importing_1_no_normalise(is_file, mock_image): +def test_importing_PIL_1_no_normalise(is_file, mock_image): from menpo.image import BooleanImage mock_image.return_value = PILImage.new('1', (10, 10)) is_file.return_value = True - im = mio.import_image('fake_image_being_mocked.jpg', normalise=False) + im = mio.import_image('fake_image_being_mocked.ppm', normalise=False) assert im.shape == (10, 10) assert im.n_channels == 1 assert im.pixels.dtype == np.bool @@ -273,11 +326,11 @@ def test_importing_1_no_normalise(is_file, mock_image): @patch('PIL.Image.open') @patch('menpo.io.input.base.Path.is_file') -def test_importing_P_normalise(is_file, mock_image): +def test_importing_PIL_P_normalise(is_file, mock_image): mock_image.return_value = PILImage.new('P', (10, 10)) is_file.return_value = True - im = mio.import_image('fake_image_being_mocked.jpg', normalise=True) + im = mio.import_image('fake_image_being_mocked.ppm', normalise=True) assert im.shape == (10, 10) assert im.n_channels == 3 assert im.pixels.dtype == np.float @@ -285,11 +338,11 @@ def test_importing_P_normalise(is_file, mock_image): @patch('PIL.Image.open') @patch('menpo.io.input.base.Path.is_file') -def test_importing_P_no_normalise(is_file, mock_image): +def test_importing_PIL_P_no_normalise(is_file, mock_image): mock_image.return_value = PILImage.new('P', (10, 10)) is_file.return_value = True - im = mio.import_image('fake_image_being_mocked.jpg', normalise=False) + im = mio.import_image('fake_image_being_mocked.ppm', normalise=False) assert im.shape == (10, 10) assert im.n_channels == 3 assert im.pixels.dtype == np.uint8 @@ -297,7 +350,7 @@ def test_importing_P_no_normalise(is_file, mock_image): @patch('PIL.Image.open') @patch('menpo.io.input.base.Path.is_file') -def test_importing_GIF_normalise(is_file, mock_image): +def test_importing_PIL_GIF_normalise(is_file, mock_image): mock_image.return_value = PILImage.new('P', (10, 10)) is_file.return_value = True @@ -309,7 +362,7 @@ def test_importing_GIF_normalise(is_file, mock_image): @patch('PIL.Image.open') @patch('menpo.io.input.base.Path.is_file') -def test_importing_GIF_no_normalise(is_file, mock_image): +def test_importing_PIL_GIF_no_normalise(is_file, mock_image): mock_image.return_value = PILImage.new('P', (10, 10)) is_file.return_value = True @@ -322,7 +375,7 @@ def test_importing_GIF_no_normalise(is_file, mock_image): @patch('PIL.Image.open') @patch('menpo.io.input.base.Path.is_file') @raises(ValueError) -def test_importing_GIF_non_pallete_exception(is_file, mock_image): +def test_importing_PIL_GIF_non_pallete_exception(is_file, mock_image): mock_image.return_value = PILImage.new('RGB', (10, 10)) is_file.return_value = True diff --git a/setup.py b/setup.py index 6ec48f249..648c20b47 100644 --- a/setup.py +++ b/setup.py @@ -27,9 +27,10 @@ cython_exts = cythonize(cython_modules, quiet=True) include_dirs = [np.get_include()] install_requires = ['numpy>=1.10,<1.11', - 'scipy>=0.16,<0.17', + 'scipy>=0.17,<0.18', 'matplotlib>=1.4,<1.6', 'pillow>=3.0,<4.0', + 'imageio>=1.5.0,<1.6.0', 'Cython>=0.23,<0.24'] if sys.version_info.major == 2: