2011-04-19 9 views
1

오디오 편집에 사용 된 것과 매우 유사한 다중 채널 플롯을 만들려고하지만 의료 데이터 용입니다.PyGTK의 카이로 데이터 플로터 : pixbuffer를 사용해야하나요?

이러한 종류의 프로그램은 의미있는 이벤트를 찾아 분류하기 위해 (다른 것들 중에서) 데이터 플롯 위로 수평으로 확대/축소해야하는 사람이 사용해야합니다.

그래서 나는 데이터의 첫 번째와 마지막 인덱스를 기반으로 카이로를 사용하여 gtk.DrawingArea에 플롯 할 데이터 스트림 (수만 개의 샘플 목록)을 갖습니다. 플롯 할 데이터 간격과 그리기 영역의 픽셀 너비 사이의 너비를 지정합니다. 대부분의 이미지 뷰어 및 Google지도와 마찬가지로 데이터를 "드래그"하기 위해 일부 마우스 이벤트를 만들었지 만 (지금까지는 수평축에서만 작업하고 있습니다).

사실 : 패닝이 매우 느리고 다시 그려지기 때문에 다시 그려지는데, 이는 다시 그려지는 간격의 길이에 따라 달라지기 때문에 생각합니다. ("확대/축소"설정과 관련하여 더 많은 것을 보여줍니다. 고밀도 데이터 간격). 나는 전체 플롯을 (큰) pixbuffer로 렌더링해야하는지 궁금하다.이 pixbuffer를 재배치하면 해당 부분이 윈도우 그리기 영역에 커밋된다.

그래서, 내 질문은 :? "보통 Pygtk에서 수행 를 팬/줌 음모를 꾸미고 2D 이러한 종류의 데이터가 어떻게 그 일의 '표준'방법이 있나요 나는 거대한 pixbuffer를 작성해야하는 I 카이로 원천으로 사용할 수 있고 그것을 번역하고 그림 영역 카이로 표면에 '찍기'할 수 있습니까? 사람이 같은 목표를 달성하는 다른 방법에 대한 다른 팁이 있다면

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() 

또한, 나는 그것을받을 매우 기쁠 :

는 내 코드의 일부는 다음과 수축.

EDIT를 읽기위한

감사합니다 : 나는 플롯에 사용할 수있는 픽셀 사이의 비율에 변수 step 의존하게 코드를 변경하고 데이터의 간격 아이폰에 플롯 할 수 없습니다. 이 방법으로, 창에 1000 픽셀 만있는 경우 전체 샘플 중에서 1000 개의 샘플 값만있는 "슬라이스"가 사용됩니다. 결과가 너무 부드럽지는 않지만 매우 빠르며 자세한 내용을 원하면 확대하여 해상도를 높일 수 있습니다 (따라서 단계 재 계산)

답변

1

변수를 step으로 변경하여 코드의 사용 가능한 픽셀과 데이터의 간격 길이 사이의 비율에 따라 달라집니다. 음모. 이 방법으로, 창에 1000 픽셀 만있는 경우 전체 샘플 중에서 1000 개의 샘플 값만있는 "슬라이스"가 사용됩니다. 결과는 매우 부드러운 아니지만, 매우 빨리, 한 자세한 내용을 원한다면, 해상도 (따라서 단계를 다시 계산) 증가 확대 될 수있다 :

step = int(self.samples/float(w)) if step >= 1 else 1 
stream = self.channel['recording'][startsample:endsample:step] 
나는 그것에 대해 생각
+0

나는 그것이 내가 지금까지 얻었던 (그리고 @ilius 대답이 속도 문제를 해결하지 못했기 때문에) 내 자신의 질문에 대답했다. 물론 다른 대답도 환영한다. – heltonbiker

0

성능이 그리 중요하지 않으면 matplotlib을 사용하는 것이 좋습니다. . 매우 완벽하고 GtkEgg를 포함한 여러 백엔드에서 작동합니다 (내가 기억하는 한)

+0

을하지만, 실제로 나는이 '계획'은 항상 내 UI 프로그램에서 낮은 수준의 그리기 방법을 선호합니다. 예를 들어, 확대/축소가 괜찮 으면 곡선의 한 점을 클릭하고 오른쪽에 10 초의 투명 사각형을 그리고 마지막 5 분의 데이터 이동 평균을 계산하고이 선을 그려야합니다. 다른 채널의 마커 ... 어쨌든 귀하의 답변에 감사드립니다! – heltonbiker