from pyspark import SparkContext
import re
import random
import numpy as np
import hashlib
import time
import sys
from datetime import datetime

'''
    Receive a document and return a set of shingles
'''
def shingling(doc, k=6):
    shingles = []
    for i in range(len(doc[1]) - k + 1):
        shingles.append(doc[1][i:i+k])

    return (doc[0],set(shingles))

'''
    Given a set `doc_shingles`, pass each member of the set through all permutation
    functions, and set the `ith` position of `vec` to the `ith` permutation
    function's output if that output is smaller than `vec[i]`.
'''
def minhash(doc):

    # initialize a minhash of length N with positive infinity values
    doc_shingles = doc[1]
    signature_vector = [float('inf') for i in range(N)]

    for val in doc_shingles:

        # ensure doc_shingles is composed of integers
        if not isinstance(val, int): val = hash(val)

        # loop over each "permutation function"
        for perm_idx, perm_vals in enumerate(perms):
            a, b = perm_vals

            # pass `val` through the `ith` permutation function
            hash_value = ((a * val + b) % LARGE_PRIME)

            # conditionally update the `ith` value of vec
            signature_vector[perm_idx] = min(signature_vector[perm_idx], hash_value)
            #if signature_vector[perm_idx] > hash_value:
                #signature_vector[perm_idx] = hash_value

    # the returned vector represents the minimum hash of the set s
    return doc[0], signature_vector

'''
    Calculate the Jaccard Similarity beetween two sets
'''
def jacc_similarity(a,b):
    a = set(a)
    b = set(b)
    return len(a.intersection(b))/len(a.union(b))

'''
    check if two lists have in common, at least, one element
'''
def common_data(list1, list2):
    result = False
    # traverse in the 1st list
    for x in list1:

        # traverse in the 2nd list
        for y in list2:

            # if one common
            if sorted(x) == sorted(y):
                result = True
                return result

    return result

'''
    Get similar pairs of docs - "candidates"
'''
def get_candidates(bands, signatures_dict):
    bands_processed = []
    pairs_candidates = []
    docs = list(bands.keys())
    for i1 in range(len(docs)):
        for i2 in range(i1+1, len(docs)):
            band1 = bands[docs[i1]]
            band2 = bands[docs[i2]]
            if common_data(band1, band2):
                sign1 = signatures_dict[docs[i1]]
                sign2 = signatures_dict[docs[i2]]
                jaccard_similarity = jacc_similarity(sign1, sign2)
                pairs_candidates.append((docs[i1],docs[i2],jaccard_similarity))

    return pairs_candidates

'''
    Bands creation
'''
def split_vector(doc, b=20, r=5):
    signature = doc[1]
    # code splitting signature in b parts
    subvecs = []
    for i in range(0, len(signature), r):
        subvecs.append(signature[i : i+r])
    return doc[0], subvecs

'''
    Given a movie, returns all other movies that are at least 80% similar in terms of their plots but no more than 98%
'''
def similar_movies(movie, similar_candidates, treshold=0.8):
    sim_movies = []
    for m in similar_candidates:
        if (movie == m[0]) and m[2]>0.8 and m[2]<0.98:
            sim_movies.append(m[1])
        if (movie == m[1]) and m[2]>0.8 and m[2]<0.98:
            sim_movies.append(m[0])

    return sim_movies

'''
    Get False Positives rate
'''
def fp_evaluation(shingles_dict, sim_candidates):
    fp = 0
    for cand in sim_candidates:
        doc1, doc2, sim = cand
        shingle1 = shingles_dict[doc1]
        shingle2 = shingles_dict[doc2]
        real_similarity = jacc_similarity(shingle1,shingle2)

        if sim > 0.8 and real_similarity < 0.8: fp+=1

    return (fp/len(sim_candidates))

'''
    Get False Negatives rate
'''
def fn_evaluation(shingles_dict, sim_candidates):
    fn = 0
    total_candidates = 0
    doc_candidates = []
    docs = list(shingles_dict.keys())
    for id1 in range(len(docs)):
        for id2 in range(id1+1, len(docs)):
            sig1 = shingles_dict[docs[id1]]
            sig2 = shingles_dict[docs[id2]]
            similarity_sig = jacc_similarity(sig1,sig2)
            total_candidates += 1
            if similarity_sig > 0.8:
                doc_candidates.append((docs[id1],docs[id2]))

    false_negatives = len(set(sim_candidates) - set(doc_candidates))
    return (fn/total_candidates)

if __name__ == "__main__":
    file = "assign1/data/small_plot_sum.txt"
    try:

        file = sys.argv[1]
        k = int(sys.argv[2])
        r = int(sys.argv[3])
        b = int(sys.argv[4])

    except:
        print("Usage: movies.py <int:k> <int:rows> <int:bands>")
        exit(1)

    sc = SparkContext(master='local', appName="Assignment1_E2")
    LARGE_PRIME = 4294967311

    begin = time.time()
    data = sc.textFile(file)
    item_baskets = data.map(lambda line: re.split('\t', line.lower()))

    #Shingling
    docs_shingles = item_baskets.map(lambda line: shingling(line, k))
    #docs_shingles = item_baskets.map(shingling)
    print(docs_shingles.count())
    print("Time elapsed to shingling: ",time.time()-begin)

    # Min Hashing
    # specify the length of each minhash vector
    N = 128
    #max_val = (2**32)-1
    max_val = 500

    # create N tuples that will serve as permutation functions
    # these permutation values are used to hash all input sets
    perms = [ (random.randint(0,max_val), random.randint(0,max_val)) for i in range(N)]

    # get the signature vectors for each doc
    begin = time.time()
    signatures = docs_shingles.map(minhash)
    print(signatures.count())
    print("Time elapsed to hashing: ",time.time()-begin)

    '''
    Find as candidates at least 99.5% of pairs with 80% similarity and less than 5% of pairs with 40%
    similarity.
    '''
    begin = time.time()
    signatures_dict = { doc:sign for doc,sign in signatures.collect() }
    bands = signatures.map(lambda line: split_vector(line, b, r))
    bands_dict = { doc:band for doc,band in bands.collect() }

    sim_candidates = get_candidates(bands_dict, signatures_dict)
    print("Time elapsed to get candidate pairs: ",time.time()-begin)
    print("Number of Candidates: ",len(sim_candidates))

    #Ex: 2.2 - Similar movies
    movie = "31186339"
    sim_movies = similar_movies(movie, sim_candidates)
    print("Movies similar to {}: {}".format(movie, sim_movies))

    #Ex: 2.3
    shingles_dict = { doc:shingle for doc,shingle in docs_shingles.collect() }

    # FP rate
    if(len(sim_candidates) != 0):
        fp_eval = fp_evaluation(shingles_dict, sim_candidates)
        print("FP eval: ",fp_eval)
    else:
        print("Number of Candidates = 0 !")

    # FN rate
    fn_eval = fn_evaluation(shingles_dict, sim_candidates)
    print("FN eval: ",fn_eval)

    # Save results
    similar_pairs_rdd = sc.parallelize(sim_candidates).sortBy(lambda line: -line[2])
    format_time = str(datetime.now().strftime("%Y-%m-%dT%H_%M_%S"))
    similar_pairs_rdd.saveAsTextFile("{0}/{1}".format("assign1/results", format_time))
    sc.stop()


from configs import *
from utils import *

from flask import Flask
app = Flask(__name__)
@app.route('/', methods=['POST'])

def main():

    #Ações que devem ser realizadas pelos comandos
    #####################################################################

    #Videoke - Listas Prontas
    #############################
    def consultar_videoke(call,listaVideoke):
        bot.send_message(call.message.chat.id,'Você selecionou a opção Consultar Videokê Tutu! Baixando a lista do BQ...',parse_mode='html')
        bot.send_chat_action(call.message.chat.id,"typing")

        select = f"SELECT Interprete, Nome, Codigo FROM `videokebot.videoke.videoke` where {listaVideoke} = 'Sim' order by Interprete Asc, Nome Asc"
        df_query = query_table(select)

        bot.send_chat_action(call.message.chat.id,"typing")

        listaFinalVideoke = df_query.values.tolist()
        lenListaFinalVideoke = len(str(listaFinalVideoke))

        enviador_lista_call(call,lenListaFinalVideoke,listaFinalVideoke)
    #############################

    #Videoke - Ferramenta de Busca
    #############################
    def buscador(call,tipoBusca):
        mensagemBot = bot.send_message(call.message.chat.id,'Você selecionou a opção por Busca! Por favor, digite o nome do Artista ou Canção',parse_mode='html')

        if tipoBusca == "Artista":
            bot.register_next_step_handler(mensagemBot, busca_handler_videoke_artista)
        else:
            bot.register_next_step_handler(mensagemBot, busca_handler_videoke_cancao)
    #############################

    #Handler para pegar o input do usuário - Artista
    #####################################################################
    def busca_handler_videoke_artista(message):
        busca = message.text.lower()

        bot.send_message(message.chat.id, f"Abrindo o livro do Videoke...")

        select = f"SELECT Interprete, Nome, Codigo FROM `videokebot.videoke.videoke` where REGEXP_CONTAINS(lower(Interprete),'{busca}') order by Interprete Asc, Nome Asc"
        bot.send_chat_action(message.chat.id,"typing")
        df_query = query_table(select)
        listaFinalVideoke = df_query.values.tolist()
        lenListaFinalVideoke = len(str(listaFinalVideoke))
        enviador_lista_message(message,lenListaFinalVideoke,listaFinalVideoke)
    #####################################################################

    #Handler para pegar o input do usuário - Canção
    #####################################################################
    def busca_handler_videoke_cancao(message):
        busca = message.text.lower()

        bot.send_message(message.chat.id, f"Abrindo o livro do Videoke...")

        select = f"SELECT Interprete, Nome, Codigo FROM `videokebot.videoke.videoke` where REGEXP_CONTAINS(lower(Nome),'{busca}') order by Interprete Asc, Nome Asc"
        bot.send_chat_action(message.chat.id,"typing")
        df_query = query_table(select)
        listaFinalVideoke = df_query.values.tolist()
        lenListaFinalVideoke = len(str(listaFinalVideoke))
        enviador_lista_message(message,lenListaFinalVideoke,listaFinalVideoke)
    #####################################################################

    #Acionador Inicial
    #####################################################################
    @bot.message_handler(content_types=["text"])
    def responder(message):
        print(message) #Útil para debugar
        if message.chat.id == USER_ID_1 or message.chat.id == USER_ID_2:
            first_name = message.from_user.first_name
            last_name = message.from_user.last_name
            textoEntrada = f'Olá, <b>{first_name} {last_name}</b>! Bem vindo ao VideokêBot!'
            bot.reply_to(message,textoEntrada,parse_mode='html')

            envia_menu(message)
        else:
            texto = f"O seu id de usuário ({message.chat.id}) não é um id autorizado! Caso conheça o Tutu, mande este número para que ele te dê acesso."
            bot.reply_to(message,texto)
    #####################################################################

    #Acionador Funções
    #####################################################################
    @bot.callback_query_handler(func=lambda message: True)
    def callback_query(call):
        if call.data == "consultarVideokeTutuMpb":
            bot.answer_callback_query(call.id, "Consultar Videokê Tutu - MPB!")
            listaVideoke = "FavoritoTutuNacionalMPB"
            consultar_videoke(call,listaVideoke)

        if call.data == "consultarVideokeTutuNacionais":
            bot.answer_callback_query(call.id, "Consultar Videokê Tutu - Nacionais!")
            listaVideoke = "FavoritoTutuNacionalOutros"
            consultar_videoke(call,listaVideoke)

        if call.data == "consultarVideokeTutuInternacionais":
            bot.answer_callback_query(call.id, "Consultar Videokê Tutu - Internacionais!")
            listaVideoke = "FavoritoTutuInternacional"
            consultar_videoke(call,listaVideoke)

        if call.data == "consultarVideokeCarolNacionais":
            bot.answer_callback_query(call.id, "Consultar Videokê Carol - Nacionais!")
            listaVideoke = "FavoritoCarolNacional"
            consultar_videoke(call,listaVideoke)

        if call.data == "consultarVideokeCarolInternacionais":
            bot.answer_callback_query(call.id, "Consultar Videokê Carol - Internacionais!")
            listaVideoke = "FavoritoCarolInternacional"
            consultar_videoke(call,listaVideoke)

        if call.data == "buscarArtista":
            bot.answer_callback_query(call.id, "Você optou por Buscar Artista!")
            tipoBusca = "Artista"
            buscador(call,tipoBusca)

        if call.data == "buscarCancao":
            bot.answer_callback_query(call.id, "Você optou por Buscar Canção!")
            tipoBusca = "Cancao"
            buscador(call,tipoBusca)
    #####################################################################

    #Ativa o bot.
    bot.infinity_polling(timeout=10, long_polling_timeout = 5)

    return "OK"

#Comente quando for subir pra núvem. Útil para testes locais.
print(main())


# Python program to implement a basic Pong game
from turtle import Screen, Turtle
from paddle import Paddle
from ball import Ball
from score_keeper import ScoreBoard

PADDLE_RIGHT_X = 390
PADDLE_LEFT_X = -390

# Create the screen
screen = Screen()
screen.title("Python Pong Game")
screen.bgcolor("black")
screen.setup(width=900, height=700)
screen.tracer(0)
print(screen.screensize(800, 600))

# Drawing the border
boundary = Turtle()
boundary.hideturtle()
boundary.pensize(1)
boundary.color("white")
boundary.penup()
boundary.goto(-400, 300)
boundary.pendown()
for _ in range(2):
    boundary.forward(800)
    boundary.right(90)
    boundary.forward(600)
    boundary.right(90)

boundary.penup()
boundary.goto(0, 295)
boundary.pensize(2)
# boundary.color("white")
boundary.right(90)
for _ in range(30):
    boundary.pendown()
    boundary.forward(10)
    boundary.penup()
    boundary.forward(10)

# Create and move paddle objects
paddle_right = Paddle(20, 100, PADDLE_RIGHT_X, 0)
paddle_left = Paddle(20, 100, PADDLE_LEFT_X, 0)
screen.update()

# Create and move the ball object
ball = Ball()

# Create scoreboards
score_board_left = ScoreBoard(-400, "LEFT")
score_board_right = ScoreBoard(400, "RIGHT")

screen.listen()
screen.onkeypress(key="Up", fun=paddle_right.move_up)
screen.onkeypress(key="Down", fun=paddle_right.move_down)
screen.onkeypress(key="w", fun=paddle_left.move_up)
screen.onkeypress(key="s", fun=paddle_left.move_down)

game_on = True
while game_on:
    screen.update()
    ball.move()  # Starts with upward movement for a new game

    # Detect collision with the top and bottom walls and bounce
    if ball.ycor() > 281 or ball.ycor() < -280:  # Check for top and lower walls
        ball.bounce_up_down()

    # Detect collision with the paddle and bounce back
    if ball.xcor() >= PADDLE_RIGHT_X - 21 and ball.distance(paddle_right) < 50:
        print(f"Ball hit paddle {ball.distance(paddle_right)}")
        ball.bounce_front_back()

    if ball.xcor() <= PADDLE_LEFT_X + 21 and ball.distance(paddle_left) < 50:
        ball.bounce_front_back()

    # Detect if ball goes beyond the right and left walls
    if ball.xcor() > 400:  # Right player loses point
        print("Point for Left Player")
        score_board_left.increase_score()  # Keep scorecard
        ball.color("red")
        screen.update()
        ball.reset_position()
    elif ball.xcor() < -400:  # Left player loses point
        print("Point for Right Player")
        score_board_right.increase_score()  # Keep scorecard
        ball.color("red")
        screen.update()
        ball.reset_position()

screen.exitonclick()


import tensorflow as tf
from PIL import Image
import keras
from keras.datasets import mnist
from keras.layers import Dense, Dropout
from keras.models import Sequential
from keras.optimizers import Adam, SGD
from keras.callbacks import EarlyStopping
import matplotlib.pyplot as plt
from keras.preprocessing import image

(train_x, train_y), (test_x, test_y) = mnist.load_data()
train_x = train_x.astype('float32') / 255
test_x = test_x.astype('float32') / 255

print(train_x.shape)
print(test_x.shape)
print(train_y.shape)
print(test_y.shape)

train_x = train_x.reshape(60000, 784)
test_x = test_x.reshape(10000, 784)
print(train_x.shape)
print(test_x.shape)

train_y = keras.utils.to_categorical(train_y, 10)
test_y = keras.utils.to_categorical(test_y, 10)

model = Sequential()
model.add(Dense(512, activation='relu', input_shape=(784, )))
model.add(Dense(256, activation='relu'))
model.add(Dense(256, activation='relu'))
model.add(Dense(128, activation='relu'))
model.add(Dense(10, activation="softmax"))
model.summary()
model.compile(optimizer=Adam(0.001), loss="categorical_crossentropy", metrics=["accuracy"])
model.load_weights("/home/wasi/Desktop/scrapy_projects/DL_Projects/Project/mnistmodel.h5")
#
# my_callBacks = [EarlyStopping(monitor='val_acc', patience=5, mode=max)]
# model.fit(train_x, train_y, batch_size=32, epochs=15, verbose=1,
#           callbacks=my_callBacks, validation_data=(test_x, test_y))
# model.save("mnistmodel.h5")
# accuracy = model.evaluate(x=test_x, y=test_y, batch_size=32)
# print("Accuracy of the model is --> {}".format(accuracy[1] * 100))

img = image.load_img(path="/home/wasi/Downloads/1_0D7K4JZNABjK2RMQyM5zVQ.png", color_mode='grayscale',
                     target_size=(28, 28, 1))
img = image.img_to_array(img)
test_img = img.reshape((1, 784))
img_class = model.predict_classes(test_img)
prediction = img_class[0]
classname = img_class[0]
print("Class: ", classname)
img = img.reshape((28, 28))
plt.imshow(img)
plt.title(prediction)
plt.show()


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

# In[23]:

from typing import Optional, Callable, Tuple, List, NoReturn
from functools import partial, reduce

import matplotlib.pyplot as plt
import matplotlib.image as img

import numpy as np
import cv2 as cv
import PIL as pil

import importlib

# In[4]:

# User-defined functions, utils module found in the same directory as Erosion.ipynb
import utils as utils
importlib.reload(utils)
from utils import binarise, side_by_side, rescale_img, reverse, structuring_circle, opening, closing

# In[5]:

# Importamos todas nuestras funciones:
import mfilt_funcs as mine
importlib.reload(mine)
from mfilt_funcs import *

# In[5]:

x = img.imread('imagenes/Im1T4.png')

# In[6]:

plt.imshow(x, cmap='gray')

# In[7]:

x = reverse(x)

# In[8]:

plt.imshow(x, cmap='gray')

# In[9]:

binaria = binarise(x)
plt.imshow(binaria, cmap='gray')

# In[10]:

help(opening)

# # Opening

# In[11]:

kernel = np.ones((10, 10))
side_by_side(binaria, opening(binaria, kernel), title1='Original', title2=f'opening() with Kernel {kernel.shape}')

# In[12]:

kernel = np.ones((10, 10))
side_by_side(binaria, cv.morphologyEx(binaria, cv.MORPH_OPEN, kernel), title1='Original', title2=f'cv.MORPH_OPEN with Kernel {kernel.shape}')

# In[13]:

kernel = np.ones((2, 50))
side_by_side(binaria, opening(binaria, kernel), title1='Original', title2=f'opening() with Kernel {kernel.shape}')

# In[14]:

kernel = np.ones((2, 50))
side_by_side(binaria, cv.morphologyEx(binaria, cv.MORPH_OPEN, kernel), title1='Original', title2=f'cv.MORPH_OPEN with Kernel {kernel.shape}')

# In[15]:

kernel = np.ones((50, 2))
side_by_side(binaria, opening(binaria, kernel), title1='Original', title2=f'opening() with Kernel {kernel.shape}')

# In[16]:

kernel = np.ones((50, 2))
side_by_side(binaria, cv.morphologyEx(binaria, cv.MORPH_OPEN, kernel), title1='Original', title2=f'cv.MORPH_OPEN with Kernel {kernel.shape}')

# As Gonzalez explained in the book, an opening is nothing but an erosion followed by a dilation.
# Our custom function ```opening(image, kernel)``` yields the same result as executing ```cv.morphologyEx(image, cv.MORPH_OPEN, kernel)```

# # Closing

# In[17]:

kernel = np.ones((10, 10))
side_by_side(binaria, closing(binaria, kernel), title1='Original', title2=f'closing() with Kernel {kernel.shape}')

# In[18]:

kernel = np.ones((10, 10))
side_by_side(binaria, cv.morphologyEx(binaria, cv.MORPH_CLOSE, kernel), title1='Original', title2=f'cv.MORPH_CLOSE with Kernel {kernel.shape}')

# In[19]:

kernel = np.ones((2, 50))
side_by_side(binaria, closing(binaria, kernel), title1='Original', title2=f'closing() with Kernel {kernel.shape}')

# In[20]:

kernel = np.ones((2, 50))
side_by_side(binaria, cv.morphologyEx(binaria, cv.MORPH_CLOSE, kernel), title1='Original', title2=f'cv.MORPH_CLOSE with Kernel {kernel.shape}')

# In[21]:

kernel = np.ones((50, 2))
side_by_side(binaria, closing(binaria, kernel), title1='Original', title2=f'closing() with Kernel {kernel.shape}')

# In[22]:

kernel = np.ones((50, 2))
side_by_side(binaria, cv.morphologyEx(binaria, cv.MORPH_CLOSE, kernel), title1='Original', title2=f'cv.MORPH_CLOSE with Kernel {kernel.shape}')

# As Gonzalez explained in the book, the closing operation is nothing but a dilation followed by an erosion.
# Our custom function ```closing(image, kernel)``` yields the same result as executing ```cv.morphologyEx(image, cv.MORPH_CLOSE, kernel)```

# # Example found on page 643

# In[23]:

figura = cv.imread('imagenes/figura.png', 0) / 255.0
figura = reverse(figura)
figura.shape, figura.dtype

# In[24]:

plt.imshow(figura, cmap='gray')

# In[25]:

figura2 = binarise(figura)
plt.imshow(figura2, cmap='gray')

# In[26]:

kernel = cv.getStructuringElement(cv.MORPH_ELLIPSE, (20, 20))
side_by_side(figura2, cv.morphologyEx(figura2, cv.MORPH_OPEN ,kernel), title1='Original', title2=f'OPEN : Kernel {kernel.shape}')

# In[27]:

side_by_side(figura2, cv.morphologyEx(figura2, cv.MORPH_CLOSE ,kernel), title1='Original', title2=f'CLOSE : Kernel {kernel.shape}')

# In[28]:

plt.figure(figsize=(15,10))
plt.imshow(cv.imread('imagenes/opening.png'))
plt.title('Opening, acording to Gonzalez', size = 18)

# In[29]:

plt.figure(figsize=(15,10))
plt.imshow(cv.imread('imagenes/closing.png'))
plt.title('Closing, acording to Gonzalez', size = 18)

#

# # Idempotence property
#
# 1. $$ (A \circ B) \circ B = A \circ B $$
#
# Through induction, one arrives to the conclusion that this operation can be repeated indefinitely and the result will always be the same as one opening.

# In[30]:

kernel = cv.getStructuringElement(cv.MORPH_RECT, (5, 5))
kernel

# In[31]:

idemPot = figura2.copy()
for i in range(50):
    idemPot = cv.morphologyEx(idemPot, cv.MORPH_OPEN, kernel)
    side_by_side(figura2, idemPot, title1='Original', title2=f'OPEN : Kernel {kernel.shape}, iter = {i+1}')

# In[118]:

plt.close('all')

# In[108]:

kernel = cv.getStructuringElement(cv.MORPH_CROSS, (15, 15))
kernel

# In[110]:

idemPot = figura2.copy()
for i in range(50):
    idemPot = cv.morphologyEx(idemPot, cv.MORPH_OPEN, kernel)
    side_by_side(figura2, idemPot, title1='Original', title2=f'OPEN : Kernel {kernel.shape}, iter = {i+1}')

# In[32]:

plt.close('all')

# In[156]:

#kernel = cv.getStructuringElement(cv.MORPH_ELLIPSE, (12, 12))
kernel = structuring_circle(radius=12)
#kernel

# In[157]:

#side_by_side(figura2, opening(figura2, kernel), title1='Original', title2=f'OPEN : Kernel {kernel.shape}, iter = {i+1}')

# In[158]:

idemPot = figura2.copy()
for i in range(50):
    idemPot = cv.morphologyEx(idemPot, cv.MORPH_OPEN, kernel)
    side_by_side(figura2, idemPot, title1='Original', title2=f'OPEN : Kernel {kernel.shape}, iter = {i+1}')

# In[149]:

plt.close('all')

# It seems that OpenCV's implementation of the ellyptical/circular structuring element is kind of poor, i.e. its lack of precision breaks the idempotence property of Opening.
# Creating a better structuring element (i.e. having it to be symmetrical at least) will result in idempotence being respected.
#
# That's why I created ```structuring_circle()``` found in ```.utils.py``` which gives a decent circle mask.

# In[10]:

fing = cv.imread('imagenes/fingerprint.png', 0) / 255
fing = binarise(fing)
fing.shape, fing.dtype

# In[11]:

plt.imshow(fing, cmap='gray')

# In[14]:

kernel = structuring_circle(radius=2)
fing_filtered = closing(opening(fing, kernel), kernel)
side_by_side(fing, fing_filtered)

# In[20]:

plt.imshow(cv.bitwise_and(fing, fing_filtered), cmap='gray')

# In[86]:

fing2 = fing.copy()
fing2[:,100:300] = 0
plt.imshow(fing2, cmap='gray')

# In[87]:

fing3 = fing.copy()
fing3[100:300,:] = 0
plt.imshow(fing3, cmap='gray')

# In[88]:

fing4 = cv.bitwise_or(fing2, fing3)
side_by_side(fing4, cv.bitwise_not(fing4))

# In[67]:

reduce(lambda x, y: x and y, (fing == fing3).flatten())

# In[69]:

if np.equal(fing, fing3).prod():
    print('iguales')

# In[37]:

_x.prod()

# In[47]:

fing[1,1:24], reverse(fing[1,1:24])

# In[91]:

merged = reduce(lambda x, y: cv.bitwise_or(x, y), [fing2, fing3, cv.bitwise_not(fing)])
plt.imshow(merged, cmap='gray')

# In[97]:

type(fing[0,0])

# In[99]:

class DontCare(np.float64):
    """

    """
    def __mul__(self, lol):
        return lol

# In[101]:

x = DontCare()

# In[102]:

x * 5

# In[ ]:

dil


"""User and Feedback models detailed."""

from flask_sqlalchemy import SQLAlchemy
from sqlalchemy.orm import backref

db = SQLAlchemy()

def connect_db(app):
    """Connect to the database."""
    db.app = app
    db.init_app(app)

class Pinned(db.Model):
    """Connetion of a pinner and the pinned person."""

    __tablename__='pinned'

    pinned_user = db.Column(db.Integer,
                            db.ForeignKey('users.id', ondelete='cascade'),
                            primary_key=True)
    pinning_user = db.Column(db.Integer,
                             db.ForeignKey('users.id', ondelete="cascade"),
                             primary_key=True)

class User(db.Model):
    """User model."""

    __tablename__ = "users"

    id = db.Column(db.Integer,
                   primary_key=True,
                   autoincrement=True)
    username = db.Column(db.String(20),
                         unique=True,
                         nullable=False)
    first_name = db.Column(db.String(30),
                           nullable=False)
    last_name = db.Column(db.String(30),
                          nullable=False)
    job_title = db.Column(db.String(50))
    availability = db.Column(db.String(20))
    rooms = db.relationship(
        "Room",
        secondary="users_rooms",
        back_populates='users')
    pinned_people = db.relationship(
        "User",
        secondary="pinned",
        primaryjoin=(Pinned.pinned_user == id),
        secondaryjoin=(Pinned.pinning_user == id),
        backref=backref('pinning_user', lazy='dynamic'),
        lazy='dynamic'
    )

    def __repr__(self):
        return f"<User {self.id} {self.username} {self.first_name} {self.last_name} {self.job_title} {self.availability} {self.rooms} {self.pinned_people} >"

class Room(db.Model):
    """Room model."""

    __tablename__ = "rooms"

    id = db.Column(db.Integer,
                   primary_key=True,
                   autoincrement=True)
    name = db.Column(db.String(50),
                      nullable=False,
                      unique=True)
    notes = db.Column(db.String())
    users = db.relationship(
        "User",
        secondary="users_rooms",
        back_populates='rooms')
    tasks = db.relationship('Task')
    events = db.relationship('Event')

    def __repr__(self):
        return f"<Room {self.id} {self.name} {self.notes} {self.users} {self.tasks} {self.events} >"

class Task(db.Model):
    """Task model."""

    __tablename__ = "tasks"

    id = db.Column(db.Integer,
                   primary_key=True,
                   autoincrement=True)
    complete = db.Column(db.String(5))
    item = db.Column(db.String(100),
                     nullable=False)
    room = db.Column(db.Integer,
                     db.ForeignKey('rooms.id', ondelete='CASCADE'),
                     nullable=False)

    def __repr__(self):
        return f"<Task {self.id} {self.complete} {self.item} {self.room} >"

class Event(db.Model):
    """Event model."""

    __tablename__ = "events"

    id = db.Column(db.Integer,
                   primary_key=True,
                   autoincrement=True)
    name = db.Column(db.String(50),
                     nullable=False)
    datetime = db.Column(db.DateTime,
                     nullable=False)
    room = db.Column(db.String(),
                     db.ForeignKey('rooms.name', ondelete='CASCADE'),
                     nullable=False)

    def __repr__(self):
        return f"<Event {self.id} {self.name} {self.datetime} {self.room} >"

class User_Room(db.Model):
    """Many to many table for users and rooms."""

    __tablename__ = "users_rooms"

    user_id = db.Column(db.Integer,
                        db.ForeignKey('users.id'),
                        primary_key=True)
    room_id = db.Column(db.Integer,
                        db.ForeignKey('rooms.id'),
                        primary_key=True)


#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import copy
import torch
import numpy as np
from tqdm import tqdm
from sklearn.metrics import classification_report, accuracy_score

device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

best_model = None
best_accuracy = 0.0
best_epoch = 0

class Trainer(object):

    def __init__(self, configs):
        super(Trainer, self).__init__()
        self.configs = configs

    def train(self, epoch, model, dataloader, optimizer):
        predictions_labels = []
        true_labels = []
        total_loss = 0
        total_acc = 0
        model.train()

        for count, (x_batch, y_batch) in enumerate(dataloader, 1):

            x_batch = x_batch.to(device)
            y_batch = y_batch.to(device)

            optimizer.zero_grad()
            outputs = model(x_batch, labels=y_batch)
            loss, y_pred = outputs[0:2]

            loss.backward()
            optimizer.step()
            total_loss += loss.item()

            y_pred = torch.argmax(y_pred, dim=1)
            correct_num = torch.sum(y_pred == y_batch).double()
            total_acc += correct_num / y_pred.shape[0]
            predictions_labels.append(y_pred.cpu().numpy())
            true_labels.append(y_batch.cpu().numpy())

            print("\x1b[2K\rEpoch: {} / {} Loss: {:.5f} Acc: {:.5f}".format(
                epoch, self.configs["model_params"]["scibert_param"]["epoch_num"], total_loss/count, total_acc/count), end="")

        return

    def evaluate(self, epoch, model, tokenizer, dataloader, model_dir=None):
        global best_accuracy, best_epoch
        best_accuracy = 0.0 if epoch == 0 else best_accuracy
        best_epoch = 0 if epoch == 0 else best_epoch

        predictions_labels = []
        true_labels = []
        total_loss = 0
        total_acc = 0
        model.eval()

        with torch.no_grad():
            for count, (x_batch, y_batch) in enumerate(dataloader, 1):
                x_batch = x_batch.to(device)
                y_batch = y_batch.to(device)
                outputs = model(x_batch, labels=y_batch)
                loss, y_pred = outputs[0:2]

                # compute loss
                total_loss += loss.item()

                # compute accuracy
                y_pred = torch.argmax(y_pred, dim=1)
                correct_num = torch.sum(y_pred == y_batch).double()
                total_acc += correct_num / y_pred.shape[0]
                predictions_labels.append(y_pred.cpu().numpy())
                true_labels.append(y_batch.cpu().numpy())

                print("\x1b[2K\rEval Loss: {:.5f} Acc: {:.5f}".format(total_loss/count, total_acc/count), end="")

        predictions_labels = np.hstack(predictions_labels)
        true_labels = np.hstack(true_labels)
        acc = accuracy_score(true_labels, predictions_labels)
        print("\nAccuracy: {:.5f}\n".format(acc))

        if model_dir is not None:

            if acc > best_accuracy:
                best_model = copy.deepcopy(model.state_dict())
                best_accuracy = acc
                best_epoch = epoch

                model.load_state_dict(best_model)
                print("saving best model at epoch {}".format(best_epoch))
                model.save_pretrained(self.saved_model_dir)
                tokenizer.save_pretrained(self.saved_model_dir)

        return acc, predictions_labels, true_labels


import xml.etree.ElementTree as ET

class Parser:
    def __init__(self, filename):
        # xml files now stored in 'network_xmls' directory:
        filepath = "./network_xmls/" + filename
        self.root = ET.parse(filepath).getroot()
        self.sections = [child for child in self.root]
        self.nodes, self.links = self._getData()

    def _getData(self):
        [nodes, links] = [child for child in self.sections[0]]
        # keys contains the 'id's of the Germany50 nodes (e.g. 'Berlin')
        keys = []
        # iterate through all the nodes listed in xml file and append their id's to keys list
        for child in nodes:
            keys = keys + [child.attrib["id"]]
        node_data = {key: None for key in keys}
        # get coordinates for each node:
        attributes = [
            [axis.text for axis in coordinate] for node in nodes for coordinate in node
        ]
        for index, key in enumerate(node_data.keys()):
            node_data[key] = attributes[index]
        link_data = {
            key: None for key in [attributes.attrib["id"] for attributes in links]
        }

        attributes = [
            [
                axis.text
                for axis in link
                if any(axis.tag == tag for tag in ["source", "target"])
            ]
            for link in links
        ]
        # iterate through each link and get the capacity under "additionalModules/addModule/capacity", and get the text from the capacity object
        # appends each capacity to capacity list which is returned seperately
        for idx, link in enumerate(links):
            #note the find method only finds the FIRST matching path. Some networks have multiple 'addModule'
            #but only the first one is obtained.
            attributes[idx].extend(
                [
                    idx, #index of link
                    link.find("additionalModules/addModule/capacity").text, #capacity
                    link.find("additionalModules/addModule/cost").text, #cost
                ]
            )
        for index, key in enumerate(link_data.keys()):
            link_data[key] = attributes[index]
        return node_data, link_data


import csv, mindwave, time, datetime, os

def check_device():
    command = "find /dev/ -name rfcomm*"
    result = os.popen(command).read()
    items = result.split('\n')
    items.remove('')
    check = -999
    for i in range(len(items)):
        if int(items[i][-1]) > check:
            check = int(items[i][-1])
    for i in range(len(items)):
        if int(items[i][-1]) == check:
            device = items[i]
    return device

def connect_device(new_device):
    headset = mindwave.Headset(new_device)
    print("Device is connected!! id: %s\n"%new_device)
    time.sleep(2)
    return headset

print('Hi, give me name of the recording session, for example persons name. Timestamp will be added automatically.')
session_name = input('Session name: ')

ts = datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S')
filename = f'{session_name}_{ts}.dat'

print(f'Writing to {filename}')

print('Connecting to Mindwave...')
device = check_device()
headset = connect_device(device)

print('Connected, waiting 10 seconds for data to start streaming')
time.sleep(10)

print('Starting to record, automatically recording 10 minute slice so keep on working...')
f = open(filename, 'w')
now = time.time()
future = now + 60*10
with f:
    writer = csv.writer(f)
    writer.writerow(['Timestamp','Raw','Attention','Meditation','delta','theta','low-alpha','high-alpha','low-beta','high-beta','low-gamma','mid-gamma'])
    while time.time() < future:
    #while True:
        ts = datetime.datetime.utcnow().isoformat()
        print ("Raw value: %s, Attention: %s, Meditation: %s" % (headset.raw_value, headset.attention, headset.meditation))
        print ("Waves: {}".format(headset.waves))
        values = list(headset.waves.values())
        values = [ts,headset.raw_value,headset.attention, headset.meditation] + values
        writer.writerow(values)
        time.sleep(.5)


"""empty message

Revision ID: 14a31a096ca4
Revises: cded1b6a4595
Create Date: 2023-06-28 22:09:16.157596

"""
import sqlalchemy as sa
from alembic import op

# revision identifiers, used by Alembic.
revision = '14a31a096ca4'
down_revision = 'cded1b6a4595'
branch_labels = None
depends_on = None

def upgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    op.create_table('answer',
    sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
    sa.Column('content', sa.Text(), nullable=False),
    sa.Column('answer_time', sa.DateTime(), nullable=True),
    sa.Column('question_id', sa.Integer(), nullable=True),
    sa.Column('answer_id', sa.Integer(), nullable=True),
    sa.ForeignKeyConstraint(['answer_id'], ['user.id'], ),
    sa.ForeignKeyConstraint(['question_id'], ['question.id'], ),
    sa.PrimaryKeyConstraint('id')
    )
    # ### end Alembic commands ###

def downgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    op.drop_table('answer')
    # ### end Alembic commands ###


#!/usr/bin/env python
from comm.mapper import Mapper
from comm import comm

class PCD2JPG(Mapper):
    def __init__(self):
        Mapper.__init__(self, ["pcd"],
                        [comm.PointCloudMessage],
                        "jpg", comm.MultiChannelImageMessage)
    def func(self, xyz_bgr):
        _, bgr = xyz_bgr
        return bgr

comm.initComm()
M = PCD2JPG()
M.run()


import cv2 as cv
import numpy as np

class rwVideo:
    def __init__(self,root,nameVideo="Video",guardar=False) -> None:
        self.cap = cv.VideoCapture(root)

        if type(root) == int:

            if not self.cap.isOpened():
                print("No se pudo abrir la camara")
                exit()
        self.guardar = guardar
        if guardar:
            self.exit = cv.VideoWriter('Video_Guardado.mkv',cv.VideoWriter_fourcc(*'XVID'),20.0,(int(self.cap.get(3)),int(self.cap.get(4))))

    def canny(self,frame):
        im = cv.cvtColor(frame, cv.COLOR_BGR2GRAY)
        canny = cv.Canny(im,100,200)
        canny =  cv.cvtColor(canny, cv.COLOR_GRAY2BGR)
        return canny

    def prewitt(self,frame):
        im = cv.cvtColor(frame, cv.COLOR_BGR2GRAY)
        kernelx = np.array([[-1,0,1],[-1,0,1],[-1,0,1]])
        kernely = np.array([[1,1,1],[0,0,0],[-1,-1,-1]])
        img_prewitt_x = cv.filter2D(im, -1, kernelx)
        img_prewitt_y = cv.filter2D(im, -1, kernely)

        img_prewitt = img_prewitt_x + img_prewitt_y
        img_prewitt = cv.cvtColor(img_prewitt, cv.COLOR_GRAY2BGR)

        return img_prewitt

    def sobel(self,frame):
        im = cv.cvtColor(frame, cv.COLOR_BGR2GRAY)

        img_sobelx = cv.convertScaleAbs(cv.Sobel(im,cv.CV_64F,1,0,ksize=3))
        img_sobely = cv.convertScaleAbs(cv.Sobel(im,cv.CV_64F,0,1,ksize=3))

        img_sobel = img_sobelx + img_sobely
        img_sobel = cv.cvtColor(img_sobel, cv.COLOR_GRAY2BGR)

        return img_sobel

    def laplaciano(self,frame):
        im = cv.cvtColor(frame, cv.COLOR_BGR2GRAY)
        img_laplaciano = cv.convertScaleAbs(cv.Laplacian(im,cv.CV_64F,ksize=3))

        img_laplaciano = cv.cvtColor(img_laplaciano, cv.COLOR_GRAY2BGR)

        return img_laplaciano

    def laplaciano_gauss(self,frame):
        frame = cv.GaussianBlur(frame,(5,5),0)
        return self.laplaciano(frame)

    def mostrarVideo(self,filter=None):
        guardar = self.guardar
        print("Para salir del video presione: d")

        while True:
            istrue, frame = self.cap.read()

            if istrue:
                if filter:
                    frame = filter(frame)

                cv.imshow('Video', frame)
                if guardar:
                    self.exit.write(frame)
                if cv.waitKey(20) & 0xFF==ord('d'):
                    break
            else:
                break

        self.cap.release()
        if guardar:
            print("Video Guardado")
            self.exit.release()
        cv.destroyAllWindows()

if __name__ == "__main__":
    filtervideo = rwVideo(0,nameVideo="Filtro Canny",guardar=True)
    filtervideo.mostrarVideo(filter=filtervideo.laplaciano_gauss)


"""
    This is a script that trains 2D U-Nets in the XY, YZ and XZ direction from scratch
    Usage:
        python train.py --method 2D
"""

"""
    Necessary libraries
"""
import argparse
import datetime
import torch
import torch.optim as optim
import os
from torch.utils.data import DataLoader

from data.datasets import *
from networks.unet import UNet2D
from util.preprocessing import get_augmenters_2d
from util.io import imwrite3D
from util.losses import CrossEntropyLoss
from util.validation import segment
from util.metrics import jaccard, dice, accuracy_metrics
from util.tools import get_sampling_ordering

"""
    Parse all the arguments
"""
print('[%s] Parsing arguments' % (datetime.datetime.now()))
parser = argparse.ArgumentParser()
# logging parameters
parser.add_argument("--log_dir", help="Logging directory", type=str, default="logs")
parser.add_argument("--write_dir", help="Writing directory", type=str, default=None)
parser.add_argument("--data", help="Dataset for training", type=str, default="epfl") # options: 'epfl', 'embl_mito', 'embl_er', vnc, med
parser.add_argument("--print_stats", help="Number of iterations between each time to log training losses", type=int, default=100)

# network parameters
parser.add_argument("--resolution", help="Pixel resolution of the dataset in (z,y,x) ordering", type=str, default="5,5,5")
parser.add_argument("--input_size_xy", help="Size of the XY blocks that propagate through the network", type=str, default="1,512,512")
parser.add_argument("--input_size_zx", help="Size of the XZ blocks that propagate through the network", type=str, default="1,160,160")
parser.add_argument("--input_size_yz", help="Size of the YZ blocks that propagate through the network", type=str, default="1,160,160")
parser.add_argument("--fm", help="Number of initial feature maps in the segmentation U-Net", type=int, default=64)
parser.add_argument("--levels", help="Number of levels in the segmentation U-Net (i.e. number of pooling stages)", type=int, default=4)
parser.add_argument("--group_norm", help="Use group normalization instead of batch normalization", type=int, default=0)
parser.add_argument("--augment_noise", help="Use noise augmentation", type=int, default=1)
parser.add_argument("--class_weight", help="Percentage of the reference class", type=float, default=(0.5))

# optimization parameters
parser.add_argument("--lr", help="Learning rate of the optimization", type=float, default=1e-3)
parser.add_argument("--step_size", help="Number of epochs after which the learning rate should decay", type=int, default=10)
parser.add_argument("--gamma", help="Learning rate decay factor", type=float, default=0.9)
parser.add_argument("--epochs", help="Total number of epochs to train", type=int, default=100)
parser.add_argument("--test_freq", help="Number of epochs between each test stage", type=int, default=1)
parser.add_argument("--train_batch_size", help="Batch size in the training stage", type=int, default=1)
parser.add_argument("--test_batch_size", help="Batch size in the testing stage", type=int, default=1)

args = parser.parse_args()
args.resolution = [int(item) for item in args.resolution.split(',')]
args.input_size_xy = [int(item) for item in args.input_size_xy.split(',')]
args.input_size_zx = [int(item) for item in args.input_size_zx.split(',')]
args.input_size_yz = [int(item) for item in args.input_size_yz.split(',')]
sampling_ordering, input_sizes = get_sampling_ordering(args.resolution, (args.input_size_xy, args.input_size_zx, args.input_size_yz))
num_networks = len(sampling_ordering)
weight = torch.FloatTensor([1-args.class_weight, args.class_weight]).cuda()
loss_fn_seg = CrossEntropyLoss(weight=weight)

"""
    Setup logging directory
"""
if not os.path.exists(args.log_dir):
    os.mkdir(args.log_dir)
net_dirs = ['unets']
for dir in net_dirs:
    if not os.path.exists(os.path.join(args.log_dir, dir)):
        os.mkdir(os.path.join(args.log_dir, dir))
    for subdir in sampling_ordering:
        if not os.path.exists(os.path.join(args.log_dir, dir, str(subdir))):
            os.mkdir(os.path.join(args.log_dir, dir, str(subdir)))
if args.write_dir is not None:
    if not os.path.exists(args.write_dir):
        os.mkdir(args.write_dir)
    for subdir in sampling_ordering:
        if not os.path.exists(os.path.join(args.write_dir, str(subdir))):
            os.mkdir(os.path.join(args.write_dir, str(subdir)))
        os.mkdir(os.path.join(args.write_dir, str(subdir), 'segmentation_last_checkpoint'))
        os.mkdir(os.path.join(args.write_dir, str(subdir), 'segmentation_best_checkpoint'))

"""
    Load the data
"""
input_shapes = input_sizes[0]
# load data
print('[%s] Loading data' % (datetime.datetime.now()))
train_xtransform, train_ytransform, test_xtransform, test_ytransform = get_augmenters_2d(augment_noise=(args.augment_noise==1))
if args.data == 'epfl':
    train = EPFLTrainDataset(input_shapes=input_shapes, transform=train_xtransform, target_transform=train_ytransform)
    test = EPFLTestDataset(input_shapes=input_shapes, transform=test_xtransform, target_transform=test_ytransform)
elif args.data == 'vnc':
    train = VNCTrainDataset(input_shapes=input_shapes, transform=train_xtransform, target_transform=train_ytransform)
    test = VNCTestDataset(input_shapes=input_shapes, transform=test_xtransform, target_transform=test_ytransform)
elif args.data == 'med':
    train = MEDTrainDataset(input_shapes=input_shapes, transform=train_xtransform, target_transform=train_ytransform)
    test = MEDTestDataset(input_shapes=input_shapes, transform=test_xtransform, target_transform=test_ytransform)
else:
    if args.data == 'embl_mito':
        train = EMBLMitoTrainDataset(input_shapes=input_shapes, transform=train_xtransform, target_transform=train_ytransform)
        test = EMBLMitoTestDataset(input_shapes=input_shapes, transform=test_xtransform, target_transform=test_ytransform)
    else:
        train = EMBLERTrainDataset(input_shapes=input_shapes, transform=train_xtransform, target_transform=train_ytransform)
        test = EMBLERTestDataset(input_shapes=input_shapes, transform=test_xtransform, target_transform=test_ytransform)
train_loader = DataLoader(train, batch_size=args.train_batch_size)
test_loader = DataLoader(test, batch_size=args.test_batch_size)

"""
    Train the network for each required orientation
"""
print('[%s] Training 2D U-Nets from scratch...' % (datetime.datetime.now()))
for i, so in enumerate(sampling_ordering):
    # logging stuff
    log_dir = os.path.join(args.log_dir, net_dirs[0], str(so))
    if args.write_dir is not None:
        write_dir = os.path.join(args.write_dir, str(so))

    input_shapes = input_sizes[i]
    train_loader.dataset.input_shapes = input_shapes
    test_loader.dataset.input_shapes = input_shapes
    train_loader.dataset.orientations = so
    test_loader.dataset.orientations = so

    """
        Setup optimization for training
    """
    print('[%s] Setting up optimization for training' % (datetime.datetime.now()))
    net = UNet2D(feature_maps=args.fm, levels=args.levels, group_norm=(args.group_norm==1))
    optimizer = optim.Adam(net.parameters(), lr=args.lr)
    scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=args.step_size, gamma=args.gamma)

    print('[%s] Initiating training of network %d...' % (datetime.datetime.now(), i+1))
    # actual training
    net.train_net(train_loader=train_loader, test_loader=test_loader,
                  loss_fn=loss_fn_seg, optimizer=optimizer, scheduler=scheduler,
                  epochs=args.epochs, test_freq=args.test_freq, print_stats=args.print_stats,
                  log_dir=log_dir)

    """
        Validate the trained network
    """
    for j, o in enumerate(so):
        input_size = input_shapes[j]
        print('[%s] Validating trained network %d in orientation %s...' % (datetime.datetime.now(), i+1, str(o)))
        test_data = np.transpose(test.data, o)
        test_labels = np.transpose(test.labels, o)
        segmentation_last_checkpoint = segment(test_data, net, input_size, batch_size=args.test_batch_size)
        j = jaccard(segmentation_last_checkpoint, test_labels)
        d = dice(segmentation_last_checkpoint, test_labels)
        a, p, r, f = accuracy_metrics(segmentation_last_checkpoint, test_labels)
        print('[%s] Results last checkpoint:' % (datetime.datetime.now()))
        print('[%s]     Jaccard: %f' % (datetime.datetime.now(), j))
        print('[%s]     Dice: %f' % (datetime.datetime.now(), d))
        print('[%s]     Accuracy: %f' % (datetime.datetime.now(), a))
        print('[%s]     Precision: %f' % (datetime.datetime.now(), p))
        print('[%s]     Recall: %f' % (datetime.datetime.now(), r))
        print('[%s]     F-score: %f' % (datetime.datetime.now(), f))
        net = torch.load(os.path.join(log_dir, 'best_checkpoint.pytorch'))
        segmentation_best_checkpoint = segment(test_data, net, input_size, batch_size=args.test_batch_size)
        j = jaccard(segmentation_best_checkpoint, test_labels)
        d = dice(segmentation_best_checkpoint, test_labels)
        a, p, r, f = accuracy_metrics(segmentation_best_checkpoint, test_labels)
        print('[%s] Results best checkpoint:' % (datetime.datetime.now()))
        print('[%s]     Jaccard: %f' % (datetime.datetime.now(), j))
        print('[%s]     Dice: %f' % (datetime.datetime.now(), d))
        print('[%s]     Accuracy: %f' % (datetime.datetime.now(), a))
        print('[%s]     Precision: %f' % (datetime.datetime.now(), p))
        print('[%s]     Recall: %f' % (datetime.datetime.now(), r))
        print('[%s]     F-score: %f' % (datetime.datetime.now(), f))

        """
            Write out the results
        """
        if args.write_dir is not None:
            print('[%s] Writing the output' % (datetime.datetime.now()))
            imwrite3D(segmentation_last_checkpoint, os.path.join(write_dir, 'segmentation_last_checkpoint', str(o)), rescale=True)
            imwrite3D(segmentation_best_checkpoint, os.path.join(write_dir, 'segmentation_best_checkpoint', str(o)), rescale=True)

print('[%s] Finished!' % (datetime.datetime.now()))


# 네이버 오픈 API 이용하기

import requests

client_id = "ObR7t_ig_MksARWbtN86"
client_secret = "RH_A6LmYyr"

headers = {"X-Naver-Client-Id": client_id, "X-Naver-Client-Secret": client_secret}

start = 1
for idx in range(10):
    start_num = start + (idx * 100)

    URL = (
        "https://openapi.naver.com/v1/search/shop.json?query=키보드&display=100&start="
        + str(start_num)
    )

    print(URL)

    res = requests.get(URL, headers=headers)

    # JSON 데이터 확인
    data = res.json()
    for item in data["items"]:
        print(item["title"])


# encoding: utf-8

import ast
from IPython.display import display, HTML
from ipywidgets import widgets
from Appearance import LayoutSytle
from SearchFiles import FindFiles, RefineJson, ApplyFilters

_lt = LayoutSytle()

class BuildFilterPanel:

    def __init__(self, path):

        self._css = """
            <style>
            /*.container{
                    width: 98%;
                }*/

            .output{
                height:100%;
                }

            .output_scroll{
                height: 100%;
                }

            .widget-label{
                    height: 100px;
                    white-space: wrap;
                    font-weight: bold;
                    font-size: 16px;
                    text-align: left;
                    }

            .widget-checkbox {
                    width: 90%;
                }

            .space{
                text-align: left;
                font-size: 13px;
                }

            .space-checkBox{
                text-align: left;
                font-size: 13px;
                margin-left: 3%;
                }

            .vazio{
                color: red;
                font-size: 12px;
                text-align: left;
                }

            .center{
                text-align: center;
                font-size: 17px;
                }

            .blue{
                color: blue;
                font-size: 11.5px;
                }

            .vertical-line{
                border-right: 1px groove black;
                }

            .horizontal-line{
                border-bottom: 1px groove black;
                }

            .top-line{
                border-top: 1px groove black;
                }
            .border{
                border: 1px groove black;
                border-radius: 5px;
                }
            </style>"""

        ###### Boxes Creation ######
        self._static = widgets.HBox()
        self._multiple_box = widgets.VBox()
        self._uniquePlusCollapse = widgets.HBox()
        self._selectMultiple = widgets.SelectMultiple()
        self._select = widgets.VBox()

        ###### Buttons Creation ######
        self._collapse = widgets.Button(description="", tooltip="Collapse", icon="fa-angle-double-up")
        self._filter_btn = widgets.Button(description="Aplicar Filtros", icon="fa-paper-plane", button_style="primary")
        self._applyChooses = widgets.Button(description="Aplicar", button_style="primary", icon="fa-check-circle")

        ###### Variables Creation ######
        self._path = path
        self._unicos = list()
        self._multiplas = list()
        self.finded = list()
        self._filtro = dict()
        self._arquivo = dict()
        self._arquivos = dict()
        self._refined = dict()
        self._fullFiles = dict()
        self._file_Path = dict()

        ###### Variable that receive external methods return - Creation ######
        self._emExibicao = widgets.Label(value="", align_self="center")
        self._buildJson = FindFiles(self._path)
        self._mj = self._buildJson.montaJson()
        self._refineJson = RefineJson(self._mj)
        self._json = self._refineJson.build()

        self._leftBox()

    def _buildSingularFilters(self):
        uniKey = self._json['unique'].keys()
        for i in uniKey:
            key = str(i.encode("ascii", "replace"))
            value = str(self._json['unique'][i])
            value = value.encode("ascii", "replace")
            description = str(self._json['unique'][i])

            self._unicos.append(self._labelSingularFilter(key, value ,description))

        if len(self._unicos) == 0:
            self._unicos.append(self._notUnicos())

        return self._unicos

    @staticmethod
    def _notUnicos():
        return widgets.Label(value="Nao Ha Caracteristicas Unicas", description="Nao ha", display="flex-shrink", layout = _lt.notUnicos_layout()).add_class('vazio')

    @staticmethod
    def _labelSingularFilter(key, value, description):
        return widgets.Label(value=key + ": " + value, description=description, display="flex-wrap", width="95%", style={'description_width':'initial'}).add_class('space').add_class('blue')

    def _buildMultipleFilters(self):
        multiplos = dict()
        multKey = self._json['multiple'].keys()
        for j in multKey:
            listaDeCheckBox = list()
            for k in self._json['multiple'][j]:
                option = self._checkBoxMultipleFilters(unicode(str(k)), str(j))
                option.observe(self._change, names='value')
                listaDeCheckBox.append(option)

            multiplos[j] = listaDeCheckBox

        for k, v in multiplos.iteritems():
            sBox = widgets.VBox()
            sBox.children=[self._labelMultipleFilter(str(k), str(k)), self._boxMultipleFilters(v)]
            sBox.log.info = str(k)
            # print(sBox.log.info)
            self._multiplas.append(sBox)
            # self._multiplas.append(self._labelMultipleFilter(str(k),str(k)))
            # self._multiplas.append(self._boxMultipleFilters(v))

    def _change(self, x):
        key = x.owner.style.description_width
        value = x.owner.description
        if x.new:
            if not key in self._filtro.keys():
                self._filtro[key] = list()
            if not value in self._filtro[key]:
                self._filtro[key].append(value)
        else:
            if len(self._filtro[key]) == 1:
                del (self._filtro[key])
            else:
                self._filtro[key].remove(value)

    @staticmethod
    def _checkBoxMultipleFilters(description, auxiliar):
        return widgets.Checkbox(description=description, encoding="ascii", width="100%", display='flex', indent=False, style={"description_width": auxiliar}).add_class('space-checkBox')

    @staticmethod
    def _labelMultipleFilter(value, description):
        return widgets.Label(value=value.capitalize(), description=description.capitalize(), display="flex-shrink", layout=_lt.labelFilterMultiple_layout()).add_class("space")

    def _boxMultipleFilters(self, childrens):
        boxMultipleFilter = widgets.HBox([v for v in childrens], layout=_lt.boxMultipleFilter_layout())
        return boxMultipleFilter

    def _staticBox(self):
        unicos = self._buildSingularFilters()
        self._static.children = [i for i in unicos]
        self._static.layout = _lt.staticBox_layout()
        return self._static

    def _multipleBox(self):
        self._buildMultipleFilters()
        self._multiple_box.layout = _lt.multipleBox_layout()
        self._multiple_box.style = {'description_width': 'initial'}
        self._multiple_box.children = [i for i in self._multiplas]
        return self._multiple_box

    def _filterButton(self):
        self._filter_btn.layout = _lt.filterBtn_layout()
        self._filter_btn.style.button_color = '#009850'  # '#2CD660'
        self._filter_btn.on_click(self._aplicaFiltros)
        return self._filter_btn

    def _multiSelection(self):
        self._selectMultiple.layout = _lt.selectMultiple_layout()
        return self._selectMultiple

    def _applyChooseButton(self):
        self._applyChooses.layout = _lt.applyChooseButton_layout()
        self._applyChooses.style.button_color = "#009850"
        self._applyChooses.on_click(self._renderJson)
        return self._applyChooses

    def _getIndices(self):
        indices = dict()
        for i in self._selectMultiple.index:
            for j in self._mj:
                if self._file_Path[self._selectMultiple.options[i]] in self._mj[j]['file']:
                    indices[j] = self._selectMultiple.options[i]

        self._arquivos = indices
        return indices

    def _getProperties(self, dictionary):
        intersection = dict()
        indexes = dictionary.keys()
        keys = self._mj[indexes[0]]['content'].keys()
        for i in indexes:
            for j in keys:
                if not dictionary[i] in intersection:
                    intersection[dictionary[i]] = dict()
                intersection[dictionary[i]][j] = self._mj[i]['content'][j]  # - intersection[j]

        self._refined = intersection
        return intersection

    def _renderJson(self, n):
        indices = self._getIndices()
        intersection = self._getProperties(indices)

        from PreRenderJson import BuildTree
        mt = BuildTree()

        for i in intersection.keys():
            for j in intersection[i]['message']:
                if isinstance(j, unicode) and ":" in j:
                    k = j.split(":")
                    j = str("{'"+k[0]+"'"+":'"+k[1]+"'}")
                if not isinstance(j, unicode):
                    mt.fillNumProperties(ast.literal_eval(j))
            self._arquivo[i] = mt.dict

        from RenderJson import RenderJSON
        for k, v in self._arquivo.iteritems():
            pass
            # print k
            # display(RenderJSON(v))

    def exibeFiltrados(self, jsons):
        self._selectMultiple = self._multiSelection()
        items = list()

        for i in jsons:
            for j in self._mj:
                if i == self._mj[j]['file']:
                    fullPath = str(i).split("/")
                    arq = fullPath[-1]
                    self._file_Path[arq] = i
                    items.append(arq)
        options = [i for i in items]
        self._selectMultiple.options = options
        self._selectMultiple.layout.visibility = "visible"
        self._select.children = [self._selectMultiple, self._applyChooseButton()]
        self._fullFiles = self._file_Path
        display(self._select)

    def _aplicaFiltros(self, n):
        af = ApplyFilters(self._filtro, self._mj)
        filtrado = af.make()
        capturados = set()
        for k in filtrado.keys():
            for v in filtrado[k]:
                capturados.add(v)

        if len(filtrado.keys()) != 0:
            self._emExibicao.value = "Sua busca retornou " + str(len(capturados)) + " de " + str(len(self._mj.keys())) + " arquivos"

        self.finded = list(capturados)
        # display('Os arquivos estao salvos na varivel finded. Para visualizar solicite a impressao da mesma. Caso queira exibir na caixa de dialogo, invoque o metodo exibeFiltrados.')

    def _uniqueCollapse(self):
        self._uniquePlusCollapse.children = [widgets.Label(value="Caracteristicas Unicas", description="Caracteristicas Unicas", display="flex-shrink", layout = _lt.uniqueCollapse_layout()), self._collapsable()]
        return self._uniquePlusCollapse

    def _collapseUnicas(self, n):
        if (self._static.layout.visibility == "hidden"):
            self._collapse.icon = "fa-angle-double-up"
            self._collapse.tooltip = "Collapse"
            self._static.layout.height = "100%"
            self._static.layout.visibility = "visible"
        else:
            self._static.layout.visibility = "hidden"
            self._static.layout.height = "0"
            self._collapse.tooltip = "Expand"
            self._collapse.icon = "fa-angle-double-down"

    def _collapsable(self):
        self._collapse.layout = _lt.collapseButton()
        self._collapse.style.button_color = "white"
        self._collapse.on_click(self._collapseUnicas)
        return self._collapse

    def _exibicao(self):
        self._emExibicao = widgets.Label(value="Foram encontrados "+str(len(self._mj))+ " arquivos.").add_class("center")
        return self._emExibicao

    def _leftBox(self):
        left_box = widgets.VBox()
        left_box.children = [self._exibicao(),
                            widgets.Label(value="Filtros", description="Filtros", display="flex-shrink", layout=_lt.leftBox_layout()),
                             self._uniqueCollapse(),
                             self._staticBox(),
                             widgets.Label(value="Caracteristicas Multiplas", description="Caracteristicas Multiplas", display="flex-shrink",
                                           layout=_lt.labelCaracteristicasMultiplas_layout()).add_class('top-line'),
                             self._multipleBox(),
                             self._filterButton()]

        left_box.layout = _lt.leftBox_layout()
        display(HTML(self._css))
        display(left_box)


import numpy as np
import matplotlib.pyplot as plt
#If using termux
import subprocess
import shlex

x = [1,2,3,4,2,1]
#Assuming length of x(n) and h(n) as same
N = len(x)
def h(N):
	h = []
	for i in range(N):
		out = 0;
		if i >= 0:
			out = out+((-0.5)**i)
		if i-2 >= 0:
			out = out+((-0.5)**(i-2))
		h.append(out)

	return h

def DFT(f):
	F = []
	N = len(f)
	for k in range(N):
		out = 0 * 1j
		for n in range(N):
			out=out+(f[n]*np.exp(-1j*2*np.pi*k*n/N))
		F.append(out)
	return F
h=h(N)
print ("DFT of x(n)\n",DFT(x))
print()
print ("DFT of h(n)\n",DFT(h))

fig , ax = plt.subplots(nrows = 3, ncols = 2, figsize=(12,14))

ax[0][0].stem(range(0,N),x)
ax[0][0].set_title(r'$x(n)$')
ax[0][0].grid()

ax[0][1].stem(range(0,N),h)
ax[0][1].set_title(r'$h(n)$')
ax[0][1].grid()

ax[1][0].stem(range(0,N),np.abs(DFT(x)))
ax[1][0].set_title(r'$|DFT(x)|$')
ax[1][0].grid()

ax[1][1].stem(range(0,N),np.angle(DFT(x)))
ax[1][1].set_title(r'$phase(DFT(x))$')
ax[1][1].grid()

ax[2][0].stem(range(0,N),np.abs(DFT(h)))
ax[2][0].set_title(r'$|DFT(h)|$')
ax[2][0].grid()

ax[2][1].stem(range(0,N),np.angle(DFT(h)))
ax[2][1].set_title(r'$phase(DFT(h))$')
ax[2][1].grid()
#If using termux
plt.savefig('../figs/ee18btech11012.pdf')
plt.savefig('../figs/ee18btech11012.eps')
subprocess.run(shlex.split("termux-open ../figs/ee18btech11012.pdf"))
#else
#plt.show()


import json
import os
import platform as os_platform
import shutil
import subprocess
import sys
import time

import wx

global compiler_logs
compiler_logs = ''

def scrollToEnd(txtCtrl):
    if os_platform.system() != 'Darwin':
        txtCtrl.SetInsertionPoint(-1)
    txtCtrl.ShowPosition(txtCtrl.GetLastPosition())
    txtCtrl.Refresh()
    txtCtrl.Update()

def runCommand(command):
    cmd_response = None

    try:
        cmd_response = subprocess.check_output(command, shell=True, stderr=subprocess.STDOUT)
        #cmd_response = subprocess.check_output(command, stderr=subprocess.STDOUT, shell=True)
    except subprocess.CalledProcessError as exc:
        cmd_response = exc.output

    if cmd_response == None:
        return ''

    return cmd_response.decode('utf-8')

def loadHals():
    # load hals list from json file, or construct it
    if (os.name == 'nt'):
        jfile = 'editor\\arduino\\examples\\Baremetal\\hals.json'
    else:
        jfile = 'editor/arduino/examples/Baremetal/hals.json'

    f = open(jfile, 'r')
    jsonStr = f.read()
    f.close()
    return(json.loads(jsonStr))

def saveHals(halObj):
    jsonStr = json.dumps(halObj)
    if (os.name == 'nt'):
        jfile = 'editor\\arduino\\examples\\Baremetal\\hals.json'
    else:
        jfile = 'editor/arduino/examples/Baremetal/hals.json'
    f = open(jfile, 'w')
    f.write(jsonStr)
    f.flush()
    f.close()

def readBoardInstalled(platform):
    hals = loadHals()
    cli_command = ''
    if os_platform.system() == 'Windows':
        cli_command = 'editor\\arduino\\bin\\arduino-cli-w32'
    elif os_platform.system() == 'Darwin':
        cli_command = 'editor/arduino/bin/arduino-cli-mac'
    else:
        cli_command = 'editor/arduino/bin/arduino-cli-l64'
    for board in hals:
            if hals[board]['platform'] == platform:
                board_details = runCommand(cli_command + ' board details -b ' + platform)
                board_details = board_details.splitlines()
                board_version = '0'
                for line in board_details:
                    if "Board version:" in line:
                        board_version = line.split('Board version:')[1]
                        board_version = ''.join(board_version.split()) #remove white spaces
                        hals[board]['version'] = board_version
                        saveHals(hals)
                        break

def readBoardsInstalled():
    hasToSave = False
    hals = loadHals()
    cli_command = ''
    if os_platform.system() == 'Windows':
        cli_command = 'editor\\arduino\\bin\\arduino-cli-w32'
    elif os_platform.system() == 'Darwin':
        cli_command = 'editor/arduino/bin/arduino-cli-mac'
    else:
        cli_command = 'editor/arduino/bin/arduino-cli-l64'
    boardInstalled = runCommand(cli_command + ' board listall')
    for board in hals:
        if board in boardInstalled:
            platform = hals[board]['platform']
            board_details = runCommand(cli_command + ' board details -b ' + platform)
            board_details = board_details.splitlines()
            board_version = '0'
            for line in board_details:
                if "Board version:" in line:
                    board_version = line.split('Board version:')[1]
                    board_version = ''.join(board_version.split()) #remove white spaces
                    hals[board]['version'] = board_version
                    hasToSave = True
                    break
        if board not in boardInstalled:
            hals[board]['version'] = '0'
            hasToSave = True

    if hasToSave:
        saveHals(hals)

def setLangArduino():
    cli_command = ''
    if os_platform.system() == 'Windows':
        cli_command = 'editor\\arduino\\bin\\arduino-cli-w32'
    elif os_platform.system() == 'Darwin':
        cli_command = 'editor/arduino/bin/arduino-cli-mac'
    else:
        cli_command = 'editor/arduino/bin/arduino-cli-l64'

    # Initialize arduino-cli config - if it hasn't been initialized yet
    runCommand(cli_command + ' config init')

    dump = runCommand(cli_command + ' config dump')
    dump = dump.splitlines()
    arduino_dir = ''
    for line in dump:
        if "data:" in line:
            #get the directory of arduino ide
            arduino_dir = line.split('data:')[1]
            arduino_dir = ''.join(arduino_dir.split()) #remove white spaces

        if "locale:" in line:
            if "en" not in line:
                #remove the line from dump variable
                dump.remove(line)
            else:
                return #already set to english

    dump.append('locale: en')
    dump = '\n'.join(dump)
    #write on the config file all the lines# Open the file in write mode
    with open(arduino_dir + '/arduino-cli.yaml', 'w') as f:
        # Write the variable to the file
        f.write(str(dump))

    #runCommand('echo ' + dump + ' > ' + arduino_dir + '/arduino-cli.yaml')

def build(st_file, platform, source_file, port, txtCtrl, update_subsystem):
    global compiler_logs
    compiler_logs = ''
    setLangArduino()

    #check if platform is installed in the arduino ide
    readBoardInstalled(platform)

    hals = loadHals()

    #Check if board is installed
    board_installed = False
    core = ''
    for board in hals:
        if hals[board]['platform'] == platform:
            core = hals[board]['core']
            if hals[board]['version'] != "0":
                board_installed = True

    #Check MatIEC compiler
    if (os.path.exists("editor/arduino/bin/iec2c") or os.path.exists("editor/arduino/bin/iec2c.exe") or os.path.exists("editor/arduino/bin/iec2c_mac")):
        # remove old files first
        if os.path.exists('editor/arduino/src/POUS.c'):
            os.remove('editor/arduino/src/POUS.c')
        if os.path.exists('editor/arduino/src/POUS.h'):
            os.remove('editor/arduino/src/POUS.h')
        if os.path.exists('editor/arduino/src/LOCATED_VARIABLES.h'):
            os.remove('editor/arduino/src/LOCATED_VARIABLES.h')
        if os.path.exists('editor/arduino/src/VARIABLES.csv'):
            os.remove('editor/arduino/src/VARIABLES.csv')
        if os.path.exists('editor/arduino/src/Config0.c'):
            os.remove('editor/arduino/src/Config0.c')
        if os.path.exists('editor/arduino/src/Config0.h'):
            os.remove('editor/arduino/src/Config0.h')
        if os.path.exists('editor/arduino/src/Res0.c'):
            os.remove('editor/arduino/src/Res0.c')
    else:
        compiler_logs += "Error: iec2c compiler not found!\n"
        wx.CallAfter(txtCtrl.SetValue, compiler_logs)
        wx.CallAfter(scrollToEnd, txtCtrl)
        return

    #Install/Update board support
    if board_installed == False or update_subsystem == True:
        if board_installed == False:
            compiler_logs += "Support for " + platform + " is not installed on OpenPLC Editor. Please be patient and wait while " + platform + " is being installed...\n"
            wx.CallAfter(txtCtrl.SetValue, compiler_logs)
        elif update_subsystem == True:
            compiler_logs += "Updating support for " + platform + ". Please be patient and wait while " + platform + " is being installed...\n"
            wx.CallAfter(txtCtrl.SetValue, compiler_logs)

        cli_command = ''
        if os_platform.system() == 'Windows':
            cli_command = 'editor\\arduino\\bin\\arduino-cli-w32'
        elif os_platform.system() == 'Darwin':
            cli_command = 'editor/arduino/bin/arduino-cli-mac'
        else:
            cli_command = 'editor/arduino/bin/arduino-cli-l64'

        """
        ### ARDUINO-CLI CHEAT SHEET ###

        1. List installed boards:
          => arduino-cli board listall

        2. Search for a core (even if not installed yet):
          => arduino-cli core search [search text]

        3. Dump current configuration:
          => arduino-cli config dump

        4. Get additional board parameters:
          => arduino-cli board details -fqbn [board fqbn]
        """

        # Initialize arduino-cli config - if it hasn't been initialized yet
        compiler_logs += runCommand(cli_command + ' config init')
        wx.CallAfter(txtCtrl.SetValue, compiler_logs)
        wx.CallAfter(scrollToEnd, txtCtrl)

        # Setup boards - remove 3rd party boards to re-add them later since we don't know if they're there or not
        compiler_logs += runCommand(
            cli_command + ' config remove board_manager.additional_urls \
https://arduino.esp8266.com/stable/package_esp8266com_index.json \
https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json \
https://github.com/stm32duino/BoardManagerFiles/raw/main/package_stmicroelectronics_index.json \
https://raw.githubusercontent.com/CONTROLLINO-PLC/CONTROLLINO_Library/master/Boards/package_ControllinoHardware_index.json \
https://github.com/earlephilhower/arduino-pico/releases/download/global/package_rp2040_index.json \
https://facts-engineering.gitlab.io/facts-open-source/p1am/beta_file_hosting/package_productivity-P1AM_200-boardmanagermodule_index.json \
https://raw.githubusercontent.com/VEA-SRL/IRUINO_Library/main/package_vea_index.json \
"2>&1"')
        wx.CallAfter(txtCtrl.SetValue, compiler_logs)
        wx.CallAfter(scrollToEnd, txtCtrl)

        # Setup boards - add 3rd party boards
        compiler_logs += runCommand(
            cli_command + ' config add board_manager.additional_urls \
https://arduino.esp8266.com/stable/package_esp8266com_index.json \
https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json \
https://github.com/stm32duino/BoardManagerFiles/raw/main/package_stmicroelectronics_index.json \
https://raw.githubusercontent.com/CONTROLLINO-PLC/CONTROLLINO_Library/master/Boards/package_ControllinoHardware_index.json \
https://github.com/earlephilhower/arduino-pico/releases/download/global/package_rp2040_index.json \
https://facts-engineering.gitlab.io/facts-open-source/p1am/beta_file_hosting/package_productivity-P1AM_200-boardmanagermodule_index.json \
https://raw.githubusercontent.com/VEA-SRL/IRUINO_Library/main/package_vea_index.json')
        wx.CallAfter(txtCtrl.SetValue, compiler_logs)
        wx.CallAfter(scrollToEnd, txtCtrl)

        # Update
        compiler_logs += runCommand(cli_command + ' core update-index')
        wx.CallAfter(txtCtrl.SetValue, compiler_logs)
        wx.CallAfter(scrollToEnd, txtCtrl)
        compiler_logs += runCommand(cli_command + ' update')
        wx.CallAfter(txtCtrl.SetValue, compiler_logs)
        wx.CallAfter(scrollToEnd, txtCtrl)

        # Install board
        compiler_logs += runCommand(cli_command + ' core install ' + core)
        wx.CallAfter(txtCtrl.SetValue, compiler_logs)
        wx.CallAfter(scrollToEnd, txtCtrl)

        # Install all libs - required after core install/update
        compiler_logs += runCommand(cli_command + ' lib install \
WiFiNINA \
Ethernet \
Arduino_MachineControl \
Arduino_EdgeControl \
OneWire \
DallasTemperature \
P1AM \
CONTROLLINO \
"Adafruit ADS1X15" \
PubSubClient \
ArduinoJson \
arduinomqttclient \
RP2040_PWM')
        wx.CallAfter(txtCtrl.SetValue, compiler_logs)
        wx.CallAfter(scrollToEnd, txtCtrl)

        readBoardsInstalled()

    # Generate C files
    compiler_logs += "Compiling .st file...\n"
    wx.CallAfter(txtCtrl.SetValue, compiler_logs)
    wx.CallAfter(scrollToEnd, txtCtrl)
    if (os.name == 'nt'):
        base_path = 'editor\\arduino\\src\\'
    else:
        base_path = 'editor/arduino/src/'
    f = open(base_path+'plc_prog.st', 'w')
    f.write(st_file)
    f.flush()
    f.close()

    time.sleep(0.2)  # make sure plc_prog.st was written to disk

    if os_platform.system() == 'Windows':
        compilation = subprocess.Popen(['editor\\arduino\\bin\\iec2c.exe', 'plc_prog.st'], cwd='editor\\arduino\\src',
                                       creationflags=0x08000000, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    elif os_platform.system() == 'Darwin':
        compilation = subprocess.Popen(['../bin/iec2c_mac', 'plc_prog.st'],
                                       cwd='./editor/arduino/src', stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    else:
        compilation = subprocess.Popen(
            ['../bin/iec2c', 'plc_prog.st'], cwd='./editor/arduino/src', stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    stdout, stderr = compilation.communicate()
    compiler_logs += stdout.decode('UTF-8')
    compiler_logs += stderr.decode('UTF-8')
    wx.CallAfter(txtCtrl.SetValue, compiler_logs)
    wx.CallAfter(scrollToEnd, txtCtrl)

    # Remove temporary plc program
    # if os.path.exists(base_path+'plc_prog.st'):
    #    os.remove(base_path+'plc_prog.st')

    # Generate glueVars.c
    if not (os.path.exists(base_path+'LOCATED_VARIABLES.h')):
        compiler_logs += "Error: Couldn't find LOCATED_VARIABLES.h. Check iec2c compiler output for more information\n"
        wx.CallAfter(txtCtrl.SetValue, compiler_logs)
        wx.CallAfter(scrollToEnd, txtCtrl)
        return

    located_vars_file = open(base_path+'LOCATED_VARIABLES.h', 'r')
    located_vars = located_vars_file.readlines()
    glueVars = """
#include "iec_std_lib.h"

#define __LOCATED_VAR(type, name, ...) type __##name;
#include "LOCATED_VARIABLES.h"
#undef __LOCATED_VAR
#define __LOCATED_VAR(type, name, ...) type* name = &__##name;
#include "LOCATED_VARIABLES.h"
#undef __LOCATED_VAR

TIME __CURRENT_TIME;
BOOL __DEBUG;
extern unsigned long long common_ticktime__;

//OpenPLC Buffers
#if defined(__AVR_ATmega328P__) || defined(__AVR_ATmega168__) || defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega16U4__)

#define MAX_DIGITAL_INPUT          8
#define MAX_DIGITAL_OUTPUT         32
#define MAX_ANALOG_INPUT           6
#define MAX_ANALOG_OUTPUT          32

#else

#define MAX_DIGITAL_INPUT          56
#define MAX_DIGITAL_OUTPUT         56
#define MAX_ANALOG_INPUT           32
#define MAX_ANALOG_OUTPUT          32

#endif

IEC_BOOL *bool_input[MAX_DIGITAL_INPUT/8][8];
IEC_BOOL *bool_output[MAX_DIGITAL_OUTPUT/8][8];
IEC_UINT *int_input[MAX_ANALOG_INPUT];
IEC_UINT *int_output[MAX_ANALOG_OUTPUT];

void glueVars()
{
"""
    for located_var in located_vars:
        # cleanup located var line
        if ('__LOCATED_VAR(' in located_var):
            located_var = located_var.split('(')[1].split(')')[0]
            var_data = located_var.split(',')
            if (len(var_data) < 5):
                compiler_logs += 'Error processing located var line: ' + located_var + '\n'
                wx.CallAfter(txtCtrl.SetValue, compiler_logs)
                wx.CallAfter(scrollToEnd, txtCtrl)
            else:
                var_type = var_data[0]
                var_name = var_data[1]
                var_address = var_data[4]
                var_subaddress = '0'
                if (len(var_data) > 5):
                    var_subaddress = var_data[5]

                # check variable type and assign to correct buffer pointer
                if ('QX' in var_name):
                    if (int(var_address) > 6 or int(var_subaddress) > 7):
                        compiler_logs += 'Error: wrong location for var ' + var_name + '\n'
                        wx.CallAfter(txtCtrl.SetValue, compiler_logs)
                        wx.CallAfter(scrollToEnd, txtCtrl)
                        return
                    glueVars += '    bool_output[' + var_address + \
                        '][' + var_subaddress + '] = ' + var_name + ';\n'
                elif ('IX' in var_name):
                    if (int(var_address) > 6 or int(var_subaddress) > 7):
                        compiler_logs += 'Error: wrong location for var ' + var_name + '\n'
                        wx.CallAfter(txtCtrl.SetValue, compiler_logs)
                        wx.CallAfter(scrollToEnd, txtCtrl)
                        return
                    glueVars += '    bool_input[' + var_address + \
                        '][' + var_subaddress + '] = ' + var_name + ';\n'
                elif ('QW' in var_name):
                    if (int(var_address) > 32):
                        compiler_logs += 'Error: wrong location for var ' + var_name + '\n'
                        wx.CallAfter(txtCtrl.SetValue, compiler_logs)
                        wx.CallAfter(scrollToEnd, txtCtrl)
                        return
                    glueVars += '    int_output[' + \
                        var_address + '] = ' + var_name + ';\n'
                elif ('IW' in var_name):
                    if (int(var_address) > 32):
                        compiler_logs += 'Error: wrong location for var ' + var_name + '\n'
                        wx.CallAfter(txtCtrl.SetValue, compiler_logs)
                        wx.CallAfter(scrollToEnd, txtCtrl)
                        return
                    glueVars += '    int_input[' + \
                        var_address + '] = ' + var_name + ';\n'
                else:
                    compiler_logs += 'Could not process location "' + \
                        var_name + '" from line: ' + located_var + '\n'
                    wx.CallAfter(txtCtrl.SetValue, compiler_logs)
                    wx.CallAfter(scrollToEnd, txtCtrl)
                    return

    glueVars += """
}

void updateTime()
{
    __CURRENT_TIME.tv_nsec += common_ticktime__;

    if (__CURRENT_TIME.tv_nsec >= 1000000000)
    {
        __CURRENT_TIME.tv_nsec -= 1000000000;
        __CURRENT_TIME.tv_sec += 1;
    }
}
    """
    f = open(base_path+'glueVars.c', 'w')
    f.write(glueVars)
    f.flush()
    f.close()

    time.sleep(2)  # make sure glueVars.c was written to disk

    # Patch POUS.c to include POUS.h
    f = open(base_path+'POUS.c', 'r')
    pous_c = '#include "POUS.h"\n\n' + f.read()
    f.close()

    f = open(base_path+'POUS.c', 'w')
    f.write(pous_c)
    f.flush()
    f.close()

    # Patch Res0.c to include POUS.h instead of POUS.c
    f = open(base_path+'Res0.c', 'r')
    res0_c = ''
    lines = f.readlines()
    for line in lines:
        if '#include "POUS.c"' in line:
            res0_c += '#include "POUS.h"\n'
        else:
            res0_c += line
    f.close()

    f = open(base_path+'Res0.c', 'w')
    f.write(res0_c)
    f.flush()
    f.close()

    # Copy HAL file
    if os_platform.system() == 'Windows':
        source_path = 'editor\\arduino\\src\\hal\\'
        destination = 'editor\\arduino\\src\\arduino.cpp'
    else:
        source_path = 'editor/arduino/src/hal/'
        destination = 'editor/arduino/src/arduino.cpp'

    shutil.copyfile(source_path + source_file, destination)

    # Generate Pin Array Sizes defines
    # We need to write the hal specific pin size defines on the global defines.h so that it is
    # available everywhere

    if os_platform.system() == 'Windows':
        define_path = 'editor\\arduino\\examples\\Baremetal\\'
    else:
        define_path = 'editor/arduino/examples/Baremetal/'
    file = open(define_path+'defines.h', 'r')
    define_file = file.read() + '\n\n//Pin Array Sizes\n'
    hal = open(destination, 'r')
    lines = hal.readlines()
    for line in lines:
        if (line.find('define NUM_DISCRETE_INPUT') > 0):
            define_file += line
        if (line.find('define NUM_ANALOG_INPUT') > 0):
            define_file += line
        if (line.find('define NUM_DISCRETE_OUTPUT') > 0):
            define_file += line
        if (line.find('define NUM_ANALOG_OUTPUT') > 0):
            define_file += line

    # Write defines.h file back to disk
    if os_platform.system() == 'Windows':
        define_path = 'editor\\arduino\\examples\\Baremetal\\'
    else:
        define_path = 'editor/arduino/examples/Baremetal/'
    f = open(define_path+'defines.h', 'w')
    f.write(define_file)
    f.flush()
    f.close()

    # Generate .elf file
    compiler_logs += "Generating binary file...\n"
    wx.CallAfter(txtCtrl.SetValue, compiler_logs)
    wx.CallAfter(scrollToEnd, txtCtrl)

    # if (os.name == 'nt'):
    #    compilation = subprocess.check_output('editor\\arduino\\bin\\arduino-cli-w32 compile -v --libraries=editor\\arduino --build-property compiler.c.extra_flags="-Ieditor\\arduino\\src\\lib" --build-property compiler.cpp.extra_flags="-Ieditor\\arduino\\src\\lib" --export-binaries -b ' + platform + ' editor\\arduino\\examples\\Baremetal\\Baremetal.ino 2>&1')
    #compilation = subprocess.Popen(['editor\\arduino\\bin\\arduino-cli-w32', 'compile', '-v', '--libraries=..\\..\\', '--build-property', 'compiler.c.extra_flags="-I..\\src\\lib"', '--build-property', 'compiler.cpp.extra_flags="I..\\src\\lib"', '--export-binaries', '-b', platform, '..\\examples\\Baremetal\\Baremetal.ino'], cwd='editor\\arduino\\src', stdout = subprocess.PIPE, stderr = subprocess.PIPE)
    # else:
    #    compilation = subprocess.check_output('editor/arduino/bin/arduino-cli-l64 compile -v --libraries=editor/arduino --build-property compiler.c.extra_flags="-Ieditor/arduino/src/lib" --build-property compiler.cpp.extra_flags="-Ieditor/arduino/src/lib" --export-binaries -b ' + platform + ' editor/arduino/examples/Baremetal/Baremetal.ino 2>&1')
    #compiler_logs += compilation.decode('utf-8')
    #wx.CallAfter(txtCtrl.SetValue, compiler_logs)
    #wx.CallAfter(scrollToEnd, txtCtrl)

    compiler_logs += '\nCOMPILATION START: '
    compiler_logs += platform
    compiler_logs += '\n'

    if os_platform.system() == 'Windows':
        compilation = subprocess.Popen(['editor\\arduino\\bin\\arduino-cli-w32', 'compile', '-v', '--libraries=editor\\arduino', '--build-property', 'compiler.c.extra_flags="-Ieditor\\arduino\\src\\lib"', '--build-property',
                                       'compiler.cpp.extra_flags="-Ieditor\\arduino\\src\\lib"', '--export-binaries', '-b', platform, 'editor\\arduino\\examples\\Baremetal\\Baremetal.ino'], creationflags=0x08000000, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    elif os_platform.system() == 'Darwin':
        compilation = subprocess.Popen(['editor/arduino/bin/arduino-cli-mac', 'compile', '-v', '--libraries=editor/arduino', '--build-property', 'compiler.c.extra_flags="-Ieditor/arduino/src/lib"', '--build-property',
                                       'compiler.cpp.extra_flags="-Ieditor/arduino/src/lib"', '--export-binaries', '-b', platform, 'editor/arduino/examples/Baremetal/Baremetal.ino'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    else:
        compilation = subprocess.Popen(['editor/arduino/bin/arduino-cli-l64', 'compile', '-v', '--libraries=editor/arduino', '--build-property', 'compiler.c.extra_flags="-Ieditor/arduino/src/lib"', '--build-property',
                                       'compiler.cpp.extra_flags="-Ieditor/arduino/src/lib"', '--export-binaries', '-b', platform, 'editor/arduino/examples/Baremetal/Baremetal.ino'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    stdout, stderr = compilation.communicate()
    compiler_logs += stdout.decode('UTF-8')
    compiler_logs += stderr.decode('UTF-8')
    if (compilation.returncode != 0):
        compiler_logs += '\nCOMPILATION FAILED!\n'
    wx.CallAfter(txtCtrl.SetValue, compiler_logs)
    wx.CallAfter(scrollToEnd, txtCtrl)

    if (compilation.returncode == 0):
        if (port != None):
            compiler_logs += '\nUploading program to Arduino board at ' + port + '...\n'
            wx.CallAfter(txtCtrl.SetValue, compiler_logs)
            wx.CallAfter(scrollToEnd, txtCtrl)
            if os_platform.system() == 'Windows':
                compiler_logs += runCommand('editor\\arduino\\bin\\arduino-cli-w32 upload --port ' +
                                            port + ' --fqbn ' + platform + ' editor\\arduino\\examples\\Baremetal/')
            elif os_platform.system() == 'Darwin':
                compiler_logs += runCommand('editor/arduino/bin/arduino-cli-mac upload --port ' +
                                            port + ' --fqbn ' + platform + ' editor/arduino/examples/Baremetal/ 2>&1')
            else:
                compiler_logs += runCommand('editor/arduino/bin/arduino-cli-l64 upload --port ' +
                                            port + ' --fqbn ' + platform + ' editor/arduino/examples/Baremetal/')
            compiler_logs += '\nDone!\n'
            wx.CallAfter(txtCtrl.SetValue, compiler_logs)
            wx.CallAfter(scrollToEnd, txtCtrl)
        else:
            cwd = os.getcwd()
            compiler_logs += '\nOUTPUT DIRECTORY:\n'
            if os_platform.system() == 'Windows':
                compiler_logs += cwd + '\\editor\\arduino\\examples\\Baremetal\\build\n'
            else:
                compiler_logs += cwd + '/editor/arduino/examples/Baremetal/build\n'
            compiler_logs += '\nCOMPILATION DONE!'
            wx.CallAfter(txtCtrl.SetValue, compiler_logs)
            wx.CallAfter(scrollToEnd, txtCtrl)
    time.sleep(1)  # make sure files are not in use anymore

    # no clean up
    return

    # Clean up and return
    if os.path.exists(base_path+'POUS.c'):
        os.remove(base_path+'POUS.c')
    if os.path.exists(base_path+'POUS.h'):
        os.remove(base_path+'POUS.h')
    if os.path.exists(base_path+'LOCATED_VARIABLES.h'):
        os.remove(base_path+'LOCATED_VARIABLES.h')
    if os.path.exists(base_path+'VARIABLES.csv'):
        os.remove(base_path+'VARIABLES.csv')
    if os.path.exists(base_path+'Config0.c'):
        os.remove(base_path+'Config0.c')
    if os.path.exists(base_path+'Config0.h'):
        os.remove(base_path+'Config0.h')
    if os.path.exists(base_path+'Config0.o'):
        os.remove(base_path+'Config0.o')
    if os.path.exists(base_path+'Res0.c'):
        os.remove(base_path+'Res0.c')
    if os.path.exists(base_path+'Res0.o'):
        os.remove(base_path+'Res0.o')
    if os.path.exists(base_path+'glueVars.c'):
        os.remove(base_path+'glueVars.c')


from keras.callbacks import Callback
import keras.backend as K
import json

class Logger(Callback):

	def __init__(self, filepath,  hyperparams, period=1):
		super(Logger, self).__init__()
		self.filepath = filepath #Save filepath for logging
		self.period = period #Logging period
		self.epochs_since_last_save = 0
		self.hyperparams = hyperparams

	def on_epoch_end(self, epoch, logs=None):
		logs = logs or {} #Creating a dictionary object from logs, if empty, logs becomes an empty dict

		self.epochs_since_last_save += 1
		if self.epochs_since_last_save >= self.period: #Need to log
			self.epochs_since_last_save = 0
			print("Generating checkpoint and logs")

			#### Generating log file ####
			#Current train state
			trainState = {
            	"trainLoss" : logs['loss'],
            	"validateLoss" : logs['val_loss'],
            	"epoch" : epoch,
            	"learningRate" : float(K.get_value(self.model.optimizer.lr))
			}
			#Adding hyperparameters to log file
			outputDict = {
            	'trainState' : trainState,
            	'trainingHyperparameters' : self.hyperparams
			}
			#Populate variables in log filepath
			filepath = self.filepath.format(epoch=epoch + 1, **logs)
			with open(filepath, 'w') as f:
				json.dump(outputDict, f) #Write to file


from curses import window
import matplotlib
import numpy as np
import pandas as pd
from collections import namedtuple
from matplotlib import pyplot as plt

EpisodeStats = namedtuple("Stats",["episode_lengths", "episode_rewards", "step_reward_avg"])

def plot_value_function(V, title="Value Function"):
    """
    Plots the value function as a surface plot.
    """
    min_x = min(k[0] for k in V.keys())
    max_x = max(k[0] for k in V.keys())
    min_y = min(k[1] for k in V.keys())
    max_y = max(k[1] for k in V.keys())

    x_range = np.arange(min_x, max_x + 1)
    y_range = np.arange(min_y, max_y + 1)
    X, Y = np.meshgrid(x_range, y_range)

    # Find value for all (x, y) coordinates
    Z_noace = np.apply_along_axis(lambda _: V[(_[0], _[1], False)], 2, np.dstack([X, Y]))
    Z_ace = np.apply_along_axis(lambda _: V[(_[0], _[1], True)], 2, np.dstack([X, Y]))

    def plot_surface(X, Y, Z, title):
        fig = plt.figure(figsize=(20, 10))
        ax = fig.add_subplot(111, projection='3d')
        surf = ax.plot_surface(X, Y, Z, rstride=1, cstride=1,
                               cmap=matplotlib.cm.coolwarm, vmin=-1.0, vmax=1.0)
        ax.set_xlabel('Player Sum')
        ax.set_ylabel('Dealer Showing')
        ax.set_zlabel('Value')
        ax.set_title(title)
        ax.view_init(ax.elev, -120)
        fig.colorbar(surf)
        plt.show()

    plot_surface(X, Y, Z_noace, "{} (No Usable Ace)".format(title))
    plot_surface(X, Y, Z_ace, "{} (Usable Ace)".format(title))

def plot_episode_stats(stats, name, hypa, color, smoothing_window=100, noshow=False):
    window_name = name + hypa
    '''
    # Plot the episode reward over time
    fig = plt.figure(window_name)
    rewards_smoothed = pd.Series(stats.episode_rewards).rolling(smoothing_window, min_periods=smoothing_window).mean()
    plt.plot(rewards_smoothed, color=color, label=hypa)

    plt.xlabel("Episode")
    plt.ylabel("Episode Reward (Smoothed)")
    plt.title(f"Episode Reward over Time (Smoothed over window size {name})".format(smoothing_window))
    plt.legend()
'''
    fig2 = plt.figure(window_name)
    rewards_smoothed = pd.Series(stats.step_reward_avg).rolling(smoothing_window, min_periods=smoothing_window).mean()
    plt.plot(rewards_smoothed, color=color, label=hypa)

    plt.xlabel("Step")
    plt.ylabel("Step Reward Avg(Smoothed)")
    plt.title(f"Step Reward over Time (Smoothed over window size {name})".format(smoothing_window))
    plt.legend()

    return fig2
     #, fig2, fig3


from django import forms
from .models import Ficha, Review, Assignment, FichaImage, get_default_start_date
from django.contrib.admin.widgets import AdminDateWidget, AdminTimeWidget
from django.forms import modelformset_factory
from django.core.exceptions import ValidationError

class AssignmentForm(forms.ModelForm):
    time_window_start = forms.DateField(
        widget=forms.DateInput(
            attrs={
                "type": "date",
            }
        ),
        initial=get_default_start_date,
    )
    time_window_end = forms.DateField(
        widget=forms.DateInput(
            attrs={
                "type": "date",
            }
        )
    )

    class Meta:
        model = Assignment
        fields = [
            "title",
            "description",
            "time_window_start",
            "time_window_end",
        ]

    def clean(self):
        cleaned_data = super().clean()
        time_window_start = cleaned_data.get("time_window_start")
        time_window_end = cleaned_data.get("time_window_end")

        if time_window_end < time_window_start:
            msg = "La fecha de cierre no puede ser anterior a la fecha de inicio"
            self.add_error("time_window_end", msg)
        return cleaned_data

# class CustomFileInput(forms.ClearableFileInput):
#     template_name = "components/custom_file_input.html"
#
#
# imgChoices = (
#     ("MAIN", "main"),
#     ("FOTO", "fotograma"),
#     ("COMP", "complementaria"),
#     ("REF", "referencia"),
#     ("O", "otra"),
# )
#
#
# class FichaImageForm(forms.ModelForm):
#     attributes = forms.ChoiceField(
#         choices=imgChoices, label="Tipo de imagen", widget=forms.Select()
#     )
#
#     class Meta:
#         model = FichaImage
#         fields = ["image", "attributes"]
#         widgets = {
#             "image": CustomFileInput(),
#         }
#
#
# # add a fichaimage inlineformset to fichaform

class FichaForm(forms.ModelForm):
    main_image = forms.ImageField()

    class Meta:
        model = Ficha
        exclude = ["student", "status", "created_at", "updated_at"]
        fields = [
            "title",
            "main_image",
            "description",
            "analysis",
            "references",
            "keywords",
            "anexos",
            "misc",
        ]
        widgets = {
            "title": forms.TextInput(
                attrs={
                    "rows": 2,
                    "placeholder": "Título de la ficha",
                    "label": "Título",
                }
            ),
            "description": forms.Textarea(
                attrs={
                    "rows": 5,
                    "placeholder": "Descripción de la ficha",
                    "label": "Descripción",
                }
            ),
            "analysis": forms.Textarea(
                attrs={
                    "rows": 5,
                    "placeholder": "Análisis Operacional",
                    "label": "Análisis",
                }
            ),
            "references": forms.Textarea(
                attrs={
                    "rows": 5,
                    "placeholder": "Analisis Referencial",
                    "label": "Referentes",
                }
            ),
            "misc": forms.Textarea(
                attrs={
                    "rows": 2,
                    "placeholder": "Información adicional",
                    "label": "Información adicional",
                }
            ),
            "keywords": forms.Textarea(
                attrs={
                    "rows": 2,
                    "placeholder": "Palabras clave",
                    "label": "Palabras clave",
                }
            ),
            "anexos": forms.URLInput(
                attrs={"class": "url-input", "placeholder": "URL"}
            ),
            # specify other field widgets here...
        }

    def __init__(self, *args, **kwargs):
        self.user = kwargs.pop("user", None)
        super(FichaForm, self).__init__(*args, **kwargs)
        if self.instance.pk:
            self.initial["keywords"] = ", ".join(
                [kw.name for kw in self.instance.keywords.all()]
            )

    def save(self, commit=True):
        instance = super(FichaForm, self).save(commit=False)

        if commit:
            instance.save()

        if instance.pk:
            instance.keywords.clear()  # clear old keywords
            keywords = self.cleaned_data["keywords"]
            # Check if keywords is a string or a list and then handle it accordingly
            if isinstance(keywords, str):
                keywords = keywords.split(",")
            for keyword in keywords:
                instance.keywords.add(keyword.strip())  # add new keywords

        return instance

class ReviewForm(forms.ModelForm):
    class Meta:
        model = Review
        fields = ["review"]
        widgets = {
            "review": forms.Textarea(attrs={"rows": 3}),
        }