Search code examples
numpymatplotlibscipysmoothing

How to get a smoothed contour line in matplotlib given non-smooth underlying data


I have boolean data on a 2D grid and want to use matplotlib to plot a countour between the areas where the data is True and False.

However, the separation between these areas is not smooth within the actual data. How can I compute a smoothed countour given this data?

Here is a minimal example:

import numpy as np
import matplotlib.pyplot as plt

# generate some non-smooth example data
MESHSIZE = 10
REFINEMENT = 4*MESHSIZE
x = np.linspace(-MESHSIZE, MESHSIZE, REFINEMENT)
xv, yv = np.meshgrid(x, x)
xvf = xv.reshape(-1)
yvf = yv.reshape(-1)


def choppy_circle(x, y):
    inner = x.astype(int)**2+y.astype(int)**2 < 10.0
    return inner


# consider this the *actual* data given to me as-is
my_x = xvf
my_y = yvf
my_z = choppy_circle(xvf, yvf)

# need to visualize the contour that separates areas where
# my_z is True/False
plt.tricontour(my_x, my_y, my_z, levels=np.array([1.0-1e-3]))
plt.scatter(xv, yv, s=0.1)
plt.show()

This produces the following plot, which is faithful to the data, but not what I'm looking for:

enter image description here

How can I use the data given in my_x, my_y and my_z to construct a smoothed contour around the domain where my_z is True?

Something like this:

enter image description here


Solution

  • Extracting the contour data like proposed in this answer and interpolating with splines as proposed by @user2640045 allows doing it for arbitrary contours:

    # my_x, my_y, my_z as above...
    
    # get contour data
    cs = plt.tricontour(my_x, my_y, my_z, levels=np.array([1.0-1e-3]))
    print(type(cs))
    
    # process each contour
    for contour in cs.collections[0].get_paths():
        vert = contour.vertices
        vert_x = vert[:, 0]
        vert_y = vert[:, 1]
    
        # plot contour
        plt.plot(vert_x, vert_y)
    
        # interpolate contour points
        s = 20
        tck, u = interpolate.splprep([vert_x, vert_y], per=1, s=s)
        x_interp, y_interp = interpolate.splev(np.linspace(0, 1, 10**3), tck)
    
        # plot interpolated contour
        plt.plot(x_interp, y_interp)
    
    # plot grid
    plt.scatter(xv, yv, s=0.1)
    
    # display plot
    plt.show()
    

    The important bit is the loop header

    for contour in cs.collections[0].get_paths():
    

    where the x-y data of each contour line is obtained.