'Python: find contour lines from matplotlib.pyplot.contour()

I'm trying to find (but not draw!) contour lines for some data:

from pprint import pprint 
import matplotlib.pyplot 
z = [[0.350087, 0.0590954, 0.002165], [0.144522, 0.885409, 0.378515], 
     [0.027956, 0.777996, 0.602663], [0.138367, 0.182499, 0.460879], 
     [0.357434, 0.297271, 0.587715]] 
cn = matplotlib.pyplot.contour(z) 

I know cn contains the contour lines I want, but I can't seem to get to them. I've tried several things:

print dir(cn) 
pprint(cn.collections[0]) 
print dir(cn.collections[0]) 
pprint(cn.collections[0].figure) 
print dir(cn.collections[0].figure) 

to no avail. I know cn is a ContourSet, and cn.collections is an array of LineCollections. I would think a LineCollection is an array of line segments, but I can't figure out how to extract those segments.

My ultimate goal is to create a KML file that plots data on a world map, and the contours for that data as well.

However, since some of my data points are close together, and others are far away, I need the actual polygons (linestrings) that make up the contours, not just a rasterized image of the contours.

I'm somewhat surprised qhull doesn't do something like this.

Using Mathematica's ListContourPlot and then exporting as SVG works, but I want to use something open source.

I can't use the well-known CONREC algorithm because my data isn't on a mesh (there aren't always multiple y values for a given x value, and vice versa).

The solution doesn't have to python, but does have to be open source and runnable on Linux.



Solution 1:[1]

It seems that the contour data is in the .allsegs attribute of the QuadContourSet object returned by the plt.contour() function.

The .allseg attribute is a list of all the levels (which can be specified when calling plt.contour(X,Y,Z,V). For each level you get a list of n x 2 NumPy arrays.

plt.figure()
C = plt.contour(X, Y, Z, [0], colors='r')

plt.figure()
for ii, seg in enumerate(C.allsegs[0]):
    plt.plot(seg[:,0], seg[:,1], '.-', label=ii)
plt.legend(fontsize=9, loc='best')

In the above example, only one level is given, so len(C.allsegs) = 1. You get:

contour plot

the extracted curves

Solution 2:[2]

I would suggest to use scikit-image find_contours

It returns a list of contours for a given level.

matplotlib._cntr has been removed from matplotlib since v2.2 (see here).

Solution 3:[3]

The vertices of an all paths can be returned as a numpy array of float64 simply via:

vertices = cn.allsegs[i][j]  # for element j, in level i

with cn defines as in the original question:

import matplotlib.pyplot as plt
z = [[0.350087, 0.0590954, 0.002165], [0.144522, 0.885409, 0.378515], 
     [0.027956, 0.777996, 0.602663], [0.138367, 0.182499, 0.460879], 
     [0.357434, 0.297271, 0.587715]] 
cn = plt.contour(z) 

More detailed:

Going through the collections and extracting the paths and vertices is not the most straight forward or fastest thing to do. The returned Contour object actually has attributes for the segments via cs.allsegs, which returns a nested list of shape [level][element][vertex_coord]:

num_levels = len(cn.allsegs)
num_element = len(cn.allsegs[0])  # in level 0
num_vertices = len(cn.allsegs[0][0])  # of element 0, in level 0
num_coord = len(cn.allsegs[0][0][0])  # of vertex 0, in element 0, in level 0

See reference: https://matplotlib.org/3.1.1/api/contour_api.html

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 vanPelt2
Solution 2 Michele
Solution 3