58

I need to rapidly plot jpg frames that result as the output of a tracking algorithm. Companion with the jpg frames are text files containing simple (x,y) data locating the image targets that are being tracked. I would like to use matplotlib to plot the jpg images, then overlay a scatter plot of the (x,y) data which gets read from the text file and stored into a Pythonic list. Below is code that will plot the jpg image, but in all of the scouring I have done of matplotlib, scipy, and PIL manuals and help pages, I cannot find anything that explains how to maintain this plot window and simply overlay a scatter plot of simple markers at various (x,y) locations in the image. Any help is greatly appreciated.

import matplotlib.pyplot as plt;
im = plt.imread(image_name);
implot = plt.imshow(im);
plt.show()
Paul
  • 42,322
  • 15
  • 106
  • 123
ely
  • 74,674
  • 34
  • 147
  • 228

5 Answers5

88

The pyplot.scatter() function was tailor made for this reason:

import matplotlib.pyplot as plt
im = plt.imread(image_name)
implot = plt.imshow(im)

# put a blue dot at (10, 20)
plt.scatter([10], [20])

# put a red dot, size 40, at 2 locations:
plt.scatter(x=[30, 40], y=[50, 60], c='r', s=40)

plt.show()

See the documentation for more info.

lothario
  • 1,788
  • 1
  • 14
  • 8
  • 1
    +1 Yes, this orients the axes to the typical pixel numbering: (0,0) = upper-left – Paul Feb 22 '11 at 02:58
  • @Paul may I ask what is the benefit of *the typical pixel numbering: (0,0) = upper-left* – Sibbs Gambling Sep 21 '13 at 03:46
  • 1
    But when I set the transparent parameter `alpha` in `plt.scatter`, the transparency of the image also changed... – LittleLittleQ Feb 12 '15 at 13:09
  • 1
    Is it possible to do this without imshow? The display window that opens up refuses to close in my system and is blocking the flow when I have to plot a hundred such instances. – Ambareesh Oct 06 '19 at 22:41
13

this should work:

import matplotlib.pyplot as plt
im = plt.imread('test.png')
implot = plt.imshow(im)
plt.plot([100,200,300],[200,150,200],'o')
plt.show()

keep in mind that each pixel in the image is one unit on the x,y axes. The 'o' is a shorthand way of getting the plot function to use circles instead of lines.

Paul
  • 42,322
  • 15
  • 106
  • 123
  • and given that it's a background image, you likely want to manipulate the alpha channel, which you can do this way: im[:, :, -1] = .7 – doug Feb 22 '11 at 02:50
12

I know this has been answered but similarly zorder works as well. Which is great if you want to put something on top of a scatterplot or under it

import matplotlib as plt
im = plt.imread(image_name)
plt.imshow(im,zorder=1)
plt.scatter(x,y,zorder=2)
plt.show()

lower zorder means it is below other things

Ceddrick
  • 158
  • 1
  • 3
0

Here's a way to plot directly over an image without having to invoke imshow().

It renders the plot as a transparent image with an identical shape as the input and then alpha-composits it onto the input image.

import numpy as np
import matplotlib.pyplot as plt
import imageio
from contextlib import contextmanager

@contextmanager
def plot_over(img, extent=None, origin="upper", dpi=100):
    h, w, d = img.shape
    assert d == 3
    if extent is None:
        xmin, xmax, ymin, ymax = -0.5, w + 0.5, -0.5, h + 0.5
    else:
        xmin, xmax, ymin, ymax = extent
    if origin == "upper":
        ymin, ymax = ymax, ymin
    elif origin != "lower":
        raise ValueError("origin must be 'upper' or 'lower'")
    fig = plt.figure(figsize=(w / dpi, h / dpi), dpi=dpi)
    ax = plt.Axes(fig, (0, 0, 1, 1))
    ax.set_axis_off()
    ax.set_xlim(xmin, xmax)
    ax.set_ylim(ymin, ymax)
    fig.add_axes(ax)
    fig.set_facecolor((0, 0, 0, 0))
    yield ax
    fig.canvas.draw()
    plot = np.frombuffer(fig.canvas.buffer_rgba(), dtype=np.uint8).reshape(h, w, 4)
    plt.close(fig)
    rgb = plot[..., :3]
    alpha = plot[..., 3, None]
    img[...] = ((255 - alpha) * img.astype(np.uint16) + alpha * rgb.astype(np.uint16)) // 255

img = imageio.imread("image.jpg")
img_with_plot = img.copy()
with plot_over(img_with_plot) as ax:
    ax.scatter(...)
    # etc
imageio.imwrite("result.png", img_with_plot)
Martin Valgur
  • 5,793
  • 1
  • 33
  • 45
0

Another way to do this is to use opencv2 to draw the scatter circles directly on the image.

The original way of plotting the scatter plot would be:

import matplotlib.pyplot as plt

# plot points over image
plt.imshow(image)
plt.scatter(u, v, c=z, cmap='rainbow_r', alpha=0.5, s=2);

In this case 'z' is the color list that could represent something like depth or a category for each scatter point. The issue with this is that it does not actually place the scatter points on the image, and you will not be able to directly work with the overlaid points on the image using this approach.

Another approach is to use opencv2 to draw circles on the image like so:

import cv2
from matplotlib import cm
import matplotlib.pyplot as plt

# get color for corresponding color list value
rainbow_r = cm.get_cmap('rainbow_r', 100)
get_color = lambda z : [255*val for val in rainbow_r(int(z.round()))[:3]]

# draw circles on image
proj_image = image.copy()
for i in range(len(u)):
    cv2.circle(proj_image, (int(u[i]), int(v[i])), 1, get_color(z[i]), -1);

# OPTIONAL: make the circles transparent (larger alpha --> more transparent)
alpha = 0.5
proj_image = cv2.addWeighted(proj_image, 1 - alpha, image, alpha, 0)