Проверьте, находятся ли точки внутри эллипса быстрее, чем метод contains_point

Я использую 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 работает очень медленно! Это работало 5 минут для 300-точечной скаттерограммы, и мне приходится генерировать их тысячи параллельно. Может быть, есть более быстрый подход? P.S. Весь проект привязан к matplotlib, я не могу использовать другие библиотеки.


person Maroth    schedule 04.05.2016    source источник
comment
определить два фокуса эллипса, вычислить сумму расстояний от точки до двух фокусов. если это меньше, чем большая ось, точка находится внутри эллипса. en.wikipedia.org/wiki/Ellipse   -  person yosukesabai    schedule 04.05.2016


Ответы (2)


Этот подход должен проверять, находится ли точка внутри эллипса, учитывая центр эллипса, ширину, высоту и угол. Вы находите координаты 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 точек. Это включает в себя создание и сохранение фигуры и т. д.

Процесс установки colors_array с использованием описанного выше метода np.where занимает 0,00007 с для 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')
person tmdavison    schedule 04.05.2016
comment
Было бы интересно, если бы вы сказали что-нибудь о производительности этого решения. - person unwind; 04.05.2016
comment
Если вы исключите np.radians(180.-angle) и вычислите sin и cos только один раз для заданного угла, ваш код станет более компактным и, вероятно, заметно быстрее. - person 9000; 04.05.2016
comment
Спасибо, я постараюсь. Однако проверка этих 500 точек занимает всего несколько секунд. - person tmdavison; 04.05.2016
comment
@unwind: он линейный по количеству точек, не зависит от размера и ориентации эллипса и использует постоянную память для вычислений (или снова линейный, если вы накапливаете результаты). Тригонометрические функции работают довольно быстро на современных процессорах. - person 9000; 04.05.2016
comment
@tom: На самом деле вы можете вычислить sin и cos вне цикла, что значительно улучшит производительность. (И ждать визуализацию целые секунды — не лучший вариант; я подозреваю, что это решение может получить несколько кадров в секунду после некоторой полировки.) - person 9000; 04.05.2016
comment
Возможно, секунды были завышенной оценкой. Я имел в виду это как сравнение с 5 минутами ОП. В любом случае цикл for увеличился с 0,0060 секунды до 0,0012 секунды в моем первом редактировании (для 500 баллов). - person tmdavison; 04.05.2016
comment
@ 9000, хорошо, я вижу, теперь я векторизовал гораздо больше расчетов, и теперь он ускорился до 0,00017 секунды. Спасибо. Я преобразовывал какой-то старый код на Фортране, который работал только с отдельными частицами, поэтому не было необходимости в векторизации, и я не подумал об этом должным образом перед публикацией! - person tmdavison; 04.05.2016
comment
Это работает отлично! Большое тебе спасибо. Теперь мне очень стыдно за свои математические способности. - person Maroth; 05.05.2016

Ваша текущая реализация должна вызывать 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 и вместо этого заменит их гораздо более быстрыми сравнениями.

person Matt Jordan    schedule 04.05.2016
comment
Хорошее решение. Но, к сожалению, в моем случае 60-100 % точек находятся внутри обоих эллипсов или близко к их границам. - person Maroth; 05.05.2016