Beispiel #1
0
class LoadImage(QtWidgets.QMainWindow, Ui_MainWindow):
    """Load board images from the menu and the pins icon list."""
    def __init__(self):
        """Set up the user interference from the QT Designer.

        Initiate the menu bar and clicks on menu options.
        """
        super().__init__()
        """Set up the user interference from QT Designer."""
        self.setupUi(self)

        self.df_cls = None
        self.board_type = None
        self.board_label = None
        self.first_load = True
        self.first_resize = True
        self.resize_timer = None
        self.list_items = {}

        self.centralwidget.setDisabled(True)

        # Creating two menu options: Boards and Export
        self.file_menu = self.menuBar().addMenu('&File')
        self.boards_menu = self.menuBar().addMenu('&Boards')
        # self.export_menu = self.menuBar().addMenu('&Export')

        self.refresh_menus()

        self.export_action = self.file_menu.addAction('Save Board Label')
        self.run_macro_action = self.file_menu.addAction('Run the Macro File')
        self.exit_action = self.file_menu.addAction('Exit')
        self.exit_action.triggered.connect(QtWidgets.qApp.quit)

        # self.export_action = self.export_menu.addAction('Save Board Label')

    def refresh_menus(self):
        self.boards_menu.clear()

        unprocessed_menu = self.boards_menu.addMenu('Unprocessed')
        processed_menu = self.boards_menu.addMenu('Processed')

        config = ConfigParser()

        config.read('socketear.ini')

        file_path = os.path.normpath(config.get('path', 'dirpath'))

        # Going through the Board folder to create the appropriate actions
        for dirpath, dirnames, filenames in os.walk(
                os.path.join(file_path, 'Processed')):

            if 'pins' in dirnames and all(
                    fn in filenames for fn in
                ['classification.csv', 'stitched.png', 'info.txt']):
                # Add actions for the right path
                action_cb = processed_menu.addAction(
                    os.path.split(os.path.basename(dirpath))[-1])
                action_cb.triggered.connect(
                    functools.partial(self.load_image, dirpath))

        for dirpath, dirnames, filenames in os.walk(
                os.path.join(file_path, 'Unprocessed')):
            if all(fn.endswith(".tif") for fn in filenames) and not dirnames:

                board_name = os.path.split(os.path.split(dirpath)[0])[1]
                save_path = os.path.join(file_path, 'Processed')
                action_nb = unprocessed_menu.addAction(board_name)
                action_nb.triggered.connect(
                    functools.partial(self.load_model, dirpath, board_name,
                                      save_path))

    def remove_board(self):
        """Remove the layer of matplotlib container and widgetlist.

        Also, disconnects all the buttons.
        """
        self.mpl_vl.removeWidget(self.canvas)
        self.canvas.close()
        # self.mpl_vl.removeWidget(self.toolbar)
        # self.toolbar.close()
        self.mpl_figs.clear()
        self.mpl_figs.currentItemChanged.disconnect()
        self.nwo_button.disconnect()
        self.nco_button.disconnect()
        self.normal_button.disconnect()
        self.hnp_button.disconnect()
        self.sb_button.disconnect()
        self.type1_button.disconnect()
        self.type2_button.disconnect()
        self.type3_button.disconnect()
        self.type4_button.disconnect()
        self.reject_button.disconnect()
        # self.no_crack_button.disconnect()
        self.type_a_button.disconnect()
        self.type_b_button.disconnect()
        self.type_c_button.disconnect()
        self.type_d_button.disconnect()
        self.type_e_button.disconnect()
        self.refresh_sjr_button.disconnect()
        self.refresh_sjq_button.disconnect()
        self.sjr_image_button.disconnect()
        self.export_action.disconnect()
        self.export_action.setDisabled(True)

    def add_mpl(self):
        """Add a layer of matplotlib container."""

        self.figure = Figure(figsize=(5, 4), dpi=100)
        self.canvas = FigureCanvas(self.figure)
        self.mpl_vl.addWidget(self.canvas)
        self.canvas.draw()
        # self.toolbar = NavigationToolbar(self.canvas,
        #                                  self.mpl_window,
        #                                  coordinates=True)
        # self.addToolBar(self.toolbar)

    def button_clicked(self, value):
        """Change the radio button.

        Updates the CSV file after a radio button was clicked.
        """
        pin_index = self.mpl_figs.currentItem().data(32)

        if self.board_type == 'SJR':
            if isinstance(value, six.string_types):
                self.df_cls.set_value(pin_index, 'DYE Correction', value)

            elif isinstance(value, int):
                self.df_cls.set_value(pin_index, 'SJR Correction', value)

        elif self.board_type == 'SJQ':
            self.df_cls.set_value(pin_index, 'SJQ Correction', value)

        image_path = self.df_cls.loc[pin_index]['Image Path']

        csv_path = os.path.split(
            os.path.split(os.path.normpath(image_path))[0])[0]

        # Saving the CSV file
        header = [
            'Row', 'Col', 'StitchedX', 'StitchedY', 'Pin', 'SJQ', 'SJR', 'DYE',
            'SJQ Correction', 'SJR Correction', 'DYE Correction',
            'Type Change', 'Sorted SJQ', 'Image Path', 'Label Coords',
            'DYE Image Path'
        ]
        self.df_cls.to_csv(os.path.join(csv_path, 'classification.csv'),
                           columns=header)

    def nwo_button_clicked(self, enabled):
        """Activate the nwo button for SJQ."""
        if enabled:
            self.button_clicked(0)

    def nco_button_clicked(self, enabled):
        """Activate the nco button for SJQ."""
        if enabled:
            self.button_clicked(1)

    def normal_button_clicked(self, enabled):
        """Activate the normal button for SJQ."""
        if enabled:
            self.button_clicked(2)

    def hnp_button_clicked(self, enabled):
        """Activate the hnp button for SJQ."""
        if enabled:
            self.button_clicked(3)

    def sb_button_clicked(self, enabled):
        """Activate the sb button for SJQ."""
        if enabled:
            self.button_clicked(4)

    def type1_button_clicked(self, enabled):
        """Activate the type1 button for SJR."""
        if enabled:
            self.button_clicked(0)

    def type2_button_clicked(self, enabled):
        """Activate the type2 button for SJR."""
        if enabled:
            self.button_clicked(1)

    def type3_button_clicked(self, enabled):
        """Activate the normal button for SJR."""
        if enabled:
            self.button_clicked(2)

    def type4_button_clicked(self, enabled):
        """Activate the type4 button for SJR."""
        if enabled:
            self.button_clicked(3)

    def reject_button_clicked(self, enabled):
        """Activate the type4 button for SJR."""
        if enabled:
            self.button_clicked(4)

    # def no_crack_button_clicked(self, enabled):
    #     """Activate the no crack button for SJR."""
    #     if enabled:
    #         self.button_clicked('O')

    def type_a_button_clicked(self, enabled):
        """Activate the typeA button for SJR."""
        if enabled:
            self.button_clicked('A')

    def type_b_button_clicked(self, enabled):
        """Activate the typeB button for SJR."""
        if enabled:
            self.button_clicked('B')

    def type_c_button_clicked(self, enabled):
        """Activate the typeC button for SJR."""
        if enabled:
            self.button_clicked('C')

    def type_d_button_clicked(self, enabled):
        """Activate the typeD button for SJR."""
        if enabled:
            self.button_clicked('D')

    def type_e_button_clicked(self, enabled):
        """Activate the typeE button for SJR."""
        if enabled:
            self.button_clicked('E')

    def refresh(self, predictions, dye_predictions=None):

        # start_refresh = datetime.now()

        self.board_label = self.drawing_tool(shape=1000,
                                             pred=predictions,
                                             dyepred=dye_predictions)

        # print('remove scatters!')

        self.axes1_scatter.remove()
        self.axes2_scatter.remove()

        self.axes2_image = self.axes2.imshow(self.board_label)

        self.canvas.draw()

        self.background_board = self.canvas.copy_from_bbox(self.axes2.bbox)

        pin_index = self.mpl_figs.currentItem().data(32)

        self.load_pin_cls(pin_index)

        cx_image = self.df_cls.ix[pin_index, 'StitchedX']
        cy_image = self.df_cls.ix[pin_index, 'StitchedY']

        self.axes1_scatter = self.axes1.scatter(cy_image,
                                                cx_image,
                                                edgecolor='#ff01d0',
                                                marker='s',
                                                s=80,
                                                linewidth='2',
                                                facecolors='none')

        cx_label = self.df_cls.ix[pin_index, 'Label Coords'][0] * 1000
        cy_label = self.df_cls.ix[pin_index, 'Label Coords'][1] * 1000

        self.axes2_scatter = self.axes2.scatter(cy_label,
                                                cx_label,
                                                edgecolor='#ff01d0',
                                                marker='s',
                                                s=80,
                                                linewidth='2',
                                                facecolors='none')

        self.canvas.blit(self.axes1.bbox)
        self.canvas.blit(self.axes2.bbox)

        # print('timer canvas.draw: ', datetime.now() - start_refresh)
        # start_refresh = datetime.now()

        self.sort_classification()

        # print('timer sort_classification: ', datetime.now() - start_refresh)
        # start_refresh = datetime.now()

        self.pin_dividers()

        # print('timer pin_dividers: ', datetime.now() - start_refresh)

        self.mpl_figs.clear()

        # start_refresh = datetime.now()

        self.load_pins()

        # print('timer load_pins: ', datetime.now() - start_refresh)

        self.mpl_figs.setCurrentItem(self.mpl_figs.item(0))
        board_pin_info = self.board_info + "\nPin name: {0}".format(
            str(self.df_cls.ix[pin_index, 'Pin']))
        self.board_display_label.setText(board_pin_info)

    def refresh_sjq_clicked(self):
        """Redraw the label board for SJQ model."""

        # start_refresh = datetime.now()
        print('Starting to refresh...')

        predictions = self.df_cls.set_index('Pin')['SJQ Correction'].to_dict()
        dye_predictions = None

        self.refresh(predictions, dye_predictions)

        # print('start_refresh: ', datetime.now() - start_refresh)

    def refresh_sjr_clicked(self):
        """Redraw the label board for SJQ model."""

        # start_refresh = datetime.now()
        print('Starting to refresh...')

        predictions = self.df_cls.set_index('Pin')['SJR Correction'].to_dict()
        dye_predictions = self.df_cls.set_index(
            'Pin')['DYE Correction'].to_dict()

        self.refresh(predictions, dye_predictions)

        # print('start_refresh: ', datetime.now() - start_refresh)

    def sjr_image_switch(self):
        """Switch the pin image and dyed pin image for SJR models."""

        pin_index = self.mpl_figs.currentItem().data(32)

        if self.pin_dye_image == self.df_cls.loc[pin_index, 'Image Path']:
            self.pin_dye_image = self.df_cls.loc[pin_index, 'DYE Image Path']
            self.pin_image.setPixmap(
                QtGui.QPixmap(QtGui.QImage(self.pin_dye_image)))

        elif self.pin_dye_image == self.df_cls.loc[pin_index,
                                                   'DYE Image Path']:
            self.pin_dye_image = self.df_cls.loc[pin_index, 'Image Path']
            self.pin_image.setPixmap(
                QtGui.QPixmap(QtGui.QImage(self.pin_dye_image)))

    def on_click(self, event):

        if not event.inaxes:
            print('Clicked outside axes bounds but inside plot window')
            return

        if event.inaxes == self.axes1:

            self.df_cls['Image Coords'] = self.df_cls[[
                'StitchedX', 'StitchedY'
            ]].apply(tuple, axis=1)
            z = self.df_cls[['StitchedX', 'StitchedY']].values
            distance = np.linalg.norm(
                (z - np.array([event.ydata, event.xdata])), axis=1)
            row_index = np.argmin(distance)

            current_item = None
            for index in range(self.mpl_figs.count()):
                item = self.mpl_figs.item(index)
                if item.data(32) == row_index:
                    current_item = item

            if current_item:
                self.mpl_figs.setCurrentItem(current_item)
                pin_index = current_item.data(32)
                self.load_pin_cls(pin_index)
            else:
                print('No item found for row {}'.format(row_index))

        if event.inaxes == self.axes2:
            z = np.zeros(shape=(len(self.guide.pins), 2))

            for j, pin in enumerate(self.guide.pins):
                z[j] = 1000 * np.array(self.guide.position(pin))

            distance = np.linalg.norm(
                (z - np.array([event.ydata, event.xdata])), axis=1)

            k = np.argmin(distance)

            pin_name = self.guide.pins[k]
            row = self.df_cls.Pin[self.df_cls.Pin ==
                                  pin_name].index.tolist()[0]

            current_item = None
            for index in range(self.mpl_figs.count()):
                item = self.mpl_figs.item(index)
                if item.data(32) == row:
                    current_item = item

            if current_item:
                self.mpl_figs.setCurrentItem(current_item)
                pin_index = current_item.data(32)
                self.load_pin_cls(pin_index)
            else:
                print('No item found for row {}'.format(row))

    def current_item_changed(self):
        """Update the zoom-in pin image and the board image position.

        Based on the items in the scrollable list(WidgetListItem).
        """
        if self.mpl_figs.currentItem():

            pin_index = self.mpl_figs.currentItem().data(32)

            self.load_pin_cls(pin_index)
            cx_label = self.df_cls.ix[pin_index, 'Label Coords'][0] * 1000
            cy_label = self.df_cls.ix[pin_index, 'Label Coords'][1] * 1000

            try:
                self.axes2_scatter.remove()
            except:
                pass

            self.axes2_scatter = self.axes2.scatter(cy_label,
                                                    cx_label,
                                                    edgecolor='#ff01d0',
                                                    marker='s',
                                                    s=80,
                                                    linewidth='2',
                                                    facecolors='none')

            cx_image = self.df_cls.ix[pin_index, 'StitchedX']
            cy_image = self.df_cls.ix[pin_index, 'StitchedY']

            try:
                self.axes1_scatter.remove()
            except:
                pass

            self.axes1_scatter = self.axes1.scatter(cy_image,
                                                    cx_image,
                                                    edgecolor='#ff01d0',
                                                    marker='s',
                                                    s=80,
                                                    linewidth='2',
                                                    facecolors='none')

            start_canvas = datetime.now()

            self.canvas.restore_region(self.background_image)
            self.canvas.restore_region(self.background_board)
            self.axes1.draw_artist(self.axes1_scatter)
            self.axes2.draw_artist(self.axes2_scatter)
            self.canvas.blit(self.axes1.bbox)
            self.canvas.blit(self.axes2.bbox)

            self.canvas.flush_events()
            # print('canvas: ', datetime.now() - start_canvas)
            board_pin_info = self.board_info + "\nPin name: {0}".format(
                str(self.df_cls.ix[pin_index, 'Pin']))
            self.board_display_label.setText(board_pin_info)

    def load_pin_cls(self, pin_index):
        """Load the zoom-in pin image.

        And activate the appropriate radio button.
        """

        self.pin_dye_image = self.df_cls.ix[pin_index, 'Image Path']
        self.pin_image.setScaledContents(True)
        self.pin_image.setPixmap(
            QtGui.QPixmap(QtGui.QImage(self.pin_dye_image)))

        if self.board_type == 'SJR':

            classification = self.df_cls.ix[pin_index, 'SJR Correction']
            dye_cls = self.df_cls.ix[pin_index, 'DYE Correction']

            if classification == 0:
                self.type1_button.setChecked(True)
            elif classification == 1:
                self.type2_button.setChecked(True)
            elif classification == 2:
                self.type3_button.setChecked(True)
            elif classification == 3:
                self.type4_button.setChecked(True)
            elif classification == 4:
                self.reject_button.setChecked(True)

            # if dye_cls == 'O':
            #     self.no_crack_button.setChecked(True)
            if dye_cls == 'A':
                self.type_a_button.setChecked(True)
            elif dye_cls == 'B':
                self.type_b_button.setChecked(True)
            elif dye_cls == 'C':
                self.type_c_button.setChecked(True)
            elif dye_cls == 'D':
                self.type_d_button.setChecked(True)
            elif dye_cls == 'E':
                self.type_e_button.setChecked(True)

        elif self.board_type == 'SJQ':

            classification = self.df_cls.ix[pin_index, 'SJQ Correction']

            if classification == 0:
                self.nwo_button.setChecked(True)
            elif classification == 1:
                self.nco_button.setChecked(True)
            elif classification == 2:
                self.normal_button.setChecked(True)
            elif classification == 3:
                self.hnp_button.setChecked(True)
            elif classification == 4:
                self.sb_button.setChecked(True)

    def sort_sjq(self, row):

        if row['SJQ Correction'] == 0:
            value = 'a'
        elif row['SJQ Correction'] == 1:
            value = 'b'
        elif row['SJQ Correction'] == 2:
            value = 'e'
        elif row['SJQ Correction'] == 3:
            value = 'c'
        elif row['SJQ Correction'] == 4:
            value = 'd'
        else:
            raise ValueError('Unknown classification')

        return value

    def set_buttons(self, column_names):

        if all(cn in column_names for cn in ['SJR', 'SJQ', 'DYE']):

            if not(self.df_cls['SJR'].isnull().all()) and \
                    not(self.df_cls['DYE'].isnull().all()) and \
                    self.df_cls['SJQ'].isnull().all():

                self.board_type = 'SJR'
                self.nwo_button.setDisabled(True)
                self.nco_button.setDisabled(True)
                self.normal_button.setDisabled(True)
                self.hnp_button.setDisabled(True)
                self.sb_button.setDisabled(True)
                self.refresh_sjq_button.setDisabled(True)
                self.type1_button.setDisabled(False)
                self.type2_button.setDisabled(False)
                self.type3_button.setDisabled(False)
                self.type4_button.setDisabled(False)
                self.reject_button.setDisabled(False)
                # self.no_crack_button.setDisabled(False)
                self.type_a_button.setDisabled(False)
                self.type_b_button.setDisabled(False)
                self.type_c_button.setDisabled(False)
                self.type_d_button.setDisabled(False)
                self.type_e_button.setDisabled(False)
                self.refresh_sjr_button.setDisabled(False)
                self.sjr_image_button.setDisabled(False)

            elif not(self.df_cls['SJR'].isnull().all()) and \
                    not(self.df_cls['SJQ'].isnull().all()) and \
                    self.df_cls['DYE'].isnull().all():

                self.board_type = 'SJQ'
                self.nwo_button.setDisabled(False)
                self.nco_button.setDisabled(False)
                self.normal_button.setDisabled(False)
                self.hnp_button.setDisabled(False)
                self.sb_button.setDisabled(False)
                self.refresh_sjq_button.setDisabled(False)
                self.type1_button.setDisabled(True)
                self.type2_button.setDisabled(True)
                self.type3_button.setDisabled(True)
                self.type4_button.setDisabled(True)
                self.reject_button.setDisabled(True)
                # self.no_crack_button.setDisabled(True)
                self.type_a_button.setDisabled(True)
                self.type_b_button.setDisabled(True)
                self.type_c_button.setDisabled(True)
                self.type_d_button.setDisabled(True)
                self.type_e_button.setDisabled(True)
                self.refresh_sjr_button.setDisabled(True)
                self.sjr_image_button.setDisabled(True)

            else:
                raise ValueError('Incomplete CSV file')

        else:
            raise ValueError('Unknown board')

    def sort_classification(self):

        if self.board_type == 'SJQ':
            self.df_cls['Sorted SJQ'] = self.df_cls['SJQ Correction']

            self.df_cls['Sorted SJQ'] = self.df_cls.apply(self.sort_sjq,
                                                          axis=1)
            self.df_cls = self.df_cls.sort_values(
                by=['Sorted SJQ', 'SJR Correction'], ascending=[True, True])
            self.df_cls = self.df_cls.reset_index(drop=True)

        if self.board_type == 'SJR':

            self.df_cls = self.df_cls.sort_values(
                by=['SJR Correction', 'DYE Correction'],
                ascending=[True, False])
            self.df_cls = self.df_cls.reset_index(drop=True)

    def pin_dividers(self):

        # Create columns to indicate the divider location for the QListWidgetItem
        if self.board_type == 'SJQ':
            self.df_cls['Type Change'] = self.df_cls['SJQ Correction'].shift(
                -1) != self.df_cls['SJQ Correction']
            if self.df_cls.ix[0, 'SJQ Correction'] != self.df_cls.ix[
                    1, 'SJQ Correction']:
                self.df_cls.ix[0, 'Type Change'] = True
            else:
                self.df_cls.ix[0, 'Type Change'] = False

        elif self.board_type == 'SJR':
            self.df_cls['Type Change'] = self.df_cls['SJR Correction'].shift(
                -1) != self.df_cls['SJR Correction']
            if self.df_cls.ix[0, 'SJR Correction'] != self.df_cls.ix[
                    1, 'SJR Correction']:
                self.df_cls.ix[0, 'Type Change'] = True
            else:
                self.df_cls.ix[0, 'Type Change'] = False

    def load_pins(self, first_load=False):

        if first_load:
            self.list_items = {}

        count_image_cat = 0

        # Create a list of the 'Image Path'  column
        image_path_values = self.df_cls['Image Path'].values.tolist()
        for image_path in self.df_cls['Image Path'].tolist():

            if first_load:
                icon = QtGui.QIcon(image_path)
            else:
                icon = self.list_items[image_path]

            # Adding the pin images to the scrollable list (WidgetListItem)
            item = QtWidgets.QListWidgetItem()
            item.setIcon(icon)
            self.list_items[image_path] = icon

            index = image_path_values.index(image_path)
            item.setData(32, index)

            self.mpl_figs.addItem(item)

            count_image_cat += 1

            if self.df_cls.iloc[index]['Type Change'] == True:

                mod_of_5 = count_image_cat % 5
                if mod_of_5 != 0:
                    amount_of_white = (5 - mod_of_5) + 5
                else:
                    amount_of_white = 5

                for x in range(0, amount_of_white):

                    item = QtWidgets.QListWidgetItem()  # delimiter
                    item.setData(32, -1)
                    item.setFlags(
                        QtCore.Qt.NoItemFlags)  # item should not be selectable
                    self.mpl_figs.addItem(item)

                count_image_cat = 0

    def get_pins(self, root):
        """Load list of pin icons."""

        # Reading the CSV file from the appropriate path
        self.df_cls = pd.read_csv(os.path.join(root, 'classification.csv'))

        if not ('SJQ Correction' in self.df_cls.columns):
            self.df_cls['SJQ Correction'] = self.df_cls['SJQ'].copy()
        if not ('SJR Correction' in self.df_cls.columns):
            self.df_cls['SJR Correction'] = self.df_cls['SJR'].copy()
        if not ('DYE Correction' in self.df_cls.columns):
            self.df_cls['DYE Correction'] = self.df_cls['DYE'].copy()

        # Adding the Pin column and adding the pin names to it by
        # combining the Col and Row Columns
        self.df_cls['Pin'] = self.df_cls[['Row', 'Col']].apply(
            lambda x: '{}{}{}'.format(x[0], '_', x[1]), axis=1)

        # Adding a label for the index column
        self.df_cls.columns.names = ['Index']

        column_names = list(self.df_cls)

        self.set_buttons(column_names)

        self.sort_classification()

        df_pin_list = self.df_cls['Pin'].tolist()

        # Adding the path of the each pin image to the Dataframe

        # Create a column in the dataframe to store the image paths
        self.df_cls['Image Path'] = np.nan

        # Create a column in the dataframe to store the DYE image paths
        self.df_cls['DYE Image Path'] = np.nan

        # Matching the pins with the correct image and adding them to
        # the 'Image Path' column

        # for every file (image_path) in the pins folder:
        for f in os.listdir(os.path.join(root, 'pins')):

            if f.endswith("_1.tif"):
                image_path = os.path.join(os.getcwd(),
                                          os.path.join(root, 'pins'), f)
                pin_name = os.path.basename(image_path).replace("_1.tif", "")

                # finding the index of the row where the pin is located in
                # the dataframe
                if pin_name in df_pin_list:

                    index = self.df_cls[self.df_cls['Pin'] ==
                                        pin_name].index[0]

                    # adding the pin's image path to the same row in the dataframe
                    # where the pin is located
                    self.df_cls.loc[index, 'Image Path'] = image_path

        if self.board_type == 'SJR':
            if 'dye' in os.listdir(os.path.join(root, '')):
                for f in os.listdir(os.path.join(root, 'dye')):

                    dye_path = os.path.join(os.getcwd(),
                                            os.path.join(root, 'dye'), f)
                    pin_name = os.path.basename(dye_path).replace("_1.tif", "")

                    if pin_name in df_pin_list:

                        index = self.df_cls[self.df_cls['Pin'] ==
                                            pin_name].index[0]

                        self.df_cls.loc[index, 'DYE Image Path'] = dye_path

            else:
                print('Unable to locate the DYE folder. Thus no dye images')
                self.sjr_image_button.setDisabled(True)

        self.pin_dividers()

        self.load_pins(first_load=True)

    def load_board(self, root):
        """Load the appropriate board based on the menu clicked."""
        self.centralwidget.setDisabled(True)

        print('Path: ', root)
        board_name = os.path.basename(root)

        print('Board name: ', board_name)
        print('Board type: ', self.board_type)

        new_font = QtGui.QFont("Arial", 10, QtGui.QFont.Bold)
        self.board_display_label.setFont(new_font)
        self.board_info = "Board name: {0}\nBoard type: {1}".format(
            board_name, self.board_type)
        self.board_display_label.setText(self.board_info)

        with open(os.path.join(root, 'info.txt')) as json_file:
            data = json.load(json_file)
            print('guide path: ', data['guidePath'])

        csvpath = data['guidePath']
        b2p_ratio = int(data['b2p_ratio'])
        from guides.generalguide import GeneralGuide

        self.guide = GeneralGuide(csvpath, b2p_ratio)

        pin_coords = []

        for pin in self.df_cls['Pin'].tolist():
            if pin in self.guide.pins:
                pin_coords.append(tuple(self.guide.position(pin)))

        self.df_cls['Label Coords'] = pd.Series(pin_coords,
                                                index=self.df_cls.index)

        self.drawing_tool = DrawPredictions(guide=self.guide,
                                            mode=self.board_type)

        cls_corrected_col = "{} Correction".format(self.board_type)
        # print('cls_corrected_col', cls_corrected_col)

        predictions = self.df_cls.set_index('Pin')[cls_corrected_col].to_dict()

        if self.board_type == 'SJR':
            dye_predictions = self.df_cls.set_index(
                'Pin')['DYE Correction'].to_dict()
            self.board_label = self.drawing_tool(shape=1000,
                                                 pred=predictions,
                                                 dyepred=dye_predictions)
        elif self.board_type == 'SJQ':
            self.board_label = self.drawing_tool(shape=1000, pred=predictions)
        else:
            raise ValueError('Unknown board')

        self.board_img = mpimg.imread(os.path.join(root, 'stitched.png'))

        gs = gridspec.GridSpec(1, 2)
        gs.update(left=0.005, right=0.99, wspace=0.05)

        self.axes1 = self.figure.add_subplot(gs[0])
        self.axes1.get_xaxis().set_visible(False)
        self.axes1.get_yaxis().set_visible(False)

        self.axes2 = self.figure.add_subplot(gs[1])
        self.axes2.get_xaxis().set_visible(False)
        self.axes2.get_yaxis().set_visible(False)

        # print('init scatter')

        self.axes1_scatter = None
        self.axes2_scatter = None

        self.canvas.mpl_connect('resize_event', self.connect_resize)
        self.export_action.triggered.connect(
            functools.partial(self.save_board_label, root))
        self.export_action.setDisabled(False)

        self.run_macro_action.triggered.connect(
            functools.partial(self.run_excel_macro, root))

        self.first_load = False

        self.centralwidget.setDisabled(False)

    def connect_resize(self, event):
        print('resize event', event)

        self.centralwidget.setDisabled(True)

        self.perform_resize()

        # try:
        #     print('resize event try', event)
        #     self.resize_timer.cancel()
        # except:
        #     pass
        #
        # self.resize_timer = Timer(1, self.perform_resize)
        # self.resize_timer.start()

    def perform_resize(self):
        # print('perform_resize')

        self.axes1.imshow(self.board_img)
        self.axes2.imshow(self.board_label)

        try:
            self.axes1_scatter.remove()
        except:
            print('Unable to remove axes1_scatter')
            pass

        try:
            self.axes2_scatter.remove()
        except:
            print('Unable to remove axes2_scatter')
            pass

        self.canvas.draw()

        self.background_image = self.canvas.copy_from_bbox(self.axes1.bbox)
        self.background_board = self.canvas.copy_from_bbox(self.axes2.bbox)

        self.centralwidget.setDisabled(False)

    def connect_click(self):
        """Just testing this."""

        # connect the click or arrow keys press on the the scrollable
        # image list to the currentItemChanged
        self.mpl_figs.currentItemChanged.connect(self.current_item_changed)

        self.nwo_button.toggled.connect(self.nwo_button_clicked)

        self.nco_button.toggled.connect(self.nco_button_clicked)

        self.normal_button.toggled.connect(self.normal_button_clicked)

        self.hnp_button.toggled.connect(self.hnp_button_clicked)

        self.sb_button.toggled.connect(self.sb_button_clicked)

        self.type1_button.toggled.connect(self.type1_button_clicked)

        self.type2_button.toggled.connect(self.type2_button_clicked)

        self.type3_button.toggled.connect(self.type3_button_clicked)

        self.type4_button.toggled.connect(self.type4_button_clicked)

        self.reject_button.toggled.connect(self.reject_button_clicked)

        # self.no_crack_button.toggled.connect(self.no_crack_button_clicked)

        self.type_a_button.toggled.connect(self.type_a_button_clicked)

        self.type_b_button.toggled.connect(self.type_b_button_clicked)

        self.type_c_button.toggled.connect(self.type_c_button_clicked)

        self.type_d_button.toggled.connect(self.type_d_button_clicked)

        self.type_e_button.toggled.connect(self.type_e_button_clicked)

        self.refresh_sjq_button.clicked.connect(self.refresh_sjq_clicked)

        self.refresh_sjr_button.clicked.connect(self.refresh_sjr_clicked)

        self.sjr_image_button.clicked.connect(self.sjr_image_switch)

        self.canvas.callbacks.connect('button_press_event', self.on_click)

    def save_board_label(self, path):
        import cv2
        cv2.imwrite(os.path.join(path, 'board.png'),
                    255 * self.board_label[:, :, ::-1])

    def run_excel_macro(self, path):

        try:
            xlApp = win32com.client.Dispatch('Excel.Application')
            xlsPath = os.path.abspath(os.path.join(path, 'MacroTest.xlsm'))
            print(xlsPath)
            macroxl = xlApp.Workbooks.Open(xlsPath)
            xlApp.Run('MacroTest.xlsm!TEST_1')
            macroxl.Save()
            xlApp.Quit()
            print("Macro ran successfully!")

        except Exception as e:
            print("Error found while running the excel macro!")
            raise

    def load_image(self, root):
        """Construct the board image and load it on matplotlib container."""
        # Rest the matplotlib container and the buttons

        print('Starting to load the images...')

        if not self.first_load:
            self.remove_board()

        # Add a matplotlib container
        self.add_mpl()

        # load pin images in the zoom-in container and
        # the scrollable image list
        self.get_pins(root)

        self.load_pin_cls(0)

        self.load_board(root)

        self.connect_click()

    def closeEvent(self, event):
        try:
            self.resize_timer.cancel()
        except:
            pass

    def load_model(self, root, board_name, save_path):

        default_settings = {
            'sampleID': board_name,
            'rawImgPath': root,
            'cropSavePath': os.path.join(save_path, "{}", 'pins'),
            'savePath': os.path.join(save_path, "{}"),
            'additionalCrop': 'None',
            'b2p_ratio': '150',
            'device': 'gpu0'
        }

        dialog = Ui_SettingsWindow(default_settings)
        dialog.setAttribute(QtCore.Qt.WA_DeleteOnClose)

        exit_code = dialog.exec_()
        # print(exit_code)

        if exit_code:
            # print(dialog.values)
            settings_dict = dialog.values

            userName = settings_dict['userName']
            sampleID = settings_dict['sampleID']
            analysisType = settings_dict['analysisType']
            rawImgPath = settings_dict['rawImgPath']
            cropSavePath = settings_dict['cropSavePath']
            segDataPath = settings_dict['segDataPath']
            savePath = settings_dict['savePath']
            guidePath = settings_dict['guidePath']
            additionalCrop = settings_dict['additionalCrop']
            b2p_ratio = int(settings_dict['b2p_ratio'])
            device = settings_dict['device']

            class TaskThread(QtCore.QThread):
                def run(self):
                    print("task is running")
                    try:
                        from guides.generalguide import GeneralGuide
                        guide = GeneralGuide(guidePath, b2p_ratio)

                        from model import Model
                        valid_additionalCrop = None if additionalCrop == 'None' else (
                            int(additionalCrop), int(additionalCrop))

                        f_model = Model(sampleID, analysisType, guide,
                                        valid_additionalCrop, rawImgPath,
                                        segDataPath, cropSavePath, savePath,
                                        device)

                        from time import time
                        st = time()
                        print('Starting the analysis...')
                        f_model(saveResults=True)
                        sp = time() - st
                        print('Analysis took {} seconds'.format(sp))
                    except Exception as e:
                        print(e)

            task_thread = TaskThread()

            from progress_bar import ConstantProgressBar
            progress_dialog = ConstantProgressBar(task_thread)
            exit_code = progress_dialog.exec_()
            print(exit_code)

            json.dump(settings_dict,
                      open(os.path.join(savePath, 'info.txt'), 'w'),
                      indent=4)

            self.refresh_menus()

            self.load_image(savePath)
Beispiel #2
0
class MainWidget(QWidget):
    def __init__(self):
        super(MainWidget, self).__init__()
        self.initUI()

    def initUI(self):
        self.resize(1100, 650)
        self.setWindowTitle("Stok Viewer")
        vbox = QVBoxLayout()
        self.setLayout(vbox)

        dailyBtn = QPushButton("Daily", self)
        weeklyBtn = QPushButton("Weeky", self)
        monthlyBtn = QPushButton("Monthly", self)
        dailyBtn.clicked.connect(lambda: self.plot("day"))
        weeklyBtn.clicked.connect(lambda: self.plot("week"))
        monthlyBtn.clicked.connect(lambda: self.plot("month"))
        timelineBtnBox = QHBoxLayout()
        timelineBtnBox.addStretch(1)
        timelineBtnBox.addWidget(dailyBtn)
        timelineBtnBox.addWidget(weeklyBtn)
        timelineBtnBox.addWidget(monthlyBtn)

        # widgets and layouts for plotting
        self.ybuffer = 5.5  # buffer area for bottom and top(%)
        self.zoomSpeed = 5  # How much area to zoom for one scroll
        self.figure = Figure()
        self.canvas = Canvas(self.figure)
        self.canvas.mpl_connect("button_press_event", self.onPressEvent)
        self.canvas.mpl_connect("button_release_event", self.onReleaseEvent)
        self.canvas.mpl_connect("scroll_event", self.onScrollEvent)
        self.chartBox = QHBoxLayout()
        self.chartBox.addWidget(self.canvas)
        vbox.addLayout(timelineBtnBox)
        vbox.addLayout(self.chartBox)
        self.plot("day")
        self.show()

    def plot(self, timeline=""):
        self.figure.clf()
        self.df = DataFetcher.getStockByTimeline("006400.KS", timeline)
        self.ax = self.figure.add_subplot()
        rsiList = getRsi(self.df)
        self.canvas.draw_idle()
        custplot(self.ax, self.df)

        ### 중복되는 기능, delete it later
        self.lineEndList = []
        self.lineStartList = []
        for obj in self.ax.get_children():
            if type(obj) is type(matplotlib.collections.LineCollection([])):
                self.lineStartList.append(
                    obj.get_paths()[0].get_extents().get_points()[0][1])
                self.lineEndList.append(
                    obj.get_paths()[0].get_extents().get_points()[1][1])
        ###
        self.autoscale()

    def onPressEvent(self, event):
        # check if the pressed button was "LEFT"
        if not event.button == 1:
            return

        self.lineEndList = []
        self.lineStartList = []
        for obj in self.ax.get_children():
            if type(obj) is type(matplotlib.collections.LineCollection([])):
                self.lineStartList.append(
                    obj.get_paths()[0].get_extents().get_points()[0][1])
                self.lineEndList.append(
                    obj.get_paths()[0].get_extents().get_points()[1][1])

        for obj in self.ax.get_children():
            if type(obj) is type(matplotlib.patches.Rectangle):
                break
                #print(obj)

        self.xStart = self.ax.get_xlim()[0]
        self.xCursor = event.x
        self.cidDrag = self.canvas.mpl_connect("motion_notify_event",
                                               self.onDragEvent)

    def onDragEvent(self, event):
        xlim = self.ax.get_xlim()
        # make drag limits to prevent overflow
        ###
        # IMPLMENT PENDING
        ###
        movement = (event.x - self.xCursor) / 10
        self.xCursor = event.x
        self.ax.set_xlim(xlim[0] - movement, xlim[1] - movement)
        self.autoscale()
        self.canvas.draw_idle()

    def onReleaseEvent(self, event):
        self.canvas.mpl_disconnect(self.cidDrag)

    # method for getting new range of axes and updating them to the df
    def updateAxes(self):
        # calculate how much x axis was moved
        xlim = self.ax.get_xlim()
        xEnd = -xlim[0]

        # get new df
        ogdate = self.df["Date"].iloc[0]
        newdf = DataFetcher.getStockByDate("006400.KS", ogdate, xEnd)
        newax = custplot(self.ax, newdf, timeline="day")

        # update the new df to the ax
        background = self.canvas.copy_from_bbox(self.ax.bbox)
        self.ax.draw_artist(newax)
        self.canvas.blit(self.ax.bbox)

    def onScrollEvent(self, event):
        xlim = self.ax.get_xlim()
        width = xlim[1] - xlim[0]
        if event.button == "up" or event.button == "down":
            if event.button == "up":
                xmin = xlim[0] + (width / 100 * self.zoomSpeed)
                xmax = xlim[1] - (width / 100 * self.zoomSpeed)
            else:
                xmin = xlim[0] - (width / 100 * self.zoomSpeed)
                xmax = xlim[1] + (width / 100 * self.zoomSpeed)
            self.ax.set_xlim(xmin, xmax)
            self.autoscale()
            self.canvas.draw_idle()
        else:
            print("error: unrecognized scroll movement")

    # method for autoscaling y axis heights according to y limits
    def autoscale(self):
        xlim = self.ax.get_xlim()
        ylim = self.ax.get_ylim()
        # check if updating lines are out of bound
        start = int(xlim[0]) if int(xlim[0]) > 0 else 0
        end = int(xlim[1]) if int(xlim[1]) < len(self.df["High"]) else (
            len(self.df["High"]) - 1)
        # get the highest and lowest point of y from start to end
        highest = max(self.lineEndList[start:end + 1])
        lowest = min(self.lineStartList[start:end + 1])

        height = highest - lowest
        self.ax.set_ylim(lowest - (height / 100 * self.ybuffer),
                         highest + (height / 100 * self.ybuffer))
Beispiel #3
0
class MatPlotLibBase(QWidget):
    def __init__(self,
                 parent,
                 file_dialog_service,
                 h_margin=(0.8, 0.1),
                 v_margin=(0.5, 0.15),
                 h_axes=[Size.Scaled(1.0)],
                 v_axes=[Size.Scaled(1.0)],
                 nx_default=1,
                 ny_default=1):
        QWidget.__init__(self, parent)
        self._file_dialog_service = file_dialog_service
        self._figure = Figure()
        self._canvas = FigureCanvas(self._figure)
        h = [Size.Fixed(h_margin[0]), *h_axes, Size.Fixed(h_margin[1])]
        v = [Size.Fixed(v_margin[0]), *v_axes, Size.Fixed(v_margin[1])]
        self._divider = Divider(self._figure, (0.0, 0.0, 1.0, 1.0),
                                h,
                                v,
                                aspect=False)
        self._axes = LocatableAxes(self._figure, self._divider.get_position())
        self._axes.set_axes_locator(
            self._divider.new_locator(nx=nx_default, ny=ny_default))
        self._axes.set_zorder(2)
        self._axes.patch.set_visible(False)
        for spine in ['top', 'right']:
            self._axes.spines[spine].set_visible(False)
        self._figure.add_axes(self._axes)

        self._canvas.setParent(self)

        self._layout = QVBoxLayout(self)
        self._layout.setContentsMargins(0, 0, 0, 0)
        self._layout.addWidget(self._canvas)
        self.setLayout(self._layout)

        self._figure.canvas.mpl_connect('scroll_event', self._on_scroll)
        self._xy_extents = None
        self._background_cache = None
        self._decoration_artists = []
        self._is_panning = False

        self._zoom_selector = _RectangleSelector(self._axes,
                                                 self._zoom_selected)
        self._zoom_selector.set_active(False)
        self._x_extent_padding = 0.01
        self._y_extent_padding = 0.01
        self._axes.ticklabel_format(style='sci', axis='x', scilimits=(-4, 4))
        self._axes.ticklabel_format(style='sci', axis='y', scilimits=(-4, 4))
        self._active_tools = {}
        self._span = _SpanSeletor(self._axes,
                                  self._handle_span_select,
                                  'horizontal',
                                  rectprops=dict(alpha=0.2,
                                                 facecolor='red',
                                                 edgecolor='k'),
                                  span_stays=True)
        self._span.set_on_select_none(self._handle_span_select_none)
        self.span = self._previous_span = None
        self._span_center_mouse_event = None
        self._span_left_mouse_event = None
        self._span_right_mouse_event = None
        self._figure.canvas.mpl_connect('button_press_event',
                                        self._handle_press)
        self._figure.canvas.mpl_connect('motion_notify_event',
                                        self._handle_move)
        self._figure.canvas.mpl_connect('button_release_event',
                                        self._handle_release)
        self._figure.canvas.mpl_connect('resize_event', self._handle_resize)
        self.activateTool(ToolType.span, self.isActiveDefault(ToolType.span))
        self._pan_event = None
        self._pending_draw = None
        self._pending_artists_draw = None
        self._other_draw_events = []
        self._draw_timer = QTimer(self)
        self._draw_timer.timeout.connect(self._do_draw_events)
        self._draw_timer.start(20)
        self._zoom_skew = None

        self._menu = QMenu(self)
        self._copy_image_action = QAction(self.tr('Copy To Clipboard'), self)
        self._copy_image_action.triggered.connect(self.copyToClipboard)
        self._copy_image_action.setShortcuts(QKeySequence.Copy)
        self._save_image_action = QAction(self.tr('Save As Image'), self)
        self._save_image_action.triggered.connect(self.saveAsImage)
        self._show_table_action = QAction(self.tr('Show Table'), self)
        self._show_table_action.triggered.connect(self.showTable)
        self._menu.addAction(self._copy_image_action)
        self._menu.addAction(self._save_image_action)
        self._menu.addAction(self._show_table_action)
        self.addAction(self._copy_image_action)

        self._table_view = None
        self._single_axis_zoom_enabled = True
        self._cached_label_width_height = None

        if hasattr(type(self), 'dataChanged'):
            self.dataChanged.connect(self._on_data_changed)

        self._options_view = None
        self._secondary_axes = self._secondary_y_extent = self._secondary_x_extent = None
        self._legend = None
        self._draggable_legend = None
        self._setting_axis_limits = False

        self.hasHiddenSeries = False

    enabledToolsChanged = pyqtSignal()
    spanChanged = pyqtSignal(SpanModel)
    hasHiddenSeriesChanged = pyqtSignal(bool)

    span = AutoProperty(SpanModel)
    hasHiddenSeries = AutoProperty(bool)

    def setOptionsView(self, options_view):
        self._options_view = options_view
        self._options_view.setSecondaryYLimitsEnabled(
            self._secondary_y_enabled())
        self._options_view.setSecondaryXLimitsEnabled(
            self._secondary_x_enabled())

        self._options_view.showGridLinesChanged.connect(
            self._update_grid_lines)
        self._options_view.xAxisLowerLimitChanged.connect(
            self._handle_options_view_limit_changed(x_min_changed=True))
        self._options_view.xAxisUpperLimitChanged.connect(
            self._handle_options_view_limit_changed(x_max_changed=True))
        self._options_view.yAxisLowerLimitChanged.connect(
            self._handle_options_view_limit_changed(y_min_changed=True))
        self._options_view.yAxisUpperLimitChanged.connect(
            self._handle_options_view_limit_changed(y_max_changed=True))
        self._options_view.xAxisLimitsChanged.connect(
            self._handle_options_view_limit_changed(x_min_changed=True,
                                                    x_max_changed=True))
        self._options_view.yAxisLimitsChanged.connect(
            self._handle_options_view_limit_changed(y_min_changed=True,
                                                    y_max_changed=True))

        self._options_view.secondaryXAxisLowerLimitChanged.connect(
            self._handle_options_view_secondary_limit_changed(
                x_min_changed=True))
        self._options_view.secondaryXAxisUpperLimitChanged.connect(
            self._handle_options_view_secondary_limit_changed(
                x_max_changed=True))
        self._options_view.secondaryYAxisLowerLimitChanged.connect(
            self._handle_options_view_secondary_limit_changed(
                y_min_changed=True))
        self._options_view.secondaryYAxisUpperLimitChanged.connect(
            self._handle_options_view_secondary_limit_changed(
                y_max_changed=True))
        self._options_view.secondaryXAxisLimitsChanged.connect(
            self._handle_options_view_secondary_limit_changed(
                x_min_changed=True, x_max_changed=True))
        self._options_view.secondaryYAxisLimitsChanged.connect(
            self._handle_options_view_secondary_limit_changed(
                y_min_changed=True, y_max_changed=True))

    def setLegendControl(self, legend_control):
        self._legend_control = legend_control
        self._legend_control.seriesUpdated.connect(self._legend_series_updated)
        self._legend_control.showLegendChanged.connect(self._show_legend)
        self._legend_control.seriesNameChanged.connect(
            self._handle_series_name_changed)
        self._legend_control.showSeriesChanged.connect(
            self._handle_show_series_changed)
        bind(self._legend_control, self, 'hasHiddenSeries', two_way=False)

    def _legend_series_updated(self):
        if self._legend is not None:
            self._show_legend(self._legend_control.showLegend)

    def _show_legend(self, show):
        if self._legend and not show:
            self._legend.remove()
            self._legend = None
            self.draw()
        elif show:
            if self._legend:
                self._legend.remove()
            show_series = self._legend_control.showSeries
            handles = [
                h for h, s in zip(self._legend_control.seriesHandles,
                                  show_series) if s
            ]
            names = [
                n
                for n, s in zip(self._legend_control.seriesNames, show_series)
                if s
            ]
            axes = (self._secondary_axes if self._secondary_axes
                    and self._secondary_axes.get_visible()
                    and self._secondary_axes.get_zorder() >
                    self._axes.get_zorder() else self._axes)
            self._legend = self._create_legend(
                axes,
                handles,
                names,
                markerscale=self._get_legend_markerscale())
            if self._get_legend_text_color() is not None:
                for text in self._legend.texts:
                    text.set_color(self._get_legend_text_color())
            self._draggable_legend = DraggableLegend(self._legend)
            self.draw()

    def _get_legend_markerscale(self):
        return 5

    def _create_legend(self, axes, handles, names, **kwargs):
        return axes.legend(handles, names, **kwargs)

    def _get_legend_text_color(self):
        return None

    def _handle_series_name_changed(self, index, series_name):
        if self._legend is not None and index < len(
                self._legend_control.seriesHandles):
            visible_handles = [
                h for h, s in zip(self._legend_control.seriesHandles,
                                  self._legend_control.showSeries)
                if s and h is not None
            ]
            try:
                legend_index = visible_handles.index(
                    self._legend_control.seriesHandles[index])
            except ValueError:
                return
            if legend_index < len(self._legend.texts):
                self._legend.texts[legend_index].set_text(series_name)
                self.draw()

    def _handle_show_series_changed(self, index, show_series):
        if index < len(self._legend_control.seriesHandles):
            self._set_series_visibility(
                self._legend_control.seriesHandles[index], show_series)
        if self._legend is not None:
            self._show_legend(self._legend_control.showLegend)
        else:
            self.draw()

    def _set_series_visibility(self, handle, visible):
        if not handle:
            return
        if hasattr(handle, 'set_visible'):
            handle.set_visible(visible)
        elif hasattr(handle, 'get_children'):
            for child in handle.get_children():
                self._set_series_visibility(child, visible)

    def _update_grid_lines(self):
        show_grid_lines = False if self._options_view is None else self._options_view.showGridLines
        gridline_color = self._axes.spines['bottom'].get_edgecolor()
        gridline_color = gridline_color[0], gridline_color[1], gridline_color[
            2], 0.5
        kwargs = dict(color=gridline_color,
                      alpha=0.5) if show_grid_lines else {}
        self._axes.grid(show_grid_lines, **kwargs)
        self.draw()

    def _handle_options_view_limit_changed(self,
                                           x_min_changed=False,
                                           x_max_changed=False,
                                           y_min_changed=False,
                                           y_max_changed=False):
        def _():
            if self._options_view is None or self._setting_axis_limits:
                return
            (x_min, x_max), (y_min, y_max) = (new_x_min, new_x_max), (
                new_y_min, new_y_max) = self._get_xy_extents()
            (x_opt_min,
             x_opt_max), (y_opt_min,
                          y_opt_max) = self._get_options_view_xy_extents()
            if x_min_changed:
                new_x_min = x_opt_min
            if x_max_changed:
                new_x_max = x_opt_max
            if y_min_changed:
                new_y_min = y_opt_min
            if y_max_changed:
                new_y_max = y_opt_max
            if [new_x_min, new_x_max, new_y_min, new_y_max
                ] != [x_min, x_max, y_min, y_max]:
                self._xy_extents = (new_x_min, new_x_max), (new_y_min,
                                                            new_y_max)
                self._set_axes_limits()
                self.draw()

        return _

    def _get_options_view_xy_extents(self):
        (x_data_min, x_data_max), (y_data_min,
                                   y_data_max) = self._get_data_xy_extents()
        x_min = x_data_min if np.isnan(
            self._options_view.xAxisLowerLimit
        ) else self._options_view.xAxisLowerLimit
        x_max = x_data_max if np.isnan(
            self._options_view.xAxisUpperLimit
        ) else self._options_view.xAxisUpperLimit
        y_min = y_data_min if np.isnan(
            self._options_view.yAxisLowerLimit
        ) else self._options_view.yAxisLowerLimit
        y_max = y_data_max if np.isnan(
            self._options_view.yAxisUpperLimit
        ) else self._options_view.yAxisUpperLimit
        return (x_min, x_max), (y_min, y_max)

    def _handle_options_view_secondary_limit_changed(self,
                                                     x_min_changed=False,
                                                     x_max_changed=False,
                                                     y_min_changed=False,
                                                     y_max_changed=False):
        def _():
            if self._options_view is None or self._setting_axis_limits:
                return
            updated = False
            (x_opt_min, x_opt_max), (
                y_opt_min,
                y_opt_max) = self._get_options_view_secondary_xy_extents()
            if self._has_secondary_y_extent() and (y_min_changed
                                                   or y_max_changed):
                y_min, y_max = new_y_min, new_y_max = self._get_secondary_y_extent(
                )
                if y_min_changed:
                    new_y_min = y_opt_min
                if y_max_changed:
                    new_y_max = y_opt_max
                if [new_y_min, new_y_max] != [y_min, y_max]:
                    self._secondary_y_extent = (new_y_min, new_y_max)
                    updated = True
            if self._has_secondary_x_extent() and (x_min_changed
                                                   or x_max_changed):
                x_min, x_max = new_x_min, new_x_max = self._get_secondary_x_extent(
                )
                if x_min_changed:
                    new_x_min = x_opt_min
                if x_max_changed:
                    new_x_max = x_opt_max
                if [new_x_min, new_x_max] != [x_min, x_max]:
                    self._secondary_x_extent = (new_x_min, new_x_max)
                    updated = True
            if updated:
                self._set_axes_limits()
                self.draw()

        return _

    def _get_options_view_secondary_xy_extents(self):
        x_data_min, x_data_max = self._get_data_secondary_x_extent()
        y_data_min, y_data_max = self._get_data_secondary_y_extent()
        x_min = x_data_min if np.isnan(
            self._options_view.secondaryXAxisLowerLimit
        ) else self._options_view.secondaryXAxisLowerLimit
        x_max = x_data_max if np.isnan(
            self._options_view.secondaryXAxisUpperLimit
        ) else self._options_view.secondaryXAxisUpperLimit
        y_min = y_data_min if np.isnan(
            self._options_view.secondaryYAxisLowerLimit
        ) else self._options_view.secondaryYAxisLowerLimit
        y_max = y_data_max if np.isnan(
            self._options_view.secondaryYAxisUpperLimit
        ) else self._options_view.secondaryYAxisUpperLimit
        return (x_min, x_max), (y_min, y_max)

    def _on_data_changed(self):
        self._cached_label_width_height = None

    def closeEvent(self, event):
        QWidget.closeEvent(self, event)
        if event.isAccepted():
            self._zoom_selector.onselect = self._span.onselect = self._span._select_none_handler = None

    def set_divider_h_margin(self, h_margin):
        h = [
            Size.Fixed(h_margin[0]),
            Size.Scaled(1.0),
            Size.Fixed(h_margin[1])
        ]
        self._divider.set_horizontal(h)

    def set_divider_v_margin(self, v_margin):
        v = [
            Size.Fixed(v_margin[0]),
            Size.Scaled(1.0),
            Size.Fixed(v_margin[1])
        ]
        self._divider.set_vertical(v)

    @property
    def x_extent_padding(self):
        return self._x_extent_padding

    @x_extent_padding.setter
    def x_extent_padding(self, value):
        self._x_extent_padding = value

    @property
    def y_extent_padding(self):
        return self._y_extent_padding

    @y_extent_padding.setter
    def y_extent_padding(self, value):
        self._y_extent_padding = value

    def _in_interval(self, value, interval):
        return interval[0] <= value <= interval[1]

    def _interval_skew(self, value, interval):
        return (value - interval[0]) / (interval[1] - interval[0])

    def _in_x_scroll_zone(self, event):
        return self._in_interval(event.x, self._axes.bbox.intervalx
                                 ) and event.y <= self._axes.bbox.intervaly[1]

    def _in_y_scroll_zone(self, event):
        return self._in_interval(event.y, self._axes.bbox.intervaly
                                 ) and event.x <= self._axes.bbox.intervalx[1]

    def _on_scroll(self, event):
        if self._secondary_axes is not None:
            self._handle_scroll_secondary(event)
        in_x = self._in_x_scroll_zone(event)
        in_y = self._in_y_scroll_zone(event)
        if in_x or in_y and event.button in ['up', 'down']:
            (x_min, x_max), (y_min, y_max) = self._get_actual_xy_extents()
            if (in_x and self._single_axis_zoom_enabled) or (in_x and in_y):
                skew = self._zoom_skew and self._zoom_skew[0]
                skew = self._interval_skew(
                    event.x,
                    self._axes.bbox.intervalx) if skew is None else skew
                x_min, x_max = self._zoom(x_min, x_max, skew, event.button)
            if (in_y and self._single_axis_zoom_enabled) or (in_x and in_y):
                skew = self._zoom_skew and self._zoom_skew[1]
                skew = self._interval_skew(
                    event.y,
                    self._axes.bbox.intervaly) if skew is None else skew
                y_min, y_max = self._zoom(y_min, y_max, skew, event.button)
            self._xy_extents = (x_min, x_max), (y_min, y_max)
        self._set_axes_limits()
        self.draw()

    def _in_secondary_y_scroll_zone(self, event):
        return self._in_interval(event.y, self._axes.bbox.intervaly) and \
                event.x >= self._axes.bbox.intervalx[1]

    def _in_secondary_x_scroll_zone(self, event):
        return self._in_interval(event.x, self._axes.bbox.intervalx) and \
                event.y >= self._axes.bbox.intervaly[1]

    def _handle_scroll_secondary(self, event):
        if self._has_secondary_y_extent():
            in_secondary_y = self._in_secondary_y_scroll_zone(event)
            if in_secondary_y and event.button in ['up', 'down']:
                self._secondary_y_extent = self._zoom(
                    *self._get_secondary_y_extent(),
                    self._interval_skew(event.y, self._axes.bbox.intervaly),
                    event.button)
        if self._has_secondary_x_extent():
            in_secondary_x = self._in_secondary_x_scroll_zone(event)
            if in_secondary_x and event.button in ['up', 'down']:
                self._secondary_x_extent = self._zoom(
                    *self._get_secondary_x_extent(),
                    self._interval_skew(event.x, self._axes.bbox.intervalx),
                    event.button)

    def _get_zoom_multiplier(self):
        return 20 / 19

    def _zoom(self, min_, max_, skew, direction):
        zoom_multiplier = self._get_zoom_multiplier(
        ) if direction == 'up' else 1 / self._get_zoom_multiplier()
        range_ = max_ - min_
        diff = (range_ * (1 / zoom_multiplier)) - range_
        max_ += diff * (1 - skew)
        min_ -= diff * skew
        return min_, max_

    def _set_axes_limits(self):
        try:
            self._setting_axis_limits = True
            if self._secondary_axes is not None:
                self._set_secondary_axes_limits()
            self._update_ticks()
            (x_min, x_max), (y_min, y_max) = self._get_xy_extents()
            if self._options_view is not None:
                if self._options_view.x_limits:
                    self._options_view.setXLimits(float(x_min), float(x_max))
                if self._options_view.y_limits:
                    self._options_view.setYLimits(float(y_min), float(y_max))
            self._axes.set_xlim(*_safe_limits(x_min, x_max))
            self._axes.set_ylim(*_safe_limits(y_min, y_max))
        finally:
            self._setting_axis_limits = False

    def _set_secondary_axes_limits(self):
        if self._options_view is not None:
            if self._options_view.secondary_y_limits:
                enabled = self._secondary_y_enabled()
                secondary_y_min, secondary_y_max = self._get_secondary_y_extent(
                ) if enabled else (float('nan'), float('nan'))
                self._options_view.setSecondaryYLimitsEnabled(enabled)
                self._options_view.setSecondaryYLimits(float(secondary_y_min),
                                                       float(secondary_y_max))
            if self._options_view.secondary_x_limits:
                enabled = self._secondary_x_enabled()
                secondary_x_min, secondary_x_max = self._get_secondary_x_extent(
                ) if enabled else (float('nan'), float('nan'))
                self._options_view.setSecondaryXLimitsEnabled(enabled)
                self._options_view.setSecondaryXLimits(float(secondary_x_min),
                                                       float(secondary_x_max))
        if self._has_secondary_y_extent():
            self._secondary_axes.set_ylim(*_safe_limits(
                *self._get_secondary_y_extent()))
        if self._has_secondary_x_extent():
            self._secondary_axes.set_xlim(*_safe_limits(
                *self._get_secondary_x_extent()))

    def _secondary_y_enabled(self):
        return True if self._secondary_axes and self._secondary_axes.get_visible(
        ) and self._has_secondary_y_extent() else False

    def _secondary_x_enabled(self):
        return True if self._secondary_axes and self._secondary_axes.get_visible(
        ) and self._has_secondary_x_extent() else False

    def _set_axes_labels(self):
        self._axes.set_xlabel(self.data.xAxisTitle)
        self._axes.set_ylabel(self.data.yAxisTitle)

    def _set_center(self, center):
        if not all(c is not None for c in center):
            center = (0, 0)
        x_extent, y_extent = self._get_xy_extents()
        span = x_extent[1] - x_extent[0], y_extent[1] - y_extent[0]
        x_extent = center[0] - span[0] / 2, center[0] + span[0] / 2
        y_extent = center[1] - span[1] / 2, center[1] + span[1] / 2
        self._xy_extents = x_extent, y_extent

    def _get_xy_extents(self):
        if self.data is None:
            return (0, 0), (0, 0)
        if self._xy_extents is None:
            return self._get_data_xy_extents()
        return self._xy_extents

    def _get_data_xy_extents(self):
        if self.data is None:
            return (0, 0), (0, 0)
        (x_min, x_max), (y_min, y_max) = self.data.get_xy_extents()
        return self._pad_extent(x_min, x_max,
                                self.x_extent_padding), self._pad_extent(
                                    y_min, y_max, self.y_extent_padding)

    def _has_secondary_y_extent(self):
        return hasattr(self.data, 'get_secondary_y_extent')

    def _get_secondary_y_extent(self):
        if self._secondary_y_extent is not None:
            return self._secondary_y_extent
        if self.data is not None:
            return self._get_data_secondary_y_extent()
        return (0, 0)

    def _get_data_secondary_y_extent(self):
        if self.data is None:
            return (0, 0)
        return self._pad_extent(*self.data.get_secondary_y_extent(),
                                self.y_extent_padding)

    def _has_secondary_x_extent(self):
        return hasattr(self.data, 'get_secondary_x_extent')

    def _get_secondary_x_extent(self):
        if self._secondary_x_extent is not None:
            return self._secondary_x_extent
        if self.data is not None:
            return self._get_data_secondary_x_extent()
        return (0, 0)

    def _get_data_secondary_x_extent(self):
        if self.data is None or not hasattr(self.data,
                                            'get_secondary_x_extent'):
            return (0, 0)
        return self._pad_extent(*self.data.get_secondary_x_extent(),
                                self.x_extent_padding)

    def _get_actual_xy_extents(self):
        return self._axes.get_xlim(), self._axes.get_ylim()

    def _pad_extent(self, min_, max_, padding):
        min_, max_ = self._zero_if_nan(min_), self._zero_if_nan(max_)
        range_ = max_ - min_
        return min_ - padding * range_, max_ + padding * range_

    def _zoom_selected(self, start_pos, end_pos):
        x_min, x_max = min(start_pos.xdata,
                           end_pos.xdata), max(start_pos.xdata, end_pos.xdata)
        y_min, y_max = min(start_pos.ydata,
                           end_pos.ydata), max(start_pos.ydata, end_pos.ydata)
        self._xy_extents = (x_min, x_max), (y_min, y_max)
        self._set_axes_limits()
        self.draw()

    def _handle_span_select(self, x_min, x_max):
        x_min, x_max = self._round_to_bin_width(x_min, x_max)
        self._update_span_rect(x_min, x_max)
        self.span = SpanModel(self, x_min, x_max)
        self.draw()

    def _handle_span_select_none(self):
        self.span = None

    def _handle_press(self, event):
        if event.button == 1:
            if self._is_panning:
                self._pan_event = event
            elif self._span.active:
                self._handle_span_press(event)

    def _handle_move(self, event):
        if event.xdata and self._pan_event:
            self._handle_pan_move(event)
        elif event.xdata and any(self._span_events()):
            self._handle_span_move(event)

    def _handle_release(self, event):
        if self._pan_event:
            self._pan_event = None
        elif any(self._span_events()):
            self._handle_span_release(event)

    def _handle_pan_move(self, event):
        from_x, from_y = self._axes.transData.inverted().transform(
            (self._pan_event.x, self._pan_event.y))
        to_x, to_y = self._axes.transData.inverted().transform(
            (event.x, event.y))
        self._pan(from_x - to_x, from_y - to_y)
        self._pan_event = event

    def _pan(self, delta_x, delta_y):
        (x_min, x_max), (y_min, y_max) = self._get_xy_extents()
        self._xy_extents = (x_min + delta_x,
                            x_max + delta_x), (y_min + delta_y,
                                               y_max + delta_y)
        self._set_axes_limits()
        self.draw()

    def _span_events(self):
        return self._span_center_mouse_event, self._span_left_mouse_event, self._span_right_mouse_event

    def _handle_span_press(self, event):
        if not event.xdata:
            return
        span_min, span_max = (self.span.left,
                              self.span.right) if self.span else (0, 0)
        edge_tolerance = self._span_tolerance()
        if abs(span_min - event.xdata) < edge_tolerance:
            self._span.active = False
            self._span_left_mouse_event = event
        elif abs(span_max - event.xdata) < edge_tolerance:
            self._span.active = False
            self._span_right_mouse_event = event
        elif span_min < event.xdata < span_max:
            self._span.active = False
            self._span_center_mouse_event = event

    def _handle_span_move(self, event):
        if not self.span:
            return
        x_min, x_max = self.span.left, self.span.right
        last_event = next(x for x in self._span_events() if x)
        diff_x = event.xdata - last_event.xdata
        if self._span_center_mouse_event is not None:
            self._update_span_rect(x_min + diff_x)
        elif self._span_left_mouse_event is not None:
            self._update_span_rect(x_min + diff_x, x_max)
        elif self._span_right_mouse_event is not None:
            self._update_span_rect(x_min, x_max + diff_x)
        self.draw([self._span.rect])

    def _handle_span_release(self, _event):
        x_min = self._span.rect.get_x()
        x_max = x_min + self._span.rect.get_width()
        x_min, x_max = self._round_to_bin_width(x_min, x_max)
        self._update_span_rect(x_min, x_max)
        self.span = SpanModel(self, x_min, x_max)
        self.draw()
        self._span.active = True
        self._span_center_mouse_event = self._span_left_mouse_event = self._span_right_mouse_event = None

    def _update_span_rect(self, x_min, x_max=None):
        self._span.rect.set_x(x_min)
        self._span.stay_rect.set_x(x_min)
        if x_max:
            self._span.rect.set_width(x_max - x_min)
            self._span.stay_rect.set_width(x_max - x_min)

    def _round_to_bin_width(self, x_min, x_max):
        return x_min, x_max

    def _span_tolerance(self):
        return 5

    def toolEnabled(self, _tool_type):
        return False

    def toolAvailable(self, _tool_type):
        return False

    def activateTool(self, tool_type, active):
        if tool_type == ToolType.zoom:
            self._zoom_selector.set_active(active)
        elif tool_type == ToolType.span:
            if self._span.active and not active:
                self._previous_span = self.span
                self.span = None
                for r in [self._span.rect, self._span.stay_rect]:
                    self._remove_artist(r)
            elif not self._span.active and active:
                self.span = self._previous_span
                for r in [self._span.rect, self._span.stay_rect]:
                    self._add_artist(r)
            self._span.active = active
            self.draw()
        elif tool_type == ToolType.pan:
            self._is_panning = active
        self._active_tools[tool_type] = active

    def toolActive(self, tool_type):
        return self._active_tools.get(tool_type, False)

    def isActiveDefault(self, _tool_type):
        return False

    def _add_artist(self, artist):
        self._axes.add_artist(artist)
        self._decoration_artists.append(artist)

    def _remove_artist(self, artist):
        artist.remove()
        if artist in self._decoration_artists:
            self._decoration_artists.remove(artist)

    def _handle_resize(self, _event):
        self._update_ticks()
        return self.draw()

    def draw(self, artists=None):
        if artists is None:

            def _update():
                for a in self._decoration_artists:
                    a.remove()
                self._canvas.draw()
                self._background_cache = self._canvas.copy_from_bbox(
                    self._figure.bbox)
                for a in self._decoration_artists:
                    self._axes.add_artist(a)
                    self._axes.draw_artist(a)
                self._canvas.update()

            self._pending_draw = _update
        else:

            def _update():
                if self._background_cache is None:
                    raise RuntimeError('Must run draw before drawing artists!')
                self._canvas.restore_region(self._background_cache)
                for a in artists:
                    self._axes.draw_artist(a)
                self._canvas.update()

            self._pending_artists_draw = _update

    def _do_draw_events(self):
        if self._pending_draw is not None:
            self._pending_draw()
            self._pending_draw = None
        if self._pending_artists_draw is not None:
            self._pending_artists_draw()
            self._pending_artists_draw = None
        if self._other_draw_events:
            for draw_event in self._other_draw_events:
                draw_event()
            self._other_draw_events = []

    def addDrawEvent(self, draw_event):
        self._other_draw_events.append(draw_event)

    def resetZoom(self):
        self._secondary_y_extent = self._secondary_x_extent = None
        self._xy_extents = None
        self._set_axes_limits()
        self.draw()

    def _twinx(self, ylabel):
        axes = self._axes.twinx()
        for spine in ['top', 'left']:
            axes.spines[spine].set_visible(False)
        axes.set_ylabel(ylabel)
        axes.set_zorder(1)
        return axes

    @property
    def axes(self):
        return self._axes

    @property
    def secondary_axes(self):
        if self._secondary_axes is None:
            self._set_secondary_axes(self._twinx(''))
        return self._secondary_axes

    def _set_secondary_axes(self, axes):
        self._secondary_axes = axes

    @staticmethod
    def sizeHint():
        """function::sizeHint()
        Override the default sizeHint to ensure the plot has an initial size
        """
        return QSize(600, 400)

    def minimumSizeHint(self):
        """function::sizeHint()
        Override the default sizeHint to ensure the plot does not shrink below minimum size
        """
        return self.sizeHint()

    @staticmethod
    def _zero_if_nan(value):
        return value if not isinstance(value,
                                       float) or not np.isnan(value) else 0

    def canShowTable(self):
        return hasattr(self, 'data') and self.data is not None and hasattr(
            self.data, 'table')

    def contextMenuEvent(self, event):
        self._show_table_action.setEnabled(self.canShowTable())
        self._menu.exec_(event.globalPos())

    def copyToClipboard(self):
        with BytesIO() as buffer:
            self._figure.savefig(buffer,
                                 facecolor=self._figure.get_facecolor())
            QApplication.clipboard().setImage(
                QImage.fromData(buffer.getvalue()))

    def saveAsImage(self):
        filename = self._file_dialog_service.get_save_filename(
            self, self.tr('Portable Network Graphics (*.png)'))
        if filename:
            self._figure.savefig(filename,
                                 facecolor=self._figure.get_facecolor())

    def showTable(self):
        if self.canShowTable():
            self._table_view = TableView(None)
            self._table_view.pasteEnabled = False
            self._table_view.setModel(self.data.table)
            self._table_view.setMinimumSize(800, 600)
            self._table_view.show()

    def _update_ticks(self):
        if not self.data:
            return
        if hasattr(self.data, 'x_labels'):
            step = self.data.x_tick_interval if hasattr(
                self.data, 'x_tick_interval') else None
            x_ticks, x_labels = self._get_labels(self.data.x_labels,
                                                 step,
                                                 horizontal=True)
            self._axes.set_xticks(x_ticks)
            self._axes.set_xticklabels(x_labels)
        if hasattr(self.data, 'y_labels'):
            step = self.data.y_tick_interval if hasattr(
                self.data, 'y_tick_interval') else None
            y_ticks, y_labels = self._get_labels(self.data.y_labels,
                                                 step,
                                                 horizontal=False)
            self._axes.set_yticks(y_ticks)
            self._axes.set_yticklabels(y_labels)

    def _get_labels(self, labels, step, horizontal=True):
        (x0, x1), (y0, y1) = self._get_xy_extents()
        start, end = (int(x0), int(x1)) if horizontal else (int(y0), int(y1))
        visible_points = end - start
        if not (step and step > 0):
            width, height = self._get_label_width_height(labels)
            axes_bbox = self._axes.get_window_extent(
                self._figure.canvas.get_renderer()).transformed(
                    self._figure.dpi_scale_trans.inverted())
            plot_size = (axes_bbox.width if horizontal else
                         axes_bbox.height) * self._figure.dpi
            size = (width if horizontal else height)
            if plot_size == 0 or size == 0:
                n_labels = 16
            else:
                n_labels = int(plot_size / size)
                if n_labels == 0:
                    n_labels = 16
            step = int(visible_points / n_labels) + 1
        else:
            step = int(step)
        indexes = list(range(len(labels)))
        display_labels = list(labels)
        for i in indexes:
            if i % step:
                display_labels[i] = ''
        return indexes, display_labels

    def _get_label_width_height(self, labels):
        if not self._cached_label_width_height:
            font = MatPlotLibFont.default()
            width = 0
            height = 0
            for label in labels:
                next_width, next_height = font.get_size(
                    str(label), matplotlib.rcParams['font.size'],
                    self._figure.dpi)
                width = max(width, next_width)
                height = max(height, next_height)
            self._cached_label_width_height = width, height
        return self._cached_label_width_height

    def _create_new_axes(self, nx=1, ny=1) -> LocatableAxes:
        axes = LocatableAxes(self._figure, self._divider.get_position())
        axes.set_axes_locator(self._divider.new_locator(nx=nx, ny=ny))
        self._figure.add_axes(axes)
        return axes

    @staticmethod
    def _create_secondary_xy_axes(figure,
                                  divider,
                                  nx=1,
                                  ny=1,
                                  visible=False,
                                  z_order=1):
        axes = LocatableAxes(figure, divider.get_position())
        axes.set_axes_locator(divider.new_locator(nx=nx, ny=ny))
        axes.xaxis.tick_top()
        axes.xaxis.set_label_position('top')
        axes.yaxis.tick_right()
        axes.yaxis.set_label_position('right')
        axes.patch.set_visible(visible)
        axes.set_zorder(z_order)
        figure.add_axes(axes)
        axes.ticklabel_format(style='sci', axis='x', scilimits=(-4, 4))
        axes.ticklabel_format(style='sci', axis='y', scilimits=(-4, 4))
        return axes

    @staticmethod
    def _create_shared_axes(figure,
                            divider,
                            shared_axes,
                            nx=1,
                            ny=1,
                            visible=False,
                            z_order=1):
        axes = LocatableAxes(figure,
                             divider.get_position(),
                             sharex=shared_axes,
                             sharey=shared_axes,
                             frameon=False)
        axes.set_axes_locator(divider.new_locator(nx=nx, ny=ny))
        for spine in axes.spines.values():
            spine.set_visible(False)
        for axis in axes.axis.values():
            axis.set_visible(False)
        axes.patch.set_visible(False)
        axes.set_visible(False)
        axes.set_zorder(z_order)
        figure.add_axes(axes)
        return axes
class twoDimage(QWidget):
    def __init__(self, viewer):
        super(twoDimage, self).__init__()
        self.data = Spectrum()
        self.viewer = viewer
        self.aspect = "auto"

        #init Image
        self.fig = Figure(figsize=(5, 3), dpi=150)
        gs = GridSpec(3, 3, figure=self.fig)
        self.ax_XDC = self.fig.add_subplot(gs[0, 0:2])  # X Distribution Curve
        self.ax_Spec = self.fig.add_subplot(gs[1:, 0:2])  # Spectrum
        self.ax_YDC = self.fig.add_subplot(gs[1:, -1])  # Y Distribution Curve
        self.lx1 = self.ax_Spec.axhline(self.ax_Spec.get_ybound()[0],
                                        visible=False,
                                        animated=True,
                                        color='k',
                                        linestyle='--',
                                        linewidth=0.75)  # the horiz line 1
        self.ly1 = self.ax_Spec.axvline(self.ax_Spec.get_xbound()[0],
                                        visible=False,
                                        animated=True,
                                        color='k',
                                        linestyle='--',
                                        linewidth=0.75)  # the vert line 1
        self.lx2 = self.ax_Spec.axhline(self.ax_Spec.get_ybound()[0],
                                        visible=False,
                                        animated=True,
                                        color='k',
                                        linestyle='--',
                                        linewidth=0.75)  # the horiz line 2
        self.ly2 = self.ax_Spec.axvline(self.ax_Spec.get_xbound()[0],
                                        visible=False,
                                        animated=True,
                                        color='k',
                                        linestyle='--',
                                        linewidth=0.75)  # the vert line 2
        self.vl = self.ax_XDC.axvline(self.ax_XDC.get_xbound()[0],
                                      visible=False,
                                      animated=True,
                                      color='k',
                                      linestyle='--',
                                      linewidth=0.75)
        self.hl = self.ax_YDC.axhline(self.ax_YDC.get_ybound()[0],
                                      visible=False,
                                      animated=True,
                                      color='k',
                                      linestyle='--',
                                      linewidth=0.75)  # the horiz line
        self.ax_cmap = self.fig.add_subplot(gs[0, 2])  # color bar
        self.gradient = np.vstack((np.linspace(0, 1,
                                               256), np.linspace(0, 1, 256)))
        self.setTickLabelFont(self.ax_Spec, "Comic Sans MS")
        self.setTickLabelFont(self.ax_XDC, "Comic Sans MS")
        self.setTickLabelFont(self.ax_YDC, "Comic Sans MS")
        self.ax_Spec.set_axisbelow(False)
        self.ax_Spec.tick_params(direction='inout', labelsize=8.5)
        self.ax_XDC.tick_params(axis='x', labelsize=8.5, labelbottom=False)
        self.ax_XDC.tick_params(axis='y',
                                which='both',
                                left=False,
                                labelleft=False)
        self.ax_YDC.tick_params(axis='x',
                                which='both',
                                bottom=False,
                                labelbottom=False)
        self.ax_YDC.tick_params(axis='y', labelsize=8.5, labelleft=False)
        self.fig.subplots_adjust(top=0.96,
                                 bottom=0.08,
                                 left=0.1,
                                 right=0.96,
                                 wspace=0.15,
                                 hspace=0.15)
        x0, y0, width, height = self.ax_cmap.get_position().bounds
        nheight = height * 0.2
        ny0 = y0 + height * 0.5 * (1 - 0.2)
        self.ax_cmap.set_position([x0, ny0, width, nheight])
        self.ax_cmap.tick_params(left=False,
                                 labelleft=False,
                                 bottom=False,
                                 labelbottom=False)
        self.ax_cmap.set_title("Color Scale", {'fontsize': 10},
                               fontname="Comic Sans Ms")
        self.fig.patch.set_facecolor("None")
        self.canvas = FigureCanvas(self.fig)
        self.canvas.setStyleSheet("background-color:transparent;")
        self.canvas.setFocusPolicy(Qt.ClickFocus)
        self.XDCLine = Line2D(np.array([0]),
                              np.array([0]),
                              color="blue",
                              linewidth=1,
                              animated=True,
                              solid_joinstyle="round")
        self.YDCLine = Line2D(np.array([0]),
                              np.array([0]),
                              color="blue",
                              linewidth=1,
                              animated=True,
                              solid_joinstyle="round")
        self.ax_XDC.add_line(self.XDCLine)
        self.ax_YDC.add_line(self.YDCLine)
        self.selector = Selector(self.ax_Spec, self.canvas)
        self.cid_press = self.canvas.mpl_connect('button_press_event',
                                                 self.OnPress)
        self.cid_release = self.canvas.mpl_connect('button_release_event',
                                                   self.OnRelease)
        self.cid_keypress = self.canvas.mpl_connect('key_press_event',
                                                    self.OnKeyPress)
        #self.cid_scroll = self.canvas.mpl_connect('scroll_event', self.OnExpand)

        #contextMenu
        self.contextMenu = QMenu(self)
        self.CropAction = self.contextMenu.addAction("Crop")
        self.CropAction.setIcon(QIcon("./image/crop.ico"))
        self.CropAction.triggered.connect(self.crop)
        self.RestoreAction = self.contextMenu.addAction("Restore")
        self.RestoreAction.setIcon(QIcon("./image/restore.ico"))
        self.RestoreAction.triggered.connect(
            self.viewer.Win.DataProcessor.tab_single.RestoreData)
        self.SelectMenu = self.contextMenu.addMenu("Select")
        self.SelectMenu.setIcon(QIcon("./image/select.ico"))
        self.SelectX = self.SelectMenu.addAction("X Range")
        self.SelectX.triggered.connect(self.setSelectionRange)
        self.SelectY = self.SelectMenu.addAction("Y Range")
        self.SelectY.triggered.connect(self.setSelectionRange)
        self.SelectXY = self.SelectMenu.addAction("XY Area")
        self.SelectXY.triggered.connect(self.setSelectionRange)
        self.outputMenu = self.contextMenu.addMenu("Output")
        self.outputMenu.setIcon(QIcon("./image/output.ico"))
        self.XDC_action = self.outputMenu.addAction("XDC")
        self.YDC_action = self.outputMenu.addAction("YDC")

        #init Tool
        self.toolPanel = QGroupBox("Coordinate System")
        self.toolPanel.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
        self.toolPanel.setCheckable(True)
        self.toolPanel.setChecked(False)
        self.toolPanel.setStyle(QStyleFactory.create('Fusion'))
        self.toolPanel.toggled.connect(self.begincursor)
        self.X = QDoubleSpinBox()
        self.X.setFixedWidth(100)
        self.X.setDecimals(6)
        self.X.setRange(-1, 1)
        self.X.setSingleStep(0.01)
        self.X.setKeyboardTracking(False)
        self.X.setStyle(QStyleFactory.create('Fusion'))
        self.X.setValue(-1)
        self.X.valueChanged.connect(self.setValueFromSpinBox)
        self.XLabel = QLabel("X:")
        self.XLabel.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
        self.Y = QDoubleSpinBox()
        self.Y.setFixedWidth(100)
        self.Y.setDecimals(6)
        self.Y.setRange(-1, 1)
        self.Y.setSingleStep(0.01)
        self.Y.setKeyboardTracking(False)
        self.Y.setStyle(QStyleFactory.create('Fusion'))
        self.Y.setValue(-1)
        self.Y.valueChanged.connect(self.setValueFromSpinBox)
        self.YLabel = QLabel("Y:")
        self.YLabel.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
        font = QFont()
        font.setPointSize(12)
        font.setBold(True)
        self.IntLabel = QLabel("Count:")
        self.IntLabel.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
        self.Int = QLabel("0")
        self.Int.setFont(font)
        self.Int.setMinimumWidth(100)
        self.Int.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
        self.Int.setStyleSheet("QLabel {background-color: white;}")
        self.Int.setFrameStyle(QFrame.Panel)
        self.XSlider = QSlider(Qt.Horizontal)
        self.XSlider.setRange(0, 10000)
        self.XSlider.setStyle(QStyleFactory.create('Fusion'))
        self.XSlider.sliderMoved.connect(self.setValueFromSlider)
        self.XSLabel = QLabel("X:")
        self.YSlider = QSlider(Qt.Horizontal)
        self.YSlider.setRange(0, 10000)
        self.YSlider.setStyle(QStyleFactory.create('Fusion'))
        self.YSlider.sliderMoved.connect(self.setValueFromSlider)
        self.YSLabel = QLabel("Y:")
        self.XSlice = QSpinBox()
        self.XSlice.setRange(1, 10000)
        self.XSlice.setSingleStep(2)
        self.XSlice.setKeyboardTracking(False)
        self.XSlice.setStyle(QStyleFactory.create('Fusion'))
        self.XSlice.valueChanged.connect(self.setSlice)
        self.XSliceLabel = QLabel("Slice:")
        self.YSlice = QSpinBox()
        self.YSlice.setRange(1, 10000)
        self.YSlice.setSingleStep(2)
        self.YSlice.setKeyboardTracking(False)
        self.YSlice.setStyle(QStyleFactory.create('Fusion'))
        self.YSlice.valueChanged.connect(self.setSlice)
        self.YSliceLabel = QLabel("Slice:")
        hbox_tool = QHBoxLayout()
        hbox_tool.addStretch(2)
        hbox_tool.addWidget(self.XLabel)
        hbox_tool.addWidget(self.X)
        hbox_tool.addStretch(1)
        hbox_tool.addWidget(self.YLabel)
        hbox_tool.addWidget(self.Y)
        hbox_tool.addStretch(1)
        hbox_tool.addWidget(self.IntLabel)
        hbox_tool.addWidget(self.Int)
        hbox_tool.addStretch(2)
        hbox2_tool = QHBoxLayout()
        hbox2_tool.addWidget(self.XSLabel)
        hbox2_tool.addWidget(self.XSlider)
        hbox2_tool.addWidget(self.XSliceLabel)
        hbox2_tool.addWidget(self.XSlice)
        hbox3_tool = QHBoxLayout()
        hbox3_tool.addWidget(self.YSLabel)
        hbox3_tool.addWidget(self.YSlider)
        hbox3_tool.addWidget(self.YSliceLabel)
        hbox3_tool.addWidget(self.YSlice)
        vbox_tool = QVBoxLayout()
        vbox_tool.addLayout(hbox_tool)
        vbox_tool.addLayout(hbox2_tool)
        vbox_tool.addLayout(hbox3_tool)
        self.toolPanel.setLayout(vbox_tool)

        #color scale
        self.colorPanel = QGroupBox("Color Scale")
        self.colorPanel.setFixedHeight(105)
        self.colorPanel.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
        self.cmapmenu = CustomComboBox()
        self.cmapmenu.setFixedWidth(120)
        self.cmapmenu.setStyle(QStyleFactory.create('Fusion'))
        self.cmapmenu.setCurrentText("twilight")
        self.cmapmenu.currentIndexChanged.connect(self.changecmapbyindex)
        self.Vmin = QDoubleSpinBox()
        self.Vmin.setFixedWidth(100)
        self.Vmin.setRange(-1e15, 1e15)
        self.Vmin.setDecimals(4)
        self.Vmin.setKeyboardTracking(False)
        self.Vmin.valueChanged.connect(self.setDynamicRange)
        self.Vmin.setStyle(QStyleFactory.create('Fusion'))
        self.VminLabel = QLabel("Min:")
        self.VminLabel.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
        self.Vmax = QDoubleSpinBox()
        self.Vmax.setFixedWidth(100)
        self.Vmax.setRange(-1e15, 1e15)
        self.Vmax.setDecimals(4)
        self.Vmax.setKeyboardTracking(False)
        self.Vmax.valueChanged.connect(self.setDynamicRange)
        self.Vmax.setStyle(QStyleFactory.create('Fusion'))
        self.VmaxLabel = QLabel("Max:")
        self.VmaxLabel.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
        self.resetVrange = QPushButton("Re")
        self.resetVrange.setFixedWidth(25)
        self.resetVrange.clicked.connect(self.resetDynamicRange)
        self.resetVrange.setStyle(QStyleFactory.create('Fusion'))
        self.revcmap = QCheckBox("R")
        self.revcmap.stateChanged.connect(self.reversecmap)
        self.GammaLabel = QLabel("Gamma:")
        self.GammaLabel.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
        self.Gamma = QDoubleSpinBox()
        self.Gamma.setFixedWidth(100)
        self.Gamma.setDecimals(3)
        self.Gamma.setRange(0.05, 20)
        self.Gamma.setSingleStep(0.01)
        self.Gamma.setValue(1)
        self.Gamma.setKeyboardTracking(False)
        self.Gamma.valueChanged.connect(self.setGammaFromSpinBox)
        self.Gamma.setStyle(QStyleFactory.create('Fusion'))
        self.GSlider = QSlider(Qt.Horizontal)
        self.GSlider.setRange(1000000 * np.log(0.05), 1000000 * np.log(20))
        self.GSlider.setValue(0)
        self.GSlider.sliderMoved.connect(self.setGammaFromSlider)
        self.GSlider.setStyle(QStyleFactory.create('Fusion'))
        hbox1_cmap = QHBoxLayout()
        hbox1_cmap.addWidget(self.cmapmenu)
        hbox1_cmap.addStretch(1)
        hbox1_cmap.addWidget(self.VminLabel)
        hbox1_cmap.addWidget(self.Vmin)
        hbox1_cmap.addStretch(1)
        hbox1_cmap.addWidget(self.VmaxLabel)
        hbox1_cmap.addWidget(self.Vmax)
        hbox1_cmap.addStretch(1)
        hbox1_cmap.addWidget(self.resetVrange)
        hbox2_cmap = QHBoxLayout()
        hbox2_cmap.addWidget(self.GammaLabel)
        hbox2_cmap.addWidget(self.Gamma)
        hbox2_cmap.addWidget(self.GSlider)
        hbox2_cmap.addWidget(self.revcmap)
        vbox_cmap = QVBoxLayout()
        vbox_cmap.addLayout(hbox1_cmap)
        vbox_cmap.addLayout(hbox2_cmap)
        self.colorPanel.setLayout(vbox_cmap)
        self.colorPanel.setEnabled(False)

        #Layout
        box = QVBoxLayout()
        box.addWidget(self.toolPanel)
        box.addWidget(self.canvas)
        box.addWidget(self.colorPanel)
        self.setLayout(box)

    def loaddata(self, data):
        self.canvas.draw()
        self.selector.set_visible(False)
        self.data = data
        self.GetWhiteBackground()
        self.X.setRange(data.xmin, data.xmax)
        self.X.setSingleStep(data.xstep)
        self.Y.setRange(data.ymin, data.ymax)
        self.Y.setSingleStep(data.ystep)
        newX = 0
        if data.xmin * data.xmax < 0:
            newX = 0
        else:
            newX = (data.xmin + data.xmax) / 2
        if round(newX, 6) == self.X.value():
            self.X.valueChanged.emit(newX)
        else:
            self.X.setValue(newX)
        newY = 0
        if data.ymin * data.ymax < 0:
            newY = 0
        else:
            newY = (data.ymin + data.ymax) / 2
        if round(newY, 6) == self.Y.value():
            self.Y.valueChanged.emit(newY)
        else:
            self.Y.setValue(newY)
        if data.data[~np.isnan(data.data)].max() > 0:
            self.Vmax.setValue(data.data[~np.isnan(data.data)].max())
            self.Vmin.setValue(data.data[~np.isnan(data.data)].min())
        else:
            self.Vmin.setValue(data.data[~np.isnan(data.data)].min())
            self.Vmax.setValue(data.data[~np.isnan(data.data)].max())
        self.XSlice.setRange(1, self.data.dimension[0])
        self.YSlice.setRange(1, self.data.dimension[1])
        self.plotimage(data, self.getcurrentcmap(), self.Vmin.value(),
                       self.Vmax.value(), False)
        self.updatecolorscale(self.getcurrentcmap(), False)
        self.updateAxesPosition(True)
        self.colorPanel.setEnabled(True)

    def setValueFromSlider(self, value):
        if self.sender() == self.XSlider:
            self.X.setValue(value *
                            (self.X.maximum() - self.X.minimum()) / 10000 +
                            self.X.minimum())
        elif self.sender() == self.YSlider:
            self.Y.setValue(value *
                            (self.Y.maximum() - self.Y.minimum()) / 10000 +
                            self.Y.minimum())

    def setValueFromSpinBox(self, value):
        if self.sender() == self.X:
            self.XSlider.setValue(
                (value - self.X.minimum()) /
                (self.X.maximum() - self.X.minimum()) * 10000)
            half_wid_num = (self.XSlice.value() - 1) / 2
            self.ly1.set_xdata(value - self.data.xstep * half_wid_num)
            self.ly2.set_xdata(value + self.data.xstep * half_wid_num)
            self.vl.set_xdata(value)
            self.YDC = self.get_YDC(value)
            self.resetYDC()
            self.plotYDC(True, True)
            self.plotXDC(True, True)
        elif self.sender() == self.Y:
            self.YSlider.setValue(
                (value - self.Y.minimum()) /
                (self.Y.maximum() - self.Y.minimum()) * 10000)
            half_wid_num = (self.YSlice.value() - 1) / 2
            self.lx1.set_ydata(value - self.data.ystep * half_wid_num)
            self.lx2.set_ydata(value + self.data.ystep * half_wid_num)
            self.hl.set_ydata(value)
            self.XDC = self.get_XDC(value)
            self.resetXDC()
            self.plotXDC(True, True)
            self.plotYDC(True, True)
        self.plotArtist()
        self.setIntensity()

    def setIntensity(self):
        intensity = self.get_intensity(self.X.value(), self.Y.value())
        if np.isnan(intensity):
            self.Int.setText("NaN")
        elif np.abs(intensity) >= 0.01:
            self.Int.setText("%.2f" % intensity)
        else:
            self.Int.setText("%.2e" % intensity)

    def setSlice(self, value):
        if self.sender() == self.XSlice:
            if value % 2 == 0:
                self.XSlice.setValue(value - 1)
            else:
                if value == 1:
                    self.ly2.set_visible(False)
                else:
                    self.ly2.set_visible(True)
                half_wid_num = (self.XSlice.value() - 1) / 2
                self.ly1.set_xdata(self.X.value() -
                                   self.data.xstep * half_wid_num)
                self.ly2.set_xdata(self.X.value() +
                                   self.data.xstep * half_wid_num)
                self.plotArtist()
                self.YDC = self.get_YDC(self.X.value())
                self.resetYDC()
                self.plotYDC(True, True)
        if self.sender() == self.YSlice:
            if value % 2 == 0:
                self.YSlice.setValue(value - 1)
            else:
                if value == 1:
                    self.lx2.set_visible(False)
                else:
                    self.lx2.set_visible(True)
                half_wid_num = (self.YSlice.value() - 1) / 2
                self.lx1.set_ydata(self.Y.value() -
                                   self.data.ystep * half_wid_num)
                self.lx2.set_ydata(self.Y.value() +
                                   self.data.ystep * half_wid_num)
                self.plotArtist()
                self.XDC = self.get_XDC(self.Y.value())
                self.resetXDC()
                self.plotXDC(True, True)
        self.setIntensity()

    def setDynamicRange(self, value):
        if self.sender() == self.Vmin:
            if value <= self.Vmax.value():
                self.plotimage(self.data, self.getcurrentcmap(), value,
                               self.Vmax.value(), True)
            else:
                self.plotimage(self.data, self.getcurrentcmap(),
                               self.Vmax.value(), self.Vmax.value(), True)
        elif self.sender() == self.Vmax:
            if value >= self.Vmin.value():
                self.plotimage(self.data, self.getcurrentcmap(),
                               self.Vmin.value(), value, True)
            else:
                self.plotimage(self.data, self.getcurrentcmap(),
                               self.Vmin.value(), self.Vmin.value(), True)

    def resetDynamicRange(self):
        self.Vmin.setValue(self.data.data[~np.isnan(self.data.data)].min())
        self.Vmax.setValue(self.data.data[~np.isnan(self.data.data)].max())

    def setTickLabelFont(self, ax, font):
        for tick in ax.get_xticklabels():
            tick.set_fontname(font)
        for tick in ax.get_yticklabels():
            tick.set_fontname(font)

    def scale2pnt(self, value, scale):
        return (np.abs(scale - value)).argmin()

    def get_intensity(self, xvalue, yvalue):
        xidx = self.scale2pnt(xvalue, self.data.xscale)
        yidx = self.scale2pnt(yvalue, self.data.yscale)
        half_wid_xnum = int((self.XSlice.value() - 1) / 2)
        half_wid_ynum = int((self.YSlice.value() - 1) / 2)
        if xidx < half_wid_xnum:
            xminidx = 0
        else:
            xminidx = xidx - half_wid_xnum
        xmaxidx = xidx + half_wid_xnum + 1
        if yidx < half_wid_ynum:
            yminidx = 0
        else:
            yminidx = yidx - half_wid_ynum
        ymaxidx = yidx + half_wid_ynum + 1
        return self.data.data[xminidx:xmaxidx, yminidx:ymaxidx].sum()

    def get_XDC(self, yvalue):
        yidx = self.scale2pnt(yvalue, self.data.yscale)
        half_wid_num = int((self.YSlice.value() - 1) / 2)
        if yidx < half_wid_num:
            yminidx = 0
        else:
            yminidx = yidx - half_wid_num
        ymaxidx = yidx + half_wid_num + 1
        array = self.data.data[:, yminidx:ymaxidx]
        return np.sum(array, axis=1)

    def get_YDC(self, xvalue):
        xidx = self.scale2pnt(xvalue, self.data.xscale)
        half_wid_num = int((self.XSlice.value() - 1) / 2)
        if xidx < half_wid_num:
            xminidx = 0
        else:
            xminidx = xidx - half_wid_num
        xmaxidx = xidx + half_wid_num + 1
        array = self.data.data[xminidx:xmaxidx, :]
        return np.sum(array, axis=0)

    def getcurrentcmap(self):
        cmap_str = self.cmapmenu.currentText()
        if self.revcmap.checkState() == Qt.Checked:
            cmap_str += "_r"
        raw_cmap = cm.get_cmap(cmap_str, 256)
        linearIndex = np.linspace(0, 1, 256)
        nonlinearIndex = np.power(linearIndex, self.Gamma.value())
        new_cmap = ListedColormap(raw_cmap(nonlinearIndex))
        return new_cmap

    def setGammaFromSpinBox(self, value):
        self.updatecolorscale(self.getcurrentcmap(), True)
        self.plotimage(self.data, self.getcurrentcmap(), self.Vmin.value(),
                       self.Vmax.value(), True)
        self.GSlider.setValue(1000000 * np.log(value))

    def setGammaFromSlider(self, value):
        self.Gamma.setValue(np.exp(value / 1000000))

    def reversecmap(self, state):
        self.updatecolorscale(self.getcurrentcmap(), True)
        self.plotimage(self.data, self.getcurrentcmap(), self.Vmin.value(),
                       self.Vmax.value(), True)

    def changecmapbyindex(self, index):
        self.updatecolorscale(self.getcurrentcmap(), True)
        self.plotimage(self.data, self.getcurrentcmap(), self.Vmin.value(),
                       self.Vmax.value(), True)

    def updatecolorscale(self, cmap, removeflag):
        if removeflag:
            for img in self.ax_cmap.get_images():
                img.remove()
        self.cmapimage = self.ax_cmap.imshow(self.gradient,
                                             cmap=cmap,
                                             origin="lower",
                                             aspect="auto")
        self.ax_cmap.redraw_in_frame()
        self.canvas.blit(self.ax_cmap.bbox)

    def begincursor(self, on):
        if on:
            self.lx1.set_visible(True)
            self.ly1.set_visible(True)
            if self.YSlice.value() > 1:
                self.lx2.set_visible(True)
            if self.XSlice.value() > 1:
                self.ly2.set_visible(True)
            self.vl.set_visible(True)
            self.hl.set_visible(True)
            self.plotArtist()
            self.plotXDC(True, True)
            self.plotYDC(True, True)
            self.cid_mouse = self.canvas.mpl_connect('motion_notify_event',
                                                     self.navigate)
            self.cid_key = self.canvas.mpl_connect('key_press_event',
                                                   self.navigate)
        else:
            self.lx1.set_visible(False)
            self.ly1.set_visible(False)
            self.lx2.set_visible(False)
            self.ly2.set_visible(False)
            self.vl.set_visible(False)
            self.hl.set_visible(False)
            self.plotXDC(True, False)
            self.plotYDC(True, False)
            self.plotArtist()
            self.canvas.mpl_disconnect(self.cid_mouse)
            self.canvas.mpl_disconnect(self.cid_key)

    def navigate(self, event):
        if event.inaxes == self.ax_Spec and event.key == "control":
            self.X.setValue(event.xdata)
            self.Y.setValue(event.ydata)

    def OnPress(self, event):
        if event.inaxes == self.ax_Spec and event.button == 1:
            if not self.selector.visible:  #draw selector from nothing
                self.selector.moving_state = "draw"
                self.selector.origin = (event.xdata, event.ydata)
                self.cid_drawRS = self.canvas.mpl_connect(
                    'motion_notify_event', self.OnDrawRS)
            else:
                dist = self.selector.nearestCorner(event.x, event.y)
                if dist < 10:
                    self.selector.moving_state = "moveHandle"
                    self.selector.origin = (event.xdata, event.ydata)
                    self.cid_moveHandle = self.canvas.mpl_connect(
                        'motion_notify_event', self.OnMoveHandle)
                else:
                    if self.selector.isinRegion(
                            event.xdata, event.ydata):  #move the selector
                        self.selector.moving_state = "move"
                        self.selector.origin = (event.xdata, event.ydata)
                        self.cid_moveRS = self.canvas.mpl_connect(
                            'motion_notify_event', self.OnMoveRS)
                    else:  #clear the selector
                        self.selector.set_visible(False)
                        self.plotArtist()

    def OnDrawRS(self, event):
        if event.inaxes == self.ax_Spec and event.button == 1:
            if event.xdata != self.selector.origin[
                    0] and event.ydata != self.selector.origin[1]:
                self.selector.set_visible(True)
                self.selector.resize(self.selector.origin[0],
                                     self.selector.origin[1], event.xdata,
                                     event.ydata)
                self.plotArtist()

    def OnMoveRS(self, event):
        if event.inaxes == self.ax_Spec and event.button == 1:
            xmin, ymin, xmax, ymax = self.selector.region
            self.selector.resize(xmin + event.xdata - self.selector.origin[0],
                                 ymin + event.ydata - self.selector.origin[1],
                                 xmax + event.xdata - self.selector.origin[0],
                                 ymax + event.ydata - self.selector.origin[1])
            self.plotArtist()
            self.selector.origin = (event.xdata, event.ydata)

    def OnMoveHandle(self, event):
        if event.inaxes == self.ax_Spec and event.button == 1:
            xmin, ymin, xmax, ymax = self.selector.region
            if self.selector.active_handle == 0:
                self.selector.resize(
                    xmin + event.xdata - self.selector.origin[0],
                    ymin + event.ydata - self.selector.origin[1], xmax, ymax)
            elif self.selector.active_handle == 1:
                self.selector.resize(
                    xmin, ymin + event.ydata - self.selector.origin[1], xmax,
                    ymax)
            elif self.selector.active_handle == 2:
                self.selector.resize(
                    xmin, ymin + event.ydata - self.selector.origin[1],
                    xmax + event.xdata - self.selector.origin[0], ymax)
            elif self.selector.active_handle == 3:
                self.selector.resize(
                    xmin, ymin, xmax + event.xdata - self.selector.origin[0],
                    ymax)
            elif self.selector.active_handle == 4:
                self.selector.resize(
                    xmin, ymin, xmax + event.xdata - self.selector.origin[0],
                    ymax + event.ydata - self.selector.origin[1])
            elif self.selector.active_handle == 5:
                self.selector.resize(
                    xmin, ymin, xmax,
                    ymax + event.ydata - self.selector.origin[1])
            elif self.selector.active_handle == 6:
                self.selector.resize(
                    xmin + event.xdata - self.selector.origin[0], ymin, xmax,
                    ymax + event.ydata - self.selector.origin[1])
            elif self.selector.active_handle == 7:
                self.selector.resize(
                    xmin + event.xdata - self.selector.origin[0], ymin, xmax,
                    ymax)
            self.plotArtist()
            self.selector.origin = (event.xdata, event.ydata)

    def OnRelease(self, event):
        if event.inaxes == self.ax_Spec:
            if event.button == 1:
                if self.selector.moving_state == "draw":
                    self.canvas.mpl_disconnect(self.cid_drawRS)
                elif self.selector.moving_state == "move":
                    self.canvas.mpl_disconnect(self.cid_moveRS)
                elif self.selector.moving_state == "moveHandle":
                    self.canvas.mpl_disconnect(self.cid_moveHandle)
            if event.button == 3:
                if self.selector.visible and self.selector.isinRegion(
                        event.xdata, event.ydata):
                    self.CropAction.setEnabled(True)
                    self.SelectMenu.setEnabled(True)
                else:
                    self.CropAction.setEnabled(False)
                    self.SelectMenu.setEnabled(False)
                self.showContextMenu()
        else:
            if event.button == 3:
                self.CropAction.setEnabled(False)
                self.SelectMenu.setEnabled(False)
                self.showContextMenu()

    def showContextMenu(self):
        self.contextMenu.move(QCursor.pos())
        self.contextMenu.show()

    def crop(self):
        x0, y0, x1, y1 = self.selector.region
        self.viewer.Win.DataProcessor.tab_single.setSelectXYRange(
            x0, x1, y0, y1, True)
        self.selector.set_visible(False)
        self.plotArtist()
        self.viewer.Win.DataProcessor.tab_single.crop2D.clicked.emit()

    def setSelectionRange(self):
        x0, y0, x1, y1 = self.selector.region
        if self.sender() == self.SelectX:
            self.viewer.Win.DataProcessor.tab_single.setSelectXRange(x0, x1)
        elif self.sender() == self.SelectY:
            self.viewer.Win.DataProcessor.tab_single.setSelectYRange(y0, y1)
        elif self.sender() == self.SelectXY:
            self.viewer.Win.DataProcessor.tab_single.setSelectXYRange(
                x0, x1, y0, y1, True)
        self.selector.set_visible(False)
        self.plotArtist()

    def outputData(self):
        if self.sender() == self.XDC_action:
            pass
        elif self.sender() == self.YDC_action:
            pass

    def OnKeyPress(self, event):
        if event.key == 'ctrl+a':
            if self.toolPanel.isChecked():
                self.toolPanel.setChecked(False)
            else:
                self.toolPanel.setChecked(True)

    def GetWhiteBackground(self):
        self.ax_XDC.redraw_in_frame()
        self.canvas.blit(self.ax_XDC.get_window_extent())
        self.ax_YDC.redraw_in_frame()
        self.canvas.blit(self.ax_YDC.get_window_extent())
        self.background_XDC = self.canvas.copy_from_bbox(
            self.ax_XDC.get_window_extent())
        self.background_YDC = self.canvas.copy_from_bbox(
            self.ax_YDC.get_window_extent())
        self.background_Spec = self.canvas.copy_from_bbox(
            self.ax_Spec.get_window_extent())

    def plotArtist(self):
        self.canvas.restore_region(self.background_Spec)
        for line in self.ax_Spec.get_lines():
            self.ax_Spec.draw_artist(line)
        for artist in self.selector.artist:
            self.ax_Spec.draw_artist(artist)
        self.canvas.blit(self.ax_Spec.get_window_extent())

    def plotimage(self, data, cmap, vmin, vmax, useblit):
        for img in self.ax_Spec.get_images():
            img.remove()
        self.twoDspec = self.ax_Spec.imshow(
            data.data.T,
            cmap=cmap,
            vmin=vmin,
            vmax=vmax,
            origin="lower",
            extent=(data.xmin - 0.5 * data.xstep, data.xmax + 0.5 * data.xstep,
                    data.ymin - 0.5 * data.ystep,
                    data.ymax + 0.5 * data.ystep),
            aspect=self.aspect,
            interpolation='none')
        self.setTickLabelFont(self.ax_Spec, "Comic Sans MS")
        visible_flag = self.selector.visible
        self.selector.set_visible(False)
        if useblit:
            self.refreshimage()
        else:
            pass
        self.background_Spec = self.canvas.copy_from_bbox(
            self.ax_Spec.get_window_extent()
        )  #this line makes sure that the background will be updated when user change color map
        self.selector.set_visible(visible_flag)
        self.plotArtist()

    def refreshimage(self):
        self.ax_Spec.redraw_in_frame()
        self.canvas.blit(self.ax_Spec.get_window_extent())

    def plotXDC(self, useblit, usecursor):
        if useblit:
            self.canvas.restore_region(self.background_XDC)
            self.ax_XDC.draw_artist(self.XDCLine)
            if usecursor:
                self.ax_XDC.draw_artist(self.vl)
            self.canvas.blit(self.ax_XDC.get_window_extent())
        else:
            self.canvas.draw()

    def plotYDC(self, useblit, usecursor):
        if useblit:
            self.canvas.restore_region(self.background_YDC)
            self.ax_YDC.draw_artist(self.YDCLine)
            if usecursor:
                self.ax_YDC.draw_artist(self.hl)
            self.canvas.blit(self.ax_YDC.get_window_extent())
        else:
            self.canvas.draw()

    def resetXDC(self):
        mask = np.isnan(self.XDC)
        self.XDCLine.set_data(self.data.xscale[~mask], self.XDC[~mask])
        if len(self.XDC[~mask]) > 1:
            minvalue = min(self.XDC[~mask])
            maxvalue = max(self.XDC[~mask])
            if minvalue == maxvalue:
                minvalue = minvalue - 0.5
                maxvalue = maxvalue + 0.5
        else:
            minvalue = 0
            maxvalue = 1
        self.ax_XDC.set_ybound(lower=minvalue - 0.01 * (maxvalue - minvalue),
                               upper=maxvalue + 0.01 * (maxvalue - minvalue))
        self.ax_XDC.set_xbound(lower=self.data.xmin - self.data.xstep * 0.5,
                               upper=self.data.xmax + self.data.xstep * 0.5)

    def resetYDC(self):
        mask = np.isnan(self.YDC)
        self.YDCLine.set_data(self.YDC[~mask], self.data.yscale[~mask])
        if len(self.YDC[~mask]) > 1:
            minvalue = min(self.YDC[~mask])
            maxvalue = max(self.YDC[~mask])
            if minvalue == maxvalue:
                minvalue = minvalue - 0.5
                maxvalue = maxvalue + 0.5
        else:
            minvalue = 0
            maxvalue = 1
        self.ax_YDC.set_xbound(lower=minvalue - 0.01 * (maxvalue - minvalue),
                               upper=maxvalue + 0.01 * (maxvalue - minvalue))
        self.ax_YDC.set_ybound(lower=self.data.ymin - self.data.ystep * 0.5,
                               upper=self.data.ymax + self.data.ystep * 0.5)

    def changeAspect(self, state):
        if state:
            self.aspect = "equal"
        else:
            self.aspect = "auto"
        self.ax_Spec.set_aspect(self.aspect)
        self.updateAxesPosition(True)

    def updateAxesPosition(self, useDraw):
        x_Spec, y_Spec, w_Spec, h_Spec = self.ax_Spec.get_position().bounds
        x_XDC, y_XDC, w_XDC, h_XDC = self.ax_XDC.get_position().bounds
        x_YDC, y_YDC, w_YDC, h_YDC = self.ax_YDC.get_position().bounds
        self.ax_XDC.set_position([x_Spec, y_XDC, w_Spec, h_XDC])
        self.ax_YDC.set_position([x_YDC, y_Spec, w_YDC, h_Spec])
        visible_flag = self.selector.visible
        self.selector.set_visible(False)
        if useDraw:
            self.canvas.draw()
        self.GetWhiteBackground()
        self.selector.set_visible(visible_flag)
        self.plotArtist()
        self.plotXDC(True, True)
        self.plotYDC(True, True)

    def resizeEvent_wrapper(self):
        self.updateAxesPosition(False)