Esempio n. 1
0
class Window(QtGui.QDialog):
    def __init__(self, parent=None):
        super(Window, self).__init__(parent)
 
        self.figure = plt.figure()
        self.canvas = FigureCanvas(self.figure)
 
         
        self.toolbar = NavigationToolbar(self.canvas, self)
        # self.toolbar.hide()
 
        # Just some button 
        self.button = QtGui.QPushButton('Plot')
        self.button.clicked.connect(self.plot)
 
        self.button1 = QtGui.QPushButton('Zoom')
        self.button1.clicked.connect(self.zoom)
         
        self.button2 = QtGui.QPushButton('Pan')
        self.button2.clicked.connect(self.pan)
         
        self.button3 = QtGui.QPushButton('Home')
        self.button3.clicked.connect(self.home)
 
 
        # set the layout
        layout = QtGui.QVBoxLayout()
        layout.addWidget(self.toolbar)
        layout.addWidget(self.canvas)
        layout.addWidget(self.button)
        layout.addWidget(self.button1)
        layout.addWidget(self.button2)
        layout.addWidget(self.button3)
        self.setLayout(layout)
 
    def home(self):
        self.toolbar.home()
    def zoom(self):
        self.toolbar.zoom()
    def pan(self):
        self.toolbar.pan()
         
    def plot(self):
        ''' plot some random stuff '''
        data = [random.random() for i in range(25)]
        ax = self.figure.add_subplot(211)
        ax.hold(False)
        ax.plot(data, '*-')

        bx = self.figure.add_subplot(212)
        bx.hold(False)
        bx.plot(data, '*-')

        self.canvas.draw()
Esempio n. 2
0
class MPlotWidget(QtGui.QWidget):
    def __init__(self, parent=None):
        super(MPlotWidget, self).__init__(parent)

        self.figure = plt.figure()
        self.canvas = FigureCanvas(self.figure)

        self.toolbar = NavigationToolbar(self.canvas, self)
        self.toolbar.hide()

        self.plotbutton = QtGui.QPushButton('Plot')

        self.zoombutton = QtGui.QPushButton('Zoom')
        self.zoombutton.clicked.connect(self.zoom)

        self.panbutton = QtGui.QPushButton('Pan')
        self.panbutton.clicked.connect(self.pan)

        self.homebutton = QtGui.QPushButton('Home')
        self.homebutton.clicked.connect(self.home)

        self.savebutton = QtGui.QPushButton('Save')
        self.savebutton.clicked.connect(self.save)

        layout = QtGui.QVBoxLayout()

        layout.addWidget(self.toolbar)
        layout.addWidget(self.canvas)

        buttonbox = QtGui.QHBoxLayout()
        buttonbox.addWidget(self.plotbutton)
        buttonbox.addWidget(self.zoombutton)
        buttonbox.addWidget(self.panbutton)
        buttonbox.addWidget(self.homebutton)
        buttonbox.addWidget(self.savebutton)

        layout.addLayout(buttonbox)
        self.setLayout(layout)

        self.ax = self.figure.add_subplot(111)
        self.ax.hold(False)

    def home(self):
        self.toolbar.home()
    def zoom(self):
        self.toolbar.zoom()
    def pan(self):
        self.toolbar.pan()
    def save(self):
        self.figure.savefig('1.png')

    """
Esempio n. 3
0
class Window(QtGui.QDialog):
    def __init__(self, parent=None):
        super(Window, self).__init__(parent)

        self.figure = plt.figure()
        self.canvas = FigureCanvas(self.figure)


        self.toolbar = NavigationToolbar(self.canvas, self)
        self.toolbar.hide()

        # Just some button
        self.button = QtGui.QPushButton('Plot')
        self.button.clicked.connect(self.plot)

        self.button1 = QtGui.QPushButton('Zoom')
        self.button1.clicked.connect(self.zoom)

        self.button2 = QtGui.QPushButton('Pan')
        self.button2.clicked.connect(self.pan)

        self.button3 = QtGui.QPushButton('Home')
        self.button3.clicked.connect(self.home)


        # set the layout
        layout = QtGui.QVBoxLayout()
        layout.addWidget(self.toolbar)
        layout.addWidget(self.canvas)
        layout.addWidget(self.button)
        layout.addWidget(self.button1)
        layout.addWidget(self.button2)
        layout.addWidget(self.button3)
        self.setLayout(layout)

    def home(self):
        self.toolbar.home()
    def zoom(self):
        self.toolbar.zoom()
    def pan(self):
        self.toolbar.pan()

    def plot(self):
        ''' plot some random stuff '''
        data = [random.random() for i in range(25)]
        ax = self.figure.add_subplot(111)
        ax.hold(False)
        ax.plot(data, '*-')
        self.canvas.draw()
Esempio n. 4
0
    def zoom(self, *args):
        """
        Turn on/off zoom (zoom button)
        :param args:
        :return:
        """
        NavigationToolbar2.zoom(self, args)

        if self._navigationMode == MyNavigationToolbar.NAVIGATION_MODE_ZOOM:
            # out of zoom mode
            self._navigationMode = MyNavigationToolbar.NAVIGATION_MODE_NONE
        else:
            # into zoom mode
            self._navigationMode = MyNavigationToolbar.NAVIGATION_MODE_ZOOM

        return
Esempio n. 5
0
    def zoom(self, *args):
        """
        Turn on/off zoom (zoom button)
        :param args:
        :return:
        """
        NavigationToolbar2.zoom(self, args)

        if self._navigationMode == MyNavigationToolbar.NAVIGATION_MODE_ZOOM:
            # out of zoom mode
            self._navigationMode = MyNavigationToolbar.NAVIGATION_MODE_NONE
        else:
            # into zoom mode
            self._navigationMode = MyNavigationToolbar.NAVIGATION_MODE_ZOOM

        return
Esempio n. 6
0
	def zoom( self, *args ):
		self.resetActionsState( self.zoomAction )
		NavigationToolbar2QTAgg.zoom( self, *args )
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()
class Window(QtGui.QDialog):
    def __init__(self, parent=None):
        super(Window, self).__init__(parent)

        self.figure = plt.figure()
        self.canvas = FigureCanvas(self.figure)
        self.canvas.setSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Expanding)

        self.toolbar = NavigationToolbar(self.canvas, self)
        self.toolbar.hide()

        # Timestream info
        self.timestreamLabel = QtGui.QLabel('Time-stream root path:')
        self.timestreamText = QtGui.QLineEdit('')
        self.timestreamText.setSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Fixed)

        self.timestreamDateLabel = QtGui.QLabel('Start date (yyyy_mm_dd_hh_mm_ss):')
        self.timestreamDateText = QtGui.QLineEdit('')
        self.timestreamDateText.setSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Fixed)

        self.timestreamTimeLabel = QtGui.QLabel('Time interval (seconds):')
        self.timestreamTimeText = QtGui.QLineEdit('')
        self.timestreamTimeText.setSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Fixed)

        self.initStreamTimeButton = QtGui.QPushButton('&Initialise timestream by time')
        self.initStreamTimeButton.clicked.connect(self.initialiseTimestreamByTime)

        self.initStreamFileButton = QtGui.QPushButton('&Initialise timestream by files')
        self.initStreamFileButton.clicked.connect(self.initialiseTimestreamByFiles)

        # Image loading and processing
        self.loadImageButton = QtGui.QPushButton('&Load (next) image')
        self.loadImageButton.clicked.connect(self.loadImage)

        self.rotateImageButton = QtGui.QPushButton('&Rotate 90-deg')
        self.rotateImageButton.clicked.connect(self.rotateImage90Degrees)

        self.slider = QtGui.QSlider(QtCore.Qt.Horizontal)
        self.slider.setFocusPolicy(QtCore.Qt.StrongFocus)
        self.slider.setSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Fixed)
        self.slider.setTickPosition(QtGui.QSlider.TicksBothSides)
        self.slider.setMinimum(-16)
        self.slider.setMaximum(16)
        self.slider.setValue(0)
        self.slider.setTickInterval(4)
        self.slider.setSingleStep(1)
        self.slider.valueChanged.connect(self.rotateSmallAngle)

        self.applySmallRotationButton = QtGui.QPushButton('&Apply')
        self.applySmallRotationButton.clicked.connect(self.applySmallRotation)

        self.loadCamCalibButton = QtGui.QPushButton('Load &cam. param.')
        self.loadCamCalibButton.clicked.connect(self.loadCamCalib)

        self.colorcardRadioButton = QtGui.QRadioButton('Select color car&d')
        self.colorcardRadioButton.setChecked(False)
        self.colorcardRadioButton.clicked.connect(self.selectWhat)

        self.trayRadioButton = QtGui.QRadioButton('Select &tray')
        self.trayRadioButton.setChecked(False)
        self.trayRadioButton.clicked.connect(self.selectWhat)

        self.trayRoundCheckBox = QtGui.QCheckBox('Round')
        self.trayRoundCheckBox.setChecked(True)

        self.potRadioButton = QtGui.QRadioButton('Select &pot')
        self.potRadioButton.setChecked(False)
        self.potRadioButton.clicked.connect(self.selectWhat)

        self.zoomButton = QtGui.QPushButton('&Zoom')
        self.zoomButton.setCheckable(True)
        self.zoomButton.clicked.connect(self.zoom)

        self.panButton = QtGui.QPushButton('&Pan')
        self.panButton.setCheckable(True)
        self.panButton.clicked.connect(self.pan)

        self.homeButton = QtGui.QPushButton('&Home')
        self.homeButton.clicked.connect(self.home)

        self.correctColorButton = QtGui.QPushButton('Correct colo&r')
        self.correctColorButton.clicked.connect(self.correctColor)

        self.save2PipelineButton = QtGui.QPushButton('&Save as pipeline settings')
        self.save2PipelineButton.clicked.connect(self.savePipelineSettings)

        self.testPipelineButton = QtGui.QPushButton('Test &pipeline processing')
        self.testPipelineButton.clicked.connect(self.testPipeline)

        self.status = QtGui.QTextEdit('')
        self.status.setSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Expanding)
        self.mousePosition = QtGui.QLabel('')

        # set the layout
        layout = QtGui.QHBoxLayout()
        rightWidget = QtGui.QWidget()
        buttonlayout = QtGui.QVBoxLayout(rightWidget)
        buttonlayout.addWidget(self.timestreamLabel)
        buttonlayout.addWidget(self.timestreamText)
        buttonlayout.addWidget(self.timestreamDateLabel)
        buttonlayout.addWidget(self.timestreamDateText)
        buttonlayout.addWidget(self.timestreamTimeLabel)
        buttonlayout.addWidget(self.timestreamTimeText)
        buttonlayout.addWidget(self.initStreamTimeButton)
        buttonlayout.addWidget(self.initStreamFileButton)
        buttonlayout.addWidget(self.loadImageButton)
        buttonlayout.addWidget(self.loadCamCalibButton)
        buttonlayout.addWidget(self.rotateImageButton)

        layoutSmallRotation = QtGui.QHBoxLayout()
        layoutSmallRotation.addWidget(self.slider)
        layoutSmallRotation.addWidget(self.applySmallRotationButton)
        buttonlayout.addLayout(layoutSmallRotation)

        buttonlayout.addWidget(self.colorcardRadioButton)

        layoutTrayShape = QtGui.QHBoxLayout()
        layoutTrayShape.addWidget(self.trayRadioButton)
        layoutTrayShape.addWidget(self.trayRoundCheckBox)
        buttonlayout.addLayout(layoutTrayShape)

        buttonlayout.addWidget(self.potRadioButton)
        buttonlayout.addWidget(self.zoomButton)
        buttonlayout.addWidget(self.panButton)
        buttonlayout.addWidget(self.homeButton)
        buttonlayout.addWidget(self.correctColorButton)
        buttonlayout.addWidget(self.save2PipelineButton)
        buttonlayout.addWidget(self.testPipelineButton)
        buttonlayout.addWidget(self.status)
        buttonlayout.addWidget(self.mousePosition)
        rightWidget.setMaximumWidth(250)
        leftLayout = QtGui.QVBoxLayout()
        leftLayout.addWidget(self.toolbar)
        leftLayout.addWidget(self.canvas)

        layout.addWidget(rightWidget)
        layout.addLayout(leftLayout)
        self.setLayout(layout)

        self.group = QtGui.QButtonGroup()
        self.group.addButton(self.colorcardRadioButton)
        self.group.addButton(self.trayRadioButton)
        self.group.addButton(self.potRadioButton)

        self.loadPreviousInputs()

        self.panMode = False
        self.zoomMode = False

        self.ts = None
        self.tsImages = None
        self.ax = None
        self.plotRect = None
        self.plotImg = None
        self.image = None
        self.potTemplate = None
        self.UndistMapX = None
        self.UndistMapY = None
        self.trayAspectRatio = 0.835
        self.colorcardAspectRatio = 1.5
        self.potAspectRatio = 1.0
        self.leftClicks = []
        self.colorcardParams = None
        self.scriptPath = os.path.dirname(os.path.realpath(__file__))
        self.timestreamRootPath = None
        self.pl = None

        # Ouput parameters
        self.ImageSize = None
        self.colorcardList = []
        self.trayList = []
        self.potList = []
        self.rotationAngle = 0.0
        self.smaleRotationAngle = 0.0
        self.CameraMatrix = None
        self.DistCoefs = None
        self.isDistortionCorrected = False
        self.settingFileName = None

    def selectWhat(self):
        if self.trayRadioButton.isChecked():
            self.status.append('Start selecting tray.')
        elif self.colorcardRadioButton.isChecked():
            self.status.append('Start selecting color bar.')
        else:
            self.status.append('Start selecting pot.')

    def home(self):
        self.toolbar.home()
    def zoom(self):
        self.toolbar.zoom()
        if not self.zoomMode:
            self.zoomMode = True
            self.panMode = False
            self.panButton.setChecked(False)
        else:
            self.zoomMode = False
    def pan(self):
        self.toolbar.pan()
        if not self.panMode:
            self.panMode = True
            self.zoomMode = False
            self.zoomButton.setChecked(False)
        else:
            self.panMode = False

    def initialiseTimestreamByTime(self):
        self.timestreamRootPath = str(self.timestreamText.text())
        if len(self.timestreamRootPath) > 0:
            self.status.append('Initialise a timestream at ' + str(self.timestreamRootPath))
            self.ts = timestream.TimeStream()
            self.ts.load(self.timestreamRootPath)
            self.status.append('Done')
            startDate = None
            timeInterval = None
            date = str(self.timestreamDateText.text())
            if len(date) > 0:
                startDate = timestream.parse.ts_parse_date(date)
            time = str(self.timestreamTimeText.text())
            if len(time) > 0:
                timeInterval = int(eval(time))
            self.tsImages = self.ts.iter_by_timepoints(start = startDate, interval = timeInterval, remove_gaps=False)
            self.loadImage()
        else:
            self.status.append('Please provide timestream root path.')

    def initialiseTimestreamByFiles(self):
        self.timestreamRootPath = str(self.timestreamText.text())
        if len(self.timestreamRootPath) > 0:
            self.status.append('Initialise a timestream at ' + str(self.timestreamRootPath))
            self.ts = timestream.TimeStream()
            self.ts.load(self.timestreamRootPath)
            self.status.append('Done')
            self.tsImages = self.ts.iter_by_files()
            self.loadImage()
        else:
            self.status.append('Please provide timestream root path.')

    def loadImage(self):
        ''' load and show an image'''
        if self.tsImages != None:
            try:
                tsImage = self.tsImages.next()
                if tsImage.pixels == np.array([]):
                    self.status.append('Missing image.')
            except:
                tsImage.pixels == np.array([])
                self.status.append('There is no more images.')

            if tsImage.pixels == np.array([]):
                self.image = None
                self.updateFigure()
                return
            self.image = tsImage.pixels
            fname = tsImage.path
        else:
            fname = QtGui.QFileDialog.getOpenFileName(self, 'Open image', '/mnt/phenocam/a_data/TimeStreams/Borevitz/BVZ0036/BVZ0036-GC02L-C01~fullres-orig/2014/2014_06/2014_06_24/2014_06_24_08/')
            app.processEvents()
            if len(fname) == 0:
                return
            self.image = cv2.imread(str(fname))[:,:,::-1]
        self.status.append('Loaded image from ' + str(fname))

        # reset all outputs
#        self.colorcardList = []
#        self.trayList = []
#        self.potList = []
        self.isDistortionCorrected = False

        if self.rotationAngle != None:
            self.image = cd.rotateImage(self.image, self.rotationAngle + self.smaleRotationAngle)

        # Undistort image if mapping available
        if not self.isDistortionCorrected and self.UndistMapX != None and self.UndistMapY != None:
            self.image = cv2.remap(self.image.astype(np.uint8), self.UndistMapX, self.UndistMapY, cv2.INTER_CUBIC)
            self.isDistortionCorrected = True

        self.updateFigure()

    def changeCursor(self, event):
#        cursor = Cursor(self.ax, useblit=True, color='red', linewidth=1)
        self.canvas.setCursor(QtGui.QCursor(QtCore.Qt.CrossCursor ))

    def updateFigure(self, image = None, resetFigure = False):
        if self.image != None:
            if image == None:
                image = self.image
            if self.ax == None:
                self.ax = self.figure.add_subplot(111)
                self.ax.figure.canvas.mpl_connect('button_press_event', self.onMouseClicked)
                self.ax.figure.canvas.mpl_connect('motion_notify_event', self.onMouseMoves)
                self.ax.figure.canvas.mpl_connect('figure_enter_event', self.changeCursor)
            self.ax.hold(False)
            if self.plotImg == None or resetFigure:
                self.plotImg = self.ax.imshow(image)
            else:
                self.plotImg.set_data(image)
            self.figure.tight_layout()

        xs, ys = [], []
        for Rect in self.colorcardList:
            tl, bl, br, tr = Rect
            xs = xs + [tl[0], bl[0], br[0], tr[0], tl[0], np.nan]
            ys = ys + [tl[1], bl[1], br[1], tr[1], tl[1], np.nan]
        for Rect in self.trayList:
            tl, bl, br, tr = Rect
            xs = xs + [tl[0], bl[0], br[0], tr[0], tl[0], np.nan]
            ys = ys + [tl[1], bl[1], br[1], tr[1], tl[1], np.nan]
        for Rect in self.potList:
            tl, bl, br, tr = Rect
            xs = xs + [tl[0], bl[0], br[0], tr[0], tl[0], np.nan]
            ys = ys + [tl[1], bl[1], br[1], tr[1], tl[1], np.nan]
        for x,y in self.leftClicks:
            xs = xs + [x]
            ys = ys + [y]
#        if self.crosshair != None:
#            xs = xs + [np.nan, 0, self.image.shape[1], np.nan, self.crosshair[0], self.crosshair[0], np.nan]
#            ys = ys + [np.nan, self.crosshair[1], self.crosshair[1], np.nan, 0, self.image.shape[0], np.nan]
        if len(xs) > 0 and len(ys) > 0:
            if self.plotRect == None:
                self.ax.hold(True)
                self.plotRect, = self.ax.plot(xs, ys, 'b')
                self.ax.hold(False)
                self.ax.set_xlim([0,self.image.shape[1]])
                self.ax.set_ylim([0,self.image.shape[0]])
                self.ax.invert_yaxis()
            else:
                self.plotRect.set_data(xs, ys)
        self.canvas.draw()

        app.processEvents()


    def loadCamCalib(self):
        ''' load camera calibration image and show an image'''
        calibPath = os.path.join(self.scriptPath, './data')
        CalibFile = QtGui.QFileDialog.getOpenFileName(self, 'Open image', calibPath)
        self.ImageSize, SquareSize, self.CameraMatrix, self.DistCoefs, RVecs, TVecs = cd.readCalibration(CalibFile)
        self.status.append('Loaded camera parameters from ' + CalibFile)
        print('CameraMatrix =', self.CameraMatrix)
        print('DistCoefs =', self.DistCoefs)
        self.UndistMapX, self.UndistMapY = cv2.initUndistortRectifyMap(self.CameraMatrix, self.DistCoefs, \
            None, self.CameraMatrix, self.ImageSize, cv2.CV_32FC1)

        if self.image != None:
            self.image = cv2.remap(self.image.astype(np.uint8), self.UndistMapX, self.UndistMapY, cv2.INTER_CUBIC)
            self.isDistortionCorrected = True
            self.status.append('Corrected image distortion.')
            self.updateFigure()

#    def loadPotTemplate(self):
#        ''' load pot template image'''
#        fname = QtGui.QFileDialog.getOpenFileName(self, 'Open image', '/home/chuong/Workspace/traitcapture-bin/unwarp_rectify/data')
#        self.status.append('Loading image...')
#        app.processEvents()
#        self.potTemplate = cv2.imread(str(fname))[:,:,::-1]
#        if len(self.potList) > 0:

    def correctColor(self):
        if self.colorcardParams == None:
            if len(self.colorcardList) > 0:
                medianSize = cd.getMedianRectSize(self.colorcardList)
                capturedColorcards = cd.rectifyRectImages(self.image, self.colorcardList, medianSize)
                self.colorcardColors, _ = cd.getColorcardColors(capturedColorcards[0], GridSize = [6, 4])
                self.colorcardParams = cd.estimateColorParameters(cd.CameraTrax_24ColorCard, self.colorcardColors)
            else:
                self.status.append('Need to select a color card first.')
                return

        colorMatrix, colorConstant, colorGamma = self.colorcardParams
        self.imageCorrected = cd.correctColorVectorised(self.image.astype(np.float), colorMatrix, colorConstant, colorGamma)
        self.imageCorrected[np.where(self.imageCorrected < 0)] = 0
        self.imageCorrected[np.where(self.imageCorrected > 255)] = 255
        self.imageCorrected = self.imageCorrected.astype(np.uint8)
        self.updateFigure(self.imageCorrected)

    def savePipelineSettings(self):
        ''' save to pipeline setting file'''
        self.settingFileName = str(QtGui.QFileDialog.getSaveFileName(self, \
            'Save selection (default in ./_data)', self.timestreamRootPath))
        if len(self.settingFileName) == 0:
            return
        self.settingPath = os.path.relpath(os.path.dirname(self.settingFileName), self.timestreamRootPath)

        self.settings = {}
        if self.ImageSize != None and self.CameraMatrix != None and self.DistCoefs != None:
            undistortDic = {'cameraMatrix': self.CameraMatrix.tolist(), \
                          'distortCoefs': self.DistCoefs.tolist(), \
                          'imageSize': list(self.ImageSize),
                          'rotationAngle': self.rotationAngle + self.smaleRotationAngle
                         }
        else:
            undistortDic = {}
        self.settings['undistort'] = undistortDic

        if len(self.colorcardList) > 0:
            medianSize = cd.getMedianRectSize(self.colorcardList)
            capturedColorcards = cd.rectifyRectImages(self.image, self.colorcardList, medianSize)
            colorCardFile = 'CapturedColorcard.png'
            cv2.imwrite(os.path.join(self.timestreamRootPath, self.settingPath, colorCardFile), capturedColorcards[0][:,:,::-1].astype(np.uint8))
            colorcardColors, colorStd  = cd.getColorcardColors(capturedColorcards[0], GridSize = [6,4])
            colorcardPosition, Width, Height, Angle = cd.getRectangleParamters(self.colorcardList[0])
            colorcardDic = {'colorcardFile': colorCardFile,\
                            'colorcardPosition': colorcardPosition.tolist(),\
                            'colorcardTrueColors': cd.CameraTrax_24ColorCard,\
                            'settingPath': '_data'
                            }
        else:
            colorcardDic = {}
        self.settings['colorcarddetect'] = colorcardDic

        self.settings['colorcorrect'] = {'minIntensity': 15}

        if len(self.trayList) > 0:
            trayMedianSize = cd.getMedianRectSize(self.trayList)
            trayImages = cd.rectifyRectImages(self.image, self.trayList, trayMedianSize)
            trayDict = {}
            trayDict['settingPath'] = '_data'
            trayDict['trayNumber'] = len(self.trayList)
            trayDict['trayFiles'] = 'Tray_%02d.png'
            trayPositions = []
            for i,tray in enumerate(trayImages):
                cv2.imwrite(os.path.join(self.timestreamRootPath, self.settingPath, trayDict['trayFiles'] %i), tray[:,:,::-1].astype(np.uint8))
                trayPosition, Width, Height, Angle = cd.getRectangleParamters(self.trayList[i])
                trayPositions.append(trayPosition.tolist())
            trayDict['trayPositions'] = trayPositions
        else:
            trayDict = {}
        self.settings['traydetect'] = trayDict

        if len(self.potList) > 0:
            trayMedianSize = cd.getMedianRectSize(self.trayList)
            potPosition, Width, Height, Angle = cd.getRectangleParamters(self.potList[0])
            Width, Height = int(Width), int(Height)
            topLeft = [int(self.potList[0][0][0]), int(self.potList[0][0][1])]
            self.potImage = self.image[topLeft[1]:topLeft[1]+Height, topLeft[0]:topLeft[0]+Width, :]
            potFile = 'Pot.png'
            cv2.imwrite(os.path.join(self.timestreamRootPath, self.settingPath, potFile), self.potImage[:,:,::-1].astype(np.uint8))
            potDict = {}
            potDict['potPositions'] = potPosition.tolist()
            potDict['potSize'] = [int(Width), int(Height)]
            potDict['traySize'] = [int(trayMedianSize[0]), int(trayMedianSize[1])]
            potDict['potFile'] = potFile
            potDict['potTemplateFile'] = 'PotTemplate.png'
            potDict['settingPath'] = '_data'
            potTemplatePathIn = os.path.join(self.scriptPath, './data/PotTemplate.png')
            potTemplatePathOut = os.path.join(self.timestreamRootPath, self.settingPath, potDict['potTemplateFile'])
            shutil.copyfile(potTemplatePathIn, potTemplatePathOut)
            if self.potTemplate != None:
                potTemplateFile = 'potTemplate.png'
                cv2.imwrite(os.path.join(self.timestreamRootPath, self.settingPath, potTemplateFile), self.potTemplate[:,:,::-1])
                potDict['potTemplateFile'] = potTemplateFile
        else:
            potDict = {}
        self.settings['potdetect'] = potDict

        self.settings['plantextract'] = {'meth': 'method1',
                                         'methargs': {'threshold' : 0.6,
                                         'kSize' : 5, 'blobMinSize' : 50} }

        with open(self.settingFileName, 'w') as outfile:
            outfile.write( yaml.dump(self.settings, default_flow_style=None) )
            self.status.append('Saved initial data to ' + self.settingFileName)

    def testPipeline(self):
        ''' try running processing pipeline based on user inputs'''
        if self.tsImages != None:
            # load setting file if missing
            if self.settingFileName == None:
                settingFileName = str(QtGui.QFileDialog.getOpenFileName(self, 'Open pipeline setting file', self.ts.path))
                if len(settingFileName) > 0:
                    self.settingFileName = settingFileName
                else:
                    return
            # initialise pipeline
            if self.pl == None:
                f = file(self.settingFileName)
                self.settings = yaml.load(f)
                self.ts.data["settings"] = self.settings
                self.pl = pipeline.ImagePipeline(self.settings)
                # process only from undistortion to pot detection
                self.pl.pipeline = self.pl.pipeline[:5]

            # read next image instant of pipeline
            try:
                tsImage = self.tsImages.next()
                if tsImage == None:
                    self.status.append('Missing image.')
                    return
            except:
                tsImage = None
                self.status.append('There is no more images.')
                return
            self.status.append('Processing ' + tsImage.path)
            context = {"rts":self.ts, "wts":None, "img":tsImage}
            result = self.pl.process(context, [tsImage])
            self.image, potPosList2 = result
            potSize = self.settings[4][1]["potSize"]
            self.potList = []
            for potPosList in potPosList2:
                if potPosList == None:
                    continue
                for potPos in potPosList:
                    tl = [potPos[0] - potSize[0]//2, potPos[1] - potSize[1]//2]
                    bl = [potPos[0] - potSize[0]//2, potPos[1] + potSize[1]//2]
                    br = [potPos[0] + potSize[0]//2, potPos[1] + potSize[1]//2]
                    tr = [potPos[0] + potSize[0]//2, potPos[1] - potSize[1]//2]
                    self.potList.append([tl, bl, br, tr])
            self.updateFigure()
            self.status.append('Done')
        else:
            self.status.append('Pipeline or setting file is missing.')

    def rotateImage90Degrees(self):
        if self.image == None:
            self.status.append('No image to rotate.')
            return
        self.rotationAngle = self.rotationAngle + 90
        if self.rotationAngle >= 360:
            self.rotationAngle = self.rotationAngle - 360
        self.image = np.rot90(self.image) #.astype(uint8)
        self.status.append('Rot. angle = %d deg' %self.rotationAngle)
        self.updateFigure(resetFigure = True)

    def rotateSmallAngle(self, value):
        self.smaleRotationAngle = float(value)/4.0
        if self.image != None:
            self.rotatedImage = cd.rotateImage(self.image, self.smaleRotationAngle)
            self.updateFigure(self.rotatedImage)
            self.status.append('Rot. angle = %f deg' %(self.rotationAngle + self.smaleRotationAngle))

    def applySmallRotation(self):
        if self.image != None:
            self.image = cd.rotateImage(self.image, self.smaleRotationAngle)
            self.updateFigure()
            self.status.append('Apply small angle rotation')

    def onMouseClicked(self, event):
        if self.panMode or self.zoomMode:
            return
        print('click', event.button, event.xdata, event.ydata)

        if event.button == 1 and event.xdata != None and event.ydata != None:
            self.leftClicks.append([event.xdata, event.ydata])
            print('self.leftClicks =', self.leftClicks)
            Rect = []
            AspectRatio = None
            if self.trayRadioButton.isChecked():
                AspectRatio = self.trayAspectRatio
            elif self.colorcardRadioButton.isChecked():
                AspectRatio = self.colorcardAspectRatio
            elif self.potRadioButton.isChecked():
                AspectRatio = self.potAspectRatio

            if len(self.leftClicks) == 2 and AspectRatio != None:
                if self.colorcardRadioButton.isChecked():
                    Rect = cd.getRectCornersFrom2Points(self.image, self.leftClicks, AspectRatio)
                if self.trayRadioButton.isChecked():
                    Rect = cd.getRectCornersFrom2Points(self.image, self.leftClicks, AspectRatio, Rounded = self.trayRoundCheckBox.isChecked())
                elif self.potRadioButton.isChecked():
                    Rect = cd.getRectCornersFrom2Points(self.image, self.leftClicks, AspectRatio, Rounded = True)
                self.leftClicks = []
            elif len(self.leftClicks) == 4:
                Rect = [[x,y] for x,y in self.leftClicks]
                Rect = cd.correctPointOrder(Rect)
                self.leftClicks = []

            if len(Rect) > 0:
                if self.trayRadioButton.isChecked():
                    self.trayList.append(Rect)
                    self.status.append('Added tray selection.')
                elif self.colorcardRadioButton.isChecked():
                    self.colorcardList.append(Rect)
                    self.status.append('Added color card selection.')
                else:
                    self.potList.append(Rect)
                    self.status.append('Added pot selection.')
            self.updateFigure()
        elif event.button == 3:
            # remove the last selection
            if len(self.leftClicks) > 0:
                self.leftClicks = self.leftClicks[:-1]
                self.status.append('Removed a previous click')
            else:
                if self.trayRadioButton.isChecked() and len(self.trayList) > 0:
                    self.trayList = self.trayList[:-1]
                    self.status.append('Removed a previous tray selection')
                elif self.colorcardRadioButton.isChecked() and len(self.colorcardList) > 0:
                    self.colorcardList = self.colorcardList[:-1]
                    self.status.append('Removed a previous color card selection.')
                elif self.potRadioButton.isChecked() and len(self.potList) > 0:
                    self.potList = self.potList[:-1]
                    self.status.append('Removed a previous pot selection')
            self.updateFigure()
        else:
            print('Ignored click')

    def onMouseMoves(self, event):
        if event.inaxes == self.ax:
            self.mousePosition.setText('x=%d, y=%d' %(event.xdata, event.ydata))
#            self.crosshair = [event.xdata, event.ydata]
        else:
            self.mousePosition.setText('')
#            self.crosshair = None
#        self.updateFigure()

    def keyPressEvent(self, e):
        if e.key() == QtCore.Qt.Key_Escape:
            self.close()

    def closeEvent(self, event):

        quit_msg = "Are you sure you want to exit the program?"
        reply = QtGui.QMessageBox.question(self, 'Message',
                         quit_msg, QtGui.QMessageBox.Yes, QtGui.QMessageBox.No)

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

    def saveInputs(self):
        with open('./.inputs.yml', 'w') as myfile:
            dicInputs = {'rootPath': str(self.timestreamText.text()),
                         'startDate': str(self.timestreamDateText.text()),
                         'timeInterval': str(self.timestreamTimeText.text()),
                        }
            myfile.write( yaml.dump(dicInputs, default_flow_style=None) )

    def loadPreviousInputs(self):
        try:
            with open('./.inputs.yml', 'r') as myfile:
                dicInputs = yaml.load(myfile)
                self.timestreamText.setText(dicInputs['rootPath'])
                self.timestreamDateText.setText(dicInputs['startDate'])
                self.timestreamTimeText.setText(dicInputs['timeInterval'])
        except:
            pass
Esempio n. 9
0
 def zoom(self, *args) :
     print 'Zoom is clicked'
     NavigationToolbar.zoom(self)
     fig = self.canvas.figure
     if fig.ntbZoomIsOn : fig.ntbZoomIsOn = False
     else               : fig.ntbZoomIsOn = True
 def zoom(self, *args) :
     print 'Zoom is clicked'
     NavigationToolbar.zoom(self)
     fig = self.canvas.figure
     if fig.ntbZoomIsOn : fig.ntbZoomIsOn = False
     else               : fig.ntbZoomIsOn = True
Esempio n. 11
0
 def zoom(self, *args) :
     print 'Zoom is clicked'
     NavigationToolbar.zoom(self)
Esempio n. 12
0
 def zoom(self, *args):
     self.resetActionsState(self.zoomAction)
     NavigationToolbar2QTAgg.zoom(self, *args)
Esempio n. 13
0
 def zoom(self, *args):
     print 'Zoom is clicked'
     NavigationToolbar.zoom(self)
class Window(QtGui.QDialog):
    def __init__(self, parent=None):
        super(Window, self).__init__(parent)
 
        self.figure = plt.figure()
        self.canvas = FigureCanvas(self.figure)
        self.canvas.setSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Expanding)
         
        self.toolbar = NavigationToolbar(self.canvas, self)
        self.toolbar.hide()
 
        # Just some button 
        self.colorcardRadioButton = QtGui.QRadioButton('Select color car&d')
        self.colorcardRadioButton.setChecked(False)
        self.colorcardRadioButton.clicked.connect(self.selectWhat)
        
        self.trayRadioButton = QtGui.QRadioButton('Select &tray')
        self.trayRadioButton.setChecked(False)
        self.trayRadioButton.clicked.connect(self.selectWhat)

        self.potRadioButton = QtGui.QRadioButton('Select &pot')
        self.potRadioButton.setChecked(False)
        self.potRadioButton.clicked.connect(self.selectWhat)

        self.loadImageButton = QtGui.QPushButton('&Load image')
        self.loadImageButton.clicked.connect(self.loadImage)
 
        self.rotateImageButton = QtGui.QPushButton('&Rotate 90-deg')
        self.rotateImageButton.clicked.connect(self.rotateImage90Degrees)
 
        self.loadCamCalibButton = QtGui.QPushButton('Load &cam. param.')
        self.loadCamCalibButton.clicked.connect(self.loadCamCalib)
 
        self.saveGeometriesButton = QtGui.QPushButton('&Save selected geometries')
        self.saveGeometriesButton.clicked.connect(self.saveSelectedGeometries)
 
        self.saveTraysButton = QtGui.QPushButton('&Save selected tray images')
        self.saveTraysButton.clicked.connect(self.saveSelectedTrayImages)
 
        self.saveColorcadButton = QtGui.QPushButton('&Save sel. col. card images')
        self.saveColorcadButton.clicked.connect(self.saveSelectedColorcardImages)
 
        self.save2PipelineButton = QtGui.QPushButton('&Save as pipeline settings')
        self.save2PipelineButton.clicked.connect(self.savePipelineSettings)
 
        self.zoomButton = QtGui.QPushButton('&Zoom')
        self.zoomButton.setCheckable(True)
        self.zoomButton.clicked.connect(self.zoom)
         
        self.panButton = QtGui.QPushButton('&Pan')
        self.panButton.setCheckable(True)
        self.panButton.clicked.connect(self.pan)
         
        self.homeButton = QtGui.QPushButton('&Home')
        self.homeButton.clicked.connect(self.home)
        
        self.status = QtGui.QTextEdit('')
        self.status.setSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Expanding)
        self.mousePosition = QtGui.QLabel('')
 
        # set the layout
        layout = QtGui.QHBoxLayout()
        rightWidget = QtGui.QWidget()
        buttonlayout = QtGui.QVBoxLayout(rightWidget)
        buttonlayout.addWidget(self.loadImageButton)
        buttonlayout.addWidget(self.rotateImageButton)
        buttonlayout.addWidget(self.loadCamCalibButton)
        buttonlayout.addWidget(self.colorcardRadioButton)
        buttonlayout.addWidget(self.trayRadioButton)
        buttonlayout.addWidget(self.potRadioButton)
        buttonlayout.addWidget(self.zoomButton)
        buttonlayout.addWidget(self.panButton)
        buttonlayout.addWidget(self.homeButton)
        buttonlayout.addWidget(self.saveGeometriesButton)
        buttonlayout.addWidget(self.saveColorcadButton)
        buttonlayout.addWidget(self.saveTraysButton)
        buttonlayout.addWidget(self.save2PipelineButton)
        buttonlayout.addWidget(self.status)
        buttonlayout.addWidget(self.mousePosition)
        rightWidget.setMaximumWidth(200)
        leftLayout = QtGui.QVBoxLayout()
        leftLayout.addWidget(self.toolbar)
        leftLayout.addWidget(self.canvas)

        layout.addWidget(rightWidget)
        layout.addLayout(leftLayout)
        self.setLayout(layout)
 
        self.group = QtGui.QButtonGroup()
        self.group.addButton(self.colorcardRadioButton)
        self.group.addButton(self.trayRadioButton) 
        self.group.addButton(self.potRadioButton) 
        
        self.panMode = False
        self.zoomMode = False
        
        self.ax = None
        self.plotRect = None
        self.plotImg = None
        self.image = None
        self.potTemplate = None
        self.UndistMapX = None
        self.UndistMapY = None
        self.trayAspectRatio = 0.835
        self.colorcardAspectRatio = 1.5
        self.potAspectRatio = 1.0
        self.leftClicks = []
        
        self.ImageSize = None
        self.CameraMatrix = None
        self.DistCoefs = None
        
#        # change cursor shape
#        self.setCursor(QtGui.QCursor(QtCore.Qt.CrossCursor ))
        
        # Ouput parameters
        self.colorcardList = []
        self.trayList = []
        self.potList = []
        self.rotationAngle = 0
        self.isDistortionCorrected = False

    def selectWhat(self):
        if self.trayRadioButton.isChecked():
            self.status.append('Start selecting tray.')
        elif self.colorcardRadioButton.isChecked():
            self.status.append('Start selecting color bar.')
        else:
            self.status.append('Start selecting pot.')
        
    def home(self):
        self.toolbar.home()
    def zoom(self):
        self.toolbar.zoom()
        if not self.zoomMode:
            self.zoomMode = True
            self.panMode = False
            self.panButton.setChecked(False)
        else:
            self.zoomMode = False
    def pan(self):
        self.toolbar.pan()
        if not self.panMode:
            self.panMode = True
            self.zoomMode = False
            self.zoomButton.setChecked(False)
        else:
            self.panMode = False
         
    def loadImage(self):
        ''' load and show an image'''
        fname = QtGui.QFileDialog.getOpenFileName(self, 'Open image', '/mnt/phenocam/a_data/TimeStreams/BorevitzTest/BVZ0036/BVZ0036-GC02L~fullres-orig/2014/2014_06/2014_06_20/2014_06_20_12')
        self.status.append('Loading image...')
        app.processEvents()
        self.image = cv2.imread(str(fname))[:,:,::-1]
        self.status.append('Loaded image from ' + str(fname))
        
        # reset all outputs
        self.colorcardList = []
        self.trayList = []
        self.potList = []
        self.rotationAngle = 0
        self.isDistortionCorrected = False

        # Undistort image if mapping available
        if not self.isDistortionCorrected and self.UndistMapX != None and self.UndistMapY != None:
            self.image = cv2.remap(self.image.astype(np.uint8), self.UndistMapX, self.UndistMapY, cv2.INTER_CUBIC)
            self.isDistortionCorrected = True

        if self.image != None:
            if self.ax == None:
                self.ax = self.figure.add_subplot(111)
                self.ax.figure.canvas.mpl_connect('button_press_event', self.onMouseClicked)
                self.ax.figure.canvas.mpl_connect('motion_notify_event', self.onMouseMoves)
                self.ax.figure.canvas.mpl_connect('figure_enter_event', self.changeCursor)
            self.ax.hold(False)
            if self.plotImg == None:
                self.plotImg = self.ax.imshow(self.image)
            else:
                self.plotImg.set_data(self.image)
            self.figure.tight_layout()
            self.canvas.draw()
        
    def changeCursor(self, event):
#        cursor = Cursor(self.ax, useblit=True, color='red', linewidth=1)
        self.canvas.setCursor(QtGui.QCursor(QtCore.Qt.CrossCursor ))
        
    def updateFigure(self):
        xs, ys = [], []
        for Rect in self.colorcardList:
            tl, bl, br, tr = Rect
            xs = xs + [tl[0], bl[0], br[0], tr[0], tl[0], np.nan]
            ys = ys + [tl[1], bl[1], br[1], tr[1], tl[1], np.nan]
        for Rect in self.trayList:
            tl, bl, br, tr = Rect
            xs = xs + [tl[0], bl[0], br[0], tr[0], tl[0], np.nan]
            ys = ys + [tl[1], bl[1], br[1], tr[1], tl[1], np.nan]
        for Rect in self.potList:
            tl, bl, br, tr = Rect
            xs = xs + [tl[0], bl[0], br[0], tr[0], tl[0], np.nan]
            ys = ys + [tl[1], bl[1], br[1], tr[1], tl[1], np.nan]
        for x,y in self.leftClicks:
            xs = xs + [x]
            ys = ys + [y]
#        if self.crosshair != None:
#            xs = xs + [np.nan, 0, self.image.shape[1], np.nan, self.crosshair[0], self.crosshair[0], np.nan]
#            ys = ys + [np.nan, self.crosshair[1], self.crosshair[1], np.nan, 0, self.image.shape[0], np.nan]
        if len(xs) > 0 and len(ys) > 0:
            if self.plotRect == None:
                self.ax.hold(True)
                self.plotRect, = self.ax.plot(xs, ys, 'b')
                self.ax.hold(False)
                self.ax.set_xlim([0,self.image.shape[1]])
                self.ax.set_ylim([0,self.image.shape[0]])
                self.ax.invert_yaxis()
            else:
                self.plotRect.set_data(xs, ys)
        self.canvas.draw()
           
        app.processEvents()

         
    def loadCamCalib(self):
        ''' load camera calibration image and show an image'''
        CalibFile = QtGui.QFileDialog.getOpenFileName(self, 'Open image', '/home/chuong/Workspace/traitcapture-bin/unwarp_rectify/data')
        self.ImageSize, SquareSize, self.CameraMatrix, self.DistCoefs, RVecs, TVecs = utils.readCalibration(CalibFile)
        self.status.append('Loaded camera parameters from ' + CalibFile) 
        print('CameraMatrix =', self.CameraMatrix)
        print('DistCoefs =', self.DistCoefs)
        self.UndistMapX, self.UndistMapY = cv2.initUndistortRectifyMap(self.CameraMatrix, self.DistCoefs, \
            None, self.CameraMatrix, self.ImageSize, cv2.CV_32FC1)    

        if self.image != None:
            self.image = cv2.remap(self.image.astype(np.uint8), self.UndistMapX, self.UndistMapY, cv2.INTER_CUBIC)
            self.isDistortionCorrected = True
            self.status.append('Corrected image distortion.') 
            if self.plotImg == None:
                self.plotImg = self.ax.imshow(self.image)
            else:
                self.plotImg.set_data(self.image)
            self.canvas.draw()
        
#    def loadPotTemplate(self):
#        ''' load pot template image'''
#        fname = QtGui.QFileDialog.getOpenFileName(self, 'Open image', '/home/chuong/Workspace/traitcapture-bin/unwarp_rectify/data')
#        self.status.append('Loading image...')
#        app.processEvents()
#        self.potTemplate = cv2.imread(str(fname))[:,:,::-1]
#        if len(self.potList) > 0:
            

    def saveSelectedGeometries(self):
        ''' save selected geometries'''
        fname = QtGui.QFileDialog.getSaveFileName(self, 'Save selected geometries', '/home/chuong/Workspace/traitcapture-bin/unwarp_rectify/data')
        colorcardList2 = []
        for colorcard in self.colorcardList:
            colorcardList2 = colorcardList2 + colorcard
        trayList2 = []
        for tray in self.trayList:
            trayList2 = trayList2 + tray
        potList2 = []
        for pot in self.potList:
            potList2 = potList2 + pot
        dicdata = {'colorcardself.crosshair = NoneList':np.asarray(colorcardList2), \
                   'trayList':np.asarray(trayList2), \
                   'potList':np.asarray(potList2), \
                   'rotationAngle':self.rotationAngle, \
                   'distortionCorrected':int(self.isDistortionCorrected)}
        cv2yml.dic2yml(fname, dicdata)
        self.status.append('Saved selected geometries to ' + fname)
        
    def saveSelectedTrayImages(self):
        ''' save selected trays'''
        fname = QtGui.QFileDialog.getSaveFileName(self, 'Save selected tray images', '/home/chuong/Workspace/traitcapture-bin/unwarp_rectify/data')
        medianWidth, medianHeight = utils.getMedianRectSize(self.trayList)
        rectifiedTrayImages = utils.rectifyRectImages(self.image, self.trayList, MedianSize = [medianWidth, medianHeight])
        for i,rectifiedImage in enumerate(rectifiedTrayImages):
            cv2.imwrite(str(fname) %i, rectifiedImage)

    def saveSelectedColorcardImages(self):
        ''' save selected trays'''
        fname = QtGui.QFileDialog.getSaveFileName(self, 'Save selected color card images', '/home/chuong/Workspace/traitcapture-bin/unwarp_rectify/data')
        medianWidth, medianHeight = utils.getMedianRectSize(self.colorcardList)
        rectifiedColorcardImages = utils.rectifyRectImages(self.image, self.colorcardList, MedianSize = [medianWidth, medianHeight])
        for i,rectifiedImage in enumerate(rectifiedColorcardImages):
            cv2.imwrite(str(fname) %i, rectifiedImage)
            
    def savePipelineSettings(self):
        ''' save to pipeline setting file'''
        fname = QtGui.QFileDialog.getSaveFileName(self, 'Save selection to pipeline settings', '/home/chuong/Workspace/traitcapture-bin/unwarp_rectify/data')
        settingPath = os.path.dirname(str(fname))
        settings = []
        if self.ImageSize != None and self.CameraMatrix != None and self.DistCoefs != None:
            undistort = ['undistort', \
                         {'mess': 'perform optical undistortion', \
                          'cameraMatrix': self.CameraMatrix.tolist(), \
                          'distortCoefs': self.DistCoefs.tolist(), \
                          'imageSize': list(self.ImageSize),
                          'rotationAngle': self.rotationAngle
                         } \
                        ]
        else:
            undistort = ['undistort', {'mess': '---skip optical undistortion---'}]
        settings.append(undistort)
        
        if len(self.colorcardList) > 0:
            medianSize = utils.getMedianRectSize(self.colorcardList)
            capturedColorcards = utils.rectifyRectImages(self.image, self.colorcardList, medianSize)
            colorCardFile = 'CapturedColorcard.png'
            cv2.imwrite(os.path.join(settingPath, colorCardFile), capturedColorcards[0][:,:,::-1].astype(np.uint8))
            colorcardColors, colorStd  = utils.getColorcardColors(capturedColorcards[0], GridSize = [6,4])
            colorcardPosition, Width, Height, Angle = utils.getRectangleParamters(self.colorcardList[0])
            colorcarddetect = ['colorcarddetect', \
                               {'mess': '---perform color card detection---', \
                                'colorcardFile': colorCardFile,\
                                'colorcardPosition': colorcardPosition.tolist(),\
                                'colorcardTrueColors': utils.CameraTrax_24ColorCard
                               }
                              ]
        else:
            colorcarddetect = ['colorcarddetect', {'mess': '---skip color card detection---'}]
        settings.append(colorcarddetect)
            
        colorcorrect = ['colorcorrect', {'mess': '---perform color correction---'}]
        settings.append(colorcorrect)

        if len(self.trayList) > 0:
            trayMedianSize = utils.getMedianRectSize(self.trayList)
            trayImages = utils.rectifyRectImages(self.image, self.trayList, trayMedianSize)
            colorcardColors, colorStd  = utils.getColorcardColors(capturedColorcards[0], GridSize = [6,4])
            trayDict = {'mess': '---perform tray detection---'}
            trayDict['trayNumber'] = len(self.trayList)
            trayDict['trayFiles'] = 'Tray_%02d.png'
            trayPositions = []
            for i,tray in enumerate(trayImages):
                cv2.imwrite(os.path.join(settingPath, trayDict['trayFiles'] %i), tray[:,:,::-1].astype(np.uint8))
                trayPosition, Width, Height, Angle = utils.getRectangleParamters(self.trayList[i])
                trayPositions.append(trayPosition.tolist())
            trayDict['trayPositions'] = trayPositions
            traydetect = ['traydetect', trayDict]
        else:
            traydetect = ['traydetect', {'mess': '---skip tray detection---'}]
        settings.append(traydetect)

        if len(self.potList) > 0:
            trayMedianSize = utils.getMedianRectSize(self.trayList)
            potPosition, Width, Height, Angle = utils.getRectangleParamters(self.potList[0])
            Width, Height = int(Width), int(Height)
            topLeft = [int(self.potList[0][0][0]), int(self.potList[0][0][1])]
            self.potImage = self.image[topLeft[1]:topLeft[1]+Height, topLeft[0]:topLeft[0]+Width, :]
            potFile = 'Pot.png'
            cv2.imwrite(os.path.join(settingPath, potFile), self.potImage[:,:,::-1].astype(np.uint8))
            potDict = {'mess': '---perform pot detection---'}
            potDict['potPosition'] = potPosition.tolist()
            potDict['potSize'] = [int(Width), int(Height)]
            potDict['traySize'] = [int(trayMedianSize[0]), int(trayMedianSize[1])]
            potDict['potFile'] = potFile
            if self.potTemplate != None:
                potTemplateFile = 'potTemplate.png'
                cv2.imwrite(os.path.join(settingPath, potTemplateFile), self.potTemplate[:,:,::-1])
                potDict['potTemplateFile'] = potTemplateFile
            potdetect = ['potdetect', potDict]
        else:
            potdetect = ['potdetect', {'mess': '---skip pot detection---'}]
        settings.append(potdetect)
        
        plantextract = ['plantextract', {'mess': '---perfrom plant biometrics extraction---'}]
        settings.append(plantextract)
        
        with open(fname, 'w') as outfile:
            outfile.write( yaml.dump(settings, default_flow_style=None) )


    def rotateImage90Degrees(self):
        if self.image == None:
            self.status.append('No image to rotate.')
            return
        self.rotationAngle = self.rotationAngle + 90
        if self.rotationAngle >= 360:
            self.rotationAngle = self.rotationAngle - 360
        self.image = np.rot90(self.image) #.astype(uint8)
        self.status.append('Rot. angle = %d deg' %self.rotationAngle) 
        if self.plotImg == None:
            self.plotImg = self.ax.imshow(self.image)
        else:
            self.plotImg.set_data(self.image)
        self.canvas.draw()

    def onMouseClicked(self, event):
        if self.panMode or self.zoomMode:
            return
        print('click', event.button, event.xdata, event.ydata)
        
        if event.button == 1 and event.xdata != None and event.ydata != None:
            self.leftClicks.append([event.xdata, event.ydata])
            print('self.leftClicks =', self.leftClicks)
            Rect = []
            AspectRatio = None
            if self.trayRadioButton.isChecked():
                AspectRatio = self.trayAspectRatio
            elif self.colorcardRadioButton.isChecked():
                AspectRatio = self.colorcardAspectRatio
            elif self.potRadioButton.isChecked():
                AspectRatio = self.potAspectRatio
                
            if len(self.leftClicks) == 2 and AspectRatio != None:
                if self.potRadioButton.isChecked():
                    Rect = utils.getRectCornersFrom2Points(self.image, self.leftClicks, AspectRatio, Rounded = True)
                else:
                    Rect = utils.getRectCornersFrom2Points(self.image, self.leftClicks, AspectRatio)
                self.leftClicks = []            
            elif len(self.leftClicks) == 4:
                Rect = [[x,y] for x,y in self.leftClicks]
                Rect = utils.correctPointOrder(Rect)
                self.leftClicks = []            
    
            if len(Rect) > 0:
                if self.trayRadioButton.isChecked():
                    self.trayList.append(Rect)
                    self.status.append('Added tray selection.')
                elif self.colorcardRadioButton.isChecked():
                    self.colorcardList.append(Rect)
                    self.status.append('Added color card selection.')
                else:
                    self.potList.append(Rect)
                    self.status.append('Added pot selection.')
            self.updateFigure()
        elif event.button == 3:
            # remove the last selection
            if len(self.leftClicks) > 0:
                self.leftClicks = self.leftClicks[:-1]
                self.status.append('Removed a previous click')
            else:
                if self.trayRadioButton.isChecked() and len(self.trayList) > 0:
                    self.trayList = self.trayList[:-1]
                    self.status.append('Removed a previous tray selection')
                elif self.colorcardRadioButton.isChecked() and len(self.colorcardList) > 0:
                    self.colorcardList = self.colorcardList[:-1]
                    self.status.append('Removed a previous color card selection.')
                elif self.potRadioButton.isChecked() and len(self.potList) > 0:
                    self.potList = self.potList[:-1]
                    self.status.append('Removed a previous pot selection')
            self.updateFigure()
        else:
            print('Ignored click')

    def onMouseMoves(self, event):
        if event.inaxes == self.ax:
            self.mousePosition.setText('x=%d, y=%d' %(event.xdata, event.ydata))
#            self.crosshair = [event.xdata, event.ydata]
        else:
            self.mousePosition.setText('')
#            self.crosshair = None
#        self.updateFigure()
        
    def keyPressEvent(self, e):
        if e.key() == QtCore.Qt.Key_Escape:
            self.close()

    def closeEvent(self, event):
    
        quit_msg = "Are you sure you want to exit the program?"
        reply = QtGui.QMessageBox.question(self, 'Message', 
                         quit_msg, QtGui.QMessageBox.Yes, QtGui.QMessageBox.No)
    
        if reply == QtGui.QMessageBox.Yes:
            event.accept()
        else:
            event.ignore()