EVOLUTION-MANAGER
Edit File: utils.py
"""Utilities for real-time data augmentation on image data. """ from __future__ import absolute_import from __future__ import division from __future__ import print_function import io import os import warnings import numpy as np try: from PIL import ImageEnhance from PIL import Image as pil_image except ImportError: pil_image = None ImageEnhance = None if pil_image is not None: _PIL_INTERPOLATION_METHODS = { 'nearest': pil_image.NEAREST, 'bilinear': pil_image.BILINEAR, 'bicubic': pil_image.BICUBIC, } # These methods were only introduced in version 3.4.0 (2016). if hasattr(pil_image, 'HAMMING'): _PIL_INTERPOLATION_METHODS['hamming'] = pil_image.HAMMING if hasattr(pil_image, 'BOX'): _PIL_INTERPOLATION_METHODS['box'] = pil_image.BOX # This method is new in version 1.1.3 (2013). if hasattr(pil_image, 'LANCZOS'): _PIL_INTERPOLATION_METHODS['lanczos'] = pil_image.LANCZOS def validate_filename(filename, white_list_formats): """Check if a filename refers to a valid file. # Arguments filename: String, absolute path to a file white_list_formats: Set, allowed file extensions # Returns A boolean value indicating if the filename is valid or not """ return (filename.lower().endswith(white_list_formats) and os.path.isfile(filename)) def save_img(path, x, data_format='channels_last', file_format=None, scale=True, **kwargs): """Saves an image stored as a Numpy array to a path or file object. # Arguments path: Path or file object. x: Numpy array. data_format: Image data format, either "channels_first" or "channels_last". file_format: Optional file format override. If omitted, the format to use is determined from the filename extension. If a file object was used instead of a filename, this parameter should always be used. scale: Whether to rescale image values to be within `[0, 255]`. **kwargs: Additional keyword arguments passed to `PIL.Image.save()`. """ img = array_to_img(x, data_format=data_format, scale=scale) if img.mode == 'RGBA' and (file_format == 'jpg' or file_format == 'jpeg'): warnings.warn('The JPG format does not support ' 'RGBA images, converting to RGB.') img = img.convert('RGB') img.save(path, format=file_format, **kwargs) def load_img(path, grayscale=False, color_mode='rgb', target_size=None, interpolation='nearest'): """Loads an image into PIL format. # Arguments path: Path to image file. grayscale: DEPRECATED use `color_mode="grayscale"`. color_mode: The desired image format. One of "grayscale", "rgb", "rgba". "grayscale" supports 8-bit images and 32-bit signed integer images. Default: "rgb". target_size: Either `None` (default to original size) or tuple of ints `(img_height, img_width)`. interpolation: Interpolation method used to resample the image if the target size is different from that of the loaded image. Supported methods are "nearest", "bilinear", and "bicubic". If PIL version 1.1.3 or newer is installed, "lanczos" is also supported. If PIL version 3.4.0 or newer is installed, "box" and "hamming" are also supported. Default: "nearest". # Returns A PIL Image instance. # Raises ImportError: if PIL is not available. ValueError: if interpolation method is not supported. """ if grayscale is True: warnings.warn('grayscale is deprecated. Please use ' 'color_mode = "grayscale"') color_mode = 'grayscale' if pil_image is None: raise ImportError('Could not import PIL.Image. ' 'The use of `load_img` requires PIL.') with open(path, 'rb') as f: img = pil_image.open(io.BytesIO(f.read())) if color_mode == 'grayscale': # if image is not already an 8-bit, 16-bit or 32-bit grayscale image # convert it to an 8-bit grayscale image. if img.mode not in ('L', 'I;16', 'I'): img = img.convert('L') elif color_mode == 'rgba': if img.mode != 'RGBA': img = img.convert('RGBA') elif color_mode == 'rgb': if img.mode != 'RGB': img = img.convert('RGB') else: raise ValueError('color_mode must be "grayscale", "rgb", or "rgba"') if target_size is not None: width_height_tuple = (target_size[1], target_size[0]) if img.size != width_height_tuple: if interpolation not in _PIL_INTERPOLATION_METHODS: raise ValueError( 'Invalid interpolation method {} specified. Supported ' 'methods are {}'.format( interpolation, ", ".join(_PIL_INTERPOLATION_METHODS.keys()))) resample = _PIL_INTERPOLATION_METHODS[interpolation] img = img.resize(width_height_tuple, resample) return img def list_pictures(directory, ext=('jpg', 'jpeg', 'bmp', 'png', 'ppm', 'tif', 'tiff')): """Lists all pictures in a directory, including all subdirectories. # Arguments directory: string, absolute path to the directory ext: tuple of strings or single string, extensions of the pictures # Returns a list of paths """ ext = tuple('.%s' % e for e in ((ext,) if isinstance(ext, str) else ext)) return [os.path.join(root, f) for root, _, files in os.walk(directory) for f in files if f.lower().endswith(ext)] def _iter_valid_files(directory, white_list_formats, follow_links): """Iterates on files with extension in `white_list_formats` contained in `directory`. # Arguments directory: Absolute path to the directory containing files to be counted white_list_formats: Set of strings containing allowed extensions for the files to be counted. follow_links: Boolean, follow symbolic links to subdirectories. # Yields Tuple of (root, filename) with extension in `white_list_formats`. """ def _recursive_list(subpath): return sorted(os.walk(subpath, followlinks=follow_links), key=lambda x: x[0]) for root, _, files in _recursive_list(directory): for fname in sorted(files): if fname.lower().endswith('.tiff'): warnings.warn('Using ".tiff" files with multiple bands ' 'will cause distortion. Please verify your output.') if fname.lower().endswith(white_list_formats): yield root, fname def _list_valid_filenames_in_directory(directory, white_list_formats, split, class_indices, follow_links): """Lists paths of files in `subdir` with extensions in `white_list_formats`. # Arguments directory: absolute path to a directory containing the files to list. The directory name is used as class label and must be a key of `class_indices`. white_list_formats: set of strings containing allowed extensions for the files to be counted. split: tuple of floats (e.g. `(0.2, 0.6)`) to only take into account a certain fraction of files in each directory. E.g.: `segment=(0.6, 1.0)` would only account for last 40 percent of images in each directory. class_indices: dictionary mapping a class name to its index. follow_links: boolean, follow symbolic links to subdirectories. # Returns classes: a list of class indices filenames: the path of valid files in `directory`, relative from `directory`'s parent (e.g., if `directory` is "dataset/class1", the filenames will be `["class1/file1.jpg", "class1/file2.jpg", ...]`). """ dirname = os.path.basename(directory) if split: all_files = list(_iter_valid_files(directory, white_list_formats, follow_links)) num_files = len(all_files) start, stop = int(split[0] * num_files), int(split[1] * num_files) valid_files = all_files[start: stop] else: valid_files = _iter_valid_files( directory, white_list_formats, follow_links) classes = [] filenames = [] for root, fname in valid_files: classes.append(class_indices[dirname]) absolute_path = os.path.join(root, fname) relative_path = os.path.join( dirname, os.path.relpath(absolute_path, directory)) filenames.append(relative_path) return classes, filenames def array_to_img(x, data_format='channels_last', scale=True, dtype='float32'): """Converts a 3D Numpy array to a PIL Image instance. # Arguments x: Input Numpy array. data_format: Image data format, either "channels_first" or "channels_last". Default: "channels_last". scale: Whether to rescale the image such that minimum and maximum values are 0 and 255 respectively. Default: True. dtype: Dtype to use. Default: "float32". # Returns A PIL Image instance. # Raises ImportError: if PIL is not available. ValueError: if invalid `x` or `data_format` is passed. """ if pil_image is None: raise ImportError('Could not import PIL.Image. ' 'The use of `array_to_img` requires PIL.') x = np.asarray(x, dtype=dtype) if x.ndim != 3: raise ValueError('Expected image array to have rank 3 (single image). ' 'Got array with shape: %s' % (x.shape,)) if data_format not in {'channels_first', 'channels_last'}: raise ValueError('Invalid data_format: %s' % data_format) # Original Numpy array x has format (height, width, channel) # or (channel, height, width) # but target PIL image has format (width, height, channel) if data_format == 'channels_first': x = x.transpose(1, 2, 0) if scale: x = x - np.min(x) x_max = np.max(x) if x_max != 0: x /= x_max x *= 255 if x.shape[2] == 4: # RGBA return pil_image.fromarray(x.astype('uint8'), 'RGBA') elif x.shape[2] == 3: # RGB return pil_image.fromarray(x.astype('uint8'), 'RGB') elif x.shape[2] == 1: # grayscale if np.max(x) > 255: # 32-bit signed integer grayscale image. PIL mode "I" return pil_image.fromarray(x[:, :, 0].astype('int32'), 'I') return pil_image.fromarray(x[:, :, 0].astype('uint8'), 'L') else: raise ValueError('Unsupported channel number: %s' % (x.shape[2],)) def img_to_array(img, data_format='channels_last', dtype='float32'): """Converts a PIL Image instance to a Numpy array. # Arguments img: PIL Image instance. data_format: Image data format, either "channels_first" or "channels_last". dtype: Dtype to use for the returned array. # Returns A 3D Numpy array. # Raises ValueError: if invalid `img` or `data_format` is passed. """ if data_format not in {'channels_first', 'channels_last'}: raise ValueError('Unknown data_format: %s' % data_format) # Numpy array x has format (height, width, channel) # or (channel, height, width) # but original PIL image has format (width, height, channel) x = np.asarray(img, dtype=dtype) if len(x.shape) == 3: if data_format == 'channels_first': x = x.transpose(2, 0, 1) elif len(x.shape) == 2: if data_format == 'channels_first': x = x.reshape((1, x.shape[0], x.shape[1])) else: x = x.reshape((x.shape[0], x.shape[1], 1)) else: raise ValueError('Unsupported image shape: %s' % (x.shape,)) return x