Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Sub-pixel accuracy scatter plots with matplotlib?

Note: I figured out a solution to this question while writing it. My answer is below.

Is there an easy way to get sub-pixel antialiased placement of circles using matplotlib? I was able to create the following .gif but the fact that the motion of the circles steps by integer pixels is really bugging me.

enter image description here

I can of course render a large image (plt.savefig("image.png",dpi=1000)) and scale down, but the added complexity is a pain (this is an example for students, so importing extra tools will annoy the students and installing extra tools will annoy campus IT!)

import matplotlib.pyplot as plt
from math import sin,cos
x=[]
y=[]
#Create 41**2 particles in the unit square
for i in range(41):
    for j in range(41):
        x.append(i*0.05-1.0)
        y.append(j*0.05-1.0)
#5 second video at 30 fps = 150 frames
for n in range(150):
    plt.close('all')
    fig, axes = plt.subplots(figsize=(5,5))
    #some cool motion
    for i in range(len(x)):
        x[i]+=0.001*cos(x[i]+3*y[i])
        y[i]+=0.001*sin(6*x[i]-4*y[i])
    #create the scatter plot
    axes.scatter(x,y,s=3,antialiased=True)
    axes.set_xlim(-1.4,1.4)
    axes.set_ylim(-1.4,1.4)
    axes.set_aspect('equal')
    plt.savefig("out/fig%03d.png"%n,dpi=80)

2 Answers

The scatter function must be optimized to just do quick integer placement. If you manually add Circle objects, the sub-pixel antialiasing is handled for you. It's quite a bit slower. Example:

import matplotlib.pyplot as plt
from matplotlib.collections import PatchCollection
from math import sin,cos
x=[]
y=[]
#Create 41**2 particles in the unit square
for i in range(41):
    for j in range(41):
        x.append(i*0.05-1.0)
        y.append(j*0.05-1.0)
#5 second video at 30 fps = 150 frames
for n in range(150):
    plt.close('all')
    fig, axes = plt.subplots(figsize=(5,5))
    #adding the Circles to the plot using a PatchCollection is much faster.
    patches=[]
    #some cool motion
    for i in range(len(x)):
        x[i]+=0.001*cos(x[i]+3*y[i])
        y[i]+=0.001*sin(6*x[i]-4*y[i])
        patches.append(plt.Circle((x[i],y[i]),0.015))
    axes.add_collection(PatchCollection(patches, alpha=0.95))
    axes.set_xlim(-1.4,1.4)
    axes.set_ylim(-1.4,1.4)
    axes.set_aspect('equal')
    plt.savefig("out/fig%03d.png"%n,dpi=80)

The result:

enter image description here

You may be interested in https://github.com/anntzer/mplcairo, a new cairo-based backend for Matplotlib which I originally wrote exactly because I was unhappy with this issue. It does not display the aforementioned problem, while maintaining reasonable performance.

like image 24
antony Avatar answered Apr 15 '26 02:04

antony