import numpy as np
import argparse
import cv2
import sys
from scipy.spatial import distance
import datetime as dt
from pathlib import Path
import time
import os
from threading import Thread
# VideoCaptureAsync implements separate thread for reading stream from camera
from utils.videocaptureasync import VideoCaptureAsync
from utils.frameDisplay import FrameDisplay
from utils.distanceCalc import calculate_dist
from utils.DL_model import DetectorAPI,centre_calcualtion
from utils.process_stream import VisionSurveillance , spawn_process
import multiprocessing

from multiprocessing import shared_memory , Queue
from ui.ui_main import *
import ctypes

from utils.CSVConverter import writeToCSV, readFromCSV

# The below model_path contains the link to frozen graph of pretrained tensorflow object detection model.
model_path = 'ssd_mobilenet_v1_coco_2018_01_28/frozen_inference_graph.pb'
# Probability of detected box to be human
detection_confidence = 0.4
# A standard shape has been fixed for all images
shape = (800,800)

# Human Detector class is responsible of initiating the object detection model
# Here the detection task is designed to run on a separate process.
class HumanDetector():
    def __init__(self):
        self.isStarted=True

    # Start function spawn the process for detection tasks and executes detector function.
    def start(self,queue,N):
        # Spawn the process and store the refernece in p
        self.p=multiprocessing.Process(target=self.detector,args=(queue,N))
        self.p.start()
        return self

    # Detector function does the detection on each image from all cams.
    # It uses a queue to receive and process the request from different cameras in a First Come First Serve basis.
    # It fetches the images of coreesponding cam from shared memory and does detection task.
    # The results of detection are the stored in a separate sahred memory container.
    def detector(self,queue,N):
        # DetectorAPI is the tensorflow Object Detection API which loads the model and performs detection
        # with help of image tensors and saved model tensors
        odapi = DetectorAPI(model_path)

        # Below the different shared memory are connected and assigned to specific numpy arrays as needed

        # The image conatiner conatins the images of all N cameras in a numpy aaray format.
        # The shape of image is fixed beforehand declared as global variable on start of this script as 'shape'
        image_container = shared_memory.SharedMemory(name='image_container')
        # Reshape the connected memory in desired numpy aaray and assign to local variable image
        images = np.ndarray((N,shape[0],shape[1],3), dtype=np.float32, buffer=image_container.buf)

        # Boxes shared stores the vertices of the rectangular boxes of all cameras given by the detectorAPI
        boxes_shared = shared_memory.SharedMemory(name='boxes_container')
        boxes = np.ndarray((N,100,4),dtype=np.float32, buffer=boxes_shared.buf)

        # Scores stores the probability of each predicted boxes
        scores_shared = shared_memory.SharedMemory(name='scores_container')
        scores = np.ndarray((N,100),dtype=np.float32, buffer=scores_shared.buf)

        # Classes stores the class index of boxes predicted by the detector
        # Note: The detector currently being used does multiclass detection
        classes_shared = shared_memory.SharedMemory(name='classes_container')
        classes = np.ndarray((N,100),dtype=np.float32, buffer=classes_shared.buf)

        # num stores the total number of objects detected in a frame.
        num_shared = shared_memory.SharedMemory(name='num_container')
        num = np.ndarray((N),dtype=np.int, buffer=num_shared.buf)

        status_flag_memory = shared_memory.SharedMemory(name='status_flag_container')
        status_flag = np.ndarray((N), dtype=bool, buffer=status_flag_memory.buf)
        # Start the while loop till user exits it and performs the detection.
        # The index of cameras who has requested for detection is fetched from queue
        # The request are adhered in First Come First Serve basis
        # print('Detection start')
        while self.isStarted :
            if not queue.empty():
                # get index or Cam ID of cam with first request from queue
                index = queue.get()
                if status_flag[index]:
                    # Fetch most recent image of indexed camera from the shared memory
                    img = images[index]
                    # processFrame does the detection on image feeded to it
                    # Store the results like boxes , scores , classes,num of the respective frame in the
                    # shared memory with the cam id as index
                    boxes[index], scores[index], classes[index], num[index] = odapi.processFrame(img)
        # print('Detection closed')
        # After exit of while loop disconnect all shared memory.
        image_container.close()
        boxes_shared.close()
        scores_shared.close()
        classes_shared.close()
        num_shared.close()
        sys.exit(0)

def start_streams_process(sources,M,queue,N):
    i=0
    jobs = []
    while i<len(sources):
        # print(i)
        if (i+M)<=len(sources):
            process = multiprocessing.Process(target=spawn_process,
                            args=(sources[i:i+M],i,queue,N))
        else:
            process = multiprocessing.Process(target=spawn_process,
                            args=(sources[i:],i,queue,N))

        process.start()
        jobs.append(process)
        i+=M

    return jobs

# T=terminate Process is responsible for killing all the processes initiated by this programe for different cameras
def terminate_processes(jobs,detector_process):

    for process in jobs:
        process.terminate()
        process.join()

def update_UI(ui,MainWindow,dispaly,frame_data,current_cam_count,sources):
    # Append the different cameras and their sources in the UI
    for index, src in enumerate(sources):
        ui.addCamera(index,src[0],tag=src[1])

    current_cam_count=len(sources)
    while True:
        if ui.removeStreamTrigger:
            status_flag[ui.removeStreamId]=False
            ui.removeStreamTrigger=False
            sources.pop(ui.removeStreamId)
            ui.clearWidgets()
            # Returns restart or exit
            return True

        if ui.newStreamTrigger:
            ui.newStreamTrigger=False
            sources.append([ui.newStreamDict['ip'],ui.newStreamDict['tag']])
            ui.newStreamDict.clear()
            ui.clearWidgets()
            return True

        if ui.editStreamTrigger:
            ui.editStreamTrigger=False
            sources[ui.editStreamDict['id']] = [ ui.editStreamDict['ip'], ui.editStreamDict['tag'] ]
            ui.editStreamDict.clear()
            if ui.editStreamIPTrigger:
                ui.editStreamIPTrigger = False
                ui.clearWidgets()
                return True

        for i in range(current_cam_count):
            if status_flag[i]:
                # cf = current _frame : extract current frame from sahred memory
                cf = display[i]
                cf = np.round(cf)
                cf = np.uint8(cf)
                # update the frame in respective window in UI
                ui.updateCamera(i, cf,frame_data[i][0],frame_data[i][1]
                                    ,frame_data[i][2],frame_data[i][3])

                # selectedIndex show the index of camera which is in focus in Main Window
                if i == ui.selectedIndex:
                    ui.setLabeltoFrame(ui.getCameraWidget(i).currentQtFrame, ui.mainDisplay)
                    ui.setDescription(i)
                    ui.getCameraWidget(i).setChecked(True)
                    ui.getCameraWidget(i).camera.setLineWidth(5)
                    ui.getCameraWidget(i).cameraBoxTag.setLineWidth(5)
                ui.getCameraWidget(i).camera.setEnabled(True)
                ui.getCameraWidget(i).cameraBoxTag.setEnabled(True)

        cv2.waitKey(1)
        if not MainWindow.isVisible():
            # print('exit')
            return False

if __name__ == '__main__':
    #Code to support multiprocessing module for application packaging using pyinstaller
    multiprocessing.freeze_support()

    # M show the max number of cam we are willing to run in one process
    M = 4
    # N will show the max no. of cameras the program is able to handle i.e. upper threshold of no. of cams
    N = 50

    # Sets directory for saving the vilation images
    save_directory_file = open("save_directory_path.txt","r")
    name = save_directory_file.readline().strip()
    save_directory_file.close()
    save_dir=name

    if name=='' or os.path.isdir(save_dir)==False:
        name = os.getcwd()
        save_dir = os.path.join(name,'captures')
        try:
            os.mkdir(save_dir)
        except OSError as error:
            pass
    else:
        save_dir = str(Path(name))

    CameraIPData = 'CameraIPData'

    sources = readFromCSV(filename = CameraIPData)

    # N = total number of streams to process
    current_cam_count = len(sources)

    # The below commands initiate some memories in the volatile spaces ehich can be shared and accesses from
    # all the different processes

    # First we make some standard variable which will decide the size of shared memory containers.

    images = np.zeros((N,shape[0],shape[1],3),dtype=np.float32)
    boxes = np.zeros((N,100,4),dtype=np.float32)
    scores=np.zeros((N,100),dtype=np.float32)
    classes=np.zeros((N,100),dtype=np.float32)
    num = np.zeros((N),dtype=np.int)
    frame_data = np.zeros((N,4),dtype=np.int)
    status_flag = np.zeros((N),dtype=bool)
    directory_path = np.chararray(1,itemsize=500)

    # The image conatiner conatins the images of all N cameras in a numpy aaray format.
    # The shape of image is fixed beforehand declared as global variable on start of this script as 'shape'
    try:
        img_shared = shared_memory.SharedMemory(create=True,size=images.nbytes,name='image_container')
    except FileExistsError:
        img_shared = shared_memory.SharedMemory(name='image_container')
    images = np.ndarray((N,shape[0],shape[1],3), dtype=np.float32, buffer=img_shared.buf)

    try:
        display_shared = shared_memory.SharedMemory(create=True,size=images.nbytes,name='display_container')
    except FileExistsError:
        display_shared = shared_memory.SharedMemory(name='display_container')
    display = np.ndarray((N,shape[0],shape[1],3), dtype=np.float32, buffer=display_shared.buf)

    try:
        boxes_shared = shared_memory.SharedMemory(create=True,size=boxes.nbytes,name='boxes_container')
    except FileExistsError:
        boxes_shared = shared_memory.SharedMemory(name='boxes_container')
    boxes = np.ndarray((N,100,4),dtype=np.float32, buffer=boxes_shared.buf)

    try:
        scores_shared = shared_memory.SharedMemory(create=True,size=scores.nbytes,name='scores_container')
    except FileExistsError:
        scores_shared = shared_memory.SharedMemory(name='scores_container')
    scores = np.ndarray((N,100),dtype=np.float32, buffer=scores_shared.buf)

    try:
        classes_shared = shared_memory.SharedMemory(create=True,size=classes.nbytes,name='classes_container')
    except FileExistsError:
        classes_shared = shared_memory.SharedMemory(name='classes_container')
    classes = np.ndarray((N,100),dtype=np.float32, buffer=classes_shared.buf)
    try:
        num_shared = shared_memory.SharedMemory(create=True,size=num.nbytes,name='num_container')
    except FileExistsError:
        num_shared = shared_memory.SharedMemory(name='num_container')
    num = np.ndarray((N),dtype=np.int, buffer=num_shared.buf)

    try:
        status_flag_memory = shared_memory.SharedMemory(create=True,size=status_flag.nbytes,name='status_flag_container')
    except FileExistsError:
        status_flag_memory = shared_memory.SharedMemory(name='status_flag_container')
    status_flag = np.ndarray((N),dtype=bool, buffer=status_flag_memory.buf)

    # Frame data will store all the data and parameters like no. of humans , no. of safe and unsafe humans
    # of processed frame in the shared memory which can be accessed by the UI
    try:
        frame_data_memory = shared_memory.SharedMemory(create=True,size=frame_data.nbytes,name='frame_data_container')
    except FileExistsError:
        frame_data_memory = shared_memory.SharedMemory(name='frame_data_container')
    frame_data = np.ndarray((N,4), dtype=np.float32, buffer=frame_data_memory.buf)

    try:
        path_memory = shared_memory.SharedMemory(create=True,size=directory_path.nbytes,name='directory_path_category')
    except FileExistsError:
        path_memory = shared_memory.SharedMemory(name='directory_path_category')
    directory_path = np.chararray(1,itemsize=500, buffer=path_memory.buf)

    directory_path[0] = save_dir
    try:
        os.mkdir(directory_path[0].decode())
    except OSError as error:
        pass

    queue = Queue()

    detector_process = HumanDetector().start(queue,N)

    app = QtWidgets.QApplication(sys.argv)
    MainWindow = QtWidgets.QMainWindow()
    ui = Ui_MainWindow()
    ui.setupUi(MainWindow)
    MainWindow.show()
# ////////////////////////////////////////////////////////////////////////////////////

    while True:
        jobs = start_streams_process(sources,M,queue,N)

        rest = update_UI(ui,MainWindow,display,frame_data,current_cam_count,sources)

        terminate_processes(jobs,detector_process)

        if not rest:
            break

    # print('Saving sources to CSV')
    writeToCSV(CameraIPData, sources)
    # save savinf folder name to txt file
    save_directory_file = open("save_directory_path.txt","w")
    save_directory_file.truncate(0)
    save_directory_file.write(directory_path[0].decode())
    save_directory_file.close()
    # print('Clear Garbage')
    terminate_processes(jobs,detector_process)
    # close the detector process i.e. HumanDetection class
    detector_process.isStarted = False
    detector_process.p.terminate()
    detector_process.p.join()
    # Erase all the reserved memory
    img_shared.unlink()
    display_shared.unlink()
    boxes_shared.unlink()
    scores_shared.unlink()
    classes_shared.unlink()
    num_shared.unlink()
    status_flag_memory.unlink()
    frame_data_memory.unlink()
    sys.exit(0)


import re
from datetime import datetime

from django.utils.dateparse import parse_datetime

from allauthdemo.polls.models import Event, Poll, PollOption, EmailUser
from allauthdemo.auth.models import DemoUser

'''
    Goal: Convert the new form data (from the updated DEMOS2 UI) returned to '/event/create' into
          an Event object that can be persisted via a Model to the DB

    Author: Vincent de Almeida

    Created: 11/06/2018
'''

# TODO: Define a validation function that can do back-end verification on top of the front end validation
# TODO: Validation can make use of __contains__ from QueryDict:
# TODO: https://docs.djangoproject.com/en/2.0/ref/request-response/#django.http.QueryDict

class EventModelAdaptor:
    # Raw data from form and django
    form_data = None
    user = None

    # Used for validating the form data
    form_data_validation = None
    invalid_form_fields = {}
    validation_starts_at = None
    validation_ends_at = None

    # Extracted form data
    event_name = None
    identifier = None
    starts_at = None
    ends_at = None
    organisers = []
    trustees = []
    voters = []

    # Each element of the map has a sub array with 2 elements - poll and associated options
    polls_options_map = []

    # Event Model Object containing all the extracted data
    event = None

    def __init__(self, form_data, user):
        self.form_data = form_data.copy()
        self.form_data_validation = form_data.copy()
        self.user = user

    def isFormDataValid(self, events, demo_users):
        nameValid = self.__isNameValid(events)
        identifierValid = self.__isIdentifierValid(events)
        eventTimingsValid = self.__isEventTimingsValid()
        pollsValid = self.__arePollsValid()
        organisersEmailsValid = self.__areOrganisersEmailsValid(demo_users)
        trusteesEmailsValid = self.__areTrusteeEmailsValid()
        votersListValid = self.__isVotersListValid()

        return nameValid \
               and identifierValid \
               and eventTimingsValid \
               and pollsValid \
               and organisersEmailsValid \
               and trusteesEmailsValid \
               and votersListValid

    def __isNameValid(self, events):
        valid = True

        event_name = self.form_data_validation.pop('name-input')[0]

        if event_name == '':
            self.invalid_form_fields['event_name'] = {'error': 'The event name field is blank.'}
            valid = False
        else:
            for event in events:
                if event.title == event_name:
                    self.invalid_form_fields['event_name'] = {'error': "The event name '" + event_name + "' is already in use."}
                    valid = False
                    break

        self.invalid_form_fields['event_name_data'] = {'val': event_name}

        return valid

    def __isIdentifierValid(self, events):
        valid = True

        identifier = self.form_data_validation.pop('identifier-input')[0]

        if identifier == '':
            self.invalid_form_fields['identifier'] = {'error': 'The event slug field is blank.'}
            valid = False
        else:
            for event in events:
                if event.EID == identifier:
                    self.invalid_form_fields['identifier'] = {'error': "The event slug '" + identifier + "' is already in use."}
                    valid = False
                    break

        self.invalid_form_fields['identifier_data'] = {'val': identifier}

        return valid

    def __isVoteStartValid(self):
        valid = True

        # Extract start and end times as string and convert to datetime to perform validation
        # The UTC offset comes with a colon i.e. '+01:00' which needs to be removed
        validation_error = "The voting start date and time format is invalid."
        starts_at_input = self.form_data_validation.pop('vote-start-input')[0]

        if starts_at_input == '':
            self.invalid_form_fields['starts_at'] = {'error': 'The voting start time is blank.'}
            return False

        starts_at = starts_at_input
        starts_at_offset_index = starts_at.find('+')

        if starts_at_offset_index != -1:
            # timezone data has been supplied so use parse_datetime from django
            starts_at_time = starts_at[0: starts_at_offset_index - 1].replace(' ', 'T')
            starts_at_offset = starts_at[starts_at_offset_index:].replace(':', '')
            starts_at = starts_at_time + starts_at_offset

            try:
                starts_at = parse_datetime(starts_at)

                if starts_at is None:
                    self.invalid_form_fields['starts_at'] = {'error': validation_error}
                    valid = False
            except ValueError:
                self.invalid_form_fields['starts_at'] = {'error': validation_error}
                valid = False
        else:
            # No Timezone data has been supplied so use strptime instead
            try:
                starts_at = datetime.strptime(starts_at, '%Y-%m-%d %H:%M')

                if starts_at is None:
                    self.invalid_form_fields['starts_at'] = {'error': validation_error}
                    valid = False
            except ValueError:
                self.invalid_form_fields['starts_at'] = {'error': validation_error}
                valid = False

        self.validation_starts_at = starts_at
        self.invalid_form_fields['starts_at_data'] = {'val': starts_at_input}

        return valid

    def __isVoteEndValid(self):
        valid = True

        validation_error = "The voting end date and time format is invalid."
        ends_at_input = self.form_data_validation.pop('vote-end-input')[0]

        if ends_at_input == '':
            self.invalid_form_fields['ends_at'] = {'error': 'The voting end time is blank.'}
            return False

        ends_at = ends_at_input
        ends_at_offset_index = ends_at.find('+')

        if ends_at_offset_index != -1:
            # timezone data has been supplied so use parse_datetime from django
            ends_at_time = ends_at[0:ends_at_offset_index - 1].replace(' ', 'T')
            ends_at_offset = ends_at[ends_at_offset_index:].replace(':', '')
            ends_at = ends_at_time + ends_at_offset

            try:
                ends_at = parse_datetime(ends_at)

                if ends_at is None:
                    self.invalid_form_fields['ends_at'] = {'error': validation_error}
                    valid = False
            except ValueError:
                self.invalid_form_fields['ends_at'] = {'error': validation_error}
                valid = False
        else:
            # No Timezone data has been supplied so use strptime instead
            try:
                ends_at = datetime.strptime(ends_at, '%Y-%m-%d %H:%M')

                if ends_at is None:
                    self.invalid_form_fields['ends_at'] = {'error': validation_error}
                    valid = False
            except ValueError:
                self.invalid_form_fields['ends_at'] = {'error': validation_error}
                valid = False

        # Store the ends_at for further validation as well as the original data val
        self.validation_ends_at = ends_at
        self.invalid_form_fields['ends_at_data'] = {'val': ends_at_input}

        return valid

    def __isEventTimingsValid(self):
        # Ensure that the start and end times are independently valid and then ensure they don't overlap
        # in an invalid manner
        voteStartValid = self.__isVoteStartValid()
        voteEndValid = self.__isVoteEndValid()
        eventTimingsValid = True

        # Ensure that the start date is before the end date and that the end is after the start
        if voteStartValid and voteEndValid:
            if not self.validation_starts_at < self.validation_ends_at and self.validation_ends_at > self.validation_starts_at:
                self.invalid_form_fields['event_timings'] = {'error': 'The start date must be before the end date and the end after the start date.'}
                eventTimingsValid = False

        return voteStartValid and voteEndValid and eventTimingsValid

    def __arePollsValid(self):
        valid = True

        # Get the poll count
        poll_count = int(self.form_data_validation.pop('poll-count-input')[0])
        polls_json = []
        errors_summary = "The following poll # have errors: "

        for i in range(poll_count):
            # Whether there are errors for this specific poll
            poll_valid = True

            # JSON representation of the poll
            poll_json = {}

            # JSON struct for defining errors in the poll
            poll_errors_json = {}

            # String version of i
            i_str = str(i)
            poll_json['no'] = {'val': i_str}

            # Inspect all of the options for this poll
            options = self.form_data_validation.pop('option-name-input-' + i_str)
            options_list = []
            blank_count = 0

            for option in options:
                if option == '':
                    blank_count += 1
                else:
                    options_list.append(option)

            # Add back the blank options to the option list not including the hidden one
            for i in range(blank_count-1):
                options_list.append("")

            # blank count is expected to be 1 due to the hidden option row that's cloned in the
            # front end every time a new option is added
            if blank_count > 1:
                poll_errors_json['options'] = {'val': "There are " + str(blank_count-1) + " blank poll options"}
                valid = False
                poll_valid = False

            poll_json['options'] = {'val': options_list}

            # Ensure that the poll question / statement isn't blank
            name = self.form_data_validation.pop('question-name-input-' + i_str)[0]

            if name == '':
                poll_errors_json['name'] = {'val': "The poll name is blank."}
                valid = False
                poll_valid = False

            # Record the poll name in the JSON representation of the poll
            poll_json['name'] = {'val': name}

            # Validate the min max poll option selections
            min_num_selections_str = self.form_data_validation.pop('minimum-input-' + i_str)[0]
            max_num_selections_str = self.form_data_validation.pop('maximum-input-' + i_str)[0]
            errors = ""

            if min_num_selections_str == '':
                errors = "The minimum selection cannot be blank. "
                valid = False
                poll_valid = False
            else:
                min_num_selections = None

                try:
                    min_num_selections = int(min_num_selections_str)

                    if min_num_selections < 0:
                        errors = "The minimum selection cannot be less than zero. "
                        valid = False
                        poll_valid = False

                    if min_num_selections > len(options) - blank_count:
                        if len(errors) > 0:
                            errors = errors + "and it cannot be more than the number of options. "
                        else:
                            errors = "The minimum selection cannot be greater than the number of options. "

                        valid = False
                        poll_valid = False

                    if max_num_selections_str == '':
                        max_sel_blank_err = "The maximum selection cannot be blank. "

                        if len(errors) > 0:
                            errors = errors + max_sel_blank_err
                        else:
                            errors = max_sel_blank_err

                        valid = False
                        poll_valid = False
                    else:
                        max_num_selections = None

                        try:
                            max_num_selections = int(max_num_selections_str)

                            if min_num_selections > max_num_selections:
                                min_gt_max_err = "The minimum selection cannot be greater than the maximum. "
                                if len(errors) > 0:
                                    errors = errors + min_gt_max_err
                                else:
                                    errors = min_gt_max_err

                                valid = False
                                poll_valid = False

                            if max_num_selections < 0:
                                max_less_zero_err = "The maximum cannot be less than 0. "

                                if len(errors) > 0:
                                    errors = errors + max_less_zero_err
                                else:
                                    errors = max_less_zero_err

                                valid = False
                                poll_valid = False

                            if max_num_selections > len(options) - blank_count:
                                max_options_err = "The max number of option selections cannot be more than the number of options."

                                if len(errors) > 0:
                                    errors = errors + max_options_err
                                else:
                                    errors = max_options_err

                                valid = False
                                poll_valid = False

                            # Record the min max poll option selection values in the JSON rep of the poll
                            poll_json['min_selection'] = {'val': min_num_selections}
                            poll_json['max_selection'] = {'val': max_num_selections}
                        except ValueError:
                            max_opts_input_err = "The maximum option selection input is not valid. "

                            if len(errors) > 0:
                                errors = errors + max_opts_input_err
                            else:
                                errors = max_opts_input_err

                            valid = False
                            poll_valid = False
                except ValueError:
                    errors = "The minimum option selection input is not valid."
                    valid = False
                    poll_valid = False

            poll_errors_json['min_max'] = {'val': errors}

            # Store the errors as part of the JSON rep of the poll
            poll_json['errors'] = {'val': poll_errors_json}

            # Add the poll rep to the list of polls
            polls_json.append(poll_json)

            # If the validation for the poll has failed, add it to the error summary
            if not poll_valid:
                errors_summary = errors_summary + str(i + 1) + " "

        self.invalid_form_fields['polls_data'] = {'val': polls_json}
        self.invalid_form_fields['poll_count'] = {'val': poll_count}

        if not valid and len(errors_summary) > 34:
            errors_summary = errors_summary + "and can be corrected by editing them."
            self.invalid_form_fields['polls_errors'] = {'error': errors_summary}

        return valid

    def __areOrganisersEmailsValid(self, demo_users):
        valid = True

        # Create a list of emails from the demo users
        emails = []
        for user in demo_users:
            emails.append(user.email)

        # Check that the list of organiser emails are actually valid
        organisers_list_input = self.form_data_validation.pop('organiser-email-input')
        organisers_list = []
        blank_count = 0
        error = "The following email(s) supplied are not organisers: "

        for organiser in organisers_list_input:
            if organiser != '':
                organisers_list.append(organiser)

                if organiser not in emails:
                    error = error + organiser + " "
                    valid = False
            else:
                blank_count += 1

        if blank_count > 1:
            if not valid:
                error = error + " and there are " + str(blank_count - 1) + " blank organiser inputs."
            else:
                error = "There are " + str(blank_count - 1) + " blank organiser inputs."
                valid = False

            # This adds in blank organisers so that the template can render them for the user to fix
            for i in range(blank_count - 1):
                organisers_list.append("")

        if not valid:
            self.invalid_form_fields['organiser_emails'] = {'error': error}

        self.invalid_form_fields['organiser_emails_data'] = {'val': organisers_list}

        return valid

    def __areTrusteeEmailsValid(self):
        valid = True

        # Check that the list of trustees is valid
        trustees_list_input = self.form_data_validation.pop('trustee-email-input')
        trustees_list = []
        error = "The following email(s) supplied are not valid: "
        blank_count = 0

        for trustee in trustees_list_input:
            if trustee != '':
                trustees_list.append(trustee)
                match = re.match(r'[^\s@]+@[^\s@]+\.[^\s@]+', trustee)

                if match is None:
                    error = error + trustee + " "
                    valid = False
            else:
                blank_count += 1

        if blank_count > 1:
            if not valid:
                error = error + " and there are " + str(blank_count - 1) + " blank trustee inputs."
            else:
                error = "There are " + str(blank_count - 1) + " blank trustee inputs."
                valid = False

            # This adds in blank trustees so that the template can render them for the user to fix
            for i in range(blank_count - 1):
                trustees_list.append("")

        if not valid:
            self.invalid_form_fields['trustee_emails'] = {'error': error}

        self.invalid_form_fields['trustee_emails_data'] = {'val': trustees_list}

        return valid

    def __isVotersListValid(self):
        valid = True

        # Check that the list of voters is valid
        voters_csv_string = self.form_data_validation.pop('voters-list-input')[0].replace(' ', '')

        if voters_csv_string == '':
            self.invalid_form_fields['voters_emails'] = {'error': 'The voters list is blank.'}
            self.invalid_form_fields['voters_emails_data'] = {'val': voters_csv_string}
            return False

        voters_email_list = voters_csv_string.split(',')
        error = "The following email(s) supplied are not valid: "

        for voter_email in voters_email_list:
            if voter_email != '' and re.match(r'[^\s@]+@[^\s@]+\.[^\s@]+', voter_email) is None:
                error = error + voter_email + " "
                valid = False

        if not valid:
            self.invalid_form_fields['voters_emails'] = {'error': error}

        self.invalid_form_fields['voters_emails_data'] = {'val': voters_csv_string}

        return valid

    def getInvalidFormFields(self):
        return self.invalid_form_fields

    def extractData(self):
        # Extract name and identifier first
        self.event_name = self.form_data.pop('name-input')[0]
        self.identifier = self.form_data.pop('identifier-input')[0]

        # Extract start and end times as string and convert to datetime
        # The UTC offset comes with a colon i.e. '+01:00' which needs to be removed
        starts_at = self.form_data.pop('vote-start-input')[0]
        starts_at_offset_index = starts_at.find('+')

        if starts_at_offset_index != -1:
            # timezone data has been supplied so use parse_datetime from django
            starts_at_time = starts_at[0: starts_at_offset_index-1].replace(' ', 'T')
            starts_at_offset = starts_at[starts_at_offset_index:].replace(':', '')
            starts_at = starts_at_time + starts_at_offset
            self.starts_at = parse_datetime(starts_at)
        else:
            # No Timezone data has been supplied so use strptime instead
            self.starts_at = datetime.strptime(starts_at, '%Y-%m-%d %H:%M')

        ends_at = self.form_data.pop('vote-end-input')[0]
        ends_at_offset_index = ends_at.find('+')

        if ends_at_offset_index != -1:
            # timezone data has been supplied so use parse_datetime from django
            ends_at_time = ends_at[0:ends_at_offset_index-1].replace(' ', 'T')
            ends_at_offset = ends_at[ends_at_offset_index:].replace(':', '')
            ends_at = ends_at_time + ends_at_offset
            self.ends_at = parse_datetime(ends_at)
        else:
            # No Timezone data has been supplied so use strptime instead
            self.ends_at = datetime.strptime(ends_at, '%Y-%m-%d %H:%M')

        # Extract the list of organisers
        organisers_list = self.form_data.pop('organiser-email-input')

        for organiser in organisers_list:
            if organiser != '' and DemoUser.objects.filter(email=organiser).exists():
                self.organisers.append(DemoUser.objects.filter(email=organiser).get())

        # Extract the list of trustees
        trustees_list = self.form_data.pop('trustee-email-input')

        for trustee_email in trustees_list:
            if trustee_email != '':
                self.trustees.append(trustee_email)

        # Extract the email list of voters
        voters_csv_string = self.form_data.pop('voters-list-input')[0].replace(' ', '')
        voters_email_list = voters_csv_string.split(',')

        for voter_email in voters_email_list:
            if voter_email != '':
                self.voters.append(voter_email)

        # Create the Event model object - this does not persist it to the DB
        creator = ""
        if self.user.first_name is not None:
            creator += self.user.first_name + " "

        if self.user.last_name is not None:
            creator += self.user.last_name

        self.event = Event(start_time=self.starts_at,
                           end_time=self.ends_at,
                           title=self.event_name,
                           EID=self.identifier,
                           creator=creator,
                           c_email=self.user.email,
                           trustees=voters_csv_string)

    def __gen_polls_options_map(self):
        # Get the poll count (the number of poll and options that have been defined)
        poll_count = int(self.form_data.pop('poll-count-input')[0])

        for i in range(poll_count):
            # String version of i
            i_str = str(i)

            # Generate PollOption objects from the option data defined in form_data
            options = self.form_data.pop('option-name-input-' + i_str)
            poll_options_list = []
            votes = 0

            for option in options:
                if option != '':
                    poll_options_list.append(PollOption(choice_text=option, votes=votes))

            # Extract required Poll object data and create a poll with its PollOption objects
            text = self.form_data.pop('question-name-input-' + i_str)[0]
            min_num_selections = int(self.form_data.pop('minimum-input-' + i_str)[0])
            max_num_selections = int(self.form_data.pop('maximum-input-' + i_str)[0])

            poll = Poll(question_text=text,
                        total_votes=votes,
                        min_num_selections=min_num_selections,
                        max_num_selections=max_num_selections,
                        event=self.event)

            self.polls_options_map.append([poll, poll_options_list])

    # Instantiate all the polls and their associated poll options
    def __get_instantiated_polls(self):
        polls = []
        for poll_option_map in self.polls_options_map:
            poll = poll_option_map[0]
            poll_options = poll_option_map[1]

            # Save the poll to the db
            poll.save()

            # Instantiate poll options
            for option in poll_options:
                option.question = poll
                option.save()

            poll.options = poll_options
            poll.save()

            polls.append(poll)

        return polls

    def updateModel(self):
        # First thing to do is persist the event object to the db
        # with basic data before adding things like poll data
        self.event.save()

        # List of organisers should already be instantiated and present in the db
        # so it can just be added
        self.event.users_organisers = self.organisers

        # Add the list of trustees to the event
        db_trustees = list()
        for trustee in self.trustees:
            user, created = EmailUser.objects.get_or_create(email=trustee)
            db_trustees.append(user)

        self.event.users_trustees = db_trustees

        # Add the list of voters to the event
        db_voters = list()
        for voter in self.voters:
            user, created = EmailUser.objects.get_or_create(email=voter)
            db_voters.append(user)

        self.event.voters = db_voters

        # Extract all the poll data for the event and associated poll option data
        # This can only be done at this point as the event has been persisted
        self.__gen_polls_options_map()

        # Get the instantiated list of polls which have already instantiated options
        self.event.polls = self.__get_instantiated_polls()

        self.event.save()

        # Finally return a reference to the event
        return self.event

    def clear_data(self):
        self.form_data = None
        self.form_data_validation = None
        self.invalid_form_fields = {}
        self.validation_starts_at = None
        self.validation_ends_at = None
        self.user = None
        self.event_name = None
        self.identifier = None
        self.starts_at = None
        self.ends_at = None
        self.organisers[:] = []
        self.trustees[:] = []
        self.voters[:] = []
        self.polls_options_map[:] = []
        self.event = None


# -*- coding: utf-8 -*-
"""
Created on Tue Oct 15 13:05:02 2019

@author: paulo
"""

#DATA AUGMENTATION
import os
import cv2
import random
import numpy as np
import matplotlib.pyplot as plt
from keras.preprocessing.image import ImageDataGenerator
import os
from PIL import Image
from skimage.color import rgb2gray

from keras.models import load_model
import tensorflow as tf
tf.compat.v1.disable_eager_execution()
from keras.layers import InputLayer
from keras.models import Sequential
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
#import cv2
from keras import layers
from keras import models

from keras.layers import Flatten
from keras.layers import Dense

from keras.preprocessing.image import ImageDataGenerator
from keras.callbacks import ModelCheckpoint
from keras.models import Sequential

from keras.applications.vgg16 import VGG16

from vis.losses import ActivationMaximization
from vis.regularizers import TotalVariation, LPNorm
from vis.input_modifiers import Jitter
from vis.optimizer import Optimizer
from vis.callbacks import GifGenerator

import cv2

from vis.losses import ActivationMaximization
from vis.regularizers import TotalVariation, LPNorm
from vis.input_modifiers import Jitter
from vis.optimizer import Optimizer
from vis.callbacks import GifGenerator

from vis.utils import utils
#from keras import activations
from keras.preprocessing import image
import keras
from keras.layers import Dropout
from keras import backend as K
from keras import regularizers
from mpl_toolkits.axes_grid1 import make_axes_locatable
import os

from keras.applications.vgg16 import preprocess_input
from keras.applications.vgg16 import (VGG16, preprocess_input, decode_predictions)
from keras.preprocessing import image

#identifies what is the average size of the pillars images
def image_size(SERS,NONSERS):

    #get the files inside the folders
    SERS_train = os.listdir(SERS)
    NONSERS_train = os.listdir(NONSERS)

    all_dir = [SERS, NONSERS]
    all_data = [SERS_train, NONSERS_train]

    shape = []

    for dire,file in zip(all_dir, all_data):

        folder = dire.split('/')[-2]+'/'
        for f in file:
            ima = cv2.imread(dire+f)
            shape.append([ima.shape[0],ima.shape[1]])

    return shape

from PIL import Image
#breaks the images in parts shaped like shape vector [x,y]
def break_image(input_frames,shape):

    if len(shape) == 2:

        height = int(shape[0])
        width = int(shape[1])
        size =  cv2.imread(input_frames).shape[0:2]

        division = [size[0]//height,size[1]//width]

    else:

        max_shape = np.max(shape,axis=0)
        min_shape = np.min(shape,axis=0)

        size =  cv2.imread(input_frames).shape[0:2]

        division = np.mean(size)//np.mean([max_shape,min_shape])

        shape_vec = np.mean([max_shape,min_shape],axis=0)

        height = int(shape_vec[0])
        width = int(shape_vec[1])

    im = Image.open(input_frames)
    imgwidth, imgheight = im.size
    test_image = []

    for i in range(0,imgheight,height):
        for j in range(0,imgwidth,width):
            box = (j, i, j+width, i+height)
            a = im.crop(box)
            test_image.append(a)

    return test_image,division

# image = cap[0]
# ADD ALEX SECTION HERE

#load the pillar identification model

def generate_regionof_interest(image_identifier,test_image,label):

    model = Sequential()
    for layer in image_identifier.layers:
        model.add(layer)

    for layer in model.layers:
        layer.trainable = False

    labels = ['pillar', 'nopillar']
    from keras.applications.vgg16 import preprocess_input
    from keras.applications.vgg16 import (VGG16, preprocess_input, decode_predictions)

    heatmaps = []
    from keras.preprocessing import image

    location = []
    #get the activations from the test image broken in pieces

    for cnt,f in enumerate(test_image):
        img = f.convert('RGB').resize((model.layers[0].layers[0].output.shape[1]
                                       ,model.layers[0].layers[0].output.shape[2]),
                                      Image.ANTIALIAS)

        x = image.img_to_array(img)
        x = np.expand_dims(x, axis=0)

        preds = model.predict(x)
        if preds[0][1]>0.5:
            location.append(1)
#            plt.figure()
#            plt.imshow(img)
#            plt.savefig(r'C:\Users\paulo\OneDrive - University of Birmingham\Desktop\birmingham_02\MEng student\3rd year\report\code\files to transfer\gen\\'+label+str(cnt)+'.png', dpi = 300,bbox_inches="tight")
#            plt.close()
        else:
            location.append(0)

        num = np.argmax(preds)

        img_output = model.layers[0].layers[-2].output[:,num]
        last_conv_layer = model.layers[0].get_layer('block5_conv3')

        grads = K.gradients(img_output, last_conv_layer.output)[0]
        pooled_grads = K.mean(grads, axis= (0,1,2))
        #    pooled_grads = K.mean(grads, axis= (0,1,2))

        iterate = K.function([ model.layers[0].layers[0].input],
                              [pooled_grads, last_conv_layer.output[0]])

        pooled_grads_value , conv_layer_output_value = iterate([x])

        for j in range(512):
            conv_layer_output_value[:,:,j] *= pooled_grads_value[j]

        heatmap = np.mean(conv_layer_output_value , axis=-1)
        heatmap = np.maximum(heatmap,0)
        heatmap /= np.max(heatmap)

        img = f.convert('RGB').resize((f.size[0],f.size[1]), Image.ANTIALIAS)

        heatmap = cv2.resize(heatmap, (img.size[1],img.size[0]))
        heatmap = np.uint8(255 * heatmap)
        heatmap = cv2.applyColorMap(heatmap, cv2.COLORMAP_HOT)
        heatmap = cv2.cvtColor(heatmap, cv2.COLOR_BGR2RGB)

        if max(max(img.getextrema()))==0:
            img *= np.uint8(255.0/(0.000001+max(max(img.getextrema()))))
        else:
            img *= np.uint8(255.0/max(max(img.getextrema())))

        blend = cv2.addWeighted(img,0.5, heatmap,0.5, 0)
        heatmaps.append(blend)

    return heatmaps,location

import pandas as pd

def plot_results(test,heatmaps,location,division):

    #plot the test image with heatmaps

    w,h,c=heatmaps[0].shape
    iteray = int(division[0])
    iterax = int(division[1])
    final_heath = np.zeros(shape=(w*iteray,h*iterax,c))

    for i in range(iteray):
        for j in range(iterax):
            final_heath[i*w:(i+1)*w,j*h:(j+1)*h,:] = heatmaps[i*iterax+j]*location[i*iterax+j]

    # final_heat = final_heat//final_heat.max()
#    plt.figure()
#    plt.imshow(final_heath/255)

    #perfect the mask into a square, filling up all previous area

    mask = pd.DataFrame(np.array(location).reshape(iteray,iterax))
    locx = []
    locy = []

    for i in range(mask.shape[0]):
        for j in range(mask.shape[1]):
            if mask.iloc[i,j] == 1:
                locx.append(i)
                locy.append(j)

    mask_fill = pd.DataFrame(np.zeros((iteray,iterax)))

    for i in range(mask.shape[0]):
        for j in range(mask.shape[1]):
            if (i>np.min(locx)-1 and i<np.max(locx)+1) and (j>np.min(locy)-1 and j<np.max(locy)+1):
                mask_fill.iloc[i,j] = 1

    #display the test image in the previous square shape

    w,h,c=heatmaps[0].shape
    iteray = int(division[0])
    iterax = int(division[1])
    final_heat = np.zeros(shape=(w*iteray,h*iterax,c))

    for i in range(iteray):
        for j in range(iterax):

            fimg = test_image[i*iterax+j].convert('RGB').resize((w,h), Image.ANTIALIAS)

            if max(max(fimg.getextrema()))==0:
                fimg *= np.uint8(255.0/(0.000001+max(max(fimg.getextrema()))))
            else:
                fimg *= np.uint8(255.0/max(max(fimg.getextrema())))

            final_heat[i*w:(i+1)*w,j*h:(j+1)*h,:] = fimg*mask_fill.stack().tolist()[i*iterax+j]

    # final_heat = final_heat//final_heat.max()
#    plt.figure()
#    plt.imshow(final_heat/255)

#    plt.figure()
#    plt.imshow(test)

    return final_heat

def output_cropped_interest_area(final_heat):

    final_test = np.uint8(final_heat)

    gray = cv2.cvtColor(final_test,cv2.COLOR_BGR2GRAY)
    _,thresh = cv2.threshold(gray,1,255,cv2.THRESH_BINARY)

    contours,hierarchy = cv2.findContours(thresh,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
    cnt = contours[0]
    x,y,w,h = cv2.boundingRect(cnt)

    crop = final_test[y:y+h,x:x+w]

#    plt.figure()
#    plt.imshow(crop)

    return crop

import PIL
import math

def break_image1(input_frames,shape):

    if len(shape) == 2:

      height = int(shape[0])
      width = int(shape[1])
      size =  input_frames.shape[0:2]

      division = [math.ceil(size[0]/height),math.ceil(size[1]/width)]

    else:

        max_shape = np.max(shape,axis=0)
        min_shape = np.min(shape,axis=0)

        size = input_frames.shape[0:2]

        division = np.mean(size)//np.mean([max_shape,min_shape])

        shape_vec = np.mean([max_shape,min_shape],axis=0)

        height = int(shape_vec[0])
        width = int(shape_vec[1])

    im = PIL.Image.fromarray(input_frames)
    imgwidth, imgheight = im.size
    test_image = []

    for i in range(0,imgheight,height):
        for j in range(0,imgwidth,width):
            box = (j, i, j+width, i+height)
            a = im.crop(box)
            test_image.append(a)
            mask = np.zeros((imgheight,imgwidth,3))
            mask[i:i+height,j:j+width,:]=1

    return test_image,division

def break_image2(input_frames,shape):

    if len(shape) == 2:

      height = int(shape[0])
      width = int(shape[1])
      size =  input_frames.shape[0:2]

      division = [math.ceil(size[0]/height),math.ceil(size[1]/width)]

    else:

        max_shape = np.max(shape,axis=0)
        min_shape = np.min(shape,axis=0)

        size = input_frames.shape[0:2]

        division = np.mean(size)//np.mean([max_shape,min_shape])

        shape_vec = np.mean([max_shape,min_shape],axis=0)

        height = int(shape_vec[0])
        width = int(shape_vec[1])

    im = PIL.Image.fromarray(input_frames)
    imgwidth, imgheight = im.size
    test_image = []
    masked = []

    for i in range(0,imgheight,height):
        for j in range(0,imgwidth,width):
            box = (j, i, j+width, i+height)
            a = im.crop(box)
            test_image.append(a)
            mask = np.zeros((imgheight,imgwidth,3))
            mask[i:i+height,j:j+width,:]=1
            masked.append(mask)

    return test_image,division,masked

#in the break image code need to add part to get the mask for the location of the breaking
#place where the box is is 1 otherwhise 0

def plot_results2(test,heatmaps,division,location):

  #plot the test image with heatmaps

    w,h,c = heatmaps[0].shape
    iteray = int(division[0])
    iterax = int(division[1])
    final_heath = np.zeros(shape=(w*iteray,h*iterax,c))

    for i in range(iteray):
        for j in range(iterax):
            final_heath[i*w:(i+1)*w,j*h:(j+1)*h,:] = heatmaps[i*iterax+j]*location[i*iterax+j]

    # final_heat = final_heat//final_heat.max()
#    plt.figure()
#    plt.imshow(final_heath/255)

#    plt.figure()
#    plt.imshow(test)

    return final_heat

from PIL import ImageDraw,Image

def buildup1(final,masked2,cropped):

    buraco = []
    for f,m in zip(final,masked2):

           #generate a patch to put the masks times the blend image
        patch = np.zeros((cropped.shape[0],cropped.shape[1],cropped.shape[2]))

        #generate a image with all the mask holes
        if not isinstance(f,int):
            continue
        else:
            buraco.append(m)

    buraco2 = sum(buraco)

    patch = np.zeros((cropped.shape[0],cropped.shape[1],cropped.shape[2]))

    for f,m in zip(final,masked2):
        if not isinstance(f,int):

            #find the mask first position
            xx = [np.min(np.where(m==1)[0]),np.max(np.where(m==1)[0])]
            yy = [np.min(np.where(m==1)[1]),np.max(np.where(m==1)[1])]

    #        buraco2[xx[0]:xx[1]+1,yy[0]:yy[1]+1,:]=fscale/255.0
            patch[xx[0]:xx[1]+1,yy[0]:yy[1]+1,:]=f[0:abs(xx[0]-xx[1]-1),0:abs(yy[0]-yy[1]-1),:]

    croppedclass = buraco2*cropped/255.0+patch

    return croppedclass

def buildup2(frame,final_heat,croppedclass):

    i_img = frame

    buraco = final_heat/255.0

    image_hole = np.array([1. if a_ >0 else 0. for a_ in buraco.ravel()]).reshape(buraco.shape[0], buraco.shape[1], buraco.shape[2])

    xx = [np.min(np.where(image_hole==1)[0]),np.max(np.where(image_hole==1)[0])]
    yy = [np.min(np.where(image_hole==1)[1]),np.max(np.where(image_hole==1)[1])]

    image_hole[xx[0]:xx[1]+1,yy[0]:yy[1]+1,:] = 1.

    patch = np.zeros((i_img.shape[0],i_img.shape[1],i_img.shape[2]))

    #find the mask first position
    xx = [np.min(np.where(image_hole==1)[0]),np.max(np.where(image_hole==1)[0])]
    yy = [np.min(np.where(image_hole==1)[1]),np.max(np.where(image_hole==1)[1])]

    #        buraco2[xx[0]:xx[1]+1,yy[0]:yy[1]+1,:]=fscale/255.0
    patch[xx[0]:xx[1]+1,yy[0]:yy[1]+1,:]=croppedclass

    f_img = (-1*(image_hole[0:i_img.shape[0],0:i_img.shape[1],:]-1)*i_img)/255.0

    croppedimage = patch+f_img

    return croppedimage

#subsection2(test_image2,sers_identifier,'sers_notsers',image_identifier)

def subsection2(test_image2,label,image_identifier):

    finalimg = []
    model = Sequential()
    for layer in image_identifier.layers:
        model.add(layer)

    for layer in model.layers:
        layer.trainable = False

#    labels = ['No Enhancement', 'SERS']

    for f in test_image2:
        sectionit = []
        mask = []
        size =  list(image_identifier.layers[0].layers[0].output.shape[1:3])
        img = f.convert('RGB').resize((size[0] ,size[1]), Image.ANTIALIAS)

        open_cv_image = np.array(img)
        open_cv_image = open_cv_image[:, :, ::-1].copy()
        img_gray = cv2.cvtColor(open_cv_image, cv2.COLOR_BGR2GRAY)

        im_gauss = cv2.GaussianBlur(img_gray , (5, 5), 0)
        ret, thresh = cv2.threshold(im_gauss, 127, 255, 0)
        # get contours
        contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

        contours_cirles = []
        contours_area = []
        # calculate area and filter into new array
        comp = size[0]//10
        for con in contours:
            if any(con.ravel()<comp) or any(con.ravel()>size[0]-comp):
                continue
            area = cv2.contourArea(con)
#            print(area)
            if 5 < area < 1000:
                contours_area.append(con)

        # check if contour is of circular shape
        for con in contours_area:
            perimeter = cv2.arcLength(con, True)
            area = cv2.contourArea(con)
            if perimeter == 0:
                break
            circularity = 4*math.pi*(area/(perimeter*perimeter))
#            print(circularity)
            if 0.7 < circularity < 1.2:
                contours_cirles.append(con)

        for con in contours_cirles:
    #        cv.drawContours(open_cv_image,con,-1,(0,255,0),1)
            xm = con.ravel()[::2].min()-comp
            xM = con.ravel()[::2].max()+comp
            ym = con.ravel()[1::2].min()-comp
            yM = con.ravel()[1::2].max()+comp

            sectionit.append(open_cv_image[ym:yM,xm:xM,:])
            zeromask = np.zeros((img_gray.shape[0],img_gray.shape[1],3))
            zeromask[ym:yM,xm:xM,:]=np.ones((abs(ym-yM),abs(xm-xM),3))
            mask.append(zeromask)

        if sectionit == []:
            finalimg.append(0)
        else:

#            fig, axs = plt.subplots(int(np.sqrt(len(sectionit)))+1,int(np.sqrt(len(sectionit)))+1)
#            fig2, ax = plt.subplots()
#            ax.imshow(f)
#            axes = axs.ravel()

            heatmaps = []
            #get the activations from the test image broken in pieces
            location = []

            for cnt,sel in enumerate(sectionit):
#                axes[cnt].imshow(sel)

                sel2 = cv2.resize(sel,(model.layers[0].layers[0].output.shape[1]
                               ,model.layers[0].layers[0].output.shape[2]),
                                interpolation = cv2.INTER_AREA)

                x = image.img_to_array(sel2)
                x = np.expand_dims(x, axis=0)

                preds = model.predict(x)

                num = np.argmax(preds)

#                axes[cnt].imshow(sel2)
#                axes[cnt].text(0,0,labels[num])

                if preds[0][1]>0.5:
                    location.append(1)

                    img_output = model.layers[0].layers[-2].output[:,num]
                    last_conv_layer = model.layers[0].get_layer('block5_conv3')

                    grads = K.gradients(img_output, last_conv_layer.output)[0]
                    pooled_grads = K.mean(grads, axis= (0,1,2))
                    #    pooled_grads = K.mean(grads, axis= (0,1,2))

                    iterate = K.function([ model.layers[0].layers[0].input],
                                          [pooled_grads, last_conv_layer.output[0]])

                    pooled_grads_value , conv_layer_output_value = iterate([x])

                    for j in range(512):
                        conv_layer_output_value[:,:,j] *= pooled_grads_value[j]

                    heatmap = np.mean(conv_layer_output_value , axis=-1)
                    heatmap = np.maximum(heatmap,0)
                    heatmap /= np.max(heatmap)

                    heatmap = np.array([0 if a_ < 0.8 else a_ for a_ in heatmap.ravel()]).reshape(heatmap.shape[0],
                                                                                heatmap.shape[1])

                    #blend the heatmap with the image but ignore values below 0

                    img = sel2

                    heatmap = cv2.resize(heatmap, (img.shape[1],img.shape[0]))
                    marker = heatmap

                    heatmap[heatmap!=0] = heatmap[heatmap!=0]*0.2+0.8

                    #preform a proper scalling for the heatmap colour

                    heatmap = np.uint8(255 * heatmap)
                    heatmap = cv2.applyColorMap(heatmap, cv2.COLORMAP_JET)
                    heatmap = cv2.cvtColor(heatmap, cv2.COLOR_BGR2RGB)

                    if img.max()==0:
                        img *= np.uint8(255.0/(0.000001+(img.max())))
                    else:
                        img *= np.uint8(255.0/img.max())

                    blend = np.zeros((img.shape[0],img.shape[1],3))

                    for n in range(blend.shape[0]):
                        for k in range(blend.shape[1]):
                            if marker[n,k]!=0:
                                blend[n,k,:] = np.transpose(cv2.addWeighted(img[n,k,:],0.5,heatmap[n,k,:],0.5,0.0))[0]
                            else:
                                blend[n,k,:] = img[n,k,:]

                    blend = np.uint8(blend)

                    heatmaps.append(blend)

#                    axes[cnt].imshow(blend)

                else:
                    location.append(0)
                    heatmaps.append(0)

            #generate a patch to put the masks times the blend image
            patch = np.zeros((mask[0].shape[0],mask[0].shape[1],mask[0].shape[2]))

            #generate a image with all the mask holes
            image_hole = np.array([0 if a_ < 1 else 1 for a_ in sum(mask).ravel()]).reshape(mask[0].shape[0],
                                                                                mask[0].shape[1],
                                                                                mask[0].shape[2])
            for m,h,z in zip(mask,heatmaps,sectionit):
                if not isinstance(h,int):
                    #resize the heatmap to its real heatmap size
                    hscale = cv2.resize(h,(z.shape[1],z.shape[0]))

                    #find the mask first position
                    xx = [np.min(np.where(m==1)[0]),np.max(np.where(m==1)[0])]
                    yy = [np.min(np.where(m==1)[1]),np.max(np.where(m==1)[1])]

                    patch[xx[0]:xx[1]+1,yy[0]:yy[1]+1,:]=hscale/255.0
                else:
                    xx = [np.min(np.where(m==1)[0]),np.max(np.where(m==1)[0])]
                    yy = [np.min(np.where(m==1)[1]),np.max(np.where(m==1)[1])]

                    patch[xx[0]:xx[1]+1,yy[0]:yy[1]+1,:]=z/255.0

            #final image is the initial image opened with all the holes from the masks and added patches with the sers
            #non sers model
            final_img = open_cv_image*(-1*(image_hole-1)/255.0)+patch

#            plt.figure()
#            plt.imshow(final_img)
            finalimg.append(final_img)

                        #fill the hole with the blend
    return finalimg

import numpy as np
import cv2
import matplotlib.pyplot as plt

import sys
from scipy import ndimage as ndi
from skimage.util import random_noise
from skimage import feature
from skimage.color import rgb2gray

from tensorflow.keras.preprocessing.image import img_to_array
from tensorflow.keras.models import load_model

import cv2
import os.path
from tqdm import tqdm
import matplotlib.pyplot as plt

import numpy as np
import tensorflow as tf
from tensorflow import keras

#FINAL MANUAL CROPPED

video_path = "pillar image irl.mkv"

# MIX MIX MIX MIX this one plot heatmaps correctly with correct crop

#counts = [26,29,53,60+14,60+42,60*2+7,60*2+30,60*2+58,60*3+27]
counts = [60*2+58,60*3+27]
counts = [a*60 for a in counts]

import cv2

cap = cv2.VideoCapture(video_path)
count = 0

while cap.isOpened():
    ret, frame = cap.read()

    if ret:
        for i,count in enumerate(counts):
            print(count)
            print(i)

            if i == len(counts)-1:
                cap.release()
                break

            try:
                ret, frame = cap.read()
                cap.set(1, count)

                final_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
                frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)

                loculx = 64
                loculy = 230
                locdrx = 818
                locdry = 712

                factorx = final_frame.shape[1]/loculx
                factorxx = final_frame.shape[1]/locdrx

                factory = final_frame.shape[0]/loculy
                factoryy = final_frame.shape[0]/locdry

                y = int(final_frame.shape[1]/factorx)
                yy = int(final_frame.shape[1]/factorxx)
                x = int(final_frame.shape[0]/factory)
                xx = int(final_frame.shape[0]/factoryy)

                cropped = final_frame[x:xx,y:yy,:]

                size2 = [50,50]

	            # newcropped = cropped[:,1000:,:]

                test_image2,division2,masked2 = break_image2(cropped,size2)

                sers_identifier = load_model('SERS_NOSERS_pillars_v08.h5')

                #previous version wasnt using the sers not sers model, but the pillar not pillar model...

                final = subsection2(test_image2,'sers_notsers',sers_identifier)

                croppedclass = buildup1(final,masked2,cropped)

                f_frame = np.zeros((final_frame.shape[0],final_frame.shape[1],final_frame.shape[2]))
                f_frame[x:xx,y:yy,:]=1

                croppedimage =  buildup2(final_frame,f_frame,croppedclass)
                plt.figure()
                plt.imshow(croppedimage)
                plt.axis('off')
#                plt.savefig(r'C:\Users\paulo\OneDrive - University of Birmingham\Desktop\birmingham_02\MEng student\3rd year\report\code\files to transfer\final images\\'+'final_image_v01_'+str(count)+'.png', dpi = 300,bbox_inches="tight")
#                plt.close()

            except:
                pass
    else:
        cap.release()
        break


import matplotlib.pyplot as plt
import numpy as np
from scipy.interpolate import interp1d
from scipy.interpolate import CubicSpline as cs
from numba import jit
import warnings
import cProfile
import pyopencl as cl

warnings.simplefilter('ignore', np.RankWarning)

def load(directory):
    data = np.loadtxt(directory, skiprows=1)
    x = directory.split('_')
    y = x[-1].split('x')
    z = np.zeros(len(data[0]))
    z[0] = int(y[0])
    data = np.vstack((data, z))
    return data, z[0]

def NK():
    T = np.arange(0.0, 2.0, 0.01)
    rho = 2 * T / np.pi
    plt.plot(T, rho)

@jit
def xy(data):
    x = []
    y = []
    for i in data[:-1]:
        x.append(i[0])
        y.append(i[1])
    return x, y

def interpolate(x, y, Tbkt, end):
    f = np.poly1d(np.polyfit(x, y, 5))
    xfit = np.linspace(Tbkt, end, 1000)
    yfit = f(xfit)
    return xfit, yfit

@jit
def reduce(upsilon, T, x, y):
    ups = []
    newT = []
    newX = []
    newY = []
    for i in range(len(T)):
        if upsilon[i] > 0:
            ups.append(upsilon[i])
            newT.append(T[i])
            newX.append(x[i + 1])
            newY.append(y[i + 1])
    return ups, newT, newX, newY

def plot(data):
    x, y = xy(data)
    x = x[3:]
    y = y[3:]
    plt.plot(x, y, '--')
    return x, y

@jit
def ups(L, C, c, Tbkt, Tarray):
    Upsilon = []
    Temp = []
    for T in Tarray:
        if (T - Tbkt > 0):

            Ups = (2 / np.pi) * (
                    T / (1 - 0.5 * np.sqrt(c * (T - Tbkt)) / np.tan((np.log(L) + C) * np.sqrt(c * (T - Tbkt)))))
            Temp.append(T)
            Upsilon.append(Ups)

            if (Ups < 0):
                return Upsilon, Temp
    return Upsilon, Temp

@jit
def leastSquare(x, y, ups):
    lsq = 0;
    for i in range(len(x)):
        res = y[i] - ups[i]
        sqr = np.square(res)
        lsq += sqr
    lsq = lsq / len(x)
    return lsq

@jit
def minimize(x, y, L):
    # Arbitrarily pick constants
    print(L)
    Tbkt = 0.8935
    end = 1.5
    Tarray = np.linspace(Tbkt, end, 1000)

    X, Y = interpolate(x, y, Tbkt, end)

    # loop over different constants
    Carray = np.linspace(0.0002, 0.1, 100)
    carray = np.linspace(0.1, 5, 100)

    minlsq = np.inf
    minups = []
    minY = []
    minX = []
    minc = 0
    minC = 0
    for C in Carray:
        for c in carray:
            upsilon = []
            T = []
            X, Y = interpolate(x, y, Tbkt, end)
            # work out Upsilon and T
            upsilon, T = ups(L, C, c, Tbkt, Tarray)

            # Reduce to arrays of same length
            upsilon, T, X, Y = reduce(upsilon, T, X, Y)
            lsq = leastSquare(X, Y, upsilon)
            if (lsq < minlsq):
                minlsq = lsq
                minups = upsilon
                minY = Y
                minX = X
                minC = C
                minc = c
    print(minC)
    print(minc)
    print(minups)
    return minups, minX, minX, minY

def arbitrary(x, y, L):
    # Arbitrarily pick constants
    Tbkt = 0.8935
    end = 1.5
    Tarray = np.linspace(Tbkt, end, 1000)

    # loop over different constants
    C = 0.01
    c = 5
    X, Y = interpolate(x, y, Tbkt, end)
    # work out Upsilon and T
    upsilon, T = ups(L, C, c, Tbkt, Tarray)

    # Reduce to arrays of same length
    upsilon, T, X, Y = reduce(upsilon, T, X, Y)
    lsq = leastSquare(X, Y, upsilon)
    print(lsq)
    return upsilon, T, X, Y

def minFit(directory):
    data, L = load(directory)
    x, y = plot(data)

    # Draw Nelson-Kosterlitz line
    NK()

    upsilon, T, x, y = minimize(x, y, L)
    # upsilon, T, x, y = arbitrary(x, y)

    plt.plot(T, upsilon)
    plt.plot(x, y)

    plt.grid(True)
    plt.show()

def fit(directory):
    data, L = load(directory)
    x, y = plot(data)

    # Draw Nelson-Kosterlitz line
    NK()

    upsilon, T, x, y = arbitrary(x, y, L)
    # upsilon, T, x, y = arbitrary(x, y)

    plt.plot(T, upsilon,'-')
    plt.plot(x, y)

    plt.grid(True)
    plt.show()

'''
##checking differences between 1 run and 2 run datasets
data3 = load("/home/tejan/Desktop/ECMCData/Run5/topology_ECMC_XY_032x032.dat")
data2 = load("/home/tejan/Desktop/ECMCData/Run32-Part2/topology_ECMC_XY_0032x0032.dat")
data1 = load("/home/tejan/Desktop/ECMCData/Run32-Part1/topology_ECMC_XY_0032x0032.dat")

plot(data1)
plot(data2)
plot(data3)

Tbkt = 0.8935
end = 1.5
L = 32
C = 1
c = 3
Tarray = np.linspace(Tbkt, end, 1000)
upsilon, T = ups(L, C, c, Tbkt, Tarray)

plt.plot(T, upsilon)
NK()
plt.grid(True)
plt.show()
'''

##Staring fitting procedure
# load and plot data L=64

#fit("/home/tejan/Desktop/ECMCData/TransitionRegion/256/topology_ECMC_XY_0256x0256.dat")
#minFit("/home/tejan/Desktop/ECMCData/TransitionRegion/256/topology_ECMC_XY_256x256.dat")
fit("/home/tejan/Desktop/ECMCData/TransitionRegion/16/topology_ECMC_XY_016x016.dat")
#minFit("/home/tejan/Desktop/ECMCData/TransitionRegion/16/topology_ECMC_XY_016x016.dat")


import marshal
import sys
import os
import dis
import tempfile

def get_code(file):
    absfilename = os.path.abspath(file)
    fle = open(absfilename, 'rb')
    if sys.version_info.minor >= 7:
        ret = fle.read()[16:]
    else:
        ret = fle.read()[12:]
    fle.close()
    return marshal.loads(ret)

def get_dis(code):
    bts = bytes()
    fle = open('temp', 'w+')
    dis.dis(code, file=fle)
    fle.close()
    fle = open('temp', 'r')
    content = fle.read()
    fle.close()
    return f'''
{content}
    '''

def code_to_xml(code):
    if isinstance(code, str):
        return f'''
            {code}
        '''
    if not code:
        return ''
    return f''' <code>
        <co_argcount>{code.co_argcount}</co_argcount>
        <co_cellvars>{','.join(code.co_cellvars)}</co_cellvars>
        <co_code>{get_dis(code.co_code)}</co_code>
        <co_consts>{''.join(["<co_const>" + code_to_xml(x) + "</co_const>" for x in code.co_consts])}</co_consts>
        <co_filename>{code.co_filename}</co_filename>
        <co_firstlineno>{code.co_firstlineno}</co_firstlineno>
        <co_flags>{code.co_flags}</co_flags>
        <co_freevars>{','.join(code.co_freevars)}</co_freevars>
        <co_kwonlyargcount>{code.co_kwonlyargcount}</co_kwonlyargcount>
        <co_lnotab>{code.co_lnotab}</co_lntotab>
        <co_name>{code.co_name}</co_name>
        <co_names>{','.join(code.co_names)}</co_names>
        <co_nlocals>{code.co_nlocals}</co_nlocals>
        <co_stacksize>{code.co_stacksize}</co_stacksize>
        <co_varnames>{','.join(code.co_varnames)}</co_varnames>
    </code>'''

if __name__ == '__main__':
    pyc_file_name = sys.argv[1]
    out_file_name = sys.argv[2]
    pycode = get_code(pyc_file_name)
    xmlstring = code_to_xml(pycode)
    absoutname = os.path.abspath(out_file_name)
    out = open(absoutname, 'w')
    out.write(
            f'''<xml>
    {xmlstring}
</xml>'''
        )
    out.close()
    os.remove('temp')


#!/usr/bin/env python
# coding: utf-8
import logging
logging.basicConfig(level=logging.ERROR)
from pathlib import Path
import warnings
with warnings.catch_warnings():
  warnings.filterwarnings("ignore",category=FutureWarning)
  import tensorflow as tf
  from tensorflow import feature_column
  from tensorflow.keras import layers
  from tensorflow.keras.layers import Dense, Flatten, Conv2D
import platform
import numpy as np
import pandas as pd
import math
import sys
import os
import glob
import time
from advtrain.cleverhans import projected_gradient_descent
import yaml
#!pip install -q tensorflow==2.0.0-beta1
import argparse
from sklearn.model_selection import train_test_split
from advtrain import model_metrics
from advtrain import adversarial
from utils import Bunch, df_column_specs, get_param_grid
tf.random.set_seed(123)
np.random.seed(312)
from test_tube import Experiment
from test_tube import HyperOptArgumentParser
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
exp_path = 'exp/mushroom'

######### arg parsing ###########
parser = argparse.ArgumentParser()
parser.add_argument('--hparams', default='mushroom-dnn-adv.yaml',
                    type=str,
                    help='params grid to run exps on')
parser.add_argument('-q', '--quick', default=False,
                    action='store_true',
                    help='whether to do a quick test')
parser.add_argument('--out', type=str, help='results output file')

args = parser.parse_args(sys.argv[1:])
# log_codes = dict(e=tf.logging.ERROR,
#                  i=tf.logging.INFO,
#                  w=tf.logging.WARN,
#                  d=tf.logging.DEBUG)

if args.out is not None:
  exp_path = os.path.join(args.out, exp_path)

exp = Experiment(exp_path)

params = Bunch(yaml.load(open(args.hparams)))
grid = get_param_grid(params)
params.pop('grid')
# tf.logging.set_verbosity(log_codes.get(params.log.lower()[0],
#                                        tf.logging.ERROR))

# if platform.system() == 'Linux':
#   tf.compat.v1.disable_eager_execution()
#   print(f'******** TF EAGER MODE DISABLED on {platform.system()} *****')

def run_exp(params):
  exp.tag(params)
  URL = 'mushroom/all.csv'
  dataframe = pd.read_csv(URL)
  dataframe.head()

  specs, target = df_column_specs(dataframe, params=None)

  train, test = train_test_split(dataframe, test_size=0.2)
  train, val = train_test_split(train, test_size=0.2)
  print(len(train), 'train examples')
  print(len(val), 'validation examples')
  print(len(test), 'test examples')

  batch_size = params.batch or 32

  def df_to_dataset(dataframe, shuffle=True, batch_size=batch_size):
    dataframe = dataframe.copy()
    labels = dataframe.pop('target')
    ds = tf.data.Dataset.from_tensor_slices((dict(dataframe), labels))
    if shuffle:
      ds = ds.shuffle(buffer_size=len(dataframe))
    ds = ds.batch(batch_size)
    return ds

  # A utility method to create a tf.data dataset from a Pandas Dataframe

  feature_columns = []
  # for mushroom we know the cols are all categorical, so we're not being too
  # careful here
  for col in specs:
    feature_columns.append(
      feature_column.indicator_column(
        feature_column.categorical_column_with_identity(
          col['name'], col['card'])))

  feature_layer = tf.keras.layers.DenseFeatures(feature_columns)

  train_ds = df_to_dataset(train, batch_size=batch_size)
  val_ds = df_to_dataset(val, shuffle=False, batch_size=batch_size)
  test_ds = df_to_dataset(test, shuffle=False, batch_size=batch_size)

# ## Create Model
#

# In[20]:

  featurizer = tf.keras.Sequential([
      feature_layer
    ],
      name='featurizer'
  )

  l1 = params.l1 or 0.0
  inner_model_logits = tf.keras.Sequential([
    layers.Dense(1,
                 kernel_regularizer=tf.keras.regularizers.l1(l=l1),
                 kernel_initializer=tf.random_normal_initializer(),
                 bias_initializer=tf.random_normal_initializer())
  ],
      name='inner_model_logits'
  )

  logit_prob = tf.keras.Sequential([
      layers.Activation('sigmoid')
  ], name='logit_prob')

  features_prob = tf.keras.Sequential([
      inner_model_logits,
      logit_prob
  ])

  # try out the model
  example_batch, label_batch = next(iter(train_ds))
  features = featurizer(example_batch)
  logits = inner_model_logits(features)
  logit_prob(logits)[:4]
  features_prob(features[:4])

#  predictions = model(example_batch)[:,0]

  loss_object = tf.keras.losses.BinaryCrossentropy()

  if params.adam:
    optimizer = tf.keras.optimizers.Adam(learning_rate=0.01)
  else:
    optimizer = tf.keras.optimizers.Ftrl(learning_rate=0.01)

#  features_prob.compile(optimizer=optimizer, loss=loss_object)
  def inner_model_logits_2_class(x):
    logits = inner_model_logits(x)
    return tf.concat([-logits, logits], axis=1)

  features_perturbed = projected_gradient_descent(inner_model_logits_2_class,
                                                  features, 0.4, 0.2,
                                                  5, np.inf,
                                                  y=label_batch, targeted=False)

  input_dim = features_prob.layers[0].inputs[0].shape[1]
  inputs = tf.keras.Input(shape=(input_dim,))
  outputs = tf.keras.layers.Dense(1,
                                  activation='sigmoid',
                                  kernel_regularizer = tf.keras.regularizers.l1(l=l1),
                                  kernel_initializer = tf.random_normal_initializer(),
                                  bias_initializer = tf.random_normal_initializer())(inputs)
  features_prob_functional = tf.keras.Model(inputs=inputs, outputs=outputs)

  model = tf.keras.Sequential([
    featurizer,
    features_prob_functional
    # inner_model_logits,
    # logit_prob
  ])
  model(example_batch)[:4]

  def inner_model_probs_2_class(x):
    probs = features_prob_functional(x)
    return tf.concat([1-probs, probs], axis=1)

  def mdl_to_2_class(mdl):
    def fn(x):
      probs = mdl(x)
      return tf.concat([1-probs, probs], axis=1)
    return fn

  # test out adv perturbation, and closed form

  # features_perturbed_closed_form = adversarial.logistic_perturb(
  #   inner_model_logits, features, label_batch, 0.4)
  #
  # [features_perturbed_closed_form[0, :5], features_perturbed[0, :5]]

  train_loss = tf.keras.metrics.Mean(name='train_loss')
  train_auc = tf.keras.metrics.AUC(name='train_auc')
  train_acc = tf.keras.metrics.BinaryAccuracy(name='train_acc')

  test_loss = tf.keras.metrics.Mean(name='test_loss')
  test_auc = tf.keras.metrics.AUC(name='test_auc')
  test_acc = tf.keras.metrics.BinaryAccuracy(name='test_acc')

# In[26]:

  #@tf.function
  def train_step_perturb_input(examples, labels, eps, eps_step, num_pgd_steps):
    features = featurizer(examples)
    features_prob_functional(features) # dummy call to force weights to be
    # created
    # this is done outside the tape, but it's ok because we've ensured
    # there are no trainable vars in the featurizer (no embeddings!)
    features_perturbed = adversarial.logistic_perturb(
      features_prob_functional, features, labels, eps)
    with tf.GradientTape() as tape:
      predictions = features_prob_functional(features_perturbed)
      loss = loss_object(labels, predictions[:,0])
      loss += sum(features_prob_functional.losses)
    vars = featurizer.trainable_variables + \
           features_prob_functional.trainable_variables
    gradients = tape.gradient(loss, vars)
    optimizer.apply_gradients(zip(gradients, vars))
    train_loss(loss)
    train_auc(labels, tf.reshape(predictions,[-1]))
    train_acc(labels, tf.reshape(predictions,[-1]))

  def train_step_perturb_last_layer(examples, labels, eps,
                                    freeze_initial_layers=True):
    with tf.GradientTape() as tape:
      features = featurizer(examples)
      left, right = model_metrics.sub_models(inner_model_logits)
      penultimate_activations = left(features)

      right(penultimate_activations) # dummy call to force weights to be created
      d = penultimate_activations.shape[1]
      activations_perturbed = adversarial.logistic_perturb(right,
                                                        penultimate_activations, labels,
                                                        eps/math.sqrt(d))
      logits_perturbed = right(activations_perturbed)
      predictions = logit_prob(logits_perturbed)
      loss = loss_object(labels, predictions[:,0])
      loss += sum(features_prob.losses)
    if freeze_initial_layers:
      mdl = inner_model_logits.layers[1]
    else:
      mdl = inner_model_logits
    vars = mdl.trainable_variables # + featurizer.trainable_variables
    gradients = tape.gradient(loss, vars)
    optimizer.apply_gradients(zip(gradients, vars))

    train_loss(loss)
    train_auc(labels, tf.reshape(predictions,[-1]))

  # In[27]:

  #@tf.function
  def test_step(examples, labels):
    predictions = model(examples)[:,0]
    t_loss = loss_object(labels, predictions)
    test_loss(t_loss)
    test_auc(labels, predictions)
    test_acc(labels, predictions)

# In[28]:

# example_batch, label_batch = next(iter(train_ds))
# predictions = model(example_batch)[:,0]
# loss_object(label_batch, predictions)

# In[29]:

  def get_all_x_y(ds):
    batches = []
    y = []
    for examples, labels in ds:
      x = featurizer(examples)
      batches = batches + [x]
      y = y + [labels]
    return tf.concat(batches, axis=0), tf.concat(y, axis=0)

  def reset_states():
    train_loss.reset_states()
    train_auc.reset_states()
    train_acc.reset_states()
    test_loss.reset_states()
    test_auc.reset_states()
    test_acc.reset_states()

  EPOCHS = params.epochs or 20
  if args.quick:
    EPOCHS = 2
  eps = params.eps or 0.0  # 1.3 ?
  num_pgd_steps = params.pgd_steps or 5
  perturb_input = params.perturb_input
  clean_epochs = params.clean_epochs or 2
  if args.quick:
    clean_epochs = 1
  eps_step_factor = params.eps_step_factor or 5.0
  for epoch in range(EPOCHS):
    reset_states()
    if epoch < clean_epochs:
      eps_val = 0
    else:
      eps_val = eps
    eps_step = eps_val/eps_step_factor

    for images, labels in train_ds:
      if perturb_input:
        train_step_perturb_input(images, labels, eps_val, eps_step, num_pgd_steps)
      else:
        # perturb penultimate layer, using closed form for binary prob output
        train_step_perturb_last_layer(images, labels, eps_val,
                                      freeze_initial_layers=True)

    for test_images, test_labels in test_ds:
      test_step(test_images, test_labels)

    template = 'Eps={}, Epoch {}, Loss: {}, AUC: {}, ' \
               'Test Loss: {}, Test AUC: {}, Test AC: {}'
    if epoch % 5 == 0:
          print (template.format(eps_val, epoch+1,
                           train_loss.result(),
                           train_auc.result()*100,
                           test_loss.result(),
                           test_auc.result()*100,
                           test_acc.result()*100))

  # dummy examples to force model shapes to be set
  example_batch, label_batch = next(iter(train_ds))
  model(example_batch)

  model_file = 'mushroom_models/' + f'eps={eps}'

  #model.save(model_file, overwrite=True)
  model.save_weights(model_file + ".ckpt", overwrite=True)
  m = model_metrics.weight_metrics(model)
  print(f'weight metrics with eps={eps}:')
  print(m)

  x_test, y_test = get_all_x_y(test_ds)
  attribution_method = 'shap' if params.attribution_shap else 'ig'
  mdl_shap = features_prob_functional
  mdl_ig = inner_model_probs_2_class
  av_ig_pct1pct, ig_ent, av_gini, ig_results = \
    model_metrics.attribs_pct( mdl_shap, mdl_ig,
                               x_test,
                               y_test,
                               attribution_method=attribution_method)
  print(f'eps={eps}: IG_pct1pct={av_ig_pct1pct}')

  print(f'****done with eps={eps} ****')
  print(f'****************************')

  params_and_results = params.mod(dict(
    test_auc = np.round(test_auc.result()*100,2),
    test_acc = np.round(test_acc.result()*100,2),
    ig_ent = np.round(ig_ent, 2),
    gini = np.round(av_gini, 3),
    ig_1p = np.round(av_ig_pct1pct[0], 2)))
  print('*** logging ****')
  print(params_and_results.dict())
  exp.log(params_and_results.dict())
  exp.save()
  exp.close()
  return ig_results

ig_init_cols = ['correct', 'ent', 'a1p', 'gin']
n_init_cols = len(ig_init_cols)
for i, p in enumerate(grid):
  if args.quick and i > 2:
    break
  str_p = list(p.keys())[0] + '=' + str(list(p.values())[0])
  ig_results = run_exp(params.mod(p))
  exp_dir = exp.get_data_path(exp.name, exp.version)
  ig_file_pkl = os.path.join(exp_dir, str_p, 'results.pkl')
  ig_file_csv = os.path.join(exp_dir, str_p, 'results.csv')
  ig_cols = ['ig' + str(c) for c in range(ig_results.shape[1] - n_init_cols)]
  cols = ig_init_cols + ig_cols
  df_ig_results = pd.DataFrame(ig_results, columns=cols)
  df_ig_results = df_ig_results.astype(dict(correct='int32'))
  os.makedirs(Path(ig_file_pkl).parent,  exist_ok=True)
  df_ig_results.to_csv(ig_file_csv, sep=' ')
  df_ig_results.to_pickle(ig_file_pkl)

print('!!!!!!!  experiments done  !!!!')
print('visualize with tensorboard using: ')
print(f'tensorboard --logdir ./{exp_path}')

list_of_files = glob.glob(os.path.join(exp_path, 'default', '*/metrics.csv'))
# * means
latest_file = max(list_of_files, key=os.path.getctime)
latest_dir = os.path.dirname(latest_file)
pkl_file = os.path.join(latest_dir, 'metrics.pkl')

df = pd.read_csv(latest_file)
df.to_pickle(pkl_file)

# fig, (ax1, ax2) = plt.subplots(2)
# fig.suptitle('Test AUC, IG1PCT vs epsilon')
# ax1.plot(df.eps, df.test_auc)
# ax1.set(title='Test AUC', xlabel='epsilon')
# ax1.label_outer()
# ax2.plot(df.eps, df.ig_pct1pct)
# ax2.set(title='IG1PCT', xlabel='epsilon')
# ax2.label_outer()
# plt.show()


import numpy as np
import numpy.matlib
import copy
from util import *
import joblib

from sklearn.ensemble import RandomForestRegressor
from sklearn.svm import SVR
from sklearn.linear_model import HuberRegressor
from sklearn.linear_model import LinearRegression
from sklearn.linear_model import ElasticNet
from sklearn.ensemble import BaggingRegressor
from sklearn.neural_network import MLPRegressor
from sklearn.neighbors import KNeighborsRegressor
from sklearn.ensemble import ExtraTreesRegressor
from sklearn.multioutput import MultiOutputRegressor
from sklearn.linear_model import Lasso
from sklearn.tree import DecisionTreeRegressor

from sklearn.ensemble import RandomForestClassifier
from sklearn.svm import SVC
from sklearn.ensemble import BaggingClassifier
from sklearn.ensemble import AdaBoostClassifier
from sklearn.neural_network import MLPClassifier
from sklearn.neighbors import KNeighborsClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import ExtraTreesClassifier
from sklearn.tree import DecisionTreeClassifier
from sklearn.svm import LinearSVC
from HybridCrowdClassifier import HybridCrowdClassifier
from sklearn.dummy import DummyClassifier

from CRnnLearner import CRnnLearner
from ANCnnLearner import ANCnnLearner
from DmlpLearner import DmlpLearner

# Train a regression or classification model
# To find a function F such that Y=F(X)
# OUTPUT: model
def trainModel(
    train, # the training set in pandas dataframe (contain train["X"] and train["Y"])
    test=None, # the testing set in pandas dataframe (contain test["X"] and test["Y"])
    out_p=None, # the path for saving the model
    method="ET", # the regression or classification method
    is_regr=False, # is regression or not
    balance=False, # oversample or undersample data or not
    logger=None):

    log("Training model with " + str(train["X"].shape[1]) + " features...", logger)

    # Build model
    multi_output = True if len(train["Y"]) > 1 and train["Y"].shape[1] > 1 else False
    if is_regr:
        if method == "RF":
            model = RandomForestRegressor(n_estimators=200, max_features=90, min_samples_split=2, n_jobs=-1)
        elif method == "ET":
            model = ExtraTreesRegressor(n_estimators=200, max_features=180, min_samples_split=32, n_jobs=-1)
        elif method == "SVM":
            model = SVR(max_iter=1000, C=100, gamma=0.01)
            if multi_output: model = MultiOutputRegressor(model, n_jobs=-1)
        elif method == "RLR":
            model = HuberRegressor(max_iter=1000)
            if multi_output: model = MultiOutputRegressor(model, n_jobs=-1)
        elif method == "LR":
            model = LinearRegression()
            if multi_output: model = MultiOutputRegressor(model, n_jobs=-1)
        elif method == "EN":
            model = ElasticNet(alpha=0.1, l1_ratio=0.5, max_iter=1000)
            if multi_output: model = MultiOutputRegressor(model, n_jobs=-1)
        elif method == "LA":
            model = Lasso(alpha=0.01, max_iter=1000)
            if multi_output: model = MultiOutputRegressor(model, n_jobs=-1)
        elif method == "MLP":
            model = MLPRegressor(hidden_layer_sizes=(128, 64))
        elif method == "KN":
            model = KNeighborsRegressor(n_neighbors=10, weights="uniform")
        elif method == "DMLP":
            model = DmlpLearner(test=test, logger=logger, is_regr=is_regr)
        elif method == "CRNN":
            model = CRnnLearner(test=test, logger=logger, is_regr=is_regr)
        elif method == "ANCNN":
            model = ANCnnLearner(test=test, logger=logger, is_regr=is_regr)
        elif method == "DT":
            model = DecisionTreeRegressor()
        else:
            m = method[:2]
            if m in ["RF", "ET"]:
                # parse tuning parameters
                p = method.split("-")
                log(p[0] + ", n_estimators=" + p[1] + ", max_features=" + p[2] + ", min_samples_split=" + p[3], logger)
                for i in range(1, len(p)):
                    if p[i] == "None": p[i] = None
                    elif p[i] == "auto": p[i] = "auto"
                    else: p[i] = int(p[i])
                if m == "RF":
                    model = RandomForestRegressor(n_estimators=p[1],max_features=p[2],min_samples_split=p[3],
                        random_state=0,n_jobs=-1)
                elif m == "ET":
                    model = ExtraTreesRegressor(n_estimators=p[1],max_features=p[2],min_samples_split=p[3],
                        random_state=0,n_jobs=-1)
            else:
                log("ERROR: method " + method + " is not supported", logger)
                return None
    else:
        if method == "RF":
            model = RandomForestClassifier(n_estimators=1000, max_features=30, min_samples_split=2, n_jobs=-1)
        elif method == "ET":
            model = ExtraTreesClassifier(n_estimators=1000, max_features=60, min_samples_split=32, n_jobs=-1)
        elif method == "SVM":
            model = SVC(max_iter=5000, kernel="rbf", probability=True)
        elif method == "MLP":
            model = MLPClassifier(hidden_layer_sizes=(128, 64))
        elif method == "KN":
            model = KNeighborsClassifier(n_neighbors=10, weights="uniform")
        elif method == "LG":
            model = LogisticRegression(penalty="l1", C=1)
        elif method == "DMLP":
            model = DmlpLearner(test=test, logger=logger, is_regr=is_regr)
        elif method == "CRNN":
            model = CRnnLearner(test=test, logger=logger, is_regr=is_regr)
        elif method == "ANCNN":
            model = ANCnnLearner(test=test, logger=logger, is_regr=is_regr)
        elif method == "HCR":
            model = ExtraTreesClassifier(n_estimators=1000, max_features=90, min_samples_split=32, n_jobs=-1)
            model = HybridCrowdClassifier(base_estimator=model, logger=logger)
        elif method == "CR":
            model = HybridCrowdClassifier(logger=logger)
        elif method == "DT":
            model = DecisionTreeClassifier(min_samples_split=20, max_depth=8, min_samples_leaf=5)
        elif method == "Base1":
            model = DummyClassifier(strategy="stratified")
        elif method == "Base2":
            model = DummyClassifier(strategy="uniform")
        elif method == "Base3":
            model = DummyClassifier(strategy="constant", constant=1)
        else:
            m = method[:2]
            if m in ["RF", "ET"]:
                # parse tuning parameters
                p = method.split("-")
                log(p[0] + ", n_estimators=" + p[1] + ", max_features=" + p[2] + ", min_samples_split=" + p[3], logger)
                for i in range(1, len(p)):
                    if p[i] == "None": p[i] = None
                    elif p[i] == "auto": p[i] = "auto"
                    else: p[i] = int(p[i])
                if m == "RF":
                    model = RandomForestClassifier(n_estimators=p[1],max_features=p[2],min_samples_split=p[3],
                        random_state=0,n_jobs=-1)
                elif m == "ET":
                    model = ExtraTreesClassifier(n_estimators=p[1],max_features=p[2],min_samples_split=p[3],
                        random_state=0,n_jobs=-1)
            else:
                log("ERROR: method " + method + " is not supported", logger)
                return None

    # Use balanced dataset or not
    if balance:
        log("Compute balanced dataset...", logger)
        X, Y = balanceDataset(train["X"], train["Y"])
    else:
        X, Y = copy.deepcopy(train["X"]), copy.deepcopy(train["Y"])

    # For one-class classification task, we only want to use the minority class (because we are sure that they are labeled)
    if not is_regr and method == "IF":
        y_minor = findLeastCommon(Y)
        select_y = (Y == y_minor)
        X, Y = X[select_y], Y[select_y]

    # Fit data to the model
    if method == "ANCNN":
        model.fit(X, np.squeeze(Y), copy.deepcopy(train["X_pretrain"]))
    else:
        model.fit(X, np.squeeze(Y))

    # Save and return model
    if out_p is not None:
        if method == "CRNN" or method == "CRMANN":
            model.save(out_p)
        else:
            joblib.dump(model, out_p)
        log("Model saved at " + out_p, logger)
    return model


word = input()
k = input()

points = []
positions = []
last = []

for i in range(len(word)):
    positions.append(i)
    if word[i] == k:
        points.append(i)
MIN = 100
for i in positions:
    for j in points:
        x = abs(i-j)
        if x < MIN:
            MIN = x
    last.append(MIN)
    MIN = 100
print(*last)


import torch

from . import DQN
from ..nn import quantile_huber_loss

class QuantileRegressionDQN(DQN):
    def __init__(self, *args, atoms=51, **kwargs):
        super().__init__(*args, **kwargs)
        self.atoms = atoms
        low = 0.5 / atoms
        high = 1 - 0.5 / atoms
        self._quantile_midpoints = torch.linspace(low, high, atoms) \
                                        .view(1, -1, 1)

    def q_values(self, state):
        state = self.state_to_tensor([state])
        with torch.no_grad():
            return self.online_network(state).mean(-1).squeeze(0)

    def _q_quantiles(self, state, action):
        action = action.view(-1, 1, 1).expand(-1, -1, self.atoms)
        return self.online_network(state).gather(1, action).squeeze(1)

    def _q_target_quantiles(self, next_state, reward, done):
        action = self.online_network(next_state) \
                     .mean(-1).argmax(1) \
                     .view(-1, 1, 1) \
                     .expand(-1, -1, self.atoms)
        q_next = (1 - done) * self.target_network(next_state) \
                                  .gather(1, action) \
                                  .view(-1, self.atoms)
        return reward + (self.gamma ** self.n_steps) * q_next

    def loss(self, state, action, next_state, reward, done):
        q = self._q_quantiles(state, action)
        with torch.no_grad():
            q_target = self._q_target_quantiles(next_state, reward, done)
        tau_i = self._quantile_midpoints
        theta_i = q.unsqueeze(-1)
        T_theta_j = q_target.unsqueeze(1)
        td_error = T_theta_j - theta_i
        loss = quantile_huber_loss(error, tau_i, self.delta_clip, reduction='none')
        return loss.mean(1).sum(1, keepdim=True)


import telebot
import telebot.types as typ
from libs.message import messages
import wikipedia as wk
from telebot.apihelper import ApiTelegramException
from wikipedia.exceptions import PageError
BOT_TOKEN = "<YOUR_BOT_TOKEN_HERE>"
bot = telebot.TeleBot(BOT_TOKEN, parse_mode="HTML")

@bot.message_handler(commands=["start"])
def welcome_message(msg):
    language_keyboard = typ.InlineKeyboardMarkup(row_width=2)
    UZ = typ.InlineKeyboardButton("🇺🇿 O'zbekcha", callback_data="UZ")
    RU = typ.InlineKeyboardButton("🇷🇺 Русский", callback_data="RU")
    EN = typ.InlineKeyboardButton("🏴 English", callback_data="EN")
    language_keyboard.add(UZ, RU, EN)
    print(msg.from_user)

    bot.send_message(msg.chat.id, messages(msg).select_language(), reply_markup=language_keyboard)

@bot.callback_query_handler(func= lambda call: True)
def callback_handlear(call):
    if call.data == "UZ":
        wk.set_lang("uz")
        bot.edit_message_text("O'zbek tili tanlandi.", call.from_user.id, call.message.id)
        bot.send_message(call.from_user.id, messages(call).welcome("UZ"))
    elif call.data == 'RU':
        wk.set_lang("ru")
        bot.edit_message_text("Был выбран русский язык.", call.from_user.id, call.message.id)
        bot.send_message(call.from_user.id, messages(call).welcome("RU"))
    elif call.data == 'EN':
        wk.set_lang("en")
        bot.edit_message_text("English was chosen.", call.from_user.id, call.message.id)
        bot.send_message(call.from_user.id, messages(call).welcome("EN"))

@bot.message_handler(func= lambda message: True)
def messsage_handlear(msg):
    if msg.text != "BACK":
        try:
            back_key = typ.ReplyKeyboardMarkup(resize_keyboard=True)
            back = typ.KeyboardButton("BACK")
            back_key.add(back)

            res = wk.summary(msg.text)
            bot.send_message(msg.chat.id, res, reply_markup=back_key)

        except Exception as e:
            back_key = typ.ReplyKeyboardMarkup(resize_keyboard=True)
            back = typ.KeyboardButton("BACK")
            back_key.add(back)

            if str(e) == "A request to the Telegram API was unsuccessful. Error code: 400. Description: Bad Request: message is too long":
                bot.send_message(msg.chat.id, messages(msg).TelegramParse("ALL"), reply_markup=back_key)
            else:
                bot.send_message(msg.chat.id, messages(msg).wiki_error(), reply_markup=back_key)
    else:
        language_keyboard = typ.InlineKeyboardMarkup(row_width=2)
        UZ = typ.InlineKeyboardButton("🇺🇿 O'zbekcha", callback_data="UZ")
        RU = typ.InlineKeyboardButton("🇷🇺 Русский", callback_data="RU")
        EN = typ.InlineKeyboardButton("🏴 English", callback_data="EN")
        language_keyboard.add(UZ, RU, EN)

        bot.send_message(msg.chat.id, messages(msg).select_language(), reply_markup=language_keyboard)

print("Working...")
bot.polling(non_stop=True)
print("Stopped bot.")