2012-11-29 8 views
5

Python. matplotlib: Wie koloriert man eine große Anzahl von Liniensegmenten effizient als unabhängige Gradienten?
Lesen Sie bereits this und this und andere Sachen; keiner von ihnen ist unsere Antwort!Matplotlib: Wie man eine große Anzahl von Liniensegmenten effizient als unabhängige Gradienten einfärbt

Wir haben eine Reihe von separaten Linien, die jeweils in Farbverlauf zeichnen möchten.

Die im ersten Link oben erwähnte Lösung funktioniert nicht, wenn Sie mehr als eine Zeichenfolge der Zeile haben. Mit anderen Worten beeinflusst das Ändern des Farbzyklus alles in der Zeichnung, nicht die einzige interessierende Linie. Das ist nicht von unserem Interesse.

Der zweite Link, der zur Site matplotlib gehört, verwendet die Segmentierung jeder Zeile in viele. Dies ist kein guter Ansatz, weil für eine große Anzahl von Zeilen, sagen wir, 10000 oder mehr; Selbst wenn Sie nur 10 Segmente pro Zeile wählen, ist das Ergebnis zu groß! Selbst dann sind die resultierenden Linien überhaupt nicht glatt gefärbt! Wenn Sie die Anzahl der Segmentierung abhängig von den Liniensegmenten für einen besseren Verlauf machen, wird das Ergebnis sehr groß sein! Schwer zu zeigen, schwer als Datei richtig zu speichern.

+2

Ich vermute, dass Sie eine Einschränkung von 'matplotlib' getroffen haben, aber glaube nicht, Das ist dein Hauptproblem. Wenn Sie 10k-Zeilen haben, selbst wenn Sie sie mit 1px Breite gezeichnet haben, würde das Ausrichten eine große Anzeige/einen großen Ausdruck erfordern, um sie alle unabhängig voneinander sehen zu können. Selbst wenn Sie einen Weg finden könnten, diese Handlung zu machen, würden Sie nicht in der Lage sein, es vernünftig zu betrachten. Gibt es einen Weg, wie Sie Ihre Daten grobkörnig machen können? – tacaswell

+0

Haben Sie versucht, eine einzelne Zeilensammlung anstelle von 10000 separaten Zeilensammlungen hinzuzufügen? Es ist immer noch langsam, aber es ist schneller. Es ist auch reaktionsschneller beim Schwenken und Zoomen. –

+0

@JoeKington: Netter Trick. – Developer

Antwort

6

Eine (kleinere) Beschleunigung würde eine einzelne Zeilensammlung anstelle von 10000 separaten Zeilensammlungen hinzufügen.

Solange alle Zeilen die gleiche Farbpalette haben, können Sie sie zu einer einzigen Zeilensammlung zusammenfassen, wobei jede Zeile einen unabhängigen Farbverlauf haben kann.

Matplotlib ist immer noch langsam für diese Art von Sache. Es ist optimiert für die Ausgabequalität und nicht für die schnelle Zeichenzeit. Sie können jedoch etwas beschleunigen (~ 3x). (?)

So, als Beispiel dafür, wie ich glaube, du bist wahrscheinlich tun es jetzt:

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() 

Stattdessen würde ich etwas in dieser Richtung reccomend tun (die einzigen Unterschiede sind in der main Funktion):

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() 

Beide Varianten erzeugen eine identische Handlung:

enter image description here


Wenn wir die Anzahl der Zeilen auf 10000 erhöhen, werden wir jedoch deutliche Leistungsunterschiede feststellen.

Verwenden von 10000 Zeilen, mit 3 Punkten jeden und weitere 20 Punkte im gesamten für den Farbverlauf interpolierten (23 Segmente in jeder Zeile) und man die Zeit, um eine Abbildung auf eine PNG speichern es dauert:

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

Also, die Verwendung einer einzelnen Zeile Sammlung wird ein bisschen weniger als eine 3x Beschleunigung in diesem speziellen Fall geben. Es ist nicht hervorragend, aber es ist besser als nichts.

Hier ist der Timing-Code und die Ausgabezahl, für was auch immer es sich lohnt (Die Ausgabezahlen sind nicht ganz identisch wegen der verschiedenen Anordnungen der Zeichnung.Wenn Sie die Kontrolle über z-Ebene benötigen, werden Sie in separaten Zeile Sammlungen halten müssen):

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

Vielen Dank. Es ist in der Tat ein netter Trick. Wir akzeptieren Ihre vollständige Antwort so. Wir sind uns fast sicher, dass dies aufgrund des Segmentierungskonzepts am besten möglich ist. Wir möchten hier erwähnen, dass in unserer Anwendung ein massives Dreiecksgitter von Interesse ist, das entsprechend den Werten der verbundenen Ecken gefärbt wird. Wir schätzen Ihre Bemühungen. – Developer

+0

http://stackoverflow.com/a/10253183/2851664 Diese Lösung benötigt keine Interpolation, ist viel schneller und erzeugt schönere Plots. – sebix

+0

@sebix - Diese Lösung ist identisch mit dieser. Die Interpolation ist optional. Sie werden es in beiden Fällen benötigen, wenn Sie die Farben zwischen den ursprünglichen Scheitelpunkten übergehen möchten. –