"""
scikit-image viewer plugins and widgets.
"""
from skimage import viewer, draw, filters, exposure, measure, color, morphology
import scipy.ndimage as nd
import numpy as np
import pdb
##
# Viewer
##
class ImageViewer(viewer.viewers.ImageViewer):
"override viewer to not emit plugin._update_original_image"
#copied from scikit-image
def __add__(self, plugin):
"""Add plugin to ImageViewer"""
plugin.attach(self)
# do not emit
# self.original_image_changed.connect(plugin._update_original_image)
if plugin.dock:
location = self.dock_areas[plugin.dock]
dock_location = viewer.qt.Qt.DockWidgetArea(location)
dock = viewer.qt.QtWidgets.QDockWidget()
dock.setWidget(plugin)
dock.setWindowTitle(plugin.name)
self.addDockWidget(dock_location, dock)
horiz = (self.dock_areas['left'], self.dock_areas['right'])
dimension = 'width' if location in horiz else 'height'
self._add_widget_size(plugin, dimension=dimension)
return self
def show(self):
"Call filter_image of first plugin, then super.show()"
# find first plugin which have image_filter != None
for plugin in self.plugins:
if plugin.image_filter:
plugin.filter_image()
break
return super(ImageViewer, self).show()
##
# Plugins
##
class SeriesPlugin(viewer.plugins.Plugin):
"Attach widgets in series. Output of one plugin is sent to the next one."
def attach(self, image_viewer):
"""Override attach to link plugins with plugin.image_changed instead
of all listening to image_viewer.original_image_changed
"""
self.dock = 'right'
self.setParent(image_viewer)
self.setWindowFlags(viewer.qt.QtCore.Qt.Dialog)
self.image_viewer = image_viewer
if len(image_viewer.plugins) == 0:
self.arguments = [image_viewer.image]
image_viewer.original_image_changed.connect(self._update_original_image)
else:
self.arguments = [image_viewer.plugins[-1].arguments[0]]
image_viewer.plugins[-1].image_changed.connect(self._update_original_image)
image_viewer.plugins.append(self)
# do not filter image, wait until plugin.show is called
#self.filter_image()
class EnablePlugin(SeriesPlugin):
"Plugin with checkbox for enable/disable"
def __init__(self, **kwargs):
super(EnablePlugin, self).__init__(**kwargs)
enable = viewer.widgets.CheckBox('enabled', value=True, ptype='plugin')
self.add_widget(enable)
self.enabled = True
def update_plugin(self, name, val):
super(EnablePlugin, self).update_plugin(name,val)
self.filter_image()
def filter_image(self, **kwargs):
"Filter if plugin enabled and we have image."
if self.enabled and len(self.arguments):
super(EnablePlugin, self).filter_image(**kwargs)
elif len(self.arguments):
self.display_filtered_image(self.arguments[0])
self.image_changed.emit(self.arguments[0])
class SelemPlugin(EnablePlugin):
"""Add selem size widget for filters that use selem, instead of defining a
separate filter-function for each of them.
"""
selem_size = 2
def __init__(self, **kwargs):
super(SelemPlugin, self).__init__(**kwargs)
size = viewer.widgets.Slider('selem', low=1, high=10,
value=self.selem_size, value_type='float', ptype='plugin',
update_on='release')
self.add_widget(size)
size.callback = self.update_selem
self.keyword_arguments['selem'] = morphology.disk(self.selem_size)
def update_selem(self, name, value):
self.keyword_arguments['selem'] = morphology.disk(value)
self.filter_image()
class CropPlugin(SeriesPlugin):
"Crop plugin with reset button"
def __init__(self, maxdist=10, **kwargs):
super(CropPlugin, self).__init__(**kwargs)
self.name = 'Crop'
self.maxdist = maxdist
def attach(self, image_viewer):
super(CropPlugin, self).attach(image_viewer)
self.rect_tool = viewer.canvastools.RectangleTool(image_viewer,
maxdist=self.maxdist,
on_enter=self.crop)
self.artists.append(self.rect_tool)
self.add_widget(ResetWidget())
def crop(self, extents):
xmin, xmax, ymin, ymax = extents
cropped = self.arguments[0][ymin:ymax+1, xmin:xmax+1]
self.display_filtered_image(cropped)
self.image_changed.emit(cropped)
class EntropyPlugin(SelemPlugin):
name = "Entropy"
def image_filter(self, img, selem, **kwargs):
ent = filters.rank.entropy(img, selem)
return exposure.rescale_intensity(ent)
class HistogramWidthPlugin(SelemPlugin):
name = "Histogram width"
selem_size = 2
def image_filter(self, img, selem, **kwargs):
filtered = filters.rank.pop_bilateral(img, selem, s0=2, s1=2)
thresh = filters.threshold_li(filtered)
return filtered < thresh
class OtsuPlugin(EnablePlugin):
name = "Otsu"
def image_filter(self, image, **kwargs):
t = filters.threshold_otsu(image)
return image >= t
class ErosionPlugin(SelemPlugin):
name = "Erosion"
selem_size = 2.5
def image_filter(self, image, selem, **kwargs):
from skimage import morphology
return morphology.erosion(image, selem)
class DilationPlugin(SelemPlugin):
name = "Dilation"
selem_size = 2
def image_filter(self, image, selem, **kwargs):
from skimage import morphology
return morphology.dilation(image, selem)
class MinimumAreaPlugin(EnablePlugin):
def __init__(self, minimum_area=1000, **kwargs):
super(MinimumAreaPlugin, self).__init__(**kwargs)
self.name = "Minimum area"
area = viewer.widgets.Slider('minimum_area', low=1, high=10000, value=minimum_area,
value_type='int')
self.add_widget(area)
def image_filter(self, img, minimum_area, **kwargs):
from numpy import bincount
labels = measure.label(img)
counts = bincount(labels.ravel())
# set background count to zero
counts[counts.argmax()] = 0
mask = counts > minimum_area
return mask[labels]
class FillHolesPlugin(EnablePlugin):
def __init__(self, **kwargs):
super(FillHolesPlugin, self).__init__(**kwargs)
self.name = 'Fill holes'
self.add_widget(viewer.widgets.CheckBox('clear_border'))
self.add_widget(viewer.widgets.Slider('zero_border', low=0, high=20,
value=3, value_type='int'))
def image_filter(self, img, clear_border, zero_border):
cleared = img.copy()
if zero_border:
a = zero_border
cleared[ :a,:] = 0
cleared[-a:,:] = 0
cleared[:, :a] = 0
cleared[:,-a:] = 0
if clear_border:
segmentation.clear_border(cleared)
return nd.morphology.binary_fill_holes(cleared)
class LabelPlugin(EnablePlugin):
name = 'Label'
def image_filter(self, img, **kwargs):
l = measure.label(img, background=0)
if l.max() > 2**16-1:
print('more than 2^16 labels, aborting labeling')
return img
return color.label2rgb(l, image=self.image_viewer.original_image)
#bg_color=(0,0,0))
class RegionPlugin(EnablePlugin):
name = 'Region'
def image_filter(self, img):
labels = measure.label(img, background=0)
self.circles = np.zeros_like(labels)
self.regions = [r for r in measure.regionprops(labels)]
rs = [reg.equivalent_diameter/2 for reg in self.regions]
# median as representation for area
r = np.median(rs)
for region in self.regions:
# draw circle around regions of interest
rr, cc = draw.circle(*region.centroid + (r,))
self.circles[rr, cc] = region.label
# creat .x and .y property for easy access
region.y, region.x, region.y_end, region.x_end = region.bbox
self.regions = set_well_positions(self.regions)
# set background to -1, label2rbg will not draw -1
# ** this will change in skimage v0.12 -1 -> 0 **
self.circles[self.circles==0] = -1
# return overlay image
return color.label2rgb(self.circles, image=self.image_viewer.original_image)
def output(self):
return (self.circles, self.regions)
##
# Widgets
##
class ResetWidget(viewer.widgets.BaseWidget):
"Reset button which sets image to original_image"
def __init__(self):
super(ResetWidget, self).__init__(self)
self.reset_button = viewer.qt.QtGui.QPushButton('Reset')
self.reset_button.clicked.connect(self.reset)
self.layout = viewer.qt.QtGui.QHBoxLayout(self)
self.layout.addWidget(self.reset_button)
def reset(self):
img = self.plugin.image_viewer.original_image.copy()
self.plugin.display_filtered_image(img)
self.plugin.image_changed.emit(img)
[docs]def set_well_positions(regions):
"""Set property well_x/y on region. Helper function for RegionPlugin.
Parameters
----------
regions : list of skimage.regionprops
Region should also have set ``x`` and ``y`` property.
Returns
-------
list of skimage.regionprops
Regions with extra property ``well_x`` and ``well_y`` set.
"""
for direction in ['x', 'y']:
regions = sorted(regions, key=lambda r: getattr(r, direction))
gradients = np.gradient([getattr(r, direction) for r in regions])
gradient_treshold = max(gradients) / 2
# add well_x/y property to region
well = 0
previous = regions[0]
for region in regions:
dx = getattr(region, direction) - getattr(previous, direction)
# if gradient to prev coordinate is high, we have a new row/column
if dx > gradient_treshold:
well += 1
setattr(region, 'well_' + direction, well)
previous = region
return regions