Source code for guiqwt.tools
# -*- coding: utf-8 -*-
#
# Copyright © 2009-2010 CEA
# Pierre Raybaut
# Licensed under the terms of the CECILL License
# (see guiqwt/__init__.py for details)
# pylint: disable=C0103
"""
guiqwt.tools
------------
The `tools` module provides a collection of `plot tools` :
* :py:class:`guiqwt.tools.RectZoomTool`
* :py:class:`guiqwt.tools.SelectTool`
* :py:class:`guiqwt.tools.SelectPointTool`
* :py:class:`guiqwt.tools.MultiLineTool`
* :py:class:`guiqwt.tools.FreeFormTool`
* :py:class:`guiqwt.tools.LabelTool`
* :py:class:`guiqwt.tools.RectangleTool`
* :py:class:`guiqwt.tools.PointTool`
* :py:class:`guiqwt.tools.SegmentTool`
* :py:class:`guiqwt.tools.CircleTool`
* :py:class:`guiqwt.tools.EllipseTool`
* :py:class:`guiqwt.tools.PlaceAxesTool`
* :py:class:`guiqwt.tools.AnnotatedRectangleTool`
* :py:class:`guiqwt.tools.AnnotatedCircleTool`
* :py:class:`guiqwt.tools.AnnotatedEllipseTool`
* :py:class:`guiqwt.tools.AnnotatedPointTool`
* :py:class:`guiqwt.tools.AnnotatedSegmentTool`
* :py:class:`guiqwt.tools.HRangeTool`
* :py:class:`guiqwt.tools.DummySeparatorTool`
* :py:class:`guiqwt.tools.AntiAliasingTool`
* :py:class:`guiqwt.tools.DisplayCoordsTool`
* :py:class:`guiqwt.tools.ReverseYAxisTool`
* :py:class:`guiqwt.tools.AspectRatioTool`
* :py:class:`guiqwt.tools.PanelTool`
* :py:class:`guiqwt.tools.ItemListPanelTool`
* :py:class:`guiqwt.tools.ContrastPanelTool`
* :py:class:`guiqwt.tools.ColormapTool`
* :py:class:`guiqwt.tools.XCSPanelTool`
* :py:class:`guiqwt.tools.YCSPanelTool`
* :py:class:`guiqwt.tools.CrossSectionTool`
* :py:class:`guiqwt.tools.AverageCrossSectionTool`
* :py:class:`guiqwt.tools.SaveAsTool`
* :py:class:`guiqwt.tools.CopyToClipboardTool`
* :py:class:`guiqwt.tools.OpenFileTool`
* :py:class:`guiqwt.tools.OpenImageTool`
* :py:class:`guiqwt.tools.SnapshotTool`
* :py:class:`guiqwt.tools.PrintTool`
* :py:class:`guiqwt.tools.SaveItemsTool`
* :py:class:`guiqwt.tools.LoadItemsTool`
* :py:class:`guiqwt.tools.AxisScaleTool`
* :py:class:`guiqwt.tools.HelpTool`
* :py:class:`guiqwt.tools.ExportItemDataTool`
* :py:class:`guiqwt.tools.EditItemDataTool`
* :py:class:`guiqwt.tools.ItemCenterTool`
* :py:class:`guiqwt.tools.DeleteItemTool`
A `plot tool` is an object providing various features to a plotting widget
(:py:class:`guiqwt.curve.CurvePlot` or :py:class:`guiqwt.image.ImagePlot`):
buttons, menus, selection tools, image I/O tools, etc.
To make it work, a tool has to be registered to the plotting widget's manager,
i.e. an instance of the :py:class:`guiqwt.plot.PlotManager` class (see
the :py:mod:`guiqwt.plot` module for more details on the procedure).
The `CurvePlot` and `ImagePlot` widgets do not provide any `PlotManager`: the
manager has to be created separately. On the contrary, the ready-to-use widgets
:py:class:`guiqwt.plot.CurveWidget` and :py:class:`guiqwt.plot.ImageWidget`
are higher-level plotting widgets with integrated manager, tools and panels.
.. seealso::
Module :py:mod:`guiqwt.plot`
Module providing ready-to-use curve and image plotting widgets and
dialog boxes
Module :py:mod:`guiqwt.curve`
Module providing curve-related plot items and plotting widgets
Module :py:mod:`guiqwt.image`
Module providing image-related plot items and plotting widgets
Example
~~~~~~~
The following example add all the existing tools to an `ImageWidget` object
for testing purpose:
.. literalinclude:: /../guiqwt/tests/image_plot_tools.py
:start-after: SHOW
:end-before: Workaround for Sphinx v0.6 bug: empty 'end-before' directive
.. image:: /images/screenshots/image_plot_tools.png
Reference
~~~~~~~~~
.. autoclass:: RectZoomTool
:members:
:inherited-members:
.. autoclass:: SelectTool
:members:
:inherited-members:
.. autoclass:: SelectPointTool
:members:
:inherited-members:
.. autoclass:: MultiLineTool
:members:
:inherited-members:
.. autoclass:: FreeFormTool
:members:
:inherited-members:
.. autoclass:: LabelTool
:members:
:inherited-members:
.. autoclass:: RectangleTool
:members:
:inherited-members:
.. autoclass:: PointTool
:members:
:inherited-members:
.. autoclass:: SegmentTool
:members:
:inherited-members:
.. autoclass:: CircleTool
:members:
:inherited-members:
.. autoclass:: EllipseTool
:members:
:inherited-members:
.. autoclass:: PlaceAxesTool
:members:
:inherited-members:
.. autoclass:: AnnotatedRectangleTool
:members:
:inherited-members:
.. autoclass:: AnnotatedCircleTool
:members:
:inherited-members:
.. autoclass:: AnnotatedEllipseTool
:members:
:inherited-members:
.. autoclass:: AnnotatedPointTool
:members:
:inherited-members:
.. autoclass:: AnnotatedSegmentTool
:members:
:inherited-members:
.. autoclass:: HRangeTool
:members:
:inherited-members:
.. autoclass:: DummySeparatorTool
:members:
:inherited-members:
.. autoclass:: AntiAliasingTool
:members:
:inherited-members:
.. autoclass:: DisplayCoordsTool
:members:
:inherited-members:
.. autoclass:: ReverseYAxisTool
:members:
:inherited-members:
.. autoclass:: AspectRatioTool
:members:
:inherited-members:
.. autoclass:: PanelTool
:members:
:inherited-members:
.. autoclass:: ItemListPanelTool
:members:
:inherited-members:
.. autoclass:: ContrastPanelTool
:members:
:inherited-members:
.. autoclass:: ColormapTool
:members:
:inherited-members:
.. autoclass:: XCSPanelTool
:members:
:inherited-members:
.. autoclass:: YCSPanelTool
:members:
:inherited-members:
.. autoclass:: CrossSectionTool
:members:
:inherited-members:
.. autoclass:: AverageCrossSectionTool
:members:
:inherited-members:
.. autoclass:: SaveAsTool
:members:
:inherited-members:
.. autoclass:: CopyToClipboardTool
:members:
:inherited-members:
.. autoclass:: OpenFileTool
:members:
:inherited-members:
.. autoclass:: OpenImageTool
:members:
:inherited-members:
.. autoclass:: SnapshotTool
:members:
:inherited-members:
.. autoclass:: PrintTool
:members:
:inherited-members:
.. autoclass:: SaveItemsTool
:members:
:inherited-members:
.. autoclass:: LoadItemsTool
:members:
:inherited-members:
.. autoclass:: AxisScaleTool
:members:
:inherited-members:
.. autoclass:: HelpTool
:members:
:inherited-members:
.. autoclass:: ExportItemDataTool
:members:
:inherited-members:
.. autoclass:: EditItemDataTool
:members:
:inherited-members:
.. autoclass:: ItemCenterTool
:members:
:inherited-members:
.. autoclass:: DeleteItemTool
:members:
:inherited-members:
"""
# TODO: z(long-terme) à partir d'une sélection rectangulaire sur une image
# afficher un ArrayEditor montrant les valeurs de la zone sélectionnée
import sys
import numpy as np
import weakref
import os.path as osp
from qtpy.QtCore import Qt, QObject, QPointF, Signal
from qtpy.QtGui import QKeySequence
from qtpy.QtPrintSupport import QPrinter, QPrintDialog
from qtpy.QtWidgets import (
QMenu,
QActionGroup,
QMessageBox,
QAction,
QToolButton,
)
from qtpy.compat import getsavefilename, getopenfilename
from guidata.qthelpers import get_std_icon, add_actions, add_separator
from guidata.widgets.arrayeditor import ArrayEditor
from guidata.configtools import get_icon
from guidata.dataset.datatypes import DataSet
from guidata.dataset.dataitems import BoolItem, FloatItem
# Local imports
from guiqwt.config import _
from guiqwt.events import (
setup_standard_tool_filter,
ObjectHandler,
KeyEventMatch,
StandardKeyMatch,
QtDragHandler,
ZoomRectHandler,
RectangularSelectionHandler,
PointSelectionHandler,
ClickHandler,
)
from guiqwt.shapes import (
Axes,
RectangleShape,
Marker,
PolygonShape,
EllipseShape,
SegmentShape,
PointShape,
ObliqueRectangleShape,
)
from guiqwt.annotations import (
AnnotatedRectangle,
AnnotatedCircle,
AnnotatedEllipse,
AnnotatedSegment,
AnnotatedPoint,
AnnotatedObliqueRectangle,
)
from guiqwt.colormap import get_colormap_list, build_icon_from_cmap_name
from guiqwt.interfaces import (
IColormapImageItemType,
IPlotManager,
IVoiImageItemType,
IStatsImageItemType,
ICurveItemType,
)
from guiqwt.panels import ID_XCS, ID_YCS, ID_OCS, ID_ITEMLIST, ID_CONTRAST
from guiqwt.interfaces import IImageItemType
class DefaultToolbarID:
pass
class GuiTool(QObject):
"""Base class for interactive tool applying on a plot"""
def __init__(self, manager, toolbar_id=DefaultToolbarID):
"""Constructor"""
super(GuiTool, self).__init__()
assert IPlotManager in manager.__implements__
self.manager = manager
self.parent_tool = None
self.plots = set()
self.action = self.create_action(manager)
self.menu = self.create_action_menu(manager)
if toolbar_id is DefaultToolbarID:
toolbar = manager.get_default_toolbar()
else:
toolbar = manager.get_toolbar(toolbar_id)
if toolbar is not None:
self.setup_toolbar(toolbar)
def create_action(self, manager):
"""Create and return tool's action"""
pass
def setup_toolbar(self, toolbar):
"""Setup tool's toolbar"""
toolbar.addAction(self.action)
if self.menu is not None:
widget = toolbar.widgetForAction(self.action)
widget.setMenu(self.menu)
widget.setPopupMode(QToolButton.InstantPopup)
def create_action_menu(self, manager):
"""Create and return menu for the tool's action"""
pass
def set_parent_tool(self, tool):
"""Used to organize tools automatically in menu items"""
self.parent_tool = tool
def register_plot(self, baseplot):
"""Every BasePlot using this tool should call register_plot
to notify the tool about this widget using it
"""
self.plots.add(baseplot)
def get_active_plot(self):
for plot in self.plots:
canvas = plot.canvas()
if canvas.hasFocus():
return plot
if len(self.plots) == 1:
return list(self.plots)[0]
return None
def update_status(self, plot):
"""called by to allow derived
classes to update the states of actions based on the currently
active BasePlot
can also be called after an action modifying the BasePlot
(e.g. in order to update action states when an item is deselected)
"""
pass
def setup_context_menu(self, menu, plot):
"""If the tool supports it, this method should install an action
in the context menu"""
pass
class InteractiveTool(GuiTool):
"""Interactive tool base class"""
TITLE = None
ICON = None
TIP = None
CURSOR = Qt.CrossCursor
SWITCH_TO_DEFAULT_TOOL = False # switch to default tool when finished
#: Signal emitted by InteractiveTool when validating tool action
SIG_VALIDATE_TOOL = Signal(object)
#: Signal emitted by InteractiveTool when tool job is finished
SIG_TOOL_JOB_FINISHED = Signal()
def __init__(
self,
manager,
toolbar_id=DefaultToolbarID,
title=None,
icon=None,
tip=None,
switch_to_default_tool=None,
):
if title is not None:
self.TITLE = title
if icon is not None:
self.ICON = icon
if tip is not None:
self.TIP = tip
super(InteractiveTool, self).__init__(manager, toolbar_id)
# Starting state for every plotwidget we can act upon
self.start_state = {}
if switch_to_default_tool is None:
switch_to_default_tool = self.SWITCH_TO_DEFAULT_TOOL
if switch_to_default_tool:
self.SIG_TOOL_JOB_FINISHED.connect(self.manager.activate_default_tool)
def create_action(self, manager):
"""Create and return tool's action"""
action = manager.create_action(
self.TITLE, icon=get_icon(self.ICON), tip=self.TIP, triggered=self.activate
)
action.setCheckable(True)
group = self.manager.get_tool_group("interactive")
group.addAction(action)
group.triggered.connect(self.interactive_triggered)
return action
def cursor(self):
"""Return tool mouse cursor"""
return self.CURSOR
def register_plot(self, baseplot):
# TODO: with the introduction of PlotManager it should
# be possible to remove the per tool dictionary start_state
# since all plots from a manager share the same set of tools
# the State Machine generated by the calls to tool.setup_filter
# should be the same for all plots. Thus it should be done only once
# and not once per plot managed by the plot manager
super(InteractiveTool, self).register_plot(baseplot)
filter = baseplot.filter
start_state = self.setup_filter(baseplot)
self.start_state[baseplot] = start_state
curs = self.cursor()
if curs is not None:
filter.set_cursor(curs, start_state)
def interactive_triggered(self, action):
if action is self.action:
self.activate()
else:
self.deactivate()
def activate(self):
"""Activate tool"""
for baseplot, start_state in list(self.start_state.items()):
baseplot.filter.set_state(start_state, None)
self.action.setChecked(True)
self.manager.set_active_tool(self)
def deactivate(self):
"""Deactivate tool"""
self.action.setChecked(False)
def validate(self, filter, event):
self.SIG_VALIDATE_TOOL.emit(filter)
self.SIG_TOOL_JOB_FINISHED.emit()
[docs]class SelectTool(InteractiveTool):
"""
Graphical Object Selection Tool
"""
TITLE = _("Selection")
ICON = "selection.png"
CURSOR = Qt.ArrowCursor
def setup_filter(self, baseplot):
filter = baseplot.filter
# Initialisation du filtre
start_state = filter.new_state()
# Bouton gauche :
ObjectHandler(filter, Qt.LeftButton, start_state=start_state)
ObjectHandler(
filter,
Qt.LeftButton,
mods=Qt.ControlModifier,
start_state=start_state,
multiselection=True,
)
filter.add_event(
start_state,
KeyEventMatch((Qt.Key_Enter, Qt.Key_Return, Qt.Key_Space)),
self.validate,
start_state,
)
filter.add_event(
start_state,
StandardKeyMatch(QKeySequence.SelectAll),
self.select_all_items,
start_state,
)
return setup_standard_tool_filter(filter, start_state)
def select_all_items(self, filter, event):
filter.plot.select_all()
filter.plot.replot()
[docs]class SelectPointTool(InteractiveTool):
TITLE = _("Point selection")
ICON = "point_selection.png"
MARKER_STYLE_SECT = "plot"
MARKER_STYLE_KEY = "marker/curve"
CURSOR = Qt.PointingHandCursor
def __init__(
self,
manager,
mode="reuse",
on_active_item=False,
title=None,
icon=None,
tip=None,
end_callback=None,
toolbar_id=DefaultToolbarID,
marker_style=None,
switch_to_default_tool=None,
):
super(SelectPointTool, self).__init__(
manager,
toolbar_id,
title=title,
icon=icon,
tip=tip,
switch_to_default_tool=switch_to_default_tool,
)
assert mode in ("reuse", "create")
self.mode = mode
self.end_callback = end_callback
self.marker = None
self.last_pos = None
self.on_active_item = on_active_item
if marker_style is not None:
self.marker_style_sect = marker_style[0]
self.marker_style_key = marker_style[1]
else:
self.marker_style_sect = self.MARKER_STYLE_SECT
self.marker_style_key = self.MARKER_STYLE_KEY
def set_marker_style(self, marker):
marker.set_style(self.marker_style_sect, self.marker_style_key)
def setup_filter(self, baseplot):
filter = baseplot.filter
# Initialisation du filtre
start_state = filter.new_state()
# Bouton gauche :
handler = QtDragHandler(filter, Qt.LeftButton, start_state=start_state)
handler.SIG_START_TRACKING.connect(self.start)
handler.SIG_MOVE.connect(self.move)
handler.SIG_STOP_NOT_MOVING.connect(self.stop)
handler.SIG_STOP_MOVING.connect(self.stop)
return setup_standard_tool_filter(filter, start_state)
def start(self, filter, event):
if self.marker is None:
title = ""
if self.TITLE:
title = "<b>%s</b><br>" % self.TITLE
if self.on_active_item:
constraint_cb = filter.plot.on_active_curve
label_cb = lambda x, y: title + filter.plot.get_coordinates_str(x, y)
else:
constraint_cb = None
label_cb = lambda x, y: "%sx = %g<br>y = %g" % (title, x, y)
self.marker = Marker(label_cb=label_cb, constraint_cb=constraint_cb)
self.set_marker_style(self.marker)
self.marker.attach(filter.plot)
self.marker.setZ(filter.plot.get_max_z() + 1)
self.marker.setVisible(True)
def stop(self, filter, event):
self.move(filter, event)
if self.mode != "reuse":
self.marker.detach()
self.marker = None
if self.end_callback:
self.end_callback(self)
def move(self, filter, event):
if self.marker is None:
return # something is wrong ...
self.marker.move_local_point_to(0, event.pos())
filter.plot.replot()
self.last_pos = self.marker.xValue(), self.marker.yValue()
def get_coordinates(self):
return self.last_pos
SHAPE_Z_OFFSET = 1000
[docs]class MultiLineTool(InteractiveTool):
TITLE = _("Polyline")
ICON = "polyline.png"
CURSOR = Qt.ArrowCursor
def __init__(
self,
manager,
handle_final_shape_cb=None,
shape_style=None,
toolbar_id=DefaultToolbarID,
title=None,
icon=None,
tip=None,
switch_to_default_tool=None,
):
super(MultiLineTool, self).__init__(
manager,
toolbar_id,
title=title,
icon=icon,
tip=tip,
switch_to_default_tool=switch_to_default_tool,
)
self.handle_final_shape_cb = handle_final_shape_cb
self.shape = None
self.current_handle = None
self.init_pos = None
if shape_style is not None:
self.shape_style_sect = shape_style[0]
self.shape_style_key = shape_style[1]
else:
self.shape_style_sect = "plot"
self.shape_style_key = "shape/drag"
def reset(self):
self.shape = None
self.current_handle = None
def create_shape(self, filter, pt):
self.shape = PolygonShape(closed=False)
filter.plot.add_item_with_z_offset(self.shape, SHAPE_Z_OFFSET)
self.shape.setVisible(True)
self.shape.set_style(self.shape_style_sect, self.shape_style_key)
self.shape.add_local_point(pt)
return self.shape.add_local_point(pt)
def setup_filter(self, baseplot):
filter = baseplot.filter
# Initialisation du filtre
start_state = filter.new_state()
# Bouton gauche :
handler = QtDragHandler(filter, Qt.LeftButton, start_state=start_state)
filter.add_event(
start_state,
KeyEventMatch((Qt.Key_Enter, Qt.Key_Return, Qt.Key_Space)),
self.validate,
start_state,
)
filter.add_event(
start_state,
KeyEventMatch(
(
Qt.Key_Backspace,
Qt.Key_Escape,
)
),
self.cancel_point,
start_state,
)
handler.SIG_START_TRACKING.connect(self.mouse_press)
handler.SIG_MOVE.connect(self.move)
handler.SIG_STOP_NOT_MOVING.connect(self.mouse_release)
handler.SIG_STOP_MOVING.connect(self.mouse_release)
return setup_standard_tool_filter(filter, start_state)
def validate(self, filter, event):
super(MultiLineTool, self).validate(filter, event)
if self.handle_final_shape_cb is not None:
self.handle_final_shape_cb(self.shape)
self.reset()
def cancel_point(self, filter, event):
if self.shape is None:
return
points = self.shape.get_points()
if points is None:
return
elif len(points) <= 2:
filter.plot.del_item(self.shape)
self.reset()
else:
if self.current_handle:
newh = self.shape.del_point(self.current_handle)
else:
newh = self.shape.del_point(-1)
self.current_handle = newh
filter.plot.replot()
[docs] def mouse_press(self, filter, event):
"""We create a new shape if it's the first point
otherwise we add a new point
"""
if self.shape is None:
self.init_pos = event.pos()
self.current_handle = self.create_shape(filter, event.pos())
filter.plot.replot()
else:
self.current_handle = self.shape.add_local_point(event.pos())
[docs] def move(self, filter, event):
"""moving while holding the button down lets the user
position the last created point
"""
if self.shape is None or self.current_handle is None:
# Error ??
return
self.shape.move_local_point_to(self.current_handle, event.pos())
filter.plot.replot()
[docs] def mouse_release(self, filter, event):
"""Releasing the mouse button validate the last point position"""
if self.current_handle is None:
return
if self.init_pos is not None and self.init_pos == event.pos():
self.shape.del_point(-1)
else:
self.shape.move_local_point_to(self.current_handle, event.pos())
self.init_pos = None
self.current_handle = None
filter.plot.replot()
[docs]class FreeFormTool(MultiLineTool):
TITLE = _("Free form")
ICON = "freeform.png"
[docs] def cancel_point(self, filter, event):
"""Reimplement base class method"""
super(FreeFormTool, self).cancel_point(filter, event)
self.shape.closed = len(self.shape.points) > 2
[docs] def mouse_press(self, filter, event):
"""Reimplement base class method"""
super(FreeFormTool, self).mouse_press(filter, event)
self.shape.closed = len(self.shape.points) > 2
[docs]class LabelTool(InteractiveTool):
TITLE = _("Label")
ICON = "label.png"
LABEL_STYLE_SECT = "plot"
LABEL_STYLE_KEY = "label"
SWITCH_TO_DEFAULT_TOOL = True
def __init__(
self,
manager,
handle_label_cb=None,
label_style=None,
toolbar_id=DefaultToolbarID,
title=None,
icon=None,
tip=None,
switch_to_default_tool=None,
):
self.handle_label_cb = handle_label_cb
super(LabelTool, self).__init__(
manager,
toolbar_id,
title=title,
icon=icon,
tip=tip,
switch_to_default_tool=switch_to_default_tool,
)
if label_style is not None:
self.label_style_sect = label_style[0]
self.label_style_key = label_style[1]
else:
self.label_style_sect = self.LABEL_STYLE_SECT
self.label_style_key = self.LABEL_STYLE_KEY
def set_label_style(self, label):
label.set_style(self.label_style_sect, self.label_style_key)
def setup_filter(self, baseplot):
filter = baseplot.filter
start_state = filter.new_state()
handler = ClickHandler(filter, Qt.LeftButton, start_state=start_state)
handler.SIG_CLICK_EVENT.connect(self.add_label_to_plot)
return setup_standard_tool_filter(filter, start_state)
def add_label_to_plot(self, filter, event):
plot = filter.plot
import guidata.dataset as ds
class TextParam(ds.datatypes.DataSet):
text = ds.dataitems.TextItem("", _("Label"))
textparam = TextParam(_("Label text"))
if textparam.edit(plot):
text = textparam.text.replace("\n", "<br>")
from guiqwt.builder import make
label = make.label(text, (0, 0), (10, 10), "TL")
self.set_label_style(label)
label.setTitle(self.TITLE)
x = plot.invTransform(label.xAxis(), event.pos().x())
y = plot.invTransform(label.yAxis(), event.pos().y())
label.set_pos(x, y)
plot.add_item_with_z_offset(label, SHAPE_Z_OFFSET)
if self.handle_label_cb is not None:
self.handle_label_cb(label)
plot.replot()
self.SIG_TOOL_JOB_FINISHED.emit()
class RectangularActionTool(InteractiveTool):
SHAPE_STYLE_SECT = "plot"
SHAPE_STYLE_KEY = "shape/drag"
AVOID_NULL_SHAPE = False
def __init__(
self,
manager,
func,
shape_style=None,
toolbar_id=DefaultToolbarID,
title=None,
icon=None,
tip=None,
fix_orientation=False,
switch_to_default_tool=None,
):
self.action_func = func
self.fix_orientation = fix_orientation
super(RectangularActionTool, self).__init__(
manager,
toolbar_id,
title=title,
icon=icon,
tip=tip,
switch_to_default_tool=switch_to_default_tool,
)
if shape_style is not None:
self.shape_style_sect = shape_style[0]
self.shape_style_key = shape_style[1]
else:
self.shape_style_sect = self.SHAPE_STYLE_SECT
self.shape_style_key = self.SHAPE_STYLE_KEY
self.last_final_shape = None
self.switch_to_default_tool = switch_to_default_tool
def get_last_final_shape(self):
if self.last_final_shape is not None:
return self.last_final_shape()
def set_shape_style(self, shape):
shape.set_style(self.shape_style_sect, self.shape_style_key)
def create_shape(self):
shape = RectangleShape(0, 0, 1, 1)
self.set_shape_style(shape)
return shape, 0, 2
def setup_shape(self, shape):
pass
def get_shape(self):
"""Reimplemented RectangularActionTool method"""
shape, h0, h1 = self.create_shape()
self.setup_shape(shape)
return shape, h0, h1
def get_final_shape(self, plot, p0, p1):
shape, h0, h1 = self.create_shape()
self.setup_shape(shape)
plot.add_item_with_z_offset(shape, SHAPE_Z_OFFSET)
shape.move_local_point_to(h0, p0)
shape.move_local_point_to(h1, p1)
self.last_final_shape = weakref.ref(shape)
return shape
def get_selection_handler(self, filter, start_state):
return RectangularSelectionHandler(
filter, Qt.LeftButton, start_state=start_state
)
def setup_filter(self, baseplot):
filter = baseplot.filter
start_state = filter.new_state()
handler = self.get_selection_handler(filter, start_state)
shape, h0, h1 = self.get_shape()
handler.set_shape(
shape, h0, h1, self.setup_shape, avoid_null_shape=self.AVOID_NULL_SHAPE
)
handler.SIG_END_RECT.connect(self.end_rect)
return setup_standard_tool_filter(filter, start_state)
def end_rect(self, filter, p0, p1):
plot = filter.plot
if self.fix_orientation:
left, right = min(p0.x(), p1.x()), max(p0.x(), p1.x())
top, bottom = min(p0.y(), p1.y()), max(p0.y(), p1.y())
self.action_func(plot, QPointF(left, top), QPointF(right, bottom))
else:
self.action_func(plot, p0, p1)
self.SIG_TOOL_JOB_FINISHED.emit()
if self.switch_to_default_tool:
shape = self.get_last_final_shape()
plot.set_active_item(shape)
class RectangularShapeTool(RectangularActionTool):
TITLE = None
ICON = None
def __init__(
self,
manager,
setup_shape_cb=None,
handle_final_shape_cb=None,
shape_style=None,
toolbar_id=DefaultToolbarID,
title=None,
icon=None,
tip=None,
switch_to_default_tool=None,
):
super(RectangularShapeTool, self).__init__(
manager,
self.add_shape_to_plot,
shape_style,
toolbar_id=toolbar_id,
title=title,
icon=icon,
tip=tip,
switch_to_default_tool=switch_to_default_tool,
)
self.setup_shape_cb = setup_shape_cb
self.handle_final_shape_cb = handle_final_shape_cb
def add_shape_to_plot(self, plot, p0, p1):
"""
Method called when shape's rectangular area
has just been drawn on screen.
Adding the final shape to plot and returning it.
"""
shape = self.get_final_shape(plot, p0, p1)
self.handle_final_shape(shape)
plot.replot()
def setup_shape(self, shape):
"""To be reimplemented"""
shape.setTitle(self.TITLE)
if self.setup_shape_cb is not None:
self.setup_shape_cb(shape)
def handle_final_shape(self, shape):
"""To be reimplemented"""
if self.handle_final_shape_cb is not None:
self.handle_final_shape_cb(shape)
class ObliqueRectangleTool(RectangularShapeTool):
TITLE = _("Oblique rectangle")
ICON = "oblique_rectangle.png"
AVOID_NULL_SHAPE = True
def create_shape(self):
shape = ObliqueRectangleShape(1, 1, 2, 1, 2, 2, 1, 2)
self.set_shape_style(shape)
return shape, 0, 2
[docs]class PointTool(RectangularShapeTool):
TITLE = _("Point")
ICON = "point_shape.png"
SHAPE_STYLE_KEY = "shape/point"
def create_shape(self):
shape = PointShape(0, 0)
self.set_shape_style(shape)
return shape, 0, 0
def get_selection_handler(self, filter, start_state):
return PointSelectionHandler(filter, Qt.LeftButton, start_state=start_state)
[docs]class SegmentTool(RectangularShapeTool):
TITLE = _("Segment")
ICON = "segment.png"
SHAPE_STYLE_KEY = "shape/segment"
def create_shape(self):
shape = SegmentShape(0, 0, 1, 1)
self.set_shape_style(shape)
return shape, 0, 1
[docs]class CircleTool(RectangularShapeTool):
TITLE = _("Circle")
ICON = "circle.png"
def create_shape(self):
shape = EllipseShape(0, 0, 1, 1)
self.set_shape_style(shape)
return shape, 0, 1
[docs]class EllipseTool(RectangularShapeTool):
TITLE = _("Ellipse")
ICON = "ellipse_shape.png"
def create_shape(self):
shape = EllipseShape(0, 0, 1, 1)
self.set_shape_style(shape)
return shape, 0, 1
[docs] def handle_final_shape(self, shape):
shape.switch_to_ellipse()
super(EllipseTool, self).handle_final_shape(shape)
[docs]class PlaceAxesTool(RectangularShapeTool):
TITLE = _("Axes")
ICON = "gtaxes.png"
SHAPE_STYLE_KEY = "shape/axes"
def create_shape(self):
shape = Axes((0, 1), (1, 1), (0, 0))
self.set_shape_style(shape)
return shape, 0, 2
[docs]class AnnotatedRectangleTool(RectangleTool):
def create_shape(self):
annotation = AnnotatedRectangle(0, 0, 1, 1)
self.set_shape_style(annotation)
return annotation, 0, 2
class AnnotatedObliqueRectangleTool(ObliqueRectangleTool):
AVOID_NULL_SHAPE = True
def create_shape(self):
annotation = AnnotatedObliqueRectangle(0, 0, 1, 0, 1, 1, 0, 1)
self.set_shape_style(annotation)
return annotation, 0, 2
[docs]class AnnotatedCircleTool(CircleTool):
def create_shape(self):
annotation = AnnotatedCircle(0, 0, 1, 1)
self.set_shape_style(annotation)
return annotation, 0, 1
[docs]class AnnotatedEllipseTool(EllipseTool):
def create_shape(self):
annotation = AnnotatedEllipse(0, 0, 1, 1)
self.set_shape_style(annotation)
return annotation, 0, 1
[docs] def handle_final_shape(self, shape):
shape.shape.switch_to_ellipse()
super(EllipseTool, self).handle_final_shape(shape)
[docs]class AnnotatedPointTool(PointTool):
def create_shape(self):
annotation = AnnotatedPoint(0, 0)
self.set_shape_style(annotation)
return annotation, 0, 0
[docs]class AnnotatedSegmentTool(SegmentTool):
def create_shape(self):
annotation = AnnotatedSegment(0, 0, 1, 1)
self.set_shape_style(annotation)
return annotation, 0, 1
class ImageStatsRectangle(AnnotatedRectangle):
def __init__(self, x1=0, y1=0, x2=0, y2=0, annotationparam=None):
super(ImageStatsRectangle, self).__init__(x1, y1, x2, y2, annotationparam)
self.image_item = None
def set_image_item(self, image_item):
self.image_item = image_item
# ----AnnotatedShape API-----------------------------------------------------
def get_infos(self):
"""Return formatted string with informations on current shape"""
if self.image_item is not None:
return self.image_item.get_stats(*self.get_rect())
def update_image_tool_status(tool, plot):
from guiqwt.image import ImagePlot
enabled = isinstance(plot, ImagePlot)
tool.action.setEnabled(enabled)
return enabled
class ImageStatsTool(RectangularShapeTool):
SWITCH_TO_DEFAULT_TOOL = True
TITLE = _("Image statistics")
ICON = "imagestats.png"
SHAPE_STYLE_KEY = "shape/image_stats"
def __init__(
self,
manager,
setup_shape_cb=None,
handle_final_shape_cb=None,
shape_style=None,
toolbar_id=DefaultToolbarID,
title=None,
icon=None,
tip=None,
):
super(ImageStatsTool, self).__init__(
manager,
setup_shape_cb,
handle_final_shape_cb,
shape_style,
toolbar_id,
title,
icon,
tip,
)
self._last_item = None
def get_last_item(self):
if self._last_item is not None:
return self._last_item()
def create_shape(self):
return ImageStatsRectangle(0, 0, 1, 1), 0, 2
def setup_shape(self, shape):
super(ImageStatsTool, self).setup_shape(shape)
shape.setTitle("")
self.set_shape_style(shape)
self.register_shape(shape, final=False)
def register_shape(self, shape, final=False):
plot = shape.plot()
if plot is not None:
plot.unselect_all()
plot.set_active_item(shape)
shape.set_image_item(self.get_last_item())
def handle_final_shape(self, shape):
super(ImageStatsTool, self).handle_final_shape(shape)
self.register_shape(shape, final=True)
def get_associated_item(self, plot):
items = plot.get_selected_items(item_type=IStatsImageItemType)
if len(items) == 1:
self._last_item = weakref.ref(items[0])
return self.get_last_item()
def update_status(self, plot):
if update_image_tool_status(self, plot):
item = self.get_associated_item(plot)
self.action.setEnabled(item is not None)
[docs]class CrossSectionTool(RectangularShapeTool):
SWITCH_TO_DEFAULT_TOOL = True
TITLE = _("Cross section")
ICON = "csection.png"
SHAPE_STYLE_KEY = "shape/cross_section"
SHAPE_TITLE = TITLE
PANEL_IDS = (ID_XCS, ID_YCS)
def create_shape(self):
return AnnotatedPoint(0, 0), 0, 0
[docs] def setup_shape(self, shape):
self.setup_shape_appearance(shape)
super(CrossSectionTool, self).setup_shape(shape)
self.register_shape(shape, final=False)
def setup_shape_appearance(self, shape):
self.set_shape_style(shape)
param = shape.annotationparam
param.title = self.SHAPE_TITLE
# param.show_computations = False
param.update_annotation(shape)
def register_shape(self, shape, final=False):
plot = shape.plot()
if plot is not None:
plot.unselect_all()
plot.set_active_item(shape)
for panel_id in self.PANEL_IDS:
panel = self.manager.get_panel(panel_id)
if panel is not None:
panel.register_shape(shape, final=final)
[docs] def activate(self):
"""Activate tool"""
super(CrossSectionTool, self).activate()
for panel_id in self.PANEL_IDS:
panel = self.manager.get_panel(panel_id)
panel.setVisible(True)
shape = self.get_last_final_shape()
if shape is not None:
panel.update_plot(shape)
[docs] def handle_final_shape(self, shape):
super(CrossSectionTool, self).handle_final_shape(shape)
self.register_shape(shape, final=True)
[docs] def update_status(self, plot):
if update_image_tool_status(self, plot):
item = plot.get_selected_items(item_type=IImageItemType)
self.action.setEnabled(len(item) > 0)
[docs]class AverageCrossSectionTool(CrossSectionTool):
SWITCH_TO_DEFAULT_TOOL = True
TITLE = _("Average cross section")
ICON = "csection_a.png"
SHAPE_STYLE_KEY = "shape/average_cross_section"
SHAPE_TITLE = TITLE
def create_shape(self):
return AnnotatedRectangle(0, 0, 1, 1), 0, 2
class ObliqueCrossSectionTool(CrossSectionTool):
SWITCH_TO_DEFAULT_TOOL = True
TITLE = _("Oblique averaged cross section")
ICON = "csection_oblique.png"
SHAPE_STYLE_KEY = "shape/average_cross_section"
SHAPE_TITLE = TITLE
PANEL_IDS = (ID_OCS,)
def create_shape(self):
annotation = AnnotatedObliqueRectangle(0, 0, 1, 0, 1, 1, 0, 1)
self.set_shape_style(annotation)
return annotation, 0, 2
[docs]class RectZoomTool(InteractiveTool):
TITLE = _("Rectangle zoom")
ICON = "magnifier.png"
def setup_filter(self, baseplot):
filter = baseplot.filter
start_state = filter.new_state()
handler = ZoomRectHandler(filter, Qt.LeftButton, start_state=start_state)
shape, h0, h1 = self.get_shape()
handler.set_shape(shape, h0, h1)
return setup_standard_tool_filter(filter, start_state)
def get_shape(self):
shape = RectangleShape(0, 0, 1, 1)
shape.set_style("plot", "shape/rectzoom")
return shape, 0, 2
class BaseCursorTool(InteractiveTool):
TITLE = None
ICON = None
def __init__(
self,
manager,
toolbar_id=DefaultToolbarID,
title=None,
icon=None,
tip=None,
switch_to_default_tool=None,
):
super(BaseCursorTool, self).__init__(
manager,
toolbar_id,
title=title,
icon=icon,
tip=tip,
switch_to_default_tool=switch_to_default_tool,
)
self.shape = None
def create_shape(self):
"""Create and return the cursor/range shape"""
raise NotImplementedError
def setup_filter(self, baseplot):
filter = baseplot.filter
# Initialisation du filtre
start_state = filter.new_state()
# Bouton gauche :
self.handler = QtDragHandler(filter, Qt.LeftButton, start_state=start_state)
self.handler.SIG_MOVE.connect(self.move)
self.handler.SIG_STOP_NOT_MOVING.connect(self.end_move)
self.handler.SIG_STOP_MOVING.connect(self.end_move)
return setup_standard_tool_filter(filter, start_state)
def move(self, filter, event):
plot = filter.plot
if not self.shape:
self.shape = self.create_shape()
self.shape.attach(plot)
self.shape.setZ(plot.get_max_z() + 1)
self.shape.move_local_point_to(0, event.pos())
self.shape.setVisible(True)
self.shape.move_local_point_to(1, event.pos())
plot.replot()
def end_move(self, filter, event):
if self.shape is not None:
assert self.shape.plot() == filter.plot
filter.plot.add_item_with_z_offset(self.shape, SHAPE_Z_OFFSET)
self.shape = None
self.SIG_TOOL_JOB_FINISHED.emit()
[docs]class HRangeTool(BaseCursorTool):
TITLE = _("Horizontal selection")
ICON = "xrange.png"
[docs] def create_shape(self):
from guiqwt.shapes import XRangeSelection
return XRangeSelection(0, 0)
class VCursorTool(BaseCursorTool):
TITLE = _("Vertical cursor")
ICON = "vcursor.png"
def create_shape(self):
from guiqwt.shapes import Marker
marker = Marker()
marker.set_markerstyle("|")
return marker
class HCursorTool(BaseCursorTool):
TITLE = _("Horizontal cursor")
ICON = "hcursor.png"
def create_shape(self):
from guiqwt.shapes import Marker
marker = Marker()
marker.set_markerstyle("-")
return marker
class XCursorTool(BaseCursorTool):
TITLE = _("Cross cursor")
ICON = "xcursor.png"
def create_shape(self):
from guiqwt.shapes import Marker
marker = Marker()
marker.set_markerstyle("+")
return marker
class SignalStatsTool(BaseCursorTool):
TITLE = _("Signal statistics")
ICON = "xrange.png"
SWITCH_TO_DEFAULT_TOOL = True
def __init__(
self, manager, toolbar_id=DefaultToolbarID, title=None, icon=None, tip=None
):
super(SignalStatsTool, self).__init__(
manager, toolbar_id, title=title, icon=icon, tip=tip
)
self._last_item = None
self.label = None
def get_last_item(self):
if self._last_item is not None:
return self._last_item()
def create_shape(self):
from guiqwt.shapes import XRangeSelection
return XRangeSelection(0, 0)
def move(self, filter, event):
super(SignalStatsTool, self).move(filter, event)
if self.label is None:
plot = filter.plot
curve = self.get_associated_item(plot)
from guiqwt.builder import make
self.label = make.computations(
self.shape,
"TL",
[
(
curve,
"%g < x < %g",
lambda *args: (args[0].min(), args[0].max()),
),
(
curve,
"%g < y < %g",
lambda *args: (args[1].min(), args[1].max()),
),
(curve, "<y>=%g", lambda *args: args[1].mean()),
(curve, "σ(y)=%g", lambda *args: args[1].std()),
(curve, "∑(y)=%g", lambda *args: np.trapz(args[1])),
(curve, "∫ydx=%g", lambda *args: np.trapz(args[1], args[0])),
],
)
self.label.attach(plot)
self.label.setZ(plot.get_max_z() + 1)
self.label.setVisible(True)
def end_move(self, filter, event):
super(SignalStatsTool, self).end_move(filter, event)
if self.label is not None:
filter.plot.add_item_with_z_offset(self.label, SHAPE_Z_OFFSET)
self.label = None
def get_associated_item(self, plot):
items = plot.get_selected_items(item_type=ICurveItemType)
if len(items) == 1:
self._last_item = weakref.ref(items[0])
return self.get_last_item()
def update_status(self, plot):
item = self.get_associated_item(plot)
self.action.setEnabled(item is not None)
[docs]class DummySeparatorTool(GuiTool):
def __init__(self, manager, toolbar_id=DefaultToolbarID):
super(DummySeparatorTool, self).__init__(manager, toolbar_id)
class CommandTool(GuiTool):
"""Base class for command tools: action, context menu entry"""
CHECKABLE = False
def __init__(
self, manager, title, icon=None, tip=None, toolbar_id=DefaultToolbarID
):
self.title = title
if icon and isinstance(icon, str):
self.icon = get_icon(icon)
else:
self.icon = icon
self.tip = tip
super(CommandTool, self).__init__(manager, toolbar_id)
def create_action(self, manager):
"""Create and return tool's action"""
return manager.create_action(
self.title,
icon=self.icon,
tip=self.tip,
triggered=self.activate,
checkable=self.CHECKABLE,
)
def setup_context_menu(self, menu, plot):
if self.menu is not None:
self.menu.setTitle(self.action.text())
menu.addMenu(self.menu)
else:
menu.addAction(self.action)
def activate(self, checked=True):
plot = self.get_active_plot()
if plot is not None:
self.activate_command(plot, checked)
def set_status_active_item(self, plot):
item = plot.get_active_item()
if item:
self.action.setEnabled(True)
else:
self.action.setEnabled(False)
class ToggleTool(CommandTool):
CHECKABLE = True
def __init__(self, manager, title, icon=None, tip=None, toolbar_id=None):
super(ToggleTool, self).__init__(manager, title, icon, tip, toolbar_id)
class BasePlotMenuTool(CommandTool):
"""
A tool that gather parameter panels from the BasePlot
and proposes to edit them and set them back
"""
def __init__(
self, manager, key, title=None, icon=None, tip=None, toolbar_id=DefaultToolbarID
):
from guiqwt.baseplot import PARAMETERS_TITLE_ICON
default_title, default_icon = PARAMETERS_TITLE_ICON[key]
if title is None:
title = default_title
if icon is None:
icon = default_icon
super(BasePlotMenuTool, self).__init__(manager, title, icon, tip, toolbar_id)
# Warning: icon (str) --(Base class constructor)--> self.icon (QIcon)
self.key = key
def activate_command(self, plot, checked):
"""Activate tool"""
plot.edit_plot_parameters(self.key)
def update_status(self, plot):
status = plot.get_plot_parameters_status(self.key)
self.action.setEnabled(status)
[docs]class AntiAliasingTool(ToggleTool):
def __init__(self, manager):
super(AntiAliasingTool, self).__init__(manager, _("Antialiasing (curves)"))
[docs] def activate_command(self, plot, checked):
"""Activate tool"""
plot.set_antialiasing(checked)
plot.replot()
[docs]class DisplayCoordsTool(CommandTool):
def __init__(self, manager):
super(DisplayCoordsTool, self).__init__(
manager,
_("Markers"),
icon=get_icon("on_curve.png"),
tip=None,
toolbar_id=None,
)
self.action.setEnabled(True)
def activate_canvas_pointer(self, enable):
plot = self.get_active_plot()
if plot is not None:
plot.set_pointer("canvas" if enable else None)
def activate_curve_pointer(self, enable):
plot = self.get_active_plot()
if plot is not None:
plot.set_pointer("curve" if enable else None)
[docs] def update_status(self, plot):
self.canvas_act.setChecked(plot.canvas_pointer)
self.curve_act.setChecked(plot.curve_pointer)
[docs]class ReverseYAxisTool(ToggleTool):
def __init__(self, manager):
super(ReverseYAxisTool, self).__init__(manager, _("Reverse Y axis"))
[docs] def activate_command(self, plot, checked):
"""Activate tool"""
plot.set_axis_direction("left", checked)
plot.replot()
[docs] def update_status(self, plot):
if update_image_tool_status(self, plot):
self.action.setChecked(plot.get_axis_direction("left"))
class AspectRatioParam(DataSet):
lock = BoolItem(_("Lock aspect ratio"))
current = FloatItem(_("Current value")).set_prop("display", active=False)
ratio = FloatItem(_("Lock value"), min=1e-3)
[docs]class AspectRatioTool(CommandTool):
def __init__(self, manager):
super(AspectRatioTool, self).__init__(
manager, _("Aspect ratio"), tip=None, toolbar_id=None
)
self.action.setEnabled(True)
def set_aspect_ratio_1_1(self):
plot = self.get_active_plot()
if plot is not None:
plot.set_aspect_ratio(ratio=1)
plot.replot()
def __update_actions(self, checked):
self.ar_param.lock = checked
# self.lock_action.blockSignals(True)
self.lock_action.setChecked(checked)
# self.lock_action.blockSignals(False)
plot = self.get_active_plot()
if plot is not None:
ratio = plot.get_aspect_ratio()
self.ratio1_action.setEnabled(checked and ratio != 1.0)
[docs] def lock_aspect_ratio(self, checked):
"""Lock aspect ratio"""
plot = self.get_active_plot()
if plot is not None:
plot.set_aspect_ratio(lock=checked)
self.__update_actions(checked)
plot.replot()
def edit_aspect_ratio(self):
plot = self.get_active_plot()
if plot is not None:
self.ar_param.lock = plot.lock_aspect_ratio
self.ar_param.ratio = plot.get_aspect_ratio()
self.ar_param.current = plot.get_current_aspect_ratio()
if self.ar_param.edit(parent=plot):
lock, ratio = self.ar_param.lock, self.ar_param.ratio
plot.set_aspect_ratio(ratio=ratio, lock=lock)
self.__update_actions(lock)
plot.replot()
[docs] def update_status(self, plot):
if update_image_tool_status(self, plot):
ratio = plot.get_aspect_ratio()
lock = plot.lock_aspect_ratio
self.ar_param.ratio, self.ar_param.lock = ratio, lock
self.__update_actions(lock)
[docs]class PanelTool(ToggleTool):
panel_id = None
panel_name = None
def __init__(self, manager):
super(PanelTool, self).__init__(manager, self.panel_name)
manager.get_panel(self.panel_id).SIG_VISIBILITY_CHANGED.connect(
self.action.setChecked
)
[docs] def activate_command(self, plot, checked):
"""Activate tool"""
panel = self.manager.get_panel(self.panel_id)
panel.setVisible(checked)
[docs] def update_status(self, plot):
panel = self.manager.get_panel(self.panel_id)
self.action.setChecked(panel.isVisible())
[docs]class ContrastPanelTool(PanelTool):
panel_name = _("Contrast adjustment")
panel_id = ID_CONTRAST
[docs] def update_status(self, plot):
super(ContrastPanelTool, self).update_status(plot)
update_image_tool_status(self, plot)
item = plot.get_last_active_item(IVoiImageItemType)
panel = self.manager.get_panel(self.panel_id)
for action in panel.toolbar.actions():
if isinstance(action, QAction):
action.setEnabled(item is not None)
class OCSPanelTool(PanelTool):
panel_name = _("Oblique averaged cross section")
panel_id = ID_OCS
[docs]class SaveAsTool(CommandTool):
def __init__(self, manager, toolbar_id=DefaultToolbarID):
super(SaveAsTool, self).__init__(
manager,
_("Save as..."),
get_std_icon("DialogSaveButton", 16),
toolbar_id=toolbar_id,
)
[docs] def activate_command(self, plot, checked):
"""Activate tool"""
# FIXME: Qt's PDF printer is unable to print plots including images
# --> until this bug is fixed internally, disabling PDF output format
# when plot has image items.
formats = "%s (*.png)" % _("PNG image")
for item in plot.get_items():
if IImageItemType in item.types():
break
else:
formats += "\n%s (*.pdf)" % _("PDF document")
fname, _f = getsavefilename(plot, _("Save as"), _("untitled"), formats)
if fname:
plot.save_widget(fname)
[docs]class CopyToClipboardTool(CommandTool):
def __init__(self, manager, toolbar_id=DefaultToolbarID):
super(CopyToClipboardTool, self).__init__(
manager,
_("Copy to clipboard"),
get_icon("copytoclipboard.png"),
toolbar_id=toolbar_id,
)
def save_snapshot(plot, p0, p1, new_size=None):
"""
Save rectangular plot area
p0, p1: resp. top left and bottom right points (`QPointF` objects)
new_size: destination image size (tuple: (width, height))
"""
from guiqwt.image import (
get_image_from_plot,
get_plot_qrect,
get_items_in_rectangle,
compute_trimageitems_original_size,
)
from guiqwt import io
items = get_items_in_rectangle(plot, p0, p1)
if not items:
QMessageBox.critical(
plot,
_("Rectangle snapshot"),
_("There is no supported image item in current selection."),
)
return
src_x, src_y, src_w, src_h = get_plot_qrect(plot, p0, p1).getRect()
original_size = compute_trimageitems_original_size(items, src_w, src_h)
if new_size is None:
new_size = (int(p1.x() - p0.x() + 1), int(p1.y() - p0.y() + 1)) # Screen size
from guiqwt.widgets.resizedialog import ResizeDialog
dlg = ResizeDialog(
plot, new_size=new_size, old_size=original_size, text=_("Destination size:")
)
if not dlg.exec_():
return
from guidata.dataset.datatypes import DataSet, BeginGroup, EndGroup
from guidata.dataset.dataitems import BoolItem, ChoiceItem
class SnapshotParam(DataSet):
_levels = BeginGroup(_("Image levels adjustments"))
apply_contrast = BoolItem(_("Apply contrast settings"), default=False)
apply_interpolation = BoolItem(_("Apply interpolation algorithm"), default=True)
norm_range = BoolItem(_("Scale levels to maximum range"), default=False)
_end_levels = EndGroup(_("Image levels adjustments"))
_multiple = BeginGroup(_("Superimposed images"))
add_images = ChoiceItem(
_("If image B is behind image A, " "replace intersection by"),
[(False, "A"), (True, "A+B")],
default=None,
)
_end_multiple = EndGroup(_("Superimposed images"))
param = SnapshotParam(_("Rectangle snapshot"))
if not param.edit(parent=plot):
return
if dlg.keep_original_size:
destw, desth = original_size
else:
destw, desth = dlg.width, dlg.height
try:
data = get_image_from_plot(
plot,
p0,
p1,
destw=destw,
desth=desth,
add_images=param.add_images,
apply_lut=param.apply_contrast,
apply_interpolation=param.apply_interpolation,
original_resolution=dlg.keep_original_size,
)
dtype = None
for item in items:
if dtype is None or item.data.dtype.itemsize > dtype.itemsize:
dtype = item.data.dtype
if param.norm_range:
data = io.scale_data_to_dtype(data, dtype=dtype)
else:
data = np.array(data, dtype=dtype)
except MemoryError:
mbytes = int(destw * desth * 32.0 / (8 * 1024**2))
QMessageBox.critical(
plot,
_("Memory error"),
_(
"There is not enough memory left to process "
"this %d x %d image (%d MB would be required)."
)
% (destw, desth, mbytes),
)
return
for model_item in items:
model_fname = model_item.get_filename()
if model_fname is not None and model_fname.lower().endswith(".dcm"):
break
else:
model_fname = None
fname, _f = getsavefilename(
plot,
_("Save as"),
_("untitled"),
io.iohandler.get_filters("save", data.dtype, template=True),
)
_base, ext = osp.splitext(fname)
options = {}
if not fname:
return
elif ext.lower() == ".png":
options.update(dict(dtype=np.uint8, max_range=True))
elif ext.lower() == ".dcm":
from pydicom import dicomio
model_dcm = dicomio.read_file(model_fname)
try:
ps_attr = "ImagerPixelSpacing"
ps_x, ps_y = getattr(model_dcm, ps_attr)
except AttributeError:
ps_attr = "PixelSpacing"
ps_x, ps_y = getattr(model_dcm, ps_attr)
model_dcm.Rows, model_dcm.Columns = data.shape
dest_height, dest_width = data.shape
(
_x,
_y,
_angle,
model_dx,
model_dy,
_hflip,
_vflip,
) = model_item.get_transform()
new_ps_x = ps_x * src_w / (model_dx * dest_width)
new_ps_y = ps_y * src_h / (model_dy * dest_height)
setattr(model_dcm, ps_attr, [new_ps_x, new_ps_y])
options.update(dict(template=model_dcm))
io.imwrite(fname, data, **options)
[docs]class SnapshotTool(RectangularActionTool):
SWITCH_TO_DEFAULT_TOOL = True
TITLE = _("Rectangle snapshot")
ICON = "snapshot.png"
def __init__(self, manager, toolbar_id=DefaultToolbarID):
super(SnapshotTool, self).__init__(
manager, save_snapshot, toolbar_id=toolbar_id, fix_orientation=True
)
class RotateCropTool(CommandTool):
"""Rotate & Crop tool
See :py:class:`guiqwt.rotatecrop.RotateCropDialog` dialog."""
def __init__(self, manager, toolbar_id=DefaultToolbarID, options=None):
super(RotateCropTool, self).__init__(
manager,
title=_("Rotate and crop"),
icon=get_icon("rotate.png"),
toolbar_id=toolbar_id,
)
self.options = options
def activate_command(self, plot, checked):
"""Activate tool"""
from guiqwt.image import TrImageItem
from guiqwt.widgets.rotatecrop import RotateCropDialog
for item in plot.get_selected_items():
if isinstance(item, TrImageItem):
z = item.z()
plot.del_item(item)
dlg = RotateCropDialog(plot.parent(), options=self.options)
dlg.set_item(item)
ok = dlg.exec_()
plot.add_item(item, z=z)
if not ok:
break
def update_status(self, plot):
from guiqwt.image import TrImageItem
status = any(
[isinstance(item, TrImageItem) for item in plot.get_selected_items()]
)
self.action.setEnabled(status)
[docs]class PrintTool(CommandTool):
def __init__(self, manager, toolbar_id=DefaultToolbarID):
super(PrintTool, self).__init__(
manager, _("Print..."), get_icon("print.png"), toolbar_id=toolbar_id
)
[docs] def activate_command(self, plot, checked):
"""Activate tool"""
printer = QPrinter()
dialog = QPrintDialog(printer, plot)
saved_in, saved_out, saved_err = sys.stdin, sys.stdout, sys.stderr
sys.stdout = None
ok = dialog.exec_()
sys.stdin, sys.stdout, sys.stderr = saved_in, saved_out, saved_err
if ok:
plot.print_(printer)
[docs]class OpenFileTool(CommandTool):
#: Signal emitted by OpenFileTool when a file was opened (arg: filename)
SIG_OPEN_FILE = Signal(str)
def __init__(
self, manager, title=_("Open..."), formats="*.*", toolbar_id=DefaultToolbarID
):
super(OpenFileTool, self).__init__(
manager, title, get_std_icon("DialogOpenButton", 16), toolbar_id=toolbar_id
)
self.formats = formats
self.directory = ""
def get_filename(self, plot):
saved_in, saved_out, saved_err = sys.stdin, sys.stdout, sys.stderr
sys.stdout = None
filename, _f = getopenfilename(plot, _("Open"), self.directory, self.formats)
sys.stdin, sys.stdout, sys.stderr = saved_in, saved_out, saved_err
filename = str(filename)
if filename:
self.directory = osp.dirname(filename)
return filename
[docs] def activate_command(self, plot, checked):
"""Activate tool"""
filename = self.get_filename(plot)
if filename:
self.SIG_OPEN_FILE.emit(filename)
[docs]class SaveItemsTool(CommandTool):
def __init__(self, manager, toolbar_id=DefaultToolbarID):
super(SaveItemsTool, self).__init__(
manager,
_("Save items"),
get_std_icon("DialogSaveButton", 16),
toolbar_id=toolbar_id,
)
[docs] def activate_command(self, plot, checked):
"""Activate tool"""
fname, _f = getsavefilename(
plot, _("Save items as"), _("untitled"), "%s (*.gui)" % _("guiqwt items")
)
if not fname:
return
itemfile = open(fname, "wb")
plot.save_items(itemfile, selected=True)
[docs]class LoadItemsTool(OpenFileTool):
def __init__(self, manager, toolbar_id=DefaultToolbarID):
super(LoadItemsTool, self).__init__(
manager, title=_("Load items"), formats="*.gui", toolbar_id=toolbar_id
)
[docs] def activate_command(self, plot, checked):
"""Activate tool"""
filename = self.get_filename(plot)
if not filename:
return
itemfile = open(filename, "rb")
plot.restore_items(itemfile)
plot.replot()
[docs]class OpenImageTool(OpenFileTool):
def __init__(self, manager, toolbar_id=DefaultToolbarID):
from guiqwt import io
super(OpenImageTool, self).__init__(
manager,
title=_("Open image"),
formats=io.iohandler.get_filters("load"),
toolbar_id=toolbar_id,
)
[docs]class AxisScaleTool(CommandTool):
def __init__(self, manager):
super(AxisScaleTool, self).__init__(
manager, _("Scale"), icon=get_icon("log_log.png"), tip=None, toolbar_id=None
)
self.action.setEnabled(True)
[docs] def update_status(self, plot):
item = plot.get_active_item()
active_scale = ("lin", "lin")
if item is not None:
xscale = plot.get_axis_scale(item.xAxis())
yscale = plot.get_axis_scale(item.yAxis())
active_scale = xscale, yscale
for scale_type, scale_action in list(self.scale_menu.items()):
if item is None:
scale_action.setEnabled(False)
else:
scale_action.setEnabled(True)
if active_scale == scale_type:
scale_action.setChecked(True)
else:
scale_action.setChecked(False)
def set_scale(self, checked, xscale, yscale):
if not checked:
return
plot = self.get_active_plot()
if plot is not None:
cur_xscale, cur_yscale = plot.get_scales()
if cur_xscale != xscale or cur_yscale != yscale:
plot.set_scales(xscale, yscale)
[docs]class HelpTool(CommandTool):
def __init__(self, manager, toolbar_id=DefaultToolbarID):
super(HelpTool, self).__init__(
manager,
_("Help"),
get_std_icon("DialogHelpButton", 16),
toolbar_id=toolbar_id,
)
[docs] def activate_command(self, plot, checked):
"""Activate tool"""
QMessageBox.information(
plot,
_("Help"),
_(
"""<b>Keyboard/mouse shortcuts:</b><br><br>
- <u>single left-click</u>: item (curve, image, ...) selection<br>
- <u>single right-click</u>: context-menu relative to selected item<br>
- <u>shift</u>: on-active-curve (or image) cursor (+ <u>control</u> to maintain cursor visible)<br>
- <u>shift + control</u>: on-active-curve cursor (+ <u>control</u> to maintain cursor visible)<br>
- <u>alt</u>: free cursor<br>
- <u>left-click + mouse move</u>: move item (when available)<br>
- <u>middle-click + mouse move</u>: pan<br>
- <u>right-click + mouse move</u>: zoom"""
),
)
class AboutTool(CommandTool):
def __init__(self, manager, toolbar_id=DefaultToolbarID):
super(AboutTool, self).__init__(
manager, _("About") + " guiqwt", get_icon("guiqwt.svg"), toolbar_id=None
)
def activate_command(self, plot, checked):
"""Activate tool"""
from guiqwt import about
QMessageBox.about(plot, _("About") + " guiqwt", about(html=True))
class ItemManipulationBaseTool(CommandTool):
TITLE = None
ICON = None
TIP = None
def __init__(self, manager, toolbar_id, curve_func, image_func):
super(ItemManipulationBaseTool, self).__init__(
manager, self.TITLE, icon=self.ICON, tip=self.TIP, toolbar_id=toolbar_id
)
self.curve_func = curve_func
self.image_func = image_func
def get_supported_items(self, plot):
all_items = [
item
for item in plot.get_items(item_type=ICurveItemType)
if not item.is_empty()
]
from guiqwt.image import ImageItem
all_items += [
item
for item in plot.get_items()
if isinstance(item, ImageItem) and not item.is_empty()
]
if len(all_items) == 1:
return all_items
else:
return [item for item in all_items if item in plot.get_selected_items()]
def update_status(self, plot):
self.action.setEnabled(len(self.get_supported_items(plot)) > 0)
def activate_command(self, plot, checked):
"""Activate tool"""
for item in self.get_supported_items(plot):
if ICurveItemType in item.types():
self.curve_func(item)
else:
self.image_func(item)
plot.replot()
def export_curve_data(item):
"""Export curve item data to text file"""
item_data = item.get_data()
if len(item_data) > 2:
x, y, dx, dy = item_data
array_list = [x, y]
if dx is not None:
array_list.append(dx)
if dy is not None:
array_list.append(dy)
data = np.array(array_list).T
else:
x, y = item_data
data = np.array([x, y]).T
plot = item.plot()
title = _("Export")
if item.curveparam.label:
title += " (%s)" % item.curveparam.label
fname, _f = getsavefilename(plot, title, "", _("Text file") + " (*.txt)")
if fname:
try:
np.savetxt(str(fname), data, delimiter=",")
except RuntimeError as error:
QMessageBox.critical(
plot,
_("Export"),
_("Unable to export item data.")
+ "<br><br>"
+ _("Error message:")
+ "<br>"
+ str(error),
)
def export_image_data(item):
"""Export image item data to file"""
from guiqwt.qthelpers import exec_image_save_dialog
exec_image_save_dialog(item.plot(), item.data)
[docs]class ExportItemDataTool(ItemManipulationBaseTool):
TITLE = _("Export data...")
ICON = "export.png"
def __init__(self, manager, toolbar_id=None):
super(ExportItemDataTool, self).__init__(
manager,
toolbar_id,
curve_func=export_curve_data,
image_func=export_image_data,
)
def edit_curve_data(item):
"""Edit curve item data in array editor"""
item_data = item.get_data()
if len(item_data) > 2:
x, y, dx, dy = item_data
array_list = [x, y]
if dx is not None:
array_list.append(dx)
if dy is not None:
array_list.append(dy)
data = np.array(array_list).T
else:
x, y = item_data
data = np.array([x, y]).T
dialog = ArrayEditor(item.plot())
dialog.setup_and_check(data)
if dialog.exec_():
if data.shape[1] > 2:
if data.shape[1] == 3:
x, y, tmp = data.T
if dx is not None:
dx = tmp
else:
dy = tmp
else:
x, y, dx, dy = data.T
item.set_data(x, y, dx, dy)
else:
x, y = data.T
item.set_data(x, y)
def edit_image_data(item):
"""Edit image item data in array editor"""
dialog = ArrayEditor(item.plot())
dialog.setup_and_check(item.data)
dialog.exec_()
[docs]class EditItemDataTool(ItemManipulationBaseTool):
"""Edit item data"""
TITLE = _("Edit data...")
ICON = "arredit.png"
def __init__(self, manager, toolbar_id=None):
super(EditItemDataTool, self).__init__(
manager,
toolbar_id,
curve_func=edit_curve_data,
image_func=edit_image_data,
)
[docs]class ItemCenterTool(CommandTool):
def __init__(self, manager, toolbar_id=None):
super(ItemCenterTool, self).__init__(
manager, _("Center items"), "center.png", toolbar_id=toolbar_id
)
def get_supported_items(self, plot):
from guiqwt.shapes import RectangleShape, EllipseShape, ObliqueRectangleShape
from guiqwt.annotations import (
AnnotatedRectangle,
AnnotatedEllipse,
AnnotatedObliqueRectangle,
)
item_types = (
RectangleShape,
EllipseShape,
ObliqueRectangleShape,
AnnotatedRectangle,
AnnotatedEllipse,
AnnotatedObliqueRectangle,
)
return [
item
for item in plot.get_selected_items(z_sorted=True)
if isinstance(item, item_types)
]
[docs] def update_status(self, plot):
self.action.setEnabled(len(self.get_supported_items(plot)) > 1)
[docs] def activate_command(self, plot, checked):
"""Activate tool"""
items = self.get_supported_items(plot)
xc0, yc0 = items.pop(-1).get_center()
for item in items:
xc, yc = item.get_center()
item.move_with_selection(xc0 - xc, yc0 - yc)
plot.replot()
[docs]class DeleteItemTool(CommandTool):
def __init__(self, manager, toolbar_id=None):
super(DeleteItemTool, self).__init__(
manager, _("Remove"), "trash.png", toolbar_id=toolbar_id
)
def get_removable_items(self, plot):
return [item for item in plot.get_selected_items() if not item.is_readonly()]
[docs] def update_status(self, plot):
self.action.setEnabled(len(self.get_removable_items(plot)) > 0)
[docs] def activate_command(self, plot, checked):
"""Activate tool"""
items = self.get_removable_items(plot)
if len(items) == 1:
message = _("Do you really want to remove this item?")
else:
message = _("Do you really want to remove selected items?")
answer = QMessageBox.warning(
plot, _("Remove"), message, QMessageBox.Yes | QMessageBox.No
)
if answer == QMessageBox.Yes:
plot.del_items(items)
plot.replot()
class FilterTool(CommandTool):
def __init__(self, manager, filter, toolbar_id=None):
super(FilterTool, self).__init__(
manager, str(filter.name), toolbar_id=toolbar_id
)
self.filter = filter
def update_status(self, plot):
self.set_status_active_item()
def activate_command(self, plot, checked):
"""Activate tool"""
plot.apply_filter(self.filter)
[docs]class ColormapTool(CommandTool):
def __init__(self, manager, toolbar_id=DefaultToolbarID):
super(ColormapTool, self).__init__(
manager,
_("Colormap"),
tip=_("Select colormap for active " "image"),
toolbar_id=toolbar_id,
)
self.action.setEnabled(False)
self.action.setIconText("")
self.default_icon = build_icon_from_cmap_name("jet")
self.action.setIcon(self.default_icon)
def get_selected_images(self, plot):
items = [it for it in plot.get_selected_items(item_type=IColormapImageItemType)]
if not items:
active_image = plot.get_last_active_item(IColormapImageItemType)
if active_image:
items = [active_image]
return items
def activate_cmap(self, action):
plot = self.get_active_plot()
if plot is not None:
items = self.get_selected_images(plot)
cmap_name = str(action.text())
for item in items:
item.imageparam.colormap = cmap_name
item.imageparam.update_image(item)
self.action.setText(cmap_name)
plot.invalidate()
self.update_status(plot)
[docs] def update_status(self, plot):
if update_image_tool_status(self, plot):
item = plot.get_last_active_item(IColormapImageItemType)
icon = self.default_icon
if item:
self.action.setEnabled(True)
cmap_name = item.get_color_map_name()
if cmap_name:
icon = build_icon_from_cmap_name(cmap_name)
else:
self.action.setEnabled(False)
self.action.setIcon(icon)
class ImageMaskTool(CommandTool):
#: Signal emitted by ImageMaskTool when mask was applied
SIG_APPLIED_MASK_TOOL = Signal()
def __init__(self, manager, toolbar_id=DefaultToolbarID):
self._mask_shapes = {}
self._mask_already_restored = {}
super(ImageMaskTool, self).__init__(
manager,
_("Mask"),
icon="mask_tool.png",
tip=_("Manage image masking areas"),
toolbar_id=toolbar_id,
)
self.masked_image = None # associated masked image item
def create_action_menu(self, manager):
"""Create and return menu for the tool's action"""
rect_tool = manager.add_tool(
RectangleTool,
toolbar_id=None,
handle_final_shape_cb=lambda shape: self.handle_shape(shape, inside=True),
title=_("Mask rectangular area (inside)"),
icon="mask_rectangle.png",
)
rect_out_tool = manager.add_tool(
RectangleTool,
toolbar_id=None,
handle_final_shape_cb=lambda shape: self.handle_shape(shape, inside=False),
title=_("Mask rectangular area (outside)"),
icon="mask_rectangle_outside.png",
)
ellipse_tool = manager.add_tool(
CircleTool,
toolbar_id=None,
handle_final_shape_cb=lambda shape: self.handle_shape(shape, inside=True),
title=_("Mask circular area (inside)"),
icon="mask_circle.png",
)
ellipse_out_tool = manager.add_tool(
CircleTool,
toolbar_id=None,
handle_final_shape_cb=lambda shape: self.handle_shape(shape, inside=False),
title=_("Mask circular area (outside)"),
icon="mask_circle_outside.png",
)
menu = QMenu()
self.showmask_action = manager.create_action(
_("Show image mask"), toggled=self.show_mask
)
showshapes_action = manager.create_action(
_("Show masking shapes"), toggled=self.show_shapes
)
showshapes_action.setChecked(True)
applymask_a = manager.create_action(
_("Apply mask"), icon=get_icon("apply.png"), triggered=self.apply_mask
)
clearmask_a = manager.create_action(
_("Clear mask"), icon=get_icon("delete.png"), triggered=self.clear_mask
)
removeshapes_a = manager.create_action(
_("Remove all masking shapes"),
icon=get_icon("delete.png"),
triggered=self.remove_all_shapes,
)
add_actions(
menu,
(
self.showmask_action,
None,
showshapes_action,
rect_tool.action,
ellipse_tool.action,
rect_out_tool.action,
ellipse_out_tool.action,
applymask_a,
None,
clearmask_a,
removeshapes_a,
),
)
return menu
def update_status(self, plot):
self.action.setEnabled(self.masked_image is not None)
def register_plot(self, baseplot):
super(ImageMaskTool, self).register_plot(baseplot)
self._mask_shapes.setdefault(baseplot, [])
baseplot.SIG_ITEMS_CHANGED.connect(self.items_changed)
baseplot.SIG_ITEM_SELECTION_CHANGED.connect(self.item_selection_changed)
def show_mask(self, state):
if self.masked_image is not None:
self.masked_image.set_mask_visible(state)
def apply_mask(self):
mask = self.masked_image.get_mask()
plot = self.get_active_plot()
for shape, inside in self._mask_shapes[plot]:
if isinstance(shape, RectangleShape):
self.masked_image.align_rectangular_shape(shape)
x0, y0, x1, y1 = shape.get_rect()
self.masked_image.mask_rectangular_area(x0, y0, x1, y1, inside=inside)
else:
x0, y0, x1, y1 = shape.get_rect()
self.masked_image.mask_circular_area(x0, y0, x1, y1, inside=inside)
self.masked_image.set_mask(mask)
plot.replot()
self.SIG_APPLIED_MASK_TOOL.emit()
def remove_all_shapes(self):
message = _("Do you really want to remove all masking shapes?")
plot = self.get_active_plot()
answer = QMessageBox.warning(
plot,
_("Remove all masking shapes"),
message,
QMessageBox.Yes | QMessageBox.No,
)
if answer == QMessageBox.Yes:
self.remove_shapes()
def remove_shapes(self):
plot = self.get_active_plot()
plot.del_items(
[shape for shape, _inside in self._mask_shapes[plot]]
) # remove shapes
self._mask_shapes[plot] = []
plot.replot()
def show_shapes(self, state):
plot = self.get_active_plot()
if plot is not None:
for shape, _inside in self._mask_shapes[plot]:
shape.setVisible(state)
plot.replot()
def handle_shape(self, shape, inside):
shape.set_style("plot", "shape/mask")
shape.set_private(True)
plot = self.get_active_plot()
plot.set_active_item(shape)
self._mask_shapes[plot] += [(shape, inside)]
def find_masked_image(self, plot):
item = plot.get_active_item()
from guiqwt.image import MaskedImageItem
if isinstance(item, MaskedImageItem):
return item
else:
items = [
item for item in plot.get_items() if isinstance(item, MaskedImageItem)
]
if items:
return items[-1]
def create_shapes_from_masked_areas(self):
plot = self.get_active_plot()
self._mask_shapes[plot] = []
for area in self.masked_image.get_masked_areas():
if area.geometry == "rectangular":
shape = RectangleShape(area.x0, area.y0, area.x1, area.y1)
self.masked_image.align_rectangular_shape(shape)
else:
shape = EllipseShape(
area.x0,
0.5 * (area.y0 + area.y1),
area.x1,
0.5 * (area.y0 + area.y1),
)
shape.set_style("plot", "shape/mask")
shape.set_private(True)
self._mask_shapes[plot] += [(shape, area.inside)]
plot.blockSignals(True)
plot.add_item(shape)
plot.blockSignals(False)
def set_masked_image(self, plot):
self.masked_image = item = self.find_masked_image(plot)
if self.masked_image is not None and not self._mask_already_restored:
self.create_shapes_from_masked_areas()
self._mask_already_restored = True
enable = False if item is None else item.is_mask_visible()
self.showmask_action.setChecked(enable)
def items_changed(self, plot):
self.set_masked_image(plot)
self._mask_shapes[plot] = [
(shape, inside)
for shape, inside in self._mask_shapes[plot]
if shape.plot() is plot
]
self.update_status(plot)
def item_selection_changed(self, plot):
self.set_masked_image(plot)
self.update_status(plot)
def clear_mask(self):
message = _("Do you really want to clear the mask?")
plot = self.get_active_plot()
answer = QMessageBox.warning(
plot, _("Clear mask"), message, QMessageBox.Yes | QMessageBox.No
)
if answer == QMessageBox.Yes:
self.masked_image.unmask_all()
plot.replot()
def activate_command(self, plot, checked):
"""Activate tool"""
pass