Https://github.com/matplotlib/matplotlib/issues/23736# follow up - Quiver not shown until a view change

Hi,

We opened an issue on Github and was recommended to follow it up here by open and “issue”. The problem background was described in the github issue 23736.(see link at topic)

In short the problem is that quiver, which is a result of mouse pick, will not shown until a mouse drag view change on a 3D scatter plot. We follow the suggestion that Thomas gave us by adding draw_artist to force copy buffer to the GUI widget and addding restore background to restore unchanged part, Unfortunatiley the solution does not work.

Our target is to display the quiver once the pick is done without changing view every time. We don’t plan to use canvas.show() as its performace is not satisfying when the scatter data become large, eg 1M pt. Appreciate if anyone can give us a hand.

ps. If copy git issue content to here is required pls. let us know.

The code of on pick part as following:
def pick(self,event):
# pt3d_art = event.artist
xx=event.mouseevent.x
yy=event.mouseevent.y

    ind_min = self.find_closest(xx,yy)    
    px_flat = self.X[ind_min]  ## 
    py_flat = self.Y[ind_min]  ##self.Y
    pz_flat = self.Z[ind_min]   ##self.Z
    pt_norm = self.data_pcd.normals[ind_min]
   
    u,v,w=pt_norm[0],pt_norm[1],pt_norm[2]
    Vnorm = v3dv.Vector(*pt_norm)
    agl_z = v3dv.angle(self.Vz, Vnorm)  ## degrees
    print("norm to z angle is ",agl_z)
    
    bg = self.canvas.copy_from_bbox(self.fig.bbox)
    my_qvr = self.axes.quiver(px_flat, py_flat, pz_flat, u, v, w, colors ="r") #length=0.1, normalize=True) 

    self.canvas.blit(self.fig.bbox)
    self.rdrer = self.canvas.get_renderer()
    my_qvr.draw(self.rdrer)  ## no effect 
    self.axes.draw_artist(my_qvr)  ## 
    self.canvas.restore_region(bg)
    draw_list = self.axes.get_children()
    print("draw list is ",draw_list)
    # self.axes.redraw_in_frame()  ## poor performance
    self.canvas.update()
    # self.canvas.draw()
    self.canvas.flush_events()

def find_closest(self,c_x,c_y):
    imin=0
    mat_c = self.axes.get_proj()
    if not np.allclose(mat_c,self.mat_last):
        x2, y2, z2=proj3d.proj_transform(self.X_flat, self.Y_flat, self.Z_flat, mat_c)
        self.xy_2 = np.array((x2,y2)).T
         self.mat_last = mat_c
         
    invt = self.axes.transData.inverted()
    cx_2, cy_2 = invt.transform((c_x,c_y))
    cxy_2 = np.array([[cx_2,cy_2]])
    d_c = self.xy_2 - cxy_2
    d_pt = np.linalg.norm(d_c,axis=1)
    imin = np.argmin(d_pt)
    return imin

Regards

It is much easier for us to help you if you provide a minimal example that we can copy/paste/run and then adapt.

I pointed you at Faster rendering by using blitting — Matplotlib 3.5.3 documentation which use ax.draw_artist as part of the solution, however that is only part of the solution. You also need to do all of the steps in the first example (which is the minimal code needed to get blitting to work).

Can you get the examples in the docs to run? If not what issues are you having?

Thank you Thomas !

During this week end I will manage to
a. run the minimal code. If there are any issues, I will search resources to fix them with best effort
b. compare mini code with the our code and change accordingly
c. prepare a sample code than can run and show the issues for debugging if the issue still exits

Enjoy your weekend. I will let you know the result once I have done all above.
Regards

Hello:

Here is the summary of what we did and got after last update:

  1. The smaple code from ’ Faster rendering by using blitting — Matplotlib 3.5.3 documentation’
    result: ran well. Dynamic line works

  2. Our code change according to sample code
    We found several difference between sample code and our code:
    a. plt.show(block = False) – we beleive this is not required then – no add
    b. then animated=True of artist – We tried to add it into quiver property. However quiver never appear after it so – no add
    c. the bbox copy and restore – since we are doing accumulated work, it is not required nor harmful, so – add
    d. the blite(bbox) – add
    Result:
    No progress:
    a. Quiver can be created but not visible
    b. slightly change view by mouse drag will show the quivers

  3. Re-create the issue:
    we prepare a sample code that you can copy-paste-run to show the issue we saw. The code is attached below

Our target is to replace the canvas_draw with a faster adding quiver only method. Appreciate if you help us to find what we did wrong here.

from PyQt5 import QtWidgets
from matplotlib.figure import Figure
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
import numpy as np
import open3d as o3d
from mpl_toolkits.mplot3d
import proj3d

class Widget(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()
        self.fig = Figure()
        self.canvas = FigureCanvas(self.fig)
        self.axes = self.fig.add_subplot(111, projection='3d')

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

        self.data_pcd = o3d.geometry.PointCloud()
        
        self.X = np.arange(-10, 10, 0.2)
        self.Y = np.arange(-10, 10, 0.2)
        self.X, self.Y = np.meshgrid(self.X, self.Y)
        R = np.sqrt(self.X ** 2 + self.Y ** 2)
        self.Z = np.sin(R)
        
        self.X_flat = np.reshape(self.X,-1)
        self.Y_flat = np.reshape(self.Y,-1)
        self.Z_flat = np.reshape(self.Z,-1)
        
        self.xyz = np.zeros((np.size(self.X), 3))
        self.xyz[:, 0] = self.X_flat
        self.xyz[:, 1] = self.Y_flat
        self.xyz[:, 2] = self.Z_flat
        
        self.data_pcd.points = o3d.utility.Vector3dVector(self.xyz)
        self.data_pcd.estimate_normals(search_param=o3d.geometry.KDTreeSearchParamKNN(knn=20))
        
        # self.axes.plot_surface(self.X,self.Y,self.Z,picker = 0.25)#,rcount=20,ccount =20)
        self.axes.scatter3D(self.X,self.Y,self.Z,picker=5, s=10 ,alpha=0.1)
        self.axes.set_xlabel("X data")
        self.axes.set_ylabel("Y data")
        self.canvas.draw()
        self.cid3 = self.canvas.mpl_connect("pick_event", self.pick) #pick_event,button_press_event
        
    def pick(self,event):
        
        xx=event.mouseevent.x
        yy=event.mouseevent.y        
        ind_min = self.find_closest(xx,yy)
        
        px_flat = self.X_flat[ind_min]
        py_flat = self.Y_flat[ind_min]
        pz_flat = self.Z_flat[ind_min]
        pt_norm = self.data_pcd.normals[ind_min]        
        u,v,w=pt_norm[0],pt_norm[1],pt_norm[2]
        
        bg = self.canvas.copy_from_bbox(self.fig.bbox)
        self.canvas.restore_region(bg)   ## for consistent to matlib ref sample only
        myqv = self.axes.quiver(px_flat, py_flat, pz_flat, u, v, w, colors ="r")# ,animated=True) #length=0.1, normalize=True)
        
        self.axes.draw_artist(myqv)        
        self.canvas.blit(self.fig.bbox)
        
        self.canvas.update()
        self.canvas.flush_events()
        # self.canvas.draw()   ## remove on purpose for performance 

    def find_closest(self,c_x,c_y):
        x2arr, y2arr, z2arr = proj3d.proj_transform(self.X_flat, self.Y_flat, self.Z_flat, self.axes.get_proj())

        inv = self.axes.transData.inverted()
        cx_2,cy_2 = inv.transform((c_x,c_y))
        cxy_2 = np.array([[cx_2,cy_2]])

        xy_2 = np.array([x2arr,y2arr]).T
        d_c = xy_2 - cxy_2  
        d_pt = np.linalg.norm(d_c,axis = 1)
        imin = np.argmin(d_pt) #,axis = 1)
        return imin

if __name__ == "__main__":
    app = QtWidgets.QApplication([])
    win = Widget()
    win.show()
    app.exec()

Try adding

...
myqv = self.axes.quiver(px_flat, py_flat, pz_flat, u, v, w, colors ="r")# ,animated=True) #length=0.1, normalize=True)    
myqv.do_3d_projection()  # <- new line
self.axes.draw_artist(myqv)        
...

Do you need the open3d dependency or to do the Qt embeding by hand to demonstrate the problem?

Cool! The quiver appears …

Thank you very much Thomas! There are so many secret operations in matplotlib to learn.

We made the demo code with Qt embeding by hand… Any special consideration?

1 Like