オーディオ編集に使用されるものと非常によく似たマルチチャネルプロットを作成しようとしていますが、医療データ用です。
この種のプログラムは、いくつかの意味のあるイベントを見つけて分類するために、(とりわけ)データプロット上で水平方向にズームおよびパンする必要がある人が使用します。
したがって、Cairoを使用してgtk.DrawingAreaにプロットするデータストリーム(数万のサンプルのリスト)があり、プロットするデータの最初と最後のインデックスに基づく最初の「スケール」と幅があります。プロットするデータ間隔と描画領域のピクセル幅の比率。ほとんどの画像ビューアやGoogleマップと同じように、データを「ドラッグ」するためにいくつかのマウスイベントを作成しました(ただし、現在は横軸でのみ作業しています)。
実際のところ、パン中の再描画は非常に遅く、プロットされている間隔の長さに依存するため、再描画機能が原因だと思います(設定した「ズーム」に関連して、より密なデータ間隔を示しています) 。プロット全体を(大きな)pixbufferにレンダリングし、このpixbufferの位置を変更するだけで、対応する部分をウィンドウの描画領域にコミットする必要があるのではないかと思います。
だから、私の質問は次のとおりです。「パン/ズームを使用したこの種の2Dデータプロットは通常Pygtkでどのように行われますか?それを行う「標準」の方法はありますか?カイロソースとして使用できる巨大なpixbufferを作成する必要があります。それを翻訳して、描画領域のカイロ表面に「スタンプ」しますか?」
私のコードの縮小部分は次のとおりです。
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になります。結果はそれほどスムーズではありませんが、非常に高速です。より詳細な情報が必要な場合は、ズームインして解像度を上げることができます(したがって、ステップを再計算します)。