# -*- coding: utf-8 -*-
"""The tutorial of straditize
This module contains a guided tour to get started with straditize
**Disclaimer**
Copyright (C) 2018-2019 Philipp S. Sommer
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
import os
import os.path as osp
import shutil
import glob
from itertools import chain
import numpy as np
from straditize.widgets import StraditizerControlBase, get_icon, get_doc_file
import straditize.cross_mark as cm
from PyQt5 import QtWidgets, QtCore, QtGui
from psyplot_gui.common import get_icon as get_psy_icon, DockMixin
from psyplot_gui.help_explorer import UrlHelp
import pandas as pd
[docs]class TutorialDocs(UrlHelp, DockMixin):
"""A documentation viewer for the tutorial docs
This viewer is accessible through the :attr:`Tutorial.tutorial_docs`
attribute and shows the :attr:`~TutorialPage.src_file` for the
tutorial :attr:`~Tutorial.pages`"""
dock_position = QtCore.Qt.RightDockWidgetArea
title = 'Straditize tutorial'
def __init__(self, *args, **kwargs):
from psyplot_gui.main import mainwindow
super().__init__(*args, **kwargs)
self.bt_connect_console.setChecked(False)
self.bt_lock.setChecked(False)
self._orig_bt_url_lock = self.bt_url_lock
self.bt_url_lock = mainwindow.help_explorer.viewers[
'HTML help'].bt_url_lock
self._orig_bt_url_lock.setVisible(False)
self.bt_connect_console.setVisible(False)
self.bt_lock.setVisible(False)
self.bt_url_menus.setVisible(False)
[docs]class TutorialNavigation(QtWidgets.QWidget):
"""A widget for navigating through the tutorial. It has a button to go
to the previous step and to go to the next step. Furthermore it has a
progressbar implemented a `hint` button"""
#: Signal that is emitted, when the step changes. The first integer is the
#: old step, the second one the current step
step_changed = QtCore.pyqtSignal(int, int)
#: Signal that is emitted if the hint of the current step is requested
hint_requested = QtCore.pyqtSignal(int)
#: Signal that is emitted, if the step is skipped
skipped = QtCore.pyqtSignal(int)
#: The current step in the tutorial
current_step = 0
def __init__(self, nsteps, validate, *args, **kwargs):
"""
Parameters
----------
nsteps: int
The total number of steps in the :class:`Tutorial`
validate: callable
A callable that takes the :attr:`current_step` as an argument and
returns a :class:`bool` whether the current step is valid and
finished, or not
"""
super().__init__(*args, **kwargs)
self.enabled = True
self.nsteps = nsteps
self.validate = validate
self.lbl_progress = QtWidgets.QLabel()
self.progress_bar = QtWidgets.QProgressBar()
self.progress_bar.setMinimum(1)
self.progress_bar.setMaximum(nsteps + 1)
self.btn_prev = QtWidgets.QPushButton('Back')
self.btn_next = QtWidgets.QPushButton('Next')
self.btn_skip = QtWidgets.QPushButton('Skip')
self.btn_hint = QtWidgets.QPushButton('Hint')
self.btn_info = QtWidgets.QToolButton()
self.btn_info.setIcon(QtGui.QIcon(get_psy_icon('info.png')))
self.set_current_step(0)
# ---------------------------------------------------------------------
# --------------------------- Layouts ---------------------------------
# ---------------------------------------------------------------------
layout = QtWidgets.QVBoxLayout()
hbox = QtWidgets.QHBoxLayout()
hbox.addWidget(self.lbl_progress)
hbox.addStretch(0)
hbox.addWidget(self.btn_info)
layout.addLayout(hbox)
layout.addWidget(self.progress_bar)
button_box = QtWidgets.QHBoxLayout()
button_box.addWidget(self.btn_prev)
button_box.addStretch(0)
button_box.addWidget(self.btn_hint)
button_box.addWidget(self.btn_skip)
button_box.addStretch(0)
button_box.addWidget(self.btn_next)
layout.addLayout(button_box)
self.setLayout(layout)
# ---------------------------------------------------------------------
# --------------------------- Connections -----------------------------
# ---------------------------------------------------------------------
self.btn_prev.clicked.connect(self.goto_prev_step)
self.btn_next.clicked.connect(self.goto_next_step)
self.btn_hint.clicked.connect(self.display_hint)
self.btn_skip.clicked.connect(self.skip)
self.btn_info.clicked.connect(self.show_info)
[docs] def setEnabled(self, enable):
"""Enable or disable the navigation buttons
Parameters
----------
enable: bool
Whether to enable or disable the buttons"""
for w in [self.btn_prev, self.btn_next, self.btn_skip, self.btn_info]:
w.setEnabled(enable)
if enable:
self.maybe_enable_widgets()
self.enabled = enable
[docs] def show_info(self):
"""Trigger the :attr:`step_changed` signal with the current step"""
self.step_changed.emit(self.current_step, self.current_step)
[docs] def display_hint(self):
"""Trigger the :attr:`hint_requested` signal with the current step"""
self.hint_requested.emit(self.current_step)
[docs] def set_current_step(self, i):
"""Change the :attr:`current_step`
Parameters
----------
i: int
The :attr:`current_step` to switch to"""
self.current_step = i
self.progress_bar.setValue(i)
self.btn_next.setText('Next')
if i == 0:
self.lbl_progress.setText('Start by clicking the %s button' % (
self.btn_next.text()))
elif i < self.nsteps + 1:
self.lbl_progress.setText('Step %i/%i' % (i, self.nsteps))
else:
self.progress_bar.setValue(i+1)
self.lbl_progress.setText('Done!')
self.btn_next.setText('Done')
self.maybe_enable_widgets()
[docs] def goto_next_step(self):
"""Increase the :attr:`current_step` by one"""
if self.validate(self.current_step):
if self.current_step <= self.nsteps:
self.set_current_step(self.current_step + 1)
self.step_changed.emit(self.current_step - 1,
self.current_step)
else:
self.set_current_step(self.current_step)
[docs] def goto_prev_step(self):
"""Decrease the :attr:`current_step` by one"""
if self.current_step > 0:
self.set_current_step(self.current_step - 1)
self.step_changed.emit(self.current_step + 1, self.current_step)
[docs] def skip(self):
"""Skip the :attr:`current_step` and emit the :attr:`skipped` signal
"""
self.skipped.emit(self.current_step)
self.goto_next_step()
[docs]class TutorialPage(object):
"""A base class for the tutorial pages
Subclasses show implement the :meth:`show_hint` method and the
:meth:`is_finished` property"""
#: The source directory for the docs
src_dir = osp.join(osp.dirname(__file__), 'beginner')
#: The basename of the stratigraphic diagram image for this tutorial
src_base = 'beginner-tutorial.png'
#: The complete path to the of the stratigraphic diagram image for this
#: tutorial
src_file = osp.join(src_dir, src_base)
#: str. The tooltip that has been shown. This attribute is mainly for
#: testing purposes
_last_tooltip_shown = None
def __init__(self, filename, tutorial):
"""
Parameters
----------
filename: str
The basename (without ending) of the RST file corresponding to this
tutorial page
tutorial: Tutorial
The tutorial instance"""
self.filename = filename
self.tutorial = tutorial
self.straditizer_widgets = self.tutorial.straditizer_widgets
[docs] def show_hint(self):
"""Show a hint to the user"""
pass
[docs] def activate(self):
"""Method that is called, when the page is activated"""
pass
[docs] def deactivate(self):
"""Method that is called, when the page is deactivated"""
pass
[docs] def hint(self):
"""A method that should display a hint to the user"""
btn = self.tutorial.navigation.btn_next
self.show_tooltip_at_widget(
"Click the %r button for the next step" % btn.text(), btn)
[docs] def skip(self):
"""Skip the steps in this page"""
pass
[docs] def refresh(self):
pass
@property
def is_finished(self):
"""Boolean that is True, if the steps are all finished"""
return True
[docs] def lock_viewer(self, lock):
"""Set or unset the url lock of the HTML viewer"""
try:
self.tutorial.tutorial_docs.bt_lock.setChecked(lock)
except AttributeError:
pass
[docs] def show(self):
"""Show the page and browse the :attr:`filename` in the tutorial docs
"""
try:
self.lock_viewer(False)
self.tutorial.tutorial_docs.browse(self.filename)
self.lock_viewer(True)
except AttributeError:
with open(osp.join(self.src_dir, self.filename + '.rst')) as f:
rst = f.read()
self.tutorial.tutorial_docs.show_rst(rst, self.filename)
[docs] def show_tooltip_at_widget(self, tooltip, widget, timeout=20000):
"""Show a tooltip close to a widget
Parameters
----------
tooltip: str
The tooltip to display
widget: QWidget
The widget that should be close to the tooltip
timeout: int
The time that the tool tip shall be displayed, in milliseconds"""
self._last_tooltip_shown = tooltip
QtWidgets.QToolTip.showText(
widget.parent().mapToGlobal(widget.pos()), tooltip, widget,
self.straditizer_widgets.rect(), timeout)
[docs] def show_tooltip_in_plot(self, tooltip, x, y, timeout=20000,
transform=None):
"""Show a tooltip in the matplotlib figure at the given coordinates
Parameters
----------
tooltip: str
The tooltip to display
x: float
The x-coordinate of where to display the tooltip
y: float
The y-coordinate of where to display the tooltip
timeout: int
The time that the tool tip shall be displayed, in milliseconds
transform: matplotlib transformation
The matplotlib transformation to use. If None, the
:attr:`self.stradi.ax.transData` transformation is used and
`x` and `y` are expected to be in data coordinates"""
self._last_tooltip_shown = tooltip
stradi = self.straditizer_widgets.straditizer
fig = stradi.ax.figure
canvas = fig.canvas
# only implemented for PyQt backend
if not isinstance(canvas, QtWidgets.QWidget):
return
if transform is None:
transform = stradi.ax.transData
ax = stradi.ax
xl = sorted(ax.get_xlim())
yl = sorted(ax.get_ylim())
if xl[0] > x or xl[1] < x:
x = np.mean(xl)
if yl[0] > y or yl[1] < y:
y = np.mean(yl)
x, y = fig.transFigure.inverted().transform(
transform.transform([x, y]))
size = canvas.size()
height = size.height()
width = size.width()
point = canvas.mapToGlobal(
QtCore.QPointF(x * width, height - y * height).toPoint())
QtWidgets.QToolTip.showText(
point, tooltip, canvas, canvas.rect(), timeout)
@property
def is_selecting(self):
"""True if the user clicked the btn_select_data button"""
return self.straditizer_widgets.apply_button.isEnabled()
[docs]class Tutorial(StraditizerControlBase, TutorialPage):
"""A tutorial for digitizing an area diagram"""
@property
def current_page(self):
"""The current page of the tutorial (corresponding to the
:attr:`TutorialNavigation.current_step`)"""
return self.pages[self.navigation.current_step]
@property
def load_image_step(self):
"""The number of the page that loads the diagram image (i.e. the index
of the :class:`LoadImage` instance in the :attr:`pages` attribute"""
return next(
(i for i, p in enumerate(self.pages) if isinstance(p, LoadImage)),
1)
#: A list of the :class:`TutorialPages` for this tutorial
pages = []
#: A :class:`TutorialDocs` to display the RST-files of the tutorial
tutorial_docs = None
#: A :class:`TutorialNavigation` to navigate through the tutorial
navigation = None
def __init__(self, straditizer_widgets):
from psyplot_gui.main import mainwindow
self.init_straditizercontrol(straditizer_widgets)
self.tutorial = self
self.central_widget_key = mainwindow.central_widget_key
self.tutorial_docs = TutorialDocs()
self.docs_key = ':'.join(
[__name__, self.__class__.__name__, 'tutorial'])
mainwindow.plugins[self.docs_key] = self.tutorial_docs
self.tutorial_docs.to_dock(mainwindow)
mainwindow.set_central_widget(self.tutorial_docs)
self.setup_tutorial_pages()
self.navigation = TutorialNavigation(len(self.pages) - 2,
self.validate_page)
layout = straditizer_widgets.layout()
layout.insertWidget(layout.count() - 1, self.navigation)
self.navigation.step_changed.connect(self.goto_page)
self.navigation.hint_requested.connect(self.display_hint)
self.navigation.skipped.connect(self.skip_page)
self.show()
[docs] def get_doc_files(self):
"""Get the rst files for the tutorial
Returns
-------
str
The path to the tutorial introduction file
list of str
The paths of the remaining tutorial files"""
files = glob.glob(osp.join(self.src_dir, '*.rst')) + \
glob.glob(osp.join(self.src_dir, '*.png')) + \
glob.glob(get_doc_file('*.rst')) + \
glob.glob(get_psy_icon('*.png')) + \
glob.glob(get_doc_file('*.png')) + \
glob.glob(get_icon('*.png'))
intro = files.pop(next(
i for i, f in enumerate(files)
if osp.basename(f).endswith('-tutorial-intro.rst')))
return intro, files
[docs] def show(self):
"""Show the documentation of the tutorial"""
intro, files = self.get_doc_files()
self.filename = osp.splitext(osp.basename(intro))[0]
with open(intro) as f:
rst = f.read()
name = osp.splitext(osp.basename(intro))[0]
self.lock_viewer(False)
self.tutorial_docs.show_rst(rst, name, files=files)
[docs] def setup_tutorial_pages(self):
"""Setup the :attr:`pages` attribute and initialize the tutorial pages
"""
self.pages = [
self,
ControlIntro('beginner-tutorial-control', self),
LoadImage('beginner-tutorial-load-image', self),
TutorialPage('beginner-tutorial-plot-navigation', self),
SelectDataPart('beginner-tutorial-select-data', self),
CreateReader('beginner-tutorial-create-reader', self),
SeparateColumns('beginner-tutorial-column-starts', self),
CleanImagePage('beginner-tutorial-clean-image', self),
DigitizePage('beginner-tutorial-digitize', self),
SamplesPage('beginner-tutorial-samples', self),
TranslateYAxis('beginner-tutorial-yaxis-translation', self),
TranslateXAxis('beginner-tutorial-xaxis-translation', self),
ColumnNames('beginner-tutorial-column-names', self),
FinishPage('beginner-tutorial-finish', self),
]
[docs] def refresh(self):
stradi = self._get_tutorial_stradi()
enable = stradi is None or self.straditizer is stradi
self.navigation.setEnabled(enable)
for page in self.pages[1:]:
page.refresh()
if (stradi is None and
self.navigation.current_step > self.load_image_step):
self.navigation.set_current_step(self.load_image_step)
def _get_tutorial_stradi(self):
"""Get the straditizer for this tutorial
Returns
-------
straditize.straditizer.Straditizer
The straditizer for this tutorial or None if it is closed"""
src_file = self.src_base
get_attr = self.straditizer_widgets.get_attr
for stradi in self.straditizer_widgets._straditizers:
if (get_attr(stradi, 'image_file') and
osp.basename(get_attr(stradi, 'image_file')) == src_file):
return stradi
[docs] def close(self):
"""Close the tutorial and remove the widgets"""
stradi = self._get_tutorial_stradi()
if stradi is not None:
self.straditizer_widgets._close_stradi(stradi)
if hasattr(self, 'navigation'):
self.straditizer_widgets.layout().removeWidget(self.navigation)
del self.straditizer_widgets.tutorial
for p in self.pages:
del p.tutorial, p.straditizer_widgets
self.pages.clear()
del self.pages
self.navigation.close()
self.tutorial_docs.close()
self.tutorial_docs.remove_plugin()
del self.navigation
del self.tutorial_docs
[docs] def goto_page(self, old, new):
"""Go to another page
Parameters
----------
old: int
The index of the old page in the :attr:`pages` attribute that is
subject to be deactivated (see :meth:`TutorialPage.deactivate`)
new: int
The index of the new page in the :attr:`pages` attribute that is
subject to be activated (see :meth:`TutorialPage.activate`)
See Also
--------
TutorialPage.activate
TutorialPage.deactivate"""
self.pages[old].deactivate()
page = self.pages[new]
page.show()
page.activate()
[docs] def skip_page(self, i):
"""Skip a tutorial page
Parameters
----------
i: int
The index of the page in the :attr:`pages` attribute
See Also
--------
TutorialPage.skip"""
self.pages[i].skip()
[docs] def display_hint(self, i):
"""Display the hint for a tutorial page
Parameters
----------
i: int
The index of the page in the :attr:`pages` attribute
See Also
--------
TutorialPage.hint"""
stradi = self._get_tutorial_stradi()
if stradi is None and i > self.load_image_step:
self.navigation.set_current_step(self.load_image_step)
elif stradi is not self.straditizer:
self.show_tooltip_at_widget(
"Select the straditizer for the <i>%s</i> diagram" % (
self.src_base), self.straditizer_widgets.stradi_combo)
else:
self.pages[i].hint()
[docs] def validate_page(self, i, silent=False):
"""Validate a tutorial page
Parameters
----------
i: int
The index of the page in the :attr:`pages` attribute
silent: bool
If True, and the page is not yet finished (see
:attr:`TutorialPage.is_finished`), the hint is displayed
Returns
-------
bool
True, if the page :attr:`~TutorialPage.is_finished`"""
ret = self.pages[i].is_finished
if not silent and not ret:
self.navigation.display_hint()
return ret
[docs]class ControlIntro(TutorialPage):
"""Tutorial page for the control"""
[docs] def activate(self):
dest = osp.join(self.tutorial.tutorial_docs.build_dir,
'_static', 'straditizer-control.png')
if not osp.exists(osp.dirname(dest)):
os.makedirs(osp.dirname(dest))
shutil.copyfile(
osp.join(self.src_dir, 'straditizer-control.png'), dest)
[docs]class LoadImage(TutorialPage):
"""TutorialPage for loading the straditizer image"""
[docs] def activate(self):
sw = self.straditizer_widgets
sw.menu_actions._dirname_to_use = self.src_dir
[docs] def deactivate(self):
self.straditizer_widgets.menu_actions._dirname_to_use = None
@property
def is_finished(self):
stradi = self.tutorial.straditizer
get_attr = self.straditizer_widgets.get_attr
return stradi is not None and (
get_attr(stradi, 'image_file') and osp.basename(
get_attr(stradi, 'image_file')) == self.src_base)
[docs] def hint(self):
if self.is_finished:
super().hint()
else:
self.show_tooltip_at_widget(
'Click here to load the <i>%s</i> image' % self.src_base,
self.straditizer_widgets.btn_open_stradi)
[docs] def skip(self):
self.straditizer_widgets.menu_actions.open_straditizer(self.src_file)
[docs]class SelectDataPart(TutorialPage):
"""TutorialPage for selecting the data part"""
#: The reference x- and y- limits
ref_lims = np.array([[258, 1803], [375, 1666]])
#: Valid ranges for xmin and xmax
valid_xlims = np.array([[221, 263], [1730, 1922]])
#: Valid ranges for ymin and ymax
valid_ylims = np.array([[346, 403], [1648, 1701]])
#: true if the data box should be removed at the end
remove_data_box = True
marks = []
@property
def is_finished(self):
stradi = self.tutorial.straditizer
refx, refy = self.ref_lims
if stradi.data_xlim is None:
return False
if not self.validate_corners():
return False
if (self.remove_data_box and
getattr(stradi, 'data_box', None) is not None):
return False
return True
[docs] def validate_corners(self):
stradi = self.tutorial.straditizer
for val, lim in zip(chain(stradi.data_xlim, stradi.data_ylim),
chain(self.valid_xlims, self.valid_ylims)):
if not lim.searchsorted(val) == 1:
return False
return True
[docs] def activate(self):
self.straditizer_widgets.digitizer.btn_select_data.clicked.connect(
self.clicked_correct_button)
[docs] def deactivate(self):
self.straditizer_widgets.digitizer.btn_select_data.clicked.disconnect(
self.clicked_correct_button)
correct_button_clicked = False
[docs] def display_reference_marks(self):
stradi = self.straditizer_widgets.straditizer
p1, p2 = zip(*self.ref_lims)
try: # interrupt the current timer
self.timer.stop()
except AttributeError:
pass
else:
for t in self.timer.callbacks:
t[0]()
self.marks = marks = [
cm.CrossMarks(p1, ax=stradi.ax, selectable=[], c='r', alpha=0.5),
cm.CrossMarks(p2, ax=stradi.ax, selectable=[], c='r', alpha=0.5)]
stradi.draw_figure()
self.timer = timer = stradi.ax.figure.canvas.new_timer(10000)
timer.single_shot = True
timer.add_callback(marks[0].remove)
timer.add_callback(marks[1].remove)
timer.add_callback(marks.clear)
timer.add_callback(stradi.draw_figure)
timer.add_callback(timer.stop)
timer.add_callback(lambda: delattr(self, 'timer'))
timer.start()
[docs] def skip(self):
stradi = self.straditizer_widgets.straditizer
if self.straditizer_widgets.cancel_button.isEnabled():
self.straditizer_widgets.cancel_button.click()
stradi.data_xlim, stradi.data_ylim = self.ref_lims
self.clicked_correct_button()
if getattr(stradi, 'data_box', None) is not None:
stradi.remove_data_box()
stradi.draw_figure()
self.straditizer_widgets.refresh()
[docs] def is_valid_x(self, x):
return np.array([self.valid_xlims[0].searchsorted(x) == 1,
self.valid_xlims[1].searchsorted(x) == 1])
[docs] def is_valid_y(self, y):
return np.array([self.valid_ylims[0].searchsorted(y) == 1,
self.valid_ylims[1].searchsorted(y) == 1])
[docs] def check_mark(self, mark):
valid = self.is_valid_x(mark.x)
if any(valid):
valid = self.is_valid_y(mark.y)
if not any(valid):
self.show_tooltip_in_plot(
"<pre>Leftclick</pre> the mark and drag it to one of "
"the diagram corners. e.g. x=%i, y=%i. Make sure, you exclude "
"the x- and y-axes but include the diagram.\n\n"
"One could also remove them later in the digitization, but it "
"is easier to exlude them now." % tuple(
self.ref_lims[:, 0]), *mark.pos)
return any(valid)
[docs] def hint(self):
sw = self.straditizer_widgets
stradi = sw.straditizer
refx, refy = self.ref_lims
xlim = stradi.data_xlim
ylim = stradi.data_ylim
btn = sw.digitizer.btn_select_data
pc_item = sw.plot_control_item
pc = sw.plot_control
if xlim is None and (
not self.correct_button_clicked or not self.is_selecting):
if self.is_selecting:
self.show_tooltip_at_widget(
"Wrong button clicked! Click cancel and use the "
"<i>%s</i> button." % btn.text(), sw.cancel_button)
else:
self.show_tooltip_at_widget(
"Click the <i>%s</i> button to start" % btn.text(),
sw.digitizer.btn_select_data)
elif self.is_selecting: # currently creating marks
marks = stradi.marks
if not len(marks):
self.show_tooltip_in_plot(
"<pre>Shift+leftclick</pre> on one of the corners in the "
"diagram to create a mark", refx[0], refy[0])
elif len(marks) == 1:
mark = marks[0]
if self.check_mark(mark):
# display the tooltip in the other corner
self.show_tooltip_in_plot(
"<pre>Shift+leftclick</pre> on the other corners in "
"the diagram to create a mark", *self.ref_lims[
np.c_[~self.is_valid_x(mark.x),
~self.is_valid_y(mark.y)].T])
elif len(marks) == 2:
if self.check_mark(marks[0]) and self.check_mark(marks[1]):
xlim = np.unique(np.ceil([m.x for m in marks]))
ylim = np.unique(np.ceil([m.y for m in marks]))
if len(xlim) == 1 or len(ylim) == 1:
self.show_tooltip_in_plot(
"<pre>Leftclick</pre> the marks and drag them "
"close to the diagram corners. e.g. x=%i, y=%i "
"and x=%i, y=%i" % (refx[0], refy[0], refx[1],
refy[1]),
*marks[1].pos)
else:
self.show_tooltip_at_widget(
"Done! Click the <i>Apply</i> button",
sw.apply_button)
return
self.display_reference_marks()
elif xlim is not None:
if not self.validate_corners():
self.show_tooltip_at_widget(
"You did not select the correct diagram corners. Click "
"the <i>%s</i> button to modify your "
"selection or the <i>Skip</i> button to proceed with the "
"next step" % btn.text(), sw.digitizer.btn_select_data)
elif (self.remove_data_box and
getattr(stradi, 'data_box', None) is not None):
if not pc_item.isExpanded():
sw.tree.scrollToItem(pc_item)
self.show_tooltip_at_widget(
"Expand the <i>%s</i> item by clicking on the arrow "
"to it's left" % pc_item.text(0),
sw.tree.itemWidget(pc_item, 1))
else:
row = list(pc.table.get_artists_funcs).index(
'Diagram part')
sw.tree.scrollToItem(sw.marker_control_item)
self.show_tooltip_at_widget(
"Click the cross to remove the red rectangle",
pc.table.cellWidget(row, 1))
else:
super().hint()
[docs]class CreateReader(TutorialPage):
"""The page for creating the reader"""
@property
def is_finished(self):
return self.straditizer_widgets.straditizer.data_reader is not None
[docs] def skip(self):
self.straditizer_widgets.straditizer.init_reader('area')
self.straditizer_widgets.refresh()
[docs] def hint(self):
if not self.is_finished:
sw = self.straditizer_widgets
btn = sw.digitizer.btn_init_reader
sw.tree.scrollToItem(sw.digitizer_item)
self.show_tooltip_at_widget(
"Click <i>%s</i> to initialize the reader for the diagram" % (
btn.text()), btn)
else:
super().hint()
[docs]class SeparateColumns(TutorialPage):
"""The page for separating the columns"""
ncols = 4
@property
def is_finished(self):
reader = self.straditizer_widgets.straditizer.data_reader
return (reader._column_starts is not None and
len(reader._column_starts) == self.ncols)
[docs] def skip(self):
reader = self.straditizer_widgets.straditizer.data_reader
reader.reset_column_starts()
reader._get_column_starts()
self.straditizer_widgets.refresh()
self.clicked_correct_button()
[docs] def activate(self):
self.straditizer_widgets.digitizer.btn_column_starts.clicked.connect(
self.clicked_correct_button)
[docs] def deactivate(self):
sw = self.straditizer_widgets
sw.digitizer.btn_column_starts.clicked.disconnect(
self.clicked_correct_button)
correct_button_clicked = False
[docs] def hint(self):
sw = self.straditizer_widgets
stradi = sw.straditizer
reader = stradi.data_reader
btn = sw.digitizer.btn_column_starts
starts = reader._column_starts
if starts is None and (
not self.correct_button_clicked or not self.is_selecting):
if self.is_selecting:
self.show_tooltip_at_widget(
"Wrong button clicked! Click cancel and use the "
"<i>%s</i> button." % btn.text(), sw.cancel_button)
else:
self.show_tooltip_at_widget(
"Click the <i>%s</i> button to start" % btn.text(), btn)
elif starts is not None and len(starts) != self.ncols:
if self.is_selecting:
self.show_tooltip_in_plot(
"There are 28 columns in the diagram. Select all or "
"cancel and hit the <i>Reset</i> button",
stradi.data_xlim.mean(), stradi.data_ylim.mean())
else:
self.show_tooltip_at_widget(
"The diagram has 28 columns, not %i. Hit the <i>%s</i> "
"button or modify the column starts." % (
len(starts), sw.digitizer.btn_reset_columns.text()),
sw.digitizer.btn_reset_columns)
elif self.is_selecting: # currently creating marks
self.show_tooltip_at_widget(
"Done! Click the <i>Apply</i> button",
sw.apply_button)
elif starts is not None:
super().hint()
[docs]class ColumnNames(TutorialPage):
"""The page for recognizing column names"""
select_names_button_clicked = False
#: The column names in the diagram
column_names = [
'Pinus', 'Juniperus', 'Quercus ilex-type', 'Chenopodiaceae']
@property
def is_finished(self):
sw = self.straditizer_widgets
reader = sw.straditizer.colnames_reader
return (
reader.column_names == self.column_names and
not sw.colnames_manager.btn_select_names.isChecked())
[docs] def skip(self):
self.straditizer_widgets.straditizer.colnames_reader.column_names = \
self.column_names
btn = self.straditizer_widgets.colnames_manager.btn_select_names
if btn.isChecked():
btn.click()
[docs] def activate(self):
sw = self.straditizer_widgets
sw.colnames_manager.btn_select_names.clicked.connect(
self.clicked_select_names_button)
[docs] def deactivate(self):
sw = self.straditizer_widgets
sw.colnames_manager.btn_select_names.clicked.disconnect(
self.clicked_select_names_button)
[docs] def hint(self):
reader = self.straditizer_widgets.straditizer.colnames_reader
sw = self.straditizer_widgets
btn = sw.colnames_manager.btn_select_names
rc = sw.col_names_item
is_finished = self.is_finished
if not is_finished and (not self.select_names_button_clicked or
not sw.colnames_manager.is_shown):
sw.tree.scrollToItem(rc)
if not rc.isExpanded():
self.show_tooltip_at_widget(
"Expand the <i>%s</i> item by clicking on the arrow to "
"it's left" % rc.text(0), sw.tree.itemWidget(rc, 1))
else:
self.show_tooltip_at_widget(
"Click the <i>%s</i> button" % btn.text(), btn)
elif not is_finished:
ncols = len(reader.column_bounds)
if reader.column_names == list(map(str, range(ncols))):
self.hint_for_start_editing()
elif reader.column_names != self.column_names:
i, (curr, ref) = next(
t for t in enumerate(zip(reader.column_names,
self.column_names))
if t[1][0] != t[1][1])
self.hint_for_wrong_name(i, curr, ref)
elif btn.isChecked():
sw.tree.scrollToItem(rc)
self.show_tooltip_at_widget(
"Click the <i>%s</i> button to finish" % btn.text(), btn)
else:
super().hint()
[docs] def hint_for_start_editing(self):
from straditize.colnames import tesserocr
if tesserocr is not None:
btn = self.straditizer_widgets.colnames_manager.btn_find
ocr = ' or click the <i>%s</i> button' % btn.text()
else:
ocr = ''
self.show_tooltip_at_widget(
'Edit the column names in the table%s. You can zoom into '
'the plot on the left using the `right` mouse button and '
'navigate using the `left` mouse button' % ocr,
self.straditizer_widgets.colnames_manager.colnames_table)
[docs] def hint_for_wrong_name(self, col, curr, ref):
"""Display a hint if a name is not correctly set"""
manager = self.straditizer_widgets.colnames_manager
if manager.current_col != col:
self.show_tooltip_at_widget(
"Column name of the %s column (column %i) is not correct "
"(%r != %r)!<br><br>"
"Select the column in the table and enter the correct "
"name or click the 'skip' button" % (ref, col, curr, ref),
manager.colnames_table)
else:
self.show_tooltip_at_widget(
"Enter the correct name (%r)" % ref, manager.colnames_table)
[docs]class CleanImagePage(TutorialPage):
"""Tutorial page to clean the diagram part"""
btn_remove_yaxes_clicked = btn_remove_xaxes_clicked = False
[docs] def skip(self):
stradi = self.straditizer_widgets.straditizer
reader = stradi.data_reader
reader.recognize_yaxes(remove=True)
reader.binary[:, 1797 - int(stradi.data_xlim[0]):] = 0
reader.recognize_xaxes(remove=True)
stradi.draw_figure()
self.clicked_btn_remove_xaxes()
self.clicked_btn_remove_yaxes()
self.straditizer_widgets.refresh()
@property
def is_finished(self):
stradi = self.straditizer_widgets.straditizer
reader = stradi.data_reader
if not len(reader.vline_locs) or not len(reader.hline_locs):
return False
elif (stradi.data_xlim[1] > 1797 and
reader.binary[:, 1799 - stradi.data_xlim[0]].any()):
return False
return True
[docs] def activate(self):
sw = self.straditizer_widgets
sw.digitizer.btn_remove_yaxes.clicked.connect(
self.clicked_btn_remove_yaxes)
sw.digitizer.btn_remove_xaxes.clicked.connect(
self.clicked_btn_remove_xaxes)
[docs] def deactivate(self):
sw = self.straditizer_widgets
sw.digitizer.btn_remove_yaxes.clicked.disconnect(
self.clicked_btn_remove_yaxes)
sw.digitizer.btn_remove_xaxes.clicked.disconnect(
self.clicked_btn_remove_xaxes)
[docs] def clicked_btn_remove_yaxes(self):
self.btn_remove_yaxes_clicked = True
[docs] def clicked_btn_remove_xaxes(self):
self.btn_remove_xaxes_clicked = True
[docs] def icon_to_bytes(self, icon):
buffer = QtCore.QBuffer()
buffer.open(QtCore.QIODevice.WriteOnly)
if isinstance(icon, str):
icon = QtGui.QIcon(get_icon(icon))
pixmap = icon.pixmap(10, 10)
pixmap.save(buffer, "PNG", quality=100)
image = bytes(buffer.data().toBase64()).decode()
return '<img src="data:image/png;base64,{}">'.format(image)
[docs] def hint(self):
sw = self.straditizer_widgets
stradi = sw.straditizer
reader = stradi.data_reader
btn_x = sw.digitizer.btn_remove_xaxes
btn_y = sw.digitizer.btn_remove_yaxes
rc = sw.digitizer.remove_child
rlc = sw.digitizer.remove_line_child
if not len(reader.vline_locs) or not len(reader.hline_locs):
if not rc.isExpanded():
sw.tree.scrollToItem(rc)
self.show_tooltip_at_widget(
"Expand the <i>%s</i> item by clicking on the arrow to "
"it's left" % rc.text(0), sw.tree.itemWidget(rc, 1))
elif (not self.btn_remove_xaxes_clicked and
not self.btn_remove_yaxes_clicked and self.is_selecting):
self.show_tooltip_at_widget(
"Wrong button clicked! Click cancel and use the "
"<i>%s</i> button." % btn_y.text(), sw.cancel_button)
elif self.is_selecting:
self.show_tooltip_at_widget(
"Done! Click the <i>Remove</i> button", sw.apply_button)
elif not self.btn_remove_yaxes_clicked:
sw.tree.scrollToItem(rlc)
self.show_tooltip_at_widget(
("Click the <i>%s</i> button to select the"
" y-axes") % btn_y.text(), btn_y)
elif not self.btn_remove_xaxes_clicked:
sw.tree.scrollToItem(rlc)
self.show_tooltip_at_widget(
("Click the <i>%s</i> button to select the"
" x-axes") % btn_x.text(), btn_x)
elif not self.is_finished:
if self.is_selecting and sw.apply_button.text() != 'Remove':
self.show_tooltip_at_widget(
"Wrong button clicked! Click cancel and use the "
"selection toolbar.", sw.selection_toolbar)
elif not self.is_selecting:
if sw.selection_toolbar.wand_type != 'cols':
wand_icon = sw.selection_toolbar.wand_action.icon()
self.show_tooltip_at_widget(
"Select the %s (column selection) mode in the %s "
"menu" % (self.icon_to_bytes('col_select.png'),
self.icon_to_bytes(wand_icon)),
sw.selection_toolbar)
elif not sw.selection_toolbar.wand_action.isChecked():
self.show_tooltip_at_widget(
"Enable the %s selection" % (
self.icon_to_bytes('col-select.png'), ),
sw.selection_toolbar)
elif sw.selection_toolbar.remove_select_action.isChecked():
self.show_tooltip_at_widget(
"Enable the %s or %s selection mode" % (
self.icon_to_bytes('new_selection.png'),
self.icon_to_bytes('add_select.png')),
sw.selection_toolbar)
else:
self.show_tooltip_in_plot(
"Drag a rectangle around the most right line",
1799, int(np.mean(stradi.ax.get_ylim())))
elif not reader.selected_part[
:, 1799 - stradi.data_xlim[0]].any():
self.show_tooltip_in_plot(
"Drag a rectangle around the most right line",
1799, int(np.mean(stradi.ax.get_ylim())))
else:
self.show_tooltip_at_widget(
"Click the <i>Remove</i> button to remove the line",
sw.apply_button)
else:
super().hint()
[docs]class DigitizePage(TutorialPage):
"""The page for digitizing the diagram"""
@property
def is_finished(self):
reader = self.straditizer_widgets.straditizer.data_reader
return reader._full_df is not None
[docs] def hint(self):
btn = self.straditizer_widgets.digitizer.btn_digitize
if not self.is_finished:
self.straditizer_widgets.tree.scrollToItem(
self.straditizer_widgets.digitizer.digitize_item)
self.show_tooltip_at_widget(
"Click the <i>%s</i> button" % btn.text(), btn)
else:
super().hint()
[docs] def skip(self):
self.straditizer_widgets.straditizer.data_reader.digitize()
self.straditizer_widgets.refresh()
[docs]class SamplesPage(TutorialPage):
"""The page for finding and editing the samples"""
@property
def is_finished(self):
return self.correct_button_clicked and not self.is_selecting
[docs] def activate(self):
self.straditizer_widgets.digitizer.btn_edit_samples.clicked.connect(
self.clicked_correct_button)
[docs] def deactivate(self):
sw = self.straditizer_widgets
sw.digitizer.btn_edit_samples.clicked.disconnect(
self.clicked_correct_button)
correct_button_clicked = False
[docs] def clicked_correct_button(self):
self.correct_button_clicked = True
[docs] def skip(self):
reader = self.straditizer_widgets.straditizer.data_reader
reader.reset_samples()
reader.add_samples(*reader.find_samples(max_len=8, pixel_tol=5))
self.clicked_correct_button()
self.straditizer_widgets.refresh()
[docs] def hint(self):
reader = self.straditizer_widgets.straditizer.data_reader
sw = self.straditizer_widgets
esc = sw.digitizer.edit_samples_child
btn_find = sw.digitizer.btn_find_samples
btn_edit = sw.digitizer.btn_edit_samples
if not esc.isExpanded():
sw.tree.scrollToItem(esc)
self.show_tooltip_at_widget(
"Expand the <i>%s</i> item by clicking on the arrow to it's "
"left" % esc.text(0), sw.tree.itemWidget(esc, 1))
elif reader._sample_locs is None or not len(reader._sample_locs):
sw.tree.scrollToItem(esc.child(0))
self.show_tooltip_at_widget(
"Click the <i>%s</i> button to automatically find the "
"samples." % btn_find.text(), btn_find)
elif not self.correct_button_clicked:
sw.tree.scrollToItem(esc.child(2))
self.show_tooltip_at_widget(
"Click the <i>%s</i> button to visualize and edit the "
"samples." % btn_edit.text(), btn_edit)
elif self.is_selecting:
self.show_tooltip_at_widget(
"Click the <i>%s</i> or <i>%s</i> button to stop the "
"editing." % (sw.apply_button.text(), sw.cancel_button.text()),
sw.apply_button)
else:
super().hint()
[docs]class TranslateYAxis(TutorialPage):
"""The tutorial page for translating the y-axis"""
@property
def is_finished(self):
return self.straditizer_widgets.straditizer.yaxis_data is not None
[docs] def skip(self):
self.clicked_correct_button()
self.straditizer_widgets.straditizer.yaxis_data = np.array([150, 450])
self.straditizer_widgets.straditizer._yaxis_px_orig = \
np.array([378, 1663])
self.straditizer_widgets.refresh()
[docs] def activate(self):
btn = self.straditizer_widgets.axes_translations.btn_marks_for_y
btn.clicked.connect(self.clicked_correct_button)
[docs] def deactivate(self):
btn = self.straditizer_widgets.axes_translations.btn_marks_for_y
btn.clicked.disconnect(self.clicked_correct_button)
correct_button_clicked = False
[docs] def hint(self):
sw = self.straditizer_widgets
item = sw.axes_translations_item
btn = sw.axes_translations.btn_marks_for_y
marks = sw.straditizer.marks or []
if self.is_finished:
super().hint()
elif not self.is_selecting and not item.isExpanded():
sw.tree.scrollToItem(item)
self.show_tooltip_at_widget(
"Expand the <i>%s</i> item by clicking on the arrow to it's "
"left" % item.text(0), sw.tree.itemWidget(item, 1))
elif not self.correct_button_clicked:
if self.is_selecting:
self.show_tooltip_at_widget(
"Wrong button clicked! Click cancel and use the "
"<i>%s</i> button." % btn.text(), sw.cancel_button)
else:
sw.tree.scrollToItem(item.child(1))
self.show_tooltip_at_widget(
"Click the <i>%s</i> button to start." % btn.text(), btn)
elif len(marks) < 2:
self.show_tooltip_in_plot(
"<pre>Shift+Leftclick</pre> on a point on the vertical axis "
"to enter %s y-value" % (
"another" if len(marks) else "the corresponding"),
260, np.mean(sw.straditizer.ax.get_ylim()))
elif len(marks) == 2:
self.show_tooltip_at_widget(
"Click the <i>%s</i> button to stop the editing." % (
sw.apply_button.text()), sw.apply_button)
[docs]class TranslateXAxis(TutorialPage):
"""The tutorial page for translating the y-axis"""
@property
def is_finished(self):
return self.straditizer_widgets.straditizer.data_reader.xaxis_data is \
not None
[docs] def skip(self):
self.clicked_correct_button()
self.straditizer_widgets.straditizer.data_reader.xaxis_data = \
np.array([0, 70])
self.straditizer_widgets.straditizer.data_reader._xaxis_px_orig = \
np.array([258, 700])
self.straditizer_widgets.refresh()
[docs] def activate(self):
btn = self.straditizer_widgets.axes_translations.btn_marks_for_x
btn.clicked.connect(self.clicked_correct_button)
[docs] def deactivate(self):
btn = self.straditizer_widgets.axes_translations.btn_marks_for_x
btn.clicked.disconnect(self.clicked_correct_button)
correct_button_clicked = False
[docs] def hint(self):
sw = self.straditizer_widgets
item = sw.axes_translations_item
btn = sw.axes_translations.btn_marks_for_x
marks = sw.straditizer.marks or []
if self.is_finished:
super().hint()
elif not self.is_selecting and not item.isExpanded():
sw.tree.scrollToItem(item)
self.show_tooltip_at_widget(
"Expand the <i>%s</i> item by clicking on the arrow to it's "
"left" % item.text(0), sw.tree.itemWidget(item, 1))
elif not self.correct_button_clicked:
if self.is_selecting:
self.show_tooltip_at_widget(
"Wrong button clicked! Click cancel and use the "
"<i>%s</i> button." % btn.text(), sw.cancel_button)
else:
sw.tree.scrollToItem(item.child(0))
self.show_tooltip_at_widget(
"Click the <i>%s</i> button to start." % btn.text(), btn)
elif len(marks) < 2:
self.show_tooltip_in_plot(
"<pre>Shift+Leftclick</pre> on a point on the horizontal axis "
"to enter %s x-value" % (
"another" if len(marks) else "the corresponding"),
np.mean(sw.straditizer.ax.get_xlim()), 1662)
elif len(marks) == 2:
self.show_tooltip_at_widget(
"Click the <i>%s</i> button to stop the editing." % (
sw.apply_button.text()), sw.apply_button)
[docs]class FinishPage(TutorialPage):
"""The last page of the tutorial"""
[docs] def show(self):
"""Reimplemented to release the help explorer lock"""
super().show()
self.lock_viewer(False)