mirror of
https://github.com/wassname/scikit-image.git
synced 2026-06-27 19:48:43 +08:00
Allow plot plugin to have tools
This commit is contained in:
@@ -13,8 +13,8 @@ class CanvasToolBase(object):
|
||||
|
||||
Parameters
|
||||
----------
|
||||
viewer : :class:`skimage.viewer.Viewer`
|
||||
Skimage viewer object.
|
||||
manager : Viewer or PlotPlugin.
|
||||
Skimage viewer or plot plugin object.
|
||||
on_move : function
|
||||
Function called whenever a control handle is moved.
|
||||
This function must accept the end points of line as the only argument.
|
||||
@@ -24,13 +24,12 @@ class CanvasToolBase(object):
|
||||
Function called whenever the "enter" key is pressed.
|
||||
"""
|
||||
|
||||
def __init__(self, viewer, on_move=None, on_enter=None, on_release=None,
|
||||
useblit=True):
|
||||
self.viewer = viewer
|
||||
self.ax = viewer.ax
|
||||
def __init__(self, manager, on_move=None, on_enter=None, on_release=None,
|
||||
useblit=True, ax=None):
|
||||
self.manager = manager
|
||||
self.ax = manager.ax
|
||||
self.artists = []
|
||||
self.active = True
|
||||
viewer.add_tool(self)
|
||||
|
||||
self.callback_on_move = _pass if on_move is None else on_move
|
||||
self.callback_on_enter = _pass if on_enter is None else on_enter
|
||||
@@ -48,7 +47,7 @@ class CanvasToolBase(object):
|
||||
return False
|
||||
|
||||
def redraw(self):
|
||||
self.viewer.redraw()
|
||||
self.manager.redraw()
|
||||
|
||||
def set_visible(self, val):
|
||||
for artist in self.artists:
|
||||
@@ -58,7 +57,7 @@ class CanvasToolBase(object):
|
||||
if event.key == 'enter':
|
||||
self.callback_on_enter(self.geometry)
|
||||
self.set_visible(False)
|
||||
self.viewer.redraw()
|
||||
self.manager.redraw()
|
||||
|
||||
def on_mouse_press(self, event):
|
||||
pass
|
||||
@@ -73,7 +72,7 @@ class CanvasToolBase(object):
|
||||
pass
|
||||
|
||||
def remove(self):
|
||||
self.viewer.remove_tool(self)
|
||||
self.manager.remove_tool(self)
|
||||
|
||||
@property
|
||||
def geometry(self):
|
||||
|
||||
@@ -11,8 +11,8 @@ class LineTool(CanvasToolBase):
|
||||
|
||||
Parameters
|
||||
----------
|
||||
viewer : :class:`skimage.viewer.Viewer`
|
||||
Skimage viewer object.
|
||||
manager : Viewer or PlotPlugin.
|
||||
Skimage viewer or plot plugin object.
|
||||
on_move : function
|
||||
Function called whenever a control handle is moved.
|
||||
This function must accept the end points of line as the only argument.
|
||||
@@ -33,10 +33,10 @@ class LineTool(CanvasToolBase):
|
||||
end_points : 2D array
|
||||
End points of line ((x1, y1), (x2, y2)).
|
||||
"""
|
||||
def __init__(self, viewer, on_move=None, on_release=None, on_enter=None,
|
||||
def __init__(self, manager, on_move=None, on_release=None, on_enter=None,
|
||||
maxdist=10, line_props=None, handle_props=None,
|
||||
**kwargs):
|
||||
super(LineTool, self).__init__(viewer, on_move=on_move,
|
||||
super(LineTool, self).__init__(manager, on_move=on_move,
|
||||
on_enter=on_enter,
|
||||
on_release=on_release, **kwargs)
|
||||
|
||||
@@ -64,7 +64,7 @@ class LineTool(CanvasToolBase):
|
||||
print("length = %0.2f" %
|
||||
np.sqrt(np.diff(x)**2 + np.diff(y)**2))
|
||||
self.callback_on_enter = on_enter
|
||||
viewer.add_tool(self)
|
||||
self.manager.add_tool(self)
|
||||
|
||||
@property
|
||||
def end_points(self):
|
||||
@@ -132,8 +132,8 @@ class ThickLineTool(LineTool):
|
||||
|
||||
Parameters
|
||||
----------
|
||||
viewer : :class:`skimage.viewer.Viewer`
|
||||
Skimage viewer object.
|
||||
manager : Viewer or PlotPlugin.
|
||||
Skimage viewer or plot plugin object.
|
||||
on_move : function
|
||||
Function called whenever a control handle is moved.
|
||||
This function must accept the end points of line as the only argument.
|
||||
@@ -157,9 +157,9 @@ class ThickLineTool(LineTool):
|
||||
End points of line ((x1, y1), (x2, y2)).
|
||||
"""
|
||||
|
||||
def __init__(self, viewer, on_move=None, on_enter=None, on_release=None,
|
||||
def __init__(self, manager, on_move=None, on_enter=None, on_release=None,
|
||||
on_change=None, maxdist=10, line_props=None, handle_props=None):
|
||||
super(ThickLineTool, self).__init__(viewer,
|
||||
super(ThickLineTool, self).__init__(manager,
|
||||
on_move=on_move,
|
||||
on_enter=on_enter,
|
||||
on_release=on_release,
|
||||
|
||||
@@ -14,8 +14,8 @@ class PaintTool(CanvasToolBase):
|
||||
|
||||
Parameters
|
||||
----------
|
||||
viewer : :class:`skimage.viewer.Viewer`
|
||||
Skimage viewer object.
|
||||
manager : Viewer or PlotPlugin.
|
||||
Skimage viewer or plot plugin object.
|
||||
overlay_shape : shape tuple
|
||||
2D shape tuple used to initialize overlay image.
|
||||
alpha : float (between [0, 1])
|
||||
@@ -38,10 +38,10 @@ class PaintTool(CanvasToolBase):
|
||||
label : int
|
||||
Current paint color.
|
||||
"""
|
||||
def __init__(self, viewer, overlay_shape, radius=5, alpha=0.3,
|
||||
def __init__(self, manager, overlay_shape, radius=5, alpha=0.3,
|
||||
on_move=None, on_release=None, on_enter=None,
|
||||
rect_props=None):
|
||||
super(PaintTool, self).__init__(viewer, on_move=on_move,
|
||||
super(PaintTool, self).__init__(manager, on_move=on_move,
|
||||
on_enter=on_enter,
|
||||
on_release=on_release)
|
||||
|
||||
@@ -63,7 +63,7 @@ class PaintTool(CanvasToolBase):
|
||||
|
||||
# Note that the order is important: Redraw cursor *after* overlay
|
||||
self.artists = [self._overlay_plot, self._cursor]
|
||||
viewer.add_tool(self)
|
||||
self.manager.add_tool(self)
|
||||
|
||||
@property
|
||||
def label(self):
|
||||
@@ -136,7 +136,7 @@ class PaintTool(CanvasToolBase):
|
||||
self.callback_on_release(self.geometry)
|
||||
|
||||
def on_move(self, event):
|
||||
if not self.viewer.ax.in_axes(event):
|
||||
if not self.ax.in_axes(event):
|
||||
self._cursor.set_visible(False)
|
||||
self.redraw() # make sure cursor is not visible
|
||||
return
|
||||
|
||||
@@ -14,8 +14,8 @@ class RectangleTool(CanvasToolBase, RectangleSelector):
|
||||
|
||||
Parameters
|
||||
----------
|
||||
viewer : :class:`skimage.viewer.Viewer`
|
||||
Skimage viewer object.
|
||||
manager : Viewer or PlotPlugin.
|
||||
Skimage viewer or plot plugin object.
|
||||
on_move : function
|
||||
Function called whenever a control handle is moved.
|
||||
This function must accept the rectangle extents as the only argument.
|
||||
@@ -58,20 +58,20 @@ class RectangleTool(CanvasToolBase, RectangleSelector):
|
||||
... set_color(im, (rr4, cc4), [0, 0, 0])
|
||||
... viewer.image=im
|
||||
|
||||
>>> rect_tool = RectangleTool(viewer.ax, on_enter=print_the_rect) # doctest: +SKIP
|
||||
>>> rect_tool = RectangleTool(viewer, on_enter=print_the_rect) # doctest: +SKIP
|
||||
>>> viewer.show() # doctest: +SKIP
|
||||
"""
|
||||
|
||||
def __init__(self, viewer, on_move=None, on_release=None, on_enter=None,
|
||||
def __init__(self, manager, on_move=None, on_release=None, on_enter=None,
|
||||
maxdist=10, rect_props=None):
|
||||
self._rect = None
|
||||
props = dict(edgecolor=None, facecolor='r', alpha=0.15)
|
||||
props.update(rect_props if rect_props is not None else {})
|
||||
if props['edgecolor'] is None:
|
||||
props['edgecolor'] = props['facecolor']
|
||||
RectangleSelector.__init__(self, viewer.ax, lambda *args: None,
|
||||
RectangleSelector.__init__(self, manager.ax, lambda *args: None,
|
||||
rectprops=props)
|
||||
CanvasToolBase.__init__(self, viewer, on_move=on_move,
|
||||
CanvasToolBase.__init__(self, manager, on_move=on_move,
|
||||
on_enter=on_enter, on_release=on_release)
|
||||
|
||||
# Events are handled by the viewer
|
||||
@@ -107,7 +107,7 @@ class RectangleTool(CanvasToolBase, RectangleSelector):
|
||||
self.artists = [self._rect,
|
||||
self._corner_handles.artist,
|
||||
self._edge_handles.artist]
|
||||
viewer.add_tool(self)
|
||||
self.manager.add_tool(self)
|
||||
|
||||
@property
|
||||
def _rect_bbox(self):
|
||||
|
||||
@@ -17,7 +17,7 @@ class ColorHistogram(PlotPlugin):
|
||||
def attach(self, image_viewer):
|
||||
super(ColorHistogram, self).attach(image_viewer)
|
||||
|
||||
self.rect_tool = RectangleTool(image_viewer,
|
||||
self.rect_tool = RectangleTool(self,
|
||||
on_release=self.ab_selected)
|
||||
self._on_new_image(image_viewer.image)
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import numpy as np
|
||||
|
||||
from ..qt import QtGui
|
||||
from ..utils import new_plot
|
||||
from ..utils.canvas import BlitManager, EventManager
|
||||
from .base import Plugin
|
||||
|
||||
|
||||
@@ -23,11 +24,17 @@ class PlotPlugin(Plugin):
|
||||
|
||||
self._height = height
|
||||
self._width = width
|
||||
self._blit_manager = None
|
||||
self._tools = []
|
||||
self._event_manager = None
|
||||
|
||||
def attach(self, image_viewer):
|
||||
super(PlotPlugin, self).attach(image_viewer)
|
||||
# Add plot for displaying intensity profile.
|
||||
self.add_plot()
|
||||
if image_viewer.useblit:
|
||||
self._blit_manager = BlitManager(self.ax)
|
||||
self._event_manager = EventManager(self.ax)
|
||||
|
||||
def redraw(self):
|
||||
"""Redraw plot."""
|
||||
@@ -51,3 +58,17 @@ class PlotPlugin(Plugin):
|
||||
def _update_original_image(self, image):
|
||||
super(PlotPlugin, self)._update_original_image(image)
|
||||
self.redraw()
|
||||
|
||||
def add_tool(self, tool):
|
||||
if self._blit_manager:
|
||||
self._blit_manager.add_artists(tool.artists)
|
||||
self._tools.append(tool)
|
||||
self._event_manager.attach(tool)
|
||||
|
||||
def remove_tool(self, tool):
|
||||
if tool not in self._tools:
|
||||
return
|
||||
if self._blit_manager:
|
||||
self._blit_manager.remove_artists(tool.artists)
|
||||
self._tools.remove(tool)
|
||||
self._event_manager.detach(tool)
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
|
||||
|
||||
class BlitManager(object):
|
||||
|
||||
"""Object that manages blits on an axes"""
|
||||
|
||||
def __init__(self, ax):
|
||||
self.ax = ax
|
||||
self.canvas = ax.figure.canvas
|
||||
self.canvas.mpl_connect('draw_event', self.on_draw_event)
|
||||
self.ax = ax
|
||||
self.background = None
|
||||
self.artists = []
|
||||
|
||||
def add_artists(self, artists):
|
||||
self.artists.extend(artists)
|
||||
self.redraw()
|
||||
|
||||
def remove_artists(self, artists):
|
||||
for artist in artists:
|
||||
self.artists.remove(artist)
|
||||
|
||||
def on_draw_event(self, event=None):
|
||||
self.background = self.canvas.copy_from_bbox(self.ax.bbox)
|
||||
self.draw_artists()
|
||||
|
||||
def redraw(self):
|
||||
if self.background is not None:
|
||||
self.canvas.restore_region(self.background)
|
||||
self.draw_artists()
|
||||
self.canvas.blit(self.ax.bbox)
|
||||
else:
|
||||
self.canvas.draw_idle()
|
||||
|
||||
def draw_artists(self):
|
||||
for artist in self.artists:
|
||||
self.ax.draw_artist(artist)
|
||||
|
||||
|
||||
class EventManager(object):
|
||||
|
||||
"""Object that manages events on a canvas"""
|
||||
|
||||
def __init__(self, ax):
|
||||
self.canvas = ax.figure.canvas
|
||||
self.connect_event('button_press_event', self.on_mouse_press)
|
||||
self.connect_event('key_press_event', self.on_key_press)
|
||||
self.connect_event('button_release_event', self.on_mouse_release)
|
||||
self.connect_event('motion_notify_event', self.on_move)
|
||||
self.connect_event('scroll_event', self.on_scroll)
|
||||
|
||||
self.tools = []
|
||||
self.active_tool = None
|
||||
|
||||
def connect_event(self, name, handler):
|
||||
self.canvas.mpl_connect(name, handler)
|
||||
|
||||
def attach(self, tool):
|
||||
self.tools.append(tool)
|
||||
self.active_tool = tool
|
||||
|
||||
def detach(self, tool):
|
||||
self.tools.remove(tool)
|
||||
if self.tools:
|
||||
self.active_tool = self.tools[-1]
|
||||
else:
|
||||
self.active_tool = None
|
||||
|
||||
def on_mouse_press(self, event):
|
||||
for tool in self.tools:
|
||||
if not tool.ignore(event) and tool.hit_test(event):
|
||||
self.active_tool = tool
|
||||
break
|
||||
if self.active_tool and not self.active_tool.ignore(event):
|
||||
self.active_tool.on_mouse_press(event)
|
||||
return
|
||||
for tool in reversed(self.tools):
|
||||
if not tool.ignore(event):
|
||||
self.active_tool = tool
|
||||
tool.on_mouse_press(event)
|
||||
return
|
||||
|
||||
def on_key_press(self, event):
|
||||
tool = self._get_tool(event)
|
||||
if tool is not None:
|
||||
tool.on_key_press(event)
|
||||
|
||||
def _get_tool(self, event):
|
||||
if not self.tools or self.active_tool.ignore(event):
|
||||
return None
|
||||
return self.active_tool
|
||||
|
||||
def on_mouse_release(self, event):
|
||||
tool = self._get_tool(event)
|
||||
if tool is not None:
|
||||
tool.on_mouse_release(event)
|
||||
|
||||
def on_move(self, event):
|
||||
tool = self._get_tool(event)
|
||||
if tool is not None:
|
||||
tool.on_move(event)
|
||||
|
||||
def on_scroll(self, event):
|
||||
tool = self._get_tool(event)
|
||||
if tool is not None:
|
||||
tool.on_scroll(event)
|
||||
@@ -10,6 +10,7 @@ from ..qt import QtWidgets, Qt, Signal
|
||||
from ..widgets import Slider
|
||||
from ..utils import (dialogs, init_qtapp, figimage, start_qtapp,
|
||||
update_axes_image)
|
||||
from ..utils.canvas import BlitManager, EventManager
|
||||
from ..plugins.base import Plugin
|
||||
|
||||
|
||||
@@ -44,108 +45,6 @@ def mpl_image_to_rgba(mpl_image):
|
||||
return img_as_float(image)
|
||||
|
||||
|
||||
class BlitManager(object):
|
||||
"""Object that manages blits on an axes"""
|
||||
def __init__(self, ax):
|
||||
self.ax = ax
|
||||
self.canvas = ax.figure.canvas
|
||||
self.canvas.mpl_connect('draw_event', self.on_draw_event)
|
||||
self.ax = ax
|
||||
self.background = None
|
||||
self.artists = []
|
||||
|
||||
def add_artists(self, artists):
|
||||
self.artists.extend(artists)
|
||||
self.redraw()
|
||||
|
||||
def remove_artists(self, artists):
|
||||
for artist in artists:
|
||||
self.artists.remove(artist)
|
||||
|
||||
def on_draw_event(self, event=None):
|
||||
self.background = self.canvas.copy_from_bbox(self.ax.bbox)
|
||||
self.draw_artists()
|
||||
|
||||
def redraw(self):
|
||||
if self.background is not None:
|
||||
self.canvas.restore_region(self.background)
|
||||
self.draw_artists()
|
||||
self.canvas.blit(self.ax.bbox)
|
||||
else:
|
||||
self.canvas.draw_idle()
|
||||
|
||||
def draw_artists(self):
|
||||
for artist in self.artists:
|
||||
self.ax.draw_artist(artist)
|
||||
|
||||
|
||||
class EventManager(object):
|
||||
"""Object that manages events on a canvas"""
|
||||
def __init__(self, ax):
|
||||
self.canvas = ax.figure.canvas
|
||||
self.connect_event('button_press_event', self.on_mouse_press)
|
||||
self.connect_event('key_press_event', self.on_key_press)
|
||||
self.connect_event('button_release_event', self.on_mouse_release)
|
||||
self.connect_event('motion_notify_event', self.on_move)
|
||||
self.connect_event('scroll_event', self.on_scroll)
|
||||
|
||||
self.tools = []
|
||||
self.active_tool = None
|
||||
|
||||
def connect_event(self, name, handler):
|
||||
self.canvas.mpl_connect(name, handler)
|
||||
|
||||
def attach(self, tool):
|
||||
self.tools.append(tool)
|
||||
self.active_tool = tool
|
||||
|
||||
def detach(self, tool):
|
||||
self.tools.remove(tool)
|
||||
if self.tools:
|
||||
self.active_tool = self.tools[-1]
|
||||
else:
|
||||
self.active_tool = None
|
||||
|
||||
def on_mouse_press(self, event):
|
||||
for tool in self.tools:
|
||||
if not tool.ignore(event) and tool.hit_test(event):
|
||||
self.active_tool = tool
|
||||
break
|
||||
if self.active_tool and not self.active_tool.ignore(event):
|
||||
self.active_tool.on_mouse_press(event)
|
||||
return
|
||||
for tool in reversed(self.tools):
|
||||
if not tool.ignore(event):
|
||||
self.active_tool = tool
|
||||
tool.on_mouse_press(event)
|
||||
return
|
||||
|
||||
def on_key_press(self, event):
|
||||
tool = self._get_tool(event)
|
||||
if not tool is None:
|
||||
tool.on_key_press(event)
|
||||
|
||||
def _get_tool(self, event):
|
||||
if not self.tools or self.active_tool.ignore(event):
|
||||
return None
|
||||
return self.active_tool
|
||||
|
||||
def on_mouse_release(self, event):
|
||||
tool = self._get_tool(event)
|
||||
if not tool is None:
|
||||
tool.on_mouse_release(event)
|
||||
|
||||
def on_move(self, event):
|
||||
tool = self._get_tool(event)
|
||||
if not tool is None:
|
||||
tool.on_move(event)
|
||||
|
||||
def on_scroll(self, event):
|
||||
tool = self._get_tool(event)
|
||||
if not tool is None:
|
||||
tool.on_scroll(event)
|
||||
|
||||
|
||||
class ImageViewer(QtWidgets.QMainWindow):
|
||||
"""Viewer for displaying images.
|
||||
|
||||
@@ -405,6 +304,8 @@ class ImageViewer(QtWidgets.QMainWindow):
|
||||
self._event_manager.attach(tool)
|
||||
|
||||
def remove_tool(self, tool):
|
||||
if tool not in self._tools:
|
||||
return
|
||||
if self.useblit:
|
||||
self._blit_manager.remove_artists(tool.artists)
|
||||
self._tools.remove(tool)
|
||||
|
||||
Reference in New Issue
Block a user