#!/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))

[((80, 15, 80, 32), 'numpy.sum', 'np.sum', ({(80, 22, 80, 31): 'bin_dists'}, {}), '(bin_dists)', True, 'import numpy as np\n'), ((89, 22, 89, 37), 'numpy.minimum', 'np.minimum', ({(89, 33, 89, 34): 'p', (89, 35, 89, 36): 'q'}, {}), '(p, q)', True, 'import numpy as np\n'), ((106, 22, 106, 31), 'numpy.sum', 'np.sum', ({(106, 29, 106, 30): 'b'}, {}), '(b)', True, 'import numpy as np\n'), ((123, 22, 123, 31), 'numpy.sum', 'np.sum', ({(123, 29, 123, 30): 'b'}, {}), '(b)', True, 'import numpy as np\n'), ((140, 22, 140, 31), 'numpy.sum', 'np.sum', ({(140, 29, 140, 30): 'b'}, {}), '(b)', True, 'import numpy as np\n'), ((28, 12, 28, 25), 'numpy.asarray', 'np.asarray', ({(28, 23, 28, 24): 'p'}, {}), '(p)', True, 'import numpy as np\n'), ((29, 12, 29, 25), 'numpy.asarray', 'np.asarray', ({(29, 23, 29, 24): 'q'}, {}), '(q)', True, 'import numpy as np\n'), ((30, 30, 30, 47), 'numpy.power', 'np.power', ({(30, 40, 30, 43): '(p - q)', (30, 45, 30, 46): '(2)'}, {}), '(p - q, 2)', True, 'import numpy as np\n'), ((43, 12, 43, 25), 'numpy.asarray', 'np.asarray', ({(43, 23, 43, 24): 'p'}, {}), '(p)', True, 'import numpy as np\n'), ((44, 12, 44, 25), 'numpy.asarray', 'np.asarray', ({(44, 23, 44, 24): 'q'}, {}), '(q)', True, 'import numpy as np\n'), ((45, 16, 45, 29), 'numpy.dot', 'np.dot', ({(45, 23, 45, 26): 'p.T', (45, 27, 45, 28): 'q'}, {}), '(p.T, q)', True, 'import numpy as np\n'), ((58, 12, 58, 25), 'numpy.asarray', 'np.asarray', ({(58, 23, 58, 24): 'p'}, {}), '(p)', True, 'import numpy as np\n'), ((59, 12, 59, 25), 'numpy.asarray', 'np.asarray', ({(59, 23, 59, 24): 'q'}, {}), '(q)', True, 'import numpy as np\n'), ((64, 22, 64, 36), 'numpy.dot', 'np.dot', ({(64, 29, 64, 31): 'pm', (64, 33, 64, 35): 'qm'}, {}), '(pm, qm)', True, 'import numpy as np\n'), ((77, 12, 77, 25), 'numpy.asarray', 'np.asarray', ({(77, 23, 77, 24): 'p'}, {}), '(p)', True, 'import numpy as np\n'), ((78, 12, 78, 25), 'numpy.asarray', 'np.asarray', ({(78, 23, 78, 24): 'q'}, {}), '(q)', True, 'import numpy as np\n'), ((87, 12, 87, 25), 'numpy.asarray', 'np.asarray', ({(87, 23, 87, 24): 'p'}, {}), '(p)', True, 'import numpy as np\n'), ((88, 12, 88, 25), 'numpy.asarray', 'np.asarray', ({(88, 23, 88, 24): 'q'}, {}), '(q)', True, 'import numpy as np\n'), ((102, 12, 102, 25), 'numpy.asarray', 'np.asarray', ({(102, 23, 102, 24): 'p'}, {}), '(p)', True, 'import numpy as np\n'), ((103, 12, 103, 25), 'numpy.asarray', 'np.asarray', ({(103, 23, 103, 24): 'q'}, {}), '(q)', True, 'import numpy as np\n'), ((104, 21, 104, 34), 'numpy.dot', 'np.dot', ({(104, 28, 104, 29): 'p', (104, 30, 104, 33): 'q.T'}, {}), '(p, q.T)', True, 'import numpy as np\n'), ((119, 12, 119, 41), 'numpy.asarray', 'np.asarray', (), '', True, 'import numpy as np\n'), ((120, 12, 120, 41), 'numpy.asarray', 'np.asarray', (), '', True, 'import numpy as np\n'), ((121, 21, 121, 34), 'numpy.dot', 'np.dot', ({(121, 28, 121, 29): 'p', (121, 30, 121, 33): 'q.T'}, {}), '(p, q.T)', True, 'import numpy as np\n'), ((136, 12, 136, 41), 'numpy.asarray', 'np.asarray', (), '', True, 'import numpy as np\n'), ((137, 12, 137, 41), 'numpy.asarray', 'np.asarray', (), '', True, 'import numpy as np\n'), ((138, 21, 138, 34), 'numpy.dot', 'np.dot', ({(138, 28, 138, 29): 'p', (138, 30, 138, 33): 'q.T'}, {}), '(p, q.T)', True, 'import numpy as np\n'), ((45, 41, 45, 54), 'numpy.dot', 'np.dot', ({(45, 48, 45, 49): 'p', (45, 50, 45, 53): 'p.T'}, {}), '(p, p.T)', True, 'import numpy as np\n'), ((45, 55, 45, 68), 'numpy.dot', 'np.dot', ({(45, 62, 45, 63): 'q', (45, 64, 45, 67): 'q.T'}, {}), '(q, q.T)', True, 'import numpy as np\n'), ((79, 36, 79, 53), 'numpy.finfo', 'np.finfo', ({(79, 45, 79, 52): '"""float"""'}, {}), "('float')", True, 'import numpy as np\n'), ((105, 45, 105, 62), 'numpy.finfo', 'np.finfo', ({(105, 54, 105, 61): '"""float"""'}, {}), "('float')", True, 'import numpy as np\n'), ((122, 58, 122, 75), 'numpy.finfo', 'np.finfo', ({(122, 67, 122, 74): '"""float"""'}, {}), "('float')", True, 'import numpy as np\n'), ((139, 58, 139, 75), 'numpy.finfo', 'np.finfo', ({(139, 67, 139, 74): '"""float"""'}, {}), "('float')", True, 'import numpy as np\n'), ((64, 48, 64, 62), 'numpy.dot', 'np.dot', ({(64, 55, 64, 57): 'pm', (64, 59, 64, 61): 'pm'}, {}), '(pm, pm)', True, 'import numpy as np\n'), ((64, 74, 64, 88), 'numpy.dot', 'np.dot', ({(64, 81, 64, 83): 'qm', (64, 85, 64, 87): 'qm'}, {}), '(qm, qm)', True, 'import numpy as np\n')]