Извлечь координаты, заключенные патчем matplotlib.

Я создал эллипс, используя matplotlib.patches.ellipse, как показано ниже:

patch = mpatches.Ellipse(center, major_ax, minor_ax, angle_deg, fc='none', ls='solid', ec='g', lw='3.')

Мне нужен список всех целочисленных координат, заключенных внутри этого патча. То есть Если бы я собирался построить этот эллипс вместе с каждой целой точкой в ​​той же сетке, сколько из этих точек заключено в эллипс?

Я пробовал посмотреть, могу ли я извлечь уравнение эллипса, чтобы я мог прокручивать каждую точку и видеть, попадает ли она в линию, но я не могу найти очевидного способа сделать это, она становится более сложной так как основная ось эллипса может быть ориентирована под любым углом. Информация для этого должна храниться в патчах где-нибудь, но я не могу ее найти.

Любые советы по этому поводу будут высоко оценены.

3 ответа

Ellipse объекты имеют метод contains_point, который будет возвращать 1, если точка находится в эллипсе, 0 другой мудрый.

Кража из ответа @DrV:

import matplotlib.pyplot as plt
import matplotlib.patches
import numpy as np
# create an ellipse
el = matplotlib.patches.Ellipse((50,-23), 10, 13.7, 30, facecolor=(1,0,0,.2), edgecolor='none')
# calculate the x and y points possibly within the ellipse
y_int = np.arange(-30, -15)
x_int = np.arange(40, 60)
# create a list of possible coordinates
g = np.meshgrid(x_int, y_int)
coords = list(zip(*(c.flat for c in g)))
# create the list of valid coordinates (from untransformed)
ellipsepoints = np.vstack([p for p in coords if el.contains_point(p, radius=0)])
# just to see if this works
fig = plt.figure()
ax = fig.add_subplot(111)
ax.add_artist(el)
ep = np.array(ellipsepoints)
ax.plot(ellipsepoints[:,0], ellipsepoints[:,1], 'ko')
plt.show()

Это даст вам результат, как показано ниже:


Если вы действительно хотите использовать методы, предлагаемые matplotlib, то:

import matplotlib.pyplot as plt
import matplotlib.patches
import numpy as np
# create an ellipse
el = matplotlib.patches.Ellipse((50,-23), 10, 13.7, 30, facecolor=(1,0,0,.2), edgecolor='none')
# find the bounding box of the ellipse
bb = el.get_window_extent()
# calculate the x and y points possibly within the ellipse
x_int = np.arange(np.ceil(bb.x0), np.floor(bb.x1) + 1, dtype='int')
y_int = np.arange(np.ceil(bb.y0), np.floor(bb.y1) + 1, dtype='int')
# create a list of possible coordinates
g = np.meshgrid(x_int, y_int)
coords = np.array(zip(*(c.flat for c in g)))
# create a list of transformed points (transformed so that the ellipse is a unit circle)
transcoords = el.get_transform().inverted().transform(coords)
# find the transformed coordinates which are within a unit circle
validcoords = transcoords[:,0]**2 + transcoords[:,1]**2 < 1.0
# create the list of valid coordinates (from untransformed)
ellipsepoints = coords[validcoords]
# just to see if this works
fig = plt.figure()
ax = fig.add_subplot(111)
ax.add_artist(el)
ep = np.array(ellipsepoints)
ax.plot(ellipsepoints[:,0], ellipsepoints[:,1], 'ko')

Кажется, работает:

(Масштабирование показывает, что даже точки, висящие на краю, находятся внутри.)

Дело в том, что matplotlib обрабатывает эллипсы в виде преобразованных кругов (перевод, поворот, масштабирование, что-то аффинное). Если преобразование применяется в обратном порядке, результатом является единичный круг в начале координат, и очень просто проверить, находится ли в нем точка.

Просто предупреждение: get_window_extent может быть не очень надежным, поскольку, похоже, используется сплайн-аппроксимация круга. Кроме того, см. Комментарий tcaswell для зависимости от визуализации.

Чтобы найти более надежную ограничительную рамку, вы можете:

  • создайте горизонтальный и вертикальный вектор в координатах графика (их положение не важно, [[0,0], [1,0]) и ([0,0], [0,1]) сделают)

  • преобразуют эти векторы в координаты эллипса (get_transform и т.д.)

  • найти в системе координат эллипса (т.е. систему, где эллипс является единичной окружностью вокруг начала координат), четыре касательных окружности, которые параллельны этим двум векторам

  • найти точки пересечения векторов (4 пересечения, но 2 диагоналей будет достаточно)

  • преобразуют точки пересечения обратно в координаты графика

Это даст точную (но, конечно, ограниченную числовую точность) квадратную ограничительную рамку.

Однако вы можете использовать простое приближение:

  • все возможные точки находятся внутри круга, центр которого совпадает с центром эллипса и диаметр которого тот же, что у главной оси эллипса

Другими словами, все возможные точки находятся в квадратной ограничивающей рамке, которая находится между x0 + -m/2, y0 + -m/2, где (x0, y0) - центр эллипса, а m - большая ось.


Я хотел бы предложить другое решение, которое использует метод Path object contains_points() вместо contains_point():

Сначала получим координаты эллипса и превратим его в объект Path:

elpath=Path(el.get_verts())

(ЗАМЕЧАНИЕ, что el.get_paths() по какой-то причине не будет работать.)

Затем вызовите путь contains_points():

validcoords=elpath.contains_points(coords)

Ниже я сравниваю решение @tacaswell (метод 1), @Drv (метод 2) и свой собственный (метод 3) (я увеличил эллипс примерно на 5 раз):

import numpy
import matplotlib.pyplot as plt
from matplotlib.patches import Ellipse
from matplotlib.path import Path
import time
#----------------Create an ellipse----------------
el=Ellipse((50,-23),50,70,30,facecolor=(1,0,0,.2), edgecolor='none')
#---------------------Method 1---------------------
t1=time.time()
for ii in range(50):
 y=numpy.arange(-100,50)
 x=numpy.arange(-30,130)
 g=numpy.meshgrid(x,y)
 coords=numpy.array(zip(*(c.flat for c in g)))
 ellipsepoints = numpy.vstack([p for p in coords if el.contains_point(p, radius=0)])
t2=time.time()
print 'time of method 1',t2-t1
#---------------------Method 2---------------------
t2=time.time()
for ii in range(50):
 y=numpy.arange(-100,50)
 x=numpy.arange(-30,130)
 g=numpy.meshgrid(x,y)
 coords=numpy.array(zip(*(c.flat for c in g)))
 invtrans=el.get_transform().inverted()
 transcoords=invtrans.transform(coords)
 validcoords=transcoords[:,0]**2+transcoords[:,1]**2<=1.0
 ellipsepoints=coords[validcoords]
t3=time.time()
print 'time of method 2',t3-t2
#---------------------Method 3---------------------
t3=time.time()
for ii in range(50):
 y=numpy.arange(-100,50)
 x=numpy.arange(-30,130)
 g=numpy.meshgrid(x,y)
 coords=numpy.array(zip(*(c.flat for c in g)))
 #------Create a path from ellipse vertices------
 elpath=Path(el.get_verts())
 # call contains_points()
 validcoords=elpath.contains_points(coords)
 ellipsepoints=coords[validcoords]
t4=time.time()
print 'time of method 3',t4-t3
#---------------------Plot it ---------------------
fig,ax=plt.subplots()
ax.add_artist(el)
ep=numpy.array(ellipsepoints)
ax.plot(ellipsepoints[:,0],ellipsepoints[:,1],'ko')
plt.show(block=False)

Я получил это время выполнения:

time of method 1 62.2502269745
time of method 2 0.488734006882
time of method 3 0.588987112045

Таким образом, подход contains_point() медленнее. Метод преобразования координат быстрее моего, но когда вы получаете контуры/полигоны неправильной формы, этот метод все равно будет работать.

Наконец, результат:

licensed under cc by-sa 3.0 with attribution.