2012-11-29 22 views
5

Python。 matplotlib如何高效地将大量线段着色为独立的梯度?
已经阅读thisthis等东西;他们都不是我们的答案!Matplotlib:如何将大量线段着色为独立梯度,高效

我们有许多单独的行希望绘制每个渐变颜色。

上面第一个链接中提到的解决方案,如果您有多个字符串的行不起作用。换句话说,改变颜色周期会影响情节中的所有内容,而不是唯一感兴趣的方面。 这完全不符合我们的兴趣。

第二个连接到matplotlib网站的链接使用了将每一行分割成许多的链接。这不是一个好方法,因为对于大量的行数,比如10000甚至更多;即使你每行只选择10段,结果也太大了!即使这样,所产生的线条也不会平滑上色!如果将分割数作为线段的函数以获得更好的渐变效果,结果将会非常巨大!很难显示,难以妥善保存为文件。

+2

我怀疑你已经打matplotlib'的'的限制,但千万不要以为那是你的主要问题。如果你有10K行,即使你把它们绘制成1px宽,将它们排列起来也需要一个巨大的显示/打印出来,以便能够独立地看到它们。即使你能找到一种方法来制作这个情节,你也无法以合理的方式来看待它。有什么方法可以粗化数据吗? – tacaswell

+0

您是否尝试添加单行收集而不是10000个单独的行集合?它仍然很慢,但速度更快。平移和缩放时它也更具响应性。 –

+0

@JoeKington:好戏。 – Developer

回答

6

一个(次要)加速将会添加一个单独的行集合,而不是10000个单独的行集合。

只要所有线共享相同的颜色映射表,您可以将它们分组为单个线条集合,并且每个线条仍可以有独立的渐变。

Matplotlib对于这类事情仍然很慢。它针对质量输出进行了优化,而不是快速绘制时间。不过,你可以加速一些(〜3x)。 (?)

所以,我怎么想你可能是一个例子做它现在:

import numpy as np 
import matplotlib.pyplot as plt 
from matplotlib.collections import LineCollection 
# Make random number generation consistent between runs 
np.random.seed(5) 

def main(): 
    numlines, numpoints = 2, 3 
    lines = np.random.random((numlines, numpoints, 2)) 

    fig, ax = plt.subplots() 
    for line in lines: 
     # Add "num" additional segments to the line 
     segments, color_scalar = interp(line, num=20) 
     coll = LineCollection(segments) 
     coll.set_array(color_scalar) 
     ax.add_collection(coll) 
    plt.show() 

def interp(data, num=20): 
    """Add "num" additional points to "data" at evenly spaced intervals and 
    separate into individual segments.""" 
    x, y = data.T 
    dist = np.hypot(np.diff(x - x.min()), np.diff(y - y.min())).cumsum() 
    t = np.r_[0, dist]/dist.max() 

    ti = np.linspace(0, 1, num, endpoint=True) 
    xi = np.interp(ti, t, x) 
    yi = np.interp(ti, t, y) 

    # Insert the original vertices 
    indices = np.searchsorted(ti, t) 
    xi = np.insert(xi, indices, x) 
    yi = np.insert(yi, indices, y) 

    return reshuffle(xi, yi), ti 

def reshuffle(x, y): 
    """Reshape the line represented by "x" and "y" into an array of individual 
    segments.""" 
    points = np.vstack([x, y]).T.reshape(-1,1,2) 
    points = np.concatenate([points[:-1], points[1:]], axis=1) 
    return points 

if __name__ == '__main__': 
    main() 

相反,我会建议做的东西沿着这些路线(唯一的区别是在main功能):

import numpy as np 
import matplotlib.pyplot as plt 
from matplotlib.collections import LineCollection 
# Make random number generation consistent between runs 
np.random.seed(5) 

def main(): 
    numlines, numpoints = 2, 3 
    points = np.random.random((numlines, numpoints, 2)) 

    # Add "num" additional segments to each line 
    segments, color_scalar = zip(*[interp(item, num=20) for item in points]) 

    segments = np.vstack(segments) 
    color_scalar = np.hstack(color_scalar) 

    fig, ax = plt.subplots() 
    coll = LineCollection(segments) 
    coll.set_array(color_scalar) 
    ax.add_collection(coll) 

    plt.show() 

def interp(data, num=20): 
    """Add "num" additional points to "data" at evenly spaced intervals and 
    separate into individual segments.""" 
    x, y = data.T 
    dist = np.hypot(np.diff(x - x.min()), np.diff(y - y.min())).cumsum() 
    t = np.r_[0, dist]/dist.max() 

    ti = np.linspace(0, 1, num, endpoint=True) 
    xi = np.interp(ti, t, x) 
    yi = np.interp(ti, t, y) 

    # Insert the original vertices 
    indices = np.searchsorted(ti, t) 
    xi = np.insert(xi, indices, x) 
    yi = np.insert(yi, indices, y) 

    return reshuffle(xi, yi), ti 

def reshuffle(x, y): 
    """Reshape the line represented by "x" and "y" into an array of individual 
    segments.""" 
    points = np.vstack([x, y]).T.reshape(-1,1,2) 
    points = np.concatenate([points[:-1], points[1:]], axis=1) 
    return points 

if __name__ == '__main__': 
    main() 

两个版本都产生相同的情节:

enter image description here


如果我们将行数增加到10000,我们将开始看到显着的性能差异。

使用万线,每3个点和额外的20个点的色梯度内插(23段中的每一行),并在看它需要一个数字保存到PNG时间:

Took 10.866694212 sec with a single collection 
Took 28.594727993 sec with multiple collections 

因此,在这种特殊情况下,使用单行集合会使速度略低于3倍。这不是一流的,但总比没有好。

下面是计时代码和输出图,无论它值什么(由于绘图顺序不同,输出数字并不完全相同。如果你需要的Z级控制,你就必须坚持独立的行集合):

enter image description here

import numpy as np 
import matplotlib.pyplot as plt 
from matplotlib.collections import LineCollection 
import time 
# Make random number generation consistent between runs 
np.random.seed(5) 

def main(): 
    numlines, numpoints = 10000, 3 
    lines = np.random.random((numlines, numpoints, 2)) 

    # Overly simplistic timing, but timeit is overkill for this exmaple 
    tic = time.time() 
    single_collection(lines).savefig('/tmp/test_single.png') 
    toc = time.time() 
    print 'Took {} sec with a single collection'.format(toc-tic) 

    tic = time.time() 
    multiple_collections(lines).savefig('/tmp/test_multiple.png') 
    toc = time.time() 
    print 'Took {} sec with multiple collections'.format(toc-tic) 

def single_collection(lines): 
    # Add "num" additional segments to each line 
    segments, color_scalar = zip(*[interp(item, num=20) for item in lines]) 
    segments = np.vstack(segments) 
    color_scalar = np.hstack(color_scalar) 

    fig, ax = plt.subplots() 
    coll = LineCollection(segments) 
    coll.set_array(color_scalar) 
    ax.add_collection(coll) 
    return fig 

def multiple_collections(lines): 
    fig, ax = plt.subplots() 
    for line in lines: 
     # Add "num" additional segments to the line 
     segments, color_scalar = interp(line, num=20) 
     coll = LineCollection(segments) 
     coll.set_array(color_scalar) 
     ax.add_collection(coll) 
    return fig 

def interp(data, num=20): 
    """Add "num" additional points to "data" at evenly spaced intervals and 
    separate into individual segments.""" 
    x, y = data.T 
    dist = np.hypot(np.diff(x - x.min()), np.diff(y - y.min())).cumsum() 
    t = np.r_[0, dist]/dist.max() 

    ti = np.linspace(0, 1, num, endpoint=True) 
    xi = np.interp(ti, t, x) 
    yi = np.interp(ti, t, y) 

    # Insert the original vertices 
    indices = np.searchsorted(ti, t) 
    xi = np.insert(xi, indices, x) 
    yi = np.insert(yi, indices, y) 

    return reshuffle(xi, yi), ti 

def reshuffle(x, y): 
    """Reshape the line represented by "x" and "y" into an array of individual 
    segments.""" 
    points = np.vstack([x, y]).T.reshape(-1,1,2) 
    points = np.concatenate([points[:-1], points[1:]], axis=1) 
    return points 

if __name__ == '__main__': 
    main() 
+0

非常感谢。这确实是一个很好的窍门。我们接受您的完整答案。现在我们几乎可以肯定,这是基于细分概念我们可以做的最多的。我们希望在这里提到,在我们的应用程序中,一个巨大的三角形网格符合我们的兴趣,根据连接的顶点的值进行着色。我们感谢您的努力。 – Developer

+0

http://stackoverflow.com/a/10253183/2851664此解决方案不需要插值,速度更快,并生成更好的图。 – sebix

+0

@sebix - 该解决方案与此相同。插值是可选的。如果要在原始顶点之间平滑过渡颜色,则需要使用它。 –

相关问题