import os
import pprint
from osgeo import gdal
from os.path import join
import numpy as np
from datetime import datetime
import gc
import time

import matplotlib.pyplot as pyplot

def hist_match(source, template):
    """
    Adjust the pixel values of a grayscale image such that its histogram
    matches that of a target image

    Arguments:
    -----------
        source: flattened np.ndarray
            Image to transform; the histogram is computed over the flattened
            array
        template: np.ndarray
            Template image; can have different dimensions to source
    Returns:
    -----------
        matched: flattened np.ndarray
            The transformed output image
    """
    # oldshape = source.shape
    # source = source.ravel()
    # template = template.ravel()

    # get the set of unique pixel values and their corresponding indices and
    # counts
    s_values, bin_idx, s_counts = np.unique(
        source, return_inverse=True, return_counts=True
    )
    t_values, t_counts = np.unique(template, return_counts=True)

    # take the cumsum of the counts and normalize by the number of pixels to
    # get the empirical cumulative distribution functions for the source and
    # template images (maps pixel value --> quantile)
    s_quantiles = np.cumsum(s_counts).astype(np.float64)
    s_quantiles /= s_quantiles[-1]
    t_quantiles = np.cumsum(t_counts).astype(np.float64)
    t_quantiles /= t_quantiles[-1]

    # interpolate linearly to find the pixel values in the template image
    # that correspond most closely to the quantiles in the source image
    interp_t_values = np.interp(s_quantiles, t_quantiles, t_values)

    return interp_t_values[bin_idx]

if __name__ == "__main__":

    fullimg = "/home/tq/data_pool/zgq/estarfm/test/sub-0802.tif"  # full img
    gapimg = "/home/tq/data_pool/zgq/estarfm/test/sub-0818.tif"  # img with gap

    # read img
    ds = gdal.Open(fullimg)
    geo_trans = ds.GetGeoTransform()
    w = ds.RasterXSize
    h = ds.RasterYSize
    img_shape = [h, w]
    proj = ds.GetProjection()
    arrfull = ds.GetRasterBand(4).ReadAsArray()
    ds = None

    ds = gdal.Open(gapimg)
    geo_trans = ds.GetGeoTransform()
    w = ds.RasterXSize
    h = ds.RasterYSize
    img_shape = [h, w]
    proj = ds.GetProjection()
    arrgap = ds.GetRasterBand(4).ReadAsArray()
    ds = None

    # get mask of gap
    mask = arrgap.copy()
    mask[mask > 0] = 1
    mask[mask <= 0] = 0  # zero is cloud
    maxv = np.max(arrfull) / 5  # np.percentile(arrfull, 0.7) / 5  #

    fill = arrfull.copy()
    idx0 = np.where(mask == 0)
    idx1 = np.where(mask == 1)
    fill[idx1] = 0

    # valid pixels = vp
    vp_fill = fill[idx0]
    vp_gap = arrgap[idx1]

    vp_fill = hist_match(vp_fill, vp_gap)
    fill[idx0] = vp_fill

    arrgapnew = arrgap + fill

    pyplot.figure(num="test", figsize=(8, 8))  # image show part
    p1 = pyplot.subplot(131)
    p2 = pyplot.subplot(132)
    p3 = pyplot.subplot(133)
    p1.imshow(arrfull, cmap=pyplot.cm.rainbow, vmin=0., vmax=maxv)
    p2.imshow(arrgap, cmap=pyplot.cm.rainbow, vmin=0., vmax=maxv)
    p3.imshow(arrgapnew, cmap=pyplot.cm.rainbow, vmin=0., vmax=maxv)
    pyplot.show()  # image show part end

    print("fin")


from keras.models import Model
from keras.layers import Input
from entertrain import backend as E

class ExtremeLearningMachine:

    def __init__(self,
                 feature_extractor: Model,
                 predictor: Model,
                 solver="pseudoinverse"):
        self.feature_extractor = feature_extractor
        self.predictor = predictor
        self.full_model = None  # type: Model
        self._predictor_bias = self.predictor.get_weights()[-1]
        self.solver = {"pseudoinverse": self._solve_pseudoinverse,
                       "covariance": self._solve_covariance}[solver]
        self._build_full_model()

    def _build_full_model(self):
        model_input = Input(batch_shape=self.feature_extractor.input_shape)
        model_output = self.predictor(self.feature_extractor(model_input))
        self.full_model = Model(inputs=model_input, outputs=model_output)

    @classmethod
    def from_single_model(cls, model: Model, solver="pseudoinverse"):
        fe_input = Input(batch_shape=model.input_shape)
        fe_out = model.layers[-2](fe_input)
        feature_extractor = Model(inputs=fe_input, outputs=fe_out)
        pr_in = Input(batch_shape=model.layers[-2].output_shape)
        pr_out = model(pr_in)
        predictor = Model(inputs=pr_in, outputs=pr_out)
        return cls(feature_extractor, predictor, solver)

    def _solve_pseudoinverse(self, Z, Y):
        W = E.dot(E.pinv(Z), Y)
        self.predictor.set_weights([W, self._predictor_bias])

    def _solve_covariance(self, Z, Y):
        A = E.cov(Z.T)
        B = E.cov(Z.T, Y.T)
        W = E.dot(E.inv(A), B)
        self.predictor.set_weights([W, self._predictor_bias])

    def train_on_batch(self, x, y):
        z = self.feature_extractor.predict(x)
        self.solver(z, y)

    def predict(self, x):
        z = self.feature_extractor.predict(x)
        return self.predictor.predict(z)

    def evaluate(self, x=None, y=None, batch_size=None, verbose=1,
                 sample_weight=None, steps=None, loss=None, **kwargs):
        if not self.full_model._is_compiled:
            if loss is None:
                raise RuntimeError("Model needs to be built for evaluation, please supply a loss!")
            self.full_model.compile(optimizer="sgd", loss=loss, **kwargs)
        return self.full_model.evaluate(x, y, batch_size, verbose, sample_weight, steps)


from musixmatch import Musixmatch

class MusiXMatchAPI:

    def __init__(self, key):
        self.MM = Musixmatch(key)
        self.track_not_found = 0
        self.total_count = 0
        self.error_codes = []

    def get_song_data(self, artist_name: str, track_title: str) -> dict:
        """
        Gets song data from musixmatch api. Using the free API means
        access to only 30% of the lyrics body, so that's a no go. We
        can still use the API to augment our datasets with the
        album name, release date, and genres.

        :param artist_name: name of track artist (str)
        :param track_title: name of track title (str)
        :return: dict of form {
            "Album_Name": str,
            "Release_date": "yyyy-MM-dd,
            "Genres": list of strs
        }
        """
        self.total_count += 1
        result = self.MM.matcher_track_get(q_artist=artist_name,
                                           q_track=track_title)

        result = result["message"]
        if result["header"]["status_code"] != 200 or \
                        result["body"]["track"]["has_lyrics"] != 1:
            self.track_not_found += 1
            self.error_codes.append(result["header"]["status_code"])
            return {}

        result = result["body"]["track"]
        return {
            "Album_Name": result["album_name"],
            "Release_Date": result["first_release_date"].split("T")[0],
            "Genres": {
                "Names": [x["music_genre"]["music_genre_name"] for x
                       in result["primary_genres"]["music_genre_list"]],
                "Source": "MusixMatch"
            }
        }

    def get_usage_report(self) -> dict:
        """
        Returns usage report of the spotify scraperr

        :return: dict of form {
            "Musixmatch_Usage_Reports": {
                "Missed_Searches": int,
                "Total_Attempts": int
            }
        }
        """
        usage = {
            "Musixmatch_Usage_Reports": {
                "Missed_Searches": self.track_not_found,
                "Total_Attempts": self.total_count
            }
        }
        return usage

    def clear_usage_stats(self):
        self.track_not_found = 0
        self.total_count = 0

if __name__ == "__main__":

    import json
    import time
    from scraping import keys
    k = keys.Keys()

    MM = MusiXMatchAPI(k.musixmatch_key)

    songs = {
        "AC/DC": "Thunderstruck",
        "Twenty One Pilots": "Bandito",
        "BROCKHAMPTON": "BERLIN",
        "Eric Church": "The Snake",
        "D'Angelo": "Sugah Daddy",
        "Bad Artist Name": "Bad Song Name"
    }

    for key, val in songs.items():
        print("Artist:", key, " : ", "Track:", val)
        info = MM.get_song_data(key, val,)
        json_dump = json.dumps(info, indent=4)
        print(json_dump)
        print()
        time.sleep(2)

    report = MM.get_usage_report()
    json_dump = json.dumps(report, indent=4)
    print(json_dump)
    print()


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


from __future__ import annotations
import numpy as np
import matplotlib.pyplot as plt
from scipy.optimize import curve_fit
from abc import ABC, abstractmethod
import gc

from warnings import warn

warn('The module GaussFit is deprecated.', DeprecationWarning, stacklevel=2)

class BaseGaussFuncs:
    """Abstract class listing methods that a base class for GaussFit must provide.
    """
    @staticmethod
    @abstractmethod
    def full_field(x: np.ndarray, *params) -> np.ndarray:
        """Calculate full fit result (background + gaussians)

        Args:
            x (np.ndarray): X data.
            params (list): Background and Gaussian parameters.

        Returns:
            np.ndarray: Y data.
        """
        pass

    @staticmethod
    @abstractmethod
    def background(x: np.ndarray, *params) -> np.ndarray:
        """Calculate background only.

        Args:
            x (np.ndarray): X data.
            params (list): Background and Gaussian parameters.

        Returns:
            np.ndarray: Y data.
        """
        pass

    @staticmethod
    @abstractmethod
    def gaussian_w_bg(x: np.ndarray, id: int, *params) -> np.ndarray:
        """Calculate fit result (background + single gaussian)

        Args:
            x (np.ndarray): X data.
            id (int): ID of the Gaussian [[0, N), where N is the total number of Gaussians.]
            params (list): Background and Gaussian parameters.

        Returns:
            np.ndarray: Y data.
        """
        pass

    @staticmethod
    @abstractmethod
    def gaussian_wo_bg(x: np.ndarray, id: int, *params) -> np.ndarray:
        """Calculate single Gaussian (without background)

        Args:
            x (np.ndarray): X data.
            id (int): ID of the Gaussian [[0, N), where N is the total number of Gaussians.]
            params (list): Background and Gaussian parameters.

        Returns:
            np.ndarray: Y data.
        """
        pass

    @staticmethod
    def gaussian(x: np.ndarray, H: float, A: float, x0: float, sigma: float) -> np.ndarray:
        """Evaluate a Gaussian.

        Args:
            x (np.ndarray): X data.
            H (float): Y Offset (constant).
            A (float): Amplitude.
            x0 (float): Mean.
            sigma (float): Width (2*sigma^2)

        Returns:
            np.ndarray: Evaluated Gaussian.
        """
        return H + A * np.exp(-((x - x0)/sigma)**2)

    @staticmethod
    @abstractmethod
    def n_gaussians(*params) -> int:
        """Get number of Gaussians inferred from the parameters.

        Returns:
            int: Number of Gaussians.
        """
        pass

    @staticmethod
    @abstractmethod
    def n_background_params() -> int:
        """Get number of background parameters.

        Returns:
            int: Number of background parameters.
        """
        pass

class GaussFuncsBasic(BaseGaussFuncs):
    """This class accepts parameters of length 5 + 3 * N, where
    N is the number of Gaussians to be fit. first 5 parameters are
    a0 to a4. The background is evaluated as:
    background = a0 + a1*x + a2*x^2 + a3*x^3 + a4*x^4.
    For each Gaussian, the parameters to be supplied are [C, A, W].
    The Gaussians are evaluated as A*exp(-((x-C)/W)^2).

    WARNING: Very basic fit function. With large (or small) values of x,
    the polynomial evaluated can be very large. Use if x is around 0.
    GaussFuncs is the preferred class.
    """

    def __init__(self):
        warn('GaussFuncsBasic class is deprecated. Please use GenericFitFunc and GenericFitManager.', category=DeprecationWarning, stacklevel=2)
        pass

    @staticmethod
    def full_field(x: np.ndarray, *params) -> np.ndarray:
        """Evaluate background + gaussians from the guess/fit parameters.

        Args:
            x (np.ndarray): X data.
            params (list): Background + Gaussian parameters.

        Returns:
            np.ndarray: Y data.
        """
        if len(params) == 1:
            params = params[0]
        y = GaussFuncsBasic.background(x, params)
        for i in range(5, len(params), 3):
            ctr = params[i]
            amp = params[i + 1]
            wid = params[i + 2]
            dy = (amp * np.exp(-((x - ctr)/wid)**2))
            y += dy
        return y

    @staticmethod
    def background(x: np.ndarray, *params) -> np.ndarray:
        """Evaluate background from the guess/fit parameters.

        Args:
            x (np.ndarray): X data.
            params (list): Background + Gaussian parameters.

        Returns:
            np.ndarray: Y data.
        """
        y = np.zeros_like(x)
        if len(params) == 1:
            params = params[0]
        for i in range(5):
            y += float(params[i]) * (x ** i)
        return y

    @staticmethod
    def gaussian_w_bg(x: np.ndarray, id: int, *params) -> np.ndarray:
        """Evaluate a gaussian, with the background, from the guess/fit parameters.

        Args:
            x (np.ndarray): X data.
            id (int): ID of the Gaussian. Must be between [0, (len(params) - 5) // 3].
            params (list): Background + Gaussian parameters.

        Returns:
            np.ndarray: Y data.
        """
        if len(params) == 1:
            params = params[0]
        y = GaussFuncsBasic.background(x, params)
        base = 5 + (3 * id)
        ctr = params[base]
        amp = params[base + 1]
        wid = params[base + 2]
        y += (amp * np.exp(-((x - ctr)/wid)**2))
        return y

    @staticmethod
    def gaussian_wo_bg(x: np.ndarray, id: int, *params) -> np.ndarray:
        """Evaluate a gaussian, without the background, from the guess/fit parameters.

        Args:
            x (np.ndarray): X data.
            id (int): ID of the Gaussian. Must be between [0, (len(params) - 5) // 3].
            params (list): Background + Gaussian parameters.

        Returns:
            np.ndarray: Y data.
        """
        y = np.zeros_like(x)
        if len(params) == 1:
            params = params[0]
        base = 5 + (3 * id)
        ctr = params[base]
        amp = params[base + 1]
        wid = params[base + 2]
        y += (amp * np.exp(-((x - ctr)/wid)**2))
        return y

    @staticmethod
    def n_gaussians(*params):
        """Get number of Gaussians inferred from the parameters.

        Returns:
            int: Number of Gaussians.
        """
        if len(params) == 1:
            params = params[0]
        return (len(params) - 5) // 3

    @staticmethod
    def n_background_params() -> int:
        """Get number of background parameters.

        Returns:
            int: Number of background parameters.
        """
        return 5

class GaussFuncs(BaseGaussFuncs):
    """This class accepts parameters of length 6 + 3 * N, where
    N is the number of Gaussians to be fit. The first parameter
    is x0, and the next 5 parameters are a0 to a4.
    The background is evaluated as:
    background = a0 + a1(x-x0) + a2(x-x0)^2 + a3(x-x0)^3 + a4(x-x0)^4.
    For each Gaussian, the parameters to be supplied are [C, A, W].
    The Gaussians are evaluated as A*exp(-((x-C)/W)^2).

    This is the preferred function suite used to fit Gaussians.
    """
    @staticmethod
    def full_field(x: np.ndarray, *params) -> np.ndarray:
        """Evaluate background + gaussians from the guess/fit parameters.

        Args:
            x (np.ndarray): X data.
            params (list): Background + Gaussian parameters.

        Returns:
            np.ndarray: Y data.
        """
        if len(params) == 1:
            params = params[0]
        y = GaussFuncs.background(x, params)
        for i in range(6, len(params), 3):
            ctr = params[i]
            amp = params[i + 1]
            wid = params[i + 2]
            dy = (amp * np.exp(-((x - ctr)/wid)**2))
            y += dy
        return y

    @staticmethod
    def background(x: np.ndarray, *params) -> np.ndarray:
        """Evaluate background from the guess/fit parameters.

        Args:
            x (np.ndarray): X data.
            params (list): Background + Gaussian parameters.

        Returns:
            np.ndarray: Y data.
        """
        y = np.zeros_like(x)
        if len(params) == 1:
            params = params[0]
        x0 = params[0]
        for i in range(5):
            y += float(params[i+1])*((x-x0)**i)
        return y

    @staticmethod
    def gaussian_w_bg(x: np.ndarray, id: int, *params) -> np.ndarray:
        """Evaluate a gaussian, with the background, from the guess/fit parameters.

        Args:
            x (np.ndarray): X data.
            id (int): ID of the Gaussian. Must be between [0, (len(params) - 5) // 3].
            params (list): Background + Gaussian parameters.

        Returns:
            np.ndarray: Y data.
        """
        if len(params) == 1:
            params = params[0]
        y = GaussFuncs.background(x, params)
        base = 6 + (3 * id)
        ctr = params[base]
        amp = params[base + 1]
        wid = params[base + 2]
        y += (amp * np.exp(-((x - ctr)/wid)**2))
        return y

    @staticmethod
    def gaussian_wo_bg(x: np.ndarray, id: int, *params) -> np.ndarray:
        """Evaluate a gaussian, without the background, from the guess/fit parameters.

        Args:
            x (np.ndarray): X data.
            id (int): ID of the Gaussian. Must be between [0, (len(params) - 5) // 3].
            params (list): Background + Gaussian parameters.

        Returns:
            np.ndarray: Y data.
        """
        y = np.zeros_like(x)
        if len(params) == 1:
            params = params[0]
        base = 6 + (3 * id)
        ctr = params[base]
        amp = params[base + 1]
        wid = params[base + 2]
        y += (amp * np.exp(-((x - ctr)/wid)**2))
        return y

    @staticmethod
    def n_gaussians(*params):
        """Get number of Gaussians inferred from the parameters.

        Returns:
            int: Number of Gaussians.
        """
        if len(params) == 1:
            params = params[0]
        return (len(params) - 6) // 3

    @staticmethod
    def n_background_params() -> int:
        """Get number of background parameters.

        Returns:
            int: Number of background parameters.
        """
        return 6

class GaussFuncsExtS(BaseGaussFuncs):
    """This class accepts parameters of length 9 + 3 * N, where
    N is the number of Gaussians to be fit. The first 4 parameters
    are the x1 to x4, and the next 5 parameters are a0 to a4.
    The background is evaluated as:
    background = a0 + a1(x-x1) + a2(x-x2)^2 + a3(x-x3)^3 + a4(x-x4)^4.
    For each Gaussian, the parameters to be supplied are [C, A, W].
    The Gaussians are evaluated as A*exp(-((x-C)/W)^2).

    WARNING: Use this class only in case of very stubborn fits.
    """
    @staticmethod
    def full_field(x: np.ndarray, *params) -> np.ndarray:
        """Evaluate background + gaussians from the guess/fit parameters.

        Args:
            x (np.ndarray): X data.
            params (list): Background + Gaussian parameters.

        Returns:
            np.ndarray: Y data.
        """
        if len(params) == 1:
            params = params[0]
        y = GaussFuncsExtS.background(x, params)
        for i in range(9, len(params), 3):
            ctr = params[i]
            amp = params[i + 1]
            wid = params[i + 2]
            dy = (amp * np.exp(-((x - ctr)/wid)**2))
            y += dy
        return y

    @staticmethod
    def background(x: np.ndarray, *params) -> np.ndarray:
        """Evaluate background from the guess/fit parameters.

        Args:
            x (np.ndarray): X data.
            params (list): Background + Gaussian parameters.

        Returns:
            np.ndarray: Y data.
        """
        y = np.zeros_like(x)
        if len(params) == 1:
            params = params[0]
        x1 = params[0]
        x2 = params[1]
        x3 = params[2]
        x4 = params[3]
        y += float(params[4])
        y += float(params[5])*(x-x1)
        y += float(params[6])*(x-x2)**2
        y += float(params[7])*(x-x3)**3
        y += float(params[8])*(x-x4)**4
        return y

    @staticmethod
    def gaussian_w_bg(x: np.ndarray, id: int, *params) -> np.ndarray:
        """Evaluate a gaussian, with the background, from the guess/fit parameters.

        Args:
            x (np.ndarray): X data.
            id (int): ID of the Gaussian. Must be between [0, (len(params) - 5) // 3].
            params (list): Background + Gaussian parameters.

        Returns:
            np.ndarray: Y data.
        """
        if len(params) == 1:
            params = params[0]
        y = GaussFuncsExtS.background(x, params)
        base = 9 + (3 * id)
        ctr = params[base]
        amp = params[base + 1]
        wid = params[base + 2]
        y += (amp * np.exp(-((x - ctr)/wid)**2))
        return y

    @staticmethod
    def gaussian_wo_bg(x: np.ndarray, id: int, *params) -> np.ndarray:
        """Evaluate a gaussian, without the background, from the guess/fit parameters.

        Args:
            x (np.ndarray): X data.
            id (int): ID of the Gaussian. Must be between [0, (len(params) - 5) // 3].
            params (list): Background + Gaussian parameters.

        Returns:
            np.ndarray: Y data.
        """
        y = np.zeros_like(x)
        if len(params) == 1:
            params = params[0]
        base = 9 + (3 * id)
        ctr = params[base]
        amp = params[base + 1]
        wid = params[base + 2]
        y += (amp * np.exp(-((x - ctr)/wid)**2))
        return y

    @staticmethod
    def n_gaussians(*params):
        """Get number of Gaussians inferred from the parameters.

        Returns:
            int: Number of Gaussians.
        """
        if len(params) == 1:
            params = params[0]
        return (len(params) - 9) // 3

    @staticmethod
    def n_background_params() -> int:
        """Get number of background parameters.

        Returns:
            int: Number of background parameters.
        """
        return 9

class GaussFit:
    """Fit N Gaussians with a polynomial background using the GaussFuncs* class.
    """

    def __init__(self, x: np.ndarray, y: np.ndarray, p0: tuple | list | np.ndarray, *, baseclass: BaseGaussFuncs = GaussFuncs(), plot: bool = True, figure_title: str = None, window_title: str = None, **kwargs):
        """Initialize a Gauss fit object.

        Args:
            x (np.ndarray): X data.
            y (np.ndarray): Y data.
            p0 (tuple | list | np.ndarray): Fit parameters initial guess. p0 must have the length
            compatible with the GaussFuncs* class.
            baseclass (GaussFuncs*(BaseGaussFuncs), optional): Function set to evaluate background and gaussians. Defaults to GaussFuncs().
            plot (bool, optional): Plot progress of the fit. Defaults to True.
            figure_title (str, optional): Progress figure title. Defaults to None.
            window_title (str, optional): Progress window title. Defaults to None.
            kwargs: Additional arguments passed to the internal plt.subplots() call.

        Raises:
            TypeError: Base function class is not of GaussFuncs* type.
        """
        warn('GaussFit class is deprecated. Please use GenericFitFunc and GenericFitManager.', category=DeprecationWarning, stacklevel=2)
        if not isinstance(baseclass, BaseGaussFuncs):
            raise TypeError('Base class must be of GaussFuncs* type.')
        self._baseclass = baseclass
        self._n_gaussians = self._baseclass.n_gaussians(p0)
        if (plot):
            fig, ax = plt.subplots(1, 1, **kwargs)
            self._fig = fig
            if figure_title is not None and len(figure_title) > 0:
                self._fig.suptitle(figure_title)
            if window_title is not None and len(window_title) > 0:
                self._fig.canvas.manager.set_window_title(window_title)
            self._ax = ax
            self._orig, = self._ax.plot(x, y, color='r')
            self._bck, = self._ax.plot(
                x, self._baseclass.background(x, p0), color='k')
            self._sig = []
            for i in range(self._n_gaussians):
                sig, = self._ax.plot(
                    x, self._baseclass.gaussian_w_bg(x, i, p0))
                self._sig.append(sig)
            self._ax.set_xlim(x.min(), x.max())
        self._plot = plot
        self._iteration = 0
        self._param = p0
        self._x = x
        self._y = y
        self._last_res = None
        self._plot_every = 0
        if plot:
            plt.ion()
            plt.show()

    @property
    def param(self) -> tuple:
        """Get the latest fit parameters.

        Returns:
            tuple: Fit parameters.
        """
        return tuple(self._param)

    def _fit_func(self, x, *params):
        y = self._baseclass.full_field(x, params)
        self._update(x, *params)
        self._param = params
        return y

    def _update(self, x, *params):
        if self._plot:
            if self._plot_every > 0 and (self._iteration % self._plot_every):
                pass
            else:
                bck = self._baseclass.background(x, params)
                self._bck.set_ydata(bck)
                for idx, sig in enumerate(self._sig):
                    gaus = self._baseclass.gaussian_wo_bg(x, idx, params)
                    gaus += bck
                    sig.set_ydata(gaus)
                data = self._baseclass.full_field(x, params)
                self._ax.set_ylim(np.min([data.min(), self._y.min()]), np.max(
                    [data.max(), self._y.max()]))
                res = np.sqrt(np.sum((self._y - data)**2))
                if self._last_res is None:
                    self._last_res = res
                dres = res - self._last_res
                self._last_res = res
                self._ax.set_title('Gaussians: %d, Iteration: %d, Residual: %.3e, Delta: %.3e' % (
                    self._n_gaussians, self._iteration, res, dres))
                self._fig.canvas.draw()
                self._fig.canvas.flush_events()
        self._iteration += 1

        if self._interval > 0 and self._plot:
            plt.pause(self._interval)

    def run(self, *, plot_every: int = 0, interval: float = 1/24, ioff: bool = True, close_after: bool = False, **kwargs) -> tuple:
        """Run the fit routine. Read documentation for scipy.optimize.curve_fit to see what additional named parameters can be passed through kwargs (x, y, p0 are internally passed).

        Args:
            plot_every (int, optional): Plot every Nth frame (0 to plot every frame, default.) Setting this
            to a positive value causes run() to ignore the value of interval.
            interval (float, optional): Interval between plot frame updates in seconds. Defaults to 1/24.
            Interval is used only when plot_every is non-positive.
            ioff (bool, optional): Execute plt.ioff() after run() to revert the system to non-interactive state. Defaults to True.
            close_after (bool, optional): Execute plt.close() after run() to close the active window. Defaults to False.
            kwargs: Named arguments passed to scipy.optimize.curve_fit. DO NOT pass f, xdata, ydata, p0.

        Returns:
            tuple: popt, pcov (and infodict, mesg, ier if full_output=True) from scipy.optimize.curve_fit.
        """
        if plot_every < 0:
            plot_every = 0
        self._plot_every = plot_every
        if plot_every > 0:
            self._interval = 0
        else:
            self._interval = interval
        output = curve_fit(self._fit_func, self._x, self._y,
                           p0=self._param, **kwargs)
        if self._plot:
            data = self._baseclass.full_field(self._x, output[0])
            res = np.sqrt(np.sum((self._y - data)**2))
            self._ax.set_title('Gaussians: %d, Iteration: %d, Residual: %.3e\nOptimization complete.' % (
                self._n_gaussians, self._iteration, res))
            self._fig.canvas.draw()
            self._fig.canvas.flush_events()
            if ioff:
                plt.ioff()
            if close_after:
                plt.cla()
                plt.close(self._fig)
                self._fig = None
                gc.collect()
        return output

    def plot(self, *, x: list | np.ndarray = None, y : list | np.ndarray = None, ax: plt.Axes = None, figure_title: str = '', window_title: str = '', show: bool = True, **kwargs) -> plt.Figure:
        """Plot the data and the fit.

        Args:
            x (list | np.ndarray, optional): X data. Defaults to None for internal X data.
            y (list | np.ndarray, optional): Y data. Defaults to None for internal Y data.
            ax (plt.Axes, optional): External axis object. Defaults to None, creates new figure and axis.
            figure_title (str, optional): Figure title, used if ax is not supplied. Defaults to ''.
            window_title (str, optional): Plot window title, used if ax is not supplied. Defaults to ''.
            show (bool, optional): Execute plt.show() at the end. Defaults to True.

        Raises:
            ValueError: _description_

        Returns:
            plt.Figure: _description_
        """
        ax_is_internal = False
        if ax is None:
            # create fig, ax
            ax_is_internal = True
            fig, ax = plt.subplots(1, 1, **kwargs)
            if figure_title is not None and len(figure_title) > 0:
                fig.suptitle(figure_title)
            if window_title is not None and len(window_title) > 0:
                fig.canvas.manager.set_window_title(window_title)
        if x is None:
            x = self._x
        if y is None:
            y = self._y
        if len(x) != len(y):
            raise ValueError('X and Y arrays must have the same length.')
        x = np.asarray(x, dtype=float)
        y = np.asarray(y, dtype=float)
        # plot everything here
        ax.plot(x, y, label='Data')
        ax.plot(x, self.background(x), label='Background')
        for i in range(self.n_gaussians()):
            ax.plot(x, self.gaussian_w_bg(id=i), label='Gaussian %d'%(i))
        if ax_is_internal:
            ax.set_xlim(x.min(), x.max())
            ax.legend()
        if ax_is_internal and show:
            plt.show()
        return ax.get_figure()

    def full_field(self, x: np.ndarray = None, params: tuple | list | np.ndarray = None) -> np.ndarray:
        """Calculate full fit result (background + gaussians)

        Args:
            x (np.ndarray, optional): X data. Leave empty or None to use the X coordinates used to initialize the GaussFit() instance.
            params (tuple | list | np.ndarray, optional): Background and Gaussian parameters. Set to None to use the inferred parameters.

        Returns:
            np.ndarray: Y data.
        """
        if x is None:
            x = self._x
        if params is None:
            params = self._param
        return self._baseclass.full_field(x, params)

    def background(self, x: np.ndarray = None, params: tuple | list | np.ndarray = None) -> np.ndarray:
        """Calculate background only.

        Args:
            x (np.ndarray, optional): X data. Leave empty or None to use the X coordinates used to initialize the GaussFit() instance.
            params (tuple | list | np.ndarray, optional): Background and Gaussian parameters. Set to None to use the inferred parameters.

        Returns:
            np.ndarray: Y data.
        """
        if x is None:
            x = self._x
        if params is None:
            params = self._param
        return self._baseclass.background(x, params)

    def gaussian_w_bg(self, x: np.ndarray = None, id: int = 0, params: tuple | list | np.ndarray = None) -> np.ndarray:
        """Calculate fit result (background + single gaussian)

        Args:
            x (np.ndarray, optional): X data. Leave empty or None to use the X coordinates used to initialize the GaussFit() instance.
            id (int, optional): ID of the Gaussian [[0, N), where N is the total number of Gaussians.] Defaults to 0.
            params (tuple | list | np.ndarray, optional): Background and Gaussian parameters. Set to None to use the inferred parameters.

        Returns:
            np.ndarray: Y data.
        """
        if x is None:
            x = self._x
        if params is None:
            params = self._param
        return self._baseclass.gaussian_w_bg(x, id, params)

    def gaussian_wo_bg(self, x: np.ndarray = None, id: int = 0, params: tuple | list | np.ndarray = None) -> np.ndarray:
        """Calculate single Gaussian (without background)

        Args:
            x (np.ndarray, optional): X data. Leave empty or None to use the X coordinates used to initialize the GaussFit() instance.
            id (int, optional): ID of the Gaussian [[0, N), where N is the total number of Gaussians.] Defaults to 0.
            params (tuple | list | np.ndarray, optional): Background and Gaussian parameters. Set to None to use the inferred parameters.

        Returns:
            np.ndarray: Y data.
        """
        if x is None:
            x = self._x
        if params is None:
            params = self._param
        return self._baseclass.gaussian_wo_bg(x, id, params)

    @staticmethod
    def gaussian(x: np.ndarray, H: float, A: float, x0: float, sigma: float) -> np.ndarray:
        """Evaluate a Gaussian.

        Args:
            x (np.ndarray): X data.
            H (float): Y Offset (constant).
            A (float): Amplitude.
            x0 (float): Mean.
            sigma (float): Width (2*sigma^2)

        Returns:
            np.ndarray: Evaluated Gaussian.
        """
        return H + A * np.exp(-((x - x0)/sigma)**2)

    def n_gaussians(self, params: tuple | list | np.ndarray = None) -> int:
        """Get number of Gaussians inferred from the parameters.

        Returns:
            int: Number of Gaussians.
        """
        if params is None:
            params = self._param
        return self._baseclass.n_gaussians(params)

    def n_background_params(self) -> int:
        """Get number of background parameters.

        Returns:
            int: Number of background parameters.
        """
        return self._baseclass.n_background_params()

    def gaussfunc_name(self) -> str:
        """Get name of the underlying Gaussian function class.

        Returns:
            str: ame of the underlying Gaussian function class
        """
        return type(self._baseclass).__name__

if __name__ == '__main__':
    x = np.linspace(-2, 2, 100)
    p_def = (0, 10, 0.2, -0.5, 0, 0, 0.2, 10, 0.1)
    y = 10 + 0.2 * x - 0.5 * x**2 + 10 * np.exp(-((x - 0.2) / 0.1)**2)
    p0 = (0, 10, 0.1, -0.1, 0, 0, 0, 8, 0.5)
    p_low = (-2, 0, -np.inf, -np.inf, -1e-8, -1e-8, -2, 0, 0)
    p_high = (2, np.inf, np.inf, 0, 1e-8, 1e-8, 2, np.inf, np.inf)
    gfit = GaussFit(x, y, p0)
    gfit.run(ioff=True, close_after=False, bounds=(p_low, p_high))
    fparam = list(gfit.param)
    fparam[-1] = abs(fparam[-1])
    fparam = tuple(fparam)
    print('Original:', p_def)
    print('Derived:', fparam)
    print('Error:', ((y - gfit.full_field())**2).sum())
    gfit.plot()


from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys

# from selenium.webdriver.chrome.service import Service
# browser = webdriver.Firefox(service=path_chrome)
# browser = webdriver.Chrome(service=path_chrome)
browser = webdriver.Chrome()
browser.get("https://www.amazon.com.br")
xpath_search = '//*[@id="twotabsearchtextbox"]'
browser.find_element(By.XPATH, xpath_search).send_keys("iphone" + Keys.RETURN)

iphone_names = browser.find_elements(By.XPATH, '//span[@class="a-size-base-plus a-color-base a-text-normal"]')
prices = browser.find_elements(By.XPATH, '//*[@class="a-price"]')

name = []
price = []

# for n in iphone_names:
#     name.append(n.text)
# print(name)
#
# for p in prices:
#     price.append(p.text.replace("\n", ","))
# print(price)

cont = 1
for name, price in zip(iphone_names, prices):
    print(cont, name.text, price.text.replace("\n", ","))
    cont += 1
browser.close()


import re
import os
import errno

try:
    from urllib.parse import urlparse
except ImportError:
    from urlparse import urlparse

from django.conf import settings
from django.core.urlresolvers import resolve

IS_FILE_RE = re.compile(r'\.([a-z]{2,4})$')
YOLODEX_MEDIA_PATH = u'yolodex/sources/{}/{}'

def get_current_app(request):
    return resolve(request.path).namespace

def get_media_path(realm, path):
    return YOLODEX_MEDIA_PATH.format(realm.pk, path)

def get_absolute_media_path(realm, path):
    return os.path.join(settings.MEDIA_ROOT, get_media_path(realm, path))

def mkdir_p(path):
    try:
        os.makedirs(path)
    except OSError as exc:
        if exc.errno == errno.EEXIST and os.path.isdir(path):
            pass
        else:
            raise

def get_domain(url):
    o = urlparse(url)
    return o.netloc

def get_sources(realm, sources):
    for kind, source, extra in get_raw_sources(realm, sources):
        if kind == 'file':
            source = get_media_path(realm, source)
        yield kind, source, extra

def get_raw_sources(realm, sources):
    for line in sources.splitlines():
        if not line.strip():
            continue
        match = IS_FILE_RE.search(line)
        if line.startswith(('http://', 'https://')):
            extra = get_domain(line)
            if match is not None:
                extra = u'%s (%s)' % (extra, match.group(1).upper())
            yield ('url', line, extra)
        elif match is not None:
            ext = match.group(1).upper()
            yield ('file', line, ext)
        else:
            yield ('text', line, '')


import random
from typing import Dict, List, Tuple

from netaddr import IPNetwork

from maasserver.models import Fabric, Subnet, VLAN
from maasserver.testing.commissioning import (
    FakeCommissioningData,
    LXDAddress,
    LXDNetwork,
)
from maastesting.factory import factory

from .defs import MACHINES_PER_FABRIC

def make_networks(
    count_per_fabric: int, fabrics_count
) -> Tuple[Dict[Fabric, List[VLAN]], Dict[VLAN, IPNetwork]]:
    fabrics = [Fabric.objects.create() for _ in range(fabrics_count)]
    vlans = {}
    ip_networks = {}
    created_cidrs = set()

    for fabric in fabrics:
        vlans[fabric] = [
            VLAN.objects.create(vid=vid, fabric=fabric)
            for vid in random.sample(range(10, 4095), count_per_fabric)
        ]
        for vlan in vlans[fabric]:
            cidr = factory.make_ipv4_network(slash=24, but_not=created_cidrs)
            created_cidrs.add(cidr)
            Subnet.objects.create(
                vlan=vlan,
                cidr=cidr,
            )
            ip_networks[vlan] = IPNetwork(cidr)

    return vlans, ip_networks

def make_network_interfaces(
    machine_infos: List[FakeCommissioningData],
    vlans: Dict[Fabric, List[VLAN]],
    ip_networks: Dict[VLAN, IPNetwork],
):
    machine_infos = list(machine_infos)

    def make_network_ip(vlan: VLAN, network: LXDNetwork):
        ip_network = ip_networks[vlan]
        ip = factory.pick_ip_in_network(ip_network)
        network.addresses = [LXDAddress(str(ip), ip_network.prefixlen)]
        return ip

    for fabric_vlans in vlans.values():
        if not machine_infos:
            return
        fabric_machine_infos, machine_infos = (
            machine_infos[:MACHINES_PER_FABRIC],
            machine_infos[MACHINES_PER_FABRIC:],
        )
        for machine_info in fabric_machine_infos:
            for idx, vlan in enumerate(fabric_vlans):
                network = machine_info.create_physical_network()
                if idx == 0:
                    bridge = machine_info.create_bridge_network(
                        mac_address=network.hwaddr, parents=[network]
                    )
                    make_network_ip(vlan, bridge)
                elif idx == 1:
                    vlan_iface = machine_info.create_vlan_network(
                        vid=vlan.vid, parent=network
                    )
                    make_network_ip(vlan, vlan_iface)
                elif idx == 2:
                    network2 = machine_info.create_physical_network()
                    bond = machine_info.create_bond_network(
                        parents=[network, network2]
                    )
                    make_network_ip(vlan, bond)
                else:
                    make_network_ip(vlan, network)