我正在尝试创建一个与用于音频编辑的多通道图非常相似,但用于医疗数据。
这种程序供应该(除其他外)在数据图上水平缩放和平移的人使用,以便查找和分类一些有意义的事件。
因此,我有一个数据流(包含数万个样本的列表),我使用 Cairo 在 gtk.DrawingArea 上绘制,初始“比例”基于要绘制的数据的第一个和最后一个索引,以及宽度要绘制的数据间隔与绘图区域的像素宽度之间的比率。我创建了一些鼠标事件来“拖动”数据,就像大多数图像查看器甚至谷歌地图一样(但我现在只在水平轴上工作)。
事实是:在平移时重绘非常慢,我认为这是因为重绘功能,因为它取决于被绘制的间隔的长度(与我设置的“缩放”有关,显示更密集的数据间隔) . 我想知道我是否应该将整个绘图渲染到一个(大)pixbuffer,并且只重新定位这个pixbuffer,将相应的部分提交到窗口绘图区域。
所以,我的问题是:“这种带有平移/缩放的二维数据通常如何在 Pygtk 中完成?有没有一种‘标准’的方法?我是否应该创建一个巨大的像素缓冲区,我可以将其用作 cairo 源,翻译它并在绘图区开罗表面上“盖章”?”
我的代码的缩小部分如下:
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()
另外,如果有人对实现相同目标的其他方法有任何其他提示,我将很高兴收到它。
谢谢阅读
编辑:我更改了代码以使变量step
取决于要绘制的可用像素之间的比例和数据的间隔长度之间的比例。这样,如果窗口只有 1000 个像素,则整个间隔的“切片”将被截取,它只有 1000 个样本值。结果不是那么流畅,但速度相当快,如果想要更多细节,可以放大以提高分辨率(从而重新计算步长)