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)
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)
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()