Пример #1
0
class ChamberPressureGauge(PressureGauge):
    """
    This class implements the AGC100 pressure gauge. The class communicates with the device over RS232 using pyserial.
    """

    _possible_com_ports = ['COM' + str(i) for i in range(0, 256)]

    _DEFAULT_SETTINGS = Parameter([
        Parameter('port', 'COM7', _possible_com_ports,
                  'com port to which the gauge controller is connected'),
        Parameter(
            'timeout', 1.0, float, 'amount of time to wait for a response '
            'from the gauge controller for each query'),
        Parameter('baudrate', 9600, int,
                  'baudrate of serial communication with gauge')
    ])
Пример #2
0
class ScriptMinimalDummy(Script):
    """
Minimal Example Script that has only a single parameter (execution time)
    """

    _DEFAULT_SETTINGS = [
        Parameter('execution_time', 0.1, float, 'execution time of script (s)')
    ]

    _INSTRUMENTS = {}
    _SCRIPTS = {}

    def __init__(self, name=None, settings=None, log_function = None, data_path = None):
        """
        Example of a script
        Args:
            name (optional): name of script, if empty same as class name
            settings (optional): settings for this script, if empty same as default settings
        """
        Script.__init__(self, name, settings, log_function= log_function, data_path = data_path)


    def _function(self):
        """
        This is the actual function that will be executed. It uses only information that is provided in the settings property
        will be overwritten in the __init__
        """
        import time
        time.sleep(self.settings['execution_time'])
Пример #3
0
class AttoStep(Script):
    # COMMENT_ME
    _DEFAULT_SETTINGS = [
        Parameter('axis', 'z', ['x', 'y', 'z'], 'Axis to step on'),
        Parameter(
            'direction', 'Up', ['Up', 'Down'],
            'step direction, up or down in voltage (or on physical switch)')
    ]

    _INSTRUMENTS = {'attocube': Attocube}
    _SCRIPTS = {}

    def __init__(self,
                 instruments=None,
                 name=None,
                 settings=None,
                 log_function=None,
                 data_path=None):
        """
        Default script initialization
        """
        Script.__init__(self,
                        name,
                        settings=settings,
                        instruments=instruments,
                        log_function=log_function,
                        data_path=data_path)

    def _function(self):
        """
        Performs a single attocube step with the voltage and frequency, and in the direction, specified in settings
        """
        attocube = self.instruments['attocube']['instance']
        attocube_voltage = self.instruments['attocube']['settings'][
            self.settings['axis']]['voltage']
        attocube.update({self.settings['axis']: {'voltage': attocube_voltage}})
        attocube_freq = self.instruments['attocube']['settings'][
            self.settings['axis']]['freq']
        attocube.update({self.settings['axis']: {'freq': attocube_freq}})
        if self.settings['direction'] == 'Up':
            dir = 0
        elif self.settings['direction'] == 'Down':
            dir = 1
        self.instruments['attocube']['instance'].step(self.settings['axis'],
                                                      dir)
class SetMagnetAzimuthalAngle(Script):
    """
    This script sets magnet azimuthal angle via rotation stage controlled by KDC101
    """

    _DEFAULT_SETTINGS = [
        Parameter('azimuthal_angle', 0, float, 'azimuthal angle to set'),
        Parameter('azimuthal_angular_speed', 0, float,
                  'azimuthal angle to set')
    ]

    _INSTRUMENTS = {'KDC101': KDC101}

    _SCRIPTS = {}

    def __init__(self,
                 instruments=None,
                 scripts=None,
                 name=None,
                 settings=None,
                 log_function=None,
                 data_path=None):
        """
        Example of a script that emits a QT signal for the gui
        Args:
            name (optional): name of script, if empty same as class name
            settings (optional): settings for this script, if empty same as default settings
        """
        Script.__init__(self,
                        name,
                        settings=settings,
                        instruments=instruments,
                        scripts=scripts,
                        log_function=log_function,
                        data_path=data_path)
        self.azimuth_controller = self.instruments['KDC101']['instance']

    def _function(self):
        angle = (self.settings['azimuthal_angle'] + 360) % 360
        self.azimuth_controller._move_servo(
            angle, self.settings['azimuthal_angular_speed'])
        self.log('magnet azimuthal angle set to {:}deg'.format(
            self.settings['azimuthal_angle']))
Пример #5
0
class SetMagnetZ(Script):
    """
    This script sets magnet azimuthal angle via rotation stage controlled by KDC101
    """

    _DEFAULT_SETTINGS = [
        Parameter('magnet_z', 0, float, 'azimuthal angle to set [deg]'),
        Parameter('move_speed', 0.5, float,
                  'azimuthal angular speed [deg/sec]')
    ]

    _INSTRUMENTS = {'TDC001': TDC001}

    _SCRIPTS = {}

    def __init__(self,
                 instruments=None,
                 scripts=None,
                 name=None,
                 settings=None,
                 log_function=None,
                 data_path=None):
        """
        Example of a script that emits a QT signal for the gui
        Args:
            name (optional): name of script, if empty same as class name
            settings (optional): settings for this script, if empty same as default settings
        """
        Script.__init__(self,
                        name,
                        settings=settings,
                        instruments=instruments,
                        scripts=scripts,
                        log_function=log_function,
                        data_path=data_path)
        self.z_mag_controller = self.instruments['TDC001']['instance']

    def _function(self):
        self.z_mag_controller._move_servo(self.settings['magnet_z'],
                                          self.settings['move_speed'])
        self.log('magnet Z set to {:}mm from bottom'.format(
            self.settings['magnet_z']))
Пример #6
0
class CameraOn(Script):
    # COMMENT_ME

    _DEFAULT_SETTINGS = [Parameter('On', True, bool, '')]

    _INSTRUMENTS = {'light_control': MaestroLightControl}
    _SCRIPTS = {}

    def __init__(self,
                 instruments,
                 name=None,
                 settings=None,
                 log_function=None,
                 data_path=None):
        """
        Example of a script that makes use of an instrument
        Args:
            instruments: instruments the script will make use of
            name (optional): name of script, if empty same as class name
            settings (optional): settings for this script, if empty same as default settings
        """

        # call init of superclass
        Script.__init__(self,
                        name,
                        settings,
                        instruments,
                        log_function=log_function,
                        data_path=data_path)

    def _function(self):
        """
        This is the actual function that will be executed. It uses only information that is provided in _DEFAULT_SETTINGS
        for this dummy example we just implement a counter
        """

        if self.settings['On'] == True:
            # fluorescence filter
            self.instruments['white_light'].update({'open': False})
            self.instruments['filter_wheel'].update(
                {'current_position': 'position_3'})
            self.instruments['block_ir'].update({'open': True})
            self.instruments['block_green'].update({'open': True})

            self.log('camera on')
        else:
            # high ND
            self.instruments['filter_wheel'].update(
                {'current_position': 'position_1'})
            self.instruments['block_ir'].update({'open': False})
            self.instruments['block_green'].update({'open': False})
            self.instruments['white_light'].update({'open': True})

            self.log('camera off')
Пример #7
0
class PulseDelays(PulseBlasterBaseScript):
    # COMMENT_ME
    _DEFAULT_SETTINGS = [
        Parameter('count_source_pulse_width', 10000, int,
                  'How long to pulse the count source (in ns)'),
        Parameter('measurement_gate_pulse_width', 15, int,
                  'How long to have the DAQ acquire data (in ns)'),
        Parameter('min_delay', 0, int, 'minimum delay over which to scan'),
        Parameter('max_delay', 1000, int, 'maximum delay over which to scan'),
        Parameter('delay_interval_step_size', 15, int,
                  'Amount delay is increased for each new run'),
        Parameter('num_averages', 1000, int,
                  'number of times to average for each delay'),
        Parameter(
            'reset_time', 10000, int,
            'How long to wait for laser to turn off and reach steady state'),
        Parameter('skip_invalid_sequences', False, bool,
                  'Skips any sequences with <15ns commands')
    ]

    def _create_pulse_sequences(self):
        '''
        Creates a pulse sequence with no pulses for a reset time, then the laser on for a count_source_pulse_width time.
        The daq measurement window is then swept across the laser pulse window to measure counts from min_delay
        (can be negative) to max_delay, which are both defined with the laser on at 0.

        Returns: pulse_sequences, num_averages, tau_list, measurement_gate_width
            pulse_sequences: a list of pulse sequences, each corresponding to a different time 'tau' that is to be
            scanned over. Each pulse sequence is a list of pulse objects containing the desired pulses. Each pulse
            sequence must have the same number of daq read pulses
            num_averages: the number of times to repeat each pulse sequence
            tau_list: the list of times tau, with each value corresponding to a pulse sequence in pulse_sequences
            measurement_gate_width: the width (in ns) of the daq measurement


        '''
        pulse_sequences = []
        gate_delays = range(self.settings['min_delay'],
                            self.settings['max_delay'],
                            self.settings['delay_interval_step_size'])
        reset_time = self.settings['reset_time']
        for delay in gate_delays:
            pulse_sequences.append([
                Pulse('laser', reset_time,
                      self.settings['count_source_pulse_width']),
                Pulse('apd_readout', delay + reset_time,
                      self.settings['measurement_gate_pulse_width'])
            ])
        return pulse_sequences, self.settings[
            'num_averages'], gate_delays, self.settings[
                'measurement_gate_pulse_width']
Пример #8
0
 def value(self, value):
     if Parameter.is_valid(value, self.valid_values):
         self._value = value
         # check if there is a special case for setting such as a checkbox or combobox
         if hasattr(self, 'check'):
             self.check.setChecked(value)
         elif hasattr(self, 'combo_box'):
             self.combo_box.setCurrentIndex(
                 self.combo_box.findText(unicode(self.value)))
         else:  #for standard values
             self.setData(1, 0, value)
     else:
         if value is not None:
             raise TypeError("wrong type {:s}, expected {:s}".format(
                 str(value), str(self.valid_values)))
Пример #9
0
class SetMagneticCoils(Script):
    """
This script sets the magnetic field coils to the given magnetic field values
    """

    _DEFAULT_SETTINGS = [
        Parameter('magnetic_fields', [
            Parameter('x/r_field', 0.0, float,
                      'x or r component of field vector in Gauss'),
            Parameter('y/theta_field', 0.0, float,
                      'y or theta component of field vector in Gauss'),
            Parameter('z/phi_field', 0.0, float,
                      'z or phi component of field vector in Gauss'),
            Parameter(
                'coordinate_system', 'Cartesian', ['Cartesian', 'Spherical'],
                'Chooses what coordinate system to use to interpret field parameters'
            )
        ])
    ]

    _INSTRUMENTS = {'MagnetCoils': MagnetCoils}

    _SCRIPTS = {}

    def __init__(self,
                 instruments=None,
                 scripts=None,
                 name=None,
                 settings=None,
                 log_function=None,
                 data_path=None):
        """
        Example of a script that emits a QT signal for the gui
        Args:
            name (optional): name of script, if empty same as class name
            settings (optional): settings for this script, if empty same as default settings
        """
        Script.__init__(self,
                        name,
                        settings=settings,
                        instruments=instruments,
                        scripts=scripts,
                        log_function=log_function,
                        data_path=data_path)

    def _function(self):
        """
        This is the actual function that will be executed. It uses only information that is provided in the settings property
        will be overwritten in the __init__
        """
        self.data = {}

        input_vector = [
            self.settings['magnetic_fields']['x/r_field'],
            self.settings['magnetic_fields']['y/theta_field'],
            self.settings['magnetic_fields']['z/phi_field']
        ]

        if self.settings['magnetic_fields'][
                'coordinate_system'] == 'Spherical':
            [new_x_field, new_y_field,
             new_z_field] = spherical_to_cartesian(input_vector)
        elif self.settings['magnetic_fields'][
                'coordinate_system'] == 'Cartesian':
            [new_x_field, new_y_field, new_z_field] = input_vector

        # new_x_field = 0
        # new_y_field = 0
        # new_z_field = 10

        # try:
        #     self.instruments['MagnetCoils']['instance'].calc_voltages_for_fields([new_x_field, new_y_field, new_z_field])
        # except ValueError:
        #     raise
        #     self.log('Could not set magnetic field. Reverting to previous value.')

        #need to cast from float64 in the 'Spherical' case

        # take settings defined in the script and update with settings for the fields
        dictator = self.instruments['MagnetCoils']['settings']
        dictator.update({
            'magnetic_fields': {
                'x_field': float(new_x_field),
                'y_field': float(new_y_field),
                'z_field': float(new_z_field)
            }
        })

        self.instruments['MagnetCoils']['instance'].update(dictator)
        #
        self.log(
            'Magnetic Field set to Bx={:.4}G, By={:.4}G, Bz={:.4}G'.format(
                self.instruments['MagnetCoils']
                ['instance'].settings['magnetic_fields']['x_field'],
                self.instruments['MagnetCoils']
                ['instance'].settings['magnetic_fields']['y_field'],
                self.instruments['MagnetCoils']
                ['instance'].settings['magnetic_fields']['z_field']))

        print('requested fields',
              self.instruments['MagnetCoils']['instance'].requested_fields)
        print('applied fields',
              self.instruments['MagnetCoils']['instance'].applied_fields)

        self.data['new_voltages'] = self.instruments['MagnetCoils'][
            'instance'].new_voltages
        self.data['requested_fields'] = self.instruments['MagnetCoils'][
            'instance'].requested_fields
        self.data['applied_fields'] = self.instruments['MagnetCoils'][
            'instance'].applied_fields
Пример #10
0
class FindNV(Script):
    """
GalvoScan uses the apd, daq, and galvo to sweep across voltages while counting photons at each voltage,
resulting in an image in the current field of view of the objective. new

Known issues:
    1.) if fits are poor, check  sweep_range. It should extend significantly beyond end of NV on both sides.
    """

    _DEFAULT_SETTINGS = [
        Parameter('initial_point', [
            Parameter('x', 0, float, 'x-coordinate'),
            Parameter('y', 0, float, 'y-coordinate')
        ]),
        Parameter('sweep_range', .12, float,
                  'voltage range to sweep over to find a max'),
        Parameter('num_points', 61, int,
                  'number of points to sweep in the sweep range'),
        Parameter('nv_size', 11, int,
                  'TEMP: size of nv in pixels - need to be refined!!'),
        Parameter('min_mass', 180, int,
                  'TEMP: brightness of nv - need to be refined!!'),
        Parameter(
            'number_of_attempts', 1, int,
            'Number of times to decrease min_mass if an NV is not found'),
        Parameter('center_on_current_location', True, bool,
                  'Check to use current galvo location'),
        Parameter('laser_off_after', True, bool,
                  'if true laser is turned off after the measurement')
    ]

    _INSTRUMENTS = {'PB': CN041PulseBlaster}
    # _INSTRUMENTS = {}

    _SCRIPTS = {'take_image': GalvoScan, 'set_laser': SetLaser}

    def __init__(self,
                 scripts,
                 name=None,
                 settings=None,
                 instruments=None,
                 log_function=None,
                 timeout=1000000000,
                 data_path=None):

        Script.__init__(self,
                        name,
                        scripts=scripts,
                        settings=settings,
                        instruments=instruments,
                        log_function=log_function,
                        data_path=data_path)

    def _function(self):
        """
        This is the actual function that will be executed. It uses only information that is provided in the settings property
        will be overwritten in the __init__
        """

        # LASER ON:
        self.instruments['PB']['instance'].update({'laser': {'status': True}})

        attempt_num = 1
        if self.settings['center_on_current_location']:
            # fixed for cold setup and new DAQ ER 6/4/17
            #daq_pt = self.scripts['take_image'].instruments['daq']['instance'].get_analog_voltages([self.scripts['take_image'].settings['DAQ_channels']['x_ao_channel'], self.scripts['take_image'].settings['DAQ_channels']['y_ao_channel']])
            daq_pt = self.scripts['take_image'].instruments['NI6259'][
                'instance'].get_analog_voltages([
                    self.scripts['take_image'].settings['DAQ_channels']
                    ['x_ao_channel'],
                    self.scripts['take_image'].settings['DAQ_channels']
                    ['y_ao_channel']
                ])
            self.settings['initial_point'].update({
                'x': daq_pt[0],
                'y': daq_pt[1]
            })
        initial_point = self.settings['initial_point']
        nv_size = self.settings['nv_size']
        min_mass = self.settings['min_mass']

        self.data = {
            'maximum_point': None,
            'initial_point': initial_point,
            'image_data': [],
            'extent': [],
            'fluorescence': None
        }

        def pixel_to_voltage(pt, extent, image_dimensions):
            """"
            pt: point in pixels
            extent: [xVmin, Vmax, Vmax, yVmin] in volts
            image_dimensions: dimensions of image in pixels

            Returns: point in volts
            """

            image_x_len, image_y_len = image_dimensions
            image_x_min, image_x_max, image_y_max, image_y_min = extent

            assert image_x_max > image_x_min
            assert image_y_max > image_y_min

            volt_per_px_x = (image_x_max - image_x_min) / (image_x_len - 1)
            volt_per_px_y = (image_y_max - image_y_min) / (image_y_len - 1)

            V_x = volt_per_px_x * pt[0] + image_x_min
            V_y = volt_per_px_y * pt[1] + image_y_min

            return [V_x, V_y]

        def min_mass_adjustment(min_mass):
            #COMMENT_ME
            return (min_mass - 40)

        self.scripts['take_image'].settings['point_a'].update({
            'x':
            self.settings['initial_point']['x'],
            'y':
            self.settings['initial_point']['y']
        })
        self.scripts['take_image'].settings['point_b'].update({
            'x':
            self.settings['sweep_range'],
            'y':
            self.settings['sweep_range']
        })
        self.scripts['take_image'].update({'RoI_mode': 'center'})
        self.scripts['take_image'].settings['num_points'].update({
            'x':
            self.settings['num_points'],
            'y':
            self.settings['num_points']
        })

        self.scripts['take_image'].run()

        self.data['image_data'] = deepcopy(
            self.scripts['take_image'].data['image_data'])
        self.data['extent'] = deepcopy(
            self.scripts['take_image'].data['extent'])
        while True:  # modified ER 5/27/2017 to implement tracking
            locate_info = tp.locate(self.data['image_data'],
                                    nv_size,
                                    minmass=min_mass)
            po = [
                self.data['initial_point']['x'],
                self.data['initial_point']['y']
            ]
            if len(locate_info) == 0:
                self.data['maximum_point'] = {
                    'x': float(po[0]),
                    'y': float(po[1])
                }
            else:

                # all the points that have been identified as valid NV centers
                pts = [
                    pixel_to_voltage(p, self.data['extent'],
                                     np.shape(self.data['image_data']))
                    for p in locate_info[['x', 'y']].as_matrix()
                ]
                self.log('Found {:d} NV(s).'.format(len(pts)))
                if len(pts) > 1:
                    self.log(
                        'FindNV found more than one NV in the scan image. Selecting the one closest to initial point.'
                    )
                if self.settings['laser_off_after']:
                    self.log('Laser is off.')
                # pick the one that is closest to the original one
                pm = pts[np.argmin(
                    np.array([np.linalg.norm(p - np.array(po)) for p in pts]))]
                self.data['maximum_point'] = {
                    'x': float(pm[0]),
                    'y': float(pm[1])
                }
                # print the location of the NV
                self.log('NV position: x={:f} , y={:f}'.format(
                    float(pm[0]), float(pm[1])))
                counter = 0
                for p in pts:  # record maximum counts = fluorescence
                    if p[1] == self.data['maximum_point']['y']:
                        self.data['fluorescence'] = 2 * locate_info[[
                            'signal'
                        ]].as_matrix()[counter]
                        print('fluorescence of the NV, kCps:')
                        print(self.data['fluorescence'])
                        counter += 1
                break

            if attempt_num <= self.settings['number_of_attempts']:
                min_mass = min_mass_adjustment(min_mass)
                attempt_num += 1
            else:
                self.log(
                    'FindNV did not find an NV --- setting laser to initial point instead, setting fluorescence to zero.'
                )
                if self.settings['laser_off_after']:
                    self.log('Laser is off.')
                self.data['fluorescence'] = 0.0
                break

        self.scripts['set_laser'].settings['point'].update(
            self.data['maximum_point'])
        self.scripts['set_laser'].run()

        # LASER OFF if desired:\
        if self.settings['laser_off_after']:
            self.instruments['PB']['instance'].update(
                {'laser': {
                    'status': False
                }})

    @staticmethod
    def plot_data(axes_list, data):
        plot_fluorescence_new(data['image_data'], data['extent'], axes_list[0])

        initial_point = data['initial_point']
        patch = patches.Circle((initial_point['x'], initial_point['y']),
                               .001,
                               ec='g',
                               fc='none',
                               ls='dashed')
        axes_list[0].add_patch(patch)
        axes_list[0].text(initial_point['x'],
                          initial_point['y'] - .002,
                          'initial point',
                          color='g',
                          fontsize=8)

        # plot marker
        if data['maximum_point']:
            maximum_point = data['maximum_point']
            patch = patches.Circle((maximum_point['x'], maximum_point['y']),
                                   .001,
                                   ec='r',
                                   fc='none',
                                   ls='dashed')
            axes_list[0].add_patch(patch)
            axes_list[0].text(maximum_point['x'],
                              maximum_point['y'] - .002,
                              'found NV',
                              color='r',
                              fontsize=8)

    def _plot(self, axes_list, data=None):
        """
        plotting function for find_nv
        Args:
            axes_list: list of axes objects on which to plot plots the esr on the first axes object
            data: data (dictionary that contains keys image_data, extent, initial_point, maximum_point) if not provided use self.data
        """
        if data is None:
            data = self.data

        if self._current_subscript_stage['current_subscript'] == self.scripts[
                'take_image']:
            self.scripts['take_image']._plot(axes_list)
        else:
            self.plot_data(axes_list, data)

    def _update_plot(self, axes_list):
        """
        update plotting function for find_nv
        Args:
            axes_list: list of axes objects on which to plot plots the esr on the first axes object
        """

        if self._current_subscript_stage['current_subscript'] == self.scripts[
                'take_image']:
            self.scripts['take_image']._update_plot(axes_list)

        if self.data['maximum_point']:
            maximum_point = self.data['maximum_point']
            patch = patches.Circle((maximum_point['x'], maximum_point['y']),
                                   .001,
                                   ec='r',
                                   fc='none',
                                   ls='dashed')
            axes_list[0].add_patch(patch)
            axes_list[0].text(maximum_point['x'],
                              maximum_point['y'] - .002,
                              'found NV',
                              color='r',
                              fontsize=8)

    def get_axes_layout(self, figure_list):
        """
        returns the axes objects the script needs to plot its data
        the default creates a single axes object on each figure
        This can/should be overwritten in a child script if more axes objects are needed
        Args:
            figure_list: a list of figure objects
        Returns:
            axes_list: a list of axes objects

        """

        # create a new figure list that contains only figure 1, this assures that the super.get_axes_layout doesn't
        # empty the plot contained on figure 2
        return super(FindNV, self).get_axes_layout([figure_list[0]])
Пример #11
0
class optimizeZ(Script):

    _DEFAULT_SETTINGS = [
        Parameter('scan_range', 3, float, 'z-range for optimizing scan [V]'),
        Parameter('num_points', 50, int, 'number of z points to scan'),
        Parameter('time_per_pt', .5, [.25, .5, 1.],
                  'time in s to measure at each point for 1D z-scans only')
    ]

    _INSTRUMENTS = {'PB': CN041PulseBlaster}
    # _INSTRUMENTS = {}

    _SCRIPTS = {'scan_z': ConfocalScan, 'set_focus': SetConfocal}

    def __init__(self,
                 scripts,
                 name=None,
                 settings=None,
                 instruments=None,
                 log_function=None,
                 timeout=1000000000,
                 data_path=None):

        Script.__init__(self,
                        name,
                        scripts=scripts,
                        settings=settings,
                        instruments=instruments,
                        log_function=log_function,
                        data_path=data_path)

        self.scripts['scan_z'].update({'scan_axes': 'z'})
        self.scripts['scan_z'].update({'RoI_mode': 'center'})

    def _function(self):

        # turn laser on
        self.instruments['PB']['instance'].update({'laser': {'status': True}})

        initial_point = self.scripts['scan_z'].instruments['NI6259'][
            'instance'].get_analog_voltages([
                self.scripts['scan_z'].settings['DAQ_channels']
                ['x_ao_channel'],
                self.scripts['scan_z'].settings['DAQ_channels']
                ['y_ao_channel'],
                self.scripts['scan_z'].settings['DAQ_channels']['z_ao_channel']
            ])

        self.data = {
            'maximum_point': None,
            'initial_point': initial_point[2],
            'fluor_vector': [],
            'extent': [],
            'max_fluor': None
        }

        self.scripts['scan_z'].settings['point_a'].update(
            {'z': initial_point[2]})
        self.scripts['scan_z'].settings['point_b'].update(
            {'z': self.settings['scan_range']})
        self.scripts['scan_z'].settings['num_points'].update(
            {'z': self.settings['num_points']})
        self.scripts['scan_z'].settings['time_per_pt'].update(
            {'z-piezo': self.settings['time_per_pt']})
        self.scripts['scan_z'].run()

        self.data['fluor_vector'] = deepcopy(
            self.scripts['scan_z'].data['image_data'])
        self.data['extent'] = deepcopy(self.scripts['scan_z'].data['bounds'])

        self.data['max_fluor'] = np.amax(self.data['fluor_vector'])
        self.data['maximum_point'] = self.data['extent'][0] + (
            self.data['extent'][1] - self.data['extent'][0]) / (
                len(self.data['fluor_vector']) - 1) * float(
                    np.argmax(self.data['fluor_vector']))

        self.scripts['set_focus'].settings['point'].update({
            'x':
            initial_point[0],
            'y':
            initial_point[1],
            'z':
            self.data['maximum_point']
        })
        self.scripts['set_focus'].run()

        self.log('set z = {:f}'.format(self.data['maximum_point']))
        # turn laser off
        self.instruments['PB']['instance'].update({'laser': {'status': False}})
        self.log('Laser is off.')

    @staticmethod
    def plot_data(axes_list, data):
        plot_counts(
            axes_list[0], data['fluor_vector'],
            np.linspace(data['extent'][0], data['extent'][1],
                        len(data['fluor_vector'])), 'z [V]')
        if data['maximum_point'] and data['max_fluor']:
            axes_list[0].hold(True)
            axes_list[0].plot(data['maximum_point'], data['max_fluor'], 'ro')
            axes_list[0].hold(False)

    def _plot(self, axes_list, data=None):
        """
        Plots the confocal scan image
        Args:
            axes_list: list of axes objects on which to plot the galvo scan on the first axes object
            data: data (dictionary that contains keys image_data, extent) if not provided use self.data
        """
        if data is None:
            data = self.data

        if self._current_subscript_stage['current_subscript'] == self.scripts[
                'scan_z']:
            self.scripts['scan_z']._plot(axes_list)
        else:
            self.plot_data(axes_list, data)

        # plot_counts(axes_list[0], data['fluor_vector'],np.linspace(data['extent'][0],data['extent'][1],len(data['fluor_vector'])),'z [V]')
        # axes_list[0].hold(True)
        # axes_list[0].plot(data['maximum_point'], data['max_fluor'], 'ro')
        # axes_list[0].hold(False)

    def _update_plot(self, axes_list):
        """
        updates the galvo scan image
        Args:
            axes_list: list of axes objects on which to plot plots the esr on the first axes object
        """

        if self._current_subscript_stage['current_subscript'] == self.scripts[
                'scan_z']:
            self.scripts['scan_z']._update_plot(axes_list)

        if self.data['maximum_point'] and self.data['max_fluor']:
            axes_list[0].hold(True)
            axes_list[0].plot(self.data['maximum_point'],
                              self.data['max_fluor'], 'ro')
            axes_list[0].hold(False)

    def get_axes_layout(self, figure_list):
        """
        returns the axes objects the script needs to plot its data
        the default creates a single axes object on each figure
        This can/should be overwritten in a child script if more axes objects are needed
        Args:
            figure_list: a list of figure objects
        Returns:
            axes_list: a list of axes objects

        """

        # only pick the first figure from the figure list, this avoids that get_axes_layout clears all the figures
        return super(optimizeZ, self).get_axes_layout([figure_list[1]])
Пример #12
0
class Ni9263_BalancePolarization(Script):
    """
Ni9263_BalancePolarization:
script to balance photodetector to zero by adjusting polarization controller voltages
 uses the Ni9263 as output and the NI DAQ as input
    """

    _DEFAULT_SETTINGS = [
        Parameter(
            'channels',
            [
                Parameter('channel_WP_1', 'NI9263_ao0',
                          ['NI9263_ao0', 'NI9263_ao1', 'NI9263_ao2'],
                          'analog channel that controls waveplate 1'),
                Parameter('channel_WP_2', 'NI9263_ao1',
                          ['NI9263_ao0', 'NI9263_ao1', 'NI9263_ao2'],
                          'analog channel that controls waveplate 2'),
                Parameter('channel_WP_3', 'NI9263_ao2',
                          ['NI9263_ao0', 'NI9263_ao1', 'NI9263_ao2'],
                          'analog channel that controls waveplate 3'),
                # Parameter('channel_OnOff', 'NI6259_do8', ['NI6259_do8'], 'digital channel that turns polarization controller on/off'),
                Parameter(
                    'channel_OnOff', 'NI6259_ao3', ['NI6259_ao3'],
                    'analog channel that turns polarization controller on/off'
                ),
                Parameter('channel_detector', 'NI6259_ai0', ['NI6259_ai0'],
                          'analog input channel of the detector signal')
            ]),
        Parameter('setpoints', [
            Parameter('V_1', 2.4, float, 'voltage applied to waveplate 1'),
            Parameter('V_2', 4.0, float, 'voltage applied to waveplate 2'),
            Parameter('V_3', 2.4, float, 'voltage applied to waveplate 3'),
            Parameter('save_result_as_setpoint', False, bool,
                      'uses the current best result as the new setpoint')
        ]),
        Parameter(
            'optimization',
            [
                Parameter('target', .01, float, 'target max detector signal'),
                Parameter('settle_time', 2., float, 'settle time (s)'),
                Parameter('WP_control', 2, [1, 2, 3], 'control waveplate'),
                Parameter('dV', 0.01, float,
                          'initial step size of search algorithm'),
                Parameter(
                    'slope', 'negative', ['positive', 'negative'],
                    'is the slope around the zero crossing is positive or negative'
                )
                # Parameter('start with current', True, bool, 'uses the current output as starting point (instead of setpoint) if not zero'),
            ]),
        Parameter('measure_at_zero', [
            Parameter('on', True, bool,
                      'if true keep measuring after zero is found'),
            Parameter('N', 10, int,
                      'number of measurement points after zero is found')
        ])
    ]

    _INSTRUMENTS = {'NI6259': NI6259, 'NI9263': NI9263}

    _SCRIPTS = {}

    def __init__(self,
                 instruments,
                 scripts=None,
                 name=None,
                 settings=None,
                 log_function=None,
                 data_path=None):
        """
        Example of a script that emits a QT signal for the gui
        Args:
            name (optional): name of script, if empty same as class name
            settings (optional): settings for this script, if empty same as default settings
        """
        Script.__init__(self,
                        name,
                        settings=settings,
                        scripts=scripts,
                        instruments=instruments,
                        log_function=log_function,
                        data_path=data_path)

    def _function(self):
        """
        This is the actual function that will be executed. It uses only information that is provided in the settings property
        will be overwritten in the __init__
        """
        def get_direction(detector_value, slope):
            """
            give the direction we have to move given the detector value and the slope of the zero crossing
            Args:
                detector_value: detector value
                slope: slope of the zero crossing

            Returns: direction in which to step


            we calculate the directin based on the following truth table (i.e. xor table)

                slope   detector    direction
                +       -           +
                +       +           -
                -       +           +
                -       -           -


            """
            direction = (int(np.sign(detector_value)) == 1) ^ (int(slope) == 1)
            # now map True and False and 1 and -1
            direction = 1 if direction else -1

            return direction

        self.data = {
            'voltage_waveplate': [],
            'detector_signal': [],
            'det_signal_cont': []
        }
        wp_control = self.settings['optimization']['WP_control']
        settle_time = self.settings['optimization']['settle_time']
        v_out = float(self.settings['setpoints']['V_{:d}'.format(wp_control)])
        target = self.settings['optimization']['target']
        detector_value = 2 * target

        # convert slope to numeric value
        slope = 1 if self.settings['optimization'][
            'slope'] == 'positive' else -1

        # get the channels
        control_channel = self.settings['channels']['channel_OnOff'].split(
            'NI6259_')[1]
        channel_out = self.settings['channels']['channel_WP_{:d}'.format(
            wp_control)].split('NI9263_')[1]
        channel_in = self.settings['channels']['channel_detector'].split(
            'NI6259_')[1]

        # NIDAQ_DIO = self.instruments['NI6259']['instance'] # digital channel
        NIDAQ_AI = self.instruments['NI6259']['instance']  # analog input
        NIDAQ_AO = self.instruments['NI9263']['instance']  # analog output

        # turn controller on
        # NIDAQ_DIO.set_digital_output({control_channel: True}) #switched to analog output since digital output is
        #slightly undervolted and threshold is at 5V
        NIDAQ_AO.set_analog_voltages({control_channel: 5.1})

        # fpga_io.update({'read_io':{control_channel: True}})

        # set the setpoints for all three waveplates
        dictator = {}
        for i in [1, 2, 3]:
            # readout of daq output voltages not avaliable on Ni_9263, no analog input
            # if self.settings['optimization']['start with current'] and i == wp_control:
            #     # value = getattr(fpga_io, channel_out)
            #     value = NIDAQ_AO[channel_out]
            #     if value == 0:
            #         value = float(self.settings['setpoints']['V_{:d}'.format(i)])
            #         self.log('current value is zero take setpoint {:0.3f} V as starting point'.format(value))
            #     else:
            #         # value = int_to_voltage(value)
            #         v_out = value
            #         self.log('use current value {:0.3f} V as starting point'.format(value))
            # else:
            #     value = float(self.settings['setpoints']['V_{:d}'.format(i)])
            value = float(self.settings['setpoints']['V_{:d}'.format(i)])

            daq_wp_channel = self.settings['channels'][
                'channel_WP_{:d}'.format(i)].split('NI9263_')[1]
            dictator.update({daq_wp_channel: value})

        NIDAQ_AO.set_analog_voltages(dictator)
        time.sleep(settle_time)

        crossed_zero = False
        while abs(detector_value) > abs(target):
            if self._abort:
                break

            # set output
            # NIDAQ_AO.update({channel_out: float(v_out)})
            NIDAQ_AO.set_analog_voltages({channel_out: float(v_out)})
            # fpga_io.update({'read_io':{channel_out: float(v_out)}})
            # wait for system to settle
            time.sleep(settle_time)
            # read detector
            # detector_value = NIDAQ_DIO[channel_in]
            detector_value = NIDAQ_AI.get_analog_voltages([channel_in])[0]

            self.data['voltage_waveplate'].append(v_out)
            self.data['detector_signal'].append(detector_value)
            self.progress = 50.
            self.updateProgress.emit(self.progress)

            direction = get_direction(detector_value, slope)
            print('----> out', v_out, voltage_to_int((v_out)))
            print('----> det', detector_value, slope, direction)
            # calculate the next step
            if len(self.data['voltage_waveplate']) == 1:
                v_step = self.settings['optimization'][
                    'dV']  # start with initial step size
            elif len(self.data['voltage_waveplate']) > 1:

                # check for zero crossing
                if self.data['detector_signal'][-2] * self.data[
                        'detector_signal'][-1] < 0:
                    self.log('detected zero crossing!')
                    v_step /= 2  # decrease the step size since we are closer to zero

            # calculate next output voltage
            v_out += v_step * direction

            if v_out > 5 or v_out < 0:
                v_out = 5 if v_out > 5 else 0  # set the output to be within the range
                slope *= -1

            if min(self.data['voltage_waveplate']) == 0 and max(
                    self.data['voltage_waveplate']) == 5:
                self.log(
                    'warning! scanned full range without finding zero. abort!')
                self._abort = True

        self.data['setpoint'] = v_out
        print('starting continuous measurement')

        n_opt = len(self.data['voltage_waveplate'])
        n_cont = self.settings['measure_at_zero']['N']
        if self.settings['measure_at_zero']['on']:
            for i in range(n_cont):
                if self._abort:
                    break
                detector_value = NIDAQ_AI.get_analog_voltages([channel_in])
                self.data['det_signal_cont'].append(detector_value)
                self.progress = 100. * (i + n_opt + 1) / (n_cont + n_opt)

                self.updateProgress.emit(self.progress)
                time.sleep(settle_time)

        if self.settings['setpoints']['save_result_as_setpoint']:
            self.settings['setpoints']['V_{:d}'.format(
                self.settings['optimization']['WP_control'])] = v_out

    def _plot(self, axes_list):

        if self.data != {}:

            # dt = self.settings['optimization']['settle_time']
            # N = len(self.data['voltage_waveplate'])

            # t = np.linspace(0,N * dt, N)
            volt_range = np.array(self.data['voltage_waveplate'])
            signal = np.array(self.data['detector_signal'])

            if len(self.data['det_signal_cont']) == 0:
                axes_list[0].plot(signal, '-o')
                axes_list[0].hold(False)
                axes_list[0].set_ylabel('detector signal (bits)')

                axes_list[1].plot(volt_range, '-o')
                axes_list[1].hold(False)
                axes_list[0].set_ylabel('wp voltage (V)')
            else:
                axes_list[1].plot(signal / float(2**15),
                                  '-o',
                                  label='detector signal / (2^15)')
                axes_list[1].hold(True)
                axes_list[1].plot(volt_range / 5.,
                                  '-o',
                                  label='wp voltage / 5V')
                axes_list[1].hold(False)
                axes_list[1].set_ylabel('signal')
                axes_list[1].legend(fontsize=8)

                signal_det = np.array(self.data['det_signal_cont'])
                axes_list[0].plot(signal_det, '-o')
                axes_list[0].set_ylabel('detector signal (bit)')
                axes_list[0].hold(False)

    def _update(self, axes_list):
        volt_range = np.array(self.data['voltage_waveplate'])
        signal = np.array(self.data['detector_signal'])
        signal_det = np.array(self.data['det_signal_cont'])

        if len(self.data['det_signal_cont']) == 1:
            self._plot(self, axes_list)
        elif len(self.data['det_signal_cont']) == 0:

            axes_list[0].lines[0].set_ydata(signal)
            axes_list[1].lines[0].set_ydata(volt_range)
        else:
            axes_list[0].lines[0].set_ydata(signal_det)
Пример #13
0
class NI6259old(DAQold):
    """
    This class implements the NI6259 DAQ, which includes 32 AI, 4 AO, and 24 DI/DO channels and inherits basic
    input/output functionality from DAQ. A subset of these channels are accessible here, but more can be added up to
    these limits.
    """
    _DEFAULT_SETTINGS = Parameter([
        Parameter('device', 'Dev1', (str), 'Name of DAQ device'),
        Parameter('override_buffer_size', -1, int,
                  'Buffer size for manual override (unused if -1)'),
        Parameter(
            'ao_read_offset', .005, float,
            'Empirically determined offset for reading ao voltages internally'
        ),
        Parameter('analog_output', [
            Parameter('ao0', [
                Parameter('channel', 0, [0, 1, 2, 3], 'output channel'),
                Parameter('sample_rate', 1000.0, float,
                          'output sample rate (Hz)'),
                Parameter('min_voltage', -10.0, float,
                          'minimum output voltage (V)'),
                Parameter('max_voltage', 10.0, float,
                          'maximum output voltage (V)')
            ]),
            Parameter('ao1', [
                Parameter('channel', 1, [0, 1, 2, 3], 'output channel'),
                Parameter('sample_rate', 1000.0, float,
                          'output sample rate (Hz)'),
                Parameter('min_voltage', -10.0, float,
                          'minimum output voltage (V)'),
                Parameter('max_voltage', 10.0, float,
                          'maximum output voltage (V)')
            ]),
            Parameter('ao2', [
                Parameter('channel', 2, [0, 1, 2, 3], 'output channel'),
                Parameter('sample_rate', 1000.0, float,
                          'output sample rate (Hz)'),
                Parameter('min_voltage', -10.0, float,
                          'minimum output voltage (V)'),
                Parameter('max_voltage', 10.0, float,
                          'maximum output voltage (V)')
            ]),
            Parameter('ao3', [
                Parameter('channel', 3, [0, 1, 2, 3], 'output channel'),
                Parameter('sample_rate', 1000.0, float,
                          'output sample rate (Hz)'),
                Parameter('min_voltage', -10.0, float,
                          'minimum output voltage (V)'),
                Parameter('max_voltage', 10.0, float,
                          'maximum output voltage (V)')
            ])
        ]),
        Parameter('analog_input', [
            Parameter('ai0', [
                Parameter('channel', 0, range(0, 32), 'input channel'),
                Parameter('sample_rate', 1000.0, float,
                          'input sample rate (Hz)'),
                Parameter('min_voltage', -10.0, float,
                          'minimum input voltage'),
                Parameter('max_voltage', 10.0, float, 'maximum input voltage')
            ]),
            Parameter('ai1', [
                Parameter('channel', 1, range(0, 32), 'input channel'),
                Parameter('sample_rate', 1000.0, float, 'input sample rate'),
                Parameter('min_voltage', -10.0, float,
                          'minimum input voltage'),
                Parameter('max_voltage', 10.0, float, 'maximum input voltage')
            ]),
            Parameter('ai2', [
                Parameter('channel', 2, range(0, 32), 'input channel'),
                Parameter('sample_rate', 1000.0, float, 'input sample rate'),
                Parameter('min_voltage', -10.0, float,
                          'minimum input voltage'),
                Parameter('max_voltage', 10.0, float, 'maximum input voltage')
            ]),
            Parameter('ai3', [
                Parameter('channel', 3, range(0, 32), 'input channel'),
                Parameter('sample_rate', 1000.0, float, 'input sample rate'),
                Parameter('min_voltage', -10.0, float,
                          'minimum input voltage'),
                Parameter('max_voltage', 10.0, float, 'maximum input voltage')
            ]),
            Parameter('ai4', [
                Parameter('channel', 4, range(0, 32), 'input channel'),
                Parameter('sample_rate', 1000.0, float, 'input sample rate'),
                Parameter('min_voltage', -10.0, float,
                          'minimum input voltage'),
                Parameter('max_voltage', 10.0, float,
                          'maximum input voltage (V)')
            ])
        ]),
        Parameter('digital_input', [
            Parameter('ctr0', [
                Parameter('input_channel', 0, range(0, 32),
                          'channel for counter signal input'),
                Parameter('counter_PFI_channel', 8, range(0, 32),
                          'PFI for counter channel input'),
                Parameter('clock_PFI_channel', 13, range(0, 32),
                          'PFI for clock channel output'),
                Parameter('clock_counter_channel', 1, [0, 1],
                          'channel for clock output'),
                Parameter('sample_rate', 1000.0, float,
                          'input sample rate (Hz)')
            ])
        ]),
        Parameter(
            'digital_output',
            [
                Parameter(
                    'do0',
                    [
                        Parameter('channel', 8, range(8, 16), 'channel'),
                        # Parameter('value', False, bool, 'value')
                        Parameter('sample_rate', 1000.0, float,
                                  'output sample rate (Hz)')
                        # Parameter('min_voltage', -10.0, float, 'minimum output voltage (V)'),
                        # Parameter('max_voltage', 10.0, float, 'maximum output voltage (V)')
                    ])
            ])
    ])
Пример #14
0
class EsrRabiDeerXYnSpectrumAdvanced(Script):
    """
    Does an ESR experiment, a Rabi experiment and a DEER experiment on an NV. Can scan over RF power, 
    """

    _DEFAULT_SETTINGS = [
        Parameter('decoupling_seq', [
            Parameter('type', 'XY4', ['spin_echo', 'CPMG', 'XY4', 'XY8'],
                      'type of dynamical decoupling sequences'),
            Parameter('num_of_pulse_blocks', 1, int, 'number of pulse blocks.')
        ]),
        Parameter(
            'DEER_spectrum',
            [
                Parameter('scan_tau', [
                    Parameter('do_scan_tau', True, bool,
                              'check if doing DEER scanning over tau'),
                    Parameter('DEER_freq_sweep', [
                        Parameter('RF_center_freq', 224e6, float,
                                  'RF carrier frequency for dark spin [Hz]'),
                        Parameter(
                            'do_RF_freq_sweep', False, bool,
                            'check if taking a DEER spectrum by varying RF carrier frequency'
                        ),
                        Parameter('RF_freq_sweep_range', 100e6, float,
                                  'RF frequency sweep range [Hz]'),
                        Parameter('RF_freq_sweep_npoints', 11, float,
                                  'RF frequency sweep number of points'),
                    ]),
                    Parameter('DEER_power_sweep', [
                        Parameter('RF_pwr', -7, float,
                                  'RF pulse power for dark spin [dBm]'),
                        Parameter('do_RF_pwr_sweep', False, bool,
                                  'check if sweeping RF power when doing '),
                        Parameter('RF_pwr_sweep_range', 10, float,
                                  'RF power sweep range [dBm]'),
                        Parameter('RF_pwr_sweep_npoints', 11, float,
                                  'RF power sweep number of points'),
                    ])
                ]),
                Parameter('tau_auto_range', [
                    Parameter('min_tau_auto', 500, float,
                              'minimum accepted tau_auto'),
                    Parameter('max_tau_auto', 8000, float,
                              'maximum accepted tau_auto')
                ]),
                Parameter('scan_RF_freq', [
                    Parameter(
                        'do_scan_RF_freq', False, bool,
                        'check if doing DEER scanning over RF frequency'),
                    Parameter(
                        'set_tau', 'manual', ['auto', 'manual'],
                        'find tau automatically from deer_tau experiment or manually type in tau in the deer_freq subscript'
                    )
                ]),
                Parameter('scan_RF_power', [
                    Parameter(
                        'do_scan_RF_power', False, bool,
                        'check if doing DEER scanning over RF frequency'),
                    Parameter(
                        'set_tau', 'manual', ['auto', 'manual'],
                        'find tau automatically from deer_tau experiment or manually type in tau in the deer_pwr subscript'
                    )
                ]),
                Parameter('scan_RF_pi_time', [
                    Parameter(
                        'do_scan_RF_pi_time', False, bool,
                        'check if doing DEER scanning over RF frequency'),
                    Parameter(
                        'set_tau', 'manual', ['auto', 'manual'],
                        'find tau automatically from deer_tau experiment or manually type in tau in the deer_RFpitime subscript'
                    )
                ])

                # Parameter('scan_RF_freq',True, bool,'check if doing DEER scanning over RF frequency'),
                # Parameter('scan_RF_power', False, bool, 'check if doing DEER scanning over RF power'),
                # Parameter('scan_RF_pi_time', False, bool, 'check if doing DEER scanning over RF pi time')
            ])
    ]

    _INSTRUMENTS = {}

    _SCRIPTS = {
        'esr': ESR,
        'rabi': Rabi,
        'deer_tau': DEER_XYn,
        'deer_freq': DEER_XYn_RFfreqsw,
        'deer_pwr': DEER_XYn_RFpwrsw,
        'deer_RFpitime': DEER_XYn_RFpitimesw
    }

    def __init__(self,
                 scripts,
                 name=None,
                 settings=None,
                 log_function=None,
                 timeout=1000000000,
                 data_path=None):

        Script.__init__(self,
                        name,
                        settings=settings,
                        scripts=scripts,
                        log_function=log_function,
                        data_path=data_path)

    def _function(self):

        self.data = {'dummy': 'placeholder'}

        if (self.settings['DEER_spectrum']['scan_RF_power']['do_scan_RF_power']
                and ['DEER_spectrum']['scan_RF_power']['set_tau'] == 'auto'
            ) or (self.settings['DEER_spectrum']['scan_RF_freq']
                  ['do_scan_RF_freq']
                  and ['DEER_spectrum']['scan_RF_freq']['set_tau']
                  == 'auto') or (self.settings['DEER_spectrum']
                                 ['scan_RF_pi_time']['do_scan_RF_pi_time'] and
                                 ['DEER_spectrum'
                                  ]['scan_RF_pi_time']['set_tau'] == 'auto'):
            assert ['DEER_spectrum']['scan_tau'][
                'do_scan_tau'], "run scan_tau to set tau automatically"

        ####### run ESR script
        self.scripts['esr'].run()

        if self.scripts['esr'].data['fit_params'] is not None:
            if len(self.scripts['esr'].data['fit_params']) == 4:
                self.rabi_frequency = self.scripts['esr'].data['fit_params'][2]
            elif len(self.scripts['esr'].data['fit_params']) == 6:
                self.rabi_frequency = self.scripts['esr'].data['fit_params'][4]
            else:
                raise RuntimeError(
                    'Could not get fit parameters from esr script')

            centerfreq = self.scripts['esr'].settings['freq_start']
            freqrange = self.scripts['esr'].settings['freq_stop']
            if self.rabi_frequency < centerfreq - freqrange / 3:
                self.log(
                    'Resonance frequency found ({:0.2e}) was below esr sweep range, aborting rabi attempt'
                    .format(self.rabi_frequency))
            elif self.rabi_frequency > centerfreq + freqrange / 3:
                self.log(
                    'Resonance frequency found ({:0.2e}) was above esr sweep range, aborting rabi attempt'
                    .format(self.rabi_frequency))
            else:
                ####### run Rabi script
                self.log('Starting RABI with frequency {:.4e} Hz'.format(
                    self.rabi_frequency))
                self.scripts['rabi'].settings['mw_pulses'][
                    'mw_frequency'] = float(self.rabi_frequency)
                self.scripts['rabi'].run()

                if self.scripts['rabi'].data[
                        'pi_time'] is not None and self.scripts['rabi'].data[
                            'pi_half_time'] is not None and self.scripts[
                                'rabi'].data['three_pi_half_time'] is not None:
                    #self.scripts['deer'].settings['mw_pulses']['mw_frequency'] = float(self.rabi_frequency)
                    self.pi_time = self.scripts['rabi'].data['pi_time']
                    self.pi_half_time = self.scripts['rabi'].data[
                        'pi_half_time']
                    self.three_pi_half_time = self.scripts['rabi'].data[
                        'three_pi_half_time']

                    if not (self.pi_half_time > 15
                            and self.pi_time > self.pi_half_time
                            and self.three_pi_half_time > self.pi_time):
                        self.log(
                            'Pi/2=({:0.2e}), Pi=({:0.2e}), 3Pi/2=({:0.2e}) do not make sense, aborting DEER for this NV'
                            .format(self.pi_half_time, self.pi_time,
                                    self.three_pi_half_time))
                    else:
                        ####### run DEER script
                        run_deer = 0

                        if self.settings['DEER_spectrum']['scan_tau'][
                                'do_scan_tau']:
                            run_deer = 1
                            self.log(
                                'Starting DEER scanning over tau with Pi/2=({:0.2e}), Pi=({:0.2e}), 3Pi/2=({:0.2e})'
                                .format(self.pi_half_time, self.pi_time,
                                        self.three_pi_half_time))
                            self.scripts['deer_tau'].settings['mw_pulses'][
                                'mw_frequency'] = float(self.rabi_frequency)
                            self.scripts['deer_tau'].settings['mw_pulses'][
                                'pi_half_pulse_time'] = float(
                                    self.pi_half_time)
                            self.scripts['deer_tau'].settings['mw_pulses'][
                                'pi_pulse_time'] = float(self.pi_time)
                            self.scripts['deer_tau'].settings[
                                'RF_pulses']['RF_pi_pulse_time'] = float(
                                    self.pi_time)  # otherwise short pulse
                            self.scripts['deer_tau'].settings['mw_pulses'][
                                '3pi_half_pulse_time'] = float(
                                    self.three_pi_half_time)
                            self.scripts['deer_tau'].settings['decoupling_seq']['type'] = \
                            self.settings['decoupling_seq']['type']
                            self.scripts['deer_tau'].settings['decoupling_seq']['num_of_pulse_blocks'] = \
                            self.settings['decoupling_seq']['num_of_pulse_blocks']

                            # tag before starting deer sweeps:
                            base_tag_deer = self.scripts['deer_tau'].settings[
                                'tag']

                            if self.settings['DEER_spectrum']['scan_tau'][
                                    'DEER_freq_sweep']['do_RF_freq_sweep']:
                                self.do_deer_freq_sweep()
                            elif self.settings['DEER_spectrum']['scan_tau'][
                                    'DEER_power_sweep']['do_RF_pwr_sweep']:
                                self.do_deer_pwr_sweep()
                            else:
                                self.scripts['deer_tau'].run()

                            if self.scripts['deer_tau'].data[
                                    'tau_auto'] is not None:
                                if self.scripts['deer_tau'].data[
                                        'tau_auto'] < self.settings[
                                            'DEER_spectrum']['tau_auto_range'][
                                                'min_tau_auto'] or self.scripts[
                                                    'deer_tau'].data[
                                                        'tau_auto'] > self.settings[
                                                            'DEER_spectrum'][
                                                                'tau_auto_range'][
                                                                    'max_tau_auto']:
                                    self.tau_auto = None
                                    self.log(
                                        'tau_auto is outside acceptable tau_auto_range. use manually set tau instead in subsequent experiments'
                                    )
                                else:
                                    self.tau_auto = self.scripts[
                                        'deer_tau'].data['tau_auto']
                            else:
                                self.tau_auto = None
                                self.log(
                                    'no tau found for good contrast between deer and echo. use manually set tau instead in subsequent experiments'
                                )

                            # return to original tag:
                            self.scripts['deer_tau'].settings[
                                'tag'] = base_tag_deer

                        if self.settings['DEER_spectrum']['scan_RF_power'][
                                'do_scan_RF_power']:
                            run_deer = 1
                            self.log(
                                'Starting DEER scanning over RF power with Pi/2=({:0.2e}), Pi=({:0.2e}), 3Pi/2=({:0.2e})'
                                .format(self.pi_half_time, self.pi_time,
                                        self.three_pi_half_time))
                            self.scripts['deer_pwr'].settings['mw_pulses'][
                                'mw_frequency'] = float(self.rabi_frequency)
                            self.scripts['deer_pwr'].settings['mw_pulses'][
                                'pi_half_pulse_time'] = float(
                                    self.pi_half_time)
                            self.scripts['deer_pwr'].settings['mw_pulses'][
                                'pi_pulse_time'] = float(self.pi_time)
                            self.scripts['deer_pwr'].settings[
                                'RF_pulses']['RF_pi_pulse_time'] = float(
                                    self.pi_time)  # otherwise short pulse
                            self.scripts['deer_pwr'].settings['mw_pulses'][
                                '3pi_half_pulse_time'] = float(
                                    self.three_pi_half_time)

                            self.scripts['deer_pwr'].settings['decoupling_seq']['type'] = \
                                self.settings['decoupling_seq']['type']
                            self.scripts['deer_pwr'].settings['decoupling_seq']['num_of_pulse_blocks'] = \
                                self.settings['decoupling_seq']['num_of_pulse_blocks']

                            if ['DEER_spectrum'
                                ]['scan_RF_power']['set_tau'] == 'auto':
                                if self.tau_auto is not None:
                                    self.scripts['deer_pwr'].settings[
                                        'tau_time'] = float(self.auto)
                                    self.log(
                                        'use tau_auto = ({:0.2e})ns for DEER scan_RF_power'
                                        .format(self.tau_auto))
                                else:
                                    self.log(
                                        'set_tau auto failed, use manually set tau instead'
                                    )

                            self.scripts['deer_pwr'].run()

                        if self.settings['DEER_spectrum']['scan_RF_freq'][
                                'do_scan_RF_freq']:
                            run_deer = 1
                            self.log(
                                'Starting DEER scanning over RF frequency with Pi/2=({:0.2e}), Pi=({:0.2e}), 3Pi/2=({:0.2e})'
                                .format(self.pi_half_time, self.pi_time,
                                        self.three_pi_half_time))
                            self.scripts['deer_freq'].settings['mw_pulses'][
                                'mw_frequency'] = float(self.rabi_frequency)
                            self.scripts['deer_freq'].settings['mw_pulses'][
                                'pi_half_pulse_time'] = float(
                                    self.pi_half_time)
                            self.scripts['deer_freq'].settings['mw_pulses'][
                                'pi_pulse_time'] = float(self.pi_time)
                            self.scripts['deer_freq'].settings['RF_pulses'][
                                'RF_pi_pulse_time'] = float(self.pi_time)
                            print('here we are')  # otherwise short pulse

                            # if self.scripts['deer_freq'].settings['mw_pulses']['pi_pulse_time'] == self.scripts['deer_freq'].settings['RF_pulses']['RF_pi_pulse_time']:
                            #     print ('same pi time')
                            # else:
                            #     print ('different pi time')

                            self.scripts['deer_freq'].settings['mw_pulses'][
                                '3pi_half_pulse_time'] = float(
                                    self.three_pi_half_time)
                            self.scripts['deer_freq'].settings['decoupling_seq']['type'] = \
                                self.settings['decoupling_seq']['type']
                            self.scripts['deer_freq'].settings['decoupling_seq']['num_of_pulse_blocks'] = \
                                self.settings['decoupling_seq']['num_of_pulse_blocks']
                            # print('before running')
                            if ['DEER_spectrum'
                                ]['scan_RF_freq']['set_tau'] == 'auto':
                                if self.tau_auto is not None:
                                    self.scripts['deer_freq'].settings[
                                        'tau_time'] = float(self.auto)
                                    self.log(
                                        'use tau_auto = ({:0.2e})ns for DEER scan_RF_freq'
                                        .format(self.tau_auto))
                                else:
                                    self.log(
                                        'set_tau auto failed, use manually set tau instead'
                                    )

                            self.scripts['deer_freq'].run()

                        if self.settings['DEER_spectrum']['scan_RF_pi_time'][
                                'do_scan_RF_pi_time']:
                            run_deer = 1
                            self.log(
                                'Starting DEER scanning over RF pi time with Pi/2=({:0.2e}), Pi=({:0.2e}), 3Pi/2=({:0.2e})'
                                .format(self.pi_half_time, self.pi_time,
                                        self.three_pi_half_time))
                            self.scripts['deer_RFpitime'].settings[
                                'mw_pulses']['mw_frequency'] = float(
                                    self.rabi_frequency)
                            self.scripts['deer_RFpitime'].settings[
                                'mw_pulses']['pi_half_pulse_time'] = float(
                                    self.pi_half_time)
                            self.scripts['deer_RFpitime'].settings[
                                'mw_pulses']['pi_pulse_time'] = float(
                                    self.pi_time)
                            self.scripts['deer_RFpitime'].settings[
                                'RF_pulses']['RF_pi_pulse_time'] = float(
                                    self.pi_time)  # otherwise short pulse
                            self.scripts['deer_RFpitime'].settings[
                                'mw_pulses']['3pi_half_pulse_time'] = float(
                                    self.three_pi_half_time)
                            self.scripts['deer_RFpitime'].settings['decoupling_seq']['type'] = \
                                self.settings['decoupling_seq']['type']
                            self.scripts['deer_RFpitime'].settings['decoupling_seq']['num_of_pulse_blocks'] = \
                                self.settings['decoupling_seq']['num_of_pulse_blocks']

                            if ['DEER_spectrum'
                                ]['scan_RF_pi_time']['set_tau'] == 'auto':
                                if self.tau_auto is not None:
                                    self.scripts['deer_RFpitime'].settings[
                                        'tau_time'] = float(self.tau_auto)
                                    self.log(
                                        'use tau_auto = ({:0.2e})ns for DEER scan_RF_pi_time'
                                        .format(self.tau_auto))
                                else:
                                    self.log(
                                        'set_tau auto failed, use manually set tau instead'
                                    )

                            self.scripts['deer_RFpitime'].run()

                        if run_deer == 0:
                            self.log('No DEER measurement selected.')
                            ####### run DEER script
                        # self.log('Starting DEER sweeps with Pi/2=({:0.2e}), Pi=({:0.2e}), 3Pi/2=({:0.2e})'.format(self.pi_half_time, self.pi_time, self.three_pi_half_time))
                        # self.scripts['deer'].settings['mw_pulses']['pi_half_pulse_time'] = float(self.pi_half_time)
                        # self.scripts['deer'].settings['mw_pulses']['pi_pulse_time'] = float(self.pi_time)
                        # self.scripts['deer'].settings['mw_pulses']['3pi_half_pulse_time'] = float(self.three_pi_half_time)
                        # self.scripts['deer'].settings['decoupling_seq']['type'] = self.settings['DEER_decoupling_seq']['type']
                        # self.scripts['deer'].settings['decoupling_seq']['num_of_pulse_blocks'] = self.settings['DEER_decoupling_seq']['num_of_pulse_blocks']
                        # self.scripts['deer'].run()
                        # tag before starting deer sweeps:
                        # base_tag_deer = self.scripts['deer'].settings['tag']

                        # if self.settings['DEER_spectrum']['do_RF_freq_sweep']:
                        #     self.do_deer_freq_sweep()
                        # elif self.settings['DEER_power_sweep']['do_RF_pwr_sweep']:
                        #     self.do_deer_pwr_sweep()
                        # else:
                        #     self.scripts['deer'].run()

                        # return to original tag:
                        # self.scripts['deer'].settings['tag'] = base_tag_deer

        else:
            self.log('No resonance frequency found skipping rabi attempt')

    def do_deer_freq_sweep(self):
        deerfldrlblb1 = self.scripts['deer_tau'].settings['tag']
        for freq in np.linspace(
                self.settings['DEER_spectrum']['scan_tau']['do_scan_tau']
            ['DEER_freq_sweep']['RF_center_freq'] -
                self.settings['DEER_spectrum']['scan_tau']['do_scan_tau']
            ['DEER_freq_sweep']['RF_freq_sweep_range'] / 2,
                self.settings['DEER_spectrum']['scan_tau']['do_scan_tau']
            ['DEER_freq_sweep']['RF_center_freq'] +
                self.settings['DEER_spectrum']['scan_tau']['do_scan_tau']
            ['DEER_freq_sweep']['RF_freq_sweep_range'] / 2,
                self.settings['DEER_spectrum']['scan_tau']['do_scan_tau']
            ['DEER_freq_sweep']['RF_freq_sweep_npoints']):
            self.scripts['deer_tau'].settings['RF_pulses'][
                'RF_frequency'] = freq.tolist()
            self.log('RF frequency set to ({:0.2e})MHz'.format(freq / 1e6))
            self.scripts['deer_tau'].settings[
                'tag'] = deerfldrlblb1 + '_freq{:.0f}MHz'.format(freq / 1e6)
            ### inner loop does power sweeps:
            if self.settings['DEER_power_sweep']['do_RF_pwr_sweep']:
                self.do_deer_pwr_sweep()
            else:
                self.scripts['deer_tau'].run()

    def do_deer_pwr_sweep(self):
        deerfldrlblb2 = self.scripts['deer_tau'].settings['tag']
        for pwr in np.linspace(
                self.settings['DEER_spectrum']['scan_tau']['do_scan_tau']
            ['DEER_power_sweep']['RF_pwr'] -
                self.settings['DEER_spectrum']['scan_tau']['do_scan_tau']
            ['DEER_power_sweep']['RF_pwr_sweep_range'] / 2,
                self.settings['DEER_spectrum']['scan_tau']['do_scan_tau']
            ['DEER_power_sweep']['RF_pwr'] +
                self.settings['DEER_spectrum']['scan_tau']['do_scan_tau']
            ['DEER_power_sweep']['RF_pwr_sweep_range'] / 2,
                self.settings['DEER_spectrum']['scan_tau']['do_scan_tau']
            ['DEER_power_sweep']['RF_pwr_sweep_npoints']):
            self.scripts['deer_tau'].settings['RF_pulses'][
                'RF_power'] = pwr.tolist()
            self.log('RF power set to ({:0.2e})'.format(pwr))
            self.scripts['deer_tau'].settings[
                'tag'] = deerfldrlblb2 + '_pwr{:.0f}dBm'.format(pwr)
            self.scripts['deer_tau'].run()

    def _plot(self, axes_list):
        """
        Args:
            axes_list: list of axes objects on which to plot plots the esr on the first axes object
            data: data (dictionary that contains keys image_data, extent, initial_point, maximum_point) if not provided use self.data
        """

        # if self.scripts['esr'].is_running:
        #     self.scripts['esr']._plot([axes_list[1]])
        # elif self.scripts['rabi'].is_running:
        #     self.scripts['rabi']._plot(axes_list)
        # elif self.scripts['deer'].is_running:
        #     self.scripts['deer']._plot(axes_list)

        if self._current_subscript_stage['current_subscript'] is self.scripts[
                'esr'] and self.scripts['esr'].is_running:
            self.scripts['esr']._plot([axes_list[1]])
        elif self._current_subscript_stage['current_subscript'] is self.scripts[
                'rabi'] and self.scripts['rabi'].is_running:
            self.scripts['rabi']._plot(axes_list)
        elif self.scripts['deer_tau'].is_running:
            self.scripts['deer_tau']._plot(axes_list)
        elif self.scripts['deer_freq'].is_running:
            self.scripts['deer_freq']._plot(axes_list)
        elif self.scripts['deer_pwr'].is_running:
            self.scripts['deer_pwr']._plot(axes_list)
        elif self.scripts['deer_RFpitime'].is_running:
            self.scripts['deer_RFpitime']._plot(axes_list)

    def _update_plot(self, axes_list):
        """
        Args:
            axes_list: list of axes objects on which to plot plots the esr on the first axes object
        """

        # if self.scripts['esr'].is_running:
        #     self.scripts['esr']._update_plot([axes_list[1]])
        # elif self.scripts['rabi'].is_running:
        #     self.scripts['rabi']._update_plot(axes_list)
        # elif self.scripts['deer'].is_running:
        #     self.scripts['deer']._update_plot(axes_list)

        if self._current_subscript_stage['current_subscript'] is self.scripts[
                'esr'] and self.scripts['esr'].is_running:
            self.scripts['esr']._update_plot([axes_list[1]])
        elif self._current_subscript_stage['current_subscript'] is self.scripts[
                'rabi'] and self.scripts['rabi'].is_running:
            self.scripts['rabi']._update_plot(axes_list)
        elif self.scripts['deer_tau'].is_running:
            self.scripts['deer_tau']._update_plot(axes_list)
        elif self.scripts['deer_freq'].is_running:
            self.scripts['deer_freq']._update_plot(axes_list)
        elif self.scripts['deer_pwr'].is_running:
            self.scripts['deer_pwr']._update_plot(axes_list)
        elif self.scripts['deer_RFpitime'].is_running:
            self.scripts['deer_RFpitime']._update_plot(axes_list)
Пример #15
0
class TDC001(Instrument):
    '''
    Class to control the thorlabs TDC001 servo. Note that ALL DLL FUNCTIONS TAKING NUMERIC INPUT REQUIRE A SYSTEM.DECIMAL
    VALUE. Check help doc at C:\Program Files\Thorlabs\Kinesis\Thorlabs.MotionControl.DotNet_API for the DLL api.
    The class communicates with the device over USB.
    '''

    _DEFAULT_SETTINGS = Parameter([
        Parameter('serial_number', 83832028, int,
                  'serial number written on device'),
        Parameter('position', 0, float, 'servo position (from 0 to 6 in mm)'),
        Parameter('velocity', 0, float, 'servo maximum velocity in mm/s')
    ])

    def __init__(self, name=None, settings=None):
        super(TDC001, self).__init__(name, settings)
        try:
            DeviceManagerCLI.BuildDeviceList()
            serial_number_list = DeviceManagerCLI.GetDeviceList(
                TCubeDCServo.DevicePrefix)
        except (Exception):
            print("Exception raised by BuildDeviceList")
        if not (str(self.settings['serial_number']) in serial_number_list):
            print(
                str(self.settings['serial_number']) +
                " is not a valid serial number")
            raise

        self.device = TCubeDCServo.CreateTCubeDCServo(
            str(self.settings['serial_number']))
        if (self.device == None):
            print(self.settings['serial_number'] + " is not a TCubeDCServo")
            raise

        try:
            self.device.Connect(str(self.settings['serial_number']))
        except Exception:
            print('Failed to open device ' +
                  str(self.settings['serial_number']))
            raise

        if not self.device.IsSettingsInitialized():
            try:
                self.device.WaitForSettingsInitialized(5000)
            except Exception:
                print("Settings failed to initialize")
                raise

        self.device.StartPolling(250)

        motorSettings = self.device.GetMotorConfiguration(
            str(self.settings['serial_number']))
        currentDeviceSettings = self.device.MotorDeviceSettings

    def update(self, settings):
        '''
        Updates internal settings, as well as the position and velocity set on the physical device
        Args:
            settings: A dictionary in the form of settings as seen in default settings
        '''
        super(TDC001, self).update(settings)
        for key, value in settings.iteritems():
            if key == 'position':
                self._move_servo(value)
            elif key == 'velocity':
                self._set_velocity(value)

    @property
    def _PROBES(self):
        return {
            'position': 'servo position in mm',
            'velocity': 'servo velocity in mm/s'
        }

    def read_probes(self, key):
        assert key in self._PROBES.keys()
        assert isinstance(key, str)

        #query always returns string, need to cast to proper return type
        if key in ['position']:
            return self._get_position()
        elif key in ['velocity']:
            return self._get_velocity()

    @property
    def is_connected(self):
        DeviceManagerCLI.BuildDeviceList()
        return (str(self.settings['serial_number'])
                in DeviceManagerCLI.GetDeviceList(TCubeDCServo.DevicePrefix))

    def __del__(self):
        '''
        Cleans up TDC001 connection
        :PostState: TDC001 is disconnected
        '''
        self.device.StopPolling()
        self.device.Disconnect()

    def goto_home(self):
        '''
        Recenters device at the home position. Raises an exception on failure.
        '''
        try:
            self.device.Home(60000)
        except Exception:
            print("Failed to move to position")
            raise

    def _move_servo(self, position, velocity=0):
        '''
        Move servo to given position with given maximum velocity. Raises an exception on failure.
        :param position: position in mm, ranges from 0-6
        :param velocity: maximum velocity in mm/s, ranges from 0-2.5
        :PostState: servo has moved
        '''
        try:
            if (velocity != 0):
                self._set_velocity(velocity)
            # print("Moving Device to " + str(position))
            self.device.MoveTo(self._Py_Decimal(position), 60000)
        except Exception:
            print("Failed to move to position")
            raise

    def _get_position(self):
        '''
        :return: position of servo
        '''
        return self._Undo_Decimal(self.device.Position)

    def _set_velocity(self, velocity):
        '''
        :param maximum velocity in mm/s, ranges from 0-2.5
        :PostState: velocity changed in hardware
        '''
        if (velocity != 0):
            velPars = self.device.GetVelocityParams()
            velPars.MaxVelocity = self._Py_Decimal(velocity)
            self.device.SetVelocityParams(velPars)

    def _get_velocity(self):
        '''
        :return: maximum velocity setting
        '''
        return self._Undo_Decimal(self.device.GetVelocityParams().MaxVelocity)

    def _Py_Decimal(self, value):
        '''
        Casting a python double to System.Decimal results in the Decimal having only integer values, likely due to an
        improper selection of the overloaded Decimal function. Casting it first to System.Double, which always maintains
        precision, then from Double to Decimal, where the proper overloaded function is clear, bypasses this issue
        :param value: a python double
        :return: the input as a System.Decimal
        '''
        return Decimal(Double(value))

    def _Undo_Decimal(self, value):
        '''
        Casting back from System.Decimal to a python float fails due to overloading issues, but one can successfully
        cast back to a string. Thus, we use a two-part cast to return to python numeric types
        :param value: a System.Decimal
        :return: the input as a python float
        '''
        return float(str(value))
Пример #16
0
class MaestroBeamBlock(Instrument):
    """
    motorized mount with two positions, generally used for blocking ir unblocking beams, but can also be used as a flip mount
    """

    _DEFAULT_SETTINGS = Parameter([
        Parameter('channel', 0, int, 'channel to which motor is connected'),
        Parameter('open', True, bool, 'beam block open or closed'),
        Parameter('settle_time', 0.2, float, 'settling time'),
        Parameter('position_open', 4 * 1900, int,
                  'position corresponding to open'),
        Parameter('position_closed', 4 * 950, int,
                  'position corresponding to closed')
    ])
    _PROBES = {}

    def __init__(self, maestro=None, name=None, settings=None):
        '''
        :param maestro: maestro servo controler to which motor is connected
        :param channel: channel to which motor is connected
        :param position_list: dictonary that contains the target positions, a factor 4 is needed to get the same values as in the maestro control center
        :return:
        '''

        if maestro is None:
            maestro = MaestroController()
        assert isinstance(maestro, MaestroController)
        self.maestro = maestro

        super(MaestroBeamBlock, self).__init__(name, settings)
        self.update(self.settings)

    def update(self, settings):
        """
        Updates the settings in software, and if the 'open' setting is changed then open or closes the beamblock
        Args:
            settings: a dictionary in the standard settings format
        """

        # call the update_parameter_list to update the parameter list
        super(MaestroBeamBlock, self).update(settings)

        # now we actually apply these newsettings to the hardware
        for key, value in settings.iteritems():
            if key == 'open':
                if value is True:
                    self.goto(self.settings['position_open'])
                else:
                    self.goto(self.settings['position_closed'])

    def read_probes(self, key):
        """
        requestes value from the instrument and returns it
        Args:
            key: name of requested value

        Returns: reads values from instrument

        """
        assert key in self._PROBES.keys()

        value = None

        return value

    @property
    def is_connected(self):
        """
        check if instrument is active and connected and return True in that case
        :return: bool
        """
        return self.maestro._is_connected

    def goto(self, position):
        """
        Move to the given position
        Args:
            position: Position to move to, in proprietary units
        """
        self.maestro.set_target(self.settings['channel'], position)
        sleep(self.settings['settle_time'])
        self.maestro.disable(self.settings['channel']
                             )  # diconnect to avoid piezo from going crazy
Пример #17
0
class MaestroFilterWheel(Instrument):
    """
    Motorized filter wheel, which turns to different preset positions. By default has four windows, three of which are
    in use and accessable, but this can be overwritten by changing the defaults.
    """

    _DEFAULT_SETTINGS = Parameter([
        Parameter('channel', 0, int, 'channel to which motor is connected'),
        Parameter('settle_time', 0.8, float, 'settling time'),
        Parameter('position_1', 4 * 750, int,
                  'position corresponding to position 1'),
        Parameter('position_2', 4 * 1700, int,
                  'position corresponding to position 2'),
        Parameter('position_3', 4 * 2700, int,
                  'position corresponding to position 3'),
        Parameter('current_position', 'position_1',
                  ['position_1', 'position_2', 'position_3'],
                  'current position of filter wheel')
    ])

    _PROBES = {}

    def __init__(self, maestro=None, name=None, settings=None):
        '''
        :param maestro: maestro servo controler to which motor is connected
        :param channel: channel to which motor is connected
        :param position_list: dictonary that contains the target positions, a factor 4 is needed to get the same values as in the maestro control center
        :return:
        '''

        if maestro is None:
            maestro = MaestroController()
        assert isinstance(maestro, MaestroController)
        self.maestro = maestro

        super(MaestroFilterWheel, self).__init__(name, settings)
        self.update(self.settings)

    def update(self, settings):
        """
        Updates the settings in software and, if the 'current_position' setting is changed, moves the filter wheel to
        the new current position
        Args:
            settings: a dictionary in the standard settings format
        """
        # call the update_parameter_list to update the parameter list
        super(MaestroFilterWheel, self).update(settings)

        # now we actually apply these newsettings to the hardware
        for key, value in settings.iteritems():
            if key == 'current_position':
                self.goto(self.settings[value])

    def read_probes(self, key):
        '''
        requestes value from the instrument and returns it
        Args:
            key: name of requested value

        Returns: reads values from instrument

        '''
        assert key in self._PROBES.keys()

        value = None

        return value

    @property
    def is_connected(self):
        """
        check if instrument is active and connected and return True in that case
        :return: bool
        """
        return self.maestro._is_connected

    def goto(self, position):
        """
        Move to the given position
        Args:
            position: Position to move to, in proprietary units
        """
        self.maestro.set_target(self.settings['channel'], position)
        sleep(self.settings['settle_time'])
        self.maestro.disable(self.settings['channel']
                             )  # diconnect to avoid piezo from going crazy
Пример #18
0
class MaestroLightControl(MaestroController):
    """
    MaestroController for use in B26. Includes beamblocks and a filterwheel. To use your own devices, overwrite
    these values with the correct position specifications.
    """
    _DEFAULT_SETTINGS = Parameter([
        Parameter('port', 'COM5', ['COM1', 'COM3', 'COM5', 'COM7'],
                  'com port to which maestro controler is connected'),
        Parameter('block_green', [
            Parameter('channel', 5, int,
                      'channel to which motor is connected'),
            Parameter('open', True, bool,
                      'True: green laser on, False: green laser off'),
            Parameter('settle_time', 0.2, float, 'settling time'),
            Parameter('position_open', 4 * 1900, int,
                      'position corresponding to open'),
            Parameter('position_closed', 4 * 950, int,
                      'position corresponding to closed')
        ]),
        Parameter('block_IR', [
            Parameter('channel', 4, int,
                      'channel to which motor is connected'),
            Parameter('open', False, bool,
                      'True: IR laser on, False: IR laser off'),
            Parameter('settle_time', 0.2, float, 'settling time'),
            Parameter('position_open', 4 * 1900, int,
                      'position corresponding to open'),
            Parameter('position_closed', 4 * 950, int,
                      'position corresponding to closed')
        ]),
        Parameter('white_light', [
            Parameter('channel', 0, int,
                      'channel to which motor is connected'),
            Parameter('open', False, bool,
                      'True: white light on, False: white light off'),
            Parameter('settle_time', 0.2, float, 'settling time'),
            Parameter('position_open', 4 * 1000, int,
                      'position corresponding to open'),
            Parameter('position_closed', 4 * 1800, int,
                      'position corresponding to closed')
        ]),
        Parameter('filter_wheel', [
            Parameter('channel', 1, int,
                      'channel to which motor is connected'),
            Parameter('settle_time', 0.8, float, 'settling time'),
            Parameter('ND2.0_position', 4 * 2700, int,
                      'position corresponding to position 1'),
            Parameter('ND1.0_position', 4 * 1700, int,
                      'position corresponding to position 2'),
            Parameter('red_filter_position', 4 * 750, int,
                      'position corresponding to position 3'),
            Parameter('current_position', 'red_filter',
                      ['ND1.0', 'ND2.0', 'red_filter'],
                      'current position of filter wheel')
        ])
    ])

    _PROBES = {}

    def __init__(self, name=None, settings=None):

        self.usb = None

        super(MaestroLightControl, self).__init__(name, settings=settings)

    def update(self, settings):
        """
        Updates the settings in software and, if applicable, takes an action to modify the hardware, such as opening
        a beamblock or spinning a filterwheel
        Args:
            settings: a dictionary in the standard settings format
        """
        # call the update_parameter_list to update the parameter list
        super(MaestroLightControl, self).update(settings)
        # now we actually apply these newsettings to the hardware
        for key, value in settings.iteritems():
            if key in ['block_green', 'block_IR', 'white_light']:
                channel = self.settings[key]['channel']
                position = self.settings[key]['position_open'] if value[
                    'open'] else self.settings[key]['position_closed']
                settle_time = self.settings[key]['settle_time']
                self.goto(channel, position, settle_time)
            elif key in ['filter_wheel']:
                channel = self.settings[key]['channel']
                position = self.settings[key][
                    self.settings[key]['current_position'] + '_position']
                settle_time = self.settings[key]['settle_time']
                self.goto(channel, position, settle_time)

    def goto(self, channel, position, settle_time):
        self.set_target(channel, position)
        sleep(settle_time)
        self.disable(channel)  # diconnect to avoid piezo from going crazy
Пример #19
0
class MaestroController(Instrument):
    # When connected via USB, the Maestro creates two virtual serial ports
    # /dev/ttyACM0 for commands and /dev/ttyACM1 for communications.
    # Be sure the Maestro is configured for "USB Dual Port" serial mode.
    # "USB Chained Mode" may work as well, but hasn't been tested.
    #
    # Pololu protocol allows for multiple Maestros to be connected to a single
    # communication channel. Each connected device is then indexed by number.
    # This device number defaults to 0x0C (or 12 in decimal), which this module
    # assumes.  If two or more controllers are connected to different serial
    # ports, then you can specify the port number when intiating a controller
    # object. Ports will typically start at 0 and count by twos.  So with two
    # controllers ports 0 and 2 would be used.

    import serial

    _DEFAULT_SETTINGS = Parameter([
        Parameter('port', 'COM5', ['COM5', 'COM3'],
                  'com port to which maestro controler is connected')
    ])

    _PROBES = {}

    def __init__(self, name=None, settings=None):

        self.usb = None
        # Open the command port
        # self.usb = self.serial.Serial(port)
        # Command lead-in and device 12 are sent for each Pololu serial commands.
        self.PololuCmd = chr(0xaa) + chr(0xc)
        # Track target position for each servo. The function isMoving() will
        # use the Target vs Current servo position to determine if movement is
        # occuring.  Upto 24 servos on a Maestro, (0-23). Targets start at 0.
        self.Targets = [0] * 24
        # Servo minimum and maximum targets can be restricted to protect components.
        self.Mins = [0] * 24
        self.Maxs = [0] * 24
        super(MaestroController, self).__init__(name, settings=settings)

        self.update(self.settings)

    def update(self, settings):
        # call the update_parameter_list to update the parameter list
        super(MaestroController, self).update(settings)

        # now we actually apply these newsettings to the hardware
        for key, value in settings.iteritems():
            if key == 'port':
                try:
                    if self.usb is None or value != self.usb.port:
                        self.usb = self.serial.Serial(value)
                except OSError:
                    print('Couln\'t connect to maestro controler at port {:s}'.
                          format(value))

    def read_probes(self, key):
        '''
        requestes value from the instrument and returns it
        Args:
            key: name of requested value

        Returns: reads values from instrument

        '''
        # todo: replace getter old_functions with this function
        assert key in self._PROBES.keys()

        value = None

        return value

    @property
    def is_connected(self):
        '''
        check if instrument is active and connected and return True in that case
        :return: bool
        '''
        if self.usb is None:
            self._is_connected = False
        else:
            self._is_connected = True

        #todo: implement check

        return self._is_connected

    # Cleanup by closing USB serial port
    def __del__(self):
        if not self.usb == None:
            self.usb.close()

    # Set channels min and max value range.  Use this as a safety to protect
    # from accidentally moving outside known safe parameters. A setting of 0
    # allows unrestricted movement.
    #
    # ***Note that the Maestro itself is configured to limit the range of servo travel
    # which has precedence over these values.  Use the Maestro Control Center to configure
    # ranges that are saved to the controller.  Use setRange for software controllable ranges.
    def set_range(self, chan, min, max):
        self.Mins[chan] = min
        self.Maxs[chan] = max

    # Return Minimum channel range value
    def get_min(self, chan):
        return self.Mins[chan]

    # Return Minimum channel range value
    def get_max(self, chan):
        return self.Maxs[chan]

    # Set channel to a specified target value.  Servo will begin moving based
    # on Speed and Acceleration parameters previously set.
    # Target values will be constrained within Min and Max range, if set.
    # For servos, target represents the pulse width in of quarter-microseconds
    # Servo center is at 1500 microseconds, or 6000 quarter-microseconds
    # Typcially valid servo range is 3000 to 9000 quarter-microseconds
    # If channel is configured for digital output, values < 6000 = Low ouput
    def set_target(self, chan, target):
        # if Min is defined and Target is below, force to Min
        if self.Mins[chan] > 0 and target < self.Mins[chan]:
            target = self.Mins[chan]
        # if Max is defined and Target is above, force to Max
        if self.Maxs[chan] > 0 and target > self.Maxs[chan]:
            target = self.Maxs[chan]
        #
        lsb = target & 0x7f  #7 bits for least significant byte
        msb = (target >> 7) & 0x7f  #shift 7 and take next 7 bits for msb
        # Send Pololu intro, device number, command, channel, and target lsb/msb
        cmd = self.PololuCmd + chr(0x04) + chr(chan) + chr(lsb) + chr(msb)
        self.usb.write(cmd)
        # Record Target value
        self.Targets[chan] = target

    def disable(self, chan):
        """
        Disables the given output channel
        Args:
            chan: the channel to disable
        """
        target = 0
        #
        lsb = target & 0x7f  #7 bits for least significant byte
        msb = (target >> 7) & 0x7f  #shift 7 and take next 7 bits for msb
        # Send Pololu intro, device number, command, channel, and target lsb/msb
        cmd = self.PololuCmd + chr(0x04) + chr(chan) + chr(lsb) + chr(msb)
        self.usb.write(cmd)
        # Record Target value
        self.Targets[chan] = target

    def set_speed(self, chan, speed):
        """
        Set speed of channel
        Speed is measured as 0.25microseconds/10milliseconds
        For the standard 1ms pulse width change to move a servo between extremes, a speed
        of 1 will take 1 minute, and a speed of 60 would take 1 second.
        Speed of 0 is unrestricted.
        Args:
            chan: channel on which to set speed
            speed: movement speed to set in units of 0.25microseconds/10milliseconds
        """
        lsb = speed & 0x7f  #7 bits for least significant byte
        msb = (speed >> 7) & 0x7f  #shift 7 and take next 7 bits for msb
        # Send Pololu intro, device number, command, channel, speed lsb, speed msb
        cmd = self.PololuCmd + chr(0x07) + chr(chan) + chr(lsb) + chr(msb)
        self.usb.write(cmd)

    def set_accel(self, chan, accel):
        """
        Set acceleration of channel
        This provide soft starts and finishes when servo moves to target position.
        Args:
            chan: channel on which to set acceleration
            accel: Valid values are from 0 to 255. 0=unrestricted, 1 is slowest start. A value of 1 will take
                   the servo about 3s to move between 1ms to 2ms range.

        """
        lsb = accel & 0x7f  #7 bits for least significant byte
        msb = (accel >> 7) & 0x7f  #shift 7 and take next 7 bits for msb
        # Send Pololu intro, device number, command, channel, accel lsb, accel msb
        cmd = self.PololuCmd + chr(0x09) + chr(chan) + chr(lsb) + chr(msb)
        self.usb.write(cmd)

    def get_position(self, chan):
        """
        Get the current position of the device on the specified channel
        The result is returned in a measure of quarter-microseconds, which mirrors
        the Target parameter of setTarget.
        This is not reading the true servo position, but the last target position sent
        to the servo. If the Speed is set to below the top speed of the servo, then
        the position result will align well with the acutal servo position, assuming
        it is not stalled or slowed.
        Args:
            chan: channel on which to get position

        """
        cmd = self.PololuCmd + chr(0x10) + chr(chan)
        self.usb.write(cmd)
        lsb = ord(self.usb.read())
        msb = ord(self.usb.read())
        return (msb << 8) + lsb

    # # Test to see if a servo has reached its target position.  This only provides
    # # useful results if the Speed parameter is set slower than the maximum speed of
    # # the servo.
    # # ***Note if target position goes outside of Maestro's allowable range for the
    # # channel, then the target can never be reached, so it will appear to allows be
    # # moving to the target.  See setRange comment.
    # def isMoving(self, chan):
    #     if self.Targets[chan] > 0:
    #         if self.getPosition(chan) <> self.Targets[chan]:
    #             return True
    #     return False

    # # Have all servo outputs reached their targets? This is useful only if Speed and/or
    # # Acceleration have been set on one or more of the channels. Returns True or False.
    # def getMovingState(self):
    #     cmd = self.PololuCmd + chr(0x13)
    #     self.usb.write(cmd)
    #     if self.usb.read() == chr(0):
    #         return False
    #     else:
    #         return True

    # # Run a Maestro Script subroutine in the currently active script. Scripts can
    # # have multiple subroutines, which get numbered sequentially from 0 on up. Code your
    # # Maestro subroutine to either infinitely loop, or just end (return is not valid).
    # def runScriptSub(self, subNumber):
    #     cmd = self.PololuCmd + chr(0x27) + chr(subNumber)
    #     # can pass a param with comman 0x28
    #     # cmd = self.PololuCmd + chr(0x28) + chr(subNumber) + chr(lsb) + chr(msb)
    #     self.usb.write(cmd)
    #
    # # Stop the current Maestro Script
    # def stopScript(self):
    #     cmd = self.PololuCmd + chr(0x24)
    #     self.usb.write(cmd)

    def go_home(self):
        """
        Stop the current Maestro Script
        """
        cmd = self.PololuCmd + chr(0x22)
        self.usb.write(cmd)
Пример #20
0
class GalvoScan(Script):

    _DEFAULT_SETTINGS = [
        Parameter('point_a', [
            Parameter('x', 0, float, 'x-coordinate'),
            Parameter('y', 0, float, 'y-coordinate')
        ]),
        Parameter('point_b', [
            Parameter('x', 1.0, float, 'x-coordinate'),
            Parameter('y', 1.0, float, 'y-coordinate')
        ]),
        Parameter(
            'RoI_mode', 'center', ['corner', 'center'],
            'mode to calculate region of interest.\n \
                                                           corner: pta and ptb are diagonal corners of rectangle.\n \
                                                           center: pta is center and pta is extend or rectangle'
        ),
        Parameter('num_points', [
            Parameter('x', 126, int, 'number of x points to scan'),
            Parameter('y', 126, int, 'number of y points to scan')
        ]),
        Parameter('time_per_pt', .002,
                  [.0005, .001, .002, .005, .01, .015, .02],
                  'time in s to measure at each point'),
        Parameter('settle_time', .0002, [.0002],
                  'wait time between points to allow galvo to settle'),
        Parameter(
            'max_counts_plot', -1, int,
            'Rescales colorbar with this as the maximum counts on replotting'),
        Parameter('DAQ_channels', [
            Parameter('x_ao_channel', 'ao0', ['ao0', 'ao1', 'ao2', 'ao3'],
                      'Daq channel used for x voltage analog output'),
            Parameter('y_ao_channel', 'ao1', ['ao0', 'ao1', 'ao2', 'ao3'],
                      'Daq channel used for y voltage analog output'),
            Parameter('z_ao_channel', 'ao2', ['ao0', 'ao1', 'ao2', 'ao3'],
                      'Daq channel used for z voltage analog output'),
            Parameter('counter_channel', 'ctr0',
                      ['ctr0', 'ctr1', 'ctr2', 'ctr3'],
                      'Daq channel used for counter')
        ]),
        Parameter('ending_behavior', 'return_to_start',
                  ['return_to_start', 'return_to_origin', 'leave_at_corner'],
                  'return to the corn'),
        Parameter('daq_type', 'PCI', ['PCI', 'cDAQ'],
                  'Type of daq to use for scan')
    ]

    _INSTRUMENTS = {'NI6259': NI6259, 'NI9263': NI9263, 'NI9402': NI9402}

    _SCRIPTS = {}

    def __init__(self,
                 instruments,
                 name=None,
                 settings=None,
                 log_function=None,
                 data_path=None):
        '''
        Initializes GalvoScan script for use in gui

        Args:
            instruments: list of instrument objects
            name: name to give to instantiated script object
            settings: dictionary of new settings to pass in to override defaults
            log_function: log function passed from the gui to direct log calls to the gui log
            data_path: path to save data

        '''
        Script.__init__(self,
                        name,
                        settings=settings,
                        instruments=instruments,
                        log_function=log_function,
                        data_path=data_path)
        # defines which daqs contain the input and output based on user selection of daq interface
        if self.settings['daq_type'] == 'PCI':
            self.daq_in = self.instruments['NI6259']['instance']
            self.daq_out = self.instruments['NI6259']['instance']
        elif self.settings['daq_type'] == 'cDAQ':
            self.daq_in = self.instruments['NI9402']['instance']
            self.daq_out = self.instruments['NI9263']['instance']

    def _function(self):
        """
        Executes threaded galvo scan
        """

        # update_time = datetime.datetime.now()

        # self._plot_refresh = True

        # self._plotting = True

        def init_scan():
            self._recording = False

            self.clockAdjust = int(
                (self.settings['time_per_pt'] + self.settings['settle_time']) /
                self.settings['settle_time'])

            [self.xVmin, self.xVmax, self.yVmax,
             self.yVmin] = self.pts_to_extent(self.settings['point_a'],
                                              self.settings['point_b'],
                                              self.settings['RoI_mode'])

            self.x_array = np.repeat(
                np.linspace(self.xVmin,
                            self.xVmax,
                            self.settings['num_points']['x'],
                            endpoint=True), self.clockAdjust)
            self.y_array = np.linspace(self.yVmin,
                                       self.yVmax,
                                       self.settings['num_points']['y'],
                                       endpoint=True)
            sample_rate = float(1) / self.settings['settle_time']
            self.daq_out.settings['analog_output'][self.settings[
                'DAQ_channels']['x_ao_channel']]['sample_rate'] = sample_rate
            self.daq_out.settings['analog_output'][self.settings[
                'DAQ_channels']['y_ao_channel']]['sample_rate'] = sample_rate
            self.daq_out.settings['analog_output'][self.settings[
                'DAQ_channels']['z_ao_channel']]['sample_rate'] = sample_rate
            self.daq_in.settings['digital_input'][
                self.settings['DAQ_channels']
                ['counter_channel']]['sample_rate'] = sample_rate
            self.data = {
                'image_data':
                np.zeros((self.settings['num_points']['y'],
                          self.settings['num_points']['x'])),
                'bounds': [self.xVmin, self.xVmax, self.yVmin, self.yVmax]
            }

        if self.settings['daq_type'] == 'PCI':
            initial_position = self.daq_out.get_analog_voltages([
                self.settings['DAQ_channels']['x_ao_channel'],
                self.settings['DAQ_channels']['y_ao_channel']
            ])

        init_scan()
        self.data['extent'] = [self.xVmin, self.xVmax, self.yVmax, self.yVmin]

        for yNum in xrange(0, len(self.y_array)):

            if self._abort:
                break
            # set galvo to initial point of next line
            self.initPt = [self.x_array[0], self.y_array[yNum]]
            self.daq_out.set_analog_voltages({
                self.settings['DAQ_channels']['x_ao_channel']:
                self.initPt[0],
                self.settings['DAQ_channels']['y_ao_channel']:
                self.initPt[1]
            })

            # initialize APD thread
            ctrtask = self.daq_in.setup_counter(
                self.settings['DAQ_channels']['counter_channel'],
                len(self.x_array) + 1)
            aotask = self.daq_out.setup_AO(
                [self.settings['DAQ_channels']['x_ao_channel']], self.x_array,
                ctrtask)

            # start counter and scanning sequence
            self.daq_out.run(aotask)
            self.daq_in.run(ctrtask)
            self.daq_out.waitToFinish(aotask)
            self.daq_out.stop(aotask)
            xLineData, _ = self.daq_in.read(ctrtask)
            self.daq_in.stop(ctrtask)
            diffData = np.diff(xLineData)

            summedData = np.zeros(len(self.x_array) / self.clockAdjust)
            for i in range(0, int((len(self.x_array) / self.clockAdjust))):
                summedData[i] = np.sum(diffData[(i * self.clockAdjust +
                                                 1):(i * self.clockAdjust +
                                                     self.clockAdjust - 1)])
            # also normalizing to kcounts/sec
            self.data['image_data'][yNum] = summedData * (
                .001 / self.settings['time_per_pt'])

            self.progress = float(yNum + 1) / len(self.y_array) * 100
            self.updateProgress.emit(int(self.progress))

        # set point after scan based on ending_behavior setting
        if self.settings['ending_behavior'] == 'leave_at_corner':
            return

        elif self.settings['ending_behavior'] == 'return_to_start':
            if self.settings['daq_type'] == 'PCI':
                self.set_galvo_location(initial_position)
            else:
                self.log(
                    'Could not determine initial position with this daq. Instead using leave_at_corner behavior'
                )
                return

        elif self.settings['ending_behavior'] == 'return_to_origin':
            self.set_galvo_location([0, 0])

    def get_galvo_location(self):
        """
        Returns the current position of the galvo. Requires a daq with analog inputs internally routed to the analog
        outputs (ex. NI6259. Note that the cDAQ does not have this capability).
        Returns: list with two floats, which give the x and y position of the galvo mirror
        """
        galvo_position = self.daq_out.get_analog_voltages([
            self.settings['DAQ_channels']['x_ao_channel'],
            self.settings['DAQ_channels']['y_ao_channel']
        ])
        return galvo_position

    def set_galvo_location(self, galvo_position):
        """
        sets the current position of the galvo
        galvo_position: list with two floats, which give the x and y position of the galvo mirror
        """
        if galvo_position[0] > 10 or galvo_position[0] < -10 or galvo_position[
                1] > 10 or galvo_position[1] < -10:
            raise ValueError(
                'The script attempted to set the galvo position to an illegal position outside of +- 10 V'
            )

        pt = galvo_position
        # daq API only accepts either one point and one channel or multiple points and multiple channels
        pt = np.transpose(np.column_stack((pt[0], pt[1])))
        pt = (np.repeat(pt, 2, axis=1))

        task = self.daq_out.setup_AO([
            self.settings['DAQ_channels']['x_ao_channel'],
            self.settings['DAQ_channels']['y_ao_channel']
        ], pt)
        self.daq_out.run(task)
        self.daq_out.waitToFinish(task)
        self.daq_out.stop(task)

    @staticmethod
    def pts_to_extent(pta, ptb, roi_mode):
        """

        Args:
            pta: point a
            ptb: point b
            roi_mode:   mode how to calculate region of interest
                        corner: pta and ptb are diagonal corners of rectangle.
                        center: pta is center and ptb is extend or rectangle

        Returns: extend of region of interest [xVmin, xVmax, yVmax, yVmin]

        """
        if roi_mode == 'corner':
            xVmin = min(pta['x'], ptb['x'])
            xVmax = max(pta['x'], ptb['x'])
            yVmin = min(pta['y'], ptb['y'])
            yVmax = max(pta['y'], ptb['y'])
        elif roi_mode == 'center':
            xVmin = pta['x'] - float(ptb['x']) / 2.
            xVmax = pta['x'] + float(ptb['x']) / 2.
            yVmin = pta['y'] - float(ptb['y']) / 2.
            yVmax = pta['y'] + float(ptb['y']) / 2.
        return [xVmin, xVmax, yVmax, yVmin]

    def _plot(self, axes_list, data=None):
        """
        Plots the galvo scan image
        Args:
            axes_list: list of axes objects on which to plot the galvo scan on the first axes object
            data: data (dictionary that contains keys image_data, extent) if not provided use self.data
        """
        if data is None:
            data = self.data
        plot_fluorescence_new(data['image_data'],
                              data['extent'],
                              axes_list[0],
                              max_counts=self.settings['max_counts_plot'])

    def _update_plot(self, axes_list):
        """
        updates the galvo scan image
        Args:
            axes_list: list of axes objects on which to plot plots the esr on the first axes object
        """
        update_fluorescence(self.data['image_data'], axes_list[0],
                            self.settings['max_counts_plot'])

    def get_axes_layout(self, figure_list):
        """
        returns the axes objects the script needs to plot its data
        the default creates a single axes object on each figure
        This can/should be overwritten in a child script if more axes objects are needed
        Args:
            figure_list: a list of figure objects
        Returns:
            axes_list: a list of axes objects

        """

        # only pick the first figure from the figure list, this avoids that get_axes_layout clears all the figures
        return super(GalvoScan, self).get_axes_layout([figure_list[0]])
class R8SMicrowaveGenerator(Instrument):
    """
    This class implements the ROHDE & SCHWARZ microwave generator. The class commuicates with the
    device over USB using pyvisa.
    """

    _DEFAULT_SETTINGS = Parameter([
        Parameter('connection_type', 'USB', ['USB'],
                  'type of connection to open to controller'),
        Parameter('enable_output', False, bool, 'Type-N output enabled'),
        Parameter('freq_mode', 'CW', ['CW', 'Sweep'],
                  'select the frequency mode'),
        Parameter('power_mode', 'CW', ['CW', 'Sweep'],
                  'select the power mode'),
        Parameter('edge', 'Positive', ['Positive', 'Negative'],
                  'select the triggerring edge'),
        Parameter('frequency', 252e6, float,
                  'frequency in Hz, or with label in other units ex 300 MHz'),
        Parameter('freq_start', 100e6, float,
                  'start frequency in Hz in sweep mode'),
        Parameter('freq_stop', 400e6, float,
                  'stop frequency in Hz in sweep mode'),
        Parameter('freq_pts', 100, float,
                  'number of sweep steps in freq sweep mode'),
        Parameter('power', -45, float, 'Type-N amplitude in dBm'),
        Parameter('pwr_start', -20, float, 'start power in dBm in sweep mode'),
        Parameter('pwr_stop', 0, float, 'stop power in dBm in sweep mode'),
        Parameter('pwr_pts', 20, float,
                  'number of sweep steps in power sweep mode')
    ])

    def __init__(self, name=None, settings=None):

        super(R8SMicrowaveGenerator, self).__init__(name, settings)

        # XXXXX MW ISSUE = START
        #===========================================
        # Issue where visa.ResourceManager() takes 4 minutes no longer happens after using pdb to debug (??? not sure why???)
        try:
            self._connect()
        except pyvisa.errors.VisaIOError:
            print(
                'No Microwave Controller Detected!. Check that you are using the correct communication type'
            )
            raise
        except Exception as e:
            raise (e)
        #XXXXX MW ISSUE = END
        #===========================================

    def _connect(self):
        rm = visa.ResourceManager()
        self.srs = rm.open_resource('USB0::0x0AAD::0x006E::102953::INSTR')
        self.srs.query('*IDN?')
        print('instrument connected: ' + self.srs.query('*IDN?'))

    #Doesn't appear to be necessary, can't manually make two sessions conflict, rms may share well
    # def __del__(self):
    #     self.srs.close()

    def update(self, settings):
        """
        Updates the internal settings of the MicrowaveGenerator, and then also updates physical parameters such as
        frequency, amplitude, modulation type, etc in the hardware
        Args:
            settings: a dictionary in the standard settings format
        """
        super(R8SMicrowaveGenerator, self).update(settings)
        # XXXXX MW ISSUE = START
        # ===========================================
        for key, value in settings.iteritems():
            # print (key + ' is updated')
            # print (settings)
            # print(' ')
            if key == 'connection_type':
                self._connect()
            else:
                if key == 'enable_output':
                    value = self._output_to_internal(value)
                elif key == 'frequency':
                    if value > RANGE_MAX or value < RANGE_MIN:
                        raise ValueError(
                            "Invalid frequency. All frequencies must be between 9kHz and 3.2 GHz."
                        )
                elif key == 'freq_mode':
                    self.srs.write('SOUR:SWE:FREQ:MODE STEP')
                    self.srs.write('SOUR:SWE:FREQ:SPAC LIN')
                    self.srs.write('TRIG:FSW:SOUR EXT')
                elif key == 'power_mode':
                    self.srs.write('SOUR:SWE:POW:MODE STEP')
                    self.srs.write('TRIG:PSW:SOUR EXT')
                key = self._param_to_internal(key)
                if self._settings_initialized:
                    self.srs.write(key + ' ' + str(value))

                # only send update to instrument if connection to instrument has been established
                # frequency change operation timed using timeit.timeit and
                # completion confirmed by query('*OPC?'), found delay of <10ms
                # print(self.srs.query('*OPC?'))

        # XXXXX MW ISSUE = END
        # ===========================================

    @property
    def _PROBES(self):
        # return{
        #     'enable_output': 'if type-N output is enabled',
        #     'frequency': 'frequency of output in Hz',
        #     'amplitude': 'type-N amplitude in dBm',
        #     'phase': 'phase',
        #     'enable_modulation': 'is modulation enabled',
        #     'modulation_type': 'Modulation Type: 0= AM, 1=FM, 2= PhaseM, 3= Freq sweep, 4= Pulse, 5 = Blank, 6=IQ',
        #     'modulation_function': 'Modulation Function: 0=Sine, 1=Ramp, 2=Triangle, 3=Square, 4=Noise, 5=External',
        #     'pulse_modulation_function': 'Pulse Modulation Function: 3=Square, 4=Noise(PRBS), 5=External',
        #     'dev_width': 'Width of deviation from center frequency in FM'
        # }

        return {
            'enable_output': 'if type-N output is enabled',
            'frequency': 'frequency of output in Hz',
            'amplitude': 'type-N amplitude in dBm'
        }

    def read_probes(self, key):
        # assert hasattr(self, 'srs') #will cause read_probes to fail if connection not yet established, such as when called in init
        assert (
            self._settings_initialized
        )  #will cause read_probes to fail if settings (and thus also connection) not yet initialized
        assert key in self._PROBES.keys()

        #query always returns string, need to cast to proper return type
        # if key in ['enable_output', 'enable_modulation']:
        if key == 'enable_output':
            key_internal = self._param_to_internal(key)
            value = int(self.srs.query(key_internal + '?'))
            if value == 1:
                value = True
            elif value == 0:
                value = False
        # elif key in ['modulation_type','modulation_function','pulse_modulation_function']:
        #     key_internal = self._param_to_internal(key)
        #     value = int(self.srs.query(key_internal + '?'))
        #     if key == 'modulation_type':
        #         value = self._internal_to_mod_type(value)
        #     elif key == 'modulation_function':
        #         value = self._internal_to_mod_func(value)
        #     elif key == 'pulse_modulation_function':
        #         value = self._internal_to_pulse_mod_func(value)
        else:
            key_internal = self._param_to_internal(key)
            value = float(self.srs.query(key_internal + '?'))

        return value

    @property
    def is_connected(self):
        try:
            self.srs.query(
                '*IDN?'
            )  # arbitrary call to check connection, throws exception on failure to get response
            return True
        except pyvisa.errors.VisaIOError:
            return False

    def _param_to_internal(self, param):
        """
        Converts settings parameters to the corresponding key used for GPIB commands in the SRS.
        Args:
            param: settings parameter, ex. enable_output

        Returns: GPIB command, ex. ENBR

        """
        if param == 'enable_output':
            return 'OUTP'
        elif param == 'freq_mode':
            return 'SOUR:FREQ:MODE'
        elif param == 'power_mode':
            return 'SOUR:POW:MODE'
        elif param == 'edge':
            return 'INP:TRIG:SLOP'
        elif param == 'frequency':
            return 'FREQ'
        elif param == 'freq_start':
            return 'FREQ:STAR'
        elif param == 'freq_stop':
            return 'FREQ:STOP'
        elif param == 'freq_pts':
            return 'SWE:POIN'
        elif param == 'power':
            return 'SOUR:POW:POW'
        elif param == 'pwr_start':
            return 'POW:STAR'
        elif param == 'pwr_stop':
            return 'POW:STOP'
        elif param == 'pwr_pts':
            return 'SWE:POW:POIN'
        # elif param == 'phase':
        #     return 'PHAS'
        # elif param == 'enable_modulation':
        #     return 'MODL'
        # elif param == 'modulation_type':
        #     return 'TYPE'
        # elif param == 'modulation_function':
        #     return 'MFNC'
        # elif param == 'pulse_modulation_function':
        #     return 'PFNC'
        # elif param == 'dev_width':
        #     return 'FDEV'
        else:

            raise KeyError

    def _output_to_internal(self, value):
        if value == True:
            return 'ON'
        elif value == False:
            return 'OFF'
        else:

            raise KeyError
Пример #22
0
class NI9263old(DAQold):
    """
    This class implements the NI9263 DAQ, which includes 4 AO channels. It inherits output functionality from the DAQ
    class.
    """
    _DEFAULT_SETTINGS = Parameter([
        Parameter('device', 'cDAQ9184-1BA7633Mod4',
                  ['cDAQ9184-1BA7633Mod3', 'cDAQ9184-1BA7633Mod4'],
                  'Name of DAQ device - check in NiMax'),
        Parameter('override_buffer_size', -1, int,
                  'Buffer size for manual override (unused if -1)'),
        Parameter(
            'ao_read_offset', .005, float,
            'Empirically determined offset for reading ao voltages internally'
        ),
        Parameter('analog_output', [
            Parameter('ao0', [
                Parameter('channel', 0, [0, 1, 2, 3], 'output channel'),
                Parameter('sample_rate', 1000.0, float,
                          'output sample rate (Hz)'),
                Parameter('min_voltage', -10.0, float,
                          'minimum output voltage (V)'),
                Parameter('max_voltage', 10.0, float,
                          'maximum output voltage (V)')
            ]),
            Parameter('ao1', [
                Parameter('channel', 1, [0, 1, 2, 3], 'output channel'),
                Parameter('sample_rate', 1000.0, float,
                          'output sample rate (Hz)'),
                Parameter('min_voltage', -10.0, float,
                          'minimum output voltage (V)'),
                Parameter('max_voltage', 10.0, float,
                          'maximum output voltage (V)')
            ]),
            Parameter('ao2', [
                Parameter('channel', 2, [0, 1, 2, 3], 'output channel'),
                Parameter('sample_rate', 1000.0, float,
                          'output sample rate (Hz)'),
                Parameter('min_voltage', -10.0, float,
                          'minimum output voltage (V)'),
                Parameter('max_voltage', 10.0, float,
                          'maximum output voltage (V)')
            ]),
            Parameter('ao3', [
                Parameter('channel', 3, [0, 1, 2, 3], 'output channel'),
                Parameter('sample_rate', 1000.0, float,
                          'output sample rate (Hz)'),
                Parameter('min_voltage', -10.0, float,
                          'minimum output voltage (V)'),
                Parameter('max_voltage', 10.0, float,
                          'maximum output voltage (V)')
            ])
        ])
    ])
Пример #23
0
class AutoFocusGeneric(Script):
    """
Autofocus: Takes images at different piezo voltages and uses a heuristic to figure out the point at which the objective
            is focused.
    """

    _DEFAULT_SETTINGS = [
        Parameter('save_images', False, bool, 'save image taken at each voltage'),
        Parameter('z_axis_center_position', 5, float, 'center point of autofocus sweep'),
        Parameter('scan_width', 1, float, 'distance (in V or mm) between the minimum and maximum points of the range'),
        Parameter('num_sweep_points', 10, int, 'number of values to sweep between min and max voltage'),
        Parameter('focusing_optimizer', 'standard_deviation',
                  ['mean', 'standard_deviation', 'normalized_standard_deviation'], 'optimization function for focusing'),
        Parameter('wait_time', 0.1, float),
        Parameter('use_current_z_axis_position', False, bool, 'Overrides z axis center position and instead uses the current piezo voltage as the center of the range'),
        Parameter('center_on_current_location', False, bool, 'Check to use current galvo location rather than center point in take_image'),
        Parameter('galvo_return_to_initial', False, bool, 'Check to return galvo location to initial value (before calling autofocus)'),
        Parameter('DAQ_channel_Z', 'ao2', ['ao0', 'ao1', 'ao2', 'ao3']),
        Parameter('laser_off_after', True, bool, 'if true laser is turned off after the measurement')
        # Parameter('galvo_position', 'take_image_pta', ['take_image', 'current_location', 'last_run'], 'select galvo location (center point in acquire_image, current location of galvo or location from previous run)')
    ]

    # the take image script depends on the particular hardware, e.g. DAQ or NIFPGA
    _SCRIPTS = {
        'take_image': NotImplemented
    }

    _INSTRUMENTS = {'PB':CN041PulseBlaster}

    def __init__(self, scripts, instruments = None, name = None, settings = None, log_function = None, data_path = None):
        """
        Example of a script that emits a QT signal for the gui
        Args:
            name (optional): name of script, if empty same as class name
            settings (optional): settings for this script, if empty same as default settings
        """
        Script.__init__(self, name, settings, instruments, scripts, log_function= log_function, data_path = data_path)


    @pyqtSlot(int)
    def _receive_signal(self, progress):
        """
        this function takes care of signals emitted by the subscripts
        the default behaviour is that it just reemits the signal
        Args:
            progress: progress of subscript take_image
        """
        # just ignore the signals from the subscript, we just send out our own signal
        pass
        # sender_emitter = self.sender()
        #
        # if self._current_subscript_stage['current_subscript'] is self.scripts['take_image']:
        #     img_index = self._current_subscript_stage['subscript_exec_count']['take_image']
        #     total_img_count = self.settings['num_sweep_points']
        #     progress = 1.* ((img_index -1 + float(progress)/ 100)/ total_img_count)
        # else:
        #     print('WHERE DID THIS SIGNAL COME FROM??? sender', sender_emitter)
        #
        #     current_image = self.scripts['take_image'].data['image_data']
        #     self.data['current_image'] = deepcopy(current_image)
        #     self.data['extent'] = self.scripts['take_image'].data['extent']
        #
        # self.updateProgress.emit(int(100.*progress))

    def _function(self):
        """
        This is the actual function that will be executed. It uses only information that is provided in the settings property
        will be overwritten in the __init__
        """


        def calc_focusing_optimizer(image, optimizer):
            """
            calculates a measure for how well the image is focused
            Args:
                optimizer: one of the three strings: mean, standard_deviation, normalized_standard_deviation
            Returns:  measure for how well the image is focused
            """
            if optimizer == 'mean':
                return np.mean(image)
            elif optimizer == 'standard_deviation':
                return np.std(image)
            elif optimizer == 'normalized_standard_deviation':
                return np.std(image) / np.mean(image)

        def autofocus_loop(sweep_voltages):
            """
            this is the actual autofocus loop
            Args:
                sweep_voltages: array of sweep voltages

            Returns:

            """
            # update instrument

            take_image_tag = self.scripts['take_image'].settings['tag']

            for index, voltage in enumerate(sweep_voltages):

                if self._abort:
                    self.log('Leaving autofocusing loop')
                    break

                self.init_image()

                # set the voltage on the piezo
                self._step_piezo(voltage, self.settings['wait_time'])
                self.log('take scan, position {:0.2f}'.format(voltage))
                # update the tag of the suvbscript to reflect the current z position
                self.scripts['take_image'].settings['tag'] = '{:s}_{:0.2f}'.format(take_image_tag, voltage)
                # take a galvo scan
                self.scripts['take_image'].run()
                self.data['current_image'] = deepcopy(self.scripts['take_image'].data['image_data'])

                # calculate focusing function for this sweep
                self.data['focus_function_result'].append(
                    calc_focusing_optimizer(self.data['current_image'], self.settings['focusing_optimizer']))

                # save image if the user requests it
                if self.settings['save_images']:
                    self.scripts['take_image'].save_image_to_disk(
                        '{:s}\\image_{:03d}.jpg'.format(self.filename_image, index))
                    self.scripts['take_image'].save_data('{:s}\\image_{:03d}.csv'.format(self.filename_image, index),
                                                         'image_data')

                self.progress = 100. * index / len(sweep_voltages)
                self.updateProgress.emit(self.progress if self.progress < 100 else 99)

            self.scripts['take_image'].settings['tag'] = take_image_tag

        # LASER ON:
        self.instruments['PB']['instance'].update({'laser': {'status': True}})

        if self.settings['save'] or self.settings['save_images']:
            self.filename_image = '{:s}\\image'.format(self.filename())
        else:
            self.filename_image = None

        daq_pt = self._get_galvo_location()

        if self.settings['center_on_current_location']:
            self.scripts['take_image'].settings['point_a'].update({'x': daq_pt[0], 'y': daq_pt[1]})

        min_voltage = self.settings['z_axis_center_position'] - self.settings['scan_width']/2.0
        max_voltage = self.settings['z_axis_center_position'] + self.settings['scan_width']/2.0

        sweep_voltages = np.linspace(min_voltage, max_voltage, self.settings['num_sweep_points'])


        self.data['sweep_voltages'] = sweep_voltages
        self.data['focus_function_result'] = []
        self.data['fit_parameters'] = [0, 0, 0, 0]
        self.data['current_image'] = np.zeros([1,1])
        self.data['extent'] = None


        autofocus_loop(sweep_voltages)


        piezo_voltage, self.data['fit_parameters'] = self.fit_focus()

        self.log('autofocus fit result: z = {:s} V'.format(str(piezo_voltage)))

        self._step_piezo(piezo_voltage, self.settings['wait_time'])

        if self.settings['galvo_return_to_initial']:
            self._set_galvo_location(daq_pt)

        # LASER OFF if desired:\
        if self.settings['laser_off_after']:
            self.instruments['PB']['instance'].update({'laser': {'status': False}})

    def init_image(self):
        """
        Allows inheriting functions to perform any needed operations prior to the beginning of each autofocus loop, such
        as shifting the image location to deal with z-xy coupling.
        """
        pass

    def fit_focus(self):
        '''
        fit the data and set piezo to focus spot

        if fails return None otherwise it returns the voltage for the piezo
        '''

        noise_guess = np.min(self.data['focus_function_result'])
        amplitude_guess = np.max(self.data['focus_function_result']) - noise_guess
        center_guess = np.mean(self.data['sweep_voltages'])
        width_guess = 0.8

        p2 = [noise_guess, amplitude_guess, center_guess, width_guess]

        return_voltage = None
        try:
            p2, success = sp.optimize.curve_fit(self.gaussian, self.data['sweep_voltages'],
                                                self.data['focus_function_result'], p0=p2,
                                                bounds=([0, [np.inf, np.inf, 100., 100.]]), max_nfev=2000)

            return_voltage = p2[2]

            self.log('Found fit parameters: ' + str(p2))
        except(ValueError, RuntimeError):
            average_voltage = np.mean(self.data['sweep_voltages'])
            self.log(
                'Could not converge to fit parameters, setting piezo to middle of sweep range, {:0.3f} V'.format(
                    average_voltage))
            # z_piezo.update({'voltage': float(average_voltage)})
        finally:
            # even if there is an exception we want to script to continue
            sweep_voltages = self.data['sweep_voltages']
            if not return_voltage is None:
                if return_voltage > sweep_voltages[-1]:
                    return_voltage = float(sweep_voltages[-1])
                    self.log('Best fit found center to be above max sweep range, setting voltage to max, {:0.3f} V'.format(return_voltage))
            elif return_voltage < sweep_voltages[0]:
                return_voltage = float(sweep_voltages[0])
                self.log('Best fit found center to be below min sweep range, setting voltage to min, {:0.3f} V'.format(return_voltage))

            return return_voltage, p2

    def _get_galvo_location(self):
        """
        returns the current position of the galvo
        Returns: list with two floats, which give the x and y position of the galvo mirror

        """
        raise NotImplementedError

    def _set_galvo_location(self):
        """
        sets the current position of the galvo
        galvo_position: list with two floats, which give the x and y position of the galvo mirror
        """
        raise NotImplementedError

    def _step_piezo(self, voltage, wait_time):
        """
        steps the piezo.  Has to be overwritten specifically for each different hardware realization
        voltage: target piezo voltage
        wait_time: settle time after voltage step
        """
        raise NotImplementedError

    def _plot(self, axes_list, data = None):
        # fit the data and set piezo to focus spot

        if data is None:
            data  = self.data
        axis_focus, axis_image = axes_list

        # if take image is running we take the data from there otherwise we use the scripts own image data
        if self._current_subscript_stage['current_subscript'] is self.scripts['take_image']:

            if 'image_data' in self.scripts['take_image'].data:
                current_image = self.scripts['take_image'].data['image_data']
                extent = self.scripts['take_image'].data['extent']
            else:
                current_image = None
        else:
            current_image = data['current_image']
            extent = data['extent']
        if current_image is not None:
            plot_fluorescence_new(current_image, extent, axis_image)

        if 'focus_function_result' in data:
            focus_data = data['focus_function_result']
            sweep_voltages = data['sweep_voltages']
            if len(focus_data)>0:
                axis_focus.plot(sweep_voltages[0:len(focus_data)],focus_data)
                if not (np.array_equal(data['fit_parameters'], [0,0,0,0])):
                    axis_focus.plot(sweep_voltages[0:len(focus_data)], self.gaussian(sweep_voltages[0:len(focus_data)], *self.data['fit_parameters']), 'k')
                axis_focus.hold(False)


        # axis_focus.set_xlabel('Piezo Voltage [V]')
        axis_focus.set_xlabel(self.scan_label)

        if self.settings['focusing_optimizer'] == 'mean':
            ylabel = 'Image Mean [kcounts]'
        elif self.settings['focusing_optimizer'] == 'standard_deviation':
            ylabel = 'Image Standard Deviation [kcounts]'
        elif self.settings['focusing_optimizer'] == 'normalized_standard_deviation':
            ylabel = 'Image Normalized Standard Deviation [arb]'
        else:
            ylabel = self.settings['focusing_optimizer']

        axis_focus.set_ylabel(ylabel)
        axis_focus.set_title('Autofocusing Routine')

    def _update_plot(self, axes_list):
        # fit the data and set piezo to focus spot
        print('updating autofocus plot')
        axis_focus, axis_image = axes_list

        # if take image is running we take the data from there otherwise we use the scripts own image data
        if self._current_subscript_stage['current_subscript'] is self.scripts['take_image']:
            if 'image_data' in self.scripts['take_image'].data:
                current_image = self.scripts['take_image'].data['image_data']
            else:
                current_image = None
        else:
            current_image = self.data['current_image']

        if current_image is not None:
            update_fluorescence(current_image, axis_image)

        axis_focus, axis_image = axes_list

        update_fluorescence(self.data['current_image'], axis_image)

        focus_data = self.data['focus_function_result']
        sweep_voltages = self.data['sweep_voltages']
        if len(focus_data) > 0:
            axis_focus.plot(sweep_voltages[0:len(focus_data)], focus_data)
            axis_focus.hold(False)

    def gaussian(self, x, noise, amp, center, width):
        return (noise + amp * np.exp(-1.0 * (np.square((x - center)) / (2 * (width ** 2)))))
Пример #24
0
class GalvoScanDAQ(GalvoScanGeneric):
    """
    GalvoScan uses the apd, daq, and galvo to sweep across voltages while counting photons at each voltage,
    resulting in an image in the current field of view of the objective.
    """

    _DEFAULT_SETTINGS = [
        Parameter('DAQ_channels', [
            Parameter('x_ao_channel', 'ao0', ['ao0', 'ao1', 'ao2', 'ao3'],
                      'Daq channel used for x voltage analog output'),
            Parameter('y_ao_channel', 'ao3', ['ao0', 'ao1', 'ao2', 'ao3'],
                      'Daq channel used for y voltage analog output'),
            Parameter('counter_channel', 'ctr0', ['ctr0', 'ctr1'],
                      'Daq channel used for counter')
        ])
    ]

    _INSTRUMENTS = {'daq': NI6259}

    def __init__(self,
                 instruments,
                 name=None,
                 settings=None,
                 log_function=None,
                 data_path=None):
        '''
        Initializes GalvoScan script for use in gui

        Args:
            instruments: list of instrument objects
            name: name to give to instantiated script object
            settings: dictionary of new settings to pass in to override defaults
            log_function: log function passed from the gui to direct log calls to the gui log
            data_path: path to save data

        '''
        self._DEFAULT_SETTINGS = self._DEFAULT_SETTINGS + GalvoScanGeneric._DEFAULT_SETTINGS
        Script.__init__(self,
                        name,
                        settings=settings,
                        instruments=instruments,
                        log_function=log_function,
                        data_path=data_path)

    def setup_scan(self):

        self.clockAdjust = int(
            (self.settings['time_per_pt'] + self.settings['settle_time']) /
            self.settings['settle_time'])

        [xVmin, xVmax, yVmax,
         yVmin] = self.pts_to_extent(self.settings['point_a'],
                                     self.settings['point_b'],
                                     self.settings['RoI_mode'])

        self.x_array = np.repeat(
            np.linspace(xVmin,
                        xVmax,
                        self.settings['num_points']['x'],
                        endpoint=True), self.clockAdjust)
        self.y_array = np.linspace(yVmin,
                                   yVmax,
                                   self.settings['num_points']['y'],
                                   endpoint=True)
        sample_rate = float(1) / self.settings['settle_time']
        self.instruments['daq']['instance'].settings['analog_output'][
            self.settings['DAQ_channels']
            ['x_ao_channel']]['sample_rate'] = sample_rate
        self.instruments['daq']['instance'].settings['analog_output'][
            self.settings['DAQ_channels']
            ['y_ao_channel']]['sample_rate'] = sample_rate
        self.instruments['daq']['instance'].settings['digital_input'][
            self.settings['DAQ_channels']
            ['counter_channel']]['sample_rate'] = sample_rate

    def get_galvo_location(self):
        """
        returns the current position of the galvo
        Returns: list with two floats, which give the x and y position of the galvo mirror
        """
        galvo_position = self.instruments['daq'][
            'instance'].get_analog_voltages([
                self.settings['DAQ_channels']['x_ao_channel'],
                self.settings['DAQ_channels']['y_ao_channel']
            ])
        return galvo_position

    def set_galvo_location(self, galvo_position):
        """
        sets the current position of the galvo
        galvo_position: list with two floats, which give the x and y position of the galvo mirror
        """
        if galvo_position[0] > 1 or galvo_position[0] < -1 or galvo_position[
                1] > 1 or galvo_position[1] < -1:
            raise ValueError(
                'The script attempted to set the galvo position to an illegal position outside of +- 1 V'
            )

        pt = galvo_position
        daq = self.instruments['daq']['instance']
        # daq API only accepts either one point and one channel or multiple points and multiple channels
        pt = np.transpose(np.column_stack((pt[0], pt[1])))
        pt = (np.repeat(pt, 2, axis=1))

        daq.setup_AO([
            self.settings['DAQ_channels']['x_ao_channel'],
            self.settings['DAQ_channels']['y_ao_channel']
        ], pt)
        daq.AO_run()
        daq.AO_waitToFinish()
        daq.AO_stop()

    def read_line(self, y_pos):
        """
        reads a line of data from the DAQ
        Args:
            y_pos: y position of the scan

        Returns:

        """
        # initialize APD thread
        clk_source = self.instruments['daq']['instance'].DI_init(
            self.settings['DAQ_channels']['counter_channel'],
            len(self.x_array) + 1)
        self.initPt = np.transpose(np.column_stack((self.x_array[0], y_pos)))
        self.initPt = (np.repeat(self.initPt, 2, axis=1))

        # move galvo to first point in line
        self.instruments['daq']['instance'].setup_AO([
            self.settings['DAQ_channels']['x_ao_channel'],
            self.settings['DAQ_channels']['y_ao_channel']
        ], self.initPt, "")
        self.instruments['daq']['instance'].AO_run()
        self.instruments['daq']['instance'].AO_waitToFinish()
        self.instruments['daq']['instance'].AO_stop()
        self.instruments['daq']['instance'].setup_AO(
            [self.settings['DAQ_channels']['x_ao_channel']], self.x_array,
            clk_source)
        # start counter and scanning sequence
        self.instruments['daq']['instance'].AO_run()
        self.instruments['daq']['instance'].DI_run()
        self.instruments['daq']['instance'].AO_waitToFinish()
        self.instruments['daq']['instance'].AO_stop()
        xLineData, _ = self.instruments['daq']['instance'].DI_read()
        self.instruments['daq']['instance'].DI_stop()
        diffData = np.diff(xLineData)

        summedData = np.zeros(len(self.x_array) / self.clockAdjust)
        for i in range(0, int((len(self.x_array) / self.clockAdjust))):
            summedData[i] = np.sum(diffData[(i * self.clockAdjust +
                                             1):(i * self.clockAdjust +
                                                 self.clockAdjust - 1)])
        # also normalizing to kcounts/sec
        return summedData * (.001 / self.settings['time_per_pt'])
Пример #25
0
class SelectPoints(Script):
    """
Script to select points on an image. The selected points are saved and can be used in a superscript to iterate over.
    """
    _DEFAULT_SETTINGS = [Parameter('patch_size', 0.003)]

    _INSTRUMENTS = {}
    _SCRIPTS = {}

    def __init__(self,
                 instruments=None,
                 scripts=None,
                 name=None,
                 settings=None,
                 log_function=None,
                 data_path=None):
        """
        Select points by clicking on an image
        """
        Script.__init__(self,
                        name,
                        settings=settings,
                        instruments=instruments,
                        scripts=scripts,
                        log_function=log_function,
                        data_path=data_path)

        self.patches = []
        self.plot_settings = {}

    def _function(self):
        """
        Waits until stopped to keep script live. Gui must handle calling of Toggle_NV function on mouse click.
        """

        self.data = {'nv_locations': [], 'image_data': None, 'extent': None}

        self.progress = 50
        self.updateProgress.emit(self.progress)
        # keep script alive while NVs are selected
        while not self._abort:
            time.sleep(1)

    def plot(self, figure_list):
        '''
        Plots a dot on top of each selected NV, with a corresponding number denoting the order in which the NVs are
        listed.
        Precondition: must have an existing image in figure_list[0] to plot over
        Args:
            figure_list:
        '''
        # if there is not image data get it from the current plot
        if not self.data == {} and self.data['image_data'] is None:
            axes = figure_list[0].axes[0]
            if len(axes.images) > 0:
                self.data['image_data'] = np.array(axes.images[0].get_array())
                self.data['extent'] = np.array(axes.images[0].get_extent())
                self.plot_settings['cmap'] = axes.images[0].get_cmap().name
                self.plot_settings['xlabel'] = axes.get_xlabel()
                self.plot_settings['ylabel'] = axes.get_ylabel()
                self.plot_settings['title'] = axes.get_title()
                self.plot_settings['interpol'] = axes.images[
                    0].get_interpolation()

        Script.plot(self, figure_list)

    #must be passed figure with galvo plot on first axis
    def _plot(self, axes_list):
        '''
        Plots a dot on top of each selected NV, with a corresponding number denoting the order in which the NVs are
        listed.
        Precondition: must have an existing image in figure_list[0] to plot over
        Args:
            figure_list:
        '''

        axes = axes_list[0]

        if self.plot_settings:
            axes.imshow(self.data['image_data'],
                        cmap=self.plot_settings['cmap'],
                        interpolation=self.plot_settings['interpol'],
                        extent=self.data['extent'])
            axes.set_xlabel(self.plot_settings['xlabel'])
            axes.set_ylabel(self.plot_settings['ylabel'])
            axes.set_title(self.plot_settings['title'])

        self._update(axes_list)

    def _update(self, axes_list):

        axes = axes_list[0]

        patch_size = self.settings['patch_size']

        #first clear all old patches (circles and numbers), then redraw all
        if not self.patches == []:
            try:  #catch case where plot has been cleared, so old patches no longer exist. Then skip clearing step.
                for patch in self.patches:
                    patch.remove()
            except ValueError:
                pass

        self.patches = []

        for index, pt in enumerate(self.data['nv_locations']):
            # axes.plot(pt, fc='b')

            circ = patches.Circle((pt[0], pt[1]), patch_size, fc='b')
            axes.add_patch(circ)
            self.patches.append(circ)

            text = axes.text(pt[0],
                             pt[1],
                             '{:d}'.format(index),
                             horizontalalignment='center',
                             verticalalignment='center',
                             color='white')
            self.patches.append(text)

    def toggle_NV(self, pt):
        '''
        If there is not currently a selected NV within self.settings[patch_size] of pt, adds it to the selected list. If
        there is, removes that point from the selected list.
        Args:
            pt: the point to add or remove from the selected list

        Poststate: updates selected list

        '''
        if not self.data[
                'nv_locations']:  #if self.data is empty so this is the first point
            self.data['nv_locations'].append(pt)
            self.data['image_data'] = None  # clear image data

        else:
            # use KDTree to find NV closest to mouse click
            tree = scipy.spatial.KDTree(self.data['nv_locations'])
            #does a search with k=1, that is a search for the nearest neighbor, within distance_upper_bound
            d, i = tree.query(pt,
                              k=1,
                              distance_upper_bound=self.settings['patch_size'])

            # removes NV if previously selected
            if d is not np.inf:
                self.data['nv_locations'].pop(i)
            # adds NV if not previously selected
            else:
                self.data['nv_locations'].append(pt)
Пример #26
0
class GalvoScanGeneric(Script):
    """
    GalvoScan uses the apd, daq, and galvo to sweep across voltages while counting photons at each voltage,
    resulting in an image in the current field of view of the objective.
    """

    _DEFAULT_SETTINGS = [
        Parameter('point_a', [
            Parameter('x', 0, float, 'x-coordinate'),
            Parameter('y', 0, float, 'y-coordinate')
        ]),
        Parameter('point_b', [
            Parameter('x', 1.0, float, 'x-coordinate'),
            Parameter('y', 1.0, float, 'y-coordinate')
        ]),
        Parameter(
            'RoI_mode', 'center', ['corner', 'center'],
            'mode to calculate region of interest.\n \
                                                           corner: pta and ptb are diagonal corners of rectangle.\n \
                                                           center: pta is center and pta is extend or rectangle'
        ),
        Parameter('num_points', [
            Parameter('x', 25, int, 'number of x points to scan'),
            Parameter('y', 25, int, 'number of y points to scan')
        ]),
        Parameter('time_per_pt', .002,
                  [.0001, .001, .002, .005, .01, .015, .02],
                  'time in s to measure at each point'),
        Parameter(
            'settle_time', .0002, [.0002],
            'wait time between points to allow galvo to settle in seconds'),
        Parameter(
            'max_counts_plot', -1, int,
            'Rescales colorbar with this as the maximum counts on replotting'),
        # Parameter('DAQ_channels',
        #            [Parameter('x_ao_channel', 'ao0', ['ao0', 'ao1', 'ao2', 'ao3'], 'Daq channel used for x voltage analog output'),
        #             Parameter('y_ao_channel', 'ao3', ['ao0', 'ao1', 'ao2', 'ao3'], 'Daq channel used for y voltage analog output'),
        #             Parameter('counter_channel', 'ctr0', ['ctr0', 'ctr1'], 'Daq channel used for counter')
        #           ]),
        Parameter('ending_behavior', 'return_to_start',
                  ['return_to_start', 'return_to_origin', 'leave_at_corner'],
                  'return to the corn')
    ]

    # _INSTRUMENTS = {'daq':  DAQ}
    _INSTRUMENTS = {}
    _SCRIPTS = {}

    def __init__(self,
                 instruments,
                 name=None,
                 settings=None,
                 log_function=None,
                 data_path=None):
        '''
        Initializes GalvoScan script for use in gui

        Args:
            instruments: list of instrument objects
            name: name to give to instantiated script object
            settings: dictionary of new settings to pass in to override defaults
            log_function: log function passed from the gui to direct log calls to the gui log
            data_path: path to save data

        '''
        Script.__init__(self,
                        name,
                        settings=settings,
                        instruments=instruments,
                        log_function=log_function,
                        data_path=data_path)

    def setup_scan(self):
        """
        prepares the scan
        Returns:

        """
        pass

    def _function(self):
        """
        Executes threaded galvo scan
        """

        # initial_position = self.instruments['daq']['instance'].get_analog_out_voltages([self.settings['DAQ_channels']['x_ao_channel'], self.settings['DAQ_channels']['y_ao_channel']])
        initial_position = self.get_galvo_location()

        self.data = {
            'image_data':
            np.zeros((self.settings['num_points']['y'],
                      self.settings['num_points']['x']))
        }
        self.data['extent'] = self.pts_to_extent(self.settings['point_a'],
                                                 self.settings['point_b'],
                                                 self.settings['RoI_mode'])

        [xVmin, xVmax, yVmax, yVmin] = self.data['extent']
        self.x_array = np.linspace(xVmin,
                                   xVmax,
                                   self.settings['num_points']['x'],
                                   endpoint=True)
        self.y_array = np.linspace(yVmin,
                                   yVmax,
                                   self.settings['num_points']['y'],
                                   endpoint=True)

        self.setup_scan()

        Ny = self.settings['num_points']['y']

        for yNum in xrange(0, Ny):

            if self._abort:
                break
            line_data = self.read_line(self.y_array[yNum])
            self.data['image_data'][yNum] = line_data
            self.progress = float(yNum + 1) / Ny * 100
            self.updateProgress.emit(int(self.progress))

        #set point after scan based on ending_behavior setting
        if self.settings['ending_behavior'] == 'leave_at_corner':
            return
        elif self.settings['ending_behavior'] == 'return_to_start':
            self.set_galvo_location(initial_position)
        elif self.settings['ending_behavior'] == 'return_to_origin':
            self.set_galvo_location([0, 0])

    def get_galvo_location(self):
        """
        returns the current position of the galvo
        Returns: list with two floats, which give the x and y position of the galvo mirror
        """
        raise NotImplementedError
        return galvo_position

    def set_galvo_location(self, galvo_position):
        """
        sets the current position of the galvo
        galvo_position: list with two floats, which give the x and y position of the galvo mirror
        """
        raise NotImplementedError

    def read_line(self, y_pos):
        """
        reads a line of data from the DAQ
        Args:
            y_pos: y position of the scan

        Returns:

        """
        raise NotImplementedError

    @staticmethod
    def pts_to_extent(pta, ptb, roi_mode):
        """

        Args:
            pta: point a
            ptb: point b
            roi_mode:   mode how to calculate region of interest
                        corner: pta and ptb are diagonal corners of rectangle.
                        center: pta is center and ptb is extend or rectangle

        Returns: extend of region of interest [xVmin, xVmax, yVmax, yVmin]

        """
        if roi_mode == 'corner':
            xVmin = min(pta['x'], ptb['x'])
            xVmax = max(pta['x'], ptb['x'])
            yVmin = min(pta['y'], ptb['y'])
            yVmax = max(pta['y'], ptb['y'])
        elif roi_mode == 'center':
            xVmin = pta['x'] - float(ptb['x']) / 2.
            xVmax = pta['x'] + float(ptb['x']) / 2.
            yVmin = pta['y'] - float(ptb['y']) / 2.
            yVmax = pta['y'] + float(ptb['y']) / 2.
        return [xVmin, xVmax, yVmax, yVmin]

    def _plot(self, axes_list, data=None):
        """
        Plots the galvo scan image
        Args:
            axes_list: list of axes objects on which to plot the galvo scan on the first axes object
            data: data (dictionary that contains keys image_data, extent) if not provided use self.data
        """

        if data is None:
            data = self.data

        plot_fluorescence_new(data['image_data'],
                              data['extent'],
                              axes_list[0],
                              max_counts=self.settings['max_counts_plot'])

    def _update_plot(self, axes_list):
        """
        updates the galvo scan image
        Args:
            axes_list: list of axes objects on which to plot plots the esr on the first axes object
        """
        update_fluorescence(self.data['image_data'], axes_list[0],
                            self.settings['max_counts_plot'])

    def get_axes_layout(self, figure_list):
        """
        returns the axes objects the script needs to plot its data
        the default creates a single axes object on each figure
        This can/should be overwritten in a child script if more axes objects are needed
        Args:
            figure_list: a list of figure objects
        Returns:
            axes_list: a list of axes objects

        """

        # only pick the first figure from the figure list, this avoids that get_axes_layout clears all the figures
        return super(GalvoScanGeneric, self).get_axes_layout([figure_list[0]])
class Daq_Read_Counter_Single(Script):
    """
This script reads the Counter input from the DAQ and plots it (fixed number of samples).
    """
    _DEFAULT_SETTINGS = [
        Parameter('integration_time', 0.25, float,
                  'total time to collect counts for'),
        Parameter('N_samps', 100, int,
                  'Number of samples for estimating error bar'),
        Parameter('counter_channel', 'ctr0', ['ctr0', 'ctr1'],
                  'Daq channel used for counter')
    ]

    _INSTRUMENTS = {'daq': NI6259, 'PB': CN041PulseBlaster}

    _SCRIPTS = {}

    def __init__(self,
                 instruments,
                 scripts=None,
                 name=None,
                 settings=None,
                 log_function=None,
                 data_path=None):
        """
        Example of a script that emits a QT signal for the gui
        Args:
            name (optional): name of script, if empty same as class name
            settings (optional): settings for this script, if empty same as default settings
        """
        Script.__init__(self,
                        name,
                        settings=settings,
                        scripts=scripts,
                        instruments=instruments,
                        log_function=log_function,
                        data_path=data_path)

        self.data = {}

    def _function(self):
        """
        This is the actual function that will be executed. It uses only information that is provided in the settings property
        will be overwritten in the __init__
        """

        # turn laser on
        self.instruments['PB']['instance'].update({'laser': {'status': True}})

        sample_num = self.settings['N_samps']

        # initialize numpy arrays to store data:
        self.data = {
            'counts': np.zeros(sample_num - 1),
            'time': np.zeros(sample_num - 1)
        }

        # normalization to get kcounts/sec:
        normalization = self.settings['integration_time'] / sample_num / .001

        # set sample rate:
        sample_rate = sample_num / self.settings['integration_time']
        self.instruments['daq']['instance'].settings['digital_input'][
            self.settings['counter_channel']]['sample_rate'] = sample_rate

        # start counter acquisiotion:
        task = self.instruments['daq']['instance'].setup_counter(
            "ctr0", sample_num, continuous_acquisition=False)
        self.instruments['daq']['instance'].run(task)
        raw_data, num_read = self.instruments['daq']['instance'].read(task)

        # parse cumulative counter into a numpy array of counts collected in subsequent windows:
        self.data['counts'] = np.array(
            [raw_data[i + 1] - raw_data[i]
             for i in range(len(raw_data) - 1)]) / normalization

        # create time vector:
        self.data['time'] = np.arange(0, len(raw_data) - 1) / sample_rate

        time.sleep(2.0 / sample_rate)

        # clean up APD tasks
        self.instruments['daq']['instance'].stop(task)

        # turn laser off
        self.instruments['PB']['instance'].update({'laser': {'status': False}})
        self.log('Laser is off.')

    def plot(self, figure_list):
        # COMMENT_ME
        super(Daq_Read_Counter_Single, self).plot([figure_list[1]])

    def _plot(self, axes_list, data=None):
        # COMMENT_ME

        if data is None:
            data = self.data

        if data:
            plot_counts(axes_list[0], data['counts'], data['time'],
                        'time [sec]')
Пример #28
0
class DAQold(Instrument):
    """
    Class containing all functions used to interact with the NI DAQ, mostly
    acting as a wrapper around C-level dlls provided by NI. Tested on an
    NI DAQ 6259, but should be compatable with most daqmx devices. Supports
    analog output (ao), analog input (ai), and digital input (di) channels.
    Also supports gated digital input, using one PFI channel as a counter
    and a second as a clock.
    """
    try:
        dll_path = get_config_value(
            'NIDAQ_DLL_PATH',
            os.path.join(os.path.dirname(os.path.abspath(__file__)),
                         'config.txt'))
        if os.name == 'nt':
            #checks for windows. If not on windows, check for your OS and add
            #the path to the DLL on your machine
            nidaq = ctypes.WinDLL(dll_path)  # load the DLL
            dll_detected = True
        else:
            warnings.warn(
                "NI DAQmx DLL not found. If it should be present, check the path:"
            )
            print(dll_path)
            dll_detected = False
    except WindowsError:
        # make a fake DAQOut instrument
        dll_detected = False
    except:
        raise

    #currently includes four analog outputs, five analog inputs, and one digital counter input. Add
    #more as needed and your device allows
    _DEFAULT_SETTINGS = Parameter([
        Parameter('device', 'Dev1', (str), 'Name of DAQ device'),
        Parameter('override_buffer_size', -1, int,
                  'Buffer size for manual override (unused if -1)'),
        Parameter(
            'ao_read_offset', .005, float,
            'Empirically determined offset for reading ao voltages internally'
        ),
        Parameter('analog_output', [
            Parameter('ao0', [
                Parameter('channel', 0, [0, 1, 2, 3], 'output channel'),
                Parameter('sample_rate', 1000.0, float,
                          'output sample rate (Hz)'),
                Parameter('min_voltage', -10.0, float,
                          'minimum output voltage (V)'),
                Parameter('max_voltage', 10.0, float,
                          'maximum output voltage (V)')
            ]),
            Parameter('ao1', [
                Parameter('channel', 1, [0, 1, 2, 3], 'output channel'),
                Parameter('sample_rate', 1000.0, float,
                          'output sample rate (Hz)'),
                Parameter('min_voltage', -10.0, float,
                          'minimum output voltage (V)'),
                Parameter('max_voltage', 10.0, float,
                          'maximum output voltage (V)')
            ]),
            Parameter('ao2', [
                Parameter('channel', 2, [0, 1, 2, 3], 'output channel'),
                Parameter('sample_rate', 1000.0, float,
                          'output sample rate (Hz)'),
                Parameter('min_voltage', -10.0, float,
                          'minimum output voltage (V)'),
                Parameter('max_voltage', 10.0, float,
                          'maximum output voltage (V)')
            ]),
            Parameter('ao3', [
                Parameter('channel', 3, [0, 1, 2, 3], 'output channel'),
                Parameter('sample_rate', 1000.0, float,
                          'output sample rate (Hz)'),
                Parameter('min_voltage', -10.0, float,
                          'minimum output voltage (V)'),
                Parameter('max_voltage', 10.0, float,
                          'maximum output voltage (V)')
            ])
        ]),
        Parameter('analog_input', [
            Parameter('ai0', [
                Parameter('channel', 0, range(0, 32), 'input channel'),
                Parameter('sample_rate', 1000.0, float,
                          'input sample rate (Hz)'),
                Parameter('min_voltage', -10.0, float,
                          'minimum input voltage'),
                Parameter('max_voltage', 10.0, float, 'maximum input voltage')
            ]),
            Parameter('ai1', [
                Parameter('channel', 1, range(0, 32), 'input channel'),
                Parameter('sample_rate', 1000.0, float, 'input sample rate'),
                Parameter('min_voltage', -10.0, float,
                          'minimum input voltage'),
                Parameter('max_voltage', 10.0, float, 'maximum input voltage')
            ]),
            Parameter('ai2', [
                Parameter('channel', 2, range(0, 32), 'input channel'),
                Parameter('sample_rate', 1000.0, float, 'input sample rate'),
                Parameter('min_voltage', -10.0, float,
                          'minimum input voltage'),
                Parameter('max_voltage', 10.0, float, 'maximum input voltage')
            ]),
            Parameter('ai3', [
                Parameter('channel', 3, range(0, 32), 'input channel'),
                Parameter('sample_rate', 1000.0, float, 'input sample rate'),
                Parameter('min_voltage', -10.0, float,
                          'minimum input voltage'),
                Parameter('max_voltage', 10.0, float, 'maximum input voltage')
            ]),
            Parameter('ai4', [
                Parameter('channel', 4, range(0, 32), 'input channel'),
                Parameter('sample_rate', 1000.0, float, 'input sample rate'),
                Parameter('min_voltage', -10.0, float,
                          'minimum input voltage'),
                Parameter('max_voltage', 10.0, float,
                          'maximum input voltage (V)')
            ])
        ]),
        Parameter('digital_input', [
            Parameter('ctr0', [
                Parameter('input_channel', 0, range(0, 32),
                          'channel for counter signal input'),
                Parameter('counter_PFI_channel', 8, range(0, 32),
                          'PFI for counter channel input'),
                Parameter('clock_PFI_channel', 13, range(0, 32),
                          'PFI for clock channel output'),
                Parameter('clock_counter_channel', 1, [0, 1],
                          'channel for clock output'),
                Parameter('sample_rate', 1000.0, float,
                          'input sample rate (Hz)')
            ])
        ]),
        Parameter(
            'digital_output',
            [
                Parameter(
                    'do0',
                    [
                        Parameter('channel', 8, range(8, 16), 'channel')
                        # Parameter('value', False, bool, 'value')
                        # Parameter('sample_rate', 1000.0, float, 'output sample rate (Hz)'),
                        # Parameter('min_voltage', -10.0, float, 'minimum output voltage (V)'),
                        # Parameter('max_voltage', 10.0, float, 'maximum output voltage (V)')
                    ])
            ])
    ])

    def __init__(self, name=None, settings=None):
        if self.dll_detected:
            # buf_size = 10
            # data = ctypes.create_string_buffer('\000' * buf_size)
            # try:
            #     #Calls arbitrary function to check connection
            #     self.CHK(self.nidaq.DAQmxGetDevProductType(device, ctypes.byref(data), buf_size))
            #     self.hardware_detected = True
            # except RuntimeError:
            #     self.hardware_detected = False
            super(DAQold, self).__init__(name, settings)

    #unlike most instruments, all of the settings are sent to the DAQ on instantiation of
    #a task, such as an input or output. Thus, changing the settings only updates the internal
    #daq construct in the program and makes no hardware changes
    def update(self, settings):
        """
        Updates daq settings for each channel in the software instrument
        Args:
            settings: a settings dictionary in the standard form
        """
        super(DAQold, self).update(settings)
        print('settings', settings)
        for key, value in settings.iteritems():
            if key == 'device':
                if not (self.is_connected):
                    raise EnvironmentError(
                        'Device invalid, cannot connect to DAQ')

    @property
    def _PROBES(self):
        return None

    def read_probes(self, key):
        pass

    @property
    def is_connected(self):
        """
        Makes a non-state-changing call (a get id call) to check connection to a daq
        Returns: True if daq is connected, false if it is not
        """
        buf_size = 10
        data = ctypes.create_string_buffer('\000' * buf_size)
        try:
            #Calls arbitrary function to check connection
            self._check_error(
                self.nidaq.DAQmxGetDevProductType(self.settings['device'],
                                                  ctypes.byref(data),
                                                  buf_size))
            return True
        except RuntimeError:
            return False

    def DI_init(self, channel, sampleNum, continuous_acquisition=False):
        """
        Initializes a hardware-timed digital counter, bound to a hardware clock
        Args:
            channel: digital channel to initialize for read in
            sampleNum: number of samples to read in for finite operation, or number of samples between
                       reads for continuous operation (to set buffer size)
            continuous_acquisition: run in continuous acquisition mode (ex for a continuous counter) or
                                    finite acquisition mode (ex for a scan, where the number of samples needed
                                    is known a priori)

        Returns: source of clock that this method sets up, which can be given to another function to synch that
        input or output to the same clock

        """
        if 'digital_input' not in self.settings.keys():
            raise ValueError('This DAQ does not support digital input')
        if not channel in self.settings['digital_input'].keys():
            raise KeyError('This is not a valid digital input channel')
        channel_settings = self.settings['digital_input'][channel]
        self.running = True
        self.DI_sampleNum = sampleNum
        self.DI_sample_rate = float(channel_settings['sample_rate'])
        if not continuous_acquisition:
            self.numSampsPerChan = self.DI_sampleNum
        else:
            self.numSampsPerChan = -1
        self.DI_timeout = float64(5 * (1 / self.DI_sample_rate) *
                                  self.DI_sampleNum)
        self.input_channel_str = self.settings['device'] + '/' + channel
        self.counter_out_PFI_str = '/' + self.settings['device'] + '/PFI' + str(
            channel_settings['clock_PFI_channel']
        )  #initial / required only here, see NIDAQ documentation
        self.counter_out_str = self.settings['device'] + '/ctr' + str(
            channel_settings['clock_counter_channel'])
        self.DI_taskHandleCtr = TaskHandle(0)
        self.DI_taskHandleClk = TaskHandle(1)

        # set up clock
        self._dig_pulse_train_cont(self.DI_sample_rate, 0.5, self.DI_sampleNum)
        # set up counter using clock as reference
        self._check_error(
            self.nidaq.DAQmxCreateTask("",
                                       ctypes.byref(self.DI_taskHandleCtr)))
        self._check_error(
            self.nidaq.DAQmxCreateCICountEdgesChan(self.DI_taskHandleCtr,
                                                   self.input_channel_str, "",
                                                   DAQmx_Val_Rising, 0,
                                                   DAQmx_Val_CountUp))
        # PFI13 is standard output channel for ctr1 channel used for clock and
        # is internally looped back to ctr1 input to be read
        if not continuous_acquisition:
            self._check_error(
                self.nidaq.DAQmxCfgSampClkTiming(self.DI_taskHandleCtr,
                                                 self.counter_out_PFI_str,
                                                 float64(self.DI_sample_rate),
                                                 DAQmx_Val_Rising,
                                                 DAQmx_Val_FiniteSamps,
                                                 uInt64(self.DI_sampleNum)))
        else:
            self._check_error(
                self.nidaq.DAQmxCfgSampClkTiming(self.DI_taskHandleCtr,
                                                 self.counter_out_PFI_str,
                                                 float64(self.DI_sample_rate),
                                                 DAQmx_Val_Rising,
                                                 DAQmx_Val_ContSamps,
                                                 uInt64(self.DI_sampleNum)))
        # if (self.settings['override_buffer_size'] > 0):
        #     self._check_error(self.nidaq.DAQmxCfgInputBuffer(self.DI_taskHandleCtr, uInt64(self.settings['override_buffer_size'])))
        # self._check_error(self.nidaq.DAQmxCfgInputBuffer(self.DI_taskHandleCtr, uInt64(sampleNum)))

        self._check_error(self.nidaq.DAQmxStartTask(self.DI_taskHandleCtr))

        return self.counter_out_PFI_str

    def _dig_pulse_train_cont(self, Freq, DutyCycle, Samps):
        """
        Initializes a digital pulse train to act as a reference clock
        Args:
            Freq: frequency of reference clock
            DutyCycle: percentage of cycle that clock should be high voltage (usually .5)
            Samps: number of samples to generate

        Returns:

        """
        self._check_error(
            self.nidaq.DAQmxCreateTask("",
                                       ctypes.byref(self.DI_taskHandleClk)))
        self._check_error(
            self.nidaq.DAQmxCreateCOPulseChanFreq(self.DI_taskHandleClk,
                                                  self.counter_out_str, '',
                                                  DAQmx_Val_Hz, DAQmx_Val_Low,
                                                  float64(0.0), float64(Freq),
                                                  float64(DutyCycle)))
        self._check_error(
            self.nidaq.DAQmxCfgImplicitTiming(self.DI_taskHandleClk,
                                              DAQmx_Val_ContSamps,
                                              uInt64(Samps)))

    # start reading sampleNum values from counter into buffer
    # todo: AK - should this be threaded? original todo: is this actually blocking? Is the threading actually doing anything? see nidaq cookbook
    def DI_run(self):
        """
        start reading sampleNum values from counter into buffer
        """
        self._check_error(self.nidaq.DAQmxStartTask(self.DI_taskHandleClk))

    # read sampleNum previously generated values from a buffer, and return the
    # corresponding 1D array of ctypes.c_double values
    def DI_read(self):
        """
        read sampleNum previously generated values from a buffer, and return the
        corresponding 1D array of ctypes.c_double values
        Returns: 1d array of ctypes.c_double values with the requested counts

        """
        # initialize array and integer to pass as pointers
        self.data = (float64 * self.DI_sampleNum)()
        self.samplesPerChanRead = int32()
        self._check_error(
            self.nidaq.DAQmxReadCounterF64(
                self.DI_taskHandleCtr,
                int32(self.numSampsPerChan), float64(-1),
                ctypes.byref(self.data), uInt32(self.DI_sampleNum),
                ctypes.byref(self.samplesPerChanRead), None))
        return self.data, self.samplesPerChanRead

    def DI_stop(self):
        """
        Stops and cleans up digital input
        """
        self._DI_stopClk()
        self._DI_stopCtr()

    def _DI_stopClk(self):
        """
        stop and clean up clock
        """
        self.running = False
        self.nidaq.DAQmxStopTask(self.DI_taskHandleClk)
        self.nidaq.DAQmxClearTask(self.DI_taskHandleClk)

    def _DI_stopCtr(self):
        """
        stop and clean up counter
        """
        self.nidaq.DAQmxStopTask(self.DI_taskHandleCtr)
        self.nidaq.DAQmxClearTask(self.DI_taskHandleCtr)

    def gated_DI_init(self, channel, num_samples):
        """
        Initializes a gated digital input task. The gate acts as a clock for the counter, so if one has a fast ttl source
        this allows one to read the counter for a shorter time than would be allowed by the daq's internal clock.
        Args:
            channel:
            num_samples:

        Returns:

        """
        if 'digital_input' not in self.settings.keys():
            raise ValueError('This DAQ does not support digital input')
        if not channel in self.settings['digital_input'].keys():
            raise KeyError('This is not a valid digital input channel')
        channel_settings = self.settings['digital_input'][channel]

        input_channel_str_gated = self.settings['device'] + '/' + channel
        counter_out_PFI_str_gated = '/' + self.settings[
            'device'] + '/PFI' + str(
                channel_settings['counter_PFI_channel']
            )  # initial / required only here, see NIDAQ documentation

        self.gated_DI_sampleNum = num_samples

        self.gated_DI_taskHandle = TaskHandle(0)

        self._check_error(
            self.nidaq.DAQmxCreateTask("",
                                       ctypes.byref(self.gated_DI_taskHandle)))

        MIN_TICKS = 0
        MAX_TICKS = 100000

        #setup counter to measure pulse widths
        self._check_error(
            self.nidaq.DAQmxCreateCIPulseWidthChan(self.gated_DI_taskHandle,
                                                   input_channel_str_gated, '',
                                                   MIN_TICKS, MAX_TICKS,
                                                   DAQmx_Val_Ticks,
                                                   DAQmx_Val_Rising, ''))

        #specify number of samples to acquire
        self._check_error(
            self.nidaq.DAQmxCfgImplicitTiming(self.gated_DI_taskHandle,
                                              DAQmx_Val_FiniteSamps,
                                              uInt64(num_samples)))

        #set the terminal for the counter timebase source to the APD source
        #in B26, this is the ctr0 source PFI8, but this will vary from daq to daq
        self._check_error(
            self.nidaq.DAQmxSetCICtrTimebaseSrc(self.gated_DI_taskHandle,
                                                input_channel_str_gated,
                                                counter_out_PFI_str_gated))

        #turn on duplicate count prevention (allows 0 counts to be a valid count for clock ticks during a gate, even
        #though the timebase never went high and thus nothing would normally progress, by also referencing to the internal
        #clock at max frequency, see http://zone.ni.com/reference/en-XX/help/370466AC-01/mxdevconsid/dupcountprevention/
        #for more details)
        self._check_error(
            self.nidaq.DAQmxSetCIDupCountPrevent(self.gated_DI_taskHandle,
                                                 input_channel_str_gated,
                                                 bool32(True)))

    def gated_DI_run(self):
        """
        start reading sampleNum values from counter into buffer
        """
        self._check_error(self.nidaq.DAQmxStartTask(self.gated_DI_taskHandle))

    def gated_DI_read(self, timeout=-1):
        """
        read sampleNum previously generated values from a buffer, and return the
        corresponding 1D array of ctypes.c_double values
        Returns: 1d array of ctypes.c_double values with the requested counts

        """
        # initialize array and integer to pass as pointers
        self.data = (float64 * self.gated_DI_sampleNum)()
        self.samplesPerChanRead = int32()
        self._check_error(
            self.nidaq.DAQmxReadCounterF64(
                self.gated_DI_taskHandle, int32(self.gated_DI_sampleNum),
                float64(timeout), ctypes.byref(self.data),
                uInt32(self.gated_DI_sampleNum),
                ctypes.byref(self.samplesPerChanRead), None))
        return self.data, self.samplesPerChanRead

    def gated_DI_stop(self):
        """
        Stops gated DI task
        """
        self.nidaq.DAQmxStopTask(self.gated_DI_taskHandle)
        self.nidaq.DAQmxClearTask(self.gated_DI_taskHandle)

    def AO_init(self, channels, waveform, clk_source=""):
        """
        Initializes a arbitrary number of analog output channels to output an arbitrary waveform
        Args:
            channels: List of channels to output on
            waveform: 2d array of voltages to output, with each column giving the output values at a given time
                (the timing given by the sample rate of the channel) with the channels going from top to bottom in
                the column in the order given in channels
            clk_source: the PFI channel of the hardware clock to lock the output to, or "" to use the default
                internal clock
        """
        if 'analog_output' not in self.settings.keys():
            raise ValueError('This DAQ does not support analog output')
        for c in channels:
            if not c in self.settings['analog_output'].keys():
                raise KeyError('This is not a valid analog output channel')
        self.AO_sample_rate = float(
            self.settings['analog_output'][channels[0]]
            ['sample_rate'])  #float prevents truncation in division
        for c in channels:
            if not self.settings['analog_output'][c][
                    'sample_rate'] == self.AO_sample_rate:
                raise ValueError('All sample rates must be the same')
        channel_list = ''
        for c in channels:
            channel_list += self.settings['device'] + '/' + c + ','
        channel_list = channel_list[:-1]
        self.running = True
        # special case 1D waveform since length(waveform[0]) is undefined
        if (len(numpy.shape(waveform)) == 2):
            self.numChannels = len(waveform)
            self.periodLength = len(waveform[0])
        else:
            self.periodLength = len(waveform)
            self.numChannels = 1
        self.AO_taskHandle = TaskHandle(0)
        # special case 1D waveform since length(waveform[0]) is undefined
        # converts python array to ctypes array
        if (len(numpy.shape(waveform)) == 2):
            self.data = numpy.zeros((self.numChannels, self.periodLength),
                                    dtype=numpy.float64)
            for i in range(self.numChannels):
                for j in range(self.periodLength):
                    self.data[i, j] = waveform[i, j]
        else:
            self.data = numpy.zeros((self.periodLength), dtype=numpy.float64)
            for i in range(self.periodLength):
                self.data[i] = waveform[i]
        self._check_error(
            self.nidaq.DAQmxCreateTask("", ctypes.byref(self.AO_taskHandle)))
        self._check_error(
            self.nidaq.DAQmxCreateAOVoltageChan(self.AO_taskHandle,
                                                channel_list,
                                                "", float64(-10.0),
                                                float64(10.0), DAQmx_Val_Volts,
                                                None))
        self._check_error(
            self.nidaq.DAQmxCfgSampClkTiming(self.AO_taskHandle, clk_source,
                                             float64(self.AO_sample_rate),
                                             DAQmx_Val_Rising,
                                             DAQmx_Val_FiniteSamps,
                                             uInt64(self.periodLength)))

        self._check_error(
            self.nidaq.DAQmxWriteAnalogF64(
                self.AO_taskHandle, int32(self.periodLength), 0, float64(-1),
                DAQmx_Val_GroupByChannel,
                self.data.ctypes.data_as(ctypes.POINTER(ctypes.c_longlong)),
                None, None))

    # todo: AK - does this actually need to be threaded like in example code? Is it blocking?
    def AO_run(self):
        """
        Begin outputting waveforms (or, if a non-default clock is used, trigger output immediately
        on that clock starting)
        """
        self._check_error(self.nidaq.DAQmxStartTask(self.AO_taskHandle))

    def AO_waitToFinish(self):
        """
        Wait until output has finished
        """
        self._check_error(
            self.nidaq.DAQmxWaitUntilTaskDone(
                self.AO_taskHandle,
                float64(self.periodLength / self.AO_sample_rate * 4 + 1)))

    def AO_stop(self):
        """
        Stop and clean up output
        """
        self.running = False
        self.nidaq.DAQmxStopTask(self.AO_taskHandle)
        self.nidaq.DAQmxClearTask(self.AO_taskHandle)

    # def AO_set_pt(self, xVolt, yVolt):
    #     pt = numpy.transpose(numpy.column_stack((xVolt,yVolt)))
    #     pt = (numpy.repeat(pt, 2, axis=1))
    #     # prefacing string with b should do nothing in python 2, but otherwise this doesn't work
    #     pointthread = DaqOutputWave(nidaq, device, pt, pt, sample_rate)
    #     pointthread.run()
    #     pointthread.waitToFinish()
    #     pointthread.stop()

    def AI_init(self, channel, num_samples_to_acquire):
        """
        Initializes an input channel to read on
        Args:
            channel: Channel to read input
            num_samples_to_acquire: number of samples to acquire on that channel
        """
        if 'analog_input' not in self.settings.keys():
            raise ValueError('This DAQ does not support analog input')
        self.AI_taskHandle = TaskHandle(0)
        self.AI_numSamples = num_samples_to_acquire
        self.data = numpy.zeros((self.AI_numSamples, ), dtype=numpy.float64)
        # now, on with the program
        self._check_error(
            self.nidaq.DAQmxCreateTask("", ctypes.byref(self.AI_taskHandle)))
        self._check_error(
            self.nidaq.DAQmxCreateAIVoltageChan(self.AI_taskHandle,
                                                self.settings['device'], "",
                                                DAQmx_Val_Cfg_Default,
                                                float64(-10.0), float64(10.0),
                                                DAQmx_Val_Volts, None))
        self._check_error(
            self.nidaq.DAQmxCfgSampClkTiming(
                self.AI_taskHandle, "",
                float64(self.settings['analog_input'][channel]['sample_rate']),
                DAQmx_Val_Rising, DAQmx_Val_FiniteSamps,
                uInt64(self.AI_numSamples)))

    def DO_init(self, channels, waveform, clk_source=""):
        """
        Initializes a arbitrary number of digital output channels to output an arbitrary waveform
        Args:
            channels: List of channels to output, check in self.settings['digital_output'] for available channels
            waveform: 2d array of boolean values to output, with each column giving the output values at a given time
                (the timing given by the sample rate of the channel) with the channels going from top to bottom in
                the column in the order given in channels
            clk_source: the PFI channel of the hardware clock to lock the output to, or "" to use the default
                internal clock

        sets up creates self.DO_taskHandle
        """

        task = {'handle': 0, 'sample_rate': 0, 'period_length': 0}

        if 'digital_output' not in self.settings.keys():
            raise ValueError('This DAQ does not support digital output')
        for c in channels:
            if not c in self.settings['digital_output'].keys():
                raise KeyError('This is not a valid digital output channel')
        self.DO_sample_rate = float(
            self.settings['digital_output'][channels[0]]
            ['sample_rate'])  #float prevents truncation in division
        for c in channels:
            if not self.settings['digital_output'][c][
                    'sample_rate'] == self.DO_sample_rate:
                raise ValueError('All sample rates must be the same')

        lines_list = ''
        for c in channels:
            lines_list += self.settings['device'] + '/port0/line' + str(
                self.settings['digital_output'][c]['channel']) + ','
        lines_list = lines_list[:-1]  # remove the last comma
        self.running = True
        # special case 1D waveform since length(waveform[0]) is undefined
        if (len(numpy.shape(waveform)) == 2):
            self.numChannels = len(waveform)
            self.periodLength = len(waveform[0])
        else:
            self.periodLength = len(waveform)
            self.numChannels = 1

        self.DO_taskHandle = TaskHandle(0)
        # special case 1D waveform since length(waveform[0]) is undefined
        # converts python array to ctypes array
        if (len(numpy.shape(waveform)) == 2):
            self.data = numpy.zeros((self.numChannels, self.periodLength),
                                    dtype=numpy.bool)
            for i in range(self.numChannels):
                for j in range(self.periodLength):
                    self.data[i, j] = waveform[i, j]
        else:
            self.data = numpy.zeros((self.periodLength), dtype=numpy.bool)
            for i in range(self.periodLength):
                self.data[i] = waveform[i]
        self._check_error(
            self.nidaq.DAQmxCreateTask("", ctypes.byref(self.DO_taskHandle)))
        self._check_error(
            self.nidaq.DAQmxCreateDOChan(self.DO_taskHandle, lines_list, "",
                                         DAQmx_Val_ChanPerLine))

        self._check_error(
            self.nidaq.DAQmxCfgSampClkTiming(self.DO_taskHandle, clk_source,
                                             float64(self.DO_sample_rate),
                                             DAQmx_Val_Rising,
                                             DAQmx_Val_FiniteSamps,
                                             uInt64(self.periodLength)))

        self._check_error(
            self.nidaq.DAQmxWriteDigitalLines(
                self.DO_taskHandle, int32(self.periodLength), 0, float64(-1),
                DAQmx_Val_GroupByChannel,
                self.data.ctypes.data_as(ctypes.POINTER(ctypes.c_bool)), None,
                None))

    def AI_run(self):
        """
        Start taking analog input and storing it in a buffer
        """
        self._check_error(self.nidaq.DAQmxStartTask(self.AI_taskHandle))

    def AI_read(self):
        """
        Reads the AI voltage values from the buffer
        Returns: array of ctypes.c_long with the voltage data
        """
        read = int32()
        self._check_error(
            self.nidaq.DAQmxReadAnalogF64(self.AI_taskHandle,
                                          self.AI_numSamples, float64(10.0),
                                          DAQmx_Val_GroupByChannel,
                                          self.data.ctypes.data,
                                          self.AI_numSamples,
                                          ctypes.byref(read), None))
        if self.AI_taskHandle.value != 0:
            self.nidaq.DAQmxStopTask(self.AI_taskHandle)
            self.nidaq.DAQmxClearTask(self.AI_taskHandle)

        return self.data

    def get_analog_voltages(self, channel_list):
        """
        Args:
            channel_list: list (length N) of channels from which to read the voltage, channels are given as strings, e.g. ['ao1', 'ai3']

        Returns:
            list of voltages (length N)

        """

        daq_channels_str = ''
        for channel in channel_list:
            if channel in self.settings['analog_output']:
                daq_channels_str += self.settings[
                    'device'] + '/_' + channel + '_vs_aognd, '
            elif (channel in self.settings['analog_input']):
                daq_channels_str += self.settings[
                    'device'] + '/' + channel + ', '
        daq_channels_str = daq_channels_str[:-2]  #strip final comma period
        data = (float64 * len(channel_list))()
        sample_num = 1
        get_voltage_taskHandle = TaskHandle(0)
        self._check_error(
            self.nidaq.DAQmxCreateTask("",
                                       ctypes.byref(get_voltage_taskHandle)))
        self._check_error(
            self.nidaq.DAQmxCreateAIVoltageChan(get_voltage_taskHandle,
                                                daq_channels_str, "",
                                                DAQmx_Val_Cfg_Default,
                                                float64(-10.0), float64(10.0),
                                                DAQmx_Val_Volts, None))
        self._check_error(
            self.nidaq.DAQmxReadAnalogF64(
                get_voltage_taskHandle, int32(sample_num), float64(10.0),
                DAQmx_Val_GroupByChannel, ctypes.byref(data),
                int32(sample_num * len(channel_list)), None, None))
        self._check_error(self.nidaq.DAQmxClearTask(get_voltage_taskHandle))

        for i, channel in enumerate(channel_list):
            # if channel in self.settings['analog_output']:
            data[i] += self.settings['ao_read_offset']

        return [1. * d
                for d in data]  # return and convert from ctype to python float

    def set_analog_voltages(self, output_dict):
        """

        Args:
            output_dict: dictionary with names of channels as key and voltage as value, e.g. {'ao0': 0.1} or {'0':0.1} for setting channel 0 to 0.1

        Returns: nothing

        """
        # daq API only accepts either one point and one channel or multiple points and multiple channels

        #
        # # make sure the key has the right format, e.g. ao0
        # channels = ['ao'+k.replace('ao','') for k in output_dict.keys()]

        channels = []
        voltages = []
        for k, v in output_dict.iteritems():
            channels.append('ao' + k.replace(
                'ao', ''))  # make sure the key has the right format, e.g. ao0
            voltages.append(v)

        voltages = np.array([voltages]).T
        voltages = (np.repeat(voltages, 2, axis=1))
        # pt = np.transpose(np.column_stack((pt[0],pt[1])))
        # pt = (np.repeat(pt, 2, axis=1))

        print('channels', channels)
        print('voltages', voltages)

        self.AO_init(channels, voltages)
        self.AO_run()
        self.AO_waitToFinish()
        self.AO_stop()

    def set_digital_output(self, output_dict):
        """

        Args:
            output_dict: dictionary with names of channels as key and voltage as value, e.g. {'do0': True} or {'0':True} for setting channel 0 to True

        Returns: nothing

        """

        channels = []
        values = []
        for k, v in output_dict.iteritems():
            channels.append('do' + k.replace(
                'do', ''))  # make sure the key has the right format, e.g. ao0
            values.append(v)

        values = np.array([values]).T
        values = (np.repeat(values, 2, axis=1))

        print('channels', channels)
        print('voltages', values)

        self.DO_init(channels, values)

        # --- self.DO_run()
        self._check_error(self.nidaq.DAQmxStartTask(self.DO_taskHandle))
        # -- self.DO_waitToFinish()
        self._check_error(
            self.nidaq.DAQmxWaitUntilTaskDone(
                self.DO_taskHandle,
                float64(self.periodLength / self.DO_sample_rate * 4 + 1)))
        # -- self.DO_stop()
        if self.DO_taskHandle.value != 0:
            self.nidaq.DAQmxStopTask(self.DO_taskHandle)
            self.nidaq.DAQmxClearTask(self.DO_taskHandle)

    def _check_error(self, err):
        """
        Error Checking Routine for DAQmx functions. Pass in the returned values form DAQmx functions (the errors) to get
        an error description. Raises a runtime error
        Args:
            err: 32-it integer error from an NI-DAQmx function

        Returns: a verbose description of the error taken from the nidaq dll

        """
        if err < 0:
            buffer_size = 100
            buffer = ctypes.create_string_buffer('\000' * buffer_size)
            self.nidaq.DAQmxGetErrorString(err, ctypes.byref(buffer),
                                           buffer_size)
            raise RuntimeError('nidaq call failed with error %d: %s' %
                               (err, repr(buffer.value)))
        if err > 0:
            buffer_size = 100
            buffer = ctypes.create_string_buffer('\000' * buffer_size)
            self.nidaq.DAQmxGetErrorString(err, ctypes.byref(buffer),
                                           buffer_size)
            raise RuntimeError('nidaq generated warning %d: %s' %
                               (err, repr(buffer.value)))
class ReadTemperatureLakeshore(Script):
    """
This script reads the temperature from the Lakeshore controller.
    """

    _DEFAULT_SETTINGS = [
        Parameter('sample_rate', 1, float,
                  'Rate at which temperature is recorded in seconds.'),
    ]

    _INSTRUMENTS = {'temp_controller': TemperatureController}

    _SCRIPTS = {}

    def __init__(self,
                 instruments,
                 scripts=None,
                 name=None,
                 settings=None,
                 log_function=None,
                 data_path=None):
        """
        init script function see superclass for details on the parameters
        """
        Script.__init__(self,
                        name,
                        settings=settings,
                        scripts=scripts,
                        instruments=instruments,
                        log_function=log_function,
                        data_path=data_path)

        self.data = {'temperature': deque()}

    def _function(self):
        """
        This is the actual function that will be executed. It uses only information that is provided in the settings property
        will be overwritten in the __init__
        """

        sample_rate = self.settings['sample_rate']
        self.data = {'temperature': deque()}

        while True:
            if self._abort:
                break

            temperature = self.instruments['temp_controller'][
                'instance'].temperature

            if len(temperature) == 2:
                temperature = temperature[0]
            else:
                print(
                    'warning! temperature reading failed. expected a list of length 2 received the following:'
                )
                print(temperature)
                temperature = 0
            self.data['temperature'].append(temperature)
            self.progress = 50.
            self.updateProgress.emit(int(self.progress))

            time.sleep(1.0 / sample_rate)

    def _plot(self, axes_list, data=None):

        if data is None:
            data = self.data
        if data:
            plot_temperature(axes_list[0], data['temperature'],
                             self.settings['sample_rate'])
Пример #30
0
class ESR(Script):
    """
    This class runs ESR on an NV center, outputing microwaves using a MicrowaveGenerator and reading in NV counts using
    a DAQ.
    """

    _DEFAULT_SETTINGS = [
        Parameter('power_out', 3, float, 'output power (dBm)'),
        Parameter('esr_avg', 50, int, 'number of esr averages'),
        Parameter('freq_start', 2.65e9, float, 'start frequency of scan'),
        Parameter('freq_stop', 6.0e7, float, 'end frequency of scan'),
        Parameter('range_type', 'center_range', ['start_stop', 'center_range'], 'start_stop: freq. range from freq_start to freq_stop. center_range: centered at freq_start and width freq_stop'),
        Parameter('freq_points', 200, int, 'number of frequencies in scan'),
        Parameter('integration_time', 0.01, float, 'measurement time of fluorescent counts (must be a multiple of settle time)'),
        Parameter('settle_time', .0002, float, 'time wait after changing frequencies using daq (s)'),
        Parameter('mw_generator_switching_time', .01, float, 'time wait after switching center frequencies on generator (s)'),
        Parameter('MW_off_after', True, bool, 'if true MW output is turned off after the measurement'),
        Parameter('laser_off_after', True, bool, 'if true laser is turned off after the measurement'),
        Parameter('take_ref', True, bool, 'If true normalize each frequency sweep by the average counts. This should be renamed at some point because now we dont take additional data for the reference.'),
        Parameter('save_full_esr', True, bool, 'If true save all the esr traces individually'),
        Parameter('daq_type', 'PCI', ['PCI', 'cDAQ'], 'Type of daq to use for scan'),
        Parameter('DAQ_channels',
                   [Parameter('ao_sweep_channel', 'ao3', ['ao0', 'ao1', 'ao2', 'ao3'],
                             'Daq channel used for SRS generator sweeps (frequency/power)'),
                   Parameter('counter_channel', 'ctr0', ['ctr0', 'ctr1', 'ctr2', 'ctr3'],
                             'Daq channel used for counter')])
    ]

    _INSTRUMENTS = {
        'microwave_generator': MicrowaveGenerator,
        'RF_generator': R8SMicrowaveGenerator,
        'PB': CN041PulseBlaster,
        'NI6259': NI6259,
        'NI9263': NI9263,
        'NI9402': NI9402
    }

    _SCRIPTS = {}

    def __init__(self, instruments, scripts = None, name=None, settings=None, log_function=None, data_path = None):
        Script.__init__(self, name, settings=settings, scripts=scripts, instruments=instruments, log_function=log_function, data_path = data_path)
        # defines which daqs contain the input and output based on user selection of daq interface
        if self.settings['daq_type'] == 'PCI':
            self.daq_in = self.instruments['NI6259']['instance']
            self.daq_out = self.instruments['NI6259']['instance']
        elif self.settings['daq_type'] == 'cDAQ':
            self.daq_in = self.instruments['NI9402']['instance']
            self.daq_out = self.instruments['NI9263']['instance']

    def _function(self):
        """
        This is the actual function that will be executed. It uses only information that is provided in the settings property
        will be overwritten in the __init__
        """


        def get_frequency_voltages(freq_values, sec_num, dev_width, freq_array):
            """

            Args:
                freq_values: frequency values of the whole scan
                sec_num: number of frequency section
                dev_width: width of frequency section


            Returns:

            """

            # calculate the minimum ad and max frequency of current section
            sec_min = min(freq_values) +  dev_width* 2 * sec_num
            sec_max = sec_min + dev_width * 2

            # make freq. array for current section
            freq_section_array = freq_array[np.where(np.logical_and(freq_array >= sec_min,freq_array < sec_max))]
            # if section is empty skip
            if len(freq_section_array) == 0:
                center_frequency = None
                freq_voltage_array = None

            else:
                center_frequency = (sec_max + sec_min) / 2.0
                freq_voltage_array = ((freq_section_array - sec_min) / (dev_width * 2)) * 2 - 1  # normalize voltages to +-1 range

            return freq_voltage_array, center_frequency

        def read_freq_section(freq_voltage_array, center_freq, clock_adjust):
            """
            reads a frequency section from the DAQ

            Args:
                freq_voltage_array: voltages corresponding to the frequency section to be measured (see get_frequency_voltages())
                center_freq:  center frequency corresponding to the frequency section to be measured (see get_frequency_voltages())
                clock_adjust: factor that specifies how many samples+1 go into the duration of the integration time in
                    order to allow for settling time. For example, if the settle time is .0002 and the integration time
                    is .01, the clock adjust is (.01+.0002)/.01 = 51, so 50 samples fit into the originally requested
                    .01 seconds, and each .01 seconds has a 1 sample (.0002 second) rest time.

            Returns: data from daq

            """
            self.instruments['microwave_generator']['instance'].update({'frequency': float(center_freq)})

            time.sleep(self.settings['mw_generator_switching_time'])

            ctrtask = self.daq_in.setup_counter("ctr0", len(freq_voltage_array) + 1)
            aotask = self.daq_out.setup_AO(["ao3"], freq_voltage_array)

            # start counter and scanning sequence
            self.daq_in.run(ctrtask)
            self.daq_out.run(aotask)
            self.daq_out.waitToFinish(aotask)
            self.daq_out.stop(aotask)

            raw_data, _ = self.daq_in.read_counter(ctrtask)

            # raw_data = sweep_mw_and_count_APD(freq_voltage_array, dt)
            # counter counts continiously so we take the difference to get the counts per time interval
            diff_data = np.diff(raw_data)
            summed_data = np.zeros(len(freq_voltage_array) / clock_adjust)
            for i in range(0, int((len(freq_voltage_array) / clock_adjust))):
                summed_data[i] = np.sum(diff_data[(i * clock_adjust + 1):(i * clock_adjust + clock_adjust - 1)])

            # clean up APD tasks
            self.daq_in.stop(ctrtask)

            return summed_data


        self.lines = []

        take_ref = self.settings['take_ref']

        # contruct the frequency array
        if self.settings['range_type'] == 'start_stop':
            if self.settings['freq_start']>self.settings['freq_stop']:
                self.log('end freq. must be larger than start freq when range_type is start_stop. Abort script')
                self._abort = True

            if self.settings['freq_start'] < 0 or self.settings['freq_stop'] > 4.05E9:
                self.log('start or stop frequency out of bounds')
                self._abort = True

            freq_values = np.linspace(self.settings['freq_start'], self.settings['freq_stop'], self.settings['freq_points'])
            freq_range = max(freq_values) - min(freq_values)
        elif self.settings['range_type'] == 'center_range':
            if self.settings['freq_start'] < 2 * self.settings['freq_stop']:
                self.log('end freq. (range) must be smaller than 2x start freq (center) when range_type is center_range. Abort script')
                self._abort = True
            freq_values = np.linspace(self.settings['freq_start']-self.settings['freq_stop']/2,
                                      self.settings['freq_start']+self.settings['freq_stop']/2, self.settings['freq_points'])
            freq_range = max(freq_values) - min(freq_values)

            if self.settings['freq_stop'] > 1e9:
                self.log('freq_stop (range) is quite large --- did you mean to set \'range_type\' to \'start_stop\'? ')
        else:
            self.log('unknown range parameter. Abort script')
            self._abort = True

        self.instruments['RF_generator']['instance'].update({'enable_output': False})
        self.instruments['PB']['instance'].update({'RF_switch': {'status': False}})
        self.instruments['PB']['instance'].update({'laser': {'status': True}})
        self.instruments['PB']['instance'].update({'microwave_switch':{'status': True}})

        num_freq_sections = int(freq_range) / int(self.instruments['microwave_generator']['instance'].settings['dev_width']*2) + 1
        clock_adjust = int((self.settings['integration_time'] + self.settings['settle_time']) / self.settings['settle_time'])
        freq_array = np.repeat(freq_values, clock_adjust)
        self.instruments['microwave_generator']['instance'].update({'amplitude': self.settings['power_out']})
        self.instruments['microwave_generator']['instance'].update({'modulation_type': 'FM'})
        self.instruments['microwave_generator']['instance'].update({'dev_width': 3.2E7})
        self.instruments['microwave_generator']['instance'].update({'modulation_function': 'External'})
        self.instruments['microwave_generator']['instance'].update({'enable_modulation': True})

        sample_rate = float(1) / self.settings['settle_time']
        self.daq_out.settings['analog_output']['ao3']['sample_rate'] = sample_rate
        self.daq_in.settings['digital_input']['ctr0']['sample_rate'] = sample_rate

        self.instruments['microwave_generator']['instance'].update({'enable_output': True})

        esr_data = np.zeros((self.settings['esr_avg'], len(freq_values)))
        avrg_counts = np.zeros(self.settings['esr_avg']) # here we save the avrg of the esr scan which we will use to normalize
        self.data = {'frequency': [], 'data': [], 'fit_params': [], 'avrg_counts' : avrg_counts}

        # run sweeps
        for scan_num in xrange(0, self.settings['esr_avg']):
            if self._abort:
                break
            esr_data_pos = 0

            for sec_num in xrange(0, num_freq_sections):

                freq_voltage_array, center_freq = get_frequency_voltages(freq_values,
                                                                         sec_num,
                                                                         self.instruments['microwave_generator']['instance'].settings['dev_width'],
                                                                         freq_array)
                # if section is empty skip
                if len(freq_voltage_array) is None:
                    continue

                summed_data = read_freq_section(freq_voltage_array, center_freq, clock_adjust)

                # also normalizing to kcounts/sec
                esr_data[scan_num, esr_data_pos:(esr_data_pos + len(summed_data))] = summed_data * (.001 / self.settings['integration_time'])

                esr_data_pos += len(summed_data)


            avrg_counts[scan_num] = np.mean(esr_data[scan_num])
            # print('JG20170615 avrg counts',scan_num, avrg_counts[scan_num] )
            # print('JG20170515 shape of esr daagta', np.shape(esr_data[0:(scan_num + 1)]))
            # print('JG20170515 len(freq_values)', len(freq_values))

            if take_ref is True:
                esr_data[scan_num] /=avrg_counts[scan_num]

            esr_avg = np.mean(esr_data[0:(scan_num + 1)] , axis=0)

            fit_params = fit_esr(freq_values, esr_avg)
            self.data.update({'frequency': freq_values, 'data': esr_avg, 'fit_params': fit_params})


            if self.settings['save_full_esr']:
                self.data.update({'esr_data':esr_data})


            progress = self._calc_progress(scan_num)
            self.updateProgress.emit(progress)

        if self.settings['MW_off_after']:
            self.instruments['microwave_generator']['instance'].update({'enable_output': False})
            self.instruments['PB']['instance'].update({'microwave_switch': {'status': False}})

        if self.settings['laser_off_after']:
            self.instruments['PB']['instance'].update({'laser': {'status': False}})



    def _calc_progress(self, scan_num):
        #COMMENT_ME

        progress = float(scan_num) / self.settings['esr_avg'] * 100.
        self.progress = progress
        return int(progress)

    def _plot(self, axes_list, data = None):
        """
        plotting function for esr
        Args:
            axes_list: list of axes objects on which to plot plots the esr on the first axes object
            data: data (dictionary that contains keys frequency, data and fit_params) if not provided use self.data
        Returns:

        """
        if data is None:
            data = self.data

        plot_esr(axes_list[0], data['frequency'], data['data'], data['fit_params'])

    def _update_plot(self, axes_list):
        """
        plotting function for esr
        Args:
            axes_list: list of axes objects on which to plot plots the esr on the first axes object
            data: data (dictionary that contains keys frequency, data and fit_params) if not provided use self.data
        Returns:

        """
        data = self.data

        plot_esr(axes_list[0], data['frequency'], data['data'], data['fit_params'])

    def get_axes_layout(self, figure_list):
        """
        returns the axes objects the script needs to plot its data
        the default creates a single axes object on each figure
        This can/should be overwritten in a child script if more axes objects are needed
        Args:
            figure_list: a list of figure objects
        Returns:
            axes_list: a list of axes objects

        """
        new_figure_list = [figure_list[1]]
        return super(ESR, self).get_axes_layout(new_figure_list)