Source code for motion_correction.pyimof.display

# coding: utf-8
"""Collection of utils and functions for the visualization of vector
fields.

"""

import matplotlib.pyplot as plt
import numpy as np
import skimage


def _middlebury():
    """Compute the colors used to generate the `middlebury` colormap.

    This colormap is inspired by the middlebury evaluation for optical
    flow algorithms [1]_. The RGB values are extracted from the matlab
    code [2]_

    Returns
    -------
    cmap : ~numpy.ndarray
        The colors used to generate the 'middlebury' colormap.

    References
    ----------
    .. [1] http://vision.middlebury.edu/flow/
    .. [2] http://vision.middlebury.edu/flow/code/flow-code-matlab.zip

    """

    col_len = [0, 15, 6, 4, 11, 13, 6]
    col_range = np.cumsum(col_len)
    ncol = col_range[-1]
    cmap = np.zeros((ncol, 3))

    for idx, (i0, i1, l) in enumerate(zip(col_range[:-1], col_range[1:], col_len[1:])):
        j0 = (idx // 2) % 3
        j1 = (j0 + 1) % 3
        if idx & 1:
            cmap[i0:i1, j0] = 1 - np.arange(l) / l
            cmap[i0:i1, j1] = 1
        else:
            cmap[i0:i1, j0] = 1
            cmap[i0:i1, j1] = np.arange(l) / l

    return cmap


[docs] def flow_to_color(u, v, cmap=None, scale=True): """Apply color code to a vector field according to its orientation and magnitude. Any colormap compatible with matplotlib can be applyed but circular colormaps are recommanded ( for example 'huv', 'twilight', 'twilight_shifted' and the builtin 'middlebury' colormaps). If cmap is None, the HSV image defined using optical flow orientation (hue) and magnitude (saturation) is returned. Parameters ---------- u : ~numpy.ndarray The horizontal component of the vector field. v : ~numpy.ndarray The vertical component of the vector field. cmap : str (optional) The colormap used to color code the input vector field. scale : bool (optional) whether to scale output saturation according to magnitude. Returns ------- img : ~numpy.ndarray RGBA image representing the desired color code applyed to the vector field. """ flow = u + 1j * v magnitude = np.absolute(flow) angle = np.mod(-np.angle(flow), 2 * np.pi) # Normalize flow direction angle /= 2 * np.pi # Map the magnitude between 0 and 1 magnitude -= magnitude.min() magnitude /= magnitude.max() if cmap is None: # Create the corresponding HSV image nl, nc = u.shape hsv = np.ones((nl, nc, 3)) hsv[..., 0] = angle hsv[..., 2] = magnitude img = skimage.color.hsv2rgb(hsv) else: lut = plt.cm.get_cmap(cmap) img = lut(angle) if scale: # Scale saturation according to magnitude img[..., 3] = magnitude return img
[docs] def color_wheel(u=None, v=None, nr=50, ntheta=1025): """Compute the discretization of a wheel used to describe the color code used to display a vector field (u, v). If the vector field (u, v) is provided, the radius of the wheel is equal to its maximum magnitude. Otherwise (i.e. if any of u and v is None), the radius is set to 1. Parameters ---------- u : ~numpy.ndarray (optional) The horizontal component of the vector field (default: None). v : ~numpy.ndarray (optional) The vertical component of the vector field (default: None). nr : int (optional) The number of steps used to discretise the wheel radius. ntheta : int (optional) The number of steps used to discretise the wheel sectors. Returns ------- angle, radius: tuple[~numpy.ndarray] The grid discretisation of the wheel sectors and radius. """ max_rad = 1 if u is not None or v is not None: max_rad = np.sqrt(u * u + v * v).max() radius, angle = np.mgrid[: max_rad : nr * 1j, 0 : 2 * np.pi : ntheta * 1j] return angle, radius
[docs] def get_tight_figsize(I): """Computes the matplotlib figure tight size respecting image proportions. Parameters ---------- I : ~numpy.ndarray The image to be displayed. Returns ------- w, h : tuple[float] The width and height in inch of the desired figure. """ nl, nc = I.shape[:2] dpi = plt.rcParams["figure.dpi"] dimBound = max(plt.rcParams["figure.figsize"]) h = float(nl) / dpi w = float(nc) / dpi maxDim = max(h, w) redFact = dimBound / maxDim h *= redFact w *= redFact return w, h
[docs] def plot(u, v, ax=None, cmap="middlebury", scale=True, colorwheel=True): """Plots the color coded vector field. Parameters ---------- u : ~numpy.ndarray The horizontal component of the vector field. v : ~numpy.ndarray The vertical component of the vector field. ax : ~matplotlib.pyplot.Axes (optional) Optional matplotlib axes used to plot the image. If None, the image is displayed in a tight figure. cmap : str (optional) The colormap used to color code the input vector field. scale : bool (optional) whether to scale output saturation according to magnitude. colorwheel : bool (optional) whether to display the color wheel describing the images colors or not. Returns ------- ax : ~matplotlib.pyplot.Axes The matplotlib axes where the image is displayed. """ img = flow_to_color(u, v, cmap, scale) if ax is None: nl, nc = u.shape figsize = get_tight_figsize(img) plt.figure(figsize=figsize, facecolor=(1.0, 1.0, 1.0)) ax = plt.axes([0, 0, 1, 1], frameon=False) ax.imshow(img) ax.set_axis_off() if colorwheel: if cmap is None: cmap = "hsv" bbox = ax.get_position() w, h = bbox.width, bbox.height X0, Y0 = bbox.x0, bbox.y0 x0, y0 = X0 + 0.01 * w, Y0 + 0.79 * h fig = ax.get_figure() ax2 = fig.add_axes([x0, y0, w * 0.2, h * 0.2], polar=1) angle, rad = color_wheel(u, v) ax2.pcolormesh(angle, rad, angle, cmap=cmap) ax2.set_xticks([]) return ax
[docs] def quiver(u, v, c=None, bg=None, ax=None, step=None, nvec=50, bg_cmap=None, **kwargs): """Draws a quiver plot representing a dense vector field. Parameters ---------- u : ~numpy.ndarray (with shape m×n) The horizontal component of the vector field. v : ~numpy.ndarray (with shape m×n) The vertical component of the vector field. c : ~numpy.ndarray (optional (with shape m×n)) Values used to color the arrows. bg : ~numpy.ndarray (2D or 3D optional) Background image. ax : ~matplotlib.pyplot.Axes (optional) Axes used to plot the image. If None, the image is displayed in a tight figure. step : int (optional) The grid step used to display the vector field. If None, it is computed using the nvec parameter. nvec : int The maximum number of vector over all the grid dimentions. It is ignored if the step parameter is not None. bg_cmap : str (optional) The colormap used to color the background image. Notes ----- Any other :func:`matplotlib.pyplot.quiver` valid keyword can be used, knowing that some are fixed - units = 'dots' - angles = 'xy' - scale = 'xy' Returns ------- ax : ~matplotlib.pyplot.Axes The matplotlib axes where the vector field is displayed. """ if ax is None: figsize = get_tight_figsize(u) plt.figure(figsize=figsize, facecolor=(1.0, 1.0, 1.0)) ax = plt.axes([0, 0, 1, 1], frameon=False) ax.set_axis_off() nl, nc = u.shape if step is None: step = max(nl // nvec, nc // nvec) y, x = np.mgrid[:nl:step, :nc:step] u_ = u[::step, ::step] v_ = v[::step, ::step] idx = np.logical_and( np.logical_and(x + u_ >= 0, x + u_ <= nc - 1), np.logical_and(y + v_ >= 0, y + v_ <= nl - 1), ) if bg is not None: ax.imshow(bg, cmap=bg_cmap) else: ax.axis([0, nc, nl, 0]) ax.set_aspect("equal") args = [x[idx], y[idx], u_[idx], v_[idx]] if c is not None: args.append(c[::step, ::step][idx]) kwargs["units"] = "dots" kwargs["angles"] = "xy" kwargs["scale_units"] = "xy" ax.quiver(*args, **kwargs) ax.set_axis_off() return ax