Ejemplo n.º 1
0
    def add_controls(self):
        """Add the main controls to the frame and layout"""
        #Create splitters:
        mgr = ViewerSplitManager(self)
        main = mgr.add_split(self, 'main', 'vertical', proportion=0.2)
        right = mgr.add_split(main, 'right', 'vertical', proportion=0.9)
        hleft = mgr.add_split(main, 'hleft', 'horizontal', proportion=0.5)

        #Create actual controls
        self.eegpanel = EEGPanel(right, mode=-1)
        self.console = ViewerConsole(self)
        self.viewerterminal = Terminal(hleft, console=self.console, windowtoclose=self)
        self.eventeditpnl = EventEditPanel(right)
        self.eventlistpnl = EventListPanel(hleft, self)
        self.eegpanel.set_interactive(ViewerZoomPanManager(self))

        # Register panels with SplitManager
        mgr.add_panel(self.eegpanel, 'eegpanel', showing=True)
        mgr.add_panel(self.eventeditpnl, 'eventedit', showing=True)
        mgr.add_panel(self.eventlistpnl, 'eventlist', showing=True)
        mgr.add_panel(self.viewerterminal, 'terminal', showing=True)

        # Describe the splitting tree
        mgr.set_order('right', 'eegpanel', 'eventedit')
        mgr.set_order('hleft', 'eventlist', 'terminal')
        mgr.set_order('main', 'hleft', 'right')

        self.eventeditpnl.SetEegPanel( self.eegpanel ) # REMOVE

        # Sort out which panels are showing
        self.panels = mgr

        return main
Ejemplo n.º 2
0
class ViewerFrame(wx.Frame):
    """This class defines the eeg viewer window"""

    def __init__(self,*args,**kwargs):
        """Calls wx.Frame.__init__, then creates necessary bits"""
        #Call parent constructor:
        wx.Frame.__init__(self,*args,**kwargs)

        #Start with no eegfile:
        self.eegfile = None
        self.eventctrl = EventCtrl()

        # Add controls, menu, statusbar and accelerators
        mainchild = self.add_controls()
        self.menubar, self.popupmenu = self.create_menu_bar(self.viewerterminal)
        self.SetMenuBar(self.menubar)
        self.statusbar = self.CreateStatusBar()

        # Set sizer after creating everything
        sizer = wx.BoxSizer()
        sizer.Add(mainchild, 1, wx.EXPAND)
        self.SetSizerAndFit(sizer)
        self.panels.update()

        # Create the status bar:
        self.Bind(wx.EVT_CLOSE, self.OnClose)

    def add_controls(self):
        """Add the main controls to the frame and layout"""
        #Create splitters:
        mgr = ViewerSplitManager(self)
        main = mgr.add_split(self, 'main', 'vertical', proportion=0.2)
        right = mgr.add_split(main, 'right', 'vertical', proportion=0.9)
        hleft = mgr.add_split(main, 'hleft', 'horizontal', proportion=0.5)

        #Create actual controls
        self.eegpanel = EEGPanel(right, mode=-1)
        self.console = ViewerConsole(self)
        self.viewerterminal = Terminal(hleft, console=self.console, windowtoclose=self)
        self.eventeditpnl = EventEditPanel(right)
        self.eventlistpnl = EventListPanel(hleft, self)
        self.eegpanel.set_interactive(ViewerZoomPanManager(self))

        # Register panels with SplitManager
        mgr.add_panel(self.eegpanel, 'eegpanel', showing=True)
        mgr.add_panel(self.eventeditpnl, 'eventedit', showing=True)
        mgr.add_panel(self.eventlistpnl, 'eventlist', showing=True)
        mgr.add_panel(self.viewerterminal, 'terminal', showing=True)

        # Describe the splitting tree
        mgr.set_order('right', 'eegpanel', 'eventedit')
        mgr.set_order('hleft', 'eventlist', 'terminal')
        mgr.set_order('main', 'hleft', 'right')

        self.eventeditpnl.SetEegPanel( self.eegpanel ) # REMOVE

        # Sort out which panels are showing
        self.panels = mgr

        return main

    def create_menu_bar(self, terminal):
        """Create, bind and return the menubar used by ViewerFrame"""
        # Shorten repetitive binding code
        def add_to_menu(menu, text, func, help, **kwargs):
            menuitem = menu.Append(wx.ID_ANY, text, help, **kwargs)
            self.Bind(wx.EVT_MENU, func, menuitem)
            return menuitem

        # Quickly wrap a console command as a wx event handler
        def wxrun(cmdline):
            return lambda event: self.run_command(cmdline)

        # EEG and event file operations menu
        filemenu = wx.Menu()
        add_to_menu(filemenu, '&Open...\tCtrl+O', self.OnOpen, 'Open an EEG file')
        add_to_menu(filemenu, '&Quit...\tCtrl+Q', self.OnQuit, 'Exit program' )
        add_to_menu(filemenu, 'Open spikes...', self.OnOpenSpikes, 'Exit program')
        add_to_menu(filemenu, 'Save spikes...', self.OnSaveSpikes, 'Exit program')
        add_to_menu(filemenu, 'Save cursors...\tCtrl+Z', self.OnSaveCursors, 'Save cursors')

        # Viewer layout menu
        viewmenu = wx.Menu()
        self._viewitems = {} # Store for managing checks in split_panels
        for n, panelname in enumerate(self.panels.names()):
            text = 'Show {0}\tCtrl+{1}'.format(panelname, n)
            helptext = 'Toggle display of the {0} panel'.format(panelname)
            func = wxrun('show {0}'.format(panelname))
            item = add_to_menu(viewmenu, text, func, helptext, kind=wx.ITEM_CHECK)
            self._viewitems[panelname] = item

        # Submenus for Navigation menu
        scalemenu = wx.Menu()
        for n, scale in enumerate(terminal.console.SCALES):
            add_to_menu(scalemenu, str(scale),
                    wxrun('scale {0}'.format(scale)),
                    'Set scale to ' + str(scale), kind=wx.ITEM_RADIO)
        widthmenu = wx.Menu()
        for n, width in enumerate(terminal.console.PAGE_WIDTHS):
            add_to_menu(widthmenu, str(width),
                        wxrun('zoom {0}'.format(width)),
                        'Set page width to ' + str(width), kind=wx.ITEM_RADIO)

        # EEG navigation menu
        navigatemenu = wx.Menu()
        add_to_menu(navigatemenu, 'Scroll &Left\tCtrl+Left',
                wxrun('go left 0.2'), 'Scroll the view to the left' )
        add_to_menu(navigatemenu, 'Scroll &Right\tCtrl+Right',
                wxrun('go right 0.2'), 'Scroll the view to the right' )
        add_to_menu(navigatemenu, '&Next Page\tCtrl+Pgdn',
                wxrun('go right 1'), 'Scroll the view to the right' )
        add_to_menu(navigatemenu, '&Prev Page\tCtrl+Pgup',
                wxrun('go left 1'), 'Scroll the view to the left' )
        add_to_menu(navigatemenu, '&Go to...\tCtrl+G',
                self.OnGoTo, 'Open "go to" dialog' )
        navigatemenu.AppendSeparator()
        navigatemenu.AppendMenu(wx.ID_ANY, 'Set &Scale', scalemenu)
        add_to_menu(navigatemenu, '&Decrease scale\tCtrl++',
                wxrun('scale down'), 'Decrease the scale to the next level')
        add_to_menu(navigatemenu, '&Increase scale\tCtrl+-',
                wxrun('scale up'), 'Increase the scale to the next level')
        navigatemenu.AppendSeparator()
        navigatemenu.AppendMenu(wx.ID_ANY, 'Set Page &Width', widthmenu)
        add_to_menu(navigatemenu, 'Zoom &Out\tCtrl+Down',
                wxrun('zoom out'), 'Increase the page width to the next level')
        add_to_menu(navigatemenu, '&Zoom In\tCtrl+Up',
                wxrun('zoom in'), 'Decrease the page width to the next level')
        add_to_menu(navigatemenu, '&Reset View\tCtrl+R',
                wxrun('reset'), 'Reset time, width and scale')
        navigatemenu.AppendSeparator()

        # Event edit menu
        eventmenu     = self.eventctrl.GetEventMenu( self , self.OnAddEvent )

        # Aggregate in menubar and return
        menubar = wx.MenuBar()
        menubar.Append(filemenu, '&File')
        menubar.Append(viewmenu, '&View')
        menubar.Append(navigatemenu, '&Navigate')
        menubar.Append(eventmenu, '&Event')
        menubar.viewmenu = viewmenu

        # Create add event menu for popup
        evtmenu = self.eventctrl.GetEventMenu(self, self.OnAddEvent)
        evtmenu.BindItems(self)

        return menubar, evtmenu

#:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

    def set_events(self, events):
        self.eventlistpnl.SetItems(events)
        self.eventeditpnl.ShowEvent(None)

    def run_command(self, cmdline):
        self.viewerterminal.run_command(cmdline)

    def set_graphics_mode(self, name):
        self.eegpanel.set_mode(name)

    def set_filename_time(self, fname, time):
        self.SetTitle("{0}\t{1}".format(fname, time.date()))
        self.statusbar.SetStatusText(str(time))
        self._time = time

    def get_file_name(self, rw, msg, deffile='', wildcard=''):
        styles = {'r':wx.FD_OPEN, 'w':wx.FD_SAVE}
        dialog = wx.FileDialog(self, style=styles[rw], wildcard=wildcard,
                               defaultDir=os.getcwd(), defaultFile=deffile)
        if dialog.ShowModal() == wx.ID_OK:
            path = dialog.GetPath()
            fmtindex = dialog.GetFilterIndex()
        else:
            path = None
            fmtindex = None
        dialog.Destroy()
        return path, fmtindex

    def OnOpen(self, event):
        """Prompts for file, then attempts to load as EEG file"""
        # wild card for supported types:
        wc = "All files|*.*"
        filters = []
        for fmt, format_name in eeg_formats.iteritems():
            if 'r' in eeg_supported[fmt]:
                wc += "|{0}: {1}|*.*".format(fmt.upper(), format_name)
                filters.append(fmt)
        # Open file dialogue:
        fname, index = self.get_file_name('r', "Choose EEG file", wildcard=wc)
        if not fname:
            return
        # Use format specified in filter
        if index == 0:
            cmdline = 'open "{0}"'.format(fname)
        else:
            fmt = filters[dialog.GetFilterIndex() - 1]
            cmdline = 'open "{0}" {1}'.format(fname, fmt)
        self.run_command(cmdline)

    def OnGoTo(self, event):
        """Prompts the user for a date/time and moves if OK selected"""
        dlg = DateTimeDlg(None, title='Go to time...', dt=self._time)
        dlg.ShowModal()
        t = dlg.datetime
        if t != -1:
            fmtstr = '{0.day}/{0.month}/{0.year}-' +\
                     '{0.hour}:{0.minute}:{0.second}.{1}'
            timestr = fmtstr.format(t, str(t.microsecond).rjust(6, '0'))
            self.run_command('go to {0}'.format(timestr))
        dlg.Destroy()

    def OnRightDownEEGPanel(self, axes, coords):
        """Bound to rightclick on the eegpanel"""
        # Store temporarily
        self.__tc = self.eegpanel.time_chan_from_coords(coords)
        self.eegpanel.PopupMenu(self.popupmenu)
        del self.__tc

    def OnAddEvent(self, eventclass):
        """Called by eventmenu"""
        t, chan = self.__tc
        event = eventclass(t, chan=chan, eegfile=self.eegfile)
        self.eventlistpnl.AddItem(event, select=True)


    #
    # Spike saving. Spikes can be saved in two formats:
    #

    formats = [('Text Files'      ,'txt'),\
               ('Comma Seperated' ,'csv')]
    filterformat = ''
    for text , fmt in formats:
        filterformat += text + '|*.' + fmt + '|'
    filterformat = filterformat[:-1]
    defext = '.txt'

    def OnOpenSpikes( self , event=None ):
        """Prompts for file, then attempts to load as EEG file"""
        # Retrieve filename:
        eegfile = self.viewerterminal.console.eegfile
        deffile = os.path.splitext(eegfile.name)[0]+self.defext
        dialog = wx.FileDialog( self ,"Choose filename to load spikes from",\
                                style      = wx.FD_OPEN,\
                                wildcard   = self.filterformat,\
                                defaultDir = os.getcwd(),\
                                defaultFile= deffile )

        if dialog.ShowModal() == wx.ID_OK:
            path  = dialog.GetPath()
            fmtindex = dialog.GetFilterIndex()
            fmt      = self.formats[fmtindex][1]
            # Actually load:
            self.LoadSpikes(path,fmt)
        dialog.Destroy()

    def OnSaveSpikes( self , event=None ):
        """Prompts for file, then attempts to load as EEG file"""
        message = "Choose name for spikes file"
        path, fmtindex = self.get_file_name('w', message, wildcard=self.filterformat)
        if path:
            fmt = self.formats[fmtindex][1]
            self.SaveSpikes(path,fmt)

    def OnSaveCursors( self , event=None ):
        """Prompts for file, then attempts to load as EEG file"""
        path, fmtindex = self.get_file_name('w', "Choose name for cursors file")
        if path: self.SaveCursors(path)

    def LoadSpikes( self , path , fmt ):
        """"""
        eegfile = self.viewerterminal.console.eegfile
        # Now read the events:
        f = open( path , 'r' )
        if fmt == 'txt':
            # Obtain chans by creating a false event:
            chans = SpikeEvent(eegfile=self.eegfile,\
                               t=self.eegfile.tstart+datetime.timedelta(0,1,0)).chans
            events = []
            for line in f.readlines():
                # First line is chans if nonempty:
                words = line.split('|')
                t =         eval(words[0])
                colour =     eval(words[1])
                times =     eval(words[2])
                e = SpikeEvent( t=t , colour=colour , times=times ,
                        eegfile=self.eegfile )
                e.chans = chans # Add chans manually
                events.append( e )
        elif fmt == 'csv':
            # Check EEG file name
            fname = f.readline().rstrip()
            ename = os.path.split(eegfile.name)[1]
            if fname != ename:
                raise ValueError("This spike file is for another EEG file")

            # Establish midnight datetime
            datestr = f.readline().rstrip()
            year,month,day=[int(s) for s in datestr.split('-')]
            midnight = datetime.datetime(year,month,day)

            # Data is represented in seconds from midnight:
            def str2t( s ):
                if s == 'None':
                    return None
                sec , usecf = divmod(float(s),1)
                return midnight + datetime.timedelta(0,int(sec),int(1e6*usecf))

            # Read 3rd line (chan names):
            words = f.readline().rstrip().split(',')
            chans = words[1:]

            # Now read through all lines:
            events = []
            for line in f.readlines():
                words = line.rstrip().split(',')
                t = str2t(words.pop(0))
                dts = [str2t(w) for w in words]
                times = {}
                for c,dt in zip(chans,dts):
                    times[c]=dt
                e = SpikeEvent( t=t , times=times , eegfile=self.eegfile )
                e.chans = chans
                events.append(e)

        f.close()
        self.eventlistpnl.SetItems( events )

    def SaveSpikes( self , path , fmt ):
        """"""
        events = self.eventlistpnl.GetItems()
        f = open( path , 'w' )
        # Write events one line each:
        if fmt == 'txt':
            for e in events:
                if isinstance( e , SpikeEvent ):
                    line = repr(e.t)+'|'+repr(e.colour)+'|'+repr(e.times)
                    f.write( line + '\n' )
        # For importing into excel:
        elif fmt == 'csv':
            tstart = self.eegfile.tstart
            midnight = tstart.combine(tstart.date(),datetime.time())
            # Data is represented in seconds from midnight:
            def t2str( t ):
                if t is None:
                    return 'None'
                td = t-midnight
                tsecs = td.seconds+1e-6*td.microseconds
                return str(tsecs)

            # First write name of corresponding eegfile
            ename = split(self.eegfile.name)[1]
            f.write(ename+'\n')
            # Date needed to load back in
            f.write(str(midnight.date())+'\n')
            # Now write channel names (if available):
            if events:
                e = events[0]
                chans = e.chans
                line = ','.join(chans)
                line = ','.join(['global',line]) # insert beginning
                f.write(line)
                f.write('\n')

            # Now write the individual events:
            for e in events:
                ts = [t2str(e.times[c]) for c in chans]
                line = ','.join(ts)
                line = ','.join([t2str(e.t),line])
                f.write(line)
                f.write('\n')

        f.close()

    def SaveCursors(self, path):
        """"""
        events = self.eventlistpnl.GetItems()
        events = [e for e in events if isinstance(e, CursorEvent)]
        f = open(path, 'w')
        for e in events:
            f.write(repr(e.t) + '\n')
        f.close()

    def LoadCursors(self, path):
        """"""
        # Now read the events:
        f = open( path , 'r' )
        events = [CursorEvent(eval(line)) for line in f.readlines()]
        f.close()
        self.eventlistpnl.SetItems( events )

    def OnQuit( self , event=None ):
        """Close the program"""
        self.run_command('exit')

    def OnClose( self , event=None ):
        """close the program"""
        self.run_command('onexit')
        self.Destroy()

    def OnRightButton( self , t , chan ):
        """Called when user right-clicks the canvas"""
        self._rbuttont = t
        self._rbuttonchan = chan
        self.PopupMenu( self.popupmenu )

    def OnSelectEvent( self , event , index ):
        """
        callback for self.eventlistpnl.OnSelectItem
        Moves eegpanel view to centre on selected event
        """
        if event is None:
            self.eventeditpnl.ShowEvent( None )
        else:
            e , chkin = self.eventlistpnl.CheckOutItem( index )
            self.eegpanel.set_selected_event(event)
            self.redraw()
            self.eventeditpnl.ShowEvent( e , chkin )

    def OnEventsChanged( self , events ):
        """
        callback for self.eventlistpnl.OnItemsChanged
        sends new list of items to self.eegpanel
        """
        self.eegpanel.set_events(events)
        self.redraw()

    def redraw(self):
        """This should be a console command"""
        self.viewerterminal.console.display.redraw()