Matplotlib - 在同一坐标轴上绘制两个3D曲面时出现重叠错误

28

我正在尝试使用matplotlib的plot_surface命令在同一坐标轴上绘制两个3D表面。

fig = plt.figure()
fig.figsize = fig_size
ax = fig.gca(projection='3d')

surf = ax.plot_surface(X, Y, Exp_Fric_map, alpha = 1, rstride=1, cstride=1, cmap=cm.winter, linewidth=0.5, antialiased=True)
surf = ax.plot_surface(X, Y, Fric_map, alpha = 1, rstride=1, cstride=1, cmap=cm.autumn,linewidth=0.5, antialiased=True)

我的问题是,在查看绘图时,并不总是正确的曲面处于“最上层”。例如在以下图表中:

Example plot

在后面的角落(坐标轴上的200N,2.5Hz),蓝绿色曲面位于“最上层”,而实际上黄红色更靠近观察者。如果我旋转绘图:

Second example plot

然后现在看起来没问题了,在200N和2.5Hz处,蓝绿色曲面位于黄红色曲面之下(现在在左侧)。我尝试过在stackoverflow和Google上搜索,但找不到任何类似的问题解决方案。

我在Linux系统上使用Python 2.7.3、Numpy 1.6.1和Matplotlib 1.1.1rc。


1
当您有两个相交的表面时,会出现同样的问题。Matplotlib将简单地将3D表面绘制为2D投影,每个表面在其自己的层中按顺序绘制。如下所建议,还有其他软件包可以正确地可视化多个3D表面。 - feedMe
5个回答

23

此行为在matplotlib FAQ 这里中有记录。该页面建议安装Mayavi,Mayavi与3D图形兼容性良好,其界面与matplotlib相似。

  • Mayavi的界面与matplotlib非常相似。
  • 它的主要问题在于在Python 3上安装仍然很棘手。(现在变得容易多了)

以下是“matplotlib vs mayavi”演示比较:

# generate data
import numpy as np

x = np.arange(-2, 2, 0.1)
y = np.arange(-2, 2, 0.1)
mx, my = np.meshgrid(x, y, indexing='ij')
mz1 = np.abs(mx) + np.abs(my)
mz2 = mx ** 2 + my ** 2

# A fix for "API 'QString' has already been set to version 1"
# see https://github.com/enthought/pyface/issues/286#issuecomment-335436808
from sys import version_info
if version_info[0] < 3:
    import pyface.qt


def v1_matplotlib():
    from matplotlib import pyplot as plt
    from mpl_toolkits.mplot3d import Axes3D

    fig = plt.figure()
    ax = fig.gca(projection='3d')
    surf1 = ax.plot_surface(mx, my, mz1, cmap='winter')
    surf2 = ax.plot_surface(mx, my, mz2, cmap='autumn')
    ax.view_init(azim=60, elev=16)
    fig.show()


def v2_mayavi(transparency):
    from mayavi import mlab
    fig = mlab.figure()

    ax_ranges = [-2, 2, -2, 2, 0, 8]
    ax_scale = [1.0, 1.0, 0.4]
    ax_extent = ax_ranges * np.repeat(ax_scale, 2)

    surf3 = mlab.surf(mx, my, mz1, colormap='Blues')
    surf4 = mlab.surf(mx, my, mz2, colormap='Oranges')

    surf3.actor.actor.scale = ax_scale
    surf4.actor.actor.scale = ax_scale
    mlab.view(60, 74, 17, [-2.5, -4.6, -0.3])
    mlab.outline(surf3, color=(.7, .7, .7), extent=ax_extent)
    mlab.axes(surf3, color=(.7, .7, .7), extent=ax_extent,
              ranges=ax_ranges,
              xlabel='x', ylabel='y', zlabel='z')

    if transparency:
        surf3.actor.property.opacity = 0.5
        surf4.actor.property.opacity = 0.5
        fig.scene.renderer.use_depth_peeling = 1


v1_matplotlib()
v2_mayavi(False)
v2_mayavi(True)

# To install mayavi, the following currently works for me (Windows 10):
#
#   conda create --name mayavi_test_py2 python=2.7 matplotlib mayavi=4.4.0
#    (installs pyqt=4.10.4 mayavi=4.4.0 vtk=5.10.1)
#    * the `use_depth_peeling=1` got no effect. Transparency is not correct.
#    * requires `import pyface.qt` or similar workaround
#
# or
#
#   conda create --name mayavi_test_py3 python=3.6 matplotlib
#   conda activate mayavi_test_py3
#   pip install mayavi

matplotlib vs mayavi_no_transparency vs mayavi_with_transparency


Python 3现在已经支持。如果你想使用不透明度,你会遇到与matplotlib相同的问题,除非启用深度剥离:f.scene.renderer.use_depth_peeling=1 - sedot
@sedot,感谢你提醒我!我已经更新了答案以反映这一点。然而,在python 2.7 mayavi=4.4.0中,use_depth_peeling对我无效。不确定是否有可用于python 2.7的更新版mayavi windows二进制文件。(个人而言,我不使用python 2.7,但原始问题是关于它的。) - Igor
请注意,为了使mayavi正确渲染此内容,您需要确保使用正确的后端。例如,如果我使用“ipython3 --gui=qt”,则一切正常。但是,如果我没有指定qui后端,则结果与matplotlib相同,即表面无法正确地相交。 - laolux

13

这就像绘画一样。最后绘制的那个会被视为“在顶层”。

你可以使用zorder属性来告诉matplotlib应该按什么顺序绘制图层。

例如:

ax.plot_surface(X, Y, Exp_Fric_map, alpha = 1, rstride=1, cstride=1, cmap=cm.winter, linewidth=0.5, antialiased=True, zorder = 0.5)
ax.plot_surface(X, Y, Fric_map, alpha = 1, rstride=1, cstride=1, cmap=cm.autumn,linewidth=0.5, antialiased=True, zorder = 0.3)

更新:

我进行了几次测试,我认为这是Matplotlib在单个图中绘制多个表面时存在的一个错误。例如,它会创建一些在我们的三维世界中不应存在的表面,如下图所示:

enter image description here

我尝试过很多方法,但是并没有找到有效的解决方案。根本原因是绘制的顺序,正如我所说:Matplotlib总是逐个绘制事物。如果一个表面的一部分应该在顶部,而另一部分应该在底部,Matplotlib就会出错。

因此,我的建议是停止解决这个问题,这是浪费时间,除非您想为Matplotlib做贡献。如果您必须解决此问题,我建议您找另一个绘图工具来完成您的工作。


谢谢您的回答!但这并不是我想要实现的。我希望更靠近的表面(即具有较大z轴值的表面)在前面。目前的绘图方式非常令人困惑,很难解释数据。在第一个图中,蓝绿色表面应该在黄红色表面的大部分后面隐藏起来,而事实上它却显示在前面。我可以最后绘制黄红色表面,使其在前面,但这对于其他区域的绘图来说是不正确的,因为在那些区域,蓝绿色表面具有更大的z轴值。 - Andrew Spencer
@Skyler 谢谢!我同意你的答案。对于我的大多数图,可以将它们定位在适当的方向上以便正确显示,对于其他的图,我会研究一下mayavi。 - Andrew Spencer
1
我认为这不是一个错误,而是一个缺失的功能。据我所知,所有的3D绘图都有点像黑客行为。@AndrewSpencer的mayavi比看起来要简单得多。 - tacaswell
2
截至2016年8月,这个问题仍然存在。 - Turtles Are Cute
2
这真是令人沮丧。 - mxmlnkn
显示剩余4条评论

1

可以通过手动修复来解决此问题。这显然不是最干净的解决方案,但它可以给您想要的结果。假设您想为共同的X和Y绘制Z1和Z2。

  1. 创建一个名为Z1_gte的数组,将Z1的副本存储在其中,其中Z1>=Z2,否则为np.nan。
  2. 创建一个名为Z1_lte的数组,将Z1的副本存储在其中,其中Z1<=Z2,否则为np.nan。
  3. 按以下顺序绘制三个表面:Z1_lte,Z2,Z1_gte。当沿z轴从高到低查看时,您的表面将看起来正确。如果要在沿z轴从低到高查看时使表面看起来正确,请反转顺序。

或者直接:

ax.plot_surface(X,Y,np.where(Z1<Z2,Z1,np.nan))
ax.plot_surface(X,Y,Z2)
ax.plot_surface(X,Y,np.where(Z1>=Z2,Z1,np.nan))

这种方法的明显缺点是它只适用于沿z轴的一个特定视角。

0
您可以通过同时绘制两个矩阵来规避问题。只需将两个矩阵附加在一起,并为它们指定不同的颜色即可。
def plotTwo(ax, X, Y, Z1, Z2):
    col1 = np.full(Z1.shape, 'b', dtype='U50')
    col2 = np.full(Z2.shape, 'r', dtype='U50')
    ax[2].plot_surface(np.append(X, X, axis=0),
                      np.append(Y, Y, axis=0),
                      np.append(Z1, Z2, axis=0),
                      facecolors= np.append(col1, col2, axis=0),
                      edgecolor='none', alpha=1)

这是一个很好的起点,但您仍然有两个问题:两个表面会与连接表面连接在一起,并且您需要非常高的分辨率,否则在交叉处图像看起来很丑陋。我想这两个问题都可以解决。两个绘制表面之间的连接表面可以通过反转附加数组之一的顺序然后进行裁剪来移动到图片的一侧。插值应该可以轻松解决交叉部分的丑陋问题。 - gagi

0

另一个可能对某些应用程序有用的技巧是将您的表面更改为一种颜色,并将 alpha 值设置为 0.5,然后您将能够通过旋转表面至少获得一些提示。如果您正在调试交叉表面或者像我这样想要检查尖峰平滑效果的情况下,这是一个快速的方法。蓝色是平滑表面,红色是原始表面。

Profile of two surfaces


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接