Ejemplo n.º 1
0
class mask_maker(object):
    #This is my third home-made class: A GUI for masking pixels in the spectrum.
    def __init__(self,list_of_wls,list_of_orders,list_of_saved_selected_columns,Nxticks,Nyticks,nsigma=3.0):
        """
        We initialize with a figure object, three axis objects (in a list)
        the wls, the orders, the masks already made; and we do the first plot.

        NOTICE: Anything that is potted in these things as INF actually used to be
        a NaN that was masked out before.
        """
        import numpy as np
        import pdb
        import tayph.functions as fun
        import tayph.plotting as plotting
        import tayph.drag_colour as dcb
        import matplotlib.pyplot as plt
        import itertools
        from matplotlib.widgets import MultiCursor
        import tayph.util as ut
        from tayph.vartests import typetest,postest
        import copy
        #Upon initialization, we raise the keywords onto self.
        self.N_orders = len(list_of_wls)
        if len(list_of_wls) < 1 or len(list_of_orders) < 1:# or len(list_of_masks) <1:
            raise Exception('Runtime Error in mask_maker init: lists of WLs, orders and/or masks have less than 1 element.')
        if len(list_of_wls) != len(list_of_orders):# or len(list_of_wls) != len(list_of_masks):
            raise Exception('Runtime Error in mask_maker init: List of wls and list of orders have different length (%s & %s).' % (len(list_of_wls),len(list_of_orders)))
        typetest(Nxticks,int,'Nxticks in mask_maker init',)
        typetest(Nyticks,int,'Nyticks in mask_maker init',)
        typetest(nsigma,float,'Nsigma in mask_maker init',)
        postest(Nxticks,varname='Nxticks in mask_maker init')
        postest(Nyticks,varname='Nyticks in mask_maker init')
        postest(nsigma,varname='Nsigma in mask_maker init')

        self.list_of_wls = list_of_wls
        self.list_of_orders = list_of_orders
        self.list_of_selected_columns = list(list_of_saved_selected_columns)
        #Normally, if there are no saved columns to load, list_of_saved_selected_columns is an empty list. However if
        #it is set, then its automatically loaded into self.list_of_selected_columns upon init.
        #Below there is a check to determine whether it was empty or not, and whether the list of columns
        #has the same length as the list of orders.
        if len(self.list_of_selected_columns) == 0:
            for i in range(self.N_orders):
                self.list_of_selected_columns.append([])#Make a list of empty lists.
                    #This will contain all columns masked by the user, on top of the things
                    #that are already masked by the program.
        else:
            if len(self.list_of_selected_columns) != self.N_orders:
                raise Exception('Runtime Error in mask_maker init: Trying to restore previously saved columns but the number of orders in the saved column file does not match the number of orders provided.')
            print('------Restoring previously saved columns in mask-maker')


        #All checks are now complete. Lets prepare to do the masking.
        # self.N = min([56,self.N_orders-1])#We start on order 56, or the last order if order 56 doesn't exist.
        self.N=0
        #Set the current active order to order , and calculate the meanspec
        #and residuals to be plotted, which are saved in self.
        self.set_order(self.N)


        #Sorry for the big self.spaghetti of code. This initializes the plot.
        #Functions and vars further down in the class will deal with updating the plots
        #as buttons are pressed. Some of this is copied from the construct_doppler_model
        #function; but this time I made it part of the class.
        #First define plotting and axis parameters for the colormesh below.
        self.Nxticks = Nxticks
        self.Nyticks = Nyticks
        self.nsigma = nsigma
        self.xrange = [0,self.npx-1]
        self.yrange=[0,self.nexp-1]
        self.x_axis=fun.findgen(self.npx).astype(int)
        self.y_axis = fun.findgen(self.nexp).astype(int)
        self.x2,self.y2,self.z,self.wl_sel,self.y_axis_sel,self.xticks,self.yticks,void1,void2= plotting.plotting_scales_2D(self.x_axis,self.y_axis,self.residual,self.xrange,self.yrange,Nxticks=self.Nxticks,Nyticks=self.Nyticks,nsigma=self.nsigma)
        self.fig,self.ax = plt.subplots(3,1,sharex=True,figsize=(14,6))#Initialize the figure and 3 axes.
        plt.subplots_adjust(left=0.05)#Make them more tight, we need all the space we can get.
        plt.subplots_adjust(right=0.85)

        self.ax[0].set_title('Spectral order %s  (%s - %s nm)' % (self.N,round(np.min(self.wl),1),round(np.max(self.wl),1)))
        self.ax[1].set_title('Residual of time-average')
        self.ax[2].set_title('Time average 1D spectrum')

        array1 = copy.deepcopy(self.order)
        array2 = copy.deepcopy(self.residual)
        array1[np.isnan(array1)] = np.inf#The colobar doesn't eat NaNs, so now set them to inf just for the plot.
        array2[np.isnan(array2)] = np.inf#And here too.
        #The previous three lines are repeated in self.update_plots()
        self.img1=self.ax[0].pcolormesh(self.x2,self.y2,array1,vmin=0,vmax=self.img_max,cmap='hot')
        self.img2=self.ax[1].pcolormesh(self.x2,self.y2,array2,vmin=self.vmin,vmax=self.vmax,cmap='hot')
        self.img3=self.ax[2].plot(self.x_axis,self.meanspec)
        self.ax[2].set_xlim((min(self.x_axis),max(self.x_axis)))
        self.ax[2].set_ylim(0,self.img_max)
        #This trick to associate a single CB to multiple axes comes from
        #https://stackoverflow.com/questions/13784201/matplotlib-2-subplots-1-colorbar
        self.cbar = self.fig.colorbar(self.img2, ax=self.ax.ravel().tolist(),aspect = 20)
        self.cbar = dcb.DraggableColorbar_fits(self.cbar,[self.img2],'hot')
        self.cbar.connect()

        #The rest is for dealing with the masking itself; the behaviour of the
        #add/subtact buttons, the cursor and the saving of the masked columns.
        self.col_active = ['coral','mistyrose']#The colours for the ADD and SUBTRACT buttons that
        #can be activated.
        self.col_passive = ['lightgrey','whitesmoke']#Colours when they are not active.
        self.MW = 50#The default masking width.
        self.addstatus = 0#The status for adding-to-mask mode starts as zero; i.e. it starts inactive.
        self.substatus = 0#Same for subtraction.
        self.list_of_polygons = []#This stores the polygons that are currently plotted. When initializing, none are plotted.
        #However when proceeding through draw_masked_areas below, this variable could become populated if previously selected
        #column were loaded from file.
        self.multi = MultiCursor(self.fig.canvas, (self.ax[0],self.ax[1],self.ax[2]), color='g', lw=1, horizOn=False, vertOn=True)
        self.multi.set_active(False)#The selection cursor starts deactivated as well, and is activated and deactivated
        #further down as the buttons are pressed.
        self.apply_to_all = False
        self.apply_to_all_future = False

        #The following show the z value of the plotted arrays in the statusbar,
        #taken from https://matplotlib.org/examples/api/image_zcoord.html
        numrows, numcols = self.order.shape
        def format_coord_order(x, y):
            col = int(x + 0.5)
            row = int(y + 0.5)
            if col >= 0 and col < numcols and row >= 0 and row < numrows:
                z = self.order[row, col]
                return 'x=%1.4f, y=%1.4f, z=%1.4f' % (x, y, z)
            else:
                return 'x=%1.4f, y=%1.4f' % (x, y)
        def format_coord_res(x, y):
            col = int(x + 0.5)
            row = int(y + 0.5)
            if col >= 0 and col < numcols and row >= 0 and row < numrows:
                z = self.residual[row, col]
                return 'x=%1.4f, y=%1.4f, z=%1.4f' % (x, y, z)
            else:
                return 'x=%1.4f, y=%1.4f' % (x, y)
        self.ax[0].format_coord = format_coord_order
        self.ax[1].format_coord = format_coord_res
        self.draw_masked_areas()
        #This is the end of init.


    def draw_masked_areas(self):
        """
        This function draws green boxes onto the three plots corresponding to
        which columns where masked.
        """
        import matplotlib.pyplot as plt

        def plot_span(min,max):#This is a shorthand for drawing the polygons in the same style on all subplots.
            for subax in self.ax:#There are 3 ax objects in this list.
                self.list_of_polygons.append(subax.axvspan(min,max,color='green',alpha=0.5))

        #Start by removing any polygons that were saved by earlier calls to this
        #function after switching orders. Everything needs to be redrawn each time
        #a new order is selected, i.e. each time this function is called.
        if len(self.list_of_polygons) > 0:#If blocks are already plotted...
            for i in self.list_of_polygons:
                i.remove()#emtpy the list.
            self.list_of_polygons = []

        #Select the columns defined by the select-columns events in the add and subtract subroutines.
        columns = self.list_of_selected_columns[self.N]
        if len(columns) > 0:
            columns.sort()
            min = columns[0]#start by opening a block
            for i in range(1,len(columns)-1):
                dx = columns[i] - columns[i-1]
                if dx > 1:#As long as dx=1, we are passing through adjacently selected columns.
                #Only do something if dx>1, in which case we need to end the block and start a new one.
                    max=columns[i-1]#then the previous column was the last element of the block
                    plot_span(min,max)
                    min=columns[i]#Begin a new block
            #at the end, finish the last block:
            max = columns[-1]
            plot_span(min,max)




    def set_order(self,i):
        """
        This modifies the currently active order to be plotted.
        """
        import numpy as np
        import tayph.functions as fun
        import warnings
        import tayph.util as ut
        import copy
        from tayph.vartests import typetest
        typetest(i,int,'i in mask_maker.set_order()',)

        self.wl = self.list_of_wls[i]
        self.order = self.list_of_orders[i]

        #Measure the shape of the current order
        self.nexp = np.shape(self.order)[0]
        self.npx = np.shape(self.order)[1]

        #Compute the meanspec and the residuals, ignoring runtime warnings related to NaNs:
        with warnings.catch_warnings():
            warnings.simplefilter("ignore", category=RuntimeWarning)
            self.meanspec = np.nanmean(self.list_of_orders[i],axis=0)
            self.residual = self.order / self.meanspec
        self.img_max = np.nanmean(self.meanspec[fun.selmax(self.meanspec,0.02,s=0.02)])*1.3
        self.vmin = np.nanmedian(self.residual)-3.0*np.nanstd(self.residual)
        self.vmax = np.nanmedian(self.residual)+3.0*np.nanstd(self.residual)


    def exit_add_mode(self):
        """
        This exits column-addition mode of the interface.
        This is a separate function that can be called on 3 occasions:
        When pressing the Mask button for the second time, when pressing the
        subtract button while in Mask mode, and when exiting the GUI.
        """
        self.multi.set_active(False)#This is the green vertical line. Its not shown when this mode is off.
        self.fig.canvas.mpl_disconnect(self.click_connector)
        self.addstatus = 0
        self.badd.color=self.col_passive[0]
        self.badd.hovercolor=self.col_passive[1]
        self.fig.canvas.draw()
        print('---------Exited add mode')

    def exit_sub_mode(self):
        """
        This exits column-subtraction mode of the interface.
        This is a separate function that can be called on 3 occasions:
        When pressing the Mask button for the second time, when pressing the
        subtract button while in Mask mode, and when exiting the GUI.
        """
        self.multi.set_active(False)#This is the green vertical line. Its not shown when this mode is off.
        self.fig.canvas.mpl_disconnect(self.click_connector)
        self.substatus = 0
        self.bsub.color=self.col_passive[0]
        self.bsub.hovercolor=self.col_passive[1]
        self.fig.canvas.draw()
        print('---------Exited sub mode')

    def add(self,event):
        """
        This is an event handler for pressing the Mask button in the GUI.
        It has 2 behaviours depending on whether the button was pressed before or not.
        If it was not pressed, addstatus == 0 and it will enter mask mode.
        If it was pressed, the GUI is in mask mode, addstatus == 1 and instead it will
        leave mask mode upon pressing it. Addstatus then becomes zero and the thing starts over.

        When in mask mode, the user can click on any of the three subplots to
        select columns that he/she wants to be masked. Exiting mask mode deactivates
        this behaviour.
        """

        def add_columns(event):
            #This handles with a mouseclick in either of the three plots while in add mode.
            if event.inaxes in [self.ax[0],self.ax[1],self.ax[2]]:#Check that it occurs in one of the subplots.
                ci = event.xdata*1.0#xdata is the column that is selected.
                selmin = max([int(ci-0.5*self.MW),0])#Select all columns in a range of self.HW from that click.
                selmax = min([int(ci+0.5*self.MW),self.npx-1])
                sel = self.x_axis[selmin:selmax]
                if self.apply_to_all == True:
                    for o in range(self.N_orders):#Loop through all orders.
                        for i in sel:#Add the selected range to the list of this order.
                            self.list_of_selected_columns[o].append(i)
                        self.list_of_selected_columns[o]=list(set(self.list_of_selected_columns[o]))#Remove duplicates
                elif self.apply_to_all_future == True:
                    for o in range(self.N,self.N_orders):#Loop through all orders greater than this one.
                        for i in sel:#Add the selected range to the list of this order.
                            self.list_of_selected_columns[o].append(i)
                        self.list_of_selected_columns[o]=list(set(self.list_of_selected_columns[o]))#Remove duplicates
                else:#If apply to all (future) is false:
                    for i in sel:#Add the selected range to the list of this order.
                        self.list_of_selected_columns[self.N].append(i)
                    self.list_of_selected_columns[self.N]=list(set(self.list_of_selected_columns[self.N]))#Remove duplicates

                self.draw_masked_areas()#Update the green areas.
                self.fig.canvas.draw_idle()

        if self.addstatus == 0:
            if self.substatus == 1:
                self.exit_sub_mode()
            print('---------Entered adding mode')
            self.addstatus=1
            self.badd.color=self.col_active[0]
            self.badd.hovercolor=self.col_active[1]
            self.fig.canvas.draw()
            self.multi.set_active(True)#This is the green vertical line. Its shown only when this mode is on.
            self.click_connector = self.fig.canvas.mpl_connect('button_press_event', add_columns)#This is the connector that registers clicks
            #on the plot. Not clicks on the buttons!
        else:
            self.exit_add_mode()

    def subtract(self,event):
        """
        Similar to add(), this is an event handler for pressing the Unmask button in the GUI.
        It has 2 behaviours depending on whether the button was pressed before or not.
        If it was not pressed, substatus == 0 and it will enter unmask mode.
        If it was pressed, the GUI is in unmask mode, substatus == 1 and instead it will
        leave unmask mode upon pressing it. Substatus then becomes zero and the thing starts over.

        When in unmask mode, the user can click on any of the three subplots to
        select columns that he/she wants to be removed from the list of masked columns.
        Exiting mask mode deactivates this behaviour.
        """
        def remove_columns(event):
            if event.inaxes in [self.ax[0],self.ax[1],self.ax[2]]:#Check that it occurs in one of the subplots.
                ci = event.xdata*1.0
                selmin = max([int(ci-0.5*self.MW),0])
                selmax = min([int(ci+0.5*self.MW),self.npx-1])
                sel = self.x_axis[selmin:selmax]
                if self.apply_to_all == True:
                    for o in range(self.N_orders):#Loop through all orders.
                        for i in sel:
                            if i in self.list_of_selected_columns[o]:
                                self.list_of_selected_columns[o].remove(i)
                elif self.apply_to_all_future == True:
                    for o in range(self.N,self.N_orders):#Loop through all orders.
                        for i in sel:
                            if i in self.list_of_selected_columns[o]:
                                self.list_of_selected_columns[o].remove(i)
                else:#If apply_to_all is false
                    for i in sel:
                        if i in self.list_of_selected_columns[self.N]:
                            self.list_of_selected_columns[self.N].remove(i)

                self.draw_masked_areas()
                self.fig.canvas.draw_idle()
                #End of remove_columns subroutine.


        if self.substatus == 0:
            if self.addstatus == 1:
                self.exit_add_mode()
            print('---------Entered subtraction mode')
            self.substatus=1
            self.bsub.color=self.col_active[0]
            self.bsub.hovercolor=self.col_active[1]
            self.fig.canvas.draw()
            self.multi.set_active(True)
            self.click_connector = self.fig.canvas.mpl_connect('button_press_event', remove_columns)
        else:
            self.exit_sub_mode()

    def applyall(self,event):
        """
        This is an event handler for pressing the Apply All button in the GUI.
        It has 2 behaviours depending on whether the button was pressed before or not.
        If it was not pressed, apply_to_all == 0 and it will enter apply-to-all mode.
        If it was pressed, the GUI is in apply-to-all mode, apply_to_all == 1 and instead it will
        switch it to zero upon pressing it. Apply_to_all then becomes zero and the thing starts over.
        """
        if self.apply_to_all == False:
            print('---------Entering Apply-to-all mode')
            self.apply_to_all = True
            self.ball.color=self.col_active[0]
            self.ball.hovercolor=self.col_active[1]
        else:
            print('---------Exiting Apply-to-all mode')
            self.apply_to_all = False
            self.ball.color=self.col_passive[0]
            self.ball.hovercolor=self.col_passive[1]
        if self.apply_to_all_future == True:
            print('---------Exiting Apply-to-all-future mode')
            self.apply_to_all_future = False
            self.ballf.color=self.col_passive[0]
            self.ballf.hovercolor=self.col_passive[1]
        self.fig.canvas.draw()
        self.fig.canvas.draw()

    def applyallfuture(self,event):
        """
        This is an event handler for pressing the Apply All Future button in the GUI.
        It has 2 behaviours depending on whether the button was pressed before or not.
        If it was not pressed, apply_to_all == 0 and it will enter apply-to-all mode.
        If it was pressed, the GUI is in apply-to-all mode, apply_to_all == 1 and instead it will
        switch it to zero upon pressing it. Apply_to_all then becomes zero and the thing starts over.
        """
        if self.apply_to_all_future == False:
            print('---------Entering Apply-to-all-future mode')
            self.apply_to_all_future = True
            self.ballf.color=self.col_active[0]
            self.ballf.hovercolor=self.col_active[1]
        else:
            print('---------Exiting Apply-to-all-future mode')
            self.apply_to_all_future = False
            self.ballf.color=self.col_passive[0]
            self.ballf.hovercolor=self.col_passive[1]
        if self.apply_to_all == True:
            print('---------Exiting Apply-to-all mode')
            self.apply_to_all = False
            self.ball.color=self.col_passive[0]
            self.ball.hovercolor=self.col_passive[1]
        self.fig.canvas.draw()
        self.fig.canvas.draw()


    def applytotal(self,event):
        """
        This is an event handler for pressing the Mask entire order button in the GUI.
        It has 2 behaviours depending on whether anything is masked or not.
        If nothing was masked, the entire order will be masked.
        If something was, it will all be unmasked.
        """
        import tayph.functions as fun
        ncol = len(self.list_of_orders[self.N][0])
        self.list_of_selected_columns[self.N] = list(fun.findgen(ncol))
        # print(self.list_of_selected_columns[self.N])
        self.draw_masked_areas()#Update the green areas.
        self.fig.canvas.draw()
        self.fig.canvas.draw()

    def cleartotal(self,event):
        """This is an event handler for pressing the Unmask entire order button in the GUI.
        It has 2 behaviours depending on whether anything is masked or not.
        If nothing was masked, the entire order will be masked.
        If something was, it will all be unmasked.
        """
        self.list_of_selected_columns[self.N] = []
        # print(self.list_of_selected_columns[self.N])
        self.draw_masked_areas()#Update the green areas.
        self.fig.canvas.draw()
        self.fig.canvas.draw()

    def previous(self,event):
        """
        The button to go to the previous order.
        """
        self.N -= 1
        if self.N <0:#If order tries to become less than zero, loop to the highest order.
            self.N = len(self.list_of_orders)-1
        self.set_order(self.N)
        self.mask_slider.set_val(self.N)#Update the slider value.
        self.update_plots()#Redraw everything.

    def next(self,event):
        """
        The button to go to the next order. Similar to previous().
        """
        self.N += 1
        if self.N > len(self.list_of_orders)-1:
            self.N = 0
        self.set_order(self.N)
        self.mask_slider.set_val(self.N)
        self.update_plots()

    def cancel(self,event):
        """
        This is a way to crash hard out of the python interpreter.
        """
        import sys
        print('------Canceled by user')
        sys.exit()

    def save(self,event):
        """
        The "save" button is actually only closing the plot. Actual saving of things
        happens below plt.show() below.
        """
        import matplotlib.pyplot as plt
        plt.close(self.fig)

    def update_plots(self):
        """
        This redraws the plot planels, taking care to reselect axis ranges and such.
        """
        import numpy as np
        import tayph.drag_colour as dcb
        import tayph.functions as fun
        import copy
        array1 = copy.deepcopy(self.order.ravel())
        array2 = copy.deepcopy(self.residual.ravel())
        array1[np.isnan(array1)] = np.inf#The colobar doesn't eat NaNs, so now set them to inf just for the plot.
        array2[np.isnan(array2)] = np.inf#And here too.

        self.img1.set_array(array1)
        self.img1.set_clim(vmin=0,vmax=self.img_max)
        self.img2.set_array(array2)
        self.img2.set_clim(vmin=self.vmin,vmax=self.vmax)
        self.img3[0].set_ydata(self.meanspec)
        self.ax[0].set_title('Spectral order %s  (%s - %s nm)' % (self.N,round(np.min(self.wl),1),round(np.max(self.wl),1)))
        self.ax[2].set_ylim(0,self.img_max)
        self.draw_masked_areas()
        self.fig.canvas.draw_idle()

    def slide_order(self,event):
        """
        This handles the order-selection slide bar.
        """
        self.N = int(self.mask_slider.val)
        self.set_order(self.N)
        self.update_plots()

    def slide_maskwidth(self,event):
        """
        This handles the mask-width selection slide bar.
        """
        self.MW = int(self.MW_slider.val)
Ejemplo n.º 2
0
class MainWindow(QMainWindow, Ui_MainWindow):
    def __init__(self):

        # Initiate window in class 'QMainWindow'
        super().__init__()

        # Configure window layout in Ui_MainWindow
        self.setupUi(self)

        # Initiate figure, canvas and axes
        self.fig = Figure(figsize=(100, 100))
        self.canvas = FigureCanvas(self.fig)
        self.ax = self.fig.subplots()

        # Define axes lines
        self.ax.axhline(linewidth=1, linestyle="dashdot", color="#6E6E6E")
        self.ax.axvline(linewidth=1, linestyle="dashdot", color="#6E6E6E")

        # Set plot title
        self.ax.set_title("Simple plot tool built with Python")

        # Local dictionary
        rectprops = dict(facecolor='gray', alpha=0.5)

        # Connect event with string *button_press_event* to *on_mouse_press* function
        # https://matplotlib.org/api/backend_bases_api.html?highlight=mpl_connect#matplotlib.backend_bases.FigureCanvasBase.mpl_connect
        self.canvas.mpl_connect('button_press_event', self.on_mouse_press)
        self.canvas.mpl_connect('motion_notify_event', self.on_move_mouse)

        # Create 'RectangleSelector' object to be activated when press on Zoom Rect Buttom
        # REMARK: This functions creates a set of polylines in the axes
        # https://matplotlib.org/api/widgets_api.html?highlight=rectangleselector#matplotlib.widgets.RectangleSelector
        # https://matplotlib.org/gallery/widgets/rectangle_selector.html?highlight=rectangleselector
        self.RS = RectangleSelector(self.ax,
                                    self.on_select_zoom_box,
                                    useblit=True,
                                    rectprops=rectprops)
        self.RS.set_active(False)  # deactivate the selector

        # Create 'SpanSelector' object in vertical and horizontal directions, to be activated with zoom vert and hor
        # https://matplotlib.org/api/widgets_api.html?highlight=spanselector#matplotlib.widgets.SpanSelector
        # https://matplotlib.org/gallery/widgets/span_selector.html?highlight=spanselector
        self.SSv = SpanSelector(self.ax,
                                self.on_vert_zoom,
                                'vertical',
                                useblit=True,
                                rectprops=rectprops)
        self.SSh = SpanSelector(self.ax,
                                self.on_hor_zoom,
                                'horizontal',
                                useblit=True,
                                rectprops=rectprops)
        self.SSv.set_active(False)
        self.SSh.set_active(False)

        # Create 'Multicursor' object in vertical and horizontal directions
        # https://matplotlib.org/api/widgets_api.html#matplotlib.widgets.MultiCursor
        # https://matplotlib.org/gallery/widgets/multicursor.html?highlight=multicursor
        self.MC = MultiCursor(self.canvas, (self.ax, ),
                              useblit=True,
                              horizOn=True,
                              vertOn=True,
                              linewidth=1,
                              color="#C8C8C8")
        self.MC.set_active(True)

        # Add Figure Canvas to PyQt Widget
        # REMARK: It is HERE where the matplotlib canvas is conected to PyQt layout (lacking of official documentation)
        # https://www.riverbankcomputing.com/static/Docs/PyQt5/api/qtwidgets/qboxlayout.html?highlight=addwidget
        self.verticalLayout.addWidget(self.canvas)

        # Add a empty line to end of axis's line list (RectangleSelector already created some)
        self.ax.plot([], [])
        self.lines = 1  # Number of real plot lines

        # Set axis labels
        self.ax.set_xlabel("x axis")
        self.ax.set_ylabel("y axis")

        # Initiate first current path
        self.path = []

        # Plot current equation (method already conected to signal 'returnPressed' of 'lineEditEq', defined bellow)
        self.on_lineEditEq_returnPressed()

        # Configure home axes limits (method already conected to signal 'clicked' of 'pushButtonHome', defined bellow)
        self.on_pushButtonHome_clicked()

        self.pushButtonPlayMovie.setText("Play Movie \n in last plot\n(►)")
        self.running = False

    def on_move_mouse(self, event):

        # Clears terminal
        # clc()

        if event.inaxes:
            # Print coordinates to mouse position
            print("\nPosition :==============")
            print("x = ", event.xdata, " | y = ", event.ydata)
            print("MultiCursor active? ", self.MC.active)

        else:
            # If the mouse is not over an axes
            print("Clicked out of axes")

    # Function to be called when clicking on canvas
    def on_mouse_press(self, event: matplotlib.backend_bases.MouseEvent):
        """ Function that is called when click with mouse on FIGURE CANVAS (not only inside axes)
            This Functions only prints information on the terminal
        
        Arguments:
            event {matplotlib.backend_bases.MouseEvent} -- 
            
            For the location events (button and key press/release), if the mouse is over the axes, 
            the inaxes attribute of the event will be set to the Axes the event occurs is over, and additionally, 
            the variables xdata and ydata attributes will be set to the mouse location in data coordinates. 
            See KeyEvent and MouseEvent for more info.
            https://matplotlib.org/api/backend_bases_api.html?highlight=mpl_connect#matplotlib.backend_bases.KeyEvent
            https://matplotlib.org/api/backend_bases_api.html?highlight=mpl_connect#matplotlib.backend_bases.MouseEvent
        
        """
        # Clears terminal
        clc()

        # If the mouse is over an axes
        if event.inaxes:

            # Print polylines ploted in axes
            print("Polylines objects: =================")
            i = 0
            for line in event.inaxes.lines:
                print("line [", i, "]: ", line)
                i += 1

            # Print coordinates to mouse position
            print("\nPosition :==============")
            print("x = ", event.xdata, " | y = ", event.ydata)
            self.canvas.draw()
        else:
            # If the mouse is not over an axes
            print("Clicked out of axes")

    # Function to be called by 'RectangleSelector' object
    def on_select_zoom_box(self, eclick: matplotlib.backend_bases.MouseEvent,
                           erelease: matplotlib.backend_bases.MouseEvent):
        """Function that is called by "RectangleSelector" object from "matplotlib.widgets"

        Arguments:
            eclick {matplotlib.backend_bases.MouseEvent} -- matplotlib event at press mouse button
            erelease {matplotlib.backend_bases.MouseEvent} -- matplotlib event at release mouse button
            https://matplotlib.org/api/backend_bases_api.html?highlight=matplotlib%20backend_bases%20mouseevent#matplotlib.backend_bases.MouseEvent
        """
        self.MC.set_active(
            True
        )  # Está em primeiro porque reseta os limites do eixo. Se estivesse depois, as linhas abaixo seriam sobrepostas
        self.ax.set_xlim(eclick.xdata, erelease.xdata)
        self.ax.set_ylim(eclick.ydata, erelease.ydata)
        self.get_limits()
        self.canvas.draw()
        print("")
        self.RS.set_active(False)

    # Functions to be called when "zoom" vertical and horizontal directions
    def on_vert_zoom(self, vmin: float, vmax: float):
        """Function to zoom only in vertical direction that is called by de SpanSelector object with direction="vertical"
        
        Arguments:
            vmin {float} -- min range value
            vmax {float} -- max range value
        """
        self.MC.set_active(True)
        self.ax.set_ylim(vmin, vmax)
        self.get_limits()
        self.SSv.set_active(False)

    def on_hor_zoom(self, hmin: float, hmax: float):
        """Function to zoom only in horizontal direction that is called by de SpanSelector object with direction="horizontal"
        
        Arguments:
            hmin {float} -- min range value
            hmax {float} -- max range value
        """
        self.MC.set_active(True)
        self.ax.set_xlim(hmin, hmax)
        self.get_limits()
        self.SSh.set_active(False)

    # Get values from lineEdits and set axes limits to they
    def set_limits(self):
        """Function to get values from 'lineEdits' boxes and set limits of axes"""

        # Get values from edit boxes
        xinf = float(self.lineEditXinf.text())
        xsup = float(self.lineEditXsup.text())
        yinf = float(self.lineEditYinf.text())
        ysup = float(self.lineEditYsup.text())

        # Set axes limits
        self.ax.set_xlim(xinf, xsup)
        self.ax.set_ylim(yinf, ysup)

        # Redraw figure canvas
        self.canvas.draw()

        self.get_limits()

    # Get axes limits and put on lineEdits
    def get_limits(self):
        """Function to get the actual limits of axes and put it on 'lineEdits' """

        self.lineEditXinf.setText("{:0.2f}".format(self.ax.get_xlim()[0]))
        self.lineEditXsup.setText("{:0.2f}".format(self.ax.get_xlim()[1]))
        self.lineEditYinf.setText("{:0.2f}".format(self.ax.get_ylim()[0]))
        self.lineEditYsup.setText("{:0.2f}".format(self.ax.get_ylim()[1]))

    @QtCore.pyqtSlot()
    def on_lineEditEq_returnPressed(self):

        # Get data from edit boxes
        start = float(self.lineEditStart.text())
        stop = float(self.lineEditStop.text())
        num = int(self.lineEditNum.text())

        # Calculate data to plot the curve
        x = linspace(start, stop, num)

        try:
            y = eval(self.lineEditEq.text())
        except:
            return None

        # Set new data to the curve
        self.ax.lines[-1].set_data(x, y)

        # Update x and y
        path = self.ax.lines[-1].get_path()
        x = path.vertices[:, 0]
        y = path.vertices[:, 1]

        # Color new line
        if all(x == x[0]) or all(y == y[0]):
            self.ax.lines[-1].set_color("#969696")
            self.ax.lines[-1].set_linestyle("dashdot")
        else:
            self.ax.lines[-1].set_color("#000000")
            self.ax.lines[-1].set_linestyle("solid")

        # Get the last line path
        self.path = self.ax.lines[-1].get_path()

        # Redraw figure canvas
        self.canvas.draw()

    @QtCore.pyqtSlot()
    def on_lineEditStart_returnPressed(self):
        self.on_lineEditEq_returnPressed()

    @QtCore.pyqtSlot()
    def on_lineEditStop_returnPressed(self):
        self.on_lineEditEq_returnPressed()

    @QtCore.pyqtSlot()
    def on_lineEditNum_returnPressed(self):
        self.on_lineEditEq_returnPressed()

    @QtCore.pyqtSlot()
    def on_lineEditXinf_returnPressed(self):
        self.set_limits()

    @QtCore.pyqtSlot()
    def on_lineEditXsup_returnPressed(self):
        self.set_limits()

    @QtCore.pyqtSlot()
    def on_lineEditYinf_returnPressed(self):
        self.set_limits()

    @QtCore.pyqtSlot()
    def on_lineEditYsup_returnPressed(self):
        self.set_limits()

    @QtCore.pyqtSlot()
    def on_pushButtonHome_clicked(self):

        # Reset auto-scale
        self.ax.set_autoscale_on(True)

        # Recompute data limits
        self.ax.relim()

        # Automatic axis scaling
        self.ax.autoscale_view()

        # Redraw figure canvas
        self.canvas.draw()

        self.get_limits()

    @QtCore.pyqtSlot()
    def on_pushButtonAddPlot_clicked(self):

        # Add a new line-plot to lines list, if the last wasn't empty
        # or if there is no lines
        if self.lines <= 0 or len(self.ax.lines[-1].get_xdata()) > 0:
            self.ax.plot([], [])
            self.lines += 1

        # Set focus on edit box of equation
        self.lineEditEq.setText("")
        self.lineEditEq.setFocus()

    @QtCore.pyqtSlot()
    def on_pushButtonDelPlot_clicked(self):

        if self.lines > 0:

            # Remove last line
            self.ax.lines.pop()

            # Redraw figure canvas
            self.canvas.draw()

            # Decrease number of curves
            self.lines -= 1

            # Get the last line path
            self.path = self.ax.lines[-1].get_path()

    @QtCore.pyqtSlot()
    def on_pushButtonRect_clicked(self):
        self.MC.set_active(False)
        self.SSv.set_active(False)
        self.SSh.set_active(False)
        self.RS.set_active(True)
        self.canvas.draw()

    @QtCore.pyqtSlot()
    def on_pushButtonHor_clicked(self):
        self.MC.set_active(False)
        self.RS.set_active(False)
        self.SSv.set_active(False)
        self.SSh.set_active(True)
        self.canvas.draw()

    @QtCore.pyqtSlot()
    def on_pushButtonVert_clicked(self):
        self.MC.set_active(False)
        self.RS.set_active(False)
        self.SSh.set_active(False)
        self.SSv.set_active(True)
        self.canvas.draw()

    @QtCore.pyqtSlot()
    def on_lineEditDeltaT_editingFinished(self):
        self.update_Dt()

    @QtCore.pyqtSlot(str)
    def on_lineEditDeltaT_textChanged(self):
        self.update_Dt()

    @QtCore.pyqtSlot()
    def on_lineEditDeltaT_returnPressed(self):
        self.update_Dt()

    @QtCore.pyqtSlot()
    def on_pushButtonPlayMovie_clicked(self):
        if not self.running:
            self.running = True
            self.pushButtonPlayMovie.setText("Pause Movie \n( ▍▍)")

            xt = self.path.vertices[:, 0]
            yt = self.path.vertices[:, 1]
            if all(self.path.vertices[-1, :] ==
                   self.ax.lines[-1].get_path().vertices[-1, :]):
                self.ax.lines[-1].set_data([], [])

            temp_path = self.ax.lines[-1].get_path()
            x = temp_path.vertices[:, 0]
            y = temp_path.vertices[:, 1]
            start_loop = time()
            intervals = []

            i = len(x)
            while self.running and i < len(self.path.vertices[:, 1]):

                i += 1
                x = xt[0:i]
                y = yt[0:i]
                self.ax.lines[-1].set_data(x, y)
                sleep(1)
                self.canvas.start_event_loop(
                    1)  #max([Dt-(time()-start_loop),1e-30]))
                # sleep(max([Dt-(time()-start_loop),1e-30])) # --> nao funciona
                # plt.pause(max([Dt-(time()-start_loop),1e-30])) # --> nao funciona
                intervals.append("Step " + str(i) + ": " +
                                 str(time() - start_loop))
                print(intervals[-1])
                start_loop = time()
                self.canvas.draw()
            self.running = False
            print(array(intervals))
            self.pushButtonPlayMovie.setText("Play Movie \n in last plot\n(►)")
        else:
            self.running = False

    def update_Dt(self):
        global Dt
        try:
            Dt = max([float(self.lineEditDeltaT.text()), 1e-30])
        except:
            Dt = 1.0
        print("Δt = ", Dt)