Source code for SciFiReaders.readers.generic.image

"""
:class:`~ScopeReaders.generic.image.ImageTranslator` class that extracts the
data from typical image files into :class:`~sidpy.Dataset` objects

Created on Feb 9, 2016

@author: Suhas Somnath, Chris Smith
"""

from __future__ import division, print_function, absolute_import, unicode_literals

import os
import sys
import numpy as np
from PIL import Image
from sidpy.base.num_utils import contains_integers
from sidpy import Dataset, Dimension, Reader

if sys.version_info.major == 3:
    unicode = str
else:
    FileExistsError = ValueError
    FileNotFoundError = ValueError


[docs]class ImageReader(Reader): """ Translates data from an image file to an HDF5 file """
[docs] def __init__(self, *args, **kwargs): super(ImageReader, self).__init__(*args, **kwargs)
@staticmethod def _parse_file_path(image_path): """ Returns a list of all files in the directory given by path Parameters --------------- image_path : str absolute path to the image file Returns ---------- image_path : str Absolute file path to the image h5_path : str absolute path to the desired output HDF5 file. """ if not isinstance(image_path, (str, unicode)): raise TypeError("'image_path' argument for ImageTranslator should be a str or unicode") if not os.path.exists(os.path.abspath(image_path)): raise FileNotFoundError('Specified image does not exist.') else: image_path = os.path.abspath(image_path) return image_path
[docs] def read(self, bin_factor=None, interp_func=Image.BICUBIC, normalize=False, **image_args): """ Translates the image in the provided file into a USID HDF5 file Parameters ---------------- bin_factor : uint or array-like of uint, optional Down-sampling factor for each dimension. Default is None. If specifying different binning for each dimension, please specify as (height binning, width binning) interp_func : int, optional. Default = :attr:`PIL.Image.BICUBIC` How the image will be interpolated to provide the down-sampled or binned image. For more information see instructions for the `resample` argument for :meth:`PIL.Image.resize` normalize : boolean, optional. Default = False Should the raw image be normalized between the values of 0 and 1 image_args : dict Arguments to be passed to read_image. Arguments depend on the type of image. Returns ---------- """ image_path = self._parse_file_path(self._input_file_path) image = read_image(image_path, **image_args) image_parms = dict() usize, vsize = image.shape[:2] ''' Check if a bin_factor is given. Set up binning objects if it is. ''' if bin_factor is not None: if isinstance(bin_factor, (list, tuple)): if not contains_integers(bin_factor, min_val=1): raise TypeError('bin_factor should contain positive whole integers') if len(bin_factor) == 2: bin_factor = tuple(bin_factor) else: raise ValueError('Input parameter `bin_factor` must be a length 2 array-like or an integer.\n' + '{} was given.'.format(bin_factor)) elif isinstance(bin_factor, int): bin_factor = (bin_factor, bin_factor) else: raise TypeError('bin_factor should either be an integer or an iterable of positive integers') if np.min(bin_factor) < 0: raise ValueError('bin_factor must consist of positive factors') if interp_func not in [Image.NEAREST, Image.BILINEAR, Image.BICUBIC, Image.LANCZOS]: raise ValueError("'interp_func' argument for ImageTranslator.translate must be one of " "PIL.Image.NEAREST, PIL.Image.BILINEAR, PIL.Image.BICUBIC, PIL.Image.LANCZOS") image_parms.update({'image_binning_size': bin_factor, 'image_PIL_resample_mode': interp_func}) usize = int(usize / bin_factor[0]) vsize = int(vsize / bin_factor[1]) # Unfortunately, we need to make a round-trip through PIL for the interpolation. Not possible with numpy img_obj = Image.fromarray(image) img_obj = img_obj.resize((vsize, usize), resample=interp_func) image = np.asarray(img_obj) # Working around occasional "cannot modify read-only array" error image = image.copy() ''' Normalize Raw Image ''' if normalize: image -= np.min(image) image = image / np.float32(np.max(image)) image_parms.update({'normalized': normalize, 'image_min': np.min(image), 'image_max': np.max(image)}) data_set = Dataset.from_array(image, name='random') data_set.data_type = 'image' data_set.units = 'a. u.' data_set.quantity = 'Intensity' data_set.set_dimension(0, Dimension(np.arange(usize), 'y', units='a. u.', quantity='Length', dimension_type='spatial')) data_set.set_dimension(1, Dimension(np.arange(vsize), 'x', units='a. u.', quantity='Length', dimension_type='spatial')) return data_set
[docs] def can_read(self): """ Tests whether or not the provided file has a .ndata extension Returns ------- """ exts = ['jpg', 'jpeg', 'png', 'tiff', 'bmp', 'csv', 'txt'] return super(ImageReader, self).can_read(extension=exts)
[docs]def read_image(image_path, as_grayscale=True, as_numpy_array=True, *args, **kwargs): """ Read the image file at `image_path` into a numpy array either via numpy (.txt) or via pillow (.jpg, .tif, etc.) Parameters ---------- image_path : str Path to the image file as_grayscale : bool, optional. Default = True Whether or not to read the image as a grayscale image as_numpy_array : bool, optional. Default = True If set to True, the image is read into a numpy array. If not, it is returned as a pillow Image Returns ------- image : :class:`numpy.ndarray` or :class:`PIL.Image.Image` if `as_numpy_array` is set to True - Array containing the image from the file `image_path`. If `as_numpy_array` is set to False - PIL.Image object containing the image within the file - `image_path`. """ ext = os.path.splitext(image_path)[-1] if ext in ['.txt', '.csv']: if ext == '.csv' and 'delimiter' not in kwargs.keys(): kwargs['delimiter'] = ',' img_data = np.loadtxt(image_path, *args, **kwargs) if as_numpy_array: return img_data else: img_obj = Image.fromarray(img_data) img_obj = img_obj.convert(mode="L") return img_obj else: img_obj = Image.open(image_path) if as_grayscale: img_obj = img_obj.convert(mode="L", **kwargs) if as_numpy_array: # Open the image as a numpy array return np.asarray(img_obj) return img_obj