Source code for nutsml.viewer

"""
.. module:: viewer
   :synopsis: Viewing of sample data
"""

from __future__ import print_function
from __future__ import absolute_import

import time

import numpy as np
import nutsml.imageutil as iu

from six.moves import range
from nutsflow import NutFunction, nut_function
from nutsflow.common import as_tuple, as_set, stype
from matplotlib import pyplot as plt


# TODO: Fix deprecation warning
# MatplotlibDeprecationWarning: Using default event loop until function specific
# to this GUI is implemented
[docs]class ViewImage(NutFunction): # pragma no coverage """ Display images in window. """
[docs] def __init__(self, imgcols, layout=(1, None), figsize=None, pause=0.0001, axis_off=False, labels_off=False, titles=None, every_sec=0, every_n=0, **imargs): """ iterable >> ViewImage(imgcols, layout=(1, None), figsize=None, **plotargs) | Images should be numpy arrays in one of the following formats: | MxN - luminance (grayscale, float array only) | MxNx3 - RGB (float or uint8 array) | MxNx4 - RGBA (float or uint8 array) Shapes with single-dimension axis are supported but not encouraged, e.g. MxNx1 will be converted to MxN. See http://matplotlib.org/api/pyplot_api.html#matplotlib.pyplot.imshow >>> from nutsflow import Consume >>> from nutsml import ReadImage >>> imagepath = 'tests/data/img_formats/*.jpg' >>> samples = [(1, 'nut_color'), (2, 'nut_grayscale')] >>> read_image = ReadImage(1, imagepath) >>> samples >> read_image >> ViewImage(1) >> Consume() # doctest: +SKIP >>> view_gray = ViewImage(1, cmap='gray') >>> samples >> read_image >> view_gray >> Consume() # doctest: +SKIP :param int|tuple|None imgcols: Index or tuple of indices of data columns containing images (ndarray). Use None if images are provided directly, e.g. [img1, img2, ...] >> ViewImage(None) >> Consume() :param tuple layout: Rows and columns of the viewer layout., e.g. a layout of (2,3) means that 6 images in the data are arranged in 2 rows and 3 columns. Number of cols can be None is then derived from imgcols :param tuple figsize: Figure size in inch. :param float pause: Waiting time in seconds after each plot. Pressing a key skips the waiting time. :param bool axis_off: Enable or disable display of figure axes. :param bool lables_off: Enable or disable display of axes labels. :param float every_sec: View every given second, e.g. to print every 2.5 sec every_sec = 2.5 :param int every_n: View every n-th call. :param kwargs imargs: Keyword arguments passed on to matplotlib's imshow() function, e.g. cmap='gray'. See http://matplotlib.org/api/pyplot_api.html#matplotlib.pyplot.imshow """ imgcols = (None,) if imgcols is None else as_tuple(imgcols) r, c, n = layout[0], layout[1], len(imgcols) if c is None: c = n if n != r * c: raise ValueError("Number of images and layout don't match!") fig = plt.figure(figsize=figsize) fig.canvas.set_window_title('ViewImage') self.axes = [fig.add_subplot(r, c, i + 1) for i in range(n)] self.axis_off = axis_off self.labels_off = labels_off self.titles = titles self.imgcols = imgcols self.pause = pause self.cnt = 0 self.time = time.time() self.every_sec = every_sec self.every_n = every_n self.imargs = imargs
def __delta_sec(self): """Return time in seconds (float) consumed between prints so far""" return time.time() - self.time def __should_view(self): """Return true if data should be viewed""" self.cnt += 1 return (self.cnt >= self.every_n and self.__delta_sec() >= self.every_sec)
[docs] def __call__(self, data): """ View the images in data :param tuple data: Data with images at imgcols. :return: unchanged input data :rtype: tuple """ if not self.__should_view(): return data self.cnt = 0 self.time = time.time() for i, (imgcol, ax) in enumerate(zip(self.imgcols, self.axes)): ax.clear() if self.axis_off: ax.set_axis_off() if self.labels_off: ax.get_xaxis().set_visible(False) ax.get_yaxis().set_visible(False) if self.titles: ax.set_title(self.titles[i]) img = data if imgcol is None else data[imgcol] img = np.squeeze(img) ax.imshow(img, **self.imargs) ax.figure.canvas.draw() plt.waitforbuttonpress(timeout=self.pause) # or plt.pause(self.pause) return data
[docs]class ViewImageAnnotation(NutFunction): # pragma no coverage """ Display images and annotation in window. """ TEXTPROP = {'edgecolor': 'k', 'backgroundcolor': (1, 1, 1, 0.5)} SHAPEPROP = {'edgecolor': 'y', 'facecolor': 'none', 'linewidth': 1}
[docs] def __init__(self, imgcol, annocols, figsize=None, pause=0.0001, interpolation=None, **annoargs): """ iterable >> ViewImageAnnotation(imgcol, annocols, figsize=None, pause, interpolation, **annoargs) | Images must be numpy arrays in one of the following formats: | MxN - luminance (grayscale, float array only) | MxNx3 - RGB (float or uint8 array) | MxNx4 - RGBA (float or uint8 array) | See | http://matplotlib.org/api/pyplot_api.html#matplotlib.pyplot.imshow Shapes with single-dimension axis are supported but not encouraged, e.g. MxNx1 will be converted to MxN. :param int imgcol: Index of data column that contains the image :param int|tuple annocols: Index or tuple of indices specifying the data column(s) that contain annotation (labels, or geometry) :param tuple figsize: Figure size in inch. :param float pause: Waiting time in seconds after each plot. Pressing a key skips the waiting time. :param string interpolation: Interpolation for imshow, e.g. 'nearest', 'bilinear', 'bicubic'. for details see http://matplotlib.org/api/pyplot_api.html#matplotlib.pyplot .imshow :param kwargs annoargs: Keyword arguments for visual properties of annotation, e.g. edgecolor='y', linewidth=1 """ fig = plt.figure(figsize=figsize) fig.canvas.set_window_title('ViewImageAnnotation') self.axes = fig.add_subplot(111) self.imgcol = imgcol self.annocols = as_set(annocols) self.pause = pause self.interpolation = interpolation self.annoargs = annoargs
def _shapeprops(self): """Return shape properties from kwargs or default value.""" aa = ViewImageAnnotation.SHAPEPROP.copy() aa.update(self.annoargs) return aa def _textprop(self, key): """Return text property from kwargs or default value.""" return self.annoargs.get(key, ViewImageAnnotation.TEXTPROP[key])
[docs] def __call__(self, data): """ View the image and its annotation :param tuple data: Data with image at imgcol and annotation at annocol. :return: unchanged input data :rtype: tuple """ img = np.squeeze(data[self.imgcol]) ax = self.axes ax.clear() ax.imshow(img, interpolation=self.interpolation) labelcol = 0.7 for acol in self.annocols: annos = data[acol] if isinstance(annos, (list, tuple)): props = self._shapeprops() for anno in iu.annotation2pltpatch(annos, **props): ax.add_patch(anno) else: fs = ax.get_window_extent().height / 22 p = img.shape[0] / 6 x, y = p / 2, p * labelcol labelcol += 1 ax.text(x, y, str(annos), color=self._textprop('edgecolor'), backgroundcolor=self._textprop('backgroundcolor'), size=fs, family='monospace') ax.figure.canvas.draw() plt.waitforbuttonpress(timeout=self.pause) return data