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

import numpy as np
import matplotlib.pyplot as plt

def pair(x1,x2):
    """define pairwise potential function such that pair(x1,x2) = psi(x1,x2)"""
    if (x1==x2):
        out = 0.9
    else:
        out = 0.1
    return out

def single(x,y,epsilon,ForegroundMean,ForegroundVar,BackgroundMean,BackgroundVar):
    """ define singleton potential function such that single(x,y) = phi(x,y)"""
    if x==0:
        out = epsilon + (1/((2*np.pi)**(3/2)*np.sqrt(np.linalg.det(BackgroundVar))))*np.exp(-0.5*(y-BackgroundMean).dot(np.linalg.solve(BackgroundVar, y-BackgroundMean)))
    else:
        out = epsilon + (1/((2*np.pi)**(3/2)*np.sqrt(np.linalg.det(ForegroundVar))))*np.exp(-0.5*(y-ForegroundMean).dot(np.linalg.solve(ForegroundVar, y-ForegroundMean)))
    return out

def F(epsilon,FM,FC,BM,BC,y,MessageIn):
    """define BP update function that takes a set of messages as inputs
    and ouputs the updated message according to the BP update rule
    F(y,nu=[nu1 nu2 ...]) = log(phi(1,y)/phi(0,y)) + sum{log((psi(1,1)e^nu(i)+psi(1,0))/(psi(0,1)e^nu(i)+psi(0,0)))}"""

    A=[]
    for i in range(len(MessageIn)):
        A.append((pair(1,1)*np.exp(MessageIn[i])+pair(1,0))/(pair(0,1)*np.exp(MessageIn[i])+pair(0,0)))
    MessageOut = np.log(single(1,y,epsilon,FM,FC,BM,BC)/single(0,y,epsilon,FM,FC,BM,BC)) + sum(np.log(A))
    return MessageOut

debug = False

# load image and compute the sample means and covariances
Im = (1/255)*plt.imread('flower.bmp').astype(np.double)
Foreground = plt.imread('foreground.bmp')
Background = plt.imread('background.bmp')
ImV = Im.transpose(2,1,0).reshape(3,-1)
Fo = Foreground.flatten('F')
Bo = Background.flatten('F')
ForegroundSample = ImV[:,Fo>0]
BackgroundSample = ImV[:,Bo>0]
FM = np.mean(ForegroundSample, 1)
FC = np.cov(ForegroundSample)
BM = np.mean(BackgroundSample, 1)
BC = np.cov(BackgroundSample)
print(FM)
print(FC)
print(BM)
print(BC)
# define parameters
epsilon = 0.01
nrow = Im.shape[0]
ncol = Im.shape[1]
niter= 1

# The following four matrices contain the messages (in log-likelihood format)
# such that hhorright(i,j) = log(nu_{(i,j)->(i,j+1)}(+1)/nu_{(i,j)->(i,j+1)}(0))
hhorright = np.zeros((nrow, ncol-1)) # hhorright(i,j) is the message from (i,j) to (i,j+1)
hhorleft  = np.zeros((nrow, ncol-1)) # hhorleft(i,j) is the message from (i,j+1) to (i,j)
hvertup   = np.zeros((nrow-1, ncol)) # hvertup(i,j) is the message from (i+1,j) to (i,j)
hvertdown = np.zeros((nrow-1, ncol)) # hvertdown(i,j) is the message from (i,j) to (i+1,j)

# The following matrices store the updated messages,
hhorrightnew = np.zeros((nrow, ncol-1))
hhorleftnew  = np.zeros((nrow, ncol-1))
hvertupnew   = np.zeros((nrow-1, ncol))
hvertdownnew = np.zeros((nrow-1, ncol))

prevms =[]

# run parallel BP
for t in range(niter):
    # looping over edges with 3 inputs
    for i in range(1, nrow-1):
        for j in range(2, ncol-1):
            hhorrightnew[i  , j  ] = F(epsilon,FM,FC,BM,BC,Im[i,j,:],[hhorright[i  , j-1], hvertdown[i-1, j  ], hvertup [i, j]])
            hvertdownnew[i  , j  ] = F(epsilon,FM,FC,BM,BC,Im[i,j,:],[hvertdown[i-1, j  ], hhorright[i  , j-1], hhorleft[i, j]])
            hhorleftnew [i  , j-1] = F(epsilon,FM,FC,BM,BC,Im[i,j,:],[hhorleft [i  , j  ], hvertdown[i-1, j  ], hvertup [i, j]])
            hvertupnew  [i-1, j  ] = F(epsilon,FM,FC,BM,BC,Im[i,j,:],[hvertup  [i  , j  ], hhorright[i  , j-1], hhorleft[i, j]])

    # looping over edges with 2 inputs
    for i in range(1, nrow-1):
        hhorrightnew[i  , 0     ] = F(epsilon,FM,FC,BM,BC,Im[i, 0,:]     , [hvertdown[i-1, 0     ], hvertup  [i  ,0     ]])
        hhorleftnew [i  , ncol-2] = F(epsilon,FM,FC,BM,BC,Im[i, ncol-1,:], [hvertdown[i-1, ncol-1], hvertup  [i  ,ncol-1]])
        hvertdownnew[i  , 0     ] = F(epsilon,FM,FC,BM,BC,Im[i, 0,:]     , [hvertdown[i-1, 0     ], hhorleft [i  ,0     ]])
        hvertdownnew[i  , ncol-1] = F(epsilon,FM,FC,BM,BC,Im[i, ncol-1,:], [hvertdown[i-1, ncol-1], hhorright[i-1,ncol-2]])
        hvertupnew  [i-1, 0     ] = F(epsilon,FM,FC,BM,BC,Im[i, 0,:]     , [hvertup  [i  , 0     ], hhorleft [i  ,0     ]])
        hvertupnew  [i-1, ncol-1] = F(epsilon,FM,FC,BM,BC,Im[i, ncol-1,:], [hvertup  [i  , ncol-1], hhorright[i  ,ncol-2]])

    for i in range(1, ncol-1):
        hvertupnew  [nrow-2,   i] = F(epsilon,FM,FC,BM,BC,Im[nrow-1, i,:], [hhorright[nrow-1, i-1], hhorleft [nrow-1, i]])
        hvertdownnew[0     ,   i] = F(epsilon,FM,FC,BM,BC,Im[0     , i,:], [hhorright[0     , i-1], hhorleft [0     , i]])
        hhorrightnew[0     ,   i] = F(epsilon,FM,FC,BM,BC,Im[0     , i,:], [hhorright[0     , i-1], hvertup  [0     , i]])
        hhorrightnew[nrow-1,   i] = F(epsilon,FM,FC,BM,BC,Im[nrow-1, i,:], [hhorright[nrow-1, i-1], hvertdown[nrow-2, i]])
        hhorleftnew [0     , i-1] = F(epsilon,FM,FC,BM,BC,Im[0     , i,:], [hhorleft [0     , i  ], hvertup  [0     , i]])
        hhorleftnew [nrow-1, i-1] = F(epsilon,FM,FC,BM,BC,Im[nrow-1, i,:], [hhorleft [nrow-1, i  ], hvertdown[nrow-2, i]])

    # Edges with single input
    hvertdownnew[0     , 0     ] = F(epsilon,FM,FC,BM,BC,Im[0     , 0,     :], [hhorleft [0     , 0     ]])
    hvertdownnew[0     , ncol-1] = F(epsilon,FM,FC,BM,BC,Im[0     , ncol-1,:], [hhorright[0     , ncol-2]])
    hvertupnew  [nrow-2, 0     ] = F(epsilon,FM,FC,BM,BC,Im[nrow-1, 0,     :], [hhorleft [nrow-1, 0     ]])
    hvertupnew  [nrow-2, ncol-1] = F(epsilon,FM,FC,BM,BC,Im[nrow-1, ncol-1,:], [hhorright[nrow-1, ncol-2]])

    hhorrightnew[0     , 0     ] = F(epsilon,FM,FC,BM,BC,Im[0     , 0,:     ], [hvertup  [0     , 0     ]])
    hhorrightnew[nrow-1, 0     ] = F(epsilon,FM,FC,BM,BC,Im[nrow-1, 0,:     ], [hvertdown[nrow-2, 0     ]])
    hhorleftnew [0     , ncol-2] = F(epsilon,FM,FC,BM,BC,Im[0     , ncol-1,:], [hvertup  [0     , ncol-2]])
    hhorleftnew [nrow-1, ncol-2] = F(epsilon,FM,FC,BM,BC,Im[nrow-1, ncol-1,:], [hvertdown[nrow-2, ncol-1]])

    # store the new messages as current messages
    hvertdown = hvertdownnew;
    hvertup   = hvertupnew;
    hhorleft  = hhorleftnew;
    hhorright = hhorrightnew;

    if(t in [2,3,4]):
        llr = np.zeros((nrow, ncol))

        # nodes of degree 4
        for i in range(1,nrow-1):
            for j in range(1, ncol-1):
                llr[i, j] = F(epsilon,FM,FC,BM,BC,Im[i,j,:], [hhorright[i, j-1], hvertdown[i-1, j], hvertup[i, j], hhorleft[i, j]])

        # nodes of degree 3
        for i in range(1, nrow-1):
            llr[i, 0     ] = F(epsilon,FM,FC,BM,BC,Im[i, 0     ,:], [hvertdown[i-1, 0     ], hvertup[i, 0     ], hhorleft [i, 0    ]])
            llr[i, ncol-1] = F(epsilon,FM,FC,BM,BC,Im[i, ncol-1,:], [hvertdown[i-1, ncol-1], hvertup[i, ncol-1], hhorright[i,ncol-2]])

        for j in range(1, ncol-1):
            llr[0     ,j] = F(epsilon,FM,FC,BM,BC,Im[0     , j,:], [hhorright[0     , j-1], hhorleft[0     , j], hvertup  [0     , j]])
            llr[nrow-1,j] = F(epsilon,FM,FC,BM,BC,Im[nrow-1, j,:], [hhorright[nrow-1, j-1], hhorleft[nrow-1, j], hvertdown[nrow-2, j]])

        # nodes of degree 2
        llr[0     , 0     ] = F(epsilon,FM,FC,BM,BC,Im[0     , 0     ,:], [hhorleft [0     , 0     ], hvertup  [0     , 0     ]])
        llr[0     , ncol-1] = F(epsilon,FM,FC,BM,BC,Im[0     , ncol-1,:], [hhorright[0     , ncol-2], hvertup  [0     , ncol-2]])
        llr[nrow-1, 0     ] = F(epsilon,FM,FC,BM,BC,Im[nrow-1, 0     ,:], [hhorleft [nrow-1, 0     ], hvertdown[nrow-2, 0     ]])
        llr[nrow-1, ncol-1] = F(epsilon,FM,FC,BM,BC,Im[nrow-1, ncol-1,:], [hhorright[nrow-1, ncol-2], hvertdown[nrow-2, ncol-1]])

        # convert from log-likelihoods to probabilities
        m = (np.exp(llr))/(1+np.exp(llr))
        # plot the original image
        plt.figure()
        plt.imshow(Im)
        # plot the expectation
        plt.figure()
        plt.imshow(m)
        plt.title("iter:" + str(t) + "epsilon: "+str(epsilon))
        if(debug):
            if(len(prevms)>0):
                plt.figure()
                plt.imshow(m-prevms[-1])
                #print(sum(m-prevms[-1]))
                plt.title("diff between iters "+str(t)+' '+str(t-1)+' '+str(epsilon))
            prevms.append(m)

        plt.show()

# compute log likelihood ratio of the marginals
llr = np.zeros((nrow, ncol))

# nodes of degree 4
for i in range(1,nrow-1):
    for j in range(1, ncol-1):
        llr[i, j] = F(epsilon,FM,FC,BM,BC,Im[i,j,:], [hhorright[i, j-1], hvertdown[i-1, j], hvertup[i, j], hhorleft[i, j]])

# nodes of degree 3
for i in range(1, nrow-1):
    llr[i, 0     ] = F(epsilon,FM,FC,BM,BC,Im[i, 0     ,:], [hvertdown[i-1, 0     ], hvertup[i, 0     ], hhorleft [i, 0    ]])
    llr[i, ncol-1] = F(epsilon,FM,FC,BM,BC,Im[i, ncol-1,:], [hvertdown[i-1, ncol-1], hvertup[i, ncol-1], hhorright[i,ncol-2]])

for j in range(1, ncol-1):
    llr[0     ,j] = F(epsilon,FM,FC,BM,BC,Im[0     , j,:], [hhorright[0     , j-1], hhorleft[0     , j], hvertup  [0     , j]])
    llr[nrow-1,j] = F(epsilon,FM,FC,BM,BC,Im[nrow-1, j,:], [hhorright[nrow-1, j-1], hhorleft[nrow-1, j], hvertdown[nrow-2, j]])

# nodes of degree 2
llr[0     , 0     ] = F(epsilon,FM,FC,BM,BC,Im[0     , 0     ,:], [hhorleft [0     , 0     ], hvertup  [0     , 0     ]])
llr[0     , ncol-1] = F(epsilon,FM,FC,BM,BC,Im[0     , ncol-1,:], [hhorright[0     , ncol-2], hvertup  [0     , ncol-2]])
llr[nrow-1, 0     ] = F(epsilon,FM,FC,BM,BC,Im[nrow-1, 0     ,:], [hhorleft [nrow-1, 0     ], hvertdown[nrow-2, 0     ]])
llr[nrow-1, ncol-1] = F(epsilon,FM,FC,BM,BC,Im[nrow-1, ncol-1,:], [hhorright[nrow-1, ncol-2], hvertdown[nrow-2, ncol-1]])

# convert from log-likelihoods to probabilities
m = (np.exp(llr))/(1+np.exp(llr))
# plot the original image
plt.figure()
plt.imshow(Im)
# plot the expectation
plt.figure()
plt.imshow(m)

plt.show()
