Source code for straditize.widgets.plots

# -*- coding: utf-8 -*-
"""Plot control widgets for straditize

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/>.

"""
from itertools import chain
from collections import OrderedDict
from straditize.widgets import StraditizerControlBase
from psyplot_gui.compat.qtcompat import (
    QTableWidget, QCheckBox, QToolButton, QIcon, Qt, with_qt5, QtCore, QWidget,
    QHBoxLayout, QVBoxLayout, QPushButton, QTreeWidget, QTreeWidgetItem)
from psyplot_gui.common import get_icon

if with_qt5:
    from PyQt5.QtWidgets import QHeaderView
else:
    from PyQt4.QtGui import QHeaderView


[docs]class PlotControlTable(StraditizerControlBase, QTableWidget): """A widget to control the plots This control widget is a table to plot, remove and toggle the visiblity of visual diagnostics for the straditizer. It has two columns: the first to toggle the visibility of the plot, the second to plot and remove the matplotlib artists. The vertical header are the items in the corresponding :attr:`plot_funcs`, :attr:`can_be_plotted_funcs` and/or :attr:`hide_funcs`. Rows are added to this table using the :meth:`add_item` method which stores the plotting functions in the :attr:`plot_funcs` and the functions to hide the plot in the :attr:`hide_funcs`. Whether an item can be plotted or not depends on the results of the corresponding callable in the :attr:`can_be_plotted_funcs`.""" @property def widgets2disable(self): return list(filter(None, chain.from_iterable( (self.cellWidget(row, 0), self.cellWidget(row, 1)) for row in range(self.rowCount())))) col_lines = [] #: A mapping from plot identifier to a callable that returns ``True`` if #: the corresponding function in the :attr:`plot_funcs` mapping can be #: called can_be_plotted_funcs = {} #: A mapping from plot identifier to a callable to plot the corresponding #: artists plot_funcs = {} #: A mapping from plot identifier to a callable to hide the corresponding #: artists hide_funcs = {} def __init__(self, straditizer_widgets, *args, **kwargs): super(PlotControlTable, self).__init__(*args, **kwargs) self.init_straditizercontrol(straditizer_widgets) self.setColumnCount(2) self.setHorizontalHeaderLabels(['Visible', 'Plot/Remove']) self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) self.setEditTriggers(QTableWidget.NoEditTriggers) self.setSelectionMode(QTableWidget.NoSelection) self.setRowCount(0) self.get_artists_funcs = OrderedDict() self.hide_funcs = {} self.plot_funcs = {} self.can_be_plotted_funcs = {} self.add_item('Full image', self.get_straditizer_image) self.add_item('Reader color image', self.get_data_reader_color_image, self.plot_data_reader_color_image, self.remove_data_reader_color_image, self.can_plot_data_reader_color_image) self.add_item('Data background', self.get_data_reader_background) self.add_item('Binary image', self.get_data_reader_image) self.add_item('Diagram part', self.get_data_box, self.plot_data_box, self.remove_data_box, self.can_plot_data_box) self.add_item('Column starts', self.get_column_start_lines, self.plot_column_starts, self.remove_column_starts, self.can_plot_column_starts) self.add_item('Full digitized data', self.get_full_df_lines, self.plot_full_df, self.remove_full_df_plot, self.can_plot_full_df) self.add_item('Potential samples', self.get_potential_samples_lines, self.plot_potential_samples, self.remove_potential_samples_plot, self.can_plot_potential_samples) self.add_item('Samples', self.get_sample_hlines, self.plot_sample_hlines, self.remove_sample_hlines_plot, self.can_plot_sample_hlines) self.add_item('Reconstruction', self.get_samples_lines, self.plot_samples, self.remove_samples_plot, self.can_plot_samples) self.resizeColumnsToContents() self.adjust_height()
[docs] def draw_figs(self, artists): """Draw the figures of the given `artists` Parameters ---------- artists: list of :class:`matplotlib.artist.Artist` The artists to draw the canvas from""" for canvas in {a.axes.figure.canvas for a in artists}: canvas.draw_idle()
[docs] def add_item(self, what, get_artists, plot_func=None, remove_func=None, can_be_plotted=None): """Add a plot object to the table Parameters ---------- what: str The description of the plot object get_artists: function A function that takes no arguments and returns the artists plot_func: function, optional A function that takes no arguments and makes the plot. remove_func: function, optional A function that takes no arguments and removes the plot. can_be_plotted: function, optional A function that takes no argument and returns True if the plot can be made. """ def hide_or_show(checked): checked = checked is True or checked == Qt.Checked artist = None for artist in get_artists(): artist.set_visible(checked) if artist is not None: self.draw_figs(get_artists()) def trigger_plot_btn(): a = next(iter(get_artists()), None) if a is None: if can_be_plotted is None or can_be_plotted(): plot_func() cb.setChecked(True) btn.setIcon(QIcon(get_icon('invalid.png'))) btn.setToolTip('Remove ' + what) self.draw_figs(get_artists()) cb.setEnabled(True) else: fig = a.axes.figure figs = {a.axes.figure for a in get_artists()} remove_func() btn.setIcon(QIcon(get_icon('valid.png'))) btn.setToolTip('Show ' + what) for fig in figs: fig.canvas.draw_idle() cb.setEnabled(False) self.get_artists_funcs[what] = get_artists a = next(iter(get_artists()), None) cb = QCheckBox() cb.label = what self.hide_funcs[what] = hide_or_show cb.setChecked(Qt.Checked if a is not None and a.get_visible() else Qt.Unchecked) cb.stateChanged.connect(hide_or_show) row = self.rowCount() self.setRowCount(row + 1) self.setVerticalHeaderLabels(list(self.get_artists_funcs)) self.setCellWidget(row, 0, cb) if plot_func is not None: btn = QToolButton() btn.setIcon(QIcon(get_icon( ('in' if a is None else '') + 'valid.png'))) btn.clicked.connect(trigger_plot_btn) btn.setEnabled(can_be_plotted is None or can_be_plotted()) btn.setToolTip(('Remove ' if a else 'Show ') + what) self.can_be_plotted_funcs[what] = can_be_plotted btn.label = what self.setCellWidget(row, 1, btn)
# -------- straditizer data image --------------
[docs] def get_straditizer_image(self): """Get the :attr:`straditize.straditizer.Straditizer.plot_im`""" try: ret = self.straditizer.plot_im except AttributeError: ret = None return [ret] if ret else []
# -------- white data reader background ---------
[docs] def get_data_reader_background(self): """Get the :attr:`straditize.binary.DataReader.background`""" try: ret = self.straditizer.data_reader.background except AttributeError: return [] else: if self.straditizer.data_reader.magni_background is not None: return [ret, self.straditizer.data_reader.magni_background] else: return [ret]
# -------- binary data image --------------
[docs] def get_data_reader_image(self): """Get the :attr:`straditize.binary.DataReader.plot_im`""" try: ret = self.straditizer.data_reader.plot_im except AttributeError: return [] else: if self.straditizer.data_reader.magni_plot_im is not None: return [ret, self.straditizer.data_reader.magni_plot_im] else: return [ret]
# -------- reader color image --------------
[docs] def plot_data_reader_color_image(self): """Plot the data reader color image See Also -------- straditize.binary.DataReader.plot_color_image remove_data_reader_color_image can_plot_data_reader_color_image""" self.straditizer.data_reader.plot_color_image()
[docs] def remove_data_reader_color_image(self): """Remove the :attr:`straditize.binary.DataReader.color_plot_im` See Also -------- plot_data_reader_color_image""" for a in self.get_data_reader_color_image(): try: a.remove() except ValueError: pass try: del self.straditizer.data_reader.magni_color_plot_im except AttributeError: pass try: del self.straditizer.data_reader.color_plot_im except AttributeError: pass
[docs] def get_data_reader_color_image(self): """Get the :attr:`straditize.binary.DataReader.color_plot_im` See Also -------- plot_data_reader_color_image""" try: ret = self.straditizer.data_reader.color_plot_im except AttributeError: return [] else: if self.straditizer.data_reader.magni_color_plot_im is not None: return [ret, self.straditizer.data_reader.magni_color_plot_im] else: return [ret]
[docs] def can_plot_data_reader_color_image(self): """Test if the reader color image can be plotted See Also -------- plot_data_reader_color_image""" return (self.straditizer is not None and self.straditizer.data_reader is not None)
# -------- data box --------------
[docs] def plot_data_box(self): """Plot the data box around the diagram part See Also -------- straditize.straditizer.Straditizer.draw_data_box remove_data_box get_data_box can_plot_data_box""" self.straditizer.draw_data_box()
[docs] def remove_data_box(self): """Remove the box around the diagram part See Also -------- plot_data_box""" self.straditizer.remove_data_box()
[docs] def get_data_box(self): """Get the plotted :attr:`straditize.straditizer.Straditizer.data_box` See Also -------- plot_data_box """ try: ret = [self.straditizer.data_box] except AttributeError: ret = [None] try: ret.append(self.straditizer.magni_data_box) except AttributeError: pass return ret if ret else []
[docs] def can_plot_data_box(self): """Test whether the box around the diagram part can be plotted See Also -------- plot_data_box""" return (self.straditizer is not None and self.straditizer.data_xlim is not None and self.straditizer.data_ylim is not None)
# -------- plot full df ---------
[docs] def plot_full_df(self): """Plot the :attr:`~straditize.binary.DataReader.full_df` of the reader See Also -------- straditize.binary.DataReader.plot_full_df remove_full_df_plot get_full_df_lines can_plot_full_df """ stradi = self.straditizer stradi.data_reader.plot_full_df() if stradi.magni is not None: lines = stradi.data_reader.lines[:] stradi.data_reader.plot_full_df(ax=stradi.magni.ax) stradi.data_reader.lines += lines
[docs] def remove_full_df_plot(self): """Remove the plot of the full_df See Also -------- plot_full_df""" stradi = self.straditizer for l in stradi.data_reader.lines[:]: try: l.remove() except ValueError: pass stradi.data_reader.lines.clear()
[docs] def get_full_df_lines(self): """Get the artists of the full_df plot See Also -------- plot_full_df""" try: return self.straditizer.data_reader.lines except AttributeError: return []
[docs] def can_plot_full_df(self): """Test whether the full_df can be plotted See Also -------- plot_full_df""" return (self.straditizer is not None and self.straditizer.data_reader is not None and self.straditizer.data_reader.full_df is not None)
# --------- plot samples ------------
[docs] def plot_samples(self): """Plot the samples of the reader See Also -------- straditize.binary.DataReader.plot_samples remove_samples_plot get_samples_lines can_plot_samples """ stradi = self.straditizer stradi.data_reader.plot_samples() if stradi.magni is not None: lines = stradi.data_reader.sample_lines[:] stradi.data_reader.plot_samples(ax=stradi.magni.ax) stradi.data_reader.sample_lines += lines
[docs] def remove_samples_plot(self): """Remove the plotted samples See Also -------- plot_samples""" stradi = self.straditizer for l in stradi.data_reader.sample_lines[:]: try: l.remove() except ValueError: pass stradi.data_reader.sample_lines.clear()
[docs] def get_samples_lines(self): """Get the artists of the plotted samples See Also -------- plot_samples""" try: return self.straditizer.data_reader.sample_lines except AttributeError: return []
[docs] def can_plot_samples(self): """Test whether the samples can be plotted See Also -------- plot_samples""" return (self.straditizer is not None and self.straditizer.data_reader is not None and self.straditizer.data_reader.full_df is not None and self.straditizer.data_reader._sample_locs is not None)
# --------- plot horizontal sample lines ------------
[docs] def plot_sample_hlines(self): """Plot the horizontal sample lines of the reader See Also -------- straditize.binary.DataReader.plot_sample_hlines remove_sample_hlines_plot get_sample_hlines can_plot_sample_hlines """ stradi = self.straditizer stradi.data_reader.plot_sample_hlines() if stradi.magni is not None: lines = stradi.data_reader.sample_hlines[:] stradi.data_reader.plot_sample_hlines(ax=stradi.magni.ax) stradi.data_reader.sample_hlines += lines
[docs] def remove_sample_hlines_plot(self): """Remove the sample lines See Also -------- plot_sample_hlines""" stradi = self.straditizer for l in stradi.data_reader.sample_hlines[:]: try: l.remove() except ValueError: pass stradi.data_reader.sample_hlines.clear()
[docs] def get_sample_hlines(self): """Get the plotted the sample lines See Also -------- plot_sample_hlines""" try: return self.straditizer.data_reader.sample_hlines except AttributeError: return []
[docs] def can_plot_sample_hlines(self): """Test whether the sample lines can be plotted See Also -------- plot_sample_hlines""" return (self.straditizer is not None and self.straditizer.data_reader is not None and self.straditizer.data_reader.full_df is not None and self.straditizer.data_reader._sample_locs is not None)
# --------- plot potential samples ------------
[docs] def plot_potential_samples(self): """Highlight the regions with potential samples in the plot See Also -------- straditize.binary.DataReader.plot_potential_samples remove_potential_samples_plot get_potential_samples_lines can_plot_potential_samples""" stradi = self.straditizer stradi.data_reader.plot_potential_samples() if stradi.magni is not None: lines = stradi.data_reader.sample_ranges[:] stradi.data_reader.plot_potential_samples(ax=stradi.magni.ax) stradi.data_reader.sample_ranges += lines
[docs] def remove_potential_samples_plot(self): """Remove the plot of potential samples See Also -------- plot_potential_samples""" stradi = self.straditizer for l in stradi.data_reader.sample_ranges[:]: try: l.remove() except ValueError: pass stradi.data_reader.sample_ranges.clear()
[docs] def get_potential_samples_lines(self): """Get the artists of the plot of potential samples See Also -------- plot_potential_samples""" try: return self.straditizer.data_reader.sample_ranges except AttributeError: return []
[docs] def can_plot_potential_samples(self): """Test whether potential sample regions can be plotted See Also -------- plot_potential_samples""" return self.can_plot_full_df()
# -------- column starts ----------------
[docs] def plot_column_starts(self): """Plot horizontal lines for the column starts See Also -------- remove_column_starts get_column_start_lines can_plot_column_starts""" reader = self.straditizer.data_reader cols = reader._column_starts if reader.extent is not None: cols = cols + reader.extent[0] ymin, ymax = self.straditizer.data_ylim reader.__col_lines = [reader.ax.vlines(cols, ymax, ymin, color='r')] if reader.magni is not None: reader.__col_lines.append(reader.magni.ax.vlines(cols, ymax, ymin, color='r'))
[docs] def remove_column_starts(self): """Remove the plotted lines of the column starts See Also -------- plot_column_starts""" for l in self.straditizer.data_reader.__col_lines: try: l.remove() except ValueError: pass self.straditizer.data_reader.__col_lines.clear()
[docs] def get_column_start_lines(self): """Get the artists of the column starts See Also -------- plot_column_starts""" try: return self.straditizer.data_reader.__col_lines except AttributeError: return []
[docs] def can_plot_column_starts(self): """Test whether the column starts can be visualized See Also -------- plot_column_starts""" return (self.straditizer is not None and self.straditizer.data_reader is not None and self.straditizer.data_reader._column_starts is not None)
[docs] def refresh(self): for row, (what, func) in enumerate(self.get_artists_funcs.items()): a = next(iter(func()), None) cb = self.cellWidget(row, 0) cb.setEnabled(a is not None) if a is not None: cb.setChecked(Qt.Checked if a.get_visible() else Qt.Unchecked) btn = self.cellWidget(row, 1) if btn is not None: can_be_plotted = self.can_be_plotted_funcs[what] if can_be_plotted is None or can_be_plotted(): if a is not None: btn.setIcon(QIcon(get_icon('invalid.png'))) btn.setToolTip('Remove ' + what) else: btn.setIcon(QIcon(get_icon('valid.png'))) btn.setToolTip('Show ' + what) btn.setEnabled(True) else: btn.setEnabled(False)
[docs] def should_be_enabled(self, w): get_artists = self.get_artists_funcs[w.label] row = list(self.get_artists_funcs).index(w.label) if w is self.cellWidget(row, 0): if next(iter(get_artists()), None) is not None: return True elif w is self.cellWidget(row, 1): can_plot_func = self.can_be_plotted_funcs[w.label] if can_plot_func is None or can_plot_func(): return True return False
[docs] def enable_or_disable_widgets(self, b): """b is ignored and is always set to True""" super(PlotControlTable, self).enable_or_disable_widgets(False)
[docs] def adjust_height(self): header_height = self.horizontalHeader().height() h = self.rowHeight(0) * self.rowCount() + header_height self.setMaximumHeight(h) self.setMinimumHeight(h)
[docs] def sizeHint(self): header = self.horizontalHeader().sizeHint().height() s = super(PlotControlTable, self).sizeHint() return QtCore.QSize(s.width(), self.rowHeight(0) * self.rowCount() + header)
[docs]class ResultsPlot(StraditizerControlBase): """A widget for plotting the final results This widgets contains a QPushButton :attr:`btn_plot` to plot the results using the :meth:`straditize.binary.DataReader.plot_results` method""" #: The QPushButton to call the :meth:`plot_results` method btn_plot = None #: A QCheckBox whether x- and y-axis should be translated from pixel to #: data units cb_transformed = None #: A QCheckBox whether the samples or the full digitized data shall be #: plotted cb_final = None def __init__(self, straditizer_widgets): self.init_straditizercontrol(straditizer_widgets) self.btn_plot = QPushButton("Plot results") self.cb_final = QCheckBox("Samples") self.cb_final.setToolTip( "Create the diagram based on the samples only, not on the full " "digized data") self.cb_final.setChecked(True) self.cb_final.setEnabled(False) self.cb_transformed = QCheckBox("Translated") self.cb_transformed.setToolTip( "Use the x-axis and y-axis translation") self.cb_transformed.setChecked(True) self.cb_transformed.setEnabled(False) self.btn_plot.clicked.connect(self.plot_results)
[docs] def setup_children(self, item): tree = self.straditizer_widgets.tree tree.setItemWidget(item, 0, self.btn_plot) child = QTreeWidgetItem(0) item.addChild(child) widget = QWidget() vbox = QVBoxLayout() vbox.addWidget(self.cb_final) vbox.addWidget(self.cb_transformed) widget.setLayout(vbox) tree.setItemWidget(child, 0, widget)
[docs] def refresh(self): try: self.straditizer.yaxis_px self.straditizer.data_reader.xaxis_px except (AttributeError, ValueError): self.cb_transformed.setEnabled(False) else: self.cb_transformed.setEnabled(True) try: assert (self.straditizer.data_reader._sample_locs is not None and len(self.straditizer.data_reader._sample_locs)) except (AssertionError, AttributeError): self.cb_final.setEnabled(False) else: self.cb_final.setEnabled(True) try: self.btn_plot.setEnabled( self.straditizer.data_reader._full_df is not None) except AttributeError: self.btn_plot.setEnabled(False)
[docs] def plot_results(self): """Plot the results What is plotted depends on the :attr:`cb_transformed` and the :attr:`cb_final` :attr:`cb_transformed` and :attr:`cb_final` are checked Plot the :attr:`straditize.straditizer.Straditizer.final_df` :attr:`cb_transformed` is checked but not :attr:`cb_final` Plot the :attr:`straditize.straditizer.Straditizer.full_df` :attr:`cb_transformed` is not checked but :attr:`cb_final` Plot the :attr:`straditize.binary.DataReader.sample_locs` :attr:`cb_transformed` and :attr:`cb_final` are both not checked Plot the :attr:`straditize.binary.DataReader.full_df`""" transformed = self.cb_transformed.isEnabled() and \ self.cb_transformed.isChecked() if self.cb_final.isEnabled() and self.cb_final.isChecked(): df = self.straditizer.final_df if transformed else \ self.straditizer.data_reader.sample_locs else: df = self.straditizer.full_df if transformed else \ self.straditizer.data_reader._full_df return self.straditizer.data_reader.plot_results( df, transformed=transformed)
[docs]class PlotControl(StraditizerControlBase, QWidget): """A widget for controlling the plot This widgets holds a :class:`PlotControlTable` to display visual diagnostics in the plot. Additionally it contains zoom buttons (:attr:`btn_view_global` and :attr:`btn_view_data`) and a widget to plot the results (:attr:`results_plot`)""" #: A :class:`PlotControlTable` to display visual diagnostics table = None #: A button to zoom out to the entire stratigraphic diagram #: (see :meth:`zoom_global`) btn_view_global = None #: A button to zoom to the data #: (see :meth:`zoom_data`) btn_view_data = None #: A :class:`ResultsPlot` to plot the digitized data in a new diagram results_plot = None def __init__(self, straditizer_widgets, item, *args, **kwargs): super(PlotControl, self).__init__(*args, **kwargs) self.btn_view_global = QPushButton('Zoom out') self.btn_view_data = QPushButton('Zoom to data') self.table = PlotControlTable(straditizer_widgets) self.results_plot = ResultsPlot(straditizer_widgets) self.init_straditizercontrol(straditizer_widgets, item) # --------------------------------------------------------------------- # ------------------------------ Layout ------------------------------- # --------------------------------------------------------------------- hbox = QHBoxLayout() hbox.addWidget(self.btn_view_global) hbox.addWidget(self.btn_view_data) vbox = QVBoxLayout() vbox.addLayout(hbox) vbox.addWidget(self.table) self.setLayout(vbox) # --------------------------------------------------------------------- # --------------------------- Connections ----------------------------- # --------------------------------------------------------------------- self.btn_view_global.clicked.connect(self.zoom_global) self.btn_view_data.clicked.connect(self.zoom_data)
[docs] def setup_children(self, item): super().setup_children(item) child = QTreeWidgetItem(0) item.addChild(child) self.results_plot.setup_children(child)
[docs] def zoom_global(self): """Zoom out to the full straditgraphic diagram See Also -------- straditize.straditizer.Straditizer.show_full_image""" self.straditizer.show_full_image() self.straditizer.draw_figure()
[docs] def zoom_data(self): """Zoom to the data part See Also -------- straditize.straditizer.Straditizer.show_data_diagram""" self.straditizer.show_data_diagram() self.straditizer.draw_figure()
[docs] def refresh(self): self.table.refresh() self.results_plot.refresh() if self.straditizer is None: self.btn_view_global.setEnabled(False) self.btn_view_data.setEnabled(False) else: self.btn_view_global.setEnabled(True) self.btn_view_data.setEnabled( self.straditizer.data_xlim is not None and self.straditizer.data_ylim is not None)