def main(): # To assign camera by name: put string(s) in list # Parse command line arguments parser = argparse.ArgumentParser(description='GUI for gaze tracking and pupillometry') parser.add_argument('-eye', dest='eye_file', type=str, help="Work with existing video recording, instead of live feed", default='') parser.add_argument('-world', dest='world_file', type=str, help="Work with existing video recording, instead of live feed", default='') args = parser.parse_args() # to use a pre-recorded video. # Use a string to specify the path to your video file as demonstrated below if args.eye_file == '': eye_src = ["UI154xLE-M", "USB Camera-B4.09.24.1", "FaceTime Camera (Built-in)", "Microsoft", "6000","Integrated Camera"] # to assign cameras directly, using integers as demonstrated below # eye_src = 1 else: # print "Using provide file: %s" % args.filename eye_src = args.eye_file if args.world_file == '': world_src = ["Logitech Camera","(046d:081d)","C510","B525", "C525","C615","C920","C930e"] # to assign cameras directly, using integers as demonstrated below # world_src = 0 else: world_src = args.world_file # Camera video size in pixels (width,height) eye_size = (260,216) #(1280,1024) world_size = (640,480) # on MacOS we will not use os.fork, elsewhere this does nothing. forking_enable(0) # Create and initialize IPC g_pool = Temp() g_pool.pupil_queue = Queue() g_pool.eye_rx, g_pool.eye_tx = Pipe(False) g_pool.quit = RawValue(c_bool,0) # this value will be substracted form the capture timestamp g_pool.timebase = RawValue(c_double,0) # make some constants avaiable g_pool.user_dir = user_dir g_pool.rec_dir = rec_dir g_pool.version = version g_pool.app = 'capture' # set up subprocesses p_eye = Process(target=eye, args=(g_pool,eye_src,eye_size)) # Spawn subprocess: p_eye.start() if platform.system() == 'Linux': # We need to give the camera driver some time before requesting another camera. sleep(0.5) world(g_pool,world_src,world_size) # Exit / clean-up p_eye.join()
def main(): # To assign camera by name: put string(s) in list eye_src = ["Microsoft", "6000", "Integrated Camera"] world_src = [ "Logitech Camera", "(046d:081d)", "C510", "B525", "C525", "C615", "C920", "C930e" ] # to assign cameras directly, using integers as demonstrated below # eye_src = 1 # world_src = 0 # to use a pre-recorded video. # Use a string to specify the path to your video file as demonstrated below # eye_src = '/Users/mkassner/Pupil/datasets/p1-left/frames/test.avi' # world_src = "/Users/mkassner/Desktop/2014_01_21/000/world.avi" # Camera video size in pixels (width,height) eye_size = (640, 360) world_size = (1280, 720) # on MacOS we will not use os.fork, elsewhere this does nothing. forking_enable(0) # Create and initialize IPC g_pool = Temp() g_pool.pupil_queue = Queue() g_pool.eye_rx, g_pool.eye_tx = Pipe(False) g_pool.quit = RawValue(c_bool, 0) # this value will be substracted form the capture timestamp g_pool.timebase = RawValue(c_double, 0) # make some constants avaiable g_pool.user_dir = user_dir g_pool.rec_dir = rec_dir g_pool.version = version g_pool.app = 'capture' # set up subprocesses p_eye = Process(target=eye, args=(g_pool, eye_src, eye_size)) # Spawn subprocess: p_eye.start() if platform.system() == 'Linux': # We need to give the camera driver some time before requesting another camera. sleep(0.5) world(g_pool, world_src, world_size) # Exit / clean-up p_eye.join()
def main(): # To assign camera by name: put string(s) in list eye_src = ["Microsoft", "6000","Integrated Camera"] world_src = ["Logitech Camera","(046d:081d)","C510","B525", "C525","C615","C920","C930e"] # to assign cameras directly, using integers as demonstrated below # eye_src = 1 # world_src = 0 # to use a pre-recorded video. # Use a string to specify the path to your video file as demonstrated below # eye_src = '/Users/mkassner/Pupil/datasets/p1-left/frames/test.avi' # world_src = "/Users/mkassner/Desktop/2014_01_21/000/world.avi" # Camera video size in pixels (width,height) eye_size = (640,360) world_size = (1280,720) # on MacOS we will not use os.fork, elsewhere this does nothing. forking_enable(0) # Create and initialize IPC g_pool = Temp() g_pool.pupil_queue = Queue() g_pool.eye_rx, g_pool.eye_tx = Pipe(False) g_pool.quit = RawValue(c_bool,0) # this value will be substracted form the capture timestamp g_pool.timebase = RawValue(c_double,0) # make some constants avaiable g_pool.user_dir = user_dir g_pool.rec_dir = rec_dir g_pool.version = version g_pool.app = 'capture' # set up subprocesses p_eye = Process(target=eye, args=(g_pool,eye_src,eye_size)) # Spawn subprocess: p_eye.start() if platform.system() == 'Linux': # We need to give the camera driver some time before requesting another camera. sleep(0.5) world(g_pool,world_src,world_size) # Exit / clean-up p_eye.join()
def main(): # Callback functions def on_resize(window, w, h): active_window = glfwGetCurrentContext() glfwMakeContextCurrent(window) adjust_gl_view(w, h, window) norm_size = normalize((w, h), glfwGetWindowSize(window)) fb_size = denormalize(norm_size, glfwGetFramebufferSize(window)) atb.TwWindowSize(*map(int, fb_size)) glfwMakeContextCurrent(active_window) for p in g.plugins: p.on_window_resize(window, w, h) def on_key(window, key, scancode, action, mods): if not atb.TwEventKeyboardGLFW(key, action): if action == GLFW_PRESS: pass def on_char(window, char): if not atb.TwEventCharGLFW(char, 1): pass def on_button(window, button, action, mods): if not atb.TwEventMouseButtonGLFW(button, action): pos = glfwGetCursorPos(window) pos = normalize(pos, glfwGetWindowSize(main_window)) pos = denormalize(pos, (frame.img.shape[1], frame.img.shape[0])) # Position in img pixels for p in g.plugins: p.on_click(pos, button, action) def on_pos(window, x, y): norm_pos = normalize((x, y), glfwGetWindowSize(window)) fb_x, fb_y = denormalize(norm_pos, glfwGetFramebufferSize(window)) if atb.TwMouseMotion(int(fb_x), int(fb_y)): pass def on_scroll(window, x, y): if not atb.TwMouseWheel(int(x)): pass def on_close(window): glfwSetWindowShouldClose(main_window, True) logger.debug('Process closing from window') try: rec_dir = sys.argv[1] except: #for dev, supply hardcoded dir: rec_dir = '/Users/mkassner/Desktop/Marker_Tracking_Demo_Recording/' if os.path.isdir(rec_dir): logger.debug("Dev option: Using hadcoded data dir.") else: if getattr(sys, 'frozen', False): logger.warning( "You did not supply a data directory when you called this script! \ \nPlease drag a Pupil recoding directory onto the launch icon." ) else: logger.warning( "You did not supply a data directory when you called this script! \ \nPlease supply a Pupil recoding directory as first arg when calling Pupil Player." ) return if not is_pupil_rec_dir(rec_dir): logger.error( "You did not supply a dir with the required files inside.") return #backwards compatibility fn. patch_meta_info(rec_dir) #parse and load data folder info video_path = rec_dir + "/world.avi" timestamps_path = rec_dir + "/timestamps.npy" gaze_positions_path = rec_dir + "/gaze_positions.npy" meta_info_path = rec_dir + "/info.csv" #parse info.csv file with open(meta_info_path) as info: meta_info = dict( ((line.strip().split('\t')) for line in info.readlines())) rec_version = meta_info["Capture Software Version"] rec_version_float = int( filter(type(rec_version).isdigit, rec_version)[:3]) / 100. #(get major,minor,fix of version) logger.debug("Recording version: %s , %s" % (rec_version, rec_version_float)) #load gaze information gaze_list = np.load(gaze_positions_path) timestamps = np.load(timestamps_path) #correlate data positions_by_frame = correlate_gaze(gaze_list, timestamps) # load session persistent settings session_settings = Persistent_Dict(os.path.join(user_dir, "user_settings")) def load(var_name, default): return session_settings.get(var_name, default) def save(var_name, var): session_settings[var_name] = var # Initialize capture cap = autoCreateCapture(video_path, timestamps=timestamps_path) if isinstance(cap, FakeCapture): logger.error("could not start capture.") return width, height = cap.get_size() # Initialize glfw glfwInit() main_window = glfwCreateWindow( width, height, "Pupil Player: " + meta_info["Recording Name"] + " - " + rec_dir.split(os.path.sep)[-1], None, None) glfwMakeContextCurrent(main_window) # Register callbacks main_window glfwSetWindowSizeCallback(main_window, on_resize) glfwSetWindowCloseCallback(main_window, on_close) glfwSetKeyCallback(main_window, on_key) glfwSetCharCallback(main_window, on_char) glfwSetMouseButtonCallback(main_window, on_button) glfwSetCursorPosCallback(main_window, on_pos) glfwSetScrollCallback(main_window, on_scroll) # create container for globally scoped varfs (within world) g = Temp() g.plugins = [] g.play = False g.new_seek = True g.user_dir = user_dir g.rec_dir = rec_dir g.app = 'player' g.timestamps = timestamps g.positions_by_frame = positions_by_frame # helpers called by the main atb bar def update_fps(): old_time, bar.timestamp = bar.timestamp, time() dt = bar.timestamp - old_time if dt: bar.fps.value += .1 * (1. / dt - bar.fps.value) def set_window_size(mode, data): width, height = cap.get_size() ratio = (1, .75, .5, .25)[mode] w, h = int(width * ratio), int(height * ratio) glfwSetWindowSize(main_window, w, h) data.value = mode # update the bar.value def get_from_data(data): """ helper for atb getter and setter use """ return data.value def get_play(): return g.play def set_play(value): g.play = value def next_frame(): try: cap.seek_to_frame(cap.get_frame_index()) except FileSeekError: pass g.new_seek = True def prev_frame(): try: cap.seek_to_frame(cap.get_frame_index() - 2) except FileSeekError: pass g.new_seek = True def open_plugin(selection, data): if plugin_by_index[selection] not in additive_plugins: for p in g.plugins: if isinstance(p, plugin_by_index[selection]): return g.plugins = [p for p in g.plugins if p.alive] logger.debug('Open Plugin: %s' % name_by_index[selection]) new_plugin = plugin_by_index[selection](g) g.plugins.append(new_plugin) g.plugins.sort(key=lambda p: p.order) if hasattr(new_plugin, 'init_gui'): new_plugin.init_gui() # save the value for atb bar data.value = selection def get_from_data(data): """ helper for atb getter and setter use """ return data.value atb.init() # add main controls ATB bar bar = atb.Bar(name="Controls", label="Controls", help="Scene controls", color=(50, 50, 50), alpha=100, valueswidth=150, text='light', position=(10, 10), refresh=.1, size=(300, 160)) bar.next_atb_pos = (10, 220) bar.fps = c_float(0.0) bar.timestamp = time() bar.window_size = c_int(load("window_size", 0)) window_size_enum = atb.enum("Display Size", { "Full": 0, "Medium": 1, "Half": 2, "Mini": 3 }) bar.version = create_string_buffer(version, 512) bar.recording_version = create_string_buffer(rec_version, 512) bar.add_var("fps", bar.fps, step=1., readonly=True) bar._fps = c_float(cap.get_fps()) bar.add_var("recoding fps", bar._fps, readonly=True) bar.add_var("display size", vtype=window_size_enum, setter=set_window_size, getter=get_from_data, data=bar.window_size) bar.add_var("play", vtype=c_bool, getter=get_play, setter=set_play, key="space") bar.add_button('step next', next_frame, key='right') bar.add_button('step prev', prev_frame, key='left') bar.add_var("frame index", vtype=c_int, getter=lambda: cap.get_frame_index() - 1) bar.plugin_to_load = c_int(0) plugin_type_enum = atb.enum("Plug In", index_by_name) bar.add_var("plugin", setter=open_plugin, getter=get_from_data, data=bar.plugin_to_load, vtype=plugin_type_enum) bar.add_var( "version of recording", bar.recording_version, readonly=True, help="version of the capture software used to make this recording") bar.add_var("version of player", bar.version, readonly=True, help="version of the Pupil Player") bar.add_button("exit", on_close, data=main_window, key="esc") #set the last saved window size set_window_size(bar.window_size.value, bar.window_size) on_resize(main_window, *glfwGetWindowSize(main_window)) glfwSetWindowPos(main_window, 0, 0) #we always load these plugins g.plugins.append( Export_Launcher(g, data_dir=rec_dir, frame_count=len(timestamps))) g.plugins.append(Seek_Bar(g, capture=cap)) g.trim_marks = Trim_Marks(g, capture=cap) g.plugins.append(g.trim_marks) #these are loaded based on user settings for initializer in load('plugins', []): name, args = initializer logger.debug("Loading plugin: %s with settings %s" % (name, args)) try: p = plugin_by_name[name](g, **args) g.plugins.append(p) except: logger.warning("Plugin '%s' failed to load from settings file." % name) if load('plugins', "_") == "_": #lets load some default if we dont have presets g.plugins.append(Scan_Path(g)) g.plugins.append(Vis_Polyline(g)) g.plugins.append(Vis_Circle(g)) # g.plugins.append(Vis_Light_Points(g)) #sort by exec order g.plugins.sort(key=lambda p: p.order) #init gui for p in g.plugins: if hasattr(p, 'init_gui'): p.init_gui() # gl_state settings basic_gl_setup() g.image_tex = create_named_texture((height, width, 3)) while not glfwWindowShouldClose(main_window): update_fps() #grab new frame if g.play or g.new_seek: try: new_frame = cap.get_frame() except EndofVideoFileError: #end of video logic: pause at last frame. g.play = False if g.new_seek: display_time = new_frame.timestamp g.new_seek = False frame = new_frame.copy() #new positons and events we make a deepcopy just like the image is a copy. current_pupil_positions = deepcopy(positions_by_frame[frame.index]) events = [] # allow each Plugin to do its work. for p in g.plugins: p.update(frame, current_pupil_positions, events) #check if a plugin need to be destroyed g.plugins = [p for p in g.plugins if p.alive] # render camera image glfwMakeContextCurrent(main_window) make_coord_system_norm_based() draw_named_texture(g.image_tex, frame.img) make_coord_system_pixel_based(frame.img.shape) # render visual feedback from loaded plugins for p in g.plugins: p.gl_display() #present frames at appropriate speed wait_time = frame.timestamp - display_time display_time = frame.timestamp try: spent_time = time() - timestamp sleep(wait_time - spent_time) except: pass timestamp = time() atb.draw() glfwSwapBuffers(main_window) glfwPollEvents() plugin_save = [] for p in g.plugins: try: p_initializer = p.get_class_name(), p.get_init_dict() plugin_save.append(p_initializer) except AttributeError: #not all plugins need to be savable, they will not have the init dict. # any object without a get_init_dict method will throw this exception. pass # de-init all running plugins for p in g.plugins: p.alive = False #reading p.alive actually runs plug-in cleanup _ = p.alive save('plugins', plugin_save) save('window_size', bar.window_size.value) session_settings.close() cap.close() bar.destroy() glfwDestroyWindow(main_window) glfwTerminate() logger.debug("Process done")
def export(should_terminate,frames_to_export,current_frame, data_dir,start_frame=None,end_frame=None,plugin_initializers=[],out_file_path=None): logger = logging.getLogger(__name__+' with pid: '+str(os.getpid()) ) #parse and load data dir info video_path = data_dir + "/world.avi" timestamps_path = data_dir + "/timestamps.npy" gaze_positions_path = data_dir + "/gaze_positions.npy" record_path = data_dir + "/world_viz.avi" #parse info.csv file with open(data_dir + "/info.csv") as info: meta_info = dict( ((line.strip().split('\t')) for line in info.readlines() ) ) rec_version = meta_info["Capture Software Version"] rec_version_int = int(filter(type(rec_version).isdigit, rec_version)[:3])/100 #(get major,minor,fix of version) logger.debug("Exporting a video from recording with version: %s , %s"%(rec_version,rec_version_int)) #load gaze information gaze_list = np.load(gaze_positions_path) timestamps = np.load(timestamps_path) #correlate data positions_by_frame = correlate_gaze(gaze_list,timestamps) # Initialize capture, check if it works cap = autoCreateCapture(video_path,timestamps=timestamps_path) if cap is None: logger.error("Did not receive valid Capture") return width,height = cap.get_size() #Out file path verification, we do this before but if one uses a seperate tool, this will kick in. if out_file_path is None: out_file_path = os.path.join(data_dir, "world_viz.avi") else: file_name = os.path.basename(out_file_path) dir_name = os.path.dirname(out_file_path) if not dir_name: dir_name = data_dir if not file_name: file_name = 'world_viz.avi' out_file_path = os.path.expanduser(os.path.join(dir_name,file_name)) if os.path.isfile(out_file_path): logger.warning("Video out file already exsists. I will overwrite!") os.remove(out_file_path) logger.debug("Saving Video to %s"%out_file_path) #Trim mark verification #make sure the trim marks (start frame, endframe) make sense: We define them like python list slices,thus we can test them like such. trimmed_timestamps = timestamps[start_frame:end_frame] if len(trimmed_timestamps)==0: logger.warn("Start and end frames are set such that no video will be exported.") return False if start_frame == None: start_frame = 0 #these two vars are shared with the lauching process and give an job lenght and progress report. frames_to_export.value = len(trimmed_timestamps) current_frame.value = 0 logger.debug("Will export from frame %s to frame %s. This means I will export %s frames."%(start_frame,start_frame+frames_to_export.value,frames_to_export.value)) #lets get the avg. framerate for our slice of video: fps = float(len(trimmed_timestamps))/(trimmed_timestamps[-1] - trimmed_timestamps[0]) logger.debug("Framerate of export video is %s"%fps) #setup of writer writer = cv2.VideoWriter(out_file_path, cv2.cv.CV_FOURCC(*'DIVX'), fps, (width,height)) cap.seek_to_frame(start_frame) start_time = time() plugins = [] g = Temp() g.plugins = plugins g.app = 'exporter' # load plugins from initializers: for initializer in plugin_initializers: name, args = initializer logger.debug("Loading plugin: %s with settings %s"%(name, args)) try: p = plugin_by_name[name](g,**args) plugins.append(p) except: logger.warning("Plugin '%s' failed to load." %name) while frames_to_export.value - current_frame.value > 0: if should_terminate.value: logger.warning("User aborted export. Exported %s frames to %s."%(current_frame.value,out_file_path)) #explicit release of VideoWriter writer.release() writer = None return False new_frame = cap.get_frame() #end of video logic: pause at last frame. if not new_frame: logger.error("Could not read all frames.") #explicit release of VideoWriter writer.release() writer = None return False else: frame = new_frame #new positons and events current_pupil_positions = positions_by_frame[frame.index] events = None # allow each Plugin to do its work. for p in plugins: p.update(frame,current_pupil_positions,events) # # render gl visual feedback from loaded plugins # for p in plugins: # p.gl_display(frame) writer.write(frame.img) current_frame.value +=1 writer.release() writer = None duration = time()-start_time effective_fps = float(current_frame.value)/duration logger.info("Export done: Exported %s frames to %s. This took %s seconds. Exporter ran at %s frames per second"%(current_frame.value,out_file_path,duration,effective_fps)) return True
def export(should_terminate, frames_to_export, current_frame, data_dir, start_frame=None, end_frame=None, plugin_initializers=[], out_file_path=None): logger = logging.getLogger(__name__ + ' with pid: ' + str(os.getpid())) #parse and load data dir info video_path = data_dir + "/world.avi" timestamps_path = data_dir + "/timestamps.npy" gaze_positions_path = data_dir + "/gaze_positions.npy" record_path = data_dir + "/world_viz.avi" #parse info.csv file with open(data_dir + "/info.csv") as info: meta_info = dict( ((line.strip().split('\t')) for line in info.readlines())) rec_version = meta_info["Capture Software Version"] rec_version_int = int( filter(type(rec_version).isdigit, rec_version)[:3]) / 100 #(get major,minor,fix of version) logger.debug("Exporting a video from recording with version: %s , %s" % (rec_version, rec_version_int)) #load gaze information gaze_list = np.load(gaze_positions_path) timestamps = np.load(timestamps_path) #correlate data positions_by_frame = correlate_gaze(gaze_list, timestamps) # Initialize capture, check if it works cap = autoCreateCapture(video_path, timestamps=timestamps_path) if cap is None: logger.error("Did not receive valid Capture") return width, height = cap.get_size() #Out file path verification, we do this before but if one uses a seperate tool, this will kick in. if out_file_path is None: out_file_path = os.path.join(data_dir, "world_viz.avi") else: file_name = os.path.basename(out_file_path) dir_name = os.path.dirname(out_file_path) if not dir_name: dir_name = data_dir if not file_name: file_name = 'world_viz.avi' out_file_path = os.path.expanduser(os.path.join(dir_name, file_name)) if os.path.isfile(out_file_path): logger.warning("Video out file already exsists. I will overwrite!") os.remove(out_file_path) logger.debug("Saving Video to %s" % out_file_path) #Trim mark verification #make sure the trim marks (start frame, endframe) make sense: We define them like python list slices,thus we can test them like such. trimmed_timestamps = timestamps[start_frame:end_frame] if len(trimmed_timestamps) == 0: logger.warn( "Start and end frames are set such that no video will be exported." ) return False if start_frame == None: start_frame = 0 #these two vars are shared with the lauching process and give an job lenght and progress report. frames_to_export.value = len(trimmed_timestamps) current_frame.value = 0 logger.debug( "Will export from frame %s to frame %s. This means I will export %s frames." % (start_frame, start_frame + frames_to_export.value, frames_to_export.value)) #lets get the avg. framerate for our slice of video: fps = float(len(trimmed_timestamps)) / (trimmed_timestamps[-1] - trimmed_timestamps[0]) logger.debug("Framerate of export video is %s" % fps) #setup of writer writer = cv2.VideoWriter(out_file_path, cv2.cv.CV_FOURCC(*'DIVX'), fps, (width, height)) cap.seek_to_frame(start_frame) start_time = time() plugins = [] g = Temp() g.plugins = plugins g.app = 'exporter' # load plugins from initializers: for initializer in plugin_initializers: name, args = initializer logger.debug("Loading plugin: %s with settings %s" % (name, args)) try: p = plugin_by_name[name](g, **args) plugins.append(p) except: logger.warning("Plugin '%s' failed to load." % name) while frames_to_export.value - current_frame.value > 0: if should_terminate.value: logger.warning("User aborted export. Exported %s frames to %s." % (current_frame.value, out_file_path)) #explicit release of VideoWriter writer.release() writer = None return False new_frame = cap.get_frame() #end of video logic: pause at last frame. if not new_frame: logger.error("Could not read all frames.") #explicit release of VideoWriter writer.release() writer = None return False else: frame = new_frame #new positons and events current_pupil_positions = positions_by_frame[frame.index] events = None # allow each Plugin to do its work. for p in plugins: p.update(frame, current_pupil_positions, events) # # render gl visual feedback from loaded plugins # for p in plugins: # p.gl_display(frame) writer.write(frame.img) current_frame.value += 1 writer.release() writer = None duration = time() - start_time effective_fps = float(current_frame.value) / duration logger.info( "Export done: Exported %s frames to %s. This took %s seconds. Exporter ran at %s frames per second" % (current_frame.value, out_file_path, duration, effective_fps)) return True
def main(): # Callback functions def on_resize(window,w, h): active_window = glfwGetCurrentContext() glfwMakeContextCurrent(window) adjust_gl_view(w,h,window) norm_size = normalize((w,h),glfwGetWindowSize(window)) fb_size = denormalize(norm_size,glfwGetFramebufferSize(window)) atb.TwWindowSize(*map(int,fb_size)) glfwMakeContextCurrent(active_window) for p in g.plugins: p.on_window_resize(window,w,h) def on_key(window, key, scancode, action, mods): if not atb.TwEventKeyboardGLFW(key,action): if action == GLFW_PRESS: pass def on_char(window,char): if not atb.TwEventCharGLFW(char,1): pass def on_button(window,button, action, mods): if not atb.TwEventMouseButtonGLFW(button,action): pos = glfwGetCursorPos(window) pos = normalize(pos,glfwGetWindowSize(main_window)) pos = denormalize(pos,(frame.img.shape[1],frame.img.shape[0]) ) # Position in img pixels for p in g.plugins: p.on_click(pos,button,action) def on_pos(window,x, y): norm_pos = normalize((x,y),glfwGetWindowSize(window)) fb_x,fb_y = denormalize(norm_pos,glfwGetFramebufferSize(window)) if atb.TwMouseMotion(int(fb_x),int(fb_y)): pass def on_scroll(window,x,y): if not atb.TwMouseWheel(int(x)): pass def on_close(window): glfwSetWindowShouldClose(main_window,True) logger.debug('Process closing from window') try: rec_dir = sys.argv[1] except: #for dev, supply hardcoded dir: rec_dir = '/Users/mkassner/Desktop/Marker_Tracking_Demo_Recording/' if os.path.isdir(rec_dir): logger.debug("Dev option: Using hadcoded data dir.") else: if getattr(sys, 'frozen', False): logger.warning("You did not supply a data directory when you called this script! \ \nPlease drag a Pupil recoding directory onto the launch icon.") else: logger.warning("You did not supply a data directory when you called this script! \ \nPlease supply a Pupil recoding directory as first arg when calling Pupil Player.") return if not is_pupil_rec_dir(rec_dir): logger.error("You did not supply a dir with the required files inside.") return #backwards compatibility fn. patch_meta_info(rec_dir) #parse and load data folder info video_path = rec_dir + "/world.avi" timestamps_path = rec_dir + "/timestamps.npy" gaze_positions_path = rec_dir + "/gaze_positions.npy" meta_info_path = rec_dir + "/info.csv" #parse info.csv file with open(meta_info_path) as info: meta_info = dict( ((line.strip().split('\t')) for line in info.readlines() ) ) rec_version = meta_info["Capture Software Version"] rec_version_float = int(filter(type(rec_version).isdigit, rec_version)[:3])/100. #(get major,minor,fix of version) logger.debug("Recording version: %s , %s"%(rec_version,rec_version_float)) #load gaze information gaze_list = np.load(gaze_positions_path) timestamps = np.load(timestamps_path) #correlate data positions_by_frame = correlate_gaze(gaze_list,timestamps) # load session persistent settings session_settings = Persistent_Dict(os.path.join(user_dir,"user_settings")) def load(var_name,default): return session_settings.get(var_name,default) def save(var_name,var): session_settings[var_name] = var # Initialize capture cap = autoCreateCapture(video_path,timestamps=timestamps_path) if isinstance(cap,FakeCapture): logger.error("could not start capture.") return width,height = cap.get_size() # Initialize glfw glfwInit() main_window = glfwCreateWindow(width, height, "Pupil Player: "+meta_info["Recording Name"]+" - "+ rec_dir.split(os.path.sep)[-1], None, None) glfwMakeContextCurrent(main_window) # Register callbacks main_window glfwSetWindowSizeCallback(main_window,on_resize) glfwSetWindowCloseCallback(main_window,on_close) glfwSetKeyCallback(main_window,on_key) glfwSetCharCallback(main_window,on_char) glfwSetMouseButtonCallback(main_window,on_button) glfwSetCursorPosCallback(main_window,on_pos) glfwSetScrollCallback(main_window,on_scroll) # create container for globally scoped varfs (within world) g = Temp() g.plugins = [] g.play = False g.new_seek = True g.user_dir = user_dir g.rec_dir = rec_dir g.app = 'player' g.timestamps = timestamps g.positions_by_frame = positions_by_frame # helpers called by the main atb bar def update_fps(): old_time, bar.timestamp = bar.timestamp, time() dt = bar.timestamp - old_time if dt: bar.fps.value += .1 * (1. / dt - bar.fps.value) def set_window_size(mode,data): width,height = cap.get_size() ratio = (1,.75,.5,.25)[mode] w,h = int(width*ratio),int(height*ratio) glfwSetWindowSize(main_window,w,h) data.value=mode # update the bar.value def get_from_data(data): """ helper for atb getter and setter use """ return data.value def get_play(): return g.play def set_play(value): g.play = value def next_frame(): try: cap.seek_to_frame(cap.get_frame_index()) except FileSeekError: pass g.new_seek = True def prev_frame(): try: cap.seek_to_frame(cap.get_frame_index()-2) except FileSeekError: pass g.new_seek = True def open_plugin(selection,data): if plugin_by_index[selection] not in additive_plugins: for p in g.plugins: if isinstance(p,plugin_by_index[selection]): return g.plugins = [p for p in g.plugins if p.alive] logger.debug('Open Plugin: %s'%name_by_index[selection]) new_plugin = plugin_by_index[selection](g) g.plugins.append(new_plugin) g.plugins.sort(key=lambda p: p.order) if hasattr(new_plugin,'init_gui'): new_plugin.init_gui() # save the value for atb bar data.value=selection def get_from_data(data): """ helper for atb getter and setter use """ return data.value atb.init() # add main controls ATB bar bar = atb.Bar(name = "Controls", label="Controls", help="Scene controls", color=(50, 50, 50), alpha=100,valueswidth=150, text='light', position=(10, 10),refresh=.1, size=(300, 160)) bar.next_atb_pos = (10,220) bar.fps = c_float(0.0) bar.timestamp = time() bar.window_size = c_int(load("window_size",0)) window_size_enum = atb.enum("Display Size",{"Full":0, "Medium":1,"Half":2,"Mini":3}) bar.version = create_string_buffer(version,512) bar.recording_version = create_string_buffer(rec_version,512) bar.add_var("fps", bar.fps, step=1., readonly=True) bar._fps = c_float(cap.get_fps()) bar.add_var("recoding fps",bar._fps,readonly=True) bar.add_var("display size", vtype=window_size_enum,setter=set_window_size,getter=get_from_data,data=bar.window_size) bar.add_var("play",vtype=c_bool,getter=get_play,setter=set_play,key="space") bar.add_button('step next',next_frame,key='right') bar.add_button('step prev',prev_frame,key='left') bar.add_var("frame index",vtype=c_int,getter=lambda:cap.get_frame_index()-1 ) bar.plugin_to_load = c_int(0) plugin_type_enum = atb.enum("Plug In",index_by_name) bar.add_var("plugin",setter=open_plugin,getter=get_from_data,data=bar.plugin_to_load, vtype=plugin_type_enum) bar.add_var("version of recording",bar.recording_version, readonly=True, help="version of the capture software used to make this recording") bar.add_var("version of player",bar.version, readonly=True, help="version of the Pupil Player") bar.add_button("exit", on_close,data=main_window,key="esc") #set the last saved window size set_window_size(bar.window_size.value,bar.window_size) on_resize(main_window, *glfwGetWindowSize(main_window)) glfwSetWindowPos(main_window,0,0) #we always load these plugins g.plugins.append(Export_Launcher(g,data_dir=rec_dir,frame_count=len(timestamps))) g.plugins.append(Seek_Bar(g,capture=cap)) g.trim_marks = Trim_Marks(g,capture=cap) g.plugins.append(g.trim_marks) #these are loaded based on user settings for initializer in load('plugins',[]): name, args = initializer logger.debug("Loading plugin: %s with settings %s"%(name, args)) try: p = plugin_by_name[name](g,**args) g.plugins.append(p) except: logger.warning("Plugin '%s' failed to load from settings file." %name) if load('plugins',"_") == "_": #lets load some default if we dont have presets g.plugins.append(Scan_Path(g)) g.plugins.append(Vis_Polyline(g)) g.plugins.append(Vis_Circle(g)) # g.plugins.append(Vis_Light_Points(g)) #sort by exec order g.plugins.sort(key=lambda p: p.order) #init gui for p in g.plugins: if hasattr(p,'init_gui'): p.init_gui() # gl_state settings basic_gl_setup() g.image_tex = create_named_texture((height,width,3)) while not glfwWindowShouldClose(main_window): update_fps() #grab new frame if g.play or g.new_seek: try: new_frame = cap.get_frame() except EndofVideoFileError: #end of video logic: pause at last frame. g.play=False if g.new_seek: display_time = new_frame.timestamp g.new_seek = False frame = new_frame.copy() #new positons and events we make a deepcopy just like the image is a copy. current_pupil_positions = deepcopy(positions_by_frame[frame.index]) events = [] # allow each Plugin to do its work. for p in g.plugins: p.update(frame,current_pupil_positions,events) #check if a plugin need to be destroyed g.plugins = [p for p in g.plugins if p.alive] # render camera image glfwMakeContextCurrent(main_window) make_coord_system_norm_based() draw_named_texture(g.image_tex,frame.img) make_coord_system_pixel_based(frame.img.shape) # render visual feedback from loaded plugins for p in g.plugins: p.gl_display() #present frames at appropriate speed wait_time = frame.timestamp - display_time display_time = frame.timestamp try: spent_time = time()-timestamp sleep(wait_time-spent_time) except: pass timestamp = time() atb.draw() glfwSwapBuffers(main_window) glfwPollEvents() plugin_save = [] for p in g.plugins: try: p_initializer = p.get_class_name(),p.get_init_dict() plugin_save.append(p_initializer) except AttributeError: #not all plugins need to be savable, they will not have the init dict. # any object without a get_init_dict method will throw this exception. pass # de-init all running plugins for p in g.plugins: p.alive = False #reading p.alive actually runs plug-in cleanup _ = p.alive save('plugins',plugin_save) save('window_size',bar.window_size.value) session_settings.close() cap.close() bar.destroy() glfwDestroyWindow(main_window) glfwTerminate() logger.debug("Process done")
def main(): """Batch process recordings to produce visualizations Using simple_circle as the default visualizations Steps: - User Supplies: Directory that contains many recording(s) dirs or just one recordings dir - We walk the user supplied directory to get all data folders - Data is the list we feed to our multiprocessed - Error check -- do we have required files in each dir?: world.avi, gaze_positions.npy, timestamps.npy - Result: world_viz.avi within each original data folder """ parser = argparse.ArgumentParser( formatter_class=argparse.RawDescriptionHelpFormatter, description=dedent('''\ *************************************************** Batch process recordings to produce visualizations The default visualization will use simple_circle Usage Example: python batch_export.py -d /path/to/folder-with-many-recordings -s ~/Pupil_Player/settings/user_settings -e ~/my_export_dir Arguments: -d : Specify a recording directory. This could have one or many recordings contained within it. We will recurse into the dir. -s : Specify path to Pupil Player user_settings file to use last used vizualization settings. -e : Specify export directory if you dont what the export saved within each recording dir. -c : If you dont use the player settings file, we use a default circle. You can specify color to be used Available options: white, red, green -p : Export a 120 frame preview only. ***************************************************\ ''')) parser.add_argument('-d', '--rec-dir', required=True) parser.add_argument('-s', '--settings-file', default=False) parser.add_argument('-e', '--export-to-dir', default=False) parser.add_argument('-c', '--basic-color', default='red') parser.add_argument('-p', '--preview', action='store_true') if len(sys.argv) == 1: print parser.description return args = parser.parse_args() # get the top level data folder from terminal argument data_dir = args.rec_dir if args.settings_file and os.path.isfile(args.settings_file): session_settings = shelve.open(os.path.splitext(args.settings_file)[0], protocol=2) #these are loaded based on user settings plugin_initializers = session_settings.get('plugins', []) session_settings.close() for initializer in plugin_initializers: name, var = initializer logger.debug("Loading plugin: %s with settings %s" % (name, var)) else: session_settings = {} if args.basic_color: try: color = color_lookup(args.basic_color) except KeyError: logger.warning("Not a real color. Choosing red") color = color_lookup('red') else: logger.warning("No color selected. Choosing red") color = color_lookup('red') #load a simple cirlce plugin as default logger.debug("Loading default plugin: Vis_Circle with color: %s" % (color, )) plugin_initializers = [("Vis_Circle", { 'thickness': 2, 'color': color })] if args.export_to_dir: export_dir = args.export_to_dir if os.path.isdir(export_dir): logger.info("Exporting all vids to %s" % export_dir) else: logger.info("Exporting dir is not valid %s" % export_dir) return else: export_dir = None if args.preview: preview = True else: preview = False g = Temp() g.app = "batch_export" recording_dirs = get_recording_dirs(data_dir) for r in recording_dirs: patch_meta_info(r) # start multiprocessing engine n_cpu = cpu_count() logger.info( "Using a maximum of %s CPUs to process visualizations in parallel..." % n_cpu) jobs = [] outfiles = set() for d in recording_dirs: j = Temp() logger.info("Adding new export: %s" % d) j.should_terminate = Value(c_bool, 0) j.frames_to_export = Value('i', 0) j.current_frame = Value('i', 0) j.data_dir = d j.start_frame = None if preview: j.end_frame = 30 else: j.end_frame = None j.plugin_initializers = plugin_initializers[:] if export_dir: #make a unique name created from rec_session and dir name rec_session, rec_dir = d.rsplit(os.path.sep, 2)[1:] out_name = rec_session + "_" + rec_dir + ".avi" j.out_file_path = os.path.join(os.path.expanduser(export_dir), out_name) if j.out_file_path in outfiles: logger.error( "This export setting would try to save %s at least twice pleace rename dirs to prevent this." % j.out_file_path) return outfiles.add(j.out_file_path) logger.info("Exporting to: %s" % j.out_file_path) else: j.out_file_path = None j.args = (j.should_terminate, j.frames_to_export, j.current_frame, j.data_dir, j.start_frame, j.end_frame, j.plugin_initializers, j.out_file_path) jobs.append(j) todo = jobs[:] workers = [ Process(target=export, args=todo.pop(0).args) for i in range(min(len(todo), n_cpu)) ] for w in workers: w.start() working = True t = time.time() while working: #cannot use pool as it does not allow shared memory working = False for i in range(len(workers)): if workers[i].is_alive(): working = True else: if todo: workers[i] = Process(target=export, args=todo.pop(0).args) workers[i].start() working = True show_progess(jobs) time.sleep(.25) print "\n" #lets give some cpu performance feedback. total_frames = sum((j.frames_to_export.value for j in jobs)) total_secs = time.time() - t logger.info("Export done. Exported %s frames in %s seconds. %s fps" % (total_frames, total_secs, total_frames / total_secs))