コード例 #1
0
ファイル: rnggui.py プロジェクト: mvchalupnik/qudi
class RNGGui(GUIBase):
    _modclass = 'rnggui'
    _modtype = 'gui'

    # declare connectors
    rnglogic = Connector(interface='RNGLogic')

    # Signals
    # sigStart = QtCore.Signal()
    # sigStop = QtCore.Signal()

    def on_activate(self):
        # Connect to logic
        self._rng_logic = self.rnglogic()

        # instantiate MainWindow
        self._mw = RNGMainWindow()

        # Signal connection to logic module
        self._mw.startButton.clicked.connect(self._rng_logic.start_monitoring)
        self._mw.stopButton.clicked.connect(self._rng_logic.stop_monitoring)

        self._mw.mean_box.valueChanged.connect(self.mean_changed)
        self._mw.noise_box.valueChanged.connect(self.noise_changed)
        self._mw.update_rate_box.valueChanged.connect(self.update_rate_changed)

        self._rng_logic.repeat_sig.connect(self.new_random_value_received)

    def on_deactivate(self):
        pass
        # self._mw.startButton.clicked.disconnect()
        # self._mw.startButton.clicked.disconnect()
        # self._mw.mean_box.valueChanged.disconnect()
        # self._mw.noise_box.valueChanged.disconnect()
        # self._mw.update_rate_box.valueChanged.disconnect()
        # self._rng_logic._monitor_thread.value_updated_sig.disconnect()

    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 mean_changed(self, new_mean):
        self._rng_logic.set_rng_params(mean=new_mean)

    def noise_changed(self, new_noise):
        self._rng_logic.set_rng_params(noise=new_noise)

    def update_rate_changed(self, new_update_rate):
        self._rng_logic.set_monitor_params(update_rate=new_update_rate)

    def new_random_value_received(self):
        try:
            new_value = self._rng_logic.get_current_value()[0]
            self._mw.display.display(new_value)
        except:
            return
コード例 #2
0
class SimpleDataLogic(GenericLogic):
    """ Logic module agreggating multiple hardware switches.
    """
    _modclass = 'smple_data'
    _modtype = 'logic'

    simpledata = Connector(interface='SimpleDataInterface')

    sigRepeat = QtCore.Signal()

    def on_activate(self):
        """ Prepare logic module for work.
        """
        self._data_logic = self.get_connector('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.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.unlock()
            return

        data = np.zeros((100, self._data_logic.getChannels()))
        data[:, 0] = 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()
コード例 #3
0
class AutomationLogic(GenericLogic):
    """ Logic module agreggating multiple hardware switches.
    """
    _modclass = 'AutomationLogic'
    _modtype = 'logic'

    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)
コード例 #4
0
class AutomationGui(GUIBase):
    """ Graphical interface for arranging tasks without using Python code. """
    _modclass = 'AutomationGui'
    _modtype = 'gui'
    ## 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()
コード例 #5
0
class SwitchGui(GUIBase):
    """ A grephical interface to mofe switches by hand and change their calibration.
    """
    _modclass = 'SwitchGui'
    _modtype = 'gui'
    ## 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()
コード例 #6
0
class SoftPIDController(GenericLogic, PIDControllerInterface):
    """
    Control a process via software PID.
    """
    _modclass = 'pidlogic'
    _modtype = 'logic'

    ## declare connectors
    process = Connector(interface='ProcessInterface')
    control = Connector(interface='ProcessControlInterface')

    # config opt
    timestep = ConfigOption(default=100)

    # status vars
    kP = StatusVar(default=1)
    kI = StatusVar(default=1)
    kD = StatusVar(default=1)
    setpoint = StatusVar(default=273.15)
    manualvalue = StatusVar(default=0)

    sigNewValue = QtCore.Signal(float)

    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.debug('{0}: {1}'.format(key, config[key]))

        #number of lines in the matrix plot
        self.NumberOfSecondsLog = 100
        self.threadlock = Mutex()

    def on_activate(self):
        """ Initialisation performed during activation of the module.
        """
        self._process = self.get_connector('process')
        self._control = self.get_connector('control')

        self.previousdelta = 0
        self.cv = self._control.getControlValue()

        self.timer = QtCore.QTimer()
        self.timer.setSingleShot(True)
        self.timer.setInterval(self.timestep)

        self.timer.timeout.connect(self._calcNextStep,
                                   QtCore.Qt.QueuedConnection)
        self.sigNewValue.connect(self._control.setControlValue)

        self.history = np.zeros([3, 5])
        self.savingState = False
        self.enable = False
        self.integrated = 0
        self.countdown = 2

        self.timer.start(self.timestep)

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

    def _calcNextStep(self):
        """ This function implements the Takahashi Type C PID
            controller: the P and D term are no longer dependent
             on the set-point, only on PV (which is Thlt).
             The D term is NOT low-pass filtered.
             This function should be called once every TS seconds.
        """
        self.pv = self._process.getProcessValue()

        if self.countdown > 0:
            self.countdown -= 1
            self.previousdelta = self.setpoint - self.pv
            print('Countdown: ', self.countdown)
        elif self.countdown == 0:
            self.countdown = -1
            self.integrated = 0
            self.enable = True

        if (self.enable):
            delta = self.setpoint - self.pv
            self.integrated += delta
            ## Calculate PID controller:
            self.P = self.kP * delta
            self.I = self.kI * self.timestep * self.integrated
            self.D = self.kD / self.timestep * (delta - self.previousdelta)

            self.cv += self.P + self.I + self.D
            self.previousdelta = delta

            ## limit contol output to maximum permissible limits
            limits = self._control.getControlLimits()
            if (self.cv > limits[1]):
                self.cv = limits[1]
            if (self.cv < limits[0]):
                self.cv = limits[0]

            self.history = np.roll(self.history, -1, axis=1)
            self.history[0, -1] = self.pv
            self.history[1, -1] = self.cv
            self.history[2, -1] = self.setpoint
            self.sigNewValue.emit(self.cv)
        else:
            self.cv = self.manualvalue
            limits = self._control.getControlLimits()
            if (self.cv > limits[1]):
                self.cv = limits[1]
            if (self.cv < limits[0]):
                self.cv = limits[0]
            self.sigNewValue.emit(self.cv)

        self.timer.start(self.timestep)

    def startLoop(self):
        """ Start the control loop. """
        self.countdown = 2

    def stopLoop(self):
        """ Stop the control loop. """
        self.countdown = -1
        self.enable = False

    def getSavingState(self):
        """ Find out if we are keeping data for saving later.

            @return bool: whether module is saving process and control data
        """
        return self.savingState

    def startSaving(self):
        """ Start saving process and control data.

            Does not do anything right now.
        """
        pass

    def saveData(self):
        """ Write process and control data to file.

            Does not do anything right now.
        """
        pass

    def get_kp(self):
        """ Return the proportional constant.

            @return float: proportional constant of PID controller
        """
        return self.kP

    def set_kp(self, kp):
        """ Set the proportional constant of the PID controller.

            @prarm float kp: proportional constant of PID controller
        """
        self.kP = kp

    def get_ki(self):
        """ Get the integration constant of the PID controller

            @return float: integration constant of the PID controller
        """
        return self.kI

    def set_ki(self, ki):
        """ Set the integration constant of the PID controller.

            @param float ki: integration constant of the PID controller
        """
        self.kI = ki

    def get_kd(self):
        """ Get the derivative constant of the PID controller

            @return float: the derivative constant of the PID controller
        """
        return self.kD

    def set_kd(self, kd):
        """ Set the derivative constant of the PID controller

            @param float kd: the derivative constant of the PID controller
        """
        self.kD = kd

    def get_setpoint(self):
        """ Get the current setpoint of the PID controller.

            @return float: current set point of the PID controller
        """
        return self.setpoint

    def set_setpoint(self, setpoint):
        """ Set the current setpoint of the PID controller.

            @param float setpoint: new set point of the PID controller
        """
        self.setpoint = setpoint

    def get_manual_value(self):
        """ Return the control value for manual mode.

            @return float: control value for manual mode
        """
        return self.manualvalue

    def set_manual_value(self, manualvalue):
        """ Set the control value for manual mode.

            @param float manualvalue: control value for manual mode of controller
        """
        self.manualvalue = manualvalue
        limits = self._control.getControlLimits()
        if (self.manualvalue > limits[1]):
            self.manualvalue = limits[1]
        if (self.manualvalue < limits[0]):
            self.manualvalue = limits[0]

    def get_enabled(self):
        """ See if the PID controller is controlling a process.

            @return bool: whether the PID controller is preparing to or conreolling a process
        """
        return self.enable or self.countdown >= 0

    def set_enabled(self, enabled):
        """ Set the state of the PID controller.

            @param bool enabled: desired state of PID controller
        """
        if enabled and not self.enable and self.countdown == -1:
            self.startLoop()
        if not enabled and self.enable:
            self.stopLoop()

    def get_control_limits(self):
        """ Get the minimum and maximum value of the control actuator.

            @return list(float): (minimum, maximum) values of the control actuator
        """
        return self._control.getControlLimits()

    def set_control_limits(self, limits):
        """ Set the minimum and maximum value of the control actuator.

            @param list(float) limits: (minimum, maximum) values of the control actuator

            This function does nothing, control limits are handled by the control module
        """
        pass

    def get_control_value(self):
        """ Get current control output value.

            @return float: control output value
        """
        return self.cv

    def get_process_value(self):
        """ Get current process input value.

            @return float: current process input value
        """
        return self.pv

    def get_extra(self):
        """ Extra information about the controller state.

            @return dict: extra informatin about internal controller state

            Do not depend on the output of this function, not every field
            exists for every PID controller.
        """
        return {'P': self.P, 'I': self.I, 'D': self.D}
class ODMRCounterMicrowaveInterfuse(GenericLogic, ODMRCounterInterface,
                                    MicrowaveInterface):
    """
    Interfuse to enable a software trigger of the microwave source but still
    having a hardware timed counter.

    This interfuse connects the ODMR logic with a slowcounter and a microwave
    device.
    """

    _modclass = 'ODMRCounterMicrowaveInterfuse'
    _modtype = 'interfuse'

    slowcounter = Connector(interface='SlowCounterInterface')
    microwave = Connector(interface='MicrowaveInterface')

    def __init__(self, config, **kwargs):
        super().__init__(config=config, **kwargs)
        self._pulse_out_channel = 'dummy'
        self._lock_in_active = False
        self._oversampling = 10
        self._odmr_length = 100

    def on_activate(self):
        """ Initialisation performed during activation of the module."""
        self._mw_device = self.microwave()
        self._sc_device = self.slowcounter()  # slow counter device
        pass

    def on_deactivate(self):
        pass

    ### ODMR counter interface commands

    def set_up_odmr_clock(self, clock_frequency=None, clock_channel=None):
        """ Configures the hardware clock of the NiDAQ card to give the timing.

        @param float clock_frequency: if defined, this sets the frequency of the
                                      clock
        @param str clock_channel: if defined, this is the physical channel of
                                  the clock

        @return int: error code (0:OK, -1:error)
        """
        return self._sc_device.set_up_clock(clock_frequency=clock_frequency,
                                            clock_channel=clock_channel)

    def set_up_odmr(self,
                    counter_channel=None,
                    photon_source=None,
                    clock_channel=None,
                    odmr_trigger_channel=None):
        """ Configures the actual counter with a given clock.

        @param str counter_channel: if defined, this is the physical channel of
                                    the counter
        @param str photon_source: if defined, this is the physical channel where
                                  the photons are to count from
        @param str clock_channel: if defined, this specifies the clock for the
                                  counter
        @param str odmr_trigger_channel: if defined, this specifies the trigger
                                         output for the microwave

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

        return self._sc_device.set_up_counter(counter_channels=counter_channel,
                                              sources=photon_source,
                                              clock_channel=clock_channel,
                                              counter_buffer=None)

    def set_odmr_length(self, length=100):
        """Set up the trigger sequence for the ODMR and the triggered microwave.

        @param int length: length of microwave sweep in pixel

        @return int: error code (0:OK, -1:error)
        """
        self._odmr_length = length
        return 0

    def count_odmr(self, length=100):
        """ Sweeps the microwave and returns the counts on that sweep.

        @param int length: length of microwave sweep in pixel

        @return float[]: the photon counts per second
        """

        counts = np.zeros((len(self.get_odmr_channels()), length))
        # self.trigger()
        for i in range(length):
            self.trigger()
            counts[:, i] = self._sc_device.get_counter(samples=1)[0]
        self.trigger()
        return False, counts

    def close_odmr(self):
        """ Close the odmr and clean up afterwards.

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

    def close_odmr_clock(self):
        """ Close the odmr and clean up afterwards.

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

    def get_odmr_channels(self):
        """ Return a list of channel names.

        @return list(str): channels recorded during ODMR measurement
        """
        return self._sc_device.get_counter_channels()

    ### ----------- Microwave interface commands -----------

    def trigger(self):

        return self._mw_device.trigger()

    def off(self):
        """
        Switches off any microwave output.
        Must return AFTER the device is actually stopped.

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

    def get_status(self):
        """
        Gets the current status of the MW source, i.e. the mode (cw, list or sweep) and
        the output state (stopped, running)

        @return str, bool: mode ['cw', 'list', 'sweep'], is_running [True, False]
        """
        return self._mw_device.get_status()

    def get_power(self):
        """
        Gets the microwave output power for the currently active mode.

        @return float: the output power in dBm
        """
        return self._mw_device.get_power()

    def get_frequency(self):
        """
        Gets the frequency of the microwave output.
        Returns single float value if the device is in cw mode.
        Returns list like [start, stop, step] if the device is in sweep mode.
        Returns list of frequencies if the device is in list mode.

        @return [float, list]: frequency(s) currently set for this device in Hz
        """
        return self._mw_device.get_frequency()

    def cw_on(self):
        """
        Switches on cw microwave output.
        Must return AFTER the device is actually running.

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

    def set_cw(self, frequency=None, power=None):
        """
        Configures the device for cw-mode and optionally sets frequency and/or power

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

        @return tuple(float, float, str): with the relation
            current frequency in Hz,
            current power in dBm,
            current mode
        """
        return self._mw_device.set_cw(frequency=frequency, power=power)

    def list_on(self):
        """
        Switches on the list mode microwave output.
        Must return AFTER the device is actually running.

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

    def set_list(self, frequency=None, power=None):
        """
        Configures the device for list-mode and optionally sets frequencies and/or power

        @param list frequency: list of frequencies in Hz
        @param float power: MW power of the frequency list in dBm

        @return list, float, str: current frequencies in Hz, current power in dBm, current mode
        """
        return self._mw_device.set_list(frequency=frequency, power=power)

    def reset_listpos(self):
        """
        Reset of MW list mode position to start (first frequency step)

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

    def sweep_on(self):
        """ Switches on the sweep mode.

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

    def set_sweep(self, start=None, stop=None, step=None, power=None):
        """
        Configures the device for sweep-mode and optionally sets frequency start/stop/step
        and/or power

        @return float, float, float, float, str: current start frequency in Hz,
                                                 current stop frequency in Hz,
                                                 current frequency step in Hz,
                                                 current power in dBm,
                                                 current mode
        """
        return self._mw_device.set_sweep(start=start,
                                         stop=stop,
                                         step=step,
                                         power=power)

    def reset_sweeppos(self):
        """
        Reset of MW sweep mode position to start (start frequency)

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

    def set_ext_trigger(self, pol, timing):
        """ Set the external trigger for this device with proper polarization.

        @param TriggerEdge pol: polarisation of the trigger (basically rising edge or falling edge)
        @param timing: estimated time between triggers

        @return object: current trigger polarity [TriggerEdge.RISING, TriggerEdge.FALLING]
        """
        return self._mw_device.set_ext_trigger(pol=pol, timing=timing)

    def get_limits(self):
        """ Return the device-specific limits in a nested dictionary.

          @return MicrowaveLimits: Microwave limits object
        """
        return self._mw_device.get_limits()

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

    @oversampling.setter
    def oversampling(self, val):
        if not isinstance(val, (int, float)):
            self.log.error('oversampling has to be int of float.')
        else:
            self._oversampling = int(val)

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

    @lock_in_active.setter
    def lock_in_active(self, val):
        if not isinstance(val, bool):
            self.log.error('lock_in_active has to be boolean.')
        else:
            self._lock_in_active = val
            if self._lock_in_active:
                self.log.warn('Lock-In is not implemented')
コード例 #8
0
class PIDLogic(GenericLogic):
    """
    Control a process via software PID.
    """
    _modclass = 'pidlogic'
    _modtype = 'logic'

    ## declare connectors
    controller = Connector(interface='PIDControllerInterface')
    savelogic = Connector(interface='SaveLogic')

    # status vars
    bufferLength = StatusVar('bufferlength', 1000)
    timestep = StatusVar(default=100)

    # signals
    sigUpdateDisplay = QtCore.Signal()

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

        #number of lines in the matrix plot
        self.NumberOfSecondsLog = 100
        self.threadlock = Mutex()

    def on_activate(self):
        """ Initialisation performed during activation of the module.
        """
        self._controller = self.get_connector('controller')
        self._save_logic = self.get_connector('savelogic')

        self.history = np.zeros([3, self.bufferLength])
        self.savingState = False
        self.enabled = False
        self.timer = QtCore.QTimer()
        self.timer.setSingleShot(True)
        self.timer.setInterval(self.timestep)
        self.timer.timeout.connect(self.loop)

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

    def getBufferLength(self):
        """ Get the current data buffer length.
        """
        return self.bufferLength

    def startLoop(self):
        """ Start the data recording loop.
        """
        self.enabled = True
        self.timer.start(self.timestep)

    def stopLoop(self):
        """ Stop the data recording loop.
        """
        self.enabled = False

    def loop(self):
        """ Execute step in the data recording loop: save one of each control and process values
        """
        self.history = np.roll(self.history, -1, axis=1)
        self.history[0, -1] = self._controller.get_process_value()
        self.history[1, -1] = self._controller.get_control_value()
        self.history[2, -1] = self._controller.get_setpoint()
        self.sigUpdateDisplay.emit()
        if self.enabled:
            self.timer.start(self.timestep)

    def getSavingState(self):
        """ Return whether we are saving data

            @return bool: whether we are saving data right now
        """
        return self.savingState

    def startSaving(self):
        """ Start saving data.

            Function does nothing right now.
        """
        pass

    def saveData(self):
        """ Stop saving data and write data to file.

            Function does nothing right now.
        """
        pass

    def setBufferLength(self, newBufferLength):
        """ Change buffer length to new value.

            @param int newBufferLength: new buffer length
        """
        self.bufferLength = newBufferLength
        self.history = np.zeros([3, self.bufferLength])

    def get_kp(self):
        """ Return the proportional constant.

            @return float: proportional constant of PID controller
        """
        return self._controller.get_kp()

    def set_kp(self, kp):
        """ Set the proportional constant of the PID controller.

            @prarm float kp: proportional constant of PID controller
        """
        return self._controller.set_kp(kp)

    def get_ki(self):
        """ Get the integration constant of the PID controller

            @return float: integration constant of the PID controller
        """
        return self._controller.get_ki()

    def set_ki(self, ki):
        """ Set the integration constant of the PID controller.

            @param float ki: integration constant of the PID controller
        """
        return self._controller.set_ki(ki)

    def get_kd(self):
        """ Get the derivative constant of the PID controller

            @return float: the derivative constant of the PID controller
        """
        return self._controller.get_kd()

    def set_kd(self, kd):
        """ Set the derivative constant of the PID controller

            @param float kd: the derivative constant of the PID controller
        """
        return self._controller.set_kd(kd)

    def get_setpoint(self):
        """ Get the current setpoint of the PID controller.

            @return float: current set point of the PID controller
        """
        return self.history[2, -1]

    def set_setpoint(self, setpoint):
        """ Set the current setpoint of the PID controller.

            @param float setpoint: new set point of the PID controller
        """
        self._controller.set_setpoint(setpoint)

    def get_manual_value(self):
        """ Return the control value for manual mode.

            @return float: control value for manual mode
        """
        return self._controller.get_manual_value()

    def set_manual_value(self, manualvalue):
        """ Set the control value for manual mode.

            @param float manualvalue: control value for manual mode of controller
        """
        return self._controller.set_manual_value(manualvalue)

    def get_enabled(self):
        """ See if the PID controller is controlling a process.

            @return bool: whether the PID controller is preparing to or conreolling a process
        """
        return self.enabled

    def set_enabled(self, enabled):
        """ Set the state of the PID controller.

            @param bool enabled: desired state of PID controller
        """
        if enabled and not self.enabled:
            self.startLoop()
        if not enabled and self.enabled:
            self.stopLoop()

    def get_control_limits(self):
        """ Get the minimum and maximum value of the control actuator.

            @return list(float): (minimum, maximum) values of the control actuator
        """
        return self._controller.get_control_limits()

    def set_control_limits(self, limits):
        """ Set the minimum and maximum value of the control actuator.

            @param list(float) limits: (minimum, maximum) values of the control actuator

            This function does nothing, control limits are handled by the control module
        """
        return self._controller.set_control_limits(limits)

    def get_pv(self):
        """ Get current process input value.

            @return float: current process input value
        """
        return self.history[0, -1]

    def get_cv(self):
        """ Get current control output value.

            @return float: control output value
        """
        return self.history[1, -1]
コード例 #9
0
class WavemeterLogGui(GUIBase):
    _modclass = 'WavemeterLogGui'
    _modtype = 'gui'

    ## declare connectors
    wavemeterloggerlogic1 = Connector(interface='WavemeterLoggerLogic')
    savelogic = Connector(interface='SaveLogic')

    sigStartCounter = QtCore.Signal()
    sigStopCounter = QtCore.Signal()
    sigFitChanged = QtCore.Signal(str)
    sigDoFit = QtCore.Signal()

    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]))

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

        self._wm_logger_logic = self.get_connector('wavemeterloggerlogic1')
        self._save_logic = self.get_connector('savelogic')

        # setting up the window
        self._mw = WavemeterLogWindow()

        ## giving the plots names allows us to link their axes together
        self._pw = self._mw.plotWidget  # pg.PlotWidget(name='Counter1')
        self._plot_item = self._pw.plotItem

        ## create a new ViewBox, link the right axis to its coordinate system
        self._right_axis = pg.ViewBox()
        self._plot_item.showAxis('right')
        self._plot_item.scene().addItem(self._right_axis)
        self._plot_item.getAxis('right').linkToView(self._right_axis)
        self._right_axis.setXLink(self._plot_item)

        ## create a new ViewBox, link the right axis to its coordinate system
        self._top_axis = pg.ViewBox()
        self._plot_item.showAxis('top')
        self._plot_item.scene().addItem(self._top_axis)
        self._plot_item.getAxis('top').linkToView(self._top_axis)
        self._top_axis.setYLink(self._plot_item)
        self._top_axis.invertX(b=True)

        # handle resizing of any of the elements
        self._update_plot_views()
        self._plot_item.vb.sigResized.connect(self._update_plot_views)

        self._pw.setLabel('left', 'Fluorescence', units='counts/s')
        self._pw.setLabel('right', 'Number of Points', units='#')
        self._pw.setLabel('bottom', 'Wavelength', units='nm')
        self._pw.setLabel('top', 'Relative Frequency', units='Hz')

        self._mw.actionStop_resume_scan.triggered.connect(
            self.stop_resume_clicked)
        self._mw.actionSave_histogram.triggered.connect(self.save_clicked)
        self._mw.actionStart_scan.triggered.connect(self.start_clicked)
        self._mw.actionAuto_range.triggered.connect(self.set_auto_range)

        # defining the parameters to edit
        self._mw.binSpinBox.setValue(self._wm_logger_logic.get_bins())
        self._mw.binSpinBox.editingFinished.connect(self.recalculate_histogram)

        self._mw.minDoubleSpinBox.setValue(
            self._wm_logger_logic.get_min_wavelength())
        self._mw.minDoubleSpinBox.editingFinished.connect(
            self.recalculate_histogram)

        self._mw.maxDoubleSpinBox.setValue(
            self._wm_logger_logic.get_max_wavelength())
        self._mw.maxDoubleSpinBox.editingFinished.connect(
            self.recalculate_histogram)

        self._mw.show()

        ## Create an empty plot curve to be filled later, set its pen
        self.curve_data_points = pg.PlotDataItem(pen=pg.mkPen(palette.c1),
                                                 symbol=None)

        self.curve_nm_counts = pg.PlotDataItem(pen=pg.mkPen(
            palette.c2, style=QtCore.Qt.DotLine),
                                               symbol=None)

        self.curve_hz_counts = pg.PlotDataItem(pen=pg.mkPen(
            palette.c6, style=QtCore.Qt.DotLine),
                                               symbol=None)

        self.curve_envelope = pg.PlotDataItem(pen=pg.mkPen(
            palette.c3, style=QtCore.Qt.DotLine),
                                              symbol=None)

        self.curve_fit = pg.PlotDataItem(pen=pg.mkPen(palette.c2, width=3),
                                         symbol=None)

        self._pw.addItem(self.curve_data_points)
        self._pw.addItem(self.curve_envelope)
        self._right_axis.addItem(self.curve_nm_counts)
        self._top_axis.addItem(self.curve_hz_counts)

        # scatter plot for time series
        self._spw = self._mw.scatterPlotWidget
        self._spi = self._spw.plotItem
        self._spw.setLabel('bottom', 'Wavelength', units='nm')
        self._spw.setLabel('left', 'Time', units='s')
        self._scatterplot = pg.ScatterPlotItem(size=10,
                                               pen=pg.mkPen(None),
                                               brush=pg.mkBrush(
                                                   255, 255, 255, 20))
        self._spw.addItem(self._scatterplot)
        self._spw.setXLink(self._plot_item)
        self._wm_logger_logic.sig_new_data_point.connect(self.add_data_point)

        self._wm_logger_logic.sig_data_updated.connect(self.updateData)

        # fit settings
        self._fsd = FitSettingsDialog(self._wm_logger_logic.fc)
        self._fsd.sigFitsUpdated.connect(
            self._mw.fit_methods_ComboBox.setFitFunctions)
        self._fsd.applySettings()

        self._mw.actionFit_settings.triggered.connect(self._fsd.show)
        self._mw.do_fit_PushButton.clicked.connect(self.doFit)
        self.sigDoFit.connect(self._wm_logger_logic.do_fit)
        self.sigFitChanged.connect(self._wm_logger_logic.fc.set_current_fit)
        self._wm_logger_logic.sig_fit_updated.connect(self.updateFit)

    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_()

    def updateData(self):
        """ The function that grabs the data and sends it to the plot.
        """
        self._mw.wavelengthLabel.setText('{0:,.6f} nm '.format(
            self._wm_logger_logic.current_wavelength))
        self._mw.autoMinLabel.setText('Minimum: {0:3.6f} (nm)   '.format(
            self._wm_logger_logic.intern_xmin))
        self._mw.autoMaxLabel.setText('Maximum: {0:3.6f} (nm)   '.format(
            self._wm_logger_logic.intern_xmax))

        x_axis = self._wm_logger_logic.histogram_axis
        x_axis_hz = (3.0e17 / (x_axis) - 6.0e17 /
                     (self._wm_logger_logic.get_max_wavelength() +
                      self._wm_logger_logic.get_min_wavelength()))

        plotdata = np.array(self._wm_logger_logic.counts_with_wavelength)
        if len(plotdata.shape) > 1 and plotdata.shape[1] == 3:
            self.curve_data_points.setData(plotdata[:, 2:0:-1])

        self.curve_nm_counts.setData(x=x_axis,
                                     y=self._wm_logger_logic.histogram)
        self.curve_hz_counts.setData(x=x_axis_hz,
                                     y=self._wm_logger_logic.histogram)
        self.curve_envelope.setData(x=x_axis,
                                    y=self._wm_logger_logic.envelope_histogram)

    @QtCore.Slot()
    def doFit(self):
        self.sigFitChanged.emit(
            self._mw.fit_methods_ComboBox.getCurrentFit()[0])
        self.sigDoFit.emit()

    @QtCore.Slot()
    def updateFit(self):
        """ Do the configured fit and show it in the plot """
        fit_name = self._wm_logger_logic.fc.current_fit
        fit_result = self._wm_logger_logic.fc.current_fit_result
        fit_param = self._wm_logger_logic.fc.current_fit_param

        if fit_result is not None:
            # display results as formatted text
            self._mw.fit_results_DisplayWidget.clear()
            try:
                formated_results = units.create_formatted_output(
                    fit_result.result_str_dict)
            except:
                formated_results = 'this fit does not return formatted results'
            self._mw.fit_results_DisplayWidget.setPlainText(formated_results)

        if fit_name is not None:
            self._mw.fit_methods_ComboBox.setCurrentFit(fit_name)

        # check which fit method is used and show the curve in the plot accordingly
        if fit_name != 'No Fit':
            self.curve_fit.setData(x=self._wm_logger_logic.wlog_fit_x,
                                   y=self._wm_logger_logic.wlog_fit_y)

            if self.curve_fit not in self._mw.plotWidget.listDataItems():
                self._mw.plotWidget.addItem(self.curve_fit)
        else:
            if self.curve_fit in self._mw.plotWidget.listDataItems():
                self._mw.plotWidget.removeItem(self.curve_fit)

    def add_data_point(self, point):
        if len(point) >= 3:
            spts = [{
                'pos': (point[0], point[1]),
                'size': 5,
                'brush': pg.intColor(point[2] / 100, 255)
            }]
            self._scatterplot.addPoints(spts)

    def stop_resume_clicked(self):
        """ Handling the Start button to stop and restart the counter.
        """
        # If running, then we stop the measurement and enable inputs again
        if self._wm_logger_logic.module_state() == 'running':
            self._mw.actionStop_resume_scan.setText('Resume')
            self._wm_logger_logic.stop_scanning()
            self._mw.actionStop_resume_scan.setEnabled(True)
            self._mw.actionStart_scan.setEnabled(True)
            self._mw.binSpinBox.setEnabled(True)
        # Otherwise, we start a measurement and disable some inputs.
        else:
            self._mw.actionStop_resume_scan.setText('Stop')
            self._wm_logger_logic.start_scanning(resume=True)
            self._mw.actionStart_scan.setEnabled(False)
            self._mw.binSpinBox.setEnabled(False)

    def start_clicked(self):
        """ Handling resume of the scanning without resetting the data.
        """
        if self._wm_logger_logic.module_state() == 'idle':
            self._scatterplot.clear()
            self._wm_logger_logic.start_scanning()

            # Enable the stop button once a scan starts.
            self._mw.actionStop_resume_scan.setText('Stop')
            self._mw.actionStop_resume_scan.setEnabled(True)
            self._mw.actionStart_scan.setEnabled(False)
            self._mw.binSpinBox.setEnabled(False)
            self.recalculate_histogram()
        else:
            self.log.error('Cannot scan, since a scan is alredy running.')

    def save_clicked(self):
        """ Handling the save button to save the data into a file.
        """

        timestamp = datetime.datetime.now()
        filepath = self._save_logic.get_path_for_module(
            module_name='WavemeterLogger')
        filename = os.path.join(
            filepath,
            timestamp.strftime('%Y%m%d-%H%M-%S_wavemeter_log_thumbnail'))

        exporter = pg.exporters.SVGExporter(self._pw.plotItem)
        exporter.export(filename + '.svg')

        self._wm_logger_logic.save_data(timestamp=timestamp)

    def recalculate_histogram(self):
        self._wm_logger_logic.recalculate_histogram(
            bins=self._mw.binSpinBox.value(),
            xmin=self._mw.minDoubleSpinBox.value(),
            xmax=self._mw.maxDoubleSpinBox.value())

    def set_auto_range(self):
        self._mw.minDoubleSpinBox.setValue(self._wm_logger_logic.intern_xmin)
        self._mw.maxDoubleSpinBox.setValue(self._wm_logger_logic.intern_xmax)
        self.recalculate_histogram()

    ## Handle view resizing
    def _update_plot_views(self):
        ## view has resized; update auxiliary views to match
        self._right_axis.setGeometry(self._plot_item.vb.sceneBoundingRect())
        self._top_axis.setGeometry(self._plot_item.vb.sceneBoundingRect())

        ## need to re-update linked axes since this was called
        ## incorrectly while views had different shapes.
        ## (probably this should be handled in ViewBox.resizeEvent)
        self._right_axis.linkedViewChanged(self._plot_item.vb,
                                           self._right_axis.XAxis)
        self._top_axis.linkedViewChanged(self._plot_item.vb,
                                         self._top_axis.YAxis)
コード例 #10
0
class WavemeterLoggerLogic(GenericLogic):
    """This logic module gathers data from wavemeter and the counter logic.
    """

    sig_data_updated = QtCore.Signal()
    sig_update_histogram_next = QtCore.Signal(bool)
    sig_handle_timer = QtCore.Signal(bool)
    sig_new_data_point = QtCore.Signal(list)
    sig_fit_updated = QtCore.Signal()

    _modclass = 'laserscanninglogic'
    _modtype = 'logic'

    # declare connectors
    wavemeter1 = Connector(interface='WavemeterInterface')
    counterlogic = Connector(interface='CounterLogic')
    savelogic = Connector(interface='SaveLogic')
    fitlogic = Connector(interface='FitLogic')

    # config opts
    _logic_acquisition_timing = ConfigOption('logic_acquisition_timing',
                                             20.0,
                                             missing='warn')
    _logic_update_timing = ConfigOption('logic_update_timing',
                                        100.0,
                                        missing='warn')

    def __init__(self, config, **kwargs):
        """ Create WavemeterLoggerLogic object with connectors.

          @param dict config: module configuration
          @param dict kwargs: optional parameters
        """
        super().__init__(config=config, **kwargs)

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

        self._acqusition_start_time = 0
        self._bins = 200
        self._data_index = 0

        self._recent_wavelength_window = [0, 0]
        self.counts_with_wavelength = []

        self._xmin = 650
        self._xmax = 750
        # internal min and max wavelength determined by the measured wavelength
        self.intern_xmax = -1.0
        self.intern_xmin = 1.0e10
        self.current_wavelength = 0

    def on_activate(self):
        """ Initialisation performed during activation of the module.
        """
        self._wavelength_data = []

        self.stopRequested = False

        self._wavemeter_device = self.get_connector('wavemeter1')
        #        print("Counting device is", self._counting_device)

        self._save_logic = self.get_connector('savelogic')
        self._counter_logic = self.get_connector('counterlogic')

        self._fit_logic = self.get_connector('fitlogic')
        self.fc = self._fit_logic.make_fit_container('Wavemeter counts', '1d')
        self.fc.set_units(['Hz', 'c/s'])

        if 'fits' in self._statusVariables and isinstance(
                self._statusVariables['fits'], dict):
            self.fc.load_from_dict(self._statusVariables['fits'])
        else:
            d1 = OrderedDict()
            d1['Lorentzian peak'] = {
                'fit_function': 'lorentzian',
                'estimator': 'peak'
            }
            d1['Two Lorentzian peaks'] = {
                'fit_function': 'lorentziandouble',
                'estimator': 'peak'
            }
            d1['Two Gaussian peaks'] = {
                'fit_function': 'gaussiandouble',
                'estimator': 'peak'
            }
            default_fits = OrderedDict()
            default_fits['1d'] = d1
            self.fc.load_from_dict(default_fits)

        # create a new x axis from xmin to xmax with bins points
        self.histogram_axis = np.arange(self._xmin, self._xmax,
                                        (self._xmax - self._xmin) / self._bins)
        self.histogram = np.zeros(self.histogram_axis.shape)
        self.envelope_histogram = np.zeros(self.histogram_axis.shape)

        self.sig_update_histogram_next.connect(
            self._attach_counts_to_wavelength, QtCore.Qt.QueuedConnection)

        # fit data
        self.wlog_fit_x = np.linspace(self._xmin, self._xmax, self._bins * 5)
        self.wlog_fit_y = np.zeros(self.wlog_fit_x.shape)

        # create an indepentent thread for the hardware communication
        self.hardware_thread = QtCore.QThread()

        # create an object for the hardware communication and let it live on the new thread
        self._hardware_pull = HardwarePull(self)
        self._hardware_pull.moveToThread(self.hardware_thread)

        # connect the signals in and out of the threaded object
        self.sig_handle_timer.connect(self._hardware_pull.handle_timer)

        # start the event loop for the hardware
        self.hardware_thread.start()
        self.last_point_time = time.time()

    def on_deactivate(self):
        """ Deinitialisation performed during deactivation of the module.
        """
        if self.module_state() != 'idle' and self.module_state(
        ) != 'deactivated':
            self.stop_scanning()
        self.hardware_thread.quit()
        self.sig_handle_timer.disconnect()

        if len(self.fc.fit_list) > 0:
            self._statusVariables['fits'] = self.fc.save_to_dict()

    def get_max_wavelength(self):
        """ Current maximum wavelength of the scan.

            @return float: current maximum wavelength
        """
        return self._xmax

    def get_min_wavelength(self):
        """ Current minimum wavelength of the scan.

            @return float: current minimum wavelength
        """
        return self._xmin

    def get_bins(self):
        """ Current number of bins in the spectrum.

            @return int: current number of bins in the scan
        """
        return self._bins

    def recalculate_histogram(self, bins=None, xmin=None, xmax=None):
        """ Recalculate the current spectrum from raw data.

            @praram int bins: new number of bins
            @param float xmin: new minimum wavelength
            @param float xmax: new maximum wavelength
        """
        if bins is not None:
            self._bins = bins
        if xmin is not None:
            self._xmin = xmin
        if xmax is not None:
            self._xmax = xmax

        # create a new x axis from xmin to xmax with bins points
        self.rawhisto = np.zeros(self._bins)
        self.envelope_histogram = np.zeros(self._bins)
        self.sumhisto = np.ones(self._bins) * 1.0e-10
        self.histogram_axis = np.linspace(self._xmin, self._xmax, self._bins)
        self.sig_update_histogram_next.emit(True)

    def get_fit_functions(self):
        """ Return the names of all ocnfigured fit functions.
        @return list(str): list of fit function names
        """
        return self.fc.fit_list.keys()

    def do_fit(self):
        """ Execute the currently configured fit
        """
        self.wlog_fit_x, self.wlog_fit_y, result = self.fc.do_fit(
            self.histogram_axis, self.histogram)

        self.sig_fit_updated.emit()
        self.sig_data_updated.emit()

    def start_scanning(self, resume=False):
        """ Prepare to start counting:
            zero variables, change state and start counting "loop"

            @param bool resume: whether to resume measurement
        """

        self.module_state.run()

        if self._counter_logic.module_state() == 'idle':
            self._counter_logic.startCount()

        if self._counter_logic.get_saving_state():
            self._counter_logic.save_data()

        self._wavemeter_device.start_acqusition()

        self._counter_logic.start_saving(resume=resume)

        if not resume:
            self._acqusition_start_time = self._counter_logic._saving_start_time
            self._wavelength_data = []

            self.data_index = 0

            self._recent_wavelength_window = [0, 0]
            self.counts_with_wavelength = []

            self.rawhisto = np.zeros(self._bins)
            self.sumhisto = np.ones(self._bins) * 1.0e-10
            self.intern_xmax = -1.0
            self.intern_xmin = 1.0e10
            self.recent_avg = [0, 0, 0]
            self.recent_count = 0

        # start the measuring thread
        self.sig_handle_timer.emit(True)
        self._complete_histogram = True
        self.sig_update_histogram_next.emit(False)

        return 0

    def stop_scanning(self):
        """ Set a flag to request stopping counting.
        """

        if not self.module_state() == 'idle':
            # self._wavemeter_device.stop_acqusition()
            # stop the measurement thread
            self.sig_handle_timer.emit(False)
            # set status to idle again
            self.module_state.stop()

        if self._counter_logic.get_saving_state():
            self._counter_logic.save_data(to_file=False)

        return 0

    def _attach_counts_to_wavelength(self, complete_histogram):
        """ Interpolate a wavelength value for each photon count value.  This process assumes that
        the wavelength is varying smoothly and fairly continuously, which is sensible for most
        measurement conditions.

        Recent count values are those recorded AFTER the previous stitch operation, but BEFORE the
        most recent wavelength value (do not extrapolate beyond the current wavelength
        information).
        """

        # If there is not yet any wavelength data, then wait and signal next loop
        if len(self._wavelength_data) == 0:
            time.sleep(self._logic_update_timing * 1e-3)
            self.sig_data_updated.emit()
            return

        # The end of the recent_wavelength_window is the time of the latest wavelength data
        self._recent_wavelength_window[1] = self._wavelength_data[-1][0]

        # (speed-up) We only need to worry about "recent" counts, because as the count data gets
        # very long all the earlier points will already be attached to wavelength values.
        count_recentness = 100  # TODO: calculate this from count_freq and wavemeter refresh rate

        # TODO: Does this depend on things, or do we loop fast enough to get every wavelength value?
        wavelength_recentness = np.min([5, len(self._wavelength_data)])

        recent_counts = np.array(
            self._counter_logic._data_to_save[-count_recentness:])
        recent_wavelengths = np.array(
            self._wavelength_data[-wavelength_recentness:])

        # The latest counts are those recorded during the recent_wavelength_window
        count_idx = [0, 0]
        count_idx[0] = np.searchsorted(recent_counts[:, 0],
                                       self._recent_wavelength_window[0])
        count_idx[1] = np.searchsorted(recent_counts[:, 0],
                                       self._recent_wavelength_window[1])

        latest_counts = recent_counts[count_idx[0]:count_idx[1]]

        # Interpolate to obtain wavelength values at the times of each count
        interpolated_wavelengths = np.interp(latest_counts[:, 0],
                                             xp=recent_wavelengths[:, 0],
                                             fp=recent_wavelengths[:, 1])

        # Stitch interpolated wavelength into latest counts array
        latest_stitched_data = np.insert(latest_counts,
                                         2,
                                         values=interpolated_wavelengths,
                                         axis=1)

        # Add this latest data to the list of counts vs wavelength
        self.counts_with_wavelength += latest_stitched_data.tolist()

        # The start of the recent data window for the next round will be the end of this one.
        self._recent_wavelength_window[0] = self._recent_wavelength_window[1]

        # Run the old update histogram method to keep duplicate data
        self._update_histogram(complete_histogram)

        # Signal that data has been updated
        self.sig_data_updated.emit()

        # Wait and repeat if measurement is ongoing
        time.sleep(self._logic_update_timing * 1e-3)

        if self.module_state() == 'running':
            self.sig_update_histogram_next.emit(False)

    def _update_histogram(self, complete_histogram):
        """ Calculate new points for the histogram.

        @param bool complete_histogram: should the complete histogram be recalculated, or just the
                                        most recent data?
        @return:
        """

        # If things like num_of_bins have changed, then recalculate the complete histogram
        # Note: The histogram may be recalculated (bins changed, etc) from the stitched data.
        # There is no need to recompute the interpolation for the stitched data.
        if complete_histogram:
            count_window = len(self._counter_logic._data_to_save)
            self._data_index = 0
            self.log.info('Recalcutating Laser Scanning Histogram for: '
                          '{0:d} counts and {1:d} wavelength.'.format(
                              count_window, len(self._wavelength_data)))
        else:
            count_window = min(100, len(self._counter_logic._data_to_save))

        if count_window < 2:
            time.sleep(self._logic_update_timing * 1e-3)
            self.sig_update_histogram_next.emit(False)
            return

        temp = np.array(self._counter_logic._data_to_save[-count_window:])

        # only do something if there is wavelength data to work with
        if len(self._wavelength_data) > 0:

            for i in self._wavelength_data[self._data_index:]:
                self._data_index += 1

                if i[1] < self._xmin or i[1] > self._xmax:
                    continue

                # calculate the bin the new wavelength needs to go in
                newbin = np.digitize([i[1]], self.histogram_axis)[0]
                # if the bin make no sense, start from the beginning
                if newbin > len(self.rawhisto) - 1:
                    continue

                # sum the counts in rawhisto and count the occurence of the bin in sumhisto
                interpolation = np.interp(i[0], xp=temp[:, 0], fp=temp[:, 1])
                self.rawhisto[newbin] += interpolation
                self.sumhisto[newbin] += 1.0

                self.envelope_histogram[newbin] = np.max(
                    [interpolation, self.envelope_histogram[newbin]])

                datapoint = [i[1], i[0], interpolation]
                if time.time() - self.last_point_time > 1:
                    self.sig_new_data_point.emit(self.recent_avg)
                    self.last_point_time = time.time()
                    self.recent_count = 0
                else:
                    self.recent_count += 1
                    for j in range(3):
                        self.recent_avg[
                            j] -= self.recent_avg[j] / self.recent_count
                        self.recent_avg[j] += datapoint[j] / self.recent_count

            # the plot data is the summed counts divided by the occurence of the respective bins
            self.histogram = self.rawhisto / self.sumhisto

    def save_data(self, timestamp=None):
        """ Save the counter trace data and writes it to a file.

        @param datetime timestamp: timestamp passed from gui so that saved images match filenames
                                    of data. This will be removed when savelogic handles the image
                                    creation also.

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

        self._saving_stop_time = time.time()

        filepath = self._save_logic.get_path_for_module(
            module_name='WavemeterLogger')
        filelabel = 'wavemeter_log_histogram'

        # Currently need to pass timestamp from gui so that the saved image matches saved data.
        # TODO: once the savelogic saves images, we can revert this to always getting timestamp here.
        if timestamp is None:
            timestamp = datetime.datetime.now()

        # prepare the data in a dict or in an OrderedDict:
        data = OrderedDict()
        data['Wavelength (nm)'] = np.array(self.histogram_axis)
        data['Signal (counts/s)'] = np.array(self.histogram)

        # write the parameters:
        parameters = OrderedDict()
        parameters['Bins (#)'] = self._bins
        parameters['Xmin (nm)'] = self._xmin
        parameters['XMax (nm)'] = self._xmax
        parameters['Start Time (s)'] = time.strftime(
            '%d.%m.%Y %Hh:%Mmin:%Ss',
            time.localtime(self._acqusition_start_time))
        parameters['Stop Time (s)'] = time.strftime(
            '%d.%m.%Y %Hh:%Mmin:%Ss', time.localtime(self._saving_stop_time))

        self._save_logic.save_data(data,
                                   filepath=filepath,
                                   parameters=parameters,
                                   filelabel=filelabel,
                                   timestamp=timestamp,
                                   fmt='%.12e')

        filelabel = 'wavemeter_log_wavelength'

        # prepare the data in a dict or in an OrderedDict:
        data = OrderedDict()
        data['Time (s), Wavelength (nm)'] = self._wavelength_data
        # write the parameters:
        parameters = OrderedDict()
        parameters['Acquisition Timing (ms)'] = self._logic_acquisition_timing
        parameters['Start Time (s)'] = time.strftime(
            '%d.%m.%Y %Hh:%Mmin:%Ss',
            time.localtime(self._acqusition_start_time))
        parameters['Stop Time (s)'] = time.strftime(
            '%d.%m.%Y %Hh:%Mmin:%Ss', time.localtime(self._saving_stop_time))

        self._save_logic.save_data(data,
                                   filepath=filepath,
                                   parameters=parameters,
                                   filelabel=filelabel,
                                   timestamp=timestamp,
                                   fmt='%.12e')

        filelabel = 'wavemeter_log_counts'

        # prepare the data in a dict or in an OrderedDict:
        data = OrderedDict()
        data['Time (s),Signal (counts/s)'] = self._counter_logic._data_to_save

        # write the parameters:
        parameters = OrderedDict()
        parameters['Start counting time (s)'] = time.strftime(
            '%d.%m.%Y %Hh:%Mmin:%Ss',
            time.localtime(self._counter_logic._saving_start_time))
        parameters['Stop counting time (s)'] = time.strftime(
            '%d.%m.%Y %Hh:%Mmin:%Ss', time.localtime(self._saving_stop_time))
        parameters[
            'Length of counter window (# of events)'] = self._counter_logic._count_length
        parameters[
            'Count frequency (Hz)'] = self._counter_logic._count_frequency
        parameters[
            'Oversampling (Samples)'] = self._counter_logic._counting_samples
        parameters[
            'Smooth Window Length (# of events)'] = self._counter_logic._smooth_window_length

        self._save_logic.save_data(data,
                                   filepath=filepath,
                                   parameters=parameters,
                                   filelabel=filelabel,
                                   timestamp=timestamp,
                                   fmt='%.12e')

        self.log.debug('Laser Scan saved to:\n{0}'.format(filepath))

        filelabel = 'wavemeter_log_counts_with_wavelength'

        # prepare the data in a dict or in an OrderedDict:
        data = OrderedDict()
        data[
            'Measurement Time (s), Signal (counts/s), Interpolated Wavelength (nm)'] = np.array(
                self.counts_with_wavelength)

        fig = self.draw_figure()
        # write the parameters:
        parameters = OrderedDict()
        parameters['Start Time (s)'] = time.strftime(
            '%d.%m.%Y %Hh:%Mmin:%Ss',
            time.localtime(self._acqusition_start_time))
        parameters['Stop Time (s)'] = time.strftime(
            '%d.%m.%Y %Hh:%Mmin:%Ss', time.localtime(self._saving_stop_time))

        self._save_logic.save_data(data,
                                   filepath=filepath,
                                   parameters=parameters,
                                   filelabel=filelabel,
                                   timestamp=timestamp,
                                   plotfig=fig,
                                   fmt='%.12e')
        plt.close(fig)
        return 0

    def draw_figure(self):
        """ Draw figure to save with data file.

        @return: fig fig: a matplotlib figure object to be saved to file.
        """
        # TODO: Draw plot for second APD if it is connected

        wavelength_data = [entry[2] for entry in self.counts_with_wavelength]
        count_data = np.array(
            [entry[1] for entry in self.counts_with_wavelength])

        # Index of max counts, to use to position "0" of frequency-shift axis
        count_max_index = count_data.argmax()

        # Scale count values using SI prefix
        prefix = ['', 'k', 'M', 'G']
        prefix_index = 0

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

        counts_prefix = prefix[prefix_index]

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

        # Create figure
        fig, ax = plt.subplots()

        ax.plot(wavelength_data, count_data, linestyle=':', linewidth=0.5)

        ax.set_xlabel('wavelength (nm)')
        ax.set_ylabel('Fluorescence (' + counts_prefix + 'c/s)')

        x_formatter = mpl.ticker.ScalarFormatter(useOffset=False)
        ax.xaxis.set_major_formatter(x_formatter)

        ax2 = ax.twiny()

        nm_xlim = ax.get_xlim()
        ghz_at_max_counts = self.nm_to_ghz(wavelength_data[count_max_index])
        ghz_min = self.nm_to_ghz(nm_xlim[0]) - ghz_at_max_counts
        ghz_max = self.nm_to_ghz(nm_xlim[1]) - ghz_at_max_counts

        ax2.set_xlim(ghz_min, ghz_max)
        ax2.set_xlabel('Shift (GHz)')

        return fig

    def nm_to_ghz(self, wavelength):
        """ Convert wavelength to frequency.

            @param float wavelength: vacuum wavelength

            @return float: freequency
        """
        return 3e8 / wavelength
コード例 #11
0
ファイル: spectrum.py プロジェクト: macgrun/qudi
class SpectrumLogic(GenericLogic):
    """This logic module gathers data from the spectrometer.
    """

    sig_specdata_updated = QtCore.Signal()
    sig_next_diff_loop = QtCore.Signal()

    _modclass = 'spectrumlogic'
    _modtype = 'logic'

    # declare connectors
    spectrometer = Connector(interface='SpectrometerInterface')
    odmrlogic1 = Connector(interface='ODMRLogic')
    savelogic = Connector(interface='SaveLogic')

    def __init__(self, **kwargs):
        """ Create SpectrometerLogic object with connectors.

          @param dict kwargs: optional parameters
        """
        super().__init__(**kwargs)

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

    def on_activate(self):
        """ Initialisation performed during activation of the module.
        """
        self.spectrum_data = np.array([])
        self.diff_spec_data_mod_on = np.array([])
        self.diff_spec_data_mod_off = np.array([])
        self.repetition_count = 0  # count loops for differential spectrum

        self._spectrometer_device = self.spectrometer()
        self._odmr_logic = self.odmrlogic1()
        self._save_logic = self.savelogic()

        self.sig_next_diff_loop.connect(self._loop_differential_spectrum)

    def on_deactivate(self):
        """ Deinitialisation performed during deactivation of the module.
        """
        if self.module_state() != 'idle' and self.module_state(
        ) != 'deactivated':
            pass

    def get_single_spectrum(self):
        """ Record a single spectrum from the spectrometer.
        """
        self.spectrum_data = netobtain(
            self._spectrometer_device.recordSpectrum())

        # Clearing the differential spectra data arrays so that they do not get
        # saved with this single spectrum.
        self.diff_spec_data_mod_on = np.array([])
        self.diff_spec_data_mod_off = np.array([])

        self.sig_specdata_updated.emit()

    def save_raw_spectrometer_file(self, path='', postfix=''):
        """Ask the hardware device to save its own raw file.
        """
        # TODO: sanity check the passed parameters.

        self._spectrometer_device.saveSpectrum(path, postfix=postfix)

    def start_differential_spectrum(self):
        """Start a differential spectrum acquisition.  An initial spectrum is recorded to initialise the data arrays to the right size.
        """

        self._continue_differential = True

        # Taking a demo spectrum gives us the wavelength values and the length of the spectrum data.
        demo_data = netobtain(self._spectrometer_device.recordSpectrum())

        wavelengths = demo_data[0, :]
        empty_signal = np.zeros(len(wavelengths))

        # Using this information to initialise the differential spectrum data arrays.
        self.spectrum_data = np.array([wavelengths, empty_signal])
        self.diff_spec_data_mod_on = np.array([wavelengths, empty_signal])
        self.diff_spec_data_mod_off = np.array([wavelengths, empty_signal])
        self.repetition_count = 0

        # Starting the measurement loop
        self._loop_differential_spectrum()

    def resume_differential_spectrum(self):
        """Resume a differential spectrum acquisition.
        """

        self._continue_differential = True

        # Starting the measurement loop
        self._loop_differential_spectrum()

    def _loop_differential_spectrum(self):
        """ This loop toggles the modulation and iteratively records a differential spectrum.
        """

        # If the loop should not continue, then return immediately without
        # emitting any signal to repeat.
        if not self._continue_differential:
            return

        # Otherwise, we make a measurement and then emit a signal to repeat this loop.

        # Toggle on, take spectrum and add data to the mod_on data
        self.toggle_modulation(on=True)
        these_data = netobtain(self._spectrometer_device.recordSpectrum())
        self.diff_spec_data_mod_on[1, :] += these_data[1, :]

        # Toggle off, take spectrum and add data to the mod_off data
        self.toggle_modulation(on=False)
        these_data = netobtain(self._spectrometer_device.recordSpectrum())
        self.diff_spec_data_mod_off[1, :] += these_data[1, :]

        self.repetition_count += 1  # increment the loop count

        # Calculate the differential spectrum
        self.spectrum_data[1, :] = self.diff_spec_data_mod_on[
            1, :] - self.diff_spec_data_mod_off[1, :]

        self.sig_specdata_updated.emit()

        self.sig_next_diff_loop.emit()

    def stop_differential_spectrum(self):
        """Stop an ongoing differential spectrum acquisition
        """

        self._continue_differential = False

    def toggle_modulation(self, on):
        """ Toggle the modulation.
        """

        if on:
            self._odmr_logic.MW_on()
        elif not on:
            self._odmr_logic.MW_off()
        else:
            print("Parameter 'on' needs to be boolean")

    def save_spectrum_data(self):
        """ Saves the current spectrum data to a file.
        """
        filepath = self._save_logic.get_path_for_module(module_name='spectra')
        filelabel = 'spectrum'

        # write experimental parameters
        parameters = OrderedDict()
        parameters[
            'Spectrometer acquisition repetitions'] = self.repetition_count

        # prepare the data in an OrderedDict:
        data = OrderedDict()

        data['wavelength'] = self.spectrum_data[0, :]

        # If the differential spectra arrays are not empty, save them as raw data
        if len(self.diff_spec_data_mod_on) != 0 and len(
                self.diff_spec_data_mod_off) != 0:
            data['signal_mod_on'] = self.diff_spec_data_mod_on[1, :]
            data['signal_mod_off'] = self.diff_spec_data_mod_off[1, :]
            data['differential'] = self.spectrum_data[1, :]
        else:
            data['signal'] = self.spectrum_data[1, :]

        # Prepare the figure to save as a "data thumbnail"
        plt.style.use(self._save_logic.mpl_qd_style)

        fig, ax1 = plt.subplots()

        ax1.plot(data['wavelength'], data['signal'])

        ax1.set_xlabel('Wavelength (nm)')
        ax1.set_ylabel('Signal (arb. u.)')

        fig.tight_layout()

        # Save to file
        self._save_logic.save_data(data,
                                   filepath=filepath,
                                   parameters=parameters,
                                   filelabel=filelabel,
                                   plotfig=fig)
        self.log.debug('Spectrum saved to:\n{0}'.format(filepath))
コード例 #12
0
class ConfocalScannerDummy(Base, ConfocalScannerInterface):
    """ Dummy confocal scanner.
        Produces a picture with several gaussian spots.
    """
    _modclass = 'ConfocalScannerDummy'
    _modtype = 'hardware'

    # connectors
    fitlogic = Connector(interface='FitLogic')

    # config
    _clock_frequency = ConfigOption('clock_frequency', 100, missing='warn')

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

        # Internal parameters
        self._line_length = None
        self._voltage_range = [-10, 10]

        self._position_range = [[0, 100e-6], [0, 100e-6], [0, 100e-6],
                                [0, 1e-6]]
        self._current_position = [0, 0, 0, 0][0:len(self.get_scanner_axes())]
        self._num_points = 500

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

        self._fit_logic = self.fitlogic()

        # put randomly distributed NVs in the scanner, first the x,y scan
        self._points = np.empty([self._num_points, 7])
        # amplitude
        self._points[:, 0] = np.random.normal(4e5, 1e5, self._num_points)
        # x_zero
        self._points[:, 1] = np.random.uniform(self._position_range[0][0],
                                               self._position_range[0][1],
                                               self._num_points)
        # y_zero
        self._points[:, 2] = np.random.uniform(self._position_range[1][0],
                                               self._position_range[1][1],
                                               self._num_points)
        # sigma_x
        self._points[:, 3] = np.random.normal(0.7e-6, 0.1e-6, self._num_points)
        # sigma_y
        self._points[:, 4] = np.random.normal(0.7e-6, 0.1e-6, self._num_points)
        # theta
        self._points[:, 5] = 10
        # offset
        self._points[:, 6] = 0

        # now also the z-position
        #       gaussian_function(self,x_data=None,amplitude=None, x_zero=None, sigma=None, offset=None):

        self._points_z = np.empty([self._num_points, 4])
        # amplitude
        self._points_z[:, 0] = np.random.normal(1, 0.05, self._num_points)

        # x_zero
        self._points_z[:, 1] = np.random.uniform(45e-6, 55e-6,
                                                 self._num_points)

        # sigma
        self._points_z[:, 2] = np.random.normal(0.5e-6, 0.1e-6,
                                                self._num_points)

        # offset
        self._points_z[:, 3] = 0

    def on_deactivate(self):
        """ Deactivate properly the confocal scanner dummy.
        """
        self.reset_hardware()

    def reset_hardware(self):
        """ Resets the hardware, so the connection is lost and other programs
            can access it.

        @return int: error code (0:OK, -1:error)
        """
        self.log.warning('Scanning Device will be reset.')
        return 0

    def get_position_range(self):
        """ Returns the physical range of the scanner.

        @return float [4][2]: array of 4 ranges with an array containing lower
                              and upper limit
        """
        return self._position_range

    def set_position_range(self, myrange=None):
        """ Sets the physical range of the scanner.

        @param float [4][2] myrange: array of 4 ranges with an array containing
                                     lower and upper limit

        @return int: error code (0:OK, -1:error)
        """
        if myrange is None:
            myrange = [[0, 1e-6], [0, 1e-6], [0, 1e-6], [0, 1e-6]]

        if not isinstance(myrange, (
                frozenset,
                list,
                set,
                tuple,
                np.ndarray,
        )):
            self.log.error('Given range is no array type.')
            return -1

        if len(myrange) != 4:
            self.log.error('Given range should have dimension 4, but has '
                           '{0:d} instead.'.format(len(myrange)))
            return -1

        for pos in myrange:
            if len(pos) != 2:
                self.log.error('Given range limit {1:d} should have '
                               'dimension 2, but has {0:d} instead.'.format(
                                   len(pos), pos))
                return -1
            if pos[0] > pos[1]:
                self.log.error('Given range limit {0:d} has the wrong '
                               'order.'.format(pos))
                return -1

        self._position_range = myrange

        return 0

    def set_voltage_range(self, myrange=None):
        """ Sets the voltage range of the NI Card.

        @param float [2] myrange: array containing lower and upper limit

        @return int: error code (0:OK, -1:error)
        """
        if myrange is None:
            myrange = [-10., 10.]

        if not isinstance(myrange, (
                frozenset,
                list,
                set,
                tuple,
                np.ndarray,
        )):
            self.log.error('Given range is no array type.')
            return -1

        if len(myrange) != 2:
            self.log.error('Given range should have dimension 2, but has '
                           '{0:d} instead.'.format(len(myrange)))
            return -1

        if myrange[0] > myrange[1]:
            self.log.error('Given range limit {0:d} has the wrong '
                           'order.'.format(myrange))
            return -1

        if self.module_state() == 'locked':
            self.log.error('A Scanner is already running, close this one '
                           'first.')
            return -1

        self._voltage_range = myrange

        return 0

    def get_scanner_axes(self):
        """ Dummy scanner is always 3D cartesian.
        """
        return ['x', 'y', 'z', 'a']

    def get_scanner_count_channels(self):
        """ 3 counting channels in dummy confocal: normal, negative and a ramp."""
        return ['Norm', 'Neg', 'Ramp']

    def set_up_scanner_clock(self, clock_frequency=None, clock_channel=None):
        """ Configures the hardware clock of the NiDAQ card to give the timing.

        @param float clock_frequency: if defined, this sets the frequency of the
                                      clock
        @param str clock_channel: if defined, this is the physical channel of
                                  the clock

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

        if clock_frequency is not None:
            self._clock_frequency = float(clock_frequency)

        self.log.debug('ConfocalScannerDummy>set_up_scanner_clock')
        time.sleep(0.2)
        return 0

    def set_up_scanner(self,
                       counter_channels=None,
                       sources=None,
                       clock_channel=None,
                       scanner_ao_channels=None):
        """ Configures the actual scanner with a given clock.

        @param str counter_channel: if defined, this is the physical channel of
                                    the counter
        @param str photon_source: if defined, this is the physical channel where
                                  the photons are to count from
        @param str clock_channel: if defined, this specifies the clock for the
                                  counter
        @param str scanner_ao_channels: if defined, this specifies the analoque
                                        output channels

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

        self.log.debug('ConfocalScannerDummy>set_up_scanner')
        time.sleep(0.2)
        return 0

    def scanner_set_position(self, x=None, y=None, z=None, a=None):
        """Move stage to x, y, z, a (where a is the fourth voltage channel).

        @param float x: postion in x-direction (volts)
        @param float y: postion in y-direction (volts)
        @param float z: postion in z-direction (volts)
        @param float a: postion in a-direction (volts)

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

        if self.module_state() == 'locked':
            self.log.error(
                'A Scanner is already running, close this one first.')
            return -1

        time.sleep(0.01)

        self._current_position = [x, y, z, a][0:len(self.get_scanner_axes())]
        return 0

    def get_scanner_position(self):
        """ Get the current position of the scanner hardware.

        @return float[]: current position in (x, y, z, a).
        """
        return self._current_position[0:len(self.get_scanner_axes())]

    def _set_up_line(self, length=100):
        """ Sets up the analoque output for scanning a line.

        @param int length: length of the line in pixel

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

        self._line_length = length

        #        self.log.debug('ConfocalScannerInterfaceDummy>set_up_line')
        return 0

    def scan_line(self, line_path=None, pixel_clock=False):
        """ Scans a line and returns the counts on that line.

        @param float[][4] line_path: array of 4-part tuples defining the voltage points
        @param bool pixel_clock: whether we need to output a pixel clock for this line

        @return float[]: the photon counts per second
        """

        if not isinstance(line_path, (
                frozenset,
                list,
                set,
                tuple,
                np.ndarray,
        )):
            self.log.error('Given voltage list is no array type.')
            return np.array([[-1.]])

        if np.shape(line_path)[1] != self._line_length:
            self._set_up_line(np.shape(line_path)[1])

        count_data = np.random.uniform(0, 2e4, self._line_length)
        z_data = line_path[2, :]

        #TODO: Change the gaussian function here to the one from fitlogic and delete the local modules to calculate
        #the gaussian functions
        x_data = np.array(line_path[0, :])
        y_data = np.array(line_path[1, :])
        for i in range(self._num_points):
            count_data += self.twoD_gaussian_function(
                (x_data, y_data), *(self._points[i])) * self.gaussian_function(
                    np.array(z_data), *(self._points_z[i]))

        time.sleep(self._line_length * 1. / self._clock_frequency)
        time.sleep(self._line_length * 1. / self._clock_frequency)

        # update the scanner position instance variable
        self._current_position = list(line_path[:, -1])

        return np.array([
            count_data, 5e5 - count_data,
            np.ones(count_data.shape) * line_path[1, 0] * 100
        ]).transpose()

    def close_scanner(self):
        """ Closes the scanner and cleans up afterwards.

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

        self.log.debug('ConfocalScannerDummy>close_scanner')
        return 0

    def close_scanner_clock(self, power=0):
        """ Closes the clock and cleans up afterwards.

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

        self.log.debug('ConfocalScannerDummy>close_scanner_clock')
        return 0

############################################################################
#                                                                          #
#    the following two functions are needed to fluorescence signal        #
#                             of the dummy NVs                             #
#                                                                          #
############################################################################

    def twoD_gaussian_function(self,
                               x_data_tuple=None,
                               amplitude=None,
                               x_zero=None,
                               y_zero=None,
                               sigma_x=None,
                               sigma_y=None,
                               theta=None,
                               offset=None):

        #FIXME: x_data_tuple: dimension of arrays
        """ This method provides a two dimensional gaussian function.

        @param (k,M)-shaped array x_data_tuple: x and y values
        @param float or int amplitude: Amplitude of gaussian
        @param float or int x_zero: x value of maximum
        @param float or int y_zero: y value of maximum
        @param float or int sigma_x: standard deviation in x direction
        @param float or int sigma_y: standard deviation in y direction
        @param float or int theta: angle for eliptical gaussians
        @param float or int offset: offset

        @return callable function: returns the function

        """
        # check if parameters make sense
        #FIXME: Check for 2D matrix
        if not isinstance(x_data_tuple,
                          (frozenset, list, set, tuple, np.ndarray)):
            self.log.error('Given range of axes is no array type.')

        parameters = [
            amplitude, x_zero, y_zero, sigma_x, sigma_y, theta, offset
        ]
        for var in parameters:
            if not isinstance(var, (float, int)):
                self.log.error('Given range of parameter is no float or int.')

        (x, y) = x_data_tuple
        x_zero = float(x_zero)
        y_zero = float(y_zero)

        a = (np.cos(theta)**2) / (2 * sigma_x**2) + (np.sin(theta)**
                                                     2) / (2 * sigma_y**2)
        b = -(np.sin(2 * theta)) / (4 * sigma_x**2) + (np.sin(
            2 * theta)) / (4 * sigma_y**2)
        c = (np.sin(theta)**2) / (2 * sigma_x**2) + (np.cos(theta)**
                                                     2) / (2 * sigma_y**2)
        g = offset + amplitude * np.exp(-(a * ((x - x_zero)**2) + 2 * b *
                                          (x - x_zero) * (y - y_zero) + c *
                                          ((y - y_zero)**2)))
        return g.ravel()

    def gaussian_function(self,
                          x_data=None,
                          amplitude=None,
                          x_zero=None,
                          sigma=None,
                          offset=None):
        """ This method provides a one dimensional gaussian function.

        @param array x_data: x values
        @param float or int amplitude: Amplitude of gaussian
        @param float or int x_zero: x value of maximum
        @param float or int sigma: standard deviation
        @param float or int offset: offset

        @return callable function: returns a 1D Gaussian function

        """
        # check if parameters make sense
        if not isinstance(x_data, (frozenset, list, set, tuple, np.ndarray)):
            self.log.error('Given range of axis is no array type.')

        parameters = [amplitude, x_zero, sigma, offset]
        for var in parameters:
            if not isinstance(var, (float, int)):
                print('error', var)
                self.log.error('Given range of parameter is no float or int.')
        gaussian = amplitude * np.exp(-(x_data - x_zero)**2 /
                                      (2 * sigma**2)) + offset
        return gaussian
コード例 #13
0
class QdplotLogic(GenericLogic):
    """ This logic module helps display user data in plots, and makes it easy to save.

    @signal sigCounterUpdate: there is new counting data available
    @signal sigCountContinuousNext: used to simulate a loop in which the data
                                    acquisition runs.
    @sigmal sigCountGatedNext: ???
    """
    sigPlotDataUpdated = QtCore.Signal()
    sigPlotParamsUpdated = QtCore.Signal()

    _modclass = 'QdplotLogic'
    _modtype = 'logic'

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

    def __init__(self, **kwargs):
        """ Create QdplotLogic object with connectors.

        @param dict kwargs: optional parameters
        """
        super().__init__(**kwargs)

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

    def on_activate(self):
        """ Initialisation performed during activation of the module.
        """
        self.indep_vals = np.zeros((10, ))
        self.depen_vals = np.zeros((10, ))

        self.plot_domain = [0, 1]
        self.plot_range = [0, 1]

        self.set_hlabel()
        self.set_vlabel()

        self._save_logic = self.get_connector('savelogic')

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

    def set_data(self, x=None, y=None, clear_old=True):
        """Set the data to plot

        @param np.ndarray/list or list of np.ndarrays/lists x: data of independents variable(s)
        @param np.ndarray/list or list of np.ndarrays/lists y: data of dependent variable(s)
        @param bool clear_old: clear old plots in GUI if True
        """

        if x is None:
            self.log.error('No x-values provided, cannot set plot data.')
            return -1

        if y is None:
            self.log.error('No y-values provided, cannot set plot data.')
            return -1

        self.clear_old = clear_old
        # check if input is only an array (single plot) or a list of arrays (several plots)
        if len(x) == 1:
            self.indep_vals = [x]
            self.depen_vals = [y]
        else:
            self.indep_vals = x
            self.depen_vals = y

        self.sigPlotDataUpdated.emit()
        self.sigPlotParamsUpdated.emit()

        self.set_domain()
        self.set_range()
        return

    def set_domain(self, newdomain=None):
        """Set the plot domain, to match the data (default) or to a specified new domain.

        @param float newdomain: 2-element list containing min and max x-values
        """
        # TODO: This needs to check that newdomain is a 2-element list with numerical values.
        if newdomain is not None:
            self.plot_domain = newdomain
        else:
            domain_min = np.min([np.min(values) for values in self.indep_vals])
            domain_max = np.max([np.max(values) for values in self.indep_vals])
            domain_range = domain_max - domain_min
            self.plot_domain = [
                domain_min - 0.02 * domain_range,
                domain_max + 0.02 * domain_range
            ]

        self.sigPlotParamsUpdated.emit()
        return 0

    def set_range(self, newrange=None):
        """Set the plot range, to match the data (default) or to a specified new range

        @param float newrange: 2-element list containing min and max y-values
        """
        # TODO: This needs to check that newdomain is a 2-element list with numerical values.
        if newrange is not None:
            self.plot_range = newrange
        else:
            range_min = np.min([np.min(values) for values in self.depen_vals])
            range_max = np.max([np.max(values) for values in self.depen_vals])
            range_range = range_max - range_min
            self.plot_range = [
                range_min - 0.02 * range_range, range_max + 0.02 * range_range
            ]

        self.sigPlotParamsUpdated.emit()
        return 0

    def set_hlabel(self, label='Independent variable', units='arb. units'):
        """Set the horizontal axis label and specify units.

        @param string label: name of axis

        @param string units: symbol for units
        """
        print('label_in_sethlabel', label)
        self.h_label = label
        self.h_units = units

        self.sigPlotParamsUpdated.emit()
        return 0

    def set_vlabel(self, label='Dependent variable', units='arb. units'):
        """Set the vertical axis label and specify units.

        @param string label: name of axis

        @param string units: symbol for units
        """
        print('label_in_setvlabel', label)
        self.v_label = label
        self.v_units = units

        self.sigPlotParamsUpdated.emit()
        return 0

    def get_domain(self):
        return self.plot_domain

    def get_range(self):
        return self.plot_range

    def save_data(self, postfix=''):
        """ Save the data to a file.

        @param bool to_file: indicate, whether data have to be saved to file
        @param str postfix: an additional tag, which will be added to the filename upon save

        @return np.array([2 or 3][X]), OrderedDict: array with the
        """
        # Set the parameters:
        parameters = OrderedDict()
        parameters['User-selected display domain'] = self.plot_domain
        parameters['User-selected display range'] = self.plot_range

        # If there is a postfix then add separating underscore
        if postfix == '':
            filelabel = 'qdplot'
        else:
            filelabel = postfix

        # Data labels
        indep_label = self.h_label + ' (' + self.h_units + ')'
        depen_label = self.v_label + ' (' + self.v_units + ')'

        # prepare the data in a dict or in an OrderedDict:
        data = OrderedDict()
        for ii in range(len(self.indep_vals)):
            data['indep_label' + str(ii + 1)] = self.indep_vals[ii]
            data['depen_label' + str(ii + 1)] = self.depen_vals[ii]

        # Prepare the figure to save as a "data thumbnail"
        plt.style.use(self._save_logic.mpl_qd_style)

        fig, ax1 = plt.subplots()

        for ii in range(len(self.indep_vals)):
            ax1.plot(self.indep_vals[ii],
                     self.depen_vals[ii],
                     linestyle=':',
                     linewidth=1)

        ax1.set_xlabel(indep_label)
        ax1.set_ylabel(depen_label)

        ax1.set_xlim(self.plot_domain)
        ax1.set_ylim(self.plot_range)

        fig.tight_layout()

        filepath = self._save_logic.get_path_for_module(module_name='qdplot')

        # Call save logic to write everything to file
        self._save_logic.save_data(data,
                                   filepath=filepath,
                                   parameters=parameters,
                                   filelabel=filelabel,
                                   plotfig=fig,
                                   delimiter='\t')
        plt.close(fig)
        self.log.debug('Data saved to:\n{0}'.format(filepath))
コード例 #14
0
ファイル: poi_manager_logic.py プロジェクト: cjr189/qudi
class PoiManagerLogic(GenericLogic):

    """
    This is the Logic class for mapping and tracking bright features in the confocal scan.
    """
    _modclass = 'poimanagerlogic'
    _modtype = 'logic'

    # declare connectors
    optimiserlogic = Connector(interface='OptimizerLogic')
    scannerlogic = Connector(interface='ConfocalLogic')
    savelogic = Connector(interface='SaveLogic')

    # status vars
    _roi = StatusVar(default=dict())  # Notice constructor and representer further below
    _refocus_period = StatusVar(default=120)
    _active_poi = StatusVar(default=None)
    _move_scanner_after_optimization = StatusVar(default=True)

    # Signals for connecting modules
    sigRefocusStateUpdated = QtCore.Signal(bool)  # is_active
    sigRefocusTimerUpdated = QtCore.Signal(bool, float, float)  # is_active, period, remaining_time
    sigPoiUpdated = QtCore.Signal(str, str, np.ndarray)  # old_name, new_name, current_position
    sigActivePoiUpdated = QtCore.Signal(str)
    sigRoiUpdated = QtCore.Signal(dict)  # Dict containing ROI parameters to update

    # Internal signals
    __sigStartPeriodicRefocus = QtCore.Signal()
    __sigStopPeriodicRefocus = QtCore.Signal()

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

        # timer for the periodic refocus
        self.__timer = None
        self._last_refocus = 0
        self._periodic_refocus_poi = None

        # threading
        self._threadlock = Mutex()
        return

    def on_activate(self):
        """ Initialisation performed during activation of the module.
        """
        self.__timer = QtCore.QTimer()
        self.__timer.setSingleShot(False)
        self._last_refocus = 0
        self._periodic_refocus_poi = None

        # Connect callback for a finished refocus
        self.optimiserlogic().sigRefocusFinished.connect(
            self._optimisation_callback, QtCore.Qt.QueuedConnection)
        # Connect internal start/stop signals to decouple QTimer from other threads
        self.__sigStartPeriodicRefocus.connect(
            self.start_periodic_refocus, QtCore.Qt.QueuedConnection)
        self.__sigStopPeriodicRefocus.connect(
            self.stop_periodic_refocus, QtCore.Qt.QueuedConnection)

        # Initialise the ROI scan image (xy confocal image) if not present
        if self._roi.scan_image is None:
            self.set_scan_image(False)

        self.sigRoiUpdated.emit({'name': self.roi_name,
                                 'poi_nametag': self.poi_nametag,
                                 'pois': self.poi_positions,
                                 'history': self.roi_pos_history,
                                 'scan_image': self.roi_scan_image,
                                 'scan_image_extent': self.roi_scan_image_extent})
        self.sigActivePoiUpdated.emit('' if self.active_poi is None else self.active_poi)
        self.update_poi_tag_in_savelogic()
        return

    def on_deactivate(self):
        # Stop active processes/loops
        self.stop_periodic_refocus()

        # Disconnect signals
        self.optimiserlogic().sigRefocusFinished.disconnect()
        self.__sigStartPeriodicRefocus.disconnect()
        self.__sigStopPeriodicRefocus.disconnect()
        return

    @property
    def data_directory(self):
        return self.savelogic().data_dir

    @property
    def optimise_xy_size(self):
        return float(self.optimiserlogic().refocus_XY_size)

    @property
    def active_poi(self):
        return self._active_poi

    @active_poi.setter
    def active_poi(self, name):
        self.set_active_poi(name)
        return

    @property
    def poi_names(self):
        return self._roi.poi_names

    @property
    def poi_positions(self):
        return self._roi.poi_positions

    @property
    def poi_anchors(self):
        return self._roi.poi_anchors

    @property
    def roi_name(self):
        return self._roi.name

    @roi_name.setter
    def roi_name(self, name):
        self.rename_roi(new_name=name)

    @property
    def poi_nametag(self):
        return self._roi.poi_nametag

    @poi_nametag.setter
    def poi_nametag(self, tag):
        self.set_poi_nametag(tag)
        return

    @property
    def roi_origin(self):
        return self._roi.origin

    @property
    def roi_creation_time(self):
        return self._roi.creation_time

    @property
    def roi_creation_time_as_str(self):
        return self._roi.creation_time_as_str

    @property
    def roi_pos_history(self):
        return self._roi.pos_history

    @property
    def roi_scan_image(self):
        return self._roi.scan_image

    @property
    def roi_scan_image_extent(self):
        return self._roi.scan_image_extent

    @property
    def refocus_period(self):
        return float(self._refocus_period)

    @refocus_period.setter
    def refocus_period(self, period):
        self.set_refocus_period(period)
        return

    @property
    def time_until_refocus(self):
        if not self.__timer.isActive():
            return -1
        return max(0., self._refocus_period - (time.time() - self._last_refocus))

    @property
    def scanner_position(self):
        return self.scannerlogic().get_position()[:3]

    @property
    def move_scanner_after_optimise(self):
        return bool(self._move_scanner_after_optimization)

    @move_scanner_after_optimise.setter
    def move_scanner_after_optimise(self, move):
        self.set_move_scanner_after_optimise(move)
        return

    @QtCore.Slot(int)
    @QtCore.Slot(bool)
    def set_move_scanner_after_optimise(self, move):
        with self._threadlock:
            self._move_scanner_after_optimization = bool(move)
        return

    @QtCore.Slot(str)
    def set_poi_nametag(self, tag):
        if tag is None or isinstance(tag, str):
            if tag == '':
                tag = None
            self._roi.poi_nametag = tag
            self.sigRoiUpdated.emit({'poi_nametag': self.poi_nametag})
        else:
            self.log.error('POI name tag must be str or None.')
        return

    @QtCore.Slot()
    @QtCore.Slot(np.ndarray)
    def add_poi(self, position=None, name=None, emit_change=True):
        """
        Creates a new POI and adds it to the current ROI.
        POI can be optionally initialized with position and name.

        @param str name: Name for the POI (must be unique within ROI).
                         None (default) will create generic name.
        @param scalar[3] position: Iterable of length 3 representing the (x, y, z) position with
                                   respect to the ROI origin. None (default) causes the current
                                   scanner crosshair position to be used.
        @param bool emit_change: Flag indicating if the changed POI set should be signaled.
        """
        # Get current scanner position from scannerlogic if no position is provided.
        if position is None:
            position = self.scanner_position

        current_poi_set = set(self.poi_names)

        # Add POI to current ROI
        self._roi.add_poi(position=position, name=name)

        # Get newly added POI name from comparing POI names before and after addition of new POI
        poi_name = set(self.poi_names).difference(current_poi_set).pop()

        # Notify about a changed set of POIs if necessary
        if emit_change:
            self.sigPoiUpdated.emit('', poi_name, self.get_poi_position(poi_name))

        # Set newly created POI as active poi
        self.set_active_poi(poi_name)
        return

    @QtCore.Slot()
    def delete_poi(self, name=None):
        """
        Deletes the given poi from the ROI.

        @param str name: Name of the POI to delete. If None (default) delete active POI.
        @param bool emit_change: Flag indicating if the changed POI set should be signaled.
        """
        if len(self.poi_names) == 0:
            self.log.warning('Can not delete POI. No POI present in ROI.')
            return
        if name is None:
            if self.active_poi is None:
                self.log.error('No POI name to delete and no active POI set.')
                return
            else:
                name = self.active_poi

        self._roi.delete_poi(name)

        if self.active_poi == name:
            if len(self.poi_names) > 0:
                self.set_active_poi(self.poi_names[0])
            else:
                self.set_active_poi(None)

        # Notify about a changed set of POIs if necessary
        self.sigPoiUpdated.emit(name, '', np.zeros(3))
        return

    @QtCore.Slot(str)
    @QtCore.Slot(str, str)
    def rename_poi(self, new_name, name=None):
        """

        @param str name:
        @param str new_name:
        """
        if not isinstance(new_name, str) or not new_name:
            self.log.error('POI name to set must be str of length > 0.')
            return

        if name is None:
            if self.active_poi is None:
                self.log.error('Unable to rename POI. No POI name given and no active POI set.')
                return
            else:
                name = self.active_poi

        self._roi.rename_poi(name=name, new_name=new_name)

        self.sigPoiUpdated.emit(name, new_name, self.get_poi_position(new_name))

        if self.active_poi == name:
            self.set_active_poi(new_name)
        return

    @QtCore.Slot(str)
    def set_active_poi(self, name=None):
        """
        Set the name of the currently active POI
        @param name:
        """
        if not isinstance(name, str) and name is not None:
            self.log.error('POI name must be of type str or None.')
        elif name is None or name == '':
            self._active_poi = None
        elif name in self.poi_names:
            self._active_poi = str(name)
        else:
            self.log.error('No POI with name "{0}" found in POI list.'.format(name))

        self.sigActivePoiUpdated.emit('' if self.active_poi is None else self.active_poi)
        self.update_poi_tag_in_savelogic()
        return

    def get_poi_position(self, name=None):
        """
        Returns the POI position of the specified POI or the active POI if none is given.

        @param str name: Name of the POI to return the position for.
                             If None (default) the active POI position is returned.
        @return float[3]: Coordinates of the desired POI (x,y,z)
        """
        if name is None:
            name = self.active_poi
        return self._roi.get_poi_position(name)

    def get_poi_anchor(self, name=None):
        """
        Returns the POI anchor position (excluding sample movement) of the specified POI or the
        active POI if none is given.

        @param str name: Name of the POI to return the position for.
                         If None (default) the active POI position is returned.
        @return float[3]: Coordinates of the desired POI anchor (x,y,z)
        """
        if name is None:
            name = self.active_poi
        return self._roi.get_poi_anchor(name)

    @QtCore.Slot()
    def move_roi_from_poi_position(self, name=None, position=None):
        if position is None:
            position = self.scanner_position

        if name is None:
            if self.active_poi is None:
                self.log.error('Unable to set POI position. '
                               'No POI name given and no active POI set.')
                return
            else:
                name = self.active_poi

        if len(position) != 3:
            self.log.error('POI position must be iterable of length 3.')
            return
        if not isinstance(name, str):
            self.log.error('POI name must be of type str.')

        shift = position - self.get_poi_position(name)
        self.add_roi_position(self.roi_origin + shift)
        return

    @QtCore.Slot()
    def set_poi_anchor_from_position(self, name=None, position=None):
        if position is None:
            position = self.scanner_position

        if name is None:
            if self.active_poi is None:
                self.log.error('Unable to set POI position. '
                               'No POI name given and no active POI set.')
                return
            else:
                name = self.active_poi

        if len(position) != 3:
            self.log.error('POI position must be iterable of length 3.')
            return
        if not isinstance(name, str):
            self.log.error('POI name must be of type str.')

        shift = position - self.get_poi_position(name)
        self._roi.set_poi_anchor(name, self.get_poi_anchor(name) + shift)
        self.sigPoiUpdated.emit(name, name, self.get_poi_position(name))
        return

    @QtCore.Slot(str)
    def rename_roi(self, new_name):
        if not isinstance(new_name, str) or new_name == '':
            self.log.error('ROI name to set must be str of length > 0.')
            return
        self._roi.name = new_name
        self.sigRoiUpdated.emit({'name': self.roi_name})
        return

    @QtCore.Slot(np.ndarray)
    def add_roi_position(self, position):
        self._roi.add_history_entry(position)
        self.sigRoiUpdated.emit({'pois': self.poi_positions,
                                 'history': self.roi_pos_history,
                                 'scan_image': self.roi_scan_image,
                                 'scan_image_extent': self.roi_scan_image_extent})
        return

    @QtCore.Slot()
    @QtCore.Slot(int)
    def delete_history_entry(self, history_index=-1):
        """
        Delete an entry in the ROI history. Deletes the last position by default.

        @param int|slice history_index: List index for history entry
        """
        old_roi_origin = self.roi_origin
        self._roi.delete_history_entry(history_index)
        if np.any(old_roi_origin != self.roi_origin):
            self.sigRoiUpdated.emit({'pois': self.poi_positions,
                                     'history': self.roi_pos_history,
                                     'scan_image': self.roi_scan_image,
                                     'scan_image_extent': self.roi_scan_image_extent})
        else:
            self.sigRoiUpdated.emit({'history': self.roi_pos_history})
        return

    @QtCore.Slot()
    def go_to_poi(self, name=None):
        """
        Move crosshair to the given poi.

        @param str name: the name of the POI
        """
        if name is None:
            name = self.active_poi
        if not isinstance(name, str):
            self.log.error('POI name to move to must be of type str.')
            return
        self.move_scanner(self.get_poi_position(name))
        return

    def move_scanner(self, position):
        if len(position) != 3:
            self.log.error('Scanner position to set must be iterable of length 3.')
            return
        self.scannerlogic().set_position('poimanager', x=position[0], y=position[1], z=position[2])
        return

    @QtCore.Slot()
    def set_scan_image(self, emit_change=True):
        """ Get the current xy scan data and set as scan_image of ROI. """
        self._roi.set_scan_image(
            self.scannerlogic().xy_image[:, :, 3],
            (tuple(self.scannerlogic().image_x_range), tuple(self.scannerlogic().image_y_range)))

        if emit_change:
            self.sigRoiUpdated.emit({'scan_image': self.roi_scan_image,
                                     'scan_image_extent': self.roi_scan_image_extent})
        return

    @QtCore.Slot()
    def reset_roi(self):
        self.stop_periodic_refocus()
        self._roi = RegionOfInterest()
        self.set_scan_image(False)
        self.sigRoiUpdated.emit({'name': self.roi_name,
                                 'poi_nametag': self.poi_nametag,
                                 'pois': self.poi_positions,
                                 'history': self.roi_pos_history,
                                 'scan_image': self.roi_scan_image,
                                 'scan_image_extent': self.roi_scan_image_extent})
        self.set_active_poi(None)
        return

    @QtCore.Slot(int)
    @QtCore.Slot(float)
    def set_refocus_period(self, period):
        """ Change the duration of the periodic optimise timer during active
        periodic refocusing.

        @param float period: The time between optimisation procedures.
        """
        if period < 0:
            self.log.error('Refocus period must be a value > 0. Unable to set period of "{0}".'
                           ''.format(period))
            return
        # Acquire thread lock in order to change the period during a running periodic refocus
        with self._threadlock:
            self._refocus_period = float(period)
            if self.__timer.isActive():
                self.sigRefocusTimerUpdated.emit(True, self.refocus_period, self.time_until_refocus)
            else:
                self.sigRefocusTimerUpdated.emit(False, self.refocus_period, self.refocus_period)
        return

    def start_periodic_refocus(self, name=None):
        """
        Starts periodic refocusing of the POI <name>.

        @param str name: The name of the POI to be refocused periodically.
        If None (default) perform periodic refocus on active POI.
        """
        if name is None:
            if self.active_poi is None:
                self.log.error('Unable to start periodic refocus. No POI name given and no active '
                               'POI set.')
                return
            else:
                name = self.active_poi
        if name not in self.poi_names:
            self.log.error('No POI with name "{0}" found in POI list.\n'
                           'Unable to start periodic refocus.')
            return

        with self._threadlock:
            if self.__timer.isActive():
                self.log.error('Periodic refocus already running. Unable to start a new one.')
                return
            self.module_state.lock()
            self._periodic_refocus_poi = name
            self.optimise_poi_position(name=name)
            self._last_refocus = time.time()
            self.__timer.timeout.connect(self._periodic_refocus_loop)
            self.__timer.start(500)

            self.sigRefocusTimerUpdated.emit(True, self.refocus_period, self.refocus_period)
        return

    def stop_periodic_refocus(self):
        """ Stops the periodic refocusing of the POI. """
        with self._threadlock:
            if self.__timer.isActive():
                self.__timer.stop()
                self.__timer.timeout.disconnect()
                self._periodic_refocus_poi = None
                self.module_state.unlock()
            self.sigRefocusTimerUpdated.emit(False, self.refocus_period, self.refocus_period)
        return

    @QtCore.Slot(bool)
    def toggle_periodic_refocus(self, switch_on):
        """

        @param switch_on:
        """
        if switch_on:
            self.__sigStartPeriodicRefocus.emit()
        else:
            self.__sigStopPeriodicRefocus.emit()
        return

    @QtCore.Slot()
    def _periodic_refocus_loop(self):
        """ This is the looped function that does the actual periodic refocus.

        If the time has run out, it refocuses the current poi.
        Otherwise it just updates the time that is left.
        """
        with self._threadlock:
            if self.__timer.isActive():
                remaining_time = self.time_until_refocus
                self.sigRefocusTimerUpdated.emit(True, self.refocus_period, remaining_time)
                if remaining_time <= 0 and self.optimiserlogic().module_state() == 'idle':
                    self.optimise_poi_position(self._periodic_refocus_poi)
                    self._last_refocus = time.time()
        return

    @QtCore.Slot()
    def optimise_poi_position(self, name=None, update_roi_position=True):
        """
        Triggers the optimisation procedure for the given poi using the optimiserlogic.
        The difference between old and new position can be used to update the ROI position.
        This function will return immediately. The function "_optimisation_callback" will handle
        the aftermath of the optimisation.

        @param str name: Name of the POI for which to optimise the position.
        @param bool update_roi_position: Flag indicating if the ROI should be shifted accordingly.
        """
        if name is None:
            if self.active_poi is None:
                self.log.error('Unable to optimize POI position. '
                               'No POI name given and not active POI set.')
                return
            else:
                name = self.active_poi

        if update_roi_position:
            tag = 'poimanagermoveroi_{0}'.format(name)
        else:
            tag = 'poimanager_{0}'.format(name)

        if self.optimiserlogic().module_state() == 'idle':
            self.optimiserlogic().start_refocus(initial_pos=self.get_poi_position(name),
                                                caller_tag=tag)
            self.sigRefocusStateUpdated.emit(True)
        else:
            self.log.warning('Unable to start POI refocus procedure. '
                             'OptimizerLogic module is still locked.')
        return

    def _optimisation_callback(self, caller_tag, optimal_pos):
        """
        Callback function for a finished position optimisation.
        If desired the relative shift of the optimised POI can be used to update the ROI position.
        The scanner is moved to the optimised POI if desired.

        @param caller_tag:
        @param optimal_pos:
        """
        # If the refocus was initiated by poimanager, update POI and ROI position
        if caller_tag.startswith('poimanager_') or caller_tag.startswith('poimanagermoveroi_'):
            shift_roi = caller_tag.startswith('poimanagermoveroi_')
            poi_name = caller_tag.split('_', 1)[1]
            if poi_name in self.poi_names:
                # We only need x, y, z
                optimal_pos = np.array(optimal_pos[:3], dtype=float)
                if shift_roi:
                    self.move_roi_from_poi_position(name=poi_name, position=optimal_pos)
                else:
                    self.set_poi_anchor_from_position(name=poi_name, position=optimal_pos)
                if self._move_scanner_after_optimization:
                    self.move_scanner(position=optimal_pos)
        self.sigRefocusStateUpdated.emit(False)
        return

    def update_poi_tag_in_savelogic(self):
        if not self._active_poi:
            self.savelogic().remove_additional_parameter('Active POI')
        else:
            self.savelogic().update_additional_parameters({'Active POI': self._active_poi})

    def save_roi(self):
        """
        Save all current absolute POI coordinates to a file.
        Save ROI history to a second file.
        Save ROI scan image (if present) to a third file (binary numpy .npy-format).
        """
        # File path and names
        filepath = self.savelogic().get_path_for_module(module_name='ROIs')
        roi_name_no_blanks = self.roi_name.replace(' ', '_')
        timestamp = datetime.now()
        pois_filename = '{0}_poi_list'.format(roi_name_no_blanks)
        roi_history_filename = '{0}_{1}_history.npy'.format(
            timestamp.strftime('%Y%m%d-%H%M-%S'), roi_name_no_blanks)
        roi_image_filename = '{0}_{1}_scan_image.npy'.format(
            timestamp.strftime('%Y%m%d-%H%M-%S'), roi_name_no_blanks)

        # Metadata to save in both file headers
        x_extent, y_extent = self.roi_scan_image_extent
        parameters = OrderedDict()
        parameters['roi_name'] = self.roi_name
        parameters['poi_nametag'] = '' if self.poi_nametag is None else self.poi_nametag
        parameters['roi_creation_time'] = self.roi_creation_time_as_str
        parameters['scan_image_x_extent'] = '{0:.9e},{1:.9e}'.format(*x_extent)
        parameters['scan_image_y_extent'] = '{0:.9e},{1:.9e}'.format(*y_extent)

        ##################################
        # Save POI positions to first file
        ##################################
        poi_dict = self.poi_positions
        poi_positions = np.array(tuple(poi_dict.values()), dtype=float)
        data = OrderedDict()
        # Save POI names in the first column
        data['name'] = np.array(tuple(poi_dict), dtype=str)
        # Save x,y,z coordinates in the following 3 columns
        data['X (m)'] = poi_positions[:, 0]
        data['Y (m)'] = poi_positions[:, 1]
        data['Z (m)'] = poi_positions[:, 2]

        self.savelogic().save_data(data,
                                   timestamp=timestamp,
                                   filepath=filepath,
                                   parameters=parameters,
                                   filelabel=pois_filename,
                                   fmt=['%s', '%.6e', '%.6e', '%.6e'])

        ############################################
        # Save ROI history to second file (binary) if present
        ############################################
        if len(self.roi_pos_history) > 1:
            np.save(os.path.join(filepath, roi_history_filename), self.roi_pos_history)

        #######################################################
        # Save ROI scan image to third file (binary) if present
        #######################################################
        if self.roi_scan_image is not None:
            np.save(os.path.join(filepath, roi_image_filename), self.roi_scan_image)
        return

    def load_roi(self, complete_path=None):
        if complete_path is None:
            return
        filepath, filename = os.path.split(complete_path)

        # Try to detect legacy file format
        is_legacy_format = False
        if not complete_path.endswith('_poi_list.dat'):
            self.log.info('Trying to read ROI from legacy file format...')
            with open(complete_path, 'r') as file:
                for line in file.readlines():
                    if line.strip() == '#POI Name\tPOI Key\tX\tY\tZ':
                        is_legacy_format = True
                    elif not line.startswith('#'):
                        break
            if not is_legacy_format:
                self.log.error('Unable to load ROI from file. File format not understood.')
                return

        if is_legacy_format:
            filetag = filename.split('_', 1)[1].rsplit('.dat', 1)[0]
        else:
            filetag = filename.rsplit('_poi_list.dat', 1)[0]

        # Read POI data as well as roi metadata from textfile
        poi_names = np.loadtxt(complete_path, delimiter='\t', usecols=0, dtype=str)
        if is_legacy_format:
            poi_coords = np.loadtxt(complete_path, delimiter='\t', usecols=(2, 3, 4), dtype=float)
        else:
            poi_coords = np.loadtxt(complete_path, delimiter='\t', usecols=(1, 2, 3), dtype=float)

        # Create list of POI instances
        poi_list = [PointOfInterest(pos, poi_names[i]) for i, pos in enumerate(poi_coords)]

        roi_name = None
        poi_nametag = None
        roi_creation_time = None
        scan_extent = None
        if is_legacy_format:
            roi_name = filetag
        else:
            with open(complete_path, 'r') as file:
                for line in file.readlines():
                    if not line.startswith('#'):
                        break
                    if line.startswith('#roi_name:'):
                        roi_name = line.split('#roi_name:', 1)[1].strip()
                    elif line.startswith('#poi_nametag:'):
                        poi_nametag = line.split('#poi_nametag:', 1)[1].strip()
                    elif line.startswith('#roi_creation_time:'):
                        roi_creation_time = line.split('#roi_creation_time:', 1)[1].strip()
                    elif line.startswith('#scan_image_x_extent:'):
                        scan_x_extent = line.split('#scan_image_x_extent:', 1)[1].strip().split(',')
                    elif line.startswith('#scan_image_y_extent:'):
                        scan_y_extent = line.split('#scan_image_y_extent:', 1)[1].strip().split(',')
            scan_extent = ((float(scan_x_extent[0]), float(scan_x_extent[1])),
                           (float(scan_y_extent[0]), float(scan_y_extent[1])))
            poi_nametag = None if not poi_nametag else poi_nametag

        # Read ROI position history from binary file
        history_filename = os.path.join(filepath, '{0}_history.npy'.format(filetag))
        try:
            roi_history = np.load(history_filename)
        except FileNotFoundError:
            roi_history = None

        # Read ROI scan image from binary file
        image_filename = os.path.join(filepath, '{0}_scan_image.npy'.format(filetag))
        try:
            roi_scan_image = np.load(image_filename)
        except FileNotFoundError:
            roi_scan_image = None

        # Reset current ROI and initialize new one from loaded data
        self.reset_roi()
        self._roi = RegionOfInterest(name=roi_name,
                                     creation_time=roi_creation_time,
                                     history=roi_history,
                                     scan_image=roi_scan_image,
                                     scan_image_extent=scan_extent,
                                     poi_list=poi_list,
                                     poi_nametag=poi_nametag)
        print(poi_nametag, self.poi_nametag)
        self.sigRoiUpdated.emit({'name': self.roi_name,
                                 'poi_nametag': self.poi_nametag,
                                 'pois': self.poi_positions,
                                 'history': self.roi_pos_history,
                                 'scan_image': self.roi_scan_image,
                                 'scan_image_extent': self.roi_scan_image_extent})
        self.set_active_poi(None if len(poi_names) == 0 else poi_names[0])
        return

    @_roi.constructor
    def dict_to_roi(self, roi_dict):
        return RegionOfInterest.from_dict(roi_dict)

    @_roi.representer
    def roi_to_dict(self, roi):
        return roi.to_dict()

    def transform_roi(self, transform_matrix):
        # TODO: Implement this
        if transform_matrix.shape != (3, 3):
            self.log.error('Tranformation matrix must be numpy array of shape (3, 3).')
            return
        self.log.error('Tranformation of all POI positions not implemented yet.')
        return
コード例 #15
0
class MagnetControlLogic(GenericLogic):
    """This is the Logic class for ODMR."""
    _modclass = 'magnetcontrollogic'
    _modtype = 'logic'

    # declare connectors
    fitlogic = Connector(interface='FitLogic')
    savelogic = Connector(interface='SaveLogic')
    magnetstage = Connector(interface='magnet_control_interface')
    counter = Connector(interface='CounterLogic')

    curr_x_pos = StatusVar('curr_x_pos', 0.0000)
    curr_y_pos = StatusVar('curr_y_pos', 0.0000)
    curr_z_pos = StatusVar('curr_z_pos', 0.0000)

    set_x_pos = StatusVar('set_x_pos', 0.0000)
    set_y_pos = StatusVar('set_y_pos', 0.0000)
    set_z_pos = StatusVar('set_z_pos', 0.0000)

    N_AF_points = StatusVar('N_AF_points', 10)

    x_start = StatusVar('x_start', 9.4600)
    x_end = StatusVar('x_end', 9.9600)
    step_x = StatusVar('step_x', 0.0300)
    n_x_points = StatusVar('n_x_points', 0.0)

    y_start = StatusVar('y_start', 9.4600)
    y_end = StatusVar('y_end', 9.9600)
    step_y = StatusVar('step_y', 0.0300)
    n_y_points = StatusVar('n_y_points', 0.0)

    Xmax = StatusVar('Xmax', 0.0000)
    Ymax = StatusVar('Ymax', 0.0000)

    x_scan_fit_x = StatusVar('x_scan_fit_x', 0.0000)
    x_scan_fit_y = StatusVar('x_scan_fit_x', 0.0000)
    y_scan_fit_x = StatusVar('x_scan_fit_x', 0.0000)
    y_scan_fit_y = StatusVar('x_scan_fit_x', 0.0000)

    fc = StatusVar('fits', None)
    i = StatusVar('i', 0)

    motion_time = StatusVar('motion_time', 0.0000)

    fluorescence_integration_time = StatusVar('fluorescence_integration_time',
                                              0.5)

    # Update signals, e.g. for GUI module
    sigPlotXUpdated = QtCore.Signal(np.ndarray, np.ndarray)
    sigPlotYUpdated = QtCore.Signal(np.ndarray, np.ndarray)
    sigFitXUpdated = QtCore.Signal(np.ndarray, np.ndarray, dict, str)
    sigFitYUpdated = QtCore.Signal(np.ndarray, np.ndarray, dict, str)
    sigPositionUpdated = QtCore.Signal()
    sigNextXPoint = QtCore.Signal()
    sigNextYPoint = QtCore.Signal()
    signal_stop_scanning = QtCore.Signal()

    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._magnetstage = self.get_connector('magnetstage')
        self._fit_logic = self.get_connector('fitlogic')
        self._counter = self.get_connector('counter')
        self._save_logic = self.get_connector('savelogic')

        # Set flags
        # for stopping a measurement
        self.stopRequested = False

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

        # Connect signals
        self.sigNextXPoint.connect(self._next_x_point,
                                   QtCore.Qt.QueuedConnection)
        self.sigNextYPoint.connect(self._next_y_point,
                                   QtCore.Qt.QueuedConnection)

        return

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

    @fc.constructor
    def sv_set_fits(self, val):
        # Setup fit container
        fc = self.fitlogic().make_fit_container('length', '1d')
        fc.set_units(['mm', 'c/s'])
        if isinstance(val, dict) and len(val) > 0:
            fc.load_from_dict(val)
        else:
            d1 = OrderedDict()
            d1['Gaussian peak'] = {
                'fit_function': 'gaussian',
                'estimator': 'peak'
            }

            d1['Lorentzian peak'] = {
                'fit_function': 'lorentzian',
                'estimator': 'peak'
            }
            d1['Two Lorentzian dips'] = {
                'fit_function': 'lorentziandouble',
                'estimator': 'dip'
            }
            d1['N14'] = {
                'fit_function': 'lorentziantriple',
                'estimator': 'N14'
            }
            d1['N15'] = {
                'fit_function': 'lorentziandouble',
                'estimator': 'N15'
            }

            default_fits = OrderedDict()
            default_fits['1d'] = d1['Gaussian peak']

            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_plots(self):
        """ Initializing the ODMR plots (line and matrix). """
        self.fluor_plot_x = np.arange(self.x_start, self.x_end, self.step_x)
        self.fluor_plot_y = np.zeros(self.fluor_plot_x.size)

        self.x_scan_fit_x = np.arange(self.x_start, self.x_end, self.step_x)
        self.x_scan_fit_y = np.zeros(self.fluor_plot_x.size)

        self.yfluor_plot_x = np.arange(self.y_start, self.y_end, self.step_y)
        self.yfluor_plot_y = np.zeros(self.yfluor_plot_x.size)

        self.y_scan_fit_x = np.arange(self.y_start, self.y_end, self.step_y)
        self.y_scan_fit_y = np.zeros(self.yfluor_plot_x.size)

        self.sigPlotXUpdated.emit(self.fluor_plot_x, self.fluor_plot_y)
        self.sigPlotYUpdated.emit(self.yfluor_plot_x, self.yfluor_plot_y)

        current_x_fit = self.fc.current_fit
        self.sigFitXUpdated.emit(self.x_scan_fit_x, self.x_scan_fit_y, {},
                                 current_x_fit)

        current_y_fit = self.fc.current_fit
        self.sigFitXUpdated.emit(self.y_scan_fit_x, self.y_scan_fit_y, {},
                                 current_y_fit)
        return

    def get_current_position(self):
        try:
            self.curr_x_pos = float(
                self._magnetstage.get_current_position(1)[3:-2])
        except pyvisa.errors.VisaIOError:
            print('visa error')
            time.sleep(0.05)
            try:
                self.curr_x_pos = float(
                    self._magnetstage.get_current_position(1)[3:-2])
            except pyvisa.errors.VisaIOError:
                print('visa error')
                time.sleep(0.05)
                self.curr_x_pos = float(
                    self._magnetstage.get_current_position(1)[3:-2])

        time.sleep(0.05)
        try:
            self.curr_y_pos = float(
                self._magnetstage.get_current_position(2)[3:-2])
        except pyvisa.errors.VisaIOError:
            print('visa error')
            time.sleep(0.05)
            self.curr_y_pos = float(
                self._magnetstage.get_current_position(2)[3:-2])
        self.curr_z_pos = float(
            self._magnetstage.get_current_position(3)[3:-2])

        return

    def set_position(self):
        self._magnetstage.move_absolute(1, self.set_x_pos)
        self._magnetstage.move_absolute(2, self.set_y_pos)
        self._magnetstage.move_absolute(3, self.set_z_pos)
        return

    def start_x_scanning(self, tag='logic'):
        """Starts scanning
        """
        with self.threadlock:
            if self.module_state() == 'locked':
                self.log.error(
                    'Can not start fluorescence scan. Logic is already locked.'
                )

                return -1

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

            self.step_x = int(self.step_x / 2e-4) * 2e-4
            self.fluor_plot_x = np.arange(self.x_start, self.x_end,
                                          self.step_x)
            self.fluor_plot_y = np.zeros((len(self.fluor_plot_x)))
            self.curr_x_pos = float(
                self._magnetstage.get_current_position(1)[3:-2])
            time.sleep(0.1)
            self.motion_time = float(
                self._magnetstage.get_motiontime_relativemove(
                    1, self.step_x)[3:-2]) + 0.05

            if self.x_start != self.curr_x_pos:
                t = float(
                    self._magnetstage.get_motiontime_relativemove(
                        1, np.abs(self.x_start - self.curr_x_pos))[3:-2])
                self._magnetstage.move_absolute(1, self.x_start)
                time.sleep(t + 1)

            self.get_current_position()
            self.i = 0
            self.sigNextXPoint.emit()

            return 0

    def _next_x_point(self):

        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._magnetstage.stop_motion(1)
                self.signal_stop_scanning.emit()
                self.module_state.unlock()
                return

            # Move the magnet
            self._magnetstage.move_relative(1, self.step_x)
            time.sleep(self.motion_time)
            self.curr_x_pos = float(
                self._magnetstage.get_current_position(1)[3:-2])
            time.sleep(0.1)
            if self.curr_x_pos > (self.x_end + self.step_x):
                self.stopRequested = False
                self._magnetstage.stop_motion(1)
                self.module_state.unlock()
                self.signal_stop_scanning.emit()
                return

            # Acquire count data
            if self.i <= (self.n_x_points - 1):
                self.fluor_plot_y[
                    self.i] = self._perform_fluorescence_measure()[0]
                self.i = self.i + 1

            else:
                self.module_state.unlock()
                self.signal_stop_scanning.emit()
                return

            # Fire update signals
            self.sigPlotXUpdated.emit(self.fluor_plot_x, self.fluor_plot_y)
            self.sigPositionUpdated.emit()
            self.sigNextXPoint.emit()
            return

    def start_y_scanning(self, tag='logic'):
        """Starts scanning
        """
        with self.threadlock:
            if self.module_state() == 'locked':
                self.log.error(
                    'Can not start ODMR scan. Logic is already locked.')
                return -1

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

            self.step_y = int(self.step_y / 2e-4) * 2e-4
            self.yfluor_plot_x = np.arange(self.y_start, self.y_end,
                                           self.step_y)
            self.yfluor_plot_y = np.zeros((len(self.yfluor_plot_x)))
            self.curr_y_pos = float(
                self._magnetstage.get_current_position(1)[3:-2])

            time.sleep(0.1)
            self.motion_time = float(
                self._magnetstage.get_motiontime_relativemove(
                    2, self.step_y)[3:-2]) + 0.05
            if self.y_start != self.curr_y_pos:
                t = float(
                    self._magnetstage.get_motiontime_relativemove(
                        2, np.abs(self.y_start - self.curr_y_pos))[3:-2])
                self._magnetstage.move_absolute(2, self.y_start)
                time.sleep(t + 1)

            self.get_current_position()
            self.i = 0
            self.sigNextYPoint.emit()

            return 0

    def _next_y_point(self):

        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._magnetstage.stop_motion(2)
                self.signal_stop_scanning.emit()
                self.module_state.unlock()
                return

            # Move the magnet
            self._magnetstage.move_relative(2, self.step_y)
            time.sleep(self.motion_time + 0.1)
            self.curr_y_pos = float(
                self._magnetstage.get_current_position(2)[3:-2])
            time.sleep(0.1)
            if self.curr_y_pos > (self.y_end + self.step_y):
                self.stopRequested = False
                self._magnetstage.stop_motion(2)
                self.module_state.unlock()
                self.signal_stop_scanning.emit()
                return

            # Acquire count data
            if self.i <= (self.n_y_points - 1):
                self.yfluor_plot_y[
                    self.i] = self._perform_fluorescence_measure()[0]
                self.i = self.i + 1
            else:
                self.module_state.unlock()
                self.signal_stop_scanning.emit()
                return

            # Fire update signals
            self.sigPlotYUpdated.emit(self.yfluor_plot_x, self.yfluor_plot_y)
            self.sigPositionUpdated.emit()
            self.sigNextYPoint.emit()
            return

    def stop_scanning(self):
        """Stops the scan

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

    def _perform_fluorescence_measure(self):

        #FIXME: that should be run through the TaskRunner! Implement the call
        #       by not using this connection!

        if self._counter.get_counting_mode() != 0:
            self._counter.set_counting_mode(mode='CONTINUOUS')

        self._counter.start_saving()
        time.sleep(self.fluorescence_integration_time)
        self._counter.stopCount()
        data_array, parameters = self._counter.save_data(to_file=False)

        data_array = np.array(data_array)[:, 1]

        return data_array.mean(), parameters

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

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

    def do_x_fit(self, fit_function=None, x_data=None, y_data=None):
        """
        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.fluor_plot_x
            y_data = self.fluor_plot_y

        if fit_function is not None and isinstance(fit_function, str):
            if fit_function in self.get_fit_x_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.x_scan_fit_x, self.x_scan_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
        # print(result.result_str_dict)
        self.sigFitXUpdated.emit(self.x_scan_fit_x, self.x_scan_fit_y,
                                 result_str_dict, self.fc.current_fit)
        return

    def do_y_fit(self, fit_function=None, x_data=None, y_data=None):
        """
        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.yfluor_plot_x
            y_data = self.yfluor_plot_y

        if fit_function is not None and isinstance(fit_function, str):
            if fit_function in self.get_fit_y_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.y_scan_fit_x, self.y_scan_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.sigFitYUpdated.emit(self.y_scan_fit_x, self.y_scan_fit_y,
                                 result_str_dict, self.fc.current_fit)
        return

    def save_data(self,
                  tag=None,
                  colorscale_range=None,
                  percentile_range=None):
        """ Saves the current data to a file."""
        if tag is None:
            tag = ''

        # two paths to save the raw data and the odmr scan data.
        filepath = self._save_logic.get_path_for_module(module_name='MAGNET')
        filepath2 = self._save_logic.get_path_for_module(module_name='MAGNET')

        timestamp = datetime.datetime.now()

        if len(tag) > 0:
            filelabel = tag + '_ODMR_data'
            filelabel2 = tag + '_ODMR_data_raw'
        else:
            filelabel = 'ODMR_data'
            filelabel2 = 'ODMR_data_raw'

        # 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
        data2['count data (counts/s)'] = self.odmr_raw_data[:self.
                                                            elapsed_sweeps, :]

        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
        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(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))
        return

    def draw_figure(self, 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
        fit_freq_vals = self.odmr_fit_x
        fit_count_vals = self.odmr_fit_y
        matrix_data = self.odmr_plot_xy

        # 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
コード例 #16
0
class SingleShotLogic(GenericLogic):
    """ This class brings raw data coming from fastcounter measurements (gated or ungated)
        into trace form processable by the trace_analysis_logic.
    """

    _modclass = 'SingleShotLogic'
    _modtype = 'logic'

    # declare connectors
    savelogic = Connector(interface='SaveLogic')
    fitlogic = Connector(interface='FitLogic')
    fastcounter = Connector(interface='FastCounterInterface')
    pulseextractionlogic = Connector(interface='PulseExtractionLogic')
    pulsedmeasurementlogic = Connector(interface='PulsedMeasurementLogic')
    traceanalysislogic1 = Connector(interface='TraceAnalysisLogic')
    pulsegenerator = Connector(interface='PulserInterface')
    scannerlogic = Connector(interface='ScannerLogic')
    optimizerlogic = Connector(interface='OptimizerLogic')
    pulsedmasterlogic = Connector(interface='PulsedMasterLogic')
    odmrlogic = Connector(interface='ODMRLogic')

    # add possible signals here
    sigHistogramUpdated = QtCore.Signal()
    sigMeasurementFinished = QtCore.Signal()
    sigTraceUpdated = QtCore.Signal()

    def __init__(self, config, **kwargs):
        """ Create CounterLogic object with connectors.

        @param dict config: module configuration
        @param dict kwargs: optional parameters
        """
        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.debug('{0}: {1}'.format(key, config[key]))

        # initalize internal variables here
        self.hist_data = None
        self._hist_num_bins = None

        self.data_dict = None

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

        self._fast_counter_device = self.get_connector('fastcounter')
        self._pulse_generator_device = self.get_connector('pulsegenerator')
        self._save_logic = self.get_connector('savelogic')
        self._fit_logic = self.get_connector('fitlogic')
        self._traceanalysis_logic = self.get_connector('traceanalysislogic1')
        self._pe_logic = self.get_connector('pulseextractionlogic')
        self._pm_logic = self.get_connector('pulsedmeasurementlogic')
        self._odmr_logic = self.get_connector('odmrlogic')
        self._pulsed_master_logic = self.get_connector('pulsedmasterlogic')
        self._confocal_logic = self.get_connector('scannerlogic')
        self._optimizer_logic = self.get_connector('optimizerlogic')

        self.hist_data = None
        self.trace = None
        self.sigMeasurementFinished.connect(self.ssr_measurement_analysis)

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

        @param object e: Event class object from Fysom. A more detailed
                         explanation can be found in method activation.
        """
        return

    # =========================================================================
    #                           Raw Data Analysis
    # =========================================================================

    def get_data(self, fastcounter='fastcomtec'):
        """
        get the singleshot data from the fastcounter along with its shape
        @param: optional string fastcounter: Determines how the data is extracted from the fastcounter
        @return: dictionary containing shape of the data as well as the raw data coming from fastcounter and
                 possible additional data to calculate dt ( the time between two singleshot measurements ) later.
        """
        return_dict = OrderedDict()

        if not self._fast_counter_device.is_gated():
            if fastcounter == 'fastcomtec':
                settings = self._fast_counter_device.get_settings()
                # check if settings object is coming from a remote connection
                settings = netobtain(settings)
                n_rows = settings.cycles
                # looks like this is in ns, but I'm not completely sure
                n_columns = settings.range
                reps_per_row = settings.swpreset
                raw_data = netobtain(
                    self._fast_counter_device.get_data_trace(sweep_reset=True))

                return_dict['n_rows'] = n_rows
                return_dict['n_columns'] = n_columns
                return_dict['reps_per_row'] = reps_per_row
                return_dict['raw_data'] = raw_data
                # needed to internally calculate the measurement time, unless the columns are
                # always in ns ?
                return_dict[
                    'bin_width'] = self._fast_counter_device.get_binwidth()
            else:
                self.log.warning(
                    'other ungated counters are not implemented at the moment')
        else:
            self.log.warning('using gated counter not implemented yet')

        self.data_dict = return_dict

        return 0

    def find_laser(self, smoothing=10.0, n_laserpulses=2):
        """
        returns the start and stop indices of laserpulses
        @param smoothing: smoothing data to improve flank detection
        @param n_laserpulses: the number of laserpulses expected in the data
        @return: list containing tupels of start and stop values of individual laser pulses
        """

        data = self.data_dict['raw_data']
        n_rows = self.data_dict['n_rows']

        # we want to add up the pulses along the cycles axis
        shape = data.shape
        if shape[0] == n_rows:
            axis = 0
        elif shape[1] == n_rows:
            axis = 1
        else:
            self.log.debug(
                'something went wrong in identifying the correct axis of data in find_laser '
                'of singleshot_logic')

        summed_pulses = np.sum(data, axis)

        # TODO make the type of pulsed extraction adjustable
        self._pe_logic.number_of_lasers = n_laserpulses
        self._pe_logic.conv_std_dev = smoothing
        return_dict = self._pe_logic.ungated_extraction_methods['conv_deriv'](
            summed_pulses)
        rising_ind = return_dict['laser_indices_rising']
        falling_ind = return_dict['laser_indices_falling']

        start_stop_tupel_list = []
        for jj, rising in enumerate(rising_ind):
            start_stop_tupel_list.append((rising, falling_ind[jj]))

        return start_stop_tupel_list

    def sum_laserpulse(self, smoothing=10.0, n_laserpulses=2):
        """
        First find the laserpulses, then go on to add up the individual laserpulses.
        After that the data depending on the sequence may need normalization and higher
        binwidths may be calculated ( add to gather 2 up to floor( n_rows // 2) values ).

        @param float smoothing: If pulse detection doesn't work, change this value
        @return numpy array: dimensionality is n_rows x n_laserpulses
        """
        sum_single_pulses = []
        start_stop_tupel_list = self.find_laser(smoothing=smoothing,
                                                n_laserpulses=n_laserpulses)
        if self.data_dict:
            data = self.data_dict['raw_data']
            for row in data:
                laser_pulses = [
                    np.sum(row[jj[0]:jj[1]]) for jj in start_stop_tupel_list
                ]
                sum_single_pulses.append(laser_pulses)
        else:
            self.log.error(
                'Pull data from fastcounting device using get_data function before trying to sum_laserpulse.'
            )

        return np.array(sum_single_pulses)

    def get_normalized_signal(self, smoothing=10.0):
        """
        given the raw data this function will calculate
        the normalized signal. It assumes that the pulse sequence used has
        2 laserpulses ( for normalization purposes )

        @param float smoothing: If pulse detection doesn't work, change this value
        @return numpy array: 1D array containing the normalized signal
        """

        sum_single_pulses = self.sum_laserpulse()
        if sum_single_pulses.shape[1] == 2:
            normalized_signal = np.array([(ii[0] - ii[1]) / (ii[0] + ii[1])
                                          for ii in sum_single_pulses])
        else:
            self.log.warning(
                'could not perform normalisation. Wrong number of laserpulses.'
            )

        return normalized_signal

    def calc_all_binnings(self, num_bins=100):
        """
        calculate reasonable binnings of the signal
        @param int num_bins: minimal number the binnings can have
        @return list bin_list: Contains the arrays with the binned data.
                               Data is structured as follows: bin_list[0] is the
                               initial binning given by the measurement and then going up.
        """

        if self.data_dict:
            data = self.data_dict
        else:
            self.log.error(
                'Pull data from fastcounting device using get_data function '
                'before trying to calc_all_binnings.')

        NN = data['n_rows']
        # this is just a guess value, at some point it doesn't make
        # sense anymore to further decrease the number of bins
        max_bin = NN // num_bins
        count_var = 1
        bin_list = []
        temp_list = []
        signal = self.sum_laserpulse()
        while count_var <= max_bin:
            # check if the the first run through loop was done
            if temp_list:
                app_arr = np.array(temp_list)
                bin_list.append(app_arr)
                temp_list = []
            jj = 0
            while jj < NN:
                sum_ind = np.linspace(jj,
                                      jj + count_var - 1,
                                      count_var,
                                      dtype=np.int)
                # make sure we don't try to adress not reserved memory
                jj += count_var
                if sum_ind[-1] < NN:
                    # normalize
                    temp_list.append(
                        np.array([
                            np.sum(signal[sum_ind, 0]),
                            np.sum(signal[sum_ind, 1])
                        ]))
                else:
                    jj = NN
            count_var += 1

        return np.array(bin_list)

    def calc_all_binnings_normalized(self, num_bins=100):
        """
        Calculate all normalized binnings from singleshot data
        @param integer num_bins: Tells how many data points should still remain ( in this sense restricts the maximum
                                 number of data points added up together )
        @return list normalized_bin_list: The entries are numpy arrays that represent different binnings
                                          ( 1 to n values)
        """

        bin_list = self.calc_all_binnings(num_bins=num_bins)
        normalized_bin_list = []
        for binning in bin_list:
            normalized_binning = (binning[:, 0] - binning[:, 1]) / (
                binning[:, 0] + binning[:, 1])
            normalized_bin_list.append(normalized_binning)

        return np.array(normalized_bin_list)

    def get_timetrace(self):
        """
        This function will help to find the optimal binning in the fastcomtec data,
        for now under development, as we decided for now to manually pick the right binning.
        Therefore the function visualize_bin_list is there to help with that task. It will make
        a plot of all binnings and show which shows the best features.
        @return:
        """
        # what needs to be done here now is the basic evaluation steps like fit, threshold
        # readout fidelity

        bin_list = self.calc_all_binnings(self, num_bins=100)

        param_dict_list = []
        fidelity_list = []
        for ii in bin_list:
            # what is a good estimate for the number of bins ?
            hist_y_val, hist_x_val = np.histogram(ii, bins=50)
            hist_data = np.array([hist_x_val, hist_y_val])
            threshold_fit, fidelity, \
            param_dict = self._traceanalysis_logic.calculate_threshold(hist_data=hist_data,
                                                                       distr='gaussian_normalized')
            param_dict_list.append(param_dict)
            fidelity_list.append(fidelity)

        # now get the maximum fidelity, not really working up till now. The fidelity alone is not a good indicator
        # because the fit can still be bad. Need somehow a mixed measure of this. Will look for some heuristic.

        ind = np.argmax(np.array(fidelity_list))

        timetrace = bin_list[ind]

        return timetrace

    # =========================================================================
    #                           Single Shot measurements
    # =========================================================================

    # TODO make more general for other devices
    def do_singleshot(self,
                      mw_dict=None,
                      refocus=True,
                      laser_wfm='LaserOn',
                      singleshot_wfm='SSR_normalise_2MW',
                      normalized=True):
        """
        For additional microwave usage this assumes an external signal generator. Could be also done with an
        additional pulser channel though.
        """
        use_mw = False
        if mw_dict:
            use_mw = True
            if mw_dict['freq']:
                mw_freq = mw_dict['freq']
                self._odmr_logic.set_frequency(frequency=mw_freq)
            if mw_dict['power']:
                mw_power = mw_dict['power']
                self._odmr_logic.set_power(mw_power)
        # set mw power
        # self._odmr_logic.set_power(mw_power)

        # turn on laser to refocus
        self._pulse_generator_device.load_asset(laser_wfm)
        self._pulse_generator_device.pulser_on()
        # TODO make this with queue connections
        if refocus:
            self._do_optimize_pos()
        # load sequence for SSR measurement
        self._pulse_generator_device.load_asset(singleshot_wfm)
        self._pulse_generator_device.pulser_on()

        # set mw frequency and turn it on
        if use_mw:
            self._odmr_logic.MW_on()

        self._fast_counter_device.start_measure()
        time.sleep(10)
        # try to do this differently
        tmp_var1 = self._fast_counter_device.get_status()
        while (tmp_var1 - 1):
            time.sleep(5)
            tmp_var1 = self._fast_counter_device.get_status()

        # pull data. This will also update the variable self.data_dict
        self.get_data()

        if normalized:
            bin_list = self.calc_all_binnings()
        else:
            bin_list = self.calc_all_binnings_normalized()

        return bin_list

    # I would very much like to have this function here, both in respect to the magnet logic, which will
    # need such a function for the nuclear alignment as well as for other measurements such as rf odmr and so on
    # therefore I'm going to include it here.
    # TODO include focusing on a single peak here
    # TODO refocus replaced through refocus frequency
    def do_pulsed_odmr(self,
                       measurement_time,
                       controlled_vals_start,
                       controlled_vals_incr,
                       num_of_lasers,
                       sequence_length_s,
                       refocus=True,
                       pulsedODMR_wfm='PulsedODMR',
                       save_tag=''):
        """
        A function to do pulsed odmr. Important as exact transition frequencies are important.
        @param measurement_time:
        @param controlled_vals_start:
        @param controlled_vals_incr:
        @param num_of_lasers:
        @param sequence_length_s:
        @param refocus:
        @param pulsedODMR_wfm:
        @param save_tag:
        @return:
        """
        laser_ignore_list = []
        # TODO maybe this data is also differently available or units can be set within the logic
        alternating = False

        controlled_vals = np.arange(
            controlled_vals_start,
            controlled_vals_start + (controlled_vals_incr * num_of_lasers) -
            (controlled_vals_incr / 2), controlled_vals_incr)

        self._pulsed_master_logic.measurement_sequence_settings_changed(
            controlled_vals, num_of_lasers, sequence_length_s,
            laser_ignore_list, alternating)
        self._pm_logic._initialize_plots()

        self._pulse_generator_device.load_asset(pulsedODMR_wfm)
        self._pulsed_master_logic.start_measurement()
        if refocus:
            self._do_optimize_pos()
        time.sleep(measurement_time)
        self._pulsed_master_logic.stop_measurement()

        freqs = self._pm_logic.signal_plot_x
        signal = self._pm_logic.signal_plot_y
        # now everything is saved, lets do the fitting
        results = self._fit_logic.make_N14_fit(freqs, signal)
        freq_peaks = np.array([
            results.params['l0_center'].value,
            results.params['l1_center'].value,
            results.params['l2_center'].value
        ])
        if save_tag:
            controlled_val_unit = 'Hz'
            self._pulsed_master_logic.save_measurement_data(
                controlled_val_unit, save_tag)

        return freq_peaks

    def save_singleshot(self, tag=None, normalized=True, visualize=True):
        """
        When called this will save the attribute data_dict of class savelogic to file.
        The raw data will be postprocessed to bin lists as well as normalized bin lists
        ( containing all the possible binnings of the data. Additionally the meta_data
        will be saved.
        @return:
        """
        filepath = self._save_logic.get_path_for_module(
            module_name='SingleShot')
        timestamp = datetime.datetime.now()
        timestamp_str = timestamp.strftime('%Y%m%d-%H%M-%S')
        if normalized:
            if tag is not None and len(tag) > 0:
                filelabel2 = tag + '_' + timestamp_str + '_normalized_bin_list'
            else:
                filelabel2 = timestamp_str + '_normalized_bin_list'

            normalized_bin_list = self.calc_all_binnings_normalized()
            save_path2 = os.path.join(filepath, filelabel2)
            np.save(save_path2, normalized_bin_list)
            if visualize:
                visualize_path = os.path.join(
                    filepath, timestamp_str + '_visualize_bins')
                os.mkdir(visualize_path)
                self.visualize_bin_list(normalized_bin_list, visualize_path)

        else:
            if tag is not None and len(tag) > 0:
                filelabel1 = tag + '_' + timestamp_str + '_bin_list'
            else:
                filelabel1 = timestamp_str + '_bin_list'

            bin_list = self.calc_all_binnings()
            save_path1 = os.path.join(filepath, filelabel1)
            np.save(save_path1, bin_list)

        meta_data_dict = copy.deepcopy(self.data_dict)
        meta_data_dict.pop('raw_data')
        meta_path = os.path.join(filepath, timestamp_str + '_meta_data')
        np.save(meta_path, meta_data_dict)
        for key in meta_data_dict:
            meta_data_dict[key] = [meta_data_dict[key]]
        self._save_logic.save_data(meta_data_dict,
                                   filepath=filepath,
                                   filelabel='meta_data')

        return

    # Helper methods

    def _do_optimize_pos(self):

        curr_pos = self._confocal_logic.get_position()

        self._optimizer_logic.start_refocus(curr_pos,
                                            caller_tag='singleshot_logic')

        # check just the state of the optimizer
        while self._optimizer_logic.getState() != 'idle':
            time.sleep(0.5)

        # use the position to move the scanner
        self._confocal_logic.set_position('magnet_logic',
                                          self._optimizer_logic.optim_pos_x,
                                          self._optimizer_logic.optim_pos_y,
                                          self._optimizer_logic.optim_pos_z)

    def visualize_bin_list(self, bin_list, path):
        """
        Will create a histogram of all bin_list entries and save it to the specified path
        """
        # TODO use savelogic here
        for jj, bin_entry in enumerate(bin_list):
            hist_x, hist_y = self._traceanalysis_logic.calculate_histogram(
                bin_entry, num_bins=50)
            pb.plot(hist_x[0:len(hist_y)], hist_y)
            fname = 'bin_' + str(jj) + '.png'
            savepath = os.path.join(path, fname)
            pb.savefig(savepath)
            pb.close()

    # =========================================================================
    #                           Connecting to GUI
    # =========================================================================

    # absolutely not working at the moment.

    def ssr_measurement_analysis(self, record_length):
        """
        Gets executed when a single shot measurment has finished. This function will update GUI elements
        @param record_length:
        @return:
        """
        normalized_bin_list = self.calc_all_binnings_normalized(self,
                                                                num_bins=100)

        # for now take only the initial binning
        data = normalized_bin_list[0]
        measurement = self.data_dict
        # also only the initial binning, needs to be adjusted then
        time_axis = np.linspace(
            record_length * measurement['reps_per_row'],
            record_length * (measurement['reps_per_row'] + 1),
            measurement['n_rows'])
        # update the histogram in the gui
        self.do_calculate_histogram(data)

        # update the trace in the gui
        self._do_calculate_trace(time_axis, data)

    def do_calculate_histogram(self, data):
        """ Passes all the needed parameters to the appropriated methods.

        @return:
        """
        self.hist_data = self._traceanalysis_logic.calculate_histogram(
            data, self._traceanalysis_logic._hist_num_bins)

        self.sigHistogramUpdated.emit()

    def do_calculate_trace(self, time_axis, data):

        self.trace = np.array([time_axis, data])
        self.sigTraceUpdated.emit()
コード例 #17
0
class PolarizationDependenceSim(Base, SlowCounterInterface, MotorInterface):
    """ This class wraps the slow-counter dummy and adds polarisation angle dependence in order to simulate dipole polarisation measurements.
    """

    _modclass = 'polarizationdepsim'
    _modtype = 'hardware'

    # Connectors
    counter1 = Connector(interface='SlowCounterInterface')

    _move_signal = QtCore.Signal()

    def on_activate(self):
        """ Activation of the class
        """
        # name connected modules
        self._counter_hw = self.get_connector('counter1')

        # Required class variables to pretend to be the counter hardware
        self._photon_source2 = None

        # initialize class variables
        self.hwp_angle = 0

        self.dipole_angle = random.uniform(0, 360)

        self.velocity = 10
        self.clock_frequency = 50
        self.forwards_motion = True
        self.moving = False

        # Signals
        self._move_signal.connect(self._move_step, QtCore.Qt.QueuedConnection)

    def on_deactivate(self):
        self._counter_hw.close_counter()
        self._counter_hw.close_clock()

    # Wrapping the slow counter methods
    def set_up_clock(self, clock_frequency=None, clock_channel=None):
        """ Direct pass-through to the counter hardware module
        """
        self.clock_frequency = clock_frequency
        return self._counter_hw.set_up_clock(clock_frequency=clock_frequency,
                                             clock_channel=clock_channel)

    def get_constraints(self):
        """ Pass through counter constraints. """
        return self._counter_hw.get_constraints()

    def set_up_counter(self,
                       counter_channel=None,
                       photon_source=None,
                       counter_channel2=None,
                       photon_source2=None,
                       clock_channel=None):
        """ Direct pass-through to the counter hardware module
        """
        return self._counter_hw.set_up_counter(
            counter_channel=counter_channel,
            photon_source=photon_source,
            counter_channel2=counter_channel2,
            photon_source2=photon_source2,
            clock_channel=clock_channel)

    def get_counter(self, samples=None):
        """ Direct pass-through to the counter hardware module
        """
        raw_count = self._counter_hw.get_counter(samples=samples)

        # modulate the counts with a polarisation dependence
        angle = np.radians(self.hwp_angle - self.dipole_angle)
        count = raw_count * np.sin(angle) * np.sin(angle) + random.uniform(
            -0.1, 0.1)
        return count

    def close_counter(self):
        """ Direct pass-through to the counter hardware module
        """
        return self._counter_hw.close_counter()

    def close_clock(self, power=0):
        """ Direct pass-through to the counter hardware module
        """
        return self._counter_hw.close_clock(power=power)

    # Satisfy the motor interface

    def move_rel(self, axis=None, distance=None):
        """ Move the polarisation angle by relative degrees
        """
        if distance is None:
            #TODO warning
            pass

        self.destination = self.hwp_angle + distance

        # Keep track of the motion direction so we will know when we are past the destination
        if distance > 0:
            self.forwards_motion = True
        else:
            self.forwards_motion = False

        self.moving = True
        self._move_signal.emit()
        return 0

    def move_abs(self, axis=None, position=None):
        """ Move the polarisation angle to absolute degrees
        """
        if position is None:
            #TODO warning
            pass
        self.destination = position

        # Keep track of the motion direction so we will know when we are past the destination
        if position > self.hwp_angle:
            self.forwards_motion = True
        else:
            self.forwards_motion = False

        self.moving = True
        self._move_signal.emit()
        return 0

    def _move_step(self):
        """Make movement steps in a threaded loop
        """

        # if abort is requested, then stop moving
        if not self.moving:
            return

        # If we have reached the destination then stop the movement
        if self.forwards_motion:
            if self.hwp_angle > self.destination:
                return
        else:
            if self.hwp_angle < self.destination:
                return

        # Otherwise make a movement step

        step_size = self.velocity / self.clock_frequency

        if self.forwards_motion:
            self.hwp_angle += step_size
        else:
            self.hwp_angle -= step_size

        time.sleep(1. / self.clock_frequency)
        self._move_signal.emit()

    def abort(self):
        self.moving = False
        return 0

    def get_pos(self, axis=None):
        return self.hwp_angle

    def get_status(self):
        return 0

    def calibrate(self, axis=None):
        self.hwp_angle = 0
        return 0

    def get_velocity(self, axis=None):
        return self.velocity

    def set_velocity(self, axis=None, velocity=None):
        self.velocity = velocity
        return 0
コード例 #18
0
class TraceAnalysisLogic(GenericLogic):
    """ Perform a gated counting measurement with the hardware.  """

    _modclass = 'TraceAnalysisLogic'
    _modtype = 'logic'

    # declare connectors
    counterlogic1 = Connector(interface='CounterLogic')
    savelogic = Connector(interface='SaveLogic')
    fitlogic = Connector(interface='FitLogic')

    sigHistogramUpdated = QtCore.Signal()

    def __init__(self, config, **kwargs):
        """ Create CounterLogic object with connectors.

        @param dict config: module configuration
        @param dict kwargs: optional parameters
        """
        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.debug('{0}: {1}'.format(key, config[key]))

        self.hist_data = None
        self._hist_num_bins = None

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

        self._counter_logic = self.get_connector('counterlogic1')
        self._save_logic = self.get_connector('savelogic')
        self._fit_logic = self.get_connector('fitlogic')

        self._counter_logic.sigGatedCounterFinished.connect(
            self.do_calculate_histogram)

        self.current_fit_function = 'No Fit'

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

    def set_num_bins_histogram(self, num_bins, update=True):
        """ Set the number of bins

        @param int num_bins: number of bins for the histogram
        @param bool update: if the change of bins should evoke a recalculation
                            of the histogram.
        """
        self._hist_num_bins = num_bins

        if update:
            self.do_calculate_histogram()

    def do_calculate_histogram(self, mode='normal'):
        """ Passes all the needed parameters to the appropriated methods.

        @return:
        """
        if mode == 'normal':
            self.hist_data = self.calculate_histogram(
                self._counter_logic.countdata[0], self._hist_num_bins)
        if mode == 'fastcomtec':
            self.sigHistogramUpdated.emit()

    def calculate_histogram(self, trace, num_bins=None, custom_bin_arr=None):
        """ Calculate the histogram of a given trace.

        @param np.array trace: a 1D trace
        @param int num_bins: number of bins between the minimal and maximal
                             value of the trace. That must be an integer greater
                             than or equal to 1.
        @param np.array custom_bin_arr: optional, 1D array. If a specific,
                                        non-uniform binning array is desired
                                        then it can be passed to the numpy
                                        routine. Then the parameter num_bins is
                                        ignored. Otherwise a uniform binning is
                                        applied by default.
        @return: np.array: a 2D array, where first entry are the x_values and
                           second entry are the count values. The length of the
                           array is normally determined by the num_bins
                           parameter.

        Usually the bins for the histogram are taken to be equally spaced,
        ranging from the minimal to the maximal value of the input trace array.
        """

        if custom_bin_arr is not None:
            hist_y_val, hist_x_val = np.histogram(trace,
                                                  custom_bin_arr,
                                                  density=False)
        else:

            # analyze the trace, and check whether all values are the same
            difference = trace.max() - trace.min()

            # if all values are the same, run at least the method with an zero
            # array. That will ensure at least an output:
            if np.isclose(0, difference) and num_bins is None:
                # numpy can handle an array of zeros
                num_bins = 50
                hist_y_val, hist_x_val = np.histogram(trace, num_bins)

            # if no number of bins are passed, then take the integer difference
            # between the counts, that will prevent strange histogram artifacts:
            elif not np.isclose(0, difference) and num_bins is None:
                hist_y_val, hist_x_val = np.histogram(trace, int(difference))

            # a histogram with self defined number of bins
            else:
                hist_y_val, hist_x_val = np.histogram(trace, num_bins)

        return hist_x_val, hist_y_val

    def analyze_flip_prob(self, trace, num_bins=None, threshold=None):
        """General method, which analysis how often a value was changed from
           one data point to another in relation to a certain threshold.

        @param np.array trace: 1D trace of data
        @param int num_bins: optional, if a specific size for the histogram is
                             desired, which is used to calculate the threshold.
        @param float threshold: optional, if a specific threshold is going to be
                                used, otherwise the threshold is calculated from
                                the data.

        @return tuple(flip_prop, param):

                      float flip_prop: the actual flip probability
                      int num_of_flips: the total number of flips
                      float fidelity: the fidelity
                      float threshold: the calculated or passed threshold
                      float lifetime_dark: the lifetime in the dark state in s
                      float lifetime_bright: lifetime in the bright state in s
        """

        hist_data = self.calculate_histogram(trace=trace, num_bins=num_bins)
        threshold_fit, fidelity, fit_param = self.calculate_threshold(
            hist_data)
        bin_trace = self.calculate_binary_trace(trace, threshold_fit)

        # here the index_arr contain all indices where the state is above
        # threshold, indicating the bright state.
        index_arr, filtered_arr = self.extract_filtered_values(trace,
                                                               threshold_fit,
                                                               below=False)

        # by shifting the index_arr one value further, one will investigate
        # basically the next state, where a change has happened.
        next_index_arr = index_arr + 1

        # Just for safety neglect the last value in the index_arr so that one
        # will not go beyond the array.
        next_filtered_bin_arr = bin_trace[next_index_arr[:-1]]

        # calculate how many darkstates are present in the array, remember
        # filtered_arr contains all the bright states.
        num_dark_state = len(trace) - len(filtered_arr)
        num_bright_state = len(filtered_arr)

        # extract the number of state, which has been flipped to dark state
        # (True) started in the bright state (=False)
        num_flip_to_dark = len(np.where(next_filtered_bin_arr == True)[0])

        # flip probability:
        # In the array filtered_bin_arr all states are in bright state meaning
        # that if you would perform for
        #   filtered_bin_arr = bin_trace[index_arr]
        # the mean value with filtered_bin_arr.mean() then you should get 0.0
        # since every entry in that array is False. By looking at the next index
        # it might be that some entries turn to True, i.e. a flip from bright to
        # dark occurred. Then you get a different mean value, which would
        # indicate how many states are flipped from bright (False) to dark (True).
        # If all the next states would be dark (True), then you would perform a
        # perfect flip into the dark state, meaning a flip probability of 1.
        flip_prob = next_filtered_bin_arr.mean()

        # put all the calculated parameters in a proper dict:
        param = OrderedDict()
        param['num_dark_state'] = num_dark_state  # Number of Dark States
        param['num_bright_state'] = num_bright_state  # Number of Bright States
        param[
            'num_flip_to_dark'] = num_flip_to_dark  # Number of flips from bright to dark
        param['fidelity'] = fidelity  # Fidelity of Double Poissonian Fit
        param['threshold'] = threshold_fit  # Threshold

        # add the fit parameter to the output parameter:
        param.update(fit_param)

        return flip_prob, param

    def analyze_flip_prob_postselect(self):
        """ Post select the data trace so that the flip probability is only
            calculated from a jump from below a threshold value to an value
            above threshold.

        @return:
        """
        pass

    def get_fit_functions(self):
        """ Return all fit functions, which are currently implemented for that module.

        @return list: with string entries denoting the name of the fit.
        """
        return [
            'No Fit', 'Gaussian', 'Double Gaussian', 'Poisson',
            'Double Poisson'
        ]

    def do_fit(self, fit_function=None):
        """ Makes the a fit of the current fit function.

        @param str fit_function: name of the chosen fit function.

        @return tuple(x_val, y_val, fit_results):
                    x_val: a 1D numpy array containing the x values
                    y_val: a 1D numpy array containing the y values
                    fit_results: a string containing the information of the fit
                                 results.

        You can obtain with get_fit_methods all implemented fit methods.
        """

        if self.hist_data is None:
            hist_fit_x = []
            hist_fit_y = []
            param_dict = OrderedDict()
            fit_result = None
            return hist_fit_x, hist_fit_y, param_dict, fit_result
        else:

            # self.log.debug((self.calculate_threshold(self.hist_data)))

            # shift x axis to middle of bin
            axis = self.hist_data[0][:-1] + (self.hist_data[0][1] -
                                             self.hist_data[0][0]) / 2.
            data = self.hist_data[1]

            if fit_function == 'No Fit':
                hist_fit_x, hist_fit_y, fit_param_dict, fit_result = self.do_no_fit(
                )
                return hist_fit_x, hist_fit_y, fit_param_dict, fit_result
            elif fit_function == 'Gaussian':
                hist_fit_x, hist_fit_y, fit_param_dict, fit_result = self.do_gaussian_fit(
                    axis, data)
                return hist_fit_x, hist_fit_y, fit_param_dict, fit_result
            elif fit_function == 'Double Gaussian':
                hist_fit_x, hist_fit_y, fit_param_dict, fit_result = self.do_doublegaussian_fit(
                    axis, data)
                return hist_fit_x, hist_fit_y, fit_param_dict, fit_result
            elif fit_function == 'Poisson':
                hist_fit_x, hist_fit_y, fit_param_dict, fit_result = self.do_possonian_fit(
                    axis, data)
                return hist_fit_x, hist_fit_y, fit_param_dict, fit_result
            elif fit_function == 'Double Poisson':
                hist_fit_x, hist_fit_y, fit_param_dict, fit_result = self.do_doublepossonian_fit(
                    axis, data)
                return hist_fit_x, hist_fit_y, fit_param_dict, fit_result

    def do_no_fit(self):
        """ Perform no fit, basically return an empty array.

        @return tuple(x_val, y_val, fit_results):
                    x_val: a 1D numpy array containing the x values
                    y_val: a 1D numpy array containing the y values
                    fit_results: a string containing the information of the fit
                                 results.
        """
        hist_fit_x = []
        hist_fit_y = []
        param_dict = {}
        fit_result = None
        return hist_fit_x, hist_fit_y, param_dict, fit_result

    def analyze_lifetime(self,
                         trace,
                         dt,
                         method='postselect',
                         distr='gaussian_normalized',
                         state='|-1>',
                         num_bins=50):
        """ Perform an lifetime analysis of a 1D time trace. The analysis is
            based on the method provided ( for now only post select is implemented ).

        @param numpy array trace: 1 D array
        @param string method: The method used for the lifetime analysis
        @param string distr: distribution used for analysis
        @param string state: State that the mw was applied to
        @param int num_bins: number of bins used in the histogram to determine the threshold before digitalisation
                             of data
        @return: dictionary containing the lifetimes of the different states |0>, |1>, |-1> in the case of the HMM method
                 For the postselect method only lifetime for bright and darkstate is returned, keys are 'bright_state' and
                 'dark_state'
        """
        lifetime_dict = {}

        if method == 'postselect':
            if distr == 'gaussian_normalized':
                hist_y_val, hist_x_val = np.histogram(trace, num_bins)
                hist_data = np.array([hist_x_val, hist_y_val])
                threshold_fit, fidelity, param_dict = self.calculate_threshold(
                    hist_data=hist_data, distr='gaussian_normalized')
                threshold = threshold_fit

            # helper functions to get and analyze the timetrace
            def analog_digitial_converter(cut_off, data):
                digital_trace = []
                for data_point in data:
                    if data_point >= cut_off:
                        digital_trace.append(1)
                    else:
                        digital_trace.append(0)
                return digital_trace

            def time_in_high_low(digital_trace, dt):
                """
                What I need this function to do is to get all consecutive {1, ... , n} 1s or 0s and add
                them up and put into a list to later make a histogram from them.
                """
                occurances = []
                index = 0
                index2 = 0

                while (index < len(digital_trace)):
                    occurances.append(0)
                    # start following the consecutive 1s
                    while (digital_trace[index] == 1):
                        occurances[index2] += 1
                        if index == (len(digital_trace) - 1):
                            occurances = np.array(occurances)
                            return occurances * dt
                        else:
                            index += 1
                    if digital_trace[index - 1] == 1:
                        index2 += 1
                        occurances.append(0)
                    # start following the consecutive 0s
                    while (digital_trace[index] == 0):
                        occurances[index2] -= 1
                        if index == (len(digital_trace) - 1):
                            occurances = np.array(occurances)
                            return occurances * dt
                        else:
                            index += 1
                    index2 += 1

            digital_trace = analog_digitial_converter(threshold, trace)
            time_array = time_in_high_low(digital_trace, dt)

            # now we need to make a histogram as well as a fit
            # what would be a good estimate for the number of bins

            # longest = np.max(np.array(occurances))
            # number of steps in between, rather not use that for now
            # est_bins = np.int(longest/dt)

            time_array_high = np.array(
                [ii for ii in filter(lambda x: x > 0, time_array)])
            time_array_low = np.array(
                [ii for ii in filter(lambda x: x < 0, time_array)])

            # get lifetime of bright state
            time_hist_high = np.histogram(time_array_high, bins=num_bins)
            vals = [
                i for i in filter(lambda x: x[1] > 0,
                                  enumerate(time_hist_high[0][0:num_bins]))
            ]

            indices = np.array([val[0] for val in vals])
            indices = np.array([np.int(indice) for indice in indices])
            self.log.debug('threshold {0}'.format(threshold))
            self.log.debug('time_array:{0}'.format(time_array))
            self.log.debug('time_array_high:{0}'.format(time_array_high))
            self.log.debug('time_hist_high:{0}'.format(time_hist_high))
            self.log.debug('indices: {0}'.format(indices))
            self.debug_lifetime_x = time_hist_high[1][indices]
            self.debug_lifetime_y = time_hist_high[0][indices]
            para = dict()
            para['offset'] = {"value": 0.0, "vary": False}
            result = self._fit_logic.make_decayexponential_fit(
                time_hist_high[1][indices],
                time_hist_high[0][indices],
                self._fit_logic.estimate_decayexponential,
                add_params=para)
            bright_liftime = result.params['lifetime']
            # for debug purposes give also the results back of the fits for now
            lifetime_dict['result_bright'] = result
            # also give back the data used for the fit
            lifetime_dict['bright_raw'] = np.array(
                [time_hist_high[1][indices], time_hist_high[0][indices]])

            # get lifetime of dark state
            time_hist_low = np.histogram(time_array_low, bins=num_bins)
            vals = [
                i for i in filter(lambda x: x[1] > 0,
                                  enumerate(time_hist_low[0][0:num_bins]))
            ]
            indices = np.array([val[0] for val in vals])
            indices = np.array([np.int(indice) for indice in indices])
            values = np.array([val[1] for val in vals])
            # positive axis
            mirror_axis = -time_hist_low[1][indices]
            result = self._fit_logic.make_decayexponential_fit(
                mirror_axis,
                values,
                self._fit_logic.estimate_decayexponential,
                add_params=para)
            dark_liftime = result.params['lifetime']
            lifetime_dict['result_dark'] = result

            lifetime_dict['bright_state'] = bright_liftime.value
            lifetime_dict['dark_state'] = dark_liftime.value
            # also give back the data used for the fit
            lifetime_dict['dark_raw'] = np.array([mirror_axis, values])

        return lifetime_dict

    def do_gaussian_fit(self, axis, data):
        """ Perform a gaussian fit.

        @param axis:
        @param data:
        @return:
        """

        model, params = self._fit_logic.make_gaussian_model()
        if len(axis) < len(params):
            self.log.warning('Fit could not be performed because number of '
                             'parameters is smaller than data points.')
            return self.do_no_fit()

        else:

            parameters_to_substitute = dict()
            update_dict = dict()

            #TODO: move this to "gated counter" estimator in fitlogic
            #      make the filter an extra function shared and usable for other
            #      functions
            gauss = gaussian(10, 10)
            data_smooth = filters.convolve1d(data,
                                             gauss / gauss.sum(),
                                             mode='mirror')

            # integral of data corresponds to sqrt(2) * Amplitude * Sigma
            function = InterpolatedUnivariateSpline(axis, data_smooth, k=1)
            Integral = function.integral(axis[0], axis[-1])
            amp = data_smooth.max()
            sigma = Integral / amp / np.sqrt(2 * np.pi)
            amplitude = amp * sigma * np.sqrt(2 * np.pi)

            update_dict['offset'] = {
                'min': 0,
                'max': data.max(),
                'value': 0,
                'vary': False
            }
            update_dict['center'] = {
                'min': axis.min(),
                'max': axis.max(),
                'value': axis[np.argmax(data)]
            }
            update_dict['sigma'] = {
                'min': -np.inf,
                'max': np.inf,
                'value': sigma
            }
            update_dict['amplitude'] = {
                'min': 0,
                'max': np.inf,
                'value': amplitude
            }

            result = self._fit_logic.make_gaussian_fit(
                x_axis=axis,
                data=data,
                estimator=self._fit_logic.estimate_gaussian_peak,
                units=None,  # TODO
                add_params=update_dict)
            # 1000 points in x axis for smooth fit data
            hist_fit_x = np.linspace(axis[0], axis[-1], 1000)
            hist_fit_y = model.eval(x=hist_fit_x, params=result.params)

            param_dict = OrderedDict()

            # create the proper param_dict with the values:
            param_dict['sigma_0'] = {
                'value': result.params['sigma'].value,
                'error': result.params['sigma'].stderr,
                'unit': 'Occurrences'
            }

            param_dict['FWHM'] = {
                'value': result.params['fwhm'].value,
                'error': result.params['fwhm'].stderr,
                'unit': 'Counts/s'
            }

            param_dict['Center'] = {
                'value': result.params['center'].value,
                'error': result.params['center'].stderr,
                'unit': 'Counts/s'
            }

            param_dict['Amplitude'] = {
                'value': result.params['amplitude'].value,
                'error': result.params['amplitude'].stderr,
                'unit': 'Occurrences'
            }

            param_dict['chi_sqr'] = {'value': result.chisqr, 'unit': ''}

            return hist_fit_x, hist_fit_y, param_dict, result

    def do_doublegaussian_fit(self, axis, data):
        model, params = self._fit_logic.make_gaussiandouble_model()

        if len(axis) < len(params):
            self.log.warning('Fit could not be performed because number of '
                             'parameters is smaller than data points')
            return self.do_no_fit()

        else:
            result = self._fit_logic.make_gaussiandouble_fit(
                axis, data, self._fit_logic.estimate_gaussiandouble_peak)

            # 1000 points in x axis for smooth fit data
            hist_fit_x = np.linspace(axis[0], axis[-1], 1000)
            hist_fit_y = model.eval(x=hist_fit_x, params=result.params)

            # this dict will be passed to the formatting method
            param_dict = OrderedDict()

            # create the proper param_dict with the values:
            param_dict['sigma_0'] = {
                'value': result.params['g0_sigma'].value,
                'error': result.params['g0_sigma'].stderr,
                'unit': 'Counts/s'
            }

            param_dict['FWHM_0'] = {
                'value': result.params['g0_fwhm'].value,
                'error': result.params['g0_fwhm'].stderr,
                'unit': 'Counts/s'
            }

            param_dict['Center_0'] = {
                'value': result.params['g0_center'].value,
                'error': result.params['g0_center'].stderr,
                'unit': 'Counts/s'
            }

            param_dict['Amplitude_0'] = {
                'value': result.params['g0_amplitude'].value,
                'error': result.params['g0_amplitude'].stderr,
                'unit': 'Occurrences'
            }

            param_dict['sigma_1'] = {
                'value': result.params['g1_sigma'].value,
                'error': result.params['g1_sigma'].stderr,
                'unit': 'Counts/s'
            }

            param_dict['FWHM_1'] = {
                'value': result.params['g1_fwhm'].value,
                'error': result.params['g1_fwhm'].stderr,
                'unit': 'Counts/s'
            }

            param_dict['Center_1'] = {
                'value': result.params['g1_center'].value,
                'error': result.params['g1_center'].stderr,
                'unit': 'Counts/s'
            }

            param_dict['Amplitude_1'] = {
                'value': result.params['g1_amplitude'].value,
                'error': result.params['g1_amplitude'].stderr,
                'unit': 'Occurrences'
            }

            param_dict['chi_sqr'] = {'value': result.chisqr, 'unit': ''}

            return hist_fit_x, hist_fit_y, param_dict, result

    def do_doublepossonian_fit(self, axis, data):
        model, params = self._fit_logic.make_multiplepoissonian_model(
            no_of_functions=2)
        if len(axis) < len(params):
            self.log.warning('Fit could not be performed because number of '
                             'parameters is smaller than data points')
            return self.do_no_fit()

        else:
            result = self._fit_logic.make_doublepoissonian_fit(x_axis=axis,
                                                               data=data,
                                                               add_params=None)

            # 1000 points in x axis for smooth fit data
            hist_fit_x = np.linspace(axis[0], axis[-1], 1000)
            hist_fit_y = model.eval(x=hist_fit_x, params=result.params)

            # this dict will be passed to the formatting method
            param_dict = OrderedDict()

            # create the proper param_dict with the values:
            param_dict['lambda_0'] = {
                'value': result.params['p0_mu'].value,
                'error': result.params['p0_mu'].stderr,
                'unit': 'Counts/s'
            }
            param_dict['Amplitude_0'] = {
                'value': result.params['p0_amplitude'].value,
                'error': result.params['p0_amplitude'].stderr,
                'unit': 'Occurrences'
            }
            param_dict['lambda_1'] = {
                'value': result.params['p1_mu'].value,
                'error': result.params['p1_mu'].stderr,
                'unit': 'Counts/s'
            }
            param_dict['Amplitude_1'] = {
                'value': result.params['p1_amplitude'].value,
                'error': result.params['p1_amplitude'].stderr,
                'unit': 'Occurrences'
            }

            param_dict['chi_sqr'] = {'value': result.chisqr, 'unit': ''}
            # removed last return value <<result>> here, because function calculate_threshold only expected
            # three return values
            return hist_fit_x, hist_fit_y, param_dict

    def do_possonian_fit(self, axis, data):
        model, params = self._fit_logic.make_poissonian_model()
        if len(axis) < len(params):
            self.log.error('Fit could not be performed because number of '
                           'parameters is smaller than data points')
            return self.do_no_fit()
        else:
            result = self._fit_logic.make_poissonian_fit(x_axis=axis,
                                                         data=data,
                                                         add_params=None)

            # 1000 points in x axis for smooth fit data
            hist_fit_x = np.linspace(axis[0], axis[-1], 1000)
            hist_fit_y = model.eval(x=hist_fit_x, params=result.params)

            # this dict will be passed to the formatting method
            param_dict = OrderedDict()

            # create the proper param_dict with the values:
            param_dict['lambda'] = {
                'value': result.params['mu'].value,
                'error': result.params['mu'].stderr,
                'unit': 'Counts/s'
            }

            param_dict['chi_sqr'] = {'value': result.chisqr, 'unit': ''}

            return hist_fit_x, hist_fit_y, param_dict, result

    def get_poissonian(self, x_val, mu, amplitude):
        """ Calculate, bases on the passed values a poisson distribution.

        @param float mu: expected value of poisson distribution
        @param float amplitude: Amplitude to which is multiplied on distribution
        @param int,float or np.array x_val: x values for poisson distribution,
                                            also works for numbers (int or float)
        @return np.array: a 1D array with the calculated poisson distribution,
                          corresponding to given parameters/ x values

        Calculate a Poisson distribution according to:
            P(k) =  mu^k * exp(-mu) / k!
        """

        model, params = self._fit_logic.make_poissonian_model()

        return model.eval(x=np.array(x_val),
                          poissonian_mu=mu,
                          poissonian_amplitude=amplitude)

    def guess_threshold(self, hist_val=None, trace=None, max_ratio_value=0.1):
        """ Assume a distribution between two values and try to guess the threshold.

        @param np.array hist_val: 1D array whitch represent the y values of a
                                    histogram of a trace. Optional, if None
                                    is passed here, the passed trace will be
                                    used for calculations.
        @param np.array trace: optional, 1D array containing the y values of a
                               meausured counter trace. If None is passed to
                               hist_y_val then the threshold will be calculated
                               from the trace.
        @param float max_ration_value: the ratio how strong the lower y values
                                       will be cut off. For max_ratio_value=0.1
                                       all the data which are 10% or less in
                                       amptitude compared to the maximal value
                                       are neglected.

        The guess procedure tries to find all values, which are
        max_ratio_value * maximum value of the histogram of the trace and
        selects those by indices. Then taking the first an the last might and
        assuming that the threshold is in the middle, gives a first estimate
        of the threshold value.

        FIXME: That guessing procedure can be improved!

        @return float: a guessed threshold
        """

        if hist_val is None and trace is not None:
            hist_val = self.calculate_histogram(trace)

        hist_val = np.array(hist_val)  # just to be sure to have a np.array
        indices_arr = np.where(
            hist_val[1] > hist_val[1].max() * max_ratio_value)[0]
        guessed_threshold = hist_val[0][int(
            (indices_arr[-1] + indices_arr[0]) / 2)]

        return guessed_threshold

    def calculate_threshold(self, hist_data=None, distr='poissonian'):
        """ Calculate the threshold by minimizing its overlap with the poissonian fits.

        @param np.array hist_data: 2D array whitch represent the x and y values
                                   of a histogram of a trace.
               string distr: tells the function on what distribution it should calculate
                             the threshold ( Added because it might happen that one normalizes data
                             between (-1,1) and then a poissonian distribution won't work anymore.

        @return tuple(float, float):
                    threshold: the calculated threshold between two overlapping
                               poissonian distributed peaks.
                    fidelity: the measure how good the two peaks are resolved
                              according to the calculated threshold

        The calculation of the threshold relies on fitting two poissonian
        distributions to the count histogram and minimize a threshold with
        respect to the overlap area:

        """
        # in any case calculate the hist data
        x_axis = hist_data[0][:-1] + (hist_data[0][1] - hist_data[0][0]) / 2.
        y_data = hist_data[1]
        if distr == 'poissonian':
            # perform the fit

            hist_fit_x, hist_fit_y, param_dict = self.do_doublepossonian_fit(
                x_axis, y_data)

            if param_dict.get('lambda_0') is None:
                self.log.error(
                    'The double poissonian fit does not work! Take at '
                    'least a dummy value, in order not to break the '
                    'routine.')
                amp0 = 1
                amp1 = 1

                param_dict['Amplitude_0'] = {
                    'value': amp0,
                    'unit': 'occurences'
                }
                param_dict['Amplitude_1'] = {
                    'value': amp0,
                    'unit': 'occurences'
                }

                # make them a bit different so that fit works.
                mu0 = hist_data[0][:].mean() - 0.1
                mu1 = hist_data[0][:].mean() + 0.1

                param_dict['lambda_0'] = {'value': mu0, 'unit': 'counts'}
                param_dict['lambda_1'] = {'value': mu1, 'unit': 'counts'}

            else:

                mu0 = param_dict['lambda_0']['value']
                mu1 = param_dict['lambda_1']['value']

                amp0 = param_dict['Amplitude_0']['value']
                amp1 = param_dict['Amplitude_1']['value']

            if mu0 < mu1:
                first_dist = self.get_poissonian(x_val=hist_data[0],
                                                 mu=mu0,
                                                 amplitude=amp0)
                sec_dist = self.get_poissonian(x_val=hist_data[0],
                                               mu=mu1,
                                               amplitude=amp1)
            else:
                first_dist = self.get_poissonian(x_val=hist_data[0],
                                                 mu=mu1,
                                                 amplitude=amp1)
                sec_dist = self.get_poissonian(x_val=hist_data[0],
                                               mu=mu0,
                                               amplitude=amp0)

            # create a two poissonian array, where the second poissonian
            # distribution is add as negative values. Now the transition from
            # positive to negative values will get the threshold:
            difference_poissonian = first_dist - sec_dist

            trans_index = 0
            for i in range(len(difference_poissonian) - 1):
                # go through the combined histogram array and the point which
                # changes the sign. The transition from positive to negative values
                # will get the threshold:
                if difference_poissonian[i] < 0 and difference_poissonian[
                        i + 1] >= 0:
                    trans_index = i
                    break
                elif difference_poissonian[i] > 0 and difference_poissonian[
                        i + 1] <= 0:
                    trans_index = i
                    break

            threshold_fit = hist_data[0][trans_index]

            # Calculate also the readout fidelity, i.e. sum the area under the
            # first peak before the threshold of the first and second distribution
            # and take the ratio of that area. Do the same thing after the threshold
            # (of course with a reversed choice of the distribution). If the overlap
            # in both cases is very small, then the fidelity is good, if the overlap
            # is identical, then fidelity indicates a poor separation of the peaks.

            if mu0 < mu1:
                area0_low = self.get_poissonian(hist_data[0][0:trans_index],
                                                mu0, 1).sum()
                area0_high = self.get_poissonian(hist_data[0][trans_index:],
                                                 mu0, 1).sum()
                area1_low = self.get_poissonian(hist_data[0][0:trans_index],
                                                mu1, 1).sum()
                area1_high = self.get_poissonian(hist_data[0][trans_index:],
                                                 mu1, 1).sum()

                area0_low_amp = self.get_poissonian(
                    hist_data[0][0:trans_index], mu0, amp0).sum()
                area0_high_amp = self.get_poissonian(
                    hist_data[0][trans_index:], mu0, amp0).sum()
                area1_low_amp = self.get_poissonian(
                    hist_data[0][0:trans_index], mu1, amp1).sum()
                area1_high_amp = self.get_poissonian(
                    hist_data[0][trans_index:], mu1, amp1).sum()

            else:
                area1_low = self.get_poissonian(hist_data[0][0:trans_index],
                                                mu0, 1).sum()
                area1_high = self.get_poissonian(hist_data[0][trans_index:],
                                                 mu0, 1).sum()
                area0_low = self.get_poissonian(hist_data[0][0:trans_index],
                                                mu1, 1).sum()
                area0_high = self.get_poissonian(hist_data[0][trans_index:],
                                                 mu1, 1).sum()

                area1_low_amp = self.get_poissonian(
                    hist_data[0][0:trans_index], mu0, amp0).sum()
                area1_high_amp = self.get_poissonian(
                    hist_data[0][trans_index:], mu0, amp0).sum()
                area0_low_amp = self.get_poissonian(
                    hist_data[0][0:trans_index], mu1, amp1).sum()
                area0_high_amp = self.get_poissonian(
                    hist_data[0][trans_index:], mu1, amp1).sum()

            # Now calculate how big is the overlap relative to the sum of the other
            # part of the area, that will give the normalized fidelity:
            fidelity = 1 - (area1_low / area0_low +
                            area0_high / area1_high) / 2

            area0 = self.get_poissonian(hist_data[0][:], mu0, amp0).sum()
            area1 = self.get_poissonian(hist_data[0][:], mu1, amp1).sum()

            # try this new measure for the fidelity
            fidelity2 = 1 - ((area1_low_amp / area1) /
                             (area0_low_amp / area0) +
                             (area0_high_amp / area0) /
                             (area1_high_amp / area1)) / 2

            param_dict['normalized_fidelity'] = fidelity2

            return threshold_fit, fidelity, param_dict

        # this works if your data is normalized to the interval (-1,1)
        if distr == 'gaussian_normalized':
            # first some helper functions
            def two_gaussian_intersect(m1, m2, std1, std2, amp1, amp2):
                """
                function to calculate intersection of two gaussians
                """
                a = 1 / (2 * std1**2) - 1 / (2 * std2**2)
                b = m2 / (std2**2) - m1 / (std1**2)
                c = m1**2 / (2 * std1**2) - m2**2 / (2 * std2**2) - np.log(
                    amp2 / amp1)
                return np.roots([a, b, c])

            def gaussian(counts, amp, stdv, mean):
                return amp * np.exp(
                    -(counts - mean)**2 /
                    (2 * stdv**2)) / (stdv * np.sqrt(2 * np.pi))

            try:
                result = self._fit_logic.make_gaussiandouble_fit(
                    x_axis, y_data,
                    self._fit_logic.estimate_gaussiandouble_peak)
                # calculating the threshold
                # NOTE the threshold is taken as the intersection of the two gaussians, while this should give
                # a good approximation I doubt it is mathematical exact.

                mu0 = result.params['g0_center'].value
                mu1 = result.params['g1_center'].value
                sigma0 = result.params['g0_sigma'].value
                sigma1 = result.params['g1_sigma'].value
                amp0 = result.params['g0_amplitude'].value / (
                    sigma0 * np.sqrt(2 * np.pi))
                amp1 = result.params['g1_amplitude'].value / (
                    sigma1 * np.sqrt(2 * np.pi))
                candidates = two_gaussian_intersect(mu0, mu1, sigma0, sigma1,
                                                    amp0, amp1)

                # we want to get the intersection that lies between the two peaks
                if mu0 < mu1:
                    threshold = [
                        i for i in filter(lambda x: (x > mu0) & (x < mu1),
                                          candidates)
                    ]
                else:
                    threshold = [
                        i for i in filter(lambda x: (x < mu0) & (x > mu1),
                                          candidates)
                    ]

                threshold = threshold[0]

                # now we want to get the readout fidelity
                # of the bigger peak ( most likely the two states that aren't driven by the mw pi pulse )
                if mu0 < mu1:
                    gc0 = integrate.quad(
                        lambda counts: gaussian(counts, amp1, sigma1, mu1), -1,
                        1)
                    gp0 = integrate.quad(
                        lambda counts: gaussian(counts, amp1, sigma1, mu1), -1,
                        threshold)
                else:
                    gc0 = integrate.quad(
                        lambda counts: gaussian(counts, amp0, sigma0, mu0), -1,
                        1)
                    gp0 = integrate.quad(
                        lambda counts: gaussian(counts, amp0, sigma0, mu0), -1,
                        threshold)

                # and then the same for the other peak ]

                if mu0 > mu1:
                    gc1 = integrate.quad(
                        lambda counts: gaussian(counts, amp1, sigma1, mu1), -1,
                        1)
                    gp1 = integrate.quad(
                        lambda counts: gaussian(counts, amp1, sigma1, mu1),
                        threshold, 1)
                else:
                    gc1 = integrate.quad(
                        lambda counts: gaussian(counts, amp0, sigma0, mu0), -1,
                        1)
                    gp1 = integrate.quad(
                        lambda counts: gaussian(counts, amp0, sigma0, mu0),
                        threshold, 1)

                param_dict = {}
                fidelity = 1 - (gp0[0] / gc0[0] + gp1[0] / gc1[0]) / 2
                fidelity1 = 1 - (gp0[0] / gc0[0])
                fidelity2 = 1 - gp1[0] / gc1[0]
                threshold_fit = threshold
                # if the fit worked, add also the result to the param_dict, which might be useful for debugging
                param_dict['result'] = result
            except:
                self.log.error('could not fit the data')
                error = True
                fidelity = 0
                threshold_fit = 0
                param_dict = {}
                new_dict = {}
                new_dict['value'] = np.inf
                param_dict['chi_sqr'] = new_dict

            return threshold_fit, fidelity, param_dict

    def calculate_binary_trace(self, trace, threshold):
        """ Calculate for a given threshold all the trace values und output a
            binary array, where
                True = Below or equal Threshold
                False = Above Threshold.

        @param np.array trace: a 1D array containing the y data, e.g. ccunts
        @param float threshold: value to decide whether a point in the trace
                                is below/equal (True) or above threshold (False).

        @return np.array: 1D trace of the length(trace) but now with boolean
                          entries
        """
        return trace <= threshold

    def extract_filtered_values(self, trace, threshold, below=True):
        """ Extract only those values, which are below or equal a certain Threshold.

        @param np.array trace:
        @param float threshold:

        @return tuple(index_array, filtered_array):
                    np.array index_array: 1D integer array containing the
                                          indices of the passed trace array
                                          which are equal or below the threshold
                    np.array filtered_array: the actual values of the trace,
                                             which are equal or below threshold
        """
        if below:
            index_array = np.where(trace <= threshold)[0]
        else:
            index_array = np.where(trace > threshold)[0]
        filtered_array = trace[index_array]
        return index_array, filtered_array
コード例 #19
0
class ConfocalScanner_PI_Swabian_Interfuse(Base, FastCounterInterface,
                                           PulserInterface):
    """This is the Interface class to define the controls for the simple
    microwave hardware.
    """
    _modclass = 'Swabian I/O Interfuse'
    _modtype = 'hardware'

    # connectors
    fitlogic = Connector(interface='FitLogic')
    pulsestreamer = Connector(interface='PulserInterface')
    timetagger = Connector(interface='FastCounterInterface')

    # config options
    _clock_frequency = ConfigOption('clock_frequency', 100, missing='warn')

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

        # Internal parameters

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

        self._fit_logic = self.fitlogic()
        self._output_hw = self.pulsestreamer()
        self._input_hw = self.timetagger()

        self._input_hw.on_activate()
        self._output_hw.on_activate()
        # configure(self, bin_width_s, record_length_s, number_of_gates=0)
        self._input_hw.configure(self._output_hw._channel_detect)

    def on_deactivate(self):

        self._input_hw.on_deactivate()
        self._output_hw.on_deactivate()

# === timetagger / input functionality ===

    def get_constraints(self):
        """ Retrieve the hardware constrains from the Fast counting device.

        @return dict: dict with keys being the constraint names as string and
                      items are the definition for the constaints.
        """

        self._input_hw.get_constraints()

    def configure(self, bin_width_s, record_length_s, number_of_gates=0):
        """ Configuration of the fast counter.

        @param float bin_width_s: Length of a single time bin in the time
                                  trace histogram in seconds.
        @param float record_length_s: Total length of the timetrace/each
                                      single gate in seconds.
        @param int number_of_gates: optional, number of gates in the pulse
                                    sequence. Ignore for not gated counter.

        @return tuple(binwidth_s, record_length_s, number_of_gates):
                    binwidth_s: float the actual set binwidth in seconds
                    gate_length_s: the actual record length in seconds
                    number_of_gates: the number of gated, which are accepted, None if not-gated
        """
        self._input_hw.configure(bin_width_s, record_length_s, number_of_gates)

    def get_status(self):
        """ Receives the current status of the Fast Counter and outputs it as
            return value.

        0 = unconfigured
        1 = idle
        2 = running
        3 = paused
        -1 = error state
        """
        self._input_hw.get_status()

    def start_measure(self):
        """ Start the fast counter. """
        self._input_hw.start_measure()

    def stop_measure(self):
        """ Stop the fast counter. """
        self._input_hw.stop_measure()

    def pause_measure(self):
        """ Pauses the current measurement.

        Fast counter must be initially in the run state to make it pause.
        """
        self._input_hw.pause_measure()

    def continue_measure(self):
        """ Continues the current measurement.

        If fast counter is in pause state, then fast counter will be continued.
        """
        self._input_hw.continue_measure()

    def is_gated(self):
        """ Check the gated counting possibility.

        @return bool: Boolean value indicates if the fast counter is a gated
                      counter (TRUE) or not (FALSE).
        """
        self._input_hw.is_gated()

    def get_binwidth(self):
        """ Returns the width of a single timebin in the timetrace in seconds.

        @return float: current length of a single bin in seconds (seconds/bin)
        """
        self._input_hw.get_binwidth()

    def get_data_trace(self):
        """ Polls the current timetrace data from the fast counter.

        Return value is a numpy array (dtype = int64).
        The binning, specified by calling configure() in forehand, must be
        taken care of in this hardware class. A possible overflow of the
        histogram bins must be caught here and taken care of.
        If the counter is NOT GATED it will return a 1D-numpy-array with
            returnarray[timebin_index]
        If the counter is GATED it will return a 2D-numpy-array with
            returnarray[gate_index, timebin_index]
        """
        self._input_hw.get_data_trace()

# === PulseStreamer / output ===

    def get_constraints(self):
        """
        Retrieve the hardware constrains from the Pulsing device.

        @return constraints object: object with pulser constraints as attributes.
        """
        self._output_hw.get_constraints()

    def pulser_on(self):
        """ Switches the pulsing device on.

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

    def pulser_off(self):
        """ Switches the pulsing device off.

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

    def upload_asset(self, asset_name=None):
        """ Upload an already hardware conform file to the device mass memory.
            Also loads these files into the device workspace if present.
            Does NOT load waveforms/sequences/patterns into channels.

        @param asset_name: string, name of the ensemble/sequence to be uploaded

        @return int: error code (0:OK, -1:error)
        """
        self._output_hw.upload_asset(asset_name)

    def load_asset(self, asset_name, load_dict=None):
        """ Loads a sequence or waveform to the specified channel of the pulsing device.
        For devices that have a workspace (i.e. AWG) this will load the asset from the device
        workspace into the channel.
        For a device without mass memory this will transfer the waveform/sequence/pattern data
        directly to the device so that it is ready to play.

        @param str asset_name: The name of the asset to be loaded

        @param dict load_dict:  a dictionary with keys being one of the available channel numbers
                                and items being the name of the already sampled waveform/sequence
                                files.
                                Examples:   {1: rabi_Ch1, 2: rabi_Ch2}
                                            {1: rabi_Ch2, 2: rabi_Ch1}
                                This parameter is optional. If none is given then the channel
                                association is invoked from the file name, i.e. the appendix
                                (_ch1, _ch2 etc.)

        @return int: error code (0:OK, -1:error)
        """
        self._output_hw.load_asset(asset_name, load_dict)

    def get_loaded_asset(self):
        """ Retrieve the currently loaded asset name of the device.

        @return str: Name of the current asset ready to play. (no filename)
        """
        self._output_hw.get_loaded_asset()

    def clear_all(self):
        """ Clears all loaded waveforms from the pulse generators RAM/workspace.

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

    def get_status(self):
        """ Retrieves the status of the pulsing hardware

        @return (int, dict): tuple with an interger value of the current status and a corresponding
                             dictionary containing status description for all the possible status
                             variables of the pulse generator hardware.
        """
        self._output_hw.get_status()

    def get_sample_rate(self):
        """ Get the sample rate of the pulse generator hardware

        @return float: The current sample rate of the device (in Hz)
        """
        self._output_hw.get_sample_rate()

    def set_sample_rate(self, sample_rate):
        """ Set the sample rate of the pulse generator hardware.

        @param float sample_rate: The sampling rate to be set (in Hz)

        @return float: the sample rate returned from the device (in Hz).
        """
        self._output_hw.set_sample_rate(sample_rate)

    def get_analog_level(self, amplitude=None, offset=None):
        """ Retrieve the analog amplitude and offset of the provided channels.

        @param list amplitude: optional, if the amplitude value (in Volt peak to peak, i.e. the
                               full amplitude) of a specific channel is desired.
        @param list offset: optional, if the offset value (in Volt) of a specific channel is
                            desired.

        @return: (dict, dict): tuple of two dicts, with keys being the channel descriptor string
                               (i.e. 'a_ch1') and items being the values for those channels.
                               Amplitude is always denoted in Volt-peak-to-peak and Offset in volts.
        """
        self._output_hw.get_analog_level(amplitude, offset)

    def set_analog_level(self, amplitude=None, offset=None):
        """ Set amplitude and/or offset value of the provided analog channel(s).

        @param dict amplitude: dictionary, with key being the channel descriptor string
                               (i.e. 'a_ch1', 'a_ch2') and items being the amplitude values
                               (in Volt peak to peak, i.e. the full amplitude) for the desired
                               channel.
        @param dict offset: dictionary, with key being the channel descriptor string
                            (i.e. 'a_ch1', 'a_ch2') and items being the offset values
                            (in absolute volt) for the desired channel.

        @return (dict, dict): tuple of two dicts with the actual set values for amplitude and
                              offset for ALL channels.
        """
        self._output_hw.set_analog_level(amplitude, offset)

    def get_digital_level(self, low=None, high=None):
        """ Retrieve the digital low and high level of the provided/all channels.

        @param list low: optional, if the low value (in Volt) of a specific channel is desired.
        @param list high: optional, if the high value (in Volt) of a specific channel is desired.

        @return: (dict, dict): tuple of two dicts, with keys being the channel descriptor strings
                               (i.e. 'd_ch1', 'd_ch2') and items being the values for those
                               channels. Both low and high value of a channel is denoted in volts.
        """
        self._output_hw.get_digital_level(low, high)

    def set_digital_level(self, low=None, high=None):
        """ Set low and/or high value of the provided digital channel.

        @param dict low: dictionary, with key being the channel descriptor string
                         (i.e. 'd_ch1', 'd_ch2') and items being the low values (in volt) for the
                         desired channel.
        @param dict high: dictionary, with key being the channel descriptor string
                          (i.e. 'd_ch1', 'd_ch2') and items being the high values (in volt) for the
                          desired channel.

        @return (dict, dict): tuple of two dicts where first dict denotes the current low value and
                              the second dict the high value for ALL digital channels.
                              Keys are the channel descriptor strings (i.e. 'd_ch1', 'd_ch2')
        """
        self._output_hw.set_digital_level(low, high)

    def get_active_channels(self, ch=None):
        """ Get the active channels of the pulse generator hardware.

        @param list ch: optional, if specific analog or digital channels are needed to be asked
                        without obtaining all the channels.

        @return dict:  where keys denoting the channel string and items boolean expressions whether
                       channel are active or not.
        """
        self._output_hw.get_active_channels(ch)

    def set_active_channels(self, ch=None):
        """ Set the active channels for the pulse generator hardware.

        @param dict ch: dictionary with keys being the analog or digital string generic names for
                        the channels (i.e. 'd_ch1', 'a_ch2') with items being a boolean value.
                        True: Activate channel, False: Deactivate channel

        @return dict: with the actual set values for ALL active analog and digital channels
        """
        self._output_hw.set_active_channels(ch)

    def get_uploaded_asset_names(self):
        """ Retrieve the names of all uploaded assets on the device.

        @return list: List of all uploaded asset name strings in the current device directory.
                      This is no list of the file names.

        Unused for pulse generators without sequence storage capability (PulseBlaster, FPGA).
        """
        self._output_hw.get_uploaded_asset_names()

    def get_saved_asset_names(self):
        """ Retrieve the names of all sampled and saved assets on the host PC. This is no list of
            the file names.

        @return list: List of all saved asset name strings in the current
                      directory of the host PC.
        """
        self._output_hw.get_saved_asset_names()

    def delete_asset(self, asset_name):
        """ Delete all files associated with an asset with the passed asset_name from the device
            memory (mass storage as well as i.e. awg workspace/channels).

        @param str asset_name: The name of the asset to be deleted
                               Optionally a list of asset names can be passed.

        @return list: a list with strings of the files which were deleted.

        Unused for pulse generators without sequence storage capability (PulseBlaster, FPGA).
        """
        self._output_hw.delete_asset(asset_name)

    def set_asset_dir_on_device(self, dir_path):
        """ Change the directory where the assets are stored on the device.

        @param str dir_path: The target directory

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

        Unused for pulse generators without changeable file structure (PulseBlaster, FPGA).
        """
        self._output_hw.set_asset_dir_on_device(dir_path)

    def get_asset_dir_on_device(self):
        """ Ask for the directory where the hardware conform files are stored on the device.

        @return str: The current file directory

        Unused for pulse generators without changeable file structure (i.e. PulseBlaster, FPGA).
        """
        self._output_hw.get_asset_dir_on_device()

    def get_interleave(self):
        """ Check whether Interleave is ON or OFF in AWG.

        @return bool: True: ON, False: OFF

        Will always return False for pulse generator hardware without interleave.
        """
        self._output_hw.get_interleave()

    def set_interleave(self, state=False):
        """ Turns the interleave of an AWG on or off.

        @param bool state: The state the interleave should be set to
                           (True: ON, False: OFF)

        @return bool: actual interleave status (True: ON, False: OFF)

        Note: After setting the interleave of the device, retrieve the
              interleave again and use that information for further processing.

        Unused for pulse generator hardware other than an AWG.
        """
        self._output_hw.set_interleave(state)

    def tell(self, command):
        """ Sends a command string to the device.

        @param string command: string containing the command

        @return int: error code (0:OK, -1:error)
        """
        self._output_hw.tell(command)

    def ask(self, question):
        """ Asks the device a 'question' and receive and return an answer from it.

        @param string question: string containing the command

        @return string: the answer of the device to the 'question' in a string
        """
        self._output_hw.ask(question)

    def reset(self):
        """ Reset the device.

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

    def has_sequence_mode(self):
        """ Asks the pulse generator whether sequence mode exists.

        @return: bool, True for yes, False for no.
        """
        self._output_hw.has_sequence_mode()
コード例 #20
0
class PoiManagerGui(GUIBase):
    """ This is the GUI Class for PoiManager """

    _modclass = 'PoiManagerGui'
    _modtype = 'gui'

    # declare connectors
    poimanagerlogic1 = Connector(interface='PoiManagerLogic')
    confocallogic1 = Connector(interface='ConfocalLogic')

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

    def on_activate(self):
        """ Initializes the overall GUI, and establishes the connectors.

        This method executes the init methods for each of the GUIs.
        """

        # Connectors
        self._poi_manager_logic = self.poimanagerlogic1()
        self._confocal_logic = self.confocallogic1()
        self.log.debug("POI Manager logic is {0}".format(
            self._poi_manager_logic))
        self.log.debug("Confocal logic is {0}".format(self._confocal_logic))

        # Initializing the GUIs
        self.initMainUI()
        self.initReorientRoiDialogUI()

        # There could be POIs created in the logic already, so update lists and map
        self.populate_poi_list()
        self._redraw_sample_shift()
        self._redraw_poi_markers()

    def mouseMoved(self, event):
        """ Handles any mouse movements inside the image.

        @param event:   Event that signals the new mouse movement.
                        This should be of type QPointF.

        Gets the mouse position, converts it to a position scaled to the image axis
        and than calculates and updated the position to the current POI.
        """

        # converts the absolute mouse position to a position relative to the axis
        mouse_point = self._mw.roi_map_ViewWidget.getPlotItem().getViewBox(
        ).mapSceneToView(event.toPoint())
        #self.log.debug("Mouse at x = {0:0.2e}, y = {1:0.2e}".format(mouse_point.x(), mouse_point.y()))

        # only calculate distance, if a POI is selected
        if self._poi_manager_logic.active_poi is not None:
            cur_poi_pos = self._poi_manager_logic.get_poi_position(
                poikey=self._poi_manager_logic.active_poi.get_key())
            dx = ScaledFloat(mouse_point.x() - cur_poi_pos[0])
            dy = ScaledFloat(mouse_point.y() - cur_poi_pos[1])
            d_total = ScaledFloat(
                np.sqrt((mouse_point.x() - cur_poi_pos[0])**2 +
                        (mouse_point.y() - cur_poi_pos[1])**2))

            self._mw.poi_distance_label.setText(
                '{0:.2r}m ({1:.2r}m, {2:.2r}m)'.format(d_total, dx, dy))

    def initMainUI(self):
        """ Definition, configuration and initialisation of the POI Manager GUI.
        This init connects all the graphic modules, which were created in the
        *.ui file and configures the event handling between the modules.
        """

        # Use the inherited class 'Ui_PoiManagerGuiTemplate' to create now the
        # GUI element:
        self._mw = PoiManagerMainWindow()

        #####################
        # Configuring the dock widgets
        #####################

        # All our gui elements are dockable, and so there should be no "central" widget.
        self._mw.centralwidget.hide()
        self._mw.setDockNestingEnabled(True)
        #####################
        # Setting up display of ROI map xy image
        #####################

        # Get the image for the display from the logic:
        self.roi_xy_image_data = self._poi_manager_logic.roi_map_data[:, :, 3]

        # Load the image in the display:
        self.roi_map_image = pg.ImageItem(image=self.roi_xy_image_data,
                                          axisOrder='row-major')
        self.roi_map_image.setRect(
            QtCore.QRectF(
                self._confocal_logic.image_x_range[0],
                self._confocal_logic.image_y_range[0],
                self._confocal_logic.image_x_range[1] -
                self._confocal_logic.image_x_range[0],
                self._confocal_logic.image_y_range[1] -
                self._confocal_logic.image_y_range[0]))

        # Add the display item to the roi map ViewWidget defined in the UI file
        self._mw.roi_map_ViewWidget.addItem(self.roi_map_image)
        self._mw.roi_map_ViewWidget.setLabel('bottom', 'X position', units='m')
        self._mw.roi_map_ViewWidget.setLabel('left', 'Y position', units='m')

        # Set to fixed 1.0 aspect ratio, since the metaphor is a "map" of the sample
        self._mw.roi_map_ViewWidget.setAspectLocked(lock=True, ratio=1.0)

        # Get the colorscales and set LUT
        my_colors = ColorScaleInferno()

        self.roi_map_image.setLookupTable(my_colors.lut)

        # Add color bar:
        self.roi_cb = ColorBar(my_colors.cmap_normed, 100, 0, 100000)

        self._mw.roi_cb_ViewWidget.addItem(self.roi_cb)
        self._mw.roi_cb_ViewWidget.hideAxis('bottom')
        self._mw.roi_cb_ViewWidget.setLabel('left',
                                            'Fluorescence',
                                            units='c/s')
        self._mw.roi_cb_ViewWidget.setMouseEnabled(x=False, y=False)

        #####################
        # Setting up display of sample shift plot
        #####################

        # Load image in the display
        self.x_shift_plot = pg.PlotDataItem([0], [0],
                                            pen=pg.mkPen(
                                                palette.c1,
                                                style=QtCore.Qt.DotLine),
                                            symbol='o',
                                            symbolPen=palette.c1,
                                            symbolBrush=palette.c1,
                                            symbolSize=5,
                                            name='x')
        self.y_shift_plot = pg.PlotDataItem([0], [0],
                                            pen=pg.mkPen(
                                                palette.c2,
                                                style=QtCore.Qt.DotLine),
                                            symbol='s',
                                            symbolPen=palette.c2,
                                            symbolBrush=palette.c2,
                                            symbolSize=5,
                                            name='y')
        self.z_shift_plot = pg.PlotDataItem([0], [0],
                                            pen=pg.mkPen(
                                                palette.c3,
                                                style=QtCore.Qt.DotLine),
                                            symbol='t',
                                            symbolPen=palette.c3,
                                            symbolBrush=palette.c3,
                                            symbolSize=5,
                                            name='z')

        self._mw.sample_shift_ViewWidget.addLegend()

        # Add the plot to the ViewWidget defined in the UI file
        self._mw.sample_shift_ViewWidget.addItem(self.x_shift_plot)
        self._mw.sample_shift_ViewWidget.addItem(self.y_shift_plot)
        self._mw.sample_shift_ViewWidget.addItem(self.z_shift_plot)

        # Label axes
        self._mw.sample_shift_ViewWidget.setLabel('bottom', 'Time', units='s')
        self._mw.sample_shift_ViewWidget.setLabel('left',
                                                  'Sample shift',
                                                  units='m')

        #####################
        # Connect signals
        #####################

        # Distance Measurement:
        # Introducing a SignalProxy will limit the rate of signals that get fired.
        # Otherwise we will run into a heap of unhandled function calls.
        proxy = pg.SignalProxy(self.roi_map_image.scene().sigMouseMoved,
                               rateLimit=60,
                               slot=self.mouseMoved)
        # Connecting a Mouse Signal to trace to mouse movement function.
        self.roi_map_image.scene().sigMouseMoved.connect(self.mouseMoved)

        # Toolbar actions
        self._mw.new_roi_Action.triggered.connect(self.make_new_roi)
        self._mw.save_roi_Action.triggered.connect(self.save_roi)
        self._mw.load_roi_Action.triggered.connect(self.load_roi)
        self._mw.reorient_roi_Action.triggered.connect(
            self.open_reorient_roi_dialog)
        self._mw.autofind_pois_Action.triggered.connect(
            self.do_autofind_poi_procedure)
        self._mw.optimize_roi_Action.triggered.connect(self.optimize_roi)

        self._mw.new_poi_Action.triggered.connect(self.set_new_poi)
        self._mw.goto_poi_Action.triggered.connect(self.goto_poi)
        self._mw.refind_poi_Action.triggered.connect(self.update_poi_pos)
        self._mw.track_poi_Action.triggered.connect(self.toggle_tracking)

        # Interface controls
        self._mw.get_confocal_image_PushButton.clicked.connect(
            self.get_confocal_image)
        self._mw.set_poi_PushButton.clicked.connect(self.set_new_poi)
        self._mw.delete_last_pos_Button.clicked.connect(self.delete_last_point)
        self._mw.manual_update_poi_PushButton.clicked.connect(
            self.manual_update_poi)
        self._mw.move_poi_PushButton.clicked.connect(self.move_poi)
        self._mw.poi_name_LineEdit.returnPressed.connect(self.change_poi_name)
        self._mw.roi_name_LineEdit.editingFinished.connect(self.set_roi_name)
        self._mw.delete_poi_PushButton.clicked.connect(self.delete_poi)

        self._mw.goto_poi_after_update_checkBox.toggled.connect(
            self.toggle_follow)

        # This needs to be activated so that it only listens to user input, and ignores
        # algorithmic index changes
        self._mw.active_poi_ComboBox.activated.connect(
            self.handle_active_poi_ComboBox_index_change)
        self._mw.refind_method_ComboBox.currentIndexChanged.connect(
            self.change_refind_method)

        # Connect the buttons and inputs for the colorbar
        self._mw.roi_cb_centiles_RadioButton.toggled.connect(
            self.refresh_roi_colorscale)
        self._mw.roi_cb_manual_RadioButton.toggled.connect(
            self.refresh_roi_colorscale)
        self._mw.roi_cb_min_SpinBox.valueChanged.connect(
            self.shortcut_to_roi_cb_manual)
        self._mw.roi_cb_max_SpinBox.valueChanged.connect(
            self.shortcut_to_roi_cb_manual)
        self._mw.roi_cb_low_percentile_DoubleSpinBox.valueChanged.connect(
            self.shortcut_to_roi_cb_centiles)
        self._mw.roi_cb_high_percentile_DoubleSpinBox.valueChanged.connect(
            self.shortcut_to_roi_cb_centiles)

        self._mw.display_shift_vs_duration_RadioButton.toggled.connect(
            self._redraw_sample_shift)
        self._mw.display_shift_vs_clocktime_RadioButton.toggled.connect(
            self._redraw_sample_shift)

        self._markers = dict()

        # Signal at end of refocus
        self._poi_manager_logic.signal_timer_updated.connect(
            self._update_timer, QtCore.Qt.QueuedConnection)
        self._poi_manager_logic.signal_poi_updated.connect(
            self._redraw_sample_shift, QtCore.Qt.QueuedConnection)
        self._poi_manager_logic.signal_poi_updated.connect(
            self.populate_poi_list, QtCore.Qt.QueuedConnection)
        self._poi_manager_logic.signal_poi_updated.connect(
            self._redraw_poi_markers, QtCore.Qt.QueuedConnection)
        self._poi_manager_logic.signal_poi_deleted.connect(
            self._remove_poi_marker)
        self._poi_manager_logic.signal_confocal_image_updated.connect(
            self._redraw_roi_image)

        self._poi_manager_logic.signal_periodic_opt_duration_changed.connect(
            self._track_period_changed)
        self._poi_manager_logic.signal_periodic_opt_started.connect(
            self._tracking_started)
        self._poi_manager_logic.signal_periodic_opt_stopped.connect(
            self._tracking_stopped)

        # Connect track period after setting the GUI value from the logic
        initial_period = self._poi_manager_logic.timer_duration
        self._mw.track_period_SpinBox.setValue(initial_period)
        self._mw.time_till_next_update_ProgressBar.setMaximum(initial_period)
        self._mw.time_till_next_update_ProgressBar.setValue(initial_period)
        self._mw.track_period_SpinBox.valueChanged.connect(
            self.set_track_period)

        # Redraw the sample_shift axes if the range changes
        self._mw.sample_shift_ViewWidget.plotItem.sigRangeChanged.connect(
            self._redraw_sample_shift)

        self._mw.show()

    def initReorientRoiDialogUI(self):
        """ Definition, configuration and initialization fo the Reorient ROI Dialog GUI.

        This init connects all the graphic modules which were created in the
        *.ui file and configures event handling.
        """

        # Create the Reorient ROI Dialog window
        self._rrd = ReorientRoiDialog()

        # Connect the QDialog buttons to methods in the GUI
        self._rrd.accepted.connect(self.do_roi_reorientation)
        self._rrd.rejected.connect(self.reset_reorientation_dialog)

        # Connect the at_crosshair buttons
        self._rrd.ref_a_at_crosshair_PushButton.clicked.connect(
            self.ref_a_at_crosshair)
        self._rrd.ref_b_at_crosshair_PushButton.clicked.connect(
            self.ref_b_at_crosshair)
        self._rrd.ref_c_at_crosshair_PushButton.clicked.connect(
            self.ref_c_at_crosshair)

        # Connect input value changes to update the sanity-check values
        self._rrd.ref_a_poi_ComboBox.activated.connect(
            self.reorientation_sanity_check)
        self._rrd.ref_b_poi_ComboBox.activated.connect(
            self.reorientation_sanity_check)
        self._rrd.ref_c_poi_ComboBox.activated.connect(
            self.reorientation_sanity_check)
        self._rrd.ref_a_x_pos_DoubleSpinBox.valueChanged.connect(
            self.reorientation_sanity_check)
        self._rrd.ref_a_y_pos_DoubleSpinBox.valueChanged.connect(
            self.reorientation_sanity_check)
        self._rrd.ref_a_z_pos_DoubleSpinBox.valueChanged.connect(
            self.reorientation_sanity_check)
        self._rrd.ref_b_x_pos_DoubleSpinBox.valueChanged.connect(
            self.reorientation_sanity_check)
        self._rrd.ref_b_y_pos_DoubleSpinBox.valueChanged.connect(
            self.reorientation_sanity_check)
        self._rrd.ref_b_z_pos_DoubleSpinBox.valueChanged.connect(
            self.reorientation_sanity_check)
        self._rrd.ref_c_x_pos_DoubleSpinBox.valueChanged.connect(
            self.reorientation_sanity_check)
        self._rrd.ref_c_y_pos_DoubleSpinBox.valueChanged.connect(
            self.reorientation_sanity_check)
        self._rrd.ref_c_z_pos_DoubleSpinBox.valueChanged.connect(
            self.reorientation_sanity_check)

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

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

    def get_confocal_image(self):
        """ Update the roi_map_data in poi manager logic, and use this updated
            data to redraw an image of the ROI.
        """

        # Make poi manager logic get the confocal data
        self._poi_manager_logic.get_confocal_image_data()

    def _redraw_roi_image(self):

        # the image data is the fluorescence part
        self.roi_xy_image_data = self._poi_manager_logic.roi_map_data[:, :, 3]

        # Also get the x and y range limits and hold them locally
        self.roi_map_xmin = np.min(self._poi_manager_logic.roi_map_data[:, :,
                                                                        0])
        self.roi_map_xmax = np.max(self._poi_manager_logic.roi_map_data[:, :,
                                                                        0])
        self.roi_map_ymin = np.min(self._poi_manager_logic.roi_map_data[:, :,
                                                                        1])
        self.roi_map_ymax = np.max(self._poi_manager_logic.roi_map_data[:, :,
                                                                        1])

        self.roi_map_image.getViewBox().enableAutoRange()
        self.roi_map_image.setRect(
            QtCore.QRectF(self.roi_map_xmin, self.roi_map_ymin,
                          self.roi_map_xmax - self.roi_map_xmin,
                          self.roi_map_ymax - self.roi_map_ymin))
        self.roi_map_image.setImage(image=self.roi_xy_image_data,
                                    autoLevels=True)

    def shortcut_to_roi_cb_manual(self):
        self._mw.roi_cb_manual_RadioButton.setChecked(True)
        self.refresh_roi_colorscale()

    def shortcut_to_roi_cb_centiles(self):
        self._mw.roi_cb_centiles_RadioButton.setChecked(True)
        self.refresh_roi_colorscale()

    def refresh_roi_colorscale(self):
        """ Adjust the colorbar in the ROI xy image, and update the image with the
        new color scale.

        Calls the refresh method from colorbar, which takes either the lowest
        and higherst value in the image or predefined ranges. Note that you can
        invert the colorbar if the lower border is bigger then the higher one.
        """

        cb_min, cb_max = self.determine_cb_range()

        self.roi_map_image.setImage(image=self.roi_xy_image_data,
                                    levels=(cb_min, cb_max))

        self.roi_cb.refresh_colorbar(cb_min, cb_max)
        self._mw.roi_cb_ViewWidget.update()

    def determine_cb_range(self):
        """ Process UI input to determine color bar range"""

        # If "Centiles" is checked, adjust colour scaling automatically to centiles.
        # Otherwise, take user-defined values.
        if self._mw.roi_cb_centiles_RadioButton.isChecked():
            low_centile = self._mw.roi_cb_low_percentile_DoubleSpinBox.value()
            high_centile = self._mw.roi_cb_high_percentile_DoubleSpinBox.value(
            )

            cb_min = np.percentile(self.roi_xy_image_data, low_centile)
            cb_max = np.percentile(self.roi_xy_image_data, high_centile)

        else:
            cb_min = self._mw.roi_cb_min_SpinBox.value()
            cb_max = self._mw.roi_cb_max_SpinBox.value()

        return cb_min, cb_max

    def set_new_poi(self):
        """ This method sets a new poi from the current crosshair position."""
        key = self._poi_manager_logic.add_poi()

    def delete_last_point(self):
        """ Delete the last track position of a chosen poi. """
        if self._poi_manager_logic.active_poi is None:
            self.log.warning("No POI selected. No datapoint can be deleted")
        else:
            self._poi_manager_logic.delete_last_position(
                poikey=self._poi_manager_logic.active_poi.get_key())

    def delete_poi(self):
        """ Delete the active poi from the list of managed points. """
        if self._poi_manager_logic.active_poi is None:
            self.log.warning("No POI selected.")
        else:
            key = self._poi_manager_logic.active_poi.get_key()

            # todo: this needs to handle the case where the logic deletes a POI.

            self._poi_manager_logic.delete_poi(poikey=key)

    def _remove_poi_marker(self, poikey):
        """ Remove the POI marker for a POI that was deleted.
        """
        self._markers[poikey].delete_from_viewwidget()
        del self._markers[poikey]

    def manual_update_poi(self):
        """ Manually adds a point to the trace of a given poi without refocussing, and uses that information to update sample position.
        """
        if self._poi_manager_logic.active_poi is None:
            self.log.warning("No POI selected.")
        else:
            self._poi_manager_logic.set_new_position(
                poikey=self._poi_manager_logic.active_poi.get_key())

    def move_poi(self):
        """Manually move a POI to a new location in the sample map, but WITHOUT changing the sample position.  This moves a POI relative to all the others.
        """
        if self._poi_manager_logic.active_poi is None:
            self.log.warning("No POI selected.")
        else:
            self._poi_manager_logic.move_coords(
                poikey=self._poi_manager_logic.active_poi.get_key())

    def toggle_tracking(self):
        if self._poi_manager_logic.active_poi is None:
            self.log.warning("No POI selected.")
        else:
            if self._poi_manager_logic.timer is None:
                self._poi_manager_logic.start_periodic_refocus(
                    poikey=self._poi_manager_logic.active_poi.get_key())

            else:
                self._poi_manager_logic.stop_periodic_refocus()

    def _tracking_started(self):
        self._mw.track_poi_Action.setChecked(True)

    def _tracking_stopped(self):
        self._mw.track_poi_Action.setChecked(False)

    def goto_poi(self, key):
        """ Go to the last known position of poi <key>."""
        if self._poi_manager_logic.active_poi is None:
            self.log.warning("No POI selected.")
        else:
            self._poi_manager_logic.go_to_poi(
                poikey=self._poi_manager_logic.active_poi.get_key())

    def populate_poi_list(self):
        """ Populate the dropdown box for selecting a poi. """
        self.log.debug('started populate_poi_list at {0}'.format(time.time()))
        self._mw.active_poi_ComboBox.clear()
        self._mw.offset_anchor_ComboBox.clear()
        self._rrd.ref_a_poi_ComboBox.clear()
        self._rrd.ref_b_poi_ComboBox.clear()
        self._rrd.ref_c_poi_ComboBox.clear()

        for key in self._poi_manager_logic.get_all_pois(abc_sort=True):
            if key is not 'crosshair' and key is not 'sample':
                poi_list_empty = False
                self._mw.active_poi_ComboBox.addItem(
                    self._poi_manager_logic.poi_list[key].get_name(), key)
                self._mw.offset_anchor_ComboBox.addItem(
                    self._poi_manager_logic.poi_list[key].get_name(), key)
                self._rrd.ref_a_poi_ComboBox.addItem(
                    self._poi_manager_logic.poi_list[key].get_name(), key)
                self._rrd.ref_b_poi_ComboBox.addItem(
                    self._poi_manager_logic.poi_list[key].get_name(), key)
                self._rrd.ref_c_poi_ComboBox.addItem(
                    self._poi_manager_logic.poi_list[key].get_name(), key)

        # If there is no active POI, set the combobox to nothing (-1)
        if self._poi_manager_logic.active_poi is None:
            self._mw.active_poi_ComboBox.setCurrentIndex(-1)

        # Otherwise, set it to the active POI
        else:
            self._mw.active_poi_ComboBox.setCurrentIndex(
                self._mw.active_poi_ComboBox.findData(
                    self._poi_manager_logic.active_poi.get_key()))

        self.log.debug('finished populating at '.format(time.time()))

    def change_refind_method(self):
        """ Make appropriate changes in the GUI to reflect the newly chosen refind method."""

        if self._mw.refind_method_ComboBox.currentText(
        ) == 'position optimisation':
            self._mw.offset_anchor_ComboBox.setEnabled(False)
        elif self._mw.refind_method_ComboBox.currentText() == 'offset anchor':
            self.log.error(
                "Anchor method not fully implemented yet. "
                "Feel free to fix this method. Using position optimisation instead."
            )
            self._mw.offset_anchor_ComboBox.setEnabled(True)
        else:
            # TODO: throw an error
            self.log.debug('error 123')

    def set_roi_name(self):
        """ Set the name of a ROI (useful when saving)."""

        self._poi_manager_logic.roi_name = self._mw.roi_name_LineEdit.text(
        ).replace(" ", "_")

    def change_poi_name(self):
        """ Change the name of a poi."""

        newname = self._mw.poi_name_LineEdit.text()

        self._poi_manager_logic.rename_poi(
            poikey=self._poi_manager_logic.active_poi.get_key(), name=newname)

        # After POI name is changed, empty name field
        self._mw.poi_name_LineEdit.setText('')

    def handle_active_poi_ComboBox_index_change(self):
        """ Handle the change of index in the active POI combobox."""

        key = self._mw.active_poi_ComboBox.itemData(
            self._mw.active_poi_ComboBox.currentIndex())

        self._poi_manager_logic.set_active_poi(poikey=key)

        self._redraw_poi_markers(
        )  # todo when line 660 signal in logic is done, this is not necessary

    def select_poi_from_marker(self, poikey=None):
        """ Process the selection of a POI from click on POImark."""

        # Keep track of selected POI
        self._poi_manager_logic.set_active_poi(poikey=poikey)

#        # Set the selected POI in the combobox
#        self._mw.active_poi_ComboBox.setCurrentIndex(self._mw.active_poi_ComboBox.findData(poikey))
#        self._redraw_poi_markers()

    def update_poi_pos(self):
        if self._poi_manager_logic.active_poi is None:
            self.log.warning("No POI selected.")
        else:
            if self._mw.refind_method_ComboBox.currentText(
            ) == 'position optimisation':
                self._poi_manager_logic.optimise_poi(
                    poikey=self._poi_manager_logic.active_poi.get_key())

            elif self._mw.refind_method_ComboBox.currentText(
            ) == 'offset anchor':
                anchor_key = self._mw.offset_anchor_ComboBox.itemData(
                    self._mw.offset_anchor_ComboBox.currentIndex())
                self._poi_manager_logic.optimise_poi(
                    poikey=self._poi_manager_logic.active_poi.get_key(),
                    anchorkey=anchor_key)

    def toggle_follow(self):
        if self._mw.goto_poi_after_update_checkBox.isChecked():
            self._poi_manager_logic.go_to_crosshair_after_refocus = False
        else:
            self._poi_manager_logic.go_to_crosshair_after_refocus = True

    def _update_timer(self):
        self._mw.time_till_next_update_ProgressBar.setValue(
            self._poi_manager_logic.time_left)

    def set_track_period(self):
        """ Change the progress bar and update the timer duration."""

        new_track_period = self._mw.track_period_SpinBox.value()
        self._poi_manager_logic.set_periodic_optimize_duration(
            duration=new_track_period)

    def _track_period_changed(self):
        """ Reflect the changed track period in the GUI elements.
        """
        new_track_period = self._poi_manager_logic.timer_duration
        # Set the new maximum for the progress bar
        self._mw.time_till_next_update_ProgressBar.setMaximum(new_track_period)

        # If the tracker is not active, then set the value of the progress bar to the
        # new maximum
        if not self._mw.track_poi_Action.isChecked():
            self._mw.time_till_next_update_ProgressBar.setValue(
                new_track_period)

    def _redraw_clocktime_ticks(self):
        """If duration is displayed, reset ticks to default.
        Otherwise, create and update custom date/time ticks to the new axis range.
        """
        myAxisItem = self._mw.sample_shift_ViewWidget.plotItem.axes['bottom'][
            'item']

        # if duration display, reset to default ticks
        if self._mw.display_shift_vs_duration_RadioButton.isChecked():
            myAxisItem.setTicks(None)

        # otherwise, convert tick strings to clock format
        else:

            # determine size of the sample shift bottom axis item in pixels
            bounds = myAxisItem.mapRectFromParent(myAxisItem.geometry())
            span = (bounds.topLeft(), bounds.topRight())
            lengthInPixels = (span[1] - span[0]).manhattanLength()

            if lengthInPixels == 0:
                return -1
            if myAxisItem.range[0] < 0:
                return -1

            default_ticks = myAxisItem.tickValues(myAxisItem.range[0],
                                                  myAxisItem.range[1],
                                                  lengthInPixels)

            newticks = []
            for i, tick_level in enumerate(default_ticks):
                newticks_this_level = []
                ticks = tick_level[1]
                for ii, tick in enumerate(ticks):
                    # For major ticks, include date
                    if i == 0:
                        string = time.strftime("%H:%M (%d.%m.)",
                                               time.localtime(tick * 3600))
                        # (the axis is plotted in hours to get naturally better placed ticks.)

                    # for middle and minor ticks, just display clock time
                    else:
                        string = time.strftime("%H:%M",
                                               time.localtime(tick * 3600))

                    newticks_this_level.append((tick, string))
                newticks.append(newticks_this_level)

            myAxisItem.setTicks(newticks)
            return 0

    def _redraw_sample_shift(self):

        # Get trace data and calculate shifts in x,y,z
        poi_trace = self._poi_manager_logic.poi_list[
            'sample'].get_position_history()

        # If duration display is checked, subtract initial time and convert to
        # mins or hours as appropriate
        if self._mw.display_shift_vs_duration_RadioButton.isChecked():
            time_shift_data = (poi_trace[:, 0] - poi_trace[0, 0])

            if np.max(time_shift_data) < 300:
                self._mw.sample_shift_ViewWidget.setLabel('bottom',
                                                          'Time elapsed',
                                                          units='s')
            elif np.max(time_shift_data) < 7200:
                time_shift_data = time_shift_data / 60.0
                self._mw.sample_shift_ViewWidget.setLabel('bottom',
                                                          'Time elapsed',
                                                          units='min')
            else:
                time_shift_data = time_shift_data / 3600.0
                self._mw.sample_shift_ViewWidget.setLabel('bottom',
                                                          'Time elapsed',
                                                          units='hr')

        # Otherwise, take the actual time but divide by 3600 so that tickmarks
        # automatically fall on whole hours
        else:
            time_shift_data = poi_trace[:, 0] / 3600.0
            self._mw.sample_shift_ViewWidget.setLabel('bottom',
                                                      'Time',
                                                      units='')

        # Subtract initial position to get shifts
        x_shift_data = (poi_trace[:, 1] - poi_trace[0, 1])
        y_shift_data = (poi_trace[:, 2] - poi_trace[0, 2])
        z_shift_data = (poi_trace[:, 3] - poi_trace[0, 3])

        # Plot data
        self.x_shift_plot.setData(time_shift_data, x_shift_data)
        self.y_shift_plot.setData(time_shift_data, y_shift_data)
        self.z_shift_plot.setData(time_shift_data, z_shift_data)

        self._redraw_clocktime_ticks()

    def _redraw_poi_markers(self):

        self.log.debug('starting redraw_poi_markers {0}'.format(time.time()))

        for key in self._poi_manager_logic.get_all_pois():
            if key is not 'crosshair' and key is not 'sample':
                position = self._poi_manager_logic.get_poi_position(poikey=key)
                position = position[:2]

                if key in self._markers.keys():
                    self._markers[key].set_position(position)
                    self._markers[key].deselect()
                else:
                    # Create Region of Interest as marker:
                    marker = PoiMark(position,
                                     poi=self._poi_manager_logic.poi_list[key],
                                     click_action=self.select_poi_from_marker,
                                     movable=False,
                                     scaleSnap=False,
                                     snapSize=1.0e-6)

                    # Add to the Map Widget
                    marker.add_to_viewwidget(self._mw.roi_map_ViewWidget)
                    self._markers[key] = marker

        if self._poi_manager_logic.active_poi is not None:
            active_poi_key = self._poi_manager_logic.active_poi.get_key()

            self._markers[active_poi_key].select()
            cur_poi_pos = self._poi_manager_logic.get_poi_position(
                poikey=active_poi_key)
            self._mw.poi_coords_label.setText(
                '({0:.2r}m, {1:.2r}m, {2:.2r}m)'.format(
                    ScaledFloat(cur_poi_pos[0]), ScaledFloat(cur_poi_pos[1]),
                    ScaledFloat(cur_poi_pos[2])))
        self.log.debug('finished redraw at {0}'.format(time.time()))

    def make_new_roi(self):
        """ Start new ROI by removing all POIs and resetting the sample history."""

        for key in self._poi_manager_logic.get_all_pois():
            if key is not 'crosshair' and key is not 'sample':
                self._markers[key].delete_from_viewwidget()

        del self._markers
        self._markers = dict()

        self._poi_manager_logic.reset_roi()

        self.populate_poi_list()

    def save_roi(self):
        """ Save ROI to file."""

        self._poi_manager_logic.save_poi_map_as_roi()

    def load_roi(self):
        """ Load a saved ROI from file."""

        this_file = QtWidgets.QFileDialog.getOpenFileName(
            self._mw, str("Open ROI"), None, str("Data files (*.dat)"))[0]

        self._poi_manager_logic.load_roi_from_file(filename=this_file)

        self.populate_poi_list()

    def open_reorient_roi_dialog(self):
        """ Open the dialog for reorienting the ROI. """
        self._rrd.show()

    def ref_a_at_crosshair(self):
        """ Set the newpos for ref A from the current crosshair position. """
        # TODO: get the range for these spinboxes from the hardware scanner range!
        self._rrd.ref_a_x_pos_DoubleSpinBox.setValue(
            self._confocal_logic.get_position()[0])
        self._rrd.ref_a_y_pos_DoubleSpinBox.setValue(
            self._confocal_logic.get_position()[1])
        self._rrd.ref_a_z_pos_DoubleSpinBox.setValue(
            self._confocal_logic.get_position()[2])

    def ref_b_at_crosshair(self):
        """ Set the newpos for ref B from the current crosshair position. """
        self._rrd.ref_b_x_pos_DoubleSpinBox.setValue(
            self._confocal_logic.get_position()[0])
        self._rrd.ref_b_y_pos_DoubleSpinBox.setValue(
            self._confocal_logic.get_position()[1])
        self._rrd.ref_b_z_pos_DoubleSpinBox.setValue(
            self._confocal_logic.get_position()[2])

    def ref_c_at_crosshair(self):
        """ Set the newpos for ref C from the current crosshair position. """
        self._rrd.ref_c_x_pos_DoubleSpinBox.setValue(
            self._confocal_logic.get_position()[0])
        self._rrd.ref_c_y_pos_DoubleSpinBox.setValue(
            self._confocal_logic.get_position()[1])
        self._rrd.ref_c_z_pos_DoubleSpinBox.setValue(
            self._confocal_logic.get_position()[2])

    def do_roi_reorientation(self):
        """Pass the old and new positions of refs A, B, C to PoiManager Logic to reorient every POI in the ROI.
        """

        ref_a_coords, ref_b_coords, ref_c_coords, ref_a_newpos, ref_b_newpos, ref_c_newpos = self._read_reorient_roi_dialog_values(
        )

        self._poi_manager_logic.reorient_roi(ref_a_coords, ref_b_coords,
                                             ref_c_coords, ref_a_newpos,
                                             ref_b_newpos, ref_c_newpos)

        # Clear the values in the Reorient Roi Dialog in case it is needed again
        self.reset_reorientation_dialog()

    def _read_reorient_roi_dialog_values(self):
        """ This reads the values from reorient ROI Dialog, and returns them. """

        # Get POI keys for the chosen ref points
        ref_a_key = self._rrd.ref_a_poi_ComboBox.itemData(
            self._rrd.ref_a_poi_ComboBox.currentIndex())
        ref_b_key = self._rrd.ref_b_poi_ComboBox.itemData(
            self._rrd.ref_b_poi_ComboBox.currentIndex())
        ref_c_key = self._rrd.ref_c_poi_ComboBox.itemData(
            self._rrd.ref_c_poi_ComboBox.currentIndex())

        # Get the old coords for these refs
        ref_a_coords = np.array(
            self._poi_manager_logic.poi_list[ref_a_key].get_coords_in_sample())
        ref_b_coords = np.array(
            self._poi_manager_logic.poi_list[ref_b_key].get_coords_in_sample())
        ref_c_coords = np.array(
            self._poi_manager_logic.poi_list[ref_c_key].get_coords_in_sample())

        ref_a_newpos = np.array([
            self._rrd.ref_a_x_pos_DoubleSpinBox.value(),
            self._rrd.ref_a_y_pos_DoubleSpinBox.value(),
            self._rrd.ref_a_z_pos_DoubleSpinBox.value()
        ])
        ref_b_newpos = np.array([
            self._rrd.ref_b_x_pos_DoubleSpinBox.value(),
            self._rrd.ref_b_y_pos_DoubleSpinBox.value(),
            self._rrd.ref_b_z_pos_DoubleSpinBox.value()
        ])
        ref_c_newpos = np.array([
            self._rrd.ref_c_x_pos_DoubleSpinBox.value(),
            self._rrd.ref_c_y_pos_DoubleSpinBox.value(),
            self._rrd.ref_c_z_pos_DoubleSpinBox.value()
        ])

        return ref_a_coords, ref_b_coords, ref_c_coords, ref_a_newpos * 1e-6, ref_b_newpos * 1e-6, ref_c_newpos * 1e-6

    def reset_reorientation_dialog(self):
        """ Reset all the values in the reorient roi dialog. """

        self._rrd.ref_a_x_pos_DoubleSpinBox.setValue(0)
        self._rrd.ref_a_y_pos_DoubleSpinBox.setValue(0)
        self._rrd.ref_a_z_pos_DoubleSpinBox.setValue(0)

        self._rrd.ref_b_x_pos_DoubleSpinBox.setValue(0)
        self._rrd.ref_b_y_pos_DoubleSpinBox.setValue(0)
        self._rrd.ref_b_z_pos_DoubleSpinBox.setValue(0)

        self._rrd.ref_c_x_pos_DoubleSpinBox.setValue(0)
        self._rrd.ref_c_y_pos_DoubleSpinBox.setValue(0)
        self._rrd.ref_c_z_pos_DoubleSpinBox.setValue(0)

    def reorientation_sanity_check(self):
        """ Calculate the difference in length between edges of old triangle defined by refs A, B, C and the new triangle.
        """

        # Get set of positions from GUI
        ref_a_coords, ref_b_coords, ref_c_coords, ref_a_newpos, ref_b_newpos, ref_c_newpos = self._read_reorient_roi_dialog_values(
        )

        # Calculate the difference in side lengths AB, BC, CA between the old triangle and the new triangle
        delta_ab = np.linalg.norm(ref_b_coords -
                                  ref_a_coords) - np.linalg.norm(ref_b_newpos -
                                                                 ref_a_newpos)
        delta_bc = np.linalg.norm(ref_c_coords -
                                  ref_b_coords) - np.linalg.norm(ref_c_newpos -
                                                                 ref_b_newpos)
        delta_ca = np.linalg.norm(ref_a_coords -
                                  ref_c_coords) - np.linalg.norm(ref_a_newpos -
                                                                 ref_c_newpos)

        # Write to the GUI
        self._rrd.length_difference_ab_Label.setText(str(delta_ab))
        self._rrd.length_difference_bc_Label.setText(str(delta_bc))
        self._rrd.length_difference_ca_Label.setText(str(delta_ca))

    def do_autofind_poi_procedure(self):
        """Run the autofind_pois procedure in the POI Manager Logic to get all the POIs in the current ROI image."""
        #Fixme: Add here the appropriate functionality

        self.log.error("Has to be implemented properly. Feel free to do it.")

        # # Get the thresholds from the user-chosen color bar range
        # cb_min, cb_max = self.determine_cb_range()
        #
        # this_min_threshold = cb_min + 0.3 * (cb_max - cb_min)
        # this_max_threshold = cb_max
        #
        # self._poi_manager_logic.autofind_pois(neighborhood_size=1, min_threshold=this_min_threshold, max_threshold=this_max_threshold)

    def optimize_roi(self):
        """Run the autofind_pois procedure in the POI Manager Logic to get all the POIs in the current ROI image."""
        #Fixme: Add here the appropriate functionality
        self.log.error("Not implemented yet. Feel free to help!")
コード例 #21
0
class PulsedMasterLogic(GenericLogic):
    """
    This logic module combines the functionality of two modules.

    It can be used to generate pulse sequences/waveforms and to control the settings for the pulse
    generator via SequenceGeneratorLogic. Essentially this part controls what is played on the
    pulse generator.
    Furthermore it can be used to set up a pulsed measurement with an already set-up pulse generator
    together with a fast counting device via PulsedMeasurementLogic.

    The main purpose for this module is to provide a single interface while maintaining a modular
    structure for complex pulsed measurements. Each of the sub-modules can be used without this
    module but more care has to be taken in that case.
    Automatic transfer of information from one sub-module to the other for convenience is also
    handled here.
    Another important aspect is the use of this module in scripts (e.g. jupyter notebooks).
    All calls to sub-module setter functions (PulsedMeasurementLogic and SequenceGeneratorLogic)
    are decoupled from the calling thread via Qt queued connections.
    This ensures a more intuitive and less error prone use of scripting.
    """
    _modclass = 'pulsedmasterlogic'
    _modtype = 'logic'

    # declare connectors
    pulsedmeasurementlogic = Connector(interface='PulsedMeasurementLogic')
    sequencegeneratorlogic = Connector(interface='SequenceGeneratorLogic')

    # PulsedMeasurementLogic control signals
    sigDoFit = QtCore.Signal(str)
    sigToggleMeasurement = QtCore.Signal(bool, str)
    sigToggleMeasurementPause = QtCore.Signal(bool)
    sigTogglePulser = QtCore.Signal(bool)
    sigToggleExtMicrowave = QtCore.Signal(bool)
    sigFastCounterSettingsChanged = QtCore.Signal(dict)
    sigMeasurementSettingsChanged = QtCore.Signal(dict)
    sigExtMicrowaveSettingsChanged = QtCore.Signal(dict)
    sigAnalysisSettingsChanged = QtCore.Signal(dict)
    sigExtractionSettingsChanged = QtCore.Signal(dict)
    sigTimerIntervalChanged = QtCore.Signal(float)
    sigAlternativeDataTypeChanged = QtCore.Signal(str)
    sigManuallyPullData = QtCore.Signal()

    # signals for master module (i.e. GUI) coming from PulsedMeasurementLogic
    sigMeasurementDataUpdated = QtCore.Signal()
    sigTimerUpdated = QtCore.Signal(float, int, float)
    sigFitUpdated = QtCore.Signal(str, np.ndarray, object)
    sigMeasurementStatusUpdated = QtCore.Signal(bool, bool)
    sigPulserRunningUpdated = QtCore.Signal(bool)
    sigExtMicrowaveRunningUpdated = QtCore.Signal(bool)
    sigExtMicrowaveSettingsUpdated = QtCore.Signal(dict)
    sigFastCounterSettingsUpdated = QtCore.Signal(dict)
    sigMeasurementSettingsUpdated = QtCore.Signal(dict)
    sigAnalysisSettingsUpdated = QtCore.Signal(dict)
    sigExtractionSettingsUpdated = QtCore.Signal(dict)

    # SequenceGeneratorLogic control signals
    sigSavePulseBlock = QtCore.Signal(object)
    sigSaveBlockEnsemble = QtCore.Signal(object)
    sigSaveSequence = QtCore.Signal(object)
    sigDeletePulseBlock = QtCore.Signal(str)
    sigDeleteBlockEnsemble = QtCore.Signal(str)
    sigDeleteSequence = QtCore.Signal(str)
    sigLoadBlockEnsemble = QtCore.Signal(str)
    sigLoadSequence = QtCore.Signal(str)
    sigSampleBlockEnsemble = QtCore.Signal(str)
    sigSampleSequence = QtCore.Signal(str)
    sigClearPulseGenerator = QtCore.Signal()
    sigGeneratorSettingsChanged = QtCore.Signal(dict)
    sigSamplingSettingsChanged = QtCore.Signal(dict)
    sigGeneratePredefinedSequence = QtCore.Signal(str, dict)

    # signals for master module (i.e. GUI) coming from SequenceGeneratorLogic
    sigBlockDictUpdated = QtCore.Signal(dict)
    sigEnsembleDictUpdated = QtCore.Signal(dict)
    sigSequenceDictUpdated = QtCore.Signal(dict)
    sigAvailableWaveformsUpdated = QtCore.Signal(list)
    sigAvailableSequencesUpdated = QtCore.Signal(list)
    sigSampleEnsembleComplete = QtCore.Signal(object)
    sigSampleSequenceComplete = QtCore.Signal(object)
    sigLoadedAssetUpdated = QtCore.Signal(str, str)
    sigGeneratorSettingsUpdated = QtCore.Signal(dict)
    sigSamplingSettingsUpdated = QtCore.Signal(dict)
    sigPredefinedSequenceGenerated = QtCore.Signal(object)

    def __init__(self, config, **kwargs):
        """ Create PulsedMasterLogic object with connectors.

          @param dict kwargs: optional parameters
        """
        super().__init__(config=config, **kwargs)

        # Dictionary servings as status register
        self.status_dict = dict()
        return

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

        # Initialize status register
        self.status_dict = {
            'sampling_ensemble_busy': False,
            'sampling_sequence_busy': False,
            'sampload_busy': False,
            'loading_busy': False,
            'pulser_running': False,
            'measurement_running': False,
            'microwave_running': False,
            'predefined_generation_busy': False,
            'fitting_busy': False
        }

        # Connect signals controlling PulsedMeasurementLogic
        self.sigDoFit.connect(self.pulsedmeasurementlogic().do_fit,
                              QtCore.Qt.QueuedConnection)
        self.sigToggleMeasurement.connect(
            self.pulsedmeasurementlogic().toggle_pulsed_measurement,
            QtCore.Qt.QueuedConnection)
        self.sigToggleMeasurementPause.connect(
            self.pulsedmeasurementlogic().toggle_measurement_pause,
            QtCore.Qt.QueuedConnection)
        self.sigTogglePulser.connect(
            self.pulsedmeasurementlogic().toggle_pulse_generator,
            QtCore.Qt.QueuedConnection)
        self.sigToggleExtMicrowave.connect(
            self.pulsedmeasurementlogic().toggle_microwave,
            QtCore.Qt.QueuedConnection)
        self.sigFastCounterSettingsChanged.connect(
            self.pulsedmeasurementlogic().set_fast_counter_settings,
            QtCore.Qt.QueuedConnection)
        self.sigMeasurementSettingsChanged.connect(
            self.pulsedmeasurementlogic().set_measurement_settings,
            QtCore.Qt.QueuedConnection)
        self.sigExtMicrowaveSettingsChanged.connect(
            self.pulsedmeasurementlogic().set_microwave_settings,
            QtCore.Qt.QueuedConnection)
        self.sigAnalysisSettingsChanged.connect(
            self.pulsedmeasurementlogic().set_analysis_settings,
            QtCore.Qt.QueuedConnection)
        self.sigExtractionSettingsChanged.connect(
            self.pulsedmeasurementlogic().set_extraction_settings,
            QtCore.Qt.QueuedConnection)
        self.sigTimerIntervalChanged.connect(
            self.pulsedmeasurementlogic().set_timer_interval,
            QtCore.Qt.QueuedConnection)
        self.sigAlternativeDataTypeChanged.connect(
            self.pulsedmeasurementlogic().set_alternative_data_type,
            QtCore.Qt.QueuedConnection)
        self.sigManuallyPullData.connect(
            self.pulsedmeasurementlogic().manually_pull_data,
            QtCore.Qt.QueuedConnection)

        # Connect signals coming from PulsedMeasurementLogic
        self.pulsedmeasurementlogic().sigMeasurementDataUpdated.connect(
            self.sigMeasurementDataUpdated, QtCore.Qt.QueuedConnection)
        self.pulsedmeasurementlogic().sigTimerUpdated.connect(
            self.sigTimerUpdated, QtCore.Qt.QueuedConnection)
        self.pulsedmeasurementlogic().sigFitUpdated.connect(
            self.fit_updated, QtCore.Qt.QueuedConnection)
        self.pulsedmeasurementlogic().sigMeasurementStatusUpdated.connect(
            self.measurement_status_updated, QtCore.Qt.QueuedConnection)
        self.pulsedmeasurementlogic().sigPulserRunningUpdated.connect(
            self.pulser_running_updated, QtCore.Qt.QueuedConnection)
        self.pulsedmeasurementlogic().sigExtMicrowaveRunningUpdated.connect(
            self.ext_microwave_running_updated, QtCore.Qt.QueuedConnection)
        self.pulsedmeasurementlogic().sigExtMicrowaveSettingsUpdated.connect(
            self.sigExtMicrowaveSettingsUpdated, QtCore.Qt.QueuedConnection)
        self.pulsedmeasurementlogic().sigFastCounterSettingsUpdated.connect(
            self.sigFastCounterSettingsUpdated, QtCore.Qt.QueuedConnection)
        self.pulsedmeasurementlogic().sigMeasurementSettingsUpdated.connect(
            self.sigMeasurementSettingsUpdated, QtCore.Qt.QueuedConnection)
        self.pulsedmeasurementlogic().sigAnalysisSettingsUpdated.connect(
            self.sigAnalysisSettingsUpdated, QtCore.Qt.QueuedConnection)
        self.pulsedmeasurementlogic().sigExtractionSettingsUpdated.connect(
            self.sigExtractionSettingsUpdated, QtCore.Qt.QueuedConnection)

        # Connect signals controlling SequenceGeneratorLogic
        self.sigSavePulseBlock.connect(
            self.sequencegeneratorlogic().save_block,
            QtCore.Qt.QueuedConnection)
        self.sigSaveBlockEnsemble.connect(
            self.sequencegeneratorlogic().save_ensemble,
            QtCore.Qt.QueuedConnection)
        self.sigSaveSequence.connect(
            self.sequencegeneratorlogic().save_sequence,
            QtCore.Qt.QueuedConnection)
        self.sigDeletePulseBlock.connect(
            self.sequencegeneratorlogic().delete_block,
            QtCore.Qt.QueuedConnection)
        self.sigDeleteBlockEnsemble.connect(
            self.sequencegeneratorlogic().delete_ensemble,
            QtCore.Qt.QueuedConnection)
        self.sigDeleteSequence.connect(
            self.sequencegeneratorlogic().delete_sequence,
            QtCore.Qt.QueuedConnection)
        self.sigLoadBlockEnsemble.connect(
            self.sequencegeneratorlogic().load_ensemble,
            QtCore.Qt.QueuedConnection)
        self.sigLoadSequence.connect(
            self.sequencegeneratorlogic().load_sequence,
            QtCore.Qt.QueuedConnection)
        self.sigSampleBlockEnsemble.connect(
            self.sequencegeneratorlogic().sample_pulse_block_ensemble,
            QtCore.Qt.QueuedConnection)
        self.sigSampleSequence.connect(
            self.sequencegeneratorlogic().sample_pulse_sequence,
            QtCore.Qt.QueuedConnection)
        self.sigClearPulseGenerator.connect(
            self.sequencegeneratorlogic().clear_pulser,
            QtCore.Qt.QueuedConnection)
        self.sigGeneratorSettingsChanged.connect(
            self.sequencegeneratorlogic().set_pulse_generator_settings,
            QtCore.Qt.QueuedConnection)
        self.sigSamplingSettingsChanged.connect(
            self.sequencegeneratorlogic().set_generation_parameters,
            QtCore.Qt.QueuedConnection)
        self.sigGeneratePredefinedSequence.connect(
            self.sequencegeneratorlogic().generate_predefined_sequence,
            QtCore.Qt.QueuedConnection)

        # Connect signals coming from SequenceGeneratorLogic
        self.sequencegeneratorlogic().sigBlockDictUpdated.connect(
            self.sigBlockDictUpdated, QtCore.Qt.QueuedConnection)
        self.sequencegeneratorlogic().sigEnsembleDictUpdated.connect(
            self.sigEnsembleDictUpdated, QtCore.Qt.QueuedConnection)
        self.sequencegeneratorlogic().sigSequenceDictUpdated.connect(
            self.sigSequenceDictUpdated, QtCore.Qt.QueuedConnection)
        self.sequencegeneratorlogic().sigAvailableWaveformsUpdated.connect(
            self.sigAvailableWaveformsUpdated, QtCore.Qt.QueuedConnection)
        self.sequencegeneratorlogic().sigAvailableSequencesUpdated.connect(
            self.sigAvailableSequencesUpdated, QtCore.Qt.QueuedConnection)
        self.sequencegeneratorlogic().sigGeneratorSettingsUpdated.connect(
            self.sigGeneratorSettingsUpdated, QtCore.Qt.QueuedConnection)
        self.sequencegeneratorlogic().sigSamplingSettingsUpdated.connect(
            self.sigSamplingSettingsUpdated, QtCore.Qt.QueuedConnection)
        self.sequencegeneratorlogic().sigPredefinedSequenceGenerated.connect(
            self.predefined_sequence_generated, QtCore.Qt.QueuedConnection)
        self.sequencegeneratorlogic().sigSampleEnsembleComplete.connect(
            self.sample_ensemble_finished, QtCore.Qt.QueuedConnection)
        self.sequencegeneratorlogic().sigSampleSequenceComplete.connect(
            self.sample_sequence_finished, QtCore.Qt.QueuedConnection)
        self.sequencegeneratorlogic().sigLoadedAssetUpdated.connect(
            self.loaded_asset_updated, QtCore.Qt.QueuedConnection)
        return

    def on_deactivate(self):
        """

        @return:
        """
        # Disconnect all signals
        # Disconnect signals controlling PulsedMeasurementLogic
        self.sigDoFit.disconnect()
        self.sigToggleMeasurement.disconnect()
        self.sigToggleMeasurementPause.disconnect()
        self.sigTogglePulser.disconnect()
        self.sigToggleExtMicrowave.disconnect()
        self.sigFastCounterSettingsChanged.disconnect()
        self.sigMeasurementSettingsChanged.disconnect()
        self.sigExtMicrowaveSettingsChanged.disconnect()
        self.sigAnalysisSettingsChanged.disconnect()
        self.sigExtractionSettingsChanged.disconnect()
        self.sigTimerIntervalChanged.disconnect()
        self.sigAlternativeDataTypeChanged.disconnect()
        self.sigManuallyPullData.disconnect()
        # Disconnect signals coming from PulsedMeasurementLogic
        self.pulsedmeasurementlogic().sigMeasurementDataUpdated.disconnect()
        self.pulsedmeasurementlogic().sigTimerUpdated.disconnect()
        self.pulsedmeasurementlogic().sigFitUpdated.disconnect()
        self.pulsedmeasurementlogic().sigMeasurementStatusUpdated.disconnect()
        self.pulsedmeasurementlogic().sigPulserRunningUpdated.disconnect()
        self.pulsedmeasurementlogic().sigExtMicrowaveRunningUpdated.disconnect(
        )
        self.pulsedmeasurementlogic(
        ).sigExtMicrowaveSettingsUpdated.disconnect()
        self.pulsedmeasurementlogic().sigFastCounterSettingsUpdated.disconnect(
        )
        self.pulsedmeasurementlogic().sigMeasurementSettingsUpdated.disconnect(
        )
        self.pulsedmeasurementlogic().sigAnalysisSettingsUpdated.disconnect()
        self.pulsedmeasurementlogic().sigExtractionSettingsUpdated.disconnect()

        # Disconnect signals controlling SequenceGeneratorLogic
        self.sigSavePulseBlock.disconnect()
        self.sigSaveBlockEnsemble.disconnect()
        self.sigSaveSequence.disconnect()
        self.sigDeletePulseBlock.disconnect()
        self.sigDeleteBlockEnsemble.disconnect()
        self.sigDeleteSequence.disconnect()
        self.sigLoadBlockEnsemble.disconnect()
        self.sigLoadSequence.disconnect()
        self.sigSampleBlockEnsemble.disconnect()
        self.sigSampleSequence.disconnect()
        self.sigClearPulseGenerator.disconnect()
        self.sigGeneratorSettingsChanged.disconnect()
        self.sigSamplingSettingsChanged.disconnect()
        self.sigGeneratePredefinedSequence.disconnect()
        # Disconnect signals coming from SequenceGeneratorLogic
        self.sequencegeneratorlogic().sigBlockDictUpdated.disconnect()
        self.sequencegeneratorlogic().sigEnsembleDictUpdated.disconnect()
        self.sequencegeneratorlogic().sigSequenceDictUpdated.disconnect()
        self.sequencegeneratorlogic().sigAvailableWaveformsUpdated.disconnect()
        self.sequencegeneratorlogic().sigAvailableSequencesUpdated.disconnect()
        self.sequencegeneratorlogic().sigGeneratorSettingsUpdated.disconnect()
        self.sequencegeneratorlogic().sigSamplingSettingsUpdated.disconnect()
        self.sequencegeneratorlogic(
        ).sigPredefinedSequenceGenerated.disconnect()
        self.sequencegeneratorlogic().sigSampleEnsembleComplete.disconnect()
        self.sequencegeneratorlogic().sigSampleSequenceComplete.disconnect()
        self.sequencegeneratorlogic().sigLoadedAssetUpdated.disconnect()
        return

    #######################################################################
    ###             Pulsed measurement properties                       ###
    #######################################################################
    @property
    def fast_counter_constraints(self):
        return self.pulsedmeasurementlogic().fastcounter_constraints

    @property
    def fast_counter_settings(self):
        return self.pulsedmeasurementlogic().fast_counter_settings

    @property
    def ext_microwave_constraints(self):
        return self.pulsedmeasurementlogic().ext_microwave_constraints

    @property
    def ext_microwave_settings(self):
        return self.pulsedmeasurementlogic().ext_microwave_settings

    @property
    def measurement_settings(self):
        return self.pulsedmeasurementlogic().measurement_settings

    @property
    def timer_interval(self):
        return self.pulsedmeasurementlogic().timer_interval

    @property
    def analysis_methods(self):
        return self.pulsedmeasurementlogic().analysis_methods

    @property
    def extraction_methods(self):
        return self.pulsedmeasurementlogic().extraction_methods

    @property
    def analysis_settings(self):
        return self.pulsedmeasurementlogic().analysis_settings

    @property
    def extraction_settings(self):
        return self.pulsedmeasurementlogic().extraction_settings

    @property
    def signal_data(self):
        return self.pulsedmeasurementlogic().signal_data

    @property
    def signal_alt_data(self):
        return self.pulsedmeasurementlogic().signal_alt_data

    @property
    def measurement_error(self):
        return self.pulsedmeasurementlogic().measurement_error

    @property
    def raw_data(self):
        return self.pulsedmeasurementlogic().raw_data

    @property
    def laser_data(self):
        return self.pulsedmeasurementlogic().laser_data

    @property
    def alternative_data_type(self):
        return self.pulsedmeasurementlogic().alternative_data_type

    @property
    def fit_container(self):
        return self.pulsedmeasurementlogic().fc

    #######################################################################
    ###             Pulsed measurement methods                          ###
    #######################################################################
    @QtCore.Slot(dict)
    def set_measurement_settings(self, settings_dict=None, **kwargs):
        """

        @param settings_dict:
        @param kwargs:
        """
        if isinstance(settings_dict, dict):
            self.sigMeasurementSettingsChanged.emit(settings_dict)
        else:
            self.sigMeasurementSettingsChanged.emit(kwargs)
        return

    @QtCore.Slot(dict)
    def set_fast_counter_settings(self, settings_dict=None, **kwargs):
        """

        @param settings_dict:
        @param kwargs:
        """
        if isinstance(settings_dict, dict):
            self.sigFastCounterSettingsChanged.emit(settings_dict)
        else:
            self.sigFastCounterSettingsChanged.emit(kwargs)
        return

    @QtCore.Slot(dict)
    def set_ext_microwave_settings(self, settings_dict=None, **kwargs):
        """

        @param settings_dict:
        @param kwargs:
        """
        if isinstance(settings_dict, dict):
            self.sigExtMicrowaveSettingsChanged.emit(settings_dict)
        else:
            self.sigExtMicrowaveSettingsChanged.emit(kwargs)
        return

    @QtCore.Slot(dict)
    def set_analysis_settings(self, settings_dict=None, **kwargs):
        """

        @param settings_dict:
        @param kwargs:
        """
        if isinstance(settings_dict, dict):
            self.sigAnalysisSettingsChanged.emit(settings_dict)
        else:
            self.sigAnalysisSettingsChanged.emit(kwargs)
        return

    @QtCore.Slot(dict)
    def set_extraction_settings(self, settings_dict=None, **kwargs):
        """

        @param settings_dict:
        @param kwargs:
        """
        if isinstance(settings_dict, dict):
            self.sigExtractionSettingsChanged.emit(settings_dict)
        else:
            self.sigExtractionSettingsChanged.emit(kwargs)
        return

    @QtCore.Slot(int)
    @QtCore.Slot(float)
    def set_timer_interval(self, interval):
        """

        @param int|float interval: The timer interval to set in seconds.
        """
        if isinstance(interval, (int, float)):
            self.sigTimerIntervalChanged.emit(interval)
        return

    @QtCore.Slot(str)
    def set_alternative_data_type(self, alt_data_type):
        """

        @param alt_data_type:
        @return:
        """
        if isinstance(alt_data_type, str):
            self.sigAlternativeDataTypeChanged.emit(alt_data_type)
        return

    @QtCore.Slot()
    def manually_pull_data(self):
        """
        """
        self.sigManuallyPullData.emit()
        return

    @QtCore.Slot(bool)
    def toggle_ext_microwave(self, switch_on):
        """

        @param switch_on:
        """
        if isinstance(switch_on, bool):
            self.sigToggleExtMicrowave.emit(switch_on)
        return

    @QtCore.Slot(bool)
    def ext_microwave_running_updated(self, is_running):
        """

        @param is_running:
        """
        if isinstance(is_running, bool):
            self.status_dict['microwave_running'] = is_running
            self.sigExtMicrowaveRunningUpdated.emit(is_running)
        return

    @QtCore.Slot(bool)
    def toggle_pulse_generator(self, switch_on):
        """

        @param switch_on:
        """
        if isinstance(switch_on, bool):
            self.sigTogglePulser.emit(switch_on)
        return

    @QtCore.Slot(bool)
    def pulser_running_updated(self, is_running):
        """

        @param is_running:
        """
        if isinstance(is_running, bool):
            self.status_dict['pulser_running'] = is_running
            self.sigPulserRunningUpdated.emit(is_running)
        return

    @QtCore.Slot(bool)
    @QtCore.Slot(bool, str)
    def toggle_pulsed_measurement(self, start, stash_raw_data_tag=''):
        """

        @param bool start:
        @param str stash_raw_data_tag:
        """
        if isinstance(start, bool) and isinstance(stash_raw_data_tag, str):
            self.sigToggleMeasurement.emit(start, stash_raw_data_tag)
        return

    @QtCore.Slot(bool)
    def toggle_pulsed_measurement_pause(self, pause):
        """

        @param pause:
        """
        if isinstance(pause, bool):
            self.sigToggleMeasurementPause.emit(pause)
        return

    @QtCore.Slot(bool, bool)
    def measurement_status_updated(self, is_running, is_paused):
        """

        @param is_running:
        @param is_paused:
        """
        if isinstance(is_running, bool) and isinstance(is_paused, bool):
            self.status_dict['measurement_running'] = is_running
            self.sigMeasurementStatusUpdated.emit(is_running, is_paused)
        return

    @QtCore.Slot(str)
    def do_fit(self, fit_function):
        """

        @param fit_function:
        """
        if isinstance(fit_function, str):
            self.status_dict['fitting_busy'] = True
            self.sigDoFit.emit(fit_function)
        return

    @QtCore.Slot(str, np.ndarray, object)
    def fit_updated(self, fit_name, fit_data, fit_result):
        """

        @return:
        """
        self.status_dict['fitting_busy'] = False
        self.sigFitUpdated.emit(fit_name, fit_data, fit_result)
        return

    def save_measurement_data(self, tag, with_error):
        """
        Prepare data to be saved and create a proper plot of the data.
        This is just handed over to the measurement logic.

        @param str tag: a filetag which will be included in the filename
        @param bool with_error: select whether errors should be saved/plotted
        """
        self.pulsedmeasurementlogic().save_measurement_data(tag, with_error)
        return

    #######################################################################
    ###             Sequence generator properties                       ###
    #######################################################################
    @property
    def pulse_generator_constraints(self):
        return self.sequencegeneratorlogic().pulse_generator_constraints

    @property
    def pulse_generator_settings(self):
        return self.sequencegeneratorlogic().pulse_generator_settings

    @property
    def generation_parameters(self):
        return self.sequencegeneratorlogic().generation_parameters

    @property
    def analog_channels(self):
        return self.sequencegeneratorlogic().analog_channels

    @property
    def digital_channels(self):
        return self.sequencegeneratorlogic().digital_channels

    @property
    def saved_pulse_blocks(self):
        return self.sequencegeneratorlogic().saved_pulse_blocks

    @property
    def saved_pulse_block_ensembles(self):
        return self.sequencegeneratorlogic().saved_pulse_block_ensembles

    @property
    def saved_pulse_sequences(self):
        return self.sequencegeneratorlogic().saved_pulse_sequences

    @property
    def sampled_waveforms(self):
        return self.sequencegeneratorlogic().sampled_waveforms

    @property
    def sampled_sequences(self):
        return self.sequencegeneratorlogic().sampled_sequences

    @property
    def loaded_asset(self):
        return self.sequencegeneratorlogic().loaded_asset

    @property
    def generate_methods(self):
        return self.sequencegeneratorlogic().generate_methods

    @property
    def generate_method_params(self):
        return self.sequencegeneratorlogic().generate_method_params

    #######################################################################
    ###             Sequence generator methods                          ###
    #######################################################################
    @QtCore.Slot()
    def clear_pulse_generator(self):
        still_busy = self.status_dict[
            'sampling_ensemble_busy'] or self.status_dict[
                'sampling_sequence_busy'] or self.status_dict[
                    'loading_busy'] or self.status_dict['sampload_busy']
        if still_busy:
            self.log.error(
                'Can not clear pulse generator. Sampling/Loading still in progress.'
            )
        else:
            self.sigClearPulseGenerator.emit()
        return

    @QtCore.Slot(str)
    @QtCore.Slot(str, bool)
    def sample_ensemble(self, ensemble_name, with_load=False):
        already_busy = self.status_dict[
            'sampling_ensemble_busy'] or self.status_dict[
                'sampling_sequence_busy'] or self.sequencegeneratorlogic(
                ).module_state() == 'locked'
        if already_busy:
            self.log.error(
                'Sampling of a different asset already in progress.\n'
                'PulseBlockEnsemble "{0}" not sampled!'.format(ensemble_name))
        else:
            if with_load:
                self.status_dict['sampload_busy'] = True
            self.status_dict['sampling_ensemble_busy'] = True
            self.sigSampleBlockEnsemble.emit(ensemble_name)
        return

    @QtCore.Slot(object)
    def sample_ensemble_finished(self, ensemble):
        self.status_dict['sampling_ensemble_busy'] = False
        self.sigSampleEnsembleComplete.emit(ensemble)
        if self.status_dict['sampload_busy'] and not self.status_dict[
                'sampling_sequence_busy']:
            if ensemble is None:
                self.status_dict['sampload_busy'] = False
                self.sigLoadedAssetUpdated.emit(*self.loaded_asset)
            else:
                self.load_ensemble(ensemble.name)
        return

    @QtCore.Slot(str)
    @QtCore.Slot(str, bool)
    def sample_sequence(self, sequence_name, with_load=False):
        already_busy = self.status_dict[
            'sampling_ensemble_busy'] or self.status_dict[
                'sampling_sequence_busy'] or self.sequencegeneratorlogic(
                ).module_state() == 'locked'
        if already_busy:
            self.log.error(
                'Sampling of a different asset already in progress.\n'
                'PulseSequence "{0}" not sampled!'.format(sequence_name))
        else:
            if with_load:
                self.status_dict['sampload_busy'] = True
            self.status_dict['sampling_sequence_busy'] = True
            self.sigSampleSequence.emit(sequence_name)
        return

    @QtCore.Slot(object)
    def sample_sequence_finished(self, sequence):
        self.status_dict['sampling_sequence_busy'] = False
        self.sigSampleSequenceComplete.emit(sequence)
        if self.status_dict['sampload_busy']:
            if sequence is None:
                self.status_dict['sampload_busy'] = False
                self.sigLoadedAssetUpdated.emit(*self.loaded_asset)
            else:
                self.load_sequence(sequence.name)
        return

    @QtCore.Slot(str)
    def load_ensemble(self, ensemble_name):
        if self.status_dict['loading_busy']:
            self.log.error(
                'Loading of a different asset already in progress.\n'
                'PulseBlockEnsemble "{0}" not loaded!'.format(ensemble_name))
        else:
            self.status_dict['loading_busy'] = True
            self.sigLoadBlockEnsemble.emit(ensemble_name)
        return

    @QtCore.Slot(str)
    def load_sequence(self, sequence_name):
        if self.status_dict['loading_busy']:
            self.log.error(
                'Loading of a different asset already in progress.\n'
                'PulseSequence "{0}" not loaded!'.format(sequence_name))
        else:
            self.status_dict['loading_busy'] = True
            self.sigLoadSequence.emit(sequence_name)
        return

    @QtCore.Slot(str, str)
    def loaded_asset_updated(self, asset_name, asset_type):
        """

        @param asset_name:
        @param asset_type:
        @return:
        """
        self.status_dict['sampload_busy'] = False
        self.status_dict['loading_busy'] = False
        self.sigLoadedAssetUpdated.emit(asset_name, asset_type)
        # Transfer sequence information from PulseBlockEnsemble or PulseSequence to
        # PulsedMeasurementLogic to be able to invoke measurement settings from them
        if not asset_type:
            # If no asset loaded or asset type unknown, clear sequence_information dict

            object_instance = None
        elif asset_type == 'PulseBlockEnsemble':
            object_instance = self.saved_pulse_block_ensembles.get(asset_name)
        elif asset_type == 'PulseSequence':
            object_instance = self.saved_pulse_sequences.get(asset_name)
        else:
            object_instance = None

        if object_instance is None:
            self.pulsedmeasurementlogic().sampling_information = dict()
            self.pulsedmeasurementlogic().measurement_information = dict()
        else:
            self.pulsedmeasurementlogic(
            ).sampling_information = object_instance.sampling_information
            self.pulsedmeasurementlogic(
            ).measurement_information = object_instance.measurement_information
        return

    @QtCore.Slot(object)
    def save_pulse_block(self, block_instance):
        """

        @param block_instance:
        @return:
        """
        self.sigSavePulseBlock.emit(block_instance)
        return

    @QtCore.Slot(object)
    def save_block_ensemble(self, ensemble_instance):
        """


        @param ensemble_instance:
        @return:
        """
        self.sigSaveBlockEnsemble.emit(ensemble_instance)
        return

    @QtCore.Slot(object)
    def save_sequence(self, sequence_instance):
        """

        @param sequence_instance:
        @return:
        """
        self.sigSaveSequence.emit(sequence_instance)
        return

    @QtCore.Slot(str)
    def delete_pulse_block(self, block_name):
        """

        @param block_name:
        @return:
        """
        self.sigDeletePulseBlock.emit(block_name)
        return

    @QtCore.Slot(str)
    def delete_block_ensemble(self, ensemble_name):
        """

        @param ensemble_name:
        @return:
        """
        self.sigDeleteBlockEnsemble.emit(ensemble_name)
        return

    @QtCore.Slot(str)
    def delete_sequence(self, sequence_name):
        """

        @param sequence_name:
        @return:
        """
        self.sigDeleteSequence.emit(sequence_name)
        return

    @QtCore.Slot(dict)
    def set_pulse_generator_settings(self, settings_dict=None, **kwargs):
        """
        Either accept a settings dictionary as positional argument or keyword arguments.
        If both are present both are being used by updating the settings_dict with kwargs.
        The keyword arguments take precedence over the items in settings_dict if there are
        conflicting names.

        @param settings_dict:
        @param kwargs:
        @return:
        """
        if not isinstance(settings_dict, dict):
            settings_dict = kwargs
        else:
            settings_dict.update(kwargs)
        self.sigGeneratorSettingsChanged.emit(settings_dict)
        return

    @QtCore.Slot(dict)
    def set_generation_parameters(self, settings_dict=None, **kwargs):
        """
        Either accept a settings dictionary as positional argument or keyword arguments.
        If both are present both are being used by updating the settings_dict with kwargs.
        The keyword arguments take precedence over the items in settings_dict if there are
        conflicting names.

        @param settings_dict:
        @param kwargs:
        @return:
        """
        if not isinstance(settings_dict, dict):
            settings_dict = kwargs
        else:
            settings_dict.update(kwargs)

        # Force empty gate channel if fast counter is not gated
        if 'gate_channel' in settings_dict and not self.fast_counter_settings.get(
                'is_gated'):
            settings_dict['gate_channel'] = ''
        self.sigSamplingSettingsChanged.emit(settings_dict)
        return

    @QtCore.Slot(str)
    @QtCore.Slot(str, dict)
    def generate_predefined_sequence(self,
                                     generator_method_name,
                                     kwarg_dict=None):
        """

        @param generator_method_name:
        @param kwarg_dict:
        @return:
        """
        if not isinstance(kwarg_dict, dict):
            kwarg_dict = dict()
        self.status_dict['predefined_generation_busy'] = True
        self.sigGeneratePredefinedSequence.emit(generator_method_name,
                                                kwarg_dict)
        return

    @QtCore.Slot(object)
    def predefined_sequence_generated(self, generated_name):
        self.status_dict['predefined_generation_busy'] = False
        self.sigPredefinedSequenceGenerated.emit(generated_name)
        return

    def get_ensemble_info(self, ensemble):
        """
        """
        return self.sequencegeneratorlogic().get_ensemble_info(
            ensemble=ensemble)

    def get_sequence_info(self, sequence):
        """
        """
        return self.sequencegeneratorlogic().get_sequence_info(
            sequence=sequence)
コード例 #22
0
class PowerStabilizationLogic(GenericLogic):
    """This is the Interface class to define the controls for the simple
    microwave hardware.
    """
    _modclass = 'power_stabilization_logic'
    _modtype = 'logic'

    # connectors
    # TiS_camera_hardware = Connector(interface='EmptyInterface')
    # arduino_hardware = Connector(interface='EmptyInterface')
    # power_meter_hardware = Connector(interface='EmptyInterface')

    powermeter1 = Connector(interface='SimpleDataInterface')
    confocalscanner1 = Connector(interface='ConfocalScannerInterface')

    sigPowerUpdated = QtCore.Signal()
    sigPowerDataNext = QtCore.Signal()

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

        self._powermeter = self.powermeter1()
        self._daq_card = self.confocalscanner1()

        #### activate the NI card AO channel
        self.z_range = self._daq_card.get_position_range()[2]
        # Initialise the current position of all four scanner channels.
        self.current_position = self._daq_card.get_scanner_position()
        # initialise the range for scanning
        # self.scan_range = [0.0, 1.0]
        # self.set_scan_range(self.scan_range)
        self._static_v = 0.
        # Keep track of the current static voltage even while a scan may cause the real-time
        # voltage to change.
        self.goto_voltage(self._static_v)

        # set power meter
        self._powermeter.set_wavelength(600.0)
        self._powermeter.set_autorange(1)

        # PID
        self.pid_status = False
        self.ramp_status = False
        self.setpoint = 0.
        self.polarity = 1
        self.kp, self.ki, self.kd = 500, 1, 0
        self.min_pid_out, self.max_pid_out = 0., 0.4
        self.min_volt, self.max_volt = 0., 0.8
        self.ramping_factor = 0.01
        self.offset = 0
        self.error_p_prev = 0.
        self.power_prev = 0.
        self.error_p = 0
        self.error_i = 0
        self.error_d = 0
        self.output = 0
        self.time_loop = []
        self.power = None
        self.error = None

        # Thread
        self.threadlock = Mutex()

        self.sigPowerDataNext.connect(self.set_power,
                                      QtCore.Qt.QueuedConnection)
        return

    def on_deactivate(self):
        """  Performed during deactivation of the module. """
        # self._TiS_camera_hardware.on_deactivate()
        # self.set_power(0)
        # self._arduino_hardware.on_deactivate()
        self.sigPowerDataNext.disconnect()
        self.goto_voltage(1)
        return

    #### NI card scanner

    @QtCore.Slot(float)
    def goto_voltage(self, volts=None):
        """Forwarding the desired output voltage to the scanning ♣.

        @param float volts: desired voltage (volts)

        @return int: error code (0:OK, -1:error)
        """
        # print(tag, x, y, z)
        # Changes the respective value
        if volts is not None:
            self._static_v = volts
        ch_array = ['z']
        pos_array = [self._static_v]
        pos_dict = {}
        pos_dict[ch_array[0]] = pos_array[0]

        self._daq_card.scanner_set_position(**pos_dict)

    def set_voltage(self, volts):
        """ Set the channel idle voltage """
        self._static_v = np.clip(volts, self.z_range[0], self.z_range[1])
        self.goto_voltage(self._static_v)

    def get_current_voltage(self):
        """returns current voltage of hardware device(atm NIDAQ 3rd output)"""
        return self._daq_card.get_scanner_position()[2]

    #### PID controler

    def set_setpoint(self, setpoint):
        """ Set the power setpoint of the laser. """
        self.setpoint = setpoint
        return

    def get_setpoint(self):
        """ Get the power setpoint of the laser. """
        return self.setpoint

    def set_pid_status(self, boolean):
        """ Set the PID to True or False. """
        self.pid_status = boolean
        return

    def is_pid_status(self):
        """ Get the status of the PID (boolean). """
        return self.pid_status

    def set_kp(self, value):
        """ Set the proportional term of the PID. """
        self.kp = value
        return

    def set_ki(self, value):
        """ Set the integral term of the PID. """
        self.ki = value
        return

    def set_kd(self, value):
        """ Set the derivative term of the PID. """
        self.kd = value
        return

    def clear_integral(self):
        """ Clear the integral term of the PID. """
        self.error_i = 0
        return

    def set_ramp_status(self, boolean):
        """ Set the ramp status (True ot False).
        Used in order to increase the changes of laser power"""
        self.ramp_status = boolean
        return

    def set_power(self):
        """Set the voltage with or without PID"""
        # if not self.laser_on:
        #     # Switch console from remote to local mode and exit
        #     self._power_meter_hardware.power_meter._inst.control_ren(6)
        #     return

        # measure power and the time
        self.power = self.get_power()
        self.time_loop.append(time.time())
        # We delete the useless data in order to not saturate the memory
        if len(self.time_loop) > 2:
            del self.time_loop[0]
            if self.time_loop[-1] == self.time_loop[-2]:
                # If the time is the same for two loops then we call the function again
                pass
            else:
                # We update the power on the GUI
                self.sigPowerUpdated.emit()
                # get error
                self.error = self.get_setpoint() - self.power
                #### The ramp is there to speed up the process to fo the the setpoint value
                #### At the moment, the use of the ramp is disable by setting a high value
                #### TODO, delete the ramp which is more a source of trouble than anything
                if self.ramp_status:
                    if abs(self.error) > 10e-7:
                        self.offset = self.output
                        self.output += np.sign(self.error) * 1e-4
                        #### set the output value
                        self.goto_voltage(self.output)
                    else:
                        self.ramp_status = False
                        self.clear_integral()
                        self.pid_status = True
                elif self.pid_status:
                    delta_t = self.time_loop[-1] - self.time_loop[-2]
                    self.error_p = self.error
                    self.error_i += self.error * delta_t
                    self.error_d = (self.error - self.error_p_prev) / delta_t
                    p = self.kp * self.error_p
                    i = self.ki * self.error_i
                    d = self.kd * self.error_d
                    pid_out = self.polarity * (p + i + d / 100)

                    #### ramp up/down until setpoint is reached?
                    correction = self.offset + pid_out
                    k_factor = 1e3

                    if correction >= self.max_pid_out:
                        self.output = self.max_pid_out
                    elif self.get_current_voltage(
                    ) + correction * k_factor <= self.min_pid_out:
                        self.output = self.min_pid_out
                    else:
                        self.output = correction

                    err = self.output - self.power_prev

                    #### What to do with the correction, is it the right way the correction signal? Medidate on that
                    if abs(err) > 1e-10:
                        if self.get_current_voltage(
                        ) + correction * k_factor >= self.max_volt:
                            self.goto_voltage(self.get_current_voltage())
                            print("maximum voltage reached")
                        elif self.get_current_voltage(
                        ) + correction * k_factor <= self.min_volt:
                            self.goto_voltage(self.get_current_voltage())
                            print("minimum voltage reached")
                        else:
                            self.goto_voltage(self.get_current_voltage() +
                                              correction * k_factor)
                    self.power_prev = self.output
                    self.error_p_prev = self.error_p
                else:
                    self.goto_voltage(self._static_v)
        self.sigPowerDataNext.emit()
        return

    #### Power Meter

    def get_power(self):
        """ Get the optical power measured by the power meter. """
        return self._powermeter.get_power()

    def get_wavelength(self):
        """ Get the wavelength value of the PM100 powermeter """
        return self._powermeter.get_wavelength()

    def set_wavelength(self, wavelength):
        """ Set the wavelength value to the PM100 powermeter """
        self._powermeter.set_wavelength(wavelength)
コード例 #23
0
class SpectrometerInterfaceDummy(Base, SpectrometerInterface):
    """ Dummy spectrometer module.

        Shows a silicon vacancy spectrum at liquid helium temperatures.
    """

    fitlogic = Connector(interface='FitLogic')

    def on_activate(self):
        """ Activate module.
        """
        self._fitLogic = self.fitlogic()
        self.exposure = 0.1

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

    def recordSpectrum(self):
        """ Record a dummy spectrum.

            @return ndarray: 1024-value ndarray containing wavelength and intensity of simulated spectrum
        """
        length = 1024

        data = np.empty((2, length), dtype=np.double)
        data[0] = np.arange(730, 750, 20 / length)
        data[1] = np.random.uniform(0, 2000, length)

        lorentz, params = self._fitLogic.make_multiplelorentzian_model(
            no_of_functions=4)
        sigma = 0.05
        params.add('l0_amplitude', value=2000)
        params.add('l0_center', value=736.46)
        params.add('l0_sigma', value=1.5 * sigma)
        params.add('l1_amplitude', value=5800)
        params.add('l1_center', value=736.545)
        params.add('l1_sigma', value=sigma)
        params.add('l2_amplitude', value=7500)
        params.add('l2_center', value=736.923)
        params.add('l2_sigma', value=sigma)
        params.add('l3_amplitude', value=1000)
        params.add('l3_center', value=736.99)
        params.add('l3_sigma', value=1.5 * sigma)
        params.add('offset', value=50000.)

        data[1] += lorentz.eval(x=data[0], params=params)

        time.sleep(self.exposure)
        return data

    def saveSpectrum(self, path, postfix=''):
        """ Dummy save function.

            @param str path: path of saved spectrum
            @param str postfix: postfix of saved spectrum file
        """
        timestr = strftime("%Y%m%d-%H%M-%S_", localtime())
        print('Dummy would save to: ' + str(path) + timestr + str(postfix) +
              ".spe")

    def getExposure(self):
        """ Get exposure time.

            @return float: exposure time
        """
        return self.exposure

    def setExposure(self, exposureTime):
        """ Set exposure time.

            @param float exposureTime: exposure time
        """
        self.exposure = exposureTime
コード例 #24
0
ファイル: hbt_logic.py プロジェクト: WarwickEPR/qudi
class HbtLogic(GenericLogic):
    """
    This is the logic for running HBT experiments
    """
    _modclass = 'hbtlogic'
    _modtype = 'logic'

    _channel_apd_0 = ConfigOption('timetagger_channel_apd_0', missing='error')
    _channel_apd_1 = ConfigOption('timetagger_channel_apd_1', missing='error')
    _bin_width = ConfigOption('bin_width', 500, missing='info')
    _n_bins = ConfigOption('bins', 2000, missing='info')
    savelogic = Connector(interface='SaveLogic')

    hbt_updated = QtCore.Signal()
    hbt_fit_updated = QtCore.Signal()
    hbt_saved = QtCore.Signal()
    sigStart = QtCore.Signal()
    sigStop = QtCore.Signal()

    def __init__(self, config, **kwargs):
        super().__init__(config=config, **kwargs)
        self.fit_times = []
        self.bin_times = []
        self.fit_g2 = []
        self.g2_data = []
        self.g2_data_normalised = []
        self.hbt_available = False
        self._setup_measurement()
        self._close_measurement()

    def on_activate(self):
        """ Connect and configure the access to the FPGA.
        """
        self._save_logic = self.get_connector('savelogic')
        self._number_of_gates = int(100)
        self.g2_data = np.zeros_like(self.bin_times)
        self.g2_data_normalised = np.zeros_like(self.bin_times)
        self.fit_times = self.bin_times
        self.fit_g2 = np.zeros_like(self.fit_times)

        self.timer = QtCore.QTimer()
        self.timer.timeout.connect(self.update)

        self.sigStart.connect(self._start_hbt)
        self.sigStop.connect(self._stop_hbt)

    def _setup_measurement(self):
        self._tagger = tt.createTimeTagger()
        self.coin = tt.Correlation(self._tagger,
                                   self._channel_apd_0,
                                   self._channel_apd_1,
                                   binwidth=self._bin_width,
                                   n_bins=self._n_bins)
        self.bin_times = self.coin.getIndex()

    def _close_measurement(self):
        self.coin.stop()
        self.coin = None
        self._tagger = None

    def start_hbt(self):
        self.sigStart.emit()

    def stop_hbt(self):
        self.sigStop.emit()

    def _start_hbt(self):
        self._setup_measurement()
        self.coin.clear()
        self.coin.start()
        self.timer.start(500)  # 0.5s

    def update(self):
        self.bin_times = self.coin.getIndex()
        self.g2_data = self.coin.getData()
        self.hbt_available = True
        lvl = np.mean(self.g2_data[0:100])
        if lvl > 0:
            self.g2_data_normalised = self.g2_data / lvl
        else:
            self.g2_data_normalised = np.zeros_like(self.g2_data)
        self.hbt_updated.emit()

    def pause_hbt(self):
        if self.coin is not None:
            self.coin.stop()

    def continue_hbt(self):
        if self.coin is not None:
            self.coin.start()

    def _stop_hbt(self):
        if self.coin is not None:
            self._close_measurement()

        self.timer.stop()

    def fit_data(self):
        pass
        # model, param = self.fitlogic.make_hyperbolicsaturation_model()
        # param['I_sat'].min = 0
        # param['I_sat'].max = 1e7
        # param['I_sat'].value = max(self.psat_data) * .7
        # param['P_sat'].max = 100.0
        # param['P_sat'].min = 0.0
        # param['P_sat'].value = 1.0
        # param['slope'].min = 0.0
        # param['slope'].value = 1e3
        # param['offset'].min = 0.0
        # fit = self.fitlogic.make_hyperbolicsaturation_fit(x_axis=self.psat_powers, data=self.psat_data,
        #                                                   estimator=self.fitlogic.estimate_hyperbolicsaturation,
        #                                                   add_params=param)
        # self.fit = fit
        # self.fitted_Psat = fit.best_values['P_sat']
        # self.fitted_Isat = fit.best_values['I_sat']

    def save_hbt(self):
        # File path and name
        filepath = self._save_logic.get_path_for_module(module_name='HBT')

        # We will fill the data OrderedDict to send to savelogic
        data = OrderedDict()
        data['Time (ns)'] = np.array(self.bin_times)
        data['g2(t)'] = np.array(self.g2_data)
        data['g2(t) normalised'] = np.array(self.g2_data_normalised)

        self._save_logic.save_data(data,
                                   filepath=filepath,
                                   filelabel='g2data',
                                   fmt=['%.6e', '%.6e', '%.6e'])
        self.log.debug('HBT data saved to:\n{0}'.format(filepath))

        self.hbt_saved.emit()
        return 0

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

        @return int: error code (0:OK, -1:error)
        """
        return 0
コード例 #25
0
class CavityLogic(GenericLogic):
    """
    This is the Logic class for cavity scanning.
    
    """
    _modclass = 'confocallogic'
    _modtype = 'logic'

    # declare connectors
    nicard = Connector(interface='ConfocalScannerInterface')
    scope = Connector(interface='scopeinterface')
    savelogic = Connector(interface='SaveLogic')

    sigFullSweepPlotUpdated = QtCore.Signal(np.ndarray, np.ndarray)
    sigLinewidthPlotUpdated = QtCore.Signal(np.ndarray, np.ndarray)
    sigResonancesUpdated = QtCore.Signal(np.ndarray)
    sigSweepNumberUpdated = QtCore.Signal(int)
    sigTargetModeNumberUpdated = QtCore.Signal(int)

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

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

        self._full_sweep_freq = 2 / 3
        self.RampUp_time = np.linspace(0, 1, 100)
        self.RampUp_signalR = np.linspace(1, 1, 100)
        self._full_sweep_start = 0.0
        self._full_sweep_stop = -3.75
        self._acqusition_time = 2.0
        self.reflection_channel = 0
        self.ramp_channel = 1
        self.position_channel = 3
        self.velocity_channel = 2
        self.SG_scale = 10  # V
        self.lamb = 637e-9  # wavelenght
        self.current_mode_number = 10
        self.current_sweep_number = 1

        self.first_sweep = None
        self.first_corrected_resonances = None
        self.last_sweep = None
        self.last_corrected_resonances = None

        self.mode_shift_list = [0]

        self._current_filepath = r'C:\BittorrentSyncDrive\Personal - Rasmus\Rasmus notes\Measurements\test'

    def on_activate(self):
        """ Initialisation performed during activation of the module.
        """
        self._ni = self.get_connector('nicard')
        self._scope = self.get_connector('scope')
        self._save_logic = self.get_connector('savelogic')
        self.cavity_range = self._ni._cavity_position_range[
            1] - self._ni._cavity_position_range[0]

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

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

# ############################################ DATA AQUISITION START #########################################

    def _trim_data(self, times, volts):
        '''
        Trims data to the ramp

        :return:
        '''
        total_trace = times[-1] - times[0]  # sec
        ramp_period = 1.0 / self._full_sweep_freq  # sec
        period_index = len(times) * ramp_period / total_trace
        ramp_mid = np.argmin(volts[self.ramp_channel])

        low_index = ramp_mid - int(period_index / 2)
        high_index = ramp_mid + int(period_index / 2)

        volts_trim = volts[:, low_index:high_index + 1]
        time_trim = times[low_index:high_index + 1]

        return time_trim, volts_trim

    def _data_split_up(self):
        [self.RampUp_time,
         self.RampDown_time] = np.array_split(self.time_trim, 2)
        [self.RampUp_signalR, self.RampDown_signalR
         ] = np.array_split(self.volts_trim[self.reflection_channel], 2)
        [self.RampUp_signalNI, self.RampDown_signalNI
         ] = np.array_split(self.volts_trim[self.ramp_channel], 2)
        [self.RampUp_signalSG, self.RampDown_signalSG
         ] = np.array_split(self.volts_trim[self.position_channel], 2)
        [self.RampUp_signalSG_v, self.RampDown_signalSG_v
         ] = np.array_split(self.volts_trim[self.velocity_channel], 2)
        return 0

    def _get_ramp_up_signgals(self):
        self.time_trim, self.volts_trim = self._trim_data(
            self.time, self.volts)
        self._data_split_up()
        return 0
# ############################################ DATA AQUSITION END ##########################################

# ############################################ FITTING START ###############################################

    def _polyfit_SG(self, xdata, ydata, order=3, plot=False):
        xdata_trim = xdata[::9]
        ydata_trim = ydata[::9]
        p_fit = np.poly1d(np.polyfit(xdata_trim, ydata_trim, order))

        if plot is True:
            plt.plot(xdata, ydata, '-', xdata, p_fit(xdata), '--')
            plt.show()

        return p_fit(xdata)

    def _fit_ramp(self, xdata, ydata):
        # Fitting setup
        parameter_guess = [
            self._full_sweep_start, self._full_sweep_stop,
            self._full_sweep_freq, self.time_trim[0]
        ]

        func = self._ni.sweep_function

        # Actual fitting
        popt, pcov = curve_fit(func, xdata, ydata, parameter_guess)
        return popt

# ############################################  FITTING END  ##############################################

# ################################## SAVE AND LOAD DATA START ##########################################################

    def _load_full_sweep(self, filepath=None, filename=None):
        """
        Loads data from full sweep

        :param filepath:
        :param filename:
        :return:
        """
        delimiter = '\t'

        if filepath is None:
            filepath = self._current_filepath

        if filename is None:
            filename = self._current_filename

        with open(os.path.join(filepath, filename), 'rb') as file:
            data = np.loadtxt(file, delimiter=delimiter)

        self.time = data[:, 0].transpose()
        self.volts = data[:, 1:5].transpose()

    def _save_raw_data(self, label=''):
        date = datetime.datetime.fromtimestamp(
            time()).strftime('%Y-%m-%d_%H%M%S')
        self._current_filename = date + label + '_full_sweep_data.dat'

        data = np.vstack([self.time, self.volts])

        fmt = ['%.8e', '%.3e', '%.3e', '%.3e', '%.3e']
        header = ''
        delimiter = '\t'
        comments = '#'

        with open(os.path.join(self._current_filepath, self._current_filename),
                  'wb') as file:
            np.savetxt(file,
                       data.transpose(),
                       fmt=fmt,
                       delimiter=delimiter,
                       header=header,
                       comments=comments)

    def _save_linewidth_data(self, label, data):
        date = datetime.datetime.fromtimestamp(
            time()).strftime('%Y-%m-%d_%H%M%S')
        self._current_filename = date + label + '_linewidth_data.dat'

        fmt = ['%.8e']
        for i in range(np.shape(data)[0] - 1):
            fmt.append('%.3e')
        header = ''
        delimiter = '\t'
        comments = '#'

        with open(os.path.join(self._current_filepath, self._current_filename),
                  'wb') as file:
            np.savetxt(file,
                       data.transpose(),
                       fmt=fmt,
                       delimiter=delimiter,
                       header=header,
                       comments=comments)

# ############################# Save and load data end #############################################################

# ################################### FULL SWEEPS START ##################################################

    def _get_scope_data(self):
        """
        Get scope data for all four channels

        This is loaded into self.volts and self.time

        :return: 
        """
        times, volts = self._scope.aquire_data()
        volts = volts.reshape(4, int(len(volts) / 4))
        times = times.reshape(4, int(len(times) / 4))
        time = times[0]

        # First point hare not good! Scope outputs 0 or maybe a header
        self.volts = volts[:, 2000:]
        self.time = time[2000:]

    def setup_scope_for_full_sweep(self):
        self._scope.set_record_lenght(linewidth=False)
        self._scope.set_acquisition_time(self._acqusition_time)
        self._scope.set_data_composition_to_env()
        # HARD CODED!!!!!
        self._scope.set_vertical_scale(2, 500E-3)
        self._scope.set_vertical_position(2, 3500E-3)
        self._scope.set_vertical_scale(3.0, 5E-3)
        self._scope.set_vertical_position(3.0, 0)
        self._scope.set_vertical_scale(4.0, 2)
        self._scope.set_vertical_position(4.0, -2.5)
        sleep(1)

    def start_full_sweep(self):
        """
        Starts a single full sweep

        1. set up the scope for a full sweep
        2. sets up a single ramp 
        3. executes the sweep
        4. get_data
        4. closed sweep


        :return: 
        """
        self._ni.cavity_set_voltage(0.0)
        sleep(1.0)

        # Set up scope for full sweep
        self.setup_scope_for_full_sweep()

        # set up ni card for full sweep
        # One full sweep
        RepOfSweep = 1
        self._ni.set_up_sweep(self._full_sweep_start, self._full_sweep_stop,
                              self._full_sweep_freq, RepOfSweep)

        # start sweep
        self._scope.run_single()
        # HARD CODED!!!!!
        sleep(0.5)
        self._ni.start_sweep()

        # stop sweep
        # HARD CODED!!!!!
        sleep(self._acqusition_time)
        self._ni.close_sweep()

        self._get_scope_data()

        return 0

    def get_nth_full_sweep(self, sweep_number=None, save=True):
        """

        :param sweep_number: 
        :return: 
        """
        if sweep_number is None:
            sweep_number = self.current_sweep_number

        if sweep_number > 1:
            self.last_sweep = self.RampUp_signalR
            self.last_corrected_resonances = self.current_resonances

        try_num = 1
        while True:
            try:
                self.start_full_sweep()

                self._get_ramp_up_signgals()

                self.RampUp_signalSG_polyfit = self._polyfit_SG(
                    xdata=self.RampUp_time,
                    ydata=self.RampUp_signalSG,
                    order=3,
                    plot=False)

                resonances = self._peak_search(self.RampUp_signalR)
                corrected_resonances = self._find_missing_resonances(
                    resonances)

                if len(resonances) < 3:
                    continue
                else:
                    break
            except:
                # Did not get the full sweep
                if try_num < 3:
                    try_num += 1
                    continue
                else:
                    return -1

        if sweep_number == 1:
            self.first_sweep = self.RampUp_signalR
            self.first_corrected_resonances = corrected_resonances
            self.first_RampUp_signalSG_polyfit = self.RampUp_signalSG_polyfit
            plt.plot(self.first_RampUp_signalSG_polyfit, self.first_sweep)
            plt.plot(self.first_RampUp_signalSG_polyfit[corrected_resonances],
                     self.first_sweep[corrected_resonances],
                     'o',
                     color='r')
            plt.grid()
            plt.show()

        self.current_sweep_number += 1
        self.current_resonances = corrected_resonances

        self.sigFullSweepPlotUpdated.emit(self.RampUp_time,
                                          self.RampUp_signalR)
        self.sigResonancesUpdated.emit(self.current_resonances)

        if save is True:
            self._save_raw_data(label='_{}'.format(sweep_number))

        return 0

# #################################### FULL SWEEPS STOP ##################################################

# #################################### TARGET MODE START ###################################################

    def find_phase_difference(self, signal_a, signal_b, show=False):
        # regularize datasets by subtracting mean and dividing by s.d.
        mod_signal_a = signal_a - signal_a.mean()
        mod_signal_a = -mod_signal_a / mod_signal_a.std()
        mod_signal_b = signal_b - signal_b.mean()
        mod_signal_b = -mod_signal_b / mod_signal_b.std()

        # Calculate cross correlation function https://en.wikipedia.org/wiki/Cross-correlation
        xcorr = np.correlate(mod_signal_a, mod_signal_b, 'same')

        nsamples = mod_signal_a.size
        dt = np.arange(-nsamples / 2, nsamples / 2, dtype=int)

        # Add penalty
        penalty = -0.25 * np.max(xcorr) * np.abs(dt)
        xcorr = xcorr + penalty

        mode_delay = dt[xcorr.argmax()]

        if np.abs(mode_delay) > 3:
            return None

        if show is True:
            plt.plot(dt, xcorr)
            plt.grid()
            plt.savefig(
                self._current_filepath +
                r'\correlation{}.png'.format(self.current_sweep_number),
                dpi=200)
            plt.show()

        return int(mode_delay)

    def get_target_mode(self,
                        resonances,
                        low_mode=None,
                        high_mode=None,
                        plot=False):

        # Find phase difference
        if low_mode is None:
            low_mode = 0
        if high_mode is None:
            high_mode = np.min(
                [len(self.last_corrected_resonances),
                 len(resonances)])

        if self.last_sweep is None:
            self.last_sweep = self.first_sweep
            self.last_corrected_resonances = self.first_corrected_resonances

        #mode_shift = self.find_phase_difference(self.last_sweep[self.last_corrected_resonances[low_mode:high_mode]],
        #                                        self.RampUp_signalR[resonances[low_mode:high_mode]], show=True)

        closet_old_mode = np.argmin(
            np.abs(self.target_position -
                   self.RampUp_signalSG_polyfit[resonances]))

        #if mode_shift == None:
        #    return None

        # store mode shifts
        #self.mode_shift_list.append(mode_shift+self.mode_shift_list[-1])

        # Find closets mode
        target_mode = closet_old_mode - 1

        if plot is True:
            index = self.first_corrected_resonances[self.current_mode_number]
            new_index = resonances[target_mode]
            plt.plot(self.first_RampUp_signalSG_polyfit, self.first_sweep)
            plt.plot(self.first_RampUp_signalSG_polyfit[index],
                     self.first_sweep[index],
                     'o',
                     markersize=10,
                     color='r')
            plt.plot(self.RampUp_signalSG_polyfit, self.RampUp_signalR)
            plt.plot(self.RampUp_signalSG_polyfit[new_index],
                     self.RampUp_signalR[new_index],
                     'x',
                     markersize=20,
                     color='r')
            plt.grid()
            plt.savefig(
                self._current_filepath +
                r'\Target_mode_plot_{}.png'.format(self.current_sweep_number),
                dpi=200)
            plt.show()

        return target_mode
# #################################### TARGET MODE END ###################################################

# ############################# LINEWIDTH MEASUREMENT ####################################################

    def read_position_from_strain_gauge(self):
        """
        This read the strain gauge voltage from the ni card
        :return:
        """
        rawdata = self._ni.read_position()

        position = np.average(rawdata)
        return position

    def _move_closer_to_resonance(self, current_offset, position_error):
        """

        :param current_offset:
        :param position_error:
        :return:
        """
        # Approximate correction
        response = (
            -3.75 / 20
        ) * 2  # (expansion of pzt) V/um / (position in volt) 20 um/ 10 V  = 2.0

        correction = -response * position_error
        new_offset = current_offset + correction
        self._ni.cavity_set_voltage(new_offset)

        return new_offset

    def _find_resonance_position_from_strain_gauge(self, current_offset,
                                                   target_position,
                                                   threshold_pos):
        """

        :return: offset for mode
        """
        self.log.info('Target position = {:0.3f}'.format(2 * target_position))
        i = 0
        while i < 10:
            self._ni.cavity_set_voltage(current_offset)
            sleep(3.0)
            try:
                position_in_volt = self.read_position_from_strain_gauge()
                position_error = position_in_volt - target_position
                self.log.info(
                    'Current position = {:0.3f}, Distance from target {:0.3f}'.
                    format(2 * position_in_volt, position_error * 2))
                if np.abs(position_error
                          ) < threshold_pos / 2.0:  # Convert from volt to nm
                    break
                else:
                    current_offset = self._move_closer_to_resonance(
                        current_offset, position_error)
                    i += 1
                    continue
            except:
                self.log.error('could not find resonance position')

        if i > 10:
            self.log.warning('Did not find a position')

        return current_offset

    def setup_scope_for_linewidth(self, trigger_level, acquisition_time):
        """
        
        :param trigger_level: 
        :param acquisition_time: 
        :param position: 
        :param scale: 
        :return: 
        """
        # Adjust ramp channel:
        self._scope.set_data_composition_to_yt()
        self._scope.set_acquisition_time(acquisition_time)
        self._scope.set_record_lenght(linewidth=True)
        self._scope.set_egde_trigger(channel=1, level=trigger_level)

        # FIXME: Adjust position and velocity
        # self._scope.set_vertical_scale(channel=4, scale=1.0)

    def _linewidth_get_data(self):
        """
        Get data from scope

        :return: 
        """
        linewidth_times, self.linewidth_volts = self._scope.aquire_data()
        linewidth_times = linewidth_times.reshape(
            4, int(len(linewidth_times) / 4))
        self.linewidth_time = linewidth_times[0]

    def linewidth_measurement(self, modes, target_mode, repeat, freq=40):
        """
        1. sets up scope for linewidth measurement
        2. start a ramp around the target mode
        3. gets data if triggered
        4. closes ramp and saves data
        
        :param modes: List of NI_card voltages for each resonances
        :param target_mode:
        :param repeat: number of linewidth measurements
        :param freq: 
        :return: 
        """

        # Setup scope for linewidth measurements with trigger on ramp signal
        contrast = np.abs(self.RampUp_signalR.min() -
                          np.median(self.RampUp_signalR))
        trigger_level = np.median(self.RampUp_signalR) - contrast
        self.setup_scope_for_linewidth(trigger_level=trigger_level,
                                       acquisition_time=40e-6)

        # start continues ramp
        #Two first cases are for end modes
        # FIXME: End modes still does not work because of bound of 0, -3.75
        if target_mode >= np.size(modes):
            amplitude = 2 * abs(modes[target_mode - 1] -
                                modes[target_mode]) / 2.0
        elif target_mode == 0:
            amplitude = 2 * abs(modes[target_mode] -
                                modes[target_mode + 1]) / 2.0
        else:
            amplitude = abs(modes[target_mode - 1] -
                            modes[target_mode + 1]) / 2.0

        self.linewidth_time_list = np.array([])
        self.linewidth_volts_list = np.array([])
        self.target_position = self.RampUp_signalSG_polyfit[
            self.current_resonances[target_mode]]
        # Get data from scope
        i = 0
        while i < repeat:
            k = 1.0

            # Refind correct position
            offset = self._find_resonance_position_from_strain_gauge(
                current_offset=modes[target_mode],
                target_position=self.RampUp_signalSG_polyfit[
                    self.current_resonances[target_mode]],
                threshold_pos=0.025)  # 25 nm

            # Start new ramp
            self._ni.set_up_ramp_output(amplitude, offset, freq)
            self._ni.start_ramp()

            trigger_level = np.median(self.RampUp_signalR) - k * contrast
            self._scope.set_egde_trigger(channel=1, level=trigger_level)
            self._scope.run_single()

            while True:
                if k < 0.2:
                    print('did not find resonance {} {}'.format(
                        self.current_mode_number, i))
                    self.linewidth_time = np.zeros_like(self.linewidth_time)
                    self.linewidth_volts = np.zeros_like(self.linewidth_volts)
                    ret_str = 0
                    break
                # if triggered then get data

                self._scope.scope.write('*OPC?')
                sleep(0.1)
                try:
                    ret_str = self._scope.scope.read()
                    break
                except:
                    k -= 0.02
                    trigger_level = np.median(
                        self.RampUp_signalR) - k * contrast
                    self._scope.set_egde_trigger(channel=1,
                                                 level=trigger_level)
                    continue

            if ret_str == r'1':
                self._linewidth_get_data()
                i += 1
                self.linewidth_time_list = np.concatenate(
                    [self.linewidth_time_list, self.linewidth_time])
                self.linewidth_volts_list = np.concatenate(
                    [self.linewidth_volts_list, self.linewidth_volts])

                self.sigLinewidthPlotUpdated.emit(
                    self.linewidth_time,
                    self.linewidth_volts[0:int(len(self.linewidth_volts) / 4)])

            # Make sure we are still at the right position
            self._ni.stop_ramp()
            self._ni.close_ramp()

            #Update plot in gui

        data = self.linewidth_volts_list
        data = data.reshape(4 * repeat, int(len(data) / (4 * repeat)))
        data = np.vstack([self.linewidth_time, data])

        # close ramp
        self._ni.cavity_set_position(20.0e-6)

        self._save_linewidth_data(label='_{}'.format(self.current_mode_number),
                                  data=data)

        return 0

# ############################### PEAK DETECTION START #################################################

    def _detect_peaks(self,
                      x,
                      y=None,
                      mph=None,
                      mpd=1,
                      threshold=0,
                      edge='rising',
                      kpsh=False,
                      valley=False,
                      show=False,
                      ax=None):
        """
        Detect peaks in data based on their amplitude and other features.

        Parameters
        ----------
        x : 1D array_like
            data.
        mph : {None, number}, optional (default = None)
            detect peaks that are greater than minimum peak height.
        mpd : positive integer, optional (default = 1)
            detect peaks that are at least separated by minimum peak distance (in
            number of data).
        threshold : positive number, optional (default = 0)
            detect peaks (valleys) that are greater (smaller) than `threshold`
            in relation to their immediate neighbors.
        edge : {None, 'rising', 'falling', 'both'}, optional (default = 'rising')
            for a flat peak, keep only the rising edge ('rising'), only the
            falling edge ('falling'), both edges ('both'), or don't detect a
            flat peak (None).
        kpsh : bool, optional (default = False)
            keep peaks with same height even if they are closer than `mpd`.
        valley : bool, optional (default = False)
            if True (1), detect valleys (local minima) instead of peaks.
        show : bool, optional (default = False)
            if True (1), plot data in matplotlib figure.
        ax : a matplotlib.axes.Axes instance, optional (default = None).

        Returns
        -------
        ind : 1D array_like
            indeces of the peaks in `x`.

        Notes
        -----
        The detection of valleys instead of peaks is performed internally by simply
        negating the data: `ind_valleys = detect_peaks(-x)`

        The function can handle NaN's 

        See this IPython Notebook [1]_.

        References
        ----------
        .. [1] http://nbviewer.ipython.org/github/demotu/BMC/blob/master/notebooks/DetectPeaks.ipynb

        """

        x = np.atleast_1d(x).astype('float64')
        if x.size < 3:
            return np.array([], dtype=int)
        if valley:
            x = -x
        # find indices of all peaks
        dx = x[1:] - x[:-1]
        # handle NaN's
        indnan = np.where(np.isnan(x))[0]
        if indnan.size:
            x[indnan] = np.inf
            dx[np.where(np.isnan(dx))[0]] = np.inf
        ine, ire, ife = np.array([[], [], []], dtype=int)
        if not edge:
            ine = np.where((np.hstack((dx, 0)) < 0)
                           & (np.hstack((0, dx)) > 0))[0]
        else:
            if edge.lower() in ['rising', 'both']:
                ire = np.where((np.hstack((dx, 0)) <= 0)
                               & (np.hstack((0, dx)) > 0))[0]
            if edge.lower() in ['falling', 'both']:
                ife = np.where((np.hstack((dx, 0)) < 0)
                               & (np.hstack((0, dx)) >= 0))[0]
        ind = np.unique(np.hstack((ine, ire, ife)))
        # handle NaN's
        if ind.size and indnan.size:
            # NaN's and values close to NaN's cannot be peaks
            ind = ind[np.in1d(ind,
                              np.unique(
                                  np.hstack((indnan, indnan - 1, indnan + 1))),
                              invert=True)]
        # first and last values of x cannot be peaks
        if ind.size and ind[0] == 0:
            ind = ind[1:]
        if ind.size and ind[-1] == x.size - 1:
            ind = ind[:-1]
        # remove peaks < minimum peak height
        if ind.size and mph is not None:
            ind = ind[x[ind] >= mph]
        # remove peaks - neighbors < threshold
        if ind.size and threshold > 0:
            dx = np.min(np.vstack([x[ind] - x[ind - 1], x[ind] - x[ind + 1]]),
                        axis=0)
            ind = np.delete(ind, np.where(dx < threshold)[0])
        # detect small peaks closer than minimum peak distance
        if ind.size and mpd > 0:
            ind = ind[np.argsort(x[ind])][::-1]  # sort ind by peak height
            idel = np.zeros(ind.size, dtype=bool)
            for i in range(ind.size):
                if not idel[i]:
                    # keep peaks with the same height if kpsh is True
                    if y is not None:
                        idel = idel | (y[ind] >= y[ind[i]] - mpd) & (y[ind] <= y[ind[i]] + mpd) \
                                      & (x[ind[i]] > x[ind] if kpsh else True)
                    else:
                        idel = idel | (ind >= ind[i] - mpd) & (ind <= ind[i] + mpd) \
                                      & (x[ind[i]] > x[ind] if kpsh else True)
                    idel[i] = 0  # Keep current peak
            # remove the small peaks and sort back the indices by their occurrence
            ind = np.sort(ind[~idel])

        if show:
            if indnan.size:
                x[indnan] = np.nan
            if valley:
                x = -x
            self._plot(x, mph, mpd, threshold, edge, valley, ax, ind)

        return ind

    def _plot(self, x, mph, mpd, threshold, edge, valley, ax, ind):
        """Plot results of the detect_peaks function, see its help."""
        try:
            import matplotlib.pyplot as plt
        except ImportError:
            print('matplotlib is not available.')
        else:
            if ax is None:
                _, ax = plt.subplots(1, 1, figsize=(8, 4))

            ax.plot(x, 'b', lw=1)
            if ind.size:
                label = 'valley' if valley else 'peak'
                label = label + 's' if ind.size > 1 else label
                ax.plot(ind,
                        x[ind],
                        '+',
                        mfc=None,
                        mec='r',
                        mew=2,
                        ms=8,
                        label='%d %s' % (ind.size, label))
                ax.legend(loc='best', framealpha=.5, numpoints=1)
            ax.set_xlim(-.02 * x.size, x.size * 1.02 - 1)
            ymin, ymax = x[np.isfinite(x)].min(), x[np.isfinite(x)].max()
            yrange = ymax - ymin if ymax > ymin else 1
            ax.set_ylim(ymin - 0.1 * yrange, ymax + 0.1 * yrange)
            ax.set_xlabel('Data #', fontsize=14)
            ax.set_ylabel('Amplitude', fontsize=14)
            mode = 'Valley detection' if valley else 'Peak detection'
            ax.set_title("%s (mph=%s, mpd=%d, threshold=%s, edge='%s')" %
                         (mode, str(mph), mpd, str(threshold), edge))
            # plt.grid()
            plt.show()

    def _check_for_outliers(self, peaks, outlier_cutoff=1.5):
        """
        Finds the distances between between resonaces and locates where the is a missing resoances.
        The is when the distances is larger than 1.5 fsr.
        
        :param peaks: list with resonances 
        :param outlier_cutoff: the distance in the units of fsr  
        :return: 
        """

        # Expected fsr in voltage
        one_fsr = self.SG_scale / self.cavity_range * (self.lamb / 2.0
                                                       )  # in Volt

        # Distance between resonances
        delta_peaks = self.RampUp_signalSG_polyfit[
            peaks[1:]] - self.RampUp_signalSG_polyfit[peaks[:-1]]

        # Find where the distance between resonances is to large compared to the cutoff
        outliers = np.where(delta_peaks > outlier_cutoff * one_fsr)[0]

        if outliers.size > 0:
            return outliers
        else:
            return np.array([])

    def _find_missing_resonances(self, resonances, outlier_cutoff=1.5):
        """
        Inserts a index for a missing resonance if there is more that 1.5 fsr between two resonances
        
        :param resonances: 
        :param outlier_cutoff: 
        :return: 
        """
        corrected_resonances = resonances
        i = 0
        while i < int(1 / 4 * len(resonances)):
            outliers = self._check_for_outliers(resonances, outlier_cutoff)
            i += 1
            if len(outliers) > 0:
                outlier = outliers[0]

                delta_peaks = self.RampUp_signalSG_polyfit[resonances[
                    1:]] - self.RampUp_signalSG_polyfit[resonances[:-1]]

                value = self.RampUp_signalSG_polyfit[
                    resonances[outlier]] + np.median(delta_peaks)
                # insert new peak in new corrected array
                corrected_resonances = np.insert(
                    corrected_resonances, outlier + 1,
                    np.abs(self.RampUp_signalSG_polyfit - value).argmin())

            else:
                # found no new peaks
                break

        return corrected_resonances

    def _peak_search(self, signal, outlier_cutoff=1.5, show=False):
        """
        This uses the function peak detect with a few different parameters to find the parameter with
        gives the least amount of outliers (in terms of distance between resonances)
        
        :param signal: 
        :param outlier_cutoff: 
        :param show: 
        :return: 
        """
        # minimum peak height
        mph = -(signal.mean() - np.abs(signal.max() - signal.mean()))
        # minimum peak distance
        one_fsr = self.SG_scale / self.cavity_range * (self.lamb / 2.0
                                                       )  # in Volt
        MaxNumPeak = int(
            (self.RampUp_signalSG.max() - self.RampUp_signalSG.min()) /
            one_fsr) + 10

        contrast = np.abs(signal.min() - signal.mean())

        errors = [0.75, 0.8, 0.85, 0.9, 0.95]
        constants = np.linspace(0.0, 0.1, 10)

        OutlierList = []
        ErrorList = []
        ConstantList = []

        for error, constant in product(errors, constants):
            # Search for the parameters with least outliers
            mpd = error * one_fsr
            threshold = constant * contrast
            resonances = self._detect_peaks(signal,
                                            y=self.RampUp_signalSG_polyfit,
                                            mph=mph,
                                            mpd=mpd,
                                            threshold=threshold,
                                            valley=True,
                                            show=False)
            outliers = self._check_for_outliers(resonances,
                                                outlier_cutoff=outlier_cutoff)

            # Check to see if there is too many resonances
            if len(resonances) < MaxNumPeak:
                OutlierList.append(len(outliers))
                ErrorList.append(error)
                ConstantList.append(constant)

        #Optimal parameters for peak search
        OptimalError = ErrorList[np.argmin(OutlierList)]
        OptimalConstant = ConstantList[np.argmin(OutlierList)]

        mpd = OptimalError * one_fsr
        threshold = OptimalConstant * contrast

        resonances = self._detect_peaks(signal,
                                        y=self.RampUp_signalSG_polyfit,
                                        mph=mph,
                                        mpd=mpd,
                                        threshold=threshold,
                                        valley=True,
                                        show=show)

        return resonances

# ############################################# PEAK DETECTION End ######################################################

    def get_hw_constraints(self):
        """ Return the names of all ocnfigured fit functions.
        @return object: Hardware constraints object
        """
        # FIXME: Should be from hardware
        constraints_dict = {
            'min_position': 0,
            'max_position': 20e-6,
            'min_speed': 0,
            'max_speed': 100,
            'min_temperature': -100,
            'max_temperature': 30,
            'min_averages': 1,
            'max_averages': 1000,
            'min_exposure': 0,
            'max_exposure': 100
        }

        return constraints_dict

    def start_ramp(self, amplitude, offset, freq):

        self._ni.set_up_ramp_output(amplitude, offset, freq)
        self._ni.start_ramp()

    def stop_ramp(self):

        self._ni.stop_ramp()
        self._ni.close_ramp()

    def start_finesse_measurement(self, repeat=10, freq=40):
        """
        Starts the finesse measurement

        @param repeat:
        @param freq:
        @return:
        """

        self.finesse_measure_cont = True
        ret_val = self.get_nth_full_sweep(sweep_number=1, save=True)
        if ret_val != 0:
            self.log.error('Did not get first sweep!')

        self.ramp_popt = self._fit_ramp(
            xdata=self.time_trim[::9],
            ydata=self.volts_trim[self.ramp_channel, ::9])
        Modes = self._ni.sweep_function(
            self.RampUp_time[self.first_corrected_resonances], *self.ramp_popt)
        self.current_mode_number = len(self.first_corrected_resonances) - 2
        self.linewidth_measurement(Modes,
                                   target_mode=self.current_mode_number,
                                   repeat=repeat,
                                   freq=freq)

        high_mode = len(self.first_corrected_resonances) - 2
        low_mode = 0

        for i in range(15):
            if self.finesse_measure_cont is True:
                self.current_mode_number -= 1
                ret_val = self.get_nth_full_sweep(sweep_number=2 + i)
                target_mode = self.get_target_mode(self.current_resonances,
                                                   low_mode=low_mode,
                                                   high_mode=high_mode,
                                                   plot=True)
                if target_mode == None:
                    print('Moved more that 5 modes')
                self.ramp_popt = self._fit_ramp(
                    xdata=self.time_trim[::9],
                    ydata=self.volts_trim[self.ramp_channel, ::9])
                Modes = self._ni.sweep_function(
                    self.RampUp_time[self.current_resonances], *self.ramp_popt)
                self.linewidth_measurement(Modes,
                                           target_mode=target_mode,
                                           repeat=repeat,
                                           freq=freq)
            else:
                break

    def continue_finesse_measurements(self):
        self.finesse_measure_cont = False
        pass

    def stop_finesse_measurement(self):
        pass

    def set_cavity_position(self, position):
        self._ni.cavity_set_position(position)
コード例 #26
0
class SimpleDataGui(GUIBase):
    """ FIXME: Please document
    """
    _modclass = 'simplegui'
    _modtype = 'gui'

    ## declare connectors
    simplelogic = Connector(interface='SimpleDataLogic')

    sigStart = QtCore.Signal()
    sigStop = QtCore.Signal()

    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]))

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

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

        # Setup dock widgets
        self._mw.centralwidget.hide()
        self._mw.setDockNestingEnabled(True)

        # Plot labels.
        self._pw = self._mw.trace_PlotWidget

        self.plot1 = self._pw.plotItem
        self.plot1.setLabel('left',
                            'Some Value',
                            units='some unit',
                            color='#00ff00')
        self.plot1.setLabel('bottom', 'Number of values', units='some unit')
        self.plot2 = self._pw.plotItem
        self.plot2.setLabel('right',
                            'Smooth Value',
                            units='some unit',
                            color='#ff0000')

        self.curvearr = []
        self.smootharr = []
        colorlist = (palette.c1, palette.c2, palette.c3, palette.c4,
                     palette.c5, palette.c6)
        ## Create an empty plot curve to be filled later, set its pen
        for i in range(self._simple_logic._data_logic.getChannels()):
            self.curvearr.append(self.plot1.plot())
            self.curvearr[-1].setPen(colorlist[(2 * i) % len(colorlist)])
            self.smootharr.append(self.plot2.plot())
            self.smootharr[-1].setPen(colorlist[(2 * i + 1) % len(colorlist)],
                                      width=2)

        # make correct button state
        self._mw.startAction.setChecked(False)

        #####################
        # Connecting user interactions
        self._mw.startAction.triggered.connect(self.start_clicked)
        self._mw.recordAction.triggered.connect(self.save_clicked)

        #####################
        # starting the physical measurement
        self.sigStart.connect(self._simple_logic.startMeasure)
        self.sigStop.connect(self._simple_logic.stopMeasure)

        self._simple_logic.sigRepeat.connect(self.updateData)

    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):
        """ Deactivate the module properly.
        """
        # FIXME: !
        self._mw.close()

    def updateData(self):
        """ The function that grabs the data and sends it to the plot.
        """
        for i in range(self._simple_logic._data_logic.getChannels()):
            self.curvearr[i].setData(y=self._simple_logic.buf[0:-11, i],
                                     x=np.arange(
                                         0,
                                         len(self._simple_logic.buf[0:-11])))
            self.smootharr[i].setData(
                y=self._simple_logic.smooth[24:-25 - 10, i],
                x=np.arange(0, len(self._simple_logic.smooth[24:-25 - 10])))

        if self._simple_logic.module_state() == 'locked':
            self._mw.startAction.setText('Stop')
        else:
            self._mw.startAction.setText('Start')

    def start_clicked(self):
        """ Handling the Start button to stop and restart the counter.
        """
        if self._simple_logic.module_state() == 'locked':
            self._mw.startAction.setText('Start')
            self.sigStop.emit()
        else:
            self._mw.startAction.setText('Stop')
            self.sigStart.emit()

    def save_clicked(self):
        """ Handling the save button to save the data into a file.
        """
        return
コード例 #27
0
class NuclearOperationsGui(GUIBase):
    """ This is the main GUI Class for Nuclear Operations. """

    _modclass = 'NuclearOperationsGui'
    _modtype = 'gui'

    # 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
コード例 #28
0
class SpectrometerGui(GUIBase):
    _modclass = 'SpectrometerGui'
    _modtype = 'gui'

    # declare connectors
    spectrumlogic = Connector(interface='SpectrumLogic')

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

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

        self._spectrum_logic = self.spectrumlogic()

        # setting up the window
        self._mw = SpectrometerWindow()

        self._mw.stop_diff_spec_Action.setEnabled(False)
        self._mw.resume_diff_spec_Action.setEnabled(False)
        self._mw.correct_background_Action.setChecked(
            self._spectrum_logic.background_correction)

        # giving the plots names allows us to link their axes together
        self._pw = self._mw.plotWidget  # pg.PlotWidget(name='Counter1')
        self._plot_item = self._pw.plotItem

        # create a new ViewBox, link the right axis to its coordinate system
        self._right_axis = pg.ViewBox()
        self._plot_item.showAxis('right')
        self._plot_item.scene().addItem(self._right_axis)
        self._plot_item.getAxis('right').linkToView(self._right_axis)
        self._right_axis.setXLink(self._plot_item)

        # create a new ViewBox, link the right axis to its coordinate system
        self._top_axis = pg.ViewBox()
        self._plot_item.showAxis('top')
        self._plot_item.scene().addItem(self._top_axis)
        self._plot_item.getAxis('top').linkToView(self._top_axis)
        self._top_axis.setYLink(self._plot_item)
        self._top_axis.invertX(b=True)

        # handle resizing of any of the elements

        self._pw.setLabel('left', 'Fluorescence', units='counts/s')
        self._pw.setLabel('right', 'Number of Points', units='#')
        self._pw.setLabel('bottom', 'Wavelength', units='m')
        self._pw.setLabel('top', 'Relative Frequency', units='Hz')

        # Create an empty plot curve to be filled later, set its pen
        self._curve1 = self._pw.plot()
        self._curve1.setPen(palette.c1, width=2)

        self._curve2 = self._pw.plot()
        self._curve2.setPen(palette.c2, width=2)

        self.update_data()

        # Connect singals
        self._mw.rec_single_spectrum_Action.triggered.connect(
            self.record_single_spectrum)
        self._mw.start_diff_spec_Action.triggered.connect(
            self.start_differential_measurement)
        self._mw.stop_diff_spec_Action.triggered.connect(
            self.stop_differential_measurement)
        self._mw.resume_diff_spec_Action.triggered.connect(
            self.resume_differential_measurement)

        self._mw.save_spectrum_Action.triggered.connect(
            self.save_spectrum_data)
        self._mw.correct_background_Action.triggered.connect(
            self.correct_background)
        self._mw.acquire_background_Action.triggered.connect(
            self.acquire_background)
        self._mw.save_background_Action.triggered.connect(
            self.save_background_data)

        self._mw.restore_default_view_Action.triggered.connect(
            self.restore_default_view)

        self._spectrum_logic.sig_specdata_updated.connect(self.update_data)
        self._spectrum_logic.spectrum_fit_updated_Signal.connect(
            self.update_fit)
        self._spectrum_logic.fit_domain_updated_Signal.connect(
            self.update_fit_domain)

        self._mw.show()

        self._save_PNG = True

        # Internal user input changed signals
        self._mw.fit_domain_min_doubleSpinBox.valueChanged.connect(
            self.set_fit_domain)
        self._mw.fit_domain_max_doubleSpinBox.valueChanged.connect(
            self.set_fit_domain)

        # Internal trigger signals
        self._mw.do_fit_PushButton.clicked.connect(self.do_fit)
        self._mw.fit_domain_all_data_pushButton.clicked.connect(
            self.reset_fit_domain_all_data)

        # fit settings
        self._fsd = FitSettingsDialog(self._spectrum_logic.fc)
        self._fsd.sigFitsUpdated.connect(
            self._mw.fit_methods_ComboBox.setFitFunctions)
        self._fsd.applySettings()
        self._mw.action_FitSettings.triggered.connect(self._fsd.show)

    def on_deactivate(self):
        """ Deinitialisation performed during deactivation of the module.
        """
        # disconnect signals
        self._fsd.sigFitsUpdated.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 update_data(self):
        """ The function that grabs the data and sends it to the plot.
        """
        data = self._spectrum_logic.spectrum_data

        # erase previous fit line
        self._curve2.setData(x=[], y=[])

        # draw new data
        self._curve1.setData(x=data[0, :], y=data[1, :])

    def update_fit(self, fit_data, result_str_dict, current_fit):
        """ Update the drawn fit curve and displayed fit results.
        """
        if current_fit != 'No Fit':
            # display results as formatted text
            self._mw.spectrum_fit_results_DisplayWidget.clear()
            try:
                formated_results = units.create_formatted_output(
                    result_str_dict)
            except:
                formated_results = 'this fit does not return formatted results'
            self._mw.spectrum_fit_results_DisplayWidget.setPlainText(
                formated_results)

            # redraw the fit curve in the GUI plot.
            self._curve2.setData(x=fit_data[0, :], y=fit_data[1, :])

    def record_single_spectrum(self):
        """ Handle resume of the scanning without resetting the data.
        """
        self._spectrum_logic.get_single_spectrum()

    def start_differential_measurement(self):

        # Change enabling of GUI actions
        self._mw.stop_diff_spec_Action.setEnabled(True)
        self._mw.start_diff_spec_Action.setEnabled(False)
        self._mw.rec_single_spectrum_Action.setEnabled(False)
        self._mw.resume_diff_spec_Action.setEnabled(False)

        self._spectrum_logic.start_differential_spectrum()

    def stop_differential_measurement(self):
        self._spectrum_logic.stop_differential_spectrum()

        # Change enabling of GUI actions
        self._mw.stop_diff_spec_Action.setEnabled(False)
        self._mw.start_diff_spec_Action.setEnabled(True)
        self._mw.rec_single_spectrum_Action.setEnabled(True)
        self._mw.resume_diff_spec_Action.setEnabled(True)

    def resume_differential_measurement(self):
        self._spectrum_logic.resume_differential_spectrum()

        # Change enabling of GUI actions
        self._mw.stop_diff_spec_Action.setEnabled(True)
        self._mw.start_diff_spec_Action.setEnabled(False)
        self._mw.rec_single_spectrum_Action.setEnabled(False)
        self._mw.resume_diff_spec_Action.setEnabled(False)

    def save_spectrum_data(self):
        self._spectrum_logic.save_spectrum_data()

    def correct_background(self):
        self._spectrum_logic.background_correction = self._mw.correct_background_Action.isChecked(
        )

    def acquire_background(self):
        self._spectrum_logic.get_single_spectrum(background=True)

    def save_background_data(self):
        self._spectrum_logic.save_spectrum_data(background=True)

    def do_fit(self):
        """ Command spectrum logic to do the fit with the chosen fit function.
        """
        fit_function = self._mw.fit_methods_ComboBox.getCurrentFit()[0]
        self._spectrum_logic.do_fit(fit_function)

    def set_fit_domain(self):
        """ Set the fit domain in the spectrum logic to values given by the GUI spinboxes.
        """
        lambda_min = self._mw.fit_domain_min_doubleSpinBox.value()
        lambda_max = self._mw.fit_domain_max_doubleSpinBox.value()

        new_fit_domain = np.array([lambda_min, lambda_max])

        self._spectrum_logic.set_fit_domain(new_fit_domain)

    def reset_fit_domain_all_data(self):
        """ Reset the fit domain to match the full data set.
        """
        self._spectrum_logic.set_fit_domain()

    def update_fit_domain(self, domain):
        """ Update the displayed fit domain to new values (set elsewhere).
        """
        self._mw.fit_domain_min_doubleSpinBox.setValue(domain[0])
        self._mw.fit_domain_max_doubleSpinBox.setValue(domain[1])

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

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

        # Arrange docks widgets
        self._mw.addDockWidget(
            QtCore.Qt.DockWidgetArea(QtCore.Qt.TopDockWidgetArea),
            self._mw.spectrum_fit_dockWidget)

        # Set the toolbar to its initial top area
        self._mw.addToolBar(QtCore.Qt.TopToolBarArea, self._mw.measure_ToolBar)
        self._mw.addToolBar(QtCore.Qt.TopToolBarArea,
                            self._mw.background_ToolBar)
        self._mw.addToolBar(QtCore.Qt.TopToolBarArea,
                            self._mw.differential_ToolBar)
        return 0
コード例 #29
0
class PoiManagerLogic(GenericLogic):

    """
    This is the Logic class for mapping and tracking bright features in the confocal scan.
    """
    _modclass = 'poimanagerlogic'
    _modtype = 'logic'

    # declare connectors
    optimizer1 = Connector(interface='OptimizerLogic')
    scannerlogic = Connector(interface='ConfocalLogic')
    savelogic = Connector(interface='SaveLogic')

    # status vars
    poi_list = StatusVar(default=OrderedDict())
    roi_name = StatusVar(default='')
    active_poi = StatusVar(default=None)

    signal_timer_updated = QtCore.Signal()
    signal_poi_updated = QtCore.Signal()
    signal_poi_deleted = QtCore.Signal(str)
    signal_confocal_image_updated = QtCore.Signal()
    signal_periodic_opt_started = QtCore.Signal()
    signal_periodic_opt_duration_changed = QtCore.Signal()
    signal_periodic_opt_stopped = QtCore.Signal()

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

        self._current_poi_key = None
        self.go_to_crosshair_after_refocus = False  # default value

        # timer and its handling for the periodic refocus
        self.timer = None
        self.time_left = 0
        self.timer_step = 0
        self.timer_duration = 300

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

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

        self._optimizer_logic = self.optimizer1()
        self._confocal_logic = self.scannerlogic()
        self._save_logic = self.savelogic()

        # listen for the refocus to finish
        self._optimizer_logic.sigRefocusFinished.connect(self._refocus_done)

        # listen for the deactivation of a POI caused by moving to a different position
        self._confocal_logic.signal_change_position.connect(self.user_move_deactivates_poi)

        # Initialise the roi_map_data (xy confocal image)
        self.roi_map_data = self._confocal_logic.xy_image

    def on_deactivate(self):
        return

    def user_move_deactivates_poi(self, tag):
        """ Deactivate the active POI if the confocal microscope scanner position is
        moved by anything other than the optimizer
        """
        pass

    def add_poi(self, position=None, key=None, emit_change=True):
        """ Creates a new poi and adds it to the list.

        @return int: key of this new poi

        A position can be provided (such as during re-loading a saved ROI).
        If no position is provided, then the current crosshair position is used.
        """
        # If there are only 2 POIs (sample and crosshair) then the newly added POI needs to start the sample drift logging.
        if len(self.poi_list) == 2:
            self.poi_list['sample']._creation_time = time.time()
            # When the poimanager is activated the 'sample' poi is created because it is needed
            # from the beginning for various functionalities. If the tracking of the sample is started it has
            # to be reset such that this first point is deleted here
            # Probably this can be solved a lot nicer.
            self.poi_list['sample'].delete_last_position(empty_array_completely=True)
            self.poi_list['sample'].add_position_to_history(position=[0, 0, 0])
            self.poi_list['sample'].set_coords_in_sample(coords=[0, 0, 0])

        if position is None:
            position = self._confocal_logic.get_position()[:3]
        if len(position) != 3:
            self.log.error('Given position is not 3-dimensional.'
                           'Please pass POIManager a 3-dimensional position to set a POI.')
            return

        new_poi = PoI(pos=position, key=key)
        self.poi_list[new_poi.get_key()] = new_poi

        # The POI coordinates are set relative to the last known sample position
        most_recent_sample_pos = self.poi_list['sample'].get_position_history()[-1, :][1:4]
        this_poi_coords = position - most_recent_sample_pos
        new_poi.set_coords_in_sample(coords=this_poi_coords)

        # Since POI was created at current scanner position, it automatically
        # becomes the active POI.
        self.set_active_poi(poikey=new_poi.get_key())

        if emit_change:
            self.signal_poi_updated.emit()

        return new_poi.get_key()

    def get_confocal_image_data(self):
        """ Get the current confocal xy scan data to hold as image of ROI"""

        # get the roi_map_data (xy confocal image)
        self.roi_map_data = self._confocal_logic.xy_image

        self.signal_confocal_image_updated.emit()

    def get_all_pois(self, abc_sort=False):
        """ Returns a list of the names of all existing POIs.

        @return string[]: List of names of the POIs

        Also crosshair and sample are included.
        """
        if abc_sort is False:
            return sorted(self.poi_list.keys())

        elif abc_sort is True:
            # First create a dictionary with poikeys indexed against names
            poinames = [''] * len(self.poi_list.keys())
            for i, poikey in enumerate(self.poi_list.keys()):
                poiname = self.poi_list[poikey].get_name()
                poinames[i] = [poiname, poikey]

            # Sort names in the way that humans expect (site1, site2, site11, etc)

            # Regular expressions to make sorting key
            convert = lambda text: int(text) if text.isdigit() else text
            alphanum_key = lambda key: [convert(c) for c in re.split('([0-9]+)', key[0])]
            # Now we can sort poinames by name and return keys in that order
            return [key for [name, key] in sorted(poinames, key=alphanum_key)]

        else:
            # TODO: produce sensible error about unknown value of abc_sort.
            self.log.debug('fix TODO!')

        # TODO: Find a way to return a list of POI keys sorted in order of the POI names.

    def delete_last_position(self, poikey=None):
        """ Delete the last position in the history.

        @param string poikey: the key of the poi

        @return int: error code (0:OK, -1:error)
        """
        if poikey is not None and poikey in self.poi_list.keys():
            self.poi_list[poikey].delete_last_position()
            self.poi_list['sample'].delete_last_position()
            self.signal_poi_updated.emit()
            return 0
        else:
            self.log.error('The last position of given POI ({0}) could not be deleted.'.format(
                poikey))
            return -1

    def delete_poi(self, poikey=None):
        """ Completely deletes the whole given poi.

        @param string poikey: the key of the poi

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

        Does not delete the crosshair and sample.
        """

        if poikey is not None and poikey in self.poi_list.keys():
            if poikey is 'crosshair' or poikey is 'sample':
                self.log.warning('You cannot delete the crosshair or sample.')
                return -1
            del self.poi_list[poikey]

            # If the active poi was deleted, there is no way to automatically choose
            # another active POI, so we deactivate POI
            if self.active_poi is not None and poikey == self.active_poi.get_key():
                self._deactivate_poi()

            self.signal_poi_updated.emit()
            self.signal_poi_deleted.emit(poikey)
            return 0
        elif poikey is None:
            self.log.warning('No POI for deletion specified.')
        else:
            self.log.error('X. The given POI ({0}) does not exist.'.format(
                poikey))
            return -1

    def optimise_poi(self, poikey=None):
        """ Starts the optimisation procedure for the given poi.

        @param string poikey: the key of the poi

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

        This is threaded, so it returns directly.
        The function _refocus_done handles the data when the optimisation returns.
        """

        if poikey is not None and poikey in self.poi_list.keys():
            self.poi_list['crosshair'].add_position_to_history(position=self._confocal_logic.get_position()[:3])
            self._current_poi_key = poikey
            self._optimizer_logic.start_refocus(
                initial_pos=self.get_poi_position(poikey=poikey),
                caller_tag='poimanager')
            return 0
        else:
            self.log.error(
                'Z. The given POI ({0}) does not exist.'.format(poikey))
            return -1

    def go_to_poi(self, poikey=None):
        """ Goes to the given poi and saves it as the current one.

        @param string poikey: the key of the poi

        @return int: error code (0:OK, -1:error)
        """
        if poikey is not None and poikey in self.poi_list.keys():
            self._current_poi_key = poikey
            x, y, z = self.get_poi_position(poikey=poikey)
            self._confocal_logic.set_position('poimanager', x=x, y=y, z=z)
        else:
            self.log.error('The given POI ({0}) does not exist.'.format(
                poikey))
            return -1
        # This is now the active POI to send to save logic for naming in any saved filenames.
        self.set_active_poi(poikey)

        #Fixme: After pressing the Go to Poi button the active poi is empty and the following lines do fix this
        # The time.sleep is somehow needed if not active_poi can not be set
        time.sleep(0.001)
        self.active_poi = self.poi_list[poikey]
        self.signal_poi_updated.emit()

    def get_poi_position(self, poikey=None):
        """ Returns the current position of the given poi, calculated from the
        POI coords in sample and the current sample position.

        @param string poikey: the key of the poi

        @return
        """

        if poikey is not None and poikey in self.poi_list.keys():

            poi_coords = self.poi_list[poikey].get_coords_in_sample()
            sample_pos = self.poi_list['sample'].get_position_history()[-1, :][1:4]
            return sample_pos + poi_coords

        else:
            self.log.error('G. The given POI ({0}) does not exist.'.format(
                poikey))
            return [-1., -1., -1.]

    def set_new_position(self, poikey=None, newpos=None):
        """
        Moves the given POI to a new position, and uses this information to update
        the sample position.

        @param string poikey: the key of the poi
        @param float[3] newpos: coordinates of the new position

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

        # If no new position is given, take the current confocal crosshair position
        if newpos is None:
            newpos = self._confocal_logic.get_position()[:3]

        if poikey is not None and poikey in self.poi_list.keys():
            if len(newpos) != 3:
                self.log.error('Length of set poi is not 3.')
                return -1
            # Add new position to trace of POI
            self.poi_list[poikey].add_position_to_history(position=newpos)

            # Calculate sample shift and add it to the trace of 'sample' POI
            sample_shift = newpos - self.get_poi_position(poikey=poikey)
            sample_shift += self.poi_list['sample'].get_position_history()[-1, :][1:4]
            self.poi_list['sample'].add_position_to_history(position=sample_shift)

            # signal POI has been updated (this will cause GUI to redraw)
            if (poikey is not 'crosshair') and (poikey is not 'sample'):
                self.signal_poi_updated.emit()

            return 0

        self.log.error('J. The given POI ({0}) does not exist.'.format(poikey))
        return -1

    def move_coords(self, poikey=None, newpos=None):
        """Updates the coords of a given POI, and adds a position to the POI history,
        but DOES NOT update the sample position.
        """
        if newpos is None:
            newpos = self._confocal_logic.get_position()[:3]

        if poikey is not None and poikey in self.poi_list.keys():
            if len(newpos) != 3:
                self.log.error('Length of set poi is not 3.')
                return -1
            this_poi = self.poi_list[poikey]
            return_val = this_poi.add_position_to_history(position=newpos)

            sample_pos = self.poi_list['sample'].get_position_history()[-1, :][1:4]

            new_coords = newpos - sample_pos

            this_poi.set_coords_in_sample(new_coords)

            self.signal_poi_updated.emit()

            return return_val

        self.log.error('JJ. The given POI ({0}) does not exist.'.format(poikey))
        return -1

    def rename_poi(self, poikey=None, name=None, emit_change=True):
        """ Sets the name of the given poi.

        @param string poikey: the key of the poi
        @param string name: name of the poi to be set

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

        if poikey is not None and name is not None and poikey in self.poi_list.keys():

            success = self.poi_list[poikey].set_name(name=name)

            # if this is the active POI then we need to update poi tag in savelogic
            if self.poi_list[poikey] == self.active_poi:
                self.update_poi_tag_in_savelogic()

            if emit_change:
                self.signal_poi_updated.emit()

            return success

        else:
            self.log.error('AAAThe given POI ({0}) does not exist.'.format(
                poikey))
            return -1

    def start_periodic_refocus(self, poikey=None):
        """ Starts the perodic refocussing of the poi.

        @param float duration: (optional) the time between periodic optimization
        @param string poikey: (optional) the key of the poi to be set and refocussed on.

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

        if poikey is not None and poikey in self.poi_list.keys():
            self._current_poi_key = poikey
        else:
            # Todo: warning message that active POI used by default
            self._current_poi_key = self.active_poi.get_key()

        self.log.info('Periodic refocus on {0}.'.format(self._current_poi_key))

        self.timer_step = 0
        self.timer = QtCore.QTimer()
        self.timer.setSingleShot(False)
        self.timer.timeout.connect(self._periodic_refocus_loop)
        self.timer.start(300)

        self.signal_periodic_opt_started.emit()
        return 0

    def set_periodic_optimize_duration(self, duration=None):
        """ Change the duration of the periodic optimize timer during active
        periodic refocussing.

        @param float duration: (optional) the time between periodic optimization.
        """
        if duration is not None:
            self.timer_duration = duration
        else:
            self.log.warning('No timer duration given, using {0} s.'.format(
                self.timer_duration))

        self.signal_periodic_opt_duration_changed.emit()

    def _periodic_refocus_loop(self):
        """ This is the looped function that does the actual periodic refocus.

        If the time has run out, it refocussed the current poi.
        Otherwise it just updates the time that is left.
        """
        self.time_left = self.timer_step - time.time() + self.timer_duration
        self.signal_timer_updated.emit()
        if self.time_left <= 0:
            self.timer_step = time.time()
            self.optimise_poi(poikey=self._current_poi_key)

    def stop_periodic_refocus(self):
        """ Stops the perodic refocussing of the poi.

        @return int: error code (0:OK, -1:error)
        """
        if self.timer is None:
            self.log.warning('No timer to stop.')
            return -1
        self.timer.stop()
        self.timer = None

        self.signal_periodic_opt_stopped.emit()
        return 0

    def _refocus_done(self, caller_tag, optimal_pos):
        """ Gets called automatically after the refocus is done and saves the new position
        to the poi history.

        Also it tracks the sample and may go back to the crosshair.

        @return int: error code (0:OK, -1:error)
        """
        # We only need x, y, z
        optimized_position = optimal_pos[0:3]

        # If the refocus was on the crosshair, then only update crosshair POI and don't
        # do anything with sample position.
        caller_tags = ['confocalgui', 'magnet_logic', 'singleshot_logic']
        if caller_tag in caller_tags:
            self.poi_list['crosshair'].add_position_to_history(position=optimized_position)

        # If the refocus was initiated here by poimanager, then update POI and sample
        elif caller_tag == 'poimanager':

            if self._current_poi_key is not None and self._current_poi_key in self.poi_list.keys():

                self.set_new_position(poikey=self._current_poi_key, newpos=optimized_position)

                if self.go_to_crosshair_after_refocus:
                    temp_key = self._current_poi_key
                    self.go_to_poi(poikey='crosshair')
                    self._current_poi_key = temp_key
                else:
                    self.go_to_poi(poikey=self._current_poi_key)
                return 0
            else:
                self.log.error('The given POI ({0}) does not exist.'.format(
                    self._current_poi_key))
                return -1

        else:
            self.log.warning("Unknown caller_tag for the optimizer. POI "
                             "Manager does not know what to do with optimized "
                             "position, and has done nothing.")

    def reset_roi(self):

        del self.poi_list
        self.poi_list = dict()

        self.active_poi = None

        self.roi_name = ''

        # initally add crosshair to the pois
        crosshair = PoI(pos=[0, 0, 0], name='crosshair')
        crosshair._key = 'crosshair'
        self.poi_list[crosshair._key] = crosshair

        # Re-initialise sample in the poi list
        sample = PoI(pos=[0, 0, 0], name='sample')
        sample._key = 'sample'
        self.poi_list[sample._key] = sample

        self.signal_poi_updated.emit()

    def set_active_poi(self, poikey=None):
        """
        Set the active POI object.
        """

        if poikey is None:
            # If poikey is none and no active poi is set, then do nothing
            if self.active_poi is None:
                return
            else:
                self.active_poi = None

        elif poikey in self.get_all_pois():
            # If poikey is the current active POI then do nothing
            if self.poi_list[poikey] == self.active_poi:
                return

            else:
                self.active_poi = self.poi_list[poikey]

        else:
            # todo: error poikey unknown
            return -1

        self.update_poi_tag_in_savelogic()
        self.signal_poi_updated.emit()  # todo: this breaks the emit_change = false case

    def _deactivate_poi(self):
        self.set_active_poi(poikey=None)

    def update_poi_tag_in_savelogic(self):

        if self.active_poi is not None:
            self._save_logic.active_poi_name = self.active_poi.get_name()
        else:
            self._save_logic.active_poi_name = ''

    def save_poi_map_as_roi(self):
        """ Save a list of POIs with their coordinates to a file.
        """
        # File path and name
        filepath = self._save_logic.get_path_for_module(module_name='ROIs')

        # We will fill the data OderedDict to send to savelogic
        data = OrderedDict()

        # Lists for each column of the output file
        poinames = []
        poikeys = []
        x_coords = []
        y_coords = []
        z_coords = []

        for poikey in self.get_all_pois(abc_sort=True):
            if poikey is not 'sample' and poikey is not 'crosshair':
                thispoi = self.poi_list[poikey]

                poinames.append(thispoi.get_name())
                poikeys.append(poikey)
                x_coords.append(thispoi.get_coords_in_sample()[0])
                y_coords.append(thispoi.get_coords_in_sample()[1])
                z_coords.append(thispoi.get_coords_in_sample()[2])

        data['POI Name'] = np.array(poinames)
        data['POI Key'] = np.array(poikeys)
        data['X'] = np.array(x_coords)
        data['Y'] = np.array(y_coords)
        data['Z'] = np.array(z_coords)

        self._save_logic.save_data(
            data,
            filepath=filepath,
            filelabel=self.roi_name,
            fmt=['%s', '%s', '%.6e', '%.6e', '%.6e']
        )

        self.log.debug('ROI saved to:\n{0}'.format(filepath))
        return 0

    def load_roi_from_file(self, filename=None):

        if filename is None:
            return -1

        with open(filename, 'r') as roifile:
            for line in roifile:
                if line[0] != '#' and line.split()[0] != 'NaN':
                    saved_poi_name = line.split()[0]
                    saved_poi_key = line.split()[1]
                    saved_poi_coords = [
                        float(line.split()[2]), float(line.split()[3]), float(line.split()[4])]

                    this_poi_key = self.add_poi(
                        position=saved_poi_coords,
                        key=saved_poi_key,
                        emit_change=False)
                    self.rename_poi(poikey=this_poi_key, name=saved_poi_name, emit_change=False)

            # Now that all the POIs are created, emit the signal for other things (ie gui) to update
            self.signal_poi_updated.emit()
        return 0

    @poi_list.constructor
    def dict_to_poi_list(self, val):
        pdict = {}
        # initially add crosshair to the pois
        crosshair = PoI(pos=[0, 0, 0], name='crosshair')
        crosshair._key = 'crosshair'
        pdict[crosshair._key] = crosshair

        # initally add sample to the pois
        sample = PoI(pos=[0, 0, 0], name='sample')
        sample._key = 'sample'
        pdict[sample._key] = sample

        if isinstance(val, dict):
            for key, poidict in val.items():
                try:
                    if len(poidict['pos']) >= 3:
                        newpoi = PoI(name=poidict['name'], key=poidict['key'])
                        newpoi.set_coords_in_sample(poidict['pos'])
                        newpoi._creation_time = poidict['time']
                        newpoi._position_time_trace = poidict['history']
                        pdict[key] = newpoi
                except Exception as e:
                    self.log.exception('Could not load PoI {0}: {1}'.format(key, poidict))
        return pdict

    @poi_list.representer
    def poi_list_to_dict(self, val):
        pdict = {
            key: poi.to_dict() for key, poi in val.items()
        }
        return pdict

    @active_poi.representer
    def active_poi_to_dict(self, val):
        if isinstance(val, PoI):
            return val.to_dict()
        return None

    @active_poi.constructor
    def dict_to_active_poi(self, val):
        try:
            if isinstance(val, dict):
                if len(val['pos']) >= 3:
                    newpoi = PoI(pos=val['pos'], name=val['name'], key=val['key'])
                    newpoi._creation_time = val['time']
                    newpoi._position_time_trace = val['history']
                    return newpoi
        except Exception as e:
            self.log.exception('Could not load active poi {0}'.format(val))
            return None

    def triangulate(self, r, a1, b1, c1, a2, b2, c2):
        """ Reorients a coordinate r that is known relative to reference points a1, b1, c1 to
            produce a new vector rnew that has exactly the same relation to rotated/shifted/tilted
            reference positions a2, b2, c2.

            @param np.array r: position to be remapped.

            @param np.array a1: initial location of ref1.

            @param np.array a2: final location of ref1.

            @param np.array b1, b2, c1, c2: similar for ref2 and ref3
        """

        ab_old = b1 - a1
        ac_old = c1 - a1

        ab_new = b2 - a2
        ac_new = c2 - a2

        # Firstly, find the angle to rotate ab_old onto ab_new.  This rotation must be done in
        # the plane that contains these two vectors, which means rotating about an axis
        # perpendicular to both of them (the cross product).

        axis1 = np.cross(ab_old, ab_new)  # Only works if ab_old and ab_new are not parallel
        axis1length = np.sqrt((axis1 * axis1).sum())

        if axis1length == 0:
            ab_olddif = ab_old + np.array([100, 0, 0])
            axis1 = np.cross(ab_old, ab_olddif)

        # normalising the axis1 vector
        axis1 = axis1 / np.sqrt((axis1 * axis1).sum())

        # The dot product gives the angle between ab_old and ab_new
        dot = np.dot(ab_old, ab_new)
        x_modulus = np.sqrt((ab_old * ab_old).sum())
        y_modulus = np.sqrt((ab_new * ab_new).sum())

        # float errors can cause the division to be slightly above 1 for 90 degree rotations, which
        # will confuse arccos.
        cos_angle = min(dot / x_modulus / y_modulus, 1)

        angle1 = np.arccos(cos_angle)  # angle in radians

        # Construct a rotational matrix for axis1
        n1 = axis1[0]
        n2 = axis1[1]
        n3 = axis1[2]

        m1 = np.matrix(((((n1 * n1) * (1 - np.cos(angle1)) + np.cos(angle1)),
                         ((n1 * n2) * (1 - np.cos(angle1)) - n3 * np.sin(angle1)),
                         ((n1 * n3) * (1 - np.cos(angle1)) + n2 * np.sin(angle1))
                         ),
                        (((n2 * n1) * (1 - np.cos(angle1)) + n3 * np.sin(angle1)),
                         ((n2 * n2) * (1 - np.cos(angle1)) + np.cos(angle1)),
                         ((n2 * n3) * (1 - np.cos(angle1)) - n1 * np.sin(angle1))
                         ),
                        (((n3 * n1) * (1 - np.cos(angle1)) - n2 * np.sin(angle1)),
                         ((n3 * n2) * (1 - np.cos(angle1)) + n1 * np.sin(angle1)),
                         ((n3 * n3) * (1 - np.cos(angle1)) + np.cos(angle1))
                         )
                        )
                       )

        # Now that ab_old can be rotated to overlap with ab_new, we need to rotate in another
        # axis to fix "tilt".  By choosing ab_new as the rotation axis we ensure that the
        # ab vectors stay where they need to be.

        # ac_old_rot is the rotated ac_old (around axis1).  We need to find the angle to rotate
        # ac_old_rot around ab_new to get ac_new.
        ac_old_rot = np.array(np.dot(m1, ac_old))[0]

        axis2 = -ab_new  # TODO: check maths to find why this negative sign is necessary.  Empirically it is now working.
        axis2 = axis2 / np.sqrt((axis2 * axis2).sum())

        # To get the angle of rotation it is most convenient to work in the plane for which axis2 is the normal.
        # We must project vectors ac_old_rot and ac_new into this plane.
        a = ac_old_rot - np.dot(ac_old_rot, axis2) * axis2  # projection of ac_old_rot in the plane of rotation about axis2
        b = ac_new - np.dot(ac_new, axis2) * axis2  # projection of ac_new in the plane of rotation about axis2

        # The dot product gives the angle of rotation around axis2
        dot = np.dot(a, b)

        x_modulus = np.sqrt((a * a).sum())
        y_modulus = np.sqrt((b * b).sum())
        cos_angle = min(dot / x_modulus / y_modulus, 1)  # float errors can cause the division to be slightly above 1 for 90 degree rotations, which will confuse arccos.
        angle2 = np.arccos(cos_angle)  # angle in radians

        # Construct a rotation matrix around axis2
        n1 = axis2[0]
        n2 = axis2[1]
        n3 = axis2[2]

        m2 = np.matrix(((((n1 * n1) * (1 - np.cos(angle2)) + np.cos(angle2)),
                         ((n1 * n2) * (1 - np.cos(angle2)) - n3 * np.sin(angle2)),
                         ((n1 * n3) * (1 - np.cos(angle2)) + n2 * np.sin(angle2))
                         ),
                        (((n2 * n1) * (1 - np.cos(angle2)) + n3 * np.sin(angle2)),
                         ((n2 * n2) * (1 - np.cos(angle2)) + np.cos(angle2)),
                         ((n2 * n3) * (1 - np.cos(angle2)) - n1 * np.sin(angle2))
                         ),
                        (((n3 * n1) * (1 - np.cos(angle2)) - n2 * np.sin(angle2)),
                         ((n3 * n2) * (1 - np.cos(angle2)) + n1 * np.sin(angle2)),
                         ((n3 * n3) * (1 - np.cos(angle2)) + np.cos(angle2))
                         )
                        )
                       )

        # To find the new position of r, displace by (a2 - a1) and do the rotations
        a1r = r - a1

        rnew = a2 + np.array(np.dot(m2, np.array(np.dot(m1, a1r))[0]))[0]

        return rnew

    def reorient_roi(self, ref1_coords, ref2_coords, ref3_coords, ref1_newpos, ref2_newpos, ref3_newpos):
        """ Move and rotate the ROI to a new position specified by the newpos of 3 reference POIs from the saved ROI.

        @param ref1_coords: coordinates (from ROI save file) of reference 1.

        @param ref2_coords: similar, ref2.

        @param ref3_coords: similar, ref3.

        @param ref1_newpos: the new (current) position of POI reference 1.

        @param ref2_newpos: similar, ref2.

        @param ref3_newpos: similar, ref3.
        """

        for poikey in self.get_all_pois(abc_sort=True):
            if poikey is not 'sample' and poikey is not 'crosshair':
                thispoi = self.poi_list[poikey]

                old_coords = thispoi.get_coords_in_sample()

                new_coords = self.triangulate(old_coords, ref1_coords, ref2_coords, ref3_coords, ref1_newpos, ref2_newpos, ref3_newpos)

                self.move_coords(poikey=poikey, newpos=new_coords)

    def autofind_pois(self, neighborhood_size=1, min_threshold=10000, max_threshold=1e6):
        """Automatically search the xy scan image for POIs.

        @param neighborhood_size: size in microns.  Only the brightest POI per neighborhood will be found.

        @param min_threshold: POIs must have c/s above this threshold.

        @param max_threshold: POIs must have c/s below this threshold.
        """

        # Calculate the neighborhood size in pixels from the image range and resolution
        x_range_microns = np.max(self.roi_map_data[:, :, 0]) - np.min(self.roi_map_data[:, :, 0])
        y_range_microns = np.max(self.roi_map_data[:, :, 1]) - np.min(self.roi_map_data[:, :, 1])
        y_pixels = len(self.roi_map_data)
        x_pixels = len(self.roi_map_data[1, :])

        pixels_per_micron = np.max([x_pixels, y_pixels]) / np.max([x_range_microns, y_range_microns])
        # The neighborhood in pixels is nbhd_size * pixels_per_um, but it must be 1 or greater
        neighborhood_pix = int(np.max([math.ceil(pixels_per_micron * neighborhood_size), 1]))

        data = self.roi_map_data[:, :, 3]

        data_max = filters.maximum_filter(data, neighborhood_pix)
        maxima = (data == data_max)
        data_min = filters.minimum_filter(data, 3 * neighborhood_pix)
        diff = ((data_max - data_min) > min_threshold)
        maxima[diff is False] = 0

        labeled, num_objects = ndimage.label(maxima)
        xy = np.array(ndimage.center_of_mass(data, labeled, range(1, num_objects + 1)))

        for count, pix_pos in enumerate(xy):
            poi_pos = self.roi_map_data[pix_pos[0], pix_pos[1], :][0:3]
            this_poi_key = self.add_poi(position=poi_pos, emit_change=False)
            self.rename_poi(poikey=this_poi_key, name='spot' + str(count), emit_change=False)

        # Now that all the POIs are created, emit the signal for other things (ie gui) to update
        self.signal_poi_updated.emit()
コード例 #30
0
class MagnetMotorInterfuse(GenericLogic, MagnetInterface):

    _modclass = 'MagnetMotorInterfuse'
    _modtype = 'interfuse'

    # declare connectors, here you can see the interfuse action: the in
    # connector will cope a motor hardware, that means a motor device can
    # connect to the in connector of the logic.
    motorstage = Connector(interface='MotorInterface')

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

        # save the idle state in this class variable, since that is not present
        # in the actual motor hardware device. Use this variable to decide
        # whether movement commands are passed to the hardware.
        self._magnet_idle = False

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

        self._motor_device = self.motorstage()

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

    def get_constraints(self):
        """ Retrieve the hardware constrains from the magnet driving device.

        @return dict: dict with constraints for the magnet hardware. These
                      constraints will be passed via the logic to the GUI so
                      that proper display elements with boundary conditions
                      could be made.
        """
        return self._motor_device.get_constraints()

    def move_rel(self, param_dict):
        """ Moves stage in given direction (relative movement)

        @param dict param_dict: dictionary, which passes all the relevant
                                parameters, which should be changed. Usage:
                                 {'axis_label': <the-abs-pos-value>}.
                                 'axis_label' must correspond to a label given
                                 to one of the axis.

        A smart idea would be to ask the position after the movement.
        """

        if not self._magnet_idle:
            self._motor_device.move_rel(param_dict)
        else:
            self.log.warning(
                'Motor Device is in Idle state and cannot '
                'perform "move_rel" commands. Couple the Motor to '
                'control via the command "set_magnet_idle_state(False)" '
                'to have control over its movement.')
        return param_dict

    def move_abs(self, param_dict):
        """ Moves stage to absolute position (absolute movement)

        @param dict param_dict: dictionary, which passes all the relevant
                                parameters, which should be changed. Usage:
                                 {'axis_label': <the-abs-pos-value>}.
                                 'axis_label' must correspond to a label given
                                 to one of the axis.
        """
        if not self._magnet_idle:
            self._motor_device.move_abs(param_dict)
        else:
            self.log.warning(
                'Motor Device is in Idle state and cannot '
                'perform "move_abs" commands. Couple the Motor to '
                'control via the command "set_magnet_idle_state (False)" '
                'to have control over its movement.')
        return param_dict

    def abort(self):
        """ Stops movement of the stage

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

    def get_pos(self, param_list=None):
        """ Gets current position of the stage

        @param list param_list: optional, if a specific position of an axis
                                is desired, then the labels of the needed
                                axis should be passed in the param_list.
                                If nothing is passed, then from each axis the
                                position is asked.

        @return dict: with keys being the axis labels and item the current
                      position.
        """
        return self._motor_device.get_pos(param_list)

    def get_status(self, param_list=None):
        """ Get the status of the position

        @param list param_list: optional, if a specific status of an axis
                                is desired, then the labels of the needed
                                axis should be passed in the param_list.
                                If nothing is passed, then from each axis the
                                status is asked.

        @return dict: with the axis label as key and the status number as item.
        """
        return self._motor_device.get_status(param_list)

    def calibrate(self, param_list=None):
        """ Calibrates the stage.

        @param dict param_list: param_list: optional, if a specific calibration
                                of an axis is desired, then the labels of the
                                needed axis should be passed in the param_list.
                                If nothing is passed, then all connected axis
                                will be calibrated.

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

        After calibration the stage moves to home position which will be the
        zero point for the passed axis. The calibration procedure will be
        different for each stage.
        """
        if not self._magnet_idle:
            self._motor_device.calibrate(param_list)
        else:
            self.log.warning(
                'Motor Device is in Idle state and cannot '
                'perform "calibrate" commands. Couple the Motor to '
                'control via the command "set_magnet_idle_state(False)" '
                'to have control over its movement.')

    def get_velocity(self, param_list=None):
        """ Gets the current velocity for all connected axes.

        @param dict param_list: optional, if a specific velocity of an axis
                                is desired, then the labels of the needed
                                axis should be passed as the param_list.
                                If nothing is passed, then from each axis the
                                velocity is asked.

        @return dict: with the axis label as key and the velocity as item.
        """
        return self._motor_device.get_velocity(param_list)

    def set_velocity(self, param_dict=None):
        """ Write new value for velocity.

        @param dict param_dict: dictionary, which passes all the relevant
                                parameters, which should be changed. Usage:
                                 {'axis_label': <the-velocity-value>}.
                                 'axis_label' must correspond to a label given
                                 to one of the axis.

        @return int: error code (0:OK, -1:error)
        """
        if not self._magnet_idle:
            self._motor_device.set_velocity(param_dict)
        else:
            self.log.warning(
                'Motor Device is in Idle state and cannot '
                'perform "set_velocity" commands. Couple the Motor to '
                'control via the command "set_magnet_idle_state(False)" '
                'to have control over its movement.')
        return param_dict

    def tell(self, param_dict=None):
        """ Send a command to the magnet.

        @param dict param_dict: dictionary, which passes all the relevant
                                parameters, which should be changed. Usage:
                                 {'axis_label': <the command string>}.
                                 'axis_label' must correspond to a label given
                                 to one of the axis.

        @return int: error code (0:OK, -1:error)
        """
        self.log.info('You can tell the motor dummy as much as you want, it '
                      'has always an open ear for you. But do not expect an '
                      'answer, it is very shy!')
        return param_dict

    def ask(self, param_dict=None):
        """ Ask the magnet a question.

        @param dict param_dict: dictionary, which passes all the relevant
                                parameters, which should be changed. Usage:
                                 {'axis_label': <the question string>}.
                                 'axis_label' must correspond to a label given
                                 to one of the axis.

        @return dict: contains the answer to the specific axis coming from the
                      magnet. Keywords are the axis names, item names are the
                      string answers of the axis.
        """

        self.log.info(
            'The Motor Hardware does not support an "ask" command '
            'and is not be able to answer the questions "{0}" to the '
            'axis "{1}"! If you want to talk to someone ask Siri, maybe '
            'she will listen to you and answer your questions '
            ':P.'.format(list(param_dict.values()), list(param_dict)))

        return_val = {}
        for entry in param_dict:
            return_val[entry] = 'Nothing to say, Motor is quite.'

        return return_val

    def initialize(self):
        """
        Acts as a switch. When all coils of the superconducting magnet are
        heated it cools them, else the coils get heated.
        @return int: (0: Ok, -1:error)
        """
        self.log.info('Motor Hardware does not need initialization for '
                      'starting or ending a movement. Nothing will happen.')
        return 0

    def set_magnet_idle_state(self, magnet_idle=True):
        """ Set the magnet to couple/decouple to/from the control.

        @param bool magnet_idle: if True then magnet will be set to idle and
                                 each movement command will be ignored from the
                                 hardware file. If False the magnet will react
                                 on movement changes of any kind.

        @return bool: the actual state which was set in the magnet hardware.
                        True = idle, decoupled from control
                        False = Not Idle, coupled to control
        """

        self._magnet_idle = magnet_idle
        return self._magnet_idle

    def get_magnet_idle_state(self):
        """ Retrieve the current state of the magnet, whether it is idle or not.

        @return bool: the actual state which was set in the magnet hardware.
                        True = idle, decoupled from control
                        False = Not Idle, coupled to control
        """

        return self._magnet_idle