3D сюжет с Matplotlib

Я просто пытаюсь изобразить поверхность и ее контур в 3D, как в this пример.

Вот код, который я использую для этого:

import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import axes3d
from matplotlib import cm
import numpy

def plot_3d_contour(x_dim, y_dim, x_steps, y_steps, scalar_field, file_path):
    fig = plt.figure()

    x, y = numpy.mgrid[-x_dim/2:x_dim/2:x_steps*1j, -y_dim/2:y_dim/2:y_steps*1j]
    v_min = numpy.min(scalar_field)
    v_max = nupmy.max(scalar_field)

    ax = fig.gca(projection='3d')

    cset = ax.contourf(x, y, scalar_field, zdir='z', offset=v_min, cmap=cm.coolwarm)
    cset = ax.contourf(x, y, scalar_field, zdir='x', offset=-x_dim/2-1, cmap=cm.coolwarm)
    cset = ax.contourf(x, y, scalar_field, zdir='y', offset=y_dim/2+1, cmap=cm.coolwarm)

    ax.plot_surface(x, y, scalar_field, rstride=10, cstride=10, alpha=0.3)

    ax.set_xlabel('X')
    ax.set_xlim(-x_dim/2-1, x_dim/2+1)
    ax.set_ylabel('Y')
    ax.set_ylim(-y_dim/2-1, y_dim/2+1)
    ax.set_zlabel('Z')
    ax.set_zlim(v_min, v_max)

    plt.savefig(file_path + '.jpg')
    plt.close()

scalar_field = numpy.loadtxt('../scalar_field', delimiter=",")
plot_3d_contour(12, 12, 100, 100, scalar_field, 'scalar_field3D')

Однако я получаю странное поведение, при котором контур (zdir=y) находится над поверхностью. Кроме того, я получаю странный контур в z_dir=z (с отсутствующим разделом):

введите описание изображения здесь

Мне интересно, что мне не хватает. Скалярное поле можно найти здесь.


person pceccon    schedule 19.12.2014    source источник
comment
Вы пытались изменить zorder при черчении?   -  person nicoguaro    schedule 19.12.2014
comment
Да, это была моя первая попытка, @nicoguaro.   -  person pceccon    schedule 19.12.2014
comment
Я могу воспроизвести это в matplotlib 1.4.0. Один из разработчиков может прокомментировать, но я думаю, что это связано с общими проблемами 3D-построения - если вы сделаете эту фигуру интерактивной, вы увидите, что изменение угла обзора изменяет то, какие элементы рисуются на переднем плане. Что касается забавной полосы отсутствия данных в контуре xy-плоскости, это может быть что-то забавное, происходящее с закрытием одного из контуров. Я не думаю, что с вашим кодом что-то не так.   -  person Ajean    schedule 19.12.2014
comment
Спасибо, @Ajean. И это происходит только с contourf. Если я использую contour, этих проблем не будет.   -  person pceccon    schedule 21.12.2014
comment
Интересно. Это немного не ответ, но я видел, как люди из matplotlib, сталкиваясь с запутанными 3D-материалами, предлагали вместо этого использовать mayavi для 3D (я никогда не использовал его, поэтому я не могу с ним разговаривать).   -  person Ajean    schedule 23.12.2014
comment
IIRC, у matplotlib на самом деле нет буфера глубины, который сохраняется между командами построения   -  person Eric    schedule 27.04.2015
comment
@pceccon просто спрашиваю с моей точки зрения. Можем ли мы вывести уравнение поверхности, которое у вас получилось ... ??   -  person diffracteD    schedule 08.05.2015


Ответы (1)


Я согласен с Аджаном. Я считаю, что проблема возникает из-за того, что каждый художник matplotlib (т.е. PolygonCollection) отображается отдельно. Невозможно визуализировать разные грани одного и того же объекта на разных сторонах другого объекта в сцене.

Вот полезный фрагмент кода:

from mpl_toolkits.mplot3d import axes3d
import matplotlib.pyplot as plt
from matplotlib import cm
import numpy as np

file_path = "./3D_surface_and_contour.jpg"
p = 0.05
f = -0.01

def get_data(p):
    x, y, z = axes3d.get_test_data(p)
    z = f * z
    return x, y, z

def plot_3d_contour(p, f):
    nrows = 4
    ncols = 5

    x, y, z = get_data(p)

    x_min, x_max = np.min(x), np.max(x)
    y_min, y_max = np.min(y), np.max(y)
    z_min, z_max = np.min(z), np.max(z)

    fig = plt.figure(figsize=(15, 10))
    for n in range(nrows * ncols):
        i = n % ncols
        j = n / ncols
        k = n + 1
        if j == 0:
            azim = -60 + (i - 2) * 15
            elev = 30
        elif j == 1:
            azim = -60
            elev = 30 + (i - 2) * 5
        elif j == 2:
            azim = 60 + (i - 2) * 10
            elev = 30
        elif j == 3:
            azim = 60
            elev = 30 + (i - 2) * 5
        ax = fig.add_subplot(nrows, ncols, k, projection='3d')
        ax.set_title("azim=" + str(azim) + " elev=" + str(elev))
        ax.tick_params(labelsize=8)
        ax.view_init(azim=azim, elev=elev)
        ax.plot_surface(x, y, z, rstride=10, cstride=10, alpha=0.3)
        ax.contourf(x, y, z, zdir='z', offset=z_min, cmap=cm.coolwarm)
        ax.contourf(x, y, z, zdir='x', offset=x_min, cmap=cm.coolwarm)
        if j == 0 or j == 1:
            ax.contourf(x, y, z, zdir='y', offset=y_max, cmap=cm.coolwarm)
        elif j == 2 or j == 3:
            ax.contourf(x, y, z, zdir='y', offset=y_min, cmap=cm.coolwarm)

        ax.set_xlabel('X')
        ax.set_xlim(x_min, x_max)
        ax.set_ylabel('Y')
        ax.set_ylim(y_min, y_max)
        ax.set_zlabel('Z')
        ax.set_zlim(z_min, z_max)

    plt.savefig(file_path, dpi=80)
    plt.close()

plot_3d_contour(p, f)

что дает следующее изображение:

введите описание изображения здесь

Первые две строки создаются кодом, похожим на ваш. Вы могли заметить, что установка высоты с помощью view_init на более высокое значение решает проблему. Но это не вызывает нареканий. Я также определил влияние диапазона значений z (здесь не показаны), ошибка, кажется, появляется только тогда, когда этот диапазон мал (вы можете использовать параметр f для проверки), что объясняет, почему example от этого не страдает.

Предлагаемое мной решение - заменить:

ax.contourf(x, y, scalar_field, zdir='y', offset=y_dim/2+1, cmap=cm.coolwarm)

by :

ax.contourf(x, y, scalar_field, zdir='y', offset=-y_dim/2-1, cmap=cm.coolwarm)

в свой код и добавьте эту дополнительную строку:

ax.view_init(azim=60, elev=30)

Как показано в последних двух строках предыдущего изображения, таким образом вы сможете избежать капризов matplotlib.

person Flabetvibes    schedule 08.05.2015