Matplotlib: ошибка в интерактивном инструменте масштабирования после обновления фигуры - PullRequest
1 голос
/ 04 июля 2019

Я снова застрял с интерактивным построением графиков с помощью matplotlib.

Все остальное работает как шарм (зависание и щелчок объектов на фигуре), но если я увеличу показанную фигуру, и она будет обновлена, прямоугольник масштабирования останется на новой фигуре. Вероятно, мне нужно как-то сбросить настройки масштабирования, но я не смог найти правильный способ сделать это из других вопросов StackOverflow (очевидно, недостаточно прояснить рисунок).

Я построил игрушечный пример, чтобы проиллюстрировать проблему. Четыре точки прикреплены к четырем изображениям, и они нанесены на рисунок. В интерактивном режиме, вставив курсор поверх выбранной точки, он показывает связанное изображение в графическом окне. После нажатия на одну точку программа ждет 2 секунды и обновляет вид, поворачивая все выборки на 15 градусов.

Проблема возникает, когда текущее представление увеличивается, а затем обновляется. Масштабирование до прямоугольника начнется автоматически, и после одного нажатия в любом месте рисунка прямоугольник исчезнет, ​​ничего не делая. Это показано на рисунке ниже. Я просто хочу иметь нормальный курсор после обновления рисунка.

enter image description here

Вот код для примера игрушки:

import matplotlib.pyplot as plt
import matplotlib as mpl
from matplotlib.offsetbox import OffsetImage, AnnotationBbox
import numpy as np
import copy

def initialize_figure(fignum):
    plt.figure(fignum)
    plt.clf()

def draw_interactive_figures(new_samples, images):
    global new_samples_tmp, images_tmp, offset_image_tmp, image_box_tmp, fig_tmp, x_tmp, y_tmp
    initialize_figure(1)
    plt.ion()
    fig_tmp = plt.gcf()
    images_tmp = copy.deepcopy(images)
    offset_image_tmp = OffsetImage(images_tmp[0,:,:,:])
    image_box_tmp = (40., 40.)
    x_tmp = new_samples[:,0]
    y_tmp = new_samples[:,1]
    new_samples_tmp = copy.deepcopy(new_samples)
    update_plot()

    fig_tmp.canvas.mpl_connect('motion_notify_event', hover)
    fig_tmp.canvas.mpl_connect('button_press_event', click)
    plt.show()
    fig_tmp.canvas.start_event_loop()
    plt.ioff()

def update_plot():
    global points_tmp, annotationbox_tmp
    ax = plt.gca()
    points_tmp = plt.scatter(*new_samples_tmp.T, s=14, c='b', edgecolor='k')
    annotationbox_tmp = AnnotationBbox(offset_image_tmp, (0,0), xybox=image_box_tmp, xycoords='data', boxcoords='offset points',  pad=0.3,  arrowprops=dict(arrowstyle='->'))
    ax.add_artist(annotationbox_tmp)
    annotationbox_tmp.set_visible(False)

def hover(event):
    if points_tmp.contains(event)[0]:
        inds = points_tmp.contains(event)[1]['ind']
        ind = inds[0]
        w,h = fig_tmp.get_size_inches()*fig_tmp.dpi
        ws = (event.x > w/2.)*-1 + (event.x <= w/2.) 
        hs = (event.y > h/2.)*-1 + (event.y <= h/2.)
        annotationbox_tmp.xybox = (image_box_tmp[0]*ws, image_box_tmp[1]*hs)
        annotationbox_tmp.set_visible(True)
        annotationbox_tmp.xy =(x_tmp[ind], y_tmp[ind])
        offset_image_tmp.set_data(images_tmp[ind,:,:])
    else:
        annotationbox_tmp.set_visible(False)
    fig_tmp.canvas.draw_idle()

def click(event):
    if points_tmp.contains(event)[0]:
        inds = points_tmp.contains(event)[1]['ind']
        ind = inds[0]
        initialize_figure(1)
        update_plot()
        plt.scatter(x_tmp[ind], y_tmp[ind], s=20, marker='*', c='y')
        plt.pause(2)
        fig_tmp.canvas.stop_event_loop()
    fig_tmp.canvas.draw_idle()

def main():
    fig, ax = plt.subplots(1, figsize=(7, 7))

    points = np.array([[1,1],[1,-1],[-1,1],[-1,-1]])
    zero_layer = np.zeros([28,28])
    one_layer = np.ones([28,28])*255
    images = np.array([np.array([zero_layer, zero_layer, one_layer]).astype(np.uint8),np.array([zero_layer, one_layer, zero_layer]).astype(np.uint8),np.array([one_layer, zero_layer, zero_layer]).astype(np.uint8),np.array([one_layer, zero_layer, one_layer]).astype(np.uint8)])
    images = np.transpose(images, (0,3,2,1))
    theta = 0
    delta = 15 * (np.pi/180)
    rotation_matrix = np.array([[np.cos(theta),-np.sin(theta)],[np.sin(theta),np.cos(theta)]])
    while True:
        rotated_points = np.matmul(points, rotation_matrix)
        draw_interactive_figures(rotated_points, images)
        theta += delta
        rotation_matrix = np.array([[np.cos(theta),-np.sin(theta)],[np.sin(theta),np.cos(theta)]])

if __name__== "__main__":
    main()

Заранее спасибо!

1 Ответ

0 голосов
/ 04 июля 2019

Я предоставляю вам отправную точку здесь. Ниже приведен сценарий, который создает график и позволяет добавлять новые точки, нажимая на оси. Для каждой точки можно навести курсор мыши и показать соответствующее изображение.

import matplotlib.pyplot as plt
from matplotlib.offsetbox import OffsetImage, AnnotationBbox
import numpy as np


class MyInteractivePlotter():
    def __init__(self):
        self.fig, self.ax = plt.subplots()
        self.ax.set(xlim=(0,1), ylim=(0,1))

        self.points = np.array([[0.5,0.5]]) # will become N x 2 array
        self.images = [np.random.rand(10,10)]

        self.scatter = self.ax.scatter(*self.points.T)
        self.im = OffsetImage(self.images[0], zoom=5)

        self.ab = AnnotationBbox(self.im, (0,0), xybox=(50., 50.), xycoords='data',
                                 boxcoords="offset points",  pad=0.3,  
                                 arrowprops=dict(arrowstyle="->"))
        # add it to the axes and make it invisible
        self.ax.add_artist(self.ab)
        self.ab.set_visible(False)

        self.cid = self.fig.canvas.mpl_connect("button_press_event", self.onclick)
        self.hid = self.fig.canvas.mpl_connect("motion_notify_event", self.onhover)

    def add_point(self):
        # Update points (here, we just add a new random point)
        self.points = np.concatenate((self.points, np.random.rand(1,2)), axis=0)
        # For each points there is an image. (Here, we just add a random one)
        self.images.append(np.random.rand(10,10))
        # Update the scatter plot to show the new point
        self.scatter.set_offsets(self.points)


    def onclick(self, event):
        self.add_point()
        self.fig.canvas.draw_idle()

    def onhover(self, event):
        # if the mouse is over the scatter points
        if self.scatter.contains(event)[0]:
            # find out the index within the array from the event
            ind, = self.scatter.contains(event)[1]["ind"]           
            # make annotation box visible
            self.ab.set_visible(True)
            # place it at the position of the hovered scatter point
            self.ab.xy = self.points[ind,:]
            # set the image corresponding to that point
            self.im.set_data(self.images[ind])
        else:
            #if the mouse is not over a scatter point
            self.ab.set_visible(False)
        self.fig.canvas.draw_idle()


m = MyInteractivePlotter()
plt.show()

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...