Example #1
0
        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)
Example #2
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)
Example #3
0
 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
Example #4
0
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()
Example #5
0
    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)
Example #6
0
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()
Example #7
0
    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()
Example #8
0
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()