使用 matplotlib 的亚像素精度散点图?

Sub-pixel accuracy scatter plots with matplotlib?

注:我是边写边想办法解决这个问题的。我的回答如下。

有没有一种简单的方法可以使用 matplotlib 获得圆的亚像素抗锯齿位置?我能够创建以下 .gif,但圆圈的运动按整数像素步进的事实确实困扰着我。

我当然可以渲染大图(plt.savefig("image.png",dpi=1000))并缩小,但增加的复杂性是一种痛苦(这是给学生的例子,所以导入额外的工具会惹恼学生并安装额外的工具会惹恼校园 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)

必须优化分散函数才能快速放置整数。如果您手动添加 Circle 对象,则会为您处理 sub-pixel 抗锯齿。它慢了很多。示例:

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)

结果:

问题出在 Agg 后端的散点标记标记上。关于它还有this issue,目前还没有解决。

但是,使用 Cairo 后端,问题不存在。

import matplotlib
matplotlib.use("TkCairo") # or Qt4Cairo, Qt5Cairo

但是,保存动画时出现问题。这已在 this PR 中修复。因此,一个选项是将 backend_cairo.py 替换为固定版本并使用 Cairo 后端。

下面是一个使用animtion模块的例子,这样可以在屏幕上实时观察动画并轻松保存:

import numpy as np
import matplotlib
matplotlib.use("TkCairo")
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation

x, y = np.meshgrid(np.linspace(-1,1,31),np.linspace(-1,1,31))

fig, ax = plt.subplots(figsize=(2.8,2.8))
fig.subplots_adjust(.02,.02,.98,.9)
ax.set_title("scatter")

### Scatter
sc = ax.scatter(x,y,s=2)

ax.axis("off")
ax.set_xlim(-1.2,1.2)
ax.set_ylim(-1.2,1.2)
ax.set_aspect('equal')

def generator():
    X=np.copy(x); Y=np.copy(y)
    for i in range(100):
        X += 0.0015*np.cos(x+3*y)
        Y += 0.0015*np.sin(6*x-4*y)
        yield (X,Y)

def animate(i): 
    x,y = i
    # update scatter
    sc.set_offsets(np.c_[x.flatten(),y.flatten()])

ani = FuncAnimation(fig, animate, frames=generator, interval=50, repeat=False)
ani.save("skewedgrid_scatter.gif", writer="imagemagick")
plt.show()

您可能对 https://github.com/anntzer/mplcairo 感兴趣,它是 Matplotlib 的新 cairo-based 后端,我最初完全 编写了它,因为我对这个问题不满意。它不会出现上述问题,同时保持合理的性能。