Source code for menpo.visualize.base

# This has to go above the default importers to prevent cyclical importing
import abc

from collections import Iterable


class Renderer(object):
    r"""
    Abstract class for rendering visualizations. Framework specific
    implementations of these classes are made in order to separate
    implementation cleanly from the rest of the code.

    It is assumed that the renderers follow some form of stateful pattern for
    rendering to Figures. Therefore, the major interface for rendering involves
    providing a `figure_id` or a boolean about whether a new figure should
    be used. If neither are provided then the default state of the rendering
    engine is assumed to maintained.

    Providing a `figure_id` and `new_figure == True` is not a valid state.

    Parameters
    ----------
    figure_id : object
        A figure id. Could be any valid object that identifies
        a figure in a given framework (string, int, etc)
    new_figure : bool
        Whether the rendering engine should create a new figure.

    Raises
    ------
    ValueError
        It is not valid to provide a figure id AND request a new figure to
        be rendered on.
    """

    __metaclass__ = abc.ABCMeta

    def __init__(self, figure_id, new_figure):
        if figure_id is not None and new_figure:
            raise ValueError("Conflicting arguments. figure_id cannot be "
                             "specified if the new_figure flag is True")

        self.figure_id = figure_id
        self.new_figure = new_figure
        self.figure = self.get_figure()

    def render(self, **kwargs):
        r"""
        Render the object on the figure given at instantiation.

        Parameters
        ----------
        kwargs : dict
            Passed through to specific rendering engine.

        Returns
        -------
        viewer : :class:`Renderer`
            Pointer to `self`.
        """
        return self._render(**kwargs)

    @abc.abstractmethod
    def _render(self, **kwargs):
        r"""
        Abstract method to be overridden the renderer. This will implement the
        actual rendering code for a given object class.

        Parameters
        ----------
        kwargs : dict
            Options to be set when rendering.

        Returns
        -------
        viewer : :class:`Renderer`
            Pointer to `self`.
        """
        pass

    @abc.abstractmethod
    def get_figure(self):
        r"""
        Abstract method for getting the correct figure to render on. Should
        also set the correct `figure_id` for the figure.

        Returns
        -------
        figure : object
            The figure object that the renderer will render on.
        """
        pass


[docs]class Viewable(object): r""" Abstract interface for objects that can visualize themselves. """ __metaclass__ = abc.ABCMeta
[docs] def view_on(self, figure_id, **kwargs): r""" View the object on a a specific figure specified by the given id. Parameters ---------- figure_id : object A unique identifier for a figure. kwargs : dict Passed through to specific rendering engine. Returns ------- viewer : :class:`Renderer` The renderer instantiated. """ return self._view(figure_id=figure_id, **kwargs)
[docs] def view_new(self, **kwargs): r""" View the object on a new figure. Parameters ---------- kwargs : dict Passed through to specific rendering engine. Returns ------- viewer : :class:`Renderer` The renderer instantiated. """ return self._view(new_figure=True, **kwargs)
[docs] def view(self, **kwargs): r""" View the object using the default rendering engine figure handling. For example, the default behaviour for Matplotlib is that all draw commands are applied to the same `figure` object. Parameters ---------- kwargs : dict Passed through to specific rendering engine. Returns ------- viewer : :class:`Renderer` The renderer instantiated. """ return self._view(**kwargs)
@abc.abstractmethod def _view(self, figure_id=None, new_figure=False, **kwargs): r""" Abstract method to be overridden by viewable objects. This will instantiate a specific visualisation implementation Parameters ---------- figure_id : object, optional A unique identifier for a figure. Default: `None` new_figure : bool, optional Whether the rendering engine should create a new figure. Default: `False` kwargs : dict Passed through to specific rendering engine. Returns ------- viewer : :class:`Renderer` The renderer instantiated. """ pass
from menpo.visualize.viewmayavi import MayaviPointCloudViewer3d, \ MayaviTriMeshViewer3d, MayaviTexturedTriMeshViewer3d, \ MayaviLandmarkViewer3d, MayaviVectorViewer3d, MayaviSurfaceViewer3d, \ MayaviColouredTriMeshViewer3d from menpo.visualize.viewmatplotlib import MatplotlibImageViewer2d, \ MatplotlibImageSubplotsViewer2d, MatplotlibPointCloudViewer2d, \ MatplotlibLandmarkViewer2d, MatplotlibLandmarkViewer2dImage, \ MatplotlibTriMeshViewer2d, MatplotlibAlignmentViewer2d, \ MatplotlibGraphPlotter, MatplotlibMultiImageViewer2d, \ MatplotlibMultiImageSubplotsViewer2d, MatplotlibFittingViewer2d, \ MatplotlibFittingSubplotsViewer2d # Default importer types PointCloudViewer2d = MatplotlibPointCloudViewer2d PointCloudViewer3d = MayaviPointCloudViewer3d TriMeshViewer2d = MatplotlibTriMeshViewer2d TriMeshViewer3d = MayaviTriMeshViewer3d TexturedTriMeshViewer3d = MayaviTexturedTriMeshViewer3d ColouredTriMeshViewer3d = MayaviColouredTriMeshViewer3d LandmarkViewer3d = MayaviLandmarkViewer3d LandmarkViewer2d = MatplotlibLandmarkViewer2d LandmarkViewer2dImage = MatplotlibLandmarkViewer2dImage ImageViewer2d = MatplotlibImageViewer2d ImageSubplotsViewer2d = MatplotlibImageSubplotsViewer2d VectorViewer3d = MayaviVectorViewer3d AlignmentViewer2d = MatplotlibAlignmentViewer2d GraphPlotter = MatplotlibGraphPlotter MultiImageViewer2d = MatplotlibMultiImageViewer2d MultiImageSubplotsViewer2d = MatplotlibMultiImageSubplotsViewer2d FittingViewer2d = MatplotlibFittingViewer2d FittingSubplotsViewer2d = MatplotlibFittingSubplotsViewer2d
[docs]class LandmarkViewer(object): r""" Base Landmark viewer that abstracts away dimensionality Parameters ---------- figure_id : object A figure id. Could be any valid object that identifies a figure in a given framework (string, int, etc) new_figure : bool Whether the rendering engine should create a new figure. group_label : string The main label of the landmark set. pointcloud : :class:`menpo.shape.pointcloud.PointCloud` The pointclouds representing the landmarks. labels_to_masks : dict(string, ndarray) A dictionary of labels to masks into the pointcloud that represent which points belong to the given label. target : :class:`menpo.landmarks.base.Landmarkable` The parent shape that we are drawing the landmarks for. """ def __init__(self, figure_id, new_figure, group_label, pointcloud, labels_to_masks, target): self.pointcloud = pointcloud self.group_label = group_label self.labels_to_masks = labels_to_masks self.target = target self.figure_id = figure_id self.new_figure = new_figure
[docs] def render(self, **kwargs): r""" Select the correct type of landmark viewer for the given parent shape. Parameters ---------- kwargs : dict Passed through to landmark viewer. Returns ------- viewer : :class:`Renderer` The rendering object. Raises ------ DimensionalityError Only 2D and 3D viewers are supported. """ if self.pointcloud.n_dims == 2: from menpo.image.base import Image if isinstance(self.target, Image): return LandmarkViewer2dImage( self.figure_id, self.new_figure, self.group_label, self.pointcloud, self.labels_to_masks).render(**kwargs) else: return LandmarkViewer2d(self.figure_id, self.new_figure, self.group_label, self.pointcloud, self.labels_to_masks).render(**kwargs) elif self.pointcloud.n_dims == 3: return LandmarkViewer3d(self.figure_id, self.new_figure, self.group_label, self.pointcloud, self.labels_to_masks).render(**kwargs) else: raise ValueError("Only 2D and 3D landmarks are " "currently supported")
[docs]class PointCloudViewer(object): r""" Base PointCloud viewer that abstracts away dimensionality. Parameters ---------- figure_id : object A figure id. Could be any valid object that identifies a figure in a given framework (string, int, etc) new_figure : bool Whether the rendering engine should create a new figure. points : (N, D) ndarray The points to render. """ def __init__(self, figure_id, new_figure, points): self.figure_id = figure_id self.new_figure = new_figure self.points = points
[docs] def render(self, **kwargs): r""" Select the correct type of pointcloud viewer for the given pointcloud dimensionality. Parameters ---------- kwargs : dict Passed through to pointcloud viewer. Returns ------- viewer : :class:`Renderer` The rendering object. Raises ------ DimensionalityError Only 2D and 3D viewers are supported. """ if self.points.shape[1] == 2: return PointCloudViewer2d(self.figure_id, self.new_figure, self.points).render(**kwargs) elif self.points.shape[1] == 3: return PointCloudViewer3d(self.figure_id, self.new_figure, self.points).render(**kwargs) else: raise ValueError("Only 2D and 3D pointclouds are " "currently supported")
class ImageViewer(object): r""" Base Image viewer that abstracts away dimensionality. It can visualize multiple channels of an image in subplots. Parameters ---------- figure_id : object A figure id. Could be any valid object that identifies a figure in a given framework (string, int, etc) new_figure : bool Whether the rendering engine should create a new figure. dimensions : {2, 3} int The number of dimensions in the image pixels : (N, D) ndarray The pixels to render. channels: int or list or 'all' or None A specific selection of channels to render. The user can choose either a single or multiple channels. If all, render all channels in subplot mode. If None and channels are less than 36, render them all. If None and channels are more than 36, render the first 36. Default: None mask: (N, D) ndarray A boolean mask to be applied to the image. All points outside the mask are set to 0. """ def __init__(self, figure_id, new_figure, dimensions, pixels, channels=None, mask=None): pixels = pixels.copy() self.figure_id = figure_id self.new_figure = new_figure self.dimensions = dimensions pixels, self.use_subplots = \ self._parse_channels(channels, pixels) self.pixels = self._masked_pixels(pixels, mask) def _parse_channels(self, channels, pixels): r""" Parse channels parameter. If channels is int or list, keep it as is. If channels is all, return a list of all the image's channels. If channels is None, return the minimum between an upper_limit and the image's number of channels. If image is grayscale or RGB and channels is None, then do not plot channels in different subplots. Parameters ---------- channels: int or list or 'all' or None A specific selection of channels to render. pixels : (N, D) ndarray The image's pixels to render. upper_limit: int The upper limit of subplots for the channels=None case. """ # Flag to trigger ImageSubplotsViewer2d or ImageViewer2d use_subplots = True n_channels = pixels.shape[2] if channels is None: if n_channels == 1: pixels = pixels[..., 0] use_subplots = False elif n_channels == 3: use_subplots = False elif channels != 'all': if isinstance(channels, Iterable): pixels = pixels[..., channels] else: pixels = pixels[..., channels] use_subplots = False return pixels, use_subplots def _masked_pixels(self, pixels, mask): r""" Return the masked pixels using a given boolean mask. In order to make sure that the non-masked pixels are visualized in black, their value is set to the minimum between min(pixels) and 0. Parameters ---------- pixels : (N, D) ndarray The image's pixels to render. mask: (N, D) ndarray A boolean mask to be applied to the image. All points outside the mask are set to 0. If mask is None, then the initial pixels are returned. """ if mask is not None: pixels[~mask] = min(pixels.min(), 0) return pixels def render(self, **kwargs): r""" Select the correct type of image viewer for the given image dimensionality. Parameters ---------- kwargs : dict Passed through to image viewer. Returns ------- viewer : :class:`Renderer` The rendering object. Raises ------ DimensionalityError Only 2D images are supported. """ if self.dimensions == 2: if self.use_subplots: return ImageSubplotsViewer2d(self.figure_id, self.new_figure, self.pixels).render(**kwargs) else: return ImageViewer2d(self.figure_id, self.new_figure, self.pixels).render(**kwargs) else: raise ValueError("Only 2D images are currently supported")
[docs]class TriMeshViewer(object): r""" Base TriMesh viewer that abstracts away dimensionality. Parameters ---------- figure_id : object A figure id. Could be any valid object that identifies a figure in a given framework (string, int, etc) new_figure : bool Whether the rendering engine should create a new figure. points : (N, D) ndarray The points to render. trilist : (M, 3) ndarray The triangulation for the points. """ def __init__(self, figure_id, new_figure, points, trilist): self.figure_id = figure_id self.new_figure = new_figure self.points = points self.trilist = trilist
[docs] def render(self, **kwargs): r""" Select the correct type of trimesh viewer for the given trimesh dimensionality. Parameters ---------- kwargs : dict Passed through to trimesh viewer. Returns ------- viewer : :class:`Renderer` The rendering object. Raises ------ DimensionalityError Only 2D and 3D viewers are supported. """ if self.points.shape[1] == 2: return TriMeshViewer2d(self.figure_id, self.new_figure, self.points, self.trilist).render(**kwargs) elif self.points.shape[1] == 3: return TriMeshViewer3d(self.figure_id, self.new_figure, self.points, self.trilist).render(**kwargs) else: raise ValueError("Only 2D and 3D TriMeshes " "are currently supported")
class MultipleImageViewer(ImageViewer): def __init__(self, figure_id, new_figure, dimensions, pixels_list, channels=None, mask=None): super(MultipleImageViewer, self).__init__( figure_id, new_figure, dimensions, pixels_list[0], channels=channels, mask=mask) pixels_list = [self._parse_channels(channels, p)[0] for p in pixels_list] self.pixels_list = [self._masked_pixels(p, mask) for p in pixels_list] def render(self, **kwargs): if self.dimensions == 2: if self.use_subplots: MultiImageSubplotsViewer2d(self.figure_id, self.new_figure, self.pixels_list).render(**kwargs) else: return MultiImageViewer2d(self.figure_id, self.new_figure, self.pixels_list).render(**kwargs) else: raise ValueError("Only 2D images are currently supported") class FittingViewer(ImageViewer): def __init__(self, figure_id, new_figure, dimensions, pixels, target_list, channels=None, mask=None): super(FittingViewer, self).__init__( figure_id, new_figure, dimensions, pixels, channels=channels, mask=mask) self.target_list = target_list def render(self, **kwargs): if self.dimensions == 2: if self.use_subplots: FittingSubplotsViewer2d( self.figure_id, self.new_figure, self.pixels, self.target_list).render(**kwargs) else: return FittingViewer2d( self.figure_id, self.new_figure, self.pixels, self.target_list).render(**kwargs) else: raise ValueError("Only 2D images are currently supported")