示例#1
0
class CV:
    """ Base class for impedance analysis experiments """
    def __init__(self, master, devman, in_batch=False):
        self.master = master
        self.dm = devman
        self.id = 'CV'

        # Create the main variables of the class
        self.in_batch = in_batch
        self.create_variables()

        self.plot_format = {
            'ratios': (1, 1),
            'xlabel': 'Frequency (Hz)',
            'x_scale': 'log',
            'Ch1_ylabel': '|Z| (Ohm)',
            'Ch2_ylabel': 'Tz (deg)',
            'Ch1_scale': 'log',
            'Ch2_scale': 'linear'
        }

        top0 = 'Bias = '
        top1 = '0 V'
        col0 = self.plot_format['xlabel']
        col1 = self.plot_format['Ch1_ylabel']
        col2 = self.plot_format['Ch2_ylabel']
        self.header = str(top0) + '= ' + str(top1) + '\n' + str(
            col0) + '\t' + str(col1) + '\t' + str(col2)
        self.header.encode('utf-8')

        self.extension = 'txt'
        self.batch = Batch(self.master, self.dm,
                           fileheader=self.header)  ## Dummy mode

        # Create the interface
        self.create_interface()

        # We load the dummy devices by default
        self.fill_devices()

    def quit(self):
        """ Safe closing of the devices. The devices must be closed by the Device Manager, not directly,
        so they are registered as "free".
        """

        if self.za is not None:
            self.dm.close_device(self.za)

        self.batch.window.destroy()

    def create_variables(self):

        # Data variables
        self.record = None

        # Hardware variables
        self.za = None

        # Acquisition variables
        self.n_averages = 10
        self.stop = True

    def create_menu_bar(self):
        """ Add elements to the master menu bar
        """

        # Hardware menu
        self.master.menu_hardware.add_command(
            label='ZA', command=lambda: self.za.interface(self.master))
        self.master.menu_hardware.entryconfig("ZA", state="disabled")

        # Batch menu
        if not self.in_batch:
            self.master.menu_batch.add_command(label='Disable',
                                               command=self.batch.disable)
            self.master.menu_batch.add_command(
                label='Temperature',
                command=lambda: self.new_batch('Temperature'))
            self.master.menu_batch.add_command(
                label='Time', command=lambda: self.new_batch('Time'))

    def new_batch(self, batch_mode):
        """ Shows current batch window or, if a different batch is chosen, destroys the old one and creates a new one
        for the new batch mode
        :param batch_mode: the selected type of batch
        :return: None
        """
        if self.batch.mode == batch_mode:
            self.batch.show()
        else:
            self.batch.window.destroy()
            self.batch = Batch(self.master,
                               self.dm,
                               fileheader=self.header,
                               mode=batch_mode)

    def create_interface(self):
        """ Create GUI for the CV experiment
        :return: None
        """
        # Add elements to the menu bar
        self.create_menu_bar()

        cv_frame = ttk.Frame(self.master.control_frame)
        cv_frame.grid(column=0, row=0, sticky=(tk.EW))
        cv_frame.columnconfigure(0, weight=1)

        # Hardware widgets
        hardware_frame = ttk.Labelframe(cv_frame,
                                        text='Selected hardware:',
                                        padding=(0, 5, 0, 15))
        hardware_frame.grid(column=0, row=0, sticky=(tk.EW))
        hardware_frame.columnconfigure(0, weight=1)

        self.za_var = tk.StringVar()
        self.za_box = ttk.Combobox(master=hardware_frame,
                                   textvariable=self.za_var,
                                   state="readonly")
        self.za_box.grid(column=0, row=0, sticky=(tk.EW))
        self.za_box.bind('<<ComboboxSelected>>', self.select_za)

        # Scan mode widgets ---------------------------------
        mode_frame = ttk.Labelframe(cv_frame,
                                    text='Scan mode:',
                                    padding=(0, 5, 0, 15))
        mode_frame.grid(column=0, row=1, sticky=(tk.EW))
        mode_frame.columnconfigure(1, weight=1)

        self.fixed_var = tk.StringVar()
        self.fixed_var.set('bias')
        ttk.Radiobutton(mode_frame,
                        text="Fixed Bias",
                        variable=self.fixed_var,
                        value='bias',
                        command=self.mode).grid(column=0,
                                                row=0,
                                                sticky=(tk.E, tk.W, tk.S))
        ttk.Radiobutton(mode_frame,
                        text="Fixed Frequency",
                        variable=self.fixed_var,
                        value='freq',
                        command=self.mode).grid(column=1,
                                                row=0,
                                                sticky=(tk.E, tk.W, tk.S))

        self.fixed_value_var = tk.StringVar()
        self.fixed_value_var.set('0')
        self.fixed_value_label = ttk.Label(mode_frame, text='Set bias (V): ')
        self.fixed_value_label.grid(column=0, row=1, sticky=(tk.E, tk.W, tk.S))
        ttk.Entry(master=mode_frame,
                  width=10,
                  textvariable=self.fixed_value_var).grid(column=1,
                                                          row=1,
                                                          sticky=(tk.E, tk.W,
                                                                  tk.S))

        self.osc_amp_var = tk.StringVar()
        self.osc_amp_var.set('0.01')
        self.osc_amp_label = ttk.Label(mode_frame, text='Set AC bias (V): ')
        self.osc_amp_label.grid(column=0, row=2, sticky=(tk.E, tk.W, tk.S))
        ttk.Entry(master=mode_frame, width=10,
                  textvariable=self.osc_amp_var).grid(column=1,
                                                      row=2,
                                                      sticky=(tk.E, tk.W,
                                                              tk.S))

        # Ch1 setup widgets --------------------------------
        Ch1_frame = ttk.Labelframe(cv_frame,
                                   text='Ch1 setup:',
                                   padding=(0, 5, 0, 15))
        Ch1_frame.grid(column=0, row=2, sticky=(tk.EW))
        Ch1_frame.columnconfigure(1, weight=1)
        self.Ch1_param_var = tk.StringVar()
        self.Ch1_param_var.set('Z')
        self.Ch1_scale_var = tk.StringVar()
        self.Ch1_scale_var.set('log')
        ttk.Radiobutton(Ch1_frame,
                        text="Z",
                        variable=self.Ch1_param_var,
                        value='Z',
                        command=self.channel_param).grid(column=0,
                                                         row=0,
                                                         sticky=(tk.E, tk.W,
                                                                 tk.S))
        ttk.Radiobutton(Ch1_frame,
                        text="Cs",
                        variable=self.Ch1_param_var,
                        value='Cs',
                        command=self.channel_param).grid(column=1,
                                                         row=0,
                                                         sticky=(tk.E, tk.W,
                                                                 tk.S))
        ttk.Radiobutton(Ch1_frame,
                        text="Cp",
                        variable=self.Ch1_param_var,
                        value='Cp',
                        command=self.channel_param).grid(column=2,
                                                         row=0,
                                                         sticky=(tk.E, tk.W,
                                                                 tk.S))
        ttk.Radiobutton(Ch1_frame,
                        text="R",
                        variable=self.Ch1_param_var,
                        value='R',
                        command=self.channel_param).grid(column=3,
                                                         row=0,
                                                         sticky=(tk.E, tk.W,
                                                                 tk.S))
        self.idc_button = ttk.Radiobutton(Ch1_frame,
                                          text="Idc",
                                          variable=self.Ch1_param_var,
                                          value='Idc',
                                          command=self.channel_param)
        self.idc_button.grid(column=4, row=0, sticky=(tk.E, tk.W, tk.S))
        self.idc_button.configure(state='disabled')
        ttk.Radiobutton(Ch1_frame,
                        text="Linear",
                        variable=self.Ch1_scale_var,
                        value='linear',
                        command=self.scan_scale).grid(column=0,
                                                      row=1,
                                                      sticky=(tk.E, tk.W,
                                                              tk.S))
        ttk.Radiobutton(Ch1_frame,
                        text="Log10",
                        variable=self.Ch1_scale_var,
                        value='log',
                        command=self.scan_scale).grid(column=1,
                                                      row=1,
                                                      sticky=(tk.E, tk.W,
                                                              tk.S))

        # Ch2 setup widgets --------------------------------
        Ch2_frame = ttk.Labelframe(cv_frame,
                                   text='Ch2 setup:',
                                   padding=(0, 5, 0, 15))
        Ch2_frame.grid(column=0, row=3, sticky=(tk.EW))
        Ch2_frame.columnconfigure(1, weight=1)
        self.Ch2_param_var = tk.StringVar()
        self.Ch2_param_var.set('Tz')
        self.Ch2_scale_var = tk.StringVar()
        self.Ch2_scale_var.set('linear')
        ttk.Radiobutton(Ch2_frame,
                        text="Tz",
                        variable=self.Ch2_param_var,
                        value='Tz',
                        command=self.channel_param).grid(column=0,
                                                         row=0,
                                                         sticky=(tk.E, tk.W,
                                                                 tk.S))
        ttk.Radiobutton(Ch2_frame,
                        text="Rs",
                        variable=self.Ch2_param_var,
                        value='Rs',
                        command=self.channel_param).grid(column=1,
                                                         row=0,
                                                         sticky=(tk.E, tk.W,
                                                                 tk.S))
        ttk.Radiobutton(Ch2_frame,
                        text="Rp",
                        variable=self.Ch2_param_var,
                        value='Rp',
                        command=self.channel_param).grid(column=2,
                                                         row=0,
                                                         sticky=(tk.E, tk.W,
                                                                 tk.S))
        ttk.Radiobutton(Ch2_frame,
                        text="X",
                        variable=self.Ch2_param_var,
                        value='X',
                        command=self.channel_param).grid(column=3,
                                                         row=0,
                                                         sticky=(tk.E, tk.W,
                                                                 tk.S))
        ttk.Radiobutton(Ch2_frame,
                        text="Linear",
                        variable=self.Ch2_scale_var,
                        value='linear',
                        command=self.scan_scale).grid(column=0,
                                                      row=1,
                                                      sticky=(tk.E, tk.W,
                                                              tk.S))
        self.Ch2log_button = ttk.Radiobutton(Ch2_frame,
                                             text="Log10",
                                             variable=self.Ch2_scale_var,
                                             value='log',
                                             command=self.scan_scale)
        self.Ch2log_button.grid(column=1, row=1, sticky=(tk.E, tk.W, tk.S))
        self.Ch2log_button.configure(state='disabled')

        # Sweep setup widgets ---------------------------------
        sweep_frame = ttk.Labelframe(cv_frame,
                                     text='Sweep setup:',
                                     padding=(0, 5, 0, 15))
        sweep_frame.columnconfigure(0, weight=1)
        sweep_frame.grid(column=0, row=4, sticky=(tk.EW))

        self.sweep_start_var = tk.StringVar()
        self.sweep_start_var.set('20')
        self.sweep_start_label = ttk.Label(sweep_frame,
                                           text='Frequency start (Hz): ')
        self.sweep_start_label.grid(column=0, row=0, sticky=(tk.E, tk.W, tk.S))
        ttk.Entry(master=sweep_frame,
                  width=10,
                  textvariable=self.sweep_start_var).grid(column=1,
                                                          row=0,
                                                          sticky=(tk.E, tk.W,
                                                                  tk.S))

        self.sweep_stop_var = tk.StringVar()
        self.sweep_stop_var.set('1e07')
        self.sweep_stop_label = ttk.Label(sweep_frame,
                                          text='Frequency stop (Hz): ')
        self.sweep_stop_label.grid(column=0, row=1, sticky=(tk.E, tk.W, tk.S))
        ttk.Entry(master=sweep_frame,
                  width=10,
                  textvariable=self.sweep_stop_var).grid(column=1,
                                                         row=1,
                                                         sticky=(tk.E, tk.W,
                                                                 tk.S))

        self.x_scale_var = tk.StringVar()
        self.x_scale_var.set('log')
        ttk.Radiobutton(sweep_frame,
                        text="Linear",
                        variable=self.x_scale_var,
                        value='linear',
                        command=self.scan_scale).grid(column=0,
                                                      row=2,
                                                      sticky=(tk.E, tk.W,
                                                              tk.S))
        self.xlog_button = ttk.Radiobutton(sweep_frame,
                                           text="Log10",
                                           variable=self.x_scale_var,
                                           value='log',
                                           command=self.scan_scale)
        self.xlog_button.grid(column=1, row=2, sticky=(tk.E, tk.W, tk.S))

        self.nop_var = tk.StringVar()
        self.nop_var.set('400')
        self.nop_label = ttk.Label(sweep_frame, text='Number of points: ')
        self.nop_label.grid(column=0, row=3, sticky=(tk.E, tk.W, tk.S))
        ttk.Entry(master=sweep_frame, width=10,
                  textvariable=self.nop_var).grid(column=1,
                                                  row=3,
                                                  sticky=(tk.E, tk.W, tk.S))

        self.navg_var = tk.StringVar()
        self.navg_var.set('3')
        self.navg_label = ttk.Label(sweep_frame,
                                    text='Number of point averages: ')
        self.navg_label.grid(column=0, row=4, sticky=(tk.E, tk.W, tk.S))
        ttk.Entry(master=sweep_frame, width=10,
                  textvariable=self.navg_var).grid(column=1,
                                                   row=4,
                                                   sticky=(tk.E, tk.W, tk.S))

        self.sweep_dir_var = tk.StringVar()
        self.sweep_dir_var.set('up')
        ttk.Radiobutton(sweep_frame,
                        text="Sweep Up",
                        variable=self.sweep_dir_var,
                        value='up').grid(column=0,
                                         row=5,
                                         sticky=(tk.E, tk.W, tk.S))
        ttk.Radiobutton(sweep_frame,
                        text="Sweep Down",
                        variable=self.sweep_dir_var,
                        value='down').grid(column=1,
                                           row=5,
                                           sticky=(tk.E, tk.W, tk.S))

        #self.abort_button = ttk.Button(master=sweep_frame, width=10, text='ABORT!', command=self.abort)
        #self.abort_button.grid(column=0, row=98, sticky=( tk.E, tk.SW, tk.S))

        # The "Run" button is only available if not in batch mode
        if not self.in_batch:
            self.run_button = ttk.Button(master=sweep_frame,
                                         width=10,
                                         text='Run',
                                         command=self.start_stop_scan)
            self.run_button.grid(column=1, row=98, sticky=(tk.E, tk.SW, tk.S))

    def update_header(self):
        """ Updates header in output text file
        :return: None
        """
        top0 = self.fixed_var.get()
        top1 = self.fixed_value_var.get()

        if top0 == 'bias':
            top2 = 'V'
        else:
            top2 = 'Hz'

        col0 = self.plot_format['xlabel']
        col1 = self.plot_format['Ch1_ylabel']
        col2 = self.plot_format['Ch2_ylabel']
        self.header = str(top0) + '= ' + str(top1) + ' ' + str(
            top2) + '\n' + str(col0) + '\t' + str(col1) + '\t' + str(col2)
        self.header.encode('utf-8')

    def fill_devices(self):
        """ Fills the device selectors with the corresponding type of devices
        :return: None
        """
        self.za_box['values'] = self.dm.get_devices(['ZA'])
        self.za_box.current(0)

        self.select_za()

    def select_za(self, *args):
        """ When the Z analyser selector changes, this function updates some variables and the graphical interface
        to adapt it to the selected device.
        :param args: Dummy variable that does nothing but must exist (?)
        :return: None
        """

        if self.za is not None:
            self.dm.close_device(self.za)

        dev_name = self.za_var.get()
        self.za = self.dm.open_device(dev_name)

        if self.za is None:
            self.za_box.current(0)
            self.za = self.dm.open_device(self.za_var.get())

        elif self.dm.current_config[dev_name]['Type'] == 'ZA':
            pass
        ## Line required to avoid 'busy device' error

        else:
            self.za_box.current(0)
            self.za = self.dm.open_device(self.za_var.get())

        # If the device has an interface to set options, we link it to the entry in the menu
        interface = getattr(self.za, "interface", None)
        if callable(interface):
            self.master.menu_hardware.entryconfig("ZA", state="normal")
        else:
            self.master.menu_hardware.entryconfig("ZA", state="disabled")

    def refresh_buttons(self):
        """ Updates button features when different experimental setups are selected
	:return: None
        """
        if self.fixed_var.get() == 'freq':
            self.x_scale_var.set('linear')
            self.scan_scale(
            )  ## Updates plot_format, which is used to pass parameters to the device
            self.xlog_button.configure(state='disabled')
            self.idc_button.configure(state='normal')
        else:
            self.xlog_button.configure(state='normal')
            self.idc_button.configure(state='disabled')

        if self.Ch2_param_var.get() == 'Tz' or self.Ch2_param_var.get() == 'X':
            self.Ch2_scale_var.set('linear')
            self.scan_scale(
            )  ## Updates plot_format, which is used to pass parameters to the device
            self.Ch2log_button.configure(state='disabled')
        else:
            self.Ch2log_button.configure(state='normal')

    def mode(self):
        """ When the experimental setup is changed, this function updates some internal variables and the graphical interface
        :return: None
        """

        if self.fixed_var.get() == 'bias':
            self.sweep_start_label['text'] = 'Frequency start (Hz): '
            self.sweep_stop_label['text'] = 'Frequency stop (Hz): '
            self.sweep_start_var.set('20')
            self.sweep_stop_var.set('1e7')
            self.fixed_value_label['text'] = 'Set Bias (V): '
            self.fixed_value_var.set('0')
            self.osc_amp_var.set('0.01')
            self.plot_format['xlabel'] = 'Frequency (Hz)'
            self.Ch1_param_var.set(
                'Z')  ## Required to avoid havind Idc selected and disabled

        elif self.fixed_var.get() == 'freq':
            self.sweep_start_label['text'] = 'Bias start (V): '
            self.sweep_stop_label['text'] = 'Bias stop (V): '
            self.sweep_start_var.set('-0.5')
            self.sweep_stop_var.set('0.5')
            self.fixed_value_label['text'] = 'Set Frequency (Hz): '
            self.fixed_value_var.set('1e5')
            self.plot_format['xlabel'] = 'Bias (V)'
            self.fixed_value_var.set('1e5')
            self.osc_amp_var.set('0.01')

        self.refresh_buttons()
        self.master.update_plot_axis(self.plot_format)

    def channel_param(self):
        """ When the channel parameters are changed, this function updates some internal variables and the graphical interface
        :return: None
        """
        ## Channel 1 labels ----------------------------------
        if self.Ch1_param_var.get() == 'Z':
            self.plot_format['Ch1_ylabel'] = '|Z| (Ohm)'
        elif self.Ch1_param_var.get() == 'Cp':
            self.plot_format['Ch1_ylabel'] = 'Cp (F)'
        elif self.Ch1_param_var.get() == 'Cs':
            self.plot_format['Ch1_ylabel'] = 'Cs (F)'
        elif self.Ch1_param_var.get() == 'R':
            self.plot_format['Ch1_ylabel'] = 'Zreal (Ohm)'
        elif self.Ch1_param_var.get() == 'Idc':
            self.plot_format['Ch1_ylabel'] = 'Idc (A)'
        ## Channel 2 labels ----------------------------------
        if self.Ch2_param_var.get() == 'Tz':
            self.plot_format['Ch2_ylabel'] = 'Tz (deg)'
        elif self.Ch2_param_var.get() == 'Rp':
            self.plot_format['Ch2_ylabel'] = 'Rp (Ohm)'
        elif self.Ch2_param_var.get() == 'Rs':
            self.plot_format['Ch2_ylabel'] = 'Rs (Ohm)'
        elif self.Ch2_param_var.get() == 'X':
            self.plot_format['Ch2_ylabel'] = 'Zimag (Ohm)'

        self.refresh_buttons()
        self.master.update_plot_axis(self.plot_format)

    def scan_scale(self):
        """ When the scan scales are changed, this function updates some internal variables and the graphical interface
        :return: None
        """
        ## X-axis scale ----------------------------------
        if self.x_scale_var.get() == 'linear':
            self.plot_format['x_scale'] = 'linear'
        elif self.x_scale_var.get() == 'log':
            self.plot_format['x_scale'] = 'log'
        ## Channel 1 scale ----------------------------------
        if self.Ch1_scale_var.get() == 'linear':
            self.plot_format['Ch1_scale'] = 'linear'
        elif self.Ch1_scale_var.get() == 'log':
            self.plot_format['Ch1_scale'] = 'log'
        ## Channel 2 scale ----------------------------------
        if self.Ch2_scale_var.get() == 'linear':
            self.plot_format['Ch2_scale'] = 'linear'
        elif self.Ch2_scale_var.get() == 'log':
            self.plot_format['Ch2_scale'] = 'log'

        self.master.update_plot_axis(self.plot_format)

    def start_stop_scan(self):
        """ Starts and stops a scan
        :return: None
        """
        self.run_check()
        if False not in self.run_ok:

            if self.stop:
                self.stop = False

                if self.batch.ready:
                    self.run_button['text'] = 'Stop batch'
                else:
                    self.run_button['state'] = 'disabled'
                self.prepare_scan()
            else:
                if self.batch.ready:
                    self.batch.ready = False
                    self.stop = True
                    self.run_button['text'] = 'Run'
                else:
                    self.run_button['state'] = 'enabled'
                self.finish_scan()

    def prepare_scan(self):
        """ Any scan is divided in three stages:
        1) Prepare the conditions of the scan (this function), creating all relevant variables.
        2) Calls the 'start_scan' function to begin the measurement
        :return: None
        """
        ## If inputs are OK, fill up the options dictionary and proceed.
        self.options = {
            'fixed': self.mode,
            'fixed_value': self.fixed_value,
            'start': self.sweep_start,
            'stop': self.sweep_stop,
            'nop': self.nop,
            'navg': self.navg,
            'osc_amp': self.osc_amp,
            'sweep_dir': self.sweep_dir
        }

        # # If we are in a batch, we proceed to the next point
        if self.batch.ready:
            self.batch.batch_proceed()

        print('Starting scan...')

        # Create the record array. In this case it is irrelevant
        self.record = np.zeros((self.options['nop'] + 1, 4))
        self.record[:, 0] = np.linspace(0, 1, self.options['nop'] + 1)
        self.record[:, 1] = np.ones_like(self.record[:, 0])
        self.record[:, 2] = np.ones_like(self.record[:, 0])
        self.master.prepare_meas(self.record)
        self.start_scan()

    def start_scan(self):
        """ Begins the experiment
		:return: None
        """

        self.za.setup_measurement(self.plot_format, self.options)
        self.za.measure(self.options)
        self.get_data()

    def get_data(self):
        """ Retireves the data collected during the scan
        :return: None
        """

        data0, data1, data2 = self.za.return_data()

        self.record = np.zeros((len(data0), 3))
        self.record[:, 0] = data0
        self.record[:, 1] = data1
        self.record[:, 2] = data2

        self.master.update_plot(self.record)
        self.finish_scan()

    def finish_scan(self):
        """ Finish the scan, updating some global variables, saving the data in the temp file and offering to save the
        data somewhere else.
        :return: None
        """

        if self.batch.ready:
            self.master.finish_meas(self.record, finish=False)
            self.batch.batch_wrapup(self.record)
        else:
            self.master.finish_meas(self.record, finish=True)
            self.stop = True

        if self.stop or not self.batch.ready:
            # We reduce the number of counts in the batch to resume at the unfinished point if necessary
            # In other words, it repeats the last, un-finished measurement
            self.batch.count = self.batch.count - 1
            self.run_button['text'] = 'Run'
            self.run_button['state'] = 'enabled'

        else:
            self.prepare_scan()

    def check_inputs(self, parameter, value, min, max):
        """ Check the values of relevant scan parameters and gives an error if they are out of range
	
        :param parameter: the name of the parameter to check
        :param: value: the value stored in the parameter variable
        :param: min: the minimum value allowed for the parameter
        :param: max: the maximum value allowed for the parameter
        :return: Error: parameter out of range
        """

        if value < min or value > max:
            messagebox.showerror("Error", parameter + " is out of range!")
            self.run_button['state'] = 'enabled'
            self.run_ok.append(False)

        else:
            self.run_ok.append(True)

    def run_check(self):
        """ Calls the 'check_inputs' function on all of relevant scan parameters
	
        :return: Error - parameter out of range
        """

        self.mode = self.fixed_var.get()
        self.fixed_value = float(self.fixed_value_var.get())
        self.sweep_start = float(self.sweep_start_var.get())
        self.sweep_stop = float(self.sweep_stop_var.get())
        self.nop = int(self.nop_var.get())
        self.navg = int(self.navg_var.get())
        self.osc_amp = float(self.osc_amp_var.get())
        self.sweep_dir = self.sweep_dir_var.get()

        ## Check that inputs are within safe/correct limits
        self.run_ok = []
        if self.mode == 'bias':
            self.check_inputs('DC bias', self.fixed_value, -10, 10)
            self.check_inputs('Minimun frequency', self.sweep_start, 20, 1e7)
            self.check_inputs('Maximun frequency', self.sweep_stop, 20, 1e7)
        elif self.mode == 'freq':
            self.check_inputs('CW frequency', self.fixed_value, 20, 1e7)
            self.check_inputs('Minimun DC bias', self.sweep_start, -10, 10)
            self.check_inputs('Maximun frequency', self.sweep_stop, -10, 10)

        self.check_inputs('Number of points', self.nop, 1, 1000)
        self.check_inputs('Number of averages', self.navg, 1, 100)
        self.check_inputs('AC bias', self.osc_amp, 0.001, 1)

    def abort(self):
        """ Aborts scan
	
        :return: None
        """
        self.za.abort_sweep()
        self.get_data()
示例#2
0
class IV:
    """ Base class for IV experiments """

    def __init__(self, master, devman, in_batch=False):

        self.master = master
        self.dm = devman
        self.id = 'iv'

        # Create the main variables of the class
        self.in_batch = in_batch
        self.create_variables()

        # Pre-load the batch, witout creating any interface
        self.header = 'Voltage (V)\tAbs(Current) (A)\tCurrent (A)'
        self.extension = 'iv'
        self.batch = Batch(self.master, self.dm, fileheader=self.header)

        # Create the interface
        self.create_interface()
        self.plot_format = {'ratios': (1, 1),
                            'xlabel': 'Voltage (V)',
                            'Ch1_ylabel': 'Abs(Current) (A)',
                            'Ch2_ylabel': 'Current (A)',
                            'Ch1_scale': 'log',
                            'Ch2_scale': 'linear'}

        # We load the dummy devices by default
        self.fill_devices()

    def quit(self):
        """ Safe closing of the devices. The devices must be closed by the Device Manager, not directly,
        so they are registered as "free".
        """

        if self.smu is not None:
            self.dm.close_device(self.smu)

        self.batch.window.destroy()

    def create_variables(self):

        # Data variables
        self.record = None

        # Hardware variables
        self.smu = None

        # Adquisition variables
        self.integration_time = 300
        self.delay = 100
        self.stop = True

    def create_menu_bar(self):
        """ Add elememnts to the master menubar
        """

        # Hardware menu
        self.master.menu_hardware.add_command(label='SMU', command=lambda: self.smu.interface(self.master))
        self.master.menu_hardware.entryconfig("SMU", state="disabled")

        # Batch menu
        if not self.in_batch:
            self.master.menu_batch.add_command(label='Disable', command=self.batch.disable)
            self.master.menu_batch.add_command(label='Temperature', command=lambda: self.new_batch('Temperature'))
            self.master.menu_batch.add_command(label='Time', command=lambda: self.new_batch('Time'))

    def new_batch(self, batch_mode):
        """ Shows current batch window or, if a different batch is chosen, destroys the old one and creates a new one
        for the new batch mpde

        :param batch_mode: the selected type of batch
        :return: None
        """
        if self.batch.mode == batch_mode:
            self.batch.show()
        else:
            self.batch.window.destroy()
            self.batch = Batch(self.master, self.dm, mode=batch_mode, fileheader=self.header)

    def create_interface(self):

        # Add elements to the menu bar
        self.create_menu_bar()

        iv_frame = ttk.Frame(self.master.control_frame)
        iv_frame.grid(column=0, row=0, sticky=(tk.EW))
        iv_frame.columnconfigure(0, weight=1)

        # Hardware widgets
        hardware_frame = ttk.Labelframe(iv_frame, text='Selected hardware:', padding=(0, 5, 0, 15))
        hardware_frame.grid(column=0, row=0, sticky=(tk.EW))
        hardware_frame.columnconfigure(0, weight=1)

        self.smu_var = tk.StringVar()
        self.smu_box = ttk.Combobox(master=hardware_frame, textvariable=self.smu_var, state="readonly")
        self.smu_box.grid(column=0, row=0, sticky=(tk.EW))
        self.smu_box.bind('<<ComboboxSelected>>', self.select_smu)

        # Set widgets ---------------------------------
        set_frame = ttk.Labelframe(iv_frame, text='Set:', padding=(0, 5, 0, 15))
        set_frame.grid(column=0, row=1, sticky=(tk.EW))
        set_frame.columnconfigure(1, weight=1)

        self.integration_time_button = ttk.Button(master=set_frame, text='Integration time (ms)', command=self.update_integration_time)
        self.integration_time_list = ttk.Combobox(set_frame, state="readonly", width=10)

        self.waiting_time_button = ttk.Button(master=set_frame, text='Waiting time (ms)', command=self.update_waiting_time)
        self.waiting_time_entry = ttk.Entry(master=set_frame, width=10)
        self.waiting_time_entry.insert(0, '10')

        self.integration_time_button.grid(column=0, row=2, sticky=(tk.EW))
        self.integration_time_list.grid(column=1, row=2, sticky=(tk.EW))
        self.waiting_time_button.grid(column=0, row=3, sticky=(tk.EW))
        self.waiting_time_entry.grid(column=1, row=3, sticky=(tk.EW))

        self.source_var = tk.StringVar()
        self.source_var.set('v')
        ttk.Radiobutton(set_frame, text="Voltage (V)", variable=self.source_var, value='v', command=self.mode).grid(column=0, row=4, sticky=(tk.E, tk.W, tk.S))
        ttk.Radiobutton(set_frame, text="Current (A)", variable=self.source_var, value='i', command=self.mode).grid(column=1, row=4, sticky=(tk.E, tk.W, tk.S))

        # Scan widgets ---------------------------------
        scan_frame = ttk.Labelframe(iv_frame, text='Scan:', padding=(0, 5, 0, 15))
        scan_frame.columnconfigure(0, weight=1)
        scan_frame.grid(column=0, row=3, sticky=(tk.EW))

        self.start_var = tk.StringVar()
        self.start_var.set('0.00')
        self.start_label = ttk.Label(scan_frame, text='Start (V): ')
        self.start_label.grid(column=0, row=0, sticky=(tk.E, tk.W, tk.S))
        ttk.Entry(master=scan_frame, width=10, textvariable=self.start_var).grid(column=1, row=0, sticky=(tk.E, tk.W, tk.S))

        self.stop_var = tk.StringVar()
        self.stop_var.set('1.00')
        self.stop_label = ttk.Label(scan_frame, text='Stop (V): ')
        self.stop_label.grid(column=0, row=1, sticky=(tk.E, tk.W, tk.S))
        ttk.Entry(master=scan_frame, width=10, textvariable=self.stop_var).grid(column=1, row=1, sticky=(tk.E, tk.W, tk.S))

        self.step_var = tk.StringVar()
        self.step_var.set('0.05')
        self.step_label = ttk.Label(scan_frame, text='Step (V): ')
        self.step_entry = ttk.Entry(master=scan_frame, width=10, textvariable=self.step_var)

        self.points_label = ttk.Label(scan_frame, text='Points (per decade): ')
        self.points_list = ttk.Combobox(scan_frame, state="readonly", width=10)

        self.compliance_var = tk.StringVar()
        self.compliance_var.set('0.01')
        self.compliance_label = ttk.Label(scan_frame, text='Compliance (A): ')
        self.compliance_label.grid(column=0, row=3, sticky=(tk.E, tk.W, tk.S))
        ttk.Entry(scan_frame, width=10, textvariable=self.compliance_var).grid(column=1, row=3, sticky=(tk.E, tk.W, tk.S))

        # Combobox containing the available hardware
        ttk.Label(scan_frame, text='Range: ').grid(column=0, row=4, sticky=(tk.E, tk.W, tk.S))
        self.range_list = ttk.Combobox(scan_frame,  state="readonly", width=10)
        self.range_list.grid(column=1, row=4, sticky=(tk.E, tk.W, tk.S))

        # The "Run" button is only available if not in batch mode
        if not self.in_batch:
            self.run_button = ttk.Button(master=scan_frame, width=10, text='Run', command=self.start_stop_scan)
            self.run_button.grid(column=1, row=98, sticky=( tk.E, tk.W, tk.S))

    def fill_devices(self):
        """ Fills the device selectors with the corresponding type of devices

        :return: None
        """
        self.smu_box['values'] = self.dm.get_devices(['SMU'])
        self.smu_box.current(0)

        self.select_smu()

    def select_smu(self, *args):
        """ When the SMU selector changes, this function updates some variables and the graphical interface
        to adapt it to the selected device.

        :param args: Dummy variable that does nothing but must exist (?)
        :return: None
        """

        if self.smu is not None:
            self.dm.close_device(self.smu)

        dev_name = self.smu_var.get()
        self.smu = self.dm.open_device(dev_name)

        if self.smu is None:
            self.smu_box.current(0)
            self.smu = self.dm.open_device(self.smu_var.get())

        elif self.dm.current_config[dev_name]['Type'] == 'SMU':
            self.source_var.set('v')
            self.range_list['values'] = self.smu.i_range
            self.range_list.current(0)
            self.integration_time_list['values'] = self.smu.int_time
            self.integration_time_list.current(0)
            self.points_list['values'] = self.smu.log_points
            self.points_list.current(1)
            self.mode()

        else:
            self.smu_box.current(0)
            self.smu = self.dm.open_device(self.smu_var.get())

        # If the device has an interface to set options, we link it to the entry in the menu
        interface = getattr(self.smu, "interface", None)
        if callable(interface):
            self.master.menu_hardware.entryconfig("SMU", state="normal")
        else:
            self.master.menu_hardware.entryconfig("SMU", state="disabled")

    def mode(self):
        """ When the bias mode is changed, this function updates some internal variables and the graphical interface

        :return: None
        """

        if self.source_var.get() == 'v':
            self.start_label['text'] = 'Start (V): '
            self.stop_label['text'] = 'Stop (V): '
            self.compliance_label['text'] = 'Compliance (A): '
            self.range_list['values'] = self.smu.i_range
            self.start_var.set('0')
            self.stop_var.set('1')
            self.compliance_var.set('1e-3')
            self.step_label.grid(column=0, row=2, sticky=(tk.E, tk.W, tk.S))
            self.step_entry.grid(column=1, row=2, sticky=(tk.E, tk.W, tk.S))
            self.points_label.grid_forget()
            self.points_list.grid_forget()

        else:
            self.start_label['text'] = 'Start (A): '
            self.stop_label['text'] = 'Stop (A): '
            self.compliance_label['text'] = 'Compliance (V): '
            self.range_list['values'] = self.smu.v_range
            self.start_var.set('1e-9')
            self.stop_var.set('1e-3')
            self.compliance_var.set('2')
            self.points_label.grid(column=0, row=2, sticky=(tk.E, tk.W, tk.S))
            self.points_list.grid(column=1, row=2, sticky=(tk.E, tk.W, tk.S))
            self.step_label.grid_forget()
            self.step_entry.grid_forget()

    def update_integration_time(self):
        """ Sets the value of the integration time. Informtaion IS sent to the instrument.

        :return:
        """
        self.integration_time = int(self.integration_time_list.current())
        self.smu.update_integration_time(self.integration_time)
        
    def update_waiting_time(self):
        """ Sets the value of the delay between applying a bias and taking the measurement. Informtaion IS sent to the instrument.

        :return:
        """
        self.delay = int(self.waiting_time_entry.get())
        self.smu.update_waiting_time(self.delay)

    def update_compliance_and_range(self):
        """ Sets the value of the variables compliance and meas_range. Information IS NOT sent to the instrument.

        :return: None
        """
        self.compliance = float(self.compliance_var.get())
        self.meas_range = self.range_list.current()

    def start_stop_scan(self):
        """ Starts and stops an scan

        :return: None
        """
        if self.stop:
            self.stop = False

            if self.batch.ready:
                self.run_button['text'] = 'Stop batch'
            else:
                self.run_button['state'] = 'disabled'
            self.prepare_scan()
        else:
            if self.batch.ready:
                self.batch.ready = False
                self.stop = True
                self.run_button['text'] = 'Run'
                self.run_button['state'] = 'disabled'

    def prepare_scan(self):
        """ Any scan is divided in three stages:
        1) Prepare the conditions of the scan (this function), getting starting point, integration time and creating all relevant variables.
        2) Runing the scan, performed by a recursive function "get_next_datapoint"
        3) Finish the scan, where we update some variables and save the data.

        :return: None
        """
        mode = self.source_var.get()

        start = float(self.start_var.get())
        stop = float(self.stop_var.get())
        step = float(self.step_var.get())
        points = int(self.points_list.current())

        integration_time = int(self.integration_time_list.current())
        delay = int(self.waiting_time_entry.get())
        compliance = float(self.compliance_var.get())
        meas_range = self.range_list.current()

        self.options = {'source':   mode,
                        'start':    start,
                        'stop':     stop,
                        'step':     step,
                        'points':   points,
                        'compliance':compliance,
                        'measRange':meas_range,
                        'delay':    delay,
                        'intTime':  integration_time}

        # # If we are in a batch, we proceed to the next point
        if self.batch.ready:
            self.batch.batch_proceed()

        print('Starting scan...')

        # Create the record array. In this case it is irrelevant
        self.record = np.zeros((points+1, 3))
        self.record[:, 0] = np.linspace(0, 1, points+1)
        self.record[:, 1] = np.ones_like(self.record[:, 1])
        self.record[:, 2] = np.ones_like(self.record[:, 1])

        self.master.prepare_meas(self.record)
        self.start_scan()

    def start_scan(self):

        measTime = self.smu.measure(**self.options)
        self.master.window.after(int(measTime), self.get_data)

    def get_data(self):

        data0, data1 = self.smu.get_data()

        self.record = np.zeros((len(data0), 3))
        self.record[:, 0] = data0
        self.record[:, 1] = abs(data1)
        self.record[:, 2] = data1

        self.master.update_plot(self.record)
        self.finish_scan()

    def finish_scan(self):
        """ Finish the scan, updating some global variables, saving the data in the temp file and offering to save the
        data somewhere else.

        :return: None
        """

        if self.batch.ready:
            self.master.finish_meas(self.record, finish=False)
            self.batch.batch_wrapup(self.record)
        else:
            self.master.finish_meas(self.record, finish=True)
            self.stop = True

        if self.stop or not self.batch.ready:
            # We reduce the number of counts in the batch to resume at the unfinished point if necessary
            # In other words, it repeats the last, un-finished measurement
            self.batch.count = self.batch.count - 1
            self.run_button['state'] = 'enabled'

        else:
            self.prepare_scan()
示例#3
0
class Spectroscopy:
    """ Base class for spectroscopy experiments """

    def __init__(self, master, devman):

        self.master = master
        self.dm = devman
        self.id = 'spec'
        self.header = 'Wavelength (nm)\tCh-1\tCh-2'
        self.extension = 'txt'

        # Pre-load the batch, witout creating any interface
        self.batch = Batch(self.master, self.dm, fileheader=self.header)

        # Create the main variables of the class
        self.create_variables()

        # Create the interface
        self.create_interface()
        self.plot_format = {'ratios' : (3, 1),
                            'xlabel' : 'Wavelength (nm)',
                            'Ch1_ylabel' : 'Ch-1',
                            'Ch2_ylabel' : 'Ch-2',
                            'Ch1_scale' : 'linear',
                            'Ch2_scale' : 'linear'}

        # We load the dummy devices by default
        self.fill_devices()

    def quit(self):
        """ Safe closing of the devices. The devices must be closed by the Device Manager, not directly,
        so they are registered as "free".
        """

        if self.monochromator is not None:
            self.dm.close_device(self.monochromator)
        if self.adquisition is not None:
            self.dm.close_device(self.adquisition)

        self.batch.quit()

    def create_variables(self):

        # Data variables
        self.record = None
        self.background = None

        # Hardware variables
        self.monochromator = None
        self.adquisition = None

        # Adquisition variables
        self.integration_time = 300
        self.waiting_time = 100
        self.stop = True

    def create_interface(self):

        # Add elements to the menu bar
        self.create_menu_bar()

        # Creates the spectroscopy frame
        self.spectroscopy_frame = ttk.Frame(master=self.master.control_frame)
        self.spectroscopy_frame.grid(column=0, row=0, sticky=(tk.EW))
        self.spectroscopy_frame.columnconfigure(0, weight=1)

        # Hardware widgets
        hardware_frame = ttk.Labelframe(self.spectroscopy_frame, text='Selected hardware:', padding=(0, 5, 0, 15))
        hardware_frame.columnconfigure(0, weight=1)
        hardware_frame.grid(column=0, row=0, sticky=(tk.EW))

        self.mono_var = tk.StringVar()
        self.adq_var = tk.StringVar()
        self.monochromator_box = ttk.Combobox(master=hardware_frame, textvariable=self.mono_var, state="readonly")
        self.adquisition_box = ttk.Combobox(master=hardware_frame, textvariable=self.adq_var, state="readonly")

        self.monochromator_box.bind('<<ComboboxSelected>>', self.select_monochromator)
        self.adquisition_box.bind('<<ComboboxSelected>>', self.select_adquisition)

        self.monochromator_box.grid(column=0, row=0, sticky=(tk.EW))
        self.adquisition_box.grid(column=0, row=1, sticky=(tk.EW))

        # Set widgets ---------------------------------
        set_frame = ttk.Labelframe(self.spectroscopy_frame, text='Set:', padding=(0, 5, 0, 15))
        set_frame.columnconfigure(1, weight=1)

        self.GoTo_button = ttk.Button(master=set_frame, text='GoTo', command=self.goto)
        self.GoTo_entry = ttk.Entry(master=set_frame, width=10)
        self.GoTo_entry.insert(0, '700.0')
        self.integration_time_button = ttk.Button(master=set_frame, text='Integration time (ms)',
                                                  command=self.update_integration_time)
        self.integration_time_entry = ttk.Entry(master=set_frame, width=10)
        self.integration_time_entry.insert(0, '300')
        self.waiting_time_button = ttk.Button(master=set_frame, text='Waiting time (ms)',
                                              command=self.update_waiting_time)
        self.waiting_time_entry = ttk.Entry(master=set_frame, width=10)
        self.waiting_time_entry.insert(0, '100')

        set_frame.grid(column=0, row=1, sticky=(tk.EW))
        self.GoTo_button.grid(column=0, row=0, sticky=(tk.EW))
        self.GoTo_entry.grid(column=1, row=0, sticky=(tk.EW))
        self.integration_time_button.grid(column=0, row=2, sticky=(tk.EW))
        self.integration_time_entry.grid(column=1, row=2, sticky=(tk.EW))
        self.waiting_time_button.grid(column=0, row=3, sticky=(tk.EW))
        self.waiting_time_entry.grid(column=1, row=3, sticky=(tk.EW))

        # Live adquisition widgets
        live_frame = ttk.Labelframe(self.spectroscopy_frame, text='Live:', padding=(0, 5, 0, 15))
        live_frame.columnconfigure(1, weight=1)
        live_frame.columnconfigure(2, weight=1)

        self.window_live_lbl = ttk.Label(master=live_frame, text="Window (points):")
        self.window_live_entry = ttk.Entry(master=live_frame, width=10)
        self.window_live_entry.insert(0, '100')

        self.record_live_button = ttk.Button(master=live_frame, text='Record', command=self.record_live)
        self.pause_live_button = ttk.Button(master=live_frame, text='Pause', state=tk.DISABLED, command=self.pause_live)

        live_frame.grid(column=0, row=2, sticky=(tk.EW))
        self.window_live_lbl.grid(column=0, row=0, sticky=(tk.EW))
        self.window_live_entry.grid(column=1, row=0, columnspan=2, sticky=(tk.EW))
        self.record_live_button.grid(column=1, row=3, sticky=(tk.EW))
        self.pause_live_button.grid(column=2, row=3, sticky=(tk.EW))

        # Scan widgets ---------------------------------
        scan_frame = ttk.Labelframe(self.spectroscopy_frame, text='Scan:', padding=(0, 5, 0, 15))
        scan_frame.columnconfigure(0, weight=1)

        Start_lbl = ttk.Label(master=scan_frame, text="Start (nm):")
        self.Start_entry = ttk.Entry(master=scan_frame)
        self.Start_entry.insert(0, '700.0')
        Stop_lbl = ttk.Label(master=scan_frame, text="Stop (nm):")
        self.Stop_entry = ttk.Entry(master=scan_frame)
        self.Stop_entry.insert(0, '900.0')
        Step_lbl = ttk.Label(master=scan_frame, text="Step (nm):")
        self.Step_entry = ttk.Entry(master=scan_frame)
        self.Step_entry.insert(0, '5.0')

        self.scan_button = ttk.Button(master=scan_frame, text='Run', command=self.start_stop_scan, width=7)
        self.pause_button = ttk.Button(master=scan_frame, text='Pause', state=tk.DISABLED, command=self.pause_scan,
                                       width=7)

        scan_frame.grid(column=0, row=3, sticky=(tk.EW))
        Start_lbl.grid(column=0, row=0, sticky=(tk.EW))
        self.Start_entry.grid(column=1, row=0, columnspan=2, sticky=(tk.EW))
        Stop_lbl.grid(column=0, row=1, sticky=(tk.EW))
        self.Stop_entry.grid(column=1, row=1, columnspan=2, sticky=(tk.EW))
        Step_lbl.grid(column=0, row=2, sticky=(tk.EW))
        self.Step_entry.grid(column=1, row=2, columnspan=2, sticky=(tk.EW))
        self.scan_button.grid(column=1, row=3, sticky=(tk.EW))
        self.pause_button.grid(column=2, row=3, sticky=(tk.EW))

        # Background widgets
        self.background_frame = ttk.Labelframe(self.spectroscopy_frame, text='Background:', padding=(0, 5, 0, 15))
        self.background_frame.columnconfigure(0, weight=1)
        self.background_frame.columnconfigure(1, weight=1)
        self.background_button = ttk.Button(master=self.background_frame, text='Get', command=self.get_background)
        self.clear_background_button = ttk.Button(master=self.background_frame, text='Clear',
                                                  command=self.clear_background)

        self.background_frame.grid(column=0, row=4, sticky=(tk.NSEW))
        self.background_button.grid(column=0, row=0, sticky=(tk.EW, tk.S))
        self.clear_background_button.grid(column=1, row=0, sticky=(tk.EW, tk.S))

    def create_menu_bar(self):
        """ Add elememnts to the master menubar
        """

        # Hardware menu
        self.master.menu_hardware.add_command(label='Monochromator', command=lambda: self.monochromator.interface(self.master))
        self.master.menu_hardware.entryconfig("Monochromator", state="disabled")
        self.master.menu_hardware.add_command(label='Adquisition', command=lambda: self.adquisition.interface(self.master))
        self.master.menu_hardware.entryconfig("Adquisition", state="disabled")

        # Batch menu
        self.master.menu_batch.add_command(label='Disable', command=self.batch.disable)
        self.master.menu_batch.add_command(label='IV', command=lambda: self.new_batch('IV'))
        self.master.menu_batch.add_command(label='Temperature', command=lambda: self.new_batch('Temperature'))
        self.master.menu_batch.add_command(label='Time', command=lambda: self.new_batch('Time'))

    def new_batch(self, batch_mode):
        """ Shows current batch window or, if a different batch is chosen, destroys the old one and creates a new one
        for the new batch mpde

        :param batch_mode: the selected type of batch
        :return: None
        """
        if self.batch.mode == batch_mode:
            self.batch.show()
        else:
            self.batch.quit()
            self.batch = Batch(self.master, self.dm, mode=batch_mode)

    def fill_devices(self):
        """ Fills the device selectors with the corresponding type of devices

        :return:
        """

        self.monochromator_box['values'] = self.dm.get_devices(['Monochromator'])
        self.monochromator_box.current(0)
        self.adquisition_box['values'] = self.dm.get_devices(['Lock-In', 'Spectrometer'])
        self.adquisition_box.current(0)

        self.select_monochromator()
        self.select_adquisition()

    def select_monochromator(self, *args):

        if self.monochromator is not None:
            self.dm.close_device(self.monochromator)

        dev_name = self.mono_var.get()
        self.monochromator = self.dm.open_device(dev_name)

        if self.monochromator is None:
            self.monochromator_box.current(0)
            self.monochromator = self.dm.open_device(self.mono_var.get())

        elif self.dm.current_config[dev_name]['Type'] == 'Monochromator':
            self.move = self.monochromator.move

        else:
            self.monochromator_box.current(0)
            self.monochromator = self.dm.open_device(self.mono_var.get())

        # If the device has an interface to set options, we link it to the entry in the menu
        interface = getattr(self.monochromator, "interface", None)
        if callable(interface):
            self.master.menu_hardware.entryconfig("Monochromator", state="normal")
        else:
            self.master.menu_hardware.entryconfig("Monochromator", state="disabled")

    def select_adquisition(self, *args):
        """ When the adquisition selector changes, this function updates some variables and the graphical interface
        to adapt it to the selected device.

        :param args: Dummy variable that does nothing but must exist (?)
        :return: None
        """

        if self.adquisition is not None:
            self.dm.close_device(self.adquisition)

        dev_name = self.adq_var.get()
        self.adquisition = self.dm.open_device(dev_name)

        if self.adquisition is None:
            self.adquisition_box.current(0)
            self.adquisition = self.dm.open_device(self.adq_var.get())

        elif self.dm.current_config[dev_name]['Type'] == 'Spectrometer':
            self.move = self.null
            self.prepare_scan = self.prepare_scan_spectrometer
            self.get_next_datapoint = self.mode_spectrometer
            self.start_live = self.prepare_live_spectrometer
            self.live = self.live_spectrometer
            self.background_frame.grid(column=0, row=4, sticky=(tk.NSEW))
            self.window_live_lbl.grid_forget()
            self.window_live_entry.grid_forget()
            self.monochromator_box['state'] = 'disabled'
            self.Step_entry['state'] = 'disabled'
            self.GoTo_button['state'] = 'disabled'
            self.GoTo_entry['state'] = 'disabled'

        elif self.dm.current_config[dev_name]['Type'] in ['Lock-In', 'Multimeter']:
            self.move = self.monochromator.move
            self.prepare_scan = self.prepare_scan_lockin
            self.get_next_datapoint = self.mode_lockin
            self.start_live = self.prepare_live_lockin
            self.live = self.live_lockin
            self.background_frame.grid_forget()
            self.window_live_lbl.grid(column=0, row=0, sticky=(tk.EW))
            self.window_live_entry.grid(column=1, row=0, columnspan=2, sticky=(tk.EW))
            self.monochromator_box['state'] = 'normal'
            self.Step_entry['state'] = 'normal'
            self.GoTo_button['state'] = 'normal'
            self.GoTo_entry['state'] = 'normal'

        else:
            self.adquisition_box.current(0)
            self.adquisition = self.dm.open_device(self.adq_var.get())

        interface = getattr(self.adquisition, "interface", None)
        if callable(interface):
            self.master.menu_hardware.entryconfig("Adquisition", state="normal")
        else:
            self.master.menu_hardware.entryconfig("Adquisition", state="disabled")

    def null(self, *args, **kwargs):
        """ Empty function that does nothing

        :return: None
        """
        pass

    def start_stop_scan(self):
        """ Starts and stops an scan

        :return: None
        """
        if self.stop:
            self.prepare_scan()
        else:
            self.stop = True
            self.finish_scan()

    def pause_scan(self):
        """ Pauses an scan or resumes the adquisition

        :return: None
        """
        self.stop = not self.stop

        if self.stop:
            self.pause_button['text'] = 'Resume'
        else:
            self.pause_button['text'] = 'Pause'

        self.get_next_datapoint()

    def prepare_scan_lockin(self):
        """ Any scan is divided in three stages:
        1) Prepare the conditions of the scan (this function), getting starting point, integration time and creating all relevant variables.
        2) Runing the scan, performed by a recursive function  "mode_spectrometer" or "mode_lockin"
        3) Finish the scan, where we update some variables and save the data.

        :return: None
        """
        self.update_integration_time()
        self.update_waiting_time()

        # Get the scan conditions
        self.start_wl = max(float(self.Start_entry.get()), 250)
        self.stop_wl = max(min(float(self.Stop_entry.get()), 3000), self.start_wl + 1)
        step = min(max(float(self.Step_entry.get()), self.adquisition.min_wavelength), self.stop_wl - self.start_wl)

        # # If we are in a batch, we proceed to the next point
        if self.batch.ready:
            self.batch.batch_proceed()

        self.move(self.start_wl, speed='Fast')
        print('Starting scan...')

        # Create the record array
        self.size = int(np.ceil((self.stop_wl - self.start_wl + 0.5 * step) / step))
        self.num = self.size
        self.record = np.zeros((self.size, 3))
        self.record[:, 0] = np.arange(self.start_wl, self.stop_wl + 0.5 * step, step)
        self.record[:, 1] = self.record[:, 1] * np.NaN
        self.record[:, 2] = self.record[:, 1] * np.NaN

        self.master.prepare_meas(self.record)

        self.i = 0

        self.scan_running()

        self.mode_lockin()

    def mode_lockin(self):
        """ Gets the next data point in a scan. This function depends on the adquisition device

        :return: None
        """

        if not self.stop:
            data = self.adquisition.measure()
            self.record[self.i, 1] = data[0]
            self.record[self.i, 2] = data[1]

            self.master.update_plot(self.record)

            if self.i < self.num - 1:
                self.i += 1
                self.move(self.record[self.i, 0], speed='Fast')
                self.master.window.after(int(self.integration_time + self.waiting_time), self.mode_lockin)

            else:
                self.finish_scan()

    def prepare_scan_spectrometer(self):
        """ Any scan is divided in three stages:
        1) Prepare the conditions of the scan (this function), getting starting point, integration time and creating all relevant variables.
        2) Runing the scan, performed by a recursive function "mode_spectrometer" or "mode_lockin"
        3) Finish the scan, where we update some variables and save the data.

        :return: None
        """
        self.update_integration_time()
        self.update_waiting_time()

        # We check the background is not none and offer to update it
        if self.background is None:
            meas_bg = messagebox.askyesno(message='There is no background spectrum for this integration time!',
                                              detail='Do you want to measure it now?', icon='question',
                                              title='Measure background?')

            self.get_background(meas_bg)

        # # If we are in a batch, we proceed to the next point
        if self.batch.ready:
            self.batch.batch_proceed()

        # If the integration time is too long, we have to split the adquisition in several steps,
        # otherwise the spectrometer hangs
        self.num = int(np.ceil(self.integration_time / self.adquisition.max_integration_time))

        # Here we select the wavelength range we want to record and re-shape the record array
        # Get the scan conditions
        self.start_wl = max(float(self.Start_entry.get()), 300)
        self.stop_wl = max(min(float(self.Stop_entry.get()), 2000), self.start_wl + 1)
        wl = self.adquisition.measure()[0]
        self.idx = np.where((self.start_wl <= wl) & (wl <= self.stop_wl))
        self.size = len(self.idx[0])

        # Create the record array
        self.record = np.zeros((self.size, 3))
        self.record[:, 0] = wl[self.idx]
        self.record[:, 2] = self.background[self.idx]

        self.master.prepare_meas(self.record)

        self.i = 0

        self.scan_running()

        self.mode_spectrometer()

    def mode_spectrometer(self):
        """ Gets the whole spectrum at once recorded by the spectrometer in the range selected

        :return: None
        """

        if not self.stop:
            data = self.adquisition.measure()
            intensity = data[1][self.idx] - self.background[self.idx]
            self.record[:, 1] = (intensity + self.i * self.record[:, 1]) / (self.i + 1.)

            self.master.update_plot(self.record)

            if self.i < self.num - 1:
                self.i = self.i + 1
                self.master.window.after(int(self.integration_time / self.num), self.mode_spectrometer)

            else:
                self.finish_scan()

    def finish_scan(self):
        """ Finish the scan, updating some global variables, saving the data in the temp file and offering to save the
        data somewhere else.

        :return: None
        """

        if self.batch.ready:
            self.master.finish_meas(self.record, finish=False)
            self.batch.batch_wrapup(self.record)
        else:
            self.master.finish_meas(self.record, finish=True)

        if self.stop or not self.batch.ready:
            # We reduce the number of counts in the batch to resume at the unfinished point if necessary
            # In other words, it repeats the last, un-finished measurement
            self.batch.count = max(self.batch.count - 1, 0)
            self.scan_stopped()

        else:
            self.prepare_scan()

    def scan_running(self):
        """ Updates the graphical interface, disable the buttoms that must be disabled during the measurement.

        :return: None
        """
        self.scan_button['text'] = 'Stop'
        self.pause_button['state'] = 'enabled'
        self.record_live_button['state'] = 'disabled'
        self.GoTo_button['state'] = 'disabled'
        self.integration_time_button['state'] = 'disabled'
        self.waiting_time_button['state'] = 'disabled'
        self.background_button['state'] = 'disabled'
        self.clear_background_button['state'] = 'disabled'
        self.stop = False

    def scan_stopped(self):
        """ Returns the graphical interface to normal once the measurement has finished.

        :return: None
        """
        self.scan_button['text'] = 'Run'
        self.pause_button['state'] = 'disabled'
        self.record_live_button['state'] = 'enabled'
        self.GoTo_button['state'] = 'enabled'
        self.integration_time_button['state'] = 'enabled'
        self.waiting_time_button['state'] = 'enabled'
        self.background_button['state'] = 'enabled'
        self.clear_background_button['state'] = 'enabled'
        self.stop = True

    def get_background(self, meas_bg=True):
        """ Gets the background when using the spectrometer, if requested.

        :parameter: meas_bg     True or False: wether to measure a background or just produce a bg with zeros
        :return: None
        """

        if meas_bg:
            self.update_integration_time()
            self.background = self.adquisition.measure()[1]
            messagebox.showinfo(message='Background taken!', detail='Press OK to continue.', title='Background taken!')
        else:
            self.background = self.adquisition.measure()[1]*0.0

    def clear_background(self):
        """ Clears the background when using the spectrometer.

        :return: None
        """
        self.background = None

    def record_live(self):
        """ Starts and stops a live recording.

        :return: None
        """
        if self.stop:
            self.stop = False
            self.record_live_button['text'] = 'Stop'
            self.pause_live_button['state'] = 'enabled'
            self.scan_button['state'] = 'disabled'
            self.background_button['state'] = 'disabled'
            self.clear_background_button['state'] = 'disabled'
            self.start_live()
        else:
            self.record_live_button['text'] = 'Record'
            self.pause_live_button['state'] = 'disabled'
            self.scan_button['state'] = 'enabled'
            self.background_button['state'] = 'enabled'
            self.clear_background_button['state'] = 'enabled'
            self.stop = True
            self.finish_live()

    def pause_live(self):
        """ Pauses a live recording or resumes the adquisition
        """
        self.stop = not self.stop

        if self.stop:
            self.pause_live_button['text'] = 'Resume'
        else:
            self.pause_live_button['text'] = 'Pause'

        self.live()

    def prepare_live_lockin(self):
        """ Prepares the lock-in live adquisition and prepare some variables
        """
        self.goto()
        self.update_integration_time()

        self.window_points = int(self.window_live_entry.get())

        self.live_data = np.zeros((self.window_points, 3))
        self.live_data[:, 0] = np.arange(self.window_points)

        # Removes all plots, but not the data, and change the horizontal axis conditions
        self.master.clear_plot(xtitle='Time', ticks='off')
        self.master.prepare_meas(self.live_data)

        self.live_lockin()

    def live_lockin(self):
        """ Runs the live lock-in adquisition
        """
        if not self.stop:
            self.live_data[:-1, 1] = self.live_data[1:, 1]
            self.live_data[:-1, 2] = self.live_data[1:, 2]

            self.live_data[-1, 1], self.live_data[-1, 2] = self.adquisition.measure()

            self.master.update_plot(self.live_data)

            self.master.window.after(self.integration_time, self.live_lockin)

    def prepare_live_spectrometer(self):
        """ Prepares the spectrometer live adquisition and prepare some variables
        """
        self.update_integration_time()

        if self.integration_time > self.adquisition.max_integration_time:
            self.integration_time = int(self.adquisition.max_integration_time)
            self.adquisition.update_integration_time(self.integration_time)

        data0, data1 = self.adquisition.measure()

        self.live_data = np.zeros((len(data0), 3))
        self.live_data[:, 0] = data0
        self.live_data[:, 1] = data1

        # Removes all plots, but not the data, and change the horizontal axis conditions
        self.master.clear_plot(xtitle='Wavelength (nm)', ticks='on')
        self.master.prepare_meas(self.live_data)

        self.live_spectrometer()

    def live_spectrometer(self):
        """ Runs the live spectrometer adquisition
        """
        if not self.stop:
            data0, data1 = self.adquisition.measure()
            self.live_data[:, 1] = data1

            self.master.update_plot(self.live_data)

            self.master.window.after(self.integration_time, self.live_spectrometer)

    def finish_live(self):
        """ Finish the live adquisition, returning the front end to the scan mode
        """
        self.master.replot_data(xtitle='Wavelength (nm)', ticks='on')

    def update_integration_time(self):
        """ Updates the integration time
        """
        old_integration_time = self.integration_time
        self.integration_time = int(self.integration_time_entry.get())

        if old_integration_time != self.integration_time:
            self.clear_background()
            self.integration_time = self.adquisition.update_integration_time(self.integration_time)
            self.integration_time_entry.delete(0, tk.END)
            self.integration_time_entry.insert(0, '%i' % self.integration_time)

    def update_waiting_time(self):
        """ Updates the waiting time
        """
        self.waiting_time = int(self.waiting_time_entry.get())

    def goto(self):
        """ Go to the specified wavelength
        """
        wl = float(self.GoTo_entry.get())
        self.move(wl, speed='Fast')
        print('Done! Wavelength = {} nm'.format(wl))
示例#4
0
class Photoreflectance:
    """ Base class for photoreflectance experiments """
    def __init__(self, master, devman):

        self.master = master
        self.dm = devman
        self.id = 'PR'

        # Create the main variables of the class
        self.create_variables()

        self.plot_format = {
            'ratios': (1, 1),
            'xlabel': 'Wavelength (nm)',
            'x_scale': 'linear',
            'Ch1_ylabel': 'Xsignal/Rbaseline',
            'Ch2_ylabel': 'Rbaseline',
            'Ch1_scale': 'linear',
            'Ch2_scale': 'linear'
        }
        self.header = ''
        self.header.encode('utf-8')
        self.extension = 'txt'

        # Pre-load the batch, witout creating any interface
        self.batch = Batch(self.master, self.dm, fileheader=self.header)

        # Create the interface
        self.create_interface()

        # We load the dummy devices by default
        self.fill_devices()

    def quit(self):
        """ Safe closing of the devices. The devices must be closed by the Device Manager, not directly,
        so they are registered as "free".
        """

        if self.monochromator is not None:
            self.monochromator.close()  ## Destroys the system model
            self.dm.close_device(self.monochromator)
        if self.acquisition is not None:
            self.acquisition.close()  ## Unsubscribes from the demodulators
            self.dm.close_device(self.acquisition)

        self.batch.quit()

    def create_variables(self):

        # Data variables
        self.record = None
        self.background = None
        self.plotdata = None  ## Transformed data to be plotted

        # Hardware variables
        self.monochromator = None
        self.acquisition = None

        # acquisition variables
        self.integration_time = 100
        self.waiting_time = 100
        self.stop = True

    def create_interface(self):

        # Add elements to the menu bar
        self.create_menu_bar()

        # Creates the photoreflectance frame
        self.spectroscopy_frame = ttk.Frame(master=self.master.control_frame)
        self.spectroscopy_frame.grid(column=0, row=0, sticky=(tk.EW))
        self.spectroscopy_frame.columnconfigure(0, weight=1)

        # Hardware widgets
        hardware_frame = ttk.Labelframe(self.spectroscopy_frame,
                                        text='Selected hardware:',
                                        padding=(0, 5, 0, 15))
        hardware_frame.columnconfigure(0, weight=1)
        hardware_frame.grid(column=0, row=0, sticky=(tk.EW))

        self.mono_var = tk.StringVar()
        self.acq_var = tk.StringVar()
        self.monochromator_box = ttk.Combobox(master=hardware_frame,
                                              textvariable=self.mono_var,
                                              state="readonly")
        self.acquisition_box = ttk.Combobox(master=hardware_frame,
                                            textvariable=self.acq_var,
                                            state="readonly")

        self.monochromator_box.bind('<<ComboboxSelected>>',
                                    self.select_monochromator)
        self.acquisition_box.bind('<<ComboboxSelected>>',
                                  self.select_acquisition)

        self.monochromator_box.grid(column=0, row=0, sticky=(tk.EW))
        self.acquisition_box.grid(column=0, row=1, sticky=(tk.EW))

        # Set widgets ---------------------------------
        set_frame = ttk.Labelframe(self.spectroscopy_frame,
                                   text='Set:',
                                   padding=(0, 5, 0, 15))
        set_frame.columnconfigure(1, weight=1)

        self.GoTo_button = ttk.Button(master=set_frame,
                                      text='GoTo',
                                      command=self.goto)
        self.GoTo_entry = ttk.Entry(master=set_frame, width=10)
        self.GoTo_entry.insert(0, '700.0')
        self.integration_time_button = ttk.Button(
            master=set_frame,
            text='Integration time (ms)',
            command=self.update_integration_time)
        self.integration_time_entry = ttk.Entry(master=set_frame, width=10)
        self.integration_time_entry.insert(0, '100')
        self.waiting_time_button = ttk.Button(master=set_frame,
                                              text='Waiting time (ms)',
                                              command=self.update_waiting_time)
        self.waiting_time_entry = ttk.Entry(master=set_frame, width=10)
        self.waiting_time_entry.insert(0, '100')

        set_frame.grid(column=0, row=1, sticky=(tk.EW))
        self.GoTo_button.grid(column=0, row=0, sticky=(tk.EW))
        self.GoTo_entry.grid(column=1, row=0, sticky=(tk.EW))
        self.integration_time_button.grid(column=0, row=2, sticky=(tk.EW))
        self.integration_time_entry.grid(column=1, row=2, sticky=(tk.EW))
        self.waiting_time_button.grid(column=0, row=3, sticky=(tk.EW))
        self.waiting_time_entry.grid(column=1, row=3, sticky=(tk.EW))

        # Live acquisition widgets
        live_frame = ttk.Labelframe(self.spectroscopy_frame,
                                    text='Live:',
                                    padding=(0, 5, 0, 15))
        live_frame.columnconfigure(1, weight=1)
        live_frame.columnconfigure(2, weight=1)

        self.window_live_lbl = ttk.Label(master=live_frame,
                                         text="Window (points):")
        self.window_live_entry = ttk.Entry(master=live_frame, width=10)
        self.window_live_entry.insert(0, '100')

        self.record_live_button = ttk.Button(master=live_frame,
                                             text='Record',
                                             command=self.record_live)
        self.pause_live_button = ttk.Button(master=live_frame,
                                            text='Pause',
                                            state=tk.DISABLED,
                                            command=self.pause_live)

        live_frame.grid(column=0, row=2, sticky=(tk.EW))
        self.window_live_lbl.grid(column=0, row=0, sticky=(tk.EW))
        self.window_live_entry.grid(column=1,
                                    row=0,
                                    columnspan=2,
                                    sticky=(tk.EW))
        self.record_live_button.grid(column=1, row=3, sticky=(tk.EW))
        self.pause_live_button.grid(column=2, row=3, sticky=(tk.EW))

        # Scan widgets ---------------------------------
        scan_frame = ttk.Labelframe(self.spectroscopy_frame,
                                    text='Scan:',
                                    padding=(0, 5, 0, 15))
        scan_frame.columnconfigure(0, weight=1)

        Start_lbl = ttk.Label(master=scan_frame, text="Start (nm):")
        self.Start_entry = ttk.Entry(master=scan_frame)
        self.Start_entry.insert(0, '700.0')
        Stop_lbl = ttk.Label(master=scan_frame, text="Stop (nm):")
        self.Stop_entry = ttk.Entry(master=scan_frame)
        self.Stop_entry.insert(0, '900.0')
        Step_lbl = ttk.Label(master=scan_frame, text="Step (nm):")
        self.Step_entry = ttk.Entry(master=scan_frame)
        self.Step_entry.insert(0, '5.0')

        self.scan_button = ttk.Button(master=scan_frame,
                                      text='Run',
                                      command=self.start_stop_scan,
                                      width=7)
        self.pause_button = ttk.Button(master=scan_frame,
                                       text='Pause',
                                       state=tk.DISABLED,
                                       command=self.pause_scan,
                                       width=7)

        scan_frame.grid(column=0, row=3, sticky=(tk.EW))
        Start_lbl.grid(column=0, row=0, sticky=(tk.EW))
        self.Start_entry.grid(column=1, row=0, columnspan=2, sticky=(tk.EW))
        Stop_lbl.grid(column=0, row=1, sticky=(tk.EW))
        self.Stop_entry.grid(column=1, row=1, columnspan=2, sticky=(tk.EW))
        Step_lbl.grid(column=0, row=2, sticky=(tk.EW))
        self.Step_entry.grid(column=1, row=2, columnspan=2, sticky=(tk.EW))
        self.scan_button.grid(column=1, row=3, sticky=(tk.EW))
        self.pause_button.grid(column=2, row=3, sticky=(tk.EW))

        # Background widgets---------------------------------------------
        self.background_frame = ttk.Labelframe(self.spectroscopy_frame,
                                               text='Background:',
                                               padding=(0, 5, 0, 15))
        self.background_frame.columnconfigure(0, weight=1)
        self.background_frame.columnconfigure(1, weight=1)
        self.background_button = ttk.Button(master=self.background_frame,
                                            text='Get',
                                            command=self.get_background)
        self.clear_background_button = ttk.Button(
            master=self.background_frame,
            text='Clear',
            command=self.clear_background)

        self.background_frame.grid(column=0, row=4, sticky=(tk.NSEW))
        self.background_button.grid(column=0, row=0, sticky=(tk.EW, tk.S))
        self.clear_background_button.grid(column=1,
                                          row=0,
                                          sticky=(tk.EW, tk.S))

        # Ch1 setup widgets --------------------------------
        self.Ch1_frame = ttk.Labelframe(self.spectroscopy_frame,
                                        text='Ch1 setup:',
                                        padding=(0, 5, 0, 15))
        self.Ch1_frame.grid(column=0, row=5, sticky=(tk.EW))
        self.Ch1_frame.columnconfigure(0, weight=1)
        self.Ch1_frame.columnconfigure(1, weight=1)
        self.Ch1_param1_var = tk.StringVar()
        self.Ch1_param1_var.set('Xsignal')
        self.Ch1_param2_var = tk.StringVar()
        self.Ch1_param2_var.set('Rbaseline')
        self.numer_label = ttk.Label(self.Ch1_frame, text='Numerator: ')
        self.numer_label.grid(column=0, row=0, sticky=(tk.E, tk.W, tk.S))
        ttk.Radiobutton(self.Ch1_frame,
                        text="X signal",
                        variable=self.Ch1_param1_var,
                        value='Xsignal',
                        command=self.channel_param).grid(column=1,
                                                         row=0,
                                                         sticky=(tk.E, tk.W,
                                                                 tk.S))
        ttk.Radiobutton(self.Ch1_frame,
                        text="R signal",
                        variable=self.Ch1_param1_var,
                        value='Rsignal',
                        command=self.channel_param).grid(column=2,
                                                         row=0,
                                                         sticky=(tk.E, tk.W,
                                                                 tk.S))
        self.denom_label = ttk.Label(self.Ch1_frame, text='Denominator: ')
        self.denom_label.grid(column=0, row=1, sticky=(tk.E, tk.W, tk.S))
        ttk.Radiobutton(self.Ch1_frame,
                        text="R baseline",
                        variable=self.Ch1_param2_var,
                        value='Rbaseline',
                        command=self.channel_param).grid(column=1,
                                                         row=1,
                                                         sticky=(tk.E, tk.W,
                                                                 tk.S))
        ttk.Radiobutton(self.Ch1_frame,
                        text="No denominator",
                        variable=self.Ch1_param2_var,
                        value='ND',
                        command=self.channel_param).grid(column=2,
                                                         row=1,
                                                         sticky=(tk.E, tk.W,
                                                                 tk.S))

        # Ch2 setup widgets --------------------------------
        self.Ch2_frame = ttk.Labelframe(self.spectroscopy_frame,
                                        text='Ch2 setup:',
                                        padding=(0, 5, 0, 15))
        self.Ch2_frame.grid(column=0, row=6, sticky=(tk.EW))
        self.Ch2_frame.columnconfigure(0, weight=1)
        self.Ch2_frame.columnconfigure(1, weight=1)
        self.Ch2_param_var = tk.StringVar()
        self.Ch2_param_var.set('Rbaseline')
        ttk.Radiobutton(self.Ch2_frame,
                        text="R baseline",
                        variable=self.Ch2_param_var,
                        value='Rbaseline',
                        command=self.channel_param).grid(column=0,
                                                         row=1,
                                                         sticky=(tk.E, tk.W,
                                                                 tk.S))

    def update_header(self):
        col0 = self.plot_format['xlabel']
        col1 = 'Xc'
        col2 = 'Yc'
        col3 = 'Xsum'
        col4 = 'Ysum'
        col5 = 'Xdif'
        col6 = 'Ydif'

        self.header = str(col0) + '\t' + str(col1) + '\t' + str(
            col2) + '\t' + str(col3) + '\t' + str(col4) + '\t' + str(
                col5) + '\t' + str(col6)
        self.header.encode('utf-8')

    def create_menu_bar(self):
        """ Add elememnts to the master menubar
        """

        # Hardware menu
        self.master.menu_hardware.add_command(
            label='Monochromator',
            command=lambda: self.monochromator.interface(self.master))
        self.master.menu_hardware.entryconfig("Monochromator",
                                              state="disabled")
        self.master.menu_hardware.add_command(
            label='acquisition',
            command=lambda: self.acquisition.interface(self.master))
        self.master.menu_hardware.entryconfig("acquisition", state="disabled")

        # Batch menu
        self.master.menu_batch.add_command(label='Disable',
                                           command=self.batch.disable)
        self.master.menu_batch.add_command(
            label='IV', command=lambda: self.new_batch('IV'))
        self.master.menu_batch.add_command(
            label='Temperature', command=lambda: self.new_batch('Temperature'))
        self.master.menu_batch.add_command(
            label='Time', command=lambda: self.new_batch('Time'))

    def new_batch(self, batch_mode):
        """ Shows current batch window or, if a different batch is chosen, destroys the old one and creates a new one
        for the new batch mpde

        :param batch_mode: the selected type of batch
        :return: None
        """
        if self.batch.mode == batch_mode:
            self.batch.show()
        else:
            self.batch.quit()
            self.batch = Batch(self.master, self.dm, mode=batch_mode)

    def fill_devices(self):
        """ Fills the device selectors with the corresponding type of devices

        :return:
        """

        self.monochromator_box['values'] = self.dm.get_devices(
            ['Monochromator'])
        self.monochromator_box.current(0)
        self.acquisition_box['values'] = self.dm.get_devices(
            ['Lock-In', 'Spectrometer'])
        self.acquisition_box.current(0)

        self.select_monochromator()
        self.select_acquisition()

    def select_monochromator(self, *args):

        if self.monochromator is not None:
            self.dm.close_device(self.monochromator)

        dev_name = self.mono_var.get()
        self.monochromator = self.dm.open_device(dev_name)

        if self.monochromator is None:
            self.monochromator_box.current(0)
            self.monochromator = self.dm.open_device(self.mono_var.get())

        elif self.dm.current_config[dev_name]['Type'] == 'Monochromator':
            self.move = self.monochromator.move

        else:
            self.monochromator_box.current(0)
            self.monochromator = self.dm.open_device(self.mono_var.get())

        # If the device has an interface to set options, we link it to the entry in the menu
        interface = getattr(self.monochromator, "interface", None)
        if callable(interface):
            self.master.menu_hardware.entryconfig("Monochromator",
                                                  state="normal")
        else:
            self.master.menu_hardware.entryconfig("Monochromator",
                                                  state="disabled")

    def select_acquisition(self, *args):
        """ When the acquisition selector changes, this function updates some variables and the graphical interface
        to adapt it to the selected device.

        :param args: Dummy variable that does nothing but must exist (?)
        :return: None
        """

        if self.acquisition is not None:
            self.dm.close_device(self.acquisition)

        dev_name = self.acq_var.get()
        self.acquisition = self.dm.open_device(dev_name)

        if self.acquisition is None:
            self.acquisition_box.current(0)
            self.acquisition = self.dm.open_device(self.acq_var.get())

        elif self.dm.current_config[dev_name]['Type'] == 'Spectrometer':
            self.move = self.null
            self.prepare_scan = self.prepare_scan_spectrometer
            self.get_next_datapoint = self.mode_spectrometer
            self.start_live = self.prepare_live_spectrometer
            self.live = self.live_spectrometer
            self.background_frame.grid(column=0, row=4, sticky=(tk.NSEW))
            self.window_live_lbl.grid_forget()
            self.window_live_entry.grid_forget()
            self.monochromator_box['state'] = 'disabled'
            self.Step_entry['state'] = 'disabled'
            self.GoTo_button['state'] = 'disabled'
            self.GoTo_entry['state'] = 'disabled'

        elif self.dm.current_config[dev_name]['Type'] in [
                'Lock-In', 'Multimeter'
        ]:
            self.move = self.monochromator.move
            self.prepare_scan = self.prepare_scan_lockin
            self.get_next_datapoint = self.mode_lockin
            self.start_live = self.prepare_live_lockin
            self.live = self.live_lockin
            self.background_frame.grid_forget()
            self.window_live_lbl.grid(column=0, row=0, sticky=(tk.EW))
            self.window_live_entry.grid(column=1,
                                        row=0,
                                        columnspan=2,
                                        sticky=(tk.EW))
            self.monochromator_box['state'] = 'normal'
            self.Step_entry['state'] = 'normal'
            self.GoTo_button['state'] = 'normal'
            self.GoTo_entry['state'] = 'normal'

        else:
            self.acquisition_box.current(0)
            self.acquisition = self.dm.open_device(self.acq_var.get())

        interface = getattr(self.acquisition, "interface", None)
        if callable(interface):
            self.master.menu_hardware.entryconfig("acquisition",
                                                  state="normal")
        else:
            self.master.menu_hardware.entryconfig("acquisition",
                                                  state="disabled")

    def channel_param(self):
        """ When the channel parameters are changed, this function updates some internal variables and the graphical interface

        :return: None
        """
        ## Channel 1 labels ----------------------------------
        if self.Ch1_param1_var.get() == 'Xsignal' and self.Ch1_param2_var.get(
        ) == 'Rbaseline':
            self.plot_format['Ch1_ylabel'] = 'Xsignal/Rbaseline'
        elif self.Ch1_param1_var.get(
        ) == 'Xsignal' and self.Ch1_param2_var.get() == 'ND':
            self.plot_format['Ch1_ylabel'] = 'Xsignal'
        elif self.Ch1_param1_var.get(
        ) == 'Rsignal' and self.Ch1_param2_var.get() == 'Rbaseline':
            self.plot_format['Ch1_ylabel'] = 'Rsignal/Rbaseline'
        elif self.Ch1_param1_var.get(
        ) == 'Rsignal' and self.Ch1_param2_var.get() == 'ND':
            self.plot_format['Ch1_ylabel'] = 'Rsignal'
        ## Channel 2 labels ----------------------------------
        if self.Ch2_param_var.get() == 'Rbaseline':
            self.plot_format['Ch2_ylabel'] = 'Rbaseline'

        self.master.update_plot_axis(self.plot_format)

    def null(self, *args, **kwargs):
        """ Empty function that does nothing

        :return: None
        """
        pass

    def start_stop_scan(self):
        """ Starts and stops an scan

        :return: None
        """
        self.run_check()
        if self.stop:
            self.prepare_scan()
        else:
            self.stop = True
            self.finish_scan()

    def pause_scan(self):
        """ Pauses an scan or resumes the acquisition

        :return: None
        """
        self.stop = not self.stop

        if self.stop:
            self.pause_button['text'] = 'Resume'
        else:
            self.pause_button['text'] = 'Pause'

        self.get_next_datapoint()

    def prepare_scan_lockin(self):
        """ Any scan is divided in three stages:
        1) Prepare the conditions of the scan (this function), getting starting point, integration time and creating all relevant variables.
        2) Runing the scan, performed by a recursive function  "mode_spectrometer" or "mode_lockin"
        3) Finish the scan, where we update some variables and save the data.

        :return: None
        """

        self.update_integration_time()
        self.update_waiting_time()
        self.acquisition.open()
        # Get the scan conditions
        self.start_wl = max(float(self.Start_entry.get()), 250)
        self.stop_wl = max(min(float(self.Stop_entry.get()), 3000),
                           self.start_wl + 1)
        step = min(
            max(float(self.Step_entry.get()), self.acquisition.min_wavelength),
            self.stop_wl - self.start_wl)

        # # If we are in a batch, we proceed to the next point
        if self.batch.ready:
            self.batch.batch_proceed()

        self.move(self.start_wl, speed='Fast')
        print('Starting scan...')

        # Create the record array
        self.size = int(
            np.ceil((self.stop_wl - self.start_wl + 0.5 * step) / step))
        self.num = self.size

        self.record = np.zeros((self.size, 7))  ## Data to be saved
        self.record[:, 0] = np.arange(self.start_wl, self.stop_wl + 0.5 * step,
                                      step)
        self.record[:, 1] = self.record[:, 1] * np.NaN
        self.record[:, 2] = self.record[:, 1] * np.NaN
        self.record[:, 3] = self.record[:, 1] * np.NaN
        self.record[:, 4] = self.record[:, 1] * np.NaN

        self.plotdata = np.zeros((self.size, 3))  ## Data to be plotted
        self.plotdata[:, 0] = self.record[:, 0]
        self.plotdata[:, 1] = self.plotdata[:, 1] * np.NaN
        self.plotdata[:, 2] = self.plotdata[:, 1] * np.NaN

        self.master.prepare_meas(self.record)

        self.i = 0

        self.scan_running()

        self.mode_lockin()

    def mode_lockin(self):
        """ Gets the next data point in a scan. This function depends on the acquisition device

        :return: None
        """

        if not self.stop:
            Xc, Yc, Xsum, Ysum, Xdif, Ydif = self.acquisition.measure()

            ## Correct for RMS and half signals
            Xc = Xc * sqrt(2)
            Yc = Yc * sqrt(2)
            Xsum = Xsum * 2 * sqrt(2)
            Ysum = Ysum * 2 * sqrt(2)
            Xdif = Xdif * 2 * sqrt(2)
            Ydif = Ydif * 2 * sqrt(2)

            self.record[self.i, 1] = Xc
            self.record[self.i, 2] = Yc
            self.record[self.i, 3] = Xsum
            self.record[self.i, 4] = Ysum
            self.record[self.i, 5] = Xdif
            self.record[self.i, 6] = Ydif

            if self.plot_format['Ch1_ylabel'] == 'Xsignal/Rbaseline':
                self.plotdata[self.i, 1] = Xsum / sqrt(Xc**2 + Yc**2)
            elif self.plot_format['Ch1_ylabel'] == 'Rsignal/Rbaseline':
                self.plotdata[self.i, 1] = sqrt(Xsum**2 +
                                                Ysum**2) / sqrt(Xc**2 + Yc**2)
            elif self.plot_format['Ch1_ylabel'] == 'Xsignal':
                self.plotdata[self.i, 1] = Xsum
            elif self.plot_format['Ch1_ylabel'] == 'Rsignal':
                self.plotdata[self.i, 1] = sqrt(Xsum**2 + Ysum**2)
            else:
                self.plotdata[self.i, 1] = 0  ## Zero signal indicates an error
                print(
                    'The signal in Channel 1 is 0, possibly due to a communication Error'
                )

            if self.plot_format['Ch2_ylabel'] == 'Rbaseline':
                self.plotdata[self.i, 2] = sqrt(Xc**2 + Yc**2)
            else:
                self.plotdata[self.i, 2] = 0  ## Zero signal indicates an error
                print(
                    'The signal in Channel 2 is 0, possibly due to a communication Error'
                )

            self.master.update_plot(self.plotdata)

            if self.i < self.num - 1:
                self.i += 1
                self.move(self.record[self.i, 0], speed='Fast')
                self.master.window.after(
                    int(self.integration_time + self.waiting_time),
                    self.mode_lockin)

            else:
                self.finish_scan()

    def prepare_scan_spectrometer(self):
        """ Any scan is divided in three stages:
        1) Prepare the conditions of the scan (this function), getting starting point, integration time and creating all relevant variables.
        2) Runing the scan, performed by a recursive function "mode_spectrometer" or "mode_lockin"
        3) Finish the scan, where we update some variables and save the data.

        :return: None
        """
        self.options_mono = {
            'opt1': self.start_wl,
        }  #### Edit as required

        self.update_integration_time()
        self.update_waiting_time()

        # We check the background is not none and offer to update it
        if self.background is None:
            meas_bg = messagebox.askyesno(
                message=
                'There is no background spectrum for this integration time!',
                detail='Do you want to measure it now?',
                icon='question',
                title='Measure background?')

            self.get_background(meas_bg)

        # # If we are in a batch, we proceed to the next point
        if self.batch.ready:
            self.batch.batch_proceed()

        # If the integration time is too long, we have to split the acquisition in several steps,
        # otherwise the spectrometer hangs
        self.num = int(
            np.ceil(self.integration_time /
                    self.acquisition.max_integration_time))

        # Here we select the wavelength range we want to record and re-shape the record array
        # Get the scan conditions
        self.start_wl = max(float(self.Start_entry.get()), 300)
        self.stop_wl = max(min(float(self.Stop_entry.get()), 2000),
                           self.start_wl + 1)
        wl = self.acquisition.measure()[0]
        self.idx = np.where((self.start_wl <= wl) & (wl <= self.stop_wl))
        self.size = len(self.idx[0])

        # Create the record array
        self.record = np.zeros((self.size, 3))
        self.record[:, 0] = wl[self.idx]
        self.record[:, 2] = self.background[self.idx]

        self.master.prepare_meas(self.record)

        self.i = 0

        self.scan_running()

        self.mode_spectrometer()

    def mode_spectrometer(self):
        """ Gets the whole spectrum at once recorded by the spectrometer in the range selected

        :return: None
        """

        if not self.stop:
            data = self.acquisition.measure()
            intensity = data[1][self.idx] - self.background[self.idx]
            self.record[:, 1] = (intensity +
                                 self.i * self.record[:, 1]) / (self.i + 1.)

            self.master.update_plot(self.record)

            if self.i < self.num - 1:
                self.i = self.i + 1
                self.master.window.after(int(self.integration_time / self.num),
                                         self.mode_spectrometer)

            else:
                self.finish_scan()

    def finish_scan(self):
        """ Finish the scan, updating some global variables, saving the data in the temp file and offering to save the
        data somewhere else.

        :return: None
        """

        if self.batch.ready:
            self.master.finish_meas(self.record, finish=False)
            self.batch.batch_wrapup(self.record)
        else:
            self.master.finish_meas(self.record, finish=True)

        if self.stop or not self.batch.ready:
            # We reduce the number of counts in the batch to resume at the unfinished point if necessary
            # In other words, it repeats the last, un-finished measurement
            self.batch.count = max(self.batch.count - 1, 0)
            self.scan_stopped()

        else:
            self.prepare_scan()

    def check_inputs(self, parameter, value, min, max):
        """ Checks that the input parameters are within a specified range.

        :return: Error popup
        """
        if value < min or value > max:
            messagebox.showerror("Error", parameter + " is out of range!")
            self.run_button['state'] = 'enabled'
            self.run_ok.append(False)

        else:
            self.run_ok.append(True)

    def run_check(self):
        self.Start_wave = float(self.Start_entry.get())
        self.Stop_wave = float(self.Stop_entry.get())
        self.Step_wave = float(self.Step_entry.get())

        ## Check that inputs are within safe/correct limits
        self.run_ok = []
        self.check_inputs('Start wavelength', self.Start_wave, 100, 2000)
        self.check_inputs('End wavelength', self.Stop_wave, 100, 2000)
        self.check_inputs('Step', self.Step_wave, 0.1, 2000)

    def scan_running(self):
        """ Updates the graphical interface, disable the buttoms that must be disabled during the measurement.

        :return: None
        """
        self.scan_button['text'] = 'Stop'
        self.pause_button['state'] = 'enabled'
        self.record_live_button['state'] = 'disabled'
        self.GoTo_button['state'] = 'disabled'
        self.integration_time_button['state'] = 'disabled'
        self.waiting_time_button['state'] = 'disabled'
        self.background_button['state'] = 'disabled'
        self.clear_background_button['state'] = 'disabled'
        self.stop = False

    def scan_stopped(self):
        """ Returns the graphical interface to normal once the measurement has finished.

        :return: None
        """
        self.scan_button['text'] = 'Run'
        self.pause_button['state'] = 'disabled'
        self.record_live_button['state'] = 'enabled'
        self.GoTo_button['state'] = 'enabled'
        self.integration_time_button['state'] = 'enabled'
        self.waiting_time_button['state'] = 'enabled'
        self.background_button['state'] = 'enabled'
        self.clear_background_button['state'] = 'enabled'
        self.stop = True

    def get_background(self, meas_bg=True):
        """ Gets the background when using the spectrometer, if requested.

        :parameter: meas_bg     True or False: wether to measure a background or just produce a bg with zeros
        :return: None
        """

        if meas_bg:
            self.update_integration_time()
            self.background = self.acquisition.measure()[1]
            messagebox.showinfo(message='Background taken!',
                                detail='Press OK to continue.',
                                title='Background taken!')
        else:
            self.background = self.acquisition.measure()[1] * 0.0

    def clear_background(self):
        """ Clears the background when using the spectrometer.

        :return: None
        """
        self.background = None

    def record_live(self):
        """ Starts and stops a live recording.

        :return: None
        """
        if self.stop:
            self.stop = False
            self.record_live_button['text'] = 'Stop'
            self.pause_live_button['state'] = 'enabled'
            self.scan_button['state'] = 'disabled'
            self.background_button['state'] = 'disabled'
            self.clear_background_button['state'] = 'disabled'
            self.start_live()
        else:
            self.record_live_button['text'] = 'Record'
            self.pause_live_button['state'] = 'disabled'
            self.scan_button['state'] = 'enabled'
            self.background_button['state'] = 'enabled'
            self.clear_background_button['state'] = 'enabled'
            self.stop = True
            self.finish_live()

    def pause_live(self):
        """ Pauses a live recording or resumes the acquisition
        """
        self.stop = not self.stop

        if self.stop:
            self.pause_live_button['text'] = 'Resume'
        else:
            self.pause_live_button['text'] = 'Pause'

        self.live()

    def prepare_live_lockin(self):
        """ Prepares the lock-in live acquisition and prepare some variables
        """
        self.acquisition.open()
        self.goto()
        self.update_integration_time()

        self.window_points = int(self.window_live_entry.get())

        self.live_data = np.zeros((self.window_points, 3))
        self.live_data[:, 0] = np.arange(self.window_points)

        # Removes all plots, but not the data, and change the horizontal axis conditions
        self.master.clear_plot(xtitle='Time', ticks='off')
        self.master.prepare_meas(self.live_data)

        self.live_lockin()

    def live_lockin(self):
        """ Runs the live lock-in acquisition
        """
        if not self.stop:
            self.live_data[:-1, 1] = self.live_data[1:, 1]
            self.live_data[:-1, 2] = self.live_data[1:, 2]

            Xc, Yc, Xsum, Ysum, Xdif, Ydif = self.acquisition.measure()

            ## Correct for RMS and half signals
            Xc = Xc * sqrt(2)
            Yc = Yc * sqrt(2)
            Xsum = Xsum * 2 * sqrt(2)
            Ysum = Ysum * 2 * sqrt(2)
            Xdif = Xdif * 2 * sqrt(2)
            Ydif = Ydif * 2 * sqrt(2)

            if self.plot_format['Ch1_ylabel'] == 'Xsignal/Rbaseline':
                self.live_data[-1, 1] = Xsum / sqrt(Xc**2 + Yc**2)
            elif self.plot_format['Ch1_ylabel'] == 'Rsignal/Rbaseline':
                self.live_data[-1, 1] = sqrt(Xsum**2 + Ysum**2) / sqrt(Xc**2 +
                                                                       Yc**2)
            elif self.plot_format['Ch1_ylabel'] == 'Xsignal':
                self.live_data[-1, 1] = Xsum
            elif self.plot_format['Ch1_ylabel'] == 'Rsignal':
                self.live_data[-1, 1] = sqrt(Xsum**2 + Ysum**2)
            else:
                self.live_data[-1, 1] = 0  ## Zero signal indicates an error
                print(
                    'The signal in Channel 1 is 0, possibly due to a communication Error'
                )

            if self.plot_format['Ch2_ylabel'] == 'Rbaseline':
                self.live_data[-1, 2] = sqrt(Xc**2 + Yc**2)
            else:
                self.live_data[-1, 2] = 0  ## Zero signal indicates an error
                print(
                    'The signal in Channel 2 is 0, possibly due to a communication Error'
                )

            self.master.update_plot(self.live_data)

            self.master.window.after(self.integration_time, self.live_lockin)

    def prepare_live_spectrometer(self):
        """ Prepares the spectrometer live acquisition and prepare some variables
        """
        self.update_integration_time()

        if self.integration_time > self.acquisition.max_integration_time:
            self.integration_time = int(self.acquisition.max_integration_time)
            self.acquisition.update_integration_time(self.integration_time)

        data0, data1 = self.acquisition.measure()

        self.live_data = np.zeros((len(data0), 3))

        self.live_data[:, 0] = data0
        self.live_data[:, 1] = data1

        # Removes all plots, but not the data, and change the horizontal axis conditions
        self.master.clear_plot(xtitle='Wavelength (nm)', ticks='on')
        self.master.prepare_meas(self.live_data)

        self.live_spectrometer()

    def live_spectrometer(self):
        """ Runs the live spectrometer acquisition
        """
        if not self.stop:
            data0, data1 = self.acquisition.measure()
            self.live_data[:, 1] = data1

            self.master.update_plot(self.live_data)

            self.master.window.after(self.integration_time,
                                     self.live_spectrometer)

    def finish_live(self):
        """ Finish the live acquisition, returning the front end to the scan mode
        """
        self.master.replot_data(xtitle='Wavelength (nm)', ticks='on')

    def update_integration_time(self):
        """ Updates the integration time
        """
        old_integration_time = self.integration_time
        self.integration_time = int(self.integration_time_entry.get())

        if old_integration_time != self.integration_time:
            self.clear_background()
            self.integration_time = self.acquisition.update_integration_time(
                self.integration_time)
            self.integration_time_entry.delete(0, tk.END)
            self.integration_time_entry.insert(0, '%i' % self.integration_time)

    def update_waiting_time(self):
        """ Updates the waiting time
        """
        self.waiting_time = int(self.waiting_time_entry.get())

    def goto(self):
        """ Go to the specified wavelength
        """
        wl = float(self.GoTo_entry.get())
        self.move(wl, speed='Fast')
        print('Done! Wavelength = {} nm'.format(wl))