import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline

x = np.linspace(-3, 3, 5) # Computes values immediately
print x

"""
Explanation: TensorFlow Basics & Convolution
TensorFlow does not do computation immediately but constructs a graph. We define everything that we want to compute, in a graph and running it requires a session.
Intro
End of explanation
"""


x = tf.linspace(-3.0, 3.0, 100) # Doesn't compute immediately
# Note that tf.linspace(-3, 3, 5) gives an error because datatypes are
# mismatched
print (x)

"""
Explanation: LinSpace:0 means output of LinSpace. TensorFlow doesn't compute the values immediately. It only specifies the nature of the output of a TF operation, also called an Op node.
End of explanation
"""


g = tf.get_default_graph()
print [op.name for op in g.get_operations()] # List of ops

# This next step would not work because the tensor doesn't exist yet, we will compute it later.

### print g.get_tensor_by_name('LinSpace_1:0')

# Note that LinSpace has a :0 at the end of it. Without :0, it refers to the Node itself, with :0 it refers to the 
# tensor.

"""
Explanation: We can get the elements of the graph by doing as follows. We can also get the output of a certain node in the graph
End of explanation
"""


sess = tf.Session()

# We can ask a session to compute the value of a node
computed_x = sess.run(x)
# print (computed_x)

# Or we can ask the node to compute itself using the session
computed_x = x.eval(session = sess)

# print computed_x

# We can close the session by doing this
sess.close()

"""
Explanation: Session
In order to get run a TF program, we need a session. The session computes the graph we construct. Here's an example.
End of explanation
"""


g = tf.get_default_graph() # Fetch the default graph
g2 = tf.Graph()
print g2

sess2 = tf.Session(graph = g2)
print sess2
sess2.close()

"""
Explanation: We can ask TF to create a new graph and have it be connected to another session. We are allowed to have multiple sessions running at the same time.
End of explanation
"""


sess = tf.InteractiveSession()

# print x.eval()

print x.get_shape() # x.shape
print x.get_shape().as_list() # x.shape.tolist()

"""
Explanation: Interactive Session - This is a way to run session in environments like notebooks where you don't want to pass around a session variable. But it's just like a session. Here's how to create one. Also this behaves more like a normal python program. You have to recompute the formula if you want updates. For example, z is defined below in the gaussian curve example. You have to rerun the formula after changing x to get new z. Just running z.eval() won't do it. However, in a normal session, it will.
Without interactive session, whenever you call x.eval(), you have to pass session as x.eval(session = sess)
End of explanation
"""


mean = 0
sigma = 1.0
z = 1.0/(tf.sqrt(2*3.14)*sigma) * (tf.exp(-1*(tf.pow(x-mean, 2)/(2*tf.pow(sigma, 2)))))

res = z.eval() # Note that x is already defined from above
plt.plot(res)
plt.show()

"""
Explanation: Example - Creating a Gaussian Curve
End of explanation
"""


l = z.get_shape().as_list()[0]
res2d = tf.matmul(tf.reshape(z, [l, 1]), tf.reshape(z, [1, l])).eval()
plt.imshow(res2d)
plt.show()

"""
Explanation: Making it into a 2D Gaussian
End of explanation
"""


from skimage import data
img = data.camera().astype(np.float32)

plt.imshow(img, cmap='gray')
plt.show()

"""
Explanation: Convolution
Loading 'camera' images from sklearn
End of explanation
"""


# Image shape is 512x512
img4d = tf.reshape(img, [1, img.shape[0], img.shape[1], 1])
print img4d.get_shape()

"""
Explanation: Convolution operation in TF takes in a 4d tensor for images. The dimensions are (Batch x Height x Width x Channel). Our image is grayscale. So we reshape it using numpy into 4d as shown below.
Tensors must be float16, float32 or float64.
End of explanation
"""


l = res2d.shape[0]
kernel = tf.reshape(res2d, [l, l, 1, 1])
print kernel.get_shape()

# Convolution operation
convolved = tf.nn.conv2d(img4d, kernel, strides = [1, 1, 1, 1],
                        padding = 'SAME')

plt.imshow(convolved.eval()[0, :, :, 0], cmap = 'gray')
plt.show()

"""
Explanation: For the convolution operation we need to provide the specifics of the kernels - Height x Width x Channel x Number of kernels. Let's now convert our gaussian kernel in this format and convolve our image.
End of explanation
"""


ksize = 100
xs = tf.linspace(-3.0, 3.0, ksize)
ys = tf.sin(xs+2)

# The following two statements are equivalent to 
# plt.plot(xs.eval(), ys.eval())
plt.figure()
plt.plot(ys.eval())

plt.show()

"""
Explanation: Gabor Kernel
We can take a sin wave and modulate it with the gaussian kernel to get a gabor kernel.
End of explanation
"""


ys = tf.reshape(ys, [ksize, 1])
ones = tf.ones([1, ksize])

mat = tf.matmul(ys, ones)
plt.imshow(mat.eval(), cmap = 'gray')
plt.show()

# Multiply with the gaussian kernel
# kernel is 4 dimensional, res2d is the 2d version
gabor = tf.matmul(mat, res2d)
plt.imshow(gabor.eval(), cmap = 'gray')
plt.show()

"""
Explanation: We need to convert this sine wave into a matrix and multiply with the gaussian kernel. That will be the gabor filter.
End of explanation
"""


img = tf.placeholder(tf.float32, shape = [None, None], name = 'img')
# Reshaping inbuilt function
img3d = tf.expand_dims(img, 2)
print img3d.get_shape()

img4d = tf.expand_dims(img3d, 0)
print img4d.get_shape()

mean = tf.placeholder(tf.float32, name = 'mean')
sigma = tf.placeholder(tf.float32, name = 'sigma')
ksize = tf.placeholder(tf.int32, name = 'ksize')

# Giving formula for x, gaussian kernel, gabor kernel etc..
x = tf.linspace(-3.0, 3.0, ksize)
z = 1.0/(tf.sqrt(2*3.14)*sigma) * (tf.exp(-1*(tf.pow(x-mean, 2)/(2*tf.pow(sigma, 2)))))
z2d = tf.matmul(tf.reshape(z, [ksize, 1]), tf.reshape(z, [1, ksize]))

xs = tf.linspace(-3.0, 3.0, ksize)
ys = tf.sin(xs)

ys = tf.reshape(ys, [ksize, 1])
ones = tf.ones([1, ksize])

mat = tf.matmul(ys, ones)
gabor = tf.matmul(mat, z2d)

gabor4d = tf.reshape(gabor, [ksize, ksize, 1, 1])

convolved = tf.nn.conv2d(img4d, gabor4d, strides = [1, 1, 1, 1],
                        padding = 'SAME')

# We defined the graph above, now we are going to evaluate it. 
result = convolved.eval(feed_dict = {
        img: data.camera(), 
        mean: 0.0, 
        sigma: 1.0, 
        ksize: 5
    })

plt.imshow(result[0, :, :, 0], cmap = 'gray')
plt.title('Gabor filter output')
plt.show()

"""
Explanation: Convolution using Placeholders
We can specify parameters that we expect to fit in the graph later on, now, by using placeholders. Convolution using placeholders is presented below.
End of explanation
"""


