class CellularAutomataMain(Tk): GRID_WIDTH_PX = DisplayConfiguration.CA_CANVAS_MAX_PIX_X GRID_HEIGHT_PX = DisplayConfiguration.CA_CANVAS_MAX_PIX_Y def __init__(self, width_cells=DisplayConfiguration.DEFAULT_WIDTH, *args, **kwargs): Tk.__init__(self, *args, **kwargs) self.resizable(width=False, height=False) self.state = {'rules': None, 'rules_file': None, 'grid': None} self.random_start = BooleanVar(self) self.random_start.trace('w', self.random_start_callback) #Load blank grid self.width_cells = width_cells self.height_cells = width_cells # Default setting should probably have the grid be square. canvas_width = self.GRID_WIDTH_PX-80 canvas_height = self.GRID_HEIGHT_PX-80 self.state['grid'] = build_blank_grid(width_cells, width_cells) self.grid_display = GridDisplay(self, self.state['grid'], self.GRID_WIDTH_PX, self.GRID_HEIGHT_PX, width=canvas_width, height=canvas_height, relief=GROOVE, bd=4) self.grid_display.grid(row=0, column=0, padx=20, pady=20) #Build top menus self.menubar = Menu(self) try: self.config(menu=self.Menu) except AttributeError: self.tk.call(self, 'config', '-menu', self.menubar) self.file_menu = Menu(self, tearoff=False) self.file_menu.add_command(label="Load Ruleset", command=self.load_dialogue) self.file_menu.add_separator() self.file_menu.add_command(label="Save Automata (Image)", command=self.save_image_dialogue) self.file_menu.add_separator() self.file_menu.add_command(label="Quit", command=self.quit) self.menubar.add_cascade(menu=self.file_menu, label='File') self.config_menu = Menu(self, tearoff=False) self.config_menu.add_command(label='Set Dimensions', command=self.config_dimensions) self.config_menu.add_checkbutton(label='Set Grid Wrap') self.config_menu.add_checkbutton(label='Random Start Row', variable=self.random_start) self.bg_color_menu = self.build_selector_menu(GridDisplay.ALLOWED_COLORS, self.grid_display.set_bg_color, lambda: self.grid_display.draw_grid(self.state['grid']), 'white') self.fill_color_menu = self.build_selector_menu(GridDisplay.ALLOWED_COLORS, self.grid_display.set_fill_color, lambda: self.grid_display.draw_grid(self.state['grid']), 'black') self.config_menu.add_cascade(menu=self.bg_color_menu, label="Set Background Color...") self.config_menu.add_cascade(menu=self.fill_color_menu, label="Set Fill Color...") self.config_menu.add_separator() self.config_menu.add_command(label='Configure Plug-ins') self.plugin_menu = Menu(self) self.config_menu.add_cascade(menu=self.plugin_menu, label="Plugins...") self.menubar.add_cascade(menu=self.config_menu, label='Configure') self.menubar.add_command(label="About", command=self.about) #Load status bar self.status_bar_var = StringVar(self) self.status_bar_var.set('Initialized.') self.status_bar = Label(self, textvar=self.status_bar_var, relief=SUNKEN, justify=LEFT, anchor=W) self.status_bar.grid(row=1, column=0, sticky="EW", padx=2, pady=2) #Build plugin manager disp_logger.info('Loading plug-in manager') self.status_bar_var.set('Loading plug-in manager...') self.plugin_manager = PluginManager() self.plugin_manager.setPluginPlaces(['../plugins/']) self.plugin_manager.collectPlugins() disp_logger.info('Plug-in manager loaded. {} plug-ins were loaded.' ''.format(len(self.plugin_manager.getAllPlugins()))) def load_dialogue(self): new_rule_filename = tkFileDialog.askopenfilename(parent=self, title='Open Rule File', defaultextension=".txt", filetypes=[('Rules Files', '*.txt'), ('All Files', '*')]) if not new_rule_filename: return try: self.load(new_rule_filename) except: disp_logger.exception('Faulted loading rules file!') def save_image_dialogue(self): automata_image_filename = tkFileDialog.asksaveasfilename(parent=self, title='Save Automata Image', defaultextension='.eps', filetypes=[('Postscript', '.eps'), ('PNG', '.png'), ('JPEG', '.jpg')]) if not automata_image_filename: return try: #TODO: Should we be converting, or should I have a local image representation instead? if not automata_image_filename.endswith('.eps'): self.grid_display.postscript(file='tmp.eps') img = Image.open('tmp.eps') img.save(automata_image_filename) os.remove('tmp.eps') else: self.grid_display.postscript(file=automata_image_filename) self.status_bar_var.set('Saved automata image file as: {}'.format(automata_image_filename)) except: disp_logger.exception('Faulted saving automata image!') def load(self, rule_file): disp_logger.info('Attempting to load new rules file: {}'.format(rule_file)) rules = load_rules(rule_file) self.state['rules'] = rules self.state['rules_file'] = rule_file self._build_grid(rules) self.status_bar_var.set('Loaded rules file: {}'.format(rule_file)) def _build_grid(self, rules): grid = [] if self.random_start.get(): start_row = build_random_start_row(self.width_cells) else: start_row = build_default_start_row(self.width_cells) grid.append(start_row) grid.extend(evolve_system_multi(start_row, rules, self.height_cells)) self.state['grid'] = grid self.grid_display.draw_grid(grid) def config_dimensions(self): new_width, new_height = DimensionsDialog(self, title='Set Automata Dimensions', prompt='Set Automata Dimensions', x_name='Width', y_name='Height', x_default=self.width_cells, y_default=self.height_cells) if not new_width or not new_height: return disp_logger.info('Resizing automata to new width: {}, height: {}'.format(new_width, new_height)) self.width_cells = new_width self.height_cells = new_height self._build_grid(self.state['rules']) def random_start_callback(self, *args): self._build_grid(self.state['rules']) @staticmethod def about(): tkMessageBox.showinfo('About', '{app} ({ver})\nby {auth}, 2014' ''.format(ver=__version__, auth=__author__, app=__application_name__)) def build_selector_menu(self, choices, set_fxn, update_fxn=None, default=None): new_menu = Menu(self) choice_map = {} def build_selector_trace(var): def callback(*args): set_fxn(choice_map[var.get()]) if update_fxn: update_fxn() return callback if not default: default = choices[0] new_var = StringVar(new_menu) new_var.set(str(default)) new_var.trace('w', build_selector_trace(new_var)) for choice in choices: choice_map[str(choice)] = choice new_menu.add_radiobutton(label=str(choice).capitalize(), variable=new_var, value=str(choice)) return new_menu
class Cap(Frame): def __init__(self): ' set defaults, create widgets, bind callbacks, start live view ' self.root = Tk() # menu: self.menu = Menu(self.root) self.root.config(menu=self.menu) # bind global keypresses: self.root.bind('q', lambda e: self.root.quit()) self.root.bind('x', lambda e: self.root.quit()) self.root.bind('<Destroy>', self.do_stop_video) self.root.bind('<space>', self.do_single_shot) self.root.bind('<Return>', self.do_single_shot) self.root.bind('<Button-3>', self.do_single_shot) self.root.bind('<Left>', lambda e: self.degree.set(-90)) self.root.bind('<Right>', lambda e: self.degree.set(90)) self.root.bind('<Up>', lambda e: self.degree.set(0)) self.root.bind('a', lambda e: self.autocontrast.set(not self.autocontrast.get())) self.root.bind('e', lambda e: self.equalize.set(not self.equalize.get())) self.root.bind('g', lambda e: self.grayscale.set(not self.grayscale.get())) self.root.bind('i', lambda e: self.invert.set(not self.invert.get())) self.root.bind('s', lambda e: self.solarize.set(not self.solarize.get())) # config: self.config = RawConfigParser() self.config.read('filmroller.conf') if not self.config.has_section('global'): self.config.add_section('global') self.video = None self.invert = BooleanVar(name='invert') self.invert.set(self.config_get('invert', True)) self.invert.trace('w', self.do_configure) self.grayscale = BooleanVar(name='grayscale') self.grayscale.set(self.config_get('grayscale', False)) self.grayscale.trace('w', self.do_configure) self.autocontrast = BooleanVar(name='autocontrast') self.autocontrast.set(self.config_get('autocontrast', True)) self.autocontrast.trace('w', self.do_configure) self.equalize = BooleanVar(name='equalize') self.equalize.set(self.config_get('equalize', False)) self.equalize.trace('w', self.do_configure) self.solarize = BooleanVar(name='solarize') self.solarize.set(self.config_get('solarize', False)) self.solarize.trace('w', self.do_configure) self.degree = IntVar(name='degree') self.degree.set(0) self.filename = StringVar(name='filename') self.videodevice = StringVar(name='videodevice') dev_names = sorted(['/dev/{}'.format(x) for x in listdir('/dev') if x.startswith('video')]) d = self.config_get('videodevice', dev_names[-1]) if not d in dev_names: d = dev_names[-1] self.videodevice.set(d) self.videodevice.trace('w', self.do_configure) # self.path = 'filmroller' if not exists(self.path): makedirs(self.path) # create gui: Frame.__init__(self, self.root) self.grid() self.x_canvas = Canvas(self, width=640, height=640, ) self.x_canvas.pack(side='top') self.x_canvas.bind('<Button-1>', self.do_change_rotation) Checkbutton(self, text='Invert', variable=self.invert).pack(side='left') Checkbutton(self, text='Gray', variable=self.grayscale).pack(side='left') Checkbutton(self, text='Auto', variable=self.autocontrast).pack(side='left') OptionMenu(self, self.videodevice, *dev_names, command=self.restart_video).pack(side='left') Button(self, text='First role', command=self.do_first_role).pack(side='left') Label(self, textvariable=self.filename).pack(side='left') Button(self, text='Next role', command=self.do_inc_role).pack(side='left') Button(self, text='Take!', command=self.do_single_shot).pack(side='right') #filemenu = Menu(self.menu) #self.menu.add_cascade(label=self.videodevice.get(), menu=filemenu, ) #for n in dev_names: # filemenu.add_command(label=n, ) #filemenu.add_separator() # start operation: self.do_first_role() self.do_start_video() def do_change_rotation(self, event): ' determine where the image was clicked and turn that to the top ' if event.x < 200: self.degree.set(-90) elif event.x > 640 - 200: self.degree.set(90) else: self.degree.set(0) def config_get(self, name, default): ' read a configuration entry, fallback to default if not already stored ' if not self.config.has_option('global', name): return default if isinstance(default, bool): return self.config.getboolean('global', name) else: return self.config.get('global', name) def do_configure(self, name, mode, cbname): ' change a configuration entry ' if cbname == 'w': value = getattr(self, name).get() self.config.set('global', name, str(value)) self.config.write(open('filmroller.conf', 'w')) def do_first_role(self, *args): ' jump back to first role ' self.role = 'aa' self.serial = 0 self.inc_picture() def inc_picture(self): ' increment the picture number, jump over existing files ' self.filename.set('{}/scanned.{}-{:04}.jpg'.format(self.path, self.role, self.serial, )) while exists(self.filename.get()): self.serial += 1 self.filename.set('{}/scanned.{}-{:04}.jpg'.format(self.path, self.role, self.serial, )) self.root.title('filmroller - ' + self.filename.get()) def do_inc_role(self, *args): ' increment to next role ' self.serial = 0 self.role = ascii_increment(self.role) self.inc_picture() def set_pauseimage(self): ' show pause image (during shot) ' self.image = fromfile('filmroller.pause.png') self.image.thumbnail((self.previewsize['size_x'], self.previewsize['size_y'], ), ) self.photo = PhotoImage(self.image) self.x_canvas.create_image(640/2, 640/2, image=self.photo) def do_stop_video(self, *args): ' stop video and release device ' if self.video is not None: self.video.stop() self.video.close() self.video = None def restart_video(self, *args): ' restart video (if device changes or hangs) ' self.do_stop_video() self.root.after(1, self.do_start_video) def do_start_video(self, *args): ' init video and start live view ' if self.video is None: self.video = Video_device(self.videodevice.get()) _, _, self.fourcc = self.video.get_format() caps = sorted(self.video.get_framesizes(self.fourcc), cmp=lambda a, b: cmp(a['size_x']*a['size_y'], b['size_x']*b['size_y'])) self.previewsize, self.highressize = caps[0], caps[-1] self.previewsize['size_x'], self.previewsize['size_y'] = self.video.set_format( self.previewsize['size_x'], self.previewsize['size_y'], 0, 'MJPEG') try: self.video.set_auto_white_balance(True) except: pass try: self.video.set_exposure_auto(True) except: pass try: self.video.set_focus_auto(True) except: pass self.video.create_buffers(30) self.video.queue_all_buffers() self.video.start() self.root.after(1, self.do_live_view) #self.x_canvas.config(width=640, height=480) #self.x_canvas.pack(side='top') self.degree.set(0) def do_live_view(self, *args): ' show single pic live view and ask tk to call us again later ' if self.video is not None: select((self.video, ), (), ()) data = self.video.read_and_queue() self.image = frombytes('RGB', (self.previewsize['size_x'], self.previewsize['size_y']), data) if self.invert.get(): self.image = invert(self.image) if self.grayscale.get(): self.image = grayscale(self.image) if self.autocontrast.get(): self.image = autocontrast(self.image) if self.equalize.get(): self.image = equalize(self.image) if self.solarize.get(): self.image = solarize(self.image) if self.degree.get(): self.image = self.image.rotate(self.degree.get()) self.photo = PhotoImage(self.image) self.x_canvas.create_image(640/2, 640/2, image=self.photo) self.root.after(3, self.do_live_view) def do_single_shot(self, *args): ' do a high res single shot and store it ' def _go(): self.video = Video_device(self.videodevice.get()) try: self.highressize['size_x'], self.highressize['size_y'] = self.video.set_format( self.highressize['size_x'], self.highressize['size_y'], 0, 'MJPEG') try: self.video.set_auto_white_balance(True) except: pass try: self.video.set_exposure_auto(True) except: pass try: self.video.set_focus_auto(True) except: pass self.video.create_buffers(7) self.video.queue_all_buffers() self.video.start() stop_time = time() + 3.0 # wait for auto while stop_time >= time(): select((self.video, ), (), ()) self.update_idletasks() data = self.video.read_and_queue() image = frombytes('RGB', (self.highressize['size_x'], self.highressize['size_y'], ), data) if self.invert.get(): image = invert(image) if self.grayscale.get(): image = grayscale(image) if self.autocontrast.get(): image = autocontrast(image) if self.equalize.get(): self.image = equalize(self.image) if self.solarize.get(): self.image = solarize(self.image) if self.degree.get(): image = image.rotate(self.degree.get()) image.save(self.filename.get()) self.inc_picture() self.root.bell() self.video.stop() finally: self.video.close() self.video = None self.root.after(1, self.do_start_video) self.do_stop_video() self.set_pauseimage() self.update_idletasks() self.root.after(1, _go)