【问题标题】:Fill OUTSIDE of polygon | Mask array where indicies are beyond a circular boundary?填充多边形的外部 |标记超出圆形边界的掩码数组?
【发布时间】:2010-07-23 16:35:33
【问题描述】:

我使用plot(x,y,'r') 绘制一个红色圆圈。 x 和 y 是数组,当配对为 (x,y) 并绘制时,所有点形成一条圆线。

fill(x,y,'r') 绘制一个以红色填充(或着色)的红色圆圈。

如何保持圆圈内部为白色,但将圆圈外部填充到轴边界?

我曾考虑使用fill_between(x_array, y1_array, y2_array, where),但在玩了一会儿之后,我认为这不适用于我的 x,y 数组。我想fill_between() 在圆外,在由轴边界定义的正方形内,但我不认为fill_between() 有能力......我确信我可以将它变成一个完整类型的增量问题x 和 delta y 将归零,但我不愿意。

如果有人看到我在fill_between() 中遗漏了什么,请告诉我。

我真正需要做的就是屏蔽掉二维数组中的数字,这些数字位于用 x 和 y 创建的圆的边界之外,这样当二维数组被视为内部的颜色图或轮廓时圆圈将成为图像,而外部将被涂白。

这可以通过二维数组的掩蔽技术来实现吗?喜欢使用 masked_where() 吗?我还没有研究它,但会的。

有什么想法吗?谢谢

编辑 1:这是我有权表明我认为可以解释我的问题的内容。

from pylab import *
from matplotlib.path import Path
from matplotlib.patches import PathPatch

f=Figure()
a=f.add_subplot(111)

# x,y,z are 2d arrays

# sometimes i plot a color plot
# im = a.pcolor(x,y,z)
a.pcolor(x,y,z)

# sometimes i plot a contour
a.contour(x,y,z)

# sometimes i plot both using a.hold(True)

# here is the masking part.
# sometimes i just want to see the boundary drawn without masking
# sometimes i want to see the boundary drawn with masking inside of the boundary
# sometimes i want to see the boundary drawn with masking outside of the boundary

# depending on the vectors that define x_bound and y_bound, sometimes the boundary
# is a circle, sometimes it is not.

path=Path(vpath)
patch=PathPatch(path,facecolor='none')
a.add_patch(patch) # just plots boundary if anything has been previously plotted on a
if ('I want to mask inside'):
    patch.set_facecolor('white') # masks(whitens) inside if pcolor is currently on a,
    # but if contour is on a, the contour part is not whitened out. 
else: # i want to mask outside 
    im.set_clip_path(patch) # masks outside only when im = a.pcolor(x,y,z)
    # the following commands don't update any masking but they don't produce errors?
    # patch.set_clip_on(True)
    # a.set_clip_on(True)
    # a.set_clip_path(patch)

a.show()

【问题讨论】:

    标签: python matplotlib


    【解决方案1】:

    我真正需要做的就是面具 输出二维数组中的数字 位于该边界之外 用 x 和 y 创建的圆,这样 当二维数组被视为一种颜色时 在圆内绘制或轮廓 将是图像,外部将是 变白了。

    你有两个选择:

    首先,您可以为图像使用掩码数组。这更复杂,但更安全。要屏蔽圆外的数组,请从中心点生成距离图,并在距离大于半径的地方进行屏蔽。

    更简单的选择是在绘制图像后使用 im.set_clip_path() 裁剪补丁之外的区域。

    this example from the matplotlib gallery。不幸的是,根据我的经验,对于某些轴(非笛卡尔轴),这可能有点小故障。不过,在其他所有情况下,它都应该可以完美运行。

    编辑:顺便说一句,this is how to do what you originally asked:绘制一个内部有洞的多边形。但是,如果您只想遮盖图像,则最好使用上述两个选项中的任何一个。

    Edit2:只是举一个简单的例子来说明这两种方式......

    import numpy as np
    import matplotlib.pyplot as plt
    import matplotlib.patches as patches
    
    def main():
        # Generate some random data
        nx, ny = 100, 100
        data = np.random.random((ny,nx))
    
        # Define a circle in the center of the data with a radius of 20 pixels
        radius = 20
        center_x = nx // 2
        center_y = ny // 2
    
        plot_masked(data, center_x, center_y, radius)
        plot_clipped(data, center_x, center_y, radius)
        plt.show()
    
    def plot_masked(data, center_x, center_y, radius):
        """Plots the image masked outside of a circle using masked arrays"""
        # Calculate the distance from the center of the circle
        ny, nx = data.shape
        ix, iy = np.meshgrid(np.arange(nx), np.arange(ny))
        distance = np.sqrt((ix - center_x)**2 + (iy - center_y)**2)
    
        # Mask portions of the data array outside of the circle
        data = np.ma.masked_where(distance > radius, data)
    
        # Plot
        plt.figure()
        plt.imshow(data)
        plt.title('Masked Array')
    
    def plot_clipped(data, center_x, center_y, radius):
        """Plots the image clipped outside of a circle by using a clip path"""
        fig = plt.figure()
        ax = fig.add_subplot(111)
    
        # Make a circle
        circ = patches.Circle((center_x, center_y), radius, facecolor='none')
        ax.add_patch(circ) # Plot the outline
    
        # Plot the clipped image
        im = ax.imshow(data, clip_path=circ, clip_on=True)
    
        plt.title('Clipped Array')
    
    main()
    

    编辑 2:在原始图上绘制一个遮罩多边形: 以下是有关如何绘制一个多边形的更多详细信息,该多边形在当前绘图上掩盖了它之外的所有内容。显然,没有更好的方法来剪辑等高线图(无论如何我都能找到......)。

    import numpy as np
    import matplotlib.pyplot as plt
    
    def main():
        # Contour some regular (fake) data
        grid = np.arange(100).reshape((10,10))
        plt.contourf(grid)
    
        # Verticies of the clipping polygon in counter-clockwise order
        #  (A triange, in this case)
        poly_verts = [(2, 2), (5, 2.5), (6, 8), (2, 2)]
    
        mask_outside_polygon(poly_verts)
    
        plt.show()
    
    def mask_outside_polygon(poly_verts, ax=None):
        """
        Plots a mask on the specified axis ("ax", defaults to plt.gca()) such that
        all areas outside of the polygon specified by "poly_verts" are masked.  
    
        "poly_verts" must be a list of tuples of the verticies in the polygon in
        counter-clockwise order.
    
        Returns the matplotlib.patches.PathPatch instance plotted on the figure.
        """
        import matplotlib.patches as mpatches
        import matplotlib.path as mpath
    
        if ax is None:
            ax = plt.gca()
    
        # Get current plot limits
        xlim = ax.get_xlim()
        ylim = ax.get_ylim()
    
        # Verticies of the plot boundaries in clockwise order
        bound_verts = [(xlim[0], ylim[0]), (xlim[0], ylim[1]), 
                       (xlim[1], ylim[1]), (xlim[1], ylim[0]), 
                       (xlim[0], ylim[0])]
    
        # A series of codes (1 and 2) to tell matplotlib whether to draw a line or 
        # move the "pen" (So that there's no connecting line)
        bound_codes = [mpath.Path.MOVETO] + (len(bound_verts) - 1) * [mpath.Path.LINETO]
        poly_codes = [mpath.Path.MOVETO] + (len(poly_verts) - 1) * [mpath.Path.LINETO]
    
        # Plot the masking patch
        path = mpath.Path(bound_verts + poly_verts, bound_codes + poly_codes)
        patch = mpatches.PathPatch(path, facecolor='white', edgecolor='none')
        patch = ax.add_patch(patch)
    
        # Reset the plot limits to their original extents
        ax.set_xlim(xlim)
        ax.set_ylim(ylim)
    
        return patch
    
    if __name__ == '__main__':
        main()
    

    【讨论】:

    • 谢谢乔。 set_clip_path 似乎是我最好的选择,但如果我想使用第一个选项,我会使用MaskedArray() 方法吗?
    • @AmyS - 是的,我添加了一个示例来展示两种做事方式。希望对您有所帮助!
    • 感谢乔的额外帮助。有趣且有用,但对于我正在开发的当前应用程序,我的边界由两个向量定义,当配对时有时会形成一个圆圈,有时不会。我使用pcolor(),因为我的轴是由 2 个二维数组定义的,这些数组并不总是笛卡尔坐标,我不知道如何用imshow() 处理它。幸运的是,set_clip_plath(patch) 是一个属性。对于pcolor(),但不适用于contour()plot() :( 如果您仍然感兴趣,您是否可以查看我的问题上面的编辑1,看看我是否可以在没有预定义pcolor 的情况下向轴添加遮罩补丁?
    • @AmyS - 查看添加的代码 sn-p。它应该在当前绘图上绘制一个填充在已定义多边形之外的任何地方的多边形。我认为有一种更简洁的方式来剪裁轮廓,但显然没有。希望这会更好一点!
    • @JoeKington 我意识到这是一篇旧帖子,但我刚刚找到它,不得不感谢你提供了如此有用的代码 sn-p!只是希望这个问题和答案更容易找到......在找到您的解决方案之前,我花了一段时间寻找解决方案。
    【解决方案2】:

    注意:此答案使用 MATLAB 语法,因为该问题最初是这样标记的。但是,即使您在 Python 中使用 matplotlib,即使语法略有不同,概念也应该是相同的。

    你有一个选择是制作一个多边形,看起来里面有一个洞,但实际上只有两条边环绕着一个空白空间并接触。您可以通过创建一组 xy 坐标来执行此操作,这些坐标围绕圆的边缘进行跟踪,然后从圆的边缘跟踪到边界正方形的边缘,然后围绕该正方形的边缘进行跟踪并返回沿着同一条线到圆的边缘。这是一个以原点为中心的单位圆和 4 x 4 正方形的示例:

    theta = linspace(0,2*pi,100);      %# A vector of 100 angles from 0 to 2*pi
    xCircle = cos(theta);              %# x coordinates for circle
    yCircle = sin(theta);              %# y coordinates for circle
    xSquare = [2 2 -2 -2 2 2];         %# x coordinates for square
    ySquare = [0 -2 -2 2 2 0];         %# y coordinates for square
    hp = fill([xCircle xSquare],...    %# Plot the filled polygon
              [yCircle ySquare],'r');
    axis equal                         %# Make axes tick marks equal in size
    

    这是你应该看到的数字:

    注意右边连接圆形和方形边缘的线。这是红色多边形的两条边相遇并相互接触的地方。如果您不希望边缘线可见,您可以将它们的颜色更改为与多边形的填充颜色相同,如下所示:

    set(hp,'EdgeColor','r');
    

    【讨论】:

    • MATLAB 有办法直接绘制带孔的多边形吗?再说一次,我似乎也找不到它...... matplotlib 处理它的方式如下:matplotlib.sourceforge.net/examples/api/donut_demo.html
    • 感谢您的想法。似乎最方便的方法是从 path.Path 类中制作一个补丁并从那里开始工作,如上面的示例,或者使用 set_clip_path() 在其他答案中给出的示例
    【解决方案3】:

    由于这是谷歌搜索matplotlib fill outside时出现的第一个结果,我将回答标题中提出的内容。

    背景

    据我了解,matplotlib 不提供填充多边形外部区域的功能,而仅在其内部使用Axes.fill。如果我们创建一个更大的“外部”多边形,其中包含较小的多边形,并将两者结合在一起,不会产生任何交集,则有可能“愚弄”matplotlib 认为内部多边形是外部多边形的缝隙。如果外部多边形保持在视野范围之外,那么这将具有填充内部多边形之外的整个空间的效果。

    要记住的一件事是外部多边形的方向,因为连接到外部的通道不应与自身相交。为此,外部多边形的方向应该与内部多边形的方向相反。

    解决方案

    下面的函数找到最靠近左下角的内部多边形的点,并在那里插入外部多边形的路径,使用由拼接点的向量创建的平行四边形的有符号区域来处理方向,并且下一个。

    import numpy as np
    
    def concat(*arrs) -> np.ndarray:
        return np.concatenate(tuple(map(np.asarray, arrs)))
    
    def insert_at(outer_arr, arr, n) -> np.ndarray:
        outer_arr = np.asarray(outer_arr)
        prev, post = np.split(outer_arr, (n,))
        return concat(prev, arr, post)
    
    def cross2d(x1, y1, x2, y2):
        return x1*y2-x2*y1
    
    def is_clockwise(x1, y1, x2, y2):
        cp = cross2d(x1, y1, x2, y2)
        return cp < 0 if cp != 0 else None
    
    def fill_outside(x, y, ll, ur, counter_clockwise=None):
        """
        Creates a polygon where x and y form a crevice of an outer
        rectangle with lower left and upper right corners `ll` and `ur`
        respectively. If `counter_clockwise` is `None` then the orientation
        of the outer polygon will be guessed to be the opposite of the
        inner connecting points.
        """
        x = np.asarray(x)
        y = np.asarray(y)
        xmin, ymin = ll
        xmax, ymax = ur
        xmin, ymin = min(xmin, min(x)), min(ymin, min(y))
        xmax, ymax = max(xmax, max(x)), max(ymax, max(y))
        corners = np.array([
            [xmin, ymin],
            [xmin, ymax],
            [xmax, ymax],
            [xmax, ymin],
            [xmin, ymin],
        ])
        lower_left = corners[0]
        # Get closest point to splicing corner
        x_off, y_off = x-lower_left[0], y-lower_left[1]
        closest_n = (x_off**2+y_off**2).argmin()
        # Guess orientation
        p = [x_off[closest_n], y_off[closest_n]]
        try:
            pn = [x_off[closest_n+1], y_off[closest_n+1]]
        except IndexError:
            # wrap around if we're at the end of the array
            pn = [x_off[0], y_off[0]]
        if counter_clockwise is None:
            counter_clockwise = not is_clockwise(*p, *pn)
        corners = corners[::-1] if counter_clockwise else corners
        # Join the arrays
        corners = concat(np.array([[x[closest_n], y[closest_n]]]), corners)
        xs, ys = np.transpose(corners)
        return insert_at(x, xs, closest_n), insert_at(y, ys, closest_n)
    

    示例

    在一个简单的三角形之外填充

    import matplotlib.pyplot as plt
    fig, (ax1, ax2) = plt.subplots(1, 2)
    fig.set_figwidth(10)
    
    x = [0, 1, 2]
    y = [0, 1, 0]
    ll, ur = (-.5, -.25), (2.5, 1.25)
    x, y = fill_outside(x, y, ll, ur)
    ax1.fill(x, y)
    ax1.plot(x, y, c="C1")
    ax2.fill(x, y)
    ax2.set_xlim((ll[0], ur[0]))
    ax2.set_ylim((ll[1], ur[1]))
    

    生产:

    在任意形状之外填充

    import numpy as np
    
    def concat(*arrs) -> np.ndarray:
        return np.concatenate(tuple(map(np.asarray, arrs)))
    
    def z_eq_damping(damping, n=100):
        theta = np.arccos(damping)
        u = np.cos(theta)-np.sin(theta)*1j
        x = np.linspace(0, np.pi/u.imag, num=n)
        contour = np.exp(u*x)
        re, im = contour.real, contour.imag
        return concat(re, np.flip(re)), concat(im, np.flip(-im))
    
    fig, (ax1, ax2) = plt.subplots(1, 2)
    fig.set_figwidth(10)
    
    x, y = z_eq_damping(.7)
    ll, ur = (-1, -1), (1, 1)
    x, y = fill_outside(x, y, ll, ur)
    ax1.fill(x, y)
    ax1.plot(x, y, c="C1")
    ax2.fill(x, y)
    ax2.set_xlim((ll[0], ur[0]))
    ax2.set_ylim((ll[1], ur[1]))
    

    生产:

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-06-08
      • 2021-11-21
      • 1970-01-01
      • 2020-05-26
      • 1970-01-01
      相关资源
      最近更新 更多