#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Array Utilities
===============
Defines array utilities objects.
"""
from __future__ import division, unicode_literals
import numpy as np
from colour.constants import EPSILON
__author__ = 'Colour Developers'
__copyright__ = 'Copyright (C) 2013 - 2015 - Colour Developers'
__license__ = 'New BSD License - http://opensource.org/licenses/BSD-3-Clause'
__maintainer__ = 'Colour Developers'
__email__ = 'colour-science@googlegroups.com'
__status__ = 'Production'
__all__ = ['as_numeric',
'closest',
'normalise',
'steps',
'is_uniform',
'in_array',
'tstack',
'tsplit',
'row_as_diagonal',
'dot_vector',
'dot_matrix']
[docs]def as_numeric(x):
"""
Converts given :math:`x` variable to *numeric*. In the event where
:math:`x` cannot be converted, it is passed as is.
Parameters
----------
x : object
Variable to convert.
Returns
-------
ndarray
:math:`x` variable converted to *numeric*.
See Also
--------
as_stack, as_shape, auto_axis
Examples
--------
>>> as_numeric(np.array([1]))
1.0
>>> as_numeric(np.arange(10))
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
"""
try:
return float(x)
except TypeError:
return x
[docs]def closest(y, x):
"""
Returns closest :math:`y` variable element to reference :math:`x` variable.
Parameters
----------
y : array_like
Variable to search for the closest element.
x : numeric
Reference variable.
Returns
-------
numeric
Closest :math:`y` variable element.
Examples
--------
>>> y = np.array([24.31357115,
... 63.62396289,
... 55.71528816,
... 62.70988028,
... 46.84480573,
... 25.40026416])
>>> closest(y, 63)
62.70988028
"""
return y[(np.abs(np.array(y) - x)).argmin()]
[docs]def normalise(x, axis=None, factor=1, clip=True):
"""
Normalises given *array_like* :math:`x` variable values and optionally clip
them between.
Parameters
----------
x : array_like
:math:`x` variable to normalise.
axis : numeric, optional
Normalization axis.
factor : numeric, optional
Normalization factor.
clip : bool, optional
Clip values between in domain [0, 'factor'].
Returns
-------
ndarray
Normalised :math:`x` variable.
Examples
--------
>>> x = np.array([0.48224885, 0.31651974, 0.22070513])
>>> normalise(x) # doctest: +ELLIPSIS
array([ 1. , 0.6563411..., 0.4576581...])
"""
x = np.asarray(x)
maximum = np.max(x, axis=axis)
x *= (1 / maximum[..., np.newaxis]) * factor
return np.clip(x, 0, factor) if clip else x
[docs]def steps(distribution):
"""
Returns the steps of given distribution.
Parameters
----------
distribution : array_like
Distribution to retrieve the steps.
Returns
-------
ndarray
Distribution steps.
Examples
--------
Uniformly spaced variable:
>>> y = np.array([1, 2, 3, 4, 5])
>>> steps(y)
array([1])
Non-uniformly spaced variable:
>>> y = np.array([1, 2, 3, 4, 8])
>>> steps(y)
array([1, 4])
"""
return np.unique([distribution[i + 1] - distribution[i]
for i in range(len(distribution) - 1)])
[docs]def in_array(a, b, tolerance=EPSILON):
"""
Tests whether each element of an array is also present in a second array
within given tolerance.
Parameters
----------
a : array_like
Array to test the elements from.
b : array_like
The values against which to test each value of array *a*.
Returns
-------
ndarray
A boolean array with *a* shape describing whether an element of *a* is
present in *b* within given tolerance.
References
----------
.. [1] Yorke, R. (2014). Python: Change format of np.array or allow
tolerance in in1d function. Retrieved March 27, 2015, from
http://stackoverflow.com/a/23521245/931625
Examples
--------
>>> a = np.array([0.50, 0.60])
>>> b = np.linspace(0, 10, 101)
>>> np.in1d(a, b)
array([ True, False], dtype=bool)
>>> in_array(a, b)
array([ True, True], dtype=bool)
"""
a = np.asarray(a)
b = np.asarray(b)
d = np.abs(np.ravel(a) - b[..., np.newaxis])
return np.any(d <= tolerance, axis=0).reshape(a.shape)
[docs]def tstack(a):
"""
Stacks arrays in sequence along the last axis (tail).
Rebuilds arrays divided by :func:`tsplit`.
Parameters
----------
a : array_like
Array to perform the stacking.
Returns
-------
ndarray
See Also
--------
tsplit
Examples
--------
>>> a = 0
>>> tstack((a, a, a))
array([0, 0, 0])
>>> a = np.arange(0, 6)
>>> tstack((a, a, a))
array([[0, 0, 0],
[1, 1, 1],
[2, 2, 2],
[3, 3, 3],
[4, 4, 4],
[5, 5, 5]])
>>> a = np.reshape(a, (1, 6))
>>> tstack((a, a, a))
array([[[0, 0, 0],
[1, 1, 1],
[2, 2, 2],
[3, 3, 3],
[4, 4, 4],
[5, 5, 5]]])
>>> a = np.reshape(a, (1, 1, 6))
>>> tstack((a, a, a))
array([[[[0, 0, 0],
[1, 1, 1],
[2, 2, 2],
[3, 3, 3],
[4, 4, 4],
[5, 5, 5]]]])
"""
a = np.asarray(a)
return np.concatenate([x[..., np.newaxis] for x in a], axis=-1)
[docs]def tsplit(a):
"""
Splits arrays in sequence along the last axis (tail).
Parameters
----------
a : array_like
Array to perform the splitting.
Returns
-------
ndarray
See Also
--------
tstack
Examples
--------
>>> a = np.array([0, 0, 0])
>>> tsplit(a)
array([0, 0, 0])
>>> a = np.array([[0, 0, 0],
... [1, 1, 1],
... [2, 2, 2],
... [3, 3, 3],
... [4, 4, 4],
... [5, 5, 5]])
>>> tsplit(a)
array([[0, 1, 2, 3, 4, 5],
[0, 1, 2, 3, 4, 5],
[0, 1, 2, 3, 4, 5]])
>>> a = np.array([[[0, 0, 0],
... [1, 1, 1],
... [2, 2, 2],
... [3, 3, 3],
... [4, 4, 4],
... [5, 5, 5]]])
>>> tsplit(a)
array([[[0, 1, 2, 3, 4, 5]],
<BLANKLINE>
[[0, 1, 2, 3, 4, 5]],
<BLANKLINE>
[[0, 1, 2, 3, 4, 5]]])
"""
a = np.asarray(a)
return np.array([a[..., x] for x in range(a.shape[-1])])
[docs]def row_as_diagonal(a):
"""
Returns the per row diagonal matrices of the given array.
Parameters
----------
a : array_like
Array to perform the diagonal matrices computation.
Returns
-------
ndarray
References
----------
.. [1] Castro, S. (2014). Numpy: Fastest way of computing diagonal for
each row of a 2d array. Retrieved August 22, 2014, from
http://stackoverflow.com/questions/26511401/numpy-fastest-way-of-computing-diagonal-for-each-row-of-a-2d-array/26517247#26517247 # noqa
Examples
--------
>>> a = np.array([[0.25891593, 0.07299478, 0.36586996],
... [0.30851087, 0.37131459, 0.16274825],
... [0.71061831, 0.67718718, 0.09562581],
... [0.71588836, 0.76772047, 0.15476079],
... [0.92985142, 0.22263399, 0.88027331]])
>>> row_as_diagonal(a)
array([[[ 0.25891593, 0. , 0. ],
[ 0. , 0.07299478, 0. ],
[ 0. , 0. , 0.36586996]],
<BLANKLINE>
[[ 0.30851087, 0. , 0. ],
[ 0. , 0.37131459, 0. ],
[ 0. , 0. , 0.16274825]],
<BLANKLINE>
[[ 0.71061831, 0. , 0. ],
[ 0. , 0.67718718, 0. ],
[ 0. , 0. , 0.09562581]],
<BLANKLINE>
[[ 0.71588836, 0. , 0. ],
[ 0. , 0.76772047, 0. ],
[ 0. , 0. , 0.15476079]],
<BLANKLINE>
[[ 0.92985142, 0. , 0. ],
[ 0. , 0.22263399, 0. ],
[ 0. , 0. , 0.88027331]]])
"""
a = np.expand_dims(a, -2)
return np.eye(a.shape[-1]) * a
[docs]def dot_vector(m, v):
"""
Convenient wrapper around :func:`np.einsum` with the following subscripts:
*'...ij,...j->...i'*.
It performs the dot product of two arrays where *m* parameter is expected
to be an array of 3x3 matrices and parameter *v* an array of vectors.
Parameters
----------
m : array_like
Array of 3x3 matrices.
v : array_like
Array of vectors.
Returns
-------
ndarray
See Also
--------
dot_matrix
Examples
--------
>>> m = np.array([[0.7328, 0.4296, -0.1624],
... [-0.7036, 1.6975, 0.0061],
... [0.0030, 0.0136, 0.9834]])
>>> m = np.reshape(np.tile(m, (6, 1)), (6, 3, 3))
>>> v = np.array([0.07049534, 0.10080000, 0.09558313])
>>> v = np.tile(v, (6, 1))
>>> dot_vector(m, v) # doctest: +ELLIPSIS
array([[ 0.0794399..., 0.1220905..., 0.0955788...],
[ 0.0794399..., 0.1220905..., 0.0955788...],
[ 0.0794399..., 0.1220905..., 0.0955788...],
[ 0.0794399..., 0.1220905..., 0.0955788...],
[ 0.0794399..., 0.1220905..., 0.0955788...],
[ 0.0794399..., 0.1220905..., 0.0955788...]])
"""
return np.einsum('...ij,...j->...i', m, v)
[docs]def dot_matrix(a, b):
"""
Convenient wrapper around :func:`np.einsum` with the following subscripts:
*'...ij,...jk->...ik'*.
It performs the dot product of two arrays where *a* parameter is expected
to be an array of 3x3 matrices and parameter *b* another array of of 3x3
matrices.
Parameters
----------
a : array_like
Array of 3x3 matrices.
b : array_like
Array of 3x3 matrices.
Returns
-------
ndarray
See Also
--------
dot_matrix
Examples
--------
>>> a = np.array([[0.7328, 0.4296, -0.1624],
... [-0.7036, 1.6975, 0.0061],
... [0.0030, 0.0136, 0.9834]])
>>> a = np.reshape(np.tile(a, (6, 1)), (6, 3, 3))
>>> b = a
>>> dot_matrix(a, b) # doctest: +ELLIPSIS
array([[[ 0.2342420..., 1.0418482..., -0.2760903...],
[-1.7099407..., 2.5793226..., 0.1306181...],
[-0.0044203..., 0.0377490..., 0.9666713...]],
<BLANKLINE>
[[ 0.2342420..., 1.0418482..., -0.2760903...],
[-1.7099407..., 2.5793226..., 0.1306181...],
[-0.0044203..., 0.0377490..., 0.9666713...]],
<BLANKLINE>
[[ 0.2342420..., 1.0418482..., -0.2760903...],
[-1.7099407..., 2.5793226..., 0.1306181...],
[-0.0044203..., 0.0377490..., 0.9666713...]],
<BLANKLINE>
[[ 0.2342420..., 1.0418482..., -0.2760903...],
[-1.7099407..., 2.5793226..., 0.1306181...],
[-0.0044203..., 0.0377490..., 0.9666713...]],
<BLANKLINE>
[[ 0.2342420..., 1.0418482..., -0.2760903...],
[-1.7099407..., 2.5793226..., 0.1306181...],
[-0.0044203..., 0.0377490..., 0.9666713...]],
<BLANKLINE>
[[ 0.2342420..., 1.0418482..., -0.2760903...],
[-1.7099407..., 2.5793226..., 0.1306181...],
[-0.0044203..., 0.0377490..., 0.9666713...]]])
"""
return np.einsum('...ij,...jk->...ik', a, b)