Matplotlib EllipseSelector - 如何获取路径?

问题描述

以下是 matplotlib 小部件 EllipseSelector 的基本示例。顾名思义,这个小部件用于通过在轴上绘制椭圆来选择数据。
准确地说,用户可以通过在轴上单击和拖动来绘制和修改椭圆。每次释放鼠标按钮时都会调用一个回调函数(例如:onselect)。
例子如下:

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import EllipseSelector

class EllipseExample:

    def __init__(self):

        # creating data points
        self.X,self.Y = (0,1,2),(0,-1,-2)
        self.XY = np.asarray((self.X,self.Y)).T

        # plotting
        self.fig,self.ax = plt.subplots()
        self.ax.scatter(self.X,self.Y) # just for visualization

        # creating the EllipseSelector
        self.es = EllipseSelector(self.ax,self.onselect,drawtype='Box',interactive=True)

        # bool array about selection status of XY rows.
        self.selection_bool = None # e.g. (False,True,False)
        
        plt.show()

    # selector callback method
    def onselect(self,eclick,erelease):
        print('click: (%f,%f)' % (eclick.xdata,eclick.ydata))
        print('release  : (%f,%f)' % (erelease.xdata,erelease.ydata))
        # how to get the path of the selector's ellipse?
        # path = self.es.??? <--- no clue how to get there
        # self.selection_bool = path.contains_points(self.XY)
        # print('selection:\n',self.selection_bool)

example = EllipseExample()

我使用过其他 matplotlib 选择小部件(polygonSelector、RectangleSelector、LassoSelector)。这些都以某种方式返回与选择形状相对应的选择顶点,可用于直接过滤数据(例如 RectangleSelector 给出矩形范围的 x0、x1、y0、y1 坐标)或创建路径并通过 path.contains_points 检查如果数据在选择范围内。
基本上我是问:
我怎样才能将 EllipseSelector 不仅用于绘制和椭圆,还用于选择器部分?如何获取绘制椭圆的路径,以便我可以通过 path.contains_points 检查我的数据,如上面示例中的注释中所建议的那样。

解决方法

似乎没有直接的方法可以通过 .contains_points() 检查选择器中是否包含点。我能找到的最简单的方法是从 EllipseSelector 的属性创建一个椭圆补丁。这些属性继承自 RectangleSelector btw.
通过将选择的中心、宽度和高度传递给 matplotlib.patches.Ellipse,我们得到一个椭圆补丁,我们可以在该补丁上调用方法 contains_points()。该方法返回一个bool ndarray,每个元素对应一个数据点(True:选择包含点,False:选择不包含点)。
所述布尔数组可用于例如过滤熊猫数据框。
注意:在任何情况下都不要将此补丁添加到轴(即不要绘制此补丁),因为它的坐标将被转换,如果没有转换步骤,您将无法再检查原始数据。
这是一个分步初学者友好的示例,带有详细的代码注释:

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import EllipseSelector
from matplotlib.patches import Ellipse

class EllipseSelectorExample:

    def __init__(self):

        # creating data points as numpy arrays
        self.X = np.asarray((0,1,2,3,4,5,6))
        self.Y = np.asarray((0,0))
        
        # plotting
        self.fig,self.ax = plt.subplots()
        self.ax.set_xlim(-1,7),self.ax.set_ylim(-3,3)
        self.ax.grid(True)
        self.ax.scatter(self.X,self.Y)

        # creating the EllipseSelector and connecting it to onselect
        self.es = EllipseSelector(self.ax,self.onselect,drawtype='box',interactive=True)
        plt.show()

    # selector callback method
    def onselect(self,eclick,erelease):

        # 1. Collect ellipse parameters (center,width,height)

        # getting the center property of the drawn ellipse
        cx,cy = self.es.center # tuple of floats: (x,y)

        # calculating the width and height
        # self.es.extents returns tuple of floats: (xmin,xmax,ymin,ymax)
        xmin,ymax = self.es.extents
        width = xmax - xmin
        height = ymax - ymin
        print(f'center=({cx:.2f},{cy:.2f}),'
              f'width={width:.2f},height={height:.2f}')

        # 2. Create an ellipse patch
        # CAUTION: DO NOT PLOT (==add this patch to ax),as the coordinates will
        # be transformed and you will not be able to directly check your data
        # points.
        ellipse = Ellipse((cx,cy),height)

        # 3. Check which points are contained in the ellipse by directly calling
        # contains_points on the ellipse.
        # contains_points wants input like ( (x0,y0),(x1,y1),... )

        # X=x0,x1,... Y=y0,y1,...  ->  [ [x0,y0],[x1,y1],[x2,y2],... ]
        XY = np.asarray((self.X,self.Y)).T

        # calling contains_plot and returning our filter ndarray
        filter_array = ellipse.contains_points(XY)

        # 4. Apply filter to your data (optional)
        X_filtered = self.X[filter_array]
        Y_filtered = self.Y[filter_array]

        # results:
        print(f'\n'
              f'original data:\nX={self.X}\nY={self.Y}\n'
              f'filter_array={filter_array}\n'
              f'resulting data:\nX={X_filtered}\nY={Y_filtered}')

example = EllipseSelectorExample()

这是上面例子的一个简短版本,检查点只有3行代码:

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import EllipseSelector
from matplotlib.patches import Ellipse

class EllipseSelectorExample:

    def __init__(self):
        self.MYDATA = np.array([[0,6],[0,0]])
        self.fig,3),self.ax.grid(True)
        self.ax.scatter(self.MYDATA[0],self.MYDATA[1])
        self.es = EllipseSelector(self.ax,erelease):
        ext = self.es.extents
        ellipse = Ellipse(self.es.center,ext[1]-ext[0],ext[3]-ext[2])
        # result:
        print(ellipse.contains_points(self.MYDATA.T))

example = EllipseSelectorExample()