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