mirror of
https://github.com/wassname/scikit-image.git
synced 2026-07-10 23:41:29 +08:00
Add line profile plugin back in.
I saved a copy of the line profile plugin in a branch and then deleted the plugin from the main qtmpl-viewer branch. Unfortunately, I forgot that rebasing off the main branch would erase the plugin. This commit just adds the plugin back.
This commit is contained in:
@@ -0,0 +1,241 @@
|
||||
import numpy as np
|
||||
import scipy.ndimage as ndi
|
||||
from skimage.util.dtype import dtype_range
|
||||
|
||||
from .plotplugin import PlotPlugin
|
||||
|
||||
|
||||
__all__ = ['LineProfile']
|
||||
|
||||
#TODO: Extract line tool and add it to a new `canvastools` subpackage.
|
||||
|
||||
class LineProfile(PlotPlugin):
|
||||
"""Plugin to compute interpolated intensity under a scan line on an image.
|
||||
|
||||
See PlotPlugin and Plugin classes for additional details.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
linewidth : float
|
||||
Line width for interpolation. Wider lines average over more pixels.
|
||||
epsilon : float
|
||||
Maximum pixel distance allowed when selecting end point of scan line.
|
||||
limits : tuple or {None, 'image', 'dtype'}
|
||||
(minimum, maximum) intensity limits for plotted profile. The following
|
||||
special values are defined:
|
||||
|
||||
None : rescale based on min/max intensity along selected scan line.
|
||||
'image' : fixed scale based on min/max intensity in image.
|
||||
'dtype' : fixed scale based on min/max intensity of image dtype.
|
||||
"""
|
||||
name = 'Line Profile'
|
||||
draws_on_image = True
|
||||
|
||||
def __init__(self, linewidth=1, epsilon=5, limits='image', **kwargs):
|
||||
super(LineProfile, self).__init__(**kwargs)
|
||||
self.linewidth = linewidth
|
||||
self.epsilon = epsilon
|
||||
self._active_pt = None
|
||||
self._limit_type = limits
|
||||
self.line_kwargs = dict(color='y', lw=linewidth, alpha=0.5, marker='s',
|
||||
markersize=5, solid_capstyle='butt')
|
||||
print self.help()
|
||||
|
||||
def attach(self, image_viewer):
|
||||
super(LineProfile, self).attach(image_viewer)
|
||||
|
||||
image = image_viewer.original_image
|
||||
|
||||
if self._limit_type == 'image':
|
||||
self.limits = (np.min(image), np.max(image))
|
||||
elif self._limit_type == 'dtype':
|
||||
self.self._limit_type = dtype_range[image.dtype.type]
|
||||
elif self._limit_type is None or len(self._limit_type) == 2:
|
||||
self.limits = self._limit_type
|
||||
else:
|
||||
raise ValueError("Unrecognized `limits`: %s" % self._limit_type)
|
||||
|
||||
if not self._limit_type is None:
|
||||
self.ax.set_ylim(self.limits)
|
||||
|
||||
h, w = image.shape
|
||||
self._init_end_pts = np.array([[w/3, h/2], [2*w/3, h/2]])
|
||||
self.end_pts = self._init_end_pts.copy()
|
||||
|
||||
x, y = np.transpose(self.end_pts)
|
||||
self.scan_line = image_viewer.ax.plot(x, y, **self.line_kwargs)[0]
|
||||
self.artists.append(self.scan_line)
|
||||
|
||||
scan_data = profile_line(image, self.end_pts)
|
||||
self.profile = self.ax.plot(scan_data, 'k-')[0]
|
||||
self._autoscale_view()
|
||||
|
||||
self.connect_image_event('key_press_event', self.on_key_press)
|
||||
self.connect_image_event('button_press_event', self.on_mouse_press)
|
||||
self.connect_image_event('button_release_event', self.on_mouse_release)
|
||||
self.connect_image_event('motion_notify_event', self.on_move)
|
||||
self.connect_image_event('scroll_event', self.on_scroll)
|
||||
|
||||
self.image_viewer.redraw()
|
||||
|
||||
def help(self):
|
||||
helpstr = ("Line profile tool",
|
||||
"+ and - keys or mouse scroll changes width of scan line.",
|
||||
"Select and drag ends of the scan line to adjust it.")
|
||||
return '\n'.join(helpstr)
|
||||
|
||||
def get_profile(self):
|
||||
"""Return intensity profile of the selected line.
|
||||
|
||||
Returns
|
||||
-------
|
||||
end_pts: (2, 2) array
|
||||
The positions ((x1, y1), (x2, y2)) of the line ends.
|
||||
profile: 1d array
|
||||
Profile of intensity values.
|
||||
"""
|
||||
end_pts = self.scan_line.get_xydata()
|
||||
profile = self.profile.get_ydata()
|
||||
return end_pts, profile
|
||||
|
||||
def on_scroll(self, event):
|
||||
if not event.inaxes: return
|
||||
if event.button == 'up':
|
||||
self._thicken_scan_line()
|
||||
elif event.button == 'down':
|
||||
self._shrink_scan_line()
|
||||
|
||||
def on_key_press(self, event):
|
||||
if not event.inaxes: return
|
||||
elif event.key == '+':
|
||||
self._thicken_scan_line()
|
||||
elif event.key == '-':
|
||||
self._shrink_scan_line()
|
||||
elif event.key == 'r':
|
||||
self.reset()
|
||||
|
||||
def _thicken_scan_line(self):
|
||||
self.linewidth += 1
|
||||
self.line_changed(None, None)
|
||||
|
||||
def _shrink_scan_line(self):
|
||||
if self.linewidth > 1:
|
||||
self.linewidth -= 1
|
||||
self.line_changed(None, None)
|
||||
|
||||
def _autoscale_view(self):
|
||||
if self.limits is None:
|
||||
self.ax.autoscale_view(tight=True)
|
||||
else:
|
||||
self.ax.autoscale_view(scaley=False, tight=True)
|
||||
|
||||
def get_pt_under_cursor(self, event):
|
||||
"""Return index of the end point under cursor, if sufficiently close"""
|
||||
xy = np.asarray(self.scan_line.get_xydata())
|
||||
xyt = self.scan_line.get_transform().transform(xy)
|
||||
xt, yt = xyt[:, 0], xyt[:, 1]
|
||||
d = np.sqrt((xt - event.x)**2 + (yt - event.y)**2)
|
||||
indseq = np.nonzero(np.equal(d, np.amin(d)))[0]
|
||||
ind = indseq[0]
|
||||
if d[ind] >= self.epsilon:
|
||||
ind = None
|
||||
return ind
|
||||
|
||||
def on_mouse_press(self, event):
|
||||
if event.button != 1: return
|
||||
if event.inaxes==None: return
|
||||
self._active_pt = self.get_pt_under_cursor(event)
|
||||
|
||||
def on_mouse_release(self, event):
|
||||
if event.button != 1: return
|
||||
self._active_pt = None
|
||||
|
||||
def on_move(self, event):
|
||||
if event.button != 1: return
|
||||
if self._active_pt is None: return
|
||||
if not self.image_viewer.ax.in_axes(event): return
|
||||
x,y = event.xdata, event.ydata
|
||||
self.line_changed(x, y)
|
||||
|
||||
def reset(self):
|
||||
self.end_pts = self._init_end_pts.copy()
|
||||
self.scan_line.set_data(np.transpose(self.end_pts))
|
||||
self.line_changed(None, None)
|
||||
|
||||
def line_changed(self, x, y):
|
||||
if x is not None:
|
||||
self.end_pts[self._active_pt, :] = x, y
|
||||
self.scan_line.set_data(np.transpose(self.end_pts))
|
||||
self.scan_line.set_linewidth(self.linewidth)
|
||||
|
||||
scan = profile_line(self.image_viewer.original_image, self.end_pts,
|
||||
linewidth=self.linewidth)
|
||||
self.profile.set_xdata(np.arange(scan.shape[0]))
|
||||
self.profile.set_ydata(scan)
|
||||
|
||||
self.ax.relim()
|
||||
|
||||
if self.useblit:
|
||||
self.image_viewer.canvas.restore_region(self.img_background)
|
||||
self.ax.draw_artist(self.scan_line)
|
||||
self.ax.draw_artist(self.profile)
|
||||
self.image_viewer.canvas.blit(self.image_viewer.ax.bbox)
|
||||
|
||||
self._autoscale_view()
|
||||
|
||||
self.image_viewer.redraw()
|
||||
self.redraw()
|
||||
|
||||
|
||||
def profile_line(img, end_pts, linewidth=1):
|
||||
"""Return the intensity profile of an image measured along a scan line.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
img : 2d array
|
||||
The image.
|
||||
end_pts: (2, 2) list
|
||||
End points ((x1, y1), (x2, y2)) of scan line.
|
||||
linewidth: int
|
||||
Width of the scan, perpendicular to the line
|
||||
|
||||
Returns
|
||||
-------
|
||||
return_value : array
|
||||
The intensity profile along the scan line. The length of the profile
|
||||
is the ceil of the computed length of the scan line.
|
||||
"""
|
||||
point1, point2 = end_pts
|
||||
x1, y1 = point1 = np.asarray(point1, dtype = float)
|
||||
x2, y2 = point2 = np.asarray(point2, dtype = float)
|
||||
dx, dy = point2 - point1
|
||||
|
||||
# Quick calculation if perfectly horizontal or vertical (remove?)
|
||||
if x1 == x2:
|
||||
pixels = img[min(y1, y2) : max(y1, y2)+1,
|
||||
x1 - linewidth / 2 : x1 + linewidth / 2 + 1]
|
||||
intensities = pixels.mean(axis = 1)
|
||||
return intensities
|
||||
elif y1 == y2:
|
||||
pixels = img[y1 - linewidth / 2 : y1 + linewidth / 2 + 1,
|
||||
min(x1, x2) : max(x1, x2)+1]
|
||||
intensities = pixels.mean(axis = 0)
|
||||
return intensities
|
||||
|
||||
theta = np.arctan2(dy,dx)
|
||||
a = dy/dx
|
||||
b = y1 - a * x1
|
||||
length = np.hypot(dx, dy)
|
||||
|
||||
line_x = np.linspace(min(x1, x2), max(x1, x2), np.ceil(length))
|
||||
line_y = line_x * a + b
|
||||
y_width = abs(linewidth * np.cos(theta)/2)
|
||||
perp_ys = np.array([np.linspace(yi - y_width,
|
||||
yi + y_width, linewidth) for yi in line_y])
|
||||
perp_xs = - a * perp_ys + (line_x + a * line_y)[:, np.newaxis]
|
||||
|
||||
perp_lines = np.array([perp_ys, perp_xs])
|
||||
pixels = ndi.map_coordinates(img, perp_lines)
|
||||
intensities = pixels.mean(axis=1)
|
||||
|
||||
return intensities
|
||||
@@ -0,0 +1,50 @@
|
||||
import numpy as np
|
||||
from PyQt4 import QtGui
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
from ..utils import MatplotlibCanvas
|
||||
from .base import Plugin
|
||||
|
||||
|
||||
class PlotCanvas(MatplotlibCanvas):
|
||||
"""Canvas for displaying images.
|
||||
|
||||
This canvas derives from Matplotlib, and has attributes `fig` and `ax`,
|
||||
which point to Matplotlib figure and axes.
|
||||
"""
|
||||
def __init__(self, parent, height, width, **kwargs):
|
||||
self.fig, self.ax = plt.subplots(figsize=(height, width), **kwargs)
|
||||
super(PlotCanvas, self).__init__(parent, self.fig, **kwargs)
|
||||
self.setMinimumHeight(150)
|
||||
|
||||
class PlotPlugin(Plugin):
|
||||
"""Plugin for ImageViewer that contains a plot canvas.
|
||||
|
||||
Base class for plugins that contain a Matplotlib plot canvas, which can,
|
||||
for example, display an image histogram.
|
||||
|
||||
See base Plugin class for additional details.
|
||||
"""
|
||||
|
||||
def attach(self, image_viewer):
|
||||
super(PlotPlugin, self).attach(image_viewer)
|
||||
# Add plot for displaying intensity profile.
|
||||
self.add_plot()
|
||||
|
||||
def redraw(self):
|
||||
"""Redraw plot."""
|
||||
self.canvas.draw_idle()
|
||||
|
||||
def add_plot(self, height=4, width=4):
|
||||
self.canvas = PlotCanvas(self, height, width)
|
||||
self.fig = self.canvas.fig
|
||||
#TODO: Converted color is slightly different than Qt background.
|
||||
qpalette = QtGui.QPalette()
|
||||
qcolor = qpalette.color(QtGui.QPalette.Window)
|
||||
bgcolor = qcolor.toRgb().value()
|
||||
if np.isscalar(bgcolor):
|
||||
bgcolor = str(bgcolor / 255.)
|
||||
self.fig.patch.set_facecolor(bgcolor)
|
||||
self.ax = self.canvas.ax
|
||||
self.layout.addWidget(self.canvas, self.row, 0)
|
||||
@@ -0,0 +1,9 @@
|
||||
from skimage import data
|
||||
from skimage.viewer import ImageViewer
|
||||
from skimage.viewer.plugins.lineprofile import LineProfile
|
||||
|
||||
|
||||
image = data.camera()
|
||||
viewer = ImageViewer(image)
|
||||
viewer += LineProfile()
|
||||
viewer.show()
|
||||
Reference in New Issue
Block a user