9

I use Matplotlib to generate PNG files of scatterplots. Now, for each scatterplot, in addition to a PNG file, I would also like to generate a list of pixel coordinates of the various points in the scatterplot.

The code I use to generate the PNG files for the scatterplots is basically like this:

from matplotlib.figure import Figure
from matplotlib.pyplot import setp
from matplotlib.backends.backend_agg import FigureCanvasAgg

...

fig = Figure(figsize=(3, 3), dpi=100)
ax = fig.gca()
for (x, y), m, c in zip(points, markers, colors):
    ax.scatter(x, y, marker=m, c=c, s=SIZE, vmin=VMIN, vmax=VMAX)

# several assorted tweaks like ax.spines['top'].set_color('none'), etc.

setp(fig, 'facecolor', 'none')

# FigureCanvasAgg(fig).print_png(FILEPATH)

...(where the variables in UPPERCASE stand for settable parameters).

How can I also produce a list of (px, py) pairs of the pixel coordinates in the resulting PNG corresponding to the points in points?

[EDIT: removed some nonsense about imshow.]

[EDIT:

OK, here's what I finally came up with, based on Joe Kington's suggestions.

# continued from above...

cnvs = FigureCanvasAgg(fig)
fig.set_canvas(cnvs)
_, ht = cnvs.get_width_height()
pcoords = [(int(round(t[0])), int(round(ht - t[1]))) for t in
           ax.transData.transform(points)]
fig.savefig(FILEPATH, dpi=fig.dpi)

The resulting pixel coords (in pcoords) are pretty close to the correct values. In fact, the y coords are exactly right. The x coords are 1 or 2 pixels off, which is good enough for my purposes.

]

kjo
  • 33,683
  • 52
  • 148
  • 265

2 Answers2

21

Doing this is fairly simple, but to understand what's going on, you'll need to read up a bit on matplotlib's transforms. The transformations tutorial is a good place to start.

At any rate, here's an example:

import numpy as np
import matplotlib.pyplot as plt

fig, ax = plt.subplots()
points, = ax.plot(range(10), 'ro')
ax.axis([-1, 10, -1, 10])

# Get the x and y data and transform it into pixel coordinates
x, y = points.get_data()
xy_pixels = ax.transData.transform(np.vstack([x,y]).T)
xpix, ypix = xy_pixels.T

# In matplotlib, 0,0 is the lower left corner, whereas it's usually the upper 
# left for most image software, so we'll flip the y-coords...
width, height = fig.canvas.get_width_height()
ypix = height - ypix

print 'Coordinates of the points in pixel coordinates...'
for xp, yp in zip(xpix, ypix):
    print '{x:0.2f}\t{y:0.2f}'.format(x=xp, y=yp)

# We have to be sure to save the figure with it's current DPI
# (savfig overrides the DPI of the figure, by default)
fig.savefig('test.png', dpi=fig.dpi)

This yields:

Coordinates of the points in pixel coordinates...
125.09  397.09
170.18  362.18
215.27  327.27
260.36  292.36
305.45  257.45
350.55  222.55
395.64  187.64
440.73  152.73
485.82  117.82
530.91  82.91

enter image description here

ElRudi
  • 2,122
  • 2
  • 18
  • 33
Joe Kington
  • 275,208
  • 71
  • 604
  • 463
  • Thanks! I take it that what I want to do can't be done if one uses `scatter` instead of `plot`? (I *have* attempted to read on Matplotlib, I even bought a book on it, but I find it absolutely incomprehensible... If it were up to me, I'd use Mathematica or even R for this, but I'm working with legacy code... This, BTW, is why I hesitate to replace `scatter` with `plot`.) – kjo Dec 01 '12 at 20:25
  • 1
    It will work fine for `scatter`, just pass in your `x` and `y` coordinates directly instead of calling `get_data`. `scatter` and `plot` do entirely different things, so they're not interchangable. I wasn't paying enough attention to your question, and used `plot` in my example. `scatter` colors and scales points by a 3rd and 4th variable. Plot just plots points and lines. The difference in this case is that `scatter` returns a collection, which behaves a bit differently than the line artist that `plot` returns, so there's no `get_data` method for the artist that `scatter` returns. – Joe Kington Dec 01 '12 at 20:33
  • 2
    And just for whatever it's worth, I find Mathematica completely incomprehensible :) Then again, I come from a Matlab and Fortran background, originally, and both are truly awful as general purpose programming languages. Matplotlib deliberately shares a lot of Matlab's warts, which certainly makes it a bit odd at times, compared to most python libraries. – Joe Kington Dec 01 '12 at 20:37
  • Thanks, that worked! The y-coords are spot on; the x-coords, strangely enough, are 1-2 pixels off (the deviation gets worse towards the edges). This is close enough for my purposes at the moment, though. – kjo Dec 01 '12 at 21:13
  • Huh... That's odd.. Could it be a center-of-the-point vs edge-of-the-point issue? (The coordinates should reflect the centers of the points by default, though...) If it's close enough, it's close enough, but it's quite possible I'm missing something in my example. Good luck, at any rate! – Joe Kington Dec 01 '12 at 21:36
  • Hi Joe, I have a question. You said that 0,0 for most image software is usually the upper right corner. Isn't the origin usually the upper left corner? – Chan mankong Dec 18 '18 at 07:24
1

Try annotation box : http://matplotlib.org/examples/pylab_examples/demo_annotation_box.html

import matplotlib.pyplot as plt
from matplotlib.offsetbox import TextArea, DrawingArea, OffsetImage, \
     AnnotationBbox

for (x, y), m, c in zip(points, markers, colors):
    ax.scatter(x, y, marker=m, c=c, s=SIZE, vmin=VMIN, vmax=VMAX)

    for px, py in zip(x,y):
        offsetbox = TextArea( " %s, %s" (px, py ) , minimumdescent=False)
        ab = AnnotationBbox(offsetbox,(px, py ),
                        xybox=(-20, 40),
                        xycoords='data',
                        boxcoords="offset points",
                        arrowprops=dict(arrowstyle="->"))
        ax.add_artist(ab)

I don't have matplotlib installed on my current computer, so my code might not work.

lucasg
  • 10,734
  • 4
  • 35
  • 57
  • 2
    sorry, I misread the question. I thought the OP wanted to add the coordinates next to the scatter dots. – lucasg Dec 01 '12 at 20:45