How to map coordinates in AxesImage to coordinates in saved image file?

2024/9/8 10:43:58

I use matplotlib to display a matrix of numbers as an image, attach labels along the axes, and save the plot to a PNG file. For the purpose of creating an HTML image map, I need to know the pixel coordinates in the PNG file for a region in the image being displayed by imshow.

I have found an example of how to do this with a regular plot, but when I try to do the same with imshow, the mapping is not correct. Here is my code, which saves an image and attempts to print the pixel coordinates of the center of each square on the diagonal:

import numpy as np
import matplotlib.pyplot as pltfig = 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 resulting foo.png, shown as a screenshot in order to include the rulers:

Screenshot of foo.png with rulers

The output of the script 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 see, the y-coordinates are correct, but the x-coordinates are stretched: they range from 73 to 585 instead of the expected 135 to 506, and they are spaced 19 pixels o.c. instead of the expected 14. What am I doing wrong?

Answer

This is one of the more confusing parts of trying to get exact pixel values from matplotlib. Matplotlib separates the renderer that handles exact pixel values from the canvas that the figure and axes are drawn on.

Basically, the renderer that exists when the figure is initially created (but not yet displayed) is not necessarily the same as the renderer that is used when displaying the figure or saving it to a file.

What you're doing is correct, but it's using the initial renderer, not the one that's used when the figure is saved.

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

import numpy as np
import matplotlib.pyplot as pltfig = 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 yields similar results to what you have above: (the differences are due to different rendering backends between your machine and mine)

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

However, if we do the exact same thing, but instead draw the figure before displaying the coordinates, we get the correct answer!

import numpy as np
import matplotlib.pyplot as pltfig = 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 at <-0.5, -0.5> in data coordinates, not <0, 0>. (i.e. the coordinates for the plotted image are pixel-centered) This is why <0, 0> yields 143, 55, and not 135, 48)

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

Of course, drawing the figure just to draw it again when it's saved is redundant and computationally expensive.

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

import numpy as np
import matplotlib.pyplot as pltdef print_pixel_coords(event):fig = event.canvas.figureax = fig.axes[0] # I'm assuming there's 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)

Which yields the correct output, while only drawing the figure 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 dpi in the call to fig.savefig without having to manually set the fig object's dpi beforehand. Therefore, when using the callback function, you can just do fig.savefig('foo.png'), (or fig.savefig('foo.png', dpi=whatever)) and you'll get output that matches the saved .png file. (The default dpi when saving a figure is 100, while the default dpi for a figure object is 80, which is why you had to specify the dpi to be the same as fig.dpi in the first place)

Hopefully that's at least somewhat clear!

https://en.xdnf.cn/q/73193.html

Related Q&A

Why am I getting IOError: [Errno 13] Permission denied?

I am creating Log file for the code but I am getting the following error :[Tue Jun 11 17:22:59 2013] [error] [client 127.0.0.1] import mainLCF [Tue Jun 11 17:22:59 2013] [error] [client 127.0.0.1] …

Difference between bytearray and list

What is the difference between bytearray and for example, a list or tuple?As the name suggests, bytearray must be an array that carries byte objects. In python, it seems that bytes and str are treate…

python NameError: name anything is not defined (but it is!)

Note: Solved. It turned out that I was importing a previous version of the same module.It is easy to find similar topics on StackOverflow, where someone ran into a NameError. But most of the questions …

Python File Creation Date Rename - Request for Critique

Scenario: When I photograph an object, I take multiple images, from several angles. Multiplied by the number of objects I "shoot", I can generate a large number of images. Problem: Camera gen…

Secure authentication system in python?

I am making a web application in python and I would like to have a secure login system.I have done login systems many times before by having the user login and then a random string is saved in a cookie…

Finding All Positions Of A Character In A String

Im trying to find all the index numbers of a character in a python string using a very basic skill set. For example if I have the string "Apples are totally awesome" and I want to find the pl…

DataError: (1406, Data too long for column name at row 1)

Ive read nearly all other posts with the same error and cant seem to find a proper solution. In my models.py file I have this:class LetsSayCups(models.Model):name = models.CharField(max_length=65535)de…

Continued Fractions Python [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.Want to improve this question? Update the question so it focuses on one problem only by editing this post.Closed 7…

Spark: equivelant of zipwithindex in dataframe

Assuming I am having the following dataframe:dummy_data = [(a,1),(b,25),(c,3),(d,8),(e,1)] df = sc.parallelize(dummy_data).toDF([letter,number])And i want to create the following dataframe: [(a,0),(b,2…

How to find list comprehension in python code

I want to find a list comprehension in python source code, for that I tried to use Pygments, but it didnt find the way to do that. To be more specific, I want to do a function that recognize all the po…