예제 #1
0
	def mouseEventCallback(self, callback):
		if self.cid != None:
			FigureCanvas.mpl_disconnect(self, self.cid)
			
		self.cid = FigureCanvas.mpl_connect(self, 'button_press_event', callback)
class PlotCanvas(QtCore.QObject):
    """
    Class handling the plotting area in the application.
    """

    # Signals:
    # Request for new bitmap to display. The parameter
    # is a list with [xmin, xmax, ymin, ymax, zoom(optional)]
    update_screen_request = QtCore.pyqtSignal(list)

    def __init__(self, container, app):
        """
        The constructor configures the Matplotlib figure that
        will contain all plots, creates the base axes and connects
        events to the plotting area.

        :param container: The parent container in which to draw plots.
        :rtype: PlotCanvas
        """

        super(PlotCanvas, self).__init__()

        self.app = app

        # Options
        self.x_margin = 15  # pixels
        self.y_margin = 25  # Pixels

        # Parent container
        self.container = container

        # Plots go onto a single matplotlib.figure
        self.figure = Figure(dpi=50)  # TODO: dpi needed?
        self.figure.patch.set_visible(False)

        # These axes show the ticks and grid. No plotting done here.
        # New axes must have a label, otherwise mpl returns an existing one.
        self.axes = self.figure.add_axes([0.05, 0.05, 0.9, 0.9], label="base", alpha=0.0)
        self.axes.set_aspect(1)
        self.axes.grid(True)
        self.axes.axhline(color='Black')
        self.axes.axvline(color='Black')

        # The canvas is the top level container (FigureCanvasQTAgg)
        self.canvas = FigureCanvas(self.figure)
        # self.canvas.setFocusPolicy(QtCore.Qt.ClickFocus)
        # self.canvas.setFocus()

        #self.canvas.set_hexpand(1)
        #self.canvas.set_vexpand(1)
        #self.canvas.set_can_focus(True)  # For key press

        # Attach to parent
        #self.container.attach(self.canvas, 0, 0, 600, 400)  # TODO: Height and width are num. columns??
        self.container.addWidget(self.canvas)  # Qt

        # Copy a bitmap of the canvas for quick animation.
        # Update every time the canvas is re-drawn.
        self.background = self.canvas.copy_from_bbox(self.axes.bbox)

        ### Bitmap Cache
        self.cache = CanvasCache(self, self.app)
        self.cache_thread = QtCore.QThread()
        self.cache.moveToThread(self.cache_thread)
        #super(PlotCanvas, self).connect(self.cache_thread, QtCore.SIGNAL("started()"), self.cache.run)
        # self.connect()
        self.cache_thread.start()
        self.cache.new_screen.connect(self.on_new_screen)

        # Events
        self.canvas.mpl_connect('button_press_event', self.on_mouse_press)
        self.canvas.mpl_connect('button_release_event', self.on_mouse_release)
        self.canvas.mpl_connect('motion_notify_event', self.on_mouse_move)
        #self.canvas.connect('configure-event', self.auto_adjust_axes)
        self.canvas.mpl_connect('resize_event', self.auto_adjust_axes)
        #self.canvas.add_events(Gdk.EventMask.SMOOTH_SCROLL_MASK)
        #self.canvas.connect("scroll-event", self.on_scroll)
        self.canvas.mpl_connect('scroll_event', self.on_scroll)
        self.canvas.mpl_connect('key_press_event', self.on_key_down)
        self.canvas.mpl_connect('key_release_event', self.on_key_up)
        self.canvas.mpl_connect('draw_event', self.on_draw)

        self.mouse = [0, 0]
        self.key = None

        self.pan_axes = []
        self.panning = False

    def on_new_screen(self):

        log.debug("Cache updated the screen!")

    def on_key_down(self, event):
        """

        :param event:
        :return:
        """
        FlatCAMApp.App.log.debug('on_key_down(): ' + str(event.key))
        self.key = event.key

    def on_key_up(self, event):
        """

        :param event:
        :return:
        """
        self.key = None

    def mpl_connect(self, event_name, callback):
        """
        Attach an event handler to the canvas through the Matplotlib interface.

        :param event_name: Name of the event
        :type event_name: str
        :param callback: Function to call
        :type callback: func
        :return: Connection id
        :rtype: int
        """
        return self.canvas.mpl_connect(event_name, callback)

    def mpl_disconnect(self, cid):
        """
        Disconnect callback with the give id.
        :param cid: Callback id.
        :return: None
        """
        self.canvas.mpl_disconnect(cid)

    def connect(self, event_name, callback):
        """
        Attach an event handler to the canvas through the native Qt interface.

        :param event_name: Name of the event
        :type event_name: str
        :param callback: Function to call
        :type callback: function
        :return: Nothing
        """
        self.canvas.connect(event_name, callback)

    def clear(self):
        """
        Clears axes and figure.

        :return: None
        """

        # Clear
        self.axes.cla()
        try:
            self.figure.clf()
        except KeyError:
            FlatCAMApp.App.log.warning("KeyError in MPL figure.clf()")

        # Re-build
        self.figure.add_axes(self.axes)
        self.axes.set_aspect(1)
        self.axes.grid(True)

        # Re-draw
        self.canvas.draw_idle()

    def adjust_axes(self, xmin, ymin, xmax, ymax):
        """
        Adjusts all axes while maintaining the use of the whole canvas
        and an aspect ratio to 1:1 between x and y axes. The parameters are an original
        request that will be modified to fit these restrictions.

        :param xmin: Requested minimum value for the X axis.
        :type xmin: float
        :param ymin: Requested minimum value for the Y axis.
        :type ymin: float
        :param xmax: Requested maximum value for the X axis.
        :type xmax: float
        :param ymax: Requested maximum value for the Y axis.
        :type ymax: float
        :return: None
        """

        # FlatCAMApp.App.log.debug("PC.adjust_axes()")

        width = xmax - xmin
        height = ymax - ymin
        try:
            r = width / height
        except ZeroDivisionError:
            FlatCAMApp.App.log.error("Height is %f" % height)
            return
        canvas_w, canvas_h = self.canvas.get_width_height()
        canvas_r = float(canvas_w) / canvas_h
        x_ratio = float(self.x_margin) / canvas_w
        y_ratio = float(self.y_margin) / canvas_h

        if r > canvas_r:
            ycenter = (ymin + ymax) / 2.0
            newheight = height * r / canvas_r
            ymin = ycenter - newheight / 2.0
            ymax = ycenter + newheight / 2.0
        else:
            xcenter = (xmax + xmin) / 2.0
            newwidth = width * canvas_r / r
            xmin = xcenter - newwidth / 2.0
            xmax = xcenter + newwidth / 2.0

        # Adjust axes
        for ax in self.figure.get_axes():
            if ax._label != 'base':
                ax.set_frame_on(False)  # No frame
                ax.set_xticks([])  # No tick
                ax.set_yticks([])  # No ticks
                ax.patch.set_visible(False)  # No background
                ax.set_aspect(1)
            ax.set_xlim((xmin, xmax))
            ax.set_ylim((ymin, ymax))
            ax.set_position([x_ratio, y_ratio, 1 - 2 * x_ratio, 1 - 2 * y_ratio])

        # Sync re-draw to proper paint on form resize
        self.canvas.draw()

        ##### Temporary place-holder for cached update #####
        self.update_screen_request.emit([0, 0, 0, 0, 0])

    def auto_adjust_axes(self, *args):
        """
        Calls ``adjust_axes()`` using the extents of the base axes.

        :rtype : None
        :return: None
        """

        xmin, xmax = self.axes.get_xlim()
        ymin, ymax = self.axes.get_ylim()
        self.adjust_axes(xmin, ymin, xmax, ymax)

    def zoom(self, factor, center=None):
        """
        Zooms the plot by factor around a given
        center point. Takes care of re-drawing.

        :param factor: Number by which to scale the plot.
        :type factor: float
        :param center: Coordinates [x, y] of the point around which to scale the plot.
        :type center: list
        :return: None
        """

        xmin, xmax = self.axes.get_xlim()
        ymin, ymax = self.axes.get_ylim()
        width = xmax - xmin
        height = ymax - ymin

        if center is None or center == [None, None]:
            center = [(xmin + xmax) / 2.0, (ymin + ymax) / 2.0]

        # For keeping the point at the pointer location
        relx = (xmax - center[0]) / width
        rely = (ymax - center[1]) / height

        new_width = width / factor
        new_height = height / factor

        xmin = center[0] - new_width * (1 - relx)
        xmax = center[0] + new_width * relx
        ymin = center[1] - new_height * (1 - rely)
        ymax = center[1] + new_height * rely

        # Adjust axes
        for ax in self.figure.get_axes():
            ax.set_xlim((xmin, xmax))
            ax.set_ylim((ymin, ymax))

        # Async re-draw
        self.canvas.draw_idle()

        ##### Temporary place-holder for cached update #####
        self.update_screen_request.emit([0, 0, 0, 0, 0])

    def pan(self, x, y):
        xmin, xmax = self.axes.get_xlim()
        ymin, ymax = self.axes.get_ylim()
        width = xmax - xmin
        height = ymax - ymin

        # Adjust axes
        for ax in self.figure.get_axes():
            ax.set_xlim((xmin + x * width, xmax + x * width))
            ax.set_ylim((ymin + y * height, ymax + y * height))

        # Re-draw
        self.canvas.draw_idle()

        ##### Temporary place-holder for cached update #####
        self.update_screen_request.emit([0, 0, 0, 0, 0])

    def new_axes(self, name):
        """
        Creates and returns an Axes object attached to this object's Figure.

        :param name: Unique label for the axes.
        :return: Axes attached to the figure.
        :rtype: Axes
        """

        return self.figure.add_axes([0.05, 0.05, 0.9, 0.9], label=name)

    def on_scroll(self, event):
        """
        Scroll event handler.

        :param event: Event object containing the event information.
        :return: None
        """

        # So it can receive key presses
        # self.canvas.grab_focus()
        self.canvas.setFocus()

        # Event info
        # z, direction = event.get_scroll_direction()

        if self.key is None:

            if event.button == 'up':
                self.zoom(1.5, self.mouse)
            else:
                self.zoom(1 / 1.5, self.mouse)
            return

        if self.key == 'shift':

            if event.button == 'up':
                self.pan(0.3, 0)
            else:
                self.pan(-0.3, 0)
            return

        if self.key == 'control':

            if event.button == 'up':
                self.pan(0, 0.3)
            else:
                self.pan(0, -0.3)
            return

    def on_mouse_press(self, event):

        # Check for middle mouse button press
        if event.button == self.app.mouse_pan_button:

            # Prepare axes for pan (using 'matplotlib' pan function)
            self.pan_axes = []
            for a in self.figure.get_axes():
                if (event.x is not None and event.y is not None and a.in_axes(event) and
                        a.get_navigate() and a.can_pan()):
                    a.start_pan(event.x, event.y, 1)
                    self.pan_axes.append(a)

            # Set pan view flag
            if len(self.pan_axes) > 0: self.panning = True;

    def on_mouse_release(self, event):

        # Check for middle mouse button release to complete pan procedure
        if event.button == self.app.mouse_pan_button:
            for a in self.pan_axes:
                a.end_pan()

            # Clear pan flag
            self.panning = False

    def on_mouse_move(self, event):
        """
        Mouse movement event hadler. Stores the coordinates. Updates view on pan.

        :param event: Contains information about the event.
        :return: None
        """
        self.mouse = [event.xdata, event.ydata]

        # Update pan view on mouse move
        if self.panning is True:
            for a in self.pan_axes:
                a.drag_pan(1, event.key, event.x, event.y)

            # Async re-draw (redraws only on thread idle state, uses timer on backend)
            self.canvas.draw_idle()

            ##### Temporary place-holder for cached update #####
            self.update_screen_request.emit([0, 0, 0, 0, 0])

    def on_draw(self, renderer):

        # Store background on canvas redraw
        self.background = self.canvas.copy_from_bbox(self.axes.bbox)

    def get_axes_pixelsize(self):
        """
        Axes size in pixels.

        :return: Pixel width and height
        :rtype: tuple
        """
        bbox = self.axes.get_window_extent().transformed(self.figure.dpi_scale_trans.inverted())
        width, height = bbox.width, bbox.height
        width *= self.figure.dpi
        height *= self.figure.dpi
        return width, height

    def get_density(self):
        """
        Returns unit length per pixel on horizontal
        and vertical axes.

        :return: X and Y density
        :rtype: tuple
        """
        xpx, ypx = self.get_axes_pixelsize()

        xmin, xmax = self.axes.get_xlim()
        ymin, ymax = self.axes.get_ylim()
        width = xmax - xmin
        height = ymax - ymin

        return width / xpx, height / ypx
예제 #3
0
class MainWindow(QtGui.QMainWindow, OnClick, OnMotion):
    def __init__(self):
        super(MainWindow, self).__init__()
        uic.loadUi("PeakInspector_layout.ui", self)
        self.setWindowTitle("PeakInspector (beta) (c) A.Salykin - Masaryk University - CC-BY-SA 4.0")

        # main variable:
        self.multiple_data_sets = pd.DataFrame()  # Initialise the final dataframe to export to Excel

        self.coordinates = []
        self.area = []
        self.amplitudes = []
        self.amplitude_line_coordinates = []
        self.left_peak_border = []
        self.right_peak_border = []
        self.pickable_artists_pts_AX2 = []
        self.pickable_artists_pts_AX3 = []
        self.pickable_artists_lns_AX3 = []
        self.pickable_artists_fill_AX3 = []
        self.pickable_artists_plb_AX3 = []
        self.pickable_artists_prb_AX3 = []
        self.pickable_artists_lnsP_AX3 = []

        self.left_border = []
        self.right_border = []

        # Connect buttons to class methods:
        self.BtnLoadFile.clicked.connect(self.load_file)
        self.BtnReplot.clicked.connect(self.replot_graph)
        self.chbxDotPickEnable.stateChanged.connect(self.dot_pick_enable)
        self.BtnSaveCurrent.clicked.connect(self.coordinates_analysis)
        self.BtnSaveFullDataset.clicked.connect(self.save_data)
        self.BoxMplPlotStyle.currentIndexChanged.connect(self.mpl_style_change)

        style.use(self.BoxMplPlotStyle.currentText())

        self.BtnLoadFile.setStyleSheet("background-color: #7CF2BD")
        self.BtnReplot.setStyleSheet("background-color: #FAF6F2")
        self.BtnSaveCurrent.setStyleSheet("background-color: #FAF6F2")
        self.BtnSaveFullDataset.setStyleSheet("background-color: #FAF6F2")

        # Initialise figure instance
        self.fig = plt.figure()
        self.show()

    def addmpl(self, ):
        self.canvas = FigureCanvas(self.fig)
        self.toolbar = NavigationToolbar(self.canvas, self.CanvasWidget, coordinates=True)
        self.CanvasLayout.addWidget(self.toolbar)
        self.CanvasLayout.addWidget(self.canvas)
        if self.chbxDotPickEnable.isChecked():
            self.cid_click = self.canvas.mpl_connect('button_press_event', self.on_click)
            self.cid_motion = self.canvas.mpl_connect('motion_notify_event', self.on_motion)
        self.canvas.draw()

    def rmmpl(self, ):  #
        self.canvas.mpl_disconnect(self.cid_click)
        self.canvas.mpl_disconnect(self.cid_motion)
        self.CanvasLayout.removeWidget(self.canvas)
        self.canvas.close()
        self.CanvasLayout.removeWidget(self.toolbar)
        self.toolbar.close()

    def dot_pick_enable(self, ):  # if checked, user can choose peaks
        try:  # if figure and canvas is initiated
            if self.chbxDotPickEnable.isChecked():
                self.cid_click = self.canvas.mpl_connect('button_press_event', self.on_click)
                self.cid_motion = self.canvas.mpl_connect('motion_notify_event', self.on_motion)
            else:
                self.canvas.mpl_disconnect(self.cid_click)
                self.canvas.mpl_disconnect(self.cid_motion)
        except:
            message = MessageBox()
            message.about(self, 'Warning!', "File was not loaded! \n Please be sure that your file has \
                \n 1) 1 or 2 columns; \n 2) check headers, footers and delimeter \n and try again.")

    def load_file(self, ):
        self.BtnLoadFile.setStyleSheet("background-color: #FAF6F2")
        # Check if we already have some file loaded - then remove canvas
        if hasattr(self, 'cid_click'):
            self.rmmpl()

        # Make sure that np data arrays and lists from previous dataset are empty
        self.x = np.empty([])
        self.y = np.empty([])
        self.clear_data()

        name = QtGui.QFileDialog.getOpenFileName(self, 'Open File')
        if not name:
            return self.import_error()

        # get more readable file name for graph title
        try:
            slash_index = self.find_character(name, '/')
            dot_index = self.find_character(name, '.')
            self.graph_name = name[slash_index[-1] + 1:dot_index[-1]]
        except:
            self.graph_name = name[-10:]

        skip_header_rows = self.BoxSkipHeader.value()
        skip_footer_rows = self.BoxSkipFooter.value()

        if self.BoxDelimeterChoice.currentText() == 'Tab':
            delimiter = "\t"
        elif self.BoxDelimeterChoice.currentText() == 'Space':
            delimiter = " "
        elif self.BoxDelimeterChoice.currentText() == 'Comma':
            delimiter = ","
        elif self.BoxDelimeterChoice.currentText() == 'Dot':
            delimiter = "."

        # unpack file
        try:  # if data file has 2 columns
            self.x, self.y = np.genfromtxt(name,
                                 delimiter = delimiter,
                                 skip_header = skip_header_rows,
                                 skip_footer = skip_footer_rows,
                                 unpack = True)
            if len(self.y) < 100:
                return self.import_error()
            return self.process_opened_file()

        except:  # if data file has 1 column
            self.y = np.genfromtxt(name,
                              skip_header=skip_header_rows,
                              skip_footer=skip_footer_rows, unpack=True)
            if len(self.y) < 100:
                return self.import_error()
            self.x = np.arange(0, len(self.y), 1)
            return self.process_opened_file()

    def import_error(self,):
        message = MessageBox()
        message.about(self, 'Warning!', "Data were not loaded. \n Please, be sure that:\n "
                                                "1. Data have 1 or 2 columns.\n"
                                                "2. Data are longer than 100 points.\n"
                                                "3. Delimiter is correctly specified.\n"
                                                "4. Rows in data contain only numeric values\n")

    def process_opened_file(self, ):
        self.x = tuple(self.x)
        self.data_preprocessing(self.y)
        self.baseline_calculation()
        self.plot_data()

    def data_preprocessing(self, data_to_preprocess):
        try:
            # Detrend dataset
            if self.chbxDetrendData.isChecked():
                self.data_detrended = sig.detrend(data_to_preprocess)
            else:
                self.data_detrended = data_to_preprocess

            # Application of Savitzkyi-Golay filter for data smoothing
            sg_window_frame = self.BoxSGwindowFrame.value()
            sg_polynom_degree = self.BoxSGpolynomDegree.value()
            self.data_after_filter = sig.savgol_filter(self.data_detrended, sg_window_frame, sg_polynom_degree)
        except:
            message = MessageBox()
            message.about(self, 'Warning!',
                          "Not possible to detrend and/or smooth data! \n Please check your dataset and try again.")

    def baseline_calculation(self, ):
        '''
        Calculate baseline of detrended data and add it to dataset for baseline to be equal 0
        '''
        databaseline = min(self.data_after_filter)
        if self.chbxDetrendData.isChecked():
            self.data_after_filter = [i + abs(databaseline) for i in self.data_after_filter]
            self.data_detrended = [i + abs(databaseline) for i in self.data_detrended]
        else:
            self.data_after_filter = [i - abs(databaseline) for i in self.data_after_filter]
            self.data_detrended = [i - abs(databaseline) for i in self.data_detrended]

    def interpolation(self, p1, p2, left_index, right_index):
        f = interpolate.interp1d([p1[0], p2[0]], [p1[1], p2[1]])
        num = len(self.x[left_index:right_index])
        xx = np.linspace(self.x[left_index], self.x[right_index], num)
        return f(xx)

    def plot_data(self, ):
        if self.BoxPlotCustomStyle.currentText() == 'Line':
            plot_style_custom = '-'
            marker_size = 1
        elif self.BoxPlotCustomStyle.currentText() == 'Line & small markers':
            plot_style_custom = 'o-'
            marker_size = 3
        elif self.BoxPlotCustomStyle.currentText() == 'Line & big markers':
            plot_style_custom = 'o-'
            marker_size = 6
        elif self.BoxPlotCustomStyle.currentText() == 'Small markers':
            plot_style_custom = 'o'
            marker_size = 3
        elif self.BoxPlotCustomStyle.currentText() == 'Big markers':
            plot_style_custom = 'o'
            marker_size = 6

        font_size = 14

        self.ax1 = plt.subplot2grid((4, 1), (0, 0), rowspan=1, colspan=1)
        plt.title(self.graph_name)
        self.ax1.plot(self.x, self.y, plot_style_custom, ms=marker_size, linewidth=1)  # plot raw data
        plt.ylabel('Original raw data', fontsize=font_size)

        self.ax2 = plt.subplot2grid((4, 1), (1, 0), rowspan=1, colspan=1)
        self.ax2.plot(self.x, self.data_detrended, plot_style_custom, ms=marker_size, linewidth=1)  # plot detrended data
        plt.ylabel('Detrended data', fontsize=font_size)

        self.ax3 = plt.subplot2grid((4, 1), (2, 0), rowspan=2, colspan=1, sharex=self.ax2, sharey=self.ax2)
        self.ax3.plot(self.x, self.data_after_filter, plot_style_custom, ms=marker_size, linewidth=1)  # plot filtered detrended data
        self.baselinePlotArtist = self.ax3.plot([self.x[0], self.x[-1]], [0, 0], 'k', linewidth=1)  # plot baseline
        plt.ylabel('Savitzky-Golay filter \n for detrended data', fontsize=font_size)
        self.ax3.set_xlim(0, self.x[-1])
        plt.xlabel('Time, sec')

        self.addmpl()

    def replot_graph(self, ):
        self.clear_data()
        self.rmmpl()
        self.data_preprocessing(self.y)
        self.baseline_calculation()
        self.plot_data()

    def coordinates_analysis(self, ):
        """
        Main function
        """
        coord_x, coord_y = zip(*self.coordinates)
        leftpb_x, leftpb_y = zip(*self.left_peak_border)
        rightpb_x, rightpb_y= zip(*self.right_peak_border)

        # absolute amplitude % and MAX
        relative_amplitude = []
        ampl_max = max(self.amplitudes)
        relative_amplitude[:] = [(i / ampl_max) for i in self.amplitudes]

        # create temporal Pandas DataFrame for sorting and calculation:
        temp_dataset = list(
            zip(coord_x, self.amplitudes, relative_amplitude, leftpb_x, leftpb_y, rightpb_x, rightpb_y, self.area))
        df = pd.DataFrame(data=temp_dataset,
                          columns=['Peak Time',
                                   'Amplitude',
                                   'Relative Amplitude \n (F/Fmax)',
                                   'Peak Start Time',
                                   'Peak Start Ordinate',
                                   'Peak Stop Time',
                                   'Peak Stop Ordinate',
                                   'Area'])

        # Sort data in DataFrame according to the time of peak appearance
        df_sorted = df.sort_values(['Peak Time'], ascending=True)
        df_sorted.index = range(0, len(df_sorted))  # reset indexing

        # calculate periods
        periods = []
        for i in range(1, len(df_sorted['Peak Time'])):
            periods.append(df_sorted.at[i, 'Peak Time'] - df_sorted.at[i - 1, 'Peak Time'])
        periods.insert(0, np.nan)  # add placeholder because len(periods)=len(peaks)-1

        # calculate frequencies based on calculated periods
        frequencies = []
        frequencies[:] = [(1 / i) for i in periods]

        # Analise peak start - stop time (left and right peak borders)
        peak_full_time = []
        for i in range(0, len(df_sorted['Peak Time']), 1):
            peak_full_time.append(df_sorted.at[i, 'Peak Stop Time'] - df_sorted.at[i, 'Peak Start Time'])
        peak_up_time = []
        for i in range(0, len(df_sorted['Peak Time']), 1):
            peak_up_time.append(df_sorted.at[i, 'Peak Time'] - df_sorted.at[i, 'Peak Start Time'])
        peak_down_time = []
        for i in range(0, len(df_sorted['Peak Time']), 1):
            peak_down_time.append(df_sorted.at[i, 'Peak Stop Time'] - df_sorted.at[i, 'Peak Time'])

        # Compute area under the peak using the composite trapezoidal rule.
        peak_area = []
        for i in range(0, len(df_sorted['Peak Time']), 1):
            peak_area.append(np.trapz(df_sorted.at[i, 'Area']))

        # Analise the peak decay area
        half_decay_time = []
        half_decay_amplitude = []
        for i in range(0, len(df_sorted['Peak Time']), 1):
            half_decay_ampl = df_sorted.at[i, 'Amplitude'] / 2  # calculate the half of the amplitude
            peak_index = self.x.index(df_sorted.at[i, 'Peak Time'])  # find index of the peak time
            stop_idx = self.x.index(df_sorted.at[i, 'Peak Stop Time'])  # find index of the right peak border
            data_decay_region = self.data_after_filter[peak_index:stop_idx]  # determine the amplitude region where to search for halftime decay index
            time_decay_region = self.x[peak_index:stop_idx]
            half_decay_idx = (np.abs(data_decay_region - half_decay_ampl)).argmin()  # find the closet value in data_decay_region that corresponds to the half amplitude

            half_decay_amplitude.append(half_decay_ampl)
            half_decay_time.append(time_decay_region[half_decay_idx] - df_sorted.at[i, 'Peak Time'])

        # Compute amplitude normalised to the baseline
        normalised_amplitude = []
        sg_window_frame = self.BoxSGwindowFrame.value()
        sg_polynom_degree = self.BoxSGpolynomDegree.value()
        orig_data_filtered = sig.savgol_filter(self.y, sg_window_frame, sg_polynom_degree)
        for i in range(0, len(df_sorted['Peak Time']), 1):
            start_idx = self.x.index(df_sorted.at[i, 'Peak Start Time'])
            F0 = orig_data_filtered[start_idx]
            amplitude_normed_computation = df_sorted.at[i, 'Amplitude'] / F0
            normalised_amplitude.append(amplitude_normed_computation)

        # normalised amplitude %
        relative_normalised_amplitude = []
        maxATB = max(normalised_amplitude)
        relative_normalised_amplitude[:] = [(i / maxATB) for i in normalised_amplitude]

        # normalised amplitude MAX
        normalised_amplitude_max = list(range(0, len(df_sorted['Peak Time']) - 1))
        normalised_amplitude_max[:] = [np.nan for _ in normalised_amplitude_max]
        normalised_amplitude_max.insert(0, maxATB)

        # add file name as first column
        file_name = list(range(0, len(df_sorted['Peak Time']) - 1))
        file_name[:] = [np.nan for _ in file_name]
        file_name.insert(0, self.graph_name)

        # add maximum amplitude
        absolute_amplitude_max = list(range(0, len(df_sorted['Peak Time']) - 1))
        absolute_amplitude_max[:] = [np.nan for _ in absolute_amplitude_max]
        absolute_amplitude_max.insert(0, max(df_sorted['Amplitude']))

        # peak sorting
        big_peaks_number = [p for p in self.amplitudes if (p > ampl_max * 0.66)]
        medium_peaks_number = [p for p in self.amplitudes if (p > ampl_max * 0.33 and p <= ampl_max * 0.66)]
        small_peaks_number = [p for p in self.amplitudes if (p > 0 and p <= ampl_max * 0.33)]

        big_peaks_frequency = list(range(0, len(df_sorted['Peak Time']) - 1))
        big_peaks_frequency[:] = [np.nan for _ in big_peaks_frequency]
        big_peaks_frequency.insert(0, len(big_peaks_number) / (self.x[-1] - self.x[0]))

        medium_peaks_frequency = list(range(0, len(df_sorted['Peak Time']) - 1))
        medium_peaks_frequency[:] = [np.nan for _ in medium_peaks_frequency]
        medium_peaks_frequency.insert(0, len(medium_peaks_number) / (self.x[-1] - self.x[0]))

        small_peaks_frequency = list(range(0, len(df_sorted['Peak Time']) - 1))
        small_peaks_frequency[:] = [np.nan for _ in small_peaks_frequency]
        small_peaks_frequency.insert(0, len(small_peaks_number) / (self.x[-1] - self.x[0]))

        final_dataset = list(zip(file_name,
                                df_sorted['Peak Time'],
                                df_sorted['Amplitude'],
                                df_sorted['Relative Amplitude \n (F/Fmax)'],
                                absolute_amplitude_max,
                                normalised_amplitude,
                                relative_normalised_amplitude,
                                normalised_amplitude_max,
                                periods,
                                frequencies,
                                half_decay_time,
                                half_decay_amplitude,
                                df_sorted['Peak Start Time'],
                                df_sorted['Peak Start Ordinate'],
                                df_sorted['Peak Stop Time'],
                                df_sorted['Peak Stop Ordinate'],
                                peak_up_time,
                                peak_down_time,
                                peak_full_time,
                                peak_area,
                                big_peaks_frequency,
                                medium_peaks_frequency,
                                small_peaks_frequency))

        final_dataframe = pd.DataFrame(data=final_dataset,
                                       columns=['File name',
                                                'Peak time',
                                                'Absolute amplitude',
                                                'Absolute amplitude (%)',
                                                'Absolute amplitude MAX',
                                                'Normalised amplitude',
                                                'Normalised amplitude (%)',
                                                'Normalised amplitude MAX',
                                                'Period',
                                                'Frequency',
                                                'Half-decay time',
                                                'Half-decay amplitude',
                                                'Start time',
                                                'Start ordinate',
                                                'Stop time',
                                                'Stop ordinate',
                                                'Ascending time',
                                                'Decay time',
                                                'Full peak time',
                                                'AUC',
                                                'Big peaks, Hz',
                                                'Mid peaks, Hz',
                                                'Small peaks, Hz'])

        # specify data for export acording to the settings tab in GUI
        # and append current analysed dataset to existing ones
        try:
            columns_to_delete_for_export = []
            if not self.chbxFileName.isChecked(): columns_to_delete_for_export.append('File name')
            if not self.chbxPeakTime.isChecked(): columns_to_delete_for_export.append('Peak time')
            if not self.chbxAmplAbs.isChecked(): columns_to_delete_for_export.append('Absolute amplitude')
            if not self.chbxAmplAbsRel.isChecked(): columns_to_delete_for_export.append('Absolute amplitude (%)')
            if not self.chbxAmplAbsMax.isChecked(): columns_to_delete_for_export.append('Absolute amplitude MAX')
            if not self.chbxAmplNorm.isChecked(): columns_to_delete_for_export.append('Normalised amplitude')
            if not self.chbxAmplNormRel.isChecked(): columns_to_delete_for_export.append('Normalised amplitude (%)')
            if not self.chbxAmplNormMax.isChecked(): columns_to_delete_for_export.append('Normalised amplitude MAX')
            if not self.chbxPeriod.isChecked(): columns_to_delete_for_export.append('Period')
            if not self.chbxFreq.isChecked(): columns_to_delete_for_export.append('Frequency')
            if not self.chbxHalfDecayTime.isChecked(): columns_to_delete_for_export.append('Half-decay time')
            if not self.chbxHalfDecayAmpl.isChecked(): columns_to_delete_for_export.append('Half-decay amplitude')
            if not self.chbxLeftBorderTime.isChecked(): columns_to_delete_for_export.append('Start time')
            if not self.chbxLeftBorder.isChecked(): columns_to_delete_for_export.append('Start ordinate')
            if not self.chbxRightBorderTime.isChecked(): columns_to_delete_for_export.append('Stop time')
            if not self.chbxRightBorder.isChecked(): columns_to_delete_for_export.append('Stop ordinate')
            if not self.chbxTimeToPeak.isChecked(): columns_to_delete_for_export.append('Ascending time')
            if not self.chbxDecayTime.isChecked(): columns_to_delete_for_export.append('Decay time')
            if not self.chbxFullPeakTime.isChecked(): columns_to_delete_for_export.append('Full peak time')
            if not self.chbxAUC.isChecked(): columns_to_delete_for_export.append('AUC')
            if not self.chbxSmallPeaks.isChecked(): columns_to_delete_for_export.append('Big peaks, Hz')
            if not self.chbxMidPeaks.isChecked(): columns_to_delete_for_export.append('Mid peaks, Hz')
            if not self.chbxBigPeaks.isChecked(): columns_to_delete_for_export.append('Small peaks, Hz')
            final_dataframe.drop(columns_to_delete_for_export, axis=1, inplace=True)

            self.multiple_data_sets = self.multiple_data_sets.append(final_dataframe)

            if self.chbxSaveFig.isChecked():
                os.makedirs('_Figures', exist_ok=True)
                dpi = self.BoxDPI.value()
                plt.savefig(os.path.join('_Figures', 'Fig_{figName}.png'.format(figName=self.graph_name)), dpi=dpi)

            del df
            del df_sorted
            del final_dataframe

            dialog = MessageBox.question(self, '', "Current dataset was analysed \n and added to previous ones (if exist). \n Would you like to load next file? ",
                                         QtGui.QMessageBox.Yes, QtGui.QMessageBox.No)
            if dialog == QtGui.QMessageBox.Yes:
                self.load_file()
            else:
                self.rmmpl()
                self.BtnSaveFullDataset.setStyleSheet("background-color: #7CF2BD")
                self.BtnLoadFile.setStyleSheet("background-color: #7CF2BD")

        except:
            message = MessageBox()
            message.about(self, 'Warning!', "Data were not added to existing dataset. \n Plese be sure that you did not change the output settings.")

    def save_data(self, ):
        try:
            file_name = QtGui.QFileDialog.getSaveFileName(self, 'Save file')
            writer = pd.ExcelWriter('{}.xlsx'.format(file_name))
            self.multiple_data_sets.to_excel(writer, index=True, sheet_name='Results')
            writer.sheets['Results'].set_zoom(80)
            writer.sheets['Results'].set_column('A:A', 5)
            writer.sheets['Results'].set_column('B:X', 23)
            writer.save()

            message = MessageBox()
            message.about(self, 'Data saved', "Data were saved!")
            self.multiple_data_sets = pd.DataFrame()
            self.BtnSaveFullDataset.setStyleSheet("background-color: #FAF6F2")
            self.BtnLoadFile.setStyleSheet("background-color: #7CF2BD")
        except:
            message = MessageBox()
            message.about(self, 'Warning!', "Data were not exported to Excel! \n Please try again.")

    def mpl_style_change(self, ):
        style.use(self.BoxMplPlotStyle.currentText())

    def clear_data(self):
        self.coordinates = []
        self.area = []
        self.amplitudes = []
        self.amplitude_line_coordinates = []
        self.left_peak_border = []
        self.right_peak_border = []
        self.pickable_artists_pts_AX2 = []
        self.pickable_artists_pts_AX3 = []
        self.pickable_artists_lns_AX3 = []
        self.pickable_artists_fill_AX3 = []
        self.pickable_artists_plb_AX3 = []
        self.pickable_artists_prb_AX3 = []
        self.pickable_artists_lnsP_AX3 = []

    def closeEvent(self, event):
        """Exchange default event to add a dialog"""
        if self.multiple_data_sets.empty:
            reply = MessageBox.question(self, 'Warning!',
                                        "Are you sure to quit?", QtGui.QMessageBox.Yes, QtGui.QMessageBox.No)
        else:
            reply = MessageBox.question(self, 'Warning!',
                                        "You have unsaved analysed data! \n Are you sure to quit?",
                                        QtGui.QMessageBox.Yes, QtGui.QMessageBox.No)

        if reply == QtGui.QMessageBox.Yes:
            event.accept()
        else:
            event.ignore()

    @staticmethod
    def find_character(s, ch):  # for graph title
        return [i for i, ltr in enumerate(s) if ltr == ch]
예제 #4
0
class ProfileDockWidget(QDockWidget):
    """
    DockWidget class to display the profile
    """

    closeSignal = pyqtSignal()

    def __init__(self, iface):
        """
        Constructor
        :param iface: interface
        """
        QDockWidget.__init__(self)
        self.setWindowTitle(QCoreApplication.translate("VDLTools", "Profile Tool"))
        self.resize(1024, 400)
        self.__iface = iface
        self.__canvas = self.__iface.mapCanvas()
        self.__types = ['PDF', 'PNG']  # ], 'SVG', 'PS']
        self.__libs = []
        if Qwt5_loaded:
            self.__lib = 'Qwt5'
            self.__libs.append('Qwt5')
            if matplotlib_loaded:
                self.__libs.append('Matplotlib')
        elif matplotlib_loaded:
            self.__lib = 'Matplotlib'
            self.__libs.append('Matplotlib')
        else:
            self.__lib = None
            self.__iface.messageBar().pushMessage(
                QCoreApplication.translate("VDLTools", "No graph lib available (qwt5 or matplotlib)"),
                level=QgsMessageBar.CRITICAL, duration=0)

        self.__doTracking = False
        self.__vline = None

        self.__profiles = None
        self.__numLines = None
        self.__mntPoints = None

        self.__marker = None
        self.__tabmouseevent = None

        self.__contentWidget = QWidget()
        self.setWidget(self.__contentWidget)

        self.__boxLayout = QHBoxLayout()
        self.__contentWidget.setLayout(self.__boxLayout)

        self.__plotFrame = QFrame()
        self.__frameLayout = QHBoxLayout()
        self.__plotFrame.setLayout(self.__frameLayout)

        self.__printLayout = QHBoxLayout()
        self.__printLayout.addWidget(self.__plotFrame)

        self.__legendLayout = QVBoxLayout()
        self.__printLayout.addLayout(self.__legendLayout)

        self.__printWdg = QWidget()
        self.__printWdg.setLayout(self.__printLayout)

        self.__plotWdg = None
        self.__changePlotWidget()

        size = QSize(150, 20)

        self.__boxLayout.addWidget(self.__printWdg)

        self.__vertLayout = QVBoxLayout()

        self.__libCombo = QComboBox()
        self.__libCombo.setFixedSize(size)
        self.__libCombo.addItems(self.__libs)
        self.__vertLayout.addWidget(self.__libCombo)
        self.__libCombo.currentIndexChanged.connect(self.__setLib)

        self.__maxLabel = QLabel("y max")
        self.__maxLabel.setFixedSize(size)
        self.__vertLayout.addWidget(self.__maxLabel)
        self.__maxSpin = QSpinBox()
        self.__maxSpin.setFixedSize(size)
        self.__maxSpin.setRange(-10000, 10000)
        self.__maxSpin.valueChanged.connect(self.__reScalePlot)
        self.__vertLayout.addWidget(self.__maxSpin)
        self.__vertLayout.insertSpacing(10, 20)

        self.__minLabel = QLabel("y min")
        self.__minLabel.setFixedSize(size)
        self.__vertLayout.addWidget(self.__minLabel)
        self.__minSpin = QSpinBox()
        self.__minSpin.setFixedSize(size)
        self.__minSpin.setRange(-10000, 10000)
        self.__minSpin.valueChanged.connect(self.__reScalePlot)
        self.__vertLayout.addWidget(self.__minSpin)
        self.__vertLayout.insertSpacing(10, 40)

        self.__typeCombo = QComboBox()
        self.__typeCombo.setFixedSize(size)
        self.__typeCombo.addItems(self.__types)
        self.__vertLayout.addWidget(self.__typeCombo)
        self.__saveButton = QPushButton(QCoreApplication.translate("VDLTools", "Save"))
        self.__saveButton.setFixedSize(size)
        self.__saveButton.clicked.connect(self.__save)
        self.__vertLayout.addWidget(self.__saveButton)

        self.__boxLayout.addLayout(self.__vertLayout)

        self.__maxSpin.setEnabled(False)
        self.__minSpin.setEnabled(False)

        self.__colors = []
        for cn in QColor.colorNames():
            qc = QColor(cn)
            val = qc.red() + qc.green() + qc.blue()
            if 0 < val < 450:
                self.__colors.append(cn)

    def __changePlotWidget(self):
        """
        When plot widget is change (qwt <-> matplotlib)
        """
        self.__activateMouseTracking(False)
        while self.__frameLayout.count():
            child = self.__frameLayout.takeAt(0)
            child.widget().deleteLater()
        self.__plotWdg = None

        if self.__lib == 'Qwt5':
            self.__plotWdg = QwtPlot(self.__plotFrame)
            sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
            sizePolicy.setHorizontalStretch(10)
            sizePolicy.setVerticalStretch(0)
            self.__plotWdg.setSizePolicy(sizePolicy)
            self.__plotWdg.setAutoFillBackground(False)
            # Decoration
            self.__plotWdg.setCanvasBackground(Qt.white)
            self.__plotWdg.plotLayout().setAlignCanvasToScales(False)
            self.__plotWdg.plotLayout().setSpacing(100)
            self.__plotWdg.plotLayout().setCanvasMargin(10, QwtPlot.xBottom)
            self.__plotWdg.plotLayout().setCanvasMargin(10, QwtPlot.yLeft)
            title = QwtText(QCoreApplication.translate("VDLTools", "Distance [m]"))
            title.setFont(QFont("Helvetica", 10))
            self.__plotWdg.setAxisTitle(QwtPlot.xBottom, title)
            title.setText(QCoreApplication.translate("VDLTools", "Elevation [m]"))
            title.setFont(QFont("Helvetica", 10))
            self.__plotWdg.setAxisTitle(QwtPlot.yLeft, title)
            self.__zoomer = QwtPlotZoomer(QwtPlot.xBottom, QwtPlot.yLeft, QwtPicker.DragSelection, QwtPicker.AlwaysOff,
                                          self.__plotWdg.canvas())
            self.__zoomer.setRubberBandPen(QPen(Qt.blue))
            grid = QwtPlotGrid()
            grid.setPen(QPen(QColor('grey'), 0, Qt.DotLine))
            grid.attach(self.__plotWdg)
            self.__frameLayout.addWidget(self.__plotWdg)

        elif self.__lib == 'Matplotlib':
            # __plotWdg.figure : matplotlib.figure.Figure
            fig = Figure((1.0, 1.0), linewidth=0.0, subplotpars=SubplotParams(left=0, bottom=0, right=1, top=1,
                                                                              wspace=0, hspace=0))

            font = {'family': 'arial', 'weight': 'normal', 'size': 12}
            rc('font', **font)

            rect = fig.patch
            rect.set_facecolor((0.9, 0.9, 0.9))

            self.__axes = fig.add_axes((0.07, 0.16, 0.92, 0.82))
            self.__axes.set_xbound(0, 1000)
            self.__axes.set_ybound(0, 1000)
            self.__manageMatplotlibAxe(self.__axes)
            self.__plotWdg = FigureCanvasQTAgg(fig)
            sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
            sizePolicy.setHorizontalStretch(0)
            sizePolicy.setVerticalStretch(0)
            self.__plotWdg.setSizePolicy(sizePolicy)
            self.__frameLayout.addWidget(self.__plotWdg)

    def setProfiles(self, profiles, numLines):
        """
        To set the profiles
        :param profiles: profiles : positions with elevations (for line and points)
        :param numLines: number of selected connected lines
        """
        self.__numLines = numLines
        self.__profiles = profiles
        if self.__lib == 'Matplotlib':
            self.__prepare_points()

    def __getLinearPoints(self):
        """
        To extract the linear points of the profile
        """
        profileLen = 0
        self.__profiles[0]['l'] = profileLen
        for i in range(0, len(self.__profiles)-1):
            x1 = float(self.__profiles[i]['x'])
            y1 = float(self.__profiles[i]['y'])
            x2 = float(self.__profiles[i+1]['x'])
            y2 = float(self.__profiles[i+1]['y'])
            profileLen += sqrt(((x2-x1)*(x2-x1)) + ((y2-y1)*(y2-y1)))
            self.__profiles[i+1]['l'] = profileLen

    def __getMnt(self, settings):
        """
        To get the MN data for the profile
        :param settings: settings containing MN url
        """
        if settings is None or settings.mntUrl is None or settings.mntUrl == "None":
            url = 'http://map.lausanne.ch/main/wsgi/profile.json'
        elif settings.mntUrl == "":
            return
        else:
            url = settings.mntUrl
        names = ['mnt', 'mns', 'toit_rocher']
        url += '?layers='
        pos = 0
        for name in names:
            if pos > 0:
                url += ','
            pos += 1
            url += name
        url += '&geom={"type":"LineString","coordinates":['
        pos = 0
        for i in range(len(self.__profiles)):
            if pos > 0:
                url += ','
            pos += 1
            url += '[' + str(self.__profiles[i]['x']) + ',' + str(self.__profiles[i]['y']) + ']'
        url = url + ']}&nbPoints=' + str(int(self.__profiles[len(self.__profiles)-1]['l']))
        try:
            response = urlopen(url)
            j = response.read()
            j_obj = json.loads(j)
            profile = j_obj['profile']
            self.__mntPoints = []
            self.__mntPoints.append(names)
            mnt_l = []
            mnt_z = []
            for p in range(len(names)):
                z = []
                mnt_z.append(z)
            for pt in profile:
                mnt_l.append(float(pt['dist']))
                values = pt['values']
                for p in range(len(names)):
                    if names[p] in values:
                        mnt_z[p].append(float(values[names[p]]))
                    else:
                        mnt_z[p].append(None)
            self.__mntPoints.append(mnt_l)
            self.__mntPoints.append(mnt_z)
        except HTTPError as e:
            self.__iface.messageBar().pushMessage(
                QCoreApplication.translate("VDLTools", "HTTP Error"),
                QCoreApplication.translate("VDLTools", "status error [" + str(e.code) + "] : " + e.reason),
                level=QgsMessageBar.CRITICAL, duration=0)
        except URLError as e:
            self.__iface.messageBar().pushMessage(
                QCoreApplication.translate("VDLTools", "URL Error"),
                e.reason, level=QgsMessageBar.CRITICAL, duration=0)

    def attachCurves(self, names, settings, usedMnts):
        """
        To attach the curves for the layers to the profile
        :param names: layers names
        """
        if (self.__profiles is None) or (self.__profiles == 0):
            return

        self.__getLinearPoints()
        if usedMnts is not None and (usedMnts[0] or usedMnts[1] or usedMnts[2]):
            self.__getMnt(settings)

        c = 0

        if self.__mntPoints is not None:
                for p in range(len(self.__mntPoints[0])):
                    if usedMnts[p]:
                        legend = QLabel("<font color='" + self.__colors[c] + "'>" + self.__mntPoints[0][p]
                                        + "</font>")
                        self.__legendLayout.addWidget(legend)

                        if self.__lib == 'Qwt5':

                            xx = [list(g) for k, g in itertools.groupby(self.__mntPoints[1],
                                                                        lambda x: x is None) if not k]
                            yy = [list(g) for k, g in itertools.groupby(self.__mntPoints[2][p], lambda x: x is None)
                                  if not k]

                            for j in range(len(xx)):
                                curve = QwtPlotCurve(self.__mntPoints[0][p])
                                curve.setData(xx[j], yy[j])
                                curve.setPen(QPen(QColor(self.__colors[c]), 3))
                                curve.attach(self.__plotWdg)

                        elif self.__lib == 'Matplotlib':
                            qcol = QColor(self.__colors[c])
                            self.__plotWdg.figure.get_axes()[0].plot(self.__mntPoints[1], self.__mntPoints[2][p],
                                                                     gid=self.__mntPoints[0][p], linewidth=3)
                            tmp = self.__plotWdg.figure.get_axes()[0].get_lines()
                            for t in range(len(tmp)):
                                if self.__mntPoints[0][p] == tmp[t].get_gid():
                                    tmp[c].set_color((old_div(qcol.red(), 255.0), old_div(qcol.green(), 255.0),
                                                      old_div(qcol.blue(), 255.0), old_div(qcol.alpha(), 255.0)))
                                    self.__plotWdg.draw()
                                    break
                        c += 1

        if 'z' in self.__profiles[0]:
            for i in range(len(self.__profiles[0]['z'])):
                if i < self.__numLines:
                    v = 0
                else:
                    v = i - self.__numLines + 1
                name = names[v]
                xx = []
                yy = []
                for prof in self.__profiles:
                    xx.append(prof['l'])
                    yy.append(prof['z'][i])

                for j in range(len(yy)):
                    if yy[j] is None:
                        xx[j] = None

                if i == 0 or i > (self.__numLines-1):
                    legend = QLabel("<font color='" + self.__colors[c] + "'>" + name + "</font>")
                    self.__legendLayout.addWidget(legend)

                if self.__lib == 'Qwt5':

                    # Split xx and yy into single lines at None values
                    xx = [list(g) for k, g in itertools.groupby(xx, lambda x: x is None) if not k]
                    yy = [list(g) for k, g in itertools.groupby(yy, lambda x: x is None) if not k]

                    # Create & attach one QwtPlotCurve per one single line
                    for j in range(len(xx)):
                        curve = QwtPlotCurve(name)
                        curve.setData(xx[j], yy[j])
                        curve.setPen(QPen(QColor(self.__colors[c]), 3))
                        if i > (self.__numLines-1):
                            curve.setStyle(QwtPlotCurve.Dots)
                            pen = QPen(QColor(self.__colors[c]), 8)
                            pen.setCapStyle(Qt.RoundCap)
                            curve.setPen(pen)
                        curve.attach(self.__plotWdg)

                elif self.__lib == 'Matplotlib':
                    qcol = QColor(self.__colors[c])
                    if i < self.__numLines:
                        self.__plotWdg.figure.get_axes()[0].plot(xx, yy, gid=name, linewidth=3)
                    else:
                        self.__plotWdg.figure.get_axes()[0].plot(xx, yy, gid=name, linewidth=5, marker='o',
                                                                 linestyle='None')
                    tmp = self.__plotWdg.figure.get_axes()[0].get_lines()
                    for t in range(len(tmp)):
                        if name == tmp[t].get_gid():
                            tmp[c].set_color((old_div(qcol.red(), 255.0), old_div(qcol.green(), 255.0),
                                              old_div(qcol.blue(), 255.0), old_div(qcol.alpha(), 255.0)))
                            self.__plotWdg.draw()
                            break
                c += 1

        # scaling this
        try:
            self.__reScalePlot(None, True)
        except:
            self.__iface.messageBar().pushMessage(
                QCoreApplication.translate("VDLTools", "Rescale problem... (trace printed)"),
                level=QgsMessageBar.CRITICAL, duration=0)
            print(
                QCoreApplication.translate("VDLTools", "rescale problem : "), sys.exc_info()[0], traceback.format_exc())
        if self.__lib == 'Qwt5':
            self.__plotWdg.replot()
        elif self.__lib == 'Matplotlib':
            self.__plotWdg.figure.get_axes()[0].redraw_in_frame()
            self.__plotWdg.draw()
            self.__activateMouseTracking(True)
            self.__marker.show()

    def __reScalePlot(self, value=None, auto=False):
        """
        To rescale the profile plot depending to the bounds
        """
        if (self.__profiles is None) or (self.__profiles == 0):
            self.__plotWdg.replot()
            return

        maxi = 0
        for i in range(len(self.__profiles)):
            if (int(self.__profiles[i]['l'])) > maxi:
                maxi = int(self.__profiles[i]['l']) + 1
        if self.__lib == 'Qwt5':
            self.__plotWdg.setAxisScale(2, 0, maxi, 0)
        elif self.__lib == 'Matplotlib':
            self.__plotWdg.figure.get_axes()[0].set_xbound(0, maxi)

        minimumValue = self.__minSpin.value()
        maximumValue = self.__maxSpin.value()

        # to set max y and min y displayed
        if auto:
            minimumValue = 1000000000
            maximumValue = -1000000000
            for i in range(len(self.__profiles)):
                if 'z' in self.__profiles[i]:
                    mini = self.__minTab(self.__profiles[i]['z'])
                    if int(mini) < minimumValue:
                        minimumValue = int(mini) - 1
                    maxi = self.__maxTab(self.__profiles[i]['z'])
                    if int(maxi) > maximumValue:
                        maximumValue = int(maxi) + 1
                if self.__mntPoints is not None:
                    for pts in self.__mntPoints[2]:
                        miniMnt = self.__minTab(pts)
                        if int(miniMnt) < minimumValue:
                            minimumValue = int(miniMnt) - 1
                        maxiMnt = self.__maxTab(pts)
                        if int(maxiMnt) > maximumValue:
                            maximumValue = int(maxiMnt) + 1
        self.__maxSpin.setValue(maximumValue)
        self.__minSpin.setValue(minimumValue)
        self.__maxSpin.setEnabled(True)
        self.__minSpin.setEnabled(True)

        if self.__lib == 'Qwt5':
            rect = QRectF(0, minimumValue, maxi, maximumValue-minimumValue)
            self.__zoomer.setZoomBase(rect)

        # to draw vertical lines
        for i in range(len(self.__profiles)):
            zz = []
            for j in range(self.__numLines):
                if self.__profiles[i]['z'][j] is not None:
                    zz.append(j)
            color = None
            if len(zz) == 2:
                width = 3
                color = QColor('red')
            else:
                width = 1

            if self.__lib == 'Qwt5':
                vertLine = QwtPlotMarker()
                vertLine.setLineStyle(QwtPlotMarker.VLine)
                pen = vertLine.linePen()
                pen.setWidth(width)
                if color is not None:
                    pen.setColor(color)
                vertLine.setLinePen(pen)
                vertLine.setXValue(self.__profiles[i]['l'])
                label = vertLine.label()
                label.setText(str(i))
                vertLine.setLabel(label)
                vertLine.setLabelAlignment(Qt.AlignLeft)
                vertLine.attach(self.__plotWdg)
            elif self.__lib == 'Matplotlib':
                self.__plotWdg.figure.get_axes()[0].vlines(self.__profiles[i]['l'], minimumValue, maximumValue,
                                                           linewidth=width)

        if minimumValue < maximumValue:
            if self.__lib == 'Qwt5':
                self.__plotWdg.setAxisScale(0, minimumValue, maximumValue, 0)
                self.__plotWdg.replot()
            elif self.__lib == 'Matplotlib':
                self.__plotWdg.figure.get_axes()[0].set_ybound(minimumValue, maximumValue)
                self.__plotWdg.figure.get_axes()[0].redraw_in_frame()
                self.__plotWdg.draw()

    @staticmethod
    def __minTab(tab):
        """
        To get the minimum value in a table
        :param tab: table to scan
        :return: minimum value
        """
        mini = 1000000000
        for t in tab:
            if t is None:
                continue
            if t < mini:
                mini = t
        return mini

    @staticmethod
    def __maxTab(tab):
        """
        To get the maximum value in a table
        :param tab: table to scan
        :return: maximum value
        """
        maxi = -1000000000
        for t in tab:
            if t is None:
                continue
            if t > maxi:
                maxi = t
        return maxi

    def __setLib(self):
        """
        To set the new widget library (qwt <-> matplotlib)
        """
        self.__lib = self.__libs[self.__libCombo.currentIndex()]
        self.__changePlotWidget()

    def __save(self):
        """
        To save the profile in a file, on selected format
        """
        idx = self.__typeCombo.currentIndex()
        if idx == 0:
            self.__outPDF()
        elif idx == 1:
            self.__outPNG()
        else:
            self.__iface.messageBar().pushMessage(
                QCoreApplication.translate("VDLTools", "Invalid index ") + str(idx),
                level=QgsMessageBar.CRITICAL, duration=0)

    def __outPDF(self):
        """
        To save the profile as pdf file
        """
        fileName = QFileDialog.getSaveFileName(
            self.__iface.mainWindow(), QCoreApplication.translate("VDLTools", "Save As"),
            QCoreApplication.translate("VDLTools", "Profile.pdf"),"Portable Document Format (*.pdf)")
        if fileName is not None:
            if self.__lib == 'Qwt5':
                printer = QPrinter()
                printer.setCreator(QCoreApplication.translate("VDLTools", "QGIS Profile Plugin"))
                printer.setOutputFileName(fileName)
                printer.setOutputFormat(QPrinter.PdfFormat)
                printer.setOrientation(QPrinter.Landscape)
                self.__plotWdg.print_(printer)
            elif self.__lib == 'Matplotlib':
                self.__plotWdg.figure.savefig(str(fileName))
            # printer = QPrinter()
            # printer.setCreator(QCoreApplication.translate("VDLTools", "QGIS Profile Plugin"))
            # printer.setOutputFileName(fileName)
            # printer.setOutputFormat(QPrinter.PdfFormat)
            # printer.setOrientation(QPrinter.Landscape)
            # printer.setPaperSize(QSizeF(self.__printWdg.size()), QPrinter.Millimeter)
            # printer.setFullPage(True)
            # self.__printWdg.render(printer)

    def __outPNG(self):
        """
        To save the profile as png file
        """
        fileName = QFileDialog.getSaveFileName(
            self.__iface.mainWindow(), QCoreApplication.translate("VDLTools", "Save As"),
            QCoreApplication.translate("VDLTools", "Profile.png"),"Portable Network Graphics (*.png)")
        if fileName is not None:
            QPixmap.grabWidget(self.__printWdg).save(fileName, "PNG")

    def clearData(self):
        """
        To clear the displayed data
        """
        if self.__profiles is None:
            return
        if self.__lib == 'Qwt5':
            self.__plotWdg.clear()
            self.__profiles = None
            temp1 = self.__plotWdg.itemList()
            for j in range(len(temp1)):
                if temp1[j].rtti() == QwtPlotItem.Rtti_PlotCurve:
                    temp1[j].detach()
        elif self.__lib == 'Matplotlib':
            self.__plotWdg.figure.get_axes()[0].cla()
            self.__manageMatplotlibAxe(self.__plotWdg.figure.get_axes()[0])
        self.__maxSpin.setEnabled(False)
        self.__minSpin.setEnabled(False)
        self.__maxSpin.setValue(0)
        self.__minSpin.setValue(0)

        # clear legend
        while self.__legendLayout.count():
            child = self.__legendLayout.takeAt(0)
            child.widget().deleteLater()

    def __manageMatplotlibAxe(self, axe):
        """
        To manage the axes for matplotlib library
        :param axe: the axes element
        """
        axe.grid()
        axe.tick_params(axis="both", which="major", direction="out", length=10, width=1, bottom=True, top=False,
                        left=True, right=False)
        axe.minorticks_on()
        axe.tick_params(axis="both", which="minor", direction="out", length=5, width=1, bottom=True, top=False,
                        left=True, right=False)
        axe.set_xlabel(QCoreApplication.translate("VDLTools", "Distance [m]"))
        axe.set_ylabel(QCoreApplication.translate("VDLTools", "Elevation [m]"))

    def __activateMouseTracking(self, activate):
        """
        To (de)activate the mouse tracking on the profile for matplotlib library
        :param activate: true to activate, false to deactivate
        """
        if activate:
            self.__doTracking = True
            self.__loadRubber()
            self.cid = self.__plotWdg.mpl_connect('motion_notify_event', self.__mouseevent_mpl)
        elif self.__doTracking:
            self.__doTracking = False
            self.__plotWdg.mpl_disconnect(self.cid)
            if self.__marker is not None:
                self.__canvas.scene().removeItem(self.__marker)
            try:
                if self.__vline is not None:
                    self.__plotWdg.figure.get_axes()[0].lines.remove(self.__vline)
                    self.__plotWdg.draw()
            except Exception as e:
                self.__iface.messageBar().pushMessage(
                    QCoreApplication.translate("VDLTools", "Tracking exception : ") + str(e),
                    level=QgsMessageBar.CRITICAL, duration=0)

    def __mouseevent_mpl(self, event):
        """
        To manage matplotlib mouse tracking event
        :param event: mouse tracking event
        """
        if event.xdata is not None:
            try:
                if self.__vline is not None:
                    self.__plotWdg.figure.get_axes()[0].lines.remove(self.__vline)
            except Exception as e:
                self.__iface.messageBar().pushMessage(
                    QCoreApplication.translate("VDLTools", "Mouse event exception : ") + str(e),
                    level=QgsMessageBar.CRITICAL, duration=0)
            xdata = float(event.xdata)
            self.__vline = self.__plotWdg.figure.get_axes()[0].axvline(xdata, linewidth=2, color='k')
            self.__plotWdg.draw()
            i = 1
            while i < len(self.__tabmouseevent)-1 and xdata > self.__tabmouseevent[i][0]:
                i += 1
            i -= 1

            x = self.__tabmouseevent[i][1] + (self.__tabmouseevent[i + 1][1] - self.__tabmouseevent[i][1]) / (
            self.__tabmouseevent[i + 1][0] - self.__tabmouseevent[i][0]) * (xdata - self.__tabmouseevent[i][0])
            y = self.__tabmouseevent[i][2] + (self.__tabmouseevent[i + 1][2] - self.__tabmouseevent[i][2]) / (
            self.__tabmouseevent[i + 1][0] - self.__tabmouseevent[i][0]) * (xdata - self.__tabmouseevent[i][0])
            self.__marker.show()
            self.__marker.setCenter(QgsPoint(x, y))

    def __loadRubber(self):
        """
        To load te rubber band for mouse tracking on map
        """
        self.__marker = QgsVertexMarker(self.__canvas)
        self.__marker.setIconSize(5)
        self.__marker.setIconType(QgsVertexMarker.ICON_BOX)
        self.__marker.setPenWidth(3)

    def __prepare_points(self):
        """
        To prepare the points on map for mouse tracking on profile
        """
        self.__tabmouseevent = []
        length = 0
        for i, point in enumerate(self.__profiles):
            if i == 0:
                self.__tabmouseevent.append([0, point['x'], point['y']])
            else:
                length += ((self.__profiles[i]['x'] - self.__profiles[i-1]['x']) ** 2 +
                           (self.__profiles[i]['y'] - self.__profiles[i-1]['y']) ** 2) ** 0.5
                self.__tabmouseevent.append([float(length), float(point['x']), float(point['y'])])

    def closeEvent(self, event):
        """
        When the dock widget is closed
        :param event: close event
        """
        if self.__maxSpin is not None:
            Signal.safelyDisconnect(self.__maxSpin.valueChanged, self.__reScalePlot)
            self.__maxSpin = None
        if self.__minSpin is not None:
            Signal.safelyDisconnect(self.__minSpin.valueChanged, self.__reScalePlot)
            self.__minSpin = None
        if self.__saveButton is not None:
            Signal.safelyDisconnect(self.__saveButton.clicked, self.__save)
            self.__saveButton = None
        if self.__libCombo is not None:
            Signal.safelyDisconnect(self.__libCombo.currentIndexChanged, self.__setLib)
            self.__libCombo = None
        self.closeSignal.emit()
        if self.__marker is not None:
            self.__marker.hide()
        QDockWidget.closeEvent(self, event)
class calibrlogger(PyQt4.QtGui.QMainWindow, Calibr_Ui_Dialog): # An instance of the class Calibr_Ui_Dialog is created same time as instance of calibrlogger is created

    def __init__(self, parent, settingsdict1={}, obsid=''):
        PyQt4.QtGui.QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))#show the user this may take a long time...
        self.obsid = obsid
        self.log_pos = None
        self.y_pos = None
        self.meas_ts = None
        self.head_ts = None
        self.level_masl_ts = None
        self.loggerpos_masl_or_offset_state = 1

        self.settingsdict = settingsdict1
        PyQt4.QtGui.QDialog.__init__(self, parent)        
        self.setAttribute(PyQt4.QtCore.Qt.WA_DeleteOnClose)
        self.setupUi(self) # Required by Qt4 to initialize the UI
        self.setWindowTitle("Calibrate logger") # Set the title for the dialog
        self.connect(self.pushButton, PyQt4.QtCore.SIGNAL("clicked()"), self.calibrateandplot)
        self.INFO.setText("Select the observation point with logger data to be calibrated.")
        self.log_calc_manual.setText("<a href=\"https://sites.google.com/site/midvattenpluginforqgis/usage/3-edit-data?pli=1#TOC-Calibrate-water-level-measurements-from-data-logger-\">Midvatten manual</a>")
      
        # Create a plot window with one single subplot
        self.calibrplotfigure = plt.figure() 
        self.axes = self.calibrplotfigure.add_subplot( 111 )
        self.canvas = FigureCanvas( self.calibrplotfigure )
        self.mpltoolbar = NavigationToolbar( self.canvas, self.widgetPlot )
        lstActions = self.mpltoolbar.actions()
        self.mpltoolbar.removeAction( lstActions[ 7 ] )
        self.layoutplot.addWidget( self.canvas )
        self.layoutplot.addWidget( self.mpltoolbar )
        self.show()

        self.cid =[]
                
        self.connect(self.pushButtonFrom, PyQt4.QtCore.SIGNAL("clicked()"), self.set_from_date_from_x)
        self.connect(self.pushButtonTo, PyQt4.QtCore.SIGNAL("clicked()"), self.set_to_date_from_x)
        self.connect(self.pushButtonupdateplot, PyQt4.QtCore.SIGNAL("clicked()"), self.update_plot)
        self.connect(self.loggerpos_masl_or_offset, PyQt4.QtCore.SIGNAL("clicked()"), self.loggerpos_masl_or_offset_change)
        self.connect(self.pushButtonLpos, PyQt4.QtCore.SIGNAL("clicked()"), self.calibrate_from_plot_selection)
        self.connect(self.pushButtonCalcBestFit, PyQt4.QtCore.SIGNAL("clicked()"), self.calc_best_fit)

        self.connect(self.pushButton_delete_logger, PyQt4.QtCore.SIGNAL("clicked()"), lambda: self.delete_selected_range(u'w_levels_logger'))
        self.connect(self.pushButton_delete_meas, PyQt4.QtCore.SIGNAL("clicked()"), lambda: self.delete_selected_range(u'w_levels'))

        self.get_tolerance()

        # Populate combobox with obsid from table w_levels_logger
        self.load_obsid_from_db()

        PyQt4.QtGui.QApplication.restoreOverrideCursor()#now this long process is done and the cursor is back as normal

    def load_obsid_from_db(self):
        self.combobox_obsid.clear()
        myconnection = utils.dbconnection()
        if myconnection.connect2db() == True:
            # skapa en cursor
            curs = myconnection.conn.cursor()
            rs=curs.execute("""select distinct obsid from w_levels_logger order by obsid""")
            self.combobox_obsid.addItem('')
            for row in curs:
                self.combobox_obsid.addItem(row[0])
            rs.close()
            myconnection.closedb()

    def load_obsid_and_init(self):
        """ Checks the current obsid and reloads all ts.
        :return: obsid

        Info: Before, some time series was only reloaded when the obsid was changed, but this caused a problem if the
        data was changed in the background in for example spatialite gui. Now all time series are reloaded always.
        It's rather fast anyway.
        """
        obsid = unicode(self.combobox_obsid.currentText())
        if not obsid:
            utils.pop_up_info("ERROR: no obsid is chosen")

        meas_sql = r"""SELECT date_time, level_masl FROM w_levels WHERE obsid = '""" + obsid + """' ORDER BY date_time"""
        self.meas_ts = self.sql_into_recarray(meas_sql)
        head_sql = r"""SELECT date_time as 'date [datetime]', head_cm / 100 FROM w_levels_logger WHERE obsid = '""" + obsid + """' ORDER BY date_time"""
        self.head_ts = self.sql_into_recarray(head_sql)
        self.obsid = obsid
        level_masl_ts_sql = r"""SELECT date_time as 'date [datetime]', level_masl FROM w_levels_logger WHERE obsid = '""" + self.obsid + """' ORDER BY date_time"""
        self.level_masl_ts = self.sql_into_recarray(level_masl_ts_sql)
        return obsid

    def getlastcalibration(self):
        obsid = self.load_obsid_and_init()
        if not obsid=='':
            sql = """SELECT MAX(date_time), loggerpos FROM (SELECT date_time, (level_masl - (head_cm/100)) as loggerpos FROM w_levels_logger WHERE level_masl > -990 AND obsid = '"""
            sql += obsid
            sql += """')"""
            self.lastcalibr = utils.sql_load_fr_db(sql)[1]
            if self.lastcalibr[0][1] and self.lastcalibr[0][0]:
                text = """Last pos. for logger in """
                text += obsid
                text += """\nwas """ + str(self.lastcalibr[0][1]) + """ masl\nat """ +  str(self.lastcalibr[0][0])
            else:
                text = """There is no earlier known\nposition for the logger\nin """ + unicode(self.combobox_obsid.currentText())#self.obsid[0]
            self.INFO.setText(text)

    def calibrateandplot(self):
        obsid = self.load_obsid_and_init()
        if not self.LoggerPos.text() == '':
            self.calibrate()
        self.update_plot()
        
#    def calibrate(self, fr_d_t=self.FromDateTime.dateTime().toPyDateTime(), to_d_t=self.ToDateTime.dateTime().toPyDateTime()):
    def calibrate(self):
        self.calib_help.setText("Calibrating")
        PyQt4.QtGui.QApplication.setOverrideCursor(PyQt4.QtCore.Qt.WaitCursor)
        obsid = self.load_obsid_and_init()
        if not obsid=='':        
            sanity1sql = """select count(obsid) from w_levels_logger where obsid = '""" +  obsid[0] + """'"""
            sanity2sql = """select count(obsid) from w_levels_logger where head_cm not null and head_cm !='' and obsid = '""" +  obsid[0] + """'"""
            if utils.sql_load_fr_db(sanity1sql)[1] == utils.sql_load_fr_db(sanity2sql)[1]: # This must only be done if head_cm exists for all data
                fr_d_t = self.FromDateTime.dateTime().toPyDateTime()
                to_d_t = self.ToDateTime.dateTime().toPyDateTime()

                if self.loggerpos_masl_or_offset_state == 1:
                    self.update_level_masl_from_head(obsid, fr_d_t, to_d_t, self.LoggerPos.text())
                else:
                    self.update_level_masl_from_level_masl(obsid, fr_d_t, to_d_t, self.LoggerPos.text())

                self.getlastcalibration()
            else:
                utils.pop_up_info("Calibration aborted!!\nThere must not be empty cells or\nnull values in the 'head_cm' column!")
        else:
            self.INFO.setText("Select the observation point with logger data to be calibrated.")
        self.calib_help.setText("")
        PyQt4.QtGui.QApplication.restoreOverrideCursor()

    def update_level_masl_from_level_masl(self, obsid, fr_d_t, to_d_t, newzref):
        """ Updates the level masl using newzref
        :param obsid: (str) The obsid
        :param fr_d_t: (datetime) start of calibration
        :param to_d_t: (datetime) end of calibration
        :param newzref: (int/float/str [m]) The correction that should be made against the head [m]
        :return: None
        """
        sql =r"""UPDATE w_levels_logger SET level_masl = """
        sql += str(newzref)
        sql += """ + level_masl WHERE obsid = '"""
        sql += obsid
        # Sqlite seems to have problems with date comparison date_time >= a_date, so they have to be converted into total seconds first.
        sql += """' AND CAST(strftime('%s', date_time) AS NUMERIC) >= """
        sql += str((fr_d_t - datetime.datetime(1970,1,1)).total_seconds())
        sql += """ AND CAST(strftime('%s', date_time) AS NUMERIC) <= """
        sql += str((to_d_t - datetime.datetime(1970,1,1)).total_seconds())
        sql += """ """
        dummy = utils.sql_alter_db(sql)

    def update_level_masl_from_head(self, obsid, fr_d_t, to_d_t, newzref):
        """ Updates the level masl using newzref
        :param obsid: (str) The obsid
        :param fr_d_t: (datetime) start of calibration
        :param to_d_t: (datetime) end of calibration
        :param newzref: (int/float/str [m]) The correction that should be made against the head [m]
        :return: None
        """
        sql =r"""UPDATE w_levels_logger SET level_masl = """
        sql += str(newzref)
        sql += """ + head_cm / 100 WHERE obsid = '"""
        sql += obsid
        # Sqlite seems to have problems with date comparison date_time >= a_date, so they have to be converted into total seconds first.
        sql += """' AND CAST(strftime('%s', date_time) AS NUMERIC) >= """
        sql += str((fr_d_t - datetime.datetime(1970,1,1)).total_seconds())
        sql += """ AND CAST(strftime('%s', date_time) AS NUMERIC) <= """
        sql += str((to_d_t - datetime.datetime(1970,1,1)).total_seconds())
        sql += """ """
        dummy = utils.sql_alter_db(sql)

    def sql_into_recarray(self, sql):
        """ Converts and runs an sql-string and turns the answer into an np.recarray and returns it""" 
        my_format = [('date_time', datetime.datetime), ('values', float)] #Define (with help from function datetime) a good format for numpy array     
        recs = utils.sql_load_fr_db(sql)[1]
        table = np.array(recs, dtype=my_format)  #NDARRAY
        table2=table.view(np.recarray)   # RECARRAY   Makes the two columns inte callable objects, i.e. write table2.values 
        return table2        

    def update_plot(self):
        """ Plots self.level_masl_ts, self.meas_ts and maybe self.head_ts """
        self.reset_plot_selects_and_calib_help()
        self.calib_help.setText("Updating plot")
        PyQt4.QtGui.QApplication.setOverrideCursor(PyQt4.QtCore.Qt.WaitCursor)
        obsid = self.load_obsid_and_init()
        self.axes.clear()
        
        p=[None]*2 # List for plot objects
    
        # Load manual reading (full time series) for the obsid
        self.plot_recarray(self.axes, self.meas_ts, obsid, 'o-', 10)
        
        # Load Loggerlevels (full time series) for the obsid
        if self.loggerLineNodes.isChecked():
            logger_line_style = '.-'
        else:
            logger_line_style = '-'                
        self.plot_recarray(self.axes, self.level_masl_ts, obsid + unicode(' logger', 'utf-8'), logger_line_style, 10)

        #Plot the original head_cm
        if self.plot_logger_head.isChecked():
            self.plot_recarray(self.axes, self.head_ts, obsid + unicode(' original logger head', 'utf-8'), logger_line_style, 10)

        """ Finish plot """
        self.axes.grid(True)
        self.axes.yaxis.set_major_formatter(tick.ScalarFormatter(useOffset=False, useMathText=False))
        self.calibrplotfigure.autofmt_xdate()
        self.axes.set_ylabel(unicode('Level (masl)', 'utf-8'))  #This is the method that accepts even national characters ('åäö') in matplotlib axes labels
        self.axes.set_title(unicode('Calibration plot for ', 'utf-8') + str(obsid))  #This is the method that accepts even national characters ('åäö') in matplotlib axes labels
        for label in self.axes.xaxis.get_ticklabels():
            label.set_fontsize(10)
        for label in self.axes.yaxis.get_ticklabels():
            label.set_fontsize(10)
        #plt.show()
        self.canvas.draw()
        plt.close(self.calibrplotfigure)#this closes reference to self.calibrplotfigure
        PyQt4.QtGui.QApplication.restoreOverrideCursor()
        self.calib_help.setText("")

    def plot_recarray(self, axes, a_recarray, lable, line_style, picker=10):
        """ Plots a recarray to the supplied axes object """
        # Get help from function datestr2num to get date and time into float
        myTimestring = [a_recarray.date_time[idx] for idx in xrange(len(a_recarray))]
        numtime=datestr2num(myTimestring)  #conv list of strings to numpy.ndarray of floats
        axes.plot_date(numtime, a_recarray.values, line_style, label=lable, picker=picker)

    def set_from_date_from_x(self):
        """ Used to set the self.FromDateTime by clicking on a line node in the plot self.canvas """
        self.reset_plot_selects_and_calib_help()
        self.calib_help.setText("Select a node to use as \"from\"")
        self.deactivate_pan_zoom()
        self.canvas.setFocusPolicy(Qt.ClickFocus)
        self.canvas.setFocus()   
        self.cid.append(self.canvas.mpl_connect('pick_event', lambda event: self.set_date_from_x_onclick(event, self.FromDateTime)))

    def set_to_date_from_x(self):
        """ Used to set the self.ToDateTime by clicking on a line node in the plot self.canvas """    
        self.reset_plot_selects_and_calib_help()
        self.calib_help.setText("Select a node to use as \"to\"")
        self.deactivate_pan_zoom()
        self.canvas.setFocusPolicy(Qt.ClickFocus)
        self.canvas.setFocus()   
        self.cid.append(self.canvas.mpl_connect('pick_event', lambda event: self.set_date_from_x_onclick(event, self.ToDateTime)))

    def set_date_from_x_onclick(self, event, date_holder):
        """ Sets the date_holder to a date from a line node closest to the pick event

            date_holder: a QDateTimeEdit object.
        """
        found_date = utils.find_nearest_date_from_event(event)
        date_holder.setDateTime(found_date)           
        self.reset_plot_selects_and_calib_help()
    
    def reset_plot_selects_and_calib_help(self):
        """ Reset self.cid and self.calib_help """
        self.reset_cid()
        self.log_pos = None
        self.y_pos = None
        self.calib_help.setText("")

    def reset_cid(self):
        """ Resets self.cid to an empty list and disconnects unused events """
        for x in self.cid:
            self.canvas.mpl_disconnect(x)
        self.cid = []

    def calibrate_from_plot_selection(self):
        """ Calibrates by selecting a line node and a y-position on the plot

            The user have to click on the button three times and follow instructions.
        
            The process:
            1. Selecting a line node.
            2. Selecting a selecting a y-position from the plot.
            3. Extracting the head from head_ts with the same date as the line node.
            4. Calculating y-position - head (or level_masl) and setting self.LoggerPos.
            5. Run calibration.
        """            
        #Run init to make sure self.meas_ts and self.head_ts is updated for the current obsid.           
        self.load_obsid_and_init()
        self.deactivate_pan_zoom()
        self.canvas.setFocusPolicy(Qt.ClickFocus)
        self.canvas.setFocus()

        if self.log_pos is None:
            self.calib_help.setText("Select a logger node.")
            self.cid.append(self.canvas.mpl_connect('pick_event', self.set_log_pos_from_node_date_click))  
        
        if self.log_pos is not None and self.y_pos is None:
            self.calib_help.setText("Select a y position to move to.")
            self.cid.append(self.canvas.mpl_connect('button_press_event', self.set_y_pos_from_y_click))
            
        if self.log_pos is not None and self.y_pos is not None:
            PyQt4.QtGui.QApplication.setOverrideCursor(PyQt4.QtCore.Qt.WaitCursor)

            if self.loggerpos_masl_or_offset_state == 1:
                logger_ts = self.head_ts
            else:
                logger_ts = self.level_masl_ts
            
            y_pos = self.y_pos
            log_pos = self.log_pos
            self.y_pos = None
            self.log_pos = None
            log_pos_date = datestring_to_date(log_pos).replace(tzinfo=None)
            logger_value = None

            #Get the value for the selected node
            for idx, date_value_tuple in enumerate(logger_ts):
                raw_date, logger_value = date_value_tuple
                date = datestring_to_date(raw_date).replace(tzinfo=None)
                if date == log_pos_date:
                    break

            if logger_value is None:
                utils.pop_up_info("No connection between head_ts dates and logger date could be made!\nTry again or choose a new logger line node!")   
            else:
                self.LoggerPos.setText(str(float(y_pos) - float(logger_value)))

                PyQt4.QtGui.QApplication.restoreOverrideCursor()
                self.calibrateandplot()

            self.calib_help.setText("")
        
    def set_log_pos_from_node_date_click(self, event):
        """ Sets self.log_pos variable to the date (x-axis) from the node nearest the pick event """
        found_date = utils.find_nearest_date_from_event(event)
        self.calib_help.setText("Logger node " + str(found_date) + " selected, click button \"Calibrate by selection in plot\" again.")
        self.log_pos = found_date
        self.reset_cid()
 
    def set_y_pos_from_y_click(self, event):
        """ Sets the self.y_pos variable to the y value of the click event """
        self.y_pos = event.ydata
        self.calib_help.setText("Y position set, click button \"Calibrate by selection in plot\" again for calibration.")
        self.reset_cid()
        
    def calc_best_fit(self):
        """ Calculates the self.LoggerPos from self.meas_ts and self.head_ts
        
            First matches measurements from self.meas_ts to logger values from
            self.head_ts. This is done by making a mean of all logger values inside
            self.meas_ts date - tolerance and self.meas_ts date + tolerance.
            (this could probably be change to get only the closest logger value
            inside the tolerance instead)
            (Tolerance is gotten from self.get_tolerance())
            
            Then calculates the mean of all matches and set to self.LoggerPos.
        """
        obsid = self.load_obsid_and_init()
        self.reset_plot_selects_and_calib_help()
        tolerance = self.get_tolerance()
        really_calibrate_question = utils.askuser("YesNo", """This will calibrate all values inside the chosen period\nusing the mean difference between logger values and measurements.\n\nTime tolerance for matching logger and measurement nodes set to '""" + ' '.join(tolerance) + """'\n\nContinue?""")
        if really_calibrate_question.result == 0: # if the user wants to abort
            return

        PyQt4.QtGui.QApplication.setOverrideCursor(PyQt4.QtCore.Qt.WaitCursor)
        if self.loggerpos_masl_or_offset_state == 1:
            logger_ts = self.head_ts
        else:
            logger_ts = self.level_masl_ts



        coupled_vals = self.match_ts_values(self.meas_ts, logger_ts, tolerance)
        if not coupled_vals:
            utils.pop_up_info("There was no matched measurements or logger values inside the chosen period.\n Try to increase the tolerance!")
        else:            
            self.LoggerPos.setText(str(utils.calc_mean_diff(coupled_vals)))
            self.calibrateandplot()
        PyQt4.QtGui.QApplication.restoreOverrideCursor()
     
    def match_ts_values(self, meas_ts, logger_ts, tolerance):
        """ Matches two timeseries values for shared timesteps 
        
            For every measurement point, a mean of logger values inside 
            measurementpoint + x minutes to measurementpoint - x minutes
            is coupled together.

            At the first used measurement, only logger values greater than
            the set start date is used.
            At the last measurement, only logger values lesser than the set end
            date is used.
            This is done so that values from another logger reposition is not
            mixed with the chosen logger positioning. (Hard to explain).
        """
        coupled_vals = []
        
        #Get the tolerance, default to 10 minutes
        tol = int(tolerance[0])
        tol_period = tolerance[1]
  
        logger_gen = utils.ts_gen(logger_ts)
        try:
            l = next(logger_gen)
        except StopIteration:
            return None
        log_vals = []

        all_done = False
        #The .replace(tzinfo=None) is used to remove info about timezone. Needed for the comparisons. This should not be a problem though as the date scale in the plot is based on the dates from the database. 
        outer_begin = self.FromDateTime.dateTime().toPyDateTime().replace(tzinfo=None)
        outer_end = self.ToDateTime.dateTime().toPyDateTime().replace(tzinfo=None)
        logger_step = datestring_to_date(l[0]).replace(tzinfo=None)
        for m in meas_ts:
            if logger_step is None:
                break
            meas_step = datestring_to_date(m[0]).replace(tzinfo=None)

            step_begin = dateshift(meas_step, -tol, tol_period)
            step_end = dateshift(meas_step, tol, tol_period)

            if step_end < outer_begin:
                continue
            if step_begin > outer_end:
                break

            #Skip logger steps that are earlier than the chosen begin date or are not inside the measurement period.
            while logger_step < step_begin or logger_step < outer_begin:
                try:
                    l = next(logger_gen)
                except StopIteration:
                    all_done = True
                    break
                logger_step = datestring_to_date(l[0]).replace(tzinfo=None)

            log_vals = []

            while logger_step is not None and logger_step <= step_end and logger_step <= outer_end:
                if not math.isnan(float(l[1])) or l[1] == 'nan' or l[1] == 'NULL':
                    log_vals.append(float(l[1]))
                try:
                    l = next(logger_gen)
                except StopIteration:
                    all_done = True
                    break
                logger_step = datestring_to_date(l[0]).replace(tzinfo=None)                     

            if log_vals:
                mean = np.mean(log_vals)
                if not math.isnan(mean):
                    coupled_vals.append((m[1], mean))
            if all_done:
                break
        return coupled_vals
                      
    def get_tolerance(self):
        """ Get the period tolerance, default to 10 minutes """
        if not self.bestFitTolerance.text():
            tol = '10 minutes'
            self.bestFitTolerance.setText(tol)
        else:
            tol = self.bestFitTolerance.text()

        tol_splitted = tol.split()
        if len(tol_splitted) != 2:
            utils.pop_up_info("Must write time resolution also, ex. 10 minutes")
        return tuple(tol_splitted)

    def loggerpos_masl_or_offset_change(self):
        if self.loggerpos_masl_or_offset_state == 1:
            self.label_11.setText("Offset relative to calibrated values:")
            self.loggerpos_masl_or_offset.setText("Change to logger position")
            self.label_adjustment_info.setText("Adjustments made from calibrated values")
            self.loggerpos_masl_or_offset_state = 0
        else:
            self.label_11.setText("Logger position, masl:")
            self.loggerpos_masl_or_offset.setText("Change to offset")
            self.label_adjustment_info.setText("Adjustments made from head")
            self.loggerpos_masl_or_offset_state = 1

    def deactivate_pan_zoom(self):
        """ Deactivates the NavigationToolbar pan or zoom feature if they are currently active """
        if self.mpltoolbar._active == "PAN":
            self.mpltoolbar.pan()
        elif self.mpltoolbar._active == "ZOOM":
            self.mpltoolbar.zoom()

    def delete_selected_range(self, table_name):
        """ Deletes the current selected range from the database from w_levels_logger
        :return: De
        """
        current_loaded_obsid = self.obsid
        selected_obsid = self.load_obsid_and_init()
        if current_loaded_obsid != selected_obsid:
            utils.pop_up_info("Error!\n The obsid selection has been changed but the plot has not been updated. No deletion done.\nUpdating plot.")
            self.update_plot()
            return

        fr_d_t = str((self.FromDateTime.dateTime().toPyDateTime() - datetime.datetime(1970,1,1)).total_seconds())
        to_d_t = str((self.ToDateTime.dateTime().toPyDateTime() - datetime.datetime(1970,1,1)).total_seconds())

        sql_list = []
        sql_list.append(r"""delete from "%s" """%table_name)
        sql_list.append(r"""where obsid = '%s' """%selected_obsid)
        sql_list.append(r"""AND CAST(strftime('%s', date_time) AS NUMERIC) """)
        sql_list.append(r""" >= '%s' """%fr_d_t)
        sql_list.append(r"""AND CAST(strftime('%s', date_time) AS NUMERIC) """)
        sql_list.append(r""" <= '%s' """%to_d_t)
        sql = ''.join(sql_list)

        really_delete = utils.askuser("YesNo", "Do you want to delete the period " +
                                      str(self.FromDateTime.dateTime().toPyDateTime()) + " to " +
                                      str(self.ToDateTime.dateTime().toPyDateTime()) +
                                      " for obsid " + selected_obsid + " from table " + table_name + "?").result
        if really_delete:
            utils.sql_alter_db(sql)
            self.update_plot()
예제 #6
0
    def mouseEventCallback(self, callback):
        if self.cid != None:
            FigureCanvas.mpl_disconnect(self, self.cid)

        self.cid = FigureCanvas.mpl_connect(self, 'button_press_event',
                                            callback)
예제 #7
0
class ROISelect(QMdiSubWindow):
    ###########
    # Signals #
    ###########
    plotSignal = pyqtSignal()

    ########################
    # Initializing Methods #
    ########################
    def __init__(self, title, imgfile, control, parent=None):
        '''
        Initializes internal variables
        '''
        QMdiSubWindow.__init__(self, parent)
        self.setWindowTitle(title)
        self.imgfile = imgfile
        self.control = control
        self.xys = []
        self.ROI = np.zeros((600, 800)).astype(np.bool)
        for i in range(800):
            for j in range(600):
                self.xys.append((i, j))
        self.create_main_frame()
        self.onDraw()

    def create_main_frame(self):
        '''
        Creates the main window
        '''
        # Widgets
        self.main_frame = QWidget()
        self.fig = Figure()
        self.canvas = FigureCanvas(self.fig)
        self.canvas.setParent(self.main_frame)
        self.axes = self.fig.add_subplot(111)
        self.mpl_toolbar = NavigationToolbar(self.canvas, self.main_frame)
        self.reset = QPushButton('&Reset')

        # Connections
        self.cid = self.canvas.mpl_connect('button_press_event', self.onpress)
        self.control.imageChanged.connect(self.onImageChanged)
        self.control.closeSignal.connect(self.close)
        self.connect(self.reset, SIGNAL('clicked()'), self.onReset)

        # Layouts
        vbox = QVBoxLayout()
        vbox.addWidget(self.canvas)
        vbox.addWidget(self.mpl_toolbar)
        vbox.addWidget(self.reset)

        self.main_frame.setLayout(vbox)
        self.setWidget(self.main_frame)

    #########
    # Slots #
    #########
    def onReset(self):
        self.ROI = np.zeros((600, 800)).astype(np.bool)
        self.cid = self.canvas.mpl_connect('button_press_event', self.onpress)
        self.onDraw()

    def onDraw(self):
        self.axes.clear()
        img = Image.open(self.imgfile)
        img = np.asarray(img)
        self.axes.imshow(img)
        self.axes.imshow(self.ROI, alpha=0.1, cmap='gray')
        self.plotSignal.emit()
        self.canvas.draw()

    def getROI(self, verts):
        ind = points_inside_poly(self.xys, verts)
        self.canvas.draw_idle()
        self.canvas.widgetlock.release(self.lasso)
        del self.lasso
        self.ROI = ind
        self.ROI = self.ROI.reshape((600, 800), order='F')
        self.canvas.mpl_disconnect(self.cid)
        self.onDraw()

    def onpress(self, event):
        if self.canvas.widgetlock.locked(): return
        if event.inaxes is None: return
        self.lasso = Lasso(event.inaxes, (event.xdata, event.ydata),
                           self.getROI)
        self.canvas.widgetlock(self.lasso)

    def onImageChanged(self, filename):
        '''
        Catches the signal from the ControlPanel that the current image has changed
        '''
        self.imgfile = str(filename)
        self.onDraw()
예제 #8
0
class PlotCanvas:
    """
    Class handling the plotting area in the application.
    """

    def __init__(self, container):
        """
        The constructor configures the Matplotlib figure that
        will contain all plots, creates the base axes and connects
        events to the plotting area.

        :param container: The parent container in which to draw plots.
        :rtype: PlotCanvas
        """
        # Options
        self.x_margin = 15  # pixels
        self.y_margin = 25  # Pixels

        # Parent container
        self.container = container

        # Plots go onto a single matplotlib.figure
        self.figure = Figure(dpi=50)  # TODO: dpi needed?
        self.figure.patch.set_visible(False)

        # These axes show the ticks and grid. No plotting done here.
        # New axes must have a label, otherwise mpl returns an existing one.
        self.axes = self.figure.add_axes([0.05, 0.05, 0.9, 0.9], label="base", alpha=0.0)
        self.axes.set_aspect(1)
        self.axes.grid(True)

        # The canvas is the top level container (Gtk.DrawingArea)
        self.canvas = FigureCanvas(self.figure)
        # self.canvas.setFocusPolicy(QtCore.Qt.ClickFocus)
        # self.canvas.setFocus()

        #self.canvas.set_hexpand(1)
        #self.canvas.set_vexpand(1)
        #self.canvas.set_can_focus(True)  # For key press

        # Attach to parent
        #self.container.attach(self.canvas, 0, 0, 600, 400)  # TODO: Height and width are num. columns??
        self.container.addWidget(self.canvas)  # Qt

        # Copy a bitmap of the canvas for quick animation.
        # Update every time the canvas is re-drawn.
        self.background = self.canvas.copy_from_bbox(self.axes.bbox)

        # Events
        self.canvas.mpl_connect('button_press_event', self.on_mouse_press)
        self.canvas.mpl_connect('button_release_event', self.on_mouse_release)
        self.canvas.mpl_connect('motion_notify_event', self.on_mouse_move)
        #self.canvas.connect('configure-event', self.auto_adjust_axes)
        self.canvas.mpl_connect('resize_event', self.auto_adjust_axes)
        #self.canvas.add_events(Gdk.EventMask.SMOOTH_SCROLL_MASK)
        #self.canvas.connect("scroll-event", self.on_scroll)
        self.canvas.mpl_connect('scroll_event', self.on_scroll)
        self.canvas.mpl_connect('key_press_event', self.on_key_down)
        self.canvas.mpl_connect('key_release_event', self.on_key_up)
        self.canvas.mpl_connect('draw_event', self.on_draw)

        self.mouse = [0, 0]
        self.key = None

        self.pan_axes = []
        self.panning = False

    def on_key_down(self, event):
        """

        :param event:
        :return:
        """
        FlatCAMApp.App.log.debug('on_key_down(): ' + str(event.key))
        self.key = event.key

    def on_key_up(self, event):
        """

        :param event:
        :return:
        """
        self.key = None

    def mpl_connect(self, event_name, callback):
        """
        Attach an event handler to the canvas through the Matplotlib interface.

        :param event_name: Name of the event
        :type event_name: str
        :param callback: Function to call
        :type callback: func
        :return: Connection id
        :rtype: int
        """
        return self.canvas.mpl_connect(event_name, callback)

    def mpl_disconnect(self, cid):
        """
        Disconnect callback with the give id.
        :param cid: Callback id.
        :return: None
        """
        self.canvas.mpl_disconnect(cid)

    def connect(self, event_name, callback):
        """
        Attach an event handler to the canvas through the native GTK interface.

        :param event_name: Name of the event
        :type event_name: str
        :param callback: Function to call
        :type callback: function
        :return: Nothing
        """
        self.canvas.connect(event_name, callback)

    def clear(self):
        """
        Clears axes and figure.

        :return: None
        """

        # Clear
        self.axes.cla()
        try:
            self.figure.clf()
        except KeyError:
            FlatCAMApp.App.log.warning("KeyError in MPL figure.clf()")

        # Re-build
        self.figure.add_axes(self.axes)
        self.axes.set_aspect(1)
        self.axes.grid(True)

        # Re-draw
        self.canvas.draw_idle()

    def adjust_axes(self, xmin, ymin, xmax, ymax):
        """
        Adjusts all axes while maintaining the use of the whole canvas
        and an aspect ratio to 1:1 between x and y axes. The parameters are an original
        request that will be modified to fit these restrictions.

        :param xmin: Requested minimum value for the X axis.
        :type xmin: float
        :param ymin: Requested minimum value for the Y axis.
        :type ymin: float
        :param xmax: Requested maximum value for the X axis.
        :type xmax: float
        :param ymax: Requested maximum value for the Y axis.
        :type ymax: float
        :return: None
        """

        # FlatCAMApp.App.log.debug("PC.adjust_axes()")

        width = xmax - xmin
        height = ymax - ymin
        try:
            r = width / height
        except ZeroDivisionError:
            FlatCAMApp.App.log.error("Height is %f" % height)
            return
        canvas_w, canvas_h = self.canvas.get_width_height()
        canvas_r = float(canvas_w) / canvas_h
        x_ratio = float(self.x_margin) / canvas_w
        y_ratio = float(self.y_margin) / canvas_h

        if r > canvas_r:
            ycenter = (ymin + ymax) / 2.0
            newheight = height * r / canvas_r
            ymin = ycenter - newheight / 2.0
            ymax = ycenter + newheight / 2.0
        else:
            xcenter = (xmax + xmin) / 2.0
            newwidth = width * canvas_r / r
            xmin = xcenter - newwidth / 2.0
            xmax = xcenter + newwidth / 2.0

        # Adjust axes
        for ax in self.figure.get_axes():
            if ax._label != 'base':
                ax.set_frame_on(False)  # No frame
                ax.set_xticks([])  # No tick
                ax.set_yticks([])  # No ticks
                ax.patch.set_visible(False)  # No background
                ax.set_aspect(1)
            ax.set_xlim((xmin, xmax))
            ax.set_ylim((ymin, ymax))
            ax.set_position([x_ratio, y_ratio, 1 - 2 * x_ratio, 1 - 2 * y_ratio])

        # Sync re-draw to proper paint on form resize
        self.canvas.draw()

    def auto_adjust_axes(self, *args):
        """
        Calls ``adjust_axes()`` using the extents of the base axes.

        :rtype : None
        :return: None
        """

        xmin, xmax = self.axes.get_xlim()
        ymin, ymax = self.axes.get_ylim()
        self.adjust_axes(xmin, ymin, xmax, ymax)

    def zoom(self, factor, center=None):
        """
        Zooms the plot by factor around a given
        center point. Takes care of re-drawing.

        :param factor: Number by which to scale the plot.
        :type factor: float
        :param center: Coordinates [x, y] of the point around which to scale the plot.
        :type center: list
        :return: None
        """

        xmin, xmax = self.axes.get_xlim()
        ymin, ymax = self.axes.get_ylim()
        width = xmax - xmin
        height = ymax - ymin

        if center is None or center == [None, None]:
            center = [(xmin + xmax) / 2.0, (ymin + ymax) / 2.0]

        # For keeping the point at the pointer location
        relx = (xmax - center[0]) / width
        rely = (ymax - center[1]) / height

        new_width = width / factor
        new_height = height / factor

        xmin = center[0] - new_width * (1 - relx)
        xmax = center[0] + new_width * relx
        ymin = center[1] - new_height * (1 - rely)
        ymax = center[1] + new_height * rely

        # Adjust axes
        for ax in self.figure.get_axes():
            ax.set_xlim((xmin, xmax))
            ax.set_ylim((ymin, ymax))

        # Async re-draw
        self.canvas.draw_idle()

    def pan(self, x, y):
        xmin, xmax = self.axes.get_xlim()
        ymin, ymax = self.axes.get_ylim()
        width = xmax - xmin
        height = ymax - ymin

        # Adjust axes
        for ax in self.figure.get_axes():
            ax.set_xlim((xmin + x * width, xmax + x * width))
            ax.set_ylim((ymin + y * height, ymax + y * height))

        # Re-draw
        self.canvas.draw_idle()

    def new_axes(self, name):
        """
        Creates and returns an Axes object attached to this object's Figure.

        :param name: Unique label for the axes.
        :return: Axes attached to the figure.
        :rtype: Axes
        """

        return self.figure.add_axes([0.05, 0.05, 0.9, 0.9], label=name)

    def on_scroll(self, event):
        """
        Scroll event handler.

        :param event: Event object containing the event information.
        :return: None
        """

        # So it can receive key presses
        # self.canvas.grab_focus()
        self.canvas.setFocus()

        # Event info
        # z, direction = event.get_scroll_direction()

        if self.key is None:

            if event.button == 'up':
                self.zoom(1.5, self.mouse)
            else:
                self.zoom(1 / 1.5, self.mouse)
            return

        if self.key == 'shift':

            if event.button == 'up':
                self.pan(0.3, 0)
            else:
                self.pan(-0.3, 0)
            return

        if self.key == 'control':

            if event.button == 'up':
                self.pan(0, 0.3)
            else:
                self.pan(0, -0.3)
            return

    def on_mouse_press(self, event):

        # Check for middle mouse button press
        if event.button == 2:

            # Prepare axes for pan (using 'matplotlib' pan function)
            self.pan_axes = []
            for a in self.figure.get_axes():
                if (event.x is not None and event.y is not None and a.in_axes(event) and
                        a.get_navigate() and a.can_pan()):
                    a.start_pan(event.x, event.y, 1)
                    self.pan_axes.append(a)

            # Set pan view flag
            if len(self.pan_axes) > 0:
                self.panning = True;

    def on_mouse_release(self, event):

        # Check for middle mouse button release to complete pan procedure
        if event.button == 2:
            for a in self.pan_axes:
                a.end_pan()

            # Clear pan flag
            self.panning = False

    def on_mouse_move(self, event):
        """
        Mouse movement event hadler. Stores the coordinates. Updates view on pan.

        :param event: Contains information about the event.
        :return: None
        """
        self.mouse = [event.xdata, event.ydata]

        # Update pan view on mouse move
        if self.panning is True:
            for a in self.pan_axes:
                a.drag_pan(1, event.key, event.x, event.y)

            # Async re-draw (redraws only on thread idle state, uses timer on backend)
            self.canvas.draw_idle()

    def on_draw(self, renderer):

        # Store background on canvas redraw
        self.background = self.canvas.copy_from_bbox(self.axes.bbox)
예제 #9
0
파일: ROI.py 프로젝트: DanBushey/myModules
class roiSelectionTool(QtGui.QWidget):
    def __init__(self, filelist, rois, parent=None):
        super(roiSelectionTool, self).__init__(parent)
        self.rois = rois
        # a figure instance to plot on
        self.figure = plt.figure()
        self.filelist = filelist
        self.currentImage = 0  # number in list of current images
        # this is the Canvas Widget that displays the `figure`
        # it takes the `figure` instance as a parameter to __init__

        #generate list widget
        self.listW = QtGui.QListWidget()
        self.listW.addItems(self.rois)
        self.colors = [(1, 0, 0), (0, 1, 0), (0, 0, 1), (1, 1, 0), (1, 0, 1),
                       (1, 0.55, 0.55), (0.55, 0.55, 0.55), (0.5, 0, 0),
                       (1, 0.5, 0)]
        for index in xrange(self.listW.count()):
            self.listW.item(index).setTextColor(
                QtGui.QColor(self.colors[index][0] * 255,
                             self.colors[index][1] * 255,
                             self.colors[index][2] * 255))
        self.listW.setCurrentRow(0)
        #self.setMaximumWidth(10000)

        #generate canvas
        self.canvas = FigureCanvas(self.figure)
        self.cid1 = self.canvas.mpl_connect('button_press_event',
                                            self.onPressCanvas)
        #self.canvas.setMaximumSize(1000000, 10000000)

        #generate scrollbar
        self.scroll = QtGui.QScrollBar()
        self.scroll.valueChanged.connect(self.changeSlice)
        self.scroll.setMaximum(255)
        self.scroll.setOrientation(1)
        self.scroll.setMaximumSize(300000, 50)

        #self.scroll.sliderMoved.connect(1)
        # this is the Navigation widget
        # it takes the Canvas widget and a parent
        self.toolbar = NavigationToolbar(self.canvas, self)

        #radio buttons choosing image type
        self.imageTypeBox = QtGui.QGroupBox('Choose Image')
        imagetypes = ['StDev', 'Max', 'Sum', 'Stack']
        radioboxForm = QtGui.QFormLayout()
        self.radioImageType = {}
        for ctype in imagetypes:
            cr = QtGui.QRadioButton(ctype)
            cr.clicked.connect(self.updateImage)
            self.radioImageType[ctype] = cr
            radioboxForm.addRow(cr)
            if ctype == 'StDev':
                cr.setChecked(1)
        self.imageTypeBox.setLayout(radioboxForm)
        #import pdb; pdb.set_trace()
        #self.imageTypeBox.clicked.connect(self.currentImageType)
        #self.imageTypeBox.mousePressEvent(self.currentImageType)

        #radio buttons choosing iROI tool
        self.roiTypeBox = QtGui.QGroupBox('Choose ROI')
        roiTypes = ['lasso', 'polygon', 'circle', 'square']
        radioboxForm = QtGui.QFormLayout()
        self.radioRoiTypes = {}
        for ctype in roiTypes:
            cr = QtGui.QRadioButton(ctype)
            self.radioRoiTypes[ctype] = cr
            radioboxForm.addRow(cr)
            if ctype == 'lasso':
                cr.setChecked(1)
        self.roiTypeBox.setLayout(radioboxForm)

        # buttons to change image currently viewed
        self.nextImgB = QtGui.QPushButton('Next Image')
        self.nextImgB.clicked.connect(self.nextImage)
        self.nextImgB.setMaximumSize(200, 50)
        self.prevImgB = QtGui.QPushButton('Previous Image')
        self.prevImgB.clicked.connect(self.prevImage)
        self.prevImgB.setMaximumSize(200, 50)
        self.eraseMaskB = QtGui.QPushButton('Erase Mask')
        self.eraseMaskB.clicked.connect(self.eraseMask)
        self.eraseMaskB.setMaximumSize(200, 50)

        # buttons to control contrast
        self.contrastB = QtGui.QPushButton('Contrast')
        self.contrastB.clicked.connect(self.contrast)
        self.contrastB.setMaximumSize(200, 50)

        # set the layout
        self.grid = QtGui.QGridLayout()
        self.grid.setSpacing(5)
        #first is row and next is column
        self.grid.addWidget(self.toolbar, 0, 0)
        self.grid.addWidget(self.canvas, 1, 0, 4, 3)
        self.grid.addWidget(self.nextImgB, 0, 2)
        self.grid.addWidget(self.prevImgB, 0, 1)
        self.grid.addWidget(self.scroll, 5, 0, 5, 3)
        self.grid.addWidget(self.listW, 0, 3, 2, 3)
        self.grid.addWidget(self.imageTypeBox, 3, 3)
        self.grid.addWidget(self.roiTypeBox, 4, 3)
        self.grid.addWidget(self.eraseMaskB, 4, 4)
        self.grid.addWidget(self.contrastB, 5, 4)
        self.grid.setColumnStretch(1, 4)
        self.grid.setRowStretch(3, 1)
        #self.grid.setColumnStretch()
        self.setLayout(self.grid)

        self.showMaximized()
        self.LoadImage()

    def eraseMask(self):
        savefile = os.path.split(self.imgpath)
        savefile = os.path.join(
            savefile[0],
            str(self.listW.currentItem().text()) + '_Mask.npy')
        print('Deleting_' + savefile)
        os.remove(savefile)
        self.drawROI()

    def saveMask(self):
        savefile = os.path.split(self.imgpath)
        savefile = os.path.join(
            savefile[0],
            str(self.listW.currentItem().text()) + '_Mask.npy')
        print('Saving_' + savefile)
        np.save(savefile, self.IndexMask)

    def onPressCanvas(self, event):
        print("Event detected starting whatever tool is selected")
        self.canvas.mpl_disconnect(self.cid1)
        if self.canvas.widgetlock.locked():
            return
        if event.inaxes is None:
            return
        if self.radioRoiTypes['lasso'].isChecked():
            self.lasso = LassoDB(event.inaxes, (event.xdata, event.ydata),
                                 self.generateMask)
        elif self.radioRoiTypes['polygon'].isChecked():
            #self.polygon = polygonCreator2(self.canvas, self.ax, (event.xdata, event.ydata), self.generateMask)
            self.polygon = polygonCreator(event.inaxes,
                                          (event.xdata, event.ydata),
                                          self.generateMask)
        else:
            print('Tool not implemented')

        # acquire a lock on the widget drawing
        #self.canvas.widgetlock(self.lasso)

    def generateMask(self, verts):
        p = matplotlib.lines.Path(verts)
        pix1 = np.arange(self.img.shape[1])
        pix2 = np.arange(self.img.shape[2])
        xv, yv = np.meshgrid(pix1, pix2)
        pix = np.vstack((xv.flatten(), yv.flatten())).T
        ind = p.contains_points(pix, radius=1)
        savefile = os.path.split(self.imgpath)
        savefile = os.path.join(
            savefile[0],
            str(self.listW.currentItem().text()) + '_Mask.npy')
        if os.path.isfile(savefile):
            index = np.load(savefile)
            self.Mask = np.zeros([self.img.shape[1], self.img.shape[2]])
            if len(index) != 0:
                self.Mask[(index[0], index[1])] = 1
        else:
            self.Mask = np.zeros([self.img.shape[1], self.img.shape[2]])
        lin = np.arange(self.Mask.size)
        newArray = self.Mask.flatten()
        newArray[lin[ind]] = 1
        #newArray[indices] = 1
        self.Mask = newArray.reshape(self.Mask.shape)
        self.IndexMask = np.where(self.Mask == 1)
        self.saveMask()
        self.drawROI()
        self.cid1 = self.canvas.mpl_connect('button_press_event',
                                            self.onPressCanvas)
        #del self.lasso

    def drawROI(self):
        #remove old plot
        if 'plottedlines' in dir(self):
            for i, line1 in enumerate(self.plottedlines):
                #import pdb; pdb.set_trace()
                self.ax.lines.remove(line1[0])
                #del self.ax.lines(i)
                #line1.remove()

        #plot masks
        self.plottedlines = []
        for cn, cmask in enumerate(self.rois):
            savefile = os.path.split(self.imgpath)
            savefile = os.path.join(savefile[0], cmask + '_Mask.npy')
            #import pdb; pdb.set_trace()
            if os.path.isfile(savefile):
                index = np.load(savefile)
                self.Mask = np.zeros([self.img.shape[1], self.img.shape[2]])
                if len(index) != 0:
                    self.Mask[(index[0], index[1])] = 1
                    contours = skimage.measure.find_contours(self.Mask, 0.8)
                    for n, contour in enumerate(contours):
                        self.plottedlines.append(
                            self.ax.plot(contour[:, 1],
                                         contour[:, 0],
                                         linewidth=2,
                                         color=self.colors[cn]))
                    #msk.set_data(array)
                    self.ax.axis('on')
                    self.ax.set(adjustable="datalim")
                    self.ax.set_ylim([self.img.shape[2], 0])
                    self.ax.set_xlim([0, self.img.shape[1]])
                    #self.imagehandle.ylim([0, 512])
                    #self.ax.xlim([0, 512])
                    #self.ax.set(adjustable = 'datalim')
        self.figure.canvas.draw_idle()

    def LoadImage(self):

        self.imgpath = self.filelist[self.currentImage]
        #self.cimg +=1
        print('loading Image')
        print(self.imgpath)
        self.img = tifffile.imread(self.imgpath)
        print('Finished Loading Image')
        #self.img=exposure.adjust_gamma(self.img, 0.6)
        self.scroll.setMaximum(
            self.img.shape[0])  #set maximum number of images for scroll bar
        # create an axis
        self.ax = self.figure.add_axes([0, 0, 1, 1])
        self.ax.axis('off')

        # discards the old graph
        #self.ax.hold(False)

        # plot data
        self.currentSlice = 0
        #self.imagehandle = self.ax.imshow(np.std(self.img, axis=0), extent = [0, self.img.shape[1], 0, self.img.shape[2]]) #use extent to try to prevent resizing after plotting mask but only prevents resizing in Y
        #self.imagehandle = self.ax.imshow(np.std(self.img, axis=0))
        self.imagehandle = self.ax.imshow(np.std(self.img, axis=0),
                                          aspect='equal',
                                          cmap='Greys_r')
        self.ax.set(adjustable="datalim")
        print('Xaxis_ ' + str(self.ax.get_xlim()))
        print('Yaxis_ ' + str(self.ax.get_ylim()))
        if 'text1' in dir(self):
            self.text1.set_text(self.imgpath[-50:])
        else:
            self.text1 = self.ax.text(self.ax.get_xlim()[0] + 10,
                                      self.ax.get_ylim()[1] + 10,
                                      self.imgpath[-50:],
                                      color=(1, 1, 1),
                                      verticalalignment='bottom',
                                      horizontalalignment='left')
        #self.ax.text(250, 250, self.imgpath[-15], color = (1,1,1), verticalalignment = 'bottom', horizontalalignment ='right')

        #draw in masks
        self.drawROI()
        self.canvas.draw()

    def updateImage(self):
        print('updatingimage')
        #import pdb; pdb.set_trace()
        if self.radioImageType['Stack'].isChecked():
            self.imagehandle.set_data(self.img[self.currentSlice, :, :])
            self.imagehandle.set_clim(np.min(self.img), np.max(self.img))
        elif self.radioImageType['StDev'].isChecked():
            self.imagehandle.set_data(np.std(self.img, axis=0))
            self.imagehandle.set_clim(np.min(np.std(self.img, axis=0)),
                                      np.max(np.std(self.img, axis=0)))
        elif self.radioImageType['Max'].isChecked():
            self.imagehandle.set_data(np.max(self.img, axis=0))
            self.imagehandle.set_clim(np.min(self.img), np.max(self.img))
        elif self.radioImageType['Sum'].isChecked():
            self.imagehandle.set_data(np.sum(self.img, axis=0))
            self.imagehandle.set_clim(np.min(np.sum(self.img, axis=0)),
                                      np.max(np.sum(self.img, axis=0)))
        self.canvas.draw()

    def changeSlice(self):
        self.currentSlice = self.scroll.value()
        self.updateImage()

    def prevImage(self):
        self.currentImage -= 1
        if self.currentImage >= len(self.filelist):
            self.currentImage = len(self.filelist) - 1
        self.LoadImage()

    def nextImage(self):
        self.currentImage += 1
        if self.currentImage < 0:
            self.currentImage = 0
        self.LoadImage()

    def contrast(self):
        print('Not finished')
        self.cid1 = self.canvas.mpl_connect('button_press_event',
                                            self.onPressCanvas)
예제 #10
0
class ROISelect(QMdiSubWindow):
    ###########
    # Signals #
    ###########
    plotSignal = pyqtSignal()

    ########################
    # Initializing Methods #
    ########################
    def __init__(self, title, imgfile, control, parent=None):
        '''
        Initializes internal variables
        '''
        QMdiSubWindow.__init__(self, parent)
        self.setWindowTitle(title)
        self.imgfile = imgfile
        self.control = control
        self.xys = []
        self.ROI = np.zeros((600,800)).astype(np.bool)
        for i in range(800):
            for j in range(600):
                self.xys.append((i,j))
        self.create_main_frame()
        self.onDraw()

    def create_main_frame(self):
        '''
        Creates the main window
        '''
        # Widgets 
        self.main_frame = QWidget()
        self.fig = Figure()
        self.canvas = FigureCanvas(self.fig)
        self.canvas.setParent(self.main_frame)
        self.axes = self.fig.add_subplot(111)
        self.mpl_toolbar = NavigationToolbar(self.canvas, self.main_frame)
        self.reset = QPushButton('&Reset')
        
        # Connections
        self.cid = self.canvas.mpl_connect('button_press_event', self.onpress)
        self.control.imageChanged.connect(self.onImageChanged)
        self.control.closeSignal.connect(self.close)
        self.connect(self.reset, SIGNAL('clicked()'), self.onReset)

        # Layouts
        vbox = QVBoxLayout()
        vbox.addWidget(self.canvas)
        vbox.addWidget(self.mpl_toolbar)
        vbox.addWidget(self.reset)

        self.main_frame.setLayout(vbox)
        self.setWidget(self.main_frame)

    #########
    # Slots #
    #########
    def onReset(self):
        self.ROI = np.zeros((600,800)).astype(np.bool)
        self.cid = self.canvas.mpl_connect('button_press_event', self.onpress)
        self.onDraw()

    def onDraw(self):
        self.axes.clear()
        img = Image.open(self.imgfile)
        img = np.asarray(img)
        self.axes.imshow(img)  
        self.axes.imshow(self.ROI, alpha=0.1, cmap='gray')
        self.plotSignal.emit()
        self.canvas.draw()
      
    def getROI(self, verts):
        ind = points_inside_poly(self.xys, verts)
        self.canvas.draw_idle()
        self.canvas.widgetlock.release(self.lasso)
        del self.lasso
        self.ROI = ind
        self.ROI = self.ROI.reshape((600,800), order='F')
        self.canvas.mpl_disconnect(self.cid)
        self.onDraw()

    def onpress(self, event):
        if self.canvas.widgetlock.locked(): return
        if event.inaxes is None: return
        self.lasso = Lasso(event.inaxes, (event.xdata, event.ydata), self.getROI)
        self.canvas.widgetlock(self.lasso)
        
    def onImageChanged(self, filename):
        '''
        Catches the signal from the ControlPanel that the current image has changed
        '''
        self.imgfile = str(filename)
        self.onDraw()
예제 #11
0
class Plotter(QMainWindow):
    ''' MainWindow consisting of menu bar, tab widget, tree list
    menu bar: load data
    tab widget: - dragWindow
                - widget consisting of picture viewer and two plots (raw and rms)
    tree list: to show loaded data, expand to see their channels and click to plot
    '''
    
    
    def __init__(self, parent=None):
        super(Plotter, self).__init__(parent)
        self.ui =  Ui_MainWindow()
        self.ui.setupUi(self)
        self.setCanvas()
        
        w = DragWindow()
        grid = QGridLayout()
        grid.addWidget(w)
        
        self.ui.tab_2.acceptDrops()
        self.ui.tab_2.setLayout(grid)
        
        self.moveConnection = None
        self.currentParent = None
        
        self.datas = []        
        self.offsetRecording = 0
        self.lines = [{}, {}]
    
    def setCanvas(self):
        ''' set widgets for a tab page
         - left side show image of current frame
         - right side show two plots: raw and rms
         - bot: button to edit triggers and slider to adjust 
                video-emg offset
        '''
        
        ########################
        # set matplotlib plots #
        ########################
        self.fig = Figure(dpi=70)
        self.canvas = FigureCanvas(self.fig)
        self.canvas.setParent(self.ui.mainFrame)
        self.axes = self.fig.add_subplot(211)
        self.axesRMS = self.fig.add_subplot(212)
        self.mpl_toolbar = NavigationToolbar2(self.canvas, self.ui.mainFrame)
        self.canvas.mpl_connect('draw_event', self.onDraw)
        
        ####################################
        # add button to matplotlib toolbar #
        #################################### 
        redb = QPushButton('Edit Triggers')
        redb.setCheckable(True)
        self.mpl_toolbar.addWidget(redb)
        redb.clicked[bool].connect(self.toggleEditMode)
        
        # container for current frame of video 
        layout = pq.GraphicsLayoutWidget()
        vb = layout.addViewBox()
        vb.setAspectLocked(True)
        
        self.ri = pq.ImageItem()
        vb.addItem(self.ri)
        
        # layout to organize elements
        grid = QGridLayout()
        wrapper = QWidget()        
        vbox = QVBoxLayout(wrapper)
        splitter = QSplitter()
        
        vbox.addWidget(self.canvas)
        vbox.addWidget(self.mpl_toolbar)
        wrapper.setLayout(vbox)
        
        splitter.addWidget(layout)
        splitter.addWidget(wrapper)
        
        grid.addWidget(splitter)
        
        self.ri.show()
        layout.show()
        self.ui.mainFrame.setLayout(grid)
        
    def loadData(self):
        ''' open dialog for choosing files to load '''
        fileNames, ok1= QFileDialog.getOpenFileNames(
                     self, self.tr("Open data"),
                     plattform.fixpath("D:\Master\emg\Benedictus"), self.tr("Pickle Files (*.pk)"))
        if ok1:
            self.load(fileNames)
    
    def load(self, fileNames):
        ''' unpickles data and add elements to tree view
        in case loading takes a while, a progress bar shows current status 
        '''
        progress = QProgressDialog("Loading files...", "Abort Copy", 0, len(fileNames), self)
        progress.setWindowModality(Qt.WindowModal)
        i = 0
        for fileName in fileNames:
            with open(fileName, 'rb') as ifile:
                tmp = pickle.load(ifile)
                if os.path.isfile(fileName[:-2] + "oni"):
                    tmp = tmp + (fileName[:-2]+"oni",)
                if isinstance(tmp, pyHPF.pyHPF):
                    self.datas.append(tmp)
                else:
                    self.datas.append(extendedPyHPF(tmp))
                self.updateTree(fileName[-5:])
                
                i+=1
                progress.setValue(i)

    def updateTree(self, name):
        ''' adds DataItem to tree view '''
        dataItem = QTreeWidgetItem(self.ui.dataView)
        dataItem.setText(0, name)
        dataItem.setFlags(Qt.ItemIsEnabled|Qt.ItemIsEditable)
        for i in range(len(self.datas[-1].data)/4):
            x = DataItem(dataItem)
            x.setText(0, self.datas[-1].name[i*4])
            x.setFlags(Qt.ItemIsEnabled|Qt.ItemIsSelectable)
            x.setNumData(self.datas[-1].data[i*4])
            if hasattr(self.datas[-1], 'triggers'):
                x.triggers = self.datas[-1].triggers
            if hasattr(self.datas[-1], 'frames'):
                x.depth = self.datas[-1].seekFrame
                
    def toggleEditMode(self, toggle):
        ''' button pressed to toggle edit mode for triggers
         - if pressed in edit mode again, dialog to save changes to
           a existing file
        '''
        if toggle:
            item = self.ui.dataView.selectedItems()[-1]
            self.interactionMode = Interactor(self.canvas,
                                              [self.axes, self.axesRMS], 
                                              item.triggers, self.lines, item.emg.shape[0])
        else:
            self.interactionMode = None
            reply = QMessageBox.question(self, 'QMessageBox.question()',
                             'Update triggers?',
                             QMessageBox.Yes | QMessageBox.No)
            if reply == QMessageBox.Yes:
                fileName, ok= QFileDialog.getOpenFileName(
                                    self, self.tr("Choose which file to overwrite data"),
                
                plattform.fixpath("D:\Master\emg\Benedictus"), self.tr("Pickle Files (*.pk)"))
                if ok:
                    save(self.ui.dataView.selectedItems()[-1].triggers, fileName)
        
    def plotData(self):
        ''' clicking a DataItem will plot the raw data and rms of
        emg data
        - if oni files are present, it will connect the mouse position inside
          plots to frame number of video 
        '''
        
        #################
        #   clearing    #
        #################
        self.axes.clear()
        self.axesRMS.clear()
        if self.moveConnection is not None:
            self.canvas.mpl_disconnect(self.moveConnection)
        
        ########################
        #  get selected item   #
        ########################
        if len(self.ui.dataView.selectedItems()) > 0:
            item = self.ui.dataView.selectedItems()[-1]
            if hasattr(item, 'depth'):
                self.ui.offsetSlider.setEnabled(True)
                ''' if selected channel is from other dataset
                 - slider is available
                 - current frame of video is set
                 - and new parent is saved
                '''
                if item.parent() is not self.currentParent:
                    self.offsetRecording = 0
                    self.ui.offsetSlider.setValue(150)
                    if self.currentParent:
                        self.currentParent.child(0).depth(-1)
                    self.ri.setImage(item.depth(0))
                    self.currentParent = item.parent()
        ########################
        # if deselected, reset #
        ########################
        else:
            self.ui.offsetSlider.setEnabled(False)
            self.ri.setImage(np.zeros((1,1,1)))
        
        ##########################
        # plot selected data     #
        ##########################
        for item in self.ui.dataView.selectedItems():
            c = int(item.text(0)[-1])-1
            if c >= len(colors):
                c = c % len(colors)
            self.axes.plot(item.emg, label=item.text(0), c=colors[c])
            self.axesRMS.plot(feature.rootMeanSquare(np.abs(item.emg), 200), c=colors[c])
            
            self.lines = [{}, {}]
            for p in item.triggers:
                self.lines[0][p], = self.axes.plot([p]*2, [np.min(item.emg), np.max(item.emg)], c='k', animated = True)
                self.lines[1][p], = self.axesRMS.plot([p]*2, [np.min(item.emg), np.max(item.emg)], c='k', animated = True)
            c+=1
        
        # connect mouse position inside plots to frame index of video
        self.moveConnection = self.canvas.mpl_connect("motion_notify_event", self.updateVideo)

        self.axes.legend()
        self.canvas.draw()
        
    def onDraw(self, event):
        ''' partially updating plots
        see: http://wiki.scipy.org/Cookbook/Matplotlib/Animations#head-3d51654b8306b1585664e7fe060a60fc76e5aa08
        '''
        items = self.ui.dataView.selectedItems()
        if len(items)== 0: return
        
        for i in range(2):
            ax = [self.axes, self.axesRMS]
            for t in items[-1].triggers:
                ax[i].draw_artist(self.lines[i][t])

        self.canvas.blit()
            
    def correctOffset(self, offs):
        self.offsetRecording = offs-150
        
    def updateVideo(self, event):
        ''' set frame index according to mouse position '''
        if event.xdata is not None:
            frameIdx = int(event.xdata/2000.0*30 + self.offsetRecording)
            if frameIdx < 0:
                frameIdx = 0
            if len(self.ui.dataView.selectedItems()) > 0:
                if hasattr(self.ui.dataView.selectedItems()[-1], 'depth'):
                    self.ri.setImage(self.ui.dataView.selectedItems()[-1].depth(frameIdx)) 
예제 #12
0
class Main(QMainWindow, Ui_MainWindow):
    def __init__(self, ):
        super(Main, self).__init__()
        self.setupUi(self)
        self.fig_dict = {}
        self.fg_dict  = {}
        
        self.mplfigs.itemClicked.connect(self.changefig)
        self.datacube.clicked.connect(self.selectFile)
        self.polarization.clicked.connect(self.selectPol)
        self.fetch.clicked.connect(self._fetch)
        self.res.clicked.connect(self._res)
        self.calc.clicked.connect(self._calc)
        self.reset.clicked.connect(self._reset)

        self.xst.valueChanged[str].connect(self.xchg)
        self.yst.valueChanged[str].connect(self.ychg)
        self.x_st, self.y_st = 0,0

        fig = Figure()
        self.fig = fig
        self.addmpl(fig, np.ones((100,100)))

        self.previous_point = []
        self.tempxpt,self.tempypt = [],[]
        self.allxpoints = []
        self.allypoints = []
        self.start_point = []
        self.end_point = []
        self.line = None
        self.roicolor = 'r'
        self.ax = plt.gca()
        self.dcube_path = '/Users/Mipanox/Desktop/coding_temp/SF/wzError/L1455_rgd.fits'
        self.poldt_path = '/Users/Mipanox/Desktop/coding_temp/SF/wzError/scupollegacy_l1455_cube.fits'
        self.dc_nm.setText(self.dcube_path)
        self.po_nm.setText(self.poldt_path)

        self.dn = None
        self.ds = None
        
        self.__ID2 = self.fig.canvas.mpl_connect(
            'button_press_event', self.__button_press_callback)

    def selectFile(self):
        self.dc_nm.setText(QFileDialog.getOpenFileName())
        self.dcube_path = unicode(self.dc_nm.text())
        
    def selectPol(self):
        self.po_nm.setText(QFileDialog.getOpenFileName())
        self.poldt_path = unicode(self.po_nm.text())

    def xchg(self):
        self.x_st = self.xst.value()
    def ychg(self):
        self.y_st = self.yst.value()

    def _fetch(self):
        if self.checkBox.isChecked() == True:
            self.__fet()
        else:
            self.dn = sfn(ds=self.dcube_path,name='foo',od=2.,bn=1.,
                          pol=self.poldt_path,du=1.5e-5)
            self.ds = sf(ds=self.dcube_path,name='foo',od=2.,bn=1.,
                         pol=self.poldt_path,du=1.5e-5)
            m0 = self.dn.m0
            m1 = self.dn.m1
        
            i = 0
            for n in [m0,m1]:
                fig_ = Figure()
                axf_ = fig_.add_subplot(111)
                cax = axf_.imshow(n,origin='lower')
                fig_.colorbar(cax)
                name = 'moment %s' %i
                self.fig_dict[name] = fig_
                self.fg_dict[name] = n
                self.mplfigs.addItem(name)
                i += 1
            
    def __fet(self):
        from astropy.utils.data import get_readable_fileobj
        from astropy.io import fits

        with get_readable_fileobj(self.dcube_path, cache=True) as f:
            fitsfile = fits.open(f)
            gd       = fitsfile[0].data[0][0]
            dshd     = fitsfile[0].header

        with get_readable_fileobj(self.poldt_path, cache=True) as e:
            fitsfile = fits.open(e)
            po       = fitsfile[0].data[0][0] + 90. # to B-field
            pshd     = fitsfile[0].header

        tx = 'gd-pol'
        fig = Figure()
        self.fig_dict[tx] = fig
        self.fg_dict[tx] = [gd,po]
        self.mplfigs.addItem(tx)
        axf = fig.add_subplot(111)
        self.__quiver(gd,po,axf,tx)

    def __quiver(self,gd,po,plt,tx):
        lx,ly = gd.shape
        y,x = np.mgrid[0:(lx-1):(lx)*1j, 0:(ly-1):(ly)*1j]

        gx,gy = -np.sin(np.radians(gd)),np.cos(np.radians(gd))
        px,py = -np.sin(np.radians(po)+90.),np.cos(np.radians(po)+90.)

        quiveropts = dict(headlength=0, pivot='middle',
                          scale=5e1, headaxislength=0)
        plt.axis('equal');
        plt.quiver(x,y,gx,gy,color='r',**quiveropts)
        plt.quiver(x,y,px,py,color='b',alpha=0.5,**quiveropts)
    
    def _calc(self):
        def avg_adj(ar,n): # reshaping even-indexed arrays
            (M,N) = ar.shape
            tt = np.zeros((M-n+1,N-n+1))
            for (x,y),i in np.ndenumerate(ar):
                if x > ar.shape[0]-n or y > ar.shape[1]-n: continue
                else:
                    ap = ar[slice(x,x+n),slice(y,y+n)]
                    tt[x,y] = ap.mean()
            return tt

        pol = self.dn._grad(pol=1)[0]
        grd = self.ds._grad()

        for i in range(1,len(grd)):
            if i % 2:
                pt = avg_adj(pol,i+1)
            else:
                pt = pol[i/2:-i/2,i/2:-i/2]
            gt = grd[i]
            tx = '%s x %s' %(i+1,i+1)
            fig = Figure()
            self.fig_dict[tx] = fig
            self.fg_dict[tx] = [gt,pt]
            self.mplfigs.addItem(tx)
            axf = fig.add_subplot(111)
            self.__quiver(gt,pt,axf,tx)

    def _res(self, item):
        cg,cp = self.fg[0],self.fg[1]
        
        if self.x_st >= 0 and self.y_st >= 0:
            gd = np.pad(cg,((0,2*self.x_st),(0,2*self.y_st)),
                        mode='constant', constant_values=(np.nan))
            po = np.pad(cp,((self.x_st,self.x_st),(self.y_st,self.y_st)),
                        mode='constant', constant_values=(np.nan))
        elif self.x_st < 0 and self.y_st >= 0:
            gd = np.pad(cg,((-self.x_st,-self.x_st),(0,2*self.y_st)),
                        mode='constant', constant_values=(np.nan))
            po = np.pad(cp,((0,-2*self.x_st),(self.y_st,self.y_st)),
                        mode='constant', constant_values=(np.nan))
        elif self.x_st >=0 and self.y_st < 0:
            gd = np.pad(cg,((0,2*self.x_st),(-self.y_st,-self.y_st)),
                        mode='constant', constant_values=(np.nan))
            po = np.pad(cp,((self.x_st,self.x_st),(0,-2*self.y_st)),
                        mode='constant', constant_values=(np.nan))
        else:
            gd = np.pad(cg,((-self.x_st,-self.x_st),(-self.y_st,-self.y_st)),
                        mode='constant', constant_values=(np.nan))
            po = np.pad(cp,((0,-2*self.x_st),(0,-2*self.y_st)),
                        mode='constant', constant_values=(np.nan))

        if self.x_st == 0 and self.y_st == 0: pass
        else:
            tx = '%s - (%s x %s) shifted' %(str(self.mplfigs.currentItem().text()),
                                            self.x_st,self.y_st)
            fig = Figure()
            self.fig_dict[tx] = fig
            self.fg_dict[tx] = [gd,po]
            self.mplfigs.addItem(tx)
            axf = fig.add_subplot(111)
            self.__quiver(gd,po,axf,tx)
            
        
        if len(self.allxpoints) > 0: # if roi selected
            if self.checkBox.isChecked() == True:
                tp = self.getMask(gd)
            else:
                self.tempxpt,self.tempypt = self.allxpoints,self.allypoints
                old_xd,old_yd = self.dn.m0.shape
                new_xd,new_yd = gd.shape
            
                for i in range(len(self.allxpoints)):
                    self.allxpoints[i] *= float(new_xd) / float(old_xd)
                    self.allypoints[i] *= float(new_yd) / float(old_yd)
        
                    tp = self.getMask(gd)
        
            gd[tp==False] = np.nan
            po[tp==False] = np.nan
        
        gx,gy = -np.sin(np.radians(gd)),np.cos(np.radians(gd))
        px,py = -np.sin(np.radians(po)),np.cos(np.radians(po))
        # +/- 90 doesn't matter

        overlay = np.sum(~np.isnan(gd) * ~np.isnan(po) * 1.)
        self.spsize.setText('%d' %(overlay))
        
        v_c = vc(v1=np.array([gx,gy]),v2=np.array([px,py]))
        self.rho_c.setText('%3e' %(v_c.corr_c()) )
        self.rho_h.setText('%3e' %(v_c.corr_h()) )
        
        self.allxpoints = self.tempxpt
        self.allypoints = self.tempypt

    def _reset(self):
        self.previous_point = []
        self.tempxpt,self.tempypt = [],[]
        self.allxpoints = []
        self.allypoints = []
        self.start_point = []
        self.end_point = []
        self.line = None

        ## remember to right-click before reset if changed frame
        self.ax.lines = []
        self.changefig(self.mplfigs.currentItem())

        self.xst.setValue(0)
        self.yst.setValue(0)
            
    def changefig(self, item):
        text = str(item.text())
        self.rmmpl()
        self.addmpl(self.fig_dict[text], self.fg_dict[text])

    def addmpl(self, fig, fg):
        self.canvas = FigureCanvas(fig)
        self.mplvl.addWidget(self.canvas)
        self.toolbar = NavigationToolbar(self.canvas,
                                         self.mplwindow, coordinates=True)
        self.mplvl.addWidget(self.toolbar)
        self.fg = fg
        
        self.ax = self.fig.add_subplot(111)
        self.canvas.draw()
        self.canvas.setFocusPolicy( Qt.ClickFocus )
        self.canvas.setFocus()
        self.canvas.mpl_connect('button_press_event', self.__button_press_callback)
        
    def rmmpl(self,):
        self.mplvl.removeWidget(self.canvas)
        self.canvas.close()
        self.mplvl.removeWidget(self.toolbar)
        self.toolbar.close()

    def __button_press_callback(self, event):
        if event.inaxes:
            x, y = event.xdata, event.ydata
            self.ax = event.inaxes
            if event.button == 1 and event.dblclick == False:  # If you press the left button, single click
                if self.line == None: # if there is no line, create a line
                    self.line = plt.Line2D([x, x],
                                           [y, y],
                                           marker='o',
                                           color=self.roicolor)
                    self.start_point = [x,y]
                    self.previous_point =  self.start_point
                    self.allxpoints=[x]
                    self.allypoints=[y]
                                                
                    self.ax.add_line(self.line)
                    self.canvas.draw()
                    # add a segment
                else: # if there is a line, create a segment
                    self.line = plt.Line2D([self.previous_point[0], x],
                                           [self.previous_point[1], y],
                                           marker = 'o',color=self.roicolor)
                    self.previous_point = [x,y]
                    self.allxpoints.append(x)
                    self.allypoints.append(y)
                                                                                
                    event.inaxes.add_line(self.line)
                    self.canvas.draw()
            elif ((event.button == 1 and event.dblclick==True) or
                  (event.button == 3 and event.dblclick==False)) and self.line != None: # close the loop and disconnect
                self.canvas.mpl_disconnect(self.__ID2) #joerg
                        
                self.line.set_data([self.previous_point[0],
                                    self.start_point[0]],
                                   [self.previous_point[1],
                                    self.start_point[1]])
                self.ax.add_line(self.line)
                self.canvas.draw()
                self.line = None
                                    
    def getMask(self, ci):
        ny, nx = ci.shape        
        poly_verts = [(self.allxpoints[0], self.allypoints[0])]
        for i in range(len(self.allxpoints)-1, -1, -1):
            poly_verts.append((self.allxpoints[i], self.allypoints[i]))
        
        x, y = np.meshgrid(np.arange(nx), np.arange(ny))
        x, y = x.flatten(), y.flatten()
        points = np.vstack((x,y)).T

        ROIpath = mplPath.Path(poly_verts)
        grid = ROIpath.contains_points(points).reshape((ny,nx))
        
        return grid
예제 #13
0
class ProfileDockWidget(QDockWidget):
    """
    DockWidget class to display the profile
    """

    closeSignal = pyqtSignal()

    def __init__(self, iface, geometry, mntButton=False, zerosButton=False):
        """
        Constructor
        :param iface: interface
        :param width: dock widget geometry
        """
        QDockWidget.__init__(self)
        self.setWindowTitle(QCoreApplication.translate("VDLTools", "Profile Tool"))
        self.__iface = iface
        self.__geom = geometry
        self.__canvas = self.__iface.mapCanvas()
        self.__types = ['PDF', 'PNG']  # ], 'SVG', 'PS']
        self.__libs = []
        if Qwt5_loaded:
            self.__lib = 'Qwt5'
            self.__libs.append('Qwt5')
            if matplotlib_loaded:
                self.__libs.append('Matplotlib')
        elif matplotlib_loaded:
            self.__lib = 'Matplotlib'
            self.__libs.append('Matplotlib')
        else:
            self.__lib = None
            self.__iface.messageBar().pushMessage(
                QCoreApplication.translate("VDLTools", "No graph lib available (qwt5 or matplotlib)"),
                level=QgsMessageBar.CRITICAL, duration=0)

        self.__doTracking = False
        self.__vline = None

        self.__profiles = None
        self.__numLines = None
        self.__mntPoints = None

        self.__marker = None
        self.__tabmouseevent = None

        if self.__geom is not None:
            self.setGeometry(self.__geom)

        self.__contentWidget = QWidget()
        self.setWidget(self.__contentWidget)

        self.__boxLayout = QHBoxLayout()
        self.__contentWidget.setLayout(self.__boxLayout)

        self.__plotFrame = QFrame()
        self.__frameLayout = QHBoxLayout()
        self.__plotFrame.setLayout(self.__frameLayout)

        self.__printLayout = QHBoxLayout()
        self.__printLayout.addWidget(self.__plotFrame)

        self.__legendLayout = QVBoxLayout()
        self.__printLayout.addLayout(self.__legendLayout)

        self.__printWdg = QWidget()
        self.__printWdg.setLayout(self.__printLayout)

        self.__plotWdg = None
        self.__changePlotWidget()

        size = QSize(150, 20)

        self.__boxLayout.addWidget(self.__printWdg)

        self.__vertLayout = QVBoxLayout()

        self.__libCombo = QComboBox()
        self.__libCombo.setFixedSize(size)
        self.__libCombo.addItems(self.__libs)
        self.__vertLayout.addWidget(self.__libCombo)
        self.__libCombo.currentIndexChanged.connect(self.__setLib)

        if mntButton:
            self.__displayMnt = False
            self.__mntButton = QPushButton(QCoreApplication.translate("VDLTools", "Display MNT"))
            self.__mntButton.setFixedSize(size)
            self.__mntButton.clicked.connect(self.__mnt)
            self.__vertLayout.addWidget(self.__mntButton)

        if zerosButton:
            self.__displayZeros = False
            self.__zerosButton = QPushButton(QCoreApplication.translate("VDLTools", "Display Zeros"))
            self.__zerosButton.setFixedSize(size)
            self.__zerosButton.clicked.connect(self.__zeros)
            self.__vertLayout.addWidget(self.__zerosButton)
        else:
            self.__displayZeros = True

        self.__maxLabel = QLabel("y max")
        self.__maxLabel.setFixedSize(size)
        self.__vertLayout.addWidget(self.__maxLabel)
        self.__maxSpin = QSpinBox()
        self.__maxSpin.setFixedSize(size)
        self.__maxSpin.setRange(-10000, 10000)
        self.__maxSpin.valueChanged.connect(self.__reScalePlot)
        self.__vertLayout.addWidget(self.__maxSpin)
        self.__vertLayout.insertSpacing(10, 20)

        self.__minLabel = QLabel("y min")
        self.__minLabel.setFixedSize(size)
        self.__vertLayout.addWidget(self.__minLabel)
        self.__minSpin = QSpinBox()
        self.__minSpin.setFixedSize(size)
        self.__minSpin.setRange(-10000, 10000)
        self.__minSpin.valueChanged.connect(self.__reScalePlot)
        self.__vertLayout.addWidget(self.__minSpin)
        self.__vertLayout.insertSpacing(10, 40)

        self.__typeCombo = QComboBox()
        self.__typeCombo.setFixedSize(size)
        self.__typeCombo.addItems(self.__types)
        self.__vertLayout.addWidget(self.__typeCombo)
        self.__saveButton = QPushButton(QCoreApplication.translate("VDLTools", "Save"))
        self.__saveButton.setFixedSize(size)
        self.__saveButton.clicked.connect(self.__save)
        self.__vertLayout.addWidget(self.__saveButton)

        self.__boxLayout.addLayout(self.__vertLayout)

        self.__maxSpin.setEnabled(False)
        self.__minSpin.setEnabled(False)

        self.__colors = []
        for cn in QColor.colorNames():
            qc = QColor(cn)
            val = qc.red() + qc.green() + qc.blue()
            if 0 < val < 450:
                self.__colors.append(cn)

    def mntButton(self):
        """
        To get the mnt button instance
        :return: mnt button instance
        """
        return self.__mntButton

    def zerosButton(self):
        """
        To get the zeros button instance
        :return: zeros button instance
        """
        return self.__zerosButton

    def displayMnt(self):
        """
        To get if we want to display mnt
        :return: true or false
        """
        return self.__displayMnt

    def __mnt(self):
        """
        To toggle mnt display choice
        """
        if self.__displayMnt:
            self.__displayMnt = False
            self.__mntButton.setText(QCoreApplication.translate("VDLTools", "Display MNT"))
        else:
            self.__displayMnt = True
            self.__mntButton.setText(QCoreApplication.translate("VDLTools", "Remove MNT"))

    def __zeros(self):
        """
        To toggle if we want to display zero elevations or not
        """
        if self.__displayZeros:
            self.__displayZeros = False
            self.__zerosButton.setText(QCoreApplication.translate("VDLTools", "Display Zeros"))
        else:
            self.__displayZeros = True
            self.__zerosButton.setText(QCoreApplication.translate("VDLTools", "Remove Zeros"))

    def __changePlotWidget(self):
        """
        When plot widget is change (qwt <-> matplotlib)
        """
        self.__activateMouseTracking(False)
        while self.__frameLayout.count():
            child = self.__frameLayout.takeAt(0)
            child.widget().deleteLater()
        self.__plotWdg = None

        if self.__lib == 'Qwt5':
            self.__plotWdg = QwtPlot(self.__plotFrame)
            sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
            sizePolicy.setHorizontalStretch(10)
            sizePolicy.setVerticalStretch(0)
            self.__plotWdg.setSizePolicy(sizePolicy)
            self.__plotWdg.setAutoFillBackground(False)
            # Decoration
            self.__plotWdg.setCanvasBackground(Qt.white)
            self.__plotWdg.plotLayout().setAlignCanvasToScales(False)
            self.__plotWdg.plotLayout().setSpacing(100)
            self.__plotWdg.plotLayout().setCanvasMargin(10, QwtPlot.xBottom)
            self.__plotWdg.plotLayout().setCanvasMargin(10, QwtPlot.yLeft)
            title = QwtText(QCoreApplication.translate("VDLTools", "Distance [m]"))
            title.setFont(QFont("Helvetica", 10))
            self.__plotWdg.setAxisTitle(QwtPlot.xBottom, title)
            title.setText(QCoreApplication.translate("VDLTools", "Elevation [m]"))
            title.setFont(QFont("Helvetica", 10))
            self.__plotWdg.setAxisTitle(QwtPlot.yLeft, title)
            self.__zoomer = QwtPlotZoomer(QwtPlot.xBottom, QwtPlot.yLeft, QwtPicker.DragSelection, QwtPicker.AlwaysOff,
                                          self.__plotWdg.canvas())
            self.__zoomer.setRubberBandPen(QPen(Qt.blue))
            grid = QwtPlotGrid()
            grid.setPen(QPen(QColor('grey'), 0, Qt.DotLine))
            grid.attach(self.__plotWdg)
            self.__frameLayout.addWidget(self.__plotWdg)

        elif self.__lib == 'Matplotlib':
            fig = Figure((1.0, 1.0), linewidth=0.0, subplotpars=SubplotParams(left=0, bottom=0, right=1, top=1,
                                                                              wspace=0, hspace=0))

            font = {'family': 'arial', 'weight': 'normal', 'size': 12}
            rc('font', **font)

            rect = fig.patch
            rect.set_facecolor((0.9, 0.9, 0.9))

            self.__axes = fig.add_axes((0.07, 0.16, 0.92, 0.82))
            self.__axes.set_xbound(0, 1000)
            self.__axes.set_ybound(0, 1000)
            self.__manageMatplotlibAxe(self.__axes)
            self.__plotWdg = FigureCanvasQTAgg(fig)
            sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
            sizePolicy.setHorizontalStretch(0)
            sizePolicy.setVerticalStretch(0)
            self.__plotWdg.setSizePolicy(sizePolicy)
            self.__frameLayout.addWidget(self.__plotWdg)

    def setProfiles(self, profiles, numLines):
        """
        To set the profiles
        :param profiles: profiles : positions with elevations (for line and points)
        :param numLines: number of selected connected lines
        """
        self.__numLines = numLines
        self.__profiles = profiles
        if self.__lib == 'Matplotlib':
            self.__prepare_points()

    def __getLinearPoints(self):
        """
        To extract the linear points of the profile
        """
        profileLen = 0
        self.__profiles[0]['l'] = profileLen
        for i in range(0, len(self.__profiles)-1):
            x1 = float(self.__profiles[i]['x'])
            y1 = float(self.__profiles[i]['y'])
            x2 = float(self.__profiles[i+1]['x'])
            y2 = float(self.__profiles[i+1]['y'])
            profileLen += sqrt(((x2-x1)*(x2-x1)) + ((y2-y1)*(y2-y1)))
            self.__profiles[i+1]['l'] = profileLen

    def __getMnt(self, settings):
        """
        To get the MN data for the profile
        :param settings: settings containing MN url
        """
        if settings is None or settings.mntUrl is None or settings.mntUrl == "None":
            url = 'https://map.lausanne.ch/prod/wsgi/profile.json'
        elif settings.mntUrl == "":
            return
        else:
            url = settings.mntUrl
        names = ['MNT', 'MNS', 'Rocher (approx.)']
        data = "layers=MNT%2CMNS%2CRocher%20(approx.)&geom=%7B%22type%22%3A%22LineString%22%2C%22coordinates%22%3A%5B"

        pos = 0
        for i in range(len(self.__profiles)):
            if pos > 0:
                data += "%2C"
            pos += 1
            data += "%5B" + str(self.__profiles[i]['x']) + "%2C" + str(self.__profiles[i]['y']) + "%5D"
        data += "%5D%7D&nbPoints=" + str(int(self.__profiles[len(self.__profiles)-1]['l']+1))
        try:
            response = requests.post(url, data=data)
            j = response.text
            j_obj = json.loads(j)
            profile = j_obj['profile']
            self.__mntPoints = []
            self.__mntPoints.append(names)
            mnt_l = []
            mnt_z = []
            for p in range(len(names)):
                z = []
                mnt_z.append(z)
            for pt in profile:
                mnt_l.append(float(pt['dist']))
                values = pt['values']
                for p in range(len(names)):
                    if names[p] in values:
                        mnt_z[p].append(float(values[names[p]]))
                    else:
                        mnt_z[p].append(None)
            self.__mntPoints.append(mnt_l)
            self.__mntPoints.append(mnt_z)
        except HTTPError as e:
            self.__iface.messageBar().pushMessage(
                QCoreApplication.translate("VDLTools", "HTTP Error"),
                QCoreApplication.translate("VDLTools", "status error") + "[" + str(e.code) + "] : " + e.reason,
                level=QgsMessageBar.CRITICAL, duration=0)
        except URLError as e:
            self.__iface.messageBar().pushMessage(
                QCoreApplication.translate("VDLTools", "URL Error"),
                e.reason, level=QgsMessageBar.CRITICAL, duration=0)
        except ValueError as e:
            self.__iface.messageBar().pushMessage(
                QCoreApplication.translate("VDLTools", "No MNT values here"),
                level=QgsMessageBar.CRITICAL, duration=0)


    def attachCurves(self, names, settings, usedMnts):
        """
        To attach the curves for the layers to the profile
        :param names: layers names
        """
        if (self.__profiles is None) or (self.__profiles == 0):
            return

        self.__getLinearPoints()
        if usedMnts is not None and (usedMnts[0] or usedMnts[1] or usedMnts[2]):
            self.__getMnt(settings)

        c = 0

        if self.__mntPoints is not None:
                for p in range(len(self.__mntPoints[0])):
                    if usedMnts[p]:
                        legend = QLabel("<font color='" + self.__colors[c] + "'>" + self.__mntPoints[0][p]
                                        + "</font>")
                        self.__legendLayout.addWidget(legend)

                        if self.__lib == 'Qwt5':

                            xx = [list(g) for k, g in itertools.groupby(self.__mntPoints[1],
                                                                        lambda x: x is None) if not k]
                            yy = [list(g) for k, g in itertools.groupby(self.__mntPoints[2][p], lambda x: x is None)
                                  if not k]

                            for j in range(len(xx)):
                                curve = QwtPlotCurve(self.__mntPoints[0][p])
                                curve.setData(xx[j], yy[j])
                                curve.setPen(QPen(QColor(self.__colors[c]), 3))
                                curve.attach(self.__plotWdg)

                        elif self.__lib == 'Matplotlib':
                            qcol = QColor(self.__colors[c])
                            self.__plotWdg.figure.get_axes()[0].plot(self.__mntPoints[1], self.__mntPoints[2][p],
                                                                     gid=self.__mntPoints[0][p], linewidth=3)
                            tmp = self.__plotWdg.figure.get_axes()[0].get_lines()
                            for t in range(len(tmp)):
                                if self.__mntPoints[0][p] == tmp[t].get_gid():
                                    tmp[c].set_color((old_div(qcol.red(), 255.0), old_div(qcol.green(), 255.0),
                                                      old_div(qcol.blue(), 255.0), old_div(qcol.alpha(), 255.0)))
                                    self.__plotWdg.draw()
                                    break
                        c += 1

        if 'z' in self.__profiles[0]:
            for i in range(len(self.__profiles[0]['z'])):
                if i < self.__numLines:
                    v = 0
                else:
                    v = i - self.__numLines + 1
                name = names[v]
                xx = []
                yy = []
                for prof in self.__profiles:
                    xx.append(prof['l'])
                    yy.append(prof['z'][i])

                for j in range(len(yy)):
                    if yy[j] is None:
                        xx[j] = None

                if i == 0 or i > (self.__numLines-1):
                    legend = QLabel("<font color='" + self.__colors[c] + "'>" + name + "</font>")
                    self.__legendLayout.addWidget(legend)

                if self.__lib == 'Qwt5':

                    # Split xx and yy into single lines at None values
                    xx = [list(g) for k, g in itertools.groupby(xx, lambda x: x is None) if not k]
                    yy = [list(g) for k, g in itertools.groupby(yy, lambda x: x is None) if not k]

                    # Create & attach one QwtPlotCurve per one single line
                    for j in range(len(xx)):
                        curve = QwtPlotCurve(name)
                        curve.setData(xx[j], yy[j])
                        curve.setPen(QPen(QColor(self.__colors[c]), 3))
                        if i > (self.__numLines-1):
                            curve.setStyle(QwtPlotCurve.Dots)
                            pen = QPen(QColor(self.__colors[c]), 8)
                            pen.setCapStyle(Qt.RoundCap)
                            curve.setPen(pen)
                        curve.attach(self.__plotWdg)

                elif self.__lib == 'Matplotlib':
                    qcol = QColor(self.__colors[c])
                    if i < self.__numLines:
                        self.__plotWdg.figure.get_axes()[0].plot(xx, yy, gid=name, linewidth=3)
                    else:
                        self.__plotWdg.figure.get_axes()[0].plot(xx, yy, gid=name, linewidth=5, marker='o',
                                                                 linestyle='None')
                    tmp = self.__plotWdg.figure.get_axes()[0].get_lines()
                    for t in range(len(tmp)):
                        if name == tmp[t].get_gid():
                            tmp[c].set_color((old_div(qcol.red(), 255.0), old_div(qcol.green(), 255.0),
                                              old_div(qcol.blue(), 255.0), old_div(qcol.alpha(), 255.0)))
                            self.__plotWdg.draw()
                            break
                c += 1

        # scaling this
        try:
            self.__reScalePlot(None, True)
        except:
            self.__iface.messageBar().pushMessage(
                QCoreApplication.translate("VDLTools", "Rescale problem... (trace printed)"),
                level=QgsMessageBar.CRITICAL, duration=0)
            print(sys.exc_info()[0], traceback.format_exc())
        if self.__lib == 'Qwt5':
            self.__plotWdg.replot()
        elif self.__lib == 'Matplotlib':
            self.__plotWdg.figure.get_axes()[0].redraw_in_frame()
            self.__plotWdg.draw()
            self.__activateMouseTracking(True)
            self.__marker.show()

    def __reScalePlot(self, value=None, auto=False):
        """
        To rescale the profile plot depending to the bounds
        :param value: juste because connections give value
        :param auto: if automatic ranges calcul is wanted
        """
        if (self.__profiles is None) or (self.__profiles == 0):
            self.__plotWdg.replot()
            return

        maxi = 0
        for i in range(len(self.__profiles)):
            if (ceil(self.__profiles[i]['l'])) > maxi:
                maxi = ceil(self.__profiles[i]['l'])
        if self.__lib == 'Qwt5':
            self.__plotWdg.setAxisScale(2, 0, maxi, 0)
        elif self.__lib == 'Matplotlib':
            self.__plotWdg.figure.get_axes()[0].set_xbound(0, maxi)

        minimumValue = self.__minSpin.value()
        maximumValue = self.__maxSpin.value()

        # to set max y and min y displayed
        if auto:
            minimumValue = 1000000000
            maximumValue = -1000000000
            for i in range(len(self.__profiles)):
                if 'z' in self.__profiles[i]:
                    mini = self.__minTab(self.__profiles[i]['z'])
                    if (mini > 0 or self.__displayZeros) and mini < minimumValue:
                        minimumValue = ceil(mini) - 1
                    maxi = self.__maxTab(self.__profiles[i]['z'])
                    if maxi > maximumValue:
                        maximumValue = floor(maxi) + 1
                if self.__mntPoints is not None:
                    for pts in self.__mntPoints[2]:
                        miniMnt = self.__minTab(pts)
                        if (miniMnt > 0 or self.__displayZeros) and miniMnt < minimumValue:
                            minimumValue = ceil(miniMnt) - 1
                        maxiMnt = self.__maxTab(pts)
                        if maxiMnt > maximumValue:
                            maximumValue = floor(maxiMnt) + 1
        self.__maxSpin.setValue(maximumValue)
        self.__minSpin.setValue(minimumValue)
        self.__maxSpin.setEnabled(True)
        self.__minSpin.setEnabled(True)

        if self.__lib == 'Qwt5':
            rect = QRectF(0, minimumValue, maxi, maximumValue-minimumValue)
            self.__zoomer.setZoomBase(rect)

        # to draw vertical lines
        for i in range(len(self.__profiles)):
            zz = []
            for j in range(self.__numLines):
                if self.__profiles[i]['z'][j] is not None:
                    zz.append(j)
            color = None
            if len(zz) == 2:
                width = 3
                color = QColor('red')
            else:
                width = 1

            if self.__lib == 'Qwt5':
                vertLine = QwtPlotMarker()
                vertLine.setLineStyle(QwtPlotMarker.VLine)
                pen = vertLine.linePen()
                pen.setWidth(width)
                if color is not None:
                    pen.setColor(color)
                vertLine.setLinePen(pen)
                vertLine.setXValue(self.__profiles[i]['l'])
                label = vertLine.label()
                label.setText(str(i))
                vertLine.setLabel(label)
                vertLine.setLabelAlignment(Qt.AlignLeft)
                vertLine.attach(self.__plotWdg)
            elif self.__lib == 'Matplotlib':
                self.__plotWdg.figure.get_axes()[0].vlines(self.__profiles[i]['l'], minimumValue, maximumValue,
                                                           linewidth=width)

        if minimumValue < maximumValue:
            if self.__lib == 'Qwt5':
                self.__plotWdg.setAxisScale(0, minimumValue, maximumValue, 0)
                self.__plotWdg.replot()
            elif self.__lib == 'Matplotlib':
                self.__plotWdg.figure.get_axes()[0].set_ybound(minimumValue, maximumValue)
                self.__plotWdg.figure.get_axes()[0].redraw_in_frame()
                self.__plotWdg.draw()

    @staticmethod
    def __minTab(tab):
        """
        To get the minimum value in a table
        :param tab: table to scan
        :return: minimum value
        """
        mini = 1000000000
        for t in tab:
            if t is None:
                continue
            if t < mini:
                mini = t
        return mini

    @staticmethod
    def __maxTab(tab):
        """
        To get the maximum value in a table
        :param tab: table to scan
        :return: maximum value
        """
        maxi = -1000000000
        for t in tab:
            if t is None:
                continue
            if t > maxi:
                maxi = t
        return maxi

    def __setLib(self):
        """
        To set the new widget library (qwt <-> matplotlib)
        """
        self.__lib = self.__libs[self.__libCombo.currentIndex()]
        self.__changePlotWidget()

    def __save(self):
        """
        To save the profile in a file, on selected format
        """
        idx = self.__typeCombo.currentIndex()
        if idx == 0:
            self.__outPDF()
        elif idx == 1:
            self.__outPNG()
        else:
            self.__iface.messageBar().pushMessage(
                QCoreApplication.translate("VDLTools", "Invalid index ") + str(idx),
                level=QgsMessageBar.CRITICAL, duration=0)

    def __outPDF(self):
        """
        To save the profile as pdf file
        """
        fileName = QFileDialog.getSaveFileName(
            self.__iface.mainWindow(), QCoreApplication.translate("VDLTools", "Save As"),
            QCoreApplication.translate("VDLTools", "Profile.pdf"),"Portable Document Format (*.pdf)")
        if fileName is not None:
            if self.__lib == 'Qwt5':
                printer = QPrinter()
                printer.setCreator(QCoreApplication.translate("VDLTools", "QGIS Profile Plugin"))
                printer.setOutputFileName(fileName)
                printer.setOutputFormat(QPrinter.PdfFormat)
                printer.setOrientation(QPrinter.Landscape)
                self.__plotWdg.print_(printer)
            elif self.__lib == 'Matplotlib':
                self.__plotWdg.figure.savefig(str(fileName))

    def __outPNG(self):
        """
        To save the profile as png file
        """
        fileName = QFileDialog.getSaveFileName(
            self.__iface.mainWindow(), QCoreApplication.translate("VDLTools", "Save As"),
            QCoreApplication.translate("VDLTools", "Profile.png"),"Portable Network Graphics (*.png)")
        if fileName is not None:
            QPixmap.grabWidget(self.__printWdg).save(fileName, "PNG")

    def clearData(self):
        """
        To clear the displayed data
        """
        if self.__profiles is None:
            return
        if self.__lib == 'Qwt5':
            self.__plotWdg.clear()
            self.__profiles = None
            temp1 = self.__plotWdg.itemList()
            for j in range(len(temp1)):
                if temp1[j].rtti() == QwtPlotItem.Rtti_PlotCurve:
                    temp1[j].detach()
        elif self.__lib == 'Matplotlib':
            self.__plotWdg.figure.get_axes()[0].cla()
            self.__manageMatplotlibAxe(self.__plotWdg.figure.get_axes()[0])
        self.__maxSpin.setEnabled(False)
        self.__minSpin.setEnabled(False)
        self.__maxSpin.setValue(0)
        self.__minSpin.setValue(0)

        # clear legend
        while self.__legendLayout.count():
            child = self.__legendLayout.takeAt(0)
            child.widget().deleteLater()

    def __manageMatplotlibAxe(self, axe):
        """
        To manage the axes for matplotlib library
        :param axe: the axes element
        """
        axe.grid()
        axe.tick_params(axis="both", which="major", direction="out", length=10, width=1, bottom=True, top=False,
                        left=True, right=False)
        axe.minorticks_on()
        axe.tick_params(axis="both", which="minor", direction="out", length=5, width=1, bottom=True, top=False,
                        left=True, right=False)
        axe.set_xlabel(QCoreApplication.translate("VDLTools", "Distance [m]"))
        axe.set_ylabel(QCoreApplication.translate("VDLTools", "Elevation [m]"))

    def __activateMouseTracking(self, activate):
        """
        To (de)activate the mouse tracking on the profile for matplotlib library
        :param activate: true to activate, false to deactivate
        """
        if activate:
            self.__doTracking = True
            self.__loadRubber()
            self.cid = self.__plotWdg.mpl_connect('motion_notify_event', self.__mouseevent_mpl)
        elif self.__doTracking:
            self.__doTracking = False
            self.__plotWdg.mpl_disconnect(self.cid)
            if self.__marker is not None:
                self.__canvas.scene().removeItem(self.__marker)
            try:
                if self.__vline is not None:
                    self.__plotWdg.figure.get_axes()[0].lines.remove(self.__vline)
                    self.__plotWdg.draw()
            except Exception as e:
                self.__iface.messageBar().pushMessage(
                    QCoreApplication.translate("VDLTools", "Tracking exception : ") + str(e),
                    level=QgsMessageBar.CRITICAL, duration=0)

    def __mouseevent_mpl(self, event):
        """
        To manage matplotlib mouse tracking event
        :param event: mouse tracking event
        """
        if event.xdata is not None:
            try:
                if self.__vline is not None:
                    self.__plotWdg.figure.get_axes()[0].lines.remove(self.__vline)
            except Exception as e:
                self.__iface.messageBar().pushMessage(
                    QCoreApplication.translate("VDLTools", "Mouse event exception : ") + str(e),
                    level=QgsMessageBar.CRITICAL, duration=0)
            xdata = float(event.xdata)
            self.__vline = self.__plotWdg.figure.get_axes()[0].axvline(xdata, linewidth=2, color='k')
            self.__plotWdg.draw()
            i = 1
            while i < len(self.__tabmouseevent)-1 and xdata > self.__tabmouseevent[i][0]:
                i += 1
            i -= 1

            x = self.__tabmouseevent[i][1] + (self.__tabmouseevent[i + 1][1] - self.__tabmouseevent[i][1]) / (
            self.__tabmouseevent[i + 1][0] - self.__tabmouseevent[i][0]) * (xdata - self.__tabmouseevent[i][0])
            y = self.__tabmouseevent[i][2] + (self.__tabmouseevent[i + 1][2] - self.__tabmouseevent[i][2]) / (
            self.__tabmouseevent[i + 1][0] - self.__tabmouseevent[i][0]) * (xdata - self.__tabmouseevent[i][0])
            self.__marker.show()
            self.__marker.setCenter(QgsPoint(x, y))

    def __loadRubber(self):
        """
        To load te rubber band for mouse tracking on map
        """
        self.__marker = QgsVertexMarker(self.__canvas)
        self.__marker.setIconSize(5)
        self.__marker.setIconType(QgsVertexMarker.ICON_BOX)
        self.__marker.setPenWidth(3)

    def __prepare_points(self):
        """
        To prepare the points on map for mouse tracking on profile
        """
        self.__tabmouseevent = []
        length = 0
        for i, point in enumerate(self.__profiles):
            if i == 0:
                self.__tabmouseevent.append([0, point['x'], point['y']])
            else:
                length += ((self.__profiles[i]['x'] - self.__profiles[i-1]['x']) ** 2 +
                           (self.__profiles[i]['y'] - self.__profiles[i-1]['y']) ** 2) ** 0.5
                self.__tabmouseevent.append([float(length), float(point['x']), float(point['y'])])

    def closeEvent(self, event):
        """
        When the dock widget is closed
        :param event: close event
        """
        if self.__maxSpin is not None:
            Signal.safelyDisconnect(self.__maxSpin.valueChanged, self.__reScalePlot)
            self.__maxSpin = None
        if self.__minSpin is not None:
            Signal.safelyDisconnect(self.__minSpin.valueChanged, self.__reScalePlot)
            self.__minSpin = None
        if self.__saveButton is not None:
            Signal.safelyDisconnect(self.__saveButton.clicked, self.__save)
            self.__saveButton = None
        if self.__libCombo is not None:
            Signal.safelyDisconnect(self.__libCombo.currentIndexChanged, self.__setLib)
            self.__libCombo = None
        self.closeSignal.emit()
        if self.__marker is not None:
            self.__marker.hide()
        QDockWidget.closeEvent(self, event)