# -*- coding: utf-8 -*-
"""
Created on Tue Mar 15 23:18:35 2022

@author: lamya
"""

from flask import Flask, render_template, request
import pickle

import re
#from farasa.segmenter import FarasaSegmenter
from arabert.preprocess import ArabertPreprocessor
from pyarabic.araby import strip_tashkeel,strip_tatweel
from nltk.tokenize import TweetTokenizer
model_name = "aubmindlab/bert-base-arabertv2"
arabert_prep = ArabertPreprocessor(model_name=model_name)
def preprocess_text(tweet):
    outputs=[]
    tokenizer = TweetTokenizer()
    tweet = str(tweet)
    # remove old style retweet text "RT"
    tweet = re.sub(r'^RT[\s]+', '', tweet)
    # remove hyperlinks
    tweet = re.sub(r'https?:\/\/.*[\r\n]*', '', tweet)
    # remove hashtags
    # only removing the hash # sign from the word
    tweet = re.sub(r'#', '', tweet)
    #removing mentions
    tweet = re.sub(r':', '', tweet)
    tweet = re.sub(r'@[\w]+','',tweet)
    #replace punctuations with space
    tweet = re.sub(r"[,.;@#?!&$_]+\ *", " ", tweet)
    #find arabic letters only
    tweet = ' '.join(re.findall(r'[\u0600-\u06FF]+',tweet))
    #remove tashkeel
    tweet = strip_tashkeel(tweet)
    #remove tatweel
    tweet = strip_tatweel(tweet)
    #remove numbers from string
    pattern = r'[0-9]'
    # Match all digits in the string and replace them with an empty string
    tweet = re.sub(pattern, '', tweet)
    #tokenize tweets
    tweet_tokens = tokenizer.tokenize(tweet)
    tweet_clean=[]
    for word in tweet_tokens: # Go through every word in your tokens list
        #if word not in string.punctuation:  # remove punctuation
        word_reg = re.compile(r'\w')
        if word_reg.search(word):
            tweet_clean.append(word)
    outputs.append((' '.join(tweet_clean)))
    text_preprocessed=arabert_prep.preprocess(outputs)
    return text_preprocessed

#Flask App
app = Flask(__name__)

#Load LSTM Model
lstm_model = pickle.load(open('ls_model.pkl', 'rb'))

#Home
@app.route('/')#, methods=['GET'])
def home():
    return 'Hello World!'
    #return render_template('index.html',variable=None, variables=None)

#Model Prediction
@app.route('/predict',methods=['POST'])
def predict():
    tweet = request.form['text']
    result = list()
    result.append(dict())
    result[0]['tweet'] = tweet

    dialect_tweet = preprocess_text(tweet)

    dialect_prediction = lstm_model.predict(dialect_tweet)

    dialect_proba = lstm_model.predict_proba(dialect_tweet)[0]

    #18 Classes: ['EG', 'PL', 'KW', 'LY', 'QA', 'JO', 'LB', 'SA',
    # 'AE', 'BH', 'OM', 'SY', 'DZ', 'IQ', 'SD', 'MA', 'YE', 'TN']

    result[0]['dialect'] = {}
    if dialect_prediction == "EG":
        result[0]['dialect']["EG"] = round(dialect_proba[0]*100, 2)
    if dialect_prediction == "PL":
        result[0]['dialect']["PL"] = round(dialect_proba[1]*100, 2)
    if dialect_prediction == "KW":
        result[0]['dialect']["KW"] = round(dialect_proba[2]*100, 2)
    if dialect_prediction == "LY":
        result[0]['dialect']["LY"] = round(dialect_proba[3]*100, 2)
    if dialect_prediction == "QA":
        result[0]['dialect']["QA"] = round(dialect_proba[4]*100, 2)
    if dialect_prediction == "JO":
        result[0]['dialect']["JO"] = round(dialect_proba[5]*100, 2)
    if dialect_prediction == "LB":
        result[0]['dialect']["LB"] = round(dialect_proba[6]*100, 2)
    if dialect_prediction == "SA":
        result[0]['dialect']["SA"] = round(dialect_proba[7]*100, 2)
    if dialect_prediction == "AE":
        result[0]['dialect']["AE"] = round(dialect_proba[8]*100, 2)
    if dialect_prediction == "BH":
        result[0]['dialect']["BH"] = round(dialect_proba[9]*100, 2)
    if dialect_prediction == "OM":
        result[0]['dialect']["OM"] = round(dialect_proba[10]*100, 2)
    if dialect_prediction == "SY":
        result[0]['dialect']["SY"] = round(dialect_proba[11]*100, 2)
    if dialect_prediction == "DZ":
        result[0]['dialect']["DZ"] = round(dialect_proba[12]*100, 2)
    if dialect_prediction == "IQ":
        result[0]['dialect']["IQ"] = round(dialect_proba[13]*100, 2)
    if dialect_prediction == "SD":
        result[0]['dialect']["SD"] = round(dialect_proba[14]*100, 2)
    if dialect_prediction == "MA":
        result[0]['dialect']["MA"] = round(dialect_proba[15]*100, 2)
    if dialect_prediction == "YE":
        result[0]['dialect']["YE"] = round(dialect_proba[16]*100, 2)
    if dialect_prediction == "TN":
        result[0]['dialect']["TN"] = round(dialect_proba[17]*100, 2)

    return render_template('index.html', variable=result, variables=None)

# App Main
if __name__ == "__main__":
    app.run(debug=False)

#Check
print("Final Done!")


# mFLIM dataset synthetic generator
import logging as log
import numpy as np
import scipy.io
from scipy import linalg

def update_args(kwargs: dict) -> dict:
    args_given = list(kwargs.keys())
    default_args = {'n_order': 2, 'n_samples': 60, 'ts': 0.25e-9, 'snr': 0, 'psnr': 0}
    for arg in args_given:
        if arg in default_args.keys():
            default_args[arg] = kwargs[arg]
    return default_args

# Check type-hint
def mflimsynth(**kwargs: [int, float]) -> [np.ndarray, np.ndarray, np.ndarray, dict]:
    """
    INPUTS:
    n_order --> Order of multi-exponential model. Only n = 2,3,4.
    n_samples --> Number of pixels in x & y axes. Size of square image.
    ts --> Sampling Time.
    snr --> Level of Gaussian noise (dB), SNR = 45,50,55.
    psnr --> Level for Shot noise (dB), PSNR = 15,20,25.
    OUTPUTS:
    y_matrix --> matrix of fluorescence decays of size [186 x (n_samples*n_samples)].
    a_matrix --> matrix of abundances of [n x (n_samples*n_samples)].
    p_matrix --> matrix of endmembers [186 x n_order].
    - Daniel U. Campos-Delgado (July/2020).
    """
    try:
        # Synthetic FLIM Dataset
        num_input_args = len(kwargs)
        if num_input_args == 0:
            args = {'n_order': 2, 'n_samples': 60, 'ts': 0.25e-9, 'snr': 40, 'psnr': 20}
        elif num_input_args in [1, 2, 3, 4, 5]:
            args = update_args(kwargs)

        if args['n_order'] > 4:
            args['n_order'] = 4
            log.info("The maximum number of components is 4!!")

        if args['snr'] or args['psnr']:
            noise_measurement = True
            if args['snr']:
                noise_gaussian = True
            else:
                noise_gaussian = False
            if args['psnr']:
                noise_shot = True
            else:
                noise_shot = False
        else:
            noise_measurement = noise_gaussian = noise_shot = False
    except Exception as error:
        log.error(f"ERROR [Input arguments step]: {error}")
    try:
        num_samples = args['n_samples']
        x = y = np.arange(1, args['n_samples'] + 1)
        xx, yy = np.meshgrid(x, y)
        k = args['n_samples'] * args['n_samples']
        dataexp1 = scipy.io.loadmat("./files/dataexp1")
        u, y = dataexp1["u"], dataexp1["y"]
        del dataexp1
        u_len = len(u)
        u = u/sum(u)  # vector of laser input
        n = np.arange(u_len).reshape((1, u_len))
        t = n*args['ts']  # vector of time samples
        zeros = np.zeros((1, u_len))
        zeros[0][0] = u[0][0]
        u_toe = linalg.toeplitz(u.T, zeros)
        tau11, tau12, tau13, tau14 = [10, 20, 5, 15]
        tau21, tau22, tau23, tau24 = [25, 10, 15, 5]
        tau31, tau32, tau33, tau34 = [5, 7, 35, 15]

        if args['n_order'] == 2:
            aa1 = 7 * np.exp(-0.001 * (xx - num_samples / 2) ** 2 - 0.001 * (yy - num_samples / 2) ** 2) + 0.5
            aa2 = 2.5 * np.exp(-0.001 * (xx - num_samples) ** 2 - 0.001 * yy ** 2) + 2.5 * np.exp(
                -0.0001 * xx ** 2 - 0.001 * (yy - num_samples) ** 2) + \
                  2.5 * np.exp(-0.001 * xx ** 2 - 0.0001 * (yy - num_samples) ** 2) + 2.5 * \
                  np.exp(-0.0001 * (xx - num_samples) ** 2 - 0.001 * (yy - num_samples) ** 2)
            a1 = np.zeros((num_samples, num_samples))
            a2 = np.zeros((num_samples, num_samples))
            for i in np.arange(num_samples):
                for j in np.arange(num_samples):
                    a1[i, j] = aa1[i, j] / (aa1[i, j] + aa2[i, j])
                    a2[i, j] = aa2[i, j] / (aa1[i, j] + aa2[i, j])

        elif args['n_order'] == 3:
            aa1 = 7 * np.exp(-0.005 * (xx - num_samples / 2) ** 2 - 0.005 * (yy - num_samples / 2) ** 2) + 0.5
            aa2 = 2.5 * np.exp(-0.001 * (xx - num_samples) ** 2 - 0.001 * yy ** 2) + \
                  2.5 * np.exp(-0.0001 * xx ** 2 - 0.001 * (yy - num_samples) ** 2)
            aa3 = 3.5 * np.exp(-0.001 * xx ** 2 - 0.0001 * (yy - num_samples) ** 2) + 2.5 * \
                  np.exp(-0.0001 * (xx - num_samples) ** 2 - 0.001 * (yy - num_samples) ** 2)
            a1 = np.zeros((num_samples, num_samples))
            a2 = np.zeros((num_samples, num_samples))
            a3 = np.zeros((num_samples, num_samples))
            for i in np.arange(num_samples):
                for j in np.arange(num_samples):
                    a1[i, j] = aa1[i, j] / (aa1[i, j] + aa2[i, j] + aa3[i, j])
                    a2[i, j] = aa2[i, j] / (aa1[i, j] + aa2[i, j] + aa3[i, j])
                    a3[i, j] = aa3[i, j] / (aa1[i, j] + aa2[i, j] + aa3[i, j])

        elif args['n_order'] == 4:
            aa1 = 2.5 * np.exp(-0.005 * (xx - num_samples / 2) ** 2 - 0.0005 * (yy - num_samples / 2) ** 2) + 0
            # \ + 2.5 * np.exp(-0.0001 * (xx)**2 - 0.001 * (yy - num_samples)**2)
            aa2 = 2.5 * np.exp(-0.001 * (xx - num_samples) ** 2 - 0.00025 * yy ** 2)
            aa3 = 2.5 * np.exp(-0.001 * xx ** 2 - 0.0002 * (yy - num_samples) ** 2)
            aa4 = 2.5 * np.exp(-0.001 * (xx - 8 * (num_samples / 9)) ** 2 - 0.001 * (yy - 8 * (num_samples / 9)) ** 2)\
                  + 2.5 * np.exp(-0.001 * (xx - (num_samples / 9)) ** 2 - 0.001 * (yy - 8 * (num_samples / 9)) ** 2)
            a1 = np.zeros((num_samples, num_samples))
            a2 = np.zeros((num_samples, num_samples))
            a3 = np.zeros((num_samples, num_samples))
            a4 = np.zeros((num_samples, num_samples))
            for i in np.arange(num_samples):
                for j in np.arange(num_samples):
                    a1[i, j] = aa1[i, j] / (aa1[i, j] + aa2[i, j] + aa3[i, j] + aa4[i, j])
                    a2[i, j] = aa2[i, j] / (aa1[i, j] + aa2[i, j] + aa3[i, j] + aa4[i, j])
                    a3[i, j] = aa3[i, j] / (aa1[i, j] + aa2[i, j] + aa3[i, j] + aa4[i, j])
                    a4[i, j] = aa4[i, j] / (aa1[i, j] + aa2[i, j] + aa3[i, j] + aa4[i, j])

        p11 = u_toe @ np.exp(-n.T / tau11) * 0.6
        p21 = u_toe @ np.exp(-n.T / tau21) * 0.2
        p31 = u_toe @ np.exp(-n.T / tau31) * 0.2
        p1 = np.r_[p11, p21, p31]

        if args['n_order'] >= 2:
            p12 = u_toe @ np.exp(-n.T / tau12) * 0.2
            p22 = u_toe @ np.exp(-n.T / tau22) * 0.6
            p32 = u_toe @ np.exp(-n.T / tau32) * 0.2
            p2 = np.r_[p12, p22, p32]

            if args['n_order'] >= 3:
                p13 = u_toe @ np.exp(-n.T / tau13) * 0.15
                p23 = u_toe @ np.exp(-n.T / tau23) * 0.15
                p33 = u_toe @ np.exp(-n.T / tau33) * 0.70
                p3 = np.r_[p13, p23, p33]

                if args['n_order'] >= 4:
                    p14 = u_toe @ np.exp(-n.T / tau14) * 0.0
                    p24 = u_toe @ np.exp(-n.T / tau24) * 0.4
                    p34 = u_toe @ np.exp(-n.T / tau34) * 0.6
                    p4 = np.r_[p14, p24, p34]

        y_y = np.zeros((num_samples, num_samples, 3 * u_len))

        for i in range(num_samples):
            for j in range(num_samples):
                if args['n_order'] == 2:
                    y = a1[i, j] * p1 + a2[i, j] * p2
                elif args['n_order'] == 3:
                    y = a1[i, j] * p1 + a2[i, j] * p2 + a3[i, j] * p3
                elif args['n_order'] == 4:
                    y = a1[i, j] * p1 + a2[i, j] * p2 + a3[i, j] * p3 + a4[i, j] * p4

                if noise_measurement and noise_gaussian:
                    sigma_y = np.sqrt((y.T @ y) / (10 ** (args['snr'] / 10)))
                    yy1 = sigma_y * np.random.normal(size=(3 * u_len, 1))
                else:
                    yy1 = np.zeros((3 * u_len, 1))

                if noise_measurement and noise_shot:
                    sigma_y = np.sqrt(np.max(y) / (10 ** (args['psnr'] / 10)))
                    yy2 = sigma_y * np.random.normal(size=(3 * u_len, 1)) * np.sqrt(abs(y))
                else:
                    yy2 = np.zeros((3 * u_len, 1))

                y_y[i, j, :] = y.T + yy1.T + yy2.T

        if args['n_order'] == 2:
            p_matrix = np.c_[p1, p2]
            a_matrix = np.r_[a1.reshape((1, k), order="F"), a2.reshape((1, k), order="F")]
        elif args['n_order'] == 3:
            p_matrix = np.c_[p1, p2, p3]
            a_matrix = np.r_[a1.reshape((1, k), order="F"), a2.reshape(
                (1, k), order="F"), a3.reshape((1, k), order="F")]
        elif args['n_order'] == 4:
            p_matrix = np.c_[p1, p2, p3, p4]
            a_matrix = np.r_[a1.reshape((1, k), order="F"), a2.reshape(
                (1, k), order="F"), a3.reshape((1, k), order="F"), a4.reshape((1, k), order="F")]
        p_sum = np.sum(p_matrix, 0)
        p_matrix = p_matrix / np.tile(p_sum, [p_matrix.shape[0], 1])
        y_matrix_origin = y_y.reshape((k, 3 * u_len), order="F").T
        return y_matrix_origin, p_matrix, a_matrix, args
    except Exception as error:
        log.error(f"ERROR [mFLIM matrices calculation]: {error}")


from bs4 import BeautifulSoup
import requests
import re
from selenium import webdriver

def importUBetNBAPage():
    #Opens the page and returns the html inside
    browser = webdriver.Chrome()
    url = "https://ubet.com/sports/basketball/nba/nba-matches"
    browser.get(url)
    # innerHTML = browser.execute_script("return document.body.innerHTML")

    innerHTML = browser.page_source
    return innerHTML

def createUBetNBAMatchup():
    matchups = []
    currMatchup = ()
    pageHTML = importUBetNBAPage()
    #
    # ###################################
    # # print(pageHTML)
    output = open('ubetoutput.html', 'w', encoding='utf-8')
    output.write(pageHTML)
    # pageHTML = open('ubetoutput.html', 'r')
    ###################################
    soup = BeautifulSoup(pageHTML, 'lxml')
    #This gets the divs for each matchup
    teamDivs = soup.findAll('div', {"ng-multi-transclude":"content"})
    output = open('ubetoutput2.html', 'w', encoding='utf-8')
    output.write(str(teamDivs))
    for row in teamDivs:

        #Then we find the "Head to head" section,
        headToHead = row.find('div', {"class":"ubet-sub-events-summary"})

        #And then we find the name and odds for each team, which are contained in divs
        try:
            nameAndOddDivs = headToHead.findAll("div", {"class":"ubet-offer-win-only"})
        except AttributeError:
            return []

        #Then we retrieve the name and the odds, which are contained in spans of each divs

        #matchupInfo is a list of lists, with each inner list containing name and Odds
        matchupInfo = []
        for team in nameAndOddDivs:
            spans = team.findAll("span")
            spans = [span.text for span in spans]
            matchupInfo.append(spans)
        # print(matchupInfo)
        teamName1 = matchupInfo[0][0]
        teamOdds1 = matchupInfo[0][1]
        teamName2 = matchupInfo[1][0]
        teamOdds2 = matchupInfo[1][1]

        nameAndOdds1 = (teamName1, teamOdds1)
        nameAndOdds2 = (teamName2, teamOdds2)
        currMatchup = (nameAndOdds1, nameAndOdds2)
        sortedMatchup = sorted(currMatchup, key=lambda odds: odds[1], reverse=True)
        matchups.append(sortedMatchup)

    return matchups


#!python

__author__ = "DMcG"
__date__ = "$Jun 23, 2015 10:27:29 AM$"

import socket
import time

from io import BytesIO

from opendis.DataOutputStream import DataOutputStream
from opendis.dis7 import EntityStatePdu
from opendis.RangeCoordinates import *

UDP_PORT = 3001
DESTINATION_ADDRESS = "127.0.0.1"

udpSocket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
udpSocket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)

gps = GPS() # conversion helper

def send():
    pdu = EntityStatePdu()
    pdu.entityID.entityID = 42
    pdu.entityID.siteID = 17
    pdu.entityID.applicationID = 23
    pdu.marking.setString('Igor3d')

     # Entity in Monterey, CA, USA facing North, no roll or pitch
    montereyLocation = gps.llarpy2ecef(deg2rad(36.6),   # longitude (radians)
                                       deg2rad(-121.9), # latitude (radians)
                                       1,               # altitude (meters)
                                       0,               # roll (radians)
                                       0,               # pitch (radians)
                                       0                # yaw (radians)
                                       )

    pdu.entityLocation.x = montereyLocation[0]
    pdu.entityLocation.y = montereyLocation[1]
    pdu.entityLocation.z = montereyLocation[2]
    pdu.entityOrientation.psi = montereyLocation[3]
    pdu.entityOrientation.theta = montereyLocation[4]
    pdu.entityOrientation.phi = montereyLocation[5]

    memoryStream = BytesIO()
    outputStream = DataOutputStream(memoryStream)
    pdu.serialize(outputStream)
    data = memoryStream.getvalue()

    while True:
        udpSocket.sendto(data, (DESTINATION_ADDRESS, UDP_PORT))
        print("Sent {}. {} bytes".format(pdu.__class__.__name__, len(data)))
        time.sleep(60)

send()


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

def cargar_dataset():
    train_dataset = h5py.File('datos/train_gato_vs_otros.h5', "r")
    # características del conjunto de entrenamiento
    train_set_x_orig = np.array(train_dataset["train_set_x"][:])
    # etiquetas del conjunto de entrenamiento
    train_set_y_orig = np.array(train_dataset["train_set_y"][:])

    test_dataset = h5py.File('datos/test_gato_vs_otros.h5', "r")
    # características del conjunto de prueba
    test_set_x_orig = np.array(test_dataset["test_set_x"][:])
    # etiquetas del conjunto de pruebas
    test_set_y_orig = np.array(test_dataset["test_set_y"][:])

    # listado de las clases
    clases = np.array(test_dataset["list_classes"][:])

    train_set_y_orig = train_set_y_orig.reshape((1, train_set_y_orig.shape[0]))
    test_set_y_orig = test_set_y_orig.reshape((1, test_set_y_orig.shape[0]))

    return train_set_x_orig, train_set_y_orig, test_set_x_orig, test_set_y_orig, clases

def inicializar_parametros(n_x, n_h, n_y):
    """
    Argumentos:
    n_x -- tamaño de la capa de entrada
    n_h -- tamaño de la capa oculta
    n_y -- tamaño de la capa de salida

    Devuelve:
    parametros -- diccionario python que contiene los parámetros:
                    W1 -- matriz de pesos de la forma (n_h, n_x)
                    b1 -- vector de sesgos de la forma (n_h, 1)
                    W2 -- matriz de pesos de la forma (n_y, n_h)
                    b2 -- vector de sesgos de la forma (n_y, 1)
    """
    np.random.seed(1)
    W1 = np.random.randn(n_h, n_x) * 0.01
    b1 = np.zeros(shape=(n_h, 1))
    W2 = np.random.randn(n_y, n_h) * 0.01
    b2 = np.zeros(shape=(n_y, 1))

    # Asegurar que las dimensiones son las correctas
    assert(W1.shape == (n_h, n_x))
    assert(b1.shape == (n_h, 1))
    assert(W2.shape == (n_y, n_h))
    assert(b2.shape == (n_y, 1))

    parametros = {"W1": W1,
                  "b1": b1,
                  "W2": W2,
                  "b2": b2}

    return parametros

def sigmoide(Z):
    """
    Calcula el sigmoide de Z

    Argumentos:
    Z -- array de NumPy de cualquier dimension (salida lineal de la capa)

    Devuelve:
    A -- resultado de sigmoide(Z), de las mismas dimensiones que Z.
    cache -- devuelve el argumento de entrada, Z, que se utilizara en la retropropagacion.
    """

    A = 1 / (1 + np.exp(-Z))
    cache = Z

    return A, cache

def relu(Z):
    """
    Implementa la funcion ReLU.

    Argumentos:
    Z -- array de NumPy de cualquier dimension (salida lineal de la capa)

    Returns:
    A -- resultado de aplicar relu(Z), de las mismas dimensiones que Z. Es el parametro post-activacion.
    cache -- devuelve el argumento de entrada, Z, que se utilizara en la retropropagacion.
    """

    A = np.maximum(0,Z)

    assert(A.shape == Z.shape)

    cache = Z
    return A, cache

def forward_lineal(A, W, b):
    """
    Implementa la parte lineal de la propagación hacia delante de una capa.

    Argumentos:
    A -- activaciones de la capa anterior (o datos de entrada): (tamaño de la capa anterior, numero de ejemplos).
    W -- matriz de pesos: matriz numpy de dimensiones (tamaño de la capa actual, tamaño de la capa anterior).
    b -- vector de sesgos: matriz numpy de dimensiones (tamaño de la capa actual, 1).

    Devuelve:
    Z -- la entrada de la función de activación, tambien llamado parametro de pre-activacion .
    cache -- una tupla python que contiene "A", "W" y "b" ; almacenada para calcular eficientemente la retropropagación.
    """

    Z = np.dot(W, A) + b
    # Aseguramos que las dimensiones son correctas
    assert(Z.shape == (W.shape[0], A.shape[1]))

    cache = (A, W, b)

    return Z, cache

def forward_activacion_lineal(A_prev, W, b, activacion):
    """
    Implementar la propagación hacia delante para la capa de la forma LINEAL->ACTIVACIÓN

    Argumentos:
    A_prev -- activaciones de la capa anterior (o datos de entrada): (tamaño de la capa anterior, numero de ejemplos)
    W -- matriz de pesos: matriz numpy de forma (tamaño de la capa actual, tamaño de la capa anterior)
    b -- vector de sesgo, matriz numpy de forma (tamaño de la capa actual, 1)
    activacion -- la funcion de activacion a utilizar en esta capa, almacenada como cadena de texto: "sigmoide" o "relu".

    Devuelve:
    A -- la salida de la funcion de activacion, tambien llamada valor post-activación
    cache -- una tupla python que contiene "cache_lineal" y "activacion_cache";
             almacenada para calcular la retropropagacion eficientemente
    """

    if activacion == "sigmoide":
        Z, cache_lineal = forward_lineal(A_prev, W, b)
        A, activacion_cache = sigmoide(Z)
    elif activacion == "relu":
        Z, cache_lineal = forward_lineal(A_prev, W, b)
        A, activacion_cache = relu(Z)
    else:
        print("¡Error! Solo se admiten relu o sigmoide como parametros en \"activacion\"")

    # Aseguramos dimensiones
    assert(A.shape == (W.shape[0], A_prev.shape[1]))

    cache = (cache_lineal, activacion_cache)

    return A, cache

def calcular_coste(AL, Y):
    """
    Calcula el coste de la entropía cruzada dado en la ecuación (3).

    Argumentos:
    AL -- La salida de la activación de la capa L, de dimensiones (1, numero de casos de entrenamiento).
          Corresponde con el vector de probabilidades asociado a las etiquetas verdaderas, de las mismas dimensiones.
    Y -- Vector de etiquetas verdaderas de dimensiones (1, número de casos de entrenamiento).

    Devuelve:
    coste -- coste de la entropia cruzada dada la ecuacion (3).
    """

    # Numero de casos de entrenamiento
    m = Y.shape[1]

    # Calculo de la perdida de entropia cruzada
    # Forma 1
    # perdida = np.multiply(Y, np.log(AL)) + np.multiply(1 - Y, np.log(1 - AL))
    # coste = (-1 / m) * np.sum(perdida)

    # Forma 2
    coste = (1. / m) * (- np.dot(Y, np.log(AL).T) - np.dot(1 - Y, np.log(1 - AL).T))
    coste = np.squeeze(coste) # Por ejemplo, convierte [[17]] en 17

    assert(coste.shape == ())

    return coste

def relu_backward(dA, cache):
    """
    Implementa la propagación hacia atrás para una unica unidad ReLU.

    Argumentos:
    dA -- gradiente post-activación, de cualquier dimension.
    cache -- Es la variable 'Z' que fue almacenada en 'cache' para hacer un calculo de la retropropagacion eficiente.

    Devuelve:
    dZ -- Gradiente del coste con respecto a Z.
    """

    Z = cache
    # Hacemos una copia profunda de dA con 'copy=True', argumento que crea una copia completa de un objeto y todos sus elementos.
    # En el caso de una matriz, esto significa que se crea una nueva matriz con los mismos valores que la matriz original,
    # pero en una ubicación de memoria diferente. Esto significa que si cambias un valor en la matriz original,
    # no afectará a la copia profunda.
    dZ = np.array(dA, copy=True)

    # Cuando Z = 0, podemos decidir si queremos que equivalga a 0 o a 1. En este caso, a 0.
    dZ[Z <= 0] = 0

    # Clave asegurar que las dimensiones son correctas
    assert(dZ.shape == Z.shape)

    return dZ

def sigmoide_backward(dA, cache):
    """
    Implementa la propagación hacia atrás para una unica unidad ReLU.

    Argumentos:
    dA -- gradiente post-activación, de cualquier dimension.
    cache -- Es la variable 'Z' que fue almacenada en 'cache' para hacer un calculo de la retropropagacion eficiente.

    Devuelve:
    dZ -- Gradiente del coste con respecto a Z.
    """

    Z = cache
    # Sigmoide va a ser la ultima capa. dZ es la derivada del coste con respecto a Z de la ultima capa, es decir, dJ/dZ y,
    # segun la regla de la cadena, es equivalente a:
    #       dZ = dJ/dZ = dJ/dA * dA/dZ, donde dJ/dA = dA, y donde dA/dZ = derivada de la funcion 'sigmoide(Z)',
    # y la derivada del sigmoide es: s'(z) = s(z)*(1-s(z))
    # Funcion sigmoide
    s = 1 / (1 + np.exp(-Z))
    # dJ/dZ
    dZ = dA * s * (1 - s)

    # Clave asegurar que las dimensiones son correctas
    assert(dZ.shape == Z.shape)

    return dZ

def backward_lineal(dZ, cache):
    """
    Implementa la parte lineal de la retropropagación para una sola capa (capa l)

    Argumentos:
    dZ -- Gradiente del coste con respecto a la salida lineal (de la capa l actual).
    cache -- tupla de valores (A_prev, W, b) procedentes de forward_lineal() en la capa actual

    Devuelve:
    dA_prev -- Gradiente del coste con respecto a la activación (de la capa anterior l-1); mismas dimensiones que A_prev
    dW -- Gradiente del coste con respecto a W (capa actual l), mismas dimensiones que W
    db -- Gradiente del coste con respecto a b (capa actual l), mismas dimensiones que b
    """
    A_prev, W, b = cache
    m = A_prev.shape[1]

    dW = np.dot(dZ, A_prev.T)/m
    db = np.sum(dZ, axis=1, keepdims=True)/m
    dA_prev = np.dot(W.T, dZ)

    # Aseguramos que las dimensiones sean correctas
    assert(dA_prev.shape == A_prev.shape)
    assert(dW.shape == W.shape)
    assert(db.shape == b.shape)

    return dA_prev, dW, db

def backward_activacion_lineal(dA, cache, activacion):
    """
    Implementar la retropropagacion para la capa de la forma LINEAL->ACTIVACION

    Argumentos:
    dA -- gradiente post-activación para la capa actual l
    cache -- tupla de valores (cache_lineal, activacion_cache) almacenados para calcular la propagación hacia atrás eficientemente
    activation -- la funcion de activacion a utilizar en esta capa, almacenada como cadena de texto: "sigmoide" o "relu".

    Devuelve
    dA_prev -- Gradiente del coste con respecto a la activación (de la capa anterior l-1); mismas dimensiones que A_prev
    dW -- Gradiente del coste con respecto a W (capa actual l), mismas dimensiones que W
    db -- Gradiente del coste con respecto a b (capa actual l), mismas dimensiones que b
    """
    cache_lineal, activacion_cache = cache

    if activacion == "relu":
        dZ = relu_backward(dA, activacion_cache)
        dA_prev, dW, db = backward_lineal(dZ, cache_lineal)
    elif activacion == "sigmoide":
        dZ = sigmoide_backward(dA, activacion_cache)
        dA_prev, dW, db = backward_lineal(dZ, cache_lineal)
    else:
        print("¡Error! Solo se admiten relu o sigmoide como parametros en \"activacion\"")

    return dA_prev, dW, db

def actualizar_parametros(parametros_entrada, gradientes, tasa_aprendizaje):
    """
    Actualiza los parámetros utilizando la regla de actualización por descenso de gradiente.

    Argumentos:
    parametros_entrada -- diccionario python que contiene los parametros
    gradientes -- diccionario python que contiene los gradientes
    tasa_aprendizaje -- hiperparametro que representa la tasa de aprendizaje utilizada en la regla de actualizacion

    Devuelve:
    parametros -- diccionario python que contiene los parametros actualizados
                  parametros["W" + str(l)] = ...
                  parametros["b" + str(l)] = ...
    """
    # Recupera una copia de cada parámetro del diccionario "parametros_entrada".
    parametros = parametros_entrada.copy()
    # numero de capas de la red neuronal (para cada l-esima capa hay un parametro W y otro b)
    L = len(parametros) // 2

    # Aplicar regla de actualizacion a cada parametro
    for l in range(L):
        parametros["W" + str(l + 1)] = parametros["W" + str(l + 1)] - tasa_aprendizaje * gradientes["dW" + str(l + 1)]
        parametros["b" + str(l + 1)] = parametros["b" + str(l + 1)] - tasa_aprendizaje * gradientes["db" + str(l + 1)]

    return parametros

def inicializacion_profunda(dims_capas):
    """
    Argumentos:
    dims_capas -- array python (lista) que contiene las dimensiones de cada capa de nuestra red.

    Devuelve:
    parametros -- diccionario python que contiene los parámetros "W1", "b1", ..., "WL", "bL":
                    Wl -- matriz de pesos de dimensiones (dims_capas[l], dims_capas[l-1])
                    bl -- vector de sesgo de dimensiones (dims_capas[l], 1)
    """

    np.random.seed(1)
    parametros = {}
    # Numero de capas en la red neuronal
    L = len(dims_capas)

    for l in range(1, L):
        # Multiplicar por 0.01 ya no es suficiente con un modelo mas grande
        parametros['W' + str(l)] = np.random.randn(dims_capas[l], dims_capas[l - 1]) / np.sqrt(dims_capas[l-1]) #*0.01
        parametros['b' + str(l)] = np.zeros((dims_capas[l], 1))

        # Asegurar que las dimensiones son correctas
        assert(parametros['W' + str(l)].shape == (dims_capas[l], dims_capas[l - 1]))
        assert(parametros['b' + str(l)].shape == (dims_capas[l], 1))

    return parametros

def propagacion_L_capas(X, parametros):
    """
    Implementa la propagación hacia delante para el cálculo [LINEAL->RELU]*(L-1)->LINEAL->SIGMOIDE

    Argumentos:
    X -- datos, array numpy de dimensiones (caracteristicas por ejemplo, número de ejemplos)
    parametros -- salida de inicializacion_profunda()

    Devuelve:
    AL -- valor de activación de la capa de salida
    caches -- lista de caches que contienen cada cache de forward_activacion_lineal() (hay L, indexadas de 0 a L-1)
    """

    caches = []
    # La activacion inicial, A[0], es la capa de entrada
    A = X
    # numero de capas de la red neuronal (para cada l-esima capa hay un parametro W y otro b)
    L = len(parametros) // 2

    # Implementar [LINEAL -> RELU]*(L-1). Añadimos "cache" a la lista de "caches".
    # El bucle comienza en 1 porque la capa 0 es la de entrada
    for l in range(1, L):
        A_prev = A
        A, cache = forward_activacion_lineal(A_prev,
                                             parametros['W' + str(l)],
                                             parametros['b' + str(l)],
                                             activacion="relu")
        caches.append(cache)

    # Implementar LINEAL -> SIGMOIDE. Añadimos "cache" a la lista de "caches".
    AL, cache = forward_activacion_lineal(A,
                                          parametros['W' + str(L)],
                                          parametros['b' + str(L)],
                                          activacion="sigmoide")
    caches.append(cache)

    # Nos aseguramos que las dimensiones sean correctas (n_y, m)
    assert(AL.shape == (1,X.shape[1]))

    return AL, caches

def retropropagacion_L_capas(AL, Y, caches):
    """
    Implementa la retropropagacion para el grupo [LINEAL->RELU] * (L-1) -> LINEAL -> SIGMOIDE

    Argumentos:
    AL -- vector de predicciones, salida de la propagación hacia delante (propagacion_L_capas())
    Y -- Vector de etiquetas verdaderas de dimensiones (1, número de casos de entrenamiento).
    caches -- lista del cache de cada capa. Contiene:
                cada cache linear_activation_forward("relu") (es caches[l], para l en range(L-1), es decir, l = 0...L-2)
                el cache de linear_activation_forward("sigmoide") (es caches[L-1])

    Devuelve:
    gradientes -- Un diccionario con los gradientes
                  gradientes["dA" + str(l)] = ...
                  gradientes["dW" + str(l)] = ...
                  gradientes["db" + str(l)] = ...
    """
    gradientes = {}
    # Numero de capas
    L = len(caches)
    m = AL.shape[1]
    # Tras esta linea, Y tiene las mismas dimensiones que AL.
    Y = Y.reshape(AL.shape)

    # Inicializar la retropropagacion
    dAL = - (np.divide(Y, AL) - np.divide(1 - Y, 1 - AL))

    # Capa L: gradientes de (SIGMOID -> LINEAL). Entrada: "dAL, cache_actual".
    #                                            Salidas: "gradientes["dAL-1"], gradientes["dWL"], gradientes["dbL"]
    # la capa L es el ultimo valor de la lista "caches", que contiene L elementos, y al empezar el indice por 0, el ultimo es L-1
    cache_actual = caches[L-1] # tambien cache_actual = caches[-1] nos lleva al ultimo elemento
    dA_prev_temp, dW_temp, db_temp = backward_activacion_lineal(dAL, cache_actual, activacion = "sigmoide")
    gradientes["dA" + str(L-1)] = dA_prev_temp
    gradientes["dW" + str(L)] = dW_temp
    gradientes["db" + str(L)] = db_temp

    # Bucle de l=L-2 a l=0 para implementar los gradientes de (RELU -> LINEAL)
    for l in reversed(range(L-1)):
        # Entradas: "gradientes["dA" + str(l + 1)], cache_actual".
        # Salidas: "gradientes["dA" + str(l)] , gradientes["dW" + str(l + 1)] , gradientes["db" + str(l + 1)]
        # La suma (l + 1) es por el hecho de que nuestro indice va de (0, L-1) y no de (1, L), por lo que la equivalencia se
        # logra sumando 1.
        cache_actual = caches[l]
        dA_prev_temp, dW_temp, db_temp = backward_activacion_lineal(gradientes["dA" + str(l + 1)],
                                                                    cache_actual,
                                                                    activacion = "relu")
        # Otra forma equivalente, que es lo que tenemos encapsulado en 2 lineas en la funcion backward_activacion_lineal():
        # dA_prev_temp, dW_temp, db_temp = backward_lineal(relu_backward(grads["dA" + str(l + 1)], current_cache[1]), current_cache[0])
        gradientes["dA" + str(l)] = dA_prev_temp
        gradientes["dW" + str(l + 1)] = dW_temp
        gradientes["db" + str(l + 1)] = db_temp

    return gradientes

# Descomentar para debuggear

# def modelo_L_capas(X, Y, dims_capas, tasa_aprendizaje = 0.0075, num_iteraciones = 3000, dibujar_coste=False):
#     """
#     Implementa una red neuronal de L capas: [LINEAL->RELU]*(L-1)->LINEAL->SIGMOIDE.

#     Argumentos:
#     X -- datos de entrada, de dimensiones (n_x, numero de ejemplos)
#     Y -- vector de etiquetas verdaderas de dimensiones (1, numero de ejemplos)
#     dims_capas -- lista que contiene los tamaños de la capa de entrada y el resto de capas.
#                   Es decir, contiene L + 1 elementos, siendo L el numero de capas de la red
#                   (solo se cuentan ocultas y la de salida)
#     tasa_aprendizaje -- tasa de aprendizaje de la regla de actualización de descenso de gradiente
#     num_iteraciones -- numero de iteraciones del bucle de optimización
#     dibujar_coste -- si True, se imprimira el coste cada 100 iteraciones

#     Devuelve:
#     parametros -- un diccionario que contiene los parametros hallados por el modelo;
#                   pueden utilizarse para hacer predicciones.
#     """

#     np.random.seed(1)
#     # Lista para llevar registro de los costes
#     costes = []

#     # Inicializamos los parametros
#     parametros = inicializacion_profunda(dims_capas)

#     # Bucle que aplica el descenso del gradiente
#     for i in range(0, num_iteraciones):

#         # Forward propagation: [LINEAL -> RELU]*(L-1) -> LINEAL -> SIGMOIDE.
#         AL, caches = propagacion_L_capas(X, parametros)

#         # Calcular el coste
#         coste = calcular_coste(AL, Y)

#         # Backward propagation.
#         gradientes = retropropagacion_L_capas(AL, Y, caches)

#         # Actualizacion de parametros
#         parametros = actualizar_parametros(parametros, gradientes, tasa_aprendizaje)

#         # Mostrar el coste cada 100 iteraciones de entrenamiento
#         if dibujar_coste and i % 100 == 0 or i == num_iteraciones - 1:
#             print("Coste tras cada iteración %i: %f" %(i, np.squeeze(coste)))
#         if i % 100 == 0 or i == num_iteraciones:
#             costes.append(coste)

#     return parametros, costes

# train_set_x_orig, train_y, test_set_x_orig, test_y, clases = cargar_dataset()
# train_set_x_re = train_set_x_orig.reshape(train_set_x_orig.shape[0], -1).T
# test_set_x_re = test_set_x_orig.reshape(test_set_x_orig.shape[0], -1).T
# train_x = train_set_x_re / 255.
# test_x = test_set_x_re / 255.
# L_dims_capas = [12288, 20, 7, 5, 1]
# tasa_aprendizaje_L = 0.0075
# parametros_L, costes_L = modelo_L_capas(train_x, train_y, L_dims_capas, tasa_aprendizaje_L, num_iteraciones = 3000, dibujar_coste=True)


import os
import numpy as np
from tqdm import tqdm
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation

from data_source import DataSource
from settings import videos_folder
from util import parseTimestampToDate

class DataPlot:

    """ This class creates the video with plotted data. """

    axes_all = []
    """ All the axes """
    lines_all = []
    """ All the lines """
    sources_all = []
    """ Data sources where each value is the data to be plotted in a corresponding line """
    axes_legend = None
    """ Axes for legend """
    data_source = None
    """ Instance of DataSource """
    x_points = None
    """ Points to X axis """
    window = 1000
    """ Window of plot (amount of data by second in a frame)"""
    count = None
    """ Amount of data to be ploted in this video part. Equals to length of interval between start and end indexes. """
    part = None
    """ Video part (sequence) """
    start = None
    """ Index the refer the first sample to be plotted in this video part """
    end = None
    """ Index the refer the last sample to be plotted in this video part """
    fig = None
    """ Matplot Figure, where axes and lines are plotted """
    gs = None
    """ Matplotlib Grid Specification """

    @property
    def data(self):
        """Get data loaded.

        Returns:
            dataframe: the data.
        """
        return self.data_source.data

    @property
    def interval(self):
        """
        Get interval between two samples (in ms).

        Returns:
            int: the interval.
        """
        return self.data_source.interval

    @property
    def fps(self):
        """
        Frames per second to create video.

        Returns:
            int: the fps.
        """
        return self.data_source.fps

    @property
    def size(self):
        """
        The dataset size.

        Returns:
            int: the size.
        """
        return self.data_source.size

    @property
    def sample_start(self):
        """
        Sample where starts de video creation.
        It is used for show sample number in the legend.

        Returns:
            int: the index of start.
        """
        return self.data_source.sample_start

    def __init__(self, part, count):
        """
        Initialize the plot class.
        Instanciate a DataSource and load data.

        Args:
            part (int): index sequence of video parts.
            count (int): amount of data to be ploted in this video part.
        """

        self.data_source = DataSource()
        self.data_source.load()
        self.x_points = np.arange(self.size)

        self.part = part
        self.count = self.size if count == -1 else count

        self.start = self.part * self.count
        self.end = min(((self.part + 1) * self.count), self.size) - 1
        self.count = self.end - self.start + 1

    def createLine(self, axes, ydata, color, label):
        """
        Create a line in a plot area.

        Args:
            axes (axes): axes from the subplot.
            ydata (dataframe): data to plot in Y axis.
            color (str): color of line.
            label (str): line label (to legend).
        """

        line, = axes.plot(self.x_points, ydata, color=color, label=label)

        axes.ticklabel_format(useOffset=False, style="plain")
        self.axes_all.append(axes)
        self.lines_all.append(line)
        self.sources_all.append(ydata)

    def createSubPlotLegend(self):
        """
        Create the subplot where is plotted the legend.
        """

        self.axesLegend = self.fig.add_subplot(self.gs[0, 2])
        self.axesLegend.set_axis_off()
        legend = self.axesLegend.legend(handles=self.lines_all[0:4], loc='center')

    def createSubPlotSpeed(self):
        """
        Create the subplot where is plotted speed data in km/h.
        """

        speed_color = 'k'
        speed_label = 'Speed'
        ydata = self.data['speed'] * 3.6

        axes = self.fig.add_subplot(self.gs[0, 0:2])
        axes.set_title('', fontdict={'fontsize': 10})
        axes.set_xlabel('Sample Number')
        axes.set_ylabel('Speed (km/h)')

        self.createLine(axes=axes, ydata=ydata, color=speed_color, label=speed_label)

    def createSubPlot(self, loc, title='', ylabel='', xlabel='', field=''):
        """
        Create the subplot where is plotted the data from accelerometer, gyroscope and magnetometer.

        Args:
            loc (list): location in grid spec.
            title (str, optional): title of subplot. Defaults to ''.
            ylabel (str, optional): label of subplot Y axis. Defaults to ''.
            xlabel (str, optional): label of subplot X axis. Defaults to ''.
            field (str, optional): preffix corresponding to field data type and axis. Defaults to ''.
        """

        axes = self.fig.add_subplot(self.gs[loc[0], loc[1]])
        axes.set_title(title, fontdict={'fontsize': 10})
        axes.set_xlabel(xlabel)
        axes.set_ylabel(ylabel)

        if not (field.startswith('mag')):
            bellow_suspension_color = 'g'
            bellow_suspension_label = 'Below Suspension'
            ydata = self.data[field + '_below_suspension']
            self.createLine(axes=axes, ydata=ydata, color=bellow_suspension_color, label=bellow_suspension_label)

        above_suspension_color = 'b'
        above_suspension_label = 'Above Suspension'
        ydata = self.data[field + '_above_suspension']
        self.createLine(axes=axes, ydata=ydata, color=above_suspension_color, label=above_suspension_label)

        dashboard_color = 'r'
        dashboard_label = 'Dashboard'
        ydata = self.data[field + '_dashboard']
        self.createLine(axes=axes, ydata=ydata, color=dashboard_color, label=dashboard_label)

    def create(self):
        """
        Create the plot area.
        """

        self.fig = plt.figure(self.part, figsize=(16, 9))
        self.gs = self.fig.add_gridspec(nrows=4, ncols=3, wspace=0.25, hspace=0.5)

        self.createSubPlotSpeed()

        self.createSubPlot(loc=[1, 0], field='gyro_x', title='X-Axis', ylabel='Rotation Rate (°/s)')
        self.createSubPlot(loc=[1, 1], field='gyro_y', title='Y-Axis')
        self.createSubPlot(loc=[1, 2], field='gyro_z', title='Z-Axis')

        self.createSubPlot(loc=[2, 0], field='acc_x', ylabel='Acceleration (m/s²)')
        self.createSubPlot(loc=[2, 1], field='acc_y')
        self.createSubPlot(loc=[2, 2], field='acc_z')

        self.createSubPlot(loc=[3, 0], xlabel='Sample Number', field='mag_x', ylabel='Magnetic Field (μT)')
        self.createSubPlot(loc=[3, 1], xlabel='Sample Number', field='mag_y')
        self.createSubPlot(loc=[3, 2], xlabel='Sample Number', field='mag_z')

        self.createSubPlotLegend()

    def show(self):
        """
        Shows a live data plotting.
        """
        self.plot(show=True)

    def save(self):
        """
        Save a video with plotted data.
        """
        self.plot(save=True)

    def clear(self):
        """
        Clear all the lines.
        """

        for line in self.lines_all:
            line.set_data([], [])

    def point(self, i):
        """
        Draw the data of point i in all subplots (lines/axes).

        Args:
            i (int): index point.
        """

        position = self.start + i - 1
        startWindow = max(0, position - self.window)
        endWindow = min(max(1, position), self.end)

        xdata = self.x_points[startWindow:endWindow + 1]

        for j in range(0, len(self.lines_all)):
            ydata = self.sources_all[j][startWindow:endWindow + 1]
            self.lines_all[j].set_data(xdata, ydata)

        for j in range(0, len(self.axes_all)):
            self.axes_all[j].set_xlim(left=startWindow, right=endWindow)
            # self.axes_all[j].relim()
            # self.axes_all[j].autoscale_view()

        self.axesLegend.set_title("Time: " + parseTimestampToDate(self.data['timestamp'].iloc[position]) + " | Sample: " + str(self.sample_start + position), fontdict={'fontsize': 10})

    def plot(self, save=False, show=False):
        """
        Starts process to create a animated data plot (video).
        Each video part generation progress is showed in a loading bar.

        Args:
            save (bool, optional): if plot must be saved to a .mp4 file. Defaults to False.
            show (bool, optional): if plot must be showed in live plot. Defaults to False.
        """

        linesTuple = tuple(self.lines_all)
        load_bar = tqdm(total=self.count, position=self.part, desc='Part ' + str(self.part), ascii=True, ncols=200)

        def init():
            self.clear()
            return linesTuple

        def animate(i):
            self.point(i)
            # fig.canvas.draw()
            return linesTuple

        def progress(current_frame: int, total_frames: int):
            load_bar.update(1)

        anim = FuncAnimation(self.fig, animate, init_func=init, frames=self.count, repeat=False, interval=self.interval)

        if save:
            anim.save(os.path.join(videos_folder, "part_" + str(self.part) + ".mp4"), fps=self.fps, extra_args=['-vcodec', 'libx264'], progress_callback=progress)

        if show:
            plt.show()


import os
import torch
import torch.nn as nn
import torch.optim as optim
import torchvision
import torchvision.models as models
import torchvision.transforms as transforms
import argparse
import importlib
from PIL import ImageFile

ImageFile.LOAD_TRUNCATED_IMAGES = True

def test(model, test_loader, criterion, device, hook, args):
    smd = importlib.import_module('smdebug')
    modes = getattr(smd, 'modes')
    model.eval()
    hook.set_mode(modes.EVAL)
    running_loss = 0
    running_corrects = 0

    for inputs, labels in test_loader:
        inputs = inputs.to(device)
        labels = labels.to(device)
        outputs = model(inputs)
        loss = criterion(outputs, labels)
        _, predictions = torch.max(outputs, 1)
        running_loss += loss.item() * inputs.size(0)
        running_corrects += torch.sum(predictions == labels.data).item()

    total_loss = running_loss / len(test_loader.dataset)
    total_acc = running_corrects / len(test_loader.dataset)
    print(
        f"Test for Hyperparameters, lr: {args.lr}, "
        f"batch size: {args.batch_size}")

    print(f"Testing Loss: {total_loss}, "
          f"Testing Accuracy: {100 * total_acc}")

def train(model, train_loader, validation_loader, loss_criterion, optimizer, device, hook):
    smd = importlib.import_module('smdebug')
    loss_counter = 0
    best_loss = 1e6
    epochs = 30
    hook.set_mode(smd.modes.TRAIN)  # set debugging hook
    image_dataset = {'train': train_loader, 'valid': validation_loader}

    for epoch in range(epochs):
        print(f"Epoch:{epoch}")
        for phase in ['train', 'valid']:
            if phase == 'train':
                model.train()
                hook.set_mode(smd.modes.TRAIN)  # set debugging hook
            else:
                model.eval()
                hook.set_mode(smd.modes.EVAL)
            running_loss = 0.0
            running_corrects = 0

            for inputs, labels in image_dataset[phase]:
                inputs = inputs.to(device)
                labels = labels.to(device)
                outputs = model(inputs)
                loss = loss_criterion(outputs, labels)

                if phase == 'train':
                    optimizer.zero_grad()
                    loss.backward()
                    optimizer.step()

                _, preds = torch.max(outputs, 1)
                running_loss += loss.item() * inputs.size(0)
                running_corrects += torch.sum(preds == labels.data)

            epoch_loss = running_loss // len(image_dataset[phase])
            epoch_acc = running_corrects // len(image_dataset[phase])

            if phase == 'valid':
                if epoch_loss < best_loss:
                    best_loss = epoch_loss
                else:
                    loss_counter += 1

            print('{} loss: {:.4f}, acc: {:.4f}, best loss: {:.4f}'.format(phase,
                                                                           epoch_loss,
                                                                           epoch_acc,
                                                                           best_loss))
        if loss_counter == 1:
            break
        if epoch == 0:
            break
    return model

def model_fn(model_dir):
    model = net()
    with open(os.path.join(model_dir, "model.pth"), "rb") as f:
        model.load_state_dict(torch.load(f))
    return model

def save_model(model, model_dir):
    print(f"Saving the model.")
    path = os.path.join(model_dir, "model.pth")
    torch.save(model.cpu().state_dict(), path)

def net():
    model = models.resnet50(pretrained=True)

    for param in model.parameters():
        param.requires_grad = False
        nfeatures = model.fc.in_features
    model.fc = nn.Sequential(
        nn.Linear(nfeatures, 512),
        nn.ReLU(inplace=True),
        nn.Linear(512, 256),
        nn.ReLU(inplace=True),
        nn.Linear(256, 133))

    return model

def create_data_loaders(data, batch_size):
    transform = transforms.Compose([
        transforms.Resize((224, 224)),
        transforms.ToTensor(),
    ])

    dataset = torchvision.datasets.ImageFolder(data, transform=transform)
    data_loader = torch.utils.data.DataLoader(dataset, batch_size=batch_size, shuffle=True)

    return data_loader

def main(args):
    pytorch = importlib.import_module('smdebug.pytorch')

    device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")

    print(f"Device: {device}")
    print(f'Hyperparameters: {args.lr}, Batch Size: {args.batch_size}')
    print(f'Data paths: {args.data}')

    train_loader = create_data_loaders(os.environ['SM_CHANNEL_TRAIN'], args.batch_size)
    validation_loader = create_data_loaders(os.environ['SM_CHANNEL_VALID'], args.batch_size)
    test_loader = create_data_loaders(os.environ['SM_CHANNEL_TEST'], args.batch_size)

    model = net()
    model = model.to(device)

    criterion = nn.CrossEntropyLoss(ignore_index=133)
    optimizer = optim.SGD(model.fc.parameters(), lr=args.lr)

    hook = pytorch.Hook.create_from_json_file()
    hook.register_hook(model)
    hook.register_loss(criterion)
    print("Training started......")
    model = train(model, train_loader, validation_loader, criterion, optimizer, device, hook)

    print("Testing started......")
    test(model, test_loader, criterion, device, hook, args)

    torch.save(model.cpu().state_dict(), os.path.join(args.model_dir, "model.pth"))

if __name__ == '__main__':
    parser = argparse.ArgumentParser()

    parser.add_argument(
        "--batch_size",
        type=int,
        default=64,
        metavar="N",
        help="input training batch size (default: 64)",
    )
    parser.add_argument(
        "--epochs",
        type=int,
        default=30,
        metavar="N",
        help="number of epochs (default : 30)"
    )
    parser.add_argument(
        "--lr",
        type=float,
        default=0.1,
        metavar="LR",
        help="learning rate (default: 0.1)")

    parser.add_argument('--data', type=str, default=os.environ['SM_CHANNEL_TRAIN'])
    parser.add_argument('--model_dir', type=str, default=os.environ['SM_MODEL_DIR'])
    parser.add_argument('--output_dir', type=str, default=os.environ['SM_OUTPUT_DATA_DIR'])

    args = parser.parse_args()

    main(args)


import numpy as np
import pandas as pd

from os.path import exists, join
from sklearn.metrics import mean_squared_error as mse

from datasets.dataloading import get_all_datasets
from experiments import results_path

def scale_loss(L, L_min, L_max):
    return (L - L_min) / (L_max - L_min)

def main():

    data = get_all_datasets()
    predictor_names = sorted(pd.read_csv('results/test_electricity_hourly_#0.csv', index_col=0).drop(columns=['y', 'best-case-real', 'worst-case-real']).columns.tolist())
    print(predictor_names)
    n_predictors = len(predictor_names)
    n_datasets = len(data)

    norm_loss_matrix = np.zeros((n_datasets, n_predictors))

    for row_idx, (ds_name, ds_index) in enumerate(data):
        ds_path = join(results_path, f'test_{ds_name}_#{str(ds_index)}.csv')
        df_test = pd.read_csv(ds_path, index_col=0)

        # Establish upper and lower loss bounds with worst/bestcase selection
        y = df_test['y'].to_numpy().squeeze()
        best_case_prediction = df_test['best-case-real'].to_numpy().squeeze()
        worst_case_prediction = df_test['worst-case-real'].to_numpy().squeeze()
        L_min = mse(y, best_case_prediction, squared=False)
        L_max = mse(y, worst_case_prediction, squared=False)

        for col_idx, model_name in enumerate(predictor_names):
            pred = df_test[model_name].to_numpy().squeeze()
            L = mse(y, pred, squared=False)
            L_scaled = scale_loss(L, L_min, L_max)
            assert L_scaled >= 0 and L_scaled <= 1, model_name

            norm_loss_matrix[row_idx, col_idx] = L_scaled

    mean_losses = np.mean(norm_loss_matrix, axis=0)
    loss_ranking = np.argsort(-mean_losses)
    for m_name, s_loss in zip(np.array(predictor_names)[loss_ranking], mean_losses[loss_ranking]):
        print(m_name, s_loss)

    print('---------------------------------------')

    loss_ranking = np.argsort(np.argsort(norm_loss_matrix, axis=1), axis=1)
    mean_losses = np.mean(loss_ranking, axis=0)

    sorting = np.argsort(-mean_losses)
    for m_name, m_mean_rank in zip(np.array(predictor_names)[sorting], mean_losses[sorting]):
        print(m_name, m_mean_rank)

if __name__ == '__main__':
    main()


import requests
from datetime import datetime
from bs4 import BeautifulSoup

def show_definitions(soup):
    senseList = []
    senses = soup.find_all('li', class_='sense')
    for s in senses:
        definition = s.find('span', class_='def').text
        print(definition)

word_to_search = 'warp'
scrape_url = 'https://www.oxfordlearnersdictionaries.com/definition/english/' + word_to_search

headers = {"User-Agent": ""}
web_response = requests.get(scrape_url, headers=headers)

if web_response.status_code == 200:
    soup = BeautifulSoup(web_response.text, 'html.parser')

    try:
        show_definitions(soup)
    except AttributeError:
        print('Word not found!!')
else:
    print('Failed to get response...')


import pandas as pd

from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from webdriver_manager.chrome import ChromeDriverManager

from selenium.common.exceptions import NoSuchElementException
from selenium.common.exceptions import StaleElementReferenceException
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions

class Tweet():
    '''
    Extract froms selenium tweet objects the information needed, so it can be
    added to the csv file with the rest of the user's tweets
    '''
    def __init__(self,tweet):
        '''
        Initializate all the css tags of interest.
        '''
        self.tweet = tweet
        self.css_username = 'div.css-901oao.css-bfa6kz.r-18jsvk2.r-1qd0xha.r-a023e6.r-b88u0q.r-ad9z0x.r-bcqeeo.r-3s2u2q.r-qvutc0'
                     # 'div.css-901oao.css-bfa6kz.r-hkyrab.r-1qd0xha.r-a023e6.r-b88u0q.r-ad9z0x.r-bcqeeo.r-3s2u2q.r-qvutc0'
        self.css_user_tag = 'div.css-901oao.css-bfa6kz.r-m0bqgq.r-18u37iz.r-1qd0xha.r-a023e6.r-16dba41.r-ad9z0x.r-bcqeeo.r-qvutc0'
                     # 'div.css-901oao.css-bfa6kz.r-1re7ezh.r-18u37iz.r-1qd0xha.r-a023e6.r-16dba41.r-ad9z0x.r-bcqeeo.r-qvutc0'
        self.css_tweet = 'div.css-901oao.r-18jsvk2.r-1qd0xha.r-a023e6.r-16dba41.r-ad9z0x.r-bcqeeo.r-bnwqim.r-qvutc0'
                  # 'div.css-901oao.r-hkyrab.r-1qd0xha.r-a023e6.r-16dba41.r-ad9z0x.r-bcqeeo.r-bnwqim.r-qvutc0'
        self.css_date =  'div.css-1dbjc4n.r-1d09ksm.r-18u37iz.r-1wbh5a2'
                  # 'a.css-4rbku5.css-18t94o4.css-901oao.r-m0bqgq.r-1loqt21.r-1q142lx.r-1qd0xha.r-a023e6.r-16dba41.r-ad9z0x.r-bcqeeo.r-3s2u2q.r-qvutc0'
                  # 'a.r-1re7ezh.r-1loqt21.r-1q142lx.r-1qd0xha.r-a023e6.r-16dba41.r-ad9z0x.r-bcqeeo.r-3s2u2q.r-qvutc0.css-4rbku5.css-18t94o4.css-901oao'
        self.css_retweet = 'a.css-4rbku5.css-18t94o4.css-901oao.r-m0bqgq.r-1loqt21.r-1qd0xha.r-a023e6.r-16dba41.r-ad9z0x.r-bcqeeo.r-qvutc0'
                    # 'a.css-4rbku5.css-18t94o4.css-901oao.r-1re7ezh.r-1loqt21.r-1qd0xha.r-a023e6.r-16dba41.r-ad9z0x.r-bcqeeo.r-qvutc0'
    def descomposing_tweet(self,driver):

        '''
        Decompose the selenium web element into the elements of interest, return a dictionary to
        to the pandas variable along the rest of the tweets of the user
        '''
        #USERNAME
        self._waiting_func(driver,self.css_username)
        username = self.tweet.find_element_by_css_selector(self.css_username).text

        #USER_TAG
        self._waiting_func(driver,self.css_user_tag)
        user_tag = self.tweet.find_element_by_css_selector(self.css_user_tag).text

        # TEXT
        try:
            self._waiting_func(driver,self.css_tweet)
            text = self.tweet.find_element_by_css_selector(self.css_tweet).text
        except:
            text = 'No text'

        #DATE
        self._waiting_func(driver,self.css_date)
        date_ob = tweet.find_element_by_css_selector(self.css_date)
        ad = date_ob.find_elements_by_tag_name("time")
        #attrs = driver.execute_script('var items = {}; for (index = 0; index < arguments[0].attributes.length; ++index) { items[arguments[0].attributes[index].name] = arguments[0].attributes[index].value }; return items;', ad[0])
        last_date = datetime.datetime.strptime(ad[0].get_attribute('datetime'), "%Y-%m-%dT%H:%M:%S.000Z")

        # Replies, Retweets & Likes
        self._waiting_func(driver,"div[data-testid='reply']")
        replies = self.tweet.find_element_by_css_selector("div[data-testid='reply']").text
        retweets = self.tweet.find_element_by_css_selector("div[data-testid='retweet']").text
        likes = self.tweet.find_element_by_css_selector("div[data-testid='like']").text

        # Retweet?
        # Needs to be reviewed
        self._waiting_func(driver,self.css_retweet)
        try:
            if 'Retweeted' in tweet.find_element_by_css_selector(self.css_retweet).text:
                retweet = True
            else:
                retweet = False
        except:
            retweet = False

        # New row for the pandas with all the info of the current tweet
        tweet_row = {'Username':username,'User_tag':user_tag,'Date':last_date,'Tweet':text,'Replies':replies,'Retweets':retweets,'Likes':likes,'Retweet':retweet}
        return tweet_row

    def _waiting_func(self,driver,my_element_id):
        ignored_exceptions=(NoSuchElementException,StaleElementReferenceException)
        try: #$find_element_by_class_name
            WebDriverWait(driver, 4,ignored_exceptions=ignored_exceptions).until(expected_conditions.presence_of_element_located((By.CSS_SELECTOR, my_element_id)))
        except : #(NoSuchElementException, TimeoutException)
            print('Loading took too much time, may be not results')

class User():
    '''
    Class to track all the tweets of the user given the period of time
    '''
    def __init__(self,joined_date,user_tag):
        # time variables
        self.joined_date = joined_date
        self.finish_time  = date(2020,11,13)
        self.proposed_start_time = date(2019,10,1)
        # self.start_time is a dynamic variable that will constanly change
        self.start_time = _find_start_time()

        self.user_tag = user_tag
        self.status = status
        self.tweets = pd.DataFrame(columns=['Username','User_tag','Date','Tweet','Replies','Retweets','Likes','Retweet'])

    def get_timeframe_for_crawling(self):
        end_time = self.start_time + timedelta(days=5)
        start_time_str = str(self.start_time.year) + '-' + str(self.start_time.month) + '-' + str(self.start_time.day)
        end_time_str = str(end_time.year) + '-' + str(end_time.month) + '-' + str(end_time.day)
        print('Period: ' + start_time_str + ' / ' + end_time_str)
        # at the end of the iteration end_time has to be the new self.start_time
        return start_time_str,end_time_str,end_time

    def retrieving_tweets_from_user(self,driver,end_time):
        '''
        Given a timeframe and an username, this method find all the tweet this users
        made during the period of time given. It scrolls down the page until it has
        collected all the tweets
        '''

        times = 0
        previous_location = None
        red_flag = 0
        done = False
        last_location = 0
        while True:
            print('**********************')
            print('Number of time it has scrolled down: ',times)
            print('**********************')
            times += 1
            # Check if there are tweets for this period of time
            # $$$$$$$$$$$$$$$$$$$$$$$$$$$$
            # CREATE A METHOD TO CHECK RIGHT AWAY IF THERE IS ANY TWEET IN THIS PERIOD OF TIME
            # $$$$$$$$$$$$$$$$$$$$$$$$$$$$
            init_height = driver.execute_script("return document.body.scrollHeight")
            time.sleep(0.2)

            # 'div.css-1dbjc4n.r-my5ep6.r-qklmqi.r-1adg3ll.r-1ny4l3l'
            css_tweets = 'div.css-1dbjc4n.r-j7yic.r-qklmqi.r-1adg3ll.r-1ny4l3l'
            self._waiting_func(driver,css_tweets)
            tweets = driver.find_elements_by_css_selector(css_tweets)

            print('Number of tweets found: ',len(tweets))
            if len(tweets) > 0:
                for i in range(len(tweets)):
                    # checking each of the tweets

                    cur_tweet = Tweet(tweets[i])
                    tweet_row = cur_tweet.descomposing_tweet(driver)
                    self.tweets = pd_user.append(tweet_row,ignore_index=True)
                    time.sleep(0.2)
                    last_location = tweets[i].location['y']
                    print(self.tweets.shape)

            driver.execute_script("window.scrollBy(0, 1700);")
            time.sleep(1)
            finish_height = driver.execute_script("return document.body.scrollHeight")

            if last_location == previous_location:
                red_flag += 1

            if red_flag > 1:
                print('Reached bottom')
                done = True
                driver.close()
                break

            previous_location = last_location
            self.start_time = end_time

    def stop_crawling_user(self):
        '''
        Checks if we have reach the end of the timeframe that we are crawling
        '''
        if self.start_time >= self.finish_time:
            return True
        else:
            return False

    def _waiting_func(self,driver,my_element_id):
        ignored_exceptions=(NoSuchElementException,StaleElementReferenceException)
        try: #$find_element_by_class_name
            WebDriverWait(driver, 4,ignored_exceptions=ignored_exceptions).until(expected_conditions.presence_of_element_located((By.CSS_SELECTOR, my_element_id)))
        except : #(NoSuchElementException, TimeoutException)
            print('Loading took too much time, may be not results')

    def _find_start_time(self):
        '''
        Establish as start date the lastest out of the proposed and the joined date
        '''
        if self.joined_date > self.proposed_start_time:
            start_time = self.joined_date
        else:
            start_time = self.proposed_start_time
        return start_time

    def save_tweets(self):
        self.tweets = self.tweets.drop_duplicates()
        self.tweets.to_csv('Users_tweets/' + self.user_tag + '.csv')

    @classmethod
    def init_user(cls,username):

        # Get the date when the user joined Twitter
        joined_date = cls.user_join_date(username)

        return cls(joined_date,username)

    @staticmethod
    def user_join_date(user_tag):
        '''
        Finds the date a user joined to twitter, by getting this date, we avoid the cralwer to
        scrape dates when the user was not even on twotter, makes things faster

        Args:   @input - user_tag of the user e.g.: @realDonalTrump
                @output - datetime variable
        '''
        query = "https://twitter.com/" + str(user_tag)

        #driver = webdriver.Chrome(chrome_driver_binary,options=options)
        driver = webdriver.Chrome(options=options)

        driver.get(query)
        time.sleep(1)
        aa = driver.find_elements_by_css_selector('span.css-901oao.css-16my406.r-m0bqgq.r-4qtqp9.r-1qd0xha.r-ad9z0x.r-zso239.r-bcqeeo.r-qvutc0')
        joined_date = datetime.datetime.strptime(aa[-1].text.replace('Joined ',''), "%B %Y").date()
        return joined_date


import tempfile
import unittest

import yaml

import config_helper
import print_config

class PrintConfigTest(unittest.TestCase):
  def test_load_yaml_file(self):
    schema = config_helper.Schema.load_yaml(
        """
        properties:
          propertyInt:
            type: int
          propertyString:
            type: string
        """)
    with tempfile.NamedTemporaryFile('w') as f:
      f.write("""
              propertyInt: 3
              propertyString: abc
              """.encode('utf_8'))
      f.flush()

      values = config_helper.load_values(f.name, '/non/existence/dir', 'utf_8',
                                         schema)
      self.assertEqual({'propertyInt': 3, 'propertyString': 'abc'}, values)

  def test_output_shell_vars(self):
    self.assertEqual(
        '$propertyInt $propertyString',
        print_config.output_shell_vars({'propertyInt': 1,
                                        'propertyString': 'Value'}))

  def test_output_yaml(self):
    values = {'propertyInt': 1,
              'propertyString': 'unnested',
              'dotted.propertyInt': 2,
              'dotted.propertyString': 'nested_1',
              'double.propertyInt': 3,
              'double.propertyString': 'nested_2',
              'double.dotted.propertyInt': 4,
              'double.dotted.propertyString': 'double_nested_1'}
    actual = print_config.output_yaml(values)
    self.assertEquals(yaml.load(actual), {
      'propertyInt': 1,
      'propertyString': 'unnested',
      'dotted': {
        'propertyInt': 2,
        'propertyString': 'nested_1'
      },
      'double': {
        'propertyInt': 3,
        'propertyString': 'nested_2',
        'dotted': {
          'propertyInt': 4,
          'propertyString': 'double_nested_1'
        },
      }
    })

  def test_output_param(self):
    values = {'propertyInt': 1,
              'propertyString': 'Value'}
    schema = config_helper.Schema.load_yaml(
        """
        properties:
          propertyInt:
            type: int
          propertyString:
            type: string
        """)
    self.assertEqual('1',
                     print_config.output_param(values, schema,
                                               {'name': 'propertyInt'}))
    self.assertEqual('Value',
                     print_config.output_param(values, schema,
                                               {'name': 'propertyString'}))

  def test_output_param_multiple(self):
    values = {'property1': 'Value1',
              'property2': 'Value2'}
    schema = config_helper.Schema.load_yaml(
        """
        properties:
          property1:
            type: string
          property2:
            type: string
        """)
    self.assertRaises(
        print_config.InvalidParameter,
        lambda: print_config.output_param(values, schema, {'type': 'string'}))


import argparse
import sys
from sudoku import Sudoku
from ac3 import AC3
from backtrack import recursive_backtrack_algorithm
from utils import fetch_sudokus, print_grid

"""
default sudokus' grid
"""
sudokus = dict(
    easy = "000079065000003002005060093340050106000000000608020059950010600700600000820390000",
    medium = "102004070000902800009003004000240006000107000400068000200800700007501000080400109",
    hard = "002008050000040070480072000008000031600080005570000600000960048090020000030800900")

"""
solve
solves a sudoku based on its String grid
"""
def solve(grid, index, total):

    print("\nSudoku {}/{} : \n{}".format(index, total, print_grid(grid)))

    print("{}/{} : AC3 starting".format(index, total))

    # instanciate Sudoku
    sudoku = Sudoku(grid)

    # launch AC-3 algorithm of it
    AC3_result = AC3(sudoku)

    # Sudoku has no solution
    if not AC3_result:
        print("{}/{} : this sudoku has no solution".format(index, total))

    else:

        # check if AC-3 algorithm has solve the Sudoku
        if sudoku.isFinished():

            print("{}/{} : AC3 was enough to solve this sudoku !".format(index,total))
            print("{}/{} : Result: \n{}".format(index, total, sudoku))

        # continue the resolution
        else:

            print("{}/{} : AC3 finished, Backtracking starting...".format(index,total))

            assignment = {}

            # for the already known values
            for cell in sudoku.cells:

                if len(sudoku.possibilities[cell]) == 1:
                    assignment[cell] = sudoku.possibilities[cell][0]

            # start backtracking
            assignment = recursive_backtrack_algorithm(assignment, sudoku)

            # merge the computed values for the cells at one place
            for cell in sudoku.possibilities:
                sudoku.possibilities[cell] = assignment[cell] if len(cell) > 1 else sudoku.possibilities[cell]

            if assignment:
                print("{}/{} : Result: \n{}".format(index, total, sudoku))

            else:
                print("{}/{} : No solution exists".format(index, total))

if __name__ == "__main__":

    # argument parsing using argparse module
    # doc @ https://docs.python.org/3/library/argparse.html
    parser = argparse.ArgumentParser(description='Solve a Sudoku with CSP')
    parser.add_argument('--string', type=str, help='sudoku as a String, can be multiple sudokus in a row in the same line')
    parser.add_argument('--level', type=str, default='medium', choices=['easy', 'medium', 'hard'], help='selects default sudoku\'s based on level (default: %(default)s)')
    args = parser.parse_args()

    sudoku_grid_as_string = ""

    # if user didnt provide a custom string or its string has not the right format
    if not args.string:

        # let's use the level's default
        sudoku_grid_as_string = sudokus[args.level]
        print("\nUsing default sudoku, level : {}".format(args.level))

    else:

        sudoku_grid_as_string = args.string

    # fetch sudokus from user input
    sudoku_queue = fetch_sudokus(sudoku_grid_as_string)

    # for each sudoku, solve it !
    for index, sudoku_grid in enumerate(sudoku_queue):
        solve(sudoku_grid, index + 1, len(sudoku_queue))


from cProfile import label
import sys,os
sys.path.append(os.path.join(os.getcwd(), '.'))
import aditofpython.aditofpython as tof
import numpy as np
import cv2 as cv

from enum import Enum

import matplotlib.pyplot as plt
import time

from time import perf_counter

from scipy.interpolate import make_interp_spline, BSpline
from scipy import interpolate

WINDOW_NAME_DEPTH = "Display Depth"
WINDOW_NAME_COLOR = "Display Color"
configFile = "aditofpython/tof-viewer_config.json"

class ModesEnum(Enum):
    MODE_NEAR = 0
    MODE_MEDIUM = 1
    MODE_FAR = 2

# def fps_plot(data_list, data):
#     raw_data = data[1:len(data)-1]
#     data_con = raw_data.split(", ")

#     for i in data_con :
#         i_int = int(float(i))
#         data_list.append(i_int)
#     y_axis = np.array(data_list)
#     x_axis = np.array([x for x in range (len(y_axis))])
#     nx_axis = np.linspace(1, len(x_axis), 800)
#     bspline = interpolate.make_interp_spline(x_axis,y_axis)
#     ny_axis = bspline(nx_axis)

#     plt.figure("Frame Rate")
#     plt.plot(x_axis, y_axis)
#     plt.plot(nx_axis, ny_axis)
#     plt.xlabel('Sample')
#     plt.ylabel('Frame')
#     plt.show()
#     plt.close()

# def time_plot(data_list, data):
#     raw_data = data[1:len(data)-1]
#     data_con = raw_data.split(", ")

#     for i in data_con :
#         i_int = float (i)
#         data_list.append(i_int)
#     y_axis = np.array(data_list)
#     x_axis = np.array([x for x in range (len(y_axis))])

#     # Smoothen
#     nx_axis = np.linspace(0, len(x_axis), 600)
#     bspline = interpolate.make_interp_spline(x_axis,y_axis)
#     ny_axis = bspline(nx_axis)

#     # Plot
#     plt.figure("Processing Time")
#     plt.plot(x_axis, y_axis)
#     plt.plot(nx_axis, ny_axis)
#     plt.xlabel('Sample')
#     plt.ylabel('Depth Time')
#     plt.show()
#     plt.close()

def fps_time_plot(data_list1, data1, data_list2, data2):

    # FPS Plot
    fps_raw_data = data1[1:len(data1)-1]
    fps_data_con = fps_raw_data.split(", ")

    for i in fps_data_con :
        i_int = int(float (i))
        data_list1.append(i_int)
    y_axis1 = np.array(data_list1)
    x_axis1 = np.array([x for x in range (len(y_axis1))])

    # Smoothen
    nx_axis1 = np.linspace(0, len(x_axis1)-1, 1000)
    bspline1 = make_interp_spline(x_axis1,y_axis1)
    ny_axis1 = bspline1(nx_axis1)

    # Time Plot
    time_raw_data = data2[1:len(data2)-1]
    time_data_con = time_raw_data.split(", ")

    for i in time_data_con :
        i_int = float (i)
        data_list2.append(i_int)
    y_axis2 = np.array(data_list2)
    x_axis2 = np.array([x for x in range (len(y_axis2))])

    # Smoothen
    nx_axis2 = np.linspace(1, len(x_axis2)-1, 800)
    bspline2 = make_interp_spline(x_axis2,y_axis2)
    ny_axis2 = bspline2(nx_axis2)

    fig, (ax1, ax2) = plt.subplots(1,2)
    fig.suptitle("ADTF3175D")
    ax1.plot(nx_axis1,ny_axis1)
    ax1.set_title('Frame Rate (fps)')
    ax1.set(xlabel='Sample', ylabel='FPS')
    ax2.plot(nx_axis2, ny_axis2, 'tab:orange')
    ax2.set_title('Processing Time (ms)')
    ax2.set(xlabel='Sample', ylabel='Depth time')

    plt.tight_layout()
    plt.show()
    plt.close()

if __name__ =="__main__":

    system = tof.System()

    cameras = []
    status = system.getCameraListAtIp(cameras,"10.42.0.1")
    if not status:
        print("system.getCameraList() failed with status: ", status)
    status = cameras[0].setControl("initialization_config", configFile)
    if not status:
        print("cameras[0].setControl() failed with status: ", status)

    status = cameras[0].initialize()
    if not status:
        print("cameras[0].initialize() failed with status: ", status)

    modes = []
    status = cameras[0].getAvailableModes(modes)
    if not status:
        print("system.getAvailableModes() failed with status: ", status)

    status = cameras[0].setMode(modes[ModesEnum.MODE_MEDIUM.value])
    if not status:
        print("cameras[0].setMode() failed with status: ", status)

    print (f"\nstatus {status} setMode\n")

    types = []
    status = cameras[0].getAvailableFrameTypes(types)
    if not status:
        print("system.getAvailableFrameTypes() failed with status: ", status)

    print (f"\nstatus {status}getavailableframetype {types} \n")

    status = cameras[0].setFrameType(types[0]) # types[2] is 'mp_pcm' type.

    print (f"\nstatus {status} the error is{cameras[0].setFrameType(types[0])}\n ")

    if not status:
        print("cameras[0].setFrameType() failed with status:", status)

    status = cameras[0].start()
    if not status:
        print("cameras[0].start() failed with status:", status)

    camDetails = tof.CameraDetails()
    status = cameras[0].getDetails(camDetails)
    if not status:
        print("system.getDetails() failed with status: ", status)

    # Enable noise reduction for better results
    smallSignalThreshold = 100
    cameras[0].setControl("noise_reduction_threshold", str(smallSignalThreshold))

    camera_range = 5000
    bitCount = 9
    frame = tof.Frame()

    max_value_of_IR_pixel = 2 ** bitCount - 1
    distance_scale_ir = 255.0 / max_value_of_IR_pixel
    distance_scale = 255.0 / camera_range

    # Time -initial
    prev_frame_time = 0
    new_frame_time = 0

    pre_fps = 0
    smoothing = 0.9

    before_get_data = 0
    after_get_data = 0

    ir_prev_time = 0
    ir_aft_time = 0

    dp_prev_time = 0

    # Data stamps -initial
    save_fps = []
    save_time = []

    while True:
        # Capture frame-by-frame
        status = cameras[0].requestFrame(frame)
        if not status:
            print("cameras[0].requestFrame() failed with status: ", status)

        # Capture depth -frame -time
        dp_prev_time = time.perf_counter()
        depth_data = frame.getData("depth")
        after_get_data = perf_counter()
        dp_time = after_get_data - dp_prev_time

        # Capture ir -frame -time
        ir_prev_time = time.perf_counter()
        ir_data = frame.getData("ir")
        ir_aft_time = time.perf_counter()
        ir_time = ir_aft_time -ir_prev_time

        # Capture processing -time
        total_time = dp_time - ir_time
        ct_time = total_time*1000

        display_total_time = str("{:.2f}".format(ct_time)) + " ms"

        # Latency
        latency = after_get_data - before_get_data
        before_get_data = after_get_data
        # latency = int(latency)

        depth_map = np.array(depth_data, dtype="uint16", copy=False)
        ir_map = np.array(ir_data, dtype="uint16", copy=False)

        new_shape = (int(depth_map.shape[0] / 2), depth_map.shape[1])
        depth_map = np.resize(depth_map, new_shape)
        depth_map = cv.flip(depth_map, 1)
        distance_map = depth_map
        depth_map = distance_scale * depth_map
        depth_map = np.uint8(depth_map)
        depth_map = cv.applyColorMap(depth_map, cv.COLORMAP_RAINBOW)

        # ir_map = ir_map[0: int(ir_map.shape[0] / 2), :]
        # ir_map = distance_scale_ir * ir_map
        # ir_map = np.uint8(ir_map)
        # ir_map = cv.flip(ir_map, 1)
        # ir_map = cv.cvtColor(ir_map, cv.COLORMAP_RAINBOW)

        # result = cv.addWeighted(ir_map, 0.4, depth_map, 0.6, 0)

        new_frame_time=perf_counter()

        acutal = 1 / (new_frame_time-prev_frame_time)
        prev_frame_time = new_frame_time

        fps = (pre_fps * smoothing) + (acutal * (1-smoothing))
        pre_fps = fps

        display_fps = str(int(fps)) + " fps"

        save_fps.append(fps)
        save_time.append(ct_time)

        # print (f"\nthe latency is : {latency}\n")
        print ("Frame: " + display_fps)
        print("Time: " + display_total_time)

        cv.putText(depth_map, display_fps, (7, 20), cv.FONT_HERSHEY_DUPLEX, 0.7, (255, 255, 255), 1)
        cv.putText(depth_map, display_total_time, (7, 45), cv.FONT_HERSHEY_DUPLEX, 0.7, (255, 255, 255), 1)
        # cv.putText(depth_map, display_total_time_converted, (7, 90), cv.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 2, cv.LINE_8)
        # cv.putText(depth_map, display_fps, (7, 90), cv.FONT_HERSHEY_SIMPLEX, 0.8, (255, 255, 255), 2, cv.LINE_8)

        cv.namedWindow(WINDOW_NAME_DEPTH, cv.WINDOW_AUTOSIZE)
        cv.imshow(WINDOW_NAME_DEPTH, depth_map)

        if cv.waitKey(1) >= 0:
            break
        if len(save_fps) == 1000:
            break

    # Fps data
    with open ("fps-log.txt","w") as file :
        file.write(str(save_fps))

    with open ("fps-log.txt","r") as file :
        fps_data = file.read()
    fps_data_list = []

    # Time data
    with open ("time-log.txt","w") as file :
        file.write(str(save_time))
    with open ("time-log.txt","r") as file :
        time_data = file.read()
    time_data_list = []

    # fps_plot(fps_data_list, fps_data)
    # time_plot(time_data_list, time_data)

    fps_time_plot(fps_data_list, fps_data, time_data_list, time_data)


from collections.abc import Iterator
import contextlib
import os
import random
import re

import numpy as np

from tensorflow.core.framework import types_pb2
from tensorflow.core.protobuf import config_pb2
from tensorflow.core.protobuf import rewriter_config_pb2
from tensorflow.python.client import session
from tensorflow.python.compiler.xla import jit
from tensorflow.python.eager import context
from tensorflow.python.framework import dtypes
from tensorflow.python.framework import ops
from tensorflow.python.framework import random_seed
from tensorflow.python.framework import test_util
from tensorflow.python.ops import array_ops
from tensorflow.python.ops import variables
from tensorflow.python.platform import flags
from tensorflow.python.platform import test
from tensorflow.python.platform import tf_logging as logging

FLAGS = flags.FLAGS

flags.DEFINE_string('test_device', None,
                    'Tensorflow device on which to place operators under test')
flags.DEFINE_string('types', None, 'Types to test. Comma-separated list.')
flags.DEFINE_string('disabled_manifest', None,
                    'Path to a file with a list of tests that should not run.')
flags.DEFINE_string('tf_xla_flags', None,
                    'Value to set the TF_XLA_FLAGS environment variable to')

def parse_disabled_manifest(manifest_content):
  comments_re = re.compile('#.*$')
  disabled_tests = []
  disabled_method_types = []
  for l in manifest_content.splitlines():
    stripped = comments_re.sub('', l).strip()
    if not stripped:
      continue
    entry = stripped.split(' ')
    if len(entry) == 1:
      disabled_tests.append(entry[0])
    elif len(entry) == 2:
      disabled_method_types.append((entry[0], entry[1].strip().split(',')))
    else:
      raise ValueError('Bad entry in manifest file.')

  disabled_regex = '|'.join(disabled_tests)
  method_types_filter = {}
  for method, types in disabled_method_types:
    method_types_filter[method] = set([
        dtypes.as_dtype(types_pb2.DataType.Value(name)).as_numpy_dtype
        for name in types
    ])
  return disabled_regex, method_types_filter

class TPURewriteSession(session.Session):
  """Tensorflow session that runs tpu.rewrite() on ops on run()."""

  def __init__(self, *args, **kwargs):
    super().__init__(*args, **kwargs)
    self.topology = None

  def run(self, fetches, feed_dict=None, options=None, run_metadata=None):
    from tensorflow.python.tpu import tpu  # pylint: disable=g-import-not-at-top
    if self.topology is None:
      self.topology = super().run(tpu.initialize_system())
      assert self.topology is not None
    fetch_mapper = session._FetchMapper.for_fetch(fetches)
    new_fetches = []
    for fetch in fetch_mapper.unique_fetches():
      if isinstance(fetch, ops.Operation):
        fetch = tpu.rewrite(lambda fetch=fetch: fetch)
      new_fetches.append(fetch)
    rewritten_fetches = fetch_mapper.build_results(new_fetches)
    return super().run(rewritten_fetches, feed_dict, options, run_metadata)

class XLATestCase(test.TestCase):
  """XLA test cases are parameterized test cases."""

  def __init__(self, method_name='runTest'):
    super(XLATestCase, self).__init__(method_name)
    if 'XLA' in FLAGS.test_device:
      context.context().enable_xla_devices()

    # Check if the mlir bridge has been explicitly enabled or disabled. If
    # is_mlir_bridge_enabled() returns None, the user did not explictly enable
    # or disable the bridge so do not update enable_mlir_bridge.
    if test_util.is_mlir_bridge_enabled():
      context.context().enable_mlir_bridge = True
    elif test_util.is_mlir_bridge_enabled() is not None:
      context.context().enable_mlir_bridge = False

    self.device = FLAGS.test_device
    self.has_custom_call = (self.device == 'XLA_CPU')

    # Some tests (e.g. ftrl_ops) only work if the program goes through the
    # _TPUCompileMLIR op. They will set this flag to True.
    # TODO(kramm): Flip to true (and enable MLIR bridge) for more tests.
    self.rewrite_ops_for_tpu = False

    self._all_tf_types = set([
        dtypes.as_dtype(types_pb2.DataType.Value(name))
        for name in FLAGS.types.split(',')
    ])
    self.int_tf_types = set([
        dtype for dtype in self._all_tf_types if dtype.is_integer
    ])
    self._float_tf_types = set([
        dtype for dtype in self._all_tf_types if dtype.is_floating
    ])
    self.complex_tf_types = set([
        dtype for dtype in self._all_tf_types if dtype.is_complex
    ])
    self._numeric_tf_types = set(
        self.int_tf_types | self._float_tf_types | self.complex_tf_types)
    self.quantized_tf_types = set(
        dtype for dtype in self._all_tf_types if dtype.is_quantized)

    # Quantized types don't have a numpy equivalent, include them in
    # all_tf_types but not in all_types.
    # TODO(b/115960798): Parametrize tests on TF types instead of numpy types
    # and remove all_types.
    self._all_types = set(dtype.as_numpy_dtype
                          for dtype in self._all_tf_types
                          if not dtype.is_quantized)
    self._int_types = set([dtype.as_numpy_dtype for dtype in self.int_tf_types])
    self.signed_int_types = set(dtype.as_numpy_dtype
                                for dtype in self.int_tf_types
                                if not dtype.is_unsigned)
    self.unsigned_int_types = set(dtype.as_numpy_dtype
                                  for dtype in self.int_tf_types
                                  if dtype.is_unsigned)
    self._float_types = set(
        [dtype.as_numpy_dtype for dtype in self._float_tf_types])
    self.complex_types = set([
        dtype.as_numpy_dtype for dtype in self.complex_tf_types
    ])
    self._numeric_types = set(self._int_types | self._float_types
                              | self.complex_types)

    # Parse the manifest file, if any, into a regex identifying tests to
    # disable
    # TODO(xpan): Make it text proto if it doesn't scale.
    # Each line of the manifest file specifies an entry. The entry can be
    # 1) TestNameRegex  // E.g. CumprodTest.* Or
    # 2) TestName TypeName  // E.g. AdamOptimizerTest.testSharing DT_BFLOAT16
    # The 1) disables the entire test. While 2) only filter some numeric types
    # so that they are not used in those tests.
    self.disabled_regex = None
    self._method_types_filter = {}

    if FLAGS.disabled_manifest is not None:
      with open(FLAGS.disabled_manifest, 'r') as manifest_file:
        disabled_regex, self._method_types_filter = (
            parse_disabled_manifest(manifest_file.read()))
        if disabled_regex:
          self.disabled_regex = re.compile(disabled_regex)

    if FLAGS.tf_xla_flags is not None:
      os.environ['TF_XLA_FLAGS'] = FLAGS.tf_xla_flags

  @property
  def all_tf_types(self):
    name = '{}.{}'.format(type(self).__name__, self._testMethodName)
    tf_types = set([dtypes.as_dtype(t)
                    for t in self._method_types_filter.get(name, set())])
    return self._all_tf_types - tf_types

  @property
  def float_types(self):
    name = '{}.{}'.format(type(self).__name__, self._testMethodName)
    return self._float_types - self._method_types_filter.get(name, set())

  @property
  def float_tf_types(self):
    name = '{}.{}'.format(type(self).__name__, self._testMethodName)
    return self._float_tf_types - self._method_types_filter.get(name, set())

  @property
  def int_types(self):
    name = '{}.{}'.format(type(self).__name__, self._testMethodName)
    return self._int_types - self._method_types_filter.get(name, set())

  @property
  def numeric_tf_types(self):
    name = '{}.{}'.format(type(self).__name__, self._testMethodName)
    tf_types = set([dtypes.as_dtype(t)
                    for t in self._method_types_filter.get(name, set())])
    return self._numeric_tf_types - tf_types

  @property
  def numeric_types(self):
    name = '{}.{}'.format(type(self).__name__, self._testMethodName)
    return self._numeric_types - self._method_types_filter.get(name, set())

  @property
  def all_types(self):
    name = '{}.{}'.format(type(self).__name__, self._testMethodName)
    return self._all_types - self._method_types_filter.get(name, set())

  def setUp(self):
    super(XLATestCase, self).setUp()
    name = '{}.{}'.format(type(self).__name__, self._testMethodName)
    if self.disabled_regex is not None and self.disabled_regex.match(name):
      logging.info('Disabled test case: %s', name)
      self.skipTest('{} is disabled by manifest.'.format(name))
      return
    logging.info('Start test case: %s', name)

    random.seed(random_seed.DEFAULT_GRAPH_SEED)
    np.random.seed(random_seed.DEFAULT_GRAPH_SEED)

  def tearDown(self):
    super(XLATestCase, self).tearDown()
    logging.info('End test case: %s', self._testMethodName)

  @contextlib.contextmanager
  def session(self) -> Iterator[session.Session]:
    """Custom implementation of session() for XLA tests.

    We override the standard Tensorflow session() since it is too
    specific to CPU and GPU tests. In particular, we want to disable soft
    placement and explicitly assign ops to devices under test.

    Yields:
      A session to use when running a test case.
    """
    graph = ops.Graph()
    config = context.context().config

    # Grappler can constant fold TensorListFromTensor ops into DT_VARIANT
    # constants which XLA does not understand.  So disable constant folding in
    # these tests.
    config.graph_options.rewrite_options.constant_folding = (
        rewriter_config_pb2.RewriterConfig.OFF)

    if self.rewrite_ops_for_tpu:
      session_type = TPURewriteSession
    else:
      session_type = session.Session

    with session_type(graph=graph, config=config) as sess, graph.as_default():
      yield sess

  def cached_session(self):
    raise NotImplementedError(
        'cached_session not supported on XLATestCase, please use session')

  def test_session(self):
    raise NotImplementedError(
        'test_session not supported on XLATestCase, please use session')

  @contextlib.contextmanager
  def device_scope(self):
    """Scope that runs tests on `self.device`.

    Yields:
      A scope to apply to the operators under test.
    """
    with ops.device('device:{}:0'.format(self.device)):
      yield

  def test_scope(self):
    """Deprecated alias of `device_scope`.

    This should be avoided as the name starts with `test`, so test runners
    treat it as a test. This interferes with class decorators that operate on
    each test method.
    """
    return self.device_scope()

def Benchmark(tf_bench,
              builder_fn,
              use_xla_jit,
              device,
              separate_compiled_gradients=False):
  """Build a graph and run benchmarks against it, with or without XLA.

  Args:
    tf_bench: An instance of tf.test.Benchmark, used to run the benchmark.
    builder_fn: A function that builds a graph when invoked, and returns
        (name, fetches), where name is the name of the test, and fetches
        is a list of tensors to fetch as output.
    use_xla_jit: If true compile with the XLA JIT, otherwise use regular TF.
    device: The tensorflow device to run on, e.g. "cpu", "gpu".
    separate_compiled_gradients: If true put each gradient subgraph into a
      separate compilation scope. This gives fine-grained control over which
      portions of the graph will be compiled as a single unit. Compiling
      gradients separately may yield better performance for some graphs.
      The scope is named based on the scope of the forward computation as well
      as the name of the gradients. As a result, the gradients will be compiled
      in a scope that is separate from both the forward computation, and from
      other gradients.
  """

  with ops.Graph().as_default():
    name = None
    targets = []
    with ops.device(device):
      fetches = []
      jit_scope = jit.experimental_jit_scope
      with jit_scope(
          compile_ops=use_xla_jit,
          separate_compiled_gradients=separate_compiled_gradients):
        name, fetches = builder_fn()

      # We only want to benchmark the operations themselves, and not the data
      # transfer of the result(s).  Non-compiled identity ops ensure XLA
      # doesn't know we're dropping the results, otherwise it might compile
      # away the entire computation.
      for fetch in fetches:
        targets.append(array_ops.identity(fetch).op)

    # TODO(b/132430685):  Should we allow soft placement here?
    config = config_pb2.ConfigProto(allow_soft_placement=True)
    with session.Session(config=config) as sess:
      sess.run(variables.global_variables_initializer())
      xla = 'xla_' if use_xla_jit else ''
      tf_bench.run_op_benchmark(
          sess, targets, name='%s_%s%s' % (name, xla, device))


# coding: utf-8
# Standard Python libraries
import io
from typing import Optional, Tuple, Union

# https://github.com/usnistgov/DataModelDict
from DataModelDict import DataModelDict as DM

# https://github.com/usnistgov/yabadaba
from yabadaba.record import Record

class Link(Record):
    """
    Class for describing website link
    """
    def __init__(self,
                 model: Union[str, io.IOBase, DM, None] = None,
                 name: Optional[str] = None,
                 database = None,
                 **kwargs):
        """
        Initializes a Link object providing a hyperlink to content. Note that
        this is meant as a component class for other record objects.

        Parameters
        ----------
        model : str, file-like object or DataModelDict, optional
            A JSON/XML data model for the content.
        name : str, optional
            The name to assign to the record.  Not used by this class.
        database : yabadaba.Database, optional
            Allows for a default database to be associated with the record.
        url : str, optional
            URL for the link.
        label : str, optional
            A short description label.
        linktext : str, optional
            The text for the link, i.e. what gets clicked on.
        """
        assert name is None, 'name is not used by this class'
        assert database is None, 'database is not used by this class'
        super().__init__(model=model, name=name, database=database, **kwargs)

    @property
    def modelroot(self) -> str:
        """str: The root element of the content"""
        return 'link'

    @property
    def xsl_filename(self) -> Tuple[str, str]:
        """tuple: The module path and file name of the record's xsl html transformer"""
        return ('potentials.xsl', 'link.xsl')

    @property
    def xsd_filename(self) -> Tuple[str, str]:
        """tuple: The module path and file name of the record's xsd schema"""
        return ('potentials.xsd', 'link.xsd')

    @property
    def url(self) -> Optional[str]:
        """str: URL for the link"""
        return self.__url

    @url.setter
    def url(self, v: Optional[str]):
        if v is None:
            self.__url = None
        else:
            self.__url = str(v)

    @property
    def label(self) -> Optional[str]:
        """str or None: short descriptive label"""
        return self.__label

    @label.setter
    def label(self, v: Optional[str]):
        if v is None:
            self.__label = None
        else:
            self.__label = str(v)

    @property
    def linktext(self) -> Optional[str]:
        """str or None: text to show for the link"""
        return self.__linktext

    @linktext.setter
    def linktext(self, v: Optional[str]):
        if v is None:
            self.__linktext = None
        else:
            self.__linktext = str(v)

    def set_values(self,
                   name: Optional[str] = None,
                   **kwargs):
        """
        Sets an Artifact object's attributes

        Parameters
        ----------
        name : str, optional
            The name to assign to the record.  Not used by this class.
        url : str, optional
            URL for the link.
        label : str, optional
            A short description label.
        linktext : str, optional
            The text for the link, i.e. what gets clicked on.
        """
        assert name is None, 'name is not used by this class'
        self.linktext = kwargs.get('linktext', None)
        self.label = kwargs.get('label', None)
        self.url = kwargs.get('url', None)

    def load_model(self,
                   model: Union[str, io.IOBase, DM],
                   name: Optional[str] = None):
        """
        Loads the object info from data model content

        Parameters
        ----------
        model : str, file-like object or DataModelDict
            A JSON/XML data model for the content.
        name : str, optional
            The name to assign to the record.  Not used by this class.
        """
        assert name is None, 'name is not used by this class'
        link = DM(model).find('link')
        self.url = link['web-link'].get('URL', None)
        self.label = link['web-link'].get('label', None)
        self.linktext = link['web-link'].get('link-text', None)

    def build_model(self) -> DM:
        """
        Returns the object info as data model content

        Returns
        ----------
        DataModelDict: The data model content.
        """
        model = DM()
        model['link'] = DM()
        model['link']['web-link'] = DM()
        if self.url is not None:
            model['link']['web-link']['URL'] = self.url
        if self.label is not None:
            model['link']['web-link']['label'] = self.label
        if self.linktext is not None:
            model['link']['web-link']['link-text'] = self.linktext

        return model

    def metadata(self) -> dict:
        """
        Generates a dict of simple metadata values associated with the record.
        Useful for quickly comparing records and for building pandas.DataFrames
        for multiple records of the same style.
        """
        meta = {}
        meta['linktext'] = self.linktext
        meta['label'] = self.label
        meta['url'] = self.url

        return meta