Contour plots are useful for visualizing 2D arrays of numbers where the values vary relatively smoothly over space. A less common use is to annotate regions in a plot, in which case the identity of each region is typically coded by an integer. If you have multiple regions, the values aren't going to vary smoothly, and the built-in matplot contour command won't look quite right: higher-valued regions will have multiple borders around them. The solution is to make a separate plot call for each unique value of the label. An optional gaussian filter can be used to smooth off region edges.

dcontour example


import numpy as nx
def dcontour(ax, *args, **kwargs):
    """
    Discrete contour function. Given a matrix I with a discrete number
    of unique levels, plots a contour at each unique level. Values less than 0
    are ignored.
   
    DCONTOUR(axes, I) plots the unique levels in I
    DCONTOUR(axes, X,Y,I) - X,Y specify the (x,y) coordinates of the points in I

    Optional arguments:

    smooth - specify a float or 2-ple of floats, which are used to gaussian filter
             each data level prior to contouring (which gives smoother contour lines)
             
    Other keyword arguments are passed to contour()
    "
""
    from scipy.ndimage import gaussian_filter

    smooth = kwargs.get('smooth', None)
   
    I = args[0]
    if len(args) > 1:
        (X, Y) = args[1:3]
    else:
        (Y, X) = (nx.arange(I.shape[0]), nx.arange(I.shape[1]))
   
    labels = nx.unique(I[I>-1])

    h = []

    kwargs['hold'] = 1
    for i in labels:
        if smooth!=None:
            data = gaussian_filter((I==i).astype('d'), smooth)
        else:
            data = I==i
        hh = ax.contour(X, Y, data,1, colors=colorcycle(i), **kwargs)
        h.append(hh)

    return h
 


The following code, or something equivalent, is necessary to ensure that the contour colors cycle through different values for each level. I find these particular colors pleasing and fairly easy to distinguish, but your mileage may vary, especially if you need to annotate more than 15 different regions.


_manycolors = ['b','g','r','#00eeee','m','y',
               'teal',  'maroon', 'olive', 'orange', 'steelblue', 'darkviolet',
               'burlywood','darkgreen','sienna','crimson',
               ]
   
def colorcycle(ind=None, colors=_manycolors):
    """
    Returns the color cycle, or a color cycle, for manually advancing
    line colors.
    "
""
    return colors[ind % len(colors)] if ind!=None else colors