#!/usr/bin/env python
# -*- coding: utf-8 -*-

# Copyright (c) Philipp Wagner. All rights reserved.
# Licensed under the BSD license. See LICENSE file in the project root for full license information.

import numpy as np

class AbstractDistance(object):
    def __init__(self, name):
        self._name = name
        
    def __call__(self,p,q):
        raise NotImplementedError("Every AbstractDistance must implement the __call__ method.")
        
    @property
    def name(self):
        return self._name

    def __repr__(self):
        return self._name
        
class EuclideanDistance(AbstractDistance):
    def __init__(self):
        AbstractDistance.__init__(self,"EuclideanDistance")

    def __call__(self, p, q):
        p = np.asarray(p).flatten()
        q = np.asarray(q).flatten()
        return np.sqrt(np.sum(np.power((p-q),2)))

class CosineDistance(AbstractDistance):
    """
        Negated Mahalanobis Cosine Distance.
    
        Literature:
            "Studies on sensitivity of face recognition performance to eye location accuracy.". Master Thesis (2004), Wang
    """
    def __init__(self):
        AbstractDistance.__init__(self,"CosineDistance")

    def __call__(self, p, q):
        p = np.asarray(p).flatten()
        q = np.asarray(q).flatten()
        return -np.dot(p.T,q) / (np.sqrt(np.dot(p,p.T)*np.dot(q,q.T)))

class NormalizedCorrelation(AbstractDistance):
    """
        Calculates the NormalizedCorrelation Coefficient for two vectors.
    
        Literature:
            "Multi-scale Local Binary Pattern Histogram for Face Recognition". PhD (2008). Chi Ho Chan, University Of Surrey.
    """
    def __init__(self):
        AbstractDistance.__init__(self,"NormalizedCorrelation")
    
    def __call__(self, p, q):
        p = np.asarray(p).flatten()
        q = np.asarray(q).flatten()
        pmu = p.mean()
        qmu = q.mean()
        pm = p - pmu
        qm = q - qmu
        return 1.0 - (np.dot(pm, qm) / (np.sqrt(np.dot(pm, pm)) * np.sqrt(np.dot(qm, qm))))
        
class ChiSquareDistance(AbstractDistance):
    """
        Negated Mahalanobis Cosine Distance.
    
        Literature:
            "Studies on sensitivity of face recognition performance to eye location accuracy.". Master Thesis (2004), Wang
    """
    def __init__(self):
        AbstractDistance.__init__(self,"ChiSquareDistance")

    def __call__(self, p, q):
        p = np.asarray(p).flatten()
        q = np.asarray(q).flatten()
        bin_dists = (p-q)**2 / (p+q+np.finfo('float').eps)
        return np.sum(bin_dists)

class HistogramIntersection(AbstractDistance):
    def __init__(self):
        AbstractDistance.__init__(self,"HistogramIntersection")

    def __call__(self, p, q):
        p = np.asarray(p).flatten()
        q = np.asarray(q).flatten()
        return np.sum(np.minimum(p,q))

class BinRatioDistance(AbstractDistance):
    """
    Calculates the Bin Ratio Dissimilarity.

    Literature:
      "Use Bin-Ratio Information for Category and Scene Classification" (2010), Xie et.al. 
    """
    def __init__(self):
        AbstractDistance.__init__(self,"BinRatioDistance")

    def __call__(self, p, q):
        p = np.asarray(p).flatten()
        q = np.asarray(q).flatten()
        a = np.abs(1-np.dot(p,q.T)) # NumPy needs np.dot instead of * for reducing to tensor
        b = ((p-q)**2 + 2*a*(p*q))/((p+q)**2+np.finfo('float').eps)
        return np.abs(np.sum(b))

class L1BinRatioDistance(AbstractDistance):
    """
    Calculates the L1-Bin Ratio Dissimilarity.

    Literature:
      "Use Bin-Ratio Information for Category and Scene Classification" (2010), Xie et.al. 
    """
    def __init__(self):
        AbstractDistance.__init__(self,"L1-BinRatioDistance")
    
    def __call__(self, p, q):
        p = np.asarray(p, dtype=np.float).flatten()
        q = np.asarray(q, dtype=np.float).flatten()
        a = np.abs(1-np.dot(p,q.T)) # NumPy needs np.dot instead of * for reducing to tensor
        b = ((p-q)**2 + 2*a*(p*q)) * abs(p-q) / ((p+q)**2+np.finfo('float').eps)
        return np.abs(np.sum(b))

class ChiSquareBRD(AbstractDistance):
    """
    Calculates the ChiSquare-Bin Ratio Dissimilarity.

    Literature:
      "Use Bin-Ratio Information for Category and Scene Classification" (2010), Xie et.al. 
    """
    def __init__(self):
        AbstractDistance.__init__(self,"ChiSquare-BinRatioDistance")
    
    def __call__(self, p, q):
        p = np.asarray(p, dtype=np.float).flatten()
        q = np.asarray(q, dtype=np.float).flatten()
        a = np.abs(1-np.dot(p,q.T)) # NumPy needs np.dot instead of * for reducing to tensor
        b = ((p-q)**2 + 2*a*(p*q)) * (p-q)**2 / ((p+q)**3+np.finfo('float').eps)
        return np.abs(np.sum(b))

[((2543, 2560), 'numpy.sum', 'np.sum', (['bin_dists'], {}), '(bin_dists)\n', (2549, 2560), True, 'import numpy as np\n'), ((2822, 2838), 'numpy.minimum', 'np.minimum', (['p', 'q'], {}), '(p, q)\n', (2832, 2838), True, 'import numpy as np\n'), ((3420, 3429), 'numpy.sum', 'np.sum', (['b'], {}), '(b)\n', (3426, 3429), True, 'import numpy as np\n'), ((4069, 4078), 'numpy.sum', 'np.sum', (['b'], {}), '(b)\n', (4075, 4078), True, 'import numpy as np\n'), ((4726, 4735), 'numpy.sum', 'np.sum', (['b'], {}), '(b)\n', (4732, 4735), True, 'import numpy as np\n'), ((742, 755), 'numpy.asarray', 'np.asarray', (['p'], {}), '(p)\n', (752, 755), True, 'import numpy as np\n'), ((778, 791), 'numpy.asarray', 'np.asarray', (['q'], {}), '(q)\n', (788, 791), True, 'import numpy as np\n'), ((832, 850), 'numpy.power', 'np.power', (['(p - q)', '(2)'], {}), '(p - q, 2)\n', (840, 850), True, 'import numpy as np\n'), ((1226, 1239), 'numpy.asarray', 'np.asarray', (['p'], {}), '(p)\n', (1236, 1239), True, 'import numpy as np\n'), ((1262, 1275), 'numpy.asarray', 'np.asarray', (['q'], {}), '(q)\n', (1272, 1275), True, 'import numpy as np\n'), ((1302, 1316), 'numpy.dot', 'np.dot', (['p.T', 'q'], {}), '(p.T, q)\n', (1308, 1316), True, 'import numpy as np\n'), ((1781, 1794), 'numpy.asarray', 'np.asarray', (['p'], {}), '(p)\n', (1791, 1794), True, 'import numpy as np\n'), ((1817, 1830), 'numpy.asarray', 'np.asarray', (['q'], {}), '(q)\n', (1827, 1830), True, 'import numpy as np\n'), ((1951, 1965), 'numpy.dot', 'np.dot', (['pm', 'qm'], {}), '(pm, qm)\n', (1957, 1965), True, 'import numpy as np\n'), ((2409, 2422), 'numpy.asarray', 'np.asarray', (['p'], {}), '(p)\n', (2419, 2422), True, 'import numpy as np\n'), ((2445, 2458), 'numpy.asarray', 'np.asarray', (['q'], {}), '(q)\n', (2455, 2458), True, 'import numpy as np\n'), ((2740, 2753), 'numpy.asarray', 'np.asarray', (['p'], {}), '(p)\n', (2750, 2753), True, 'import numpy as np\n'), ((2776, 2789), 'numpy.asarray', 'np.asarray', (['q'], {}), '(q)\n', (2786, 2789), True, 'import numpy as np\n'), ((3177, 3190), 'numpy.asarray', 'np.asarray', (['p'], {}), '(p)\n', (3187, 3190), True, 'import numpy as np\n'), ((3213, 3226), 'numpy.asarray', 'np.asarray', (['q'], {}), '(q)\n', (3223, 3226), True, 'import numpy as np\n'), ((3258, 3272), 'numpy.dot', 'np.dot', (['p', 'q.T'], {}), '(p, q.T)\n', (3264, 3272), True, 'import numpy as np\n'), ((3781, 3810), 'numpy.asarray', 'np.asarray', (['p'], {'dtype': 'np.float'}), '(p, dtype=np.float)\n', (3791, 3810), True, 'import numpy as np\n'), ((3833, 3862), 'numpy.asarray', 'np.asarray', (['q'], {'dtype': 'np.float'}), '(q, dtype=np.float)\n', (3843, 3862), True, 'import numpy as np\n'), ((3894, 3908), 'numpy.dot', 'np.dot', (['p', 'q.T'], {}), '(p, q.T)\n', (3900, 3908), True, 'import numpy as np\n'), ((4438, 4467), 'numpy.asarray', 'np.asarray', (['p'], {'dtype': 'np.float'}), '(p, dtype=np.float)\n', (4448, 4467), True, 'import numpy as np\n'), ((4490, 4519), 'numpy.asarray', 'np.asarray', (['q'], {'dtype': 'np.float'}), '(q, dtype=np.float)\n', (4500, 4519), True, 'import numpy as np\n'), ((4551, 4565), 'numpy.dot', 'np.dot', (['p', 'q.T'], {}), '(p, q.T)\n', (4557, 4565), True, 'import numpy as np\n'), ((1327, 1341), 'numpy.dot', 'np.dot', (['p', 'p.T'], {}), '(p, p.T)\n', (1333, 1341), True, 'import numpy as np\n'), ((1341, 1355), 'numpy.dot', 'np.dot', (['q', 'q.T'], {}), '(q, q.T)\n', (1347, 1355), True, 'import numpy as np\n'), ((2505, 2522), 'numpy.finfo', 'np.finfo', (['"""float"""'], {}), "('float')\n", (2513, 2522), True, 'import numpy as np\n'), ((3375, 3392), 'numpy.finfo', 'np.finfo', (['"""float"""'], {}), "('float')\n", (3383, 3392), True, 'import numpy as np\n'), ((4024, 4041), 'numpy.finfo', 'np.finfo', (['"""float"""'], {}), "('float')\n", (4032, 4041), True, 'import numpy as np\n'), ((4681, 4698), 'numpy.finfo', 'np.finfo', (['"""float"""'], {}), "('float')\n", (4689, 4698), True, 'import numpy as np\n'), ((1977, 1991), 'numpy.dot', 'np.dot', (['pm', 'pm'], {}), '(pm, pm)\n', (1983, 1991), True, 'import numpy as np\n'), ((2003, 2017), 'numpy.dot', 'np.dot', (['qm', 'qm'], {}), '(qm, qm)\n', (2009, 2017), True, 'import numpy as np\n')]