class PlayWorld(object): def __init__(self, config=None): if config is None: self.config = {} execfile('play_config.py', self.config) else: self.config = config self.reward = None print self.config['pydaq'] if pydaq and self.config.setdefault('pydaq', True) is not None: self.reward = pydaq.GiveReward() self.reward_count = 0 # adjustment to speed so corresponds to gobananas task # 7 seconds to cross original environment # speed needs to be adjusted to both speed in original # environment and c_range of colors # self.speed = 0.05 * (self.c_range[1] - self.c_range[0]) # speed is own variable, so can be changed during training. self.speed = self.config['speed'] # need a multiplier to the joystick output to tolerable speed self.vel_base = 4 self.max_vel = [500, 500, 0] self.base = ShowBase() self.base.disableMouse() # self.base.setFrameRateMeter(True) # assume we are showing windows unless proven otherwise if self.config.get('win', True): # only need inputs if we have a window self.inputs = Inputs(self.base) props = WindowProperties() props.setCursorHidden(True) props.setForeground(True) print self.config.get('resolution') if self.config.get('resolution'): # main window props.set_size(int(self.config['resolution'][0]), int(self.config['resolution'][1])) # props.set_origin(1920, 0) props.set_origin(500, 0) else: props.set_size(600, 600) props.set_origin(400, 50) self.base.win.requestProperties(props) # print 'background color', self.base.getBackgroundColor() # field = self.base.loader.loadModel("../goBananas/models/play_space/field.bam") field = self.base.loader.loadModel("../goBananas/models/play_space/round_courtyard.bam") field.setPos(0, 0, 0) field.reparent_to(self.base.render) field_node_path = field.find('**/+CollisionNode') field_node_path.node().setIntoCollideMask(0) sky = self.base.loader.loadModel("../goBananas/models/sky/sky_kahana2.bam") sky.setPos(0, 0, 0) sky.setScale(1.6) sky.reparentTo(self.base.render) windmill = self.base.loader.loadModel("../goBananas/models/windmill/windmill.bam") windmill.setPos(-10, 30, -1) windmill.setScale(0.03) windmill.reparentTo(self.base.render) # mountain = self.base.loader.loadModel("../goBananas/models/mountain/mountain.bam") # mountain.setScale(0.0005) # mountain.setPos(10, 30, -0.5) # create the avatar self.avatar = NodePath(ActorNode("avatar")) self.avatar.reparentTo(self.base.render) self.avatar.setPos(0, 0, 1) self.avatar.setScale(0.5) pl = self.base.cam.node().getLens() pl.setFov(60) self.base.cam.node().setLens(pl) self.base.camera.reparentTo(self.avatar) # initialize task variables self.frame_task = None self.started_game = None self.showed_match = None self.gave_reward = None # initialize and start the game self.set_next_trial() # print 'end init' def start_loop(self): # need to get new match self.start_play() def start_play(self): print 'start play' # log this # print self.base.render.ls() self.frame_task = self.base.taskMgr.add(self.game_loop, "game_loop") self.frame_task.last = 0 # initiate task time of the last frame def game_loop(self, task): dt = task.time - task.last task.last = task.time velocity = self.inputs.poll_inputs(LVector3(0)) self.move_avatar(dt, velocity) return task.cont def reward_loop(self, task): self.reward_count += 1 if self.reward_count <= self.config['num_beeps']: if self.reward: # log this print 'give a bloody reward already' self.reward.pumpOut() print 'give reward' return task.again else: self.end_loop() return task.done def move_avatar(self, dt, velocity): # print 'velocity', self.velocity self.avatar.setH(self.avatar.getH() - velocity[0] * 1.1) move = LVector3(0, velocity[1], 0) self.avatar.setPos(self.avatar, move * dt * self.vel_base) def give_reward(self): # clear the background self.base.setBackgroundColor(0.41, 0.41, 0.41) print 'give first reward' self.reward_count = 1 if self.reward: # log this self.reward.pumpOut() self.gave_reward = self.base.taskMgr.doMethodLater(self.config['pump_delay'], self.reward_loop, 'reward_loop') def end_loop(self): print 'end loop' # clear avatar map # if there is a match set, return to center of color gradient, # set new match, if applicable self.set_next_trial() def set_next_trial(self): print 'set next trial' # move avatar back to beginning position, only matters for # showing card for next color match # self.avatar.set_pos(-10, -10, 2) # start the game self.start_loop() def check_key_map(self): if self.config['colors'][0]: if self.inputs.key_map['r']: self.config['match_direction'] = ['right'] elif self.inputs.key_map['r'] is not None: self.config['match_direction'] = ['left'] elif self.config['colors'][1]: if self.inputs.key_map['f']: self.config['match_direction'] = ['front'] elif self.inputs.key_map['f'] is not None: self.config['match_direction'] = ['back'] def setup_display2(self, display_node): print 'setup display2' props = WindowProperties() props.set_cursor_hidden(True) props.set_foreground(False) if self.config.get('resolution'): props.setSize(700, 700) props.setOrigin(-int(self.config['resolution'][0] - 5), 5) else: props.setSize(300, 300) props.setOrigin(10, 10) window2 = self.base.openWindow(props=props, aspectRatio=1) lens = OrthographicLens() lens.set_film_size(2, 2) lens.setNearFar(-100, 100) self.render2d = NodePath('render2d') self.render2d.attach_new_node(display_node) camera2d = self.base.makeCamera(window2) camera2d.setPos(0, -10, 0) camera2d.node().setLens(lens) camera2d.reparentTo(self.render2d)
class ColorWorld(object): def __init__(self, config=None): # keep track of velocity, this allows me to counteract joystick with keyboard self.velocity = LVector3(0) if config is None: self.config = {} execfile('config.py', self.config) else: self.config = config self.reward = None if pydaq: self.reward = pydaq.GiveReward() self.reward_count = 0 # self.color_map always corresponds to (r, g, b) # does not change during game, each game uses a particular color space self.color_dict = square.make_color_map(self.config['colors']) # sets the range of colors for this map self.c_range = self.config['c_range'] # color variables (make dictionary?) # color_list is set in beginning, and then after that this is only # called again for non-random (training) self.color_list = square.set_start_position_colors(self.config) self.color_match = [0, 0, 0] self.color_tolerance = [] self.last_avt, self.avt_factor = square.translate_color_map( self.config, self.color_dict, self.color_list) print 'starting avt position', self.last_avt print 'map avatar factor', self.avt_factor self.random = True if self.config.get('match_direction'): self.random = False # adjustment to speed so corresponds to gobananas task # 7 seconds to cross original environment # speed needs to be adjusted to both speed in original # environment and c_range of colors # self.speed = 0.05 * (self.c_range[1] - self.c_range[0]) # speed is own variable, so can be changed during training. self.speed = self.config['speed'] # map avatar variables self.render2d = None self.match_square = None self.map_avt_node = [] # need a multiplier to the joystick output to tolerable speed self.vel_base = 3 self.max_vel = [500, 500, 0] self.card = None self.base = ShowBase() self.base.disableMouse() # assume we are showing windows unless proven otherwise if self.config.get('win', True): # only need inputs if we have a window self.inputs = Inputs(self.base) props = WindowProperties() props.setCursorHidden(True) props.setForeground(True) print self.config.get('resolution') if self.config.get('resolution'): props.set_size(int(self.config['resolution'][0]), int(self.config['resolution'][1])) props.set_origin(0, 0) else: props.set_size(600, 600) props.set_origin(400, 50) self.base.win.requestProperties(props) # print self.base.win.get_size() # setup color map on second window sq_node = square.setup_square(self.config) self.setup_display2(sq_node) # print 'background color', self.base.getBackgroundColor() # create the avatar self.avatar = NodePath(ActorNode("avatar")) self.avatar.reparentTo(self.base.render) self.avatar.setH(self.base.camera.getH()) self.base.camera.reparentTo(self.avatar) self.base.camera.setPos(0, 0, 0) # initialize task variables self.frame_task = None self.started_game = None self.showed_match = None self.gave_reward = None # initialize and start the game self.set_next_trial() # print 'end init' def start_loop(self): # need to get new match print 'start loop' self.started_game = self.base.taskMgr.doMethodLater( 5, self.start_play, 'start_play') self.showed_match = self.base.taskMgr.add(self.show_match_sample, 'match_image') # Task methods def show_match_sample(self, task): print 'show match sample' print self.color_match[:] # match_image.fill(*self.color_match[:]) card = CardMaker('card') color_match = self.color_match[:] # add alpha channel color_match.append(1) print color_match card.set_color(*color_match[:]) card.set_frame(-12, -8, 0, 4) # log this self.card = self.base.render.attach_new_node(card.generate()) return task.done def start_play(self, task): print 'start play' # log this self.base.taskMgr.remove('match_image') self.card.removeNode() # print self.base.render.ls() self.frame_task = self.base.taskMgr.add(self.game_loop, "game_loop") self.frame_task.last = 0 # initiate task time of the last frame # log this self.base.setBackgroundColor(self.color_list[:]) return task.done def game_loop(self, task): dt = task.time - task.last task.last = task.time self.velocity = self.inputs.poll_inputs(self.velocity) move = self.move_avatar(dt) stop = self.change_background(move) self.move_map_avatar(move, stop) match = self.check_color_match() if match: self.give_reward() return task.done return task.cont def reward_loop(self, task): self.reward_count += 1 if self.reward_count <= self.config['num_beeps']: if self.reward: # log this print 'give a bloody reward already' self.reward.pumpOut() print 'give reward' return task.again else: self.end_loop() return task.done def move_avatar(self, dt): # print 'velocity', self.velocity # this makes for smooth (correct speed) diagonal movement # print 'velocity', self.velocity magnitude = max(abs(self.velocity[0]), abs(self.velocity[1])) move = None if self.velocity.normalize(): # go left in increasing amount # print 'dt', dt # print 'normalized' # print 'velocity', self.velocity # print 'magnitude', magnitude self.velocity *= magnitude # print 'velocity', self.velocity # this makes for smooth movement move = self.velocity * self.vel_base * dt # print move self.avatar.setFluidPos(self.avatar, move) return move def change_background(self, move): stop = [True, True, True] if move: # print move move *= self.speed for i in range(3): value = self.color_dict[i] if value is not None: stop[i] = False # keys correspond to x,y,z # values correspond to r,g,b if i == 2: # z axis is treated differently # need to work on this. z should # be at min when both x and y are at max # taking the average is not quite right... z_move = (move[0] + move[1]) / 2 # print z_move self.color_list[value] -= z_move else: self.color_list[value] += move[i] if self.color_list[value] < self.c_range[0]: self.color_list[value] = self.c_range[0] stop[i] = True elif self.color_list[value] > self.c_range[1]: self.color_list[value] = self.c_range[1] stop[i] = True # log this self.base.setBackgroundColor(self.color_list[:]) # print self.base.getBackgroundColor() return stop def move_map_avatar(self, move, stop): # print move # avatar is mapped assuming c_range of 0.5. What do I need to # change to use a different c_range? c_range of one is twice # the if move: avt = LineSegs() avt.setThickness(1) avt.setColor(1, 1, 1) # print 'last', self.last_avt avt.move_to(self.last_avt[0], -5, self.last_avt[1]) # print 'move', move new_move = [ i + (j * self.avt_factor) for i, j in zip(self.last_avt, move) ] # new_move = [i + j for i, j in zip(self.last_avt, move)] # would it be better to have a local stop condition? if stop[0]: new_move[0] = self.last_avt[0] # print 'stop x', self.last_avt[0] if stop[1]: new_move[1] = self.last_avt[1] # print 'stop y', self.last_avt[1] # print 'new', new_move self.last_avt = [new_move[0], new_move[1]] avt.draw_to(new_move[0], -5, new_move[1]) self.map_avt_node.append( self.render2d.attach_new_node(avt.create())) # print self.map_avt_node[-1] # can't let too many nodes pile up if len(self.map_avt_node) > 299: # removing the node does not remove the object from the list for i, j in enumerate(self.map_avt_node): j.removeNode() if i > 49: break del self.map_avt_node[0:50] def check_color_match(self): # print 'match this', self.color_tolerance # print self.color_list check_color = [ j[0] < self.color_list[i] < j[1] for i, j in enumerate(self.color_tolerance) ] # print check_color if all(check_color): return True else: return False def give_reward(self): # clear the background self.base.setBackgroundColor(0.41, 0.41, 0.41) print 'give first reward' self.reward_count = 1 if self.reward: # log this self.reward.pumpOut() self.gave_reward = self.base.taskMgr.doMethodLater( self.config['pump_delay'], self.reward_loop, 'reward_loop') def end_loop(self): print 'end loop' # clear avatar map self.clear_avatar_map() # if there is a match set, return to center of color gradient, # set new match, if applicable self.set_next_trial() def clear_avatar_map(self): for i, j in enumerate(self.map_avt_node): j.removeNode() self.map_avt_node = [] def plot_match_square(self, corners): print 'plot match square' print corners match = LineSegs() match.setThickness(1.5) match.setColor(0, 0, 0) match.move_to(corners[0][0], -5, corners[1][0]) match.draw_to(corners[0][1], -5, corners[1][0]) match.draw_to(corners[0][1], -5, corners[1][1]) match.draw_to(corners[0][0], -5, corners[1][1]) match.draw_to(corners[0][0], -5, corners[1][0]) # print self.render2d self.match_square = self.render2d.attach_new_node(match.create()) def create_avatar_map_match_square(self, config=None): print 'make new square for map' if config is not None: config_dict = config else: config_dict = self.config # create square on avatar map for new color match map_color_match, factor = square.translate_color_map( config_dict, self.color_dict, self.color_match) tolerance = config_dict['tolerance'] * factor map_color_tolerance = [(i - tolerance, i + tolerance) for i in map_color_match] print map_color_tolerance if self.render2d: if self.match_square: self.match_square.removeNode() self.plot_match_square(map_color_tolerance) def set_next_trial(self): print 'set next trial' # move avatar back to beginning position, only matters for # showing card for next color match self.avatar.set_pos(-10, -10, 2) # set color_list with starting color # if random, won't use this again, but for manual, will # return to center # need to update self.config to new direction, if there is one if self.config.get('match_direction'): self.check_key_map() # return to center, otherwise random will start where you left off self.color_list = square.set_start_position_colors(self.config) # starting position for map avatar, just translate new color_list self.last_avt, self.avt_factor = square.translate_color_map( self.config, self.color_dict, self.color_list) print 'start color', self.color_list print self.color_dict # again need to update self.config for match if using keys self.color_match = square.set_match_colors(self.config, self.color_dict) # sets the tolerance for how close to a color for reward self.color_tolerance = [(i - self.config['tolerance'], i + self.config['tolerance']) for i in self.color_match] print 'color match', self.color_match print 'color tolerance', self.color_tolerance self.create_avatar_map_match_square(self.config) # start the game self.start_loop() def check_key_map(self): if self.config['colors'][0]: if self.inputs.key_map['r']: self.config['match_direction'] = ['right'] elif self.inputs.key_map['r'] is not None: self.config['match_direction'] = ['left'] elif self.config['colors'][1]: if self.inputs.key_map['f']: self.config['match_direction'] = ['front'] elif self.inputs.key_map['f'] is not None: self.config['match_direction'] = ['back'] def setup_display2(self, display_node): print 'setup display2' props = WindowProperties() props.set_cursor_hidden(True) props.set_foreground(False) if self.config.get('resolution'): props.setSize(700, 700) props.setOrigin(-int(self.config['resolution'][0] - 5), 5) else: props.setSize(300, 300) props.setOrigin(10, 10) window2 = self.base.openWindow(props=props, aspectRatio=1) lens = OrthographicLens() lens.set_film_size(2, 2) lens.setNearFar(-100, 100) self.render2d = NodePath('render2d') self.render2d.attach_new_node(display_node) camera2d = self.base.makeCamera(window2) camera2d.setPos(0, -10, 0) camera2d.node().setLens(lens) camera2d.reparentTo(self.render2d)
class RhomdosRender: def __init__(self, game, duration=None, storage_path="storage/3d/frame", fps=30, as_gif=False, gif_name=None, size=(480, 480)): self.game = game self.duration = duration self.storage_path = storage_path self.fps = fps self.as_gif = as_gif self.gif_name = gif_name self.base = ShowBase(windowType='none') self.base.openWindow(type=('offscreen' if as_gif else 'onscreen'), size=size) depth, height, width = game.shape self.rhomdos = [] for z in range(depth): self.rhomdos.append([]) for y in range(height): self.rhomdos[-1].append([]) for x in range(width): rr = RhomdoRender((z, y, x), game.shape) render.attachNewNode(rr.geomnode) self.rhomdos[-1][-1].append(rr) plight = PointLight('plight') plight.setColor(VBase4(1, 1, 1, 1)) plight.setAttenuation((0, 0, .0005)) self.plnp = render.attachNewNode(plight) self.plnp.setPos(0, 0, (game.shape[0] * SR2) + 40) render.setLight(self.plnp) if self.as_gif: self.base.movie(self.storage_path, duration=self.duration, fps=self.fps) self.base.taskMgr.add(self.spinCameraTask, "SpinCameraTask") self.base.taskMgr.add(self.updateClock, "UpdateClock") self.frame = 0 self.game_step = 0 def spinCameraTask(self, task): angleDegrees = task.time * 40.0 angleRadians = angleDegrees * (pi / 180.0) self.base.camera.setPos(60 * sin(angleRadians), -60 * cos(angleRadians), 32) self.base.camera.setHpr(angleDegrees, -30, 0) return Task.cont def run(self): if self.as_gif: for i in tqdm(list(range(self.duration * self.fps))): self.base.taskMgr.step() self.generate_gif() else: while True: self.base.taskMgr.step() def updateClock(self, task): self.frame = task.time adjusted_time = task.time * 8 if ceil(adjusted_time) > self.game_step: self.updateRhomdos() self.game_step = ceil(adjusted_time) return Task.cont def generate_gif(self): with imageio.get_writer(self.gif_name, mode='I') as writer: for i in tqdm(list(range(3, self.duration * self.fps))): filename = f"{self.storage_path}_{str(i).rjust(4, '0')}.png" writer.append_data(imageio.imread(filename)) os.remove(filename) def updateRhomdos(self): self.game.advance() depth, height, width = self.game.shape for z in range(depth): for y in range(height): for x in range(width): rr = self.rhomdos[z][y][x] if self.game.grid[-1, z, y, x, 0] == 1: rr.show() else: rr.hide()
class PlayWorld(object): def __init__(self, config=None): if config is None: self.config = {} execfile('play_config.py', self.config) else: self.config = config self.reward = None print self.config['pydaq'] if pydaq and self.config.setdefault('pydaq', True) is not None: self.reward = pydaq.GiveReward() self.reward_count = 0 # adjustment to speed so corresponds to gobananas task # 7 seconds to cross original environment # speed needs to be adjusted to both speed in original # environment and c_range of colors # self.speed = 0.05 * (self.c_range[1] - self.c_range[0]) # speed is own variable, so can be changed during training. self.speed = self.config['speed'] # need a multiplier to the joystick output to tolerable speed self.vel_base = 4 self.max_vel = [500, 500, 0] self.base = ShowBase() self.base.disableMouse() # self.base.setFrameRateMeter(True) # assume we are showing windows unless proven otherwise if self.config.get('win', True): # only need inputs if we have a window self.inputs = Inputs(self.base) props = WindowProperties() props.setCursorHidden(True) props.setForeground(True) print self.config.get('resolution') if self.config.get('resolution'): # main window props.set_size(int(self.config['resolution'][0]), int(self.config['resolution'][1])) # props.set_origin(1920, 0) props.set_origin(500, 0) else: props.set_size(600, 600) props.set_origin(400, 50) self.base.win.requestProperties(props) # print 'background color', self.base.getBackgroundColor() # field = self.base.loader.loadModel("../goBananas/models/play_space/field.bam") field = self.base.loader.loadModel( "../goBananas/models/play_space/round_courtyard.bam") field.setPos(0, 0, 0) field.reparent_to(self.base.render) field_node_path = field.find('**/+CollisionNode') field_node_path.node().setIntoCollideMask(0) sky = self.base.loader.loadModel( "../goBananas/models/sky/sky_kahana2.bam") sky.setPos(0, 0, 0) sky.setScale(1.6) sky.reparentTo(self.base.render) windmill = self.base.loader.loadModel( "../goBananas/models/windmill/windmill.bam") windmill.setPos(-10, 30, -1) windmill.setScale(0.03) windmill.reparentTo(self.base.render) # mountain = self.base.loader.loadModel("../goBananas/models/mountain/mountain.bam") # mountain.setScale(0.0005) # mountain.setPos(10, 30, -0.5) # create the avatar self.avatar = NodePath(ActorNode("avatar")) self.avatar.reparentTo(self.base.render) self.avatar.setPos(0, 0, 1) self.avatar.setScale(0.5) pl = self.base.cam.node().getLens() pl.setFov(60) self.base.cam.node().setLens(pl) self.base.camera.reparentTo(self.avatar) # initialize task variables self.frame_task = None self.started_game = None self.showed_match = None self.gave_reward = None # initialize and start the game self.set_next_trial() # print 'end init' def start_loop(self): # need to get new match self.start_play() def start_play(self): print 'start play' # log this # print self.base.render.ls() self.frame_task = self.base.taskMgr.add(self.game_loop, "game_loop") self.frame_task.last = 0 # initiate task time of the last frame def game_loop(self, task): dt = task.time - task.last task.last = task.time velocity = self.inputs.poll_inputs(LVector3(0)) self.move_avatar(dt, velocity) return task.cont def reward_loop(self, task): self.reward_count += 1 if self.reward_count <= self.config['num_beeps']: if self.reward: # log this print 'give a bloody reward already' self.reward.pumpOut() print 'give reward' return task.again else: self.end_loop() return task.done def move_avatar(self, dt, velocity): # print 'velocity', self.velocity self.avatar.setH(self.avatar.getH() - velocity[0] * 1.1) move = LVector3(0, velocity[1], 0) self.avatar.setPos(self.avatar, move * dt * self.vel_base) def give_reward(self): # clear the background self.base.setBackgroundColor(0.41, 0.41, 0.41) print 'give first reward' self.reward_count = 1 if self.reward: # log this self.reward.pumpOut() self.gave_reward = self.base.taskMgr.doMethodLater( self.config['pump_delay'], self.reward_loop, 'reward_loop') def end_loop(self): print 'end loop' # clear avatar map # if there is a match set, return to center of color gradient, # set new match, if applicable self.set_next_trial() def set_next_trial(self): print 'set next trial' # move avatar back to beginning position, only matters for # showing card for next color match # self.avatar.set_pos(-10, -10, 2) # start the game self.start_loop() def check_key_map(self): if self.config['colors'][0]: if self.inputs.key_map['r']: self.config['match_direction'] = ['right'] elif self.inputs.key_map['r'] is not None: self.config['match_direction'] = ['left'] elif self.config['colors'][1]: if self.inputs.key_map['f']: self.config['match_direction'] = ['front'] elif self.inputs.key_map['f'] is not None: self.config['match_direction'] = ['back'] def setup_display2(self, display_node): print 'setup display2' props = WindowProperties() props.set_cursor_hidden(True) props.set_foreground(False) if self.config.get('resolution'): props.setSize(700, 700) props.setOrigin(-int(self.config['resolution'][0] - 5), 5) else: props.setSize(300, 300) props.setOrigin(10, 10) window2 = self.base.openWindow(props=props, aspectRatio=1) lens = OrthographicLens() lens.set_film_size(2, 2) lens.setNearFar(-100, 100) self.render2d = NodePath('render2d') self.render2d.attach_new_node(display_node) camera2d = self.base.makeCamera(window2) camera2d.setPos(0, -10, 0) camera2d.node().setLens(lens) camera2d.reparentTo(self.render2d)
# create a light ambient = ambient_light((.3, .3, .3, 1)) ambient = base.render.attach_new_node(ambient) base.render.set_light(ambient) # create another light directional = directional_light((1, 1, 1, 1), (-1, -2, -3)) directional = base.render.attach_new_node(directional) base.render.set_light(directional) for filename, tile_list in tile_lists.items(): cols = 8 rows = max(tile.tile_id for tile in tile_list.values()) // cols + 1 win = base.openWindow(type='offscreen', size=(cols * width, rows * height), makeCamera=True) level = base.render.attachNewNode("level") for tile in tile_list.values(): placeholder = level.attachNewNode("tile-placeholder") col = tile.tile_id % cols row = tile.tile_id // cols placeholder.setPos((cols - col - 1) + 0.5, 0, -((row + 1.0) * aspect - 0.5) * 2**0.5 - 0.1) tile.node.instanceTo(placeholder) if isinstance(tile, Train): train_node = tile.train.instanceTo(placeholder) lens.setFilmSize(cols, rows * aspect) base.camNode.setLens(lens) base.camera.set_pos(cols / 2, 8, -rows * 2**0.5 * aspect / 2 + 8)
class ColorWorld(object): def __init__(self, config=None): # keep track of velocity, this allows me to counteract joystick with keyboard self.velocity = LVector3(0) if config is None: self.config = {} execfile('config.py', self.config) else: self.config = config self.reward = None if pydaq: self.reward = pydaq.GiveReward() self.reward_count = 0 # self.color_map always corresponds to (r, g, b) # does not change during game, each game uses a particular color space self.color_dict = square.make_color_map(self.config['colors']) # sets the range of colors for this map self.c_range = self.config['c_range'] # color variables (make dictionary?) # color_list is set in beginning, and then after that this is only # called again for non-random (training) self.color_list = square.set_start_position_colors(self.config) self.color_match = [0, 0, 0] self.color_tolerance = [] self.last_avt, self.avt_factor = square.translate_color_map(self.config, self.color_dict, self.color_list) print 'starting avt position', self.last_avt print 'map avatar factor', self.avt_factor self.random = True if self.config.get('match_direction'): self.random = False # adjustment to speed so corresponds to gobananas task # 7 seconds to cross original environment # speed needs to be adjusted to both speed in original # environment and c_range of colors # self.speed = 0.05 * (self.c_range[1] - self.c_range[0]) # speed is own variable, so can be changed during training. self.speed = self.config['speed'] # map avatar variables self.render2d = None self.match_square = None self.map_avt_node = [] # need a multiplier to the joystick output to tolerable speed self.vel_base = 3 self.max_vel = [500, 500, 0] self.card = None self.base = ShowBase() self.base.disableMouse() # assume we are showing windows unless proven otherwise if self.config.get('win', True): # only need inputs if we have a window self.inputs = Inputs(self.base) props = WindowProperties() props.setCursorHidden(True) props.setForeground(True) print self.config.get('resolution') if self.config.get('resolution'): props.set_size(int(self.config['resolution'][0]), int(self.config['resolution'][1])) props.set_origin(0, 0) else: props.set_size(600, 600) props.set_origin(400, 50) self.base.win.requestProperties(props) # print self.base.win.get_size() # setup color map on second window sq_node = square.setup_square(self.config) self.setup_display2(sq_node) # print 'background color', self.base.getBackgroundColor() # create the avatar self.avatar = NodePath(ActorNode("avatar")) self.avatar.reparentTo(self.base.render) self.avatar.setH(self.base.camera.getH()) self.base.camera.reparentTo(self.avatar) self.base.camera.setPos(0, 0, 0) # initialize task variables self.frame_task = None self.started_game = None self.showed_match = None self.gave_reward = None # initialize and start the game self.set_next_trial() # print 'end init' def start_loop(self): # need to get new match print 'start loop' self.started_game = self.base.taskMgr.doMethodLater(5, self.start_play, 'start_play') self.showed_match = self.base.taskMgr.add(self.show_match_sample, 'match_image') # Task methods def show_match_sample(self, task): print 'show match sample' print self.color_match[:] # match_image.fill(*self.color_match[:]) card = CardMaker('card') color_match = self.color_match[:] # add alpha channel color_match.append(1) print color_match card.set_color(*color_match[:]) card.set_frame(-12, -8, 0, 4) # log this self.card = self.base.render.attach_new_node(card.generate()) return task.done def start_play(self, task): print 'start play' # log this self.base.taskMgr.remove('match_image') self.card.removeNode() # print self.base.render.ls() self.frame_task = self.base.taskMgr.add(self.game_loop, "game_loop") self.frame_task.last = 0 # initiate task time of the last frame # log this self.base.setBackgroundColor(self.color_list[:]) return task.done def game_loop(self, task): dt = task.time - task.last task.last = task.time self.velocity = self.inputs.poll_inputs(self.velocity) move = self.move_avatar(dt) stop = self.change_background(move) self.move_map_avatar(move, stop) match = self.check_color_match() if match: self.give_reward() return task.done return task.cont def reward_loop(self, task): self.reward_count += 1 if self.reward_count <= self.config['num_beeps']: if self.reward: # log this print 'give a bloody reward already' self.reward.pumpOut() print 'give reward' return task.again else: self.end_loop() return task.done def move_avatar(self, dt): # print 'velocity', self.velocity # this makes for smooth (correct speed) diagonal movement # print 'velocity', self.velocity magnitude = max(abs(self.velocity[0]), abs(self.velocity[1])) move = None if self.velocity.normalize(): # go left in increasing amount # print 'dt', dt # print 'normalized' # print 'velocity', self.velocity # print 'magnitude', magnitude self.velocity *= magnitude # print 'velocity', self.velocity # this makes for smooth movement move = self.velocity * self.vel_base * dt # print move self.avatar.setFluidPos(self.avatar, move) return move def change_background(self, move): stop = [True, True, True] if move: # print move move *= self.speed for i in range(3): value = self.color_dict[i] if value is not None: stop[i] = False # keys correspond to x,y,z # values correspond to r,g,b if i == 2: # z axis is treated differently # need to work on this. z should # be at min when both x and y are at max # taking the average is not quite right... z_move = (move[0] + move[1])/2 # print z_move self.color_list[value] -= z_move else: self.color_list[value] += move[i] if self.color_list[value] < self.c_range[0]: self.color_list[value] = self.c_range[0] stop[i] = True elif self.color_list[value] > self.c_range[1]: self.color_list[value] = self.c_range[1] stop[i] = True # log this self.base.setBackgroundColor(self.color_list[:]) # print self.base.getBackgroundColor() return stop def move_map_avatar(self, move, stop): # print move # avatar is mapped assuming c_range of 0.5. What do I need to # change to use a different c_range? c_range of one is twice # the if move: avt = LineSegs() avt.setThickness(1) avt.setColor(1, 1, 1) # print 'last', self.last_avt avt.move_to(self.last_avt[0], -5, self.last_avt[1]) # print 'move', move new_move = [i + (j * self.avt_factor) for i, j in zip(self.last_avt, move)] # new_move = [i + j for i, j in zip(self.last_avt, move)] # would it be better to have a local stop condition? if stop[0]: new_move[0] = self.last_avt[0] # print 'stop x', self.last_avt[0] if stop[1]: new_move[1] = self.last_avt[1] # print 'stop y', self.last_avt[1] # print 'new', new_move self.last_avt = [new_move[0], new_move[1]] avt.draw_to(new_move[0], -5, new_move[1]) self.map_avt_node.append(self.render2d.attach_new_node(avt.create())) # print self.map_avt_node[-1] # can't let too many nodes pile up if len(self.map_avt_node) > 299: # removing the node does not remove the object from the list for i, j in enumerate(self.map_avt_node): j.removeNode() if i > 49: break del self.map_avt_node[0:50] def check_color_match(self): # print 'match this', self.color_tolerance # print self.color_list check_color = [j[0] < self.color_list[i] < j[1] for i, j in enumerate(self.color_tolerance)] # print check_color if all(check_color): return True else: return False def give_reward(self): # clear the background self.base.setBackgroundColor(0.41, 0.41, 0.41) print 'give first reward' self.reward_count = 1 if self.reward: # log this self.reward.pumpOut() self.gave_reward = self.base.taskMgr.doMethodLater(self.config['pump_delay'], self.reward_loop, 'reward_loop') def end_loop(self): print 'end loop' # clear avatar map self.clear_avatar_map() # if there is a match set, return to center of color gradient, # set new match, if applicable self.set_next_trial() def clear_avatar_map(self): for i, j in enumerate(self.map_avt_node): j.removeNode() self.map_avt_node = [] def plot_match_square(self, corners): print 'plot match square' print corners match = LineSegs() match.setThickness(1.5) match.setColor(0, 0, 0) match.move_to(corners[0][0], -5, corners[1][0]) match.draw_to(corners[0][1], -5, corners[1][0]) match.draw_to(corners[0][1], -5, corners[1][1]) match.draw_to(corners[0][0], -5, corners[1][1]) match.draw_to(corners[0][0], -5, corners[1][0]) # print self.render2d self.match_square = self.render2d.attach_new_node(match.create()) def create_avatar_map_match_square(self, config=None): print 'make new square for map' if config is not None: config_dict = config else: config_dict = self.config # create square on avatar map for new color match map_color_match, factor = square.translate_color_map(config_dict, self.color_dict, self.color_match) tolerance = config_dict['tolerance'] * factor map_color_tolerance = [(i - tolerance, i + tolerance) for i in map_color_match] print map_color_tolerance if self.render2d: if self.match_square: self.match_square.removeNode() self.plot_match_square(map_color_tolerance) def set_next_trial(self): print 'set next trial' # move avatar back to beginning position, only matters for # showing card for next color match self.avatar.set_pos(-10, -10, 2) # set color_list with starting color # if random, won't use this again, but for manual, will # return to center # need to update self.config to new direction, if there is one if self.config.get('match_direction'): self.check_key_map() # return to center, otherwise random will start where you left off self.color_list = square.set_start_position_colors(self.config) # starting position for map avatar, just translate new color_list self.last_avt, self.avt_factor = square.translate_color_map(self.config, self.color_dict, self.color_list) print 'start color', self.color_list print self.color_dict # again need to update self.config for match if using keys self.color_match = square.set_match_colors(self.config, self.color_dict) # sets the tolerance for how close to a color for reward self.color_tolerance = [(i - self.config['tolerance'], i + self.config['tolerance']) for i in self.color_match] print 'color match', self.color_match print 'color tolerance', self.color_tolerance self.create_avatar_map_match_square(self.config) # start the game self.start_loop() def check_key_map(self): if self.config['colors'][0]: if self.inputs.key_map['r']: self.config['match_direction'] = ['right'] elif self.inputs.key_map['r'] is not None: self.config['match_direction'] = ['left'] elif self.config['colors'][1]: if self.inputs.key_map['f']: self.config['match_direction'] = ['front'] elif self.inputs.key_map['f'] is not None: self.config['match_direction'] = ['back'] def setup_display2(self, display_node): print 'setup display2' props = WindowProperties() props.set_cursor_hidden(True) props.set_foreground(False) if self.config.get('resolution'): props.setSize(700, 700) props.setOrigin(-int(self.config['resolution'][0] - 5), 5) else: props.setSize(300, 300) props.setOrigin(10, 10) window2 = self.base.openWindow(props=props, aspectRatio=1) lens = OrthographicLens() lens.set_film_size(2, 2) lens.setNearFar(-100, 100) self.render2d = NodePath('render2d') self.render2d.attach_new_node(display_node) camera2d = self.base.makeCamera(window2) camera2d.setPos(0, -10, 0) camera2d.node().setLens(lens) camera2d.reparentTo(self.render2d)
class World(DirectObject): def __init__(self, mode=None, config_file=None): DirectObject.__init__(self) # Python assumes all input from sys are string, but not # input variables # mode sets whether starts at manual or auto, default is manual, auto is 0 # Start in auto, if, and only if the input number was a zero, if mode == '0' or mode == 0: self.manual = False else: self.manual = True # print('manual', self.manual) if not config_file: config_file = 'config.py' self.pydaq = pydaq # get configurations from config file self.config = {} execfile(config_file, self.config) print 'Subject is', self.config['SUBJECT'] self.config.setdefault('file_name', config_file) print 'Calibration file is', config_file # if subject is test, doing unit tests if self.config['SUBJECT'] == 'test': # doing tests so use fake eye data and testing configuration. # for testing, always leave gain at one, so eye_data and eye_data_to_plot are the same gain = [1, 1] # print 'test' self.testing = True self.use_daq_reward = False else: gain = self.config['GAIN'] self.testing = False self.use_daq_reward = self.config.setdefault('REWARD', True) # default is not fake data, and don't send signal data_type = 'IScan' if self.config.setdefault('FAKE_DATA', False) or not self.pydaq: print('using fake data') data_type = 'Fake Data' self.config.setdefault('SEND_DATA', False) # assume 0.2 seconds for pump delay, if not set self.config.setdefault('PUMP_DELAY', 0.2) # start Panda3d self.base = ShowBase() # default is no subroutines, will fill in later, if using self.sub_index = None self.call_subroutine = [] # This will be the photo object, if we are showing photos self.photos = None # initialize text before setting up second window. # text will be overridden there. # text only happens on second window self.text_nodes = [None] * 5 self.text_dict = dict(Gain=0, Offset=1, Tolerance=3, Manual=4) self.text_dict[data_type] = 2 eye_position = '[0, 0]' data_type = '' # always start with Manual and eye in the center # stuff that changes with plotting offset = [0, 0] # tolerance in degrees, will need to be changed to pixels to be useful, # but since tolerance can change (in degrees), makes sense to do this on the fly self.plot_variables = [ gain, offset, eye_position, self.config['TOLERANCE'], data_type ] # print base.pipe.getDisplayWidth() # print base.pipe.getDisplayHeight() # if window is offscreen (for testing), does not have WindowProperties, # so can't open second window. # if an actual resolution in config file, change to that resolution, # otherwise keep going... if self.config['WIN_RES'] != 'Test': # only set up windows if not testing self.setup_windows() eye_res = self.config['EYE_RES'] position = [ (0 - eye_res[0] / 4, 0, eye_res[1] / 2 - eye_res[1] / 16), (0, 0, eye_res[1] / 2 - eye_res[1] / 16), (0 + eye_res[0] / 4, 0, eye_res[1] / 2 - eye_res[1] / 16), (0, 0, eye_res[1] / 2 - eye_res[1] * 2 / 16), (-eye_res[0] / 2 + eye_res[0] * 1 / 16, 0, eye_res[1] / 2 - eye_res[1] * 1 / 16) ] for k, v in self.text_dict.items(): self.text_nodes[v] = (self.setup_text(k, self.plot_variables[v], position[v])) self.text_dict['Auto'] = 4 else: # resolution in file equal to test, so use the projector screen # value for determining pixels size. In this case, accuracy is not # important, because never actually calibrating with this setup. resolution = [1280, 800] self.deg_per_pixel = visual_angle(self.config['SCREEN'], resolution, self.config['VIEW_DIST'])[0] # print('gain', self.gain) # print 'window loaded' # empty list for plotting eye nodes self.eye_nodes = [] self.current_eye_data = None # initialize file variables self.eye_file_name = '' self.time_file_name = '' self.eye_data_file = None self.time_data_file = None # starts out not fixated, and not checking for fixation (will # check for fixation when stimulus comes on, if we are doing an auto task) self.fixated = False self.base.setBackgroundColor(115 / 255, 115 / 255, 115 / 255) self.base.disableMouse() # create dummy variable for helper objects self.logging = None self.eye_data = None self.sequences = None # initialize list for eye window self.eye_window = [] # set up daq for reward, if on windows and not testing # testing auto mode depends on being able to control eye position self.reward_task = None self.num_beeps = self.config[ 'NUM_BEEPS'] # number of rewards each time self.num_reward = 0 # count number of rewards # Keyboard stuff: # initiate self.key_dict = {"switch": 0, "task_flag": False} def start_gig(self): # used when beginning in either auto or manual mode, # either at start or after switching # print 'start new gig' if not self.testing: # text4 and text5 change if self.manual: text_label = 'Manual' else: text_label = 'Auto' self.update_text('Tolerance', self.plot_variables[self.text_dict['Tolerance']]) self.update_text(text_label) # open files, start data stream, prepare tasks self.logging.open_files( self.manual, self.plot_variables[self.text_dict['Tolerance']]) self.logging.log_config('Gain', self.plot_variables[self.text_dict['Gain']]) self.logging.log_config('Offset', self.plot_variables[self.text_dict['Offset']]) self.eye_data.start_logging(self.logging) self.sequences.prepare_task(self.manual) def end_gig(self): # used when end in either auto or manual mode, # either at start or after switching # print 'end gig' # clear screen # self.clear_eyes() # close stuff self.eye_data.stop_logging() self.logging.close_files() def change_tasks(self): # change from manual to auto-calibrate or vise-versa # print 'change task' self.manual = not self.manual # print('switched manual?', self.manual) # reset stuff self.key_dict['task_flag'] = False self.end_gig() self.start_gig() # if going from manual to auto, start automatically, otherwise # wait for keypress to start. if not self.manual: self.start_main_loop() def start_main_loop(self, good_trial=None): # check to see if manual, no subroutines for manual if self.manual: self.sequences.setup_manual_sequence() self.sequences.manual_sequence.start() else: # check to see if we are doing a subroutine # print 'new loop, not manual' do_subroutine = False if self.call_subroutine: # print 'check subroutines' for index, tasks in enumerate(self.call_subroutine): do_subroutine = tasks.check_trial(good_trial, self.start_plot_eye_task) if do_subroutine: # print 'show photo' self.sub_index = index break else: self.sub_index = None # print 'after call_subroutine, do_subroutine now', do_subroutine if not do_subroutine: # print 'show square' self.sequences.setup_auto_sequences(good_trial) self.sequences.auto_sequence_one.start() def cleanup_main_loop(self): # print 'cleanup main loop' # print('time', time()) # end of loop, check to see if we are switching tasks, start again good_trial = self.num_reward > 0 self.num_reward = 0 self.fixated = False # if we change tasks, wait for keypress to start again if self.key_dict['task_flag']: # print 'change tasks' self.change_tasks() else: # unit tests we step through, rather than # run main loop if not self.testing: # print 'start next loop' self.start_main_loop(good_trial) # print('done cleanup_main_loop') def give_reward(self): # if got reward, square can move # print 'reward, 3' # print(self.base.taskMgr) # give reward for each num_beeps # give one reward right away, have # to wait delay before giving next reward if self.reward_task: # print 'first reward' self.num_reward = 1 self.reward_task.pumpOut() # if using actual reward have to wait to give next reward self.base.taskMgr.doMethodLater(self.config['PUMP_DELAY'], self.reward_after_pause, 'reward') else: for i in range(self.num_beeps): # print 'beep' self.num_reward += 1 # print 'give reward returns' # print('time', time()) def reward_after_pause(self, task): # print 'give another reward' self.reward_task.pumpOut() self.num_reward += 1 if self.num_reward < self.num_beeps: return task.again # print 'reward done' return task.done def clear_fix_window(self): # remove threshold window around square for win in self.eye_window: win.detachNode() def clear_screen(self): # print 'clear screen' # We can now stop plotting eye positions, # and get rid of old eye positions. self.stop_plot_eye_task() self.clear_fix_window() if self.eye_nodes: # print self.eye_nodes # can do this in a loop, since does not # delete object from list for eye in self.eye_nodes: if not eye.isEmpty(): eye.removeNode() self.current_eye_data = None # print 'should be no nodes now', self.eye_nodes self.eye_nodes = [] # Eye plotting def start_plot_eye_task(self, check_eye=False, timer=False): # print 'start plot eye task' # print 'check_eye', check_eye # print 'timer', timer target = None if check_eye: target, on_interval = self.check_fixation_target() if timer: self.start_fixation_timer(target, on_interval) self.stop_plot_eye_task() self.base.taskMgr.add(self.process_eye_data, 'plot_eye', extraArgs=[check_eye, target], appendTask=True) def stop_plot_eye_task(self): self.base.taskMgr.remove('plot_eye') def check_fixation_target(self): if self.sub_index is not None: # would be great if this were more generic, but works for now target, on_interval = self.call_subroutine[ self.sub_index].get_fixation_target() # print target, on_interval else: # else is going to be regular auto calibrate target, on_interval = self.sequences.get_fixation_target() return target, on_interval # Eye Methods def start_fixation_timer(self, target, on_interval): # print 'show fixation window, start timer' # print on_interval # print target self.show_window(target) # start timing for on task, this runs for target on time and waits for fixation, # if no fixation, method runs to abort trial # print time() if self.sub_index is not None: no_fix_task = self.call_subroutine[self.sub_index].no_fixation else: no_fix_task = self.sequences.no_fixation self.base.taskMgr.doMethodLater(on_interval, no_fix_task, 'wait_for_fix') # print('should still not be fixated', self.fixated) def process_eye_data(self, check_eye=None, target=None, task=None): # get data from producer eye_data = self.eye_data.consume_queue() if not eye_data: return task.cont # print 'plot eye data', eye_data # convert to pixels for plotting and testing distance, # need the eye position from the last run for the starting # position for move to position for plotting, and the # current eye position for ending position if not self.current_eye_data: # print 'use first data point in this chunk' # if no previous eye, just use first data point start_eye = self.eye_data_to_pixel(eye_data[0]) else: # print 'use previous data' start_eye = self.current_eye_data[-1] # print 'eye data in calibration', start_eye # print start_eye # save data self.current_eye_data = [ self.eye_data_to_pixel(data_point) for data_point in eye_data ] self.plot_eye_trace(start_eye) # and set text to last data point if not self.testing: node = self.text_dict[self.eye_data.data_type] # print self.text_dict # print self.eye_data.data_type # print node self.text_nodes[node].setText(self.eye_data.data_type + ' [' + str(round(eye_data[-1][0], 3)) + ', ' + str(round(eye_data[-1][1], 3)) + ']') # print 'check_eye', check_eye if check_eye: # print 'check eye', check_eye # print 'check fixation' self.evaluate_fixation(target) return task.cont def plot_eye_trace(self, first_eye): # print 'plot trace' # if plotting too many eye positions, things slow down and # python goes into lala land. Never need more than 500, and # last 300 is definitely plenty, so every time it hits 500, # get rid of first 200. if len(self.eye_nodes) > 500: # print('get rid of eye nodes', len(self.eye_nodes)) # Since this just removes the node, but doesn't delete # the object in the list, can do this in a for loop, for index in range(200): self.eye_nodes[index].removeNode() # now get rid of the empty nodes in eye_nodes # print('new length', len(self.eye_nodes)) self.eye_nodes = self.eye_nodes[200:] # print('new length', len(self.eye_nodes)) eye = LineSegs() # eye.setThickness(2.0) eye.setThickness(2.0) # print 'last', last_eye # print 'now', self.current_eye_data eye.moveTo(first_eye[0], 55, first_eye[1]) for data_point in self.current_eye_data: eye.drawTo(data_point[0], 55, data_point[1]) # print('plotted eye', eye_data_to_plot) node = self.base.render.attachNewNode(eye.create(True)) node.show(BitMask32.bit(0)) node.hide(BitMask32.bit(1)) self.eye_nodes.append(node) # print 'end plot trace' def evaluate_fixation(self, target): # print 'evaluate' previous_fixation = self.fixated # convert tolerance to pixels tolerance = self.plot_variables[ self.text_dict['Tolerance']] / self.deg_per_pixel # send in eye data converted to pixels, self.current_eye_data fixated = [] # print 'target', target if target is None: # target is none if we are using a subroutine's check_fixation # (usually means fixation area is square instead of round) for data_point in self.current_eye_data: # print 'use sub routine check_fixation' fixated.append(self.call_subroutine[ self.sub_index].check_fixation(data_point)) else: for data_point in self.current_eye_data: # print 'actual data', data_point fixated.append(check_fixation(data_point, tolerance, target)) # print 'fixation array', fixated self.fixated = all(fixated) # print('fixated?', self.fixated) # need to check if time to start fixation period or time to end # fixation period, otherwise business as usual if self.fixated and not previous_fixation: # print 'fixated, start fixation period' # end waiting period self.base.taskMgr.remove('wait_for_fix') # start fixation period if self.sub_index is not None: # print 'subroutine' self.call_subroutine[self.sub_index].start_fixation_period() else: # print 'auto_fix' self.sequences.start_fixation_period() elif not self.fixated and previous_fixation: # print 'broke fixation' if self.sub_index is not None: self.call_subroutine[self.sub_index].broke_fixation() else: self.sequences.broke_fixation() def eye_data_to_pixel(self, eye_data): # change the offset and gain as necessary, so eye data looks # right on screen. Actually, most of this is changed in IScan # before it ever makes it to this machine, but found we have to # at least change the gain by a couple of order of magnitudes return [ (eye_data[0] + self.plot_variables[self.text_dict['Offset']][0]) * self.plot_variables[self.text_dict['Gain']][0], (eye_data[1] + self.plot_variables[self.text_dict['Offset']][1]) * self.plot_variables[self.text_dict['Gain']][1] ] def start_eye_data(self, start_pos=None, variance=None): # if we are sending in variance, then coming from testing and we need to close first # (just stops task producing fake data, so we can restart in different place) if variance is not None: self.eye_data.close() self.eye_data.start_producer_thread('producer', origin=start_pos, variance=variance) self.eye_data.start_consumer_thread('consumer') def show_window(self, target_pos): # draw line around target representing how close the subject has to be looking to get reward # print('show window around square', square_pos) tolerance = self.plot_variables[ self.text_dict['Tolerance']] / self.deg_per_pixel # print 'tolerance in pixels', tolerance # print 'square', square[0], square[2] eye_window = LineSegs() eye_window.setThickness(2.0) eye_window.setColor(1, 0, 0, 1) angle_radians = radians(360) for i in range(50): a = angle_radians * i / 49 y = tolerance * sin(a) x = tolerance * cos(a) eye_window.drawTo((x + target_pos[0], 55, y + target_pos[1])) # draw a radius line # eye_window.moveTo(square[0], 55, square[2]) # eye_window.drawTo(square[0], 55, square[2] + self.plot_variables[self.text_dict['Tolerance']]) # print 'distance drawn', self.distance((square[0], square[2]), (square[0], square[2] + self.plot_variables[self.text_dict['Tolerance']])) # True optimizes the line segments, which sounds useful node = self.base.render.attachNewNode(eye_window.create(True)) node.show(BitMask32.bit(0)) node.hide(BitMask32.bit(1)) self.eye_window.append(node) def setup_text(self, name, text_frag, position): text_node = TextNode(name) if not text_frag: text_node.setText(name) elif name == 'Tolerance': # always start in manual, so tolerance meaningless text_node.setText('') else: text_node.setText(name + ': ' + str(text_frag)) text_node_path = self.base.render.attach_new_node(text_node) text_node_path.setScale(25) text_node_path.setPos(position) text_node_path.show(BitMask32.bit(0)) text_node_path.hide(BitMask32.bit(1)) return text_node def update_text(self, ch_type, change=''): # print 'update', ch_type if not change: update_string = ch_type else: update_string = change if ch_type == 'Tolerance': if self.manual: update_string = '' else: degree = unichr(176).encode('utf-8') update_string = ch_type + ': ' + str( change) + degree + ' V.A., \n alt-arrow to adjust' node = self.text_dict[ch_type] self.text_nodes[node].setText(update_string) def start_reward_task(self): self.reward_task = pydaq.GiveReward() # Key Functions # key press or messenger methods def change_gain_or_offset(self, ch_type, x_or_y, ch_amount): self.plot_variables[self.text_dict[ch_type]][x_or_y] += ch_amount node = self.text_dict[ch_type] self.text_nodes[node].setText( ch_type + ': ' + str(self.plot_variables[self.text_dict[ch_type]])) self.logging.log_change(ch_type, self.plot_variables[self.text_dict[ch_type]]) def change_tolerance(self, direction): # print 'change tolerance' tolerance = self.plot_variables[self.text_dict['Tolerance']] tolerance += direction self.logging.log_change('Tolerance', tolerance) self.update_text('Tolerance', tolerance) self.plot_variables[self.text_dict['Tolerance']] = tolerance # erase any window up currently for win in self.eye_window: win.detachNode() # self.eye_window.detachNode() # and redraw new window target = self.sequences.get_fixation_target() self.show_window(target[0]) # this simply sets a key in the self.key_dict dictionary to the given value def set_key(self, key, val): self.key_dict[key] = val # print 'set key', self.key_dict[key] # this actually assigns keys to methods def setup_keys(self): self.accept("escape", self.close) # escape # starts turning square on self.accept("space", self.start_main_loop) # switches from manual to auto-calibrate or vise-versa, # but only at end of current loop (after reward) # True signifies that we want to change self.accept("s", self.set_key, ["task_flag", True]) # For adjusting calibration # inputs, gain or offset, x or y, how much change # gain - up and down are y self.accept("shift-arrow_up", self.change_gain_or_offset, ['Gain', 1, 1]) self.accept("shift-arrow_up-repeat", self.change_gain_or_offset, ['Gain', 1, 1]) self.accept("shift-arrow_down", self.change_gain_or_offset, ['Gain', 1, -1]) self.accept("shift-arrow_down-repeat", self.change_gain_or_offset, ['Gain', 1, -1]) # gain - right and left are x self.accept("shift-arrow_right", self.change_gain_or_offset, ['Gain', 0, 1]) self.accept("shift-arrow_right-repeat", self.change_gain_or_offset, ['Gain', 0, 1]) self.accept("shift-arrow_left", self.change_gain_or_offset, ['Gain', 0, -1]) self.accept("shift-arrow_left-repeat", self.change_gain_or_offset, ['Gain', 0, -1]) # offset - up and down are y self.accept("control-arrow_up", self.change_gain_or_offset, ['Offset', 1, 0.5]) self.accept("control-arrow_up-repeat", self.change_gain_or_offset, ['Offset', 1, 0.5]) self.accept("control-arrow_down", self.change_gain_or_offset, ['Offset', 1, -0.5]) self.accept("control-arrow_down-repeat", self.change_gain_or_offset, ['Offset', 1, -0.5]) # offset - right and left are x self.accept("control-arrow_right", self.change_gain_or_offset, ['Offset', 0, 0.5]) self.accept("control-arrow_right-repeat", self.change_gain_or_offset, ['Offset', 0, 0.5]) self.accept("control-arrow_left", self.change_gain_or_offset, ['Offset', 0, -0.5]) self.accept("control-arrow_left-repeat", self.change_gain_or_offset, ['Offset', 0, -0.5]) # For adjusting tolerance (allowable distance from target that still gets reward) self.accept("alt-arrow_up", self.change_tolerance, [0.5]) self.accept("alt-arrow_up-repeat", self.change_tolerance, [0.5]) self.accept("alt-arrow_down", self.change_tolerance, [-0.5]) self.accept("alt-arrow_down-repeat", self.change_tolerance, [-0.5]) # send messages from other classes # done with an outside process, time to cleanup self.accept("cleanup", self.cleanup_main_loop) self.accept("reward", self.give_reward) self.accept("clear", self.clear_screen) self.accept("clear_fix", self.clear_fix_window) self.accept("plot", self.start_plot_eye_task) # keys will update the list, and loop will query it # to get new position # why is this a dictionary? It only has one entry?!?! # needs to be a dictionary for other classes to see changes # someday should figure out how to do this better # keyboard self.accept("1", self.set_key, ["switch", 1]) self.accept("2", self.set_key, ["switch", 2]) self.accept("3", self.set_key, ["switch", 3]) self.accept("4", self.set_key, ["switch", 4]) self.accept("5", self.set_key, ["switch", 5]) self.accept("6", self.set_key, ["switch", 6]) self.accept("7", self.set_key, ["switch", 7]) self.accept("8", self.set_key, ["switch", 8]) self.accept("9", self.set_key, ["switch", 9]) # setup methods def setup_windows(self): # get properties for setting up researchers window props = WindowProperties() # props.setForeground(True) props.setCursorHidden(True) try: self.base.win.requestProperties(props) # print props except AttributeError: print 'Cannot open second window. To open just one window, ' \ 'change the resolution in the config file to Test ' \ 'or change the resolution to None for default Panda window' # go ahead and give the traceback and exit raise # Need to get this better. keypress only works with one window. # plus looks ugly. window2 = self.base.openWindow() window2.setClearColor((115 / 255, 115 / 255, 115 / 255, 1)) # resolution of window for actual calibration resolution = self.config['WIN_RES'] eye_res = self.config['EYE_RES'] # if resolution given, set the appropriate resolution # otherwise assume want small windows if resolution is not None: # properties for second window props.setOrigin(-int(eye_res[0]), 0) # props.setOrigin(0, 0) # resolution for second window, one for plotting eye data # props.setSize(1024, 768) props.setSize(int(eye_res[0]), int(eye_res[1])) else: resolution = [ 800, 600 ] # if no resolution given, assume normal panda window props.setOrigin( 600, 200) # make it so windows aren't on top of each other # x and y are pretty damn close, so just use x # degree per pixel is important only for determining where to plot squares and # determining tolerance, but no effect on actual eye position plotting, uses projector # resolution, screen size, etc self.deg_per_pixel = visual_angle(self.config['SCREEN'], resolution, self.config['VIEW_DIST'])[0] # print 'deg_per_pixel', self.deg_per_pixel # set the properties for eye data window props.set_foreground(False) window2.requestProperties(props) # print 'main', window1.getReqeustedProperties() # print 'researcher', window2.getRequestedProperties() # resolution for main window, subjects monitor self.set_resolution(resolution) # orthographic lens means 2d, then we can set size to resolution # so coordinate system is in pixels lens = OrthographicLens() lens.setFilmSize(int(resolution[0]), int(resolution[1])) # lens.setFilmSize(800, 600) # this allows us to layer, as long as we use between -100 # and 100 for z. (eye position on top of squares) lens.setNearFar(-100, 100) camera = self.base.camList[0] camera.node().setLens(lens) camera.reparentTo(self.base.render) camera2 = self.base.camList[1] camera2.node().setLens(lens) camera2.reparentTo(self.base.render) # set bit mask for eye positions camera.node().setCameraMask(BitMask32.bit(1)) camera2.node().setCameraMask(BitMask32.bit(0)) def set_resolution(self, res): # sets the resolution for the main window (projector) wp = WindowProperties() # print 'calibration window', res wp.setSize(int(res[0]), int(res[1])) # wp.setSize(1600, 900) # wp.setOrigin(-1600, 0) wp.setOrigin(0, 0) # wp.setOrigin(-int(res[0]), 0) # wp.setUndecorated(True) wp.set_foreground(True) self.base.win.requestProperties(wp) # print 'main', self.base.win.getRequestedProperties() def setup_game(self): # this only happens once, at beginning # set up keys self.setup_keys() self.logging = Logging(self.config) self.sequences = CalSequences(self.config, self.base, self.logging, self.key_dict) if self.config.setdefault('PHOTO_PATH', False): self.photos = Photos(self.config, self.base, self.logging, self.deg_per_pixel) self.photos.load_all_photos() self.call_subroutine.append(self.photos) # print 'call_subroutine', self.call_subroutine # start generating/receiving data self.eye_data = EyeData(self.base, self.config['FAKE_DATA']) self.start_eye_data() # start reward capabilities, if using daq if self.use_daq_reward: # print 'setup reward' self.start_reward_task() if not self.testing: self.start_gig() def close(self): # print 'close' # if we close during a photo showing or photo break, will interrupt task # also want to keep track of where we ended. Move this to Photos. # make sure eye data is # close any subroutines if self.call_subroutine: for tasks in self.call_subroutine: tasks.close() self.eye_data.close() self.logging.close_files() if self.testing: self.ignoreAll( ) # ignore everything, so nothing weird happens after deleting it. else: sys.exit()