1

我正在尝试创建一个与用于音频编辑的多通道图非常相似,但用于医疗数据。

这种程序供应该(除其他外)在数据图上水平缩放和平移的人使用,以便查找和分类一些有意义的事件。

因此,我有一个数据流(包含数万个样本的列表),我使用 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 个样本值。结果不是那么流畅,但速度相当快,如果想要更多细节,可以放大以提高分辨率(从而重新计算步长)

4

2 回答 2

1

我更改了代码以使变量step取决于要绘制的可用像素之间的比例以及确实要绘制的数据的间隔长度。这样,如果窗口只有 1000 个像素,则整个间隔的“切片”将被截取,它只有 1000 个样本值。结果不是那么平滑,但速度相当快,如果想要更多细节,可以放大以提高分辨率(从而重新计算步长):

step = int(self.samples/float(w)) if step >= 1 else 1
stream = self.channel['recording'][startsample:endsample:step]
于 2011-04-27T16:13:24.860 回答
0

如果性能无关紧要,我建议您使用matplotlib。它非常完美,并且可以与包括 GtkEgg 在内的多个后端一起使用(只要我记得)

于 2011-04-20T10:39:54.550 回答