How to compare coordinates in AxesImage with coordinates in a saved image file?

I use matplotlib to display a matrix of numbers as an image, attach labels along the axes, and save the graph to a PNG file. To create an HTML image map, I need to know the coordinates of the pixels in the PNG file for the image area displayed by imshow.

I found an example on how to do this with a normal plot, but when I try to do the same with imshow, the display is not correct. Here is my code that saves the image and tries to print the pixel coordinates of the center of each square diagonally:

import numpy as np import matplotlib.pyplot as plt fig = plt.figure() ax = fig.add_axes([0.1, 0.1, 0.8, 0.8]) axim = ax.imshow(np.random.random((27,27)), interpolation='nearest') for x, y in axim.get_transform().transform(zip(range(28), range(28))): print int(x), int(fig.get_figheight() * fig.get_dpi() - y) plt.savefig('foo.png', dpi=fig.get_dpi()) 

Here is the result of foo.png, shown as a screenshot to enable the rulers:

Screenshot of foo.png with rulers

The script output starts and ends as follows:

 73 55 92 69 111 83 130 97 149 112 … 509 382 528 396 547 410 566 424 585 439 

As you can see, the y-coordinates are correct, but the x-coordinates are stretched: they range from 73 to 585 instead of the expected 135-506, and they are located at a distance of 19 pixels oc instead of the expected 14. What am I doing wrong?

+6
python matplotlib
source share
2 answers

This is one of the most confusing parts for getting exact pixel values ​​from matplotlib. Matplotlib separates the renderer, which processes the exact pixel values ​​from the canvas onto which the shape and axes are drawn.

In principle, the visualization tool that exists when the drawing is initially created (but not yet displayed) does not necessarily coincide with the visualizer that is used to display the shape or save it to a file.

What you do is correct, but it uses the original renderer, not the one used to save the shape.

To illustrate this, here is a slightly simplified version of your code:

 import numpy as np import matplotlib.pyplot as plt fig = plt.figure() ax = fig.add_subplot(111) im = ax.imshow(np.random.random((27,27)), interpolation='nearest') for i in range(28): x, y = ax.transData.transform_point([i,i]) print '%i, %i' % (x, fig.bbox.height - y) fig.savefig('foo.png', dpi=fig.dpi) 

This gives similar results to what you have above: (the differences are related to the different rendering mechanisms between your machine and mine)

 89, 55 107, 69 125, 83 ... 548, 410 566, 424 585, 439 

However, if we do the same, but instead draw a shape before displaying the coordinates, we get the right answer!

 import numpy as np import matplotlib.pyplot as plt fig = plt.figure() ax = fig.add_subplot(111) im = ax.imshow(np.random.random((27,27)), interpolation='nearest') fig.canvas.draw() for i in range(28): x, y = ax.transData.transform_point([i,i]) print '%i, %i' % (x, fig.bbox.height - y) fig.savefig('foo.png', dpi=fig.dpi) 

This yields: (Keep in mind that the edge of the figure is located at <-0.5, -0.5> in the data coordinates, and not <0, 0> . (I.e. the coordinates for the graphic image are in pixel orientation). That is why <0, 0> gives 143, 55 , not 135, 48 )

 143, 55 157, 69 171, 83 ... 498, 410 512, 424 527, 439 

Of course, drawing a figure just to repeat it while saving it is redundant and computationally expensive.

To avoid drawing twice, you can connect the callback function to the draw event and display your HTML image map inside this function. As a short example:

 import numpy as np import matplotlib.pyplot as plt def print_pixel_coords(event): fig = event.canvas.figure ax = fig.axes[0] # I'm assuming there only one subplot here... for i in range(28): x, y = ax.transData.transform_point([i,i]) print '%i, %i' % (x, fig.bbox.height - y) fig = plt.figure() ax = fig.add_subplot(111) im = ax.imshow(np.random.random((27,27)), interpolation='nearest') fig.canvas.mpl_connect('draw_event', print_pixel_coords) fig.savefig('foo.png', dpi=fig.dpi) 

This gives the correct result, but only draws the picture once when it is saved:

 143, 55 157, 69 171, 83 ... 498, 410 512, 424 527, 439 

Another advantage is that you can use any tag in the fig.savefig call without having to manually set the fig tag manually. Therefore, when using the callback function, you can simply execute fig.savefig('foo.png') , (or fig.savefig('foo.png', dpi=whatever) ), and you will get the result corresponding to the saved .png file. (The default value when saving a digit is 100, while the default value for an object with numbers is 80, so you had to specify that the dpi would be the same as fig.dpi )

Hope to be at least somewhat clear!

+11
source share

I don’t know if I understand correctly what you are asking for, but if I do, you need the pixel coordinates for the lower left corner of the graph (an attempt that in graph units it is (-0.5, -0.5)), and from there You can calculate the pixel coordinates for each field, just using the side in pixels.

To get them, do:

 x = ax.get_xaxis().get_clip_box().x0 y = ax.get_yaxis().get_clip_box().y1 

(I got the values ​​128, 432 for this image)

+1
source share

All Articles