예제 #1
0
class WavemeterArtiqUpdater:
    """
    Simple loop to push new values from the wavemeter server to ARTIQ datasets. Subscribes to a list of channels and
    appends their name to a common prefix to determine the target dataset.

    :param channels: list of channels to handle entries can be of the form <n>, 'ch<n>', 'T', or 'p'
    :param host_artiq: host running the ARTIQ master
    :param port_artiq: port of the ARTIQ master's RPC server
    :param host_wavemeter_pub: host of the wavemeter publisher
    :param port_wavemeter_pub: port of the wavemeter publisher
    :param dataset_name_prefix: prefix for the target dataset names
    :param event_loop: asyncio event loop for the subscribers (defaults to asyncio.get_event_loop())
    """
    def __init__(self, channels: List[Any], host_artiq: str = "::1", port_artiq: int = 3251,
                 host_wavemeter_pub: str = "::1", port_wavemeter_pub: int = 3281,
                 dataset_name_prefix: str = "wavemeter.", event_loop: Any = None):

        self._rpc_client = None
        self._loop = asyncio.get_event_loop() if event_loop is None else event_loop

        self.channels = []
        for ch in channels:
            try:  # accept integer (or string lacking the "ch" prefix) as channel argument
                self.channels.append("ch{}".format(int(ch)))
            except ValueError:
                self.channels.append(ch)

        self.channels = list(set(self.channels))  # remove duplicates

        self.host_artiq = host_artiq
        self.port_artiq = port_artiq
        self.host_wavemeter_pub = host_wavemeter_pub
        self.port_wavemeter_pub = port_wavemeter_pub

        self._wavemeter_clients = []

        self.dataset_name_prefix = dataset_name_prefix

    def run(self):
        self._rpc_client = Client(self.host_artiq, self.port_artiq, "master_dataset_db")

        def callback_factory(client, dataset):
            def callback():
                self._rpc_client.set(dataset, client.value)
            return callback

        for channel in self.channels:
            client = WavemeterClient(channel=channel, host=self.host_wavemeter_pub, port=self.port_wavemeter_pub,
                                     event_loop=self._loop)

            client._new_value_callback = callback_factory(client, self.dataset_name_prefix + channel)

            self._wavemeter_clients.append(client)

        try:
            self._loop.run_forever()
        finally:
            self._rpc_client.close_rpc()
            for cl in self._wavemeter_clients:
                cl.close_subscriber()
예제 #2
0
class PMTControlDock(QtWidgets.QDockWidget):
    def __init__(self, acxn):
        QtWidgets.QDockWidget.__init__(self, "Manual Controls")
        self.setObjectName("pmt_control")
        self.setFeatures(QtWidgets.QDockWidget.DockWidgetMovable |
                         QtWidgets.QDockWidget.DockWidgetFloatable)
        self.pv = None
        self.pm = None
        self.bb = None
        self.acxn = acxn
        self.setup_listeners()

        self.dset_ctl = Client("::1", 3251, "master_dataset_db")
        self.scheduler = Client("::1", 3251, "master_schedule")
        self.dataset_db = Client("::1", 3251, "master_dataset_db")
        self.rid = None
        self.pulsed = False
        self.expid_continuous = {
            "arguments": {},
            "class_name": "pmt_collect_continuously",
            "file": "run_continuously/run_pmt_continuously.py",
            "log_level": 30,
            "repo_rev": None,
            "priority": 0
        }

        self.expid_pulsed = {
            "arguments": {},
            "class_name": "pmt_collect_pulsed",
            "file": "run_continuously/run_pmt_pulsed.py",
            "log_level": 30,
            "repo_rev": None,
            "priority": 0
        }

        self.expid_ttl = {
            "class_name": "change_ttl",
            "file": "misc/manual_ttl_control.py",
            "log_level": 30,
            "repo_rev": None,
            "priority": 1
        }

        self.expid_dds = {
            "arguments": {},
            "class_name": "change_cw",
            "file": "misc/manual_dds_control.py",
            "log_level": 30,
            "repo_rev": None,
            "priority": 1
        }

        self.expid_dc = {
            "arguments": {},
            "class_name": "set_dopplercooling_and_statereadout",
            "file": "misc/set_dopplercooling_and_statereadout.py",
            "log_level": 30,
            "repo_rev": None,
            "priority": 2
        }

        frame = QtWidgets.QFrame()
        layout = QtWidgets.QVBoxLayout()
        pmt_frame = self.create_pmt_frame()
        linetrigger_frame = self.create_linetrigger_frame()
        dds_frame = self.create_dds_frame()
        picomotor_frame = self.create_picomotor_frame()
        layout.addWidget(pmt_frame)
        layout.addWidget(dds_frame)
        layout.addWidget(linetrigger_frame)
        layout.addWidget(picomotor_frame)
        layout.setSpacing(50)
        layout.setContentsMargins(0, 50, 0, 50)
        frame.setLayout(layout)

        scroll = QtWidgets.QScrollArea()
        scroll.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
        scroll.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
        scroll.setWidgetResizable(False)
        scroll.setWidget(frame)
        scroll.setAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter)
        scroll.setSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding,
                             QtWidgets.QSizePolicy.MinimumExpanding)
        self.setWidget(scroll)

        self.connect_servers()

    def create_pmt_frame(self):
        pmtLabel = boldLabel("PMT")
        self.onButton = QtWidgets.QPushButton("On")
        self.onButton.setCheckable(True)
        self.onButton.clicked[bool].connect(self.set_state)
        self.setDCButton = QtWidgets.QPushButton("set")
        self.setDCButton.clicked.connect(self.set_dc_and_state_readout)
        self.clearPMTPlotButton = QtWidgets.QPushButton("clear")
        self.clearPMTPlotButton.clicked.connect(self.clear_pmt_plot)
        self.autoLoadButton = QtWidgets.QPushButton("On")
        self.autoLoadButton.setCheckable(True)
        self.autoLoadButton.clicked[bool].connect(self.toggle_autoload)
        self.autoLoadSpin = customIntSpinBox(0, (0, 1000000))
        self.autoLoadCurrentSpin = customSpinBox(0, (0, 10), " A")
        self.countDisplay = QtWidgets.QLCDNumber()
        self.countDisplay.setSegmentStyle(2)
        self.countDisplay.display(0)
        self.timer = QtCore.QTimer()
        self.timer.timeout.connect(self.updateLCD)
        self.timer.start(250)
        self.countDisplay.setStyleSheet("background-color: lightGray;"
                                        "color: green;")
        self.unitsLabel = QtWidgets.QLabel("kcounts / s")

        self.durationLabel = QtWidgets.QLabel("Duration (ms): ")
        self.duration = QtWidgets.QLineEdit("100")
        try:
            with labrad.connection() as cxn:
                p = cxn.parametervault
                p.set_parameter(["PmtReadout", "duration", U(100, "ms")])
        except:
            logger.error("Failed to initially connect to labrad.")
            self.duration.setDisabled(True)
        validator = QtGui.QDoubleValidator()
        self.duration.setValidator(validator)
        self.duration.returnPressed.connect(self.duration_changed)
        self.duration.setStyleSheet("QLineEdit { background-color:  #c4df9b}" )
        self.modeLabel = QtWidgets.QLabel("Mode: ")
        self.setMode = customComboBox(["continuous", "pulsed"])
        self.setMode.currentIndexChanged.connect(self.set_mode)
        layout = QtWidgets.QGridLayout()
        frame = QtWidgets.QFrame()
        frame.setFrameStyle(QtWidgets.QFrame.Panel | QtWidgets.QFrame.Sunken)
        frame.setLineWidth(2)
        frame.setMidLineWidth(3)
        layout.addWidget(pmtLabel, 0, 0, 1, 3)
        layout.addWidget(self.onButton, 1, 0)
        layout.addWidget(self.countDisplay, 1, 1)
        layout.addWidget(self.unitsLabel, 1, 2)
        layout.addWidget(self.durationLabel, 2, 0)
        layout.addWidget(self.duration, 2, 1, 1, 2)
        layout.addWidget(self.modeLabel, 3, 0)
        layout.addWidget(self.setMode, 3, 1, 1, 2)
        layout.addWidget(QtWidgets.QLabel("Autoload: "), 4, 0)
        layout.addWidget(self.autoLoadButton, 4, 1)
        layout.addWidget(self.autoLoadSpin, 4, 2)
        layout.addWidget(QtWidgets.QLabel("Current: "), 5, 0)
        layout.addWidget(self.autoLoadCurrentSpin, 5, 1)
        dcLabel = QtWidgets.QLabel("Set Doppler cooling and state readout: ")
        layout.addWidget(dcLabel, 6, 0, 1, 2)
        layout.addWidget(self.setDCButton, 6, 2)
        # clearLabel = QtWidgets.QLabel("Reset PMT plot: ")
        # layout.addWidget(clearLabel, 7, 0)
        # layout.addWidget(self.clearPMTPlotButton, 7, 1, 1, 2)
        frame.setLayout(layout)
        return frame

    def create_linetrigger_frame(self):
        linetriggerLabel = boldLabel("LINETRIGGER")
        self.linetriggerButton = QtWidgets.QPushButton("Off")
        self.linetriggerButton.setCheckable(True)
        self.linetriggerButton.setChecked(True)
        self.linetriggerButton.clicked[bool].connect(self.toggle_linetrigger)
        self.linetriggerLineEdit = QtWidgets.QLineEdit("0")
        self.linetriggerLineEdit.returnPressed.connect(self.linetrigger_duration_changed)
        layout = QtWidgets.QGridLayout()
        frame = QtWidgets.QFrame()
        frame.setFrameStyle(QtWidgets.QFrame.Panel | QtWidgets.QFrame.Sunken)
        frame.setLineWidth(2)
        frame.setMidLineWidth(3)
        layout.addWidget(linetriggerLabel, 0, 0, 1, 3)
        layout.addWidget(self.linetriggerButton, 1, 0, 1, 3)
        layout.addWidget(QtWidgets.QLabel("Offset duration (us): "), 2, 0)
        layout.addWidget(self.linetriggerLineEdit, 2, 1, 1, 2)
        frame.setLayout(layout)
        return frame

    def create_picomotor_frame(self):
        layout = QtWidgets.QGridLayout()
        frame = QtWidgets.QFrame()
        frame.setFrameStyle(QtWidgets.QFrame.Panel | QtWidgets.QFrame.Sunken)
        frame.setLineWidth(2)
        frame.setMidLineWidth(3)
        piezoLabel = boldLabel("PICOMOTOR")
        ctls = ["Local X", "Local Y", "Global X", "Global Y"]
        self.piezoStepSize = dict()
        self.piezoCurrentPos = dict()
        self.piezoLastPos = dict()
        layout.addWidget(piezoLabel, 0, 0, 1, 3)
        for i, ctl in enumerate(ctls):
            layout.addWidget(QtWidgets.QLabel(ctl + ": "), i + 1, 0)
            self.piezoStepSize[ctl] = customIntSpinBox(0, (0, 300))
            self.piezoStepSize[ctl].setToolTip("Set step size.")
            self.piezoStepSize[ctl].setObjectName(ctl)
            self.piezoStepSize[ctl].setKeyboardTracking(False)
            self.piezoStepSize[ctl].valueChanged.connect(self.piezo_step_size_changed)
            self.piezoStepSize[ctl].setRange(0, 300)
            layout.addWidget(self.piezoStepSize[ctl], i + 1, 1)
            self.piezoCurrentPos[ctl] = QtWidgets.QSpinBox()
            self.piezoCurrentPos[ctl].setSingleStep(0)
            self.piezoCurrentPos[ctl].setToolTip("Current position.")
            self.piezoCurrentPos[ctl].setRange(-100000, 100000)
            self.piezoCurrentPos[ctl].setObjectName(str(i + 1))
            self.piezoLastPos[i + 1] = 0
            self.piezoCurrentPos[ctl].setKeyboardTracking(False)
            self.piezoCurrentPos[ctl].valueChanged.connect(self.piezo_changed)
            layout.addWidget(self.piezoCurrentPos[ctl], i + 1, 2)
        frame.setLayout(layout)
        return frame

    def create_dds_frame(self):
        layout = QtWidgets.QGridLayout()
        frame = QtWidgets.QFrame()
        frame.setFrameStyle(QtWidgets.QFrame.Panel | QtWidgets.QFrame.Sunken)
        frame.setLineWidth(2)
        frame.setMidLineWidth(3)
        ddsLabel = boldLabel("DDS Control")
        layout.addWidget(ddsLabel, 0, 0, 1, 2)
        home_dir = os.path.expanduser("~")
        dir_ = os.path.join(home_dir, "artiq-master/HardwareConfiguration.py")
        settings = run_path(dir_)
        dds_dict = settings["dds_config"]
        self.all_dds_specs = dict()
        self.dds_widgets = dict()

        try:
            cxn = labrad.connect()
            p = cxn.parametervault
            for (name, specs) in dds_dict.items():
                try:
                    params = p.get_parameter(["dds_cw_parameters", name])
                    freq, amp, state, att = params[1]
                except:
                    freq = str(specs.default_freq)
                    att = str(specs.default_att)
                    amp = str(1.)
                    state = str(0)
                    p.new_parameter("dds_cw_parameters", name,
                                    ("cw_settings", [freq, amp, state, att]))
                self.all_dds_specs[name] = {"cpld": int(specs.urukul),
                                            "frequency": float(freq) * 1e6,
                                            "att": float(att),
                                            "state": bool(int(state)),
                                            "amplitude": float(amp)}
            for i, (name, specs) in enumerate(sorted(dds_dict.items())):
                widget = ddsControlWidget(name, specs, self.scheduler, self)
                layout.addWidget(widget, i // 2 + 1 , i % 2)
                self.dds_widgets[name] = widget
            frame.setLayout(layout)
            cxn.disconnect()
        except:
            logger.error("Failed to initially connect to labrad.")

        return frame

    def set_state(self, override=False):
        if override:
            flag = True
        else:
            flag = self.onButton.isChecked()

        if flag:
            if self.rid is None:
                if self.setMode.currentText() == "continuous":
                    self.rid = self.scheduler.submit("main", self.expid_continuous, 0)
                else:
                    self.rid = self.scheduler.submit("main", self.expid_pulsed, 0)
            self.onButton.setText("Off")

        else:
            if self.rid is None:
                return  # Shouldn't happen
            else:
                self.scheduler.request_termination(self.rid)
                self.rid = None
            self.onButton.setText("On")

    def set_dc_and_state_readout(self):
        self.scheduler.submit("main", self.expid_dc, 2)

    def clear_pmt_plot(self):
        self.dataset_db.set("clear_pmt_plot", True)

    @inlineCallbacks
    def duration_changed(self, *args, **kwargs):
        # connect to parametervault here
        if self.pv is None:
            self.pv = yield self.acxn.get_server("ParameterVault")
        sender = self.sender()
        validator = sender.validator()
        state = validator.validate(sender.text(), 0)[0]
        if state == QtGui.QValidator.Acceptable:
            color = "#c4df9b" # green
        elif state == QtGui.QValidator.Intermediate:
            color = "#fff79a" # yellow
        else:
            color = "#f6989d" # red
        sender.setStyleSheet("QLineEdit { background-color: %s }" %color)
        try:
            min = 1e-3 # 1 us
            raw_duration = float(sender.text())
            duration = raw_duration if raw_duration >= min else min
            yield self.pv.set_parameter(["PmtReadout", "duration", U(duration, "ms")])
            a = yield self.pv.get_parameter(["PmtReadout", "duration"])
            if self.rid is None:
                return
            else:
                self.scheduler.request_termination(self.rid)
                self.rid = None
                self.set_state(True)
        except ValueError:
            # Shouldn't happen
            yield print("")
            logger.warning("Problem trying to update duration", exc_info=True)

    def set_mode(self):
        txt = str(self.setMode.currentText())
        if self.rid is None:
            self.pulsed = txt == "pulsed"
        elif  txt == "continuous":
            if not self.pulsed:
                return
            else:
                self.pulsed = False
                self.scheduler.request_termination(self.rid)
                self.rid = self.scheduler.submit("main", self.expid_continuous, 0)
        else:
            if self.pulsed:
                return
            else:
                self.pulsed = True
                self.scheduler.request_termination(self.rid)
                self.rid = self.scheduler.submit("main", self.expid_pulsed, 0)

    def updateLCD(self):
        if not self.onButton.isChecked():
            self.countDisplay.display(0)
            return
        try:
            raw_val = self.dset_ctl.get("pmt_counts")[-1]
            try:
                # duration in mseconds
                duration = float(self.duration.text())
            except ValueError:
                # picked up a backspace or something
                logger.warning("Failed to update LCD", exc_info=True)
                return
            val = raw_val / duration  # kcounts / second
            self.countDisplay.display(val)
        except KeyError:
            # dataset doesn't exist
            logger.info("dataset doesn't exist yet")
            self.countDisplay.display(0)
        except IndexError:
            # timer too fast
            pass

    @inlineCallbacks
    def toggle_linetrigger(self, *args):
        sender = self.sender()
        flag = sender.isChecked()
        if flag:
            sender.setText("Off")
            yield self.pv.set_parameter(["line_trigger_settings", "enabled", True])
        else:
            sender.setText("On")
            yield self.pv.set_parameter(["line_trigger_settings", "enabled", False])

    @inlineCallbacks
    def linetrigger_duration_changed(self, *args):
        value = float(self.sender().text())
        yield self.pv.set_parameter(["line_trigger_settings", "offset_duration", value])

    def piezo_step_size_changed(self):
        sender = self.sender()
        step = int(sender.value())
        ctl = sender.objectName()
        self.piezoCurrentPos[ctl].setSingleStep(step)

    @inlineCallbacks
    def piezo_changed(self, *args):
        if self.pm is None:
            yield print("not connected to picomotor")
        sender = self.sender()
        piezo = int(sender.objectName())
        current_pos = int(sender.value())
        last_pos = self.piezoLastPos[piezo]
        self.piezoLastPos[piezo] = current_pos
        move = current_pos - last_pos
        yield self.pm.relative_move(piezo, move)

    @inlineCallbacks
    def toggle_autoload(self, *args):
        sender = self.autoLoadButton
        flag = sender.isChecked()
        if flag:
            try:
                self.check_pmt_data_length = len(self.dataset_db.get("pmt_counts"))
            except KeyError:
                sender.setChecked(False)
                return
            sender.setText("Off")
            self.expid_ttl.update({"arguments": {"device": "blue_PIs",
                                                 "state": True}})
            if not hasattr(self, "check_pmt_timer"):
                self.check_pmt_timer = QtCore.QTimer()
                self.check_pmt_timer.timeout.connect(self.check_pmt_counts)
            self.check_pmt_timer.start(100)
            yield self.bb.connect()
            yield self.bb.set_current(self.autoLoadCurrentSpin.value())
            yield self.bb.on()
        else:
            sender.setText("On")
            if not hasattr(self, "check_pmt_timer"):
                return
            self.check_pmt_timer.stop()
            self.expid_ttl.update({"arguments": {"device": "blue_PIs",
                                                 "state": False}})
            yield self.bb.off()
        self.scheduler.submit("main", self.expid_ttl, priority=1)

    def check_pmt_counts(self):
        try:
            counts = self.dataset_db.get("pmt_counts")[self.check_pmt_data_length:]
        except KeyError:
            return
        if len(counts) == 0:
            return
        if max(counts) > int(self.autoLoadSpin.value()):
            self.autoLoadButton.setChecked(False)
            self.autoLoadButton.clicked.emit()

    def save_state(self):
        return {"ctl": {ctl: self.piezoStepSize[ctl].value()
                        for ctl in self.piezoStepSize.keys()},
                "offset":  self.linetriggerLineEdit.text(),
                "autoload": self.autoLoadSpin.value(),
                "mode": self.setMode.currentText(),
                "ltrigger": self.linetriggerButton.isChecked(),
                "current": self.autoLoadCurrentSpin.value()}

    def restore_state(self, state):
        for ctl, value in state["ctl"].items():
            self.piezoStepSize[ctl].setValue(value)
            self.piezoCurrentPos[ctl].setSingleStep(int(value))
        self.linetriggerLineEdit.setText(state["offset"])
        self.autoLoadSpin.setValue(int(state["autoload"]))
        self.setMode.setCurrentText(state["mode"])
        self.linetriggerButton.setChecked(state["ltrigger"])
        d = {False: "On", True: "Off"}
        self.linetriggerButton.setText(d[state["ltrigger"]])
        self.autoLoadCurrentSpin.setValue(state["current"])

    def setup_listeners(self):
        self.acxn.add_on_connect("ParameterVault", self.parameter_vault_connect)
        self.acxn.add_on_disconnect("ParameterVault", self.parameter_vault_disconnect)
        self.acxn.add_on_connect("picomotorserver", self.picomotor_connect)
        self.acxn.add_on_disconnect("picomotorserver", self.picomotor_disconnect)
        self.acxn.add_on_connect("barebonese3663a", self.barebones_connect)
        self.acxn.add_on_disconnect("barebonese3663a", self.barebones_disconnect)

    def parameter_vault_connect(self):
        self.duration.setDisabled(False)

    def parameter_vault_disconnect(self):
        self.duration.setDisabled(True)

    def picomotor_connect(self):
        for spinbox in self.piezoStepSize.values():
            spinbox.setDisabled(False)
        for spinbox in self.piezoCurrentPos.values():
            spinbox.setDisabled(False)

    def picomotor_disconnect(self):
        for spinbox in self.piezoStepSize.values():
            spinbox.setDisabled(True)
        for spinbox in self.piezoCurrentPos.values():
            spinbox.setDisabled(True)

    def barebones_connect(self):
        self.autoLoadCurrentSpin.setDisabled(False)

    def barebones_disconnect(self):
        self.autoLoadCurrentSpin.setDisabled(True)

    @inlineCallbacks
    def connect_servers(self):
        if self.pv is None:
            try:
                self.pv = yield self.acxn.get_server("ParameterVault")
            except:
                self.parameter_vault_disconnect()
        if self.pm is None:
            try:
                self.pm = yield self.acxn.get_server("picomotorserver")
            except:
                self.picomotor_disconnect()
        if self.bb is None:
            try:
                self.bb = yield self.acxn.get_server("barebonese3663a")
            except:
                self.barebones_disconnect()
예제 #3
0
class PMTPlot(pyqtgraph.PlotWidget):
    def __init__(self, args):
        pyqtgraph.PlotWidget.__init__(self)
        self.args = args
        self.curves = {}
        self.current_curve_x_start = {}
        self.current_curve_point_count = {}
        self.showGrid(x=True, y=True, alpha=0.75)
        self.setYRange(0, 1000)
        self.setXRange(0, 500)

        self.pens = {
            "with_866_on": pyqtgraph.mkPen((255, 0, 0), width=2),
            "with_866_off": pyqtgraph.mkPen((0, 0, 255), width=2),
            "diff_counts": pyqtgraph.mkPen((0, 255, 0), width=2)
        }

        self.z_values = {
            "with_866_on": 30,
            "with_866_off": 20,
            "diff_counts": 10
        }

        legend = self.addLegend()
        legend.addItem(pyqtgraph.PlotDataItem(pen=self.pens["with_866_on"]),
                       "   866 ON")
        legend.addItem(pyqtgraph.PlotDataItem(pen=self.pens["with_866_off"]),
                       "   866 OFF")
        legend.addItem(pyqtgraph.PlotDataItem(pen=self.pens["diff_counts"]),
                       " Diff")

        self.autoscroll = True
        self.setLimits(yMin=0, xMin=0)
        self.disableAutoRange()
        self.scene().sigMouseClicked.connect(self.mouse_clicked)
        self.getPlotItem().setClipToView(True)
        self.data_mgr = Client("::1", 3251, "master_dataset_db")

    def data_changed(self, data, mods, title):
        try:
            clear_pmt_plot = data[self.args.clear_pmt_plot][1]
            if clear_pmt_plot:
                self.clear()
                self.curves = dict()
                self.current_curve_x_start = dict()
                self.current_curve_point_count = dict()
                self.data_mgr.set("clear_pmt_plot", False)
                self.data_mgr.set("pmt_counts", [])
                self.data_mgr.set("pmt_counts_866_off", [])
                self.data_mgr.set("diff_counts", [])
                self.setYRange(0, 1000)
                self.setXRange(0, 500)
                return  # don't want to plot twice
        except Exception as e:
            print(Exception)
        self.disableAutoRange()
        raw_data = {}
        try:
            raw_data["with_866_on"] = data[self.args.with_866_on][1][1:]
            raw_data["with_866_off"] = data[self.args.with_866_off][1][1:]

            raw_data["diff_counts"] = []
            max_with_866_off = max(raw_data["with_866_off"], default=0)
            for i in range(
                    min(len(raw_data["with_866_on"]),
                        len(raw_data["with_866_off"]))):
                if max_with_866_off > 0:
                    raw_data["diff_counts"].append(raw_data["with_866_on"][i] -
                                                   raw_data["with_866_off"][i])
                else:
                    raw_data["diff_counts"].append(-1)

            pulsed = data[self.args.pulsed][1][0]
        except KeyError:
            return

        for curve_name in ["with_866_on", "diff_counts", "with_866_off"]:
            if not curve_name in raw_data.keys():
                continue

            data_to_plot = raw_data[curve_name]
            num_points = len(data_to_plot)

            if not curve_name in self.curves.keys():
                self.curves[curve_name] = []

            if not curve_name in self.current_curve_point_count.keys():
                self.current_curve_point_count[curve_name] = 0

            if not curve_name in self.current_curve_x_start.keys():
                self.current_curve_x_start[curve_name] = 0

            if num_points == self.current_curve_point_count[curve_name]:
                # nothing new to plot for this curve
                continue
            if num_points < self.current_curve_point_count[curve_name]:
                # if this dataset has fewer points than the current curve, the dataset
                # must have been restarted, so we will simply begin a new curve.
                # update the x_start value for the new curve
                self.current_curve_x_start[
                    curve_name] += self.current_curve_point_count[curve_name]
            else:
                # we have more points, so the current curve needs to be updated.
                # remove the current curve so that we will recreate it.
                num_curves_now = len(self.curves[curve_name])
                if num_curves_now > 0:
                    latest_curve = self.curves[curve_name][-1]
                    self.removeItem(latest_curve)
                    self.curves[curve_name].pop(-1)
                    num_curves_now -= 1

            self.current_curve_point_count[curve_name] = num_points

            # for curves with all negative values, don't use any pen to plot them.
            # this will happen, e.g., if we are running the PMT in continuous mode
            # rather than in pulsed mode -- the 866_off and diff curves will not
            # contain valid data.
            pen = self.pens[curve_name]
            if num_points > 0 and max(data_to_plot, default=0) < 0:
                pen = pyqtgraph.mkPen(None)

            x_start = self.current_curve_x_start[curve_name]
            x_end = x_start + num_points
            x = np.arange(x_start, x_end)
            curve = self.plot(x, data_to_plot, pen=pen, fillLevel=0)
            curve.setZValue(self.z_values[curve_name])
            self.curves[curve_name].append(curve)

        if self.autoscroll:
            (xmin_cur, xmax_cur), _ = self.viewRange()
            max_x = 0
            for curve_name in self.curves.keys():
                for curve in self.curves[curve_name]:
                    localxmax = curve.dataBounds(0)[-1]
                    try:
                        if localxmax > max_x:
                            max_x = localxmax
                    except TypeError:
                        continue
            window_width = xmax_cur - xmin_cur
            if max_x > xmin_cur + window_width:
                shift = (xmax_cur - xmin_cur) / 2
                xmin = xmin_cur + shift
                xmax = xmax_cur + shift
                limits = [xmin, xmax]
                self.setXRange(*limits)
            self.setTitle(title)

    def mouse_clicked(self, ev):
        if ev.double():
            self.autoscroll = not self.autoscroll