【问题标题】:Check if points are inside ellipse faster than contains_point method检查点是否在椭圆内比 contains_point 方法快
【发布时间】:2016-08-30 02:32:03
【问题描述】:

我使用 matplotlib 1.15.1 并尝试生成这样的散点图:

椭圆有固定大小,并用中心坐标、宽度、高度和角度绘制(从外部提供):我不知道它们的引号是什么。

g_ell_center = (0.8882, 0.8882)
g_ell_width = 0.36401857095483
g_ell_height = 0.16928136341606
g_ellipse = patches.Ellipse(g_ell_center, g_ell_width, g_ell_height, angle=angle, fill=False, edgecolor='green', linewidth=2)

这个省略号应该在我的绘图上标记正常和半正常数据。 然后,我有一个大约 500 个点的数组,必须根据它们所属的椭圆着色。所以我尝试用 contains_point 方法检查每个点:

colors_array = []
colors_scheme = ['green', 'yellow', 'black']
for point in points_array:
    if g_ellipse.contains_point(point, radius=0):
        colors_array.append(0)
    elif y_ellipse.contains_point(point, radius=0):
        colors_array.append(1)
    else:
        colors_array.append(2)

最后绘制点:

plt.scatter(x_array, y_array, s=10, c=[colors_scheme[x] for x in colors_array], edgecolor="k", linewidths=0.3)

但是 contains_point 非常慢!它为 300 点散点图工作了 5 分钟,我必须并行生成数千个。也许有更快的方法? 附言整个项目绑定了matplotlib,其他库我用不了。

【问题讨论】:

  • 确定椭圆的两个焦点,计算点到两个焦点的距离之和。如果这小于主轴,则该点在椭圆内。 en.wikipedia.org/wiki/Ellipse

标签: python python-3.x matplotlib ellipse


【解决方案1】:

在给定椭圆的中心、宽度、高度和角度的情况下,此方法应测试一个点是否在椭圆内。您找到该点相对于椭圆中心的 x 和 y 坐标,然后使用角度将它们转换为沿主轴和次轴的坐标。最后,您会找到该点与像元中心的归一化距离,其中椭圆上的距离为 1,内部小于 1,外部距离大于 1。

import matplotlib.pyplot as plt
import matplotlib.patches as patches
import numpy as np

fig,ax = plt.subplots(1)
ax.set_aspect('equal')

# Some test points
x = np.random.rand(500)*0.5+0.7
y = np.random.rand(500)*0.5+0.7

# The ellipse
g_ell_center = (0.8882, 0.8882)
g_ell_width = 0.36401857095483
g_ell_height = 0.16928136341606
angle = 30.

g_ellipse = patches.Ellipse(g_ell_center, g_ell_width, g_ell_height, angle=angle, fill=False, edgecolor='green', linewidth=2)
ax.add_patch(g_ellipse)

cos_angle = np.cos(np.radians(180.-angle))
sin_angle = np.sin(np.radians(180.-angle))

xc = x - g_ell_center[0]
yc = y - g_ell_center[1]

xct = xc * cos_angle - yc * sin_angle
yct = xc * sin_angle + yc * cos_angle 

rad_cc = (xct**2/(g_ell_width/2.)**2) + (yct**2/(g_ell_height/2.)**2)

# Set the colors. Black if outside the ellipse, green if inside
colors_array = np.array(['black'] * len(rad_cc))
colors_array[np.where(rad_cc <= 1.)[0]] = 'green'

ax.scatter(x,y,c=colors_array,linewidths=0.3)

plt.show()

请注意,整个脚本需要 0.6 秒来运行并处理 500 个点。这包括创建和保存图形等。

使用上述np.where方法设置colors_array的过程需要0.00007s 500个点。

注意,在下面显示的旧实现中,在循环中设置 colors_array 需要 0.00016 秒:

colors_array = []

for r in rad_cc:
    if r <= 1.:
        # point in ellipse
        colors_array.append('green')
    else:
        # point not in ellipse
        colors_array.append('black')

【讨论】:

  • 如果你能谈谈这个解决方案的性能会很有趣。
  • 如果您将np.radians(180.-angle) 分解为给定角度仅计算sincos 一次,您的代码将变得更紧凑,并且可能明显更快。
  • 谢谢,我会试试的。测试这 500 个点只需要几秒钟
  • @unwind:它与点的数量成线性关系,不依赖于椭圆的大小和方向,并且使用常量内存进行计算(或者如果您累积结果,则再次使用线性)。三角函数在现代 CPU 上非常快。
  • @9000,好的,我明白了,我现在已经对更多的计算进行了矢量化,现在它已经加速到了 0.00017 秒。谢谢。我正在转换一些仅在单个粒子上运行的旧 fortran 代码,因此无需矢量化,并且在发布之前我认为它不正确!
【解决方案2】:

您当前的实现应该只调用 contains_point 25,000 到 50,000 次,这并不多。所以,我猜contains_point 的实现是针对精度而不是速度。

由于您的点分布在任何给定椭圆中只有一小部分,因此大多数点很少会在任何给定椭圆附近,您可以轻松地使用直角坐标作为快捷方式来确定是否点与椭圆足够接近,值得调用contains_point

计算椭圆的左右 x 坐标和上下 y 坐标,可能加上一点填充以解决渲染差异,然后检查点是否在其中,例如以下伪代码:

if point.x >= ellipse_left and point.x <= ellipse_right and _
   point.y >= ellipse_top and point.y <= ellipse_bottom:
    if ellipse.contains_point(point, radius=0):
        ... use the contained point here

这种方法消除了对大多数点的昂贵计算,允许进行简单的比较以排除明显的不匹配,同时保持计算的准确性,因为点足够接近以至于它可能在椭圆中。如果例如只有 1% 的点位于给定椭圆附近,这种方法将消除 99% 的对 contains_point 的调用,取而代之的是更快的比较。

【讨论】:

  • 很好的解决方案。但不幸的是,在我的情况下,60-100% 的点都在两个椭圆内或靠近它们的边界。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-02-08
  • 2013-03-12
  • 2013-07-20
  • 2015-10-17
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多