import warnings import numpy as np from skimage.util.dtype import dtype_range from skimage import draw from skimage import measure from .plotplugin import PlotPlugin from ..canvastools import ThickLineTool __all__ = ['LineProfile'] class LineProfile(PlotPlugin): """Plugin to compute interpolated intensity under a scan line on an image. See PlotPlugin and Plugin classes for additional details. Parameters ---------- maxdist : float Maximum pixel distance allowed when selecting end point of scan line. epsilon : float Deprecated. Use `maxdist` instead. 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' def __init__(self, maxdist=10, epsilon='deprecated', limits='image', **kwargs): super(LineProfile, self).__init__(**kwargs) if not epsilon == 'deprecated': warnings.warn("Parameter `epsilon` deprecated; use `maxdist`.") maxdist = epsilon self.maxdist = maxdist self._limit_type = limits 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._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[0:2] x = [w / 3, 2 * w / 3] y = [h / 2] * 2 self.line_tool = ThickLineTool(self.image_viewer.ax, maxdist=self.maxdist, on_move=self.line_changed, on_change=self.line_changed) self.line_tool.end_points = np.transpose([x, y]) scan_data = measure.profile_line(image, *self.line_tool.end_points[:, ::-1]) self.scan_data = scan_data if scan_data.ndim == 1: scan_data = scan_data[:, np.newaxis] self.reset_axes(scan_data) self._autoscale_view() 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_profiles(self): """Return intensity profile of the selected line. Returns ------- end_points: (2, 2) array The positions ((x1, y1), (x2, y2)) of the line ends. profile: list of 1d arrays Profile of intensity values. Length 1 (grayscale) or 3 (rgb). """ profiles = [data.get_ydata() for data in self.profile] return self.line_tool.end_points, profiles 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 line_changed(self, end_points): x, y = np.transpose(end_points) self.line_tool.end_points = end_points scan = measure.profile_line(self.image_viewer.original_image, *end_points[:, ::-1], linewidth=self.line_tool.linewidth) self.scan_data = scan if scan.ndim == 1: scan = scan[:, np.newaxis] if scan.shape[1] != len(self.profile): self.reset_axes(scan) for i in range(len(scan[0])): self.profile[i].set_xdata(np.arange(scan.shape[0])) self.profile[i].set_ydata(scan[:, i]) self.ax.relim() self._autoscale_view() self.redraw() def reset_axes(self, scan_data): # Clear lines out for line in self.ax.lines: self.ax.lines = [] if scan_data.shape[1] == 1: self.profile = self.ax.plot(scan_data, 'k-') else: self.profile = self.ax.plot(scan_data[:, 0], 'r-', scan_data[:, 1], 'g-', scan_data[:, 2], 'b-') def output(self): """Return the drawn line and the resulting scan. Returns ------- line_image : (M, N) uint8 array, same shape as image An array of 0s with the scanned line set to 255. scan : (P,) or (P, 3) array of int or float The line scan values across the image. """ (x1, y1), (x2, y2) = self.line_tool.end_points line_image = np.zeros(self.image_viewer.original_image.shape[:2], np.uint8) rr, cc = draw.line(y1, x1, y2, x2) line_image[rr, cc] = 255 return line_image, self.scan_data