Source code for colour.utilities.common

# -*- coding: utf-8 -*-
"""
Common Utilities
================

Defines common utilities objects that don't fall in any specific category.

References
----------
-   :cite:`Kienzle2011a` : Kienzle, P., Patel, N., & Krycka, J. (2011).
    refl1d.numpyerrors - Refl1D v0.6.19 documentation. Retrieved January 30,
    2015, from http://www.reflectometry.org/danse/docs/refl1d/_modules/\
refl1d/numpyerrors.html
"""

from __future__ import division, unicode_literals

import inspect
import functools
import numpy as np
import re
import warnings
from collections import OrderedDict
from copy import deepcopy
from six import string_types

from colour.constants import INTEGER_THRESHOLD, DEFAULT_FLOAT_DTYPE
from colour.utilities import Lookup

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

__all__ = [
    'handle_numpy_errors', 'ignore_numpy_errors', 'raise_numpy_errors',
    'print_numpy_errors', 'warn_numpy_errors', 'ignore_python_warnings',
    'batch', 'is_openimageio_installed', 'is_pandas_installed', 'is_iterable',
    'is_string', 'is_numeric', 'is_integer', 'is_sibling', 'filter_kwargs',
    'filter_mapping', 'first_item', 'get_domain_range_scale',
    'set_domain_range_scale', 'domain_range_scale', 'to_domain_1',
    'to_domain_10', 'to_domain_100', 'to_domain_degrees', 'to_domain_int',
    'from_range_1', 'from_range_10', 'from_range_100', 'from_range_degrees',
    'from_range_int'
]


[docs]def handle_numpy_errors(**kwargs): """ Decorator for handling *Numpy* errors. Other Parameters ---------------- \\**kwargs : dict, optional Keywords arguments. Returns ------- object References ---------- :cite:`Kienzle2011a` Examples -------- >>> import numpy >>> @handle_numpy_errors(all='ignore') ... def f(): ... 1 / numpy.zeros(3) >>> f() """ context = np.errstate(**kwargs) def wrapper(function): """ Wrapper for given function. """ @functools.wraps(function) def wrapped(*args, **kwargs): """ Wrapped function. """ with context: return function(*args, **kwargs) return wrapped return wrapper
ignore_numpy_errors = handle_numpy_errors(all='ignore') raise_numpy_errors = handle_numpy_errors(all='raise') print_numpy_errors = handle_numpy_errors(all='print') warn_numpy_errors = handle_numpy_errors(all='warn')
[docs]def ignore_python_warnings(function): """ Decorator for ignoring *Python* warnings. Parameters ---------- function : object Function to decorate. Returns ------- object Examples -------- >>> @ignore_python_warnings ... def f(): ... warnings.warn('This is an ignored warning!') >>> f() """ @functools.wraps(function) def wrapped(*args, **kwargs): """ Wrapped function. """ with warnings.catch_warnings(): warnings.simplefilter('ignore') return function(*args, **kwargs) return wrapped
[docs]def batch(iterable, k=3): """ Returns a batch generator from given iterable. Parameters ---------- iterable : iterable Iterable to create batches from. k : integer Batches size. Returns ------- bool Is *string_like* variable. Examples -------- >>> batch(tuple(range(10))) # doctest: +ELLIPSIS <generator object batch at 0x...> """ for i in range(0, len(iterable), k): yield iterable[i:i + k]
[docs]def is_openimageio_installed(raise_exception=False): """ Returns if *OpenImageIO* is installed and available. Parameters ---------- raise_exception : bool Raise exception if *OpenImageIO* is unavailable. Returns ------- bool Is *OpenImageIO* installed. Raises ------ ImportError If *OpenImageIO* is not installed. """ try: import OpenImageIO # noqa return True except ImportError as error: if raise_exception: raise ImportError(('"OpenImageIO" related Api features ' 'are not available: "{0}".').format(error)) return False
[docs]def is_pandas_installed(raise_exception=False): """ Returns if *Pandas* is installed and available. Parameters ---------- raise_exception : bool Raise exception if *Pandas* is unavailable. Returns ------- bool Is *Pandas* installed. Raises ------ ImportError If *Pandas* is not installed. """ try: import pandas # noqa return True except ImportError as error: if raise_exception: raise ImportError(('"Pandas" related Api features ' 'are not available: "{0}".').format(error)) return False
[docs]def is_iterable(a): """ Returns if given :math:`a` variable is iterable. Parameters ---------- a : object Variable to check the iterability. Returns ------- bool :math:`a` variable iterability. Examples -------- >>> is_iterable([1, 2, 3]) True >>> is_iterable(1) False """ return is_string(a) or (True if getattr(a, '__iter__', False) else False)
[docs]def is_string(a): """ Returns if given :math:`a` variable is a *string* like variable. Parameters ---------- a : object Data to test. Returns ------- bool Is :math:`a` variable a *string* like variable. Examples -------- >>> is_string("I'm a string!") True >>> is_string(["I'm a string!"]) False """ return True if isinstance(a, string_types) else False
[docs]def is_numeric(a): """ Returns if given :math:`a` variable is a number. Parameters ---------- a : object Variable to check. Returns ------- bool Is :math:`a` variable a number. Examples -------- >>> is_numeric(1) True >>> is_numeric((1,)) False """ return isinstance( a, (int, float, complex, np.integer, np.floating, np.complex))
[docs]def is_integer(a): """ Returns if given :math:`a` variable is an integer under given threshold. Parameters ---------- a : object Variable to check. Returns ------- bool Is :math:`a` variable an integer. Notes ----- - The determination threshold is defined by the :attr:`colour.algebra.common.INTEGER_THRESHOLD` attribute. Examples -------- >>> is_integer(1) True >>> is_integer(1.01) False """ return abs(a - round(a)) <= INTEGER_THRESHOLD
[docs]def is_sibling(element, mapping): """ Returns whether given element type is present in given mapping types. Parameters ---------- element : object Element to check if its type is present in the mapping types. mapping : dict Mapping. Returns ------- bool Whether given element type is present in given mapping types. """ return isinstance(element, tuple( set(type(element) for element in mapping.values())))
[docs]def filter_kwargs(function, **kwargs): """ Filters keyword arguments incompatible with the given function signature. Parameters ---------- function : callable Callable to filter the incompatible keyword arguments. Other Parameters ---------------- \\**kwargs : dict, optional Keywords arguments. Returns ------- dict Filtered keyword arguments. Examples -------- >>> def fn_a(a): ... return a >>> def fn_b(a, b=0): ... return a, b >>> def fn_c(a, b=0, c=0): ... return a, b, c >>> fn_a(1, **filter_kwargs(fn_a, b=2, c=3)) 1 >>> fn_b(1, **filter_kwargs(fn_b, b=2, c=3)) (1, 2) >>> fn_c(1, **filter_kwargs(fn_c, b=2, c=3)) (1, 2, 3) """ kwargs = deepcopy(kwargs) args, _varargs, _keywords, _defaults = inspect.getargspec(function) args = set(kwargs.keys()) - set(args) for key in args: kwargs.pop(key) return kwargs
[docs]def filter_mapping(mapping, filterers, anchors=True, flags=re.IGNORECASE): """ Filters given mapping with given filterers. Parameters ---------- mapping : dict_like Mapping to filter. filterers : unicode or object or array_like Filterer pattern for given mapping elements or a list of filterers. anchors : bool, optional Whether to use Regex line anchors, i.e. *^* and *$* are added, surrounding the filterer pattern. flags : int, optional Regex flags. Returns ------- OrderedDict Filtered mapping elements. Notes ----- - To honour the filterers ordering, the return value is an :class:`OrderedDict` class instance. Examples -------- >>> class Element(object): ... pass >>> mapping = { ... 'Element A': Element(), ... 'Element B': Element(), ... 'Element C': Element(), ... 'Not Element C': Element(), ... } >>> # Doctests skip for Python 2.x compatibility. >>> filter_mapping(mapping, '\\w+\\s+A') # doctest: +SKIP {u'Element A': <colour.utilities.common.Element object at 0x...>} >>> # Doctests skip for Python 2.x compatibility. >>> sorted(filter_mapping(mapping, 'Element.*')) # doctest: +SKIP [u'Element A', u'Element B', u'Element C'] """ def filter_mapping_with_filter(mapping, filterer, anchors, flags): """ Filters given mapping with given filterer. Parameters ---------- mapping : dict_like Mapping to filter. filterer : unicode or object Filterer pattern for given mapping elements. anchors : bool, optional Whether to use Regex line anchors, i.e. *^* and *$* are added, surrounding the filterer pattern. flags : int, optional Regex flags. Returns ------- OrderedDict Filtered mapping elements. """ if anchors: filterer = '^{0}$'.format(filterer) filterer = filterer.replace('^^', '^').replace('$$', '$') elements = [ mapping[element] for element in mapping if re.match(filterer, element, flags) ] lookup = Lookup(mapping) return OrderedDict((lookup.first_key_from_value(element), element) for element in elements) if is_string(filterers): filterers = [filterers] filtered_mapping = OrderedDict() for filterer in filterers: filtered_mapping.update( filter_mapping_with_filter(mapping, filterer, anchors, flags)) return filtered_mapping
[docs]def first_item(a): """ Return the first item of an iterable. Parameters ---------- a : object Iterable to get the first item from. Returns ------- object Raises ------ StopIteration If the iterable is empty. Examples -------- >>> a = range(10) >>> first_item(a) 0 """ return next(iter(a))
_DOMAIN_RANGE_SCALE = 'reference' """ Global variable storing the current *Colour* domain-range scale. _DOMAIN_RANGE_SCALE : unicode """
[docs]def get_domain_range_scale(): """ Returns the current *Colour* domain-range scale. The following scales are available: - **'Reference'**, the default *Colour* domain-range scale which varies depending on the referenced algorithm, e.g. [0, 1], [0, 10], [0, 100], [0, 255], etc... - **'1'**, a domain-range scale normalised to [0, 1], it is important to acknowledge that this is a soft normalisation and it is possible to use negative out of gamut values or high dynamic range data exceeding 1. Returns ------- unicode *Colour* domain-range scale. """ return _DOMAIN_RANGE_SCALE
[docs]def set_domain_range_scale(scale='Reference'): """ Sets the current *Colour* domain-range scale. The following scales are available: - **'Reference'**, the default *Colour* domain-range scale which varies depending on the referenced algorithm, e.g. [0, 1], [0, 10], [0, 100], [0, 255], etc... - **'1'**, a domain-range scale normalised to [0, 1], it is important to acknowledge that this is a soft normalisation and it is possible to use negative out of gamut values or high dynamic range data exceeding 1. Parameters ---------- scale : unicode or int **{'Reference', '1'}**, *Colour* domain-range scale to set. """ global _DOMAIN_RANGE_SCALE scale = str(scale).lower() valid = ('1', '100', 'reference', 'ignore') assert scale in valid, 'Scale must be one of "{0}".'.format(valid) _DOMAIN_RANGE_SCALE = scale
[docs]class domain_range_scale(object): """ A context manager and decorator temporarily setting *Colour* domain-range scale. The following scales are available: - **'Reference'**, the default *Colour* domain-range scale which varies depending on the referenced algorithm, e.g. [0, 1], [0, 10], [0, 100], [0, 255], etc... - **'1'**, a domain-range scale normalised to [0, 1], it is important to acknowledge that this is a soft normalisation and it is possible to use negative out of gamut values or high dynamic range data exceeding 1. Parameters ---------- scale : unicode **{'Reference', '1'}**, *Colour* domain-range scale to set. """
[docs] def __init__(self, scale): self._scale = scale self._previous_scale = get_domain_range_scale()
def __enter__(self): """ Called upon entering the context manager and decorator. """ set_domain_range_scale(self._scale) return self def __exit__(self, *args): """ Called upon exiting the context manager and decorator. """ set_domain_range_scale(self._previous_scale) def __call__(self, function): """ Calls the wrapped definition. """ @functools.wraps(function) def wrapper(*args, **kwargs): with self: return function(*args, **kwargs) return wrapper
[docs]def to_domain_1(a, scale_factor=100, dtype=DEFAULT_FLOAT_DTYPE): """ Scales given array :math:`a` to domain **'1'**. The behaviour is as follows: - If *Colour* domain-range scale is **'Reference'** or **'1'**, the definition is almost entirely by-passed and will just conveniently convert array :math:`a` to :class:`np.ndarray`. - If *Colour* domain-range scale is **'100'** (currently unsupported private value only used for unit tests), array :math:`a` is divided by ``scale_factor``, typically 100. Parameters ---------- a : array_like :math:`a` to scale to domain **'1'**. scale_factor : numeric or array_like, optional Scale factor, usually *numeric* but can be an *array_like* if some axis need different scaling to be brought to domain **'1'**. dtype : object, optional Data type used for the conversion to :class:`np.ndarray`. Returns ------- ndarray :math:`a` scaled to domain **'1'**. Examples -------- With *Colour* domain-range scale set to **'Reference'**: >>> with domain_range_scale('Reference'): ... to_domain_1(1) array(1.0) With *Colour* domain-range scale set to **'1'**: >>> with domain_range_scale('1'): ... to_domain_1(1) array(1.0) With *Colour* domain-range scale set to **'100'** (unsupported): >>> with domain_range_scale('100'): ... to_domain_1(1) array(0.01) """ a = np.asarray(a, dtype).copy() if _DOMAIN_RANGE_SCALE == '100': a /= scale_factor return a
[docs]def to_domain_10(a, scale_factor=10, dtype=DEFAULT_FLOAT_DTYPE): """ Scales given array :math:`a` to domain **'10'**, used by *Munsell Renotation System*. The behaviour is as follows: - If *Colour* domain-range scale is **'Reference'**, the definition is almost entirely by-passed and will just conveniently convert array :math:`a` to :class:`np.ndarray`. - If *Colour* domain-range scale is **'1'**, array :math:`a` is multiplied by ``scale_factor``, typically 10. - If *Colour* domain-range scale is **'100'** (currently unsupported private value only used for unit tests), array :math:`a` is divided by ``scale_factor``, typically 10. Parameters ---------- a : array_like :math:`a` to scale to domain **'10'**. scale_factor : numeric or array_like, optional Scale factor, usually *numeric* but can be an *array_like* if some axis need different scaling to be brought to domain **'10'**. dtype : object, optional Data type used for the conversion to :class:`np.ndarray`. Returns ------- ndarray :math:`a` scaled to domain **'10'**. Examples -------- With *Colour* domain-range scale set to **'Reference'**: >>> with domain_range_scale('Reference'): ... to_domain_10(1) array(1.0) With *Colour* domain-range scale set to **'1'**: >>> with domain_range_scale('1'): ... to_domain_10(1) array(10.0) With *Colour* domain-range scale set to **'100'** (unsupported): >>> with domain_range_scale('100'): ... to_domain_10(1) array(0.1) """ a = np.asarray(a, dtype).copy() if _DOMAIN_RANGE_SCALE == '1': a *= scale_factor if _DOMAIN_RANGE_SCALE == '100': a /= scale_factor return a
[docs]def to_domain_100(a, scale_factor=100, dtype=DEFAULT_FLOAT_DTYPE): """ Scales given array :math:`a` to domain **'100'**. The behaviour is as follows: - If *Colour* domain-range scale is **'Reference'** or **'100'** (currently unsupported private value only used for unit tests), the definition is almost entirely by-passed and will just conveniently convert array :math:`a` to :class:`np.ndarray`. - If *Colour* domain-range scale is **'1'**, array :math:`a` is multiplied by ``scale_factor``, typically 100. Parameters ---------- a : array_like :math:`a` to scale to domain **'100'**. scale_factor : numeric or array_like, optional Scale factor, usually *numeric* but can be an *array_like* if some axis need different scaling to be brought to domain **'100'**. dtype : object, optional Data type used for the conversion to :class:`np.ndarray`. Returns ------- ndarray :math:`a` scaled to domain **'100'**. Examples -------- With *Colour* domain-range scale set to **'Reference'**: >>> with domain_range_scale('Reference'): ... to_domain_100(1) array(1.0) With *Colour* domain-range scale set to **'1'**: >>> with domain_range_scale('1'): ... to_domain_100(1) array(100.0) With *Colour* domain-range scale set to **'100'** (unsupported): >>> with domain_range_scale('100'): ... to_domain_100(1) array(1.0) """ a = np.asarray(a, dtype).copy() if _DOMAIN_RANGE_SCALE == '1': a *= scale_factor return a
[docs]def to_domain_degrees(a, scale_factor=360, dtype=DEFAULT_FLOAT_DTYPE): """ Scales given array :math:`a` to degrees domain. The behaviour is as follows: - If *Colour* domain-range scale is **'Reference'**, the definition is almost entirely by-passed and will just conveniently convert array :math:`a` to :class:`np.ndarray`. - If *Colour* domain-range scale is **'1'**, array :math:`a` is multiplied by ``scale_factor``, typically 360. - If *Colour* domain-range scale is **'100'** (currently unsupported private value only used for unit tests), array :math:`a` is multiplied by ``scale_factor`` / 100, typically 360 / 100. Parameters ---------- a : array_like :math:`a` to scale to degrees domain. scale_factor : numeric or array_like, optional Scale factor, usually *numeric* but can be an *array_like* if some axis need different scaling to be brought to degrees domain. dtype : object, optional Data type used for the conversion to :class:`np.ndarray`. Returns ------- ndarray :math:`a` scaled to degrees domain. Examples -------- With *Colour* domain-range scale set to **'Reference'**: >>> with domain_range_scale('Reference'): ... to_domain_degrees(1) array(1.0) With *Colour* domain-range scale set to **'1'**: >>> with domain_range_scale('1'): ... to_domain_degrees(1) array(360.0) With *Colour* domain-range scale set to **'100'** (unsupported): >>> with domain_range_scale('100'): ... to_domain_degrees(1) array(3.6) """ a = np.asarray(a, dtype).copy() if _DOMAIN_RANGE_SCALE == '1': a *= scale_factor if _DOMAIN_RANGE_SCALE == '100': a *= scale_factor / 100 return a
[docs]def to_domain_int(a, bit_depth=8, dtype=DEFAULT_FLOAT_DTYPE): """ Scales given array :math:`a` to int domain. The behaviour is as follows: - If *Colour* domain-range scale is **'Reference'**, the definition is almost entirely by-passed and will just conveniently convert array :math:`a` to :class:`np.ndarray`. - If *Colour* domain-range scale is **'1'**, array :math:`a` is multiplied by :math:`2^{bit\\_depth} - 1`. - If *Colour* domain-range scale is **'100'** (currently unsupported private value only used for unit tests), array :math:`a` is multiplied by :math:`2^{bit\\_depth} - 1`. Parameters ---------- a : array_like :math:`a` to scale to int domain. bit_depth : numeric or array_like, optional Bit depth, usually *int* but can be an *array_like* if some axis need different scaling to be brought to int domain. dtype : object, optional Data type used for the conversion to :class:`np.ndarray`. Returns ------- ndarray :math:`a` scaled to int domain. Notes ----- - To avoid precision issues and rounding, the scaling is performed on floating-point numbers. Examples -------- With *Colour* domain-range scale set to **'Reference'**: >>> with domain_range_scale('Reference'): ... to_domain_int(1) array(1.0) With *Colour* domain-range scale set to **'1'**: >>> with domain_range_scale('1'): ... to_domain_int(1) array(255.0) With *Colour* domain-range scale set to **'100'** (unsupported): >>> with domain_range_scale('100'): ... to_domain_int(1) array(2.55) """ a = np.asarray(a, dtype).copy() maximum_code_value = 2 ** bit_depth - 1 if _DOMAIN_RANGE_SCALE == '1': a *= maximum_code_value if _DOMAIN_RANGE_SCALE == '100': a *= maximum_code_value / 100 return a
[docs]def from_range_1(a, scale_factor=100): """ Scales given array :math:`a` from range **'1'**. The behaviour is as follows: - If *Colour* domain-range scale is **'Reference'** or **'1'**, the definition is entirely by-passed. - If *Colour* domain-range scale is **'100'** (currently unsupported private value only used for unit tests), array :math:`a` is multiplied by ``scale_factor``, typically 100. Parameters ---------- a : array_like :math:`a` to scale from range **'1'**. scale_factor : numeric or array_like, optional Scale factor, usually *numeric* but can be an *array_like* if some axis need different scaling to be brought from range **'1'**. Returns ------- ndarray :math:`a` scaled from range **'1'**. Examples -------- With *Colour* domain-range scale set to **'Reference'**: >>> with domain_range_scale('Reference'): ... from_range_1(1) 1 With *Colour* domain-range scale set to **'1'**: >>> with domain_range_scale('1'): ... from_range_1(1) 1 With *Colour* domain-range scale set to **'100'** (unsupported): >>> with domain_range_scale('100'): ... from_range_1(1) 100 """ if _DOMAIN_RANGE_SCALE == '100': a *= scale_factor return a
[docs]def from_range_10(a, scale_factor=10): """ Scales given array :math:`a` from range **'10'**, used by *Munsell Renotation System*. The behaviour is as follows: - If *Colour* domain-range scale is **'Reference'**, the definition is entirely by-passed. - If *Colour* domain-range scale is **'1'**, array :math:`a` is divided by ``scale_factor``, typically 10. - If *Colour* domain-range scale is **'100'** (currently unsupported private value only used for unit tests), array :math:`a` is multiplied by ``scale_factor``, typically 10. Parameters ---------- a : array_like :math:`a` to scale from range **'10'**. scale_factor : numeric or array_like, optional Scale factor, usually *numeric* but can be an *array_like* if some axis need different scaling to be brought from range **'10'**. Returns ------- ndarray :math:`a` scaled from range **'10'**. Examples -------- With *Colour* domain-range scale set to **'Reference'**: >>> with domain_range_scale('Reference'): ... from_range_10(1) 1 With *Colour* domain-range scale set to **'1'**: >>> with domain_range_scale('1'): ... from_range_10(1) 0.1 With *Colour* domain-range scale set to **'100'** (unsupported): >>> with domain_range_scale('100'): ... from_range_10(1) 10 """ if _DOMAIN_RANGE_SCALE == '1': a /= scale_factor if _DOMAIN_RANGE_SCALE == '100': a *= scale_factor return a
[docs]def from_range_100(a, scale_factor=100): """ Scales given array :math:`a` from range **'100'**. The behaviour is as follows: - If *Colour* domain-range scale is **'Reference'** or **'100'** (currently unsupported private value only used for unit tests), the definition is entirely by-passed. - If *Colour* domain-range scale is **'1'**, array :math:`a` is divided by ``scale_factor``, typically 100. Parameters ---------- a : array_like :math:`a` to scale from range **'100'**. scale_factor : numeric or array_like, optional Scale factor, usually *numeric* but can be an *array_like* if some axis need different scaling to be brought from range **'100'**. Returns ------- ndarray :math:`a` scaled from range **'100'**. Examples -------- With *Colour* domain-range scale set to **'Reference'**: >>> with domain_range_scale('Reference'): ... from_range_100(1) 1 With *Colour* domain-range scale set to **'1'**: >>> with domain_range_scale('1'): ... from_range_100(1) 0.01 With *Colour* domain-range scale set to **'100'** (unsupported): >>> with domain_range_scale('100'): ... from_range_100(1) 1 """ if _DOMAIN_RANGE_SCALE == '1': a /= scale_factor return a
[docs]def from_range_degrees(a, scale_factor=360): """ Scales given array :math:`a` from degrees range. The behaviour is as follows: - If *Colour* domain-range scale is **'Reference'**, the definition is entirely by-passed. - If *Colour* domain-range scale is **'1'**, array :math:`a` is divided by ``scale_factor``, typically 360. - If *Colour* domain-range scale is **'100'** (currently unsupported private value only used for unit tests), array :math:`a` is divided by ``scale_factor`` / 100, typically 360 / 100. Parameters ---------- a : array_like :math:`a` to scale from degrees range. scale_factor : numeric or array_like, optional Scale factor, usually *numeric* but can be an *array_like* if some axis need different scaling to be brought from degrees range. Returns ------- ndarray :math:`a` scaled from degrees range. Examples -------- With *Colour* domain-range scale set to **'Reference'**: >>> with domain_range_scale('Reference'): ... from_range_degrees(1) 1 With *Colour* domain-range scale set to **'1'**: >>> with domain_range_scale('1'): ... from_range_degrees(1) # doctest: +ELLIPSIS 0.0027777... With *Colour* domain-range scale set to **'100'** (unsupported): >>> with domain_range_scale('100'): ... from_range_degrees(1) # doctest: +ELLIPSIS 0.2777777... """ if _DOMAIN_RANGE_SCALE == '1': a /= scale_factor if _DOMAIN_RANGE_SCALE == '100': a /= scale_factor / 100 return a
[docs]def from_range_int(a, bit_depth=8, dtype=DEFAULT_FLOAT_DTYPE): """ Scales given array :math:`a` from int range. The behaviour is as follows: - If *Colour* domain-range scale is **'Reference'**, the definition is entirely by-passed. - If *Colour* domain-range scale is **'1'**, array :math:`a` is converted to :class:`np.ndarray` and divided by :math:`2^{bit\\_depth} - 1`. - If *Colour* domain-range scale is **'100'** (currently unsupported private value only used for unit tests), array :math:`a` is converted to :class:`np.ndarray` and divided by :math:`2^{bit\\_depth} - 1`. Parameters ---------- a : array_like :math:`a` to scale from int range. bit_depth : numeric or array_like, optional Bit depth, usually *int* but can be an *array_like* if some axis need different scaling to be brought from int range. dtype : object, optional Data type used for the conversion to :class:`np.ndarray`. Returns ------- ndarray :math:`a` scaled from int range. Notes ----- - To avoid precision issues and rounding, the scaling is performed on floating-point numbers. Examples -------- With *Colour* domain-range scale set to **'Reference'**: >>> with domain_range_scale('Reference'): ... from_range_int(1) 1 With *Colour* domain-range scale set to **'1'**: >>> with domain_range_scale('1'): ... from_range_int(1) # doctest: +ELLIPSIS array(0.0039215...) With *Colour* domain-range scale set to **'100'** (unsupported): >>> with domain_range_scale('100'): ... from_range_int(1) # doctest: +ELLIPSIS array(0.3921568...) """ maximum_code_value = 2 ** bit_depth - 1 if _DOMAIN_RANGE_SCALE == '1': a = np.asarray(a, dtype) a /= maximum_code_value if _DOMAIN_RANGE_SCALE == '100': a = np.asarray(a, dtype) a /= maximum_code_value / 100 return a