Пример #1
0
class TestWinspec(QWidget):
    """
    Gui for beam blocker flags.
    Requires hyperion.instrument.misc.beam_flags_instr/BeamFlagsInstr instrument as input.

    :param beam_flags_instr: instrument object to control
    :type beam_flags_instr: an instance of the class
    """

    def __init__(self, winspec_instr):
        """
        Gui for beam blocker flags.

        :param beam_flags_instr: The beam flags instrument object to create the gui for
        :type beam_flags_instr: BeamFlagsInstr object
        """
        super().__init__()
        self.logger = logging.getLogger(__name__)

        self.title = 'Test Winspec'
        self.left = 400
        self.top = 400
        self.width = 240
        self.height = 80

        self.ws = winspec_instr

        self.initUI()


    def initUI(self):
        """
        Create all the gui elements and connect signals to methods.
        Adds 'state' and 'label' key and value to each flag in the settings dict.
        Reads the current state from the device and sets 'state' key and gui accordingly.
        """

        self.logger.info('Creating gui elements')
        self.setWindowTitle(self.title)
        self.setGeometry(self.left, self.top, self.width, self.height)

        layout = QGridLayout()

        button = QPushButton('test', self)
        button.pressed.connect(self.button_clicked)

        layout.addWidget(button, 0 , 0)
        self.setLayout(layout)
        self.show()


    def button_clicked(self):
        print('test button is clicked')
        ws.controller.spt_set('NEW_POSITION',300)

        self.worker_thread = WorkThread(ws.controller.spt.Move)
        self.worker_thread.start()
Пример #2
0
    def initialize(self):
        self._is_initialized = False
        if self._force_threading is True or (self._force_threading is None
                                             and 'PyQt5' in sys.modules):
            # if _force_threading is None and PyQt5 is detected
            # if _force_threading is True
            self.logger.info(
                'Winspec in threading mode ' +
                ['(automatic)', '(forced)'][self._force_threading is True])
            from hyperion.view.general_worker import WorkThread
            from time import sleep
            init_thread = WorkThread(self.__initialize_in_thread)
            self._busy_with_thread = True
            init_thread.start()
            timeout_count = 50  # wait 5 seconds before spitting out warnings
            while self._busy_with_thread:
                sleep(.2)
                if timeout_count <= 0:
                    self.logger.warning('Waiting for Winspec')
            init_thread.quit()
            self._threading_mode = True
        else:
            # if _force_threading is False
            # if _force_threading is None and PyQt5 is not detected
            self.logger.info(
                'Winspec NOT in threading mode ' +
                ['(forced)', '(automatic)'][self._force_threading is None])
            self._threading_mode = False
            self.__initialize_without_threading_support()

        self._generate_params()
        self._is_initialized = True  # THIS IS MANDATORY!!
        # this is to prevent you to close the device connection if you
        # have not initialized it inside a with statement

        # self.xdim = self.exp_get('XDIM')[0]
        # self.ydim = self.exp_get('YDIM')[0]
        self.xdim = self.exp_get('XDIMDET')[0]
        self.ydim = self.exp_get('YDIMDET')[0]
Пример #3
0
class OsaGui(QWidget):
    """
    OSA Gui class. It builds the GUI for the OSA instrument.

    """

    def __init__(self, instr, draw):
        """ Init of the class.
            The class needs an instance of the osa instrument.
        """
        super().__init__()
        self.title = 'Osa gui'
        self.left = 50          #how many pixels are away from the left of the GUI
        self.top = 50           #how many pixels are away form the top of the GUI
        self.width = 700        #how many pixels in width the GUI is
        self.height = 350       #how many pixels in height the GUI is
        self.logger = logging.getLogger(__name__)
        self.logger.info('Class App created')
        self.layout = QGridLayout()
        self.setLayout(self.layout)
        self.instr = instr
        self.draw = draw
        self.initUI()

    def initUI(self):
        """
        This method initialises all the QWidgets needed.
        """
        self.set_gui_constructor()

        self.set_textboxs()

        #only works if the pyvisa bug is fixed
        #self.set_textboxs_to_osa_machine_values()

        self.set_labels()

        self.set_submit_button()

        self.set_recommended_sample_points_button()

        self.show()

    def set_gui_constructor(self):
        """ a constructor to set the properties of  the GUI in"""
        self.setWindowTitle(self.title)
        self.setGeometry(self.left, self.top, self.width, self.height)
        #self.statusBar().showMessage("Just some text")

    def set_recommended_sample_points_button(self):
        """calculate the recommended sample_points"""
        button_calculate_recommended_sample_points = QPushButton('get recommended sample_points', self)
        button_calculate_recommended_sample_points.setToolTip(
            "click this button to \n get the recommended sample_points")
        self.layout.addWidget(button_calculate_recommended_sample_points, 11, 0)
        button_calculate_recommended_sample_points.clicked.connect(self.on_click_recommended)
    def set_submit_button(self):
        """code to make the submit button """
        self.button_submit = QPushButton('submit', self)
        self.button_submit.setToolTip('You are hovering over the button, \n what do you expect?')
        self.layout.addWidget(self.button_submit, 12, 0)
        self.button_submit.clicked.connect(self.on_click_submit)

    def set_labels(self):
        self.set_start_wav_label()
        self.set_end_wav_label()
        self.set_optical_resolution_label()
        self.set_sample_points_label()
        self.set_sensitivity_label()
    def set_sensitivity_label(self):
        # set the sensitivity label
        self.layout.addWidget(QLabel("the sensitivity"), 8, 0)
    def set_sample_points_label(self):
        # set the sample points label
        self.layout.addWidget(QLabel("the amount of sample points"), 6, 0)
    def set_optical_resolution_label(self):
        # set the optical resolution label
        self.layout.addWidget(QLabel("the optical resolution in stepvalue of nm"), 4, 0)
    def set_end_wav_label(self):
        # set the end_wav label
        self.layout.addWidget(QLabel("the end wavelength, from 600.00 nm to 1750.00 nm"), 2, 0)
    def set_start_wav_label(self):
        # set the start_wav label
        self.layout.addWidget(QLabel("the start wavelength, from 600.00 nm to 1750.00 nm"), 0, 0)

    def set_textboxs(self):
        self.set_start_wav_textbox()
        self.set_end_wav_text_box()
        self.set_optical_resolution_textbox()
        self.set_sample_points_textbox()
        self.set_sensitivity_dropdown()
    def set_sensitivity_dropdown(self):
        """"code to make the sensitivity_dropdown """
        self.dropdown_sensitivity = QComboBox(self)
        self.dropdown_sensitivity.addItems(
            ["sensitivity normal range", "sensitivity normal range automatic", "sensitivity medium range",
             "sensitivity high 1 range", "sensitivity high 2 range", "sensitivity high 3 range"])
        self.layout.addWidget(self.dropdown_sensitivity, 9, 0)
    def set_sample_points_textbox(self):
        # this is the sample_points_textbox
        self.textbox_sample_points = QLineEdit(self)
        self.layout.addWidget(self.textbox_sample_points, 7, 0)
    def set_optical_resolution_textbox(self):
        """"code to make the optical_resolution dropdown"""
        self.dropdown_optical_resolution = QComboBox(self)
        self.dropdown_optical_resolution.addItems(["0.01", "0.02", "0.05", "0.1", "0.2", "0.5", "1.0", "2.0", "5.0"])
        self.layout.addWidget(self.dropdown_optical_resolution, 5, 0)
    def set_end_wav_text_box(self):
        # this is the end_wav_textbox
        self.textbox_end_wav = QLineEdit(self)
        self.layout.addWidget(self.textbox_end_wav, 3, 0)
    def set_start_wav_textbox(self):
        # this is the start_wav_textbox
        self.textbox_start_wav = QLineEdit(self)
        self.layout.addWidget(self.textbox_start_wav, 1, 0)

    def get_chosen_optical_resolution(self):
        return self.dropdown_optical_resolution.currentText()
    def get_chosen_sensitivity(self):
        return self.dropdown_sensitivity.currentText()


    def on_click_recommended(self):
        """"when the recommend sample points button is clicked
        the textfield will be set empty and the recommend sample points will be calculated.
        using the formula: 1 + (2*(end_wav  - start_wav)/float(optical_resolution))
        """
        print("the recommended sample points will be calculated ")
        if self.instr.controller._is_initialized:
            self.instr.controller.perform_single_sweep()
        else:
            print("pyvisa does not work like intended")
            print(id(self.instr.controller._osa))

        self.textbox_sample_points.setText("")

        #check if the textboxs are not empty
        if self.is_start_wav_not_empty() and self.is_end_wav_not_empty():
            #all fields are filled
            self.get_recommended_sample_points()
        else:
            #some parameter are missing in one of the textboxs
            self.error_message_not_all_fields_are_filled()

    def is_sample_points_not_empty(self):
        if self.textbox_sample_points.text() != "":
            return True
        else:
            return False
    def is_end_wav_not_empty(self):
        if self.textbox_end_wav.text() != "":
            return True
        else:
            return False
    def is_start_wav_not_empty(self):
        if self.textbox_start_wav.text() != "":
            return True
        else:
            return False

    def error_message_not_all_fields_are_filled(self):
        message = "You did not type the\n"

        if not self.is_start_wav_not_empty():
            message += " start wavelength value\n"
        if not self.is_end_wav_not_empty():
            message += " end wavelength value\n"
        if not self.is_optical_resolution_not_empty():
            message += " optical resolution value\n"
        if not self.is_sample_points_not_empty():
            message += " sample points value\n"
        message += "you should set the specified value\n"

        QMessageBox.question(self, 'you did something wrong', message,
                             QMessageBox.Ok,
                             QMessageBox.Ok)

    def get_values_from_textboxs(self):
        # get all the values from the textfields
        start_wav = self.get_start_wav()
        end_wav = self.get_end_wav()
        optical_resolution = self.get_optical_resolution()
        sample_points = self.get_sample_points()
        return end_wav, optical_resolution, sample_points, start_wav
    def get_sample_points(self):
        sample_points = self.textbox_sample_points.text()
        return int(sample_points)
    def get_optical_resolution(self):
        optical_resolution = self.dropdown_optical_resolution.currentText()
        return float(optical_resolution)
    def get_end_wav(self):
        end_wav = self.textbox_end_wav.text()
        return Q_(end_wav)
    def get_start_wav(self):
        start_wav = self.textbox_start_wav.text()
        return Q_(start_wav)

    def get_recommended_sample_points(self):
        self.textbox_sample_points.setText(
            str(int(1 + (2 * (Q_(self.textbox_end_wav.text()) - Q_(self.textbox_start_wav.text())).m_as('nm') / float(
                self.dropdown_optical_resolution.currentText())))))

    def on_click_submit(self):
        """"
        In this method there is a request to do a single sweep and plot the data.
        """
        if self.instr.controller._is_initialized:
            self.instr.controller.perform_single_sweep()
        else:
            print("pyvisa does not work like intended")
            print(id(self.instr.controller._osa))

        self.logger.info('submit button clicked')

        self.get_submit_button_status()
        if self.is_start_wav_not_empty() and self.is_end_wav_not_empty() and self.is_sample_points_not_empty():
            #self.logger.info('fields not empty, grabbing fields')
            end_wav, optical_resolution, sample_points, start_wav = self.get_values_from_textboxs()
            #check if conditions for a single sweep are met
            if self.instr.is_start_wav_value_correct(start_wav) and self.instr.is_end_wav_value_correct(end_wav) and self.instr.is_end_wav_bigger_than_start_wav(start_wav,end_wav):
                #all tests are succesfull. now the rest of the code may be executed.
                # set the settings of the osa machine

                print('setting parameters in instrument')
                print('set_parameters_for_osa_machine( ... )')
                self.set_parameters_for_osa_machine(end_wav, optical_resolution, sample_points, start_wav)

                #try:
                self.make_thread()
                #except:
                #    print("exception occured: "+str(sys.exc_info()[0]))
                self.plot_data()

            self.get_output_message(end_wav, optical_resolution, sample_points, start_wav)
            self.set_textboxs_to_osa_machine_values()

        else:
            self.error_message_not_all_fields_are_filled()

    def set_parameters_for_osa_machine(self, end_wav, optical_resolution, sample_points, start_wav):
        #print(self.instr.start_wav)
        #print(self.instr.end_wav)
        #print(self.instr.optical_resolution)
        #print(self.instr.sample_points)
        self.instr.start_wav = Q_(start_wav)
        self.instr.end_wav = Q_(end_wav)
        self.instr.optical_resolution = float(optical_resolution)
        self.instr.sample_points = int(sample_points)

    def make_thread(self):
        print('starting sweep')
        print(id(self.instr.controller._osa))

        self.worker_thread = WorkThread(self.instr.take_spectrum)
        print("starting")
        self.worker_thread.start()

    def plot_data(self):
        """
        Plot the data. On the x axis there is the wavelength and the y axis is the
        spectrum data.
        """
        wav = self.instr.wav
        spec = self.instr.spec
        self.draw.random_plot.plot(wav, spec, clear=True)

    def set_textboxs_to_osa_machine_values(self):
        # set the textboxs to the value from the osa machine
        self.textbox_start_wav.setText("{} nm".format(self.instr.start_wav.m_as('nm')))
        self.textbox_end_wav.setText("{} nm".format(self.instr.end_wav.m_as('nm')))
        self.textbox_sample_points.setText("{}".format(int(self.instr.sample_points)))
    def get_output_message(self, end_wav, optical_resolution, sample_points, start_wav):
        # make a textbox to display given values.
        QMessageBox.question(self, 'What is this, a response?', "You typed: " + str(start_wav) + "\n" + str(end_wav) +
                             "\n" + str(optical_resolution) + "\n" + str(sample_points), QMessageBox.Ok,
                             QMessageBox.Ok)
    def get_submit_button_status(self):
        # when the button is clicked this method will be executed
        print('button says something')
        self.statusBar().showMessage("you have clicked the button, nothing happens(yet)")
Пример #4
0
class Thorlabs_motor_GUI(BaseGui):
    """
    | The initialization of the thorlabs xyz gui.
    | Settings of the meta instrument are used. Here
    | Serial number and name are in the settings given underneath, so thorlabs_instrument knows them.
    | Initialize of the instrument is already done by the init of the thorlabs_instrument, that runs with the with downstairs.
    """
    def __init__(self, thorlabs_meta_instrument):
        super().__init__()
        self.logger = logging.getLogger(__name__)
        self.left = 50
        self.top = 50
        self.width = 400
        self.height = 200
        self.grid_layout = QGridLayout()
        self.setLayout(self.grid_layout)

        self.wp_one = thorlabs_meta_instrument.wp_one
        self.logger.debug('You are connected to a {}'.format(
            self.wp_one.kind_of_device))

        self.wp_two = thorlabs_meta_instrument.wp_two
        self.logger.debug('You are connected to a {}'.format(
            self.wp_one.kind_of_device))

        self.title = 'Thorlabs {} GUI two waveplates'

        self.current_position_one = None
        self.current_position_two = None

        self.wp_one_target = 0
        self.wp_two_target = 0

        self.offset_one = 0
        self.offset_two = 0

        self.keyboard_use_move = False

        self.timer = QTimer()
        self.timer.timeout.connect(self.set_current_motor_position_label)
        self.timer.start(100)

        self.initUI()

    def initUI(self):
        self.logger.debug('Setting up the two waveplates Motor GUI')
        self.setWindowTitle(self.title)
        self.setGeometry(self.left, self.top, self.width, self.height)

        self.make_buttons()
        self.make_labels()
        self.make_misc_gui_stuff()

        self.show()

    def make_buttons(self):
        self.make_goto_one_button()
        self.make_goto_two_button()
        self.make_moveby_both_button()
        self.make_reset_one_button()
        self.make_reset_two_button()
        self.make_keyboard_move_button()

    def make_labels(self):
        self.make_current_pos_one_label()
        self.make_current_pos_two_label()
        self.make_both_text_label()
        self.make_keyboard_text_label()

    def make_misc_gui_stuff(self):
        self.make_wp_one_spinbox()
        self.make_wp_two_spinbox()
        self.make_wp_both_spinbox()

    ############################Make buttons, boxes and labels#########################################

    def make_goto_one_button(self):
        self.goto_one_button = QPushButton("Go to", self)
        self.goto_one_button.setToolTip('Go to')
        self.goto_one_button.clicked.connect(self.goto_one)
        self.grid_layout.addWidget(self.goto_one_button, 0, 2)

    def make_goto_two_button(self):
        self.goto_two_button = QPushButton("Go to", self)
        self.goto_two_button.setToolTip('Go to')
        self.goto_two_button.clicked.connect(self.goto_two)
        self.grid_layout.addWidget(self.goto_two_button, 1, 2)

    def make_reset_one_button(self):
        self.reset_one_button = QPushButton("Reset", self)
        self.reset_one_button.setToolTip('Reset')
        self.reset_one_button.clicked.connect(self.reset_one)
        self.grid_layout.addWidget(self.reset_one_button, 0, 3)

    def make_reset_two_button(self):
        self.reset_two_button = QPushButton("Reset", self)
        self.reset_two_button.setToolTip('Reset')
        self.reset_two_button.clicked.connect(self.reset_two)
        self.grid_layout.addWidget(self.reset_two_button, 1, 3)

    def make_moveby_both_button(self):
        self.moveby_both_button = QPushButton("Move by", self)
        self.moveby_both_button.setToolTip('Reset')
        self.moveby_both_button.clicked.connect(self.moveby_both)
        self.grid_layout.addWidget(self.moveby_both_button, 2, 2)

    def make_keyboard_move_button(self):
        self.keyboard_button_move = QPushButton("keyboard\nmove", self)
        self.keyboard_button_move.setToolTip(
            "use the keyboard to move the thorlabs_motor,\nit works great")
        self.keyboard_button_move.clicked.connect(self.use_keyboard_move)
        self.grid_layout.addWidget(self.keyboard_button_move, 3, 3)

    def make_current_pos_one_label(self):
        self.current_pos_one_label = QLabel(self)
        try:
            self.current_pos_one_label.setText(self.wp_one.position())
        except Exception:
            self.current_pos_one_label.setText("currently/unavailable")
        self.grid_layout.addWidget(self.current_pos_one_label, 0, 0)

    def make_current_pos_two_label(self):
        self.current_pos_two_label = QLabel(self)
        try:
            self.current_pos_two_label.setText(self.wp_two.position())
        except Exception:
            self.current_pos_two_label.setText("currently/unavailable")
        self.grid_layout.addWidget(self.current_pos_two_label, 1, 0)

    def make_both_text_label(self):
        self.both_text_label = QLabel(self)
        self.both_text_label.setText("Both waveplates")
        self.grid_layout.addWidget(self.both_text_label, 2, 0)

    def make_keyboard_text_label(self):
        self.keyboard_text_label = QLabel(self)
        self.keyboard_text_label.setText(
            "y/u to move wp one - h/j to move wp two - n/m to move both\nt to quit)"
        )
        self.grid_layout.addWidget(self.keyboard_text_label, 3, 0, 1, 2)

    def make_wp_one_spinbox(self):
        self.wp_one_spinbox = QDoubleSpinBox(self)
        self.grid_layout.addWidget(self.wp_one_spinbox, 0, 1)
        self.wp_one_spinbox.setValue(0)
        self.wp_one_spinbox.setMinimum(
            -999999999)  #otherwise you cannot reach higher than 99
        self.wp_one_spinbox.setMaximum(999999999)
        self.wp_one_spinbox.valueChanged.connect(self.set_wp_one_target)

    def make_wp_two_spinbox(self):
        self.wp_two_spinbox = QDoubleSpinBox(self)
        self.grid_layout.addWidget(self.wp_two_spinbox, 1, 1)
        self.wp_two_spinbox.setValue(0)
        self.wp_two_spinbox.setMinimum(
            -999999999)  #otherwise you cannot reach higher than 99
        self.wp_two_spinbox.setMaximum(999999999)
        self.wp_two_spinbox.valueChanged.connect(self.set_wp_two_target)

    def make_wp_both_spinbox(self):
        self.wp_both_spinbox = QDoubleSpinBox(self)
        self.grid_layout.addWidget(self.wp_both_spinbox, 2, 1)
        self.wp_both_spinbox.setValue(0)
        self.wp_both_spinbox.setMinimum(
            -999999999)  #otherwise you cannot reach higher than 99
        self.wp_both_spinbox.setMaximum(999999999)
        self.wp_both_spinbox.valueChanged.connect(self.set_wp_both_degrees)

    def set_current_motor_position_label(self):
        """ In the instrument level, the current position is remembered and updated through self.position,
        which is called in the moving_loop during the moves.
        This method read this out (continuously, through the timer in the init) and displays the value.
        """
        self.current_position_one = self.wp_one.position()
        self.current_pos_one_label.setText(
            "waveplate one:" +
            str(round(self.current_position_one - self.offset_one, 2)))

        self.current_position_two = self.wp_two.position()
        self.current_pos_two_label.setText(
            "waveplate two:" +
            str(round(self.current_position_two - self.offset_two, 2)))

    def set_wp_both_degrees(self):
        value = self.wp_both_spinbox.value()
        unit = "degrees"
        toggle_distance = ur(str(value) + unit)
        self.logger.debug('toggle waveplate 1 distance value {}'.format(value))
        self.wp_both_target_relative = toggle_distance

    def set_wp_one_target(self):
        value = self.wp_one_spinbox.value()
        unit = "degrees"
        toggle_distance = ur(str(value) + unit)
        self.logger.debug('toggle waveplate 1 distance value {}'.format(value))
        self.wp_one_target = toggle_distance

    def reset_one(self):
        self.offset_one = self.wp_one.position()

    def reset_two(self):
        self.offset_two = self.wp_two.position()

    def set_wp_two_target(self):
        value = self.wp_two_spinbox.value()
        unit = "degrees"
        toggle_distance = ur(str(value) + unit)
        self.logger.debug('toggle waveplate 2 distance value {}'.format(value))
        self.wp_two_target = toggle_distance

    def goto_one(self):
        self.moving_one_thread = WorkThread(
            self.wp_one.move_absolute, self.wp_one_target + self.offset_one,
            True)
        self.moving_one_thread.start()

    def goto_two(self):
        self.moving_two_thread = WorkThread(
            self.wp_two.move_absolute, self.wp_two_target + self.offset_one,
            True)
        self.moving_two_thread.start()

    def moveby_both(self):
        self.moving_one_thread = WorkThread(self.wp_one.move_relative,
                                            self.wp_both_target_relative, True)
        self.moving_one_thread.start()

        self.moving_two_thread = WorkThread(self.wp_two.move_relative,
                                            self.wp_both_target_relative, True)
        self.moving_two_thread.start()

    def use_keyboard_move(self):
        """Set text of keyboard_label to using keyboard.
        Collect events until released.
        """
        if self.keyboard_use_move == False:
            self.worker_move_thread = WorkThread(
                self.create_keyboard_listener_move)
            self.worker_move_thread.start()
            self.keyboard_button_move.setStyleSheet("background-color: green")
            self.keyboard_use_move = True

        else:
            self.keyboard_button_move.setStyleSheet("")
            self.keyboard_use_move = False
            if self.worker_move_thread.isRunning():
                self.listener_move.stop()

    def create_keyboard_listener_move(self):
        with Listener(on_press=self.on_press_move,
                      on_release=self.on_release_move) as self.listener_move:
            self.listener_move.join()

    def on_press_move(self, key):
        """
        In this method if the w is pressed the thorlabs_motor
        selected in the combobox will move forward or if
        s is pressed the thorlabs_motor will move backward.
        The w and s are written as: "'w'"/"'s'" because of syntacs.
        """

        if str(key) == "'y'":
            self.moving_one_thread = WorkThread(self.wp_one.move_velocity, 2,
                                                True)
            self.moving_one_thread.start()

        elif str(key) == "'u'":
            self.moving_one_thread = WorkThread(self.wp_one.move_velocity, 1,
                                                True)
            self.moving_one_thread.start()

        elif str(key) == "'h'":
            self.moving_two_thread = WorkThread(self.wp_two.move_velocity, 2,
                                                True)
            self.moving_two_thread.start()

        elif str(key) == "'j'":
            self.moving_two_thread = WorkThread(self.wp_two.move_velocity, 1,
                                                True)
            self.moving_two_thread.start()

        elif str(key) == "'n'":
            self.moving_two_thread = WorkThread(self.wp_two.move_velocity, 2,
                                                True)
            self.moving_one_thread = WorkThread(self.wp_one.move_velocity, 2,
                                                True)
            self.moving_one_thread.start()
            self.moving_two_thread.start()

        elif str(key) == "'m'":
            self.moving_two_thread = WorkThread(self.wp_two.move_velocity, 1,
                                                True)
            self.moving_one_thread = WorkThread(self.wp_one.move_velocity, 1,
                                                True)
            self.moving_one_thread.start()
            self.moving_two_thread.start()

    def on_release_move(self, key):
        """
        In this method if the w or s is released the thorlabs_motor will stop moving.
        If q is released the keyboard mode stops.
        """
        if str(key) == "'y'" or str(key) == "'u'":
            #stop the thorlabs_motor from going
            self.moving_one_thread.quit()
            self.wp_one.stop_moving()

        elif str(key) == "'h'" or str(key) == "'j'":
            #stop the thorlabs_motor from going
            self.moving_two_thread.quit()
            self.wp_two.stop_moving()

        elif str(key) == "'n'" or str(key) == "'m'":
            # stop the thorlabs_motor from going
            self.moving_one_thread.quit()
            self.wp_one.stop_moving()
            self.moving_two_thread.quit()
            self.wp_two.stop_moving()
Пример #5
0
class Hydraharp_GUI(BaseGui):
    """
    GUI class for the Hydraharp correlator instrument

    :param hydra_instrument: instrument to control with the GUI
    :type hydra_instrument: instance of the class for the instrument to control
    :param draw: a window where the plotting o the data acquired will be shown.
    :type draw: a plot widget class
    """
    def __init__(self, hydra_instrument, draw=None, also_close_output=False):
        super().__init__()
        self.logger = logging.getLogger(__name__)
        self.title = 'Hydraharp400 correlator gui'
        self.left = 50
        self.top = 50
        self.width = 320
        self.height = 200
        self.histogram_number = 0
        self.grid_layout = QGridLayout()
        self.setLayout(self.grid_layout)

        self.hydra_instrument = hydra_instrument

        if type(draw) is dict:
            self.draw = list(draw.values())[0]
        else:
            self.draw = draw

        #default values, could be put in a yml file as well
        self.array_length = 65536
        self.resolution = "1ps"
        self.integration_time = 5 * ur('s')
        self.channel = '0'
        self.time_passed = 0 * ur(
            's')  #which also makes sure that the units are the same

        self.max_time = 24 * ur('hour')
        self.max_length = 65536

        self.endtime = []
        self.time_axis = []
        self.units = 's'

        self.hydra_instrument.configurate()
        self.initUI()

        #This one is to continuously (= every 100ms) show the remaining time
        self.timer = QTimer()
        self.timer.timeout.connect(self.show_time_passed)

        # timer to update
        self.timer_plot = QTimer()
        self.timer_plot.timeout.connect(self.update_plot)

        self.histogram_thread = WorkThread(
            self.hydra_instrument.make_histogram, self.integration_time,
            self.channel)

        self.stop = self.stop_histogram

    def initUI(self):
        self.setWindowTitle(self.title)
        self.setGeometry(self.left, self.top, self.width, self.height)

        self.show()

        self.make_groupBoxes()
        self.make_basics()
        self.make_user_input()
        self.make_saving_input()

    def make_groupBoxes(self):
        self.groupBox_basic = QGroupBox()
        self.grid_layout.addWidget(self.groupBox_basic, 0, 1)

        self.groupBox_basic_layout = QVBoxLayout()
        self.groupBox_basic.setLayout(self.groupBox_basic_layout)
        self.groupBox_basic.setStyleSheet(
            "QGroupBox {border: 1px solid orange; border-radius: 9px;}")

        self.groupBox_values = QGroupBox()
        self.grid_layout.addWidget(self.groupBox_values, 0, 0)

        self.groupBox_values_layout = QGridLayout()
        self.groupBox_values.setLayout(self.groupBox_values_layout)
        self.groupBox_values.setStyleSheet(
            "QGroupBox {border: 1px solid orange; border-radius: 9px;}")

        self.groupBox_saving = QGroupBox()
        self.grid_layout.addWidget(self.groupBox_saving, 1, 0, 1, 2)

        self.groupBox_saving_layout = QGridLayout()
        self.groupBox_saving.setLayout(self.groupBox_saving_layout)
        self.groupBox_saving.setStyleSheet(
            "QGroupBox {border: 1px solid orange; border-radius: 9px;}")

    def make_basics(self):
        self.take_histogram_button = QPushButton('take histogram', self)
        self.take_histogram_button.setToolTip('take the histogram')
        self.take_histogram_button.clicked.connect(self.take_histogram)

        self.stop_histogram_button = QPushButton('stop histogram', self)
        self.stop_histogram_button.setToolTip(('stop your histogram'))
        self.stop_histogram_button.clicked.connect(self.stop_histogram)
        self.stop_histogram_button.setStyleSheet("background-color: red")

        self.showing_remaining_time = QLabel(self)
        self.showing_remaining_time.setText(str(self.time_passed))

        self.progressbar = QProgressBar(self)
        self.progressbar.setMaximum(int(self.integration_time.magnitude))
        self.progressbar.setValue(int(self.time_passed.magnitude))
        self.progressbar.setTextVisible(False)
        self.progressbar.valueChanged.connect(lambda: self.show_time_passed)

        self.groupBox_basic_layout.addWidget(self.take_histogram_button)
        self.groupBox_basic_layout.addWidget(self.stop_histogram_button)
        self.groupBox_basic_layout.addWidget(self.showing_remaining_time)
        self.groupBox_basic_layout.addWidget(self.progressbar)

    def make_user_input(self):
        self.integration_time_label = QLabel("Integration time: ")
        self.resolution_label = QLabel("Resolution: ")
        self.channel_label = QLabel("Channel: ")
        self.array_length_label = QLabel("Array length: ")
        self.array_length_label.setEnabled(False)
        self.time_axis_label = QLabel("Time on axis: ")

        self.integration_time_spinbox = QSpinBox(self)
        self.integration_time_spinbox.setValue(self.integration_time.m_as('s'))
        self.integration_time_spinbox.valueChanged.connect(
            self.set_integration_time)

        self.time_unit_combobox = QComboBox(self)
        self.time_unit_combobox.addItems(["s", "min", "hour"])
        self.time_unit_combobox.setCurrentText('s')
        self.time_unit_combobox.currentTextChanged.connect(
            self.set_integration_time)

        self.array_length_spinbox = QSpinBox(self)
        self.array_length_spinbox.setMaximum(999999999)
        self.array_length_spinbox.setValue(self.array_length)
        self.array_length_spinbox.setEnabled(False)
        self.array_length_spinbox.valueChanged.connect(self.set_array_length)

        self.resolution_combobox = QComboBox(self)
        #self.resolution_spinbox.setMaximum(999999999)
        #self.resolution_spinbox.setValue(self.resolution.m_as('ps'))
        #self.resolution_spinbox.setSuffix('ps')
        self.resolution_combobox.addItems([
            "1ps", "2ps", "4ps", "8ps", "16ps", "32ps", "64ps", "128ps",
            "256ps", "512ps", "1024ps"
        ])
        self.resolution_combobox.setCurrentText(self.resolution)
        self.resolution_combobox.currentTextChanged.connect(
            self.set_resolution)

        self.endtime_label = QLabel(self)
        self.calculate_axis()

        self.channel_combobox = QComboBox(self)
        self.channel_combobox.addItems(["0", "1"])
        self.channel_combobox.setCurrentText(self.channel)
        self.channel_combobox.currentTextChanged.connect(self.set_channel)

        self.groupBox_values_layout.addWidget(self.integration_time_label, 0,
                                              0)
        self.groupBox_values_layout.addWidget(self.resolution_label, 1, 0)
        self.groupBox_values_layout.addWidget(self.channel_label, 2, 0)
        self.groupBox_values_layout.addWidget(self.array_length_label, 3, 0)
        self.groupBox_values_layout.addWidget(self.time_axis_label, 4, 0)

        self.groupBox_values_layout.addWidget(self.integration_time_spinbox, 0,
                                              1)
        self.groupBox_values_layout.addWidget(self.time_unit_combobox, 0, 2)
        self.groupBox_values_layout.addWidget(self.resolution_combobox, 1, 1)
        self.groupBox_values_layout.addWidget(self.channel_combobox, 2, 1)
        self.groupBox_values_layout.addWidget(self.array_length_spinbox, 3, 1)
        self.groupBox_values_layout.addWidget(self.endtime_label, 4, 1)

    def make_saving_input(self):
        self.export_label = QLabel("Export file: ")

        self.save_histogram_button = QPushButton('save histrogram', self)
        self.save_histogram_button.setToolTip('save your histogram in a file')
        self.save_histogram_button.setEnabled(False)
        self.save_histogram_button.clicked.connect(self.save_histogram)

        self.export_textfield = QLineEdit(self)
        self.export_textfield.setText(root_dir)

        self.groupBox_saving_layout.addWidget(self.export_label, 0, 0)
        self.groupBox_saving_layout.addWidget(self.save_histogram_button, 0,
                                              3)  # 5,4
        self.groupBox_saving_layout.addWidget(self.export_textfield, 0, 1, 1,
                                              2)

    #------------------------------------------------------------------------------------
    def show_time_passed(self):
        """This method asks the remaining time from the instrument level,
        and calculates the progress, so both can be displayed.
        """
        self.time_passed = self.hydra_instrument.time_passed
        self.showing_remaining_time.setText(str(self.time_passed))

        self.progressbar.setMaximum(int(self.integration_time.magnitude))
        self.progressbar.setValue(int(self.time_passed.magnitude))
        #print(self.hydra_instrument.remaining_time)

    # ------------------------------------------------------------------------------------
    def set_channel(self):
        """ This method sets the channel that the user puts, and remembers the string in the init in self.channel.
        """
        self.logger.debug('setting the channel')

        self.channel = self.channel_combobox.currentText()
        self.logger.debug('channel: ' + self.channel)

    def set_array_length(self):
        """This method sets the array length that the user puts,
        and remembers the int in the init in self.array_length.
        It compares it to a max and min value.
        """
        self.logger.debug('setting the array length')
        self.logger.warning('are you sure you want to change this value?')

        if self.sender().value() > self.max_length:
            self.sender().setValue(self.max_length)
        elif self.sender().value() < 1:
            self.sender().setValue(1)

        self.calculate_axis()
        self.endtime_label.setText(str(self.endtime))

        self.array_length = int(self.sender().value())

    def set_integration_time(self):
        """This method combines the integration time that the user puts in the spinbox with the unit in the combobox,
        and remembers the pint quantity in the init in self.integration_time.
        It compares it to a max (24 hours) and min (1 s) value.
        """
        self.logger.debug('setting the integration time')

        tijd = self.integration_time_spinbox.value()
        unit = self.time_unit_combobox.currentText()

        local_time = ur(str(tijd) + unit)

        self.logger.debug('local time value: ' + str(local_time))

        if local_time > self.max_time:
            self.logger.debug('time really too much')
            local_time = self.max_time.to(unit)
            self.logger.debug(str(local_time))
            self.integration_time_spinbox.setValue(local_time.m_as(unit))
        elif local_time < 1 * ur('s'):
            self.logger.debug('you need to integrate more time')
            local_time = 1 * ur('s')
            self.integration_time_spinbox.setValue(local_time.m_as('s'))

        self.integration_time = local_time
        self.logger.debug('time remembered is: ' + str(self.integration_time))

    def set_resolution(self):
        """| This method takes the chosen resolution by the user and remembers it for the rest of this class.
        | It would be cool if I could make it a spinbox, that only allows values of 2^n, but I couldnt make that work...
        """
        self.logger.debug('setting the resolution')

        self.resolution = self.resolution_combobox.currentText()
        self.logger.debug('resolution: ' + self.resolution)

        self.calculate_axis()
        #
        # value = self.sender().value()
        #
        # Array = np.zeros(20)
        # for ii in range(20):
        #     Array[ii] = 2**ii
        #
        # if value not in Array:
        #     self.logger.debug('not in A')
        #     Diff = abs(Array - value)
        #     index = np.where(Diff == min(Diff))
        #     index = index[0][0]
        #     self.sender().setValue(Array[index+1])
        #     self.logger.debug('new value: ' + str(Array[index+1]))
        # else:
        #     index = np.where(Array == value)
        #     index = index[0][0]
        #     self.logger.debug('value: ' + str(Array[index]))
        #
        # #self.sender().setValue(Array[index+1])
        #
        # self.resolution = self.sender().value()*ur('ps')
        #
        #
        # self.endtime_label.setText(str(round(self.endtime.to(self.units), 4)))
        #
        # self.logger.debug(str(self.sender().value()))

    def calculate_axis(self):
        """| This method calculates the axis that should be put on the graph.
        | This end time is both displayed in the gui and used for the graph time axis.
        """
        self.endtime = round(
            ur(self.resolution).m_as('ns') * self.array_length * ur('s'), 4)

        if self.endtime.m_as(
                's') < 120:  # below two minutes, display in seconds
            self.units = 's'
        elif self.endtime.m_as(
                's') < 120 * 60:  # below two hours, display in minutes
            self.units = 'min'
        elif self.endtime.m_as(
                's') < 120 * 60 * 24:  # below two days, display in hours
            self.units = 'hour'
        else:
            self.units = 'days'

        self.endtime_label.setText(str(round(self.endtime.to(self.units), 4)))

        #self.logger.debug('endtime: {}, units: {}, array length: {}'.format(self.endtime, self.units, self.array_length))
        self.time_axis = np.linspace(0, float(self.endtime.m_as(self.units)),
                                     self.array_length)
        #self.logger.debug('time axis: {}'.format(self.time_axis))

    #------------------------------------------------------------------------------------
    def take_histogram(self):
        """| In this method there will be made a histogram using the input of the user.
        | All user inputs were stored previously in the values as declared in the init
        | (histogram length, resolution, integration time and channel).
        | A thread is started to be able to also stop the histogram taking.
        | The data gets plot in the DrawHistogram plot(self.draw.histogram_plot.plot()).
        | The time axis is calculated in calculate_axis and used for the plot.
        """
        self.logger.info("Taking the histrogram")

        #first, set the array length and resolution of the histogram
        self.logger.debug('chosen histogram length: ' + str(self.array_length))
        self.logger.debug('chosen resolution: ' + str(self.resolution))

        self.hydra_instrument.set_histogram(self.array_length,
                                            ur(self.resolution))

        #Then, start the histogram + thread
        self.logger.debug('chosen integration time: ' +
                          str(self.integration_time))
        self.logger.debug('chosen channel: ' + str(self.channel))

        self.timer.start(100)
        self.timer_plot.start(100)
        self.show_time_passed()
        self.histogram_thread = WorkThread(
            self.hydra_instrument.make_histogram, self.integration_time,
            self.channel)
        self.histogram_thread.start()

        #make it possible to press the save_histogram_button.(should be True)
        self.save_histogram_button.setEnabled(True)
        self.take_histogram_button.setEnabled(True)

        self.hydra_instrument.time_passed = 0 * ur('s')
        self.show_time_passed()

    def update_plot(self):
        pen = pg.mkPen(color=(0, 0, 0))  # makes the plotted lines black
        if self.hydra_instrument.hist_ended:
            self.take_histogram_button.setEnabled(True)
            self.timer_plot.stop()
            self.timer.stop()
            self.histogram = self.hydra_instrument.hist
            self.calculate_axis()
            self.draw.histogram_plot.plot(self.time_axis,
                                          self.histogram,
                                          clear=True,
                                          pen=pen)
            self.draw.histogram_plot.setLabel(
                'bottom',
                "<span style=\"color:black;font-size:20px\"> Time ({}) </span>"
                .format(self.units))

        else:
            self.take_histogram_button.setEnabled(False)
            self.histogram = self.hydra_instrument.hist

            self.calculate_axis()
            self.logger.debug(
                'length time axis: {}, length histogram: {}'.format(
                    len(self.time_axis), len(self.histogram)))

            if len(self.histogram) > 0:
                self.draw.histogram_plot.plot(self.time_axis,
                                              self.histogram,
                                              clear=True)
                self.draw.histogram_plot.setLabel(
                    'bottom',
                    "<span style=\"color:black;font-size:20px\"> Time ({}) </span>"
                    .format(self.units))

    def save_histogram(self):
        """| In this method the made histogram gets saved.
        | This is done with pyqtgraph.exporters. The width and height can be set of the picture below.
        """
        self.logger.info('saving the histogram')
        try:
            plt = pg.plot(self.histogram)
            exporter = pg.exporters.ImageExporter(plt.plotItem)
            # set export parameters if needed
            exporter.parameters(
            )['height'] = 100  # (note this also affects width parameter)
            exporter.parameters(
            )['width'] = 100  # (note this also affects height parameter)
            self.actually_save_histogram(exporter)
            #there must first be made another(or the same) histogram before this method can be accessed.(should be False)
            self.save_histogram_button.setEnabled(False)
            plt.close()
        except Exception:
            self.logger.warning(
                "There is no picture to export...change that by clicking the button above"
            )

    def actually_save_histogram(self, exporter):
        """|  In this method it is defined what the file_name is via checking if there is text in the export textfield.
        |If there is none, than a file_chooser will be used to have a location where the .png's will be saved.

        :param exporter: A exporter object with which you can save data
        :type exporter: pyqtgraph.exporter, doesn't say that much, I know
        """

        if self.export_textfield.text() != "":
            # save to file via the textfield
            file_name = self.export_textfield.text() + "\\histogram_" + str(
                self.histogram_number) + ".png"
            self.histogram_number += 1
            exporter.export(file_name)
        else:
            #a file chooser will be used
            file_name = self.get_file_path_via_filechooser()
            exporter.export(file_name)
        #self.make_progress_label.setText("The histogram has been saved at: \n" + str(file_name))

    def get_file_path_via_filechooser(self):
        """| This is code plucked from the internet...so I have no clou what is happening and that is fine really.
        | If the code breaks, go to: https://pythonspot.com/pyqt5-file-dialog/

        :return: the filepath, .png needs to be attached in order to save the picture as a...picture
        :rtype: string
        """
        options = QFileDialog.Options()
        options |= QFileDialog.DontUseNativeDialog
        fileName, _ = QFileDialog.getSaveFileName(
            self,
            "QFileDialog.getSaveFileName()",
            "",
            "All Files (*);;Text Files (*.txt)",
            options=options)
        return fileName + ".png"

    def stop_histogram(self):
        """| Here the self.hydra_instrument.stop is set to True, which means the while loop in the instrument breaks.
        | Afterwards, the hydraharp itself is actually set to stop.
        | To avoid errors, it is important to quit the thread.
        """
        self.logger.info('Histogram should stop here')
        self.hydra_instrument.stop = True
        self.hydra_instrument.stop_histogram()

        if self.histogram_thread.isRunning:
            self.logger.debug('histogram thread was running')
            self.histogram_thread.quit()

        self.hydra_instrument.time_passed = 0 * ur('s')
        self.show_time_passed()
        self.hydra_instrument.stop = False
class App(QWidget): # CHANGE THIS F##%% name!!!

    def __init__(self, motor_hub):
        """
        This init of this class,
        make sure that the .yml file is correctly configurated, else it will not work, trust me, I am an expert.
        """
        super().__init__()
        self.title = 'thorlabs motors GUI'
        self.left = 50
        self.top = 50
        self.width = 400
        self.height = 200
        self.grid_layout = QGridLayout()
        self.setLayout(self.grid_layout)
        self.motor_hub = motor_hub
        self.motor_bag = {}
        self.slider_dict = {}
        self.position_1_all_motors_dict = {}
        self.position_2_all_motors_dict = {}
        self.position_3_all_motors_dict = {}
        self.velocity_motors_dict = {}
        self.initUI()
        
    def initUI(self):
        self.setWindowTitle(self.title)
        self.setGeometry(self.left, self.top, self.width, self.height)
        
        self.motor_bag = self.motor_hub.initialize_available_motors(self.motor_bag)
        
        self.make_misc_gui_stuff()
        self.make_labels()
        self.make_buttons()
        
        self.show()
        
    def make_labels(self):    
        #make labels:
        self.make_save_pos_1_label()
        self.make_save_pos_2_label()
        self.make_save_pos_3_label()
        self.make_recover_1_label()
        self.make_recover_2_label()
        self.make_recover_3_label()
        self.make_x_coordinate_label()
        self.make_y_coordinate_label()
        self.make_z_coordinate_label()
        self.make_x_coordinate_second_label()
        self.make_y_coordinate_second_label()
        self.make_z_coordinate_second_label()
        self.make_motor_x_current_position_label()
        self.make_motor_y_current_position_label()
        self.make_motor_z_current_position_label()
        self.make_use_keyboard_label()
        self.make_go_faster_label()
    def make_buttons(self):
        #make buttons
        self.make_save_pos_3_button()
        self.make_save_pos_1_button()
        self.make_save_pos_2_button()
        self.make_recover_1_button()
        self.make_recover_2_button()
        self.make_recover_3_button()
        self.make_go_home_button()
        self.make_go_to_button()
        self.make_use_keyboard_button()
        
    def make_misc_gui_stuff(self):
        #make all the miscellaneous gui stuff. 
        """
        In this method th slider and other miscellaneous gui stuff will be made.
        The slider get made by adding a tuple with the name of the slider and the name of the
        thorlabs_motor that the slider will use.
        """
        slider_list = self.motor_hub.make_slider_list()
        opteller = 1
        for slider in slider_list:    
            self.make_slider(lambda: slider[0], slider[1], opteller)
            opteller += 2
        self.make_go_faster_slider()
        self.make_dropdown_motor()
        self.make_go_to_input_textfield()
    def make_save_pos_1_label(self):
        label = QLabel(self)
        label.setText("save pos. 1:")
        self.grid_layout.addWidget(label, 1, 0)        
    def make_save_pos_2_label(self):
        label = QLabel(self)
        label.setText("save pos. 2:")
        self.grid_layout.addWidget(label, 2, 0)        
    def make_save_pos_3_label(self):
        label = QLabel(self)
        label.setText("save pos. 3:")
        self.grid_layout.addWidget(label, 3, 0)
        
    def make_recover_1_label(self):
        label = QLabel(self)
        label.setText("recover 1:")
        self.grid_layout.addWidget(label, 1, 2)
    def make_recover_2_label(self):
        label = QLabel(self)
        label.setText("recover 2:")
        self.grid_layout.addWidget(label, 2, 2)
    def make_recover_3_label(self):
        label = QLabel(self)
        label.setText("recover 3:")
        self.grid_layout.addWidget(label, 3, 2)
        
    def make_x_coordinate_second_label(self):
        label = QLabel(self)
        label.setText("x: ")
        self.grid_layout.addWidget(label, 1, 4)
    def make_y_coordinate_second_label(self):
        label = QLabel(self)
        label.setText("y: ")
        self.grid_layout.addWidget(label, 2, 4)
    def make_z_coordinate_second_label(self):
        label = QLabel(self)
        label.setText("z: ")
        self.grid_layout.addWidget(label, 3, 4)
        
    def make_x_coordinate_label(self):
        label = QLabel(self)
        label.setText("speed\nx: ")
        self.grid_layout.addWidget(label, 4, 0)
    def make_y_coordinate_label(self):
        label = QLabel(self)
        label.setText("speed\ny: ")
        self.grid_layout.addWidget(label, 4, 2)        
    def make_z_coordinate_label(self):
        label = QLabel(self)
        label.setText("speed\nz: ")
        self.grid_layout.addWidget(label, 4, 4)
    
    def make_motor_x_current_position_label(self):
        """
        update the x position label.
        The same is done for the y and z in make_motor_y/z_current_position_label
        """
        self.current_motor_x_position_label = QLabel(self)
        try:
            self.current_motor_x_position_label.setText(self.motor_bag[self.motor_combobox.currentText()].controller.position())
        except Exception:
            self.current_motor_x_position_label.setText("currently\nunavailable")
        self.grid_layout.addWidget(self.current_motor_x_position_label, 1, 5)
    def make_motor_y_current_position_label(self):
        self.current_motor_y_position_label = QLabel(self)
        try:
            self.current_motor_y_position_label.setText(self.motor_bag[self.motor_combobox.currentText()].controller.position())    
        except Exception:
            self.current_motor_y_position_label.setText("currently\nunavailable")
        self.grid_layout.addWidget(self.current_motor_y_position_label, 2, 5)
    def make_motor_z_current_position_label(self):
        self.current_motor_z_position_label = QLabel(self)
        try:
            self.current_motor_z_position_label.setText(self.motor_bag[self.motor_combobox.currentText()].controller.position())    
        except Exception:
            self.current_motor_z_position_label.setText("currently\nunavailable")
        self.grid_layout.addWidget(self.current_motor_z_position_label, 3, 5)
        
    def make_use_keyboard_label(self):
        self.keyboard_label = QLabel(self)
        self.keyboard_label.setText("use keyboard\nto control selected\n combobox thorlabs_motor:")
        self.grid_layout.addWidget(self.keyboard_label, 3, 6)
    def make_go_faster_label(self):
        self.go_faster_label = QLabel(self)
        self.go_faster_label.setText("change\nthorlabs_motor speed:")
        self.grid_layout.addWidget(self.go_faster_label, 4, 6)
        
    #make buttons:
    def make_save_pos_1_button(self):
        self.save_1_button = QPushButton('save pos 1', self)
        self.save_1_button.setToolTip('save the first position')
        #self, button, color, position_all_motors_dict
        self.save_1_button.clicked.connect(lambda: self.save_position_for_all_motors(self.save_1_button, "green", self.position_1_all_motors_dict))
        self.grid_layout.addWidget(self.save_1_button, 1, 1)    
    def make_save_pos_2_button(self):
        self.save_2_button = QPushButton('save pos 2', self)
        self.save_2_button.setToolTip('save the second position')
        self.save_2_button.clicked.connect(lambda: self.save_position_for_all_motors(self.save_2_button, "yellow", self.position_2_all_motors_dict))
        self.grid_layout.addWidget(self.save_2_button, 2, 1)        
    def make_save_pos_3_button(self):
        self.save_3_button = QPushButton('save pos 3', self)
        self.save_3_button.setToolTip('save the third position')
        self.save_3_button.clicked.connect(lambda: self.save_position_for_all_motors(self.save_3_button, "red", self.position_3_all_motors_dict))
        self.grid_layout.addWidget(self.save_3_button, 3, 1)
        
    def make_recover_1_button(self):
        button = QPushButton('recover 1', self)
        button.setToolTip('recover the first position')
        button.clicked.connect(lambda: self.recover_position_all_motors(self.position_1_all_motors_dict))
        self.grid_layout.addWidget(button, 1, 3)        
    def make_recover_2_button(self):
        button = QPushButton('recover 2', self)
        button.setToolTip('recover the second position')
        button.clicked.connect(lambda: self.recover_position_all_motors(self.position_2_all_motors_dict))
        self.grid_layout.addWidget(button, 2, 3)        
    def make_recover_3_button(self):
        button = QPushButton('recover 3', self)
        button.setToolTip('recover the third position')
        button.clicked.connect(lambda: self.recover_position_all_motors(self.position_3_all_motors_dict))
        self.grid_layout.addWidget(button, 3, 3)
        
    def make_go_home_button(self):
        self.go_home_button = QPushButton('go home', self)
        self.go_home_button.setToolTip('go to home position')
        self.go_home_button.clicked.connect(self.go_home_motor)
        self.grid_layout.addWidget(self.go_home_button, 1, 6)
    def make_go_to_button(self):
        self.move_button = QPushButton('move to in um', self)
        self.move_button.setToolTip('move to given input')
        self.move_button.clicked.connect(self.go_to_input)
        self.grid_layout.addWidget(self.move_button, 2, 6)
    def make_use_keyboard_button(self):
        self.use_keyboard_button = QPushButton('use keyboard', self)
        self.use_keyboard_button.setToolTip('use keyboard to control motors')
        self.use_keyboard_button.clicked.connect(self.control_motor_with_keyboard)
        self.grid_layout.addWidget(self.use_keyboard_button, 3, 7)
    
    #make misc gui stuff:
    def make_slider(self, slider, motor_name, opteller):
        """
        In this method sliders get made automatically. 
        This done by getting the name of the slider an setting it in a slider_dict.
        The rest of the parameters get set in this method.
        
        :param slider: a slider name
        :type string
        :param motor_name: the name of the thorlabs_motor which connects with the slider
        :type string
        :param opteller: indication on which grid the slider must be set
        :type int
        """
        self.slider_ding = QSlider(Qt.Vertical, self)
        self.slider_ding.setFocusPolicy(Qt.StrongFocus)
        self.slider_ding.setTickPosition(QSlider.TicksBothSides)
        self.slider_ding.setMinimum(1)
        self.slider_ding.setMaximum(9)
        self.slider_ding.setValue(5)
        self.slider_ding.setTickInterval(1)
        self.slider_ding.setSingleStep(1)
        self.slider_ding.sliderReleased.connect(lambda: self.set_slider_to_middle(slider, motor_name))
        self.slider_ding.sliderMoved.connect(lambda: self.make_slider_motor_move(slider, motor_name))
        
        self.slider_dict[slider] = self.slider_ding
        self.grid_layout.addWidget(self.slider_dict[slider], 4, opteller)
    
    def set_slider_to_middle(self, slider, motor_name):
        """
        In this method an slider is set to the middle. This is done 
        through connecting a signal and slot.
        
        :param slider_object: an slider that must be set to it's middle
        :type QSlider
        :param motor_name: the name of the thorlabs_motor to stop
        :type string
        """
        self.slider_dict[slider].setValue(5)
        self.motor_bag[motor_name].controller.stop_profiled()
        
    def make_slider_motor_move(self, slider, motor_name):
        """
        In this method the thorlabs_motor moves if the slider is moved.
        
        :param slider_object: the slider that is being moved
        :type QSlider
        :param motor_name: the name of the thorlabs_motor to move
        :type string
        """
        if self.slider_dict[slider].value() > 5:
            #moving forward
            self.motor_bag[motor_name].controller.move_velocity(2)
        elif self.slider_dict[slider].value() < 5:
            #moving reverse
            self.motor_bag[motor_name].controller.move_velocity(1)    
    def make_go_faster_slider(self):
        """

        """
        self.go_faster_slider = QSlider(Qt.Horizontal, self)
        self.go_faster_slider.setFocusPolicy(Qt.StrongFocus)
        self.go_faster_slider.setTickPosition(QSlider.TicksBelow)
        self.go_faster_slider.setMinimum(0)
        self.go_faster_slider.setMaximum(10)
        self.go_faster_slider.setValue(0)
        self.go_faster_slider.setTickInterval(1)
        self.go_faster_slider.setSingleStep(1)
        self.go_faster_slider.sliderReleased.connect(self.change_motor_speed)
        self.grid_layout.addWidget(self.go_faster_slider, 4, 7)
    def change_motor_speed(self):
        #print("for thorlabs_motor: "+str(self.motor_combobox.currentText())+" the limits are: "+str(self.motor_bag[self.motor_combobox.currentText()].controller.get_velocity_parameter_limits()))
        output = self.motor_bag[self.motor_combobox.currentText()].controller.get_velocity_parameters()
        # minimum velocity = output[0]
        # acceleration = ouput[1]
        # maximum velocity = output[2]
        if not self.motor_combobox.currentText() in self.velocity_motors_dict:
            #setting the original highest value of the thorlabs_motor to prevent degrading in speed.
            self.velocity_motors_dict[self.motor_combobox.currentText()] = output[2]
        if (output[0] - output[2]) == 0:
            #the numbers are equal meaning that the speed cannot change
            print("you cannot change the speed of this thorlabs_motor")
        else:
            #the speed can change
            total_speed = self.velocity_motors_dict[self.motor_combobox.currentText()] - output[0]
            tenth_of_total_speed = total_speed / 10
            print(tenth_of_total_speed)
            slider_value = self.go_faster_slider.value()
            print(slider_value)
            new_speed = slider_value * tenth_of_total_speed
            print("the max speed: "+str(self.velocity_motors_dict[self.motor_combobox.currentText()])+"the new speed "+str(new_speed))
            print("-"*40)
            if new_speed < output[2] and new_speed > output[0]:
                self.motor_bag[self.motor_combobox.currentText()].controller.set_velocity_parameters(output[0], output[1], new_speed)
            
    def make_dropdown_motor(self):    
        list_with_motors = []
        self.motor_combobox = QComboBox(self)
        #these are all the available motors:
        for index in self.motor_bag.keys():
            list_with_motors.append(str(index))
        self.motor_combobox.addItems(list_with_motors)
        self.motor_combobox.currentIndexChanged.connect(self.set_current_motor_label)
        self.grid_layout.addWidget(self.motor_combobox, 1, 7)
    def make_go_to_input_textfield(self):
        self.input_textfield = QLineEdit(self)
        self.input_textfield.setText("0.01")
        self.grid_layout.addWidget(self.input_textfield, 2, 7)
    
    def set_current_motor_label(self):
        """
        The position for the x, y, z motor will be set in this method.
        If another motor is found the x label will change. Why x...because, IDK. What else to do.
        """
        if self.motor_combobox.currentText() == "zMotor":
            self.current_motor_x_position_label.setText(str(round(self.motor_bag[self.motor_combobox.currentText()].controller.position, 2)))
        elif self.motor_combobox.currentText() == "yMotor": 
            self.current_motor_y_position_label.setText(str(round(self.motor_bag[self.motor_combobox.currentText()].controller.position, 2)))
        elif self.motor_combobox.currentText() == "testMotor":
            self.current_motor_z_position_label.setText(str(round(self.motor_bag[self.motor_combobox.currentText()].controller.position, 2)))
        else:
            #this is a thorlabs_motor that is not the zMotor, yMotor or the testMotor, so
            #let's set the x position to something else
            self.current_motor_x_position_label.setText(str(round(self.motor_bag[self.motor_combobox.currentText()].controller.position, 2)))
            
            
        
    def go_home_motor(self):
        selected_motor = str(self.motor_combobox.currentText())
        self.motor_bag[selected_motor].controller.move_home(True)
        self.set_current_motor_label()
        
    def go_to_input(self):
        selected_motor = str(self.motor_combobox.currentText())
        try:
            go_to_input = self.input_textfield.text() * ur('micrometer')
            self.motor_bag[selected_motor].move_absolute(float(go_to_input.magnitude))
            self.set_current_motor_label()
        except ValueError:
            print("The input is not a float, change this")
            return
        
    def on_press(self, key):
        """ 
        In this method if the w is pressed the thorlabs_motor
        selected in the combobox will move forward or if 
        s is pressed the thorlabs_motor will move backward.
        The w and s are written as: "'w'"/"'s'" because of syntacs.
        """
        if str(key) == "'w'":
            #forward
            self.set_current_motor_label()
            self.motor_bag[self.motor_combobox.currentText()].controller.move_velocity(2)
            self.set_current_motor_label()
        elif str(key) == "'s'":
            #backwards
            self.set_current_motor_label()
            self.motor_bag[self.motor_combobox.currentText()].controller.move_velocity(1)
            self.set_current_motor_label()
    def on_release(self, key):
        """
        In this method if the w or s is released the thorlabs_motor will stop moving.
        If q is released the keyboard mode stops. 
        """
        if str(key) == "'w'" or str(key) == "'s'":
            #stop the thorlabs_motor from going
            self.motor_bag[self.motor_combobox.currentText()].controller.stop_profiled()
            self.set_current_motor_label()
        elif str(key) == "'q'":
            # Stop listener
            if self.worker_thread.isRunning():
                self.set_current_motor_label()
                self.worker_thread.quit()
                self.worker_thread.wait()
                return False
    def control_motor_with_keyboard(self):
        """ 
        In this method with the Listener object you can 
        press a button on the keyboard and with that input a thorlabs_motor will move.
        
        """
        #set text of keyboard_label to using keyboard
        self.keyboard_label.setText("using keyboard/npress esc to exit")
        # Collect events until released
        self.worker_thread = WorkThread(self.create_keyboard_listener)
        self.worker_thread.start()
        
        #set the text back to you can use the keyboard.
        self.keyboard_label.setText("use keyboard\nto control selected\n combobox thorlabs_motor:")
    def create_keyboard_listener(self):
        with Listener(on_press=self.on_press, on_release=self.on_release) as listener:
            listener.join()
        
    def save_position_for_all_motors(self, button, color, position_all_motors_dict):
        #make sure the user knows the button is pressed by setting it to a different color
        button.setStyleSheet("background-color: "+color)
        #get positions
        for motor in self.motor_bag.items():
            #thorlabs_motor[0] == serial nummer
            #thorlabs_motor[1] == Thorlabs thorlabs_motor instance
            try:
                position = motor[1].controller.position
            except Exception:
                #the thorlabs_motor position has not been found, could be because it is a
                #piezo thorlabs_motor or because the software is not running as expected.
                print("for thorlabs_motor: "+ str(motor[0]) +" the position has not been set")
                position = None
            position_all_motors_dict[motor[0]] = position
    def recover_position_all_motors(self, position_all_motors_dict):
        #set position of motors
        #(this only works if the position of the motors is from the home position):
        #so, that should be changed. 
        if bool(position_all_motors_dict) == False:
            print("the positions have not been set!")
            return
        for motor in self.motor_bag.items():
            #thorlabs_motor[0] == serial nummer
            #thorlabs_motor[1] == Thorlabs thorlabs_motor instance
            retrieved_position = position_all_motors_dict[motor[0]]
            if retrieved_position != None and retrieved_position != motor[1].controller.position:
                motor[1].controller.set_position = float(retrieved_position)
class ExampleGui(QWidget):
    """"
    This is simple pyqt5 gui with the ability to create threads and stop them,
    that is harder than it sounds.
    """
    def __init__(self, example_ins):
        super().__init__()
        self.title = 'example gui'
        self.left = 40
        self.top = 40
        self.width = 320
        self.height = 200
        self.example_ins = example_ins
        self.initUI()

    def initUI(self):
        self.setWindowTitle(self.title)
        self.setGeometry(self.left, self.top, self.width, self.height)

        self.setAutoFillBackground(True)
        self.p = self.palette()
        self.set_color(Qt.red)
        self.make_button_1()
        self.make_button_2()
        self.show()

    def make_button_1(self):
        self.button = QPushButton('start button', self)
        self.button.setToolTip('This is an example button')
        self.button.move(10, 10)
        self.button.clicked.connect(self.on_click)

    def make_button_2(self):
        self.button_2 = QPushButton('end button', self)
        self.button_2.setToolTip('end the function')
        self.button_2.move(90, 10)
        self.button_2.clicked.connect(self.stop_on_click_function)

    def set_color(self, color):
        """
        Set the color of the widget
        :param color: a color you want the gui to be
        :type string
        """
        self.p.setColor(self.backgroundRole(), color)
        self.setPalette(self.p)

    def on_click(self):
        #initialize a long(couple of seconds) test function.
        self.worker_thread = WorkThread(self.go_to_sleep)
        self.worker_thread.start()

    def stop_on_click_function(self):
        """
        stop a thread if one is running
        """
        if self.worker_thread.isRunning():
            self.worker_thread.quit()
            self.worker_thread.wait()
            print('this function is going to stop the on_click function')
        else:
            return

    def go_to_sleep(self):
        """
        function that starts the thread.
        """
        print('button click')
        self.button.setEnabled(False)
        self.set_color(Qt.yellow)
        time.sleep(4)
        self.set_color(Qt.red)
        self.button.setEnabled(True)
Пример #8
0
class BeamFlagsGui(QWidget):
    """
    Gui for beam blocker flags.
    Requires hyperion.instrument.misc.beam_flags_instr/BeamFlagsInstr instrument as input.

    :param beam_flags_instr: instrument object to control
    :type beam_flags_instr: an instance of the class
    """
    def __init__(self, beam_flags_instr, also_close_output=False):
        """
        Gui for beam blocker flags.

        :param beam_flags_instr: The beam flags instrument object to create the gui for
        :type beam_flags_instr: BeamFlagsInstr object
        """
        super().__init__()
        self.logger = logging.getLogger(__name__)

        self.left = 400
        self.top = 400
        self.width = 240
        self.height = 80

        self.bfi = beam_flags_instr
        self.bf_settings = self.bfi.settings['gui_flags']

        self.all_labels = {}

        if 'name' in self.bfi.settings:
            self.title = self.bfi.settings['name']
        else:
            self.title = 'Beam Flags'

        self.red_char = self.bfi.settings['flag_states']['red']
        self.green_char = self.bfi.settings['flag_states']['green']
        self.red_color = self.bfi.settings['gui_red_color']
        self.green_color = self.bfi.settings['gui_green_color']

        self.initUI()

        # Start timer to repeatedly pull the current state of the toggle switches:
        self.logger.info('Starting timer thread')
        # self._busy = False
        if 'gui_state_update_ms' in self.bfi.settings:
            self.indicator_update_time = self.bfi.settings[
                'gui_state_update_ms']
        else:
            self.indicator_update_time = 100  # ms

        #Pay attention: using the Qtimer makes the gui very slow
        #Instead, use a workthread
        self.timer_mode = False
        if self.timer_mode:
            self.timer = QTimer()
            self.timer.timeout.connect(self.update_label_states)
            self.timer.start(self.indicator_update_time)
        else:
            self._thread = WorkThread(self.update_wrapper)
            self._thread.start(priority=QThread.LowestPriority)

    def update_wrapper(self):
        while True:
            sleep(self.indicator_update_time / 1000.0)
            if not self._busy:
                self.update_label_states()
            else:
                self.logger.warning(
                    'Still busy: skipping checking for manual flag changes')

    def initUI(self):
        """
        Create all the gui elements and connect signals to methods.
        Adds 'state' and 'label' key and value to each flag in the settings dict.
        Reads the current state from the device and sets 'state' key and gui accordingly.
        """

        self.logger.info('Creating gui elements')
        self.setWindowTitle(self.title)
        self.setGeometry(self.left, self.top, self.width, self.height)

        layout = QGridLayout()

        for index, (flag_id, gui_flag) in enumerate(self.bf_settings.items()):
            self.logger.info("Adding flag '{}' to gui".format(flag_id))
            state = self.bfi.get_specific_flag_state(flag_id)
            self.bf_settings[flag_id]['state'] = state
            label = QLabel('')
            self.all_labels[flag_id] = label

            #self.bf_settings[flag_id]['label'] = label     #this will save a python qt object in the yml file = wrong

            button = QPushButton(gui_flag['name'], self)

            button.setSizePolicy(QSizePolicy.MinimumExpanding,
                                 QSizePolicy.MinimumExpanding)
            label.setSizePolicy(QSizePolicy.MinimumExpanding,
                                QSizePolicy.MinimumExpanding)

            button.pressed.connect(lambda x=flag_id: self.button_clicked(x))
            if 'shortkey' in gui_flag:
                shortk = gui_flag['shortkey']
                shortk_tip = '  (' + shortk + ')'
                button.setText(gui_flag['name'] + shortk_tip)
                shortcut = QShortcut(
                    QKeySequence(shortk),
                    self)  # not sure if this will work in our modular design
                shortcut.activated.connect(
                    lambda x=flag_id: self.button_clicked(x))
            else:
                shortk_tip = ''
                button.setStatusTip('Toggle ' + gui_flag['name'] + shortk_tip)

            label.setAlignment(Qt.AlignCenter)
            self.set_label_state(flag_id)

            layout.addWidget(button, index, 0)
            layout.addWidget(label, index, 1)

        self.setLayout(layout)
        self.show()

    def update_label_states(self):
        """ Asks device for current states of all beam flags and updates state key and gui if it has changed."""
        if self._busy:
            self.logger.warning()
            return
        self._busy = True
        if self.bfi.passive_update_from_manual_changes():
            for flag_id in self.bf_settings.keys():
                state = self.bfi.get_specific_flag_state(flag_id)
                if state != self.bf_settings[flag_id]['state']:
                    self.logger.debug(
                        "Manual state change detected of switch '{}': '{}'".
                        format(flag_id, state))
                    self.bf_settings[flag_id]['state'] = state
                    self.set_label_state(flag_id)
        self._busy = False

    def set_label_state(self, flag_id, keep_busy_flag=False):
        """ Sets gui label identified by flag_id to the corresponding value in the state key in settings."""
        self._busy = True
        label = self.all_labels[flag_id]
        state = self.bf_settings[flag_id]['state']
        self.logger.info("Set flag '{}' to '{}'".format(flag_id, state))
        if state == self.red_char:
            label.setStyleSheet('background-color: ' + self.red_color)
            label.setText(self.bf_settings[flag_id]['red_name'])
        elif state == self.green_char:
            label.setStyleSheet('background-color: ' + self.green_color)
            label.setText(self.bf_settings[flag_id]['green_name'])
        else:
            self.logger.warning('received state is unknown')
        self._busy = keep_busy_flag

    def button_clicked(self, flag_id):
        """
        The gui buttons connect to this function.
        It toggles the state flag of the corresponding beam flag in the settings dict.
        It creates and runs a thread that sets the state of the beam flag on the instrument.
        """
        self._busy = True
        self.logger.debug("Button of flag '{}' clicked".format(flag_id))
        state = self.bf_settings[flag_id]['state']
        if state == self.red_char:
            state = self.green_char
        elif state == self.green_char:
            state = self.red_char
        else:
            self.logger.warning('unknown state in internal dictionary')
        self.bf_settings[flag_id]['state'] = state
        self.set_label_state(flag_id, keep_busy_flag=True)
        self.bfi.set_specific_flag_state(flag_id,
                                         state)  # Actually toggle the flag
        self._busy = False
Пример #9
0
class Attocube_GUI(BaseGui):
    """
    | **Attocube piezo GUI for the instrument.**
    | Uses the attocube.ui file that was made with qt Designer. The maximum values for amplitude, frequency dcLevel on Scanner and distance are set here to be used in the rest of the class.
    | The start parameters for current_axis, current_move, direction and distance are set here, to be used and changed in the rest of the class.
    | A timer is started here to update the position real time.
    | A moving_thread is already made here, so the program doesnt break if somebody clicks stop before he has moved anywhere.

    :param anc350_instrument: class for the instrument to control.
    :type anc350_instrument: instance of the instrument class

    """
    def __init__(self, anc350_instrument, also_close_output=False):
        """Attocube
        """
        super().__init__()
        self.logger = logging.getLogger(__name__)
        self.title = 'Attocube GUI'
        self.anc350_instrument = anc350_instrument

        name = 'attocube.ui'
        gui_folder = os.path.dirname(os.path.abspath(__file__))
        gui_file = os.path.join(gui_folder, name)
        self.logger.debug('Loading the GUI file: {}'.format(gui_file))
        self.gui = uic.loadUi(gui_file, self)

        self.max_amplitude_V = 60
        self.max_frequency = 2000
        self.max_dclevel_V = 60 * ur(
            'V')  #Pay attention: this max only goes for 4K,
        self.max_dcLevel_mV_300K = 60 * ur(
            'V')  #at room temperature use 60V as max
        self.max_distance = 5 * ur('mm')

        self.current_positions = {}

        self.current_axis = 'X,Y Piezo Stepper'
        self.current_move = 'step'
        self.direction = 'left'
        self.distance = 0 * ur('um')

        self.settings = {
            'amplitudeX': 30,
            'amplitudeY': 40,
            'amplitudeZ': 30,
            'frequencyX': 1000,
            'frequencyY': 1000,
            'frequencyZ': 1000
        }

        self.temperature = 300

        self.scanner_unitX = 'V'
        self.scanner_unitY = 'V'
        self.scanner_unitZ = 'V'

        self.dcX = 1 * ur(self.scanner_unitX)
        self.dcY = 1 * ur(self.scanner_unitY)
        self.dcZ = 0 * ur(self.scanner_unitZ)

        self.stop = self.stop_moving

        self.initUI()

        #This one is to continuously (= every 100ms) show the position of the axes
        self.timer = QTimer()
        self.timer.timeout.connect(self.show_position)
        self.timer.start(100)  #time in ms

        self.moving_thread = WorkThread(self.anc350_instrument.move_to,
                                        self.current_axis, self.distance)

    def initUI(self):
        """Connect all buttons, comboBoxes and doubleSpinBoxes to methods
        """
        self.logger.debug('Setting up the Measurement GUI')
        self.setWindowTitle(self.title)

        self.show()

        self.make_combobox_scanner()
        self.make_combobox_movements()
        self.make_combobox_configurate()
        self.make_combobox_basic()

    def make_combobox_basic(self):
        """| *Layout of basic combobox*
        | Sets the blue border, the colour of the stop button and disables all other comboboxes.
        | Connects buttons and show_position, which works with a timer that is started in the init of this class.
        """
        self.gui.groupBox_basic.setObjectName("Colored_basic")
        self.gui.groupBox_basic.setStyleSheet(
            "QGroupBox#Colored_basic {border: 1px solid blue; border-radius: 9px;}"
        )

        self.gui.groupBox_configurate.setObjectName("Colored_configure")
        self.gui.groupBox_configurate.setStyleSheet(
            "QGroupBox#Colored_configure {border: 1px solid blue; border-radius: 9px;}"
        )

        #combobox basic
        self.gui.comboBox_axis.setCurrentText(self.current_axis)
        self.gui.comboBox_axis.currentTextChanged.connect(self.get_axis)

        self.gui.pushButton_stop.clicked.connect(self.stop_moving)
        self.pushButton_stop.setStyleSheet("background-color: red")

        self.gui.doubleSpinBox_temperature.setValue(self.temperature)
        self.set_temperature()
        self.gui.doubleSpinBox_temperature.valueChanged.connect(
            self.set_temperature)

    def make_combobox_configurate(self):
        """| *Layout of configurate combobox*
        | Sets the blue border. Connects the spinboxes to methode set_value.
        | Values are read and remembered in the whole class. Connects the configurate button.
        """
        self.gui.doubleSpinBox_amplitudeX.setValue(self.settings['amplitudeX'])
        self.gui.doubleSpinBox_frequencyX.setValue(self.settings['frequencyX'])

        self.gui.doubleSpinBox_amplitudeY.setValue(self.settings['amplitudeY'])
        self.gui.doubleSpinBox_frequencyY.setValue(self.settings['frequencyY'])

        self.gui.doubleSpinBox_amplitudeZ.setValue(self.settings['amplitudeZ'])
        self.gui.doubleSpinBox_frequencyZ.setValue(self.settings['frequencyZ'])

        self.gui.doubleSpinBox_frequencyX.valueChanged.connect(
            lambda: self.set_value('X', 'frequency'))
        self.gui.doubleSpinBox_amplitudeX.valueChanged.connect(
            lambda: self.set_value('X', 'amplitude'))

        self.gui.doubleSpinBox_amplitudeY.valueChanged.connect(
            lambda: self.set_value('Y', 'amplitude'))
        self.gui.doubleSpinBox_frequencyY.valueChanged.connect(
            lambda: self.set_value('Y', 'frequency'))

        self.gui.doubleSpinBox_amplitudeY.valueChanged.connect(
            lambda: self.set_value('Z', 'amplitude'))
        self.gui.doubleSpinBox_frequencyY.valueChanged.connect(
            lambda: self.set_value('Z', 'frequency'))

        self.gui.pushButton_configurateStepper.clicked.connect(
            self.configure_stepper)

        self.get_axis()

    def make_combobox_movements(self):
        """| *Layout of combobox of all movements*
        | Sets the blue border. Runs the self.get_move method to find out what kind of movement is selected (step, continuous, ...).
        | Connects the spinbox and unit combobox to method set_value. Values are read and remembered in the whole class.
        | Connects all buttons to the methode self.move, that figures out in which way and direction to move.
        """
        self.gui.comboBox_kindOfMove.setCurrentText(self.current_move)
        self.gui.comboBox_kindOfMove.currentTextChanged.connect(self.get_move)

        self.gui.comboBox_unit.setCurrentText('um')
        self.gui.doubleSpinBox_distance.setValue(self.distance.m_as('um'))
        self.gui.doubleSpinBox_distance.valueChanged.connect(self.set_distance)
        self.gui.comboBox_unit.currentTextChanged.connect(self.set_distance)

        # self.gui.pushButton_left.setCheckable(True)
        # self.gui.pushButton_left.toggle()
        self.gui.pushButton_left.clicked.connect(lambda: self.move('left'))
        self.gui.pushButton_right.clicked.connect(lambda: self.move('right'))
        self.gui.pushButton_up.clicked.connect(lambda: self.move('up'))
        self.gui.pushButton_down.clicked.connect(lambda: self.move('down'))

    def make_combobox_scanner(self):
        """| *Layout of scanner combobox*
        | Connects the spinboxes to set_value, that in case of the scanner will immediately put the voltage to move the scanner.
        """
        self.gui.comboBox_unitX.setCurrentText(self.scanner_unitX)
        self.gui.comboBox_unitX.currentTextChanged.connect(
            lambda: self.set_scanner_position('X'))

        self.gui.comboBox_unitY.setCurrentText(self.scanner_unitY)
        self.gui.comboBox_unitY.currentTextChanged.connect(
            lambda: self.set_scanner_position('Y'))

        self.gui.comboBox_unitZ.setCurrentText(self.scanner_unitZ)
        self.gui.comboBox_unitZ.currentTextChanged.connect(
            lambda: self.set_scanner_position('Z'))

        self.gui.doubleSpinBox_scannerX.setValue(int(self.dcX.m_as('V')))
        self.gui.doubleSpinBox_scannerY.setValue(int(self.dcY.m_as('V')))
        self.gui.doubleSpinBox_scannerZ.setValue(int(self.dcZ.m_as('V')))

        self.move_scanner('dCX')
        self.move_scanner('dCY')
        self.move_scanner('dCZ')

        self.gui.doubleSpinBox_scannerX.valueChanged.connect(
            lambda: self.set_scanner_position('X'))
        self.gui.doubleSpinBox_scannerY.valueChanged.connect(
            lambda: self.set_scanner_position('Y'))
        self.gui.doubleSpinBox_scannerZ.valueChanged.connect(
            lambda: self.set_scanner_position('Z'))

        self.gui.pushButton_zero_scanners.clicked.connect(self.zero_scanners)

    def show_position(self):
        """In the instrument level, the current positions of both Stepper and Scanner are remembered in a dictionary and updated through get_position.
        This method read them out (continuously, through the timer in the init) and displays their values.
        """
        self.anc350_instrument.update_all_positions()

        self.current_positions = self.anc350_instrument.current_positions

        self.gui.label_actualPositionX.setText(
            str(self.current_positions['XPiezoStepper']))
        self.gui.label_actualPositionY.setText(
            str(self.current_positions['YPiezoStepper']))
        self.gui.label_actualPositionZ.setText(
            str(self.current_positions['ZPiezoStepper']))

        self.gui.scan_positionX.setText(
            str(self.current_positions['XPiezoScanner']))
        self.gui.scan_positionY.setText(
            str(self.current_positions['YPiezoScanner']))
        self.gui.scan_positionZ.setText(
            str(self.current_positions['ZPiezoScanner']))

    def get_axis(self):
        """| *Layout stacked widgets plus blue borders*
        | Depending on the selected axis, the gui looks differently.
        | - The basic box is always enabled.
        | - If one of the Steppers is selected, only the configuration box is shown.
        | - After configuration, also the box with all the moves will be enabled.
        | - If one of the Scanners is selected, only the scanner box is shown.
        | - When the Z Piezo Stepper is selected, all of the X values change to Z.
        | - When the Z Piezo Scanner is selected, similar but now only for the two boxes in the scanner part.
        | - **Important** self.current_axis is saved here and used in the whole program.
        """
        self.current_axis = self.gui.comboBox_axis.currentText()
        self.logger.debug('current axis:' + str(self.current_axis))

        if 'Stepper' in self.current_axis:
            #self.gui.groupBox_configurate.setEnabled(True)
            self.gui.groupBox_configurate.setStyleSheet(
                "QGroupBox#Colored_configure {border: 1px solid blue; border-radius: 9px;}"
            )

            self.gui.groupBox_actions.setStyleSheet("QGroupBox default")

            self.gui.stackedWidget_actions.setCurrentWidget(
                self.gui.page_configure_stepper)
            self.gui.stackedWidget_stepper.setCurrentWidget(
                self.gui.stackedWidgetMoving)
            self.gui.stackedWidgetMoving.setEnabled(False)

            if 'Z' in self.current_axis:
                #Disable the xy groupboxes, enable the z groupboxes,
                # choose the page_amplZ of the stackedWidget_configure
                self.gui.groupBox_XY.setEnabled(False)
                self.gui.groupBox_Z.setEnabled(True)

                self.gui.stackedWidget_configure.setCurrentWidget(
                    self.gui.page_amplZ)

                self.gui.pushButton_up.setEnabled(False)
                self.gui.pushButton_down.setEnabled(False)
                self.gui.pushButton_left.setText('closer')
                self.gui.pushButton_right.setText('away')
            else:
                #Enable the xy groupboxes, disable the z groupboxes,
                # choose the page_amplXY of the stackedWidget_configure.

                self.gui.groupBox_XY.setEnabled(True)
                self.gui.groupBox_Z.setEnabled(False)

                self.gui.stackedWidget_configure.setCurrentWidget(
                    self.gui.page_amplXY)

                self.gui.pushButton_up.setEnabled(True)
                self.gui.pushButton_down.setEnabled(True)
                self.gui.pushButton_left.setText('left')
                self.gui.pushButton_right.setText('right')

        elif 'Scanner' in self.current_axis:
            #Choose the page_move_scanner of the stackedWidget_actions and the stackedWidgetEmpty of the stackedWidget_stepper
            self.gui.stackedWidget_actions.setCurrentWidget(
                self.gui.page_move_scanner)
            self.gui.stackedWidget_stepper.setCurrentWidget(
                self.gui.stackedWidgetempty)

            #Give the configurate box a border and the action box none
            self.gui.groupBox_configurate.setStyleSheet(
                "QGroupBox#Colored_configure {border: 1px solid blue; border-radius: 9px;}"
            )
            self.gui.groupBox_actions.setStyleSheet("QGroupBox default")

            #Choose either the page_scannerZ or page_scannerXY of the stackedWidget_voltScanner
            if 'Z' in self.current_axis:
                self.gui.stackedWidget_voltScanner.setCurrentWidget(
                    self.gui.page_scannerZ)
            else:
                self.gui.stackedWidget_voltScanner.setCurrentWidget(
                    self.gui.page_scannerXY)

    def get_move(self):
        """| *Layout of all moving options*
        | Similar to the get_axis, the box with all the moves has lots of options that get chosen from the stacked widgets.
        | - When continuous is selected, it gives you the speed in the selected axes.
        | - When step is selected, it gives you the stepsize of the selected axes.
        | - When move absolute or move relative are selected, the user can enter the desired position/distance.
        """

        self.current_move = self.gui.comboBox_kindOfMove.currentText()
        self.logger.debug('current way of moving: ' + str(self.current_move))

        if 'absolute' in self.current_move:
            #disable all buttons, except for one or two, to move
            if 'Z' in self.current_axis:
                self.gui.pushButton_left.setEnabled(False)
                self.gui.pushButton_up.setEnabled(False)
                self.gui.pushButton_down.setEnabled(False)
                self.gui.pushButton_right.setText('move Z')
            else:
                self.gui.pushButton_left.setEnabled(False)
                self.gui.pushButton_up.setText('move Y')
                self.gui.pushButton_down.setEnabled(False)
                self.gui.pushButton_right.setText('move X')
        else:
            #enable the buttons that were disabled in move absolute
            if 'Z' in self.current_axis:
                self.gui.pushButton_left.setEnabled(True)
                self.gui.pushButton_up.setEnabled(False)
                self.gui.pushButton_down.setEnabled(False)
                self.gui.pushButton_right.setText('away')
            else:
                self.gui.pushButton_left.setEnabled(True)
                self.gui.pushButton_up.setText('up')
                self.gui.pushButton_down.setEnabled(True)
                self.gui.pushButton_right.setText('right')

        if self.current_move == 'move relative':
            #disable the info box (with speed or step size), enable user input posibility
            self.gui.label_sortMove.setText('to relative distance')
            self.gui.stackedWidget_moveDependent.setCurrentWidget(
                self.gui.page_distance)

        elif self.current_move == 'move absolute':
            # disable the info box (with speed or step size), enable user input posibility
            self.gui.label_sortMove.setText('to absolute position')
            self.gui.stackedWidget_moveDependent.setCurrentWidget(
                self.gui.page_distance)

        elif self.current_move == 'continuous':
            #disable the user input possibility, show either the speed of current axes (depends on amplitude)
            if 'Z' in self.current_axis:
                self.gui.label_speed_stepZ.setText('speed Z')
                self.gui.label_speedsize_stepsizeZ.setText(
                    str(self.anc350_instrument.Speed[1] *
                        ur('nm/s').to('um/s')))
                self.gui.stackedWidget_moveDependent.setCurrentWidget(
                    self.gui.page_stepZ)
            else:
                self.gui.label_speed_stepX.setText('speed X')
                self.gui.label_speed_stepY.setText('speed Y')

                self.gui.label_speedsize_stepsizeX.setText(
                    str(self.anc350_instrument.Speed[0] *
                        ur('nm/s').to('um/s')))
                self.gui.label_speedsize_stepsizeY.setText(
                    str(self.anc350_instrument.Speed[2] *
                        ur('nm/s').to('um/s')))
                self.gui.stackedWidget_moveDependent.setCurrentWidget(
                    self.gui.page_stepXY)

        elif self.current_move == 'step':
            # disable the user input possibility, show either the step size on current axes (depends on frequency)
            if 'Z' in self.current_axis:
                self.gui.label_speed_stepZ.setText('step size Z')
                self.gui.label_speedsize_stepsizeZ.setText(
                    str(self.anc350_instrument.Stepwidth[1] * ur('nm')))
                self.gui.stackedWidget_moveDependent.setCurrentWidget(
                    self.gui.page_stepZ)
            else:
                self.gui.label_speed_stepX.setText('step size X')
                self.gui.label_speed_stepY.setText('step size Y')
                self.gui.label_speedsize_stepsizeX.setText(
                    str(self.anc350_instrument.Stepwidth[0] * ur('nm')))
                self.gui.label_speedsize_stepsizeY.setText(
                    str(self.anc350_instrument.Stepwidth[2] * ur('nm')))
                self.gui.stackedWidget_moveDependent.setCurrentWidget(
                    self.gui.page_stepXY)

    def set_temperature(self):
        """Set the temperature and change the scanner piezo voltage limits accordingly.
        """
        self.temperature = self.gui.doubleSpinBox_temperature.value()
        self.logger.debug('Changing the temperature to {}K'.format(
            self.temperature))

        self.anc350_instrument.temperature = self.temperature
        self.anc350_instrument.set_temperature_limits()

        self.max_dclevel_V = self.anc350_instrument.max_dC_level

        self.logger.debug('Changed the scanner piezo limits to {}'.format(
            self.max_dclevel_V))

    def set_value(self, axis, value_type):
        """| Reads the values that the user filled in: amplitude, frequency or dc level on scanner.
        | Sets either the user input or the default amplitudes/frequencies as in the dictionary. The value is saved in self.settings.
        | If X and Y Scanner are selected, values are set separately; with Z, there is only one spinbox to fill in.
        | Values from dictionary are used in configure_stepper, but only if the user clicks configure.
        | axis and value_type are locally changed into the name as known in the dictionaries, like amplitudeX or dcZ.

        :param axis: axis X, Y, Z
        :type axis: string

        :param value_type: amplitude, frequency or dc
        :type value_type: string
        """
        self.logger.debug('changing a user input value')
        local_axis_name = value_type + axis

        if value_type == 'amplitude':
            self.logger.debug('changing the amplitude')
            max_value = self.max_amplitude_V
        elif value_type == 'frequency':
            self.logger.debug('changing the frequency')
            max_value = self.max_frequency

        if self.sender().value() > max_value:
            self.sender().setValue(max_value)
        elif self.sender().value() < 0:
            self.sender().setValue(0)

        # Store the new value in the dictionary in the init
        self.logger.debug(local_axis_name)
        self.settings[local_axis_name] = int(self.sender().value())
        self.logger.debug(self.settings)
        self.logger.debug('axis changed: ' + str(local_axis_name))
        self.logger.debug('value put: ' + str(self.settings[local_axis_name]))

    def set_scanner_position(self, axis):
        """To make it possible to use both V and mV for the scanner.
        The value of the spinbox and the unit in the combobox are combined.
        Then they are tested against the maximum and minimum values.
        If they are labeled too high or too low, the user input is changed to the maximum or minimum value.

        :param axis: axis X, Y, Z
        :type axis: string
        """
        change = 'not'

        if axis == 'X':
            scanner_pos = self.gui.doubleSpinBox_scannerX.value()
            self.scanner_unitX = self.gui.comboBox_unitX.currentText()
            scanner_unit = self.scanner_unitX
        elif axis == 'Y':
            scanner_pos = self.gui.doubleSpinBox_scannerY.value()
            self.scanner_unitY = self.gui.comboBox_unitY.currentText()
            scanner_unit = self.scanner_unitY
        elif axis == 'Z':
            scanner_pos = self.gui.doubleSpinBox_scannerZ.value()
            self.scanner_unitZ = self.gui.comboBox_unitZ.currentText()
            scanner_unit = self.scanner_unitZ

        local_position = ur(str(scanner_pos) + scanner_unit)
        self.logger.debug('{} local scanner position value: {}'.format(
            axis, local_position))

        if local_position > self.max_dclevel_V:
            self.logger.debug('value too high')
            local_max = self.max_dclevel_V.to(scanner_unit)
            self.logger.debug(str(local_max))
            change = 'high'
        elif local_position > self.max_dcLevel_mV_300K:
            self.logger.warning(
                'You are exceeding the 60V maximum for the piezo at room temperature'
            )
        elif local_position < 0:
            self.logger.debug('value too low')
            change = 'low'

        if axis == 'X':
            if change == 'high':
                self.gui.doubleSpinBox_scannerX.setValue(
                    local_max.m_as(scanner_unit))
            elif change == 'low':
                self.gui.doubleSpinBox_scannerX.setValue(0)
            scanner_pos = self.gui.doubleSpinBox_scannerX.value()
            local_position = ur(str(scanner_pos) + scanner_unit)
            self.dcX = local_position
            self.logger.debug('dictionary position {} changed to: {}'.format(
                axis, self.dcX))

        elif axis == 'Y':
            if change == 'high':
                self.gui.doubleSpinBox_scannerY.setValue(
                    local_max.m_as(scanner_unit))
            elif change == 'low':
                self.gui.doubleSpinBox_scannerY.setValue(0)
            scanner_pos = self.gui.doubleSpinBox_scannerY.value()
            local_position = ur(str(scanner_pos) + scanner_unit)
            self.dcY = local_position
            self.logger.debug('dictionary position {} changed to: {}'.format(
                axis, self.dcY))

        elif axis == 'Z':
            if change == 'high':
                self.gui.doubleSpinBox_scannerZ.setValue(
                    local_max.m_as(scanner_unit))
            elif change == 'low':
                self.gui.doubleSpinBox_scannerZ.setValue(0)
            scanner_pos = self.gui.doubleSpinBox_scannerZ.value()
            local_position = ur(str(scanner_pos) + scanner_unit)
            self.dcZ = local_position
            self.logger.debug('dictionary position {} changed to: {}'.format(
                axis, self.dcZ))

        self.move_scanner('dc' + axis)

    def set_distance(self):
        """Works similar to set_value method, but now only for the distance spinBox and unit.
        Combines value of spinbox with unit to make pint quantity and checks against maximum value defined up.
        Either applies the dictionary value of the distance, or changes that dictionary value and than applies it.
        """
        distance = self.gui.doubleSpinBox_distance.value()
        unit = self.gui.comboBox_unit.currentText()

        local_distance = ur(str(distance) + unit)
        self.logger.debug('local distance value: ' + str(local_distance))

        if local_distance > self.max_distance:
            self.logger.debug('value too high')
            local_max = self.max_distance.to(unit)
            self.logger.debug(str(local_max))
            self.gui.doubleSpinBox_distance.setValue(local_max.m_as(unit))
            distance = self.gui.doubleSpinBox_distance.value()
        elif local_distance < 0:
            self.logger.debug('value too low')
            self.gui.doubleSpinBox_distance.setValue(0)
            distance = self.gui.doubleSpinBox_distance.value()

        local_distance = ur(str(distance) + unit)  #in case something changed
        self.distance = local_distance
        self.logger.debug('dictionary distance changed to: ' +
                          str(self.distance))

    def configure_stepper(self):
        """Configures the stepper, using the amplitude and frequency that had been set in set_frequency and set_amplitude.
        After configuration, the box with all the different moves is chosen
        and the get_move is run to set the layout fit for the current move.
        """
        self.logger.info('configurating stepper')
        if 'Z' in self.current_axis:
            self.anc350_instrument.configure_stepper(
                'ZPiezoStepper', self.settings['amplitudeZ'] * ur('V'),
                self.settings['frequencyZ'] * ur('Hz'))
        else:
            self.anc350_instrument.configure_stepper(
                'XPiezoStepper', self.settings['amplitudeX'] * ur('V'),
                self.settings['frequencyX'] * ur('Hz'))
            self.anc350_instrument.configure_stepper(
                'YPiezoStepper', self.settings['amplitudeY'] * ur('V'),
                self.settings['frequencyY'] * ur('Hz'))

        self.gui.groupBox_actions.setObjectName("Colored_actions")
        self.gui.groupBox_actions.setStyleSheet(
            "QGroupBox#Colored_actions {border: 1px solid blue; border-radius: 9px;}"
        )

        self.gui.stackedWidgetMoving.setEnabled(True)

        self.get_move()

    def move_scanner(self, axis):
        """| Moves the scanner.
        | Is called by set_value, moves as soon as the user clicked Enter.

        :param axis: axis as they are called in the dictionary self.stepper_settings: dcX, dcY, dcZ
        :type axis: string
        """
        self.logger.info('moving the scanner ' + axis)

        if 'X' in axis:
            self.logger.debug('move by {}'.format(self.dcX))
            self.anc350_instrument.move_scanner('XPiezoScanner', self.dcX)
        elif 'Y' in axis:
            self.logger.debug('move by {}'.format(self.dcY))
            self.anc350_instrument.move_scanner('YPiezoScanner', self.dcY)
        elif 'Z' in axis:
            self.logger.debug('move by {}'.format(self.dcZ))
            self.anc350_instrument.move_scanner('ZPiezoScanner', self.dcZ)

    def zero_scanners(self):
        """Put 0V on all scanners.
        """
        self.logger.info('Zero all Scanners.')
        self.anc350_instrument.zero_scanners()

    def move(self, direction):
        """| Here the actual move takes place, after the user clicked on one of the four directional buttons.
        | The clicked button determines the direction that is chosen.
        | - For the continuous and step move, that is than converted to 0 or 1.
        | This is correct as it is written right now, I checked it.
        | - For the relative move, the direction is than converted in adding a minus sign or not.
        | - For every kind of move, the self.moving_thread is started, so the stop button can be used and the position label can be updated.
        | I gave this thread the same name every time, don't know whether that is bad practice. The thread is quit in the stop method.

        :param direction: direction of move, left, right, up, down
        :type direction: string
        """

        self.direction = direction
        self.logger.debug('current direction: ' + direction)

        #remember axis name that instrument thinks in
        if 'Z' in self.current_axis:
            axis_string = 'ZPiezoStepper'
        else:
            if self.direction == 'left' or self.direction == 'right':
                axis_string = 'XPiezoStepper'
            else:
                axis_string = 'YPiezoStepper'

        if self.current_move == 'move absolute':
            #combine the spinbox and unit combobox user input to a pint quantity
            self.logger.info('moving to an absolute position')
            distance = self.gui.doubleSpinBox_distance.value()
            unit = self.gui.comboBox_unit.currentText()

            self.logger.debug('axis: ' + axis_string)
            local_distance = ur(str(distance) + unit)
            self.logger.debug('to position: ' + str(local_distance))

            self.moving_thread = WorkThread(self.anc350_instrument.move_to,
                                            axis_string, local_distance)
            self.moving_thread.start()

        elif self.current_move == 'move relative':
            # combine the spinbox and unit combobox user input to a pint quantity
            # add minussign to communicate correct direction to instrument
            self.logger.info('moving relative')
            distance = self.gui.doubleSpinBox_distance.value()
            unit = self.gui.comboBox_unit.currentText()
            self.logger.debug('axis:' + axis_string)
            self.logger.debug('direction: ' + direction)

            if self.direction == 'right' or self.direction == 'up':
                local_distance = ur(str(distance) + unit)
                self.logger.debug(str(local_distance))
            elif self.direction == 'left' or self.direction == 'down':
                local_distance = ur(str(-1 * distance) + unit)
                self.logger.debug(str(local_distance))

            self.moving_thread = WorkThread(
                self.anc350_instrument.move_relative, axis_string,
                local_distance)
            self.moving_thread.start()

        elif self.current_move == 'continuous' or self.current_move == 'step':
            # convert direction buttons clicked to direction integers that instrument wants
            # than move for 1s continuously, since the stop button doesnt work yet
            if self.direction == 'left':
                if 'Z' in self.current_axis:
                    direction_int = 0  # correct direction, corresponds to labels closer and away
                else:
                    direction_int = 1
            elif self.direction == 'right':
                if 'Z' in self.current_axis:
                    direction_int = 1  # correct direction, corresponds to labels closer and away
                else:
                    direction_int = 0
            elif self.direction == 'up':
                direction_int = 0
            elif self.direction == 'down':
                direction_int = 1

            if self.current_move == 'continuous':
                self.logger.info('moving continuously')
                self.moving_thread = WorkThread(
                    self.anc350_instrument.move_continuous, axis_string,
                    direction_int)
                self.moving_thread.start()

            elif self.current_move == 'step':
                self.logger.info('making a step')
                self.anc350_instrument.given_step(axis_string, direction_int,
                                                  1)

    def stop_moving(self):
        """| Stops movement of all steppers.
        | The check_if_moving loop in the instrument level checks whether the stop is True, and if so, breaks the loop.
        | Similar for a loop in the move_continuous in the instrument level, that checks for the stop.
        | The stop_moving in the instrument level actually stops the device.
        | Because of the moving_thread that is started in the method move in this class, the loops in the methods in instrument actually keep checking for this stop value.
        """
        self.logger.info('stop moving')
        self.anc350_instrument.stop = True
        self.anc350_instrument.stop_moving('XPiezoStepper')
        self.anc350_instrument.stop_moving('YPiezoStepper')
        self.anc350_instrument.stop_moving('ZPiezoStepper')

        if self.moving_thread.isRunning:
            self.moving_thread.quit()

        self.anc350_instrument.stop = False
Пример #10
0
class AutoMeasurementGui(BaseGui):
    """
    Builds a Measurement GUI based on the Measurement actionlist in experiment.properties (which is read from the
    config file). The GUI will follow the nested structure in the actionlist.
    Note that the Actions in the actionlist need to refer to an appropriate GUI file and Widget and to an appropriate
    method in the experiment (which should contain nested() if nesting is to be possible).
    I also builds the Start/Pause, Break and Stop buttons that contol the flow of the measurement

    :param experiment: hyperion experiment object
    :param measurement: (str) name of a measurement (specified in the config of the experiment)
    :param parent: parent QWidget
    """
    def __init__(self, experiment, measurement, parent=None, output_guis=None, graphs_in_standalone=None):
        self.logger = logging.getLogger(__name__)
        self.logger.debug('Creating BaseMeasurement object')
        super().__init__(parent)
        self.child_action_widgets = {}
        self.experiment = experiment
        self.measurement = measurement
        self.output_guis = output_guis
        self._parent = parent
        if not hasattr(self.experiment, 'properties'):
            self.logger.error('Experiment object needs to have properties dictionary. Make sure you load config.')
        if measurement not in self.experiment.properties['Measurements']:
            self.logger.error('Unknown measurement: {}'.format(measurement))
        if 'automated_actionlist' not in self.experiment.properties['Measurements'][measurement]:
            self.logger.error("Measurement doesn't have automated_actionlist: {}".format(measurement))

        if 'ActionTypes' in self.experiment.properties:
            self.types = self.experiment.properties['ActionTypes']
        else:
            self.types = {}

        # self.measurement_thread = WorkThread(experiment.dummy_measurement_for_testing_gui_buttons)
        self.measurement_thread = WorkThread(lambda: experiment.perform_measurement(self.measurement))

        self._valid = self.validate()

        # self.outer_layout = QGridLayout()
        self.outer_layout = QVBoxLayout()

        self.outer_layout.setSpacing(20)
        self.button_layout = self.create_buttons()

        # self.outer_layout.addLayout(self.button_layout, 0, 0)
        self.outer_layout.addLayout(self.button_layout)

        self.actions_layout = QVBoxLayout()
        label_incorrect = QLabel('incorrect config file')
        label_incorrect.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)
        self.actions_layout.addWidget(label_incorrect)
        self.create_actionlist_guis()
        # This line controls the size of the whole layout.
        # .SetDefaultConstraint causes it to adjust to the content size, but keeps it adjustable
        # .SetFixedSize adjust to the content and prevents manual resizing
        # self.outer_layout.setSizeConstraint(QLayout.SetDefaultConstraint)
        # self.outer_layout.setSizeConstraint(QLayout.SetFixedSize)
        self.setLayout(self.outer_layout)
        self.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)
        self.show()

        # Set up QTimer to periodically update button states (based on
        self.timer_update_buttons = QTimer()
        self.timer_update_buttons.timeout.connect(self.update_buttons)
        self.timer_update_buttons.start(50) # in ms

        # If run in standalone mode, but with graphs, display those graphs
        self.graphs_in_standalone = graphs_in_standalone
        if graphs_in_standalone is False:
            self.logger.debug("Not using graphs_in_standalone mode")
        if graphs_in_standalone is None:
            if parent is None and output_guis is not None:
                self.logger.info("Automatically using graphs_in_standalone mode")
                self.graphs_in_standalone = True
            else:
                self.logger.info("Automatically not using graphs_in_standalone mode because parent is not None or output_guis is None")
                self.graphs_in_standalone = False
        elif graphs_in_standalone:
            self.logger.debug("Using graphs_in_standalone mode")

        if self.graphs_in_standalone:
            self.show_ouputs_if_standalone()

        # Set up QTimer to periodically update status message in parent gui if available.
        if self._parent is not None:
            self.timer_update_status = QTimer()
            self.timer_update_status.timeout.connect(lambda: self._parent.update_statusbar(self.measurement, timer=self.timer_update_status))


    def closeEvent(self, *args, **kwargs):
        if self.graphs_in_standalone:
            self.logger.debug('Running in graphs_in_standalone mode: Closing the QApplication')
            QApplication.instance().quit()
        super().closeEvent(*args, **kwargs)

    def show_ouputs_if_standalone(self):
        for name, gui in self.output_guis.items():
            gui.show()

    def create_buttons(self):
        """
        Create Start/Pause, Break, Stop and Config Button. And link to appropriate methods.
        :return: layout containing buttons
        """
        self.logger.debug('Creating start stop buttons')
        layout_buttons = QHBoxLayout()
        self.button_start_pause = QPushButton('Start', clicked = self.start_pause)
        self.button_break = QPushButton('Break', clicked = self.apply_break)
        self.button_stop = QPushButton('Stop', clicked = self.apply_stop)
        self.button_config = QPushButton('Config', clicked = self.config)
        layout_buttons.addWidget(self.button_start_pause)
        layout_buttons.addWidget(self.button_break)
        layout_buttons.addWidget(self.button_stop)
        layout_buttons.addWidget(self.button_config)
        return layout_buttons

    def create_actionlist_guis(self):
        """
        Creates and updates the the (nested) measurement GUI.
        """
        self.deleteItemsOfLayout(self.actions_layout)
        if self._valid:
            self.child_action_widgets = {}
            self.actions_layout = self.add_actions_recursively(self.experiment.properties['Measurements'][self.measurement]['automated_actionlist'])
        else:
            self.actions_layout = QVBoxLayout()
            self.actions_layout.addWidget(QLabel('incorrect config file'))

        # self.outer_layout.addLayout(self.actions_layout, 1, 0)
        self.outer_layout.addLayout(self.actions_layout)

        self.update()

    def add_actions_recursively(self, actionlist, nesting_level=0):
        """
        Recursive function to build nested layout of action GUIs.

        :param actionlist: (list)
        :param nesting_level: (int) Used for recursion (use default when calling method)
        :return: layout containing the nested GUIs
        """
        layout = QVBoxLayout()
        layout.setContentsMargins(0,0,0,0)
        layout.setSpacing(5+self.__shift(nesting_level)[0])
        for act in actionlist:
            actiondict = ActionDict(act, self.types)
            box = QGroupBox(actiondict['Name'])
            box.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed)
            box.setCheckable(True)   # This adds the checkbox in the top-left corner
            box_layout = QVBoxLayout()
            box_layout.setContentsMargins(0,5,0,0)
            box_layout.setSpacing(0)                    # distance between action widget and its nested items
            if '_view' in actiondict:
                action_gui_class = get_class(actiondict['_view'])
                action_gui_widget = action_gui_class(actiondict, self.experiment, parent=box)
                self.child_action_widgets[actiondict['Name']] = action_gui_widget
                action_gui_widget.layout.setContentsMargins(7,0,20-self.__shift(nesting_level)[1],10)
                action_gui_widget.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed)
                box_layout.addWidget(action_gui_widget)
                if '_disabled' in actiondict and actiondict['_disabled']:
                    box.setChecked(False)
                box.toggled.connect(lambda state, a=actiondict: a.__setitem__('_disabled', not state))
            if '~nested' in actiondict:
                nested_layout = self.add_actions_recursively(actiondict['~nested'], nesting_level+1)
                nested_layout.setContentsMargins(max(3,12-nesting_level), 0, self.__shift(nesting_level)[0],5+max(0,5-nesting_level))
                box_layout.addLayout(nested_layout)
            if box_layout.count():
                box.setLayout(box_layout)
                layout.addWidget(box)
        return layout

    def __shift(self, level, maxlev=5):
        # Helper function to calculating border widths for aligning nested layout in add_actions_recursively().
        # Returns tuple
        inverted = max(0, maxlev - level)
        shift = 0
        for s in range(inverted, maxlev):
            shift += s+2
        return inverted, shift

    def start_plotting(self, *args, **kwargs):
        """
        This method should be overwritten by a parent class.
        """
        self.logger.warning('I didnt start plotting, ended up here in base_guis.AutoMeasurementGui')
        None

    def ensure_output_docks_are_open(self):
        """ Uses the show_dock method added by ExpGui to ensure that all aoutput guis are opened before starting the measurement"""
        if self._parent is not None:
            if self.output_guis is not None:
                for instance in self.output_guis.values():
                    if hasattr(instance, 'show_dock'):
                        instance.show_dock(True)

    def start_pause(self):
        """
        Called when Start/Pause/Continue button is pressed.
        Starts a measurement thread or communicates to experiment through the Measurement status flags that a
        measurement should be paused or continued.
        """
        self.logger.debug('start/pause pressed')
        self.experiment.apply_pause = not self.experiment.apply_pause
        if self.experiment.running_status == self.experiment._not_running:
            self.ensure_output_docks_are_open()
            self.measurement_thread.start()
            if self._parent is not None:
                self.timer_update_status.start(100)
            self.start_plotting()
        self.update_buttons()

    def apply_break(self):
        """
        Called when Break button is pressed.
        Communicates to experiment through the Measurement status flags that a measurement break ("soft stop") should be
        applied.
        """
        self.logger.debug('break pressed')
        self.experiment.apply_break = True
        self.update_buttons()

    def apply_stop(self):
        """
        Called when Stop button is pressed.
        Communicates to experiment through the Measurement status flags that a measurement should be stopped (immediately).
        """
        self.logger.debug('stop pressed')
        self.experiment.apply_stop = True
        self.update_buttons()

    def config(self):
        """
        Called when Config button is pressed.
        Opens the ModifyMeasurement Dialog to modify/correct the config text directly.
        """
        # Prepare window to modify config:
        dialog_config = ModifyMeasurement(self.experiment, self.measurement, self)
        dialog_config.exec_()
        self.update_buttons()
        self.create_actionlist_guis()
        self.update()

    def update_buttons(self):
        """
        Updates the status of the start/pause/continue, break, stop buttons.
        """
        # note that:
        # experiment._not_running = 0
        # experiment._running =     1
        # experiment._pausing =     2
        # experiment._breaking =    3
        # experiment._stopping =    4
        if not self._valid:
            self.button_start_pause.setEnabled(False)
            self.button_break.setEnabled(False)
            self.button_stop.setEnabled(False)
            self.button_config.setEnabled(True)
            return
        else:
            if self.experiment.running_status == self.experiment._not_running:
                self.button_start_pause.setText('Start')
            elif self.experiment.apply_pause:
                self.button_start_pause.setText('Continue')
            else:
                self.button_start_pause.setText('Pause')
            if self.experiment.apply_break:
                self.button_break.setText('Breaking')
            else:
                self.button_break.setText('Break')
            self.button_start_pause.setEnabled(self.experiment.running_status < self.experiment._breaking)
            self.button_break.setEnabled(self.experiment._not_running < self.experiment.running_status < self.experiment._breaking)
            self.button_stop.setEnabled(self.experiment._not_running < self.experiment.running_status < self.experiment._stopping)
            self.button_config.setEnabled(self.experiment.running_status == self.experiment._not_running)

    def validate(self):
        """
        Uses self.experiment._validate_actionlist() to determine if the current actionlist in experiment.properties is
        valid.
        :return: (boolean) True if valid
        """
        new_action_list, invalid_methods, invalid_names = self.experiment._validate_actionlist(self.experiment.properties['Measurements'][self.measurement]['automated_actionlist'])
        return (invalid_methods==0 and invalid_names==0)



# if __name__ == '__main__':
#     app = QApplication(sys.argv)
#     pass
#     app.exec_()
#     # sys.exit(app.exec_())
Пример #11
0
class Hydraharp_aligning_GUI(BaseGui):
    """

    :param
    :type

    """
    def __init__(self, hydra_instrument):
        """Hydraharp aligning
        """

        super().__init__()
        self.logger = logging.getLogger(__name__)
        self.title = 'Hydraharp Aligning GUI'
        self.left = 50
        self.top = 50
        self.width = 500
        self.height = 250
        self.hydra_instrument = hydra_instrument

        self.exp_type = 'Finite'
        self.pausetime = 50*ur('ms')
        self.lengthaxis = 10*ur('s')
        self.sync = True
        self.chan1 = False
        self.chan2 = False

        self.sync_counts = 0.0
        self.counts1 = 0.0
        self.counts2 = 0.0

        self.running = False
        self.Sync_counts_array = []
        self.Counts1_array = []
        self.Counts2_array = []
        self.time_axis = []

        self.default_name = 'counts.txt'
        self.path = 'D:\\LabSoftware\\Data\\'

        self.something_selected = False

        self.pen = pg.mkPen(color=(0, 0, 0))  # makes the plotted lines black

        name = 'aligning.ui'
        gui_folder = os.path.dirname(os.path.abspath(__file__))
        gui_file = os.path.join(gui_folder, name)
        self.logger.info('Loading the GUI file: {}'.format(gui_file))
        self.gui = uic.loadUi(gui_file, self)

        self.initUI()

        self.timer = QTimer()
        self.timer.timeout.connect(self.ask_counts)
        self.timer.start(100)       #time in ms

    def initUI(self):
        """Connect all buttons, comboBoxes and doubleSpinBoxes to methods
        """
        self.logger.debug('Setting up the Measurement GUI')
        self.setWindowTitle(self.title)
        self.setGeometry(self.left, self.top, self.width, self.height)

        self.show()

        self.gui.pushButton_start.clicked.connect(self.start_plotting)
        self.gui.pushButton_stop.clicked.connect(self.stop_plotting)
        self.pushButton_stop.setStyleSheet("background-color: red")

        self.gui.pushButton_save.clicked.connect(self.save_counts)
        self.gui.pushButton_save.setEnabled(False)

        self.gui.comboBox_finite.setCurrentText(self.exp_type)
        self.gui.comboBox_finite.currentTextChanged.connect(self.get_exp_type)

        self.gui.doubleSpinBox_pause.setValue(self.pausetime.m_as('ms'))
        self.gui.doubleSpinBox_pause.valueChanged.connect(self.set_pausetime)

        self.gui.doubleSpinBox_timeaxis.setValue(self.lengthaxis.m_as('s'))
        self.gui.doubleSpinBox_timeaxis.valueChanged.connect(self.set_lengthaxis)

        self.gui.checkBox_sync.setChecked(self.sync)
        self.gui.checkBox_sync.stateChanged.connect(self.set_channel)

        self.gui.checkBox_chan1.setChecked(self.chan1)
        self.gui.checkBox_chan1.stateChanged.connect(self.set_channel)

        self.gui.checkBox_chan2.setChecked(self.chan2)
        self.gui.checkBox_chan2.stateChanged.connect(self.set_channel)

        self.set_name_path()
        self.gui.lineEdit_name.textChanged.connect(self.get_name_path)
        self.gui.lineEdit_path.textChanged.connect(self.get_name_path)

    #Read user inputs
    # -----------------------------------------------------------------------------------------
    def get_exp_type(self):
        self.logger.debug('Should read the experiment type here')
        self.exp_type = self.gui.comboBox_finite.currentText()
        self.logger.debug('Chosen type: {}'.format(self.exp_type))

    def set_pausetime(self):
        self.logger.debug('Should set the pause time here')
        self.pausetime = self.gui.doubleSpinBox_pause.value()*ur('ms')
        self.logger.debug('Chosen time: {}'.format(self.pausetime))

    def set_lengthaxis(self):
        self.logger.debug('Should set the length of the axis here')
        self.lengthaxis = self.gui.doubleSpinBox_timeaxis.value()*ur('s')
        self.logger.debug('Chosen length of axis: {}'.format(self.lengthaxis))

    def set_channel(self):
        self.sync = self.checkBox_sync.isChecked()
        self.chan1 = self.gui.checkBox_chan1.isChecked()
        self.chan2 = self.gui.checkBox_chan2.isChecked()
        self.logger.debug('Sync channel: {}, channel 1: {}, channel 2: {}'.format(self.sync, self.chan1, self.chan2))

    def set_name_path(self):
        self.gui.lineEdit_name.setText(self.default_name)
        self.gui.lineEdit_path.setText(self.path)

    def get_name_path(self):
        self.default_name = self.gui.lineEdit_name.text()
        self.path = self.gui.lineEdit_path.text()

        print(self.gui.lineEdit_name.text())
        print(self.gui.lineEdit_path.text())

    #Actual methods doing something
    #-----------------------------------------------------------------------------------------
    def ask_counts(self):
        """ | Connects to the device and read out the count rate of either the sync or on of the count channels.
        | Displays this on the labels on the gui, which are updated via the timer in the init.
        """
        #self.logger.debug('Asking for counts')

        self.something_selected = False

        if self.sync:
            self.sync_counts = self.hydra_instrument.sync_rate()
            #self.logger.debug("{}".format(self.sync_counts))
            self.gui.label_counts_sync.setText(str(self.sync_counts))
            self.something_selected = True
        else:
            self.gui.label_counts_sync.setText('currently unavailable')

        if self.chan1:
            self.counts1 = self.hydra_instrument.count_rate(0)
            #self.logger.debug("{}".format(self.counts1))
            self.something_selected = True
            self.gui.label_counts1.setText(str(self.counts1))
        else:
            self.gui.label_counts1.setText('currently unavailable')

        if self.chan2:
            self.counts2 = self.hydra_instrument.count_rate(1)
            #self.logger.debug("{}".format(self.counts2))
            self.gui.label_counts2.setText(str(self.counts2))
            self.something_selected = True
        else:
            self.gui.label_counts2.setText('currently unavailable')

        if self.something_selected == False:
            self.logger.warning('Nothing is selected')


    def start_plotting(self):
        """| Prepares for plotting by making a time axis based on the axis length and pause time, and starts an empty counts array.
        | Then opens 1 to 3 plot windows and a thread to plot them, depending on the selected channels.
        """
        self.logger.info('Should start counting here')

        self.logger.debug('Settings: {}, {}, {}'.format(self.exp_type, self.pausetime, self.lengthaxis))

        self.time_axis = np.linspace(0, self.lengthaxis.m_as('s') * self.pausetime.m_as('s'), int(self.lengthaxis.m_as('s'))) * ur('s')
        self.logger.debug("{}".format(self.time_axis))

        if self.something_selected:
            if self.sync:
                self.draw0 = DrawCounts()
                self.draw0.counts_plot.setTitle("<span style=\"color:yellow;font-size:30px\">Counts on sync channel </span>")
                #self.draw0.counts_plot.plot(self.time_axis.m_as('s'), Counts, clear=True, pen=self.pen)

            if self.chan1:
                self.draw1 = DrawCounts()
                self.draw1.counts_plot.setTitle("<span style=\"color:orange;font-size:30px\">Counts on channel 1 </span>")
                #self.draw1.counts_plot.plot(self.time_axis.m_as('s'), Counts, clear=True, pen=self.pen)

            if self.chan2:
                self.draw2 = DrawCounts()
                self.draw2.counts_plot.setTitle("<span style=\"color:red;font-size:30px\">Counts on channel 2 </span>")
                #self.draw2.counts_plot.plot(self.time_axis.m_as('s'), Counts, clear=True, pen=self.pen)

            self.plotting_thread = WorkThread(self.update_plot)
            self.plotting_thread.start()

        else:
            self.logger.warning('You have to select something first, before making a graph.')


    def update_plot(self):
        """| This method is called and threaded from start_plotting, depending on the selected channels it plots in 1 to 3 graphs.
        | It either works for a Finite amount of time or works infinitely, to make aligning possible.
        | It draws the plot with the time axis, which is the same for all channels,
        | and the Counts, that are a numpy array that is constantly filled with the current Rate.
        | Depending on the channel, the counts are plotted in three different self.draw windows.
        | After the plotting is finished, the prepare_save method is started, so the name to be given to a potential file is set.

        :param Counts: array of the size of the axis length
        :type Counts: numpy array
        """
        self.running = True

        self.Sync_counts_array = np.zeros(int(self.lengthaxis.m_as('s')))
        self.Counts1_array = np.zeros(int(self.lengthaxis.m_as('s')))
        self.Counts2_array = np.zeros(int(self.lengthaxis.m_as('s')))

        if self.exp_type == 'Finite':

            for ii in range(0, int(self.lengthaxis.m_as('s'))):
                if self.running == False:
                    break
                else:
                    if self.sync:
                        curr_sync_Rate = self.sync_counts
                        self.Sync_counts_array[ii] = curr_sync_Rate.m_as('cps')
                        self.logger.debug('Counts: {}'.format(self.Sync_counts_array))

                        self.draw0.counts_plot.plot(self.time_axis, self.Sync_counts_array, clear = True,pen=self.pen)

                    if self.chan1:
                        currRate1 = self.counts1
                        self.Counts1_array[ii] = currRate1.m_as('cps')
                        self.logger.debug('Counts: {}'.format(self.Counts1_array))

                        self.draw1.counts_plot.plot(self.time_axis, self.Counts1_array, clear = True, pen=self.pen)

                    if self.chan2:
                        currRate2 = self.counts2
                        self.Counts2_array[ii] = currRate2.m_as('cps')
                        self.logger.debug('Counts: {}'.format(self.Counts2_array))

                        self.draw2.counts_plot.plot(self.time_axis, self.Counts2_array, clear = True, pen=self.pen)

                    time.sleep(self.pausetime.m_as('s'))

        elif self.exp_type == 'Infinite':
            ii=0
            while self.running:
                if self.sync:
                    curr_sync_Rate = self.sync_counts
                    if ii < int(self.lengthaxis.m_as('s')):
                        self.Sync_counts_array[ii] = curr_sync_Rate.m_as('cps')
                    else:
                        self.Sync_counts_array = np.roll(self.Sync_counts_array, -1)
                        self.Sync_counts_array[-1] = curr_sync_Rate.m_as('cps')
                        self.logger.debug('{}'.format(self.Sync_counts_array))

                    self.draw0.counts_plot.plot(self.time_axis, self.Sync_counts_array, clear=True, pen=self.pen)

                if self.chan1:
                    currRate1 = self.counts1
                    if ii < int(self.lengthaxis.m_as('s')):
                        self.Counts1_array[ii] = currRate1.m_as('cps')
                    else:
                        self.Counts1_array = np.roll(self.Counts1_array, -1)
                        self.Counts1_array[-1] = currRate1.m_as('cps')
                        self.logger.debug('{}'.format(self.Counts1_array))
                    self.draw1.counts_plot.plot(self.time_axis, self.Counts1_array, clear=True, pen=self.pen)

                if self.chan2:
                    currRate2 = self.counts2
                    if ii < int(self.lengthaxis.m_as('s')):
                        self.Counts2_array[ii] = currRate2.m_as('cps')
                    else:
                        self.Counts2_array = np.roll(self.Counts2_array, -1)
                        self.Counts2_array[-1] = currRate2.m_as('cps')
                        self.logger.debug('{}'.format(self.Counts2_array))
                    self.draw2.counts_plot.plot(self.time_axis, self.Counts2_array, clear=True, pen=self.pen)

                ii+=1
                time.sleep(self.pausetime.m_as('s'))

        self.running = False
        self.prepare_save()

    def stop_plotting(self):
        """| Stops the thread and the plotting, if there was actually something running.
        | Does not stop the showing of the counts, that happens no matter what.
        """
        if self.running:
            self.logger.info('Should stop counting here')
            self.running = False

            self.plotting_thread.quit()
        else:
            self.logger.warning('There is nothing to stop.')

    def prepare_save(self):
        """| Enables the save button and prepares for saving by constructing a filename based on the data taken,
        | and filling that name in the input.
        | The data always have 4 columns, some of which might just contain zeros.

        """
        self.gui.pushButton_save.setEnabled(True)

        rowlength = len(self.time_axis)
        self.data = np.zeros([rowlength, 4])  # first column will be time, the others counts on different channels

        self.data[:, 0] = self.time_axis.m_as('s')
        channels = ''

        if self.sync:
            self.data[:, 1] = self.Sync_counts_array
            channels += 'sync_'

        if self.chan1:
            self.data[:, 2] = self.Counts1_array
            channels += 'counts1_'

        if self.chan2:
            self.data[:, 3] = self.Counts2_array
            channels += 'counts2_'

        total_time = int(self.pausetime.m_as('ms') * self.lengthaxis.m_as('s'))

        now = datetime.now()
        datum = str(now.year) + '_' + str(now.month) + '_' + str(now.day) + '_'

        filename = '{}counts_{}time{}ms'.format(datum, channels, total_time)
        self.logger.debug('filename: {}'.format(filename))

        self.default_name = filename
        self.set_name_path()

        self.logger.debug(self.path + filename + '.txt')

    def save_counts(self):
        self.logger.info('Saving the last plot')
        np.savetxt(self.path + self.default_name + '.txt',self.data)

        self.gui.pushButton_save.setEnabled(False)
Пример #12
0
class Thorlabs_motor_GUI(BaseGui):
    """
    | The initialization of the single_thorlabs gui.
    | Serial number and name are in the settings given underneath, so thorlabs_instrument knows them.
    | Initialize of the instrument is already done by the init of the thorlabs_instrument, that runs with the with downstairs.
    """

    def __init__(self, thorlabs_instrument, also_close_output=False):
        super().__init__()
        self.logger = logging.getLogger(__name__)

        self.overall_layout = QHBoxLayout()
        self.setLayout(self.overall_layout)

        self.motor = thorlabs_instrument
        self.logger.debug('You are connected to a {}'.format(self.motor.kind_of_device))
        self.title = 'Thorlabs {} GUI'.format(self.motor._name)

        self.saved_position = None
        self.current_position = None

        self.distance = 1.0*ur('mm')

        self.min_distance = -12.0 * ur('mm')
        self.max_distance = 12.0 * ur('mm')

        self.stop = self.stop_moving

        self.initUI()

        self.timer = QTimer()
        self.timer.timeout.connect(self.set_current_motor_position_label)
        self.timer.start(100)       #time in ms

        self.moving_thread = WorkThread(self.motor.move_absolute, self.current_position, True)

    def initUI(self):
        self.logger.debug('Setting up the Single Thorlabs Motor GUI')
        self.setWindowTitle(self.title)

        groupBox = QGroupBox()
        self.overall_layout.addWidget(groupBox)
        groupBox.setStyleSheet("QGroupBox {border: 1px solid green; border-radius: 9px;}")

        self.grid_layout = QGridLayout()
        groupBox.setLayout(self.grid_layout)

        self.make_buttons()
        self.make_boxes()
        self.fill_up_widget()

        self.show()

    def make_buttons(self):
        """This method makes all the buttons in this GUI and connects them to the correct methods.
        """
        self.go_home_button = QPushButton("go home", self)
        self.go_home_button.setToolTip('go to home position')
        self.go_home_button.clicked.connect(self.go_home_motor)

        self.move_button = QPushButton('move to', self)
        self.move_button.setToolTip('move to given input')
        self.move_button.clicked.connect(self.go_to_input)

        self.keyboard_button = QPushButton("keyboard", self)
        self.keyboard_button.setToolTip("use the keyboard to move the thorlabs_motor,\nit works great")
        self.keyboard_button.clicked.connect(self.use_keyboard)

        self.save_pos_button = QPushButton("save pos", self)
        self.save_pos_button.setToolTip('save the current position of the thorlabs_motor')
        self.save_pos_button.clicked.connect(self.save_position)

        self.recover_pos_button = QPushButton("recover pos", self)
        self.recover_pos_button.setToolTip("recover the set position of the thorlabs_motor")
        self.recover_pos_button.clicked.connect(self.recover_position)

        self.stop_button = QPushButton("stop moving", self)
        self.stop_button.setToolTip("stop any moving")
        self.stop_button.clicked.connect(self.stop_moving)
        self.stop_button.setStyleSheet("background-color: red")

    def make_boxes(self):
        """This method makes the labels, spinbox and combobox to make them available for the rest of this class,
        and connects them to the correct methods.
        """
        self.current_motor_position_label = QLabel(self)
        try:
            self.current_motor_position_label.setText(self.motor.position())
        except Exception:
            self.current_motor_position_label.setText("currently/nunavailable")

        self.save_label = QLabel()
        self.save_label.setText('saved:')

        self.distance_spinbox = QDoubleSpinBox(self)
        if self.motor.kind_of_device == 'waveplate':
            self.distance_spinbox.setValue(self.distance.m_as('mm'))
            self.distance = self.distance.m_as('mm') * ur('degrees')
            self.min_distance = 0 * ur('degrees')
            self.max_distance = 360 * ur('degrees')
        else:
            self.distance_spinbox.setValue(self.distance.m_as('mm'))

        self.distance_spinbox.setMinimum(-999999999)  # otherwise you cannot reach higher than 99
        self.distance_spinbox.setMaximum(999999999)
        self.distance_spinbox.valueChanged.connect(self.set_distance)


        self.unit_combobox = QComboBox(self)
        if self.motor.kind_of_device == 'waveplate':
            self.unit_combobox.addItems(["degrees"])
            self.unit_combobox.setCurrentText('degrees')
            self.unit_combobox.setEnabled(False)
        else:
            self.unit_combobox.addItems(["nm", "um", "mm"])
            self.unit_combobox.setCurrentText('mm')

        self.unit_combobox.currentTextChanged.connect(self.set_distance)

        self.set_current_motor_position_label()

    def fill_up_widget(self):
        """This method puts all the widgets in the correct positions in the grid.
        """
        self.grid_layout.addWidget(self.go_home_button, 0, 0)
        self.grid_layout.addWidget(self.move_button, 1, 0)
        self.grid_layout.addWidget(QLabel("use keyboard\n(w/s, q to quit)"), 2, 0)

        self.grid_layout.addWidget(self.current_motor_position_label, 0, 1)
        self.grid_layout.addWidget(self.distance_spinbox, 1, 1)
        self.grid_layout.addWidget(self.keyboard_button, 2, 1)

        self.grid_layout.addWidget(self.save_label, 0, 3)
        self.grid_layout.addWidget(self.unit_combobox, 1, 3)

        self.grid_layout.addWidget(self.save_pos_button, 0, 4)
        self.grid_layout.addWidget(self.recover_pos_button, 1, 4)
        self.grid_layout.addWidget(self.stop_button, 2, 4)


    def set_current_motor_position_label(self):
        """ In the instrument level, the current position is remembered and updated through self.position,
        which is called in the moving_loop during the moves.
        This method read this out (continuously, through the timer in the init) and displays the value.
        """
        self.current_position = self.motor.current_position
        self.current_motor_position_label.setText(str(round(self.current_position, 2)))

#----------------------------------------------------------------------------------------------------------------------

    def set_distance(self):
        """| Reads the value that the user filled in the spinbox and combines it with the unit to make a pint quantity.
        | The pint quantity is saved in self.distance.
        | Also compares the wanted distance with the maximum and minimum values,
        | which are set in the init or changed to degrees in make_distance_spinbox.
        | If the user input is too high or low, the spinbox is changed to the maximum or minimum value.
        """
        value = self.distance_spinbox.value()
        unit = self.unit_combobox.currentText()

        local_distance = ur(str(value)+unit)
        self.logger.debug('local distance value {}'.format(self.distance))
        self.logger.debug("{}".format(value > self.max_distance.m_as(unit)))

        if value > self.max_distance.m_as(unit):
            self.logger.debug('value too high')
            local_max = self.max_distance.to(unit)
            self.logger.debug(str(local_max))
            self.distance_spinbox.setValue(local_max.m_as(unit))
        elif value < self.min_distance.m_as(unit):
            self.logger.debug('value too low')
            local_min = self.min_distance.to(unit)
            self.distance_spinbox.setValue(local_min.m_as(unit))

        self.distance = local_distance
        self.logger.debug('dictionary distance changed to: ' + str(self.distance))

    def go_home_motor(self):
        """Starts a thread and communicates to the instrument to move home.
        The instrument loop will take care of updating the current position and checking whether self.stop is True or False.
        """
        self.moving_thread = WorkThread(self.motor.move_home, True)
        self.moving_thread.start()
        #self.motor.move_home(True)
        #self.set_current_motor_position_label()

    def go_to_input(self):
        """Starts a thread to make an absolute move with the distance that is read out in self.set_distance from the spinbox.
        Value error has become a little bit irrelevant, now that I changed to pint quantities for distance.
        """
        try:
            self.moving_thread = WorkThread(self.motor.move_absolute, self.distance, True)
            self.moving_thread.start()
            #self.set_current_motor_position_label()
        except ValueError:
            self.logger.warning("The input is not a float, change this")
            return

    def save_position(self):
        """Saves the current position for the user.
        Makes sure the user knows the button is pressed by setting it to a different color.
        Gives an error if the thorlabs_motor position has not been found, could be because it is a
        piezo thorlabs_motor or because the software is not running as expected.
        """
        self.save_pos_button.setStyleSheet("background-color: green")
        try:
            self.saved_position = self.motor.position()
            self.logger.debug(str(round(self.saved_position,2)))
            self.save_label.setText("saved: " + str(round(self.saved_position,2)))
        except Exception:
            self.logger.warning("the position has not been set yet")
            self.saved_position = None

    def recover_position(self):
        """Sets position of motors to the saved position with a thread.
        When done, changes the save button to default.
        """
        self.logger.debug("current position: {}".format(self.current_position))
        if self.saved_position == None:
            self.logger.warning("the positions have not been set!")
            return
        else:
            self.moving_thread = WorkThread(self.motor.move_absolute, self.saved_position, True)
            self.moving_thread.start()
            self.save_pos_button.setStyleSheet("default")

    def use_keyboard(self):
        """Set text of keyboard_label to using keyboard.
        Collect events until released.
        """
        self.keyboard_label.setText("using keyboard/npress q to exit")

        self.worker_thread = WorkThread(self.create_keyboard_listener)
        self.worker_thread.start()

        #set the text back to you can use the keyboard.
        self.keyboard_label.setText("use keyboard\nto control selected\n combobox thorlabs_motor:")

    def create_keyboard_listener(self):
        with Listener(on_press=self.on_press, on_release=self.on_release) as listener:
            listener.join()

    def on_press(self, key):
        """ 
        In this method if the w is pressed the thorlabs_motor
        selected in the combobox will move forward or if 
        s is pressed the thorlabs_motor will move backward.
        The w and s are written as: "'w'"/"'s'" because of syntacs.
        """
        if str(key) == "'w'":
            #forward
            self.set_current_motor_position_label()
            self.motor.controller.move_velocity(2)
            self.set_current_motor_position_label()
        elif str(key) == "'s'":
            #backwards
            self.set_current_motor_position_label()
            self.motor.controller.move_velocity(1)
            self.set_current_motor_position_label()
    def on_release(self, key):
        """
        In this method if the w or s is released the thorlabs_motor will stop moving.
        If q is released the keyboard mode stops. 
        """
        if str(key) == "'w'" or str(key) == "'s'":
            #stop the thorlabs_motor from going
            self.motor.stop_moving()
            self.set_current_motor_position_label()
        elif str(key) == "'q'":
            # Stop listener
            if self.worker_thread.isRunning():
                self.set_current_motor_position_label()
                self.worker_thread.quit()
                self.worker_thread.wait()
                return False

    def stop_moving(self):
        """| Stops movement of the current cube.
        | The moving_loop method in the instrument level checks whether the stop is True, and if so, breaks the loop.
        | The stop_moving method in the instrument actually stops the device.
        | Because of the moving_thread that is started in the method move in this class,
        | the loops in the methods in instrument actually keep checking for this stop value.
        """
        self.logger.info('stop moving')
        self.motor.stop = True
        self.motor.stop_moving()

        if self.moving_thread.isRunning:
            self.logger.debug('Moving thread was running.')
            self.moving_thread.quit()

        self.motor.stop = False