import numpy as np
import pandas as pd

import sklearn
from sklearn.model_selection import KFold

import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
from tensorflow.keras.layers import Dense, Dropout, Activation, Flatten, Convolution2D, MaxPooling2D, BatchNormalization
from tensorflow.keras.models import Sequential
from tensorflow.keras.preprocessing.image import ImageDataGenerator
from tensorflow.random import set_seed

from typing import Tuple, List
from tqdm import tqdm
import os

#Seeding random state to 13 for reproducibility
seed = 13
np.random.seed(seed)
set_seed(seed)

#Declare Callbacks: stop training if accuracy doesn't rise 1% within 3 epochs
callbacks = [
    keras.callbacks.EarlyStopping(
        monitor = "val_loss",
        patience = 6,
        verbose = 1,
        restore_best_weights = True,
        min_delta = 0.01
    )
]

#Helper Function: Return the paths to all jpg files found within a directory
def getImageDirs(root: str = "data"):
    imageDirs = []
    for subDirectory, directory, files in os.walk(root):
        for file in files:
            if file[-4:] == ".jpg":
                path = os.path.join(subDirectory, file)
                imageDirs.append(path)
    return imageDirs

#Helper Function: Return the class weights given a list of classes
def getClassWeightsFromLabels(labels: List[int]):
    weights = sklearn.utils.class_weight.compute_class_weight(class_weight="balanced", classes=np.unique(labels), y=labels)
    return {0: weights[0], 1: weights[1]}

#Helper Function: Return the img paths and classes in seperate lists given a txt file from the LABELS folder
def getDirsAndClasses(root: str, file: str) -> Tuple[List[str], List[int]]:
    imageDirs = []
    classes = []
    line = ""
    with open(root + file, "r") as f:
        for line in tqdm(f):
            imageDir, clazz = line.split()
            imageDirs.append(imageDir)
            classes.append(int(clazz))
    return imageDirs, classes

def makeDataset(pathFromCwdToDataRoot = "Data") -> pd.DataFrame:
    #Get the Train Dataset using split from the LABELS folder
    root = f'{os.getcwd()}/{pathFromCwdToDataRoot}/CNR-EXT-150x150'
    imageDirs, classes = getDirsAndClasses(root, "/LABELS/train.txt")
    patchesRoot = root + "/PATCHES/"
    train = pd.DataFrame([
                {
                    "image": patchesRoot + filename,
                    "class": "free" if clazz == 0 else "busy"
                }
                for filename, clazz in tqdm(zip(imageDirs, classes))
        ])
    #Now Get Test
    imageDirs, classes = getDirsAndClasses(root, "/LABELS/test.txt")
    test = pd.DataFrame([
                {
                    "image": patchesRoot + filename,
                    "class": "free" if clazz == 0 else "busy"
                }
                for filename, clazz in tqdm(zip(imageDirs, classes))
        ])

    # Combine them together to get the full dataset
    dataset = train.append(test)

    return dataset

# Helper Function: Create the k-fold splits and calculate the class weights
def makeFolds(dataset: pd.DataFrame, n_folds: int = 1, batch_size: int = 128, target_size: Tuple[int] = (150, 150)):
    # Choose number of folds (1=normal experiment)
    # Make the k folds
    kFCV_sets=[]

    busy_samples=dataset.loc[dataset["class"] == "busy"]
    free_samples=dataset.loc[dataset["class"] == "free"]

    # If we don't want to perform k-folding
    if n_folds == 1:
        busy = [i for i in range(int(len(busy_train)*0.9))],[i for i in range(int(len(busy_train)*0.9), len(busy_train))]
        free = [i for i in range(int(len(free_train)*0.9))],[i for i in range(int(len(free_train)*0.9), len(free_train))]
    # We do want to perform k-folding
    else:
        busy_kf = sklearn.model_selection.KFold(n_splits = n_folds)
        free_kf = sklearn.model_selection.KFold(n_splits = n_folds)

    # Iterate through the folds and make the data generators for the models to use
    for k in range(n_folds):
        if n_folds != 1:
            busy = next(busy_kf.split(busy_samples), None)
            free = next(busy_kf.split(free_samples), None)
        busy_train, free_train = busy_samples.iloc[busy[0]], free_samples.iloc[free[0]]
        busy_train, busy_val = busy_train[:int(len(busy_train)*0.9)], busy_train[int(len(busy_train)*0.9):]
        free_train, free_val = free_train[:int(len(free_train)*0.9)], free_train[int(len(free_train)*0.9):]

        train = busy_train.append(free_train)
        val = busy_val.append(free_val)
        test = busy_samples.iloc[busy[1]].append(free_samples.iloc[free[1]])

        #Declare data generators and preprocessing
        train_datagen = ImageDataGenerator(
            #Augment data with random flips, normalize each sample's input
            vertical_flip = True,
            horizontal_flip = True,
            rescale = 1.0 / 255.0,
            samplewise_std_normalization = True
        )
        train_generator = train_datagen.flow_from_dataframe(
            directory = None, #none since the df has absolute paths
            dataframe = train,
            x_col = "image",
            y_col = "class",
            validate_filenames = False, #faster for huge datasets
            target_size = target_size,
            color_mode = "rgb",
            batch_size = batch_size,
            class_mode = "binary",
            shuffle = True
        )

        test_datagen = ImageDataGenerator(
            samplewise_std_normalization = True
        )
        test_generator = test_datagen.flow_from_dataframe(
            directory = None,
            dataframe = test,
            x_col = "image",
            y_col = "class",
            validate_filenames = False,
            target_size = target_size,
            color_mode = "rgb",
            batch_size = batch_size,
            class_mode = "binary",
            shuffle = True
        )
        val_generator = test_datagen.flow_from_dataframe(
            directory = None,
            dataframe = val,
            x_col = "image",
            y_col = "class",
            validate_filenames = False,
            target_size = target_size,
            color_mode = "rgb",
            batch_size = batch_size,
            class_mode = "binary",
            shuffle = True
        )

        print()

        kFCV_sets.append([train_generator, test_generator, val_generator])

    #Extract Class Weights (Weights will be the same for all folds)
    classes = list(train["class"])
    weights_dict = getClassWeightsFromLabels(classes)

    return kFCV_sets, weights_dict

#Helper Function: Create a TF/Keras model
def makeModel(inputShape: Tuple[int], modelName:str = '') -> keras.Model:
    model = None

    if modelName == "AlexNet":
        model = makeAlexNet(inputShape=inputShape, modelName=modelName)
    elif modelName in ["InceptionResNetV2","MobileNetV2","ResNet50V2","DenseNet121","DenseNet201","NASNetLarge"]:
        model = makePrebuiltModel(inputShape=inputShape, modelName=modelName)
    elif modelName == "SimpleNet":
        model = makeSimpleNet(inputShape=inputShape, modelName=modelName)
    elif modelName == "SimpleDenseNet":
        model = makeSimpleDenseNet(inputShape=inputShape, modelName=modelName)
    elif modelName == "SimpleResNet":
        model = makeSimpleResNet(inputShape=inputShape, modelName=modelName)
    elif modelName == "mAlexNet":
        model = makemAlexNet(inputShape=inputShape, modelName=modelName)
    elif modelName == "mDenseNet":
        model = makemDenseNet(inputShape=inputShape, modelName=modelName)
    else:
        raise("Unknown model name given. Available names are AlexNet, InceptionResNetV2, MobileNetV2, ResNet50V2, DenseNet121, DenseNet201, NASNetLarge, SimpleNet, SimpleDenseNet, SimpleResNet, mAlexNet, and mDenseNet.")

    return model

##################################################################################################

"""
All of the following functions define the various models that can be used.
Each has the same signature:
------------------------ Params ------------------------
    inputShape: Tuple[int]      The shape of the images
    modelName: str              The name of the model
--------------------------------------------------------
"""

#Helper Function: Create an AlexNet model
def makeAlexNet(inputShape: Tuple[int], modelName: str ='') -> keras.Model:
    #Build Model
    AlexNet = Sequential(name=modelName)

    AlexNet.add(tf.keras.layers.experimental.preprocessing.Resizing(height=224, width=224))

    #1st Convolutional Layer
    AlexNet.add(Convolution2D(filters=96, input_shape=inputShape, kernel_size=(11,11), strides=(4,4), padding='same'))
    AlexNet.add(BatchNormalization())
    AlexNet.add(Activation('relu'))
    AlexNet.add(MaxPooling2D(pool_size=(2,2), strides=(2,2), padding='same'))

    #2nd Convolutional Layer
    AlexNet.add(Convolution2D(filters=256, kernel_size=(5, 5), strides=(1,1), padding='same'))
    AlexNet.add(BatchNormalization())
    AlexNet.add(Activation('relu'))
    AlexNet.add(MaxPooling2D(pool_size=(2,2), strides=(2,2), padding='same'))

    #3rd Convolutional Layer
    AlexNet.add(Convolution2D(filters=384, kernel_size=(3,3), strides=(1,1), padding='same'))
    AlexNet.add(BatchNormalization())
    AlexNet.add(Activation('relu'))

    #4th Convolutional Layer
    AlexNet.add(Convolution2D(filters=384, kernel_size=(3,3), strides=(1,1), padding='same'))
    AlexNet.add(BatchNormalization())
    AlexNet.add(Activation('relu'))

    #5th Convolutional Layer
    AlexNet.add(Convolution2D(filters=256, kernel_size=(3,3), strides=(1,1), padding='same'))
    AlexNet.add(BatchNormalization())
    AlexNet.add(Activation('relu'))
    AlexNet.add(MaxPooling2D(pool_size=(2,2), strides=(2,2), padding='same'))

    #Passing it to a Fully Connected layer
    AlexNet.add(Flatten())
    # 1st Fully Connected Layer
    AlexNet.add(Dense(4096, input_shape=(32,32,3,)))
    AlexNet.add(BatchNormalization())
    AlexNet.add(Activation('relu'))
    # Add Dropout to prevent overfitting
    AlexNet.add(Dropout(0.4))

    #2nd Fully Connected Layer
    AlexNet.add(Dense(4096))
    AlexNet.add(BatchNormalization())
    AlexNet.add(Activation('relu'))
    #Add Dropout
    AlexNet.add(Dropout(0.4))

    #3rd Fully Connected Layer
    AlexNet.add(Dense(1000))
    AlexNet.add(BatchNormalization())
    AlexNet.add(Activation('relu'))
    #Add Dropout
    AlexNet.add(Dropout(0.4))

    #Output Layer
    AlexNet.add(Dense(1))
    AlexNet.add(BatchNormalization())
    AlexNet.add(Activation('sigmoid'))

    return AlexNet

#Helper Function: Create a Keras prebuilt model
def makePrebuiltModel(inputShape: Tuple[int], modelName:str ='') -> keras.Model:
    """
    Load model by inputing the name to modelName
    Options are "InceptionResNetV2", "MobileNetV2", "ResNet50V2", "DenseNet121", "DenseNet201", and "NASNetLarge"
    """
    input = keras.Input(shape=inputShape, name="Input")
    x=None
    if modelName=="InceptionResNetV2":
        baseModel = keras.applications.InceptionResNetV2(include_top=False, weights="imagenet", input_shape=inputShape)(input)
    elif modelName=="MobileNetV2":
        baseModel = keras.applications.MobileNetV2(include_top=False, weights="imagenet", input_shape=inputShape)(input)
    elif modelName=="ResNet50V2":
        baseModel = keras.applications.ResNet50V2(include_top=False, weights="imagenet", input_shape=inputShape)(input)
    elif modelName=="DenseNet121":
        baseModel = keras.applications.DenseNet121(include_top=False, weights="imagenet", input_shape=inputShape)(input)
    elif modelName=="DenseNet201":
        baseModel = keras.applications.DenseNet201(include_top=False, weights="imagenet", input_shape=inputShape)(input)
    elif modelName=="NASNetLarge":
        x = tf.keras.layers.experimental.preprocessing.Resizing(height=331, width=331)(input)
        baseModel = keras.applications.NASNetLarge(include_top=False, weights="imagenet", input_shape=(331,331,3))(x)
    else:
        raise("Model Name Not In Recognized Keras Models")

    baseModel.trainable = False
    x = layers.Flatten()(baseModel)
    x = layers.Dense(128, activation="relu")(x)
    x = layers.Dropout(0.3)(x)
    x = layers.Dense(128, activation="relu")(x)
    x = layers.Dropout(0.3)(x)

    if x != None:
        output=layers.Dense(1, activation="sigmoid")(x)
        return keras.Model(inputs=input, outputs=output, name=modelName)
    else:
        raise("Model Name Not Recognized")

#Helper Function: Create a very simple model as a baseline
def makeSimpleNet(inputShape: Tuple[int], modelName:str ='') -> keras.Model:
    input = keras.Input(shape=inputShape, name="Input")
    x = layers.AveragePooling2D(pool_size=(50, 50))(input)
    x = layers.Flatten()(x)
    x = layers.Dense(64, activation="relu")(x)
    x = layers.Dropout(0.3)(x)
    x = layers.Dense(64, activation="relu")(x)
    x = layers.Dropout(0.3)(x)
    output=layers.Dense(1, activation="sigmoid")(x)
    return keras.Model(inputs=input, outputs=output, name=modelName)

#Helper Function: Create a mini AlexNet
def makemAlexNet(inputShape: Tuple[int], modelName:str ='') -> keras.Model:
    inputs = keras.Input(shape=inputShape, name="Input")

    x = tf.keras.layers.experimental.preprocessing.Resizing(height=224, width=224)(inputs)
    x = layers.Conv2D(filters=16, kernel_size=11, strides=4, activation="relu")(x)
    x = layers.MaxPooling2D(pool_size=3, strides=2)(x)
    x = layers.Conv2D(filters=20, kernel_size=5, strides=1, activation="relu")(x)
    x = layers.MaxPooling2D(pool_size=3, strides=2)(x)
    x = layers.Conv2D(filters=30, kernel_size=3, strides=1, activation="relu")(x)
    x = layers.MaxPooling2D(pool_size=3, strides=2)(x)
    x = layers.Dense(units=48, activation="relu")(x)
    x = layers.Flatten()(x)
    outputs = layers.Dense(units=1, activation="sigmoid")(x)

    model = keras.Model(inputs=inputs, outputs=outputs, name=modelName)
    return model

#Helper Function: Create a mini DenseNet
def makemDenseNet(inputShape: Tuple[int], modelName:str ='') -> keras.Model:
    def makeDenseBlock(groupCount: int, inputs):
        blockConcats = []
        x = layers.BatchNormalization()(inputs)
        x = layers.Conv2D(filters=16, kernel_size=(1, 1), activation="relu", padding="same")(x)
        x = layers.Conv2D(filters=16, kernel_size=(3, 3), activation="relu", padding="same")(x)
        blockConcats.append(x)
        for count in range(groupCount):
            x = layers.Concatenate()(blockConcats) if len(blockConcats) > 1 else x
            x = layers.BatchNormalization()(x)
            x = layers.Conv2D(filters=16, kernel_size=(1, 1), activation="relu", padding="same")(x)
            x = layers.Conv2D(filters=16, kernel_size=(3, 3), activation="relu", padding="same")(x)
            blockConcats.append(x)
        x = layers.BatchNormalization()(x)
        x = layers.Conv2D(filters=16, kernel_size=(1, 1), activation="relu", padding="same")(x)
        x = layers.AveragePooling2D(pool_size=(2, 2), strides=(2, 2))(x)
        return x

    inputs = keras.Input(shape=inputShape, name="Input")
    x = layers.Conv2D(filters=16, kernel_size=(7, 7), strides=(5, 5), activation="relu")(inputs)
    x = layers.MaxPooling2D(pool_size=(3, 3), strides=(2, 2))(x)

    x = makeDenseBlock(groupCount=2, inputs=x)

    x = layers.GlobalAveragePooling2D()(x)
    outputs = layers.Dense(units=1, activation="sigmoid")(x)

    return keras.Model(inputs=inputs, outputs=outputs, name=modelName)

#Helper Function: Create a simple DenseNet
def makeSimpleDenseNet(inputShape: Tuple[int], modelName:str ='') -> keras.Model:
    def makeDenseBlock(groupCount: int, inputs):
        blockConcats = []
        x = layers.BatchNormalization()(inputs)
        x = layers.Conv2D(filters=64, kernel_size=(1, 1), activation="relu", padding="same")(x)
        x = layers.Conv2D(filters=64, kernel_size=(3, 3), activation="relu", padding="same")(x)
        blockConcats.append(x)
        for count in range(groupCount):
            x = layers.Concatenate()(blockConcats) if len(blockConcats) > 1 else x
            x = layers.BatchNormalization()(x)
            x = layers.Conv2D(filters=64, kernel_size=(1, 1), activation="relu", padding="same")(x)
            x = layers.Conv2D(filters=64, kernel_size=(3, 3), activation="relu", padding="same")(x)
            blockConcats.append(x)
        x = layers.BatchNormalization()(x)
        x = layers.Conv2D(filters=64, kernel_size=(1, 1), activation="relu", padding="same")(x)
        x = layers.AveragePooling2D(pool_size=(2, 2), strides=(2, 2))(x)
        return x

    inputs = keras.Input(shape=inputShape, name="Input")
    x = layers.Conv2D(filters=32, kernel_size=(7, 7), strides=(2, 2), activation="relu")(inputs)
    x = layers.MaxPooling2D(pool_size=(3, 3), strides=(2, 2))(x)

    x = makeDenseBlock(groupCount=6, inputs=x)

    x = layers.GlobalAveragePooling2D()(x)
    outputs = layers.Dense(units=1, activation="sigmoid")(x)

    return keras.Model(inputs=inputs, outputs=outputs, name=modelName)

#Helper Function: Create a simple ResNet
def makeSimpleResNet(inputShape: Tuple[int], modelName:str ='') -> keras.Model:
    """
    Source: https://www.tensorflow.org/guide/keras/functional#a_toy_resnet_model
    """
    inputs = keras.Input(shape=inputShape, name="Input")
    x = layers.Conv2D(filters=32, kernel_size=(3, 3), activation="relu")(inputs)
    x = layers.Conv2D(filters=64, kernel_size=(3, 3), activation="relu")(x)
    block_1_output = layers.MaxPooling2D(pool_size=(3, 3), strides=(3, 3))(x)

    x = layers.Conv2D(filters=64, kernel_size=(3, 3), activation="relu", padding="same")(block_1_output)
    x = layers.Conv2D(filters=64, kernel_size=(3, 3), activation="relu", padding="same")(x)
    block_2_output = layers.add([x, block_1_output])

    x = layers.Conv2D(filters=64, kernel_size=(3, 3), activation="relu", padding="same")(block_2_output)
    x = layers.Conv2D(filters=64, kernel_size=(3, 3), activation="relu", padding="same")(x)
    block_3_output = layers.add([x, block_2_output])

    x = layers.Conv2D(filters=64, kernel_size=(3, 3), activation="relu")(block_3_output)
    x = layers.GlobalAveragePooling2D()(x)
    x = layers.Dense(units=256, activation="relu")(x)
    x = layers.Dense(units=256, activation="relu")(x)
    x = layers.Dense(units=256, activation="relu")(x)
    x = layers.Dropout(0.5)(x)
    outputs = layers.Dense(units=1, activation="sigmoid")(x)

    return keras.Model(inputs=inputs, outputs=outputs, name=modelName)


import pandas as pd

class NaiveBayes:

    """
    ##################################################################

    NaiveBayes(df:pd.DataFrame, target:str)

    -----------------------------------------------------------------

    get_nb() -> pd.DataFrame:
        Quando invocado retorna a tabela de probabilidades Naive Bayes
        :return: Pandas DataFrame

    -----------------------------------------------------------------

    get_data() -> pd.DataFrame:
        Quando invocado retorna os dados originais
        :return: Pandas DataFrame

    -----------------------------------------------------------------

    get_raw() -> pd.DataFrame:
        Quando invocado retorna a tabela com os dados originais actegorizados para classificação Naive Bayes
        :return: Pandas DataFrame

    ------------------------------------------------------------------

    classify(x: list) -> dict:
        Recebe a lista de propriedades do elemento para classificação
        :param x: list ['prop_1',...,'prop_n']
        :return: Pandas DataFrame

    ##################################################################
    """

    def __init__(self, df:pd.DataFrame, target:str):
        super(NaiveBayes, self).__init__()
        self.__df_raw = pd.DataFrame()
        self.__data = df
        self.__target = target
        self.__df = self.__build_df()

    def __format_columns(self, cols:list) -> pd.MultiIndex:
        """
        Quando invocado formata as colunas da tabela de probabilidades Naive Bayes
        :param cols: Lista de features do dataset original
        :return: Pandas MultiIndex
        """

        if len(self.__df_raw) == 0:
            df = self.__data
        else:
            df = self.__df_raw

        micolumns = []
        for col in cols:
            for val in df[col].unique():
                micolumns.append((col, val))

        micolumns.append(('Total','Proba'))

        return pd.MultiIndex.from_tuples(micolumns, names=['Classe',self.__target])

    def __prob_calculates(self, df:pd.DataFrame) -> pd.DataFrame:
        """
        Quando invocado calcula as probabilidades de cada feature da tabela de probabilidades Naive Bayes
        :return: Pandas DataFrame
        """
        for classe, col in self.__df.columns[:-1]:
            values = []

            for idx in self.__df.index:
                freq = len(df.query(f'{self.__target} == "{idx}" & {classe} == "{col}"'))
                freq_cls = len(df.query(f'{self.__target} == "{idx}"'))

                if freq == 0:
                    freq = 1

                values.append(freq / freq_cls)

            self.__df.loc[tuple(self.__df.index),(classe, col)] = values

        self.__df.loc[tuple(sorted(df[self.__target].unique())),("Total")] = (
            df[self.__target].value_counts() / len(df)).values

        return self.__df

    def __sum_calculates(self, df_x:pd.DataFrame) -> list:
        """
        Recebe o 'x' formatado como Pandas DataFrame e multiplica as probabilidades de cada classe
        :param x: Pandas DataFrame
        :return: List [prod_cls_1,...prod_cls_n
        """
        pro_probs = []
        for line in df_x.iterrows():
            p = 1
            for val in line[1].values:
                p*=val
            pro_probs.append(p)

        return pro_probs

    def __map_categ(self, x: float, quants: tuple) -> str:
        """
        Mapea o valor de acordo com o quartis da feature
        :param x: (float) Valor da feature de entrada
        :param quants: (tuple) Valores dos quartis específicos (q_25, q_75)
        :return: (str) Categoria 'Low'|'Normal'|'High'
        """
        if x <= quants[0]:
            return 'Low'
        elif x >= quants[1]:
            return 'High'
        else:
            return 'Normal'

    def __format_df(self, col:str, classify:bool = False) -> None:
        """
        Quando invocado classifica cada dado em ['Low','Normal','High'], de acordo com a coluna [<= q_25, > q_25 & < q_75, >= q_75]
        :param col: (str) Nome da feature
        :param classify: (bool) Default False, se True retorna o (q_25, q_75) para classificação da entrada
        :return: None
        """

        q1 = self.__data[col].quantile(0.25)
        q3 = self.__data[col].quantile(0.75)

        if classify:
            return q1, q3

        self.__df_raw[col] = self.__data[col].apply(lambda x: self.__map_categ(x, (q1, q3)))

    def __check_types(self, cols:list) -> None:
        """
        Quando invocado verifica o tipo de dado de cada coluna
        :param cols: lista de features do Pandas DataFrame
        :return: None
        """
        for col in cols:
            if self.__data[col].dtype != 'object':
                self.__format_df(col)

    def __build_df(self) -> pd.DataFrame:
        """
        Quando invocado constroi a tabela de probabilidades Naive Bayes
        :return: Pandas DataFrame
        """

        cols = self.__data.drop(self.__target, axis=1).columns
        self.__check_types(cols)

        df_mult = self.__format_columns(cols)

        if len(self.__df_raw) == 0:
            self.__df = pd.DataFrame(columns = df_mult, index=sorted(self.__data[self.__target].unique()))
            return self.__prob_calculates(self.__data)

        else:
            self.__df_raw[self.__target] = self.__data[self.__target]
            self.__df = pd.DataFrame(columns = df_mult, index=sorted(self.__df_raw[self.__target].unique()))
            return self.__prob_calculates(self.__df_raw)

    def get_nb(self) -> pd.DataFrame:
        """
        Quando invocado retorna a tabela de probabilidades Naive Bayes
        :return: Pandas DataFrame
        """
        return self.__df

    def get_raw(self) -> pd.DataFrame:
        """
        Quando invocado retorna o DataFrame categórico formatado para classificação Naive Bayes
        :return: Pandas DataFrame
        """
        return self.__df_raw

    def get_data(self) -> pd.DataFrame:
        """
        Quando invocado retorna od dados originais
        :return: Pandas DataFrame
        """
        return self.__data

    def classify(self, x: list) -> dict:
        """
        Recebe a lista de propriedades do elemento para classificação
        :param x: list ['prop_1',...,'prop_n']
        :return: Pandas DataFrame
        """
        cols = self.__data.drop(self.__target, axis=1).columns
        for i, val in enumerate(x):
            if type(val) != str:
                x[i] = self.__map_categ(x[i], self.__format_df(cols[i], classify=True))

        x = [col for col in zip(cols, x)]

        df_x = self.__df.loc[tuple(self.__df.index), (x)].copy()

        df_x[('Total','Proba')] = self.__df[('Total','Proba')]

        print(df_x)

        cls_prob = {}
        sum_probs = self.__sum_calculates(df_x)
        for i, val in enumerate(sum_probs):
            cls_prob[df_x.index[i]] = f'{round((val / sum(sum_probs)) * 100,2)}%'

        return cls_prob


import cv2
import mediapipe as mp
import time

class FaceDetector:
	def __init__(self, min_detection_con=0.5):
		self.min_detection_con = min_detection_con
		self.mpFace = mp.solutions.face_detection
		self.face = self.mpFace.FaceDetection(min_detection_con)
		self.mpDraw = mp.solutions.drawing_utils

	def find_faces(self, img, draw=True):
		imgRGB = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
		results = self.face.process(imgRGB)
		bboxs = []
		self.detections = results.detections
		if self.detections:
			for id, detection in enumerate(self.detections):
				bboxC = detection.location_data.relative_bounding_box
				h, w, c = img.shape
				bbox = (int(bboxC.xmin * w), int(bboxC.ymin * h),
					int(bboxC.width * w), int(bboxC.height * h))
				bboxs.append([id, bbox, detection.score])
				if draw:
					img = self.fancy_draw(img, bbox)
					cv2.putText(img, f'{int(detection.score[0]*100)}%',
						(bbox[0],bbox[1]-20),
						cv2.FONT_HERSHEY_PLAIN,
						2, (255, 0, 255), 2)
		return img, bboxs

	def fancy_draw(self, img, bbox, length=30, thick=5, rect_thick=1):
		x, y, w, h = bbox
		x1, y1 = x + w, y + h
		# Top left
		cv2.line(img, (x,y), (x+length,y), (255,0,255), thick)
		cv2.line(img, (x,y), (x,y+length), (255,0,255), thick)
		# Top right
		cv2.line(img, (x1,y), (x1-length,y), (255,0,255), thick)
		cv2.line(img, (x1,y), (x1,y+length), (255,0,255), thick)
		# Bottom left
		cv2.line(img, (x,y1), (x+length,y1), (255,0,255), thick)
		cv2.line(img, (x,y1), (x,y1-length), (255,0,255), thick)
		# Bottom right
		cv2.line(img, (x1,y1), (x1-length,y1), (255,0,255), thick)
		cv2.line(img, (x1,y1), (x1,y1-length), (255,0,255), thick)
		# Rectangle
		cv2.rectangle(img, bbox, (255,0,255), rect_thick)
		return img

def main():
	cap = cv2.VideoCapture(0) # 1 ?
	detector = FaceDetector()
	pTime = 0
	cTime = 0
	while 1:
		success, img = cap.read()
		img, bboxs = detector.find_faces(img)
		print(bboxs)
		cTime = time.time()
		fps = 1/(cTime - pTime)
		pTime = cTime
		cv2.putText(img, str(int(fps)), (10, 70), cv2.FONT_HERSHEY_PLAIN, 3, (0, 255, 0), 3)
		cv2.imshow("Image", img)
		cv2.waitKey(1)

if __name__ == '__main__':
	main()


import pydicom
import os
import re
import pickle
import argparse

import numpy as np
from matplotlib.path import Path

from lymphkill.file_utils import find_prefixed_file, find_dicom_directory

'''
Load all valid CT images for a given patient
Parameters:
	directory - The directory to look in
	siUID - The patient Study Instance UID
Returns:
	imgheaders - A list of loaded images
'''
def load_dicom_imageheaders(directory, siUID):
	#Loop over dicom files in directory
	imgheaders = []
	for i in os.listdir(directory):
		try:
			info = pydicom.dcmread(os.path.join(directory, i))
			if info.StudyInstanceUID == siUID and re.search('^RT', info.Modality) is None:
				imgheaders.append(info)
		except:
			continue
	imgheaders.sort(key=lambda x: int(x.InstanceNumber))
	return imgheaders

'''
Build an affine transformation for transforming real space points to voxel points
Parameters:
	headers - Imageheaders for each CT image
Returns:
	A - the affine transformation matrix
'''
def build_affine_transformation(headers):
	print('here is headers', type(headers), headers)
	N = len(headers)
	dr, dc = float(headers[0].PixelSpacing[0]), float(headers[0].PixelSpacing[1])
	F = np.zeros([3, 2])
	F[:, 0] = headers[0].ImageOrientationPatient[:3]
	F[:, 1] = headers[0].ImageOrientationPatient[3:6]

	T1 = np.array(headers[0].ImagePositionPatient, dtype='float')
	TN = np.array(headers[-1].ImagePositionPatient, dtype='float')
	k = (T1 - TN) / (1 - N)

	A = np.array([
		[F[0, 0] * dr, F[0, 1] * dc, k[0], T1[0]],
		[F[1, 0] * dr, F[1, 1] * dc, k[1], T1[1]],
		[F[2, 0] * dr, F[2, 1] * dc, k[2], T1[2]],
		[0, 0, 0, 1]])

	return A

'''
Read structures from rtstruct information and build contours dictionary
Parameters:
	rtstruct - The loaded RTSTRUCT file
	imageheaders - headers for each CT image in the patient directory
Returns
	contours - A dictionary containing organ information
		keys: ROIName, Points, VoxPoints, Segmentation
'''
def read_structures(rtstruct, imageheaders):
	xfm = build_affine_transformation(imageheaders)
	dim_min = np.array([0, 0, 0, 1])
	dim_max = np.array([int(imageheaders[0].Columns)-1, int(imageheaders[1].Rows)-1, len(imageheaders)-1, 1])

	roi_contour_sequence = rtstruct.ROIContourSequence
	roi_structureset_sequence = rtstruct.StructureSetROISequence

	nrois = len(roi_contour_sequence)
	contours = {
		'ROIName': [None] * nrois,
		'Points': [None] * nrois,
		'VoxPoints': [None] * nrois,
		'Segmentation': [None] * nrois
	}

	for i in range(nrois):
		template = np.zeros([imageheaders[0].Columns, imageheaders[1].Rows, len(imageheaders)],
							dtype=bool)
		roi_contour = roi_contour_sequence[i]
		roi_number = roi_contour.ReferencedROINumber
		roi_structureset = next((x for x in roi_structureset_sequence if x.ROINumber == roi_number), None)
		contours['ROIName'][i] = roi_structureset.ROIName
		contours['Segmentation'][i] = template

		print(contours['ROIName'][i])

		try:
			contour_sequence = roi_contour.ContourSequence
			segments = [None] * len(contour_sequence)
			#Loop through segments
			for j in range(len(contour_sequence)):
				contour = contour_sequence[j]
				if contour.ContourGeometricType == 'CLOSED_PLANAR':
					#Read segment points
					segments[j] = np.reshape(contour.ContourData,
						[contour.NumberOfContourPoints, 3])

					#Make lattice
					d4seg = np.ones([segments[j].shape[0], 4])
					d4seg[:, :3] = segments[j]
					d4seg = d4seg.transpose()
					d4start = np.append(segments[j][0], 1)
					points = np.linalg.solve(xfm, d4seg)
					start = np.linalg.solve(xfm, d4start)

					minvox = np.maximum(np.floor(np.min(points, axis=1)), dim_min)
					maxvox = np.minimum(np.ceil(np.max(points, axis=1)), dim_max)

					minvox[2] = round(start[2])
					maxvox[2] = round(start[2])

					minvox = minvox.astype(int)
					maxvox = maxvox.astype(int)

					x, y, z = np.meshgrid(
						np.arange(minvox[0], maxvox[0]+1),
						np.arange(minvox[1], maxvox[1]+1),
						np.arange(minvox[2], maxvox[2]+1))

					points = np.matmul(
						xfm,
						np.array([x.flatten(), y.flatten(), z.flatten(), np.ones(x.size)]))

					#Make binary image
					segpath = Path(segments[j][:, :2])
					inpoly = segpath.contains_points(points[:2].transpose())
					inpoly = inpoly.reshape(x.shape)
					inpoly = np.transpose(inpoly, (1, 0, 2))
					contours['Segmentation'][i][minvox[0]:maxvox[0]+1, minvox[1]:maxvox[1]+1, minvox[2]:maxvox[2]+1] = inpoly

			contours['Points'][i] = np.vstack(segments)
			d4p = np.ones([contours['Points'][i].shape[0], 4])
			d4p[:, :3] = contours['Points'][i]
			d4p = d4p.transpose()
			contours['VoxPoints'][i] = np.linalg.solve(xfm, d4p)
			contours['VoxPoints'][i] = contours['VoxPoints'][i][:3]
		except Exception as ex:
			print(type(ex), ex)
			print('Loading contour %d: %s failed' % (i, contours['ROIName'][i]))

	return contours

'''
Load in structures from the RTSTRUCT file in a given directory
Parameters:
	directory - The directory containing dicom files
	struct_prefix - The file prefix for the RTSTRUCT file
Returns:
	contours - A dictionary containing organ information
		keys: ROIName, Points, VoxPoints, Segmentation
'''
def load_structures(directory, struct_prefix='RTSTRUCT'):
	rtstruct_file = find_prefixed_file(directory, struct_prefix)
	rtstruct = pydicom.dcmread(rtstruct_file)
	imageheaders = load_dicom_imageheaders(directory, rtstruct.StudyInstanceUID)

	print('Found Organs:')
	for struct in rtstruct.StructureSetROISequence:
		print(struct.ROIName)

	print('Loading in contours')
	contours = read_structures(rtstruct, imageheaders)

	return contours

if __name__=='__main__':
	parser = argparse.ArgumentParser()
	parser.add_argument('directory', type=str, help='The patient directory to look in')
	args = parser.parse_args()

	directory = find_dicom_directory(args.directory)
	contours = load_structures(directory)
	for i in contours['ROIName']:
		print(i)
	outfile = open(os.path.join(args.directory, 'contours.pickle'), 'wb')
	pickle.dump(contours, outfile)
	outfile.close()


import wx
import pickle
from new_GPIB_manager import *
import time
import numpy as np

############################################################################
############################################################################
######                                                                ######
######                          Parameters to specify                 ######
######                                                                ######
############################################################################
############################################################################

source_list = ['HP', 'Santec']  # List of the laser sources currently connected

default_data_folder = 'C:/Users/Marc'  # Base folder to store the data
path_loss_cal_file = 'cal_l-r_bypass_patchcord.pickle'  # Path to the pickle calibration file
persistent_pickle_file = 'persistent_parameter_data.pickle'

############################################################################
############################################################################
######                                                                ######
######              Controller Frame Class Definition                 ######
######                                                                ######
############################################################################
############################################################################

# Create a button class to handle the laser commands (helper function)
class LaserButton(wx.ToggleButton):

    def __init__(self, parent):

        wx.ToggleButton.__init__(self, parent, -1, 'Laser Power', size=(75, 20))
        self.parent = parent
        self.active = 0  # Start assuming laser is off
        self.SetBackgroundColour(wx.RED)
        self.Bind(wx.EVT_TOGGLEBUTTON, self.on_toggle)

    def on_toggle(self, event):

        if self.active == 0:
            # If laser is off, turn it on
            self.parent.parent.parent.gpib_manager.turn_on_laser_f()
            self.active = 1
            self.SetBackgroundColour(wx.GREEN)
            self.parent.parent.sb.SetStatusText('Turning on the Source laser')
        else:
            # If laser is on, turn it off
            self.parent.parent.parent.gpib_manager.turn_off_laser_f()
            self.active = 0
            self.SetBackgroundColour(wx.RED)
            self.parent.parent.sb.SetStatusText('Turning off the Source laser')

# Create a new frame class, derived from the wxPython Frame.
class ControllerFrame(wx.Frame):

    def __init__(self, parent, id, title):

        # First, call the base class' __init__ method to create the frame
        wx.Frame.__init__(self, None, id, title)

        # Save our variables
        self.parent = parent
        self.id = id
        self.title = title

        # Bind the close event to the function on_exit
        # (This assures that even if you hit the x button the
        # device gets properly closed.)
        self.Bind(wx.EVT_CLOSE, self.on_exit)

        # Create a status bar to show some messages in the bottom of the GUI
        self.sb = self.CreateStatusBar()

        # Create the Menu. This is a private method.
        self.__create_menu__()

        # Initialize panel
        panel = wx.Panel(self, -1)

        # Add panel to frame
        panel.parent = self
        self.panel = panel

        sizer_list = self.__create_GUI_controls__()

        self.__create_top_level_sizers__(sizer_list)

        # Default: take wavelength sweeps with the NI DAQ
        self.daq_sweep.SetValue(True)

    def __create_menu__(self):
        # For each dropdown menu, the process is always the same: generate an element
        # and give it an ID number so that we can afterwards relate it to a function
        # that will be called when the element is clicked.

        # Set up a File Menu.
        file_menu_ids = [0, 1]
        file_menu_captions = ["&About", "E&xit"]
        file_menu_infos = [" Information about PhotonMover",
                           " Terminate PhotonMover"]
        file_menu_methods = [self.on_about, self.on_exit]
        file_menu = self.__create_dropdown__(file_menu_ids, file_menu_captions,
                                             file_menu_infos, file_menu_methods)

        # Set up a Defaults Menu
        defaults_menu_ids = [10, 11]
        defaults_menu_captions = ["&Save", "&Load"]
        defaults_menu_infos = [" Save Current Parameters as Defaults",
                               " Load Default Parameters"]
        defaults_menu_methods = [self.on_save, self.on_load]
        defaults_menu = self.__create_dropdown__(defaults_menu_ids, defaults_menu_captions,
                                                 defaults_menu_infos, defaults_menu_methods)

        # Set up an Experiments Menu
        exp_menu_ids = [100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112]
        exp_menu_captions = ["Transmission",
                             "Transmission vs. bias",
                             "Transmission vs. power",
                             "I-V",
                             "Responsivity-Lambda-V",
                             "Transmission-Time",
                             "Responsivity-Lambda-P",
                             "Calibration",
                             "Retrieve Bandwidth measurement",
                             "Bandwidth vs bias vs wl measurement",
                             "Measure and get bandwidth data",
                             "Measure transistor output curve",
                             "Measure pv mod output curve"]
        exp_menu_infos = [" Measure transmission over preset wavelength set, constant bias",
                          " Measure transmission over preset wavelength set and bias, constant power",
                          " Perform wavelength sweeps at different power levels",
                          " IV curve at a specific power using the parameter analyzer with preset V steps",
                          " Measure responsivity over preset wavelength and bias set",
                          " Measure transmission over time",
                          " Measure responsivity over preset wavelength and power set",
                          " Routine to calibrate the setup's back to back transmission vs. wavelength",
                          " Acquires a bandwidth trace from the VNA",
                          " Gets bandwidth data at different bias voltages and wavelengths using the vNA",
                          " Triggers a VNA measurement and saves the data",
                          " Measure Ids vs Vds for varying Vgs",
                          " Meaure Vds vs Vgs for a fixed Ids"
                          ]
        exp_menu_methods = [self.on_tx, self.on_tx_vs_V, self.on_tx_vs_P, self.on_IV,
                            self.on_RLV, self.on_tx_vs_time, self.on_RLP, self.on_calibration,
                            self.on_BW, self.on_BW_vs_V, self.on_BW_trigger, self.on_trans_output_curve,
                            self.on_pv_mod_outp]
        experiments_menu = self.__create_dropdown__(exp_menu_ids, exp_menu_captions,
                                                    exp_menu_infos, exp_menu_methods)
        # Create the Menubar
        menu_bar = wx.MenuBar()
        menu_bar.Append(file_menu, "&File")
        menu_bar.Append(defaults_menu, "&Defaults")
        menu_bar.Append(experiments_menu, "&Experiments")
        self.SetMenuBar(menu_bar)

    def __create_dropdown__(self, id_list, caption_list, info_list, method_list):
        """
        Creates a dropdown with the elements specified in the parameters
        :param id_list: list of IDs for each menu element
        :param caption_list: list of captions for each menu element
        :param info_list:  list of description for each menu element
        :param method_list: list of methods to bind each menu element to
        :return: The menu handler
        """

        # Create the menu
        menu = wx.Menu()

        # Generate the elements and bind them to a method
        for i in range(len(id_list)):
            menu.Append(id_list[i], caption_list[i], info_list[i])
            wx.EVT_MENU(self, id_list[i], method_list[i])

        return menu

    def __create_GUI_controls__(self):
        """
        Creates the different GUI elements (buttons, texts...). It sucks because it is very manual.
        :return: A list with the sizers containing the different elements
        """

        # Create a font for headings
        # heading_font = wx.Font(15, wx.DEFAULT, wx.NORMAL, wx.BOLD)

        # self.optics_control_heading = wx.StaticText(self.panel, -1, 'Optics Status and Controls')
        # self.optics_control_heading.SetFont(heading_font)

        # Create the controls and bind them to the correct handling functions if necessary

        # I don't think we need this anymore
        #laser_source_label, self.laser_source_menu = \
        #    self.create_label_and_dropdown('Choose Laser Source:', 1000, source_list)
        #self.Bind(wx.EVT_COMBOBOX, self.on_laser_change, id=1000)

        laser_wl_label, self.laser_wl_param = \
            self.create_label_and_textbox('Set wl:', 1001, "%.2f" % self.parent.wavelength, (60, 20))
        self.Bind(wx.EVT_TEXT_ENTER, self.on_wavelength_change, id=1001)

        tf_wl_label, self.tf_wl_param = \
            self.create_label_and_textbox('Set tun. filter wl:', 1023, "%.2f" % self.parent.tf_wavelength, (60, 20))
        self.Bind(wx.EVT_TEXT_ENTER, self.on_tf_wavelength_change, id=1023)

        tf_w_laser_label, self.tf_w_laser = \
            self.create_label_and_checkbox('Tunable filter track laser?', 1024)
        self.Bind(wx.EVT_CHECKBOX, self.on_tf_w_laser_change, id=1024)

        sweep_ni_daq_label, self.daq_sweep = \
            self.create_label_and_checkbox('Take wavelength sweeps with DAQ?', 1025)
        self.Bind(wx.EVT_CHECKBOX, self.on_daq_acq_change, id=1025)

        store_current_label, self.store_current = \
            self.create_label_and_checkbox('Store the current when lam sweep? (applies only if not using DAQ)', 1026)
        self.Bind(wx.EVT_CHECKBOX, self.on_store_current_change, id=1026)

        pm_range_label, self.pm_range_param = \
            self.create_label_and_textbox('Power Meter Range:', 1027, "%.2f" % self.parent.pm_range, (80, 20))
        self.Bind(wx.EVT_TEXT_ENTER, self.on_range_change, id=1027)

        laser_power_label, self.laser_power_param = \
            self.create_label_and_textbox('Set Power:', 1002, "%.2f" % self.parent.power, (40, 20))
        self.Bind(wx.EVT_TEXT_ENTER, self.on_power_change, id=1002)

        meas_power_label, self.meas_power_param = \
            self.create_label_and_textbox('Meas. In Power:', 1003, "????", (80, 20))

        meas_wl_label, self.meas_wl_param = \
            self.create_label_and_textbox('Meas. wl:', 1004, "????", (70, 20))

        meas_rec_power_label, self.meas_rec_power_param = \
            self.create_label_and_textbox('Received Power:', 1005, "--.---", (80, 20))

        tap_power_label, self.tap_power_param = \
            self.create_label_and_textbox('Tap Power:', 1006, "--.---", (80, 20))

        through_loss_label, self.through_loss_param = \
            self.create_label_and_textbox('Loss:', 1007, "--.---", (60, 20))

        pc_channel_label, self.pc_channel_param = \
            self.create_label_and_textbox('SM Ch.:', 1008, "%d" % self.parent.smu_channel, (25, 20))
        self.Bind(wx.EVT_TEXT_ENTER, self.on_sm_channel_change, id=1008)

        pd_bias_voltage_label, self.pd_bias_voltage_param = \
            self.create_label_and_textbox('SM voltage (V):', 1009, "%d" % self.parent.pd_bias, (30, 20))
        self.Bind(wx.EVT_TEXT_ENTER, self.on_bias_change, id=1009)

        meas_photocurrent_label, self.meas_photocurrent_param = \
            self.create_label_and_textbox('SM current (A):', 1010, "????", (70, 20))

        gate_bias_voltage_label, self.gate_bias_voltage_param = \
            self.create_label_and_textbox('SM2 voltage (V):', 1031, "%d" % self.parent.pd_bias_2, (30, 20))
        self.Bind(wx.EVT_TEXT_ENTER, self.on_bias_2_change, id=1031)

        meas_gate_current_label, self.meas_gate_current_param = \
            self.create_label_and_textbox('SM2 current (A):', 1032, "????", (70, 20))

        el_attenuation_label, self.el_attenuation_param = \
            self.create_label_and_textbox('Electrical Att (dB):', 1022, "%d" % self.parent.el_att, (30, 20))
        self.Bind(wx.EVT_TEXT_ENTER, self.on_el_att_change, id=1022)

        responsivity_label, self.responsivity_param = \
            self.create_label_and_textbox('Calc. Resp:', 1011, "????", (50, 20))

        qe_label, self.qe_param = \
            self.create_label_and_textbox('Calc. QE:', 1012, "????", (50, 20))

        self.ID_START_MEAS_WAVELENGTH_PARAM = 1013
        start_wl_label, self.start_wl_param = \
            self.create_label_and_textbox('Start Lambda (nm):', self.ID_START_MEAS_WAVELENGTH_PARAM, str(self.parent.start_meas_wl), (100, 20))
        self.Bind(wx.EVT_TEXT_ENTER, self.on_measurement_param_change, id=self.ID_START_MEAS_WAVELENGTH_PARAM)

        self.ID_STOP_MEAS_WAVELENGTH_PARAM = 1014
        stop_wl_label, self.stop_wl_param = \
            self.create_label_and_textbox('Stop Lambda (nm):', self.ID_STOP_MEAS_WAVELENGTH_PARAM, str(self.parent.stop_meas_wl), (100, 20))
        self.Bind(wx.EVT_TEXT_ENTER, self.on_measurement_param_change, id=self.ID_STOP_MEAS_WAVELENGTH_PARAM)

        self.ID_NUM_MEAS_WAVELENGTH_PARAM = 1015
        num_wl_label, self.num_wl_param = \
            self.create_label_and_textbox('Num. Lambda (nm):', self.ID_NUM_MEAS_WAVELENGTH_PARAM, str(self.parent.num_meas_wl), (60, 20))
        self.Bind(wx.EVT_TEXT_ENTER, self.on_measurement_param_change, id=self.ID_NUM_MEAS_WAVELENGTH_PARAM)

        self.ID_START_MEAS_POWER_PARAM = 1016
        start_power_label, self.start_power_param = \
            self.create_label_and_textbox('Start Power (mW):', self.ID_START_MEAS_POWER_PARAM, str(self.parent.start_meas_power), (100, 20))
        self.Bind(wx.EVT_TEXT_ENTER, self.on_measurement_param_change, id=self.ID_START_MEAS_POWER_PARAM)

        self.ID_STOP_MEAS_POWER_PARAM = 1017
        stop_power_label, self.stop_power_param = \
            self.create_label_and_textbox('Stop Power (mW):', self.ID_STOP_MEAS_POWER_PARAM, str(self.parent.stop_meas_power), (100, 20))
        self.Bind(wx.EVT_TEXT_ENTER, self.on_measurement_param_change, id=self.ID_STOP_MEAS_POWER_PARAM)

        self.ID_NUM_MEAS_POWER_PARAM = 1018
        num_power_label, self.num_power_param = \
            self.create_label_and_textbox('Num. Power (mW):', self.ID_NUM_MEAS_POWER_PARAM, str(self.parent.num_meas_power), (60, 20))
        self.Bind(wx.EVT_TEXT_ENTER, self.on_measurement_param_change, id=self.ID_NUM_MEAS_POWER_PARAM)

        self.ID_START_MEAS_V_PARAM = 1019
        start_v_label, self.start_v_param = \
            self.create_label_and_textbox('Start Bias (V):', self.ID_START_MEAS_V_PARAM, str(self.parent.start_meas_v), (100, 20))
        self.Bind(wx.EVT_TEXT_ENTER, self.on_measurement_param_change, id=self.ID_START_MEAS_V_PARAM)

        self.ID_STOP_MEAS_V_PARAM = 1020
        stop_v_label, self.stop_v_param = \
            self.create_label_and_textbox('Stop Bias (V):', self.ID_STOP_MEAS_V_PARAM, str(self.parent.stop_meas_v), (100, 20))
        self.Bind(wx.EVT_TEXT_ENTER, self.on_measurement_param_change, id=self.ID_STOP_MEAS_V_PARAM)

        self.ID_NUM_MEAS_V_PARAM = 1021
        num_v_label, self.num_v_param = \
            self.create_label_and_textbox('Num. Bias (V):', self.ID_NUM_MEAS_V_PARAM, str(self.parent.num_meas_v), (60, 20))
        self.Bind(wx.EVT_TEXT_ENTER, self.on_measurement_param_change, id=self.ID_NUM_MEAS_V_PARAM)

        self.ID_START_GATE_V_PARAM = 1028
        start_gate_v_label, self.start_gate_v_param = \
            self.create_label_and_textbox('Start Gate Bias (V):', self.ID_START_GATE_V_PARAM, str(self.parent.start_gate_v),
                                          (100, 20))
        self.Bind(wx.EVT_TEXT_ENTER, self.on_measurement_param_change, id=self.ID_START_GATE_V_PARAM)

        self.ID_STOP_GATE_V_PARAM = 1029
        stop_gate_v_label, self.stop_gate_v_param = \
            self.create_label_and_textbox('Stop Gate Bias (V):', self.ID_STOP_GATE_V_PARAM, str(self.parent.stop_gate_v),
                                          (100, 20))
        self.Bind(wx.EVT_TEXT_ENTER, self.on_measurement_param_change, id=self.ID_STOP_GATE_V_PARAM)

        self.ID_NUM_GATE_V_PARAM = 1030
        num_gate_v_label, self.num_gate_v_param = \
            self.create_label_and_textbox('Num. Gate Bias (V):', self.ID_NUM_GATE_V_PARAM, str(self.parent.num_gate_v),
                                          (60, 20))
        self.Bind(wx.EVT_TEXT_ENTER, self.on_measurement_param_change, id=self.ID_NUM_GATE_V_PARAM)

        # Add connections for updating info every time power is measured
        self.through_loss_param.Connect(-1, -1, EVT_MEASURED_POWERS, self.on_measured_powers)
        self.through_loss_param.Connect(-1, -1, EVT_DONE, self.on_done)

        # Laser button to turn it on and off
        laser_power_button = LaserButton(self.panel)

        # Now, let's create sizers grouping the different elements
        # Create and populate optics control sizer
        sizer_list = list()

        # Sweep type control
        sizer_list.append(self.create_sizer(wx.HORIZONTAL,
                                            [sweep_ni_daq_label, self.daq_sweep],
                                            [3, 8]))

        sizer_list.append(self.create_sizer(wx.HORIZONTAL,
                                            [store_current_label, self.store_current],
                                            [3, 8]))

        # Laser source control sizer
        #sizer_list.append(self.create_sizer(wx.HORIZONTAL,
        #                                    [laser_source_label, self.laser_source_menu,
        #                                     laser_power_button],
        #                                    [3, 8]))
        sizer_list.append(self.create_sizer(wx.HORIZONTAL,
                                            [laser_power_button, pm_range_label, self.pm_range_param],
                                            [3, 3, 10]))

        # Laser wavelength sizer
        sizer_list.append(self.create_sizer(wx.HORIZONTAL,
                                            [laser_wl_label, self.laser_wl_param, meas_wl_label,
                                             self.meas_wl_param],
                                            [3, 8, 3]))

        # Tunable filter wavelength sizer
        sizer_list.append(self.create_sizer(wx.HORIZONTAL,
                                            [tf_wl_label, self.tf_wl_param, tf_w_laser_label, self.tf_w_laser],
                                            [3, 8, 3]))

        # Laser power sizer
        sizer_list.append(self.create_sizer(wx.HORIZONTAL,
                                            [laser_power_label, self.laser_power_param,
                                             meas_power_label, self.meas_power_param],
                                            [3, 8, 3]))

        # Received power sizer
        sizer_list.append(self.create_sizer(wx.HORIZONTAL,
                                            [meas_rec_power_label, self.meas_rec_power_param],
                                            [3]))

        # Measured tap power sizer
        sizer_list.append(self.create_sizer(wx.HORIZONTAL,
                                            [tap_power_label, self.tap_power_param],
                                            [3]))

        # Transmission sizer
        sizer_list.append(self.create_sizer(wx.HORIZONTAL,
                                            [through_loss_label, self.through_loss_param],
                                            [3]))

        # SM sizer
        sizer_list.append(self.create_sizer(wx.HORIZONTAL,
                                            [pc_channel_label, self.pc_channel_param,
                                             pd_bias_voltage_label, self.pd_bias_voltage_param,
                                             meas_photocurrent_label, self.meas_photocurrent_param],
                                            [3, 8, 3, 8, 3]))

        sizer_list.append(self.create_sizer(wx.HORIZONTAL,
                                            [gate_bias_voltage_label, self.gate_bias_voltage_param,
                                             meas_gate_current_label, self.meas_gate_current_param],
                                            [3, 8, 3]))

        # Attenuator
        sizer_list.append(self.create_sizer(wx.HORIZONTAL,
                                            [el_attenuation_label, self.el_attenuation_param],
                                            [3]))

        # Responsivity and QE sizer
        sizer_list.append(self.create_sizer(wx.HORIZONTAL,
                                            [responsivity_label, self.responsivity_param,
                                             qe_label, self.qe_param,],
                                            [3, 8, 3]))

        # Wavelength sweep sizers
        # sizer_list.append(self.create_sizer(wx.HORIZONTAL,
        #                                     [start_wl_label, self.start_wl_param],
        #                                     [3]))
        # sizer_list.append(self.create_sizer(wx.HORIZONTAL,
        #                                     [stop_wl_label, self.stop_wl_param],
        #                                     [3]))
        # sizer_list.append(self.create_sizer(wx.HORIZONTAL,
        #                                     [num_wl_label, self.num_wl_param],
        #                                     [3]))
        sizer_list.append(self.create_sizer(wx.HORIZONTAL,
                                            [start_wl_label, self.start_wl_param, stop_wl_label, self.stop_wl_param,
                                             num_wl_label, self.num_wl_param],
                                            [3, 5, 3, 5, 3]))

        # Power sweep sizers
        #sizer_list.append(self.create_sizer(wx.HORIZONTAL,
        #                                    [start_power_label, self.start_power_param],
        #                                    [3]))
        #sizer_list.append(self.create_sizer(wx.HORIZONTAL,
        #                                    [stop_power_label, self.stop_power_param],
        #                                    [3]))
        #sizer_list.append(self.create_sizer(wx.HORIZONTAL,
        #                                    [num_power_label, self.num_power_param],
        #                                    [3]))
        sizer_list.append(self.create_sizer(wx.HORIZONTAL,
                                            [start_power_label, self.start_power_param, stop_power_label,
                                             self.stop_power_param, num_power_label, self.num_power_param],
                                            [3, 5, 3, 5, 3]))

        # Voltage sweep sizers
        # sizer_list.append(self.create_sizer(wx.HORIZONTAL,
        #                                     [start_v_label, self.start_v_param],
        #                                     [3]))
        # sizer_list.append(self.create_sizer(wx.HORIZONTAL,
        #                                     [stop_v_label, self.stop_v_param],
        #                                     [3]))
        # sizer_list.append(self.create_sizer(wx.HORIZONTAL,
        #                                     [num_v_label, self.num_v_param],
        #                                     [3]))
        sizer_list.append(self.create_sizer(wx.HORIZONTAL,
                                            [start_v_label, self.start_v_param, stop_v_label, self.stop_v_param,
                                             num_v_label, self.num_v_param],
                                            [3, 5, 3, 5, 3]))

        # Gate Voltage sweep sizers
        # sizer_list.append(self.create_sizer(wx.HORIZONTAL,
        #                                     [start_gate_v_label, self.start_gate_v_param],
        #                                     [3]))
        # sizer_list.append(self.create_sizer(wx.HORIZONTAL,
        #                                     [stop_gate_v_label, self.stop_gate_v_param],
        #                                     [3]))
        # sizer_list.append(self.create_sizer(wx.HORIZONTAL,
        #                                     [num_gate_v_label, self.num_gate_v_param],
        #                                     [3]))
        sizer_list.append(self.create_sizer(wx.HORIZONTAL,
                                            [start_gate_v_label, self.start_gate_v_param, stop_gate_v_label,
                                             self.stop_gate_v_param, num_gate_v_label, self.num_gate_v_param],
                                            [3, 5, 2, 5, 3]))

        return sizer_list

    def __create_top_level_sizers__(self, sizer_list):
        """
        Creates the top level sizer integrating all the subsizers with the different controls.
        :param sizer_list: List of lower level sizers to be integrated
        :return: None
        """

        top_level_sizer = wx.BoxSizer(wx.VERTICAL)
        #top_level_sizer.Add(self.optics_control_heading, 0, wx.BOTTOM, 10)

        for sizer in sizer_list:
            top_level_sizer.Add(sizer, 0, wx.ALIGN_LEFT | wx.BOTTOM, 20)

        # Create the outer box and fit it all
        border = wx.BoxSizer()
        border.Add(top_level_sizer, 0, wx.ALL | wx.EXPAND, 10)
        self.panel.SetSizerAndFit(border)
        self.Fit()

    def create_label_and_dropdown(self, label, id, dropdown_list):
        """
        Creates a pair of static label and dropdown with the specified parameters
        :param label: Label of the static text box
        :param id: id taht will be later used to bind a function
        :param dropdown_list: list with the different options that will appear in the dropdown
        :return: two elements, the first one is the static text, and the second is the dropdown
        """

        label = wx.StaticText(self.panel, -1, label)

        dropdown = wx.ComboBox(self.panel, id, value=dropdown_list[0],
                               choices=dropdown_list, style=wx.CB_READONLY)

        return label, dropdown

    def create_label_and_checkbox(self, label, id):
        """
        Creates a pair of static label and checkbox with the specified parameters
        :param label: Label of the static text box
        :param id: id taht will be later used to bind a function
        :return: two elements, the first one is the static text, and the second is the checkbox
        """

        label = wx.StaticText(self.panel, -1, label)

        checkbox = wx.CheckBox(self.panel, id)

        return label, checkbox

    def create_label_and_textbox(self, label, id, textbox_content, size):
        """
        Creates a pair of static label and text box taht you can write to
        :param label: Label of the static text box
        :param id: id that will be later used to bind a function
        :param size: size of the text box
        :param textbox_content: initial caption of the textbox
        :return: two elements, the first one is the static text, and the second is the textbox
        """

        label = wx.StaticText(self.panel, -1, label)

        text_box = wx.TextCtrl(self.panel, id, textbox_content, size=size,
                               style=wx.TE_PROCESS_ENTER)

        return label, text_box

    def create_sizer(self, sizer_direction, element_list, spacer_list):
        """
        Creates sizers containing the specified elements
        :param sizer_direction: Direction of the sizer, either wx.HORIZONTAL or wx.VERTICAL
        :param element_list: list containing the elements to be put in each sizer
        :param spacer_list: list of the spacer dimensions (x,y) to be put between each element
        :return: the sizer
        """

        sizer = wx.BoxSizer(sizer_direction)
        for ind in range(len(element_list)):
            sizer.Add(element_list[ind])
            if len(spacer_list) > ind:
                sizer.AddSpacer(spacer_list[ind])

        return sizer

    ############################################################################
    ############################################################################
    ######                                                                ######
    ######       Here starts the definition of the bidnings               ######
    ######                                                                ######
    ############################################################################
    ############################################################################

    # Binding for wavelength parameter change
    def on_wavelength_change(self, event):
        self.parent.wavelength = float(self.laser_wl_param.GetValue())
        if self.parent.gpib_manager.tf_with_laser:
            #  Change the tunable filter wavelength in the textbox
            self.tf_wl_param.SetValue(self.laser_wl_param.GetValue())
        self.parent.gpib_manager.set_wavelength(self.parent.wavelength)
        self.sb.SetStatusText('Adjusting the Laser Wavelength to %.2f' % self.parent.wavelength)

    # Binding for tunable filter wavelength parameter change
    def on_tf_wavelength_change(self, event):
        self.parent.tf_wavelength = float(self.tf_wl_param.GetValue())
        self.parent.gpib_manager.set_tf_wavelength(self.parent.tf_wavelength)
        self.sb.SetStatusText('Adjusting the Tun Filter Wavelength to %.2f' % self.parent.wavelength)

    # Binding for the tunable filter wavelength tracking the laser
    def on_tf_w_laser_change(self, event):

        self.parent.gpib_manager.set_tf_w_laser(self.tf_w_laser.IsChecked())
        if self.tf_w_laser.IsChecked():
            self.sb.SetStatusText('Tun Filter will track laser wl')
        else:
            self.sb.SetStatusText('Tun Filter will not track laser wl')

    # Binding for the checkbox indicating the use of the NI DAQ to take wavelength sweeps
    def on_daq_acq_change(self, event):
        self.parent.gpib_manager.set_DAQ_acq(self.daq_sweep.IsChecked())
        if self.daq_sweep.IsChecked():
            self.sb.SetStatusText('Wav sweeps will be taken wit the NI DAQ')
        else:
            self.sb.SetStatusText('Wav sweeps wii be taken using the HP Lightwave')

    # Binding for the checkbox indicating the use of the NI DAQ to take wavelength sweeps
    def on_store_current_change(self, event):
        self.parent.gpib_manager.set_store_current(self.store_current.IsChecked())
        if self.store_current.IsChecked():
            self.sb.SetStatusText('Current will be recorded when sweeping with HP Lightwave')
        else:
            self.sb.SetStatusText('Current will not be recorded when sweeping with HP Lightwave')

    # Binding for power meter range change
    def on_range_change(self, event):
        self.parent.pm_range = float(self.pm_range_param.GetValue())
        self.parent.gpib_manager.set_pm_range(self.parent.pm_range)
        self.sb.SetStatusText('Adjusting the Power Meter Range to %.2f' % self.parent.pm_range)

    # Binding for power parameter change
    def on_power_change(self, event):
        self.parent.power = float(self.laser_power_param.GetValue())
        self.parent.gpib_manager.set_power(self.parent.power)
        self.sb.SetStatusText('Adjusting the Laser Power to %.2f' % self.parent.power)

    # Binding for laser source change
    # def on_laser_change(self, event):
    #    self.parent.laser_source = self.laser_source_menu.GetValue()

    # Binding for parameter analyzer photocurrent channel change
    def on_sm_channel_change(self, event):
        self.parent.sm_channel = int(self.pc_channel_param.GetValue())
        self.parent.gpib_manager.set_bias_f()

    # Binding for parameter analyzer bias voltage change
    def on_bias_change(self, event):
        try:
            self.parent.pd_bias = float(self.pd_bias_voltage_param.GetValue())
        except:
            self.parent.pd_bias = 0.0
        self.parent.gpib_manager.set_bias_f()

    # Binding for parameter analyzer bias voltage change
    def on_bias_2_change(self, event):
        try:
            self.parent.pd_bias_2 = float(self.gate_bias_voltage_param.GetValue())
        except:
            self.parent.pd_bias_2 = 0.0
        self.parent.gpib_manager.set_bias_2_f()

    # Binding for electrical attenuation change
    def on_el_att_change(self, event):
        try:
            self.parent.el_att = float(self.el_attenuation_param.GetValue())
        except:
            self.parent.el_att = 0.0
        self.parent.gpib_manager.set_electrical_att()

    # Binding for measurement parameter change
    def on_measurement_param_change(self, event):
        if event.GetId() == self.ID_START_MEAS_WAVELENGTH_PARAM:
            self.parent.start_meas_wl = float(self.start_wl_param.GetValue())
            self.sb.SetStatusText('Setting the Start Wavelength for Transmission Scan')
        elif event.GetId() == self.ID_STOP_MEAS_WAVELENGTH_PARAM:
            self.parent.stop_meas_wl = float(self.stop_wl_param.GetValue())
            self.sb.SetStatusText('Setting the Stop Wavelength for Transmission Scan')
        elif event.GetId() == self.ID_NUM_MEAS_WAVELENGTH_PARAM:
            self.parent.num_meas_wl = int(self.num_wl_param.GetValue())
            self.sb.SetStatusText('Setting Number of Wavelengths for Transmission Scan')

        elif event.GetId() == self.ID_START_MEAS_POWER_PARAM:
            self.parent.start_meas_power = float(self.start_power_param.GetValue())
            self.sb.SetStatusText('Setting the Start Power for Transmission Scan')
        elif event.GetId() == self.ID_STOP_MEAS_POWER_PARAM:
            self.parent.stop_meas_power = float(self.stop_power_param.GetValue())
            self.sb.SetStatusText('Setting the Stop Power for Transmission Scan')
        elif event.GetId() == self.ID_NUM_MEAS_POWER_PARAM:
            self.parent.num_meas_power = int(self.num_power_param.GetValue())
            self.sb.SetStatusText('Setting Number of Powers for Transmission Scan')

        elif event.GetId() == self.ID_START_MEAS_V_PARAM:
            self.parent.start_meas_v = float(self.start_v_param.GetValue())
            self.sb.SetStatusText('Setting the Start V for Transmission Scan')
        elif event.GetId() == self.ID_STOP_MEAS_V_PARAM:
            self.parent.stop_meas_v = float(self.stop_v_param.GetValue())
            self.sb.SetStatusText('Setting the Stop V for Transmission Scan')
        elif event.GetId() == self.ID_NUM_MEAS_V_PARAM:
            self.parent.num_meas_v = int(self.num_v_param.GetValue())
            self.sb.SetStatusText('Setting Number of V for Transmission Scan')
        elif event.GetId() == self.ID_NUM_GATE_V_PARAM:
            self.parent.num_gate_v = int(self.num_gate_v_param.GetValue())
            self.sb.SetStatusText('Setting Number of gate V for Transmission Scan')
        elif event.GetId() == self.ID_STOP_GATE_V_PARAM:
            self.parent.stop_gate_v = float(self.stop_gate_v_param.GetValue())
            self.sb.SetStatusText('Setting the stop gate V for Transmission Scan')
        elif event.GetId() == self.ID_START_GATE_V_PARAM:
            self.parent.start_gate_v = float(self.start_gate_v_param.GetValue())
            self.sb.SetStatusText('Setting the start gate V for Transmission Scan')

        else:
            print('ERROR: Uncaught ID in on_measurement_param_change')

    def on_about(self, e):
        d = wx.MessageDialog(self, " A GUI front-end for the PhotonMover test setup \n"
                                   " to enable optical testing of our photonic and electronic devices \n"
                                   "\n By Marc de Cea, 2019", "About PhotonMover", wx.OK)
        d.ShowModal()
        d.Destroy()

    def on_save(self, e):
        pickle_file_object = open(self.parent.pickle_file, 'w')
        pickle.dump(self.parent.data, pickle_file_object)
        pickle_file_object.close()

    def on_load(self, e):
        self.parent.Load_default_data()

    def on_exit(self, e):
        self.parent.done = 1
        if self.parent.gpib_manager.running == 0:
            self.Destroy()
        else:
            self.parent.gpib_manager.close()

    def on_tx(self, e):

        power_range = None

        # We ask different things depending on the acquisition method
        if self.parent.gpib_manager.sweep_daq_acq:

            power_options = [-70.0, -60.0, -50.0, -40.0, -30.0, -20.0, -10.0, 0.0, 10.0]

            current_power = float(self.meas_rec_power_param.GetValue())
            current_power_dBm = 10*np.log10(current_power*1e3)
            mes = 'Choose the power range for the power meter measuring the tx. The current power is %.4f uW (%.4f dBm)' \
                  % (current_power*1e6, current_power_dBm)

            dialog1 = wx.MultiChoiceDialog(None, message= mes,
                                           caption='Range for power meter',
                                           choices=['-70 dBm', '-60dBm', '-50dBm', '-40dBm', '-30dBm', '-20dBm',
                                                    '-10dBm', '0 dBm', '10 dBm'])
            dialog1.ShowModal()
            index = dialog1.GetSelections()
            power_range = power_options[index[0]]

        dialog2 = wx.FileDialog(None, message='Enter only the measurement reference in the correct directory',
                                defaultDir=self.parent.data_folder, style=wx.FD_SAVE)

        if dialog2.ShowModal() == wx.ID_OK:
            print('Starting Transmission vs Wavelength Measurement Routine')
            self.parent.gpib_manager.tx_scan_f(dialog2.GetPath(), power_range)
        else:
            print('Nothing was selected.')

        dialog2.Destroy()

    def on_tx_vs_time(self, e):

        power_range = None

        # We ask different things depending on the acquisition method
        if self.parent.gpib_manager.sweep_daq_acq:
            power_options = [-70, -60, -50, -40, -30, -20, -10, 0, 10]

            current_power = float(self.meas_rec_power_param.GetValue())
            current_power_dBm = 10 * np.log10(current_power * 1e3)
            mes = 'Choose the power range for the power meter measuring the tx. The current power is %.4f uW (%.4f dBm)' \
                  % (current_power * 1e3, current_power_dBm)

            dialog1 = wx.MultiChoiceDialog(None, message=mes,
                                           caption='Range for power meter', n=9,
                                           choices=['-70 dBm', '-60dBm', '-50dBm', '-40dBm', '-30dBm', '-20dBm',
                                                    '-10dBm', '0 dBm', '10 dBm'])
            dialog1.ShowModal()
            index = dialog1.GetSelections()
            power_range = power_options[index[0]]

        dialog = wx.FileDialog(None, message='Enter only the measurement reference in the correct directory',
                               defaultDir=self.parent.data_folder, style=wx.FD_SAVE)

        if dialog.ShowModal() == wx.ID_OK:
            print('Starting Transmission vs Time Routine')
            self.parent.gpib_manager.tx_time_scan_f(dialog.GetPath(), power_range)
        else:
            print('Nothing was selected.')

        dialog.Destroy()

    def on_tx_vs_V(self, e):

        power_range = None

        # We ask different things depending on the acquisition method
        if self.parent.gpib_manager.sweep_daq_acq:
            power_options = [-70, -60, -50, -40, -30, -20, -10, 0, 10]

            current_power = float(self.meas_rec_power_param.GetValue())
            current_power_dBm = 10 * np.log10(current_power * 1e3)
            mes = 'Choose the power range for the power meter measuring the tx. The current power is %.4f uW (%.4f dBm)' \
                  % (current_power * 1e3, current_power_dBm)

            dialog1 = wx.MultiChoiceDialog(None, message=mes,
                                           caption='Range for power meter', n=9,
                                           choices=['-70 dBm', '-60dBm', '-50dBm', '-40dBm', '-30dBm', '-20dBm',
                                                    '-10dBm', '0 dBm', '10 dBm'])
            dialog1.ShowModal()
            index = dialog1.GetSelections()
            power_range = power_options[index[0]]

        dialog = wx.FileDialog(None, message='Enter only the measurement reference in the correct directory',
                               defaultDir=self.parent.data_folder, style=wx.FD_SAVE)

        if dialog.ShowModal() == wx.ID_OK:
            print('Starting Transmission vs Wavelength vs Voltage Routine')
            self.parent.gpib_manager.tx_bias_scan_f(dialog.GetPath(), power_range)
        else:
            print('Nothing was selected.')

        dialog.Destroy()

    def on_tx_vs_P(self, e):

        power_range = None

        # We ask different things depending on the acquisition method
        if self.parent.gpib_manager.sweep_daq_acq:
            power_options = [-70, -60, -50, -40, -30, -20, -10, 0, 10]

            current_power = float(self.meas_rec_power_param.GetValue())
            current_power_dBm = 10 * np.log10(current_power * 1e3)
            mes = 'Choose the power range for the power meter measuring the tx. The current power is %.4f uW (%.4f dBm)' \
                  % (current_power * 1e3, current_power_dBm)

            dialog1 = wx.MultiChoiceDialog(None, message=mes,
                                           caption='Range for power meter', n=9,
                                           choices=['-70 dBm', '-60dBm', '-50dBm', '-40dBm', '-30dBm', '-20dBm',
                                                    '-10dBm', '0 dBm', '10 dBm'])
            dialog1.ShowModal()
            index = dialog1.GetSelections()
            power_range = power_options[index[0]]

        dialog = wx.FileDialog(None, message='Enter only the measurement reference in the correct directory',
                               defaultDir=self.parent.data_folder, style=wx.FD_SAVE)

        if dialog.ShowModal() == wx.ID_OK:
            print('Starting Transmission vs Wavelength vs Power Routine')
            self.parent.gpib_manager.tx_power_scan_f(dialog.GetPath(), power_range)
        else:
            print('Nothing was selected.')

        dialog.Destroy()

    def on_pv_mod_outp(self, e):

        # Ask for the desired current
        mes = 'Choose the desired drain-source current (in uA)'

        dialog1 = wx.TextEntryDialog(None, message=mes,
                                       caption='Ids (uA)', value='1')
        dialog1.ShowModal()
        ds_current = float(dialog1.GetValue())*1e-6

        dialog = wx.FileDialog(None, message='Enter only the measurement reference in the correct directory',
                               defaultDir=self.parent.data_folder, style=wx.FD_SAVE)

        if dialog.ShowModal() == wx.ID_OK:
            print('Starting PV mod output characteristics for the specified current')
            self.parent.gpib_manager.meas_pv_mod_outp(ds_current, dialog.GetPath())
        else:
            print('Nothing was selected.')

        dialog.Destroy()

    def on_BW(self, e):

        dialog = wx.FileDialog(None, message='Enter only the measurement reference in the correct directory',
                               defaultDir=self.parent.data_folder, style=wx.FD_SAVE)

        if dialog.ShowModal() == wx.ID_OK:
            print('Starting acquisition from VNA')
            self.parent.gpib_manager.get_VNA_trace(dialog.GetPath())
        else:
            print('Nothing was selected.')

        dialog.Destroy()

    def on_BW_trigger(self, e):

        dialog = wx.FileDialog(None, message='Enter only the measurement reference in the correct directory',
                               defaultDir=self.parent.data_folder, style=wx.FD_SAVE)

        if dialog.ShowModal() == wx.ID_OK:
            print('Starting acquisition from VNA')
            self.parent.gpib_manager.trigger_VNA_trace(dialog.GetPath())
        else:
            print('Nothing was selected.')

        dialog.Destroy()

    def on_trans_output_curve(self, e):

        dialog = wx.FileDialog(None, message='Enter only the measurement reference in the correct directory',
                               defaultDir=self.parent.data_folder, style=wx.FD_SAVE)

        if dialog.ShowModal() == wx.ID_OK:
            print('Starting output curve acquisition')
            self.parent.gpib_manager.take_output_curve(dialog.GetPath())
        else:
            print('Nothing was selected.')

        dialog.Destroy()

    def on_BW_vs_V(self, e):

        dialog = wx.FileDialog(None, message='Enter only the measurement reference in the correct directory',
                               defaultDir=self.parent.data_folder, style=wx.FD_SAVE)

        if dialog.ShowModal() == wx.ID_OK:
            print('Starting bandwidth vs Wavelength vs Voltage Routine')
            self.parent.gpib_manager.VNA_bias_wl_scan(dialog.GetPath())
        else:
            print('Nothing was selected.')

        dialog.Destroy()

    def on_IV(self, e):

        dialog = wx.FileDialog(None, message='Enter only the measurement reference in the correct directory',
                               defaultDir=self.parent.data_folder, style=wx.FD_SAVE)
        if dialog.ShowModal() == wx.ID_OK:
            print('Starting IV Measurement Routine')
            self.parent.gpib_manager.take_IV_f(dialog.GetPath())
        else:
            print('Nothing was selected.')

        dialog.Destroy()

    def on_RLV(self, e):

        dialog = wx.FileDialog(None, message='Enter only the measurement reference in the correct directory',
                               defaultDir=self.parent.data_folder, style=wx.FD_SAVE)

        if dialog.ShowModal() == wx.ID_OK:
            print('Starting RLV Measurement Routine')
            self.parent.gpib_manager.take_RLV_f(dialog.GetPath())
        else:
            print('Nothing was selected.')

        dialog.Destroy()

    def on_RLP(self, e):

        dialog = wx.FileDialog(None, message='Enter only the measurement reference in the correct directory',
                               defaultDir=self.parent.data_folder, style=wx.FD_SAVE)

        if dialog.ShowModal() == wx.ID_OK:
            print('Starting RLP Measurement Routine')
            self.parent.gpib_manager.take_RLP_f(dialog.GetPath())
        else:
            print('Nothing was selected.')

        dialog.Destroy()

    def on_calibration(self, e):

        choices = ['Optical path calibration', 'No calibration']

        dialog = wx.SingleChoiceDialog(None, 'Ensure fibers are connected correctly (no DUT)!',
                                       'Photonmover Calibrations',
                                       choices)

        # The user pressed the "OK" button in the dialog
        if dialog.ShowModal() == wx.ID_OK:

            choice_result = dialog.GetSelection()
            if choice_result == 0:
                print('Starting Calibration Routine')
                self.parent.gpib_manager.calibration_f()
            elif choice_result == 1:
                print('Starting No Calibration Routine')
                self.parent.gpib_manager.no_calibration_f()

        # The user exited the dialog without pressing the "OK" button
        else:

            print('You did not select anything. No calibration will be performed')

        dialog.Destroy()

    def on_done(self, event):
        # When a subthread finishes, it creates a done event. If the
        # program is ready to be closed, the program will quit.
        if self.parent.done == 1 and self.parent.gpib_manager.done == 1:
            self.Destroy()

    def on_measured_powers(self, event):
        # What to do when the optics update readings have changed
        self.through_loss_param.SetValue("%.3f" % event.through_loss)
        self.meas_power_param.SetValue("%.3e" % event.input_power)
        self.meas_wl_param.SetValue("%.5f" % event.wavelength)
        self.meas_rec_power_param.SetValue("%.3e" % event.rec_power)
        self.tap_power_param.SetValue("%.3e" % event.tap_power)
        self.meas_photocurrent_param.SetValue("%.3e" % event.photocurrent)
        self.meas_gate_current_param.SetValue("%.3e" % event.photocurrent_2)
        self.responsivity_param.SetValue("%.3f" % event.responsivity)
        self.qe_param.SetValue("%d%%" % event.qe)

############################################################################
############################################################################
######                                                                ######
######            Controller Application Class Definition             ######
######                                                                ######
############################################################################
############################################################################

# Every wxWidgets application must have a class derived from wx.App
class ControllerApp(wx.App):

    # wxWindows calls this method to initialize the application
    def OnInit(self):

        # Initialize to generic wavelength
        self.wavelength = 1550.00  # nm
        self.tf_wavelength = 1550.00 # nm

        # Initialize to generic power
        self.power = 1.00  # dBm

        # Power meter range to AUTO.
        self.pm_range = PM_AUTO_RANGE

        # Laser Source
        self.laser_source = source_list[0]

        # Responsivity Channel
        self.smu_channel = 1

        # Photodiode Bias Voltage
        self.pd_bias = 0.0

        # GS voltage for transistor
        self.pd_bias_2 = 0.0

        # Electrical attenuation
        self.el_att = 0.0  # dB

        # Calibration parameters default to having the optical path loss calibrated
        self.cal_set = "PATH_CALIBRATION"

        # Measurement Parameters
        self.data_folder = default_data_folder
        self.start_meas_wl = 1520.0
        self.stop_meas_wl = 1580.0
        self.num_meas_wl = 120

        self.start_meas_power = -10
        self.stop_meas_power = 0
        self.num_meas_power = 5

        self.start_meas_v = 0
        self.stop_meas_v = -3
        self.num_meas_v = 5

        self.start_gate_v = 0
        self.stop_gate_v = 1
        self.num_gate_v = 5

        # Program state indicator
        self.done = 0

        # Persistent calibration pickle
        self.optical_calibration = list()
        self.optical_calibration_file = path_loss_cal_file

        # Create an instance of our customized Frame class
        self.app_frame = ControllerFrame(self, -1, "PhotonMover Fiber Control Front-End")

        # Create an instance of the GPIB Manager Class
        self.gpib_manager = GPIBManager(self, self.app_frame.through_loss_param)

        # Show the application
        self.app_frame.Show(True)

        # Tell wxWindows that this is our main window
        self.SetTopWindow(self.app_frame)

        # Initialize our variables
        self.load_calibration()

        # Spawn the main loop to manage gpib connections and begin polling power sensors.
        self.gpib_manager.start()
        time.sleep(0.001)

        # Set the wavelength to the default wavelength
        self.gpib_manager.set_wavelength(self.wavelength)
        self.gpib_manager.set_tf_wavelength(self.tf_wavelength)

        # Return a success flag
        return True

    def load_default_data(self):
        # Load data from the pickle files
        pickle_file_object = open(self.optical_calibration_file, 'r')
        self.data = pickle.load(pickle_file_object)
        pickle_file_object.close()

        self.load_calibration()

    def load_calibration(self):

        if self.cal_set == "PATH_CALIBRATION":
            pickle_file_object = open(self.optical_calibration_file, 'rb')
            self.optical_calibration = pickle.load(pickle_file_object)
            pickle_file_object.close()

        elif self.cal_set == "NONE":
            self.optical_calibration = None

############################################################################
############################################################################
######                                                                ######
######                Code to run when main application               ######
######                                                                ######
############################################################################
############################################################################

if __name__ == "__main__":
    app = ControllerApp()  # Create an instance of the application class
    app.MainLoop()  # Tell it to start processing events


grid = [["1","1","0","0","0"],["1","1","0","0","0"],["0","0","1","0","0"],["0","0","0","1","1"]]
m = len(grid)
for i in grid:
    n = len(i)
visited = [[0]*(n) for _ in range(m)]
count = 0
def sol():
    global count
    for i in range(m):
        for j in range(n):
            if grid[i][j] == "1" and visited[i][j] == 0:
                bfs(i,j)
                count += 1
                return count

def bfs(i,j):
    x = [1,-1,0,0]
    y = [0,0,1,-1]
    q = [[i,j]]

    while q:
        s = q.pop(0)
        for i in x:
            for j in y:
                k = s[0] +i
                t = s[1] + j
                if (k >=0 and k <=m-1) and (t>=0 and t <=n-1) and grid[k][t]=="1":
                    if visited[k][t] == 0:
                        visited[k][t] ==1
                        q.append([k,t])

print(sol())
print(visited)


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

# In[1]:

## #importing libraries
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import re
get_ipython().run_line_magic('matplotlib', 'inline')
import seaborn as sns

# In[2]:

#loading and check data data
store_dataset=pd.read_csv('C:\\Users\\User\\Documents\\data_analysis_challenge_data.csv')

# In[3]:

print(type(store_dataset))

# In[4]:

store_dataset.info()

# In[5]:

print(store_dataset.shape)

# In[6]:

print(store_dataset.shape[0])

# In[7]:

print(store_dataset.shape[1])

# In[8]:

store_dataset.head()

# In[9]:

#Data overview
print(f"Rows     : {store_dataset.shape[0]}")
print(f"Columns  : {store_dataset.shape[1]}" ,)
print()

# In[10]:

# Print the column names
print(f"Features : {store_dataset.columns.tolist()}")
print()

# In[11]:

# Print the total number of null values in the data
print(f"Missing values :  {store_dataset.isnull().sum().values.sum()}")

# In[12]:

# For each column, print the number of unique values
print(f"Unique values :  {store_dataset.nunique()}")

# In[13]:

#Descriptive statistics for continuous variables
store_dataset.describe().T

# In[14]:

#correlation
corr = store_dataset.corr()
print(type(corr))
print(corr)

# In[15]:

store_new=store_dataset

# In[16]:

print(store_new)

# In[17]:

##  Sales by store – Calculate the total sales amount made by each store over the
##   entire three months and visualize with a plot of your choice.

store_new1=store_new[['STORECODE', 'VALUE']]

# In[18]:

store_new1=store_new1.groupby('STORECODE').sum('VALUE')

# In[19]:

print(store_new1)

# In[20]:

store_new1.plot.bar()

# In[21]:

## 2.	Sales by category – For each store, find the category of products that’s sold the most (qty) a
##  nd visualize with a plot of

store_new1=store_new[store_new["STORECODE"]=='N1']

# In[22]:

## For the first Store

store_new2=store_new1[['GRP', 'QTY']]

# In[23]:

store_new2=store_new2.groupby('GRP').sum('QTY')

# In[24]:

print(store_new2)

# In[25]:

store_new2.sort_values(by=['QTY'],inplace=True,ascending=False)
df=store_new2.head()

# In[26]:

df.plot.bar()

# In[27]:

## For store N2

store_N2=store_new[store_new["STORECODE"]=='N2']
store_N2=store_N2[['GRP', 'QTY']]
store_N2=store_N2.groupby('GRP').sum('QTY')
print(store_N2)
store_N2.sort_values(by=['QTY'],inplace=True,ascending=False)
print(store_N2.head())
store_N2.sort_values(by=['QTY'],inplace=True,ascending=False)
df_N2=store_N2.head()
df_N2.plot.bar()

# In[28]:

## For store N3

store_N3=store_new[store_new["STORECODE"]=='N3']
store_N3=store_N3[['GRP', 'QTY']]
store_N3=store_N3.groupby('GRP').sum('QTY')
print(store_N3)
store_N3.sort_values(by=['QTY'],inplace=True,ascending=False)
print(store_N3.head())
store_N3.sort_values(by=['QTY'],inplace=True,ascending=False)
df_N3=store_N3.head()
df_N3.plot.bar()

# In[29]:

## For store N4

store_N4=store_new[store_new["STORECODE"]=='N4']
store_N4=store_N4[['GRP', 'QTY']]
store_N4=store_N4.groupby('GRP').sum('QTY')
print(store_N4)
store_N4.sort_values(by=['QTY'],inplace=True,ascending=False)
print(store_N4.head())
store_N4.sort_values(by=['QTY'],inplace=True,ascending=False)
df_N4=store_N4.head()
df_N4.plot.bar()

# In[30]:

## For Store N5

store_N5=store_new[store_new["STORECODE"]=='N5']
store_N5=store_N5[['GRP', 'QTY']]
store_N5=store_N5.groupby('GRP').sum('QTY')
print(store_N5)
store_N5.sort_values(by=['QTY'],inplace=True,ascending=False)
print(store_N5.head())
store_N5.sort_values(by=['QTY'],inplace=True,ascending=False)
df_N5=store_N5.head()
df_N5.plot.bar()

# In[31]:

## For Store N6

store_N6=store_new[store_new["STORECODE"]=='N6']
store_N6=store_N6[['GRP', 'QTY']]
store_N6=store_N6.groupby('GRP').sum('QTY')
print(store_N6)
store_N6.sort_values(by=['QTY'],inplace=True,ascending=False)
print(store_N6.head())
store_N6.sort_values(by=['QTY'],inplace=True,ascending=False)
df_N6=store_N6.head()
df_N6.plot.bar()

# In[32]:

## For Store N7

store_N7=store_new[store_new["STORECODE"]=='N7']
store_N7=store_N7[['GRP', 'QTY']]
store_N7=store_N7.groupby('GRP').sum('QTY')
print(store_N7)
store_N7.sort_values(by=['QTY'],inplace=True,ascending=False)
print(store_N7.head())
store_N7.sort_values(by=['QTY'],inplace=True,ascending=False)
df_N7=store_N7.head()
df_N7.plot.bar()

# In[33]:

## For Store N8
store_N8=store_new[store_new["STORECODE"]=='N8']
store_N8=store_N8[['GRP', 'QTY']]
store_N8=store_N8.groupby('GRP').sum('QTY')
print(store_N8)
store_N8.sort_values(by=['QTY'],inplace=True,ascending=False)
print(store_N8.head())
store_N8.sort_values(by=['QTY'],inplace=True,ascending=False)
df_N8=store_N8.head()
df_N8.plot.bar()

# In[34]:

## For Store N9

store_N9=store_new[store_new["STORECODE"]=='N9']
store_N9=store_N9[['GRP', 'QTY']]
store_N9=store_N9.groupby('GRP').sum('QTY')
print(store_N9)
store_N9.sort_values(by=['QTY'],inplace=True,ascending=False)
print(store_N9.head())
store_N9.sort_values(by=['QTY'],inplace=True,ascending=False)
df_N9=store_N9.head()
df_N9.plot.bar()

# In[35]:

## For Store N10

store_N10=store_new[store_new["STORECODE"]=='N10']
store_N10=store_N10[['GRP', 'QTY']]
store_N10=store_N10.groupby('GRP').sum('QTY')
print(store_N10)
store_N10.sort_values(by=['QTY'],inplace=True,ascending=False)
print(store_N10.head())
store_N10.sort_values(by=['QTY'],inplace=True,ascending=False)
df_N10=store_N10.head()
df_N10.plot.bar()

# In[36]:

## 3.	Sales by day – For each store, find the day across all three months where the total sales amount is the highest. In other words, find the best day for each store in
## terms of sales amount and visualize with a plot of your choice.

store_N1_day=store_new[store_new["STORECODE"]=='N1']
store_N1_day=store_N1_day[['DAY', 'VALUE']]
store_N1_day=store_N1_day.groupby('DAY').sum('VALUE')
print(store_N1_day)
store_N1_day.sort_values(by=['VALUE'],inplace=True,ascending=False)
print(store_N1_day.head())
store_N1_day.sort_values(by=['VALUE'],inplace=True,ascending=False)
df_N1_day=store_N1_day.head()
df_N1_day.plot.bar()

# In[37]:

## For every Store

store_N2_day=store_new[store_new["STORECODE"]=='N2']
store_N2_day=store_N2_day[['DAY', 'VALUE']]
store_N2_day=store_N2_day.groupby('DAY').sum('VALUE')
print(store_N2_day)
store_N2_day.sort_values(by=['VALUE'],inplace=True,ascending=False)
print(store_N2_day.head())
store_N2_day.sort_values(by=['VALUE'],inplace=True,ascending=False)
df_N2_day=store_N2_day.head()
df_N2_day.plot.bar()

store_N3_day=store_new[store_new["STORECODE"]=='N3']
store_N3_day=store_N3_day[['DAY', 'VALUE']]
store_N3_day=store_N3_day.groupby('DAY').sum('VALUE')
print(store_N3_day)
store_N3_day.sort_values(by=['VALUE'],inplace=True,ascending=False)
print(store_N3_day.head())
store_N3_day.sort_values(by=['VALUE'],inplace=True,ascending=False)
df_N3_day=store_N3_day.head()
df_N3_day.plot.bar()

store_N4_day=store_new[store_new["STORECODE"]=='N4']
store_N4_day=store_N4_day[['DAY', 'VALUE']]
store_N4_day=store_N4_day.groupby('DAY').sum('VALUE')
print(store_N4_day)
store_N4_day.sort_values(by=['VALUE'],inplace=True,ascending=False)
print(store_N4_day.head())
store_N4_day.sort_values(by=['VALUE'],inplace=True,ascending=False)
df_N4_day=store_N4_day.head()
df_N4_day.plot.bar()

store_N5_day=store_new[store_new["STORECODE"]=='N5']
store_N5_day=store_N5_day[['DAY', 'VALUE']]
store_N5_day=store_N5_day.groupby('DAY').sum('VALUE')
print(store_N5_day)
store_N5_day.sort_values(by=['VALUE'],inplace=True,ascending=False)
print(store_N5_day.head())
store_N5_day.sort_values(by=['VALUE'],inplace=True,ascending=False)
df_N5_day=store_N5_day.head()
df_N5_day.plot.bar()

store_N6_day=store_new[store_new["STORECODE"]=='N6']
store_N6_day=store_N6_day[['DAY', 'VALUE']]
store_N6_day=store_N6_day.groupby('DAY').sum('VALUE')
print(store_N6_day)
store_N6_day.sort_values(by=['VALUE'],inplace=True,ascending=False)
print(store_N6_day.head())
store_N6_day.sort_values(by=['VALUE'],inplace=True,ascending=False)
df_N6_day=store_N6_day.head()
df_N6_day.plot.bar()

store_N7_day=store_new[store_new["STORECODE"]=='N7']
store_N7_day=store_N7_day[['DAY', 'VALUE']]
store_N7_day=store_N7_day.groupby('DAY').sum('VALUE')
print(store_N7_day)
store_N7_day.sort_values(by=['VALUE'],inplace=True,ascending=False)
print(store_N7_day.head())
store_N7_day.sort_values(by=['VALUE'],inplace=True,ascending=False)
df_N7_day=store_N7_day.head()
df_N7_day.plot.bar()

store_N8_day=store_new[store_new["STORECODE"]=='N8']
store_N8_day=store_N8_day[['DAY', 'VALUE']]
store_N8_day=store_N8_day.groupby('DAY').sum('VALUE')
print(store_N8_day)
store_N8_day.sort_values(by=['VALUE'],inplace=True,ascending=False)
print(store_N8_day.head())
store_N8_day.sort_values(by=['VALUE'],inplace=True,ascending=False)
df_N8_day=store_N8_day.head()
df_N8_day.plot.bar()

store_N9_day=store_new[store_new["STORECODE"]=='N9']
store_N9_day=store_N9_day[['DAY', 'VALUE']]
store_N9_day=store_N9_day.groupby('DAY').sum('VALUE')
print(store_N9_day)
store_N9_day.sort_values(by=['VALUE'],inplace=True,ascending=False)
print(store_N9_day.head())
store_N9_day.sort_values(by=['VALUE'],inplace=True,ascending=False)
df_N9_day=store_N9_day.head()
df_N9_day.plot.bar()

store_N10_day=store_new[store_new["STORECODE"]=='N10']
store_N10_day=store_N10_day[['DAY', 'VALUE']]
store_N10_day=store_N10_day.groupby('DAY').sum('VALUE')
print(store_N10_day)
store_N10_day.sort_values(by=['VALUE'],inplace=True,ascending=False)
print(store_N10_day.head())
store_N10_day.sort_values(by=['VALUE'],inplace=True,ascending=False)
df_N10_day=store_N10_day.head()
df_N10_day.plot.bar()

# In[38]:

## Bonus:

## Sales by month – Calculate total sales amount across all stores per month to find the best
## month across the given three months and visualize with a plot of your choice.

store_month=store_new[['MONTH', 'VALUE']]
store_month=store_month.groupby('MONTH').sum('VALUE')
print(store_month)
store_month.plot.bar()

# In[ ]:


"""Helper classes for testing a tkinter GUI application.

Methods
-------
_widgets_by_name : Iterates recoursively through the application and
    compares their name to the searched string.
find_widget_by_name : Returns a widget with a specified name from the
    application.
"""

def _widgets_by_name(parent, name, widgets):
    """Traverses the widget tree leaves in search for the specified
    name.

    Parameters
    ----------
    parent
        The tkinter widget to be checked for children (continues to
        traverse) of their name.
    name : str
        The widget name to search for.
    widgets : list
        A reference to a list to store found widgets in.
    """
    if not parent.winfo_children():
        if name == parent.winfo_name():
            widgets.append(parent)
    else:
        for child in parent.winfo_children():
            _widgets_by_name(child, name, widgets)

def find_widget_by_name(parent, name):
    """Searches for tkinter widgets by their respective name. Does not
    search for named frames!

    Parameters
    ----------
    parent
        The tkinter widget from which to start the search from.
    name : str
        The name of the widget to search for.
    """
    widgets = []
    _widgets_by_name(parent, name, widgets)
    if len(widgets) == 0:
        raise ValueError(f"No widget named {name} found.")
    elif len(widgets) > 1:
        raise ValueError(f"Multiple widgets with name {name} found.")
    return widgets[0]