예제 #1
0
class DAQ_PID(QObject):
    """
    """
    log_signal = pyqtSignal(str)
    #look for eventual model files
    command_pid = pyqtSignal(ThreadCommand)
    command_stage = pyqtSignal(ThreadCommand)
    move_done_signal = pyqtSignal(str, float)

    models = []
    try:
        model_mod = importlib.import_module('pymodaq_pid_models')
        for ind_file, entry in enumerate(os.scandir(os.path.join(model_mod.__path__[0], 'models'))):
            if not entry.is_dir() and entry.name != '__init__.py':
                try:
                    file, ext = os.path.splitext(entry.name)
                    importlib.import_module('.'+file, model_mod.__name__+'.models')

                    models.append(file)
                except Exception as e:
                    print(e)
        if 'PIDModelMock' in models:
            mods = models
            mods.pop(models.index('PIDModelMock'))
            models = ['PIDModelMock']
            models.extend(mods)

    except Exception as e:
        print(e)

    if len(models) == 0:
        logger.warning('No valid installed models')


    def __init__(self,area, detector_modules = [], actuator_modules =[]):
        QLocale.setDefault(QLocale(QLocale.English, QLocale.UnitedStates))
        super(DAQ_PID,self).__init__()

        self.settings = Parameter.create(title='PID settings', name='pid_settings', type='group', children=params)
        self.title = 'PyMoDAQ PID'
        self.Initialized_state = False
        self.model_class = None
        self.detector_modules = detector_modules
        self.actuator_modules = actuator_modules
        self.dock_area = area
        self.overshoot = None
        self.check_moving = False
        self.preset_manager = PresetManager()
        self.setupUI()
        self.command_stage.connect(self.move_Abs) #to be compatible with actuator modules within daq scan

        self.enable_controls_pid(False)


        self.enable_controls_pid_run(False)



    def ini_PID(self):

        if self.ini_PID_action.isChecked():
            output_limits =[None,None]
            if self.settings.child('main_settings', 'pid_controls', 'output_limits', 'output_limit_min_enabled').value():
                output_limits[0] = self.settings.child('main_settings', 'pid_controls', 'output_limits', 'output_limit_min').value()
            if self.settings.child('main_settings', 'pid_controls', 'output_limits', 'output_limit_max_enabled').value():
                output_limits[1] = self.settings.child('main_settings', 'pid_controls', 'output_limits', 'output_limit_max').value()


            self.PIDThread = QThread()
            pid_runner = PIDRunner(self.model_class,
                            [mod.move_done_signal for mod in self.actuator_modules],
                            [mod.grab_done_signal for mod in self.detector_modules],
                           [mod.command_stage for mod in self.actuator_modules],
                           [mod.command_detector for mod in self.detector_modules],
                            dict(Kp=self.settings.child('main_settings', 'pid_controls', 'pid_constants', 'kp').value(),
                            Ki=self.settings.child('main_settings', 'pid_controls', 'pid_constants', 'ki').value(),
                            Kd=self.settings.child('main_settings', 'pid_controls', 'pid_constants', 'kd').value(),
                            setpoint=self.settings.child('main_settings', 'pid_controls', 'set_point').value(),
                            sample_time=self.settings.child('main_settings', 'pid_controls', 'sample_time').value()/1000,
                            output_limits=output_limits,
                            auto_mode=False),
                            filter=dict(enable=self.settings.child('main_settings', 'pid_controls', 'filter', 'filter_enable').value(),
                                         value=self.settings.child('main_settings', 'pid_controls', 'filter', 'filter_step').value()),
                            det_averaging=[mod.settings.child('main_settings', 'Naverage').value() for mod in self.detector_modules],
                                   )

            self.PIDThread.pid_runner = pid_runner
            pid_runner.pid_output_signal.connect(self.process_output)
            pid_runner.status_sig.connect(self.thread_status)
            self.command_pid.connect(pid_runner.queue_command)

            pid_runner.moveToThread(self.PIDThread)

            self.PIDThread.start()
            self.pid_led.set_as_true()
            self.enable_controls_pid_run(True)



        else:
            if hasattr(self,'PIDThread'):
                if self.PIDThread.isRunning():
                    try:
                        self.PIDThread.quit()
                    except:
                        pass
            self.pid_led.set_as_false()
            self.enable_controls_pid_run(False)

        self.Initialized_state = True

    pyqtSlot(dict)
    def process_output(self, datas):
        self.output_viewer.show_data([[dat] for dat in datas['output']])
        self.input_viewer.show_data([[dat] for dat in datas['input']])
        self.currpoint_sb.setValue(np.mean(datas['input']))

        if self.check_moving:
            if np.abs(np.mean(datas['input'])-self.settings.child('main_settings', 'pid_controls', 'set_point').value()) < \
                    self.settings.child('main_settings', 'epsilon').value():
                self.move_done_signal.emit(self.title, np.mean(datas['input']))
                self.check_moving = False
                print('Move from {:s} is done: {:f}'.format('PID', np.mean(datas['input'])))


    @pyqtSlot(ThreadCommand)
    def move_Abs(self, command=ThreadCommand()):
        """
        """
        if command.command == "move_Abs":
            self.check_moving = True
            self.setpoint_sb.setValue(command.attributes[0])
            QtWidgets.QApplication.processEvents()



    def enable_controls_pid(self,enable = False):
        self.ini_PID_action.setEnabled(enable)
        self.setpoint_sb.setOpts(enabled = enable)

    def enable_controls_pid_run(self,enable = False):
        self.run_action.setEnabled(enable)
        self.pause_action.setEnabled(enable)


    def setupUI(self):

        self.dock_pid = Dock('PID controller', self.dock_area)
        self.dock_area.addDock(self.dock_pid)

        #%% create logger dock
        self.logger_dock=Dock("Logger")
        self.logger_list=QtWidgets.QListWidget()
        self.logger_list.setMinimumWidth(300)
        self.logger_dock.addWidget(self.logger_list)
        self.dock_area.addDock(self.logger_dock,'right')
        self.logger_dock.setVisible(True)





        widget = QtWidgets.QWidget()
        widget_toolbar = QtWidgets.QWidget()
        verlayout = QtWidgets.QVBoxLayout()
        widget.setLayout(verlayout)
        toolbar_layout = QtWidgets.QGridLayout()
        widget_toolbar.setLayout(toolbar_layout)


        iconquit = QtGui.QIcon()
        iconquit.addPixmap(QtGui.QPixmap(":/icons/Icon_Library/close2.png"), QtGui.QIcon.Normal,
                          QtGui.QIcon.Off)
        self.quit_action = QtWidgets.QPushButton(iconquit, "Quit")
        self.quit_action.setToolTip('Quit the application')
        toolbar_layout.addWidget(self.quit_action,0,0,1,2)
        self.quit_action.clicked.connect(self.quit_fun)

        iconini = QtGui.QIcon()
        iconini.addPixmap(QtGui.QPixmap(":/icons/Icon_Library/ini.png"), QtGui.QIcon.Normal,
                          QtGui.QIcon.Off)
        self.ini_model_action = QtWidgets.QPushButton(iconini, "Init Model")
        self.ini_model_action.setToolTip('Initialize the chosen model')
        toolbar_layout.addWidget(self.ini_model_action,2,0)
        self.ini_model_action.clicked.connect(self.ini_model)
        self.model_led = QLED()
        toolbar_layout.addWidget(self.model_led, 2,1)

        self.ini_PID_action = QtWidgets.QPushButton(iconini, "Init PID")
        self.ini_PID_action.setToolTip('Initialize the PID loop')
        toolbar_layout.addWidget(self.ini_PID_action,2,2)
        self.ini_PID_action.setCheckable(True)
        self.ini_PID_action.clicked.connect(self.ini_PID)
        self.pid_led = QLED()
        toolbar_layout.addWidget(self.pid_led, 2,3)

        self.iconrun = QtGui.QIcon()
        self.iconrun.addPixmap(QtGui.QPixmap(":/icons/Icon_Library/run2.png"), QtGui.QIcon.Normal,
                           QtGui.QIcon.Off)
        self.icon_stop = QtGui.QIcon()
        self.icon_stop.addPixmap(QtGui.QPixmap(":/icons/Icon_Library/stop.png"))
        self.run_action = QtWidgets.QPushButton(self.iconrun, "", None)
        self.run_action.setToolTip('Start PID loop')
        self.run_action.setCheckable(True)
        toolbar_layout.addWidget(self.run_action,0,2)
        self.run_action.clicked.connect(self.run_PID)


        iconpause = QtGui.QIcon()
        iconpause.addPixmap(QtGui.QPixmap(":/icons/Icon_Library/pause.png"), QtGui.QIcon.Normal,
                           QtGui.QIcon.Off)
        self.pause_action = QtWidgets.QPushButton(iconpause, "", None)
        self.pause_action.setToolTip('Pause PID')
        self.pause_action.setCheckable(True)
        toolbar_layout.addWidget(self.pause_action,0,3)
        self.pause_action.setChecked(True)
        self.pause_action.clicked.connect(self.pause_PID)

        lab = QtWidgets.QLabel('Set Point:')
        toolbar_layout.addWidget(lab, 3,0,1,2)

        self.setpoint_sb = custom_tree.SpinBoxCustom()
        self.setpoint_sb.setMinimumHeight(40)
        font = self.setpoint_sb.font()
        font.setPointSizeF(20)
        self.setpoint_sb.setFont(font)
        self.setpoint_sb.setDecimals(6)
        toolbar_layout.addWidget(self.setpoint_sb,3,2,1,2)
        self.setpoint_sb.valueChanged.connect(self.settings.child('main_settings', 'pid_controls', 'set_point').setValue)

        lab1 = QtWidgets.QLabel('Current Point:')
        toolbar_layout.addWidget(lab1, 4,0,1,2)

        self.currpoint_sb = custom_tree.SpinBoxCustom()
        self.currpoint_sb.setMinimumHeight(40)
        self.currpoint_sb.setReadOnly(True)
        self.currpoint_sb.setDecimals(6)
        self.currpoint_sb.setButtonSymbols(QtWidgets.QAbstractSpinBox.NoButtons)
        font = self.currpoint_sb.font()
        font.setPointSizeF(20)
        self.currpoint_sb.setFont(font)
        toolbar_layout.addWidget(self.currpoint_sb,4,2,1,2)


        #create main parameter tree
        self.settings_tree = ParameterTree()
        self.settings_tree.setParameters(self.settings, showTop=False)

        verlayout.addWidget(widget_toolbar)
        verlayout.addWidget(self.settings_tree)


        self.dock_output = Dock('PID output')
        widget_output = QtWidgets.QWidget()
        self.output_viewer = Viewer0D(widget_output)
        self.dock_output.addWidget(widget_output)
        self.dock_area.addDock(self.dock_output, 'right')

        self.dock_input = Dock('PID input')
        widget_input = QtWidgets.QWidget()
        self.input_viewer = Viewer0D(widget_input)
        self.dock_input.addWidget(widget_input)
        self.dock_area.addDock(self.dock_input, 'bottom',self.dock_output)

        if len(self.models) != 0:
            self.get_set_model_params(self.models[0])


        #connecting from tree
        self.settings.sigTreeStateChanged.connect(self.parameter_tree_changed)#any changes on the settings will update accordingly the detector
        self.dock_pid.addWidget(widget)

    def get_set_model_params(self, model_file):
        self.settings.child('models', 'model_params').clearChildren()
        model = importlib.import_module('.' + model_file, self.model_mod.__name__+'.models')
        model_class = getattr(model, model_file)
        params = getattr(model_class, 'params')
        self.settings.child('models', 'model_params').addChildren(params)

    def run_PID(self):
        if self.run_action.isChecked():
            self.run_action.setIcon(self.icon_stop)
            self.command_pid.emit(ThreadCommand('start_PID', [self.model_class.curr_input]))
            QtWidgets.QApplication.processEvents()

            QtWidgets.QApplication.processEvents()

            self.command_pid.emit(ThreadCommand('run_PID', [self.model_class.curr_output]))
        else:
            self.run_action.setIcon(self.iconrun)
            self.command_pid.emit(ThreadCommand('stop_PID'))

            QtWidgets.QApplication.processEvents()

    def pause_PID(self):
        self.command_pid.emit(ThreadCommand('pause_PID', [self.pause_action.isChecked()]))

    def update_status(self,txt,log_type=None):
        """
            Show the txt message in the status bar with a delay of wait_time ms.

            =============== =========== =======================
            **Parameters**    **Type**    **Description**
            *txt*             string      The message to show
            *wait_time*       int         the delay of showing
            *log_type*        string      the type of the log
            =============== =========== =======================
        """
        try:
            if log_type is not None:
                self.log_signal.emit(txt)
                logging.info(txt)
        except Exception as e:
            pass

    @pyqtSlot(str)
    def add_log(self,txt):
        """
            Add the QListWisgetItem initialized with txt informations to the User Interface logger_list and to the save_parameters.logger array.

            =============== =========== ======================
            **Parameters**    **Type**   **Description**
            *txt*             string     the log info to add.
            =============== =========== ======================
        """
        try:
            now=datetime.datetime.now()
            new_item=QtWidgets.QListWidgetItem(now.strftime('%Y/%m/%d %H:%M:%S')+": "+txt)
            self.logger_list.addItem(new_item)
        except:
            pass

    def set_file_preset(self,model):
        """
            Set a file managers from the converted xml file given by the filename parameter.


            =============== =========== ===================================================
            **Parameters**    **Type**    **Description**
            *filename*        string      the name of the xml file to be converted/treated
            =============== =========== ===================================================

            Returns
            -------
            (Object list, Object list) tuple
                The updated (Move modules list, Detector modules list).

            See Also
            --------
            custom_tree.XML_file_to_parameter, set_param_from_param, stop_moves, update_status,DAQ_Move_main.daq_move, DAQ_viewer_main.daq_viewer
        """

        filename = os.path.join(get_set_pid_path(), model + '.xml')
        self.preset_file = filename
        self.preset_manager.set_file_preset(filename, show=False)
        self.move_docks = []
        self.det_docks_settings = []
        self.det_docks_viewer = []
        move_forms = []
        actuator_modules = []
        detector_modules = []
        move_types = []


        #################################################################
        ###### sort plugins by IDs and within the same IDs by Master and Slave status
        plugins=[{'type': 'move', 'value': child} for child in self.preset_manager.preset_params.child(('Moves')).children()]+[{'type': 'det', 'value': child} for child in self.preset_manager.preset_params.child(('Detectors')).children()]

        for plug in plugins:
            plug['ID']=plug['value'].child('params','main_settings','controller_ID').value()
            if plug["type"]=='det':
                plug['status']=plug['value'].child('params','detector_settings','controller_status').value()
            else:
                plug['status']=plug['value'].child('params','move_settings', 'multiaxes', 'multi_status').value()

        IDs=list(set([plug['ID'] for plug in plugins]))
        #%%
        plugins_sorted=[]
        for id in IDs:
            plug_Ids=[]
            for plug in plugins:
                if plug['ID']==id:
                    plug_Ids.append(plug)
            plug_Ids.sort(key=lambda status: status['status'])
            plugins_sorted.append(plug_Ids)
        #################################################################
        #######################

        ind_move=-1
        ind_det=-1
        for plug_IDs in plugins_sorted:
            for ind_plugin, plugin in enumerate(plug_IDs):


                plug_name=plugin['value'].child(('name')).value()
                plug_init=plugin['value'].child(('init')).value()
                plug_settings=plugin['value'].child(('params'))

                if plugin['type'] == 'move':
                    ind_move+=1
                    plug_type=plug_settings.child('main_settings','move_type').value()
                    self.move_docks.append(Dock(plug_name, size=(150,250)))
                    if ind_move==0:
                        self.dock_area.addDock(self.move_docks[-1], 'top',self.logger_dock)
                    else:
                        self.dock_area.addDock(self.move_docks[-1], 'above',self.move_docks[-2])
                    move_forms.append(QtWidgets.QWidget())
                    mov_mod_tmp=DAQ_Move(move_forms[-1],plug_name)

                    mov_mod_tmp.ui.Stage_type_combo.setCurrentText(plug_type)
                    mov_mod_tmp.ui.Quit_pb.setEnabled(False)
                    QtWidgets.QApplication.processEvents()

                    set_param_from_param(mov_mod_tmp.settings,plug_settings)
                    QtWidgets.QApplication.processEvents()

                    mov_mod_tmp.bounds_signal[bool].connect(self.stop_moves)
                    self.move_docks[-1].addWidget(move_forms[-1])
                    actuator_modules.append(mov_mod_tmp)

                    try:
                        if ind_plugin==0: #should be a master type plugin
                            if plugin['status']!="Master":
                                raise Exception('error in the master/slave type for plugin {}'.format(plug_name))
                            if plug_init:
                                actuator_modules[-1].ui.IniStage_pb.click()
                                QtWidgets.QApplication.processEvents()
                                if 'Mock' in plug_type:
                                    QThread.msleep(500)
                                else:
                                    QThread.msleep(4000)  # to let enough time for real hardware to init properly
                                QtWidgets.QApplication.processEvents()
                                master_controller=actuator_modules[-1].controller
                        else:
                            if plugin['status']!="Slave":
                                raise Exception('error in the master/slave type for plugin {}'.format(plug_name))
                            if plug_init:
                                actuator_modules[-1].controller=master_controller
                                actuator_modules[-1].ui.IniStage_pb.click()
                                QtWidgets.QApplication.processEvents()
                                if 'Mock' in plug_type:
                                    QThread.msleep(500)
                                else:
                                    QThread.msleep(4000)  # to let enough time for real hardware to init properly
                                QtWidgets.QApplication.processEvents()
                    except Exception as e:
                        self.update_status(getLineInfo()+ str(e),'log')


                else:
                    ind_det+=1
                    plug_type=plug_settings.child('main_settings','DAQ_type').value()
                    plug_subtype=plug_settings.child('main_settings','detector_type').value()

                    self.det_docks_settings.append(Dock(plug_name+" settings", size=(150,250)))
                    self.det_docks_viewer.append(Dock(plug_name+" viewer", size=(350,350)))

                    if ind_det==0:
                        self.logger_dock.area.addDock(self.det_docks_settings[-1], 'bottom', self.dock_input) #dock_area of the logger dock
                    else:
                        self.dock_area.addDock(self.det_docks_settings[-1], 'bottom',self.det_docks_settings[-2])
                    self.dock_area.addDock(self.det_docks_viewer[-1],'right',self.det_docks_settings[-1])

                    det_mod_tmp=DAQ_Viewer(self.dock_area,dock_settings=self.det_docks_settings[-1],
                                                        dock_viewer=self.det_docks_viewer[-1],title=plug_name,
                                           DAQ_type=plug_type, parent_scan=self)
                    detector_modules.append(det_mod_tmp)
                    detector_modules[-1].ui.Detector_type_combo.setCurrentText(plug_subtype)
                    detector_modules[-1].ui.Quit_pb.setEnabled(False)
                    set_param_from_param(det_mod_tmp.settings,plug_settings)
                    QtWidgets.QApplication.processEvents()


                    try:
                        if ind_plugin==0: #should be a master type plugin
                            if plugin['status']!="Master":
                                raise Exception('error in the master/slave type for plugin {}'.format(plug_name))
                            if plug_init:
                                detector_modules[-1].ui.IniDet_pb.click()
                                QtWidgets.QApplication.processEvents()
                                if 'Mock' in plug_subtype:
                                    QThread.msleep(500)
                                else:
                                    QThread.msleep(4000)  # to let enough time for real hardware to init properly
                                QtWidgets.QApplication.processEvents()
                                master_controller=detector_modules[-1].controller
                        else:
                            if plugin['status']!="Slave":
                                raise Exception('error in the master/slave type for plugin {}'.format(plug_name))
                            if plug_init:
                                detector_modules[-1].controller=master_controller
                                detector_modules[-1].ui.IniDet_pb.click()
                                QtWidgets.QApplication.processEvents()
                                if 'Mock' in plug_subtype:
                                    QThread.msleep(500)
                                else:
                                    QThread.msleep(4000)  # to let enough time for real hardware to init properly
                                QtWidgets.QApplication.processEvents()
                    except Exception as e:
                        self.update_status(getLineInfo()+ str(e),'log')

                    detector_modules[-1].settings.child('main_settings','overshoot').show()
                    detector_modules[-1].overshoot_signal[bool].connect(self.stop_moves)

        QtWidgets.QApplication.processEvents()

        return actuator_modules,detector_modules


    pyqtSlot(bool)
    def stop_moves(self,overshoot):
        """
            Foreach module of the move module object list, stop motion.

            See Also
            --------
            stop_scan,  DAQ_Move_main.daq_move.stop_Motion
        """
        self.overshoot = overshoot
        for mod in self.actuator_modules:
            mod.stop_Motion()

    def set_default_preset(self):
        actuators = self.model_class.actuators
        actuator_names = self.model_class.actuators_name

        detectors_type = self.model_class.detectors_type
        detectors = self.model_class.detectors
        detectors_name = self.model_class.detectors_name

        detector_modules = []
        for ind_det, det in enumerate(detectors):

            detector_modules.append(DAQ_Viewer(area, title=detectors_name[ind_det], DAQ_type=detectors_type[ind_det]))
            #self.detector_modules[-1].ui.IniDet_pb.click()
            QtWidgets.QApplication.processEvents()
            detector_modules[-1].ui.Detector_type_combo.setCurrentText(detectors[ind_det])
            detector_modules[-1].ui.Quit_pb.setEnabled(False)


        self.dock_area.addDock(self.dock_output, 'bottom')
        self.dock_area.moveDock(self.dock_input, 'bottom', self.dock_output)
        self.dock_area.addDock(self.dock_pid, 'left')

        dock_moves = []
        actuator_modules = []
        for ind_act, act in enumerate(actuators):
            form = QtWidgets.QWidget()
            dock_moves.append(Dock(actuator_names[ind_act]))
            area.addDock(dock_moves[-1], 'bottom', self.dock_pid)
            dock_moves[-1].addWidget(form)
            actuator_modules.append(DAQ_Move(form))
            QtWidgets.QApplication.processEvents()
            actuator_modules[-1].ui.Stage_type_combo.setCurrentText(actuators[ind_act])
            actuator_modules[-1].ui.Quit_pb.setEnabled(False)
            #self.actuator_modules[-1].ui.IniStage_pb.click()
            #QThread.msleep(1000)
            QtWidgets.QApplication.processEvents()

        return actuator_modules, detector_modules

    def ini_model(self):
        try:
            model_name = self.settings.child('models', 'model_class').value()
            model = importlib.import_module('.' +model_name, self.model_mod.__name__+'.models')
            self.model_class = getattr(model, model_name)(self)


            #try to get corresponding managers file
            filename = os.path.join(get_set_pid_path(), model_name + '.xml')
            if os.path.isfile(filename):
                self.actuator_modules,  self.detector_modules = self.set_file_preset(model_name)
            else:
                self.actuator_modules, self.detector_modules = self.set_default_preset()

            # # connecting to logger
            # for mov in self.actuator_modules:
            #     mov.log_signal[str].connect(self.add_log)
            # for det in self.detector_modules:
            #     det.log_signal[str].connect(self.add_log)
            # self.log_signal[str].connect(self.add_log)

            self.model_class.ini_model()

            self.enable_controls_pid(True)
            self.model_led.set_as_true()
            self.ini_model_action.setEnabled(False)



        except Exception as e:
            self.update_status(getLineInfo() + str(e), log_type='log')

    def quit_fun(self):
        """
        """
        try:
            try:
                self.PIDThread.exit()
            except Exception as e:
                print(e)

            for module in self.actuator_modules:
                try:
                    module.quit_fun()
                    QtWidgets.QApplication.processEvents()
                    QThread.msleep(1000)
                    QtWidgets.QApplication.processEvents()
                except Exception as e:
                    print(e)

            for module in self.detector_modules:
                try:
                    module.stop_all()
                    QtWidgets.QApplication.processEvents()
                    module.quit_fun()
                    QtWidgets.QApplication.processEvents()
                    QThread.msleep(1000)
                    QtWidgets.QApplication.processEvents()
                except Exception as e:
                    print(e)

            areas=self.dock_area.tempAreas[:]
            for area in areas:
                area.win.close()
                QtWidgets.QApplication.processEvents()
                QThread.msleep(1000)
                QtWidgets.QApplication.processEvents()

            self.dock_area.parent().close()

        except Exception as e:
            print(e)


    def parameter_tree_changed(self,param,changes):
        """
            Foreach value changed, update :
                * Viewer in case of **DAQ_type** parameter name
                * visibility of button in case of **show_averaging** parameter name
                * visibility of naverage in case of **live_averaging** parameter name
                * scale of axis **else** (in 2D pymodaq type)

            Once done emit the update settings signal to link the commit.

            =============== =================================== ================================================================
            **Parameters**    **Type**                           **Description**
            *param*           instance of ppyqtgraph parameter   the parameter to be checked
            *changes*         tuple list                         Contain the (param,changes,info) list listing the changes made
            =============== =================================== ================================================================

            See Also
            --------
            change_viewer, daq_utils.custom_parameter_tree.iter_children
        """

        for param, change, data in changes:
            path = self.settings.childPath(param)
            if change == 'childAdded':
                pass

            elif change == 'value':
                if param.name() == 'model_class':
                    self.get_set_model_params(param.value())

                elif param.name() == 'module_settings':
                    if param.value():
                        self.settings.sigTreeStateChanged.disconnect( self.parameter_tree_changed)
                        param.setValue(False)
                        self.settings.sigTreeStateChanged.connect( self.parameter_tree_changed)
                        self.preset_manager.set_PID_preset(self.settings.child('models','model_class').value())

                elif param.name() == 'refresh_plot_time' or param.name() == 'timeout':
                    self.command_pid.emit(ThreadCommand('update_timer', [param.name(),param.value()]))

                elif param.name() == 'set_point':
                    if self.pid_led.state:
                        self.command_pid.emit(ThreadCommand('update_options', dict(setpoint=param.value())))
                    else:
                        output = self.model_class.convert_output(param.value(),0, stab=False)
                        for ind_act, act in enumerate(self.actuator_modules):
                            act.move_Abs(output[ind_act])



                elif param.name() == 'sample_time':
                    self.command_pid.emit(ThreadCommand('update_options', dict(sample_time=param.value())))

                elif param.name() in custom_tree.iter_children(self.settings.child('main_settings', 'pid_controls', 'output_limits'), []):
                    output_limits = [None, None]
                    if self.settings.child('main_settings', 'pid_controls', 'output_limits', 'output_limit_min_enabled').value():
                        output_limits[0] = self.settings.child('main_settings', 'pid_controls', 'output_limits', 'output_limit_min').value()
                    if self.settings.child('main_settings', 'pid_controls', 'output_limits', 'output_limit_max_enabled').value():
                        output_limits[1] = self.settings.child('main_settings', 'pid_controls', 'output_limits', 'output_limit_max').value()

                    self.command_pid.emit(ThreadCommand('update_options', dict(output_limits=output_limits)))

                elif param.name() in custom_tree.iter_children(self.settings.child('main_settings', 'pid_controls', 'filter'), []):
                    self.command_pid.emit(ThreadCommand('update_filter',
                                    [dict(enable=self.settings.child('main_settings', 'pid_controls', 'filter', 'filter_enable').value(),
                                         value=self.settings.child('main_settings', 'pid_controls', 'filter', 'filter_step').value())]))

                elif param.name() in custom_tree.iter_children(self.settings.child('main_settings', 'pid_controls', 'pid_constants'), []):
                    Kp = self.settings.child('main_settings', 'pid_controls', 'pid_constants', 'kp').value()
                    Ki = self.settings.child('main_settings', 'pid_controls', 'pid_constants', 'ki').value()
                    Kd = self.settings.child('main_settings', 'pid_controls', 'pid_constants', 'kd').value()
                    self.command_pid.emit(ThreadCommand('update_options', dict(tunings= (Kp, Ki, Kd))))

                elif param.name() in custom_tree.iter_children(self.settings.child('models', 'model_params'),[]):
                    self.model_class.update_settings(param)

                elif param.name() == 'detector_modules':
                    self.model_class.update_detector_names()

            elif change == 'parent':
                pass

    @pyqtSlot(list)
    def thread_status(self,status): # general function to get datas/infos from all threads back to the main
        """
            | General function to get datas/infos from all threads back to the main.
            |

            Switch the status with :
                * *"Update status"* : Update the status bar with the status attribute txt message

        """
        if status[0]=="Update_Status":
            self.update_status(status[1],log_type=status[2])
예제 #2
0
class Spectrometer(QObject):
    """
    Defines a Spectrometer object, unified interface for many spectrometers

    Parameters that could be set in the selected detector plugin (should be defined there):
    'laser_wl' : value of the configured laser (could eventually be changed, case of Xplora, Labram...)
    'spectro_center_freq': value of the configured grating center wavelength (could eventually be changed, case of Shamrock, Xplora...)


    """
    #custom signal that will be fired sometimes. Could be connected to an external object method or an internal method
    log_signal = Signal(str)

    #list of dicts enabling the settings tree on the user interface
    params = [
        {
            'title':
            'Configuration settings:',
            'name':
            'config_settings',
            'type':
            'group',
            'children': [
                {
                    'title': 'Laser wavelength (nm):',
                    'name': 'laser_wl',
                    'type': 'float',
                    'value': 515.
                },
                {
                    'title': 'Laser wavelength (nm):',
                    'name': 'laser_wl_list',
                    'type': 'list',
                    'limits': ['']
                },
                {
                    'title': 'Current Detector:',
                    'name': 'curr_det',
                    'type': 'str',
                    'value': ''
                },
                {
                    'title': 'Show detector:',
                    'name': 'show_det',
                    'type': 'bool',
                    'value': False
                },
            ],
        },
        {
            'title':
            'Calibration settings:',
            'name':
            'calib_settings',
            'type':
            'group',
            'children': [
                {
                    'title': 'Use calibration:',
                    'name': 'use_calib',
                    'type': 'bool',
                    'value': False
                },
                {
                    'title': 'Save calibration',
                    'name': 'save_calib',
                    'type': 'bool_push',
                    'value': False
                },
                {
                    'title': 'Load calibration',
                    'name': 'load_calib',
                    'type': 'bool_push',
                    'value': False
                },
                {
                    'title':
                    'Calibration coeffs:',
                    'name':
                    'calib_coeffs',
                    'type':
                    'group',
                    'children': [
                        {
                            'title': 'Center wavelength (nm):',
                            'name': 'center_calib',
                            'type': 'float',
                            'value': 515.
                        },
                        {
                            'title': 'Slope (nm/pxl):',
                            'name': 'slope_calib',
                            'type': 'float',
                            'value': 1.
                        },
                        {
                            'title': 'Second order :',
                            'name': 'second_calib',
                            'type': 'float',
                            'value': 0
                        },
                        {
                            'title': 'third:',
                            'name': 'third_calib',
                            'type': 'float',
                            'value': 0
                        },
                    ]
                },
                {
                    'title': 'Perform calibration:',
                    'name': 'do_calib',
                    'type': 'bool',
                    'value': False
                },
            ]
        },
        {
            'title':
            'Acquisition settings:',
            'name':
            'acq_settings',
            'type':
            'group',
            'children': [
                {
                    'title': 'Spectro. Center:',
                    'name': 'spectro_center_freq',
                    'type': 'float',
                    'value': 800,
                },
                {
                    'title': 'Spectro. Center:',
                    'name': 'spectro_center_freq_txt',
                    'type': 'str',
                    'value': '????',
                    'readonly': True
                },
                {
                    'title': 'Units:',
                    'name': 'units',
                    'type': 'list',
                    'value': 'nm',
                    'limits': ['nm', 'cm-1', 'eV']
                },
                {
                    'title': 'Exposure (ms):',
                    'name': 'exposure_ms',
                    'type': 'float',
                    'value': 100,
                },
            ]
        },
    ]

    def __init__(self, parent):
        QLocale.setDefault(QLocale(QLocale.English, QLocale.UnitedStates))
        super().__init__()
        if not isinstance(parent, DockArea):
            raise Exception('no valid parent container, expected a DockArea')

        self.wait_time = 2000  #ms
        self.offline = True
        self.dockarea = parent
        self.mainwindow = parent.parent()
        self.spectro_widget = QtWidgets.QWidget()
        self.data_dict = None
        """
        List of the possible plugins that could be used with Spectrometer module
        type : dimensionality of the detector
        name: name of the plugin
        calib = True means there is a builtin calibration of the frequency axis
        movable : tells if the dispersion can be set (for instance by moving a grating)
        unit: valid only if calib is True. Unit of the calibration axis (x_axis of the detector), most often in
              nanometers. Possible values are 'nm', 'radfs' (rad/femtosecond), 'eV'
        laser: if False,  laser cannot be changed by the program, do it manually
        laser_list: if laser is True, laser_list gives a list of selectable lasers
        
        """

        self.current_det = None  # will be after initialization

        self.laser_set_manual = True

        #init the object parameters
        self.detector = None
        self.save_file_pathname = None
        self._spectro_wl = 550  # center wavelngth of the spectrum
        self.viewer_freq_axis = utils.Axis(data=None,
                                           label='Photon energy',
                                           units='')
        self.raw_data = []

        #init the user interface
        self.dashboard = self.set_dashboard()
        self.dashboard.preset_loaded_signal.connect(
            lambda: self.show_detector(False))
        self.dashboard.preset_loaded_signal.connect(self.set_detector)
        self.dashboard.preset_loaded_signal.connect(self.initialized)
        self.set_GUI()
        self.dashboard.new_preset_created.connect(
            lambda: self.create_menu(self.menubar))

        self.show_detector(False)
        self.dockarea.setEnabled(False)

    def set_dashboard(self):
        params = [
            {
                'title':
                'Spectro Settings:',
                'name':
                'spectro_settings',
                'type':
                'group',
                'children': [
                    {
                        'title':
                        'Is calibrated?',
                        'name':
                        'iscalibrated',
                        'type':
                        'bool',
                        'value':
                        False,
                        'tooltip':
                        'Whether the selected plugin has internal frequency calibration or not.'
                    },
                    {
                        'title':
                        'Movable?',
                        'name':
                        'ismovable',
                        'type':
                        'bool',
                        'value':
                        False,
                        'tooltip':
                        'Whether the selected plugin has a functionality to change its central frequency: as a movable grating'
                        ' for instance.'
                    },
                    {
                        'title':
                        'Laser selectable?',
                        'name':
                        'laser_selectable',
                        'type':
                        'bool',
                        'value':
                        False,
                        'tooltip':
                        'Whether the selected plugin has a functionality to change its excitation ray'
                    },
                    {
                        'title': 'Laser ray:',
                        'name': 'laser_ray',
                        'type': 'list',
                        'value': '',
                        'show_pb': True,
                        'tooltip':
                        'List of settable laser rays (not manual ones)'
                    },
                ]
            },
        ]
        dashboard = DashBoard(self.dockarea.addTempArea())
        dashboard.set_preset_path(spectro_path)
        options = [
            dict(path='saving_options', options_dict=dict(visible=False)),
            dict(path='use_pid', options_dict=dict(visible=False)),
            dict(path='Moves', options_dict=dict(visible=False))
        ]
        dashboard.set_extra_preset_params(params, options)

        dashboard.dockarea.window().setVisible(False)
        return dashboard

    def set_GUI(self):
        ###########################################
        ###########################################
        #init the docks containing the main widgets

        #######################################################################################################################
        #create a dock containing a viewer object, displaying the data for the spectrometer
        self.dock_viewer = Dock('Viewer dock', size=(350, 350))
        self.dockarea.addDock(self.dock_viewer, 'left')
        target_widget = QtWidgets.QWidget()
        self.viewer = Viewer1D(target_widget)
        self.dock_viewer.addWidget(target_widget)

        ################################################################
        #create a logger dock where to store info senf from the programm
        self.dock_logger = Dock("Logger")
        self.logger_list = QtWidgets.QListWidget()
        self.logger_list.setMinimumWidth(300)
        self.dock_logger.addWidget(self.logger_list)
        self.dockarea.addDock(self.dock_logger, 'right')
        self.log_signal[str].connect(self.add_log)

        ############################################
        # creating a menubar
        self.menubar = self.mainwindow.menuBar()
        self.create_menu(self.menubar)

        #creating a toolbar
        self.toolbar = QtWidgets.QToolBar()
        self.create_toolbar()
        self.mainwindow.addToolBar(self.toolbar)

        #creating a status bar
        self.statusbar = QtWidgets.QStatusBar()
        self.statusbar.setMaximumHeight(25)

        self.status_laser = QtWidgets.QLabel('????')
        self.status_laser.setAlignment(Qt.AlignCenter)
        #self.status_laser.setButtonSymbols(QtWidgets.QAbstractSpinBox.NoButtons)
        #self.status_laser.setReadOnly(True)
        self.status_laser.setMaximumWidth(80)
        self.status_laser.setMinimumWidth(80)
        self.status_laser.setToolTip('Current laser wavelength')
        self.status_laser.setStyleSheet("background-color: red")

        self.status_center = QtWidgets.QLabel('????')
        self.status_center.setAlignment(Qt.AlignCenter)
        #self.status_center.setReadOnly(True)
        #self.status_center.setButtonSymbols(QtWidgets.QAbstractSpinBox.NoButtons)
        self.status_center.setMaximumWidth(80)
        self.status_center.setMinimumWidth(80)
        self.status_center.setToolTip(
            'center frequency of the spectrum, either in nm or cm-1')
        self.status_center.setStyleSheet("background-color: red")

        self.status_init = QLED()
        self.status_init.setToolTip('Initialization state of the detector')
        self.status_init.set_as_false()
        self.status_init.clickable = False

        self.statusbar.addPermanentWidget(self.status_laser)
        self.statusbar.addPermanentWidget(self.status_center)
        self.statusbar.addPermanentWidget(self.status_init)
        self.dockarea.window().setStatusBar(self.statusbar)

        #############################################
        self.settings = Parameter.create(name='settings',
                                         type='group',
                                         children=self.params)
        self.settings.sigTreeStateChanged.connect(self.parameter_tree_changed)

        dock_config_settings = Dock('Configuration', size=(300, 350))
        self.dockarea.addDock(dock_config_settings, 'above', self.dock_logger)
        # create main parameter tree
        self.config_settings_tree = ParameterTree()
        dock_config_settings.addWidget(self.config_settings_tree, 10)
        self.config_settings_tree.setMinimumWidth(300)
        self.config_settings_tree.setParameters(self.settings.child(
            ('config_settings')),
                                                showTop=False)
        #any change to the tree on the user interface will call the parameter_tree_changed method where all actions will be applied

        dock_calib_settings = Dock('Calibration', size=(300, 350))
        self.dockarea.addDock(dock_calib_settings, 'above', self.dock_logger)
        # create main parameter tree
        self.calib_settings_tree = ParameterTree()
        dock_calib_settings.addWidget(self.calib_settings_tree, 10)
        self.calib_settings_tree.setMinimumWidth(300)
        self.calib_settings_tree.setParameters(self.settings.child(
            ('calib_settings')),
                                               showTop=False)
        #any change to the tree on the user interface will call the parameter_tree_changed method where all actions will be applied

        #this one for the custom application settings
        dock_acq_settings = Dock('Acquisition', size=(300, 350))
        self.dockarea.addDock(dock_acq_settings, 'above', dock_config_settings)
        # create main parameter tree
        self.acq_settings_tree = ParameterTree()
        dock_acq_settings.addWidget(self.acq_settings_tree, 10)
        self.acq_settings_tree.setMinimumWidth(300)
        self.acq_settings_tree.setParameters(self.settings.child(
            ('acq_settings')),
                                             showTop=False)

    @Slot(ThreadCommand)
    def cmd_from_det(self, status):
        try:
            if status.command == 'spectro_wl':
                self.status_center.setStyleSheet("background-color: green")
                self.spectro_wl_is(status.attributes[0])

            elif status.command == 'laser_wl':
                #self.laser_set_manual = False
                self.settings.child('config_settings',
                                    'laser_wl_list').setValue(
                                        status.attributes[0])
                self.status_laser.setText('{:}nm'.format(status.attributes[0]))
                self.status_laser.setStyleSheet("background-color: green")
                self.update_center_frequency(self.spectro_wl)

            elif status.command == 'exposure_ms':
                self.settings.child('acq_settings', 'exposure_ms').setValue(
                    status.attributes[0])

            elif status.command == "x_axis":
                x_axis = status.attributes[0]
                if np.any(x_axis['data'] != self.viewer_freq_axis['data']
                          ) and self.current_det['calib']:
                    self.viewer_freq_axis.update(x_axis)
                    self.update_axis()

        except Exception as e:
            logger.exception(str(e))

    def update_status(self, txt, wait_time=1000, log_type=None):
        """

        """
        self.statusbar.showMessage(txt, wait_time)
        if log_type is not None:
            self.log_signal.emit(txt)

    def set_detector(self):

        self.detector = self.dashboard.detector_modules[0]
        self.settings.child('config_settings', 'curr_det').setValue(
            f"{self.detector.settings.child('main_settings','DAQ_type').value()} / "
            f"{self.detector.settings.child('main_settings','detector_type').value()} / {self.detector.title}"
        )
        self.detector.custom_sig[ThreadCommand].connect(self.cmd_from_det)
        self.current_det = \
            dict(laser=self.dashboard.preset_manager.preset_params.child('spectro_settings', 'laser_selectable').value(),
                 laser_list=self.dashboard.preset_manager.preset_params.child('spectro_settings', 'laser_ray').opts['limits'],
                 movable=self.dashboard.preset_manager.preset_params.child('spectro_settings', 'ismovable').value(),
                 calib=self.dashboard.preset_manager.preset_params.child('spectro_settings', 'iscalibrated').value(),
                 )

        self.detector.grab_done_signal.connect(self.show_data)

        self.settings.sigTreeStateChanged.disconnect(
            self.parameter_tree_changed)
        if self.current_det['laser']:
            self.settings.child('config_settings', 'laser_wl_list').show()
            self.settings.child('config_settings', 'laser_wl').hide()
            self.settings.child(
                'config_settings',
                'laser_wl_list').setOpts(limits=self.current_det['laser_list'])
        else:
            self.settings.child('config_settings', 'laser_wl').show()
            self.settings.child('config_settings', 'laser_wl_list').hide()
        self.settings.sigTreeStateChanged.connect(self.parameter_tree_changed)

        #apply current detector particularities
        #self.settings.child('acq_settings', 'spectro_center_freq').setOpts(readonly=not self.current_det['movable'])
        self.get_spectro_wl()
        QtWidgets.QApplication.processEvents()

        self.get_laser_wl()
        QtWidgets.QApplication.processEvents()

        self.get_exposure_ms()
        QtWidgets.QApplication.processEvents()

    def get_exposure_ms(self):
        self.detector.command_detector.emit(ThreadCommand('get_exposure_ms'))

    def set_exposure_ms(self, data):
        self.detector.command_detector.emit(
            ThreadCommand('set_exposure_ms', [data]))

    @Slot(bool)
    def initialized(self, state, offline=False):
        self.offline = offline
        self.grab_action.setEnabled(state)
        self.snap_action.setEnabled(state)
        if state or offline:
            self.status_init.set_as_true()
            self.dockarea.setEnabled(True)
        else:
            self.status_init.set_as_false()

    def update_center_frequency(self, spectro_wl):
        self._spectro_wl = spectro_wl
        if self.settings.child('acq_settings', 'units').value() == 'nm':
            self.settings.child('acq_settings',
                                'spectro_center_freq').setValue(spectro_wl)
        elif self.settings.child('acq_settings', 'units').value() == 'cm-1':
            self.settings.child('acq_settings',
                                'spectro_center_freq').setValue(
                                    Enm2cmrel(
                                        spectro_wl,
                                        self.settings.child(
                                            'config_settings',
                                            'laser_wl').value()))
        elif self.settings.child('acq_settings', 'units').value() == 'eV':
            self.settings.child('acq_settings',
                                'spectro_center_freq').setValue(
                                    nm2eV(spectro_wl))

        self.set_status_center(
            self.settings.child('acq_settings', 'spectro_center_freq').value(),
            self.settings.child('acq_settings', 'units').value())

    def set_status_center(self, val, unit, precision=3):
        self.status_center.setText(f'{val:.{precision}f} {unit}')

    def spectro_wl_is(self, spectro_wl):
        """
        this slot receives a signal from the detector telling it what's the current spectro_wl
        Parameters
        ----------
        spectro_wl
        """
        self._spectro_wl = spectro_wl
        self.update_center_frequency(spectro_wl)

    def set_spectro_wl(self, spectro_wl):
        try:
            if self.current_det['movable']:
                self.detector.command_detector.emit(
                    ThreadCommand('set_spectro_wl', [spectro_wl]))
        except Exception as e:
            logger.exception(str(e))

    def get_spectro_wl(self):
        if self.current_det['calib']:
            self.settings.child('acq_settings', 'spectro_center_freq').show()
            self.settings.child('acq_settings',
                                'spectro_center_freq_txt').hide()
            self.detector.command_detector.emit(
                ThreadCommand('get_spectro_wl'))
            self.detector.command_detector.emit(ThreadCommand('get_axis'))
        else:
            self.settings.child('acq_settings', 'spectro_center_freq').hide()
            self.settings.child('acq_settings',
                                'spectro_center_freq_txt').show()
            self.viewer_freq_axis['units'] = 'Pxls'

    def get_laser_wl(self):
        if self.current_det['laser']:
            self.detector.command_detector.emit(ThreadCommand('get_laser_wl'))
        else:
            self.settings.child('config_settings', 'laser_wl').setValue(0)

    @property
    def spectro_wl(self):
        # try to get the param value from detector (if it has been added in the plugin)
        return self._spectro_wl

    @spectro_wl.setter
    def spectro_wl(self, spec_wl):
        # try to get the param value from detector (if it has been added in the plugin)
        self.set_spectro_wl(spec_wl)

    def show_detector(self, show=True):
        self.dashboard.mainwindow.setVisible(show)
        for area in self.dashboard.dockarea.tempAreas:
            area.window().setVisible(show)

    def parameter_tree_changed(self, param, changes):
        for param, change, data in changes:
            path = self.settings.childPath(param)
            if path is not None:
                childName = '.'.join(path)
            else:
                childName = param.name()
            if change == 'childAdded':
                pass

            elif change == 'value':
                if param.name() == 'show_det':
                    self.show_detector(data)

                elif param.name() == 'spectro_center_freq':
                    unit = self.settings.child('acq_settings', 'units').value()
                    if unit == 'nm':
                        center_wavelength = data
                    elif unit == 'cm-1':
                        center_wavelength = Ecmrel2Enm(
                            data,
                            self.settings.child('config_settings',
                                                'laser_wl').value())
                    elif unit == 'eV':
                        center_wavelength = eV2nm(data)

                    if int(self.spectro_wl * 100) != int(
                            100 * center_wavelength):  #comprison at 1e-2
                        self.spectro_wl = center_wavelength

                    self.update_axis()

                elif param.name() == 'units':
                    if self.settings.child(
                            'acq_settings',
                            'spectro_center_freq').value() > 0.000000001:
                        if data == 'nm':
                            self.settings.child(
                                'acq_settings',
                                'spectro_center_freq').setValue(
                                    self._spectro_wl)
                        elif data == 'cm-1':
                            self.settings.child(
                                'acq_settings',
                                'spectro_center_freq').setValue(
                                    Enm2cmrel(
                                        self._spectro_wl,
                                        self.settings.child(
                                            'config_settings',
                                            'laser_wl').value()))
                        elif data == 'eV':
                            self.settings.child(
                                'acq_settings',
                                'spectro_center_freq').setValue(
                                    nm2eV(self._spectro_wl))

                        self.set_status_center(
                            self.settings.child('acq_settings',
                                                'spectro_center_freq').value(),
                            self.settings.child('acq_settings',
                                                'units').value())

                elif param.name() == 'laser_wl_list':
                    if data is not None:
                        self.move_laser_wavelength(data)

                elif param.name() == 'laser_wl':
                    if data is not None:
                        self.move_laser_wavelength(data)
                        if int(data) == 0:
                            self.settings.child('acq_settings',
                                                'units').setValue('nm')
                            self.settings.child('acq_settings',
                                                'units').setOpts(readonly=True)
                        else:
                            self.settings.child(
                                'acq_settings',
                                'units').setOpts(readonly=False)
                        if data != 0:
                            self.set_manual_laser_wl(data)

                elif param.name() == 'exposure_ms':
                    self.set_exposure_ms(data)

                elif param.name() == 'do_calib':
                    if len(self.raw_data) != 0:
                        if data:
                            self.calib_dock = Dock('Calibration module')
                            self.dockarea.addDock(self.calib_dock)
                            self.calibration = Calibration(self.dockarea)
                            self.calib_dock.addWidget(self.calibration)

                            self.calibration.coeffs_calib.connect(
                                self.update_calibration)
                        else:
                            self.calib_dock.close()

                elif param.name() == 'save_calib':
                    filename = select_file(start_path=self.save_file_pathname,
                                           save=True,
                                           ext='xml')
                    if filename != '':
                        custom_tree.parameter_to_xml_file(
                            self.settings.child('calib_settings',
                                                'calib_coeffs'), filename)

                elif param.name() == 'load_calib':
                    filename = select_file(start_path=self.save_file_pathname,
                                           save=False,
                                           ext='xml')
                    if filename != '':
                        children = custom_tree.XML_file_to_parameter(filename)
                        self.settings.child(
                            'calib_settings', 'calib_coeffs').restoreState(
                                Parameter.create(
                                    title='Calibration coeffs:',
                                    name='calib_coeffs',
                                    type='group',
                                    children=children).saveState())



                elif param.name() in custom_tree.iter_children(self.settings.child('calib_settings', 'calib_coeffs')) \
                        or param.name() == 'use_calib':
                    if self.settings.child('calib_settings',
                                           'use_calib').value():
                        calib_coeffs = [
                            self.settings.child('calib_settings',
                                                'calib_coeffs',
                                                'third_calib').value(),
                            self.settings.child('calib_settings',
                                                'calib_coeffs',
                                                'second_calib').value(),
                            self.settings.child('calib_settings',
                                                'calib_coeffs',
                                                'slope_calib').value(),
                            self.settings.child('calib_settings',
                                                'calib_coeffs',
                                                'center_calib').value()
                        ]

                        self.update_center_frequency(
                            self.settings.child('calib_settings',
                                                'calib_coeffs',
                                                'center_calib').value())
                        self.settings.child('acq_settings',
                                            'spectro_center_freq').show()
                        self.settings.child(
                            'acq_settings',
                            'spectro_center_freq').setOpts(readonly=True)
                        self.status_center.setStyleSheet(
                            "background-color: green")
                        self.settings.child('acq_settings',
                                            'spectro_center_freq_txt').hide()
                        x_axis_pxls = np.linspace(0, self.raw_data[0].size - 1,
                                                  self.raw_data[0].size)
                        self.viewer_freq_axis['data'] = np.polyval(
                            calib_coeffs,
                            x_axis_pxls - np.max(x_axis_pxls) / 2)
                        self.update_axis()
                    else:
                        self.settings.child('acq_settings',
                                            'spectro_center_freq').hide()
                        self.settings.child('acq_settings',
                                            'spectro_center_freq_txt').show()
                        self.status_center.setStyleSheet(
                            "background-color: red")

            elif change == 'parent':
                pass

    @Slot(list)
    def update_calibration(self, coeffs):
        self.settings.child('calib_settings', 'calib_coeffs',
                            'center_calib').setValue(coeffs[0])
        self.settings.child('calib_settings', 'calib_coeffs',
                            'slope_calib').setValue(coeffs[1])
        if len(coeffs) > 2:
            self.settings.child('calib_settings', 'calib_coeffs',
                                'second_calib').setValue(coeffs[2])
        else:
            self.settings.child('calib_settings', 'calib_coeffs',
                                'second_calib').setValue(0)
        if len(coeffs) > 3:
            self.settings.child('calib_settings', 'calib_coeffs',
                                'third_calib').setValue(coeffs[3])
        else:
            self.settings.child('calib_settings', 'calib_coeffs',
                                'third_calib').setValue(0)

    def set_manual_laser_wl(self, laser_wl):
        messg = QtWidgets.QMessageBox()
        messg.setText(
            'You manually changed the laser wavelength to {:}nm!'.format(
                laser_wl))
        messg.setInformativeText("Is that correct?")
        messg.setStandardButtons(QtWidgets.QMessageBox.Yes
                                 | QtWidgets.QMessageBox.No)
        ret = messg.exec()
        if ret == QtWidgets.QMessageBox.Yes:
            self.status_laser.setText('{:}nm'.format(laser_wl))
            self.status_laser.setStyleSheet("background-color: green")
            self.settings.child('acq_settings',
                                'units').setOpts(readonly=False)

    def move_laser_wavelength(self, laser_wavelength):
        #do hardware stuff if possible (Mock, labspec...)
        try:
            if self.current_det['laser']:
                self.detector.command_detector.emit(
                    ThreadCommand('set_laser_wl', [laser_wavelength]))
        except Exception as e:
            logger.exception(str(e))

    @Slot(OrderedDict)
    def show_data(self, data):
        """
        do stuff with data from the detector if its grab_done_signal has been connected
        Parameters
        ----------
        data: (OrderedDict) #OrderedDict(name=self.title,x_axis=None,y_axis=None,z_axis=None,data0D=None,data1D=None,data2D=None)
        """
        self.data_dict = data
        if 'data1D' in data:
            self.raw_data = []
            for key in data['data1D']:
                self.raw_data.append(data['data1D'][key]['data'])
                if 'x_axis' in data['data1D'][key]:
                    x_axis = data['data1D'][key]['x_axis']
                else:
                    x_axis = utils.Axis(data=np.linspace(
                        0,
                        len(data['data1D'][key]['data']) - 1,
                        len(data['data1D'][key]['data'])),
                                        units='pxls',
                                        label='')
                if self.viewer_freq_axis['data'] is None:
                    self.viewer_freq_axis.update(x_axis)
                elif np.any(x_axis['data'] != self.viewer_freq_axis['data']
                            ) and self.current_det['calib']:
                    self.viewer_freq_axis.update(x_axis)

            self.viewer.show_data(self.raw_data)
            self.update_axis()

    def update_axis(self):
        axis = utils.Axis()
        unit = self.settings.child('acq_settings', 'units').value()
        if unit == 'nm':
            axis['data'] = self.viewer_freq_axis['data']
        elif unit == 'cm-1':
            axis['data'] = Enm2cmrel(
                self.viewer_freq_axis['data'],
                self.settings.child('config_settings', 'laser_wl').value())
        elif unit == 'eV':
            axis['data'] = nm2eV(self.viewer_freq_axis['data'])
        axis['units'] = unit
        axis['label'] = 'Photon energy'
        self.viewer.x_axis = axis

    def create_menu(self, menubar):
        """
        """
        menubar.clear()

        # %% create file menu
        file_menu = menubar.addMenu('File')
        load_action = file_menu.addAction('Load file')
        load_action.triggered.connect(self.load_file)
        save_action = file_menu.addAction('Save file')
        save_action.triggered.connect(self.save_data)
        export_action = file_menu.addAction('Export as ascii')
        export_action.triggered.connect(lambda: self.save_data(export=True))

        file_menu.addSeparator()
        file_menu.addAction('Show log file', self.show_log)
        file_menu.addSeparator()
        quit_action = file_menu.addAction('Quit')
        quit_action.triggered.connect(self.quit_function)

        settings_menu = menubar.addMenu('Settings')
        settings_menu.addAction('Show Units Converter',
                                self.show_units_converter)
        docked_menu = settings_menu.addMenu('Docked windows')
        docked_menu.addAction('Load Layout', self.load_layout_state)
        docked_menu.addAction('Save Layout', self.save_layout_state)

        self.preset_menu = menubar.addMenu(self.dashboard.preset_menu)
        self.preset_menu.menu().addSeparator()
        self.preset_menu.menu().addAction(
            'Offline Mode',
            lambda: self.initialized(state=False, offline=True))

    def load_layout_state(self, file=None):
        """
            Load and restore a layout state from the select_file obtained pathname file.

            See Also
            --------
            utils.select_file
        """
        try:
            if file is None:
                file = select_file(save=False, ext='dock')
            if file is not None:
                with open(str(file), 'rb') as f:
                    dockstate = pickle.load(f)
                    self.dockarea.restoreState(dockstate)
            file = file.name
            self.settings.child('loaded_files', 'layout_file').setValue(file)
        except Exception as e:
            logger.exception(str(e))

    def save_layout_state(self, file=None):
        """
            Save the current layout state in the select_file obtained pathname file.
            Once done dump the pickle.

            See Also
            --------
            utils.select_file
        """
        try:
            dockstate = self.dockarea.saveState()
            if 'float' in dockstate:
                dockstate['float'] = []
            if file is None:
                file = select_file(start_path=None, save=True, ext='dock')
            if file is not None:
                with open(str(file), 'wb') as f:
                    pickle.dump(dockstate, f, pickle.HIGHEST_PROTOCOL)
        except Exception as e:
            logger.exception(str(e))

    def show_log(self):
        import webbrowser
        webbrowser.open(logging.getLogger('pymodaq').handlers[0].baseFilename)

    def show_units_converter(self):
        self.units_converter = UnitsConverter()
        dock_converter = Dock('Units Converter', size=(300, 350))
        self.dockarea.addDock(dock_converter, 'bottom', self.dock_logger)
        dock_converter.addWidget(self.units_converter.parent)

    def load_file(self):
        data, fname, node_path = browse_data(ret_all=True)
        if data is not None:
            h5utils = H5BrowserUtil()
            h5utils.open_file(fname)
            data, axes, nav_axes, is_spread = h5utils.get_h5_data(node_path)
            data_node = h5utils.get_node(node_path)
            if data_node.attrs['type'] == 'data':
                if data_node.attrs['data_dimension'] == '1D':
                    data_dict = OrderedDict(data1D=dict(
                        raw=dict(data=data, x_axis=axes['x_axis'])))
                    self.show_data(data_dict)
            h5utils.close_file()

    def quit_function(self):
        #close all stuff that need to be
        if self.detector is not None:
            self.detector.quit_fun()
            QtWidgets.QApplication.processEvents()
            self.mainwindow.close()

    def create_toolbar(self):
        self.toolbar.addWidget(QtWidgets.QLabel('Acquisition:'))

        iconquit = QtGui.QIcon()
        iconquit.addPixmap(QtGui.QPixmap(":/icons/Icon_Library/close2.png"),
                           QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.quit_action = QtWidgets.QAction(iconquit, "Quit program", None)
        self.toolbar.addAction(self.quit_action)
        self.quit_action.triggered.connect(self.quit_function)

        iconload = QtGui.QIcon()
        iconload.addPixmap(QtGui.QPixmap(":/icons/Icon_Library/Open.png"),
                           QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.loadaction = QtWidgets.QAction(
            iconload, "Load target file (.h5, .png, .jpg) or data from camera",
            None)
        self.toolbar.addAction(self.loadaction)
        self.loadaction.triggered.connect(self.load_file)

        iconsave = QtGui.QIcon()
        iconsave.addPixmap(QtGui.QPixmap(":/icons/Icon_Library/SaveAs.png"),
                           QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.saveaction = QtWidgets.QAction(iconsave, "Save current data",
                                            None)
        self.toolbar.addAction(self.saveaction)
        self.saveaction.triggered.connect(self.save_data)

        iconrun = QtGui.QIcon()
        iconrun.addPixmap(QtGui.QPixmap(":/icons/Icon_Library/run2.png"),
                          QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.grab_action = QtWidgets.QAction(iconrun, 'Grab', None)
        self.grab_action.setCheckable(True)
        self.toolbar.addAction(self.grab_action)
        self.grab_action.triggered.connect(self.grab_detector)

        iconsnap = QtGui.QIcon()
        iconsnap.addPixmap(QtGui.QPixmap(":/icons/Icon_Library/snap.png"),
                           QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.snap_action = QtWidgets.QAction(iconsnap, 'Snap', None)
        self.snap_action.triggered.connect(self.snap_detector)
        self.toolbar.addAction(self.snap_action)

        self.grab_action.setEnabled(False)
        self.snap_action.setEnabled(False)

    def grab_detector(self):
        self.detector.ui.grab_pb.click()

    def snap_detector(self):
        self.detector.ui.single_pb.click()

    def save_data(self, export=False):
        try:
            if export:
                ext = 'dat'
            else:
                ext = 'h5'
            path = select_file(start_path=self.save_file_pathname,
                               save=True,
                               ext=ext)
            if not (not (path)):
                if not export:
                    h5saver = H5Saver(save_type='detector')
                    h5saver.init_file(update_h5=True,
                                      custom_naming=False,
                                      addhoc_file_path=path)

                    settings_str = b'<All_settings>' + custom_tree.parameter_to_xml_string(
                        self.settings)
                    if self.detector is not None:
                        settings_str += custom_tree.parameter_to_xml_string(
                            self.detector.settings)
                        if hasattr(self.detector.ui.viewers[0], 'roi_manager'):
                            settings_str += custom_tree.parameter_to_xml_string(
                                self.detector.ui.viewers[0].roi_manager.
                                settings)
                    settings_str += custom_tree.parameter_to_xml_string(
                        h5saver.settings)
                    settings_str += b'</All_settings>'

                    det_group = h5saver.add_det_group(h5saver.raw_group,
                                                      "Data", settings_str)
                    try:
                        self.channel_arrays = OrderedDict([])
                        data_dim = 'data1D'
                        if not h5saver.is_node_in_group(det_group, data_dim):
                            self.channel_arrays['data1D'] = OrderedDict([])
                            data_group = h5saver.add_data_group(
                                det_group, data_dim)
                            for ind_channel, data in enumerate(
                                    self.raw_data):  # list of numpy arrays
                                channel = f'CH{ind_channel:03d}'
                                channel_group = h5saver.add_CH_group(
                                    data_group, title=channel)

                                self.channel_arrays[data_dim][
                                    'parent'] = channel_group
                                self.channel_arrays[data_dim][
                                    channel] = h5saver.add_data(
                                        channel_group,
                                        dict(data=data,
                                             x_axis=self.viewer_freq_axis),
                                        scan_type='',
                                        enlargeable=False)
                        h5saver.close_file()
                    except Exception as e:
                        logger.exception(str(e))
                else:
                    data_to_save = [self.viewer_freq_axis['data']]
                    data_to_save.extend([dat for dat in self.raw_data])
                    np.savetxt(path, data_to_save, delimiter='\t')

        except Exception as e:
            logger.exception(str(e))

    @Slot(str)
    def add_log(self, txt):
        """
            Add a log to the logger list from the given text log and the current time

            ================ ========= ======================
            **Parameters**   **Type**   **Description**

             *txt*             string    the log to be added
            ================ ========= ======================

        """
        now = datetime.datetime.now()
        new_item = QtWidgets.QListWidgetItem(str(now) + ": " + txt)
        self.logger_list.addItem(new_item)
        ##to do
        ##self.save_parameters.logger_array.append(str(now)+": "+txt)

    @Slot(str)
    def emit_log(self, txt):
        """
            Emit a log-signal from the given log index

            =============== ======== =======================
            **Parameters**  **Type** **Description**

             *txt*           string   the log to be emitted
            =============== ======== =======================

        """
        self.log_signal.emit(txt)