Data visualization

Individual frames

- Matplotlib 2D/3D : point cloud

Sequences

- Matplotlib 2D/3D : point cloud, with colored trace over time
- Matplotlib 2D : all channels (xyz) vs time: curves
- Matplotlib 2D : all channels (xyz) vs time: pixel-color coded

Model visualization

Parallel coordinates visualizations can be used to summarize model architecture configuration and performances:

import numpy
import matplotlib.pyplot as plt
from itertools import product


def parallel_coordinates(columns_names, values,
                         custom_ticks=None, space_ticks_evenly=False,
                         cmap_name='plasma', figsize=(16.0, 10.0),
                         save_as=None, return_fig=False, show=True):
    """Plot 2d array `values` using K parallel coordinates.

    Arguments:
        columns_names -- list of K strings with the coordinate names
        values        -- numpy.ndarray of shape (N, K) where N is the nb of experiments
        custom_ticks  -- list of K elements, each element is itself a list of strings with the names of the ticks for its own axis/dim.
        cmap_name     -- matplotlib colormap name
    """

    N, K = values.shape

    if space_ticks_evenly is True:
        raise Exception('TODO: this option is still to be implemented, please use space_ticks_evenly=False for now. :)')

    if custom_ticks is None:
        custom_ticks = [None for i in range(K)]

    # normalise data
    vmin, vmax = values.min(0), values.max(0)
    normed_values = (values - vmin) / (vmax - vmin)

    normed_custom_ticks_values = [None for i in range(K)]
    for i in range(K):
        if custom_ticks[i] is not None:
            axvmin, axvmax = numpy.array(custom_ticks[i]).min(), numpy.array(custom_ticks[i]).max()
            if space_ticks_evenly:
                normed_custom_ticks_values[i] = np.linspace(0, 1, num=len(custom_ticks[i]))
            else:
                normed_custom_ticks_values[i] = (custom_ticks[i] - axvmin) / (axvmax - axvmin)

    # font size
    plt.rcParams.update({'font.size': 16})

    # create independent, attached axes
    fig, axes = plt.subplots(1, K-1, figsize=figsize)
    fig.subplots_adjust(wspace=0)

    # obtain colors
    cmap = plt.get_cmap(cmap_name)

    for i in range(K - 1):
        ax = axes[i]

        lines = ax.plot(normed_values.T)

        # set line colors
        for il, l in enumerate(lines):
            l.set_color(cmap(normed_values[il, -1]))

        # configure axes
        ax.spines['top'].set_visible(False)
        ax.spines['bottom'].set_position(('outward', 5))
        ax.spines['bottom'].set_visible(False)
        ax.yaxis.set_ticks_position('left')
        ax.xaxis.set_ticks_position('none')

        # set limit to show only single line segment
        ax.set_xlim((i, i+1))
        ax.set_xticks([i])
        ax.set_xticklabels([columns_names[i]])

        # set the scale
        if custom_ticks[i] is None:
            ax.set_yticks([0, 1])
            ax.set_yticklabels([
                numpy.round(vmin[i], 2),
                numpy.round(vmax[i], 2),
            ])
        else:
            ax.set_yticks(normed_custom_ticks_values[i])
            ax.set_yticklabels(custom_ticks[i])
            if space_ticks_evenly:
                ax.set_yticks(np.linspace(0, 1, num=len(custom_ticks[i])))

    # we have to deal with rightmost axis separately
    axes[-1].spines['top'].set_visible(False)
    axes[-1].spines['bottom'].set_position(('outward', 5))
    axes[-1].spines['bottom'].set_visible(False)
    axes[-1].set_xticks([K-2, K-1])
    axes[-1].set_xticklabels(columns_names[-2:])

    ax = axes[-1].twinx()
    ax.tick_params(axis='y', direction='out')
    ax.xaxis.set_ticks_position('none')
    ax.set_yticks([0, 1])
    ax.set_yticklabels([
        numpy.round(vmin[-1], 2),
        numpy.round(vmax[-1], 2),
    ])

    # added
    ax.spines['top'].set_visible(False)
    ax.spines['bottom'].set_position(('outward', 5))
    ax.spines['bottom'].set_visible(False)

    # set leftmost axis as the default for labelling
    plt.sca(axes[0])

    if save_as is not None:
        fig.savefig(save_as)
    if show is True:
        plt.show()
    if return_fig is True:
        return fig


if __name__ == "__main__":
    # -------------------------------------
    # Generate some results
    # -------------------------------------
    n_layers = 2
    experiments_configs = list(product([32, 64, 128, 256], repeat=n_layers))
    experiments_configs = numpy.array(experiments_configs)

    # add a last column for the result
    experiments_configs = numpy.c_[experiments_configs, numpy.zeros(experiments_configs.shape[0])]

    # get the result of the experiment. here we add random results:
    experiments_configs[:, -1] = numpy.random.uniform(low=0., high=1., size=experiments_configs.shape[0])

    # -------------------------------------
    # Show the image
    # -------------------------------------
    parallel_coordinates(
        columns_names = ['Neurons in layer {}'.format(i+1) for i in range(n_layers)] + ['Accuracy'],
        values = experiments_configs,
        custom_ticks = [[32, 64, 128, 256] for _ in range(experiments_configs.shape[1])],
        space_ticks_evenly=False,
        cmap_name='winter',
        return_fig=False,
        show=True,
        save_as='./image.png'
    )