Beispiel #1
0
    def __init__(self,
                 ctr_client: si_tt.Client,
                 ui='count_monitor',
                 logger_client=None,
                 server_port=None,
                 combined_channel=False,
                 config=None):
        """ Constructor for CountMonitor script

        :param ctr_client: instance of hardware client for counter
        :param gui_client: (optional) instance of client of desired output GUI
        :param logger_client: (obj) instance of logger client.
        :param server_port: (int) port number of script server
        :combined_channel: (bool) If true, show additional trace with summed counts.
        """

        self._ctr = ctr_client
        self.log = logger_client
        self.combined_channel = combined_channel
        self._bin_width = None
        self._n_bins = None
        self._ch_list = None
        self._plot_list = None  # List of channels to assign to each plot (e.g. [[1,2], [3,4]])
        self._plots_assigned = [
        ]  # List of plots on the GUI that have been assigned

        if self.combined_channel:
            ui = 'count_monitor_combined'
        else:
            ui = 'count_monitor'

        # Instantiate GUI window
        self.gui = Window(gui_template=ui, host=get_ip(), port=server_port)

        # Setup stylesheet.
        self.gui.apply_stylesheet()

        if self.combined_channel:
            num_plots = 3
        else:
            num_plots = 2

        # Get all GUI widgets
        self.widgets = get_gui_widgets(self.gui,
                                       graph_widget=num_plots,
                                       number_label=8,
                                       event_button=num_plots,
                                       legend_widget=num_plots)

        # Load config
        self.config = {}
        if config is not None:
            self.config = load_script_config(script='monitor_counts',
                                             config=config,
                                             logger=self.logger_client)

        if not 'name' in self.config:
            self.config.update({'name': f'monitor{np.random.randint(1000)}'})
    def __init__(self,
                 nanopos_client: smaract_mcs2.Client,
                 attocube_client: attocube_anc300.Client,
                 gui='positioner_control_mixed',
                 log_client=None,
                 config=None,
                 port=None):
        """ Instantiates the controller

        :param nanopos_client: (pylabnet.network.client_server.smaract_mcs2.Client)
        :param gui: name of .ui file ot use
        :param log_client: (pylabnet.utils.logging.logger.LogClient)
        :param config: (str) name of config file, optional
        :param port: (int) port number for update/script server
        """

        self.pos = nanopos_client
        self.attocube = attocube_client

        self.log = LogHandler(logger=log_client)
        self.gui = Window(gui_template=gui, host=get_ip(), port=port)
        self.gui.apply_stylesheet()

        self.widgets = get_gui_widgets(self.gui, **self.WIDGET_DICT)
        self.save_params = generate_widgets(
            dict(n_steps=self.NUM_CHANNELS,
                 amplitude=self.NUM_CHANNELS,
                 frequency=self.NUM_CHANNELS,
                 velocity=self.NUM_CHANNELS))

        # Additional attributes
        self.prev_amplitude = [50] * self.NUM_CHANNELS
        self.prev_frequency = [30] * self.NUM_CHANNELS
        self.prev_velocity = [100] * self.NUM_CHANNELS
        self.prev_voltage = [50] * self.NUM_CHANNELS
        self.voltage_override = False
        self.config = config
        self.lock_status = [False] * int(self.NUM_CHANNELS / 3)
        self.released = [False] * self.NUM_CHANNELS
        self.gui.config_label.setText(self.config)

        # Configure all button and parameter updates
        self._setup_gui()

        # Setup shortcut to use keyboard to step fiber
        self.press_right = QShortcut(QKeySequence('Right'), self.gui)
        self.press_left = QShortcut(QKeySequence('Left'), self.gui)
        self.press_up = QShortcut(QKeySequence('Up'), self.gui)
        self.press_down = QShortcut(QKeySequence('Down'), self.gui)
        self.press_up_z = QShortcut(QKeySequence('PgUp'), self.gui)
        self.press_down_z = QShortcut(QKeySequence('PgDown'), self.gui)

        self.widgets['keyboard_change_combo'].currentIndexChanged.connect(
            self._bind_arrow_keys)
Beispiel #3
0
    def __init__(self,
                 logger=None,
                 client_tuples=None,
                 config=None,
                 config_name=None):

        self.log = LogHandler(logger)
        self.dataset = None

        # Instantiate GUI window
        self.gui = Window(gui_template='data_taker', host=get_ip())

        # Configure list of experiments
        self.gui.config.setText(config_name)
        self.config = config
        self.exp_path = self.config['exp_path']
        if self.exp_path is None:
            self.exp_path = os.getcwd()
        sys.path.insert(1, self.exp_path)
        self.update_experiment_list()

        # Configure list of clients
        self.clients = {}

        # Retrieve Clients
        for client_entry in self.config['servers']:
            client_type = client_entry['type']
            client_config = client_entry['config']
            client = find_client(clients=client_tuples,
                                 settings=client_config,
                                 client_type=client_type,
                                 client_config=client_config,
                                 logger=self.log)
            self.clients[f"{client_type}_{client_config}"] = client

        for client_name, client_obj in self.clients.items():
            client_item = QtWidgets.QListWidgetItem(client_name)
            client_item.setToolTip(str(client_obj))
            self.gui.clients.addItem(client_item)

        # Configure dataset menu
        for name, obj in inspect.getmembers(datasets):
            if inspect.isclass(obj) and issubclass(obj, datasets.Dataset):
                self.gui.dataset.addItem(name)

        # Configure button clicks
        self.gui.configure.clicked.connect(self.configure)
        self.gui.run.clicked.connect(self.run)
        self.gui.save.clicked.connect(self.save)
        self.gui.load_config.clicked.connect(self.reload_config)
        self.gui.showMaximized()
        self.gui.apply_stylesheet()
    def __init__(self,
                 filterwheel1,
                 filterwheel2,
                 gui='toptica_filterwheels',
                 logger=None,
                 port=None):

        self.filterwheel1 = filterwheel1
        self.filterwheel2 = filterwheel2

        self.log = LogHandler(logger)

        # Setup GUI
        self.gui = Window(gui_template=gui, host=get_ip(), port=port)

        # Get Widgets
        self.widgets = get_gui_widgets(self.gui,
                                       comboBox_filter1=1,
                                       comboBox_filter2=1,
                                       nd_label=1)

        # Retrieve filter dicts.
        filters1 = filterwheel1.get_filter_dict()
        filters2 = filterwheel2.get_filter_dict()

        # Fill comboboxes.
        self.widgets['comboBox_filter1'].addItems(filters1.values())
        self.widgets['comboBox_filter2'].addItems(filters2.values())

        # Get current fitler positions
        self.current_pos_1 = filterwheel1.get_pos()
        self.current_pos_2 = filterwheel2.get_pos()

        # Set comboboxes to current filter positions.
        self.widgets['comboBox_filter1'].setCurrentIndex(
            int(self.current_pos_1) - 1)
        self.widgets['comboBox_filter2'].setCurrentIndex(
            int(self.current_pos_2) - 1)

        # Connect change events
        self.widgets['comboBox_filter1'].currentTextChanged.connect(
            lambda: self.change_filter(filter_index=1))
        self.widgets['comboBox_filter2'].currentTextChanged.connect(
            lambda: self.change_filter(filter_index=2))

        # Update OD reading
        self.update_od()

        # Setup stylesheet.
        self.gui.apply_stylesheet()
Beispiel #5
0
    def __init__(self,
                 dlc: toptica_dl_pro.Client,
                 gui='toptica_control',
                 logger=None,
                 port=None):
        """ Initializes toptica specific parameters

        :param dlc: DLC client for the Toptica laser
        :param gui: .ui file to use
        :param logger: LogClient for logging purposes
        :param port: port number of script server
        """

        self.log = LogHandler(logger)

        # Setup GUI
        self.gui = Window(gui_template=gui, host=get_ip(), port=port)
        self.widgets = get_gui_widgets(gui=self.gui,
                                       on_off=2,
                                       temperature=2,
                                       temperature_actual=2,
                                       current=2,
                                       current_actual=2,
                                       offset=2,
                                       amplitude=2,
                                       frequency=2,
                                       scan=2,
                                       update_temp=2,
                                       update_current=2,
                                       update_params=1)

        self.dlc = dlc
        self.offset = 65
        self.amplitude = 100
        self.scan = [False, False]
        self.emission = [False, False]

        # Setup stylesheet.
        self.gui.apply_stylesheet()

        self._setup_GUI()
Beispiel #6
0
    def __init__(self,
                 pm_client,
                 gui='fiber_coupling',
                 logger=None,
                 calibration=None,
                 name=None,
                 port=None):
        """ Instantiates a monitor for 2-ch power meter with GUI

        :param pm_clients: (client, list of clients) clients of power meter
        :param gui_client: client of monitor GUI
        :param logger: instance of LogClient
        :calibration: (float) Calibration value for power meter.
        :name: (str) Humand-readable name of the power meter.
        """

        self.log = LogHandler(logger)
        self.gui = Window(gui_template=gui, host=get_ip(), port=port)

        self.gui.apply_stylesheet()
        self.wavelength = []
        self.calibration = calibration
        self.name = name
        self.ir_index, self.rr_index = [], []
        self.pm = pm_client
        self.running = False
        self.num_plots = 3

        # Get all GUI widgets
        self.widgets = get_gui_widgets(self.gui,
                                       graph_widget=self.num_plots,
                                       number_widget=4,
                                       label_widget=2,
                                       name_label=1,
                                       combo_widget=2)

        self._initialize_gui()
class TopticaFilterWheelController:
    """ Class for controlling Toptica scan and laser properties """
    def __init__(self,
                 filterwheel1,
                 filterwheel2,
                 gui='toptica_filterwheels',
                 logger=None,
                 port=None):

        self.filterwheel1 = filterwheel1
        self.filterwheel2 = filterwheel2

        self.log = LogHandler(logger)

        # Setup GUI
        self.gui = Window(gui_template=gui, host=get_ip(), port=port)

        # Get Widgets
        self.widgets = get_gui_widgets(self.gui,
                                       comboBox_filter1=1,
                                       comboBox_filter2=1,
                                       nd_label=1)

        # Retrieve filter dicts.
        filters1 = filterwheel1.get_filter_dict()
        filters2 = filterwheel2.get_filter_dict()

        # Fill comboboxes.
        self.widgets['comboBox_filter1'].addItems(filters1.values())
        self.widgets['comboBox_filter2'].addItems(filters2.values())

        # Get current fitler positions
        self.current_pos_1 = filterwheel1.get_pos()
        self.current_pos_2 = filterwheel2.get_pos()

        # Set comboboxes to current filter positions.
        self.widgets['comboBox_filter1'].setCurrentIndex(
            int(self.current_pos_1) - 1)
        self.widgets['comboBox_filter2'].setCurrentIndex(
            int(self.current_pos_2) - 1)

        # Connect change events
        self.widgets['comboBox_filter1'].currentTextChanged.connect(
            lambda: self.change_filter(filter_index=1))
        self.widgets['comboBox_filter2'].currentTextChanged.connect(
            lambda: self.change_filter(filter_index=2))

        # Update OD reading
        self.update_od()

        # Setup stylesheet.
        self.gui.apply_stylesheet()

    def change_filter(self, filter_index):
        ''' Read in index from combobox and change filter accordingly.'''
        # Read in new filter.
        new_pos = int(
            self.widgets[f'comboBox_filter{filter_index}'].currentIndex()) + 1

        if filter_index == 1:
            filterwheel = self.filterwheel1
        elif filter_index == 2:
            filterwheel = self.filterwheel2

        # Disable comboboxes
        self.widgets['comboBox_filter1'].setEnabled(False)
        self.widgets['comboBox_filter2'].setEnabled(False)

        # Change position
        successful_changed = filterwheel.change_filter(new_pos)

        if not successful_changed:
            # Read in new position to verify
            changed_position = filterwheel.get_pos()

            # Set combobox to new position
            self.widgets[f'comboBox_filter{filter_index}'].setCurrentIndex(
                int(changed_position) - 1)

        # Update OD reading
        self.update_od()

        # Enable combobos
        self.widgets['comboBox_filter1'].setEnabled(True)
        self.widgets['comboBox_filter2'].setEnabled(True)

    def update_od(self):

        filter_pos1 = int(self.widgets['comboBox_filter1'].currentIndex()) + 1
        filter_string1 = self.filterwheel1.get_filter_dict()[str(filter_pos1)]
        filter1_od = float(filter_string1.replace(' OD', ""))

        filter_pos2 = int(self.widgets['comboBox_filter2'].currentIndex()) + 1
        filter_string2 = self.filterwheel2.get_filter_dict()[str(filter_pos2)]
        filter2_od = float(filter_string2.replace(' OD', ""))

        new_od_string = f"{filter1_od+filter2_od} OD"

        self.widgets['nd_label'].setText(new_od_string)

    def run(self, check_vals=False):
        """ Runs an iteration of checks for updates and implements
Beispiel #8
0
class CountMonitor:

    # Generate all widget instances for the .ui to use
    # _plot_widgets, _legend_widgets, _number_widgets = generate_widgets()

    def __init__(self,
                 ctr_client: si_tt.Client,
                 ui='count_monitor',
                 logger_client=None,
                 server_port=None,
                 combined_channel=False,
                 config=None):
        """ Constructor for CountMonitor script

        :param ctr_client: instance of hardware client for counter
        :param gui_client: (optional) instance of client of desired output GUI
        :param logger_client: (obj) instance of logger client.
        :param server_port: (int) port number of script server
        :combined_channel: (bool) If true, show additional trace with summed counts.
        """

        self._ctr = ctr_client
        self.log = logger_client
        self.combined_channel = combined_channel
        self._bin_width = None
        self._n_bins = None
        self._ch_list = None
        self._plot_list = None  # List of channels to assign to each plot (e.g. [[1,2], [3,4]])
        self._plots_assigned = [
        ]  # List of plots on the GUI that have been assigned

        if self.combined_channel:
            ui = 'count_monitor_combined'
        else:
            ui = 'count_monitor'

        # Instantiate GUI window
        self.gui = Window(gui_template=ui, host=get_ip(), port=server_port)

        # Setup stylesheet.
        self.gui.apply_stylesheet()

        if self.combined_channel:
            num_plots = 3
        else:
            num_plots = 2

        # Get all GUI widgets
        self.widgets = get_gui_widgets(self.gui,
                                       graph_widget=num_plots,
                                       number_label=8,
                                       event_button=num_plots,
                                       legend_widget=num_plots)

        # Load config
        self.config = {}
        if config is not None:
            self.config = load_script_config(script='monitor_counts',
                                             config=config,
                                             logger=self.logger_client)

        if not 'name' in self.config:
            self.config.update({'name': f'monitor{np.random.randint(1000)}'})

    def set_hardware(self, ctr):
        """ Sets hardware client for this script

        :param ctr: instance of count monitor hardware client
        """

        # Initialize counter instance
        self._ctr = ctr

    def set_params(self,
                   bin_width=1e9,
                   n_bins=1e4,
                   ch_list=[1],
                   plot_list=None):
        """ Sets counter parameters

        :param bin_width: bin width in ps
        :param n_bins: number of bins to display on graph
        :param ch_list: (list) channels to record
        :param plot_list: list of channels to assign to each plot (e.g. [[1,2], [3,4]])
        """

        # Save params to internal variables
        self._bin_width = int(bin_width)
        self._n_bins = int(n_bins)
        self._ch_list = ch_list
        self._plot_list = plot_list

    def run(self):
        """ Runs the counter from scratch"""

        try:

            # Start the counter with desired parameters
            self._initialize_display()

            # Give time to initialize
            # time.sleep(0.05)
            self._is_running = True

            self._ctr.start_trace(name=self.config['name'],
                                  ch_list=self._ch_list,
                                  bin_width=self._bin_width,
                                  n_bins=self._n_bins)

            # Continuously update data until paused
            while self._is_running:
                self._update_output()
                self.gui.force_update()

        except Exception as exc_obj:
            self._is_running = False
            raise exc_obj

    def pause(self):
        """ Pauses the counter"""

        self._is_running = False

    def resume(self):
        """ Resumes the counter.

        To be used to resume after the counter has been paused.
        """

        try:
            self._is_running = True

            # Clear counter and resume plotting
            self._ctr.clear_ctr(name=self.config['name'])
            while self._is_running:
                self._update_output()

        except Exception as exc_obj:
            self._is_running = False
            raise exc_obj

    # Technical methods

    def _initialize_display(self):
        """ Initializes the display (configures all plots) """

        plot_index = 0
        for index in range(len(self.widgets['graph_widget'])):
            # Configure and return legend widgets
            self.widgets['legend_widget'][
                index] = get_legend_from_graphics_view(
                    self.widgets['legend_widget'][index])

        for color, channel in enumerate(self._ch_list):

            # Figure out which plot to assign to
            if self._plot_list is not None:
                for index, channel_set in enumerate(self._plot_list):
                    if channel in channel_set:
                        plot_index = index
                        break

            # If we have not assigned this plot yet, assign it
            # if plot_index not in self._plots_assigned:
            #     self.gui_handler.assign_plot(
            #         plot_widget=self._plot_widgets[plot_index],
            #         plot_label='Counter Monitor {}'.format(plot_index + 1),
            #         legend_widget=self._legend_widgets[plot_index]
            #     )
            #     self._plots_assigned.append(plot_index)

            # Now assign this curve
            # self.gui_handler.assign_curve(
            #     plot_label='Counter Monitor {}'.format(plot_index + 1),
            #     curve_label='Channel {}'.format(channel),
            #     error=True
            # )

            # Create a curve and store the widget in our dictionary
            self.widgets[f'curve_{channel}'] = self.widgets['graph_widget'][
                plot_index].plot(pen=pg.mkPen(
                    color=self.gui.COLOR_LIST[color]))
            self.widgets['legend_widget'][plot_index].addItem(
                self.widgets[f'curve_{channel}'], ' - ' + f'Channel {channel}')

            # Assign scalar
            # self.gui_handler.assign_label(
            #     label_widget=self._number_widgets[channel - 1],
            #     label_label='Channel {}'.format(channel)
            # )

        # Handle button pressing
        from functools import partial

        for plot_index, clear_button in enumerate(
                self.widgets['event_button']):
            clear_button.clicked.connect(
                partial(lambda plot_index: self._clear_plot(plot_index),
                        plot_index=plot_index))

        if self.combined_channel:
            self.widgets['curve_combo'] = self.widgets['graph_widget'][
                index + 1].plot(pen=pg.mkPen(color=self.gui.COLOR_LIST[color +
                                                                       1]))
            self.widgets['legend_widget'][index + 1].addItem(
                self.widgets['curve_combo'], ' - ' + 'Combined Counts')

    def _clear_plot(self, plot_index):
        """ Clears the curves on a particular plot

        :param plot_index: (int) index of plot to clear
        """

        # First, handle case where combined count channel is clears (very ugly).
        if self.combined_channel and plot_index == len(self._plot_list):
            channel = 'combo'
            # Set the curve to constant with last point for all entries
            self.widgets[f'curve_{channel}'].setData(
                np.ones(self._n_bins) *
                self.widgets[f'curve_{channel}'].yData[-1])
        else:
            # Find all curves in this plot
            for channel in self._plot_list[plot_index]:

                # Set the curve to constant with last point for all entries
                self.widgets[f'curve_{channel}'].setData(
                    np.ones(self._n_bins) *
                    self.widgets[f'curve_{channel}'].yData[-1])

        self._ctr.clear_ctr(name=self.config['name'])

    def _update_output(self):
        """ Updates the output to all current values"""

        # Update all active channels
        # x_axis = self._ctr.get_x_axis()/1e12

        counts = self._ctr.get_counts(name=self.config['name'])
        counts_per_sec = counts * (1e12 / self._bin_width)
        # noise = np.sqrt(counts)*(1e12/self._bin_width)
        # plot_index = 0

        summed_counts = np.sum(counts_per_sec, axis=0)

        for index, count_array in enumerate(counts_per_sec):

            # Figure out which plot to assign to
            channel = self._ch_list[index]
            # if self._plot_list is not None:
            #     for index_plot, channel_set in enumerate(self._plot_list):
            #         if channel in channel_set:
            #             plot_index = index_plot
            #             break

            # Update GUI data

            # self.gui_handler.set_curve_data(
            #     data=count_array,
            #     error=noise[index],
            #     plot_label='Counter Monitor {}'.format(plot_index + 1),
            #     curve_label='Channel {}'.format(channel)
            # )
            # self.gui_handler.set_label(
            #     text='{:.4e}'.format(count_array[-1]),
            #     label_label='Channel {}'.format(channel)
            # )

            self.widgets[f'curve_{channel}'].setData(count_array)
            self.widgets[f'number_label'][channel - 1].setText(
                str(count_array[-1]))

        if self.combined_channel:
            self.widgets['curve_combo'].setData(summed_counts)
Beispiel #9
0
    def __init__(self,
                 logger=None,
                 client_tuples=None,
                 config=None,
                 config_name=None):

        self.log = LogHandler(logger)
        self.dataset = None

        # Instantiate GUI window
        self.gui = Window(gui_template='data_taker', host=get_ip())

        # Configure list of experiments
        self.gui.config.setText(config_name)
        self.config = config
        self.exp_path = self.config['exp_path']
        if self.exp_path is None:
            self.exp_path = os.getcwd()
        sys.path.insert(1, self.exp_path)
        self.update_experiment_list()

        # Configure list of clients
        self.clients = {}

        # Configure list of missing clients
        self.missing_clients = {}

        # Setup Autosave
        # First check whether autosave is specified in config file
        if 'auto_save' in self.config:
            if self.config['auto_save']:
                self.gui.autosave.setChecked(True)

        # Retrieve Clients
        for client_entry in self.config['servers']:
            client_type = client_entry['type']
            client_config = client_entry['config']
            client = find_client(clients=client_tuples,
                                 settings=client_config,
                                 client_type=client_type,
                                 client_config=client_config,
                                 logger=self.log)
            if (client == None):
                self.missing_clients[f"{client_type}_{client_config}"] = [
                    client_type, client_config
                ]
            else:
                self.clients[f"{client_type}_{client_config}"] = client

        for client_name, client_obj in self.clients.items():
            client_item = QtWidgets.QListWidgetItem(client_name)
            client_item.setToolTip(str(client_obj))
            self.gui.clients.addItem(client_item)

        for client_name, client_config in self.missing_clients.items():
            client_item = QtWidgets.QListWidgetItem(client_name)
            client_item.setForeground(Qt.gray)
            self.gui.clients.addItem(client_item)
            self.log.error("Datataker missing client: " + client_name)

        # Configure button clicks
        self.gui.configure.clicked.connect(self.configure)
        self.gui.run.clicked.connect(self.run)
        self.gui.save.clicked.connect(self.save)
        self.gui.clearData.clicked.connect(self.clear_data)
        self.gui.load_config.clicked.connect(self.reload_config)
        self.gui.showMaximized()
        self.gui.apply_stylesheet()
Beispiel #10
0
class DataTaker:
    def __init__(self,
                 logger=None,
                 client_tuples=None,
                 config=None,
                 config_name=None):

        self.log = LogHandler(logger)
        self.dataset = None

        # Instantiate GUI window
        self.gui = Window(gui_template='data_taker', host=get_ip())

        # Configure list of experiments
        self.gui.config.setText(config_name)
        self.config = config
        self.exp_path = self.config['exp_path']
        if self.exp_path is None:
            self.exp_path = os.getcwd()
        sys.path.insert(1, self.exp_path)
        self.update_experiment_list()

        # Configure list of clients
        self.clients = {}

        # Configure list of missing clients
        self.missing_clients = {}

        # Setup Autosave
        # First check whether autosave is specified in config file
        if 'auto_save' in self.config:
            if self.config['auto_save']:
                self.gui.autosave.setChecked(True)

        # Retrieve Clients
        for client_entry in self.config['servers']:
            client_type = client_entry['type']
            client_config = client_entry['config']
            client = find_client(clients=client_tuples,
                                 settings=client_config,
                                 client_type=client_type,
                                 client_config=client_config,
                                 logger=self.log)
            if (client == None):
                self.missing_clients[f"{client_type}_{client_config}"] = [
                    client_type, client_config
                ]
            else:
                self.clients[f"{client_type}_{client_config}"] = client

        for client_name, client_obj in self.clients.items():
            client_item = QtWidgets.QListWidgetItem(client_name)
            client_item.setToolTip(str(client_obj))
            self.gui.clients.addItem(client_item)

        for client_name, client_config in self.missing_clients.items():
            client_item = QtWidgets.QListWidgetItem(client_name)
            client_item.setForeground(Qt.gray)
            self.gui.clients.addItem(client_item)
            self.log.error("Datataker missing client: " + client_name)

        # Configure button clicks
        self.gui.configure.clicked.connect(self.configure)
        self.gui.run.clicked.connect(self.run)
        self.gui.save.clicked.connect(self.save)
        self.gui.clearData.clicked.connect(self.clear_data)
        self.gui.load_config.clicked.connect(self.reload_config)
        self.gui.showMaximized()
        self.gui.apply_stylesheet()

    def update_experiment_list(self):
        """ Updates list of experiments """

        self.gui.exp.clear()
        for filename in os.listdir(self.exp_path):
            if filename.endswith('.py'):
                self.gui.exp.addItem(filename[:-3])
        self.gui.exp.itemClicked.connect(self.display_experiment)

    def display_experiment(self, item):
        """ Displays the currently clicked experiment in the text browser

        :param item: (QlistWidgetItem) with label of name of experiment to display
        """

        with open(os.path.join(self.exp_path, f'{item.text()}.py'),
                  'r') as exp_file:
            exp_content = exp_file.read()

        self.gui.exp_preview.setText(exp_content)
        self.gui.exp_preview.setStyleSheet('font: 10pt "Consolas"; '
                                           'color: rgb(255, 255, 255); '
                                           'background-color: rgb(0, 0, 0);')
        self.log.update_metadata(experiment_file=exp_content)

    def configure(self):
        """ Configures the currently selected experiment + dataset """

        # If the experiment is running, do nothing
        try:
            if self.experiment_thread.isRunning():
                self.log.warn('Did not configure experiment, since it '
                              'is still in progress')
                return
        except:
            pass

        # Load the config
        self.reload_config()

        # Set all experiments to normal state and highlight configured expt
        for item_no in range(self.gui.exp.count()):
            self.gui.exp.item(item_no).setBackground(
                QtGui.QBrush(QtGui.QColor('black')))
        self.gui.exp.currentItem().setBackground(
            QtGui.QBrush(QtGui.QColor('darkRed')))
        exp_name = self.gui.exp.currentItem().text()
        self.module = importlib.import_module(exp_name)
        self.module = importlib.reload(self.module)

        # Clear graph area and set up new or cleaned up dataset
        for index in reversed(range(self.gui.graph_layout.count())):
            try:
                self.gui.graph_layout.itemAt(index).widget().deleteLater()
            except AttributeError:
                try:
                    self.gui.graph_layout.itemAt(index).layout().deleteLater()
                except AttributeError:
                    pass
        self.gui.windows = {}
        # If we're not setting up a new measurement type, just clear the data

        # We are reading in the required base-dataset by looking at the define_dataset() as defined in the experiment script.
        try:
            classname = self.module.define_dataset()
        except AttributeError:
            error_msg = "No 'define_dataset' method found in experiment script."
            self.log.error(
                "No 'define_dataset' method found in experiment script.")
            return

        try:
            self.dataset = getattr(datasets, classname)(gui=self.gui,
                                                        log=self.log,
                                                        config=self.config)
        except AttributeError:
            error_msg = f"Dataset name {classname} as provided in 'define_dataset' method in experiment script is not valid."
            self.log.error(error_msg)
            return

        # Run any pre-experiment configuration
        try:
            self.module.configure(dataset=self.dataset, **self.clients)
        except AttributeError:
            pass
        self.experiment = self.module.experiment

        self.log.info(f'Experiment {exp_name} configured')
        self.gui.exp_preview.setStyleSheet(
            'font: 10pt "Consolas"; '
            'color: rgb(255, 255, 255); '
            'background-color: rgb(50, 50, 50);')

    def clear_data(self):
        """ Clears all data from curves"""
        self.log.info("Clearing data")
        self.dataset.clear_all_data()

    def run(self):
        """ Runs/stops the experiment """

        # Run experiment
        if self.gui.run.text() == 'Run':
            self.gui.run.setStyleSheet('background-color: red')
            self.gui.run.setText('Stop')
            self.log.info('Experiment started')

            # Run update thread
            self.update_thread = UpdateThread(
                autosave=self.gui.autosave.isChecked(),
                save_time=self.gui.autosave_interval.value())
            self.update_thread.data_updated.connect(self.dataset.update)
            self.update_thread.save_flag.connect(self.save)
            self.gui.autosave.toggled.connect(
                self.update_thread.update_autosave)
            self.gui.autosave_interval.valueChanged.connect(
                self.update_thread.update_autosave_interval)
            '''
            # Step 2: Create a QThread object
            self.thread = QThread()
            # Step 3: Create a worker object
            self.worker = Worker()
            # Step 4: Move worker to the thread
            self.worker.moveToThread(self.thread)
            # Step 5: Connect signals and slots
            self.thread.started.connect(self.worker.run)
            self.worker.finished.connect(self.thread.quit)
            self.worker.finished.connect(self.worker.deleteLater)
            self.thread.finished.connect(self.thread.deleteLater)
            self.worker.progress.connect(self.reportProgress)
            # Step 6: Start the thread
            self.thread.start()

            # Final resets
            self.longRunningBtn.setEnabled(False)
            self.thread.finished.connect(
                lambda: self.longRunningBtn.setEnabled(True)
            )
            self.thread.finished.connect(
                lambda: self.stepLabel.setText("Long-Running Step: 0")
            )
            '''

            self.experiment_thread = ExperimentThread(self.experiment,
                                                      dataset=self.dataset,
                                                      gui=self.gui,
                                                      **self.clients)

            self.experiment_thread.status_flag.connect(
                self.dataset.interpret_status)
            self.experiment_thread.finished.connect(self.stop)
            self.log.update_metadata(
                exp_start_time=datetime.now().strftime('%d/%m/%Y %H:%M:%S:%f'))

        # Stop experiment
        else:
            self.experiment_thread.running = False

    def stop(self):
        """ Stops the experiment"""

        self.gui.run.setStyleSheet('background-color: green')
        self.gui.run.setText('Run')
        self.log.info('Experiment stopped')
        self.update_thread.running = False

        self.log.update_metadata(
            exp_stop_time=datetime.now().strftime('%d/%m/%Y %H:%M:%S:%f'))

        # Autosave if relevant
        if self.gui.autosave.isChecked():
            self.save()

    def save(self):
        """ Saves data """

        self.log.update_metadata(notes=self.gui.notes.toPlainText())
        filename = self.gui.save_name.text()
        directory = self.config['save_path']
        self.dataset.save(filename=filename,
                          directory=directory,
                          date_dir=True)
        save_metadata(self.log, filename, directory, True)
        self.log.info('Data saved')

    def reload_config(self):
        """ Loads a new config file """

        self.config = load_script_config(script='data_taker',
                                         config=self.gui.config.text(),
                                         logger=self.log)
Beispiel #11
0
class Controller:
    """ A script class for controlling MCS2 positioners + interfacing with GUI"""

    NUM_CHANNELS = 9
    WIDGET_DICT = dict(step_left=NUM_CHANNELS,
                       step_right=NUM_CHANNELS,
                       walk_left=NUM_CHANNELS,
                       walk_right=NUM_CHANNELS,
                       n_steps=NUM_CHANNELS,
                       is_moving=NUM_CHANNELS,
                       amplitude=NUM_CHANNELS,
                       frequency=NUM_CHANNELS,
                       velocity=NUM_CHANNELS,
                       voltage=NUM_CHANNELS,
                       lock_button=int(NUM_CHANNELS / 3),
                       keyboard_change_combo=1)
    DC_TOLERANCE = 0.1
    AXIS_ORDER = [[4, 3, 7], [6, 1, 5], [8, 0, 2]]

    def __init__(self,
                 nanopos_client: smaract_mcs2.Client,
                 gui='positioner_control',
                 log_client=None,
                 config=None,
                 port=None):
        """ Instantiates the controller

        :param nanopos_client: (pylabnet.network.client_server.smaract_mcs2.Client)
        :param gui: name of .ui file ot use
        :param log_client: (pylabnet.utils.logging.logger.LogClient)
        :param config: (str) name of config file, optional
        :param port: (int) port number for update/script server
        """

        self.pos = nanopos_client
        self.log = LogHandler(logger=log_client)
        self.gui = Window(gui_template=gui, host=get_ip(), port=port)
        self.gui.apply_stylesheet()

        self.widgets = get_gui_widgets(self.gui, **self.WIDGET_DICT)
        self.save_params = generate_widgets(
            dict(n_steps=self.NUM_CHANNELS,
                 amplitude=self.NUM_CHANNELS,
                 frequency=self.NUM_CHANNELS,
                 velocity=self.NUM_CHANNELS))

        # Additional attributes
        self.prev_amplitude = [50] * self.NUM_CHANNELS
        self.prev_frequency = [30] * self.NUM_CHANNELS
        self.prev_velocity = [100] * self.NUM_CHANNELS
        self.prev_voltage = [50] * self.NUM_CHANNELS
        self.voltage_override = False
        self.config = config
        self.lock_status = [False] * int(self.NUM_CHANNELS / 3)
        self.released = [False] * self.NUM_CHANNELS
        self.gui.config_label.setText(self.config)

        # Configure all button and parameter updates
        self._setup_gui()

        # Setup shortcut to use keyboard to step fiber
        self.press_right = QShortcut(QKeySequence('Right'), self.gui)
        self.press_left = QShortcut(QKeySequence('Left'), self.gui)
        self.press_up = QShortcut(QKeySequence('Up'), self.gui)
        self.press_down = QShortcut(QKeySequence('Down'), self.gui)
        self.press_up_z = QShortcut(QKeySequence('PgUp'), self.gui)
        self.press_down_z = QShortcut(QKeySequence('PgDown'), self.gui)

        self.widgets['keyboard_change_combo'].currentIndexChanged.connect(
            self._bind_arrow_keys)

    def _disconnect_arrow_keys(self):
        """ Unbinds the arrow, up/down keys from any actions."""
        self.press_right.activated.disconnect()
        self.press_left.activated.disconnect()
        self.press_up.activated.disconnect()
        self.press_down.activated.disconnect()
        self.press_up_z.activated.disconnect()
        self.press_down_z.activated.disconnect()

    def _bind_arrow_keys(self):
        """ Binds arroy keys on keyboard to step around front fiber."""

        try:
            self._disconnect_arrow_keys()
        except TypeError:
            self.log.info('Initial call of arrowkey binding.')

        binding_index = self.widgets['keyboard_change_combo'].currentIndex()

        front_names = [
            'step_left', 'step_right', 'step_left', 'step_right', 'step_right',
            'step_left'
        ]
        front_index = [6, 6, 1, 1, 5, 5]

        rear_names = [
            'step_right', 'step_left', 'step_right', 'step_left', 'step_right',
            'step_left'
        ]
        rear_index = [8, 8, 0, 0, 2, 2]

        if binding_index == 0:
            return
        if binding_index == 1:
            names = front_names
            index = front_index
        elif binding_index == 2:
            names = rear_names
            index = rear_index

        self.press_right.activated.connect(
            lambda: self.widgets[names[0]][index[0]].animateClick())
        self.press_left.activated.connect(
            lambda: self.widgets[names[1]][index[1]].animateClick())
        self.press_up.activated.connect(
            lambda: self.widgets[names[2]][index[2]].animateClick())
        self.press_down.activated.connect(
            lambda: self.widgets[names[3]][index[3]].animateClick())
        self.press_up_z.activated.connect(
            lambda: self.widgets[names[4]][index[4]].animateClick())
        self.press_down_z.activated.connect(
            lambda: self.widgets[names[5]][index[5]].animateClick())

    def initialize_parameters(self, channel, params):
        """ Initializes all parameters to values given by params, except for DC voltage

        :param channel: (int) channel index (from 0)
        :param params: (tuple) params in order n_steps, is_moving, amplitude, frequency, velocity,
            voltage
        """

        self.pos.set_parameters(channel, amplitude=params[2])
        self.pos.set_parameters(channel, frequency=params[3])
        self.pos.set_parameters(channel, dc_vel=params[4])

        # Measure DC voltage and set it in the GUI
        self._set_voltage_display(channel)

    def get_GUI_parameters(self, channel):
        """ Gets the current GUI parameters for a given channel

        :param channel: (int) channel index (from 0)

        :return: (tuple) params in order n_steps, is_moving, amplitude, frequency, velocity, voltage
        """

        return (self.widgets['n_steps'][channel].value(),
                self.widgets['is_moving'][channel].isChecked(),
                self.widgets['amplitude'][channel].value(),
                self.widgets['frequency'][channel].value(),
                self.widgets['velocity'][channel].value(),
                self.widgets['voltage'][channel].value())

    def stop_all(self):
        """ Stops all channels """

        for channel in range(self.NUM_CHANNELS):
            self.pos.stop(channel)

    def load_settings(self):
        """ Loads settings from configuration """
        self.gui.load_gui("mcs2_control",
                          self.gui.config_label.text(),
                          logger=self.log)

    # Technical methods

    def _set_voltage_display(self, channel):
        """ Sets the voltage on the GUI to the current value measured by the controller

        :param channel: (int) channel index (from 0)
        """

        voltage = self.pos.get_voltage(channel)
        self.prev_voltage[channel] = voltage
        self.widgets['voltage'][channel].setValue(voltage)
        self.voltage_override = True

    def save(self):
        """Saves or loads settings if relevant"""

        scalars = []
        for save_item in self.save_params:
            scalars += save_item
        self.gui.save_gui(self.gui.get_text('config_label'),
                          logger=self.log,
                          scalars=scalars)

    def _lock_stack(self, stack: int):
        """ Locks/unlocks a particular stack"""

        if self.lock_status[stack]:
            self.lock_status[stack] = False
            self.widgets['lock_button'][stack].setStyleSheet(
                'background-color:rgb(100,0,0)')
            self.widgets['lock_button'][stack].setText('Lock')
        else:
            self.lock_status[stack] = True
            self.widgets['lock_button'][stack].setStyleSheet(
                'background-color:rgb(0,100,0)')
            self.widgets['lock_button'][stack].setText('Unlock')

    def _is_axis_locked(self, channel: int):
        """ Checks if an axis is locked and returns boolean

        :param channel: (int) axis to check
        :return: (bool) whether or not the axis is currently locked
        """

        for index, ordering in enumerate(self.AXIS_ORDER):
            if channel in ordering:
                locked = self.lock_status[index]

        return locked

    def _step_left(self, channel: int):
        """ Steps a particular channel if unlocked

        :param channel: (int) channel to step
        """

        if not self._is_axis_locked(channel):

            self.pos.n_steps(channel=channel,
                             n=-self.widgets['n_steps'][channel].value())
            self.widgets['step_left'][channel].setStyleSheet(
                'background-color:red')

            if self.pos.is_moving(channel):

                while self.pos.is_moving(channel):
                    self.widgets['is_moving'][channel].setCheckable(True)
                    self.widgets['is_moving'][channel].setChecked(True)
                    self.gui.force_update()
                self.widgets['is_moving'][channel].setChecked(False)
                self.widgets['is_moving'][channel].setCheckable(False)

            self.widgets['step_left'][channel].setStyleSheet(
                'background-color:black')

            # Ugly hack to set DC voltage of Front X to 0
            self.log.info(f"Channel {channel}")
            if channel in BROKEN_CHANNELS:
                self._update_voltage(channel, 0)

            self._set_voltage_display(channel)
        else:
            self.log.info("LOCKED")

    def _step_right(self, channel: int):
        """ Steps a particular channel if unlocked

        :param channel: (int) channel to step
        """

        if not self._is_axis_locked(channel):

            self.pos.n_steps(channel=channel,
                             n=self.widgets['n_steps'][channel].value())
            self.widgets['step_right'][channel].setStyleSheet(
                'background-color:red')

            if self.pos.is_moving(channel):

                while self.pos.is_moving(channel):
                    self.widgets['is_moving'][channel].setCheckable(True)
                    self.widgets['is_moving'][channel].setChecked(True)
                    self.gui.force_update()
                self.widgets['is_moving'][channel].setChecked(False)
                self.widgets['is_moving'][channel].setCheckable(False)

            self.widgets['step_right'][channel].setStyleSheet(
                'background-color:black')

            # Ugly hack to set DC voltage of Front X to 0
            if channel in BROKEN_CHANNELS:
                self._update_voltage(channel, 0)

            self._set_voltage_display(channel)

    def _walk_left(self, channel: int):

        if not self._is_axis_locked(channel):

            if not self.pos.is_moving(channel):
                self.widgets['walk_left'][channel].setStyleSheet(
                    'background-color:red')
                self.pos.move(channel, backward=True)

                while self.pos.is_moving(channel):
                    self.widgets['is_moving'][channel].setCheckable(True)
                    self.widgets['is_moving'][channel].setChecked(True)
                    self.gui.force_update()
                self.widgets['is_moving'][channel].setChecked(False)
                self.widgets['is_moving'][channel].setCheckable(False)

                if not self.widgets['walk_left'][channel].isDown():
                    self.widgets['walk_left'][channel].setStyleSheet(
                        'background-color:black')

                    self._set_voltage_display(channel)

    def _walk_right(self, channel: int):

        if not self._is_axis_locked(channel):

            if not self.pos.is_moving(channel):
                self.widgets['walk_right'][channel].setStyleSheet(
                    'background-color:red')
                self.pos.move(channel, backward=False)

                while self.pos.is_moving(channel):
                    self.widgets['is_moving'][channel].setCheckable(True)
                    self.widgets['is_moving'][channel].setChecked(True)
                    self.gui.force_update()
                self.widgets['is_moving'][channel].setChecked(False)
                self.widgets['is_moving'][channel].setCheckable(False)

                if not self.widgets['walk_right'][channel].isDown():
                    self.widgets['walk_right'][channel].setStyleSheet(
                        'background-color:black')

                    self._set_voltage_display(channel)

    def _update_voltage(self, channel: int, voltage: float):
        """ Updates the channels DC voltage

        :param channel: (int) channel to update
        :param voltage: (float) value of voltage to update ot
        """

        # If locked, get the current voltage and reset the GUI value to it
        if self._is_axis_locked(channel):
            self.widgets['voltage'][channel].setValue(
                self.pos.get_voltage(channel))

        # Otherwise set the DC voltage
        else:
            self.pos.set_voltage(channel, voltage)

            if self.pos.is_moving(channel):
                while self.pos.is_moving(channel):
                    self.widgets['is_moving'][channel].setCheckable(True)
                    self.widgets['is_moving'][channel].setChecked(True)
                    self.gui.force_update()
                self.widgets['is_moving'][channel].setChecked(False)
                self.widgets['is_moving'][channel].setCheckable(False)

    def _setup_gui(self):
        """ Configures what all buttons do """

        self.gui.load_button.clicked.connect(self.load_settings)
        self.gui.save_button.clicked.connect(self.save)
        self.gui.emergency_button.clicked.connect(self.stop_all)

        # Stack based items (common to 3 axes)
        for stack in range(int(self.NUM_CHANNELS / 3)):

            stack_no = copy.deepcopy(stack)

            # Lock button
            self.widgets['lock_button'][stack].pressed.connect(
                lambda stack=stack_no: self._lock_stack(stack))

        for channel in range(self.NUM_CHANNELS):

            channel_no = copy.deepcopy(channel)

            # Step buttons
            self.widgets['step_left'][channel_no].pressed.connect(
                lambda channel=channel_no: self._step_left(channel))
            self.widgets['step_right'][channel_no].pressed.connect(
                lambda channel=channel_no: self._step_right(channel))

            # Walk buttons
            self.widgets['walk_left'][channel_no].pressed.connect(
                lambda channel=channel_no: self._walk_left(channel))
            self.widgets['walk_left'][channel_no].released.connect(
                lambda channel=channel_no: self.pos.stop(channel))
            self.widgets['walk_right'][channel_no].pressed.connect(
                lambda channel=channel_no: self._walk_right(channel))
            self.widgets['walk_right'][channel_no].released.connect(
                lambda channel=channel_no: self.pos.stop(channel))

            # Parameters
            self.widgets['voltage'][channel_no].valueChanged.connect(
                lambda state, channel=channel_no: self._update_voltage(
                    channel=channel, voltage=state))
            self.widgets['amplitude'][channel_no].valueChanged.connect(
                lambda state, channel=channel_no: self.pos.set_parameters(
                    channel=channel, amplitude=state))
            self.widgets['frequency'][channel_no].valueChanged.connect(
                lambda state, channel=channel_no: self.pos.set_parameters(
                    channel=channel, frequency=state))
            self.widgets['velocity'][channel_no].valueChanged.connect(
                lambda state, channel=channel_no: self.pos.set_parameters(
                    channel=channel, dc_vel=state))
Beispiel #12
0
class Controller(MultiChSweep1D):
    def __init__(self,
                 logger=None,
                 channels=['Channel 1'],
                 clients={},
                 config=None,
                 fast=True):
        """ Instantiates controller (only 1D data supported so far)

        :param logger: instance of LogClient
        :param channels: (list) list of channel names
        :param config: (str) name of config file
        :param fast: (bool) whether to operate in fast mode
            fast mode only updates heat maps at the end of each scan (this speeds things up)
        """
        self.config = load_script_config('scan1d', config, logger=logger)

        if 'sweep_type' in self.config.keys():
            self.sweep_type = self.config['sweep_type']
        else:
            self.sweep_type = 'triangle'

        super().__init__(logger, channels, sweep_type=self.sweep_type)

        self.module = None

        # Instantiate GUI
        self.gui = Window(gui_template='scan_1d', host=get_ip(), max=True)
        self.widgets = get_gui_widgets(self.gui,
                                       p_min=1,
                                       p_max=1,
                                       pts=1,
                                       config=1,
                                       graph=2,
                                       legend=2,
                                       clients=1,
                                       exp=1,
                                       exp_preview=1,
                                       configure=1,
                                       run=1,
                                       autosave=1,
                                       save_name=1,
                                       save=1,
                                       reps=1,
                                       rep_tracker=1,
                                       avg=2)

        # Configure default parameters
        self.min = self.widgets['p_min'].value()
        self.max = self.widgets['p_max'].value()
        self.pts = self.widgets['pts'].value()
        self.reps = self.widgets['reps'].value()

        self.data_fwd = []
        self.data_fwd_2nd_reading = []  ### ADDED
        self.data_bwd = []
        self.avg_fwd = []
        self.avg_fwd_2nd_reading = []  ### ADDED
        self.avg_bwd = []
        self.fit_popup = None
        self.p0_fwd = None
        self.p0_bwd = None
        self.x_fwd = self._generate_x_axis()
        if self.sweep_type != 'sawtooth':
            self.x_bwd = self._generate_x_axis(backward=True)
        else:
            self.x_bwd = self._generate_x_axis()
        self.fast = fast

        # Configure list of experiments
        self.widgets['config'].setText(config)

        self.exp_path = self.config['exp_path']
        model = QtWidgets.QFileSystemModel()
        model.setRootPath(self.exp_path)
        model.setNameFilterDisables(False)
        model.setNameFilters(['*.py'])

        self.widgets['exp'].setModel(model)
        self.widgets['exp'].setRootIndex(model.index(self.exp_path))
        self.widgets['exp'].hideColumn(1)
        self.widgets['exp'].hideColumn(2)
        self.widgets['exp'].hideColumn(3)
        self.widgets['exp'].clicked.connect(self.display_experiment)

        # Configure list of clients
        self.clients = clients
        for client_entry in self.config['servers']:
            client_type = client_entry['type']
            client_config = client_entry['config']
            client = find_client(clients=self.clients,
                                 settings=client_config,
                                 client_type=client_type,
                                 client_config=client_config,
                                 logger=self.log)
            if (client == None):
                client_name_concat = client_type
                client_item = QtWidgets.QListWidgetItem(client_name_concat)
                client_item.setForeground(Qt.gray)
                client_item.setToolTip(str("Disconnected"))
                self.widgets['clients'].addItem(client_item)

            else:
                self.log.info(client)
                client_name_concat = f"{client_type}_{client_config}"
                client_item = QtWidgets.QListWidgetItem(client_name_concat)
                client_item.setToolTip(str(client))
                self.widgets['clients'].addItem(client_item)

        #Checking for any missing clients, and adding them as greyed out on the list of clients

        # Manually add logger to client
        self.clients['logger'] = logger

        # Configure button
        self.widgets['configure'].clicked.connect(self.configure_experiment)
        self.widgets['run'].clicked.connect(self.run_pressed)
        self.widgets['autosave'].toggled.connect(self._update_autosave)
        self.widgets['save'].pressed.connect(
            lambda: self.save(filename=self.widgets['save_name'].text(),
                              directory=self.config['save_path']))
        self.widgets['reps'].valueChanged.connect(self.set_reps)
        self.widgets['avg'][0].clicked.connect(
            lambda: self._clear_show_trace(0))
        self.widgets['avg'][1].clicked.connect(
            lambda: self._clear_show_trace(1))
        self.gui.fit.clicked.connect(self.fit_config)

        # Create legends
        self.widgets['curve'] = []
        for index in range(len(self.widgets['graph'])):
            self.widgets['legend'][index] = get_legend_from_graphics_view(
                self.widgets['legend'][index])

        # Configure hmaps
        self.widgets['hmap'] = []
        for index in range(2):

            # Configure Hmap to work the way we want
            hmap = pg.ImageView(view=pg.PlotItem())
            self.gui.graph_layout.insertWidget(2 * index + 1, hmap)
            hmap.setPredefinedGradient('inferno')
            hmap.show()
            hmap.view.setAspectLocked(False)
            hmap.view.invertY(False)
            self.widgets['hmap'].append(hmap)

        # Setup stylesheet.
        self.gui.apply_stylesheet()

    def display_experiment(self, index):
        """ Displays the currently clicked experiment in the text browser

        :param index: (QModelIndex) index of QTreeView for selected file
        """

        filepath = self.widgets['exp'].model().filePath(index)
        if not os.path.isdir(filepath):
            with open(filepath, 'r') as exp_file:
                exp_content = exp_file.read()

            self.widgets['exp_preview'].setText(exp_content)
            self.widgets['exp_preview'].setStyleSheet(
                'font: 12pt "Consolas"; '
                'color: rgb(255, 255, 255); '
                'background-color: #19232D;')
            self.cur_path = self.widgets['exp'].model().filePath(
                self.widgets['exp'].currentIndex())
            self.exp_name = os.path.split(os.path.basename(self.cur_path))[0]

    def configure_experiment(self):
        """ Configures experiment to be the currently selected item """

        spec = importlib.util.spec_from_file_location(self.exp_name,
                                                      self.cur_path)
        self.module = importlib.util.module_from_spec(spec)
        spec.loader.exec_module(self.module)

        self.experiment = self.module.experiment
        self.min = self.widgets['p_min'].value()
        self.max = self.widgets['p_max'].value()
        self.pts = self.widgets['pts'].value()

        self.x_fwd = self._generate_x_axis()
        if self.sweep_type != 'sawtooth':
            self.x_bwd = self._generate_x_axis(backward=True)
        else:
            self.x_bwd = self._generate_x_axis()

        # Run any pre-experiment configuration
        try:
            self.module.configure(self)
        except AttributeError:
            pass

        self.log.info(f'Experiment {self.exp_name} configured')
        self.widgets['exp_preview'].setStyleSheet(
            'font: 12pt "Consolas"; '
            'color: rgb(255, 255, 255); '
            'background-color: rgb(50, 50, 50);')

    def run_pressed(self):
        """ Handles button pressing for run and stop """

        if self.widgets['run'].text() == 'Run':
            self.widgets['run'].setStyleSheet('background-color: red')
            self.widgets['run'].setText('Stop')
            self.log.info('Sweep experiment started')
            self.widgets['rep_tracker'].setValue(1)

            # set min and max
            self.min = self.widgets['p_min'].value()
            self.max = self.widgets['p_max'].value()
            self.pts = self.widgets['pts'].value()

            self.run()
            self.widgets['rep_tracker'].setValue(0)
            self.widgets['reps'].setValue(0)
            self.widgets['run'].setStyleSheet('background-color: green')
            self.widgets['run'].setText('Run')
            for button in self.widgets['avg']:
                button.setText('Avg only')
            self.log.info('Sweep experiment stopped')
        else:
            self.widgets['rep_tracker'].setValue(0)
            self.widgets['reps'].setValue(0)
            for button in self.widgets['avg']:
                button.setText('Avg only')
            self.widgets['run'].setStyleSheet('background-color: green')
            self.widgets['run'].setText('Run')
            self.stop()
            self.log.info('Sweep experiment stopped')

    def save(self, filename=None, directory=None, date_dir=True):
        """ Saves the dataset

        :param filename: (str) name of file identifier
        :param directory: (str) filepath to save to
        :param date_dir: (bool) whether or not to store in date-specific sub-directory
        """

        if filename is None:
            filename = self.widgets['save_name'].text()
        if directory is None:
            directory = self.config['save_path']

        # Save heatmap
        generic_save(data=fill_2dlist(self.data_fwd),
                     filename=f'{filename}_fwd_scans',
                     directory=directory,
                     date_dir=date_dir)
        pyqtgraph_save(widget=self.widgets['hmap'][0].getView(),
                       filename=f'{filename}_fwd_scans',
                       directory=directory,
                       date_dir=date_dir)

        # Save average
        generic_save(data=np.vstack((self.x_fwd, np.array([self.avg_fwd]))),
                     filename=f'{filename}_fwd_avg',
                     directory=directory,
                     date_dir=date_dir)
        pyqtgraph_save(widget=self.widgets['graph'][0].getPlotItem(),
                       filename=f'{filename}_fwd_avg',
                       directory=directory,
                       date_dir=date_dir)

        if self.sweep_type != 'sawtooth':

            # Save heatmap
            generic_save(data=fill_2dlist(self.data_bwd),
                         filename=f'{filename}_bwd_scans',
                         directory=directory,
                         date_dir=date_dir)
            pyqtgraph_save(widget=self.widgets['hmap'][1].getView(),
                           filename=f'{filename}_bwd_scans',
                           directory=directory,
                           date_dir=date_dir)
            # Save average
            generic_save(data=np.vstack(
                (self.x_bwd, np.array([self.avg_bwd]))),
                         filename=f'{filename}_bwd_avg',
                         directory=directory,
                         date_dir=date_dir)
            pyqtgraph_save(widget=self.widgets['graph'][1].getPlotItem(),
                           filename=f'{filename}_bwd_avg',
                           directory=directory,
                           date_dir=date_dir)
        '''else:

            if len(self.data_bwd) > 0:
                # Save heatmap
                generic_save(
                    data=fill_2dlist(self.data_bwd),
                    filename=f'{filename}_bwd_scans',
                    directory=directory,
                    date_dir=date_dir
                )
                pyqtgraph_save(
                    widget=self.widgets['hmap'][1].getView(),
                    filename=f'{filename}_bwd_scans',
                    directory=directory,
                    date_dir=date_dir
                )
                # Save average
                generic_save(
                    data = np.vstack((self.x_bwd, np.array([self.avg_bwd]))),
                    filename=f'{filename}_bwd_avg',
                    directory=directory,
                    date_dir=date_dir
                )
                pyqtgraph_save(
                    widget=self.widgets['graph'][1].getPlotItem(),
                    filename=f'{filename}_bwd_avg',
                    directory=directory,
                    date_dir=date_dir
                )'''

    def fit_config(self, status: bool):
        """ Configures fitting add-on

        :param status: (bool) whether or not fit button is checked
        """

        # If box is newly checked, instantiate popup
        if status:
            self.fit_popup = FitPopup(ui='fit_popup',
                                      x_fwd=self.x_fwd,
                                      data_fwd=self.avg_fwd,
                                      x_bwd=self.x_bwd,
                                      data_bwd=self.avg_bwd,
                                      p0_fwd=None,
                                      p0_bwd=None,
                                      config=self.config,
                                      log=self.log)
            self.fit_popup.model_type.activated.connect(
                self.fit_popup.fit_selection)
            '''
            if len(self.avg_fwd) != 0:
                    self.fit_popup = FitPopup(ui='fit_popup', data = self.avg_fwd, log=self.log)
                    self.fit_popup.model_type.activated.connect(self.fit_popup.fit_selection)
            else:
                self.fit_error = Popup(ui = 'fit_error')
            '''
        # If box isn't checked, remove popup
        else:
            self.fit_popup = None

    def _configure_plots(self, plot=True):
        """ Configures the plots """

        # Clear plots
        if len(self.widgets['curve']) > 0:
            # self.widgets['curve'][0].clear()
            # self.widgets['curve'][1].clear()
            self.widgets['graph'][0].getPlotItem().clear()
            self.widgets['graph'][1].getPlotItem().clear()
            self.widgets['hmap'][0].clear()
            self.widgets['hmap'][1].clear()
            self.widgets['legend'][0].clear()
            self.widgets['legend'][1].clear()
            self.widgets['curve_avg'][0].clear()
            self.widgets['curve_avg'][1].clear()
            self.widgets['fit_avg'][0].clear()
            self.widgets['fit_avg'][1].clear()
            self.data_fwd = []
            self.data_bwd = []
            self.avg_fwd = []
            self.avg_bwd = []
            self.fit_fwd = []
            self.fit_bwd = []
            self.p0_fwd = None
            self.p0_bwd = None
            self.x_fwd = self._generate_x_axis()
            if self.sweep_type != 'sawtooth':
                self.x_bwd = self._generate_x_axis(backward=True)
            else:
                self.x_bwd = self._generate_x_axis()

        self.widgets['curve'] = []
        self.widgets['curve_avg'] = []
        self.widgets['fit_avg'] = []

        for index, graph in enumerate(self.widgets['graph']):

            if self.sweep_type != 'sawtooth':
                self.widgets['curve'].append(
                    graph.plot(pen=pg.mkPen(color=self.gui.COLOR_LIST[6])))
                add_to_legend(self.widgets['legend'][index],
                              self.widgets['curve'][index],
                              f'{"Fwd" if index==0 else "Bwd"} trace')

                self.widgets['curve_avg'].append(
                    graph.plot(pen=pg.mkPen(color=self.gui.COLOR_LIST[0])))
                add_to_legend(self.widgets['legend'][index],
                              self.widgets['curve_avg'][index],
                              f'{"Fwd" if index==0 else "Bwd"} avg')

                self.widgets['fit_avg'].append(
                    graph.plot(pen=pg.mkPen(color=self.gui.COLOR_LIST[1])))
                add_to_legend(self.widgets['legend'][index],
                              self.widgets['fit_avg'][index],
                              f'{"Fwd" if index==0 else "Bwd"} fit avg')
            else:
                self.widgets['curve'].append(
                    graph.plot(pen=pg.mkPen(color=self.gui.COLOR_LIST[6])))
                add_to_legend(self.widgets['legend'][index],
                              self.widgets['curve'][index],
                              f'{"1st" if index==0 else "2nd"} trace')

                self.widgets['curve_avg'].append(
                    graph.plot(pen=pg.mkPen(color=self.gui.COLOR_LIST[0])))
                add_to_legend(self.widgets['legend'][index],
                              self.widgets['curve_avg'][index],
                              f'{"1st" if index==0 else "2nd"} avg')

                self.widgets['fit_avg'].append(
                    graph.plot(pen=pg.mkPen(color=self.gui.COLOR_LIST[1])))
                add_to_legend(self.widgets['legend'][index],
                              self.widgets['fit_avg'][index],
                              f'{"1st" if index==0 else "2nd"} fit avg')

        for hmap in self.widgets['hmap']:
            hmap.view.setLimits(xMin=self.min, xMax=self.max)

    def _reset_plots(self):
        """ Resets things after a rep """
        self.data_bwd.append([])
        self.data_fwd.append([])

    def _run_and_plot(self, x_value, backward=False):

        if self.sweep_type != 'sawtooth':
            if backward:

                # Single trace
                self.data_bwd[-1].append(
                    self.experiment(x_value, self, gui=self.gui))
                cur_ind = len(self.data_bwd[-1])
                self.widgets['curve'][1].setData(self.x_bwd[:cur_ind],
                                                 self.data_bwd[-1])

                # Update average and plot
                try:
                    cur_rep = len(self.data_bwd)
                    self.avg_bwd[cur_ind - 1] = (
                        (cur_rep - 1) * self.avg_bwd[cur_ind - 1] +
                        self.data_bwd[-1][-1]) / cur_rep
                    self.widgets['curve_avg'][1].setData(
                        self.x_bwd, self.avg_bwd)

                # If it is the first run, just add the data
                except IndexError:
                    self.avg_bwd.append(self.data_bwd[-1][-1])

                # Heat map
                if not self.fast:
                    self.widgets['hmap'][1].setImage(
                        img=np.transpose(np.fliplr(fill_2dlist(
                            self.data_bwd))),
                        pos=(self.min, 0),
                        scale=((self.max - self.min) / self.pts, 1),
                        autoRange=False)
            else:

                self.data_fwd[-1].append(
                    self.experiment(x_value, self, gui=self.gui))
                cur_ind = len(self.data_fwd[-1])
                self.widgets['curve'][0].setData(self.x_fwd[:cur_ind],
                                                 self.data_fwd[-1])

                # Update average and plot
                try:
                    cur_rep = len(self.data_fwd)
                    self.avg_fwd[cur_ind - 1] = (
                        (cur_rep - 1) * self.avg_fwd[cur_ind - 1] +
                        self.data_fwd[-1][-1]) / cur_rep
                    self.widgets['curve_avg'][0].setData(
                        self.x_fwd, self.avg_fwd)

                # If it is the first run, just add the data
                except IndexError:
                    self.avg_fwd.append(self.data_fwd[-1][-1])

                # Heat map
                if not self.fast:
                    self.widgets['hmap'][0].setImage(
                        img=np.transpose(fill_2dlist(self.data_fwd)),
                        pos=(self.min, 0),
                        scale=((self.max - self.min) / self.pts, 1),
                        autoRange=False)

        else:
            reading = self.experiment(x_value, self, gui=self.gui)
            try:
                n_readings = len(reading)
                self.data_fwd[-1].append(reading[0])
                cur_ind = len(self.data_fwd[-1])
                self.widgets['curve'][0].setData(self.x_fwd[:cur_ind],
                                                 self.data_fwd[-1])

                # Update average and plot
                try:
                    cur_rep = len(self.data_fwd)
                    self.avg_fwd[cur_ind - 1] = (
                        (cur_rep - 1) * self.avg_fwd[cur_ind - 1] +
                        self.data_fwd[-1][-1]) / cur_rep
                    self.widgets['curve_avg'][0].setData(
                        self.x_fwd, self.avg_fwd)

                # If it is the first run, just add the data
                except IndexError:
                    self.avg_fwd.append(self.data_fwd[-1][-1])

                # Heat map
                if not self.fast:
                    self.widgets['hmap'][0].setImage(
                        img=np.transpose(fill_2dlist(self.data_fwd)),
                        pos=(self.min, 0),
                        scale=((self.max - self.min) / self.pts, 1),
                        autoRange=False)
                    self.widgets['hmap'][1].setImage(
                        img=np.transpose(fill_2dlist(self.data_bwd)),
                        pos=(self.min, 0),
                        scale=((self.max - self.min) / self.pts, 1),
                        autoRange=False)

                self.data_bwd[-1].append(reading[1])
                cur_ind = len(self.data_bwd[-1])
                self.widgets['curve'][1].setData(self.x_bwd[:cur_ind],
                                                 self.data_bwd[-1])

                # Update average and plot
                try:
                    cur_rep = len(self.data_bwd)
                    self.avg_bwd[cur_ind - 1] = (
                        (cur_rep - 1) * self.avg_bwd[cur_ind - 1] +
                        self.data_bwd[-1][-1]) / cur_rep
                    self.widgets['curve_avg'][1].setData(
                        self.x_bwd, self.avg_bwd)

                # If it is the first run, just add the data
                except IndexError:
                    self.avg_bwd.append(self.data_bwd[-1][-1])

                # Heat map
                if not self.fast:
                    self.widgets['hmap'][1].setImage(
                        img=np.transpose(np.fliplr(fill_2dlist(
                            self.data_bwd))),
                        pos=(self.min, 0),
                        scale=((self.max - self.min) / self.pts, 1),
                        autoRange=False)
            except TypeError:
                self.data_fwd[-1].append(reading)
                cur_ind = len(self.data_fwd[-1])
                self.widgets['curve'][0].setData(self.x_fwd[:cur_ind],
                                                 self.data_fwd[-1])

                # Update average and plot
                try:
                    cur_rep = len(self.data_fwd)
                    self.avg_fwd[cur_ind - 1] = (
                        (cur_rep - 1) * self.avg_fwd[cur_ind - 1] +
                        self.data_fwd[-1][-1]) / cur_rep
                    self.widgets['curve_avg'][0].setData(
                        self.x_fwd, self.avg_fwd)

                # If it is the first run, just add the data
                except IndexError:
                    self.avg_fwd.append(self.data_fwd[-1][-1])

                # Heat map
                if not self.fast:
                    self.widgets['hmap'][0].setImage(
                        img=np.transpose(fill_2dlist(self.data_fwd)),
                        pos=(self.min, 0),
                        scale=((self.max - self.min) / self.pts, 1),
                        autoRange=False)

        self.gui.force_update()

    def _update_hmaps(self, reps_done):
        """ Updates hmap if in fast mode """

        if self.fast:
            if self.sweep_type == 'triangle':
                self.widgets['hmap'][1].setImage(
                    img=np.transpose(np.fliplr(fill_2dlist(self.data_bwd))),
                    pos=(self.min, 0),
                    scale=((self.max - self.min) / self.pts, 1),
                    autoRange=False)
            self.widgets['hmap'][0].setImage(
                img=np.transpose(fill_2dlist(self.data_fwd)),
                pos=(self.min, 0),
                scale=((self.max - self.min) / self.pts, 1),
                autoRange=False)

    def _update_integrated(self, reps_done):
        """ Update repetition counter """

        self.widgets['rep_tracker'].setValue(reps_done + 1)
        self._update_fits()

    def _update_fits(self):
        """ Updates fits """
        if len(self.avg_fwd) != 0 and self.fit_popup is not None:
            if self.fit_popup.mod is not None and\
                    self.fit_popup.mod.init_params is not None:
                self.fit_popup.data_fwd = np.array(self.avg_fwd)
                self.fit_popup.data_bwd = np.array(self.avg_bwd)
                if self.p0_fwd is not None and self.p0_bwd is not None:
                    self.fit_popup.p0_fwd = self.p0_fwd
                    self.fit_popup.p0_bwd = self.p0_bwd
                #method = getattr(self.fit_popup, self.fit_popup.fit_method)
                self.fit_fwd, self.fit_bwd, self.p0_fwd, self.p0_bwd = self.fit_popup.fit_mod(
                )
                if self.fit_popup.fit_suc:
                    self.widgets['fit_avg'][0].setData(self.x_fwd,
                                                       self.fit_fwd)
                    self.widgets['fit_avg'][1].setData(self.x_bwd,
                                                       self.fit_bwd)
                #print(self.avg_fwd)
        else:
            pass
        #print(self.config)

    def _update_autosave(self):
        """ Updates autosave status """

        self.autosave = self.widgets['autosave'].isChecked()

    def _clear_show_trace(self, index):
        """ Clears or shows the single scan trace of a graph

        :param index: (int) index of graph
        """

        # Check status of button
        try:
            if self.widgets['avg'][index].text() == 'Avg only':
                self.widgets['avg'][index].setText('Show trace')
                self.widgets['graph'][index].removeItem(
                    self.widgets['curve'][index])
            else:
                self.widgets['avg'][index].setText('Avg only')
                self.widgets['graph'][index].addItem(
                    self.widgets['curve'][index])
        except KeyError:
            pass
Beispiel #13
0
class WlmMonitor:
    """ A script class for monitoring and locking lasers based on the wavemeter """
    def __init__(self,
                 wlm_client,
                 logger_client,
                 gui='wavemeter_monitor',
                 ao_clients=None,
                 display_pts=5000,
                 threshold=0.0002,
                 port=None,
                 params=None,
                 three_lasers=False):
        """ Instantiates WlmMonitor script object for monitoring wavemeter

        :param wlm_client: (obj) instance of wavemeter client
        :param gui_client: (obj) instance of GUI client.
        :param logger_client: (obj) instance of logger client.
        :param ao_clients: (dict, optional) dictionary of ao client objects with keys to identify. Exmaple:
            {'ni_usb_1': nidaqmx_usb_client_1, 'ni_usb_2': nidaqmx_usb_client_2, 'ni_pxi_multi': nidaqmx_pxi_client}
        :param display_pts: (int, optional) number of points to display on plot
        :param threshold: (float, optional) threshold in THz for lock error signal
        :param port: (int) port number for update server
        :param params: (dict) see set_parameters below for details
        :three_lasers: (bool) If three lasers are in use (instead of 2)
        """
        self.channels = []

        if three_lasers:
            gui = 'wavemeter_monitor_3lasers'

        self.wlm_client = wlm_client
        self.ao_clients = ao_clients
        self.display_pts = display_pts
        self.threshold = threshold
        self.log = LogHandler(logger_client)

        # Instantiate gui
        self.gui = Window(
            gui_template=gui,
            host=get_ip(),
            port=port,
        )

        # Setup stylesheet.
        self.gui.apply_stylesheet()

        if three_lasers:
            self.widgets = get_gui_widgets(gui=self.gui,
                                           freq=3,
                                           sp=3,
                                           rs=3,
                                           lock=3,
                                           error_status=3,
                                           graph=6,
                                           legend=6,
                                           clear=6,
                                           zero=6,
                                           voltage=3,
                                           error=3)
        else:
            self.widgets = get_gui_widgets(gui=self.gui,
                                           freq=2,
                                           sp=2,
                                           rs=2,
                                           lock=2,
                                           error_status=2,
                                           graph=4,
                                           legend=4,
                                           clear=4,
                                           zero=4,
                                           voltage=2,
                                           error=2)

        # Set parameters
        self.set_parameters(**params)

        # Configure plots
        # Get actual legend widgets
        self.widgets['legend'] = [
            get_legend_from_graphics_view(legend)
            for legend in self.widgets['legend']
        ]

        self.widgets['curve'] = []
        self.initialize_channels()

        for channel in self.channels:
            self.update_parameters(
                dict(channel=channel.number, setpoint=channel.data[-1]))

    def set_parameters(self, channel_params):
        """ Instantiates new channel objects with given parameters and assigns them to the WlmMonitor

        Note: parameters for a channel that has already been assigned can be set or updated later using the
        update_parameters() method via an update client in a separate python process.

        :param channel_params: (list) of dictionaries containing all parameters. Example of full parameter set:
            {'channel': 1, 'name': 'Velocity', 'setpoint': 406.7, 'lock':True, 'memory': 20,
             'pid': {'p': 1, 'i': 0.1, 'd': 0}, 'ao': {'client':'nidaqmx_client', 'channel': 'ao1'}}

            In more detail:
            - 'channel': should be from 1-8 for the High-Finesse Wavemeter (with switch) and should ALWAYS be provided,
                as a reference so that we know which channel to assign all the other parameters to
            - 'name': a string that can just be provided once and is used as a user-friendly name for the channel.
                Initializes to 'Channel X' where X is a random integer if not provided
            - 'setpoint': setpoint for this channel.
            - 'lock': boolean that tells us whether or not to turn on the lock. Ignored if setpoint is None. Default is
                False.
            - 'memory': Number of points for integral memory of pid (history of the integral). Default is 20.
            - 'pid': dict containing pid parameters. Uses the pylabnet.scripts.pid module. By default instantiates the
                default PID() object.
            - 'ao': dict containing two elements: 'client' which is a string that is the name of the ao client to use
                for locking. This should match up with a key in self.ao_clients. 'channel'is an identifier for which
                analog output to use for this channel. By default it is set to None and no active locking is performed
        """

        # Check if it is only a single channel
        if type(channel_params) is dict:
            channel_params = [channel_params]

        # Initialize each channel individually
        for channel_param_set in channel_params:
            self.channels.append(
                Channel(channel_param_set, self.ao_clients, log=self.log))

    def update_parameters(self, parameters):
        """ Updates only the parameters given. Can be used in the middle of the script operation via an update client.

        :param parameters: (list) list of dictionaries, see set_parameters() for details
        """

        if not isinstance(parameters, list):
            parameters = [parameters]

        for parameter in parameters:

            # Make sure a channel is given
            if 'channel' in parameter:

                # Check if it is a channel that is already logged
                channel_list = self._get_channels()
                if parameter['channel'] in channel_list:

                    # Find index of the desired channel
                    index = channel_list.index(parameter['channel'])
                    channel = self.channels[channel_list.index(
                        parameter['channel'])]

                    # Set all other parameters for this channel
                    if 'name' in parameter:
                        channel.name = parameter['name']

                    if 'setpoint' in parameter:

                        # self.widgets['sp'][index].setValue(parameter['setpoint'])
                        # channel.setpoint = parameter['setpoint']

                        # Mark that we should override GUI setpoint, since it has been updated by the script
                        channel.setpoint_override = parameter['setpoint']

                    if 'lock' in parameter:

                        self.widgets['lock'][index].setChecked(
                            parameter['lock'])
                        # channel.lock = parameter['lock']

                        # Mark that we should override the GUI lock since it has been updated by the script
                        # channel.lock_override = True

                    if 'memory' in parameter:
                        channel.memory = parameter['memory']
                    if 'pid' in parameter:
                        channel.pid.set_parameters(
                            p=parameter['pid']['p'],
                            i=parameter['pid']['i'],
                            d=parameter['pid']['d'],
                        )

                    # Ignore ao requests if clients have not been assigned
                    if 'ao' in parameter and self.ao_clients is not None:

                        # Convert ao from string to object using lookup
                        try:
                            channel.ao = {
                                'client':
                                self.ao_clients[parameter['ao']['client']],
                                'channel':
                                parameter['ao']['channel']
                            }

                        # Handle case where the ao client does not exist
                        except KeyError:
                            channel.ao = None

                # Otherwise, it is a new channel so we should add it
                else:
                    self.channels.append(Channel(parameter))
                    self._initialize_channel(index=len(self.channels) - 1,
                                             channel=self.channels[-1])

    def initialize_channels(self):
        """Initializes all channels and outputs to the GUI"""

        for index, channel in enumerate(self.channels):
            self._initialize_channel(index, channel)

    def clear_channel(self, channel):
        """ Clears the plot output for this channel

        :param channel: Channel object to clear
        """

        try:
            channel.initialize(channel.data[-1])

        # If the channel isn't monitored
        except:
            self.log.warn('Could not clear channel')

    def clear_all(self):
        """ Clears all channels """

        for channel in self.channels:
            self.clear_channel(channel)

    def run(self):
        """Runs the WlmMonitor

        Can be stopped using the pause() method
        """

        self._get_gui_data()
        self._update_channels()
        self.gui.force_update()

    def zero_voltage(self, channel):
        """ Zeros the output voltage for this channel

        :param channel: Channel object to zero voltage of
        """

        try:
            channel.zero_voltage()
            self.log.info(f'Voltage centered for channel {channel.name}')

        # If the channel isn't monitored
        except:
            self.log.warn('Failed to zero voltage')

    def go_to(self, channel, value, step_size, hold_time):
        """ Sends laser to a setpoint value gradually

        :param channel: (int) channel number on wavemeter
        :param value: (float) value to set laser frequency to
        :param step_size: (float) step size in THz for laser freq steps
        :param hold_time: (float) time in seconds to wait between steps
        """

        # Index of channel
        physical_channel = self.channels[self._get_channels().index(channel)]

        # Generate array of points to go to
        traverse = np.linspace(
            physical_channel.setpoint, value,
            int((value - physical_channel.setpoint) / step_size))

        for frequency in traverse:
            self.set_parameters([dict(channel=channel, setpoint=frequency)])
            time.sleep(hold_time)

    # Technical methods

    def _initialize_channel(self, index, channel):
        """Initializes a channel and outputs to the GUI

        Should only be called in the beginning of channel use to assign physical GUI widgets
        """

        # Get wavelength and initialize data arrays
        channel.initialize(wavelength=self.wlm_client.get_wavelength(
            channel.number),
                           display_pts=self.display_pts)

        # Create curves
        # frequency
        self.widgets['curve'].append(self.widgets['graph'][2 * index].plot(
            pen=pg.mkPen(color=self.gui.COLOR_LIST[0])))
        add_to_legend(legend=self.widgets['legend'][2 * index],
                      curve=self.widgets['curve'][4 * index],
                      curve_name=channel.curve_name)

        # Setpoint
        self.widgets['curve'].append(self.widgets['graph'][2 * index].plot(
            pen=pg.mkPen(color=self.gui.COLOR_LIST[1])))
        add_to_legend(legend=self.widgets['legend'][2 * index],
                      curve=self.widgets['curve'][4 * index + 1],
                      curve_name=channel.setpoint_name)

        # Clear data
        self.widgets['clear'][2 * index].clicked.connect(
            lambda: self.clear_channel(channel))
        self.widgets['clear'][2 * index + 1].clicked.connect(
            lambda: self.clear_channel(channel))

        # Setpoint reset
        self.widgets['rs'][index].clicked.connect(
            lambda: self.update_parameters(
                dict(channel=channel.number, setpoint=channel.data[-1])))

        # Voltage
        self.widgets['curve'].append(self.widgets['graph'][2 * index + 1].plot(
            pen=pg.mkPen(color=self.gui.COLOR_LIST[0])))
        add_to_legend(legend=self.widgets['legend'][2 * index + 1],
                      curve=self.widgets['curve'][4 * index + 2],
                      curve_name=channel.voltage_curve)

        # Error
        self.widgets['curve'].append(self.widgets['graph'][2 * index + 1].plot(
            pen=pg.mkPen(color=self.gui.COLOR_LIST[1])))
        add_to_legend(legend=self.widgets['legend'][2 * index + 1],
                      curve=self.widgets['curve'][4 * index + 3],
                      curve_name=channel.error_curve)

        # zero
        self.widgets['zero'][2 * index].clicked.connect(
            lambda: self.zero_voltage(channel))
        self.widgets['zero'][2 * index + 1].clicked.connect(
            lambda: self.zero_voltage(channel))

    def _update_channels(self):
        """ Updates all channels + displays

        Called continuously inside run() method to refresh WLM data and output on GUI
        """

        for index, channel in enumerate(self.channels):

            # Check for override
            if channel.setpoint_override:
                self.widgets['sp'][index].setValue(channel.setpoint_override)
                channel.setpoint_override = 0

            # Update data with the new wavelength
            channel.update(self.wlm_client.get_wavelength(channel.number))

            # Update frequency
            self.widgets['curve'][4 * index].setData(channel.data)
            self.widgets['freq'][index].setValue(channel.data[-1])

            # Update setpoints
            self.widgets['curve'][4 * index + 1].setData(channel.sp_data)

            # Update the setpoint to GUI directly if it has been changed
            # if channel.setpoint_override:

            #     # Tell GUI to pull data provided by script and overwrite direct GUI input
            #     self.widgets['sp'][index].setValue(channel.setpoint)

            # If the lock has been updated, override the GUI
            # if channel.lock_override:
            #     self.widgets['lock'][index].setChecked(channel.lock)

            # Set the error boolean (true if the lock is active and we are outside the error threshold)
            if channel.lock and np.abs(channel.data[-1] -
                                       channel.setpoint) > self.threshold:
                self.widgets['error_status'][index].setChecked(True)
            else:
                self.widgets['error_status'][index].setChecked(False)

            # Now update lock + voltage plots
            self.widgets['curve'][4 * index + 2].setData(channel.voltage)
            self.widgets['voltage'][index].setValue(channel.voltage[-1])
            self.widgets['curve'][4 * index + 3].setData(channel.error)
            self.widgets['error'][index].setValue(channel.error[-1])

    def _get_gui_data(self):
        """ Updates setpoint and lock parameters with data pulled from GUI

        Does not overwrite the script setpoints and locks, but stores the GUI values for comparison based on context.
        See Channel.update() method for behavior on how script chooses whether to use internal values or GUI values
        """
        for index, channel in enumerate(self.channels):

            # Pull the current value from the GUI
            channel.gui_setpoint = self.widgets['sp'][index].value()
            channel.gui_lock = self.widgets['lock'][index].isChecked()

    def _get_channels(self):
        """ Returns all active channel numbers

        Usually used for checking whether a newly input channel has already been assigned to the script

        :return: (list) all active channel numbers
        """

        channel_list = []
        for channel in self.channels:
            channel_list.append(channel.number)
        return channel_list

    def get_wavelength(self, channel):
        # Index of channel
        physical_channel = self.channels[self._get_channels().index(channel)]
        return self.wlm_client.get_wavelength(physical_channel.number)
Beispiel #14
0
class TimeTraceGui(TimeTrace):
    """ Same as TimeTrace but with a dedicated GUI for display
    and parameter setting"""

    STYLESHEET = 'color: rgb(255, 255, 255); font: 25 12pt "Calibri Light";'

    def __init__(self, ctr: si_tt.Client, log: LogClient, config, ui='histogram', **kwargs):
        """ Instantiates TimeTrace measurement

        :param ctr: (si_tt.Client) client to timetagger hardware
        :param log: (LogClient) instance of logclient for logging
        :param config: (str) name of config file
        :param ui: (str) name of ui file
        :param **kwargs: additional keyword arguments
            TODO: in future, can implement multiple histograms if useful
        """

        # Setup GUI
        self.gui = Window(
            gui_template='histogram',
            host=get_ip()
        )

        # Setup stylesheet.
        self.gui.apply_stylesheet()

        # Store config
        self.config = config

        self.correlation = False
        if 'type' in self.config:
            if self.config['type'] == 'correlation':
                self.correlation = True

        super().__init__(
            ctr=ctr,
            log=log,
            click_ch=self.config['click_ch'],
            start_ch=self.config['start_ch'],
            binwidth=int(self._get_binwidth()),
            n_bins=self.gui.n_bins.value(),
            update_interval=0,
            correlation=self.correlation
        )

        if not type(self.config['click_ch']) == int:
            combined_click_channel = f"{self.config['click_ch'][0]}+{self.config['click_ch'][1]}"
            ctr.create_combined_channel(
                channel_name=combined_click_channel,
                channel_list=self.config['click_ch']
            )
            self.config['click_ch'] = [combined_click_channel]

        self.gates = {}
        if 'gate_ch' in self.config:

            # Handle singular input
            if not isinstance(self.config['gate_ch'], list):
                self.config['gate_ch'] = [self.config['gate_ch']]

            # Update GUI to handle gates
            self._configure_gui_gates()

            # Setup gated channels
            for gate_ch in self.config['gate_ch']:
                ch_name = f'Gated histogram channel {gate_ch}'
                ctr.create_gated_channel(
                    ch_name,
                    self.config['click_ch'],
                    gate_ch,
                    delay=self.delays[ch_name].value()
                )
                self.gates[ch_name] = TimeTrace(
                    ctr=ctr,
                    log=log,
                    click_ch=ch_name,
                    start_ch=self.config['start_ch'],
                    binwidth=int(self._get_binwidth()),
                    n_bins=self.gui.n_bins.value(),
                    update_interval=0,
                    correlation=self.correlation
                )

        # Configure clicks
        self.gui.configure.clicked.connect(lambda: self.update_parameters(
            binwidth=int(self._get_binwidth()),
            n_bins=self.gui.n_bins.value()
        ))
        self.gui.clear.clicked.connect(self.clear_all)
        # Need Lambda to force it to use default args
        # https://stackoverflow.com/questions/60001583/pyqt5-slot-function-does-not-take-default-argument
        self.gui.save.clicked.connect(lambda: self.save())
        self.gui.run.clicked.connect(self.run)

        #### CHANGED CHANGED CHANGED
        self.gui.fit.clicked.connect(self.fit_config)

        self._configure_delay_updates()

        # Configure window length preview
        self.gui.n_bins.valueChanged.connect(self.update_window_length_label)
        self.gui.binwidth.valueChanged.connect(self.update_window_length_label)

        # Configure window length preview
        self.update_window_length_label()

        # Initialize plot info
        self.curve = self.gui.graph.plot(
            pen=pg.mkPen(color=self.gui.COLOR_LIST[0])
        )

        self.gui.graph.getPlotItem().setLabel('bottom', 'Time (s)')
        self.legend = get_legend_from_graphics_view(self.gui.legend)
        add_to_legend(self.legend, self.curve, 'Histogram')

        self.gate_curves = {}
        index = 1
        for gate in self.gates:
            self.gate_curves[gate] = self.gui.graph.plot(
                pen=pg.mkPen(color=self.gui.COLOR_LIST[index])
            )
            index += 1
            add_to_legend(self.legend, self.gate_curves[gate], gate)

        self.gui.apply_stylesheet()
        self.fitting = False

    def clear_all(self):
        """ Clears all plots """

        self.clear()
        for gate in self.gates.values():
            gate.clear()

    def update_window_length_label(self):
        """ Update label previewing the total window length"""
        binwidth = int(self._get_binwidth()),
        n_bins = self.gui.n_bins.value()

        window_length = binwidth[0] * n_bins # in ps
        self.gui.window_length.setText(f'{window_length/1000} ns')

    def update_parameters(self, binwidth, n_bins):
        """ Updates parameters of all histograms

        :param binwidth: (float) binwidth in ps
        :param n_Bins: (int) total number of bins
        """

        self.set_parameters(binwidth, n_bins)
        for gate in self.gates.values():
            gate.set_parameters(binwidth, n_bins)

    def fit_config(self, status: bool):
        """ Configures fitting add-on

        :param status: (bool) whether or not fit button is checked
        """

        # If box is newly checked, instantiate popup
        if status:
            self.fit_popup = FitPopup(ui='fit_popup_hist',
                                      x=np.array(range(self.gui.n_bins.value())) * int(self._get_binwidth()) / 1e12, #dummy values, not actually used None, #self.ctr.get_x_axis(self.hist)/1e12,
                                      data=np.zeros(self.gui.n_bins.value()), #dummy values, not actually used #None, #self.ctr.get_counts(self.hist)[0],
                                      p0=None,
                                      config=self.config,
                                      log=self.log)
            self.fit_popup.model_type.activated.connect(self.fit_popup.fit_selection)
            self.fit_curve = self.gui.graph.plot(
                pen=pg.mkPen(color=self.gui.COLOR_LIST[5])
            )
            add_to_legend(self.legend, self.fit_curve, 'Histogram Fit')

            self.fitting = True
            self.p0 = None

        # If box isn't checked, remove popup
        else:
            self.fit_popup = None
            self.fitting = False

    def run(self):
        """ Handles run button click """

        if self.gui.run.text() == 'Run':
            self.gui.run.setText('Stop')
            self.gui.run.setStyleSheet('background-color: red')
            self.log.info('Running histogram')
            self.go()
        else:
            self.gui.run.setText('Run')
            self.gui.run.setStyleSheet('background-color: green')
            self.log.info('Stopped histogram')
            if self.gui.autosave.isChecked():
                self.save()
            self.pause()
            for gate in self.gates.values():
                gate.pause()

    def go(self):
        """ Runs counter from scratch """

        self.start_acquisition()
        for gate in self.gates.values():
            gate.start_acquisition()
        self.init_plot()

        self.is_paused = False
        last_save = time.time()
        last_clear = last_save
        while not self.is_paused:

            if self.gui.autosave.isChecked():
                current_time = time.time()
                if current_time - last_save > self.gui.save_time.value():
                    self.save()
                    last_save = current_time
            if self.gui.auto_clear.isChecked():
                current_time = time.time()
                if current_time - last_clear > self.gui.clear_time.value():
                    self.clear_all()
                    last_clear = current_time
            self._update_data()
            self.gui.force_update()

    def init_plot(self):
        """ Initializes the plot """

        # Clear existing data
        self.curve.clear()

        self.curve.setData(
            self.ctr.get_x_axis(self.hist) / 1e12,
            self.ctr.get_counts(self.hist)[0]
        )

        if self.fitting:
            self.fit_curve.clear()

            self._update_fit()

        for gate_name, gate_curve in self.gate_curves.items():
            gate_curve.clear()
            gate_curve.setData(
                self.gates[gate_name].ctr.get_x_axis(self.gates[gate_name].hist) / 1e12,
                self.gates[gate_name].ctr.get_counts(self.gates[gate_name].hist)[0]
            )

    def _update_data(self):
        """ Adds latest data to the plot """

        self.curve.setData(
            self.ctr.get_x_axis(self.hist) / 1e12,
            self.ctr.get_counts(self.hist)[0]
        )

        if self.fitting:
            self._update_fit()

        for gate_name, gate_curve in self.gate_curves.items():
            gate_curve.setData(
                self.gates[gate_name].ctr.get_x_axis(self.gates[gate_name].hist) / 1e12,
                self.gates[gate_name].ctr.get_counts(self.gates[gate_name].hist)[0]
            )

    def _update_fit(self):
        """ Updates fits """
        if self.fit_popup.mod is not None and self.fit_popup.mod.init_params is not None:
            self.fit_popup.data = np.array(self.ctr.get_counts(self.hist)[0])
            self.fit_popup.x = np.array(self.ctr.get_x_axis(self.hist) / 1e12)
            if self.p0 is not None:
                self.fit_popup.p0 = self.p0
            self.fit, self.p0 = self.fit_popup.fit_mod()
            if self.fit_popup.fit_suc:
                self.fit_curve.setData(
                    self.ctr.get_x_axis(self.hist) / 1e12,
                    self.fit
                )

    def _get_binwidth(self):
        """ Gets the binwidth using the unit combo box

        :return: (float) binwidth in ps
        """

        val = self.gui.binwidth.value()
        unit_index = self.gui.units.currentIndex()
        if unit_index == 0:
            return val
        elif unit_index == 1:
            return val * 1e3
        elif unit_index == 2:
            return val * 1e6
        elif unit_index == 3:
            return val * 1e9

    def _configure_gui_gates(self):
        """ Configures the gates part of the GUI """

        # Configure base layout for all gates
        gate_box = QtWidgets.QGroupBox("Gates")
        gate_box.setStyleSheet(self.STYLESHEET)
        vbox = QtWidgets.QVBoxLayout()

        # Now add widgets for each gate
        self.delays = {}
        for index, gate_ch in enumerate(self.config['gate_ch']):

            # Configure widgets
            ch_name = f'Gated histogram channel {gate_ch}'
            hbox = QtWidgets.QHBoxLayout()
            hbox.addWidget(QtWidgets.QLabel(text=f'Gate {gate_ch}'))
            hbox.addWidget(QtWidgets.QLabel(text='Delay: '))
            self.delays[ch_name] = QtWidgets.QDoubleSpinBox()
            self.delays[ch_name].setMaximum(1e12)
            self.delays[ch_name].setSuffix(' ps')
            self.delays[ch_name].setButtonSymbols(2)
            hbox.addWidget(self.delays[ch_name])

            # Check for preconfigured delay and set the value
            if 'delays' in self.config:
                try:
                    self.delays[ch_name].setValue(self.config['delays'][index])
                except IndexError:
                    pass

            # Add to vertical layout
            vbox.addLayout(hbox)

        # Configure layout to a group box and add to GUI in layout
        gate_box.setLayout(vbox)
        self.gui.graph_layout.addWidget(gate_box)

    def _configure_delay_updates(self):
        """ Configures delay updates when a value is changed """

        for channel_name, delay in self.delays.items():
            delay.valueChanged.connect(lambda state, x=channel_name: self.gates[channel_name].ctr.update_delay(
                channel_name=x,
                delay=state
            ))

    def save(self, filename=None, directory=None):
        """ Saves the current data """

        if filename is None:
            filename = self.gui.save_name.text()

        if directory is None:
            directory = self.config['save_path']

        generic_save(
            data=np.array([
                self.ctr.get_x_axis(self.hist) / 1e12,
                self.ctr.get_counts(self.hist)[0]
            ]),
            filename=filename,
            directory=directory,
            date_dir=True
        )

        for gate_name, gate in self.gates.items():
            generic_save(
                data=np.array([
                    gate.ctr.get_x_axis(gate.hist) / 1e12,
                    gate.ctr.get_counts(gate.hist)[0]
                ]),
                filename=gate_name,
                directory=directory,
                date_dir=True
            )

        pyqtgraph_save(
            widget=self.gui.graph.getPlotItem(),
            filename=filename,
            directory=directory,
            date_dir=True
        )

        self.log.info('Saved histogram data')
Beispiel #15
0
    def __init__(self,
                 logger=None,
                 channels=['Channel 1'],
                 clients={},
                 config=None,
                 fast=True):
        """ Instantiates controller (only 1D data supported so far)

        :param logger: instance of LogClient
        :param channels: (list) list of channel names
        :param config: (str) name of config file
        :param fast: (bool) whether to operate in fast mode
            fast mode only updates heat maps at the end of each scan (this speeds things up)
        """
        self.config = load_script_config('scan1d', config, logger=logger)

        if 'sweep_type' in self.config.keys():
            self.sweep_type = self.config['sweep_type']
        else:
            self.sweep_type = 'triangle'

        super().__init__(logger, channels, sweep_type=self.sweep_type)

        self.module = None

        # Instantiate GUI
        self.gui = Window(gui_template='scan_1d', host=get_ip(), max=True)
        self.widgets = get_gui_widgets(self.gui,
                                       p_min=1,
                                       p_max=1,
                                       pts=1,
                                       config=1,
                                       graph=2,
                                       legend=2,
                                       clients=1,
                                       exp=1,
                                       exp_preview=1,
                                       configure=1,
                                       run=1,
                                       autosave=1,
                                       save_name=1,
                                       save=1,
                                       reps=1,
                                       rep_tracker=1,
                                       avg=2)

        # Configure default parameters
        self.min = self.widgets['p_min'].value()
        self.max = self.widgets['p_max'].value()
        self.pts = self.widgets['pts'].value()
        self.reps = self.widgets['reps'].value()

        self.data_fwd = []
        self.data_fwd_2nd_reading = []  ### ADDED
        self.data_bwd = []
        self.avg_fwd = []
        self.avg_fwd_2nd_reading = []  ### ADDED
        self.avg_bwd = []
        self.fit_popup = None
        self.p0_fwd = None
        self.p0_bwd = None
        self.x_fwd = self._generate_x_axis()
        if self.sweep_type != 'sawtooth':
            self.x_bwd = self._generate_x_axis(backward=True)
        else:
            self.x_bwd = self._generate_x_axis()
        self.fast = fast

        # Configure list of experiments
        self.widgets['config'].setText(config)

        self.exp_path = self.config['exp_path']
        model = QtWidgets.QFileSystemModel()
        model.setRootPath(self.exp_path)
        model.setNameFilterDisables(False)
        model.setNameFilters(['*.py'])

        self.widgets['exp'].setModel(model)
        self.widgets['exp'].setRootIndex(model.index(self.exp_path))
        self.widgets['exp'].hideColumn(1)
        self.widgets['exp'].hideColumn(2)
        self.widgets['exp'].hideColumn(3)
        self.widgets['exp'].clicked.connect(self.display_experiment)

        # Configure list of clients
        self.clients = clients
        for client_entry in self.config['servers']:
            client_type = client_entry['type']
            client_config = client_entry['config']
            client = find_client(clients=self.clients,
                                 settings=client_config,
                                 client_type=client_type,
                                 client_config=client_config,
                                 logger=self.log)
            if (client == None):
                client_name_concat = client_type
                client_item = QtWidgets.QListWidgetItem(client_name_concat)
                client_item.setForeground(Qt.gray)
                client_item.setToolTip(str("Disconnected"))
                self.widgets['clients'].addItem(client_item)

            else:
                self.log.info(client)
                client_name_concat = f"{client_type}_{client_config}"
                client_item = QtWidgets.QListWidgetItem(client_name_concat)
                client_item.setToolTip(str(client))
                self.widgets['clients'].addItem(client_item)

        #Checking for any missing clients, and adding them as greyed out on the list of clients

        # Manually add logger to client
        self.clients['logger'] = logger

        # Configure button
        self.widgets['configure'].clicked.connect(self.configure_experiment)
        self.widgets['run'].clicked.connect(self.run_pressed)
        self.widgets['autosave'].toggled.connect(self._update_autosave)
        self.widgets['save'].pressed.connect(
            lambda: self.save(filename=self.widgets['save_name'].text(),
                              directory=self.config['save_path']))
        self.widgets['reps'].valueChanged.connect(self.set_reps)
        self.widgets['avg'][0].clicked.connect(
            lambda: self._clear_show_trace(0))
        self.widgets['avg'][1].clicked.connect(
            lambda: self._clear_show_trace(1))
        self.gui.fit.clicked.connect(self.fit_config)

        # Create legends
        self.widgets['curve'] = []
        for index in range(len(self.widgets['graph'])):
            self.widgets['legend'][index] = get_legend_from_graphics_view(
                self.widgets['legend'][index])

        # Configure hmaps
        self.widgets['hmap'] = []
        for index in range(2):

            # Configure Hmap to work the way we want
            hmap = pg.ImageView(view=pg.PlotItem())
            self.gui.graph_layout.insertWidget(2 * index + 1, hmap)
            hmap.setPredefinedGradient('inferno')
            hmap.show()
            hmap.view.setAspectLocked(False)
            hmap.view.invertY(False)
            self.widgets['hmap'].append(hmap)

        # Setup stylesheet.
        self.gui.apply_stylesheet()
Beispiel #16
0
class Controller:
    """ Class for controlling Toptica scan and laser properties """
    def __init__(self,
                 dlc: toptica_dl_pro.Client,
                 gui='toptica_control',
                 logger=None,
                 port=None):
        """ Initializes toptica specific parameters

        :param dlc: DLC client for the Toptica laser
        :param gui: .ui file to use
        :param logger: LogClient for logging purposes
        :param port: port number of script server
        """

        self.log = LogHandler(logger)

        # Setup GUI
        self.gui = Window(gui_template=gui, host=get_ip(), port=port)
        self.widgets = get_gui_widgets(gui=self.gui,
                                       on_off=2,
                                       temperature=2,
                                       temperature_actual=2,
                                       current=2,
                                       current_actual=2,
                                       offset=2,
                                       amplitude=2,
                                       frequency=2,
                                       scan=2,
                                       update_temp=2,
                                       update_current=2,
                                       update_params=1)

        self.dlc = dlc
        self.offset = 65
        self.amplitude = 100
        self.scan = [False, False]
        self.emission = [False, False]

        # Setup stylesheet.
        self.gui.apply_stylesheet()

        self._setup_GUI()

    def run(self, check_vals=False):
        """ Runs an iteration of checks for updates and implements

        :param check_vals: (bool) whether or not to check the values of current and temp
        """

        # Update actual current and temperature
        # self.gui.activate_scalar('temperature_actual')
        # self.gui.set_scalar(self.dlc.temp_act(), 'temperature_actual')
        # self.gui.deactivate_scalar('temperature_actual')

        # self.gui.activate_scalar('current_actual')
        # self.gui.set_scalar(self.dlc.current_act(), 'current_actual')
        # self.gui.deactivate_scalar('current_actual')

        # Check for on/off updates
        for i in range(2):
            if self.widgets['on_off'][i].isChecked() != self.emission[i]:

                # If laser was already on, turn off
                if self.emission[i]:
                    self.dlc.turn_off(i + 1)
                    self.emission[i] = False
                    self.log.info(f'Toptica DL {i+1} turned off')

                # Otherwise turn on
                else:
                    self.dlc.turn_on(i + 1)
                    self.emission[i] = True
                    self.log.info(f'Toptica DL {i+1} turned on')

        # Now handle a scan event
        for i in range(2):
            if self.widgets['scan'][i].isChecked() != self.scan[i]:

                # If we were previously scanning, terminate the scan
                if self.scan[i]:
                    self.dlc.stop_scan(i + 1)
                    self.scan[i] = False
                    self.log.info(f'Toptica DL {i+1} scan stopped')

                # Otherwise, configure and start the scan
                else:
                    offset = self.widgets['offset'][i].value()
                    amplitude = self.widgets['amplitude'][i].value()
                    frequency = self.widgets['frequency'][i].value()
                    self.dlc.configure_scan(offset=offset,
                                            amplitude=amplitude,
                                            frequency=frequency,
                                            laser_num=i + 1)
                    self.dlc.start_scan(i + 1)
                    self.scan[i] = True
                    self.log.info(f'Toptica DL Scan {i+1} initiated '
                                  f'with offset: {offset}, '
                                  f'amplitude: {amplitude}, '
                                  f'frequency: {frequency}')

        # Handle value checking
        if self.widgets['update_params'].isChecked():

            for i in range(2):
                self.widgets['temperature_actual'][i].setDisabled(False)
                self.widgets['current_actual'][i].setDisabled(False)

            if check_vals:
                try:
                    temp_1 = self.dlc.temp_act(1)
                    if temp_1 < 50:
                        self.widgets['temperature_actual'][0].setValue(temp_1)
                    time.sleep(0.1)
                    self.widgets['current_actual'][0].setValue(
                        self.dlc.current_act(1))
                    temp_2 = self.dlc.temp_act(2)
                    if temp_2 < 50:
                        self.widgets['temperature_actual'][1].setValue(temp_2)
                    time.sleep(0.1)
                    self.widgets['current_actual'][1].setValue(
                        self.dlc.current_act(2))
                except ValueError:
                    pass
        else:
            for i in range(2):
                self.widgets['temperature_actual'][i].setDisabled(True)
                self.widgets['current_actual'][i].setDisabled(True)

        self.gui.force_update()

    def _setup_GUI(self):
        """ Sets values to current parameters """

        # Check if laser is on and update

        for i in range(2):
            self.emission[i] = self.dlc.is_laser_on(i + 1)
            self.widgets['on_off'][i].setChecked(self.emission[i])
            time.sleep(0.1)

        # Get temperature setpoint and actual temperature
        temp_sp_1 = 100
        while temp_sp_1 > 50:
            temp_sp_1 = self.dlc.temp_sp(1)
            temp_sp_2 = self.dlc.temp_sp(2)
            time.sleep(0.1)

        self.widgets['temperature'][0].setValue(temp_sp_1)
        self.widgets['temperature'][1].setValue(temp_sp_2)

        temp_act_1 = 100
        while temp_act_1 > 50:
            temp_act_1 = self.dlc.temp_act(1)
            temp_act_2 = self.dlc.temp_act(2)
            time.sleep(0.1)
        self.widgets['temperature_actual'][0].setValue(temp_act_1)
        self.widgets['temperature_actual'][1].setValue(temp_act_2)

        # Get current setpoint and actual current
        self.widgets['current'][0].setValue(self.dlc.current_sp(1))
        time.sleep(0.1)
        self.widgets['current'][1].setValue(self.dlc.current_sp(2))
        time.sleep(0.1)
        self.widgets['current_actual'][0].setValue(self.dlc.current_act(1))
        time.sleep(0.1)
        self.widgets['current_actual'][1].setValue(self.dlc.current_act(2))
        time.sleep(0.1)

        # Assign button pressing
        self.widgets['update_temp'][0].clicked.connect(
            lambda: self._set_temperature(1))
        self.widgets['update_temp'][1].clicked.connect(
            lambda: self._set_temperature(2))
        self.widgets['update_current'][0].clicked.connect(
            lambda: self._set_current(1))
        self.widgets['update_current'][1].clicked.connect(
            lambda: self._set_current(2))

    def _set_temperature(self, laser_num):
        """ Sets the temperature to the setpoint value in the GUI """

        temperature = self.widgets['temperature'][laser_num - 1].value()
        self.dlc.set_temp(temperature, laser_num)
        self.log.info(
            f'Set Toptica {laser_num} temperature setpoint to {temperature}')

    def _set_current(self, laser_num):
        """ Sets the current to the setpoint value in the GUI """

        current = self.widgets['current'][laser_num - 1].value()
        self.dlc.set_current(current, laser_num)
        self.log.info(f'Set Toptica {laser_num} current setpoint to {current}')
Beispiel #17
0
    def __init__(self,
                 config,
                 ao_client=None,
                 ai_client=None,
                 hd=None,
                 check_aom=False,
                 logger=None):
        """Instantiates LaserStabilizer script object for stabilizing the laser
             :param config: (str) name of config file """

        # Instantiate GUI
        self.gui = Window(gui_template='power_stabilizer', host=get_ip())
        self.widgets = get_gui_widgets(self.gui,
                                       p_setpoint=1,
                                       p_outputVoltage=1,
                                       label_power=1,
                                       config=1,
                                       graph=2,
                                       legend=2,
                                       hardware_control=1,
                                       clear=1,
                                       start=1,
                                       stop=1)

        self._ao_client = ao_client
        self._ai_client = ai_client
        self._hd = hd
        self.log = logger

        self.config = config
        self.check_aom = check_aom
        self._load_config_file()

        #Now initialize control/output voltage to 0, and set up label
        self._curr_output_voltage = self.widgets['p_outputVoltage'].value(
        )  #Stores current output voltage that is outputted by the AO
        self.widgets['p_outputVoltage'].valueChanged.connect(
            self._set_output_voltage_from_label)
        # self._ao_client.set_ao_voltage(self._ao_channel, self._curr_output_voltage)

        #Update the input power label
        self._last_power_text_update = 2
        self.read_power()

        self._initialize_graphs()

        #Setting up hardware power control options
        self.widgets['hardware_control'].enabled = False
        self._under_hardware_control = False
        self.widgets['hardware_control'].toggled.connect(
            self._update_hardware_control_from_gui)

        #Finally hookup the final buttons
        self.widgets['start'].clicked.connect(
            lambda: self.start(update_st_gui=True))
        self.widgets['stop'].clicked.connect(self.stop)
        self.widgets['clear'].clicked.connect(
            lambda: self._clear_data_plots(display_pts=1000))

        #Initially the program starts in the "unlocked" phase
        self._is_stabilizing = False
Beispiel #18
0
class LaserStabilizer:
    """A class for stabilizing the laser power given a DAQ input, a power control output, and a setpoint"""
    def __init__(self,
                 config,
                 ao_client=None,
                 ai_client=None,
                 hd=None,
                 check_aom=False,
                 logger=None):
        """Instantiates LaserStabilizer script object for stabilizing the laser
             :param config: (str) name of config file """

        # Instantiate GUI
        self.gui = Window(gui_template='power_stabilizer', host=get_ip())
        self.widgets = get_gui_widgets(self.gui,
                                       p_setpoint=1,
                                       p_outputVoltage=1,
                                       label_power=1,
                                       config=1,
                                       graph=2,
                                       legend=2,
                                       hardware_control=1,
                                       clear=1,
                                       start=1,
                                       stop=1)

        self._ao_client = ao_client
        self._ai_client = ai_client
        self._hd = hd
        self.log = logger

        self.config = config
        self.check_aom = check_aom
        self._load_config_file()

        #Now initialize control/output voltage to 0, and set up label
        self._curr_output_voltage = self.widgets['p_outputVoltage'].value(
        )  #Stores current output voltage that is outputted by the AO
        self.widgets['p_outputVoltage'].valueChanged.connect(
            self._set_output_voltage_from_label)
        # self._ao_client.set_ao_voltage(self._ao_channel, self._curr_output_voltage)

        #Update the input power label
        self._last_power_text_update = 2
        self.read_power()

        self._initialize_graphs()

        #Setting up hardware power control options
        self.widgets['hardware_control'].enabled = False
        self._under_hardware_control = False
        self.widgets['hardware_control'].toggled.connect(
            self._update_hardware_control_from_gui)

        #Finally hookup the final buttons
        self.widgets['start'].clicked.connect(
            lambda: self.start(update_st_gui=True))
        self.widgets['stop'].clicked.connect(self.stop)
        self.widgets['clear'].clicked.connect(
            lambda: self._clear_data_plots(display_pts=1000))

        #Initially the program starts in the "unlocked" phase
        self._is_stabilizing = False

    def _load_config_file(self):
        """Read values from config file."""

        self._ai_channel = self.config["power_input_channel"]
        self._hwc_ai_channel = self.config['hardware_ctrl_input_channel']
        self._ao_channel = self.config["ctrl_output_channel"]

        # Configure default parameters
        self.min_voltage = self.config[
            'min_output_voltage']  #Minimum output voltage
        self.max_voltage = self.config[
            'max_output_voltage']  #Maximum output voltage
        self.gain = self.config[
            'gain']  #"Gain" between measured voltage and corresponding power
        # NOTE: Internally we store all measured powers as the raw voltages
        # we then only multiply by the gain factor when displaying
        # it to the user.
        self.max_input_voltage = self.config[
            'max_input_voltage']  #Maximum possible input voltage, used for scaling
        #the DAQ acquisition range.

        self._hwc_thresh = self.config['hardware_ctrl_thresh']
        #Threshold to turn hardware control on/off

        #Loading PID parameters
        self.paramP = self.config["pid"]["p"]
        self.paramI = self.config["pid"]["i"]
        self.paramD = self.config["pid"]["d"]
        self.paramMemory = self.config["memory"]  #
        self._update_voltageSetpoint_fromGUI()
        self._update_PID()

        self.numReadsPerCycle = self.config[
            "reads_per_cycle"]  #Number of the reads on the DAQ card that are averaged over for an update cycle.

    def check_aom_up(self):
        """Checks if a given HDAWG DIO bit attached to an AOM switch is high"""

        dio_config = load_config('dio_assignment_global')
        DIO_bit = dio_config[self.config["aom_staticline"]]
        current_config = self._hd.geti('dios/0/output')
        DIO_bit_bitshifted = (0b1 << DIO_bit)  # for bit 3: 000...0001000

        DIO_bitup = current_config & DIO_bit_bitshifted

        if DIO_bitup > 0:
            return True
        else:
            return False

    def run(self):
        """Main function to update both teh feedback loop as well as the gui"""

        self._check_hardware_control()

        if self._is_stabilizing:
            #If we are locking the power, then need to update the feedback loop and change the output label

            # Check if DIO bit is high
            if self.check_aom:
                if self.check_aom_up():
                    currSignal = self._update_feedback()
                    self._update_output_voltage_label()
                else:
                    #If we are not locking the power, just read the power
                    currSignal = self.read_power()
            else:
                currSignal = self._update_feedback()
                self._update_output_voltage_label()

        else:
            #If we are not locking the power, just read the power
            currSignal = self.read_power()

        #We always need to update the plots
        self._update_plots(currSignal)
        self.gui.force_update()

    def start(self, update_st_gui=True, display_pts=1000):
        """This method turns on power stabilizationpdate_vs to False to not update the setpoint from the GUI
            :param update_vs_gui: (Boolean) whether the setpoint should be updated based on the value in the gui,
                                Will always be true when start is run in the GUI, but should be false if
                                an external program wishes to start power locking the laser and manually sets the setpoint.
            :param display_pts: (int) number of display points to use in the plots"""
        if update_st_gui:
            #First, update the setpoint based on the text in the GUI
            self._update_voltageSetpoint_fromGUI()
            self._set_output_voltage_from_label()

        #Update hte PID parameters, which will save the new setpoint to the PID object we use
        self._update_PID()

        #Reset the graphs
        #self._clear_data_plots(display_pts)

        #Finally turn on the power stabilization
        self._is_stabilizing = True

    def stop(self):
        """This stops power stabilization"""
        self._is_stabilizing = False

    def _update_hardware_control_from_gui(self):
        """Updates hardware_control based on the widget being checked"""
        self._under_hardware_control = self.widgets[
            'hardware_control'].isChecked()

    def _check_hardware_control(self):
        """Method updates whether the program is stabilizing the power or not
        dependent on the status of the voltage at the input port"""
        if self._under_hardware_control:
            v_input = self._ai_client.get_ai_voltage(
                self._hwc_ai_channel,
                max_range=10)  #CHeck status of hwc voltage input
            v_input = v_input[-1]
            if self._is_stabilizing:
                if v_input < self._hwc_thresh:
                    self.stop()
            else:
                if v_input > self._hwc_thresh:
                    self.start()

    def set_hardware_control(self, value):
        """Allows external program to set hardware control to the value inputed
            :param value: (Boolean) whether hardware control should be turned off or no
            """
        self.widgets['hardware_control'].setChecked(value)
        self._under_hardware_control = value

    def read_power(self):
        """reads power and passes latest measurement"""

        currSignal = None
        while currSignal is None:
            try:
                currSignal = self._ai_client.get_ai_voltage(
                    self._ai_channel,
                    self.numReadsPerCycle,
                    max_range=self.max_input_voltage)
            except:
                pass  #time.sleep(0.005)

        #Checks if > 1s has elapsed since the last change to the power reading label
        #I do this since otherwise the text label updates too quickly and it's annoying
        #to read.

        currTime = time.time()
        if currTime - self._last_power_text_update > 1:

            power = self.gain * currSignal

            self.widgets['label_power'].setText(str(self.gain * power[-1])[:5])
            self._last_power = power[-1] / self.gain
            self._last_power_text_update = currTime

        return currSignal

    #TODO: Can potentially use some getters/setters to clean up the below two functions make them a little more cllean for the user.

    def _update_output_voltage_label(self):
        """Updates the output voltage label to the current voltage being outputted.
        This is called when the laser is "locked" and the PID loop is actively changing
        the output voltage"""
        self.widgets['p_outputVoltage'].setValue((self._curr_output_voltage))

    def _set_output_voltage_from_label(self):
        """Updates the output control voltage based on the text in the output voltage text box.
        This method is automatically run when the user changes the value in the text box, allowing
        the user to control the output voltage  directly when the laser power is not "locked".
        """
        if (
                not self._is_stabilizing
        ):  #Only updates value if we are not stabilizing, otherwise the PID loop will be driving the output voltage
            #as opposed to the user.
            self._curr_output_voltage = self.widgets['p_outputVoltage'].value()
            self._ao_client.set_ao_voltage(self._ao_channel,
                                           self._curr_output_voltage)

    def set_control_voltage(self, value):
        """Allows an external program to directly set the control/output voltage in use by the stabilizer
        :param value: (float) value to set output voltage to"""
        self._curr_output_voltage = value
        self._update_output_voltage_label()

    def _update_voltageSetpoint_fromGUI(self):
        """Update the voltage setpoint to whatever value is currently in the setpoint spin box"""
        self.voltageSetpoint = self.widgets['p_setpoint'].value() / self.gain

    def set_setpoint(self, value):
        """Updates the power setpoint, for use by external programs wishing to interface with
        this one.
        :param value: (float) setpoint value to use in units of power (not voltage!)
        NOTE: Using the GUI this is not normally automatically called. Instead the user must
        hit start again to update the setpoint if they are in the middle of power stabilizing"""
        self.voltageSetpoint = value / self.gain
        self.widgets['p_setpoint'].setValue(value)
        #Need to reset the PID loop with this new setpoint value
        self._update_PID()

    def _update_PID(self):
        """Creates a new PID object based on the current PID member variables to be used for power
        feedbacking"""
        self.pid = PID(p=self.paramP,
                       i=self.paramI,
                       d=self.paramD,
                       setpoint=self.voltageSetpoint,
                       memory=self.paramMemory)

    def _update_feedback(self):
        """ Runs the actual feedback loop"""
        #First read in the current voltage (power)
        #Read in numReadsPerCycle signals (arb) to average
        #TODO: allow user to select reads per signal
        currSignal = None
        while currSignal is None:
            try:
                currSignal = self._ai_client.get_ai_voltage(
                    self._ai_channel,
                    self.numReadsPerCycle,
                    max_range=self.max_input_voltage)
            except:
                pass  #time.sleep(0.005)
        #Add new data to the pid
        self.pid.set_pv(np.atleast_1d(np.mean(currSignal)))
        #self.pid.set_pv(np.mean(currSignal))

        #Now compute the new control value and update the AO
        self.pid.set_cv()
        self._curr_output_voltage = self._curr_output_voltage + self.pid.cv
        if self._curr_output_voltage < self.min_voltage:
            self._curr_output_voltage = self.min_voltage
        elif self._curr_output_voltage > self.max_voltage:
            self._curr_output_voltage = self.max_voltage

        #Finally updating the analog output

        #Do a final check to make sure that if you are in hardware control mode that the voltage control is still HIGH
        #This is to avoid the potential error if the voltage control is toggled low between the last call of _check_hardware_control
        #and update_feedback, whcih would mean that currSignal would be 0 (assuming a pulsed experiment), and causing a garbage
        #feedback which could be an issue in the next pulse.
        if (
                not self._under_hardware_control
        ):  #or self.ai_client.get_ai_voltage(self._hwc_ai_channel)[-1] > self._hwc_thresh):
            self._ao_client.set_ao_voltage(self._ao_channel,
                                           self._curr_output_voltage)

        #Checks if > 1s has elapsed since the last change to the power reading label
        #I do this since otherwise the text label updates too quickly and it's annoying
        #to read.
        currTime = time.time()
        if currTime - self._last_power_text_update > 1:
            power = self.gain * currSignal

            self.widgets['label_power'].setText(str(self.gain * power[-1])[:5])
            self._last_power = power[-1] / self.gain
            self._last_power_text_update = currTime

        return currSignal

    def _clear_data_plots(self, display_pts=1000):
        """Initializes/clears the variables holding the data which is plotted"""
        #Initializing variables for plotting
        self.out_voltages = np.ones(display_pts) * self._curr_output_voltage
        self.measured_powers = np.ones(display_pts) * self._last_power

        # Check that setpoint is reasonable, otherwise set error to 0
        self.errors = np.ones(display_pts) * (self._last_power -
                                              self.voltageSetpoint)
        self.sp_data = np.ones(display_pts) * self.voltageSetpoint

    def _initialize_graphs(self):
        """Initializes a channel and outputs to the GUI

        Should only be called in the initialization of the project
        """
        # Add in teh cleared widgets array
        self.widgets['curve'] = []
        self.widgets['legend'] = [
            get_legend_from_graphics_view(legend)
            for legend in self.widgets['legend']
        ]

        # Create curves
        # Power
        self.widgets['curve'].append(self.widgets['graph'][0].plot(
            pen=pg.mkPen(color=self.gui.COLOR_LIST[0])))
        add_to_legend(legend=self.widgets['legend'][0],
                      curve=self.widgets['curve'][0],
                      curve_name="Power")

        # Setpoint
        self.widgets['curve'].append(self.widgets['graph'][0].plot(
            pen=pg.mkPen(color=self.gui.COLOR_LIST[1])))
        add_to_legend(legend=self.widgets['legend'][0],
                      curve=self.widgets['curve'][1],
                      curve_name="Setpoint")

        # Voltage
        self.widgets['curve'].append(self.widgets['graph'][1].plot(
            pen=pg.mkPen(color=self.gui.COLOR_LIST[0])))
        add_to_legend(legend=self.widgets['legend'][1],
                      curve=self.widgets['curve'][2],
                      curve_name="Voltage")

        # Error
        self.widgets['curve'].append(self.widgets['graph'][1].plot(
            pen=pg.mkPen(color=self.gui.COLOR_LIST[1])))
        add_to_legend(legend=self.widgets['legend'][1],
                      curve=self.widgets['curve'][3],
                      curve_name="Error")

        self._clear_data_plots(1000)

    def _update_plots(self, currSignal):
        """Updates the plots, both by adding in the new data and then drawing the data on the graph"""
        #Adding in new data to plots
        #currSignal = None
        #while currSignal is None:
        #    try:
        #        currSignal = self._ai_client.get_ai_voltage(self._ai_channel, max_range=self.max_input_voltage)
        #    except:
        #        time.sleep(0.01)

        self.measured_powers = np.append(self.measured_powers[1:],
                                         np.mean(currSignal))
        self.out_voltages = np.append(self.out_voltages[1:],
                                      self._curr_output_voltage)
        self.errors = np.append(self.errors[1:],
                                (currSignal[-1] - self.voltageSetpoint))
        self.sp_data = np.append(self.sp_data[1:], self.voltageSetpoint)

        #Update power plot
        self.widgets['curve'][0].setData(self.measured_powers * self.gain)
        #Update setpoint plot
        self.widgets['curve'][1].setData(self.sp_data * self.gain)
        # Update voltage plot
        self.widgets['curve'][2].setData(self.out_voltages)
        # Update error plot
        self.widgets['curve'][3].setData(self.errors * self.gain)
Beispiel #19
0
    def __init__(self, ctr: si_tt.Client, log: LogClient, config, ui='histogram', **kwargs):
        """ Instantiates TimeTrace measurement

        :param ctr: (si_tt.Client) client to timetagger hardware
        :param log: (LogClient) instance of logclient for logging
        :param config: (str) name of config file
        :param ui: (str) name of ui file
        :param **kwargs: additional keyword arguments
            TODO: in future, can implement multiple histograms if useful
        """

        # Setup GUI
        self.gui = Window(
            gui_template='histogram',
            host=get_ip()
        )

        # Setup stylesheet.
        self.gui.apply_stylesheet()

        # Store config
        self.config = config

        self.correlation = False
        if 'type' in self.config:
            if self.config['type'] == 'correlation':
                self.correlation = True

        super().__init__(
            ctr=ctr,
            log=log,
            click_ch=self.config['click_ch'],
            start_ch=self.config['start_ch'],
            binwidth=int(self._get_binwidth()),
            n_bins=self.gui.n_bins.value(),
            update_interval=0,
            correlation=self.correlation
        )

        if not type(self.config['click_ch']) == int:
            combined_click_channel = f"{self.config['click_ch'][0]}+{self.config['click_ch'][1]}"
            ctr.create_combined_channel(
                channel_name=combined_click_channel,
                channel_list=self.config['click_ch']
            )
            self.config['click_ch'] = [combined_click_channel]

        self.gates = {}
        if 'gate_ch' in self.config:

            # Handle singular input
            if not isinstance(self.config['gate_ch'], list):
                self.config['gate_ch'] = [self.config['gate_ch']]

            # Update GUI to handle gates
            self._configure_gui_gates()

            # Setup gated channels
            for gate_ch in self.config['gate_ch']:
                ch_name = f'Gated histogram channel {gate_ch}'
                ctr.create_gated_channel(
                    ch_name,
                    self.config['click_ch'],
                    gate_ch,
                    delay=self.delays[ch_name].value()
                )
                self.gates[ch_name] = TimeTrace(
                    ctr=ctr,
                    log=log,
                    click_ch=ch_name,
                    start_ch=self.config['start_ch'],
                    binwidth=int(self._get_binwidth()),
                    n_bins=self.gui.n_bins.value(),
                    update_interval=0,
                    correlation=self.correlation
                )

        # Configure clicks
        self.gui.configure.clicked.connect(lambda: self.update_parameters(
            binwidth=int(self._get_binwidth()),
            n_bins=self.gui.n_bins.value()
        ))
        self.gui.clear.clicked.connect(self.clear_all)
        # Need Lambda to force it to use default args
        # https://stackoverflow.com/questions/60001583/pyqt5-slot-function-does-not-take-default-argument
        self.gui.save.clicked.connect(lambda: self.save())
        self.gui.run.clicked.connect(self.run)

        #### CHANGED CHANGED CHANGED
        self.gui.fit.clicked.connect(self.fit_config)

        self._configure_delay_updates()

        # Configure window length preview
        self.gui.n_bins.valueChanged.connect(self.update_window_length_label)
        self.gui.binwidth.valueChanged.connect(self.update_window_length_label)

        # Configure window length preview
        self.update_window_length_label()

        # Initialize plot info
        self.curve = self.gui.graph.plot(
            pen=pg.mkPen(color=self.gui.COLOR_LIST[0])
        )

        self.gui.graph.getPlotItem().setLabel('bottom', 'Time (s)')
        self.legend = get_legend_from_graphics_view(self.gui.legend)
        add_to_legend(self.legend, self.curve, 'Histogram')

        self.gate_curves = {}
        index = 1
        for gate in self.gates:
            self.gate_curves[gate] = self.gui.graph.plot(
                pen=pg.mkPen(color=self.gui.COLOR_LIST[index])
            )
            index += 1
            add_to_legend(self.legend, self.gate_curves[gate], gate)

        self.gui.apply_stylesheet()
        self.fitting = False
Beispiel #20
0
class Monitor:
    RANGE_LIST = [
        'AUTO', 'R1NW', 'R10NW', 'R100NW', 'R1UW', 'R10UW', 'R100UW', 'R1MW',
        'R10MW', 'R100MW', 'R1W', 'R10W', 'R100W', 'R1KW'
    ]

    def __init__(self,
                 pm_client,
                 gui='fiber_coupling',
                 logger=None,
                 calibration=None,
                 name=None,
                 port=None):
        """ Instantiates a monitor for 2-ch power meter with GUI

        :param pm_clients: (client, list of clients) clients of power meter
        :param gui_client: client of monitor GUI
        :param logger: instance of LogClient
        :calibration: (float) Calibration value for power meter.
        :name: (str) Humand-readable name of the power meter.
        """

        self.log = LogHandler(logger)
        self.gui = Window(gui_template=gui, host=get_ip(), port=port)

        self.gui.apply_stylesheet()
        self.wavelength = []
        self.calibration = calibration
        self.name = name
        self.ir_index, self.rr_index = [], []
        self.pm = pm_client
        self.running = False
        self.num_plots = 3

        # Get all GUI widgets
        self.widgets = get_gui_widgets(self.gui,
                                       graph_widget=self.num_plots,
                                       number_widget=4,
                                       label_widget=2,
                                       name_label=1,
                                       combo_widget=2)

        self._initialize_gui()

    def sync_settings(self):
        """ Pulls current settings from PM and sets them to GUI """

        # Configure wavelength
        self.wavelength = self.pm.get_wavelength(1)

        self.widgets['number_widget'][-1].setValue(self.wavelength)

        # Configure Range to be Auto
        self.pm.set_range(1, self.RANGE_LIST[0])
        self.pm.set_range(2, self.RANGE_LIST[0])
        self.ir_index = 0
        self.rr_index = 0

        # Connect wavelength change action.
        self.widgets['number_widget'][-1].valueChanged.connect(
            self._update_wavelength)

        # Connect range change.
        self.widgets['combo_widget'][0].currentIndexChanged.connect(
            lambda: self._update_range(0))
        self.widgets['combo_widget'][1].currentIndexChanged.connect(
            lambda: self._update_range(1))

    def _update_wavelength(self):
        """ Updates wavelength of pm to WL of GUI"""

        gui_wl = self.widgets['number_widget'][-1].value()

        if self.wavelength != gui_wl:
            self.wavelength = gui_wl
            self.pm.set_wavelength(1, self.wavelength)
            self.pm.set_wavelength(2, self.wavelength)

    def _update_range(self, channel):
        """ Update range settings if combobox has been changed."""

        range_index = self.widgets['combo_widget'][channel].currentIndex()

        if channel == 0:
            if self.ir_index != range_index:
                self.ir_index = range_index
                self.pm.set_range(1, self.RANGE_LIST[self.ir_index])
        elif channel == 1:
            if self.rr_index != range_index:
                self.rr_index = range_index
                self.pm.set_range(2, self.RANGE_LIST[self.rr_index])

    def update_settings(self, channel=0):
        """ Checks GUI for settings updates and implements

        :param channel: (int) channel of power meter to use
        """

        gui_wl = self.widgets['number_widget_4'].value()

        if self.wavelength[channel] != gui_wl:
            self.wavelength[channel] = gui_wl
            self.pm[channel].set_wavelength(1, self.wavelength[channel])
            self.pm[channel].set_wavelength(2, self.wavelength[channel])

        gui_ir = self.gui.get_item_index(f'ir_{channel}')
        if self.ir_index[channel] != gui_ir:
            self.ir_index[channel] = gui_ir
            self.pm[channel].set_range(2 * channel + 1,
                                       self.RANGE_LIST[self.ir_index[channel]])

        gui_rr = self.gui.get_item_index(f'rr_{channel}')
        if self.rr_index[channel] != gui_rr:
            self.rr_index[channel] = gui_rr
            self.pm[channel].set_range(2 * channel + 2,
                                       self.RANGE_LIST[self.rr_index[channel]])

    def run(self):
        # Continuously update data until paused
        self.running = True

        while self.running:
            time.sleep(BUFFER)
            self._update_output()
            self.gui.force_update()

    def _update_output(self):
        """ Runs the power monitor """

        # Check for/implement changes to settings
        #self.update_settings(0)

        # Get all current values
        try:
            p_in = self.pm.get_power(1)
            split_in = split(p_in)

        # Handle zero error
        except OverflowError:
            p_in = 0
            split_in = (0, 0)
        try:
            p_ref = self.pm.get_power(2)
            split_ref = split(p_ref)
        except OverflowError:
            p_ref = 0
            split_ref = (0, 0)
        try:
            efficiency = np.sqrt(p_ref / (p_in * self.calibration[0]))
        except ZeroDivisionError:
            efficiency = 0
        values = [p_in, p_ref, efficiency]

        # For the two power readings, reformat.
        # E.g., split(0.003) will return (3, -3)
        # And prefix(-3) will return 'm'
        formatted_values = [split_in[0], split_ref[0], efficiency]
        value_prefixes = [
            prefix(split_val[1]) for split_val in [split_in, split_ref]
        ]

        # Update GUI
        for plot_no in range(self.num_plots):
            # Update Number
            self.widgets['number_widget'][plot_no].setValue(
                formatted_values[plot_no])

            # Update Curve
            self.plotdata[plot_no] = np.append(self.plotdata[plot_no][1:],
                                               values[plot_no])
            self.widgets[f'curve_{plot_no}'].setData(self.plotdata[plot_no])

            if plot_no < 2:
                self.widgets["label_widget"][plot_no].setText(
                    f'{value_prefixes[plot_no]}W')

    def _initialize_gui(self):
        """ Instantiates GUI by assigning widgets """

        # Store plot data
        self.plotdata = [np.zeros(1000) for i in range(self.num_plots)]

        for plot_no in range(self.num_plots):
            # Create a curve and store the widget in our dictionary
            self.widgets[f'curve_{plot_no}'] = self.widgets['graph_widget'][
                plot_no].plot(pen=pg.mkPen(color=self.gui.COLOR_LIST[0]))
Beispiel #21
0
    def __init__(self,
                 wlm_client,
                 logger_client,
                 gui='wavemeter_monitor',
                 ao_clients=None,
                 display_pts=5000,
                 threshold=0.0002,
                 port=None,
                 params=None,
                 three_lasers=False):
        """ Instantiates WlmMonitor script object for monitoring wavemeter

        :param wlm_client: (obj) instance of wavemeter client
        :param gui_client: (obj) instance of GUI client.
        :param logger_client: (obj) instance of logger client.
        :param ao_clients: (dict, optional) dictionary of ao client objects with keys to identify. Exmaple:
            {'ni_usb_1': nidaqmx_usb_client_1, 'ni_usb_2': nidaqmx_usb_client_2, 'ni_pxi_multi': nidaqmx_pxi_client}
        :param display_pts: (int, optional) number of points to display on plot
        :param threshold: (float, optional) threshold in THz for lock error signal
        :param port: (int) port number for update server
        :param params: (dict) see set_parameters below for details
        :three_lasers: (bool) If three lasers are in use (instead of 2)
        """
        self.channels = []

        if three_lasers:
            gui = 'wavemeter_monitor_3lasers'

        self.wlm_client = wlm_client
        self.ao_clients = ao_clients
        self.display_pts = display_pts
        self.threshold = threshold
        self.log = LogHandler(logger_client)

        # Instantiate gui
        self.gui = Window(
            gui_template=gui,
            host=get_ip(),
            port=port,
        )

        # Setup stylesheet.
        self.gui.apply_stylesheet()

        if three_lasers:
            self.widgets = get_gui_widgets(gui=self.gui,
                                           freq=3,
                                           sp=3,
                                           rs=3,
                                           lock=3,
                                           error_status=3,
                                           graph=6,
                                           legend=6,
                                           clear=6,
                                           zero=6,
                                           voltage=3,
                                           error=3)
        else:
            self.widgets = get_gui_widgets(gui=self.gui,
                                           freq=2,
                                           sp=2,
                                           rs=2,
                                           lock=2,
                                           error_status=2,
                                           graph=4,
                                           legend=4,
                                           clear=4,
                                           zero=4,
                                           voltage=2,
                                           error=2)

        # Set parameters
        self.set_parameters(**params)

        # Configure plots
        # Get actual legend widgets
        self.widgets['legend'] = [
            get_legend_from_graphics_view(legend)
            for legend in self.widgets['legend']
        ]

        self.widgets['curve'] = []
        self.initialize_channels()

        for channel in self.channels:
            self.update_parameters(
                dict(channel=channel.number, setpoint=channel.data[-1]))
Beispiel #22
0
class DataTaker:
    def __init__(self,
                 logger=None,
                 client_tuples=None,
                 config=None,
                 config_name=None):

        self.log = LogHandler(logger)
        self.dataset = None

        # Instantiate GUI window
        self.gui = Window(gui_template='data_taker', host=get_ip())

        # Configure list of experiments
        self.gui.config.setText(config_name)
        self.config = config
        self.exp_path = self.config['exp_path']
        if self.exp_path is None:
            self.exp_path = os.getcwd()
        sys.path.insert(1, self.exp_path)
        self.update_experiment_list()

        # Configure list of clients
        self.clients = {}

        # Retrieve Clients
        for client_entry in self.config['servers']:
            client_type = client_entry['type']
            client_config = client_entry['config']
            client = find_client(clients=client_tuples,
                                 settings=client_config,
                                 client_type=client_type,
                                 client_config=client_config,
                                 logger=self.log)
            self.clients[f"{client_type}_{client_config}"] = client

        for client_name, client_obj in self.clients.items():
            client_item = QtWidgets.QListWidgetItem(client_name)
            client_item.setToolTip(str(client_obj))
            self.gui.clients.addItem(client_item)

        # Configure dataset menu
        for name, obj in inspect.getmembers(datasets):
            if inspect.isclass(obj) and issubclass(obj, datasets.Dataset):
                self.gui.dataset.addItem(name)

        # Configure button clicks
        self.gui.configure.clicked.connect(self.configure)
        self.gui.run.clicked.connect(self.run)
        self.gui.save.clicked.connect(self.save)
        self.gui.load_config.clicked.connect(self.reload_config)
        self.gui.showMaximized()
        self.gui.apply_stylesheet()

    def update_experiment_list(self):
        """ Updates list of experiments """

        self.gui.exp.clear()
        for filename in os.listdir(self.exp_path):
            if filename.endswith('.py'):
                self.gui.exp.addItem(filename[:-3])
        self.gui.exp.itemClicked.connect(self.display_experiment)

    def display_experiment(self, item):
        """ Displays the currently clicked experiment in the text browser

        :param item: (QlistWidgetItem) with label of name of experiment to display
        """

        with open(os.path.join(self.exp_path, f'{item.text()}.py'),
                  'r') as exp_file:
            exp_content = exp_file.read()

        self.gui.exp_preview.setText(exp_content)
        self.gui.exp_preview.setStyleSheet('font: 10pt "Consolas"; '
                                           'color: rgb(255, 255, 255); '
                                           'background-color: rgb(0, 0, 0);')
        self.log.update_metadata(experiment_file=exp_content)

    def configure(self):
        """ Configures the currently selected experiment + dataset """

        # If the experiment is running, do nothing
        try:
            if self.experiment_thread.isRunning():
                self.log.warn('Did not configure experiment, since it '
                              'is still in progress')
                return
        except:
            pass

        # Load the config
        self.reload_config()

        # Set all experiments to normal state and highlight configured expt
        for item_no in range(self.gui.exp.count()):
            self.gui.exp.item(item_no).setBackground(
                QtGui.QBrush(QtGui.QColor('black')))
        self.gui.exp.currentItem().setBackground(
            QtGui.QBrush(QtGui.QColor('darkRed')))
        exp_name = self.gui.exp.currentItem().text()
        self.module = importlib.import_module(exp_name)
        self.module = importlib.reload(self.module)

        # Clear graph area and set up new or cleaned up dataset
        for index in reversed(range(self.gui.graph_layout.count())):
            try:
                self.gui.graph_layout.itemAt(index).widget().deleteLater()
            except AttributeError:
                try:
                    self.gui.graph_layout.itemAt(index).layout().deleteLater()
                except AttributeError:
                    pass
        self.gui.windows = {}
        # If we're not setting up a new measurement type, just clear the data
        self.dataset = getattr(datasets, self.gui.dataset.currentText())(
            gui=self.gui, log=self.log, config=self.config)

        # Run any pre-experiment configuration
        try:
            self.module.configure(dataset=self.dataset, **self.clients)
        except AttributeError:
            pass
        self.experiment = self.module.experiment

        self.log.info(f'Experiment {exp_name} configured')
        self.gui.exp_preview.setStyleSheet(
            'font: 10pt "Consolas"; '
            'color: rgb(255, 255, 255); '
            'background-color: rgb(50, 50, 50);')

    def run(self):
        """ Runs/stops the experiment """

        # Run experiment
        if self.gui.run.text() == 'Run':
            self.gui.run.setStyleSheet('background-color: red')
            self.gui.run.setText('Stop')
            self.log.info('Experiment started')

            # Run update thread
            self.update_thread = UpdateThread(
                autosave=self.gui.autosave.isChecked(),
                save_time=self.gui.autosave_interval.value())
            self.update_thread.data_updated.connect(self.dataset.update)
            self.update_thread.save_flag.connect(self.save)
            self.gui.autosave.toggled.connect(
                self.update_thread.update_autosave)
            self.gui.autosave_interval.valueChanged.connect(
                self.update_thread.update_autosave_interval)

            self.experiment_thread = ExperimentThread(self.experiment,
                                                      dataset=self.dataset,
                                                      gui=self.gui,
                                                      **self.clients)

            self.experiment_thread.status_flag.connect(
                self.dataset.interpret_status)
            self.experiment_thread.finished.connect(self.stop)
            self.log.update_metadata(
                exp_start_time=datetime.now().strftime('%d/%m/%Y %H:%M:%S:%f'))

        # Stop experiment
        else:
            self.experiment_thread.running = False

    def stop(self):
        """ Stops the experiment"""

        self.gui.run.setStyleSheet('background-color: green')
        self.gui.run.setText('Run')
        self.log.info('Experiment stopped')
        self.update_thread.running = False

        self.log.update_metadata(
            exp_stop_time=datetime.now().strftime('%d/%m/%Y %H:%M:%S:%f'))

        # Autosave if relevant
        if self.gui.autosave.isChecked():
            self.save()

    def save(self):
        """ Saves data """

        self.log.update_metadata(notes=self.gui.notes.toPlainText())
        filename = self.gui.save_name.text()
        directory = self.config['save_path']
        self.dataset.save(filename=filename,
                          directory=directory,
                          date_dir=True)
        save_metadata(self.log, filename, directory, True)
        self.log.info('Data saved')

    def reload_config(self):
        """ Loads a new config file """

        self.config = load_script_config(script='data_taker',
                                         config=self.gui.config.text(),
                                         logger=self.log)