Source code for colourlab.tensor

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
tensor: Compute colour metric tensors. Part of the colourlab package.

Copyright (C) 2013-2021 Ivar Farup

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or (at
your option) any later version.

This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""

import numpy as np
from . import data, space


# =============================================================================
# Colour metric tensors
# =============================================================================

[docs]def construct_tensor(sp, tensor_ndata, dat): """ Construct the Tensors object with correct dimensions The tensors_ndata has shape N x 3 x 3. Construct Tensors object with shape corresponding to the data points in dat. Parameters ---------- sp: space.Space The colour space of the tensor_ndata tensor_ndata: ndarray N x 3 x 3 ndarray of tensor data. dat: data.Points Colour data points """ sh = np.hstack((np.array(dat.sh), 3)) return data.Tensors(sp, np.reshape(tensor_ndata, sh), dat)
[docs]def euclidean(sp, dat): """ Compute the general Euclidean metric in the given colour space. Returns Tensors. Parameters ---------- sp : space.Space The colour space in which the metric tensor is Euclidean. dat : data.Points The colour points for which to compute the metric. Returns ------- Euclidean : Tensors The metric tensors. """ g = sp.empty_matrix(dat.flattened_XYZ) for i in range(np.shape(g)[0]): g[i] = np.eye(3) return construct_tensor(sp, g, dat)
[docs]def polar(sp, dat): """ Compute the general Euclidean metric in cylindrical coordinates. Assumes z, r, theta (LCh) order. Returns Tensors. Parameters ---------- sp : space.Space The colour space in which the metric tensor is Euclidean, with cylindrical coordinates dat : data.Points The colour points for which to compute the metric. Returns ------- Tensors The metric tensors. """ g = sp.empty_matrix(dat.flattened_XYZ) points = dat.get_flattened(sp) for i in range(np.shape(g)[0]): g[i] = np.eye(3) g[i, 2, 2] = points[i, 1]**2 return construct_tensor(sp, g, dat)
[docs]def dE_ab(dat): """ Compute the DEab metric as Tensors for the given data points. Parameters ---------- dat : data.Points The colour points for which to compute the metric. Returns ------- DEab : Tensors The metric tensors. """ return euclidean(space.cielab, dat)
[docs]def dE_uv(dat): """ Compute the DEuv metric as Tensors for the given data points. Parameters ---------- dat : data.Points The colour points for which to compute the metric. Returns ------- DEuv : Tensors The metric tensors. """ return euclidean(space.cieluv, dat)
[docs]def dE_E(dat): """ Compute the DEE metric as Tensors for the given data points. Parameters ---------- dat : data.Points The colour points for which to compute the metric. Returns ------- DEE : Tensors The metric tensors. """ return euclidean(space.lgj_e, dat)
[docs]def dE_DIN99(dat): """ Compute the DIN99 metric as Tensors for the given data points. Parameters ---------- dat : data.Points The colour points for which to compute the metric. Returns ------- DIN99 : Tensors The metric tensors. """ return euclidean(space.din99, dat)
[docs]def dE_DIN99b(dat): """ Compute the DIN99b metric as Tensors for the given data points. Parameters ---------- dat : data.Points The colour points for which to compute the metric. Returns ------- DIN99b : Tensors The metric tensors. """ return euclidean(space.din99b, dat)
[docs]def dE_DIN99c(dat): """ Compute the DIN99c metric as Tensors for the given data points. Parameters ---------- dat : data.Points The colour points for which to compute the metric. Returns ------- DIN99c : Tensors The metric tensors. """ return euclidean(space.din99c, dat)
[docs]def dE_DIN99d(dat): """ Compute the DIN99d metric as Tensors for the given data points. Parameters ---------- dat : data.Points The colour points for which to compute the metric. Returns ------- DIN99d : Tensors The metric tensors. """ return euclidean(space.din99d, dat)
[docs]def dE_00(dat, k_L=1, k_C=1, k_h=1): """ Compute the Riemannised CIEDE00 metric for the given data points. Returns Tensors. Be aware that the tensor is singluar at C = 0. Parameters ---------- dat : data.Points The colour points for which to compute the metric. k_L : float Parameter of the CIEDE00 metric k_C : float Parameter of the CIEDE00 metric k_h : float Parameter of the CIEDE00 metric Returns ------- DE00 : Tensors The metric tensors. """ lch = dat.get_flattened(space.ciede00lch) L = lch[:, 0] C = lch[:, 1] h = lch[:, 2] h_deg = np.rad2deg(h) h_deg[h_deg < 0] = h_deg[h_deg < 0] + 360 S_L = 1 + (0.015 * (L - 50)**2) / np.sqrt(20 + (L - 50)**2) S_C = 1 + 0.045 * C T = 1 - 0.17 * np.cos(np.deg2rad(h_deg - 30)) + \ .24 * np.cos(2*h) + \ .32 * np.cos(np.deg2rad(3 * h_deg + 6)) - \ .2 * np.cos(np.deg2rad(4 * h_deg - 63)) S_h = 1 + 0.015 * C * T R_C = 2 * np.sqrt(C**7 / (C**7 + 25**7)) d_theta = 30 * np.exp(-((h_deg - 275) / 25)**2) R_T = - R_C * np.sin(np.deg2rad(2 * d_theta)) g = space.ciede00lch.empty_matrix(lch) g[:, 0, 0] = (k_L * S_L)**(-2) g[:, 1, 1] = (k_C * S_C)**(-2) g[:, 2, 2] = C**2 * (k_h * S_h)**(-2) g[:, 1, 2] = .5 * C * R_T / (k_C * S_C * k_h * S_h) g[:, 2, 1] = .5 * C * R_T / (k_C * S_C * k_h * S_h) return construct_tensor(space.ciede00lch, g, dat)
[docs]def poincare_disk(sp, dat): """ Compute the general Poincare Disk metric in the given colour space. Returns Tensors. Assumes that sp is a Poincare Disk of some kind, and thus has a radius of curvature as sp.R. Parameters ---------- dat : data.Points The colour points for which to compute the metric. Returns ------- Poincare : Tensors The metric tensors. """ d = dat.get_flattened(sp) g = sp.empty_matrix(d) for i in range(np.shape(g)[0]): g[i, 0, 0] = 1 g[i, 1, 1] = sp.R**2 * 4. / (1 - d[i, 1]**2 - d[i, 2]**2)**2 g[i, 2, 2] = sp.R**2 * 4. / (1 - d[i, 1]**2 - d[i, 2]**2)**2 return construct_tensor(sp, g, dat)
# TODO: # # Functions (returning Tensors): # stiles # helmholz # schrodinger # vos # SVF # CIECAM02 # CAM16 # +++ # ============================================================================= # Christoffel symbols # =============================================================================
[docs]def christoffel(sp, tensor, dat, du=1e-8): """ Compute the Christoffel symbols numerically. Compute the Christoffel symbols of the second kind for the given metric tensor function in the given colour space at the given data points. Parameters ---------- sp : space.Space The colour space. tensor : func Function returning the metric tensor of the data dat : data.Points The colour data points (or image) du : float Delta u for the computation of the numerical derivatives Returns ------- ndata : The Christoffel symbols of the second kind as M x ... x N x 3 x 3 x 3 ndarray (for M x ... x N x 3 data points) """ x = dat.get(sp) dxi = np.zeros(x.shape) dxj = np.zeros(x.shape) dxk = np.zeros(x.shape) dxi[..., 0] = du dxj[..., 1] = du dxk[..., 2] = du datpdxi = data.Points(sp, x + dxi) datpdxj = data.Points(sp, x + dxj) datpdxk = data.Points(sp, x + dxk) g = tensor(dat).get(sp) gpdxi = tensor(datpdxi).get(sp) gpdxj = tensor(datpdxj).get(sp) gpdxk = tensor(datpdxk).get(sp) dgdx = np.zeros(np.hstack((np.array(g.shape), 3))) dgdx[..., 0] = (gpdxi - g) / du dgdx[..., 1] = (gpdxj - g) / du dgdx[..., 2] = (gpdxk - g) / du Gamma = np.zeros(dgdx.shape) # first kind for c in range(3): for a in range(3): for b in range(3): Gamma[..., c, a, b] = .5 * (dgdx[..., c, a, b] + dgdx[..., c, b, a] - dgdx[..., a, b, c]) ginv = np.linalg.inv(g) return np.einsum('...ij,...jkl', ginv, Gamma) # convert to second kind