Có cách nào trong matplotlib để kiểm tra những nghệ sĩ nào đang ở trong khu vực hiện đang hiển thị của các trục không?


9

Tôi có một chương trình với một nhân vật tương tác, nơi thỉnh thoảng có nhiều nghệ sĩ được vẽ. Trong hình này, bạn cũng có thể phóng to và xoay bằng chuột. Tuy nhiên, hiệu suất trong khi phóng to thu phóng không phải là rất tốt vì mọi nghệ sĩ luôn được vẽ lại. Có cách nào để kiểm tra những nghệ sĩ nào đang ở trong khu vực hiện đang hiển thị và chỉ vẽ lại những nghệ sĩ đó không? (Trong ví dụ bên dưới perfomace vẫn tương đối tốt, nhưng nó có thể bị làm xấu đi một cách tùy tiện bằng cách sử dụng nhiều hoặc nhiều nghệ sĩ phức tạp hơn)

Tôi đã có một vấn đề hiệu suất tương tự với hoverphương pháp mà bất cứ khi nào nó được gọi là nó chạy canvas.draw()ở cuối. Nhưng như bạn có thể thấy tôi đã tìm thấy một cách giải quyết gọn gàng cho điều đó bằng cách sử dụng bộ nhớ đệm và khôi phục nền của các trục (dựa trên điều này ). Điều này cải thiện đáng kể hiệu suất và bây giờ ngay cả với nhiều nghệ sĩ, nó chạy rất trơn tru. Có thể có một cách tương tự để làm điều này nhưng cho panzoomphương pháp?

Xin lỗi cho mẫu mã dài, hầu hết không liên quan trực tiếp đến câu hỏi nhưng cần thiết cho một ví dụ hoạt động để làm nổi bật vấn đề.

BIÊN TẬP

Tôi đã cập nhật MWE thành một cái gì đó đại diện hơn cho mã thực tế của tôi.

import numpy as np
import numpy as np
import sys
import matplotlib.pyplot as plt
from matplotlib.backends.backend_qt5agg import \
    FigureCanvasQTAgg
import matplotlib.patheffects as PathEffects
from matplotlib.text import Annotation
from matplotlib.collections import LineCollection

from PyQt5.QtWidgets import QApplication, QVBoxLayout, QDialog


def check_limits(base_xlim, base_ylim, new_xlim, new_ylim):
    if new_xlim[0] < base_xlim[0]:
        overlap = base_xlim[0] - new_xlim[0]
        new_xlim[0] = base_xlim[0]
        if new_xlim[1] + overlap > base_xlim[1]:
            new_xlim[1] = base_xlim[1]
        else:
            new_xlim[1] += overlap
    if new_xlim[1] > base_xlim[1]:
        overlap = new_xlim[1] - base_xlim[1]
        new_xlim[1] = base_xlim[1]
        if new_xlim[0] - overlap < base_xlim[0]:
            new_xlim[0] = base_xlim[0]
        else:
            new_xlim[0] -= overlap
    if new_ylim[1] < base_ylim[1]:
        overlap = base_ylim[1] - new_ylim[1]
        new_ylim[1] = base_ylim[1]
        if new_ylim[0] + overlap > base_ylim[0]:
            new_ylim[0] = base_ylim[0]
        else:
            new_ylim[0] += overlap
    if new_ylim[0] > base_ylim[0]:
        overlap = new_ylim[0] - base_ylim[0]
        new_ylim[0] = base_ylim[0]
        if new_ylim[1] - overlap < base_ylim[1]:
            new_ylim[1] = base_ylim[1]
        else:
            new_ylim[1] -= overlap

    return new_xlim, new_ylim


class FigureCanvas(FigureCanvasQTAgg):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.bg_cache = None

    def draw(self):
        ax = self.figure.axes[0]
        hid_annotation = False
        if ax.annot.get_visible():
            ax.annot.set_visible(False)
            hid_annotation = True
        hid_highlight = False
        if ax.last_artist:
            ax.last_artist.set_path_effects([PathEffects.Normal()])
            hid_highlight = True
        super().draw()
        self.bg_cache = self.copy_from_bbox(self.figure.bbox)
        if hid_highlight:
            ax.last_artist.set_path_effects(
                [PathEffects.withStroke(
                    linewidth=7, foreground="c", alpha=0.4
                )]
            )
            ax.draw_artist(ax.last_artist)
        if hid_annotation:
            ax.annot.set_visible(True)
            ax.draw_artist(ax.annot)

        if hid_highlight:
            self.update()


def position(t_, coeff, var=0.1):
    x_ = np.random.normal(np.polyval(coeff[:, 0], t_), var)
    y_ = np.random.normal(np.polyval(coeff[:, 1], t_), var)

    return x_, y_


class Data:
    def __init__(self, times):
        self.length = np.random.randint(1, 20)
        self.t = np.sort(
            np.random.choice(times, size=self.length, replace=False)
        )
        self.vel = [np.random.uniform(-2, 2), np.random.uniform(-2, 2)]
        self.accel = [np.random.uniform(-0.01, 0.01), np.random.uniform(-0.01,
                                                                      0.01)]
        x0, y0 = np.random.uniform(0, 1000, 2)
        self.x, self.y = position(
            self.t, np.array([self.accel, self.vel, [x0, y0]])
        )


class Test(QDialog):
    def __init__(self):
        super().__init__()
        self.fig, self.ax = plt.subplots()
        self.canvas = FigureCanvas(self.fig)
        self.artists = []
        self.zoom_factor = 1.5
        self.x_press = None
        self.y_press = None
        self.annot = Annotation(
            "", xy=(0, 0), xytext=(-20, 20), textcoords="offset points",
            bbox=dict(boxstyle="round", fc="w", alpha=0.7), color='black',
            arrowprops=dict(arrowstyle="->"), zorder=6, visible=False,
            annotation_clip=False, in_layout=False,
        )
        self.annot.set_clip_on(False)
        setattr(self.ax, 'annot', self.annot)
        self.ax.add_artist(self.annot)
        self.last_artist = None
        setattr(self.ax, 'last_artist', self.last_artist)

        self.image = np.random.uniform(0, 100, 1000000).reshape((1000, 1000))
        self.ax.imshow(self.image, cmap='gray', interpolation='nearest')
        self.times = np.linspace(0, 20)
        for i in range(1000):
            data = Data(self.times)
            points = np.array([data.x, data.y]).T.reshape(-1, 1, 2)
            segments = np.concatenate([points[:-1], points[1:]], axis=1)
            z = np.linspace(0, 1, data.length)
            norm = plt.Normalize(z.min(), z.max())
            lc = LineCollection(
                segments, cmap='autumn', norm=norm, alpha=1,
                linewidths=2, picker=8, capstyle='round',
                joinstyle='round'
            )
            setattr(lc, 'data_id', i)
            lc.set_array(z)
            self.ax.add_artist(lc)
            self.artists.append(lc)
        self.default_xlim = self.ax.get_xlim()
        self.default_ylim = self.ax.get_ylim()

        self.canvas.draw()

        self.cid_motion = self.fig.canvas.mpl_connect(
            'motion_notify_event', self.motion_event
        )
        self.cid_button = self.fig.canvas.mpl_connect(
            'button_press_event', self.pan_press
        )
        self.cid_zoom = self.fig.canvas.mpl_connect(
            'scroll_event', self.zoom
        )

        layout = QVBoxLayout()
        layout.addWidget(self.canvas)
        self.setLayout(layout)

    def zoom(self, event):
        if event.inaxes == self.ax:
            scale_factor = np.power(self.zoom_factor, -event.step)
            xdata = event.xdata
            ydata = event.ydata
            cur_xlim = self.ax.get_xlim()
            cur_ylim = self.ax.get_ylim()
            x_left = xdata - cur_xlim[0]
            x_right = cur_xlim[1] - xdata
            y_top = ydata - cur_ylim[0]
            y_bottom = cur_ylim[1] - ydata

            new_xlim = [
                xdata - x_left * scale_factor, xdata + x_right * scale_factor
            ]
            new_ylim = [
                ydata - y_top * scale_factor, ydata + y_bottom * scale_factor
            ]
            # intercept new plot parameters if they are out of bounds
            new_xlim, new_ylim = check_limits(
                self.default_xlim, self.default_ylim, new_xlim, new_ylim
            )

            if cur_xlim != tuple(new_xlim) or cur_ylim != tuple(new_ylim):
                self.ax.set_xlim(new_xlim)
                self.ax.set_ylim(new_ylim)

                self.canvas.draw_idle()

    def motion_event(self, event):
        if event.button == 1:
            self.pan_move(event)
        else:
            self.hover(event)

    def pan_press(self, event):
        if event.inaxes == self.ax:
            self.x_press = event.xdata
            self.y_press = event.ydata

    def pan_move(self, event):
        if event.inaxes == self.ax:
            xdata = event.xdata
            ydata = event.ydata
            cur_xlim = self.ax.get_xlim()
            cur_ylim = self.ax.get_ylim()
            dx = xdata - self.x_press
            dy = ydata - self.y_press
            new_xlim = [cur_xlim[0] - dx, cur_xlim[1] - dx]
            new_ylim = [cur_ylim[0] - dy, cur_ylim[1] - dy]

            # intercept new plot parameters that are out of bound
            new_xlim, new_ylim = check_limits(
                self.default_xlim, self.default_ylim, new_xlim, new_ylim
            )

            if cur_xlim != tuple(new_xlim) or cur_ylim != tuple(new_ylim):
                self.ax.set_xlim(new_xlim)
                self.ax.set_ylim(new_ylim)

                self.canvas.draw_idle()

    def update_annot(self, event, artist):
        self.ax.annot.xy = (event.xdata, event.ydata)
        text = f'Data #{artist.data_id}'
        self.ax.annot.set_text(text)
        self.ax.annot.set_visible(True)
        self.ax.draw_artist(self.ax.annot)

    def hover(self, event):
        vis = self.ax.annot.get_visible()
        if event.inaxes == self.ax:
            ind = 0
            cont = None
            while (
                ind in range(len(self.artists))
                and not cont
            ):
                artist = self.artists[ind]
                cont, _ = artist.contains(event)
                if cont and artist is not self.ax.last_artist:
                    if self.ax.last_artist is not None:
                        self.canvas.restore_region(self.canvas.bg_cache)
                        self.ax.last_artist.set_path_effects(
                            [PathEffects.Normal()]
                        )
                        self.ax.last_artist = None
                    artist.set_path_effects(
                        [PathEffects.withStroke(
                            linewidth=7, foreground="c", alpha=0.4
                        )]
                    )
                    self.ax.last_artist = artist
                    self.ax.draw_artist(self.ax.last_artist)
                    self.update_annot(event, self.ax.last_artist)
                ind += 1

            if vis and not cont and self.ax.last_artist:
                self.canvas.restore_region(self.canvas.bg_cache)
                self.ax.last_artist.set_path_effects([PathEffects.Normal()])
                self.ax.last_artist = None
                self.ax.annot.set_visible(False)
        elif vis:
            self.canvas.restore_region(self.canvas.bg_cache)
            self.ax.last_artist.set_path_effects([PathEffects.Normal()])
            self.ax.last_artist = None
            self.ax.annot.set_visible(False)
        self.canvas.update()
        self.canvas.flush_events()


if __name__ == '__main__':
    app = QApplication(sys.argv)
    test = Test()
    test.show()
    sys.exit(app.exec_())

Tôi không hiểu vấn đề. Vì các nghệ sĩ ở ngoài rìu không được vẽ dù sao, họ cũng sẽ không làm chậm bất cứ điều gì.
ImportanceOfByingErnest

Vì vậy, bạn đang nói rằng đã có một thói quen kiểm tra những nghệ sĩ nào có thể được nhìn thấy để chỉ những người nhìn thấy được thực sự được vẽ? Có lẽ thói quen này là những gì tính toán rất tốn kém? Bởi vì bạn có thể dễ dàng thấy sự khác biệt về hiệu suất nếu bạn thử các cách sau, ví dụ: với 1000 nghệ sĩ WME của tôi ở trên, phóng to một nghệ sĩ duy nhất và xoay quanh. Bạn sẽ nhận thấy một sự chậm trễ đáng kể. Bây giờ làm tương tự nhưng chỉ vẽ 1 (hoặc thậm chí 100) nghệ sĩ và bạn sẽ thấy rằng hầu như không có độ trễ.
mapf

Vâng, câu hỏi là, bạn có thể viết một thói quen hiệu quả hơn? Trong một trường hợp đơn giản, có thể. Vì vậy, bạn có thể kiểm tra những nghệ sĩ nào nằm trong giới hạn chế độ xem và đặt tất cả những thứ vô hình khác. Nếu kiểm tra chỉ so sánh tọa độ trung tâm của các chấm, thì nhanh hơn. Nhưng điều đó sẽ khiến bạn mất dấu chấm nếu chỉ có trung tâm của nó ở bên ngoài nhưng hơi ít hơn một nửa trong số đó vẫn ở trong tầm nhìn. Điều đó đang được nói, vấn đề chính ở đây là có 1000 nghệ sĩ trong các trục. Nếu thay vào đó, bạn chỉ sử dụng một điểm duy nhất plotvới tất cả các điểm, vấn đề sẽ không xảy ra.
ImportanceOfByingErnest

Vâng hoàn toàn đúng. Chỉ là tiền đề của tôi đã sai. Tôi nghĩ lý do cho màn trình diễn tệ hại là tất cả các nghệ sĩ đều bị thu hút độc lập về việc họ có thể được nhìn thấy hay không. Vì vậy, tôi nghĩ rằng một thói quen thông minh chỉ thu hút các nghệ sĩ sẽ được nhìn thấy sẽ cải thiện hiệu suất nhưng rõ ràng thói quen đó đã được áp dụng, vì vậy tôi đoán rằng không có nhiều điều có thể được thực hiện ở đây. Tôi khá chắc chắn rằng tôi sẽ không thể viết một thói quen hiệu quả hơn, ít nhất là cho một trường hợp chung.
mapf

Tuy nhiên, trong trường hợp của tôi, tôi thực sự đang xử lý các linecollection (cộng với một hình ảnh ở hậu cảnh) và như bạn đã nói, ngay cả khi nó chỉ là các chấm như trong MWE của tôi, chỉ cần kiểm tra xem tọa độ bên trong các trục là không đủ. Có lẽ tôi nên cập nhật MWE cho phù hợp để làm cho nó rõ ràng hơn.
mapf

Câu trả lời:


0

Bạn có thể tìm thấy nghệ sĩ nào đang ở trong khu vực hiện tại của các trục nếu bạn tập trung vào dữ liệu mà các nghệ sĩ đang vẽ.

Ví dụ: nếu bạn đặt dữ liệu điểm của mình ( abmảng) vào một mảng gọn gàng như thế này:

self.points = np.random.randint(0, 100, (1000, 2))

bạn có thể lấy danh sách các điểm bên trong giới hạn x và y hiện tại:

xmin, xmax = self.ax.get_xlim()
ymin, ymax = self.ax.get_ylim()

p = self.points

indices_of_visible_points = (np.argwhere((p[:, 0] > xmin) & (p[:, 0] < xmax) & (p[:, 1] > ymin) &  (p[:, 1] < ymax))).flatten()

bạn có thể sử dụng indices_of_visible_pointsđể lập chỉ mục self.artistsdanh sách liên quan của bạn


Cảm ơn bạn vì câu trả lời! Thật không may, điều này chỉ hoạt động trong trường hợp các nghệ sĩ là điểm duy nhất. Nó đã không còn hoạt động nữa nếu các nghệ sĩ là dòng. Ví dụ: hình ảnh một đường được xác định chỉ bởi hai điểm trong đó các điểm nằm ngoài giới hạn trục, tuy nhiên đường nối các điểm đang giao nhau với khung trục. Có lẽ tôi nên chỉnh sửa MWE cho phù hợp để nó rõ ràng hơn.
mapf

Đối với tôi cách tiếp cận là như nhau, tập trung vào dữ liệu . Nếu các nghệ sĩ là các dòng, bạn có thể kiểm tra thêm giao điểm với hình chữ nhật xem. Nếu bạn đang vẽ các đường cong, có lẽ bạn đang lấy mẫu chúng theo các khoảng thời gian cố định, giảm chúng thành các đoạn đường. Nhân tiện, bạn có thể đưa ra một mẫu thực tế hơn về những gì bạn đang âm mưu?
Guglie

Tôi đã cập nhật lên MWE
mapf
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.