From 4f6f25efe9d7d1e3167856a0c3736af0c4af55ec Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Thu, 1 Nov 2012 21:48:12 -0400 Subject: [PATCH 001/215] ENH: Add canvastools subpckages with base class and line tools --- skimage/viewer/canvastools/__init__.py | 1 + skimage/viewer/canvastools/base.py | 109 +++++++++++++++ skimage/viewer/canvastools/line_tool.py | 169 ++++++++++++++++++++++++ 3 files changed, 279 insertions(+) create mode 100644 skimage/viewer/canvastools/__init__.py create mode 100644 skimage/viewer/canvastools/base.py create mode 100644 skimage/viewer/canvastools/line_tool.py diff --git a/skimage/viewer/canvastools/__init__.py b/skimage/viewer/canvastools/__init__.py new file mode 100644 index 00000000..a04d4188 --- /dev/null +++ b/skimage/viewer/canvastools/__init__.py @@ -0,0 +1 @@ +from line_tool import LineTool, ThickLineTool diff --git a/skimage/viewer/canvastools/base.py b/skimage/viewer/canvastools/base.py new file mode 100644 index 00000000..f14b431e --- /dev/null +++ b/skimage/viewer/canvastools/base.py @@ -0,0 +1,109 @@ +import numpy as np +import matplotlib as mpl +from matplotlib import lines + + +__all__ = ['CanvasToolBase', 'ToolHandles'] + + +class CanvasToolBase(object): + """Base canvas tool for matplotlib axes. + + Parameters + ---------- + """ + def __init__(self, ax, useblit=None): + self.ax = ax + self.canvas = ax.figure.canvas + self.cids = [] + self._artists = [] + self.active = True + + if useblit is None: + useblit = True if mpl.backends.backend.endswith('Agg') else False + self.useblit = useblit + if useblit: + bbox = self.ax.bbox + self.img_background = self.canvas.copy_from_bbox(bbox) + + def connect_event(self, event, callback): + """Connect callback with an event. + + This should be used in lieu of `figure.canvas.mpl_connect` since this + function stores call back ids for later clean up. + """ + cid = self.canvas.mpl_connect(event, callback) + self.cids.append(cid) + + def disconnect_events(self): + """Disconnect all events created by this widget.""" + for c in self.cids: + self.canvas.mpl_disconnect(c) + + def ignore(self, event): + """Return True if event should be ignored. + + This method (or a version of it) should be called at the beginning + of any event callback. + """ + return not self.active + + def set_visible(self, val): + for a in self._artists: + a.set_visible(val) + + +class ToolHandles(object): + """Control handles for canvas tools. + + Parameters + ---------- + ax : :class:`matplotlib.axes.Axes` + Matplotlib axes where tool handles are displayed. + x, y : 1D arrays + Coordinates of control handles. + marker : str + Shape of marker used to display handle. See `matplotlib.pyplot.plot`. + marker_props : see :class:`matplotlib.lines.Line2D`. + """ + def __init__(self, ax, x, y, marker='o', markerprops=None): + self.ax = ax + + props = dict(mfc='w', ls='none', alpha=0.5, visible=False) + props.update(markerprops if markerprops is not None else {}) + self._markers = lines.Line2D(x, y, marker=marker, **props) + self.ax.add_line(self._markers) + self.artist = self._markers + + @property + def x(self): + return self._markers.get_xdata() + + @property + def y(self): + return self._markers.get_ydata() + + def set_data(self, pts, y=None): + """Set x and y positions of handles""" + if y is not None: + x = pts + pts = np.array([x, y]) + self._markers.set_data(pts) + + def set_visible(self, val): + self._markers.set_visible(val) + + def set_animated(self, val): + self._markers.set_animated(val) + + def draw(self): + self.ax.draw_artist(self._markers) + + def closest(self, x, y): + """Return index and pixel distance to closest index.""" + pts = np.transpose((self.x, self.y)) + # Transform data coordinates to pixel coordinates. + pts = self.ax.transData.transform(pts) + diff = pts - ((x, y)) + dist = np.sqrt(np.sum(diff**2, axis=1)) + return np.argmin(dist), np.min(dist) diff --git a/skimage/viewer/canvastools/line_tool.py b/skimage/viewer/canvastools/line_tool.py new file mode 100644 index 00000000..7dd3d064 --- /dev/null +++ b/skimage/viewer/canvastools/line_tool.py @@ -0,0 +1,169 @@ +import numpy as np +from matplotlib import lines + +from base import CanvasToolBase, ToolHandles + + +__all__ = ['LineTool', 'ThickLineTool'] + + +class LineTool(CanvasToolBase): + """ + Parameters + ---------- + on_update : function + Function accepting end points of line as the only argument. + + Attributes + ---------- + end_pts : 2D array + End points of line ((x1, y1), (x2, y2)). + """ + def __init__(self, ax, x, y, on_update=None, on_enter=None, maxdist=10, + lineprops=None): + super(LineTool, self).__init__(ax) + + #TODO: Figure out how to cleanly restore image background for useblit + self.useblit = False + + props = dict(color='r', linewidth=1, alpha=0.4, solid_capstyle='butt') + props.update(lineprops if lineprops is not None else {}) + self.linewidth = props['linewidth'] + self.maxdist = maxdist + self._active_pt = None + + if on_update is None: + on_update = lambda *args: None + self.on_update = on_update + + if on_enter is None: + on_enter = lambda *args: None + self.on_enter = on_enter + + self._init_end_pts = np.transpose([x, y]) + self.end_pts = self._init_end_pts.copy() + + self._line = lines.Line2D(x, y, **props) + ax.add_line(self._line) + + self._handles = ToolHandles(ax, x, y) + self._handles.set_visible(True) + self._artists = [self._line, self._handles.artist] + + self.connect_event('button_press_event', self.on_mouse_press) + self.connect_event('button_release_event', self.on_mouse_release) + self.connect_event('motion_notify_event', self.on_move) + + def on_mouse_press(self, event): + if event.button != 1: + return + if event.inaxes == None: + return + idx, px_dist = self._handles.closest(event.x, event.y) + if px_dist < self.maxdist: + self._active_pt = idx + + 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.ax.in_axes(event): + return + x, y = event.xdata, event.ydata + self.update(x, y) + + def on_key_press(self, event): + if event.key == 'enter': + self.on_enter(self.end_pts) + self.set_visible(False) + self.redraw() + + def reset(self): + self.end_pts = self._init_end_pts.copy() + self._line.set_data(np.transpose(self.end_pts)) + self._handles.set_data(np.transpose(self.end_pts)) + self.update(None, None) + + def update(self, x, y): + if x is not None: + self.end_pts[self._active_pt, :] = x, y + self._line.set_data(np.transpose(self.end_pts)) + self._handles.set_data(np.transpose(self.end_pts)) + self._line.set_linewidth(self.linewidth) + + self.ax.relim() + self.redraw() + + self.on_update(self.end_pts) + + def redraw(self): + if self.useblit: + # self.canvas.restore_region(self.img_background) + self.ax.draw_artist(self._line) + self.canvas.blit(self.ax.bbox) + else: + self.canvas.draw_idle() + + +class ThickLineTool(LineTool): + + def __init__(self, ax, x, y, on_update=None, on_enter=None, maxdist=10, + lineprops=None): + super(ThickLineTool, self).__init__(ax, x, y, on_update=on_update, + on_enter=on_enter, maxdist=maxdist, + lineprops=lineprops) + + self.connect_event('key_press_event', self.on_key_press) + self.connect_event('scroll_event', self.on_scroll) + + 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): + super(ThickLineTool, self).on_key_press(event) + + if 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.update(None, None) + + def _shrink_scan_line(self): + if self.linewidth > 1: + self.linewidth -= 1 + self.update(None, None) + + +if __name__ == '__main__': + import matplotlib.pyplot as plt + from skimage import data + + image = data.camera() + + f, ax = plt.subplots() + ax.imshow(image, interpolation='nearest') + h, w = image.shape + + def printer(pts): + x, y = np.transpose(pts) + print "length = %0.2f" % np.sqrt(np.diff(x)**2 + np.diff(y)**2) + + # line_tool = LineTool(ax, [w/3, 2*w/3], [h/2, h/2]) + line_tool = ThickLineTool(ax, [w/3, 2*w/3], [h/2, h/2], on_enter=printer) + plt.show() From 2ff3e78c52e4e5baa051076f85a0cac75e9dee92 Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Fri, 2 Nov 2012 23:47:29 -0400 Subject: [PATCH 002/215] BUG: Move callback connection to parent class --- skimage/viewer/canvastools/line_tool.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skimage/viewer/canvastools/line_tool.py b/skimage/viewer/canvastools/line_tool.py index 7dd3d064..eab78fb9 100644 --- a/skimage/viewer/canvastools/line_tool.py +++ b/skimage/viewer/canvastools/line_tool.py @@ -53,6 +53,7 @@ class LineTool(CanvasToolBase): self.connect_event('button_press_event', self.on_mouse_press) self.connect_event('button_release_event', self.on_mouse_release) self.connect_event('motion_notify_event', self.on_move) + self.connect_event('key_press_event', self.on_key_press) def on_mouse_press(self, event): if event.button != 1: @@ -119,7 +120,6 @@ class ThickLineTool(LineTool): on_enter=on_enter, maxdist=maxdist, lineprops=lineprops) - self.connect_event('key_press_event', self.on_key_press) self.connect_event('scroll_event', self.on_scroll) def on_scroll(self, event): From 5be58130707b2682c4fc1a9b787fe91e71b449f4 Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Sun, 4 Nov 2012 21:42:47 -0500 Subject: [PATCH 003/215] STY: Reuse ThickLineTool in LineProfile --- skimage/viewer/plugins/lineprofile.py | 128 ++++++-------------------- 1 file changed, 26 insertions(+), 102 deletions(-) diff --git a/skimage/viewer/plugins/lineprofile.py b/skimage/viewer/plugins/lineprofile.py index 69c9ebfb..09aa5d7e 100644 --- a/skimage/viewer/plugins/lineprofile.py +++ b/skimage/viewer/plugins/lineprofile.py @@ -1,14 +1,15 @@ +import warnings + import numpy as np import scipy.ndimage as ndi from skimage.util.dtype import dtype_range from .plotplugin import PlotPlugin +from ..canvastools import ThickLineTool __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. @@ -19,8 +20,10 @@ class LineProfile(PlotPlugin): ---------- linewidth : float Line width for interpolation. Wider lines average over more pixels. - epsilon : float + 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: @@ -32,14 +35,15 @@ class LineProfile(PlotPlugin): name = 'Line Profile' draws_on_image = True - def __init__(self, linewidth=1, epsilon=5, limits='image', **kwargs): + def __init__(self, linewidth=1, maxdist=5, epsilon='deprecated', + limits='image', **kwargs): super(LineProfile, self).__init__(**kwargs) - self.linewidth = linewidth - self.epsilon = epsilon - self._active_pt = None + + if not epsilon == 'deprecated': + warnings.warn("Parameter `epsilon` deprecated; use `maxdist`.") + maxdist = epsilon + self.maxdist = maxdist 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): @@ -50,7 +54,7 @@ class LineProfile(PlotPlugin): 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] + 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: @@ -60,25 +64,18 @@ class LineProfile(PlotPlugin): 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 = [w / 3, 2 * w / 3] + y = [h / 2] * 2 + self.end_pts = np.transpose([x, y]) - 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) + self.line_tool = ThickLineTool(self.image_viewer.ax, x, y, + maxdist=self.maxdist, + on_update=self.line_changed) 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.", @@ -95,36 +92,8 @@ class LineProfile(PlotPlugin): 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) + return self.end_pts, profile def _autoscale_view(self): if self.limits is None: @@ -132,67 +101,22 @@ class LineProfile(PlotPlugin): 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 line_changed(self, end_pts): + x, y = np.transpose(end_pts) - 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) + self.end_pts = end_pts + scan = profile_line(self.image_viewer.original_image, end_pts, + linewidth=self.line_tool.linewidth) - 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() From d219a76f5808c415be93031a0b79ba2737dd8e71 Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Thu, 15 Nov 2012 21:56:51 -0500 Subject: [PATCH 004/215] BUG: Fix blitting behavior in canvastools --- skimage/viewer/canvastools/base.py | 4 ++-- skimage/viewer/canvastools/line_tool.py | 8 +++----- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/skimage/viewer/canvastools/base.py b/skimage/viewer/canvastools/base.py index f14b431e..68f84726 100644 --- a/skimage/viewer/canvastools/base.py +++ b/skimage/viewer/canvastools/base.py @@ -23,8 +23,8 @@ class CanvasToolBase(object): useblit = True if mpl.backends.backend.endswith('Agg') else False self.useblit = useblit if useblit: - bbox = self.ax.bbox - self.img_background = self.canvas.copy_from_bbox(bbox) + self.canvas.draw() + self.img_background = self.canvas.copy_from_bbox(self.ax.bbox) def connect_event(self, event, callback): """Connect callback with an event. diff --git a/skimage/viewer/canvastools/line_tool.py b/skimage/viewer/canvastools/line_tool.py index eab78fb9..8f1cd25a 100644 --- a/skimage/viewer/canvastools/line_tool.py +++ b/skimage/viewer/canvastools/line_tool.py @@ -23,9 +23,6 @@ class LineTool(CanvasToolBase): lineprops=None): super(LineTool, self).__init__(ax) - #TODO: Figure out how to cleanly restore image background for useblit - self.useblit = False - props = dict(color='r', linewidth=1, alpha=0.4, solid_capstyle='butt') props.update(lineprops if lineprops is not None else {}) self.linewidth = props['linewidth'] @@ -105,8 +102,9 @@ class LineTool(CanvasToolBase): def redraw(self): if self.useblit: - # self.canvas.restore_region(self.img_background) - self.ax.draw_artist(self._line) + self.canvas.restore_region(self.img_background) + for artist in self._artists: + self.ax.draw_artist(artist) self.canvas.blit(self.ax.bbox) else: self.canvas.draw_idle() From 0d411e21dc6196b78e58cbf0dc0d534d6d7d5322 Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Thu, 15 Nov 2012 22:04:02 -0500 Subject: [PATCH 005/215] STY: Refactor parts of LineTool to CanvasToolBase --- skimage/viewer/canvastools/base.py | 19 ++++++++++++++++++- skimage/viewer/canvastools/line_tool.py | 20 ++------------------ 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/skimage/viewer/canvastools/base.py b/skimage/viewer/canvastools/base.py index 68f84726..e73dcf24 100644 --- a/skimage/viewer/canvastools/base.py +++ b/skimage/viewer/canvastools/base.py @@ -12,7 +12,7 @@ class CanvasToolBase(object): Parameters ---------- """ - def __init__(self, ax, useblit=None): + def __init__(self, ax, useblit=None, on_update=None, on_enter=None): self.ax = ax self.canvas = ax.figure.canvas self.cids = [] @@ -26,6 +26,14 @@ class CanvasToolBase(object): self.canvas.draw() self.img_background = self.canvas.copy_from_bbox(self.ax.bbox) + if on_update is None: + on_update = lambda *args: None + self.on_update = on_update + + if on_enter is None: + on_enter = lambda *args: None + self.on_enter = on_enter + def connect_event(self, event, callback): """Connect callback with an event. @@ -52,6 +60,15 @@ class CanvasToolBase(object): for a in self._artists: a.set_visible(val) + def redraw(self): + if self.useblit: + self.canvas.restore_region(self.img_background) + for artist in self._artists: + self.ax.draw_artist(artist) + self.canvas.blit(self.ax.bbox) + else: + self.canvas.draw_idle() + class ToolHandles(object): """Control handles for canvas tools. diff --git a/skimage/viewer/canvastools/line_tool.py b/skimage/viewer/canvastools/line_tool.py index 8f1cd25a..c9e4f420 100644 --- a/skimage/viewer/canvastools/line_tool.py +++ b/skimage/viewer/canvastools/line_tool.py @@ -21,7 +21,8 @@ class LineTool(CanvasToolBase): """ def __init__(self, ax, x, y, on_update=None, on_enter=None, maxdist=10, lineprops=None): - super(LineTool, self).__init__(ax) + super(LineTool, self).__init__(ax, on_update=on_update, + on_enter=on_enter) props = dict(color='r', linewidth=1, alpha=0.4, solid_capstyle='butt') props.update(lineprops if lineprops is not None else {}) @@ -29,14 +30,6 @@ class LineTool(CanvasToolBase): self.maxdist = maxdist self._active_pt = None - if on_update is None: - on_update = lambda *args: None - self.on_update = on_update - - if on_enter is None: - on_enter = lambda *args: None - self.on_enter = on_enter - self._init_end_pts = np.transpose([x, y]) self.end_pts = self._init_end_pts.copy() @@ -100,15 +93,6 @@ class LineTool(CanvasToolBase): self.on_update(self.end_pts) - def redraw(self): - if self.useblit: - self.canvas.restore_region(self.img_background) - for artist in self._artists: - self.ax.draw_artist(artist) - self.canvas.blit(self.ax.bbox) - else: - self.canvas.draw_idle() - class ThickLineTool(LineTool): From 9d653fec78e8f2c2456cbfa90d6a68c0c46ff734 Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Thu, 15 Nov 2012 23:53:47 -0500 Subject: [PATCH 006/215] BUG: Clean up imports --- skimage/viewer/canvastools/base.py | 8 ++++++-- skimage/viewer/canvastools/line_tool.py | 6 +++++- skimage/viewer/plugins/plotplugin.py | 12 ++++++++++-- viewer_examples/viewers/collection_viewer.py | 1 - 4 files changed, 21 insertions(+), 6 deletions(-) diff --git a/skimage/viewer/canvastools/base.py b/skimage/viewer/canvastools/base.py index e73dcf24..288d12ea 100644 --- a/skimage/viewer/canvastools/base.py +++ b/skimage/viewer/canvastools/base.py @@ -1,6 +1,10 @@ import numpy as np -import matplotlib as mpl -from matplotlib import lines + +try: + import matplotlib as mpl + from matplotlib import lines +except ImportError: + print("Could not import matplotlib -- skimage.viewer not available.") __all__ = ['CanvasToolBase', 'ToolHandles'] diff --git a/skimage/viewer/canvastools/line_tool.py b/skimage/viewer/canvastools/line_tool.py index c9e4f420..f3764df7 100644 --- a/skimage/viewer/canvastools/line_tool.py +++ b/skimage/viewer/canvastools/line_tool.py @@ -1,5 +1,9 @@ import numpy as np -from matplotlib import lines + +try: + from matplotlib import lines +except ImportError: + print("Could not import matplotlib -- skimage.viewer not available.") from base import CanvasToolBase, ToolHandles diff --git a/skimage/viewer/plugins/plotplugin.py b/skimage/viewer/plugins/plotplugin.py index fa06088d..d3d4ae63 100644 --- a/skimage/viewer/plugins/plotplugin.py +++ b/skimage/viewer/plugins/plotplugin.py @@ -1,7 +1,14 @@ import numpy as np -from PyQt4 import QtGui -import matplotlib.pyplot as plt +try: + from PyQt4 import QtGui +except ImportError: + print("Could not import PyQt4 -- skimage.viewer not available.") + +try: + import matplotlib.pyplot as plt +except ImportError: + print("Could not import matplotlib -- skimage.viewer not available.") from ..utils import MatplotlibCanvas from .base import Plugin @@ -18,6 +25,7 @@ class PlotCanvas(MatplotlibCanvas): super(PlotCanvas, self).__init__(parent, self.fig, **kwargs) self.setMinimumHeight(150) + class PlotPlugin(Plugin): """Plugin for ImageViewer that contains a plot canvas. diff --git a/viewer_examples/viewers/collection_viewer.py b/viewer_examples/viewers/collection_viewer.py index 62cdbb26..61df3dd9 100644 --- a/viewer_examples/viewers/collection_viewer.py +++ b/viewer_examples/viewers/collection_viewer.py @@ -18,7 +18,6 @@ home/end keys First/last image in collection. """ -import numpy as np from skimage import data from skimage.viewer import CollectionViewer from skimage.transform import pyramid_gaussian From ef2bb54c455c7384821ff744a6a0db701feef7b7 Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Sun, 9 Dec 2012 20:51:18 -0500 Subject: [PATCH 007/215] Fix figure canvas creation. Avoid use of matplotlib's pyplot functions for creating figure canvases. In particular, key press events were ignored by the canvas. --- skimage/viewer/plugins/plotplugin.py | 24 ++---- skimage/viewer/utils/core.py | 121 ++++++++++++++++++--------- skimage/viewer/viewers/core.py | 18 ++-- 3 files changed, 95 insertions(+), 68 deletions(-) diff --git a/skimage/viewer/plugins/plotplugin.py b/skimage/viewer/plugins/plotplugin.py index d3d4ae63..850d0b71 100644 --- a/skimage/viewer/plugins/plotplugin.py +++ b/skimage/viewer/plugins/plotplugin.py @@ -5,25 +5,11 @@ try: except ImportError: print("Could not import PyQt4 -- skimage.viewer not available.") -try: - import matplotlib.pyplot as plt -except ImportError: - print("Could not import matplotlib -- skimage.viewer not available.") - -from ..utils import MatplotlibCanvas +from ..utils import new_plot 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) +__all__ = ['PlotPlugin'] class PlotPlugin(Plugin): @@ -45,8 +31,9 @@ class PlotPlugin(Plugin): self.canvas.draw_idle() def add_plot(self, height=4, width=4): - self.canvas = PlotCanvas(self, height, width) - self.fig = self.canvas.fig + self.fig, self.ax = new_plot(figsize=(height, width)) + self.canvas = self.fig.canvas + self.canvas.setMinimumHeight(150) #TODO: Converted color is slightly different than Qt background. qpalette = QtGui.QPalette() qcolor = qpalette.color(QtGui.QPalette.Window) @@ -54,5 +41,4 @@ class PlotPlugin(Plugin): 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) diff --git a/skimage/viewer/utils/core.py b/skimage/viewer/utils/core.py index cf632d5a..9646bbd6 100644 --- a/skimage/viewer/utils/core.py +++ b/skimage/viewer/utils/core.py @@ -3,8 +3,11 @@ import warnings import numpy as np try: - import matplotlib.pyplot as plt + import matplotlib as mpl + from matplotlib.figure import Figure + from matplotlib import _pylab_helpers from matplotlib.colors import LinearSegmentedColormap + from matplotlib.backends.backend_qt4 import FigureManagerQT from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg except ImportError: FigureCanvasQTAgg = object # hack to prevent nosetest and autodoc errors @@ -18,7 +21,7 @@ except ImportError: __all__ = ['init_qtapp', 'start_qtapp', 'RequiredAttr', 'figimage', - 'LinearColormap', 'ClearColormap', 'MatplotlibCanvas'] + 'LinearColormap', 'ClearColormap', 'FigureCanvas', 'new_plot'] QApp = None @@ -55,38 +58,6 @@ class RequiredAttr(object): self.val = val -def figimage(image, scale=1, dpi=None, **kwargs): - """Return figure and axes with figure tightly surrounding image. - - Unlike pyplot.figimage, this actually plots onto an axes object, which - fills the figure. Plotting the image onto an axes allows for subsequent - overlays of axes artists. - - Parameters - ---------- - image : array - image to plot - scale : float - If scale is 1, the figure and axes have the same dimension as the - image. Smaller values of `scale` will shrink the figure. - dpi : int - Dots per inch for figure. If None, use the default rcParam. - """ - dpi = dpi if dpi is not None else plt.rcParams['figure.dpi'] - kwargs.setdefault('interpolation', 'nearest') - kwargs.setdefault('cmap', 'gray') - - h, w, d = np.atleast_3d(image).shape - figsize = np.array((w, h), dtype=float) / dpi * scale - - fig, ax = plt.subplots(figsize=figsize, dpi=dpi) - fig.subplots_adjust(left=0, bottom=0, right=1, top=1) - - ax.set_axis_off() - ax.imshow(image, **kwargs) - return fig, ax - - class LinearColormap(LinearSegmentedColormap): """LinearSegmentedColormap in which color varies smoothly. @@ -124,14 +95,88 @@ class ClearColormap(LinearColormap): LinearColormap.__init__(self, name, cg_speq) -class MatplotlibCanvas(FigureCanvasQTAgg): +class FigureCanvas(FigureCanvasQTAgg): """Canvas for displaying images.""" - def __init__(self, parent, figure, **kwargs): + def __init__(self, figure, **kwargs): self.fig = figure FigureCanvasQTAgg.__init__(self, self.fig) FigureCanvasQTAgg.setSizePolicy(self, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) FigureCanvasQTAgg.updateGeometry(self) - # Note: `setParent` must be called after `FigureCanvasQTAgg.__init__`. - self.setParent(parent) + + #TODO: Consider overriding Matplotlib key-event handling + # def keyPressEvent(self, event): + # # Override key events defined by Matplotlib + # event.ignore() + + # def keyReleaseEvent(self, event): + # # Override key events defined by Matplotlib + # event.ignore() + + +def new_canvas(*args, **kwargs): + """Return a new figure canvas.""" + allnums = _pylab_helpers.Gcf.figs.keys() + num = max(allnums) + 1 if allnums else 1 + + FigureClass = kwargs.pop('FigureClass', Figure) + figure = FigureClass(*args, **kwargs) + canvas = FigureCanvas(figure) + fig_manager = FigureManagerQT(canvas, num) + return fig_manager.canvas + + +def new_plot(parent=None, subplot_kw=None, **fig_kw): + """Return new figure and axes. + + Parameters + ---------- + parent : QtWidget + Qt widget that displays the plot objects. If None, you must manually + call ``canvas.setParent`` and pass the parent widget. + subplot_kw : dict + Keyword arguments passed ``matplotlib.figure.Figure.add_subplot``. + fig_kw : dict + Keyword arguments passed ``matplotlib.figure.Figure``. + """ + if subplot_kw is None: + subplot_kw = {} + canvas = new_canvas(**fig_kw) + canvas.setParent(parent) + + fig = canvas.figure + ax = fig.add_subplot(1, 1, 1, **subplot_kw) + return fig, ax + + +def figimage(image, scale=1, dpi=None, **kwargs): + """Return figure and axes with figure tightly surrounding image. + + Unlike pyplot.figimage, this actually plots onto an axes object, which + fills the figure. Plotting the image onto an axes allows for subsequent + overlays of axes artists. + + Parameters + ---------- + image : array + image to plot + scale : float + If scale is 1, the figure and axes have the same dimension as the + image. Smaller values of `scale` will shrink the figure. + dpi : int + Dots per inch for figure. If None, use the default rcParam. + """ + dpi = dpi if dpi is not None else mpl.rcParams['figure.dpi'] + kwargs.setdefault('interpolation', 'nearest') + kwargs.setdefault('cmap', 'gray') + + h, w, d = np.atleast_3d(image).shape + figsize = np.array((w, h), dtype=float) / dpi * scale + + fig, ax = new_plot(figsize=figsize, dpi=dpi) + fig.subplots_adjust(left=0, bottom=0, right=1, top=1) + + ax.set_axis_off() + ax.imshow(image, **kwargs) + return fig, ax diff --git a/skimage/viewer/viewers/core.py b/skimage/viewer/viewers/core.py index e11967f7..f740eb81 100644 --- a/skimage/viewer/viewers/core.py +++ b/skimage/viewer/viewers/core.py @@ -16,13 +16,6 @@ from ..widgets import Slider __all__ = ['ImageViewer', 'CollectionViewer'] -class ImageCanvas(utils.MatplotlibCanvas): - """Canvas for displaying images.""" - def __init__(self, parent, image, **kwargs): - self.fig, self.ax = utils.figimage(image, **kwargs) - super(ImageCanvas, self).__init__(parent, self.fig, **kwargs) - - class ImageViewer(QMainWindow): """Viewer for displaying images. @@ -72,9 +65,10 @@ class ImageViewer(QMainWindow): self.main_widget = QtGui.QWidget() self.setCentralWidget(self.main_widget) - self.canvas = ImageCanvas(self.main_widget, image) - self.fig = self.canvas.fig - self.ax = self.canvas.ax + self.fig, self.ax = utils.figimage(image) + self.canvas = self.fig.canvas + self.canvas.setParent(self) + self.ax.autoscale(enable=False) self._image_plot = self.ax.images[0] @@ -274,6 +268,8 @@ class CollectionViewer(ImageViewer): if 48 <= key < 58: index = 0.1 * int(key - 48) * self.num_images self.update_index('', index) - event.accept() + event.accept() + else: + event.ignore() else: event.ignore() From 588fe29b57f7bf3c7985360610c983be6843874b Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Sun, 9 Dec 2012 20:54:10 -0500 Subject: [PATCH 008/215] BUG: Allow slider to accept key press events --- skimage/viewer/widgets/core.py | 1 + 1 file changed, 1 insertion(+) diff --git a/skimage/viewer/widgets/core.py b/skimage/viewer/widgets/core.py index 9382652b..f6e591a1 100644 --- a/skimage/viewer/widgets/core.py +++ b/skimage/viewer/widgets/core.py @@ -131,6 +131,7 @@ class Slider(BaseWidget): self.slider.sliderReleased.connect(self._on_slider_changed) else: raise ValueError("Unexpected value %s for 'update_on'" % update_on) + self.slider.setFocusPolicy(QtCore.Qt.StrongFocus) self.name_label = QtGui.QLabel() self.name_label.setText(self.name) From 211bba0195336443efa7932d2daae96f78c3d78b Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Sun, 9 Dec 2012 21:11:06 -0500 Subject: [PATCH 009/215] ENH: Default to blitting Since the Qt4 Agg backend is always used, it's always safe to use blitting. --- skimage/viewer/canvastools/base.py | 7 ++----- skimage/viewer/plugins/base.py | 9 +-------- skimage/viewer/plugins/lineprofile.py | 1 - 3 files changed, 3 insertions(+), 14 deletions(-) diff --git a/skimage/viewer/canvastools/base.py b/skimage/viewer/canvastools/base.py index 288d12ea..8b631d38 100644 --- a/skimage/viewer/canvastools/base.py +++ b/skimage/viewer/canvastools/base.py @@ -1,7 +1,6 @@ import numpy as np try: - import matplotlib as mpl from matplotlib import lines except ImportError: print("Could not import matplotlib -- skimage.viewer not available.") @@ -16,19 +15,17 @@ class CanvasToolBase(object): Parameters ---------- """ - def __init__(self, ax, useblit=None, on_update=None, on_enter=None): + def __init__(self, ax, useblit=True, on_update=None, on_enter=None): self.ax = ax self.canvas = ax.figure.canvas self.cids = [] self._artists = [] self.active = True - if useblit is None: - useblit = True if mpl.backends.backend.endswith('Agg') else False - self.useblit = useblit if useblit: self.canvas.draw() self.img_background = self.canvas.copy_from_bbox(self.ax.bbox) + self.useblit = useblit if on_update is None: on_update = lambda *args: None diff --git a/skimage/viewer/plugins/base.py b/skimage/viewer/plugins/base.py index 198bac6a..cc6352dc 100644 --- a/skimage/viewer/plugins/base.py +++ b/skimage/viewer/plugins/base.py @@ -9,11 +9,6 @@ except ImportError: QDialog = object # hack to prevent nosetest and autodoc errors print("Could not import PyQt4 -- skimage.viewer not available.") -try: - import matplotlib as mpl -except ImportError: - print("Could not import matplotlib -- skimage.viewer not available.") - from ..utils import RequiredAttr, init_qtapp @@ -81,7 +76,7 @@ class Plugin(QDialog): image_viewer = RequiredAttr("%s is not attached to ImageViewer" % name) draws_on_image = False - def __init__(self, image_filter=None, height=0, width=400, useblit=None): + def __init__(self, image_filter=None, height=0, width=400, useblit=True): init_qtapp() super(Plugin, self).__init__() @@ -98,8 +93,6 @@ class Plugin(QDialog): self.arguments = [] self.keyword_arguments= {} - if useblit is None: - useblit = True if mpl.backends.backend.endswith('Agg') else False self.useblit = useblit self.cids = [] self.artists = [] diff --git a/skimage/viewer/plugins/lineprofile.py b/skimage/viewer/plugins/lineprofile.py index 09aa5d7e..1f9e046e 100644 --- a/skimage/viewer/plugins/lineprofile.py +++ b/skimage/viewer/plugins/lineprofile.py @@ -103,7 +103,6 @@ class LineProfile(PlotPlugin): def line_changed(self, end_pts): x, y = np.transpose(end_pts) - self.end_pts = end_pts scan = profile_line(self.image_viewer.original_image, end_pts, linewidth=self.line_tool.linewidth) From 0428f821e57b6c39cbdb5357d1d8e127d9cd9914 Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Mon, 10 Dec 2012 21:18:50 -0500 Subject: [PATCH 010/215] BUG: Fix background update when canvas resized --- skimage/viewer/canvastools/base.py | 21 +++++++++++++++++---- skimage/viewer/utils/core.py | 6 ++++++ 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/skimage/viewer/canvastools/base.py b/skimage/viewer/canvastools/base.py index 8b631d38..3951c15f 100644 --- a/skimage/viewer/canvastools/base.py +++ b/skimage/viewer/canvastools/base.py @@ -25,6 +25,7 @@ class CanvasToolBase(object): if useblit: self.canvas.draw() self.img_background = self.canvas.copy_from_bbox(self.ax.bbox) + self.connect_event('resize_event', self._update_saved_image) self.useblit = useblit if on_update is None: @@ -58,18 +59,30 @@ class CanvasToolBase(object): return not self.active def set_visible(self, val): - for a in self._artists: - a.set_visible(val) + for artist in self._artists: + artist.set_visible(val) def redraw(self): if self.useblit: self.canvas.restore_region(self.img_background) - for artist in self._artists: - self.ax.draw_artist(artist) + 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) + + def _update_saved_image(self, event): + # Hide canvas artists and save background image + self.set_visible(False) + self.canvas.draw() + self.img_background = self.canvas.copy_from_bbox(self.ax.bbox) + # Redraw canvas artists + self.set_visible(True) + self._draw_artists() + class ToolHandles(object): """Control handles for canvas tools. diff --git a/skimage/viewer/utils/core.py b/skimage/viewer/utils/core.py index 9646bbd6..a49af7e5 100644 --- a/skimage/viewer/utils/core.py +++ b/skimage/viewer/utils/core.py @@ -105,6 +105,12 @@ class FigureCanvas(FigureCanvasQTAgg): QtGui.QSizePolicy.Expanding) FigureCanvasQTAgg.updateGeometry(self) + def resizeEvent(self, event): + FigureCanvasQTAgg.resizeEvent(self, event) + # Call to `resize_event` missing in FigureManagerQT. + # See https://github.com/matplotlib/matplotlib/pull/1585 + self.resize_event() + #TODO: Consider overriding Matplotlib key-event handling # def keyPressEvent(self, event): # # Override key events defined by Matplotlib From 15b1ad492990892fddab5b203965b7e50973a985 Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Mon, 10 Dec 2012 21:30:25 -0500 Subject: [PATCH 011/215] STY: Remove commented out code --- skimage/viewer/utils/core.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/skimage/viewer/utils/core.py b/skimage/viewer/utils/core.py index a49af7e5..38bf7c6c 100644 --- a/skimage/viewer/utils/core.py +++ b/skimage/viewer/utils/core.py @@ -111,15 +111,6 @@ class FigureCanvas(FigureCanvasQTAgg): # See https://github.com/matplotlib/matplotlib/pull/1585 self.resize_event() - #TODO: Consider overriding Matplotlib key-event handling - # def keyPressEvent(self, event): - # # Override key events defined by Matplotlib - # event.ignore() - - # def keyReleaseEvent(self, event): - # # Override key events defined by Matplotlib - # event.ignore() - def new_canvas(*args, **kwargs): """Return a new figure canvas.""" From 866e7d7d6044eceea4d1a8326143cf281ed8970b Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Wed, 12 Dec 2012 11:45:02 -0500 Subject: [PATCH 012/215] BUG: Fix blitting behavior --- skimage/viewer/canvastools/base.py | 37 +++++++++++-------------- skimage/viewer/canvastools/line_tool.py | 2 +- 2 files changed, 17 insertions(+), 22 deletions(-) diff --git a/skimage/viewer/canvastools/base.py b/skimage/viewer/canvastools/base.py index 3951c15f..ed0feb30 100644 --- a/skimage/viewer/canvastools/base.py +++ b/skimage/viewer/canvastools/base.py @@ -23,9 +23,7 @@ class CanvasToolBase(object): self.active = True if useblit: - self.canvas.draw() - self.img_background = self.canvas.copy_from_bbox(self.ax.bbox) - self.connect_event('resize_event', self._update_saved_image) + self.connect_event('draw_event', self._blit_on_draw_event) self.useblit = useblit if on_update is None: @@ -62,6 +60,14 @@ class CanvasToolBase(object): for artist in self._artists: artist.set_visible(val) + def _blit_on_draw_event(self, event=None): + self.img_background = self.canvas.copy_from_bbox(self.ax.bbox) + self._draw_artists() + + def _draw_artists(self): + for artist in self._artists: + self.ax.draw_artist(artist) + def redraw(self): if self.useblit: self.canvas.restore_region(self.img_background) @@ -70,19 +76,6 @@ class CanvasToolBase(object): else: self.canvas.draw_idle() - def _draw_artists(self): - for artist in self._artists: - self.ax.draw_artist(artist) - - def _update_saved_image(self, event): - # Hide canvas artists and save background image - self.set_visible(False) - self.canvas.draw() - self.img_background = self.canvas.copy_from_bbox(self.ax.bbox) - # Redraw canvas artists - self.set_visible(True) - self._draw_artists() - class ToolHandles(object): """Control handles for canvas tools. @@ -95,14 +88,16 @@ class ToolHandles(object): Coordinates of control handles. marker : str Shape of marker used to display handle. See `matplotlib.pyplot.plot`. - marker_props : see :class:`matplotlib.lines.Line2D`. + marker_props : dict + Additional marker properties. See :class:`matplotlib.lines.Line2D`. """ - def __init__(self, ax, x, y, marker='o', markerprops=None): + def __init__(self, ax, x, y, marker='o', marker_props=None): self.ax = ax - props = dict(mfc='w', ls='none', alpha=0.5, visible=False) - props.update(markerprops if markerprops is not None else {}) - self._markers = lines.Line2D(x, y, marker=marker, **props) + props = dict(marker=marker, markersize=7, mfc='w', ls='none', + alpha=0.5, visible=False) + props.update(marker_props if marker_props is not None else {}) + self._markers = lines.Line2D(x, y, animated=True, **props) self.ax.add_line(self._markers) self.artist = self._markers diff --git a/skimage/viewer/canvastools/line_tool.py b/skimage/viewer/canvastools/line_tool.py index f3764df7..9dd990b4 100644 --- a/skimage/viewer/canvastools/line_tool.py +++ b/skimage/viewer/canvastools/line_tool.py @@ -37,7 +37,7 @@ class LineTool(CanvasToolBase): self._init_end_pts = np.transpose([x, y]) self.end_pts = self._init_end_pts.copy() - self._line = lines.Line2D(x, y, **props) + self._line = lines.Line2D(x, y, animated=True, **props) ax.add_line(self._line) self._handles = ToolHandles(ax, x, y) From 16a720770a6e81c388fc1b18b471d8155738d7ea Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Wed, 12 Dec 2012 13:34:53 -0500 Subject: [PATCH 013/215] STY: Clean up unused blitting-related code --- skimage/viewer/plugins/base.py | 12 ------------ skimage/viewer/plugins/lineprofile.py | 1 - 2 files changed, 13 deletions(-) diff --git a/skimage/viewer/plugins/base.py b/skimage/viewer/plugins/base.py index cc6352dc..c5dd2770 100644 --- a/skimage/viewer/plugins/base.py +++ b/skimage/viewer/plugins/base.py @@ -74,7 +74,6 @@ class Plugin(QDialog): """ name = 'Plugin' image_viewer = RequiredAttr("%s is not attached to ImageViewer" % name) - draws_on_image = False def __init__(self, image_filter=None, height=0, width=400, useblit=True): init_qtapp() @@ -116,8 +115,6 @@ class Plugin(QDialog): #TODO: Always passing image as first argument may be bad assumption. self.arguments.append(self.image_viewer.original_image) - if self.draws_on_image: - self.connect_image_event('draw_event', self.on_draw) # Call filter so that filtered image matches widget values self.filter_image() @@ -148,15 +145,6 @@ class Plugin(QDialog): self.add_widget(widget) return self - def on_draw(self, event): - """Save image background when blitting. - - The saved image is used to "clear" the figure before redrawing artists. - """ - if self.useblit: - bbox = self.image_viewer.ax.bbox - self.img_background = self.image_viewer.canvas.copy_from_bbox(bbox) - def filter_image(self, *widget_arg): """Call `image_filter` with widget args and kwargs diff --git a/skimage/viewer/plugins/lineprofile.py b/skimage/viewer/plugins/lineprofile.py index 1f9e046e..9d394e5f 100644 --- a/skimage/viewer/plugins/lineprofile.py +++ b/skimage/viewer/plugins/lineprofile.py @@ -33,7 +33,6 @@ class LineProfile(PlotPlugin): 'dtype' : fixed scale based on min/max intensity of image dtype. """ name = 'Line Profile' - draws_on_image = True def __init__(self, linewidth=1, maxdist=5, epsilon='deprecated', limits='image', **kwargs): From 9f70148f083006dadd47f474ae1af7bc43293779 Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Wed, 12 Dec 2012 15:48:45 -0500 Subject: [PATCH 014/215] STY: Minor code clean up --- skimage/viewer/canvastools/base.py | 4 ++++ skimage/viewer/canvastools/line_tool.py | 13 +++---------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/skimage/viewer/canvastools/base.py b/skimage/viewer/canvastools/base.py index ed0feb30..dddca19a 100644 --- a/skimage/viewer/canvastools/base.py +++ b/skimage/viewer/canvastools/base.py @@ -69,6 +69,10 @@ class CanvasToolBase(object): self.ax.draw_artist(artist) def redraw(self): + """Redraw image and canvas artists. + + This method should be called by subclasses when artists are updated. + """ if self.useblit: self.canvas.restore_region(self.img_background) self._draw_artists() diff --git a/skimage/viewer/canvastools/line_tool.py b/skimage/viewer/canvastools/line_tool.py index 9dd990b4..c2bdd724 100644 --- a/skimage/viewer/canvastools/line_tool.py +++ b/skimage/viewer/canvastools/line_tool.py @@ -50,9 +50,7 @@ class LineTool(CanvasToolBase): self.connect_event('key_press_event', self.on_key_press) def on_mouse_press(self, event): - if event.button != 1: - return - if event.inaxes == None: + if event.button != 1 and event.inaxes == None: return idx, px_dist = self._handles.closest(event.x, event.y) if px_dist < self.maxdist: @@ -64,14 +62,11 @@ class LineTool(CanvasToolBase): self._active_pt = None def on_move(self, event): - if event.button != 1: - return - if self._active_pt is None: + if event.button != 1 or self._active_pt is None: return if not self.ax.in_axes(event): return - x, y = event.xdata, event.ydata - self.update(x, y) + self.update(event.xdata, event.ydata) def on_key_press(self, event): if event.key == 'enter': @@ -92,9 +87,7 @@ class LineTool(CanvasToolBase): self._handles.set_data(np.transpose(self.end_pts)) self._line.set_linewidth(self.linewidth) - self.ax.relim() self.redraw() - self.on_update(self.end_pts) From bacc12e00ad4842d8da330a6f939bd4b810487f0 Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Wed, 12 Dec 2012 15:50:49 -0500 Subject: [PATCH 015/215] ENH: Draw new line when click is far from handle --- skimage/viewer/canvastools/line_tool.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/skimage/viewer/canvastools/line_tool.py b/skimage/viewer/canvastools/line_tool.py index c2bdd724..085c92b8 100644 --- a/skimage/viewer/canvastools/line_tool.py +++ b/skimage/viewer/canvastools/line_tool.py @@ -55,6 +55,10 @@ class LineTool(CanvasToolBase): idx, px_dist = self._handles.closest(event.x, event.y) if px_dist < self.maxdist: self._active_pt = idx + else: + self._active_pt = 0 + x, y = event.xdata, event.ydata + self.end_pts = np.array([[x, y], [x, y]]) def on_mouse_release(self, event): if event.button != 1: From ece045790e523b402fddd8014daeef9b54230ff7 Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Wed, 12 Dec 2012 15:54:12 -0500 Subject: [PATCH 016/215] STY: Remove reset functionality in line tool Since a line can be draw without selecting handles, the reset functionality doesn't seem necessary. --- skimage/viewer/canvastools/line_tool.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/skimage/viewer/canvastools/line_tool.py b/skimage/viewer/canvastools/line_tool.py index 085c92b8..5db94a7e 100644 --- a/skimage/viewer/canvastools/line_tool.py +++ b/skimage/viewer/canvastools/line_tool.py @@ -34,8 +34,7 @@ class LineTool(CanvasToolBase): self.maxdist = maxdist self._active_pt = None - self._init_end_pts = np.transpose([x, y]) - self.end_pts = self._init_end_pts.copy() + self.end_pts = np.transpose([x, y]) self._line = lines.Line2D(x, y, animated=True, **props) ax.add_line(self._line) @@ -78,12 +77,6 @@ class LineTool(CanvasToolBase): self.set_visible(False) self.redraw() - def reset(self): - self.end_pts = self._init_end_pts.copy() - self._line.set_data(np.transpose(self.end_pts)) - self._handles.set_data(np.transpose(self.end_pts)) - self.update(None, None) - def update(self, x, y): if x is not None: self.end_pts[self._active_pt, :] = x, y @@ -120,8 +113,6 @@ class ThickLineTool(LineTool): 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 From 67aada9adcea8e77e25f30d07cd23bc090920119 Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Wed, 12 Dec 2012 15:58:10 -0500 Subject: [PATCH 017/215] ENH: Increase default selection threshold for ease of use --- skimage/viewer/plugins/lineprofile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skimage/viewer/plugins/lineprofile.py b/skimage/viewer/plugins/lineprofile.py index 9d394e5f..69326ff2 100644 --- a/skimage/viewer/plugins/lineprofile.py +++ b/skimage/viewer/plugins/lineprofile.py @@ -34,7 +34,7 @@ class LineProfile(PlotPlugin): """ name = 'Line Profile' - def __init__(self, linewidth=1, maxdist=5, epsilon='deprecated', + def __init__(self, linewidth=1, maxdist=10, epsilon='deprecated', limits='image', **kwargs): super(LineProfile, self).__init__(**kwargs) From d06c7bcb34f0f7a22df95b6383391237a34b2515 Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Wed, 12 Dec 2012 16:01:06 -0500 Subject: [PATCH 018/215] STY: Rename line_tool module to linetool --- skimage/viewer/canvastools/__init__.py | 2 +- skimage/viewer/canvastools/{line_tool.py => linetool.py} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename skimage/viewer/canvastools/{line_tool.py => linetool.py} (100%) diff --git a/skimage/viewer/canvastools/__init__.py b/skimage/viewer/canvastools/__init__.py index a04d4188..326ce780 100644 --- a/skimage/viewer/canvastools/__init__.py +++ b/skimage/viewer/canvastools/__init__.py @@ -1 +1 @@ -from line_tool import LineTool, ThickLineTool +from linetool import LineTool, ThickLineTool diff --git a/skimage/viewer/canvastools/line_tool.py b/skimage/viewer/canvastools/linetool.py similarity index 100% rename from skimage/viewer/canvastools/line_tool.py rename to skimage/viewer/canvastools/linetool.py From cb30c24427b8f7cf22635d036f945b118f581710 Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Wed, 12 Dec 2012 21:46:08 -0500 Subject: [PATCH 019/215] ENH: Add measure tool plugin --- skimage/viewer/plugins/measure.py | 54 +++++++++++++++++++++++++++++++ skimage/viewer/widgets/core.py | 25 +++++++++++++- 2 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 skimage/viewer/plugins/measure.py diff --git a/skimage/viewer/plugins/measure.py b/skimage/viewer/plugins/measure.py new file mode 100644 index 00000000..49f53963 --- /dev/null +++ b/skimage/viewer/plugins/measure.py @@ -0,0 +1,54 @@ +# -*- coding: utf-8 -*- +import numpy as np + +from .base import Plugin +from ..widgets import Text +from ..canvastools import LineTool + + +__all__ = ['Measure'] + + +rad2deg = 180 / np.pi + + +class Measure(Plugin): + name = 'Measure' + draws_on_image = True + + def __init__(self, maxdist=10, **kwargs): + super(Measure, self).__init__(**kwargs) + + self.maxdist = maxdist + self._length = Text('Length:') + self._angle = Text('Angle:') + + self.add_widget(self._length) + self.add_widget(self._angle) + + print self.help() + + def attach(self, image_viewer): + super(Measure, self).attach(image_viewer) + + image = image_viewer.original_image + h, w = image.shape + x = [w / 3, 2 * w / 3] + y = [h / 2] * 2 + self.line_tool = LineTool(self.image_viewer.ax, x, y, + maxdist=self.maxdist, + on_update=self.line_changed) + # initialize displayed values + self.line_changed(np.transpose((x, y))) + + def help(self): + helpstr = ("Line profile tool", + "Select line to measure distance and angle.") + return '\n'.join(helpstr) + + def line_changed(self, end_pts): + x, y = np.transpose(end_pts) + dx = np.diff(x)[0] + dy = np.diff(y)[0] + self._length.text = '%.1f' % np.hypot(dx, dy) + self._angle.text = u'%.1f°' % (180 - np.arctan2(dy, dx) * rad2deg) diff --git a/skimage/viewer/widgets/core.py b/skimage/viewer/widgets/core.py index f6e591a1..bdbfb71e 100644 --- a/skimage/viewer/widgets/core.py +++ b/skimage/viewer/widgets/core.py @@ -27,7 +27,7 @@ except ImportError: from ..utils import RequiredAttr -__all__ = ['BaseWidget', 'Slider', 'ComboBox'] +__all__ = ['BaseWidget', 'Slider', 'ComboBox', 'Text'] class BaseWidget(QWidget): @@ -50,6 +50,29 @@ class BaseWidget(QWidget): self.callback(self.name, value) +class Text(BaseWidget): + + def __init__(self, name=None, text=''): + super(Text, self).__init__(name) + self._label = QtGui.QLabel() + self.text = text + self.layout = QtGui.QHBoxLayout(self) + if name is not None: + name_label = QtGui.QLabel() + name_label.setText(name) + self.layout.addWidget(name_label) + self.layout.addWidget(self._label) + + + @property + def text(self): + return self._label.text() + + @text.setter + def text(self, text_str): + self._label.setText(text_str) + + class Slider(BaseWidget): """Slider widget for adjusting numeric parameters. From 36bc6da7574d6b38f0fbab1ee10498a48b997b97 Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Wed, 12 Dec 2012 23:42:15 -0500 Subject: [PATCH 020/215] STY: Refactors general parts of line tool to base tool --- skimage/viewer/canvastools/base.py | 14 +++++++++++++ skimage/viewer/canvastools/linetool.py | 27 ++++++++++++-------------- skimage/viewer/plugins/measure.py | 1 - viewer_examples/plugins/measure.py | 9 +++++++++ 4 files changed, 35 insertions(+), 16 deletions(-) create mode 100644 viewer_examples/plugins/measure.py diff --git a/skimage/viewer/canvastools/base.py b/skimage/viewer/canvastools/base.py index dddca19a..2642df57 100644 --- a/skimage/viewer/canvastools/base.py +++ b/skimage/viewer/canvastools/base.py @@ -34,6 +34,8 @@ class CanvasToolBase(object): on_enter = lambda *args: None self.on_enter = on_enter + self.connect_event('key_press_event', self._on_key_press) + def connect_event(self, event, callback): """Connect callback with an event. @@ -79,6 +81,18 @@ class CanvasToolBase(object): self.canvas.blit(self.ax.bbox) else: self.canvas.draw_idle() + self.on_update(self.geometry) + + def _on_key_press(self, event): + if event.key == 'enter': + self.on_enter(self.geometry) + self.set_visible(False) + self.redraw() + + @property + def geometry(self): + """Geometry information that gets passed to callback functions.""" + raise NotImplementedError class ToolHandles(object): diff --git a/skimage/viewer/canvastools/linetool.py b/skimage/viewer/canvastools/linetool.py index 5db94a7e..b19c3736 100644 --- a/skimage/viewer/canvastools/linetool.py +++ b/skimage/viewer/canvastools/linetool.py @@ -43,10 +43,15 @@ class LineTool(CanvasToolBase): self._handles.set_visible(True) self._artists = [self._line, self._handles.artist] + if on_enter is None: + def on_enter(pts): + x, y = np.transpose(pts) + print "length = %0.2f" % np.sqrt(np.diff(x)**2 + np.diff(y)**2) + self.on_enter = on_enter + self.connect_event('button_press_event', self.on_mouse_press) self.connect_event('button_release_event', self.on_mouse_release) self.connect_event('motion_notify_event', self.on_move) - self.connect_event('key_press_event', self.on_key_press) def on_mouse_press(self, event): if event.button != 1 and event.inaxes == None: @@ -71,12 +76,6 @@ class LineTool(CanvasToolBase): return self.update(event.xdata, event.ydata) - def on_key_press(self, event): - if event.key == 'enter': - self.on_enter(self.end_pts) - self.set_visible(False) - self.redraw() - def update(self, x, y): if x is not None: self.end_pts[self._active_pt, :] = x, y @@ -85,7 +84,10 @@ class LineTool(CanvasToolBase): self._line.set_linewidth(self.linewidth) self.redraw() - self.on_update(self.end_pts) + + @property + def geometry(self): + return self.end_pts class ThickLineTool(LineTool): @@ -97,6 +99,7 @@ class ThickLineTool(LineTool): lineprops=lineprops) self.connect_event('scroll_event', self.on_scroll) + self.connect_event('key_press_event', self.on_key_press) def on_scroll(self, event): if not event.inaxes: @@ -107,8 +110,6 @@ class ThickLineTool(LineTool): self._shrink_scan_line() def on_key_press(self, event): - super(ThickLineTool, self).on_key_press(event) - if event.key == '+': self._thicken_scan_line() elif event.key == '-': @@ -134,10 +135,6 @@ if __name__ == '__main__': ax.imshow(image, interpolation='nearest') h, w = image.shape - def printer(pts): - x, y = np.transpose(pts) - print "length = %0.2f" % np.sqrt(np.diff(x)**2 + np.diff(y)**2) - # line_tool = LineTool(ax, [w/3, 2*w/3], [h/2, h/2]) - line_tool = ThickLineTool(ax, [w/3, 2*w/3], [h/2, h/2], on_enter=printer) + line_tool = ThickLineTool(ax, [w/3, 2*w/3], [h/2, h/2]) plt.show() diff --git a/skimage/viewer/plugins/measure.py b/skimage/viewer/plugins/measure.py index 49f53963..83dda6d4 100644 --- a/skimage/viewer/plugins/measure.py +++ b/skimage/viewer/plugins/measure.py @@ -14,7 +14,6 @@ rad2deg = 180 / np.pi class Measure(Plugin): name = 'Measure' - draws_on_image = True def __init__(self, maxdist=10, **kwargs): super(Measure, self).__init__(**kwargs) diff --git a/viewer_examples/plugins/measure.py b/viewer_examples/plugins/measure.py new file mode 100644 index 00000000..6200a717 --- /dev/null +++ b/viewer_examples/plugins/measure.py @@ -0,0 +1,9 @@ +from skimage import data +from skimage.viewer import ImageViewer +from skimage.viewer.plugins.measure import Measure + + +image = data.camera() +viewer = ImageViewer(image) +viewer += Measure() +viewer.show() From 631e97d9735ea6fd5e956c8255e5d9f99f9c9ad2 Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Wed, 12 Dec 2012 23:43:46 -0500 Subject: [PATCH 021/215] ENH: Add rectangle tool and crop plugin example --- skimage/viewer/canvastools/__init__.py | 1 + skimage/viewer/canvastools/recttool.py | 195 +++++++++++++++++++++++++ skimage/viewer/plugins/crop.py | 34 +++++ skimage/viewer/viewers/core.py | 11 ++ viewer_examples/plugins/croptool.py | 9 ++ 5 files changed, 250 insertions(+) create mode 100644 skimage/viewer/canvastools/recttool.py create mode 100644 skimage/viewer/plugins/crop.py create mode 100644 viewer_examples/plugins/croptool.py diff --git a/skimage/viewer/canvastools/__init__.py b/skimage/viewer/canvastools/__init__.py index 326ce780..5a50b8a5 100644 --- a/skimage/viewer/canvastools/__init__.py +++ b/skimage/viewer/canvastools/__init__.py @@ -1 +1,2 @@ from linetool import LineTool, ThickLineTool +from recttool import RectangleTool diff --git a/skimage/viewer/canvastools/recttool.py b/skimage/viewer/canvastools/recttool.py new file mode 100644 index 00000000..ba189b4b --- /dev/null +++ b/skimage/viewer/canvastools/recttool.py @@ -0,0 +1,195 @@ +import matplotlib.widgets as mwidgets + +from skimage.viewer.canvastools.base import CanvasToolBase +from skimage.viewer.canvastools.base import ToolHandles + + +__all__ = ['RectangleTool'] + + +class RectangleTool(mwidgets.RectangleSelector, CanvasToolBase): + """Widget for selecting a rectangular region in a plot. + + After making the desired selection, press "Enter" to accept the selection + and call the `on_enter` callback function. + + Parameters + ---------- + ax : :class:`matplotlib.axes.Axes + on_enter : function + Function accepting rectangle extents as the only argument; called + whenever "Enter" key is pressed. If None, print extents of rectangle. + maxdist : float + Maximum distance in pixels for selection of a control handle + (i.e. corner or edge) handle. + rectprops : dict + Properties for :class:`matplotlib.patches.Rectangle`. This class + redefines defaults in :class:`matplotlib.widgets.RectangleSelector`. + kwargs : see :class:`matplotlib.widgets.RectangleSelector`. + + Attributes + ---------- + extents : tuple + Rectangle extents: (xmin, xmax, ymin, ymax). + """ + + def __init__(self, ax, on_update=None, on_enter=None, rectprops=None, + maxdist=10, **kwargs): + CanvasToolBase.__init__(self, ax, on_update=on_update, + on_enter=on_enter) + + props = dict(edgecolor=None, facecolor='r', alpha=0.15) + props.update(rectprops if rectprops is not None else {}) + if props['edgecolor'] is None: + props['edgecolor'] = props['facecolor'] + mwidgets.RectangleSelector.__init__(self, ax, lambda *args: None, + rectprops=props, + useblit=self.useblit) + # Alias rectangle attribute, which is initialized in RectangleSelector. + self._rect = self.to_draw + self._rect.set_animated(True) + + self.maxdist = maxdist + self.active_handle = None + self._extents_on_press = None + + if on_enter is None: + def on_enter(extents): + print "(xmin=%.3g, xmax=%.3g, ymin=%.3g, ymax=%.3g)" % extents + self.on_enter = on_enter + + props = dict(mec=props['edgecolor']) + self._corner_order = ['NW', 'NE', 'SE', 'SW'] + xc, yc = self.corners + self._corner_handles = ToolHandles(ax, xc, yc, marker_props=props) + + self._edge_order = ['W', 'N', 'E', 'S'] + xe, ye = self.edge_centers + self._edge_handles = ToolHandles(ax, xe, ye, marker='s', + marker_props=props) + + self._artists = [self._rect, + self._corner_handles.artist, + self._edge_handles.artist] + + @property + def _rect_bbox(self): + x0 = self._rect.get_x() + y0 = self._rect.get_y() + width = self._rect.get_width() + height = self._rect.get_height() + return x0, y0, width, height + + @property + def corners(self): + """Corners of rectangle from lower left, moving clockwise.""" + x0, y0, width, height = self._rect_bbox + xc = x0, x0 + width, x0 + width, x0 + yc = y0, y0, y0 + height, y0 + height + return xc, yc + + @property + def edge_centers(self): + """Midpoint of rectangle edges from left, moving clockwise.""" + x0, y0, width, height = self._rect_bbox + w = width / 2. + h = height / 2. + xe = x0, x0 + w, x0 + width, x0 + w + ye = y0 + h, y0, y0 + h, y0 + height + return xe, ye + + @property + def extents(self): + """Return (xmin, xmax, ymin, ymax).""" + x0, y0, width, height = self._rect_bbox + xmin, xmax = sorted([x0, x0 + width]) + ymin, ymax = sorted([y0, y0 + height]) + return xmin, xmax, ymin, ymax + + def release(self, event): + mwidgets.RectangleSelector.release(self, event) + self._extents_on_press = None + # Undo hiding of rectangle and redraw. + self.set_visible(True) + self.redraw() + + def press(self, event): + self._set_active_handle(event) + if self.active_handle is None: + # Clear previous rectangle before drawing new rectangle. + self.set_visible(False) + self.redraw() + self.set_visible(True) + mwidgets.RectangleSelector.press(self, event) + + def _set_active_handle(self, event): + """Set active handle based on the location of the mouse event""" + # Note: event.xdata/ydata in data coordinates, event.x/y in pixels + c_idx, c_dist = self._corner_handles.closest(event.x, event.y) + e_idx, e_dist = self._edge_handles.closest(event.x, event.y) + + # Set active handle as closest handle, if mouse click is close enough. + if c_dist > self.maxdist and e_dist > self.maxdist: + self.active_handle = None + return + elif c_dist < e_dist: + self.active_handle = self._corner_order[c_idx] + else: + self.active_handle = self._edge_order[e_idx] + + # Save coordinates of rectangle at the start of handle movement. + x1, x2, y1, y2 = self.extents + # Switch variables so that only x2 and/or y2 are updated on move. + if self.active_handle in ['W', 'SW', 'NW']: + x1, x2 = x2, event.xdata + if self.active_handle in ['N', 'NW', 'NE']: + y1, y2 = y2, event.ydata + self._extents_on_press = x1, x2, y1, y2 + + def onmove(self, event): + + if self.eventpress is None or self.ignore(event): + return + + if self.active_handle is None: + # New rectangle + x1 = self.eventpress.xdata + y1 = self.eventpress.ydata + x2, y2 = event.xdata, event.ydata + else: + x1, x2, y1, y2 = self._extents_on_press + if self.active_handle in ['E', 'W'] + self._corner_order: + x2 = event.xdata + if self.active_handle in ['N', 'S'] + self._corner_order: + y2 = event.ydata + xmin, xmax = sorted([x1, x2]) + ymin, ymax = sorted([y1, y2]) + + # Update displayed rectangle + self._rect.set_x(xmin) + self._rect.set_y(ymin) + self._rect.set_width(xmax - xmin) + self._rect.set_height(ymax - ymin) + + # Update displayed handles + self._corner_handles.set_data(*self.corners) + self._edge_handles.set_data(*self.edge_centers) + + self.redraw() + + @property + def geometry(self): + return self.extents + + +if __name__ == '__main__': + import matplotlib.pyplot as plt + from skimage import data + + f, ax = plt.subplots() + ax.imshow(data.camera(), interpolation='nearest') + + rect_tool = RectangleTool(ax) + plt.show() + print "Final selection:", + rect_tool.on_enter(rect_tool.extents) diff --git a/skimage/viewer/plugins/crop.py b/skimage/viewer/plugins/crop.py new file mode 100644 index 00000000..49f6014b --- /dev/null +++ b/skimage/viewer/plugins/crop.py @@ -0,0 +1,34 @@ +from .base import Plugin +from ..canvastools import RectangleTool +from skimage.viewer.widgets.history import SaveButtons + + +__all__ = ['Crop'] + + +class Crop(Plugin): + name = 'Crop' + + def __init__(self, maxdist=10, **kwargs): + super(Crop, self).__init__(**kwargs) + self.maxdist = maxdist + self.add_widget(SaveButtons()) + print self.help() + + def attach(self, image_viewer): + super(Crop, self).attach(image_viewer) + + self.rect_tool = RectangleTool(self.image_viewer.ax, + maxdist=self.maxdist, + on_enter=self.crop) + + def help(self): + helpstr = ("Crop tool", + "Select rectangular region and press enter to crop.") + return '\n'.join(helpstr) + + def crop(self, extents): + xmin, xmax, ymin, ymax = extents + image = self.image_viewer.image[ymin:ymax+1, xmin:xmax+1] + self.image_viewer.image = image + self.image_viewer.ax.relim() diff --git a/skimage/viewer/viewers/core.py b/skimage/viewer/viewers/core.py index f740eb81..4bd70f64 100644 --- a/skimage/viewer/viewers/core.py +++ b/skimage/viewer/viewers/core.py @@ -137,10 +137,21 @@ class ImageViewer(QMainWindow): def image(self, image): self._img = image self._image_plot.set_array(image) + + # Adjust size if new image shape doesn't match the original + h, w = image.shape + # update data coordinates (otherwise pixel coordinates are off) + self._image_plot.set_extent((0, w, h, 0)) + # update display (otherwise image doesn't fill the canvas) + self.ax.set_xlim(0, w) + self.ax.set_ylim(h, 0) + + # update color range clim = dtype_range[image.dtype.type] if clim[0] < 0 and image.min() >= 0: clim = (0, clim[1]) self._image_plot.set_clim(clim) + self.redraw() def reset_image(self): diff --git a/viewer_examples/plugins/croptool.py b/viewer_examples/plugins/croptool.py new file mode 100644 index 00000000..0eb44903 --- /dev/null +++ b/viewer_examples/plugins/croptool.py @@ -0,0 +1,9 @@ +from skimage import data +from skimage.viewer import ImageViewer +from skimage.viewer.plugins.crop import Crop + + +image = data.camera() +viewer = ImageViewer(image) +viewer += Crop() +viewer.show() From 1aafdeb7d4c681adfb3523dbb163f2830136d838 Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Thu, 13 Dec 2012 00:23:30 -0500 Subject: [PATCH 022/215] ENH: Overhaul callback system. Remove `on_update` callback and add `on_move` and `on_release` callbacks arguments. Also, prefix callback functions with `callback_` for clarity. --- skimage/viewer/canvastools/base.py | 20 ++++++++++---------- skimage/viewer/canvastools/linetool.py | 25 +++++++++++++++---------- skimage/viewer/canvastools/recttool.py | 15 ++++++++------- 3 files changed, 33 insertions(+), 27 deletions(-) diff --git a/skimage/viewer/canvastools/base.py b/skimage/viewer/canvastools/base.py index 2642df57..37329d6a 100644 --- a/skimage/viewer/canvastools/base.py +++ b/skimage/viewer/canvastools/base.py @@ -9,13 +9,18 @@ except ImportError: __all__ = ['CanvasToolBase', 'ToolHandles'] +def _pass(*args): + pass + + class CanvasToolBase(object): """Base canvas tool for matplotlib axes. Parameters ---------- """ - def __init__(self, ax, useblit=True, on_update=None, on_enter=None): + def __init__(self, ax, on_move=None, on_enter=None, on_release=None, + useblit=True): self.ax = ax self.canvas = ax.figure.canvas self.cids = [] @@ -26,13 +31,9 @@ class CanvasToolBase(object): self.connect_event('draw_event', self._blit_on_draw_event) self.useblit = useblit - if on_update is None: - on_update = lambda *args: None - self.on_update = on_update - - if on_enter is None: - on_enter = lambda *args: None - self.on_enter = on_enter + 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 + self.callback_on_release = _pass if on_release is None else on_release self.connect_event('key_press_event', self._on_key_press) @@ -81,11 +82,10 @@ class CanvasToolBase(object): self.canvas.blit(self.ax.bbox) else: self.canvas.draw_idle() - self.on_update(self.geometry) def _on_key_press(self, event): if event.key == 'enter': - self.on_enter(self.geometry) + self.callback_on_enter(self.geometry) self.set_visible(False) self.redraw() diff --git a/skimage/viewer/canvastools/linetool.py b/skimage/viewer/canvastools/linetool.py index b19c3736..fba44b69 100644 --- a/skimage/viewer/canvastools/linetool.py +++ b/skimage/viewer/canvastools/linetool.py @@ -15,7 +15,7 @@ class LineTool(CanvasToolBase): """ Parameters ---------- - on_update : function + on_move : function Function accepting end points of line as the only argument. Attributes @@ -23,10 +23,10 @@ class LineTool(CanvasToolBase): end_pts : 2D array End points of line ((x1, y1), (x2, y2)). """ - def __init__(self, ax, x, y, on_update=None, on_enter=None, maxdist=10, - lineprops=None): - super(LineTool, self).__init__(ax, on_update=on_update, - on_enter=on_enter) + def __init__(self, ax, x, y, on_move=None, on_enter=None, on_release=None, + maxdist=10, lineprops=None): + super(LineTool, self).__init__(ax, on_move=on_move, on_enter=on_enter, + on_release=on_release) props = dict(color='r', linewidth=1, alpha=0.4, solid_capstyle='butt') props.update(lineprops if lineprops is not None else {}) @@ -47,7 +47,7 @@ class LineTool(CanvasToolBase): def on_enter(pts): x, y = np.transpose(pts) print "length = %0.2f" % np.sqrt(np.diff(x)**2 + np.diff(y)**2) - self.on_enter = on_enter + self.callback_on_enter = on_enter self.connect_event('button_press_event', self.on_mouse_press) self.connect_event('button_release_event', self.on_mouse_release) @@ -68,6 +68,7 @@ class LineTool(CanvasToolBase): if event.button != 1: return self._active_pt = None + self.callback_on_release(self.geometry) def on_move(self, event): if event.button != 1 or self._active_pt is None: @@ -75,6 +76,7 @@ class LineTool(CanvasToolBase): if not self.ax.in_axes(event): return self.update(event.xdata, event.ydata) + self.callback_on_move(self.geometry) def update(self, x, y): if x is not None: @@ -92,10 +94,13 @@ class LineTool(CanvasToolBase): class ThickLineTool(LineTool): - def __init__(self, ax, x, y, on_update=None, on_enter=None, maxdist=10, - lineprops=None): - super(ThickLineTool, self).__init__(ax, x, y, on_update=on_update, - on_enter=on_enter, maxdist=maxdist, + def __init__(self, ax, x, y, on_move=None, on_enter=None, on_release=None, + maxdist=10, lineprops=None): + super(ThickLineTool, self).__init__(ax, x, y, + on_move=on_move, + on_enter=on_enter, + on_release=on_release, + maxdist=maxdist, lineprops=lineprops) self.connect_event('scroll_event', self.on_scroll) diff --git a/skimage/viewer/canvastools/recttool.py b/skimage/viewer/canvastools/recttool.py index ba189b4b..5eeb6f23 100644 --- a/skimage/viewer/canvastools/recttool.py +++ b/skimage/viewer/canvastools/recttool.py @@ -25,7 +25,6 @@ class RectangleTool(mwidgets.RectangleSelector, CanvasToolBase): rectprops : dict Properties for :class:`matplotlib.patches.Rectangle`. This class redefines defaults in :class:`matplotlib.widgets.RectangleSelector`. - kwargs : see :class:`matplotlib.widgets.RectangleSelector`. Attributes ---------- @@ -33,10 +32,10 @@ class RectangleTool(mwidgets.RectangleSelector, CanvasToolBase): Rectangle extents: (xmin, xmax, ymin, ymax). """ - def __init__(self, ax, on_update=None, on_enter=None, rectprops=None, - maxdist=10, **kwargs): - CanvasToolBase.__init__(self, ax, on_update=on_update, - on_enter=on_enter) + def __init__(self, ax, on_move=None, on_enter=None, on_release=None, + maxdist=10, rectprops=None): + CanvasToolBase.__init__(self, ax, on_move=on_move, + on_enter=on_enter, on_release=on_release) props = dict(edgecolor=None, facecolor='r', alpha=0.15) props.update(rectprops if rectprops is not None else {}) @@ -56,7 +55,7 @@ class RectangleTool(mwidgets.RectangleSelector, CanvasToolBase): if on_enter is None: def on_enter(extents): print "(xmin=%.3g, xmax=%.3g, ymin=%.3g, ymax=%.3g)" % extents - self.on_enter = on_enter + self.callback_on_enter = on_enter props = dict(mec=props['edgecolor']) self._corner_order = ['NW', 'NE', 'SE', 'SW'] @@ -112,6 +111,7 @@ class RectangleTool(mwidgets.RectangleSelector, CanvasToolBase): # Undo hiding of rectangle and redraw. self.set_visible(True) self.redraw() + self.callback_on_release(self.geometry) def press(self, event): self._set_active_handle(event) @@ -176,6 +176,7 @@ class RectangleTool(mwidgets.RectangleSelector, CanvasToolBase): self._edge_handles.set_data(*self.edge_centers) self.redraw() + self.callback_on_move(self.geometry) @property def geometry(self): @@ -192,4 +193,4 @@ if __name__ == '__main__': rect_tool = RectangleTool(ax) plt.show() print "Final selection:", - rect_tool.on_enter(rect_tool.extents) + rect_tool.callback_on_enter(rect_tool.extents) From 04434b008f55e0b7314a5fcf09c2ce013a32cb0d Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Thu, 13 Dec 2012 10:39:11 -0500 Subject: [PATCH 023/215] BUG: Fix image viewer for RGB images --- skimage/viewer/viewers/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skimage/viewer/viewers/core.py b/skimage/viewer/viewers/core.py index 4bd70f64..1c369707 100644 --- a/skimage/viewer/viewers/core.py +++ b/skimage/viewer/viewers/core.py @@ -139,7 +139,7 @@ class ImageViewer(QMainWindow): self._image_plot.set_array(image) # Adjust size if new image shape doesn't match the original - h, w = image.shape + h, w = image.shape[:2] # update data coordinates (otherwise pixel coordinates are off) self._image_plot.set_extent((0, w, h, 0)) # update display (otherwise image doesn't fill the canvas) From 19e86ed661a079f597a6a1179853ed245ecc6aef Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Thu, 13 Dec 2012 13:51:29 -0500 Subject: [PATCH 024/215] BUG: Fix matplotlib import for compatibility --- skimage/viewer/canvastools/recttool.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/skimage/viewer/canvastools/recttool.py b/skimage/viewer/canvastools/recttool.py index 5eeb6f23..ae962761 100644 --- a/skimage/viewer/canvastools/recttool.py +++ b/skimage/viewer/canvastools/recttool.py @@ -1,4 +1,7 @@ -import matplotlib.widgets as mwidgets +try: + import matplotlib.widgets as mwidgets +except ImportError: + print("Could not import matplotlib -- skimage.viewer not available.") from skimage.viewer.canvastools.base import CanvasToolBase from skimage.viewer.canvastools.base import ToolHandles From 7dcb2df3fc5806c36c553559f98284ab4cf78d8a Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Thu, 13 Dec 2012 14:29:14 -0500 Subject: [PATCH 025/215] STY: Switch order of callback parameters --- skimage/viewer/canvastools/linetool.py | 12 +++++++++--- skimage/viewer/canvastools/recttool.py | 10 +++++++--- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/skimage/viewer/canvastools/linetool.py b/skimage/viewer/canvastools/linetool.py index fba44b69..9395e513 100644 --- a/skimage/viewer/canvastools/linetool.py +++ b/skimage/viewer/canvastools/linetool.py @@ -12,18 +12,24 @@ __all__ = ['LineTool', 'ThickLineTool'] class LineTool(CanvasToolBase): - """ + """Widget for line selection in a plot. + Parameters ---------- on_move : function - Function accepting end points of line as the only argument. + Function called whenever a control handle is moved. + This function must accept the end points of line as the only argument. + on_release : function + Function called whenever the control handle is released. + on_enter : function + Function called whenever the "enter" key is pressed. Attributes ---------- end_pts : 2D array End points of line ((x1, y1), (x2, y2)). """ - def __init__(self, ax, x, y, on_move=None, on_enter=None, on_release=None, + def __init__(self, ax, x, y, on_move=None, on_release=None, on_enter=None, maxdist=10, lineprops=None): super(LineTool, self).__init__(ax, on_move=on_move, on_enter=on_enter, on_release=on_release) diff --git a/skimage/viewer/canvastools/recttool.py b/skimage/viewer/canvastools/recttool.py index ae962761..8742124e 100644 --- a/skimage/viewer/canvastools/recttool.py +++ b/skimage/viewer/canvastools/recttool.py @@ -19,9 +19,13 @@ class RectangleTool(mwidgets.RectangleSelector, CanvasToolBase): Parameters ---------- ax : :class:`matplotlib.axes.Axes + on_move : function + Function called whenever a control handle is moved. + This function must accept the rectangle extents as the only argument. + on_release : function + Function called whenever the control handle is released. on_enter : function - Function accepting rectangle extents as the only argument; called - whenever "Enter" key is pressed. If None, print extents of rectangle. + Function called whenever the "enter" key is pressed. maxdist : float Maximum distance in pixels for selection of a control handle (i.e. corner or edge) handle. @@ -35,7 +39,7 @@ class RectangleTool(mwidgets.RectangleSelector, CanvasToolBase): Rectangle extents: (xmin, xmax, ymin, ymax). """ - def __init__(self, ax, on_move=None, on_enter=None, on_release=None, + def __init__(self, ax, on_move=None, on_release=None, on_enter=None, maxdist=10, rectprops=None): CanvasToolBase.__init__(self, ax, on_move=on_move, on_enter=on_enter, on_release=on_release) From 6fee0fbcfe4bd2e81be7babc2257992918cddba5 Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Thu, 13 Dec 2012 14:30:02 -0500 Subject: [PATCH 026/215] BUG: Fix line visibility Line gets cleared when "enter" key is pressed. Make sure it's visible on a mouse press. --- skimage/viewer/canvastools/linetool.py | 1 + 1 file changed, 1 insertion(+) diff --git a/skimage/viewer/canvastools/linetool.py b/skimage/viewer/canvastools/linetool.py index 9395e513..91ca2bb2 100644 --- a/skimage/viewer/canvastools/linetool.py +++ b/skimage/viewer/canvastools/linetool.py @@ -62,6 +62,7 @@ class LineTool(CanvasToolBase): def on_mouse_press(self, event): if event.button != 1 and event.inaxes == None: return + self.set_visible(True) idx, px_dist = self._handles.closest(event.x, event.y) if px_dist < self.maxdist: self._active_pt = idx From 70461b4bc968fdd2597a43c3d91e6b1559a920bd Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Thu, 13 Dec 2012 17:26:37 -0500 Subject: [PATCH 027/215] STY: Rename parameters for clarity --- skimage/viewer/canvastools/linetool.py | 13 +++++++++---- skimage/viewer/canvastools/recttool.py | 9 ++++----- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/skimage/viewer/canvastools/linetool.py b/skimage/viewer/canvastools/linetool.py index 91ca2bb2..1d672f0b 100644 --- a/skimage/viewer/canvastools/linetool.py +++ b/skimage/viewer/canvastools/linetool.py @@ -23,6 +23,11 @@ class LineTool(CanvasToolBase): Function called whenever the control handle is released. on_enter : function Function called whenever the "enter" key is pressed. + maxdist : float + Maximum pixel distance allowed when selecting control handle. + line_props : dict + Properties for :class:`matplotlib.patches.Rectangle`. This class + redefines defaults in :class:`matplotlib.widgets.RectangleSelector`. Attributes ---------- @@ -30,12 +35,12 @@ class LineTool(CanvasToolBase): End points of line ((x1, y1), (x2, y2)). """ def __init__(self, ax, x, y, on_move=None, on_release=None, on_enter=None, - maxdist=10, lineprops=None): + maxdist=10, line_props=None): super(LineTool, self).__init__(ax, on_move=on_move, on_enter=on_enter, on_release=on_release) props = dict(color='r', linewidth=1, alpha=0.4, solid_capstyle='butt') - props.update(lineprops if lineprops is not None else {}) + props.update(line_props if line_props is not None else {}) self.linewidth = props['linewidth'] self.maxdist = maxdist self._active_pt = None @@ -102,13 +107,13 @@ class LineTool(CanvasToolBase): class ThickLineTool(LineTool): def __init__(self, ax, x, y, on_move=None, on_enter=None, on_release=None, - maxdist=10, lineprops=None): + maxdist=10, line_props=None): super(ThickLineTool, self).__init__(ax, x, y, on_move=on_move, on_enter=on_enter, on_release=on_release, maxdist=maxdist, - lineprops=lineprops) + line_props=line_props) self.connect_event('scroll_event', self.on_scroll) self.connect_event('key_press_event', self.on_key_press) diff --git a/skimage/viewer/canvastools/recttool.py b/skimage/viewer/canvastools/recttool.py index 8742124e..a8b74e87 100644 --- a/skimage/viewer/canvastools/recttool.py +++ b/skimage/viewer/canvastools/recttool.py @@ -27,9 +27,8 @@ class RectangleTool(mwidgets.RectangleSelector, CanvasToolBase): on_enter : function Function called whenever the "enter" key is pressed. maxdist : float - Maximum distance in pixels for selection of a control handle - (i.e. corner or edge) handle. - rectprops : dict + Maximum pixel distance allowed when selecting control handle. + rect_props : dict Properties for :class:`matplotlib.patches.Rectangle`. This class redefines defaults in :class:`matplotlib.widgets.RectangleSelector`. @@ -40,12 +39,12 @@ class RectangleTool(mwidgets.RectangleSelector, CanvasToolBase): """ def __init__(self, ax, on_move=None, on_release=None, on_enter=None, - maxdist=10, rectprops=None): + maxdist=10, rect_props=None): CanvasToolBase.__init__(self, ax, on_move=on_move, on_enter=on_enter, on_release=on_release) props = dict(edgecolor=None, facecolor='r', alpha=0.15) - props.update(rectprops if rectprops is not None else {}) + props.update(rect_props if rect_props is not None else {}) if props['edgecolor'] is None: props['edgecolor'] = props['facecolor'] mwidgets.RectangleSelector.__init__(self, ax, lambda *args: None, From 43fecd0cb828dd0bb150b576d795fa24f1cf47bb Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Thu, 13 Dec 2012 17:36:22 -0500 Subject: [PATCH 028/215] BUG: Fix renamed parameter --- skimage/viewer/plugins/lineprofile.py | 2 +- skimage/viewer/plugins/measure.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/skimage/viewer/plugins/lineprofile.py b/skimage/viewer/plugins/lineprofile.py index 69326ff2..29939e8e 100644 --- a/skimage/viewer/plugins/lineprofile.py +++ b/skimage/viewer/plugins/lineprofile.py @@ -69,7 +69,7 @@ class LineProfile(PlotPlugin): self.line_tool = ThickLineTool(self.image_viewer.ax, x, y, maxdist=self.maxdist, - on_update=self.line_changed) + on_move=self.line_changed) scan_data = profile_line(image, self.end_pts) self.profile = self.ax.plot(scan_data, 'k-')[0] diff --git a/skimage/viewer/plugins/measure.py b/skimage/viewer/plugins/measure.py index 83dda6d4..046fe2d8 100644 --- a/skimage/viewer/plugins/measure.py +++ b/skimage/viewer/plugins/measure.py @@ -36,7 +36,7 @@ class Measure(Plugin): y = [h / 2] * 2 self.line_tool = LineTool(self.image_viewer.ax, x, y, maxdist=self.maxdist, - on_update=self.line_changed) + on_move=self.line_changed) # initialize displayed values self.line_changed(np.transpose((x, y))) From cfe9e7def9454373a46026bcd201bbbcdb9054d7 Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Thu, 13 Dec 2012 17:58:08 -0500 Subject: [PATCH 029/215] Fix typo --- skimage/viewer/plugins/measure.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skimage/viewer/plugins/measure.py b/skimage/viewer/plugins/measure.py index 046fe2d8..75ee86a4 100644 --- a/skimage/viewer/plugins/measure.py +++ b/skimage/viewer/plugins/measure.py @@ -41,7 +41,7 @@ class Measure(Plugin): self.line_changed(np.transpose((x, y))) def help(self): - helpstr = ("Line profile tool", + helpstr = ("Measure tool", "Select line to measure distance and angle.") return '\n'.join(helpstr) From 8e4f24af00684daf9f184081c12a3f4bd616d114 Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Thu, 13 Dec 2012 18:01:53 -0500 Subject: [PATCH 030/215] ENH: Add color histogram plugin --- skimage/viewer/plugins/color_histogram.py | 68 ++++++++++++++++++++++ viewer_examples/plugins/color_histogram.py | 9 +++ 2 files changed, 77 insertions(+) create mode 100644 skimage/viewer/plugins/color_histogram.py create mode 100644 viewer_examples/plugins/color_histogram.py diff --git a/skimage/viewer/plugins/color_histogram.py b/skimage/viewer/plugins/color_histogram.py new file mode 100644 index 00000000..3c15fbd3 --- /dev/null +++ b/skimage/viewer/plugins/color_histogram.py @@ -0,0 +1,68 @@ +import numpy as np +import matplotlib.pyplot as plt + +from skimage import color +from skimage import exposure +from .plotplugin import PlotPlugin +from ..canvastools import RectangleTool + + +class ColorHistogram(PlotPlugin): + name = 'Color Histogram' + + def __init__(self, **kwargs): + super(ColorHistogram, self).__init__(height=400, **kwargs) + + print self.help() + + def attach(self, image_viewer): + super(ColorHistogram, self).attach(image_viewer) + + self.rect_tool = RectangleTool(self.ax, on_release=self.ab_selected) + self.lab_image = color.rgb2lab(image_viewer.image) + + # Calculate color histogram in the Lab colorspace: + L, a, b = self.lab_image.T + left, right = -100, 100 + ab_extents = [left, right, right, left] + bins = np.arange(left, right) + hist, x_edges, y_edges = np.histogram2d(a.flatten(), b.flatten(), bins, + normed=True) + + # Clip bin heights that dominate a-b histogram + max_val = pct_total_area(hist, percentile=99) + hist = exposure.rescale_intensity(hist, in_range=(0, max_val)) + self.ax.imshow(hist, extent=ab_extents, cmap=plt.cm.gray) + + self.ax.set_title('Color Histogram') + self.ax.set_xlabel('b') + self.ax.set_ylabel('a') + + def help(self): + helpstr = ("Color Histogram tool:", + "Select region of a-b colorspace to highlight on image.") + return '\n'.join(helpstr) + + def ab_selected(self, extents): + x0, x1, y0, y1 = extents + + lab_masked = self.lab_image.copy() + L, a, b = lab_masked.T + + mask = ((a > y0) & (a < y1)) & ((b > x0) & (b < x1)) + lab_masked[..., 1:][~mask.T] = 0 + + self.image_viewer.image = color.lab2rgb(lab_masked) + + +def pct_total_area(image, percentile=80): + """Return threshold value based on percentage of total area. + + The specified percent of pixels less than the given intensity threshold. + """ + idx = int((image.size - 1) * percentile / 100.0) + sorted_pixels = np.sort(image.flat) + return sorted_pixels[idx] + + + diff --git a/viewer_examples/plugins/color_histogram.py b/viewer_examples/plugins/color_histogram.py new file mode 100644 index 00000000..0b654b3e --- /dev/null +++ b/viewer_examples/plugins/color_histogram.py @@ -0,0 +1,9 @@ +from skimage.viewer import ImageViewer +from skimage.viewer.plugins.color_histogram import ColorHistogram +from skimage import data + + +image = data.load('color.png') +viewer = ImageViewer(image) +viewer += ColorHistogram() +viewer.show() From e98476ea41bf41686f92fb7846c0b2160e286e40 Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Thu, 13 Dec 2012 19:11:08 -0500 Subject: [PATCH 031/215] ENH: Handle redraw edge case. You can call redraw before the first draw event so that the background never gets saved. In this case, skip blitting so we don't need the background image. --- skimage/viewer/canvastools/base.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/skimage/viewer/canvastools/base.py b/skimage/viewer/canvastools/base.py index 37329d6a..3d179578 100644 --- a/skimage/viewer/canvastools/base.py +++ b/skimage/viewer/canvastools/base.py @@ -23,6 +23,7 @@ class CanvasToolBase(object): useblit=True): self.ax = ax self.canvas = ax.figure.canvas + self.img_background = None self.cids = [] self._artists = [] self.active = True @@ -76,7 +77,7 @@ class CanvasToolBase(object): This method should be called by subclasses when artists are updated. """ - if self.useblit: + if self.useblit and self.img_background is not None: self.canvas.restore_region(self.img_background) self._draw_artists() self.canvas.blit(self.ax.bbox) From 3d631eadc3e3785288f0aed6b1738a13cc495ca7 Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Thu, 13 Dec 2012 20:40:47 -0500 Subject: [PATCH 032/215] Remove x, y parameters from line tool to simplify API --- skimage/viewer/canvastools/linetool.py | 36 +++++++++++++++++--------- skimage/viewer/plugins/lineprofile.py | 4 ++- 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/skimage/viewer/canvastools/linetool.py b/skimage/viewer/canvastools/linetool.py index 1d672f0b..ea639ee1 100644 --- a/skimage/viewer/canvastools/linetool.py +++ b/skimage/viewer/canvastools/linetool.py @@ -31,10 +31,10 @@ class LineTool(CanvasToolBase): Attributes ---------- - end_pts : 2D array + end_points : 2D array End points of line ((x1, y1), (x2, y2)). """ - def __init__(self, ax, x, y, on_move=None, on_release=None, on_enter=None, + def __init__(self, ax, on_move=None, on_release=None, on_enter=None, maxdist=10, line_props=None): super(LineTool, self).__init__(ax, on_move=on_move, on_enter=on_enter, on_release=on_release) @@ -45,13 +45,15 @@ class LineTool(CanvasToolBase): self.maxdist = maxdist self._active_pt = None - self.end_pts = np.transpose([x, y]) + x = (0, 0) + y = (0, 0) + self._end_pts = np.transpose([x, y]) - self._line = lines.Line2D(x, y, animated=True, **props) + self._line = lines.Line2D(x, y, visible=False, animated=True, **props) ax.add_line(self._line) self._handles = ToolHandles(ax, x, y) - self._handles.set_visible(True) + self._handles.set_visible(False) self._artists = [self._line, self._handles.artist] if on_enter is None: @@ -74,7 +76,7 @@ class LineTool(CanvasToolBase): else: self._active_pt = 0 x, y = event.xdata, event.ydata - self.end_pts = np.array([[x, y], [x, y]]) + self._end_pts = np.array([[x, y], [x, y]]) def on_mouse_release(self, event): if event.button != 1: @@ -92,23 +94,33 @@ class LineTool(CanvasToolBase): def update(self, x, y): if x is not None: - self.end_pts[self._active_pt, :] = x, y - self._line.set_data(np.transpose(self.end_pts)) - self._handles.set_data(np.transpose(self.end_pts)) + self._end_pts[self._active_pt, :] = x, y + self._line.set_data(np.transpose(self._end_pts)) + self._handles.set_data(np.transpose(self._end_pts)) self._line.set_linewidth(self.linewidth) self.redraw() @property def geometry(self): - return self.end_pts + return self.end_points + + @property + def end_points(self): + return self._end_pts + + @end_points.setter + def end_points(self, pts): + self._end_pts = pts + self.set_visible(True) + self.update(None, None) class ThickLineTool(LineTool): - def __init__(self, ax, x, y, on_move=None, on_enter=None, on_release=None, + def __init__(self, ax, on_move=None, on_enter=None, on_release=None, maxdist=10, line_props=None): - super(ThickLineTool, self).__init__(ax, x, y, + super(ThickLineTool, self).__init__(ax, on_move=on_move, on_enter=on_enter, on_release=on_release, diff --git a/skimage/viewer/plugins/lineprofile.py b/skimage/viewer/plugins/lineprofile.py index 29939e8e..855b8ce9 100644 --- a/skimage/viewer/plugins/lineprofile.py +++ b/skimage/viewer/plugins/lineprofile.py @@ -67,9 +67,11 @@ class LineProfile(PlotPlugin): y = [h / 2] * 2 self.end_pts = np.transpose([x, y]) - self.line_tool = ThickLineTool(self.image_viewer.ax, x, y, + self.line_tool = ThickLineTool(self.image_viewer.ax, maxdist=self.maxdist, on_move=self.line_changed) + self.line_tool.end_points = self.end_pts + self.line_tool.update(None, None) scan_data = profile_line(image, self.end_pts) self.profile = self.ax.plot(scan_data, 'k-')[0] From 3fab96fb091dc994120de2ee67985402fe343af0 Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Thu, 13 Dec 2012 20:49:58 -0500 Subject: [PATCH 033/215] STY: Rename end_pts to end_points --- skimage/viewer/plugins/lineprofile.py | 24 +++++++++++------------- skimage/viewer/plugins/measure.py | 4 ++-- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/skimage/viewer/plugins/lineprofile.py b/skimage/viewer/plugins/lineprofile.py index 855b8ce9..665481a0 100644 --- a/skimage/viewer/plugins/lineprofile.py +++ b/skimage/viewer/plugins/lineprofile.py @@ -65,15 +65,13 @@ class LineProfile(PlotPlugin): h, w = image.shape x = [w / 3, 2 * w / 3] y = [h / 2] * 2 - self.end_pts = np.transpose([x, y]) self.line_tool = ThickLineTool(self.image_viewer.ax, maxdist=self.maxdist, on_move=self.line_changed) - self.line_tool.end_points = self.end_pts - self.line_tool.update(None, None) + self.line_tool.end_points = np.transpose([x, y]) - scan_data = profile_line(image, self.end_pts) + scan_data = profile_line(image, self.line_tool.end_points) self.profile = self.ax.plot(scan_data, 'k-')[0] self._autoscale_view() @@ -88,13 +86,13 @@ class LineProfile(PlotPlugin): Returns ------- - end_pts: (2, 2) array + end_points: (2, 2) array The positions ((x1, y1), (x2, y2)) of the line ends. profile: 1d array Profile of intensity values. """ profile = self.profile.get_ydata() - return self.end_pts, profile + return self.line_tool.end_points, profile def _autoscale_view(self): if self.limits is None: @@ -102,10 +100,10 @@ class LineProfile(PlotPlugin): else: self.ax.autoscale_view(scaley=False, tight=True) - def line_changed(self, end_pts): - x, y = np.transpose(end_pts) - self.end_pts = end_pts - scan = profile_line(self.image_viewer.original_image, end_pts, + def line_changed(self, end_points): + x, y = np.transpose(end_points) + self.line_tool.end_points = end_points + scan = profile_line(self.image_viewer.original_image, end_points, linewidth=self.line_tool.linewidth) self.profile.set_xdata(np.arange(scan.shape[0])) @@ -120,14 +118,14 @@ class LineProfile(PlotPlugin): self.redraw() -def profile_line(img, end_pts, linewidth=1): +def profile_line(img, end_points, 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: (2, 2) list End points ((x1, y1), (x2, y2)) of scan line. linewidth: int Width of the scan, perpendicular to the line @@ -138,7 +136,7 @@ def profile_line(img, end_pts, linewidth=1): 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 + point1, point2 = end_points x1, y1 = point1 = np.asarray(point1, dtype=float) x2, y2 = point2 = np.asarray(point2, dtype=float) dx, dy = point2 - point1 diff --git a/skimage/viewer/plugins/measure.py b/skimage/viewer/plugins/measure.py index 75ee86a4..6f385aa4 100644 --- a/skimage/viewer/plugins/measure.py +++ b/skimage/viewer/plugins/measure.py @@ -45,8 +45,8 @@ class Measure(Plugin): "Select line to measure distance and angle.") return '\n'.join(helpstr) - def line_changed(self, end_pts): - x, y = np.transpose(end_pts) + def line_changed(self, end_points): + x, y = np.transpose(end_points) dx = np.diff(x)[0] dy = np.diff(y)[0] self._length.text = '%.1f' % np.hypot(dx, dy) From e5dfb8c7acf3c5dcba2cbe4da29fba74513a4731 Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Thu, 13 Dec 2012 20:56:27 -0500 Subject: [PATCH 034/215] STY: Remove linewidth parameter to simplify API --- skimage/viewer/plugins/lineprofile.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/skimage/viewer/plugins/lineprofile.py b/skimage/viewer/plugins/lineprofile.py index 665481a0..9e5b90f1 100644 --- a/skimage/viewer/plugins/lineprofile.py +++ b/skimage/viewer/plugins/lineprofile.py @@ -18,8 +18,6 @@ class LineProfile(PlotPlugin): Parameters ---------- - linewidth : float - Line width for interpolation. Wider lines average over more pixels. maxdist : float Maximum pixel distance allowed when selecting end point of scan line. epsilon : float @@ -34,7 +32,7 @@ class LineProfile(PlotPlugin): """ name = 'Line Profile' - def __init__(self, linewidth=1, maxdist=10, epsilon='deprecated', + def __init__(self, maxdist=10, epsilon='deprecated', limits='image', **kwargs): super(LineProfile, self).__init__(**kwargs) From 260984c018ab7532d948a395f5c8ba239d10e76a Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Thu, 13 Dec 2012 21:11:53 -0500 Subject: [PATCH 035/215] DOC: Add parameters to docstring --- skimage/viewer/canvastools/base.py | 12 ++++++++++ skimage/viewer/canvastools/linetool.py | 31 ++++++++++++++++++++++++-- skimage/viewer/canvastools/recttool.py | 3 ++- 3 files changed, 43 insertions(+), 3 deletions(-) diff --git a/skimage/viewer/canvastools/base.py b/skimage/viewer/canvastools/base.py index 3d179578..05709c61 100644 --- a/skimage/viewer/canvastools/base.py +++ b/skimage/viewer/canvastools/base.py @@ -18,6 +18,18 @@ class CanvasToolBase(object): Parameters ---------- + ax : :class:`matplotlib.axes.Axes` + Matplotlib axes where tool is displayed. + on_move : function + Function called whenever a control handle is moved. + This function must accept the end points of line as the only argument. + on_release : function + Function called whenever the control handle is released. + on_enter : function + Function called whenever the "enter" key is pressed. + useblit : bool + If True, update canvas by blitting, which is much faster than normal + redrawing (turn off for debugging purposes). """ def __init__(self, ax, on_move=None, on_enter=None, on_release=None, useblit=True): diff --git a/skimage/viewer/canvastools/linetool.py b/skimage/viewer/canvastools/linetool.py index ea639ee1..6937f38a 100644 --- a/skimage/viewer/canvastools/linetool.py +++ b/skimage/viewer/canvastools/linetool.py @@ -16,6 +16,8 @@ class LineTool(CanvasToolBase): Parameters ---------- + ax : :class:`matplotlib.axes.Axes` + Matplotlib axes where tool is displayed. on_move : function Function called whenever a control handle is moved. This function must accept the end points of line as the only argument. @@ -26,8 +28,7 @@ class LineTool(CanvasToolBase): maxdist : float Maximum pixel distance allowed when selecting control handle. line_props : dict - Properties for :class:`matplotlib.patches.Rectangle`. This class - redefines defaults in :class:`matplotlib.widgets.RectangleSelector`. + Properties for :class:`matplotlib.lines.Line2D`. Attributes ---------- @@ -117,6 +118,32 @@ class LineTool(CanvasToolBase): class ThickLineTool(LineTool): + """Widget for line selection in a plot. + + The thickness of the line can be varied using the mouse scroll wheel, or + with the '+' and '-' keys. + + Parameters + ---------- + ax : :class:`matplotlib.axes.Axes` + Matplotlib axes where tool is displayed. + on_move : function + Function called whenever a control handle is moved. + This function must accept the end points of line as the only argument. + on_release : function + Function called whenever the control handle is released. + on_enter : function + Function called whenever the "enter" key is pressed. + maxdist : float + Maximum pixel distance allowed when selecting control handle. + line_props : dict + Properties for :class:`matplotlib.lines.Line2D`. + + Attributes + ---------- + end_points : 2D array + End points of line ((x1, y1), (x2, y2)). + """ def __init__(self, ax, on_move=None, on_enter=None, on_release=None, maxdist=10, line_props=None): diff --git a/skimage/viewer/canvastools/recttool.py b/skimage/viewer/canvastools/recttool.py index a8b74e87..ce321c42 100644 --- a/skimage/viewer/canvastools/recttool.py +++ b/skimage/viewer/canvastools/recttool.py @@ -18,7 +18,8 @@ class RectangleTool(mwidgets.RectangleSelector, CanvasToolBase): Parameters ---------- - ax : :class:`matplotlib.axes.Axes + ax : :class:`matplotlib.axes.Axes` + Matplotlib axes where tool is displayed. on_move : function Function called whenever a control handle is moved. This function must accept the rectangle extents as the only argument. From 25b9f5d5aa7d46c55121bcaf53f9d826b18e2816 Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Thu, 13 Dec 2012 21:12:35 -0500 Subject: [PATCH 036/215] BUG: Fix LineTool example after change to call signature x, y parameters were removed for simplicity, but example wasn't updated --- skimage/viewer/canvastools/linetool.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/skimage/viewer/canvastools/linetool.py b/skimage/viewer/canvastools/linetool.py index 6937f38a..c8287016 100644 --- a/skimage/viewer/canvastools/linetool.py +++ b/skimage/viewer/canvastools/linetool.py @@ -191,6 +191,7 @@ if __name__ == '__main__': ax.imshow(image, interpolation='nearest') h, w = image.shape - # line_tool = LineTool(ax, [w/3, 2*w/3], [h/2, h/2]) - line_tool = ThickLineTool(ax, [w/3, 2*w/3], [h/2, h/2]) + # line_tool = LineTool(ax) + line_tool = ThickLineTool(ax) + line_tool.end_points = ([w/3, h/2], [2*w/3, h/2]) plt.show() From 34de6d323a1ea3e13333299abaa3ea1b648c9bd7 Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Thu, 13 Dec 2012 21:21:36 -0500 Subject: [PATCH 037/215] BUG: Fix line thickness change call back --- skimage/viewer/canvastools/linetool.py | 11 ++++++++++- skimage/viewer/plugins/lineprofile.py | 3 ++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/skimage/viewer/canvastools/linetool.py b/skimage/viewer/canvastools/linetool.py index c8287016..0c14c2ae 100644 --- a/skimage/viewer/canvastools/linetool.py +++ b/skimage/viewer/canvastools/linetool.py @@ -134,6 +134,8 @@ class ThickLineTool(LineTool): Function called whenever the control handle is released. on_enter : function Function called whenever the "enter" key is pressed. + on_change : function + Function called whenever the line thickness is changed. maxdist : float Maximum pixel distance allowed when selecting control handle. line_props : dict @@ -146,7 +148,7 @@ class ThickLineTool(LineTool): """ def __init__(self, ax, on_move=None, on_enter=None, on_release=None, - maxdist=10, line_props=None): + on_change=None, maxdist=10, line_props=None): super(ThickLineTool, self).__init__(ax, on_move=on_move, on_enter=on_enter, @@ -154,6 +156,11 @@ class ThickLineTool(LineTool): maxdist=maxdist, line_props=line_props) + if on_change is None: + def on_change(*args): + pass + self.callback_on_change = on_change + self.connect_event('scroll_event', self.on_scroll) self.connect_event('key_press_event', self.on_key_press) @@ -174,11 +181,13 @@ class ThickLineTool(LineTool): def _thicken_scan_line(self): self.linewidth += 1 self.update(None, None) + self.callback_on_change(self.geometry) def _shrink_scan_line(self): if self.linewidth > 1: self.linewidth -= 1 self.update(None, None) + self.callback_on_change(self.geometry) if __name__ == '__main__': diff --git a/skimage/viewer/plugins/lineprofile.py b/skimage/viewer/plugins/lineprofile.py index 9e5b90f1..5946b478 100644 --- a/skimage/viewer/plugins/lineprofile.py +++ b/skimage/viewer/plugins/lineprofile.py @@ -66,7 +66,8 @@ class LineProfile(PlotPlugin): self.line_tool = ThickLineTool(self.image_viewer.ax, maxdist=self.maxdist, - on_move=self.line_changed) + on_move=self.line_changed, + on_change=self.line_changed) self.line_tool.end_points = np.transpose([x, y]) scan_data = profile_line(image, self.line_tool.end_points) From 2e7e92f536be0c4f3523d93be54b48a73b53b76d Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Thu, 13 Dec 2012 21:31:54 -0500 Subject: [PATCH 038/215] STY: Simplify imports --- skimage/viewer/plugins/crop.py | 2 +- viewer_examples/plugins/median_filter.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/skimage/viewer/plugins/crop.py b/skimage/viewer/plugins/crop.py index 49f6014b..97a43cb5 100644 --- a/skimage/viewer/plugins/crop.py +++ b/skimage/viewer/plugins/crop.py @@ -1,6 +1,6 @@ from .base import Plugin from ..canvastools import RectangleTool -from skimage.viewer.widgets.history import SaveButtons +from skimage.viewer.widgets import SaveButtons __all__ = ['Crop'] diff --git a/viewer_examples/plugins/median_filter.py b/viewer_examples/plugins/median_filter.py index 3a050382..a20ad8f3 100644 --- a/viewer_examples/plugins/median_filter.py +++ b/viewer_examples/plugins/median_filter.py @@ -2,8 +2,7 @@ from skimage import data from skimage.filter import median_filter from skimage.viewer import ImageViewer -from skimage.viewer.widgets import Slider -from skimage.viewer.widgets.history import OKCancelButtons, SaveButtons +from skimage.viewer.widgets import Slider, OKCancelButtons, SaveButtons from skimage.viewer.plugins.base import Plugin From 78e1efefcb71314c4b098e0884a86a6afd30022d Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Thu, 13 Dec 2012 21:52:32 -0500 Subject: [PATCH 039/215] ENH: Add setter for extents property --- skimage/viewer/canvastools/recttool.py | 32 +++++++++++++++----------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/skimage/viewer/canvastools/recttool.py b/skimage/viewer/canvastools/recttool.py index ce321c42..a34348d1 100644 --- a/skimage/viewer/canvastools/recttool.py +++ b/skimage/viewer/canvastools/recttool.py @@ -112,6 +112,23 @@ class RectangleTool(mwidgets.RectangleSelector, CanvasToolBase): ymin, ymax = sorted([y0, y0 + height]) return xmin, xmax, ymin, ymax + @extents.setter + def extents(self, extents): + x1, x2, y1, y2 = extents + xmin, xmax = sorted([x1, x2]) + ymin, ymax = sorted([y1, y2]) + # Update displayed rectangle + self._rect.set_x(xmin) + self._rect.set_y(ymin) + self._rect.set_width(xmax - xmin) + self._rect.set_height(ymax - ymin) + # Update displayed handles + self._corner_handles.set_data(*self.corners) + self._edge_handles.set_data(*self.edge_centers) + + self.set_visible(True) + self.redraw() + def release(self, event): mwidgets.RectangleSelector.release(self, event) self._extents_on_press = None @@ -169,20 +186,7 @@ class RectangleTool(mwidgets.RectangleSelector, CanvasToolBase): x2 = event.xdata if self.active_handle in ['N', 'S'] + self._corner_order: y2 = event.ydata - xmin, xmax = sorted([x1, x2]) - ymin, ymax = sorted([y1, y2]) - - # Update displayed rectangle - self._rect.set_x(xmin) - self._rect.set_y(ymin) - self._rect.set_width(xmax - xmin) - self._rect.set_height(ymax - ymin) - - # Update displayed handles - self._corner_handles.set_data(*self.corners) - self._edge_handles.set_data(*self.edge_centers) - - self.redraw() + self.extents = (x1, x2, y1, y2) self.callback_on_move(self.geometry) @property From 1a199b4405324d73422b25b3e236b984d73a4ec7 Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Thu, 13 Dec 2012 22:25:53 -0500 Subject: [PATCH 040/215] ENH: Improve end_points setter --- skimage/viewer/canvastools/linetool.py | 37 +++++++++++++------------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/skimage/viewer/canvastools/linetool.py b/skimage/viewer/canvastools/linetool.py index 0c14c2ae..59f22076 100644 --- a/skimage/viewer/canvastools/linetool.py +++ b/skimage/viewer/canvastools/linetool.py @@ -67,6 +67,21 @@ class LineTool(CanvasToolBase): self.connect_event('button_release_event', self.on_mouse_release) self.connect_event('motion_notify_event', self.on_move) + @property + def end_points(self): + return self._end_pts + + @end_points.setter + def end_points(self, pts): + self._end_pts = pts + + self._line.set_data(np.transpose(pts)) + self._handles.set_data(np.transpose(pts)) + self._line.set_linewidth(self.linewidth) + + self.set_visible(True) + self.redraw() + def on_mouse_press(self, event): if event.button != 1 and event.inaxes == None: return @@ -93,29 +108,15 @@ class LineTool(CanvasToolBase): self.update(event.xdata, event.ydata) self.callback_on_move(self.geometry) - def update(self, x, y): + def update(self, x=None, y=None): if x is not None: self._end_pts[self._active_pt, :] = x, y - self._line.set_data(np.transpose(self._end_pts)) - self._handles.set_data(np.transpose(self._end_pts)) - self._line.set_linewidth(self.linewidth) - - self.redraw() + self.end_points = self._end_pts @property def geometry(self): return self.end_points - @property - def end_points(self): - return self._end_pts - - @end_points.setter - def end_points(self, pts): - self._end_pts = pts - self.set_visible(True) - self.update(None, None) - class ThickLineTool(LineTool): """Widget for line selection in a plot. @@ -180,13 +181,13 @@ class ThickLineTool(LineTool): def _thicken_scan_line(self): self.linewidth += 1 - self.update(None, None) + self.update() self.callback_on_change(self.geometry) def _shrink_scan_line(self): if self.linewidth > 1: self.linewidth -= 1 - self.update(None, None) + self.update() self.callback_on_change(self.geometry) From 829479512ff6561a4b0373ebed64194d55076982 Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Thu, 13 Dec 2012 22:26:26 -0500 Subject: [PATCH 041/215] BUG: Update measure for change in line_tool API --- skimage/viewer/plugins/measure.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/skimage/viewer/plugins/measure.py b/skimage/viewer/plugins/measure.py index 6f385aa4..e05f8fe8 100644 --- a/skimage/viewer/plugins/measure.py +++ b/skimage/viewer/plugins/measure.py @@ -32,13 +32,9 @@ class Measure(Plugin): image = image_viewer.original_image h, w = image.shape - x = [w / 3, 2 * w / 3] - y = [h / 2] * 2 - self.line_tool = LineTool(self.image_viewer.ax, x, y, + self.line_tool = LineTool(self.image_viewer.ax, maxdist=self.maxdist, on_move=self.line_changed) - # initialize displayed values - self.line_changed(np.transpose((x, y))) def help(self): helpstr = ("Measure tool", From 2532c659ec0144fcdd87e2f9b4a963bfa61d9f4e Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Thu, 13 Dec 2012 23:40:33 -0500 Subject: [PATCH 042/215] BUG: Fix error when matplotlib is not available --- skimage/viewer/canvastools/recttool.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/skimage/viewer/canvastools/recttool.py b/skimage/viewer/canvastools/recttool.py index a34348d1..909763ab 100644 --- a/skimage/viewer/canvastools/recttool.py +++ b/skimage/viewer/canvastools/recttool.py @@ -1,6 +1,7 @@ try: - import matplotlib.widgets as mwidgets + from matplotlib.widgets import RectangleSelector except ImportError: + RectangleSelector = object print("Could not import matplotlib -- skimage.viewer not available.") from skimage.viewer.canvastools.base import CanvasToolBase @@ -10,7 +11,7 @@ from skimage.viewer.canvastools.base import ToolHandles __all__ = ['RectangleTool'] -class RectangleTool(mwidgets.RectangleSelector, CanvasToolBase): +class RectangleTool(RectangleSelector, CanvasToolBase): """Widget for selecting a rectangular region in a plot. After making the desired selection, press "Enter" to accept the selection @@ -48,9 +49,9 @@ class RectangleTool(mwidgets.RectangleSelector, CanvasToolBase): props.update(rect_props if rect_props is not None else {}) if props['edgecolor'] is None: props['edgecolor'] = props['facecolor'] - mwidgets.RectangleSelector.__init__(self, ax, lambda *args: None, - rectprops=props, - useblit=self.useblit) + RectangleSelector.__init__(self, ax, lambda *args: None, + rectprops=props, + useblit=self.useblit) # Alias rectangle attribute, which is initialized in RectangleSelector. self._rect = self.to_draw self._rect.set_animated(True) @@ -130,7 +131,7 @@ class RectangleTool(mwidgets.RectangleSelector, CanvasToolBase): self.redraw() def release(self, event): - mwidgets.RectangleSelector.release(self, event) + RectangleSelector.release(self, event) self._extents_on_press = None # Undo hiding of rectangle and redraw. self.set_visible(True) @@ -144,7 +145,7 @@ class RectangleTool(mwidgets.RectangleSelector, CanvasToolBase): self.set_visible(False) self.redraw() self.set_visible(True) - mwidgets.RectangleSelector.press(self, event) + RectangleSelector.press(self, event) def _set_active_handle(self, event): """Set active handle based on the location of the mouse event""" From 67042858599cba303fd8a33c895f946132641661 Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Fri, 14 Dec 2012 10:01:52 -0500 Subject: [PATCH 043/215] BUG: Fix MRO bug when matplotlib not installed --- skimage/viewer/canvastools/recttool.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skimage/viewer/canvastools/recttool.py b/skimage/viewer/canvastools/recttool.py index 909763ab..dd6886c7 100644 --- a/skimage/viewer/canvastools/recttool.py +++ b/skimage/viewer/canvastools/recttool.py @@ -11,7 +11,7 @@ from skimage.viewer.canvastools.base import ToolHandles __all__ = ['RectangleTool'] -class RectangleTool(RectangleSelector, CanvasToolBase): +class RectangleTool(CanvasToolBase, RectangleSelector): """Widget for selecting a rectangular region in a plot. After making the desired selection, press "Enter" to accept the selection From 244e68c232ced5fd58eb6347aa1d94e64cc61dbd Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Sat, 15 Dec 2012 09:27:07 -0500 Subject: [PATCH 044/215] BUG: fix end_points setter when given a tuple Dragging of end points was broken. --- skimage/viewer/canvastools/linetool.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skimage/viewer/canvastools/linetool.py b/skimage/viewer/canvastools/linetool.py index 59f22076..7195e91d 100644 --- a/skimage/viewer/canvastools/linetool.py +++ b/skimage/viewer/canvastools/linetool.py @@ -73,7 +73,7 @@ class LineTool(CanvasToolBase): @end_points.setter def end_points(self, pts): - self._end_pts = pts + self._end_pts = np.asarray(pts) self._line.set_data(np.transpose(pts)) self._handles.set_data(np.transpose(pts)) From ea0821ff93afc88e6861163ad56ebf6b7422450c Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Sat, 15 Dec 2012 09:28:41 -0500 Subject: [PATCH 045/215] BUG: Validate mouse interaction Fixes error messages when mouse moved out of axes, plus a few other issues --- skimage/viewer/canvastools/linetool.py | 2 +- skimage/viewer/canvastools/recttool.py | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/skimage/viewer/canvastools/linetool.py b/skimage/viewer/canvastools/linetool.py index 7195e91d..7cdf91b5 100644 --- a/skimage/viewer/canvastools/linetool.py +++ b/skimage/viewer/canvastools/linetool.py @@ -83,7 +83,7 @@ class LineTool(CanvasToolBase): self.redraw() def on_mouse_press(self, event): - if event.button != 1 and event.inaxes == None: + if event.button != 1 or not self.ax.in_axes(event): return self.set_visible(True) idx, px_dist = self._handles.closest(event.x, event.y) diff --git a/skimage/viewer/canvastools/recttool.py b/skimage/viewer/canvastools/recttool.py index dd6886c7..3dcff080 100644 --- a/skimage/viewer/canvastools/recttool.py +++ b/skimage/viewer/canvastools/recttool.py @@ -131,6 +131,11 @@ class RectangleTool(CanvasToolBase, RectangleSelector): self.redraw() def release(self, event): + if event.button != 1: + return + if not self.ax.in_axes(event): + self.eventpress = None + return RectangleSelector.release(self, event) self._extents_on_press = None # Undo hiding of rectangle and redraw. @@ -139,12 +144,14 @@ class RectangleTool(CanvasToolBase, RectangleSelector): self.callback_on_release(self.geometry) def press(self, event): + if event.button != 1 or not self.ax.in_axes(event): + return self._set_active_handle(event) if self.active_handle is None: # Clear previous rectangle before drawing new rectangle. self.set_visible(False) self.redraw() - self.set_visible(True) + self.set_visible(True) RectangleSelector.press(self, event) def _set_active_handle(self, event): @@ -172,8 +179,7 @@ class RectangleTool(CanvasToolBase, RectangleSelector): self._extents_on_press = x1, x2, y1, y2 def onmove(self, event): - - if self.eventpress is None or self.ignore(event): + if self.eventpress is None or not self.ax.in_axes(event): return if self.active_handle is None: From 9bf512da8b93b640431c8520fec18f7b62e86663 Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Sat, 15 Dec 2012 09:32:26 -0500 Subject: [PATCH 046/215] ENH: Allow Save buttons to save overlay Closes Github #264 --- skimage/viewer/plugins/base.py | 5 +++++ skimage/viewer/plugins/overlayplugin.py | 8 ++++++++ skimage/viewer/widgets/history.py | 9 ++++++++- 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/skimage/viewer/plugins/base.py b/skimage/viewer/plugins/base.py index c5dd2770..9f5d25dc 100644 --- a/skimage/viewer/plugins/base.py +++ b/skimage/viewer/plugins/base.py @@ -165,6 +165,11 @@ class Plugin(QDialog): # If param is a widget, return its `val` attribute. return param if not hasattr(param, 'val') else param.val + @property + def filtered_image(self): + """Return filtered image.""" + return self.image_viewer.image + def display_filtered_image(self, image): """Display the filtered image on image viewer. diff --git a/skimage/viewer/plugins/overlayplugin.py b/skimage/viewer/plugins/overlayplugin.py index f9d37e59..b4307d34 100644 --- a/skimage/viewer/plugins/overlayplugin.py +++ b/skimage/viewer/plugins/overlayplugin.py @@ -74,6 +74,14 @@ class OverlayPlugin(Plugin): self._overlay_plot.set_cmap(self.cmap) self.image_viewer.redraw() + @property + def filtered_image(self): + """Return filtered image. + + This "filtered image" is used when saving from the plugin. + """ + return self.overlay + def display_filtered_image(self, image): """Display filtered image as an overlay on top of image in viewer.""" self.overlay = image diff --git a/skimage/viewer/widgets/history.py b/skimage/viewer/widgets/history.py index efc8a21c..43a940dc 100644 --- a/skimage/viewer/widgets/history.py +++ b/skimage/viewer/widgets/history.py @@ -6,6 +6,9 @@ try: except ImportError: print("Could not import PyQt4 -- skimage.viewer not available.") +import numpy as np + +import skimage from skimage import io from .core import BaseWidget @@ -84,7 +87,11 @@ class SaveButtons(BaseWidget): basename, ext = os.path.splitext(filename) if not ext: filename = '%s.%s' % (filename, self.default_format) - io.imsave(filename, self.plugin.image_viewer.image) + image = self.plugin.filtered_image + if image.dtype == np.bool: + #TODO: This check/conversion should probably be in `imsave`. + image = skimage.img_as_ubyte(image) + io.imsave(filename, image) def notify(msg): From 70f1e6dd486bdd0ef21ae83d16d886d2e4fd5a68 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Sun, 16 Dec 2012 20:56:03 -0600 Subject: [PATCH 047/215] BUG: Remove focus from buttons which caused unintential clicking. --- skimage/viewer/widgets/history.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/skimage/viewer/widgets/history.py b/skimage/viewer/widgets/history.py index 43a940dc..3adac0db 100644 --- a/skimage/viewer/widgets/history.py +++ b/skimage/viewer/widgets/history.py @@ -29,9 +29,11 @@ class OKCancelButtons(BaseWidget): self.ok = QtGui.QPushButton('OK') self.ok.clicked.connect(self.update_original_image) self.ok.setMaximumWidth(button_width) + self.ok.setFocusPolicy(QtCore.Qt.NoFocus) self.cancel = QtGui.QPushButton('Cancel') self.cancel.clicked.connect(self.close_plugin) self.cancel.setMaximumWidth(button_width) + self.cancel.setFocusPolicy(QtCore.Qt.NoFocus) self.layout = QtGui.QHBoxLayout(self) self.layout.addStretch() @@ -61,8 +63,10 @@ class SaveButtons(BaseWidget): self.save_file = QtGui.QPushButton('File') self.save_file.clicked.connect(self.save_to_file) + self.save_file.setFocusPolicy(QtCore.Qt.NoFocus) self.save_stack = QtGui.QPushButton('Stack') self.save_stack.clicked.connect(self.save_to_stack) + self.save_stack.setFocusPolicy(QtCore.Qt.NoFocus) self.layout = QtGui.QHBoxLayout(self) self.layout.addWidget(self.name_label) From 1fd4042ee6e881e3f1eeb81cd3e9b45634afdac5 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Sun, 16 Dec 2012 21:00:57 -0600 Subject: [PATCH 048/215] BUG: Added required QtCore import --- skimage/viewer/widgets/history.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skimage/viewer/widgets/history.py b/skimage/viewer/widgets/history.py index 3adac0db..0d9d39ae 100644 --- a/skimage/viewer/widgets/history.py +++ b/skimage/viewer/widgets/history.py @@ -2,7 +2,7 @@ import os from textwrap import dedent try: - from PyQt4 import QtGui + from PyQt4 import QtGui, QtCore except ImportError: print("Could not import PyQt4 -- skimage.viewer not available.") From 9c88dd8644c17166e1778be5d05e148a2fafa46d Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Sun, 16 Dec 2012 22:15:11 -0500 Subject: [PATCH 049/215] ENH: Let CannyPlugin adapt to input image Add `dtype_limits` helper function. --- skimage/util/dtype.py | 19 ++++++++++++++++++- skimage/viewer/plugins/canny.py | 18 +++++++++++++++--- 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/skimage/util/dtype.py b/skimage/util/dtype.py index 9f804406..b8d57243 100644 --- a/skimage/util/dtype.py +++ b/skimage/util/dtype.py @@ -2,7 +2,7 @@ from __future__ import division import numpy as np __all__ = ['img_as_float', 'img_as_int', 'img_as_uint', 'img_as_ubyte', - 'img_as_bool'] + 'img_as_bool', 'dtype_limits'] from .. import get_log log = get_log('dtype_converter') @@ -28,6 +28,23 @@ if np.__version__ >= "1.6.0": _supported_types += (np.float16, ) +def dtype_limits(image, auto_clip=True): + """Return intensity limits, i.e. (min, max) tuple, of the image's dtype. + + Parameters + ---------- + image : ndarray + Input image. + auto_clip : bool + If True, clip the negative range (i.e. return 0 for min intensity) + if the input image has no negative values. + """ + imin, imax = dtype_range[image.dtype.type] + if auto_clip and imin < 0 and image.min() >= 0: + imin = 0 + return imin, imax + + def convert(image, dtype, force_copy=False, uniform=False): """ Convert an image to the requested data-type. diff --git a/skimage/viewer/plugins/canny.py b/skimage/viewer/plugins/canny.py index 7c7bfb3b..c2294ba8 100644 --- a/skimage/viewer/plugins/canny.py +++ b/skimage/viewer/plugins/canny.py @@ -1,5 +1,7 @@ -from skimage.filter import canny +import numpy as np +import skimage +from skimage.filter import canny from .overlayplugin import OverlayPlugin from ..widgets import Slider, ComboBox @@ -12,7 +14,17 @@ class CannyPlugin(OverlayPlugin): def __init__(self, *args, **kwargs): super(CannyPlugin, self).__init__(image_filter=canny, **kwargs) + def attach(self, image_viewer): + image = image_viewer.image + imin, imax = skimage.dtype_limits(image) + itype = 'float' if np.issubdtype(image.dtype, float) else 'int' self.add_widget(Slider('sigma', 0, 5, update_on='release')) - self.add_widget(Slider('low threshold', 0, 255, update_on='release')) - self.add_widget(Slider('high threshold', 0, 255, update_on='release')) + self.add_widget(Slider('low threshold', imin, imax, value_type=itype, + update_on='release')) + self.add_widget(Slider('high threshold', imin, imax, value_type=itype, + update_on='release')) self.add_widget(ComboBox('color', self.color_names, ptype='plugin')) + # Call parent method at end b/c it calls `filter_image`, which needs + # the values specified by the widgets. Alternatively, move call to + # parent method to beginning and add a call to `self.filter_image()` + super(CannyPlugin,self).attach(image_viewer) From 0e08acd7c96049aa3c04e6c701a6ff80aa55ca37 Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Sun, 16 Dec 2012 22:49:12 -0500 Subject: [PATCH 050/215] ENH: Warn if using OverlayPlugin w/ matplotlib < 1.2 --- skimage/viewer/plugins/overlayplugin.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/skimage/viewer/plugins/overlayplugin.py b/skimage/viewer/plugins/overlayplugin.py index b4307d34..278f072c 100644 --- a/skimage/viewer/plugins/overlayplugin.py +++ b/skimage/viewer/plugins/overlayplugin.py @@ -1,8 +1,19 @@ +from warnings import warn + from skimage.util.dtype import dtype_range from .base import Plugin from ..utils import ClearColormap +__all__ = ['OverlayPlugin'] + + +def recent_mpl_version(): + import matplotlib + version = matplotlib.__version__.split('.') + return int(version[0]) == 1 and int(version[1]) >= 2 + + class OverlayPlugin(Plugin): """Plugin for ImageViewer that displays an overlay on top of main image. @@ -25,6 +36,9 @@ class OverlayPlugin(Plugin): 'cyan': (0, 1, 1)} def __init__(self, **kwargs): + if not recent_mpl_version(): + msg = "Matplotlib >= 1.2 required for OverlayPlugin." + warn(RuntimeWarning(msg)) super(OverlayPlugin, self).__init__(**kwargs) self._overlay_plot = None self._overlay = None From 06a8e7af85c0233541bc2b3f3692adfa1a9fc60e Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Wed, 19 Dec 2012 14:07:42 -0500 Subject: [PATCH 051/215] BUG: Fix save to stack so it saves filtered image --- skimage/viewer/widgets/history.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skimage/viewer/widgets/history.py b/skimage/viewer/widgets/history.py index 0d9d39ae..ce87f9ac 100644 --- a/skimage/viewer/widgets/history.py +++ b/skimage/viewer/widgets/history.py @@ -74,7 +74,7 @@ class SaveButtons(BaseWidget): self.layout.addWidget(self.save_file) def save_to_stack(self): - image = self.plugin.image_viewer.image.copy() + image = self.plugin.filtered_image.copy() io.push(image) msg = dedent('''\ From d3edd350f9baa5d771b12492e3aba859ef7956e6 Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Sun, 23 Dec 2012 16:19:03 -0800 Subject: [PATCH 052/215] ENH: Add open and save menu actions --- skimage/viewer/utils/dialogs.py | 26 +++++++++++++++++++++++++ skimage/viewer/viewers/core.py | 32 ++++++++++++++++++++++++++++++- skimage/viewer/widgets/history.py | 10 +++------- 3 files changed, 60 insertions(+), 8 deletions(-) create mode 100644 skimage/viewer/utils/dialogs.py diff --git a/skimage/viewer/utils/dialogs.py b/skimage/viewer/utils/dialogs.py new file mode 100644 index 00000000..849fcad7 --- /dev/null +++ b/skimage/viewer/utils/dialogs.py @@ -0,0 +1,26 @@ +import os + +try: + from PyQt4 import QtGui +except ImportError: + print("Could not import PyQt4 -- skimage.viewer not available.") + + +def open_file_dialog(default_format='png'): + """Return user-selected file path.""" + filename = str(QtGui.QFileDialog.getOpenFileName()) + if len(filename) == 0: + return None + return filename + + +def save_file_dialog(default_format='png'): + """Return user-selected file path.""" + filename = str(QtGui.QFileDialog.getSaveFileName()) + if len(filename) == 0: + return None + #TODO: io plugins should assign default image formats + basename, ext = os.path.splitext(filename) + if not ext: + filename = '%s.%s' % (filename, default_format) + return filename diff --git a/skimage/viewer/viewers/core.py b/skimage/viewer/viewers/core.py index 1c369707..83ef65a3 100644 --- a/skimage/viewer/viewers/core.py +++ b/skimage/viewer/viewers/core.py @@ -8,9 +8,11 @@ except ImportError: QMainWindow = object # hack to prevent nosetest and autodoc errors print("Could not import PyQt4 -- skimage.viewer not available.") +from skimage import io from skimage.util.dtype import dtype_range from .. import utils from ..widgets import Slider +from ..utils import dialogs __all__ = ['ImageViewer', 'CollectionViewer'] @@ -58,7 +60,11 @@ class ImageViewer(QMainWindow): self.setWindowTitle("Image Viewer") self.file_menu = QtGui.QMenu('&File', self) - self.file_menu.addAction('&Quit', self.close, + self.file_menu.addAction('Open file', self.open_file, + QtCore.Qt.CTRL + QtCore.Qt.Key_O) + self.file_menu.addAction('Save to file', self.save_to_file, + QtCore.Qt.CTRL + QtCore.Qt.Key_S) + self.file_menu.addAction('Quit', self.close, QtCore.Qt.CTRL + QtCore.Qt.Key_Q) self.menuBar().addMenu(self.file_menu) @@ -101,6 +107,30 @@ class ImageViewer(QMainWindow): plugin.attach(self) return self + def open_file(self): + """Open image file and display in viewer.""" + filename = dialogs.open_file_dialog() + if filename is None: + return + image = io.imread(filename) + self.original_image = image # update saved image + self.image = image # update displayed image + + def save_to_file(self): + """Save current image to file. + + The current behavior is not ideal: It saves the image displayed on + screen, so all images will be converted to RGB, and the image size is + not preserved (resizing the viewer window will alter the size of the + saved image). + """ + filename = dialogs.save_file_dialog() + if filename is None: + return + extent = self.ax.get_window_extent() + extent = extent.transformed(self.fig.dpi_scale_trans.inverted()) + self.fig.savefig(filename, bbox_inches=extent) + def closeEvent(self, event): self.close() diff --git a/skimage/viewer/widgets/history.py b/skimage/viewer/widgets/history.py index ce87f9ac..e3aaa938 100644 --- a/skimage/viewer/widgets/history.py +++ b/skimage/viewer/widgets/history.py @@ -1,4 +1,3 @@ -import os from textwrap import dedent try: @@ -11,6 +10,7 @@ import numpy as np import skimage from skimage import io from .core import BaseWidget +from ..utils import dialogs __all__ = ['OKCancelButtons', 'SaveButtons'] @@ -84,13 +84,9 @@ class SaveButtons(BaseWidget): notify(msg) def save_to_file(self): - filename = str(QtGui.QFileDialog.getSaveFileName()) - if len(filename) == 0: + filename = dialogs.save_file_dialog() + if filename is None: return - #TODO: io plugins should assign default image formats - basename, ext = os.path.splitext(filename) - if not ext: - filename = '%s.%s' % (filename, self.default_format) image = self.plugin.filtered_image if image.dtype == np.bool: #TODO: This check/conversion should probably be in `imsave`. From 6c1b969b21521ae63ceb6de65b687ecaa469c6f9 Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Wed, 26 Dec 2012 21:39:39 -0800 Subject: [PATCH 053/215] BUG: Fix clean up of canvastools NOTE: The rectangle tool doesn't seem to clean up properly. --- skimage/viewer/canvastools/base.py | 10 ++++++++++ skimage/viewer/plugins/base.py | 12 ++++++------ skimage/viewer/plugins/crop.py | 1 + skimage/viewer/plugins/measure.py | 1 + skimage/viewer/viewers/core.py | 27 --------------------------- 5 files changed, 18 insertions(+), 33 deletions(-) diff --git a/skimage/viewer/canvastools/base.py b/skimage/viewer/canvastools/base.py index 05709c61..6fcda9c4 100644 --- a/skimage/viewer/canvastools/base.py +++ b/skimage/viewer/canvastools/base.py @@ -84,6 +84,16 @@ class CanvasToolBase(object): for artist in self._artists: self.ax.draw_artist(artist) + def remove(self): + """Remove artists and events from axes. + + Note that the naming here mimics the interface of Matplotlib artists. + """ + #TODO: For some reason, RectangleTool doesn't get properly removed + self.disconnect_events() + for a in self._artists: + a.remove() + def redraw(self): """Redraw image and canvas artists. diff --git a/skimage/viewer/plugins/base.py b/skimage/viewer/plugins/base.py index 9f5d25dc..f7f9882c 100644 --- a/skimage/viewer/plugins/base.py +++ b/skimage/viewer/plugins/base.py @@ -44,8 +44,9 @@ class Plugin(QDialog): name : str Name of plugin. This is displayed as the window title. artist : list - List of Matplotlib artists. Any artists created by the plugin should - be added to this list so that it gets cleaned up on close. + List of Matplotlib artists and canvastools. Any artists created by the + plugin should be added to this list so that it gets cleaned up on + close. Examples -------- @@ -190,8 +191,7 @@ class Plugin(QDialog): def closeEvent(self, event): """On close disconnect all artists and events from ImageViewer. - Note that events must be connected using `self.connect_image_event` and - artists must be appended to `self.artists`. + Note that artists must be appended to `self.artists`. """ self.disconnect_image_events() self.remove_image_artists() @@ -222,6 +222,6 @@ class Plugin(QDialog): self.image_viewer.disconnect_event(c) def remove_image_artists(self): - """Disconnect artists that are connected to the image viewer.""" + """Remove artists that are connected to the image viewer.""" for a in self.artists: - self.image_viewer.remove_artist(a) + a.remove() diff --git a/skimage/viewer/plugins/crop.py b/skimage/viewer/plugins/crop.py index 97a43cb5..39e59eb8 100644 --- a/skimage/viewer/plugins/crop.py +++ b/skimage/viewer/plugins/crop.py @@ -21,6 +21,7 @@ class Crop(Plugin): self.rect_tool = RectangleTool(self.image_viewer.ax, maxdist=self.maxdist, on_enter=self.crop) + self.artists.append(self.rect_tool) def help(self): helpstr = ("Crop tool", diff --git a/skimage/viewer/plugins/measure.py b/skimage/viewer/plugins/measure.py index e05f8fe8..4911a1ff 100644 --- a/skimage/viewer/plugins/measure.py +++ b/skimage/viewer/plugins/measure.py @@ -35,6 +35,7 @@ class Measure(Plugin): self.line_tool = LineTool(self.image_viewer.ax, maxdist=self.maxdist, on_move=self.line_changed) + self.artists.append(self.line_tool) def help(self): helpstr = ("Measure tool", diff --git a/skimage/viewer/viewers/core.py b/skimage/viewer/viewers/core.py index 83ef65a3..ef450b3e 100644 --- a/skimage/viewer/viewers/core.py +++ b/skimage/viewer/viewers/core.py @@ -83,14 +83,6 @@ class ImageViewer(QMainWindow): self.image = image.copy() self.plugins = [] - # List of axes artists to check for removal. - self._axes_artists = [self.ax.artists, - self.ax.collections, - self.ax.images, - self.ax.lines, - self.ax.patches, - self.ax.texts] - self.layout = QtGui.QVBoxLayout(self.main_widget) self.layout.addWidget(self.canvas) @@ -196,25 +188,6 @@ class ImageViewer(QMainWindow): """Disconnect callback by its id (returned by `connect_event`).""" self.canvas.mpl_disconnect(callback_id) - def remove_artist(self, artist): - """Disconnect matplotlib artist from image viewer. - - The `closeEvent` method of a Plugin should remove artists (Matplotlib - lines, markers, etc.) from the viewer so that they aren't stranded. - - Parameters - ---------- - artist : Matplotlib Artist - Artists created by Matplotlib functions (e.g., `plot` returns list - of `Line2D` artists) should be saved by the plugin for removal. - """ - # Note: an `add_artist` method is unnecessary since Matplotlib - - # There's probably a smarter way to find where the artist is stored. - for artist_list in self._axes_artists: - if artist in artist_list: - artist_list.remove(artist) - def _update_status_bar(self, event): if event.inaxes and event.inaxes.get_navigate(): self.status_message(self._format_coord(event.xdata, event.ydata)) From 16f159fb3e82c6ac67d071d927636bef62b49889 Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Wed, 26 Dec 2012 21:41:03 -0800 Subject: [PATCH 054/215] ENH: Remove unnecessary clean up functions. Connecting matplotlib events are now the responsibility of canvastools, not the viewer. --- skimage/viewer/plugins/base.py | 27 ++++----------------------- 1 file changed, 4 insertions(+), 23 deletions(-) diff --git a/skimage/viewer/plugins/base.py b/skimage/viewer/plugins/base.py index f7f9882c..5adf986e 100644 --- a/skimage/viewer/plugins/base.py +++ b/skimage/viewer/plugins/base.py @@ -193,33 +193,14 @@ class Plugin(QDialog): Note that artists must be appended to `self.artists`. """ - self.disconnect_image_events() + self.clean_up() + self.close() + + def clean_up(self): self.remove_image_artists() self.image_viewer.plugins.remove(self) self.image_viewer.reset_image() self.image_viewer.redraw() - self.close() - - def connect_image_event(self, event, callback): - """Connect callback with an event in the image viewer. - - This should be used in lieu of `figure.canvas.mpl_connect` since this - function stores call back ids for later clean up. - - Parameters - ---------- - event : str - Matplotlib event. - callback : function - Callback function with a matplotlib Event object as its argument. - """ - cid = self.image_viewer.connect_event(event, callback) - self.cids.append(cid) - - def disconnect_image_events(self): - """Disconnect all events created by this widget.""" - for c in self.cids: - self.image_viewer.disconnect_event(c) def remove_image_artists(self): """Remove artists that are connected to the image viewer.""" From df916713f521a4f1c68394f5a8c4cb0d5d53c424 Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Wed, 26 Dec 2012 22:03:59 -0800 Subject: [PATCH 055/215] ENH: Add PaintTool and LabelPlugin --- skimage/viewer/canvastools/__init__.py | 1 + skimage/viewer/canvastools/painttool.py | 203 ++++++++++++++++++++++++ skimage/viewer/plugins/labelplugin.py | 62 ++++++++ skimage/viewer/widgets/core.py | 9 +- 4 files changed, 274 insertions(+), 1 deletion(-) create mode 100644 skimage/viewer/canvastools/painttool.py create mode 100644 skimage/viewer/plugins/labelplugin.py diff --git a/skimage/viewer/canvastools/__init__.py b/skimage/viewer/canvastools/__init__.py index 5a50b8a5..22a2d205 100644 --- a/skimage/viewer/canvastools/__init__.py +++ b/skimage/viewer/canvastools/__init__.py @@ -1,2 +1,3 @@ from linetool import LineTool, ThickLineTool from recttool import RectangleTool +from painttool import PaintTool diff --git a/skimage/viewer/canvastools/painttool.py b/skimage/viewer/canvastools/painttool.py new file mode 100644 index 00000000..3fbab153 --- /dev/null +++ b/skimage/viewer/canvastools/painttool.py @@ -0,0 +1,203 @@ +import numpy as np + +try: + import matplotlib.pyplot as plt + import matplotlib.colors as mcolors + LABELS_CMAP = mcolors.ListedColormap(['white', 'red', 'dodgerblue', 'gold', + 'greenyellow', 'blueviolet']) +except ImportError: + print("Could not import matplotlib -- skimage.viewer not available.") + +from base import CanvasToolBase + + +__all__ = ['PaintTool'] + + +class PaintTool(CanvasToolBase): + """Widget for painting on top of a plot. + + Parameters + ---------- + ax : :class:`matplotlib.axes.Axes` + Matplotlib axes where tool is displayed. + overlay_shape : shape tuple + 2D shape tuple used to initialize overlay image. + alpha : float (between [0, 1]) + Opacity of overlay + on_move : function + Function called whenever a control handle is moved. + This function must accept the end points of line as the only argument. + on_release : function + Function called whenever the control handle is released. + on_enter : function + Function called whenever the "enter" key is pressed. + rect_props : dict + Properties for :class:`matplotlib.patches.Rectangle`. This class + redefines defaults in :class:`matplotlib.widgets.RectangleSelector`. + + Attributes + ---------- + overlay : array + Overlay of painted labels displayed on top of image. + label : int + Current paint color. + """ + def __init__(self, ax, overlay_shape, radius=5, alpha=0.3, on_move=None, + on_release=None, on_enter=None, rect_props=None): + super(PaintTool, self).__init__(ax, on_move=on_move, on_enter=on_enter, + on_release=on_release) + + props = dict(edgecolor='r', facecolor='0.7', alpha=0.5, animated=True) + props.update(rect_props if rect_props is not None else {}) + + self.alpha = alpha + self.cmap = LABELS_CMAP + self._overlay_plot = None + self._shape = overlay_shape + self.overlay = np.zeros(overlay_shape, dtype='uint8') + + self._cursor = plt.Rectangle((0, 0), 0, 0, **props) + self._cursor.set_visible(False) + self.ax.add_patch(self._cursor) + + # `label` and `radius` can only be set after initializing `_cursor` + self.label = 1 + self.radius = radius + + # Note that the order is important: Redraw cursor *after* overlay + self._artists = [self._overlay_plot, self._cursor] + + self.connect_event('button_press_event', self.on_mouse_press) + self.connect_event('button_release_event', self.on_mouse_release) + self.connect_event('motion_notify_event', self.on_move) + + @property + def label(self): + return self._label + + @label.setter + def label(self, value): + if value >= self.cmap.N: + raise ValueError('Maximum label value = %s' % len(self.cmap - 1)) + self._label = value + self._cursor.set_edgecolor(self.cmap(value)) + + @property + def radius(self): + return self._radius + + @radius.setter + def radius(self, r): + self._radius = r + self._width = 2 * r + 1 + self._cursor.set_width(self._width) + self._cursor.set_height(self._width) + self.window = CenteredWindow(r, self._shape) + + @property + def overlay(self): + return self._overlay + + @overlay.setter + def overlay(self, image): + self._overlay = image + if image is None: + self.ax.images.remove(self._overlay_plot) + self._overlay_plot = None + elif self._overlay_plot is None: + props = dict(cmap=self.cmap, alpha=self.alpha, + norm=mcolors.no_norm(), animated=True) + self._overlay_plot = self.ax.imshow(image, **props) + else: + self._overlay_plot.set_data(image) + self.redraw() + + def _on_key_press(self, event): + if event.key == 'enter': + self.callback_on_enter(self.geometry) + self.redraw() + + def on_mouse_press(self, event): + if event.button != 1 or not self.ax.in_axes(event): + return + self.update_cursor(event.xdata, event.ydata) + self.update_overlay(event.xdata, event.ydata) + + def on_mouse_release(self, event): + if event.button != 1: + return + self.callback_on_release(self.geometry) + + def on_move(self, event): + if not self.ax.in_axes(event): + self._cursor.set_visible(False) + self.redraw() # make sure cursor is not visible + return + self._cursor.set_visible(True) + + self.update_cursor(event.xdata, event.ydata) + if event.button != 1: + self.redraw() # update cursor position + return + self.update_overlay(event.xdata, event.ydata) + self.callback_on_move(self.geometry) + + def update_overlay(self, x, y): + overlay = self.overlay + overlay[self.window.at(y, x)] = self.label + # Note that overlay calls `redraw` + self.overlay = overlay + + def update_cursor(self, x, y): + x = x - self.radius - 1 + y = y - self.radius - 1 + self._cursor.set_xy((x, y)) + + @property + def geometry(self): + return self.overlay + + +class CenteredWindow(object): + """Window that create slices numpy arrays over 2D windows. + + Example + ------- + >>> a = np.arange(16).reshape(4, 4) + >>> w = CenteredWindow(1, a.shape) + >>> a[w.at(1, 1)] + array([[ 0, 1, 2], + [ 4, 5, 6], + [ 8, 9, 10]]) + >>> a[w.at(0, 0)] + array([[0, 1], + [4, 5]]) + >>> a[w.at(4, 3)] + array([[14, 15]]) + """ + def __init__(self, radius, array_shape): + self.radius = radius + self.array_shape = array_shape + + def at(self, row, col): + h, w = self.array_shape + r = self.radius + xmin = max(0, col - r) + xmax = min(w, col + r + 1) + ymin = max(0, row - r) + ymax = min(h, row + r + 1) + return [slice(ymin, ymax), slice(xmin, xmax)] + + +if __name__ == '__main__': + np.testing.rundocs() + import matplotlib.pyplot as plt + from skimage import data + + image = data.camera() + + f, ax = plt.subplots() + ax.imshow(image, interpolation='nearest') + paint_tool = PaintTool(ax, image.shape) + plt.show() diff --git a/skimage/viewer/plugins/labelplugin.py b/skimage/viewer/plugins/labelplugin.py new file mode 100644 index 00000000..8ef02234 --- /dev/null +++ b/skimage/viewer/plugins/labelplugin.py @@ -0,0 +1,62 @@ +import numpy as np + +from .base import Plugin +from ..widgets import ComboBox, Slider +from ..canvastools import PaintTool + + +__all__ = ['LabelPainter'] + + +rad2deg = 180 / np.pi + + +class LabelPainter(Plugin): + name = 'LabelPainter' + + def __init__(self, max_radius=20, **kwargs): + super(LabelPainter, self).__init__(**kwargs) + + # These widgets adjust plugin properties instead of an image filter. + self._radius_widget = Slider('radius', low=1, high=max_radius, + value=5, value_type='int', ptype='plugin') + labels = [str(i) for i in range(6)] + self._label_widget = ComboBox('label', labels, ptype='plugin') + self.add_widget(self._radius_widget) + self.add_widget(self._label_widget) + + print self.help() + + def help(self): + helpstr = ("Label painter", + "Hold left-mouse button and paint on canvas.") + return '\n'.join(helpstr) + + def attach(self, image_viewer): + super(LabelPainter, self).attach(image_viewer) + + image = image_viewer.original_image + self.paint_tool = PaintTool(self.image_viewer.ax, image.shape, + on_enter=self.on_enter) + self.paint_tool.radius = self.radius + self.paint_tool.label = self._label_widget.index = 1 + self.artists.append(self.paint_tool) + + def on_enter(self, overlay): + pass + + @property + def radius(self): + return self._radius_widget.val + + @radius.setter + def radius(self, val): + self.paint_tool.radius = val + + @property + def label(self): + return self._label_widget.val + + @label.setter + def label(self, val): + self.paint_tool.label = val diff --git a/skimage/viewer/widgets/core.py b/skimage/viewer/widgets/core.py index bdbfb71e..14259f1e 100644 --- a/skimage/viewer/widgets/core.py +++ b/skimage/viewer/widgets/core.py @@ -63,7 +63,6 @@ class Text(BaseWidget): self.layout.addWidget(name_label) self.layout.addWidget(self._label) - @property def text(self): return self._label.text() @@ -251,3 +250,11 @@ class ComboBox(BaseWidget): @property def val(self): return self._combo_box.value() + + @property + def index(self): + return self._combo_box.currentIndex() + + @index.setter + def index(self, i): + self._combo_box.setCurrentIndex(i) From d77aa6034edaf6c3f8feb1beebd3b2431c5d3d69 Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Wed, 26 Dec 2012 22:10:27 -0800 Subject: [PATCH 056/215] ENH: Add demo of LabelPlugin --- viewer_examples/plugins/watershed_demo.py | 31 +++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 viewer_examples/plugins/watershed_demo.py diff --git a/viewer_examples/plugins/watershed_demo.py b/viewer_examples/plugins/watershed_demo.py new file mode 100644 index 00000000..7119b7f2 --- /dev/null +++ b/viewer_examples/plugins/watershed_demo.py @@ -0,0 +1,31 @@ +import matplotlib.pyplot as plt + +from skimage import data +from skimage import filter +from skimage import morphology +from skimage.viewer import ImageViewer +from skimage.viewer.plugins.labelplugin import LabelPainter + + +class WatershedPlugin(LabelPainter): + + def help(self): + helpstr = ("Watershed plugin", + "----------------", + "Use mouse to paint each region with a different label.", + "Press enter to display segmented image.") + return '\n'.join(helpstr) + + def on_enter(self, overlay): + viewer = self.image_viewer + edge_image = filter.sobel(viewer.image) + labels = morphology.watershed(edge_image, overlay) + viewer.ax.imshow(labels, cmap=plt.cm.jet, alpha=0.5) + viewer.redraw() + self.close() + + +image = data.coins() +viewer = ImageViewer(image) +viewer += WatershedPlugin() +viewer.show() From 9c4eb3a6fc70ba19855f8f0c1462064abf8971a0 Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Wed, 26 Dec 2012 22:21:24 -0800 Subject: [PATCH 057/215] Use print function for python 3 compatibility --- skimage/viewer/canvastools/recttool.py | 4 ++-- skimage/viewer/plugins/color_histogram.py | 2 +- skimage/viewer/plugins/crop.py | 2 +- skimage/viewer/plugins/labelplugin.py | 2 +- skimage/viewer/plugins/lineprofile.py | 2 +- skimage/viewer/plugins/measure.py | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/skimage/viewer/canvastools/recttool.py b/skimage/viewer/canvastools/recttool.py index 3dcff080..d4ae113e 100644 --- a/skimage/viewer/canvastools/recttool.py +++ b/skimage/viewer/canvastools/recttool.py @@ -62,7 +62,7 @@ class RectangleTool(CanvasToolBase, RectangleSelector): if on_enter is None: def on_enter(extents): - print "(xmin=%.3g, xmax=%.3g, ymin=%.3g, ymax=%.3g)" % extents + print("(xmin=%.3g, xmax=%.3g, ymin=%.3g, ymax=%.3g)" % extents) self.callback_on_enter = on_enter props = dict(mec=props['edgecolor']) @@ -210,5 +210,5 @@ if __name__ == '__main__': rect_tool = RectangleTool(ax) plt.show() - print "Final selection:", + print("Final selection:") rect_tool.callback_on_enter(rect_tool.extents) diff --git a/skimage/viewer/plugins/color_histogram.py b/skimage/viewer/plugins/color_histogram.py index 3c15fbd3..166b2805 100644 --- a/skimage/viewer/plugins/color_histogram.py +++ b/skimage/viewer/plugins/color_histogram.py @@ -13,7 +13,7 @@ class ColorHistogram(PlotPlugin): def __init__(self, **kwargs): super(ColorHistogram, self).__init__(height=400, **kwargs) - print self.help() + print(self.help()) def attach(self, image_viewer): super(ColorHistogram, self).attach(image_viewer) diff --git a/skimage/viewer/plugins/crop.py b/skimage/viewer/plugins/crop.py index 39e59eb8..61f61034 100644 --- a/skimage/viewer/plugins/crop.py +++ b/skimage/viewer/plugins/crop.py @@ -13,7 +13,7 @@ class Crop(Plugin): super(Crop, self).__init__(**kwargs) self.maxdist = maxdist self.add_widget(SaveButtons()) - print self.help() + print(self.help()) def attach(self, image_viewer): super(Crop, self).attach(image_viewer) diff --git a/skimage/viewer/plugins/labelplugin.py b/skimage/viewer/plugins/labelplugin.py index 8ef02234..d224811c 100644 --- a/skimage/viewer/plugins/labelplugin.py +++ b/skimage/viewer/plugins/labelplugin.py @@ -25,7 +25,7 @@ class LabelPainter(Plugin): self.add_widget(self._radius_widget) self.add_widget(self._label_widget) - print self.help() + print(self.help()) def help(self): helpstr = ("Label painter", diff --git a/skimage/viewer/plugins/lineprofile.py b/skimage/viewer/plugins/lineprofile.py index 5946b478..c9ceedc5 100644 --- a/skimage/viewer/plugins/lineprofile.py +++ b/skimage/viewer/plugins/lineprofile.py @@ -41,7 +41,7 @@ class LineProfile(PlotPlugin): maxdist = epsilon self.maxdist = maxdist self._limit_type = limits - print self.help() + print(self.help()) def attach(self, image_viewer): super(LineProfile, self).attach(image_viewer) diff --git a/skimage/viewer/plugins/measure.py b/skimage/viewer/plugins/measure.py index 4911a1ff..71412a3b 100644 --- a/skimage/viewer/plugins/measure.py +++ b/skimage/viewer/plugins/measure.py @@ -25,7 +25,7 @@ class Measure(Plugin): self.add_widget(self._length) self.add_widget(self._angle) - print self.help() + print(self.help()) def attach(self, image_viewer): super(Measure, self).attach(image_viewer) From 103bd96a3bf376f0c00fb0e5a56651a932e22af0 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Fri, 28 Dec 2012 18:24:29 -0600 Subject: [PATCH 058/215] ENH: Adjusted save_to_file to create a proper composite image --- skimage/viewer/viewers/core.py | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/skimage/viewer/viewers/core.py b/skimage/viewer/viewers/core.py index ef450b3e..7db26659 100644 --- a/skimage/viewer/viewers/core.py +++ b/skimage/viewer/viewers/core.py @@ -8,8 +8,9 @@ except ImportError: QMainWindow = object # hack to prevent nosetest and autodoc errors print("Could not import PyQt4 -- skimage.viewer not available.") -from skimage import io +from skimage import io, img_as_float from skimage.util.dtype import dtype_range +import numpy as np from .. import utils from ..widgets import Slider from ..utils import dialogs @@ -119,9 +120,21 @@ class ImageViewer(QMainWindow): filename = dialogs.save_file_dialog() if filename is None: return - extent = self.ax.get_window_extent() - extent = extent.transformed(self.fig.dpi_scale_trans.inverted()) - self.fig.savefig(filename, bbox_inches=extent) + if len(self.ax.images) == 1: + io.imsave(self.image, filename) + else: + im1 = self.ax.images[1] + overlay = im1.cmap(im1.get_array()) + overlay = img_as_float(overlay) + im0 = self.ax.images[0] + underlay = im0.cmap(im0.get_array()) + underlay = img_as_float(underlay) + alpha = overlay[:, :, 3] + alpha = np.dstack((alpha, alpha, alpha)) + alpha /= alpha.max() + composite = (overlay[:, :, :3] * alpha + + underlay[:, :, :3] * (1 - alpha)) + io.imsave(filename, composite) def closeEvent(self, event): self.close() From f517eaa0e7362b52bb8f597b0aea69beef21485a Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Sat, 29 Dec 2012 18:21:49 -0500 Subject: [PATCH 059/215] ENH: Cast arrays for numpy compatibility. The casting rules in newer versions of numpy are stricter, so ensure that we have a compatible data type. --- skimage/viewer/viewers/core.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/skimage/viewer/viewers/core.py b/skimage/viewer/viewers/core.py index 7db26659..8d40fb12 100644 --- a/skimage/viewer/viewers/core.py +++ b/skimage/viewer/viewers/core.py @@ -124,16 +124,16 @@ class ImageViewer(QMainWindow): io.imsave(self.image, filename) else: im1 = self.ax.images[1] - overlay = im1.cmap(im1.get_array()) + overlay = im1.cmap(img_as_float(im1.get_array())) overlay = img_as_float(overlay) im0 = self.ax.images[0] - underlay = im0.cmap(im0.get_array()) + underlay = im0.cmap(img_as_float(im0.get_array())) underlay = img_as_float(underlay) alpha = overlay[:, :, 3] alpha = np.dstack((alpha, alpha, alpha)) alpha /= alpha.max() composite = (overlay[:, :, :3] * alpha + - underlay[:, :, :3] * (1 - alpha)) + underlay[:, :, :3] * (1 - alpha)) io.imsave(filename, composite) def closeEvent(self, event): From bf4709155d3c64d26553eb6614e496083af2cb22 Mon Sep 17 00:00:00 2001 From: Stefan van der Walt Date: Thu, 17 Jan 2013 11:11:21 +0700 Subject: [PATCH 060/215] PKG: MSVC build fixes by Christoph Gohlke. --- skimage/draw/setup.py | 2 +- skimage/filter/rank/_crank16.pyx | 4 ++-- skimage/filter/rank/_crank8.pyx | 4 ++-- skimage/filter/setup.py | 20 ++++++++++---------- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/skimage/draw/setup.py b/skimage/draw/setup.py index 5b8e237d..1414fca8 100644 --- a/skimage/draw/setup.py +++ b/skimage/draw/setup.py @@ -15,7 +15,7 @@ def configuration(parent_package='', top_path=None): cython(['_draw.pyx'], working_path=base_path) config.add_extension('_draw', sources=['_draw.c'], - include_dirs=[get_numpy_include_dirs(), '../shared']) + include_dirs=[get_numpy_include_dirs(), '../_shared']) return config diff --git a/skimage/filter/rank/_crank16.pyx b/skimage/filter/rank/_crank16.pyx index e81bf81f..b7aca483 100644 --- a/skimage/filter/rank/_crank16.pyx +++ b/skimage/filter/rank/_crank16.pyx @@ -5,7 +5,7 @@ import numpy as np cimport numpy as np -from libc.math cimport log2 +from libc.math cimport log from skimage.filter.rank._core16 cimport _core16 @@ -274,7 +274,7 @@ cdef inline np.uint16_t kernel_entropy(Py_ssize_t * histo, float pop, for i in range(maxbin): p = histo[i] / pop if p > 0: - e -= p * log2(p) + e -= p * log(p) / 0.30102999566398119521373889472449 return < np.uint16_t > e * 1000 else: diff --git a/skimage/filter/rank/_crank8.pyx b/skimage/filter/rank/_crank8.pyx index 8bff9703..d7ece4dd 100644 --- a/skimage/filter/rank/_crank8.pyx +++ b/skimage/filter/rank/_crank8.pyx @@ -5,7 +5,7 @@ import numpy as np cimport numpy as np -from libc.math cimport log2 +from libc.math cimport log from skimage.filter.rank._core8 cimport _core8 @@ -279,7 +279,7 @@ cdef inline np.uint8_t kernel_entropy(Py_ssize_t * histo, float pop, for i in range(256): p = histo[i] / pop if p > 0: - e -= p * log2(p) + e -= p * log(p) / 0.30102999566398119521373889472449 return < np.uint8_t > e * 10 else: diff --git a/skimage/filter/setup.py b/skimage/filter/setup.py index 650a26a6..934d3f2e 100644 --- a/skimage/filter/setup.py +++ b/skimage/filter/setup.py @@ -29,31 +29,31 @@ def configuration(parent_package='', top_path=None): include_dirs=[get_numpy_include_dirs()]) config.add_extension('_denoise_cy', sources=['_denoise_cy.c'], include_dirs=[get_numpy_include_dirs(), '../_shared']) - config.add_extension('rank/_core8', sources=['rank/_core8.c'], + config.add_extension('rank._core8', sources=['rank/_core8.c'], include_dirs=[get_numpy_include_dirs()]) - config.add_extension('rank/_core16', sources=['rank/_core16.c'], + config.add_extension('rank._core16', sources=['rank/_core16.c'], include_dirs=[get_numpy_include_dirs()]) - config.add_extension('rank/_crank8', sources=['rank/_crank8.c'], + config.add_extension('rank._crank8', sources=['rank/_crank8.c'], include_dirs=[get_numpy_include_dirs()]) config.add_extension( - 'rank/_crank8_percentiles', sources=['rank/_crank8_percentiles.c'], + 'rank._crank8_percentiles', sources=['rank/_crank8_percentiles.c'], include_dirs=[get_numpy_include_dirs()]) - config.add_extension('rank/_crank16', sources=['rank/_crank16.c'], + config.add_extension('rank._crank16', sources=['rank/_crank16.c'], include_dirs=[get_numpy_include_dirs()]) config.add_extension( - 'rank/_crank16_percentiles', sources=['rank/_crank16_percentiles.c'], + 'rank._crank16_percentiles', sources=['rank/_crank16_percentiles.c'], include_dirs=[get_numpy_include_dirs()]) config.add_extension( - 'rank/_crank16_bilateral', sources=['rank/_crank16_bilateral.c'], + 'rank._crank16_bilateral', sources=['rank/_crank16_bilateral.c'], include_dirs=[get_numpy_include_dirs()]) config.add_extension( - 'rank/rank', sources=['rank/rank.c'], + 'rank.rank', sources=['rank/rank.c'], include_dirs=[get_numpy_include_dirs()]) config.add_extension( - 'rank/percentile_rank', sources=['rank/percentile_rank.c'], + 'rank.percentile_rank', sources=['rank/percentile_rank.c'], include_dirs=[get_numpy_include_dirs()]) config.add_extension( - 'rank/bilateral_rank', sources=['rank/bilateral_rank.c'], + 'rank.bilateral_rank', sources=['rank/bilateral_rank.c'], include_dirs=[get_numpy_include_dirs()]) return config From ad23f203da33b69cd20ba9044bb0b9787156afbb Mon Sep 17 00:00:00 2001 From: Matt McCormick Date: Sun, 22 Jul 2012 19:36:33 -0400 Subject: [PATCH 061/215] ENH: Add SimpleITK IO plugin. IO plugin for SimpleITK, http://simpleitk.org/ imread and imsave implemented. Tests based off the PIL tests. --- skimage/io/_plugins/simpleitk_plugin.ini | 3 + skimage/io/_plugins/simpleitk_plugin.py | 21 ++++++ skimage/io/tests/test_simpleitk.py | 93 ++++++++++++++++++++++++ 3 files changed, 117 insertions(+) create mode 100644 skimage/io/_plugins/simpleitk_plugin.ini create mode 100644 skimage/io/_plugins/simpleitk_plugin.py create mode 100644 skimage/io/tests/test_simpleitk.py diff --git a/skimage/io/_plugins/simpleitk_plugin.ini b/skimage/io/_plugins/simpleitk_plugin.ini new file mode 100644 index 00000000..75a6d995 --- /dev/null +++ b/skimage/io/_plugins/simpleitk_plugin.ini @@ -0,0 +1,3 @@ +[simpleitk] +description = Image reading and writing via SimpleITK +provides = imread, imsave diff --git a/skimage/io/_plugins/simpleitk_plugin.py b/skimage/io/_plugins/simpleitk_plugin.py new file mode 100644 index 00000000..90f7cbc6 --- /dev/null +++ b/skimage/io/_plugins/simpleitk_plugin.py @@ -0,0 +1,21 @@ +__all__ = ['imread', 'imsave'] + +try: + import SimpleITK as sitk +except ImportError: + raise ImportError("SimpleITK could not be found. " + "Please try " + " easy_install SimpleITK " + "or refer to " + " http://simpleitk.org/ " + "for further instructions.") + + +def imread(fname): + sitk_img = sitk.ReadImage(fname) + return sitk.GetArrayFromImage(sitk_img) + + +def imsave(fname, arr): + sitk_img = sitk.GetImageFromArray(arr, isVector=True) + sitk.WriteImage(sitk_img, fname) diff --git a/skimage/io/tests/test_simpleitk.py b/skimage/io/tests/test_simpleitk.py new file mode 100644 index 00000000..4bb2cc23 --- /dev/null +++ b/skimage/io/tests/test_simpleitk.py @@ -0,0 +1,93 @@ +import os.path +import numpy as np +from numpy.testing import * +from numpy.testing.decorators import skipif + +from tempfile import NamedTemporaryFile + +from skimage import data_dir +from skimage.io import imread, imsave, use_plugin, reset_plugins + +try: + import SimpleITK as sitk + use_plugin('simpleitk') +except ImportError: + sitk_available = False +else: + sitk_available = True + + +def teardown(): + reset_plugins() + + +def setup_module(self): + """The effect of the `plugin.use` call may be overridden by later imports. + Call `use_plugin` directly before the tests to ensure that sitk is used. + + """ + try: + use_plugin('simpleitk') + except ImportError: + pass + + +@skipif(not sitk_available) +def test_imread_flatten(): + # a color image is flattened + img = imread(os.path.join(data_dir, 'color.png'), flatten=True) + assert img.ndim == 2 + assert img.dtype == np.float64 + img = imread(os.path.join(data_dir, 'camera.png'), flatten=True) + # check that flattening does not occur for an image that is grey already. + assert np.sctype2char(img.dtype) in np.typecodes['AllInteger'] + + +@skipif(not sitk_available) +def test_bilevel(): + expected = np.zeros((10, 10)) + expected[::2] = 255 + + img = imread(os.path.join(data_dir, 'checker_bilevel.png')) + assert_array_equal(img, expected) + + +@skipif(not sitk_available) +def test_imread_uint16(): + expected = np.load(os.path.join(data_dir, 'chessboard_GRAY_U8.npy')) + img = imread(os.path.join(data_dir, 'chessboard_GRAY_U16.tif')) + assert np.issubdtype(img.dtype, np.uint16) + assert_array_almost_equal(img, expected) + + +@skipif(not sitk_available) +def test_imread_uint16_big_endian(): + expected = np.load(os.path.join(data_dir, 'chessboard_GRAY_U8.npy')) + img = imread(os.path.join(data_dir, 'chessboard_GRAY_U16B.tif')) + assert_array_almost_equal(img, expected) + + +class TestSave: + def roundtrip(self, dtype, x): + f = NamedTemporaryFile(suffix='.mha') + fname = f.name + f.close() + imsave(fname, x) + y = imread(fname) + + assert_array_almost_equal(x, y) + + @skipif(not sitk_available) + def test_imsave_roundtrip(self): + for shape in [(10, 10), (10, 10, 3), (10, 10, 4)]: + for dtype in (np.uint8, np.uint16, np.float32, np.float64): + x = np.ones(shape, dtype=dtype) * np.random.random(shape) + + if np.issubdtype(dtype, float): + yield self.roundtrip, dtype, x + else: + x = (x * 255).astype(dtype) + yield self.roundtrip, dtype, x + +if __name__ == "__main__": + run_module_suite() From 230fefa748db13af928e5cf699899efd797d6349 Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Thu, 24 Jan 2013 21:47:42 -0600 Subject: [PATCH 062/215] ENH: Improve usability of watershed_demo. * Replace 0 label with 'Erase' * Use OK button instead of enter key to calculate watershed. --- skimage/viewer/plugins/labelplugin.py | 1 + viewer_examples/plugins/watershed_demo.py | 21 ++++++++++++++++----- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/skimage/viewer/plugins/labelplugin.py b/skimage/viewer/plugins/labelplugin.py index d224811c..b3c289f1 100644 --- a/skimage/viewer/plugins/labelplugin.py +++ b/skimage/viewer/plugins/labelplugin.py @@ -21,6 +21,7 @@ class LabelPainter(Plugin): self._radius_widget = Slider('radius', low=1, high=max_radius, value=5, value_type='int', ptype='plugin') labels = [str(i) for i in range(6)] + labels[0] = 'Erase' self._label_widget = ComboBox('label', labels, ptype='plugin') self.add_widget(self._radius_widget) self.add_widget(self._label_widget) diff --git a/viewer_examples/plugins/watershed_demo.py b/viewer_examples/plugins/watershed_demo.py index 7119b7f2..683e8a30 100644 --- a/viewer_examples/plugins/watershed_demo.py +++ b/viewer_examples/plugins/watershed_demo.py @@ -4,8 +4,17 @@ from skimage import data from skimage import filter from skimage import morphology from skimage.viewer import ImageViewer +from skimage.viewer.widgets import history from skimage.viewer.plugins.labelplugin import LabelPainter +class OKCancelButtons(history.OKCancelButtons): + + def update_original_image(self): + # OKCancelButtons updates the original image with the filtered image + # by default. Override this method to update the overlay. + self.plugin._show_watershed() + self.plugin.close() + class WatershedPlugin(LabelPainter): @@ -13,19 +22,21 @@ class WatershedPlugin(LabelPainter): helpstr = ("Watershed plugin", "----------------", "Use mouse to paint each region with a different label.", - "Press enter to display segmented image.") + "Press OK to display segmented image.") return '\n'.join(helpstr) - def on_enter(self, overlay): + def _show_watershed(self): viewer = self.image_viewer edge_image = filter.sobel(viewer.image) - labels = morphology.watershed(edge_image, overlay) + labels = morphology.watershed(edge_image, self.paint_tool.overlay) viewer.ax.imshow(labels, cmap=plt.cm.jet, alpha=0.5) viewer.redraw() - self.close() image = data.coins() +plugin = WatershedPlugin() +plugin += OKCancelButtons() + viewer = ImageViewer(image) -viewer += WatershedPlugin() +viewer += plugin viewer.show() From bdb2df89c030a16bf9d59eb7dc3602d935a96d0f Mon Sep 17 00:00:00 2001 From: "Thouis (Ray) Jones" Date: Tue, 12 Feb 2013 14:32:08 -0500 Subject: [PATCH 063/215] use SSE to add/subtract histograms --- skimage/filter/_ctmf.pyx | 35 +++++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/skimage/filter/_ctmf.pyx b/skimage/filter/_ctmf.pyx index c133d8d6..d24a9693 100644 --- a/skimage/filter/_ctmf.pyx +++ b/skimage/filter/_ctmf.pyx @@ -17,6 +17,13 @@ cimport cython from libc.stdlib cimport malloc, free from libc.string cimport memset +cdef extern from "emmintrin.h": + ctypedef long __m128i + __m128i _mm_load_si128 (__m128i *p) + __m128i _mm_adds_epu16 (__m128i a, __m128i b) + __m128i _mm_subs_epu16 (__m128i a, __m128i b) + void _mm_store_si128 (__m128i *p, __m128i a) + np.import_array() ############################################################################## @@ -342,14 +349,30 @@ cdef inline np.int32_t trailing_edge_colidx(Histograms *ph, np.int32_t colidx): # TO_DO - optimize using SIMD instructions # cdef inline void add16(np.uint16_t *dest, np.uint16_t *src): - cdef int i - for i in range(16): - dest[i] += src[i] + cdef __m128i d, s, *pd, *ps + pd = <__m128i *> dest + ps = <__m128i *> src + d = _mm_load_si128(pd) + s = _mm_load_si128(ps) + d = _mm_adds_epu16 (d, s) + _mm_store_si128(pd, d) + d = _mm_load_si128(pd + 1) + s = _mm_load_si128(ps + 1) + d = _mm_adds_epu16 (d, s) + _mm_store_si128(pd + 1, d) cdef inline void sub16(np.uint16_t *dest, np.uint16_t *src): - cdef int i - for i in range(16): - dest[i] -= src[i] + cdef __m128i d, s, *pd, *ps + pd = <__m128i *> dest + ps = <__m128i *> src + d = _mm_load_si128(pd) + s = _mm_load_si128(ps) + d = _mm_subs_epu16 (d, s) + _mm_store_si128(pd, d) + d = _mm_load_si128(pd + 1) + s = _mm_load_si128(ps + 1) + d = _mm_subs_epu16 (d, s) + _mm_store_si128(pd + 1, d) ############################################################################ # From 32c0b1ca979fcfd201a435f13851666c01247941 Mon Sep 17 00:00:00 2001 From: "Thouis (Ray) Jones" Date: Tue, 12 Feb 2013 15:40:43 -0500 Subject: [PATCH 064/215] Rewrite histogram operations to remove GPL license on Perreault's code --- skimage/filter/_ctmf.pyx | 42 ++----------- skimage/filter/_histogram.h | 119 ++++++++++++++++++++++++++++++++++++ skimage/filter/setup.py | 2 +- 3 files changed, 124 insertions(+), 39 deletions(-) create mode 100644 skimage/filter/_histogram.h diff --git a/skimage/filter/_ctmf.pyx b/skimage/filter/_ctmf.pyx index d24a9693..8133efb0 100644 --- a/skimage/filter/_ctmf.pyx +++ b/skimage/filter/_ctmf.pyx @@ -17,12 +17,10 @@ cimport cython from libc.stdlib cimport malloc, free from libc.string cimport memset -cdef extern from "emmintrin.h": - ctypedef long __m128i - __m128i _mm_load_si128 (__m128i *p) - __m128i _mm_adds_epu16 (__m128i a, __m128i b) - __m128i _mm_subs_epu16 (__m128i a, __m128i b) - void _mm_store_si128 (__m128i *p, __m128i a) +cdef extern from "_histogram.h": + ctypedef unsigned short int uint16_t + void add16(uint16_t *dest, uint16_t *src) + void sub16(uint16_t *dest, uint16_t *src) np.import_array() @@ -341,38 +339,6 @@ cdef inline np.int32_t leading_edge_colidx(Histograms *ph, np.int32_t colidx): cdef inline np.int32_t trailing_edge_colidx(Histograms *ph, np.int32_t colidx): return (colidx + 3*ph.radius - 1) % ph.stripe_length -# -# add16 - add 16 consecutive integers -# -# Add an array of 16 16-bit integers to an accumulator of 16 16-bit integers -# -# TO_DO - optimize using SIMD instructions -# -cdef inline void add16(np.uint16_t *dest, np.uint16_t *src): - cdef __m128i d, s, *pd, *ps - pd = <__m128i *> dest - ps = <__m128i *> src - d = _mm_load_si128(pd) - s = _mm_load_si128(ps) - d = _mm_adds_epu16 (d, s) - _mm_store_si128(pd, d) - d = _mm_load_si128(pd + 1) - s = _mm_load_si128(ps + 1) - d = _mm_adds_epu16 (d, s) - _mm_store_si128(pd + 1, d) - -cdef inline void sub16(np.uint16_t *dest, np.uint16_t *src): - cdef __m128i d, s, *pd, *ps - pd = <__m128i *> dest - ps = <__m128i *> src - d = _mm_load_si128(pd) - s = _mm_load_si128(ps) - d = _mm_subs_epu16 (d, s) - _mm_store_si128(pd, d) - d = _mm_load_si128(pd + 1) - s = _mm_load_si128(ps + 1) - d = _mm_subs_epu16 (d, s) - _mm_store_si128(pd + 1, d) ############################################################################ # diff --git a/skimage/filter/_histogram.h b/skimage/filter/_histogram.h new file mode 100644 index 00000000..968b664f --- /dev/null +++ b/skimage/filter/_histogram.h @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2013 Thouis "Ray" Jones + * Modified from code + * Copyright (C) 2006 Simon Perreault + * + * Reference: S. Perreault and P. Hébert, "Median Filtering in Constant Time", + * IEEE Transactions on Image Processing, September 2007. + * + */ + +/* Intrinsic declarations */ +#if defined(__SSE2__) +#include +#elif defined(__MMX__) +#include +#elif defined(__ALTIVEC__) +#include +#endif + +/* Compiler peculiarities */ +#if defined(__GNUC__) +#include +#elif defined(_MSC_VER) +#define inline __inline +#endif + +/** + * Add 16 unsigned 16-bit integers using SSE2, MMX or Altivec, if + * available. + */ +#if defined(__SSE2__) +static inline void add16(uint16_t *dest, uint16_t *src) +{ + __m128i *d, *s; + d = (__m128i *) dest; + s = (__m128i *) src; + *d = _mm_add_epi16(*d, *s); + d++; s++; + *d = _mm_add_epi16(*d, *s); +} +#elif defined(__MMX__) +static inline void add16(uint16_t *dest, uint16_t *src) +{ + __m64 *d, *s; + d = (__m64 *) dest; + s = (__m64 *) src; + *d = _mm_add_pi16(*d, *s); + d++; s++; + *d = _mm_add_pi16(*d, *s); + d++; s++; + *d = _mm_add_pi16(*d, *s); + d++; s++; + *d = _mm_add_pi16(*d, *s); +} +#elif defined(__ALTIVEC__) +static inline void add16(uint16_t *dest, uint16_t *src) +{ + vector unsigned short *d, *s; + d = (vector unsigned short *) dest; + s = (vector unsigned short *) src; + *d = vec_add(*d, *s); + d++; s++; + *d = vec_add(*d, *s); +} +#else +static inline void add16(uint16_t *dest, uint16_t *src) +{ + int i; + + for (i = 0; i < 16; i++) dest[i] += src[i]; +} +#endif + +/** + * Subtract 16 unsigned 16-bit integers using SSE2, MMX or Altivec, if + * available. + */ +#if defined(__SSE2__) +static inline void sub16(uint16_t *dest, uint16_t *src) +{ + __m128i *d, *s; + d = (__m128i *) dest; + s = (__m128i *) src; + *d = _mm_sub_epi16(*d, *s); + d++; s++; + *d = _mm_sub_epi16(*d, *s); +} +#elif defined(__MMX__) +static inline void sub16(uint16_t *dest, uint16_t *src) +{ + __m64 *d, *s; + d = (__m64 *) dest; + s = (__m64 *) src; + *d = _mm_sub_pi16(*d, *s); + d++; s++; + *d = _mm_sub_pi16(*d, *s); + d++; s++; + *d = _mm_sub_pi16(*d, *s); + d++; s++; + *d = _mm_sub_pi16(*d, *s); +} +#elif defined(__ALTIVEC__) +static inline void sub16(uint16_t *dest, uint16_t *src) +{ + vector unsigned short *d, *s; + d = (vector unsigned short *) dest; + s = (vector unsigned short *) src; + *d = vec_sub(*d, *s); + d++; s++; + *d = vec_sub(*d, *s); +} +#else +static inline void sub16(uint16_t *dest, uint16_t *src) +{ + int i; + + for (i = 0; i < 16; i++) dest[i] -= src[i]; +} +#endif diff --git a/skimage/filter/setup.py b/skimage/filter/setup.py index 650a26a6..cfcb0794 100644 --- a/skimage/filter/setup.py +++ b/skimage/filter/setup.py @@ -25,7 +25,7 @@ def configuration(parent_package='', top_path=None): cython(['rank/percentile_rank.pyx'], working_path=base_path) cython(['rank/bilateral_rank.pyx'], working_path=base_path) - config.add_extension('_ctmf', sources=['_ctmf.c'], + config.add_extension('_ctmf', sources=['_ctmf.c', '_histogram.h'], include_dirs=[get_numpy_include_dirs()]) config.add_extension('_denoise_cy', sources=['_denoise_cy.c'], include_dirs=[get_numpy_include_dirs(), '../_shared']) From db764671d43d5e57242842508ff6d6fe31e0cfd1 Mon Sep 17 00:00:00 2001 From: odebeir Date: Wed, 13 Feb 2013 16:20:32 +0100 Subject: [PATCH 065/215] fix: Otsu(8bit) and bilateral functions docstrings --- skimage/filter/rank/bilateral_rank.pyx | 37 +++++++++++++------------- skimage/filter/rank/rank.pyx | 12 +++++---- 2 files changed, 26 insertions(+), 23 deletions(-) diff --git a/skimage/filter/rank/bilateral_rank.pyx b/skimage/filter/rank/bilateral_rank.pyx index dec07c28..04f6cb35 100644 --- a/skimage/filter/rank/bilateral_rank.pyx +++ b/skimage/filter/rank/bilateral_rank.pyx @@ -3,8 +3,8 @@ The local histogram is computed using a sliding window similar to the method described in [1]_. -Input image can be 8-bit or 16-bit with a value < 4096 (i.e. 12 bit), 8-bit -images are casted in 16-bit the number of histogram bins is determined from the +Input image must be 16-bit with a value < 4096 (i.e. 12 bit), +the number of histogram bins is determined from the maximum value present in the image. The pixel neighborhood is defined by: @@ -89,9 +89,8 @@ def bilateral_mean(image, selem, out=None, mask=None, shift_x=False, Parameters ---------- image : ndarray - Image array (uint8 array or uint16). If image is uint16, as the - algorithm uses max. 12bit histogram, an exception will be raised if - image has a value > 4095 + Image array (uint16). As the algorithm uses max. 12bit histogram, + an exception will be raised if image has a value > 4095 selem : ndarray The neighborhood expressed as a 2-D array of 1's and 0's. out : ndarray @@ -108,7 +107,7 @@ def bilateral_mean(image, selem, out=None, mask=None, shift_x=False, Returns ------- - out : uint16 array (uint8 image are casted to uint16) + out : uint16 array The result of the local bilateral mean. See also @@ -118,17 +117,15 @@ def bilateral_mean(image, selem, out=None, mask=None, shift_x=False, Notes ----- - * input image can be 8-bit or 16-bit with a value < 4096 (i.e. 12 bit) - - * 8-bit images are casted in 16-bit + * input image are 16-bit only Examples -------- >>> from skimage import data >>> from skimage.morphology import disk >>> from skimage.filter.rank import bilateral_mean - >>> # Load test image - >>> ima = data.camera() + >>> # Load test image / cast to uint16 + >>> ima = data.camera().astype(np.uint16) >>> # bilateral filtering of cameraman image using a flat kernel >>> bilat_ima = bilateral_mean(ima, disk(20), s0=10,s1=10) """ @@ -146,9 +143,8 @@ def bilateral_pop(image, selem, out=None, mask=None, shift_x=False, Parameters ---------- image : ndarray - Image array (uint8 array or uint16). If image is uint16, as the - algorithm uses max. 12bit histogram, an exception will be raised if - image has a value > 4095 + Image array (uint16). As the algorithm uses max. 12bit histogram, + an exception will be raised if image has a value > 4095 selem : ndarray The neighborhood expressed as a 2-D array of 1's and 0's. out : ndarray @@ -165,20 +161,25 @@ def bilateral_pop(image, selem, out=None, mask=None, shift_x=False, Returns ------- - out : uint16 array (uint8 image are casted to uint16) + out : uint16 array the local number of pixels inside the bilateral neighborhood + Notes + ----- + + * input image are 16-bit only + Examples -------- >>> # Local mean >>> from skimage.morphology import square >>> import skimage.filter.rank as rank - >>> ima8 = 255 * np.array([[0, 0, 0, 0, 0], + >>> ima16 = 255 * np.array([[0, 0, 0, 0, 0], ... [0, 1, 1, 1, 0], ... [0, 1, 1, 1, 0], ... [0, 1, 1, 1, 0], - ... [0, 0, 0, 0, 0]], dtype=np.uint8) - >>> rank.bilateral_pop(ima8, square(3), s0=10,s1=10) + ... [0, 0, 0, 0, 0]], dtype=np.uint16) + >>> rank.bilateral_pop(ima16, square(3), s0=10,s1=10) array([[3, 4, 3, 4, 3], [4, 4, 6, 4, 4], [3, 6, 9, 6, 3], diff --git a/skimage/filter/rank/rank.pyx b/skimage/filter/rank/rank.pyx index 43f03133..261f0bb6 100644 --- a/skimage/filter/rank/rank.pyx +++ b/skimage/filter/rank/rank.pyx @@ -726,9 +726,7 @@ def otsu(image, selem, out=None, mask=None, shift_x=False, shift_y=False): Parameters ---------- image : ndarray - Image array (uint8 array or uint16). If image is uint16, the algorithm - uses max. 12bit histogram, an exception will be raised if image has a - value > 4095. + Image array (uint8 array). selem : ndarray The neighborhood expressed as a 2-D array of 1's and 0's. out : ndarray @@ -743,20 +741,24 @@ def otsu(image, selem, out=None, mask=None, shift_x=False, shift_y=False): Returns ------- - out : uint8 array or uint16 array (same as input image) + out : uint8 array Otsu's threshold values References ---------- .. [otsu] http://en.wikipedia.org/wiki/Otsu's_method + Notes + ----- + * input image are 8-bit only + Examples -------- >>> # Local entropy >>> from skimage import data >>> from skimage.filter.rank import otsu >>> from skimage.morphology import disk - >>> # defining a 8- and a 16-bit test images + >>> # defining a 8-bit test images >>> a8 = data.camera() >>> loc_otsu = otsu(a8, disk(5)) >>> thresh_image = a8 >= loc_otsu From b5b99d8872649e5fddb7d9307985d6e3cbbebab7 Mon Sep 17 00:00:00 2001 From: "Thouis (Ray) Jones" Date: Tue, 19 Feb 2013 16:05:31 -0500 Subject: [PATCH 066/215] fix UTF-8 --- skimage/filter/_histogram.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skimage/filter/_histogram.h b/skimage/filter/_histogram.h index 968b664f..3df3a70d 100644 --- a/skimage/filter/_histogram.h +++ b/skimage/filter/_histogram.h @@ -3,7 +3,7 @@ * Modified from code * Copyright (C) 2006 Simon Perreault * - * Reference: S. Perreault and P. Hébert, "Median Filtering in Constant Time", + * Reference: S. Perreault and P. Hébert, "Median Filtering in Constant Time", * IEEE Transactions on Image Processing, September 2007. * */ From b41bfba22f4c92e8d5910bc73971f43238c89f30 Mon Sep 17 00:00:00 2001 From: "Thouis (Ray) Jones" Date: Tue, 19 Feb 2013 16:41:25 -0500 Subject: [PATCH 067/215] remove typedef, use numpy type in its place --- skimage/filter/_ctmf.pyx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/skimage/filter/_ctmf.pyx b/skimage/filter/_ctmf.pyx index 8133efb0..f5cd5fd0 100644 --- a/skimage/filter/_ctmf.pyx +++ b/skimage/filter/_ctmf.pyx @@ -18,9 +18,8 @@ from libc.stdlib cimport malloc, free from libc.string cimport memset cdef extern from "_histogram.h": - ctypedef unsigned short int uint16_t - void add16(uint16_t *dest, uint16_t *src) - void sub16(uint16_t *dest, uint16_t *src) + void add16(np.uint16_t *dest, np.uint16_t *src) + void sub16(np.uint16_t *dest, np.uint16_t *src) np.import_array() From 4af05245edb0537037b4fda3dc81efa01f1f4977 Mon Sep 17 00:00:00 2001 From: "Thouis (Ray) Jones" Date: Tue, 19 Feb 2013 16:41:54 -0500 Subject: [PATCH 068/215] add casts to suppress compiler warnings --- skimage/filter/_ctmf.pyx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/skimage/filter/_ctmf.pyx b/skimage/filter/_ctmf.pyx index f5cd5fd0..87e4a8b9 100644 --- a/skimage/filter/_ctmf.pyx +++ b/skimage/filter/_ctmf.pyx @@ -327,17 +327,17 @@ cdef void set_stride(Histograms *ph, SCoord *psc): # ############################################################################ cdef inline np.int32_t tl_br_colidx(Histograms *ph, np.int32_t colidx): - return (colidx + 3*ph.radius + ph.current_row) % ph.stripe_length + return (colidx + 3*ph.radius + ph.current_row) % ph.stripe_length cdef inline np.int32_t tr_bl_colidx(Histograms *ph, np.int32_t colidx): - return (colidx + 3*ph.radius + ph.row_count-ph.current_row) % \ + return (colidx + 3*ph.radius + ph.row_count-ph.current_row) % \ ph.stripe_length cdef inline np.int32_t leading_edge_colidx(Histograms *ph, np.int32_t colidx): - return (colidx + 5*ph.radius) % ph.stripe_length + return (colidx + 5*ph.radius) % ph.stripe_length cdef inline np.int32_t trailing_edge_colidx(Histograms *ph, np.int32_t colidx): - return (colidx + 3*ph.radius - 1) % ph.stripe_length + return (colidx + 3*ph.radius - 1) % ph.stripe_length ############################################################################ # @@ -612,7 +612,7 @@ cdef inline np.uint8_t find_median(Histograms *ph): if ph.accumulator_count == 0: return 0 - pixels_below = ((ph.accumulator_count * ph.percent + 50) + pixels_below = ((ph.accumulator_count * ph.percent + 50) / 100) # +50 for roundoff if pixels_below > 0: pixels_below -= 1 @@ -774,8 +774,8 @@ def median_filter( raise ValueError('Data shape (%d, %d) is not output shape (%d, %d)' % (data.shape[0], data.shape[1], output.shape[0], output.shape[1])) - if c_median_filter(data.shape[0], data.shape[1], - data.strides[0], data.strides[1], + if c_median_filter( data.shape[0], data.shape[1], + data.strides[0], data.strides[1], radius, percent, data.data, mask.data, From ba1d27b35761b168ef6eedfcc2c7092a498a6eb2 Mon Sep 17 00:00:00 2001 From: "Thouis (Ray) Jones" Date: Tue, 19 Feb 2013 21:44:06 -0500 Subject: [PATCH 069/215] move _histogram.h to _shared/vectorized_ops.h, and copyright info to CONTRIBUTORS.txt --- CONTRIBUTORS.txt | 3 +++ .../{filter/_histogram.h => _shared/vectorized_ops.h} | 10 ---------- skimage/filter/_ctmf.pyx | 2 +- 3 files changed, 4 insertions(+), 11 deletions(-) rename skimage/{filter/_histogram.h => _shared/vectorized_ops.h} (90%) diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index d582ab63..38e2ea32 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -132,3 +132,6 @@ - François Boulogne Andres Method for circle perimeter, ellipse perimeter drawing. + +- Thouis Jones + Vectorized operators for arrays of 16-bit ints. diff --git a/skimage/filter/_histogram.h b/skimage/_shared/vectorized_ops.h similarity index 90% rename from skimage/filter/_histogram.h rename to skimage/_shared/vectorized_ops.h index 3df3a70d..e426c86e 100644 --- a/skimage/filter/_histogram.h +++ b/skimage/_shared/vectorized_ops.h @@ -1,13 +1,3 @@ -/* - * Copyright (C) 2013 Thouis "Ray" Jones - * Modified from code - * Copyright (C) 2006 Simon Perreault - * - * Reference: S. Perreault and P. Hébert, "Median Filtering in Constant Time", - * IEEE Transactions on Image Processing, September 2007. - * - */ - /* Intrinsic declarations */ #if defined(__SSE2__) #include diff --git a/skimage/filter/_ctmf.pyx b/skimage/filter/_ctmf.pyx index 87e4a8b9..75c1b47a 100644 --- a/skimage/filter/_ctmf.pyx +++ b/skimage/filter/_ctmf.pyx @@ -17,7 +17,7 @@ cimport cython from libc.stdlib cimport malloc, free from libc.string cimport memset -cdef extern from "_histogram.h": +cdef extern from "../_shared/vectorized_ops.h": void add16(np.uint16_t *dest, np.uint16_t *src) void sub16(np.uint16_t *dest, np.uint16_t *src) From 1580c466c17409ee47d0fca1d36865256e3d6eaf Mon Sep 17 00:00:00 2001 From: "Thouis (Ray) Jones" Date: Wed, 20 Feb 2013 10:35:51 -0500 Subject: [PATCH 070/215] pep8 --- skimage/filter/_ctmf.pyx | 108 +++++++++++++++++++-------------------- 1 file changed, 53 insertions(+), 55 deletions(-) diff --git a/skimage/filter/_ctmf.pyx b/skimage/filter/_ctmf.pyx index 75c1b47a..5a81cb98 100644 --- a/skimage/filter/_ctmf.pyx +++ b/skimage/filter/_ctmf.pyx @@ -62,11 +62,11 @@ cdef struct HistogramPiece: np.uint16_t fine[256] cdef struct Histogram: - HistogramPiece top_left # top-left corner - HistogramPiece top_right # top-right corner - HistogramPiece edge # leading/trailing edge - HistogramPiece bottom_left # bottom-left corner - HistogramPiece bottom_right # bottom-right corner + HistogramPiece top_left # top-left corner + HistogramPiece top_right # top-right corner + HistogramPiece edge # leading/trailing edge + HistogramPiece bottom_left # bottom-left corner + HistogramPiece bottom_right # bottom-right corner # The pixel count has the number of pixels histogrammed in # each of the five compartments for this position. This changes @@ -127,16 +127,16 @@ cdef struct Histograms: # # x --> # - SCoord last_top_left # (-) left side of octagon's top - 1 row - SCoord top_left # (+) -1 row from trailing edge top - SCoord last_top_right # (-) right side of octagon's top - 1 col - 1 row - SCoord top_right # (+) -1 col -1 row from leading edge top - SCoord last_leading_edge # (-) leading edge (right) top stride - 1 row - SCoord leading_edge # (+) leading edge bottom stride - SCoord last_bottom_right # (-) leading edge bottom - 1 col - SCoord bottom_right # (+) right side of octagon's bottom - 1 col - SCoord last_bottom_left # (-) trailing edge bottom - 1 col - SCoord bottom_left # (+) left side of octagon's bottom - 1 col + SCoord last_top_left # (-) left side of octagon's top - 1 row + SCoord top_left # (+) -1 row from trailing edge top + SCoord last_top_right # (-) right side of octagon's top - 1 col - 1 row + SCoord top_right # (+) -1 col -1 row from leading edge top + SCoord last_leading_edge # (-) leading edge (right) top stride - 1 row + SCoord leading_edge # (+) leading edge bottom stride + SCoord last_bottom_right # (-) leading edge bottom - 1 col + SCoord bottom_right # (+) right side of octagon's bottom - 1 col + SCoord last_bottom_left # (-) trailing edge bottom - 1 col + SCoord bottom_left # (+) left side of octagon's bottom - 1 col np.int32_t row_stride # stride between one row and the next np.int32_t col_stride # stride between one column and the next @@ -185,25 +185,25 @@ cdef Histograms *allocate_histograms(np.int32_t rows, SCoord *psc memory_size = (adjusted_stripe_length * - (sizeof(Histogram) + sizeof(PixelCount))+ - sizeof(Histograms)+32) + (sizeof(Histogram) + sizeof(PixelCount)) + + sizeof(Histograms) + 32) ptr = malloc(memory_size) memset(ptr, 0, memory_size) - ph = ptr + ph = ptr if not ptr: return ph ph.memory = ptr - ptr = (ph+1) - ph.pixel_count = ptr - ptr = (ph.pixel_count + adjusted_stripe_length) + ptr = (ph + 1) + ph.pixel_count = ptr + ptr = (ph.pixel_count + adjusted_stripe_length) # # Align histogram memory to a 32-byte boundary # - roundoff = ptr + roundoff = ptr roundoff += 31 roundoff -= roundoff % 32 - ptr = roundoff - ph.histogram = ptr + ptr = roundoff + ph.histogram = ptr # # Fill in the statistical things we keep around # @@ -232,7 +232,7 @@ cdef Histograms *allocate_histograms(np.int32_t rows, # a_2 is the offset from the center to each of the octagon # corners # - a = (radius * 2.0 / 2.414213) + a = ( radius * 2.0 / 2.414213) a_2 = a / 2 if a_2 == 0: a_2 = 1 @@ -327,17 +327,18 @@ cdef void set_stride(Histograms *ph, SCoord *psc): # ############################################################################ cdef inline np.int32_t tl_br_colidx(Histograms *ph, np.int32_t colidx): - return (colidx + 3*ph.radius + ph.current_row) % ph.stripe_length + return (colidx + 3 * ph.radius + ph.current_row) % \ + ph.stripe_length cdef inline np.int32_t tr_bl_colidx(Histograms *ph, np.int32_t colidx): - return (colidx + 3*ph.radius + ph.row_count-ph.current_row) % \ - ph.stripe_length + return (colidx + 3 * ph.radius + ph.row_count - ph.current_row) % \ + ph.stripe_length cdef inline np.int32_t leading_edge_colidx(Histograms *ph, np.int32_t colidx): - return (colidx + 5*ph.radius) % ph.stripe_length + return (colidx + 5 * ph.radius) % ph.stripe_length cdef inline np.int32_t trailing_edge_colidx(Histograms *ph, np.int32_t colidx): - return (colidx + 3*ph.radius - 1) % ph.stripe_length + return (colidx + 3 * ph.radius - 1) % ph.stripe_length ############################################################################ # @@ -528,9 +529,9 @@ cdef inline void update_histogram(Histograms *ph, y = last_coord.y + current_row stride = current_stride+last_coord.stride - if (x >= 0 and x < column_count and - y >= 0 and y < row_count and - ph.mask[stride]): + if (x >= 0 and x < column_count and \ + y >= 0 and y < row_count and \ + ph.mask[stride]): value = ph.data[stride] pixel_count[0] -= 1 hist_piece.fine[value] -= 1 @@ -540,9 +541,9 @@ cdef inline void update_histogram(Histograms *ph, y = coord.y + current_row stride = current_stride + coord.stride - if (x >= 0 and x < column_count and - y >= 0 and y < row_count and - ph.mask[stride]): + if (x >= 0 and x < column_count and \ + y >= 0 and y < row_count and \ + ph.mask[stride]): value = ph.data[stride] pixel_count[0] += 1 hist_piece.fine[value] += 1 @@ -613,7 +614,7 @@ cdef inline np.uint8_t find_median(Histograms *ph): if ph.accumulator_count == 0: return 0 pixels_below = ((ph.accumulator_count * ph.percent + 50) - / 100) # +50 for roundoff + / 100) # +50 for roundoff if pixels_below > 0: pixels_below -= 1 @@ -625,7 +626,7 @@ cdef inline np.uint8_t find_median(Histograms *ph): accumulator -= ph.accumulator.coarse[i] update_fine(ph, i) - for j in range(i*16,(i+1)*16): + for j in range(i * 16, (i + 1) * 16): accumulator += ph.accumulator.fine[j] if accumulator > pixels_below: return j @@ -659,9 +660,7 @@ cdef int c_median_filter(np.int32_t rows, cdef: Histograms *ph Histogram *phistogram - int row - int col - int i + int row, col, i np.int32_t top_left_off np.int32_t top_right_off np.int32_t bottom_left_off @@ -670,7 +669,7 @@ cdef int c_median_filter(np.int32_t rows, ph = allocate_histograms(rows, columns, row_stride, col_stride, radius, percent, data, mask, output) if not ph: - return 1 + return 1 for row in range(-radius, rows): # @@ -709,7 +708,7 @@ cdef int c_median_filter(np.int32_t rows, # Update locations and coarse accumulator for the octagon # for points before 0 # - for col in range(-radius, 0 if row >=0 else columns+radius): + for col in range(-radius, 0 if row >= 0 else columns+radius): ph.current_column = col ph.current_stride = row * row_stride + col * col_stride update_current_location(ph) @@ -730,16 +729,15 @@ cdef int c_median_filter(np.int32_t rows, ph.current_stride = row * row_stride + col * col_stride update_current_location(ph) - free_histograms(ph) return 0 -def median_filter( - np.ndarray[dtype=np.uint8_t, ndim=2, negative_indices=False, mode='c'] data, - np.ndarray[dtype=np.uint8_t, ndim=2, negative_indices=False, mode='c'] mask, - np.ndarray[dtype=np.uint8_t, ndim=2, negative_indices=False, mode='c'] output, - int radius, - np.int32_t percent): + +def median_filter(np.ndarray[dtype=np.uint8_t, ndim=2, negative_indices=False, mode='c'] data, + np.ndarray[dtype=np.uint8_t, ndim=2, negative_indices=False, mode='c'] mask, + np.ndarray[dtype=np.uint8_t, ndim=2, negative_indices=False, mode='c'] output, + int radius, + np.int32_t percent): """Median filter with octagon shape and masking. Parameters @@ -761,10 +759,10 @@ def median_filter( """ if percent < 0: - raise ValueError('Median filter percent = %d is less than zero' % \ + raise ValueError('Median filter percent = %d is less than zero' % percent) if percent > 100: - raise ValueError('Median filter percent = %d is greater than 100' % \ + raise ValueError('Median filter percent = %d is greater than 100' % percent) if data.shape[0] != mask.shape[0] or data.shape[1] != mask.shape[1]: raise ValueError('Data shape (%d, %d) is not mask shape (%d, %d)' % @@ -777,7 +775,7 @@ def median_filter( if c_median_filter( data.shape[0], data.shape[1], data.strides[0], data.strides[1], radius, percent, - data.data, - mask.data, - output.data): + data.data, + mask.data, + output.data): raise MemoryError('Failed to allocate scratchpad memory') From d2b98a93e9d578c86d8bd7cd440f0996dbdf3821 Mon Sep 17 00:00:00 2001 From: "Thouis (Ray) Jones" Date: Thu, 21 Feb 2013 11:45:32 -0500 Subject: [PATCH 071/215] remove _histogram.h from setup.py, it's been moved to _shared --- skimage/filter/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skimage/filter/setup.py b/skimage/filter/setup.py index cfcb0794..650a26a6 100644 --- a/skimage/filter/setup.py +++ b/skimage/filter/setup.py @@ -25,7 +25,7 @@ def configuration(parent_package='', top_path=None): cython(['rank/percentile_rank.pyx'], working_path=base_path) cython(['rank/bilateral_rank.pyx'], working_path=base_path) - config.add_extension('_ctmf', sources=['_ctmf.c', '_histogram.h'], + config.add_extension('_ctmf', sources=['_ctmf.c'], include_dirs=[get_numpy_include_dirs()]) config.add_extension('_denoise_cy', sources=['_denoise_cy.c'], include_dirs=[get_numpy_include_dirs(), '../_shared']) From 92f609a9486d14e1bdde85ff7296b755caf1f3b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Tue, 22 Jan 2013 18:33:00 +0100 Subject: [PATCH 072/215] Change type to ssize_t for all index and size variables --- skimage/draw/_draw.pyx | 73 +++++++++++++++++++++--------------------- 1 file changed, 37 insertions(+), 36 deletions(-) diff --git a/skimage/draw/_draw.pyx b/skimage/draw/_draw.pyx index e0c9231e..28103304 100644 --- a/skimage/draw/_draw.pyx +++ b/skimage/draw/_draw.pyx @@ -2,15 +2,15 @@ #cython: boundscheck=False #cython: nonecheck=False #cython: wraparound=False -import numpy as np -import math -from libc.math cimport sqrt -cimport numpy as np cimport cython +cimport numpy as np +from libc.math cimport sqrt +import math +import numpy as np from skimage._shared.geometry cimport point_in_polygon -def line(int y, int x, int y2, int x2): +def line(ssize_t y, ssize_t x, ssize_t y2, ssize_t x2): """Generate line pixel coordinates. Parameters @@ -29,12 +29,12 @@ def line(int y, int x, int y2, int x2): """ - cdef np.ndarray[np.int32_t, ndim=1, mode="c"] rr, cc + cdef np.ndarray[ssize_t, ndim=1, mode="c"] rr, cc - cdef int steep = 0 - cdef int dx = abs(x2 - x) - cdef int dy = abs(y2 - y) - cdef int sx, sy, d, i + cdef char steep = 0 + cdef ssize_t dx = abs(x2 - x) + cdef ssize_t dy = abs(y2 - y) + cdef ssize_t sx, sy, d, i if (x2 - x) > 0: sx = 1 @@ -51,8 +51,8 @@ def line(int y, int x, int y2, int x2): sx, sy = sy, sx d = (2 * dy) - dx - rr = np.zeros(int(dx) + 1, dtype=np.int32) - cc = np.zeros(int(dx) + 1, dtype=np.int32) + rr = np.zeros(int(dx) + 1, dtype=np.intp) + cc = np.zeros(int(dx) + 1, dtype=np.intp) for i in range(dx): if steep: @@ -96,18 +96,18 @@ def polygon(y, x, shape=None): """ - cdef int nr_verts = x.shape[0] - cdef int minr = max(0, y.min()) - cdef int maxr = math.ceil(y.max()) - cdef int minc = max(0, x.min()) - cdef int maxc = math.ceil(x.max()) + cdef ssize_t nr_verts = x.shape[0] + cdef ssize_t minr = max(0, y.min()) + cdef ssize_t maxr = math.ceil(y.max()) + cdef ssize_t minc = max(0, x.min()) + cdef ssize_t maxc = math.ceil(x.max()) # make sure output coordinates do not exceed image size if shape is not None: maxr = min(shape[0] - 1, maxr) maxc = min(shape[1] - 1, maxc) - cdef int r, c + cdef ssize_t r, c #: make contigous arrays for r, c coordinates cdef np.ndarray contiguous_rdata, contiguous_cdata @@ -148,17 +148,17 @@ def ellipse(double cy, double cx, double yradius, double xradius, shape=None): """ - cdef int minr = max(0, cy - yradius) - cdef int maxr = math.ceil(cy + yradius) - cdef int minc = max(0, cx - xradius) - cdef int maxc = math.ceil(cx + xradius) + cdef ssize_t minr = max(0, cy - yradius) + cdef ssize_t maxr = math.ceil(cy + yradius) + cdef ssize_t minc = max(0, cx - xradius) + cdef ssize_t maxc = math.ceil(cx + xradius) # make sure output coordinates do not exceed image size if shape is not None: maxr = min(shape[0] - 1, maxr) maxc = min(shape[1] - 1, maxc) - cdef int r, c + cdef ssize_t r, c #: output coordinate arrays cdef list rr = list() @@ -195,7 +195,8 @@ def circle(double cy, double cx, double radius, shape=None): return ellipse(cy, cx, radius, radius, shape) -def circle_perimeter(int cy, int cx, int radius, method='bresenham'): +def circle_perimeter(ssize_t cy, ssize_t cx, ssize_t radius, + method='bresenham'): """Generate circle perimeter coordinates. Parameters @@ -234,9 +235,9 @@ def circle_perimeter(int cy, int cx, int radius, method='bresenham'): cdef list rr = list() cdef list cc = list() - cdef int x = 0 - cdef int y = radius - cdef int d = 0 + cdef ssize_t x = 0 + cdef ssize_t y = radius + cdef ssize_t d = 0 cdef char cmethod if method == 'bresenham': d = 3 - 2 * radius @@ -302,8 +303,8 @@ def ellipse_perimeter(int cy, int cx, int yradius, int xradius): return np.array(cy), np.array(cx) # a and b are xradius an yradius compute 2a^2 and 2b^2 - cdef int twoasquared = 2 * xradius**2 - cdef int twobsquared = 2 * yradius**2 + cdef ssize_t twoasquared = 2 * xradius**2 + cdef ssize_t twobsquared = 2 * yradius**2 # Pixels cdef list px = list() @@ -311,14 +312,14 @@ def ellipse_perimeter(int cy, int cx, int yradius, int xradius): # First set of points: # start at the top - cdef int x = xradius - cdef int y = 0 + cdef ssize_t x = xradius + cdef ssize_t y = 0 - cdef int err = 0 - cdef int xstop = twobsquared * xradius - cdef int ystop = 0 - cdef int xchange = yradius * yradius * (1 - 2 * xradius) - cdef int ychange = xradius * xradius + cdef ssize_t err = 0 + cdef ssize_t xstop = twobsquared * xradius + cdef ssize_t ystop = 0 + cdef ssize_t xchange = yradius * yradius * (1 - 2 * xradius) + cdef ssize_t ychange = xradius * xradius while xstop > ystop: px.extend([x, -x, -x, x]) From df6852646261c959c12077f7cb7bc10c818df4d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Tue, 22 Jan 2013 18:49:18 +0100 Subject: [PATCH 073/215] Change type to ssize_t for all index and size variables --- skimage/_shared/geometry.pxd | 6 +-- skimage/_shared/geometry.pyx | 14 +++--- skimage/_shared/interpolation.pxd | 20 ++++---- skimage/_shared/interpolation.pyx | 78 +++++++++++++++---------------- skimage/_shared/transform.pxd | 2 +- skimage/_shared/transform.pyx | 2 +- skimage/transform/_warps_cy.pyx | 2 +- 7 files changed, 62 insertions(+), 62 deletions(-) diff --git a/skimage/_shared/geometry.pxd b/skimage/_shared/geometry.pxd index afdc6b5b..a89ce894 100644 --- a/skimage/_shared/geometry.pxd +++ b/skimage/_shared/geometry.pxd @@ -1,6 +1,6 @@ -cdef unsigned char point_in_polygon(int nr_verts, double *xp, double *yp, +cdef unsigned char point_in_polygon(ssize_t nr_verts, double *xp, double *yp, double x, double y) -cdef void points_in_polygon(int nr_verts, double *xp, double *yp, - int nr_points, double *x, double *y, +cdef void points_in_polygon(ssize_t nr_verts, double *xp, double *yp, + ssize_t nr_points, double *x, double *y, unsigned char *result) diff --git a/skimage/_shared/geometry.pyx b/skimage/_shared/geometry.pyx index 3f4850b0..5a56df7c 100644 --- a/skimage/_shared/geometry.pyx +++ b/skimage/_shared/geometry.pyx @@ -4,8 +4,8 @@ #cython: wraparound=False -cdef inline unsigned char point_in_polygon(int nr_verts, double *xp, double *yp, - double x, double y): +cdef inline unsigned char point_in_polygon(ssize_t nr_verts, double *xp, + double *yp, double x, double y): """Test whether point lies inside a polygon. Parameters @@ -17,9 +17,9 @@ cdef inline unsigned char point_in_polygon(int nr_verts, double *xp, double *yp, x, y : double Coordinates of point. """ - cdef int i + cdef ssize_t i cdef unsigned char c = 0 - cdef int j = nr_verts - 1 + cdef ssize_t j = nr_verts - 1 for i in range(nr_verts): if ( (((yp[i] <= y) and (y < yp[j])) or @@ -31,8 +31,8 @@ cdef inline unsigned char point_in_polygon(int nr_verts, double *xp, double *yp, return c -cdef void points_in_polygon(int nr_verts, double *xp, double *yp, - int nr_points, double *x, double *y, +cdef void points_in_polygon(ssize_t nr_verts, double *xp, double *yp, + ssize_t nr_points, double *x, double *y, unsigned char *result): """Test whether points lie inside a polygon. @@ -49,6 +49,6 @@ cdef void points_in_polygon(int nr_verts, double *xp, double *yp, result : unsigned char array Test results for each point. """ - cdef int n + cdef ssize_t n for n in range(nr_points): result[n] = point_in_polygon(nr_verts, xp, yp, x[n], y[n]) diff --git a/skimage/_shared/interpolation.pxd b/skimage/_shared/interpolation.pxd index 4e00dfb0..0f9123e9 100644 --- a/skimage/_shared/interpolation.pxd +++ b/skimage/_shared/interpolation.pxd @@ -1,27 +1,27 @@ -cdef double nearest_neighbour_interpolation(double* image, int rows, - int cols, double r, +cdef double nearest_neighbour_interpolation(double* image, ssize_t rows, + ssize_t cols, double r, double c, char mode, double cval) -cdef double bilinear_interpolation(double* image, int rows, int cols, +cdef double bilinear_interpolation(double* image, ssize_t rows, ssize_t cols, double r, double c, char mode, double cval) cdef double quadratic_interpolation(double x, double[3] f) -cdef double biquadratic_interpolation(double* image, int rows, int cols, +cdef double biquadratic_interpolation(double* image, ssize_t rows, ssize_t cols, double r, double c, char mode, double cval) cdef double cubic_interpolation(double x, double[4] f) -cdef double bicubic_interpolation(double* image, int rows, int cols, +cdef double bicubic_interpolation(double* image, ssize_t rows, ssize_t cols, double r, double c, char mode, double cval) -cdef double get_pixel2d(double* image, int rows, int cols, int r, int c, - char mode, double cval) +cdef double get_pixel2d(double* image, ssize_t rows, ssize_t cols, ssize_t r, + ssize_t c, char mode, double cval) -cdef double get_pixel3d(double* image, int rows, int cols, int dims, int r, - int c, int d, char mode, double cval) +cdef double get_pixel3d(double* image, ssize_t rows, ssize_t cols, ssize_t dims, + ssize_t r, ssize_t c, ssize_t d, char mode, double cval) -cdef int coord_map(int dim, int coord, char mode) +cdef ssize_t coord_map(ssize_t dim, ssize_t coord, char mode) diff --git a/skimage/_shared/interpolation.pyx b/skimage/_shared/interpolation.pyx index b34ba507..50b05a00 100644 --- a/skimage/_shared/interpolation.pyx +++ b/skimage/_shared/interpolation.pyx @@ -5,12 +5,12 @@ from libc.math cimport ceil, floor -cdef inline int round(double r): - return ((r + 0.5) if (r > 0.0) else (r - 0.5)) +cdef inline ssize_t round(double r): + return ((r + 0.5) if (r > 0.0) else (r - 0.5)) -cdef inline double nearest_neighbour_interpolation(double* image, int rows, - int cols, double r, +cdef inline double nearest_neighbour_interpolation(double* image, ssize_t rows, + ssize_t cols, double r, double c, char mode, double cval): """Nearest neighbour interpolation at a given position in the image. @@ -35,13 +35,12 @@ cdef inline double nearest_neighbour_interpolation(double* image, int rows, """ - return get_pixel2d(image, rows, cols, round(r), round(c), - mode, cval) + return get_pixel2d(image, rows, cols, round(r), round(c), mode, cval) -cdef inline double bilinear_interpolation(double* image, int rows, int cols, - double r, double c, char mode, - double cval): +cdef inline double bilinear_interpolation(double* image, ssize_t rows, + ssize_t cols, double r, double c, + char mode, double cval): """Bilinear interpolation at a given position in the image. Parameters @@ -64,12 +63,12 @@ cdef inline double bilinear_interpolation(double* image, int rows, int cols, """ cdef double dr, dc - cdef int minr, minc, maxr, maxc + cdef ssize_t minr, minc, maxr, maxc - minr = floor(r) - minc = floor(c) - maxr = ceil(r) - maxc = ceil(c) + minr = floor(r) + minc = floor(c) + maxr = ceil(r) + maxc = ceil(c) dr = r - minr dc = c - minc top = (1 - dc) * get_pixel2d(image, rows, cols, minr, minc, mode, cval) \ @@ -98,9 +97,9 @@ cdef inline double quadratic_interpolation(double x, double[3] f): return f[1] - 0.25 * (f[0] - f[2]) * x -cdef inline double biquadratic_interpolation(double* image, int rows, int cols, - double r, double c, char mode, - double cval): +cdef inline double biquadratic_interpolation(double* image, ssize_t rows, + ssize_t cols, double r, double c, + char mode, double cval): """Biquadratic interpolation at a given position in the image. Parameters @@ -123,8 +122,8 @@ cdef inline double biquadratic_interpolation(double* image, int rows, int cols, """ - cdef int r0 = round(r) - cdef int c0 = round(c) + cdef ssize_t r0 = round(r) + cdef ssize_t c0 = round(c) if r < 0: r0 -= 1 if c < 0: @@ -139,7 +138,7 @@ cdef inline double biquadratic_interpolation(double* image, int rows, int cols, cdef double fc[3], fr[3] - cdef int pr, pc + cdef ssize_t pr, pc # row-wise cubic interpolation for pr in range(r0, r0 + 3): @@ -174,9 +173,9 @@ cdef inline double cubic_interpolation(double x, double[4] f): (3.0 * (f[1] - f[2]) + f[3] - f[0]))) -cdef inline double bicubic_interpolation(double* image, int rows, int cols, - double r, double c, char mode, - double cval): +cdef inline double bicubic_interpolation(double* image, ssize_t rows, + ssize_t cols, double r, double c, + char mode, double cval): """Bicubic interpolation at a given position in the image. Parameters @@ -199,8 +198,8 @@ cdef inline double bicubic_interpolation(double* image, int rows, int cols, """ - cdef int r0 = r - 1 - cdef int c0 = c - 1 + cdef ssize_t r0 = r - 1 + cdef ssize_t c0 = c - 1 if r < 0: r0 -= 1 if c < 0: @@ -211,7 +210,7 @@ cdef inline double bicubic_interpolation(double* image, int rows, int cols, cdef double fc[4], fr[4] - cdef int pr, pc + cdef ssize_t pr, pc # row-wise cubic interpolation for pr in range(r0, r0 + 4): @@ -223,8 +222,8 @@ cdef inline double bicubic_interpolation(double* image, int rows, int cols, return cubic_interpolation(xr, fr) -cdef inline double get_pixel2d(double* image, int rows, int cols, int r, int c, - char mode, double cval): +cdef inline double get_pixel2d(double* image, ssize_t rows, ssize_t cols, + ssize_t r, ssize_t c, char mode, double cval): """Get a pixel from the image, taking wrapping mode into consideration. Parameters @@ -255,8 +254,9 @@ cdef inline double get_pixel2d(double* image, int rows, int cols, int r, int c, return image[coord_map(rows, r, mode) * cols + coord_map(cols, c, mode)] -cdef inline double get_pixel3d(double* image, int rows, int cols, int dims, int r, - int c, int d, char mode, double cval): +cdef inline double get_pixel3d(double* image, ssize_t rows, ssize_t cols, + ssize_t dims, ssize_t r, ssize_t c, ssize_t d, + char mode, double cval): """Get a pixel from the image, taking wrapping mode into consideration. Parameters @@ -289,7 +289,7 @@ cdef inline double get_pixel3d(double* image, int rows, int cols, int dims, int + d] -cdef inline int coord_map(int dim, int coord, char mode): +cdef inline ssize_t coord_map(ssize_t dim, ssize_t coord, char mode): """ Wrap a coordinate, according to a given mode. @@ -308,20 +308,20 @@ cdef inline int coord_map(int dim, int coord, char mode): if mode == 'R': # reflect if coord < 0: # How many times times does the coordinate wrap? - if (-coord / dim) % 2 != 0: - return dim - (-coord % dim) + if (-coord / dim) % 2 != 0: + return dim - (-coord % dim) else: - return (-coord % dim) + return (-coord % dim) elif coord > dim: - if (coord / dim) % 2 != 0: - return (dim - (coord % dim)) + if (coord / dim) % 2 != 0: + return (dim - (coord % dim)) else: - return (coord % dim) + return (coord % dim) elif mode == 'W': # wrap if coord < 0: - return (dim - (-coord % dim)) + return (dim - (-coord % dim)) elif coord > dim: - return (coord % dim) + return (coord % dim) elif mode == 'N': # nearest if coord < 0: return 0 diff --git a/skimage/_shared/transform.pxd b/skimage/_shared/transform.pxd index 0edc22a4..f91a1658 100644 --- a/skimage/_shared/transform.pxd +++ b/skimage/_shared/transform.pxd @@ -2,4 +2,4 @@ cimport numpy as cnp cdef float integrate(cnp.ndarray[float, ndim=2, mode="c"] sat, - int r0, int c0, int r1, int c1) + ssize_t r0, ssize_t c0, ssize_t r1, ssize_t c1) diff --git a/skimage/_shared/transform.pyx b/skimage/_shared/transform.pyx index ba0efc71..24e3150d 100644 --- a/skimage/_shared/transform.pyx +++ b/skimage/_shared/transform.pyx @@ -6,7 +6,7 @@ cimport numpy as cnp cdef float integrate(cnp.ndarray[float, ndim=2, mode="c"] sat, - int r0, int c0, int r1, int c1): + ssize_t r0, ssize_t c0, ssize_t r1, ssize_t c1): """ Using a summed area table / integral image, calculate the sum over a given window. diff --git a/skimage/transform/_warps_cy.pyx b/skimage/transform/_warps_cy.pyx index ce400ed6..a5a372d2 100644 --- a/skimage/transform/_warps_cy.pyx +++ b/skimage/transform/_warps_cy.pyx @@ -109,7 +109,7 @@ def _warp_fast(np.ndarray image, np.ndarray H, output_shape=None, int order=1, cdef int rows = img.shape[0] cdef int cols = img.shape[1] - cdef double (*interp_func)(double*, int, int, double, double, + cdef double (*interp_func)(double*, ssize_t, ssize_t, double, double, char, double) if order == 0: interp_func = nearest_neighbour_interpolation From 6026ddd9a27be6457a4cf07ece73e5afa1ed587b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Tue, 22 Jan 2013 18:58:48 +0100 Subject: [PATCH 074/215] Change type to ssize_t for all index and size variables --- skimage/feature/_template.pyx | 31 ++++++++++++++++++------------- skimage/feature/_texture.pyx | 12 ++++++------ skimage/feature/corner_cy.pyx | 8 ++++---- 3 files changed, 28 insertions(+), 23 deletions(-) diff --git a/skimage/feature/_template.pyx b/skimage/feature/_template.pyx index 58d48524..938e3a18 100644 --- a/skimage/feature/_template.pyx +++ b/skimage/feature/_template.pyx @@ -42,12 +42,17 @@ from skimage._shared.transform cimport integrate @cython.boundscheck(False) def match_template(np.ndarray[float, ndim=2, mode="c"] image, np.ndarray[float, ndim=2, mode="c"] template): + cdef np.ndarray[float, ndim=2, mode="c"] corr cdef np.ndarray[float, ndim=2, mode="c"] image_sat cdef np.ndarray[float, ndim=2, mode="c"] image_sqr_sat cdef float template_mean = np.mean(template) cdef float template_ssd cdef float inv_area + cdef ssize_t r, c, r_end, c_end + cdef ssize_t template_rows = template.shape[0] + cdef ssize_t template_cols = template.shape[1] + cdef float den, window_sqr_sum, window_mean_sqr, window_sum image_sat = integral.integral_image(image) image_sqr_sat = integral.integral_image(image**2) @@ -63,24 +68,24 @@ def match_template(np.ndarray[float, ndim=2, mode="c"] image, mode="valid"), dtype=np.float32) - cdef int i, j - cdef float den, window_sqr_sum, window_mean_sqr, window_sum, - # move window through convolution results, normalizing in the process - for i in range(corr.shape[0]): - for j in range(corr.shape[1]): - # subtract 1 because `i_end` and `j_end` are used for indexing into - # summed-area table, instead of slicing windows of the image. - i_end = i + template.shape[0] - 1 - j_end = j + template.shape[1] - 1 - window_sum = integrate(image_sat, i, j, i_end, j_end) + # move window through convolution results, normalizing in the process + for r in range(corr.shape[0]): + for c in range(corr.shape[1]): + # subtract 1 because `i_end` and `c_end` are used for indexing into + # summed-area table, instead of slicing windows of the image. + r_end = r + template_rows - 1 + c_end = c + template_cols - 1 + + window_sum = integrate(image_sat, r, c, r_end, c_end) window_mean_sqr = window_sum * window_sum * inv_area - window_sqr_sum = integrate(image_sqr_sat, i, j, i_end, j_end) + window_sqr_sum = integrate(image_sqr_sat, r, c, r_end, c_end) if window_sqr_sum <= window_mean_sqr: - corr[i, j] = 0 + corr[r, c] = 0 continue den = sqrt((window_sqr_sum - window_mean_sqr) * template_ssd) - corr[i, j] /= den + corr[r, c] /= den + return corr diff --git a/skimage/feature/_texture.pyx b/skimage/feature/_texture.pyx index 70a446bb..e2e3bb35 100644 --- a/skimage/feature/_texture.pyx +++ b/skimage/feature/_texture.pyx @@ -39,7 +39,7 @@ def _glcm_loop(np.ndarray[dtype=np.uint8_t, ndim=2, """ cdef: np.int32_t a_inx, d_idx - np.int32_t r, c, rows, cols, row, col + ssize_t r, c, rows, cols, row, col np.int32_t i, j rows = image.shape[0] @@ -52,8 +52,8 @@ def _glcm_loop(np.ndarray[dtype=np.uint8_t, ndim=2, i = image[r, c] # compute the location of the offset pixel - row = r + (sin(angle) * distance + 0.5) - col = c + (cos(angle) * distance + 0.5); + row = r + (sin(angle) * distance + 0.5) + col = c + (cos(angle) * distance + 0.5); # make sure the offset is within bounds if row >= 0 and row < rows and \ @@ -123,11 +123,11 @@ def _local_binary_pattern(np.ndarray[double, ndim=2] image, output_shape = (image.shape[0], image.shape[1]) cdef np.ndarray[double, ndim=2] output = np.zeros(output_shape, np.double) - cdef int rows = image.shape[0] - cdef int cols = image.shape[1] + cdef ssize_t rows = image.shape[0] + cdef ssize_t cols = image.shape[1] cdef double lbp - cdef int r, c, changes, i + cdef ssize_t r, c, changes, i for r in range(image.shape[0]): for c in range(image.shape[1]): for i in range(P): diff --git a/skimage/feature/corner_cy.pyx b/skimage/feature/corner_cy.pyx index 2f001ea4..34913450 100644 --- a/skimage/feature/corner_cy.pyx +++ b/skimage/feature/corner_cy.pyx @@ -10,7 +10,7 @@ from skimage.color import rgb2grey from skimage.util import img_as_float -def corner_moravec(image, int window_size=1): +def corner_moravec(image, ssize_t window_size=1): """Compute Moravec corner measure response image. This is one of the simplest corner detectors and is comparatively fast but @@ -56,8 +56,8 @@ def corner_moravec(image, int window_size=1): [ 0., 0., 0., 0., 0., 0., 0.]]) """ - cdef int rows = image.shape[0] - cdef int cols = image.shape[1] + cdef ssize_t rows = image.shape[0] + cdef ssize_t cols = image.shape[1] cdef cnp.ndarray[dtype=cnp.double_t, ndim=2, mode='c'] cimage, out @@ -71,7 +71,7 @@ def corner_moravec(image, int window_size=1): cdef double* out_data = out.data cdef double msum, min_msum - cdef int r, c, br, bc, mr, mc, a, b + cdef ssize_t r, c, br, bc, mr, mc, a, b for r in range(2 * window_size, rows - 2 * window_size): for c in range(2 * window_size, cols - 2 * window_size): min_msum = DBL_MAX From 96c5f894ecee50f16104fea8ee6ef7b38cd31f12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Tue, 22 Jan 2013 19:14:49 +0100 Subject: [PATCH 075/215] Change type to ssize_t for all index and size variables --- skimage/filter/_ctmf.pyx | 196 ++++++++++++++++----------------- skimage/filter/_denoise_cy.pyx | 32 +++--- 2 files changed, 113 insertions(+), 115 deletions(-) diff --git a/skimage/filter/_ctmf.pyx b/skimage/filter/_ctmf.pyx index 5a81cb98..83269e46 100644 --- a/skimage/filter/_ctmf.pyx +++ b/skimage/filter/_ctmf.pyx @@ -84,9 +84,9 @@ cdef struct PixelCount: # relative offsets from the octagon center # cdef struct SCoord: - np.int32_t stride # add the stride to the memory location - np.int32_t x - np.int32_t y + ssize_t stride # add the stride to the memory location + ssize_t x + ssize_t y cdef struct Histograms: void *memory # pointer to the allocated memory @@ -95,16 +95,16 @@ cdef struct Histograms: np.uint8_t *data # pointer to the image data np.uint8_t *mask # pointer to the image mask np.uint8_t *output # pointer to the output array - np.int32_t column_count # number of columns represented by this + ssize_t column_count # number of columns represented by this # structure - np.int32_t stripe_length # number of columns including "radius" before + ssize_t stripe_length # number of columns including "radius" before # and after - np.int32_t row_count # number of rows available in image - np.int32_t current_column # the column being processed - np.int32_t current_row # the row being processed - np.int32_t current_stride # offset in data and mask to current location - np.int32_t radius # the "radius" of the octagon - np.int32_t a_2 # 1/2 of the length of a side of the octagon + ssize_t row_count # number of rows available in image + ssize_t current_column # the column being processed + ssize_t current_row # the row being processed + ssize_t current_stride # offset in data and mask to current location + ssize_t radius # the "radius" of the octagon + ssize_t a_2 # 1/2 of the length of a side of the octagon # # # The strides are the offsets in the array to the points that need to @@ -138,50 +138,50 @@ cdef struct Histograms: SCoord last_bottom_left # (-) trailing edge bottom - 1 col SCoord bottom_left # (+) left side of octagon's bottom - 1 col - np.int32_t row_stride # stride between one row and the next - np.int32_t col_stride # stride between one column and the next + ssize_t row_stride # stride between one row and the next + ssize_t col_stride # stride between one column and the next # The accumulator holds the running histogram # HistogramPiece accumulator # # The running count of pixels in the accumulator # - np.uint32_t accumulator_count + ssize_t accumulator_count # # The percent of pixels within the octagon whose value is # less than or equal to the median-filtered value (e.g. for # median, this is 50, for lower quartile it's 25) # - np.int32_t percent + ssize_t percent # # last_update_column keeps track of the column # of the last update # to the fine histogram accumulator. Short-term, the median # stays in one coarse block so only one fine histogram might # need to be updated # - np.int32_t last_update_column[16] + ssize_t last_update_column[16] ############################################################################ # # allocate_histograms - allocates the Histograms structure for the run # ############################################################################ -cdef Histograms *allocate_histograms(np.int32_t rows, - np.int32_t columns, - np.int32_t row_stride, - np.int32_t col_stride, - np.int32_t radius, - np.int32_t percent, +cdef Histograms *allocate_histograms(ssize_t rows, + ssize_t columns, + ssize_t row_stride, + ssize_t col_stride, + ssize_t radius, + ssize_t percent, np.uint8_t *data, np.uint8_t *mask, np.uint8_t *output): cdef: - unsigned int adjusted_stripe_length = columns + 2*radius + 1 - unsigned int memory_size + ssize_t adjusted_stripe_length = columns + 2*radius + 1 + ssize_t memory_size void *ptr Histograms *ph size_t roundoff - int a + ssize_t a SCoord *psc memory_size = (adjusted_stripe_length * @@ -199,7 +199,7 @@ cdef Histograms *allocate_histograms(np.int32_t rows, # # Align histogram memory to a 32-byte boundary # - roundoff = ptr + roundoff = ptr roundoff += 31 roundoff -= roundoff % 32 ptr = roundoff @@ -232,7 +232,7 @@ cdef Histograms *allocate_histograms(np.int32_t rows, # a_2 is the offset from the center to each of the octagon # corners # - a = ( radius * 2.0 / 2.414213) + a = (radius * 2.0 / 2.414213) a_2 = a / 2 if a_2 == 0: a_2 = 1 @@ -326,19 +326,18 @@ cdef void set_stride(Histograms *ph, SCoord *psc): # a column that is "radius" to the left. # ############################################################################ -cdef inline np.int32_t tl_br_colidx(Histograms *ph, np.int32_t colidx): - return (colidx + 3 * ph.radius + ph.current_row) % \ - ph.stripe_length +cdef inline ssize_t tl_br_colidx(Histograms *ph, ssize_t colidx): + return (colidx + 3*ph.radius + ph.current_row) % ph.stripe_length -cdef inline np.int32_t tr_bl_colidx(Histograms *ph, np.int32_t colidx): - return (colidx + 3 * ph.radius + ph.row_count - ph.current_row) % \ - ph.stripe_length +cdef inline ssize_t tr_bl_colidx(Histograms *ph, ssize_t colidx): + return (colidx + 3*ph.radius + ph.row_count-ph.current_row) % \ + ph.stripe_length -cdef inline np.int32_t leading_edge_colidx(Histograms *ph, np.int32_t colidx): - return (colidx + 5 * ph.radius) % ph.stripe_length +cdef inline ssize_t leading_edge_colidx(Histograms *ph, ssize_t colidx): + return (colidx + 5*ph.radius) % ph.stripe_length -cdef inline np.int32_t trailing_edge_colidx(Histograms *ph, np.int32_t colidx): - return (colidx + 3 * ph.radius - 1) % ph.stripe_length +cdef inline ssize_t trailing_edge_colidx(Histograms *ph, ssize_t colidx): + return (colidx + 3*ph.radius - 1) % ph.stripe_length ############################################################################ # @@ -349,9 +348,8 @@ cdef inline np.int32_t trailing_edge_colidx(Histograms *ph, np.int32_t colidx): # colidx - the index of the column to add # ############################################################################ -cdef inline void accumulate_coarse_histogram(Histograms *ph, np.int32_t colidx): - cdef: - int offset +cdef inline void accumulate_coarse_histogram(Histograms *ph, ssize_t colidx): + cdef ssize_t offset offset = tr_bl_colidx(ph, colidx) if ph.pixel_count[offset].top_right > 0: @@ -372,9 +370,8 @@ cdef inline void accumulate_coarse_histogram(Histograms *ph, np.int32_t colidx): # for a given column # ############################################################################ -cdef inline void deaccumulate_coarse_histogram(Histograms *ph, np.int32_t colidx): - cdef: - int offset +cdef inline void deaccumulate_coarse_histogram(Histograms *ph, ssize_t colidx): + cdef ssize_t offset # # The trailing diagonals don't appear until here # @@ -403,11 +400,11 @@ cdef inline void deaccumulate_coarse_histogram(Histograms *ph, np.int32_t colidx # ############################################################################ cdef inline void accumulate_fine_histogram(Histograms *ph, - np.int32_t colidx, - np.uint32_t fineidx): + ssize_t colidx, + ssize_t fineidx): cdef: - int fineoffset = fineidx * 16 - int offset + ssize_t fineoffset = fineidx * 16 + ssize_t offset offset = tr_bl_colidx(ph, colidx) add16(ph.accumulator.fine + fineoffset, @@ -427,11 +424,11 @@ cdef inline void accumulate_fine_histogram(Histograms *ph, # ############################################################################ cdef inline void deaccumulate_fine_histogram(Histograms *ph, - np.int32_t colidx, - np.uint32_t fineidx): + ssize_t colidx, + ssize_t fineidx): cdef: - int fineoffset = fineidx * 16 - int offset + ssize_t fineoffset = fineidx * 16 + ssize_t offset # # The trailing diagonals don't appear until here @@ -459,10 +456,7 @@ cdef inline void deaccumulate_fine_histogram(Histograms *ph, ############################################################################ cdef inline void accumulate(Histograms *ph): - cdef: - int i - int j - np.int32_t accumulator + cdef np.int32_t accumulator accumulate_coarse_histogram(ph, ph.current_column) deaccumulate_coarse_histogram(ph, ph.current_column) @@ -486,11 +480,11 @@ cdef inline void accumulate(Histograms *ph): # to choose remains to be done. ############################################################################ -cdef inline void update_fine(Histograms *ph, int fineidx): +cdef inline void update_fine(Histograms *ph, ssize_t fineidx): cdef: - int first_update_column = ph.last_update_column[fineidx]+1 - int update_limit = ph.current_column+1 - int i + ssize_t first_update_column = ph.last_update_column[fineidx]+1 + ssize_t update_limit = ph.current_column+1 + ssize_t i for i in range(first_update_column, update_limit): accumulate_fine_histogram(ph, i, fineidx) @@ -515,15 +509,15 @@ cdef inline void update_histogram(Histograms *ph, SCoord *last_coord, SCoord *coord): cdef: - np.int32_t current_column = ph.current_column - np.int32_t current_row = ph.current_row - np.int32_t current_stride = ph.current_stride - np.int32_t column_count = ph.column_count - np.int32_t row_count = ph.row_count + ssize_t current_column = ph.current_column + ssize_t current_row = ph.current_row + ssize_t current_stride = ph.current_stride + ssize_t column_count = ph.column_count + ssize_t row_count = ph.row_count np.uint8_t value - np.int32_t stride - np.int32_t x - np.int32_t y + ssize_t stride + ssize_t x + ssize_t y x = last_coord.x + current_column y = last_coord.y + current_row @@ -556,21 +550,21 @@ cdef inline void update_histogram(Histograms *ph, ############################################################################ cdef inline void update_current_location(Histograms *ph): cdef: - np.int32_t current_column = ph.current_column - np.int32_t radius = ph.radius - np.int32_t top_left_off = tl_br_colidx(ph, current_column) - np.int32_t top_right_off = tr_bl_colidx(ph, current_column) - np.int32_t bottom_left_off = tr_bl_colidx(ph, current_column) - np.int32_t bottom_right_off = tl_br_colidx(ph, current_column) - np.int32_t leading_edge_off = leading_edge_colidx(ph, current_column) + ssize_t current_column = ph.current_column + ssize_t radius = ph.radius + ssize_t top_left_off = tl_br_colidx(ph, current_column) + ssize_t top_right_off = tr_bl_colidx(ph, current_column) + ssize_t bottom_left_off = tr_bl_colidx(ph, current_column) + ssize_t bottom_right_off = tl_br_colidx(ph, current_column) + ssize_t leading_edge_off = leading_edge_colidx(ph, current_column) np.int32_t *coarse_histogram np.int32_t *fine_histogram - np.int32_t last_xoff - np.int32_t last_yoff - np.int32_t last_stride - np.int32_t xoff - np.int32_t yoff - np.int32_t stride + ssize_t last_xoff + ssize_t last_yoff + ssize_t last_stride + ssize_t xoff + ssize_t yoff + ssize_t stride update_histogram(ph, &ph.histogram[top_left_off].top_left, &ph.pixel_count[top_left_off].top_left, @@ -605,16 +599,18 @@ cdef inline void update_current_location(Histograms *ph): cdef inline np.uint8_t find_median(Histograms *ph): cdef: - np.uint32_t pixels_below # of pixels below the median - int i - int j - int k + ssize_t pixels_below # of pixels below the median + ssize_t i + ssize_t j + ssize_t k np.uint32_t accumulator if ph.accumulator_count == 0: return 0 - pixels_below = ((ph.accumulator_count * ph.percent + 50) - / 100) # +50 for roundoff + + # +50 for roundoff + pixels_below = (ph.accumulator_count * ph.percent + 50) / 100 + if pixels_below > 0: pixels_below -= 1 @@ -626,10 +622,10 @@ cdef inline np.uint8_t find_median(Histograms *ph): accumulator -= ph.accumulator.coarse[i] update_fine(ph, i) - for j in range(i * 16, (i + 1) * 16): + for j in range(i*16, (i + 1)*16): accumulator += ph.accumulator.fine[j] if accumulator > pixels_below: - return j + return j return 0 @@ -648,23 +644,25 @@ cdef inline np.uint8_t find_median(Histograms *ph): # output - array to be filled with filtered pixels # ############################################################################ -cdef int c_median_filter(np.int32_t rows, - np.int32_t columns, - np.int32_t row_stride, - np.int32_t col_stride, - np.int32_t radius, - np.int32_t percent, +cdef int c_median_filter(ssize_t rows, + ssize_t columns, + ssize_t row_stride, + ssize_t col_stride, + ssize_t radius, + ssize_t percent, np.uint8_t *data, np.uint8_t *mask, np.uint8_t *output): cdef: Histograms *ph Histogram *phistogram - int row, col, i - np.int32_t top_left_off - np.int32_t top_right_off - np.int32_t bottom_left_off - np.int32_t bottom_right_off + ssize_t row + ssize_t col + ssize_t i + ssize_t top_left_off + ssize_t top_right_off + ssize_t bottom_left_off + ssize_t bottom_right_off ph = allocate_histograms(rows, columns, row_stride, col_stride, radius, percent, data, mask, output) diff --git a/skimage/filter/_denoise_cy.pyx b/skimage/filter/_denoise_cy.pyx index 8ebf5c73..91c09fbe 100644 --- a/skimage/filter/_denoise_cy.pyx +++ b/skimage/filter/_denoise_cy.pyx @@ -17,7 +17,7 @@ cdef inline double _gaussian_weight(double sigma, double value): return exp(-0.5 * (value / sigma)**2) -cdef double* _compute_color_lut(int bins, double sigma, double max_value): +cdef double* _compute_color_lut(ssize_t bins, double sigma, double max_value): cdef: double* color_lut = malloc(bins * sizeof(double)) @@ -29,7 +29,7 @@ cdef double* _compute_color_lut(int bins, double sigma, double max_value): return color_lut -cdef double* _compute_range_lut(int win_size, double sigma): +cdef double* _compute_range_lut(ssize_t win_size, double sigma): cdef: double* range_lut = malloc(win_size**2 * sizeof(double)) @@ -45,9 +45,9 @@ cdef double* _compute_range_lut(int win_size, double sigma): return range_lut -def denoise_bilateral(image, int win_size=5, sigma_range=None, - double sigma_spatial=1, int bins=10000, mode='constant', - double cval=0): +def denoise_bilateral(image, ssize_t win_size=5, sigma_range=None, + double sigma_spatial=1, ssize_t bins=10000, + mode='constant', double cval=0): """Denoise image using bilateral filter. This is an edge-preserving and noise reducing denoising filter. It averages @@ -100,10 +100,10 @@ def denoise_bilateral(image, int win_size=5, sigma_range=None, image = np.atleast_3d(img_as_float(image)) cdef: - Py_ssize_t rows = image.shape[0] - Py_ssize_t cols = image.shape[1] - Py_ssize_t dims = image.shape[2] - Py_ssize_t window_ext = (win_size - 1) / 2 + ssize_t rows = image.shape[0] + ssize_t cols = image.shape[1] + ssize_t dims = image.shape[2] + ssize_t window_ext = (win_size - 1) / 2 double max_value = image.max() @@ -224,14 +224,14 @@ def denoise_tv_bregman(image, double weight, int max_iter=100, double eps=1e-3): image = np.atleast_3d(img_as_float(image)) cdef: - Py_ssize_t rows = image.shape[0] - Py_ssize_t cols = image.shape[1] - Py_ssize_t dims = image.shape[2] - Py_ssize_t rows2 = rows + 2 - Py_ssize_t cols2 = cols + 2 - Py_ssize_t r, c, k + ssize_t rows = image.shape[0] + ssize_t cols = image.shape[1] + ssize_t dims = image.shape[2] + ssize_t rows2 = rows + 2 + ssize_t cols2 = cols + 2 + ssize_t r, c, k - Py_ssize_t total = rows * cols * dims + ssize_t total = rows * cols * dims shape_ext = (rows2, cols2, dims) From 8eb208616cd8b45ba35d0a3f855c3fea95d49f77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Tue, 22 Jan 2013 19:16:39 +0100 Subject: [PATCH 076/215] Rename Py_ssize_t to ssize_t for consistency within skimage --- skimage/filter/rank/_core16.pxd | 10 +- skimage/filter/rank/_core16.pyx | 54 ++--- skimage/filter/rank/_core8.pxd | 10 +- skimage/filter/rank/_core8.pyx | 50 ++--- skimage/filter/rank/_crank16.pyx | 212 +++++++++---------- skimage/filter/rank/_crank16_bilateral.pyx | 16 +- skimage/filter/rank/_crank16_percentiles.pyx | 84 ++++---- skimage/filter/rank/_crank8.pyx | 140 ++++++------ skimage/filter/rank/_crank8_percentiles.pyx | 48 ++--- 9 files changed, 312 insertions(+), 312 deletions(-) diff --git a/skimage/filter/rank/_core16.pxd b/skimage/filter/rank/_core16.pxd index 5f7de9df..96f34121 100644 --- a/skimage/filter/rank/_core16.pxd +++ b/skimage/filter/rank/_core16.pxd @@ -6,12 +6,12 @@ cdef int int_min(int a, int b) # 16-bit core kernel receives extra information about data bitdepth -cdef void _core16(np.uint16_t kernel(Py_ssize_t *, float, np.uint16_t, - Py_ssize_t, Py_ssize_t, Py_ssize_t, float, - float, Py_ssize_t, Py_ssize_t), +cdef void _core16(np.uint16_t kernel(ssize_t *, float, np.uint16_t, + ssize_t, ssize_t, ssize_t, float, + float, ssize_t, ssize_t), np.ndarray[np.uint16_t, ndim=2] image, np.ndarray[np.uint8_t, ndim=2] selem, np.ndarray[np.uint8_t, ndim=2] mask, np.ndarray[np.uint16_t, ndim=2] out, - char shift_x, char shift_y, Py_ssize_t bitdepth, - float p0, float p1, Py_ssize_t s0, Py_ssize_t s1) except * + char shift_x, char shift_y, ssize_t bitdepth, + float p0, float p1, ssize_t s0, ssize_t s1) except * diff --git a/skimage/filter/rank/_core16.pyx b/skimage/filter/rank/_core16.pyx index aa959fd0..323692ec 100644 --- a/skimage/filter/rank/_core16.pyx +++ b/skimage/filter/rank/_core16.pyx @@ -17,38 +17,38 @@ cdef inline int int_min(int a, int b): return a if a <= b else b -cdef inline void histogram_increment(Py_ssize_t * histo, float * pop, +cdef inline void histogram_increment(ssize_t * histo, float * pop, np.uint16_t value): histo[value] += 1 pop[0] += 1 -cdef inline void histogram_decrement(Py_ssize_t * histo, float * pop, +cdef inline void histogram_decrement(ssize_t * histo, float * pop, np.uint16_t value): histo[value] -= 1 pop[0] -= 1 -cdef void _core16(np.uint16_t kernel(Py_ssize_t *, float, np.uint16_t, - Py_ssize_t, Py_ssize_t, Py_ssize_t, float, - float, Py_ssize_t, Py_ssize_t), +cdef void _core16(np.uint16_t kernel(ssize_t *, float, np.uint16_t, + ssize_t, ssize_t, ssize_t, float, + float, ssize_t, ssize_t), np.ndarray[np.uint16_t, ndim=2] image, np.ndarray[np.uint8_t, ndim=2] selem, np.ndarray[np.uint8_t, ndim=2] mask, np.ndarray[np.uint16_t, ndim=2] out, - char shift_x, char shift_y, Py_ssize_t bitdepth, - float p0, float p1, Py_ssize_t s0, Py_ssize_t s1) except *: + char shift_x, char shift_y, ssize_t bitdepth, + float p0, float p1, ssize_t s0, ssize_t s1) except *: """Compute histogram for each pixel neighborhood, apply kernel function and use kernel function return value for output image. """ - cdef Py_ssize_t rows = image.shape[0] - cdef Py_ssize_t cols = image.shape[1] - cdef Py_ssize_t srows = selem.shape[0] - cdef Py_ssize_t scols = selem.shape[1] + cdef ssize_t rows = image.shape[0] + cdef ssize_t cols = image.shape[1] + cdef ssize_t srows = selem.shape[0] + cdef ssize_t scols = selem.shape[1] - cdef Py_ssize_t centre_r = int(selem.shape[0] / 2) + shift_y - cdef Py_ssize_t centre_c = int(selem.shape[1] / 2) + shift_x + cdef ssize_t centre_r = int(selem.shape[0] / 2) + shift_y + cdef ssize_t centre_c = int(selem.shape[1] / 2) + shift_x # check that structuring element center is inside the element bounding box assert centre_r >= 0 @@ -61,8 +61,8 @@ cdef void _core16(np.uint16_t kernel(Py_ssize_t *, float, np.uint16_t, midbin_list = [0, 0, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048] # set maxbin and midbin - cdef Py_ssize_t maxbin = maxbin_list[bitdepth] - cdef Py_ssize_t midbin = midbin_list[bitdepth] + cdef ssize_t maxbin = maxbin_list[bitdepth] + cdef ssize_t midbin = midbin_list[bitdepth] assert (image < maxbin).all() @@ -72,30 +72,30 @@ cdef void _core16(np.uint16_t kernel(Py_ssize_t *, float, np.uint16_t, cdef np.uint8_t * mask_data = mask.data # define local variable types - cdef Py_ssize_t r, c, rr, cc, s, value, local_max, i, even_row + cdef ssize_t r, c, rr, cc, s, value, local_max, i, even_row # number of pixels actually inside the neighborhood (float) cdef float pop # allocate memory with malloc - cdef Py_ssize_t max_se = srows * scols + cdef ssize_t max_se = srows * scols # number of element in each attack border - cdef Py_ssize_t num_se_n, num_se_s, num_se_e, num_se_w + cdef ssize_t num_se_n, num_se_s, num_se_e, num_se_w # the current local histogram distribution - cdef Py_ssize_t * histo = malloc(maxbin * sizeof(Py_ssize_t)) + cdef ssize_t * histo = malloc(maxbin * sizeof(ssize_t)) # these lists contain the relative pixel row and column for each of the 4 # attack borders east, west, north and south e.g. se_e_r lists the rows of # the east structuring element border - cdef Py_ssize_t * se_e_r = malloc(max_se * sizeof(Py_ssize_t)) - cdef Py_ssize_t * se_e_c = malloc(max_se * sizeof(Py_ssize_t)) - cdef Py_ssize_t * se_w_r = malloc(max_se * sizeof(Py_ssize_t)) - cdef Py_ssize_t * se_w_c = malloc(max_se * sizeof(Py_ssize_t)) - cdef Py_ssize_t * se_n_r = malloc(max_se * sizeof(Py_ssize_t)) - cdef Py_ssize_t * se_n_c = malloc(max_se * sizeof(Py_ssize_t)) - cdef Py_ssize_t * se_s_r = malloc(max_se * sizeof(Py_ssize_t)) - cdef Py_ssize_t * se_s_c = malloc(max_se * sizeof(Py_ssize_t)) + cdef ssize_t * se_e_r = malloc(max_se * sizeof(ssize_t)) + cdef ssize_t * se_e_c = malloc(max_se * sizeof(ssize_t)) + cdef ssize_t * se_w_r = malloc(max_se * sizeof(ssize_t)) + cdef ssize_t * se_w_c = malloc(max_se * sizeof(ssize_t)) + cdef ssize_t * se_n_r = malloc(max_se * sizeof(ssize_t)) + cdef ssize_t * se_n_c = malloc(max_se * sizeof(ssize_t)) + cdef ssize_t * se_s_r = malloc(max_se * sizeof(ssize_t)) + cdef ssize_t * se_s_c = malloc(max_se * sizeof(ssize_t)) # build attack and release borders # by using difference along axis diff --git a/skimage/filter/rank/_core8.pxd b/skimage/filter/rank/_core8.pxd index 38236b87..6ee2e0a3 100644 --- a/skimage/filter/rank/_core8.pxd +++ b/skimage/filter/rank/_core8.pxd @@ -5,18 +5,18 @@ cdef np.uint8_t uint8_max(np.uint8_t a, np.uint8_t b) cdef np.uint8_t uint8_min(np.uint8_t a, np.uint8_t b) -cdef np.uint8_t is_in_mask(Py_ssize_t rows, Py_ssize_t cols, - Py_ssize_t r, Py_ssize_t c, +cdef np.uint8_t is_in_mask(ssize_t rows, ssize_t cols, + ssize_t r, ssize_t c, np.uint8_t * mask) # 8-bit core kernel receives extra information about data inferior and superior # percentiles -cdef void _core8(np.uint8_t kernel(Py_ssize_t *, float, np.uint8_t, float, - float, Py_ssize_t, Py_ssize_t), +cdef void _core8(np.uint8_t kernel(ssize_t *, float, np.uint8_t, float, + float, ssize_t, ssize_t), np.ndarray[np.uint8_t, ndim=2] image, np.ndarray[np.uint8_t, ndim=2] selem, np.ndarray[np.uint8_t, ndim=2] mask, np.ndarray[np.uint8_t, ndim=2] out, char shift_x, char shift_y, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1) except * + ssize_t s0, ssize_t s1) except * diff --git a/skimage/filter/rank/_core8.pyx b/skimage/filter/rank/_core8.pyx index 9f4bc693..7dbd4524 100644 --- a/skimage/filter/rank/_core8.pyx +++ b/skimage/filter/rank/_core8.pyx @@ -16,20 +16,20 @@ cdef inline np.uint8_t uint8_min(np.uint8_t a, np.uint8_t b): return a if a <= b else b -cdef inline void histogram_increment(Py_ssize_t * histo, float * pop, +cdef inline void histogram_increment(ssize_t * histo, float * pop, np.uint8_t value): histo[value] += 1 pop[0] += 1 -cdef inline void histogram_decrement(Py_ssize_t * histo, float * pop, +cdef inline void histogram_decrement(ssize_t * histo, float * pop, np.uint8_t value): histo[value] -= 1 pop[0] -= 1 -cdef inline np.uint8_t is_in_mask(Py_ssize_t rows, Py_ssize_t cols, - Py_ssize_t r, Py_ssize_t c, +cdef inline np.uint8_t is_in_mask(ssize_t rows, ssize_t cols, + ssize_t r, ssize_t c, np.uint8_t * mask): """Check whether given coordinate is within image and mask is true.""" if r < 0 or r > rows - 1 or c < 0 or c > cols - 1: @@ -41,25 +41,25 @@ cdef inline np.uint8_t is_in_mask(Py_ssize_t rows, Py_ssize_t cols, return 0 -cdef void _core8(np.uint8_t kernel(Py_ssize_t *, float, np.uint8_t, float, - float, Py_ssize_t, Py_ssize_t), +cdef void _core8(np.uint8_t kernel(ssize_t *, float, np.uint8_t, float, + float, ssize_t, ssize_t), np.ndarray[np.uint8_t, ndim=2] image, np.ndarray[np.uint8_t, ndim=2] selem, np.ndarray[np.uint8_t, ndim=2] mask, np.ndarray[np.uint8_t, ndim=2] out, char shift_x, char shift_y, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1) except *: + ssize_t s0, ssize_t s1) except *: """Compute histogram for each pixel neighborhood, apply kernel function and use kernel function return value for output image. """ - cdef Py_ssize_t rows = image.shape[0] - cdef Py_ssize_t cols = image.shape[1] - cdef Py_ssize_t srows = selem.shape[0] - cdef Py_ssize_t scols = selem.shape[1] + cdef ssize_t rows = image.shape[0] + cdef ssize_t cols = image.shape[1] + cdef ssize_t srows = selem.shape[0] + cdef ssize_t scols = selem.shape[1] - cdef Py_ssize_t centre_r = int(selem.shape[0] / 2) + shift_y - cdef Py_ssize_t centre_c = int(selem.shape[1] / 2) + shift_x + cdef ssize_t centre_r = int(selem.shape[0] / 2) + shift_y + cdef ssize_t centre_c = int(selem.shape[1] / 2) + shift_x # check that structuring element center is inside the element bounding box assert centre_r >= 0 @@ -74,31 +74,31 @@ cdef void _core8(np.uint8_t kernel(Py_ssize_t *, float, np.uint8_t, float, cdef np.uint8_t * mask_data = mask.data # define local variable types - cdef Py_ssize_t r, c, rr, cc, s, value, local_max, i, even_row + cdef ssize_t r, c, rr, cc, s, value, local_max, i, even_row # number of pixels actually inside the neighborhood (float) cdef float pop # allocate memory with malloc - cdef Py_ssize_t max_se = srows * scols + cdef ssize_t max_se = srows * scols # number of element in each attack border - cdef Py_ssize_t num_se_n, num_se_s, num_se_e, num_se_w + cdef ssize_t num_se_n, num_se_s, num_se_e, num_se_w # the current local histogram distribution - cdef Py_ssize_t * histo = malloc(256 * sizeof(Py_ssize_t)) + cdef ssize_t * histo = malloc(256 * sizeof(ssize_t)) # these lists contain the relative pixel row and column for each of the 4 # attack borders east, west, north and south e.g. se_e_r lists the rows of # the east structuring element border - cdef Py_ssize_t * se_e_r = malloc(max_se * sizeof(Py_ssize_t)) - cdef Py_ssize_t * se_e_c = malloc(max_se * sizeof(Py_ssize_t)) - cdef Py_ssize_t * se_w_r = malloc(max_se * sizeof(Py_ssize_t)) - cdef Py_ssize_t * se_w_c = malloc(max_se * sizeof(Py_ssize_t)) - cdef Py_ssize_t * se_n_r = malloc(max_se * sizeof(Py_ssize_t)) - cdef Py_ssize_t * se_n_c = malloc(max_se * sizeof(Py_ssize_t)) - cdef Py_ssize_t * se_s_r = malloc(max_se * sizeof(Py_ssize_t)) - cdef Py_ssize_t * se_s_c = malloc(max_se * sizeof(Py_ssize_t)) + cdef ssize_t * se_e_r = malloc(max_se * sizeof(ssize_t)) + cdef ssize_t * se_e_c = malloc(max_se * sizeof(ssize_t)) + cdef ssize_t * se_w_r = malloc(max_se * sizeof(ssize_t)) + cdef ssize_t * se_w_c = malloc(max_se * sizeof(ssize_t)) + cdef ssize_t * se_n_r = malloc(max_se * sizeof(ssize_t)) + cdef ssize_t * se_n_c = malloc(max_se * sizeof(ssize_t)) + cdef ssize_t * se_s_r = malloc(max_se * sizeof(ssize_t)) + cdef ssize_t * se_s_c = malloc(max_se * sizeof(ssize_t)) # build attack and release borders # by using difference along axis diff --git a/skimage/filter/rank/_crank16.pyx b/skimage/filter/rank/_crank16.pyx index e81bf81f..b5dda3c2 100644 --- a/skimage/filter/rank/_crank16.pyx +++ b/skimage/filter/rank/_crank16.pyx @@ -14,12 +14,12 @@ from skimage.filter.rank._core16 cimport _core16 # ----------------------------------------------------------------- -cdef inline np.uint16_t kernel_autolevel(Py_ssize_t * histo, float pop, - np.uint16_t g, Py_ssize_t bitdepth, - Py_ssize_t maxbin, Py_ssize_t midbin, +cdef inline np.uint16_t kernel_autolevel(ssize_t * histo, float pop, + np.uint16_t g, ssize_t bitdepth, + ssize_t maxbin, ssize_t midbin, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): - cdef Py_ssize_t i, imin, imax, delta + ssize_t s0, ssize_t s1): + cdef ssize_t i, imin, imax, delta if pop: for i in range(maxbin - 1, -1, -1): @@ -37,12 +37,12 @@ cdef inline np.uint16_t kernel_autolevel(Py_ssize_t * histo, float pop, return < np.uint16_t > (imax - imin) -cdef inline np.uint16_t kernel_bottomhat(Py_ssize_t * histo, float pop, - np.uint16_t g, Py_ssize_t bitdepth, - Py_ssize_t maxbin, Py_ssize_t midbin, +cdef inline np.uint16_t kernel_bottomhat(ssize_t * histo, float pop, + np.uint16_t g, ssize_t bitdepth, + ssize_t maxbin, ssize_t midbin, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): - cdef Py_ssize_t i + ssize_t s0, ssize_t s1): + cdef ssize_t i if pop: for i in range(maxbin): @@ -53,12 +53,12 @@ cdef inline np.uint16_t kernel_bottomhat(Py_ssize_t * histo, float pop, else: return < np.uint16_t > (0) -cdef inline np.uint16_t kernel_equalize(Py_ssize_t * histo, float pop, - np.uint16_t g, Py_ssize_t bitdepth, - Py_ssize_t maxbin, Py_ssize_t midbin, +cdef inline np.uint16_t kernel_equalize(ssize_t * histo, float pop, + np.uint16_t g, ssize_t bitdepth, + ssize_t maxbin, ssize_t midbin, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): - cdef Py_ssize_t i + ssize_t s0, ssize_t s1): + cdef ssize_t i cdef float sum = 0. if pop: @@ -72,12 +72,12 @@ cdef inline np.uint16_t kernel_equalize(Py_ssize_t * histo, float pop, return < np.uint16_t > (0) -cdef inline np.uint16_t kernel_gradient(Py_ssize_t * histo, float pop, - np.uint16_t g, Py_ssize_t bitdepth, - Py_ssize_t maxbin, Py_ssize_t midbin, +cdef inline np.uint16_t kernel_gradient(ssize_t * histo, float pop, + np.uint16_t g, ssize_t bitdepth, + ssize_t maxbin, ssize_t midbin, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): - cdef Py_ssize_t i, imin, imax + ssize_t s0, ssize_t s1): + cdef ssize_t i, imin, imax if pop: for i in range(maxbin - 1, -1, -1): @@ -93,12 +93,12 @@ cdef inline np.uint16_t kernel_gradient(Py_ssize_t * histo, float pop, return < np.uint16_t > (0) -cdef inline np.uint16_t kernel_maximum(Py_ssize_t * histo, float pop, - np.uint16_t g, Py_ssize_t bitdepth, - Py_ssize_t maxbin, Py_ssize_t midbin, +cdef inline np.uint16_t kernel_maximum(ssize_t * histo, float pop, + np.uint16_t g, ssize_t bitdepth, + ssize_t maxbin, ssize_t midbin, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): - cdef Py_ssize_t i + ssize_t s0, ssize_t s1): + cdef ssize_t i if pop: for i in range(maxbin - 1, -1, -1): @@ -108,12 +108,12 @@ cdef inline np.uint16_t kernel_maximum(Py_ssize_t * histo, float pop, return < np.uint16_t > (0) -cdef inline np.uint16_t kernel_mean(Py_ssize_t * histo, float pop, - np.uint16_t g, Py_ssize_t bitdepth, - Py_ssize_t maxbin, Py_ssize_t midbin, +cdef inline np.uint16_t kernel_mean(ssize_t * histo, float pop, + np.uint16_t g, ssize_t bitdepth, + ssize_t maxbin, ssize_t midbin, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): - cdef Py_ssize_t i + ssize_t s0, ssize_t s1): + cdef ssize_t i cdef float mean = 0. if pop: @@ -124,15 +124,15 @@ cdef inline np.uint16_t kernel_mean(Py_ssize_t * histo, float pop, return < np.uint16_t > (0) -cdef inline np.uint16_t kernel_meansubstraction(Py_ssize_t * histo, +cdef inline np.uint16_t kernel_meansubstraction(ssize_t * histo, float pop, np.uint16_t g, - Py_ssize_t bitdepth, - Py_ssize_t maxbin, - Py_ssize_t midbin, + ssize_t bitdepth, + ssize_t maxbin, + ssize_t midbin, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): - cdef Py_ssize_t i + ssize_t s0, ssize_t s1): + cdef ssize_t i cdef float mean = 0. if pop: @@ -143,12 +143,12 @@ cdef inline np.uint16_t kernel_meansubstraction(Py_ssize_t * histo, return < np.uint16_t > (0) -cdef inline np.uint16_t kernel_median(Py_ssize_t * histo, float pop, - np.uint16_t g, Py_ssize_t bitdepth, - Py_ssize_t maxbin, Py_ssize_t midbin, +cdef inline np.uint16_t kernel_median(ssize_t * histo, float pop, + np.uint16_t g, ssize_t bitdepth, + ssize_t maxbin, ssize_t midbin, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): - cdef Py_ssize_t i + ssize_t s0, ssize_t s1): + cdef ssize_t i cdef float sum = pop / 2.0 if pop: @@ -161,12 +161,12 @@ cdef inline np.uint16_t kernel_median(Py_ssize_t * histo, float pop, return < np.uint16_t > (0) -cdef inline np.uint16_t kernel_minimum(Py_ssize_t * histo, float pop, - np.uint16_t g, Py_ssize_t bitdepth, - Py_ssize_t maxbin, Py_ssize_t midbin, +cdef inline np.uint16_t kernel_minimum(ssize_t * histo, float pop, + np.uint16_t g, ssize_t bitdepth, + ssize_t maxbin, ssize_t midbin, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): - cdef Py_ssize_t i + ssize_t s0, ssize_t s1): + cdef ssize_t i if pop: for i in range(maxbin): @@ -176,12 +176,12 @@ cdef inline np.uint16_t kernel_minimum(Py_ssize_t * histo, float pop, return < np.uint16_t > (0) -cdef inline np.uint16_t kernel_modal(Py_ssize_t * histo, float pop, - np.uint16_t g, Py_ssize_t bitdepth, - Py_ssize_t maxbin, Py_ssize_t midbin, +cdef inline np.uint16_t kernel_modal(ssize_t * histo, float pop, + np.uint16_t g, ssize_t bitdepth, + ssize_t maxbin, ssize_t midbin, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): - cdef Py_ssize_t hmax = 0, imax = 0 + ssize_t s0, ssize_t s1): + cdef ssize_t hmax = 0, imax = 0 if pop: for i in range(maxbin): @@ -193,15 +193,15 @@ cdef inline np.uint16_t kernel_modal(Py_ssize_t * histo, float pop, return < np.uint16_t > (0) -cdef inline np.uint16_t kernel_morph_contr_enh(Py_ssize_t * histo, +cdef inline np.uint16_t kernel_morph_contr_enh(ssize_t * histo, float pop, np.uint16_t g, - Py_ssize_t bitdepth, - Py_ssize_t maxbin, - Py_ssize_t midbin, + ssize_t bitdepth, + ssize_t maxbin, + ssize_t midbin, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): - cdef Py_ssize_t i, imin, imax + ssize_t s0, ssize_t s1): + cdef ssize_t i, imin, imax if pop: for i in range(maxbin - 1, -1, -1): @@ -220,20 +220,20 @@ cdef inline np.uint16_t kernel_morph_contr_enh(Py_ssize_t * histo, return < np.uint16_t > (0) -cdef inline np.uint16_t kernel_pop(Py_ssize_t * histo, float pop, - np.uint16_t g, Py_ssize_t bitdepth, - Py_ssize_t maxbin, Py_ssize_t midbin, +cdef inline np.uint16_t kernel_pop(ssize_t * histo, float pop, + np.uint16_t g, ssize_t bitdepth, + ssize_t maxbin, ssize_t midbin, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): + ssize_t s0, ssize_t s1): return < np.uint16_t > (pop) -cdef inline np.uint16_t kernel_threshold(Py_ssize_t * histo, float pop, - np.uint16_t g, Py_ssize_t bitdepth, - Py_ssize_t maxbin, Py_ssize_t midbin, +cdef inline np.uint16_t kernel_threshold(ssize_t * histo, float pop, + np.uint16_t g, ssize_t bitdepth, + ssize_t maxbin, ssize_t midbin, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): - cdef Py_ssize_t i + ssize_t s0, ssize_t s1): + cdef ssize_t i cdef float mean = 0. if pop: @@ -244,12 +244,12 @@ cdef inline np.uint16_t kernel_threshold(Py_ssize_t * histo, float pop, return < np.uint16_t > (0) -cdef inline np.uint16_t kernel_tophat(Py_ssize_t * histo, float pop, - np.uint16_t g, Py_ssize_t bitdepth, - Py_ssize_t maxbin, Py_ssize_t midbin, +cdef inline np.uint16_t kernel_tophat(ssize_t * histo, float pop, + np.uint16_t g, ssize_t bitdepth, + ssize_t maxbin, ssize_t midbin, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): - cdef Py_ssize_t i + ssize_t s0, ssize_t s1): + cdef ssize_t i if pop: for i in range(maxbin - 1, -1, -1): @@ -260,12 +260,12 @@ cdef inline np.uint16_t kernel_tophat(Py_ssize_t * histo, float pop, else: return < np.uint16_t > (0) -cdef inline np.uint16_t kernel_entropy(Py_ssize_t * histo, float pop, - np.uint16_t g, Py_ssize_t bitdepth, - Py_ssize_t maxbin, Py_ssize_t midbin, +cdef inline np.uint16_t kernel_entropy(ssize_t * histo, float pop, + np.uint16_t g, ssize_t bitdepth, + ssize_t maxbin, ssize_t midbin, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): - cdef Py_ssize_t i + ssize_t s0, ssize_t s1): + cdef ssize_t i cdef float e, p if pop: @@ -289,132 +289,132 @@ def autolevel(np.ndarray[np.uint16_t, ndim=2] image, np.ndarray[np.uint8_t, ndim=2] selem, np.ndarray[np.uint8_t, ndim=2] mask=None, np.ndarray[np.uint16_t, ndim=2] out=None, - char shift_x=0, char shift_y=0, Py_ssize_t bitdepth=8): + char shift_x=0, char shift_y=0, ssize_t bitdepth=8): _core16(kernel_autolevel, image, selem, mask, out, shift_x, shift_y, - bitdepth, 0, 0, 0, 0) + bitdepth, 0, 0, 0, 0) def bottomhat(np.ndarray[np.uint16_t, ndim=2] image, np.ndarray[np.uint8_t, ndim=2] selem, np.ndarray[np.uint8_t, ndim=2] mask=None, np.ndarray[np.uint16_t, ndim=2] out=None, - char shift_x=0, char shift_y=0, Py_ssize_t bitdepth=8): + char shift_x=0, char shift_y=0, ssize_t bitdepth=8): _core16(kernel_bottomhat, image, selem, mask, out, shift_x, shift_y, - bitdepth, 0, 0, 0, 0) + bitdepth, 0, 0, 0, 0) def equalize(np.ndarray[np.uint16_t, ndim=2] image, np.ndarray[np.uint8_t, ndim=2] selem, np.ndarray[np.uint8_t, ndim=2] mask=None, np.ndarray[np.uint16_t, ndim=2] out=None, - char shift_x=0, char shift_y=0, Py_ssize_t bitdepth=8): + char shift_x=0, char shift_y=0, ssize_t bitdepth=8): _core16(kernel_equalize, image, selem, mask, out, shift_x, shift_y, - bitdepth, 0, 0, 0, 0) + bitdepth, 0, 0, 0, 0) def gradient(np.ndarray[np.uint16_t, ndim=2] image, np.ndarray[np.uint8_t, ndim=2] selem, np.ndarray[np.uint8_t, ndim=2] mask=None, np.ndarray[np.uint16_t, ndim=2] out=None, - char shift_x=0, char shift_y=0, Py_ssize_t bitdepth=8): + char shift_x=0, char shift_y=0, ssize_t bitdepth=8): _core16(kernel_gradient, image, selem, mask, out, shift_x, shift_y, - bitdepth, 0, 0, 0, 0) + bitdepth, 0, 0, 0, 0) def maximum(np.ndarray[np.uint16_t, ndim=2] image, np.ndarray[np.uint8_t, ndim=2] selem, np.ndarray[np.uint8_t, ndim=2] mask=None, np.ndarray[np.uint16_t, ndim=2] out=None, - char shift_x=0, char shift_y=0, Py_ssize_t bitdepth=8): + char shift_x=0, char shift_y=0, ssize_t bitdepth=8): _core16(kernel_maximum, image, selem, mask, out, shift_x, shift_y, - bitdepth, 0, 0, 0, 0) + bitdepth, 0, 0, 0, 0) def mean(np.ndarray[np.uint16_t, ndim=2] image, np.ndarray[np.uint8_t, ndim=2] selem, np.ndarray[np.uint8_t, ndim=2] mask=None, np.ndarray[np.uint16_t, ndim=2] out=None, - char shift_x=0, char shift_y=0, Py_ssize_t bitdepth=8): + char shift_x=0, char shift_y=0, ssize_t bitdepth=8): _core16(kernel_mean, image, selem, mask, out, shift_x, shift_y, - bitdepth, 0, 0, 0, 0) + bitdepth, 0, 0, 0, 0) def meansubstraction(np.ndarray[np.uint16_t, ndim=2] image, np.ndarray[np.uint8_t, ndim=2] selem, np.ndarray[np.uint8_t, ndim=2] mask=None, np.ndarray[np.uint16_t, ndim=2] out=None, - char shift_x=0, char shift_y=0, Py_ssize_t bitdepth=8): + char shift_x=0, char shift_y=0, ssize_t bitdepth=8): _core16(kernel_meansubstraction, image, selem, mask, out, shift_x, shift_y, - bitdepth, 0, 0, 0, 0) + bitdepth, 0, 0, 0, 0) def median(np.ndarray[np.uint16_t, ndim=2] image, np.ndarray[np.uint8_t, ndim=2] selem, np.ndarray[np.uint8_t, ndim=2] mask=None, np.ndarray[np.uint16_t, ndim=2] out=None, - char shift_x=0, char shift_y=0, Py_ssize_t bitdepth=8): + char shift_x=0, char shift_y=0, ssize_t bitdepth=8): _core16(kernel_median, image, selem, mask, out, shift_x, shift_y, - bitdepth, 0, 0, 0, 0) + bitdepth, 0, 0, 0, 0) def minimum(np.ndarray[np.uint16_t, ndim=2] image, np.ndarray[np.uint8_t, ndim=2] selem, np.ndarray[np.uint8_t, ndim=2] mask=None, np.ndarray[np.uint16_t, ndim=2] out=None, - char shift_x=0, char shift_y=0, Py_ssize_t bitdepth=8): + char shift_x=0, char shift_y=0, ssize_t bitdepth=8): _core16(kernel_minimum, image, selem, mask, out, shift_x, shift_y, - bitdepth, 0, 0, 0, 0) + bitdepth, 0, 0, 0, 0) def morph_contr_enh(np.ndarray[np.uint16_t, ndim=2] image, np.ndarray[np.uint8_t, ndim=2] selem, np.ndarray[np.uint8_t, ndim=2] mask=None, np.ndarray[np.uint16_t, ndim=2] out=None, - char shift_x=0, char shift_y=0, Py_ssize_t bitdepth=8): + char shift_x=0, char shift_y=0, ssize_t bitdepth=8): _core16(kernel_morph_contr_enh, image, selem, mask, out, shift_x, shift_y, - bitdepth, 0, 0, 0, 0) + bitdepth, 0, 0, 0, 0) def modal(np.ndarray[np.uint16_t, ndim=2] image, np.ndarray[np.uint8_t, ndim=2] selem, np.ndarray[np.uint8_t, ndim=2] mask=None, np.ndarray[np.uint16_t, ndim=2] out=None, - char shift_x=0, char shift_y=0, Py_ssize_t bitdepth=8): + char shift_x=0, char shift_y=0, ssize_t bitdepth=8): _core16(kernel_modal, image, selem, mask, out, shift_x, shift_y, - bitdepth, 0, 0, 0, 0) + bitdepth, 0, 0, 0, 0) def pop(np.ndarray[np.uint16_t, ndim=2] image, np.ndarray[np.uint8_t, ndim=2] selem, np.ndarray[np.uint8_t, ndim=2] mask=None, np.ndarray[np.uint16_t, ndim=2] out=None, - char shift_x=0, char shift_y=0, Py_ssize_t bitdepth=8): + char shift_x=0, char shift_y=0, ssize_t bitdepth=8): _core16(kernel_pop, image, selem, mask, out, shift_x, shift_y, - bitdepth, 0, 0, 0, 0) + bitdepth, 0, 0, 0, 0) def threshold(np.ndarray[np.uint16_t, ndim=2] image, np.ndarray[np.uint8_t, ndim=2] selem, np.ndarray[np.uint8_t, ndim=2] mask=None, np.ndarray[np.uint16_t, ndim=2] out=None, - char shift_x=0, char shift_y=0, Py_ssize_t bitdepth=8): + char shift_x=0, char shift_y=0, ssize_t bitdepth=8): _core16(kernel_threshold, image, selem, mask, out, shift_x, shift_y, - bitdepth, 0, 0, 0, 0) + bitdepth, 0, 0, 0, 0) def tophat(np.ndarray[np.uint16_t, ndim=2] image, np.ndarray[np.uint8_t, ndim=2] selem, np.ndarray[np.uint8_t, ndim=2] mask=None, np.ndarray[np.uint16_t, ndim=2] out=None, - char shift_x=0, char shift_y=0, Py_ssize_t bitdepth=8): + char shift_x=0, char shift_y=0, ssize_t bitdepth=8): _core16(kernel_tophat, image, selem, mask, out, shift_x, shift_y, - bitdepth, 0, 0, 0, 0) + bitdepth, 0, 0, 0, 0) def entropy(np.ndarray[np.uint16_t, ndim=2] image, np.ndarray[np.uint8_t, ndim=2] selem, np.ndarray[np.uint8_t, ndim=2] mask=None, np.ndarray[np.uint16_t, ndim=2] out=None, - char shift_x=0, char shift_y=0, Py_ssize_t bitdepth=8): + char shift_x=0, char shift_y=0, ssize_t bitdepth=8): _core16(kernel_entropy, image, selem, mask, out, shift_x, shift_y, - bitdepth, 0, 0, 0, 0) + bitdepth, 0, 0, 0, 0) diff --git a/skimage/filter/rank/_crank16_bilateral.pyx b/skimage/filter/rank/_crank16_bilateral.pyx index c71ccc5c..82236d63 100644 --- a/skimage/filter/rank/_crank16_bilateral.pyx +++ b/skimage/filter/rank/_crank16_bilateral.pyx @@ -13,11 +13,11 @@ from skimage.filter.rank._core16 cimport _core16 # ----------------------------------------------------------------- -cdef inline np.uint16_t kernel_mean(Py_ssize_t * histo, float pop, - np.uint16_t g, Py_ssize_t bitdepth, - Py_ssize_t maxbin, Py_ssize_t midbin, +cdef inline np.uint16_t kernel_mean(ssize_t * histo, float pop, + np.uint16_t g, ssize_t bitdepth, + ssize_t maxbin, ssize_t midbin, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): + ssize_t s0, ssize_t s1): cdef int i, bilat_pop = 0 cdef float mean = 0. @@ -35,11 +35,11 @@ cdef inline np.uint16_t kernel_mean(Py_ssize_t * histo, float pop, return < np.uint16_t > (0) -cdef inline np.uint16_t kernel_pop(Py_ssize_t * histo, float pop, - np.uint16_t g, Py_ssize_t bitdepth, - Py_ssize_t maxbin, Py_ssize_t midbin, +cdef inline np.uint16_t kernel_pop(ssize_t * histo, float pop, + np.uint16_t g, ssize_t bitdepth, + ssize_t maxbin, ssize_t midbin, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): + ssize_t s0, ssize_t s1): cdef int i, bilat_pop = 0 diff --git a/skimage/filter/rank/_crank16_percentiles.pyx b/skimage/filter/rank/_crank16_percentiles.pyx index 220d2386..e8836b5b 100644 --- a/skimage/filter/rank/_crank16_percentiles.pyx +++ b/skimage/filter/rank/_crank16_percentiles.pyx @@ -13,11 +13,11 @@ from skimage.filter.rank._core16 cimport _core16, int_min, int_max # ----------------------------------------------------------------- -cdef inline np.uint16_t kernel_autolevel(Py_ssize_t * histo, float pop, - np.uint16_t g, Py_ssize_t bitdepth, - Py_ssize_t maxbin, Py_ssize_t midbin, +cdef inline np.uint16_t kernel_autolevel(ssize_t * histo, float pop, + np.uint16_t g, ssize_t bitdepth, + ssize_t maxbin, ssize_t midbin, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): + ssize_t s0, ssize_t s1): cdef int i, imin, imax, sum, delta @@ -46,11 +46,11 @@ cdef inline np.uint16_t kernel_autolevel(Py_ssize_t * histo, float pop, return < np.uint16_t > (0) -cdef inline np.uint16_t kernel_gradient(Py_ssize_t * histo, float pop, - np.uint16_t g, Py_ssize_t bitdepth, - Py_ssize_t maxbin, Py_ssize_t midbin, +cdef inline np.uint16_t kernel_gradient(ssize_t * histo, float pop, + np.uint16_t g, ssize_t bitdepth, + ssize_t maxbin, ssize_t midbin, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): + ssize_t s0, ssize_t s1): cdef int i, imin, imax, sum, delta @@ -74,11 +74,11 @@ cdef inline np.uint16_t kernel_gradient(Py_ssize_t * histo, float pop, return < np.uint16_t > (0) -cdef inline np.uint16_t kernel_mean(Py_ssize_t * histo, float pop, - np.uint16_t g, Py_ssize_t bitdepth, - Py_ssize_t maxbin, Py_ssize_t midbin, +cdef inline np.uint16_t kernel_mean(ssize_t * histo, float pop, + np.uint16_t g, ssize_t bitdepth, + ssize_t maxbin, ssize_t midbin, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): + ssize_t s0, ssize_t s1): cdef int i, sum, mean, n @@ -100,14 +100,14 @@ cdef inline np.uint16_t kernel_mean(Py_ssize_t * histo, float pop, return < np.uint16_t > (0) -cdef inline np.uint16_t kernel_mean_substraction(Py_ssize_t * histo, +cdef inline np.uint16_t kernel_mean_substraction(ssize_t * histo, float pop, np.uint16_t g, - Py_ssize_t bitdepth, - Py_ssize_t maxbin, - Py_ssize_t midbin, + ssize_t bitdepth, + ssize_t maxbin, + ssize_t midbin, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): + ssize_t s0, ssize_t s1): cdef int i, sum, mean, n @@ -128,14 +128,14 @@ cdef inline np.uint16_t kernel_mean_substraction(Py_ssize_t * histo, return < np.uint16_t > (0) -cdef inline np.uint16_t kernel_morph_contr_enh(Py_ssize_t * histo, +cdef inline np.uint16_t kernel_morph_contr_enh(ssize_t * histo, float pop, np.uint16_t g, - Py_ssize_t bitdepth, - Py_ssize_t maxbin, - Py_ssize_t midbin, + ssize_t bitdepth, + ssize_t maxbin, + ssize_t midbin, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): + ssize_t s0, ssize_t s1): cdef int i, imin, imax, sum, delta @@ -165,11 +165,11 @@ cdef inline np.uint16_t kernel_morph_contr_enh(Py_ssize_t * histo, return < np.uint16_t > (0) -cdef inline np.uint16_t kernel_percentile(Py_ssize_t * histo, float pop, - np.uint16_t g, Py_ssize_t bitdepth, - Py_ssize_t maxbin, Py_ssize_t midbin, +cdef inline np.uint16_t kernel_percentile(ssize_t * histo, float pop, + np.uint16_t g, ssize_t bitdepth, + ssize_t maxbin, ssize_t midbin, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): + ssize_t s0, ssize_t s1): cdef int i cdef float sum = 0. @@ -185,11 +185,11 @@ cdef inline np.uint16_t kernel_percentile(Py_ssize_t * histo, float pop, return < np.uint16_t > (0) -cdef inline np.uint16_t kernel_pop(Py_ssize_t * histo, float pop, - np.uint16_t g, Py_ssize_t bitdepth, - Py_ssize_t maxbin, Py_ssize_t midbin, +cdef inline np.uint16_t kernel_pop(ssize_t * histo, float pop, + np.uint16_t g, ssize_t bitdepth, + ssize_t maxbin, ssize_t midbin, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): + ssize_t s0, ssize_t s1): cdef int i, sum, n @@ -205,11 +205,11 @@ cdef inline np.uint16_t kernel_pop(Py_ssize_t * histo, float pop, return < np.uint16_t > (0) -cdef inline np.uint16_t kernel_threshold(Py_ssize_t * histo, float pop, - np.uint16_t g, Py_ssize_t bitdepth, - Py_ssize_t maxbin, Py_ssize_t midbin, +cdef inline np.uint16_t kernel_threshold(ssize_t * histo, float pop, + np.uint16_t g, ssize_t bitdepth, + ssize_t maxbin, ssize_t midbin, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): + ssize_t s0, ssize_t s1): cdef int i cdef float sum = 0. @@ -239,7 +239,7 @@ def autolevel(np.ndarray[np.uint16_t, ndim=2] image, """bottom hat """ _core16(kernel_autolevel, image, selem, mask, out, shift_x, shift_y, - bitdepth, p0, p1, 0, 0) + bitdepth, p0, p1, 0, 0) def gradient(np.ndarray[np.uint16_t, ndim=2] image, @@ -251,7 +251,7 @@ def gradient(np.ndarray[np.uint16_t, ndim=2] image, """return p0,p1 percentile gradient """ _core16(kernel_gradient, image, selem, mask, out, shift_x, shift_y, - bitdepth, p0, p1, 0, 0) + bitdepth, p0, p1, 0, 0) def mean(np.ndarray[np.uint16_t, ndim=2] image, @@ -263,7 +263,7 @@ def mean(np.ndarray[np.uint16_t, ndim=2] image, """return mean between [p0 and p1] percentiles """ _core16(kernel_mean, image, selem, mask, out, shift_x, shift_y, - bitdepth, p0, p1, 0, 0) + bitdepth, p0, p1, 0, 0) def mean_substraction(np.ndarray[np.uint16_t, ndim=2] image, @@ -276,7 +276,7 @@ def mean_substraction(np.ndarray[np.uint16_t, ndim=2] image, """ _core16( kernel_mean_substraction, image, selem, mask, out, shift_x, shift_y, - bitdepth, p0, p1, 0, 0) + bitdepth, p0, p1, 0, 0) def morph_contr_enh(np.ndarray[np.uint16_t, ndim=2] image, @@ -288,7 +288,7 @@ def morph_contr_enh(np.ndarray[np.uint16_t, ndim=2] image, """reforce contrast using percentiles """ _core16(kernel_morph_contr_enh, image, selem, mask, out, shift_x, shift_y, - bitdepth, p0, p1, 0, 0) + bitdepth, p0, p1, 0, 0) def percentile(np.ndarray[np.uint16_t, ndim=2] image, @@ -300,7 +300,7 @@ def percentile(np.ndarray[np.uint16_t, ndim=2] image, """return p0 percentile """ _core16(kernel_percentile, image, selem, mask, out, shift_x, shift_y, - bitdepth, p0, p1, 0, 0) + bitdepth, p0, p1, 0, 0) def pop(np.ndarray[np.uint16_t, ndim=2] image, @@ -312,7 +312,7 @@ def pop(np.ndarray[np.uint16_t, ndim=2] image, """return nb of pixels between [p0 and p1] """ _core16(kernel_pop, image, selem, mask, out, shift_x, shift_y, - bitdepth, p0, p1, 0, 0) + bitdepth, p0, p1, 0, 0) def threshold(np.ndarray[np.uint16_t, ndim=2] image, @@ -324,4 +324,4 @@ def threshold(np.ndarray[np.uint16_t, ndim=2] image, """return (maxbin-1) if g > percentile p0 """ _core16(kernel_threshold, image, selem, mask, out, shift_x, shift_y, - bitdepth, p0, p1, 0, 0) + bitdepth, p0, p1, 0, 0) diff --git a/skimage/filter/rank/_crank8.pyx b/skimage/filter/rank/_crank8.pyx index 8bff9703..582bcbb7 100644 --- a/skimage/filter/rank/_crank8.pyx +++ b/skimage/filter/rank/_crank8.pyx @@ -14,11 +14,11 @@ from skimage.filter.rank._core8 cimport _core8 # ----------------------------------------------------------------- -cdef inline np.uint8_t kernel_autolevel(Py_ssize_t * histo, float pop, +cdef inline np.uint8_t kernel_autolevel(ssize_t * histo, float pop, np.uint8_t g, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): + ssize_t s0, ssize_t s1): - cdef Py_ssize_t i, imin, imax, delta + cdef ssize_t i, imin, imax, delta if pop: for i in range(255, -1, -1): @@ -38,11 +38,11 @@ cdef inline np.uint8_t kernel_autolevel(Py_ssize_t * histo, float pop, return < np.uint8_t > (0) -cdef inline np.uint8_t kernel_bottomhat(Py_ssize_t * histo, float pop, +cdef inline np.uint8_t kernel_bottomhat(ssize_t * histo, float pop, np.uint8_t g, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): + ssize_t s0, ssize_t s1): - cdef Py_ssize_t i + cdef ssize_t i if pop: for i in range(256): @@ -54,11 +54,11 @@ cdef inline np.uint8_t kernel_bottomhat(Py_ssize_t * histo, float pop, return < np.uint8_t > (0) -cdef inline np.uint8_t kernel_equalize(Py_ssize_t * histo, float pop, +cdef inline np.uint8_t kernel_equalize(ssize_t * histo, float pop, np.uint8_t g, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): + ssize_t s0, ssize_t s1): - cdef Py_ssize_t i + cdef ssize_t i cdef float sum = 0. if pop: @@ -72,11 +72,11 @@ cdef inline np.uint8_t kernel_equalize(Py_ssize_t * histo, float pop, return < np.uint8_t > (0) -cdef inline np.uint8_t kernel_gradient(Py_ssize_t * histo, float pop, +cdef inline np.uint8_t kernel_gradient(ssize_t * histo, float pop, np.uint8_t g, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): + ssize_t s0, ssize_t s1): - cdef Py_ssize_t i, imin, imax + cdef ssize_t i, imin, imax if pop: for i in range(255, -1, -1): @@ -92,11 +92,11 @@ cdef inline np.uint8_t kernel_gradient(Py_ssize_t * histo, float pop, return < np.uint8_t > (0) -cdef inline np.uint8_t kernel_maximum(Py_ssize_t * histo, float pop, +cdef inline np.uint8_t kernel_maximum(ssize_t * histo, float pop, np.uint8_t g, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): + ssize_t s0, ssize_t s1): - cdef Py_ssize_t i + cdef ssize_t i if pop: for i in range(255, -1, -1): @@ -106,11 +106,11 @@ cdef inline np.uint8_t kernel_maximum(Py_ssize_t * histo, float pop, return < np.uint8_t > (0) -cdef inline np.uint8_t kernel_mean(Py_ssize_t * histo, float pop, +cdef inline np.uint8_t kernel_mean(ssize_t * histo, float pop, np.uint8_t g, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): + ssize_t s0, ssize_t s1): - cdef Py_ssize_t i + cdef ssize_t i cdef float mean = 0. if pop: @@ -121,11 +121,11 @@ cdef inline np.uint8_t kernel_mean(Py_ssize_t * histo, float pop, return < np.uint8_t > (0) -cdef inline np.uint8_t kernel_meansubstraction(Py_ssize_t * histo, float pop, +cdef inline np.uint8_t kernel_meansubstraction(ssize_t * histo, float pop, np.uint8_t g, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): + ssize_t s0, ssize_t s1): - cdef Py_ssize_t i + cdef ssize_t i cdef float mean = 0. if pop: @@ -136,11 +136,11 @@ cdef inline np.uint8_t kernel_meansubstraction(Py_ssize_t * histo, float pop, return < np.uint8_t > (0) -cdef inline np.uint8_t kernel_median(Py_ssize_t * histo, float pop, +cdef inline np.uint8_t kernel_median(ssize_t * histo, float pop, np.uint8_t g, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): + ssize_t s0, ssize_t s1): - cdef Py_ssize_t i + cdef ssize_t i cdef float sum = pop / 2.0 if pop: @@ -153,11 +153,11 @@ cdef inline np.uint8_t kernel_median(Py_ssize_t * histo, float pop, return < np.uint8_t > (0) -cdef inline np.uint8_t kernel_minimum(Py_ssize_t * histo, float pop, +cdef inline np.uint8_t kernel_minimum(ssize_t * histo, float pop, np.uint8_t g, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): + ssize_t s0, ssize_t s1): - cdef Py_ssize_t i + cdef ssize_t i if pop: for i in range(256): @@ -167,11 +167,11 @@ cdef inline np.uint8_t kernel_minimum(Py_ssize_t * histo, float pop, return < np.uint8_t > (0) -cdef inline np.uint8_t kernel_modal(Py_ssize_t * histo, float pop, +cdef inline np.uint8_t kernel_modal(ssize_t * histo, float pop, np.uint8_t g, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): + ssize_t s0, ssize_t s1): - cdef Py_ssize_t hmax = 0, imax = 0 + cdef ssize_t hmax = 0, imax = 0 if pop: for i in range(256): @@ -183,11 +183,11 @@ cdef inline np.uint8_t kernel_modal(Py_ssize_t * histo, float pop, return < np.uint8_t > (0) -cdef inline np.uint8_t kernel_morph_contr_enh(Py_ssize_t * histo, float pop, +cdef inline np.uint8_t kernel_morph_contr_enh(ssize_t * histo, float pop, np.uint8_t g, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): + ssize_t s0, ssize_t s1): - cdef Py_ssize_t i, imin, imax + cdef ssize_t i, imin, imax if pop: for i in range(255, -1, -1): @@ -206,18 +206,18 @@ cdef inline np.uint8_t kernel_morph_contr_enh(Py_ssize_t * histo, float pop, return < np.uint8_t > (0) -cdef inline np.uint8_t kernel_pop(Py_ssize_t * histo, float pop, +cdef inline np.uint8_t kernel_pop(ssize_t * histo, float pop, np.uint8_t g, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): + ssize_t s0, ssize_t s1): return < np.uint8_t > (pop) -cdef inline np.uint8_t kernel_threshold(Py_ssize_t * histo, float pop, +cdef inline np.uint8_t kernel_threshold(ssize_t * histo, float pop, np.uint8_t g, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): + ssize_t s0, ssize_t s1): - cdef Py_ssize_t i + cdef ssize_t i cdef float mean = 0. if pop: @@ -228,11 +228,11 @@ cdef inline np.uint8_t kernel_threshold(Py_ssize_t * histo, float pop, return < np.uint8_t > (0) -cdef inline np.uint8_t kernel_tophat(Py_ssize_t * histo, float pop, +cdef inline np.uint8_t kernel_tophat(ssize_t * histo, float pop, np.uint8_t g, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): + ssize_t s0, ssize_t s1): - cdef Py_ssize_t i + cdef ssize_t i if pop: for i in range(255, -1, -1): @@ -243,12 +243,12 @@ cdef inline np.uint8_t kernel_tophat(Py_ssize_t * histo, float pop, else: return < np.uint8_t > (0) -cdef inline np.uint8_t kernel_noise_filter(Py_ssize_t * histo, float pop, +cdef inline np.uint8_t kernel_noise_filter(ssize_t * histo, float pop, np.uint8_t g, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): + ssize_t s0, ssize_t s1): - cdef Py_ssize_t i - cdef Py_ssize_t min_i + cdef ssize_t i + cdef ssize_t min_i # early stop if at least one pixel of the neighborhood has the same g if histo[g] > 0: @@ -267,10 +267,10 @@ cdef inline np.uint8_t kernel_noise_filter(Py_ssize_t * histo, float pop, return < np.uint8_t > min_i -cdef inline np.uint8_t kernel_entropy(Py_ssize_t * histo, float pop, +cdef inline np.uint8_t kernel_entropy(ssize_t * histo, float pop, np.uint8_t g, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): - cdef Py_ssize_t i + ssize_t s0, ssize_t s1): + cdef ssize_t i cdef float e, p if pop: @@ -285,11 +285,11 @@ cdef inline np.uint8_t kernel_entropy(Py_ssize_t * histo, float pop, else: return < np.uint8_t > (0) -cdef inline np.uint8_t kernel_otsu(Py_ssize_t * histo, float pop, np.uint8_t g, - float p0, float p1, Py_ssize_t s0, - Py_ssize_t s1): - cdef Py_ssize_t i - cdef Py_ssize_t max_i +cdef inline np.uint8_t kernel_otsu(ssize_t * histo, float pop, np.uint8_t g, + float p0, float p1, ssize_t s0, + ssize_t s1): + cdef ssize_t i + cdef ssize_t max_i cdef float P, mu1, mu2, q1, new_q1, sigma_b, max_sigma_b cdef float mu = 0. @@ -334,7 +334,7 @@ def autolevel(np.ndarray[np.uint8_t, ndim=2] image, np.ndarray[np.uint8_t, ndim=2] out=None, char shift_x=0, char shift_y=0): _core8(kernel_autolevel, image, selem, mask, out, shift_x, shift_y, - 0, 0, 0, 0) + 0, 0, 0, 0) def bottomhat(np.ndarray[np.uint8_t, ndim=2] image, @@ -343,7 +343,7 @@ def bottomhat(np.ndarray[np.uint8_t, ndim=2] image, np.ndarray[np.uint8_t, ndim=2] out=None, char shift_x=0, char shift_y=0): _core8(kernel_bottomhat, image, selem, mask, out, shift_x, shift_y, - 0, 0, 0, 0) + 0, 0, 0, 0) def equalize(np.ndarray[np.uint8_t, ndim=2] image, @@ -352,7 +352,7 @@ def equalize(np.ndarray[np.uint8_t, ndim=2] image, np.ndarray[np.uint8_t, ndim=2] out=None, char shift_x=0, char shift_y=0): _core8(kernel_equalize, image, selem, mask, out, shift_x, shift_y, - 0, 0, 0, 0) + 0, 0, 0, 0) def gradient(np.ndarray[np.uint8_t, ndim=2] image, @@ -361,7 +361,7 @@ def gradient(np.ndarray[np.uint8_t, ndim=2] image, np.ndarray[np.uint8_t, ndim=2] out=None, char shift_x=0, char shift_y=0): _core8(kernel_gradient, image, selem, mask, out, shift_x, shift_y, - 0, 0, 0, 0) + 0, 0, 0, 0) def maximum(np.ndarray[np.uint8_t, ndim=2] image, @@ -370,7 +370,7 @@ def maximum(np.ndarray[np.uint8_t, ndim=2] image, np.ndarray[np.uint8_t, ndim=2] out=None, char shift_x=0, char shift_y=0): _core8(kernel_maximum, image, selem, mask, out, shift_x, shift_y, - 0, 0, 0, 0) + 0, 0, 0, 0) def mean(np.ndarray[np.uint8_t, ndim=2] image, @@ -379,7 +379,7 @@ def mean(np.ndarray[np.uint8_t, ndim=2] image, np.ndarray[np.uint8_t, ndim=2] out=None, char shift_x=0, char shift_y=0): _core8(kernel_mean, image, selem, mask, out, shift_x, shift_y, - 0, 0, 0, 0) + 0, 0, 0, 0) def meansubstraction(np.ndarray[np.uint8_t, ndim=2] image, @@ -388,7 +388,7 @@ def meansubstraction(np.ndarray[np.uint8_t, ndim=2] image, np.ndarray[np.uint8_t, ndim=2] out=None, char shift_x=0, char shift_y=0): _core8(kernel_meansubstraction, image, selem, mask, out, shift_x, shift_y, - 0, 0, 0, 0) + 0, 0, 0, 0) def median(np.ndarray[np.uint8_t, ndim=2] image, @@ -397,7 +397,7 @@ def median(np.ndarray[np.uint8_t, ndim=2] image, np.ndarray[np.uint8_t, ndim=2] out=None, char shift_x=0, char shift_y=0): _core8(kernel_median, image, selem, mask, out, shift_x, shift_y, - 0, 0, 0, 0) + 0, 0, 0, 0) def minimum(np.ndarray[np.uint8_t, ndim=2] image, @@ -406,7 +406,7 @@ def minimum(np.ndarray[np.uint8_t, ndim=2] image, np.ndarray[np.uint8_t, ndim=2] out=None, char shift_x=0, char shift_y=0): _core8(kernel_minimum, image, selem, mask, out, shift_x, shift_y, - 0, 0, 0, 0) + 0, 0, 0, 0) def morph_contr_enh(np.ndarray[np.uint8_t, ndim=2] image, @@ -415,7 +415,7 @@ def morph_contr_enh(np.ndarray[np.uint8_t, ndim=2] image, np.ndarray[np.uint8_t, ndim=2] out=None, char shift_x=0, char shift_y=0): _core8(kernel_morph_contr_enh, image, selem, mask, out, shift_x, shift_y, - 0, 0, 0, 0) + 0, 0, 0, 0) def modal(np.ndarray[np.uint8_t, ndim=2] image, @@ -424,7 +424,7 @@ def modal(np.ndarray[np.uint8_t, ndim=2] image, np.ndarray[np.uint8_t, ndim=2] out=None, char shift_x=0, char shift_y=0): _core8(kernel_modal, image, selem, mask, out, shift_x, shift_y, - 0, 0, 0, 0) + 0, 0, 0, 0) def pop(np.ndarray[np.uint8_t, ndim=2] image, @@ -433,7 +433,7 @@ def pop(np.ndarray[np.uint8_t, ndim=2] image, np.ndarray[np.uint8_t, ndim=2] out=None, char shift_x=0, char shift_y=0): _core8(kernel_pop, image, selem, mask, out, shift_x, shift_y, - 0, 0, 0, 0) + 0, 0, 0, 0) def threshold(np.ndarray[np.uint8_t, ndim=2] image, @@ -442,7 +442,7 @@ def threshold(np.ndarray[np.uint8_t, ndim=2] image, np.ndarray[np.uint8_t, ndim=2] out=None, char shift_x=0, char shift_y=0): _core8(kernel_threshold, image, selem, mask, out, shift_x, shift_y, 0, 0, - 0, 0) + 0, 0) def tophat(np.ndarray[np.uint8_t, ndim=2] image, @@ -451,7 +451,7 @@ def tophat(np.ndarray[np.uint8_t, ndim=2] image, np.ndarray[np.uint8_t, ndim=2] out=None, char shift_x=0, char shift_y=0): _core8(kernel_tophat, image, selem, mask, out, shift_x, shift_y, - 0, 0, 0, 0) + 0, 0, 0, 0) def noise_filter(np.ndarray[np.uint8_t, ndim=2] image, @@ -460,7 +460,7 @@ def noise_filter(np.ndarray[np.uint8_t, ndim=2] image, np.ndarray[np.uint8_t, ndim=2] out=None, char shift_x=0, char shift_y=0): _core8(kernel_noise_filter, image, selem, mask, out, shift_x, shift_y, - 0, 0, 0, 0) + 0, 0, 0, 0) def entropy(np.ndarray[np.uint8_t, ndim=2] image, @@ -469,7 +469,7 @@ def entropy(np.ndarray[np.uint8_t, ndim=2] image, np.ndarray[np.uint8_t, ndim=2] out=None, char shift_x=0, char shift_y=0): _core8(kernel_entropy, image, selem, mask, out, shift_x, shift_y, - 0, 0, 0, 0) + 0, 0, 0, 0) def otsu(np.ndarray[np.uint8_t, ndim=2] image, @@ -478,4 +478,4 @@ def otsu(np.ndarray[np.uint8_t, ndim=2] image, np.ndarray[np.uint8_t, ndim=2] out=None, char shift_x=0, char shift_y=0): _core8(kernel_otsu, image, selem, mask, out, shift_x, shift_y, - 0, 0, 0, 0) + 0, 0, 0, 0) diff --git a/skimage/filter/rank/_crank8_percentiles.pyx b/skimage/filter/rank/_crank8_percentiles.pyx index 31b00742..bd48c78b 100644 --- a/skimage/filter/rank/_crank8_percentiles.pyx +++ b/skimage/filter/rank/_crank8_percentiles.pyx @@ -13,9 +13,9 @@ from skimage.filter.rank._core8 cimport _core8, uint8_max, uint8_min # ----------------------------------------------------------------- -cdef inline np.uint8_t kernel_autolevel(Py_ssize_t * histo, float pop, +cdef inline np.uint8_t kernel_autolevel(ssize_t * histo, float pop, np.uint8_t g, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): + ssize_t s0, ssize_t s1): cdef int i, imin, imax, sum, delta if pop: @@ -45,9 +45,9 @@ cdef inline np.uint8_t kernel_autolevel(Py_ssize_t * histo, float pop, return < np.uint8_t > (128) -cdef inline np.uint8_t kernel_gradient(Py_ssize_t * histo, float pop, +cdef inline np.uint8_t kernel_gradient(ssize_t * histo, float pop, np.uint8_t g, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): + ssize_t s0, ssize_t s1): cdef int i, imin, imax, sum, delta if pop: @@ -70,9 +70,9 @@ cdef inline np.uint8_t kernel_gradient(Py_ssize_t * histo, float pop, return < np.uint8_t > (0) -cdef inline np.uint8_t kernel_mean(Py_ssize_t * histo, float pop, +cdef inline np.uint8_t kernel_mean(ssize_t * histo, float pop, np.uint8_t g, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): + ssize_t s0, ssize_t s1): cdef int i, sum, mean, n if pop: @@ -92,11 +92,11 @@ cdef inline np.uint8_t kernel_mean(Py_ssize_t * histo, float pop, return < np.uint8_t > (0) -cdef inline np.uint8_t kernel_mean_substraction(Py_ssize_t * histo, +cdef inline np.uint8_t kernel_mean_substraction(ssize_t * histo, float pop, np.uint8_t g, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): + ssize_t s0, ssize_t s1): cdef int i, sum, mean, n if pop: @@ -116,10 +116,10 @@ cdef inline np.uint8_t kernel_mean_substraction(Py_ssize_t * histo, return < np.uint8_t > (0) -cdef inline np.uint8_t kernel_morph_contr_enh(Py_ssize_t * histo, +cdef inline np.uint8_t kernel_morph_contr_enh(ssize_t * histo, float pop, np.uint8_t g, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): + ssize_t s0, ssize_t s1): cdef int i, imin, imax, sum, delta if pop: @@ -148,9 +148,9 @@ cdef inline np.uint8_t kernel_morph_contr_enh(Py_ssize_t * histo, return < np.uint8_t > (0) -cdef inline np.uint8_t kernel_percentile(Py_ssize_t * histo, float pop, +cdef inline np.uint8_t kernel_percentile(ssize_t * histo, float pop, np.uint8_t g, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): + ssize_t s0, ssize_t s1): cdef int i cdef float sum = 0. @@ -165,9 +165,9 @@ cdef inline np.uint8_t kernel_percentile(Py_ssize_t * histo, float pop, return < np.uint8_t > (0) -cdef inline np.uint8_t kernel_pop(Py_ssize_t * histo, float pop, +cdef inline np.uint8_t kernel_pop(ssize_t * histo, float pop, np.uint8_t g, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): + ssize_t s0, ssize_t s1): cdef int i, sum, n if pop: @@ -182,9 +182,9 @@ cdef inline np.uint8_t kernel_pop(Py_ssize_t * histo, float pop, return < np.uint8_t > (0) -cdef inline np.uint8_t kernel_threshold(Py_ssize_t * histo, float pop, +cdef inline np.uint8_t kernel_threshold(ssize_t * histo, float pop, np.uint8_t g, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): + ssize_t s0, ssize_t s1): cdef int i cdef float sum = 0. @@ -212,7 +212,7 @@ def autolevel(np.ndarray[np.uint8_t, ndim=2] image, """autolevel """ _core8(kernel_autolevel, image, selem, mask, out, shift_x, shift_y, p0, p1, - 0, 0) + 0, 0) def gradient(np.ndarray[np.uint8_t, ndim=2] image, @@ -223,7 +223,7 @@ def gradient(np.ndarray[np.uint8_t, ndim=2] image, """return p0,p1 percentile gradient """ _core8(kernel_gradient, image, selem, mask, out, shift_x, shift_y, p0, p1, - 0, 0) + 0, 0) def mean(np.ndarray[np.uint8_t, ndim=2] image, @@ -234,7 +234,7 @@ def mean(np.ndarray[np.uint8_t, ndim=2] image, """return mean between [p0 and p1] percentiles """ _core8(kernel_mean, image, selem, mask, out, shift_x, shift_y, p0, p1, - 0, 0) + 0, 0) def mean_substraction(np.ndarray[np.uint8_t, ndim=2] image, @@ -245,7 +245,7 @@ def mean_substraction(np.ndarray[np.uint8_t, ndim=2] image, """return original - mean between [p0 and p1] percentiles *.5 +127 """ _core8(kernel_mean_substraction, image, selem, mask, out, shift_x, shift_y, - p0, p1, 0, 0) + p0, p1, 0, 0) def morph_contr_enh(np.ndarray[np.uint8_t, ndim=2] image, @@ -256,7 +256,7 @@ def morph_contr_enh(np.ndarray[np.uint8_t, ndim=2] image, """reforce contrast using percentiles """ _core8(kernel_morph_contr_enh, image, selem, mask, out, shift_x, shift_y, - p0, p1, 0, 0) + p0, p1, 0, 0) def percentile(np.ndarray[np.uint8_t, ndim=2] image, @@ -267,7 +267,7 @@ def percentile(np.ndarray[np.uint8_t, ndim=2] image, """return p0 percentile """ _core8(kernel_percentile, image, selem, mask, out, shift_x, shift_y, - p0, p1, 0, 0) + p0, p1, 0, 0) def pop(np.ndarray[np.uint8_t, ndim=2] image, @@ -278,7 +278,7 @@ def pop(np.ndarray[np.uint8_t, ndim=2] image, """return nb of pixels between [p0 and p1] """ _core8(kernel_pop, image, selem, mask, out, shift_x, shift_y, p0, p1, - 0, 0) + 0, 0) def threshold(np.ndarray[np.uint8_t, ndim=2] image, @@ -289,4 +289,4 @@ def threshold(np.ndarray[np.uint8_t, ndim=2] image, """return 255 if g > percentile p0 """ _core8(kernel_threshold, image, selem, mask, out, shift_x, shift_y, p0, p1, - 0, 0) + 0, 0) From 0b99eb68f45dee29e2d59ad77bdd861e3eb4cbca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Tue, 22 Jan 2013 19:24:56 +0100 Subject: [PATCH 077/215] Change type to ssize_t for all index and size variables --- skimage/graph/heap.pxd | 15 +++++++-------- skimage/measure/_find_contours.pyx | 13 +++++-------- skimage/measure/_moments.pyx | 4 ++-- 3 files changed, 14 insertions(+), 18 deletions(-) diff --git a/skimage/graph/heap.pxd b/skimage/graph/heap.pxd index 51b4f79c..74366368 100644 --- a/skimage/graph/heap.pxd +++ b/skimage/graph/heap.pxd @@ -1,29 +1,29 @@ """ This is the definition file for heap.pyx. It contains the definitions of the heap classes, such that other cython modules can "cimport heap" and thus use the -C versions of pop(), push(), and value_of(): pop_fast(), push_fast() and +C versions of pop(), push(), and value_of(): pop_fast(), push_fast() and value_of_fast() """ # determine datatypes for heap ctypedef double VALUE_T -ctypedef Py_ssize_t REFERENCE_T +ctypedef ssize_t REFERENCE_T ctypedef REFERENCE_T INDEX_T ctypedef unsigned char BOOL_T ctypedef unsigned char LEVELS_T cdef class BinaryHeap: cdef readonly INDEX_T count - cdef readonly LEVELS_T levels, min_levels + cdef readonly LEVELS_T levels, min_levels cdef VALUE_T *_values cdef REFERENCE_T *_references cdef REFERENCE_T _popped_ref - + cdef void _add_or_remove_level(self, LEVELS_T add_or_remove) cdef void _update(self) cdef void _update_one(self, INDEX_T i) cdef void _remove(self, INDEX_T i) - + cdef INDEX_T push_fast(self, VALUE_T value, REFERENCE_T reference) cdef VALUE_T pop_fast(self) @@ -32,8 +32,7 @@ cdef class FastUpdateBinaryHeap(BinaryHeap): cdef INDEX_T *_crossref cdef BOOL_T _invalid_ref cdef BOOL_T _pushed - + cdef VALUE_T value_of_fast(self, REFERENCE_T reference) - cdef INDEX_T push_if_lower_fast(self, VALUE_T value, + cdef INDEX_T push_if_lower_fast(self, VALUE_T value, REFERENCE_T reference) - \ No newline at end of file diff --git a/skimage/measure/_find_contours.pyx b/skimage/measure/_find_contours.pyx index 4f1b3cee..30eb1250 100644 --- a/skimage/measure/_find_contours.pyx +++ b/skimage/measure/_find_contours.pyx @@ -1,10 +1,7 @@ -# -*- python -*- # cython: cdivision=True - import numpy as np cimport numpy as np -np.import_array() cdef inline double _get_fraction(double from_value, double to_value, double level): @@ -14,7 +11,7 @@ cdef inline double _get_fraction(double from_value, double to_value, def iterate_and_store(np.ndarray[double, ndim=2] array, - double level, int vertex_connect_high): + double level, ssize_t vertex_connect_high): """Iterate across the given array in a marching-squares fashion, looking for segments that cross 'level'. If such a segment is found, its coordinates are added to a growing list of segments, @@ -27,7 +24,7 @@ def iterate_and_store(np.ndarray[double, ndim=2] array, raise ValueError("Input array must be at least 2x2.") cdef list arc_list = [] - cdef int n + cdef ssize_t n # The plan is to iterate a 2x2 square across the input array. This means # that the upper-left corner of the square needs to iterate across a @@ -39,17 +36,17 @@ def iterate_and_store(np.ndarray[double, ndim=2] array, # index varies the fastest). # Current coords start at 0,0. - cdef int[2] coords + cdef ssize_t[2] coords coords[0] = 0 coords[1] = 0 # Calculate the number of iterations we'll need - cdef int num_square_steps = (array.shape[0] - 1) * (array.shape[1] - 1) + cdef ssize_t num_square_steps = (array.shape[0] - 1) * (array.shape[1] - 1) cdef unsigned char square_case = 0 cdef tuple top, bottom, left, right cdef double ul, ur, ll, lr - cdef int r0, r1, c0, c1 + cdef ssize_t r0, r1, c0, c1 for n in range(num_square_steps): # There are sixteen different possible square types, diagramed below. diff --git a/skimage/measure/_moments.pyx b/skimage/measure/_moments.pyx index f84e14dd..759e4a4f 100644 --- a/skimage/measure/_moments.pyx +++ b/skimage/measure/_moments.pyx @@ -7,7 +7,7 @@ cimport numpy as np def central_moments(np.ndarray[np.double_t, ndim=2] array, double cr, double cc, int order): - cdef int p, q, r, c + cdef ssize_t p, q, r, c cdef np.ndarray[np.double_t, ndim=2] mu mu = np.zeros((order + 1, order + 1), 'double') for p in range(order + 1): @@ -18,7 +18,7 @@ def central_moments(np.ndarray[np.double_t, ndim=2] array, double cr, double cc, return mu def normalized_moments(np.ndarray[np.double_t, ndim=2] mu, int order): - cdef int p, q + cdef ssize_t p, q cdef np.ndarray[np.double_t, ndim=2] nu nu = np.zeros((order + 1, order + 1), 'double') for p in range(order + 1): From c9f72e93d6f6cb3cdde2548c1fc213c7d31b7ee2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Tue, 22 Jan 2013 22:14:22 +0100 Subject: [PATCH 078/215] Change type to ssize_t for all index and size variables --- skimage/morphology/_convex_hull.pyx | 57 ++++++++------- skimage/morphology/_pnpoly.pyx | 13 ++-- skimage/morphology/_skeletonize.py | 4 +- skimage/morphology/_skeletonize_cy.pyx | 36 +++++----- skimage/morphology/_watershed.pyx | 49 ++++++------- skimage/morphology/ccomp.pxd | 12 ++-- skimage/morphology/ccomp.pyx | 37 +++++----- skimage/morphology/cmorph.pyx | 38 +++++----- skimage/morphology/heap_general.pxi | 84 +++++++++++------------ skimage/morphology/heap_watershed.pxi | 7 +- skimage/morphology/watershed.py | 2 - skimage/segmentation/_felzenszwalb_cy.pyx | 14 ++-- 12 files changed, 173 insertions(+), 180 deletions(-) diff --git a/skimage/morphology/_convex_hull.pyx b/skimage/morphology/_convex_hull.pyx index dc426900..6c9d1083 100644 --- a/skimage/morphology/_convex_hull.pyx +++ b/skimage/morphology/_convex_hull.pyx @@ -13,47 +13,44 @@ def possible_hull(np.ndarray[dtype=np.uint8_t, ndim=2, mode="c"] img): Returns ------- - coords : ndarray (N, 2) + coords : ndarray (cols, 2) The ``(row, column)`` coordinates of all pixels that possibly belong to the convex hull. """ - cdef int i, j, k - cdef unsigned int M, N - - M = img.shape[0] - N = img.shape[1] + cdef ssize_t r, c + cdef ssize_t rows = img.shape[0] + cdef ssize_t cols = img.shape[1] - # Output: M storage slots for left boundary pixels - # N storage slots for top boundary pixels - # M storage slots for right boundary pixels - # N storage slots for bottom boundary pixels - cdef np.ndarray[dtype=np.int_t, ndim=2] nonzero = \ - np.ones((2 * (M + N), 2), dtype=np.int) - nonzero *= -1 + # Output: rows storage slots for left boundary pixels + # cols storage slots for top boundary pixels + # rows storage slots for right boundary pixels + # cols storage slots for bottom boundary pixels + cdef np.ndarray[dtype=ssize_t, ndim=2] nonzero = \ + np.ones((2 * (rows + cols), 2), dtype=np.int) + nonzero *= -1 - k = 0 - for i in range(M): - for j in range(N): - if img[i, j] != 0: + for r in range(rows): + for c in range(cols): + if img[r, c] != 0: # Left check - if nonzero[i, 1] == -1: - nonzero[i, 0] = i - nonzero[i, 1] = j + if nonzero[r, 1] == -1: + nonzero[r, 0] = r + nonzero[r, 1] = c # Right check - elif nonzero[M + N + i, 1] < j: - nonzero[M + N + i, 0] = i - nonzero[M + N + i, 1] = j + elif nonzero[rows + cols + r, 1] < c: + nonzero[rows + cols + r, 0] = r + nonzero[rows + cols + r, 1] = c # Top check - if nonzero[M + j, 1] == -1: - nonzero[M + j, 0] = i - nonzero[M + j, 1] = j + if nonzero[rows + c, 1] == -1: + nonzero[rows + c, 0] = r + nonzero[rows + c, 1] = c # Bottom check - elif nonzero[2 * M + N + j, 0] < i: - nonzero[2 * M + N + j, 0] = i - nonzero[2 * M + N + j, 1] = j - + elif nonzero[2 * rows + cols + c, 0] < r: + nonzero[2 * rows + cols + c, 0] = r + nonzero[2 * rows + cols + c, 1] = c + return nonzero[nonzero[:, 0] != -1] diff --git a/skimage/morphology/_pnpoly.pyx b/skimage/morphology/_pnpoly.pyx index 7deb6a50..d1413a24 100644 --- a/skimage/morphology/_pnpoly.pyx +++ b/skimage/morphology/_pnpoly.pyx @@ -31,18 +31,19 @@ def grid_points_inside_poly(shape, verts): vx = verts[:, 0].astype(np.double) vy = verts[:, 1].astype(np.double) - cdef int V = vx.shape[0] + cdef ssize_t V = vx.shape[0] - cdef int M = shape[0] - cdef int N = shape[1] - cdef int m, n + cdef ssize_t M = shape[0] + cdef ssize_t N = shape[1] + cdef ssize_t m, n cdef np.ndarray[dtype=np.uint8_t, ndim=2, mode="c"] out = \ np.zeros((M, N), dtype=np.uint8) for m in range(M): for n in range(N): - out[m, n] = point_in_polygon(V, vx.data, vy.data, m, n) + out[m, n] = point_in_polygon(V, vx.data, vy.data, + m, n) return out.view(bool) @@ -76,7 +77,7 @@ def points_inside_poly(points, verts): vy = verts[:, 1].astype(np.double) cdef np.ndarray[np.uint8_t, ndim=1] out = \ - np.zeros(x.shape[0], dtype=np.uint8) + np.zeros(x.shape[0], dtype=np.uint8) points_in_polygon(vx.shape[0], vx.data, vy.data, x.shape[0], x.data, y.data, diff --git a/skimage/morphology/_skeletonize.py b/skimage/morphology/_skeletonize.py index 04a65da6..b48beb86 100644 --- a/skimage/morphology/_skeletonize.py +++ b/skimage/morphology/_skeletonize.py @@ -277,8 +277,8 @@ def medial_axis(image, mask=None, return_distance=False): i, j = np.mgrid[0:image.shape[0], 0:image.shape[1]] result = masked_image.copy() distance = distance[result] - i = np.ascontiguousarray(i[result], np.int32) - j = np.ascontiguousarray(j[result], np.int32) + i = np.ascontiguousarray(i[result], np.intp) + j = np.ascontiguousarray(j[result], np.intp) result = np.ascontiguousarray(result, np.uint8) # Determine the order in which pixels are processed. diff --git a/skimage/morphology/_skeletonize_cy.pyx b/skimage/morphology/_skeletonize_cy.pyx index ff5fcdf2..c4aa0d48 100644 --- a/skimage/morphology/_skeletonize_cy.pyx +++ b/skimage/morphology/_skeletonize_cy.pyx @@ -15,11 +15,11 @@ cimport cython @cython.boundscheck(False) -def _skeletonize_loop(np.ndarray[dtype=np.uint8_t, ndim=2, +def _skeletonize_loop(np.ndarray[dtype=np.uint8_t, ndim=2, negative_indices=False, mode='c'] result, - np.ndarray[dtype=np.int32_t, ndim=1, + np.ndarray[dtype=np.intp_t, ndim=1, negative_indices=False, mode='c'] i, - np.ndarray[dtype=np.int32_t, ndim=1, + np.ndarray[dtype=np.intp_t, ndim=1, negative_indices=False, mode='c'] j, np.ndarray[dtype=np.int32_t, ndim=1, negative_indices=False, mode='c'] order, @@ -37,13 +37,13 @@ def _skeletonize_loop(np.ndarray[dtype=np.uint8_t, ndim=2, i, j : ndarrays The coordinates of each foreground pixel in the image - + order : ndarray The index of each pixel, in the order of processing (order[0] is the first pixel to process, etc.) - + table : ndarray - The 512-element lookup table of values after transformation + The 512-element lookup table of values after transformation (whether to keep or not each configuration in a binary 3x3 array) Notes @@ -55,15 +55,15 @@ def _skeletonize_loop(np.ndarray[dtype=np.uint8_t, ndim=2, the quench-line of the brushfire will be evaluated later than a point closer to the edge. - Note that the neighbourhood of a pixel may evolve before the loop - arrives at this pixel. This is why it is possible to compute the + Note that the neighbourhood of a pixel may evolve before the loop + arrives at this pixel. This is why it is possible to compute the skeleton in only one pass, thanks to an adapted ordering of the pixels. """ cdef: np.int32_t accumulator - np.int32_t index, order_index - np.int32_t ii, jj + ssize_t index, order_index + ssize_t ii, jj for index in range(order.shape[0]): accumulator = 16 @@ -110,21 +110,21 @@ def _table_lookup_index(np.ndarray[dtype=np.uint8_t, ndim=2, 256 128 64 32 16 8 4 2 1 - + but this runs about twice as fast because of inlining and the hardwired kernel. """ cdef: - np.ndarray[dtype=np.int32_t, ndim=2, + np.ndarray[dtype=np.int32_t, ndim=2, negative_indices=False, mode='c'] indexer np.int32_t *p_indexer np.uint8_t *p_image - np.int32_t i_stride - np.int32_t i_shape - np.int32_t j_shape - np.int32_t i - np.int32_t j - np.int32_t offset + ssize_t i_stride + ssize_t i_shape + ssize_t j_shape + ssize_t i + ssize_t j + ssize_t offset i_shape = image.shape[0] j_shape = image.shape[1] diff --git a/skimage/morphology/_watershed.pyx b/skimage/morphology/_watershed.pyx index c86d8744..b98ca332 100644 --- a/skimage/morphology/_watershed.pyx +++ b/skimage/morphology/_watershed.pyx @@ -9,39 +9,33 @@ All rights reserved. Original author: Lee Kamentsky """ - -cdef extern from "numpy/arrayobject.h": - cdef void import_array() -import_array() - import numpy as np cimport numpy as np cimport cython -DTYPE_INT32 = np.int32 + ctypedef np.int32_t DTYPE_INT32_t DTYPE_BOOL = np.bool ctypedef np.int8_t DTYPE_BOOL_t + include "heap_watershed.pxi" + @cython.boundscheck(False) -def watershed(np.ndarray[DTYPE_INT32_t, ndim=1, negative_indices=False, - mode='c'] image, - np.ndarray[DTYPE_INT32_t, ndim=2, negative_indices=False, - mode='c'] pq, - DTYPE_INT32_t age, - np.ndarray[DTYPE_INT32_t, ndim=2, negative_indices=False, - mode='c'] structure, - DTYPE_INT32_t ndim, - np.ndarray[DTYPE_BOOL_t, ndim=1, negative_indices=False, - mode='c'] mask, - np.ndarray[DTYPE_INT32_t, ndim=1, negative_indices=False, - mode='c'] image_shape, - np.ndarray[DTYPE_INT32_t, ndim=1, negative_indices=False, - mode='c'] output): +def watershed(np.ndarray[DTYPE_INT32_t, ndim=1, negative_indices=False, + mode='c'] image, + np.ndarray[DTYPE_INT32_t, ndim=2, negative_indices=False, + mode='c'] pq, + ssize_t age, + np.ndarray[DTYPE_INT32_t, ndim=2, negative_indices=False, + mode='c'] structure, + np.ndarray[DTYPE_BOOL_t, ndim=1, negative_indices=False, + mode='c'] mask, + np.ndarray[DTYPE_INT32_t, ndim=1, negative_indices=False, + mode='c'] output): """Do heavy lifting of watershed algorithm - + Parameters ---------- @@ -58,20 +52,17 @@ def watershed(np.ndarray[DTYPE_INT32_t, ndim=1, negative_indices=False, in a flattened array. The remaining elements are the offsets from the point to its neighbor in the various dimensions - ndim - # of dimensions in the image mask - numpy boolean (char) array indicating which pixels to consider and which to ignore. Also flattened. - image_shape - the dimensions of the image, for boundary checking, - a numpy array of np.int32 output - put the image labels in here """ cdef Heapitem elem cdef Heapitem new_elem - cdef DTYPE_INT32_t nneighbors = structure.shape[0] - cdef DTYPE_INT32_t i = 0 - cdef DTYPE_INT32_t index = 0 - cdef DTYPE_INT32_t old_index = 0 - cdef DTYPE_INT32_t max_index = image.shape[0] + cdef ssize_t nneighbors = structure.shape[0] + cdef ssize_t i = 0 + cdef ssize_t index = 0 + cdef ssize_t old_index = 0 + cdef ssize_t max_index = image.shape[0] cdef Heap *hp = heap_from_numpy2() diff --git a/skimage/morphology/ccomp.pxd b/skimage/morphology/ccomp.pxd index 0b431832..b50703e9 100644 --- a/skimage/morphology/ccomp.pxd +++ b/skimage/morphology/ccomp.pxd @@ -1,10 +1,10 @@ """Export fast union find in Cython""" cimport numpy as np -DTYPE = np.int -ctypedef np.int_t DTYPE_t +DTYPE = np.intp +ctypedef np.intp_t DTYPE_t -cdef DTYPE_t find_root(np.int_t *forest, np.int_t n) -cdef set_root(np.int_t *forest, np.int_t n, np.int_t root) -cdef join_trees(np.int_t *forest, np.int_t n, np.int_t m) -cdef link_bg(np.int_t *forest, np.int_t n, np.int_t *background_node) +cdef DTYPE_t find_root(DTYPE_t *forest, DTYPE_t n) +cdef set_root(DTYPE_t *forest, DTYPE_t n, DTYPE_t root) +cdef join_trees(DTYPE_t *forest, DTYPE_t n, DTYPE_t m) +cdef link_bg(DTYPE_t *forest, DTYPE_t n, DTYPE_t *background_node) diff --git a/skimage/morphology/ccomp.pyx b/skimage/morphology/ccomp.pyx index 6a4fb1f2..dc85196b 100644 --- a/skimage/morphology/ccomp.pyx +++ b/skimage/morphology/ccomp.pyx @@ -23,23 +23,25 @@ See also: # Tree operations implemented by an array as described in Wu et al. # The term "forest" is used to indicate an array that stores one or more trees -DTYPE = np.int +DTYPE = np.intp -cdef DTYPE_t find_root(np.int_t *forest, np.int_t n): + +cdef DTYPE_t find_root(DTYPE_t *forest, DTYPE_t n): """Find the root of node n. """ - cdef np.int_t root = n + cdef DTYPE_t root = n while (forest[root] < root): root = forest[root] return root -cdef set_root(np.int_t *forest, np.int_t n, np.int_t root): + +cdef set_root(DTYPE_t *forest, DTYPE_t n, DTYPE_t root): """ Set all nodes on a path to point to new_root. """ - cdef np.int_t j + cdef DTYPE_t j while (forest[n] < n): j = forest[n] forest[n] = root @@ -48,12 +50,12 @@ cdef set_root(np.int_t *forest, np.int_t n, np.int_t root): forest[n] = root -cdef join_trees(np.int_t *forest, np.int_t n, np.int_t m): +cdef join_trees(DTYPE_t *forest, DTYPE_t n, DTYPE_t m): """Join two trees containing nodes n and m. """ - cdef np.int_t root = find_root(forest, n) - cdef np.int_t root_m + cdef DTYPE_t root = find_root(forest, n) + cdef DTYPE_t root_m if (n != m): root_m = find_root(forest, m) @@ -64,7 +66,8 @@ cdef join_trees(np.int_t *forest, np.int_t n, np.int_t m): set_root(forest, n, root) set_root(forest, m, root) -cdef link_bg(np.int_t *forest, np.int_t n, np.int_t *background_node): + +cdef link_bg(DTYPE_t *forest, DTYPE_t n, DTYPE_t *background_node): """ Link a node to the background node. @@ -76,7 +79,7 @@ cdef link_bg(np.int_t *forest, np.int_t n, np.int_t *background_node): # Connected components search as described in Fiorio et al. -def label(input, np.int_t neighbors=8, np.int_t background=-1): +def label(input, DTYPE_t neighbors=8, DTYPE_t background=-1): """Label connected regions of an integer array. Two pixels are connected when they are neighbors and have the same value. @@ -134,8 +137,8 @@ def label(input, np.int_t neighbors=8, np.int_t background=-1): [-1 -1 -1]] """ - cdef np.int_t rows = input.shape[0] - cdef np.int_t cols = input.shape[1] + cdef DTYPE_t rows = input.shape[0] + cdef DTYPE_t cols = input.shape[1] cdef np.ndarray[DTYPE_t, ndim=2] data = np.array(input, copy=True, dtype=DTYPE) @@ -143,12 +146,12 @@ def label(input, np.int_t neighbors=8, np.int_t background=-1): forest = np.arange(data.size, dtype=DTYPE).reshape((rows, cols)) - cdef np.int_t *forest_p = forest.data - cdef np.int_t *data_p = data.data + cdef DTYPE_t *forest_p = forest.data + cdef DTYPE_t *data_p = data.data - cdef np.int_t i, j + cdef DTYPE_t i, j - cdef np.int_t background_node = -999 + cdef DTYPE_t background_node = -999 if neighbors != 4 and neighbors != 8: raise ValueError('Neighbors must be either 4 or 8.') @@ -197,7 +200,7 @@ def label(input, np.int_t neighbors=8, np.int_t background=-1): # Label output - cdef np.int_t ctr = 0 + cdef DTYPE_t ctr = 0 for i in range(rows): for j in range(cols): if (i*cols + j) == background_node: diff --git a/skimage/morphology/cmorph.pyx b/skimage/morphology/cmorph.pyx index 9b8b3a27..418b0571 100644 --- a/skimage/morphology/cmorph.pyx +++ b/skimage/morphology/cmorph.pyx @@ -13,13 +13,13 @@ def dilate(np.ndarray[np.uint8_t, ndim=2] image, np.ndarray[np.uint8_t, ndim=2] out=None, char shift_x=0, char shift_y=0): - cdef int rows = image.shape[0] - cdef int cols = image.shape[1] - cdef int srows = selem.shape[0] - cdef int scols = selem.shape[1] + cdef ssize_t rows = image.shape[0] + cdef ssize_t cols = image.shape[1] + cdef ssize_t srows = selem.shape[0] + cdef ssize_t scols = selem.shape[1] - cdef int centre_r = int(selem.shape[0] / 2) - shift_y - cdef int centre_c = int(selem.shape[1] / 2) - shift_x + cdef ssize_t centre_r = int(selem.shape[0] / 2) - shift_y + cdef ssize_t centre_c = int(selem.shape[1] / 2) - shift_x image = np.ascontiguousarray(image) if out is None: @@ -30,11 +30,11 @@ def dilate(np.ndarray[np.uint8_t, ndim=2] image, cdef np.uint8_t* out_data = out.data cdef np.uint8_t* image_data = image.data - cdef int r, c, rr, cc, s, value, local_max + cdef ssize_t r, c, rr, cc, s, value, local_max - cdef int selem_num = np.sum(selem != 0) - cdef int* sr = malloc(selem_num * sizeof(int)) - cdef int* sc = malloc(selem_num * sizeof(int)) + cdef ssize_t selem_num = np.sum(selem != 0) + cdef ssize_t* sr = malloc(selem_num * sizeof(ssize_t)) + cdef ssize_t* sc = malloc(selem_num * sizeof(ssize_t)) s = 0 for r in range(srows): @@ -68,13 +68,13 @@ def erode(np.ndarray[np.uint8_t, ndim=2] image, np.ndarray[np.uint8_t, ndim=2] out=None, char shift_x=0, char shift_y=0): - cdef int rows = image.shape[0] - cdef int cols = image.shape[1] - cdef int srows = selem.shape[0] - cdef int scols = selem.shape[1] + cdef ssize_t rows = image.shape[0] + cdef ssize_t cols = image.shape[1] + cdef ssize_t srows = selem.shape[0] + cdef ssize_t scols = selem.shape[1] - cdef int centre_r = int(selem.shape[0] / 2) - shift_y - cdef int centre_c = int(selem.shape[1] / 2) - shift_x + cdef ssize_t centre_r = int(selem.shape[0] / 2) - shift_y + cdef ssize_t centre_c = int(selem.shape[1] / 2) - shift_x image = np.ascontiguousarray(image) if out is None: @@ -87,9 +87,9 @@ def erode(np.ndarray[np.uint8_t, ndim=2] image, cdef int r, c, rr, cc, s, value, local_min - cdef int selem_num = np.sum(selem != 0) - cdef int* sr = malloc(selem_num * sizeof(int)) - cdef int* sc = malloc(selem_num * sizeof(int)) + cdef ssize_t selem_num = np.sum(selem != 0) + cdef ssize_t* sr = malloc(selem_num * sizeof(ssize_t)) + cdef ssize_t* sc = malloc(selem_num * sizeof(ssize_t)) s = 0 for r in range(srows): diff --git a/skimage/morphology/heap_general.pxi b/skimage/morphology/heap_general.pxi index a113b98e..c60096d6 100644 --- a/skimage/morphology/heap_general.pxi +++ b/skimage/morphology/heap_general.pxi @@ -10,21 +10,18 @@ All rights reserved. Original author: Lee Kamentsky """ -cdef extern from "stdlib.h": - ctypedef unsigned long size_t - void free(void *ptr) - void *malloc(size_t size) - void *realloc(void *ptr, size_t size) +from libc.stdlib cimport free, malloc, realloc + cdef struct Heap: - unsigned int items - unsigned int space + ssize_t items + ssize_t space Heapitem *data Heapitem **ptrs cdef inline Heap *heap_from_numpy2(): - cdef unsigned int k - cdef Heap *heap + cdef ssize_t k + cdef Heap *heap heap = malloc(sizeof (Heap)) heap.items = 0 heap.space = 1000 @@ -39,7 +36,7 @@ cdef inline void heap_done(Heap *heap): free(heap.ptrs) free(heap) -cdef inline void swap(unsigned int a, unsigned int b, Heap *h): +cdef inline void swap(ssize_t a, ssize_t b, Heap *h): h.ptrs[a], h.ptrs[b] = h.ptrs[b], h.ptrs[a] @@ -47,13 +44,13 @@ cdef inline void swap(unsigned int a, unsigned int b, Heap *h): # heappop - inlined # # pop an element off the heap, maintaining heap invariant -# +# # Note: heap ordering is the same as python heapq, i.e., smallest first. ###################################################### -cdef inline void heappop(Heap *heap, - Heapitem *dest): - cdef unsigned int i, smallest, l, r # heap indices - +cdef inline void heappop(Heap *heap, Heapitem *dest): + + cdef ssize_t i, smallest, l, r # heap indices + # # Start by copying the first element to the destination # @@ -76,10 +73,10 @@ cdef inline void heappop(Heap *heap, smallest = i while True: # loop invariant here: smallest == i - + # find smallest of (i, l, r), and swap it to i's position if necessary - l = i*2+1 #__left(i) - r = i*2+2 #__right(i) + l = i * 2 + 1 #__left(i) + r = i * 2 + 2 #__right(i) if l < heap.items: if smaller(heap.ptrs[l], heap.ptrs[i]): smallest = l @@ -88,13 +85,14 @@ cdef inline void heappop(Heap *heap, else: # this is unnecessary, but trims 0.04 out of 0.85 seconds... break - # the element at i is smaller than either of its children, heap invariant restored. + # the element at i is smaller than either of its children, heap + # invariant restored. if smallest == i: break # swap swap(i, smallest, heap) i = smallest - + ################################################## # heappush - inlined # @@ -102,34 +100,36 @@ cdef inline void heappop(Heap *heap, # # Note: heap ordering is the same as python heapq, i.e., smallest first. ################################################## -cdef inline void heappush(Heap *heap, - Heapitem *new_elem): - cdef unsigned int child = heap.items - cdef unsigned int parent - cdef unsigned int k - cdef Heapitem *new_data +cdef inline void heappush(Heap *heap, Heapitem *new_elem): - # grow if necessary - if heap.items == heap.space: + cdef ssize_t child = heap.items + cdef ssize_t parent + cdef ssize_t k + cdef Heapitem *new_data + + # grow if necessary + if heap.items == heap.space: heap.space = heap.space * 2 - new_data = realloc( heap.data, (heap.space * sizeof(Heapitem))) - heap.ptrs = realloc( heap.ptrs, (heap.space * sizeof(Heapitem *))) + new_data = realloc(heap.data, + (heap.space * sizeof(Heapitem))) + heap.ptrs = realloc(heap.ptrs, + (heap.space * sizeof(Heapitem *))) for k in range(heap.items): heap.ptrs[k] = new_data + (heap.ptrs[k] - heap.data) for k in range(heap.items, heap.space): heap.ptrs[k] = new_data + k heap.data = new_data - # insert new data at child - heap.ptrs[child][0] = new_elem[0] - heap.items += 1 + # insert new data at child + heap.ptrs[child][0] = new_elem[0] + heap.items += 1 - # restore heap invariant, all parents <= children - while child>0: - parent = (child + 1) / 2 - 1 # __parent(i) - - if smaller(heap.ptrs[child], heap.ptrs[parent]): - swap(parent, child, heap) - child = parent - else: - break + # restore heap invariant, all parents <= children + while child > 0: + parent = (child + 1) / 2 - 1 # __parent(i) + + if smaller(heap.ptrs[child], heap.ptrs[parent]): + swap(parent, child, heap) + child = parent + else: + break diff --git a/skimage/morphology/heap_watershed.pxi b/skimage/morphology/heap_watershed.pxi index ea66da26..43917690 100644 --- a/skimage/morphology/heap_watershed.pxi +++ b/skimage/morphology/heap_watershed.pxi @@ -13,14 +13,17 @@ import numpy as np cimport numpy as np cimport cython + cdef struct Heapitem: np.int32_t value np.int32_t age - np.int32_t index + ssize_t index + cdef inline int smaller(Heapitem *a, Heapitem *b): if a.value <> b.value: - return a.value < b.value + return a.value < b.value return a.age < b.age + include "heap_general.pxi" diff --git a/skimage/morphology/watershed.py b/skimage/morphology/watershed.py index b6f2a4fb..fbd63281 100644 --- a/skimage/morphology/watershed.py +++ b/skimage/morphology/watershed.py @@ -214,9 +214,7 @@ def watershed(image, markers, connectivity=None, offset=None, mask=None): c_mask = c_mask.astype(np.int8).flatten() _watershed.watershed(c_image.flatten(), pq, age, c, - c_image.ndim, c_mask, - np.array(c_image.shape, np.int32), c_output) c_output = c_output.reshape(c_image.shape)[[slice(1, -1, None)] * image.ndim] diff --git a/skimage/segmentation/_felzenszwalb_cy.pyx b/skimage/segmentation/_felzenszwalb_cy.pyx index efae45a4..79131eac 100644 --- a/skimage/segmentation/_felzenszwalb_cy.pyx +++ b/skimage/segmentation/_felzenszwalb_cy.pyx @@ -54,23 +54,23 @@ def _felzenszwalb_grey(image, double scale=1, sigma=0.8, int min_size=20): uright_cost.ravel()]).astype(np.float) # compute edges between pixels: height, width = image.shape[:2] - cdef np.ndarray[np.int_t, ndim=2] segments \ - = np.arange(width * height, dtype=np.int).reshape(height, width) + cdef np.ndarray[np.intp_t, ndim=2] segments \ + = np.arange(width * height, dtype=np.intp).reshape(height, width) right_edges = np.c_[segments[1:, :].ravel(), segments[:-1, :].ravel()] down_edges = np.c_[segments[:, 1:].ravel(), segments[:, :-1].ravel()] dright_edges = np.c_[segments[1:, 1:].ravel(), segments[:-1, :-1].ravel()] uright_edges = np.c_[segments[:-1, 1:].ravel(), segments[1:, :-1].ravel()] - cdef np.ndarray[np.int_t, ndim=2] edges \ + cdef np.ndarray[np.intp_t, ndim=2] edges \ = np.vstack([right_edges, down_edges, dright_edges, uright_edges]) # initialize data structures for segment size # and inner cost, then start greedy iteration over edges. edge_queue = np.argsort(costs) edges = np.ascontiguousarray(edges[edge_queue]) costs = np.ascontiguousarray(costs[edge_queue]) - cdef np.int_t *segments_p = segments.data - cdef np.int_t *edges_p = edges.data + cdef np.intp_t *segments_p = segments.data + cdef np.intp_t *edges_p = edges.data cdef np.float_t *costs_p = costs.data - cdef np.ndarray[np.int_t, ndim=1] segment_size \ + cdef np.ndarray[np.intp_t, ndim=1] segment_size \ = np.ones(width * height, dtype=np.int) # inner cost of segments cdef np.ndarray[np.float_t, ndim=1] cint = np.zeros(width * height) @@ -96,7 +96,7 @@ def _felzenszwalb_grey(image, double scale=1, sigma=0.8, int min_size=20): cint[seg_new] = costs_p[0] # postprocessing to remove small segments - edges_p = edges.data + edges_p = edges.data for e in range(costs.size): seg0 = find_root(segments_p, edges_p[0]) seg1 = find_root(segments_p, edges_p[1]) From 9f55fa2112ab10c8112276bb15b24a4284164a7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Tue, 22 Jan 2013 22:23:54 +0100 Subject: [PATCH 079/215] Change type to ssize_t for all index and size variables --- skimage/segmentation/_felzenszwalb_cy.pyx | 2 +- skimage/segmentation/_quickshift.pyx | 10 ++++++---- skimage/segmentation/_slic.pyx | 13 +++++++------ 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/skimage/segmentation/_felzenszwalb_cy.pyx b/skimage/segmentation/_felzenszwalb_cy.pyx index 79131eac..2a4ea173 100644 --- a/skimage/segmentation/_felzenszwalb_cy.pyx +++ b/skimage/segmentation/_felzenszwalb_cy.pyx @@ -10,7 +10,7 @@ from ..util import img_as_float @cython.boundscheck(False) @cython.wraparound(False) @cython.cdivision(True) -def _felzenszwalb_grey(image, double scale=1, sigma=0.8, int min_size=20): +def _felzenszwalb_grey(image, double scale=1, sigma=0.8, ssize_t min_size=20): """Felzenszwalb's efficient graph based segmentation for a single channel. Produces an oversegmentation of a 2d image using a fast, minimum spanning diff --git a/skimage/segmentation/_quickshift.pyx b/skimage/segmentation/_quickshift.pyx index b465eb08..4362725c 100644 --- a/skimage/segmentation/_quickshift.pyx +++ b/skimage/segmentation/_quickshift.pyx @@ -85,18 +85,19 @@ def quickshift(image, ratio=1., float kernel_size=5, max_dist=10, raise ValueError("Sigma should be >= 1") cdef int w = int(3 * kernel_size) - cdef int height = image_c.shape[0] - cdef int width = image_c.shape[1] - cdef int channels = image_c.shape[2] + cdef ssize_t height = image_c.shape[0] + cdef ssize_t width = image_c.shape[1] + cdef ssize_t channels = image_c.shape[2] cdef double current_density, closest, dist - cdef int r, c, r_, c_, channel + cdef ssize_t r, c, r_, c_, channel, r_min, c_min cdef np.float_t* image_p = image_c.data cdef np.float_t* current_pixel_p = image_p cdef np.ndarray[dtype=np.float_t, ndim=2] densities \ = np.zeros((height, width)) + # compute densities for r in range(height): for c in range(width): @@ -120,6 +121,7 @@ def quickshift(image, ratio=1., float kernel_size=5, max_dist=10, = np.arange(width * height).reshape(height, width) cdef np.ndarray[dtype=np.float_t, ndim=2] dist_parent \ = np.zeros((height, width)) + # find nearest node with higher density current_pixel_p = image_p for r in range(height): diff --git a/skimage/segmentation/_slic.pyx b/skimage/segmentation/_slic.pyx index dfb37a7c..260b7689 100644 --- a/skimage/segmentation/_slic.pyx +++ b/skimage/segmentation/_slic.pyx @@ -1,3 +1,4 @@ +#cython: boundscheck=False import numpy as np cimport numpy as np from time import time @@ -7,7 +8,7 @@ from ..color import rgb2lab, gray2rgb def slic(image, n_segments=100, ratio=10., max_iter=10, sigma=1, - convert2lab=True): + convert2lab=True): """Segments image using k-means clustering in Color-(x,y) space. Parameters @@ -62,10 +63,10 @@ def slic(image, n_segments=100, ratio=10., max_iter=10, sigma=1, image = rgb2lab(image) # initialize on grid: - cdef int height, width + cdef ssize_t height, width height, width = image.shape[:2] # approximate grid size for desired n_segments - cdef int step = np.ceil(np.sqrt(height * width / n_segments)) + cdef ssize_t step = np.ceil(np.sqrt(height * width / n_segments)) grid_y, grid_x = np.mgrid[:height, :width] means_y = grid_y[::step, ::step] means_x = grid_x[::step, ::step] @@ -81,11 +82,11 @@ def slic(image, n_segments=100, ratio=10., max_iter=10, sigma=1, ratio = (ratio / float(step)) ** 2 cdef np.ndarray[dtype=np.float_t, ndim=3] image_yx \ = np.dstack([grid_y, grid_x, image / ratio]).copy("C") - cdef int i, k, x, y, x_min, x_max, y_min, y_max, changes + cdef ssize_t i, k, x, y, x_min, x_max, y_min, y_max, changes cdef double dist_mean - cdef np.ndarray[dtype=np.int_t, ndim=2] nearest_mean \ - = np.zeros((height, width), dtype=np.int) + cdef np.ndarray[dtype=np.intp_t, ndim=2] nearest_mean \ + = np.zeros((height, width), dtype=np.intp) cdef np.ndarray[dtype=np.float_t, ndim=2] distance \ = np.empty((height, width)) cdef np.float_t* image_p = image_yx.data From 2957cb873ba5bb03f371847458c35be1441c88c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Tue, 22 Jan 2013 22:29:03 +0100 Subject: [PATCH 080/215] Change type to ssize_t for all index and size variables --- skimage/transform/_hough_transform.pyx | 30 +++++++++++++------------- skimage/transform/_warps_cy.pyx | 8 +++---- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/skimage/transform/_hough_transform.pyx b/skimage/transform/_hough_transform.pyx index ef1cf700..016be878 100644 --- a/skimage/transform/_hough_transform.pyx +++ b/skimage/transform/_hough_transform.pyx @@ -13,8 +13,8 @@ cdef double PI_2 = 1.5707963267948966 cdef double NEG_PI_2 = -PI_2 -cdef inline int round(double r): - return ((r + 0.5) if (r > 0.0) else (r - 0.5)) +cdef inline ssize_t round(double r): + return ((r + 0.5) if (r > 0.0) else (r - 0.5)) @cython.boundscheck(False) @@ -36,21 +36,20 @@ def _hough(np.ndarray img, np.ndarray[ndim=1, dtype=np.double_t] theta=None): # compute the bins and allocate the accumulator array cdef np.ndarray[ndim=2, dtype=np.uint64_t] accum cdef np.ndarray[ndim=1, dtype=np.double_t] bins - cdef int max_distance, offset + cdef ssize_t max_distance, offset - max_distance = 2 * ceil((sqrt(img.shape[0] * img.shape[0] + - img.shape[1] * img.shape[1]))) + max_distance = 2 * ceil(sqrt(img.shape[0] * img.shape[0] + + img.shape[1] * img.shape[1])) accum = np.zeros((max_distance, theta.shape[0]), dtype=np.uint64) bins = np.linspace(-max_distance / 2.0, max_distance / 2.0, max_distance) offset = max_distance / 2 # compute the nonzero indexes cdef np.ndarray[ndim=1, dtype=np.npy_intp] x_idxs, y_idxs - y_idxs, x_idxs = np.PyArray_Nonzero(img) - + y_idxs, x_idxs = np.nonzero(img) # finally, run the transform - cdef int nidxs, nthetas, i, j, x, y, accum_idx + cdef ssize_t nidxs, nthetas, i, j, x, y, accum_idx nidxs = y_idxs.shape[0] # x and y are the same shape nthetas = theta.shape[0] for i in range(nidxs): @@ -78,26 +77,27 @@ def _probabilistic_hough(np.ndarray img, int value_threshold, int line_length, \ theta = math.pi/2-np.arange(180)/180.0* math.pi ctheta = np.cos(theta) stheta = np.sin(theta) - cdef int height = img.shape[0] - cdef int width = img.shape[1] + cdef ssize_t height = img.shape[0] + cdef ssize_t width = img.shape[1] # compute the bins and allocate the accumulator array cdef np.ndarray[ndim=2, dtype=np.int64_t] accum cdef np.ndarray[ndim=2, dtype=np.uint8_t] mask = np.zeros((height, width), dtype=np.uint8) cdef np.ndarray[ndim=2, dtype=np.int32_t] line_end = np.zeros((2, 2), dtype=np.int32) - cdef int max_distance, offset, num_indexes, index + cdef ssize_t max_distance, offset, num_indexes, index cdef double a, b - cdef int nidxs, nthetas, i, j, x, y, px, py, accum_idx, value, max_value, max_theta + cdef ssize_t nidxs, nthetas, i, j, x, y, px, py, accum_idx + cdef int value, max_value, max_theta cdef int shift = 16 # maximum line number cutoff - cdef int lines_max = 2 ** 15 - cdef int xflag, x0, y0, dx0, dy0, dx, dy, gap, x1, y1, good_line, count + cdef ssize_t lines_max = 2 ** 15 + cdef ssize_t xflag, x0, y0, dx0, dy0, dx, dy, gap, x1, y1, good_line, count max_distance = 2 * ceil((sqrt(img.shape[0] * img.shape[0] + img.shape[1] * img.shape[1]))) accum = np.zeros((max_distance, theta.shape[0]), dtype=np.int64) offset = max_distance / 2 # find the nonzero indexes cdef np.ndarray[ndim=1, dtype=np.npy_intp] x_idxs, y_idxs - y_idxs, x_idxs = np.nonzero(img) + y_idxs, x_idxs = np.nonzero(img) num_indexes = y_idxs.shape[0] # x and y are the same shape nthetas = theta.shape[0] points = [] diff --git a/skimage/transform/_warps_cy.pyx b/skimage/transform/_warps_cy.pyx index a5a372d2..28c258fd 100644 --- a/skimage/transform/_warps_cy.pyx +++ b/skimage/transform/_warps_cy.pyx @@ -93,7 +93,7 @@ def _warp_fast(np.ndarray image, np.ndarray H, output_shape=None, int order=1, "`constant`, `nearest`, `wrap` or `reflect`.") cdef char mode_c = ord(mode[0].upper()) - cdef int out_r, out_c + cdef ssize_t out_r, out_c if output_shape is None: out_r = img.shape[0] out_c = img.shape[1] @@ -104,10 +104,10 @@ def _warp_fast(np.ndarray image, np.ndarray H, output_shape=None, int order=1, cdef np.ndarray[dtype=np.double_t, ndim=2] out = \ np.zeros((out_r, out_c), dtype=np.double) - cdef int tfr, tfc + cdef ssize_t tfr, tfc cdef double r, c - cdef int rows = img.shape[0] - cdef int cols = img.shape[1] + cdef ssize_t rows = img.shape[0] + cdef ssize_t cols = img.shape[1] cdef double (*interp_func)(double*, ssize_t, ssize_t, double, double, char, double) From 2c90aa70e5ece6ec5d117e3c76ad6040696c26e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Tue, 22 Jan 2013 22:36:58 +0100 Subject: [PATCH 081/215] Use np.intp rather than ssize_t for ndarrays --- skimage/draw/_draw.pyx | 2 +- skimage/morphology/_convex_hull.pyx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/skimage/draw/_draw.pyx b/skimage/draw/_draw.pyx index 28103304..50342c91 100644 --- a/skimage/draw/_draw.pyx +++ b/skimage/draw/_draw.pyx @@ -29,7 +29,7 @@ def line(ssize_t y, ssize_t x, ssize_t y2, ssize_t x2): """ - cdef np.ndarray[ssize_t, ndim=1, mode="c"] rr, cc + cdef np.ndarray[np.intp_t, ndim=1, mode="c"] rr, cc cdef char steep = 0 cdef ssize_t dx = abs(x2 - x) diff --git a/skimage/morphology/_convex_hull.pyx b/skimage/morphology/_convex_hull.pyx index 6c9d1083..6746fa9f 100644 --- a/skimage/morphology/_convex_hull.pyx +++ b/skimage/morphology/_convex_hull.pyx @@ -26,7 +26,7 @@ def possible_hull(np.ndarray[dtype=np.uint8_t, ndim=2, mode="c"] img): # cols storage slots for top boundary pixels # rows storage slots for right boundary pixels # cols storage slots for bottom boundary pixels - cdef np.ndarray[dtype=ssize_t, ndim=2] nonzero = \ + cdef np.ndarray[dtype=np.intp_t, ndim=2] nonzero = \ np.ones((2 * (rows + cols), 2), dtype=np.int) nonzero *= -1 From 614023b941913b4ad368a2584bf23a0630831523 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Fri, 15 Feb 2013 14:58:14 +0100 Subject: [PATCH 082/215] Replace ssize_t with Py_ssize_t globally --- skimage/_shared/geometry.pxd | 6 +- skimage/_shared/geometry.pyx | 12 +- skimage/_shared/interpolation.pxd | 20 +- skimage/_shared/interpolation.pyx | 68 +++--- skimage/_shared/transform.pxd | 2 +- skimage/_shared/transform.pyx | 2 +- skimage/draw/_draw.pyx | 56 ++--- skimage/feature/_template.pyx | 6 +- skimage/feature/_texture.pyx | 12 +- skimage/feature/corner_cy.pyx | 8 +- skimage/filter/_ctmf.pyx | 170 +++++++-------- skimage/filter/_denoise_cy.pyx | 30 +-- skimage/filter/rank/_core16.pxd | 10 +- skimage/filter/rank/_core16.pyx | 54 ++--- skimage/filter/rank/_core8.pxd | 10 +- skimage/filter/rank/_core8.pyx | 50 ++--- skimage/filter/rank/_crank16.pyx | 212 +++++++++---------- skimage/filter/rank/_crank16_bilateral.pyx | 16 +- skimage/filter/rank/_crank16_percentiles.pyx | 84 ++++---- skimage/filter/rank/_crank8.pyx | 140 ++++++------ skimage/filter/rank/_crank8_percentiles.pyx | 48 ++--- skimage/graph/heap.pxd | 2 +- skimage/measure/_find_contours.pyx | 10 +- skimage/measure/_moments.pyx | 4 +- skimage/morphology/_convex_hull.pyx | 6 +- skimage/morphology/_pnpoly.pyx | 8 +- skimage/morphology/_skeletonize_cy.pyx | 16 +- skimage/morphology/_watershed.pyx | 12 +- skimage/morphology/cmorph.pyx | 38 ++-- skimage/morphology/heap_general.pxi | 20 +- skimage/morphology/heap_watershed.pxi | 2 +- skimage/segmentation/_felzenszwalb_cy.pyx | 2 +- skimage/segmentation/_quickshift.pyx | 8 +- skimage/segmentation/_slic.pyx | 6 +- skimage/transform/_hough_transform.pyx | 22 +- skimage/transform/_warps_cy.pyx | 10 +- 36 files changed, 591 insertions(+), 591 deletions(-) diff --git a/skimage/_shared/geometry.pxd b/skimage/_shared/geometry.pxd index a89ce894..3379318c 100644 --- a/skimage/_shared/geometry.pxd +++ b/skimage/_shared/geometry.pxd @@ -1,6 +1,6 @@ -cdef unsigned char point_in_polygon(ssize_t nr_verts, double *xp, double *yp, +cdef unsigned char point_in_polygon(Py_ssize_t nr_verts, double *xp, double *yp, double x, double y) -cdef void points_in_polygon(ssize_t nr_verts, double *xp, double *yp, - ssize_t nr_points, double *x, double *y, +cdef void points_in_polygon(Py_ssize_t nr_verts, double *xp, double *yp, + Py_ssize_t nr_points, double *x, double *y, unsigned char *result) diff --git a/skimage/_shared/geometry.pyx b/skimage/_shared/geometry.pyx index 5a56df7c..beb07e14 100644 --- a/skimage/_shared/geometry.pyx +++ b/skimage/_shared/geometry.pyx @@ -4,7 +4,7 @@ #cython: wraparound=False -cdef inline unsigned char point_in_polygon(ssize_t nr_verts, double *xp, +cdef inline unsigned char point_in_polygon(Py_ssize_t nr_verts, double *xp, double *yp, double x, double y): """Test whether point lies inside a polygon. @@ -17,9 +17,9 @@ cdef inline unsigned char point_in_polygon(ssize_t nr_verts, double *xp, x, y : double Coordinates of point. """ - cdef ssize_t i + cdef Py_ssize_t i cdef unsigned char c = 0 - cdef ssize_t j = nr_verts - 1 + cdef Py_ssize_t j = nr_verts - 1 for i in range(nr_verts): if ( (((yp[i] <= y) and (y < yp[j])) or @@ -31,8 +31,8 @@ cdef inline unsigned char point_in_polygon(ssize_t nr_verts, double *xp, return c -cdef void points_in_polygon(ssize_t nr_verts, double *xp, double *yp, - ssize_t nr_points, double *x, double *y, +cdef void points_in_polygon(Py_ssize_t nr_verts, double *xp, double *yp, + Py_ssize_t nr_points, double *x, double *y, unsigned char *result): """Test whether points lie inside a polygon. @@ -49,6 +49,6 @@ cdef void points_in_polygon(ssize_t nr_verts, double *xp, double *yp, result : unsigned char array Test results for each point. """ - cdef ssize_t n + cdef Py_ssize_t n for n in range(nr_points): result[n] = point_in_polygon(nr_verts, xp, yp, x[n], y[n]) diff --git a/skimage/_shared/interpolation.pxd b/skimage/_shared/interpolation.pxd index 0f9123e9..c5f32b6a 100644 --- a/skimage/_shared/interpolation.pxd +++ b/skimage/_shared/interpolation.pxd @@ -1,27 +1,27 @@ -cdef double nearest_neighbour_interpolation(double* image, ssize_t rows, - ssize_t cols, double r, +cdef double nearest_neighbour_interpolation(double* image, Py_ssize_t rows, + Py_ssize_t cols, double r, double c, char mode, double cval) -cdef double bilinear_interpolation(double* image, ssize_t rows, ssize_t cols, +cdef double bilinear_interpolation(double* image, Py_ssize_t rows, Py_ssize_t cols, double r, double c, char mode, double cval) cdef double quadratic_interpolation(double x, double[3] f) -cdef double biquadratic_interpolation(double* image, ssize_t rows, ssize_t cols, +cdef double biquadratic_interpolation(double* image, Py_ssize_t rows, Py_ssize_t cols, double r, double c, char mode, double cval) cdef double cubic_interpolation(double x, double[4] f) -cdef double bicubic_interpolation(double* image, ssize_t rows, ssize_t cols, +cdef double bicubic_interpolation(double* image, Py_ssize_t rows, Py_ssize_t cols, double r, double c, char mode, double cval) -cdef double get_pixel2d(double* image, ssize_t rows, ssize_t cols, ssize_t r, - ssize_t c, char mode, double cval) +cdef double get_pixel2d(double* image, Py_ssize_t rows, Py_ssize_t cols, Py_ssize_t r, + Py_ssize_t c, char mode, double cval) -cdef double get_pixel3d(double* image, ssize_t rows, ssize_t cols, ssize_t dims, - ssize_t r, ssize_t c, ssize_t d, char mode, double cval) +cdef double get_pixel3d(double* image, Py_ssize_t rows, Py_ssize_t cols, Py_ssize_t dims, + Py_ssize_t r, Py_ssize_t c, Py_ssize_t d, char mode, double cval) -cdef ssize_t coord_map(ssize_t dim, ssize_t coord, char mode) +cdef Py_ssize_t coord_map(Py_ssize_t dim, Py_ssize_t coord, char mode) diff --git a/skimage/_shared/interpolation.pyx b/skimage/_shared/interpolation.pyx index 50b05a00..a8b96014 100644 --- a/skimage/_shared/interpolation.pyx +++ b/skimage/_shared/interpolation.pyx @@ -5,12 +5,12 @@ from libc.math cimport ceil, floor -cdef inline ssize_t round(double r): - return ((r + 0.5) if (r > 0.0) else (r - 0.5)) +cdef inline Py_ssize_t round(double r): + return ((r + 0.5) if (r > 0.0) else (r - 0.5)) -cdef inline double nearest_neighbour_interpolation(double* image, ssize_t rows, - ssize_t cols, double r, +cdef inline double nearest_neighbour_interpolation(double* image, Py_ssize_t rows, + Py_ssize_t cols, double r, double c, char mode, double cval): """Nearest neighbour interpolation at a given position in the image. @@ -38,8 +38,8 @@ cdef inline double nearest_neighbour_interpolation(double* image, ssize_t rows, return get_pixel2d(image, rows, cols, round(r), round(c), mode, cval) -cdef inline double bilinear_interpolation(double* image, ssize_t rows, - ssize_t cols, double r, double c, +cdef inline double bilinear_interpolation(double* image, Py_ssize_t rows, + Py_ssize_t cols, double r, double c, char mode, double cval): """Bilinear interpolation at a given position in the image. @@ -63,12 +63,12 @@ cdef inline double bilinear_interpolation(double* image, ssize_t rows, """ cdef double dr, dc - cdef ssize_t minr, minc, maxr, maxc + cdef Py_ssize_t minr, minc, maxr, maxc - minr = floor(r) - minc = floor(c) - maxr = ceil(r) - maxc = ceil(c) + minr = floor(r) + minc = floor(c) + maxr = ceil(r) + maxc = ceil(c) dr = r - minr dc = c - minc top = (1 - dc) * get_pixel2d(image, rows, cols, minr, minc, mode, cval) \ @@ -97,8 +97,8 @@ cdef inline double quadratic_interpolation(double x, double[3] f): return f[1] - 0.25 * (f[0] - f[2]) * x -cdef inline double biquadratic_interpolation(double* image, ssize_t rows, - ssize_t cols, double r, double c, +cdef inline double biquadratic_interpolation(double* image, Py_ssize_t rows, + Py_ssize_t cols, double r, double c, char mode, double cval): """Biquadratic interpolation at a given position in the image. @@ -122,8 +122,8 @@ cdef inline double biquadratic_interpolation(double* image, ssize_t rows, """ - cdef ssize_t r0 = round(r) - cdef ssize_t c0 = round(c) + cdef Py_ssize_t r0 = round(r) + cdef Py_ssize_t c0 = round(c) if r < 0: r0 -= 1 if c < 0: @@ -138,7 +138,7 @@ cdef inline double biquadratic_interpolation(double* image, ssize_t rows, cdef double fc[3], fr[3] - cdef ssize_t pr, pc + cdef Py_ssize_t pr, pc # row-wise cubic interpolation for pr in range(r0, r0 + 3): @@ -173,8 +173,8 @@ cdef inline double cubic_interpolation(double x, double[4] f): (3.0 * (f[1] - f[2]) + f[3] - f[0]))) -cdef inline double bicubic_interpolation(double* image, ssize_t rows, - ssize_t cols, double r, double c, +cdef inline double bicubic_interpolation(double* image, Py_ssize_t rows, + Py_ssize_t cols, double r, double c, char mode, double cval): """Bicubic interpolation at a given position in the image. @@ -198,8 +198,8 @@ cdef inline double bicubic_interpolation(double* image, ssize_t rows, """ - cdef ssize_t r0 = r - 1 - cdef ssize_t c0 = c - 1 + cdef Py_ssize_t r0 = r - 1 + cdef Py_ssize_t c0 = c - 1 if r < 0: r0 -= 1 if c < 0: @@ -210,7 +210,7 @@ cdef inline double bicubic_interpolation(double* image, ssize_t rows, cdef double fc[4], fr[4] - cdef ssize_t pr, pc + cdef Py_ssize_t pr, pc # row-wise cubic interpolation for pr in range(r0, r0 + 4): @@ -222,8 +222,8 @@ cdef inline double bicubic_interpolation(double* image, ssize_t rows, return cubic_interpolation(xr, fr) -cdef inline double get_pixel2d(double* image, ssize_t rows, ssize_t cols, - ssize_t r, ssize_t c, char mode, double cval): +cdef inline double get_pixel2d(double* image, Py_ssize_t rows, Py_ssize_t cols, + Py_ssize_t r, Py_ssize_t c, char mode, double cval): """Get a pixel from the image, taking wrapping mode into consideration. Parameters @@ -254,8 +254,8 @@ cdef inline double get_pixel2d(double* image, ssize_t rows, ssize_t cols, return image[coord_map(rows, r, mode) * cols + coord_map(cols, c, mode)] -cdef inline double get_pixel3d(double* image, ssize_t rows, ssize_t cols, - ssize_t dims, ssize_t r, ssize_t c, ssize_t d, +cdef inline double get_pixel3d(double* image, Py_ssize_t rows, Py_ssize_t cols, + Py_ssize_t dims, Py_ssize_t r, Py_ssize_t c, Py_ssize_t d, char mode, double cval): """Get a pixel from the image, taking wrapping mode into consideration. @@ -289,7 +289,7 @@ cdef inline double get_pixel3d(double* image, ssize_t rows, ssize_t cols, + d] -cdef inline ssize_t coord_map(ssize_t dim, ssize_t coord, char mode): +cdef inline Py_ssize_t coord_map(Py_ssize_t dim, Py_ssize_t coord, char mode): """ Wrap a coordinate, according to a given mode. @@ -308,20 +308,20 @@ cdef inline ssize_t coord_map(ssize_t dim, ssize_t coord, char mode): if mode == 'R': # reflect if coord < 0: # How many times times does the coordinate wrap? - if (-coord / dim) % 2 != 0: - return dim - (-coord % dim) + if (-coord / dim) % 2 != 0: + return dim - (-coord % dim) else: - return (-coord % dim) + return (-coord % dim) elif coord > dim: - if (coord / dim) % 2 != 0: - return (dim - (coord % dim)) + if (coord / dim) % 2 != 0: + return (dim - (coord % dim)) else: - return (coord % dim) + return (coord % dim) elif mode == 'W': # wrap if coord < 0: - return (dim - (-coord % dim)) + return (dim - (-coord % dim)) elif coord > dim: - return (coord % dim) + return (coord % dim) elif mode == 'N': # nearest if coord < 0: return 0 diff --git a/skimage/_shared/transform.pxd b/skimage/_shared/transform.pxd index f91a1658..ccb16ff3 100644 --- a/skimage/_shared/transform.pxd +++ b/skimage/_shared/transform.pxd @@ -2,4 +2,4 @@ cimport numpy as cnp cdef float integrate(cnp.ndarray[float, ndim=2, mode="c"] sat, - ssize_t r0, ssize_t c0, ssize_t r1, ssize_t c1) + Py_ssize_t r0, Py_ssize_t c0, Py_ssize_t r1, Py_ssize_t c1) diff --git a/skimage/_shared/transform.pyx b/skimage/_shared/transform.pyx index 24e3150d..8ce2ab67 100644 --- a/skimage/_shared/transform.pyx +++ b/skimage/_shared/transform.pyx @@ -6,7 +6,7 @@ cimport numpy as cnp cdef float integrate(cnp.ndarray[float, ndim=2, mode="c"] sat, - ssize_t r0, ssize_t c0, ssize_t r1, ssize_t c1): + Py_ssize_t r0, Py_ssize_t c0, Py_ssize_t r1, Py_ssize_t c1): """ Using a summed area table / integral image, calculate the sum over a given window. diff --git a/skimage/draw/_draw.pyx b/skimage/draw/_draw.pyx index 50342c91..5bba8c32 100644 --- a/skimage/draw/_draw.pyx +++ b/skimage/draw/_draw.pyx @@ -10,7 +10,7 @@ import numpy as np from skimage._shared.geometry cimport point_in_polygon -def line(ssize_t y, ssize_t x, ssize_t y2, ssize_t x2): +def line(Py_ssize_t y, Py_ssize_t x, Py_ssize_t y2, Py_ssize_t x2): """Generate line pixel coordinates. Parameters @@ -32,9 +32,9 @@ def line(ssize_t y, ssize_t x, ssize_t y2, ssize_t x2): cdef np.ndarray[np.intp_t, ndim=1, mode="c"] rr, cc cdef char steep = 0 - cdef ssize_t dx = abs(x2 - x) - cdef ssize_t dy = abs(y2 - y) - cdef ssize_t sx, sy, d, i + cdef Py_ssize_t dx = abs(x2 - x) + cdef Py_ssize_t dy = abs(y2 - y) + cdef Py_ssize_t sx, sy, d, i if (x2 - x) > 0: sx = 1 @@ -96,18 +96,18 @@ def polygon(y, x, shape=None): """ - cdef ssize_t nr_verts = x.shape[0] - cdef ssize_t minr = max(0, y.min()) - cdef ssize_t maxr = math.ceil(y.max()) - cdef ssize_t minc = max(0, x.min()) - cdef ssize_t maxc = math.ceil(x.max()) + cdef Py_ssize_t nr_verts = x.shape[0] + cdef Py_ssize_t minr = max(0, y.min()) + cdef Py_ssize_t maxr = math.ceil(y.max()) + cdef Py_ssize_t minc = max(0, x.min()) + cdef Py_ssize_t maxc = math.ceil(x.max()) # make sure output coordinates do not exceed image size if shape is not None: maxr = min(shape[0] - 1, maxr) maxc = min(shape[1] - 1, maxc) - cdef ssize_t r, c + cdef Py_ssize_t r, c #: make contigous arrays for r, c coordinates cdef np.ndarray contiguous_rdata, contiguous_cdata @@ -148,17 +148,17 @@ def ellipse(double cy, double cx, double yradius, double xradius, shape=None): """ - cdef ssize_t minr = max(0, cy - yradius) - cdef ssize_t maxr = math.ceil(cy + yradius) - cdef ssize_t minc = max(0, cx - xradius) - cdef ssize_t maxc = math.ceil(cx + xradius) + cdef Py_ssize_t minr = max(0, cy - yradius) + cdef Py_ssize_t maxr = math.ceil(cy + yradius) + cdef Py_ssize_t minc = max(0, cx - xradius) + cdef Py_ssize_t maxc = math.ceil(cx + xradius) # make sure output coordinates do not exceed image size if shape is not None: maxr = min(shape[0] - 1, maxr) maxc = min(shape[1] - 1, maxc) - cdef ssize_t r, c + cdef Py_ssize_t r, c #: output coordinate arrays cdef list rr = list() @@ -195,7 +195,7 @@ def circle(double cy, double cx, double radius, shape=None): return ellipse(cy, cx, radius, radius, shape) -def circle_perimeter(ssize_t cy, ssize_t cx, ssize_t radius, +def circle_perimeter(Py_ssize_t cy, Py_ssize_t cx, Py_ssize_t radius, method='bresenham'): """Generate circle perimeter coordinates. @@ -235,9 +235,9 @@ def circle_perimeter(ssize_t cy, ssize_t cx, ssize_t radius, cdef list rr = list() cdef list cc = list() - cdef ssize_t x = 0 - cdef ssize_t y = radius - cdef ssize_t d = 0 + cdef Py_ssize_t x = 0 + cdef Py_ssize_t y = radius + cdef Py_ssize_t d = 0 cdef char cmethod if method == 'bresenham': d = 3 - 2 * radius @@ -303,8 +303,8 @@ def ellipse_perimeter(int cy, int cx, int yradius, int xradius): return np.array(cy), np.array(cx) # a and b are xradius an yradius compute 2a^2 and 2b^2 - cdef ssize_t twoasquared = 2 * xradius**2 - cdef ssize_t twobsquared = 2 * yradius**2 + cdef Py_ssize_t twoasquared = 2 * xradius**2 + cdef Py_ssize_t twobsquared = 2 * yradius**2 # Pixels cdef list px = list() @@ -312,14 +312,14 @@ def ellipse_perimeter(int cy, int cx, int yradius, int xradius): # First set of points: # start at the top - cdef ssize_t x = xradius - cdef ssize_t y = 0 + cdef Py_ssize_t x = xradius + cdef Py_ssize_t y = 0 - cdef ssize_t err = 0 - cdef ssize_t xstop = twobsquared * xradius - cdef ssize_t ystop = 0 - cdef ssize_t xchange = yradius * yradius * (1 - 2 * xradius) - cdef ssize_t ychange = xradius * xradius + cdef Py_ssize_t err = 0 + cdef Py_ssize_t xstop = twobsquared * xradius + cdef Py_ssize_t ystop = 0 + cdef Py_ssize_t xchange = yradius * yradius * (1 - 2 * xradius) + cdef Py_ssize_t ychange = xradius * xradius while xstop > ystop: px.extend([x, -x, -x, x]) diff --git a/skimage/feature/_template.pyx b/skimage/feature/_template.pyx index 938e3a18..842e1eda 100644 --- a/skimage/feature/_template.pyx +++ b/skimage/feature/_template.pyx @@ -49,9 +49,9 @@ def match_template(np.ndarray[float, ndim=2, mode="c"] image, cdef float template_mean = np.mean(template) cdef float template_ssd cdef float inv_area - cdef ssize_t r, c, r_end, c_end - cdef ssize_t template_rows = template.shape[0] - cdef ssize_t template_cols = template.shape[1] + cdef Py_ssize_t r, c, r_end, c_end + cdef Py_ssize_t template_rows = template.shape[0] + cdef Py_ssize_t template_cols = template.shape[1] cdef float den, window_sqr_sum, window_mean_sqr, window_sum image_sat = integral.integral_image(image) diff --git a/skimage/feature/_texture.pyx b/skimage/feature/_texture.pyx index e2e3bb35..2b9a9adc 100644 --- a/skimage/feature/_texture.pyx +++ b/skimage/feature/_texture.pyx @@ -39,7 +39,7 @@ def _glcm_loop(np.ndarray[dtype=np.uint8_t, ndim=2, """ cdef: np.int32_t a_inx, d_idx - ssize_t r, c, rows, cols, row, col + Py_ssize_t r, c, rows, cols, row, col np.int32_t i, j rows = image.shape[0] @@ -52,8 +52,8 @@ def _glcm_loop(np.ndarray[dtype=np.uint8_t, ndim=2, i = image[r, c] # compute the location of the offset pixel - row = r + (sin(angle) * distance + 0.5) - col = c + (cos(angle) * distance + 0.5); + row = r + (sin(angle) * distance + 0.5) + col = c + (cos(angle) * distance + 0.5); # make sure the offset is within bounds if row >= 0 and row < rows and \ @@ -123,11 +123,11 @@ def _local_binary_pattern(np.ndarray[double, ndim=2] image, output_shape = (image.shape[0], image.shape[1]) cdef np.ndarray[double, ndim=2] output = np.zeros(output_shape, np.double) - cdef ssize_t rows = image.shape[0] - cdef ssize_t cols = image.shape[1] + cdef Py_ssize_t rows = image.shape[0] + cdef Py_ssize_t cols = image.shape[1] cdef double lbp - cdef ssize_t r, c, changes, i + cdef Py_ssize_t r, c, changes, i for r in range(image.shape[0]): for c in range(image.shape[1]): for i in range(P): diff --git a/skimage/feature/corner_cy.pyx b/skimage/feature/corner_cy.pyx index 34913450..407e6642 100644 --- a/skimage/feature/corner_cy.pyx +++ b/skimage/feature/corner_cy.pyx @@ -10,7 +10,7 @@ from skimage.color import rgb2grey from skimage.util import img_as_float -def corner_moravec(image, ssize_t window_size=1): +def corner_moravec(image, Py_ssize_t window_size=1): """Compute Moravec corner measure response image. This is one of the simplest corner detectors and is comparatively fast but @@ -56,8 +56,8 @@ def corner_moravec(image, ssize_t window_size=1): [ 0., 0., 0., 0., 0., 0., 0.]]) """ - cdef ssize_t rows = image.shape[0] - cdef ssize_t cols = image.shape[1] + cdef Py_ssize_t rows = image.shape[0] + cdef Py_ssize_t cols = image.shape[1] cdef cnp.ndarray[dtype=cnp.double_t, ndim=2, mode='c'] cimage, out @@ -71,7 +71,7 @@ def corner_moravec(image, ssize_t window_size=1): cdef double* out_data = out.data cdef double msum, min_msum - cdef ssize_t r, c, br, bc, mr, mc, a, b + cdef Py_ssize_t r, c, br, bc, mr, mc, a, b for r in range(2 * window_size, rows - 2 * window_size): for c in range(2 * window_size, cols - 2 * window_size): min_msum = DBL_MAX diff --git a/skimage/filter/_ctmf.pyx b/skimage/filter/_ctmf.pyx index 83269e46..65feb9e4 100644 --- a/skimage/filter/_ctmf.pyx +++ b/skimage/filter/_ctmf.pyx @@ -84,9 +84,9 @@ cdef struct PixelCount: # relative offsets from the octagon center # cdef struct SCoord: - ssize_t stride # add the stride to the memory location - ssize_t x - ssize_t y + Py_ssize_t stride # add the stride to the memory location + Py_ssize_t x + Py_ssize_t y cdef struct Histograms: void *memory # pointer to the allocated memory @@ -95,16 +95,16 @@ cdef struct Histograms: np.uint8_t *data # pointer to the image data np.uint8_t *mask # pointer to the image mask np.uint8_t *output # pointer to the output array - ssize_t column_count # number of columns represented by this + Py_ssize_t column_count # number of columns represented by this # structure - ssize_t stripe_length # number of columns including "radius" before + Py_ssize_t stripe_length # number of columns including "radius" before # and after - ssize_t row_count # number of rows available in image - ssize_t current_column # the column being processed - ssize_t current_row # the row being processed - ssize_t current_stride # offset in data and mask to current location - ssize_t radius # the "radius" of the octagon - ssize_t a_2 # 1/2 of the length of a side of the octagon + Py_ssize_t row_count # number of rows available in image + Py_ssize_t current_column # the column being processed + Py_ssize_t current_row # the row being processed + Py_ssize_t current_stride # offset in data and mask to current location + Py_ssize_t radius # the "radius" of the octagon + Py_ssize_t a_2 # 1/2 of the length of a side of the octagon # # # The strides are the offsets in the array to the points that need to @@ -138,50 +138,50 @@ cdef struct Histograms: SCoord last_bottom_left # (-) trailing edge bottom - 1 col SCoord bottom_left # (+) left side of octagon's bottom - 1 col - ssize_t row_stride # stride between one row and the next - ssize_t col_stride # stride between one column and the next + Py_ssize_t row_stride # stride between one row and the next + Py_ssize_t col_stride # stride between one column and the next # The accumulator holds the running histogram # HistogramPiece accumulator # # The running count of pixels in the accumulator # - ssize_t accumulator_count + Py_ssize_t accumulator_count # # The percent of pixels within the octagon whose value is # less than or equal to the median-filtered value (e.g. for # median, this is 50, for lower quartile it's 25) # - ssize_t percent + Py_ssize_t percent # # last_update_column keeps track of the column # of the last update # to the fine histogram accumulator. Short-term, the median # stays in one coarse block so only one fine histogram might # need to be updated # - ssize_t last_update_column[16] + Py_ssize_t last_update_column[16] ############################################################################ # # allocate_histograms - allocates the Histograms structure for the run # ############################################################################ -cdef Histograms *allocate_histograms(ssize_t rows, - ssize_t columns, - ssize_t row_stride, - ssize_t col_stride, - ssize_t radius, - ssize_t percent, +cdef Histograms *allocate_histograms(Py_ssize_t rows, + Py_ssize_t columns, + Py_ssize_t row_stride, + Py_ssize_t col_stride, + Py_ssize_t radius, + Py_ssize_t percent, np.uint8_t *data, np.uint8_t *mask, np.uint8_t *output): cdef: - ssize_t adjusted_stripe_length = columns + 2*radius + 1 - ssize_t memory_size + Py_ssize_t adjusted_stripe_length = columns + 2*radius + 1 + Py_ssize_t memory_size void *ptr Histograms *ph size_t roundoff - ssize_t a + Py_ssize_t a SCoord *psc memory_size = (adjusted_stripe_length * @@ -199,7 +199,7 @@ cdef Histograms *allocate_histograms(ssize_t rows, # # Align histogram memory to a 32-byte boundary # - roundoff = ptr + roundoff = ptr roundoff += 31 roundoff -= roundoff % 32 ptr = roundoff @@ -232,7 +232,7 @@ cdef Histograms *allocate_histograms(ssize_t rows, # a_2 is the offset from the center to each of the octagon # corners # - a = (radius * 2.0 / 2.414213) + a = (radius * 2.0 / 2.414213) a_2 = a / 2 if a_2 == 0: a_2 = 1 @@ -326,17 +326,17 @@ cdef void set_stride(Histograms *ph, SCoord *psc): # a column that is "radius" to the left. # ############################################################################ -cdef inline ssize_t tl_br_colidx(Histograms *ph, ssize_t colidx): +cdef inline Py_ssize_t tl_br_colidx(Histograms *ph, Py_ssize_t colidx): return (colidx + 3*ph.radius + ph.current_row) % ph.stripe_length -cdef inline ssize_t tr_bl_colidx(Histograms *ph, ssize_t colidx): +cdef inline Py_ssize_t tr_bl_colidx(Histograms *ph, Py_ssize_t colidx): return (colidx + 3*ph.radius + ph.row_count-ph.current_row) % \ ph.stripe_length -cdef inline ssize_t leading_edge_colidx(Histograms *ph, ssize_t colidx): +cdef inline Py_ssize_t leading_edge_colidx(Histograms *ph, Py_ssize_t colidx): return (colidx + 5*ph.radius) % ph.stripe_length -cdef inline ssize_t trailing_edge_colidx(Histograms *ph, ssize_t colidx): +cdef inline Py_ssize_t trailing_edge_colidx(Histograms *ph, Py_ssize_t colidx): return (colidx + 3*ph.radius - 1) % ph.stripe_length ############################################################################ @@ -348,8 +348,8 @@ cdef inline ssize_t trailing_edge_colidx(Histograms *ph, ssize_t colidx): # colidx - the index of the column to add # ############################################################################ -cdef inline void accumulate_coarse_histogram(Histograms *ph, ssize_t colidx): - cdef ssize_t offset +cdef inline void accumulate_coarse_histogram(Histograms *ph, Py_ssize_t colidx): + cdef Py_ssize_t offset offset = tr_bl_colidx(ph, colidx) if ph.pixel_count[offset].top_right > 0: @@ -370,8 +370,8 @@ cdef inline void accumulate_coarse_histogram(Histograms *ph, ssize_t colidx): # for a given column # ############################################################################ -cdef inline void deaccumulate_coarse_histogram(Histograms *ph, ssize_t colidx): - cdef ssize_t offset +cdef inline void deaccumulate_coarse_histogram(Histograms *ph, Py_ssize_t colidx): + cdef Py_ssize_t offset # # The trailing diagonals don't appear until here # @@ -400,11 +400,11 @@ cdef inline void deaccumulate_coarse_histogram(Histograms *ph, ssize_t colidx): # ############################################################################ cdef inline void accumulate_fine_histogram(Histograms *ph, - ssize_t colidx, - ssize_t fineidx): + Py_ssize_t colidx, + Py_ssize_t fineidx): cdef: - ssize_t fineoffset = fineidx * 16 - ssize_t offset + Py_ssize_t fineoffset = fineidx * 16 + Py_ssize_t offset offset = tr_bl_colidx(ph, colidx) add16(ph.accumulator.fine + fineoffset, @@ -424,11 +424,11 @@ cdef inline void accumulate_fine_histogram(Histograms *ph, # ############################################################################ cdef inline void deaccumulate_fine_histogram(Histograms *ph, - ssize_t colidx, - ssize_t fineidx): + Py_ssize_t colidx, + Py_ssize_t fineidx): cdef: - ssize_t fineoffset = fineidx * 16 - ssize_t offset + Py_ssize_t fineoffset = fineidx * 16 + Py_ssize_t offset # # The trailing diagonals don't appear until here @@ -480,11 +480,11 @@ cdef inline void accumulate(Histograms *ph): # to choose remains to be done. ############################################################################ -cdef inline void update_fine(Histograms *ph, ssize_t fineidx): +cdef inline void update_fine(Histograms *ph, Py_ssize_t fineidx): cdef: - ssize_t first_update_column = ph.last_update_column[fineidx]+1 - ssize_t update_limit = ph.current_column+1 - ssize_t i + Py_ssize_t first_update_column = ph.last_update_column[fineidx]+1 + Py_ssize_t update_limit = ph.current_column+1 + Py_ssize_t i for i in range(first_update_column, update_limit): accumulate_fine_histogram(ph, i, fineidx) @@ -509,15 +509,15 @@ cdef inline void update_histogram(Histograms *ph, SCoord *last_coord, SCoord *coord): cdef: - ssize_t current_column = ph.current_column - ssize_t current_row = ph.current_row - ssize_t current_stride = ph.current_stride - ssize_t column_count = ph.column_count - ssize_t row_count = ph.row_count + Py_ssize_t current_column = ph.current_column + Py_ssize_t current_row = ph.current_row + Py_ssize_t current_stride = ph.current_stride + Py_ssize_t column_count = ph.column_count + Py_ssize_t row_count = ph.row_count np.uint8_t value - ssize_t stride - ssize_t x - ssize_t y + Py_ssize_t stride + Py_ssize_t x + Py_ssize_t y x = last_coord.x + current_column y = last_coord.y + current_row @@ -550,21 +550,21 @@ cdef inline void update_histogram(Histograms *ph, ############################################################################ cdef inline void update_current_location(Histograms *ph): cdef: - ssize_t current_column = ph.current_column - ssize_t radius = ph.radius - ssize_t top_left_off = tl_br_colidx(ph, current_column) - ssize_t top_right_off = tr_bl_colidx(ph, current_column) - ssize_t bottom_left_off = tr_bl_colidx(ph, current_column) - ssize_t bottom_right_off = tl_br_colidx(ph, current_column) - ssize_t leading_edge_off = leading_edge_colidx(ph, current_column) + Py_ssize_t current_column = ph.current_column + Py_ssize_t radius = ph.radius + Py_ssize_t top_left_off = tl_br_colidx(ph, current_column) + Py_ssize_t top_right_off = tr_bl_colidx(ph, current_column) + Py_ssize_t bottom_left_off = tr_bl_colidx(ph, current_column) + Py_ssize_t bottom_right_off = tl_br_colidx(ph, current_column) + Py_ssize_t leading_edge_off = leading_edge_colidx(ph, current_column) np.int32_t *coarse_histogram np.int32_t *fine_histogram - ssize_t last_xoff - ssize_t last_yoff - ssize_t last_stride - ssize_t xoff - ssize_t yoff - ssize_t stride + Py_ssize_t last_xoff + Py_ssize_t last_yoff + Py_ssize_t last_stride + Py_ssize_t xoff + Py_ssize_t yoff + Py_ssize_t stride update_histogram(ph, &ph.histogram[top_left_off].top_left, &ph.pixel_count[top_left_off].top_left, @@ -599,10 +599,10 @@ cdef inline void update_current_location(Histograms *ph): cdef inline np.uint8_t find_median(Histograms *ph): cdef: - ssize_t pixels_below # of pixels below the median - ssize_t i - ssize_t j - ssize_t k + Py_ssize_t pixels_below # of pixels below the median + Py_ssize_t i + Py_ssize_t j + Py_ssize_t k np.uint32_t accumulator if ph.accumulator_count == 0: @@ -644,25 +644,25 @@ cdef inline np.uint8_t find_median(Histograms *ph): # output - array to be filled with filtered pixels # ############################################################################ -cdef int c_median_filter(ssize_t rows, - ssize_t columns, - ssize_t row_stride, - ssize_t col_stride, - ssize_t radius, - ssize_t percent, +cdef int c_median_filter(Py_ssize_t rows, + Py_ssize_t columns, + Py_ssize_t row_stride, + Py_ssize_t col_stride, + Py_ssize_t radius, + Py_ssize_t percent, np.uint8_t *data, np.uint8_t *mask, np.uint8_t *output): cdef: Histograms *ph Histogram *phistogram - ssize_t row - ssize_t col - ssize_t i - ssize_t top_left_off - ssize_t top_right_off - ssize_t bottom_left_off - ssize_t bottom_right_off + Py_ssize_t row + Py_ssize_t col + Py_ssize_t i + Py_ssize_t top_left_off + Py_ssize_t top_right_off + Py_ssize_t bottom_left_off + Py_ssize_t bottom_right_off ph = allocate_histograms(rows, columns, row_stride, col_stride, radius, percent, data, mask, output) diff --git a/skimage/filter/_denoise_cy.pyx b/skimage/filter/_denoise_cy.pyx index 91c09fbe..5a85e84a 100644 --- a/skimage/filter/_denoise_cy.pyx +++ b/skimage/filter/_denoise_cy.pyx @@ -17,7 +17,7 @@ cdef inline double _gaussian_weight(double sigma, double value): return exp(-0.5 * (value / sigma)**2) -cdef double* _compute_color_lut(ssize_t bins, double sigma, double max_value): +cdef double* _compute_color_lut(Py_ssize_t bins, double sigma, double max_value): cdef: double* color_lut = malloc(bins * sizeof(double)) @@ -29,7 +29,7 @@ cdef double* _compute_color_lut(ssize_t bins, double sigma, double max_value): return color_lut -cdef double* _compute_range_lut(ssize_t win_size, double sigma): +cdef double* _compute_range_lut(Py_ssize_t win_size, double sigma): cdef: double* range_lut = malloc(win_size**2 * sizeof(double)) @@ -45,8 +45,8 @@ cdef double* _compute_range_lut(ssize_t win_size, double sigma): return range_lut -def denoise_bilateral(image, ssize_t win_size=5, sigma_range=None, - double sigma_spatial=1, ssize_t bins=10000, +def denoise_bilateral(image, Py_ssize_t win_size=5, sigma_range=None, + double sigma_spatial=1, Py_ssize_t bins=10000, mode='constant', double cval=0): """Denoise image using bilateral filter. @@ -100,10 +100,10 @@ def denoise_bilateral(image, ssize_t win_size=5, sigma_range=None, image = np.atleast_3d(img_as_float(image)) cdef: - ssize_t rows = image.shape[0] - ssize_t cols = image.shape[1] - ssize_t dims = image.shape[2] - ssize_t window_ext = (win_size - 1) / 2 + Py_ssize_t rows = image.shape[0] + Py_ssize_t cols = image.shape[1] + Py_ssize_t dims = image.shape[2] + Py_ssize_t window_ext = (win_size - 1) / 2 double max_value = image.max() @@ -224,14 +224,14 @@ def denoise_tv_bregman(image, double weight, int max_iter=100, double eps=1e-3): image = np.atleast_3d(img_as_float(image)) cdef: - ssize_t rows = image.shape[0] - ssize_t cols = image.shape[1] - ssize_t dims = image.shape[2] - ssize_t rows2 = rows + 2 - ssize_t cols2 = cols + 2 - ssize_t r, c, k + Py_ssize_t rows = image.shape[0] + Py_ssize_t cols = image.shape[1] + Py_ssize_t dims = image.shape[2] + Py_ssize_t rows2 = rows + 2 + Py_ssize_t cols2 = cols + 2 + Py_ssize_t r, c, k - ssize_t total = rows * cols * dims + Py_ssize_t total = rows * cols * dims shape_ext = (rows2, cols2, dims) diff --git a/skimage/filter/rank/_core16.pxd b/skimage/filter/rank/_core16.pxd index 96f34121..5f7de9df 100644 --- a/skimage/filter/rank/_core16.pxd +++ b/skimage/filter/rank/_core16.pxd @@ -6,12 +6,12 @@ cdef int int_min(int a, int b) # 16-bit core kernel receives extra information about data bitdepth -cdef void _core16(np.uint16_t kernel(ssize_t *, float, np.uint16_t, - ssize_t, ssize_t, ssize_t, float, - float, ssize_t, ssize_t), +cdef void _core16(np.uint16_t kernel(Py_ssize_t *, float, np.uint16_t, + Py_ssize_t, Py_ssize_t, Py_ssize_t, float, + float, Py_ssize_t, Py_ssize_t), np.ndarray[np.uint16_t, ndim=2] image, np.ndarray[np.uint8_t, ndim=2] selem, np.ndarray[np.uint8_t, ndim=2] mask, np.ndarray[np.uint16_t, ndim=2] out, - char shift_x, char shift_y, ssize_t bitdepth, - float p0, float p1, ssize_t s0, ssize_t s1) except * + char shift_x, char shift_y, Py_ssize_t bitdepth, + float p0, float p1, Py_ssize_t s0, Py_ssize_t s1) except * diff --git a/skimage/filter/rank/_core16.pyx b/skimage/filter/rank/_core16.pyx index 323692ec..aa959fd0 100644 --- a/skimage/filter/rank/_core16.pyx +++ b/skimage/filter/rank/_core16.pyx @@ -17,38 +17,38 @@ cdef inline int int_min(int a, int b): return a if a <= b else b -cdef inline void histogram_increment(ssize_t * histo, float * pop, +cdef inline void histogram_increment(Py_ssize_t * histo, float * pop, np.uint16_t value): histo[value] += 1 pop[0] += 1 -cdef inline void histogram_decrement(ssize_t * histo, float * pop, +cdef inline void histogram_decrement(Py_ssize_t * histo, float * pop, np.uint16_t value): histo[value] -= 1 pop[0] -= 1 -cdef void _core16(np.uint16_t kernel(ssize_t *, float, np.uint16_t, - ssize_t, ssize_t, ssize_t, float, - float, ssize_t, ssize_t), +cdef void _core16(np.uint16_t kernel(Py_ssize_t *, float, np.uint16_t, + Py_ssize_t, Py_ssize_t, Py_ssize_t, float, + float, Py_ssize_t, Py_ssize_t), np.ndarray[np.uint16_t, ndim=2] image, np.ndarray[np.uint8_t, ndim=2] selem, np.ndarray[np.uint8_t, ndim=2] mask, np.ndarray[np.uint16_t, ndim=2] out, - char shift_x, char shift_y, ssize_t bitdepth, - float p0, float p1, ssize_t s0, ssize_t s1) except *: + char shift_x, char shift_y, Py_ssize_t bitdepth, + float p0, float p1, Py_ssize_t s0, Py_ssize_t s1) except *: """Compute histogram for each pixel neighborhood, apply kernel function and use kernel function return value for output image. """ - cdef ssize_t rows = image.shape[0] - cdef ssize_t cols = image.shape[1] - cdef ssize_t srows = selem.shape[0] - cdef ssize_t scols = selem.shape[1] + cdef Py_ssize_t rows = image.shape[0] + cdef Py_ssize_t cols = image.shape[1] + cdef Py_ssize_t srows = selem.shape[0] + cdef Py_ssize_t scols = selem.shape[1] - cdef ssize_t centre_r = int(selem.shape[0] / 2) + shift_y - cdef ssize_t centre_c = int(selem.shape[1] / 2) + shift_x + cdef Py_ssize_t centre_r = int(selem.shape[0] / 2) + shift_y + cdef Py_ssize_t centre_c = int(selem.shape[1] / 2) + shift_x # check that structuring element center is inside the element bounding box assert centre_r >= 0 @@ -61,8 +61,8 @@ cdef void _core16(np.uint16_t kernel(ssize_t *, float, np.uint16_t, midbin_list = [0, 0, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048] # set maxbin and midbin - cdef ssize_t maxbin = maxbin_list[bitdepth] - cdef ssize_t midbin = midbin_list[bitdepth] + cdef Py_ssize_t maxbin = maxbin_list[bitdepth] + cdef Py_ssize_t midbin = midbin_list[bitdepth] assert (image < maxbin).all() @@ -72,30 +72,30 @@ cdef void _core16(np.uint16_t kernel(ssize_t *, float, np.uint16_t, cdef np.uint8_t * mask_data = mask.data # define local variable types - cdef ssize_t r, c, rr, cc, s, value, local_max, i, even_row + cdef Py_ssize_t r, c, rr, cc, s, value, local_max, i, even_row # number of pixels actually inside the neighborhood (float) cdef float pop # allocate memory with malloc - cdef ssize_t max_se = srows * scols + cdef Py_ssize_t max_se = srows * scols # number of element in each attack border - cdef ssize_t num_se_n, num_se_s, num_se_e, num_se_w + cdef Py_ssize_t num_se_n, num_se_s, num_se_e, num_se_w # the current local histogram distribution - cdef ssize_t * histo = malloc(maxbin * sizeof(ssize_t)) + cdef Py_ssize_t * histo = malloc(maxbin * sizeof(Py_ssize_t)) # these lists contain the relative pixel row and column for each of the 4 # attack borders east, west, north and south e.g. se_e_r lists the rows of # the east structuring element border - cdef ssize_t * se_e_r = malloc(max_se * sizeof(ssize_t)) - cdef ssize_t * se_e_c = malloc(max_se * sizeof(ssize_t)) - cdef ssize_t * se_w_r = malloc(max_se * sizeof(ssize_t)) - cdef ssize_t * se_w_c = malloc(max_se * sizeof(ssize_t)) - cdef ssize_t * se_n_r = malloc(max_se * sizeof(ssize_t)) - cdef ssize_t * se_n_c = malloc(max_se * sizeof(ssize_t)) - cdef ssize_t * se_s_r = malloc(max_se * sizeof(ssize_t)) - cdef ssize_t * se_s_c = malloc(max_se * sizeof(ssize_t)) + cdef Py_ssize_t * se_e_r = malloc(max_se * sizeof(Py_ssize_t)) + cdef Py_ssize_t * se_e_c = malloc(max_se * sizeof(Py_ssize_t)) + cdef Py_ssize_t * se_w_r = malloc(max_se * sizeof(Py_ssize_t)) + cdef Py_ssize_t * se_w_c = malloc(max_se * sizeof(Py_ssize_t)) + cdef Py_ssize_t * se_n_r = malloc(max_se * sizeof(Py_ssize_t)) + cdef Py_ssize_t * se_n_c = malloc(max_se * sizeof(Py_ssize_t)) + cdef Py_ssize_t * se_s_r = malloc(max_se * sizeof(Py_ssize_t)) + cdef Py_ssize_t * se_s_c = malloc(max_se * sizeof(Py_ssize_t)) # build attack and release borders # by using difference along axis diff --git a/skimage/filter/rank/_core8.pxd b/skimage/filter/rank/_core8.pxd index 6ee2e0a3..38236b87 100644 --- a/skimage/filter/rank/_core8.pxd +++ b/skimage/filter/rank/_core8.pxd @@ -5,18 +5,18 @@ cdef np.uint8_t uint8_max(np.uint8_t a, np.uint8_t b) cdef np.uint8_t uint8_min(np.uint8_t a, np.uint8_t b) -cdef np.uint8_t is_in_mask(ssize_t rows, ssize_t cols, - ssize_t r, ssize_t c, +cdef np.uint8_t is_in_mask(Py_ssize_t rows, Py_ssize_t cols, + Py_ssize_t r, Py_ssize_t c, np.uint8_t * mask) # 8-bit core kernel receives extra information about data inferior and superior # percentiles -cdef void _core8(np.uint8_t kernel(ssize_t *, float, np.uint8_t, float, - float, ssize_t, ssize_t), +cdef void _core8(np.uint8_t kernel(Py_ssize_t *, float, np.uint8_t, float, + float, Py_ssize_t, Py_ssize_t), np.ndarray[np.uint8_t, ndim=2] image, np.ndarray[np.uint8_t, ndim=2] selem, np.ndarray[np.uint8_t, ndim=2] mask, np.ndarray[np.uint8_t, ndim=2] out, char shift_x, char shift_y, float p0, float p1, - ssize_t s0, ssize_t s1) except * + Py_ssize_t s0, Py_ssize_t s1) except * diff --git a/skimage/filter/rank/_core8.pyx b/skimage/filter/rank/_core8.pyx index 7dbd4524..9f4bc693 100644 --- a/skimage/filter/rank/_core8.pyx +++ b/skimage/filter/rank/_core8.pyx @@ -16,20 +16,20 @@ cdef inline np.uint8_t uint8_min(np.uint8_t a, np.uint8_t b): return a if a <= b else b -cdef inline void histogram_increment(ssize_t * histo, float * pop, +cdef inline void histogram_increment(Py_ssize_t * histo, float * pop, np.uint8_t value): histo[value] += 1 pop[0] += 1 -cdef inline void histogram_decrement(ssize_t * histo, float * pop, +cdef inline void histogram_decrement(Py_ssize_t * histo, float * pop, np.uint8_t value): histo[value] -= 1 pop[0] -= 1 -cdef inline np.uint8_t is_in_mask(ssize_t rows, ssize_t cols, - ssize_t r, ssize_t c, +cdef inline np.uint8_t is_in_mask(Py_ssize_t rows, Py_ssize_t cols, + Py_ssize_t r, Py_ssize_t c, np.uint8_t * mask): """Check whether given coordinate is within image and mask is true.""" if r < 0 or r > rows - 1 or c < 0 or c > cols - 1: @@ -41,25 +41,25 @@ cdef inline np.uint8_t is_in_mask(ssize_t rows, ssize_t cols, return 0 -cdef void _core8(np.uint8_t kernel(ssize_t *, float, np.uint8_t, float, - float, ssize_t, ssize_t), +cdef void _core8(np.uint8_t kernel(Py_ssize_t *, float, np.uint8_t, float, + float, Py_ssize_t, Py_ssize_t), np.ndarray[np.uint8_t, ndim=2] image, np.ndarray[np.uint8_t, ndim=2] selem, np.ndarray[np.uint8_t, ndim=2] mask, np.ndarray[np.uint8_t, ndim=2] out, char shift_x, char shift_y, float p0, float p1, - ssize_t s0, ssize_t s1) except *: + Py_ssize_t s0, Py_ssize_t s1) except *: """Compute histogram for each pixel neighborhood, apply kernel function and use kernel function return value for output image. """ - cdef ssize_t rows = image.shape[0] - cdef ssize_t cols = image.shape[1] - cdef ssize_t srows = selem.shape[0] - cdef ssize_t scols = selem.shape[1] + cdef Py_ssize_t rows = image.shape[0] + cdef Py_ssize_t cols = image.shape[1] + cdef Py_ssize_t srows = selem.shape[0] + cdef Py_ssize_t scols = selem.shape[1] - cdef ssize_t centre_r = int(selem.shape[0] / 2) + shift_y - cdef ssize_t centre_c = int(selem.shape[1] / 2) + shift_x + cdef Py_ssize_t centre_r = int(selem.shape[0] / 2) + shift_y + cdef Py_ssize_t centre_c = int(selem.shape[1] / 2) + shift_x # check that structuring element center is inside the element bounding box assert centre_r >= 0 @@ -74,31 +74,31 @@ cdef void _core8(np.uint8_t kernel(ssize_t *, float, np.uint8_t, float, cdef np.uint8_t * mask_data = mask.data # define local variable types - cdef ssize_t r, c, rr, cc, s, value, local_max, i, even_row + cdef Py_ssize_t r, c, rr, cc, s, value, local_max, i, even_row # number of pixels actually inside the neighborhood (float) cdef float pop # allocate memory with malloc - cdef ssize_t max_se = srows * scols + cdef Py_ssize_t max_se = srows * scols # number of element in each attack border - cdef ssize_t num_se_n, num_se_s, num_se_e, num_se_w + cdef Py_ssize_t num_se_n, num_se_s, num_se_e, num_se_w # the current local histogram distribution - cdef ssize_t * histo = malloc(256 * sizeof(ssize_t)) + cdef Py_ssize_t * histo = malloc(256 * sizeof(Py_ssize_t)) # these lists contain the relative pixel row and column for each of the 4 # attack borders east, west, north and south e.g. se_e_r lists the rows of # the east structuring element border - cdef ssize_t * se_e_r = malloc(max_se * sizeof(ssize_t)) - cdef ssize_t * se_e_c = malloc(max_se * sizeof(ssize_t)) - cdef ssize_t * se_w_r = malloc(max_se * sizeof(ssize_t)) - cdef ssize_t * se_w_c = malloc(max_se * sizeof(ssize_t)) - cdef ssize_t * se_n_r = malloc(max_se * sizeof(ssize_t)) - cdef ssize_t * se_n_c = malloc(max_se * sizeof(ssize_t)) - cdef ssize_t * se_s_r = malloc(max_se * sizeof(ssize_t)) - cdef ssize_t * se_s_c = malloc(max_se * sizeof(ssize_t)) + cdef Py_ssize_t * se_e_r = malloc(max_se * sizeof(Py_ssize_t)) + cdef Py_ssize_t * se_e_c = malloc(max_se * sizeof(Py_ssize_t)) + cdef Py_ssize_t * se_w_r = malloc(max_se * sizeof(Py_ssize_t)) + cdef Py_ssize_t * se_w_c = malloc(max_se * sizeof(Py_ssize_t)) + cdef Py_ssize_t * se_n_r = malloc(max_se * sizeof(Py_ssize_t)) + cdef Py_ssize_t * se_n_c = malloc(max_se * sizeof(Py_ssize_t)) + cdef Py_ssize_t * se_s_r = malloc(max_se * sizeof(Py_ssize_t)) + cdef Py_ssize_t * se_s_c = malloc(max_se * sizeof(Py_ssize_t)) # build attack and release borders # by using difference along axis diff --git a/skimage/filter/rank/_crank16.pyx b/skimage/filter/rank/_crank16.pyx index b5dda3c2..e81bf81f 100644 --- a/skimage/filter/rank/_crank16.pyx +++ b/skimage/filter/rank/_crank16.pyx @@ -14,12 +14,12 @@ from skimage.filter.rank._core16 cimport _core16 # ----------------------------------------------------------------- -cdef inline np.uint16_t kernel_autolevel(ssize_t * histo, float pop, - np.uint16_t g, ssize_t bitdepth, - ssize_t maxbin, ssize_t midbin, +cdef inline np.uint16_t kernel_autolevel(Py_ssize_t * histo, float pop, + np.uint16_t g, Py_ssize_t bitdepth, + Py_ssize_t maxbin, Py_ssize_t midbin, float p0, float p1, - ssize_t s0, ssize_t s1): - cdef ssize_t i, imin, imax, delta + Py_ssize_t s0, Py_ssize_t s1): + cdef Py_ssize_t i, imin, imax, delta if pop: for i in range(maxbin - 1, -1, -1): @@ -37,12 +37,12 @@ cdef inline np.uint16_t kernel_autolevel(ssize_t * histo, float pop, return < np.uint16_t > (imax - imin) -cdef inline np.uint16_t kernel_bottomhat(ssize_t * histo, float pop, - np.uint16_t g, ssize_t bitdepth, - ssize_t maxbin, ssize_t midbin, +cdef inline np.uint16_t kernel_bottomhat(Py_ssize_t * histo, float pop, + np.uint16_t g, Py_ssize_t bitdepth, + Py_ssize_t maxbin, Py_ssize_t midbin, float p0, float p1, - ssize_t s0, ssize_t s1): - cdef ssize_t i + Py_ssize_t s0, Py_ssize_t s1): + cdef Py_ssize_t i if pop: for i in range(maxbin): @@ -53,12 +53,12 @@ cdef inline np.uint16_t kernel_bottomhat(ssize_t * histo, float pop, else: return < np.uint16_t > (0) -cdef inline np.uint16_t kernel_equalize(ssize_t * histo, float pop, - np.uint16_t g, ssize_t bitdepth, - ssize_t maxbin, ssize_t midbin, +cdef inline np.uint16_t kernel_equalize(Py_ssize_t * histo, float pop, + np.uint16_t g, Py_ssize_t bitdepth, + Py_ssize_t maxbin, Py_ssize_t midbin, float p0, float p1, - ssize_t s0, ssize_t s1): - cdef ssize_t i + Py_ssize_t s0, Py_ssize_t s1): + cdef Py_ssize_t i cdef float sum = 0. if pop: @@ -72,12 +72,12 @@ cdef inline np.uint16_t kernel_equalize(ssize_t * histo, float pop, return < np.uint16_t > (0) -cdef inline np.uint16_t kernel_gradient(ssize_t * histo, float pop, - np.uint16_t g, ssize_t bitdepth, - ssize_t maxbin, ssize_t midbin, +cdef inline np.uint16_t kernel_gradient(Py_ssize_t * histo, float pop, + np.uint16_t g, Py_ssize_t bitdepth, + Py_ssize_t maxbin, Py_ssize_t midbin, float p0, float p1, - ssize_t s0, ssize_t s1): - cdef ssize_t i, imin, imax + Py_ssize_t s0, Py_ssize_t s1): + cdef Py_ssize_t i, imin, imax if pop: for i in range(maxbin - 1, -1, -1): @@ -93,12 +93,12 @@ cdef inline np.uint16_t kernel_gradient(ssize_t * histo, float pop, return < np.uint16_t > (0) -cdef inline np.uint16_t kernel_maximum(ssize_t * histo, float pop, - np.uint16_t g, ssize_t bitdepth, - ssize_t maxbin, ssize_t midbin, +cdef inline np.uint16_t kernel_maximum(Py_ssize_t * histo, float pop, + np.uint16_t g, Py_ssize_t bitdepth, + Py_ssize_t maxbin, Py_ssize_t midbin, float p0, float p1, - ssize_t s0, ssize_t s1): - cdef ssize_t i + Py_ssize_t s0, Py_ssize_t s1): + cdef Py_ssize_t i if pop: for i in range(maxbin - 1, -1, -1): @@ -108,12 +108,12 @@ cdef inline np.uint16_t kernel_maximum(ssize_t * histo, float pop, return < np.uint16_t > (0) -cdef inline np.uint16_t kernel_mean(ssize_t * histo, float pop, - np.uint16_t g, ssize_t bitdepth, - ssize_t maxbin, ssize_t midbin, +cdef inline np.uint16_t kernel_mean(Py_ssize_t * histo, float pop, + np.uint16_t g, Py_ssize_t bitdepth, + Py_ssize_t maxbin, Py_ssize_t midbin, float p0, float p1, - ssize_t s0, ssize_t s1): - cdef ssize_t i + Py_ssize_t s0, Py_ssize_t s1): + cdef Py_ssize_t i cdef float mean = 0. if pop: @@ -124,15 +124,15 @@ cdef inline np.uint16_t kernel_mean(ssize_t * histo, float pop, return < np.uint16_t > (0) -cdef inline np.uint16_t kernel_meansubstraction(ssize_t * histo, +cdef inline np.uint16_t kernel_meansubstraction(Py_ssize_t * histo, float pop, np.uint16_t g, - ssize_t bitdepth, - ssize_t maxbin, - ssize_t midbin, + Py_ssize_t bitdepth, + Py_ssize_t maxbin, + Py_ssize_t midbin, float p0, float p1, - ssize_t s0, ssize_t s1): - cdef ssize_t i + Py_ssize_t s0, Py_ssize_t s1): + cdef Py_ssize_t i cdef float mean = 0. if pop: @@ -143,12 +143,12 @@ cdef inline np.uint16_t kernel_meansubstraction(ssize_t * histo, return < np.uint16_t > (0) -cdef inline np.uint16_t kernel_median(ssize_t * histo, float pop, - np.uint16_t g, ssize_t bitdepth, - ssize_t maxbin, ssize_t midbin, +cdef inline np.uint16_t kernel_median(Py_ssize_t * histo, float pop, + np.uint16_t g, Py_ssize_t bitdepth, + Py_ssize_t maxbin, Py_ssize_t midbin, float p0, float p1, - ssize_t s0, ssize_t s1): - cdef ssize_t i + Py_ssize_t s0, Py_ssize_t s1): + cdef Py_ssize_t i cdef float sum = pop / 2.0 if pop: @@ -161,12 +161,12 @@ cdef inline np.uint16_t kernel_median(ssize_t * histo, float pop, return < np.uint16_t > (0) -cdef inline np.uint16_t kernel_minimum(ssize_t * histo, float pop, - np.uint16_t g, ssize_t bitdepth, - ssize_t maxbin, ssize_t midbin, +cdef inline np.uint16_t kernel_minimum(Py_ssize_t * histo, float pop, + np.uint16_t g, Py_ssize_t bitdepth, + Py_ssize_t maxbin, Py_ssize_t midbin, float p0, float p1, - ssize_t s0, ssize_t s1): - cdef ssize_t i + Py_ssize_t s0, Py_ssize_t s1): + cdef Py_ssize_t i if pop: for i in range(maxbin): @@ -176,12 +176,12 @@ cdef inline np.uint16_t kernel_minimum(ssize_t * histo, float pop, return < np.uint16_t > (0) -cdef inline np.uint16_t kernel_modal(ssize_t * histo, float pop, - np.uint16_t g, ssize_t bitdepth, - ssize_t maxbin, ssize_t midbin, +cdef inline np.uint16_t kernel_modal(Py_ssize_t * histo, float pop, + np.uint16_t g, Py_ssize_t bitdepth, + Py_ssize_t maxbin, Py_ssize_t midbin, float p0, float p1, - ssize_t s0, ssize_t s1): - cdef ssize_t hmax = 0, imax = 0 + Py_ssize_t s0, Py_ssize_t s1): + cdef Py_ssize_t hmax = 0, imax = 0 if pop: for i in range(maxbin): @@ -193,15 +193,15 @@ cdef inline np.uint16_t kernel_modal(ssize_t * histo, float pop, return < np.uint16_t > (0) -cdef inline np.uint16_t kernel_morph_contr_enh(ssize_t * histo, +cdef inline np.uint16_t kernel_morph_contr_enh(Py_ssize_t * histo, float pop, np.uint16_t g, - ssize_t bitdepth, - ssize_t maxbin, - ssize_t midbin, + Py_ssize_t bitdepth, + Py_ssize_t maxbin, + Py_ssize_t midbin, float p0, float p1, - ssize_t s0, ssize_t s1): - cdef ssize_t i, imin, imax + Py_ssize_t s0, Py_ssize_t s1): + cdef Py_ssize_t i, imin, imax if pop: for i in range(maxbin - 1, -1, -1): @@ -220,20 +220,20 @@ cdef inline np.uint16_t kernel_morph_contr_enh(ssize_t * histo, return < np.uint16_t > (0) -cdef inline np.uint16_t kernel_pop(ssize_t * histo, float pop, - np.uint16_t g, ssize_t bitdepth, - ssize_t maxbin, ssize_t midbin, +cdef inline np.uint16_t kernel_pop(Py_ssize_t * histo, float pop, + np.uint16_t g, Py_ssize_t bitdepth, + Py_ssize_t maxbin, Py_ssize_t midbin, float p0, float p1, - ssize_t s0, ssize_t s1): + Py_ssize_t s0, Py_ssize_t s1): return < np.uint16_t > (pop) -cdef inline np.uint16_t kernel_threshold(ssize_t * histo, float pop, - np.uint16_t g, ssize_t bitdepth, - ssize_t maxbin, ssize_t midbin, +cdef inline np.uint16_t kernel_threshold(Py_ssize_t * histo, float pop, + np.uint16_t g, Py_ssize_t bitdepth, + Py_ssize_t maxbin, Py_ssize_t midbin, float p0, float p1, - ssize_t s0, ssize_t s1): - cdef ssize_t i + Py_ssize_t s0, Py_ssize_t s1): + cdef Py_ssize_t i cdef float mean = 0. if pop: @@ -244,12 +244,12 @@ cdef inline np.uint16_t kernel_threshold(ssize_t * histo, float pop, return < np.uint16_t > (0) -cdef inline np.uint16_t kernel_tophat(ssize_t * histo, float pop, - np.uint16_t g, ssize_t bitdepth, - ssize_t maxbin, ssize_t midbin, +cdef inline np.uint16_t kernel_tophat(Py_ssize_t * histo, float pop, + np.uint16_t g, Py_ssize_t bitdepth, + Py_ssize_t maxbin, Py_ssize_t midbin, float p0, float p1, - ssize_t s0, ssize_t s1): - cdef ssize_t i + Py_ssize_t s0, Py_ssize_t s1): + cdef Py_ssize_t i if pop: for i in range(maxbin - 1, -1, -1): @@ -260,12 +260,12 @@ cdef inline np.uint16_t kernel_tophat(ssize_t * histo, float pop, else: return < np.uint16_t > (0) -cdef inline np.uint16_t kernel_entropy(ssize_t * histo, float pop, - np.uint16_t g, ssize_t bitdepth, - ssize_t maxbin, ssize_t midbin, +cdef inline np.uint16_t kernel_entropy(Py_ssize_t * histo, float pop, + np.uint16_t g, Py_ssize_t bitdepth, + Py_ssize_t maxbin, Py_ssize_t midbin, float p0, float p1, - ssize_t s0, ssize_t s1): - cdef ssize_t i + Py_ssize_t s0, Py_ssize_t s1): + cdef Py_ssize_t i cdef float e, p if pop: @@ -289,132 +289,132 @@ def autolevel(np.ndarray[np.uint16_t, ndim=2] image, np.ndarray[np.uint8_t, ndim=2] selem, np.ndarray[np.uint8_t, ndim=2] mask=None, np.ndarray[np.uint16_t, ndim=2] out=None, - char shift_x=0, char shift_y=0, ssize_t bitdepth=8): + char shift_x=0, char shift_y=0, Py_ssize_t bitdepth=8): _core16(kernel_autolevel, image, selem, mask, out, shift_x, shift_y, - bitdepth, 0, 0, 0, 0) + bitdepth, 0, 0, 0, 0) def bottomhat(np.ndarray[np.uint16_t, ndim=2] image, np.ndarray[np.uint8_t, ndim=2] selem, np.ndarray[np.uint8_t, ndim=2] mask=None, np.ndarray[np.uint16_t, ndim=2] out=None, - char shift_x=0, char shift_y=0, ssize_t bitdepth=8): + char shift_x=0, char shift_y=0, Py_ssize_t bitdepth=8): _core16(kernel_bottomhat, image, selem, mask, out, shift_x, shift_y, - bitdepth, 0, 0, 0, 0) + bitdepth, 0, 0, 0, 0) def equalize(np.ndarray[np.uint16_t, ndim=2] image, np.ndarray[np.uint8_t, ndim=2] selem, np.ndarray[np.uint8_t, ndim=2] mask=None, np.ndarray[np.uint16_t, ndim=2] out=None, - char shift_x=0, char shift_y=0, ssize_t bitdepth=8): + char shift_x=0, char shift_y=0, Py_ssize_t bitdepth=8): _core16(kernel_equalize, image, selem, mask, out, shift_x, shift_y, - bitdepth, 0, 0, 0, 0) + bitdepth, 0, 0, 0, 0) def gradient(np.ndarray[np.uint16_t, ndim=2] image, np.ndarray[np.uint8_t, ndim=2] selem, np.ndarray[np.uint8_t, ndim=2] mask=None, np.ndarray[np.uint16_t, ndim=2] out=None, - char shift_x=0, char shift_y=0, ssize_t bitdepth=8): + char shift_x=0, char shift_y=0, Py_ssize_t bitdepth=8): _core16(kernel_gradient, image, selem, mask, out, shift_x, shift_y, - bitdepth, 0, 0, 0, 0) + bitdepth, 0, 0, 0, 0) def maximum(np.ndarray[np.uint16_t, ndim=2] image, np.ndarray[np.uint8_t, ndim=2] selem, np.ndarray[np.uint8_t, ndim=2] mask=None, np.ndarray[np.uint16_t, ndim=2] out=None, - char shift_x=0, char shift_y=0, ssize_t bitdepth=8): + char shift_x=0, char shift_y=0, Py_ssize_t bitdepth=8): _core16(kernel_maximum, image, selem, mask, out, shift_x, shift_y, - bitdepth, 0, 0, 0, 0) + bitdepth, 0, 0, 0, 0) def mean(np.ndarray[np.uint16_t, ndim=2] image, np.ndarray[np.uint8_t, ndim=2] selem, np.ndarray[np.uint8_t, ndim=2] mask=None, np.ndarray[np.uint16_t, ndim=2] out=None, - char shift_x=0, char shift_y=0, ssize_t bitdepth=8): + char shift_x=0, char shift_y=0, Py_ssize_t bitdepth=8): _core16(kernel_mean, image, selem, mask, out, shift_x, shift_y, - bitdepth, 0, 0, 0, 0) + bitdepth, 0, 0, 0, 0) def meansubstraction(np.ndarray[np.uint16_t, ndim=2] image, np.ndarray[np.uint8_t, ndim=2] selem, np.ndarray[np.uint8_t, ndim=2] mask=None, np.ndarray[np.uint16_t, ndim=2] out=None, - char shift_x=0, char shift_y=0, ssize_t bitdepth=8): + char shift_x=0, char shift_y=0, Py_ssize_t bitdepth=8): _core16(kernel_meansubstraction, image, selem, mask, out, shift_x, shift_y, - bitdepth, 0, 0, 0, 0) + bitdepth, 0, 0, 0, 0) def median(np.ndarray[np.uint16_t, ndim=2] image, np.ndarray[np.uint8_t, ndim=2] selem, np.ndarray[np.uint8_t, ndim=2] mask=None, np.ndarray[np.uint16_t, ndim=2] out=None, - char shift_x=0, char shift_y=0, ssize_t bitdepth=8): + char shift_x=0, char shift_y=0, Py_ssize_t bitdepth=8): _core16(kernel_median, image, selem, mask, out, shift_x, shift_y, - bitdepth, 0, 0, 0, 0) + bitdepth, 0, 0, 0, 0) def minimum(np.ndarray[np.uint16_t, ndim=2] image, np.ndarray[np.uint8_t, ndim=2] selem, np.ndarray[np.uint8_t, ndim=2] mask=None, np.ndarray[np.uint16_t, ndim=2] out=None, - char shift_x=0, char shift_y=0, ssize_t bitdepth=8): + char shift_x=0, char shift_y=0, Py_ssize_t bitdepth=8): _core16(kernel_minimum, image, selem, mask, out, shift_x, shift_y, - bitdepth, 0, 0, 0, 0) + bitdepth, 0, 0, 0, 0) def morph_contr_enh(np.ndarray[np.uint16_t, ndim=2] image, np.ndarray[np.uint8_t, ndim=2] selem, np.ndarray[np.uint8_t, ndim=2] mask=None, np.ndarray[np.uint16_t, ndim=2] out=None, - char shift_x=0, char shift_y=0, ssize_t bitdepth=8): + char shift_x=0, char shift_y=0, Py_ssize_t bitdepth=8): _core16(kernel_morph_contr_enh, image, selem, mask, out, shift_x, shift_y, - bitdepth, 0, 0, 0, 0) + bitdepth, 0, 0, 0, 0) def modal(np.ndarray[np.uint16_t, ndim=2] image, np.ndarray[np.uint8_t, ndim=2] selem, np.ndarray[np.uint8_t, ndim=2] mask=None, np.ndarray[np.uint16_t, ndim=2] out=None, - char shift_x=0, char shift_y=0, ssize_t bitdepth=8): + char shift_x=0, char shift_y=0, Py_ssize_t bitdepth=8): _core16(kernel_modal, image, selem, mask, out, shift_x, shift_y, - bitdepth, 0, 0, 0, 0) + bitdepth, 0, 0, 0, 0) def pop(np.ndarray[np.uint16_t, ndim=2] image, np.ndarray[np.uint8_t, ndim=2] selem, np.ndarray[np.uint8_t, ndim=2] mask=None, np.ndarray[np.uint16_t, ndim=2] out=None, - char shift_x=0, char shift_y=0, ssize_t bitdepth=8): + char shift_x=0, char shift_y=0, Py_ssize_t bitdepth=8): _core16(kernel_pop, image, selem, mask, out, shift_x, shift_y, - bitdepth, 0, 0, 0, 0) + bitdepth, 0, 0, 0, 0) def threshold(np.ndarray[np.uint16_t, ndim=2] image, np.ndarray[np.uint8_t, ndim=2] selem, np.ndarray[np.uint8_t, ndim=2] mask=None, np.ndarray[np.uint16_t, ndim=2] out=None, - char shift_x=0, char shift_y=0, ssize_t bitdepth=8): + char shift_x=0, char shift_y=0, Py_ssize_t bitdepth=8): _core16(kernel_threshold, image, selem, mask, out, shift_x, shift_y, - bitdepth, 0, 0, 0, 0) + bitdepth, 0, 0, 0, 0) def tophat(np.ndarray[np.uint16_t, ndim=2] image, np.ndarray[np.uint8_t, ndim=2] selem, np.ndarray[np.uint8_t, ndim=2] mask=None, np.ndarray[np.uint16_t, ndim=2] out=None, - char shift_x=0, char shift_y=0, ssize_t bitdepth=8): + char shift_x=0, char shift_y=0, Py_ssize_t bitdepth=8): _core16(kernel_tophat, image, selem, mask, out, shift_x, shift_y, - bitdepth, 0, 0, 0, 0) + bitdepth, 0, 0, 0, 0) def entropy(np.ndarray[np.uint16_t, ndim=2] image, np.ndarray[np.uint8_t, ndim=2] selem, np.ndarray[np.uint8_t, ndim=2] mask=None, np.ndarray[np.uint16_t, ndim=2] out=None, - char shift_x=0, char shift_y=0, ssize_t bitdepth=8): + char shift_x=0, char shift_y=0, Py_ssize_t bitdepth=8): _core16(kernel_entropy, image, selem, mask, out, shift_x, shift_y, - bitdepth, 0, 0, 0, 0) + bitdepth, 0, 0, 0, 0) diff --git a/skimage/filter/rank/_crank16_bilateral.pyx b/skimage/filter/rank/_crank16_bilateral.pyx index 82236d63..c71ccc5c 100644 --- a/skimage/filter/rank/_crank16_bilateral.pyx +++ b/skimage/filter/rank/_crank16_bilateral.pyx @@ -13,11 +13,11 @@ from skimage.filter.rank._core16 cimport _core16 # ----------------------------------------------------------------- -cdef inline np.uint16_t kernel_mean(ssize_t * histo, float pop, - np.uint16_t g, ssize_t bitdepth, - ssize_t maxbin, ssize_t midbin, +cdef inline np.uint16_t kernel_mean(Py_ssize_t * histo, float pop, + np.uint16_t g, Py_ssize_t bitdepth, + Py_ssize_t maxbin, Py_ssize_t midbin, float p0, float p1, - ssize_t s0, ssize_t s1): + Py_ssize_t s0, Py_ssize_t s1): cdef int i, bilat_pop = 0 cdef float mean = 0. @@ -35,11 +35,11 @@ cdef inline np.uint16_t kernel_mean(ssize_t * histo, float pop, return < np.uint16_t > (0) -cdef inline np.uint16_t kernel_pop(ssize_t * histo, float pop, - np.uint16_t g, ssize_t bitdepth, - ssize_t maxbin, ssize_t midbin, +cdef inline np.uint16_t kernel_pop(Py_ssize_t * histo, float pop, + np.uint16_t g, Py_ssize_t bitdepth, + Py_ssize_t maxbin, Py_ssize_t midbin, float p0, float p1, - ssize_t s0, ssize_t s1): + Py_ssize_t s0, Py_ssize_t s1): cdef int i, bilat_pop = 0 diff --git a/skimage/filter/rank/_crank16_percentiles.pyx b/skimage/filter/rank/_crank16_percentiles.pyx index e8836b5b..220d2386 100644 --- a/skimage/filter/rank/_crank16_percentiles.pyx +++ b/skimage/filter/rank/_crank16_percentiles.pyx @@ -13,11 +13,11 @@ from skimage.filter.rank._core16 cimport _core16, int_min, int_max # ----------------------------------------------------------------- -cdef inline np.uint16_t kernel_autolevel(ssize_t * histo, float pop, - np.uint16_t g, ssize_t bitdepth, - ssize_t maxbin, ssize_t midbin, +cdef inline np.uint16_t kernel_autolevel(Py_ssize_t * histo, float pop, + np.uint16_t g, Py_ssize_t bitdepth, + Py_ssize_t maxbin, Py_ssize_t midbin, float p0, float p1, - ssize_t s0, ssize_t s1): + Py_ssize_t s0, Py_ssize_t s1): cdef int i, imin, imax, sum, delta @@ -46,11 +46,11 @@ cdef inline np.uint16_t kernel_autolevel(ssize_t * histo, float pop, return < np.uint16_t > (0) -cdef inline np.uint16_t kernel_gradient(ssize_t * histo, float pop, - np.uint16_t g, ssize_t bitdepth, - ssize_t maxbin, ssize_t midbin, +cdef inline np.uint16_t kernel_gradient(Py_ssize_t * histo, float pop, + np.uint16_t g, Py_ssize_t bitdepth, + Py_ssize_t maxbin, Py_ssize_t midbin, float p0, float p1, - ssize_t s0, ssize_t s1): + Py_ssize_t s0, Py_ssize_t s1): cdef int i, imin, imax, sum, delta @@ -74,11 +74,11 @@ cdef inline np.uint16_t kernel_gradient(ssize_t * histo, float pop, return < np.uint16_t > (0) -cdef inline np.uint16_t kernel_mean(ssize_t * histo, float pop, - np.uint16_t g, ssize_t bitdepth, - ssize_t maxbin, ssize_t midbin, +cdef inline np.uint16_t kernel_mean(Py_ssize_t * histo, float pop, + np.uint16_t g, Py_ssize_t bitdepth, + Py_ssize_t maxbin, Py_ssize_t midbin, float p0, float p1, - ssize_t s0, ssize_t s1): + Py_ssize_t s0, Py_ssize_t s1): cdef int i, sum, mean, n @@ -100,14 +100,14 @@ cdef inline np.uint16_t kernel_mean(ssize_t * histo, float pop, return < np.uint16_t > (0) -cdef inline np.uint16_t kernel_mean_substraction(ssize_t * histo, +cdef inline np.uint16_t kernel_mean_substraction(Py_ssize_t * histo, float pop, np.uint16_t g, - ssize_t bitdepth, - ssize_t maxbin, - ssize_t midbin, + Py_ssize_t bitdepth, + Py_ssize_t maxbin, + Py_ssize_t midbin, float p0, float p1, - ssize_t s0, ssize_t s1): + Py_ssize_t s0, Py_ssize_t s1): cdef int i, sum, mean, n @@ -128,14 +128,14 @@ cdef inline np.uint16_t kernel_mean_substraction(ssize_t * histo, return < np.uint16_t > (0) -cdef inline np.uint16_t kernel_morph_contr_enh(ssize_t * histo, +cdef inline np.uint16_t kernel_morph_contr_enh(Py_ssize_t * histo, float pop, np.uint16_t g, - ssize_t bitdepth, - ssize_t maxbin, - ssize_t midbin, + Py_ssize_t bitdepth, + Py_ssize_t maxbin, + Py_ssize_t midbin, float p0, float p1, - ssize_t s0, ssize_t s1): + Py_ssize_t s0, Py_ssize_t s1): cdef int i, imin, imax, sum, delta @@ -165,11 +165,11 @@ cdef inline np.uint16_t kernel_morph_contr_enh(ssize_t * histo, return < np.uint16_t > (0) -cdef inline np.uint16_t kernel_percentile(ssize_t * histo, float pop, - np.uint16_t g, ssize_t bitdepth, - ssize_t maxbin, ssize_t midbin, +cdef inline np.uint16_t kernel_percentile(Py_ssize_t * histo, float pop, + np.uint16_t g, Py_ssize_t bitdepth, + Py_ssize_t maxbin, Py_ssize_t midbin, float p0, float p1, - ssize_t s0, ssize_t s1): + Py_ssize_t s0, Py_ssize_t s1): cdef int i cdef float sum = 0. @@ -185,11 +185,11 @@ cdef inline np.uint16_t kernel_percentile(ssize_t * histo, float pop, return < np.uint16_t > (0) -cdef inline np.uint16_t kernel_pop(ssize_t * histo, float pop, - np.uint16_t g, ssize_t bitdepth, - ssize_t maxbin, ssize_t midbin, +cdef inline np.uint16_t kernel_pop(Py_ssize_t * histo, float pop, + np.uint16_t g, Py_ssize_t bitdepth, + Py_ssize_t maxbin, Py_ssize_t midbin, float p0, float p1, - ssize_t s0, ssize_t s1): + Py_ssize_t s0, Py_ssize_t s1): cdef int i, sum, n @@ -205,11 +205,11 @@ cdef inline np.uint16_t kernel_pop(ssize_t * histo, float pop, return < np.uint16_t > (0) -cdef inline np.uint16_t kernel_threshold(ssize_t * histo, float pop, - np.uint16_t g, ssize_t bitdepth, - ssize_t maxbin, ssize_t midbin, +cdef inline np.uint16_t kernel_threshold(Py_ssize_t * histo, float pop, + np.uint16_t g, Py_ssize_t bitdepth, + Py_ssize_t maxbin, Py_ssize_t midbin, float p0, float p1, - ssize_t s0, ssize_t s1): + Py_ssize_t s0, Py_ssize_t s1): cdef int i cdef float sum = 0. @@ -239,7 +239,7 @@ def autolevel(np.ndarray[np.uint16_t, ndim=2] image, """bottom hat """ _core16(kernel_autolevel, image, selem, mask, out, shift_x, shift_y, - bitdepth, p0, p1, 0, 0) + bitdepth, p0, p1, 0, 0) def gradient(np.ndarray[np.uint16_t, ndim=2] image, @@ -251,7 +251,7 @@ def gradient(np.ndarray[np.uint16_t, ndim=2] image, """return p0,p1 percentile gradient """ _core16(kernel_gradient, image, selem, mask, out, shift_x, shift_y, - bitdepth, p0, p1, 0, 0) + bitdepth, p0, p1, 0, 0) def mean(np.ndarray[np.uint16_t, ndim=2] image, @@ -263,7 +263,7 @@ def mean(np.ndarray[np.uint16_t, ndim=2] image, """return mean between [p0 and p1] percentiles """ _core16(kernel_mean, image, selem, mask, out, shift_x, shift_y, - bitdepth, p0, p1, 0, 0) + bitdepth, p0, p1, 0, 0) def mean_substraction(np.ndarray[np.uint16_t, ndim=2] image, @@ -276,7 +276,7 @@ def mean_substraction(np.ndarray[np.uint16_t, ndim=2] image, """ _core16( kernel_mean_substraction, image, selem, mask, out, shift_x, shift_y, - bitdepth, p0, p1, 0, 0) + bitdepth, p0, p1, 0, 0) def morph_contr_enh(np.ndarray[np.uint16_t, ndim=2] image, @@ -288,7 +288,7 @@ def morph_contr_enh(np.ndarray[np.uint16_t, ndim=2] image, """reforce contrast using percentiles """ _core16(kernel_morph_contr_enh, image, selem, mask, out, shift_x, shift_y, - bitdepth, p0, p1, 0, 0) + bitdepth, p0, p1, 0, 0) def percentile(np.ndarray[np.uint16_t, ndim=2] image, @@ -300,7 +300,7 @@ def percentile(np.ndarray[np.uint16_t, ndim=2] image, """return p0 percentile """ _core16(kernel_percentile, image, selem, mask, out, shift_x, shift_y, - bitdepth, p0, p1, 0, 0) + bitdepth, p0, p1, 0, 0) def pop(np.ndarray[np.uint16_t, ndim=2] image, @@ -312,7 +312,7 @@ def pop(np.ndarray[np.uint16_t, ndim=2] image, """return nb of pixels between [p0 and p1] """ _core16(kernel_pop, image, selem, mask, out, shift_x, shift_y, - bitdepth, p0, p1, 0, 0) + bitdepth, p0, p1, 0, 0) def threshold(np.ndarray[np.uint16_t, ndim=2] image, @@ -324,4 +324,4 @@ def threshold(np.ndarray[np.uint16_t, ndim=2] image, """return (maxbin-1) if g > percentile p0 """ _core16(kernel_threshold, image, selem, mask, out, shift_x, shift_y, - bitdepth, p0, p1, 0, 0) + bitdepth, p0, p1, 0, 0) diff --git a/skimage/filter/rank/_crank8.pyx b/skimage/filter/rank/_crank8.pyx index 582bcbb7..8bff9703 100644 --- a/skimage/filter/rank/_crank8.pyx +++ b/skimage/filter/rank/_crank8.pyx @@ -14,11 +14,11 @@ from skimage.filter.rank._core8 cimport _core8 # ----------------------------------------------------------------- -cdef inline np.uint8_t kernel_autolevel(ssize_t * histo, float pop, +cdef inline np.uint8_t kernel_autolevel(Py_ssize_t * histo, float pop, np.uint8_t g, float p0, float p1, - ssize_t s0, ssize_t s1): + Py_ssize_t s0, Py_ssize_t s1): - cdef ssize_t i, imin, imax, delta + cdef Py_ssize_t i, imin, imax, delta if pop: for i in range(255, -1, -1): @@ -38,11 +38,11 @@ cdef inline np.uint8_t kernel_autolevel(ssize_t * histo, float pop, return < np.uint8_t > (0) -cdef inline np.uint8_t kernel_bottomhat(ssize_t * histo, float pop, +cdef inline np.uint8_t kernel_bottomhat(Py_ssize_t * histo, float pop, np.uint8_t g, float p0, float p1, - ssize_t s0, ssize_t s1): + Py_ssize_t s0, Py_ssize_t s1): - cdef ssize_t i + cdef Py_ssize_t i if pop: for i in range(256): @@ -54,11 +54,11 @@ cdef inline np.uint8_t kernel_bottomhat(ssize_t * histo, float pop, return < np.uint8_t > (0) -cdef inline np.uint8_t kernel_equalize(ssize_t * histo, float pop, +cdef inline np.uint8_t kernel_equalize(Py_ssize_t * histo, float pop, np.uint8_t g, float p0, float p1, - ssize_t s0, ssize_t s1): + Py_ssize_t s0, Py_ssize_t s1): - cdef ssize_t i + cdef Py_ssize_t i cdef float sum = 0. if pop: @@ -72,11 +72,11 @@ cdef inline np.uint8_t kernel_equalize(ssize_t * histo, float pop, return < np.uint8_t > (0) -cdef inline np.uint8_t kernel_gradient(ssize_t * histo, float pop, +cdef inline np.uint8_t kernel_gradient(Py_ssize_t * histo, float pop, np.uint8_t g, float p0, float p1, - ssize_t s0, ssize_t s1): + Py_ssize_t s0, Py_ssize_t s1): - cdef ssize_t i, imin, imax + cdef Py_ssize_t i, imin, imax if pop: for i in range(255, -1, -1): @@ -92,11 +92,11 @@ cdef inline np.uint8_t kernel_gradient(ssize_t * histo, float pop, return < np.uint8_t > (0) -cdef inline np.uint8_t kernel_maximum(ssize_t * histo, float pop, +cdef inline np.uint8_t kernel_maximum(Py_ssize_t * histo, float pop, np.uint8_t g, float p0, float p1, - ssize_t s0, ssize_t s1): + Py_ssize_t s0, Py_ssize_t s1): - cdef ssize_t i + cdef Py_ssize_t i if pop: for i in range(255, -1, -1): @@ -106,11 +106,11 @@ cdef inline np.uint8_t kernel_maximum(ssize_t * histo, float pop, return < np.uint8_t > (0) -cdef inline np.uint8_t kernel_mean(ssize_t * histo, float pop, +cdef inline np.uint8_t kernel_mean(Py_ssize_t * histo, float pop, np.uint8_t g, float p0, float p1, - ssize_t s0, ssize_t s1): + Py_ssize_t s0, Py_ssize_t s1): - cdef ssize_t i + cdef Py_ssize_t i cdef float mean = 0. if pop: @@ -121,11 +121,11 @@ cdef inline np.uint8_t kernel_mean(ssize_t * histo, float pop, return < np.uint8_t > (0) -cdef inline np.uint8_t kernel_meansubstraction(ssize_t * histo, float pop, +cdef inline np.uint8_t kernel_meansubstraction(Py_ssize_t * histo, float pop, np.uint8_t g, float p0, float p1, - ssize_t s0, ssize_t s1): + Py_ssize_t s0, Py_ssize_t s1): - cdef ssize_t i + cdef Py_ssize_t i cdef float mean = 0. if pop: @@ -136,11 +136,11 @@ cdef inline np.uint8_t kernel_meansubstraction(ssize_t * histo, float pop, return < np.uint8_t > (0) -cdef inline np.uint8_t kernel_median(ssize_t * histo, float pop, +cdef inline np.uint8_t kernel_median(Py_ssize_t * histo, float pop, np.uint8_t g, float p0, float p1, - ssize_t s0, ssize_t s1): + Py_ssize_t s0, Py_ssize_t s1): - cdef ssize_t i + cdef Py_ssize_t i cdef float sum = pop / 2.0 if pop: @@ -153,11 +153,11 @@ cdef inline np.uint8_t kernel_median(ssize_t * histo, float pop, return < np.uint8_t > (0) -cdef inline np.uint8_t kernel_minimum(ssize_t * histo, float pop, +cdef inline np.uint8_t kernel_minimum(Py_ssize_t * histo, float pop, np.uint8_t g, float p0, float p1, - ssize_t s0, ssize_t s1): + Py_ssize_t s0, Py_ssize_t s1): - cdef ssize_t i + cdef Py_ssize_t i if pop: for i in range(256): @@ -167,11 +167,11 @@ cdef inline np.uint8_t kernel_minimum(ssize_t * histo, float pop, return < np.uint8_t > (0) -cdef inline np.uint8_t kernel_modal(ssize_t * histo, float pop, +cdef inline np.uint8_t kernel_modal(Py_ssize_t * histo, float pop, np.uint8_t g, float p0, float p1, - ssize_t s0, ssize_t s1): + Py_ssize_t s0, Py_ssize_t s1): - cdef ssize_t hmax = 0, imax = 0 + cdef Py_ssize_t hmax = 0, imax = 0 if pop: for i in range(256): @@ -183,11 +183,11 @@ cdef inline np.uint8_t kernel_modal(ssize_t * histo, float pop, return < np.uint8_t > (0) -cdef inline np.uint8_t kernel_morph_contr_enh(ssize_t * histo, float pop, +cdef inline np.uint8_t kernel_morph_contr_enh(Py_ssize_t * histo, float pop, np.uint8_t g, float p0, float p1, - ssize_t s0, ssize_t s1): + Py_ssize_t s0, Py_ssize_t s1): - cdef ssize_t i, imin, imax + cdef Py_ssize_t i, imin, imax if pop: for i in range(255, -1, -1): @@ -206,18 +206,18 @@ cdef inline np.uint8_t kernel_morph_contr_enh(ssize_t * histo, float pop, return < np.uint8_t > (0) -cdef inline np.uint8_t kernel_pop(ssize_t * histo, float pop, +cdef inline np.uint8_t kernel_pop(Py_ssize_t * histo, float pop, np.uint8_t g, float p0, float p1, - ssize_t s0, ssize_t s1): + Py_ssize_t s0, Py_ssize_t s1): return < np.uint8_t > (pop) -cdef inline np.uint8_t kernel_threshold(ssize_t * histo, float pop, +cdef inline np.uint8_t kernel_threshold(Py_ssize_t * histo, float pop, np.uint8_t g, float p0, float p1, - ssize_t s0, ssize_t s1): + Py_ssize_t s0, Py_ssize_t s1): - cdef ssize_t i + cdef Py_ssize_t i cdef float mean = 0. if pop: @@ -228,11 +228,11 @@ cdef inline np.uint8_t kernel_threshold(ssize_t * histo, float pop, return < np.uint8_t > (0) -cdef inline np.uint8_t kernel_tophat(ssize_t * histo, float pop, +cdef inline np.uint8_t kernel_tophat(Py_ssize_t * histo, float pop, np.uint8_t g, float p0, float p1, - ssize_t s0, ssize_t s1): + Py_ssize_t s0, Py_ssize_t s1): - cdef ssize_t i + cdef Py_ssize_t i if pop: for i in range(255, -1, -1): @@ -243,12 +243,12 @@ cdef inline np.uint8_t kernel_tophat(ssize_t * histo, float pop, else: return < np.uint8_t > (0) -cdef inline np.uint8_t kernel_noise_filter(ssize_t * histo, float pop, +cdef inline np.uint8_t kernel_noise_filter(Py_ssize_t * histo, float pop, np.uint8_t g, float p0, float p1, - ssize_t s0, ssize_t s1): + Py_ssize_t s0, Py_ssize_t s1): - cdef ssize_t i - cdef ssize_t min_i + cdef Py_ssize_t i + cdef Py_ssize_t min_i # early stop if at least one pixel of the neighborhood has the same g if histo[g] > 0: @@ -267,10 +267,10 @@ cdef inline np.uint8_t kernel_noise_filter(ssize_t * histo, float pop, return < np.uint8_t > min_i -cdef inline np.uint8_t kernel_entropy(ssize_t * histo, float pop, +cdef inline np.uint8_t kernel_entropy(Py_ssize_t * histo, float pop, np.uint8_t g, float p0, float p1, - ssize_t s0, ssize_t s1): - cdef ssize_t i + Py_ssize_t s0, Py_ssize_t s1): + cdef Py_ssize_t i cdef float e, p if pop: @@ -285,11 +285,11 @@ cdef inline np.uint8_t kernel_entropy(ssize_t * histo, float pop, else: return < np.uint8_t > (0) -cdef inline np.uint8_t kernel_otsu(ssize_t * histo, float pop, np.uint8_t g, - float p0, float p1, ssize_t s0, - ssize_t s1): - cdef ssize_t i - cdef ssize_t max_i +cdef inline np.uint8_t kernel_otsu(Py_ssize_t * histo, float pop, np.uint8_t g, + float p0, float p1, Py_ssize_t s0, + Py_ssize_t s1): + cdef Py_ssize_t i + cdef Py_ssize_t max_i cdef float P, mu1, mu2, q1, new_q1, sigma_b, max_sigma_b cdef float mu = 0. @@ -334,7 +334,7 @@ def autolevel(np.ndarray[np.uint8_t, ndim=2] image, np.ndarray[np.uint8_t, ndim=2] out=None, char shift_x=0, char shift_y=0): _core8(kernel_autolevel, image, selem, mask, out, shift_x, shift_y, - 0, 0, 0, 0) + 0, 0, 0, 0) def bottomhat(np.ndarray[np.uint8_t, ndim=2] image, @@ -343,7 +343,7 @@ def bottomhat(np.ndarray[np.uint8_t, ndim=2] image, np.ndarray[np.uint8_t, ndim=2] out=None, char shift_x=0, char shift_y=0): _core8(kernel_bottomhat, image, selem, mask, out, shift_x, shift_y, - 0, 0, 0, 0) + 0, 0, 0, 0) def equalize(np.ndarray[np.uint8_t, ndim=2] image, @@ -352,7 +352,7 @@ def equalize(np.ndarray[np.uint8_t, ndim=2] image, np.ndarray[np.uint8_t, ndim=2] out=None, char shift_x=0, char shift_y=0): _core8(kernel_equalize, image, selem, mask, out, shift_x, shift_y, - 0, 0, 0, 0) + 0, 0, 0, 0) def gradient(np.ndarray[np.uint8_t, ndim=2] image, @@ -361,7 +361,7 @@ def gradient(np.ndarray[np.uint8_t, ndim=2] image, np.ndarray[np.uint8_t, ndim=2] out=None, char shift_x=0, char shift_y=0): _core8(kernel_gradient, image, selem, mask, out, shift_x, shift_y, - 0, 0, 0, 0) + 0, 0, 0, 0) def maximum(np.ndarray[np.uint8_t, ndim=2] image, @@ -370,7 +370,7 @@ def maximum(np.ndarray[np.uint8_t, ndim=2] image, np.ndarray[np.uint8_t, ndim=2] out=None, char shift_x=0, char shift_y=0): _core8(kernel_maximum, image, selem, mask, out, shift_x, shift_y, - 0, 0, 0, 0) + 0, 0, 0, 0) def mean(np.ndarray[np.uint8_t, ndim=2] image, @@ -379,7 +379,7 @@ def mean(np.ndarray[np.uint8_t, ndim=2] image, np.ndarray[np.uint8_t, ndim=2] out=None, char shift_x=0, char shift_y=0): _core8(kernel_mean, image, selem, mask, out, shift_x, shift_y, - 0, 0, 0, 0) + 0, 0, 0, 0) def meansubstraction(np.ndarray[np.uint8_t, ndim=2] image, @@ -388,7 +388,7 @@ def meansubstraction(np.ndarray[np.uint8_t, ndim=2] image, np.ndarray[np.uint8_t, ndim=2] out=None, char shift_x=0, char shift_y=0): _core8(kernel_meansubstraction, image, selem, mask, out, shift_x, shift_y, - 0, 0, 0, 0) + 0, 0, 0, 0) def median(np.ndarray[np.uint8_t, ndim=2] image, @@ -397,7 +397,7 @@ def median(np.ndarray[np.uint8_t, ndim=2] image, np.ndarray[np.uint8_t, ndim=2] out=None, char shift_x=0, char shift_y=0): _core8(kernel_median, image, selem, mask, out, shift_x, shift_y, - 0, 0, 0, 0) + 0, 0, 0, 0) def minimum(np.ndarray[np.uint8_t, ndim=2] image, @@ -406,7 +406,7 @@ def minimum(np.ndarray[np.uint8_t, ndim=2] image, np.ndarray[np.uint8_t, ndim=2] out=None, char shift_x=0, char shift_y=0): _core8(kernel_minimum, image, selem, mask, out, shift_x, shift_y, - 0, 0, 0, 0) + 0, 0, 0, 0) def morph_contr_enh(np.ndarray[np.uint8_t, ndim=2] image, @@ -415,7 +415,7 @@ def morph_contr_enh(np.ndarray[np.uint8_t, ndim=2] image, np.ndarray[np.uint8_t, ndim=2] out=None, char shift_x=0, char shift_y=0): _core8(kernel_morph_contr_enh, image, selem, mask, out, shift_x, shift_y, - 0, 0, 0, 0) + 0, 0, 0, 0) def modal(np.ndarray[np.uint8_t, ndim=2] image, @@ -424,7 +424,7 @@ def modal(np.ndarray[np.uint8_t, ndim=2] image, np.ndarray[np.uint8_t, ndim=2] out=None, char shift_x=0, char shift_y=0): _core8(kernel_modal, image, selem, mask, out, shift_x, shift_y, - 0, 0, 0, 0) + 0, 0, 0, 0) def pop(np.ndarray[np.uint8_t, ndim=2] image, @@ -433,7 +433,7 @@ def pop(np.ndarray[np.uint8_t, ndim=2] image, np.ndarray[np.uint8_t, ndim=2] out=None, char shift_x=0, char shift_y=0): _core8(kernel_pop, image, selem, mask, out, shift_x, shift_y, - 0, 0, 0, 0) + 0, 0, 0, 0) def threshold(np.ndarray[np.uint8_t, ndim=2] image, @@ -442,7 +442,7 @@ def threshold(np.ndarray[np.uint8_t, ndim=2] image, np.ndarray[np.uint8_t, ndim=2] out=None, char shift_x=0, char shift_y=0): _core8(kernel_threshold, image, selem, mask, out, shift_x, shift_y, 0, 0, - 0, 0) + 0, 0) def tophat(np.ndarray[np.uint8_t, ndim=2] image, @@ -451,7 +451,7 @@ def tophat(np.ndarray[np.uint8_t, ndim=2] image, np.ndarray[np.uint8_t, ndim=2] out=None, char shift_x=0, char shift_y=0): _core8(kernel_tophat, image, selem, mask, out, shift_x, shift_y, - 0, 0, 0, 0) + 0, 0, 0, 0) def noise_filter(np.ndarray[np.uint8_t, ndim=2] image, @@ -460,7 +460,7 @@ def noise_filter(np.ndarray[np.uint8_t, ndim=2] image, np.ndarray[np.uint8_t, ndim=2] out=None, char shift_x=0, char shift_y=0): _core8(kernel_noise_filter, image, selem, mask, out, shift_x, shift_y, - 0, 0, 0, 0) + 0, 0, 0, 0) def entropy(np.ndarray[np.uint8_t, ndim=2] image, @@ -469,7 +469,7 @@ def entropy(np.ndarray[np.uint8_t, ndim=2] image, np.ndarray[np.uint8_t, ndim=2] out=None, char shift_x=0, char shift_y=0): _core8(kernel_entropy, image, selem, mask, out, shift_x, shift_y, - 0, 0, 0, 0) + 0, 0, 0, 0) def otsu(np.ndarray[np.uint8_t, ndim=2] image, @@ -478,4 +478,4 @@ def otsu(np.ndarray[np.uint8_t, ndim=2] image, np.ndarray[np.uint8_t, ndim=2] out=None, char shift_x=0, char shift_y=0): _core8(kernel_otsu, image, selem, mask, out, shift_x, shift_y, - 0, 0, 0, 0) + 0, 0, 0, 0) diff --git a/skimage/filter/rank/_crank8_percentiles.pyx b/skimage/filter/rank/_crank8_percentiles.pyx index bd48c78b..31b00742 100644 --- a/skimage/filter/rank/_crank8_percentiles.pyx +++ b/skimage/filter/rank/_crank8_percentiles.pyx @@ -13,9 +13,9 @@ from skimage.filter.rank._core8 cimport _core8, uint8_max, uint8_min # ----------------------------------------------------------------- -cdef inline np.uint8_t kernel_autolevel(ssize_t * histo, float pop, +cdef inline np.uint8_t kernel_autolevel(Py_ssize_t * histo, float pop, np.uint8_t g, float p0, float p1, - ssize_t s0, ssize_t s1): + Py_ssize_t s0, Py_ssize_t s1): cdef int i, imin, imax, sum, delta if pop: @@ -45,9 +45,9 @@ cdef inline np.uint8_t kernel_autolevel(ssize_t * histo, float pop, return < np.uint8_t > (128) -cdef inline np.uint8_t kernel_gradient(ssize_t * histo, float pop, +cdef inline np.uint8_t kernel_gradient(Py_ssize_t * histo, float pop, np.uint8_t g, float p0, float p1, - ssize_t s0, ssize_t s1): + Py_ssize_t s0, Py_ssize_t s1): cdef int i, imin, imax, sum, delta if pop: @@ -70,9 +70,9 @@ cdef inline np.uint8_t kernel_gradient(ssize_t * histo, float pop, return < np.uint8_t > (0) -cdef inline np.uint8_t kernel_mean(ssize_t * histo, float pop, +cdef inline np.uint8_t kernel_mean(Py_ssize_t * histo, float pop, np.uint8_t g, float p0, float p1, - ssize_t s0, ssize_t s1): + Py_ssize_t s0, Py_ssize_t s1): cdef int i, sum, mean, n if pop: @@ -92,11 +92,11 @@ cdef inline np.uint8_t kernel_mean(ssize_t * histo, float pop, return < np.uint8_t > (0) -cdef inline np.uint8_t kernel_mean_substraction(ssize_t * histo, +cdef inline np.uint8_t kernel_mean_substraction(Py_ssize_t * histo, float pop, np.uint8_t g, float p0, float p1, - ssize_t s0, ssize_t s1): + Py_ssize_t s0, Py_ssize_t s1): cdef int i, sum, mean, n if pop: @@ -116,10 +116,10 @@ cdef inline np.uint8_t kernel_mean_substraction(ssize_t * histo, return < np.uint8_t > (0) -cdef inline np.uint8_t kernel_morph_contr_enh(ssize_t * histo, +cdef inline np.uint8_t kernel_morph_contr_enh(Py_ssize_t * histo, float pop, np.uint8_t g, float p0, float p1, - ssize_t s0, ssize_t s1): + Py_ssize_t s0, Py_ssize_t s1): cdef int i, imin, imax, sum, delta if pop: @@ -148,9 +148,9 @@ cdef inline np.uint8_t kernel_morph_contr_enh(ssize_t * histo, return < np.uint8_t > (0) -cdef inline np.uint8_t kernel_percentile(ssize_t * histo, float pop, +cdef inline np.uint8_t kernel_percentile(Py_ssize_t * histo, float pop, np.uint8_t g, float p0, float p1, - ssize_t s0, ssize_t s1): + Py_ssize_t s0, Py_ssize_t s1): cdef int i cdef float sum = 0. @@ -165,9 +165,9 @@ cdef inline np.uint8_t kernel_percentile(ssize_t * histo, float pop, return < np.uint8_t > (0) -cdef inline np.uint8_t kernel_pop(ssize_t * histo, float pop, +cdef inline np.uint8_t kernel_pop(Py_ssize_t * histo, float pop, np.uint8_t g, float p0, float p1, - ssize_t s0, ssize_t s1): + Py_ssize_t s0, Py_ssize_t s1): cdef int i, sum, n if pop: @@ -182,9 +182,9 @@ cdef inline np.uint8_t kernel_pop(ssize_t * histo, float pop, return < np.uint8_t > (0) -cdef inline np.uint8_t kernel_threshold(ssize_t * histo, float pop, +cdef inline np.uint8_t kernel_threshold(Py_ssize_t * histo, float pop, np.uint8_t g, float p0, float p1, - ssize_t s0, ssize_t s1): + Py_ssize_t s0, Py_ssize_t s1): cdef int i cdef float sum = 0. @@ -212,7 +212,7 @@ def autolevel(np.ndarray[np.uint8_t, ndim=2] image, """autolevel """ _core8(kernel_autolevel, image, selem, mask, out, shift_x, shift_y, p0, p1, - 0, 0) + 0, 0) def gradient(np.ndarray[np.uint8_t, ndim=2] image, @@ -223,7 +223,7 @@ def gradient(np.ndarray[np.uint8_t, ndim=2] image, """return p0,p1 percentile gradient """ _core8(kernel_gradient, image, selem, mask, out, shift_x, shift_y, p0, p1, - 0, 0) + 0, 0) def mean(np.ndarray[np.uint8_t, ndim=2] image, @@ -234,7 +234,7 @@ def mean(np.ndarray[np.uint8_t, ndim=2] image, """return mean between [p0 and p1] percentiles """ _core8(kernel_mean, image, selem, mask, out, shift_x, shift_y, p0, p1, - 0, 0) + 0, 0) def mean_substraction(np.ndarray[np.uint8_t, ndim=2] image, @@ -245,7 +245,7 @@ def mean_substraction(np.ndarray[np.uint8_t, ndim=2] image, """return original - mean between [p0 and p1] percentiles *.5 +127 """ _core8(kernel_mean_substraction, image, selem, mask, out, shift_x, shift_y, - p0, p1, 0, 0) + p0, p1, 0, 0) def morph_contr_enh(np.ndarray[np.uint8_t, ndim=2] image, @@ -256,7 +256,7 @@ def morph_contr_enh(np.ndarray[np.uint8_t, ndim=2] image, """reforce contrast using percentiles """ _core8(kernel_morph_contr_enh, image, selem, mask, out, shift_x, shift_y, - p0, p1, 0, 0) + p0, p1, 0, 0) def percentile(np.ndarray[np.uint8_t, ndim=2] image, @@ -267,7 +267,7 @@ def percentile(np.ndarray[np.uint8_t, ndim=2] image, """return p0 percentile """ _core8(kernel_percentile, image, selem, mask, out, shift_x, shift_y, - p0, p1, 0, 0) + p0, p1, 0, 0) def pop(np.ndarray[np.uint8_t, ndim=2] image, @@ -278,7 +278,7 @@ def pop(np.ndarray[np.uint8_t, ndim=2] image, """return nb of pixels between [p0 and p1] """ _core8(kernel_pop, image, selem, mask, out, shift_x, shift_y, p0, p1, - 0, 0) + 0, 0) def threshold(np.ndarray[np.uint8_t, ndim=2] image, @@ -289,4 +289,4 @@ def threshold(np.ndarray[np.uint8_t, ndim=2] image, """return 255 if g > percentile p0 """ _core8(kernel_threshold, image, selem, mask, out, shift_x, shift_y, p0, p1, - 0, 0) + 0, 0) diff --git a/skimage/graph/heap.pxd b/skimage/graph/heap.pxd index 74366368..e31aa150 100644 --- a/skimage/graph/heap.pxd +++ b/skimage/graph/heap.pxd @@ -7,7 +7,7 @@ value_of_fast() # determine datatypes for heap ctypedef double VALUE_T -ctypedef ssize_t REFERENCE_T +ctypedef Py_ssize_t REFERENCE_T ctypedef REFERENCE_T INDEX_T ctypedef unsigned char BOOL_T ctypedef unsigned char LEVELS_T diff --git a/skimage/measure/_find_contours.pyx b/skimage/measure/_find_contours.pyx index 30eb1250..91ba318c 100644 --- a/skimage/measure/_find_contours.pyx +++ b/skimage/measure/_find_contours.pyx @@ -11,7 +11,7 @@ cdef inline double _get_fraction(double from_value, double to_value, def iterate_and_store(np.ndarray[double, ndim=2] array, - double level, ssize_t vertex_connect_high): + double level, Py_ssize_t vertex_connect_high): """Iterate across the given array in a marching-squares fashion, looking for segments that cross 'level'. If such a segment is found, its coordinates are added to a growing list of segments, @@ -24,7 +24,7 @@ def iterate_and_store(np.ndarray[double, ndim=2] array, raise ValueError("Input array must be at least 2x2.") cdef list arc_list = [] - cdef ssize_t n + cdef Py_ssize_t n # The plan is to iterate a 2x2 square across the input array. This means # that the upper-left corner of the square needs to iterate across a @@ -36,17 +36,17 @@ def iterate_and_store(np.ndarray[double, ndim=2] array, # index varies the fastest). # Current coords start at 0,0. - cdef ssize_t[2] coords + cdef Py_ssize_t[2] coords coords[0] = 0 coords[1] = 0 # Calculate the number of iterations we'll need - cdef ssize_t num_square_steps = (array.shape[0] - 1) * (array.shape[1] - 1) + cdef Py_ssize_t num_square_steps = (array.shape[0] - 1) * (array.shape[1] - 1) cdef unsigned char square_case = 0 cdef tuple top, bottom, left, right cdef double ul, ur, ll, lr - cdef ssize_t r0, r1, c0, c1 + cdef Py_ssize_t r0, r1, c0, c1 for n in range(num_square_steps): # There are sixteen different possible square types, diagramed below. diff --git a/skimage/measure/_moments.pyx b/skimage/measure/_moments.pyx index 759e4a4f..b94a7b89 100644 --- a/skimage/measure/_moments.pyx +++ b/skimage/measure/_moments.pyx @@ -7,7 +7,7 @@ cimport numpy as np def central_moments(np.ndarray[np.double_t, ndim=2] array, double cr, double cc, int order): - cdef ssize_t p, q, r, c + cdef Py_ssize_t p, q, r, c cdef np.ndarray[np.double_t, ndim=2] mu mu = np.zeros((order + 1, order + 1), 'double') for p in range(order + 1): @@ -18,7 +18,7 @@ def central_moments(np.ndarray[np.double_t, ndim=2] array, double cr, double cc, return mu def normalized_moments(np.ndarray[np.double_t, ndim=2] mu, int order): - cdef ssize_t p, q + cdef Py_ssize_t p, q cdef np.ndarray[np.double_t, ndim=2] nu nu = np.zeros((order + 1, order + 1), 'double') for p in range(order + 1): diff --git a/skimage/morphology/_convex_hull.pyx b/skimage/morphology/_convex_hull.pyx index 6746fa9f..9bec825b 100644 --- a/skimage/morphology/_convex_hull.pyx +++ b/skimage/morphology/_convex_hull.pyx @@ -18,9 +18,9 @@ def possible_hull(np.ndarray[dtype=np.uint8_t, ndim=2, mode="c"] img): the convex hull. """ - cdef ssize_t r, c - cdef ssize_t rows = img.shape[0] - cdef ssize_t cols = img.shape[1] + cdef Py_ssize_t r, c + cdef Py_ssize_t rows = img.shape[0] + cdef Py_ssize_t cols = img.shape[1] # Output: rows storage slots for left boundary pixels # cols storage slots for top boundary pixels diff --git a/skimage/morphology/_pnpoly.pyx b/skimage/morphology/_pnpoly.pyx index d1413a24..f6cf386f 100644 --- a/skimage/morphology/_pnpoly.pyx +++ b/skimage/morphology/_pnpoly.pyx @@ -31,11 +31,11 @@ def grid_points_inside_poly(shape, verts): vx = verts[:, 0].astype(np.double) vy = verts[:, 1].astype(np.double) - cdef ssize_t V = vx.shape[0] + cdef Py_ssize_t V = vx.shape[0] - cdef ssize_t M = shape[0] - cdef ssize_t N = shape[1] - cdef ssize_t m, n + cdef Py_ssize_t M = shape[0] + cdef Py_ssize_t N = shape[1] + cdef Py_ssize_t m, n cdef np.ndarray[dtype=np.uint8_t, ndim=2, mode="c"] out = \ np.zeros((M, N), dtype=np.uint8) diff --git a/skimage/morphology/_skeletonize_cy.pyx b/skimage/morphology/_skeletonize_cy.pyx index c4aa0d48..9bef86b9 100644 --- a/skimage/morphology/_skeletonize_cy.pyx +++ b/skimage/morphology/_skeletonize_cy.pyx @@ -62,8 +62,8 @@ def _skeletonize_loop(np.ndarray[dtype=np.uint8_t, ndim=2, """ cdef: np.int32_t accumulator - ssize_t index, order_index - ssize_t ii, jj + Py_ssize_t index, order_index + Py_ssize_t ii, jj for index in range(order.shape[0]): accumulator = 16 @@ -119,12 +119,12 @@ def _table_lookup_index(np.ndarray[dtype=np.uint8_t, ndim=2, negative_indices=False, mode='c'] indexer np.int32_t *p_indexer np.uint8_t *p_image - ssize_t i_stride - ssize_t i_shape - ssize_t j_shape - ssize_t i - ssize_t j - ssize_t offset + Py_ssize_t i_stride + Py_ssize_t i_shape + Py_ssize_t j_shape + Py_ssize_t i + Py_ssize_t j + Py_ssize_t offset i_shape = image.shape[0] j_shape = image.shape[1] diff --git a/skimage/morphology/_watershed.pyx b/skimage/morphology/_watershed.pyx index b98ca332..122f0262 100644 --- a/skimage/morphology/_watershed.pyx +++ b/skimage/morphology/_watershed.pyx @@ -27,7 +27,7 @@ def watershed(np.ndarray[DTYPE_INT32_t, ndim=1, negative_indices=False, mode='c'] image, np.ndarray[DTYPE_INT32_t, ndim=2, negative_indices=False, mode='c'] pq, - ssize_t age, + Py_ssize_t age, np.ndarray[DTYPE_INT32_t, ndim=2, negative_indices=False, mode='c'] structure, np.ndarray[DTYPE_BOOL_t, ndim=1, negative_indices=False, @@ -58,11 +58,11 @@ def watershed(np.ndarray[DTYPE_INT32_t, ndim=1, negative_indices=False, """ cdef Heapitem elem cdef Heapitem new_elem - cdef ssize_t nneighbors = structure.shape[0] - cdef ssize_t i = 0 - cdef ssize_t index = 0 - cdef ssize_t old_index = 0 - cdef ssize_t max_index = image.shape[0] + cdef Py_ssize_t nneighbors = structure.shape[0] + cdef Py_ssize_t i = 0 + cdef Py_ssize_t index = 0 + cdef Py_ssize_t old_index = 0 + cdef Py_ssize_t max_index = image.shape[0] cdef Heap *hp = heap_from_numpy2() diff --git a/skimage/morphology/cmorph.pyx b/skimage/morphology/cmorph.pyx index 418b0571..a09a39a3 100644 --- a/skimage/morphology/cmorph.pyx +++ b/skimage/morphology/cmorph.pyx @@ -13,13 +13,13 @@ def dilate(np.ndarray[np.uint8_t, ndim=2] image, np.ndarray[np.uint8_t, ndim=2] out=None, char shift_x=0, char shift_y=0): - cdef ssize_t rows = image.shape[0] - cdef ssize_t cols = image.shape[1] - cdef ssize_t srows = selem.shape[0] - cdef ssize_t scols = selem.shape[1] + cdef Py_ssize_t rows = image.shape[0] + cdef Py_ssize_t cols = image.shape[1] + cdef Py_ssize_t srows = selem.shape[0] + cdef Py_ssize_t scols = selem.shape[1] - cdef ssize_t centre_r = int(selem.shape[0] / 2) - shift_y - cdef ssize_t centre_c = int(selem.shape[1] / 2) - shift_x + cdef Py_ssize_t centre_r = int(selem.shape[0] / 2) - shift_y + cdef Py_ssize_t centre_c = int(selem.shape[1] / 2) - shift_x image = np.ascontiguousarray(image) if out is None: @@ -30,11 +30,11 @@ def dilate(np.ndarray[np.uint8_t, ndim=2] image, cdef np.uint8_t* out_data = out.data cdef np.uint8_t* image_data = image.data - cdef ssize_t r, c, rr, cc, s, value, local_max + cdef Py_ssize_t r, c, rr, cc, s, value, local_max - cdef ssize_t selem_num = np.sum(selem != 0) - cdef ssize_t* sr = malloc(selem_num * sizeof(ssize_t)) - cdef ssize_t* sc = malloc(selem_num * sizeof(ssize_t)) + cdef Py_ssize_t selem_num = np.sum(selem != 0) + cdef Py_ssize_t* sr = malloc(selem_num * sizeof(Py_ssize_t)) + cdef Py_ssize_t* sc = malloc(selem_num * sizeof(Py_ssize_t)) s = 0 for r in range(srows): @@ -68,13 +68,13 @@ def erode(np.ndarray[np.uint8_t, ndim=2] image, np.ndarray[np.uint8_t, ndim=2] out=None, char shift_x=0, char shift_y=0): - cdef ssize_t rows = image.shape[0] - cdef ssize_t cols = image.shape[1] - cdef ssize_t srows = selem.shape[0] - cdef ssize_t scols = selem.shape[1] + cdef Py_ssize_t rows = image.shape[0] + cdef Py_ssize_t cols = image.shape[1] + cdef Py_ssize_t srows = selem.shape[0] + cdef Py_ssize_t scols = selem.shape[1] - cdef ssize_t centre_r = int(selem.shape[0] / 2) - shift_y - cdef ssize_t centre_c = int(selem.shape[1] / 2) - shift_x + cdef Py_ssize_t centre_r = int(selem.shape[0] / 2) - shift_y + cdef Py_ssize_t centre_c = int(selem.shape[1] / 2) - shift_x image = np.ascontiguousarray(image) if out is None: @@ -87,9 +87,9 @@ def erode(np.ndarray[np.uint8_t, ndim=2] image, cdef int r, c, rr, cc, s, value, local_min - cdef ssize_t selem_num = np.sum(selem != 0) - cdef ssize_t* sr = malloc(selem_num * sizeof(ssize_t)) - cdef ssize_t* sc = malloc(selem_num * sizeof(ssize_t)) + cdef Py_ssize_t selem_num = np.sum(selem != 0) + cdef Py_ssize_t* sr = malloc(selem_num * sizeof(Py_ssize_t)) + cdef Py_ssize_t* sc = malloc(selem_num * sizeof(Py_ssize_t)) s = 0 for r in range(srows): diff --git a/skimage/morphology/heap_general.pxi b/skimage/morphology/heap_general.pxi index c60096d6..67b21fa6 100644 --- a/skimage/morphology/heap_general.pxi +++ b/skimage/morphology/heap_general.pxi @@ -14,13 +14,13 @@ from libc.stdlib cimport free, malloc, realloc cdef struct Heap: - ssize_t items - ssize_t space + Py_ssize_t items + Py_ssize_t space Heapitem *data Heapitem **ptrs cdef inline Heap *heap_from_numpy2(): - cdef ssize_t k + cdef Py_ssize_t k cdef Heap *heap heap = malloc(sizeof (Heap)) heap.items = 0 @@ -36,7 +36,7 @@ cdef inline void heap_done(Heap *heap): free(heap.ptrs) free(heap) -cdef inline void swap(ssize_t a, ssize_t b, Heap *h): +cdef inline void swap(Py_ssize_t a, Py_ssize_t b, Heap *h): h.ptrs[a], h.ptrs[b] = h.ptrs[b], h.ptrs[a] @@ -49,7 +49,7 @@ cdef inline void swap(ssize_t a, ssize_t b, Heap *h): ###################################################### cdef inline void heappop(Heap *heap, Heapitem *dest): - cdef ssize_t i, smallest, l, r # heap indices + cdef Py_ssize_t i, smallest, l, r # heap indices # # Start by copying the first element to the destination @@ -102,18 +102,18 @@ cdef inline void heappop(Heap *heap, Heapitem *dest): ################################################## cdef inline void heappush(Heap *heap, Heapitem *new_elem): - cdef ssize_t child = heap.items - cdef ssize_t parent - cdef ssize_t k + cdef Py_ssize_t child = heap.items + cdef Py_ssize_t parent + cdef Py_ssize_t k cdef Heapitem *new_data # grow if necessary if heap.items == heap.space: heap.space = heap.space * 2 new_data = realloc(heap.data, - (heap.space * sizeof(Heapitem))) + (heap.space * sizeof(Heapitem))) heap.ptrs = realloc(heap.ptrs, - (heap.space * sizeof(Heapitem *))) + (heap.space * sizeof(Heapitem *))) for k in range(heap.items): heap.ptrs[k] = new_data + (heap.ptrs[k] - heap.data) for k in range(heap.items, heap.space): diff --git a/skimage/morphology/heap_watershed.pxi b/skimage/morphology/heap_watershed.pxi index 43917690..573d32bb 100644 --- a/skimage/morphology/heap_watershed.pxi +++ b/skimage/morphology/heap_watershed.pxi @@ -17,7 +17,7 @@ cimport cython cdef struct Heapitem: np.int32_t value np.int32_t age - ssize_t index + Py_ssize_t index cdef inline int smaller(Heapitem *a, Heapitem *b): diff --git a/skimage/segmentation/_felzenszwalb_cy.pyx b/skimage/segmentation/_felzenszwalb_cy.pyx index 2a4ea173..5a8ac012 100644 --- a/skimage/segmentation/_felzenszwalb_cy.pyx +++ b/skimage/segmentation/_felzenszwalb_cy.pyx @@ -10,7 +10,7 @@ from ..util import img_as_float @cython.boundscheck(False) @cython.wraparound(False) @cython.cdivision(True) -def _felzenszwalb_grey(image, double scale=1, sigma=0.8, ssize_t min_size=20): +def _felzenszwalb_grey(image, double scale=1, sigma=0.8, Py_ssize_t min_size=20): """Felzenszwalb's efficient graph based segmentation for a single channel. Produces an oversegmentation of a 2d image using a fast, minimum spanning diff --git a/skimage/segmentation/_quickshift.pyx b/skimage/segmentation/_quickshift.pyx index 4362725c..61e4d0e0 100644 --- a/skimage/segmentation/_quickshift.pyx +++ b/skimage/segmentation/_quickshift.pyx @@ -85,12 +85,12 @@ def quickshift(image, ratio=1., float kernel_size=5, max_dist=10, raise ValueError("Sigma should be >= 1") cdef int w = int(3 * kernel_size) - cdef ssize_t height = image_c.shape[0] - cdef ssize_t width = image_c.shape[1] - cdef ssize_t channels = image_c.shape[2] + cdef Py_ssize_t height = image_c.shape[0] + cdef Py_ssize_t width = image_c.shape[1] + cdef Py_ssize_t channels = image_c.shape[2] cdef double current_density, closest, dist - cdef ssize_t r, c, r_, c_, channel, r_min, c_min + cdef Py_ssize_t r, c, r_, c_, channel, r_min, c_min cdef np.float_t* image_p = image_c.data cdef np.float_t* current_pixel_p = image_p diff --git a/skimage/segmentation/_slic.pyx b/skimage/segmentation/_slic.pyx index 260b7689..2fcee46b 100644 --- a/skimage/segmentation/_slic.pyx +++ b/skimage/segmentation/_slic.pyx @@ -63,10 +63,10 @@ def slic(image, n_segments=100, ratio=10., max_iter=10, sigma=1, image = rgb2lab(image) # initialize on grid: - cdef ssize_t height, width + cdef Py_ssize_t height, width height, width = image.shape[:2] # approximate grid size for desired n_segments - cdef ssize_t step = np.ceil(np.sqrt(height * width / n_segments)) + cdef Py_ssize_t step = np.ceil(np.sqrt(height * width / n_segments)) grid_y, grid_x = np.mgrid[:height, :width] means_y = grid_y[::step, ::step] means_x = grid_x[::step, ::step] @@ -82,7 +82,7 @@ def slic(image, n_segments=100, ratio=10., max_iter=10, sigma=1, ratio = (ratio / float(step)) ** 2 cdef np.ndarray[dtype=np.float_t, ndim=3] image_yx \ = np.dstack([grid_y, grid_x, image / ratio]).copy("C") - cdef ssize_t i, k, x, y, x_min, x_max, y_min, y_max, changes + cdef Py_ssize_t i, k, x, y, x_min, x_max, y_min, y_max, changes cdef double dist_mean cdef np.ndarray[dtype=np.intp_t, ndim=2] nearest_mean \ diff --git a/skimage/transform/_hough_transform.pyx b/skimage/transform/_hough_transform.pyx index 016be878..01a45a48 100644 --- a/skimage/transform/_hough_transform.pyx +++ b/skimage/transform/_hough_transform.pyx @@ -13,8 +13,8 @@ cdef double PI_2 = 1.5707963267948966 cdef double NEG_PI_2 = -PI_2 -cdef inline ssize_t round(double r): - return ((r + 0.5) if (r > 0.0) else (r - 0.5)) +cdef inline Py_ssize_t round(double r): + return ((r + 0.5) if (r > 0.0) else (r - 0.5)) @cython.boundscheck(False) @@ -36,9 +36,9 @@ def _hough(np.ndarray img, np.ndarray[ndim=1, dtype=np.double_t] theta=None): # compute the bins and allocate the accumulator array cdef np.ndarray[ndim=2, dtype=np.uint64_t] accum cdef np.ndarray[ndim=1, dtype=np.double_t] bins - cdef ssize_t max_distance, offset + cdef Py_ssize_t max_distance, offset - max_distance = 2 * ceil(sqrt(img.shape[0] * img.shape[0] + + max_distance = 2 * ceil(sqrt(img.shape[0] * img.shape[0] + img.shape[1] * img.shape[1])) accum = np.zeros((max_distance, theta.shape[0]), dtype=np.uint64) bins = np.linspace(-max_distance / 2.0, max_distance / 2.0, max_distance) @@ -49,7 +49,7 @@ def _hough(np.ndarray img, np.ndarray[ndim=1, dtype=np.double_t] theta=None): y_idxs, x_idxs = np.nonzero(img) # finally, run the transform - cdef ssize_t nidxs, nthetas, i, j, x, y, accum_idx + cdef Py_ssize_t nidxs, nthetas, i, j, x, y, accum_idx nidxs = y_idxs.shape[0] # x and y are the same shape nthetas = theta.shape[0] for i in range(nidxs): @@ -77,20 +77,20 @@ def _probabilistic_hough(np.ndarray img, int value_threshold, int line_length, \ theta = math.pi/2-np.arange(180)/180.0* math.pi ctheta = np.cos(theta) stheta = np.sin(theta) - cdef ssize_t height = img.shape[0] - cdef ssize_t width = img.shape[1] + cdef Py_ssize_t height = img.shape[0] + cdef Py_ssize_t width = img.shape[1] # compute the bins and allocate the accumulator array cdef np.ndarray[ndim=2, dtype=np.int64_t] accum cdef np.ndarray[ndim=2, dtype=np.uint8_t] mask = np.zeros((height, width), dtype=np.uint8) cdef np.ndarray[ndim=2, dtype=np.int32_t] line_end = np.zeros((2, 2), dtype=np.int32) - cdef ssize_t max_distance, offset, num_indexes, index + cdef Py_ssize_t max_distance, offset, num_indexes, index cdef double a, b - cdef ssize_t nidxs, nthetas, i, j, x, y, px, py, accum_idx + cdef Py_ssize_t nidxs, nthetas, i, j, x, y, px, py, accum_idx cdef int value, max_value, max_theta cdef int shift = 16 # maximum line number cutoff - cdef ssize_t lines_max = 2 ** 15 - cdef ssize_t xflag, x0, y0, dx0, dy0, dx, dy, gap, x1, y1, good_line, count + cdef Py_ssize_t lines_max = 2 ** 15 + cdef Py_ssize_t xflag, x0, y0, dx0, dy0, dx, dy, gap, x1, y1, good_line, count max_distance = 2 * ceil((sqrt(img.shape[0] * img.shape[0] + img.shape[1] * img.shape[1]))) accum = np.zeros((max_distance, theta.shape[0]), dtype=np.int64) diff --git a/skimage/transform/_warps_cy.pyx b/skimage/transform/_warps_cy.pyx index 28c258fd..951eb861 100644 --- a/skimage/transform/_warps_cy.pyx +++ b/skimage/transform/_warps_cy.pyx @@ -93,7 +93,7 @@ def _warp_fast(np.ndarray image, np.ndarray H, output_shape=None, int order=1, "`constant`, `nearest`, `wrap` or `reflect`.") cdef char mode_c = ord(mode[0].upper()) - cdef ssize_t out_r, out_c + cdef Py_ssize_t out_r, out_c if output_shape is None: out_r = img.shape[0] out_c = img.shape[1] @@ -104,12 +104,12 @@ def _warp_fast(np.ndarray image, np.ndarray H, output_shape=None, int order=1, cdef np.ndarray[dtype=np.double_t, ndim=2] out = \ np.zeros((out_r, out_c), dtype=np.double) - cdef ssize_t tfr, tfc + cdef Py_ssize_t tfr, tfc cdef double r, c - cdef ssize_t rows = img.shape[0] - cdef ssize_t cols = img.shape[1] + cdef Py_ssize_t rows = img.shape[0] + cdef Py_ssize_t cols = img.shape[1] - cdef double (*interp_func)(double*, ssize_t, ssize_t, double, double, + cdef double (*interp_func)(double*, Py_ssize_t, Py_ssize_t, double, double, char, double) if order == 0: interp_func = nearest_neighbour_interpolation From a68be842ab346aa55d1610aa2c568813c25acb00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Fri, 15 Feb 2013 15:10:08 +0100 Subject: [PATCH 083/215] Fix index type errors in draw module --- skimage/draw/_draw.pyx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/skimage/draw/_draw.pyx b/skimage/draw/_draw.pyx index 5bba8c32..4b7fb094 100644 --- a/skimage/draw/_draw.pyx +++ b/skimage/draw/_draw.pyx @@ -97,10 +97,10 @@ def polygon(y, x, shape=None): """ cdef Py_ssize_t nr_verts = x.shape[0] - cdef Py_ssize_t minr = max(0, y.min()) - cdef Py_ssize_t maxr = math.ceil(y.max()) - cdef Py_ssize_t minc = max(0, x.min()) - cdef Py_ssize_t maxc = math.ceil(x.max()) + cdef Py_ssize_t minr = int(max(0, y.min())) + cdef Py_ssize_t maxr = int(math.ceil(y.max())) + cdef Py_ssize_t minc = int(max(0, x.min())) + cdef Py_ssize_t maxc = int(math.ceil(x.max())) # make sure output coordinates do not exceed image size if shape is not None: @@ -148,10 +148,10 @@ def ellipse(double cy, double cx, double yradius, double xradius, shape=None): """ - cdef Py_ssize_t minr = max(0, cy - yradius) - cdef Py_ssize_t maxr = math.ceil(cy + yradius) - cdef Py_ssize_t minc = max(0, cx - xradius) - cdef Py_ssize_t maxc = math.ceil(cx + xradius) + cdef Py_ssize_t minr = int(max(0, cy - yradius)) + cdef Py_ssize_t maxr = int(math.ceil(cy + yradius)) + cdef Py_ssize_t minc = int(max(0, cx - xradius)) + cdef Py_ssize_t maxc = int(math.ceil(cx + xradius)) # make sure output coordinates do not exceed image size if shape is not None: From 983cb76319856e4d7bb348c23e5bd9b26433eea7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Fri, 15 Feb 2013 15:10:51 +0100 Subject: [PATCH 084/215] Remove print statement --- skimage/segmentation/tests/test_slic.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/skimage/segmentation/tests/test_slic.py b/skimage/segmentation/tests/test_slic.py index 59088fcd..89dee59b 100644 --- a/skimage/segmentation/tests/test_slic.py +++ b/skimage/segmentation/tests/test_slic.py @@ -13,8 +13,8 @@ def test_color(): img[img > 1] = 1 img[img < 0] = 0 seg = slic(img, sigma=0, n_segments=4) - # we expect 4 segments: - print(seg) + + # we expect 4 segments assert_equal(len(np.unique(seg)), 4) assert_array_equal(seg[:10, :10], 0) assert_array_equal(seg[10:, :10], 2) @@ -31,7 +31,7 @@ def test_gray(): img[img > 1] = 1 img[img < 0] = 0 seg = slic(img, sigma=0, n_segments=4, ratio=50.0) - print(seg) + assert_equal(len(np.unique(seg)), 4) assert_array_equal(seg[:10, :10], 0) assert_array_equal(seg[10:, :10], 2) From 7e578b5dec35ed52a33dcfed09d2ebc9df4e9b23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Fri, 15 Feb 2013 15:11:54 +0100 Subject: [PATCH 085/215] Fix index type error in SLIC function --- skimage/segmentation/_slic.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skimage/segmentation/_slic.pyx b/skimage/segmentation/_slic.pyx index 2fcee46b..e91ed21d 100644 --- a/skimage/segmentation/_slic.pyx +++ b/skimage/segmentation/_slic.pyx @@ -66,7 +66,7 @@ def slic(image, n_segments=100, ratio=10., max_iter=10, sigma=1, cdef Py_ssize_t height, width height, width = image.shape[:2] # approximate grid size for desired n_segments - cdef Py_ssize_t step = np.ceil(np.sqrt(height * width / n_segments)) + cdef Py_ssize_t step = int(np.ceil(np.sqrt(height * width / n_segments))) grid_y, grid_x = np.mgrid[:height, :width] means_y = grid_y[::step, ::step] means_x = grid_x[::step, ::step] From cf09c582b6091b74dab414eb29e44678ca552660 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Fri, 15 Feb 2013 15:18:22 +0100 Subject: [PATCH 086/215] Fix index type error in GLCM --- skimage/feature/_texture.pyx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/skimage/feature/_texture.pyx b/skimage/feature/_texture.pyx index 2b9a9adc..174d5e38 100644 --- a/skimage/feature/_texture.pyx +++ b/skimage/feature/_texture.pyx @@ -52,8 +52,8 @@ def _glcm_loop(np.ndarray[dtype=np.uint8_t, ndim=2, i = image[r, c] # compute the location of the offset pixel - row = r + (sin(angle) * distance + 0.5) - col = c + (cos(angle) * distance + 0.5); + row = r + (sin(angle) * distance + 0.5) + col = c + (cos(angle) * distance + 0.5) # make sure the offset is within bounds if row >= 0 and row < rows and \ From 3125e63fceda387b9938efdc1c1eda63fb09ca74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Fri, 15 Feb 2013 15:25:21 +0100 Subject: [PATCH 087/215] Improve performance and type usage for GLCM loop --- skimage/feature/_texture.pyx | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/skimage/feature/_texture.pyx b/skimage/feature/_texture.pyx index 174d5e38..ffff38d7 100644 --- a/skimage/feature/_texture.pyx +++ b/skimage/feature/_texture.pyx @@ -16,8 +16,7 @@ def _glcm_loop(np.ndarray[dtype=np.uint8_t, ndim=2, negative_indices=False, mode='c'] angles, int levels, np.ndarray[dtype=np.uint32_t, ndim=4, - negative_indices=False, mode='c'] out - ): + negative_indices=False, mode='c'] out): """Perform co-occurrence matrix accumulation. Parameters @@ -37,16 +36,19 @@ def _glcm_loop(np.ndarray[dtype=np.uint8_t, ndim=2, the results of the GLCM computation. """ + cdef: - np.int32_t a_inx, d_idx - Py_ssize_t r, c, rows, cols, row, col - np.int32_t i, j + Py_ssize_t a_idx, d_idx, r, c, rows, cols, row, col + np.uint8_t i, j + np.float64_t angle, distance rows = image.shape[0] cols = image.shape[1] - for a_idx, angle in enumerate(angles): - for d_idx, distance in enumerate(distances): + for a_idx in range(len(angles)): + angle = angles[a_idx] + for d_idx in range(len(distances)): + distance = distances[d_idx] for r in range(rows): for c in range(cols): i = image[r, c] From 52e3436f61c62b18c8f7e0fbfb8329bde302e9dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Fri, 15 Feb 2013 15:39:48 +0100 Subject: [PATCH 088/215] Fix integer type bugs in feature detection algorithms --- skimage/draw/_draw.pyx | 3 ++- skimage/feature/_daisy.py | 13 +++++++------ skimage/feature/_hog.py | 6 ++++-- skimage/feature/tests/test_hog.py | 2 +- 4 files changed, 14 insertions(+), 10 deletions(-) diff --git a/skimage/draw/_draw.pyx b/skimage/draw/_draw.pyx index 4b7fb094..efd5fdc5 100644 --- a/skimage/draw/_draw.pyx +++ b/skimage/draw/_draw.pyx @@ -274,7 +274,8 @@ def circle_perimeter(Py_ssize_t cy, Py_ssize_t cx, Py_ssize_t radius, return np.array(rr) + cy, np.array(cc) + cx -def ellipse_perimeter(int cy, int cx, int yradius, int xradius): +def ellipse_perimeter(Py_ssize_t cy, Py_ssize_t cx, Py_ssize_t yradius, + Py_ssize_t xradius): """Generate ellipse perimeter coordinates. Parameters diff --git a/skimage/feature/_daisy.py b/skimage/feature/_daisy.py index f83605b5..1bb8cbf5 100644 --- a/skimage/feature/_daisy.py +++ b/skimage/feature/_daisy.py @@ -180,7 +180,7 @@ def daisy(img, step=4, radius=15, rings=3, histograms=8, orientations=8, color = (1, 0, 0) desc_y = i * step + radius desc_x = j * step + radius - coords = draw.circle_perimeter(desc_y, desc_x, sigmas[0]) + coords = draw.circle_perimeter(desc_y, desc_x, int(sigmas[0])) draw.set_color(descs_img, coords, color) max_bin = np.max(descs[i, j, :]) for o_num, o in enumerate(orientation_angles): @@ -188,8 +188,8 @@ def daisy(img, step=4, radius=15, rings=3, histograms=8, orientations=8, bin_size = descs[i, j, o_num] / max_bin dy = sigmas[0] * bin_size * sin(o) dx = sigmas[0] * bin_size * cos(o) - coords = draw.line(desc_y, desc_x, desc_y + dy, - desc_x + dx) + coords = draw.line(desc_y, desc_x, int(desc_y + dy), + int(desc_x + dx)) draw.set_color(descs_img, coords, color) for r_num, r in enumerate(ring_radii): color_offset = float(1 + r_num) / rings @@ -199,7 +199,7 @@ def daisy(img, step=4, radius=15, rings=3, histograms=8, orientations=8, hist_y = desc_y + int(round(r * sin(t))) hist_x = desc_x + int(round(r * cos(t))) coords = draw.circle_perimeter(hist_y, hist_x, - sigmas[r_num + 1]) + int(sigmas[r_num + 1])) draw.set_color(descs_img, coords, color) for o_num, o in enumerate(orientation_angles): # Draw histogram bins @@ -209,8 +209,9 @@ def daisy(img, step=4, radius=15, rings=3, histograms=8, orientations=8, bin_size /= max_bin dy = sigmas[r_num + 1] * bin_size * sin(o) dx = sigmas[r_num + 1] * bin_size * cos(o) - coords = draw.line(hist_y, hist_x, hist_y + dy, - hist_x + dx) + coords = draw.line(hist_y, hist_x, + int(hist_y + dy), + int(hist_x + dx)) draw.set_color(descs_img, coords, color) return descs, descs_img else: diff --git a/skimage/feature/_hog.py b/skimage/feature/_hog.py index 8e8ea7f2..9fa018b7 100644 --- a/skimage/feature/_hog.py +++ b/skimage/feature/_hog.py @@ -142,8 +142,10 @@ def hog(image, orientations=9, pixels_per_cell=(8, 8), centre = tuple([y * cy + cy // 2, x * cx + cx // 2]) dx = radius * cos(float(o) / orientations * np.pi) dy = radius * sin(float(o) / orientations * np.pi) - rr, cc = draw.bresenham(centre[0] - dx, centre[1] - dy, - centre[0] + dx, centre[1] + dy) + rr, cc = draw.bresenham(int(centre[0] - dx), + int(centre[1] - dy), + int(centre[0] + dx), + int(centre[1] + dy)) hog_image[rr, cc] += orientation_histogram[y, x, o] """ diff --git a/skimage/feature/tests/test_hog.py b/skimage/feature/tests/test_hog.py index 6f2d4cdf..cfef2da0 100644 --- a/skimage/feature/tests/test_hog.py +++ b/skimage/feature/tests/test_hog.py @@ -102,7 +102,7 @@ def test_hog_orientations_circle(): width = height = 100 image = np.zeros((height, width)) - rr, cc = draw.circle(height/2, width/2, width/3) + rr, cc = draw.circle(int(height / 2), int(width / 2), int(width / 3)) image[rr, cc] = 100 image = ndimage.gaussian_filter(image, 2) From 572000f0e2908ba66d992fcdf96313caa9634827 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Sat, 16 Feb 2013 07:55:18 +0100 Subject: [PATCH 089/215] Improve code layout of probabilistic hough --- skimage/transform/_hough_transform.pyx | 76 +++++++++++++++----------- 1 file changed, 44 insertions(+), 32 deletions(-) diff --git a/skimage/transform/_hough_transform.pyx b/skimage/transform/_hough_transform.pyx index 01a45a48..f578b28c 100644 --- a/skimage/transform/_hough_transform.pyx +++ b/skimage/transform/_hough_transform.pyx @@ -1,4 +1,5 @@ cimport cython +import math import numpy as np cimport numpy as np from random import randint @@ -60,68 +61,77 @@ def _hough(np.ndarray img, np.ndarray[ndim=1, dtype=np.double_t] theta=None): accum[accum_idx, j] += 1 return accum, theta, bins -import math @cython.cdivision(True) @cython.boundscheck(False) -def _probabilistic_hough(np.ndarray img, int value_threshold, int line_length, \ - int line_gap, np.ndarray[ndim=1, dtype=np.double_t] theta=None): +def _probabilistic_hough(np.ndarray img, int value_threshold, + int line_length, int line_gap, + np.ndarray[ndim=1, dtype=np.double_t] theta=None): + if img.ndim != 2: raise ValueError('The input image must be 2D.') - # compute the array of angles and their sine and cosine - cdef np.ndarray[ndim=1, dtype=np.double_t] ctheta - cdef np.ndarray[ndim=1, dtype=np.double_t] stheta - # calculate thetas if none specified + if theta is None: - theta = np.linspace(math.pi/2, -math.pi/2, 180) - theta = math.pi/2-np.arange(180)/180.0* math.pi - ctheta = np.cos(theta) - stheta = np.sin(theta) + theta = PI_2 - np.arange(180) / 180.0 * 2 * PI_2 + cdef Py_ssize_t height = img.shape[0] cdef Py_ssize_t width = img.shape[1] + # compute the bins and allocate the accumulator array cdef np.ndarray[ndim=2, dtype=np.int64_t] accum - cdef np.ndarray[ndim=2, dtype=np.uint8_t] mask = np.zeros((height, width), dtype=np.uint8) - cdef np.ndarray[ndim=2, dtype=np.int32_t] line_end = np.zeros((2, 2), dtype=np.int32) + cdef np.ndarray[ndim=1, dtype=np.double_t] ctheta, stheta + cdef np.ndarray[ndim=2, dtype=np.uint8_t] mask = \ + np.zeros((height, width), dtype=np.uint8) + cdef np.ndarray[ndim=2, dtype=np.int32_t] line_end = \ + np.zeros((2, 2), dtype=np.int32) cdef Py_ssize_t max_distance, offset, num_indexes, index cdef double a, b - cdef Py_ssize_t nidxs, nthetas, i, j, x, y, px, py, accum_idx + cdef Py_ssize_t nidxs, i, j, x, y, px, py, accum_idx cdef int value, max_value, max_theta cdef int shift = 16 # maximum line number cutoff cdef Py_ssize_t lines_max = 2 ** 15 - cdef Py_ssize_t xflag, x0, y0, dx0, dy0, dx, dy, gap, x1, y1, good_line, count + cdef Py_ssize_t xflag, x0, y0, dx0, dy0, dx, dy, gap, x1, y1, \ + good_line, count + cdef list lines = list() + max_distance = 2 * ceil((sqrt(img.shape[0] * img.shape[0] + img.shape[1] * img.shape[1]))) accum = np.zeros((max_distance, theta.shape[0]), dtype=np.int64) offset = max_distance / 2 - # find the nonzero indexes - cdef np.ndarray[ndim=1, dtype=np.npy_intp] x_idxs, y_idxs - y_idxs, x_idxs = np.nonzero(img) - num_indexes = y_idxs.shape[0] # x and y are the same shape nthetas = theta.shape[0] - points = [] - for i in range(num_indexes): - points.append((x_idxs[i], y_idxs[i])) - lines = [] - # create mask of all non-zero indexes - for i in range(num_indexes): - mask[y_idxs[i], x_idxs[i]] = 1 + + # compute sine and cosine of angles + ctheta = np.cos(theta) + stheta = np.sin(theta) + + # find the nonzero indexes + y_idxs, x_idxs = np.nonzero(img) + points = zip(x_idxs, y_idxs) + # mask all non-zero indexes + mask[y_idxs, x_idxs] = 1 + while 1: - # select random non-zero point + + # quit if no remaining points count = len(points) if count == 0: break - index = rand() % (count) + + # select random non-zero point + index = rand() % count x = points[index][0] y = points[index][1] del points[index] + # if previously eliminated, skip if not mask[y, x]: continue + value = 0 - max_value = value_threshold-1 + max_value = value_threshold - 1 max_theta = -1 + # apply hough transform on point for j in range(nthetas): accum_idx = round((ctheta[j] * x + stheta[j] * y)) + offset @@ -132,7 +142,9 @@ def _probabilistic_hough(np.ndarray img, int value_threshold, int line_length, \ max_theta = j if max_value < value_threshold: continue - # from the random point walk in opposite directions and find line beginning and end + + # from the random point walk in opposite directions and find line + # beginning and end a = -stheta[max_theta] b = ctheta[max_theta] x0 = x @@ -188,6 +200,7 @@ def _probabilistic_hough(np.ndarray img, int value_threshold, int line_length, \ # confirm line length is sufficient good_line = abs(line_end[1, 1] - line_end[0, 1]) >= line_length or \ abs(line_end[1, 0] - line_end[0, 0]) >= line_length + # pass 2: walk the line again and reset accumulator and mask for k in range(2): px = x0 @@ -221,6 +234,5 @@ def _probabilistic_hough(np.ndarray img, int value_threshold, int line_length, \ lines.append(((line_end[0, 0], line_end[0, 1]), (line_end[1, 0], line_end[1, 1]))) if len(lines) > lines_max: return lines + return lines - - From 2d9c211cf25556cc0cb75e1ea6eafc6f2830a86f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Sat, 16 Feb 2013 08:03:12 +0100 Subject: [PATCH 090/215] Remove unused CAPI numpy import and extend cython directives --- skimage/transform/_hough_transform.pyx | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/skimage/transform/_hough_transform.pyx b/skimage/transform/_hough_transform.pyx index f578b28c..e63afa60 100644 --- a/skimage/transform/_hough_transform.pyx +++ b/skimage/transform/_hough_transform.pyx @@ -1,3 +1,7 @@ +#cython: cdivision=True +#cython: boundscheck=False +#cython: nonecheck=False +#cython: wraparound=False cimport cython import math import numpy as np @@ -7,9 +11,6 @@ from libc.math cimport abs, fabs, sqrt, ceil, floor from libc.stdlib cimport rand -np.import_array() - - cdef double PI_2 = 1.5707963267948966 cdef double NEG_PI_2 = -PI_2 @@ -18,7 +19,6 @@ cdef inline Py_ssize_t round(double r): return ((r + 0.5) if (r > 0.0) else (r - 0.5)) -@cython.boundscheck(False) def _hough(np.ndarray img, np.ndarray[ndim=1, dtype=np.double_t] theta=None): if img.ndim != 2: @@ -62,8 +62,6 @@ def _hough(np.ndarray img, np.ndarray[ndim=1, dtype=np.double_t] theta=None): return accum, theta, bins -@cython.cdivision(True) -@cython.boundscheck(False) def _probabilistic_hough(np.ndarray img, int value_threshold, int line_length, int line_gap, np.ndarray[ndim=1, dtype=np.double_t] theta=None): From aedd12faebc6ca275fc40df419546e6afe054e93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Sat, 16 Feb 2013 08:09:13 +0100 Subject: [PATCH 091/215] Remove unused imports --- skimage/transform/_hough_transform.pyx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/skimage/transform/_hough_transform.pyx b/skimage/transform/_hough_transform.pyx index e63afa60..f2a22d15 100644 --- a/skimage/transform/_hough_transform.pyx +++ b/skimage/transform/_hough_transform.pyx @@ -3,11 +3,9 @@ #cython: nonecheck=False #cython: wraparound=False cimport cython -import math import numpy as np cimport numpy as np -from random import randint -from libc.math cimport abs, fabs, sqrt, ceil, floor +from libc.math cimport abs, fabs, sqrt, ceil from libc.stdlib cimport rand @@ -40,7 +38,7 @@ def _hough(np.ndarray img, np.ndarray[ndim=1, dtype=np.double_t] theta=None): cdef Py_ssize_t max_distance, offset max_distance = 2 * ceil(sqrt(img.shape[0] * img.shape[0] + - img.shape[1] * img.shape[1])) + img.shape[1] * img.shape[1])) accum = np.zeros((max_distance, theta.shape[0]), dtype=np.uint64) bins = np.linspace(-max_distance / 2.0, max_distance / 2.0, max_distance) offset = max_distance / 2 From 6cb47a6e3b4594273257261ce47ad844a68bdd2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Sat, 16 Feb 2013 08:11:00 +0100 Subject: [PATCH 092/215] Make list of zip in Python 3 explicitely --- skimage/transform/_hough_transform.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skimage/transform/_hough_transform.pyx b/skimage/transform/_hough_transform.pyx index f2a22d15..d5483895 100644 --- a/skimage/transform/_hough_transform.pyx +++ b/skimage/transform/_hough_transform.pyx @@ -103,7 +103,7 @@ def _probabilistic_hough(np.ndarray img, int value_threshold, # find the nonzero indexes y_idxs, x_idxs = np.nonzero(img) - points = zip(x_idxs, y_idxs) + points = list(zip(x_idxs, y_idxs)) # mask all non-zero indexes mask[y_idxs, x_idxs] = 1 From b716d8b1ed203700db9f8012ca6f1f5aca36345d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Sat, 16 Feb 2013 08:22:39 +0100 Subject: [PATCH 093/215] Wrap lines --- skimage/transform/_hough_transform.pyx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/skimage/transform/_hough_transform.pyx b/skimage/transform/_hough_transform.pyx index d5483895..662b3b88 100644 --- a/skimage/transform/_hough_transform.pyx +++ b/skimage/transform/_hough_transform.pyx @@ -216,7 +216,8 @@ def _probabilistic_hough(np.ndarray img, int value_threshold, # if non-zero point found, continue the line if mask[y1, x1]: if good_line: - accum_idx = round((ctheta[j] * x1 + stheta[j] * y1)) + offset + accum_idx = round((ctheta[j] * x1 \ + + stheta[j] * y1)) + offset accum[accum_idx, max_theta] -= 1 mask[y1, x1] = 0 # exit when the point is the line end @@ -227,7 +228,8 @@ def _probabilistic_hough(np.ndarray img, int value_threshold, # add line to the result if good_line: - lines.append(((line_end[0, 0], line_end[0, 1]), (line_end[1, 0], line_end[1, 1]))) + lines.append(((line_end[0, 0], line_end[0, 1]), + (line_end[1, 0], line_end[1, 1]))) if len(lines) > lines_max: return lines From a9ec085cc41a95b36680f4a20a450fde18493f53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Sun, 24 Feb 2013 12:22:24 +0100 Subject: [PATCH 094/215] Add note about proper use of Py_ssize_t --- DEVELOPMENT.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/DEVELOPMENT.txt b/DEVELOPMENT.txt index 8726394b..daffe705 100644 --- a/DEVELOPMENT.txt +++ b/DEVELOPMENT.txt @@ -81,6 +81,9 @@ Stylistic Guidelines hough(canny(my_image)) + * Use `Py_ssize_t` as data type for all indexing, shape and size variables in + C/C++ and Cython code. + Test coverage ------------- From 62d83ad42cf30855ecd969104e0c0285018c09eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Sun, 24 Feb 2013 14:14:14 +0100 Subject: [PATCH 095/215] Globally change np to cnp if cimported --- skimage/draw/_draw.pyx | 14 +- skimage/feature/_template.pyx | 26 +- skimage/feature/_texture.pyx | 36 +- skimage/filter/_ctmf.pyx | 77 ++-- skimage/filter/rank/_core16.pxd | 19 +- skimage/filter/rank/_core16.pyx | 27 +- skimage/filter/rank/_core8.pxd | 27 +- skimage/filter/rank/_core8.pyx | 35 +- skimage/filter/rank/_crank16.pyx | 348 ++++++++++--------- skimage/filter/rank/_crank16_bilateral.pyx | 52 +-- skimage/filter/rank/_crank16_percentiles.pyx | 209 +++++------ skimage/filter/rank/_crank8.pyx | 316 ++++++++--------- skimage/filter/rank/_crank8_percentiles.pyx | 170 ++++----- skimage/graph/_mcp.pxd | 8 +- skimage/graph/_mcp.pyx | 48 +-- skimage/io/_plugins/_colormixer.pyx | 149 ++++---- skimage/io/_plugins/_histograms.pyx | 15 +- skimage/measure/_find_contours.pyx | 13 +- skimage/measure/_moments.pyx | 24 +- skimage/morphology/_convex_hull.pyx | 14 +- skimage/morphology/_pnpoly.pyx | 21 +- skimage/morphology/_skeletonize_cy.pyx | 51 +-- skimage/morphology/ccomp.pxd | 6 +- skimage/morphology/ccomp.pyx | 13 +- skimage/morphology/heap_watershed.pxi | 8 +- skimage/segmentation/_felzenszwalb_cy.pyx | 30 +- skimage/segmentation/_quickshift.pyx | 28 +- skimage/segmentation/_slic.pyx | 29 +- skimage/transform/_hough_transform.pyx | 28 +- skimage/transform/_warps_cy.pyx | 12 +- 30 files changed, 953 insertions(+), 900 deletions(-) diff --git a/skimage/draw/_draw.pyx b/skimage/draw/_draw.pyx index efd5fdc5..2b319094 100644 --- a/skimage/draw/_draw.pyx +++ b/skimage/draw/_draw.pyx @@ -2,11 +2,11 @@ #cython: boundscheck=False #cython: nonecheck=False #cython: wraparound=False -cimport cython -cimport numpy as np -from libc.math cimport sqrt import math import numpy as np + +cimport numpy as cnp +from libc.math cimport sqrt from skimage._shared.geometry cimport point_in_polygon @@ -29,7 +29,7 @@ def line(Py_ssize_t y, Py_ssize_t x, Py_ssize_t y2, Py_ssize_t x2): """ - cdef np.ndarray[np.intp_t, ndim=1, mode="c"] rr, cc + cdef cnp.ndarray[cnp.intp_t, ndim=1, mode="c"] rr, cc cdef char steep = 0 cdef Py_ssize_t dx = abs(x2 - x) @@ -110,11 +110,11 @@ def polygon(y, x, shape=None): cdef Py_ssize_t r, c #: make contigous arrays for r, c coordinates - cdef np.ndarray contiguous_rdata, contiguous_cdata + cdef cnp.ndarray contiguous_rdata, contiguous_cdata contiguous_rdata = np.ascontiguousarray(y, 'double') contiguous_cdata = np.ascontiguousarray(x, 'double') - cdef np.double_t* rptr = contiguous_rdata.data - cdef np.double_t* cptr = contiguous_cdata.data + cdef cnp.double_t* rptr = contiguous_rdata.data + cdef cnp.double_t* cptr = contiguous_cdata.data #: output coordinate arrays cdef list rr = list() diff --git a/skimage/feature/_template.pyx b/skimage/feature/_template.pyx index 842e1eda..03695959 100644 --- a/skimage/feature/_template.pyx +++ b/skimage/feature/_template.pyx @@ -1,3 +1,8 @@ +#cython: cdivision=True +#cython: boundscheck=False +#cython: nonecheck=False +#cython: wraparound=False + """ Template matching using normalized cross-correlation. @@ -30,22 +35,24 @@ the image window *before* squaring.) .. [2] J. P. Lewis, "Fast Normalized Cross-Correlation", Industrial Light and Magic. """ -import cython -cimport numpy as np + import numpy as np from scipy.signal import fftconvolve -from skimage.transform import integral + +cimport numpy as cnp from libc.math cimport sqrt, fabs from skimage._shared.transform cimport integrate -@cython.boundscheck(False) -def match_template(np.ndarray[float, ndim=2, mode="c"] image, - np.ndarray[float, ndim=2, mode="c"] template): +from skimage.transform import integral - cdef np.ndarray[float, ndim=2, mode="c"] corr - cdef np.ndarray[float, ndim=2, mode="c"] image_sat - cdef np.ndarray[float, ndim=2, mode="c"] image_sqr_sat + +def match_template(cnp.ndarray[float, ndim=2, mode="c"] image, + cnp.ndarray[float, ndim=2, mode="c"] template): + + cdef cnp.ndarray[float, ndim=2, mode="c"] corr + cdef cnp.ndarray[float, ndim=2, mode="c"] image_sat + cdef cnp.ndarray[float, ndim=2, mode="c"] image_sqr_sat cdef float template_mean = np.mean(template) cdef float template_ssd cdef float inv_area @@ -88,4 +95,3 @@ def match_template(np.ndarray[float, ndim=2, mode="c"] image, corr[r, c] /= den return corr - diff --git a/skimage/feature/_texture.pyx b/skimage/feature/_texture.pyx index ffff38d7..f98ed4ca 100644 --- a/skimage/feature/_texture.pyx +++ b/skimage/feature/_texture.pyx @@ -3,20 +3,20 @@ #cython: nonecheck=False #cython: wraparound=False import numpy as np -cimport numpy as np +cimport numpy as cnp from libc.math cimport sin, cos, abs from skimage._shared.interpolation cimport bilinear_interpolation -def _glcm_loop(np.ndarray[dtype=np.uint8_t, ndim=2, - negative_indices=False, mode='c'] image, - np.ndarray[dtype=np.float64_t, ndim=1, - negative_indices=False, mode='c'] distances, - np.ndarray[dtype=np.float64_t, ndim=1, - negative_indices=False, mode='c'] angles, +def _glcm_loop(cnp.ndarray[dtype=cnp.uint8_t, ndim=2, + negative_indices=False, mode='c'] image, + cnp.ndarray[dtype=cnp.float64_t, ndim=1, + negative_indices=False, mode='c'] distances, + cnp.ndarray[dtype=cnp.float64_t, ndim=1, + negative_indices=False, mode='c'] angles, int levels, - np.ndarray[dtype=np.uint32_t, ndim=4, - negative_indices=False, mode='c'] out): + cnp.ndarray[dtype=cnp.uint32_t, ndim=4, + negative_indices=False, mode='c'] out): """Perform co-occurrence matrix accumulation. Parameters @@ -39,8 +39,8 @@ def _glcm_loop(np.ndarray[dtype=np.uint8_t, ndim=2, cdef: Py_ssize_t a_idx, d_idx, r, c, rows, cols, row, col - np.uint8_t i, j - np.float64_t angle, distance + cnp.uint8_t i, j + cnp.float64_t angle, distance rows = image.shape[0] cols = image.shape[1] @@ -81,7 +81,7 @@ cdef inline int _bit_rotate_right(int value, int length): return (value >> 1) | ((value & 1) << (length - 1)) -def _local_binary_pattern(np.ndarray[double, ndim=2] image, +def _local_binary_pattern(cnp.ndarray[double, ndim=2] image, int P, float R, char method='D'): """Gray scale and rotation invariant LBP (Local Binary Patterns). @@ -111,19 +111,19 @@ def _local_binary_pattern(np.ndarray[double, ndim=2] image, """ # texture weights - cdef np.ndarray[int, ndim=1] weights = 2 ** np.arange(P, dtype=np.int32) + cdef cnp.ndarray[int, ndim=1] weights = 2 ** np.arange(P, dtype=np.int32) # local position of texture elements rp = - R * np.sin(2 * np.pi * np.arange(P, dtype=np.double) / P) cp = R * np.cos(2 * np.pi * np.arange(P, dtype=np.double) / P) - cdef np.ndarray[double, ndim=2] coords = np.round(np.vstack([rp, cp]).T, 5) + cdef cnp.ndarray[double, ndim=2] coords = np.round(np.vstack([rp, cp]).T, 5) # pre allocate arrays for computation - cdef np.ndarray[double, ndim=1] texture = np.zeros(P, np.double) - cdef np.ndarray[char, ndim=1] signed_texture = np.zeros(P, np.int8) - cdef np.ndarray[int, ndim=1] rotation_chain = np.zeros(P, np.int32) + cdef cnp.ndarray[double, ndim=1] texture = np.zeros(P, np.double) + cdef cnp.ndarray[char, ndim=1] signed_texture = np.zeros(P, np.int8) + cdef cnp.ndarray[int, ndim=1] rotation_chain = np.zeros(P, np.int32) output_shape = (image.shape[0], image.shape[1]) - cdef np.ndarray[double, ndim=2] output = np.zeros(output_shape, np.double) + cdef cnp.ndarray[double, ndim=2] output = np.zeros(output_shape, np.double) cdef Py_ssize_t rows = image.shape[0] cdef Py_ssize_t cols = image.shape[1] diff --git a/skimage/filter/_ctmf.pyx b/skimage/filter/_ctmf.pyx index 65feb9e4..1a03ff5a 100644 --- a/skimage/filter/_ctmf.pyx +++ b/skimage/filter/_ctmf.pyx @@ -10,18 +10,20 @@ Copyright (c) 2009-2011 Broad Institute All rights reserved. Original author: Lee Kamentsky ''' + import numpy as np -cimport numpy as np + +cimport numpy as cnp cimport cython from libc.stdlib cimport malloc, free from libc.string cimport memset -cdef extern from "../_shared/vectorized_ops.h": - void add16(np.uint16_t *dest, np.uint16_t *src) - void sub16(np.uint16_t *dest, np.uint16_t *src) -np.import_array() +cdef extern from "../_shared/vectorized_ops.h": + void add16(cnp.uint16_t *dest, cnp.uint16_t *src) + void sub16(cnp.uint16_t *dest, cnp.uint16_t *src) + ############################################################################## # @@ -43,7 +45,7 @@ np.import_array() DTYPE_UINT32 = np.uint32 DTYPE_BOOL = np.bool -ctypedef np.uint16_t pixel_count_t +ctypedef cnp.uint16_t pixel_count_t ########### # @@ -58,8 +60,8 @@ ctypedef np.uint16_t pixel_count_t ########### cdef struct HistogramPiece: - np.uint16_t coarse[16] - np.uint16_t fine[256] + cnp.uint16_t coarse[16] + cnp.uint16_t fine[256] cdef struct Histogram: HistogramPiece top_left # top-left corner @@ -92,9 +94,9 @@ cdef struct Histograms: void *memory # pointer to the allocated memory Histogram *histogram # pointer to the histogram memory PixelCount *pixel_count # pointer to the pixel count memory - np.uint8_t *data # pointer to the image data - np.uint8_t *mask # pointer to the image mask - np.uint8_t *output # pointer to the output array + cnp.uint8_t *data # pointer to the image data + cnp.uint8_t *mask # pointer to the image mask + cnp.uint8_t *output # pointer to the output array Py_ssize_t column_count # number of columns represented by this # structure Py_ssize_t stripe_length # number of columns including "radius" before @@ -172,15 +174,15 @@ cdef Histograms *allocate_histograms(Py_ssize_t rows, Py_ssize_t col_stride, Py_ssize_t radius, Py_ssize_t percent, - np.uint8_t *data, - np.uint8_t *mask, - np.uint8_t *output): + cnp.uint8_t *data, + cnp.uint8_t *mask, + cnp.uint8_t *output): cdef: Py_ssize_t adjusted_stripe_length = columns + 2*radius + 1 Py_ssize_t memory_size void *ptr Histograms *ph - size_t roundoff + Py_ssize_t roundoff Py_ssize_t a SCoord *psc @@ -232,7 +234,7 @@ cdef Histograms *allocate_histograms(Py_ssize_t rows, # a_2 is the offset from the center to each of the octagon # corners # - a = (radius * 2.0 / 2.414213) + a = (radius * 2.0 / 2.414213) a_2 = a / 2 if a_2 == 0: a_2 = 1 @@ -456,7 +458,7 @@ cdef inline void deaccumulate_fine_histogram(Histograms *ph, ############################################################################ cdef inline void accumulate(Histograms *ph): - cdef np.int32_t accumulator + cdef cnp.int32_t accumulator accumulate_coarse_histogram(ph, ph.current_column) deaccumulate_coarse_histogram(ph, ph.current_column) @@ -514,7 +516,7 @@ cdef inline void update_histogram(Histograms *ph, Py_ssize_t current_stride = ph.current_stride Py_ssize_t column_count = ph.column_count Py_ssize_t row_count = ph.row_count - np.uint8_t value + cnp.uint8_t value Py_ssize_t stride Py_ssize_t x Py_ssize_t y @@ -557,8 +559,8 @@ cdef inline void update_current_location(Histograms *ph): Py_ssize_t bottom_left_off = tr_bl_colidx(ph, current_column) Py_ssize_t bottom_right_off = tl_br_colidx(ph, current_column) Py_ssize_t leading_edge_off = leading_edge_colidx(ph, current_column) - np.int32_t *coarse_histogram - np.int32_t *fine_histogram + cnp.int32_t *coarse_histogram + cnp.int32_t *fine_histogram Py_ssize_t last_xoff Py_ssize_t last_yoff Py_ssize_t last_stride @@ -597,13 +599,13 @@ cdef inline void update_current_location(Histograms *ph): # ############################################################################ -cdef inline np.uint8_t find_median(Histograms *ph): +cdef inline cnp.uint8_t find_median(Histograms *ph): cdef: Py_ssize_t pixels_below # of pixels below the median Py_ssize_t i Py_ssize_t j Py_ssize_t k - np.uint32_t accumulator + cnp.uint32_t accumulator if ph.accumulator_count == 0: return 0 @@ -625,7 +627,7 @@ cdef inline np.uint8_t find_median(Histograms *ph): for j in range(i*16, (i + 1)*16): accumulator += ph.accumulator.fine[j] if accumulator > pixels_below: - return j + return j return 0 @@ -650,9 +652,9 @@ cdef int c_median_filter(Py_ssize_t rows, Py_ssize_t col_stride, Py_ssize_t radius, Py_ssize_t percent, - np.uint8_t *data, - np.uint8_t *mask, - np.uint8_t *output): + cnp.uint8_t *data, + cnp.uint8_t *mask, + cnp.uint8_t *output): cdef: Histograms *ph Histogram *phistogram @@ -731,11 +733,14 @@ cdef int c_median_filter(Py_ssize_t rows, return 0 -def median_filter(np.ndarray[dtype=np.uint8_t, ndim=2, negative_indices=False, mode='c'] data, - np.ndarray[dtype=np.uint8_t, ndim=2, negative_indices=False, mode='c'] mask, - np.ndarray[dtype=np.uint8_t, ndim=2, negative_indices=False, mode='c'] output, +def median_filter(cnp.ndarray[dtype=cnp.uint8_t, ndim=2, + negative_indices=False, mode='c'] data, + cnp.ndarray[dtype=cnp.uint8_t, ndim=2, + negative_indices=False, mode='c'] mask, + cnp.ndarray[dtype=cnp.uint8_t, ndim=2, + negative_indices=False, mode='c'] output, int radius, - np.int32_t percent): + cnp.int32_t percent): """Median filter with octagon shape and masking. Parameters @@ -770,10 +775,12 @@ def median_filter(np.ndarray[dtype=np.uint8_t, ndim=2, negative_indices=False, m raise ValueError('Data shape (%d, %d) is not output shape (%d, %d)' % (data.shape[0], data.shape[1], output.shape[0], output.shape[1])) - if c_median_filter( data.shape[0], data.shape[1], - data.strides[0], data.strides[1], + if c_median_filter(data.shape[0], + data.shape[1], + data.strides[0], + data.strides[1], radius, percent, - data.data, - mask.data, - output.data): + data.data, + mask.data, + output.data): raise MemoryError('Failed to allocate scratchpad memory') diff --git a/skimage/filter/rank/_core16.pxd b/skimage/filter/rank/_core16.pxd index 5f7de9df..5586aea1 100644 --- a/skimage/filter/rank/_core16.pxd +++ b/skimage/filter/rank/_core16.pxd @@ -1,4 +1,7 @@ -cimport numpy as np +cimport numpy as cnp + + +ctypedef cnp.uint16_t dtype_t cdef int int_max(int a, int b) @@ -6,12 +9,12 @@ cdef int int_min(int a, int b) # 16-bit core kernel receives extra information about data bitdepth -cdef void _core16(np.uint16_t kernel(Py_ssize_t *, float, np.uint16_t, - Py_ssize_t, Py_ssize_t, Py_ssize_t, float, - float, Py_ssize_t, Py_ssize_t), - np.ndarray[np.uint16_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask, - np.ndarray[np.uint16_t, ndim=2] out, +cdef void _core16(dtype_t kernel(Py_ssize_t *, float, dtype_t, + Py_ssize_t, Py_ssize_t, Py_ssize_t, float, + float, Py_ssize_t, Py_ssize_t), + cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[cnp.uint8_t, ndim=2] selem, + cnp.ndarray[cnp.uint8_t, ndim=2] mask, + cnp.ndarray[dtype_t, ndim=2] out, char shift_x, char shift_y, Py_ssize_t bitdepth, float p0, float p1, Py_ssize_t s0, Py_ssize_t s1) except * diff --git a/skimage/filter/rank/_core16.pyx b/skimage/filter/rank/_core16.pyx index aa959fd0..36bc500d 100644 --- a/skimage/filter/rank/_core16.pyx +++ b/skimage/filter/rank/_core16.pyx @@ -4,7 +4,8 @@ #cython: wraparound=False import numpy as np -cimport numpy as np + +cimport numpy as cnp from libc.stdlib cimport malloc, free from _core8 cimport is_in_mask @@ -18,24 +19,24 @@ cdef inline int int_min(int a, int b): cdef inline void histogram_increment(Py_ssize_t * histo, float * pop, - np.uint16_t value): + dtype_t value): histo[value] += 1 pop[0] += 1 cdef inline void histogram_decrement(Py_ssize_t * histo, float * pop, - np.uint16_t value): + dtype_t value): histo[value] -= 1 pop[0] -= 1 -cdef void _core16(np.uint16_t kernel(Py_ssize_t *, float, np.uint16_t, - Py_ssize_t, Py_ssize_t, Py_ssize_t, float, - float, Py_ssize_t, Py_ssize_t), - np.ndarray[np.uint16_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask, - np.ndarray[np.uint16_t, ndim=2] out, +cdef void _core16(dtype_t kernel(Py_ssize_t *, float, dtype_t, + Py_ssize_t, Py_ssize_t, Py_ssize_t, float, + float, Py_ssize_t, Py_ssize_t), + cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[cnp.uint8_t, ndim=2] selem, + cnp.ndarray[cnp.uint8_t, ndim=2] mask, + cnp.ndarray[dtype_t, ndim=2] out, char shift_x, char shift_y, Py_ssize_t bitdepth, float p0, float p1, Py_ssize_t s0, Py_ssize_t s1) except *: """Compute histogram for each pixel neighborhood, apply kernel function and @@ -67,9 +68,9 @@ cdef void _core16(np.uint16_t kernel(Py_ssize_t *, float, np.uint16_t, assert (image < maxbin).all() # define pointers to the data - cdef np.uint16_t * out_data = out.data - cdef np.uint16_t * image_data = image.data - cdef np.uint8_t * mask_data = mask.data + cdef dtype_t * out_data = out.data + cdef dtype_t * image_data = image.data + cdef cnp.uint8_t * mask_data = mask.data # define local variable types cdef Py_ssize_t r, c, rr, cc, s, value, local_max, i, even_row diff --git a/skimage/filter/rank/_core8.pxd b/skimage/filter/rank/_core8.pxd index 38236b87..d3b6d8c2 100644 --- a/skimage/filter/rank/_core8.pxd +++ b/skimage/filter/rank/_core8.pxd @@ -1,22 +1,25 @@ -cimport numpy as np +cimport numpy as cnp -cdef np.uint8_t uint8_max(np.uint8_t a, np.uint8_t b) -cdef np.uint8_t uint8_min(np.uint8_t a, np.uint8_t b) +ctypedef cnp.uint8_t dtype_t -cdef np.uint8_t is_in_mask(Py_ssize_t rows, Py_ssize_t cols, - Py_ssize_t r, Py_ssize_t c, - np.uint8_t * mask) +cdef dtype_t uint8_max(dtype_t a, dtype_t b) +cdef dtype_t uint8_min(dtype_t a, dtype_t b) + + +cdef dtype_t is_in_mask(Py_ssize_t rows, Py_ssize_t cols, + Py_ssize_t r, Py_ssize_t c, + dtype_t * mask) # 8-bit core kernel receives extra information about data inferior and superior # percentiles -cdef void _core8(np.uint8_t kernel(Py_ssize_t *, float, np.uint8_t, float, - float, Py_ssize_t, Py_ssize_t), - np.ndarray[np.uint8_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask, - np.ndarray[np.uint8_t, ndim=2] out, +cdef void _core8(dtype_t kernel(Py_ssize_t *, float, dtype_t, float, + float, Py_ssize_t, Py_ssize_t), + cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[dtype_t, ndim=2] selem, + cnp.ndarray[dtype_t, ndim=2] mask, + cnp.ndarray[dtype_t, ndim=2] out, char shift_x, char shift_y, float p0, float p1, Py_ssize_t s0, Py_ssize_t s1) except * diff --git a/skimage/filter/rank/_core8.pyx b/skimage/filter/rank/_core8.pyx index 9f4bc693..eca47891 100644 --- a/skimage/filter/rank/_core8.pyx +++ b/skimage/filter/rank/_core8.pyx @@ -4,33 +4,34 @@ #cython: wraparound=False import numpy as np -cimport numpy as np + +cimport numpy as cnp from libc.stdlib cimport malloc, free -cdef inline np.uint8_t uint8_max(np.uint8_t a, np.uint8_t b): +cdef inline dtype_t uint8_max(dtype_t a, dtype_t b): return a if a >= b else b -cdef inline np.uint8_t uint8_min(np.uint8_t a, np.uint8_t b): +cdef inline dtype_t uint8_min(dtype_t a, dtype_t b): return a if a <= b else b cdef inline void histogram_increment(Py_ssize_t * histo, float * pop, - np.uint8_t value): + dtype_t value): histo[value] += 1 pop[0] += 1 cdef inline void histogram_decrement(Py_ssize_t * histo, float * pop, - np.uint8_t value): + dtype_t value): histo[value] -= 1 pop[0] -= 1 -cdef inline np.uint8_t is_in_mask(Py_ssize_t rows, Py_ssize_t cols, - Py_ssize_t r, Py_ssize_t c, - np.uint8_t * mask): +cdef inline dtype_t is_in_mask(Py_ssize_t rows, Py_ssize_t cols, + Py_ssize_t r, Py_ssize_t c, + dtype_t * mask): """Check whether given coordinate is within image and mask is true.""" if r < 0 or r > rows - 1 or c < 0 or c > cols - 1: return 0 @@ -41,12 +42,12 @@ cdef inline np.uint8_t is_in_mask(Py_ssize_t rows, Py_ssize_t cols, return 0 -cdef void _core8(np.uint8_t kernel(Py_ssize_t *, float, np.uint8_t, float, - float, Py_ssize_t, Py_ssize_t), - np.ndarray[np.uint8_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask, - np.ndarray[np.uint8_t, ndim=2] out, +cdef void _core8(dtype_t kernel(Py_ssize_t *, float, dtype_t, float, + float, Py_ssize_t, Py_ssize_t), + cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[dtype_t, ndim=2] selem, + cnp.ndarray[dtype_t, ndim=2] mask, + cnp.ndarray[dtype_t, ndim=2] out, char shift_x, char shift_y, float p0, float p1, Py_ssize_t s0, Py_ssize_t s1) except *: """Compute histogram for each pixel neighborhood, apply kernel function and @@ -69,9 +70,9 @@ cdef void _core8(np.uint8_t kernel(Py_ssize_t *, float, np.uint8_t, float, # define pointers to the data - cdef np.uint8_t * out_data = out.data - cdef np.uint8_t * image_data = image.data - cdef np.uint8_t * mask_data = mask.data + cdef dtype_t * out_data = out.data + cdef dtype_t * image_data = image.data + cdef dtype_t * mask_data = mask.data # define local variable types cdef Py_ssize_t r, c, rr, cc, s, value, local_max, i, even_row diff --git a/skimage/filter/rank/_crank16.pyx b/skimage/filter/rank/_crank16.pyx index e81bf81f..87199e80 100644 --- a/skimage/filter/rank/_crank16.pyx +++ b/skimage/filter/rank/_crank16.pyx @@ -3,8 +3,7 @@ #cython: nonecheck=False #cython: wraparound=False -import numpy as np -cimport numpy as np +cimport numpy as cnp from libc.math cimport log2 from skimage.filter.rank._core16 cimport _core16 @@ -14,11 +13,14 @@ from skimage.filter.rank._core16 cimport _core16 # ----------------------------------------------------------------- -cdef inline np.uint16_t kernel_autolevel(Py_ssize_t * histo, float pop, - np.uint16_t g, Py_ssize_t bitdepth, - Py_ssize_t maxbin, Py_ssize_t midbin, - float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +ctypedef cnp.uint16_t dtype_t + + +cdef inline dtype_t kernel_autolevel(Py_ssize_t * histo, float pop, + dtype_t g, Py_ssize_t bitdepth, + Py_ssize_t maxbin, Py_ssize_t midbin, + float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef Py_ssize_t i, imin, imax, delta if pop: @@ -32,16 +34,16 @@ cdef inline np.uint16_t kernel_autolevel(Py_ssize_t * histo, float pop, break delta = imax - imin if delta > 0: - return < np.uint16_t > (1. * (maxbin - 1) * (g - imin) / delta) + return (1. * (maxbin - 1) * (g - imin) / delta) else: - return < np.uint16_t > (imax - imin) + return (imax - imin) -cdef inline np.uint16_t kernel_bottomhat(Py_ssize_t * histo, float pop, - np.uint16_t g, Py_ssize_t bitdepth, - Py_ssize_t maxbin, Py_ssize_t midbin, - float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_bottomhat(Py_ssize_t * histo, float pop, + dtype_t g, Py_ssize_t bitdepth, + Py_ssize_t maxbin, Py_ssize_t midbin, + float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef Py_ssize_t i if pop: @@ -49,15 +51,15 @@ cdef inline np.uint16_t kernel_bottomhat(Py_ssize_t * histo, float pop, if histo[i]: break - return < np.uint16_t > (g - i) + return (g - i) else: - return < np.uint16_t > (0) + return (0) -cdef inline np.uint16_t kernel_equalize(Py_ssize_t * histo, float pop, - np.uint16_t g, Py_ssize_t bitdepth, - Py_ssize_t maxbin, Py_ssize_t midbin, - float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_equalize(Py_ssize_t * histo, float pop, + dtype_t g, Py_ssize_t bitdepth, + Py_ssize_t maxbin, Py_ssize_t midbin, + float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef Py_ssize_t i cdef float sum = 0. @@ -67,16 +69,16 @@ cdef inline np.uint16_t kernel_equalize(Py_ssize_t * histo, float pop, if i >= g: break - return < np.uint16_t > (((maxbin - 1) * sum) / pop) + return (((maxbin - 1) * sum) / pop) else: - return < np.uint16_t > (0) + return (0) -cdef inline np.uint16_t kernel_gradient(Py_ssize_t * histo, float pop, - np.uint16_t g, Py_ssize_t bitdepth, - Py_ssize_t maxbin, Py_ssize_t midbin, - float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_gradient(Py_ssize_t * histo, float pop, + dtype_t g, Py_ssize_t bitdepth, + Py_ssize_t maxbin, Py_ssize_t midbin, + float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef Py_ssize_t i, imin, imax if pop: @@ -88,66 +90,66 @@ cdef inline np.uint16_t kernel_gradient(Py_ssize_t * histo, float pop, if histo[i]: imin = i break - return < np.uint16_t > (imax - imin) + return (imax - imin) else: - return < np.uint16_t > (0) + return (0) -cdef inline np.uint16_t kernel_maximum(Py_ssize_t * histo, float pop, - np.uint16_t g, Py_ssize_t bitdepth, - Py_ssize_t maxbin, Py_ssize_t midbin, - float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_maximum(Py_ssize_t * histo, float pop, + dtype_t g, Py_ssize_t bitdepth, + Py_ssize_t maxbin, Py_ssize_t midbin, + float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef Py_ssize_t i if pop: for i in range(maxbin - 1, -1, -1): if histo[i]: - return < np.uint16_t > (i) + return (i) - return < np.uint16_t > (0) + return (0) -cdef inline np.uint16_t kernel_mean(Py_ssize_t * histo, float pop, - np.uint16_t g, Py_ssize_t bitdepth, - Py_ssize_t maxbin, Py_ssize_t midbin, - float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_mean(Py_ssize_t * histo, float pop, + dtype_t g, Py_ssize_t bitdepth, + Py_ssize_t maxbin, Py_ssize_t midbin, + float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef Py_ssize_t i cdef float mean = 0. if pop: for i in range(maxbin): mean += histo[i] * i - return < np.uint16_t > (mean / pop) + return (mean / pop) else: - return < np.uint16_t > (0) + return (0) -cdef inline np.uint16_t kernel_meansubstraction(Py_ssize_t * histo, - float pop, - np.uint16_t g, - Py_ssize_t bitdepth, - Py_ssize_t maxbin, - Py_ssize_t midbin, - float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_meansubstraction(Py_ssize_t * histo, + float pop, + dtype_t g, + Py_ssize_t bitdepth, + Py_ssize_t maxbin, + Py_ssize_t midbin, + float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef Py_ssize_t i cdef float mean = 0. if pop: for i in range(maxbin): mean += histo[i] * i - return < np.uint16_t > ((g - mean / pop) / 2. + (midbin - 1)) + return ((g - mean / pop) / 2. + (midbin - 1)) else: - return < np.uint16_t > (0) + return (0) -cdef inline np.uint16_t kernel_median(Py_ssize_t * histo, float pop, - np.uint16_t g, Py_ssize_t bitdepth, - Py_ssize_t maxbin, Py_ssize_t midbin, - float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_median(Py_ssize_t * histo, float pop, + dtype_t g, Py_ssize_t bitdepth, + Py_ssize_t maxbin, Py_ssize_t midbin, + float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef Py_ssize_t i cdef float sum = pop / 2.0 @@ -156,31 +158,31 @@ cdef inline np.uint16_t kernel_median(Py_ssize_t * histo, float pop, if histo[i]: sum -= histo[i] if sum < 0: - return < np.uint16_t > (i) + return (i) else: - return < np.uint16_t > (0) + return (0) -cdef inline np.uint16_t kernel_minimum(Py_ssize_t * histo, float pop, - np.uint16_t g, Py_ssize_t bitdepth, - Py_ssize_t maxbin, Py_ssize_t midbin, - float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_minimum(Py_ssize_t * histo, float pop, + dtype_t g, Py_ssize_t bitdepth, + Py_ssize_t maxbin, Py_ssize_t midbin, + float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef Py_ssize_t i if pop: for i in range(maxbin): if histo[i]: - return < np.uint16_t > (i) + return (i) else: - return < np.uint16_t > (0) + return (0) -cdef inline np.uint16_t kernel_modal(Py_ssize_t * histo, float pop, - np.uint16_t g, Py_ssize_t bitdepth, - Py_ssize_t maxbin, Py_ssize_t midbin, - float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_modal(Py_ssize_t * histo, float pop, + dtype_t g, Py_ssize_t bitdepth, + Py_ssize_t maxbin, Py_ssize_t midbin, + float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef Py_ssize_t hmax = 0, imax = 0 if pop: @@ -188,19 +190,19 @@ cdef inline np.uint16_t kernel_modal(Py_ssize_t * histo, float pop, if histo[i] > hmax: hmax = histo[i] imax = i - return < np.uint16_t > (imax) + return (imax) else: - return < np.uint16_t > (0) + return (0) -cdef inline np.uint16_t kernel_morph_contr_enh(Py_ssize_t * histo, - float pop, - np.uint16_t g, - Py_ssize_t bitdepth, - Py_ssize_t maxbin, - Py_ssize_t midbin, - float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_morph_contr_enh(Py_ssize_t * histo, + float pop, + dtype_t g, + Py_ssize_t bitdepth, + Py_ssize_t maxbin, + Py_ssize_t midbin, + float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef Py_ssize_t i, imin, imax if pop: @@ -213,42 +215,42 @@ cdef inline np.uint16_t kernel_morph_contr_enh(Py_ssize_t * histo, imin = i break if imax - g < g - imin: - return < np.uint16_t > (imax) + return (imax) else: - return < np.uint16_t > (imin) + return (imin) else: - return < np.uint16_t > (0) + return (0) -cdef inline np.uint16_t kernel_pop(Py_ssize_t * histo, float pop, - np.uint16_t g, Py_ssize_t bitdepth, - Py_ssize_t maxbin, Py_ssize_t midbin, - float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): - return < np.uint16_t > (pop) +cdef inline dtype_t kernel_pop(Py_ssize_t * histo, float pop, + dtype_t g, Py_ssize_t bitdepth, + Py_ssize_t maxbin, Py_ssize_t midbin, + float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): + return (pop) -cdef inline np.uint16_t kernel_threshold(Py_ssize_t * histo, float pop, - np.uint16_t g, Py_ssize_t bitdepth, - Py_ssize_t maxbin, Py_ssize_t midbin, - float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_threshold(Py_ssize_t * histo, float pop, + dtype_t g, Py_ssize_t bitdepth, + Py_ssize_t maxbin, Py_ssize_t midbin, + float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef Py_ssize_t i cdef float mean = 0. if pop: for i in range(maxbin): mean += histo[i] * i - return < np.uint16_t > (g > (mean / pop)) + return (g > (mean / pop)) else: - return < np.uint16_t > (0) + return (0) -cdef inline np.uint16_t kernel_tophat(Py_ssize_t * histo, float pop, - np.uint16_t g, Py_ssize_t bitdepth, - Py_ssize_t maxbin, Py_ssize_t midbin, - float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_tophat(Py_ssize_t * histo, float pop, + dtype_t g, Py_ssize_t bitdepth, + Py_ssize_t maxbin, Py_ssize_t midbin, + float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef Py_ssize_t i if pop: @@ -256,15 +258,15 @@ cdef inline np.uint16_t kernel_tophat(Py_ssize_t * histo, float pop, if histo[i]: break - return < np.uint16_t > (i - g) + return (i - g) else: - return < np.uint16_t > (0) + return (0) -cdef inline np.uint16_t kernel_entropy(Py_ssize_t * histo, float pop, - np.uint16_t g, Py_ssize_t bitdepth, - Py_ssize_t maxbin, Py_ssize_t midbin, - float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_entropy(Py_ssize_t * histo, float pop, + dtype_t g, Py_ssize_t bitdepth, + Py_ssize_t maxbin, Py_ssize_t midbin, + float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef Py_ssize_t i cdef float e, p @@ -276,145 +278,145 @@ cdef inline np.uint16_t kernel_entropy(Py_ssize_t * histo, float pop, if p > 0: e -= p * log2(p) - return < np.uint16_t > e * 1000 + return e * 1000 else: - return < np.uint16_t > (0) + return (0) # ----------------------------------------------------------------- # python wrappers # ----------------------------------------------------------------- -def autolevel(np.ndarray[np.uint16_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint16_t, ndim=2] out=None, +def autolevel(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[cnp.uint8_t, ndim=2] selem, + cnp.ndarray[cnp.uint8_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0, Py_ssize_t bitdepth=8): _core16(kernel_autolevel, image, selem, mask, out, shift_x, shift_y, bitdepth, 0, 0, 0, 0) -def bottomhat(np.ndarray[np.uint16_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint16_t, ndim=2] out=None, +def bottomhat(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[cnp.uint8_t, ndim=2] selem, + cnp.ndarray[cnp.uint8_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0, Py_ssize_t bitdepth=8): _core16(kernel_bottomhat, image, selem, mask, out, shift_x, shift_y, bitdepth, 0, 0, 0, 0) -def equalize(np.ndarray[np.uint16_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint16_t, ndim=2] out=None, +def equalize(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[cnp.uint8_t, ndim=2] selem, + cnp.ndarray[cnp.uint8_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0, Py_ssize_t bitdepth=8): _core16(kernel_equalize, image, selem, mask, out, shift_x, shift_y, bitdepth, 0, 0, 0, 0) -def gradient(np.ndarray[np.uint16_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint16_t, ndim=2] out=None, +def gradient(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[cnp.uint8_t, ndim=2] selem, + cnp.ndarray[cnp.uint8_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0, Py_ssize_t bitdepth=8): _core16(kernel_gradient, image, selem, mask, out, shift_x, shift_y, bitdepth, 0, 0, 0, 0) -def maximum(np.ndarray[np.uint16_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint16_t, ndim=2] out=None, +def maximum(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[cnp.uint8_t, ndim=2] selem, + cnp.ndarray[cnp.uint8_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0, Py_ssize_t bitdepth=8): _core16(kernel_maximum, image, selem, mask, out, shift_x, shift_y, bitdepth, 0, 0, 0, 0) -def mean(np.ndarray[np.uint16_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint16_t, ndim=2] out=None, +def mean(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[cnp.uint8_t, ndim=2] selem, + cnp.ndarray[cnp.uint8_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0, Py_ssize_t bitdepth=8): _core16(kernel_mean, image, selem, mask, out, shift_x, shift_y, bitdepth, 0, 0, 0, 0) -def meansubstraction(np.ndarray[np.uint16_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint16_t, ndim=2] out=None, +def meansubstraction(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[cnp.uint8_t, ndim=2] selem, + cnp.ndarray[cnp.uint8_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0, Py_ssize_t bitdepth=8): _core16(kernel_meansubstraction, image, selem, mask, out, shift_x, shift_y, bitdepth, 0, 0, 0, 0) -def median(np.ndarray[np.uint16_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint16_t, ndim=2] out=None, +def median(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[cnp.uint8_t, ndim=2] selem, + cnp.ndarray[cnp.uint8_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0, Py_ssize_t bitdepth=8): _core16(kernel_median, image, selem, mask, out, shift_x, shift_y, bitdepth, 0, 0, 0, 0) -def minimum(np.ndarray[np.uint16_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint16_t, ndim=2] out=None, +def minimum(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[cnp.uint8_t, ndim=2] selem, + cnp.ndarray[cnp.uint8_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0, Py_ssize_t bitdepth=8): _core16(kernel_minimum, image, selem, mask, out, shift_x, shift_y, bitdepth, 0, 0, 0, 0) -def morph_contr_enh(np.ndarray[np.uint16_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint16_t, ndim=2] out=None, +def morph_contr_enh(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[cnp.uint8_t, ndim=2] selem, + cnp.ndarray[cnp.uint8_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0, Py_ssize_t bitdepth=8): _core16(kernel_morph_contr_enh, image, selem, mask, out, shift_x, shift_y, bitdepth, 0, 0, 0, 0) -def modal(np.ndarray[np.uint16_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint16_t, ndim=2] out=None, +def modal(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[cnp.uint8_t, ndim=2] selem, + cnp.ndarray[cnp.uint8_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0, Py_ssize_t bitdepth=8): _core16(kernel_modal, image, selem, mask, out, shift_x, shift_y, bitdepth, 0, 0, 0, 0) -def pop(np.ndarray[np.uint16_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint16_t, ndim=2] out=None, +def pop(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[cnp.uint8_t, ndim=2] selem, + cnp.ndarray[cnp.uint8_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0, Py_ssize_t bitdepth=8): _core16(kernel_pop, image, selem, mask, out, shift_x, shift_y, bitdepth, 0, 0, 0, 0) -def threshold(np.ndarray[np.uint16_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint16_t, ndim=2] out=None, +def threshold(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[cnp.uint8_t, ndim=2] selem, + cnp.ndarray[cnp.uint8_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0, Py_ssize_t bitdepth=8): _core16(kernel_threshold, image, selem, mask, out, shift_x, shift_y, bitdepth, 0, 0, 0, 0) -def tophat(np.ndarray[np.uint16_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint16_t, ndim=2] out=None, +def tophat(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[cnp.uint8_t, ndim=2] selem, + cnp.ndarray[cnp.uint8_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0, Py_ssize_t bitdepth=8): _core16(kernel_tophat, image, selem, mask, out, shift_x, shift_y, bitdepth, 0, 0, 0, 0) -def entropy(np.ndarray[np.uint16_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint16_t, ndim=2] out=None, +def entropy(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[cnp.uint8_t, ndim=2] selem, + cnp.ndarray[cnp.uint8_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0, Py_ssize_t bitdepth=8): _core16(kernel_entropy, image, selem, mask, out, shift_x, shift_y, bitdepth, 0, 0, 0, 0) diff --git a/skimage/filter/rank/_crank16_bilateral.pyx b/skimage/filter/rank/_crank16_bilateral.pyx index c71ccc5c..e431e42b 100644 --- a/skimage/filter/rank/_crank16_bilateral.pyx +++ b/skimage/filter/rank/_crank16_bilateral.pyx @@ -3,8 +3,7 @@ #cython: nonecheck=False #cython: wraparound=False -import numpy as np -cimport numpy as np +cimport numpy as cnp from skimage.filter.rank._core16 cimport _core16 @@ -13,11 +12,14 @@ from skimage.filter.rank._core16 cimport _core16 # ----------------------------------------------------------------- -cdef inline np.uint16_t kernel_mean(Py_ssize_t * histo, float pop, - np.uint16_t g, Py_ssize_t bitdepth, - Py_ssize_t maxbin, Py_ssize_t midbin, - float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +ctypedef cnp.uint16_t dtype_t + + +cdef inline dtype_t kernel_mean(Py_ssize_t * histo, float pop, + dtype_t g, Py_ssize_t bitdepth, + Py_ssize_t maxbin, Py_ssize_t midbin, + float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef int i, bilat_pop = 0 cdef float mean = 0. @@ -28,18 +30,18 @@ cdef inline np.uint16_t kernel_mean(Py_ssize_t * histo, float pop, bilat_pop += histo[i] mean += histo[i] * i if bilat_pop: - return < np.uint16_t > (mean / bilat_pop) + return (mean / bilat_pop) else: - return < np.uint16_t > (0) + return (0) else: - return < np.uint16_t > (0) + return (0) -cdef inline np.uint16_t kernel_pop(Py_ssize_t * histo, float pop, - np.uint16_t g, Py_ssize_t bitdepth, - Py_ssize_t maxbin, Py_ssize_t midbin, - float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_pop(Py_ssize_t * histo, float pop, + dtype_t g, Py_ssize_t bitdepth, + Py_ssize_t maxbin, Py_ssize_t midbin, + float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef int i, bilat_pop = 0 @@ -47,9 +49,9 @@ cdef inline np.uint16_t kernel_pop(Py_ssize_t * histo, float pop, for i in range(maxbin): if (g > (i - s0)) and (g < (i + s1)): bilat_pop += histo[i] - return < np.uint16_t > (bilat_pop) + return (bilat_pop) else: - return < np.uint16_t > (0) + return (0) # ----------------------------------------------------------------- @@ -57,10 +59,10 @@ cdef inline np.uint16_t kernel_pop(Py_ssize_t * histo, float pop, # ----------------------------------------------------------------- -def mean(np.ndarray[np.uint16_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint16_t, ndim=2] out=None, +def mean(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[cnp.uint8_t, ndim=2] selem, + cnp.ndarray[cnp.uint8_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0, int bitdepth=8, int s0=1, int s1=1): """average greylevel (clipped on uint8) """ @@ -68,10 +70,10 @@ def mean(np.ndarray[np.uint16_t, ndim=2] image, bitdepth, 0., 0., s0, s1) -def pop(np.ndarray[np.uint16_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint16_t, ndim=2] out=None, +def pop(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[cnp.uint8_t, ndim=2] selem, + cnp.ndarray[cnp.uint8_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0, int bitdepth=8, int s0=1, int s1=1): """returns the number of actual pixels of the structuring element inside the mask diff --git a/skimage/filter/rank/_crank16_percentiles.pyx b/skimage/filter/rank/_crank16_percentiles.pyx index 220d2386..0ab71353 100644 --- a/skimage/filter/rank/_crank16_percentiles.pyx +++ b/skimage/filter/rank/_crank16_percentiles.pyx @@ -3,8 +3,7 @@ #cython: nonecheck=False #cython: wraparound=False -import numpy as np -cimport numpy as np +cimport numpy as cnp from skimage.filter.rank._core16 cimport _core16, int_min, int_max @@ -13,11 +12,14 @@ from skimage.filter.rank._core16 cimport _core16, int_min, int_max # ----------------------------------------------------------------- -cdef inline np.uint16_t kernel_autolevel(Py_ssize_t * histo, float pop, - np.uint16_t g, Py_ssize_t bitdepth, - Py_ssize_t maxbin, Py_ssize_t midbin, - float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +ctypedef cnp.uint16_t dtype_t + + +cdef inline dtype_t kernel_autolevel(Py_ssize_t * histo, float pop, + dtype_t g, Py_ssize_t bitdepth, + Py_ssize_t maxbin, Py_ssize_t midbin, + float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef int i, imin, imax, sum, delta @@ -38,19 +40,20 @@ cdef inline np.uint16_t kernel_autolevel(Py_ssize_t * histo, float pop, delta = imax - imin if delta > 0: - return < np.uint16_t > (1.0 * (maxbin - 1) - * (int_min(int_max(imin, g), imax) - imin) / delta) + return (1.0 * (maxbin - 1) + * (int_min(int_max(imin, g), imax) + - imin) / delta) else: - return < np.uint16_t > (imax - imin) + return (imax - imin) else: - return < np.uint16_t > (0) + return (0) -cdef inline np.uint16_t kernel_gradient(Py_ssize_t * histo, float pop, - np.uint16_t g, Py_ssize_t bitdepth, - Py_ssize_t maxbin, Py_ssize_t midbin, - float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_gradient(Py_ssize_t * histo, float pop, + dtype_t g, Py_ssize_t bitdepth, + Py_ssize_t maxbin, Py_ssize_t midbin, + float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef int i, imin, imax, sum, delta @@ -69,16 +72,16 @@ cdef inline np.uint16_t kernel_gradient(Py_ssize_t * histo, float pop, imax = i break - return < np.uint16_t > (imax - imin) + return (imax - imin) else: - return < np.uint16_t > (0) + return (0) -cdef inline np.uint16_t kernel_mean(Py_ssize_t * histo, float pop, - np.uint16_t g, Py_ssize_t bitdepth, - Py_ssize_t maxbin, Py_ssize_t midbin, - float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_mean(Py_ssize_t * histo, float pop, + dtype_t g, Py_ssize_t bitdepth, + Py_ssize_t maxbin, Py_ssize_t midbin, + float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef int i, sum, mean, n @@ -93,21 +96,21 @@ cdef inline np.uint16_t kernel_mean(Py_ssize_t * histo, float pop, mean += histo[i] * i if n > 0: - return < np.uint16_t > (1.0 * mean / n) + return (1.0 * mean / n) else: - return < np.uint16_t > (0) + return (0) else: - return < np.uint16_t > (0) + return (0) -cdef inline np.uint16_t kernel_mean_substraction(Py_ssize_t * histo, - float pop, - np.uint16_t g, - Py_ssize_t bitdepth, - Py_ssize_t maxbin, - Py_ssize_t midbin, - float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_mean_substraction(Py_ssize_t * histo, + float pop, + dtype_t g, + Py_ssize_t bitdepth, + Py_ssize_t maxbin, + Py_ssize_t midbin, + float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef int i, sum, mean, n @@ -121,21 +124,21 @@ cdef inline np.uint16_t kernel_mean_substraction(Py_ssize_t * histo, n += histo[i] mean += histo[i] * i if n > 0: - return < np.uint16_t > ((g - (mean / n)) * .5 + midbin) + return ((g - (mean / n)) * .5 + midbin) else: - return < np.uint16_t > (0) + return (0) else: - return < np.uint16_t > (0) + return (0) -cdef inline np.uint16_t kernel_morph_contr_enh(Py_ssize_t * histo, - float pop, - np.uint16_t g, - Py_ssize_t bitdepth, - Py_ssize_t maxbin, - Py_ssize_t midbin, - float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_morph_contr_enh(Py_ssize_t * histo, + float pop, + dtype_t g, + Py_ssize_t bitdepth, + Py_ssize_t maxbin, + Py_ssize_t midbin, + float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef int i, imin, imax, sum, delta @@ -154,22 +157,22 @@ cdef inline np.uint16_t kernel_morph_contr_enh(Py_ssize_t * histo, imax = i break if g > imax: - return < np.uint16_t > imax + return imax if g < imin: - return < np.uint16_t > imin + return imin if imax - g < g - imin: - return < np.uint16_t > imax + return imax else: - return < np.uint16_t > imin + return imin else: - return < np.uint16_t > (0) + return (0) -cdef inline np.uint16_t kernel_percentile(Py_ssize_t * histo, float pop, - np.uint16_t g, Py_ssize_t bitdepth, - Py_ssize_t maxbin, Py_ssize_t midbin, - float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_percentile(Py_ssize_t * histo, float pop, + dtype_t g, Py_ssize_t bitdepth, + Py_ssize_t maxbin, Py_ssize_t midbin, + float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef int i cdef float sum = 0. @@ -180,16 +183,16 @@ cdef inline np.uint16_t kernel_percentile(Py_ssize_t * histo, float pop, if sum >= p0 * pop: break - return < np.uint16_t > (i) + return (i) else: - return < np.uint16_t > (0) + return (0) -cdef inline np.uint16_t kernel_pop(Py_ssize_t * histo, float pop, - np.uint16_t g, Py_ssize_t bitdepth, - Py_ssize_t maxbin, Py_ssize_t midbin, - float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_pop(Py_ssize_t * histo, float pop, + dtype_t g, Py_ssize_t bitdepth, + Py_ssize_t maxbin, Py_ssize_t midbin, + float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef int i, sum, n @@ -200,16 +203,16 @@ cdef inline np.uint16_t kernel_pop(Py_ssize_t * histo, float pop, sum += histo[i] if (sum >= p0 * pop) and (sum <= p1 * pop): n += histo[i] - return < np.uint16_t > (n) + return (n) else: - return < np.uint16_t > (0) + return (0) -cdef inline np.uint16_t kernel_threshold(Py_ssize_t * histo, float pop, - np.uint16_t g, Py_ssize_t bitdepth, - Py_ssize_t maxbin, Py_ssize_t midbin, - float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_threshold(Py_ssize_t * histo, float pop, + dtype_t g, Py_ssize_t bitdepth, + Py_ssize_t maxbin, Py_ssize_t midbin, + float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef int i cdef float sum = 0. @@ -220,9 +223,9 @@ cdef inline np.uint16_t kernel_threshold(Py_ssize_t * histo, float pop, if sum >= p0 * pop: break - return < np.uint16_t > ((maxbin - 1) * (g >= i)) + return ((maxbin - 1) * (g >= i)) else: - return < np.uint16_t > (0) + return (0) # ----------------------------------------------------------------- @@ -230,10 +233,10 @@ cdef inline np.uint16_t kernel_threshold(Py_ssize_t * histo, float pop, # ----------------------------------------------------------------- -def autolevel(np.ndarray[np.uint16_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint16_t, ndim=2] out=None, +def autolevel(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[cnp.uint8_t, ndim=2] selem, + cnp.ndarray[cnp.uint8_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0, int bitdepth=8, float p0=0., float p1=0.): """bottom hat @@ -242,10 +245,10 @@ def autolevel(np.ndarray[np.uint16_t, ndim=2] image, bitdepth, p0, p1, 0, 0) -def gradient(np.ndarray[np.uint16_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint16_t, ndim=2] out=None, +def gradient(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[cnp.uint8_t, ndim=2] selem, + cnp.ndarray[cnp.uint8_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0, int bitdepth=8, float p0=0., float p1=0.): """return p0,p1 percentile gradient @@ -254,10 +257,10 @@ def gradient(np.ndarray[np.uint16_t, ndim=2] image, bitdepth, p0, p1, 0, 0) -def mean(np.ndarray[np.uint16_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint16_t, ndim=2] out=None, +def mean(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[cnp.uint8_t, ndim=2] selem, + cnp.ndarray[cnp.uint8_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0, int bitdepth=8, float p0=0., float p1=0.): """return mean between [p0 and p1] percentiles @@ -266,10 +269,10 @@ def mean(np.ndarray[np.uint16_t, ndim=2] image, bitdepth, p0, p1, 0, 0) -def mean_substraction(np.ndarray[np.uint16_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint16_t, ndim=2] out=None, +def mean_substraction(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[cnp.uint8_t, ndim=2] selem, + cnp.ndarray[cnp.uint8_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0, int bitdepth=8, float p0=0., float p1=0.): """return original - mean between [p0 and p1] percentiles *.5 +127 @@ -279,10 +282,10 @@ def mean_substraction(np.ndarray[np.uint16_t, ndim=2] image, bitdepth, p0, p1, 0, 0) -def morph_contr_enh(np.ndarray[np.uint16_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint16_t, ndim=2] out=None, +def morph_contr_enh(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[cnp.uint8_t, ndim=2] selem, + cnp.ndarray[cnp.uint8_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0, int bitdepth=8, float p0=0., float p1=0.): """reforce contrast using percentiles @@ -291,10 +294,10 @@ def morph_contr_enh(np.ndarray[np.uint16_t, ndim=2] image, bitdepth, p0, p1, 0, 0) -def percentile(np.ndarray[np.uint16_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint16_t, ndim=2] out=None, +def percentile(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[cnp.uint8_t, ndim=2] selem, + cnp.ndarray[cnp.uint8_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0, int bitdepth=8, float p0=0., float p1=0.): """return p0 percentile @@ -303,10 +306,10 @@ def percentile(np.ndarray[np.uint16_t, ndim=2] image, bitdepth, p0, p1, 0, 0) -def pop(np.ndarray[np.uint16_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint16_t, ndim=2] out=None, +def pop(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[cnp.uint8_t, ndim=2] selem, + cnp.ndarray[cnp.uint8_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0, int bitdepth=8, float p0=0., float p1=0.): """return nb of pixels between [p0 and p1] @@ -315,10 +318,10 @@ def pop(np.ndarray[np.uint16_t, ndim=2] image, bitdepth, p0, p1, 0, 0) -def threshold(np.ndarray[np.uint16_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint16_t, ndim=2] out=None, +def threshold(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[cnp.uint8_t, ndim=2] selem, + cnp.ndarray[cnp.uint8_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0, int bitdepth=8, float p0=0., float p1=0.): """return (maxbin-1) if g > percentile p0 diff --git a/skimage/filter/rank/_crank8.pyx b/skimage/filter/rank/_crank8.pyx index 8bff9703..c9e8b14f 100644 --- a/skimage/filter/rank/_crank8.pyx +++ b/skimage/filter/rank/_crank8.pyx @@ -3,8 +3,7 @@ #cython: nonecheck=False #cython: wraparound=False -import numpy as np -cimport numpy as np +cimport numpy as cnp from libc.math cimport log2 from skimage.filter.rank._core8 cimport _core8 @@ -14,9 +13,12 @@ from skimage.filter.rank._core8 cimport _core8 # ----------------------------------------------------------------- -cdef inline np.uint8_t kernel_autolevel(Py_ssize_t * histo, float pop, - np.uint8_t g, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +ctypedef cnp.uint8_t dtype_t + + +cdef inline dtype_t kernel_autolevel(Py_ssize_t * histo, float pop, + dtype_t g, float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef Py_ssize_t i, imin, imax, delta @@ -31,16 +33,16 @@ cdef inline np.uint8_t kernel_autolevel(Py_ssize_t * histo, float pop, break delta = imax - imin if delta > 0: - return < np.uint8_t > (255. * (g - imin) / delta) + return (255. * (g - imin) / delta) else: - return < np.uint8_t > (imax - imin) + return (imax - imin) else: - return < np.uint8_t > (0) + return (0) -cdef inline np.uint8_t kernel_bottomhat(Py_ssize_t * histo, float pop, - np.uint8_t g, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_bottomhat(Py_ssize_t * histo, float pop, + dtype_t g, float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef Py_ssize_t i @@ -49,14 +51,14 @@ cdef inline np.uint8_t kernel_bottomhat(Py_ssize_t * histo, float pop, if histo[i]: break - return < np.uint8_t > (g - i) + return (g - i) else: - return < np.uint8_t > (0) + return (0) -cdef inline np.uint8_t kernel_equalize(Py_ssize_t * histo, float pop, - np.uint8_t g, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_equalize(Py_ssize_t * histo, float pop, + dtype_t g, float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef Py_ssize_t i cdef float sum = 0. @@ -67,14 +69,14 @@ cdef inline np.uint8_t kernel_equalize(Py_ssize_t * histo, float pop, if i >= g: break - return < np.uint8_t > ((255 * sum) / pop) + return ((255 * sum) / pop) else: - return < np.uint8_t > (0) + return (0) -cdef inline np.uint8_t kernel_gradient(Py_ssize_t * histo, float pop, - np.uint8_t g, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_gradient(Py_ssize_t * histo, float pop, + dtype_t g, float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef Py_ssize_t i, imin, imax @@ -87,28 +89,28 @@ cdef inline np.uint8_t kernel_gradient(Py_ssize_t * histo, float pop, if histo[i]: imin = i break - return < np.uint8_t > (imax - imin) + return (imax - imin) else: - return < np.uint8_t > (0) + return (0) -cdef inline np.uint8_t kernel_maximum(Py_ssize_t * histo, float pop, - np.uint8_t g, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_maximum(Py_ssize_t * histo, float pop, + dtype_t g, float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef Py_ssize_t i if pop: for i in range(255, -1, -1): if histo[i]: - return < np.uint8_t > (i) + return (i) else: - return < np.uint8_t > (0) + return (0) -cdef inline np.uint8_t kernel_mean(Py_ssize_t * histo, float pop, - np.uint8_t g, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_mean(Py_ssize_t * histo, float pop, + dtype_t g, float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef Py_ssize_t i cdef float mean = 0. @@ -116,14 +118,14 @@ cdef inline np.uint8_t kernel_mean(Py_ssize_t * histo, float pop, if pop: for i in range(256): mean += histo[i] * i - return < np.uint8_t > (mean / pop) + return (mean / pop) else: - return < np.uint8_t > (0) + return (0) -cdef inline np.uint8_t kernel_meansubstraction(Py_ssize_t * histo, float pop, - np.uint8_t g, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_meansubstraction(Py_ssize_t * histo, float pop, + dtype_t g, float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef Py_ssize_t i cdef float mean = 0. @@ -131,14 +133,14 @@ cdef inline np.uint8_t kernel_meansubstraction(Py_ssize_t * histo, float pop, if pop: for i in range(256): mean += histo[i] * i - return < np.uint8_t > ((g - mean / pop) / 2. + 127) + return ((g - mean / pop) / 2. + 127) else: - return < np.uint8_t > (0) + return (0) -cdef inline np.uint8_t kernel_median(Py_ssize_t * histo, float pop, - np.uint8_t g, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_median(Py_ssize_t * histo, float pop, + dtype_t g, float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef Py_ssize_t i cdef float sum = pop / 2.0 @@ -148,28 +150,28 @@ cdef inline np.uint8_t kernel_median(Py_ssize_t * histo, float pop, if histo[i]: sum -= histo[i] if sum < 0: - return < np.uint8_t > (i) + return (i) else: - return < np.uint8_t > (0) + return (0) -cdef inline np.uint8_t kernel_minimum(Py_ssize_t * histo, float pop, - np.uint8_t g, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_minimum(Py_ssize_t * histo, float pop, + dtype_t g, float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef Py_ssize_t i if pop: for i in range(256): if histo[i]: - return < np.uint8_t > (i) + return (i) else: - return < np.uint8_t > (0) + return (0) -cdef inline np.uint8_t kernel_modal(Py_ssize_t * histo, float pop, - np.uint8_t g, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_modal(Py_ssize_t * histo, float pop, + dtype_t g, float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef Py_ssize_t hmax = 0, imax = 0 @@ -178,14 +180,14 @@ cdef inline np.uint8_t kernel_modal(Py_ssize_t * histo, float pop, if histo[i] > hmax: hmax = histo[i] imax = i - return < np.uint8_t > (imax) + return (imax) else: - return < np.uint8_t > (0) + return (0) -cdef inline np.uint8_t kernel_morph_contr_enh(Py_ssize_t * histo, float pop, - np.uint8_t g, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_morph_contr_enh(Py_ssize_t * histo, float pop, + dtype_t g, float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef Py_ssize_t i, imin, imax @@ -199,23 +201,23 @@ cdef inline np.uint8_t kernel_morph_contr_enh(Py_ssize_t * histo, float pop, imin = i break if imax - g < g - imin: - return < np.uint8_t > (imax) + return (imax) else: - return < np.uint8_t > (imin) + return (imin) else: - return < np.uint8_t > (0) + return (0) -cdef inline np.uint8_t kernel_pop(Py_ssize_t * histo, float pop, - np.uint8_t g, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_pop(Py_ssize_t * histo, float pop, + dtype_t g, float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): - return < np.uint8_t > (pop) + return (pop) -cdef inline np.uint8_t kernel_threshold(Py_ssize_t * histo, float pop, - np.uint8_t g, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_threshold(Py_ssize_t * histo, float pop, + dtype_t g, float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef Py_ssize_t i cdef float mean = 0. @@ -223,14 +225,14 @@ cdef inline np.uint8_t kernel_threshold(Py_ssize_t * histo, float pop, if pop: for i in range(256): mean += histo[i] * i - return < np.uint8_t > (g > (mean / pop)) + return (g > (mean / pop)) else: - return < np.uint8_t > (0) + return (0) -cdef inline np.uint8_t kernel_tophat(Py_ssize_t * histo, float pop, - np.uint8_t g, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_tophat(Py_ssize_t * histo, float pop, + dtype_t g, float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef Py_ssize_t i @@ -239,20 +241,20 @@ cdef inline np.uint8_t kernel_tophat(Py_ssize_t * histo, float pop, if histo[i]: break - return < np.uint8_t > (i - g) + return (i - g) else: - return < np.uint8_t > (0) + return (0) -cdef inline np.uint8_t kernel_noise_filter(Py_ssize_t * histo, float pop, - np.uint8_t g, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_noise_filter(Py_ssize_t * histo, float pop, + dtype_t g, float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef Py_ssize_t i cdef Py_ssize_t min_i # early stop if at least one pixel of the neighborhood has the same g if histo[g] > 0: - return < np.uint8_t > 0 + return 0 for i in range(g, -1, -1): if histo[i]: @@ -262,14 +264,14 @@ cdef inline np.uint8_t kernel_noise_filter(Py_ssize_t * histo, float pop, if histo[i]: break if i - g < min_i: - return < np.uint8_t > (i - g) + return (i - g) else: - return < np.uint8_t > min_i + return min_i -cdef inline np.uint8_t kernel_entropy(Py_ssize_t * histo, float pop, - np.uint8_t g, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_entropy(Py_ssize_t * histo, float pop, + dtype_t g, float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef Py_ssize_t i cdef float e, p @@ -281,13 +283,13 @@ cdef inline np.uint8_t kernel_entropy(Py_ssize_t * histo, float pop, if p > 0: e -= p * log2(p) - return < np.uint8_t > e * 10 + return e * 10 else: - return < np.uint8_t > (0) + return (0) -cdef inline np.uint8_t kernel_otsu(Py_ssize_t * histo, float pop, np.uint8_t g, - float p0, float p1, Py_ssize_t s0, - Py_ssize_t s1): +cdef inline dtype_t kernel_otsu(Py_ssize_t * histo, float pop, dtype_t g, + float p0, float p1, Py_ssize_t s0, + Py_ssize_t s1): cdef Py_ssize_t i cdef Py_ssize_t max_i cdef float P, mu1, mu2, q1, new_q1, sigma_b, max_sigma_b @@ -299,7 +301,7 @@ cdef inline np.uint8_t kernel_otsu(Py_ssize_t * histo, float pop, np.uint8_t g, mu += histo[i] * i mu = (mu / pop) else: - return < np.uint8_t > (0) + return (0) # maximizing the between class variance max_i = 0 @@ -319,7 +321,7 @@ cdef inline np.uint8_t kernel_otsu(Py_ssize_t * histo, float pop, np.uint8_t g, max_i = i q1 = new_q1 - return < np.uint8_t > max_i + return max_i # ----------------------------------------------------------------- @@ -328,154 +330,154 @@ cdef inline np.uint8_t kernel_otsu(Py_ssize_t * histo, float pop, np.uint8_t g, # ----------------------------------------------------------------- -def autolevel(np.ndarray[np.uint8_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint8_t, ndim=2] out=None, +def autolevel(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[dtype_t, ndim=2] selem, + cnp.ndarray[dtype_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0): _core8(kernel_autolevel, image, selem, mask, out, shift_x, shift_y, 0, 0, 0, 0) -def bottomhat(np.ndarray[np.uint8_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint8_t, ndim=2] out=None, +def bottomhat(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[dtype_t, ndim=2] selem, + cnp.ndarray[dtype_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0): _core8(kernel_bottomhat, image, selem, mask, out, shift_x, shift_y, 0, 0, 0, 0) -def equalize(np.ndarray[np.uint8_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint8_t, ndim=2] out=None, +def equalize(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[dtype_t, ndim=2] selem, + cnp.ndarray[dtype_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0): _core8(kernel_equalize, image, selem, mask, out, shift_x, shift_y, 0, 0, 0, 0) -def gradient(np.ndarray[np.uint8_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint8_t, ndim=2] out=None, +def gradient(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[dtype_t, ndim=2] selem, + cnp.ndarray[dtype_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0): _core8(kernel_gradient, image, selem, mask, out, shift_x, shift_y, 0, 0, 0, 0) -def maximum(np.ndarray[np.uint8_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint8_t, ndim=2] out=None, +def maximum(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[dtype_t, ndim=2] selem, + cnp.ndarray[dtype_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0): _core8(kernel_maximum, image, selem, mask, out, shift_x, shift_y, 0, 0, 0, 0) -def mean(np.ndarray[np.uint8_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint8_t, ndim=2] out=None, +def mean(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[dtype_t, ndim=2] selem, + cnp.ndarray[dtype_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0): _core8(kernel_mean, image, selem, mask, out, shift_x, shift_y, 0, 0, 0, 0) -def meansubstraction(np.ndarray[np.uint8_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint8_t, ndim=2] out=None, +def meansubstraction(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[dtype_t, ndim=2] selem, + cnp.ndarray[dtype_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0): _core8(kernel_meansubstraction, image, selem, mask, out, shift_x, shift_y, 0, 0, 0, 0) -def median(np.ndarray[np.uint8_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint8_t, ndim=2] out=None, +def median(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[dtype_t, ndim=2] selem, + cnp.ndarray[dtype_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0): _core8(kernel_median, image, selem, mask, out, shift_x, shift_y, 0, 0, 0, 0) -def minimum(np.ndarray[np.uint8_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint8_t, ndim=2] out=None, +def minimum(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[dtype_t, ndim=2] selem, + cnp.ndarray[dtype_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0): _core8(kernel_minimum, image, selem, mask, out, shift_x, shift_y, 0, 0, 0, 0) -def morph_contr_enh(np.ndarray[np.uint8_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint8_t, ndim=2] out=None, +def morph_contr_enh(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[dtype_t, ndim=2] selem, + cnp.ndarray[dtype_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0): _core8(kernel_morph_contr_enh, image, selem, mask, out, shift_x, shift_y, 0, 0, 0, 0) -def modal(np.ndarray[np.uint8_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint8_t, ndim=2] out=None, +def modal(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[dtype_t, ndim=2] selem, + cnp.ndarray[dtype_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0): _core8(kernel_modal, image, selem, mask, out, shift_x, shift_y, 0, 0, 0, 0) -def pop(np.ndarray[np.uint8_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint8_t, ndim=2] out=None, +def pop(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[dtype_t, ndim=2] selem, + cnp.ndarray[dtype_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0): _core8(kernel_pop, image, selem, mask, out, shift_x, shift_y, 0, 0, 0, 0) -def threshold(np.ndarray[np.uint8_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint8_t, ndim=2] out=None, +def threshold(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[dtype_t, ndim=2] selem, + cnp.ndarray[dtype_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0): _core8(kernel_threshold, image, selem, mask, out, shift_x, shift_y, 0, 0, 0, 0) -def tophat(np.ndarray[np.uint8_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint8_t, ndim=2] out=None, +def tophat(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[dtype_t, ndim=2] selem, + cnp.ndarray[dtype_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0): _core8(kernel_tophat, image, selem, mask, out, shift_x, shift_y, 0, 0, 0, 0) -def noise_filter(np.ndarray[np.uint8_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint8_t, ndim=2] out=None, +def noise_filter(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[dtype_t, ndim=2] selem, + cnp.ndarray[dtype_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0): _core8(kernel_noise_filter, image, selem, mask, out, shift_x, shift_y, 0, 0, 0, 0) -def entropy(np.ndarray[np.uint8_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint8_t, ndim=2] out=None, +def entropy(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[dtype_t, ndim=2] selem, + cnp.ndarray[dtype_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0): _core8(kernel_entropy, image, selem, mask, out, shift_x, shift_y, 0, 0, 0, 0) -def otsu(np.ndarray[np.uint8_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint8_t, ndim=2] out=None, +def otsu(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[dtype_t, ndim=2] selem, + cnp.ndarray[dtype_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0): _core8(kernel_otsu, image, selem, mask, out, shift_x, shift_y, 0, 0, 0, 0) diff --git a/skimage/filter/rank/_crank8_percentiles.pyx b/skimage/filter/rank/_crank8_percentiles.pyx index 31b00742..d085957e 100644 --- a/skimage/filter/rank/_crank8_percentiles.pyx +++ b/skimage/filter/rank/_crank8_percentiles.pyx @@ -3,8 +3,7 @@ #cython: nonecheck=False #cython: wraparound=False -import numpy as np -cimport numpy as np +cimport numpy as cnp from skimage.filter.rank._core8 cimport _core8, uint8_max, uint8_min @@ -13,9 +12,12 @@ from skimage.filter.rank._core8 cimport _core8, uint8_max, uint8_min # ----------------------------------------------------------------- -cdef inline np.uint8_t kernel_autolevel(Py_ssize_t * histo, float pop, - np.uint8_t g, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +ctypedef cnp.uint8_t dtype_t + + +cdef inline dtype_t kernel_autolevel(Py_ssize_t * histo, float pop, + dtype_t g, float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef int i, imin, imax, sum, delta if pop: @@ -37,17 +39,17 @@ cdef inline np.uint8_t kernel_autolevel(Py_ssize_t * histo, float pop, break delta = imax - imin if delta > 0: - return < np.uint8_t > (255 - * (uint8_min(uint8_max(imin, g), imax) - imin) / delta) + return (255 * (uint8_min(uint8_max(imin, g), imax) + - imin) / delta) else: - return < np.uint8_t > (imax - imin) + return (imax - imin) else: - return < np.uint8_t > (128) + return (128) -cdef inline np.uint8_t kernel_gradient(Py_ssize_t * histo, float pop, - np.uint8_t g, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_gradient(Py_ssize_t * histo, float pop, + dtype_t g, float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef int i, imin, imax, sum, delta if pop: @@ -65,14 +67,14 @@ cdef inline np.uint8_t kernel_gradient(Py_ssize_t * histo, float pop, imax = i break - return < np.uint8_t > (imax - imin) + return (imax - imin) else: - return < np.uint8_t > (0) + return (0) -cdef inline np.uint8_t kernel_mean(Py_ssize_t * histo, float pop, - np.uint8_t g, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_mean(Py_ssize_t * histo, float pop, + dtype_t g, float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef int i, sum, mean, n if pop: @@ -85,18 +87,18 @@ cdef inline np.uint8_t kernel_mean(Py_ssize_t * histo, float pop, n += histo[i] mean += histo[i] * i if n > 0: - return < np.uint8_t > (1.0 * mean / n) + return (1.0 * mean / n) else: - return < np.uint8_t > (0) + return (0) else: - return < np.uint8_t > (0) + return (0) -cdef inline np.uint8_t kernel_mean_substraction(Py_ssize_t * histo, - float pop, - np.uint8_t g, - float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_mean_substraction(Py_ssize_t * histo, + float pop, + dtype_t g, + float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef int i, sum, mean, n if pop: @@ -109,17 +111,17 @@ cdef inline np.uint8_t kernel_mean_substraction(Py_ssize_t * histo, n += histo[i] mean += histo[i] * i if n > 0: - return < np.uint8_t > ((g - (mean / n)) * .5 + 127) + return ((g - (mean / n)) * .5 + 127) else: - return < np.uint8_t > (0) + return (0) else: - return < np.uint8_t > (0) + return (0) -cdef inline np.uint8_t kernel_morph_contr_enh(Py_ssize_t * histo, - float pop, - np.uint8_t g, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_morph_contr_enh(Py_ssize_t * histo, + float pop, + dtype_t g, float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef int i, imin, imax, sum, delta if pop: @@ -137,20 +139,20 @@ cdef inline np.uint8_t kernel_morph_contr_enh(Py_ssize_t * histo, imax = i break if g > imax: - return < np.uint8_t > imax + return imax if g < imin: - return < np.uint8_t > imin + return imin if imax - g < g - imin: - return < np.uint8_t > imax + return imax else: - return < np.uint8_t > imin + return imin else: - return < np.uint8_t > (0) + return (0) -cdef inline np.uint8_t kernel_percentile(Py_ssize_t * histo, float pop, - np.uint8_t g, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_percentile(Py_ssize_t * histo, float pop, + dtype_t g, float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef int i cdef float sum = 0. @@ -160,14 +162,14 @@ cdef inline np.uint8_t kernel_percentile(Py_ssize_t * histo, float pop, if sum >= p0 * pop: break - return < np.uint8_t > (i) + return (i) else: - return < np.uint8_t > (0) + return (0) -cdef inline np.uint8_t kernel_pop(Py_ssize_t * histo, float pop, - np.uint8_t g, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_pop(Py_ssize_t * histo, float pop, + dtype_t g, float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef int i, sum, n if pop: @@ -177,14 +179,14 @@ cdef inline np.uint8_t kernel_pop(Py_ssize_t * histo, float pop, sum += histo[i] if (sum >= p0 * pop) and (sum <= p1 * pop): n += histo[i] - return < np.uint8_t > (n) + return (n) else: - return < np.uint8_t > (0) + return (0) -cdef inline np.uint8_t kernel_threshold(Py_ssize_t * histo, float pop, - np.uint8_t g, float p0, float p1, - Py_ssize_t s0, Py_ssize_t s1): +cdef inline dtype_t kernel_threshold(Py_ssize_t * histo, float pop, + dtype_t g, float p0, float p1, + Py_ssize_t s0, Py_ssize_t s1): cdef int i cdef float sum = 0. @@ -194,9 +196,9 @@ cdef inline np.uint8_t kernel_threshold(Py_ssize_t * histo, float pop, if sum >= p0 * pop: break - return < np.uint8_t > (255 * (g >= i)) + return (255 * (g >= i)) else: - return < np.uint8_t > (0) + return (0) # ----------------------------------------------------------------- @@ -204,10 +206,10 @@ cdef inline np.uint8_t kernel_threshold(Py_ssize_t * histo, float pop, # ----------------------------------------------------------------- -def autolevel(np.ndarray[np.uint8_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint8_t, ndim=2] out=None, +def autolevel(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[dtype_t, ndim=2] selem, + cnp.ndarray[dtype_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0, float p0=0., float p1=0.): """autolevel """ @@ -215,10 +217,10 @@ def autolevel(np.ndarray[np.uint8_t, ndim=2] image, 0, 0) -def gradient(np.ndarray[np.uint8_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint8_t, ndim=2] out=None, +def gradient(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[dtype_t, ndim=2] selem, + cnp.ndarray[dtype_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0, float p0=0., float p1=0.): """return p0,p1 percentile gradient """ @@ -226,10 +228,10 @@ def gradient(np.ndarray[np.uint8_t, ndim=2] image, 0, 0) -def mean(np.ndarray[np.uint8_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint8_t, ndim=2] out=None, +def mean(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[dtype_t, ndim=2] selem, + cnp.ndarray[dtype_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0, float p0=0., float p1=0.): """return mean between [p0 and p1] percentiles """ @@ -237,10 +239,10 @@ def mean(np.ndarray[np.uint8_t, ndim=2] image, 0, 0) -def mean_substraction(np.ndarray[np.uint8_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint8_t, ndim=2] out=None, +def mean_substraction(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[dtype_t, ndim=2] selem, + cnp.ndarray[dtype_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0, float p0=0., float p1=0.): """return original - mean between [p0 and p1] percentiles *.5 +127 """ @@ -248,10 +250,10 @@ def mean_substraction(np.ndarray[np.uint8_t, ndim=2] image, p0, p1, 0, 0) -def morph_contr_enh(np.ndarray[np.uint8_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint8_t, ndim=2] out=None, +def morph_contr_enh(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[dtype_t, ndim=2] selem, + cnp.ndarray[dtype_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0, float p0=0., float p1=0.): """reforce contrast using percentiles """ @@ -259,10 +261,10 @@ def morph_contr_enh(np.ndarray[np.uint8_t, ndim=2] image, p0, p1, 0, 0) -def percentile(np.ndarray[np.uint8_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint8_t, ndim=2] out=None, +def percentile(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[dtype_t, ndim=2] selem, + cnp.ndarray[dtype_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0, float p0=0., float p1=0.): """return p0 percentile """ @@ -270,10 +272,10 @@ def percentile(np.ndarray[np.uint8_t, ndim=2] image, p0, p1, 0, 0) -def pop(np.ndarray[np.uint8_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint8_t, ndim=2] out=None, +def pop(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[dtype_t, ndim=2] selem, + cnp.ndarray[dtype_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0, float p0=0., float p1=0.): """return nb of pixels between [p0 and p1] """ @@ -281,10 +283,10 @@ def pop(np.ndarray[np.uint8_t, ndim=2] image, 0, 0) -def threshold(np.ndarray[np.uint8_t, ndim=2] image, - np.ndarray[np.uint8_t, ndim=2] selem, - np.ndarray[np.uint8_t, ndim=2] mask=None, - np.ndarray[np.uint8_t, ndim=2] out=None, +def threshold(cnp.ndarray[dtype_t, ndim=2] image, + cnp.ndarray[dtype_t, ndim=2] selem, + cnp.ndarray[dtype_t, ndim=2] mask=None, + cnp.ndarray[dtype_t, ndim=2] out=None, char shift_x=0, char shift_y=0, float p0=0., float p1=0.): """return 255 if g > percentile p0 """ diff --git a/skimage/graph/_mcp.pxd b/skimage/graph/_mcp.pxd index 4222d877..b2e2a548 100644 --- a/skimage/graph/_mcp.pxd +++ b/skimage/graph/_mcp.pxd @@ -4,11 +4,11 @@ other cython modules can "cimport mcp" and subclass it. """ cimport heap -cimport numpy as np +cimport numpy as cnp ctypedef heap.BOOL_T BOOL_T -ctypedef unsigned char DIM_T -ctypedef np.float64_t FLOAT_T +ctypedef unsigned char DIM_T +ctypedef cnp.float64_t FLOAT_T cdef class MCP: cdef heap.FastUpdateBinaryHeap costs_heap @@ -23,7 +23,7 @@ cdef class MCP: cdef object flat_offsets cdef object offset_lengths cdef BOOL_T dirty - cdef BOOL_T use_start_cost + cdef BOOL_T use_start_cost # if use_start_cost is true, the cost of the starting element is added to # the cost of the path. Set to true by default in the base class... diff --git a/skimage/graph/_mcp.pyx b/skimage/graph/_mcp.pyx index 320493c2..4e5f6d52 100644 --- a/skimage/graph/_mcp.pyx +++ b/skimage/graph/_mcp.pyx @@ -1,5 +1,7 @@ -# -*- python -*- - +#cython: cdivision=True +#cython: boundscheck=False +#cython: nonecheck=False +#cython: wraparound=False """Cython implementation of Dijkstra's minimum cost path algorithm, for use with data on a n-dimensional lattice. @@ -32,19 +34,19 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ -import cython -cimport numpy as np import numpy as np -cimport heap import heap -ctypedef np.int8_t OFFSET_T +cimport numpy as cnp +cimport heap + +ctypedef cnp.int8_t OFFSET_T OFFSET_D = np.int8 -ctypedef np.int16_t OFFSETS_INDEX_T +ctypedef cnp.int16_t OFFSETS_INDEX_T OFFSETS_INDEX_D = np.int16 -ctypedef np.int8_t EDGE_T +ctypedef cnp.int8_t EDGE_T EDGE_D = np.int8 -ctypedef np.intp_t INDEX_T +ctypedef cnp.intp_t INDEX_T INDEX_D = np.intp FLOAT_D = np.float64 @@ -317,7 +319,6 @@ cdef class MCP: FLOAT_T new_cost, FLOAT_T offset_length): return new_cost - @cython.boundscheck(False) def find_costs(self, starts, ends=None, find_all_ends=True): """ Find the minimum-cost path from the given starting points. @@ -366,7 +367,7 @@ cdef class MCP: cdef BOOL_T use_ends = 0 cdef INDEX_T num_ends cdef BOOL_T all_ends = find_all_ends - cdef np.ndarray[INDEX_T, ndim=1] flat_ends + cdef cnp.ndarray[INDEX_T, ndim=1] flat_ends starts = _normalize_indices(starts, self.costs_shape) if starts is None: raise ValueError('start points must all be within the costs array') @@ -385,18 +386,18 @@ cdef class MCP: # lookup and array-ify object attributes for fast use cdef heap.FastUpdateBinaryHeap costs_heap = self.costs_heap - cdef np.ndarray[FLOAT_T, ndim=1] flat_costs = self.flat_costs - cdef np.ndarray[FLOAT_T, ndim=1] flat_cumulative_costs = \ + cdef cnp.ndarray[FLOAT_T, ndim=1] flat_costs = self.flat_costs + cdef cnp.ndarray[FLOAT_T, ndim=1] flat_cumulative_costs = \ self.flat_cumulative_costs - cdef np.ndarray[OFFSETS_INDEX_T, ndim=1] traceback_offsets = \ + cdef cnp.ndarray[OFFSETS_INDEX_T, ndim=1] traceback_offsets = \ self.traceback_offsets - cdef np.ndarray[EDGE_T, ndim=2] flat_pos_edge_map = \ + cdef cnp.ndarray[EDGE_T, ndim=2] flat_pos_edge_map = \ self.flat_pos_edge_map - cdef np.ndarray[EDGE_T, ndim=2] flat_neg_edge_map = \ + cdef cnp.ndarray[EDGE_T, ndim=2] flat_neg_edge_map = \ self.flat_neg_edge_map - cdef np.ndarray[OFFSET_T, ndim=2] offsets = self.offsets - cdef np.ndarray[INDEX_T, ndim=1] flat_offsets = self.flat_offsets - cdef np.ndarray[FLOAT_T, ndim=1] offset_lengths = self.offset_lengths + cdef cnp.ndarray[OFFSET_T, ndim=2] offsets = self.offsets + cdef cnp.ndarray[INDEX_T, ndim=1] flat_offsets = self.flat_offsets + cdef cnp.ndarray[FLOAT_T, ndim=1] offset_lengths = self.offset_lengths cdef DIM_T dim = self.dim cdef int num_offsets = len(flat_offsets) @@ -514,7 +515,6 @@ cdef class MCP: self.dirty = 1 return cumulative_costs, traceback - @cython.boundscheck(False) def traceback(self, end): """traceback(end) @@ -555,12 +555,12 @@ cdef class MCP: raise ValueError('no minimum-cost path was found ' 'to the specified end point') - cdef np.ndarray[INDEX_T, ndim=1] position = \ + cdef cnp.ndarray[INDEX_T, ndim=1] position = \ np.array(ends[0], dtype=INDEX_D) - cdef np.ndarray[OFFSETS_INDEX_T, ndim=1] traceback_offsets = \ + cdef cnp.ndarray[OFFSETS_INDEX_T, ndim=1] traceback_offsets = \ self.traceback_offsets - cdef np.ndarray[OFFSET_T, ndim=2] offsets = self.offsets - cdef np.ndarray[INDEX_T, ndim=1] flat_offsets = self.flat_offsets + cdef cnp.ndarray[OFFSET_T, ndim=2] offsets = self.offsets + cdef cnp.ndarray[INDEX_T, ndim=1] flat_offsets = self.flat_offsets cdef OFFSETS_INDEX_T offset cdef DIM_T d diff --git a/skimage/io/_plugins/_colormixer.pyx b/skimage/io/_plugins/_colormixer.pyx index ace45c94..4fbe2f18 100644 --- a/skimage/io/_plugins/_colormixer.pyx +++ b/skimage/io/_plugins/_colormixer.pyx @@ -1,4 +1,7 @@ -# -*- python -*- +#cython: cdivision=True +#cython: boundscheck=False +#cython: nonecheck=False +#cython: wraparound=False """Colour Mixer @@ -9,15 +12,14 @@ one. """ import cython -import numpy as np -cimport numpy as np + +cimport numpy as cnp from libc.math cimport exp, pow -@cython.boundscheck(False) -def add(np.ndarray[np.uint8_t, ndim=3] img, - np.ndarray[np.uint8_t, ndim=3] stateimg, - int channel, int amount): +def add(cnp.ndarray[cnp.uint8_t, ndim=3] img, + cnp.ndarray[cnp.uint8_t, ndim=3] stateimg, + Py_ssize_t channel, Py_ssize_t amount): """Add a given amount to a colour channel of `stateimg`, and store the result in `img`. Overflow is clipped. @@ -26,45 +28,44 @@ def add(np.ndarray[np.uint8_t, ndim=3] img, img : (M, N, 3) ndarray of uint8 Output image. stateimg : (M, N, 3) ndarray of uint8 - Input image. + Icnput image. channel : int Channel (0 for "red", 1 for "green", 2 for "blue"). amount : int Value to add. """ - cdef int height = img.shape[0] - cdef int width = img.shape[1] - cdef int k = channel - cdef int n = amount + cdef Py_ssize_t height = img.shape[0] + cdef Py_ssize_t width = img.shape[1] + cdef Py_ssize_t k = channel + cdef Py_ssize_t n = amount - cdef np.int16_t op_result + cdef cnp.int16_t op_result - cdef np.uint8_t lut[256] + cdef cnp.uint8_t lut[256] - cdef int i, j, l + cdef Py_ssize_t i, j, l with nogil: for l from 0 <= l < 256: - op_result = (l + n) + op_result = (l + n) if op_result > 255: op_result = 255 elif op_result < 0: op_result = 0 else: pass - lut[l] = op_result + lut[l] = op_result for i from 0 <= i < height: for j from 0 <= j < width: img[i, j, k] = lut[stateimg[i,j,k]] -@cython.boundscheck(False) -def multiply(np.ndarray[np.uint8_t, ndim=3] img, - np.ndarray[np.uint8_t, ndim=3] stateimg, - int channel, float amount): +def multiply(cnp.ndarray[cnp.uint8_t, ndim=3] img, + cnp.ndarray[cnp.uint8_t, ndim=3] stateimg, + Py_ssize_t channel, float amount): """Multiply a colour channel of `stateimg` by a certain amount, and store the result in `img`. Overflow is clipped. @@ -73,23 +74,23 @@ def multiply(np.ndarray[np.uint8_t, ndim=3] img, img : (M, N, 3) ndarray of uint8 Output image. stateimg : (M, N, 3) ndarray of uint8 - Input image. + Icnput image. channel : int Channel (0 for "red", 1 for "green", 2 for "blue"). amount : float Multiplication factor. """ - cdef int height = img.shape[0] - cdef int width = img.shape[1] - cdef int k = channel + cdef Py_ssize_t height = img.shape[0] + cdef Py_ssize_t width = img.shape[1] + cdef Py_ssize_t k = channel cdef float n = amount cdef float op_result - cdef np.uint8_t lut[256] + cdef cnp.uint8_t lut[256] - cdef int i, j, l + cdef Py_ssize_t i, j, l with nogil: @@ -101,17 +102,16 @@ def multiply(np.ndarray[np.uint8_t, ndim=3] img, op_result = 0 else: pass - lut[l] = op_result + lut[l] = op_result for i from 0 <= i < height: for j from 0 <= j < width: img[i,j,k] = lut[stateimg[i,j,k]] -@cython.boundscheck(False) -def brightness(np.ndarray[np.uint8_t, ndim=3] img, - np.ndarray[np.uint8_t, ndim=3] stateimg, - float factor, int offset): +def brightness(cnp.ndarray[cnp.uint8_t, ndim=3] img, + cnp.ndarray[cnp.uint8_t, ndim=3] stateimg, + float factor, Py_ssize_t offset): """Modify the brightness of an image. 'factor' is multiplied to all channels, which are then added by 'amount'. Overflow is clipped. @@ -121,7 +121,7 @@ def brightness(np.ndarray[np.uint8_t, ndim=3] img, img : (M, N, 3) ndarray of uint8 Output image. stateimg : (M, N, 3) ndarray of uint8 - Input image. + Icnput image. factor : float Multiplication factor. offset : int @@ -129,13 +129,13 @@ def brightness(np.ndarray[np.uint8_t, ndim=3] img, """ - cdef int height = img.shape[0] - cdef int width = img.shape[1] + cdef Py_ssize_t height = img.shape[0] + cdef Py_ssize_t width = img.shape[1] cdef float op_result - cdef np.uint8_t lut[256] + cdef cnp.uint8_t lut[256] - cdef int i, j, k + cdef Py_ssize_t i, j, k with nogil: for k from 0 <= k < 256: @@ -146,7 +146,7 @@ def brightness(np.ndarray[np.uint8_t, ndim=3] img, op_result = 0 else: pass - lut[k] = op_result + lut[k] = op_result for i from 0 <= i < height: for j from 0 <= j < width: @@ -155,27 +155,25 @@ def brightness(np.ndarray[np.uint8_t, ndim=3] img, img[i,j,2] = lut[stateimg[i,j,2]] -@cython.boundscheck(False) -@cython.cdivision(True) -def sigmoid_gamma(np.ndarray[np.uint8_t, ndim=3] img, - np.ndarray[np.uint8_t, ndim=3] stateimg, +def sigmoid_gamma(cnp.ndarray[cnp.uint8_t, ndim=3] img, + cnp.ndarray[cnp.uint8_t, ndim=3] stateimg, float alpha, float beta): - cdef int height = img.shape[0] - cdef int width = img.shape[1] + cdef Py_ssize_t height = img.shape[0] + cdef Py_ssize_t width = img.shape[1] - cdef int i, j, k + cdef Py_ssize_t i, j, k cdef float c1 = 1 / (1 + exp(beta)) cdef float c2 = 1 / (1 + exp(beta - alpha)) - c1 - cdef np.uint8_t lut[256] + cdef cnp.uint8_t lut[256] with nogil: # compute the lut for k from 0 <= k < 256: - lut[k] = (((1 / (1 + exp(beta - (k / 255.) * alpha))) + lut[k] = (((1 / (1 + exp(beta - (k / 255.) * alpha))) - c1) * 255 / c2) for i from 0 <= i < height: for j from 0 <= j < width: @@ -184,17 +182,16 @@ def sigmoid_gamma(np.ndarray[np.uint8_t, ndim=3] img, img[i,j,2] = lut[stateimg[i,j,2]] -@cython.boundscheck(False) -def gamma(np.ndarray[np.uint8_t, ndim=3] img, - np.ndarray[np.uint8_t, ndim=3] stateimg, +def gamma(cnp.ndarray[cnp.uint8_t, ndim=3] img, + cnp.ndarray[cnp.uint8_t, ndim=3] stateimg, float gamma): - cdef int height = img.shape[0] - cdef int width = img.shape[1] + cdef Py_ssize_t height = img.shape[0] + cdef Py_ssize_t width = img.shape[1] - cdef np.uint8_t lut[256] + cdef cnp.uint8_t lut[256] - cdef int i, j, k + cdef Py_ssize_t i, j, k if gamma == 0: gamma = 0.00000000000000000001 @@ -204,7 +201,7 @@ def gamma(np.ndarray[np.uint8_t, ndim=3] img, # compute the lut for k from 0 <= k < 256: - lut[k] = ((pow((k / 255.), gamma) * 255)) + lut[k] = ((pow((k / 255.), gamma) * 255)) for i from 0 <= i < height: for j from 0 <= j < width: @@ -213,7 +210,6 @@ def gamma(np.ndarray[np.uint8_t, ndim=3] img, img[i,j,2] = lut[stateimg[i,j,2]] -@cython.cdivision(True) cdef void rgb_2_hsv(float* RGB, float* HSV) nogil: cdef float R, G, B, H, S, V, MAX, MIN R = RGB[0] @@ -277,11 +273,10 @@ cdef void rgb_2_hsv(float* RGB, float* HSV) nogil: HSV[2] = V -@cython.cdivision(True) cdef void hsv_2_rgb(float* HSV, float* RGB) nogil: cdef float H, S, V cdef float f, p, q, t, r, g, b - cdef int hi + cdef Py_ssize_t hi H = HSV[0] S = HSV[1] @@ -422,9 +417,8 @@ def py_rgb_2_hsv(R, G, B): return (H, S, V) -@cython.boundscheck(False) -def hsv_add(np.ndarray[np.uint8_t, ndim=3] img, - np.ndarray[np.uint8_t, ndim=3] stateimg, +def hsv_add(cnp.ndarray[cnp.uint8_t, ndim=3] img, + cnp.ndarray[cnp.uint8_t, ndim=3] stateimg, float h_amt, float s_amt, float v_amt): """Modify the image color by specifying additive HSV Values. @@ -444,7 +438,7 @@ def hsv_add(np.ndarray[np.uint8_t, ndim=3] img, img : (M, N, 3) ndarray of uint8 Output image. stateimg : (M, N, 3) ndarray of uint8 - Input image. + Icnput image. h_amt : float Ammount to add to H channel. s_amt : float @@ -455,13 +449,13 @@ def hsv_add(np.ndarray[np.uint8_t, ndim=3] img, """ - cdef int height = img.shape[0] - cdef int width = img.shape[1] + cdef Py_ssize_t height = img.shape[0] + cdef Py_ssize_t width = img.shape[1] cdef float HSV[3] cdef float RGB[3] - cdef int i, j + cdef Py_ssize_t i, j with nogil: for i from 0 <= i < height: @@ -483,14 +477,13 @@ def hsv_add(np.ndarray[np.uint8_t, ndim=3] img, RGB[1] *= 255 RGB[2] *= 255 - img[i, j, 0] = RGB[0] - img[i, j, 1] = RGB[1] - img[i, j, 2] = RGB[2] + img[i, j, 0] = RGB[0] + img[i, j, 1] = RGB[1] + img[i, j, 2] = RGB[2] -@cython.boundscheck(False) -def hsv_multiply(np.ndarray[np.uint8_t, ndim=3] img, - np.ndarray[np.uint8_t, ndim=3] stateimg, +def hsv_multiply(cnp.ndarray[cnp.uint8_t, ndim=3] img, + cnp.ndarray[cnp.uint8_t, ndim=3] stateimg, float h_amt, float s_amt, float v_amt): """Modify the image color by specifying multiplicative HSV Values. @@ -514,7 +507,7 @@ def hsv_multiply(np.ndarray[np.uint8_t, ndim=3] img, img : (M, N, 3) ndarray of uint8 Output image. stateimg : (M, N, 3) ndarray of uint8 - Input image. + Icnput image. h_amt : float Ammount to add to H channel. s_amt : float @@ -525,13 +518,13 @@ def hsv_multiply(np.ndarray[np.uint8_t, ndim=3] img, """ - cdef int height = img.shape[0] - cdef int width = img.shape[1] + cdef Py_ssize_t height = img.shape[0] + cdef Py_ssize_t width = img.shape[1] cdef float HSV[3] cdef float RGB[3] - cdef int i, j + cdef Py_ssize_t i, j with nogil: for i from 0 <= i < height: @@ -553,6 +546,6 @@ def hsv_multiply(np.ndarray[np.uint8_t, ndim=3] img, RGB[1] *= 255 RGB[2] *= 255 - img[i, j, 0] = RGB[0] - img[i, j, 1] = RGB[1] - img[i, j, 2] = RGB[2] + img[i, j, 0] = RGB[0] + img[i, j, 1] = RGB[1] + img[i, j, 2] = RGB[2] diff --git a/skimage/io/_plugins/_histograms.pyx b/skimage/io/_plugins/_histograms.pyx index 41ec79a1..1f4344d1 100644 --- a/skimage/io/_plugins/_histograms.pyx +++ b/skimage/io/_plugins/_histograms.pyx @@ -1,7 +1,10 @@ +#cython: cdivision=True +#cython: boundscheck=False +#cython: nonecheck=False +#cython: wraparound=False import numpy as np -cimport numpy as np -import cython +cimport numpy as cnp cdef inline float tri_max(float a, float b, float c): @@ -18,8 +21,7 @@ cdef inline float tri_max(float a, float b, float c): return c -@cython.boundscheck(False) -def histograms(np.ndarray[np.uint8_t, ndim=3] img, int nbins): +def histograms(cnp.ndarray[cnp.uint8_t, ndim=3] img, int nbins): '''Calculate the channel histograms of the current image. Parameters @@ -39,10 +41,7 @@ def histograms(np.ndarray[np.uint8_t, ndim=3] img, int nbins): ''' cdef int width = img.shape[1] cdef int height = img.shape[0] - cdef np.ndarray[np.int32_t, ndim=1] r - cdef np.ndarray[np.int32_t, ndim=1] g - cdef np.ndarray[np.int32_t, ndim=1] b - cdef np.ndarray[np.int32_t, ndim=1] v + cdef cnp.ndarray[cnp.int32_t, ndim=1] r, g, b, v r = np.zeros((nbins,), dtype=np.int32) g = np.zeros((nbins,), dtype=np.int32) diff --git a/skimage/measure/_find_contours.pyx b/skimage/measure/_find_contours.pyx index 91ba318c..d05d9aa7 100644 --- a/skimage/measure/_find_contours.pyx +++ b/skimage/measure/_find_contours.pyx @@ -1,6 +1,10 @@ -# cython: cdivision=True +#cython: cdivision=True +#cython: boundscheck=False +#cython: nonecheck=False +#cython: wraparound=False import numpy as np -cimport numpy as np + +cimport numpy as cnp cdef inline double _get_fraction(double from_value, double to_value, @@ -10,7 +14,7 @@ cdef inline double _get_fraction(double from_value, double to_value, return ((level - from_value) / (to_value - from_value)) -def iterate_and_store(np.ndarray[double, ndim=2] array, +def iterate_and_store(cnp.ndarray[double, ndim=2] array, double level, Py_ssize_t vertex_connect_high): """Iterate across the given array in a marching-squares fashion, looking for segments that cross 'level'. If such a segment is @@ -41,7 +45,8 @@ def iterate_and_store(np.ndarray[double, ndim=2] array, coords[1] = 0 # Calculate the number of iterations we'll need - cdef Py_ssize_t num_square_steps = (array.shape[0] - 1) * (array.shape[1] - 1) + cdef Py_ssize_t num_square_steps = (array.shape[0] - 1) \ + * (array.shape[1] - 1) cdef unsigned char square_case = 0 cdef tuple top, bottom, left, right diff --git a/skimage/measure/_moments.pyx b/skimage/measure/_moments.pyx index b94a7b89..145f6052 100644 --- a/skimage/measure/_moments.pyx +++ b/skimage/measure/_moments.pyx @@ -1,14 +1,16 @@ -#cython: boundscheck=False -#cython: wraparound=False #cython: cdivision=True +#cython: boundscheck=False +#cython: nonecheck=False +#cython: wraparound=False import numpy as np -cimport numpy as np + +cimport numpy as cnp -def central_moments(np.ndarray[np.double_t, ndim=2] array, double cr, double cc, - int order): +def central_moments(cnp.ndarray[cnp.double_t, ndim=2] array, double cr, + double cc, int order): cdef Py_ssize_t p, q, r, c - cdef np.ndarray[np.double_t, ndim=2] mu + cdef cnp.ndarray[cnp.double_t, ndim=2] mu mu = np.zeros((order + 1, order + 1), 'double') for p in range(order + 1): for q in range(order + 1): @@ -17,9 +19,10 @@ def central_moments(np.ndarray[np.double_t, ndim=2] array, double cr, double cc, mu[p,q] += array[r,c] * (r - cr) ** q * (c - cc) ** p return mu -def normalized_moments(np.ndarray[np.double_t, ndim=2] mu, int order): + +def normalized_moments(cnp.ndarray[cnp.double_t, ndim=2] mu, int order): cdef Py_ssize_t p, q - cdef np.ndarray[np.double_t, ndim=2] nu + cdef cnp.ndarray[cnp.double_t, ndim=2] nu nu = np.zeros((order + 1, order + 1), 'double') for p in range(order + 1): for q in range(order + 1): @@ -29,8 +32,9 @@ def normalized_moments(np.ndarray[np.double_t, ndim=2] mu, int order): nu[p,q] = np.nan return nu -def hu_moments(np.ndarray[np.double_t, ndim=2] nu): - cdef np.ndarray[np.double_t, ndim=1] hu = np.zeros((7,), 'double') + +def hu_moments(cnp.ndarray[cnp.double_t, ndim=2] nu): + cdef cnp.ndarray[cnp.double_t, ndim=1] hu = np.zeros((7,), 'double') cdef double t0 = nu[3,0] + nu[1,2] cdef double t1 = nu[2,1] + nu[0,3] cdef double q0 = t0 * t0 diff --git a/skimage/morphology/_convex_hull.pyx b/skimage/morphology/_convex_hull.pyx index 9bec825b..e4b6470c 100644 --- a/skimage/morphology/_convex_hull.pyx +++ b/skimage/morphology/_convex_hull.pyx @@ -1,9 +1,13 @@ -# -*- python -*- - -cimport numpy as np +#cython: cdivision=True +#cython: boundscheck=False +#cython: nonecheck=False +#cython: wraparound=False import numpy as np -def possible_hull(np.ndarray[dtype=np.uint8_t, ndim=2, mode="c"] img): +cimport numpy as cnp + + +def possible_hull(cnp.ndarray[dtype=cnp.uint8_t, ndim=2, mode="c"] img): """Return positions of pixels that possibly belong to the convex hull. Parameters @@ -26,7 +30,7 @@ def possible_hull(np.ndarray[dtype=np.uint8_t, ndim=2, mode="c"] img): # cols storage slots for top boundary pixels # rows storage slots for right boundary pixels # cols storage slots for bottom boundary pixels - cdef np.ndarray[dtype=np.intp_t, ndim=2] nonzero = \ + cdef cnp.ndarray[dtype=cnp.intp_t, ndim=2] nonzero = \ np.ones((2 * (rows + cols), 2), dtype=np.int) nonzero *= -1 diff --git a/skimage/morphology/_pnpoly.pyx b/skimage/morphology/_pnpoly.pyx index f6cf386f..f32778cc 100644 --- a/skimage/morphology/_pnpoly.pyx +++ b/skimage/morphology/_pnpoly.pyx @@ -1,7 +1,10 @@ -# -*- python -*- - -cimport numpy as np +#cython: cdivision=True +#cython: boundscheck=False +#cython: nonecheck=False +#cython: wraparound=False import numpy as np + +cimport numpy as cnp from skimage._shared.geometry cimport point_in_polygon, points_in_polygon @@ -26,7 +29,7 @@ def grid_points_inside_poly(shape, verts): True where the grid falls inside the polygon. """ - cdef np.ndarray[np.double_t, ndim=1, mode="c"] vx, vy + cdef cnp.ndarray[cnp.double_t, ndim=1, mode="c"] vx, vy verts = np.asarray(verts) vx = verts[:, 0].astype(np.double) @@ -37,7 +40,7 @@ def grid_points_inside_poly(shape, verts): cdef Py_ssize_t N = shape[1] cdef Py_ssize_t m, n - cdef np.ndarray[dtype=np.uint8_t, ndim=2, mode="c"] out = \ + cdef cnp.ndarray[dtype=cnp.uint8_t, ndim=2, mode="c"] out = \ np.zeros((M, N), dtype=np.uint8) for m in range(M): @@ -65,7 +68,7 @@ def points_inside_poly(points, verts): True if corresponding point is inside the polygon. """ - cdef np.ndarray[np.double_t, ndim=1, mode="c"] x, y, vx, vy + cdef cnp.ndarray[cnp.double_t, ndim=1, mode="c"] x, y, vx, vy points = np.asarray(points) verts = np.asarray(verts) @@ -76,12 +79,12 @@ def points_inside_poly(points, verts): vx = verts[:, 0].astype(np.double) vy = verts[:, 1].astype(np.double) - cdef np.ndarray[np.uint8_t, ndim=1] out = \ + cdef cnp.ndarray[cnp.uint8_t, ndim=1] out = \ np.zeros(x.shape[0], dtype=np.uint8) points_in_polygon(vx.shape[0], vx.data, vy.data, - x.shape[0], x.data, y.data, - out.data) + x.shape[0], x.data, y.data, + out.data) return out.astype(bool) diff --git a/skimage/morphology/_skeletonize_cy.pyx b/skimage/morphology/_skeletonize_cy.pyx index 9bef86b9..13e303d4 100644 --- a/skimage/morphology/_skeletonize_cy.pyx +++ b/skimage/morphology/_skeletonize_cy.pyx @@ -1,3 +1,8 @@ +#cython: cdivision=True +#cython: boundscheck=False +#cython: nonecheck=False +#cython: wraparound=False + ''' Originally part of CellProfiler, code licensed under both GPL and BSD licenses. Website: http://www.cellprofiler.org @@ -10,21 +15,20 @@ Original author: Lee Kamentsky ''' import numpy as np -cimport numpy as np -cimport cython + +cimport numpy as cnp -@cython.boundscheck(False) -def _skeletonize_loop(np.ndarray[dtype=np.uint8_t, ndim=2, - negative_indices=False, mode='c'] result, - np.ndarray[dtype=np.intp_t, ndim=1, - negative_indices=False, mode='c'] i, - np.ndarray[dtype=np.intp_t, ndim=1, - negative_indices=False, mode='c'] j, - np.ndarray[dtype=np.int32_t, ndim=1, - negative_indices=False, mode='c'] order, - np.ndarray[dtype=np.uint8_t, ndim=1, - negative_indices=False, mode='c'] table): +def _skeletonize_loop(cnp.ndarray[dtype=cnp.uint8_t, ndim=2, + negative_indices=False, mode='c'] result, + cnp.ndarray[dtype=cnp.intp_t, ndim=1, + negative_indices=False, mode='c'] i, + cnp.ndarray[dtype=cnp.intp_t, ndim=1, + negative_indices=False, mode='c'] j, + cnp.ndarray[dtype=cnp.int32_t, ndim=1, + negative_indices=False, mode='c'] order, + cnp.ndarray[dtype=cnp.uint8_t, ndim=1, + negative_indices=False, mode='c'] table): """ Inner loop of skeletonize function @@ -61,7 +65,7 @@ def _skeletonize_loop(np.ndarray[dtype=np.uint8_t, ndim=2, pixels. """ cdef: - np.int32_t accumulator + cnp.int32_t accumulator Py_ssize_t index, order_index Py_ssize_t ii, jj @@ -92,9 +96,10 @@ def _skeletonize_loop(np.ndarray[dtype=np.uint8_t, ndim=2, # Assign the value of table corresponding to the configuration result[ii, jj] = table[accumulator] -@cython.boundscheck(False) -def _table_lookup_index(np.ndarray[dtype=np.uint8_t, ndim=2, - negative_indices=False, mode='c'] image): + + +def _table_lookup_index(cnp.ndarray[dtype=cnp.uint8_t, ndim=2, + negative_indices=False, mode='c'] image): """ Return an index into a table per pixel of a binary image @@ -115,10 +120,10 @@ def _table_lookup_index(np.ndarray[dtype=np.uint8_t, ndim=2, hardwired kernel. """ cdef: - np.ndarray[dtype=np.int32_t, ndim=2, - negative_indices=False, mode='c'] indexer - np.int32_t *p_indexer - np.uint8_t *p_image + cnp.ndarray[dtype=cnp.int32_t, ndim=2, + negative_indices=False, mode='c'] indexer + cnp.int32_t *p_indexer + cnp.uint8_t *p_image Py_ssize_t i_stride Py_ssize_t i_shape Py_ssize_t j_shape @@ -129,8 +134,8 @@ def _table_lookup_index(np.ndarray[dtype=np.uint8_t, ndim=2, i_shape = image.shape[0] j_shape = image.shape[1] indexer = np.zeros((i_shape, j_shape), np.int32) - p_indexer = indexer.data - p_image = image.data + p_indexer = indexer.data + p_image = image.data i_stride = image.strides[0] assert i_shape >= 3 and j_shape >= 3, \ "Please use the slow method for arrays < 3x3" diff --git a/skimage/morphology/ccomp.pxd b/skimage/morphology/ccomp.pxd index b50703e9..569921fa 100644 --- a/skimage/morphology/ccomp.pxd +++ b/skimage/morphology/ccomp.pxd @@ -1,8 +1,8 @@ """Export fast union find in Cython""" -cimport numpy as np +cimport numpy as cnp -DTYPE = np.intp -ctypedef np.intp_t DTYPE_t +DTYPE = cnp.intp +ctypedef cnp.intp_t DTYPE_t cdef DTYPE_t find_root(DTYPE_t *forest, DTYPE_t n) cdef set_root(DTYPE_t *forest, DTYPE_t n, DTYPE_t root) diff --git a/skimage/morphology/ccomp.pyx b/skimage/morphology/ccomp.pyx index dc85196b..bbf6ed44 100644 --- a/skimage/morphology/ccomp.pyx +++ b/skimage/morphology/ccomp.pyx @@ -1,8 +1,11 @@ -# -*- python -*- #cython: cdivision=True +#cython: boundscheck=False +#cython: nonecheck=False +#cython: wraparound=False import numpy as np -cimport numpy as np + +cimport numpy as cnp """ See also: @@ -140,9 +143,9 @@ def label(input, DTYPE_t neighbors=8, DTYPE_t background=-1): cdef DTYPE_t rows = input.shape[0] cdef DTYPE_t cols = input.shape[1] - cdef np.ndarray[DTYPE_t, ndim=2] data = np.array(input, copy=True, - dtype=DTYPE) - cdef np.ndarray[DTYPE_t, ndim=2] forest + cdef cnp.ndarray[DTYPE_t, ndim=2] data = np.array(input, copy=True, + dtype=DTYPE) + cdef cnp.ndarray[DTYPE_t, ndim=2] forest forest = np.arange(data.size, dtype=DTYPE).reshape((rows, cols)) diff --git a/skimage/morphology/heap_watershed.pxi b/skimage/morphology/heap_watershed.pxi index 573d32bb..07b29f5c 100644 --- a/skimage/morphology/heap_watershed.pxi +++ b/skimage/morphology/heap_watershed.pxi @@ -9,14 +9,12 @@ All rights reserved. Original author: Lee Kamentsky """ -import numpy as np -cimport numpy as np -cimport cython +cimport numpy as cnp cdef struct Heapitem: - np.int32_t value - np.int32_t age + cnp.int32_t value + cnp.int32_t age Py_ssize_t index diff --git a/skimage/segmentation/_felzenszwalb_cy.pyx b/skimage/segmentation/_felzenszwalb_cy.pyx index 5a8ac012..f285e3db 100644 --- a/skimage/segmentation/_felzenszwalb_cy.pyx +++ b/skimage/segmentation/_felzenszwalb_cy.pyx @@ -1,15 +1,17 @@ +#cython: cdivision=True +#cython: boundscheck=False +#cython: nonecheck=False +#cython: wraparound=False import numpy as np -cimport numpy as np import scipy -cimport cython +cimport cython +cimport numpy as cnp from skimage.morphology.ccomp cimport find_root, join_trees from ..util import img_as_float -@cython.boundscheck(False) -@cython.wraparound(False) -@cython.cdivision(True) + def _felzenszwalb_grey(image, double scale=1, sigma=0.8, Py_ssize_t min_size=20): """Felzenszwalb's efficient graph based segmentation for a single channel. @@ -49,31 +51,31 @@ def _felzenszwalb_grey(image, double scale=1, sigma=0.8, Py_ssize_t min_size=20) down_cost = np.abs((image[:, 1:] - image[:, :-1])) dright_cost = np.abs((image[1:, 1:] - image[:-1, :-1])) uright_cost = np.abs((image[1:, :-1] - image[:-1, 1:])) - cdef np.ndarray[np.float_t, ndim=1] costs = np.hstack([right_cost.ravel(), + cdef cnp.ndarray[cnp.float_t, ndim=1] costs = np.hstack([right_cost.ravel(), down_cost.ravel(), dright_cost.ravel(), uright_cost.ravel()]).astype(np.float) # compute edges between pixels: height, width = image.shape[:2] - cdef np.ndarray[np.intp_t, ndim=2] segments \ + cdef cnp.ndarray[cnp.intp_t, ndim=2] segments \ = np.arange(width * height, dtype=np.intp).reshape(height, width) right_edges = np.c_[segments[1:, :].ravel(), segments[:-1, :].ravel()] down_edges = np.c_[segments[:, 1:].ravel(), segments[:, :-1].ravel()] dright_edges = np.c_[segments[1:, 1:].ravel(), segments[:-1, :-1].ravel()] uright_edges = np.c_[segments[:-1, 1:].ravel(), segments[1:, :-1].ravel()] - cdef np.ndarray[np.intp_t, ndim=2] edges \ + cdef cnp.ndarray[cnp.intp_t, ndim=2] edges \ = np.vstack([right_edges, down_edges, dright_edges, uright_edges]) # initialize data structures for segment size # and inner cost, then start greedy iteration over edges. edge_queue = np.argsort(costs) edges = np.ascontiguousarray(edges[edge_queue]) costs = np.ascontiguousarray(costs[edge_queue]) - cdef np.intp_t *segments_p = segments.data - cdef np.intp_t *edges_p = edges.data - cdef np.float_t *costs_p = costs.data - cdef np.ndarray[np.intp_t, ndim=1] segment_size \ + cdef cnp.intp_t *segments_p = segments.data + cdef cnp.intp_t *edges_p = edges.data + cdef cnp.float_t *costs_p = costs.data + cdef cnp.ndarray[cnp.intp_t, ndim=1] segment_size \ = np.ones(width * height, dtype=np.int) # inner cost of segments - cdef np.ndarray[np.float_t, ndim=1] cint = np.zeros(width * height) + cdef cnp.ndarray[cnp.float_t, ndim=1] cint = np.zeros(width * height) cdef int seg0, seg1, seg_new, e cdef float cost, inner_cost0, inner_cost1 # set costs_p back one. we increase it before we use it @@ -96,7 +98,7 @@ def _felzenszwalb_grey(image, double scale=1, sigma=0.8, Py_ssize_t min_size=20) cint[seg_new] = costs_p[0] # postprocessing to remove small segments - edges_p = edges.data + edges_p = edges.data for e in range(costs.size): seg0 = find_root(segments_p, edges_p[0]) seg1 = find_root(segments_p, edges_p[1]) diff --git a/skimage/segmentation/_quickshift.pyx b/skimage/segmentation/_quickshift.pyx index 61e4d0e0..cc649e8f 100644 --- a/skimage/segmentation/_quickshift.pyx +++ b/skimage/segmentation/_quickshift.pyx @@ -1,18 +1,18 @@ +#cython: cdivision=True +#cython: boundscheck=False +#cython: nonecheck=False +#cython: wraparound=False import numpy as np -cimport numpy as np -cimport cython -from libc.math cimport exp, sqrt - -from itertools import product from scipy import ndimage +from itertools import product + +cimport numpy as cnp +from libc.math cimport exp, sqrt from ..util import img_as_float from ..color import rgb2lab -@cython.boundscheck(False) -@cython.wraparound(False) -@cython.cdivision(True) def quickshift(image, ratio=1., float kernel_size=5, max_dist=10, return_tree=False, sigma=0, convert2lab=True, random_seed=None): """Segments image using quickshift clustering in Color-(x,y) space. @@ -69,7 +69,7 @@ def quickshift(image, ratio=1., float kernel_size=5, max_dist=10, image = rgb2lab(image) image = ndimage.gaussian_filter(img_as_float(image), [sigma, sigma, 0]) - cdef np.ndarray[dtype=np.float_t, ndim=3, mode="c"] image_c \ + cdef cnp.ndarray[dtype=cnp.float_t, ndim=3, mode="c"] image_c \ = np.ascontiguousarray(image) * ratio random_state = np.random.RandomState(random_seed) @@ -92,10 +92,10 @@ def quickshift(image, ratio=1., float kernel_size=5, max_dist=10, cdef Py_ssize_t r, c, r_, c_, channel, r_min, c_min - cdef np.float_t* image_p = image_c.data - cdef np.float_t* current_pixel_p = image_p + cdef cnp.float_t* image_p = image_c.data + cdef cnp.float_t* current_pixel_p = image_p - cdef np.ndarray[dtype=np.float_t, ndim=2] densities \ + cdef cnp.ndarray[dtype=cnp.float_t, ndim=2] densities \ = np.zeros((height, width)) # compute densities @@ -117,9 +117,9 @@ def quickshift(image, ratio=1., float kernel_size=5, max_dist=10, densities += random_state.normal(scale=0.00001, size=(height, width)) # default parent to self: - cdef np.ndarray[dtype=np.int_t, ndim=2] parent \ + cdef cnp.ndarray[dtype=cnp.int_t, ndim=2] parent \ = np.arange(width * height).reshape(height, width) - cdef np.ndarray[dtype=np.float_t, ndim=2] dist_parent \ + cdef cnp.ndarray[dtype=cnp.float_t, ndim=2] dist_parent \ = np.zeros((height, width)) # find nearest node with higher density diff --git a/skimage/segmentation/_slic.pyx b/skimage/segmentation/_slic.pyx index e91ed21d..7db072c0 100644 --- a/skimage/segmentation/_slic.pyx +++ b/skimage/segmentation/_slic.pyx @@ -1,8 +1,13 @@ +#cython: cdivision=True #cython: boundscheck=False +#cython: nonecheck=False +#cython: wraparound=False import numpy as np -cimport numpy as np from time import time from scipy import ndimage + +cimport numpy as cnp + from ..util import img_as_float from ..color import rgb2lab, gray2rgb @@ -72,32 +77,32 @@ def slic(image, n_segments=100, ratio=10., max_iter=10, sigma=1, means_x = grid_x[::step, ::step] means_color = np.zeros((means_y.shape[0], means_y.shape[1], 3)) - cdef np.ndarray[dtype=np.float_t, ndim=2] means \ + cdef cnp.ndarray[dtype=cnp.float_t, ndim=2] means \ = np.dstack([means_y, means_x, means_color]).reshape(-1, 5) - cdef np.float_t* current_mean - cdef np.float_t* mean_entry + cdef cnp.float_t* current_mean + cdef cnp.float_t* mean_entry n_means = means.shape[0] # we do the scaling of ratio in the same way as in the SLIC paper # so the values have the same meaning ratio = (ratio / float(step)) ** 2 - cdef np.ndarray[dtype=np.float_t, ndim=3] image_yx \ + cdef cnp.ndarray[dtype=cnp.float_t, ndim=3] image_yx \ = np.dstack([grid_y, grid_x, image / ratio]).copy("C") cdef Py_ssize_t i, k, x, y, x_min, x_max, y_min, y_max, changes cdef double dist_mean - cdef np.ndarray[dtype=np.intp_t, ndim=2] nearest_mean \ + cdef cnp.ndarray[dtype=cnp.intp_t, ndim=2] nearest_mean \ = np.zeros((height, width), dtype=np.intp) - cdef np.ndarray[dtype=np.float_t, ndim=2] distance \ + cdef cnp.ndarray[dtype=cnp.float_t, ndim=2] distance \ = np.empty((height, width)) - cdef np.float_t* image_p = image_yx.data - cdef np.float_t* distance_p = distance.data - cdef np.float_t* current_distance - cdef np.float_t* current_pixel + cdef cnp.float_t* image_p = image_yx.data + cdef cnp.float_t* distance_p = distance.data + cdef cnp.float_t* current_distance + cdef cnp.float_t* current_pixel cdef double tmp for i in range(max_iter): distance.fill(np.inf) changes = 0 - current_mean = means.data + current_mean = means.data # assign pixels to means for k in range(n_means): # compute windows: diff --git a/skimage/transform/_hough_transform.pyx b/skimage/transform/_hough_transform.pyx index 662b3b88..5d1f27af 100644 --- a/skimage/transform/_hough_transform.pyx +++ b/skimage/transform/_hough_transform.pyx @@ -2,9 +2,9 @@ #cython: boundscheck=False #cython: nonecheck=False #cython: wraparound=False -cimport cython import numpy as np -cimport numpy as np + +cimport numpy as cnp from libc.math cimport abs, fabs, sqrt, ceil from libc.stdlib cimport rand @@ -17,14 +17,14 @@ cdef inline Py_ssize_t round(double r): return ((r + 0.5) if (r > 0.0) else (r - 0.5)) -def _hough(np.ndarray img, np.ndarray[ndim=1, dtype=np.double_t] theta=None): +def _hough(cnp.ndarray img, cnp.ndarray[ndim=1, dtype=cnp.double_t] theta=None): if img.ndim != 2: raise ValueError('The input image must be 2D.') # Compute the array of angles and their sine and cosine - cdef np.ndarray[ndim=1, dtype=np.double_t] ctheta - cdef np.ndarray[ndim=1, dtype=np.double_t] stheta + cdef cnp.ndarray[ndim=1, dtype=cnp.double_t] ctheta + cdef cnp.ndarray[ndim=1, dtype=cnp.double_t] stheta if theta is None: theta = np.linspace(PI_2, NEG_PI_2, 180) @@ -33,8 +33,8 @@ def _hough(np.ndarray img, np.ndarray[ndim=1, dtype=np.double_t] theta=None): stheta = np.sin(theta) # compute the bins and allocate the accumulator array - cdef np.ndarray[ndim=2, dtype=np.uint64_t] accum - cdef np.ndarray[ndim=1, dtype=np.double_t] bins + cdef cnp.ndarray[ndim=2, dtype=cnp.uint64_t] accum + cdef cnp.ndarray[ndim=1, dtype=cnp.double_t] bins cdef Py_ssize_t max_distance, offset max_distance = 2 * ceil(sqrt(img.shape[0] * img.shape[0] + @@ -44,7 +44,7 @@ def _hough(np.ndarray img, np.ndarray[ndim=1, dtype=np.double_t] theta=None): offset = max_distance / 2 # compute the nonzero indexes - cdef np.ndarray[ndim=1, dtype=np.npy_intp] x_idxs, y_idxs + cdef cnp.ndarray[ndim=1, dtype=cnp.npy_intp] x_idxs, y_idxs y_idxs, x_idxs = np.nonzero(img) # finally, run the transform @@ -60,9 +60,9 @@ def _hough(np.ndarray img, np.ndarray[ndim=1, dtype=np.double_t] theta=None): return accum, theta, bins -def _probabilistic_hough(np.ndarray img, int value_threshold, +def _probabilistic_hough(cnp.ndarray img, int value_threshold, int line_length, int line_gap, - np.ndarray[ndim=1, dtype=np.double_t] theta=None): + cnp.ndarray[ndim=1, dtype=cnp.double_t] theta=None): if img.ndim != 2: raise ValueError('The input image must be 2D.') @@ -74,11 +74,11 @@ def _probabilistic_hough(np.ndarray img, int value_threshold, cdef Py_ssize_t width = img.shape[1] # compute the bins and allocate the accumulator array - cdef np.ndarray[ndim=2, dtype=np.int64_t] accum - cdef np.ndarray[ndim=1, dtype=np.double_t] ctheta, stheta - cdef np.ndarray[ndim=2, dtype=np.uint8_t] mask = \ + cdef cnp.ndarray[ndim=2, dtype=cnp.int64_t] accum + cdef cnp.ndarray[ndim=1, dtype=cnp.double_t] ctheta, stheta + cdef cnp.ndarray[ndim=2, dtype=cnp.uint8_t] mask = \ np.zeros((height, width), dtype=np.uint8) - cdef np.ndarray[ndim=2, dtype=np.int32_t] line_end = \ + cdef cnp.ndarray[ndim=2, dtype=cnp.int32_t] line_end = \ np.zeros((2, 2), dtype=np.int32) cdef Py_ssize_t max_distance, offset, num_indexes, index cdef double a, b diff --git a/skimage/transform/_warps_cy.pyx b/skimage/transform/_warps_cy.pyx index 951eb861..968f643a 100644 --- a/skimage/transform/_warps_cy.pyx +++ b/skimage/transform/_warps_cy.pyx @@ -2,9 +2,9 @@ #cython: boundscheck=False #cython: nonecheck=False #cython: wraparound=False - -cimport numpy as np import numpy as np + +cimport numpy as cnp from skimage._shared.interpolation cimport (nearest_neighbour_interpolation, bilinear_interpolation, biquadratic_interpolation, @@ -35,7 +35,7 @@ cdef inline void _matrix_transform(double x, double y, double* H, double *x_, y_[0] = yy / zz -def _warp_fast(np.ndarray image, np.ndarray H, output_shape=None, int order=1, +def _warp_fast(cnp.ndarray image, cnp.ndarray H, output_shape=None, int order=1, mode='constant', double cval=0): """Projective transformation (homography). @@ -83,9 +83,9 @@ def _warp_fast(np.ndarray image, np.ndarray H, output_shape=None, int order=1, """ - cdef np.ndarray[dtype=np.double_t, ndim=2, mode="c"] img = \ + cdef cnp.ndarray[dtype=cnp.double_t, ndim=2, mode="c"] img = \ np.ascontiguousarray(image, dtype=np.double) - cdef np.ndarray[dtype=np.double_t, ndim=2, mode="c"] M = \ + cdef cnp.ndarray[dtype=cnp.double_t, ndim=2, mode="c"] M = \ np.ascontiguousarray(H) if mode not in ('constant', 'wrap', 'reflect', 'nearest'): @@ -101,7 +101,7 @@ def _warp_fast(np.ndarray image, np.ndarray H, output_shape=None, int order=1, out_r = output_shape[0] out_c = output_shape[1] - cdef np.ndarray[dtype=np.double_t, ndim=2] out = \ + cdef cnp.ndarray[dtype=cnp.double_t, ndim=2] out = \ np.zeros((out_r, out_c), dtype=np.double) cdef Py_ssize_t tfr, tfc From 21d480c32c29798ab232614fa68883a43ea6a0d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Sun, 24 Feb 2013 14:20:02 +0100 Subject: [PATCH 096/215] Fix accidentially changed doc strings --- skimage/io/_plugins/_colormixer.pyx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/skimage/io/_plugins/_colormixer.pyx b/skimage/io/_plugins/_colormixer.pyx index 4fbe2f18..64ac62ff 100644 --- a/skimage/io/_plugins/_colormixer.pyx +++ b/skimage/io/_plugins/_colormixer.pyx @@ -28,7 +28,7 @@ def add(cnp.ndarray[cnp.uint8_t, ndim=3] img, img : (M, N, 3) ndarray of uint8 Output image. stateimg : (M, N, 3) ndarray of uint8 - Icnput image. + Input image. channel : int Channel (0 for "red", 1 for "green", 2 for "blue"). amount : int @@ -74,7 +74,7 @@ def multiply(cnp.ndarray[cnp.uint8_t, ndim=3] img, img : (M, N, 3) ndarray of uint8 Output image. stateimg : (M, N, 3) ndarray of uint8 - Icnput image. + Input image. channel : int Channel (0 for "red", 1 for "green", 2 for "blue"). amount : float @@ -121,7 +121,7 @@ def brightness(cnp.ndarray[cnp.uint8_t, ndim=3] img, img : (M, N, 3) ndarray of uint8 Output image. stateimg : (M, N, 3) ndarray of uint8 - Icnput image. + Input image. factor : float Multiplication factor. offset : int @@ -438,7 +438,7 @@ def hsv_add(cnp.ndarray[cnp.uint8_t, ndim=3] img, img : (M, N, 3) ndarray of uint8 Output image. stateimg : (M, N, 3) ndarray of uint8 - Icnput image. + Input image. h_amt : float Ammount to add to H channel. s_amt : float @@ -507,7 +507,7 @@ def hsv_multiply(cnp.ndarray[cnp.uint8_t, ndim=3] img, img : (M, N, 3) ndarray of uint8 Output image. stateimg : (M, N, 3) ndarray of uint8 - Icnput image. + Input image. h_amt : float Ammount to add to H channel. s_amt : float From 84cf9430de08f9f4d94de75cdf9d16858c92bb35 Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Sun, 24 Feb 2013 08:57:27 -0600 Subject: [PATCH 097/215] Fix alpha handling for image saving. --- skimage/viewer/viewers/core.py | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/skimage/viewer/viewers/core.py b/skimage/viewer/viewers/core.py index 8d40fb12..193c5114 100644 --- a/skimage/viewer/viewers/core.py +++ b/skimage/viewer/viewers/core.py @@ -10,6 +10,7 @@ except ImportError: from skimage import io, img_as_float from skimage.util.dtype import dtype_range +from skimage.exposure import rescale_intensity import numpy as np from .. import utils from ..widgets import Slider @@ -19,6 +20,18 @@ from ..utils import dialogs __all__ = ['ImageViewer', 'CollectionViewer'] +def mpl_image_to_rgba(mpl_image): + """Return RGB image from the given matplotlib image object. + + Each image in a matplotlib figure has it's own colormap and normalization + function. Return RGBA (RGB + alpha channel) image with float dtype. + """ + input_range = (mpl_image.norm.vmin, mpl_image.norm.vmax) + image = rescale_intensity(mpl_image.get_array(), in_range=input_range) + image = mpl_image.cmap(img_as_float(image)) # cmap complains on bool arrays + return img_as_float(image) + + class ImageViewer(QMainWindow): """Viewer for displaying images. @@ -121,17 +134,18 @@ class ImageViewer(QMainWindow): if filename is None: return if len(self.ax.images) == 1: - io.imsave(self.image, filename) + io.imsave(filename, self.image) else: - im1 = self.ax.images[1] - overlay = im1.cmap(img_as_float(im1.get_array())) - overlay = img_as_float(overlay) - im0 = self.ax.images[0] - underlay = im0.cmap(img_as_float(im0.get_array())) - underlay = img_as_float(underlay) + underlay = mpl_image_to_rgba(self.ax.images[0]) + overlay = mpl_image_to_rgba(self.ax.images[1]) alpha = overlay[:, :, 3] - alpha = np.dstack((alpha, alpha, alpha)) - alpha /= alpha.max() + + # alpha can be set by channel of array or by a scalar value. + # Prefer the alpha channel, but fall back to scalar value. + if np.all(alpha == 1): + alpha = np.ones_like(alpha) * self.ax.images[1].get_alpha() + + alpha = alpha[:, :, np.newaxis] composite = (overlay[:, :, :3] * alpha + underlay[:, :, :3] * (1 - alpha)) io.imsave(filename, composite) From 19e69e94c112b2f03a50ff8e570003ceeed4fc04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Boulogne?= Date: Sun, 24 Feb 2013 16:42:37 +0100 Subject: [PATCH 098/215] Docstring circle + ellipse --- skimage/draw/_draw.pyx | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/skimage/draw/_draw.pyx b/skimage/draw/_draw.pyx index efd5fdc5..1a868ae5 100644 --- a/skimage/draw/_draw.pyx +++ b/skimage/draw/_draw.pyx @@ -138,6 +138,10 @@ def ellipse(double cy, double cx, double yradius, double xradius, shape=None): Centre coordinate of ellipse. yradius, xradius : double Minor and major semi-axes. ``(x/xradius)**2 + (y/yradius)**2 = 1``. + shape : tuple, optional + image shape which is used to determine maximum extents of output pixel + coordinates. This is useful for ellipses which exceed the image size. + By default the full extents of the ellipse are used. Returns ------- @@ -182,6 +186,10 @@ def circle(double cy, double cx, double radius, shape=None): Centre coordinate of circle. radius: double Radius of circle. + shape : tuple, optional + image shape which is used to determine maximum extents of output pixel + coordinates. This is useful for circles which exceed the image size. + By default the full extents of the circle are used. Returns ------- @@ -189,7 +197,9 @@ def circle(double cy, double cx, double radius, shape=None): Pixel coordinates of circle. May be used to directly index into an array, e.g. ``img[rr, cc] = 1``. - + Notes + ----- + This function is a wrapper for skimage.draw.ellipse() """ return ellipse(cy, cx, radius, radius, shape) From 27b837b79979957879ebf027323d86f2aedf2327 Mon Sep 17 00:00:00 2001 From: Christoph Gohlke Date: Sun, 24 Feb 2013 08:57:53 -0800 Subject: [PATCH 099/215] Use log(2) instead of log10(2) --- skimage/filter/rank/_crank16.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skimage/filter/rank/_crank16.pyx b/skimage/filter/rank/_crank16.pyx index b7aca483..3c0e5ceb 100644 --- a/skimage/filter/rank/_crank16.pyx +++ b/skimage/filter/rank/_crank16.pyx @@ -274,7 +274,7 @@ cdef inline np.uint16_t kernel_entropy(Py_ssize_t * histo, float pop, for i in range(maxbin): p = histo[i] / pop if p > 0: - e -= p * log(p) / 0.30102999566398119521373889472449 + e -= p * log(p) / 0.6931471805599453 return < np.uint16_t > e * 1000 else: From 2e230c70c0c512b812e0bbd4999c2d77a42cc155 Mon Sep 17 00:00:00 2001 From: Christoph Gohlke Date: Sun, 24 Feb 2013 09:01:09 -0800 Subject: [PATCH 100/215] Use log(2) instead of log10(2) --- skimage/filter/rank/_crank8.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skimage/filter/rank/_crank8.pyx b/skimage/filter/rank/_crank8.pyx index d7ece4dd..c64d78ba 100644 --- a/skimage/filter/rank/_crank8.pyx +++ b/skimage/filter/rank/_crank8.pyx @@ -279,7 +279,7 @@ cdef inline np.uint8_t kernel_entropy(Py_ssize_t * histo, float pop, for i in range(256): p = histo[i] / pop if p > 0: - e -= p * log(p) / 0.30102999566398119521373889472449 + e -= p * log(p) / 0.6931471805599453 return < np.uint8_t > e * 10 else: From a17d8182aabfe10ba4f6629435a2bd11bcf80852 Mon Sep 17 00:00:00 2001 From: Stefan van der Walt Date: Thu, 17 Jan 2013 11:11:21 +0700 Subject: [PATCH 101/215] PKG: MSVC build fixes by Christoph Gohlke. --- skimage/filter/rank/_crank16.pyx | 2 +- skimage/filter/rank/_crank8.pyx | 2 +- skimage/filter/setup.py | 20 ++++++++++---------- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/skimage/filter/rank/_crank16.pyx b/skimage/filter/rank/_crank16.pyx index 87199e80..667c179d 100644 --- a/skimage/filter/rank/_crank16.pyx +++ b/skimage/filter/rank/_crank16.pyx @@ -276,7 +276,7 @@ cdef inline dtype_t kernel_entropy(Py_ssize_t * histo, float pop, for i in range(maxbin): p = histo[i] / pop if p > 0: - e -= p * log2(p) + e -= p * log(p) / 0.30102999566398119521373889472449 return e * 1000 else: diff --git a/skimage/filter/rank/_crank8.pyx b/skimage/filter/rank/_crank8.pyx index c9e8b14f..3e06f319 100644 --- a/skimage/filter/rank/_crank8.pyx +++ b/skimage/filter/rank/_crank8.pyx @@ -281,7 +281,7 @@ cdef inline dtype_t kernel_entropy(Py_ssize_t * histo, float pop, for i in range(256): p = histo[i] / pop if p > 0: - e -= p * log2(p) + e -= p * log(p) / 0.30102999566398119521373889472449 return e * 10 else: diff --git a/skimage/filter/setup.py b/skimage/filter/setup.py index 650a26a6..934d3f2e 100644 --- a/skimage/filter/setup.py +++ b/skimage/filter/setup.py @@ -29,31 +29,31 @@ def configuration(parent_package='', top_path=None): include_dirs=[get_numpy_include_dirs()]) config.add_extension('_denoise_cy', sources=['_denoise_cy.c'], include_dirs=[get_numpy_include_dirs(), '../_shared']) - config.add_extension('rank/_core8', sources=['rank/_core8.c'], + config.add_extension('rank._core8', sources=['rank/_core8.c'], include_dirs=[get_numpy_include_dirs()]) - config.add_extension('rank/_core16', sources=['rank/_core16.c'], + config.add_extension('rank._core16', sources=['rank/_core16.c'], include_dirs=[get_numpy_include_dirs()]) - config.add_extension('rank/_crank8', sources=['rank/_crank8.c'], + config.add_extension('rank._crank8', sources=['rank/_crank8.c'], include_dirs=[get_numpy_include_dirs()]) config.add_extension( - 'rank/_crank8_percentiles', sources=['rank/_crank8_percentiles.c'], + 'rank._crank8_percentiles', sources=['rank/_crank8_percentiles.c'], include_dirs=[get_numpy_include_dirs()]) - config.add_extension('rank/_crank16', sources=['rank/_crank16.c'], + config.add_extension('rank._crank16', sources=['rank/_crank16.c'], include_dirs=[get_numpy_include_dirs()]) config.add_extension( - 'rank/_crank16_percentiles', sources=['rank/_crank16_percentiles.c'], + 'rank._crank16_percentiles', sources=['rank/_crank16_percentiles.c'], include_dirs=[get_numpy_include_dirs()]) config.add_extension( - 'rank/_crank16_bilateral', sources=['rank/_crank16_bilateral.c'], + 'rank._crank16_bilateral', sources=['rank/_crank16_bilateral.c'], include_dirs=[get_numpy_include_dirs()]) config.add_extension( - 'rank/rank', sources=['rank/rank.c'], + 'rank.rank', sources=['rank/rank.c'], include_dirs=[get_numpy_include_dirs()]) config.add_extension( - 'rank/percentile_rank', sources=['rank/percentile_rank.c'], + 'rank.percentile_rank', sources=['rank/percentile_rank.c'], include_dirs=[get_numpy_include_dirs()]) config.add_extension( - 'rank/bilateral_rank', sources=['rank/bilateral_rank.c'], + 'rank.bilateral_rank', sources=['rank/bilateral_rank.c'], include_dirs=[get_numpy_include_dirs()]) return config From a997195881756d04839b4eba566572cd96d59fba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Boulogne?= Date: Sat, 9 Feb 2013 20:00:19 +0100 Subject: [PATCH 102/215] hough transform for circles --- skimage/transform/_hough_transform.pyx | 86 +++++++++++++++++++++++++- skimage/transform/hough_transform.py | 32 +++++++++- 2 files changed, 116 insertions(+), 2 deletions(-) diff --git a/skimage/transform/_hough_transform.pyx b/skimage/transform/_hough_transform.pyx index 662b3b88..6164f304 100644 --- a/skimage/transform/_hough_transform.pyx +++ b/skimage/transform/_hough_transform.pyx @@ -7,7 +7,7 @@ import numpy as np cimport numpy as np from libc.math cimport abs, fabs, sqrt, ceil from libc.stdlib cimport rand - +from skimage.draw import circle_perimeter cdef double PI_2 = 1.5707963267948966 cdef double NEG_PI_2 = -PI_2 @@ -17,6 +17,90 @@ cdef inline Py_ssize_t round(double r): return ((r + 0.5) if (r > 0.0) else (r - 0.5)) +@cython.boundscheck(False) +def _hough_circle(np.ndarray img, \ + np.ndarray[ndim=1, dtype=np.npy_intp] radius, \ + normalize=True): + + if img.ndim != 2: + raise ValueError('The input image must be 2D.') + + # compute the nonzero indexes + cdef np.ndarray[ndim=1, dtype=np.npy_intp] x, y + y, x = np.PyArray_Nonzero(img) + + # Offset the image + max_radius = radius.max() + x = x + max_radius + y = y + max_radius + + cdef list H = list() + + for rad in radius: + # Accumulator + out = np.zeros((img.shape[0] + 2 * max_radius, img.shape[1] + 2 * max_radius)) + + # Store in memory the circle of given radius + # centered at (0,0) + circle_x, circle_y = circle_perimeter(0, 0, rad) + + # For each non zero pixel + for (px, py) in zip(x, y): + # Plug the circle at (px, py), + # its coordinates are (tx, ty) + tx = circle_x + px + ty = circle_y + py + out[tx, ty] += 1 + + if normalize: + out = out / len(circle_x) + + H.append(out) + return np.array(H) + + +@cython.boundscheck(False) +def _hough_circle(np.ndarray img, \ + np.ndarray[ndim=1, dtype=np.npy_intp] radius, \ + normalize=True): + + if img.ndim != 2: + raise ValueError('The input image must be 2D.') + + # compute the nonzero indexes + cdef np.ndarray[ndim=1, dtype=np.npy_intp] x, y + y, x = np.PyArray_Nonzero(img) + + # Offset the image + max_radius = radius.max() + x = x + max_radius + y = y + max_radius + + cdef list H = list() + + for rad in radius: + # Accumulator + out = np.zeros((img.shape[0] + 2 * max_radius, img.shape[1] + 2 * max_radius)) + + # Store in memory the circle of given radius + # centered at (0,0) + circle_x, circle_y = circle_perimeter(0, 0, rad) + + # For each non zero pixel + for (px, py) in zip(x, y): + # Plug the circle at (px, py), + # its coordinates are (tx, ty) + tx = circle_x + px + ty = circle_y + py + out[tx, ty] += 1 + + if normalize: + out = out / len(circle_x) + + H.append(out) + return np.array(H) + + def _hough(np.ndarray img, np.ndarray[ndim=1, dtype=np.double_t] theta=None): if img.ndim != 2: diff --git a/skimage/transform/hough_transform.py b/skimage/transform/hough_transform.py index 05d5cfbe..88ef4b83 100644 --- a/skimage/transform/hough_transform.py +++ b/skimage/transform/hough_transform.py @@ -1,4 +1,4 @@ -__all__ = ['hough', 'hough_peaks', 'probabilistic_hough'] +__all__ = ['hough', 'hough_line', 'hough_circle', 'hough_peaks', 'probabilistic_hough'] from itertools import izip as zip @@ -96,8 +96,15 @@ def probabilistic_hough(img, threshold=10, line_length=50, line_gap=10, """ return _probabilistic_hough(img, threshold, line_length, line_gap, theta) +from skimage._shared.utils import deprecated +@deprecated('hough_line') def hough(img, theta=None): + return hough_line(img, theta) + +from ._hough_transform import _hough_circle + +def hough_line(img, theta=None): """Perform a straight line Hough transform. Parameters @@ -138,6 +145,29 @@ def hough(img, theta=None): """ return _hough(img, theta) +def hough_circle(img, radius, normalize=True): + """Perform a circle Hough transform. + + Parameters + ---------- + img : (M, N) ndarray + Input image with nonzero values representing edges. + radius : ndarray + Radii at which to compute the Hough transform. + normalize : boolean + Normalize the accumulator with the number + of pixels used to draw the radius + + Returns + ------- + H : 3D ndarray (radius index, (M, N) ndarray) + Hough transform accumulator for each radius + + Examples + -------- + + """ + return _hough_circle(img, radius, normalize) def hough_peaks(hspace, angles, dists, min_distance=10, min_angle=10, threshold=None, num_peaks=np.inf): From 12c1bf8883153fa2b75469210b450050f795d695 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Boulogne?= Date: Sat, 9 Feb 2013 20:00:41 +0100 Subject: [PATCH 103/215] add unittest for hough circle --- .../transform/tests/test_hough_transform.py | 32 +++++++++++++------ 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/skimage/transform/tests/test_hough_transform.py b/skimage/transform/tests/test_hough_transform.py index a38e6e76..f829b1bb 100644 --- a/skimage/transform/tests/test_hough_transform.py +++ b/skimage/transform/tests/test_hough_transform.py @@ -4,6 +4,7 @@ from numpy.testing import * import skimage.transform as tf import skimage.transform.hough_transform as ht from skimage.transform import probabilistic_hough +from skimage.draw import circle_perimeter def append_desc(func, description): @@ -14,8 +15,6 @@ def append_desc(func, description): return func -from skimage.transform import * - def test_hough(): # Generate a test image @@ -23,7 +22,7 @@ def test_hough(): for i in range(25, 75): img[100 - i, i] = 1 - out, angles, d = tf.hough(img) + out, angles, d = tf.hough_line(img) y, x = np.where(out == out.max()) dist = d[y[0]] @@ -37,7 +36,7 @@ def test_hough_angles(): img = np.zeros((10, 10)) img[0, 0] = 1 - out, angles, d = tf.hough(img, np.linspace(0, 360, 10)) + out, angles, d = tf.hough_line(img, np.linspace(0, 360, 10)) assert_equal(len(angles), 10) @@ -76,7 +75,7 @@ def test_hough_peaks_dist(): img = np.zeros((100, 100), dtype=np.bool_) img[:, 30] = True img[:, 40] = True - hspace, angles, dists = tf.hough(img) + hspace, angles, dists = tf.hough_line(img) assert len(tf.hough_peaks(hspace, angles, dists, min_distance=5)[0]) == 2 assert len(tf.hough_peaks(hspace, angles, dists, min_distance=15)[0]) == 1 @@ -86,17 +85,17 @@ def test_hough_peaks_angle(): img[:, 0] = True img[0, :] = True - hspace, angles, dists = tf.hough(img) + hspace, angles, dists = tf.hough_line(img) assert len(tf.hough_peaks(hspace, angles, dists, min_angle=45)[0]) == 2 assert len(tf.hough_peaks(hspace, angles, dists, min_angle=90)[0]) == 1 theta = np.linspace(0, np.pi, 100) - hspace, angles, dists = tf.hough(img, theta) + hspace, angles, dists = tf.hough_line(img, theta) assert len(tf.hough_peaks(hspace, angles, dists, min_angle=45)[0]) == 2 assert len(tf.hough_peaks(hspace, angles, dists, min_angle=90)[0]) == 1 theta = np.linspace(np.pi / 3, 4. / 3 * np.pi, 100) - hspace, angles, dists = tf.hough(img, theta) + hspace, angles, dists = tf.hough_line(img, theta) assert len(tf.hough_peaks(hspace, angles, dists, min_angle=45)[0]) == 2 assert len(tf.hough_peaks(hspace, angles, dists, min_angle=90)[0]) == 1 @@ -105,10 +104,25 @@ def test_hough_peaks_num(): img = np.zeros((100, 100), dtype=np.bool_) img[:, 30] = True img[:, 40] = True - hspace, angles, dists = tf.hough(img) + hspace, angles, dists = tf.hough_line(img) assert len(tf.hough_peaks(hspace, angles, dists, min_distance=0, min_angle=0, num_peaks=1)[0]) == 1 +def test_houghcircle(): + # Prepare picture + img = np.zeros((100, 100), dtype=int) + radius = 20 + x_0, y_0 = (50, 50) + x, y = circle_perimeter(y_0, x_0, radius) + img[y, x] = 1 + + out = tf.hough_circle(img, np.array([radius])) + + y, x = np.where(out[0] == out[0].max()) + # Offset for x_0, y_0 + assert_equal(x[0], x_0 + radius) + assert_equal(y[0], y_0 + radius) + if __name__ == "__main__": run_module_suite() From ffeb64572f9f3bc432aa4b540128abd8ae256bf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Boulogne?= Date: Sat, 9 Feb 2013 20:01:00 +0100 Subject: [PATCH 104/215] update contrib --- CONTRIBUTORS.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 38e2ea32..80cb92f0 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -132,6 +132,7 @@ - François Boulogne Andres Method for circle perimeter, ellipse perimeter drawing. + Hough transform for circles - Thouis Jones Vectorized operators for arrays of 16-bit ints. From 88eaaad9c797765fddac76eb5c5ae87ae4cda64f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Boulogne?= Date: Mon, 11 Feb 2013 17:31:23 +0100 Subject: [PATCH 105/215] add blank line --- skimage/transform/_hough_transform.pyx | 1 + 1 file changed, 1 insertion(+) diff --git a/skimage/transform/_hough_transform.pyx b/skimage/transform/_hough_transform.pyx index 6164f304..3c39d815 100644 --- a/skimage/transform/_hough_transform.pyx +++ b/skimage/transform/_hough_transform.pyx @@ -7,6 +7,7 @@ import numpy as np cimport numpy as np from libc.math cimport abs, fabs, sqrt, ceil from libc.stdlib cimport rand + from skimage.draw import circle_perimeter cdef double PI_2 = 1.5707963267948966 From 5161687730be0222ae9448697bd9a11dc6ef999b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Boulogne?= Date: Sat, 16 Feb 2013 08:11:30 +0100 Subject: [PATCH 106/215] various minor fixes --- skimage/transform/_hough_transform.pyx | 61 ++++++++------------------ skimage/transform/hough_transform.py | 7 +-- 2 files changed, 20 insertions(+), 48 deletions(-) diff --git a/skimage/transform/_hough_transform.pyx b/skimage/transform/_hough_transform.pyx index 3c39d815..c274ed0d 100644 --- a/skimage/transform/_hough_transform.pyx +++ b/skimage/transform/_hough_transform.pyx @@ -22,55 +22,30 @@ cdef inline Py_ssize_t round(double r): def _hough_circle(np.ndarray img, \ np.ndarray[ndim=1, dtype=np.npy_intp] radius, \ normalize=True): + """Perform a circular Hough transform. + Parameters + ---------- + img : (M, N) ndarray + Input image with nonzero values representing edges. + radius : ndarray + Radii at which to compute the Hough transform. + normalize : boolean, optional + Normalize the accumulator with the number + of pixels used to draw the radius + + Returns + ------- + H : 3D ndarray (radius index, (M, N) ndarray) + Hough transform accumulator for each radius + + """ if img.ndim != 2: raise ValueError('The input image must be 2D.') # compute the nonzero indexes cdef np.ndarray[ndim=1, dtype=np.npy_intp] x, y - y, x = np.PyArray_Nonzero(img) - - # Offset the image - max_radius = radius.max() - x = x + max_radius - y = y + max_radius - - cdef list H = list() - - for rad in radius: - # Accumulator - out = np.zeros((img.shape[0] + 2 * max_radius, img.shape[1] + 2 * max_radius)) - - # Store in memory the circle of given radius - # centered at (0,0) - circle_x, circle_y = circle_perimeter(0, 0, rad) - - # For each non zero pixel - for (px, py) in zip(x, y): - # Plug the circle at (px, py), - # its coordinates are (tx, ty) - tx = circle_x + px - ty = circle_y + py - out[tx, ty] += 1 - - if normalize: - out = out / len(circle_x) - - H.append(out) - return np.array(H) - - -@cython.boundscheck(False) -def _hough_circle(np.ndarray img, \ - np.ndarray[ndim=1, dtype=np.npy_intp] radius, \ - normalize=True): - - if img.ndim != 2: - raise ValueError('The input image must be 2D.') - - # compute the nonzero indexes - cdef np.ndarray[ndim=1, dtype=np.npy_intp] x, y - y, x = np.PyArray_Nonzero(img) + y, x = np.nonzero(img) # Offset the image max_radius = radius.max() diff --git a/skimage/transform/hough_transform.py b/skimage/transform/hough_transform.py index 88ef4b83..17b76826 100644 --- a/skimage/transform/hough_transform.py +++ b/skimage/transform/hough_transform.py @@ -146,7 +146,7 @@ def hough_line(img, theta=None): return _hough(img, theta) def hough_circle(img, radius, normalize=True): - """Perform a circle Hough transform. + """Perform a circular Hough transform. Parameters ---------- @@ -154,7 +154,7 @@ def hough_circle(img, radius, normalize=True): Input image with nonzero values representing edges. radius : ndarray Radii at which to compute the Hough transform. - normalize : boolean + normalize : boolean, optional Normalize the accumulator with the number of pixels used to draw the radius @@ -163,9 +163,6 @@ def hough_circle(img, radius, normalize=True): H : 3D ndarray (radius index, (M, N) ndarray) Hough transform accumulator for each radius - Examples - -------- - """ return _hough_circle(img, radius, normalize) From 99e29973379b9351e1b71b8314400123ea3947fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Boulogne?= Date: Sat, 16 Feb 2013 08:33:41 +0100 Subject: [PATCH 107/215] declare cython variables --- skimage/transform/_hough_transform.pyx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/skimage/transform/_hough_transform.pyx b/skimage/transform/_hough_transform.pyx index c274ed0d..98ba1bff 100644 --- a/skimage/transform/_hough_transform.pyx +++ b/skimage/transform/_hough_transform.pyx @@ -48,11 +48,13 @@ def _hough_circle(np.ndarray img, \ y, x = np.nonzero(img) # Offset the image - max_radius = radius.max() + cdef int max_radius = radius.max() x = x + max_radius y = y + max_radius cdef list H = list() + cdef int px, py + cdef np.ndarray[ndim=1, dtype=np.npy_intp] tx, ty for rad in radius: # Accumulator From de6e63de2e711af178787c4ef78f00996f30257f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Boulogne?= Date: Sat, 16 Feb 2013 09:30:26 +0100 Subject: [PATCH 108/215] use a single array --- skimage/transform/_hough_transform.pyx | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/skimage/transform/_hough_transform.pyx b/skimage/transform/_hough_transform.pyx index 98ba1bff..fb36c231 100644 --- a/skimage/transform/_hough_transform.pyx +++ b/skimage/transform/_hough_transform.pyx @@ -52,13 +52,13 @@ def _hough_circle(np.ndarray img, \ x = x + max_radius y = y + max_radius - cdef list H = list() cdef int px, py cdef np.ndarray[ndim=1, dtype=np.npy_intp] tx, ty + cdef np.ndarray acc = np.zeros((radius.size, + img.shape[0] + 2 * max_radius, + img.shape[1] + 2 * max_radius)) - for rad in radius: - # Accumulator - out = np.zeros((img.shape[0] + 2 * max_radius, img.shape[1] + 2 * max_radius)) + for i, rad in enumerate(radius): # Store in memory the circle of given radius # centered at (0,0) @@ -70,13 +70,12 @@ def _hough_circle(np.ndarray img, \ # its coordinates are (tx, ty) tx = circle_x + px ty = circle_y + py - out[tx, ty] += 1 + acc[i, tx, ty] += 1 if normalize: - out = out / len(circle_x) + acc[i] = acc[i] / len(circle_x) - H.append(out) - return np.array(H) + return acc def _hough(np.ndarray img, np.ndarray[ndim=1, dtype=np.double_t] theta=None): From 928468b999df891116f3258d0058ab38b798dba1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Boulogne?= Date: Mon, 18 Feb 2013 19:23:08 +0100 Subject: [PATCH 109/215] variable in cdef --- skimage/transform/_hough_transform.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skimage/transform/_hough_transform.pyx b/skimage/transform/_hough_transform.pyx index fb36c231..83be34ad 100644 --- a/skimage/transform/_hough_transform.pyx +++ b/skimage/transform/_hough_transform.pyx @@ -53,7 +53,7 @@ def _hough_circle(np.ndarray img, \ y = y + max_radius cdef int px, py - cdef np.ndarray[ndim=1, dtype=np.npy_intp] tx, ty + cdef np.ndarray[ndim=1, dtype=np.npy_intp] tx, ty, circle_x, circle_y cdef np.ndarray acc = np.zeros((radius.size, img.shape[0] + 2 * max_radius, img.shape[1] + 2 * max_radius)) From ecb762c9e41ddf17a22c531235711ed2a1e435ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Boulogne?= Date: Sat, 23 Feb 2013 11:08:23 +0100 Subject: [PATCH 110/215] bugfix dim + unittest --- skimage/transform/_hough_transform.pyx | 2 +- skimage/transform/tests/test_hough_transform.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/skimage/transform/_hough_transform.pyx b/skimage/transform/_hough_transform.pyx index 83be34ad..35426cad 100644 --- a/skimage/transform/_hough_transform.pyx +++ b/skimage/transform/_hough_transform.pyx @@ -45,7 +45,7 @@ def _hough_circle(np.ndarray img, \ # compute the nonzero indexes cdef np.ndarray[ndim=1, dtype=np.npy_intp] x, y - y, x = np.nonzero(img) + x, y = np.nonzero(img) # Offset the image cdef int max_radius = radius.max() diff --git a/skimage/transform/tests/test_hough_transform.py b/skimage/transform/tests/test_hough_transform.py index f829b1bb..00427332 100644 --- a/skimage/transform/tests/test_hough_transform.py +++ b/skimage/transform/tests/test_hough_transform.py @@ -111,15 +111,15 @@ def test_hough_peaks_num(): def test_houghcircle(): # Prepare picture - img = np.zeros((100, 100), dtype=int) + img = np.zeros((120, 100), dtype=int) radius = 20 - x_0, y_0 = (50, 50) + x_0, y_0 = (99, 50) x, y = circle_perimeter(y_0, x_0, radius) img[y, x] = 1 out = tf.hough_circle(img, np.array([radius])) - y, x = np.where(out[0] == out[0].max()) + x, y = np.where(out[0] == out[0].max()) # Offset for x_0, y_0 assert_equal(x[0], x_0 + radius) assert_equal(y[0], y_0 + radius) From 5572d16cb221f08fcbd97880447653af77f0aefd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Boulogne?= Date: Sat, 23 Feb 2013 13:49:21 +0100 Subject: [PATCH 111/215] add example for cicular hough transform --- doc/examples/plot_circular_hough_transform.py | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100755 doc/examples/plot_circular_hough_transform.py diff --git a/doc/examples/plot_circular_hough_transform.py b/doc/examples/plot_circular_hough_transform.py new file mode 100755 index 00000000..0caee3c4 --- /dev/null +++ b/doc/examples/plot_circular_hough_transform.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python2 +# -*- coding: utf-8 -*- +# Author: Francois Boulogne +# License: GPL + +""" +======================== +Circular Hough Transform +======================== + + + +""" + + +import numpy as np +import matplotlib.pyplot as plt +import matplotlib.patches as mpatches + +from skimage import data, filter +from skimage.transform import hough_circle +from skimage.feature import peak_local_max + +# Load picture and detect edges +image = data.coins()[0:95, 70:370] +edges = filter.canny(filter.sobel(image), sigma=2.8) + + +fig, ax = plt.subplots(ncols=1, nrows=1, figsize=(6, 6)) +ax.imshow(image, cmap=plt.cm.gray) + +# Detect two radii +radii = np.array([21, 25]) +hough_res = hough_circle(edges, radii) + +for radius, h in zip(radii, hough_res): + # For each radius, keep two circles + maxima = peak_local_max(h, num_peaks=2) + for maximum in maxima: + center_x, center_y = maximum - radii.max() + circ = mpatches.Circle((center_y, center_x), radius, + fill=False, edgecolor='red', linewidth=2) + ax.add_patch(circ) + +plt.show() From de2fa28b77591bbebe7369b43c8901a39cc878f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Boulogne?= Date: Sun, 24 Feb 2013 10:47:12 +0100 Subject: [PATCH 112/215] Docstring + J.S. modifications --- CONTRIBUTORS.txt | 2 +- doc/examples/plot_circular_hough_transform.py | 61 +++++++++++++------ 2 files changed, 45 insertions(+), 18 deletions(-) diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 80cb92f0..fd0656db 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -132,7 +132,7 @@ - François Boulogne Andres Method for circle perimeter, ellipse perimeter drawing. - Hough transform for circles + Circular Hough Transform - Thouis Jones Vectorized operators for arrays of 16-bit ints. diff --git a/doc/examples/plot_circular_hough_transform.py b/doc/examples/plot_circular_hough_transform.py index 0caee3c4..cc4ea664 100755 --- a/doc/examples/plot_circular_hough_transform.py +++ b/doc/examples/plot_circular_hough_transform.py @@ -1,14 +1,32 @@ -#!/usr/bin/env python2 -# -*- coding: utf-8 -*- -# Author: Francois Boulogne -# License: GPL - """ ======================== Circular Hough Transform ======================== +The Hough transform in its simplest form is a `method to detect +straight lines `__ +but it can also be used to detect circles. +In the following example, the Hough transform is used to detect +coin positions and match their edges. We provide a range of +plausible radii. For each radius, two circles are extracted and +we finally keep the five most prominent candidates. +The result shows that coin positions are well-detected. + + +Algorithm overview +------------------ + +Given a black circle on a white background, we first guess its +radius (or a range of radii) to construct a new circle. +This circle is applied on each black pixel of the original picture +and the coordinates of this circle are voting in an accumulator. +From this geometrical construction, the original circle center +position receives the highest score. + +Note that the accumulator size is built to be larger than the +original picture in order to detect centers outside the frame. +Its size is extended by two times the larger radius. """ @@ -23,23 +41,32 @@ from skimage.feature import peak_local_max # Load picture and detect edges image = data.coins()[0:95, 70:370] -edges = filter.canny(filter.sobel(image), sigma=2.8) - +edges = filter.canny(image, sigma=3, low_threshold=10, high_threshold=50) fig, ax = plt.subplots(ncols=1, nrows=1, figsize=(6, 6)) ax.imshow(image, cmap=plt.cm.gray) # Detect two radii -radii = np.array([21, 25]) -hough_res = hough_circle(edges, radii) +hough_radii = np.arange(15, 30, 2) +hough_res = hough_circle(edges, hough_radii) -for radius, h in zip(radii, hough_res): - # For each radius, keep two circles - maxima = peak_local_max(h, num_peaks=2) - for maximum in maxima: - center_x, center_y = maximum - radii.max() - circ = mpatches.Circle((center_y, center_x), radius, - fill=False, edgecolor='red', linewidth=2) - ax.add_patch(circ) +centers = [] +accums = [] +radii = [] + +for radius, h in zip(hough_radii, hough_res): + # For each radius, extract two circles + peaks = peak_local_max(h, num_peaks=2) + centers.extend(peaks - hough_radii.max()) + accums.extend(h[peaks[:, 0], peaks[:, 1]]) + radii.extend([radius, radius]) + +# Draw the most prominent 5 circles +for idx in np.argsort(accums)[::-1][:5]: + center_x, center_y = centers[idx] + radius = radii[idx] + circ = mpatches.Circle((center_y, center_x), radius, + fill=False, edgecolor='red', linewidth=2) + ax.add_patch(circ) plt.show() From 86f777ae3de257c1eca82a2edc9a079a63fd9455 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Boulogne?= Date: Sat, 9 Feb 2013 20:00:19 +0100 Subject: [PATCH 113/215] hough transform for circles --- skimage/transform/_hough_transform.pyx | 1 - 1 file changed, 1 deletion(-) diff --git a/skimage/transform/_hough_transform.pyx b/skimage/transform/_hough_transform.pyx index 35426cad..e1c06d77 100644 --- a/skimage/transform/_hough_transform.pyx +++ b/skimage/transform/_hough_transform.pyx @@ -59,7 +59,6 @@ def _hough_circle(np.ndarray img, \ img.shape[1] + 2 * max_radius)) for i, rad in enumerate(radius): - # Store in memory the circle of given radius # centered at (0,0) circle_x, circle_y = circle_perimeter(0, 0, rad) From 0451a2e17b775183e8a547c2a1125d20d45c0331 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Boulogne?= Date: Sun, 24 Feb 2013 16:27:40 +0100 Subject: [PATCH 114/215] use draw instead of mpl patches --- doc/examples/plot_circular_hough_transform.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/examples/plot_circular_hough_transform.py b/doc/examples/plot_circular_hough_transform.py index cc4ea664..f7490051 100755 --- a/doc/examples/plot_circular_hough_transform.py +++ b/doc/examples/plot_circular_hough_transform.py @@ -38,13 +38,13 @@ import matplotlib.patches as mpatches from skimage import data, filter from skimage.transform import hough_circle from skimage.feature import peak_local_max +from skimage.draw import circle_perimeter # Load picture and detect edges image = data.coins()[0:95, 70:370] edges = filter.canny(image, sigma=3, low_threshold=10, high_threshold=50) fig, ax = plt.subplots(ncols=1, nrows=1, figsize=(6, 6)) -ax.imshow(image, cmap=plt.cm.gray) # Detect two radii hough_radii = np.arange(15, 30, 2) @@ -65,8 +65,8 @@ for radius, h in zip(hough_radii, hough_res): for idx in np.argsort(accums)[::-1][:5]: center_x, center_y = centers[idx] radius = radii[idx] - circ = mpatches.Circle((center_y, center_x), radius, - fill=False, edgecolor='red', linewidth=2) - ax.add_patch(circ) + cx, cy = circle_perimeter(center_y, center_x, radius) + image[cy, cx] = 0 +ax.imshow(image, cmap=plt.cm.gray) plt.show() From 609ce322d9ee9f1eda9f22dbdb457fca9d7d0d9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Boulogne?= Date: Sun, 24 Feb 2013 16:45:28 +0100 Subject: [PATCH 115/215] remove import mpl patches --- doc/examples/plot_circular_hough_transform.py | 1 - 1 file changed, 1 deletion(-) diff --git a/doc/examples/plot_circular_hough_transform.py b/doc/examples/plot_circular_hough_transform.py index f7490051..2574a1ac 100755 --- a/doc/examples/plot_circular_hough_transform.py +++ b/doc/examples/plot_circular_hough_transform.py @@ -33,7 +33,6 @@ Its size is extended by two times the larger radius. import numpy as np import matplotlib.pyplot as plt -import matplotlib.patches as mpatches from skimage import data, filter from skimage.transform import hough_circle From bb2249998637c8c56cb8b7cd119c1d8d132e522e Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Mon, 25 Feb 2013 20:36:32 -0600 Subject: [PATCH 116/215] Add save buttons to viewer example. --- viewer_examples/plugins/canny_simple.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/viewer_examples/plugins/canny_simple.py b/viewer_examples/plugins/canny_simple.py index 49bc15eb..912401e1 100644 --- a/viewer_examples/plugins/canny_simple.py +++ b/viewer_examples/plugins/canny_simple.py @@ -3,18 +3,22 @@ from skimage.filter import canny from skimage.viewer import ImageViewer from skimage.viewer.widgets import Slider +from skimage.viewer.widgets.history import SaveButtons from skimage.viewer.plugins.overlayplugin import OverlayPlugin image = data.camera() -# Note: ImageViewer must be called before Plugin b/c it starts the event loop. -viewer = ImageViewer(image) + # You can create a UI for a filter just by passing a filter function... plugin = OverlayPlugin(image_filter=canny) # ... and adding widgets to adjust parameter values. plugin += Slider('sigma', 0, 5, update_on='release') plugin += Slider('low threshold', 0, 255, update_on='release') plugin += Slider('high threshold', 0, 255, update_on='release') -# Finally, attach the plugin to the image viewer. +# ... and we can also add buttons to save the overlay: +plugin += SaveButtons(name='Save overlay to:') + +# Finally, attach the plugin to an image viewer. +viewer = ImageViewer(image) viewer += plugin viewer.show() From c2023e9975946fdefe88c624e745ee6c6eb8b6cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Tue, 26 Feb 2013 12:12:02 +0100 Subject: [PATCH 117/215] Remove duplicate return statement --- skimage/transform/_warps.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/skimage/transform/_warps.py b/skimage/transform/_warps.py index 0ace0937..5af3afa6 100644 --- a/skimage/transform/_warps.py +++ b/skimage/transform/_warps.py @@ -348,6 +348,3 @@ def homography(image, H, output_shape=None, order=1, tform = ProjectiveTransform(H) return warp(image, inverse_map=tform.inverse, output_shape=output_shape, order=order, mode=mode, cval=cval) - - return warp(image, inverse_map=tform.inverse, output_shape=output_shape, - order=order, mode=mode, cval=cval) From 72e1d20a2948b166b0cea311290cdbec1610e414 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Tue, 26 Feb 2013 12:22:13 +0100 Subject: [PATCH 118/215] Remove deprecated homography function --- skimage/transform/_warps.py | 95 ------------------------------------- 1 file changed, 95 deletions(-) diff --git a/skimage/transform/_warps.py b/skimage/transform/_warps.py index 5af3afa6..7ac3e63f 100644 --- a/skimage/transform/_warps.py +++ b/skimage/transform/_warps.py @@ -253,98 +253,3 @@ def swirl(image, center=None, strength=1, radius=100, rotation=0, return warp(image, _swirl_mapping, map_args=warp_args, output_shape=output_shape, order=order, mode=mode, cval=cval) - - -def homography(image, H, output_shape=None, order=1, - mode='constant', cval=0.): - """ - .. note:: Deprecated in skimage 0.7 - `homography` will be removed in skimage 0.8, it is replaced by - `warp` because the latter provides the same functionality:: - - warp(image, ProjectiveTransform(H)) - - Perform a projective transformation (homography) on an image. - - For each pixel, given its homogeneous coordinate :math:`\mathbf{x} - = [x, y, 1]^T`, its target position is calculated by multiplying - with the given matrix, :math:`H`, to give :math:`H \mathbf{x}`. - E.g., to rotate by theta degrees clockwise, the matrix should be - - :: - - [[cos(theta) -sin(theta) 0] - [sin(theta) cos(theta) 0] - [0 0 1]] - - or, to translate x by 10 and y by 20, - - :: - - [[1 0 10] - [0 1 20] - [0 0 1 ]]. - - Parameters - ---------- - image : 2-D array - Input image. - H : array of shape ``(3, 3)`` - Transformation matrix H that defines the homography. - output_shape : tuple (rows, cols) - Shape of the output image generated. - order : int - Order of splines used in interpolation. - mode : string - How to handle values outside the image borders. Passed as-is - to ndimage. - cval : string - Used in conjunction with mode 'constant', the value outside - the image boundaries. - - Examples - -------- - >>> # rotate by 90 degrees around origin and shift down by 2 - >>> x = np.arange(9, dtype=np.uint8).reshape((3, 3)) + 1 - >>> x - array([[1, 2, 3], - [4, 5, 6], - [7, 8, 9]], dtype=uint8) - >>> theta = -np.pi/2 - >>> M = np.array([[np.cos(theta),-np.sin(theta),0], - ... [np.sin(theta), np.cos(theta),2], - ... [0, 0, 1]]) - >>> x90 = homography(x, M, order=1) - >>> x90 - array([[3, 6, 9], - [2, 5, 8], - [1, 4, 7]], dtype=uint8) - >>> # translate right by 2 and down by 1 - >>> y = np.zeros((5,5), dtype=np.uint8) - >>> y[1, 1] = 255 - >>> y - array([[ 0, 0, 0, 0, 0], - [ 0, 255, 0, 0, 0], - [ 0, 0, 0, 0, 0], - [ 0, 0, 0, 0, 0], - [ 0, 0, 0, 0, 0]], dtype=uint8) - >>> M = np.array([[ 1., 0., 2.], - ... [ 0., 1., 1.], - ... [ 0., 0., 1.]]) - >>> y21 = homography(y, M, order=1) - >>> y21 - array([[ 0, 0, 0, 0, 0], - [ 0, 0, 0, 0, 0], - [ 0, 0, 0, 255, 0], - [ 0, 0, 0, 0, 0], - [ 0, 0, 0, 0, 0]], dtype=uint8) - - """ - import warnings - warnings.warn('the homography function is deprecated; ' - 'use the `warp` and `ProjectiveTransform` class instead', - category=DeprecationWarning) - - tform = ProjectiveTransform(H) - return warp(image, inverse_map=tform.inverse, output_shape=output_shape, - order=order, mode=mode, cval=cval) From ae685075e127d3537298f40cfc1d30fe6131616a Mon Sep 17 00:00:00 2001 From: spotter Date: Tue, 26 Feb 2013 16:25:42 +0100 Subject: [PATCH 119/215] ENH: Adding color deconvolution method to convert to an Haematoxylin-Eosin-DAB colorspace --- skimage/color/colorconv.py | 51 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/skimage/color/colorconv.py b/skimage/color/colorconv.py index 4186682d..b6245006 100644 --- a/skimage/color/colorconv.py +++ b/skimage/color/colorconv.py @@ -312,6 +312,17 @@ gray_from_rgb = np.array([[0.2125, 0.7154, 0.0721], # CIE LAB constants for Observer= 2A, Illuminant= D65 lab_ref_white = np.array([0.95047, 1., 1.08883]) + +# Haematoxylin-Eosin-DAB colorspace +# From original Ruifrok's paper: A. C. Ruifrok and D. A. Johnston, +# “Quantification of histochemical staining by color deconvolution.,” +# Analytical and quantitative cytology and histology / the International +# Academy of Cytology [and] American Society of Cytology, vol. 23, no. 4, +# pp. 291–9, Aug. 2001. +hed_from_rgb = np.asarray([[0.65, 0.70, 0.29], + [0.07, 0.99, 0.11], + [0.27, 0.57, 0.78]]) + #------------------------------------------------------------- # The conversion functions that make use of the matrices above #------------------------------------------------------------- @@ -721,3 +732,43 @@ def lab2rgb(lab): This function uses lab2xyz and xyz2rgb. """ return xyz2rgb(lab2xyz(lab)) + + +def rgb2hed(rgb): + """RGB to Haematoxylin-Eosin-DAB (HED) color space conversion. + + Parameters + ---------- + rgb : array_like + The image in RGB format, in a 3-D array of shape (.., .., 3). + + Returns + ------- + out : ndarray + The image in HED format, in a 3-D array of shape (.., .., 3). + + Raises + ------ + ValueError + If `rgb` is not a 3-D array of shape (.., .., 3). + + References + ---------- + .. [1] A. C. Ruifrok and D. A. Johnston, “Quantification of histochemical + staining by color deconvolution.,” Analytical and quantitative + cytology and histology / the International Academy of Cytology [and] + American Society of Cytology, vol. 23, no. 4, pp. 291–9, Aug. 2001. + + Examples + -------- + >>> from skimage import data + >>> from skimage.color import rgb2xyz, xyz2lab + >>> ihc = data.ihc() + >>> ihc_hed = rgb2hed(ihc) + """ + arr = _prepare_colorarray(rgb) + + # convert to optical densities + arr = -np.log(arr) + + return _convert(hed_from_rgb, arr) From f2d91d2c296e3662a1b656f0fdf5191665ff363b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Tue, 26 Feb 2013 16:57:43 +0100 Subject: [PATCH 120/215] Remove deprecated import of hompgraphy --- skimage/transform/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skimage/transform/__init__.py b/skimage/transform/__init__.py index befcc23f..089487ee 100644 --- a/skimage/transform/__init__.py +++ b/skimage/transform/__init__.py @@ -6,6 +6,6 @@ from ._geometric import (warp, warp_coords, estimate_transform, SimilarityTransform, AffineTransform, ProjectiveTransform, PolynomialTransform, PiecewiseAffineTransform) -from ._warps import swirl, homography, resize, rotate, rescale +from ._warps import swirl, resize, rotate, rescale from .pyramids import (pyramid_reduce, pyramid_expand, pyramid_gaussian, pyramid_laplacian) From 306c886ad6687e7c987cfe1f5e0c5ac055b7134d Mon Sep 17 00:00:00 2001 From: spotter Date: Tue, 26 Feb 2013 17:26:12 +0100 Subject: [PATCH 121/215] ENH: Added an Immunohistochemical image example --- skimage/data/__init__.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/skimage/data/__init__.py b/skimage/data/__init__.py index d2fba7db..fbd54bd6 100644 --- a/skimage/data/__init__.py +++ b/skimage/data/__init__.py @@ -127,3 +127,21 @@ def clock(): """ return load("clock_motion.png") + + +def ihc(): + """Immunohistochemical staining with Haematoxylin-Eosin counterstaining. + + This picture is an example of immunohistochemical staining with + Haematoxylin-Eosin counterstaining. + + Notes + ----- + This image was downloaded from the + `Laboratory of Image Synthesis and Analysis (LISA) of the ULB + `__. + + No known copyright restrictions. + + """ + return load("http://lisa.ulb.ac.be/images/Rp042826d.jpg") From dd08c8cd83293cc31a3f1722577d8f4aefb0c86e Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Tue, 26 Feb 2013 20:34:29 -0600 Subject: [PATCH 122/215] Fix return value of deprecated `equalize` function. --- doc/examples/plot_equalize.py | 1 - doc/examples/plot_local_equalize.py | 2 +- skimage/exposure/exposure.py | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/doc/examples/plot_equalize.py b/doc/examples/plot_equalize.py index f57cea9c..3569c4ee 100644 --- a/doc/examples/plot_equalize.py +++ b/doc/examples/plot_equalize.py @@ -19,7 +19,6 @@ that fall within the 2nd and 98th percentiles [2]_. """ from skimage import data, img_as_float -from skimage.util.dtype import dtype_range from skimage import exposure import matplotlib.pyplot as plt diff --git a/doc/examples/plot_local_equalize.py b/doc/examples/plot_local_equalize.py index bc458505..33b2c2e4 100644 --- a/doc/examples/plot_local_equalize.py +++ b/doc/examples/plot_local_equalize.py @@ -57,7 +57,7 @@ img = data.moon() # Contrast stretching p2 = np.percentile(img, 2) p98 = np.percentile(img, 98) -img_rescale = exposure.equalize(img) +img_rescale = exposure.equalize_hist(img) # Equalization selem = disk(30) diff --git a/skimage/exposure/exposure.py b/skimage/exposure/exposure.py index 465b9182..c7f1e712 100644 --- a/skimage/exposure/exposure.py +++ b/skimage/exposure/exposure.py @@ -81,7 +81,7 @@ def cumulative_distribution(image, nbins=256): @deprecated('equalize_hist') def equalize(image, nbins=256): - equalize_hist(image, nbins) + return equalize_hist(image, nbins) def equalize_hist(image, nbins=256): From 374a967ffc76a34971043630ad6b0221629e838c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Wed, 27 Feb 2013 08:45:45 +0100 Subject: [PATCH 123/215] Remove deprecated tests for homography --- skimage/transform/tests/test_warps.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/skimage/transform/tests/test_warps.py b/skimage/transform/tests/test_warps.py index 2cdda001..93f87320 100644 --- a/skimage/transform/tests/test_warps.py +++ b/skimage/transform/tests/test_warps.py @@ -5,7 +5,7 @@ from scipy.ndimage import map_coordinates from skimage.transform import (warp, warp_coords, rotate, resize, rescale, AffineTransform, ProjectiveTransform, - SimilarityTransform, homography) + SimilarityTransform) from skimage import transform as tf, data, img_as_float from skimage.color import rgb2gray @@ -39,10 +39,6 @@ def test_homography(): assert_array_almost_equal(x90, np.rot90(x)) -def test_homography_basic(): - homography(np.random.random((25, 25)), np.eye(3)) - - def test_fast_homography(): img = rgb2gray(data.lena()).astype(np.uint8) img = img[:, :100] @@ -87,10 +83,10 @@ def test_rotate(): def test_rotate_resize(): x = np.zeros((10, 10), dtype=np.double) - + x45 = rotate(x, 45, resize=False) assert x45.shape == (10, 10) - + x45 = rotate(x, 45, resize=True) # new dimension should be d = sqrt(2 * (10/2)^2) assert x45.shape == (14, 14) From 997870ce239f38f4d2f241bc8b8b381b8145abd9 Mon Sep 17 00:00:00 2001 From: spotter Date: Tue, 26 Feb 2013 17:58:41 +0100 Subject: [PATCH 124/215] FIX: the rgb2hed function. Legal issues regarding the ihc image. ENH: added the hed2rgb function. --- skimage/color/colorconv.py | 59 +++++++++++++++++++++++++++++++------ skimage/data/__init__.py | 9 ++---- skimage/data/ihc.jpg | Bin 0 -> 274001 bytes 3 files changed, 53 insertions(+), 15 deletions(-) create mode 100644 skimage/data/ihc.jpg diff --git a/skimage/color/colorconv.py b/skimage/color/colorconv.py index b6245006..28ebdc88 100644 --- a/skimage/color/colorconv.py +++ b/skimage/color/colorconv.py @@ -53,7 +53,7 @@ __docformat__ = "restructuredtext en" import numpy as np from scipy import linalg from ..util import dtype - +from deconvolution import deconvolveHDAB as deconvolve def is_rgb(image): """Test whether the image is RGB or RGBA. @@ -319,9 +319,10 @@ lab_ref_white = np.array([0.95047, 1., 1.08883]) # Analytical and quantitative cytology and histology / the International # Academy of Cytology [and] American Society of Cytology, vol. 23, no. 4, # pp. 291–9, Aug. 2001. -hed_from_rgb = np.asarray([[0.65, 0.70, 0.29], - [0.07, 0.99, 0.11], - [0.27, 0.57, 0.78]]) +rgb_from_hed = np.array([[0.65, 0.70, 0.29], + [0.07, 0.99, 0.11], + [0.27, 0.57, 0.78]]) +hed_from_rgb = linalg.inv(rgb_from_hed) #------------------------------------------------------------- # The conversion functions that make use of the matrices above @@ -752,6 +753,7 @@ def rgb2hed(rgb): ValueError If `rgb` is not a 3-D array of shape (.., .., 3). + References ---------- .. [1] A. C. Ruifrok and D. A. Johnston, “Quantification of histochemical @@ -762,13 +764,52 @@ def rgb2hed(rgb): Examples -------- >>> from skimage import data - >>> from skimage.color import rgb2xyz, xyz2lab + >>> from skimage.color import rgb2hed >>> ihc = data.ihc() >>> ihc_hed = rgb2hed(ihc) """ - arr = _prepare_colorarray(rgb) + rgb = dtype.img_as_ubyte(rgb) + arr = [1, 1, 1] - (np.log(rgb + [1, 1, 1]) / np.log(255)) + row = np.reshape(arr, (-1,3)) + scaled = np.dot(row, hed_from_rgb) + scaled[scaled < 0] = 0 + scaled[scaled > 1] = 1 + return np.reshape(scaled, rgb.shape) - # convert to optical densities - arr = -np.log(arr) - return _convert(hed_from_rgb, arr) +def hed2rgb(hed): + """Haematoxylin-Eosin-DAB (HED) to RGB color space conversion. + + Parameters + ---------- + hed : array_like + The image in HED format, in a 3-D array of shape (.., .., 3). + + Returns + ------- + out : ndarray + The image in HED format, in a 3-D array of shape (.., .., 3). + + Raises + ------ + ValueError + If `hed` is not a 3-D array of shape (.., .., 3). + + References + ---------- + .. [1] A. C. Ruifrok and D. A. Johnston, “Quantification of histochemical + staining by color deconvolution.,” Analytical and quantitative + cytology and histology / the International Academy of Cytology [and] + American Society of Cytology, vol. 23, no. 4, pp. 291–9, Aug. 2001. + + Examples + -------- + >>> from skimage import data + >>> from skimage.color import rgb2hed, hed2rgb + >>> ihc = data.ihc() + >>> ihc_hed = rgb2hed(ihc) + >>> ihc_rgb = hed2rgb(ihc_hed) + """ + hed = dtype.img_as_float(hed) + rgb = np.exp(-np.dot(np.reshape(hed * np.log(255), (-1,3)) , rgb_from_hed)) + return np.reshape(rgb, hed.shape) diff --git a/skimage/data/__init__.py b/skimage/data/__init__.py index fbd54bd6..320b78bb 100644 --- a/skimage/data/__init__.py +++ b/skimage/data/__init__.py @@ -135,13 +135,10 @@ def ihc(): This picture is an example of immunohistochemical staining with Haematoxylin-Eosin counterstaining. - Notes - ----- - This image was downloaded from the - `Laboratory of Image Synthesis and Analysis (LISA) of the ULB - `__. + This image was acquired at the Center for Microscopy And Molecular Imaging + (CMMI). No known copyright restrictions. """ - return load("http://lisa.ulb.ac.be/images/Rp042826d.jpg") + return load("ihc.jpg") diff --git a/skimage/data/ihc.jpg b/skimage/data/ihc.jpg new file mode 100644 index 0000000000000000000000000000000000000000..24d68bd87f644c63a2eda91dd97d36aa22302cf8 GIT binary patch literal 274001 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 ry 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�HK;Gv*^(zSc!bTQA+#g!nIBUb5 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 xNl->|DO>$apIQ(4<^KS7pkt#e Date: Thu, 28 Feb 2013 16:26:57 +0100 Subject: [PATCH 125/215] ENH: Added rgb2hed and hed2rgb to __all__ list --- skimage/color/colorconv.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/skimage/color/colorconv.py b/skimage/color/colorconv.py index 28ebdc88..858fe619 100644 --- a/skimage/color/colorconv.py +++ b/skimage/color/colorconv.py @@ -45,7 +45,8 @@ from __future__ import division __all__ = ['convert_colorspace', 'rgb2hsv', 'hsv2rgb', 'rgb2xyz', 'xyz2rgb', 'rgb2rgbcie', 'rgbcie2rgb', 'rgb2grey', 'rgb2gray', 'gray2rgb', - 'xyz2lab', 'lab2xyz', 'lab2rgb', 'rgb2lab', 'is_rgb', 'is_gray' + 'xyz2lab', 'lab2xyz', 'lab2rgb', 'rgb2lab', 'rgb2hed', 'hed2rgb', + 'is_rgb', 'is_gray' ] __docformat__ = "restructuredtext en" From b065580c0aa6ce7cf47672ae02baa1a99cf8f53d Mon Sep 17 00:00:00 2001 From: spotter Date: Thu, 28 Feb 2013 17:31:52 +0100 Subject: [PATCH 126/215] FIX: Simplified deconvolution equations --- skimage/color/colorconv.py | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/skimage/color/colorconv.py b/skimage/color/colorconv.py index 858fe619..4e46a2a5 100644 --- a/skimage/color/colorconv.py +++ b/skimage/color/colorconv.py @@ -320,9 +320,9 @@ lab_ref_white = np.array([0.95047, 1., 1.08883]) # Analytical and quantitative cytology and histology / the International # Academy of Cytology [and] American Society of Cytology, vol. 23, no. 4, # pp. 291–9, Aug. 2001. -rgb_from_hed = np.array([[0.65, 0.70, 0.29], - [0.07, 0.99, 0.11], - [0.27, 0.57, 0.78]]) +rgb_from_hed = np.array([[0.65, 0.70, 0.29], + [0.07, 0.99, 0.11], + [0.27, 0.57, 0.78]]) hed_from_rgb = linalg.inv(rgb_from_hed) #------------------------------------------------------------- @@ -769,13 +769,9 @@ def rgb2hed(rgb): >>> ihc = data.ihc() >>> ihc_hed = rgb2hed(ihc) """ - rgb = dtype.img_as_ubyte(rgb) - arr = [1, 1, 1] - (np.log(rgb + [1, 1, 1]) / np.log(255)) - row = np.reshape(arr, (-1,3)) - scaled = np.dot(row, hed_from_rgb) - scaled[scaled < 0] = 0 - scaled[scaled > 1] = 1 - return np.reshape(scaled, rgb.shape) + rgb = rgb.astype(float) + 1 + hed = np.dot(np.reshape(-np.log(rgb), (-1,3)), hed_from_rgb) + return np.reshape(hed, rgb.shape) def hed2rgb(hed): @@ -811,6 +807,6 @@ def hed2rgb(hed): >>> ihc_hed = rgb2hed(ihc) >>> ihc_rgb = hed2rgb(ihc_hed) """ - hed = dtype.img_as_float(hed) - rgb = np.exp(-np.dot(np.reshape(hed * np.log(255), (-1,3)) , rgb_from_hed)) - return np.reshape(rgb, hed.shape) + logrgb1 = np.dot(-np.reshape(hed, (-1,3)) , rgb_from_hed) + rgb1 = np.exp(logrgb1) + return np.reshape(rgb1 - 1, hed.shape) From 3e6e6905903319cea7ce2e6d37c951b48dabaa27 Mon Sep 17 00:00:00 2001 From: spotter Date: Fri, 1 Mar 2013 11:50:10 +0100 Subject: [PATCH 127/215] ENH: Added a short test to validate rgb2hed and hed2rgb conversion --- skimage/color/tests/test_colorconv.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/skimage/color/tests/test_colorconv.py b/skimage/color/tests/test_colorconv.py index 962fa9fc..e49443fc 100644 --- a/skimage/color/tests/test_colorconv.py +++ b/skimage/color/tests/test_colorconv.py @@ -121,6 +121,19 @@ class TestColorconv(TestCase): img_rgb = img_as_float(self.img_rgb) assert_array_almost_equal(xyz2rgb(rgb2xyz(img_rgb)), img_rgb) + # RGB<->HED roundtrip + def test_hed_rgb_roundtrip(self): + test = np.array([[[97, 105, 152], + [217, 187, 225], + [117, 107, 168]], + [[217, 187, 225], + [176, 135, 83], + [234, 144, 205]], + [[97, 105, 152], + [117, 107, 168], + [217, 187, 225]]], dtype=np.uint8) + assert_array_almost_equal(hed2rgb(rgb2hed(img_rgb)), img_rgb) + # RGB to RGB CIE def test_rgb2rgbcie_conversion(self): gt = np.array([[[ 0.1488856 , 0.18288098, 0.19277574], From 85b1795cdb141096999436e8912200e3a34b4c04 Mon Sep 17 00:00:00 2001 From: spotter Date: Fri, 1 Mar 2013 12:17:15 +0100 Subject: [PATCH 128/215] FIX: Removed unused dependency --- skimage/color/colorconv.py | 1 - 1 file changed, 1 deletion(-) diff --git a/skimage/color/colorconv.py b/skimage/color/colorconv.py index 4e46a2a5..96ef5f86 100644 --- a/skimage/color/colorconv.py +++ b/skimage/color/colorconv.py @@ -54,7 +54,6 @@ __docformat__ = "restructuredtext en" import numpy as np from scipy import linalg from ..util import dtype -from deconvolution import deconvolveHDAB as deconvolve def is_rgb(image): """Test whether the image is RGB or RGBA. From 2ff289fcec7454b2c7af16c9b79b1bf7b38274e0 Mon Sep 17 00:00:00 2001 From: Stefan van der Walt Date: Fri, 1 Mar 2013 17:19:36 +0200 Subject: [PATCH 129/215] DOC: Use brighter color in circular Hough demo. --- doc/examples/plot_circular_hough_transform.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/doc/examples/plot_circular_hough_transform.py b/doc/examples/plot_circular_hough_transform.py index 2574a1ac..fc891d04 100755 --- a/doc/examples/plot_circular_hough_transform.py +++ b/doc/examples/plot_circular_hough_transform.py @@ -34,7 +34,7 @@ Its size is extended by two times the larger radius. import numpy as np import matplotlib.pyplot as plt -from skimage import data, filter +from skimage import data, filter, color from skimage.transform import hough_circle from skimage.feature import peak_local_max from skimage.draw import circle_perimeter @@ -61,11 +61,12 @@ for radius, h in zip(hough_radii, hough_res): radii.extend([radius, radius]) # Draw the most prominent 5 circles +image = color.gray2rgb(image) for idx in np.argsort(accums)[::-1][:5]: center_x, center_y = centers[idx] radius = radii[idx] cx, cy = circle_perimeter(center_y, center_x, radius) - image[cy, cx] = 0 + image[cy, cx] = (220, 250, 150) ax.imshow(image, cmap=plt.cm.gray) plt.show() From d6d177fec5da516d00f53fd97ab6d30f1f2acd80 Mon Sep 17 00:00:00 2001 From: Jan Margeta Date: Sat, 2 Mar 2013 15:39:20 +0100 Subject: [PATCH 130/215] Fixing MSVC build by adding appropriate typedefs for uint16_t --- skimage/_shared/vectorized_ops.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/skimage/_shared/vectorized_ops.h b/skimage/_shared/vectorized_ops.h index e426c86e..29026490 100644 --- a/skimage/_shared/vectorized_ops.h +++ b/skimage/_shared/vectorized_ops.h @@ -12,6 +12,11 @@ #include #elif defined(_MSC_VER) #define inline __inline +#if (_MSC_VER < 1300) +typedef unsigned short uint16_t; +#else +typedef unsigned __int16 uint16_t; +#endif #endif /** From db7a063d763135e08069b30fb5c4a292d39de987 Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Sat, 2 Mar 2013 17:21:14 -0600 Subject: [PATCH 131/215] ENH: If example fails, print traceback and carry on. --- doc/ext/plot2rst.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/doc/ext/plot2rst.py b/doc/ext/plot2rst.py index 65c77521..00560be4 100644 --- a/doc/ext/plot2rst.py +++ b/doc/ext/plot2rst.py @@ -69,6 +69,7 @@ import os import shutil import token import tokenize +import traceback import numpy as np import matplotlib @@ -247,7 +248,16 @@ def write_gallery(gallery_index, src_dir, rst_dir, cfg, depth=0): gallery_index.write(TOCTREE_TEMPLATE % (sub_dir + '\n '.join(ex_names))) for src_name in examples: - write_example(src_name, src_dir, rst_dir, cfg) + + try: + write_example(src_name, src_dir, rst_dir, cfg) + except Exception: + print "Exception raised while running:" + print "%s in %s" % (src_name, src_dir) + print '~' * 60 + traceback.print_exc() + print '~' * 60 + continue link_name = sub_dir.pjoin(src_name) link_name = link_name.replace(os.path.sep, '_') From 17e7ba3de7177b5afacd6b08f15f23800bcd4d36 Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Sat, 2 Mar 2013 17:22:10 -0600 Subject: [PATCH 132/215] DOC: Fix Sphinx errors. --- doc/examples/plot_corner.py | 1 - .../plot_random_walker_segmentation.py | 1 - skimage/exposure/_adapthist.py | 13 +++++----- skimage/feature/_daisy.py | 21 +++++++++------ skimage/feature/corner.py | 6 ++--- skimage/feature/corner_cy.pyx | 2 +- skimage/feature/peak.py | 7 ++--- skimage/filter/_denoise.py | 6 ++--- skimage/filter/rank/rank.pyx | 4 +-- .../random_walker_segmentation.py | 26 +++++++++++-------- 10 files changed, 48 insertions(+), 39 deletions(-) diff --git a/doc/examples/plot_corner.py b/doc/examples/plot_corner.py index 4d01d177..30f1f0cf 100644 --- a/doc/examples/plot_corner.py +++ b/doc/examples/plot_corner.py @@ -11,7 +11,6 @@ position of corners. """ -import numpy as np from matplotlib import pyplot as plt from skimage import data diff --git a/doc/examples/plot_random_walker_segmentation.py b/doc/examples/plot_random_walker_segmentation.py index 6d194293..7d9f4fa8 100644 --- a/doc/examples/plot_random_walker_segmentation.py +++ b/doc/examples/plot_random_walker_segmentation.py @@ -19,7 +19,6 @@ values, and use the random walker for the segmentation. .. [1] *Random walks for image segmentation*, Leo Grady, IEEE Trans. Pattern Anal. Mach. Intell. 2006 Nov; 28(11):1768-83 """ -print __doc__ import numpy as np from scipy import ndimage diff --git a/skimage/exposure/_adapthist.py b/skimage/exposure/_adapthist.py index 346d8424..8a825435 100644 --- a/skimage/exposure/_adapthist.py +++ b/skimage/exposure/_adapthist.py @@ -259,6 +259,8 @@ def map_histogram(hist, min_val, max_val, n_pixels): It does so by cumulating the input histogram. + Parameters + ---------- hist : ndarray Clipped histogram. min_val : int @@ -301,12 +303,11 @@ def interpolate(image, xslice, yslice, out : ndarray Original image with the subregion replaced. - Note - ---- - This function calculates the new greylevel assignments of pixels - within a submatrix of the image. - This is done by a bilinear interpolation between four different - mappings in order to eliminate boundary artifacts. + Notes + ----- + This function calculates the new greylevel assignments of pixels within + a submatrix of the image. This is done by a bilinear interpolation between + four different mappings in order to eliminate boundary artifacts. """ norm = xslice.size * yslice.size # Normalization factor # interpolation weight matrices diff --git a/skimage/feature/_daisy.py b/skimage/feature/_daisy.py index 1bb8cbf5..1a97de8f 100644 --- a/skimage/feature/_daisy.py +++ b/skimage/feature/_daisy.py @@ -53,30 +53,35 @@ def daisy(img, step=4, radius=15, rings=3, histograms=8, orientations=8, the spatial smoothing of the center histogram and the last sigma value defines the spatial smoothing of the outermost ring. Specifying sigmas overrides the following parameter. - ``rings = len(sigmas)-1`` + + ``rings = len(sigmas) - 1`` ring_radii : 1D array of int, optional Radius (in pixels) for each ring. Specifying ring_radii overrides the following two parameters. - | ``rings = len(ring_radii)`` - | ``radius = ring_radii[-1]`` + + ``rings = len(ring_radii)`` + ``radius = ring_radii[-1]`` If both sigmas and ring_radii are given, they must satisfy the following predicate since no radius is needed for the center histogram. - ``len(ring_radii) == len(sigmas)+1`` + + ``len(ring_radii) == len(sigmas) + 1`` + visualize : bool, optional Generate a visualization of the DAISY descriptors - Returns ------- descs : array Grid of DAISY descriptors for the given image as an array dimensionality (P, Q, R) where - | ``P = ceil((M-radius*2)/step)`` - | ``Q = ceil((N-radius*2)/step)`` - | ``R = (rings*histograms + 1)*orientations`` + + ``P = ceil((M - radius*2) / step)`` + ``Q = ceil((N - radius*2) / step)`` + ``R = (rings * histograms + 1) * orientations`` + descs_img : (M, N, 3) array (only if visualize==True) Visualization of the DAISY descriptors. diff --git a/skimage/feature/corner.py b/skimage/feature/corner.py index 867acb5d..12d3fb70 100644 --- a/skimage/feature/corner.py +++ b/skimage/feature/corner.py @@ -140,7 +140,7 @@ def corner_harris(image, method='k', k=0.05, eps=1e-6, sigma=1): ..[2] http://en.wikipedia.org/wiki/Corner_detection Examples - ------- + -------- >>> from skimage.feature import corner_harris, corner_peaks >>> square = np.zeros([10, 10]) >>> square[2:8, 2:8] = 1 @@ -210,7 +210,7 @@ def corner_shi_tomasi(image, sigma=1): ..[2] http://en.wikipedia.org/wiki/Corner_detection Examples - ------- + -------- >>> from skimage.feature import corner_shi_tomasi, corner_peaks >>> square = np.zeros([10, 10]) >>> square[2:8, 2:8] = 1 @@ -277,7 +277,7 @@ def corner_foerstner(image, sigma=1): ..[2] http://en.wikipedia.org/wiki/Corner_detection Examples - ------- + -------- >>> from skimage.feature import corner_foerstner, corner_peaks >>> square = np.zeros([10, 10]) >>> square[2:8, 2:8] = 1 diff --git a/skimage/feature/corner_cy.pyx b/skimage/feature/corner_cy.pyx index 407e6642..71c748f8 100644 --- a/skimage/feature/corner_cy.pyx +++ b/skimage/feature/corner_cy.pyx @@ -34,7 +34,7 @@ def corner_moravec(image, Py_ssize_t window_size=1): ..[2] http://en.wikipedia.org/wiki/Corner_detection Examples - ------- + -------- >>> from skimage.feature import moravec, peak_local_max >>> square = np.zeros([7, 7]) >>> square[3, 3] = 1 diff --git a/skimage/feature/peak.py b/skimage/feature/peak.py index 4b0f6b4f..9c7a934f 100644 --- a/skimage/feature/peak.py +++ b/skimage/feature/peak.py @@ -50,9 +50,10 @@ def peak_local_max(image, min_distance=10, threshold_abs=0, threshold_rel=0.1, Returns ------- output : (N, 2) array or ndarray of bools - If `indices = True` : (row, column) coordinates of peaks. - If `indices = False` : Boolean array shaped like `image`, - with peaks represented by True values. + + * If `indices = True` : (row, column) coordinates of peaks. + * If `indices = False` : Boolean array shaped like `image`, with peaks + represented by True values. Notes ----- diff --git a/skimage/filter/_denoise.py b/skimage/filter/_denoise.py index e26f8387..87850759 100644 --- a/skimage/filter/_denoise.py +++ b/skimage/filter/_denoise.py @@ -32,7 +32,7 @@ def _denoise_tv_chambolle_3d(im, weight=100, eps=2.e-4, n_iter_max=200): Rudin, Osher and Fatemi algorithm. Examples - --------- + -------- >>> x, y, z = np.ogrid[0:40, 0:40, 0:40] >>> mask = (x - 22)**2 + (y - 20)**2 + (z - 17)**2 < 8**2 >>> mask = mask.astype(np.float) @@ -123,7 +123,7 @@ def _denoise_tv_chambolle_2d(im, weight=50, eps=2.e-4, n_iter_max=200): Springer, 2004, 20, 89-97. Examples - --------- + -------- >>> from skimage import color, data >>> lena = color.rgb2gray(data.lena()) >>> lena += 0.5 * lena.std() * np.random.randn(*lena.shape) @@ -221,7 +221,7 @@ def denoise_tv_chambolle(im, weight=50, eps=2.e-4, n_iter_max=200, Springer, 2004, 20, 89-97. Examples - --------- + -------- 2D example on Lena image: >>> from skimage import color, data diff --git a/skimage/filter/rank/rank.pyx b/skimage/filter/rank/rank.pyx index 261f0bb6..e8a4c8f1 100644 --- a/skimage/filter/rank/rank.pyx +++ b/skimage/filter/rank/rank.pyx @@ -648,12 +648,12 @@ def noise_filter(image, selem, out=None, mask=None, shift_x=False, References ---------- .. [Hashimoto12] N. Hashimoto et al. Referenceless image quality evaluation - for whole slide imaging. J Pathol Inform 2012;3:9. + for whole slide imaging. J Pathol Inform 2012;3:9. Returns ------- out : uint8 array or uint16 array (same as input image) - The image noise . + The image noise. """ diff --git a/skimage/segmentation/random_walker_segmentation.py b/skimage/segmentation/random_walker_segmentation.py index 32baf59c..6df714d2 100644 --- a/skimage/segmentation/random_walker_segmentation.py +++ b/skimage/segmentation/random_walker_segmentation.py @@ -30,8 +30,7 @@ from ..filter import rank_order def _make_graph_edges_3d(n_x, n_y, n_z): - """ - Returns a list of edges for a 3D image. + """Returns a list of edges for a 3D image. Parameters ---------- @@ -45,9 +44,12 @@ def _make_graph_edges_3d(n_x, n_y, n_z): Returns ------- edges : (2, N) ndarray - with the total number of edges N = n_x * n_y * (nz - 1) + - n_x * (n_y - 1) * nz + - (n_x - 1) * n_y * nz + with the total number of edges:: + + N = n_x * n_y * (nz - 1) + + n_x * (n_y - 1) * nz + + (n_x - 1) * n_y * nz + Graph edges with each column describing a node-id pair. """ vertices = np.arange(n_x * n_y * n_z).reshape((n_x, n_y, n_z)) @@ -200,6 +202,7 @@ def random_walker(data, labels, beta=130, mode='bf', tol=1.e-3, copy=True, mode : {'bf', 'cg_mg', 'cg'} (default: 'bf') Mode for solving the linear system in the random walker algorithm. + - 'bf' (brute force, default): an LU factorization of the Laplacian is computed. This is fast for small images (<1024x1024), but very slow (due to the memory cost) and memory-consuming for big images (in 3-D @@ -214,6 +217,7 @@ def random_walker(data, labels, beta=130, mode='bf', tol=1.e-3, copy=True, requires that the pyamg module (http://code.google.com/p/pyamg/) is installed. For images of size > 512x512, this is the recommended (fastest) mode. + tol : float tolerance to achieve when solving the linear system, in cg' and 'cg_mg' modes. @@ -237,12 +241,12 @@ def random_walker(data, labels, beta=130, mode='bf', tol=1.e-3, copy=True, Returns ------- output : ndarray - If `return_full_prob` is False, array of ints of same shape as `data`, - in which each pixel has been labeled according to the marker that - reached the pixel first by anisotropic diffusion. - If `return_full_prob` is True, array of floats of shape - `(nlabels, data.shape)`. `output[label_nb, i, j]` is the probability - that label `label_nb` reaches the pixel `(i, j)` first. + * If `return_full_prob` is False, array of ints of same shape as + `data`, in which each pixel has been labeled according to the marker + that reached the pixel first by anisotropic diffusion. + * If `return_full_prob` is True, array of floats of shape + `(nlabels, data.shape)`. `output[label_nb, i, j]` is the probability + that label `label_nb` reaches the pixel `(i, j)` first. See also -------- From 1724fb23510224e171955fa4172ceebeb3efd5aa Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Sat, 2 Mar 2013 17:40:17 -0600 Subject: [PATCH 133/215] Change `dtype_limits` clipping behavior. --- skimage/util/dtype.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/skimage/util/dtype.py b/skimage/util/dtype.py index b8d57243..03fa62b4 100644 --- a/skimage/util/dtype.py +++ b/skimage/util/dtype.py @@ -28,19 +28,19 @@ if np.__version__ >= "1.6.0": _supported_types += (np.float16, ) -def dtype_limits(image, auto_clip=True): +def dtype_limits(image, clip_negative=True): """Return intensity limits, i.e. (min, max) tuple, of the image's dtype. Parameters ---------- image : ndarray Input image. - auto_clip : bool + clip_negative : bool If True, clip the negative range (i.e. return 0 for min intensity) - if the input image has no negative values. + even if the image dtype allows negative values. """ imin, imax = dtype_range[image.dtype.type] - if auto_clip and imin < 0 and image.min() >= 0: + if clip_negative: imin = 0 return imin, imax From 6e0112f449fb46ea11af5a004aca7acfca2f567d Mon Sep 17 00:00:00 2001 From: Stefan van der Walt Date: Fri, 1 Mar 2013 18:00:58 +0200 Subject: [PATCH 134/215] BUG: Recast label output to satisfy ndimage on 32-bit platforms. --- skimage/morphology/ccomp.pyx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/skimage/morphology/ccomp.pyx b/skimage/morphology/ccomp.pyx index bbf6ed44..1db78a6d 100644 --- a/skimage/morphology/ccomp.pyx +++ b/skimage/morphology/ccomp.pyx @@ -214,4 +214,8 @@ def label(input, DTYPE_t neighbors=8, DTYPE_t background=-1): else: data[i, j] = data_p[forest[i, j]] - return data + # Work around a bug in ndimage's type checking on 32-bit platforms + if data.dtype == np.int32: + return data.view(np.int32) + else: + return data From 5686e11c97e5855e074a677a249285f546713f63 Mon Sep 17 00:00:00 2001 From: Juan Nunez-Iglesias Date: Thu, 21 Feb 2013 17:17:58 +1100 Subject: [PATCH 135/215] Add funct to remove small connected compononents --- skimage/morphology/__init__.py | 1 + skimage/morphology/misc.py | 40 ++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 skimage/morphology/misc.py diff --git a/skimage/morphology/__init__.py b/skimage/morphology/__init__.py index d4c775eb..e93d22ad 100644 --- a/skimage/morphology/__init__.py +++ b/skimage/morphology/__init__.py @@ -7,3 +7,4 @@ from .watershed import watershed, is_local_maximum from ._skeletonize import skeletonize, medial_axis from .convex_hull import convex_hull_image from .greyreconstruct import reconstruction +from .misc import remove_small_connected_components diff --git a/skimage/morphology/misc.py b/skimage/morphology/misc.py new file mode 100644 index 00000000..159a3a3b --- /dev/null +++ b/skimage/morphology/misc.py @@ -0,0 +1,40 @@ +import numpy as np +import scipy.ndimage as nd + +def remove_small_connected_components(ar, min_size=64, + connectivity=1, in_place=False): + """Remove connected components smaller than the specified size. + + Parameters + ---------- + ar : ndarray (arbitrary shape, int or bool type) + The array containing the connected components of interest. + min_size : int, optional (default: 64) + The smallest allowable connected component size. + connectivity : int, {1, 2, ..., ar.ndim}, optional (default: 1) + The connectivity defining the neighborhood of a pixel. + in_place : bool, optional (default: False) + If `True`, remove the connected components in the input array itself. + Otherwise, make a copy. + + Returns + ------- + out : ndarray, same shape and type as input `ar` + The input array with small connected components removed. + """ + structuring_element = nd.generate_binary_structure(ar.ndim, connectivity) + if in_place: + out = ar + else: + out = ar.copy() + if min_size == 0: # shortcut for efficiency + return out + if out.dtype == bool: + ccs = nd.label(ar, structuring_element)[0] + else: + ccs = out + component_sizes = np.bincount(ccs.ravel()) + too_small = component_sizes < min_size + too_small_mask = too_small[ccs] + out[too_small_mask] = 0 + return out From 24b14508c804446f40ba1fdf4007ce35c7c7ec77 Mon Sep 17 00:00:00 2001 From: Juan Nunez-Iglesias Date: Sun, 3 Mar 2013 14:34:14 +1100 Subject: [PATCH 136/215] Add examples to docstring --- skimage/morphology/misc.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/skimage/morphology/misc.py b/skimage/morphology/misc.py index 159a3a3b..81f47e9c 100644 --- a/skimage/morphology/misc.py +++ b/skimage/morphology/misc.py @@ -21,6 +21,28 @@ def remove_small_connected_components(ar, min_size=64, ------- out : ndarray, same shape and type as input `ar` The input array with small connected components removed. + + Examples + -------- + >>> import numpy as np + >>> from skimage import morphology + >>> from scipy import ndimage as nd + >>> a = np.array([[0, 0, 0, 1, 0], + ... [1, 1, 1, 0, 0], + ... [1, 1, 1, 0, 1]], bool) + >>> b = morphology.remove_small_connected_components(a, 6) + >>> b + array([[False, False, False, False, False], + [ True, True, True, False, False], + [ True, True, True, False, False]], dtype=bool) + >>> c = morphology.remove_small_connected_components(a, 7, connectivity=2) + >>> c + array([[False, False, False, True, False], + [ True, True, True, False, False], + [ True, True, True, False, False]], dtype=bool) + >>> d = morphology.remove_small_connected_components(a, 6, in_place=True) + >>> d is a + True """ structuring_element = nd.generate_binary_structure(ar.ndim, connectivity) if in_place: From 5222fb5ff7eef0115603e99d8f266eda884fb141 Mon Sep 17 00:00:00 2001 From: Juan Nunez-Iglesias Date: Sun, 3 Mar 2013 14:50:20 +1100 Subject: [PATCH 137/215] Add tests for remove_small_connected_components --- skimage/morphology/tests/test_misc.py | 30 +++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 skimage/morphology/tests/test_misc.py diff --git a/skimage/morphology/tests/test_misc.py b/skimage/morphology/tests/test_misc.py new file mode 100644 index 00000000..77bb20c3 --- /dev/null +++ b/skimage/morphology/tests/test_misc.py @@ -0,0 +1,30 @@ +import numpy as np +from numpy.testing import assert_array_equal, assert_equal +from skimage.morphology import remove_small_connected_components + +test_image = np.array([[0, 0, 0, 1, 0], + [1, 1, 1, 0, 0], + [1, 1, 1, 0, 1]], bool) +def test_one_connectivity(): + expected = np.array([[0, 0, 0, 0, 0], + [1, 1, 1, 0, 0], + [1, 1, 1, 0, 0]], bool) + observed = remove_small_connected_components(test_image, min_size=6) + assert_array_equal(observed, expected) + +def test_two_connectivity(): + expected = np.array([[0, 0, 0, 1, 0], + [1, 1, 1, 0, 0], + [1, 1, 1, 0, 0]], bool) + observed = remove_small_connected_components(test_image, min_size=7, + connectivity=2) + assert_array_equal(observed, expected) + +def test_in_place(): + observed = remove_small_connected_components(test_image, min_size=6, + in_place=True) + assert_equal(observed is test_image, True, + "remove_small_connected_components in_place argument failed.") + +if __name__ == "__main__": + np.testing.run_module_suite() From afed313369e332f2c7b64bcb9e65582126016a4e Mon Sep 17 00:00:00 2001 From: Juan Nunez-Iglesias Date: Sun, 3 Mar 2013 15:18:12 +1100 Subject: [PATCH 138/215] Modify example to use remove_small_connected_components --- doc/examples/applications/plot_coins_segmentation.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/doc/examples/applications/plot_coins_segmentation.py b/doc/examples/applications/plot_coins_segmentation.py index f46bbd33..943c57fb 100644 --- a/doc/examples/applications/plot_coins_segmentation.py +++ b/doc/examples/applications/plot_coins_segmentation.py @@ -90,12 +90,8 @@ plt.title('Filling the holes') Small spurious objects are easily removed by setting a minimum size for valid objects. """ - -label_objects, nb_labels = ndimage.label(fill_coins) -sizes = np.bincount(label_objects.ravel()) -mask_sizes = sizes > 20 -mask_sizes[0] = 0 -coins_cleaned = mask_sizes[label_objects] +from skimage import morphology +coins_cleaned = morphology.remove_small_connected_components(fill_coins, 21) plt.figure(figsize=(4, 3)) plt.imshow(coins_cleaned, cmap=plt.cm.gray, interpolation='nearest') @@ -149,8 +145,7 @@ plt.title('markers') Finally, we use the watershed transform to fill regions of the elevation map starting from the markers determined above: """ -from skimage.morphology import watershed -segmentation = watershed(elevation_map, markers) +segmentation = morphology.watershed(elevation_map, markers) plt.figure(figsize=(4, 3)) plt.imshow(segmentation, cmap=plt.cm.gray, interpolation='nearest') From d6c5ba38ef03bd9e3c9f82bd1c724ea06ebc7f11 Mon Sep 17 00:00:00 2001 From: Juan Nunez-Iglesias Date: Sun, 3 Mar 2013 20:33:05 +1100 Subject: [PATCH 139/215] Improve docstring for remove_small_connected_components - Explain behavior if input is of int type. - Add `Raises` section for possible errors. - Remove explicit numpy import from example. --- skimage/morphology/misc.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/skimage/morphology/misc.py b/skimage/morphology/misc.py index 81f47e9c..d70e8eb8 100644 --- a/skimage/morphology/misc.py +++ b/skimage/morphology/misc.py @@ -2,13 +2,15 @@ import numpy as np import scipy.ndimage as nd def remove_small_connected_components(ar, min_size=64, - connectivity=1, in_place=False): + connectivity=1, in_place=False): """Remove connected components smaller than the specified size. Parameters ---------- ar : ndarray (arbitrary shape, int or bool type) - The array containing the connected components of interest. + The array containing the connected components of interest. If the array + type is int, it is assumed that it contains already-labeled objects. + The ints must be non-negative. min_size : int, optional (default: 64) The smallest allowable connected component size. connectivity : int, {1, 2, ..., ar.ndim}, optional (default: 1) @@ -17,6 +19,13 @@ def remove_small_connected_components(ar, min_size=64, If `True`, remove the connected components in the input array itself. Otherwise, make a copy. + Raises + ------ + ValueError + If the input array is of int type but contains negative values. + TypeError + If the input array is of an invalid type, such as float or string. + Returns ------- out : ndarray, same shape and type as input `ar` @@ -24,7 +33,6 @@ def remove_small_connected_components(ar, min_size=64, Examples -------- - >>> import numpy as np >>> from skimage import morphology >>> from scipy import ndimage as nd >>> a = np.array([[0, 0, 0, 1, 0], From b07f7fd9bd910784ee14f08800765d3276b1e6fa Mon Sep 17 00:00:00 2001 From: Juan Nunez-Iglesias Date: Sun, 3 Mar 2013 20:47:02 +1100 Subject: [PATCH 140/215] Rename function to remove_small_objects --- skimage/morphology/__init__.py | 2 +- skimage/morphology/misc.py | 3 +-- skimage/morphology/tests/test_misc.py | 10 +++++----- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/skimage/morphology/__init__.py b/skimage/morphology/__init__.py index e93d22ad..044fcf89 100644 --- a/skimage/morphology/__init__.py +++ b/skimage/morphology/__init__.py @@ -7,4 +7,4 @@ from .watershed import watershed, is_local_maximum from ._skeletonize import skeletonize, medial_axis from .convex_hull import convex_hull_image from .greyreconstruct import reconstruction -from .misc import remove_small_connected_components +from .misc import remove_small_objects diff --git a/skimage/morphology/misc.py b/skimage/morphology/misc.py index d70e8eb8..719fba5a 100644 --- a/skimage/morphology/misc.py +++ b/skimage/morphology/misc.py @@ -1,8 +1,7 @@ import numpy as np import scipy.ndimage as nd -def remove_small_connected_components(ar, min_size=64, - connectivity=1, in_place=False): +def remove_small_objects(ar, min_size=64, connectivity=1, in_place=False): """Remove connected components smaller than the specified size. Parameters diff --git a/skimage/morphology/tests/test_misc.py b/skimage/morphology/tests/test_misc.py index 77bb20c3..f27b1a45 100644 --- a/skimage/morphology/tests/test_misc.py +++ b/skimage/morphology/tests/test_misc.py @@ -1,6 +1,6 @@ import numpy as np from numpy.testing import assert_array_equal, assert_equal -from skimage.morphology import remove_small_connected_components +from skimage.morphology import remove_small_objects test_image = np.array([[0, 0, 0, 1, 0], [1, 1, 1, 0, 0], @@ -9,22 +9,22 @@ def test_one_connectivity(): expected = np.array([[0, 0, 0, 0, 0], [1, 1, 1, 0, 0], [1, 1, 1, 0, 0]], bool) - observed = remove_small_connected_components(test_image, min_size=6) + observed = remove_small_objects(test_image, min_size=6) assert_array_equal(observed, expected) def test_two_connectivity(): expected = np.array([[0, 0, 0, 1, 0], [1, 1, 1, 0, 0], [1, 1, 1, 0, 0]], bool) - observed = remove_small_connected_components(test_image, min_size=7, + observed = remove_small_objects(test_image, min_size=7, connectivity=2) assert_array_equal(observed, expected) def test_in_place(): - observed = remove_small_connected_components(test_image, min_size=6, + observed = remove_small_objects(test_image, min_size=6, in_place=True) assert_equal(observed is test_image, True, - "remove_small_connected_components in_place argument failed.") + "remove_small_objects in_place argument failed.") if __name__ == "__main__": np.testing.run_module_suite() From 6b9ecd80b91ad4c2ded3536ac34d21d09ba33bc8 Mon Sep 17 00:00:00 2001 From: Juan Nunez-Iglesias Date: Sun, 3 Mar 2013 21:43:56 +1100 Subject: [PATCH 141/215] Add raise and labeled image tests --- skimage/morphology/tests/test_misc.py | 36 +++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/skimage/morphology/tests/test_misc.py b/skimage/morphology/tests/test_misc.py index f27b1a45..4b6e6e01 100644 --- a/skimage/morphology/tests/test_misc.py +++ b/skimage/morphology/tests/test_misc.py @@ -1,10 +1,12 @@ import numpy as np -from numpy.testing import assert_array_equal, assert_equal +from numpy.testing import assert_array_equal, assert_equal, assert_raises from skimage.morphology import remove_small_objects test_image = np.array([[0, 0, 0, 1, 0], [1, 1, 1, 0, 0], [1, 1, 1, 0, 1]], bool) + + def test_one_connectivity(): expected = np.array([[0, 0, 0, 0, 0], [1, 1, 1, 0, 0], @@ -12,19 +14,43 @@ def test_one_connectivity(): observed = remove_small_objects(test_image, min_size=6) assert_array_equal(observed, expected) + def test_two_connectivity(): expected = np.array([[0, 0, 0, 1, 0], [1, 1, 1, 0, 0], [1, 1, 1, 0, 0]], bool) - observed = remove_small_objects(test_image, min_size=7, - connectivity=2) + observed = remove_small_objects(test_image, min_size=7, connectivity=2) assert_array_equal(observed, expected) + def test_in_place(): - observed = remove_small_objects(test_image, min_size=6, - in_place=True) + observed = remove_small_objects(test_image, min_size=6, in_place=True) assert_equal(observed is test_image, True, "remove_small_objects in_place argument failed.") + +def test_labeled_image(): + labeled_image = np.array([[2, 2, 2, 0, 1], + [2, 2, 2, 0, 1], + [2, 0, 0, 0, 0], + [0, 0, 3, 3, 3]], dtype=int) + expected = np.array([[2, 2, 2, 0, 0], + [2, 2, 2, 0, 0], + [2, 0, 0, 0, 0], + [0, 0, 3, 3, 3]], dtype=int) + observed = remove_small_objects(labeled_image, min_size=3) + assert_array_equal(observed, expected) + + +def test_float_input(): + float_test = np.random.rand(5, 5) + assert_raises(TypeError, remove_small_objects, float_test) + + +def test_negative_input(): + negative_int = np.random.randint(-4, -1, size=(5, 5)) + assert_raises(ValueError, remove_small_objects, negative_int) + + if __name__ == "__main__": np.testing.run_module_suite() From 332469c6df8fcb0d3151b457a76abccb1c136c1b Mon Sep 17 00:00:00 2001 From: Juan Nunez-Iglesias Date: Mon, 4 Mar 2013 01:24:26 +1100 Subject: [PATCH 142/215] Fix missed rename in example --- doc/examples/applications/plot_coins_segmentation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/examples/applications/plot_coins_segmentation.py b/doc/examples/applications/plot_coins_segmentation.py index 943c57fb..0d1d8a34 100644 --- a/doc/examples/applications/plot_coins_segmentation.py +++ b/doc/examples/applications/plot_coins_segmentation.py @@ -91,7 +91,7 @@ Small spurious objects are easily removed by setting a minimum size for valid objects. """ from skimage import morphology -coins_cleaned = morphology.remove_small_connected_components(fill_coins, 21) +coins_cleaned = morphology.remove_small_objects(fill_coins, 21) plt.figure(figsize=(4, 3)) plt.imshow(coins_cleaned, cmap=plt.cm.gray, interpolation='nearest') From dc83c64d44ca8296f1757321fad9603bad79a676 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Sun, 3 Mar 2013 15:37:45 +0100 Subject: [PATCH 143/215] Change data type of index variables --- skimage/transform/_hough_transform.pyx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/skimage/transform/_hough_transform.pyx b/skimage/transform/_hough_transform.pyx index 29a38eb7..a6ce6c27 100644 --- a/skimage/transform/_hough_transform.pyx +++ b/skimage/transform/_hough_transform.pyx @@ -50,11 +50,11 @@ def _hough_circle(cnp.ndarray img, \ x, y = np.nonzero(img) # Offset the image - cdef int max_radius = radius.max() + cdef Py_ssize_t max_radius = radius.max() x = x + max_radius y = y + max_radius - cdef int px, py + cdef Py_ssize_t px, py cdef cnp.ndarray[ndim=1, dtype=cnp.intp_t] tx, ty, circle_x, circle_y cdef cnp.ndarray acc = np.zeros((radius.size, img.shape[0] + 2 * max_radius, From 3869d1ef8a10f31ba0c345f80cc76bec5e8b3bb8 Mon Sep 17 00:00:00 2001 From: Jan Margeta Date: Sun, 3 Mar 2013 22:45:03 +0100 Subject: [PATCH 144/215] Removing unnecessary typedefs for uint16_t in MSVC build --- skimage/_shared/vectorized_ops.h | 4 ---- 1 file changed, 4 deletions(-) diff --git a/skimage/_shared/vectorized_ops.h b/skimage/_shared/vectorized_ops.h index 29026490..ab9647ea 100644 --- a/skimage/_shared/vectorized_ops.h +++ b/skimage/_shared/vectorized_ops.h @@ -12,12 +12,8 @@ #include #elif defined(_MSC_VER) #define inline __inline -#if (_MSC_VER < 1300) -typedef unsigned short uint16_t; -#else typedef unsigned __int16 uint16_t; #endif -#endif /** * Add 16 unsigned 16-bit integers using SSE2, MMX or Altivec, if From 3249faf308af182435b081e28210274179fdd81e Mon Sep 17 00:00:00 2001 From: Juan Nunez-Iglesias Date: Mon, 4 Mar 2013 16:54:29 +1100 Subject: [PATCH 145/215] Add specific error messages and exceptions --- skimage/morphology/misc.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/skimage/morphology/misc.py b/skimage/morphology/misc.py index 719fba5a..bb0ff687 100644 --- a/skimage/morphology/misc.py +++ b/skimage/morphology/misc.py @@ -51,7 +51,11 @@ def remove_small_objects(ar, min_size=64, connectivity=1, in_place=False): >>> d is a True """ - structuring_element = nd.generate_binary_structure(ar.ndim, connectivity) + errmsg = "Only numpy.ndarrays of bool or integer type are supported. " + if type(ar) != np.ndarray: + raise TypeError(errmsg + "Got a %s." % type(ar)) + elif ar.dtype != bool and not np.issubdtype(ar.dtype, np.integer): + raise TypeError(errmsg + "Got a numpy.ndarray of type %s" % ar.dtype) if in_place: out = ar else: @@ -59,10 +63,15 @@ def remove_small_objects(ar, min_size=64, connectivity=1, in_place=False): if min_size == 0: # shortcut for efficiency return out if out.dtype == bool: - ccs = nd.label(ar, structuring_element)[0] + selem = nd.generate_binary_structure(ar.ndim, connectivity) + ccs = nd.label(ar, selem)[0] else: ccs = out - component_sizes = np.bincount(ccs.ravel()) + try: + component_sizes = np.bincount(ccs.ravel()) + except ValueError: + raise ValueError("Negative value labels are not supported. Try " + "relabeling the input with `scipy.ndimage.label`.") too_small = component_sizes < min_size too_small_mask = too_small[ccs] out[too_small_mask] = 0 From dae9b7adfdabc065c2107d19779a1bb965bff759 Mon Sep 17 00:00:00 2001 From: Stefan van der Walt Date: Mon, 4 Mar 2013 11:30:19 +0200 Subject: [PATCH 146/215] STY: Minor cleanup. --- skimage/morphology/misc.py | 21 ++++++++++++++------- skimage/morphology/tests/test_misc.py | 4 ++-- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/skimage/morphology/misc.py b/skimage/morphology/misc.py index bb0ff687..5f548503 100644 --- a/skimage/morphology/misc.py +++ b/skimage/morphology/misc.py @@ -1,6 +1,7 @@ import numpy as np import scipy.ndimage as nd + def remove_small_objects(ar, min_size=64, connectivity=1, in_place=False): """Remove connected components smaller than the specified size. @@ -51,28 +52,34 @@ def remove_small_objects(ar, min_size=64, connectivity=1, in_place=False): >>> d is a True """ - errmsg = "Only numpy.ndarrays of bool or integer type are supported. " - if type(ar) != np.ndarray: - raise TypeError(errmsg + "Got a %s." % type(ar)) - elif ar.dtype != bool and not np.issubdtype(ar.dtype, np.integer): - raise TypeError(errmsg + "Got a numpy.ndarray of type %s" % ar.dtype) + # Should use `issubdtype` below, but there's a bug in numpy 1.7 + if not (ar.dtype == bool or np.issubdtype(ar.dtype, int)): + raise ValueError("Only bool or integer image types are supported. " + "Got %s." % ar.dtype) + if in_place: out = ar else: out = ar.copy() - if min_size == 0: # shortcut for efficiency + + if min_size == 0: # shortcut for efficiency return out + if out.dtype == bool: selem = nd.generate_binary_structure(ar.ndim, connectivity) ccs = nd.label(ar, selem)[0] else: ccs = out + try: component_sizes = np.bincount(ccs.ravel()) except ValueError: raise ValueError("Negative value labels are not supported. Try " - "relabeling the input with `scipy.ndimage.label`.") + "relabeling the input with `scipy.ndimage.label` or " + "`skimage.morphology.label`.") + too_small = component_sizes < min_size too_small_mask = too_small[ccs] out[too_small_mask] = 0 + return out diff --git a/skimage/morphology/tests/test_misc.py b/skimage/morphology/tests/test_misc.py index 4b6e6e01..abc718c2 100644 --- a/skimage/morphology/tests/test_misc.py +++ b/skimage/morphology/tests/test_misc.py @@ -25,7 +25,7 @@ def test_two_connectivity(): def test_in_place(): observed = remove_small_objects(test_image, min_size=6, in_place=True) - assert_equal(observed is test_image, True, + assert_equal(observed is test_image, True, "remove_small_objects in_place argument failed.") @@ -44,7 +44,7 @@ def test_labeled_image(): def test_float_input(): float_test = np.random.rand(5, 5) - assert_raises(TypeError, remove_small_objects, float_test) + assert_raises(ValueError, remove_small_objects, float_test) def test_negative_input(): From 40e7aee4de8e8a96a28b25f2fe0b4accc33ed84b Mon Sep 17 00:00:00 2001 From: Xavier Moles Lopez Date: Mon, 4 Mar 2013 10:50:17 +0100 Subject: [PATCH 147/215] FIX: Ran autopep8 on colorconv.py FIX: modified color.data.ihc function FIX: smaller ihc.jpg image --- skimage/color/colorconv.py | 5 +++-- skimage/data/ihc.jpg | Bin 274001 -> 231483 bytes 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/skimage/color/colorconv.py b/skimage/color/colorconv.py index 96ef5f86..0ff4c722 100644 --- a/skimage/color/colorconv.py +++ b/skimage/color/colorconv.py @@ -55,6 +55,7 @@ import numpy as np from scipy import linalg from ..util import dtype + def is_rgb(image): """Test whether the image is RGB or RGBA. @@ -769,7 +770,7 @@ def rgb2hed(rgb): >>> ihc_hed = rgb2hed(ihc) """ rgb = rgb.astype(float) + 1 - hed = np.dot(np.reshape(-np.log(rgb), (-1,3)), hed_from_rgb) + hed = np.dot(np.reshape(-np.log(rgb), (-1, 3)), hed_from_rgb) return np.reshape(hed, rgb.shape) @@ -806,6 +807,6 @@ def hed2rgb(hed): >>> ihc_hed = rgb2hed(ihc) >>> ihc_rgb = hed2rgb(ihc_hed) """ - logrgb1 = np.dot(-np.reshape(hed, (-1,3)) , rgb_from_hed) + logrgb1 = np.dot(-np.reshape(hed, (-1, 3)), rgb_from_hed) rgb1 = np.exp(logrgb1) return np.reshape(rgb1 - 1, hed.shape) diff --git a/skimage/data/ihc.jpg b/skimage/data/ihc.jpg index 24d68bd87f644c63a2eda91dd97d36aa22302cf8..e64d2a0da839c19bddd48ee25fe8340992b9004c 100644 GIT binary patch literal 231483 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%0DeUe9ccrL7r&_x_~GEE(# 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 literal 274001 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 ry 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�HK;Gv*^(zSc!bTQA+#g!nIBUb5 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 xNl->|DO>$apIQ(4<^KS7pkt#e Date: Mon, 4 Mar 2013 11:45:58 +0100 Subject: [PATCH 148/215] FIX: modified color.data.ihc docstring. --- skimage/data/__init__.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/skimage/data/__init__.py b/skimage/data/__init__.py index 320b78bb..a544179b 100644 --- a/skimage/data/__init__.py +++ b/skimage/data/__init__.py @@ -129,11 +129,12 @@ def clock(): return load("clock_motion.png") -def ihc(): - """Immunohistochemical staining with Haematoxylin-Eosin counterstaining. +def immunohistochemistry(): + """Immunohistochemical (IHC) staining with hematoxylin counterstaining. - This picture is an example of immunohistochemical staining with - Haematoxylin-Eosin counterstaining. + This picture shows colonic glands where the IHC expression of FHL2 protein + is revealed with DAB. Hematoxylin counterstaining is applied to enhance the + negative parts of the tissue. This image was acquired at the Center for Microscopy And Molecular Imaging (CMMI). From 7f5b331f22589093aa93e570f9a8a93868872923 Mon Sep 17 00:00:00 2001 From: Xavier Moles Lopez Date: Mon, 4 Mar 2013 12:04:44 +0100 Subject: [PATCH 149/215] FIX: import rgb2hed and hed2rgb in the test_colorconv script. --- skimage/color/tests/test_colorconv.py | 1 + 1 file changed, 1 insertion(+) diff --git a/skimage/color/tests/test_colorconv.py b/skimage/color/tests/test_colorconv.py index e49443fc..fec5c20d 100644 --- a/skimage/color/tests/test_colorconv.py +++ b/skimage/color/tests/test_colorconv.py @@ -21,6 +21,7 @@ from skimage.io import imread from skimage.color import ( rgb2hsv, hsv2rgb, rgb2xyz, xyz2rgb, + rgb2hed, hed2rgb, rgb2rgbcie, rgbcie2rgb, convert_colorspace, rgb2grey, gray2rgb, From 0a6b72bfe4f47b48e883591e61e43b0065793ea3 Mon Sep 17 00:00:00 2001 From: Xavier Moles Lopez Date: Mon, 4 Mar 2013 12:15:35 +0100 Subject: [PATCH 150/215] FIX: Fixed test_hed_rgb_roundtrip. --- skimage/color/tests/test_colorconv.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/skimage/color/tests/test_colorconv.py b/skimage/color/tests/test_colorconv.py index fec5c20d..7e76ded3 100644 --- a/skimage/color/tests/test_colorconv.py +++ b/skimage/color/tests/test_colorconv.py @@ -124,15 +124,15 @@ class TestColorconv(TestCase): # RGB<->HED roundtrip def test_hed_rgb_roundtrip(self): - test = np.array([[[97, 105, 152], - [217, 187, 225], - [117, 107, 168]], - [[217, 187, 225], - [176, 135, 83], - [234, 144, 205]], - [[97, 105, 152], - [117, 107, 168], - [217, 187, 225]]], dtype=np.uint8) + img_rgb = np.array([[[97, 105, 152], + [217, 187, 225], + [117, 107, 168]], + [[217, 187, 225], + [176, 135, 83], + [234, 144, 205]], + [[97, 105, 152], + [117, 107, 168], + [217, 187, 225]]], dtype=np.uint8) assert_array_almost_equal(hed2rgb(rgb2hed(img_rgb)), img_rgb) # RGB to RGB CIE From dec104e9ffb5514ea5cf6c8423d64acbe9831a39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Mon, 4 Mar 2013 15:00:37 +0100 Subject: [PATCH 151/215] Improve performance and usage of data types of hough circle transformation --- skimage/transform/_hough_transform.pyx | 30 +++++++++++++++----------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/skimage/transform/_hough_transform.pyx b/skimage/transform/_hough_transform.pyx index a6ce6c27..e0c004a3 100644 --- a/skimage/transform/_hough_transform.pyx +++ b/skimage/transform/_hough_transform.pyx @@ -20,10 +20,9 @@ cdef inline Py_ssize_t round(double r): return ((r + 0.5) if (r > 0.0) else (r - 0.5)) -@cython.boundscheck(False) def _hough_circle(cnp.ndarray img, \ cnp.ndarray[ndim=1, dtype=cnp.intp_t] radius, \ - normalize=True): + char normalize=True): """Perform a circular Hough transform. Parameters @@ -49,32 +48,39 @@ def _hough_circle(cnp.ndarray img, \ cdef cnp.ndarray[ndim=1, dtype=cnp.intp_t] x, y x, y = np.nonzero(img) + cdef Py_ssize_t num_pixels = x.size + # Offset the image cdef Py_ssize_t max_radius = radius.max() x = x + max_radius y = y + max_radius - cdef Py_ssize_t px, py - cdef cnp.ndarray[ndim=1, dtype=cnp.intp_t] tx, ty, circle_x, circle_y - cdef cnp.ndarray acc = np.zeros((radius.size, - img.shape[0] + 2 * max_radius, - img.shape[1] + 2 * max_radius)) + cdef Py_ssize_t i, p, c, num_circle_pixels, tx, ty + cdef cnp.ndarray[ndim=1, dtype=cnp.intp_t] circle_x, circle_y + + cdef cnp.ndarray[ndim=3, dtype=cnp.double_t] acc = \ + np.zeros((radius.size, + img.shape[0] + 2 * max_radius, + img.shape[1] + 2 * max_radius), dtype=np.double) for i, rad in enumerate(radius): # Store in memory the circle of given radius # centered at (0,0) circle_x, circle_y = circle_perimeter(0, 0, rad) + num_circle_pixels = circle_x.size + # For each non zero pixel - for (px, py) in zip(x, y): + for p in range(num_pixels): # Plug the circle at (px, py), # its coordinates are (tx, ty) - tx = circle_x + px - ty = circle_y + py - acc[i, tx, ty] += 1 + for c in range(num_circle_pixels): + tx = circle_x[c] + x[p] + ty = circle_y[c] + y[p] + acc[i, tx, ty] += 1 if normalize: - acc[i] = acc[i] / len(circle_x) + acc[i, :, :] /= num_circle_pixels return acc From 2e3f82e9d9dd4ce73b3b37c6c6aa566b64588d36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Mon, 4 Mar 2013 15:01:03 +0100 Subject: [PATCH 152/215] Remove unnecessary statement breaks --- skimage/transform/_hough_transform.pyx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/skimage/transform/_hough_transform.pyx b/skimage/transform/_hough_transform.pyx index e0c004a3..97ff720b 100644 --- a/skimage/transform/_hough_transform.pyx +++ b/skimage/transform/_hough_transform.pyx @@ -20,8 +20,8 @@ cdef inline Py_ssize_t round(double r): return ((r + 0.5) if (r > 0.0) else (r - 0.5)) -def _hough_circle(cnp.ndarray img, \ - cnp.ndarray[ndim=1, dtype=cnp.intp_t] radius, \ +def _hough_circle(cnp.ndarray img, + cnp.ndarray[ndim=1, dtype=cnp.intp_t] radius, char normalize=True): """Perform a circular Hough transform. From 2237cc1617f62618a78a8b1bc1a2b19b8106274f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Mon, 4 Mar 2013 15:02:09 +0100 Subject: [PATCH 153/215] Change color of circles for better visibility --- doc/examples/plot_circular_hough_transform.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/examples/plot_circular_hough_transform.py b/doc/examples/plot_circular_hough_transform.py index fc891d04..d2f8f2ae 100755 --- a/doc/examples/plot_circular_hough_transform.py +++ b/doc/examples/plot_circular_hough_transform.py @@ -66,7 +66,7 @@ for idx in np.argsort(accums)[::-1][:5]: center_x, center_y = centers[idx] radius = radii[idx] cx, cy = circle_perimeter(center_y, center_x, radius) - image[cy, cx] = (220, 250, 150) + image[cy, cx] = (220, 20, 20) ax.imshow(image, cmap=plt.cm.gray) plt.show() From 4925205082011dda230e7690f17042763a87b281 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Mon, 4 Mar 2013 16:26:06 +0100 Subject: [PATCH 154/215] Improve performance of normalization --- skimage/transform/_hough_transform.pyx | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/skimage/transform/_hough_transform.pyx b/skimage/transform/_hough_transform.pyx index 97ff720b..2b1acc7c 100644 --- a/skimage/transform/_hough_transform.pyx +++ b/skimage/transform/_hough_transform.pyx @@ -56,6 +56,7 @@ def _hough_circle(cnp.ndarray img, y = y + max_radius cdef Py_ssize_t i, p, c, num_circle_pixels, tx, ty + cdef double incr cdef cnp.ndarray[ndim=1, dtype=cnp.intp_t] circle_x, circle_y cdef cnp.ndarray[ndim=3, dtype=cnp.double_t] acc = \ @@ -70,6 +71,11 @@ def _hough_circle(cnp.ndarray img, num_circle_pixels = circle_x.size + if normalize: + incr = 1.0 / num_circle_pixels + else: + incr = 1 + # For each non zero pixel for p in range(num_pixels): # Plug the circle at (px, py), @@ -77,10 +83,7 @@ def _hough_circle(cnp.ndarray img, for c in range(num_circle_pixels): tx = circle_x[c] + x[p] ty = circle_y[c] + y[p] - acc[i, tx, ty] += 1 - - if normalize: - acc[i, :, :] /= num_circle_pixels + acc[i, tx, ty] += incr return acc From f3924da90b26b8bf4274463c7abd76e08366b2d4 Mon Sep 17 00:00:00 2001 From: Stefan van der Walt Date: Mon, 4 Mar 2013 18:36:32 +0200 Subject: [PATCH 155/215] DOC: Fix typo in remove_small_objects docstring. --- skimage/morphology/misc.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/skimage/morphology/misc.py b/skimage/morphology/misc.py index 5f548503..6274df53 100644 --- a/skimage/morphology/misc.py +++ b/skimage/morphology/misc.py @@ -22,8 +22,6 @@ def remove_small_objects(ar, min_size=64, connectivity=1, in_place=False): Raises ------ ValueError - If the input array is of int type but contains negative values. - TypeError If the input array is of an invalid type, such as float or string. Returns From 4a8016fde6e0aa421d8ca90557492ac538238d00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Mon, 4 Mar 2013 20:38:41 +0100 Subject: [PATCH 156/215] Add release notes for version 0.8 --- doc/release/release_0.8.txt | 71 +++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 doc/release/release_0.8.txt diff --git a/doc/release/release_0.8.txt b/doc/release/release_0.8.txt new file mode 100644 index 00000000..d146659c --- /dev/null +++ b/doc/release/release_0.8.txt @@ -0,0 +1,71 @@ +Announcement: scikits-image 0.8.0 +================================= + +We're happy to announce the 8th version of scikit-image! + +scikit-image is an image processing toolbox for SciPy that includes algorithms +for segmentation, geometric transformations, color space manipulation, +analysis, filtering, morphology, feature detection, and more. + +For more information, examples, and documentation, please visit our website: + + http://scikit-image.org + + +New Features +------------ + +- New rank filter package with many new functions and a very fast underlying + local histogram algorithm, especially for large structuring elements + `skimage.filter.rank.*` +- New function for small object removal + `skimage.morphology.remove_small_objects` +- New circular hough transformation `skimage.transform.hough_circle` +- New function to draw circle perimeter `skimage.draw.circle_perimeter` and + ellipse perimeter `skimage.draw.ellipse_perimeter` +- New dense DAISY feature descriptor `skimage.feature.daisy` +- New bilateral filter `skimage.filter.denoise_bilateral` +- New faster TV denoising filter based on split-Bregman algorithm + `skimage.filter.denoise_tv_bregman` +- New linear hough peak detection `skimage.transform.hough_peaks` +- New Scharr edge detection `skimage.filter.scharr` +- New geometric image scaling as convenience function + `skimage.transform.rescale` +- New theme for documentation and website +- Faster median filter through vectorization `skimage.filter.median_filter` +- Grayscale images supported for SLIC segmentation +- Unified peak detection with more options `skimage.feature.peak_local_max` +- `imread` can read images via URL and knows more formats `skimage.io.imread` + +Additionally, this release adds lots of bug fixes, new examples, and +performance enhancements. + + +Contributors to this release +---------------------------- + +This release was only possible due to the efforts of many contributors, both +new and old. + +- Adam Ginsburg +- Anders Boesen Lindbo Larsen +- Andreas Mueller +- Christoph Gohlke +- Christos Psaltis +- Colin Lea +- François Boulogne +- Jan Margeta +- Johannes Schönberger +- Josh Warner (Mac) +- Juan Nunez-Iglesias +- Luis Pedro Coelho +- Marianne Corvellec +- Matt McCormick +- Nicolas Pinto +- Olivier Debeir +- Paul Ivanov +- Sergey Karayev +- Stefan van der Walt +- Steven Silvester +- Thouis (Ray) Jones +- Tony S Yu From 96e5c1b1b8b1d78881ca860aea882c3e793d5bde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Mon, 4 Mar 2013 20:41:19 +0100 Subject: [PATCH 157/215] Update version number for 0.8 --- bento.info | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bento.info b/bento.info index 57d9f714..10ee84f2 100644 --- a/bento.info +++ b/bento.info @@ -1,5 +1,5 @@ Name: scikit-image -Version: 0.8.dev0 +Version: 0.8.0 Summary: Image processing routines for SciPy Url: http://scikit-image.org DownloadUrl: http://github.com/scikit-image/scikit-image diff --git a/setup.py b/setup.py index 0040534e..22971ba7 100644 --- a/setup.py +++ b/setup.py @@ -17,7 +17,7 @@ MAINTAINER_EMAIL = 'stefan@sun.ac.za' URL = 'http://scikit-image.org' LICENSE = 'Modified BSD' DOWNLOAD_URL = 'http://github.com/scikit-image/scikit-image' -VERSION = '0.8dev' +VERSION = '0.8.0' PYTHON_VERSION = (2, 5) DEPENDENCIES = { 'numpy': (1, 6), From ade23a2f7119b2d19876dacadb68df7f62ef339c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Mon, 4 Mar 2013 20:42:46 +0100 Subject: [PATCH 158/215] Update docversion for 0.8 --- doc/source/_static/docversions.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/_static/docversions.js b/doc/source/_static/docversions.js index 0b414325..fde9437b 100644 --- a/doc/source/_static/docversions.js +++ b/doc/source/_static/docversions.js @@ -1,5 +1,5 @@ function insert_version_links() { - var labels = ['dev', '0.7.0', '0.6', '0.5', '0.4', '0.3']; + var labels = ['dev', '0.8.0', '0.7.0', '0.6', '0.5', '0.4', '0.3']; for (i = 0; i < labels.length; i++){ open_list = '
  • ' From 5755ccb6129aa5fd76292ad56f550a2242246c26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Mon, 4 Mar 2013 20:46:09 +0100 Subject: [PATCH 159/215] Fix release instruction for making docs --- RELEASE.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASE.txt b/RELEASE.txt index ce05e24e..e42d1c85 100644 --- a/RELEASE.txt +++ b/RELEASE.txt @@ -12,7 +12,7 @@ How to make a new release of ``skimage`` - Edit ``doc/source/themes/agogo/static/docversions.js`` and commit - Build a clean version of the docs. Run ``make`` in the root dir, then - ``rm build -rf; make html`` in the docs. + ``rm -rf build; make html`` in the docs. - Run ``make html`` again to copy the newly generated ``random.js`` into place. Double check ``random.js``, otherwise the skimage.org front page gets broken! From 4854dc830f0aeb4b771b33c9fcdd19dcafd2bb50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Mon, 4 Mar 2013 21:22:20 +0100 Subject: [PATCH 160/215] Increase version number to 0.9 --- bento.info | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bento.info b/bento.info index 10ee84f2..71ef558e 100644 --- a/bento.info +++ b/bento.info @@ -1,5 +1,5 @@ Name: scikit-image -Version: 0.8.0 +Version: 0.9.dev0 Summary: Image processing routines for SciPy Url: http://scikit-image.org DownloadUrl: http://github.com/scikit-image/scikit-image diff --git a/setup.py b/setup.py index 22971ba7..d62a0d86 100644 --- a/setup.py +++ b/setup.py @@ -17,7 +17,7 @@ MAINTAINER_EMAIL = 'stefan@sun.ac.za' URL = 'http://scikit-image.org' LICENSE = 'Modified BSD' DOWNLOAD_URL = 'http://github.com/scikit-image/scikit-image' -VERSION = '0.8.0' +VERSION = '0.9dev' PYTHON_VERSION = (2, 5) DEPENDENCIES = { 'numpy': (1, 6), From 19e938a3deb4562cecbebd484afb2cd201cf6b83 Mon Sep 17 00:00:00 2001 From: Christoph Gohlke Date: Mon, 4 Mar 2013 17:29:44 -0800 Subject: [PATCH 161/215] Fix `ValueError: Buffer dtype mismatch` on win-amd64 --- skimage/morphology/_convex_hull.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skimage/morphology/_convex_hull.pyx b/skimage/morphology/_convex_hull.pyx index e4b6470c..7298e1ed 100644 --- a/skimage/morphology/_convex_hull.pyx +++ b/skimage/morphology/_convex_hull.pyx @@ -31,7 +31,7 @@ def possible_hull(cnp.ndarray[dtype=cnp.uint8_t, ndim=2, mode="c"] img): # rows storage slots for right boundary pixels # cols storage slots for bottom boundary pixels cdef cnp.ndarray[dtype=cnp.intp_t, ndim=2] nonzero = \ - np.ones((2 * (rows + cols), 2), dtype=np.int) + np.ones((2 * (rows + cols), 2), dtype=np.intp) nonzero *= -1 for r in range(rows): From dda83fdb5586d69b06043d57ba312a12f277c05c Mon Sep 17 00:00:00 2001 From: Christoph Gohlke Date: Mon, 4 Mar 2013 17:30:40 -0800 Subject: [PATCH 162/215] Fix `ValueError: Buffer dtype mismatch` on win-amd64 --- skimage/segmentation/_felzenszwalb_cy.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skimage/segmentation/_felzenszwalb_cy.pyx b/skimage/segmentation/_felzenszwalb_cy.pyx index f285e3db..b525a0d9 100644 --- a/skimage/segmentation/_felzenszwalb_cy.pyx +++ b/skimage/segmentation/_felzenszwalb_cy.pyx @@ -73,7 +73,7 @@ def _felzenszwalb_grey(image, double scale=1, sigma=0.8, Py_ssize_t min_size=20) cdef cnp.intp_t *edges_p = edges.data cdef cnp.float_t *costs_p = costs.data cdef cnp.ndarray[cnp.intp_t, ndim=1] segment_size \ - = np.ones(width * height, dtype=np.int) + = np.ones(width * height, dtype=np.intp) # inner cost of segments cdef cnp.ndarray[cnp.float_t, ndim=1] cint = np.zeros(width * height) cdef int seg0, seg1, seg_new, e From 7ca08cf7630787201636da4ea4409741b558d613 Mon Sep 17 00:00:00 2001 From: Christoph Gohlke Date: Mon, 4 Mar 2013 17:32:22 -0800 Subject: [PATCH 163/215] Fix `ValueError: Buffer dtype mismatch` on win-amd64 --- skimage/transform/hough_transform.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skimage/transform/hough_transform.py b/skimage/transform/hough_transform.py index 17b76826..e83cecd5 100644 --- a/skimage/transform/hough_transform.py +++ b/skimage/transform/hough_transform.py @@ -164,7 +164,7 @@ def hough_circle(img, radius, normalize=True): Hough transform accumulator for each radius """ - return _hough_circle(img, radius, normalize) + return _hough_circle(img, radius.astype(np.intp), normalize) def hough_peaks(hspace, angles, dists, min_distance=10, min_angle=10, threshold=None, num_peaks=np.inf): From 97fd434314b2a42af50d39e1ed11b5a2bfaab5a9 Mon Sep 17 00:00:00 2001 From: Christoph Gohlke Date: Mon, 4 Mar 2013 17:33:15 -0800 Subject: [PATCH 164/215] Fix `ValueError: Buffer dtype mismatch` on win-amd64 --- skimage/draw/_draw.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skimage/draw/_draw.pyx b/skimage/draw/_draw.pyx index 8a2572c4..f14fb1b2 100644 --- a/skimage/draw/_draw.pyx +++ b/skimage/draw/_draw.pyx @@ -281,7 +281,7 @@ def circle_perimeter(Py_ssize_t cy, Py_ssize_t cx, Py_ssize_t radius, y = y - 1 x = x + 1 - return np.array(rr) + cy, np.array(cc) + cx + return np.array(rr, dtype=np.intp) + cy, np.array(cc, dtype=np.intp) + cx def ellipse_perimeter(Py_ssize_t cy, Py_ssize_t cx, Py_ssize_t yradius, From 211d64644f92d1e01a5d0196d85f8997c0ce44ed Mon Sep 17 00:00:00 2001 From: Christoph Gohlke Date: Mon, 4 Mar 2013 22:45:46 -0800 Subject: [PATCH 165/215] Use np.intp type for indices --- skimage/draw/_draw.pyx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/skimage/draw/_draw.pyx b/skimage/draw/_draw.pyx index f14fb1b2..eb9293d5 100644 --- a/skimage/draw/_draw.pyx +++ b/skimage/draw/_draw.pyx @@ -126,7 +126,7 @@ def polygon(y, x, shape=None): rr.append(r) cc.append(c) - return np.array(rr), np.array(cc) + return np.array(rr, dtype=np.intp), np.array(cc, dtype=np.intp) def ellipse(double cy, double cx, double yradius, double xradius, shape=None): @@ -174,7 +174,7 @@ def ellipse(double cy, double cx, double yradius, double xradius, shape=None): rr.append(r) cc.append(c) - return np.array(rr), np.array(cc) + return np.array(rr, dtype=np.intp), np.array(cc, dtype=np.intp) def circle(double cy, double cx, double radius, shape=None): @@ -368,7 +368,7 @@ def ellipse_perimeter(Py_ssize_t cy, Py_ssize_t cx, Py_ssize_t yradius, err += ychange ychange += twobsquared - return np.array(py) + cy, np.array(px) + cx + return np.array(py, dtype=np.intp) + cy, np.array(px, dtype=np.intp) + cx def set_color(img, coords, color): From a4654b4b4517b1a049a27903d0a5951b333af35e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Tue, 5 Mar 2013 08:51:30 +0100 Subject: [PATCH 166/215] Add mailing list adresses to release instructions --- RELEASE.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/RELEASE.txt b/RELEASE.txt index e42d1c85..dfde6209 100644 --- a/RELEASE.txt +++ b/RELEASE.txt @@ -51,6 +51,12 @@ How to make a new release of ``skimage`` - Post release notes on mailing lists, blog, G+, etc. + - scikit-image@googlegroups.com + - scipy-user@scipy.org + - scikit-learn-general@lists.sourceforge.net + - pythonvision@googlegroups.com + + Debian ------ From 631a3bc687322e8f9acfa8c935bf0b2515ac98cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Tue, 5 Mar 2013 08:55:37 +0100 Subject: [PATCH 167/215] Add release instruction to update dev docs --- RELEASE.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/RELEASE.txt b/RELEASE.txt index dfde6209..1131a91a 100644 --- a/RELEASE.txt +++ b/RELEASE.txt @@ -49,6 +49,8 @@ How to make a new release of ``skimage`` - Build using ``make gh-pages``. - Push upstream: ``git push`` in ``gh-pages``. +- Update the development docs for the new version ``0.Xdev`` just like above + - Post release notes on mailing lists, blog, G+, etc. - scikit-image@googlegroups.com From e6ee2c0db607c09454f630d6f10372b4e12e81db Mon Sep 17 00:00:00 2001 From: "Thouis (Ray) Jones" Date: Tue, 5 Mar 2013 10:37:56 -0500 Subject: [PATCH 168/215] BUG: not all histogram memory was aligned correctly --- skimage/filter/_ctmf.pyx | 24 +++++++++++------------- skimage/filter/setup.py | 3 ++- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/skimage/filter/_ctmf.pyx b/skimage/filter/_ctmf.pyx index 1a03ff5a..7cb5251f 100644 --- a/skimage/filter/_ctmf.pyx +++ b/skimage/filter/_ctmf.pyx @@ -20,7 +20,7 @@ from libc.stdlib cimport malloc, free from libc.string cimport memset -cdef extern from "../_shared/vectorized_ops.h": +cdef extern from "vectorized_ops.h": void add16(cnp.uint16_t *dest, cnp.uint16_t *src) void sub16(cnp.uint16_t *dest, cnp.uint16_t *src) @@ -91,8 +91,9 @@ cdef struct SCoord: Py_ssize_t y cdef struct Histograms: - void *memory # pointer to the allocated memory - Histogram *histogram # pointer to the histogram memory + HistogramPiece accumulator # running histogram (32-byte aligned) + void *memory # pointer to the unaligned allocated memory + Histogram *histogram # pointer to the histogram memory (aligned) PixelCount *pixel_count # pointer to the pixel count memory cnp.uint8_t *data # pointer to the image data cnp.uint8_t *mask # pointer to the image mask @@ -142,9 +143,6 @@ cdef struct Histograms: Py_ssize_t row_stride # stride between one row and the next Py_ssize_t col_stride # stride between one column and the next - # The accumulator holds the running histogram - # - HistogramPiece accumulator # # The running count of pixels in the accumulator # @@ -188,10 +186,12 @@ cdef Histograms *allocate_histograms(Py_ssize_t rows, memory_size = (adjusted_stripe_length * (sizeof(Histogram) + sizeof(PixelCount)) + - sizeof(Histograms) + 32) + sizeof(Histograms) + 64) ptr = malloc(memory_size) memset(ptr, 0, memory_size) - ph = ptr + # align ph.accumulator to 32-byte boundary + roundoff = ( ptr + 31) % 32 + ph = (ptr + 31 - roundoff) if not ptr: return ph ph.memory = ptr @@ -201,10 +201,8 @@ cdef Histograms *allocate_histograms(Py_ssize_t rows, # # Align histogram memory to a 32-byte boundary # - roundoff = ptr - roundoff += 31 - roundoff -= roundoff % 32 - ptr = roundoff + roundoff = ( ptr + 31) % 32 + ptr += 31 - roundoff ph.histogram = ptr # # Fill in the statistical things we keep around @@ -696,7 +694,7 @@ cdef int c_median_filter(Py_ssize_t rows, # # Initialize the accumulator (octagon histogram) to zero # - memset(&ph.accumulator, 0, sizeof(ph.accumulator)) + memset(&(ph.accumulator), 0, sizeof(ph.accumulator)) ph.accumulator_count = 0 for i in range(16): ph.last_update_column[i] = -radius-1 diff --git a/skimage/filter/setup.py b/skimage/filter/setup.py index 934d3f2e..dba8172c 100644 --- a/skimage/filter/setup.py +++ b/skimage/filter/setup.py @@ -26,7 +26,8 @@ def configuration(parent_package='', top_path=None): cython(['rank/bilateral_rank.pyx'], working_path=base_path) config.add_extension('_ctmf', sources=['_ctmf.c'], - include_dirs=[get_numpy_include_dirs()]) + depends=['../_shared/vectorized_ops.h'], + include_dirs=[get_numpy_include_dirs(), '../_shared']) config.add_extension('_denoise_cy', sources=['_denoise_cy.c'], include_dirs=[get_numpy_include_dirs(), '../_shared']) config.add_extension('rank._core8', sources=['rank/_core8.c'], From 44350f56c99dcfc9ddf13dda5ab660697dc579f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Wed, 6 Mar 2013 16:42:18 +0100 Subject: [PATCH 169/215] Fix vectorized_ops for bento --- skimage/filter/_ctmf.pyx | 2 +- skimage/filter/setup.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/skimage/filter/_ctmf.pyx b/skimage/filter/_ctmf.pyx index 7cb5251f..302ab4c6 100644 --- a/skimage/filter/_ctmf.pyx +++ b/skimage/filter/_ctmf.pyx @@ -20,7 +20,7 @@ from libc.stdlib cimport malloc, free from libc.string cimport memset -cdef extern from "vectorized_ops.h": +cdef extern from "../_shared/vectorized_ops.h": void add16(cnp.uint16_t *dest, cnp.uint16_t *src) void sub16(cnp.uint16_t *dest, cnp.uint16_t *src) diff --git a/skimage/filter/setup.py b/skimage/filter/setup.py index dba8172c..b1d070fc 100644 --- a/skimage/filter/setup.py +++ b/skimage/filter/setup.py @@ -26,8 +26,7 @@ def configuration(parent_package='', top_path=None): cython(['rank/bilateral_rank.pyx'], working_path=base_path) config.add_extension('_ctmf', sources=['_ctmf.c'], - depends=['../_shared/vectorized_ops.h'], - include_dirs=[get_numpy_include_dirs(), '../_shared']) + include_dirs=[get_numpy_include_dirs()]) config.add_extension('_denoise_cy', sources=['_denoise_cy.c'], include_dirs=[get_numpy_include_dirs(), '../_shared']) config.add_extension('rank._core8', sources=['rank/_core8.c'], From 387c72b018f1fa08babf0543b0b63d4758c48d65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Wed, 6 Mar 2013 16:43:41 +0100 Subject: [PATCH 170/215] Remove colon as multi-line comment symbol --- skimage/draw/_draw.pyx | 6 +++--- skimage/measure/tests/test_regionprops.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/skimage/draw/_draw.pyx b/skimage/draw/_draw.pyx index eb9293d5..4316cadc 100644 --- a/skimage/draw/_draw.pyx +++ b/skimage/draw/_draw.pyx @@ -109,14 +109,14 @@ def polygon(y, x, shape=None): cdef Py_ssize_t r, c - #: make contigous arrays for r, c coordinates + # make contigous arrays for r, c coordinates cdef cnp.ndarray contiguous_rdata, contiguous_cdata contiguous_rdata = np.ascontiguousarray(y, 'double') contiguous_cdata = np.ascontiguousarray(x, 'double') cdef cnp.double_t* rptr = contiguous_rdata.data cdef cnp.double_t* cptr = contiguous_cdata.data - #: output coordinate arrays + # output coordinate arrays cdef list rr = list() cdef list cc = list() @@ -164,7 +164,7 @@ def ellipse(double cy, double cx, double yradius, double xradius, shape=None): cdef Py_ssize_t r, c - #: output coordinate arrays + # output coordinate arrays cdef list rr = list() cdef list cc = list() diff --git a/skimage/measure/tests/test_regionprops.py b/skimage/measure/tests/test_regionprops.py index 8185bf67..c1396670 100644 --- a/skimage/measure/tests/test_regionprops.py +++ b/skimage/measure/tests/test_regionprops.py @@ -49,7 +49,7 @@ def test_bbox(): def test_central_moments(): mu = regionprops(SAMPLE, ['CentralMoments'])[0]['CentralMoments'] - #: determined with OpenCV + # determined with OpenCV assert_almost_equal(mu[0,2], 436.00000000000045) # different from OpenCV results, bug in OpenCV assert_almost_equal(mu[0,3], -737.333333333333) @@ -198,7 +198,7 @@ def test_minor_axis_length(): def test_moments(): m = regionprops(SAMPLE, ['Moments'])[0]['Moments'] - #: determined with OpenCV + # determined with OpenCV assert_almost_equal(m[0,0], 72.0) assert_almost_equal(m[0,1], 408.0) assert_almost_equal(m[0,2], 2748.0) @@ -213,7 +213,7 @@ def test_moments(): def test_normalized_moments(): nu = regionprops(SAMPLE, ['NormalizedMoments'])[0]['NormalizedMoments'] - #: determined with OpenCV + # determined with OpenCV assert_almost_equal(nu[0,2], 0.08410493827160502) assert_almost_equal(nu[1,1], -0.016846707818929982) assert_almost_equal(nu[1,2], -0.002899800614433943) From 45f53c9e3a7e930bb844b24ee7491e429b4ea39c Mon Sep 17 00:00:00 2001 From: Christoph Gohlke Date: Wed, 6 Mar 2013 10:09:59 -0800 Subject: [PATCH 171/215] Fix MSVC error C2036: 'void *' : unknown size --- skimage/filter/_ctmf.pyx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/skimage/filter/_ctmf.pyx b/skimage/filter/_ctmf.pyx index 302ab4c6..e3e845e4 100644 --- a/skimage/filter/_ctmf.pyx +++ b/skimage/filter/_ctmf.pyx @@ -191,7 +191,7 @@ cdef Histograms *allocate_histograms(Py_ssize_t rows, memset(ptr, 0, memory_size) # align ph.accumulator to 32-byte boundary roundoff = ( ptr + 31) % 32 - ph = (ptr + 31 - roundoff) + ph = ( ptr + 31 - roundoff) if not ptr: return ph ph.memory = ptr @@ -202,7 +202,7 @@ cdef Histograms *allocate_histograms(Py_ssize_t rows, # Align histogram memory to a 32-byte boundary # roundoff = ( ptr + 31) % 32 - ptr += 31 - roundoff + ptr = ( ptr + 31 - roundoff) ph.histogram = ptr # # Fill in the statistical things we keep around From 7439fe96e854db26915b47bbb631b2459b2d4ba4 Mon Sep 17 00:00:00 2001 From: spotter Date: Thu, 7 Mar 2013 00:14:06 +0100 Subject: [PATCH 172/215] FIX: Testing for ubyte and float images. Input dtypes are converted beforehand. --- skimage/color/colorconv.py | 3 ++- skimage/color/tests/test_colorconv.py | 17 +++++++---------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/skimage/color/colorconv.py b/skimage/color/colorconv.py index 0ff4c722..8f44880c 100644 --- a/skimage/color/colorconv.py +++ b/skimage/color/colorconv.py @@ -769,7 +769,7 @@ def rgb2hed(rgb): >>> ihc = data.ihc() >>> ihc_hed = rgb2hed(ihc) """ - rgb = rgb.astype(float) + 1 + rgb = dtype.img_as_float(rgb) + 1 hed = np.dot(np.reshape(-np.log(rgb), (-1, 3)), hed_from_rgb) return np.reshape(hed, rgb.shape) @@ -807,6 +807,7 @@ def hed2rgb(hed): >>> ihc_hed = rgb2hed(ihc) >>> ihc_rgb = hed2rgb(ihc_hed) """ + hed = dtype.img_as_float(hed) logrgb1 = np.dot(-np.reshape(hed, (-1, 3)), rgb_from_hed) rgb1 = np.exp(logrgb1) return np.reshape(rgb1 - 1, hed.shape) diff --git a/skimage/color/tests/test_colorconv.py b/skimage/color/tests/test_colorconv.py index 7e76ded3..3171028d 100644 --- a/skimage/color/tests/test_colorconv.py +++ b/skimage/color/tests/test_colorconv.py @@ -122,17 +122,14 @@ class TestColorconv(TestCase): img_rgb = img_as_float(self.img_rgb) assert_array_almost_equal(xyz2rgb(rgb2xyz(img_rgb)), img_rgb) - # RGB<->HED roundtrip + # RGB<->HED roundtrip with ubyte image def test_hed_rgb_roundtrip(self): - img_rgb = np.array([[[97, 105, 152], - [217, 187, 225], - [117, 107, 168]], - [[217, 187, 225], - [176, 135, 83], - [234, 144, 205]], - [[97, 105, 152], - [117, 107, 168], - [217, 187, 225]]], dtype=np.uint8) + img_rgb = self.img_rgb + assert_array_almost_equal(hed2rgb(rgb2hed(img_rgb)), img_as_float(img_rgb)) + + # RGB<->HED roundtrip with float image + def test_hed_rgb_float_roundtrip(self): + img_rgb = img_as_float(self.img_rgb) assert_array_almost_equal(hed2rgb(rgb2hed(img_rgb)), img_rgb) # RGB to RGB CIE From 70e37f305559536b5d2bc8b5fc775a074d6dec58 Mon Sep 17 00:00:00 2001 From: Tony S Yu Date: Wed, 6 Mar 2013 23:03:01 -0600 Subject: [PATCH 173/215] Refactor code that generates scikit-image logo. Simplify code and use skimage functions that didn't exist when this code was originally written. --- doc/logo/scikit_image_logo.py | 210 ++++++++++++---------------------- doc/logo/scipy_logo.py | 16 +-- 2 files changed, 81 insertions(+), 145 deletions(-) diff --git a/doc/logo/scikit_image_logo.py b/doc/logo/scikit_image_logo.py index de460344..dab3f6bb 100644 --- a/doc/logo/scikit_image_logo.py +++ b/doc/logo/scikit_image_logo.py @@ -1,17 +1,26 @@ """ Script to draw skimage logo using Scipy logo as stencil. The easiest -starting point is the `plot_colorized_logo`; the "if-main" demonstrates its use. +starting point is the `plot_colorized_logo`. Original snake image from pixabay [1]_ .. [1] http://pixabay.com/en/snake-green-toxic-close-yellow-3237/ """ -import numpy as np +import sys +if len(sys.argv) != 2 or sys.argv[1] != '--no-plot': + print "Run with '--no-plot' flag to generate logo silently." +else: + import matplotlib as mpl + mpl.use('Agg') import matplotlib.pyplot as plt -import scipy.misc + +import numpy as np import skimage.io as sio -import skimage.filter as imfilt +from skimage import img_as_float +from skimage.color import gray2rgb, rgb2gray +from skimage.exposure import rescale_intensity +from skimage.filter import sobel import scipy_logo @@ -19,42 +28,21 @@ import scipy_logo # Utility functions # ================= -def get_edges(img): - edge = np.empty(img.shape) - if len(img.shape) == 3: - for i in range(3): - edge[:, :, i] = imfilt.sobel(img[:, :, i]) - else: - edge = imfilt.sobel(img) - edge = rescale_intensity(edge) - return edge +def colorize(image, color, whiten=False): + """Return colorized image from gray scale image. -def rescale_intensity(img): - i_range = float(img.max() - img.min()) - img = (img - img.min()) / i_range * 255 - return np.uint8(img) - -def colorize(img, color, whiten=False): - """Return colorized image from gray scale image - - Parameters - ---------- - img : N x M array - grayscale image - color : length-3 sequence of floats - RGB color spec. Float values should be between 0 and 1. - whiten : bool - If True, a color value less than 1 increases the image intensity. + The colorized image has values from ranging between black at the lowest + intensity to `color` at the highest. If `whiten=True`, then the color + ranges from `color` to white. """ color = np.asarray(color)[np.newaxis, np.newaxis, :] - img = img[:, :, np.newaxis] + image = image[:, :, np.newaxis] if whiten: # truncate and stretch intensity range to enhance contrast - img = np.clip(img, 80, 255) - img = rescale_intensity(img) - return np.uint8(color * (255 - img) + img) + image = rescale_intensity(image, in_range=(0.3, 1)) + return color * (1 - image) + image else: - return np.uint8(img * color) + return image * color def prepare_axes(ax): @@ -65,16 +53,6 @@ def prepare_axes(ax): spine.set_visible(False) -_rgb_stack = np.ones((1, 1, 3), dtype=bool) -def gray2rgb(arr): - """Return RGB image from a grayscale image. - - Expand h x w image to h x w x 3 image where color channels are simply copies - of the grayscale image. - """ - return arr[:, :, np.newaxis] * _rgb_stack - - # Logo generating classes # ======================= @@ -82,21 +60,17 @@ class LogoBase(object): def __init__(self): self.logo = scipy_logo.ScipyLogo(radius=self.radius) - self.mask_1 = self.logo.get_mask(self.img.shape, 'upper left') - self.mask_2 = self.logo.get_mask(self.img.shape, 'lower right') - self.edges = get_edges(self.img) + self.mask_1 = self.logo.get_mask(self.image.shape, 'upper left') + self.mask_2 = self.logo.get_mask(self.image.shape, 'lower right') + + edges = np.array([sobel(img) for img in self.image.T]).T # truncate and stretch intensity range to enhance contrast - self.edges = np.clip(self.edges, 0, 100) - self.edges = rescale_intensity(self.edges) + self.edges = rescale_intensity(edges, in_range=(0, 0.4)) - - def _crop_image(self, img): + def _crop_image(self, image): w = 2 * self.radius x, y = self.origin - return img[y:y+w, x:x+w] - - def get_canvas(self): - return 255 * np.ones(self.img.shape, dtype=np.uint8) + return image[y:y+w, x:x+w] def plot_curve(self, **kwargs): self.logo.plot_snake_curve(**kwargs) @@ -104,15 +78,13 @@ class LogoBase(object): class SnakeLogo(LogoBase): - def __init__(self): - self.radius = 250 - self.origin = (420, 0) - img = sio.imread('data/snake_pixabay.jpg') - img = self._crop_image(img) + radius = 250 + origin = (420, 0) - img = img.astype(float) * 1.1 - img[img > 255] = 255 - self.img = img.astype(np.uint8) + def __init__(self): + image = sio.imread('data/snake_pixabay.jpg') + image = self._crop_image(image) + self.image = img_as_float(image) LogoBase.__init__(self) @@ -120,107 +92,75 @@ class SnakeLogo(LogoBase): snake_color = SnakeLogo() snake = SnakeLogo() # turn RGB image into gray image -snake.img = np.mean(snake.img, axis=2) -snake.edges = np.mean(snake.edges, axis=2) +snake.image = rgb2gray(snake.image) +snake.edges = rgb2gray(snake.edges) # Demo plotting functions # ======================= -def plot_colorized_logo(logo, color, edges='light', switch=False, whiten=False): - """Convenience function to plot artificially colored logo. +def plot_colorized_logo(logo, color, edges='light', whiten=False): + """Convenience function to plot artificially-colored logo. + + The upper-left half of the logo is an edge filtered image, while the + lower-right half is unfiltered. Parameters ---------- - logo : subclass of LogoBase - color : length-3 sequence of floats + logo : LogoBase instance + color : length-3 sequence of floats or 2 length-3 sequences RGB color spec. Float values should be between 0 and 1. edges : {'light'|'dark'} Specifies whether Sobel edges are drawn light or dark - switch : bool - If False, the image is drawn on the southeast half of the Scipy curve - and the edge image is drawn on northwest half. - whiten : bool + whiten : bool or 2 bools If True, a color value less than 1 increases the image intensity. """ if not hasattr(color[0], '__iter__'): - color = [color] * 2 + color = [color] * 2 # use same color for upper-left and lower-right if not hasattr(whiten, '__iter__'): - whiten = [whiten] * 2 - img = gray2rgb(logo.get_canvas()) + whiten = [whiten] * 2 # use same setting for upper-left and lower-right + + image = gray2rgb(np.ones_like(logo.image)) mask_img = gray2rgb(logo.mask_2) mask_edge = gray2rgb(logo.mask_1) - if switch: - mask_img, mask_edge = mask_edge, mask_img + + # Compose image with colorized image and edge-image. if edges == 'dark': - lg_edge = colorize(255 - logo.edges, color[0], whiten=whiten[0]) + logo_edge = colorize(1 - logo.edges, color[0], whiten=whiten[0]) else: - lg_edge = colorize(logo.edges, color[0], whiten=whiten[0]) - lg_img = colorize(logo.img, color[1], whiten=whiten[1]) - img[mask_img] = lg_img[mask_img] - img[mask_edge] = lg_edge[mask_edge] - logo.plot_curve(lw=5, color='w') - plt.imshow(img) + logo_edge = colorize(logo.edges, color[0], whiten=whiten[0]) + logo_img = colorize(logo.image, color[1], whiten=whiten[1]) + image[mask_img] = logo_img[mask_img] + image[mask_edge] = logo_edge[mask_edge] - -def red_light_edges(logo, **kwargs): - plot_colorized_logo(logo, (1, 0, 0), edges='light', **kwargs) - - -def red_dark_edges(logo, **kwargs): - plot_colorized_logo(logo, (1, 0, 0), edges='dark', **kwargs) - -def blue_light_edges(logo, **kwargs): - plot_colorized_logo(logo, (0.35, 0.55, 0.85), edges='light', **kwargs) - - -def blue_dark_edges(logo, **kwargs): - plot_colorized_logo(logo, (0.35, 0.55, 0.85), edges='dark', **kwargs) - - -def green_orange_light_edges(logo, **kwargs): - colors = ((0.6, 0.8, 0.3), (1, 0.5, 0.1)) - plot_colorized_logo(logo, colors, edges='light', **kwargs) - -def green_orange_dark_edges(logo, **kwargs): - colors = ((0.6, 0.8, 0.3), (1, 0.5, 0.1)) - plot_colorized_logo(logo, colors, edges='dark', **kwargs) + logo.plot_curve(lw=5, color='w') # plot snake curve on current axes + plt.imshow(image) if __name__ == '__main__': - - import sys - plot = False - if len(sys.argv) < 2 or sys.argv[1] != '--no-plot': - plot = True - - print "Run with '--no-plot' flag to generate logo silently." + # Colors to use for the logo: + red = (1, 0, 0) + blue = (0.35, 0.55, 0.85) + green_orange = ((0.6, 0.8, 0.3), (1, 0.5, 0.1)) def plot_all(): - plotters = (red_light_edges, red_dark_edges, - blue_light_edges, blue_dark_edges, - green_orange_light_edges, green_orange_dark_edges) - - f, axes_array = plt.subplots(nrows=2, ncols=len(plotters)) - for plot, ax_col in zip(plotters, axes_array.T): - prepare_axes(ax_col[0]) - plot(snake) - prepare_axes(ax_col[1]) - plot(snake, whiten=True) + color_list = [red, blue, green_orange] + edge_list = ['light', 'dark'] + f, axes = plt.subplots(nrows=len(edge_list), ncols=len(color_list)) + for axes_row, edges in zip(axes, edge_list): + for ax, color in zip(axes_row, color_list): + prepare_axes(ax) + plot_colorized_logo(snake, color, edges=edges) plt.tight_layout() - def plot_snake(): - + def plot_official_logo(): f, ax = plt.subplots() prepare_axes(ax) - green_orange_dark_edges(snake, whiten=(False, True)) + plot_colorized_logo(snake, green_orange, edges='dark', + whiten=(False, True)) plt.savefig('green_orange_snake.png', bbox_inches='tight') - if plot: - plot_all() - - plot_snake() - - if plot: - plt.show() + plot_all() + plot_official_logo() + plt.show() diff --git a/doc/logo/scipy_logo.py b/doc/logo/scipy_logo.py index c9de053a..c57cd95b 100644 --- a/doc/logo/scipy_logo.py +++ b/doc/logo/scipy_logo.py @@ -3,10 +3,11 @@ Code used to trace Scipy logo. """ import numpy as np import matplotlib.pyplot as plt -import skimage.io as imgio -from scipy.misc import lena import matplotlib.nxutils as nx +from skimage import io +from skimage import data + class SymmetricAnchorPoint(object): """Anchor point in a parametric curve with symmetric handles @@ -185,7 +186,7 @@ class ScipyLogo(object): def plot_image(self, **kwargs): ax = kwargs.pop('ax', plt.gca()) - img = imgio.imread('data/scipy.png') + img = io.imread('data/scipy.png') ax.imshow(img, **kwargs) def get_mask(self, shape, region): @@ -236,9 +237,7 @@ def plot_snake_overlay(): logo = ScipyLogo((670, 250), 250) logo.plot_snake_curve() logo.plot_circle() - img = imgio.imread('data/snake_pixabay.jpg') - #mask = logo.get_mask(img.shape, 'upper left') - #img[mask] = 255 + img = io.imread('data/snake_pixabay.jpg') plt.imshow(img) @@ -247,9 +246,7 @@ def plot_lena_overlay(): logo = ScipyLogo((300, 300), 180) logo.plot_snake_curve() logo.plot_circle() - img = lena() - #mask = logo.get_mask(img.shape, 'upper left') - #img[mask] = 255 + img = data.lena() plt.imshow(img) @@ -259,4 +256,3 @@ if __name__ == '__main__': plot_lena_overlay() plt.show() - From ad0927f4592878e3317b9a6ec1ae03004d97c89f Mon Sep 17 00:00:00 2001 From: Stefan van der Walt Date: Thu, 7 Mar 2013 08:29:01 +0200 Subject: [PATCH 174/215] STY: PEP8 for logo. --- doc/logo/scikit_image_logo.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/logo/scikit_image_logo.py b/doc/logo/scikit_image_logo.py index dab3f6bb..fc8ec006 100644 --- a/doc/logo/scikit_image_logo.py +++ b/doc/logo/scikit_image_logo.py @@ -70,7 +70,7 @@ class LogoBase(object): def _crop_image(self, image): w = 2 * self.radius x, y = self.origin - return image[y:y+w, x:x+w] + return image[y:y + w, x:x + w] def plot_curve(self, **kwargs): self.logo.plot_snake_curve(**kwargs) @@ -116,9 +116,9 @@ def plot_colorized_logo(logo, color, edges='light', whiten=False): If True, a color value less than 1 increases the image intensity. """ if not hasattr(color[0], '__iter__'): - color = [color] * 2 # use same color for upper-left and lower-right + color = [color] * 2 # use same color for upper-left & lower-right if not hasattr(whiten, '__iter__'): - whiten = [whiten] * 2 # use same setting for upper-left and lower-right + whiten = [whiten] * 2 # use same setting for upper-left & lower-right image = gray2rgb(np.ones_like(logo.image)) mask_img = gray2rgb(logo.mask_2) @@ -133,7 +133,7 @@ def plot_colorized_logo(logo, color, edges='light', whiten=False): image[mask_img] = logo_img[mask_img] image[mask_edge] = logo_edge[mask_edge] - logo.plot_curve(lw=5, color='w') # plot snake curve on current axes + logo.plot_curve(lw=5, color='w') # plot snake curve on current axes plt.imshow(image) From 7c371e16623c1e0fb96e43e573ee72a8abc9c43f Mon Sep 17 00:00:00 2001 From: spotter Date: Fri, 8 Mar 2013 11:48:07 +0100 Subject: [PATCH 175/215] FIX: img_as_ubyte function threw ValueError due to floating point errors in hed2rgb function. Clipping the output of the hed2rgb function between 0 and 1 seems to correct the bug without affecting the conversion quality. --- skimage/color/colorconv.py | 4 +++- skimage/color/tests/test_colorconv.py | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/skimage/color/colorconv.py b/skimage/color/colorconv.py index 8f44880c..73beba14 100644 --- a/skimage/color/colorconv.py +++ b/skimage/color/colorconv.py @@ -807,7 +807,9 @@ def hed2rgb(hed): >>> ihc_hed = rgb2hed(ihc) >>> ihc_rgb = hed2rgb(ihc_hed) """ + from ..exposure import rescale_intensity + hed = dtype.img_as_float(hed) logrgb1 = np.dot(-np.reshape(hed, (-1, 3)), rgb_from_hed) rgb1 = np.exp(logrgb1) - return np.reshape(rgb1 - 1, hed.shape) + return rescale_intensity(np.reshape(rgb1 - 1, hed.shape), in_range=(0, 1)) diff --git a/skimage/color/tests/test_colorconv.py b/skimage/color/tests/test_colorconv.py index 3171028d..ddbfa594 100644 --- a/skimage/color/tests/test_colorconv.py +++ b/skimage/color/tests/test_colorconv.py @@ -16,7 +16,7 @@ import os.path import numpy as np from numpy.testing import * -from skimage import img_as_float +from skimage import img_as_float, img_as_ubyte from skimage.io import imread from skimage.color import ( rgb2hsv, hsv2rgb, @@ -125,7 +125,7 @@ class TestColorconv(TestCase): # RGB<->HED roundtrip with ubyte image def test_hed_rgb_roundtrip(self): img_rgb = self.img_rgb - assert_array_almost_equal(hed2rgb(rgb2hed(img_rgb)), img_as_float(img_rgb)) + assert_equal(img_as_ubyte(hed2rgb(rgb2hed(img_rgb))), img_rgb) # RGB<->HED roundtrip with float image def test_hed_rgb_float_roundtrip(self): From f66c8b5e3b773eb93e67c390a1e557bb94803a02 Mon Sep 17 00:00:00 2001 From: spotter Date: Fri, 8 Mar 2013 15:59:40 +0100 Subject: [PATCH 176/215] ENH: Added example to the gallery for ihc staining separation --- doc/examples/plot_ihc_color_separation.py | 70 +++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 doc/examples/plot_ihc_color_separation.py diff --git a/doc/examples/plot_ihc_color_separation.py b/doc/examples/plot_ihc_color_separation.py new file mode 100644 index 00000000..674e77c9 --- /dev/null +++ b/doc/examples/plot_ihc_color_separation.py @@ -0,0 +1,70 @@ +""" +============================================== +Immunohistochemical staining colors separation +============================================== + +In this example we separate the immunohistochemical (IHC) staining +from the hematoxylin counterstaining. The separation is achieved with the +method described in [1], known as "color deconvolution". + +The IHC staining expression of the FHL2 protein is here revealed with +Diaminobenzidine (DAB) which gives a brown color. + + +.. [1] A. C. Ruifrok and D. A. Johnston, “Quantification of histochemical + staining by color deconvolution.,” Analytical and quantitative + cytology and histology / the International Academy of Cytology [and] + American Society of Cytology, vol. 23, no. 4, pp. 291–9, Aug. 2001. +""" +import matplotlib.pyplot as plt + +from skimage import data +from skimage.color import rgb2hed + +ihc_rgb = data.immunohistochemistry() +ihc_hed = rgb2hed(ihc_rgb) + +fig, axes = plt.subplots(2, 2, figsize=(7, 6)) +ax0, ax1, ax2, ax3 = axes.ravel() + +ax0.imshow(ihc_rgb) +ax0.set_title("Original image") + +ax1.imshow(ihc_hed[:, :, 0], cmap=plt.cm.gray) +ax1.set_title("Hematoxylin") + +ax2.imshow(ihc_hed[:, :, 1], cmap=plt.cm.gray) +ax2.set_title("Eosin") + +ax3.imshow(ihc_hed[:, :, 2], cmap=plt.cm.gray) +ax3.set_title("DAB") + +for ax in axes.ravel(): + ax.axis('off') + +fig.subplots_adjust(hspace=0.3) + + +""" +.. image:: PLOT2RST.current_figure + +Now we can easily manipulate the hematoxylin and DAB "channels": +""" +import numpy as np + +from skimage.exposure import rescale_intensity + +# Rescale hematoxylin and DAB signals and give them a fluorescence look +h = rescale_intensity(ihc_hed[:, :, 0], out_range=(0, 1)) +d = rescale_intensity(ihc_hed[:, :, 2], out_range=(0, 1)) +zdh = np.dstack((np.zeros_like(h), d, h)) + +plt.figure() +plt.imshow(zdh) +plt.title("Stain separated image (rescaled)") +plt.axis('off') +plt.show() + +""" +.. image:: PLOT2RST.current_figure +""" From 5b25e7e9611230650ccf9d110276af21e22082f1 Mon Sep 17 00:00:00 2001 From: spotter Date: Sat, 9 Mar 2013 13:23:36 +0100 Subject: [PATCH 177/215] FIX: Replaced non-ascii double quotes in Ruifrok reference. --- doc/examples/plot_ihc_color_separation.py | 4 ++-- skimage/color/colorconv.py | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/doc/examples/plot_ihc_color_separation.py b/doc/examples/plot_ihc_color_separation.py index 674e77c9..b6c9ddf7 100644 --- a/doc/examples/plot_ihc_color_separation.py +++ b/doc/examples/plot_ihc_color_separation.py @@ -11,8 +11,8 @@ The IHC staining expression of the FHL2 protein is here revealed with Diaminobenzidine (DAB) which gives a brown color. -.. [1] A. C. Ruifrok and D. A. Johnston, “Quantification of histochemical - staining by color deconvolution.,” Analytical and quantitative +.. [1] A. C. Ruifrok and D. A. Johnston, "Quantification of histochemical + staining by color deconvolution.," Analytical and quantitative cytology and histology / the International Academy of Cytology [and] American Society of Cytology, vol. 23, no. 4, pp. 291–9, Aug. 2001. """ diff --git a/skimage/color/colorconv.py b/skimage/color/colorconv.py index 73beba14..072fc995 100644 --- a/skimage/color/colorconv.py +++ b/skimage/color/colorconv.py @@ -316,7 +316,7 @@ lab_ref_white = np.array([0.95047, 1., 1.08883]) # Haematoxylin-Eosin-DAB colorspace # From original Ruifrok's paper: A. C. Ruifrok and D. A. Johnston, -# “Quantification of histochemical staining by color deconvolution.,” +# "Quantification of histochemical staining by color deconvolution.," # Analytical and quantitative cytology and histology / the International # Academy of Cytology [and] American Society of Cytology, vol. 23, no. 4, # pp. 291–9, Aug. 2001. @@ -757,8 +757,8 @@ def rgb2hed(rgb): References ---------- - .. [1] A. C. Ruifrok and D. A. Johnston, “Quantification of histochemical - staining by color deconvolution.,” Analytical and quantitative + .. [1] A. C. Ruifrok and D. A. Johnston, "Quantification of histochemical + staining by color deconvolution.," Analytical and quantitative cytology and histology / the International Academy of Cytology [and] American Society of Cytology, vol. 23, no. 4, pp. 291–9, Aug. 2001. @@ -794,8 +794,8 @@ def hed2rgb(hed): References ---------- - .. [1] A. C. Ruifrok and D. A. Johnston, “Quantification of histochemical - staining by color deconvolution.,” Analytical and quantitative + .. [1] A. C. Ruifrok and D. A. Johnston, "Quantification of histochemical + staining by color deconvolution.," Analytical and quantitative cytology and histology / the International Academy of Cytology [and] American Society of Cytology, vol. 23, no. 4, pp. 291–9, Aug. 2001. From b306f9b70fea71d953ee39ed734f38327e7317cc Mon Sep 17 00:00:00 2001 From: Juan Nunez-Iglesias Date: Sun, 10 Mar 2013 19:23:04 +1100 Subject: [PATCH 178/215] Revert remove_small_objects to raise TypeError --- skimage/morphology/misc.py | 8 +++++--- skimage/morphology/tests/test_misc.py | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/skimage/morphology/misc.py b/skimage/morphology/misc.py index 6274df53..db0136cd 100644 --- a/skimage/morphology/misc.py +++ b/skimage/morphology/misc.py @@ -21,8 +21,10 @@ def remove_small_objects(ar, min_size=64, connectivity=1, in_place=False): Raises ------ - ValueError + TypeError If the input array is of an invalid type, such as float or string. + ValueError + If the input array contains negative values. Returns ------- @@ -50,9 +52,9 @@ def remove_small_objects(ar, min_size=64, connectivity=1, in_place=False): >>> d is a True """ - # Should use `issubdtype` below, but there's a bug in numpy 1.7 + # Should use `issubdtype` for bool below, but there's a bug in numpy 1.7 if not (ar.dtype == bool or np.issubdtype(ar.dtype, int)): - raise ValueError("Only bool or integer image types are supported. " + raise TypeError("Only bool or integer image types are supported. " "Got %s." % ar.dtype) if in_place: diff --git a/skimage/morphology/tests/test_misc.py b/skimage/morphology/tests/test_misc.py index abc718c2..a5ab7ba9 100644 --- a/skimage/morphology/tests/test_misc.py +++ b/skimage/morphology/tests/test_misc.py @@ -44,7 +44,7 @@ def test_labeled_image(): def test_float_input(): float_test = np.random.rand(5, 5) - assert_raises(ValueError, remove_small_objects, float_test) + assert_raises(TypeError, remove_small_objects, float_test) def test_negative_input(): From 0fd2f355b2e77db62a5480e256b011f0c0ed2d25 Mon Sep 17 00:00:00 2001 From: Xavier Moles Lopez Date: Mon, 11 Mar 2013 16:14:32 +0100 Subject: [PATCH 179/215] FIX: Link to Ruifrok reference --- doc/examples/plot_ihc_color_separation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/examples/plot_ihc_color_separation.py b/doc/examples/plot_ihc_color_separation.py index b6c9ddf7..02dc6761 100644 --- a/doc/examples/plot_ihc_color_separation.py +++ b/doc/examples/plot_ihc_color_separation.py @@ -5,7 +5,7 @@ Immunohistochemical staining colors separation In this example we separate the immunohistochemical (IHC) staining from the hematoxylin counterstaining. The separation is achieved with the -method described in [1], known as "color deconvolution". +method described in [1]_, known as "color deconvolution". The IHC staining expression of the FHL2 protein is here revealed with Diaminobenzidine (DAB) which gives a brown color. From 483ebae0c0c425e90d4ff4f32fe02cb96cd11621 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Mon, 11 Mar 2013 23:18:54 +0100 Subject: [PATCH 180/215] Change deprecated decorator to display warning in doc string --- skimage/_shared/utils.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/skimage/_shared/utils.py b/skimage/_shared/utils.py index 4075ddb4..6518f40b 100644 --- a/skimage/_shared/utils.py +++ b/skimage/_shared/utils.py @@ -6,7 +6,7 @@ __all__ = ['deprecated'] class deprecated(object): - """Decorator to mark deprecated functions with warning. + '''Decorator to mark deprecated functions with warning. Adapted from . @@ -17,7 +17,7 @@ class deprecated(object): behavior : {'warn', 'raise'} Behavior during call to deprecated function: 'warn' = warn user that function is deprecated; 'raise' = raise error. - """ + ''' def __init__(self, alt_func=None, behavior='warn'): self.alt_func = alt_func @@ -25,9 +25,9 @@ class deprecated(object): def __call__(self, func): - msg = "Call to deprecated function `%s`." % func.__name__ + msg = 'Call to deprecated function `%s`.' % func.__name__ if self.alt_func is not None: - msg = msg + " Use `%s` instead." % self.alt_func + msg += ' Use `%s` instead.' % self.alt_func @functools.wraps(func) def wrapped(*args, **kwargs): @@ -40,4 +40,14 @@ class deprecated(object): raise DeprecationWarning(msg) return func(*args, **kwargs) + # modify doc string to display deprecation warning + doc = 'Deprecated function.' + if self.alt_func is not None: + doc += ' Use `%s` instead.' % self.alt_func + + if wrapped.__doc__ is None: + wrapped.__doc__ = doc + else: + wrapped.__doc__ = doc + '\n\n' + wrapped.__doc__ + return wrapped From 0cb0fee339883adeb93f787b5cc19e5293463c06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Mon, 11 Mar 2013 23:21:52 +0100 Subject: [PATCH 181/215] Remove duplicate code for alternative function --- skimage/_shared/utils.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/skimage/_shared/utils.py b/skimage/_shared/utils.py index 6518f40b..102d4214 100644 --- a/skimage/_shared/utils.py +++ b/skimage/_shared/utils.py @@ -25,9 +25,12 @@ class deprecated(object): def __call__(self, func): - msg = 'Call to deprecated function `%s`.' % func.__name__ + alt_msg = '' if self.alt_func is not None: - msg += ' Use `%s` instead.' % self.alt_func + alt_msg = ' Use `%s` instead.' % self.alt_func + + msg = 'Call to deprecated function `%s`.' % func.__name__ + msg += alt_msg @functools.wraps(func) def wrapped(*args, **kwargs): @@ -41,10 +44,7 @@ class deprecated(object): return func(*args, **kwargs) # modify doc string to display deprecation warning - doc = 'Deprecated function.' - if self.alt_func is not None: - doc += ' Use `%s` instead.' % self.alt_func - + doc = 'Deprecated function.' + alt_msg if wrapped.__doc__ is None: wrapped.__doc__ = doc else: From a0d9d3f213fb28914040e9647509c897ad8cc41a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Tue, 12 Mar 2013 07:28:15 +0100 Subject: [PATCH 182/215] Use double instead of single quotes for doc string --- skimage/_shared/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/skimage/_shared/utils.py b/skimage/_shared/utils.py index 102d4214..fc13c7e6 100644 --- a/skimage/_shared/utils.py +++ b/skimage/_shared/utils.py @@ -6,7 +6,7 @@ __all__ = ['deprecated'] class deprecated(object): - '''Decorator to mark deprecated functions with warning. + """Decorator to mark deprecated functions with warning. Adapted from . @@ -17,7 +17,7 @@ class deprecated(object): behavior : {'warn', 'raise'} Behavior during call to deprecated function: 'warn' = warn user that function is deprecated; 'raise' = raise error. - ''' + """ def __init__(self, alt_func=None, behavior='warn'): self.alt_func = alt_func From 332c21f97936baab6901fccb3b6d28eaee83729c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Tue, 12 Mar 2013 07:29:39 +0100 Subject: [PATCH 183/215] Make deprecation warning in doc string bold --- skimage/_shared/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skimage/_shared/utils.py b/skimage/_shared/utils.py index fc13c7e6..acdf9c16 100644 --- a/skimage/_shared/utils.py +++ b/skimage/_shared/utils.py @@ -44,7 +44,7 @@ class deprecated(object): return func(*args, **kwargs) # modify doc string to display deprecation warning - doc = 'Deprecated function.' + alt_msg + doc = '**Deprecated function**.' + alt_msg if wrapped.__doc__ is None: wrapped.__doc__ = doc else: From d01b14f2ff89051d5fd7da507c30b6062ab3e128 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Tue, 12 Mar 2013 09:12:13 +0100 Subject: [PATCH 184/215] Add API documentation to latex docs --- doc/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/Makefile b/doc/Makefile index 7552be6b..311ce3ca 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -90,7 +90,7 @@ devhelp: @echo "# ln -s build/devhelp $$HOME/.local/share/devhelp/scikitimage" @echo "# devhelp" -latex: +latex: api $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(DEST)/latex @echo @echo "Build finished; the LaTeX files are in $(DEST)/latex." From b82764583a4991b9a07af08488e4f7bac7a75dea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Tue, 12 Mar 2013 09:36:33 +0100 Subject: [PATCH 185/215] Increase list-depth for nested quotes etc. --- doc/source/conf.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/doc/source/conf.py b/doc/source/conf.py index 5071fab1..328a75a0 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -203,7 +203,10 @@ latex_documents = [ #latex_use_parts = False # Additional stuff for the LaTeX preamble. -#latex_preamble = '' +latex_preamble = ''' +\usepackage{enumitem} +\setlistdepth{100} +''' # Documents to append as an appendix to all manuals. #latex_appendices = [] From a1ad4ca34b000d237a9a9c674751505a3ccd9510 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Tue, 12 Mar 2013 09:39:00 +0100 Subject: [PATCH 186/215] Change latex doc settings --- doc/source/conf.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/source/conf.py b/doc/source/conf.py index 328a75a0..7e742eb2 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -42,8 +42,8 @@ source_suffix = '.txt' master_doc = 'index' # General information about the project. -project = u'skimage' -copyright = u'2011, the scikit-image team' +project = 'skimage' +copyright = '2013, the scikit-image team' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -190,8 +190,8 @@ htmlhelp_basename = 'scikitimagedoc' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ - ('contents', 'scikitimage.tex', u'The Image Scikit Documentation', - u'SciPy Developers', 'manual'), + ('contents', 'scikit-image.tex', u'The scikit-image Documentation', + u'scikit-image development team', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of From 0324737c4739824186b4da88ba408482a0d6f5b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Tue, 12 Mar 2013 09:39:42 +0100 Subject: [PATCH 187/215] Fix some doc strings for latex docs --- skimage/filter/rank/rank.pyx | 4 ++-- skimage/io/collection.py | 3 --- skimage/transform/hough_transform.py | 2 -- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/skimage/filter/rank/rank.pyx b/skimage/filter/rank/rank.pyx index e8a4c8f1..5380363a 100644 --- a/skimage/filter/rank/rank.pyx +++ b/skimage/filter/rank/rank.pyx @@ -669,7 +669,7 @@ def noise_filter(image, selem, out=None, mask=None, shift_x=False, def entropy(image, selem, out=None, mask=None, shift_x=False, shift_y=False): - """Returns the entropy [wiki_entropy]_ computed locally. Entropy is computed + """Returns the entropy [1]_ computed locally. Entropy is computed using base 2 logarithm i.e. the filter returns the minimum number of bits needed to encode local greylevel distribution. @@ -698,7 +698,7 @@ def entropy(image, selem, out=None, mask=None, shift_x=False, shift_y=False): References ---------- - .. [wiki_entropy] http://en.wikipedia.org/wiki/Entropy_(information_theory) + .. [1] http://en.wikipedia.org/wiki/Entropy_(information_theory) Examples -------- diff --git a/skimage/io/collection.py b/skimage/io/collection.py index 5a5dfff6..fafca83f 100644 --- a/skimage/io/collection.py +++ b/skimage/io/collection.py @@ -102,9 +102,6 @@ class MultiImage(object): (15, 10) (15, 10) - The two frames in this image can be shown with matplotlib: - - .. plot:: show_collection.py """ def __init__(self, filename, conserve_memory=True, dtype=None): """Load a multi-img.""" diff --git a/skimage/transform/hough_transform.py b/skimage/transform/hough_transform.py index e83cecd5..1552e531 100644 --- a/skimage/transform/hough_transform.py +++ b/skimage/transform/hough_transform.py @@ -140,8 +140,6 @@ def hough_line(img, theta=None): >>> out, angles, d = hough(img) - .. plot:: hough_tf.py - """ return _hough(img, theta) From 20f27345c3dfcbf1f7c4e5ca2482e94431a948bc Mon Sep 17 00:00:00 2001 From: spotter Date: Mon, 11 Mar 2013 23:54:06 +0100 Subject: [PATCH 188/215] FIX: rgb2hed and hed2rgb can work with both signed and unsigned image. --- skimage/color/colorconv.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/skimage/color/colorconv.py b/skimage/color/colorconv.py index 072fc995..586ef340 100644 --- a/skimage/color/colorconv.py +++ b/skimage/color/colorconv.py @@ -769,7 +769,7 @@ def rgb2hed(rgb): >>> ihc = data.ihc() >>> ihc_hed = rgb2hed(ihc) """ - rgb = dtype.img_as_float(rgb) + 1 + rgb = dtype.img_as_float(rgb) + 2 hed = np.dot(np.reshape(-np.log(rgb), (-1, 3)), hed_from_rgb) return np.reshape(hed, rgb.shape) @@ -812,4 +812,4 @@ def hed2rgb(hed): hed = dtype.img_as_float(hed) logrgb1 = np.dot(-np.reshape(hed, (-1, 3)), rgb_from_hed) rgb1 = np.exp(logrgb1) - return rescale_intensity(np.reshape(rgb1 - 1, hed.shape), in_range=(0, 1)) + return rescale_intensity(np.reshape(rgb1 - 2, hed.shape), in_range=(-1, 1)) From f48097c5cac34cbba6a048391da8fa486df38d36 Mon Sep 17 00:00:00 2001 From: spotter Date: Tue, 12 Mar 2013 16:06:32 +0100 Subject: [PATCH 189/215] FIX: fixed docstrings. ENH: Added new stain separation matrices. --- skimage/color/colorconv.py | 160 ++++++++++++++++++++++++++++++++++++- 1 file changed, 156 insertions(+), 4 deletions(-) diff --git a/skimage/color/colorconv.py b/skimage/color/colorconv.py index 586ef340..4d90b67c 100644 --- a/skimage/color/colorconv.py +++ b/skimage/color/colorconv.py @@ -325,6 +325,78 @@ rgb_from_hed = np.array([[0.65, 0.70, 0.29], [0.27, 0.57, 0.78]]) hed_from_rgb = linalg.inv(rgb_from_hed) +# Following matrices are adapted form the Java code written by G.Landini. +# The original code is available at: +# http://www.dentistry.bham.ac.uk/landinig/software/cdeconv/cdeconv.html + +# Hematoxylin + DAB +rgb_from_hdx = np.array([[0.650, 0.704, 0.286], + [0.268, 0.570, 0.776], + [0.0, 0.0, 0.0]]) +rgb_from_hdx[2, :] = np.cross(rgb_from_hdx[0, :], rgb_from_hdx[1, :]) +hdx_from_rgb = linalg.inv(rgb_from_hdx) + +# Feulgen + Light Green +rgb_from_fgx = np.array([[0.46420921, 0.83008335, 0.30827187], + [0.94705542, 0.25373821, 0.19650764], + [0.0, 0.0, 0.0]]) +rgb_from_fgx[2, :] = np.cross(rgb_from_fgx[0, :], rgb_from_fgx[1, :]) +fgx_from_rgb = linalg.inv(rgb_from_fgx) + +# Giemsa: Methyl Blue + Eosin +rgb_from_bex = np.array([[0.834750233, 0.513556283, 0.196330403], + [0.092789, 0.954111, 0.283111], + [0.0, 0.0, 0.0]]) +rgb_from_bex[2, :] = np.cross(rgb_from_bex[0, :], rgb_from_bex[1, :]) +bex_from_rgb = linalg.inv(rgb_from_bex) + +# FastRed + FastBlue + DAB +rgb_from_rbd = np.array([[0.21393921, 0.85112669, 0.47794022], + [0.74890292, 0.60624161, 0.26731082], + [0.268, 0.570, 0.776]]) +rbd_from_rgb = linalg.inv(rgb_from_rbd) + +# Methyl Green + DAB +rgb_from_gdx = np.array([[0.98003, 0.144316, 0.133146], + [0.268, 0.570, 0.776], + [0.0, 0.0, 0.0]]) +rgb_from_gdx[2, :] = np.cross(rgb_from_gdx[0, :], rgb_from_gdx[1, :]) +gdx_from_rgb = linalg.inv(rgb_from_gdx) + +# Hematoxylin + AEC +rgb_from_hax = np.array([[0.650, 0.704, 0.286], + [0.2743, 0.6796, 0.6803], + [0.0, 0.0, 0.0]]) +rgb_from_hax[2, :] = np.cross(rgb_from_hax[0, :], rgb_from_hax[1, :]) +hax_from_rgb = linalg.inv(rgb_from_hax) + +# Blue matrix Anilline Blue + Red matrix Azocarmine + Orange matrix Orange-G +rgb_from_bro = np.array([[0.853033, 0.508733, 0.112656], + [0.09289875, 0.8662008, 0.49098468], + [0.10732849, 0.36765403, 0.9237484]]) +bro_from_rgb = linalg.inv(rgb_from_bro) + +# Methyl Blue + Ponceau Fuchsin +rgb_from_bpx = np.array([[0.7995107, 0.5913521, 0.10528667], + [0.09997159, 0.73738605, 0.6680326], + [0.0, 0.0, 0.0]]) +rgb_from_bpx[2, :] = np.cross(rgb_from_bpx[0, :], rgb_from_bpx[1, :]) +bpx_from_rgb = linalg.inv(rgb_from_bpx) + +# Alcian Blue + Hematoxylin +rgb_from_ahx = np.array([[0.874622, 0.457711, 0.158256], + [0.552556, 0.7544, 0.353744], + [0.0, 0.0, 0.0]]) +rgb_from_ahx[2, :] = np.cross(rgb_from_ahx[0, :], rgb_from_ahx[1, :]) +ahx_from_rgb = linalg.inv(rgb_from_ahx) + +# Hematoxylin + PAS +rgb_from_hpx = np.array([[0.644211, 0.716556, 0.266844], + [0.175411, 0.972178, 0.154589], + [0.0, 0.0, 0.0]]) +rgb_from_hpx[2, :] = np.cross(rgb_from_hpx[0, :], rgb_from_hpx[1, :]) +hpx_from_rgb = linalg.inv(rgb_from_hpx) + #------------------------------------------------------------- # The conversion functions that make use of the matrices above #------------------------------------------------------------- @@ -766,7 +838,7 @@ def rgb2hed(rgb): -------- >>> from skimage import data >>> from skimage.color import rgb2hed - >>> ihc = data.ihc() + >>> ihc = data.immunohistochemistry() >>> ihc_hed = rgb2hed(ihc) """ rgb = dtype.img_as_float(rgb) + 2 @@ -780,12 +852,12 @@ def hed2rgb(hed): Parameters ---------- hed : array_like - The image in HED format, in a 3-D array of shape (.., .., 3). + The image in the HED color space, in a 3-D array of shape (.., .., 3). Returns ------- out : ndarray - The image in HED format, in a 3-D array of shape (.., .., 3). + The image in RGB, in a 3-D array of shape (.., .., 3). Raises ------ @@ -803,7 +875,7 @@ def hed2rgb(hed): -------- >>> from skimage import data >>> from skimage.color import rgb2hed, hed2rgb - >>> ihc = data.ihc() + >>> ihc = data.immunohistochemistry() >>> ihc_hed = rgb2hed(ihc) >>> ihc_rgb = hed2rgb(ihc_hed) """ @@ -813,3 +885,83 @@ def hed2rgb(hed): logrgb1 = np.dot(-np.reshape(hed, (-1, 3)), rgb_from_hed) rgb1 = np.exp(logrgb1) return rescale_intensity(np.reshape(rgb1 - 2, hed.shape), in_range=(-1, 1)) + + +def separate_stains(rgb, conv_matrix): + """RGB to stain color space conversion. + + Parameters + ---------- + rgb : array_like + The image in RGB format, in a 3-D array of shape (.., .., 3). + conv_matrix: ndarray + The stain separation matrix as described by G. Landini [1]_. + + Returns + ------- + out : ndarray + The image in stain color space, in a 3-D array of shape (.., .., 3). + + Raises + ------ + ValueError + If `rgb` is not a 3-D array of shape (.., .., 3). + + + References + ---------- + .. [1] http://www.dentistry.bham.ac.uk/landinig/software/cdeconv/cdeconv.html + + Examples + -------- + >>> from skimage import data + >>> from skimage.color import separate_stains + >>> from skimage.color.colorconv import hdx_from_rgb + >>> ihc = data.immunohistochemistry() + >>> ihc_hdx = separate_stains(ihc, hdx_from_rgb) + """ + rgb = dtype.img_as_float(rgb) + 2 + stains = np.dot(np.reshape(-np.log(rgb), (-1, 3)), conv_matrix) + return np.reshape(stains, rgb.shape) + + +def combine_stains(stains, conv_matrix): + """Haematoxylin-Eosin-DAB (HED) to RGB color space conversion. + + Parameters + ---------- + stains : array_like + The image in stain color space, in a 3-D array of shape (.., .., 3). + conv_matrix: ndarray + The stain separation matrix as described by G. Landini [1]_. + + Returns + ------- + out : ndarray + The image in RGB format, in a 3-D array of shape (.., .., 3). + + Raises + ------ + ValueError + If `stains` is not a 3-D array of shape (.., .., 3). + + References + ---------- + .. [1] http://www.dentistry.bham.ac.uk/landinig/software/cdeconv/cdeconv.html + + + Examples + -------- + >>> from skimage import data + >>> from skimage.color import separate_stains, combine_stains + >>> from skimage.color.colorconv import hdx_from_rgb, rgb_from_hdx + >>> ihc = data.immunohistochemistry() + >>> ihc_hdx = separate_stains(ihc, hdx_from_rgb) + >>> ihc_rgb = combine_stains(ihc_hdx, rgb_from_hdx) + """ + from ..exposure import rescale_intensity + + stains = dtype.img_as_float(stains) + logrgb2 = np.dot(-np.reshape(stains, (-1, 3)), conv_matrix) + rgb2 = np.exp(logrgb2) + return rescale_intensity(np.reshape(rgb2 - 2, stains.shape), in_range=(-1, 1)) From c43986d6d603f31b123b32b06e48b099e020ff5a Mon Sep 17 00:00:00 2001 From: spotter Date: Tue, 12 Mar 2013 16:15:41 +0100 Subject: [PATCH 190/215] ENH: Adding tests for new stain separation functions. --- skimage/color/colorconv.py | 1 + skimage/color/tests/test_colorconv.py | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/skimage/color/colorconv.py b/skimage/color/colorconv.py index 4d90b67c..2615d63e 100644 --- a/skimage/color/colorconv.py +++ b/skimage/color/colorconv.py @@ -46,6 +46,7 @@ from __future__ import division __all__ = ['convert_colorspace', 'rgb2hsv', 'hsv2rgb', 'rgb2xyz', 'xyz2rgb', 'rgb2rgbcie', 'rgbcie2rgb', 'rgb2grey', 'rgb2gray', 'gray2rgb', 'xyz2lab', 'lab2xyz', 'lab2rgb', 'rgb2lab', 'rgb2hed', 'hed2rgb', + 'separate_stains', 'combine_stains', 'is_rgb', 'is_gray' ] diff --git a/skimage/color/tests/test_colorconv.py b/skimage/color/tests/test_colorconv.py index ddbfa594..3365bb1c 100644 --- a/skimage/color/tests/test_colorconv.py +++ b/skimage/color/tests/test_colorconv.py @@ -22,6 +22,8 @@ from skimage.color import ( rgb2hsv, hsv2rgb, rgb2xyz, xyz2rgb, rgb2hed, hed2rgb, + separate_stains, + combine_stains, rgb2rgbcie, rgbcie2rgb, convert_colorspace, rgb2grey, gray2rgb, @@ -132,6 +134,22 @@ class TestColorconv(TestCase): img_rgb = img_as_float(self.img_rgb) assert_array_almost_equal(hed2rgb(rgb2hed(img_rgb)), img_rgb) + # RGB<->HDX roundtrip with ubyte image + def test_hdx_rgb_roundtrip(self): + from skimage.color.colorconv import hdx_from_rgb, rgb_from_hdx + img_rgb = self.img_rgb + conv = combine_stains(separate_stains(img_rgb, hdx_from_rgb), + rgb_from_hdx) + assert_equal(img_as_ubyte(conv), img_rgb) + + # RGB<->HDX roundtrip with ubyte image + def test_hdx_rgb_roundtrip(self): + from skimage.color.colorconv import hdx_from_rgb, rgb_from_hdx + img_rgb = img_as_float(self.img_rgb) + conv = combine_stains(separate_stains(img_rgb, hdx_from_rgb), + rgb_from_hdx) + assert_array_almost_equal(conv, img_rgb) + # RGB to RGB CIE def test_rgb2rgbcie_conversion(self): gt = np.array([[[ 0.1488856 , 0.18288098, 0.19277574], From b3e557b55023f818c6c536379c6931e1e755936b Mon Sep 17 00:00:00 2001 From: spotter Date: Tue, 12 Mar 2013 16:19:51 +0100 Subject: [PATCH 191/215] FIX: using the new stain separation functions for the special case of hed. --- skimage/color/colorconv.py | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/skimage/color/colorconv.py b/skimage/color/colorconv.py index 2615d63e..70d6aac7 100644 --- a/skimage/color/colorconv.py +++ b/skimage/color/colorconv.py @@ -842,9 +842,7 @@ def rgb2hed(rgb): >>> ihc = data.immunohistochemistry() >>> ihc_hed = rgb2hed(ihc) """ - rgb = dtype.img_as_float(rgb) + 2 - hed = np.dot(np.reshape(-np.log(rgb), (-1, 3)), hed_from_rgb) - return np.reshape(hed, rgb.shape) + return separate_stains(rgb, hed_from_rgb) def hed2rgb(hed): @@ -880,12 +878,7 @@ def hed2rgb(hed): >>> ihc_hed = rgb2hed(ihc) >>> ihc_rgb = hed2rgb(ihc_hed) """ - from ..exposure import rescale_intensity - - hed = dtype.img_as_float(hed) - logrgb1 = np.dot(-np.reshape(hed, (-1, 3)), rgb_from_hed) - rgb1 = np.exp(logrgb1) - return rescale_intensity(np.reshape(rgb1 - 2, hed.shape), in_range=(-1, 1)) + return combine_stains(hed, rgb_from_hed) def separate_stains(rgb, conv_matrix): From 6f7158046fd00a17ea37a60be0b01b44325280d2 Mon Sep 17 00:00:00 2001 From: spotter Date: Tue, 12 Mar 2013 16:41:02 +0100 Subject: [PATCH 192/215] FIX: combine_stains docstring. --- skimage/color/colorconv.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skimage/color/colorconv.py b/skimage/color/colorconv.py index 70d6aac7..2b954098 100644 --- a/skimage/color/colorconv.py +++ b/skimage/color/colorconv.py @@ -920,7 +920,7 @@ def separate_stains(rgb, conv_matrix): def combine_stains(stains, conv_matrix): - """Haematoxylin-Eosin-DAB (HED) to RGB color space conversion. + """Stain to RGB color space conversion. Parameters ---------- From 74acdc9fa9567db3293ad9edb9faca2271fbf027 Mon Sep 17 00:00:00 2001 From: spotter Date: Tue, 12 Mar 2013 17:09:17 +0100 Subject: [PATCH 193/215] FIX: changed unicode dash in docstrings. --- doc/examples/plot_ihc_color_separation.py | 2 +- skimage/color/colorconv.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/examples/plot_ihc_color_separation.py b/doc/examples/plot_ihc_color_separation.py index 02dc6761..626d76ab 100644 --- a/doc/examples/plot_ihc_color_separation.py +++ b/doc/examples/plot_ihc_color_separation.py @@ -14,7 +14,7 @@ Diaminobenzidine (DAB) which gives a brown color. .. [1] A. C. Ruifrok and D. A. Johnston, "Quantification of histochemical staining by color deconvolution.," Analytical and quantitative cytology and histology / the International Academy of Cytology [and] - American Society of Cytology, vol. 23, no. 4, pp. 291–9, Aug. 2001. + American Society of Cytology, vol. 23, no. 4, pp. 291-9, Aug. 2001. """ import matplotlib.pyplot as plt diff --git a/skimage/color/colorconv.py b/skimage/color/colorconv.py index 2b954098..f74eb2f4 100644 --- a/skimage/color/colorconv.py +++ b/skimage/color/colorconv.py @@ -320,7 +320,7 @@ lab_ref_white = np.array([0.95047, 1., 1.08883]) # "Quantification of histochemical staining by color deconvolution.," # Analytical and quantitative cytology and histology / the International # Academy of Cytology [and] American Society of Cytology, vol. 23, no. 4, -# pp. 291–9, Aug. 2001. +# pp. 291-9, Aug. 2001. rgb_from_hed = np.array([[0.65, 0.70, 0.29], [0.07, 0.99, 0.11], [0.27, 0.57, 0.78]]) @@ -833,7 +833,7 @@ def rgb2hed(rgb): .. [1] A. C. Ruifrok and D. A. Johnston, "Quantification of histochemical staining by color deconvolution.," Analytical and quantitative cytology and histology / the International Academy of Cytology [and] - American Society of Cytology, vol. 23, no. 4, pp. 291–9, Aug. 2001. + American Society of Cytology, vol. 23, no. 4, pp. 291-9, Aug. 2001. Examples -------- @@ -868,7 +868,7 @@ def hed2rgb(hed): .. [1] A. C. Ruifrok and D. A. Johnston, "Quantification of histochemical staining by color deconvolution.," Analytical and quantitative cytology and histology / the International Academy of Cytology [and] - American Society of Cytology, vol. 23, no. 4, pp. 291–9, Aug. 2001. + American Society of Cytology, vol. 23, no. 4, pp. 291-9, Aug. 2001. Examples -------- From 16863535ebc33401dc886aa1225837d17cbfe112 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Wed, 13 Mar 2013 10:59:26 +0100 Subject: [PATCH 194/215] Re-enable plots in doc strings and generate PDF images for them --- doc/source/conf.py | 2 +- skimage/io/collection.py | 4 ++++ skimage/transform/hough_transform.py | 2 ++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/doc/source/conf.py b/doc/source/conf.py index 7e742eb2..c5ca8869 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -246,7 +246,7 @@ matplotlib.rcParams.update({ """ plot_include_source = True -plot_formats = [('png', 100)] +plot_formats = [('png', 100), ('pdf', 100)] plot2rst_index_name = 'README' plot2rst_rcparams = {'image.cmap' : 'gray', diff --git a/skimage/io/collection.py b/skimage/io/collection.py index fafca83f..6aa9cfc6 100644 --- a/skimage/io/collection.py +++ b/skimage/io/collection.py @@ -102,6 +102,10 @@ class MultiImage(object): (15, 10) (15, 10) + The two frames in this image can be shown with matplotlib: + + .. plot:: show_collection.py + """ def __init__(self, filename, conserve_memory=True, dtype=None): """Load a multi-img.""" diff --git a/skimage/transform/hough_transform.py b/skimage/transform/hough_transform.py index 1552e531..e83cecd5 100644 --- a/skimage/transform/hough_transform.py +++ b/skimage/transform/hough_transform.py @@ -140,6 +140,8 @@ def hough_line(img, theta=None): >>> out, angles, d = hough(img) + .. plot:: hough_tf.py + """ return _hough(img, theta) From 97d7f88b0441413fc7ff330c63244b24dc76ac96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Wed, 13 Mar 2013 11:08:02 +0100 Subject: [PATCH 195/215] Explicitly set latex font size and paper size --- doc/source/conf.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/source/conf.py b/doc/source/conf.py index c5ca8869..5d7a6fc4 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -182,10 +182,10 @@ htmlhelp_basename = 'scikitimagedoc' # -- Options for LaTeX output -------------------------------------------------- # The paper size ('letter' or 'a4'). -#latex_paper_size = 'letter' +latex_paper_size = 'a4' # The font size ('10pt', '11pt' or '12pt'). -#latex_font_size = '10pt' +latex_font_size = '10pt' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). From 3f31b48fe1c80d3d744c27622de7f5fc846ab77a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Wed, 13 Mar 2013 11:10:03 +0100 Subject: [PATCH 196/215] Use plot_directive from matplotlib.sphinxext if available --- doc/source/conf.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/doc/source/conf.py b/doc/source/conf.py index 5d7a6fc4..975de3eb 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -26,9 +26,26 @@ sys.path.append(os.path.join(curpath, '..', 'ext')) # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ['sphinx.ext.autodoc', 'sphinx.ext.pngmath', 'numpydoc', - 'sphinx.ext.autosummary', 'plot_directive', 'plot2rst', + 'sphinx.ext.autosummary', 'plot2rst', 'sphinx.ext.intersphinx'] +# Determine if the matplotlib has a recent enough version of the +# plot_directive, otherwise use the local fork. +try: + from matplotlib.sphinxext import plot_directive +except ImportError: + use_matplotlib_plot_directive = False +else: + try: + use_matplotlib_plot_directive = (plot_directive.__version__ >= 2) + except AttributeError: + use_matplotlib_plot_directive = False + +if use_matplotlib_plot_directive: + extensions.append('matplotlib.sphinxext.plot_directive') +else: + extensions.append('plot_directive') + # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] From d68515f0d8e9dab05dc834b9bc4c42ae58a39024 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Wed, 13 Mar 2013 12:03:44 +0100 Subject: [PATCH 197/215] Copy some useful latex settings from NumPy --- doc/source/conf.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/doc/source/conf.py b/doc/source/conf.py index 975de3eb..52dc8f13 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -220,9 +220,25 @@ latex_documents = [ #latex_use_parts = False # Additional stuff for the LaTeX preamble. -latex_preamble = ''' +latex_preamble = r''' \usepackage{enumitem} \setlistdepth{100} + +\usepackage{amsmath} +\DeclareUnicodeCharacter{00A0}{\nobreakspace} + +% In the parameters section, place a newline after the Parameters header +\usepackage{expdlist} +\let\latexdescription=\description +\def\description{\latexdescription{}{} \breaklabel} + +% Make Examples/etc section headers smaller and more compact +\makeatletter +\titleformat{\paragraph}{\normalsize\py@HeaderFamily}% + {\py@TitleColor}{0em}{\py@TitleColor}{\py@NormalColor} +\titlespacing*{\paragraph}{0pt}{1ex}{0pt} +\makeatother + ''' # Documents to append as an appendix to all manuals. From 169d945534f78425926057eb4900f33337a6d428 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Wed, 13 Mar 2013 12:08:08 +0100 Subject: [PATCH 198/215] Fix make.bat --- doc/make.bat | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/doc/make.bat b/doc/make.bat index e16c8d40..5f117648 100644 --- a/doc/make.bat +++ b/doc/make.bat @@ -27,6 +27,14 @@ if "%1" == "help" ( goto end ) +for %%x in (html htmlhelp latex qthelp) do ( + if "%1" == "%%x" ( + md source\api 2>NUL + python tools/build_modref_templates.py + ) +) + + if "%1" == "clean" ( for /d %%i in (build\*) do rmdir /q /s %%i del /q /s build\* @@ -34,6 +42,7 @@ if "%1" == "clean" ( ) if "%1" == "html" ( + cd source && python random_gallery.py && python coverage_generator.py && cd .. %SPHINXBUILD% -b html %ALLSPHINXOPTS% build/html echo. echo.Build finished. The HTML pages are in build/html. From 1d9fb145f6f2f0ce6261ea528f0ce3dcd09452f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Wed, 13 Mar 2013 12:09:39 +0100 Subject: [PATCH 199/215] Avoid duplicate python module index --- doc/source/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/conf.py b/doc/source/conf.py index 52dc8f13..2f39b0b3 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -245,7 +245,7 @@ latex_preamble = r''' #latex_appendices = [] # If false, no module index is generated. -#latex_use_modindex = True +latex_use_modindex = False # ----------------------------------------------------------------------------- # Numpy extensions From 5e67572ac7aaa7a5bbfa22c4c4dd35b251ba8f16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Wed, 13 Mar 2013 12:16:58 +0100 Subject: [PATCH 200/215] Do not set paper size to allow manual setting through make --- doc/source/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/conf.py b/doc/source/conf.py index 2f39b0b3..c2179b41 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -199,7 +199,7 @@ htmlhelp_basename = 'scikitimagedoc' # -- Options for LaTeX output -------------------------------------------------- # The paper size ('letter' or 'a4'). -latex_paper_size = 'a4' +#latex_paper_size = 'letter' # The font size ('10pt', '11pt' or '12pt'). latex_font_size = '10pt' From b75776cc2ebc67835f392cf0c9d8b75f74871e01 Mon Sep 17 00:00:00 2001 From: Xavier Moles Lopez Date: Wed, 13 Mar 2013 14:26:23 +0100 Subject: [PATCH 201/215] ENH: Updated stain separation function docstrings and added matrices to __all__ --- skimage/color/colorconv.py | 49 ++++++++++++++++++++++++++++++++++---- 1 file changed, 44 insertions(+), 5 deletions(-) diff --git a/skimage/color/colorconv.py b/skimage/color/colorconv.py index f74eb2f4..f40aa48f 100644 --- a/skimage/color/colorconv.py +++ b/skimage/color/colorconv.py @@ -46,7 +46,12 @@ from __future__ import division __all__ = ['convert_colorspace', 'rgb2hsv', 'hsv2rgb', 'rgb2xyz', 'xyz2rgb', 'rgb2rgbcie', 'rgbcie2rgb', 'rgb2grey', 'rgb2gray', 'gray2rgb', 'xyz2lab', 'lab2xyz', 'lab2rgb', 'rgb2lab', 'rgb2hed', 'hed2rgb', - 'separate_stains', 'combine_stains', + 'separate_stains', 'combine_stains', 'rgb_from_hed', 'hed_from_rgb', + 'rgb_from_hdx', 'hdx_from_rgb', 'rgb_from_fgx', 'fgx_from_rgb', + 'rgb_from_bex', 'bex_from_rgb', 'rgb_from_rbd', 'rbd_from_rgb', + 'rgb_from_gdx', 'gdx_from_rgb', 'rgb_from_hax', 'hax_from_rgb', + 'rgb_from_bro', 'bro_from_rgb', 'rgb_from_bpx', 'bpx_from_rgb', + 'rgb_from_ahx', 'ahx_from_rgb', 'rgb_from_hpx', 'hpx_from_rgb', 'is_rgb', 'is_gray' ] @@ -901,6 +906,23 @@ def separate_stains(rgb, conv_matrix): ValueError If `rgb` is not a 3-D array of shape (.., .., 3). + Notes + ----- + Stain separation matrices available in the ``color`` module and their + respective colorspace: + + * ``hed_from_rgb``: Hematoxylin + Eosin + DAB + * ``hdx_from_rgb``: Hematoxylin + DAB + * ``fgx_from_rgb``: Feulgen + Light Green + * ``bex_from_rgb``: Giemsa stain : Methyl Blue + Eosin + * ``rbd_from_rgb``: FastRed + FastBlue + DAB + * ``gdx_from_rgb``: Methyl Green + DAB + * ``hax_from_rgb``: Hematoxylin + AEC + * ``bro_from_rgb``: Blue matrix Anilline Blue + Red matrix Azocarmine\ + + Orange matrix Orange-G + * ``bpx_from_rgb``: Methyl Blue + Ponceau Fuchsin + * ``ahx_from_rgb``: Alcian Blue + Hematoxylin + * ``hpx_from_rgb``: Hematoxylin + PAS References ---------- @@ -909,8 +931,7 @@ def separate_stains(rgb, conv_matrix): Examples -------- >>> from skimage import data - >>> from skimage.color import separate_stains - >>> from skimage.color.colorconv import hdx_from_rgb + >>> from skimage.color import separate_stains, hdx_from_rgb >>> ihc = data.immunohistochemistry() >>> ihc_hdx = separate_stains(ihc, hdx_from_rgb) """ @@ -939,6 +960,24 @@ def combine_stains(stains, conv_matrix): ValueError If `stains` is not a 3-D array of shape (.., .., 3). + Notes + ----- + Stain combination matrices available in the ``color`` module and their + respective colorspace: + + * ``rgb_from_hed``: Hematoxylin + Eosin + DAB + * ``rgb_from_hdx``: Hematoxylin + DAB + * ``rgb_from_fgx``: Feulgen + Light Green + * ``rgb_from_bex``: Giemsa stain : Methyl Blue + Eosin + * ``rgb_from_rbd``: FastRed + FastBlue + DAB + * ``rgb_from_gdx``: Methyl Green + DAB + * ``rgb_from_hax``: Hematoxylin + AEC + * ``rgb_from_bro``: Blue matrix Anilline Blue + Red matrix Azocarmine\ + + Orange matrix Orange-G + * ``rgb_from_bpx``: Methyl Blue + Ponceau Fuchsin + * ``rgb_from_ahx``: Alcian Blue + Hematoxylin + * ``rgb_from_hpx``: Hematoxylin + PAS + References ---------- .. [1] http://www.dentistry.bham.ac.uk/landinig/software/cdeconv/cdeconv.html @@ -947,8 +986,8 @@ def combine_stains(stains, conv_matrix): Examples -------- >>> from skimage import data - >>> from skimage.color import separate_stains, combine_stains - >>> from skimage.color.colorconv import hdx_from_rgb, rgb_from_hdx + >>> from skimage.color import separate_stains, combine_stains, \ +hdx_from_rgb, rgb_from_hdx >>> ihc = data.immunohistochemistry() >>> ihc_hdx = separate_stains(ihc, hdx_from_rgb) >>> ihc_rgb = combine_stains(ihc_hdx, rgb_from_hdx) From d0a15a7cf9a1f93cc1391d69af4fa0f985702096 Mon Sep 17 00:00:00 2001 From: Xavier Moles Lopez Date: Wed, 13 Mar 2013 15:14:39 +0100 Subject: [PATCH 202/215] FIX: Example in combine_stains docstring. --- skimage/color/colorconv.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/skimage/color/colorconv.py b/skimage/color/colorconv.py index f40aa48f..fc9ab342 100644 --- a/skimage/color/colorconv.py +++ b/skimage/color/colorconv.py @@ -986,8 +986,8 @@ def combine_stains(stains, conv_matrix): Examples -------- >>> from skimage import data - >>> from skimage.color import separate_stains, combine_stains, \ -hdx_from_rgb, rgb_from_hdx + >>> from skimage.color import (separate_stains, combine_stains, + ... hdx_from_rgb, rgb_from_hdx) >>> ihc = data.immunohistochemistry() >>> ihc_hdx = separate_stains(ihc, hdx_from_rgb) >>> ihc_rgb = combine_stains(ihc_hdx, rgb_from_hdx) From f956f3257d3b83351705ac08da6d6092b9e5dc9e Mon Sep 17 00:00:00 2001 From: Xavier Moles Lopez Date: Thu, 14 Mar 2013 13:59:15 +0100 Subject: [PATCH 203/215] FIX: Added myself in CONTRIBUTORS.txt --- CONTRIBUTORS.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 38e2ea32..2c20e45d 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -135,3 +135,6 @@ - Thouis Jones Vectorized operators for arrays of 16-bit ints. + +- Xavier Moles Lopez + Color separation (color deconvolution) for several stainings. From 44095eef81c7b98d119dc121a629331f6579fc1e Mon Sep 17 00:00:00 2001 From: Malcolm Reynolds Date: Fri, 15 Mar 2013 18:50:43 +0000 Subject: [PATCH 204/215] FIX: number of peaks returned from feature.corner_peaks corner_peaks accepted a num_peaks argument, but the call to peak_local_max inside the function used np.inf instead of the value of this keyword argument, leading to an unbounded number of peaks being returned. --- skimage/feature/corner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skimage/feature/corner.py b/skimage/feature/corner.py index 12d3fb70..39816934 100644 --- a/skimage/feature/corner.py +++ b/skimage/feature/corner.py @@ -489,7 +489,7 @@ def corner_peaks(image, min_distance=10, threshold_abs=0, threshold_rel=0.1, threshold_abs=threshold_abs, threshold_rel=threshold_rel, exclude_border=exclude_border, - indices=False, num_peaks=np.inf, + indices=False, num_peaks=num_peaks, footprint=footprint, labels=labels) if min_distance > 0: coords = np.transpose(peaks.nonzero()) From c5bdf00e82315f0784bbc5950a3488a488ae9e75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Sat, 16 Mar 2013 18:03:04 +0100 Subject: [PATCH 205/215] Fix parameter list format error of rotate --- skimage/transform/_warps.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skimage/transform/_warps.py b/skimage/transform/_warps.py index 7ac3e63f..be7eee12 100644 --- a/skimage/transform/_warps.py +++ b/skimage/transform/_warps.py @@ -129,7 +129,7 @@ def rotate(image, angle, resize=False, order=1, mode='constant', cval=0.): Input image. angle : float Rotation angle in degrees in counter-clockwise direction. - resize: bool, optional + resize : bool, optional Determine whether the shape of the output image will be automatically calculated, so the complete rotated image exactly fits. Default is False. From 874c5647bce859c0f1760ea2fb972cf781404b31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Sun, 17 Mar 2013 10:47:18 +0100 Subject: [PATCH 206/215] Replace scipy.ndimage routine with skimage.ndimage.rotate --- doc/examples/plot_regionprops.py | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/doc/examples/plot_regionprops.py b/doc/examples/plot_regionprops.py index 90b40a89..8a67f2ef 100644 --- a/doc/examples/plot_regionprops.py +++ b/doc/examples/plot_regionprops.py @@ -10,26 +10,19 @@ import math import matplotlib.pyplot as plt import numpy as np +from skimage.util import img_as_ubyte from skimage.draw import ellipse from skimage.morphology import label from skimage.measure import regionprops -from scipy.ndimage import geometric_transform +from skimage.transform import rotate -ANGLE = 0.2 - -def rotate(xy): - x, y = xy - out_x = math.cos(ANGLE) * x - math.sin(ANGLE) * y - out_y = math.sin(ANGLE) * x + math.cos(ANGLE) * y - return (out_x, out_y) - -image = np.zeros((600, 600), 'int') +image = np.zeros((600, 600)) rr, cc = ellipse(300, 350, 100, 220) image[rr,cc] = 1 -image = geometric_transform(image, rotate) +image = rotate(image, angle=15) > 0 label_img = label(image) props = regionprops(label_img, [ From 96f08e68d02cadd27436e39f4091541c44aba53e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Sun, 17 Mar 2013 10:48:59 +0100 Subject: [PATCH 207/215] Use nearest neighbour interpolation to avoid different labels --- doc/examples/plot_regionprops.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/examples/plot_regionprops.py b/doc/examples/plot_regionprops.py index 8a67f2ef..4a6cd966 100644 --- a/doc/examples/plot_regionprops.py +++ b/doc/examples/plot_regionprops.py @@ -22,7 +22,7 @@ image = np.zeros((600, 600)) rr, cc = ellipse(300, 350, 100, 220) image[rr,cc] = 1 -image = rotate(image, angle=15) > 0 +image = rotate(image, angle=15, order=0) label_img = label(image) props = regionprops(label_img, [ From 7f755e83db68cea7232b98eee318a41921aec5e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Sun, 17 Mar 2013 10:50:32 +0100 Subject: [PATCH 208/215] Remove unused import --- doc/examples/plot_regionprops.py | 1 - 1 file changed, 1 deletion(-) diff --git a/doc/examples/plot_regionprops.py b/doc/examples/plot_regionprops.py index 4a6cd966..5a8ea01c 100644 --- a/doc/examples/plot_regionprops.py +++ b/doc/examples/plot_regionprops.py @@ -10,7 +10,6 @@ import math import matplotlib.pyplot as plt import numpy as np -from skimage.util import img_as_ubyte from skimage.draw import ellipse from skimage.morphology import label from skimage.measure import regionprops From c475581aa2a42d22af6ecf59dfc779ae65c2fb76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Sun, 17 Mar 2013 10:54:05 +0100 Subject: [PATCH 209/215] Replace rotate function of local binary pattern example with builtin skimage function --- doc/examples/plot_local_binary_pattern.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/examples/plot_local_binary_pattern.py b/doc/examples/plot_local_binary_pattern.py index 85fd5b95..7071a894 100644 --- a/doc/examples/plot_local_binary_pattern.py +++ b/doc/examples/plot_local_binary_pattern.py @@ -12,7 +12,7 @@ each other using the Kullback-Leibler-Divergence. import numpy as np import matplotlib import matplotlib.pyplot as plt -import scipy.ndimage as nd +from skimage.transform import rotate import skimage.feature as ft from skimage import data @@ -59,11 +59,11 @@ refs = { # classify rotated textures print 'Rotated images matched against references using LBP:' print 'original: brick, rotated: 30deg, match result:', -print match(refs, nd.rotate(brick, angle=30, reshape=False)) +print match(refs, rotate(brick, angle=30, resize=False)) print 'original: brick, rotated: 70deg, match result:', -print match(refs, nd.rotate(brick, angle=70, reshape=False)) +print match(refs, rotate(brick, angle=70, resize=False)) print 'original: grass, rotated: 145deg, match result:', -print match(refs, nd.rotate(grass, angle=145, reshape=False)) +print match(refs, rotate(grass, angle=145, resize=False)) # plot histograms of LBP of textures fig, ((ax1, ax2, ax3), (ax4, ax5, ax6)) = plt.subplots(nrows=2, ncols=3, From 045f64a00d7863957a042ecb2ba4372324756b5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Sun, 17 Mar 2013 10:54:35 +0100 Subject: [PATCH 210/215] Explicitly import local_binary_pattern function --- doc/examples/plot_local_binary_pattern.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/examples/plot_local_binary_pattern.py b/doc/examples/plot_local_binary_pattern.py index 7071a894..5429df3a 100644 --- a/doc/examples/plot_local_binary_pattern.py +++ b/doc/examples/plot_local_binary_pattern.py @@ -13,7 +13,7 @@ import numpy as np import matplotlib import matplotlib.pyplot as plt from skimage.transform import rotate -import skimage.feature as ft +from skimage.feature import local_binary_pattern from skimage import data @@ -34,7 +34,7 @@ def kullback_leibler_divergence(p, q): def match(refs, img): best_score = 10 best_name = None - lbp = ft.local_binary_pattern(img, P, R, METHOD) + lbp = local_binary_pattern(img, P, R, METHOD) hist, _ = np.histogram(lbp, normed=True, bins=P + 2, range=(0, P + 2)) for name, ref in refs.items(): ref_hist, _ = np.histogram(ref, normed=True, bins=P + 2, @@ -51,9 +51,9 @@ grass = data.load('grass.png') wall = data.load('rough-wall.png') refs = { - 'brick': ft.local_binary_pattern(brick, P, R, METHOD), - 'grass': ft.local_binary_pattern(grass, P, R, METHOD), - 'wall': ft.local_binary_pattern(wall, P, R, METHOD) + 'brick': local_binary_pattern(brick, P, R, METHOD), + 'grass': local_binary_pattern(grass, P, R, METHOD), + 'wall': local_binary_pattern(wall, P, R, METHOD) } # classify rotated textures From f80cd8114b2a280e9af0df9fb12e5c8f68b1e470 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Sun, 17 Mar 2013 10:57:08 +0100 Subject: [PATCH 211/215] Replace ndimage.zoom function in radon transform example with builtin skimage function --- doc/examples/plot_radon_transform.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/examples/plot_radon_transform.py b/doc/examples/plot_radon_transform.py index efd8a776..adca8162 100644 --- a/doc/examples/plot_radon_transform.py +++ b/doc/examples/plot_radon_transform.py @@ -22,11 +22,11 @@ import matplotlib.pyplot as plt from skimage.io import imread from skimage import data_dir -from skimage.transform import radon, iradon -from scipy.ndimage import zoom +from skimage.transform import radon, iradon, rescale + image = imread(data_dir + "/phantom.png", as_grey=True) -image = zoom(image, 0.4) +image = rescale(image, scale=0.4) plt.figure(figsize=(8, 8.5)) From 0fde67bb568e972224e7dfd70ba39bfd366e4f2d Mon Sep 17 00:00:00 2001 From: Malcolm Reynolds Date: Mon, 18 Mar 2013 15:13:18 +0000 Subject: [PATCH 212/215] TEST: added test for num_peaks kwarg in peak_local_max. --- skimage/feature/tests/test_corner.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/skimage/feature/tests/test_corner.py b/skimage/feature/tests/test_corner.py index a7ee01ff..9c59f704 100644 --- a/skimage/feature/tests/test_corner.py +++ b/skimage/feature/tests/test_corner.py @@ -100,6 +100,19 @@ def test_subpix(): assert_array_equal(subpix[0], (24.5, 24.5)) +def test_num_peaks(): + """For a bunch of different values of num_peaks, check that + peak_local_max returns exactly the right amount of peaks. Test + is run on Lena in order to produce a sufficient number of corners""" + + lena = data.lena() + + for i in xrange(20): + n = np.random.random_integers(20) + results = peak_local_max(corner_harris(lena), num_peaks=n) + assert (results.shape[0] == n) + + def test_corner_peaks(): response = np.zeros((5, 5)) response[2:4, 2:4] = 1 From 920e41398d6c9264d163dafd3fb7469449f27e10 Mon Sep 17 00:00:00 2001 From: Malcolm Reynolds Date: Mon, 18 Mar 2013 15:23:35 +0000 Subject: [PATCH 213/215] FIX: move corner detection outside loop in test_num_peaks. --- skimage/feature/tests/test_corner.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/skimage/feature/tests/test_corner.py b/skimage/feature/tests/test_corner.py index 9c59f704..372752d7 100644 --- a/skimage/feature/tests/test_corner.py +++ b/skimage/feature/tests/test_corner.py @@ -105,11 +105,11 @@ def test_num_peaks(): peak_local_max returns exactly the right amount of peaks. Test is run on Lena in order to produce a sufficient number of corners""" - lena = data.lena() + lena_corners = corner_harris(data.lena()) for i in xrange(20): n = np.random.random_integers(20) - results = peak_local_max(corner_harris(lena), num_peaks=n) + results = peak_local_max(lena_corners, num_peaks=n) assert (results.shape[0] == n) From 042b75d05fb68ec6ba89156936881baea9c68be2 Mon Sep 17 00:00:00 2001 From: Malcolm Reynolds Date: Mon, 18 Mar 2013 15:31:45 +0000 Subject: [PATCH 214/215] FIX: changed xrange to range so Python 3 works. --- skimage/feature/tests/test_corner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skimage/feature/tests/test_corner.py b/skimage/feature/tests/test_corner.py index 372752d7..fa8ddd17 100644 --- a/skimage/feature/tests/test_corner.py +++ b/skimage/feature/tests/test_corner.py @@ -107,7 +107,7 @@ def test_num_peaks(): lena_corners = corner_harris(data.lena()) - for i in xrange(20): + for i in range(20): n = np.random.random_integers(20) results = peak_local_max(lena_corners, num_peaks=n) assert (results.shape[0] == n) From 18b3d1f7e364b284ca9ea332aed72be85ca82a60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Tue, 19 Mar 2013 09:33:10 +0100 Subject: [PATCH 215/215] Fix reference syntax for corner detectors --- skimage/feature/corner.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/skimage/feature/corner.py b/skimage/feature/corner.py index 39816934..15aac545 100644 --- a/skimage/feature/corner.py +++ b/skimage/feature/corner.py @@ -136,8 +136,8 @@ def corner_harris(image, method='k', k=0.05, eps=1e-6, sigma=1): References ---------- - ..[1] http://kiwi.cs.dal.ca/~dparks/CornerDetection/harris.htm - ..[2] http://en.wikipedia.org/wiki/Corner_detection + .. [1] http://kiwi.cs.dal.ca/~dparks/CornerDetection/harris.htm + .. [2] http://en.wikipedia.org/wiki/Corner_detection Examples -------- @@ -206,8 +206,8 @@ def corner_shi_tomasi(image, sigma=1): References ---------- - ..[1] http://kiwi.cs.dal.ca/~dparks/CornerDetection/harris.htm - ..[2] http://en.wikipedia.org/wiki/Corner_detection + .. [1] http://kiwi.cs.dal.ca/~dparks/CornerDetection/harris.htm + .. [2] http://en.wikipedia.org/wiki/Corner_detection Examples -------- @@ -272,9 +272,9 @@ def corner_foerstner(image, sigma=1): References ---------- - ..[1] http://www.ipb.uni-bonn.de/uploads/tx_ikgpublication/\ - foerstner87.fast.pdf - ..[2] http://en.wikipedia.org/wiki/Corner_detection + .. [1] http://www.ipb.uni-bonn.de/uploads/tx_ikgpublication/\ + foerstner87.fast.pdf + .. [2] http://en.wikipedia.org/wiki/Corner_detection Examples -------- @@ -338,9 +338,9 @@ def corner_subpix(image, corners, window_size=11, alpha=0.99): References ---------- - ..[1] http://www.ipb.uni-bonn.de/uploads/tx_ikgpublication/\ - foerstner87.fast.pdf - ..[2] http://en.wikipedia.org/wiki/Corner_detection + .. [1] http://www.ipb.uni-bonn.de/uploads/tx_ikgpublication/\ + foerstner87.fast.pdf + .. [2] http://en.wikipedia.org/wiki/Corner_detection """