Matplotlib:用不同颜色绘制大量断开的线段

Posted

技术标签:

【中文标题】Matplotlib:用不同颜色绘制大量断开的线段【英文标题】:Matplotlib: Plotting numerous disconnected line segments with different colors 【发布时间】:2014-02-16 14:56:06 【问题描述】:

我有一组这样的数据记录:

(s1, t1), (u1, v1), color1
(s2, t2), (u2, v2), color2
.
.
.
(sN, tN), (uN, vN), colorN

在任何记录中,前两个值是线段的端点,第三个值是该线段的颜色。更具体地说,(sn, tn) 是第一个端点的 x-y 坐标,(un, vn) 是第二个端点的 x-y 坐标。此外,color 是具有 alpha 值的 rgb。

一般来说,任意两条线段不相连(意味着它们的端点不一定重合)。

如何使用 ma​​tplotlib 和单个 plot 调用(或尽可能少)绘制此数据,因为可能有数千条记录。

尝试

在一个大列表中准备数据并针对它调用plot 太慢了。例如,以下代码无法在合理的时间内完成:

import numpy as np
import matplotlib.pyplot as plt

data = []
for _ in xrange(60000):
    data.append((np.random.rand(), np.random.rand()))
    data.append((np.random.rand(), np.random.rand()))
    data.append('r')

print 'now plotting...' # from now on, takes too long
plt.plot(*data)
print 'done'
#plt.show()

我能够通过使用 None 插入技巧来加速绘图渲染,如下所示:

import numpy as np
import matplotlib.pyplot as plt
from timeit import timeit

N = 60000
_s = np.random.rand(N)
_t = np.random.rand(N)
_u = np.random.rand(N)
_v = np.random.rand(N)
x = []
y = []
for s, t, u, v in zip(_s, _t, _u, _v):
    x.append(s)
    x.append(u)
    x.append(None)
    y.append(t)
    y.append(v)
    y.append(None)
print timeit(lambda:plt.plot(x, y), number=1)

这会在一秒钟内在我的机器上执行。我仍然需要弄清楚如何嵌入颜色值(带 alpha 通道的 RGB)。

【问题讨论】:

确实使用nan 而不是None 有什么不同,它会产生相同的情节,但我可以使用numpy.tilenumpy.repeat 来构建xy 而不是附加到列表。你也知道你是否可以用这个嵌入颜色(不像LineColelction 方法)? 【参考方案1】:

函数plot允许在一次调用中绘制多条线,如果您的数据只是在一个列表中,只需在传递给plot时将其解包:

In [315]: data=[(1, 1), (2, 3), 'r', #assuming points are (1,2) (1,3) actually and,
                                     #here they are in form of (x1, x2), (y1, y2)
     ...: (2, 2), (4, 5), 'g',
     ...: (5, 5), (6, 7), 'b',]

In [316]: plot(*data)
Out[316]: 
[<matplotlib.lines.Line2D at 0x8752870>,
 <matplotlib.lines.Line2D at 0x8752a30>,
 <matplotlib.lines.Line2D at 0x8752db0>]

【讨论】:

@user698585 你试过了吗?看我粘贴的图片。我假设你的(s1, t1), (u1, v1)(x1, x2), (y1, y2) 的形式,否则你应该先将(x1, y1), (x2, y2) 压缩到(x1, x2), (y1, y2) 抱歉问题不够清楚,请查看更新。无论如何,我明白你的意思。您可能希望更新您的答案,使其与问题中具体说明的内容相兼容。 @user698585 这就是你所要求的(通过单个 plot 调用)...没有人说在单个图形上绘制 60,000 条线是有效的,或者通过单个调用或在 for 循环中调用。 @user698585,你的 Update 2 在图中画了一条线,因此只生成了一个 matplotlib.lines.Line2D 对象,我猜这就是它加速的原因。但是我不知道是否有办法用多种颜色绘制一条线......对不起 @seralouk 见:medium.com/understand-the-python/…【参考方案2】:

好的,我最终在将 PIL 图像上的线条转换为 numpy 数组之前对其进行了光栅化:

from PIL import Image
from PIL import ImageDraw
import random as rnd
import numpy as np
import matplotlib.pyplot as plt

N = 60000
s = (500, 500)

im = Image.new('RGBA', s, (255,255,255,255))
draw = ImageDraw.Draw(im)

for i in range(N):
    x1 = rnd.random() * s[0]
    y1 = rnd.random() * s[1]
    x2 = rnd.random() * s[0]
    y2 = rnd.random() * s[1]
    alpha = rnd.random()
    color  = (int(rnd.random() * 256), int(rnd.random() * 256), int(rnd.random() * 256), int(alpha * 256)) 
    draw.line(((x1,y1),(x2,y2)), fill=color, width=1)

plt.imshow(np.asarray(im),
           origin='lower')
plt.show()

这是迄今为止最快的解决方案,它完全符合我的实时需求。不过需要注意的一点是,线条是在没有抗锯齿的情况下绘制的。

【讨论】:

出于懒惰,我建议rgba = (np.random.randint(256) for _ in range(4))【参考方案3】:

使用LineCollection:

import numpy as np
import pylab as pl
from matplotlib import collections  as mc

lines = [[(0, 1), (1, 1)], [(2, 3), (3, 3)], [(1, 2), (1, 3)]]
c = np.array([(1, 0, 0, 1), (0, 1, 0, 1), (0, 0, 1, 1)])

lc = mc.LineCollection(lines, colors=c, linewidths=2)
fig, ax = pl.subplots()
ax.add_collection(lc)
ax.autoscale()
ax.margins(0.1)

这是输出:

【讨论】:

我试过了,60000行还是不够快吧? @zhangxaochen:绝对!事实上,我想为这个问题添加一个类似的更新,但决定不只是出于懒惰。 难以置信的反应!太棒了! 很好的答案,这是我使用 matplotlib 找到的唯一可以有效处理大量行的解决方案。对于约 2000 行,它是 60 毫秒,而不是其他技术的 1.6 秒。 @zhangxaochen 也许硬件变快了,但现在是。大约 2 秒内 62k 行。【参考方案4】:

我尝试了 Python 3 上可用的一些 2D 渲染引擎,同时为面向图像的深度学习和 GAN 中的输出阶段寻找快速解决方案。

使用以下基准:时间将 99 行渲染成 256x256 的离屏图像(或任何更有效的图像),有无抗锯齿。

结果,按我的旧 x301 笔记本电脑的效率排序:

PyGtk2:~2500 FPS,(Python 2,GTK 2,不确定如何获得 AA) PyQt5:~1200 FPS,~350 with Antialias PyQt4:~1100 FPS,~380 与 AA 开罗:约 750 FPS,使用 AA 时约 250(仅使用“FAST”AA 稍快) PIL:~600 FPS

基线是一个循环,大约需要 0.1 毫秒(10,000 FPS)检索随机数并调用原语。

PyGtk2 的基本代码:

from gtk import gdk
import random

WIDTH = 256
def r255(): return int(256.0*random.random())

cmap = gdk.Colormap(gdk.visual_get_best_with_depth(24), True)
black = cmap.alloc_color('black')
white = cmap.alloc_color('white')
pixmap = gdk.Pixmap(None, WIDTH, WIDTH, 24)
pixmap.set_colormap(cmap)
gc = pixmap.new_gc(black, line_width=2)
pixmap.draw_rectangle(gc, True, -1, -1, WIDTH+2, WIDTH+2);
gc.set_foreground(white)
for n in range(99):
    pixmap.draw_line(gc, r255(), r255(), r255(), r255())

gdk.Pixbuf(gdk.COLORSPACE_RGB, False, 8, WIDTH, WIDTH
    ).get_from_drawable(pixmap, cmap, 0,0, 0,0, WIDTH, WIDTH
        ).save('Gdk2-lines.png','png')

这里是 PyQt5:

from PyQt5.QtCore import Qt
from PyQt5.QtGui import *
import random

WIDTH = 256.0
def r255(): return WIDTH*random.random()

image = QImage(WIDTH, WIDTH, QImage.Format_RGB16)
painter = QPainter()
image.fill(Qt.black)
painter.begin(image)
painter.setPen(QPen(Qt.white, 2))
#painter.setRenderHint(QPainter.Antialiasing)
for n in range(99):
    painter.drawLine(WIDTH*r0to1(),WIDTH*r0to1(),WIDTH*r0to1(),WIDTH*r0to1())    
painter.end()
image.save('Qt5-lines.png', 'png')

为了完整起见,这里是 Python3-Cairo:

import cairo
from random import random as r0to1

WIDTH, HEIGHT = 256, 256

surface = cairo.ImageSurface(cairo.FORMAT_A8, WIDTH, HEIGHT)
ctx = cairo.Context(surface)
ctx.scale(WIDTH, HEIGHT)  # Normalizing the canvas
ctx.set_line_width(0.01)
ctx.set_source_rgb(1.0, 1.0, 1.0)
ctx.set_antialias(cairo.ANTIALIAS_NONE)
#ctx.set_antialias(cairo.ANTIALIAS_FAST)

ctx.set_operator(cairo.OPERATOR_CLEAR)
ctx.paint()
ctx.set_operator(cairo.OPERATOR_SOURCE)
for n in range(99):
    ctx.move_to(r0to1(), r0to1())
    ctx.line_to(r0to1(), r0to1())
    ctx.stroke()

surface.write_to_png('Cairo-lines.png')

【讨论】:

以上是关于Matplotlib:用不同颜色绘制大量断开的线段的主要内容,如果未能解决你的问题,请参考以下文章

如何使用颜色图绘制 matplotlib 线图?

python数据可视化--matplotlib绘制散点图

学会Python-Matplotlib可视化,快速完成数据分析——自定义颜色绘制精美统计图

在 Matplotlib 中重置颜色循环

在 Matplotlib 中重置颜色循环

绘制大量不同颜色的正方形