Source code for colour.io.image

# -*- coding: utf-8 -*-
"""
Image Input / Output Utilities
==============================

Defines image related input / output utilities objects.
"""

from __future__ import division, unicode_literals

import numpy as np
from collections import namedtuple
from six import string_types

from colour.utilities import (CaseInsensitiveMapping, as_float_array,
                              is_openimageio_installed)

__author__ = 'Colour Developers'
__copyright__ = 'Copyright (C) 2013-2019 - Colour Developers'
__license__ = 'New BSD License - http://opensource.org/licenses/BSD-3-Clause'
__maintainer__ = 'Colour Developers'
__email__ = 'colour-science@googlegroups.com'
__status__ = 'Production'

__all__ = [
    'BitDepth_Specification', 'ImageAttribute_Specification',
    'BIT_DEPTH_MAPPING', 'read_image', 'write_image'
]

BitDepth_Specification = namedtuple(
    'BitDepth_Specification',
    ('name', 'numpy', 'openimageio', 'domain', 'clip'))


[docs]class ImageAttribute_Specification( namedtuple('ImageAttribute_Specification', ('name', 'value', 'type_'))): """ Defines the an image specification attribute. Parameters ---------- name : unicode Attribute name. value : object Attribute value. type_ : TypeDesc, optional Attribute type as an *OpenImageIO* :class:`TypeDesc` class instance. """ def __new__(cls, name, value, type_=None): """ Returns a new instance of the :class:`colour.ImageAttribute_Specification` class. """ return super(ImageAttribute_Specification, cls).__new__( cls, name, value, type_)
if is_openimageio_installed(): from OpenImageIO import UINT8, UINT16, HALF, FLOAT BIT_DEPTH_MAPPING = CaseInsensitiveMapping({ 'uint8': BitDepth_Specification('uint8', np.uint8, UINT8, 255, True), 'uint16': BitDepth_Specification('uint16', np.uint16, UINT16, 65535, True), 'float16': BitDepth_Specification('float16', np.float16, HALF, 1, False), 'float32': BitDepth_Specification('float32', np.float32, FLOAT, 1, False) }) else: BIT_DEPTH_MAPPING = CaseInsensitiveMapping({ 'uint8': BitDepth_Specification('uint8', np.uint8, None, 255, True), 'uint16': BitDepth_Specification('uint16', np.uint16, None, 65535, True), 'float16': BitDepth_Specification('float16', np.float16, None, 1, False), 'float32': BitDepth_Specification('float32', np.float32, None, 1, False) })
[docs]def read_image(path, bit_depth='float32', attributes=False): """ Reads given image using *OpenImageIO*. Parameters ---------- path : unicode Image path. bit_depth : unicode, optional **{'float32', 'uint8', 'uint16', 'float16'}**, Image bit_depth. attributes : bool, optional Whether to return the image attributes. Returns ------- ndarray Image as a ndarray. Notes ----- - For convenience, single channel images are squeezed to 2d arrays. Examples -------- >>> import os >>> path = os.path.join('tests', 'resources', 'CMSTestPattern.exr') >>> image = read_image(path) # doctest: +SKIP """ if is_openimageio_installed(raise_exception=True): from OpenImageIO import ImageInput path = str(path) bit_depth = BIT_DEPTH_MAPPING[bit_depth].openimageio image = ImageInput.open(path) specification = image.spec() shape = (specification.height, specification.width, specification.nchannels) image_data = image.read_image(bit_depth) image.close() image = np.squeeze(np.array(image_data).reshape(shape)) if attributes: extra_attributes = [] for i in range(len(specification.extra_attribs)): attribute = specification.extra_attribs[i] extra_attributes.append( ImageAttribute_Specification( attribute.name, attribute.value, attribute.type)) return image, extra_attributes else: return image
[docs]def write_image(image, path, bit_depth='float32', attributes=None): """ Writes given image using *OpenImageIO*. Parameters ---------- image : array_like Image data. path : unicode Image path. bit_depth : unicode, optional **{'float32', 'uint8', 'uint16', 'float16'}**, Image bit_depth. attributes : array_like, optional An array of :class:`colour.io.ImageAttribute_Specification` class instances used to set attributes of the image. Returns ------- bool Definition success. Examples -------- Basic image writing: >>> import os >>> path = os.path.join('tests', 'resources', 'CMSTestPattern.exr') >>> image = read_image(path) # doctest: +SKIP >>> path = os.path.join('tests', 'resources', 'CMSTestPattern.tif') >>> write_image(image, path) # doctest: +SKIP True Advanced image writing while setting attributes: >>> compression = ImageAttribute_Specification('Compression', 'none') >>> write_image(image, path, 'uint8', [compression]) # doctest: +SKIP True """ if is_openimageio_installed(raise_exception=True): from OpenImageIO import ImageOutput, ImageOutputOpenMode, ImageSpec path = str(path) if attributes is None: attributes = [] bit_depth_specification = BIT_DEPTH_MAPPING[bit_depth] bit_depth = bit_depth_specification.openimageio image = as_float_array(image) image *= bit_depth_specification.domain if bit_depth_specification.clip: image = np.clip(image, 0, bit_depth_specification.domain) image = image.astype(bit_depth_specification.numpy) if image.ndim == 2: height, width = image.shape channels = 1 else: height, width, channels = image.shape specification = ImageSpec(width, height, channels, bit_depth) for attribute in attributes: name = str(attribute.name) value = (str(attribute.value) if isinstance( attribute.value, string_types) else attribute.value) type_ = attribute.type_ if attribute.type_ is None: specification.attribute(name, value) else: specification.attribute(name, type_, value) image_output = ImageOutput.create(path) image_output.open(path, specification, ImageOutputOpenMode.Create) image_output.write_image(bit_depth, image.tostring()) image_output.close() return True