import random # 类实例化 hardware_info = Hardware() results_info = Results() if WORK_ON_RPI: # drawer_hard = MotorAction('托盘', [31, 33, 35, 37], [12, 16, 18, 22], 8000) # lifting_hard = MotorAction('抬升', [32, 36, 38, 40], [13, 15, 7, 11], 3200) drawer_hard = MotorAction('托盘', [31, 33, 35, 37], [12, 16, 18, 22], 6000) time.sleep(0.01) lifting_hard = MotorAction('抬升', [32, 36, 38, 40], [13, 15, 7, 11], 4000) # drawer_hard = MotorAction('托盘', [32, 36, 38, 40], [7, 11, 7, 11], 400) # lifting_hard = MotorAction('抬升', [31, 33, 35, 37], [7, 11, 7, 11], 400) weight_hard = WeightSensor('/dev/ttyAMA0') time.sleep(0.01) light_hard = Light(21) time.sleep(0.01) light_plate_hard = Light(23) time.sleep(0.01) fan_hard = Fan(29) printer_hard = Printer("/dev/ttyUSB0") class ActionName: PLATE_IN = ActionNameType('托盘收回') PLATE_OUT = ActionNameType('托盘弹出') PLATE_UP = ActionNameType('托盘复位') PLATE_DOWN = ActionNameType('托盘下降') WEIGHT = ActionNameType('称重/去皮') LIGHT_ON = ActionNameType('打开灯') LIGHT_OFF = ActionNameType('关闭灯')
def __init__(self, subj, session_params, cam_params=default_cam_params): self.params = self.DEFAULT_PARAMS self.cam_params = cam_params self.subj = subj self.name = time.strftime("%Y%m%d_%H%M%S") self.saver = Saver(self.subj, self.name) # kwargs self.params.update(session_params) # checks on kwargs assert self.params["min_isi"] > self.params["stim_duration"] # otherwise same-side stims can overlap if self.params["lick_rule_side"]: assert self.params[ "lick_rule_any" ] # if must lick correct side in lick phase, then must lick at all in lick phase # extensions of kwargs if not isinstance(self.params["lam"], list): self.params["lam"] = [self.params["lam"]] if not isinstance(self.params["n_trials"], list): self.params["n_trials"] = [self.params["n_trials"]] assert len(self.params["lam"]) == len(self.params["n_trials"]) self.params["subj_name"] = self.subj.name # self.params['phase_durations'][self.PHASE_STIM] = self.params['stim_phase_intro_duration'] + self.params['stim_phase_duration'] self.cam_params.update(dict(save_name=pjoin(self.subj.subj_dir, self.name))) if self.params["hints_on"] is True: self.params["hints_on"] = 1e6 # add them in self.__dict__.update(self.params) # hardware syncobj = multiprocessing.Value("d", 0) threading.Thread(target=update_sync, args=(syncobj,)).start() self.cam = PSEye(clock_sync_obj=syncobj, **self.cam_params) self.cam.start() self.lr = AnalogReader( saver=self.saver, lick_thresh=5.0, ports=["ai0", "ai1", "ai5", "ai6"], runtime_ports=[0, 1] ) sd = self.stim_duration if self.stim_duration_override: sd = self.stim_duration_override self.stimulator = Valve(saver=self.saver, ports=["port0/line0", "port0/line1"], name="stimulator", duration=sd) self.spout = Valve( saver=self.saver, ports=["port0/line2", "port0/line3"], name="spout", duration=self.reward_duration ) self.light = Light(saver=self.saver, port="ao0") self.speaker = Speaker(saver=self.saver) # trials init self.trials = self.generate_trials() # save session info self.saver.save_session(self) # runtime variables self.session_on = 0 self.session_complete = False self.session_kill = False self.trial_on = 0 self.trial_idx = -1 self.valid_trial_idx = 0 self.saving_trial_idx = 0 self.session_runtime = -1 self.trial_runtime = -1 self.trial_outcomes = [] self.trial_corrects = [] # for use in use_trials==False self.rewards_given = 0 self.paused = 0 self.holding = False self.percentages = [0.0, 0.0] # L/R self.side_ns = [0, 0] # L/R self.bias_correction_percentages = [0.0, 0.0] # L/R self.perc_valid = 0.0 self.iter_write_begin = now()
def __init__(self, session_params, mp285=None, actuator=None): self.params = self.DEFAULT_PARAMS # incorporate kwargs self.params.update(session_params) self.__dict__.update(self.params) self.verify_params() # sync self.sync_flag = multiprocessing.Value('b', False) self.sync_to_save = multiprocessing.Queue() # saver self.saver = Saver(self.subj, self.name, self, sync_flag=self.sync_flag) # hardware self.cam = PSEye(sync_flag=self.sync_flag, **self.cam_params) self.ar = AnalogReader(saver_obj_buffer=self.saver.buf, sync_flag=self.sync_flag, **self.ar_params) self.stimulator = Valve(saver=self.saver, name='stimulator', **self.stimulator_params) self.spout = Valve(saver=self.saver, name='spout', **self.spout_params) self.light = Light(saver=self.saver, **self.light_params) self.light.set(0) self.opto = Opto(saver=self.saver, **self.opto_params) self.speaker = Speaker(saver=self.saver) # mp285, actuator self.mp285 = mp285 self.actuator = actuator self.actuator.saver = self.saver self.mp285_go(self.position_stim) if self.retract_ports: self.actuator.retract() #self.mp285_go(self.position_stim) else: self.actuator.extend() #self.mp285_go(self.position_lick) # communication self.sic = SICommunicator(self.imaging) # trials self.th = TrialHandler(saver=self.saver, condition=self.condition, **self.trial_params) # runtime variables self.stdinerr = None self.notes = {} self.session_on = 0 self.session_complete = False self.session_kill = False self.session_runtime = -1 self.trial_runtime = -1 self.rewards_given = 0 self.paused = 0 self.holding = False self.current_phase = PHASE_INTRO self.live_figure = None # sync self.sync_flag.value = True #trigger all processes to get time self.sync_val = now() #get this process's time procs = dict(saver=self.saver, cam=self.cam.pseye, ar=self.ar) sync_vals = {o: procs[o].sync_val.value for o in procs} #collect all process times sync_vals['session'] = self.sync_val self.sync_to_save.put(sync_vals)
class Session(object): L, R, X = 0, 1, 2 COR, INCOR, EARLY, BOTH, NULL, KILLED, SKIPPED = 0, 1, 2, 3, 4, 5, 6 PHASE_INTRO, PHASE_STIM, PHASE_DELAY, PHASE_LICK, PHASE_REWARD, PHASE_ITI, PHASE_END = [0, 1, 2, 3, 4, 5, 6] DEFAULT_PARAMS = dict( n_trials=[100], # if a list, corresponds to list of lams lam=[ 0.5 ], # poisson lambda for one side, as fraction of n_total_stims. ex 0.9 means that on any given trial, the expected number of stims on one side will be 0.9*n_total_stims and the expected number of stims on the other side will be 0.1*n_total_stims. If a list, will be sequential trials with those params, numbers of trials specified by n_trials parameter. If any item in this list is a list, it means multiple shuffled trials equally distributed across those lams. # n_total_stims = 10., rate_sum=5.0, max_rate_frac=2.0, # max_n_stims = 20, #on one side stim_duration=0.050, stim_duration_override=False, stim_phase_duration=[5.0, 1.0], # mean, std enforce_stim_phase_duration=True, stim_phase_intro_duration=0.200, stim_phase_end_duration=0.050, min_isi=0.100, reward_duration=[0.010, 0.010], penalty_iti_frac=1.0, # fraction of ITI to add to normal ITI when trial was incorrect distribution_mode="poisson", phase_durations={ PHASE_INTRO: 1.0, PHASE_STIM: None, PHASE_DELAY: 1.0, PHASE_LICK: 4.0, PHASE_REWARD: 4.0, PHASE_ITI: 3.0, PHASE_END: 0.0, }, # PHASE_END must always have 0.0 duration iter_resolution=0.030, hold_rule=True, puffs_on=True, rewards_on=True, hints_on=False, # False, or n first trials to give hints, or True for all trials hint_interval=0.500, bias_correction=True, bias_correction_window=6, max_bias_correction=0.1, lick_rule_phase=True, # must not lick before the lick phase lick_rule_side=True, # must not lick incorrect side during the lick phase lick_rule_any=True, # must lick some port in lick phase to get reward multiple_decisions=False, # can make multiple attempts at correct side use_trials=True, # if false, trials are ignored. used for training trial_vid_freq=2, # movie for every n trials extra_bigdiff_trials=False, condition=-1, ) def __init__(self, subj, session_params, cam_params=default_cam_params): self.params = self.DEFAULT_PARAMS self.cam_params = cam_params self.subj = subj self.name = time.strftime("%Y%m%d_%H%M%S") self.saver = Saver(self.subj, self.name) # kwargs self.params.update(session_params) # checks on kwargs assert self.params["min_isi"] > self.params["stim_duration"] # otherwise same-side stims can overlap if self.params["lick_rule_side"]: assert self.params[ "lick_rule_any" ] # if must lick correct side in lick phase, then must lick at all in lick phase # extensions of kwargs if not isinstance(self.params["lam"], list): self.params["lam"] = [self.params["lam"]] if not isinstance(self.params["n_trials"], list): self.params["n_trials"] = [self.params["n_trials"]] assert len(self.params["lam"]) == len(self.params["n_trials"]) self.params["subj_name"] = self.subj.name # self.params['phase_durations'][self.PHASE_STIM] = self.params['stim_phase_intro_duration'] + self.params['stim_phase_duration'] self.cam_params.update(dict(save_name=pjoin(self.subj.subj_dir, self.name))) if self.params["hints_on"] is True: self.params["hints_on"] = 1e6 # add them in self.__dict__.update(self.params) # hardware syncobj = multiprocessing.Value("d", 0) threading.Thread(target=update_sync, args=(syncobj,)).start() self.cam = PSEye(clock_sync_obj=syncobj, **self.cam_params) self.cam.start() self.lr = AnalogReader( saver=self.saver, lick_thresh=5.0, ports=["ai0", "ai1", "ai5", "ai6"], runtime_ports=[0, 1] ) sd = self.stim_duration if self.stim_duration_override: sd = self.stim_duration_override self.stimulator = Valve(saver=self.saver, ports=["port0/line0", "port0/line1"], name="stimulator", duration=sd) self.spout = Valve( saver=self.saver, ports=["port0/line2", "port0/line3"], name="spout", duration=self.reward_duration ) self.light = Light(saver=self.saver, port="ao0") self.speaker = Speaker(saver=self.saver) # trials init self.trials = self.generate_trials() # save session info self.saver.save_session(self) # runtime variables self.session_on = 0 self.session_complete = False self.session_kill = False self.trial_on = 0 self.trial_idx = -1 self.valid_trial_idx = 0 self.saving_trial_idx = 0 self.session_runtime = -1 self.trial_runtime = -1 self.trial_outcomes = [] self.trial_corrects = [] # for use in use_trials==False self.rewards_given = 0 self.paused = 0 self.holding = False self.percentages = [0.0, 0.0] # L/R self.side_ns = [0, 0] # L/R self.bias_correction_percentages = [0.0, 0.0] # L/R self.perc_valid = 0.0 self.iter_write_begin = now() def generate_trials(self): timess = [] lamss = [] durs = [] for lam, n_trials in zip(self.lam, self.n_trials): for _ in xrange(n_trials): if isinstance(lam, list): lami = np.random.choice(lam) else: lami = lam lams = [lami, 1.0 - lami] lamss.append(lams) dur = np.random.normal(*self.stim_phase_duration) durs.append(dur) tt = self.generate_stim_times( lams=lams, dur=dur, intro_dur=self.stim_phase_intro_duration, min_isi=self.min_isi ) if self.extra_bigdiff_trials and 1.0 in lams and np.random.random() < 0.7: enforce_n = np.random.choice([11, 12, 13, 14, 15]) while max([len(i) for i in tt]) < enforce_n: tt = self.generate_stim_times( lams=lams, dur=dur, intro_dur=self.stim_phase_intro_duration, min_isi=self.min_isi ) timess.append(tt) trials = np.zeros( (len(timess)), dtype=[("times", vlen_array_dtype), ("correct", int), ("lams", float, 2), ("dur", float)] ) lr_idxer = [0, 1] for tidx, times, lams, d in zip(range(len(timess)), timess, lamss, durs): np.random.shuffle(lr_idxer) times = [times[lr_idxer[0]], times[lr_idxer[1]]] # left or right equally likely to correspond to lam[0] lams = [lams[lr_idxer[0]], lams[lr_idxer[1]]] # maintain alignment with lams lenL, lenR = [len(times[i]) for i in [self.L, self.R]] if lenL > lenR: correct = self.L elif lenR > lenL: correct = self.R elif lenR == lenL: raise Exception("Indeterminate trial included. This should not be possible!") trials[tidx] = (times, correct, lams, d) return np.array(trials) def generate_stim_times(self, lams, dur, intro_dur, min_isi, time_resolution=0.001): n_stim = [0, 0] while n_stim[0] == n_stim[1] or np.max(n_stim) / dur > self.rate_sum * self.max_rate_frac: if self.distribution_mode == "poisson": n_stim = np.random.poisson(self.rate_sum * dur * np.asarray(lams)) elif self.distribution_mode == "abs": n_stim = np.round(self.n_total_stims * np.asarray(lams)) t = np.arange(intro_dur, intro_dur + dur + time_resolution, time_resolution) def make(sz): times = np.append(0, np.sort(np.random.choice(t, size=sz, replace=False))) while len(times) > 1 and np.diff(times).min() < min_isi: times = np.append(0, np.sort(np.random.choice(t, size=sz, replace=False))) return times stim_times = map(make, n_stim) return np.array(stim_times) def get_cum_performance(self): cum = np.asarray(self.trial_outcomes) markers = cum.copy() if self.lick_rule_phase: ignore = [self.SKIPPED, self.EARLY, self.NULL, self.KILLED] else: ignore = [self.SKIPPED, self.NULL, self.KILLED] valid = np.array([c not in ignore for c in cum]).astype(bool) cum = cum == self.COR cum = [ np.mean([c for c, v in zip(cum[:i], valid[:i]) if v]) if np.any(valid[:i]) else 0.0 for i in xrange(1, len(cum) + 1) ] # cumulative return cum, markers, np.asarray(self.trial_corrects) def stimulate(self, trial): sides = [self.L, self.R] np.random.shuffle(sides) for side in sides: tr = trial["times"][side] si = self.stim_idx[side] if si >= len(tr): return dt_phase = now() - self.phase_start if dt_phase >= tr[si]: self.stimulator.go(side) self.stim_idx[side] += 1 if np.all(np.asarray(self.stim_idx) == np.asarray(map(len, trial["times"]))): self.stim_complete = True def to_phase(self, ph): self.phase_times[self.current_phase][1] = now() self.phase_times[ph][0] = now() self.current_phase = ph self.phase_start = now() self.hinted = False if ph == self.PHASE_END: # sanity check. should have been rewarded only if solely licked on correct side if ( self.lick_rule_side and self.lick_rule_phase and (not self.licked_early) and (not self.multiple_decisions) and self.use_trials ): assert bool(self.rewarded) == ( any(self.lick_phase_licks[self.trial["correct"]]) and (not any(self.lick_phase_licks[-self.trial["correct"] + 1])) ) if not self.rewarded: if self.use_trials: self.trial_corrects.append(self.trial["correct"]) else: self.trial_corrects.append(self.X) # determine trial outcome if not self.use_trials: if any(self.lick_phase_licks): outcome = self.COR else: outcome = self.INCOR elif self.use_trials: if self.rewarded and self.lick_rule_side and not self.multiple_decisions: outcome = self.COR elif self.rewarded and ((not self.lick_rule_side) or self.multiple_decisions): lpl_min = np.array([min(i) if len(i) else -1 for i in self.lickph_andon_licks]) if np.all(lpl_min == -1): outcome = self.NULL else: lpl_min[lpl_min == -1] = now() if np.argmin(lpl_min) == self.trial["correct"]: outcome = self.COR else: outcome = self.INCOR elif self.trial_kill: outcome = self.KILLED elif self.licked_early: outcome = self.EARLY # this BOTH logic no longer works bc i include reward phase licks in lickphaselicks. both will never show up, though it still can *rarely* be a cause for trial failure # elif (any(self.lick_phase_licks[self.L]) and any(self.lick_phase_licks[self.R])): # outcome = self.BOTH elif any(self.lick_phase_licks[-self.trial["correct"] + 1]): outcome = self.INCOR elif not any(self.lick_phase_licks): outcome = self.NULL self.trial_outcomes.append(outcome) def update_licked(self): l = self.lr.licked() tst = now() for idx, li in enumerate(l): self.licks[idx] += [ tst ] * li # represent not the number of licks but the number of times the LR class queried daq and found a positive licking signal if self.current_phase in [self.PHASE_LICK, self.PHASE_REWARD]: self.lickph_andon_licks[idx] += [tst] * li if self.current_phase == self.PHASE_LICK: self.lick_phase_licks[idx] += [tst] * li if self.hold_rule: if (not self.holding) and np.any(self.lr.holding): self.holding = True self.paused += 1 elif self.holding and not np.any(self.lr.holding): self.paused = max(self.paused - 1, 0) self.holding = False if self.holding: self.speaker.pop() def run_phase(self, ph): ph_dur = self.phase_durations[ph] # intended phase duration if ph == self.PHASE_STIM: ph_dur = ( self.stim_phase_intro_duration + self.trial["dur"] + self.stim_phase_end_duration ) # before dur was introduced: np.max(np.concatenate(self.trial['times']))+self.stim_phase_end_duration dt_phase = now() - self.phase_start self.session_runtime = now() - self.session_on self.trial_runtime = now() - self.trial_on self.update_licked() # special cases if ph == self.PHASE_ITI and not self.rewarded: ph_dur *= 1 + self.penalty_iti_frac if now() - self.iter_write_begin >= self.iter_resolution: iter_info = dict( trial=self.trial_idx, phase=ph, licks=np.array([len(i) for i in self.licks]), licked_early=self.licked_early, dt_phase=dt_phase, paused=self.paused, ) self.saver.write("iterations", iter_info) self.iter_write_begin = now() if self.paused and self.current_phase in [self.PHASE_INTRO, self.PHASE_STIM, self.PHASE_DELAY, self.PHASE_LICK]: self.trial_kill = True return if self.trial_kill and not self.current_phase == self.PHASE_ITI: self.to_phase(self.PHASE_ITI) return # Intro if ph == self.PHASE_INTRO: self.light.off() if not self.intro_signaled: self.speaker.intro() self.intro_signaled = True # comment out to give intro time with licks allowed? doesnt work, cause lick variable accumulates over trial if any(self.licks) and self.lick_rule_phase: self.licked_early = min(self.licks[0] + self.licks[1]) self.to_phase(self.PHASE_ITI) return if dt_phase >= ph_dur: self.to_phase(self.PHASE_STIM) return # Stim elif ph == self.PHASE_STIM: if any(self.licks) and self.lick_rule_phase: self.licked_early = min(self.licks[0] + self.licks[1]) self.to_phase(self.PHASE_ITI) return if dt_phase >= ph_dur: self.to_phase(self.PHASE_DELAY) return if self.puffs_on: self.stimulate(self.trial) if (not self.enforce_stim_phase_duration) and self.stim_complete: self.to_phase(self.PHASE_DELAY) return # Delay elif ph == self.PHASE_DELAY: if any(self.licks) and self.lick_rule_phase: self.licked_early = min(self.licks[0] + self.licks[1]) self.to_phase(self.PHASE_ITI) return if dt_phase >= ph_dur: self.to_phase(self.PHASE_LICK) return # Lick elif ph == self.PHASE_LICK: self.light.on() if ( self.hints_on and self.hints_on > self.trial_idx and (not self.hinted or (now() - self.hinted) > self.hint_interval) and self.use_trials ): self.stimulator.go(self.trial["correct"]) self.hinted = now() if not self.laser_signaled: self.speaker.laser() self.laser_signaled = True if not self.lick_rule_any: self.to_phase(self.PHASE_REWARD) return if any(self.lick_phase_licks) and not self.multiple_decisions: self.to_phase(self.PHASE_REWARD) return elif ( any(self.lick_phase_licks) and self.multiple_decisions and any(self.lick_phase_licks[self.trial["correct"]]) ): self.to_phase(self.PHASE_REWARD) return # if time is up, to reward phase if dt_phase >= ph_dur: self.to_phase(self.PHASE_REWARD) return # Reward elif ph == self.PHASE_REWARD: self.light.on() # probably redundant if ( self.lick_rule_side and any(self.lick_phase_licks[-self.trial["correct"] + 1]) and not self.rewarded and not self.multiple_decisions ): self.speaker.wrong() self.to_phase(self.PHASE_ITI) return # sanity check. cannot reach here if any incorrect licks, ensure that: if self.lick_rule_side and not self.multiple_decisions: assert not any(self.lick_phase_licks[-self.trial["correct"] + 1]) # if no licks at all, go straight to ITI if self.lick_rule_any and not any(self.lick_phase_licks): self.to_phase(self.PHASE_ITI) return # if allowed multiple choices but only licked wrong side if self.multiple_decisions and not any(self.lick_phase_licks[self.trial["correct"]]): self.to_phase(self.PHASE_ITI) return # sanity check. can only reach here if licked correct side only if self.lick_rule_any and self.lick_rule_side: assert any(self.lick_phase_licks[self.trial["correct"]]) # from this point on, it is assumed that rewarding should occur. if self.use_trials: rside = self.trial["correct"] else: rside = np.argmin([min(i) if len(i) else now() for i in self.lick_phase_licks]) if not self.corside_added: self.trial_corrects.append(rside) self.corside_added = True if ( self.hints_on and self.hints_on > self.trial_idx and (not self.hinted or (now() - self.hinted) > self.hint_interval) ): self.stimulator.go(rside) self.hinted = now() if self.rewards_on and not self.rewarded: self.spout.go(side=rside) self.rewarded = now() self.rewards_given += 1 if dt_phase >= ph_dur: self.to_phase(self.PHASE_ITI) # ITI elif ph == self.PHASE_ITI: self.light.off() if self.licked_early and self.lick_rule_phase and not self.error_signaled: self.speaker.error() self.error_signaled = True if dt_phase >= ph_dur: self.to_phase(self.PHASE_END) return def determine_skip(self): to = np.asarray(self.trial_outcomes) if len(to) == 0: return False corincor_idxs = np.where(np.logical_or(to == self.COR, to == self.INCOR))[0] all_val = np.sum([i in [self.COR, self.INCOR, self.EARLY, self.NULL, self.KILLED] for i in to]) if all_val != 0: self.perc_valid = float(len(corincor_idxs)) / all_val corincor_trials = self.trials[corincor_idxs] trcor = corincor_trials["correct"] subcor = to[corincor_idxs] == self.COR if np.sum(trcor == self.L) > 0: perc_l = np.mean(subcor[trcor == self.L]) self.percentages[self.L] = perc_l self.side_ns[self.L] = np.sum(trcor == self.L) if np.sum(trcor == self.R) > 0: perc_r = np.mean(subcor[trcor == self.R]) self.percentages[self.R] = perc_r self.side_ns[self.R] = np.sum(trcor == self.R) if ( np.sum(trcor == self.L) < self.bias_correction_window or np.sum(trcor == self.R) < self.bias_correction_window ): return False perc_l = np.mean(subcor[trcor == self.L][-self.bias_correction_window :]) perc_r = np.mean(subcor[trcor == self.R][-self.bias_correction_window :]) self.bias_correction_percentages = [perc_l, perc_r] if perc_l == perc_r: return False this_cor = self.trials[self.trial_idx]["correct"] if self.bias_correction_percentages[this_cor] < self.bias_correction_percentages[-1 + this_cor]: return False if min(self.bias_correction_percentages) == 0: pthresh = self.max_bias_correction else: pthresh = float(min(self.bias_correction_percentages)) / max(self.bias_correction_percentages) return np.random.random() > pthresh def next_trial(self): # init the trial self.skip_trial = False self.trial_idx += 1 if self.trial_idx > 1 and self.trial_outcomes[-1] in [self.COR, self.INCOR]: self.valid_trial_idx += 1 if self.trial_idx >= len(self.trials): self.session_complete = True return self.skip_trial = self.determine_skip() self.trial_on = now() self.trial = self.trials[self.trial_idx] self.current_phase = self.PHASE_INTRO self.stim_idx = [0, 0] # index of next stim, for [L,R] self.trial_start = now() self.phase_start = self.trial_start self.phase_times = [[-1, -1] for _ in self.phase_durations] _ = self.lr.licked() # to clear any residual signal self.licks = [[], []] self.lick_phase_licks = [[], []] self.lickph_andon_licks = [[], []] self.licked_early = False self.rewarded = False self.error_signaled = False self.laser_signaled = False self.intro_signaled = False self.stim_complete = False self.trial_kill = False self.hinted = False self.corside_added = False if self.skip_trial: self.cam.SAVING.value = 0 else: if self.saving_trial_idx % self.trial_vid_freq == 0: self.cam.SAVING.value = 1 else: self.cam.SAVING.value = 0 self.saving_trial_idx += 1 # logging.info('Starting trial %i.' %self.trial_idx) # run the trial loop if self.skip_trial: self.trial_outcomes.append(self.SKIPPED) self.trial_corrects.append(self.trial["correct"]) else: while self.current_phase != self.PHASE_END: self.run_phase(self.current_phase) # save trial info trial_info = dict( idx=self.trial_idx, ns=[len(tt) for tt in self.trial["times"]], start_time=self.trial_start, licksL=self.licks[self.L], licksR=self.licks[self.R], licked_early=self.licked_early, phase_times=self.phase_times, rewarded=self.rewarded, outcome=self.trial_outcomes[-1], hints=(self.hints_on and self.hints_on > self.trial_idx), end_time=now(), condition=self.condition, ) self.saver.write("trials", trial_info) def run(self): self.session_on = now() self.lr.begin_saving() while True: self.next_trial() if self.session_kill: logging.info("Session killed manually") self.paused = False break if self.session_complete: logging.info("Session complete") break self.session_on = False self.end() def end(self): to_end = [self.lr, self.stimulator, self.spout, self.light, self.cam, self.saver] for te in to_end: te.end() time.sleep(0.050) def get_code(self): py_files = [ pjoin(d, f) for d, _, fs in os.walk(os.getcwd()) for f in fs if f.endswith(".py") and not f.startswith("__") ] code = {} for pf in py_files: with open(pf, "r") as f: code[pf] = f.read() return json.dumps(code)
class Session(object): DEFAULT_PARAMS = {} def __init__(self, session_params, mp285=None, actuator=None): self.params = self.DEFAULT_PARAMS # incorporate kwargs self.params.update(session_params) self.__dict__.update(self.params) self.verify_params() # sync self.sync_flag = multiprocessing.Value('b', False) self.sync_to_save = multiprocessing.Queue() # saver self.saver = Saver(self.subj, self.name, self, sync_flag=self.sync_flag) # hardware self.cam = PSEye(sync_flag=self.sync_flag, **self.cam_params) self.ar = AnalogReader(saver_obj_buffer=self.saver.buf, sync_flag=self.sync_flag, **self.ar_params) self.stimulator = Valve(saver=self.saver, name='stimulator', **self.stimulator_params) self.spout = Valve(saver=self.saver, name='spout', **self.spout_params) self.light = Light(saver=self.saver, **self.light_params) self.light.set(0) self.opto = Opto(saver=self.saver, **self.opto_params) self.speaker = Speaker(saver=self.saver) # mp285, actuator self.mp285 = mp285 self.actuator = actuator self.actuator.saver = self.saver self.mp285_go(self.position_stim) if self.retract_ports: self.actuator.retract() #self.mp285_go(self.position_stim) else: self.actuator.extend() #self.mp285_go(self.position_lick) # communication self.sic = SICommunicator(self.imaging) # trials self.th = TrialHandler(saver=self.saver, condition=self.condition, **self.trial_params) # runtime variables self.stdinerr = None self.notes = {} self.session_on = 0 self.session_complete = False self.session_kill = False self.session_runtime = -1 self.trial_runtime = -1 self.rewards_given = 0 self.paused = 0 self.holding = False self.current_phase = PHASE_INTRO self.live_figure = None # sync self.sync_flag.value = True #trigger all processes to get time self.sync_val = now() #get this process's time procs = dict(saver=self.saver, cam=self.cam.pseye, ar=self.ar) sync_vals = {o: procs[o].sync_val.value for o in procs} #collect all process times sync_vals['session'] = self.sync_val self.sync_to_save.put(sync_vals) def name_as_str(self): return self.name.strftime('%Y%m%d%H%M%S') def verify_params(self): if self.name is None: self.name = pd.datetime.now() logging.info('Session: {}'.format(self.name)) self.cam_params.update( dict(save_name=pjoin(self.subj.subj_dir, self.name_as_str()))) def pause(self, val): if val is True: self.paused += 1 self.light.set(0) #self.sic.stop_acq() elif val is False: self.paused = max(self.paused - 1, 0) if self.paused == 0: self.sic.start_acq() def stimulate(self): n = len(self.th.trt) t0 = now() while self.current_phase == PHASE_STIM and self.stim_idx < n: dt = now() - t0 if dt >= self.th.trt['time'][self.stim_idx]: #logging.debug(dt-self.th.trt['time'][self.stim_idx]) self.stimulator.go(self.th.trt['side'][self.stim_idx]) self.stim_idx += 1 def to_phase(self, ph): # Write last phase if not (self.th.idx == 0 and ph == 0): phase_info = dict( trial=self.th.idx, phase=self.current_phase, start_time=self.phase_start, end_time=now(), ) self.saver.write('phases', phase_info) self.current_phase = ph # tell imaging self.sic.i2c('{}.{}'.format(self.th.idx, self.current_phase)) # determine duration if self.current_phase == PHASE_STIM: self.current_phase_duration = self.th.phase_dur elif self.current_phase == PHASE_DELAY: self.current_phase_duration = self.th.delay_dur else: self.current_phase_duration = self.phase_durations[ ph] #intended phase duration self.phase_start = now() self.last_hint = now() # Flush flag for camera: if ph in [PHASE_ITI, PHASE_END]: self.cam.flushing.value = True else: self.cam.flushing.value = False # Opto LED : based on constants defined in manipulations.py if self.th.manip == MANIP_NONE: self.opto.set(0) elif self.th.manip == MANIP_OPTO_STIMDELAY and self.current_phase in [ PHASE_STIM, PHASE_DELAY ]: self.opto.set(1) elif self.th.manip == MANIP_OPTO_LICK and self.current_phase == PHASE_LICK: self.opto.set(1) elif self.th.manip == MANIP_OPTO_REWARDITI and self.current_phase in [ PHASE_REWARD, PHASE_ITI ]: self.opto.set(1) else: self.opto.set(0) # Trial ending logic if ph == PHASE_END: lpl = self.licks[self.licks['phase'] == PHASE_LICK] #lick phase licks # sanity check. should have been rewarded only if solely licked on correct side if self.th.rule_side and self.th.rule_phase and ( not self.licked_early) and ( not self.th.rule_fault) and self.use_trials: assert bool(self.rewarded) == ( any(lpl['side'] == self.th.trial.side) and not any(lpl['side'] == -self.th.trial.side + 1)) # determine trial outcome if not self.use_trials: if any(lpl): outcome = COR else: outcome = INCOR elif not self.th.rule_any: lprl = self.licks[(self.licks['phase'] == PHASE_LICK) | (self.licks['phase'] == PHASE_REWARD)] if not any(lprl): outcome = NULL elif lprl[0]['side'] == self.th.trial.side: outcome = COR else: outcome = INCOR elif self.use_trials: if self.rewarded and self.th.rule_side and not self.th.rule_fault: outcome = COR elif self.rewarded and ((not self.th.rule_side) or self.th.rule_fault): if not any(lpl): outcome = NULL else: if lpl[0]['side'] == self.th.trial.side: outcome = COR else: outcome = INCOR elif self.trial_kill: outcome = KILLED elif self.licked_early: outcome = EARLY[self.licked_early['side']] elif any(lpl['side'] == -self.th.trial.side + 1): outcome = INCOR elif not any(lpl): outcome = NULL # Save trial info nLnR = self.stimulator.get_nlnr() if config.TESTING_MODE: fake_outcome = np.random.choice( [COR, INCOR, EARLY_L, EARLY_R, NULL, KILLED], p=[0.5, 0.3, 0.15 / 2, 0.15 / 2, 0.04, 0.01]) self.th.end_trial(fake_outcome, -0.1 * (fake_outcome == COR), nLnR) if fake_outcome == COR: self.rewards_given += 1 else: self.th.end_trial(outcome, self.rewarded, nLnR) def update_licked(self): l = self.ar.licked tst = now() for idx, li in enumerate(l): if li: try: self.licks[self.lick_idx] = (self.current_phase, tst, idx) self.lick_idx += 1 except: logging.error(self.licks) if self.lick_idx >= len(self.licks): self.licks = self.licks.resize(len(self.licks) + 2000) if self.hold_rule: if (not self.holding) and np.any(self.ar.holding): self.holding = True self.pause(True) elif self.holding and not np.any(self.ar.holding): self.pause(False) self.holding = False if self.holding: self.speaker.pop(wait=False) def run_phase(self): ph = self.current_phase ph_dur = self.current_phase_duration dt_phase = now() - self.phase_start self.session_runtime = now() - self.session_on self.trial_runtime = now() - self.th.trial.start self.update_licked() # special cases if ph == PHASE_ITI and not self.rewarded: ph_dur *= 1 + self.penalty_iti_frac if self.paused and self.current_phase in [ PHASE_INTRO, PHASE_STIM, PHASE_DELAY, PHASE_LICK ]: self.trial_kill = True return if self.trial_kill and not self.current_phase == PHASE_ITI: self.to_phase(PHASE_ITI) return # Intro if ph == PHASE_INTRO: self.light.set(0) if not self.intro_signaled: self.speaker.intro() self.intro_signaled = True if dt_phase >= ph_dur: self.to_phase(PHASE_STIM) return # Stim elif ph == PHASE_STIM: if self.th.rule_phase and any(self.licks['phase'] == PHASE_STIM): self.licked_early = self.licks[0] self.to_phase(PHASE_ITI) return if dt_phase >= ph_dur: self.to_phase(PHASE_DELAY) return if self.puffs_on and not self.stimulated: threading.Thread(target=self.stimulate).start() self.stimulated = True # Delay elif ph == PHASE_DELAY: if any(self.licks['phase'] == PHASE_DELAY) and self.th.rule_phase: self.licked_early = self.licks[0] self.to_phase(PHASE_ITI) return if dt_phase >= ph_dur: self.to_phase(PHASE_LICK) return if self.th.rule_hint_delay and now( ) - self.last_hint > self.next_hint_interval: self.stimulator.go(self.th.trial.side) self.last_hint = now() self.next_hint_interval = np.random.normal(*self.hint_interval) if self.next_hint_interval < 0: self.next_hint_interval = self.hint_interval[0] # Lick elif ph == PHASE_LICK: #if self.retract_ports and (self.mp285 is not None) and (not self.moved_ports): #self.mp285_go(self.position_lick) #self.moved_ports = True if self.retract_ports and (self.actuator is not None) and (not self.moved_ports): self.actuator.extend() self.moved_ports = True if self.th.rule_hint_delay and now( ) - self.last_hint > self.next_hint_interval: #and ((self.retract_ports and self.mp285.is_moving) or (not self.retract_ports)): self.stimulator.go(self.th.trial.side) self.last_hint = now() self.next_hint_interval = np.random.normal(*self.hint_interval) if self.next_hint_interval < 0: self.next_hint_interval = self.hint_interval[0] if 'light' in self.go_cue: self.light.set(1) if 'sound' in self.go_cue and not self.laser_signaled: self.speaker.laser() self.laser_signaled = True #if self.mp285.is_moving: # return # DO NOT PROCESS LICKS UNTIL MP285 REACHES DESTINATION if not self.th.rule_any: self.to_phase(PHASE_REWARD) return if any(self.licks['phase'] == PHASE_LICK) and not self.th.rule_fault: self.to_phase(PHASE_REWARD) return elif any(self.licks['phase'] == PHASE_LICK) and self.th.rule_fault and any(self.licks[ (self.licks['phase'] == PHASE_LICK) & (self.licks['side'] == self.th.trial.side)]): self.to_phase(PHASE_REWARD) return # if time is up, to reward phase if dt_phase >= ph_dur: self.to_phase(PHASE_REWARD) return # Reward elif ph == PHASE_REWARD: if 'light' in self.go_cue: self.light.set(1) #probably redundant if self.th.rule_side and any(self.licks[ (self.licks['phase'] == PHASE_LICK) & (self.licks['side'] == -self.th.trial.side + 1)]) and not self.rewarded and not self.th.rule_fault: # we arrived here after an incorrect lick if not self.wrong_signaled: self.speaker.wrong() self.wrong_signaled = True self.do_reward = False #self.to_phase(PHASE_ITI) # by commenting it out, the ports sit there even though there's no reward #return # sanity check. cannot reach here if any incorrect licks, ensure that: if self.th.rule_side and ( not self.th.rule_fault) and self.do_reward == True: assert (not any(self.licks[ (self.licks['phase'] == PHASE_LICK) & (self.licks['side'] == -self.th.trial.side + 1)])) # if no licks at all back in lick phase, go straight to ITI if self.th.rule_any and not any( self.licks[self.licks['phase'] == PHASE_LICK]): self.to_phase(PHASE_ITI) return # if allowed multiple choices but only licked wrong side by the time the lick phase had ended if self.th.rule_any and self.th.rule_fault and not any( self.licks[(self.licks['phase'] == PHASE_LICK) & (self.licks['side'] == self.th.trial.side)]): self.to_phase(PHASE_ITI) return # sanity check. can only reach here if licked correct side only if self.th.rule_any and self.th.rule_side and self.do_reward: assert any( self.licks[(self.licks['side'] == self.th.trial.side) & (self.licks['phase'] == PHASE_LICK)]) # from this point on, it is assumed that rewarding should occur if do_reward is True (otherwise would either have moved to ITI, or do_reward would now be False) if self.use_trials: rside = self.th.trial.side else: rside = (self.licks[self.licks['phase'] == PHASE_LICK].side)[0] if self.rewards_on and (not self.rewarded) and self.do_reward: self.spout.go(side=rside, scale=self.th.reward_scale) self.rewarded = now() self.rewards_given += 1 if self.th.rule_hint_reward and now( ) - self.last_hint > self.next_hint_interval: self.stimulator.go(self.th.trial.side) self.last_hint = now() self.next_hint_interval = np.random.normal(*self.hint_interval) if self.next_hint_interval < 0: self.next_hint_interval = self.hint_interval[0] if dt_phase >= ph_dur: self.to_phase(PHASE_ITI) # ITI elif ph == PHASE_ITI: #if self.retract_ports and (self.mp285 is not None) and (not self.returned_ports): # self.mp285_go(self.position_stim) # self.returned_ports = True if self.retract_ports and (self.actuator is not None) and ( not self.returned_ports): self.actuator.retract() self.returned_ports = True self.light.set(0) if any((self.licks['phase'] > PHASE_INTRO) & (self.licks['phase'] < PHASE_LICK) ) and self.th.rule_phase and not self.error_signaled: self.speaker.error() self.error_signaled = True if dt_phase >= ph_dur: if self.rewarded or ( self.motion_control and self.ar.moving) or ( not self.motion_control) or self.session_kill: self.to_phase(PHASE_END) else: pass return def next_trial(self): self.th.next_trial() # Phase reset self.to_phase(PHASE_INTRO) _ = self.ar.licked # to clear any residual signal # Check for mp285 adjustment if self.th.do_adjust_mp285 is not False: adj = self.th.do_adjust_mp285 logging.info('Adjusting MP285, moving to side {}'.format(adj)) self.mp285.nudge(adj) # Trial-specific runtime vars self.licks = np.zeros((2000, ), dtype=[('phase', int), ('ts', float), ('side', int)]) self.lick_idx = 0 # Event trackers self.stim_idx = 0 self.do_reward = True # until determined otherwise self.rewarded = False self.wrong_signaled = False self.error_signaled = False self.laser_signaled = False self.intro_signaled = False self.moved_ports = False self.returned_ports = False self.stimulated = False self.licked_early = False self.trial_kill = False self.last_hint = -1 self.next_hint_interval = self.hint_interval[0] while self.current_phase != PHASE_END: self.run_phase() # Return value indicating whether another trial is appropriate if self.session_kill: self.paused = False return False else: return True def mp285_go(self, pos): threading.Thread(target=self.mp285.goto, args=(pos, )).start() def email_update(self): p = self.th.history_glob.iloc[-10:].fillna(0) ntrials = len(p) tim = int((now() - self.session_on) / 60.) s = 'Subject: {}\nTime: {} mins\nN Trials: {}\nRewards: {}\nLevel: {}\nPerformance:\n{}\n'.format( self.subj.name, tim, ntrials, self.rewards_given, self.th.level, str(p)) email_alert(s, subject='Rig{}, {}min, {}, {}'.format( config.rig_id, tim, self.subj.name, self.name_as_str()), figure=self.live_figure) def run(self): try: self.session_on = now() self.ar.begin_saving() self.cam.begin_saving() self.sic.start_acq() cont = True last_email = now() - 960 while cont: if now() - last_email > (900): threading.Thread(target=self.email_update).start() last_email = now() cont = self.next_trial() self.sic.stop_acq() self.end() except: logging.error('Session has encountered an error!') email_alert('Session error!', subject='ERROR! Puffs Interface Alert') raise def end(self): self.actuator.saver = None to_end = [ self.ar, self.stimulator, self.spout, self.light, self.cam, self.opto, self.sic ] for te in to_end: try: te.end() except: warnings.warn('Failed to end one of the processes: {}'.format( str(te))) time.sleep(0.100) self.saver.end(notes=self.notes) self.session_on = False def get_code(self): py_files = [ pjoin(d, f) for d, _, fs in os.walk(os.getcwd()) for f in fs if f.endswith('.py') and not f.startswith('__') ] code = {} for pf in py_files: with open(pf, 'r') as f: code[pf] = f.read() return json.dumps(code)