J'ai un ensemble d'enregistrements de données comme celui-ci:
(s1, t1), (u1, v1), color1
(s2, t2), (u2, v2), color2
.
.
.
(sN, tN), (uN, vN), colorN
Dans tout enregistrement, les deux premières valeurs sont les points d'extrémité d'un segment de ligne, la troisième valeur est le couleur de ce segment de ligne. Plus précisement, (sn, tn)
sont les coordonnées x-y du premier point final, (un, vn)
sont les coordonnées x-y du deuxième point de terminaison. De plus, couleur est un rgb avec une valeur alpha.
En général, deux segments de ligne quelconques sont déconnectés (ce qui signifie que leurs extrémités ne coïncident pas nécessairement).
Comment tracer ces données en utilisant matplotlib avec un seul appel plot
(ou aussi peu que possible) car il pourrait y avoir potentiellement des milliers d'enregistrements.
Préparer les données dans une grande liste et appeler plot
par contre est beaucoup trop lent. Par exemple, le code suivant n'a pas pu se terminer dans un délai raisonnable:
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()
J'ai pu accélérer le rendu du tracé en utilisant l'astuce d'insertion None comme suit:
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)
Cela s'exécute en moins d'une seconde sur ma machine. Je dois encore comprendre comment intégrer les valeurs de couleur (RVB avec canal alpha).
OK, j'ai fini par pixelliser les lignes d'une image PIL avant de la convertir en un tableau 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()
C'est de loin la solution la plus rapide et elle correspond parfaitement à mes besoins en temps réel. Une mise en garde est cependant que les lignes sont tracées sans anti-aliasing.
utilisez 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)
voici la sortie:
la fonction plot
permet de tracer plusieurs lignes en un seul appel, si vos données sont juste dans une liste, décompressez-les simplement en les passant à 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>]
J'ai essayé quelques bons moteurs de rendu 2D disponibles sur Python 3, tout en recherchant une solution rapide pour une étape de sortie en Deep Learning et GAN orienté image.
En utilisant le benchmark suivant: Temps pour rendre 99 lignes dans une image hors écran 256x256 (ou quoi que ce soit plus efficace) avec et sans anti-alias.
Les résultats, par ordre d'efficacité sur mon vieux portable x301:
La ligne de base est une boucle qui prend ~ 0,1 ms (10 000 FPS) pour récupérer des nombres aléatoires et appeler les primitives.
Code de base pour 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')
Et voici pour 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')
Et voici Python3-Cairo pour l'exhaustivité:
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')