def __init__(self, parent, configurator_service): Tkinter.Frame.__init__(self, parent) self.configurator_service = configurator_service choices = [cfg['config_filepath'] for cfg in self.configurator_service.configs] # Add some contents to the dialog self.menu = Pmw.OptionMenu(self, items = choices, labelpos='nw', label_text='Configuration Files', command = self._option_menu_command, #called when choice is switched #selectioncommand=self.selectionCommand, #dblclickcommand = self.setup_control_mode, #usehullsize = 1, #hull_width = 200, #hull_height = 200, ) self.text_display = TextDisplayBox(self,text_height=TEXT_DISPLAY_TEXT_HEIGHT, text_width=TEXT_DISPLAY_TEXT_WIDTH, buffer_size = TEXT_BUFFER_SIZE) self.select_button = Tkinter.Button(text='Select',command = self._select_button_command) self.menu.pack(side='top', anchor='nw') self.text_display.pack(expand = True, fill = 'both', padx = 4, pady = 4) self.select_button.pack(side='bottom') self.selection = None self.menu.invoke(index=0)
class ConfiguratorGUI(Tkinter.Frame): def __init__(self, parent, configurator_service): Tkinter.Frame.__init__(self, parent) self.configurator_service = configurator_service choices = [cfg['config_filepath'] for cfg in self.configurator_service.configs] # Add some contents to the dialog self.menu = Pmw.OptionMenu(self, items = choices, labelpos='nw', label_text='Configuration Files', command = self._option_menu_command, #called when choice is switched #selectioncommand=self.selectionCommand, #dblclickcommand = self.setup_control_mode, #usehullsize = 1, #hull_width = 200, #hull_height = 200, ) self.text_display = TextDisplayBox(self,text_height=TEXT_DISPLAY_TEXT_HEIGHT, text_width=TEXT_DISPLAY_TEXT_WIDTH, buffer_size = TEXT_BUFFER_SIZE) self.select_button = Tkinter.Button(text='Select',command = self._select_button_command) self.menu.pack(side='top', anchor='nw') self.text_display.pack(expand = True, fill = 'both', padx = 4, pady = 4) self.select_button.pack(side='bottom') self.selection = None self.menu.invoke(index=0) def _select_button_command(self): print "Selection made: %s" % self.menu.getvalue() root = self._root() root.destroy() def _option_menu_command(self, option): choice_index = self.menu.index(Pmw.SELECT) self.selection = self.configurator_service.configs[choice_index] print "Current choice index %d, option: %s" % (choice_index,option) self.text_display.clear_text() try: #read in the raw file and display it in the text box config_filename = self.selection.filename text = open(config_filename, 'r').read() self.text_display.print_text(text) self.text_display.set_text_yview(0) #forces scroll back to top except: self.text_display.print_text("*** Could not read file: %s" % config_filename)
def __init__(self, application): self.app = application #signal that experiment is running self.experiment_mode = False self.app.print_comment("Starting GUI interface:") self.app.print_comment("please wait while the application loads...") #build the GUI interface as a seperate window win = tk.Tk() Pmw.initialise(win) #initialize Python MegaWidgets win.withdraw() win.wm_title(WINDOW_TITLE) win.focus_set() #put focus on this new window self.win = win #handle the user hitting the 'X' button self.win.protocol("WM_DELETE_WINDOW", self._close) #FIXME bind some debugging keystrokes to the window #self.win.bind('<Control-f>', lambda e: self.app.force_experiment()) #build the left panel left_panel = tk.Frame(win) #capture controls tk.Label(left_panel, text="Capture Controls:", font = "Helvetica 14 bold").pack(side='top',fill='x', anchor="nw") self.change_settings_button = tk.Button(left_panel,text='Change Settings',command = self.change_settings) self.change_settings_button.pack(side='top',fill='x', anchor="sw") self.run_continually_button = tk.Button(left_panel,text='Run Continually',command = self.run_continually) self.run_continually_button.pack(side='top',fill='x', anchor="nw") self.stop_button = tk.Button(left_panel,text='Stop',command = self.stop, state='disabled') self.stop_button.pack(side='top',fill='x', anchor="nw") self.run_once_button = tk.Button(left_panel,text='Run Once',command = self.run_once) self.run_once_button.pack(side='top',fill='x', anchor="nw") self.export_data_button = tk.Button(left_panel,text='Export Data',command = self.export_data, state='disabled') self.export_data_button.pack(side='bottom',anchor="sw") left_panel.pack(fill='y',expand='no',side='left', padx = 10) #create an tk embedded figure for temperature display mid_panel = tk.Frame(win) self.temperature_plot_template = TemperaturePlot() self.temperature_figure_widget = EmbeddedFigure(mid_panel, figsize=TEMPERATURE_FIGSIZE) self.temperature_figure_widget.pack(side='left',fill='both', expand='yes') mid_panel.pack(fill='both', expand='yes',side='left') #build the right panel right_panel = tk.Frame(win) self.text_display = TextDisplayBox(right_panel,text_height=15, buffer_size = TEXT_BUFFER_SIZE) self.text_display.pack(side='left',fill='both',expand='yes') right_panel.pack(fill='both', expand='yes',side='right') #build the confirmation dialog self.settings_dialog = SettingsDialog(self.win) self.settings_dialog.withdraw() self._load_settings() #run modes self._is_running = False
class GUI(GUIBase): def build_window(self): GUIBase.build_window(self) #now size the window and center it sw = self._win.winfo_screenwidth() sh = self._win.winfo_screenheight() w = sw*WINDOW_TO_SCREENWIDTH_RATIO h = sh*WINDOW_TO_SCREENHEIGHT_RATIO x = (sw - w)/2 y = (sh - h)/2 self._win.geometry("%dx%d+%d+%d" % (w,h,x,y)) self._cv_plot_Xs = [] self._cv_plot_Ys = [] self._cv_plot_labels = [] self._vsweep_mode = None self._vsweep_stop = False def build_widgets(self): #FIXME bind some debugging keystrokes to the window #self._win.bind('<Control-f>', lambda e: self._app.) #----------------------------------------------------------------------- #build the left panel left_panel = tk.Frame(self._win) #voltage sweep controls tk.Label(left_panel, text="Voltage Sweep Controls:", font = HEADING_LABEL_FONT).pack(side='top',anchor="w") self.vsweep_settings_button = tk.Button(left_panel, text = 'Change Settings', command = self.change_vsweep_settings, width = BUTTON_WIDTH) self.vsweep_settings_button.pack(side='top', anchor="sw") self.vsweep_once_button = tk.Button(left_panel, text = 'Run Once', command = lambda: self.do_vsweep(mode = 'once'), width = BUTTON_WIDTH) self.vsweep_once_button.pack(side='top', anchor="nw") self.vsweep_continually_button = tk.Button(left_panel, text = 'Run Continually', command = lambda: self.do_vsweep(mode = 'continual'), width = BUTTON_WIDTH) self.vsweep_continually_button.pack(side='top', anchor="nw") self.vsweep_stop_button = tk.Button(left_panel, text = 'Stop', command = self.vsweep_stop, state = 'disabled', width = BUTTON_WIDTH) self.vsweep_stop_button.pack(side='top', anchor="nw") #build the capture settings dialog self.vsweep_settings_dialog = VoltageSweepSettingsDialog(self._win) self.vsweep_settings_dialog.withdraw() #finish the left panel left_panel.pack(fill='y',expand='no',side='left', padx = 10) #----------------------------------------------------------------------- #build the middle panel - a tabbed notebook mid_panel = tk.Frame(self._win) #mid_panel.pack(fill='both', expand='yes',side='left') nb = ttk.Notebook(mid_panel) nb.pack(fill='both', expand='yes',side='right') tab1 = tk.Frame(nb) tab1.pack(fill='both', expand='yes',side='right') nb.add(tab1, text = "Current vs. Voltage") #create an tk embedded figure for the current vs. voltage display self.cv_plot_template = CurrentVoltagePlot() self.cv_plot_template.configure(title = CV_PLOT_TITLE) self.cv_plot_figure_widget = EmbeddedFigure(tab1, figsize = CV_PLOT_FIGSIZE) self.cv_plot_figure_widget.pack(side='top',fill='both', expand='yes') self._update_cv_plot() #make an empty plot self.replot_cv_button = tk.Button(tab1,text='Replot',command = self.replot_cv, state='normal', width = BUTTON_WIDTH) self.replot_cv_button.pack(side='left',anchor="sw") self.clear_cv_button = tk.Button(tab1,text='Clear',command = self.clear_data, state='normal', width = BUTTON_WIDTH) self.clear_cv_button.pack(side='left',anchor="sw") self.export_data_button = tk.Button(tab1, text ='Export Data', command = self.export_data, state = 'disabled', width = BUTTON_WIDTH) self.export_data_button.pack(side='left',anchor="sw") #finish builing the middle pannel mid_panel.pack(fill='both', expand='yes',side='left') #----------------------------------------------------------------------- #build the right panel right_panel = tk.Frame(self._win) #Status variable display #tk.Label(right_panel, pady = SECTION_PADY).pack(side='top',fill='x', anchor="nw") #tk.Label(right_panel, text="Status:", font = HEADING_LABEL_FONT).pack(side='top',anchor="w") #self.condition_fields = ConditionFields(right_panel) #self.condition_fields.pack(side='top', anchor="w", expand='no') # Events text display tk.Label(right_panel, pady = SECTION_PADY).pack(side='top',fill='x', anchor="nw") tk.Label(right_panel, text="Events Monitoring:", font = HEADING_LABEL_FONT).pack(side='top',anchor="w") self.text_display = TextDisplayBox(right_panel, text_width = TEXTBOX_WIDTH, buffer_size = TEXTBOX_BUFFER_SIZE, ) self.text_display.pack(side='top',fill='both',expand='yes') #finish building the right panel right_panel.pack(fill='both', expand='yes',side='right', padx = 10) def close(self): self.cache_settings() GUIBase.close(self) def load_settings(self): if os.path.exists(SETTINGS_FILEPATH): self._app.print_comment("loading from settings file '%s'" % SETTINGS_FILEPATH) settings = shelve.open(SETTINGS_FILEPATH) self.vsweep_settings_dialog.form['v_start'] = settings.get('v_start', 0.0) self.vsweep_settings_dialog.form['v_end'] = settings.get('v_end', 1.5) self.vsweep_settings_dialog.form['v_rate'] = settings.get('v_rate', 0.25) self.vsweep_settings_dialog.form['samp_rate'] = settings.get('samp_rate', 10.0) self.vsweep_settings_dialog.form['cycles'] = settings.get('cycles', 1) self.vsweep_settings_dialog.current_range_level_var.set(settings.get('current_range_level', 0)) settings.close() else: self._app.print_comment("failed to find settings file '%s'" % SETTINGS_FILEPATH) def cache_settings(self): self._app.print_comment("caching to settings file '%s'" % SETTINGS_FILEPATH) settings = shelve.open(SETTINGS_FILEPATH) settings['v_start'] = self.vsweep_settings_dialog.form['v_start'] settings['v_end'] = self.vsweep_settings_dialog.form['v_end'] settings['v_rate'] = self.vsweep_settings_dialog.form['v_rate'] settings['samp_rate'] = self.vsweep_settings_dialog.form['samp_rate'] settings['cycles'] = self.vsweep_settings_dialog.form['cycles'] settings['current_range_level'] = self.vsweep_settings_dialog.current_range_level_var.get() settings.close() def busy(self): self.disable_control_buttons() self._win.config(cursor="watch") def not_busy(self): self.enable_control_buttons() self._win.config(cursor="") def print_to_text_display(self, text, eol='\n'): try: self.text_display.print_text(text, eol=eol) except AttributeError: #ignore missing text display widget pass def print_event(self, event, info = {}): buff = ["%s:" % event] for key,val in info.items(): buff.append("%s: %s" % (key,val)) buff = "\n".join(buff) self.print_to_text_display(buff) def disable_control_buttons(self): self.vsweep_settings_button.configure(state="disabled") self.vsweep_continually_button.configure(state="disabled") #self.capture_stop_button.configure(state="disabled") self.vsweep_once_button.configure(state="disabled") def enable_control_buttons(self): self.vsweep_settings_button.configure(state="normal") self.vsweep_continually_button.configure(state="normal") #self.vsweep_stop_button.configure(state="normal") self.vsweep_once_button.configure(state="normal") def change_vsweep_settings(self): choice = self.vsweep_settings_dialog.activate() if choice == "OK": self._app.print_comment("changing voltage sweep settings...") def do_vsweep(self, mode = 'once'): self._vsweep_mode = mode #disable all the control buttons, except the stop button self.disable_control_buttons() self.vsweep_stop_button.config(state='normal') if mode == 'once': self.vsweep_once_button.config(bg='green', relief='sunken') elif mode == 'continual': self.vsweep_continually_button.config(state='disabled', bg='green', relief="sunken") #get parameters v_start = float(self.vsweep_settings_dialog.form['v_start']) v_end = float(self.vsweep_settings_dialog.form['v_end']) v_rate = float(self.vsweep_settings_dialog.form['v_rate']) samp_rate = float(self.vsweep_settings_dialog.form['samp_rate']) cycles = int(self.vsweep_settings_dialog.form['cycles']) current_range_level = self.vsweep_settings_dialog.current_range_level_var.get() current_range_level, _ = current_range_level.split(",") current_range_level = int(current_range_level) self._app.print_comment("Running a voltage sweep:") self._app.print_comment(" v_start: %0.2f" % (v_start,)) self._app.print_comment(" v_end: %0.2f" % (v_end,)) self._app.print_comment(" v_rate: %0.2f" % (v_rate,)) self._app.print_comment(" samp_rate: %0.2f" % (samp_rate,)) self._app.print_comment(" cycles: %0.2f" % (cycles,)) #start the voltage sweep, SHOULD NOT BLOCK! self._app.start_voltage_sweep(v_start = v_start, v_end = v_end, v_rate = v_rate, samp_rate = samp_rate, cycles = cycles, current_range_level = current_range_level, ) self._win.after(LOOP_DELAY, self._wait_on_vsweep_loop) def _wait_on_vsweep_loop(self): voltage_sweep = self._app._load_controller('voltage_sweep') #read out all pending events while not voltage_sweep.event_queue.empty(): event, info = voltage_sweep.event_queue.get() self.print_event(event,info) if event == "VOLTAGE_SWEEP_SAMPLE": self._app._append_vsweep_data_record(info['control_voltage'], info['WEtoRE_voltage'], info['WE_current'], ) #use new data to update the plot V1 = self._app._vsweep_dataset['control_voltage'] V2 = self._app._vsweep_dataset['WEtoRE_voltage'] I = self._app._vsweep_dataset['WE_current'] self._update_cv_plot(X_now = V2, Y_now = I) if voltage_sweep.thread_isAlive(): #reschedule loop self._vsweep_after_id = self._win.after(VSWEEP_LOOP_DELAY,self._wait_on_vsweep_loop) else: #cycle is finished #cache the data for the plot V1 = self._app._vsweep_dataset['control_voltage'] V2 = self._app._vsweep_dataset['WEtoRE_voltage'] I = self._app._vsweep_dataset['WE_current'] self._cv_plot_Xs.append(V2) self._cv_plot_Ys.append(I) new_label = "Trial %d" % (len(self._cv_plot_labels) + 1,) self._cv_plot_labels.append(new_label) self.replot_cv() #finish up #self.not_busy() #re-enable all the buttons, except the stop button self.enable_control_buttons() self.export_data_button.config(state='normal') self._app.print_comment("voltage sweep completed") #self.export_data_button.config(state='normal') #data can now be exported if self._vsweep_mode == 'once': self.vsweep_once_button.config(bg='light gray', relief='raised') self.vsweep_stop_button.config(state='disabled') self._vsweep_stop = False elif self._vsweep_mode == 'continual': if self._vsweep_stop: self._vsweep_stop = False self._vsweep_mode = None self.vsweep_continually_button.config(bg='light gray', relief='raised') voltage_sweep.reset() else: #reschedule another voltage sweep self.do_vsweep(mode = 'continual') def vsweep_stop(self): self.vsweep_stop_button.config(state='disabled') voltage_sweep = self._app._load_controller('voltage_sweep') self._vsweep_stop = True #force it to stop right now instead of finishing sleep voltage_sweep.abort() # if not self._vsweep_after_id is None: # #cancel the next scheduled loop time # self._win.after_cancel(self._vsweep_after_id) # #then enter the loop one more time to clean up # self._wait_on_vsweep_loop() def replot_cv(self): voltage_sweep = self._app._load_controller('voltage_sweep') figure = self.cv_plot_figure_widget.get_figure() figure.clear() self.cv_plot_template._has_been_plotted = False #check to see if the current trial is still running if voltage_sweep.thread_isAlive(): #if so pass in the current trial data V1 = self._app._vsweep_dataset['control_voltage'] V2 = self._app._vsweep_dataset['WEtoRE_voltage'] I = self._app._vsweep_dataset['WE_current'] self._update_cv_plot(X_now = V2, Y_now = I) else: #otherwise just the completed data sets (i.e., avoid redundancy of last set) self._update_cv_plot() def export_data(self): self._app.print_comment("Exporting data...") dt_now = datetime.datetime.utcnow() dt_now_str = dt_now.strftime("%Y-%m-%d") #get some metadata for title v_start = float(self._app._vsweep_dataset.get_metadata('v_start')) v_end = float(self._app._vsweep_dataset.get_metadata('v_end')) v_rate = float(self._app._vsweep_dataset.get_metadata('v_rate')) default_filename = "%s_vsweep_%0.2f_to_%0.2fV_by_%0.2fVps.csv" % (dt_now_str,v_start,v_end,v_rate) fdlg = SaveFileDialog(self._win,title="Save Voltage Sweep Data") userdata_path = self._app._config['paths']['data_dir'] filename = fdlg.go(dir_or_file = userdata_path, pattern = "*.csv", default = default_filename, key = None, ) if filename: self._app.export_data(filename) self._app.print_comment("finished") def clear_data(self): self._cv_plot_Xs = [] self._cv_plot_Ys = [] self._cv_plot_labels = [] self.replot_cv() def _update_cv_plot(self, X_now = None, Y_now = None): figure = self.cv_plot_figure_widget.get_figure() plot_template = self.cv_plot_template #decide whether to plot the data (again) or update a current plot do_plot = False if not plot_template.has_been_plotted(): do_plot = True else: #check if the plot has moved out of the boundaries ax1 = figure.axes[0] xlim = ax1.get_xlim() ylim = ax1.get_ylim() if not xlim[0] <= X_now[-1] <= xlim[1]: do_plot = True if not ylim[0] <= Y_now[-1] <= ylim[1]: do_plot = True #do the update if do_plot: self._app.print_comment("Plotting the Current vs. Voltage.") Xs = self._cv_plot_Xs[:] #make copy to not mutate! if not X_now is None: Xs.append(X_now) else: Xs.append([]) Ys = self._cv_plot_Ys[:] #make copy to not mutate! if not Y_now is None: Ys.append(Y_now) else: Ys.append([]) #styles = CV_PLOT_STYLES labels = self._cv_plot_labels + ['Current Trial'] plot_template.plot(Xs, Ys, #styles = styles, labels = labels, figure = figure ) self.cv_plot_figure_widget.update() else: self._app.print_comment("Updating Current vs. Voltage plot.") #get the plot line from the figure FIXME is there an easier way? axis = figure.axes[0] last_line = axis.lines[-1] if not X_now is None: last_line.set_xdata(X_now) if not Y_now is None: last_line.set_ydata(Y_now) self.cv_plot_figure_widget.update()
def build_widgets(self): #FIXME bind some debugging keystrokes to the window #self._win.bind('<Control-f>', lambda e: self._app.) #----------------------------------------------------------------------- #build the left panel left_panel = tk.Frame(self._win) #voltage sweep controls tk.Label(left_panel, text="Voltage Sweep Controls:", font = HEADING_LABEL_FONT).pack(side='top',anchor="w") self.vsweep_settings_button = tk.Button(left_panel, text = 'Change Settings', command = self.change_vsweep_settings, width = BUTTON_WIDTH) self.vsweep_settings_button.pack(side='top', anchor="sw") self.vsweep_once_button = tk.Button(left_panel, text = 'Run Once', command = lambda: self.do_vsweep(mode = 'once'), width = BUTTON_WIDTH) self.vsweep_once_button.pack(side='top', anchor="nw") self.vsweep_continually_button = tk.Button(left_panel, text = 'Run Continually', command = lambda: self.do_vsweep(mode = 'continual'), width = BUTTON_WIDTH) self.vsweep_continually_button.pack(side='top', anchor="nw") self.vsweep_stop_button = tk.Button(left_panel, text = 'Stop', command = self.vsweep_stop, state = 'disabled', width = BUTTON_WIDTH) self.vsweep_stop_button.pack(side='top', anchor="nw") #build the capture settings dialog self.vsweep_settings_dialog = VoltageSweepSettingsDialog(self._win) self.vsweep_settings_dialog.withdraw() #finish the left panel left_panel.pack(fill='y',expand='no',side='left', padx = 10) #----------------------------------------------------------------------- #build the middle panel - a tabbed notebook mid_panel = tk.Frame(self._win) #mid_panel.pack(fill='both', expand='yes',side='left') nb = ttk.Notebook(mid_panel) nb.pack(fill='both', expand='yes',side='right') tab1 = tk.Frame(nb) tab1.pack(fill='both', expand='yes',side='right') nb.add(tab1, text = "Current vs. Voltage") #create an tk embedded figure for the current vs. voltage display self.cv_plot_template = CurrentVoltagePlot() self.cv_plot_template.configure(title = CV_PLOT_TITLE) self.cv_plot_figure_widget = EmbeddedFigure(tab1, figsize = CV_PLOT_FIGSIZE) self.cv_plot_figure_widget.pack(side='top',fill='both', expand='yes') self._update_cv_plot() #make an empty plot self.replot_cv_button = tk.Button(tab1,text='Replot',command = self.replot_cv, state='normal', width = BUTTON_WIDTH) self.replot_cv_button.pack(side='left',anchor="sw") self.clear_cv_button = tk.Button(tab1,text='Clear',command = self.clear_data, state='normal', width = BUTTON_WIDTH) self.clear_cv_button.pack(side='left',anchor="sw") self.export_data_button = tk.Button(tab1, text ='Export Data', command = self.export_data, state = 'disabled', width = BUTTON_WIDTH) self.export_data_button.pack(side='left',anchor="sw") #finish builing the middle pannel mid_panel.pack(fill='both', expand='yes',side='left') #----------------------------------------------------------------------- #build the right panel right_panel = tk.Frame(self._win) #Status variable display #tk.Label(right_panel, pady = SECTION_PADY).pack(side='top',fill='x', anchor="nw") #tk.Label(right_panel, text="Status:", font = HEADING_LABEL_FONT).pack(side='top',anchor="w") #self.condition_fields = ConditionFields(right_panel) #self.condition_fields.pack(side='top', anchor="w", expand='no') # Events text display tk.Label(right_panel, pady = SECTION_PADY).pack(side='top',fill='x', anchor="nw") tk.Label(right_panel, text="Events Monitoring:", font = HEADING_LABEL_FONT).pack(side='top',anchor="w") self.text_display = TextDisplayBox(right_panel, text_width = TEXTBOX_WIDTH, buffer_size = TEXTBOX_BUFFER_SIZE, ) self.text_display.pack(side='top',fill='both',expand='yes') #finish building the right panel right_panel.pack(fill='both', expand='yes',side='right', padx = 10)
class GUI: def __init__(self, application): self.app = application #signal that experiment is running self.experiment_mode = False self.app.print_comment("Starting GUI interface:") self.app.print_comment("please wait while the application loads...") #build the GUI interface as a seperate window win = tk.Tk() Pmw.initialise(win) #initialize Python MegaWidgets win.withdraw() win.wm_title(WINDOW_TITLE) win.focus_set() #put focus on this new window self.win = win #handle the user hitting the 'X' button self.win.protocol("WM_DELETE_WINDOW", self._close) #FIXME bind some debugging keystrokes to the window #self.win.bind('<Control-f>', lambda e: self.app.force_experiment()) #build the left panel left_panel = tk.Frame(win) #capture controls tk.Label(left_panel, text="Capture Controls:", font = "Helvetica 14 bold").pack(side='top',fill='x', anchor="nw") self.change_settings_button = tk.Button(left_panel,text='Change Settings',command = self.change_settings) self.change_settings_button.pack(side='top',fill='x', anchor="sw") self.run_continually_button = tk.Button(left_panel,text='Run Continually',command = self.run_continually) self.run_continually_button.pack(side='top',fill='x', anchor="nw") self.stop_button = tk.Button(left_panel,text='Stop',command = self.stop, state='disabled') self.stop_button.pack(side='top',fill='x', anchor="nw") self.run_once_button = tk.Button(left_panel,text='Run Once',command = self.run_once) self.run_once_button.pack(side='top',fill='x', anchor="nw") self.export_data_button = tk.Button(left_panel,text='Export Data',command = self.export_data, state='disabled') self.export_data_button.pack(side='bottom',anchor="sw") left_panel.pack(fill='y',expand='no',side='left', padx = 10) #create an tk embedded figure for temperature display mid_panel = tk.Frame(win) self.temperature_plot_template = TemperaturePlot() self.temperature_figure_widget = EmbeddedFigure(mid_panel, figsize=TEMPERATURE_FIGSIZE) self.temperature_figure_widget.pack(side='left',fill='both', expand='yes') mid_panel.pack(fill='both', expand='yes',side='left') #build the right panel right_panel = tk.Frame(win) self.text_display = TextDisplayBox(right_panel,text_height=15, buffer_size = TEXT_BUFFER_SIZE) self.text_display.pack(side='left',fill='both',expand='yes') right_panel.pack(fill='both', expand='yes',side='right') #build the confirmation dialog self.settings_dialog = SettingsDialog(self.win) self.settings_dialog.withdraw() self._load_settings() #run modes self._is_running = False def launch(self): #run the GUI handling loop IgnoreKeyboardInterrupt() self.win.deiconify() self.win.mainloop() NoticeKeyboardInterrupt() def change_settings(self): self.app.print_comment("changing capture settings...") self.settings_dialog.activate() def run_continually(self): #cache the GUI settings FIXME - is this necessary? self._cache_settings() #disable all the buttons, except the stop button self.run_once_button.config(state='disabled') self.run_continually_button.config(state='disabled') self.stop_button.config(state='normal') self._is_running = True self._run_continually_loop() def _run_continually_loop(self): if self._is_running: self.run_once() run_interval = int(1000*float(self.settings_dialog.form['run_interval'])) #convert to milliseconds #reschedule loop self.win.after(run_interval,self._run_continually_loop) else: #enable all the buttons, except the stop button self.run_once_button.config(state='normal') self.run_continually_button.config(state='normal') self.stop_button.config(state='disabled') #do not reschedule loop def run_once(self): self.app.acquire_temperature_sample() self._update_temperature_plot() self.export_data_button.config(state='normal') #data can now be exported def stop(self): self._is_running = False def export_data(self): self.app.print_comment("Exporting data...") dt_now = datetime.datetime.utcnow() dt_now_str = dt_now.strftime("%Y-%m-%d-%H_%m_%S") default_filename = "%s_temperature.csv" % (dt_now_str,) fdlg = SaveFileDialog(self.win,title="Save Temperature Data") userdata_path = self.app.config['paths']['data_dir'] filename = fdlg.go(dir_or_file = userdata_path, pattern="*.csv", default=default_filename, key = None ) if not filename: return #abort delim = "," comment_prefix = "#" eol = "\n" with open(filename, 'w') as f: #write header f.write(comment_prefix) keys = self.app.temperature_samples.keys() f.write(delim.join(keys)) f.write(eol) vals = self.app.temperature_samples.values() D = np.vstack(vals).transpose() np.savetxt(f, D, fmt=DATA_FORMAT, delimiter=delim) def _update_temperature_plot(self): figure = self.temperature_figure_widget.get_figure() figure.clear() t = np.array(self.app.timestamps) t -= t[0] t /= 3600.0 Xs = [] Ys = [] for key,temp_list in self.app.temperature_samples.items(): Xs.append(t) Ys.append(temp_list) self.temperature_plot_template.plot(Xs, Ys, figure = figure ) self.temperature_figure_widget.update() # def wait_on_experiment(self): # if self.app.check_experiment_completed(): # self.app.shutdown_experiment() # self.win.after(WAIT_DELAY,self.wait_on_experiment_shutdown) # else: # self.win.after(WAIT_DELAY,self.wait_on_experiment) def print_to_text_display(self, text, eol='\n'): self.text_display.print_text(text, eol=eol) def _load_settings(self): if os.path.exists(SETTINGS_FILEPATH): self.app.print_comment("loading from settings file '%s'" % SETTINGS_FILEPATH) settings = shelve.open(SETTINGS_FILEPATH) self.settings_dialog.form['run_interval'] = settings.get('run_interval', DEFAULT_RUN_INTERVAL) settings.close() else: self.app.print_comment("failed to find settings file '%s'" % SETTINGS_FILEPATH) def _cache_settings(self): self.app.print_comment("caching to settings file '%s'" % SETTINGS_FILEPATH) settings = shelve.open(SETTINGS_FILEPATH) settings['run_interval'] = self.settings_dialog.form['run_interval'] settings.close() def _close(self): #cache the GUI settings FIXME - is this necessary? self._cache_settings() self.win.destroy()
def __init__(self, application): self.app = application self.app.print_comment("Starting GUI interface:") self.app.print_comment("please wait while the application loads...") self._mode = "standby" self._update_counter = 0 self.data = None #build the GUI interface as a seperate window win = Tk() Pmw.initialise(win) #initialize Python MegaWidgets win.withdraw() win.wm_title(WINDOW_TITLE) win.focus_set() #put focus on this new window self.win = win #handle the user hitting the 'X' button self.win.protocol("WM_DELETE_WINDOW", self._close) #build the left panel left_frame = Frame(win) button_bar = Frame(left_frame) self.start_loop_button = Button(button_bar,text="Start Loop" ,command = self.start_loop) self.stop_loop_button = Button(button_bar,text="Stop Loop" ,command = self.stop_loop, state='disabled') self.chanA_pid_mode_button = Button(button_bar,text="Chan A PID = ON",command = self.toggle_chanA_pid_mode, relief = "sunken") self.chanB_pid_mode_button = Button(button_bar,text="Chan B PID = ON",command = self.toggle_chanB_pid_mode, relief = "sunken") self.send_command1_button = Button(button_bar,text="Send Command to TEIS" ,command = self.send_command_to_peltier_pid) self.run_script_button = Button(button_bar,text="Run Script" ,command = self.run_script) self.abort_script_button = Button(button_bar,text="Abort Script",command = self.abort_script, state='disabled') self.setpointGRAD_button = Button(button_bar,text="Set Gradient",command = lambda: self.change_setpoint(mode='GRAD')) self.setpointA_button = Button(button_bar,text="Set Temp. A" ,command = lambda: self.change_setpoint(mode='TEMP_A')) self.setpointB_button = Button(button_bar,text="Set Temp. B" ,command = lambda: self.change_setpoint(mode='TEMP_B')) self.clear_button = Button(button_bar,text="Clear Data" ,command = self.clear_data) self.export_button = Button(button_bar,text="Export Data" ,command = self.export_data) button_pack_opts = {'side':'top','fill':'x', 'expand':'yes', 'anchor':'nw'} self.start_loop_button.pack(**button_pack_opts) self.stop_loop_button.pack(**button_pack_opts) self.chanA_pid_mode_button.pack(**button_pack_opts) self.chanB_pid_mode_button.pack(**button_pack_opts) self.send_command1_button.pack(**button_pack_opts) self.run_script_button.pack(**button_pack_opts) self.abort_script_button.pack(**button_pack_opts) self.setpointGRAD_button.pack(**button_pack_opts) self.setpointA_button.pack(**button_pack_opts) self.setpointB_button.pack(**button_pack_opts) self.clear_button.pack(**button_pack_opts) self.export_button.pack(**button_pack_opts) button_bar.pack(side='top', fill='x', expand='no', anchor='nw') left_frame.pack(side='left', fill='both', padx=10) #build the middle panel mid_panel = Frame(win) self.data_plotter = DataPlotter(mid_panel) self.text_display = TextDisplayBox(mid_panel,text_height=TEXT_DISPLAY_HEIGHT, buffer_size = TEXT_BUFFER_SIZE) self.data_plotter.pack(fill='both',expand='yes') self.text_display.pack(fill='both',expand='yes') mid_panel.pack(fill='both', expand='yes',side='left') #build the right panel right_panel = Frame(win) self.sample_name_entry = Pmw.EntryField(right_panel, labelpos = 'w', label_text = 'Sample Name', command = self.change_sample_name) self.sample_name_entry.pack() right_panel.pack(side='right', fill='both', padx=10) #make a dialog window for sending a command self.command_dialog = Pmw.Dialog(parent = win, buttons = ('OK', 'Cancel'), defaultbutton = 'OK') self.command_dialog.withdraw() self.command_entry = Pmw.EntryField(self.command_dialog.interior(), labelpos = 'w', label_text = 'Command:', ) self.command_entry.pack() #make a dialog window for changing setpoint self.change_setpoint_dialog = Pmw.Dialog(parent = win, buttons = ('OK', 'Cancel'), defaultbutton = 'OK') self.change_setpoint_dialog.withdraw() self.change_setpoint_entry = Pmw.EntryField(self.change_setpoint_dialog.interior(), labelpos = 'w', label_text = 'Setpoint Value:', validate = "real") self.change_setpoint_entry.pack()
class GUI(object): def __init__(self, application): self.app = application self.app.print_comment("Starting GUI interface:") self.app.print_comment("please wait while the application loads...") self._mode = "standby" self._update_counter = 0 self.data = None #build the GUI interface as a seperate window win = Tk() Pmw.initialise(win) #initialize Python MegaWidgets win.withdraw() win.wm_title(WINDOW_TITLE) win.focus_set() #put focus on this new window self.win = win #handle the user hitting the 'X' button self.win.protocol("WM_DELETE_WINDOW", self._close) #build the left panel left_frame = Frame(win) button_bar = Frame(left_frame) self.start_loop_button = Button(button_bar,text="Start Loop" ,command = self.start_loop) self.stop_loop_button = Button(button_bar,text="Stop Loop" ,command = self.stop_loop, state='disabled') self.chanA_pid_mode_button = Button(button_bar,text="Chan A PID = ON",command = self.toggle_chanA_pid_mode, relief = "sunken") self.chanB_pid_mode_button = Button(button_bar,text="Chan B PID = ON",command = self.toggle_chanB_pid_mode, relief = "sunken") self.send_command1_button = Button(button_bar,text="Send Command to TEIS" ,command = self.send_command_to_peltier_pid) self.run_script_button = Button(button_bar,text="Run Script" ,command = self.run_script) self.abort_script_button = Button(button_bar,text="Abort Script",command = self.abort_script, state='disabled') self.setpointGRAD_button = Button(button_bar,text="Set Gradient",command = lambda: self.change_setpoint(mode='GRAD')) self.setpointA_button = Button(button_bar,text="Set Temp. A" ,command = lambda: self.change_setpoint(mode='TEMP_A')) self.setpointB_button = Button(button_bar,text="Set Temp. B" ,command = lambda: self.change_setpoint(mode='TEMP_B')) self.clear_button = Button(button_bar,text="Clear Data" ,command = self.clear_data) self.export_button = Button(button_bar,text="Export Data" ,command = self.export_data) button_pack_opts = {'side':'top','fill':'x', 'expand':'yes', 'anchor':'nw'} self.start_loop_button.pack(**button_pack_opts) self.stop_loop_button.pack(**button_pack_opts) self.chanA_pid_mode_button.pack(**button_pack_opts) self.chanB_pid_mode_button.pack(**button_pack_opts) self.send_command1_button.pack(**button_pack_opts) self.run_script_button.pack(**button_pack_opts) self.abort_script_button.pack(**button_pack_opts) self.setpointGRAD_button.pack(**button_pack_opts) self.setpointA_button.pack(**button_pack_opts) self.setpointB_button.pack(**button_pack_opts) self.clear_button.pack(**button_pack_opts) self.export_button.pack(**button_pack_opts) button_bar.pack(side='top', fill='x', expand='no', anchor='nw') left_frame.pack(side='left', fill='both', padx=10) #build the middle panel mid_panel = Frame(win) self.data_plotter = DataPlotter(mid_panel) self.text_display = TextDisplayBox(mid_panel,text_height=TEXT_DISPLAY_HEIGHT, buffer_size = TEXT_BUFFER_SIZE) self.data_plotter.pack(fill='both',expand='yes') self.text_display.pack(fill='both',expand='yes') mid_panel.pack(fill='both', expand='yes',side='left') #build the right panel right_panel = Frame(win) self.sample_name_entry = Pmw.EntryField(right_panel, labelpos = 'w', label_text = 'Sample Name', command = self.change_sample_name) self.sample_name_entry.pack() right_panel.pack(side='right', fill='both', padx=10) #make a dialog window for sending a command self.command_dialog = Pmw.Dialog(parent = win, buttons = ('OK', 'Cancel'), defaultbutton = 'OK') self.command_dialog.withdraw() self.command_entry = Pmw.EntryField(self.command_dialog.interior(), labelpos = 'w', label_text = 'Command:', ) self.command_entry.pack() #make a dialog window for changing setpoint self.change_setpoint_dialog = Pmw.Dialog(parent = win, buttons = ('OK', 'Cancel'), defaultbutton = 'OK') self.change_setpoint_dialog.withdraw() self.change_setpoint_entry = Pmw.EntryField(self.change_setpoint_dialog.interior(), labelpos = 'w', label_text = 'Setpoint Value:', validate = "real") self.change_setpoint_entry.pack() def launch(self): #run the GUI handling loop IgnoreKeyboardInterrupt() self.win.deiconify() #loop until killed self.win.mainloop() NoticeKeyboardInterrupt() def update_data(self): self.data = self.app.update_data() def update_plot(self): self.data_plotter.update(self.data) def start_loop(self): self.start_loop_button.config(state='disabled') self.stop_loop_button.config(state='normal') self._mode = "loop" self._loop_data_update() self._loop_plot_update() def _loop_data_update(self): if self._mode == "loop": self.update_data() self.win.after(DATA_UPDATE_PERIOD, self._loop_data_update) def _loop_plot_update(self): if self._mode == "loop": self.update_plot() self.win.after(PLOT_UPDATE_PERIOD, self._loop_plot_update) def stop_loop(self): self.stop_loop_button.config(state='disabled') self.start_loop_button.config(state='normal') self._mode = "standby" def toggle_chanA_pid_mode(self): relief = self.chanA_pid_mode_button.cget('relief') if relief == 'raised': self.chanA_pid_mode_button.config(text="Chan A PID = ON", relief='sunken') self.app.print_comment("Toggling Channel A PID state -> 'on'") self.app.peltier_pid.set_pid_control_mode(chan='A', mode = 'on') elif relief == 'sunken': self.chanA_pid_mode_button.config(text="Chan A PID = OFF", relief='raised') self.app.print_comment("Toggling Channel A PID state -> 'off'") self.app.peltier_pid.set_pid_control_mode(chan='A', mode = 'off') def toggle_chanB_pid_mode(self): relief = self.chanB_pid_mode_button.cget('relief') if relief == 'raised': self.chanB_pid_mode_button.config(text="Chan B PID = ON", relief='sunken') self.app.print_comment("Toggling Channel B PID state -> 'on'") self.app.peltier_pid.set_pid_control_mode(chan='B', mode = 'on') elif relief == 'sunken': self.chanB_pid_mode_button.config(text="Chan B PID = OFF", relief='raised') self.app.print_comment("Toggling Channel B PID state -> 'off'") self.app.peltier_pid.set_pid_control_mode(chan='B', mode = 'off') def send_command_to_peltier_pid(self): #self.command_entry.focus_set() result = self.command_dialog.activate() if result == "OK": cmd = self.command_entry.getvalue() resp = self.app.send_command_to_peltier_pid(cmd) def run_script(self): self.stop_loop() fdlg = FileDialog(self.win,title="Run Script") input_dir = "/home/cversek/gitwork/umass-physics/teis/python/src/pyTEIS/scripts/" #FIXME #os.getcwd() filepath = fdlg.go(dir_or_file = input_dir, pattern="*.py", ) if filepath: self.disable_controls() self.abort_script_button.config(state='normal') self._mode = "scripting" self.app.run_script(filepath) self._script_loop() def _script_loop(self): if self._mode == "scripting": while not self.app._script_event_queue.empty(): event_type, obj = self.app._script_event_queue.get() if event_type == "PRINT": self.app.print_comment(obj) elif event_type == "UPDATE_DATA": self.update_data() elif event_type == "UPDATE_PLOT": self.update_plot() elif event_type == "UPDATE": self.update_data() self.update_plot() elif event_type == "CLEAR_DATA": self.clear_data() elif event_type == "ERROR": exc_type, exc, tb = obj msg = traceback.format_exception(exc_type, exc, tb) msg = "".join(msg) self.app.print_comment("Caught Error in Script: %s" % msg) msg = "%s\nCheck the console for the traceback" % (exc,) Pmw.MessageDialog(parent = self.win, title = 'Script Error', message_text = msg, iconpos = 'w', icon_bitmap = 'error',buttons = ('OK',) ) elif event_type == "ABORTED": self.app.print_comment("Script aborted.") else: self.app.print_comment("Got unknown event '%s' with obj=%r" % (event_type,obj)) if self.app._script_thread.is_alive(): self.win.after(SCRIPT_LOOP_UPDATE_PERIOD, self._script_loop) else: self.app.print_comment("Script finished.") self.enable_controls() self._mode = "standby" def abort_script(self): self.app._script_thread.terminate() self.app._script_thread.join() self.abort_script_button.config(state='disabled') self.enable_controls() def disable_controls(self): #disable all controls self.start_loop_button.config(state='disabled') self.chanA_pid_mode_button.config(state='disabled') self.chanB_pid_mode_button.config(state='disabled') self.send_command1_button.config(state='disabled') self.run_script_button.config(state='disabled') self.setpointGRAD_button.config(state='disabled') self.setpointA_button.config(state='disabled') self.setpointB_button.config(state='disabled') self.clear_button.config(state='disabled') self.export_button.config(state='disabled') def enable_controls(self): #enable most controls self.start_loop_button.config(state='normal') self.chanA_pid_mode_button.config(state='normal') self.chanB_pid_mode_button.config(state='normal') self.send_command1_button.config(state='normal') self.run_script_button.config(state='normal') self.setpointGRAD_button.config(state='normal') self.setpointA_button.config(state='normal') self.setpointB_button.config(state='normal') self.clear_button.config(state='normal') self.export_button.config(state='normal') def print_to_text_display(self, text, eol='\n'): self.text_display.print_text(text, eol=eol) def warn(self, msg): warnings.warn(msg) self.app.print_comment("Warning: %s" % msg) def change_setpoint(self, mode): result = self.change_setpoint_dialog.activate() if result == "OK": temp = self.change_setpoint_entry.getvalue() temp = float(temp) if mode == 'GRAD': self.app.change_gradient_setpoint(temp) elif mode == 'TEMP_A': self.app.change_setpointA(temp) elif mode == 'TEMP_B': self.app.change_setpointB(temp) def clear_data(self): self.app.metadata['start_timestamp'] = time.time() self.app._init_data() self.data_plotter.setup() def change_sample_name(self): sample_name = self.sample_name_entry.getvalue() self.app.metadata['sample_name'] = sample_name self.data_plotter.change_title(new_title = sample_name) def export_data(self): self.app.print_comment("exporting data...") data = self.app.data metadata = self.app.metadata d = datetime.datetime.fromtimestamp(metadata['start_timestamp']) d = d.strftime("%Y-%m-%d") default_filename = "%s_%s_peltier_test.csv" % (d,metadata['sample_name']) fdlg = SaveFileDialog(self.win,title="Export Data") output_dir = os.getcwd() filename = fdlg.go(dir_or_file = output_dir, pattern="*.csv", default=default_filename, key = None ) if filename: self.app.export_data(filename) def _close(self): self.win.destroy()