Exemple #1
0
    def apply_labber_exe_path(self):
        '''
        Apply the Labber executable path stored in the FileManager object attributes to ScriptTools, ie "set" externally.

        NB: This method should not be used explicitly in the external script; it will be called as part of measurement pre-processing anyway.
        '''
        self.logger.debug(
            "Setting Labber executable path through ScriptTools...")
        ## Set ScriptTools path
        ScriptTools.setExePath(self.labber_exe_path)
        ## Status message
        self.logger.debug("Labber executable path set through ScriptTools.")
Exemple #2
0
    def init_MeasurementObject(self,
                               reference_path,
                               output_path,
                               *,
                               auto_init=False):
        '''
        Initialise a Labber MeasurementObject instance.

        For more information on the MeasurementObject, see the Labber API docs.
        '''
        ## debug message
        self.logger.log(LogLevels.VERBOSE, "Initialising MeasurementObject...")
        ## Check that the MeasurementObject has not already been initialised
        if self.MeasurementObject is not None:
            if not auto_init:
                ## This method has been called manually
                # warnings.warn("Labber MeasurementObject has already been initialised!", RuntimeWarning)
                self.logger.warning(
                    'Labber MeasurementObject has already been initialised!')
            return
        else:
            ## Initialise MeasurementObject
            self.MeasurementObject = ScriptTools.MeasurementObject(\
                                        reference_path,
                                        output_path)
            ## debug message
            self.logger.debug("MeasurementObject initialised.")
def RunMeasurement(ConfigName,
                   MeasLabel,
                   ItemDict={},
                   DataFolderName='Z:\Projects\Fluxonium\Data\\AugustusXVII',
                   ConfigPath='',
                   PrintOutFileName=False):
    # set path to executable
    ScriptTools.setExePath('C:\Program Files\Labber\Program')

    TimeNow = datetime.now().strftime('%Y-%m-%d-%H-%M-%S')
    TimeStr = str(TimeNow)
    Year = TimeStr[:4]
    Month = TimeStr[5:7]
    Day = TimeStr[8:10]
    # define measurement objects
    # ConfigPath = 'C:\SC Lab\GitHubRepositories\measurement-with-labber\measurement setting/'
    DataPath = DataFolderName + '/' + Year + '/' + Month + '/' + 'Data_' + Month + Day + '/'

    if not os.path.exists(DataPath):
        os.makedirs(DataPath)

    ConfigFile = ConfigPath + ConfigName
    OutputFile = DataPath + MeasLabel + '_' + TimeStr
    if PrintOutFileName:
        print(MeasLabel + '_' + TimeStr)
    MeasObj = ScriptTools.MeasurementObject(ConfigFile, OutputFile)

    for item, value in ItemDict.items():
        if type(value) is list:
            for subitem in value:
                MeasObj.updateValue(item, subitem[0], itemType=subitem[1])
        else:
            MeasObj.updateValue(item, value)
    # if second_channel != '':
    #     MeasObj.setMasterChannel(second_channel)
    MeasObj.performMeasurement(return_data=False)

    return [DataPath, MeasLabel + '_' + TimeStr + '.hdf5']
Exemple #4
0
    def __init__(self, **kwargs):

        super().__init__(**kwargs)
        # Check to see if the template file has been saved in the DySART database;
        # if not, deserialize the .hdf5 on disk.
        if not self.template:
            self.deserialize_template()

        # Deprecated by Simon's changes to Labber API?
        self.config = st.MeasurementObject(self.template_file_path,
                                           self.output_file_path)

        self.log_history = LogHistory(self.id, conf.config['labber_data_dir'],
                                      os.path.split(self.output_file_path)[-1])
Exemple #5
0
    def tune(self,
             occupation_1,
             occupation_2,
             res=1e-3,
             calibrate_nr=3,
             plot=False):
        """
        Finds the desired charge regime, given a reference point.

        Parameters
        ----------
        occupation_1 : int
                       desired charge occupation number for left dot

        occupation_2 : int
                       desired charge occupation number for right dot

        res: float, optional
             default: 1e-3
             Resolution with which frames are measured.

        calibrate_nr: int, optional
                      default: 3
                      How often the QPC is calibrated. If calibrate_nr == 3,
                      before measuring every third frame, the QPC is calibrated.

        plot: bool, optional
              default: False
              If True, all measurements taken are plotted.

        Returns
        -------
        tuple
            PG1 & PG2 voltage
        """
        # initialize variables
        self.occupation_1_f = occupation_1
        self.occupation_2_f = occupation_2

        # direction variable defines where the next frame is drawn
        # direction == 0: lower right corner (i.e. right)
        # direction == 1: upper right corner (i.e. top-right)
        # direction == 2: upper left corner (i.e. up)
        direction = 1
        found = False
        reverse = False  # if charge occupation too high, go back
        counter = 0  # length of path in frames

        print('Start tuning Charge States.')
        self.PG1['n_pts'] = self.f + 2 * self.p + 1
        self.PG2['n_pts'] = self.f + 2 * self.p + 1

        while not found:
            # define measurement variables
            self.PG1['stop'] = self.trans_path_x[-1] - (self.p + 0.5) * res
            self.PG1['start'] = self.trans_path_x[-1] + (self.f + self.p +
                                                         0.5) * res
            self.PG2['stop'] = self.trans_path_y[-1] - (self.p + 0.5) * res
            self.PG2['start'] = self.trans_path_y[-1] + (self.f + self.p +
                                                         0.5) * res

            # overwrite when necessary
            if reverse:
                if direction == 0:
                    self.PG1['stop'] = self.trans_path_x[-1] - (
                        self.f + self.p + 0.5) * res
                    self.PG1['start'] = self.trans_path_x[-1] + (self.p +
                                                                 0.5) * res
                if direction == 2:
                    self.PG2['stop'] = self.trans_path_y[-1] - (
                        self.f + self.p + 0.5) * res
                    self.PG2['start'] = self.trans_path_y[-1] + (self.p +
                                                                 0.5) * res

            # add starting point for plotting reasons
            self.trans_frame_start.append(
                (self.PG1['start'], self.PG2['start']))

            # define mean PG values for QPC calibration
            self.PG2['mean'] = self.PG2['start'] + (self.PG2['stop'] -
                                                    self.PG2['start']) / 2.
            self.PG1['mean'] = self.PG1['start'] + (self.PG1['stop'] -
                                                    self.PG1['start']) / 2.

            # update charge occupation into list
            self.occupation.append((self.occupation_1, self.occupation_2))

            # update path to take
            if direction == 0:
                if not reverse:
                    self.trans_path_x.append(self.trans_path_x[-1] +
                                             self.f * res)
                    self.trans_path_y.append(self.trans_path_y[-1])
                else:
                    self.trans_path_x.append(self.trans_path_x[-1] -
                                             self.f * res)
                    self.trans_path_y.append(self.trans_path_y[-1])

            elif direction == 1:
                if not reverse:
                    self.trans_path_x.append(self.trans_path_x[-1] +
                                             self.f * res)
                    self.trans_path_y.append(self.trans_path_y[-1] +
                                             self.f * res)
                else:  # this is never True - just for completeness
                    self.trans_path_x.append(self.trans_path_x[-1] -
                                             self.f * res)
                    self.trans_path_y.append(self.trans_path_y[-1] -
                                             self.f * res)

            elif direction == 2:
                if not reverse:
                    self.trans_path_x.append(self.trans_path_x[-1])
                    self.trans_path_y.append(self.trans_path_y[-1] +
                                             self.f * res)
                else:
                    self.trans_path_x.append(self.trans_path_x[-1])
                    self.trans_path_y.append(self.trans_path_y[-1] -
                                             self.f * res)

            # Print path number and what points the path links
            print('\n###################\n'
                  'Path: {}\n'
                  'PG1: {} -> {}\n'
                  'PG2: {} -> {}'.format(counter,
                                         round(self.trans_path_x[-2], 3),
                                         round(self.trans_path_x[-1], 3),
                                         round(self.trans_path_y[-2], 3),
                                         round(self.trans_path_y[-1], 3)))

            # define measurement object
            output_path = os.path.join(self.path_out, 'tune_' + str(counter))
            measurement = ScriptTools.MeasurementObject(
                self.path_in, output_path)

            # recalibrate QPC every calibrate_nr frames
            if counter % calibrate_nr == calibrate_nr - 1 or self.sweet_spot is None:
                calibrate = True
                gate_config = None

            else:
                calibrate = False
                gate_config = {self.gate_names['QPC_G']: self.sweet_spot}

            # get the measured QPC signal and reshape it
            measurement_signal = self.get_data_(
                measurement,
                output_path,
                DQD_log_channel=self.gate_names['I_DQD'],
                calibrate=calibrate,
                rescale=True,
                config=gate_config)
            self.trans_frames.append(measurement_signal['I_QPC'])
            I_DQD = measurement_signal['I_DQD']
            reshaped_signal = self.trans_frames[-1].reshape(
                (1, self.f + 2 * self.p, self.f + 2 * self.p, 1))

            # check whether there is a current through the dot, if so - abort
            # TODO introduce threshold as a variable
            if self.is_current_(I_DQD, 7e-12):
                print(
                    'There is too much current through the dot. Tuning stopped.\n'
                    'Last PG values:\n'
                    'PG1: {}\n'
                    'PG2: {}'.format(self.trans_path_x[-1],
                                     self.trans_path_y[-1]))
                break

            # Make the predictions
            # (QD1 transition, QD2 transition)
            # transition[i] == [1, 0, 0, 0]: (False, False)
            # transition[i] == [0, 1, 0, 0]: (False, True)
            # transition[i] == [0, 0, 1, 0]: (True, False)
            # transition[i] == [0, 0, 0, 1]: (True, True)
            # for i in {0, 1, 2}
            transition = self.trans_rec.predict(reshaped_signal)
            self.trans_classif.append(transition)

            # Check whether there is a contradiction. If there is one,
            # measure the same frame again and redo the classifications.
            is_contra = self.is_contradiction_(transition)
            if is_contra:
                print('There is a contradiction.\n' '{}'.format(transition))
                # Remeasure the frame.
                self.trans_frames.append(
                    self.get_data_(measurement,
                                   output_path,
                                   calibrate=calibrate,
                                   config=gate_config)['I_QPC'])
                # Redo the classifications
                reshaped_signal = self.trans_frames[-1].reshape(
                    (1, self.f + 2 * self.p, self.f + 2 * self.p, 1))
                transition = self.trans_rec.predict(reshaped_signal)

                # check again whether there is still a contradiction
                if self.is_contradiction_(transition):
                    print('There is still a contradiction. Proceed anyways.')
                else:
                    print(
                        'There is no contradiction anymore. Proceed as normal.'
                    )

            # plot the data
            if plot:
                pass
                # grid = plt.GridSpec(20, 20)
                # fig = plt.figure()
                # ax = plt.subplot(grid[:20, :20])
                # axins1 = inset_axes(ax,
                #                     width="3%",
                #                     height="100%",
                #                     loc='lower left',
                #                     bbox_to_anchor=(1.01, 0., 1, 1),
                #                     bbox_transform=ax.transAxes,
                #                     borderpad=0,
                #                     )
                #
                # im1 = ax.pcolormesh(self.trans_frames[-1][:, :], linewidth=0, rasterized=True)
                # cbar = fig.colorbar(im1, cax=axins1)
                # ax.axhline(y=self.p, color='black', linewidth=2)
                # ax.axhline(y=self.f+self.p, color='black', linewidth=2)
                # ax.axvline(x=self.p, color='black', linewidth=2)
                # ax.axvline(x=self.f+self.p, color='black', linewidth=2)
                # ax.plot(self.trans_path_x[-1], self.trans_path_y[-1])
                # ax.set_ylim(0, self.f+2*self.p)
                # ax.set_xlim(0, self.f+2*self.p)
                # plt.title('Path {}'.format(counter))
                # plt.show()

            # Trigger charge transitions
            if np.argmax(transition[direction]) == 0:  # no transition at all
                print('No transition.\n'
                      'Confidence: {}\n'
                      'Current charge occupation({},{})'.format(
                          np.max(transition[direction]), self.occupation_1,
                          self.occupation_2))
            elif np.argmax(transition[direction]) == 1:  # transition in QD2
                if not reverse:
                    self.occupation_2 += 1
                else:
                    self.occupation_2 -= 1
                print('Transition in QD2.\n'
                      'Confidence: {}\n'
                      'Current charge occupation({},{})'.format(
                          np.max(transition[direction]), self.occupation_1,
                          self.occupation_2))
            elif np.argmax(transition[direction]) == 2:  # transision in QD1
                if not reverse:
                    self.occupation_1 += 1
                else:
                    self.occupation_1 -= 1
                print('Transition in QD1.\n'
                      'Confidence: {}\n'
                      'Current charge occupation({},{})'.format(
                          np.max(transition[direction]), self.occupation_1,
                          self.occupation_2))
            elif np.argmax(
                    transition[direction]) == 3:  # transition in both QDs
                if not reverse:
                    self.occupation_2 += 1
                    self.occupation_1 += 1
                else:
                    self.occupation_2 -= 1
                    self.occupation_1 -= 1
                print('Transition in both QDs.\n'
                      'Confidence: {}\n'
                      'Current charge occupation({},{})'.format(
                          np.max(transition[direction]), self.occupation_1,
                          self.occupation_2))

            # define where to go based on current charge occupation
            if self.occupation_1 < self.occupation_1_f and self.occupation_2 < self.occupation_2_f:
                direction = 1
                reverse = False
                print('-> Going up right.')

            elif self.occupation_1 == self.occupation_1_f and self.occupation_2 < self.occupation_2_f:
                direction = 2
                reverse = False
                print('-> Going up.')

            elif self.occupation_1 < self.occupation_1_f and self.occupation_2 == self.occupation_2_f:
                direction = 0
                reverse = False
                print('-> Going right.')

            # go back, if one of the dots has too many electrons
            elif self.occupation_1 > self.occupation_1_f:
                direction = 0
                reverse = True
                print('-> Going left.')

            elif self.occupation_2 > self.occupation_2_f:
                direction = 2
                reverse = True
                print('-> Going down.')

            # Terminate if desired charge configuration is reached
            elif self.occupation_1 == self.occupation_1_f and self.occupation_2 == self.occupation_2_f:
                print('\nTuning successful\n'
                      'PG1: {}V\n'
                      'PG2: {}V'.format(self.trans_path_x[-1],
                                        self.trans_path_y[-1]))
                found = True

            if counter >= 15:
                print('Could not find desired charge configuration.\n'
                      'Program stopped.')
                found = True

            counter += 1
Exemple #6
0
    def find_reference(self,
                       f,
                       b,
                       x_0,
                       y_0,
                       res=8e-3,
                       calibrate_nr=3,
                       confidence=0.7,
                       plot=False):
        """
        Finds a plunger gate voltage configuration for which the charge occupation is
        given as (0,0). That is, both dots are empty. This plunger gate voltage configuration
        can then be used as a reference point in the charge occupation tuning process.

        Needs a boundary for the plunger gate voltages from which a random starting point is chosen.

        Parameters
        ----------
        f : int
            coarse frame size

        b : int
            coarse frame border

        x_0 : float
              starting point for PG1 from which a reference point is searched.

        y_0 : float
              starting point for PG2 from which a reference point is searched.

        res : float, optional
              default: 8e-3
              coarse resolution in V

        calibrate_nr : int, optional
                       default: 3
                       How often the QPC is calibrated. If calibrate_nr == 3,
                       before measuring every third frame, the QPC is calibrated.

        confidence : float, optional
                     default: 0.8
                     Determines the confidence threshold with which we recognize the empty region.

        plot: bool, optional
              default: False
              If True, a plot of every frame plus its convolution filters is made.

        Returns
        -------
        float, float

        Voltage for PG1 and PG2 for which both quantum dots are unoccupied.
        """
        found = False
        counter = 0
        self.PG1['n_pts'] = f + b + 1
        self.PG2['n_pts'] = f + b + 1

        self.ref_path_x.append(x_0)
        self.ref_path_y.append(y_0)

        while not found:
            print('\n#####################'
                  '\n Frame nr. {}'.format(counter + 1))
            # update measurement information
            self.PG1['start'] = self.ref_path_x[-1] + (b + 0.5) * res
            self.PG1['stop'] = self.ref_path_x[-1] - (f + 0.5) * res
            self.PG1['mean'] = self.PG1['start'] + (self.PG1['stop'] -
                                                    self.PG1['start']) / 2.

            self.PG2['start'] = self.ref_path_y[-1] + (b + 0.5) * res
            self.PG2['stop'] = self.ref_path_y[-1] - (f + 0.5) * res
            self.PG2['mean'] = self.PG2['start'] + (self.PG2['stop'] -
                                                    self.PG2['start']) / 2.

            # define measurement object
            output_path = os.path.join(self.path_out,
                                       'reference_' + str(counter))
            measurement = ScriptTools.MeasurementObject(
                self.path_in, output_path)

            # recalibrate QPC every 3 frames
            if counter % calibrate_nr == 0 or self.sweet_spot is None:
                calibrate = True
                gate_config = None

            else:
                calibrate = False
                gate_config = {self.gate_names['QPC_G']: self.sweet_spot}

            # perform measurement / get data
            measurement_signal = self.get_data_(
                measurement,
                output_path,
                DQD_log_channel=self.gate_names['I_DQD'],
                calibrate=calibrate,
                rescale=False,
                config=gate_config)
            self.ref_frames.append(measurement_signal['I_QPC'])
            I_DQD = measurement_signal['I_DQD']

            # reshape data in order to make it suitable for classifier
            reshaped_signal = self.ref_frames[-1].reshape((1, f + b, f + b, 1))

            # predict occupation state
            occupation = self.occupation_ref_rec.predict(reshaped_signal)[0]
            self.ref_classif.append(occupation)
            print('Classification confidences:\n{}'.format(occupation))
            print('PG1: {}V\n'
                  'PG2: {}V'.format(self.ref_path_x[-1], self.ref_path_y[-1]))
            counter += 1

            # plot measurement and visualize filters
            if plot:
                grid = plt.GridSpec(20, 20)
                fig = plt.figure()
                ax = plt.subplot(grid[:20, :20])
                axins1 = inset_axes(
                    ax,
                    width="3%",
                    height="100%",
                    loc='lower left',
                    bbox_to_anchor=(1.01, 0., 1, 1),
                    bbox_transform=ax.transAxes,
                    borderpad=0,
                )

                im1 = ax.pcolormesh(self.ref_frames[-1][:, :],
                                    linewidth=0,
                                    rasterized=True)
                cbar = fig.colorbar(im1, cax=axins1)
                ax.axhline(y=16, color='black', linewidth=2)
                ax.axvline(x=16, color='black', linewidth=2)
                ax.plot(self.ref_path_x[-1], self.ref_path_y[-1])
                ax.set_ylim(0, 20)
                ax.set_xlim(0, 20)
                plt.title('Reference {}'.format(counter))
                plt.show()

            # If confidence that DQD empty is larger than a certain threshold -> terminate
            # Classification outcome: [1, 0] -> dots occupied, [0, 1] -> dots empty
            if (occupation[1] > confidence
                    and not self.is_current_(I_DQD, threshold=7e-12)):
                found = True
                print(self.ref_path_x[-1])
                print(self.ref_path_y[-1])
                self.occupation_1 = 0
                self.occupation_2 = 0
                self.trans_path_x.append(self.ref_path_x[-1])
                self.trans_path_y.append(self.ref_path_y[-1])
                print('Found a reference point at\n'
                      'PG1: {}V\n'
                      'PG2: {}V'.format(self.ref_path_x[-1],
                                        self.ref_path_y[-1]))

            else:
                self.ref_path_x.append(self.ref_path_x[-1] - f * res / 3)
                self.ref_path_y.append(self.ref_path_y[-1] - f * res / 3)

        return self.ref_path_x[-1], self.ref_path_y[-1]
Exemple #7
0
    'name': 'MPG0',
    'mean': -410e-3,
    'start': -260e-3,
    'stop': -550e-3,
    'step': 1e-3
}

# create configurations
configs = []
keys, values = zip(*setup.items())
for v in product(*values):
    config = dict(zip(keys, v))
    config['WGR'] = config['WGL']
    configs.append(config)

ScriptTools.setExePath(r'C:\\Program Files (x86)\\Labber\\Program')
sPath = os.path.abspath(
    r'C:\\Users\\Measure2\\Desktop\\measurement_series\\charge transitions')
path_in = os.path.join(sPath, 'coarse_msm_config2.hdf5')

# Measure the configuration
for k in range(132, 136):
    print('Currently measuring:\n'
          'Configuration: {}\n'
          'Number: {}'.format(configs[k - 132], k))
    path_out = os.path.join(sPath, 'test\\{}.hdf5'.format(k))
    Measurement = ScriptTools.MeasurementObject(path_in, path_out)
    path = os.path.join(sPath, 'msm5\\{}'.format(k))
    measure(PG1,
            PG2,
            Measurement,
Exemple #8
0
import mongoengine as me
import Labber
from Labber import ScriptTools as st

from dysart.labber.labber_serialize import load_labber_scenario_as_dict
from dysart.labber.labber_serialize import save_labber_scenario_from_dict
import dysart.labber.labber_util as labber_util
from dysart.feature import Feature, exposed
from dysart.messages.errors import UnsupportedPlatformError
import toplevel.conf as conf

# Set path to executable. This should be done not-here, but it needs to be put
# somewhere for now.

if platform.system() == 'Darwin':
    st.setExePath(os.path.join(os.path.sep, 'Applications', 'Labber'))
    MAX_PATH = os.statvfs('/').f_namemax
elif platform.system() == 'Linux':
    st.setExePath(
        os.path.join(os.path.sep, 'usr', 'share', 'Labber', 'Program'))
    MAX_PATH = os.statvfs('/').f_namemax
elif platform.system() == 'Windows':
    st.setExePath(os.path.join('C:\\', 'Program Files', 'Labber', 'Program'))
    MAX_PATH = 260  # This magic constant is a piece of Windows lore.
else:
    raise UnsupportedPlatformError

# This mutex is used to synchronize calls to the Labber API
LOCK = asyncio.Lock()

    scenario = Scenario(config_path).get_config_as_dict()

    optimizer_channels = []
    for step in scenario['step_items']:
        if step['optimizer_config']['enabled'] == True:
            optimizer_channels.append(step['optimizer_config'])

    optimizer_config = scenario['optimizer']
    optimizer_config['optimizer_channels'] = optimizer_channels
    return optimizer_config


if __name__ == "__main__":
    sPath = os.path.dirname(os.path.abspath(__file__))
    config_json = 'RandomizedBenchmarking.json'
    config_out = 'RandomizedBenchmarkingOut.hdf5'

    # Read in the optimization configuration as defined in the exported config
    config = extract_nelder_mead_rb_tuneup_config(
                 os.path.join(sPath, config_json))

    # Read in the measurement configuration as it will be run to execute tuneup
    measurement_config = ScriptTools.MeasurementObject(
                             os.path.join(sPath, config_json),
                             os.path.join(sPath, config_out))
    
    cost_function = lambda x: tuneup_cost_function(x, measurement_config)

    optimum = optimize(config, cost_function)
    print(optimum)
Exemple #10
0
def main():
    ATS_var = 'AlazarTech Signal Demodulator - Channel A - Average demodulated value'
    readout_freq_var = 'readout - Frequency'
    readout_power_var = 'readout - Power'

    # set path to executable
    ScriptTools.setExePath(r'C:\Program Files (x86)\Labber\Program')

    test_path = r'E:\Data\2018\05\Data_0531'

    filename_sfx = time.strftime('%m_%d_%H_%M_%S', time.localtime())

    test_meas = os.path.join(test_path, 'test no hardware loop_config.hdf5')
    test_out = os.path.join(test_path,
                            'test no hardware loop_%s.hdf5' % filename_sfx)

    fitting_out = os.path.join(test_path, 'fit_parameters_%s' % filename_sfx)
    fits_out = Labber.createLogFile_ForData(fitting_out, [{
        'name': 'Power',
        'unit': 'dBm'
    }, {
        'name': 'Frequency',
        'unit': 'Hz'
    }, {
        'name': 'Frequency Error',
        'unit': 'Hz'
    }, {
        'name': 'FWHM',
        'unit': 'Hz'
    }, {
        'name': 'FWHM Error',
        'unit': 'Hz'
    }])

    # define measurement objects
    test = ScriptTools.MeasurementObject(test_meas, test_out)
    test.setMasterChannel(readout_power_var)

    pows = np.linspace(8, 10, 2)

    # go through list of points
    for idx, pr in enumerate(pows):
        print('\nFreq point number: %d (out of %d points).' %
              ((idx + 1), pows.size))
        print('Current Power: %.2f dBm.' % pr)

        ### TEST ###
        # set power
        test.updateValue(readout_power_var, pr)

        # find qubit
        print('Test in progress...')
        t_test = time.clock()
        (freqs, _) = test.performMeasurement()
        print('Test measurement time: %.1f s.' % (time.clock() - t_test))

        # fit spectrum
        test_in = Labber.LogFile(test_out)
        # last_entry = test_in.getEntry(-1)
        # refls = last_entry[ATS_var]
        refls = test_in.getData(ATS_var)[0]
        print('Fitting to Lorentzian...')
        try:
            fit = fitting.lorentzian_fit(freqs, refls)
            (freq, freq_err) = fit[0]
            (FWHM, FWHM_err) = fit[1]
            print('Cavity frequency: %.4f +/- %.4f GHz.' %
                  (1.e-9 * freq, 1.e-9 * freq_err))
            print('Cavity FWHM: %.4f +/- %.4f GHz.' %
                  (1.e-9 * FWHM, 1.e-9 * FWHM_err))
        except:
            print_exception()
            continue

        # save fit data
        fits_out.addEntry({
            'Power': np.array([pr]),
            'Frequency': np.array([freq]),
            'Frequency Error': np.array([freq_err]),
            'FWHM': np.array([FWHM]),
            'FWHM Error': np.array([FWHM_err])
        })