Ejemplo n.º 1
0
class Controller:

    REFRESH_INTERVAL = 500 #ms
    STATE_NULL = 0
    STATE_PREPARED = 1
    STATE_RUNNING = 2
    STATE_KILLED_SESSION = 3
    STATE_RUN_COMPLETE = 4

    def __init__(self):
        # Make app
        self.app = wx.App(False) 
        
        self.update_timer = None

        # Generate view    
        subs_list = list_subjects()
        self.cond_list = sorted(conditions.keys())
        self.manip_list = sorted(manipulations.keys())
        self.view = View(None, subs=subs_list, conditions=self.cond_list, manipulations=self.manip_list)
        self.mp285_view = MP285View(None)
        if False:#not config.TESTING_MODE:
            sys.stdout=self.view.redir_out
            sys.stderr=self.view.redir_err
        setup_logging(outwin=self.view.redir_out,errwin=self.view.redir_err)

        self.tcpip = TCPIP(config.scanimage_tcpip_address)
        self.mp285 = MP285(**config.mp285_params)
        self.actuator = LActuator(**config.actuator_params)

        # Button bindings
        self.view.start_button.Bind(wx.EVT_BUTTON, self.evt_onoff)
        self.view.prepare_button.Bind(wx.EVT_BUTTON, self.evt_prepare)
        self.view.pause_button.Bind(wx.EVT_BUTTON, self.evt_pause)
        self.view.tcpip_button.Bind(wx.EVT_BUTTON, self.evt_tcpip)
        self.view.mp285_button.Bind(wx.EVT_BUTTON, self.evt_mp285)
        self.view.resetcam_button.Bind(wx.EVT_BUTTON, self.evt_resetcam)
        self.view.resetlact_button.Bind(wx.EVT_BUTTON, self.evt_resetlact)
        self.view.Bind(wx.EVT_CLOSE, self.evt_close)
        self.view.cal_but.Bind(wx.EVT_BUTTON, self.calib)
        self.view.puffc_but.Bind(wx.EVT_BUTTON, lambda evt: puff_check())
        self.view.locklev_but.Bind(wx.EVT_BUTTON, self.locklev)
        self.view.levu_but.Bind(wx.EVT_BUTTON, lambda evt, temp=1: self.change_level(evt,temp))
        self.view.levd_but.Bind(wx.EVT_BUTTON, lambda evt, temp=-1: self.change_level(evt,temp))
        self.view.rewardl_but.Bind(wx.EVT_BUTTON, lambda evt, temp=L: self.give_reward(evt, temp))
        self.view.rewardr_but.Bind(wx.EVT_BUTTON, lambda evt, temp=R: self.give_reward(evt, temp))
        self.view.puffl_but.Bind(wx.EVT_BUTTON, lambda evt, temp=L: self.give_puff(evt, temp))
        self.view.puffr_but.Bind(wx.EVT_BUTTON, lambda evt, temp=R: self.give_puff(evt, temp))
        self.view.optoon_but.Bind(wx.EVT_BUTTON, self.opto_on)
        self.view.optooff_but.Bind(wx.EVT_BUTTON, self.opto_off)
        self.view.maniptog_but.Bind(wx.EVT_BUTTON, self.manip_toggle)
        self.view.act_ext_but.Bind(wx.EVT_BUTTON, self.actuator_extend)
        self.view.act_ret_but.Bind(wx.EVT_BUTTON, self.actuator_retract)
        self.view.add_sub_button.Bind(wx.EVT_BUTTON, self.evt_addsub)
        self.view.usrinput_box.Bind(wx.EVT_TEXT_ENTER, self.update_usrinput)

        # Button bindings (mp285 view)
        self.mp285_view.Bind(wx.EVT_CLOSE, self.evt_mp285_close)
        self.mp285_view.but_set_home.Bind(wx.EVT_BUTTON, lambda evt, temp='home': self.evt_mp285_set(evt, temp))
        self.mp285_view.but_set_stim.Bind(wx.EVT_BUTTON, lambda evt, temp='stim': self.evt_mp285_set(evt, temp))
        self.mp285_view.but_set_lick.Bind(wx.EVT_BUTTON, lambda evt, temp='lick': self.evt_mp285_set(evt, temp))
        self.mp285_view.but_goto_home.Bind(wx.EVT_BUTTON, lambda evt, temp='home': self.evt_mp285_goto(evt, temp))
        self.mp285_view.but_goto_stim.Bind(wx.EVT_BUTTON, lambda evt, temp='stim': self.evt_mp285_goto(evt, temp))
        self.mp285_view.but_goto_lick.Bind(wx.EVT_BUTTON, lambda evt, temp='lick': self.evt_mp285_goto(evt, temp))
        for direc in self.mp285_view.ctrl_buttons:
            for mag in self.mp285_view.ctrl_buttons[direc]:
                for ax in self.mp285_view.ctrl_buttons[direc][mag]:
                    self.mp285_view.ctrl_buttons[direc][mag][ax].Bind(wx.EVT_BUTTON, lambda evt, temp=(direc,mag,ax): self.evt_mp285_move(evt,temp))


        # Runtime
        self.update_state(self.STATE_NULL)
        self.last_cam_frame = None
        self.n_updates = 0

        # Run
        #self.view.Show()
        self.app.MainLoop()

    def update_state(self, st=None):
        if st is not None:
            self.state = st

        if self.state == self.STATE_NULL:
            self.view.prepare_button.Enable()
            self.view.prepare_button.SetLabel('Prepare Session')
            self.view.add_sub_button.Enable()
            self.view.start_button.Disable()
            self.view.cal_but.Enable()
            self.view.levu_but.Disable()
            self.view.levd_but.Disable()
            self.view.puffr_but.Disable()
            self.view.puffl_but.Disable()
            self.view.rewardr_but.Enable()
            self.view.rewardl_but.Enable()
            self.view.optoon_but.Disable()
            self.view.optooff_but.Disable()
            self.view.maniptog_but.Disable()
            self.view.pause_button.Disable()
            self.view.update_sub_choices(list_rewards())
            self.view.locklev_but.Disable()
            self.mp285_view.but_set_lick.Enable()
            self.mp285_view.but_set_home.Enable()
            self.mp285_view.but_set_stim.Enable()
        elif self.state == self.STATE_PREPARED:
            self.view.usrinput_box.SetValue('(notes)')
            self.view.prepare_button.SetLabel('Cancel Session')
            self.view.prepare_button.Enable()
            self.view.add_sub_button.Disable()
            self.view.cal_but.Disable()
            self.view.start_button.SetLabel("Run Session")
            self.view.start_button.SetBackgroundColour((0,255,0))
            self.view.start_button.Enable()
            self.view.puffr_but.Enable()
            self.view.puffl_but.Enable()
            self.view.optoon_but.Enable()
            self.view.optooff_but.Enable()
            self.view.maniptog_but.Enable()
            self.view.levu_but.Disable()
            self.view.levd_but.Disable()
            self.view.rewardr_but.Enable()
            self.view.rewardl_but.Enable()
            self.view.pause_button.Disable()
            self.view.locklev_but.SetLabel('Lock')
            self.view.locklev_but.Enable()
            self.mp285_view.but_set_lick.Disable()
            self.mp285_view.but_set_home.Disable()
            self.mp285_view.but_set_stim.Disable()
        elif self.state == self.STATE_RUNNING:
            self.view.prepare_button.Disable()
            self.view.prepare_button.SetLabel('Prepare Session')
            self.view.add_sub_button.Disable()
            self.view.cal_but.Disable()
            self.view.start_button.Disable()
            self.view.puffr_but.Enable()
            self.view.puffl_but.Enable()
            self.view.levu_but.Enable()
            self.view.levd_but.Enable()
            self.view.rewardr_but.Enable()
            self.view.rewardl_but.Enable()
            self.view.optoon_but.Enable()
            self.view.optooff_but.Enable()
            self.view.maniptog_but.Enable()
            self.view.start_button.SetLabel('End Session')
            self.view.start_button.SetBackgroundColour((255,0,0))
            self.view.pause_button.Enable()
            self.view.locklev_but.Enable()
            self.mp285_view.but_set_lick.Disable()
            self.mp285_view.but_set_home.Disable()
            self.mp285_view.but_set_stim.Disable()
        elif self.state == self.STATE_KILLED_SESSION:
            self.view.start_button.SetLabel('Ending...')
            self.view.start_button.Disable()
            self.view.cal_but.Disable()
            self.view.puffr_but.Disable()
            self.view.puffl_but.Disable()
            self.view.levu_but.Disable()
            self.view.levd_but.Disable()
            self.view.rewardr_but.Disable()
            self.view.rewardl_but.Disable()
            self.view.optoon_but.Disable()
            self.view.optooff_but.Disable()
            self.view.maniptog_but.Disable()
            self.view.pause_button.Disable()
            self.view.locklev_but.Disable()
            self.mp285_view.but_set_lick.Disable()
            self.mp285_view.but_set_home.Disable()
            self.mp285_view.but_set_stim.Disable()
            if self.session.session_on:
                self.update_timer = wx.CallLater(self.REFRESH_INTERVAL, self.update_state)
            else:
                self.update_state(self.STATE_RUN_COMPLETE)
            
        elif self.state == self.STATE_RUN_COMPLETE:
            pos = get_mp285_home()
            self.mp285.goto(pos)
            self.view.SetTitle('Puffs Experiment Control')
            self.update_timer.Stop()
            self.view.start_button.SetLabel("Run Session")
            self.view.start_button.SetBackgroundColour((0,255,0))
            self.view.prepare_button.Enable()
            self.view.prepare_button.SetLabel('Prepare Session')
            self.view.add_sub_button.Enable()
            self.view.startlevel_box.SetValue('(level override)')
            self.view.cal_but.Enable()
            self.view.start_button.Disable()
            self.view.puffr_but.Disable()
            self.view.puffl_but.Disable()
            self.view.rewardr_but.Enable()
            self.view.rewardl_but.Enable()
            self.view.levu_but.Disable()
            self.view.levd_but.Disable()
            self.view.optoon_but.Disable()
            self.view.optooff_but.Disable()
            self.view.maniptog_but.Disable()
            self.view.pause_button.Disable()
            self.view.update_sub_choices(list_rewards())
            self.view.locklev_but.SetLabel('Lock')
            self.view.locklev_but.Disable()
            self.mp285_view.but_set_lick.Enable()
            self.mp285_view.but_set_home.Enable()
            self.mp285_view.but_set_stim.Enable()
            self.view.imaging_box.SetValue(False)
            self.view.manip_box.SetSelection(0)
            self.view.cond_box.SetSelection(0)
    
    def update(self):
        if (not self.session.session_on) and self.state == self.STATE_RUNNING:
            self.update_state(self.STATE_RUN_COMPLETE)
            return

        # checks
        if self.view.trial_n_widg.GetValue() == str(self.session.th.idx):
            new_trial_flag = False
        else:
            new_trial_flag = True

        # clocks
        self.view.session_runtime_widg.SetValue(pretty_time(self.session.session_runtime))
        self.view.trial_runtime_widg.SetValue(pretty_time(self.session.trial_runtime))

        # plots
        try:
            self.view.set_lick_data(self.session.ar.get_accum())
        except Queue.Empty:
            pass
        except:
            logging.error('Interface could not update lick info.')
            try:
                self.view.fig_lock.release()
            except:
                pass

        # movie 
        cam_frame = self.session.cam.get()
        self.view.set_cam(cam_frame)
        # commented out b/c causes lags
        #if self.last_cam_frame is not None and self.n_updates>60 and cam_frame is not None:
        #    cc = np.corrcoef(cam_frame, self.last_cam_frame)[0,1]
        #    if cc < 0.85:
        #        self.evt_resetcam(None)
        #self.last_cam_frame = cam_frame
        
        # trial
        if new_trial_flag:
            self.update_trial()

        # pauses
        if self.session.paused:
            self.view.pause_button.SetLabel('Unpause')
            self.view.pause_button.SetBackgroundColour((0,255,0))
            self.view.start_button.Disable()
        elif not self.session.paused:
            self.view.pause_button.SetLabel('Pause')
            self.view.pause_button.SetBackgroundColour((0,150,150))
            if self.session.session_on and not self.session.session_kill:
                self.view.start_button.Enable()
        
        self.n_updates += 1
        self.update_timer = wx.CallLater(self.REFRESH_INTERVAL, self.update)

    def update_trial(self):
        try:
            if self.session.th.idx < 0:
                return
            self.view.trial_n_widg.SetValue("%s (%s)"%(str(self.session.th.idx),str(self.session.th.valid_idx)))
            self.view.set_trial_data(self.session.th, self.session)
            self.view.rewarded_widg.SetValue(str(self.session.rewards_given))
            self.view.set_bias(self.session.th.biases)
            self.view.set_manip(manip_strs[self.session.th.manip])
            if self.session.th.idx > 0:
                self.view.set_history(self.session.th)
        except:
            logging.error('Interface could not update trial info.')

    ####### EVENTS ########
    def evt_prepare(self, evt):
        if self.state == self.STATE_PREPARED:
            self.mp285.saver = None
            self.session.end()
            self.update_state(self.STATE_NULL)

        else:
            sel_sub = self.view.sub_box.GetSelection()
            sel_cond = self.view.cond_box.GetSelection()
            sel_manip = self.view.manip_box.GetSelection()
            if wx.NOT_FOUND in [sel_sub,sel_cond,sel_manip]:
                dlg = wx.MessageDialog(self.view, message='Selections not made.', caption='Preparation not performed.', style=wx.OK)
                res = dlg.ShowModal()
                dlg.Destroy()
                return

            sub_name = self.view.sub_names[sel_sub]
            cond_name = self.cond_list[sel_cond]
            manip_name = self.manip_list[sel_manip]
            if manip_name != 'none':
                logging.info('Manipulation: {}'.format(manip_name))
            imaging = self.view.imaging_box.GetValue()
            startlevel = self.view.startlevel_box.GetValue()
            if startlevel == '(level override)':
                startlevel = -1
            try:
                startlevel = int(startlevel)
            except:
                startlevel = -1
                logging.info('Start level not understood. Using default.')

            sub = Subject(sub_name)
            #self.mp285.goto(sub.get_position('stim')) # session can handle this
            ph = ParamHandler(sub, condition=conditions[cond_name], manipulation=manipulations[manip_name], imaging=imaging, start_level=startlevel, position=self.mp285.get_pos().tolist())
            self.session = Session(ph.params, mp285=self.mp285, actuator=self.actuator)
            self.session.live_figure = (self.view.fig,self.view.fig_lock)
            self.mp285.saver = self.session.saver

            # tcpip communication
            if imaging:
                si_path = config.si_data_path+r'\\{}'.format(sub_name)
                seshname = self.session.name_as_str()
                dic = dict(path=si_path, name=seshname, idx=1)
                cont = True
                while cont:
                    suc = self.tcpip.send(dic)
                    if not suc:
                        dlg = wx.MessageDialog(self.view, caption='ScanImage preparation failed.', message='Try again?', style=wx.YES_NO)
                        res = dlg.ShowModal()
                        dlg.Destroy()
                        cont = res==wx.ID_YES
                        if cont:
                            self.evt_tcpip(None)
                    else:
                        cont = False

            self.view.setup_axlick()
            self.view.SetTitle('{} - {} - {}'.format(sub_name,cond_name,manip_name))

            self.update_state(self.STATE_PREPARED)
            self.update()

    def evt_mp285(self, evt):
        self.mp285_view.Show()
        self.mp285_view.SetFocus()
        self.mp285_view.Raise()
    def evt_mp285_close(self, evt):
        evt.Veto()
        self.mp285_view.Hide()
    def evt_mp285_set(self, evt, detail):

        pos = self.mp285.get_pos()

        if detail in ['stim','lick']:
            sel_sub = self.view.sub_box.GetSelection()
            if sel_sub == wx.NOT_FOUND:
                dlg = wx.MessageDialog(self.view, message='No subject selected.', caption='Position not set.', style=wx.OK)
                res = dlg.ShowModal()
                dlg.Destroy()
                return
            
            sub_name = self.view.sub_names[sel_sub]
            sub = Subject(sub_name)
            sub.set_position(pos, kind=detail)
        elif detail == 'home':
            set_mp285_home(pos)
            
    def evt_mp285_goto(self, evt, detail):

        if detail in ['stim','lick']:
            sel_sub = self.view.sub_box.GetSelection()
            if sel_sub == wx.NOT_FOUND:
                dlg = wx.MessageDialog(self.view, message='No subject selected.', caption='Position not set.', style=wx.OK)
                res = dlg.ShowModal()
                dlg.Destroy()
                return
            
            sub_name = self.view.sub_names[sel_sub]
            sub = Subject(sub_name)
            pos = sub.get_position(kind=detail)
            self.mp285.goto(pos)
        elif detail == 'home':
            pos = get_mp285_home()
            self.mp285.goto(pos)
            
    def evt_mp285_move(self, evt, detail):
        direc,mag,ax = detail
        change = np.zeros(3)
        spot = dict(x=0,y=1,z=2)[ax]
        sign = dict(up=1,down=-1)[direc]
        change[spot] += sign*float(mag)

        curpos = self.mp285.get_pos()
        pos = curpos + change
        self.mp285.goto(pos)

    def evt_resetcam(self, evt):
        if self.state != self.STATE_RUNNING:
            return
        self.session.cam.reset_cams()
        
    def evt_resetlact(self, evt):
        logging.info('Resetting actuator...')
        self.actuator.end()
        self.actuator = LActuator(**config.actuator_params)
        
    def evt_tcpip(self, evt):
        bi = wx.BusyInfo('Connecting TCPIP; click connect on remote machine...', self.view)
        suc = self.tcpip.reconnect()
        bi.Destroy()
        if not suc:
            dlg = wx.MessageDialog(self.view, caption='TCPIP reconnection failed.', message='TCPIP not active.', style=wx.OK)
            res = dlg.ShowModal()
            dlg.Destroy()
        else:
            logging.info('TCPIP connected.')

    def evt_onoff(self, evt):
        #if self.state!=self.STATE_RUNNING and self.state!=self.STATE_PREPARED,:
        #    dlg = wx.MessageDialog(self.view, message='', caption='No session prepared.', style=wx.OK)
        #    res = dlg.ShowModal()
        #    dlg.Destroy()
        #    return
        if self.state != self.STATE_RUNNING:
            self.update_state(self.STATE_RUNNING)
            self.run_th = threading.Thread(target=self.session.run)
            self.run_th.start()
        elif self.state == self.STATE_RUNNING:
            # store log in session data
            self.mp285.saver = None
            self.session.notes['stdout'] = self.view.redir_out.text_ctrl.GetValue()
            self.session.notes['stderr'] = self.view.redir_err.text_ctrl.GetValue()
            self.session.session_kill = True
            self.update_state(self.STATE_KILLED_SESSION)

    def evt_pause(self, evt):
        if not self.session.paused:
            self.session.pause(True)
            self.view.pause_button.SetLabel('Unpause')
            self.view.pause_button.SetBackgroundColour((0,255,0))
            self.view.start_button.Disable()
        elif self.session.paused:
            self.session.pause(False)
            self.view.pause_button.SetLabel('Pause')
            self.view.pause_button.SetBackgroundColour((0,100,200))
            self.view.start_button.Enable()

    def evt_close(self, evt):
        if self.state in [self.STATE_RUNNING]:
            dlg = wx.MessageDialog(self.view, message='End session before closing interface.', caption='Session is active.', style=wx.OK)
            res = dlg.ShowModal()
            dlg.Destroy()
            evt.Veto()
        elif self.state in [self.STATE_NULL, self.STATE_RUN_COMPLETE, self.STATE_PREPARED]:
            dlg = wx.MessageDialog(self.view, message="", caption="Exit Experiment?", style=wx.OK|wx.CANCEL)
            result = dlg.ShowModal()
            dlg.Destroy()
            if result == wx.ID_OK:
                if self.state == self.STATE_PREPARED:
                    self.session.end()
                    self.update_state(self.STATE_KILLED_SESSION)
                    while self.state != self.STATE_RUN_COMPLETE:
                        pass
                if self.update_timer is not None:
                    self.update_timer.Stop()
                self.mp285.end()
                self.actuator.end()
                self.tcpip.end()
                self.mp285_view.Destroy()
                self.view.Destroy()
            else:
                evt.Veto()

    def evt_addsub(self, evt):
        dlg = wx.TextEntryDialog(self.view, message='Enter new subject name:')
        ret = dlg.ShowModal()
        if ret == wx.ID_OK:
            self.view.add_sub(dlg.GetValue().strip().lower(), rewards=list_rewards())
        else:
            pass

    def give_reward(self, evt, side):
        if self.state in [self.STATE_RUNNING,self.STATE_PREPARED]:
            self.session.spout.go(side)
        else:
            give_reward(side)
        logging.info('Manual reward given.')
    def give_puff(self, evt, side):
        self.session.stimulator.go(side)
        logging.info('Manual puff given.')
    def change_level(self, evt, inc):
        self.session.th.change_level(inc)
    def calib(self,evt):
        if self.view.cal_but.GetLabel() == 'OPEN':
            open_valves()
            self.view.cal_but.SetLabel('CLOSE')
        elif self.view.cal_but.GetLabel() == 'CLOSE':
            close_valves()
            self.view.cal_but.SetLabel('OPEN')
        
    def locklev(self, evt):
        if self.session.th.level_locked:
            self.session.th.level_locked = False
            self.view.locklev_but.SetLabel('Lock')
            logging.info('Level unlocked.')
        elif not self.session.th.level_locked:
            self.session.th.level_locked = True
            self.view.locklev_but.SetLabel('Unlock')
            logging.info('Level locked.')
    def opto_on(self, evt):
        self.session.opto.set(1)
    def opto_off(self, evt):
        self.session.opto.set(0)
    def manip_toggle(self, evt):
        if self.session.th.force_manip is None: # Disable manip
            self.session.th.force_manip = default_manipulation
            self.view.maniptog_but.SetLabel('ENABLE')
            logging.info('Manipulation disabled.')
        elif self.session.th.force_manip is not None: # Enable manip
            self.session.th.force_manip = None
            self.view.maniptog_but.SetLabel('DISABLE')
            logging.info('Manipulation enabled.')
    def actuator_extend(self, evt):
        self.actuator.extend()
    def actuator_retract(self, evt):
        self.actuator.retract()
            
    def update_usrinput(self, evt):
        self.session.notes['notes'] = self.view.usrinput_box.GetValue()
        logging.info('Metadata updated.')