"""This is an TensorFLow implementation of AlexNet by Alex Krizhevsky at all.

Paper:
(http://papers.nips.cc/paper/4824-imagenet-classification-with-deep-convolutional-neural-networks.pdf)

Explanation can be found in my blog post:
https://kratzert.github.io/2017/02/24/finetuning-alexnet-with-tensorflow.html

This script enables finetuning AlexNet on any given Dataset with any number of
classes. The structure of this script is strongly inspired by the fast.ai
Deep Learning class by Jeremy Howard and Rachel Thomas, especially their vgg16
finetuning script:
Link:
- https://github.com/fastai/courses/blob/master/deeplearning1/nbs/vgg16.py

The pretrained weights can be downloaded here and should be placed in the same
folder as this file:
- http://www.cs.toronto.edu/~guerzhoy/tf_alexnet/

@author: Frederik Kratzert (contact: f.kratzert(at)gmail.com)
"""

import numpy as np
import tensorflow as tf
from abc import abstractmethod

class Model(object):
    @abstractmethod
    def __init__(self):
        pass

    @abstractmethod
    def set_model_vars(self, variable_dict, session):
        pass

    @abstractmethod
    def get_model_vars(self, session, init=False):
        return {}

    @abstractmethod
    def load_model_vars(self, path: str, session):
        pass

    @abstractmethod
    def save_model_vars(self, path: str, session, init=False):
        pass

    @abstractmethod
    def load_model_pretrained(self, session):
        pass

    @abstractmethod
    def _create_loss(self, *args):
        pass

# noinspection PyCompatibility
class AlexNet(Model):
    """Implementation of the AlexNet."""
    TRAIN_LAYERS = ...  # type: set
    y = ...  # type: tf.placeholder

    # ATTENTION: loading pretrained weights is called outside the constructor
    def __init__(self, x, keep_prob, num_classes, train_layers, falpha=2.0,
                 weights_path='/pretrained/bvlc_alexnet.npy'):
        """Create the graph of the AlexNet model.

        Args:
            x: Placeholder for the input tensor.
            keep_prob: Dropout probability.
            num_classes: Number of classes in the dataset.
            train_layers: List of names of the layer, that get trained from
                scratch
            weights_path: Complete path to the pretrained weight file, if it
                isn't in the same folder as this code
        """
        # Parse input arguments into class variables
        super(AlexNet, self).__init__()
        self.X = x
        # self.X = tf.placeholder(tf.float32, [None, 227, 227, 3])
        self.NUM_CLASSES = num_classes
        self.KEEP_PROB = keep_prob
        self.TRAIN_LAYERS = train_layers
        self.WEIGHTS_PATH = weights_path
        self.ALPHA = falpha

        # Call the create function to build the computational graph of AlexNet
        # with tf.variable_scope('') as scope:

        self._create_discriminator()

        # define metrics
        # TODO consider switching the second dimension to self.NUM_CLASSES
        self.y = tf.placeholder(tf.float32, [None, self.NUM_CLASSES], name='y')
        self.correct_pred = tf.equal(tf.argmax(self.fc8, 1), tf.argmax(self.y, 1))
        self.accuracy = tf.reduce_mean(tf.cast(self.correct_pred, tf.float32), name='accuracy')

        self._create_loss()
        self._create_stats(falpha)

    def _create_discriminator(self):
        """Create the network graph. returns tensors of fc7 and fc8"""
        # 1st Layer: Conv (w ReLu) -> Lrn -> Pool
        conv1 = conv(self.X, 11, 11, 96, 4, 4, padding='VALID', name='conv1')
        norm1 = lrn(conv1, 2, 1e-05, 0.75, name='norm1')
        pool1 = max_pool(norm1, 3, 3, 2, 2, padding='VALID', name='pool1')
        self.conv1, self.norm1, self.pool1 = conv1, norm1, pool1

        # 2nd Layer: Conv (w ReLu)  -> Lrn -> Pool with 2 groups
        conv2 = conv(pool1, 5, 5, 256, 1, 1, groups=2, name='conv2')
        norm2 = lrn(conv2, 2, 1e-05, 0.75, name='norm2')
        pool2 = max_pool(norm2, 3, 3, 2, 2, padding='VALID', name='pool2')
        self.conv2, self.norm2, self.pool1 = conv2, norm2, pool2

        # 3rd Layer: Conv (w ReLu)
        conv3 = conv(pool2, 3, 3, 384, 1, 1, name='conv3')
        self.conv3 = conv3

        # 4th Layer: Conv (w ReLu) splitted into two groups
        conv4 = conv(conv3, 3, 3, 384, 1, 1, groups=2, name='conv4')
        self.conv4 = conv4

        # 5th Layer: Conv (w ReLu) -> Pool splitted into two groups
        conv5 = conv(conv4, 3, 3, 256, 1, 1, groups=2, name='conv5')
        pool5 = max_pool(conv5, 3, 3, 2, 2, padding='VALID', name='pool5')
        self.conv5, self.pool5 = conv5, pool5

        # 6th Layer: Flatten -> FC (w ReLu) -> Dropout
        flattened = tf.reshape(pool5, [-1, 6 * 6 * 256])
        fc6 = fc(flattened, 6 * 6 * 256, 4096, name='fc6')
        dropout6 = dropout(fc6, self.KEEP_PROB, name='dropout6')
        self.flattened, self.fc6, self.dropout6 = flattened, fc6, dropout6

        # 7th Layer: FC (w ReLu) -> Dropout
        fc7 = fc(dropout6, 4096, 4096, name='fc7')
        dropout7 = dropout(fc7, self.KEEP_PROB, name='dropout7')
        self.fc7, self.dropout7 = fc7, dropout7

        # 8th Layer: FC and return unscaled activations
        fc8 = fc(dropout7, 4096, self.NUM_CLASSES, relu=False, name='fc8')
        self.fc8 = fc8

    def _create_loss(self):
        with tf.name_scope("cross_ent"):
            self.loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=self.fc8, labels=self.y),
                                       name="loss")

    def load_model_pretrained(self, session):
        """Load weights from file into network.

        As the weights from http://www.cs.toronto.edu/~guerzhoy/tf_alexnet/
        come as a dict of lists (e.g. weights['conv1'] is a list) and not as
        dict of dicts (e.g. weights['conv1'] is a dict with keys 'weights' &
        'biases') we need a special load function
        """
        # Load the weights into memory
        variable_dict = np.load(self.WEIGHTS_PATH, encoding='bytes').item()  # type: dict
        # Loop over all layer names stored in the weights dict
        for op_name in variable_dict:  # type: str
            # Check if layer should be trained from scratch
            if op_name not in self.TRAIN_LAYERS:
                with tf.variable_scope(op_name, reuse=True):
                    # Assign weights/biases to their corresponding tf variable
                    for data in variable_dict[op_name]:
                        var_name = "biases" if len(data.shape) == 1 else "weights"
                        var = tf.get_variable(var_name, trainable=False)
                        try:
                            session.run(var.assign(data))
                        except:
                            print("Failed to assign value to", var.name)

    def _create_stats(self, alpha):
        """only works for binary classification"""
        prediction = tf.argmax(self.fc8, axis=1, name='alexnet-prediction')
        ground_truth = tf.argmax(self.y, axis=1, name='alexnet-ground-truth')
        self.prediction, self.ground_truth = prediction, ground_truth
        self.TP = tf.reduce_sum(prediction * ground_truth)  # True Positive
        self.TN = tf.reduce_sum((1 - prediction) * (1 - ground_truth))  # True Negative
        self.FP = tf.reduce_sum(prediction * (1 - ground_truth))  # False Positive
        self.FN = tf.reduce_sum((1 - prediction) * ground_truth)  # False Negative
        self.precision = self.TP / (self.TP + self.FP)
        self.recall = self.TP / (self.TP + self.FN)
        self.F_alpha = (1 + alpha) / (1 / self.precision + alpha / self.recall)
        if self.NUM_CLASSES != 2:
            print("Warning: precision, recall and F_alpha score does not apply to Multi-Label Classification")

    def get_model_vars(self, session, init=False):
        """returns a dict of variables in the model, with keys being layer names and values being list of np.arrays"""
        if init:
            session.run(tf.global_variables_initializer())
        layers = ['conv1', 'conv2', 'conv3', 'conv4', 'conv5', 'fc6', 'fc7', 'fc8']
        variable_dict = {layer: [] for layer in layers}
        for layer in variable_dict:
            with tf.variable_scope(layer, reuse=True):
                for var_name in ["weights", "biases"]:
                    var = tf.get_variable(var_name)
                    variable_dict[layer].append(session.run(var))
        return variable_dict

    def set_model_vars(self, variable_dict, session):
        """assign model variables with values from a dict passed"""
        for op_name in variable_dict:
            with tf.variable_scope(op_name, reuse=True):
                for data in variable_dict[op_name]:
                    var_name = 'biases' if len(data.shape) == 1 else "weights"
                    # in case set_model_vars() is called before load_model_pretrained(), set trainable
                    var = tf.get_variable(var_name, trainable=op_name in self.TRAIN_LAYERS)
                    session.run(var.assign(data))

    def save_model_vars(self, path: str, session, init=False):
        """save model var-value dict under passed path"""
        np.save(path, self.get_model_vars(session, init=init))

    def load_model_vars(self, path: str, session):
        """load model var-value from passed path"""
        variable_dict = np.load(path, encoding="bytes").item()  # type: dict
        self.set_model_vars(variable_dict, session)

class SiameseAlexNet(Model):
    def __init__(self, x1, x2, keep_prob, num_classes, train_layers, name_scope="Siamese", proj="flattened",
                 falpha=2.0, margin00=3.5, margin01=7.0, margin11=8.0, weights_path='/pretrained/bvlc_alexnet.npy',
                 punish00=1.0, punish11=1.0, punish01=5.0):
        super(SiameseAlexNet, self).__init__()
        self.name_scope = name_scope
        self.margin00 = margin00
        self.margin01 = margin01
        self.margin11 = margin11
        self.punish00 = punish00
        self.punish11 = punish11
        self.punish01 = punish01
        self.proj = proj
        with tf.variable_scope(self.name_scope) as scope:
            self.net1 = AlexNet(x1, keep_prob, num_classes, train_layers, falpha=falpha, weights_path=weights_path)
            scope.reuse_variables()
            self.net2 = AlexNet(x2, keep_prob, num_classes, train_layers, falpha=falpha, weights_path=weights_path)
            # define a loss for Siamese Network
            self._create_loss(proj)

    def _create_loss(self, proj):
        # XXX punishing the Pos-Neg loss harder than Pos-Pos loss and Neg-Neg loss to avoid underfitting
        proj1, proj2 = self._get_projections(proj)
        eucd2 = tf.reduce_mean((proj1 - proj2) ** 2, axis=1, name="euclidean_dist_squared")
        eucd = tf.sqrt(eucd2, name="euclidean_dist")
        print('euclidean distances tensor', eucd)
        # y1, y2 and y_cmp should be a class member
        y1 = tf.cast(tf.argmax(self.net1.y, axis=1), tf.float32, name='siam-y1')
        y2 = tf.cast(tf.argmax(self.net2.y, axis=1), tf.float32, name='siam-y2')
        self.y1_label, self.y2_label = y1, y2
        y_diff = tf.cast(y1 - y2, tf.bool, name="comparison_label_in_tf.bool")
        y_diff = tf.cast(y_diff, tf.float32, name="comparison_label_in_tf.float32")
        self.count01 = tf.reduce_sum(y_diff, name='count01')
        self.count00 = tf.reduce_sum((1 - y1) * (1 - y2), name='count00')
        self.count11 = tf.reduce_sum(y1 * y2, name='count11')

        # if label1 and label2 are the same, y_diff = 0, punish the part where eucd exceeds margin
        loss00 = tf.reduce_mean(((1 - y1) * (1 - y2) * tf.nn.relu(eucd - self.margin00)) ** 2, axis=0, name='loss00')
        loss11 = tf.reduce_mean((y1 * y2 * tf.nn.relu(eucd - self.margin11)) ** 2, axis=0, name='loss11')
        self.mean_dist00 = tf.reduce_sum((1 - y1) * (1 - y2) * eucd) / self.count00
        self.mean_dist11 = tf.reduce_sum(y1 * y2 * eucd) / self.count11

        # if label1 and label2 are different, y_diff = 1, punish the part where eucd falls short of margin
        loss01 = tf.reduce_mean((y_diff * tf.nn.relu(self.margin01 - eucd)) ** 2, axis=0, name='loss01')
        self.mean_dist01 = tf.reduce_sum(y_diff * eucd) / self.count01

        self.loss00 = loss00 * self.punish00
        self.loss01 = loss01 * self.punish01
        self.loss11 = loss11 * self.punish11
        self.loss = tf.add(self.loss00 + self.loss11, self.loss01, name="siamese-loss")
        print(self.loss)

    def _get_projections(self, proj):
        print('projection =', proj, "type=", type(proj))
        projections = (self.net1.dropout6, self.net2.dropout6)
        try:
            if proj == "fc6":
                projections = (self.net1.fc6, self.net2.fc6)
            elif proj == "fc7":
                projections = (self.net1.fc7, self.net2.fc7)
            elif proj == "fc8":
                projections = (self.net1.fc8, self.net2.fc8)
            elif proj == "dropout6":
                projections = (self.net1.dropout6, self.net2.dropout6)
            elif proj == "dropout7":
                projections = (self.net1.dropout7, self.net2.dropout7)
            elif proj == "flattened":
                projections = (self.net1.flattened, self.net2.flattened)
            else:
                raise ValueError("Illegal Projection: " + proj)
        except ValueError as e:
            print("ValueError: encountered in _get_predictions")
            print(e)
        finally:
            print("projections of %s are " % self.name_scope, projections[0].name, projections[1].name)
            print("dimensions of projection is", projections[0].shape, projections[1].shape)
            return projections

    def load_model_pretrained(self, session):
        with tf.variable_scope(self.name_scope, reuse=True):
            self.net1.load_model_pretrained(session)

    def load_model_vars(self, path: str, session):
        """load model var-value from passed path"""
        with tf.variable_scope(self.name_scope, reuse=True):
            self.net1.load_model_vars(path, session)

    def save_model_vars(self, path: str, session, init=False):
        """save model var-value dict under passed path"""
        with tf.variable_scope(self.name_scope):
            self.net1.save_model_vars(path, session, init=init)

    def get_model_vars(self, session, init=False):
        """returns a dict of variables in the model, with keys being layer names and values being list of np.arrays"""
        with tf.variable_scope(self.name_scope):
            return self.net1.get_model_vars(session, init=init)

    def set_model_vars(self, variable_dict, session):
        """assign model variables with values from a dict passed"""
        with tf.variable_scope(self.name_scope):
            self.net1.set_model_vars(variable_dict, session)

    # return a new instance of AlexNet with trainable variables
    def get_net_copy(self, session, x=None, keep_prob=None, num_classes=None, train_layers=None, falpha=None,
                     weights_path=None) -> AlexNet:
        if x is None:
            x = self.net1.X
            print("Warning: x should be specified as a new placeholder")
        if keep_prob is None:
            keep_prob = self.net1.KEEP_PROB
        if num_classes is None:
            num_classes = self.net1.NUM_CLASSES
        if train_layers is None:
            train_layers = self.net1.TRAIN_LAYERS
            print("Warning: train_layers should be specified as a new list of layer names")
        if falpha is None:
            falpha = self.net1.ALPHA
        if weights_path is None:
            weights_path = self.net1.WEIGHTS_PATH
        new_net = AlexNet(x, keep_prob, num_classes, train_layers, falpha=falpha, weights_path=weights_path)
        new_net.set_model_vars(self.get_model_vars(session), session)
        return new_net

def conv(x, filter_height, filter_width, num_filters, stride_y, stride_x, name, padding='SAME', groups=1):
    """Create a convolution layer.

    Adapted from: https://github.com/ethereon/caffe-tensorflow
    """
    # Get number of input channels
    input_channels = int(x.get_shape()[-1])

    # Create lambda function for the convolution
    convolve = lambda i, k: tf.nn.conv2d(i, k,
                                         strides=[1, stride_y, stride_x, 1],
                                         padding=padding)

    with tf.variable_scope(name) as scope:
        # Create tf variables for the weights and biases of the conv layer
        weights = tf.get_variable('weights', shape=[filter_height,
                                                    filter_width,
                                                    input_channels / groups,
                                                    num_filters])
        biases = tf.get_variable('biases', shape=[num_filters])

    if groups == 1:
        conv = convolve(x, weights)

    # In the cases of multiple groups, split inputs & weights and
    else:
        # Split input and weights and convolve them separately
        input_groups = tf.split(axis=3, num_or_size_splits=groups, value=x)
        weight_groups = tf.split(axis=3, num_or_size_splits=groups,
                                 value=weights)
        output_groups = [convolve(i, k) for i, k in zip(input_groups, weight_groups)]

        # Concat the convolved output together again
        conv = tf.concat(axis=3, values=output_groups)

    # Add biases
    bias = tf.reshape(tf.nn.bias_add(conv, biases), tf.shape(conv))

    # Apply relu function
    relu = tf.nn.relu(bias, name=scope.name)

    return relu

def fc(x, num_in, num_out, name, relu=True):
    """Create a fully connected layer."""
    with tf.variable_scope(name) as scope:

        # Create tf variables for the weights and biases
        weights = tf.get_variable('weights', shape=[num_in, num_out],
                                  trainable=True)
        biases = tf.get_variable('biases', [num_out], trainable=True)

        # Matrix multiply weights and inputs and add bias
        act = tf.nn.xw_plus_b(x, weights, biases, name=scope.name)

    if relu:
        # Apply ReLu non linearity
        relu = tf.nn.relu(act)
        return relu
    else:
        return act

def max_pool(x, filter_height, filter_width, stride_y, stride_x, name, padding='SAME'):
    """Create a max pooling layer."""
    return tf.nn.max_pool(x, ksize=[1, filter_height, filter_width, 1],
                          strides=[1, stride_y, stride_x, 1],
                          padding=padding, name=name)

def lrn(x, radius, alpha, beta, name, bias=1.0):
    """Create a local response normalization layer."""
    return tf.nn.local_response_normalization(x, depth_radius=radius,
                                              alpha=alpha, beta=beta,
                                              bias=bias, name=name)

def dropout(x, keep_prob, name='dropout'):
    """Create a dropout layer."""
    return tf.nn.dropout(x, keep_prob, name=name)

def test1():
    keep_prob = tf.placeholder(tf.float32, [], name='keep_prob')
    x = tf.placeholder(tf.float32, [None, 227, 227, 3], name='x')
    x1 = tf.placeholder(tf.float32, [None, 227, 227, 3], name='x1')
    x2 = tf.placeholder(tf.float32, [None, 227, 227, 3], name='x2')
    num_classes = 2
    sTrainLayers = ['fc6']
    aTrainLayers = ['fc7', 'fc8']
    sNet = SiameseAlexNet(x1, x2, keep_prob, num_classes, sTrainLayers)
    sess = tf.InteractiveSession()
    # sNet.load_model_pretrained(session=sess)
    print(sNet.net1.conv1)

    aNet = sNet.get_net_copy()

if __name__ == "__main__":
    # how the two nets in Siamese Net share the keep_prob placeholder?
    # the keep_prob argument passed to constructor is an integer, instead of a placeholder
    keep_prob = tf.placeholder(tf.float32, [], name='keep_prob')
    x = tf.placeholder(tf.float32, [None, 227, 227, 3], name='x')
    x1 = tf.placeholder(tf.float32, [None, 227, 227, 3], name='x1')
    x2 = tf.placeholder(tf.float32, [None, 227, 227, 3], name='x2')
    # image_batch = np.random.rand(5, 227, 227, 3)
    # label_batch = np.random.rand(5, 1000)
    net = AlexNet(x, keep_prob, 2, ['fc6', 'fc7'])
    # net = SiameseAlexNet(x1, x2, 0.5, 3, ['fc6', 'fc7', 'fc8'], name_scope="SiameseA", proj="flattened")
    # netB = SiameseAlexNet(x1, x2, 0.5, 3, ['fc6', 'fc7', 'fc8'], name_scope="SiameseB")
    # check_path = "/Users/liushuheng/Desktop/vars.npy"
    # with tf.Session() as sess:
    #     sess.run(tf.global_variables_initializer())
    #     net.load_model_pretrained(sess)
    # y1 = sess.run(netA.net1.y, feed_dict={netA.net1.X: image_batch, netA.net1.y: label_batch})
    # y2 = sess.run(netB.net1.y, feed_dict={netB.net1.X: image_batch, netB.net1.y: label_batch})
    # netA.save_model_vars(check_path, sess)
    # netB.load_model_vars(check_path, sess)
    # y3 = sess.run(netB.net1.y, feed_dict={netB.net1.X: image_batch, netB.net1.y: label_batch})
    # assert (y1 == y2).all(), "assertion1 failed"
    # print("assertion1 passed")
    # assert (y1 == y3).all(), "assertion2 failed"
    # print("assertion2 passed")
    # d = net.get_model_vars(sess)
    # init_weights = np.load("/pretrained/bvlc_alexnet.npy", encoding="bytes").item()

    # for var in tf.global_variables():
    # # for var in tf.get_default_graph().get_operations():
    #     print(var.name, end=" ")


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

#
#
# # Earth Analytics Education - EA  Python Course Spring 2021

# ## Important  - Assignment Guidelines
#
# 1. Before you submit your assignment to GitHub, make sure to run the entire notebook with a fresh kernel. To do this first, **restart the kernel** (in the menubar, select Kernel$\rightarrow$Restart & Run All)
# 2. Always replace the `raise NotImplementedError()` code with your code that addresses the activity challenge. If you don't replace that code, your notebook will not run.
#
# ```
# # YOUR CODE HERE
# raise NotImplementedError()
# ```
#
# 3. Any open ended questions will have a "YOUR ANSWER HERE" within a markdown cell. Replace that text with your answer also formatted using Markdown.
# 4. **DO NOT RENAME THIS NOTEBOOK File!** If the file name changes, the autograder will not grade your assignment properly.
# 6. When you create a figure, comment out `plt.show()` to ensure the autograder can grade your plots. For figure cells, DO NOT DELETE the code that says `DO NOT REMOVE LINE BELOW`.
#
# ```
# ### DO NOT REMOVE LINE BELOW ###
# student_plot1_ax = nb.convert_axes(plt)
# ```
#
# * Only include the package imports, code, and outputs that are required to run your homework assignment.
# * Be sure that your code can be run on any operating system. This means that:
#    1. the data should be downloaded in the notebook to ensure it's reproducible
#    2. all paths should be created dynamically using the `os.path.join`
#
# ## Follow to PEP 8 Syntax Guidelines & Documentation
#
# * Run the `autopep8` tool on all cells prior to submitting (HINT: hit shift + the tool to run it on all cells at once!
# * Use clear and expressive names for variables.
# * Organize your code to support readability.
# * Check for code line length
# * Use comments and white space sparingly where it is needed
# * Make sure all python imports are at the top of your notebook and follow PEP 8 order conventions
# * Spell check your Notebook before submitting it.
#
# For all of the plots below, be sure to do the following:
#
# * Make sure each plot has a clear TITLE and, where appropriate, label the x and y axes. Be sure to include UNITS in your labels.
#

# ### Add Your Name Below
# **Your Name: Heidi Yoon**

#

# ---

# # Week 04 and 05 Homework - Automate NDVI Workflow
#
# For this assignment, you will write code to generate a plot of the mean normalized difference vegetation index (NDVI) for two different sites in the United States across one year of data:
#
# * San Joaquin Experimental Range (SJER) in Southern California, United States
# * Harvard Forest (HARV) in the Northeastern United States
#
# The data that you will use for this week is available from **earthpy** using the following download:
#
# `et.data.get_data('ndvi-automation')`
#
# ## Assignment Goals
#
# Your goal in this assignment is to create the most efficient and concise workflow that you can that allows for:
#
# 1. The code to scale if you added new sites or more time periods to the analysis.
# 2. Someone else to understand your workflow.
# 3. The LEAST and most efficient (i.e. runs fast, minimize repetition) amount of code that completes the task.
#
# ### HINTS
#
# * Remove values outside of the landsat valid range of values as specified in the metadata, as needed.
# * Keep any output files SEPARATE FROM input files. Outputs should be created in an outputs directory that is created in the code (if needed) and/or tested for.
# * Use the functions that we demonstrated during class to make your workflow more efficient.
# * BONUS - if you  chose - you can export your data as a csv file. You will get bonus points for doing this.
#
#
# ## Assignment Requirements
#
# Your submission to the GitHub repository should include:
# * This Jupyter Notebook file (.ipynb) with:
#     * The code to create a plot of mean NDVI across a year for  2 NEON Field Sites:
#         * NDVI on the x axis and formatted dates on the y for both NEON sites on one figure/axis object
#     * The **data should be cleaned to remove the influence of clouds**. See the [earthdatascience website for an example of what your plot might look like with and without removal of clouds](https://www.earthdatascience.org/courses/earth-analytics-python/create-efficient-data-workflows/).
# * BONUS: Create one output `.csv` file that has 3 columns - NDVI, Date and Site Name - with values for SJER and HARV.
#
# Your notebook should:
# * Have *at least* 2 well documented and well named functions with docstrings.
# * Include a Markdown cell at the top of the notebook that outlines the overall workflow using pseudocode (i.e. plain language, not code)
# * Include additional Markdown cells throughout the notebook to describe:
#     * the data that you used - and where it is from
#     * how data are being processing
#     * how the code is optimized to run fast and be more concise

# # Replace this cell with your pseudocode  for this workflow
#
# If you happen to be a diagram person a diagram is ok too
#
#

# ## Workflow for this notebook
# **Steps to process one scene of Landsat data:**
# 1. Make a list of all the bands for one scene.
# 2. Open and clean data for valid values for that scene.
# 3. Calculate the NDVI, mask for clouds, and calculate the mean NDVI for that scene.
# **Steps to process all scenes for one site of Landsat data:**
# 1. Make a list of all scenes (dates) for one site.
# 2. For each scene, open and clean data for valid values, calculate the NDVI, mask for clouds, and calculate the mean NDVI.
# 3. Store the mean NDVI, date of the scene, and site name for each scene in a pandas dataframe.
# **Steps to process multiple sites of Landsat data:**
# 1. Make a list of all the sites.
# 2. For each site, get the data and clean for valid values for each scene, calculate the NDVI, mask for clouds, and calculate the mean NDVI for each scene.
# 3. Store the mean NDVI, date, and site name for each scene in a pandas dataframe.
# 4. Export the dataframe with mean NDVI to csv.

# In[1]:

# Autograding imports - do not modify this cell
import matplotcheck.autograde as ag
import matplotcheck.notebook as nb
import matplotcheck.timeseries as ts
from datetime import datetime

# In[2]:

# Import needed packages in PEP 8 order
# and no unused imports listed (10 points total)
import os
from glob import glob

import numpy as np
import pandas as pd
import geopandas as gpd
import matplotlib.pyplot as plt
import xarray as xr
import rioxarray as rxr
import earthpy as et
import earthpy.mask as em
import pyproj
from matplotlib.dates import DateFormatter

# Help with date time conversion between pandas and matplotlib
from pandas.plotting import register_matplotlib_converters
register_matplotlib_converters()

# Download data and set working directory
et.data.get_data('ndvi-automation')
os.chdir(os.path.join(et.io.HOME, "earth-analytics", "data"))

# In[3]:

# DO NOT MODIFY THIS CELL
# Tests that the working directory is set to earth-analytics/data

path = os.path.normpath(os.getcwd())
student_wd_parts = path.split(os.sep)

if student_wd_parts[-2:] == ['earth-analytics', 'data']:
    print("\u2705 Great - it looks like your working directory is set correctly to ~/earth-analytics/data")
else:
    print("\u274C Oops, the autograder will not run unless your working directory is set to earth-analytics/data")

# # Data Source and Areas of Interest
# * In this notebook, we analyze Landsat 8 imagery and vector files for two field sites in the National Ecological Observatory Network (NEON). The first field site is the Harvard Forest and Quabbin Watershed (HARV), which is located approximately 65 miles west of Boston, Massachusetts. The second field site is the San Joaquin Experimental Range (SJER) located approximately 25 miles north of Fresno, California.
# * These data are available online as part of the EarthPy Data Subset.

# # Figure 1: Plot 1 - Mean NDVI For Each Site Across the Year (50 points)
#
# Create a plot of the mean normalized difference vegetation index (NDVI) for the two different sites in the United States across the year:
#
# * NDVI on the x axis and formatted dates on the y for both NEON sites on one figure/axis object.
# * Each site should be identified with a different color in the plot and legend.
# * The final plot **data should be cleaned to remove the influence of clouds**.
# * Be sure to include appropriate title and axes labels.
#
# Add additional cells as needed for processing data (e.g. defining functions, etc), but be sure to:
# * follow the instructions in the code cells that have been provided to ensure that you are able to use the sanity check tests that are provided.
# * include only the plot code in the cell identified for the final plot code below

# ## Task 1:
#
# In the cell below, create a single dataframe containing MEAN NDVI, the site name,
# and the date of the data for the HARV site
# scene `HARV/landsat-crop/LC080130302017031701T1-SC20181023151837`.  The column names for the  final
# DataFrame should be`mean_ndvi`, and `site`, and the data should be **indexed on the date**.
#
# Use the functions that we reviewed in class (or create your own versions of them) to implement your code
#
# ### In the Cell below Place  All Functions Needed to Run this Notebook (20 points)

# In[4]:

### DO NOT REMOVE THIS LINE OR EDIT / MOVE THIS CELL ###
start_time = datetime.now()

# In[5]:

# In this cell place all of the functions needed to run your notebook
def open_clean_band(band_path, clip_extent, valid_range=None):
    """A function that opens a Landsat band as an (rio)xarray object

    Parameters
    ----------
    band_path : str
        A list of paths to the tif file

    clip_extent : geopandas geodataframe
        A geodataframe containing the clip extent of interest. NOTE:
        this will fail if the clip extent is in a different CRS than the
        raster data.

    valid_range : tuple (optional)
        The min and max valid range for the data. All pixels with values
        outside of this range will be masked.

    Returns
    -------
    An single xarray object with the Landsat band data.

    """
    # Clip a band of landsat data to the area of interest
    band = rxr.open_rasterio(band_path, masked=True).rio.clip(
        clip_extent.geometry, from_disk=True).squeeze()

    # Mask values outside of a valid range
    if valid_range:
        mask = ((band <= valid_range[0]) | (band > valid_range[1]))
        cleaned_band = band.where(~mask, np.nan)

    return cleaned_band

def masked_ndvi(all_bands, clip_extent, pixel_qa_path, vals):
    """Open and mask a single landsat band using a pixel_qa layer.

    Parameters
    -----------
    all_bands : list
        A list containing two xarray objects for landsat bands 4 and  5
    clip_extent: geopandas GeoDataFrame
        A geodataframe containing the clip extent of interest. NOTE:
        this will fail if the clip extent is in a different CRS than the
        raster data.
    pixel_qa_path: str
        A path to a pixel qa tif file.
    vals: list
        A list of values needed to create the cloud mask

    Returns
    -----------
    ndvi_crop : Xarray Dataset
        a cropped and masked xarray object containing NDVI values
    """
    # Open and clip landsat qa layer
    pixel_qa = rxr.open_rasterio(
        pixel_qa_path[0], masked=True).rio.clip(
        clip_extent.geometry, from_disk=True).squeeze()

    # Calculate NDVI
    ndvi_xr = (all_bands[1]-all_bands[0]) / (all_bands[1]+all_bands[0])

    # Apply cloud mask to NDVI
    ndvi_mask = ndvi_xr.where(~pixel_qa.isin(vals))

    return ndvi_mask

def open_site_vector(site_path):
    """A function that opens a shapefile for a site location.

    Parameters
    ----------
    site_path: str
        A list of paths to the site directory.

    Returns
    -------
    crop_bound: geopandas DataFrame
        A geodataframe of the crop boundary for the site location.
    """
    vector_dir = os.path.join(site_path, "vector")
    site_name = os.path.basename(os.path.normpath(site_path))
    site_boundary_path = os.path.join(
        vector_dir, site_name + "-crop.shp")

    # Open the crop boundary as a geodataframe
    crop_bound = gpd.read_file(site_boundary_path)
    return crop_bound

def mean_ndvi_df(folder_name):
    """Calculate mean NDVI for a landsat data directory.

    Parameters
    ----------
    folder_name: str
        A list of paths to the landsat site folder.

    Returns
    -------
    ndvi_df: pandas DataFrame
        A DataFrame containing the mean NDVI, date of when the data was
        measured, and the site name.
    """
    # Make a list of all the dates in the directory
    all_dates = glob(os.path.join(folder_name, "landsat-crop", "*"))

    # Open the crop boundary for the site location
    crop_bound = open_site_vector(folder_name)

    # Initialize lists for mean NDVI, date, and site name
    all_ndvi = []
    dates = []
    site = []
    column_names = ["mean_ndvi", "site", "date"]

    # For all the dates, list all of the band paths and qa path
    for adate in all_dates:
        band_paths = sorted(
            glob(os.path.join(adate, "*band*[4-5].tif")))
        landsat_qa_path = glob(os.path.join(adate, "*qa*"))
        # Store the date and file name for each date.
        dates.append(adate[50:58])
        site.append(adate[22:26])
        # Initialize list for bands 4 and 5 xarrays
        all_bands = []
        # For all the band paths, open and clean bands 4 and 5
        for aband in band_paths:
            band = open_clean_band(aband, crop_bound, (0, 10000))
            all_bands.append(band)

        # Calculate NDVI and mask for clouds, then calculate mean NDVI
        avg_ndvi = masked_ndvi(
            all_bands, crop_bound, landsat_qa_path, cloud_values).mean()
        all_ndvi.append(avg_ndvi)

    # Create a dataframe to store mean NDVI, date, site name
    df = pd.DataFrame(columns=column_names)
    df["mean_ndvi"] = xr.concat(all_ndvi, dim="array").to_series()
    df["site"] = site
    df["date"] = dates
    df["date"] = pd.to_datetime(df["date"])
    ndvi_df = df.set_index("date")
    return ndvi_df

def ndvi_all_sites(path_name):
    """Calculate mean NDVI for all sites in a Landsat data directory

    Parameters
    ----------
    path_name: str
        A list of paths to the Landsat data directory for all sites.

    Returns
    -------
    ndvi_allsites: pandas dataframe
        A dataframe containing the mean NDVI, site name, and date of
        Landsat measurement.
    """
    # Make a list of all the sites
    all_sites = glob(os.path.join(path_name, "*"))
    # Initialize the list of NDVI dataframes
    ndvi_ls = []
    for asite in all_sites:
        # Calculate the mean NDVI for each site
        ndvi_df = mean_ndvi_df(asite)
        ndvi_ls.append(ndvi_df)
    ndvi_allsites = pd.concat(ndvi_ls, axis=0)
    return ndvi_allsites

# # How we process all of the Landsat scenes for the HARV site
# * We process all of the scenes for the HARV site in the cell below by using the function mean_ndvi_df(). First, we make a list of all of the scenes. Then for each scene, we process the bands using the function open_clean_band(), and we calculate the NDVI and mask for clouds using the function masked_ndvi(). We also use the function open_site_vector() to open the crop boundary for the HARV site. Once we have calculated the NDVI for a scene, we calculate the mean NDVI and store it with the date of the scene and site name in a pandas dataframe. The final dataframe is returned by the mean_ndvi_df() function.
# * In order to make the code run faster, we made some choices in our functions to optimize for speed. In the functions, open_clean_band() and masked_ndvi, we pipe the rioxarray commands and read from_disk. In the function masked_ndvi(), we also chose to apply the cloud mask at the end of the NDVI calculation.
# * We made the code more concise by using functions and using loops to open and clean bands 4 and 5 for all of the scenes and to mask and calculate NDVI for all of the scenes.

# In[6]:

# Create dataframe of mean NDVI in this cell using the functions created above
# Important: to use the ungraded tests below as a sanity check,
# name your columns: mean_ndvi and site
# Call the dataframe at the end of the cell so the tests run on it!
# Be sure that the date column is an index of type date
# HINT: the time series lessons may help you remember how to do this!

# Define cloud mask values
high_cloud_confidence = (
    em.pixel_flags['pixel_qa']['L8']['High Cloud Confidence'])
cloud = em.pixel_flags['pixel_qa']['L8']['Cloud']
cloud_shadow = em.pixel_flags['pixel_qa']['L8']['Cloud Shadow']

cloud_values = high_cloud_confidence + cloud + cloud_shadow

# Create dataframe of mean NDVI for the HARV site
ndvi_harv = mean_ndvi_df("ndvi-automation/sites/HARV")

# Remove NaN values
ndvi_harv_clean = ndvi_harv.dropna(how='any')
ndvi_harv_clean

# In[7]:

# This cell  is testing your data output above

student_ndvi_ts_single_site = _

single_scene_points = 0

# Ensure the data is stored in a dataframe.
if isinstance(student_ndvi_ts_single_site, pd.DataFrame):
    print('\u2705 Your data is stored in a DataFrame!')
    single_scene_points += 1
else:
    print('\u274C It appears your data is not stored in a DataFrame. ',
          'To see what type of object your data is stored in, check its type with type(object)')

# Ensure that the date column is the index
if isinstance(student_ndvi_ts_single_site.index, pd.core.indexes.datetimes.DatetimeIndex):
    print('\u2705 You have the index set to the date column!')
    single_scene_points += 2
else:
    print('\u274C You do not have the index set to the date column.')

# Ensure that the date column is datetime
if isinstance(student_ndvi_ts_single_site.index[0], pd._libs.tslibs.timestamps.Timestamp):
    print('\u2705 The data in your date column is datetime!')
    single_scene_points += 2
else:
    print('\u274C The data in your date column is not datetime.')

# Ensure the site name is correct
if student_ndvi_ts_single_site.site.values[0] == 'HARV':
    print('\u2705 You have the correct site name!')
    single_scene_points += 5
else:
    print('\u274C You do not have the correct site name.')

if np.allclose(0.281131628228094, student_ndvi_ts_single_site.mean_ndvi.values[0]):
    print('\u2705 You have the correct mean NDVI value!')
    single_scene_points += 5
else:
    print('\u274C You do not have the correct mean ndvi value.')

print("\n \u27A1 You received {} out of 15 points for creating a dataframe.".format(
    single_scene_points))
single_scene_points

# ## Task 2:
#
# In the cell below, process all of the landsat scenes. Create a DataFrame that contains the following
# information for each scene
#
#
# |   | index  | site  | mean_ndvi  |
# |---|---|---|---|
# | Date  |   |   |   |
# |  2017-01-07  | 0  | SJER  | .4  |
#
# Be sure to call your dataframe at the end of the cell to ensure autograding works.
# HINT: FOR THIS STEP, leave any rows containing missing values (`NAN`).

# # How we process the Landsat scenes for all sites
# * We process all of the scenes for all sites in the cell below by using the function ndvi_all_sites(). First, we make a list of all the sites. Then for each site, we calculate the mean NDVI using the function mean_ndvi_df. We concatenate the list of mean NDVI dataframes for all the sites and return the final dataframe which stores the mean NDVI, date of each scene, and site name.
# * In order to make the code run faster, we use the global pyproj context.
# * We made the code more concise by using functions and using loops to calculate NDVI for all of the scenes in each site. We also used the function open_site_vector() to open the corresponding vector shapefile for each site.

# In[8]:

# Create dataframe of NDVI including the cleaning data to deal with clouds

# Important: to use the ungraded tests below as a sanity check,
# name your columns: mean_ndvi and site
# Don't forget to set date as the index and make the values of type datetime

# Set pyproj settings
pyproj.set_use_global_context(True)

# Calculate the mean NDVI for all sites
ndvi_harv_sjer = ndvi_all_sites("ndvi-automation/sites")

# Export mean NDVI dataframe to csv
ndvi_harv_sjer.to_csv("ndvi-automation/outputs/ndvi_harv_sjer.csv")

ndvi_harv_sjer

# In[9]:

# Last sanity check before creating your plot (10 points)

# Ensure that you call your dataframe at the bottom of the cell above
# and that it has columns called: mean_ndvi and site

# Ensure the data is stored in a dataframe.
student_ndvi_df = _

df_points = 0

if isinstance(student_ndvi_df, pd.DataFrame):
    print('\u2705 Your data is stored in a DataFrame!')
    df_points +=2
else:
    print('\u274C It appears your data is not stored in a DataFrame. ',
          'To see what type of object your data is stored in, check its type with type(object)')

# Check that dataframe contains the appropriate number of NAN values
if student_ndvi_df.mean_ndvi.isna().sum() == 15:
    print('\u2705 Correct number of masked data values!')
    df_points +=2
else:
    print('\u274C The amount of null data in your dataframe is incorrect.')

# Ensure that the date column is the index
if isinstance(student_ndvi_df.index, pd.core.indexes.datetimes.DatetimeIndex):
    print('\u2705 You have the index set to the date column!')
    df_points +=3
else:
    print('\u274C You do not have the index set to the date column.')

# Ensure that the date column is datetime
if isinstance(student_ndvi_df.index[0], pd._libs.tslibs.timestamps.Timestamp):
    print('\u2705 The data in your date column is datetime!')
    df_points +=3
else:
    print('\u274C The data in your date column is not datetime.')

# Output for timer, # DO NOT MODIFY
end_time = datetime.now()
total_time = end_time - start_time
print(
    "Your total run time for processing the data was {0}.".format(total_time))

print("\n \u27A1 You received {} out of 10 points for creating a dataframe.".format(
    df_points))

df_points

# # Figure: Mean NDVI for two NEON sites in 2017

# In[10]:

# Add only the plot code to this cell

# This is the final figure of mean NDVI
# for both sites across the year
# with data cleaned to deal with clouds

# Remove NaN values
ndvi_clean = ndvi_harv_sjer.dropna(how='any')

# Define plot space
fig, ax = plt.subplots(figsize=(10, 10))

# Set plot variables
for site, site_df in ndvi_clean.groupby(["site"]):

    if site == 'HARV':
        color = "purple"

    else:
        color = "blue"

    ax.plot(site_df.index,
            site_df.mean_ndvi,
            marker="o",
            color=color,
            label=site)

# Set titles and axes labels
ax.xaxis.set_major_formatter(DateFormatter("%b"))
ax.set(
    title="Mean Normalized Difference Vegetation Index from 2017 (Landsat 8)",
    xlabel="Month",
    ylabel="Mean NDVI")

# Set legend
ax.legend(loc="upper left")

### DO NOT REMOVE LINES BELOW ###
final_masked_solution = nb.convert_axes(plt, which_axes="current")

# In[11]:

# Ignore this cell for the autograding tests

# In[12]:

# Ignore this cell for the autograding tests

# # Question 1 (10 points)
#
# Imagine that you are planning NEON’s upcoming flight season to capture remote sensing data in these locations and want to ensure that you fly the area when the vegetation is the most green.
#
# When would you recommend the flights take place for each site?
#
# Answer the question in 2-3 sentences in the Markdown cell below.

# * For the HARV site, I would recommend that the flights take place during May to November to measure a mean NDVI of at least 0.6. For peak greenness at the HARV site, flights could take place from June to October with mean NDVI values of at least 0.8.
# * For the SJER site, I would recommend that the flights take place during March to May to measure a mean NDVI of at least 0.6.

# # Question 2 (10 points)
#
# How could you modify your workflow to look at vegetation changes over time in each site?
#
# Answer the question in 2-3 sentences in the Markdown cell below.

# * For more long-term studies, since Landsat scenes are collected bi-monthly, we could continue the analysis for several years to look for stability or changes from year-to-year.
# * For short-term intensive studies, we could pick time periods of interest and analyze MODIS data, which is collected daily, to get more resolution in time for NDVI fluctuations.

# # Do not edit this cell! (10 points)
#
# The notebook includes:
# * additional Markdown cells throughout the notebook to describe:
#     * the data that you used - and where it is from
#     * how data are being processing
#     * how the code is optimized to run fast and be more concise

# # Do not edit this cell! (20 points)
#
# The notebook will also be checked for overall clean code requirements as specified at the **top** of this notebook. Some of these requirements include (review the top cells for more specifics):
#
# * Notebook begins at cell [1] and runs on any machine in its entirety.
# * PEP 8 format is applied throughout (including lengths of comment and code lines).
# * No additional code or imports in the notebook that is not needed for the workflow.
# * Notebook is fully reproducible. This means:
#    * reproducible paths using the os module.
#    * data downloaded using code in the notebook.
#    * all imports at top of notebook.

# ## BONUS - Export a  .CSV File to Share (10 points possible)
#
# This is optional - if you export a **.csv** file with the columns specified above: Site, Date and NDVI Value you can get an additional 10 points.
#
# * FULL CREDIT: File exists in csv format and contains the columns specified.
# We will check your github repo for this file!
#


#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import copy
import uuid
import warnings
import functools
import numpy as np
import pycuda.driver as cuda
from pycuda.compiler import SourceModule
import graphdot.cuda
from graphdot import cpp
from graphdot.codegen import Template
from graphdot.codegen.cpptool import decltype
from graphdot.cuda.array import umempty, umzeros, umarray
from graphdot.microkernel import TensorProduct, Product
from graphdot.util.iterable import flatten, fold_like
from ._backend import Backend
from ._scratch import PCGScratch
from ._octilegraph import OctileGraph

class CUDABackend(Backend):
    """

    Parameters
    ----------
    context: :py:class:`pycuda.driver.Context` instance
        The CUDA context for launching kernels, Will use a default one if
        none is given.
    block_per_sm: int
        Tunes the GPU kernel.
    block_size: int
        Tunes the GPU kernel.
    """

    @staticmethod
    def array(ndarray):
        return umarray(ndarray)

    @staticmethod
    def zeros(size, dtype=np.float32):
        return umzeros(size, dtype)

    @staticmethod
    def empty(size, dtype=np.float32):
        return umempty(size, dtype)

    def __init__(self, **kwargs):
        self.uuid = uuid.uuid4()
        self.ctx = kwargs.pop('cuda_context', graphdot.cuda.defctx)
        self.device = self.ctx.get_device()
        self.scratch_pcg = None
        self.scratch_pcg_d = None

        self.block_per_sm = kwargs.pop('block_per_sm', 8)
        self.block_size = kwargs.pop('block_size', 128)

        self.nvcc_extra = kwargs.pop('nvcc_extra', [])
        self._source = ''
        self._module = None

    def __deepcopy__(self, memo):
        return copy.copy(self)

    def _assert_homogeneous(self, x, y):
        try:
            assert(x.weighted == y.weighted)
            assert(x.node_t == y.node_t)
            assert(x.edge_t == y.edge_t)
        except AssertionError as e:
            raise TypeError(
                f'All nodes/edges must be of the same type: {str(e)}'
                'If the graph attributes match in name but differ in type, '
                'try to normalize automatically with `Graph.normalize_types`.'
            )

    def _allocate_scratch(self, scratch, scratch_d, number, length,
                          n_temporaries):
        if (scratch is None or len(scratch) < number or
                scratch[0].nmax < length or
                scratch[0].ndim < n_temporaries):
            self.ctx.synchronize()
            scratch = [
                PCGScratch(length, n_temporaries) for _ in range(number)
            ]
            scratch_d = umarray(
                np.array([s.state for s in scratch], PCGScratch.dtype)
            )
            self.ctx.synchronize()
        return scratch, scratch_d

    def allocate_pcg_scratch(self, number, max_graph_size, traits):
        if traits.eval_gradient is True:
            if traits.nodal in [True, 'block']:
                length = max_graph_size**2
                n_temporaries = 7
            else:
                length = max_graph_size**2 * 2
                n_temporaries = 5
        else:
            length = max_graph_size**2
            n_temporaries = 5

        self.scratch_pcg, self.scratch_pcg_d = self._allocate_scratch(
            self.scratch_pcg, self.scratch_pcg_d, number, length,
            n_temporaries
        )
        return self.scratch_pcg_d

    def _register_graph(self, graph):
        if self.uuid not in graph.cookie:
            # convert to GPU format
            og = OctileGraph(graph)
            graph.cookie[self.uuid] = (og, og.state)
        return graph.cookie[self.uuid]

    def _compile(self, src):
        with warnings.catch_warnings(record=True) as w:
            module = SourceModule(
                src,
                options=['-std=c++14',
                         '-O4',
                         '--use_fast_math',
                         '--expt-relaxed-constexpr',
                         '--maxrregcount=64',
                         '-Xptxas', '-v',
                         '-lineinfo',
                         ] + self.nvcc_extra,
                no_extern_c=True,
                include_dirs=cpp.__path__,
                # keep=True
            )
        return module, [str(rec.message) for rec in w]

    @property
    @functools.lru_cache(maxsize=1)
    def template(self):
        return Template(os.path.join(os.path.dirname(__file__), 'template.cu'))

    @property
    def source(self):
        return self._source

    @source.setter
    def source(self, source):
        if self.source != source:
            self._source = source
            self._module = None

    @property
    def module(self):
        if not self._module:
            self._module, self._compiler_message = self._compile(self.source)
        return self._module

    @staticmethod
    def gencode_kernel(kernel, name):
        fun, jac = kernel.gen_expr('x1', 'x2')

        return Template(r'''
        using ${name}_theta_t = ${theta_t};

        struct ${name}_t : ${name}_theta_t {

            constexpr static int jac_dims = ${jac_dims};

            template<class X>
            __device__ __inline__
            auto operator() (X const &x1, X const &x2) const {
                return ${expr};
            }

            template<class X>
            __device__ __inline__
            auto _j_a_c_o_b_i_a_n_(X const &x1, X const &x2) const {
                graphdot::array<float, jac_dims> j;
                ${jac;\n};
                return j;
            }
        };

        __constant__ ${name}_t ${name};
        __constant__ ${name}_t ${name}_diff_grid[2 * ${n_theta}];
        __constant__ float32   ${name}_flat_theta[${n_theta}];
        ''').render(
            name=name,
            jac_dims=len(jac),
            theta_t=decltype(kernel),
            expr=fun,
            jac=[f'j[{i}] = {expr}' for i, expr in enumerate(jac)],
            n_theta=len(list(flatten(kernel.theta)))
        )

    @staticmethod
    def gencode_probability(pfunc, name):
        fun, jac = pfunc.gen_expr()

        return Template(r'''
        using ${name}_theta_t = ${theta_t};

        struct ${name}_t : ${name}_theta_t {

            constexpr static int jac_dims = ${jac_dims};

            template<class N>
            __device__ __inline__
            auto operator() (N const &n) const {
                return ${expr};
            }

            template<class N>
            __device__ __inline__
            auto _j_a_c_o_b_i_a_n_(N const &n) const {
                graphdot::array<float, jac_dims> j;
                ${jac;\n};
                return j;
            }
        };

        __constant__ ${name}_t ${name};
        ''').render(
            name=name,
            jac_dims=len(jac),
            theta_t=decltype(pfunc),
            expr=fun,
            jac=[f'j[{i}] = {expr}' for i, expr in enumerate(jac)]
        )

    @staticmethod
    def pack_state(object, diff_grid=False, diff_eps=1e-2):
        def _nudge_theta(object, i, delta):
            o = copy.deepcopy(object)
            t = logtheta.copy()
            t[i] += delta
            o.theta = fold_like(np.exp(t), o.theta)
            return o.state

        pack = [object.state]
        if diff_grid is True:
            logtheta = np.log(list(flatten(object.theta)))
            for i, _ in enumerate(logtheta):
                pack.append(_nudge_theta(object, i, diff_eps))
                pack.append(_nudge_theta(object, i, -diff_eps))
        return pack

    def __call__(self, graphs, node_kernel, edge_kernel, p, q, eps, ftol, gtol,
                 jobs, starts, gramian, gradient, nX, nY, nJ, traits, timer):
        ''' transfer graphs and starting probabilities to GPU '''
        timer.tic('transferring graphs to GPU')

        og_last = None
        graphs_d = umempty(len(graphs), dtype=OctileGraph.dtype)
        for i, g in enumerate(graphs):
            og, ogstate = self._register_graph(g)
            if i > 0:
                self._assert_homogeneous(og_last, og)
            og_last = og
            graphs_d[i] = ogstate

        weighted = og_last.weighted
        node_t = og_last.node_t
        edge_t = og_last.edge_t

        timer.toc('transferring graphs to GPU')

        ''' allocate global job counter '''
        timer.tic('allocate global job counter')
        i_job_global = umzeros(1, np.uint32)
        timer.toc('allocate global job counter')

        ''' code generation '''
        timer.tic('code generation')
        if weighted:
            edge_kernel = TensorProduct(weight=Product(),
                                        label=edge_kernel)

        use_theta_grid = all([
            traits.eval_gradient is True,
            traits.nodal in [True, 'block']
        ])
        node_kernel_src = self.gencode_kernel(node_kernel, 'node_kernel')
        edge_kernel_src = self.gencode_kernel(edge_kernel, 'edge_kernel')
        p_start_src = self.gencode_probability(p, 'p_start')

        with self.template.context(traits=traits) as template:
            self.source = template.render(
                node_kernel=node_kernel_src,
                edge_kernel=edge_kernel_src,
                p_start=p_start_src,
                node_t=decltype(node_t),
                edge_t=decltype(edge_t)
            )
        timer.toc('code generation')

        ''' JIT '''
        timer.tic('JIT')
        kernel = self.module.get_function('graph_kernel_solver')
        timer.toc('JIT')

        ''' calculate launch configuration '''
        timer.tic('calculating launch configuration')
        launch_block_count = (self.device.MULTIPROCESSOR_COUNT
                              * self.block_per_sm)
        shmem_bytes_per_warp = self.module.get_global(
            'shmem_bytes_per_warp'
        )[1]
        shmem_bytes_per_block = (shmem_bytes_per_warp * self.block_size
                                 // self.device.WARP_SIZE)

        ''' allocate scratch buffers '''
        max_graph_size = np.max([len(g.nodes) for g in graphs])
        scratch_pcg = self.allocate_pcg_scratch(
            launch_block_count, max_graph_size, traits
        )

        ''' copy micro kernel parameters to GPU '''
        for name, uker in [('node_kernel', node_kernel),
                           ('edge_kernel', edge_kernel)]:
            states = np.array(
                self.pack_state(uker, diff_grid=use_theta_grid, diff_eps=eps),
                dtype=uker.dtype
            )

            p_uker, _ = self.module.get_global(name)
            cuda.memcpy_htod(p_uker, states[:1])

            if use_theta_grid:
                p_diff_grid, _ = self.module.get_global(f'{name}_diff_grid')
                p_flat_theta, _ = self.module.get_global(f'{name}_flat_theta')
                cuda.memcpy_htod(p_diff_grid, states[1:])
                cuda.memcpy_htod(
                    p_flat_theta,
                    np.fromiter(flatten(uker.theta), dtype=np.float32)
                )

        p_p_start, _ = self.module.get_global('p_start')
        cuda.memcpy_htod(
            p_p_start, np.array([p.state], dtype=p.dtype)
        )

        timer.toc('calculating launch configuration')

        ''' GPU kernel execution '''
        timer.tic('GPU kernel execution')
        kernel(
            graphs_d,
            scratch_pcg,
            jobs,
            starts,
            gramian,
            gradient if gradient is not None else np.uintp(0),
            i_job_global,
            np.uint32(len(jobs)),
            np.uint32(nX),
            np.uint32(nY),
            np.uint32(nJ),
            np.float32(q),
            np.float32(q),  # placeholder for q0
            np.float32(eps),
            np.float32(ftol),
            np.float32(gtol),
            grid=(launch_block_count, 1, 1),
            block=(self.block_size, 1, 1),
            shared=shmem_bytes_per_block,
        )
        self.ctx.synchronize()
        timer.toc('GPU kernel execution')


import dlib
import face_recognition
import cv2
import pickle
from firebase import firebase
import time
import copy
import datetime
from datetime import date
import numpy as np
from imutils.video import FPS
import sys

room = sys.argv[1]
# initialize firebase url
firebase = firebase.FirebaseApplication(
	'https://capstone-prototype-7b1f9.firebaseio.com/', None)
# serialized facial encodings
data = pickle.loads(open("encodings.pickle", "rb").read())
detector = dlib.get_frontal_face_detector()  # face detector
sdThresh = 8                                # thresh for standard deviation
font = cv2.FONT_HERSHEY_SIMPLEX             # cv2 font general

# -----------------------------------------------------------------------------------------------
# Object detection parameters
# -----------------------------------------------------------------------------------------------
confidence_score = 0.5
threshold = 0.3
labelsPath = "yolo/obj.names"
LABELS = open(labelsPath).read().strip().split("\n")
weightsPath = "yolo/obj.weights"
configPath = "yolo/obj.cfg"
# Load YOLO and get output layer names
net = cv2.dnn.readNetFromDarknet(configPath, weightsPath)
ln = net.getLayerNames()
ln = [ln[i[0] - 1] for i in net.getUnconnectedOutLayers()]
(W, H) = (None, None)

# -----------------------------------------------------------------------------------------------
# DistMap function -> return pythogorean distance between two frames
# -----------------------------------------------------------------------------------------------

def distMap(frame1, frame2):
	diff32 = np.float32(frame1) - np.float32(frame2)
	return np.uint8((np.sqrt(diff32[:, :, 0]**2 + diff32[:, :, 1]**2 + diff32[:, :, 2]**2)/441.6729559300637)*255)

# general videocapture from default camera
cap = cv2.VideoCapture(0)
grabbed, frame1 = cap.read()                # capturing first frame
# exit if unable to grab frames
if not grabbed: sys.exit("unable to grab frames, error in camera")
#  grab frame dimensions if they are empty
if W is None or H is None: (H, W) = frame1.shape[:2]
_, frame2 = cap.read()                      # capturing second frame

time1 = time.time()
activity_count = 0
# -----------------------------------------------------------------------------------------------

# TODO: Main Loop
fps = FPS().start()
try:
	while(True):
	# -----------------------------------------------------------------------------------------------
	# TODO: Activity Monitoring
	# -----------------------------------------------------------------------------------------------
		_, frame = cap.read()                           # capture image
		# get length & width of image
		rows, cols, _ = np.shape(frame)
		# compute pythogorean distance
		dist = distMap(frame1, frame)
		frame1 = frame2                                 # reassign x[-2] frame
		frame2 = frame                                  # reassign x[-1] frame
		mod = cv2.GaussianBlur(dist, (9, 9), 0)          # Apply gaussian smoothing
		_, thresh = cv2.threshold(mod, 100, 255, 0)     # Thresholding
		# calculate std deviation test
		_, stDev = cv2.meanStdDev(mod)

		if stDev > sdThresh: activity_count += 1          # computing activity intensity
		# push to motion detection data to cloud
		if(time.time()-time1 >= 5):
				time1 = time.time()
				nowtime = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
				firebase.patch('/Motion Detection/', {nowtime: activity_count})
				print(f"activity Intensity - {activity_count}")
				activity_count = 0

	# -----------------------------------------------------------------------------------------------
	# TODO: Facial Recognition
	# -----------------------------------------------------------------------------------------------
		gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)  # grayscale image
		rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)    # rgb image

		dets = detector(rgb, 1)
		boxes = [(d.left(), d.top(), d.right(), d.bottom())\
					for i, d in enumerate(dets)]        # get tuple of box coordinates
		encodings = face_recognition.face_encodings(rgb, boxes) # encode those faces from rgb

		names = []
		text = "Unoccupied"
		# Loop over facial embeddings and check if faces match
		for encoding in encodings:
			matches = face_recognition.compare_faces(data["encodings"], encoding)
			name = "Unknown"
			if True in matches:
				matchedIdxs = [i for (i, b) in enumerate(matches) if b]
				counts = {}

				for i in matchedIdxs:
					name = data["names"][i]
					counts[name] = counts.get(name, 0) + 1
				name = max(counts, key=counts.get)
				text = 'Occupied'

			# patching data to firebase console
			x = datetime.datetime.now().strftime("%H:%M:%S")
			y = datetime.datetime.now().strftime("%Y-%m-%d")
			for i in names:
				firebase.patch('/Monitoring/'+i+'/'+y+'/', {x: "At camera "+room})
			names.append(name)
		print(*names, 'are found in', room)

	# -----------------------------------------------------------------------------------------------
	# TODO: object detection
	# -----------------------------------------------------------------------------------------------
		blob = cv2.dnn.blobFromImage(
			rgb, 1 / 255.0, (416, 416), swapRB=True, crop=False)
		net.setInput(blob)
		layerOutputs = net.forward(ln)

		# initialize lists of detected bounding boxes, confidences, and class IDs, respectively
		boxes = []
		confidences = []
		classIDs = []

		# loop over each of the layer outputs
		for output in layerOutputs:
			# loop over each of the detections
			for detection in output:
				# extract class ID and confidence by using score for each class
				scores = detection[5:]
				classID = np.argmax(scores)
				confidence = scores[classID]

				# filter out weak predictions by ensuring the detected
				# probability is greater than the minimum probability
				if confidence > confidence_score:
					# YOLO returns center coords, width and height
					box = detection[0:4] * np.array([W, H, W, H])
					(centerX, centerY, width, height) = box.astype("int")
					# derive top left corner for NMS
					x = int(centerX - (width / 2))
					y = int(centerY - (height / 2))
					# Update all
					boxes.append([x, y, int(width), int(height)])
					confidences.append(float(confidence))
					classIDs.append(classID)

		# apply NMS
		idxs = cv2.dnn.NMSBoxes(boxes, confidences, confidence_score, threshold)
		if len(idxs) > 0:  # if detected
			# loop over detected objects and push to cloud
			for i in idxs.flatten():
				detected_object = LABELS[classIDs[i]]
				nowtime = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
				firebase.patch("/Object Detection/",{nowtime:f"{detected_object} detected at camera {room}"})
				print(f"{detected_object} found in room {room}")

	# -----------------------------------------------------------------------------------------------
	# TODO: room occupacy status
	# -----------------------------------------------------------------------------------------------
		today=date.today().strftime("%m:%d:%Y")
		if text == 'Occupied':
			t = time.localtime()
			current_time = time.strftime("%H:%M:%S", t)
			firebase.patch(f'/Room Occupied/{room}/'+today,{current_time:text})
			result=firebase.get(f'/Room Occupied/{room}/'+today,'Occupied')
			if(result==None):
				firebase.patch(f'/Room Occupied/{room}/'+today,{'Occupied':1})
			else:
				result=firebase.get(f'/Room Occupied/{room}/'+today,'Occupied')
				result+=1
				firebase.patch(f'/Room Occupied/{room}/'+today,{'Occupied':result})
		else:
			t = time.localtime()
			current_time = time.strftime("%H:%M:%S", t)
			firebase.patch(f'/Room Occupied/{room}/'+today,{current_time:text})
			result=firebase.get(f'/Room Occupied/{room}/'+today,'Unoccupied')
			if(result==None):
				firebase.patch(f'/Room Occupied/{room}/'+today,{'Unoccupied':1})
			else:
				result=firebase.get(f'/Room Occupied/{room}/'+today,'Unoccupied')
				result+=1
				firebase.patch(f'/Room Occupied/{room}/'+today,{'Unoccupied':result})

		if cv2.waitKey(1) & 0xFF == 27: break       # break if esc is pressed
		fps.update()
	# -----------------------------------------------------------------------------------------------
	# TODO: End loop
	# -----------------------------------------------------------------------------------------------
except KeyboardInterrupt: pass
# stop the timer and display FPS information
fps.stop()
print("[INFO] elasped time: {:.2f}".format(fps.elapsed()))
print("[INFO] approx. FPS: {:.2f}".format(fps.fps()))

cap.release()
cv2.destroyAllWindows()


""" Exploratory Data Analysis  """

from typing import Counter
import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
from subprocess import check_output
import plotly.offline as py
import plotly.graph_objs as go
import plotly.tools as tls
import os, gc, re, distance
from nltk.corpus import stopwords
from nltk.stem import PorterStemmer
from bs4 import BeautifulSoup
pd.set_option('display.max_columns', None)

""" 3.1 Reading data and basic stats """

df = pd.read_csv("dataset/train.csv")

print("Number of data points:",df.shape[0])
# Number of data points: 404290

df.head()
"""
   id  qid1  qid2                                          question1  \
0   0     1     2  What is the step by step guide to invest in sh...
1   1     3     4  What is the story of Kohinoor (Koh-i-Noor) Dia...
2   2     5     6  How can I increase the speed of my internet co...
3   3     7     8  Why am I mentally very lonely? How can I solve...
4   4     9    10  Which one dissolve in water quikly sugar, salt...

                                           question2  is_duplicate
0  What is the step by step guide to invest in sh...             0
1  What would happen if the Indian government sto...             0
2  How can Internet speed be increased by hacking...             0
3  Find the remainder when [math]23^{24}[/math] i...             0
4            Which fish would survive in salt water?             0
"""

df.info()
"""
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 404290 entries, 0 to 404289
Data columns (total 6 columns):
 #   Column        Non-Null Count   Dtype
---  ------        --------------   -----
 0   id            404290 non-null  int64
 1   qid1          404290 non-null  int64
 2   qid2          404290 non-null  int64
 3   question1     404289 non-null  object
 4   question2     404288 non-null  object
 5   is_duplicate  404290 non-null  int64
dtypes: int64(4), object(2)
memory usage: 18.5+ MB

OBSERVATION:
We are given a minimal number of data fields here, consisting of:

id: Looks like a simple rowID
qid{1, 2}: The unique ID of each question in the pair
question{1, 2}: The actual textual contents of the questions.
is_duplicate: The label that we are trying to predict - whether the two questions are duplicates of each other."""

""" 3.2.1 Distribution of data points among output classes
- Number of duplicate(smilar) and non-duplicate(non similar) questions """

df.groupby("is_duplicate")['id'].count().plot.bar()
plt.savefig('0.4_barPlot_isDuplicateFeature.png')
plt.show()

print('Total number of question pairs for training: {}'.format(len(df)))
# Total number of question pairs for training:404290

print('No of question pairs with is_duplicate=1: {}%'.format(round(100 - df['is_duplicate'].mean()*100,2)))
print('No of question pairs with is_duplicate=0: {}%'.format(round(df['is_duplicate'].mean()*100,2)))
""" No of question pairs with is_duplicate=1: 63.08%
No of question pairs with is_duplicate=0: 36.92% """

""" 3.2.2 Number of unique questions """
qids = pd.Series(df['qid1'].to_list() + df['qid2'].to_list())
unique_qs = len(np.unique(qids))
qs_morethan_onetime = np.sum(qids.value_counts() > 1)
print('Total no of unique questions: ',len(set(df['qid1'].to_list() + df['qid2'].to_list())))
print ('Number of unique questions that appear more than one time: {} ({}%)\n'.format(qs_morethan_onetime,qs_morethan_onetime/unique_qs*100))
print('Max no of times a single question is repeated: ',max(qids.value_counts()))
"""
Total no of unique questions:  537933
Number of unique questions that appear more than one time: 111780 (20.77953945937505%)
Max no of times a single question is repeated:  157
 """

x = ["unique_questions" , "Repeated Questions"]
y =  [unique_qs , qs_morethan_onetime]
plt.figure(figsize=(10, 6))
plt.title ("Plot representing unique and repeated questions  ")
sns.barplot(x,y)
plt.savefig('0.4_barPlot_uniqueAndRepeatedQuestions.png')
plt.show()

""" 3.2.3 Checking for Duplicates """
#checking whether there are any repeated pair of questions

pair_duplicates = df[['qid1','qid2','is_duplicate']].groupby(['qid1','qid2']).count().reset_index()

print ("Number of duplicate questions",(pair_duplicates).shape[0] - df.shape[0])
# Number of duplicate questions 0

""" 3.2.4 Number of occurrences of each question """

plt.figure(figsize=(20, 10))
plt.hist(qids.value_counts(), bins=160)
plt.yscale('log', nonposy='clip')
plt.title('Log-Histogram of question appearance counts')
plt.xlabel('Number of occurences of question')
plt.ylabel('Number of questions')
plt.savefig('0.4_logHistogramPlot_questionOccurenceCount.png')
plt.show()
print ('Maximum number of times a single question is repeated: {}\n'.format(max(qids.value_counts())))

""" 3.2.5 Checking for NULL values """

#Checking whether there are any rows with null values
nan_rows = df[df.isnull().any(1)]
print (nan_rows)
"""
            id    qid1    qid2                         question1  \
105780  105780  174363  174364    How can I develop android app?
201841  201841  303951  174364  How can I create an Android app?
363362  363362  493340  493341                               NaN

                                                question2  is_duplicate
105780                                                NaN             0
201841                                                NaN             0
363362  My Chinese name is Haichao Yu. What English na...             0

OBSERVATION:
- There are two rows with null values in question2"""

# Filling the null values with ' '
df = df.fillna('')
nan_rows = df[df.isnull().any(1)]
print (nan_rows)
"""
Empty DataFrame
Columns: [id, qid1, qid2, question1, question2, is_duplicate]
Index: [] """

"""
3.3 Basic Feature Extraction (before cleaning)

Let us now construct a few features like:

freq_qid1 = Frequency of qid1's
freq_qid2 = Frequency of qid2's
q1len = Length of q1
q2len = Length of q2
q1_n_words = Number of words in Question 1
q2_n_words = Number of words in Question 2
word_Common = (Number of common unique words in Question 1 and Question 2)
word_Total =(Total num of words in Question 1 + Total num of words in Question 2)
word_share = (word_common)/(word_Total)
freq_q1+freq_q2 = sum total of frequency of qid1 and qid2
freq_q1-freq_q2 = absolute difference of frequency of qid1 and qid2 """

df['freq_qid1'] = df.groupby('qid1')['qid1'].transform('count')
df['freq_qid2'] = df.groupby('qid2')['qid2'].transform('count')
df['q1len'] = df['question1'].str.len()
df['q2len'] = df['question2'].str.len()
df['q1_n_words'] = df['question1'].apply(lambda row: len(row.split(" ")))
df['q2_n_words'] = df['question2'].apply(lambda row: len(row.split(" ")))

def normalized_word_Common(row):
    w1 = set(map(lambda word: word.lower().strip(), row['question1'].split(" ")))
    w2 = set(map(lambda word: word.lower().strip(), row['question2'].split(" ")))
    return 1.0 * len(w1 & w2)
df['word_Common'] = df.apply(normalized_word_Common, axis=1)

def normalized_word_Total(row):
    w1 = set(map(lambda word: word.lower().strip(), row['question1'].split(" ")))
    w2 = set(map(lambda word: word.lower().strip(), row['question2'].split(" ")))
    return 1.0 * (len(w1) + len(w2))
df['word_Total'] = df.apply(normalized_word_Total, axis=1)

def normalized_word_share(row):
    w1 = set(map(lambda word: word.lower().strip(), row['question1'].split(" ")))
    w2 = set(map(lambda word: word.lower().strip(), row['question2'].split(" ")))
    return 1.0 * len(w1 & w2)/(len(w1) + len(w2))
df['word_share'] = df.apply(normalized_word_share, axis=1)

df['freq_q1+q2'] = df['freq_qid1']+df['freq_qid2']
df['freq_q1-q2'] = abs(df['freq_qid1']-df['freq_qid2'])

df.to_csv("dataset/df_fe_without_preprocessing_train.csv", index=False)

df.head()
"""
   id  qid1  qid2                                          question1  \
0   0     1     2  What is the step by step guide to invest in sh...
1   1     3     4  What is the story of Kohinoor (Koh-i-Noor) Dia...
2   2     5     6  How can I increase the speed of my internet co...
3   3     7     8  Why am I mentally very lonely? How can I solve...
4   4     9    10  Which one dissolve in water quikly sugar, salt...

                                           question2  is_duplicate  freq_qid1  \
0  What is the step by step guide to invest in sh...             0          1
1  What would happen if the Indian government sto...             0          4
2  How can Internet speed be increased by hacking...             0          1
3  Find the remainder when [math]23^{24}[/math] i...             0          1
4            Which fish would survive in salt water?             0          3

   freq_qid2  q1len  q2len  q1_n_words  q2_n_words  word_Common  word_Total  \
0          1     66     57          14          12         10.0        23.0
1          1     51     88           8          13          4.0        20.0
2          1     73     59          14          10          4.0        24.0
3          1     50     65          11           9          0.0        19.0
4          1     76     39          13           7          2.0        20.0

   word_share  freq_q1+q2  freq_q1-q2
0    0.434783           2           0
1    0.200000           5           3
2    0.166667           2           0
3    0.000000           2           0
4    0.100000           4           2 """

""" 3.3.1 Analysis of some of the extracted features
Here are some questions have only one single words. """

print ("Minimum length of the questions in question1 : " , min(df['q1_n_words']))
print ("Minimum length of the questions in question2 : " , min(df['q2_n_words']))
print ("Number of Questions with minimum length [question1] :", df[df['q1_n_words']== 1].shape[0])
print ("Number of Questions with minimum length [question2] :", df[df['q2_n_words']== 1].shape[0])
"""
Minimum length of the questions in question1 :  1
Minimum length of the questions in question2 :  1
Number of Questions with minimum length [question1] : 67
Number of Questions with minimum length [question2] : 24 """

""" 3.3.1.1 Feature: word_share  """

plt.figure(figsize=(12, 8))

plt.subplot(1,2,1)
sns.violinplot(x = 'is_duplicate', y = 'word_share', data = df[0:])

plt.subplot(1,2,2)
sns.distplot(df[df['is_duplicate'] == 1.0]['word_share'][0:] , label = "1", color = 'red')
sns.distplot(df[df['is_duplicate'] == 0.0]['word_share'][0:] , label = "0" , color = 'blue' )
plt.savefig('0.4_violinPlot_wordShareFeature.png')
plt.show()

"""
OBSERVATION
- The distributions for normalized word_share have some overlap on the far right-hand side, i.e., there are quite a lot of questions with high word similarity
- The average word share and Common no. of words of qid1 and qid2 is more when they are duplicate(Similar) """

""" 3.3.1.2 Feature: word_Common  """

plt.figure(figsize=(12, 8))

plt.subplot(1,2,1)
sns.violinplot(x = 'is_duplicate', y = 'word_Common', data = df[0:])

plt.subplot(1,2,2)
sns.distplot(df[df['is_duplicate'] == 1.0]['word_Common'][0:] , label = "1", color = 'red')
sns.distplot(df[df['is_duplicate'] == 0.0]['word_Common'][0:] , label = "0" , color = 'blue' )
plt.savefig('0.4_violinPlot_wordCommonFeature.png')
plt.show()

"""
OBSERVATION:
- The distributions of the word_Common feature in similar and non-similar questions are highly overlapping """


#coding:utf-8
import sys
import numpy as np
import cv2
import os
rootPath = os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)), "../"))
sys.path.insert(0, rootPath)
from tools.common_utils import IoU

def gen_hard_bbox_pnet(srcDataSet, srcAnnotations):
    srcDataSet = os.path.join(rootPath, srcDataSet)
    srcAnnotations = os.path.join(rootPath, srcAnnotations)
    saveFolder = os.path.join(rootPath, "tmp/data/pnet/")
    print(">>>>>> Gen hard samples for pnet...")
    typeName = ["pos", "neg", "part"]
    saveFiles = {}
    for tp in typeName:
        _saveFolder = os.path.join(saveFolder, tp)
        if not os.path.isdir(_saveFolder):
            os.makedirs(_saveFolder)
        saveFiles[tp] = open(os.path.join(saveFolder, "{}.txt".format(tp)), 'w')

    annotationsFile = open(srcAnnotations, "r")
    pIdx = 0 # positive
    nIdx = 0 # negative
    dIdx = 0 # dont care
    idx = 0
    for annotation in annotationsFile:
        annotation = annotation.strip().split(' ')
        # image path
        imPath = annotation[0]
        # boxed change to float type
        bbox = map(float, annotation[1:])
        # gt. each row mean bounding box
        boxes = np.array(bbox, dtype=np.float32).reshape(-1, 4)
        #load image
        img = cv2.imread(os.path.join(srcDataSet, imPath + '.jpg'))
        idx += 1
        height, width, channel = img.shape

        # 1. NEG: random to crop negative sample image
        negNum = 0
        while negNum < 50:
            size = np.random.randint(12, min(width, height) / 2)
            # top_left
            nx = np.random.randint(0, width - size)
            ny = np.random.randint(0, height - size)
            # random crop
            crop_box = np.array([nx, ny, nx + size, ny + size])
            # cal iou and iou must below 0.3 for neg sample
            iou = IoU(crop_box, boxes)
            if np.max(iou) >= 0.3:
                continue
            # crop sample image
            cropped_im = img[ny : ny + size, nx : nx + size, :]
            resized_im = cv2.resize(cropped_im, (12, 12), interpolation=cv2.INTER_LINEAR)
            # now to save it
            save_file = os.path.join(saveFolder, "neg", "%s.jpg"%nIdx)
            saveFiles['neg'].write(save_file + ' 0\n')
            cv2.imwrite(save_file, resized_im)
            nIdx += 1
            negNum += 1
        for box in boxes:
            # box (x_left, y_top, x_right, y_bottom)
            x1, y1, x2, y2 = box
            #bbox's width and height
            w, h = x2 - x1 + 1, y2 - y1 + 1
            # ignore small faces
            # in case the ground truth boxes of small faces are not accurate
            if max(w, h) < 40 or x1 < 0 or y1 < 0:
                continue
            # 2. NEG: random to crop sample image in bbox inside
            for i in range(5):
                size = np.random.randint(12, min(width, height) / 2)
                # delta_x and delta_y are offsets of (x1, y1)
                delta_x = np.random.randint(max(-size, -x1), w)
                delta_y = np.random.randint(max(-size, -y1), h)
                nx1 = int(max(0, x1 + delta_x))
                ny1 = int(max(0, y1 + delta_y))
                if nx1 + size > width or ny1 + size > height:
                    continue
                crop_box = np.array([nx1, ny1, nx1 + size, ny1 + size])
                Iou = IoU(crop_box, boxes)
                if np.max(iou) >= 0.3:
                    continue
                cropped_im = img[ny1: ny1 + size, nx1: nx1 + size, :]
                resized_im = cv2.resize(cropped_im, (12, 12), interpolation=cv2.INTER_LINEAR)
                save_file = os.path.join(saveFolder, "neg", "%s.jpg"%nIdx)
                saveFiles['neg'].write(save_file + ' 0\n')
                cv2.imwrite(save_file, resized_im)
                nIdx += 1
            # 3. POS and PART
            for i in range(20):
                # pos and part face size [minsize*0.8,maxsize*1.25]
                size = np.random.randint(int(min(w, h) * 0.8), np.ceil(1.25 * max(w, h)))
                # delta here is the offset of box center
                delta_x = np.random.randint(-w * 0.2, w * 0.2)
                delta_y = np.random.randint(-h * 0.2, h * 0.2)
                #show this way: nx1 = max(x1+w/2-size/2+delta_x)
                nx1 = max(x1 + w / 2 + delta_x - size / 2, 0)
                #show this way: ny1 = max(y1+h/2-size/2+delta_y)
                ny1 = max(y1 + h / 2 + delta_y - size / 2, 0)
                nx2 = nx1 + size
                ny2 = ny1 + size

                if nx2 > width or ny2 > height:
                    continue
                crop_box = np.array([nx1, ny1, nx2, ny2])
                #yu gt de offset
                offset_x1 = (x1 - nx1) / float(size)
                offset_y1 = (y1 - ny1) / float(size)
                offset_x2 = (x2 - nx2) / float(size)
                offset_y2 = (y2 - ny2) / float(size)
                #crop
                cropped_im = img[int(ny1) : int(ny2), int(nx1) : int(nx2), :]
                #resize
                resized_im = cv2.resize(cropped_im, (12, 12), interpolation=cv2.INTER_LINEAR)

                box_ = box.reshape(1, -1)
                if IoU(crop_box, box_) >= 0.65:
                    save_file = os.path.join(saveFolder, "pos", "%s.jpg"%pIdx)
                    saveFiles['pos'].write(save_file + ' 1 %.2f %.2f %.2f %.2f\n'%(offset_x1, offset_y1, offset_x2, offset_y2))
                    cv2.imwrite(save_file, resized_im)
                    pIdx += 1
                elif IoU(crop_box, box_) >= 0.4:
                    save_file = os.path.join(saveFolder, "part", "%s.jpg"%dIdx)
                    saveFiles['part'].write(save_file + ' -1 %.2f %.2f %.2f %.2f\n'%(offset_x1, offset_y1, offset_x2, offset_y2))
                    cv2.imwrite(save_file, resized_im)
                    dIdx += 1
        printStr = "\r[{}] pos: {}  neg: {}  part:{}".format(idx, pIdx, nIdx, dIdx)
        sys.stdout.write(printStr)
        sys.stdout.flush()
    for f in saveFiles.values():
        f.close()
    print '\n'

if __name__ == "__main__":
    gen_hard_bbox_pnet("dataset/WIDER_train/images/", "dataset/wider_face_train.txt")


#!/usr/bin/python3

import gym
import numpy as np

env = gym.make('CartPole-v1')

def play(env, policy, is_render=False):
    observation = env.reset()
    done = False
    score = 0
    observations = []

    for _ in range(5000):
        observations += [observation.tolist()]

        if done:
            break

        outcome = np.dot(policy, observation)
        action = 1 if outcome > 0 else 0
        if(is_render==True):
            env.render()
        observation, reward, done, info = env.step(action)
        score += reward
    env.close()
    return score, observations

max_score = 0
for _ in range(100):
    policy = np.random.rand(1,4)
    score, observations = play(env, policy)
    if(score > max_score):
        max_score_policy = (policy, score)
    print('policy score', score)

policy = np.random.rand(1,4)
score, observations = play(env, max_score_policy[0], False)
print('policy score', score)

from flask import Flask
import json

app = Flask(__name__, static_folder='.')
@app.route("/data")
def data():
    return json.dumps(observations)

@app.route('/')
def root():
    return app.send_static_file('./index.html')

app.run(host='0.0.0.0', port='3000')


import numpy as np
import tensorflow as tf
from model_deepfake import build_model
from utils import random_crop_and_pad_image_and_labels
import os
import cv2
import time
import cv2
import numpy as np
from face_detection.face_detection import RetinaFace
import os
import skimage.io
import numpy as np
import cv2
detector = RetinaFace(0)
from tensorflow.python.ops import variables
slim = tf.contrib.slim
flags = tf.app.flags
FLAGS = flags.FLAGS
import random
flags.DEFINE_integer('max_to_keep', 50,
                     'Maximium number of checkpoints to be saved.')

flags.DEFINE_float('learning_power', 0.9,
                   'The power value used in the poly learning policy.')

flags.DEFINE_integer('Epochs', 100,
                     'The number of steps used for training')

flags.DEFINE_float('momentum', 0.9, 'The momentum value to use')

flags.DEFINE_integer('train_crop_size', 256 ,
                           'Image crop size [height, width] during training.')

flags.DEFINE_string('tf_initial_checkpoint', None,
                    'The initial checkpoint in tensorflow format.')

flags.DEFINE_float('learning_rate', .0000001,
                   'Learning rate employed during slow start.')

flags.DEFINE_string('image_dir', None,
                    'The Image Directory.')

flags.DEFINE_string('label_dir', None,
                    'The Label Directory.')

flags.DEFINE_string('log_dir', None,
                    'The Logs Directory.')

flags.DEFINE_float('clip_by_value', 1.0, 'The value to be used for clipping.')

flags.DEFINE_string('train_text', None,
                    'The Path to the text file containing names of Images and Labels')###This text file should not have extensions in their names such as 8192.png or 8192.jpg instead just the name such as 8192

Image_directory = FLAGS.image_dir
Label_directory = FLAGS.label_dir
my_log_dir = FLAGS.log_dir

def save(saver, sess, logdir, step):

   model_name = 'model.ckpt'
   checkpoint_path = os.path.join(logdir, model_name)

   if not os.path.exists(logdir):
      os.makedirs(logdir)
   saver.save(sess, checkpoint_path, global_step=step)
   print('The checkpoint has been created.')

def load(saver, sess, ckpt_path):
    saver.restore(sess, ckpt_path)
    print("Restored model parameters from {}".format(ckpt_path))

def main(unused_argv):

    image_ph = tf.placeholder(tf.float32,[1,256,256,3],name='image_placeholder')
    pred_sigmoid,pred = build_model(image_ph)
    print(pred)
    loader = tf.train.Saver(var_list=tf.global_variables() )
    init = variables.global_variables_initializer()
    folders='/home/ubuntu/Trueaware/Test_Fake_Videos/'
    count_true=0
    count_false=0
    with tf.Session() as sess:
        sess.run(init)
        #if FLAGS.tf_initial_checkpoint==True:
        load(loader, sess, './checkpoint/model.ckpt-700000')
        print('Training Starts........')
        step_iter = 0
        alls=os.listdir(folders)
        for al in alls:
            try :
                cap = cv2.VideoCapture(folders+al)
                print(folders+al)
                property_id = int(cv2.CAP_PROP_FRAME_COUNT)
                length = int(cv2.VideoCapture.get(cap, property_id))

                frame= random.randint(0,length)
                cap.set(cv2.CAP_PROP_POS_FRAMES, frame-1)
                res, frame = cap.read()
                faces = detector(frame)
                box, landmarks, score = faces[0]
                box = box.astype(np.int)
                fimg=frame[box[1]-10:box[3]+10,box[0]-10:box[2]+10,:]
                faces = cv2.cvtColor(np.asarray(fimg), cv2.COLOR_BGR2RGB)
                faces=cv2.resize(faces,(256,256),interpolation=cv2.INTER_CUBIC)
                input_image = faces.copy()
                input_image = np.expand_dims(faces,axis=0)
                start_time = time.time()

                feed_dict={image_ph:input_image}#,label_ph:class_id}
                P= sess.run(pred_sigmoid, feed_dict=feed_dict)
                print(P[0][0])

                if P[0][0]>0.65:    ## Threshold
                    print('DEEPFAKE DETECTED')
                    count_true=count_true+1

                else:
                    print('DEEPFAKE NOT DETECTED')
                    count_false=count_false+1

            except:
                print("ERROR")
    print('TRUE DETECTION ::',count_true)
    print('FALSE DETECTION ::',count_false)

if __name__ == '__main__':
  tf.app.run()


import os
from sympy import *
import pandas as pd
import numpy as np
import scipy.fftpack
import matplotlib as mpl
import matplotlib.pyplot as plt
plt.style.use("seaborn-paper")
from mpl_toolkits.axes_grid1 import make_axes_locatable

def find_nearest(array, value):
    array = np.asarray(array)
    idx = (np.abs(array - value)).argmin()
    return idx

def find_nearest_value(array, value):
    array = np.asarray(array)
    idx = (np.abs(array - value)).argmin()
    return array[idx]

# ## Canvas palette

# In[2]:

#Canvas for single plot
x = np.linspace(0,10,100)
y = np.sin(x)
plt.figure(figsize=[14,6])
plt.grid(True)
plt.title("Change-me!",fontsize=20)
plt.plot(x,y,label="testvalue")
plt.legend(fontsize=16)
plt.xlabel("XLABEL (unit)",fontsize=18)
plt.ylabel("YLABEL (unit)",fontsize=18)
plt.show()

# In[3]:

#Canvas for side by side
fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(14,6))
fig.suptitle("test",y=1.05,fontsize=20)

axes[0].grid(True)
axes[0].plot(x,y,label="testvalue")
axes[0].legend(fontsize=16)
axes[0].set_title("TESTTITLE",fontsize=18)
axes[0].set_xlabel("XLABEL (unit)",fontsize=18)
axes[0].set_ylabel("YLABEL (unit)",fontsize=18)
axes[0].legend(fontsize=16)
axes[0].tick_params(axis='both', which='major', labelsize=15)

axes[1].grid(True)
axes[1].plot(x,y,label="testvalue")
axes[1].legend(fontsize=16)
axes[1].set_title("TESTTITLE",fontsize=18)
axes[1].set_xlabel("XLABEL (unit)",fontsize=18)
axes[1].set_ylabel("YLABEL (unit)",fontsize=18)
axes[1].legend(fontsize=16)
axes[1].tick_params(axis='both', which='major', labelsize=15)

fig.tight_layout()
plt.show()

# In[4]:

#Canvas for side by side
fig, axes = plt.subplots(nrows=2, ncols=4, figsize=(14,6))
fig.suptitle("test",y=1.05,fontsize=20)

axes[0,0].grid(True)
axes[0,0].plot(x,y,label="testvalue")
axes[0,0].legend(fontsize=16)
axes[0,0].set_title("TESTTITLE",fontsize=18)
axes[0,0].set_xlabel("XLABEL (unit)",fontsize=18)
axes[0,0].set_ylabel("YLABEL (unit)",fontsize=18)
axes[0,0].legend(fontsize=16)
axes[0,0].tick_params(axis='both', which='major', labelsize=15)

axes[0,1].grid(True)
axes[0,1].plot(x,y,label="testvalue")
axes[0,1].legend(fontsize=16)
axes[0,1].set_title("TESTTITLE",fontsize=18)
axes[0,1].set_xlabel("XLABEL (unit)",fontsize=18)
axes[0,1].set_ylabel("YLABEL (unit)",fontsize=18)
axes[0,1].legend(fontsize=16)
axes[0,1].tick_params(axis='both', which='major', labelsize=15)

fig.tight_layout()
plt.show()

# ## Read data

# In[2]:

#Folder and paths definitions
main_path  = os.getcwd()
datafolder_path = main_path+"/results"
results_dir = "/output_py"
output_dir = main_path+results_dir
try:
    os.mkdir(output_dir)
except OSError:
    print ("Creation of the directory %s failed" % results_dir)
else:
    print ("Successfully created the directory %s " % results_dir)

# In[27]:

Kvalues = np.linspace(0,6,30)
Kvalues = np.around(Kvalues, decimals=3)

# In[28]:

pvalues = [0, 0.05, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, .95, 1]

# In[9]:

#Create dataframe dictionary. For each entry, first value is the K of the dataframe (second value)
data = []
for i in range(0,len(Kvalues)):
    for j in pvalues:
        filename = datafolder_path + "/WS_gfreq_uphase_N2000_NOMF_T20000_dt0.0100_nruns10_K%.3f_p=%.3f.tsv"%(Kvalues[i],j)
        #cols refers to timestep, avgmod, stdmod, avgphase,stdphase (of order parameter)
        df = pd.read_csv(filename,sep="\t",header=None)
        data.append([Kvalues[i],j,df])

# In[10]:

#data[0][x]#, x=0=> K, x=1=>p, x=2=> df

# In[11]:

def rinf_avg(df):
    lasts = df[1][int(T*.9):-1]
    return np.mean(lasts)

def rinf_std(df):
    lasts = df[1][int(T*.9):-1]
    return np.std(lasts)

# In[12]:

K_plot = []
p_plot = []
r_inf_avg = []
r_inf_std = []
for i in range(0,len(data)):
    t_plot = data[i][2][0]
    K_plot.append(data[i][0])
    p_plot.append(data[i][1])
    r_inf_avg.append(rinf_avg(data[i][2]))
    r_inf_std.append(rinf_std(data[i][2]))

# In[29]:

r_inf_mat = np.zeros(shape=[len(pvalues),len(Kvalues)])
for i in range(0,len(data)):
    r_inf_mat[find_nearest(pvalues, data[i][1])][find_nearest(Kvalues, data[i][0])] = rinf_avg(data[i][2])

plt.figure(figsize=[10,10])
ax = plt.gca()
#name = "Kuramoto oscillators on Watts-Strogatz network \n N = %d, r = %d, dt = %.3f, %s, n_runs = %d\n$r_{\\infty}$"%(N,2,dt,freq_plot, n_runs)

im = plt.imshow(r_inf_mat)
plt.title("Kuramoto oscillators on Watts-Strogatz network\n N=%d, $r_{WS}$=%d, T=%d, dt=%.3f, %s, n_runs=%d\n $r_{\\infty}$"%(N,3,T,dt,freq_plot,n_runs),fontsize=20)

plt.yticks(np.linspace(0,len(pvalues)-1,len(pvalues)),pvalues)
plt.ylabel("p",fontsize=18,rotation=0)
plt.xticks(np.linspace(0,len(Kvalues)-1,len(Kvalues)),Kvalues,rotation=45)
plt.xlabel("K",fontsize=18)

divider = make_axes_locatable(ax)
cax = divider.append_axes("right", size="5%", pad=0.15)
cbar = plt.colorbar(im, cax=cax)

plt.tight_layout()
plt.savefig(output_dir+config_name+"WS_rinf_heatmap.png")

plt.show()

# In[14]:

Kcs_df = pd.DataFrame([p_plot,K_plot,r_inf_avg,r_inf_std]).T

# In[15]:

Kcs_df = Kcs_df.sort_values(by=[0,1])

# In[31]:

Kc_plot = []

for i in range(0,len(pvalues)):

    idx_kc = find_nearest(Kcs_df[0+i*len(Kvalues)+1:len(Kvalues)*(i+1)][2],.5)
    rinf_value = find_nearest_value(Kcs_df[0+i*len(Kvalues)+1:len(Kvalues)*(i+1)][2],.5)
    df = Kcs_df[0+i*len(Kvalues)+1:len(Kvalues)*(i+1)]
    df = df.reset_index(inplace = False)
    Kc_plot.append([pvalues[i],df[1].iloc[idx_kc]])
    print("prob",pvalues[i],", rinf %.3f"%(rinf_value),", K",df[1].iloc[idx_kc])
Kc_plot = pd.DataFrame(Kc_plot, columns=["p","Kc"])

# In[54]:

import matplotlib.pyplot as plt
from scipy.optimize import curve_fit

# In[55]:

def func(x, a, b):
    return a+ b/x

# In[66]:

popt, pcov = curve_fit(func, Kc_plot["p"][2:],Kc_plot["Kc"][2:], p0=[1.6,2])
popt

# In[70]:

x_fit = np.linspace(Kc_plot["p"][2],1,100)
y_fit = func(x_fit,popt[0],popt[1])

# In[94]:

plt.figure(figsize=[14,6])
plt.grid(True,alpha=.3)
plt.title("Kuramoto oscillators on Watts-Strogatz network\n N=%d, $r_{WS}$=%d, T=%d, dt=%.3f, %s, n_runs=%d\n $K_{c}(p)$"%(N,3,T,dt,freq_plot,n_runs),fontsize=20)
plt.plot(x_fit,y_fit,label="y(p) = a + $\\frac{b}{p}$ fit")
plt.plot(Kc_plot["p"],Kc_plot["Kc"],c='b',marker='o',markersize=12,ls='',label="Raw Data")
plt.plot(Kc_plot["p"][2:],Kc_plot["Kc"][2:],c='r',marker='o',markersize=9,ls='--',linewidth=.7,label="Fitted Data")
plt.xticks(pvalues,pvalues)
plt.xlabel("p",fontsize=18,rotation=0)
plt.yticks(Kvalues,Kvalues)
plt.ylim(min(Kc_plot["Kc"])*.85,max(Kc_plot["Kc"])*1.1)
plt.legend(fontsize=18)

plt.text(.5,4,"a = %.4f$\pm$%.4f, b = %.4f$\pm$%.4f"%(popt[0],pcov[0,0],popt[1],pcov[1,1]),fontsize=20)

plt.tight_layout()
plt.savefig(output_dir+config_name+"WS_Kc(p).png")

import numpy,import scipy,from scipy,from sympy