예제 #1
0
class StraditizerWidgets(QWidget, DockMixin):
    """A widget that contains widgets to control the straditization in a GUI

    This widget is the basis of the straditize GUI and implemented as a
    plugin into the psyplot gui. The open straditizers are handled in the
    :attr:`_straditizer` attribute.

    The central parts of this widget are

    - The combobox to manage the open straditizers
    - The QTreeWidget in the :attr:`tree` attribute that contains all the
      controls to interface the straditizer
    - the tutorial area
    - the :guilabel:`Apply` and :guilabel:`Cancel` button"""

    #: Boolean that is True if all dialogs should be answered with `Yes`
    always_yes = False

    #: The QTreeWidget that contains the different widgets for the digitization
    tree = None

    #: The apply button
    apply_button = None

    #: The cancel button
    cancel_button = None

    #: The button to edit the straditizer attributes
    attrs_button = None

    #: The button to start a tutorial
    tutorial_button = None

    #: An :class:`InfoButton` to display the docs
    info_button = None

    #: A QComboBox to select the current straditizer
    stradi_combo = None

    #: A button to open a new straditizer
    btn_open_stradi = None

    #: A button to close the current straditizer
    btn_close_stradi = None

    #: A button to reload the last autosaved state
    btn_reload_autosaved = None

    #: The :class:`straditize.widgets.progress_widget.ProgressWidget` to
    #: display the progress of the straditization
    progress_widget = None

    #: The :class:`straditize.widgets.data.DigitizingControl` to interface
    #: the :straditize.straditizer.Straditizer.data_reader`
    digitizer = None

    #: The :class:`straditize.widgets.colnames.ColumnNamesManager` to interface
    #: the :straditize.straditizer.Straditizer.colnames_reader`
    colnames_manager = None

    #: The :class:`straditize.widgets.axes_translations.AxesTranslations` to
    #: handle the y- and x-axis conversions
    axes_translations = None

    #: The :class:`straditize.widgets.image_correction.ImageRescaler` class to
    #: rescale the image
    image_rescaler = None

    #: The :class:`straditize.widgets.image_correction.ImageRotator` class to
    #: rotate the image
    image_rotator = None

    #: The :class:`straditize.widgets.plots.PlotControl` to display additional
    #: information on the diagram
    plot_control = None

    #: The :class:`straditize.widgets.marker_control.MarkerControl` to modify
    #: the appearance of the :class:`~straditize.straditizer.Straditizer.marks`
    #: of the current straditizer
    marker_control = None

    #: The :class:`straditize.widgets.selection_toolbar.SelectionToolbar` to
    #: select features in the stratigraphic diagram
    selection_toolbar = None

    #: The :class:`straditize.straditizer.Straditizer` instance
    straditizer = None

    #: open straditizers
    _straditizers = []

    #: The :class:`straditize.widgets.tutorial.Tutorial` class
    tutorial = None

    dock_position = Qt.LeftDockWidgetArea

    #: Auto-saved straditizers
    autosaved = []

    hidden = True

    title = 'Stratigraphic diagram digitization'

    window_layout_action = None

    open_external = QtCore.pyqtSignal(list)

    def __init__(self, *args, **kwargs):
        from straditize.widgets.menu_actions import StraditizerMenuActions
        from straditize.widgets.progress_widget import ProgressWidget
        from straditize.widgets.data import DigitizingControl
        from straditize.widgets.selection_toolbar import SelectionToolbar
        from straditize.widgets.marker_control import MarkerControl
        from straditize.widgets.plots import PlotControl
        from straditize.widgets.axes_translations import AxesTranslations
        from straditize.widgets.image_correction import (ImageRotator,
                                                         ImageRescaler)
        from straditize.widgets.colnames import ColumnNamesManager
        self._straditizers = []
        super(StraditizerWidgets, self).__init__(*args, **kwargs)
        self.tree = QTreeWidget(parent=self)
        self.tree.setSelectionMode(QTreeWidget.NoSelection)
        self.refresh_button = QToolButton(self)
        self.refresh_button.setIcon(QIcon(get_psy_icon('refresh.png')))
        self.refresh_button.setToolTip('Refresh from the straditizer')
        self.apply_button = EnableButton('Apply', parent=self)
        self.cancel_button = EnableButton('Cancel', parent=self)
        self.attrs_button = QPushButton('Attributes', parent=self)
        self.tutorial_button = QPushButton('Tutorial', parent=self)
        self.tutorial_button.setCheckable(True)
        self.error_msg = PyErrorMessage(self)
        self.stradi_combo = QComboBox()
        self.btn_open_stradi = QToolButton()
        self.btn_open_stradi.setIcon(QIcon(get_psy_icon('run_arrow.png')))
        self.btn_close_stradi = QToolButton()
        self.btn_close_stradi.setIcon(QIcon(get_psy_icon('invalid.png')))
        self.btn_reload_autosaved = QPushButton("Reload")
        self.btn_reload_autosaved.setToolTip(
            "Close the straditizer and reload the last autosaved project")

        # ---------------------------------------------------------------------
        # --------------------------- Tree widgets ----------------------------
        # ---------------------------------------------------------------------
        self.tree.setHeaderLabels(['', ''])
        self.tree.setColumnCount(2)

        self.progress_item = QTreeWidgetItem(0)
        self.progress_item.setText(0, 'ToDo list')
        self.progress_widget = ProgressWidget(self, self.progress_item)

        self.menu_actions_item = QTreeWidgetItem(0)
        self.menu_actions_item.setText(0, 'Images import/export')
        self.tree.addTopLevelItem(self.menu_actions_item)
        self.menu_actions = StraditizerMenuActions(self)

        self.digitizer_item = item = QTreeWidgetItem(0)
        item.setText(0, 'Digitization control')
        self.digitizer = DigitizingControl(self, item)

        self.col_names_item = item = QTreeWidgetItem(0)
        item.setText(0, 'Column names')
        self.colnames_manager = ColumnNamesManager(self, item)
        self.add_info_button(item, 'column_names.rst')

        self.axes_translations_item = item = QTreeWidgetItem(0)
        item.setText(0, 'Axes translations')
        self.axes_translations = AxesTranslations(self, item)

        self.image_transform_item = item = QTreeWidgetItem(0)
        item.setText(0, 'Transform source image')

        self.image_rescaler = ImageRescaler(self, item)

        self.image_rotator_item = item = QTreeWidgetItem(0)
        item.setText(0, 'Rotate image')
        self.image_rotator = ImageRotator(self)
        self.image_transform_item.addChild(item)
        self.image_rotator.setup_children(item)

        self.plot_control_item = item = QTreeWidgetItem(0)
        item.setText(0, 'Plot control')
        self.plot_control = PlotControl(self, item)
        self.add_info_button(item, 'plot_control.rst')

        self.marker_control_item = item = QTreeWidgetItem(0)
        item.setText(0, 'Marker control')
        self.marker_control = MarkerControl(self, item)
        self.add_info_button(item, 'marker_control.rst')

        # ---------------------------------------------------------------------
        # ----------------------------- Toolbars ------------------------------
        # ---------------------------------------------------------------------
        self.selection_toolbar = SelectionToolbar(self, 'Selection toolbar')

        # ---------------------------------------------------------------------
        # ----------------------------- InfoButton ----------------------------
        # ---------------------------------------------------------------------
        self.info_button = InfoButton(self, get_doc_file('straditize.rst'))

        # ---------------------------------------------------------------------
        # --------------------------- Layouts ---------------------------------
        # ---------------------------------------------------------------------

        stradi_box = QHBoxLayout()
        stradi_box.addWidget(self.stradi_combo, 1)
        stradi_box.addWidget(self.btn_open_stradi)
        stradi_box.addWidget(self.btn_close_stradi)

        attrs_box = QHBoxLayout()
        attrs_box.addWidget(self.attrs_button)
        attrs_box.addStretch(0)
        attrs_box.addWidget(self.tutorial_button)

        btn_box = QHBoxLayout()
        btn_box.addWidget(self.refresh_button)
        btn_box.addWidget(self.info_button)
        btn_box.addStretch(0)
        btn_box.addWidget(self.apply_button)
        btn_box.addWidget(self.cancel_button)

        reload_box = QHBoxLayout()
        reload_box.addWidget(self.btn_reload_autosaved)
        reload_box.addStretch(0)

        vbox = QVBoxLayout()
        vbox.addLayout(stradi_box)
        vbox.addWidget(self.tree)
        vbox.addLayout(attrs_box)
        vbox.addLayout(btn_box)
        vbox.addLayout(reload_box)

        self.setLayout(vbox)

        self.apply_button.setEnabled(False)
        self.cancel_button.setEnabled(False)
        self.tree.expandItem(self.progress_item)
        self.tree.expandItem(self.digitizer_item)

        # ---------------------------------------------------------------------
        # --------------------------- Connections -----------------------------
        # ---------------------------------------------------------------------
        self.stradi_combo.currentIndexChanged.connect(self.set_current_stradi)
        self.refresh_button.clicked.connect(self.refresh)
        self.attrs_button.clicked.connect(self.edit_attrs)
        self.tutorial_button.clicked.connect(self.start_tutorial)
        self.open_external.connect(self._create_straditizer_from_args)
        self.btn_open_stradi.clicked.connect(
            self.menu_actions.open_straditizer)
        self.btn_close_stradi.clicked.connect(self.close_straditizer)
        self.btn_reload_autosaved.clicked.connect(self.reload_autosaved)

        self.refresh()
        header = self.tree.header()
        header.setStretchLastSection(False)
        header.setSectionResizeMode(0, QHeaderView.Stretch)

    def disable_apply_button(self):
        """Method that is called when the :attr:`cancel_button` is clicked"""
        for w in [self.apply_button, self.cancel_button]:
            try:
                w.clicked.disconnect()
            except TypeError:
                pass
            w.setEnabled(False)
        self.apply_button.setText('Apply')
        self.cancel_button.setText('Cancel')
        self.refresh_button.setEnabled(True)

    def switch_to_straditizer_layout(self):
        """Switch to the straditizer layout

        This method makes this widget visible and stacks it with the psyplot
        content widget"""
        mainwindow = self.dock.parent()
        mainwindow.figures_tree.hide_plugin()
        mainwindow.ds_tree.hide_plugin()
        mainwindow.fmt_widget.hide_plugin()
        self.show_plugin()
        mainwindow.tabifyDockWidget(mainwindow.project_content.dock, self.dock)
        hsize = self.marker_control.sizeHint().width() + 50
        self.menu_actions.setup_shortcuts(mainwindow)
        if with_qt5:
            mainwindow.resizeDocks([self.dock], [hsize], Qt.Horizontal)
            self.tree.resizeColumnToContents(0)
            self.tree.resizeColumnToContents(1)
        self.info_button.click()

    def to_dock(self, main, *args, **kwargs):
        ret = super(StraditizerWidgets, self).to_dock(main, *args, **kwargs)
        if self.menu_actions.window_layout_action is None:
            main.window_layouts_menu.addAction(self.window_layout_action)
            main.callbacks['straditize'] = self.open_external.emit
            main.addToolBar(self.selection_toolbar)
            self.dock.toggleViewAction().triggered.connect(
                self.show_or_hide_toolbar)
            self.menu_actions.setup_menu_actions(main)
            self.menu_actions.setup_children(self.menu_actions_item)
            try:
                main.open_file_options['Straditize project'] = \
                    self.create_straditizer_from_args
            except AttributeError:  # psyplot-gui <= 1.1.0
                pass
        return ret

    def show_or_hide_toolbar(self):
        """Show or hide the toolbar depending on the visibility of this widget
        """
        self.selection_toolbar.setVisible(self.is_shown)

    def _create_straditizer_from_args(self, args):
        """A method that is called when the :attr:`psyplot_gui.main.mainwindow`
        receives a 'straditize' callback"""
        self.create_straditizer_from_args(*args)

    def create_straditizer_from_args(self,
                                     fnames,
                                     project=None,
                                     xlim=None,
                                     ylim=None,
                                     full=False,
                                     reader_type='area'):
        """Create a straditizer from the given file name

        This method is called when the :attr:`psyplot_gui.main.mainwindow`
        receives a 'straditize' callback"""
        fname = fnames[0]
        if fname is not None:
            self.menu_actions.open_straditizer(fname)
            stradi = self.straditizer
            if stradi is None:
                return
            if xlim is not None:
                stradi.data_xlim = xlim
            if ylim is not None:
                stradi.data_ylim = ylim
            if xlim is not None or ylim is not None or full:
                if stradi.data_xlim is None:
                    stradi.data_xlim = [0, np.shape(stradi.image)[1]]
                if stradi.data_ylim is None:
                    stradi.data_ylim = [0, np.shape(stradi.image)[0]]
                stradi.init_reader(reader_type)
                stradi.data_reader.digitize()
            self.refresh()
        if not self.is_shown:
            self.switch_to_straditizer_layout()
        return fname is not None

    def start_tutorial(self, state, tutorial_cls=None):
        """Start or stop the tutorial

        Parameters
        ----------
        state: bool
            If False, the tutorial is stopped. Otherwise it is started
        tutorial_cls: straditize.widgets.tutorial.beginner.Tutorial
            The tutorial class to use. If None, it will be asked in a
            QInputDialog"""
        if self.tutorial is not None or not state:
            self.tutorial.close()
            self.tutorial_button.setText('Tutorial')
        elif state:
            if tutorial_cls is None:
                tutorial_cls, ok = QInputDialog.getItem(
                    self,
                    'Start tutorial',
                    "Select the tutorial type",
                    ["Beginner", "Advanced (Hoya del Castillo)"],
                    editable=False)
                if not ok:
                    self.tutorial_button.blockSignals(True)
                    self.tutorial_button.setChecked(False)
                    self.tutorial_button.blockSignals(False)
                    return
                if tutorial_cls == 'Beginner':
                    from straditize.widgets.tutorial import Tutorial
                else:
                    from straditize.widgets.tutorial import (
                        HoyaDelCastilloTutorial as Tutorial)
            else:
                Tutorial = tutorial_cls
            self.tutorial = Tutorial(self)
            self.tutorial_button.setText('Stop tutorial')

    def edit_attrs(self):
        """Edit the attributes of the current straditizer

        This creates a new dataframe editor to edit the
        :attr:`straditize.straditizer.Straditizer.attrs` meta informations"""
        def add_attr(key):
            model = editor.table.model()
            n = len(attrs)
            model.insertRow(n)
            model.setData(model.index(n, 0), key)
            model.setData(model.index(n, 1), '', change_type=six.text_type)

        from psyplot_gui.main import mainwindow
        from straditize.straditizer import common_attributes
        attrs = self.straditizer.attrs
        editor = mainwindow.new_data_frame_editor(attrs,
                                                  'Straditizer attributes')
        editor.table.resizeColumnToContents(1)
        editor.table.horizontalHeader().setVisible(False)
        editor.table.frozen_table_view.horizontalHeader().setVisible(False)
        combo = QComboBox()
        combo.addItems([''] + common_attributes)
        combo.currentTextChanged.connect(add_attr)
        hbox = QHBoxLayout()
        hbox.addWidget(QLabel('Common attributes:'))
        hbox.addWidget(combo)
        hbox.addStretch(0)
        editor.layout().insertLayout(1, hbox)
        return editor, combo

    def refresh(self):
        """Refresh from the straditizer"""
        for i, stradi in enumerate(self._straditizers):
            self.stradi_combo.setItemText(
                i,
                self.get_attr(stradi, 'project_file')
                or self.get_attr(stradi, 'image_file') or '')
        # toggle visibility of close button and attributes button
        enable = self.straditizer is not None
        self.btn_close_stradi.setVisible(enable)
        self.attrs_button.setEnabled(enable)
        # refresh controls
        self.menu_actions.refresh()
        self.progress_widget.refresh()
        self.digitizer.refresh()
        self.selection_toolbar.refresh()
        self.plot_control.refresh()
        self.marker_control.refresh()
        self.axes_translations.refresh()
        if self.tutorial is not None:
            self.tutorial.refresh()
        self.image_rotator.refresh()
        self.image_rescaler.refresh()
        self.colnames_manager.refresh()
        self.btn_reload_autosaved.setEnabled(bool(self.autosaved))

    def get_attr(self, stradi, attr):
        try:
            return stradi.get_attr(attr)
        except KeyError:
            pass

    docstrings.delete_params('InfoButton.parameters', 'parent')

    @docstrings.get_sectionsf('StraditizerWidgets.add_info_button')
    @docstrings.with_indent(8)
    def add_info_button(self,
                        child,
                        fname=None,
                        rst=None,
                        name=None,
                        connections=[]):
        """Add an infobutton to the :attr:`tree` widget

        Parameters
        ----------
        child: QTreeWidgetItem
            The item to which to add the infobutton
        %(InfoButton.parameters.no_parent)s
        connections: list of QPushButtons
            Buttons that should be clicked when the info button is clicked"""
        button = InfoButton(self, fname=fname, rst=rst, name=name)
        self.tree.setItemWidget(child, 1, button)
        for btn in connections:
            btn.clicked.connect(button.click)
        return button

    def raise_figures(self):
        """Raise the figures of the current straditizer in the GUI"""
        from psyplot_gui.main import mainwindow
        if mainwindow.figures and self.straditizer:
            dock = self.straditizer.ax.figure.canvas.manager.window
            dock.widget().show_plugin()
            dock.raise_()
            if self.straditizer.magni is not None:
                dock = self.straditizer.magni.ax.figure.canvas.manager.window
                dock.widget().show_plugin()
                dock.raise_()

    def set_current_stradi(self, i):
        """Set the i-th straditizer to the current one"""
        if not self._straditizers:
            return
        self.straditizer = self._straditizers[i]
        self.menu_actions.set_stradi_in_console()
        block = self.stradi_combo.blockSignals(True)
        self.stradi_combo.setCurrentIndex(i)
        self.stradi_combo.blockSignals(block)
        self.raise_figures()
        self.refresh()
        self.autosaved.clear()

    def _close_stradi(self, stradi):
        """Close the given straditizer and all it's figures"""
        is_current = stradi is self.straditizer
        if is_current:
            self.selection_toolbar.disconnect()
        stradi.close()
        try:
            i = self._straditizers.index(stradi)
        except ValueError:
            pass
        else:
            del self._straditizers[i]
            self.stradi_combo.removeItem(i)
        if is_current and self._straditizers:
            self.stradi_combo.setCurrentIndex(0)
        elif not self._straditizers:
            self.straditizer = None
            self.refresh()
        self.digitizer.digitize_item.takeChildren()
        self.digitizer.btn_digitize.setChecked(False)
        self.digitizer.btn_digitize.setCheckable(False)
        self.digitizer.toggle_txt_tolerance('')

    def close_straditizer(self):
        """Close the current straditizer"""
        self._close_stradi(self.straditizer)

    def close_all_straditizers(self):
        """Close all straditizers"""
        self.selection_toolbar.disconnect()
        for stradi in self._straditizers:
            stradi.close()
        self._straditizers.clear()
        self.straditizer = None
        self.stradi_combo.clear()
        self.digitizer.digitize_item.takeChildren()
        self.digitizer.btn_digitize.setChecked(False)
        self.digitizer.btn_digitize.setCheckable(False)
        self.digitizer.toggle_txt_tolerance('')
        self.refresh()

    def add_straditizer(self, stradi):
        """Add a straditizer to the list of open straditizers"""
        if stradi and stradi not in self._straditizers:
            self._straditizers.append(stradi)
            self.stradi_combo.addItem(' ')
            self.set_current_stradi(len(self._straditizers) - 1)

    def reset_control(self):
        """Reset the GUI of straditize"""
        if getattr(self.selection_toolbar, '_pattern_selection', None):
            self.selection_toolbar._pattern_selection.remove_plugin()
            del self.selection_toolbar._pattern_selection
        if getattr(self.digitizer, '_samples_editor', None):
            self.digitizer._close_samples_fig()
        tb = self.selection_toolbar
        tb.set_label_wand_mode()
        tb.set_rect_select_mode()
        tb.new_select_action.setChecked(True)
        tb.select_action.setChecked(False)
        tb.wand_action.setChecked(False)
        self.disable_apply_button()
        self.close_all_straditizers()
        self.colnames_manager.reset_control()

    def autosave(self):
        """Autosave the current straditizer"""
        self.autosaved = [self.straditizer.to_dataset().copy(True)] + \
            self.autosaved[:4]

    def reload_autosaved(self):
        """Reload the autosaved straditizer and close the old one"""
        from straditize.straditizer import Straditizer
        if not self.autosaved:
            return
        answer = QMessageBox.question(
            self, 'Reload autosave',
            'Shall I reload the last autosaved stage? This will close the '
            'current figures.')
        if answer == QMessageBox.Yes:
            self.close_straditizer()
            stradi = Straditizer.from_dataset(self.autosaved.pop(0))
            self.menu_actions.finish_loading(stradi)
예제 #2
0
    def __init__(self, parent, fmto, artist=None, base=None):
        """
        Parameters
        ----------
        %(FontWeightWidget.parameters)s
        """
        QWidget.__init__(self, parent)
        hbox = QHBoxLayout()
        if artist is not None:
            self.current_font = self.artist_to_qfont(artist)
            self.current_color = QtGui.QColor.fromRgbF(
                *mcol.to_rgba(artist.get_color()))
        else:
            self.current_color = QtGui.QColor(Qt.black)
        self.fmto_name = fmto.name or fmto.key

        # choose font button
        button = QPushButton('Choose font')
        button.clicked.connect(partial(self.choose_font, None))
        hbox.addWidget(button)

        # font size spin box
        self.spin_box = spin_box = QSpinBox(self)
        spin_box.setRange(1, 1e9)
        if artist is not None:
            spin_box.setValue(int(artist.get_size()))
        spin_box.valueChanged.connect(self.modify_size)
        hbox.addWidget(spin_box)

        # font color button
        self.btn_font_color = button = QToolButton(self)
        button.setIcon(QIcon(get_icon('font_color.png')))
        button.clicked.connect(partial(self.choose_color, None))
        hbox.addWidget(button)

        # bold button
        self.btn_bold = button = QToolButton(self)
        button.setIcon(QIcon(get_icon('bold.png')))
        button.clicked.connect(self.toggle_bold)
        button.setCheckable(True)
        if artist is not None:
            button.setChecked(self.current_font.weight() > 50)
        hbox.addWidget(button)

        # italic button
        self.btn_italic = button = QToolButton(self)
        button.setIcon(QIcon(get_icon('italic.png')))
        button.clicked.connect(self.toggle_italic)
        button.setCheckable(True)
        if artist is not None:
            button.setChecked(self.current_font.italic())
        hbox.addWidget(button)

        if base is not None:
            # add a button to change to the base formatoption
            fmtos = [
                base,
                getattr(fmto.plotter, base.key + 'size', None),
                getattr(fmto.plotter, base.key + 'weight', None),
            ]
            fmtos = list(filter(None, fmtos))
            hbox.addWidget(Switch2FmtButton(parent, *fmtos))

        self.setLayout(hbox)
예제 #3
0
class PatternSelectionWidget(QWidget, DockMixin):
    """A wdiget to select patterns in the image

    This widget consist of an :class:`EmbededMplCanvas` to display the template
    for the pattern and uses the :func:`skimage.feature.match_template`
    function to identify it in the :attr:`arr`

    See Also
    --------
    straditize.widget.selection_toolbar.SelectionToolbar.start_pattern_selection
    """

    #: The template to look for in the :attr:`arr`
    template = None

    #: The selector to select the template in the original image
    selector = None

    #: The extents of the :attr:`template` in the original image
    template_extents = None

    #: The matplotlib artist of the :attr:`template` in the
    #: :attr:`template_fig`
    template_im = None

    #: The :class:`EmbededMplCanvas` to display the :attr:`template`
    template_fig = None

    axes = None

    #: A QSlider to set the threshold for the template correlation
    sl_thresh = None

    _corr_plot = None

    key_press_cid = None

    def __init__(self, arr, data_obj, remove_selection=False, *args, **kwargs):
        """
        Parameters
        ----------
        arr: np.ndarray of shape ``(Ny, Nx)``
            The labeled selection array
        data_obj: straditize.label_selection.LabelSelection
            The data object whose image shall be selected
        remove_selection: bool
            If True, remove the selection on apply
        """
        super(PatternSelectionWidget, self).__init__(*args, **kwargs)
        self.arr = arr
        self.data_obj = data_obj
        self.remove_selection = remove_selection
        self.template = None

        # the figure to show the template
        self.template_fig = EmbededMplCanvas()
        # the button to select the template
        self.btn_select_template = QPushButton('Select a template')
        self.btn_select_template.setCheckable(True)
        # the checkbox to allow fractions of the template
        self.fraction_box = QGroupBox('Template fractions')
        self.fraction_box.setCheckable(True)
        self.fraction_box.setChecked(False)
        self.fraction_box.setEnabled(False)
        self.sl_fraction = QSlider(Qt.Horizontal)
        self.lbl_fraction = QLabel('0.75')
        self.sl_fraction.setValue(75)
        # the slider to select the increments of the fractions
        self.sl_increments = QSlider(Qt.Horizontal)
        self.sl_increments.setValue(3)
        self.sl_increments.setMinimum(1)
        self.lbl_increments = QLabel('3')
        # the button to perform the correlation
        self.btn_correlate = QPushButton('Find template')
        self.btn_correlate.setEnabled(False)
        # the button to plot the correlation
        self.btn_plot_corr = QPushButton('Plot correlation')
        self.btn_plot_corr.setCheckable(True)
        self.btn_plot_corr.setEnabled(False)
        # slider for subselection
        self.btn_select = QPushButton('Select pattern')
        self.sl_thresh = QSlider(Qt.Horizontal)
        self.lbl_thresh = QLabel('0.5')

        self.btn_select.setCheckable(True)
        self.btn_select.setEnabled(False)
        self.sl_thresh.setValue(75)
        self.sl_thresh.setVisible(False)
        self.lbl_thresh.setVisible(False)

        # cancel and close button
        self.btn_cancel = QPushButton('Cancel')
        self.btn_close = QPushButton('Apply')
        self.btn_close.setEnabled(False)

        vbox = QVBoxLayout()

        vbox.addWidget(self.template_fig)
        hbox = QHBoxLayout()
        hbox.addStretch(0)
        hbox.addWidget(self.btn_select_template)
        vbox.addLayout(hbox)

        fraction_layout = QGridLayout()
        fraction_layout.addWidget(QLabel('Fraction'), 0, 0)
        fraction_layout.addWidget(self.sl_fraction, 0, 1)
        fraction_layout.addWidget(self.lbl_fraction, 0, 2)
        fraction_layout.addWidget(QLabel('Increments'), 1, 0)
        fraction_layout.addWidget(self.sl_increments, 1, 1)
        fraction_layout.addWidget(self.lbl_increments, 1, 2)

        self.fraction_box.setLayout(fraction_layout)

        vbox.addWidget(self.fraction_box)
        vbox.addWidget(self.btn_correlate)
        vbox.addWidget(self.btn_plot_corr)
        vbox.addWidget(self.btn_select)
        thresh_box = QHBoxLayout()
        thresh_box.addWidget(self.sl_thresh)
        thresh_box.addWidget(self.lbl_thresh)
        vbox.addLayout(thresh_box)

        hbox = QHBoxLayout()
        hbox.addWidget(self.btn_cancel)
        hbox.addWidget(self.btn_close)
        vbox.addLayout(hbox)
        self.setLayout(vbox)

        self.btn_select_template.clicked.connect(
            self.toggle_template_selection)
        self.sl_fraction.valueChanged.connect(
            lambda i: self.lbl_fraction.setText(str(i / 100.)))
        self.sl_increments.valueChanged.connect(
            lambda i: self.lbl_increments.setText(str(i)))
        self.btn_correlate.clicked.connect(self.start_correlation)
        self.btn_plot_corr.clicked.connect(self.toggle_correlation_plot)
        self.sl_thresh.valueChanged.connect(
            lambda i: self.lbl_thresh.setText(str((i - 50) / 50.)))
        self.sl_thresh.valueChanged.connect(self.modify_selection)
        self.btn_select.clicked.connect(self.toggle_selection)

        self.btn_cancel.clicked.connect(self.cancel)
        self.btn_close.clicked.connect(self.close)

    def toggle_template_selection(self):
        """Enable or disable the template selection"""
        if (not self.btn_select_template.isChecked()
                and self.selector is not None):
            self.selector.set_active(False)
            for a in self.selector.artists:
                a.set_visible(False)
            self.btn_select_template.setText('Select a template')
        elif self.selector is not None and self.template_im is not None:
            self.selector.set_active(True)
            for a in self.selector.artists:
                a.set_visible(True)
            self.btn_select_template.setText('Apply')
        else:
            self.selector = RectangleSelector(self.data_obj.ax,
                                              self.update_image,
                                              interactive=True)
            if self.template_extents is not None:
                self.selector.draw_shape(self.template_extents)
            self.key_press_cid = self.data_obj.ax.figure.canvas.mpl_connect(
                'key_press_event', self.update_image)
            self.btn_select_template.setText('Cancel')
        self.data_obj.draw_figure()
        if self.template is not None:
            self.fraction_box.setEnabled(True)
            self.sl_increments.setMaximum(min(self.template.shape[:2]))
            self.btn_correlate.setEnabled(True)

    def update_image(self, *args, **kwargs):
        """Update the template image based on the :attr:`selector` extents"""
        if self.template_im is not None:
            self.template_im.remove()
            del self.template_im
        elif self.axes is None:
            self.axes = self.template_fig.figure.add_subplot(111)
            self.template_fig.figure.subplots_adjust(bottom=0.3)
        if not self.selector.artists[0].get_visible():
            self.template_extents = None
            self.template = None
            self.btn_select_template.setText('Cancel')
        else:
            self.template_extents = np.round(self.selector.extents).astype(int)
            x, y = self.template_extents.reshape((2, 2))
            if getattr(self.data_obj, 'extent', None) is not None:
                extent = self.data_obj.extent
                x -= int(min(extent[:2]))
                y -= int(min(extent[2:]))
            slx = slice(*sorted(x))
            sly = slice(*sorted(y))
            self.template = template = self.arr[sly, slx]
            if template.ndim == 3:
                self.template_im = self.axes.imshow(template)
            else:
                self.template_im = self.axes.imshow(template, cmap='binary')
            self.btn_select_template.setText('Apply')
        self.template_fig.draw()

    def start_correlation(self):
        """Look for the correlations of template and source"""
        if self.fraction_box.isChecked():
            self._fraction = self.sl_fraction.value() / 100.
            increments = self.sl_increments.value()
        else:
            self._fraction = 0
            increments = 1
        corr = self.correlate_template(self.arr, self.template, self._fraction,
                                       increments)
        if corr is not None:
            self._correlation = corr
        enable = self._correlation is not None
        self.btn_plot_corr.setEnabled(enable)
        self.btn_select.setEnabled(enable)

    def toggle_selection(self):
        """Modifiy the selection (or not) based on the template correlation"""
        obj = self.data_obj
        if self.btn_select.isChecked():
            self._orig_selection_arr = obj._selection_arr.copy()
            self._selected_labels = obj.selected_labels
            self._select_cmap = obj._select_cmap
            self._select_norm = obj._select_norm
            self.btn_select.setText('Reset')
            self.btn_close.setEnabled(True)
            obj.unselect_all_labels()
            self.sl_thresh.setVisible(True)
            self.lbl_thresh.setVisible(True)
            self.modify_selection(self.sl_thresh.value())
        else:
            if obj._selection_arr is not None:
                obj._selection_arr[:] = self._orig_selection_arr
                obj._select_img.set_array(self._orig_selection_arr)
                obj.select_labels(self._selected_labels)
                obj._update_magni_img()
            del self._orig_selection_arr, self._selected_labels
            self.btn_select.setText('Select pattern')
            self.btn_close.setEnabled(False)
            self.sl_thresh.setVisible(False)
            self.lbl_thresh.setVisible(False)
            obj.draw_figure()

    def modify_selection(self, i):
        """Modify the selection based on the correlation threshold

        Parameters
        ----------
        i: int
            An integer between 0 and 100, the value of the :attr:`sl_thresh`
            slider"""
        if not self.btn_select.isChecked():
            return
        obj = self.data_obj
        val = (i - 50.) / 50.
        # select the values above 50
        if not self.remove_selection:
            # clear the selection
            obj._selection_arr[:] = obj._orig_selection_arr.copy()
            select_val = obj._selection_arr.max() + 1
            obj._selection_arr[self._correlation >= val] = select_val
        else:
            obj._selection_arr[:] = self._orig_selection_arr.copy()
            obj._selection_arr[self._correlation >= val] = -1
        obj._select_img.set_array(obj._selection_arr)
        obj._update_magni_img()
        obj.draw_figure()

    def correlate_template(self,
                           arr,
                           template,
                           fraction=False,
                           increment=1,
                           report=True):
        """Correlate a template with the `arr`

        This method uses the :func:`skimage.feature.match_template` function
        to find the given `template` in the source array `arr`.

        Parameters
        ----------
        arr: np.ndarray of shape ``(Ny,Nx)``
            The labeled selection array (see :attr:`arr`), the source of the
            given `template`
        template: np.ndarray of shape ``(nx, ny)``
            The template from ``arr`` that shall be searched
        fraction: float
            If not null, we will look through the given fraction of the
            template to look for partial matches as well
        increment: int
            The increment of the loop with the `fraction`.
        report: bool
            If True and `fraction` is not null, a QProgressDialog is opened
            to inform the user about the progress"""
        from skimage.feature import match_template
        mask = self.data_obj.selected_part
        x = mask.any(axis=0)
        if not x.any():
            raise ValueError("No data selected!")
        y = mask.any(axis=1)
        xmin = x.argmax()
        xmax = len(x) - x[::-1].argmax()
        ymin = y.argmax()
        ymax = len(y) - y[::-1].argmax()
        if arr.ndim == 3:
            mask = np.tile(mask[..., np.newaxis], (1, 1, arr.shape[-1]))
        src = np.where(mask[ymin:ymax, xmin:xmax], arr[ymin:ymax, xmin:xmax],
                       0)
        sny, snx = src.shape
        if not fraction:
            corr = match_template(src, template)
            full_shape = np.array(corr.shape)
        else:  # loop through the template to allow partial hatches
            shp = np.array(template.shape, dtype=int)[:2]
            ny, nx = shp
            fshp = np.round(fraction * shp).astype(int)
            fny, fnx = fshp
            it = list(
                product(range(0, fny, increment), range(0, fnx, increment)))
            ntot = len(it)
            full_shape = fshp - shp + src.shape
            corr = np.zeros(full_shape, dtype=float)
            if report:
                txt = 'Searching template...'
                dialog = QProgressDialog(txt, 'Cancel', 0, ntot)
                dialog.setWindowModality(Qt.WindowModal)
                t0 = dt.datetime.now()
            for k, (i, j) in enumerate(it):
                if report:
                    dialog.setValue(k)
                    if k and not k % 10:
                        passed = (dt.datetime.now() - t0).total_seconds()
                        dialog.setLabelText(txt + ' %1.0f seconds remaning' %
                                            ((passed * (ntot / k - 1.))))
                if report and dialog.wasCanceled():
                    return
                else:
                    y_end, x_start = fshp - (i, j) - 1
                    sly = slice(y_end, full_shape[0])
                    slx = slice(0, -x_start or full_shape[1])
                    corr[sly, slx] = np.maximum(
                        corr[sly, slx],
                        match_template(src, template[:-i or ny, j:]))
        ret = np.zeros_like(arr, dtype=corr.dtype)
        dny, dnx = src.shape - full_shape
        for i, j in product(range(dny + 1), range(dnx + 1)):
            ret[ymin + i:ymax - dny + i, xmin + j:xmax - dnx + j] = np.maximum(
                ret[ymin + i:ymax - dny + i, xmin + j:xmax - dnx + j], corr)
        return np.where(mask, ret, 0)

    def toggle_correlation_plot(self):
        """Toggle the correlation plot between :attr:`template` and :attr:`arr`
        """
        obj = self.data_obj
        if self._corr_plot is None:
            self._corr_plot = obj.ax.imshow(
                self._correlation,
                extent=obj._select_img.get_extent(),
                zorder=obj._select_img.zorder + 0.1)
            self._corr_cbar = obj.ax.figure.colorbar(self._corr_plot,
                                                     orientation='vertical')
            self._corr_cbar.set_label('Correlation')
        else:
            for a in [self._corr_cbar, self._corr_plot]:
                try:
                    a.remove()
                except ValueError:
                    pass
            del self._corr_plot, self._corr_cbar
        obj.draw_figure()

    def to_dock(self,
                main,
                title=None,
                position=None,
                docktype='df',
                *args,
                **kwargs):
        if position is None:
            position = main.dockWidgetArea(main.help_explorer.dock)
        connect = self.dock is None
        ret = super(PatternSelectionWidget, self).to_dock(main,
                                                          title,
                                                          position,
                                                          docktype=docktype,
                                                          *args,
                                                          **kwargs)
        if connect:
            self.dock.toggleViewAction().triggered.connect(self.maybe_tabify)
        return ret

    def maybe_tabify(self):
        main = self.dock.parent()
        if self.is_shown and main.dockWidgetArea(
                main.help_explorer.dock) == main.dockWidgetArea(self.dock):
            main.tabifyDockWidget(main.help_explorer.dock, self.dock)

    def cancel(self):
        if self.btn_select.isChecked():
            self.btn_select.setChecked(False)
            self.toggle_selection()
        self.close()

    def close(self):
        from psyplot_gui.main import mainwindow
        if self.selector is not None:
            self.selector.disconnect_events()
            for a in self.selector.artists:
                try:
                    a.remove()
                except ValueError:
                    pass
            self.data_obj.draw_figure()
            del self.selector
        if self._corr_plot is not None:
            self.toggle_correlation_plot()
        if self.key_press_cid is not None:
            self.data_obj.ax.figure.canvas.mpl_disconnect(self.key_press_cid)
        for attr in ['data_obj', 'arr', 'template', 'key_press_cid']:
            try:
                delattr(self, attr)
            except AttributeError:
                pass
        mainwindow.removeDockWidget(self.dock)
        return super(PatternSelectionWidget, self).close()