J'essaie de créer un tracé multicanal très similaire à ceux utilisés pour l'édition audio, mais pour les données médicales.Traceur de données Cairo dans PyGTK: devrais-je utiliser un pixbuffer?
Ce type de programme doit être utilisé par une personne qui devrait (entre autres) zoomer et faire un panoramique horizontalement sur la parcelle de données, afin de trouver et de classifier certains événements significatifs. Donc, j'ai un flux de données (une liste de plusieurs dizaines de milliers d'échantillons) que je trace sur un gtk.DrawingArea en utilisant Cairo, avec une "échelle" initiale basée sur le premier et le dernier index des données à tracer et un rapport de largeur entre l'intervalle de données à tracer et la largeur de pixel de la zone de dessin. J'ai créé des événements souris pour "faire glisser" les données, comme le font la plupart des visionneuses d'images et même Google Maps (mais je ne travaille que sur l'axe horizontal maintenant). Le fait est: redessiner alors que le panoramique est assez lent, et je pense que c'est à cause de la fonction de redessiner, car cela dépend de la longueur de l'intervalle étant tracé (lié au "zoom" intervalle de données dense). Je me demande si je devrais rendre tout le tracé à un (grand) pixbuffer, et seulement repositionner ce pixbuffer commettre la partie correspondante dans la zone de dessin de la fenêtre.
Alors, mes questions sont les suivantes:? « Comment est ce genre de données 2D complotant avec pan/zoom fait habituellement dans Pygtk est-il un moyen « standard » de le faire Dois-je créer un énorme pixbuffer que je pourrait utiliser comme une source de cairo, en le traduisant et «estampillage» sur la surface de dessin cairo surface? "
Une Shrinked partie de mon code suit:
class DataView(gtk.DrawingArea):
""" Plots a 'rectangle' of the data, depending on predefined horizontal and vertical ranges """
def __init__(self, channel):
gtk.DrawingArea.__init__(self)
self.connect("expose_event", self.expose)
self.channel = dados.channel_content[channel]
self.top = int(self.channel['pmax'])
self.bottom = int(self.channel['pmin'])
# this part defines position and size of the plotting
self.x_offset = 0
self.y_offset = 0
self.x_scale = 1
self.y_scale = 0.01
def expose(self, widget, event):
cr = widget.window.cairo_create()
rect = self.get_allocation()
w = rect.width
h = rect.height
cr.translate(0, h/2)
cr.scale(1,-1)
cr.save()
self.x_scale = 1.*w/(signalpanel.end - signalpanel.start)
cr.translate(self.x_offset, self.y_offset)
cr.scale(self.x_scale, self.y_scale)
step = 5
# here I select a slice of my full data list
stream = self.channel['recording'][signalpanel.start:signalpanel.end:step]
# here I draw
cr.move_to(0, stream[0])
for n,s in enumerate(stream[1:]):
cr.line_to((n+1)*step, s)
cr.restore()
cr.set_source_rgb(0,0,0)
cr.set_line_width(1)
cr.stroke()
class ChannelView(gtk.HBox):
""" contains a DataView surrounded by all other satellite widgets """
def __init__(self, channel):
gtk.HBox.__init__(self)
labelpanel = gtk.VBox()
labelpanel.set_size_request(100, 100)
dataview = DataView(channel)
dataview.connect("motion_notify_event", onmove)
dataview.connect("button_press_event", onpress)
dataview.connect("button_release_event", onrelease)
dataview.connect("destroy", gtk.main_quit)
dataview.add_events(gtk.gdk.EXPOSURE_MASK
| gtk.gdk.LEAVE_NOTIFY_MASK
| gtk.gdk.BUTTON_PRESS_MASK
| gtk.gdk.BUTTON_RELEASE_MASK
| gtk.gdk.POINTER_MOTION_MASK
| gtk.gdk.POINTER_MOTION_HINT_MASK)
self.pack_end(dataview, True, True)
self.pack_end(gtk.VSeparator(), False, False)
#populate labelpanel
""" a lot of widget-creating code (ommited) """
# three functions to pan the data with the mouse
def onpress(widget, event):
if event.button == 1:
signalpanel.initial_position = event.x
signalpanel.start_x = signalpanel.start
signalpanel.end_x = signalpanel.end
signalpanel.queue_draw()
def onmove(widget, event):
if signalpanel.initial_position:
signalpanel.start = max(0, int((signalpanel.start_x - (event.x-signalpanel.initial_position))*widget.x_scale))
signalpanel.end = int((signalpanel.end_x - (event.x-signalpanel.initial_position))*widget.x_scale)
print signalpanel.start, signalpanel.end
signalpanel.queue_draw()
def onrelease(widget, event):
signalpanel.initial_position = None
signalpanel.queue_draw()
class PlotterPanel(gtk.VBox):
""" Defines a vertical panel with special features to manage multichannel plots """
def __init__(self):
gtk.VBox.__init__(self)
self.initial_position = None
# now these are the indexes defining the slice to plot
self.start = 0
self.end = 20000 # full list has 120000 values
if __name__ == "__main__":
folder = os.path.expanduser('~/Dropbox/01MIOTEC/06APNÉIA/Samples')
dados = EDF_Reader(folder, 'Osas2002plusQRS.rec') # the file from where the data come from
window = gtk.Window()
signalpanel = PlotterPanel()
signalpanel.pack_start(ChannelView('Resp abdomen'), True, True)
window.add(signalpanel)
window.connect("delete-event", gtk.main_quit)
window.set_position(gtk.WIN_POS_CENTER)
window.show_all()
gtk.main()
Aussi, si quelqu'un a une autre astuce sur d'autres façons d'atteindre le même objectif, je serais très heureux de le recevoir.
Merci pour la lecture
EDIT: J'ai changé le code pour la variable dépendante step
sur la proportion entre les pixels disponibles pour tracer et l'intervalle longueur des données ne soient parcelle. De cette façon, si la fenêtre a seulement, disons, 1000 pixels, une "tranche" de l'intervalle entier sera prise, qui n'a que 1000 valeurs d'échantillon. Le résultat n'est pas si lisse, mais il est assez rapide, et si on veut plus de détails, on peut zoomer pour augmenter la résolution (donc recalculer l'étape)
J'ai répondu à ma propre question parce que c'est ce que j'ai obtenu jusqu'à présent (et la réponse @ilius n'a pas résolu le problème de vitesse), mais bien sûr toute autre réponse est la bienvenue. – heltonbiker