Exemple #1
0
class TestLogic(GenericLogic):
    """ This is the Logic class for testing. """

    # connectors
    firsthardware = Connector(interface='FirstTestInterface')
    secondhardware = Connector(interface='SecondTestInterface')

    testvar = StatusVar(name='testvar', default=None)

    def __init__(self, config, **kwargs):
        super().__init__(config=config, **kwargs)
        return

    def on_activate(self):
        """	
        Initialisation performed during activation of the module.
        """
        return

    def on_deactivate(self):
        """ Deinitialisation performed during deactivation of the module.	
        """
        return

    def call_test1(self):
        return self.firsthardware().test()

    def call_test2(self):
        return self.secondhardware().test()
Exemple #2
0
class PolarisationDepLogic(GenericLogic):
    """This logic module rotates polarisation and records signal as a function of angle.

    """

    # declare connectors
    counterlogic = Connector(interface='CounterLogic')
    savelogic = Connector(interface='SaveLogic')
    motor = Connector(interface='MotorInterface')

    signal_rotation_finished = QtCore.Signal()
    signal_start_rotation = QtCore.Signal()

    def on_activate(self):
        """ Initialisation performed during activation of the module.
        """

        self._counter_logic = self.counterlogic()
        #        print("Counting device is", self._counting_device)

        self._save_logic = self.savelogic()

        self._hwpmotor = self.motor()

        # Initialise measurement parameters
        self.scan_length = 360
        self.scan_speed = 10  #not yet used

        # Connect signals
        self.signal_rotation_finished.connect(self.finish_scan,
                                              QtCore.Qt.QueuedConnection)
        self.signal_start_rotation.connect(self.rotate_polarisation,
                                           QtCore.Qt.QueuedConnection)

    def on_deactivate(self):
        """ Deinitialisation performed during deactivation of the module.
        """
        return

    def measure_polarisation_dependence(self):
        """Do a simple pol dep measurement.
        """

        # Set up measurement
        self._hwpmotor.move_abs(0)

        # configure the countergui

        self._counter_logic.start_saving()
        self.signal_start_rotation.emit()

    def rotate_polarisation(self):
        self._hwpmotor.move_rel(self.scan_length)
        self.log.info('rotation finished, saving data')
        self.signal_rotation_finished.emit()

    def finish_scan(self):
        self._counter_logic.save_data()
Exemple #3
0
class OptimizerLogic(GenericLogic):

    """Helper module to simplify running survey workflow scripts in the background.
        Works in cooperatively with Jupyter"""

    # Connect to everything this module has helper methods for
    # (intended as a local module although in some form this could be made configurable)
    aom = Connector(interface='aomlogic')
    hbt = Connector(interface='hbtlogic')
    poimanager = Connector(interface='poimanagerlogic')
    pulsedmaster = Connector(interface='pulsedmasterlogic')
    optimizer = Connector(interface='optimizerlogic')

    working_directory = ConfigOption('working_directory')

    _sigInternal = QtCore.Signal()


    def __init__(self, config, **kwargs):
        super().__init__(config=config, **kwargs)

        self._running = False   # Is a workflow running
        self._subdir = None

    def on_activate(self):
        """ Initialisation performed during activation of the module.

        @return int: error code (0:OK, -1:error)
        """
        return 0


    def on_deactivate(self):
        """ Reverse steps of activation

        @return int: error code (0:OK, -1:error)
        """
        return 0

    def run_in_background(self, code):
        # set up a namespace for the sub thread


    def wlog(self, msg):
        # log the workflow progress to a separate logfile
        out = _time_str() + ' ' + msg
        with open(self._log_file, 'a') as f:
            f.write(out + '\n')
        self.log.debug(msg)

    def set_laser_power(self, power):
        self.wlog("Setting power via AOM to {} mW".format(power))
        self.aom().set_power(power * 1e-3)
class LaserCtrlGUI(GUIBase):
    laserctrllogic = Connector(interface='LaserExtCtrlLogic')

    def __init__(self, config, **kwargs):
        super().__init__(config=config, **kwargs)

    def on_activate(self):
        self._laserctrllogic = self.laserctrllogic()
        self._lw = LaserCtrlMainWindow()

        self._lw.slider_widget.valueChanged.connect(self.change_extctrl_power)
        self._lw.slider_widget.sliderReleased.connect(self.get_power_atsource)

        self.get_power_atsource()
        self.show()

    def on_deactivate(self):
        print("F*****g off")

    @QtCore.Slot(int)
    def change_extctrl_power(self, value):
        power_percentage = value / self._lw.slider_widget.maximum()
        self._laserctrllogic.set_ext_ctrl_power(power_percentage)

    def get_power_atsource(self):
        power = self._laserctrllogic.get_power_atsource()
        self._lw.actual_pw.setText("{:.2f} mW".format(power))

    def show(self):
        self._lw.show()
        self._lw.activateWindow()
        self._lw.raise_()
Exemple #5
0
class LaserExtCtrlLogic(GenericLogic):

    laser = Connector(interface='ExtCtrlLaserInterface')
    option_placeholder = ConfigOption('option_placeholder', 0)

    def __init__(self, config, **kwargs):
        super().__init__(config=config, **kwargs)
        """
        self.log.debug('The following configuration was found.')

        # checking for the right configuration
        for key in config.keys():
            self.log.info('{0}: {1}'.format(key, config[key]))
        """
        self.threadlock = Mutex()

    def on_activate(self):
        self._laser = self.laser()

    def on_deactivate(self):
        pass

    def get_power_atsource(self):
        return self._laser.get_power()

    def set_ext_ctrl_power(self, power_percentage):
        self._laser.set_power_extctrl(power_percentage)

    def get_power_atpd(self):
        pass
class DepthIndicatorGUI(GUIBase):
    """ FIXME: Please document
    """
    # declare connectors
    hdi_logic = Connector(interface='DepthIndicatorLogic')
    sigPower = QtCore.Signal(float)
    sigMeasure = QtCore.Signal(bool)

    def __init__(self, config, **kwargs):
        super().__init__(config=config, **kwargs)

    def on_activate(self):
        """ Definition and initialisation of the GUI plus staring the measurement.
        """
        self._hdi_logic = self.hdi_logic()

        #####################
        # Configuring the dock widgets
        # Use the inherited class 'CounterMainWindow' to create the GUI window
        self._mw = DepthWindow()
        self._mw.measuredepthButton.clicked.connect(self.measureDepth)
        self.sigMeasure.connect(self._hdi_logic.measure_depth)
        self._hdi_logic.sigUpdate.connect(self.updateGui)
        self._mw.maximumDepth.setText('Maximum depth : {} mm'.format(
            self._hdi_logic.maximum_depth()))

    def on_deactivate(self):
        """ Deactivate the module properly.
        """
        self._mw.close()

    def show(self):
        """Make window visible and put it above all other windows.
        """
        QtWidgets.QMainWindow.show(self._mw)
        self._mw.activateWindow()
        self._mw.raise_()

    @QtCore.Slot(bool)
    def measureDepth(self, on):
        """ Disable laser shutter button and give logic signal.
            Logic reaction to that signal will enable the button again.
        """
        self._mw.measuredepthButton.setEnabled(False)
        self.sigMeasure.emit(on)

    @QtCore.Slot()
    def updateGui(self):
        """ Update labels, the plot and button states with new data. """
        if isinstance(self._hdi_logic.helium_depth, float):
            self._mw.heliumDepth.setText('{0:6.1f} mm'.format(
                self._hdi_logic.helium_depth))
        else:
            self._mw.heliumDepth.setText('{}'.format(
                self._hdi_logic.helium_depth))
        self._mw.measuredepthButton.setEnabled(True)
Exemple #7
0
class LocalFlipMirrorLogic(GenericLogic):
    servomotor1 = Connector(interface='MotorInterface')

    sigOntoOffProcessing = QtCore.Signal()
    sigOfftoOnProcessing = QtCore.Signal()
    sigMoving = QtCore.Signal(float)

    def __init__(self, config, **kwargs):
        super().__init__(config=config, **kwargs)
        self.threadlock = Mutex()

    def on_activate(self):
        self._motor = self.servomotor1()

        self._current_angle = self._motor.get_pos()
        self._step_size = self._motor._step_size
        self._slow_down_time = self._motor._slow_down_time
        self._step_time = self._slow_down_time / 90 * self._step_size

        self.sigMoving.connect(self.move_to_pos)
        self.move_to_pos(0)

    def on_deactivate(self):
        self._motor.abort()
        return 0

    def move_to_pos(self, new_angle):

        if self._current_angle < new_angle:
            if self._current_angle + self._step_size > new_angle:
                return
            else:
                self._motor.move_abs(
                    np.round(self._current_angle + self._step_size, 2))
                time.sleep(self._step_time)
                self._current_angle = self._motor.get_pos()
                self.sigOfftoOnProcessing.emit()
                self.sigMoving.emit(new_angle)
        else:
            if self._current_angle - self._step_size < new_angle:
                return
            else:
                self._motor.move_abs(
                    np.round(self._current_angle - self._step_size, 2))
                time.sleep(self._step_time)
                self._current_angle = self._motor.get_pos()
                self.sigOntoOffProcessing.emit()
                self.sigMoving.emit(new_angle)

    def detach(self):
        self._motor.abort()

    def attach(self):
        self._motor._attach()
        self._current_angle = self._motor.get_pos()
        self.move_to_pos(0)
Exemple #8
0
class MimicGui(GUIBase):

    xseries = Connector(interface='NationalInstrumentsXSeries')

    channel = ConfigOption('channel', '/Dev1/PCIe-6363', missing='warn')

    def __init__(self, config, **kwargs):
        super().__init__(config=config, **kwargs)

    def on_activate(self):
        """ Definition and initialisation of the GUI.
        """

        self.nicard = self.xseries()

        # Create main window instance
        self._mw = MimicMainWindow()

        this_dir = os.path.dirname(__file__)

        self.fibre_sw_on_pixmap = QtGui.QPixmap(
            os.path.join(this_dir, "fibre_switch_on.png"))

        self.fibre_sw_off_pixmap = QtGui.QPixmap(
            os.path.join(this_dir, "fibre_switch_off.png"))

        # Initialise module with fibre switch off
        self.fibre_switch_off()

        ###################
        # Connect UI events
        ###################

        self._mw.fibre_sw_off_btn.clicked.connect(self.fibre_switch_off)
        self._mw.fibre_sw_on_btn.clicked.connect(self.fibre_switch_on)

    def show(self):
        """Make window visible and put it above all other windows.
        """
        QtWidgets.QMainWindow.show(self._mw)
        self._mw.activateWindow()
        self._mw.raise_()

    def on_deactivate(self):
        # FIXME: !
        """ Deactivate the module
        """
        self._mw.close()

    def fibre_switch_off(self):
        self._mw.fibre_sw_mimic.setPixmap(self.fibre_sw_off_pixmap)
        self.nicard.digital_channel_switch(self.channel, False)

    def fibre_switch_on(self):
        self._mw.fibre_sw_mimic.setPixmap(self.fibre_sw_on_pixmap)
        self.nicard.digital_channel_switch(self.channel, True)
Exemple #9
0
class SimpleDataLogic(GenericLogic):
    """ Logic module agreggating multiple hardware switches.
    """

    simpledata = Connector(interface='SimpleDataInterface')

    sigRepeat = QtCore.Signal()

    def on_activate(self):
        """ Prepare logic module for work.
        """
        self._data_logic = self.simpledata()
        self.stopRequest = False
        self.bufferLength = 10000
        self.sigRepeat.connect(self.measureLoop, QtCore.Qt.QueuedConnection)

    def on_deactivate(self):
        """ Deactivate modeule.
        """
        self.stopMeasure()

    def startMeasure(self):
        """ Start measurement: zero the buffer and call loop function."""
        self.window_len = 50
        self.buf = np.zeros(
            (self.bufferLength, self._data_logic.getChannels()))
        self.smooth = np.zeros((self.bufferLength + self.window_len - 1,
                                self._data_logic.getChannels()))
        self.module_state.lock()
        self.sigRepeat.emit()

    def stopMeasure(self):
        """ Ask the measurement loop to stop. """
        self.stopRequest = True

    def measureLoop(self):
        """ Measure 10 values, add them to buffer and remove the 10 oldest values.
        """
        if self.stopRequest:
            self.stopRequest = False
            self.module_state.unlock()
            return

        data = np.zeros((100, self._data_logic.getChannels()))
        #data[:, 0] = np.array([self._data_logic.getData() for i in range(100)])
        data = np.array([self._data_logic.getData() for i in range(100)])

        self.buf = np.roll(self.buf, -100, axis=0)
        self.buf[-101:-1] = data
        w = np.hanning(self.window_len)
        s = np.r_[self.buf[self.window_len - 1:0:-1], self.buf,
                  self.buf[-1:-self.window_len:-1]]
        for channel in range(self._data_logic.getChannels()):
            convolved = np.convolve(w / w.sum(), s[:, channel], mode='valid')
            self.smooth[:, channel] = convolved
        self.sigRepeat.emit()
Exemple #10
0
class DAQaoLogic(GenericLogic):
    """ Controls the DAQ analog output and allows to set a digital output line for triggering
    """

    # declare connectors
    daq = Connector(interface='Base')

    # signals
    sigRinsingDurationFinished = QtCore.Signal()

    def __init__(self, config, **kwargs):
        super().__init__(config=config, **kwargs)
        # activate if necessary
        # self.threadlock = Mutex()

        self.threadpool = QtCore.QThreadPool()

    def on_activate(self):
        """ Initialisation performed during activation of the module.
        """
        self._daq = self.daq()

    def on_deactivate(self):
        """ Perform required deactivation. """
        pass

    def read_piezo(self):
        """
        """
        return self._daq.read_piezo()

    def move_piezo(self, pos, autostart=True, timeout=10):
        """
        """
        self._daq.move_piezo(pos, autostart=True, timeout=10)

    def write_to_do_channel(self, num_samp, digital_write, channel):
        """ use the digital output as trigger """
        self._daq.write_to_do_channel(num_samp, digital_write, channel)

    def read_do_channel(self, num_samp, channel):
        return self._daq.read_do_channel(num_samp, channel)

    def write_to_pump_ao_channel(self, voltage, autostart=True, timeout=10):
        self._daq.write_to_pump_ao_channel(voltage, autostart, timeout)

    def start_rinsing(self, duration):
        self.write_to_pump_ao_channel(-3.0)
        worker = Worker(duration)
        worker.signals.sigFinished.connect(self.stop_rinsing)
        self.threadpool.start(worker)

    def stop_rinsing(self):
        self.write_to_pump_ao_channel(0.0)
        self.sigRinsingDurationFinished.emit()
Exemple #11
0
class PowermeterGUI(GUIBase):
    """ FIXME: Please document
    """
    # declare connectors
    pm_logic = Connector(interface='PowermeterLogic')
    sigPower = QtCore.Signal(float)
    sigMeasure = QtCore.Signal(bool)

    def __init__(self, config, **kwargs):
        super().__init__(config=config, **kwargs)

    def on_activate(self):
        """ Definition and initialisation of the GUI plus staring the measurement.
        """
        self._pm_logic = self.pm_logic()

        #####################
        # Configuring the dock widgets
        # Use the inherited class 'CounterMainWindow' to create the GUI window
        self._mw = MainWindow()
        self._mw.powerMeasureButton.clicked.connect(self.measurePower)
        self.sigMeasure.connect(self._pm_logic.get_power)
        self._pm_logic.sigUpdate.connect(self.updateGui)

    def on_deactivate(self):
        """ Deactivate the module properly.
        """
        self._mw.close()

    def show(self):
        """Make window visible and put it above all other windows.
        """
        QtWidgets.QMainWindow.show(self._mw)
        self._mw.activateWindow()
        self._mw.raise_()

    @QtCore.Slot(bool)
    def measurePower(self, on):
        """ Disable laser shutter button and give logic signal.
            Logic reaction to that signal will enable the button again.
        """
        self._mw.powerMeasureButton.setEnabled(False)
        self.sigMeasure.emit(on)

    @QtCore.Slot()
    def updateGui(self):
        """ Update labels, the plot and button states with new data. """
        if isinstance(self._pm_logic.power, float):
            power_in_mW = self._pm_logic.power * 1000
            self._mw.powerValue.setText('{0:6.3f} mW'.format(power_in_mW))
            self._mw.calibratedPowerValue.setText('{0:6.3f} mW'.format(
                self._pm_logic.calibrated_power_mW))
        else:
            self._mw.powerValue.setText('{}'.format(self._pm_logic.power))
        self._mw.powerMeasureButton.setEnabled(True)
class DepthIndicatorLogic(GenericLogic):
    """ Logic module agreggating multiple hardware switches.
    """

    hdi = Connector(interface='ProcessInterface')

    sigUpdate = QtCore.Signal()

    def on_activate(self):
        """ Prepare logic module for work.
        """
        self._hdi = self.hdi()
        self.stopRequest = False
        self.bufferLength = 100
        self.data = {}

        self.hdi_unit = self._hdi.get_process_unit()

        self.init_data_logging()

    def on_deactivate(self):
        """ Deactivate module.
        """
        for i in range(5):
            QtCore.QCoreApplication.processEvents()

    @QtCore.Slot(bool)
    def measure_depth(self, state):
        """ Switched external driving on or off. """
        self.helium_depth = self._hdi.get_process_value()

        for k in self.data:
            self.data[k] = np.roll(self.data[k], -1)

        if isinstance(self.helium_depth, float):
            self.data['helium_depth'][-1] = self.helium_depth
            self.data['time'][-1] = time.time()
        else:
            self.log.warn("HDI did not return a number")
            pass
        self.sigUpdate.emit()

    def maximum_depth(self):
        return self._hdi.get_process_value_maximum()

    def init_data_logging(self):
        """ Zero all log buffers. """
        self.data['helium_depth'] = np.zeros(self.bufferLength)
        self.data['time'] = np.ones(self.bufferLength) * time.time()
Exemple #13
0
class LocalStepMotorGui(GUIBase):
    localstepmotorlogic = Connector(interface='LocalStepMotorLogic')

    def __init__(self, config, **kwargs):
        super().__init__(config=config, **kwargs)

    def on_activate(self):

        self._step_motor_logic = self.localstepmotorlogic()

        self._mw = LocalStepMotorMainWindow()

        self._mw.setDockNestingEnabled(True)
        self._motor_channel = None

        self._mw.motorchanlineEdit.returnPressed.connect(
            self.update_motor_channel)
        self._mw.moveabslineEdit.returnPressed.connect(self.MOVEABS)
        self._mw.moverellineEdit.returnPressed.connect(self.MOVEREL)

        self.show()

    def show(self):
        """Make window visible and put it above all other windows. """
        self._mw.show()
        self._mw.activateWindow()
        self._mw.raise_()

    def on_deactivate(self):

        self._mw.motorchanlineEdit.returnPressed.disconnect()
        self._mw.moveabslineEdit.returnPressed.disconnect()
        self._mw.moverellineEdit.returnPressed.disconnect()
        return 0

    def update_motor_channel(self):
        self._motor_channel = int(self._mw.motorchanlineEdit.text())

    def MOVEABS(self):
        destination = round(float(self._mw.moveabslineEdit.text()), 2)
        self._step_motor_logic.move_abs(self._motor_channel, destination)

    def MOVEREL(self):
        degree = round(float(self._mw.moverellineEdit.text()), 2)
        self._step_motor_logic.move_rel(self._motor_channel, degree)
Exemple #14
0
class AutomationLogic(GenericLogic):
    """ Logic module agreggating multiple hardware switches.
    """

    taskrunner = Connector(interface='TaskRunner')

    sigRepeat = QtCore.Signal()

    def on_activate(self):
        """ Prepare logic module for work.
        """
        self._taskrunner = self.taskrunner()
        #stuff = "a\txyz\n    b\tx\n    c\ty\n        d\tw\ne\tm\n"
        #tr = OrderedDict([
        #    ('a', OrderedDict([
        #        ('f', OrderedDict([
        #            ('g', 5)
        #        ])),
        #        ('h', 'letrole'),
        #    ])),
        #    ('b', 1),
        #    ('c', 2),
        #    ('d', 3),
        #    ('e', 4)
        #])
        self.model = TreeModel()
        #self.model.loadExecTree(tr)
        self.loadAutomation('auto.cfg')

    def on_deactivate(self):
        """ Deactivate modeule.
        """
        print(self.model.recursiveSave(self.model.rootItem))

    def loadAutomation(self, path):
        """ Load automation config into model.

            @param path str: file path
        """
        if os.path.isfile(path):
            configdict = configfile.readConfigFile(path)
            self.model.loadExecTree(configdict)
class LocalStepMotorLogic(GenericLogic):
    stepmotor1 = Connector(interface='MotorInterface')
    def __init__(self, config, **kwargs):
        super().__init__(config= config, **kwargs)
        self.threadlock = Mutex()

    def on_activate(self):
        self._motor = self.stepmotor1()


    def on_deactivate(self):
        return 0


    def move_rel(self, motor_channel=None, degree=None):
        return self._motor.move_rel(motor_channel, degree)

    
    def move_abs(self, motor_channel=None, degree=None):
        return self._motor.move_abs(motor_channel, degree)
Exemple #16
0
class AutomationGui(GUIBase):
    """ Graphical interface for arranging tasks without using Python code. """

    # declare connectors
    automationlogic = Connector(interface='AutomationLogic')

    sigRunTaskFromList = QtCore.Signal(object)
    sigPauseTaskFromList = QtCore.Signal(object)
    sigStopTaskFromList = QtCore.Signal(object)

    def on_activate(self):
        """Create all UI objects and show the window.
        """
        self._mw = AutomationMainWindow()
        self.restoreWindowPos(self._mw)
        self.logic = self.automationlogic()
        self._mw.autoTreeView.setModel(self.logic.model)
        #self._mw.taskTableView.clicked.connect(self.setRunToolState)
        #self._mw.actionStart_Task.triggered.connect(self.manualStart)
        #self._mw.actionPause_Task.triggered.connect(self.manualPause)
        #self._mw.actionStop_Task.triggered.connect(self.manualStop)
        #self.sigRunTaskFromList.connect(self.logic.startTaskByIndex)
        #self.sigPauseTaskFromList.connect(self.logic.pauseTaskByIndex)
        #self.sigStopTaskFromList.connect(self.logic.stopTaskByIndex)
        #self.logic.model.dataChanged.connect(lambda i1, i2: self.setRunToolState(None, i1))
        self.show()

    def show(self):
        """Make sure that the window is visible and at the top.
        """
        self._mw.show()

    def on_deactivate(self):
        """ Hide window and stop ipython console.
        """
        self.saveWindowPos(self._mw)
        self._mw.close()
Exemple #17
0
class SwitchGui(GUIBase):
    """ A grephical interface to mofe switches by hand and change their calibration.
    """

    # declare connectors
    switchlogic = Connector(interface='SwitchLogic')

    def on_activate(self):
        """Create all UI objects and show the window.
        """
        self._mw = SwitchMainWindow()
        lsw = self.switchlogic()
        # For each switch that the logic has, add a widget to the GUI to show its state
        for hw in lsw.switches:
            frame = QtWidgets.QGroupBox(hw, self._mw.scrollAreaWidgetContents)
            frame.setAlignment(QtCore.Qt.AlignLeft)
            frame.setFlat(False)
            self._mw.layout.addWidget(frame)
            layout = QtWidgets.QVBoxLayout(frame)
            for switch in lsw.switches[hw]:
                swidget = SwitchWidget(switch, lsw.switches[hw][switch])
                layout.addWidget(swidget)

        self.restoreWindowPos(self._mw)
        self.show()

    def show(self):
        """Make sure that the window is visible and at the top.
        """
        self._mw.show()

    def on_deactivate(self):
        """ Hide window and stop ipython console.
        """
        self.saveWindowPos(self._mw)
        self._mw.close()
Exemple #18
0
class QDPlotterGui(GUIBase):
    """ GUI  for displaying up to 3 custom plots.
    The plots are held in tabified DockWidgets and can either be manipulated in the logic
    or by corresponding parameter DockWidgets.

    Example config for copy-paste:

    qdplotter:
        module.Class: 'qdplotter.qdplotter_gui.QDPlotterGui'
        pen_color_list: [[100, 100, 100], 'c', 'm', 'g']
        connect:
            qdplot_logic: 'qdplotlogic'
    """

    sigPlotParametersChanged = QtCore.Signal(int, dict)
    sigAutoRangeClicked = QtCore.Signal(int, bool, bool)
    sigDoFit = QtCore.Signal(str, int)
    sigRemovePlotClicked = QtCore.Signal(int)

    # declare connectors
    qdplot_logic = Connector(interface='QDPlotLogic')
    # declare config options
    _pen_color_list = ConfigOption(name='pen_color_list',
                                   default=['b', 'y', 'm', 'g'])
    # declare status variables
    widget_alignment = StatusVar(name='widget_alignment', default='tabbed')

    _allowed_colors = {'b', 'g', 'r', 'c', 'm', 'y', 'k', 'w'}

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._plot_logic = None
        self._mw = None
        self._fsd = None

        self._plot_dockwidgets = list()
        self._pen_colors = list()
        self._plot_curves = list()
        self._fit_curves = list()
        self._pg_signal_proxys = list()

    def on_activate(self):
        """ Definition and initialisation of the GUI.
        """
        self._plot_logic = self.qdplot_logic()

        if not isinstance(self._pen_color_list,
                          (list, tuple)) or len(self._pen_color_list) < 1:
            self.log.warning(
                'The ConfigOption pen_color_list needs to be a list of strings but was "{0}".'
                ' Will use the following pen colors as default: {1}.'
                ''.format(self._pen_color_list, ['b', 'y', 'm', 'g']))
            self._pen_color_list = ['b', 'y', 'm', 'g']
        else:
            self._pen_color_list = list(self._pen_color_list)

        for index, color in enumerate(self._pen_color_list):
            if (isinstance(color, (list, tuple)) and len(color) == 3) or \
                    (isinstance(color, str) and color in self._allowed_colors):
                pass
            else:
                self.log.warning(
                    'The color was "{0}" but needs to be from this list: {1} '
                    'or a 3 element tuple with values from 0 to 255 for RGB.'
                    ' Setting color to "b".'.format(color,
                                                    self._allowed_colors))
                self._pen_color_list[index] = 'b'

        # Use the inherited class 'QDPlotMainWindow' to create the GUI window
        self._mw = QDPlotMainWindow()

        # Fit settings dialogs
        self._fsd = FitSettingsDialog(self._plot_logic.fit_container)
        self._fsd.applySettings()
        self._mw.fit_settings_Action.triggered.connect(self._fsd.show)

        # Connect the main window restore view actions
        self._mw.restore_tabbed_view_Action.triggered.connect(
            self.restore_tabbed_view)
        self._mw.restore_side_by_side_view_Action.triggered.connect(
            self.restore_side_by_side_view)
        self._mw.restore_arc_view_Action.triggered.connect(
            self.restore_arc_view)
        self._mw.save_all_Action.triggered.connect(self.save_all_clicked)

        # Initialize dock widgets
        self._plot_dockwidgets = list()
        self._pen_colors = list()
        self._plot_curves = list()
        self._fit_curves = list()
        self._pg_signal_proxys = list()
        self.update_number_of_plots(self._plot_logic.number_of_plots)
        # Update all plot parameters and data from logic
        for index, _ in enumerate(self._plot_dockwidgets):
            self.update_data(index)
            self.update_fit_data(index)
            self.update_plot_parameters(index)
        self.restore_view()

        # Connect signal to logic
        self.sigPlotParametersChanged.connect(
            self._plot_logic.update_plot_parameters,
            QtCore.Qt.QueuedConnection)
        self.sigAutoRangeClicked.connect(self._plot_logic.update_auto_range,
                                         QtCore.Qt.QueuedConnection)
        self.sigDoFit.connect(self._plot_logic.do_fit,
                              QtCore.Qt.QueuedConnection)
        self.sigRemovePlotClicked.connect(self._plot_logic.remove_plot,
                                          QtCore.Qt.QueuedConnection)
        self._mw.new_plot_Action.triggered.connect(self._plot_logic.add_plot,
                                                   QtCore.Qt.QueuedConnection)
        # Connect signals from logic
        self._plot_logic.sigPlotDataUpdated.connect(self.update_data,
                                                    QtCore.Qt.QueuedConnection)
        self._plot_logic.sigPlotParamsUpdated.connect(
            self.update_plot_parameters, QtCore.Qt.QueuedConnection)
        self._plot_logic.sigPlotNumberChanged.connect(
            self.update_number_of_plots, QtCore.Qt.QueuedConnection)
        self._plot_logic.sigFitUpdated.connect(self.update_fit_data,
                                               QtCore.Qt.QueuedConnection)

        self.show()

    def show(self):
        """ Make window visible and put it above all other windows. """
        self._mw.show()
        self._mw.activateWindow()
        self._mw.raise_()

    def on_deactivate(self):
        """ Deactivate the module """
        # disconnect fit
        self._mw.fit_settings_Action.triggered.disconnect()

        self._mw.restore_tabbed_view_Action.triggered.disconnect()
        self._mw.restore_side_by_side_view_Action.triggered.disconnect()
        self._mw.restore_arc_view_Action.triggered.disconnect()
        self._mw.save_all_Action.triggered.disconnect()

        # Disconnect signal to logic
        self.sigPlotParametersChanged.disconnect()
        self.sigAutoRangeClicked.disconnect()
        self.sigDoFit.disconnect()
        self.sigRemovePlotClicked.disconnect()
        self._mw.new_plot_Action.triggered.disconnect()
        # Disconnect signals from logic
        self._plot_logic.sigPlotDataUpdated.disconnect(self.update_data)
        self._plot_logic.sigPlotParamsUpdated.disconnect(
            self.update_plot_parameters)
        self._plot_logic.sigPlotNumberChanged.disconnect(
            self.update_number_of_plots)
        self._plot_logic.sigFitUpdated.disconnect(self.update_fit_data)

        # disconnect GUI elements
        self.update_number_of_plots(0)

        self._fsd.sigFitsUpdated.disconnect()
        self._mw.close()

    @QtCore.Slot(int)
    def update_number_of_plots(self, count):
        """ Adjust number of QDockWidgets to current number of plots. Does NO initialization of the
        contents.

        @param int count: Number of plots to display.
        """
        # Remove dock widgets if plot count decreased
        while count < len(self._plot_dockwidgets):
            index = len(self._plot_dockwidgets) - 1
            self._disconnect_plot_signals(index)
            self._plot_dockwidgets[-1].setParent(None)
            del self._plot_curves[-1]
            del self._fit_curves[-1]
            del self._pen_colors[-1]
            del self._plot_dockwidgets[-1]
            del self._pg_signal_proxys[-1]
        # Add dock widgets if plot count increased
        while count > len(self._plot_dockwidgets):
            index = len(self._plot_dockwidgets)
            dockwidget = PlotDockWidget('Plot {0:d}'.format(index + 1),
                                        self._mw)
            dockwidget.widget().fit_comboBox.setFitFunctions(
                self._fsd.currentFits)
            dockwidget.widget().show_fit_checkBox.setChecked(False)
            dockwidget.widget().show_controls_checkBox.setChecked(False)
            dockwidget.widget().fit_groupBox.setVisible(False)
            dockwidget.widget().controls_groupBox.setVisible(False)
            self._plot_dockwidgets.append(dockwidget)
            self._pen_colors.append(cycle(self._pen_color_list))
            self._plot_curves.append(list())
            self._fit_curves.append(list())
            self._pg_signal_proxys.append([None, None])
            self._connect_plot_signals(index)
            self.restore_view()

    def _connect_plot_signals(self, index):
        dockwidget = self._plot_dockwidgets[index].widget()
        self._fsd.sigFitsUpdated.connect(
            dockwidget.fit_comboBox.setFitFunctions)
        dockwidget.fit_pushButton.clicked.connect(
            functools.partial(self.fit_clicked, index))

        x_lim_callback = functools.partial(self.x_limits_changed, index)
        dockwidget.x_lower_limit_DoubleSpinBox.valueChanged.connect(
            x_lim_callback)
        dockwidget.x_upper_limit_DoubleSpinBox.valueChanged.connect(
            x_lim_callback)
        y_lim_callback = functools.partial(self.y_limits_changed, index)
        dockwidget.y_lower_limit_DoubleSpinBox.valueChanged.connect(
            y_lim_callback)
        dockwidget.y_upper_limit_DoubleSpinBox.valueChanged.connect(
            y_lim_callback)

        dockwidget.x_label_lineEdit.editingFinished.connect(
            functools.partial(self.x_label_changed, index))
        dockwidget.x_unit_lineEdit.editingFinished.connect(
            functools.partial(self.x_unit_changed, index))
        dockwidget.y_label_lineEdit.editingFinished.connect(
            functools.partial(self.y_label_changed, index))
        dockwidget.y_unit_lineEdit.editingFinished.connect(
            functools.partial(self.y_unit_changed, index))

        dockwidget.x_auto_PushButton.clicked.connect(
            functools.partial(self.x_auto_range_clicked, index))
        dockwidget.y_auto_PushButton.clicked.connect(
            functools.partial(self.y_auto_range_clicked, index))
        dockwidget.save_pushButton.clicked.connect(
            functools.partial(self.save_clicked, index))
        dockwidget.remove_pushButton.clicked.connect(
            functools.partial(self.remove_clicked, index))
        self._pg_signal_proxys[index][0] = SignalProxy(
            dockwidget.plot_PlotWidget.sigXRangeChanged,
            delay=0.2,
            slot=lambda args: self._pyqtgraph_x_limits_changed(index, args[1]))
        self._pg_signal_proxys[index][1] = SignalProxy(
            dockwidget.plot_PlotWidget.sigYRangeChanged,
            delay=0.2,
            slot=lambda args: self._pyqtgraph_y_limits_changed(index, args[1]))

    def _disconnect_plot_signals(self, index):
        dockwidget = self._plot_dockwidgets[index].widget()
        self._fsd.sigFitsUpdated.disconnect(
            dockwidget.fit_comboBox.setFitFunctions)
        dockwidget.fit_pushButton.clicked.disconnect()

        dockwidget.x_lower_limit_DoubleSpinBox.valueChanged.disconnect()
        dockwidget.x_upper_limit_DoubleSpinBox.valueChanged.disconnect()
        dockwidget.y_lower_limit_DoubleSpinBox.valueChanged.disconnect()
        dockwidget.y_upper_limit_DoubleSpinBox.valueChanged.disconnect()

        dockwidget.x_label_lineEdit.editingFinished.disconnect()
        dockwidget.x_unit_lineEdit.editingFinished.disconnect()
        dockwidget.y_label_lineEdit.editingFinished.disconnect()
        dockwidget.y_unit_lineEdit.editingFinished.disconnect()

        dockwidget.x_auto_PushButton.clicked.disconnect()
        dockwidget.y_auto_PushButton.clicked.disconnect()
        dockwidget.save_pushButton.clicked.disconnect()
        dockwidget.remove_pushButton.clicked.disconnect()
        for sig_proxy in self._pg_signal_proxys[index]:
            sig_proxy.sigDelayed.disconnect()
            sig_proxy.disconnect()

    @property
    def pen_color_list(self):
        return self._pen_color_list.copy()

    @pen_color_list.setter
    def pen_color_list(self, value):
        if not isinstance(value, (list, tuple)) or len(value) < 1:
            self.log.warning(
                'The parameter pen_color_list needs to be a list of strings but was "{0}".'
                ' Will use the following old pen colors: {1}.'
                ''.format(value, self._pen_color_list))
            return
        for index, color in enumerate(self._pen_color_list):
            if (isinstance(color, (list, tuple)) and len(color) == 3) or \
                    (isinstance(color, str) and color in self._allowed_colors):
                pass
            else:
                self.log.warning(
                    'The color was "{0}" but needs to be from this list: {1} '
                    'or a 3 element tuple with values from 0 to 255 for RGB.'
                    ''.format(color, self._allowed_colors))
                return
        else:
            self._pen_color_list = list(value)

    def restore_side_by_side_view(self):
        """ Restore the arrangement of DockWidgets to the default """
        self.restore_view(alignment='side_by_side')

    def restore_arc_view(self):
        """ Restore the arrangement of DockWidgets to the default """
        self.restore_view(alignment='arc')

    def restore_tabbed_view(self):
        """ Restore the arrangement of DockWidgets to the default """
        self.restore_view(alignment='tabbed')

    @QtCore.Slot()
    def restore_view(self, alignment=None):
        """ Restore the arrangement of DockWidgets to the default """

        if alignment is None:
            alignment = self.widget_alignment
        if alignment not in ('side_by_side', 'arc', 'tabbed'):
            alignment = 'tabbed'
        self.widget_alignment = alignment

        self._mw.setDockNestingEnabled(True)
        self._mw.centralwidget.setVisible(False)

        for i, dockwidget in enumerate(self._plot_dockwidgets):
            dockwidget.show()
            dockwidget.setFloating(False)
            dockwidget.widget().show_fit_checkBox.setChecked(False)
            dockwidget.widget().show_controls_checkBox.setChecked(False)
            if alignment == 'tabbed':
                self._mw.addDockWidget(QtCore.Qt.TopDockWidgetArea, dockwidget)
                if i > 0:
                    self._mw.tabifyDockWidget(self._plot_dockwidgets[0],
                                              dockwidget)
            elif alignment == 'arc':
                mod = i % 3
                if mod == 0:
                    self._mw.addDockWidget(QtCore.Qt.TopDockWidgetArea,
                                           dockwidget)
                    if i > 2:
                        self._mw.tabifyDockWidget(self._plot_dockwidgets[0],
                                                  dockwidget)
                elif mod == 1:
                    self._mw.addDockWidget(QtCore.Qt.BottomDockWidgetArea,
                                           dockwidget)
                    if i > 2:
                        self._mw.tabifyDockWidget(self._plot_dockwidgets[1],
                                                  dockwidget)
                elif mod == 2:
                    self._mw.addDockWidget(QtCore.Qt.BottomDockWidgetArea,
                                           dockwidget)
                    if i > 2:
                        self._mw.tabifyDockWidget(self._plot_dockwidgets[2],
                                                  dockwidget)
            elif alignment == 'side_by_side':
                self._mw.addDockWidget(QtCore.Qt.TopDockWidgetArea, dockwidget)
        if alignment == 'arc':
            if len(self._plot_dockwidgets) > 2:
                self._mw.resizeDocks(
                    [self._plot_dockwidgets[1], self._plot_dockwidgets[2]],
                    [1, 1], QtCore.Qt.Horizontal)
        elif alignment == 'side_by_side':
            self._mw.resizeDocks(self._plot_dockwidgets,
                                 [1] * len(self._plot_dockwidgets),
                                 QtCore.Qt.Horizontal)

    @QtCore.Slot(int, list, list, bool)
    def update_data(self,
                    plot_index,
                    x_data=None,
                    y_data=None,
                    clear_old=None):
        """ Function creates empty plots, grabs the data and sends it to them. """
        if not (0 <= plot_index < len(self._plot_dockwidgets)):
            self.log.warning(
                'Tried to update plot with invalid index {0:d}'.format(
                    plot_index))
            return

        if x_data is None:
            x_data = self._plot_logic.get_x_data(plot_index)
        if y_data is None:
            y_data = self._plot_logic.get_y_data(plot_index)
        if clear_old is None:
            clear_old = self._plot_logic.clear_old_data(plot_index)

        dockwidget = self._plot_dockwidgets[plot_index].widget()
        if clear_old:
            dockwidget.plot_PlotWidget.clear()
            self._pen_colors[plot_index] = cycle(self._pen_color_list)

        self._plot_curves[plot_index] = list()
        self._fit_curves[plot_index] = list()

        for line, xd in enumerate(x_data):
            yd = y_data[line]
            pen_color = next(self._pen_colors[plot_index])
            self._plot_curves[plot_index].append(
                dockwidget.plot_PlotWidget.plot(
                    pen=mkColor(pen_color),
                    symbol='d',
                    symbolSize=6,
                    symbolBrush=mkColor(pen_color)))
            self._plot_curves[plot_index][-1].setData(x=xd, y=yd)
            self._fit_curves[plot_index].append(
                dockwidget.plot_PlotWidget.plot())
            self._fit_curves[plot_index][-1].setPen('r')

    @QtCore.Slot(int)
    @QtCore.Slot(int, dict)
    def update_plot_parameters(self, plot_index, params=None):
        """ Function updated limits, labels and units in the plot and parameter widgets. """
        if not (0 <= plot_index < len(self._plot_dockwidgets)):
            self.log.warning(
                'Tried to update plot with invalid index {0:d}'.format(
                    plot_index))
            return

        dockwidget = self._plot_dockwidgets[plot_index].widget()
        if params is None:
            params = dict()
            params['x_label'] = self._plot_logic.get_x_label(plot_index)
            params['y_label'] = self._plot_logic.get_y_label(plot_index)
            params['x_unit'] = self._plot_logic.get_x_unit(plot_index)
            params['y_unit'] = self._plot_logic.get_y_unit(plot_index)
            params['x_limits'] = self._plot_logic.get_x_limits(plot_index)
            params['y_limits'] = self._plot_logic.get_y_limits(plot_index)

        if 'x_label' in params or 'x_unit' in params:
            label = params.get('x_label', None)
            unit = params.get('x_unit', None)
            if label is None:
                label = self._plot_logic.get_x_label(plot_index)
            if unit is None:
                unit = self._plot_logic.get_x_unit(plot_index)
            dockwidget.plot_PlotWidget.setLabel('bottom', label, units=unit)
            dockwidget.x_label_lineEdit.blockSignals(True)
            dockwidget.x_unit_lineEdit.blockSignals(True)
            dockwidget.x_label_lineEdit.setText(label)
            dockwidget.x_unit_lineEdit.setText(unit)
            dockwidget.x_label_lineEdit.blockSignals(False)
            dockwidget.x_unit_lineEdit.blockSignals(False)
        if 'y_label' in params or 'y_unit' in params:
            label = params.get('y_label', None)
            unit = params.get('y_unit', None)
            if label is None:
                label = self._plot_logic.get_y_label(plot_index)
            if unit is None:
                unit = self._plot_logic.get_y_unit(plot_index)
            dockwidget.plot_PlotWidget.setLabel('left', label, units=unit)
            dockwidget.y_label_lineEdit.blockSignals(True)
            dockwidget.y_unit_lineEdit.blockSignals(True)
            dockwidget.y_label_lineEdit.setText(label)
            dockwidget.y_unit_lineEdit.setText(unit)
            dockwidget.y_label_lineEdit.blockSignals(False)
            dockwidget.y_unit_lineEdit.blockSignals(False)
        if 'x_limits' in params:
            limits = params['x_limits']
            self._pg_signal_proxys[plot_index][0].block = True
            dockwidget.plot_PlotWidget.setXRange(*limits, padding=0)
            self._pg_signal_proxys[plot_index][0].block = False
            dockwidget.x_lower_limit_DoubleSpinBox.blockSignals(True)
            dockwidget.x_upper_limit_DoubleSpinBox.blockSignals(True)
            dockwidget.x_lower_limit_DoubleSpinBox.setValue(limits[0])
            dockwidget.x_upper_limit_DoubleSpinBox.setValue(limits[1])
            dockwidget.x_lower_limit_DoubleSpinBox.blockSignals(False)
            dockwidget.x_upper_limit_DoubleSpinBox.blockSignals(False)
        if 'y_limits' in params:
            limits = params['y_limits']
            self._pg_signal_proxys[plot_index][1].block = True
            dockwidget.plot_PlotWidget.setYRange(*limits, padding=0)
            self._pg_signal_proxys[plot_index][1].block = False
            dockwidget.y_lower_limit_DoubleSpinBox.blockSignals(True)
            dockwidget.y_upper_limit_DoubleSpinBox.blockSignals(True)
            dockwidget.y_lower_limit_DoubleSpinBox.setValue(limits[0])
            dockwidget.y_upper_limit_DoubleSpinBox.setValue(limits[1])
            dockwidget.y_lower_limit_DoubleSpinBox.blockSignals(False)
            dockwidget.y_upper_limit_DoubleSpinBox.blockSignals(False)

    def save_clicked(self, plot_index):
        """ Handling the save button to save the data into a file. """
        self._flush_pg_proxy(plot_index)
        self._plot_logic.save_data(plot_index=plot_index)

    def save_all_clicked(self):
        """ Handling the save button to save the data into a file. """
        for plot_index, _ in enumerate(self._plot_dockwidgets):
            self.save_clicked(plot_index)

    def remove_clicked(self, plot_index):
        self._flush_pg_proxy(plot_index)
        self.sigRemovePlotClicked.emit(plot_index)

    def x_auto_range_clicked(self, plot_index):
        """ Set the parameter_1_x_limits to the min/max of the data values """
        self.sigAutoRangeClicked.emit(plot_index, True, False)

    def y_auto_range_clicked(self, plot_index):
        """ Set the parameter_1_y_limits to the min/max of the data values """
        self.sigAutoRangeClicked.emit(plot_index, False, True)

    def x_limits_changed(self, plot_index):
        """ Handling the change of the parameter_1_x_limits. """
        dockwidget = self._plot_dockwidgets[plot_index].widget()
        self.sigPlotParametersChanged.emit(
            plot_index, {
                'x_limits': [
                    dockwidget.x_lower_limit_DoubleSpinBox.value(),
                    dockwidget.x_upper_limit_DoubleSpinBox.value()
                ]
            })

    def y_limits_changed(self, plot_index):
        """ Handling the change of the parameter_1_y_limits. """
        dockwidget = self._plot_dockwidgets[plot_index].widget()
        self.sigPlotParametersChanged.emit(
            plot_index, {
                'y_limits': [
                    dockwidget.y_lower_limit_DoubleSpinBox.value(),
                    dockwidget.y_upper_limit_DoubleSpinBox.value()
                ]
            })

    def x_label_changed(self, plot_index):
        """ Set the x-label """
        dockwidget = self._plot_dockwidgets[plot_index].widget()
        self.sigPlotParametersChanged.emit(
            plot_index, {'x_label': dockwidget.x_label_lineEdit.text()})

    def y_label_changed(self, plot_index):
        """ Set the y-label and the uni of plot 1 """
        dockwidget = self._plot_dockwidgets[plot_index].widget()
        self.sigPlotParametersChanged.emit(
            plot_index, {'y_label': dockwidget.y_label_lineEdit.text()})

    def x_unit_changed(self, plot_index):
        """ Set the x-label """
        dockwidget = self._plot_dockwidgets[plot_index].widget()
        self.sigPlotParametersChanged.emit(
            plot_index, {'x_unit': dockwidget.x_unit_lineEdit.text()})

    def y_unit_changed(self, plot_index):
        """ Set the y-label and the uni of plot 1 """
        dockwidget = self._plot_dockwidgets[plot_index].widget()
        self.sigPlotParametersChanged.emit(
            plot_index, {'y_unit': dockwidget.y_unit_lineEdit.text()})

    def fit_clicked(self, plot_index=0):
        """ Triggers the fit to be done. Attention, this runs in the GUI thread. """
        current_fit_method = self._plot_dockwidgets[plot_index].widget(
        ).fit_comboBox.getCurrentFit()[0]
        self.sigDoFit.emit(current_fit_method, plot_index)

    @QtCore.Slot(int, np.ndarray, str, str)
    def update_fit_data(self,
                        plot_index,
                        fit_data=None,
                        formatted_fitresult=None,
                        fit_method=None):
        """ Function that handles the fit results received from the logic via a signal.

        @param int plot_index: index of the plot the fit was performed for in the range for 0 to 2
        @param 3-dimensional np.ndarray fit_data: the fit data in a 2-d array for each data set
        @param str formatted_fitresult: string containing the parameters already formatted
        @param str fit_method: the fit_method used
        """
        dockwidget = self._plot_dockwidgets[plot_index].widget()

        if fit_data is None or formatted_fitresult is None or fit_method is None:
            fit_data, formatted_fitresult, fit_method = self._plot_logic.get_fit_data(
                plot_index)

        if not fit_method:
            fit_method = 'No Fit'

        dockwidget.fit_comboBox.blockSignals(True)

        dockwidget.show_fit_checkBox.setChecked(True)
        dockwidget.fit_textBrowser.clear()
        dockwidget.fit_comboBox.setCurrentFit(fit_method)
        if fit_method == 'No Fit':
            for index, curve in enumerate(self._fit_curves[plot_index]):
                if curve in dockwidget.plot_PlotWidget.items():
                    dockwidget.plot_PlotWidget.removeItem(curve)
        else:
            dockwidget.fit_textBrowser.setPlainText(formatted_fitresult)
            for index, curve in enumerate(self._fit_curves[plot_index]):
                if curve not in dockwidget.plot_PlotWidget.items():
                    dockwidget.plot_PlotWidget.addItem(curve)
                curve.setData(x=fit_data[index][0], y=fit_data[index][1])

        dockwidget.fit_comboBox.blockSignals(False)

    def _pyqtgraph_x_limits_changed(self, plot_index, limits):
        plot_item = self._plot_dockwidgets[plot_index].widget(
        ).plot_PlotWidget.getPlotItem()
        if plot_item.ctrl.logXCheck.isChecked(
        ) or plot_item.ctrl.fftCheck.isChecked():
            return
        self.sigPlotParametersChanged.emit(plot_index, {'x_limits': limits})

    def _pyqtgraph_y_limits_changed(self, plot_index, limits):
        plot_item = self._plot_dockwidgets[plot_index].widget(
        ).plot_PlotWidget.getPlotItem()
        if plot_item.ctrl.logYCheck.isChecked(
        ) or plot_item.ctrl.fftCheck.isChecked():
            return
        self.sigPlotParametersChanged.emit(plot_index, {'y_limits': limits})

    def _flush_pg_proxy(self, plot_index):
        x_proxy, y_proxy = self._pg_signal_proxys[plot_index]
        x_proxy.flush()
        y_proxy.flush()
class TemperatureMonitorGUI(GUIBase):
    """ FIXME: Please document
    """

    ## declare connectors
    tmlogic = Connector(interface='TemperatureMonitorLogic')
    sigQueryIntervalChanged = QtCore.Signal(int)

    def __init__(self, config, **kwargs):
        super().__init__(config=config, **kwargs)

    def on_activate(self):
        """ Definition and initialisation of the GUI plus staring the measurement.
        """
        self._tm_logic = self.tmlogic()

        #####################
        # Configuring the dock widgets
        # Use the inherited class 'CounterMainWindow' to create the GUI window
        self._mw = MainGUIWindow()

        # Setup dock widgets
        self._mw.setDockNestingEnabled(True)
        self._mw.actionReset_View.triggered.connect(self.restoreDefaultView)

        # set up plot
        self._mw.plotWidget = pg.PlotWidget(
            axisItems={'bottom': TimeAxisItem(orientation='bottom')})
        self._mw.pwContainer.layout().addWidget(self._mw.plotWidget)

        plot1 = self._mw.plotWidget.getPlotItem()
        plot1.setLabel('left', 'Temperature', units='K')
        plot1.setLabel('bottom', 'Time', units=None)

        self.curves = {}
        i = 0
        for name in self._tm_logic.data:
            if name != 'time':
                if name == 'baseplate':
                    curve = pg.PlotDataItem(pen=pg.mkPen(palette.c1, style=QtCore.Qt.DotLine),
                                            symbol='s',
                                            symbolPen=palette.c1,
                                            symbolBrush=palette.c1,
                                            symbolSize=4)
                elif name == 'tip':
                    curve = pg.PlotDataItem(pen=pg.mkPen(palette.c2, style=QtCore.Qt.DotLine),
                                            symbol='s',
                                            symbolPen=palette.c2,
                                            symbolBrush=palette.c2,
                                            symbolSize=4)
                elif name == 'magnet':
                    curve = pg.PlotDataItem(pen=pg.mkPen(palette.c3, style=QtCore.Qt.DotLine),
                                            symbol='s',
                                            symbolPen=palette.c3,
                                            symbolBrush=palette.c3,
                                            symbolSize=4)
                elif name == 'z_braid':
                    curve = pg.PlotDataItem(pen=pg.mkPen(palette.c4, style=QtCore.Qt.DotLine),
                                            symbol='s',
                                            symbolPen=palette.c4,
                                            symbolBrush=palette.c4,
                                            symbolSize=4)

                plot1.addItem(curve)
                self.curves[name] = curve
                i += 1

        self.plot1 = plot1
        self._mw.record_temperature_Action.triggered.connect(self.save_clicked)
        self._mw.actionClear_Buffer.triggered.connect(self.clear_buffer_clicked)

        self._mw.baseplatecheckBox.setStyleSheet(f"color: {palette.c1.name()}")
        self._mw.tipcheckBox.setStyleSheet(f"color: {palette.c2.name()}")
        self._mw.magnetcheckBox.setStyleSheet(f"color: {palette.c3.name()}")
        self._mw.zbraidcheckBox.setStyleSheet(f"color: {palette.c4.name()}")

        # self.updateViews()
        # self.plot1.vb.sigResized.connect(self.updateViews)
        self._tm_logic.sigSavingStatusChanged.connect(self.update_saving_Action)
        self._tm_logic.sigUpdate.connect(self.updateGui)
        self.sigQueryIntervalChanged.connect(self._tm_logic.change_qtimer_interval)
        self._mw.queryIntervalSpinBox.valueChanged.connect(self.update_query_interval)

        self._mw.queryIntervalSpinBox.setValue(self._tm_logic.queryInterval)

        # Required to autostart loop on launch
        self.update_query_interval()

    def on_deactivate(self):
        """ Deactivate the module properly.
        """
        self._tm_logic.sigSavingStatusChanged.disconnect()
        self._mw.record_temperature_Action.triggered.disconnect()
        self._mw.actionClear_Buffer.triggered.disconnect()
        self._mw.close()

    def show(self):
        """Make window visible and put it above all other windows.
        """
        QtWidgets.QMainWindow.show(self._mw)
        self._mw.activateWindow()
        self._mw.raise_()

    def restoreDefaultView(self):
        """ Restore the arrangement of DockWidgets to the default
        """
        # Show any hidden dock widgets
        self._mw.plotDockWidget.show()

        # re-dock any floating dock widgets
        self._mw.plotDockWidget.setFloating(False)

        # Arrange docks widgets
        self._mw.addDockWidget(QtCore.Qt.DockWidgetArea(2), self._mw.plotDockWidget)

    @QtCore.Slot()
    def update_query_interval(self):
        self.sigQueryIntervalChanged.emit(self._mw.queryIntervalSpinBox.value())

    @QtCore.Slot()
    def updateGui(self):
        """ Update labels, the plot and button states with new data. """
        self._mw.baseplateTemperature.setText('{0:6.3f} K'.format(self._tm_logic.data['baseplate'][-1]))
        self._mw.tipTemperature.setText('{0:6.3f} K'.format(self._tm_logic.data['tip'][-1]))
        self._mw.magnetTemperature.setText('{0:6.3f} K'.format(self._tm_logic.data['magnet'][-1]))
        self._mw.zbraidTemperature.setText('{0:6.3f} K'.format(self._tm_logic.data['z_braid'][-1]))

        if self._mw.baseplatecheckBox.isChecked():
            self.curves['baseplate'].setData(x=self._tm_logic.data['time'], y=self._tm_logic.data['baseplate'])
            self.curves['baseplate'].show()
        else:
            self.curves['baseplate'].hide()

        if self._mw.tipcheckBox.isChecked():
            self.curves['tip'].setData(x=self._tm_logic.data['time'], y=self._tm_logic.data['tip'])
            self.curves['tip'].show()
        else:
            self.curves['tip'].hide()

        if self._mw.magnetcheckBox.isChecked():
            self.curves['magnet'].setData(x=self._tm_logic.data['time'], y=self._tm_logic.data['magnet'])
            self.curves['magnet'].show()
        else:
            self.curves['magnet'].hide()

        if self._mw.zbraidcheckBox.isChecked():
            self.curves['z_braid'].setData(x=self._tm_logic.data['time'], y=self._tm_logic.data['z_braid'])
            self.curves['z_braid'].show()
        else:
            self.curves['z_braid'].hide()

        # if self._mw.xbraidcheckBox.isChecked():
        #     self._mw.xbraidTemperature.setText('{0:6.3f} K'.format(self._tm_logic.data['x_braid'][-1]))
        #     self.curves['x_braid'].show()
        #     self.curves['x_braid'].setData(x=self._tm_logic.data['time'], y=self._tm_logic.data['x_braid'])
        # else:
        #     self.curves['x_braid'].hide()
        #     self._mw.xbraidTemperature.setText('-')
        #
        # if self._mw.ybraidcheckBox.isChecked():
        #     self._mw.ybraidTemperature.setText('{0:6.3f} K'.format(self._tm_logic.data['y_braid'][-1]))
        #     self.curves['y_braid'].show()
        #     self.curves['y_braid'].setData(x=self._tm_logic.data['time'], y=self._tm_logic.data['y_braid'])
        # else:
        #     self.curves['y_braid'].hide()
        #     self._mw.ybraidTemperature.setText('-')

    def save_clicked(self):
        """ Handling the save button to save the data into a file.
        """
        if self._tm_logic.get_saving_state():
            self._mw.record_temperature_Action.setText('Stop Stream Saving')
            self._tm_logic.stop_saving()
        else:
            self._mw.record_temperature_Action.setText('Start Stream Saving')
            self._tm_logic.start_saving()
        return self._tm_logic.get_saving_state()

    def clear_buffer_clicked(self):
        self._tm_logic.clear_buffer()
        return

    def update_saving_Action(self, start):
        """Function to ensure that the GUI-save_action displays the current status

        @param bool start: True if the measurment saving is started
        @return bool start: see above
        """
        if start:
            self._mw.record_temperature_Action.setText('Stop Stream Saving')
        else:
            self._mw.record_temperature_Action.setText('Start Stream Saving')
        return start
class ProcessControlModifier(GenericLogic, ProcessControlInterface):
    """ This interfuse can be used to modify a process control on the fly. It needs a 2D array to interpolate
    General form : [[x_0, y_0], [x_1, y_1], ... , [x_n, y_n]]
    Example : [[0,0], [1,10]]
    With this example, the value 0.5 sent from the logic would be transformed to 5 sent to the hardware.



    This calibration is stored and remembered as a status variable. If this variable is None, the calibration
    can be read from a simple file with two columns :
    # X Y
    0   0
    1   10
    """

    hardware = Connector(interface='ProcessControlInterface')

    _calibration = StatusVar(default=None)
    _calibration_file = ConfigOption('calibration_file', None)
    _force_calibration_from_file = ConfigOption('force_calibration_from_file', False)
    _interpolated_function = None
    _interpolated_function_reversed = None

    _new_unit = ConfigOption('new_unit', None)

    _last_control_value = None

    def on_activate(self):
        """ Activate module.
        """
        self._hardware = self.hardware()

        if self._force_calibration_from_file and self._calibration_file is None:
            self.log.error('Loading from calibration is enforced but no calibration file has been'
                           'given.')
        if self._force_calibration_from_file or (self._calibration is None and self._calibration_file is not None):
            self.log.info('Loading from calibration file.')
            calibration = np.loadtxt(self._calibration_file)
            self.update_calibration(calibration)
        else:
            self.update_calibration()

    def on_deactivate(self):
        """ Deactivate module.
        """
        pass

    def update_calibration(self, calibration=None):
        """ Construct the interpolated function from the calibration data

        calibration (optional) 2d array : A new calibration to set

        """
        if calibration is not None:
            self._calibration = calibration
        if self._calibration is None:
            self._interpolated_function = lambda x: x
            self._interpolated_function_reversed = lambda x: x
        else:
            self._interpolated_function = interp1d(self._calibration[:, 0], self._calibration[:, 1])
            self._interpolated_function_reversed = interp1d(self._calibration[:, 1], self._calibration[:, 0])
        if self._last_control_value is not None:
            self.set_control_value(self._last_control_value)

    def reset_to_identity(self):
        """ Reset the calibration data to use identity """
        self._calibration = None
        self.update_calibration()

    def get_control_value(self):
        """ Return the original control value
        """
        if self._interpolated_function_reversed is not None:
            return self._interpolated_function_reversed(self._hardware.get_control_value())
        else:
            self.log.error('No calibration was found, please set the control value modifier data first.')

    def set_control_value(self, value):
        """ Set the control value modified
        """
        if self._interpolated_function is not None:
            self._hardware.set_control_value(self._interpolated_function(value))
        else:
            self.log.error('No calibration was found, please set the control value modifier data first.')

    def get_control_unit(self):
        """ Return the process unit
        """
        if self._new_unit is not None:
            return self._new_unit
        else:
            return self._hardware.get_control_unit()

    def get_control_limit(self):
        """ Return limits within which the controlled value can be set as a tuple of (low limit, high limit)
        """
        mini, maxi = self._hardware.get_control_limit()
        mini = float(self._interpolated_function_reversed(mini))
        maxi = float(self._interpolated_function_reversed(maxi))
        return mini, maxi
Exemple #21
0
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

"""
from core.connector import Connector
from core.responser import Responser
from core.daemon import Clean
import logging
import sys

logging.basicConfig(level=logging.DEBUG,
                    format='%(asctime)s:%(filename)s %(levelname)s %(message)s',
                    datefmt='%a, %d %b %Y %H:%M:%S',
                    filename='loframe.log',
                    filemode='w')

if __name__ == "__main__":
	conn = Connector()
	conn.start()
	rep = Responser()
	rep.start()
	clean = Clean()
	clean.start()
class NuclearOperationsGui(GUIBase):
    """ This is the main GUI Class for Nuclear Operations. """

    # declare connectors
    nuclearoperationslogic = Connector(interface='NuclearOperationsLogic')
    savelogic = Connector(interface='SaveLogic')

    def on_activate(self):
        """
        This init connects all the graphic modules, which were created in the
        *.ui file and configures the event handling between the modules.
        """

        self._no_logic = self.nuclearoperationslogic()
        self._save_logic = self.savelogic()

        # Create the MainWindow to display the GUI
        self._mw = NuclearOperationsMainWindow()

        # Add save file tag input box
        self._mw.save_tag_LineEdit = QtWidgets.QLineEdit(self._mw)
        self._mw.save_tag_LineEdit.setMaximumWidth(200)
        self._mw.save_tag_LineEdit.setToolTip('Enter a nametag which will be\n'
                                              'added to the filename.')
        self._mw.save_ToolBar.addWidget(self._mw.save_tag_LineEdit)

        # Set the values from the logic to the GUI:

        # Set the pulser parameter:
        self._mw.electron_rabi_periode_DSpinBox.setValue(
            self._no_logic.electron_rabi_periode * 1e9)
        self._mw.pulser_mw_freq_DSpinBox.setValue(
            self._no_logic.pulser_mw_freq / 1e6)
        self._mw.pulser_mw_amp_DSpinBox.setValue(self._no_logic.pulser_mw_amp)
        self._mw.pulser_mw_ch_SpinBox.setValue(self._no_logic.pulser_mw_ch)
        self._mw.nuclear_rabi_period0_DSpinBox.setValue(
            self._no_logic.nuclear_rabi_period0 * 1e6)
        self._mw.pulser_rf_freq0_DSpinBox.setValue(
            self._no_logic.pulser_rf_freq0 / 1e6)
        self._mw.pulser_rf_amp0_DSpinBox.setValue(
            self._no_logic.pulser_rf_amp0)
        self._mw.nuclear_rabi_period1_DSpinBox.setValue(
            self._no_logic.nuclear_rabi_period1 * 1e6)
        self._mw.pulser_rf_freq1_DSpinBox.setValue(
            self._no_logic.pulser_rf_freq1 / 1e6)
        self._mw.pulser_rf_amp1_DSpinBox.setValue(
            self._no_logic.pulser_rf_amp1)
        self._mw.pulser_rf_ch_SpinBox.setValue(self._no_logic.pulser_rf_ch)
        self._mw.pulser_laser_length_DSpinBox.setValue(
            self._no_logic.pulser_laser_length * 1e9)
        self._mw.pulser_laser_amp_DSpinBox.setValue(
            self._no_logic.pulser_laser_amp)
        self._mw.pulser_laser_ch_SpinBox.setValue(
            self._no_logic.pulser_laser_ch)
        self._mw.num_singleshot_readout_SpinBox.setValue(
            self._no_logic.num_singleshot_readout)
        self._mw.pulser_idle_time_DSpinBox.setValue(
            self._no_logic.pulser_idle_time * 1e9)
        self._mw.pulser_detect_ch_SpinBox.setValue(
            self._no_logic.pulser_detect_ch)

        # set the measurement parameter:
        self._mw.current_meas_asset_name_ComboBox.clear()
        self._mw.current_meas_asset_name_ComboBox.addItems(
            self._no_logic.get_meas_type_list())
        if self._no_logic.current_meas_asset_name != '':
            index = self._mw.current_meas_asset_name_ComboBox.findText(
                self._no_logic.current_meas_asset_name,
                QtCore.Qt.MatchFixedString)
            if index >= 0:
                self._mw.current_meas_asset_name_ComboBox.setCurrentIndex(
                    index)

        if self._no_logic.current_meas_asset_name == 'Nuclear_Frequency_Scan':
            self._mw.x_axis_start_DSpinBox.setValue(
                self._no_logic.x_axis_start / 1e6)
            self._mw.x_axis_step_DSpinBox.setValue(self._no_logic.x_axis_step /
                                                   1e6)
        elif self._no_logic.current_meas_asset_name in [
                'Nuclear_Rabi', 'QSD_-_Artificial_Drive', 'QSD_-_SWAP_FID',
                'QSD_-_Entanglement_FID'
        ]:
            self._mw.x_axis_start_DSpinBox.setValue(
                self._no_logic.x_axis_start * 1e6)
            self._mw.x_axis_step_DSpinBox.setValue(self._no_logic.x_axis_step *
                                                   1e6)

        self._mw.x_axis_num_points_SpinBox.setValue(
            self._no_logic.x_axis_num_points)

        self._mw.num_of_meas_runs_SpinBox.setValue(
            self._no_logic.num_of_meas_runs)

        # set the optimize parameters:
        self._mw.optimize_period_odmr_SpinBox.setValue(
            self._no_logic.optimize_period_odmr)
        self._mw.optimize_period_confocal_SpinBox.setValue(
            self._no_logic.optimize_period_confocal)
        self._mw.odmr_meas_freq0_DSpinBox.setValue(
            self._no_logic.odmr_meas_freq0 / 1e6)
        self._mw.odmr_meas_freq1_DSpinBox.setValue(
            self._no_logic.odmr_meas_freq1 / 1e6)
        self._mw.odmr_meas_freq2_DSpinBox.setValue(
            self._no_logic.odmr_meas_freq2 / 1e6)
        self._mw.odmr_meas_runtime_DSpinBox.setValue(
            self._no_logic.odmr_meas_runtime)
        self._mw.odmr_meas_freq_range_DSpinBox.setValue(
            self._no_logic.odmr_meas_freq_range / 1e6)
        self._mw.odmr_meas_step_DSpinBox.setValue(
            self._no_logic.odmr_meas_step / 1e6)
        self._mw.odmr_meas_power_DSpinBox.setValue(
            self._no_logic.odmr_meas_power)

        # set the mw parameters for measurement
        self._mw.mw_cw_freq_DSpinBox.setValue(self._no_logic.mw_cw_freq / 1e6)
        self._mw.mw_cw_power_DSpinBox.setValue(self._no_logic.mw_cw_power)
        self._mw.mw_on_odmr_peak_ComboBox.clear()
        # convert on the fly the integer entries to str entries:
        self._mw.mw_on_odmr_peak_ComboBox.addItems(
            [str(elem) for elem in self._no_logic.get_available_odmr_peaks()])

        # set gated counter parameters:
        self._mw.gc_number_of_samples_SpinBox.setValue(
            self._no_logic.gc_number_of_samples)
        self._mw.gc_samples_per_readout_SpinBox.setValue(
            self._no_logic.gc_samples_per_readout)

        # Create the graphic display for the measurement:
        self.nuclear_ops_graph = pg.PlotDataItem(
            self._no_logic.x_axis_list,
            self._no_logic.y_axis_list,
            pen=QtGui.QPen(QtGui.QColor(212, 85, 0, 255)))

        self._mw.nulcear_ops_GraphicsView.addItem(self.nuclear_ops_graph)

        # Set the proper initial display:
        self.current_meas_asset_name_changed()

        # Connect the signals:
        self._mw.current_meas_asset_name_ComboBox.currentIndexChanged.connect(
            self.current_meas_asset_name_changed)

        # adapt the unit according to the

        # Connect the start and stop signals:
        self._mw.action_run_stop.toggled.connect(self.start_stop_measurement)
        self._mw.action_continue.toggled.connect(
            self.continue_stop_measurement)
        self._mw.action_save.triggered.connect(self.save_measurement)
        self._no_logic.sigMeasurementStopped.connect(
            self._update_display_meas_stopped)

        # Connect graphic update:
        self._no_logic.sigCurrMeasPointUpdated.connect(self.update_meas_graph)
        self._no_logic.sigCurrMeasPointUpdated.connect(
            self.update_meas_parameter)

    def on_deactivate(self):
        """ Reverse steps of activation

        @return int: error code (0:OK, -1:error)
        """
        self._mw.close()

    def show(self):
        """Make window visible and put it above all other windows. """
        QtWidgets.QMainWindow.show(self._mw)
        self._mw.activateWindow()
        self._mw.raise_()

    def start_stop_measurement(self, is_checked):
        """ Manages what happens if nuclear operations are started/stopped.

        @param bool ischecked: If true measurement is started, if false
                               measurement stops.
        """

        if is_checked:

            # change the axes appearance according to input values:
            self._no_logic.stop_nuclear_meas()

            self.update_all_logic_parameter()
            self._no_logic.start_nuclear_meas()
            self._mw.action_continue.setEnabled(False)
        else:
            self._no_logic.stop_nuclear_meas()
            self._mw.action_continue.setEnabled(True)

    def continue_stop_measurement(self, is_checked):
        """ Manages what happens if nuclear operations are continued/stopped.

        @param bool ischecked: If true measurement is continued, if false
                               measurement stops.
        """

        if is_checked:
            # self._no_logic.stop_nuclear_meas()
            self._no_logic.start_nuclear_meas(continue_meas=True)
            self._mw.action_run_stop.setEnabled(False)

        else:
            self._no_logic.stop_nuclear_meas()
            self._mw.action_run_stop.setEnabled(True)

    def _update_display_meas_stopped(self):
        """ Update all the displays of the current measurement state and set
            them to stop. """

        self.start_stop_measurement(is_checked=False)
        self.continue_stop_measurement(is_checked=False)

    def current_meas_asset_name_changed(self):
        """ Adapt the input widget to the current measurement sequence. """

        name = self._mw.current_meas_asset_name_ComboBox.currentText()

        if name == 'Nuclear_Rabi':
            self._mw.nuclear_rabi_period0_DSpinBox.setVisible(False)
            self._mw.nuclear_rabi_period0_Label.setVisible(False)
            self._mw.nuclear_rabi_period1_DSpinBox.setVisible(False)
            self._mw.nuclear_rabi_period1_Label.setVisible(False)
            self._mw.pulser_rf_freq1_DSpinBox.setVisible(False)
            self._mw.pulser_rf_freq1_Label.setVisible(False)
            self._mw.pulser_rf_amp1_DSpinBox.setVisible(False)
            self._mw.pulser_rf_amp1_Label.setVisible(False)

            self._mw.pulser_rf_freq0_DSpinBox.setVisible(True)
            self._mw.pulser_rf_freq0_Label.setVisible(True)

            self._mw.nulcear_ops_GraphicsView.setLabel(axis='bottom',
                                                       text='RF pulse length',
                                                       units='s')
            self._mw.nulcear_ops_GraphicsView.setLabel(axis='left',
                                                       text='Flip probability')

            self._mw.x_axis_start_Label.setText('x start (u\u00B5s)')
            self._mw.x_axis_step_Label.setText('x step (u\u00B5s)')

            self._mw.current_meas_point_Label.setText(
                'Curr meas point (u\u00B5s)')

        elif name == 'Nuclear_Frequency_Scan':
            self._mw.pulser_rf_freq0_DSpinBox.setVisible(False)
            self._mw.pulser_rf_freq0_Label.setVisible(False)
            self._mw.nuclear_rabi_period1_DSpinBox.setVisible(False)
            self._mw.nuclear_rabi_period1_Label.setVisible(False)
            self._mw.pulser_rf_freq1_DSpinBox.setVisible(False)
            self._mw.pulser_rf_freq1_Label.setVisible(False)
            self._mw.pulser_rf_amp1_DSpinBox.setVisible(False)
            self._mw.pulser_rf_amp1_Label.setVisible(False)

            self._mw.nuclear_rabi_period0_DSpinBox.setVisible(True)
            self._mw.nuclear_rabi_period0_Label.setVisible(True)

            self._mw.nulcear_ops_GraphicsView.setLabel(
                axis='bottom', text='RF pulse Frequency', units='Hz')
            self._mw.nulcear_ops_GraphicsView.setLabel(axis='left',
                                                       text='Flip probability')

            self._mw.x_axis_start_Label.setText('x start (MHz)')
            self._mw.x_axis_step_Label.setText('x step (MHz)')

            self._mw.current_meas_point_Label.setText('Curr meas point (MHz)')

        elif name in [
                'QSD_-_Artificial_Drive', 'QSD_-_SWAP_FID',
                'QSD_-_Entanglement_FID'
        ]:

            self._mw.nuclear_rabi_period0_DSpinBox.setVisible(True)
            self._mw.nuclear_rabi_period0_Label.setVisible(True)
            self._mw.nuclear_rabi_period1_DSpinBox.setVisible(True)
            self._mw.nuclear_rabi_period1_Label.setVisible(True)
            self._mw.pulser_rf_freq1_DSpinBox.setVisible(True)
            self._mw.pulser_rf_freq1_Label.setVisible(True)
            self._mw.pulser_rf_amp1_DSpinBox.setVisible(True)
            self._mw.pulser_rf_amp1_Label.setVisible(True)
            self._mw.pulser_rf_freq0_DSpinBox.setVisible(True)
            self._mw.pulser_rf_freq0_Label.setVisible(True)

            self._mw.nulcear_ops_GraphicsView.setLabel(axis='bottom',
                                                       text='Pulse length',
                                                       units='s')
            self._mw.nulcear_ops_GraphicsView.setLabel(axis='left',
                                                       text='Flip probability')

            self._mw.x_axis_start_Label.setText('x start (\u00B5s)')
            self._mw.x_axis_step_Label.setText('x step (\u00B5s)')

            self._mw.current_meas_point_Label.setText(
                'Curr meas point (u\u00B5s)')

    def update_all_logic_parameter(self):
        """ If the measurement is started, update all parameters in the logic.
        """

        # pulser parameter:
        self._no_logic.electron_rabi_periode = self._mw.electron_rabi_periode_DSpinBox.value(
        ) / 1e9
        self._no_logic.pulser_mw_freq = self._mw.pulser_mw_freq_DSpinBox.value(
        ) * 1e6
        self._no_logic.pulser_mw_amp = self._mw.pulser_mw_amp_DSpinBox.value()
        self._no_logic.pulser_mw_ch = self._mw.pulser_mw_ch_SpinBox.value()
        self._no_logic.nuclear_rabi_period0 = self._mw.nuclear_rabi_period0_DSpinBox.value(
        ) / 1e6
        self._no_logic.pulser_rf_freq0 = self._mw.pulser_rf_freq0_DSpinBox.value(
        ) * 1e6
        self._no_logic.pulser_rf_amp0 = self._mw.pulser_rf_amp0_DSpinBox.value(
        )
        self._no_logic.nuclear_rabi_period1 = self._mw.nuclear_rabi_period1_DSpinBox.value(
        ) / 1e6
        self._no_logic.pulser_rf_freq1 = self._mw.pulser_rf_freq1_DSpinBox.value(
        ) * 1e6
        self._no_logic.pulser_rf_amp1 = self._mw.pulser_rf_amp1_DSpinBox.value(
        )
        self._no_logic.pulser_rf_ch = self._mw.pulser_rf_ch_SpinBox.value()
        self._no_logic.pulser_laser_length = self._mw.pulser_laser_length_DSpinBox.value(
        ) / 1e9
        self._no_logic.pulser_laser_amp = self._mw.pulser_laser_amp_DSpinBox.value(
        )
        self._no_logic.pulser_laser_ch = self._mw.pulser_laser_ch_SpinBox.value(
        )
        self._no_logic.num_singleshot_readout = self._mw.num_singleshot_readout_SpinBox.value(
        )
        self._no_logic.pulser_idle_time = self._mw.pulser_idle_time_DSpinBox.value(
        ) / 1e9
        self._no_logic.pulser_detect_ch = self._mw.pulser_detect_ch_SpinBox.value(
        )

        # measurement parameter
        curr_meas_name = self._mw.current_meas_asset_name_ComboBox.currentText(
        )
        self._no_logic.current_meas_asset_name = curr_meas_name

        if curr_meas_name in [
                'Nuclear_Rabi', 'QSD_-_Artificial_Drive', 'QSD_-_SWAP_FID',
                'QSD_-_Entanglement_FID'
        ]:
            self._no_logic.x_axis_start = self._mw.x_axis_start_DSpinBox.value(
            ) / 1e6
            self._no_logic.x_axis_step = self._mw.x_axis_step_DSpinBox.value(
            ) / 1e6
        elif curr_meas_name in ['Nuclear_Frequency_Scan']:
            self._no_logic.x_axis_start = self._mw.x_axis_start_DSpinBox.value(
            ) * 1e6
            self._no_logic.x_axis_step = self._mw.x_axis_step_DSpinBox.value(
            ) * 1e6
        else:
            self.log.error(
                'This measurement does not have any units associated to it!')

        self._no_logic.x_axis_num_points = self._mw.x_axis_num_points_SpinBox.value(
        )
        self._no_logic.num_of_meas_runs = self._mw.num_of_meas_runs_SpinBox.value(
        )

        # Optimization measurements:
        self._no_logic.optimize_period_odmr = self._mw.optimize_period_odmr_SpinBox.value(
        )
        self._no_logic.optimize_period_confocal = self._mw.optimize_period_confocal_SpinBox.value(
        )

        # Optimization parameters:
        self._no_logic.odmr_meas_freq0 = self._mw.odmr_meas_freq0_DSpinBox.value(
        ) * 1e6
        self._no_logic.odmr_meas_freq1 = self._mw.odmr_meas_freq1_DSpinBox.value(
        ) * 1e6
        self._no_logic.odmr_meas_freq2 = self._mw.odmr_meas_freq2_DSpinBox.value(
        ) * 1e6
        self._no_logic.odmr_meas_runtime = self._mw.odmr_meas_runtime_DSpinBox.value(
        )
        self._no_logic.odmr_meas_freq_range = self._mw.odmr_meas_freq_range_DSpinBox.value(
        ) * 1e6
        self._no_logic.odmr_meas_step = self._mw.odmr_meas_step_DSpinBox.value(
        ) * 1e6
        self._no_logic.odmr_meas_power = self._mw.odmr_meas_power_DSpinBox.value(
        )

        # mw parameters for measurement
        self._no_logic.mw_cw_freq = self._mw.mw_cw_freq_DSpinBox.value() * 1e6
        self._no_logic.mw_cw_power = self._mw.mw_cw_power_DSpinBox.value()
        self._no_logic.mw_on_odmr_peak = int(
            self._mw.mw_on_odmr_peak_ComboBox.currentText())

        # gated counter
        self._no_logic.gc_number_of_samples = self._mw.gc_number_of_samples_SpinBox.value(
        )
        self._no_logic.gc_samples_per_readout = self._mw.gc_samples_per_readout_SpinBox.value(
        )

    def save_measurement(self):
        """ Save the current measurement.

        @return:
        """
        timestamp = datetime.datetime.now()
        filetag = self._mw.save_tag_LineEdit.text()
        filepath = self._save_logic.get_path_for_module(
            module_name='NuclearOperations')

        if len(filetag) > 0:
            filename = os.path.join(
                filepath, '{0}_{1}_NuclearOps'.format(
                    timestamp.strftime('%Y%m%d-%H%M-%S'), filetag))
        else:
            filename = os.path.join(
                filepath, '{0}_NuclearOps'.format(
                    timestamp.strftime('%Y%m%d-%H%M-%S'), ))

        exporter_graph = pg.exporters.SVGExporter(
            self._mw.nulcear_ops_GraphicsView.plotItem.scene())

        #exporter_graph = pg.exporters.ImageExporter(self._mw.odmr_PlotWidget.plotItem)
        exporter_graph.export(filename + '.svg')

        # self._save_logic.
        self._no_logic.save_nuclear_operation_measurement(name_tag=filetag,
                                                          timestamp=timestamp)

    def update_meas_graph(self):
        """ Retrieve from the logic the current x and y values and display them
            in the graph.
        """

        self.nuclear_ops_graph.setData(self._no_logic.x_axis_list,
                                       self._no_logic.y_axis_list)

    def update_meas_parameter(self):
        """ Update the display parameter close to the graph. """

        self._mw.current_meas_index_SpinBox.setValue(
            self._no_logic.current_meas_index)
        self._mw.elapsed_time_DSpinBox.setValue(self._no_logic.elapsed_time)
        self._mw.num_of_current_meas_runs_SpinBox.setValue(
            self._no_logic.num_of_current_meas_runs)

        measurement_name = self._no_logic.current_meas_asset_name
        if measurement_name in [
                'Nuclear_Rabi', 'QSD_-_Artificial_Drive', 'QSD_-_SWAP_FID',
                'QSD_-_Entanglement_FID'
        ]:
            self._mw.current_meas_point_DSpinBox.setValue(
                self._no_logic.current_meas_point * 1e6)
        elif measurement_name == 'Nuclear_Frequency_Scan':
            self._mw.current_meas_point_DSpinBox.setValue(
                self._no_logic.current_meas_point / 1e6)
        else:
            pass
class ODMRLogic(GenericLogic):
    """This is the Logic class for ODMR."""

    # declare connectors
    odmrcounter = Connector(interface='ODMRCounterInterface')
    fitlogic = Connector(interface='FitLogic')
    microwave1 = Connector(interface='MicrowaveInterface')
    savelogic = Connector(interface='SaveLogic')
    taskrunner = Connector(interface='TaskRunner')
    # Connecting to camera logic
    camera = Connector(interface='CameraLogic')

    # config option
    mw_scanmode = ConfigOption(
        'scanmode',
        'LIST',
        missing='warn',
        converter=lambda x: MicrowaveMode[x.upper()])
    # Default clock frequency is set dependant on exp. time. here f is in
    # milliseconds.
    f = 1
    exp_time = StatusVar('exp_time', f)
    clock_frequency = StatusVar('clock_frequency', 1. / ((f / 1000.) + 0.0))
    cw_mw_frequency = StatusVar('cw_mw_frequency', 2870e6)
    cw_mw_power = StatusVar('cw_mw_power', -30)
    sweep_mw_power = StatusVar('sweep_mw_power', -30)
    mw_start = StatusVar('mw_start', 2800e6)
    mw_stop = StatusVar('mw_stop', 2950e6)
    mw_step = StatusVar('mw_step', 2e6)
    run_time = StatusVar('run_time', 60)
    number_of_lines = StatusVar('number_of_lines', 50)
    fc = StatusVar('fits', None)
    lines_to_average = StatusVar('lines_to_average', 0)
    _oversampling = StatusVar('oversampling', default=10)
    _lock_in_active = StatusVar('lock_in_active', default=False)

    # Internal signals
    sigNextLine = QtCore.Signal()

    # Update signals, e.g. for GUI module
    sigParameterUpdated = QtCore.Signal(dict)
    sigOutputStateUpdated = QtCore.Signal(str, bool)
    sigOdmrPlotsUpdated = QtCore.Signal(np.ndarray, np.ndarray, np.ndarray)
    sigOdmrFitUpdated = QtCore.Signal(np.ndarray, np.ndarray, dict, str)
    sigOdmrElapsedTimeUpdated = QtCore.Signal(float, int)

    def __init__(self, config, **kwargs):
        super().__init__(config=config, **kwargs)
        self.threadlock = Mutex()

    def on_activate(self):
        """
        Initialisation performed during activation of the module.
        """
        # Get connectors
        self._mw_device = self.microwave1()
        self._fit_logic = self.fitlogic()
        self._odmr_counter = self.odmrcounter()
        self._save_logic = self.savelogic()
        self._taskrunner = self.taskrunner()
        self._camera = self.camera()

        # Get hardware constraints
        limits = self.get_hw_constraints()

        # Set/recall microwave source parameters
        self.cw_mw_frequency = limits.frequency_in_range(self.cw_mw_frequency)
        self.cw_mw_power = limits.power_in_range(self.cw_mw_power)
        self.sweep_mw_power = limits.power_in_range(self.sweep_mw_power)
        self.mw_start = limits.frequency_in_range(self.mw_start)
        self.mw_stop = limits.frequency_in_range(self.mw_stop)
        self.mw_step = limits.list_step_in_range(self.mw_step)
        # self._odmr_counter.oversampling = self._oversampling
        # self._odmr_counter.lock_in_active = self._lock_in_active

        # Set the trigger polarity (RISING/FALLING) of the mw-source input trigger
        # theoretically this can be changed, but the current counting scheme
        # will not support that
        self.mw_trigger_pol = TriggerEdge.RISING
        self.set_trigger(self.mw_trigger_pol, self.clock_frequency)

        # Elapsed measurement time and number of sweeps
        self.elapsed_time = 0.0
        self.elapsed_sweeps = 0

        # Set flags
        # for stopping a measurement
        self._stopRequested = False
        # for clearing the ODMR data during a measurement
        self._clearOdmrData = False

        # Initalize the ODMR data arrays (mean signal and sweep matrix)
        self._initialize_odmr_plots()
        # Raw data array
        self.odmr_raw_data = np.zeros(
            [self.number_of_lines,
             len(self._odmr_counter.get_odmr_channels()),
             self.odmr_plot_x.size]
        )
        # The array for images of the entire sweep is intialized.
        self.sweep_images = np.zeros(
            (self.odmr_plot_x.size, *np.flip(self._camera.get_size(), axis=0))
        )
        # Switch off microwave and set CW frequency and power
        self.mw_off()
        self.set_cw_parameters(self.cw_mw_frequency, self.cw_mw_power)

        # Connect signals
        self.sigNextLine.connect(
            self._scan_odmr_line,
            QtCore.Qt.QueuedConnection)
        return

    def on_deactivate(self):
        """ Deinitialisation performed during deactivation of the module.
        """
        # Stop measurement if it is still running
        if self.module_state() == 'locked':
            self.stop_odmr_scan()
        timeout = 30.0
        start_time = time.time()
        while self.module_state() == 'locked':
            time.sleep(0.5)
            timeout -= (time.time() - start_time)
            if timeout <= 0.0:
                self.log.error(
                    'Failed to properly deactivate odmr logic. Odmr scan is still '
                    'running but can not be stopped after 30 sec.')
                break
        # Switch off microwave source for sure (also if CW mode is active or
        # module is still locked)
        self._mw_device.off()
        # The camera's deactivate function is called as well.
        self._camera.on_deactivate()
        # Disconnect signals
        self.sigNextLine.disconnect()

    @fc.constructor
    def sv_set_fits(self, val):
        # Setup fit container
        fc = self.fitlogic().make_fit_container('ODMR sum', '1d')
        fc.set_units(['Hz', 'contrast'])
        if isinstance(val, dict) and len(val) > 0:
            fc.load_from_dict(val)
        else:
            d1 = OrderedDict()
            d1['Lorentzian dip'] = {
                'fit_function': 'lorentzian',
                'estimator': 'dip'
            }
            d1['Two Lorentzian dips'] = {
                'fit_function': 'lorentziandouble',
                'estimator': 'dip'
            }
            d1['N14'] = {
                'fit_function': 'lorentziantriple',
                'estimator': 'N14'
            }
            d1['N15'] = {
                'fit_function': 'lorentziandouble',
                'estimator': 'N15'
            }
            d1['Two Gaussian dips'] = {
                'fit_function': 'gaussiandouble',
                'estimator': 'dip'
            }
            default_fits = OrderedDict()
            default_fits['1d'] = d1
            fc.load_from_dict(default_fits)
        return fc

    @fc.representer
    def sv_get_fits(self, val):
        """ save configured fits """
        if len(val.fit_list) > 0:
            return val.save_to_dict()
        else:
            return None

    def _initialize_odmr_plots(self):
        """ Initializing the ODMR plots (line and matrix). """
        self.odmr_plot_x = np.arange(
            self.mw_start,
            self.mw_stop +
            self.mw_step,
            self.mw_step)
        self.odmr_plot_y = np.zeros(
            [len(self.get_odmr_channels()), self.odmr_plot_x.size])
        self.odmr_fit_x = np.arange(
            self.mw_start,
            self.mw_stop +
            self.mw_step,
            self.mw_step)
        self.odmr_fit_y = np.zeros(self.odmr_fit_x.size)
        self.odmr_plot_xy = np.zeros([self.number_of_lines, len(
            self.get_odmr_channels()), self.odmr_plot_x.size])
        self.sigOdmrPlotsUpdated.emit(
            self.odmr_plot_x,
            self.odmr_plot_y,
            self.odmr_plot_xy)
        current_fit = self.fc.current_fit
        self.sigOdmrFitUpdated.emit(
            self.odmr_fit_x, self.odmr_fit_y, {}, current_fit)
        return

    def set_trigger(self, trigger_pol, frequency):
        """
        Set trigger polarity of external microwave trigger (for list and sweep mode).

        @param object trigger_pol: one of [TriggerEdge.RISING, TriggerEdge.FALLING]
        @param float frequency: trigger frequency during ODMR scan

        @return object: actually set trigger polarity returned from hardware
        """
        if self._lock_in_active:
            frequency = frequency / self._oversampling

        if self.module_state() != 'locked':
            self.mw_trigger_pol, _ = self._mw_device.set_ext_trigger(
                trigger_pol, 1 / frequency)
        else:
            self.log.warning('set_trigger failed. Logic is locked.')

        update_dict = {'trigger_pol': self.mw_trigger_pol}
        self.sigParameterUpdated.emit(update_dict)
        return self.mw_trigger_pol

    def set_average_length(self, lines_to_average):
        """
        Sets the number of lines to average for the sum of the data

        @param int lines_to_average: desired number of lines to average (0 means all)

        @return int: actually set lines to average
        """
        self.lines_to_average = int(lines_to_average)

        if self.lines_to_average <= 0:
            self.odmr_plot_y = np.mean(
                self.odmr_raw_data[:max(1, self.elapsed_sweeps), :, :],
                axis=0,
                dtype=np.float64
            )
        else:
            self.odmr_plot_y = np.mean(
                self.odmr_raw_data[:max(1, min(self.lines_to_average, self.elapsed_sweeps)), :, :],
                axis=0,
                dtype=np.float64
            )

        self.sigOdmrPlotsUpdated.emit(
            self.odmr_plot_x,
            self.odmr_plot_y,
            self.odmr_plot_xy)
        self.sigParameterUpdated.emit(
            {'average_length': self.lines_to_average})
        return self.lines_to_average

    def set_clock_frequency(self, clock_frequency):
        """
        Sets the frequency of the counter clock
        ## This must be dependant on the exposure time
        @param int clock_frequency: desired frequency of the clock

        @return int: actually set clock frequency
        """
        # checks if scanner is still running
        if self.module_state() != 'locked' and isinstance(clock_frequency, (int, float)):
            ##self.clock_frequency = int(clock_frequency)
            exp_res_dict = {0: 1000., 1: 1000000.}
            self.clock_frequency = 1. / ((self.exp_time / exp_res_dict[self._camera.get_exposure_resolution()]) + 0.0)
        else:
            self.log.warning(
                'set_clock_frequency failed. Logic is either locked or input value is '
                'no integer or float.')

        update_dict = {'clock_frequency': self.clock_frequency}
        self.sigParameterUpdated.emit(update_dict)
        return self.clock_frequency

    @property
    def oversampling(self):
        return self._oversampling

    @oversampling.setter
    def oversampling(self, oversampling):
        """
        Sets the frequency of the counter clock

        @param int oversampling: desired oversampling per frequency step
        """
        # checks if scanner is still running
        if self.module_state() != 'locked' and isinstance(oversampling, (int, float)):
            self._oversampling = int(oversampling)
            # self._odmr_counter.oversampling = self._oversampling
        else:
            self.log.warning(
                'setter of oversampling failed. Logic is either locked or input value is '
                'no integer or float.')

        update_dict = {'oversampling': self._oversampling}
        self.sigParameterUpdated.emit(update_dict)

    def set_oversampling(self, oversampling):
        self.oversampling = oversampling
        return self.oversampling

    @property
    def lock_in(self):
        return self._lock_in_active

    @lock_in.setter
    def lock_in(self, active):
        """
        Sets the frequency of the counter clock

        @param bool active: specify if signal should be detected with lock in
        """
        # checks if scanner is still running
        if self.module_state() != 'locked' and isinstance(active, bool):
            self._lock_in_active = active
            # self._odmr_counter.lock_in_active = self._lock_in_active
        else:
            self.log.warning(
                'setter of lock in failed. Logic is either locked or input value is no boolean.')

        update_dict = {'lock_in': self._lock_in_active}
        self.sigParameterUpdated.emit(update_dict)

    def set_lock_in(self, active):
        self.lock_in = active
        return self.lock_in

    def set_matrix_line_number(self, number_of_lines):
        """
        Sets the number of lines in the ODMR matrix

        @param int number_of_lines: desired number of matrix lines

        @return int: actually set number of matrix lines
        """
        if isinstance(number_of_lines, int):
            self.number_of_lines = number_of_lines
        else:
            self.log.warning('set_matrix_line_number failed. '
                             'Input parameter number_of_lines is no integer.')

        update_dict = {'number_of_lines': self.number_of_lines}
        self.sigParameterUpdated.emit(update_dict)
        return self.number_of_lines

    def set_runtime(self, runtime):
        """
        Sets the runtime for ODMR measurement

        @param float runtime: desired runtime in seconds

        @return float: actually set runtime in seconds
        """
        if isinstance(runtime, (int, float)):
            self.run_time = runtime
        else:
            self.log.warning(
                'set_runtime failed. Input parameter runtime is no integer or float.')

        update_dict = {'run_time': self.run_time}
        self.sigParameterUpdated.emit(update_dict)
        return self.run_time

    def set_cw_parameters(self, frequency, power):
        """ Set the desired new cw mode parameters.

        @param float frequency: frequency to set in Hz
        @param float power: power to set in dBm

        @return (float, float): actually set frequency in Hz, actually set power in dBm
        """
        if self.module_state() != 'locked' and isinstance(
                frequency, (int, float)) and isinstance(
                power, (int, float)):
            constraints = self.get_hw_constraints()
            frequency_to_set = constraints.frequency_in_range(frequency)
            power_to_set = constraints.power_in_range(power)
            self.cw_mw_frequency, self.cw_mw_power, dummy = self._mw_device.set_cw(
                frequency_to_set, power_to_set)
        else:
            self.log.warning(
                'set_cw_frequency failed. Logic is either locked or input value is '
                'no integer or float.')

        param_dict = {
            'cw_mw_frequency': self.cw_mw_frequency,
            'cw_mw_power': self.cw_mw_power}
        self.sigParameterUpdated.emit(param_dict)
        return self.cw_mw_frequency, self.cw_mw_power

    def set_sweep_parameters(self, start, stop, step, power):
        """ Set the desired frequency parameters for list and sweep mode

        @param float start: start frequency to set in Hz
        @param float stop: stop frequency to set in Hz
        @param float step: step frequency to set in Hz
        @param float power: mw power to set in dBm

        @return float, float, float, float: current start_freq, current stop_freq,
                                            current freq_step, current power
        """
        limits = self.get_hw_constraints()
        if self.module_state() != 'locked':
            if isinstance(start, (int, float)):
                self.mw_start = limits.frequency_in_range(start)
            if isinstance(
                    stop, (int, float)) and isinstance(
                    step, (int, float)):
                if stop <= start:
                    stop = start + step
                self.mw_stop = limits.frequency_in_range(stop)
                if self.mw_scanmode == MicrowaveMode.LIST:
                    self.mw_step = limits.list_step_in_range(step)
                elif self.mw_scanmode == MicrowaveMode.SWEEP:
                    self.mw_step = limits.sweep_step_in_range(step)
            if isinstance(power, (int, float)):
                self.sweep_mw_power = limits.power_in_range(power)
        else:
            self.log.warning('set_sweep_parameters failed. Logic is locked.')

        param_dict = {
            'mw_start': self.mw_start,
            'mw_stop': self.mw_stop,
            'mw_step': self.mw_step,
            'sweep_mw_power': self.sweep_mw_power}
        self.sigParameterUpdated.emit(param_dict)
        return self.mw_start, self.mw_stop, self.mw_step, self.sweep_mw_power

    def mw_cw_on(self):
        """
        Switching on the mw source in cw mode.

        @return str, bool: active mode ['cw', 'list', 'sweep'], is_running
        """
        if self.module_state() == 'locked':
            self.log.error(
                'Can not start microwave in CW mode. ODMRLogic is already locked.')
        else:
            self.cw_mw_frequency, self.cw_mw_power, mode = self._mw_device.set_cw(
                self.cw_mw_frequency, self.cw_mw_power)
            param_dict = {
                'cw_mw_frequency': self.cw_mw_frequency,
                'cw_mw_power': self.cw_mw_power}
            self.sigParameterUpdated.emit(param_dict)
            if mode != 'cw':
                self.log.error('Switching to CW microwave output mode failed.')
            else:
                err_code = self._mw_device.cw_on()
                if err_code < 0:
                    self.log.error('Activation of microwave output failed.')

        mode, is_running = self._mw_device.get_status()
        self.sigOutputStateUpdated.emit(mode, is_running)
        return mode, is_running

    def mw_sweep_on(self):
        """
        Switching on the mw source in list/sweep mode.

        @return str, bool: active mode ['cw', 'list', 'sweep'], is_running
        """

        limits = self.get_hw_constraints()
        param_dict = {}

        if self.mw_scanmode == MicrowaveMode.LIST:
            if np.abs(self.mw_stop - self.mw_start) / \
                    self.mw_step >= limits.list_maxentries:
                self.log.warning(
                    'Number of frequency steps too large for microwave device. '
                    'Lowering resolution to fit the maximum length.')
                self.mw_step = np.abs(
                    self.mw_stop - self.mw_start) / (limits.list_maxentries - 1)
                self.sigParameterUpdated.emit({'mw_step': self.mw_step})

            # adjust the end frequency in order to have an integer multiple of step size
            # The master module (i.e. GUI) will be notified about the changed
            # end frequency
            num_steps = int(
                np.rint(
                    (self.mw_stop -
                     self.mw_start) /
                    self.mw_step))
            end_freq = self.mw_start + num_steps * self.mw_step
            freq_list = np.linspace(self.mw_start, end_freq, num_steps + 1)
            freq_list, self.sweep_mw_power, mode = self._mw_device.set_list(
                freq_list, self.sweep_mw_power)
            self.mw_start = freq_list[0]
            self.mw_stop = freq_list[-1]
            self.mw_step = (self.mw_stop - self.mw_start) / \
                (len(freq_list) - 1)

            param_dict = {
                'mw_start': self.mw_start,
                'mw_stop': self.mw_stop,
                'mw_step': self.mw_step,
                'sweep_mw_power': self.sweep_mw_power}

        elif self.mw_scanmode == MicrowaveMode.SWEEP:
            if np.abs(self.mw_stop - self.mw_start) / \
                    self.mw_step >= limits.sweep_maxentries:
                self.log.warning(
                    'Number of frequency steps too large for microwave device. '
                    'Lowering resolution to fit the maximum length.')
                self.mw_step = np.abs(
                    self.mw_stop - self.mw_start) / (limits.list_maxentries - 1)
                self.sigParameterUpdated.emit({'mw_step': self.mw_step})

            sweep_return = self._mw_device.set_sweep(
                self.mw_start, self.mw_stop, self.mw_step, self.sweep_mw_power)
            self.mw_start, self.mw_stop, self.mw_step, self.sweep_mw_power, mode = sweep_return

            param_dict = {
                'mw_start': self.mw_start,
                'mw_stop': self.mw_stop,
                'mw_step': self.mw_step,
                'sweep_mw_power': self.sweep_mw_power}

        else:
            self.log.error(
                'Scanmode not supported. Please select SWEEP or LIST.')

        self.sigParameterUpdated.emit(param_dict)

        if mode != 'list' and mode != 'sweep':
            self.log.error(
                'Switching to list/sweep microwave output mode failed.')
        elif self.mw_scanmode == MicrowaveMode.SWEEP:
            err_code = self._mw_device.sweep_on()
            if err_code < 0:
                self.log.error('Activation of microwave output failed.')
        else:
            err_code = self._mw_device.list_on()
            if err_code < 0:
                self.log.error('Activation of microwave output failed.')

        mode, is_running = self._mw_device.get_status()
        self.sigOutputStateUpdated.emit(mode, is_running)
        return mode, is_running

    def reset_sweep(self):
        """
        Resets the list/sweep mode of the microwave source to the first frequency step.
        """
        if self.mw_scanmode == MicrowaveMode.SWEEP:
            self._mw_device.reset_sweeppos()
        elif self.mw_scanmode == MicrowaveMode.LIST:
            self._mw_device.reset_listpos()
        return

    def mw_off(self):
        """ Switching off the MW source.

        @return str, bool: active mode ['cw', 'list', 'sweep'], is_running
        """
        error_code = self._mw_device.off()
        if error_code < 0:
            self.log.error('Switching off microwave source failed.')

        mode, is_running = self._mw_device.get_status()
        self.sigOutputStateUpdated.emit(mode, is_running)
        return mode, is_running

    def set_exp_time(self, exp, cur_res_index):
        '''Exp time in mseconds in set from the GUI and updated for the cam class as well as in the variable
        in the ODMR logic class. The clock frequency variable is updated as well since it depends on the exp
        time of the camera.

        @param int: exp ~ 1ms - 10000ms
        '''
        self._camera.set_exposure_resolution(cur_res_index)
        self._camera.set_exposure(exp)
        self.exp_time = exp
        exp_res_dict = {0: 1000., 1: 1000000.}
        self.clock_frequency = 1. / ((exp / exp_res_dict[cur_res_index]) + 0.0)

    def _start_odmr_counter(self):
        """
        Starting the ODMR counter and set up the clock for it.
        No counter is actually set up instead the clock is set up and the camera trigger mode is set
        to edge trigger for every image in the sequence.

        @return int: error code (0:OK, -1:error)
        """

        clock_status = self._odmr_counter.set_up_odmr_clock(
            clock_frequency=self.clock_frequency, no_x=self.odmr_plot_x.size)
        # Seting exposure mode on camera via logic "Ext Trig Internal"
        # self._camera.set_trigger_seq("Ext Trig Internal")
        self._camera.set_trigger_seq("Edge Trigger")
        if clock_status < 0:
            return -1
        # Try not running the counter since we dont need it
        # counter_status = self._odmr_counter.set_up_odmr()
        # if counter_status < 0:
        #     self._odmr_counter.close_odmr_clock()
        #     return -1

        return 0

    def _stop_odmr_counter(self):
        """
        Stopping the ODMR counter.

        @return int: error code (0:OK, -1:error)
        """
        # Added a line in DAQmx hardware to wait until task is done when closing so as to allow camera to
        # complete the acquisition
        ret_val1 = self._odmr_counter.close_odmr()
        if ret_val1 != 0:
            self.log.error('ODMR counter could not be stopped!')
        # ret_val2 = self._odmr_counter.close_odmr_clock()
        # if ret_val2 != 0:
        #     self.log.error('ODMR clock could not be stopped!')
        
        # Check with a bitwise or:
        return ret_val1

    def start_odmr_scan(self):
        """ Starting an ODMR scan.

        @return int: error code (0:OK, -1:error)
        """
        with self.threadlock:
            if self.module_state() == 'locked':
                self.log.error(
                    'Can not start ODMR scan. Logic is already locked.')
                return -1

            self.set_trigger(self.mw_trigger_pol, self.clock_frequency)

            self.module_state.lock()
            self._clearOdmrData = False
            self.stopRequested = False
            self.fc.clear_result()

            self.elapsed_sweeps = 0
            self.elapsed_time = 0.0
            self._startTime = time.time()
            self.sigOdmrElapsedTimeUpdated.emit(
                self.elapsed_time, self.elapsed_sweeps)

            odmr_status = self._start_odmr_counter()
            if odmr_status < 0:
                mode, is_running = self._mw_device.get_status()
                self.sigOutputStateUpdated.emit(mode, is_running)
                self.module_state.unlock()
                return -1

            mode, is_running = self.mw_sweep_on()
            if not is_running:
                self._stop_odmr_counter()
                self.module_state.unlock()
                return -1

            self._initialize_odmr_plots()
            # initialize raw_data array
            estimated_number_of_lines = self.run_time * \
                self.clock_frequency / self.odmr_plot_x.size
            estimated_number_of_lines = int(
                1.5 * estimated_number_of_lines)  # Safety
            if estimated_number_of_lines < self.number_of_lines:
                estimated_number_of_lines = self.number_of_lines
            self.log.debug('Estimated number of raw data lines: {0:d}'
                           ''.format(estimated_number_of_lines))
            self.odmr_raw_data = np.zeros(
                [estimated_number_of_lines,
                 len(self._odmr_counter.get_odmr_channels()),
                 self.odmr_plot_x.size]
            )
            # Sweep images are set to zero at every new scan
            self.sweep_images = np.zeros(
                (self.odmr_plot_x.size, *np.flip(self._camera.get_size(), axis=0))
            )
            self.sigNextLine.emit()
            return 0

    def continue_odmr_scan(self):
        """ Continue ODMR scan.

        @return int: error code (0:OK, -1:error)
        """
        with self.threadlock:
            if self.module_state() == 'locked':
                self.log.error(
                    'Can not start ODMR scan. Logic is already locked.')
                return -1

            self.set_trigger(self.mw_trigger_pol, self.clock_frequency)

            self.module_state.lock()
            self.stopRequested = False
            self.fc.clear_result()

            self._startTime = time.time() - self.elapsed_time
            self.sigOdmrElapsedTimeUpdated.emit(
                self.elapsed_time, self.elapsed_sweeps)

            odmr_status = self._start_odmr_counter()
            if odmr_status < 0:
                mode, is_running = self._mw_device.get_status()
                self.sigOutputStateUpdated.emit(mode, is_running)
                self.module_state.unlock()
                return -1

            mode, is_running = self.mw_sweep_on()
            if not is_running:
                self._stop_odmr_counter()
                self.module_state.unlock()
                return -1

            self.sigNextLine.emit()
            return 0

    def stop_odmr_scan(self):
        """ Stop the ODMR scan.

        @return int: error code (0:OK, -1:error)
        """
        with self.threadlock:
            if self.module_state() == 'locked':
                self.stopRequested = True
        return 0

    def clear_odmr_data(self):
        """¨Set the option to clear the curret ODMR data.

        The clear operation has to be performed within the method
        _scan_odmr_line. This method just sets the flag for that. """
        with self.threadlock:
            if self.module_state() == 'locked':
                self._clearOdmrData = True
        return

    def _scan_odmr_line(self):
        """ Scans one line in ODMR

        (from mw_start to mw_stop in steps of mw_step)
        """
        with self.threadlock:
            # If the odmr measurement is not running do nothing
            if self.module_state() != 'locked':
                return

            # Stop measurement if stop has been requested
            if self.stopRequested:
                self.stopRequested = False
                self.mw_off()
                self._stop_odmr_counter()
                self.module_state.unlock()
                self._camera.set_trigger_seq("Internal Trigger")
                return

            # if during the scan a clearing of the ODMR data is needed:
            if self._clearOdmrData:
                self.elapsed_sweeps = 0
                self.sweep_images = np.zeros(
                    (self.odmr_plot_x.size, *np.flip(self._camera.get_size(), axis=0))
                )
                self._startTime = time.time()

            # reset position so every line starts from the same frequency
            self.reset_sweep()

            # Acquire count data
            # The trigger sequcne is started below after the specified delay.
            # The camera is also set to acquire and the code only goes below that line after
            # all the images in the sequence has been acquired. The triggers
            # are all stopped after that.
            error, new_counts = self._odmr_counter.count_odmr(
                length=self.odmr_plot_x.size)
            self._camera.start_trigger_seq(self.odmr_plot_x.size * 2)
            # self._odmr_counter.stop_tasks()
            # The collected frames are then acquired by the logic here from cam logic Should consider memory issues
            # for the future.
            frames = self._camera.get_last_image().astype('float')
            # The reference images from switch off time should be subtracted as below. For dummy measurements
            # so as to not be just left with noise we can do a dummy
            # subtraction.
            new_counts = (frames[0::2] - frames[1::2]) / (frames[1::2] + frames[0::2]) * 100
            # Remove for actual mesurements
            # new_counts = frames[1::2] - np.full_like(frames[0::2], 1)
            # The sweep images are added up and the new counts are taken as the mean of the image which is what
            # ends up being plotted as odmr_plot_y
            self.sweep_images += new_counts
            new_counts = np.mean(new_counts, axis=(1, 2))

            if error==-1:
                self.stopRequested = True
                self.sigNextLine.emit()
                return

            # Add new count data to raw_data array and append if array is too
            # small
            if self._clearOdmrData:
                self.odmr_raw_data[:, :, :] = 0
                self._clearOdmrData = False
            if self.elapsed_sweeps == (self.odmr_raw_data.shape[0] - 1):
                expanded_array = np.zeros(self.odmr_raw_data.shape)
                self.odmr_raw_data = np.concatenate(
                    (self.odmr_raw_data, expanded_array), axis=0)
                self.log.warning(
                    'raw data array in ODMRLogic was not big enough for the entire '
                    'measurement. Array will be expanded.\nOld array shape was '
                    '({0:d}, {1:d}), new shape is ({2:d}, {3:d}).'
                    ''.format(
                        self.odmr_raw_data.shape[0] -
                        self.number_of_lines,
                        self.odmr_raw_data.shape[1],
                        self.odmr_raw_data.shape[0],
                        self.odmr_raw_data.shape[1]))

            # shift data in the array "up" and add new data at the "bottom"
            self.odmr_raw_data = np.roll(self.odmr_raw_data, 1, axis=0)

            self.odmr_raw_data[0] = new_counts

            # Add new count data to mean signal
            if self._clearOdmrData:
                self.odmr_plot_y[:, :] = 0

            if self.lines_to_average <= 0:
                self.odmr_plot_y = np.mean(
                    self.odmr_raw_data[:max(1, self.elapsed_sweeps), :, :],
                    axis=0,
                    dtype=np.float64
                )
            else:
                self.odmr_plot_y = np.mean(
                    self.odmr_raw_data[:max(1, min(self.lines_to_average, self.elapsed_sweeps)), :, :],
                    axis=0,
                    dtype=np.float64
                )

            # Set plot slice of matrix
            self.odmr_plot_xy = self.odmr_raw_data[:self.number_of_lines, :, :]

            # Update elapsed time/sweeps
            self.elapsed_sweeps += 1
            self.elapsed_time = time.time() - self._startTime
            if self.elapsed_time >= self.run_time:
                self.stopRequested = True
            # Fire update signals
            self.sigOdmrElapsedTimeUpdated.emit(
                self.elapsed_time, self.elapsed_sweeps)
            self.sigOdmrPlotsUpdated.emit(
                self.odmr_plot_x, self.odmr_plot_y, self.odmr_plot_xy)
            self.sigNextLine.emit()
            return

    def get_odmr_channels(self):
        return ['Prime95B']

    def get_hw_constraints(self):
        """ Return the names of all ocnfigured fit functions.
        @return object: Hardware constraints object
        """
        constraints = self._mw_device.get_limits()
        return constraints

    def get_fit_functions(self):
        """ Return the hardware constraints/limits
        @return list(str): list of fit function names
        """
        return list(self.fc.fit_list)

    def print_coords(self, event, x, y, flags, param):
        '''The coords for finding the pixel spectrum are determined here from the mouse click callback.
        '''
        if (event == cv2.EVENT_LBUTTONDOWN):
            self.coord = (y, x)
            cv2.destroyAllWindows()

    def do_pixel_spectrum(self, frames):
        '''This is called from the fit depending on which button is clicked in the GUI. It displays an image
        which is the sum of all the sweep images divided by number of images so as to make sure all the features
        are seen.
        The coords of selected point are then found by mouse callback and the spectrum made into the new odmr_plot_y
        data as seen in do_fit()
        '''
        # So as to have a smaller images. Hence the times 2 in the print_coords
        # function to get actual coords.
        frame = np.sum(frames, axis=(0)) / np.shape(frames)[0]
        frame = frame.astype(np.uint16)
        # Needed because cv2 can handle only uint8(?) images.
        frame = cv2.normalize(
            frame,
            dst=None,
            alpha=0,
            beta=65535,
            norm_type=cv2.NORM_MINMAX)
        cv2.imshow(f'Sweep Image : {np.shape(frames)[0]}', frame)
        cv2.setMouseCallback(
            f'Sweep Image : {np.shape(frames)[0]}',
            self.print_coords)
        cv2.waitKey(0)

    def do_fit(
            self,
            fit_function=None,
            x_data=None,
            y_data=None,
            channel_index=0,
            pixel_fit=False):
        """
        Execute the currently configured fit on the measurement data. Optionally on passed data
        """
        # To enable default odmr_plot_y if no pixel is clicke and imshow is
        # just closed. Good for preview.
        self.coord = None
        if pixel_fit and np.count_nonzero(self.sweep_images) != 0:
            frames = self.sweep_images / self.elapsed_sweeps
            frames1 = np.zeros((np.shape(frames)[0], 600, 600))
            frames1[:] = [
                cv2.resize(
                    cv2.flip(frame, 0), (600, 600), interpolation=cv2.INTER_AREA) for frame in frames]
            frames = frames1
            self.do_pixel_spectrum(frames)
            # If no mouse click happens the odmr_plot_y data is not updated and stays the same.
            # This ends up allowing us to have a preview of the entire sweep as
            # well.

            if self.coord is not None:
                x_data = self.odmr_plot_x
                y_data = np.zeros(
                    [len(self.get_odmr_channels()), self.odmr_plot_x.size])
                y_data[0] = frames[:, self.coord[0], self.coord[1]]
                self.sigOdmrPlotsUpdated.emit(
                    x_data, y_data, self.odmr_plot_xy)
                y_data = y_data[0]
        # This enables us to reset to actual odmr_plot_y values after looking
        # at the pixel spectrum
        if not pixel_fit:
            self.sigOdmrPlotsUpdated.emit(
                self.odmr_plot_x, self.odmr_plot_y, self.odmr_plot_xy)

        if (x_data is None) or (y_data is None):
            x_data = self.odmr_plot_x
            y_data = self.odmr_plot_y[channel_index]

        if fit_function is not None and isinstance(fit_function, str):
            if fit_function in self.get_fit_functions():
                self.fc.set_current_fit(fit_function)
            else:
                self.fc.set_current_fit('No Fit')
                if fit_function != 'No Fit':
                    self.log.warning(
                        'Fit function "{0}" not available in ODMRLogic fit container.'
                        ''.format(fit_function))

        self.odmr_fit_x, self.odmr_fit_y, result = self.fc.do_fit(
            x_data, y_data)

        if result is None:
            result_str_dict = {}
        else:
            result_str_dict = result.result_str_dict
        self.sigOdmrFitUpdated.emit(
            self.odmr_fit_x,
            self.odmr_fit_y,
            result_str_dict,
            self.fc.current_fit)
        return

    def save_odmr_data(
            self,
            tag=None,
            colorscale_range=None,
            percentile_range=None,
            save_stack=False):
        """ Saves the current ODMR data to a file."""
        timestamp = datetime.datetime.now()

        if tag is None:
            tag = ''
        for nch, channel in enumerate(self.get_odmr_channels()):
            # two paths to save the raw data and the odmr scan data.
            filepath = self._save_logic.get_path_for_module(module_name='ODMR')
            filepath2 = self._save_logic.get_path_for_module(
                module_name='ODMR')

            if len(tag) > 0:
                filelabel = '{0}_ODMR_data_ch{1}'.format(tag, nch)
                filelabel2 = '{0}_ODMR_data_ch{1}_raw'.format(tag, nch)
            else:
                filelabel = 'ODMR_data_ch{0}'.format(nch)
                filelabel2 = 'ODMR_data_ch{0}_raw'.format(nch)

            # prepare the data in a dict or in an OrderedDict:
            data = OrderedDict()
            data2 = OrderedDict()
            data['frequency (Hz)'] = self.odmr_plot_x
            data['Arb. counts'] = self.odmr_plot_y[nch]
            data2['Arb. counts'] = self.odmr_raw_data[:self.elapsed_sweeps, nch, :]

            parameters = OrderedDict()
            parameters['Microwave CW Power (dBm)'] = self.cw_mw_power
            parameters['Microwave Sweep Power (dBm)'] = self.sweep_mw_power
            parameters['Run Time (s)'] = self.run_time
            parameters['Number of frequency sweeps (#)'] = self.elapsed_sweeps
            parameters['Start Frequency (Hz)'] = self.mw_start
            parameters['Stop Frequency (Hz)'] = self.mw_stop
            parameters['Step size (Hz)'] = self.mw_step
            parameters['Clock Frequency (Hz)'] = self.clock_frequency
            # Exposure time is added as well to the save parameters.
            parameters['Exposure time (ms)'] = self.exp_time
            parameters['Channel'] = '{0}: {1}'.format(nch, channel)
            if self.fc.current_fit != 'No Fit':
                parameters['Fit function'] = self.fc.current_fit

            # add all fit parameter to the saved data:
            for name, param in self.fc.current_fit_param.items():
                parameters[name] = str(param)

            fig = self.draw_figure(
                nch,
                cbar_range=colorscale_range,
                percentile_range=percentile_range)

            self._save_logic.save_data(data,
                                       filepath=filepath,
                                       parameters=parameters,
                                       filelabel=filelabel,
                                       fmt='%.6e',
                                       delimiter='\t',
                                       timestamp=timestamp,
                                       plotfig=fig)

            self._save_logic.save_data(data2,
                                       filepath=filepath2,
                                       parameters=parameters,
                                       filelabel=filelabel2,
                                       fmt='%.6e',
                                       delimiter='\t',
                                       timestamp=timestamp)
            # The files is saved as a compressed .npz file which can be looaed by np.load('.npz')['sweep_images']
            # Provides best possible compression for array storage. Saved with almost the same timestamp
            # as used in save_logic
            if save_stack:
                loc = filepath + '/' + \
                    timestamp.strftime("%Y%m%d-%H%M-%S") + '_' + filelabel + '_sweep'
                np.savez_compressed(
                loc,
                sweep_images=(self.sweep_images /
                                self.elapsed_sweeps))
            self.log.info('ODMR data saved to:\n{0}'.format(filepath))
        return

    def draw_figure(
            self,
            channel_number,
            cbar_range=None,
            percentile_range=None):
        """ Draw the summary figure to save with the data.

        @param: list cbar_range: (optional) [color_scale_min, color_scale_max].
                                 If not supplied then a default of data_min to data_max
                                 will be used.

        @param: list percentile_range: (optional) Percentile range of the chosen cbar_range.

        @return: fig fig: a matplotlib figure object to be saved to file.
        """
        freq_data = self.odmr_plot_x
        count_data = self.odmr_plot_y[channel_number]
        fit_freq_vals = self.odmr_fit_x
        fit_count_vals = self.odmr_fit_y
        matrix_data = self.odmr_plot_xy[:, channel_number]

        # If no colorbar range was given, take full range of data
        if cbar_range is None:
            cbar_range = np.array([np.min(matrix_data), np.max(matrix_data)])
        else:
            cbar_range = np.array(cbar_range)

        prefix = ['', 'k', 'M', 'G', 'T']
        prefix_index = 0

        # Rescale counts data with SI prefix
        while np.max(count_data) > 1000:
            count_data = count_data / 1000
            fit_count_vals = fit_count_vals / 1000
            prefix_index = prefix_index + 1

        counts_prefix = prefix[prefix_index]

        # Rescale frequency data with SI prefix
        prefix_index = 0

        while np.max(freq_data) > 1000:
            freq_data = freq_data / 1000
            fit_freq_vals = fit_freq_vals / 1000
            prefix_index = prefix_index + 1

        mw_prefix = prefix[prefix_index]

        # Rescale matrix counts data with SI prefix
        prefix_index = 0

        while np.max(matrix_data) > 1000:
            matrix_data = matrix_data / 1000
            cbar_range = cbar_range / 1000
            prefix_index = prefix_index + 1

        cbar_prefix = prefix[prefix_index]

        # Use qudi style
        plt.style.use(self._save_logic.mpl_qd_style)

        # Create figure
        fig, (ax_mean, ax_matrix) = plt.subplots(nrows=2, ncols=1)

        ax_mean.plot(freq_data, count_data, linestyle=':', linewidth=0.5)

        # Do not include fit curve if there is no fit calculated.
        if max(fit_count_vals) > 0:
            ax_mean.plot(fit_freq_vals, fit_count_vals, marker='None')

        ax_mean.set_ylabel('Arb. (' + counts_prefix + 'units)')
        ax_mean.set_xlim(np.min(freq_data), np.max(freq_data))

        matrixplot = ax_matrix.imshow(
            matrix_data,
            cmap=plt.get_cmap('inferno'),  # reference the right place in qd
            origin='lower',
            vmin=cbar_range[0],
            vmax=cbar_range[1],
            extent=[np.min(freq_data),
                    np.max(freq_data),
                    0,
                    self.number_of_lines
                    ],
            aspect='auto',
            interpolation='nearest')

        ax_matrix.set_xlabel('Frequency (' + mw_prefix + 'Hz)')
        ax_matrix.set_ylabel('Scan #')

        # Adjust subplots to make room for colorbar
        fig.subplots_adjust(right=0.8)

        # Add colorbar axis to figure
        cbar_ax = fig.add_axes([0.85, 0.15, 0.02, 0.7])

        # Draw colorbar
        cbar = fig.colorbar(matrixplot, cax=cbar_ax)
        cbar.set_label('Arb. (' + cbar_prefix + 'units)')

        # remove ticks from colorbar for cleaner image
        cbar.ax.tick_params(which=u'both', length=0)

        # If we have percentile information, draw that to the figure
        if percentile_range is not None:
            cbar.ax.annotate(str(percentile_range[0]),
                             xy=(-0.3, 0.0),
                             xycoords='axes fraction',
                             horizontalalignment='right',
                             verticalalignment='center',
                             rotation=90
                             )
            cbar.ax.annotate(str(percentile_range[1]),
                             xy=(-0.3, 1.0),
                             xycoords='axes fraction',
                             horizontalalignment='right',
                             verticalalignment='center',
                             rotation=90
                             )
            cbar.ax.annotate('(percentile)',
                             xy=(-0.3, 0.5),
                             xycoords='axes fraction',
                             horizontalalignment='right',
                             verticalalignment='center',
                             rotation=90
                             )

        return fig

    def perform_odmr_measurement(
            self,
            freq_start,
            freq_step,
            freq_stop,
            power,
            channel,
            runtime,
            fit_function='No Fit',
            save_after_meas=True,
            name_tag=''):
        """ An independant method, which can be called by a task with the proper input values
            to perform an odmr measurement.

        @return
        """
        timeout = 30
        start_time = time.time()
        while self.module_state() != 'idle':
            time.sleep(0.5)
            timeout -= (time.time() - start_time)
            if timeout <= 0:
                self.log.error(
                    'perform_odmr_measurement failed. Logic module was still locked '
                    'and 30 sec timeout has been reached.')
                return tuple()

        # set all relevant parameter:
        self.set_sweep_parameters(freq_start, freq_stop, freq_step, power)
        self.set_runtime(runtime)

        # start the scan
        self.start_odmr_scan()

        # wait until the scan has started
        while self.module_state() != 'locked':
            time.sleep(1)
        # wait until the scan has finished
        while self.module_state() == 'locked':
            time.sleep(1)

        # Perform fit if requested
        if fit_function != 'No Fit':
            self.do_fit(fit_function, channel_index=channel)
            fit_params = self.fc.current_fit_param
        else:
            fit_params = None

        # Save data if requested
        if save_after_meas:
            self.save_odmr_data(tag=name_tag)

        return self.odmr_plot_x, self.odmr_plot_y, fit_params
Exemple #24
0
class SpectrometerLogic(GenericLogic):
    """Obtain spectra.
    """
    # Declare connectors
    spectrometer = Connector(interface='SpectrometerInterfaceEx')
    savelogic = Connector(interface='SaveLogic')

    # Signals for GUI
    status_changed = QtCore.Signal(str)
    data_updated = QtCore.Signal(dict)

    # Internal signals
    _start_acquisition = QtCore.Signal()
    _start_save = QtCore.Signal()
    _set_param = QtCore.Signal(list)
    _update_all = QtCore.Signal()

    def on_activate(self):
        self._spectrometer = self.spectrometer()
        self._save_logic = self.savelogic()

        self.params = self._spectrometer.get_supported_params()

        self.spectrum_data = np.zeros((2, 1))

        self.connections = (self._start_acquisition.connect(
            self.acquire_spectrum),
                            self._start_save.connect(self.save_spectrum),
                            self._set_param.connect(self.set_parameters),
                            self._update_all.connect(self._update_params),
                            self.status_changed.connect(self._lock_module))

    def on_deactivate(self):
        for conn in self.connections:
            QtCore.QObject.disconnect(conn)

    ###################
    # Blocking methods
    ###################

    @QtCore.Slot(list)
    def set_parameters(self, param):
        try:
            self.status_changed.emit('busy')
            self._spectrometer.set_parameter(*param)
            self.data_updated.emit({param[0]: param[1]})
        finally:
            self.status_changed.emit('idle')

    @QtCore.Slot()
    def acquire_spectrum(self):
        try:
            self.status_changed.emit('busy')
            spect = self._spectrometer.acquire_spectrum()

            self.data_updated.emit({'spectrum': spect})
            self.spectrum_data = spect
            return spect

        finally:
            self.status_changed.emit('idle')

    @QtCore.Slot()
    def save_spectrum(self):
        self.status_changed.emit('saving')
        try:
            filepath = self._save_logic.get_path_for_module(
                module_name='Spectrometer')

            data = OrderedDict()
            data['Counts'] = self.spectrum_data[0, :]
            data['Wavelength (m)'] = self.spectrum_data[1, :]

            plot = self._create_figure()

            self._save_logic.save_data(data,
                                       filepath=filepath,
                                       filelabel='spectrum',
                                       fmt=['%.6e', '%.6e'],
                                       plotfig=plot)

            self.log.debug('Spectrum data saved to:\n{0}'.format(filepath))
        finally:
            self.status_changed.emit('idle')

    def get_limits(self, param):
        return [self.params[param]['min'], self.params[param]['max']]

    ##############################
    # Non-blocking methods for GUI
    ##############################

    def acquire_spectrum_async(self):
        self._start_acquisition.emit()

    def save_spectrum_async(self):
        self._start_save.emit()

    def set_exposure(self, exposure):
        self._set_param.emit(['exposure_time', exposure])

    def set_wavelength(self, wavelength):
        self._set_param.emit(['center_wavelength', wavelength])

    def update_params(self):
        self._update_all.emit()

    ###################
    # Internal methods
    ###################

    @QtCore.Slot()
    def _update_params(self):
        try:
            self.status_changed.emit('busy')
            param_dict = {}
            for param in self.params.keys():
                param_dict[param] = self._spectrometer.get_parameter(param)
            self.data_updated.emit(param_dict)
        finally:
            self.status_changed.emit('idle')

    @QtCore.Slot(str)
    def _lock_module(self, state):
        if state in ('busy', 'saving'):
            self.module_state.lock()
        else:
            self.module_state.unlock()

    def _create_figure(self):
        """ Creates matplotlib figure for saving
        """
        # Use qudi style
        plt.style.use(self._save_logic.mpl_qd_style)
        # Create figure
        fig, ax = plt.subplots()
        ax.set_xlabel('Wavelength (nm)')
        ax.set_ylabel('Intensity')

        ax.plot(self.spectrum_data[0, :] * 1e9, self.spectrum_data[1, :])

        return fig
Exemple #25
0
class TaskGui(GUIBase):
    """ A graphical interface to start, pause, stop and abort tasks.
    """

    # declare connectors
    tasklogic = Connector(interface='TaskRunner')

    sigRunTaskFromList = QtCore.Signal(QtCore.QModelIndex)
    sigPauseTaskFromList = QtCore.Signal(QtCore.QModelIndex)
    sigStopTaskFromList = QtCore.Signal(QtCore.QModelIndex)
    sigAbortTaskFromList = QtCore.Signal(QtCore.QModelIndex)

    def on_activate(self):
        """Create all UI objects and show the window.
        """
        self._mw = TaskMainWindow()
        self.restoreWindowPos(self._mw)
        self.logic = self.tasklogic()
        self._mw.taskTableView.setModel(self.logic.model)
        self._mw.taskTableView.clicked.connect(self.setRunToolState)
        self._mw.actionStart_Task.triggered.connect(self.manualStart)
        self._mw.actionPause_Task.triggered.connect(self.manualPause)
        self._mw.actionStop_Task.triggered.connect(self.manualStop)
        self._mw.actionAbort_Task.triggered.connect(self.manualAbort)
        self.sigRunTaskFromList.connect(self.logic.startTaskByIndex)
        self.sigPauseTaskFromList.connect(self.logic.pauseTaskByIndex)
        self.sigStopTaskFromList.connect(self.logic.stopTaskByIndex)
        self.sigAbortTaskFromList.connect(self.logic.abortTaskByIndex,
                                          QtCore.Qt.DirectConnection)
        # using a direct connection here enables modification of the aborted class attribute in generic_task even
        # while the runTaskStep method runs.
        self.logic.model.dataChanged.connect(
            lambda i1, i2: self.setRunToolState(None, i1))
        self.show()

    def show(self):
        """Make sure that the window is visible and at the top.
        """
        self._mw.show()

    def on_deactivate(self):
        """ Hide window and stop ipython console.
        """
        self.saveWindowPos(self._mw)
        self._mw.close()

    def manualStart(self):
        selected = self._mw.taskTableView.selectedIndexes()
        if len(selected) >= 1:
            self.sigRunTaskFromList.emit(selected[0])

    def manualPause(self):
        selected = self._mw.taskTableView.selectedIndexes()
        if len(selected) >= 1:
            self.sigPauseTaskFromList.emit(selected[0])

    def manualStop(self):
        selected = self._mw.taskTableView.selectedIndexes()
        if len(selected) >= 1:
            self.sigStopTaskFromList.emit(selected[0])

    def manualAbort(self):
        selected = self._mw.taskTableView.selectedIndexes()
        if len(selected) >= 1:
            self.sigAbortTaskFromList.emit(selected[0])

    def setRunToolState(self, index, index2=None):
        selected = self._mw.taskTableView.selectedIndexes()
        try:
            if index2 is not None and selected[0].row() != index2.row():
                return
        except:
            return

        if len(selected) >= 1:
            state = self.logic.model.storage[
                selected[0].row()]['object'].current
            if state == 'stopped':
                self._mw.actionStart_Task.setEnabled(True)
                self._mw.actionStop_Task.setEnabled(False)
                self._mw.actionPause_Task.setEnabled(False)
            elif state == 'running':
                self._mw.actionStart_Task.setEnabled(False)
                self._mw.actionStop_Task.setEnabled(True)
                self._mw.actionPause_Task.setEnabled(True)
            elif state == 'paused':
                self._mw.actionStart_Task.setEnabled(True)
                self._mw.actionStop_Task.setEnabled(False)
                self._mw.actionPause_Task.setEnabled(True)
            else:
                self._mw.actionStart_Task.setEnabled(False)
                self._mw.actionStop_Task.setEnabled(False)
                self._mw.actionPause_Task.setEnabled(False)
class OptimizerLogic(GenericLogic):
    """This is the Logic class for optimizing scanner position on bright features.
    """

    # declare connectors
    confocalscanner1 = Connector(interface='ConfocalScannerInterface')
    fitlogic = Connector(interface='FitLogic')

    # declare status vars
    _clock_frequency = StatusVar('clock_frequency', 50)
    return_slowness = StatusVar(default=20)
    refocus_XY_size = StatusVar('xy_size', 0.6e-6)
    optimizer_XY_res = StatusVar('xy_resolution', 10)
    refocus_Z_size = StatusVar('z_size', 2e-6)
    optimizer_Z_res = StatusVar('z_resolution', 30)
    hw_settle_time = StatusVar('settle_time', 0.1)
    optimization_sequence = StatusVar(default=['XY', 'Z'])
    do_surface_subtraction = StatusVar('surface_subtraction', False)
    surface_subtr_scan_offset = StatusVar('surface_subtraction_offset', 1e-6)
    opt_channel = StatusVar('optimization_channel', 0)

    # "private" signals to keep track of activities here in the optimizer logic
    _sigScanNextXyLine = QtCore.Signal()
    _sigScanZLine = QtCore.Signal()
    _sigCompletedXyOptimizerScan = QtCore.Signal()
    _sigDoNextOptimizationStep = QtCore.Signal()
    _sigFinishedAllOptimizationSteps = QtCore.Signal()

    # public signals
    sigImageUpdated = QtCore.Signal()
    sigRefocusStarted = QtCore.Signal(str)
    sigRefocusXySizeChanged = QtCore.Signal()
    sigRefocusZSizeChanged = QtCore.Signal()
    sigRefocusFinished = QtCore.Signal(str, list)
    sigClockFrequencyChanged = QtCore.Signal(int)
    sigPositionChanged = QtCore.Signal(float, float, float)

    def __init__(self, config, **kwargs):
        super().__init__(config=config, **kwargs)

        # locking for thread safety
        self.threadlock = Mutex()

        self.stopRequested = False
        self.is_crosshair = True

        # Keep track of who called the refocus
        self._caller_tag = ''

    def on_activate(self):
        """ Initialisation performed during activation of the module.

        @return int: error code (0:OK, -1:error)
        """
        self._scanning_device = self.confocalscanner1()
        self._fit_logic = self.fitlogic()

        # Reads in the maximal scanning range. The unit of that scan range is micrometer!
        self.x_range = self._scanning_device.get_position_range()[0]
        self.y_range = self._scanning_device.get_position_range()[1]
        self.z_range = self._scanning_device.get_position_range()[2]

        self._initial_pos_x = 0.
        self._initial_pos_y = 0.
        self._initial_pos_z = 0.
        self.optim_pos_x = self._initial_pos_x
        self.optim_pos_y = self._initial_pos_y
        self.optim_pos_z = self._initial_pos_z
        self.optim_sigma_x = 0.
        self.optim_sigma_y = 0.
        self.optim_sigma_z = 0.

        self._max_offset = 3.

        # Sets the current position to the center of the maximal scanning range
        self._current_x = (self.x_range[0] + self.x_range[1]) / 2
        self._current_y = (self.y_range[0] + self.y_range[1]) / 2
        self._current_z = (self.z_range[0] + self.z_range[1]) / 2
        self._current_a = 0.0

        ###########################
        # Fit Params and Settings #
        model, params = self._fit_logic.make_gaussianlinearoffset_model()
        self.z_params = params
        self.use_custom_params = {
            name: False
            for name, param in params.items()
        }

        # Initialization of internal counter for scanning
        self._xy_scan_line_count = 0

        # Initialization of optimization sequence step counter
        self._optimization_step = 0

        # Sets connections between signals and functions
        self._sigScanNextXyLine.connect(self._refocus_xy_line,
                                        QtCore.Qt.QueuedConnection)
        self._sigScanZLine.connect(self.do_z_optimization,
                                   QtCore.Qt.QueuedConnection)
        self._sigCompletedXyOptimizerScan.connect(
            self._set_optimized_xy_from_fit, QtCore.Qt.QueuedConnection)

        self._sigDoNextOptimizationStep.connect(
            self._do_next_optimization_step, QtCore.Qt.QueuedConnection)
        self._sigFinishedAllOptimizationSteps.connect(self.finish_refocus)
        self._initialize_xy_refocus_image()
        self._initialize_z_refocus_image()
        return 0

    def on_deactivate(self):
        """ Reverse steps of activation

        @return int: error code (0:OK, -1:error)
        """
        return 0

    def check_optimization_sequence(self):
        """ Check the sequence of scan events for the optimization.
        """

        # Check the supplied optimization sequence only contains 'XY' and 'Z'
        if len(set(self.optimization_sequence).difference({'XY', 'Z'})) > 0:
            self.log.error(
                'Requested optimization sequence contains unknown steps. Please provide '
                'a sequence containing only \'XY\' and \'Z\' strings. '
                'The default [\'XY\', \'Z\'] will be used.')
            self.optimization_sequence = ['XY', 'Z']

    def get_scanner_count_channels(self):
        """ Get lis of counting channels from scanning device.
          @return list(str): names of counter channels
        """
        return self._scanning_device.get_scanner_count_channels()

    def set_clock_frequency(self, clock_frequency):
        """Sets the frequency of the clock

        @param int clock_frequency: desired frequency of the clock

        @return int: error code (0:OK, -1:error)
        """
        # checks if scanner is still running
        if self.module_state() == 'locked':
            return -1
        else:
            self._clock_frequency = int(clock_frequency)
        self.sigClockFrequencyChanged.emit(self._clock_frequency)
        return 0

    def set_refocus_XY_size(self, size):
        """ Set the number of pixels in the refocus image for X and Y directions

            @param int size: XY image size in pixels
        """
        self.refocus_XY_size = size
        self.sigRefocusXySizeChanged.emit()

    def set_refocus_Z_size(self, size):
        """ Set the number of values for Z refocus

            @param int size: number of values for Z refocus
        """
        self.refocus_Z_size = size
        self.sigRefocusZSizeChanged.emit()

    def start_refocus(self,
                      initial_pos=None,
                      caller_tag='unknown',
                      tag='logic'):  # TODO: change the logic here****
        """ Starts the optimization scan around initial_pos

            @param list initial_pos: with the structure [float, float, float]
            @param str caller_tag:
            @param str tag:
        """
        # checking if refocus corresponding to crosshair or corresponding to initial_pos
        if isinstance(initial_pos, (np.ndarray, )) and initial_pos.size >= 3:
            self._initial_pos_x, self._initial_pos_y, self._initial_pos_z = initial_pos[
                0:3]
        elif isinstance(initial_pos, (list, tuple)) and len(initial_pos) >= 3:
            self._initial_pos_x, self._initial_pos_y, self._initial_pos_z = initial_pos[
                0:3]
        elif initial_pos is None:
            scpos = self._scanning_device.get_scanner_position()[0:3]
            self._initial_pos_x, self._initial_pos_y, self._initial_pos_z = scpos
        else:
            pass  # TODO: throw error

        # Keep track of where the start_refocus was initiated
        self._caller_tag = caller_tag

        # Set the optim_pos values to match the initial_pos values.
        # This means we can use optim_pos in subsequent steps and ensure
        # that we benefit from any completed optimization step.
        self.optim_pos_x = self._initial_pos_x
        self.optim_pos_y = self._initial_pos_y
        self.optim_pos_z = self._initial_pos_z
        self.optim_sigma_x = 0.
        self.optim_sigma_y = 0.
        self.optim_sigma_z = 0.
        #
        self._xy_scan_line_count = 0
        self._optimization_step = 0
        self.check_optimization_sequence()

        scanner_status = self.start_scanner()
        if scanner_status < 0:
            self.sigRefocusFinished.emit(
                self._caller_tag,
                [self.optim_pos_x, self.optim_pos_y, self.optim_pos_z, 0])
            return
        self.sigRefocusStarted.emit(tag)
        self._sigDoNextOptimizationStep.emit()

    def stop_refocus(self):
        """Stops refocus."""
        with self.threadlock:
            self.stopRequested = True

    def _initialize_xy_refocus_image(self):
        """Initialisation of the xy refocus image."""
        self._xy_scan_line_count = 0

        # Take optim pos as center of refocus image, to benefit from any previous
        # optimization steps that have occurred.
        x0 = self.optim_pos_x
        y0 = self.optim_pos_y

        # defining position intervals for refocushttp://www.spiegel.de/
        xmin = np.clip(x0 - 0.5 * self.refocus_XY_size, self.x_range[0],
                       self.x_range[1])
        xmax = np.clip(x0 + 0.5 * self.refocus_XY_size, self.x_range[0],
                       self.x_range[1])
        ymin = np.clip(y0 - 0.5 * self.refocus_XY_size, self.y_range[0],
                       self.y_range[1])
        ymax = np.clip(y0 + 0.5 * self.refocus_XY_size, self.y_range[0],
                       self.y_range[1])

        self._X_values = np.linspace(xmin, xmax, num=self.optimizer_XY_res)
        self._Y_values = np.linspace(ymin, ymax, num=self.optimizer_XY_res)
        self._Z_values = self.optim_pos_z * np.ones(self._X_values.shape)
        self._A_values = np.zeros(self._X_values.shape)
        self._return_X_values = np.linspace(xmax,
                                            xmin,
                                            num=self.optimizer_XY_res)
        self._return_A_values = np.zeros(self._return_X_values.shape)

        self.xy_refocus_image = np.zeros(
            (len(self._Y_values), len(self._X_values),
             3 + len(self.get_scanner_count_channels())))
        self.xy_refocus_image[:, :, 0] = np.full(
            (len(self._Y_values), len(self._X_values)), self._X_values)
        y_value_matrix = np.full((len(self._X_values), len(self._Y_values)),
                                 self._Y_values)
        self.xy_refocus_image[:, :, 1] = y_value_matrix.transpose()
        self.xy_refocus_image[:, :, 2] = self.optim_pos_z * np.ones(
            (len(self._Y_values), len(self._X_values)))

    def _initialize_z_refocus_image(self):
        """Initialisation of the z refocus image."""
        self._xy_scan_line_count = 0

        # Take optim pos as center of refocus image, to benefit from any previous
        # optimization steps that have occurred.
        z0 = self.optim_pos_z

        zmin = np.clip(z0 - 0.5 * self.refocus_Z_size, self.z_range[0],
                       self.z_range[1])
        zmax = np.clip(z0 + 0.5 * self.refocus_Z_size, self.z_range[0],
                       self.z_range[1])

        self._zimage_Z_values = np.linspace(zmin,
                                            zmax,
                                            num=self.optimizer_Z_res)
        self._fit_zimage_Z_values = np.linspace(zmin,
                                                zmax,
                                                num=self.optimizer_Z_res)
        self._zimage_A_values = np.zeros(self._zimage_Z_values.shape)
        self.z_refocus_line = np.zeros(
            (len(self._zimage_Z_values),
             len(self.get_scanner_count_channels())))
        self.z_fit_data = np.zeros(len(self._fit_zimage_Z_values))

    def _move_to_start_pos(self, start_pos):
        """Moves the scanner from its current position to the start position of the optimizer scan.

        @param start_pos float[]: 3-point vector giving x, y, z position to go to.
        """
        n_ch = len(self._scanning_device.get_scanner_axes())
        scanner_pos = self._scanning_device.get_scanner_position()
        lsx = np.linspace(scanner_pos[0], start_pos[0], self.return_slowness)
        lsy = np.linspace(scanner_pos[1], start_pos[1], self.return_slowness)
        lsz = np.linspace(scanner_pos[2], start_pos[2], self.return_slowness)
        if n_ch <= 3:
            move_to_start_line = np.vstack((lsx, lsy, lsz)[0:n_ch])
        else:
            move_to_start_line = np.vstack(
                (lsx, lsy, lsz, np.ones(lsx.shape) * scanner_pos[3]))

        counts = self._scanning_device.scan_line(move_to_start_line)
        if np.any(counts == -1):
            return -1

        time.sleep(self.hw_settle_time)
        return 0

    def _refocus_xy_line(self):
        """Scanning a line of the xy optimization image.
        This method repeats itself using the _sigScanNextXyLine
        until the xy optimization image is complete.
        """
        n_ch = len(self._scanning_device.get_scanner_axes())
        # stop scanning if instructed
        if self.stopRequested:
            with self.threadlock:
                self.stopRequested = False
                self.finish_refocus()
                self.sigImageUpdated.emit()
                self.sigRefocusFinished.emit(self._caller_tag, [
                    self.optim_pos_x, self.optim_pos_y, self.optim_pos_z, 0
                ][0:n_ch])
                return

        # move to the start of the first line
        if self._xy_scan_line_count == 0:
            status = self._move_to_start_pos([
                self.xy_refocus_image[0, 0, 0], self.xy_refocus_image[0, 0, 1],
                self.xy_refocus_image[0, 0, 2]
            ])
            if status < 0:
                self.log.error('Error during move to starting point.')
                self.stop_refocus()
                self._sigScanNextXyLine.emit()
                return

        lsx = self.xy_refocus_image[self._xy_scan_line_count, :, 0]
        lsy = self.xy_refocus_image[self._xy_scan_line_count, :, 1]
        lsz = self.xy_refocus_image[self._xy_scan_line_count, :, 2]

        # scan a line of the xy optimization image
        if n_ch <= 3:
            line = np.vstack((lsx, lsy, lsz)[0:n_ch])
        else:
            line = np.vstack((lsx, lsy, lsz, np.zeros(lsx.shape)))

        line_counts = self._scanning_device.scan_line(line)
        if np.any(line_counts == -1):
            self.log.error('The scan went wrong, killing the scanner.')
            self.stop_refocus()
            self._sigScanNextXyLine.emit()
            return

        lsx = self._return_X_values
        lsy = self.xy_refocus_image[self._xy_scan_line_count, 0, 1] * np.ones(
            lsx.shape)
        lsz = self.xy_refocus_image[self._xy_scan_line_count, 0, 2] * np.ones(
            lsx.shape)
        if n_ch <= 3:
            return_line = np.vstack((lsx, lsy, lsz))
        else:
            return_line = np.vstack((lsx, lsy, lsz, np.zeros(lsx.shape)))

        return_line_counts = self._scanning_device.scan_line(return_line)
        if np.any(return_line_counts == -1):
            self.log.error('The scan went wrong, killing the scanner.')
            self.stop_refocus()
            self._sigScanNextXyLine.emit()
            return

        s_ch = len(self.get_scanner_count_channels())
        self.xy_refocus_image[self._xy_scan_line_count, :,
                              3:3 + s_ch] = line_counts
        self.sigImageUpdated.emit()

        self._xy_scan_line_count += 1

        if self._xy_scan_line_count < np.size(self._Y_values):
            self._sigScanNextXyLine.emit()
        else:
            self._sigCompletedXyOptimizerScan.emit()

    def _set_optimized_xy_from_fit(self):
        """Fit the completed xy optimizer scan and set the optimized xy position."""
        fit_x, fit_y = np.meshgrid(self._X_values, self._Y_values)
        xy_fit_data = self.xy_refocus_image[:, :, 3 + self.opt_channel].ravel()
        axes = np.empty((len(self._X_values) * len(self._Y_values), 2))
        axes = (fit_x.flatten(), fit_y.flatten())
        result_2D_gaus = self._fit_logic.make_twoDgaussian_fit(
            xy_axes=axes,
            data=xy_fit_data,
            estimator=self._fit_logic.estimate_twoDgaussian_MLE)
        # print(result_2D_gaus.fit_report())

        if result_2D_gaus.success is False:
            self.log.error('Error: 2D Gaussian Fit was not successfull!.')
            print('2D gaussian fit not successfull')
            self.optim_pos_x = self._initial_pos_x
            self.optim_pos_y = self._initial_pos_y
            self.optim_sigma_x = 0.
            self.optim_sigma_y = 0.
        else:
            #                @reviewer: Do we need this. With constraints not one of these cases will be possible....
            if abs(self._initial_pos_x - result_2D_gaus.best_values['center_x']
                   ) < self._max_offset and abs(
                       self._initial_pos_x - result_2D_gaus.
                       best_values['center_x']) < self._max_offset:
                if self.x_range[0] <= result_2D_gaus.best_values[
                        'center_x'] <= self.x_range[1]:
                    if self.y_range[0] <= result_2D_gaus.best_values[
                            'center_y'] <= self.y_range[1]:
                        self.optim_pos_x = result_2D_gaus.best_values[
                            'center_x']
                        self.optim_pos_y = result_2D_gaus.best_values[
                            'center_y']
                        self.optim_sigma_x = result_2D_gaus.best_values[
                            'sigma_x']
                        self.optim_sigma_y = result_2D_gaus.best_values[
                            'sigma_y']
            else:
                self.optim_pos_x = self._initial_pos_x
                self.optim_pos_y = self._initial_pos_y
                self.optim_sigma_x = 0.
                self.optim_sigma_y = 0.

        # emit image updated signal so crosshair can be updated from this fit
        self.sigImageUpdated.emit()
        self._sigDoNextOptimizationStep.emit()

    def do_z_optimization(self):
        """ Do the z axis optimization."""
        # z scaning
        self._scan_z_line()

        # z-fit
        # If subtracting surface, then data can go negative and the gaussian fit offset constraints need to be adjusted
        if self.do_surface_subtraction:
            adjusted_param = {
                'offset': {
                    'value': 1e-12,
                    'min': -self.z_refocus_line[:, self.opt_channel].max(),
                    'max': self.z_refocus_line[:, self.opt_channel].max()
                }
            }
            result = self._fit_logic.make_gausspeaklinearoffset_fit(
                x_axis=self._zimage_Z_values,
                data=self.z_refocus_line[:, self.opt_channel],
                add_params=adjusted_param)
        else:
            if any(self.use_custom_params.values()):
                result = self._fit_logic.make_gausspeaklinearoffset_fit(
                    x_axis=self._zimage_Z_values,
                    data=self.z_refocus_line[:, self.opt_channel],
                    # Todo: It is required that the changed parameters are given as a dictionary or parameter object
                    add_params=None)
            else:
                result = self._fit_logic.make_gaussianlinearoffset_fit(
                    x_axis=self._zimage_Z_values,
                    data=self.z_refocus_line[:, self.opt_channel],
                    units='m',
                    estimator=self._fit_logic.
                    estimate_gaussianlinearoffset_peak)
        self.z_params = result.params

        if result.success is False:
            self.log.error('error in 1D Gaussian Fit.')
            self.optim_pos_z = self._initial_pos_z
            self.optim_sigma_z = 0.
            # interrupt here?
        else:  # move to new position
            #                @reviewer: Do we need this. With constraints not one of these cases will be possible....
            # checks if new pos is too far away
            if abs(self._initial_pos_z -
                   result.best_values['center']) < self._max_offset:
                # checks if new pos is within the scanner range
                if self.z_range[0] <= result.best_values[
                        'center'] <= self.z_range[1]:
                    self.optim_pos_z = result.best_values['center']
                    self.optim_sigma_z = result.best_values['sigma']
                    gauss, params = self._fit_logic.make_gaussianlinearoffset_model(
                    )
                    self.z_fit_data = gauss.eval(x=self._fit_zimage_Z_values,
                                                 params=result.params)
                else:  # new pos is too far away
                    # checks if new pos is too high
                    self.optim_sigma_z = 0.
                    if result.best_values['center'] > self._initial_pos_z:
                        if self._initial_pos_z + 0.5 * self.refocus_Z_size <= self.z_range[
                                1]:
                            # moves to higher edge of scan range
                            self.optim_pos_z = self._initial_pos_z + 0.5 * self.refocus_Z_size
                        else:
                            self.optim_pos_z = self.z_range[
                                1]  # moves to highest possible value
                    else:
                        if self._initial_pos_z + 0.5 * self.refocus_Z_size >= self.z_range[
                                0]:
                            # moves to lower edge of scan range
                            self.optim_pos_z = self._initial_pos_z + 0.5 * self.refocus_Z_size
                        else:
                            self.optim_pos_z = self.z_range[
                                0]  # moves to lowest possible value

        self.sigImageUpdated.emit()
        self._sigDoNextOptimizationStep.emit()

    def finish_refocus(self):
        """ Finishes up and releases hardware after the optimizer scans."""
        self.kill_scanner()

        self.log.info('Optimised from ({0:.3e},{1:.3e},{2:.3e}) to local '
                      'maximum at ({3:.3e},{4:.3e},{5:.3e}).'.format(
                          self._initial_pos_x, self._initial_pos_y,
                          self._initial_pos_z, self.optim_pos_x,
                          self.optim_pos_y, self.optim_pos_z))

        # Signal that the optimization has finished, and "return" the optimal position along with
        # caller_tag
        self.sigRefocusFinished.emit(
            self._caller_tag,
            [self.optim_pos_x, self.optim_pos_y, self.optim_pos_z, 0])

    def _scan_z_line(self):
        """Scans the z line for refocus."""

        # Moves to the start value of the z-scan
        status = self._move_to_start_pos(
            [self.optim_pos_x, self.optim_pos_y, self._zimage_Z_values[0]])
        if status < 0:
            self.log.error('Error during move to starting point.')
            self.stop_refocus()
            return

        n_ch = len(self._scanning_device.get_scanner_axes())

        # defining trace of positions for z-refocus
        scan_z_line = self._zimage_Z_values
        scan_x_line = self.optim_pos_x * np.ones(self._zimage_Z_values.shape)
        scan_y_line = self.optim_pos_y * np.ones(self._zimage_Z_values.shape)

        if n_ch <= 3:
            line = np.vstack((scan_x_line, scan_y_line, scan_z_line)[0:n_ch])
        else:
            line = np.vstack((scan_x_line, scan_y_line, scan_z_line,
                              np.zeros(scan_x_line.shape)))

        # Perform scan
        line_counts = self._scanning_device.scan_line(line)
        if np.any(line_counts == -1):
            self.log.error('Z scan went wrong, killing the scanner.')
            self.stop_refocus()
            return

        # Set the data
        self.z_refocus_line = line_counts

        # If subtracting surface, perform a displaced depth line scan
        if self.do_surface_subtraction:
            # Move to start of z-scan
            status = self._move_to_start_pos([
                self.optim_pos_x + self.surface_subtr_scan_offset,
                self.optim_pos_y, self._zimage_Z_values[0]
            ])
            if status < 0:
                self.log.error('Error during move to starting point.')
                self.stop_refocus()
                return

            # define an offset line to measure "background"
            if n_ch <= 3:
                line_bg = np.vstack(
                    (scan_x_line + self.surface_subtr_scan_offset, scan_y_line,
                     scan_z_line)[0:n_ch])
            else:
                line_bg = np.vstack(
                    (scan_x_line + self.surface_subtr_scan_offset, scan_y_line,
                     scan_z_line, np.zeros(scan_x_line.shape)))

            line_bg_counts = self._scanning_device.scan_line(line_bg)
            if np.any(line_bg_counts[0] == -1):
                self.log.error('The scan went wrong, killing the scanner.')
                self.stop_refocus()
                return

            # surface-subtracted line scan data is the difference
            self.z_refocus_line = line_counts - line_bg_counts

    def start_scanner(self):
        """Setting up the scanner device.

        @return int: error code (0:OK, -1:error)
        """
        self.module_state.lock()
        clock_status = self._scanning_device.set_up_scanner_clock(
            clock_frequency=self._clock_frequency)
        if clock_status < 0:
            self.module_state.unlock()
            return -1

        scanner_status = self._scanning_device.set_up_scanner()
        if scanner_status < 0:
            self._scanning_device.close_scanner_clock()
            self.module_state.unlock()
            return -1

        return 0

    def kill_scanner(self):
        """Closing the scanner device.

        @return int: error code (0:OK, -1:error)
        """
        try:
            rv = self._scanning_device.close_scanner()
        except:
            self.log.exception('Closing refocus scanner failed.')
            return -1
        try:
            rv2 = self._scanning_device.close_scanner_clock()
        except:
            self.log.exception('Closing refocus scanner clock failed.')
            return -1
        self.module_state.unlock()
        return rv + rv2

    def _do_next_optimization_step(self):
        """Handle the steps through the specified optimization sequence
        """

        # At the end fo the sequence, finish the optimization
        if self._optimization_step == len(self.optimization_sequence):
            self._sigFinishedAllOptimizationSteps.emit()
            return

        # Read the next step in the optimization sequence
        this_step = self.optimization_sequence[self._optimization_step]

        # Increment the step counter
        self._optimization_step += 1

        # Launch the next step
        if this_step == 'XY':
            self._initialize_xy_refocus_image()
            self._sigScanNextXyLine.emit()
        elif this_step == 'Z':
            self._initialize_z_refocus_image()
            self._sigScanZLine.emit()

    def set_position(self, tag, x=None, y=None, z=None, a=None):
        """ Set focus position.

            @param str tag: sting indicating who caused position change
            @param float x: x axis position in m
            @param float y: y axis position in m
            @param float z: z axis position in m
            @param float a: a axis position in m
        """
        if x is not None:
            self._current_x = x
        if y is not None:
            self._current_y = y
        if z is not None:
            self._current_z = z
        self.sigPositionChanged.emit(self._current_x, self._current_y,
                                     self._current_z)
Exemple #27
0
class AutofocusLogic(GenericLogic):
    """ This logic connect to the instruments necessary for the autofocus method based on the FPGA + QPD. This logic
    can be accessed from the focus_logic controlling the piezo position.

    autofocus_logic:
        module.Class: 'autofocus_logic.AutofocusLogic'
        autofocus_ref_axis : 'X' # 'Y'
        proportional_gain : 0.1 # in %%
        integration_gain : 1 # in %%
        exposure = 0.001
        connect:
            camera : 'thorlabs_camera'
            fpga: 'nifpga'
            stage: 'ms2000'
    """

    # declare connectors
    fpga = Connector(interface='FPGAInterface')  # to check _ a new interface was defined for FPGA connection
    stage = Connector(interface='MotorInterface')
    camera = Connector(interface='CameraInterface')

    # camera attributes
    _exposure = ConfigOption('exposure', 0.001, missing='warn')
    _camera_acquiring = False
    _threshold = None  # for compatibility with focus logic, not used

    # autofocus attributes
    _focus_offset = ConfigOption('focus_offset', 0, missing='warn')
    _ref_axis = ConfigOption('autofocus_ref_axis', 'X', missing='warn')
    _autofocus_stable = False
    _autofocus_iterations = 0

    # pid attributes
    _pid_frequency = 0.2  # in s, frequency for the autofocus PID update
    _P_gain = ConfigOption('proportional_gain', 0, missing='warn')
    _I_gain = ConfigOption('integration_gain', 0, missing='warn')
    _setpoint = None

    _last_pid_output_values = np.zeros((10,))

    # signals
    sigOffsetDefined = QtCore.Signal()
    sigStageMoved = QtCore.Signal()

    def __init__(self, config, **kwargs):
        super().__init__(config=config, **kwargs)
        # self.threadpool = QtCore.QThreadPool()

    def on_activate(self):
        """ Initialisation performed during activation of the module.
        """
        # hardware connections
        self._fpga = self.fpga()
        self._stage = self.stage()
        self._camera = self.camera()
        self._camera.set_exposure(self._exposure)

    def on_deactivate(self):
        """ Required deactivation.
        """
        self.stop_camera_live()

# =======================================================================================
# Public method for the autofocus, used by all the methods (camera or FPGA/QPD based)
# =======================================================================================
        
    def read_detector_signal(self):
        """ General function returning the reference signal for the autofocus correction. In the case of the
        method using a FPGA, it returns the QPD signal measured along the reference axis.
        """
        return self.qpd_read_position()

    # fpga only
    def read_detector_intensity(self):
        """ Function used for the focus search. Measured the intensity of the reflection instead of reading its
        position
        """
        return self.qpd_read_sum()

    def autofocus_check_signal(self):
        """ Check that the intensity detected by the QPD is above a specific threshold (300). If the signal is too low,
        the function returns False to indicate that the autofocus signal is lost.
        :return bool: True: signal ok, False: signal too low
        """
        qpd_sum = self.qpd_read_sum()
        print(qpd_sum)
        if qpd_sum < 300:
            return False
        else:
            return True

    def define_pid_setpoint(self):
        """ Initialize the pid setpoint
        """
        self.qpd_reset()
        self._setpoint = self.read_detector_signal()
        return self._setpoint

    def init_pid(self):
        """ Initialize the pid for the autofocus
        """
        self.qpd_reset()
        self._fpga.init_pid(self._P_gain, self._I_gain, self._setpoint, self._ref_axis)
        self.set_worker_frequency()

        self._autofocus_stable = False
        self._autofocus_iterations = 0

    def read_pid_output(self, check_stabilization):
        """ Read the pid output signal in order to adjust the position of the objective
        """
        pid_output = self._fpga.read_pid()

        if check_stabilization:
            self._autofocus_iterations += 1
            self._last_pid_output_values = np.concatenate((self._last_pid_output_values[1:10], [pid_output]))
            return pid_output, self.check_stabilization()
        else:
            return pid_output

    def check_stabilization(self):
        """ Check for the stabilization of the focus
        """
        if self._autofocus_iterations > 10:
            p = Poly.fit(np.linspace(0, 9, num=10), self._last_pid_output_values, deg=1)
            slope = p(9) - p(0)
            if np.absolute(slope) < 10:
                self._autofocus_stable = True
            else:
                self._autofocus_stable = False

        return self._autofocus_stable

    def start_camera_live(self):
        """ Launch live acquisition of the camera
        """
        self._camera.start_live_acquisition()
        self._camera_acquiring = True

    def stop_camera_live(self):
        """ Stop live acquisition of the camera
        """
        self._camera.stop_acquisition()
        self._camera_acquiring = False

    def get_latest_image(self):
        """ Get the latest acquired image from the camera. This function returns the raw image as well as the
        threshold image
        """
        im = self._camera.get_acquired_data()
        return im

    def calibrate_offset(self):
        """ Calibrate the offset between the sample position and a reference on the bottom of the coverslip. This method
        is inspired from the LSM-Zeiss microscope and is used when the sample (such as embryos) is interfering too much
        with the IR signal and makes the regular focus stabilization unstable.
        """
        # Read the stage position
        z_up = self._stage.get_pos()['z']
        offset = self._focus_offset

        # Move the stage by the default offset value along the z-axis
        self.stage_move_z(offset)

        # rescue autofocus when no signal detected
        if not self.autofocus_check_signal():
            self.rescue_autofocus()

        # Look for the position with the maximum intensity - for the QPD the SUM signal is used.
        max_sum = 0
        z_range = 5  # in µm
        z_step = 0.1  # in µm

        self.stage_move_z(-z_range/2)

        for n in range(int(z_range/z_step)):

            self.stage_move_z(z_step)

            sum = self.read_detector_intensity()
            print(sum)
            if sum > max_sum:
                max_sum = sum
            elif sum < max_sum and max_sum > 500:
                break

        # Calculate the offset for the stage and move back to the initial position
        offset = self._stage.get_pos()['z'] - z_up
        offset = np.round(offset, decimals=1)

        # avoid moving stage while QPD signal is read
        sleep(0.1)

        self.stage_move_z(-(offset))

        # send signal to focus logic that will be linked to define_autofocus_setpoint
        self.sigOffsetDefined.emit()

        self._focus_offset = offset

        return offset

    def rescue_autofocus(self):
        """ When the autofocus signal is lost, launch a rescuing procedure by using the MS2000 translation stage. The
        z position of the stage is moved until the piezo signal is found again.
        """
        success = False
        z_range = 20
        while not self.autofocus_check_signal() and z_range <= 40:

            self.stage_move_z(-z_range/2)

            for z in range(z_range):
                step = 1
                self.stage_move_z(step)

                if self.autofocus_check_signal():
                    success = True
                    print("autofocus signal found!")
                    return success

            if not self.autofocus_check_signal():
                self.stage_move_z(-z_range/2)
                z_range += 10

        return success

    def stage_move_z(self, step):
        self._stage.move_rel({'z': step})

    def do_position_correction(self, step):
        self._stage.set_velocity({'z': 0.01})
        sleep(1)
        self.stage_move_z(step)
        self._stage.wait_for_idle()
        self._stage.set_velocity({'z': 1.9})
        self.sigStageMoved.emit()

# ----------------to be completed----------------------------------
#     def start_piezo_position_correction(self, direction):
#         """ When the piezo position gets too close to the limits, the MS2000 stage is used to move the piezo back
#         to a standard position. If the piezo close to the lower limit (<5µm) it is moved to 25µm. If the piezo is too
#         close to the upper limit (>70µm), it is moved back to 50µm.
#         """
#
#         if direction == "up":
#             step = 1
#         elif direction == "down":
#             step = -1
#         else:
#             self.log.warning('no valid direction specified')
#             return
#
#         self._pos_dict = {'z': step}
#
#         # this needs to be modified :
#         if not self._run_autofocus:
#             self.start_autofocus()
#
#         stage_worker = StageAutofocusWorker()
#         stage_worker.signals.sigFinished.connect(self.run_piezo_position_correction)
#         self.threadpool.start(stage_worker)
#
#     def run_piezo_position_correction(self):
#         """ Correct the piezo position by moving the MS2000 stage while the autofocus ON
#         """
#         z = self.get_position()  # z posiiton of the piezo -- to be modified, this needs to be passed in by the focus_logic
#         # if not self._autofocus_lost and (z < 25 or z > 50):
#         if not self.autofocus_check_signal and (z < 25 or z > 50):
#             self._stage.move_rel(self._pos_dict)
#
#             stage_worker = StageAutofocusWorker()
#             stage_worker.signals.sigFinished.connect(self.run_piezo_position_correction)
#             self.threadpool.start(stage_worker)
# ---------------------------------------------------------------

# =================================================================
# private methods for QPD-based autofocus
# =================================================================

    def qpd_read_position(self):
        """ Read the QPD signal from the FPGA. The signal is read from X/Y positions. In order to make sure we are
        always reading from the latest piezo position, the method is waiting for a new count.
        """
        qpd = self._fpga.read_qpd()
        last_count = qpd[3]
        while last_count == qpd[3]:
            qpd = self._fpga.read_qpd()
            sleep(0.01)

        if self._ref_axis == 'X':
            return qpd[0]
        elif self._ref_axis == 'Y':
            return qpd[1]

    def qpd_read_sum(self):
        """ Read the SUM signal from the QPD. Returns an indication whether there is a detected signal or not
        """
        qpd = self._fpga.read_qpd()
        return qpd[2]

    def set_worker_frequency(self):
        """ Update the worker frequency according to the iteration time of the fpga
        """
        qpd = self._fpga.read_qpd()
        self._pid_frequency = qpd[4] / 1000 + 0.01

    def qpd_reset(self):
        """ Reset the QPD counter
        """
        self._fpga.reset_qpd_counter()
class LaserAomInterfuse(GenericLogic, SimpleLaserInterface):
    """ This interfuse can be used to control the laser power after an AOM driven by an analog ouput on a confocal
    scanner hardware (the 4th analog output 'a')

    The hardware module should be configured accordingly (range 0 to 1, voltage 0 to 1V for example)

    This module needs a calibration file for the AOM. This is a 2D array with the first column the relative power
    (power over maximum power) and the second column the associated voltage.
    This data is interpolated to define the power/voltage function
    """

    # connector to the confocal scanner hardware that has analog output feature
    scanner = Connector(interface='ConfocalScannerInterface')

    # max power the AOM can deliver (in Watt)
    _max_power = ConfigOption('max_power', missing='error')

    # calibration file which can be read by numpy loadtxt with two columns :
    # relative power (0.0 to 1.0), voltage (V)
    _calibration_file = ConfigOption('calibration_file', missing='error')
    _power_to_voltage = None
    _power = 0
    _laser_on = LaserState.OFF

    def on_activate(self):
        """ Activate module.
        """
        self._scanner = self.scanner()
        if 'a' not in self._scanner.get_scanner_axes():
            self.log.error(
                'Scanner does not have an "a" axe configured. Can not use it to control an AOM.'
            )

        calibration_data = np.loadtxt(self._calibration_file)
        power_rel_to_voltage = interp1d(calibration_data[:, 0],
                                        calibration_data[:, 1])
        self._power_to_voltage = lambda power: power_rel_to_voltage(
            power / self._max_power)

    def on_deactivate(self):
        """ Deactivate module.
        """
        pass

    def get_power_range(self):
        """ Return optical power range

            @return (float, float): power range
        """
        return 0, self._max_power

    def get_power(self):
        """ Return laser power

            @return float: Laser power in watts
        """
        return self._power

    def get_power_setpoint(self):
        """ Return optical power setpoint.

            @return float: power setpoint in watts
        """
        return self._power

    def set_power(self, power):
        """ Set power setpoint.

            @param float power: power setpoint

            @return float: actual new power setpoint
        """
        mini, maxi = self.get_power_range()
        if mini <= power <= maxi:
            self._power = power
            if self._laser_on == LaserState.ON:
                voltage = self._power_to_voltage(power)
            else:
                voltage = self._power_to_voltage(0)
            if self._scanner.module_state() == 'locked':
                self.log.error(
                    'Output device of the voltage for the AOM is locked, cannot set voltage.'
                )
            else:
                if self._scanner.scanner_set_position(a=voltage) < 0:
                    self.log.error(
                        'Could not set the voltage for the AOM because the scanner failed.'
                    )
        return self._power

    def get_current_unit(self):
        """ Get unit for laser current.

            @return str: unit
        """
        return '%'

    def get_current_range(self):
        """ Get laser current range.

            @return (float, float): laser current range
        """
        return 0, 100

    def get_current(self):
        """ Get current laser current

            @return float: laser current in current curent units
        """
        return 0

    def get_current_setpoint(self):
        """ Get laser curent setpoint

            @return float: laser current setpoint
        """
        return 0

    def set_current(self, current):
        """ Set laser current setpoint

            @prarm float current: desired laser current setpoint

            @return float: actual laser current setpoint
        """
        return 0

    def allowed_control_modes(self):
        """ Get supported control modes

            @return list(): list of supported ControlMode
        """
        return [ControlMode.POWER]

    def get_control_mode(self):
        """ Get the currently active control mode

            @return ControlMode: active control mode
        """
        return ControlMode.POWER

    def set_control_mode(self, control_mode):
        """ Set the active control mode

            @param ControlMode control_mode: desired control mode

            @return ControlMode: actual active ControlMode
        """
        return ControlMode.POWER

    def on(self):
        """ Turn on laser.

            @return LaserState: actual laser state
        """
        return self.set_laser_state(LaserState.ON)

    def off(self):
        """ Turn off laser.

            @return LaserState: actual laser state
        """
        return self.set_laser_state(LaserState.OFF)

    def get_laser_state(self):
        """ Get laser state

            @return LaserState: actual laser state
        """
        return self._laser_on

    def set_laser_state(self, state):
        """ Set laser state.

            @param LaserState state: desired laser state

            @return LaserState: actual laser state
        """
        self._laser_on = state
        self.set_power(self._power)

    def get_shutter_state(self):
        """ Get laser shutter state

            @return ShutterState: actual laser shutter state
        """
        return ShutterState.NOSHUTTER

    def set_shutter_state(self, state):
        """ Set laser shutter state.

            @param ShutterState state: desired laser shutter state

            @return ShutterState: actual laser shutter state
        """
        return ShutterState.NOSHUTTER

    def get_temperatures(self):
        """ Get all available temperatures.

            @return dict: dict of temperature namce and value in degrees Celsius
        """
        return {}

    def set_temperatures(self, temps):
        """ Set temperatures for lasers with tunable temperatures.

            @return {}: empty dict, dummy not a tunable laser
        """
        return {}

    def get_temperature_setpoints(self):
        """ Get temperature setpoints.

            @return dict: temperature setpoints for temperature tunable lasers
        """
        return {}

    def get_extra_info(self):
        """ Multiple lines of dignostic information

            @return str: much laser, very useful
        """
        return ""

    def set_max_power(self, maxi):
        """ Function to redefine the max power if the value has changed """
        self._max_power = maxi
Exemple #29
0
class AutofocusGUI(GUIBase):
    """ Main window containing the GUI for focus and autofocus control
    """
    
    # Define connectors to logic modules
    autofocus_logic = Connector(interface='AutofocusLogic')
    
      
    # Signals  
    sigTimetraceOn = QtCore.Signal()
    sigTimetraceOff = QtCore.Signal()

    # attributes
    _autofocus_logic = None
    _mw = None

    def __init__(self, config, **kwargs):

        # load connection
        super().__init__(config=config, **kwargs)

    def on_activate(self):
        """ Initializes all needed UI files and establishes the connectors.
        """

        self._autofocus_logic = self.autofocus_logic()

        # Windows
        self._mw = AutofocusWindow()
        self._mw.centralwidget.hide() # everything is in dockwidgets
        #self._mw.setDockNestingEnabled(True)
        self.initPiezoSettingsUI()
        
        
        
        # Menu bar actions
        # Options menu
        self._mw.piezo_settings_Action.triggered.connect(self.open_piezo_settings)
               
        
        # piezo dockwidget
        self._mw.z_track_Action.setEnabled(True) # button can be used # just to be sure, this is the initial state defined in designer
        self._mw.z_track_Action.setChecked(self._autofocus_logic.enabled) # checked state takes the same bool value as enabled attribute in logic (enabled = 0: no timetrace running) # button is defined as checkable in designer
        self._mw.z_track_Action.triggered.connect(self.start_z_track_clicked)
        
        # start and stop the physical measurement
        self.sigTimetraceOn.connect(self._autofocus_logic.start_tracking)
        self._autofocus_logic.sigUpdateDisplay.connect(self.update_timetrace)
        self.sigTimetraceOff.connect(self._autofocus_logic.stop_tracking)

        
        
        # some data for testing
        #self.t_data = np.arange(100) # not needed if the x axis stays fixed (no moving ticks)
        self.y_data = np.zeros(100) # for initialization
        

        
        # create a reference to the line object (this is returned when calling plot method of pg.PlotWidget)
        self._timetrace = self._mw.piezo_PlotWidget.plot(self.y_data)
           
        

 
        


    def on_deactivate(self):
        """ Deinitialisation performed during deactivation of the module.
        """
        self._mw.close()


    def show(self):
        """Make window visible and put it above all other windows.
        """
        QtWidgets.QMainWindow.show(self._mw)
        self._mw.activateWindow()
        self._mw.raise_()
        
        
    # Initialisation of the piezo settings windows in the options menu    
    def initPiezoSettingsUI(self):
        """ Definition, configuration and initialisation of the camera settings GUI.

        """
        # Create the Piezo settings window
        self._piezo_sd = PiezoSettingDialog()
        # Connect the action of the settings window with the code:
        self._piezo_sd.accepted.connect(self.piezo_update_settings) # ok button
        self._piezo_sd.rejected.connect(self.piezo_keep_former_settings) # cancel buttons
        #self._piezo_sd.buttonBox.button(QtWidgets.QDialogButtonBox.Apply).clicked.connect(self.piezo_update_settings)

        # write the configuration to the settings window of the GUI.
        self.piezo_keep_former_settings()
        
        
    # slots of the piezosettingswindow
    def piezo_update_settings(self):
        """ Write new settings from the gui to the file. 
        """
        self._autofocus_logic.set_step(self._piezo_sd.step_doubleSpinBox.value())

  
    def piezo_keep_former_settings(self):
        """ Keep the old settings and restores them in the gui. 
        """
        self._piezo_sd.step_doubleSpinBox.setValue(self._autofocus_logic._step)

        
    # slot to open the camerasettingswindow
    def open_piezo_settings(self):
        """ Opens the settings menu. 
        """
        self._piezo_sd.exec_()
    
    
    

    # definition of the slots    
    def start_z_track_clicked(self):
        if self._autofocus_logic.enabled: # timetrace already running
            self._mw.z_track_Action.setText('Piezo: Start Tracking')
            self.sigTimetraceOff.emit()
        else: 
            self._mw.z_track_Action.setText('Piezo: Stop Tracking')
            self.sigTimetraceOn.emit()
            
            
    def update_timetrace(self):
        # t data not needed, only if it is wanted that the axis labels move also. then see variant 2 from pyqtgraph.examples scrolling plot
        #self.t_data[:-1] = self.t_data[1:] # shift data in the array one position to the left, keeping same array size
        #self.t_data[-1] += 1 # add the new last element
        self.y_data[:-1] = self.y_data[1:] # shfit data one position to the left ..
        self.y_data[-1] = self._autofocus_logic.get_last_position()

        
        #self._timetrace.setData(self.t_data, self.y_data) # x axis values running with the timetrace
        self._timetrace.setData(self.y_data) # x axis values do not move
class ODMRLogic(GenericLogic):
    """This is the Logic class for ODMR."""

    # declare connectors
    odmrcounter = Connector(interface='ODMRCounterInterface')
    fitlogic = Connector(interface='FitLogic')
    microwave1 = Connector(interface='MicrowaveInterface')
    savelogic = Connector(interface='SaveLogic')
    taskrunner = Connector(interface='TaskRunner')

    # config option
    mw_scanmode = ConfigOption(
                    'scanmode',
                    'LIST',
                    missing='warn',
                    converter=lambda x: MicrowaveMode[x.upper()])

    clock_frequency = StatusVar('clock_frequency', 200)
    cw_mw_frequency = StatusVar('cw_mw_frequency', 2870e6)
    cw_mw_power = StatusVar('cw_mw_power', -30)
    sweep_mw_power = StatusVar('sweep_mw_power', -30)
    mw_start = StatusVar('mw_start', 2800e6)
    mw_stop = StatusVar('mw_stop', 2950e6)
    mw_step = StatusVar('mw_step', 2e6)
    run_time = StatusVar('run_time', 60)
    number_of_lines = StatusVar('number_of_lines', 50)
    fc = StatusVar('fits', None)
    lines_to_average = StatusVar('lines_to_average', 0)
    _oversampling = StatusVar('oversampling', default=10)
    _lock_in_active = StatusVar('lock_in_active', default=False)

    # Internal signals
    sigNextLine = QtCore.Signal()

    # Update signals, e.g. for GUI module
    sigParameterUpdated = QtCore.Signal(dict)
    sigOutputStateUpdated = QtCore.Signal(str, bool)
    sigOdmrPlotsUpdated = QtCore.Signal(np.ndarray, np.ndarray, np.ndarray)
    sigOdmrFitUpdated = QtCore.Signal(np.ndarray, np.ndarray, dict, str)
    sigOdmrElapsedTimeUpdated = QtCore.Signal(float, int)

    def __init__(self, config, **kwargs):
        super().__init__(config=config, **kwargs)
        self.threadlock = Mutex()

    def on_activate(self):
        """
        Initialisation performed during activation of the module.
        """
        # Get connectors
        self._mw_device = self.microwave1()
        self._fit_logic = self.fitlogic()
        self._odmr_counter = self.odmrcounter()
        self._save_logic = self.savelogic()
        self._taskrunner = self.taskrunner()

        # Get hardware constraints
        limits = self.get_hw_constraints()

        # Set/recall microwave source parameters
        self.cw_mw_frequency = limits.frequency_in_range(self.cw_mw_frequency)
        self.cw_mw_power = limits.power_in_range(self.cw_mw_power)
        self.sweep_mw_power = limits.power_in_range(self.sweep_mw_power)
        self.mw_start = limits.frequency_in_range(self.mw_start)
        self.mw_stop = limits.frequency_in_range(self.mw_stop)
        self.mw_step = limits.list_step_in_range(self.mw_step)
        self._odmr_counter.oversampling = self._oversampling
        self._odmr_counter.lock_in_active = self._lock_in_active

        # Set the trigger polarity (RISING/FALLING) of the mw-source input trigger
        # theoretically this can be changed, but the current counting scheme will not support that
        self.mw_trigger_pol = TriggerEdge.RISING
        self.set_trigger(self.mw_trigger_pol, self.clock_frequency)

        # Elapsed measurement time and number of sweeps
        self.elapsed_time = 0.0
        self.elapsed_sweeps = 0
        

        # Set flags
        # for stopping a measurement
        self._stopRequested = False
        # in case of sweep parameters being updated, so the data arrays need
        # to be resized
        self._sweep_params_updated = False

        # Initalize the ODMR data arrays (mean signal and sweep matrix)
        self._initialize_odmr_plots()

        # Switch off microwave and set CW frequency and power
        self.mw_off()
        self.set_cw_parameters(self.cw_mw_frequency, self.cw_mw_power)

        # Connect signals
        self.sigNextLine.connect(self._scan_odmr_line, QtCore.Qt.QueuedConnection)
        return

    def on_deactivate(self):
        """ Deinitialisation performed during deactivation of the module.
        """
        # Stop measurement if it is still running
        if self.module_state() == 'locked':
            self.stop_odmr_scan()
        timeout = 30.0
        start_time = time.time()
        while self.module_state() == 'locked':
            time.sleep(0.5)
            timeout -= (time.time() - start_time)
            if timeout <= 0.0:
                self.log.error('Failed to properly deactivate odmr logic. Odmr scan is still '
                               'running but can not be stopped after 30 sec.')
                break
        # Switch off microwave source for sure (also if CW mode is active or module is still locked)
        self._mw_device.off()
        # Disconnect signals
        self.sigNextLine.disconnect()

    @fc.constructor
    def sv_set_fits(self, val):
        # Setup fit container
        fc = self.fitlogic().make_fit_container('ODMR sum', '1d')
        fc.set_units(['Hz', 'c/s'])
        if isinstance(val, dict) and len(val) > 0:
            fc.load_from_dict(val)
        else:
            d1 = OrderedDict()
            d1['Lorentzian dip'] = {
                'fit_function': 'lorentzian',
                'estimator': 'dip'
                }
            d1['Two Lorentzian dips'] = {
                'fit_function': 'lorentziandouble',
                'estimator': 'dip'
                }
            d1['N14'] = {
                'fit_function': 'lorentziantriple',
                'estimator': 'N14'
                }
            d1['N15'] = {
                'fit_function': 'lorentziandouble',
                'estimator': 'N15'
                }
            d1['Two Gaussian dips'] = {
                'fit_function': 'gaussiandouble',
                'estimator': 'dip'
                }
            default_fits = OrderedDict()
            default_fits['1d'] = d1
            fc.load_from_dict(default_fits)
        return fc

    @fc.representer
    def sv_get_fits(self, val):
        """ save configured fits """
        if len(val.fit_list) > 0:
            return val.save_to_dict()
        else:
            return None

    def _initialize_odmr_plots(self):
        """ Initializing the ODMR plots (line and matrix). """

        self.elapsed_sweeps = 0
        self.elapsed_time = 0.0
        self.sigOdmrElapsedTimeUpdated.emit(self.elapsed_time, self.elapsed_sweeps)
        
        self.odmr_plot_x = np.arange(self.mw_start, self.mw_stop + self.mw_step, self.mw_step)
        self.odmr_plot_y = np.zeros([len(self.get_odmr_channels()), self.odmr_plot_x.size])
        self.odmr_fit_x = np.arange(self.mw_start, self.mw_stop + self.mw_step, self.mw_step)
        self.odmr_fit_y = np.zeros(self.odmr_fit_x.size)
        self.odmr_plot_xy = np.zeros(
            [self.number_of_lines, len(self.get_odmr_channels()), self.odmr_plot_x.size])
        self.sigOdmrPlotsUpdated.emit(self.odmr_plot_x, self.odmr_plot_y, self.odmr_plot_xy)
        self.odmr_raw_data = np.zeros(
                    [1,
                    len(self._odmr_counter.get_odmr_channels()),
                    self.odmr_plot_x.size]
                )
        current_fit = self.fc.current_fit
        self.sigOdmrFitUpdated.emit(self.odmr_fit_x, self.odmr_fit_y, {}, current_fit)
        return

    def set_trigger(self, trigger_pol, frequency):
        """
        Set trigger polarity of external microwave trigger (for list and sweep mode).

        @param object trigger_pol: one of [TriggerEdge.RISING, TriggerEdge.FALLING]
        @param float frequency: trigger frequency during ODMR scan

        @return object: actually set trigger polarity returned from hardware
        """
        if self._lock_in_active:
            frequency = frequency / self._oversampling

        if self.module_state() != 'locked':
            self.mw_trigger_pol, triggertime = self._mw_device.set_ext_trigger(trigger_pol, 1/frequency)
        else:
            self.log.warning('set_trigger failed. Logic is locked.')

        update_dict = {'trigger_pol': self.mw_trigger_pol}
        self.sigParameterUpdated.emit(update_dict)
        return self.mw_trigger_pol

    def set_average_length(self, lines_to_average):
        """
        Sets the number of lines to average for the sum of the data

        @param int lines_to_average: desired number of lines to average (0 means all)

        @return int: actually set lines to average
        """
        self.lines_to_average = int(lines_to_average)

        if self.lines_to_average <= 0:
            self.odmr_plot_y = np.mean(
                self.odmr_raw_data[:max(1, self.elapsed_sweeps), :, :],
                axis=0,
                dtype=np.float64
            )
        else:
            self.odmr_plot_y = np.mean(
                self.odmr_raw_data[:max(1, min(self.lines_to_average, self.elapsed_sweeps)), :, :],
                axis=0,
                dtype=np.float64
            )

        self.sigOdmrPlotsUpdated.emit(self.odmr_plot_x, self.odmr_plot_y, self.odmr_plot_xy)
        self.sigParameterUpdated.emit({'average_length': self.lines_to_average})
        return self.lines_to_average

    def set_clock_frequency(self, clock_frequency):
        """
        Sets the frequency of the counter clock

        @param int clock_frequency: desired frequency of the clock

        @return int: actually set clock frequency
        """
        # checks if scanner is still running
        if self.module_state() != 'locked' and isinstance(clock_frequency, (int, float)):
            self.clock_frequency = int(clock_frequency)
        else:
            self.log.warning('set_clock_frequency failed. Logic is either locked or input value is '
                             'no integer or float.')

        update_dict = {'clock_frequency': self.clock_frequency}
        self.sigParameterUpdated.emit(update_dict)
        return self.clock_frequency

    @property
    def oversampling(self):
        return self._oversampling

    @oversampling.setter
    def oversampling(self, oversampling):
        """
        Sets the frequency of the counter clock

        @param int oversampling: desired oversampling per frequency step
        """
        # checks if scanner is still running
        if self.module_state() != 'locked' and isinstance(oversampling, (int, float)):
            self._oversampling = int(oversampling)
            self._odmr_counter.oversampling = self._oversampling
        else:
            self.log.warning('setter of oversampling failed. Logic is either locked or input value is '
                             'no integer or float.')

        update_dict = {'oversampling': self._oversampling}
        self.sigParameterUpdated.emit(update_dict)

    def set_oversampling(self, oversampling):
        self.oversampling = oversampling
        return self.oversampling

    @property
    def lock_in(self):
        return self._lock_in_active

    @lock_in.setter
    def lock_in(self, active):
        """
        Sets the frequency of the counter clock

        @param bool active: specify if signal should be detected with lock in
        """
        # checks if scanner is still running
        if self.module_state() != 'locked' and isinstance(active, bool):
            self._lock_in_active = active
            self._odmr_counter.lock_in_active = self._lock_in_active
        else:
            self.log.warning('setter of lock in failed. Logic is either locked or input value is no boolean.')

        update_dict = {'lock_in': self._lock_in_active}
        self.sigParameterUpdated.emit(update_dict)

    def set_lock_in(self, active):
        self.lock_in = active
        return self.lock_in

    def set_matrix_line_number(self, number_of_lines):
        """
        Sets the number of lines in the ODMR matrix

        @param int number_of_lines: desired number of matrix lines

        @return int: actually set number of matrix lines
        """
        if isinstance(number_of_lines, int):
            self.number_of_lines = number_of_lines
        else:
            self.log.warning('set_matrix_line_number failed. '
                             'Input parameter number_of_lines is no integer.')

        update_dict = {'number_of_lines': self.number_of_lines}
        self.sigParameterUpdated.emit(update_dict)
        return self.number_of_lines

    def set_runtime(self, runtime):
        """
        Sets the runtime for ODMR measurement

        @param float runtime: desired runtime in seconds

        @return float: actually set runtime in seconds
        """
        if isinstance(runtime, (int, float)):
            self.run_time = runtime
        else:
            self.log.warning('set_runtime failed. Input parameter runtime is no integer or float.')

        update_dict = {'run_time': self.run_time}
        self.sigParameterUpdated.emit(update_dict)
        return self.run_time

    def set_cw_parameters(self, frequency, power):
        """ Set the desired new cw mode parameters.

        @param float frequency: frequency to set in Hz
        @param float power: power to set in dBm

        @return (float, float): actually set frequency in Hz, actually set power in dBm
        """
        if self.module_state() != 'locked' and isinstance(frequency, (int, float)) and isinstance(power, (int, float)):
            constraints = self.get_hw_constraints()
            frequency_to_set = constraints.frequency_in_range(frequency)
            power_to_set = constraints.power_in_range(power)
            self.cw_mw_frequency, self.cw_mw_power, dummy = self._mw_device.set_cw(frequency_to_set,
                                                                                   power_to_set)
        else:
            self.log.warning('set_cw_frequency failed. Logic is either locked or input value is '
                             'no integer or float.')

        param_dict = {'cw_mw_frequency': self.cw_mw_frequency, 'cw_mw_power': self.cw_mw_power}
        self.sigParameterUpdated.emit(param_dict)
        return self.cw_mw_frequency, self.cw_mw_power

    def set_sweep_parameters(self, start, stop, step, power):
        """ Set the desired frequency parameters for list and sweep mode

        @param float start: start frequency to set in Hz
        @param float stop: stop frequency to set in Hz
        @param float step: step frequency to set in Hz
        @param float power: mw power to set in dBm

        @return float, float, float, float: current start_freq, current stop_freq,
                                            current freq_step, current power
        """
        limits = self.get_hw_constraints()
        if self.module_state() != 'locked':
            if isinstance(start, (int, float)):
                self.mw_start = limits.frequency_in_range(start)
            if isinstance(stop, (int, float)) and isinstance(step, (int, float)):
                if stop <= start:
                    stop = start + step
                self.mw_stop = limits.frequency_in_range(stop)
                if self.mw_scanmode == MicrowaveMode.LIST:
                    self.mw_step = limits.list_step_in_range(step)
                elif self.mw_scanmode == MicrowaveMode.SWEEP:
                    self.mw_step = limits.sweep_step_in_range(step)
            if isinstance(power, (int, float)):
                self.sweep_mw_power = limits.power_in_range(power)
        else:
            self.log.warning('set_sweep_parameters failed. Logic is locked.')

        param_dict = {'mw_start': self.mw_start, 'mw_stop': self.mw_stop, 'mw_step': self.mw_step,
                      'sweep_mw_power': self.sweep_mw_power}
        self.sigParameterUpdated.emit(param_dict)
        self._sweep_params_updated = True
        return self.mw_start, self.mw_stop, self.mw_step, self.sweep_mw_power

    def mw_cw_on(self):
        """
        Switching on the mw source in cw mode.

        @return str, bool: active mode ['cw', 'list', 'sweep'], is_running
        """
        if self.module_state() == 'locked':
            self.log.error('Can not start microwave in CW mode. ODMRLogic is already locked.')
        else:
            self.cw_mw_frequency, \
            self.cw_mw_power, \
            mode = self._mw_device.set_cw(self.cw_mw_frequency, self.cw_mw_power)
            param_dict = {'cw_mw_frequency': self.cw_mw_frequency, 'cw_mw_power': self.cw_mw_power}
            self.sigParameterUpdated.emit(param_dict)
            if mode != 'cw':
                self.log.error('Switching to CW microwave output mode failed.')
            else:
                err_code = self._mw_device.cw_on()
                if err_code < 0:
                    self.log.error('Activation of microwave output failed.')

        mode, is_running = self._mw_device.get_status()
        self.sigOutputStateUpdated.emit(mode, is_running)
        return mode, is_running

    def mw_sweep_on(self):
        """
        Switching on the mw source in list/sweep mode.

        @return str, bool: active mode ['cw', 'list', 'sweep'], is_running
        """

        limits = self.get_hw_constraints()
        param_dict = {}

        if self.mw_scanmode == MicrowaveMode.LIST:
            if np.abs(self.mw_stop - self.mw_start) / self.mw_step >= limits.list_maxentries:
                self.log.warning('Number of frequency steps too large for microwave device. '
                                 'Lowering resolution to fit the maximum length.')
                self.mw_step = np.abs(self.mw_stop - self.mw_start) / (limits.list_maxentries - 1)
                self.sigParameterUpdated.emit({'mw_step': self.mw_step})

            # adjust the end frequency in order to have an integer multiple of step size
            # The master module (i.e. GUI) will be notified about the changed end frequency
            num_steps = int(np.rint((self.mw_stop - self.mw_start) / self.mw_step))
            end_freq = self.mw_start + num_steps * self.mw_step
            freq_list = np.linspace(self.mw_start, end_freq, num_steps + 1)
            freq_list, self.sweep_mw_power, mode = self._mw_device.set_list(freq_list,
                                                                            self.sweep_mw_power)
            self.mw_start = freq_list[0]
            self.mw_stop = freq_list[-1]
            self.mw_step = (self.mw_stop - self.mw_start) / (len(freq_list) - 1)

            param_dict = {'mw_start': self.mw_start, 'mw_stop': self.mw_stop,
                          'mw_step': self.mw_step, 'sweep_mw_power': self.sweep_mw_power}

        elif self.mw_scanmode == MicrowaveMode.SWEEP:
            if np.abs(self.mw_stop - self.mw_start) / self.mw_step >= limits.sweep_maxentries:
                self.log.warning('Number of frequency steps too large for microwave device. '
                                 'Lowering resolution to fit the maximum length.')
                self.mw_step = np.abs(self.mw_stop - self.mw_start) / (limits.list_maxentries - 1)
                self.sigParameterUpdated.emit({'mw_step': self.mw_step})

            sweep_return = self._mw_device.set_sweep(
                self.mw_start, self.mw_stop, self.mw_step, self.sweep_mw_power)
            self.mw_start, self.mw_stop, self.mw_step, self.sweep_mw_power, mode = sweep_return

            param_dict = {'mw_start': self.mw_start, 'mw_stop': self.mw_stop,
                          'mw_step': self.mw_step, 'sweep_mw_power': self.sweep_mw_power}

        else:
            self.log.error('Scanmode not supported. Please select SWEEP or LIST.')

        self.sigParameterUpdated.emit(param_dict)

        if mode != 'list' and mode != 'sweep':
            self.log.error('Switching to list/sweep microwave output mode failed.')
        elif self.mw_scanmode == MicrowaveMode.SWEEP:
            err_code = self._mw_device.sweep_on()
            if err_code < 0:
                self.log.error('Activation of microwave output failed.')
        else:
            err_code = self._mw_device.list_on()
            if err_code < 0:
                self.log.error('Activation of microwave output failed.')

        mode, is_running = self._mw_device.get_status()
        self.sigOutputStateUpdated.emit(mode, is_running)
        return mode, is_running

    def reset_sweep(self):
        """
        Resets the list/sweep mode of the microwave source to the first frequency step.
        """
        if self.mw_scanmode == MicrowaveMode.SWEEP:
            self._mw_device.reset_sweeppos()
        elif self.mw_scanmode == MicrowaveMode.LIST:
            self._mw_device.reset_listpos()
        return

    def mw_off(self):
        """ Switching off the MW source.

        @return str, bool: active mode ['cw', 'list', 'sweep'], is_running
        """
        error_code = self._mw_device.off()
        if error_code < 0:
            self.log.error('Switching off microwave source failed.')

        mode, is_running = self._mw_device.get_status()
        self.sigOutputStateUpdated.emit(mode, is_running)
        return mode, is_running

    def _start_odmr_counter(self):
        """
        Starting the ODMR counter and set up the clock for it.

        @return int: error code (0:OK, -1:error)
        """

        clock_status = self._odmr_counter.set_up_odmr_clock(clock_frequency=self.clock_frequency)
        if clock_status < 0:
            return -1

        counter_status = self._odmr_counter.set_up_odmr()
        if counter_status < 0:
            self._odmr_counter.close_odmr_clock()
            return -1

        return 0

    def _stop_odmr_counter(self):
        """
        Stopping the ODMR counter.

        @return int: error code (0:OK, -1:error)
        """

        ret_val1 = self._odmr_counter.close_odmr()
        if ret_val1 != 0:
            self.log.error('ODMR counter could not be stopped!')
        ret_val2 = self._odmr_counter.close_odmr_clock()
        if ret_val2 != 0:
            self.log.error('ODMR clock could not be stopped!')

        # Check with a bitwise or:
        return ret_val1 | ret_val2

    def start_odmr_scan(self):
        """ Starting an ODMR scan.

        @return int: error code (0:OK, -1:error)
        """
        with self.threadlock:
            if self.module_state() == 'locked':
                self.log.error('Can not start ODMR scan. Logic is already locked.')
                return -1

            self.set_trigger(self.mw_trigger_pol, self.clock_frequency)

            self.module_state.lock()

            self.stopRequested = False
            self.fc.clear_result()

            # If sweep parameters have been updated since last call,
            # need to clear the data and re-initialise the buffers
            if self._sweep_params_updated:
                self._initialize_odmr_plots()
                self._sweep_params_updated = False

            self._startTime = time.time() - self.elapsed_time
            self.sigOdmrElapsedTimeUpdated.emit(self.elapsed_time, self.elapsed_sweeps)

            odmr_status = self._start_odmr_counter()
            if odmr_status < 0:
                mode, is_running = self._mw_device.get_status()
                self.sigOutputStateUpdated.emit(mode, is_running)
                self.module_state.unlock()
                return -1

            mode, is_running = self.mw_sweep_on()
            if not is_running:
                self._stop_odmr_counter()
                self.module_state.unlock()
                return -1

            self.sigNextLine.emit()
            return 0

    def stop_odmr_scan(self):
        """ Stop the ODMR scan.

        @return int: error code (0:OK, -1:error)
        """
        with self.threadlock:
            if self.module_state() == 'locked':
                self.stopRequested = True
        return 0

    def clear_odmr_data(self):
        """¨Clear ODMR data and elapsed time

        Only works when a scan is not currently running
        """
        with self.threadlock:
            if self.module_state() != 'locked':
                self._initialize_odmr_plots()
        return

    def _scan_odmr_line(self):
        """ Scans one line in ODMR

        (from mw_start to mw_stop in steps of mw_step)
        """
        with self.threadlock:
            # If the odmr measurement is not running do nothing
            if self.module_state() != 'locked':
                return

            # Stop measurement if stop has been requested
            if self.stopRequested:
                self.stopRequested = False
                self.mw_off()
                self._stop_odmr_counter()
                self.module_state.unlock()
                return

            # reset position so every line starts from the same frequency
            self.reset_sweep()

            # Acquire count data
            error, new_counts = self._odmr_counter.count_odmr(length=self.odmr_plot_x.size)

            if error:
                self.stopRequested = True
                self.sigNextLine.emit()
                return

            # Add new count data to raw_data array
            self.odmr_raw_data = np.insert(self.odmr_raw_data, 0, new_counts, 0)

            if self.lines_to_average <= 0:
                self.odmr_plot_y = np.mean(
                    self.odmr_raw_data[:max(1, self.elapsed_sweeps), :, :],
                    axis=0,
                    dtype=np.float64
                )
            else:
                self.odmr_plot_y = np.mean(
                    self.odmr_raw_data[:max(1, min(self.lines_to_average, self.elapsed_sweeps)), :, :],
                    axis=0,
                    dtype=np.float64
                )

            # Get xy plot data
            pad_amount = self.number_of_lines - self.odmr_raw_data.shape[0]
            if pad_amount > 0:
                # Pad out data if needed to fill the requested size of plot
                self.odmr_plot_xy = np.concatenate((self.odmr_raw_data, 
                                                   np.zeros((pad_amount, 
                                                   *self.odmr_raw_data.shape[1:]))))
            else:
                self.odmr_plot_xy = self.odmr_raw_data[:self.number_of_lines, :, :]

            # Update elapsed time/sweeps
            self.elapsed_sweeps += 1
            self.elapsed_time = time.time() - self._startTime
            if self.elapsed_time >= self.run_time:
                self.stopRequested = True
            # Fire update signals
            self.sigOdmrElapsedTimeUpdated.emit(self.elapsed_time, self.elapsed_sweeps)
            self.sigOdmrPlotsUpdated.emit(self.odmr_plot_x, self.odmr_plot_y, self.odmr_plot_xy)
            self.sigNextLine.emit()
            return

    def get_odmr_channels(self):
        return self._odmr_counter.get_odmr_channels()

    def get_hw_constraints(self):
        """ Return the names of all ocnfigured fit functions.
        @return object: Hardware constraints object
        """
        constraints = self._mw_device.get_limits()
        return constraints

    def get_fit_functions(self):
        """ Return the hardware constraints/limits
        @return list(str): list of fit function names
        """
        return list(self.fc.fit_list)

    def do_fit(self, fit_function=None, x_data=None, y_data=None, channel_index=0):
        """
        Execute the currently configured fit on the measurement data. Optionally on passed data
        """
        if (x_data is None) or (y_data is None):
            x_data = self.odmr_plot_x
            y_data = self.odmr_plot_y[channel_index]

        if fit_function is not None and isinstance(fit_function, str):
            if fit_function in self.get_fit_functions():
                self.fc.set_current_fit(fit_function)
            else:
                self.fc.set_current_fit('No Fit')
                if fit_function != 'No Fit':
                    self.log.warning('Fit function "{0}" not available in ODMRLogic fit container.'
                                     ''.format(fit_function))

        self.odmr_fit_x, self.odmr_fit_y, result = self.fc.do_fit(x_data, y_data)

        if result is None:
            result_str_dict = {}
        else:
            result_str_dict = result.result_str_dict
        self.sigOdmrFitUpdated.emit(
            self.odmr_fit_x, self.odmr_fit_y, result_str_dict, self.fc.current_fit)
        return

    def save_odmr_data(self, colorscale_range=None, percentile_range=None):
        """ Saves the current ODMR data to a file."""
        timestamp = datetime.datetime.now()

        for nch, channel in enumerate(self.get_odmr_channels()):
            # two paths to save the raw data and the odmr scan data.
            filepath = self._save_logic.get_path_for_module(module_name='ODMR')
            filepath2 = self._save_logic.get_path_for_module(module_name='ODMR')

            # Label file with alphanumeric characters in channel name
            filelabel = 'ODMR_{0}'.format("".join(x for x in channel if x.isalnum()))
            filelabel2 = 'ODMR_{0}_raw'.format("".join(x for x in channel if x.isalnum()))

            # prepare the data in a dict or in an OrderedDict:
            data = OrderedDict()
            data2 = OrderedDict()
            data['frequency (Hz)'] = self.odmr_plot_x
            data['count data (counts/s)'] = self.odmr_plot_y[nch]
            data2['count data (counts/s)'] = self.odmr_raw_data[:self.elapsed_sweeps, nch, :]

            parameters = OrderedDict()
            parameters['Microwave CW Power (dBm)'] = self.cw_mw_power
            parameters['Microwave Sweep Power (dBm)'] = self.sweep_mw_power
            parameters['Acquisiton Time (s)'] = self.elapsed_time
            parameters['Number of frequency sweeps (#)'] = self.elapsed_sweeps
            parameters['Start Frequency (Hz)'] = self.mw_start
            parameters['Stop Frequency (Hz)'] = self.mw_stop
            parameters['Step size (Hz)'] = self.mw_step
            parameters['Clock Frequency (Hz)'] = self.clock_frequency
            parameters['Lock-in'] = self._lock_in_active
            parameters['Oversampling'] = self._oversampling
            parameters['Channel'] = '{0}: {1}'.format(nch, channel)
            if self.fc.current_fit != 'No Fit':
                parameters['Fit function'] = self.fc.current_fit

            # add all fit parameter to the saved data:
            for name, param in self.fc.current_fit_param.items():
                parameters[name] = str(param)

            fig = self.draw_figure(
                nch,
                cbar_range=colorscale_range,
                percentile_range=percentile_range)

            self._save_logic.save_data(data,
                                       filepath=filepath,
                                       parameters=parameters,
                                       filelabel=filelabel,
                                       fmt='%.6e',
                                       delimiter='\t',
                                       timestamp=timestamp,
                                       plotfig=fig)

            self._save_logic.save_data(data2,
                                       filepath=filepath2,
                                       parameters=parameters,
                                       filelabel=filelabel2,
                                       fmt='%.6e',
                                       delimiter='\t',
                                       timestamp=timestamp)

            self.log.info('ODMR data saved to:\n{0}'.format(filepath))
        
        mode, is_running = self._mw_device.get_status()
        self.sigOutputStateUpdated.emit(mode, is_running)
        return

    def draw_figure(self, channel_number, cbar_range=None, percentile_range=None):
        """ Draw the summary figure to save with the data.

        @param: list cbar_range: (optional) [color_scale_min, color_scale_max].
                                 If not supplied then a default of data_min to data_max
                                 will be used.

        @param: list percentile_range: (optional) Percentile range of the chosen cbar_range.

        @return: fig fig: a matplotlib figure object to be saved to file.
        """
        freq_data = self.odmr_plot_x
        count_data = self.odmr_plot_y[channel_number]
        fit_freq_vals = self.odmr_fit_x
        fit_count_vals = self.odmr_fit_y
        matrix_data = self.odmr_plot_xy[:, channel_number]

        # If no colorbar range was given, take full range of data
        if cbar_range is None:
            cbar_range = np.array([np.min(matrix_data), np.max(matrix_data)])
        else:
            cbar_range = np.array(cbar_range)

        prefix = ['', 'k', 'M', 'G', 'T']
        prefix_index = 0

        # Rescale counts data with SI prefix
        while np.max(count_data) > 1000:
            count_data = count_data / 1000
            fit_count_vals = fit_count_vals / 1000
            prefix_index = prefix_index + 1

        counts_prefix = prefix[prefix_index]

        # Rescale frequency data with SI prefix
        prefix_index = 0

        while np.max(freq_data) > 1000:
            freq_data = freq_data / 1000
            fit_freq_vals = fit_freq_vals / 1000
            prefix_index = prefix_index + 1

        mw_prefix = prefix[prefix_index]

        # Rescale matrix counts data with SI prefix
        prefix_index = 0

        while np.max(matrix_data) > 1000:
            matrix_data = matrix_data / 1000
            cbar_range = cbar_range / 1000
            prefix_index = prefix_index + 1

        cbar_prefix = prefix[prefix_index]

        # Use qudi style
        plt.style.use(self._save_logic.mpl_qd_style)

        # Create figure
        fig, (ax_mean, ax_matrix) = plt.subplots(nrows=2, ncols=1)

        ax_mean.plot(freq_data, count_data, linestyle=':', linewidth=0.5)

        # Do not include fit curve if there is no fit calculated.
        if max(fit_count_vals) > 0:
            ax_mean.plot(fit_freq_vals, fit_count_vals, marker='None')

        ax_mean.set_ylabel('Fluorescence (' + counts_prefix + 'c/s)')
        ax_mean.set_xlim(np.min(freq_data), np.max(freq_data))

        matrixplot = ax_matrix.imshow(
            matrix_data,
            cmap=plt.get_cmap('inferno'),  # reference the right place in qd
            origin='lower',
            vmin=cbar_range[0],
            vmax=cbar_range[1],
            extent=[np.min(freq_data),
                np.max(freq_data),
                0,
                self.number_of_lines
                ],
            aspect='auto',
            interpolation='nearest')

        ax_matrix.set_xlabel('Frequency (' + mw_prefix + 'Hz)')
        ax_matrix.set_ylabel('Scan #')

        # Adjust subplots to make room for colorbar
        fig.subplots_adjust(right=0.8)

        # Add colorbar axis to figure
        cbar_ax = fig.add_axes([0.85, 0.15, 0.02, 0.7])

        # Draw colorbar
        cbar = fig.colorbar(matrixplot, cax=cbar_ax)
        cbar.set_label('Fluorescence (' + cbar_prefix + 'c/s)')

        # remove ticks from colorbar for cleaner image
        cbar.ax.tick_params(which=u'both', length=0)

        # If we have percentile information, draw that to the figure
        if percentile_range is not None:
            cbar.ax.annotate(str(percentile_range[0]),
                             xy=(-0.3, 0.0),
                             xycoords='axes fraction',
                             horizontalalignment='right',
                             verticalalignment='center',
                             rotation=90
                             )
            cbar.ax.annotate(str(percentile_range[1]),
                             xy=(-0.3, 1.0),
                             xycoords='axes fraction',
                             horizontalalignment='right',
                             verticalalignment='center',
                             rotation=90
                             )
            cbar.ax.annotate('(percentile)',
                             xy=(-0.3, 0.5),
                             xycoords='axes fraction',
                             horizontalalignment='right',
                             verticalalignment='center',
                             rotation=90
                             )

        return fig

    def perform_odmr_measurement(self, freq_start, freq_step, freq_stop, power, channel, runtime,
                                 fit_function='No Fit', save_after_meas=True, name_tag=''):
        """ An independant method, which can be called by a task with the proper input values
            to perform an odmr measurement.

        @return
        """
        timeout = 30
        start_time = time.time()
        while self.module_state() != 'idle':
            time.sleep(0.5)
            timeout -= (time.time() - start_time)
            if timeout <= 0:
                self.log.error('perform_odmr_measurement failed. Logic module was still locked '
                               'and 30 sec timeout has been reached.')
                return tuple()

        # set all relevant parameter:
        self.set_sweep_parameters(freq_start, freq_stop, freq_step, power)
        self.set_runtime(runtime)

        # start the scan
        self.start_odmr_scan()

        # wait until the scan has started
        while self.module_state() != 'locked':
            time.sleep(1)
        # wait until the scan has finished
        while self.module_state() == 'locked':
            time.sleep(1)

        # Perform fit if requested
        if fit_function != 'No Fit':
            self.do_fit(fit_function, channel_index=channel)
            fit_params = self.fc.current_fit_param
        else:
            fit_params = None

        # Save data if requested
        if save_after_meas:
            self.save_odmr_data(tag=name_tag)

        return self.odmr_plot_x, self.odmr_plot_y, fit_params
Exemple #31
0
class VectorMagnetGui(GUIBase):
    #declare connectors
    power_supply_logic = Connector(interface='PowerSupplyLogic')

    def __init__(self, config, **kwargs):
        super().__init__(config=config, **kwargs)

    def on_activate(self):
        self.initMainUI()
        self._vector_logic = self.power_supply_logic()

        # Connecting user interactions
        self._mainwindow.ApplyButton.clicked.connect(self.ApplyButton)
        self._mainwindow.ResetButton.clicked.connect(self.ResetButton)
        self._mainwindow.Magnitude.valueChanged.connect(self.update_current)
        self._mainwindow.Theta.valueChanged.connect(self.update_current)
        self._mainwindow.Phi.valueChanged.connect(self.update_current)
        self._mainwindow.ChannelsOnOffBox.stateChanged.connect(
            self.check_channels_off)
        self._mainwindow.LimitSlider.valueChanged.connect(
            self.LimitSlider_changed)

    def on_deactivate(self):
        """ Deinitialisation performed during deactivation of the module.
        """

    def initMainUI(self):
        self._mainwindow = VectorMagnetMainWindow()
        self.show()

    def show(self):
        # Make main window visible and put it above all other windows.
        # Show the Main Confocal GUI:
        self._mainwindow.show()
        self._mainwindow.activateWindow()
        self._mainwindow.raise_()

    def update_display(self):
        """
        triggers the logic to update the currents
        """
        # update display
        self._vector_logic.applied_current = np.asarray([
            self._vector_logic.get_real_currents(1),
            self._vector_logic.get_real_currents(2),
            self._vector_logic.get_real_currents(3)
        ],
                                                        dtype=int)
        self._mainwindow.Disp1.display(
            str(round(self._vector_logic.applied_current[0], 4)))
        self._mainwindow.Disp2.display(
            str(round(self._vector_logic.applied_current[1], 4)))
        self._mainwindow.Disp3.display(
            str(round(self._vector_logic.applied_current[2], 4)))

    def ApplyButton(self):
        """
        apply settings to power supply
        """
        print('I was here')
        if self._mainwindow.ChannelsOnOffBox.checkState() == 0:
            # call logic
            self._vector_logic.apply_magnetic_field(1)
            # update GUI
            self.update_display()

    def ResetButton(self):
        """
        reset ALL current outputs to 0A
        """
        self._vector_logic.apply_magnetic_field(0)
        self.update_display()

    def update_current(self):
        """
        calls logic to transform get currents and updates currents spinboxes
        """
        #update field containers
        self._vector_logic.field_mag = self._mainwindow.Magnitude.value()
        self._vector_logic.field_theta = self._mainwindow.Theta.value()
        self._vector_logic.field_phi = self._mainwindow.Phi.value()
        # call logic
        self._vector_logic.Magn_to_Curr()
        # update current
        self._mainwindow.ChannelV1.setPlainText(
            str(round(self._vector_logic.current_x, 4)))
        self._mainwindow.ChannelV2.setPlainText(
            str(round(self._vector_logic.current_y, 4)))
        self._mainwindow.ChannelV3.setPlainText(
            str(round(self._vector_logic.current_z, 4)))

    def check_channels_off(self):
        #shut down channel outputs
        if self._mainwindow.ChannelsOnOffBox.checkState(
        ) == 2:  #0::unchecked, 1::partially checked, 2::checked
            self._vector_logic.shut_down_channels("Off")
            self._mainwindow.Disp1.setStyleSheet("""QLCDNumber { 
                                                    background-color: darkred; 
                                                    color: white; }""")
            self._mainwindow.Disp2.setStyleSheet("""QLCDNumber { 
                                                    background-color: darkred; 
                                                    color: white; }""")
            self._mainwindow.Disp3.setStyleSheet("""QLCDNumber { 
                                                    background-color: darkred; 
                                                    color: white; }""")
        elif self._mainwindow.ChannelsOnOffBox.checkState(
        ) == 0:  #0::unchecked, 1::partially checked, 2::checked
            self._vector_logic.shut_down_channels("On")
            self._mainwindow.Disp1.setStyleSheet("""""")
            self._mainwindow.Disp2.setStyleSheet("""""")
            self._mainwindow.Disp3.setStyleSheet("""""")

    def LimitSlider_changed(self):
        #set V-limits according to slider position
        Vlimit = self._mainwindow.LimitSlider.value() * 10
        self._vector_logic.set_Vlimit(Vlimit)
        self._mainwindow.LimitValue.setText('{}'.format(Vlimit))

    """