【问题标题】:Way to contour outer edge of selected grid region in Python在Python中绘制选定网格区域外边缘的方法
【发布时间】:2020-08-19 05:34:14
【问题描述】:

我有以下代码:

import numpy as np
import matplotlib.pyplot as plt

x = np.linspace(-np.pi/2, np.pi/2, 30)
y = np.linspace(-np.pi/2, np.pi/2, 30)
x,y = np.meshgrid(x,y)

z = np.sin(x**2+y**2)[:-1,:-1]

fig,ax = plt.subplots()
ax.pcolormesh(x,y,z)

这给出了这个图像:

现在假设我想突出显示某些网格框的边缘:

highlight = (z > 0.9)

我可以使用轮廓函数,但这会导致“平滑”轮廓。我只想在网格框的边缘之后突出显示区域的边缘。

我最接近的是添加这样的内容:

highlight = np.ma.masked_less(highlight, 1)

ax.pcolormesh(x, y, highlight, facecolor = 'None', edgecolors = 'w')

这给出了这个情节:

这很接近,但我真正想要的是仅突出显示“甜甜圈”的外边缘和内边缘。

所以本质上我正在寻找一些轮廓和 pcolormesh 函数的混合 - 一些遵循某个值的轮廓,但在“步骤”中遵循网格箱而不是连接点对点的东西。这有意义吗?

旁注:在 pcolormesh 参数中,我有 edgecolors = 'w',但边缘仍然是蓝色的。那里发生了什么?

编辑: JohanC 使用 add_iso_line() 的初始答案适用于提出的问题。但是,我使用的实际数据是一个非常不规则的 x,y 网格,无法转换为一维(add_iso_line() 需要。

我正在使用从极坐标 (rho, phi) 转换为笛卡尔 (x,y) 的数据。 JohanC 提出的 2D 解决方案似乎不适用于以下情况:

import numpy as np
import matplotlib.pyplot as plt
from scipy import ndimage

def pol2cart(rho, phi):
    x = rho * np.cos(phi)
    y = rho * np.sin(phi)
    return(x, y)

phi = np.linspace(0,2*np.pi,30)
rho = np.linspace(0,2,30)

pp, rr = np.meshgrid(phi,rho)

xx,yy = pol2cart(rr, pp)

z = np.sin(xx**2 + yy**2)

scale = 5
zz = ndimage.zoom(z, scale, order=0)

fig,ax = plt.subplots()
ax.pcolormesh(xx,yy,z[:-1, :-1])

xlim = ax.get_xlim()
ylim = ax.get_ylim()
xmin, xmax = xx.min(), xx.max()
ymin, ymax = yy.min(), yy.max()
ax.contour(np.linspace(xmin,xmax, zz.shape[1]) + (xmax-xmin)/z.shape[1]/2,
           np.linspace(ymin,ymax, zz.shape[0]) + (ymax-ymin)/z.shape[0]/2,
           np.where(zz < 0.9, 0, 1), levels=[0.5], colors='red')
ax.set_xlim(*xlim)
ax.set_ylim(*ylim)

【问题讨论】:

  • 是的,这个问题很明确也很有用
  • z[z>0.9] = 0 或 z[z>0.9] = 1 更改值,使它们不同。请注意,pyplot 会自动分配颜色图。您可能想要使用灰度颜色图。或者您可能只想使用 cv2.imshow() 以灰度查看它。然后您可以转换为 3 个通道并使强调红色 z1=cv2.merge([z,z,z]) 然后 z1[z>0.9]=(255,255,255)

标签: python matplotlib contour


【解决方案1】:

This post 展示了一种绘制此类线条的方法。由于适应当前的pcolormesh 并不简单,以下代码演示了一种可能的适应方式。 请注意,x 和 y 的 2d 版本已重命名,因为线段需要 1d 版本。

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection

x = np.linspace(-np.pi / 2, np.pi / 2, 30)
y = np.linspace(-np.pi / 2, np.pi / 2, 30)
xx, yy = np.meshgrid(x, y)

z = np.sin(xx ** 2 + yy ** 2)[:-1, :-1]

fig, ax = plt.subplots()
ax.pcolormesh(x, y, z)

def add_iso_line(ax, value, color):
    v = np.diff(z > value, axis=1)
    h = np.diff(z > value, axis=0)

    l = np.argwhere(v.T)
    vlines = np.array(list(zip(np.stack((x[l[:, 0] + 1], y[l[:, 1]])).T,
                               np.stack((x[l[:, 0] + 1], y[l[:, 1] + 1])).T)))
    l = np.argwhere(h.T)
    hlines = np.array(list(zip(np.stack((x[l[:, 0]], y[l[:, 1] + 1])).T,
                               np.stack((x[l[:, 0] + 1], y[l[:, 1] + 1])).T)))
    lines = np.vstack((vlines, hlines))
    ax.add_collection(LineCollection(lines, lw=1, colors=color))

add_iso_line(ax, 0.9, 'r')
plt.show()

这是对第二个答案的改编,它只能用于二维数组:

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection
from scipy import ndimage

x = np.linspace(-np.pi / 2, np.pi / 2, 30)
y = np.linspace(-np.pi / 2, np.pi / 2, 30)
x, y = np.meshgrid(x, y)

z = np.sin(x ** 2 + y ** 2)

scale = 5
zz = ndimage.zoom(z, scale, order=0)

fig, ax = plt.subplots()
ax.pcolormesh(x, y,  z[:-1, :-1] )
xlim = ax.get_xlim()
ylim = ax.get_ylim()
xmin, xmax = x.min(), x.max()
ymin, ymax = y.min(), y.max()
ax.contour(np.linspace(xmin,xmax, zz.shape[1]) + (xmax-xmin)/z.shape[1]/2,
           np.linspace(ymin,ymax, zz.shape[0]) + (ymax-ymin)/z.shape[0]/2,
           np.where(zz < 0.9, 0, 1), levels=[0.5], colors='red')
ax.set_xlim(*xlim)
ax.set_ylim(*ylim)
plt.show()

【讨论】:

  • 好的,所以这回答了提出的问题。但是,我想我把它简化得太多了。在我实际使用的数据集中,x 和 y 不是常规网格上的 a - 我无法将它们更改为一维。我应该提出一个新问题吗?
  • 您可能可以从 2d 版本中提取 1d 版本。类似于x1d = x[0,:]y1d = y[:,0]。随意扩展您当前的问题(或添加新问题)。
  • 建议的 2D 解决方案不适用于我的情况。我用一个新的例子更新了原来的问题。
【解决方案2】:

我将尝试重构add_iso_line 方法,以使其更清晰地开放优化。所以,首先,有一个必须做的部分:

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection

x = np.linspace(-np.pi/2, np.pi/2, 30)
y = np.linspace(-np.pi/2, np.pi/2, 30)
x, y = np.meshgrid(x,y)
z = np.sin(x**2+y**2)[:-1,:-1]

fig, ax = plt.subplots()
ax.pcolormesh(x,y,z)
xlim, ylim = ax.get_xlim(), ax.get_ylim()
highlight = (z > 0.9)

现在highlight 是一个二进制数组,如下所示: 之后,我们可以提取 True 单元格的索引,查找 False 邻域并识别“红”线的位置。我对以矢量化的方式做这件事不太舒服(就像add_iso_line 方法中的这里),所以只使用简单的循环:

lines = []
cells = zip(*np.where(highlight))
for x, y in cells:
    if x == 0 or highlight[x - 1, y] == 0: lines.append(([x, y], [x, y + 1]))
    if x == highlight.shape[0] or highlight[x + 1, y] == 0: lines.append(([x + 1, y], [x + 1, y + 1]))
    if y == 0 or highlight[x, y - 1] == 0: lines.append(([x, y], [x + 1, y]))
    if y == highlight.shape[1] or highlight[x, y + 1] == 0: lines.append(([x, y + 1], [x + 1, y + 1]))

最后,我调整线条的大小和中心坐标以适应 pcolormesh:

lines = (np.array(lines) / highlight.shape - [0.5, 0.5]) * [xlim[1] - xlim[0], ylim[1] - ylim[0]]
ax.add_collection(LineCollection(lines, colors='r'))
plt.show()

总之,这与 JohanC 解决方案非常相似,而且通常速度较慢。幸运的是,我们可以显着减少cells 的数量,仅使用python-opencv 包提取轮廓:

import cv2
highlight = highlight.astype(np.uint8)
contours, hierarchy = cv2.findContours(highlight, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
cells = np.vstack(contours).squeeze()

这是正在检查的单元格的图示:

【讨论】:

    猜你喜欢
    • 2016-07-28
    • 2013-05-10
    • 2011-11-29
    • 2018-09-24
    • 2016-02-16
    • 1970-01-01
    • 2015-04-25
    • 2021-06-03
    • 2020-06-20
    相关资源
    最近更新 更多