title: Efficient way to plotting multiple images with many patches in matplotlib?
body: <p>I'm writing code which displays features matches between images. The code runs fairly slow at the moment. I have some ideas on how to speed it up, but I'm not 100% comfortable with matplotlib yet or how its working behind the scenes. </p>
  
  <p>The basic structure of the code is:  (I'm leaving things out to make it more readable) </p>
  
  <pre><code>from matplotlib.patches import Rectangle, Circle, Ellipse
  import matplotlib.gridspec as gridspec
  from matplotlib.transforms import Affine2D
  from scipy.linalg import inv, sqrtm
  import matplotlib.pyplot as plt
  import numpy as np
  </code></pre>
  
  <ul>
  <li><p>Add a list images. Each image gets its own axes: ax, and remembers ax.transData</p>
  
  <pre><code>gs = gridspec.GridSpec( nr, nc )
  for i in range(num_images):
       dm.ax_list[i] = plt.subplot(gs[i])
       dm.ax_list[i].imshow( img_list[i])
       transData_list[i] = dm.ax_list[i].transData
  </code></pre></li>
  <li><p>Visualize the feature representation as Ellipses</p>
  
  <pre><code>for i in range(num_chips):
       axi =  chips[i].axi 
       ax  =  dm.ax_list[axi]
       transData = dm.transData_list[axi]
       chip_feats = chips[i].features
       for feat in chip_feats:
           (x,y,a,c,d) = feat
           A = numpy.array( [ ( a, 0, 0 ) ,
                                    ( c, d, 0 ) ,
                                    ( 0, 0, 1 ) ] , dtype=np.float64)
           EllShape = Affine2D( numpy.array(sqrtm( inv(A) ), dtype=np.float64) )
           transEll  = EllShape.translate(x,y)
           unitCirc = Circle((0,0),1,transform=transEll+transData)
           ax.add_patch(unitCirc)
  </code></pre></li>
  </ul>
  
  <p>I've used RunSnakeRun to profile the code, and all I really gather from that is it is taking a long time to draw everything. The basic idea that I had when I learned about the transformation in matplotlib was to draw each image in its own coordinates, and then maintain several transformations so I could do cool things with them later, but I suspect that it's not going to scale well. </p>
  
  <p>The actual output of the draw looks like this: </p>
  
  <p><img src="https://i.imgur.com/TeMrXv5.png" alt=""></p>
  
  <p>The figure takes about 4 seconds to redraw when I resize it, and I'm going to be wanting to pan/zoom. </p>
  
  <p>I was adding two patches for each feature, and about (300 features per image) so I could see an outline and some transparency. So, there's obviously overhead for that. But its also relatively slow even without any ellipses. </p>
  
  <p>I also need to write some code to put lines in between the matching features, but now I'm not so sure using multiple axes is a very good idea, especially when this is a relatively small dataset. </p>
  
  <p>So, for more concrete questions: </p>
  
  <ul>
  <li>Would it be more efficient to plot Ellipses vs Transformed Circles? What is the overhead of using matplotlib transformations? </li>
  <li>Is there a way to combine a group of patches so they transform together or more efficiently? </li>
  <li>Would it be more efficient to pack everything into a single axes? Can the paradigm of transforms still be used if I do that? Or is the transform the main culprit here?</li>
  <li>Is there a quick way to do the sqrtm( inv( A ) ) on a list of matrices A? Or is it just as well that I have them in a for loop? </li>
  <li>Should I switch to something like pyqtgraph? I'm not planning on their being any animation beyond pan and zoom. ( maybe in the future I'll want to embed these into a interactive graph )</li>
  </ul>
  
  <hr>
  
  <p>EDITS: </p>
  
  <p>I've been able to increase drawing efficiency by computing the form of the square root inverted matrix by hand. Its a pretty big speed up too. </p>
  
  <p>In the above code: </p>
  
  <pre><code> A = numpy.array( [ ( a, 0, 0 ) ,
                            ( c, d, 0 ) ,
                            ( 0, 0, 1 ) ] , dtype=np.float64)
   EllShape = Affine2D( numpy.array(sqrtm( inv(A) ), dtype=np.float64) )
  </code></pre>
  
  <p>is replaced by </p>
  
  <pre><code> EllShape = Affine2D([\
   ( 1/sqrt(a),         0, 0),\
   ((c/sqrt(a) - c/sqrt(d))/(a - d), 1/sqrt(d), 0),\
   (         0,         0, 1)])
  </code></pre>
  
  <p>I found some interesting timing results too: </p>
  
  <pre><code>  num_to_run = 100000
    all_setup  = ''' import numpy as np ; from scipy.linalg import sqrtm ; from numpy.linalg import inv ; from numpy import sqrt
    a=.1 ; c=43.2 ; d=32.343'''
  
    timeit( \
    'sqrtm(inv(np.array([ ( a, 0, 0 ) , ( c, d, 0 ) , ( 0, 0, 1 ) ])))',\
    setup=all_setup, number=num_to_run)
     &gt;&gt; 22.2588094075 #(Matlab reports 8 seconds for this run) 
  
    timeit(\
    '[ (1/sqrt(a), 0, 0), ((c/sqrt(a) - c/sqrt(d))/(a - d), 1/sqrt(d), 0), (0, 0, 1) ]',\
    setup=all_setup,  number=num_to_run)
    &gt;&gt; 1.10265190941 #(Matlab reports .1 seconds for this run) 
  </code></pre>
  
  <hr>
  
  <p>EDIT 2</p>
  
  <p>Ive gotten the ellipses to be computed and drawn very quickly (in about a second, I didn't profile it) using a PatchCollection and some manual calculations. 
  The only drawback is I can't seem to set the fill of the ellipses to be false</p>
  
  <pre><code> from matplotlib.collections import PatchCollection
   ell_list = []
   for i in range(num_chips):
       axi =  chips[i].axi 
       ax  =  dm.ax_list[axi]
       transData = dm.transData_list[axi]
       chip_feats = chips[i].features
       for feat in chip_feats:
           (x,y,a,c,d) = feat
           EllShape = Affine2D([\
              ( 1/sqrt(a),         0, x),\
              ((c/sqrt(a) - c/sqrt(d))/(a - d), 1/sqrt(d), y),\
              (         0,         0, 1)])
           unitCirc = Circle((0,0),1,transform=EllShape)
           ell_list = [unitCirc] + ell_list
      ellipses = PatchCollection(ell_list)
      ellipses.set_color([1,1,1])
      ellipses.face_color('none') #'none' gives no fill, while None will default to [0,0,1]
      ellipses.set_alpha(.05)
      ellipses.set_transformation(transData)
      ax.add_collection(ellipses)
  </code></pre>