Source code for SciFiReaders.readers.generic.image

# lines (164 sloc)  7.4 KB

"""
: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: Mani Valleti
"""

import os
import sys

import numpy as np
import PIL
from sidpy import Dataset, Dimension, Reader

try:
    import tifffile as tff
except ModuleNotFoundError:
    tff = None


[docs] class ImageReader(Reader): """ Translates data from an image file to a sidpy dataset """
[docs] def __init__(self, file_path, *args, **kwargs): super().__init__(file_path, *args, **kwargs) image_path = self._parse_file_path(self._input_file_path) ext = os.path.splitext(image_path)[-1] if ext not in ['.jpg', '.jpeg', '.png', '.bmp', '.tif', '.tiff']: raise NotImplementedError( 'The provided file type {} is not supported by the reader as of now \n'.format(ext) + 'Please provide one of "jpg", "jpeg", "png", "bmp", ".tif", ".tiff" file types')
@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 """ if not isinstance(image_path, str): raise TypeError("'image_path' argument for ImageReader should be a str") 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, **image_args): """ Translates the image in the provided file into a sidpy Dataset Parameters ---------------- image_args : dict Arguments to be passed to read_image. Arguments depend on the type of image. Returns ---------- sidpy Dataset """ image_path = self._parse_file_path(self._input_file_path) img_data, dimensions, metadata, original_metadata = read_image(image_path, **image_args) # Assuming that we have already dealt with classifying the shape # We are only as good as the tifffile package here # Working around occasional "cannot modify read-only array" error image = img_data.copy() data_set = Dataset.from_array(image, title='Image') data_set.data_type = 'image' # Can we somehow get the units and/or the quantity from the metadata?? data_set.units = 'a. u.' data_set.quantity = 'Intensity' data_set.metadata = metadata.copy() data_set.original_metadata = original_metadata.copy() for i, dim in enumerate(dimensions): data_set.set_dimension(i, dim.copy()) return data_set
[docs] def can_read(self): """ Tests whether or not the provided file has the appropriate extensions Returns ------- """ exts = ['jpg', 'jpeg', 'png', 'tiff', 'bmp', 'csv', 'txt'] return super(ImageReader, self).can_read(extension=exts)
[docs] def read_image(image_path, *args, **kwargs): """ Read the image file at `image_path` into a numpy array either via numpy (.txt) or via tiffifle(.tif) or via pillow (.jpg, etc.) Parameters ---------- image_path : str Path to the image file Returns ------- image : :class:`numpy.ndarray` Array containing the image from the file `image_path`. """ # As of now we are not trying to read any metadata from these image formats. # We will cross that bridge when someone raises an issue ext = os.path.splitext(image_path)[-1] original_metadata, metadata, dimensions = {}, {}, [] if ext in ['.jpg', '.jpeg', '.png', '.bmp']: img_data = np.asarray(PIL.Image.open(image_path)) # Colored images, color channel last # Here we assume that the file path provided has a single image and not a stack # For stacks we turn our attention to tiff if len(img_data.shape) == 3: dimensions.append(Dimension(np.arange(img_data.shape[0]), 'y', units='generic', quantity='Length', dimension_type='spatial')) dimensions.append(Dimension(np.arange(img_data.shape[1]), 'x', units='generic', quantity='Length', dimension_type='spatial')) dimensions.append(Dimension(np.arange(img_data.shape[2]), 's', units='generic', quantity='SamplesPerPixel', dimension_type='frame')) # Grayscale single images if len(img_data.shape) == 2: dimensions.append(Dimension(np.arange(img_data.shape[0]), 'y', units='generic', quantity='Length', dimension_type='spatial')) dimensions.append(Dimension(np.arange(img_data.shape[1]), 'x', units='generic', quantity='Length', dimension_type='spatial')) return img_data, dimensions, metadata, original_metadata elif ext in ['.tif', '.tiff']: if tff is None: raise ModuleNotFoundError("tifffile is not installed") else: tif = tff.TiffFile(image_path) img_data = tif.asarray() # Only single series is supported for now, and for definition of a series refer the tifffile package img_shape, img_axes = tif.series[0].shape, tif.series[0].axes # Dealing with metadata that's common to the whole image, we will place this in original_metadata # First let's filter out all the attributes that end with metadata metadata_names = [a for a in dir(tif) if (not a.startswith('__') or not a.startswith('_')) and a.endswith('_metadata')] for name in metadata_names: if getattr(tif, name) is not None: original_metadata[name] = getattr(tif, name) # Now metadata corresponding to individual frames. This is placed in metadata for page in tif.pages: for tag in page.tags: metadata[tag.name] = tag.value # Dealing with axes and dimensions # We will use the following dictionary to map tifffile axes to sidpy dimensions axes_dict = {'X': ['x', 'Length', 'spatial'], 'Y': ['y', 'Width', 'spatial'], 'Z': ['z', 'Depth', 'spatial'], 'S': ['s', 'SamplesPerPixel', 'frame'], 'T': ['t', 'Time', 'time'], 'C': ['c', 'Channel', 'frame'], 'Q': ['q', 'other', 'UNKNOWN'] } for i in range(len(img_shape)): if img_axes[i] == 'X' or img_axes[i] == 'Y': # We are here to handle X and Y dimensions # Here it is assumed that all the channels/time frames/depth frames have the same resolution unit res_name = 'XResolution' if img_axes[i] == 'X' else 'YResolution' for page in tif.pages: if res_name in page.tags and 'ResolutionUnit' in page.tags: if page.tags['ResolutionUnit'].value.value == 1: # No unit provided if isinstance(page.tags[res_name].value, tuple): if page.tags[res_name].value == (1, 1): res, unit = 1, 'generic' else: 'It is assumed that the resolutions is given in pixels per inch' res, unit = page.tags[res_name].value[0], 'inches' else: if page.tags[res_name].value == 1: res, unit = 1, 'generic' else: unit, res = 'inches', page.tags[res_name].value elif page.tags['ResolutionUnit'].value.value == 2: unit = 'inches' if isinstance(page.tags[res_name].value, tuple): res = page.tags[res_name].value[0] else: # At this point it is assumed that resolution is not a tuple but just and int/float res = page.tags[res_name].value elif page.tags['ResolutionUnit'].value.value == 3: unit = 'cms' if isinstance(page.tags[res_name].value, tuple): res = page.tags[res_name].value[1] else: # At this point it is assumed that resolution is not a tuple but just and int/float res = page.tags[res_name].value break dimensions.append(Dimension(np.arange(img_shape[i]) * (1. / res), axes_dict[img_axes[i]][0], quantity=axes_dict[img_axes[i]][1], units=unit, dimension_type=axes_dict[img_axes[i]][2])) else: dimensions.append(Dimension(np.arange(img_shape[i]), axes_dict[img_axes[i]][0], quantity=axes_dict[img_axes[i]][1], dimension_type=axes_dict[img_axes[i]][2])) return img_data, dimensions, metadata, original_metadata