class PIDModelMichelsonDemo(PIDModelGeneric):
    params = [
        {
            'title':
            'Stabilization',
            'name':
            'stabilization',
            'type':
            'group',
            'children': [
                {
                    'title': 'Set as zero:',
                    'name': 'set_zero',
                    'type': 'bool',
                    'value': False
                },
                {
                    'title': 'Laser wavelength (nm):',
                    'name': 'laser_wl',
                    'type': 'float',
                    'value': 632.0
                },
                {
                    'title': 'Interfero phase (deg):',
                    'name': 'interfero_phase',
                    'type': 'float',
                    'value': 0
                },
                {
                    'title': 'Interfero position (nm):',
                    'name': 'interfero_position',
                    'type': 'float',
                    'value': 0
                },
                {
                    'title': 'phase sign:',
                    'name': 'phase_sign',
                    'type': 'int',
                    'value': 1,
                    'min': -1,
                    'max': 1,
                    'step': 2
                },
                {
                    'title':
                    'Offsets',
                    'name':
                    'offsets',
                    'type':
                    'group',
                    'children': [
                        {
                            'title': 'Phase offset (deg):',
                            'name': 'phase_offset',
                            'type': 'float',
                            'value': 0
                        },
                        {
                            'title': 'Interfero (nm):',
                            'name': 'interfero_offset',
                            'type': 'float',
                            'value': 0
                        },
                        {
                            'title': 'Piezo (nm):',
                            'name': 'piezo_offset',
                            'type': 'float',
                            'value': 0
                        },
                    ]
                },
            ]
        },
        {
            'title':
            'Calibration',
            'name':
            'calibration',
            'type':
            'group',
            'expanded':
            True,
            'visible':
            True,
            'children': [
                {
                    'title': 'Do calibration:',
                    'name': 'do_calibration',
                    'type': 'bool',
                    'value': False
                },
                {
                    'title': 'Timeout:',
                    'name': 'timeout',
                    'type': 'int',
                    'value': 10000
                },
                {
                    'title':
                    'Calibration',
                    'name':
                    'calibration_move',
                    'type':
                    'group',
                    'expanded':
                    True,
                    'visible':
                    True,
                    'children': [
                        {
                            'title': 'Start pos:',
                            'name': 'start',
                            'type': 'float',
                            'value': 7.
                        },
                        {
                            'title': 'Stop pos:',
                            'name': 'stop',
                            'type': 'float',
                            'value': 8.
                        },
                        {
                            'title': 'Step size:',
                            'name': 'step',
                            'type': 'float',
                            'value': 0.04
                        },
                        {
                            'title': 'Averaging:',
                            'name': 'average',
                            'type': 'int',
                            'value': 1
                        },
                    ]
                },
                {
                    'title':
                    'Ellipse params',
                    'name':
                    'calibration_ellipse',
                    'type':
                    'group',
                    'expanded':
                    True,
                    'visible':
                    True,
                    'children': [
                        {
                            'title': 'Dx:',
                            'name': 'dx',
                            'type': 'float',
                            'value': 0.0
                        },
                        {
                            'title': 'Dy:',
                            'name': 'dy',
                            'type': 'float',
                            'value': 0.0
                        },
                        {
                            'title': 'x0:',
                            'name': 'x0',
                            'type': 'float',
                            'value': 0.00
                        },
                        {
                            'title': 'y0:',
                            'name': 'y0',
                            'type': 'float',
                            'value': 0.00
                        },
                        {
                            'title': 'phi (°):',
                            'name': 'theta',
                            'type': 'float',
                            'value': 0.00
                        },
                    ]
                },
            ]
        },
    ]

    actuators = ['PI']
    actuators_name = ['Axis test PID']

    detectors_type = [
        'DAQ2D'
    ]  # with entries either 'DAQ0D', 'DAQ1D' or 'DAQ2D'or 'DAQND'
    detectors = ['OpenCVCam']
    detectors_name = ['Testing PID']

    def __init__(self, pid_controller):
        super().__init__(pid_controller)
        self.running = False
        self.det_done_flag = False
        self.move_done_flag = False
        self.timeout_scan_flag = False

        self.curr_time = time.perf_counter()

        self.lsqe = LSqEllipse()

        self.phases = np.zeros((100, ))
        self.curr_phase = 0
        self.curr_position = 0

        self.dock_calib = Dock('Calibration')
        widget_calib = QtWidgets.QWidget()
        self.viewer_calib = Viewer1D(widget_calib)
        widget_ellipse = QtWidgets.QWidget()
        self.viewer_ellipse = Viewer1D(widget_ellipse)
        self.viewer_ellipse.show_data([np.zeros((10, )), np.zeros((10, ))])
        self.dock_calib.addWidget(widget_calib)
        self.dock_calib.addWidget(widget_ellipse, row=0, col=1)
        self.pid_controller.dock_area.addDock(self.dock_calib)
        self.dock_calib.float()

        self.timer = QTimer()
        self.timer.setSingleShot(True)
        self.timer.timeout.connect(self.timeout)

    def ini_model(self):
        """
        Defines all the action to be performed on the initialized modules (main pid, actuators, detectors)
        Either here for specific things (ROI, ...) or within the preset of the current model

        """
        model_name = self.pid_controller.settings.child(
            'models', 'model_class').value()

        self.pid_controller.detector_modules[0].ui.viewers[0].ui.roiBtn.click()

        #try to get corresponding roi file
        filename = os.path.join(get_set_pid_path(), model_name + '.roi2D')
        if os.path.isfile(filename):
            self.pid_controller.detector_modules[0].ui.viewers[0].load_ROI(
                filename)

        QtWidgets.QApplication.processEvents()
        if self.pid_controller.detector_modules[0].Initialized_state:
            #self.pid_controller.detector_modules[0].settings.child('main_settings', 'wait_time').setValue(0)
            self.pid_controller.detector_modules[0].grab_data()
        QThread.msleep(500)
        QtWidgets.QApplication.processEvents()

        self.pid_controller.settings.child('main_settings',
                                           'update_modules').setValue(True)
        QtWidgets.QApplication.processEvents()
        QThread.msleep(500)
        QtWidgets.QApplication.processEvents()

        items = self.pid_controller.settings.child(
            'main_settings', 'detector_modules').value().copy()
        if items is not None:
            items['selected'] = items['all_items'][0:2]
            self.pid_controller.settings.child(
                'main_settings', 'detector_modules').setValue(items)

        self.pid_controller.settings.child(
            'main_settings', 'pid_controls', 'output_limits',
            'output_limit_min').setValue(0)  #in microns
        self.pid_controller.settings.child(
            'main_settings', 'pid_controls', 'output_limits',
            'output_limit_min_enabled').setValue(True)
        self.pid_controller.settings.child(
            'main_settings', 'pid_controls', 'output_limits',
            'output_limit_max').setValue(15)  #in microns
        self.pid_controller.settings.child(
            'main_settings', 'pid_controls', 'output_limits',
            'output_limit_max_enabled').setValue(True)

        self.pid_controller.settings.child('main_settings', 'pid_controls',
                                           'pid_constants', 'kp').setValue(0.5)
        self.pid_controller.settings.child('main_settings', 'pid_controls',
                                           'pid_constants', 'ki').setValue(0.2)
        self.pid_controller.settings.child('main_settings', 'pid_controls',
                                           'pid_constants',
                                           'kd').setValue(0.0001)

        self.pid_controller.settings.child('main_settings', 'pid_controls',
                                           'filter',
                                           'filter_enable').setValue(True)
        self.pid_controller.settings.child(
            'main_settings', 'pid_controls', 'filter', 'filter_step').setValue(
                self.settings.child('stabilization', 'laser_wl').value() /
                1000 / 6 / 2)  #in microns

        self.pid_controller.setpoint_sb.setValue(7.5)
        self.pid_controller.settings.child('main_settings', 'pid_controls',
                                           'sample_time').setValue(50)

    def get_ellipse_fit(self, center, width, height, theta):

        t = np.linspace(0, 2 * np.pi, 1000)
        ellipse_x = center[0] + width * np.cos(t) * np.cos(
            theta) - height * np.sin(t) * np.sin(theta)
        ellipse_y = center[1] + width * np.cos(t) * np.sin(
            theta) + height * np.sin(t) * np.cos(theta)

        return ellipse_x, ellipse_y

    def update_settings(self, param):
        """
        Get a parameter instance whose value has been modified by a user on the UI
        Parameters
        ----------
        param: (Parameter) instance of Parameter object
        """
        if param.name() == 'do_calibration':
            if param.value():
                self.running = True
                self.do_calibration()
                QtWidgets.QApplication.processEvents()

            else:
                self.running = False
        elif param.name() == 'set_zero':
            self.phases = np.zeros((100, ))
            position = self.pid_controller.actuator_modules[0].current_position
            self.settings.child('stabilization', 'offsets',
                                'phase_offset').setValue(self.curr_phase)
            self.settings.child('stabilization', 'offsets',
                                'interfero_offset').setValue(position * 1000)
            self.settings.child('stabilization', 'offsets',
                                'piezo_offset').setValue(position * 1000)
            data = self.pid_controller.detector_modules[0].data_to_save_export
            data_export = OrderedDict()
            data_export[data['name']] = data
            self.pid_controller.command_pid.emit(
                ThreadCommand('input', [data_export]))
            QtWidgets.QApplication.processEvents()
            self.settings.child('stabilization', 'set_zero').setValue(False)

    def do_calibration(self):
        Naverage = self.settings.child('calibration', 'calibration_move',
                                       'average').value()

        steps = linspace_step(
            self.settings.child('calibration', 'calibration_move',
                                'start').value(),
            self.settings.child('calibration', 'calibration_move',
                                'stop').value(),
            self.settings.child('calibration', 'calibration_move',
                                'step').value())

        self.detector_data = np.zeros((steps.size, 2))
        self.detector_data_average = np.zeros((steps.size, 2))

        for mod in self.pid_controller.actuator_modules:
            mod.move_done_signal.connect(self.move_done)
        for mod in self.pid_controller.detector_modules:
            mod.grab_done_signal.connect(self.det_done)

        self.viewer_calib.x_axis = steps

        for ind_average in range(Naverage):
            for ind_step, step in enumerate(steps):
                self.move_done_flag = False
                self.pid_controller.actuator_modules[0].move_Abs(step)
                self.wait_for_move_done()

                self.det_done_flag = False
                self.pid_controller.detector_modules[0].grab_data()
                self.wait_for_det_done()
                for ind_data, name in enumerate(self.data_names):
                    self.detector_data[ind_step, ind_data] = \
                    self.pid_controller.detector_modules[0].data_to_save_export[name[1]][name[2]]
                if not self.running:
                    break
            self.detector_data_average = (
                ind_average * self.detector_data_average +
                self.detector_data) / (ind_average + 1)
            self.viewer_calib.show_data([
                self.detector_data_average[:, 0], self.detector_data_average[:,
                                                                             1]
            ])

            self.lsqe.fit([
                self.detector_data_average[:, 0], self.detector_data_average[:,
                                                                             1]
            ])
            center, width, height, theta = self.lsqe.parameters()
            ellipse_x, ellipse_y = self.get_ellipse_fit(
                center, width, height, theta)

            self.viewer_ellipse.plot_channels[0].setData(
                x=self.detector_data_average[:, 0],
                y=self.detector_data_average[:, 1])
            self.viewer_ellipse.plot_channels[1].setData(x=ellipse_x,
                                                         y=ellipse_y)

            self.settings.child('calibration', 'calibration_ellipse',
                                'x0').setValue(center[0])
            self.settings.child('calibration', 'calibration_ellipse',
                                'y0').setValue(center[1])
            self.settings.child('calibration', 'calibration_ellipse',
                                'dx').setValue(width)
            self.settings.child('calibration', 'calibration_ellipse',
                                'dy').setValue(height)
            self.settings.child('calibration', 'calibration_ellipse',
                                'theta').setValue(np.rad2deg(theta))
            if not self.running:
                break

            QtWidgets.QApplication.processEvents()
        self.pid_controller.setpoint_sb.setValue(
            self.pid_controller.setpoint_sb.value())
        self.settings.child('calibration', 'do_calibration').setValue(False)
        for mod in self.pid_controller.actuator_modules:
            mod.move_done_signal.disconnect(self.move_done)
        for mod in self.pid_controller.detector_modules:
            mod.grab_done_signal.disconnect(self.det_done)

    def timeout(self):
        """
            Send the status signal *'Time out during acquisition'* and stop the timer.
        """
        self.timeout_scan_flag = True
        self.timer.stop()
        self.pid_controller.update_status("Timeout during acquisition",
                                          log_type='log')

    def wait_for_det_done(self):
        self.timeout_scan_flag = False
        self.timer.start(self.settings.child('calibration', 'timeout').value())
        while not (self.det_done_flag or self.timeout_scan_flag):
            #wait for grab done signals to end
            QtWidgets.QApplication.processEvents()
        self.timer.stop()

    def det_done(self, data):
        """
            | Initialize 0D/1D/2D datas from given data parameter.
            | Update h5_file group and array.
            | Save 0D/1D/2D datas.

            =============== ============================== ======================================
            **Parameters**    **Type**                      **Description**
            *data*          Double precision float array   The initializing data of the detector
            =============== ============================== ======================================
        """
        #print(data['data0D']['OpenCV_Integrated_ROI_00'])
        if not (self.settings.child('calibration', 'do_calibration').value()
                or self.pid_controller.run_action.isChecked()):
            det_done_datas = OrderedDict()
            det_done_datas[data['name']] = data
            self.convert_input(det_done_datas)

        self.det_done_flag = True

    def wait_for_move_done(self):
        self.timeout_scan_flag = False
        self.timer.start(self.settings.child('calibration', 'timeout').value())
        while not (self.move_done_flag or self.timeout_scan_flag):
            #wait for move done signals to end
            QtWidgets.QApplication.processEvents()
        self.timer.stop()

    def move_done(self, name, position):
        #print(position)
        self.curr_position = position
        self.move_done_flag = True

    def get_phi_from_xy(self, x, y):
        x0 = self.settings.child('calibration', 'calibration_ellipse',
                                 'x0').value()
        y0 = self.settings.child('calibration', 'calibration_ellipse',
                                 'y0').value()
        dx = self.settings.child('calibration', 'calibration_ellipse',
                                 'dx').value()
        dy = self.settings.child('calibration', 'calibration_ellipse',
                                 'dy').value()
        theta = np.deg2rad(
            self.settings.child('calibration', 'calibration_ellipse',
                                'theta').value())

        #apply rotation of -theta to get ellipse on main axes
        v = np.array([x, y])
        rot = rot = np.array([[np.cos(-theta), -np.sin(-theta)],
                              [np.sin(-theta), np.cos(-theta)]])
        vprim = rot @ v

        phi = np.arctan2(
            (vprim[1] + x0 * np.sin(theta) - y0 * np.cos(theta)) / dy,
            (vprim[0] - x0 * np.cos(theta) - y0 * np.sin(theta)) / dx)
        self.settings.child('stabilization', 'interfero_phase').setValue(phi)
        self.curr_phase = phi
        return phi

    def interfero_from_phase(self, phi):
        laser_wl = self.settings.child('stabilization', 'laser_wl').value()
        phi_offset = self.settings.child('stabilization', 'offsets',
                                         'phase_offset').value()
        interfero_offset = self.settings.child('stabilization', 'offsets',
                                               'interfero_offset').value()
        phase_sign = self.settings.child('stabilization', 'phase_sign').value()

        self.phases = np.append(self.phases, [phi])
        self.phases = np.unwrap(self.phases[1:])

        interfero_position = interfero_offset + phase_sign * laser_wl / (
            2 * np.pi * 2) * (self.phases[-1] - phi_offset)
        return interfero_position

    def convert_input(self, measurements):
        """
        Convert the measurements in the units to be fed to the PID (same dimensionality as the setpoint)
        Parameters
        ----------
        measurements: (list) List of object from which the model extract a value of the same units as the setpoint

        Returns
        -------
        float: the converted input

        """
        #print('input conversion done')

        x = measurements[self.data_names[0][0]][self.data_names[0][1]][
            self.data_names[0][2]]
        y = measurements[self.data_names[1][0]][self.data_names[1][1]][
            self.data_names[1][2]]
        phi = self.get_phi_from_xy(x, y)

        pos = self.interfero_from_phase(phi)  #in nm
        self.settings.child('stabilization',
                            'interfero_position').setValue(pos)
        self.curr_input = pos / 1000
        return pos / 1000  # in microns for the PI stage

    def convert_output(self, output, dt, stab=True):
        """
        Convert the output of the PID in units to be fed into the actuator
        Parameters
        ----------
        output: (float) output value from the PID from which the model extract a value of the same units as the actuator

        Returns
        -------
        list: the converted output as a list (if there are a few actuators)

        """
        #print('output converted')
        #no conversion needed
        self.curr_output = output
        if stab:
            offset = 0  #self.settings.child('stabilization', 'offsets', 'piezo_offset').value()
        else:
            offset = 0
        return [output + offset]
Esempio n. 2
0
class ChronoTimer(QObject):
    def __init__(self, parent, duration=None):
        """

        :param parent:
        :param Nteams:
        :param duration: (dict) containing optional keys : days, minutes, seconds, hours, weeks, milliseconds, microseconds
        """
        super().__init__()
        self.area = parent
        if duration is None:
            self.type = 'chrono'
            self.duration = timedelta()
        else:
            self.type = 'timer'
            self.duration = timedelta(**duration)  #seconds
        self.displayed_time = 0  #in seconds
        self.started = False
        self.timer = QTimer()
        self.timer.setInterval(100)
        self.timer.timeout.connect(self.display_time)

        self.setup_ui()

    def setup_ui(self):

        self.dock_chrono_timer = Dock(self.type.capitalize())
        self.area.addDock(self.dock_chrono_timer)
        self.dock_chrono_timer.float()

        widget_chrono_timer = QtWidgets.QWidget()
        self.dock_chrono_timer.addWidget(widget_chrono_timer)

        self.layout_lcd = QtWidgets.QVBoxLayout()
        widget_chrono_timer.setLayout(self.layout_lcd)

        self.dock_chrono_timer.setAutoFillBackground(True)
        palette = self.dock_chrono_timer.palette()
        palette.setColor(palette.Background, QtGui.QColor(0, 0, 0))
        self.dock_chrono_timer.setPalette(palette)

        self.time_lcd = QtWidgets.QLCDNumber(8)
        self.set_lcd_color(self.time_lcd, 'red')
        self.layout_lcd.addWidget(self.time_lcd)

        hours, minutes, seconds = self.get_times(self.duration)

        self.time_lcd.display('{:02d}:{:02d}:{:02d}'.format(
            hours, minutes, seconds))

        self.dock_controls = Dock('Chrono/Timer Controls')
        self.area.addDock(self.dock_controls)
        self.dock_controls.setOrientation('vertical', True)
        self.dock_controls.setMaximumHeight(150)

        self.widget_controls = QtWidgets.QWidget()
        self.controls_layout = QtWidgets.QVBoxLayout()
        self.widget_controls.setLayout(self.controls_layout)

        hor_layout = QtWidgets.QHBoxLayout()
        hor_widget = QtWidgets.QWidget()
        hor_widget.setLayout(hor_layout)

        self.controls_layout.addWidget(hor_widget)

        icon = QtGui.QIcon()
        icon.addPixmap(QtGui.QPixmap(":/icons/Icon_Library/run2.png"),
                       QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.start_pb = PushButtonShortcut(icon,
                                           'Start',
                                           shortcut='Home',
                                           shortcut_widget=self.area)
        self.start_pb.clicked.connect(self.start)
        self.start_pb.setToolTip('home ("début") key shorcut')

        hor_layout.addWidget(self.start_pb)

        icon = QtGui.QIcon()
        icon.addPixmap(QtGui.QPixmap(":/icons/Icon_Library/pause.png"),
                       QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.pause_pb = PushButtonShortcut(icon,
                                           'Pause',
                                           shortcut='Ctrl+p',
                                           shortcut_widget=self.area)
        self.pause_pb.setCheckable(True)
        self.pause_pb.setToolTip("Ctrl+p key shortcut")
        self.pause_pb.clicked.connect(self.pause)
        hor_layout.addWidget(self.pause_pb)

        icon = QtGui.QIcon()
        icon.addPixmap(QtGui.QPixmap(":/icons/Icon_Library/Refresh2.png"),
                       QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.reset_pb = PushButtonShortcut(icon,
                                           'Reset',
                                           shortcut='F5',
                                           shortcut_widget=self.area)
        self.reset_pb.setToolTip('F5 key shortcut')
        self.reset_pb.clicked.connect(self.reset)
        hor_layout.addWidget(self.reset_pb)

        self.dock_controls.addWidget(self.widget_controls)

    def get_times(self, duration):
        seconds = int(duration.total_seconds() % 60)
        total_minutes = duration.total_seconds() // 60
        minutes = int(total_minutes % 60)
        hours = int(total_minutes // 60)

        return hours, minutes, seconds

    def get_elapsed_time(self):
        return time.perf_counter() - self.ini_time

    def display_time(self):
        elapsed_time = self.get_elapsed_time()
        if self.type == 'timer':
            display_timedelta = self.duration - timedelta(seconds=elapsed_time)
        else:
            display_timedelta = self.duration + timedelta(seconds=elapsed_time)

        self.displayed_time = display_timedelta.total_seconds()
        if display_timedelta.total_seconds() <= 0:
            self.reset()
            return
        else:
            hours, minutes, seconds = self.get_times(display_timedelta)

            self.time_lcd.display('{:02d}:{:02d}:{:02d}'.format(
                hours, minutes, seconds))
            QtWidgets.QApplication.processEvents()

    def start(self):
        self.ini_time = time.perf_counter()
        self.timer.start()
        self.started = True
        self.start_pb.setEnabled(False)

    def pause(self):
        if self.pause_pb.isChecked():
            self.started = False
            self.timer.stop()
            self.paused_time = time.perf_counter()
        else:
            elapsed_pause_time = time.perf_counter() - self.paused_time
            self.ini_time += elapsed_pause_time
            self.timer.start()
            self.started = True

    def reset(self):
        if self.pause_pb.isChecked():
            self.pause_pb.setChecked(False)
            QtWidgets.QApplication.processEvents()
        self.timer.stop()
        self.start_pb.setEnabled(True)
        self.started = False
        hours, minutes, seconds = self.get_times(self.duration)
        self.time_lcd.display('{:02d}:{:02d}:{:02d}'.format(
            hours, minutes, seconds))

    def set_lcd_color(self, lcd, color):
        palette = lcd.palette()
        #lcd.setPalette(QtGui.QPalette(Qt.red))
        if hasattr(Qt, color):
            palette.setBrush(palette.WindowText, getattr(Qt, color))
            palette.setColor(palette.Background, QtGui.QColor(0, 0, 0))
        lcd.setPalette(palette)