Python: найти контурные линии из matplotlib.pyplot.contour()

Я пытаюсь найти (но не рисовать!) Контурные линии для некоторых данных:

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) 

я знаю cn содержит контурные линии, которые я хочу, но я не могу добраться до них. Я пробовал несколько вещей:

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

но безрезультатно. я знаю cn это ContourSet, а также cn.collections это массив LineCollections. Я бы подумал LineCollection это массив отрезков, но я не могу понять, как извлечь эти отрезки.

Моя конечная цель - создать файл KML, который будет отображать данные на карте мира, а также контуры для этих данных.

Однако, поскольку некоторые из моих точек данных расположены близко друг к другу, а другие - далеко, мне нужны фактические полигоны (линии линий), которые составляют контуры, а не просто растровое изображение контуров.

Я несколько удивлен qhull не делает что-то подобное

Используя Mathematica's ListContourPlot и затем экспорт как SVG работает, но я хочу использовать что-то с открытым исходным кодом.

Я не могу использовать хорошо известный алгоритм CONREC, потому что мои данные не находятся в сетке (не всегда есть несколько значений y для данного значения x, и наоборот).

Решение не обязательно для Python, но должно быть с открытым исходным кодом и работоспособным в Linux.

2 ответа

Решение

Вернуть вершины можно, перебирая коллекции и пути и используя iter_segments() метод matplotlib.path.Path,

Вот функция, которая возвращает вершины в виде набора вложенных списков контурных линий, контурных секций и массивов вершин x,y:

import numpy as np

def get_contour_verts(cn):
    contours = []
    # for each contour line
    for cc in cn.collections:
        paths = []
        # for each separate section of the contour line
        for pp in cc.get_paths():
            xy = []
            # for each segment of that section
            for vv in pp.iter_segments():
                xy.append(vv[0])
            paths.append(np.vstack(xy))
        contours.append(paths)

    return contours

Редактировать:

Также возможно вычислить контуры, не создавая ничего, используя недокументированные matplotlib._cntr C module:

from matplotlib import pyplot as plt
from matplotlib import _cntr as cntr

z = np.array([[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]])

x, y = np.mgrid[:z.shape[0], :z.shape[1]]
c = cntr.Cntr(x, y, z)

# trace a contour at z == 0.5
res = c.trace(0.5)

# result is a list of arrays of vertices and path codes
# (see docs for matplotlib.path.Path)
nseg = len(res) // 2
segments, codes = res[:nseg], res[nseg:]

fig, ax = plt.subplots(1, 1)
img = ax.imshow(z.T, origin='lower')
plt.colorbar(img)
ax.hold(True)
p = plt.Polygon(segments[0], fill=False, color='w')
ax.add_artist(p)
plt.show()

Я бы предложил использовать scikit-image find_contours

Он возвращает список контуров для заданного уровня.

matplotlib._cntrбыл удален из matplotlib начиная с версии 2.2 (см. здесь).

Кажется, что данные контура находятся в .allsegs атрибут QuadContourSet объект, возвращаемый plt.contour() функция.

.allseg Атрибут - это список всех уровней (которые можно указать при вызове plt.contour(X,Y,Z,V), Для каждого уровня вы получаете список numy nx2 массивов.

plt.figure()
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')

В приведенном выше примере дается только один уровень, поэтому len(C.allsegs) =1. Ты получаешь:

контурный сюжет

извлеченные кривые

Вершины всех путей могут быть возвращены как массив чисел типа float64 просто через:

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

Более подробный:

Перебирать коллекции и извлекать пути и вершины - не самое простое и не самое быстрое занятие. Возвращенный объект Contour фактически имеет атрибуты для сегментов черезcs.allsegs, который возвращает вложенный список фигур [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

См. Ссылку:https://matplotlib.org/3.1.1/api/contour_api.html

Другие вопросы по тегам