英文:
How to add an outline to a mask with no internal lines in matplotlib
问题
对于给定的二维掩码,我想要绘制其轮廓,但不要在相邻的网格单元之间绘制内部线条。突出显示一个单元格很简单:链接,而突出显示多个单元格也很简单:链接。然而,我不希望掩码内部的边界由一条线分隔,所以上面的第二个链接不适用。
以下是生成掩码的示例代码:
import matplotlib.pyplot as plt
import numpy as np
N = 50
xbound = np.linspace(0, 10, N + 1)
ybound = np.linspace(0, 10, N + 1)
x = (xbound[:-1] + xbound[1:]) / 2
y = (ybound[:-1] + ybound[1:]) / 2
X, Y = np.meshgrid(x, y)
Z = np.exp(-((X - 2.5)**2 + (Y - 2.5)**2)) + np.exp(-2 * ((X - 7.5)**2 + (Y - 6.5)**2))
mask = Z > 0.2
plt.imshow(mask, origin='lower', extent=(0, 10, 0, 10))
生成的图像如下所示:
我想要边界围绕每个像素化的黄色圆圈,并沿着上面显示的相同边缘(即平行于x/y轴) - 我想强调底层数据是网格化的。使用以下代码可以接近:
plt.contour(x, y, mask, levels=[0.5])
但是这个轮廓位于楼梯边缘上的45度角。
如果能够填充轮廓或使用cartopy显示轮廓将获得额外的奖励分数。
英文:
For a given 2D mask, I would like to draw its outline with no internal lines between adjacent grid cells. Highlighting one cell is straightforward: <https://stackoverflow.com/questions/56654952/how-to-mark-cells-in-matplotlib-pyplot-imshow-drawing-cell-borders> and highlighting many cells is also straightforward: <https://stackoverflow.com/questions/51432498/python-matplotlib-add-borders-to-grid-plot-based-on-value>. However, I do not want internal boundaries within the mask to be separated by a line, so the second link above is not suitable.
Here is example code to generate a mask:
import matplotlib.pyplot as plt
import numpy as np
N = 50
xbound = np.linspace(0, 10, N + 1)
ybound = np.linspace(0, 10, N + 1)
x = (xbound[:-1] + xbound[1:]) / 2
y = (ybound[:-1] + ybound[1:]) / 2
X, Y = np.meshgrid(x, y)
Z = np.exp(-((X - 2.5)**2 + (Y - 2.5)**2)) + np.exp(-2 * ((X - 7.5)**2 + (Y - 6.5)**2))
mask = Z > 0.2
plt.imshow(mask, origin='lower', extent=(0, 10, 0, 10))
Which generates the image:
I want the boundary to be around each of the pixelated yellow circles, and to follow the same edges as show above (i.e. be parallel to the x/y-axis) - I want to emphasize that the underlying data is gridded. Using:
plt.contour(x, y, mask, levels=[0.5])
comes close, but the contour is at 45° along the staircase edges.
Bonus points if the outline can be filled or shown using cartopy.
答案1
得分: 1
这可以使用shapely
的形状和联合操作来完成。思路是为每个掩码为真的单元格创建一个正方形(或矩形等),然后将相邻的正方形组合在一起。然后可以绘制这些几何图形或用它们创建matplotlib
多边形。以下显示了红色轮廓(带有所需的阶梯边缘):
import shapely.geometry
import shapely.ops
geoms = []
for yidx, xidx in zip(*np.where(mask)):
geoms.append(shapely.geometry.box(xbound[xidx], ybound[yidx], xbound[xidx+1], ybound[yidx+1]))
full_geom = shapely.ops.unary_union(geoms)
for geom in full_geom.geoms:
plt.plot(*geom.exterior.xy, linewidth=4, color='r')
plt.imshow(mask, origin='lower', extent=(0, 10, 0, 10))
要填充每个形状的颜色/图案可能会很困难,因此可以使用matplotlib
多边形:
from matplotlib.patches import Polygon
polygons = []
for geom in full_geom.geoms:
p = Polygon(list(zip(*geom.exterior.xy)), closed=True, facecolor='none', edgecolor='r', linestyle='-', hatch='//', linewidth=4)
polygons.append(p)
plt.figure()
axes = plt.gca()
for p in polygons:
axes.add_patch(p)
plt.imshow(mask, origin='lower', extent=(0, 10, 0, 10))
最后,原始的shapely
多边形可以直接添加到cartopy地图中:
import cartopy.crs as ccrs
fig, ax = plt.subplots(subplot_kw={'projection': ccrs.PlateCarree()})
ax.add_geometries(
full_geom.geoms,
crs=ccrs.PlateCarree(),
facecolor='white',
edgecolor='red',
linewidth=4,
hatch='//',
)
ax.coastlines()
ax.gridlines(draw_labels=True, dms=True, x_inline=False, y_inline=False)
ax.set_xlim((0, 10))
ax.set_ylim((0, 10))
英文:
This can be done using shapely
shapes and the union operation. The idea is to create a square (or rectangle etc.) for each cell where the mask is true, then combine contiguous squares together. These geometries can then be plotted or used to create matplotlib
polygons. The following shows the outline in red (with the staircase edge as required):
import shapely.geometry
import shapely.ops
geoms = []
for yidx, xidx in zip(*np.where(mask)):
geoms.append(shapely.geometry.box(xbound[xidx], ybound[yidx], xbound[xidx+1], ybound[yidx+1]))
full_geom = shapely.ops.unary_union(geoms)
for geom in full_geom.geoms:
plt.plot(*geom.exterior.xy, linewidth=4, color='r')
plt.imshow(mask, origin='lower', extent=(0, 10, 0, 10))
It would be hard to fill each shape with a colour/pattern, so instead matplotlib
polygons can be used:
from matplotlib.patches import Polygon
polygons = []
for geom in full_geom.geoms:
p = Polygon(list(zip(*geom.exterior.xy)), closed=True, facecolor='none', edgecolor='r', linestyle='-', hatch='//', linewidth=4)
polygons.append(p)
plt.figure()
axes = plt.gca()
for p in polygons:
axes.add_patch(p)
plt.imshow(mask, origin='lower', extent=(0, 10, 0, 10))
Finally, the raw shapely
polygons can be added directly to a cartopy map:
import cartopy.crs as ccrs
fig, ax = plt.subplots(subplot_kw={'projection': ccrs.PlateCarree()})
ax.add_geometries(
full_geom.geoms,
crs=ccrs.PlateCarree(),
facecolor='white',
edgecolor='red',
linewidth=4,
hatch='//',
)
ax.coastlines()
ax.gridlines(draw_labels=True, dms=True, x_inline=False, y_inline=False)
ax.set_xlim((0, 10))
ax.set_ylim((0, 10))
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论