Exemple #1
0
 def setRadioButton(self, radioButton: QRadioButton, mode):
     radioButton.setAutoExclusive(False)
     if mode == 0:
         radioButton.setChecked(False)
     if mode == 1:
         radioButton.setChecked(True)
     if mode == 2:
         radioButton.setChecked(not radioButton.isChecked())
     radioButton.setAutoExclusive(True)
     QApplication.processEvents()
    def initUI(self):
        btn_1 = QRadioButton(self)
        btn_1.setText('버튼1')
        btn_1.move(60, 50)

        btn_2 = QRadioButton('&Button2', self)  # Alt + B 를 입력하면 단축키가 된다.
        btn_2.setText('버튼2')
        btn_2.setChecked(True)
        btn_2.move(60, 80)

        btn_3 = QRadioButton('버튼3', self)
        btn_3.move(60, 110)
        btn_3.setAutoExclusive(False)

        self.setGeometry(300, 300, 300, 150)
        self.setWindowTitle('QRadioButton')
        self.show()
Exemple #3
0
 def createRadioButton(parent: QWidget,
                       objectName="RadioButton",
                       text="RadioButton",
                       toolTip=None,
                       isEnable=True,
                       autoExclusive=True,
                       isChecked=False,
                       geometry: QRect = None,
                       sizePolicy: QSizePolicy = None,
                       onToggled=None):
     """
     创建一个单选按钮
     :param parent: 父QWidget
     :param objectName: objectName
     :param text: text
     :param toolTip: toolTip
     :param isEnable: enable
     :param autoExclusive: autoExclusive False 独立到,True 同一个父widget到为一组
     :param isChecked: isChecked 默认是否选中,true选中
     :param geometry: geometry
     :param sizePolicy: 缩放策略
     :param onToggled: toggled checked状态切换回调
     :return: 单选按钮
     """
     widget = QRadioButton(parent)
     widgetSetAttrs(widget,
                    objectName,
                    toolTip=toolTip,
                    geometry=geometry,
                    isEnable=isEnable,
                    sizePolicy=sizePolicy)
     widget.setText(_translate(contextName, text))
     widget.setAutoExclusive(autoExclusive)
     widget.setChecked(isChecked)
     if onToggled:
         widget.toggled.connect(onToggled)
     return widget
Exemple #4
0
class ServerPlugin(Plugin):
    __icon__ = "fa.qrcode"
    __pname__ = "server"
    __views__ = ["slice_viewer"]
    __tab__ = "server"

    def __init__(self, parent=None):
        super().__init__(parent=parent)

        run_config = {
            "server_ip": "127.0.0.1",
            "server_port": "8134",
            "workspace_name": "test_hunt_d4b",
            "use_ssh": False,
            "ssh_host": "ws168.diamond.ac.uk",
            "ssh_port": "22",
        }

        workspace_config = {
            "dataset_name": "data",
            "datasets_dir": "/path/to/my/data/dir",
            "vol_fname": "myfile.h5",
            "workspace_name": "my_survos_workspace",
            "downsample_by": "1",
        }

        from survos2.server.config import cfg

        pipeline_config = dict(cfg)

        self.run_config = run_config
        self.workspace_config = workspace_config
        self.pipeline_config = pipeline_config

        self.server_process = None
        self.client_process = None

        self.layout = QVBoxLayout()
        tabwidget = QTabWidget()
        tab1 = QWidget()
        tab2 = QWidget()

        tabwidget.addTab(tab1, "Setup and Start Survos")
        self.create_workspace_button = QPushButton("Create workspace")

        tab1.layout = QVBoxLayout()
        tab1.setLayout(tab1.layout)
        chroot_fields = self.get_chroot_fields()
        tab1.layout.addWidget(chroot_fields)
        workspace_fields = self.get_workspace_fields()
        tab1.layout.addWidget(workspace_fields)

        self.setup_adv_run_fields()
        self.adv_run_fields.hide()

        run_fields = self.get_run_fields()
        tab1.layout.addWidget(run_fields)
        output_config_button = QPushButton("Save config")

        self.create_workspace_button.clicked.connect(
            self.create_workspace_clicked)
        output_config_button.clicked.connect(self.output_config_clicked)
        self.layout.addWidget(tabwidget)

        self.setGeometry(300, 300, 600, 400)
        self.setWindowTitle("SuRVoS Settings Editor")
        current_fpth = os.path.dirname(os.path.abspath(__file__))
        self.setWindowIcon(
            QIcon(os.path.join(current_fpth, "resources", "logo.png")))
        self.setLayout(self.layout)
        self.show()

    def get_chroot_fields(self):
        chroot_fields = QGroupBox("Set Main Directory for Storing Workspaces:")
        chroot_fields.setMaximumHeight(130)
        chroot_layout = QGridLayout()
        self.given_chroot_linedt = QLineEdit(CHROOT)
        chroot_layout.addWidget(self.given_chroot_linedt, 1, 0, 1, 2)
        set_chroot_button = QPushButton("Set Workspaces Root")
        chroot_layout.addWidget(set_chroot_button, 1, 2)
        chroot_fields.setLayout(chroot_layout)
        set_chroot_button.clicked.connect(self.set_chroot)
        return chroot_fields

    def get_workspace_fields(self):
        """Gets the QGroupBox that contains all the fields for setting up the workspace.

        Returns:
            PyQt5.QWidgets.GroupBox: GroupBox with workspace fields.
        """
        select_data_button = QPushButton("Select")
        workspace_fields = QGroupBox("Create New Workspace:")
        wf_layout = QGridLayout()
        wf_layout.addWidget(QLabel("Data File Path:"), 0, 0)
        current_data_path = Path(self.workspace_config["datasets_dir"],
                                 self.workspace_config["vol_fname"])
        self.data_filepth_linedt = QLineEdit(str(current_data_path))
        wf_layout.addWidget(self.data_filepth_linedt, 1, 0, 1, 2)
        wf_layout.addWidget(select_data_button, 1, 2)
        wf_layout.addWidget(QLabel("HDF5 Internal Data Path:"), 2, 0, 1, 1)
        ws_dataset_name = self.workspace_config["dataset_name"]
        internal_h5_path = (ws_dataset_name
                            if str(ws_dataset_name).startswith("/") else "/" +
                            ws_dataset_name)
        self.h5_intpth_linedt = QLineEdit(internal_h5_path)
        wf_layout.addWidget(self.h5_intpth_linedt, 2, 1, 1, 1)
        wf_layout.addWidget(QLabel("Workspace Name:"), 3, 0)
        self.ws_name_linedt_1 = QLineEdit(
            self.workspace_config["workspace_name"])
        wf_layout.addWidget(self.ws_name_linedt_1, 3, 1)
        wf_layout.addWidget(QLabel("Downsample Factor:"), 4, 0)
        self.downsample_spinner = QSpinBox()
        self.downsample_spinner.setRange(1, 10)
        self.downsample_spinner.setSpecialValueText("None")
        self.downsample_spinner.setMaximumWidth(60)
        self.downsample_spinner.setValue(
            int(self.workspace_config["downsample_by"]))
        wf_layout.addWidget(self.downsample_spinner, 4, 1, 1, 1)
        # ROI
        self.setup_roi_fields()
        wf_layout.addWidget(self.roi_fields, 4, 2, 1, 2)
        self.roi_fields.hide()

        wf_layout.addWidget(self.create_workspace_button, 5, 0, 1, 3)
        workspace_fields.setLayout(wf_layout)
        select_data_button.clicked.connect(self.launch_data_loader)
        return workspace_fields

    def setup_roi_fields(self):
        """Sets up the QGroupBox that displays the ROI dimensions, if selected."""
        self.roi_fields = QGroupBox("ROI:")
        roi_fields_layout = QHBoxLayout()
        # z
        roi_fields_layout.addWidget(QLabel("z:"), 0)
        self.zstart_roi_val = QLabel("0")
        roi_fields_layout.addWidget(self.zstart_roi_val, 1)
        roi_fields_layout.addWidget(QLabel("-"), 2)
        self.zend_roi_val = QLabel("0")
        roi_fields_layout.addWidget(self.zend_roi_val, 3)
        # y
        roi_fields_layout.addWidget(QLabel("y:"), 4)
        self.ystart_roi_val = QLabel("0")
        roi_fields_layout.addWidget(self.ystart_roi_val, 5)
        roi_fields_layout.addWidget(QLabel("-"), 6)
        self.yend_roi_val = QLabel("0")
        roi_fields_layout.addWidget(self.yend_roi_val, 7)
        # x
        roi_fields_layout.addWidget(QLabel("x:"), 8)
        self.xstart_roi_val = QLabel("0")
        roi_fields_layout.addWidget(self.xstart_roi_val, 9)
        roi_fields_layout.addWidget(QLabel("-"), 10)
        self.xend_roi_val = QLabel("0")
        roi_fields_layout.addWidget(self.xend_roi_val, 11)

        self.roi_fields.setLayout(roi_fields_layout)

    def setup_adv_run_fields(self):
        """Sets up the QGroupBox that displays the advanced optiona for starting SuRVoS2."""
        self.adv_run_fields = QGroupBox("Advanced Run Settings:")
        adv_run_layout = QGridLayout()
        adv_run_layout.addWidget(QLabel("Server IP Address:"), 0, 0)
        self.server_ip_linedt = QLineEdit(self.run_config["server_ip"])
        adv_run_layout.addWidget(self.server_ip_linedt, 0, 1)
        adv_run_layout.addWidget(QLabel("Server Port:"), 1, 0)
        self.server_port_linedt = QLineEdit(self.run_config["server_port"])
        adv_run_layout.addWidget(self.server_port_linedt, 1, 1)

        # SSH Info
        self.ssh_button = QRadioButton("Use SSH")
        self.ssh_button.setAutoExclusive(False)
        adv_run_layout.addWidget(self.ssh_button, 0, 2)
        ssh_flag = self.run_config.get("use_ssh", False)
        if ssh_flag:
            self.ssh_button.setChecked(True)
        self.ssh_button.toggled.connect(self.toggle_ssh)

        self.adv_ssh_fields = QGroupBox("SSH Settings:")
        adv_ssh_layout = QGridLayout()
        adv_ssh_layout.setColumnStretch(2, 2)
        ssh_host_label = QLabel("Host")
        self.ssh_host_linedt = QLineEdit(self.run_config.get("ssh_host", ""))
        adv_ssh_layout.addWidget(ssh_host_label, 0, 0)
        adv_ssh_layout.addWidget(self.ssh_host_linedt, 0, 1, 1, 2)
        ssh_user_label = QLabel("Username")
        self.ssh_username_linedt = QLineEdit(self.get_login_username())
        adv_ssh_layout.addWidget(ssh_user_label, 1, 0)
        adv_ssh_layout.addWidget(self.ssh_username_linedt, 1, 1, 1, 2)
        ssh_port_label = QLabel("Port")
        self.ssh_port_linedt = QLineEdit(self.run_config.get("ssh_port", ""))
        adv_ssh_layout.addWidget(ssh_port_label, 2, 0)
        adv_ssh_layout.addWidget(self.ssh_port_linedt, 2, 1, 1, 2)
        self.adv_ssh_fields.setLayout(adv_ssh_layout)
        #adv_run_layout.addWidget(self.adv_ssh_fields, 1, 2, 2, 5)

        self.adv_run_fields.setLayout(adv_run_layout)

    def get_run_fields(self):
        """Gets the QGroupBox that contains the fields for starting SuRVoS.

        Returns:
            PyQt5.QWidgets.GroupBox: GroupBox with run fields.
        """
        self.run_button = QPushButton("Start Server")
        self.stop_button = QPushButton("Stop Server")

        self.existing_button = QPushButton("Use Existing Server")

        advanced_button = QRadioButton("Advanced")
        run_fields = QGroupBox("Run SuRVoS:")
        run_layout = QGridLayout()

        workspaces = os.listdir(CHROOT)
        self.workspaces_list = ComboBox()
        for s in workspaces:
            self.workspaces_list.addItem(key=s)

        run_layout.addWidget(QLabel("Workspace Name:"), 0, 0)
        self.ws_name_linedt_2 = QLineEdit(
            self.workspace_config["workspace_name"])
        self.ws_name_linedt_2.setAlignment(Qt.AlignLeft)
        self.workspaces_list.setLineEdit(self.ws_name_linedt_2)

        # run_layout.addWidget(self.ws_name_linedt_2, 0, 1)

        run_layout.addWidget(self.workspaces_list, 0, 1)
        run_layout.addWidget(advanced_button, 1, 0)
        run_layout.addWidget(self.adv_run_fields, 2, 1)
        run_layout.addWidget(self.run_button, 3, 0, 1, 3)
        run_layout.addWidget(self.stop_button, 4, 0, 1, 3)
        run_layout.addWidget(self.existing_button, 5, 0, 1, 3)
        run_fields.setLayout(run_layout)

        advanced_button.toggled.connect(self.toggle_advanced)
        self.run_button.clicked.connect(self.run_clicked)
        self.stop_button.clicked.connect(self.stop_clicked)
        self.existing_button.clicked.connect(self.existing_clicked)

        return run_fields

    def get_login_username(self):
        try:
            user = getpass.getuser()
        except Exception:
            user = ""
        return user

    def refresh_chroot(self):
        workspaces = os.listdir(DataModel.g.CHROOT)
        self.workspaces_list.clear()
        for s in workspaces:
            self.workspaces_list.addItem(key=s)

    @pyqtSlot()
    def set_chroot(self):
        CHROOT = self.given_chroot_linedt.text()
        Config.update({"model": {"chroot": CHROOT}})
        logger.debug(f"Setting CHROOT to {CHROOT}")
        DataModel.g.CHROOT = CHROOT
        self.refresh_chroot()

    @pyqtSlot()
    def launch_data_loader(self):
        """Load the dialog box widget to select data with data preview window and ROI selection."""
        path = None
        int_h5_pth = None
        dialog = LoadDataDialog(self)
        result = dialog.exec_()
        self.roi_limits = None
        if result == QDialog.Accepted:
            path = dialog.winput.path.text()
            int_h5_pth = dialog.int_h5_pth.text()
            down_factor = dialog.downsample_spinner.value()
        if path and int_h5_pth:
            self.data_filepth_linedt.setText(path)
            self.h5_intpth_linedt.setText(int_h5_pth)
            self.downsample_spinner.setValue(down_factor)
            if dialog.roi_changed:
                self.roi_limits = tuple(map(str, dialog.get_roi_limits()))
                self.roi_fields.show()
                self.update_roi_fields_from_dialog()
            else:
                self.roi_fields.hide()

    def update_roi_fields_from_dialog(self):
        """Updates the ROI fields in the main window."""
        x_start, x_end, y_start, y_end, z_start, z_end = self.roi_limits
        self.xstart_roi_val.setText(x_start)
        self.xend_roi_val.setText(x_end)
        self.ystart_roi_val.setText(y_start)
        self.yend_roi_val.setText(y_end)
        self.zstart_roi_val.setText(z_start)
        self.zend_roi_val.setText(z_end)

    @pyqtSlot()
    def toggle_advanced(self):
        """Controls displaying/hiding the advanced run fields on radio button toggle."""
        rbutton = self.sender()
        if rbutton.isChecked():
            self.adv_run_fields.show()
        else:
            self.adv_run_fields.hide()

    @pyqtSlot()
    def toggle_ssh(self):
        """Controls displaying/hiding the SSH fields on radio button toggle."""
        rbutton = self.sender()
        if rbutton.isChecked():
            self.adv_ssh_fields.show()
        else:
            self.adv_ssh_fields.hide()

    @pyqtSlot()
    def create_workspace_clicked(self):
        """Performs checks and coordinates workspace creation on button press."""
        logger.debug("Creating workspace: ")
        # Set the path to the data file
        vol_path = Path(self.data_filepth_linedt.text())
        if not vol_path.is_file():
            err_str = f"No data file exists at {vol_path}!"
            logger.error(err_str)
            self.button_feedback_response(err_str,
                                          self.create_workspace_button,
                                          "maroon")
        else:
            self.workspace_config["datasets_dir"] = str(vol_path.parent)
            self.workspace_config["vol_fname"] = str(vol_path.name)
            dataset_name = self.h5_intpth_linedt.text()
            self.workspace_config["dataset_name"] = str(dataset_name).strip(
                "/")
            # Set the workspace name
            ws_name = self.ws_name_linedt_1.text()
            self.workspace_config["workspace_name"] = ws_name
            # Set the downsample factor
            ds_factor = self.downsample_spinner.value()
            self.workspace_config["downsample_by"] = ds_factor
            # Set the ROI limits if they exist
            if self.roi_limits:
                self.workspace_config["roi_limits"] = self.roi_limits
            try:
                response = init_ws(self.workspace_config)
                _, error = response
                if not error:
                    self.button_feedback_response(
                        "Workspace created sucessfully",
                        self.create_workspace_button,
                        "green",
                    )
                    # Update the workspace name in the 'Run' section
                    self.ws_name_linedt_2.setText(self.ws_name_linedt_1.text())
            except WorkspaceException as e:
                logger.exception(e)
                self.button_feedback_response(str(e),
                                              self.create_workspace_button,
                                              "maroon")
            self.refresh_chroot()

    def button_feedback_response(self, message, button, colour_str, timeout=2):
        """Changes button colour and displays feedback message for a limited time period.

        Args:
            message (str): Message to display in button.
            button (PyQt5.QWidgets.QBushButton): The button to manipulate.
            colour_str (str): The standard CSS colour string or hex code describing the colour to change the button to.
        """
        timeout *= 1000
        msg_old = button.text()
        col_old = button.palette().button().color
        txt_col_old = button.palette().buttonText().color
        button.setText(message)
        button.setStyleSheet(f"background-color: {colour_str}; color: white")
        timer = QTimer()
        timer.singleShot(
            timeout,
            lambda: self.reset_button(button, msg_old, col_old, txt_col_old))

    @pyqtSlot()
    def reset_button(self, button, msg_old, col_old, txt_col_old):
        """Sets a button back to its original display settings.

        Args:
            button (PyQt5.QWidgets.QBushButton): The button to manipulate.
            msg_old (str): Message to display in button.
            col_old (str): The standard CSS colour string or hex code describing the colour to change the button to.
            txt_col_old (str): The standard CSS colour string or hex code describing the colour to change the button text to.
        """
        button.setStyleSheet(f"background-color: {col_old().name()}")
        button.setStyleSheet(f"color: {txt_col_old().name()}")
        button.setText(msg_old)
        button.update()

    @pyqtSlot()
    def output_config_clicked(self):
        """Outputs pipeline config YAML file on button click."""
        out_fname = "pipeline_cfg.yml"
        logger.debug(f"Outputting pipeline config: {out_fname}")
        with open(out_fname, "w") as outfile:
            yaml.dump(self.pipeline_config,
                      outfile,
                      default_flow_style=False,
                      sort_keys=False)

    def get_ssh_params(self):
        ssh_host = self.ssh_host_linedt.text()
        ssh_user = self.ssh_username_linedt.text()
        ssh_port = int(self.ssh_port_linedt.text())
        return ssh_host, ssh_user, ssh_port

    def start_server_over_ssh(self):
        params = self.get_ssh_params()
        if not all(params):
            logger.error(
                "Not all SSH parameters given! Not connecting to SSH.")
            pass
        ssh_host, ssh_user, ssh_port = params
        # Pop up dialog to ask for password
        text, ok = QInputDialog.getText(None, "Login",
                                        f"Password for {ssh_user}@{ssh_host}",
                                        QLineEdit.Password)
        if ok and text:
            self.ssh_worker = SSHWorker(params, text, self.run_config)
            self.ssh_thread = QThread(self)
            self.ssh_worker.moveToThread(self.ssh_thread)
            self.ssh_worker.button_message_signal.connect(
                self.send_msg_to_run_button)
            self.ssh_worker.error_signal.connect(self.on_ssh_error)
            self.ssh_worker.finished.connect(self.start_client)
            self.ssh_worker.update_ip_linedt_signal.connect(
                self.update_ip_linedt)
            self.ssh_thread.started.connect(
                self.ssh_worker.start_server_over_ssh)
            self.ssh_thread.start()

    def closeEvent(self, event):
        reply = QMessageBox.question(
            self,
            "Quit",
            "Are you sure you want to quit? "
            "The server will be stopped.",
            QMessageBox.Yes | QMessageBox.No,
            QMessageBox.No,
        )
        if reply == QMessageBox.Yes:
            event.accept()
        else:
            event.ignore()

    @pyqtSlot()
    def on_ssh_error(self):
        self.ssh_error = True

    @pyqtSlot(str)
    def update_ip_linedt(self, ip):
        self.server_ip_linedt.setText(ip)

    @pyqtSlot(list)
    def send_msg_to_run_button(self, param_list):
        self.button_feedback_response(param_list[0], self.run_button,
                                      param_list[1], param_list[2])

    @pyqtSlot()
    def stop_clicked(self):
        logger.debug("Stopping server")
        if self.server_process is not None:
            self.server_process.kill()

    @pyqtSlot()
    def run_clicked(self):
        """Starts SuRVoS2 server and client as subprocesses when 'Run' button pressed.

        Raises:
            Exception: If survos.py not found.
        """
        with progress(total=3) as pbar:
            pbar.set_description("Starting server...")
            pbar.update(1)

        self.ssh_error = (
            False  # Flag which will be set to True if there is an SSH error
        )
        command_dir = os.path.abspath(os.path.dirname(__file__))  # os.getcwd()

        # Set current dir to survos root
        from pathlib import Path

        command_dir = Path(
            command_dir).absolute().parent.parent.parent.resolve()
        os.chdir(command_dir)

        self.script_fullname = os.path.join(command_dir, "survos.py")
        if not os.path.isfile(self.script_fullname):
            raise Exception("{}: Script not found".format(
                self.script_fullname))
        # Retrieve the parameters from the fields TODO: Put some error checking in
        self.run_config["workspace_name"] = self.ws_name_linedt_2.text()
        self.run_config["server_port"] = self.server_port_linedt.text()
        # Temporary measure to check whether the workspace exists or not
        full_ws_path = os.path.join(Config["model.chroot"],
                                    self.run_config["workspace_name"])
        if not os.path.isdir(full_ws_path):
            logger.error(
                f"No workspace can be found at {full_ws_path}, Not starting SuRVoS."
            )
            self.button_feedback_response(
                f"Workspace {self.run_config['workspace_name']} does not appear to exist!",
                self.run_button,
                "maroon",
            )
            return
        pbar.update(1)
        # Try some fancy SSH stuff here
        if self.ssh_button.isChecked():
            self.start_server_over_ssh()
        else:
            self.server_process = subprocess.Popen([
                "python",
                self.script_fullname,
                "start_server",
                self.run_config["workspace_name"],
                self.run_config["server_port"],
                DataModel.g.CHROOT,
            ])
            try:
                outs, errs = self.server_process.communicate(timeout=10)
                print(f"OUTS: {outs, errs}")
            except subprocess.TimeoutExpired:
                pass

            # self.start_client()
            logger.info(f"setting remote: {self.server_port_linedt.text()}")
            remote_ip_port = "127.0.0.1:" + self.server_port_linedt.text()
            logger.info(f"setting remote: {remote_ip_port}")
            resp = Launcher.g.set_remote(remote_ip_port)
            logger.info(f"Response from server to setting remote: {resp}")

            cfg.ppw.clientEvent.emit({
                "source": "server_tab",
                "data": "set_workspace",
                "workspace": self.ws_name_linedt_2.text(),
            })
            cfg.ppw.clientEvent.emit({
                "source": "panel_gui",
                "data": "refresh",
                "value": None
            })
            #cfg.ppw.clientEvent.emit({'data' : 'view_feature', 'feature_id' : '001_raw'})
        pbar.update(1)

    @pyqtSlot()
    def existing_clicked(self):
        ssh_ip = self.server_ip_linedt.text()
        remote_ip_port = ssh_ip + ":" + self.server_port_linedt.text()
        logger.info(f"setting remote: {remote_ip_port}")
        resp = Launcher.g.set_remote(remote_ip_port)
        logger.info(f"Response from server to setting remote: {resp}")

        cfg.ppw.clientEvent.emit({
            "source": "server_tab",
            "data": "set_workspace",
            "workspace": self.ws_name_linedt_2.text(),
        })
        cfg.ppw.clientEvent.emit({
            "source": "panel_gui",
            "data": "refresh",
            "value": None
        })

    def start_client(self):
        if not self.ssh_error:
            self.button_feedback_response("Starting Client.", self.run_button,
                                          "green", 7)
            self.run_config["server_ip"] = self.server_ip_linedt.text()
            self.client_process = subprocess.Popen([
                "python",
                self.script_fullname,
                "nu_gui",
                self.run_config["workspace_name"],
                str(self.run_config["server_ip"]) + ":" +
                str(self.run_config["server_port"]),
            ])
Exemple #5
0
class MainWindow(QMainWindow):
    """[summary]
    Main window class containing the main window and its associated methods. 
    Args:
        QMainWindow (QObject): See qt documentation for more info.
    """
    # Send logging parameters to worker method
    logging_requested = QtCore.pyqtSignal(str, str, bool, str, object)

    def __init__(self, *args, **kwargs):
        """[summary]
        Function to initialise the Main Window, which will hold all the subsequent widgets to be created.
        """
        super(MainWindow, self).__init__(*args, **kwargs)

        self._tdc1_dev = None  # tdc1 device object
        self._dev_mode = ''  # 0 = 'singles', 1 = 'pairs', 3 = 'timestamp'
        self.device_path = ''  # Device path, eg. 'COM4'

        self.integration_time = 1
        self._logfile_name = ''  # Track the logfile(csv) being used by GUI
        self._ch_start = 1  # Start channel for pairs
        self._ch_stop = 3  # Stop channel for pairs
        self.plotSamples = 501  # Number of data points to plot

        self.log_flag = False  # Flag to track if data is being logged to csv file
        self.acq_flag = False  # Track if data is being acquired
        self._radio_flags = [
            0, 0, 0, 0
        ]  # Tracking which radio buttons are selected. All 0s by default

        self.logger = None  # Variable that will hold the logWorker object
        self.logger_thread = None  # Variable that will hold the QThread object

        self.initUI()  # UI is initialised afer the class variables are defined

        self._plot_tab = self.tabs.currentIndex(
        )  # Counts graph = 0, Coincidences graph = 1
        self.idx = min(len(self.y1), self.plotSamples)  # Index for plotting

    def initUI(self):
        """[summary]
        Contains all the UI elements and associated functionalities.
        """
        defaultFont = QtGui.QFont("Helvetica", 14)

        #---------Buttons---------#
        self.scanForDevice_Button = QtWidgets.QPushButton(
            "Scan for Device", self)
        self.scanForDevice_Button.clicked.connect(self.updateDevList)
        #self.scanForDevice_Button.setFixedSize(QSize(115, 35))

        self.liveStart_Button = QtWidgets.QPushButton("Live Start", self)
        self.liveStart_Button.clicked.connect(self.liveStart)
        #self.liveStart_Button.setFixedSize(QSize(115, 35))

        self.selectLogfile_Button = QtWidgets.QPushButton(
            "Select Logfile", self)
        self.selectLogfile_Button.clicked.connect(self.selectLogfile)
        #self.selectLogfile_Button.setFixedSize(QSize(115, 35))

        # setAutoExclusive method is used to toggle the radio buttons independently.
        self.radio1_Button = QRadioButton("Channel 1", self)
        self.radio1_Button.setStyleSheet('color: red; font-size: 14px')
        self.radio1_Button.setAutoExclusive(False)
        self.radio1_Button.toggled.connect(
            lambda: self.displayPlot1(self.radio1_Button))
        self.radio2_Button = QRadioButton("Channel 2", self)
        self.radio2_Button.setStyleSheet('color: green; font-size: 14px')
        self.radio2_Button.setAutoExclusive(False)
        self.radio2_Button.toggled.connect(
            lambda: self.displayPlot2(self.radio2_Button))
        self.radio3_Button = QRadioButton("Channel 3", self)
        self.radio3_Button.setStyleSheet('color: blue; font-size: 14px')
        self.radio3_Button.setAutoExclusive(False)
        self.radio3_Button.toggled.connect(
            lambda: self.displayPlot3(self.radio3_Button))
        self.radio4_Button = QRadioButton("Channel 4", self)
        self.radio4_Button.setStyleSheet('color: black; font-size: 14px')
        self.radio4_Button.setAutoExclusive(False)
        self.radio4_Button.toggled.connect(
            lambda: self.displayPlot4(self.radio4_Button))
        #---------Buttons---------#

        #---------Labels---------#
        #labelFontSize = "font-size: 18px"

        self.deviceLabel = QtWidgets.QLabel("Device:", self)
        self.deviceModeLabel = QtWidgets.QLabel("GUI Mode:", self)

        self.logfileLabel = QtWidgets.QLabel('', self)
        self.samplesLabel = QtWidgets.QLabel('Plot Samples:', self)
        self.integrationLabel = QtWidgets.QLabel("Integration time (ms):",
                                                 self)

        self.Ch1CountsLabel = QtWidgets.QLabel("0", self)
        self.Ch1CountsLabel.setStyleSheet("color: red; font-size: 128px")
        self.Ch1CountsLabel.setAlignment(QtCore.Qt.AlignCenter)
        self.Ch2CountsLabel = QtWidgets.QLabel("0", self)
        self.Ch2CountsLabel.setStyleSheet("color: green; font-size: 128px")
        self.Ch2CountsLabel.setAlignment(QtCore.Qt.AlignCenter)
        self.Ch3CountsLabel = QtWidgets.QLabel("0", self)
        self.Ch3CountsLabel.setStyleSheet("color: blue; font-size: 128px")
        self.Ch3CountsLabel.setAlignment(QtCore.Qt.AlignCenter)
        self.Ch4CountsLabel = QtWidgets.QLabel("0", self)
        self.Ch4CountsLabel.setStyleSheet("color: black; font-size: 128px")
        self.Ch4CountsLabel.setAlignment(QtCore.Qt.AlignCenter)

        self.startChannelLabel = QtWidgets.QLabel("Start Channel:", self)
        self.stopChannelLabel = QtWidgets.QLabel("Stop Channel:", self)
        self.centerLabel = QtWidgets.QLabel("Center:", self)
        self.pairsRateLabel = QtWidgets.QLabel("Pairs/sec: <br>" + "0")
        self.pairsRateLabel.setStyleSheet("font-size: 64px")
        self.pairsRateLabel.setAlignment(QtCore.Qt.AlignCenter)
        self.resolutionTextLabel = QtWidgets.QLabel("Bin Width:", self)
        #---------Labels---------#

        #---------Interactive Fields---------#
        self.integrationSpinBox = QSpinBox(self)
        self.integrationSpinBox.setRange(
            0, 65535)  # Max integration time based on tdc1 specs
        self.integrationSpinBox.setValue(1000)  # Default 1000ms = 1s
        self.integrationSpinBox.setKeyboardTracking(
            False
        )  # Makes sure valueChanged signal only fires when you want it to
        self.integrationSpinBox.valueChanged.connect(self.update_intTime)

        dev_list = serial_connection.search_for_serial_devices(
            tdc1.TimeStampTDC1.DEVICE_IDENTIFIER)
        self.devCombobox = QComboBox(self)
        self.devCombobox.addItem('Select your device')
        self.devCombobox.addItems(dev_list)
        self.devCombobox.currentTextChanged.connect(self.selectDevice)

        _dev_modes = ['singles', 'pairs']
        self.modesCombobox = QComboBox(self)
        self.modesCombobox.addItem('Select mode')
        self.modesCombobox.addItems(_dev_modes)
        self.modesCombobox.currentTextChanged.connect(self.selectDeviceMode)

        _channels = ['1', '2', '3', '4']
        self.channelsCombobox1 = QComboBox(self)
        self.channelsCombobox1.addItem('Select')
        self.channelsCombobox1.addItems(_channels)
        self.channelsCombobox1.setCurrentIndex(1)
        self.channelsCombobox1.currentTextChanged.connect(self.updateStart)
        self.channelsCombobox2 = QComboBox(self)
        self.channelsCombobox2.addItem('Select')
        self.channelsCombobox2.addItems(_channels)
        self.channelsCombobox2.setCurrentIndex(3)
        self.channelsCombobox2.currentTextChanged.connect(self.updateStop)

        self.samplesSpinbox = QSpinBox(self)
        self.samplesSpinbox.setRange(0, 501)
        self.samplesSpinbox.setValue(501)  # Default plot 501 data points
        self.samplesSpinbox.setKeyboardTracking(False)
        self.samplesSpinbox.valueChanged.connect(self.updatePlotSamples)

        self.centerSpinbox = QSpinBox(self)
        self.centerSpinbox.setRange(0, 1000)
        self.centerSpinbox.setKeyboardTracking(False)

        self.resolutionSpinbox = QSpinBox(self)
        self.resolutionSpinbox.setRange(0, 1000)
        self.resolutionSpinbox.setKeyboardTracking(False)
        self.resolutionSpinbox.valueChanged.connect(self.updateBins)
        #---------Interactive Fields---------#

        #---------PLOTS---------#
        # Initiating plot data variables
        # Plot 1 - Four channel counts plot
        self.x = []
        self.y1 = []
        self.y2 = []
        self.y3 = []
        self.y4 = []
        self.xnew = []
        self.y1new = []
        self.y2new = []
        self.y3new = []
        self.y4new = []
        self.y_data = [self.y1new, self.y2new, self.y3new, self.y4new]

        # Plot 2 - Time difference histogram (Channel cross-correlation)
        self.bins = 501
        self.binsize = 2  # milliseconds
        self.x0 = np.arange(0, self.bins * self.binsize, self.bins)
        self.y0 = np.zeros_like(self.x0)
        self.x0new = []
        self.y0new = []

        font = QtGui.QFont("Arial", 24)
        labelStyle = '<span style=\"color:black;font-size:25px\">'

        # Setting up plot window 1 (Plot Widget)
        self.tdcPlot = pg.PlotWidget(title="Counts Graph")
        self.tdcPlot.setBackground('w')
        self.tdcPlot.setLabel('left', labelStyle + 'Counts')
        self.tdcPlot.setLabel('bottom', labelStyle + 'Sample Number')
        self.tdcPlot.getAxis('left').tickFont = font
        self.tdcPlot.getAxis('bottom').tickFont = font
        self.tdcPlot.getAxis('bottom').setPen(color='k')
        self.tdcPlot.getAxis('left').setPen(color='k')
        self.tdcPlot.showGrid(y=True)

        # Setting up plot window 2 (Plot Widget)
        self.tdcPlot2 = pg.PlotWidget(title="Coincidences Histogram")
        self.tdcPlot2.setBackground('w')
        self.tdcPlot2.setLabel('left', labelStyle + 'Coincidences')
        self.tdcPlot2.setLabel('bottom', labelStyle + 'Time Delay')
        self.tdcPlot2.getAxis('left').tickFont = font
        self.tdcPlot2.getAxis('bottom').tickFont = font
        self.tdcPlot2.getAxis('bottom').setPen(color='k')
        self.tdcPlot2.getAxis('left').setPen(color='k')
        self.tdcPlot2.showGrid(y=True)

        # Setting up data plots (Plot data item)
        self.lineStyle1 = pg.mkPen(width=2, color='r')  # Red
        self.lineStyle2 = pg.mkPen(width=2, color='g')  # Green
        self.lineStyle3 = pg.mkPen(width=2, color='b')  # Blue
        self.lineStyle4 = pg.mkPen(width=2, color='k')  # Black
        self.lineStyle0 = pg.mkPen(width=1, color='r')

        # Plotting the graph - https://pyqtgraph.readthedocs.io/en/latest/plotting.html for organisation of plotting classes
        # Take note: multiple plotDataItems can sit on one plotWidget
        self.linePlot1 = self.tdcPlot.plot(self.x,
                                           self.y1,
                                           pen=self.lineStyle1)
        self.linePlot2 = self.tdcPlot.plot(self.x,
                                           self.y2,
                                           pen=self.lineStyle2)
        self.linePlot3 = self.tdcPlot.plot(self.x,
                                           self.y3,
                                           pen=self.lineStyle3)
        self.linePlot4 = self.tdcPlot.plot(self.x,
                                           self.y4,
                                           pen=self.lineStyle4)
        self.histogramPlot = self.tdcPlot2.plot(self.x0,
                                                self.y0,
                                                pen=self.lineStyle0,
                                                symbol='x',
                                                symbolPen='b',
                                                symbolBrush=0.2)
        self.linePlots = [
            self.linePlot1, self.linePlot2, self.linePlot3, self.linePlot4
        ]
        #---------PLOTS---------#

        #---------Main Window---------#
        self.setWindowTitle("TDC-1")
        #---------Main Window---------#

        #---------Tabs---------#
        self.tabs = QTabWidget()

        self.tab1 = QWidget()
        self.layout = QGridLayout()
        self.layout.addWidget(self.tdcPlot, 0, 0, 5, 5)
        self.layout.addWidget(self.Ch1CountsLabel, 0, 5)
        self.layout.addWidget(self.Ch2CountsLabel, 1, 5)
        self.layout.addWidget(self.Ch3CountsLabel, 2, 5)
        self.layout.addWidget(self.Ch4CountsLabel, 3, 5)
        self.tab1.setLayout(self.layout)
        self.tabs.addTab(self.tab1, "Counts")

        self.tab2 = QWidget()
        self.layout2 = QGridLayout()
        self.layout2.addWidget(self.tdcPlot2, 0, 0, 5, 5)
        self.layout2.addWidget(self.pairsRateLabel, 0, 5)
        self.tab2.setLayout(self.layout2)
        self.tabs.addTab(self.tab2, "Coincidences")
        self.tabs.currentChanged.connect(self.update_plot_tab)
        #---------Tabs---------#

        #Layout
        self.grid = QGridLayout()
        self.grid.setSpacing(20)
        self.grid.addWidget(self.deviceLabel, 0, 0)
        self.grid.addWidget(self.devCombobox, 0, 1)
        self.grid.addWidget(self.deviceModeLabel, 0, 2)
        self.grid.addWidget(self.modesCombobox, 0, 3, 1, 1)
        self.grid.addWidget(self.integrationLabel, 1, 0)
        self.grid.addWidget(self.integrationSpinBox, 1, 1)
        self.grid.addWidget(self.samplesLabel, 1, 2)
        self.grid.addWidget(self.samplesSpinbox, 1, 3, 1, 1)
        self.grid.addWidget(self.liveStart_Button, 2, 0)
        self.grid.addWidget(self.scanForDevice_Button, 2, 1)
        self.grid.addWidget(self.selectLogfile_Button, 2, 2)
        self.grid.addWidget(self.logfileLabel, 2, 3)
        self.grid.addWidget(self.tabs, 4, 0, 5, 4)

        self.singlesGroupbox = QGroupBox('Singles')
        self.singlesLayout = QHBoxLayout()
        self.singlesLayout.addWidget(self.radio1_Button)
        self.singlesLayout.addWidget(self.radio2_Button)
        self.singlesLayout.addWidget(self.radio3_Button)
        self.singlesLayout.addWidget(self.radio4_Button)
        self.singlesGroupbox.setLayout(self.singlesLayout)
        self.grid.addWidget(self.singlesGroupbox, 3, 0, 1, 2)

        self.pairsGroupbox = QGroupBox('Pairs')
        self.pairsSpinLayout = QHBoxLayout()
        self.pairsSpinLayout.addWidget(self.startChannelLabel)
        self.pairsSpinLayout.addWidget(self.channelsCombobox1)
        self.pairsSpinLayout.addWidget(self.stopChannelLabel)
        self.pairsSpinLayout.addWidget(self.channelsCombobox2)
        #self.pairsLabelLayout = QHBoxLayout()
        self.pairsCenterLayout = QHBoxLayout()
        self.pairsCenterLayout.addWidget(self.centerLabel)
        self.pairsCenterLayout.addWidget(self.centerSpinbox)
        self.pairsCenterLayout.addWidget(self.resolutionTextLabel)
        self.pairsCenterLayout.addWidget(self.resolutionSpinbox)
        self.pairsLayout = QVBoxLayout()
        #self.pairsLayout.addLayout(self.pairsLabelLayout)
        self.pairsLayout.addLayout(self.pairsSpinLayout)
        self.pairsLayout.addLayout(self.pairsCenterLayout)
        self.pairsGroupbox.setLayout(self.pairsLayout)
        self.grid.addWidget(self.pairsGroupbox, 3, 2, 1, 2)

        #Main Widget (on which the grid is to be implanted)
        self.mainwidget = QWidget()
        self.mainwidget.layout = self.grid
        self.mainwidget.setLayout(self.mainwidget.layout)
        self.mainwidget.setFont(defaultFont)
        self.setCentralWidget(self.mainwidget)

    # Connected to devComboBox.currentTextChanged
    @QtCore.pyqtSlot(str)
    def selectDevice(self, devPath: str):
        # Only allow resetting + changing device if not currently collecting data
        # Add msg box to allow user confirmation
        if self.acq_flag == False:
            self.StrongResetInternalVariables()
            self.resetDataAndPlots()
            self.resetGUIelements()
            if devPath != 'Select your device':
                self._tdc1_dev = tdc1.TimeStampTDC1(devPath)
                self.device_path = devPath

    @QtCore.pyqtSlot()
    def updateDevList(self):
        self.devCombobox.clear()
        self.devCombobox.addItem('Select your device')
        devices = serial_connection.search_for_serial_devices(
            tdc1.TimeStampTDC1.DEVICE_IDENTIFIER)
        self.devCombobox.addItems(devices)

    # Connected to modesCombobox.currentTextChanged
    @QtCore.pyqtSlot(str)
    def selectDeviceMode(self, newMode: str):
        # Only allow resetting + device mode change if not currently collecting data
        # Add msg box to allow user confirmation
        if self.acq_flag == False:
            self.WeakResetInternalVariables()
            self.resetDataAndPlots()
            self.resetGUIelements()
            if newMode != 'Select mode':
                if self._tdc1_dev == None:
                    self._tdc1_dev = tdc1.TimeStampTDC1(self.device_path)
                self._tdc1_dev.mode = newMode  # Setting tdc1 mode with @setter
                self._dev_mode = newMode

    # Update plot index on plot tab change
    @QtCore.pyqtSlot()
    def update_plot_tab(self):
        self._plot_tab = self.tabs.currentIndex()

    # Update integration time on spinbox value change
    @QtCore.pyqtSlot(int)
    def update_intTime(self, int_time: int):
        # Convert to seconds
        self.integration_time = int_time * 1e-3
        if self.logger:
            self.logger.int_time = int_time * 1e-3

    @QtCore.pyqtSlot(int)
    def updatePlotSamples(self, samples: int):
        self.plotSamples = samples

    # Click Live Start button to get started!
    @QtCore.pyqtSlot()
    # Connected to self.liveStart_button.clicked
    def liveStart(self):
        #If currently live plotting
        if self.acq_flag is True and self.liveStart_Button.text(
        ) == "Live Stop":
            self.acq_flag = False
            self.logger.active_flag = False  # To stop logger from looping
            self.selectLogfile_Button.setEnabled(True)
            self.liveStart_Button.setText("Live Start")
            time.sleep(1)  # Pause to let all loops end
            self._tdc1_dev = None  # Destroy tdc1_dev object
            self.logger = None  # Destroy logger
            self.logger_thread = None  # and thread...?
        #If not currently live plotting
        elif self.acq_flag is False and self.liveStart_Button.text(
        ) == "Live Start":
            if self._tdc1_dev == None:
                self._tdc1_dev = tdc1.TimeStampTDC1(
                    self.devCombobox.currentText())
            self.acq_flag = True
            self.resetDataAndPlots()
            self.selectLogfile_Button.setEnabled(False)
            self.modesCombobox.setEnabled(True)
            self.liveStart_Button.setText("Live Stop")
            self.startLogging()

    # Logging
    def startLogging(self):
        """[summary]
        Creation process of worker object and QThread.
        """
        # Create worker instance and a thread
        self.logger = logWorker()
        self.logger_thread = QtCore.QThread(
            self)  # QThread is not a thread, but a thread MANAGER

        # Assign worker to the thread and start the thread
        self.logger.moveToThread(self.logger_thread)
        self.logger_thread.start(
        )  # This is where the thread is actually created, I think

        # Connect signals and slots AFTER moving the object to the thread
        self.logging_requested.connect(self.logger.log_which_data)
        self.logger.data_is_logged.connect(self.update_data_from_thread)
        self.logger.histogram_logged.connect(self.updateHistogram)

        self.logger.int_time = int(
            self.integrationSpinBox.text()) * 1e-3  # Convert to seconds
        #self.log_flag = True
        self.logging_requested.emit(self._logfile_name, self.device_path, self.log_flag, self._dev_mode, \
            self._tdc1_dev)

    @QtCore.pyqtSlot()
    def selectLogfile(self):
        if self.acq_flag == False:
            if self.selectLogfile_Button.text() == 'Select Logfile':
                default_filetype = 'csv'
                start = datetime.now().strftime(
                    "%Y%m%d_%Hh%Mm%Ss ") + "_TDC1." + default_filetype
                self._logfile_name = QtGui.QFileDialog.getSaveFileName(
                    self, "Save to log file", start)[0]
                self.logfileLabel.setText(self._logfile_name)
                if self._logfile_name != '':
                    #self.startLogging_Button.setEnabled(True)
                    self.log_flag = True
                    self.selectLogfile_Button.setText('Unselect Logfile')
            elif self.selectLogfile_Button.text() == 'Unselect Logfile':
                self.logfileLabel.setText('')
                self.selectLogfile_Button.setText('Select Logfile')

    # Updating data
    # Connected to data_is_logged signal
    @QtCore.pyqtSlot(tuple, str, list)
    def update_data_from_thread(self, data: tuple, dev_mode: str,
                                radio_flags: list):
        if len(self.x) == PLT_SAMPLES:
            self.x = self.x[1:]
            self.x.append(self.x[-1] + 1)
            self.y1 = self.y1[1:]
            self.y2 = self.y2[1:]
            self.y3 = self.y3[1:]
            self.y4 = self.y4[1:]
        else:
            self.x.append(len(self.x) +
                          1)  # Takes care of empty list case as well
        self.y1.append(data[0])
        self.y2.append(data[1])
        self.y3.append(data[2])
        self.y4.append(data[3])
        self.idx = min(len(self.y1), self.plotSamples)
        self.y1new = self.y1[-self.idx:]
        self.y2new = self.y2[-self.idx:]
        self.y3new = self.y3[-self.idx:]
        self.y4new = self.y4[-self.idx:]
        self.y_data = [self.y1new, self.y2new, self.y3new, self.y4new]
        self._radio_flags = radio_flags
        self.Ch1CountsLabel.setText(str(data[0]))
        self.Ch2CountsLabel.setText(str(data[1]))
        self.Ch3CountsLabel.setText(str(data[2]))
        self.Ch4CountsLabel.setText(str(data[3]))
        self.updatePlots(self._radio_flags)

    # Updating plots 1-4
    def updatePlots(self, radio_flags: list):
        for i in range(len(radio_flags)):
            if radio_flags[i] == 1:
                self.linePlots[i].setData(self.x[-self.idx:],
                                          self.y_data[i][-self.idx:])

    # Radio button slots (functions)
    @QtCore.pyqtSlot('PyQt_PyObject')
    def displayPlot1(self, b: QRadioButton):
        if self.acq_flag == True:
            if b.isChecked() == True:
                # Possible to clear self.x and self.y1 without disrupting the worker loop?
                self.updatePlots(self._radio_flags)
                self.linePlot1.setPen(self.lineStyle1)
                self.logger.radio_flags[0] = 1
                self._radio_flags[0] = 1
            elif b.isChecked() == False:
                self.linePlot1.setPen(None)
                self.logger.radio_flags[0] = 0
                self._radio_flags[0] = 0

    @QtCore.pyqtSlot('PyQt_PyObject')
    def displayPlot2(self, b: QRadioButton):
        if self.acq_flag == True:
            if b.isChecked() == True:
                self.updatePlots(self._radio_flags)
                self.linePlot2.setPen(self.lineStyle2)
                self.logger.radio_flags[1] = 1
                self._radio_flags[1] = 1
            elif b.isChecked() == False:
                self.linePlot2.setPen(None)
                self.logger.radio_flags[1] = 0
                self._radio_flags[1] = 0

    @QtCore.pyqtSlot('PyQt_PyObject')
    def displayPlot3(self, b: QRadioButton):
        if self.acq_flag == True:
            if b.isChecked() == True:
                self.updatePlots(self._radio_flags)
                self.linePlot3.setPen(self.lineStyle3)
                self.logger.radio_flags[2] = 1
                self._radio_flags[2] = 1
            elif b.isChecked() == False:
                self.linePlot3.setPen(None)
                self.logger.radio_flags[2] = 0
                self._radio_flags[2] = 0

    @QtCore.pyqtSlot('PyQt_PyObject')
    def displayPlot4(self, b: QRadioButton):
        if self.acq_flag == True:
            if b.isChecked():
                self.updatePlots(self._radio_flags)
                self.linePlot4.setPen(self.lineStyle4)
                self.logger.radio_flags[3] = 1
                self._radio_flags[3] = 1
            elif b.isChecked() == False:
                self.linePlot4.setPen(None)
                self.logger.radio_flags[3] = 0
                self._radio_flags[3] = 0

    @QtCore.pyqtSlot(str)
    def updateStart(self, channel: str):
        cs = int(channel)
        if self.acq_flag == True and self.modesCombobox.currentText(
        ) == "pairs":
            self._ch_start = cs
            if self.logger:
                self.logger.ch_start = cs

    @QtCore.pyqtSlot(str)
    def updateStop(self, channel: str):
        cs = int(channel)
        if self.acq_flag == True and self.modesCombobox.currentText(
        ) == "pairs":
            self._ch_stop = cs
            if self.logger:
                self.logger.ch_stop = cs

    # Histogram
    # Connected to histogram_logged signal
    def updateHistogram(self, g2_data: dict):
        # {int - ch_start counts, int- ch_stop counts, int - actual acq time, float - time bins, float - histogram values}
        # time bins and histogram vals are both np arrays
        incremental_y = g2_data['histogram']
        incremental_y_int = incremental_y.astype(np.int32)
        self.y0 += incremental_y_int
        center = self.centerSpinbox.currentValue()
        idx = int(-(-center //
                    self.binsize))  # Upside-down floor div aka ceiling div
        startidx = idx - idx_width
        stopidx = idx + idx_width
        try:
            self.x0new = self.x0[startidx:stopidx]
            self.y0new = self.y0[startidx:stopidx]
        # If startidx is negative
        except IndexError:
            try:
                self.x0new = self.x0[:stopidx]
                self.y0new = self.y0[:stopidx]
            except IndexError:
                try:
                    self.x0new = self.x0[startidx:]
                    self.y0new = self.y0[startidx:]
                except:
                    pass
        self.histogramPlot.setData(self.x0new, self.y0new)
        totalpairs = np.sum(self.y0, dtype=np.int32)
        self.pairsRateLabel.setText("<font size=24>Pairs/sec: </font><br>" +
                                    "<font size=96>" + totalpairs + "</font>")

    # Reserve in case above func fails
    # def updateHistogram(self, g2_data: dict):
    #     # {int - ch_start counts, int- ch_stop counts, int - actual acq time, float - time bins, float - histogram values}
    #     # time bins and histogram vals are both np arrays
    #     incremental_y = g2_data['histogram']
    #     incremental_y_int = incremental_y.astype(np.int32)
    #     self.y0 += incremental_y_int
    #     self.idx = min(len(self.y0), self.plotSamples)
    #     self.x0new = self.x0[:(self.idx)]
    #     self.y0new = self.y0[:(self.idx)]
    #     self.histogramPlot.setData(self.x0new, self.y0new)
    #     totalpairs = np.sum(self.y0, dtype=np.int32)
    #     self.pairsRateLabel.setText("<font size=24>Pairs/sec: </font><br>" + "<font size=96>" + totalpairs + "</font>")

    @QtCore.pyqtSlot(int)
    def updateBins(self, bin_width):
        self.binsize = bin_width
        self.x0 = np.arange(0, self.bins * self.binsize, self.binsize)

    # For future use
    def StrongResetInternalVariables(self):
        self.integration_time = 1
        self._logfile_name = ''  # Track the logfile(csv) being used by GUI
        self.resetWorkerAndThread()

        self._tdc1_dev = None  # tdc1 device object
        self._dev_mode = ''  # 0 = 'singles', 1 = 'pairs', 3 = 'timestamp'
        self.device_path = ''  # Device path, eg. 'COM4'

    def WeakResetInternalVariables(self):
        # Excludes resetting variables relating to the device object (device, mode, path)
        self.integration_time = 1
        self._logfile_name = ''  # Track the logfile(csv) being used by GUI
        self.resetWorkerAndThread()

    def resetWorkerAndThread(self):
        time.sleep(2)  # To allow threads to end
        self.logger = None
        self.logger_thread = None

    # For future use
    def resetGUIelements(self):
        self.liveStart_Button.setEnabled(True)
        self.selectLogfile_Button.setEnabled(True)
        self.logfileLabel.setText('')
        self.radio1_Button.setChecked(False)
        self.radio2_Button.setChecked(False)
        self.radio3_Button.setChecked(False)
        self.radio4_Button.setChecked(False)
        self.integrationSpinBox.setValue(1000)
        self.samplesSpinbox.setValue(501)

    def resetDataAndPlots(self):
        self.x0 = np.arange(0, self.bins * self.binsize, self.binsize)
        self.y0 = np.zeros_like(self.x0)
        self.x0new = []
        self.y0new = []
        self.x = []
        self.y1 = []
        self.y2 = []
        self.y3 = []
        self.y4 = []
        self.xnew = []
        self.y1new = []
        self.y2new = []
        self.y3new = []
        self.y4new = []
        self.linePlot1.setData(self.x, self.y1)
        self.linePlot2.setData(self.x, self.y2)
        self.linePlot3.setData(self.x, self.y3)
        self.linePlot4.setData(self.x, self.y4)
        self.histogramPlot.setData(self.x0, self.y0)
        self.radio1_Button.setChecked(False)
        self.radio2_Button.setChecked(False)
        self.radio3_Button.setChecked(False)
        self.radio4_Button.setChecked(False)
        self._radio_flags = [0, 0, 0, 0]
Exemple #6
0
class PreviewArea(QWidget):
    def __init__(self):
        super().__init__()
        self.bundle = None
        self.manual_change = True
        size_policy = QSizePolicy()
        size_policy.setHorizontalPolicy(QSizePolicy.Expanding)
        size_policy.setVerticalPolicy(QSizePolicy.Expanding)
        self.setSizePolicy(size_policy)
        layout = QVBoxLayout()
        layout.setContentsMargins(0, 4, 0, 0)
        this_row = QHBoxLayout()
        this_row.addSpacing(4)
        selection_label = QLabel()
        selection_label.setText("Dieses Bild: ")
        this_row.addWidget(selection_label)
        self.keep_button = QRadioButton()
        self.keep_button.setText("behalten")
        self.keep_button.setMaximumHeight(14)
        self.keep_button.toggled.connect(self.mark_bundle)
        this_row.addWidget(self.keep_button)
        self.discard_button = QRadioButton()
        self.discard_button.setText("löschen")
        self.discard_button.setMaximumHeight(14)
        this_row.addWidget(self.discard_button)
        this_row.addStretch(1)
        layout.addLayout(this_row)
        img_scroll_area = QScrollArea()
        img_scroll_area.setSizePolicy(
            QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding))
        self.img_widget = ImageWidget(None)
        img_scroll_area.setWidget(self.img_widget)
        layout.addWidget(img_scroll_area, stretch=1)
        layout.addStretch()
        self.setLayout(layout)

    def set_image(self, img_d):
        self.manual_change = False
        self.bundle = img_d
        self.bundle.data_changed.connect(self.bundle_changed)
        self.img_widget.set_img(img_d.get_image())
        self.bundle_changed()
        self.update()
        self.manual_change = True

    def mark_bundle(self, keep=False):
        if self.manual_change:
            self.manual_change = False
            self.bundle.set_manual(keep)
        self.manual_change = True

    def bundle_changed(self):
        if self.bundle.keep is None:
            self.discard_button.setAutoExclusive(False)
            self.keep_button.setAutoExclusive(False)
            self.discard_button.setChecked(False)
            self.keep_button.setChecked(False)
            self.discard_button.setAutoExclusive(True)
            self.keep_button.setAutoExclusive(True)
        elif not self.bundle.keep:
            self.discard_button.setChecked(True)
        else:
            self.keep_button.setChecked(True)
Exemple #7
0
 def createCardSelector(self, card, parent):
     btn = QRadioButton(None, parent)
     btn.setChecked(False)
     btn.setAutoExclusive(True)
     return btn
Exemple #8
0
    def __init__(self, *args, **kwargs):
        super(CompanyTab, self).__init__(*args, **kwargs)

        self.setWidgetResizable(True)
        scroll_content = QWidget(self)
        main_layout = QVBoxLayout(scroll_content)
        scroll_content.setLayout(main_layout)
        self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)

        ga = QGroupBox()
        ga.setTitle("Genetical Algorithm")
        main_layout.addWidget(ga)

        self.layout_ga = QVBoxLayout()
        ga_fl = QHBoxLayout()
        population_label = QLabel("Population: ")
        population_edit = QSpinBox()
        population_edit.setMaximum(999)
        population_edit.setMinimum(2)
        population_edit.setValue(100)
        ga_fl.addStretch(1)

        ga_fl.addWidget(population_label)
        ga_fl.addWidget(population_edit)

        ga_fl.addStretch(1)

        generations_label = QLabel("Generations: ")
        generations_edit = QSpinBox()
        generations_edit.setMaximum(999)
        generations_edit.setMinimum(10)
        generations_edit.setValue(100)
        ga_fl.addWidget(generations_label)
        ga_fl.addWidget(generations_edit)

        ga_fl.addStretch(1)

        self.layout_ga.addLayout(ga_fl)

        t = CollapsibleBox(title="Advanced Options")

        genetical_layout = QVBoxLayout()
        one = QHBoxLayout()

        parameters = QGroupBox()
        parameters_layout = QHBoxLayout()
        parameters.setTitle("Parameters")

        mu_label = QLabel("μ: ")
        mu_edit = QDoubleSpinBox()
        mu_edit.setMaximum(1)
        mu_edit.setValue(1)
        mu_edit.setSingleStep(0.05)
        parameters_layout.addStretch(1)
        parameters_layout.addWidget(mu_label)
        parameters_layout.addWidget(mu_edit)
        parameters_layout.addStretch(1)

        lambda_label = QLabel("λ: ")
        lambda_edit = QDoubleSpinBox()
        lambda_edit.setValue(3)
        parameters_layout.addWidget(lambda_label)
        parameters_layout.addWidget(lambda_edit)
        parameters_layout.addStretch(1)
        parameters.setLayout(parameters_layout)
        one.addWidget(parameters)

        crossover = QGroupBox()
        crossover_layout = QHBoxLayout()
        crossover.setTitle("Crossover")

        cx_eta_label = QLabel("Eta: ")
        cx_eta_label.setToolTip(
            "High eta will produce children resembling to their parents, while a small eta will produce solutions much more different."
        )
        cx_eta_edit = QSpinBox()
        cx_eta_edit.setToolTip(
            "High eta will produce children resembling to their parents, while a small eta will produce solutions much more different."
        )
        cx_eta_edit.setValue(5)
        crossover_layout.addStretch(1)
        crossover_layout.addWidget(cx_eta_label)
        crossover_layout.addWidget(cx_eta_edit)
        crossover_layout.addStretch(1)

        cx_pb_label = QLabel("Probability: ")
        cx_pb_edit = QDoubleSpinBox()
        cx_pb_edit.setValue(0.5)
        cx_pb_edit.setMaximum(1)
        cx_pb_edit.setSingleStep(0.05)
        crossover_layout.addWidget(cx_pb_label)
        crossover_layout.addWidget(cx_pb_edit)
        crossover_layout.addStretch(1)
        crossover.setLayout(crossover_layout)
        one.addWidget(crossover)

        genetical_layout.addLayout(one)
        two = QHBoxLayout()

        mutations = QGroupBox()
        mutations_layout = QHBoxLayout()
        mutations.setTitle("Mutations")

        mut_eta_label = QLabel("Eta: ")
        mut_eta_label.setToolTip(
            "High eta will produce children resembling to their parents, while a small eta will produce solutions much more different."
        )
        mut_eta_edit = QSpinBox()
        mut_eta_edit.setToolTip(
            "High eta will produce children resembling to their parents, while a small eta will produce solutions much more different."
        )
        mut_eta_edit.setValue(5)
        mutations_layout.addStretch(1)
        mutations_layout.addWidget(mut_eta_label)
        mutations_layout.addWidget(mut_eta_edit)
        mutations_layout.addStretch(1)

        mut_pb_label = QLabel("Probability: ")
        mut_pb_edit = QDoubleSpinBox()
        mut_pb_edit.setMaximum(1)
        mut_pb_edit.setSingleStep(0.05)
        mut_pb_edit.setValue(0.5)
        mutations_layout.addWidget(mut_pb_label)
        mutations_layout.addWidget(mut_pb_edit)
        mutations_layout.addStretch(1)

        mut_indpb_label = QLabel("Indp probability: ")
        mut_indpb_edit = QDoubleSpinBox()
        mut_indpb_edit.setValue(1.0)
        mut_indpb_edit.setMaximum(1)
        mut_indpb_edit.setSingleStep(0.05)
        mutations_layout.addWidget(mut_indpb_label)
        mutations_layout.addWidget(mut_indpb_edit)
        mutations_layout.addStretch(1)
        mutations.setLayout(mutations_layout)
        two.addWidget(mutations)

        genetical_layout.addLayout(two)

        t.setContentLayout(genetical_layout)
        self.layout_ga.addWidget(t)

        ga.setLayout(self.layout_ga)

        #

        similarity = QGroupBox()
        similarity.setTitle("Similarity")
        similarity_layout = QHBoxLayout()
        molecules = QVBoxLayout()
        label = QLabel("Molecule(s) for the comparison")
        ligand = QRadioButton("Ligand")
        ligand.setAutoExclusive(False)
        protein = QRadioButton("Protein")
        protein.setAutoExclusive(False)
        metal = QRadioButton("Metal")
        metal.setAutoExclusive(False)
        molecules.addWidget(label)
        molecules.addWidget(ligand)
        molecules.addWidget(protein)
        molecules.addWidget(metal)
        similarity_layout.addLayout(molecules)

        threshold_layout = QHBoxLayout()
        threshold_label = QLabel("Threshold: ")
        threshold = QDoubleSpinBox()
        threshold.setSingleStep(0.05)
        threshold_layout.addStretch(1)
        threshold_layout.addWidget(threshold_label)
        threshold_layout.addWidget(threshold)
        threshold_layout.addStretch(1)
        similarity_layout.addLayout(threshold_layout)

        similarity.setLayout(similarity_layout)

        main_layout.addWidget(similarity)

        #

        output = QGroupBox()
        output.setTitle("Output")
        output_layout = QVBoxLayout()

        name = QHBoxLayout()
        name_label = QPushButton("Save as...")
        self.name_edit = QLineEdit()
        self.name_edit.setText(os.getcwd())
        name_label.clicked.connect(self.save_path)
        name.addWidget(name_label)
        name.addWidget(self.name_edit)
        # output_layout.addLayout(name)

        precision_label = QLabel("Precision: ")
        precision_edit = QSpinBox()
        precision_edit.setMinimum(-3)
        precision_edit.setMaximum(6)
        precision_edit.setValue(3)

        name.addWidget(precision_label)
        name.addWidget(precision_edit)
        output_layout.addLayout(name)

        one = QHBoxLayout()
        compress = QCheckBox("Compress")
        compress.setChecked(True)
        verbose = QCheckBox("Verbose")
        verbose.setChecked(True)
        history = QCheckBox("History")
        check = QCheckBox("Check every")
        self.check_edit = QSpinBox()
        self.check_edit.setValue(10)
        self.check_edit.setEnabled(False)
        check.clicked.connect(self.check_edit_connect)
        pareto = QCheckBox("Pareto")
        prompt = QCheckBox("Prompt of exception")

        one.addWidget(compress)
        one.addStretch(1)
        one.addWidget(verbose)
        one.addStretch(1)
        one.addWidget(history)
        one.addStretch(1)
        one.addWidget(check)
        one.addWidget(self.check_edit)
        one.addStretch(1)
        one.addWidget(pareto)
        one.addStretch(1)
        one.addWidget(prompt)
        output_layout.addLayout(one)

        output.setLayout(output_layout)

        main_layout.addWidget(output)

        #

        main_layout.addWidget(QFrame())
        main_layout.addWidget(QFrame())
        main_layout.addWidget(QFrame())
        main_layout.addWidget(QFrame())

        self.setWidget(scroll_content)
Exemple #9
0
class StartRabbitMQUtil(QtWidgets.QMainWindow):
    
    def __init__(self, parser):
        super(StartRabbitMQUtil, self).__init__()        

        self.setWindowTitle("RabbitMQ Utility Launcher")    # Set the window title
        #------------------------------
        #self.app = app
        self.parser = parser

        self.frame = None

        self.env = ""

        #------------------------------ 
        self.qapp = QApplication([])
        #V = self.qapp.desktop().screenGeometry()
        #h = V.height() - 600 
        #w = V.width() - 600

        h = 200 
        w = 250

        self.resize(w, h)

        self.widget = QWidget()           

        self.rmqmw = None
        self.rmqMenu = None     

        self.mainexit = False 

        self.statusBar = QStatusBar()
        self.setStatusBar(self.statusBar)

        self.envBox = QGroupBox("Environment")
        
        hbox = QHBoxLayout()

        vbrb = QVBoxLayout()
        

        #hbox1 = QHBoxLayout()
        self.envDev = QRadioButton("Development")
        self.envQa = QRadioButton("Qa")
        self.envProd = QRadioButton("Production")
        
        self.envDev.toggled.connect(lambda:self.btnstate(self.envDev))
        self.envQa.toggled.connect(lambda:self.btnstate(self.envQa))
        self.envProd.toggled.connect(lambda:self.btnstate(self.envProd))
        
        vbrb.addWidget(self.envDev)
        vbrb.addWidget(self.envQa)
        vbrb.addWidget(self.envProd)

        #hbox1.addLayout(vbrb)

        self.envBox.setLayout(vbrb)
        hbox.addWidget(self.envBox)

        hbox2 = QHBoxLayout()
        self.launchButton = QPushButton("Launch")
        self.launchButton.setEnabled(False)
        self.launchButton.clicked.connect(self.launch)

        self.closeButton = QPushButton("Reset")
        self.closeButton.setEnabled(False)
        self.closeButton.clicked.connect(self.closermq)

        exitButton =  QPushButton("Exit")
        exitButton.clicked.connect(self.closewin)

        hbox2.addWidget(self.launchButton)   
        hbox2.addWidget(self.closeButton)   
        hbox2.addWidget(exitButton)   

        vbox = QVBoxLayout()
        
        vbox.addLayout(hbox)
        vbox.addLayout(hbox2)
        
        self.widget.setLayout(vbox)

        self.setCentralWidget(self.widget)     

    def btnstate(self,b):
        radioButton = b.text()

        if len(radioButton) != 0 and b.isChecked() == True:
            self.launchButton.setEnabled(True)

        if radioButton == "Development" and b.isChecked() == True:
            self.env = "dev"
        elif radioButton == "Qa" and b.isChecked() == True:
            self.env = "qa"    
        else:
            self.env = "prod"

    def closermq(self):
        
        self.envDev.setEnabled(True)
        self.envQa.setEnabled(True)
        self.envProd.setEnabled(True)

        self.envDev.setAutoExclusive(False)
        self.envDev.setChecked(False)
        self.envDev.setAutoExclusive(True)

        self.envQa.setAutoExclusive(False)
        self.envQa.setChecked(False)
        self.envQa.setAutoExclusive(True)

        self.envProd.setAutoExclusive(False)
        self.envProd.setChecked(False)
        self.envProd.setAutoExclusive(True)

        if  self.rmqmw is not None:
            self.rmqmw.close() 
        
        if self.rmqMenu is not None:
            self.rmqMenu.close()

        self.closeButton.setEnabled(False)

    def launch(self):
        
        self.closeButton.setEnabled(True)

        iniFile = "./resources/properties/{env}/{env}_rabbitmq.ini".format(env=self.env)
        
        os.environ['RMQ_INI_FILE'] = iniFile

        self.parser.read(iniFile)        
        
        self.rmqMenu = RMQUMenu(self.parser)   
        self.rmqmw = windows.ModernWindow(window=self.rmqMenu, parser=self.parser)
        self.rmqMenu.setFrame(self.rmqmw)

        self.rmqMenu._mainExited.connect(self.mainexited)
      
        self.rmqmw.show()
        self.rmqMenu.show()

        self.envDev.setEnabled(False)
        self.envQa.setEnabled(False)
        self.envProd.setEnabled(False)

        self.launchButton.setEnabled(False)
        
    def mainexited(self, exited):
        self.mainexit = exited

    def setFrame(self, frame):
        self.frame = frame

    def closewin(self):

        if not self.mainexit:
            if  self.rmqmw is not None:
                self.rmqmw.close() 
            
            if self.rmqMenu is not None:
                self.rmqMenu.close()
           

        #self.close()
        #self.frame.close()

        self.qapp.quit()
    
    
    def closeEvent(self, event):
        if not self.mainexit:
            if  self.rmqmw is not None:
                self.rmqmw.close() 
            
            if self.rmqMenu is not None:
                self.rmqMenu.close()
           

        #self.close()
        #self.frame.close()

        self.qapp.quit()
Exemple #10
0
class FrontEndRunner(QWidget):
    """Main FrontEnd Runner window for creating workspace and starting SuRVoS2."""

    def __init__(self, run_config, workspace_config, pipeline_config, *args, **kwargs):
        super().__init__()

        self.run_config = run_config
        self.workspace_config = workspace_config
        self.pipeline_config = pipeline_config

        self.server_process = None
        self.client_process = None

        pipeline_config_ptree = self.init_ptree(self.pipeline_config, name="Pipeline")

        self.layout = QVBoxLayout()
        tabwidget = QTabWidget()
        tab1 = QWidget()
        tab2 = QWidget()

        tabwidget.addTab(tab1, "Setup and Start Survos")
        tabwidget.addTab(tab2, "Pipeline")

        self.create_workspace_button = QPushButton("Create workspace")

        tab1.layout = QVBoxLayout()
        tab1.setLayout(tab1.layout)

        chroot_fields = self.get_chroot_fields()
        tab1.layout.addWidget(chroot_fields)

        self.setup_adv_run_fields()
        self.adv_run_fields.hide()

        run_fields = self.get_run_fields()
        tab1.layout.addWidget(run_fields)

        output_config_button = QPushButton("Save config")
        tab2.layout = QVBoxLayout()
        tab2.setLayout(tab2.layout)
        tab2.layout.addWidget(pipeline_config_ptree)
        tab2.layout.addWidget(output_config_button)

        self.create_workspace_button.clicked.connect(self.create_workspace_clicked)
        output_config_button.clicked.connect(self.output_config_clicked)
        self.layout.addWidget(tabwidget)

        self.setGeometry(300, 300, 600, 400)
        self.setWindowTitle("SuRVoS Settings Editor")
        current_fpth = os.path.dirname(os.path.abspath(__file__))
        self.setWindowIcon(QIcon(os.path.join(current_fpth, "resources", "logo.png")))
        self.setLayout(self.layout)
        self.show()

    def get_workspace_fields(self):
        """Gets the QGroupBox that contains all the fields for setting up the workspace.

        Returns:
            PyQt5.QWidgets.GroupBox: GroupBox with workspace fields.
        """

        select_data_button = QPushButton("Select")
        workspace_fields = QGroupBox("Create Workspace:")
        wf_layout = QGridLayout()
        wf_layout.addWidget(QLabel("Data File Path:"), 0, 0)
        current_data_path = Path(
            self.workspace_config["datasets_dir"], self.workspace_config["vol_fname"]
        )
        self.data_filepth_linedt = QLineEdit(str(current_data_path))
        wf_layout.addWidget(self.data_filepth_linedt, 1, 0, 1, 2)
        wf_layout.addWidget(select_data_button, 1, 2)
        wf_layout.addWidget(QLabel("HDF5 Internal Data Path:"), 2, 0, 1, 1)
        ws_dataset_name = self.workspace_config["dataset_name"]
        internal_h5_path = (
            ws_dataset_name
            if str(ws_dataset_name).startswith("/")
            else "/" + ws_dataset_name
        )
        self.h5_intpth_linedt = QLineEdit(internal_h5_path)
        wf_layout.addWidget(self.h5_intpth_linedt, 2, 1, 1, 1)
        wf_layout.addWidget(QLabel("Workspace Name:"), 3, 0)
        self.ws_name_linedt_1 = QLineEdit(self.workspace_config["workspace_name"])
        wf_layout.addWidget(self.ws_name_linedt_1, 3, 1)
        wf_layout.addWidget(QLabel("Downsample Factor:"), 4, 0)
        self.downsample_spinner = QSpinBox()
        self.downsample_spinner.setRange(1, 10)
        self.downsample_spinner.setSpecialValueText("None")
        self.downsample_spinner.setMaximumWidth(60)
        self.downsample_spinner.setValue(int(self.workspace_config["downsample_by"]))
        wf_layout.addWidget(self.downsample_spinner, 4, 1, 1, 1)
        # ROI
        self.setup_roi_fields()
        wf_layout.addWidget(self.roi_fields, 4, 2, 1, 2)
        self.roi_fields.hide()

        wf_layout.addWidget(self.create_workspace_button, 5, 0, 1, 3)
        workspace_fields.setLayout(wf_layout)
        select_data_button.clicked.connect(self.launch_data_loader)
        return workspace_fields

    def setup_roi_fields(self):
        """Sets up the QGroupBox that displays the ROI dimensions, if selected."""
        self.roi_fields = QGroupBox("ROI:")
        roi_fields_layout = QHBoxLayout()
        # z
        roi_fields_layout.addWidget(QLabel("z:"), 0)
        self.zstart_roi_val = QLabel("0")
        roi_fields_layout.addWidget(self.zstart_roi_val, 1)
        roi_fields_layout.addWidget(QLabel("-"), 2)
        self.zend_roi_val = QLabel("0")
        roi_fields_layout.addWidget(self.zend_roi_val, 3)
        # y
        roi_fields_layout.addWidget(QLabel("y:"), 4)
        self.ystart_roi_val = QLabel("0")
        roi_fields_layout.addWidget(self.ystart_roi_val, 5)
        roi_fields_layout.addWidget(QLabel("-"), 6)
        self.yend_roi_val = QLabel("0")
        roi_fields_layout.addWidget(self.yend_roi_val, 7)
        # x
        roi_fields_layout.addWidget(QLabel("x:"), 8)
        self.xstart_roi_val = QLabel("0")
        roi_fields_layout.addWidget(self.xstart_roi_val, 9)
        roi_fields_layout.addWidget(QLabel("-"), 10)
        self.xend_roi_val = QLabel("0")
        roi_fields_layout.addWidget(self.xend_roi_val, 11)

        self.roi_fields.setLayout(roi_fields_layout)

    def setup_adv_run_fields(self):
        """Sets up the QGroupBox that displays the advanced optiona for starting SuRVoS2."""
        self.adv_run_fields = QGroupBox("Advanced Run Settings:")
        adv_run_layout = QGridLayout()
        adv_run_layout.addWidget(QLabel("Server IP Address:"), 0, 0)
        self.server_ip_linedt = QLineEdit(self.run_config["server_ip"])
        adv_run_layout.addWidget(self.server_ip_linedt, 0, 1)
        adv_run_layout.addWidget(QLabel("Server Port:"), 1, 0)
        self.server_port_linedt = QLineEdit(self.run_config["server_port"])
        adv_run_layout.addWidget(self.server_port_linedt, 1, 1)
        # SSH Info
        self.ssh_button = QRadioButton("Use SSH")
        self.ssh_button.setAutoExclusive(False)
        adv_run_layout.addWidget(self.ssh_button, 0, 2)
        ssh_flag = self.run_config.get("use_ssh", False)
        if ssh_flag:
            self.ssh_button.setChecked(True)
        self.ssh_button.toggled.connect(self.toggle_ssh)

        self.adv_ssh_fields = QGroupBox("SSH Settings:")
        adv_ssh_layout = QGridLayout()
        adv_ssh_layout.setColumnStretch(2, 2)
        ssh_host_label = QLabel("Host")
        self.ssh_host_linedt = QLineEdit(self.run_config.get("ssh_host", ""))
        adv_ssh_layout.addWidget(ssh_host_label, 0, 0)
        adv_ssh_layout.addWidget(self.ssh_host_linedt, 0, 1, 1, 2)
        ssh_user_label = QLabel("Username")
        self.ssh_username_linedt = QLineEdit(self.get_login_username())
        adv_ssh_layout.addWidget(ssh_user_label, 1, 0)
        adv_ssh_layout.addWidget(self.ssh_username_linedt, 1, 1, 1, 2)
        ssh_port_label = QLabel("Port")
        self.ssh_port_linedt = QLineEdit(self.run_config.get("ssh_port", ""))
        adv_ssh_layout.addWidget(ssh_port_label, 2, 0)
        adv_ssh_layout.addWidget(self.ssh_port_linedt, 2, 1, 1, 2)
        self.adv_ssh_fields.setLayout(adv_ssh_layout)
        adv_run_layout.addWidget(self.adv_ssh_fields, 1, 2, 2, 5)
        self.adv_run_fields.setLayout(adv_run_layout)

    def get_run_fields(self):
        """Gets the QGroupBox that contains the fields for starting SuRVoS.

        Returns:
            PyQt5.QWidgets.GroupBox: GroupBox with run fields.
        """
        self.run_button = QPushButton("Run SuRVoS")
        advanced_button = QRadioButton("Advanced")

        run_fields = QGroupBox("Run SuRVoS:")
        run_layout = QGridLayout()
        run_layout.addWidget(QLabel("Workspace Name:"), 0, 0)

        workspaces = os.listdir(CHROOT)
        self.workspaces_list = ComboBox()
        for s in workspaces:
            self.workspaces_list.addItem(key=s)
        run_layout.addWidget(self.workspaces_list, 0, 0)

        self.ws_name_linedt_2 = QLineEdit(self.workspace_config["workspace_name"])
        self.ws_name_linedt_2.setAlignment(Qt.AlignLeft)
        run_layout.addWidget(self.ws_name_linedt_2, 0, 2)
        run_layout.addWidget(advanced_button, 1, 0)
        run_layout.addWidget(self.adv_run_fields, 2, 1)
        run_layout.addWidget(self.run_button, 3, 0, 1, 3)
        run_fields.setLayout(run_layout)

        advanced_button.toggled.connect(self.toggle_advanced)
        self.run_button.clicked.connect(self.run_clicked)

        return run_fields

    def get_login_username(self):
        try:
            user = getpass.getuser()
        except Exception:
            user = ""
        return user

    @pyqtSlot()
    def launch_data_loader(self):
        """Load the dialog box widget to select data with data preview window and ROI selection."""
        path = None
        int_h5_pth = None
        dialog = LoadDataDialog(self)
        result = dialog.exec_()
        self.roi_limits = None
        if result == QDialog.Accepted:
            path = dialog.winput.path.text()
            int_h5_pth = dialog.int_h5_pth.text()
            down_factor = dialog.downsample_spinner.value()
        if path and int_h5_pth:
            self.data_filepth_linedt.setText(path)
            self.h5_intpth_linedt.setText(int_h5_pth)
            self.downsample_spinner.setValue(down_factor)
            if dialog.roi_changed:
                self.roi_limits = tuple(map(str, dialog.get_roi_limits()))
                self.roi_fields.show()
                self.update_roi_fields_from_dialog()
            else:
                self.roi_fields.hide()

    def update_roi_fields_from_dialog(self):
        """Updates the ROI fields in the main window."""
        x_start, x_end, y_start, y_end, z_start, z_end = self.roi_limits
        self.xstart_roi_val.setText(x_start)
        self.xend_roi_val.setText(x_end)
        self.ystart_roi_val.setText(y_start)
        self.yend_roi_val.setText(y_end)
        self.zstart_roi_val.setText(z_start)
        self.zend_roi_val.setText(z_end)

    @pyqtSlot()
    def toggle_advanced(self):
        """Controls displaying/hiding the advanced run fields on radio button toggle."""
        rbutton = self.sender()
        if rbutton.isChecked():
            self.adv_run_fields.show()
        else:
            self.adv_run_fields.hide()

    @pyqtSlot()
    def toggle_ssh(self):
        """Controls displaying/hiding the SSH fields on radio button toggle."""
        rbutton = self.sender()
        if rbutton.isChecked():
            self.adv_ssh_fields.show()
        else:
            self.adv_ssh_fields.hide()

    def setup_ptree_params(self, p, config_dict):
        def parameter_tree_change(param, changes):
            for param, change, data in changes:
                path = p.childPath(param)

                if path is not None:
                    childName = ".".join(path)
                else:
                    childName = param.name()

                sibs = param.parent().children()

                config_dict[path[-1]] = data

        p.sigTreeStateChanged.connect(parameter_tree_change)

        def valueChanging(param, value):
            pass

        for child in p.children():
            child.sigValueChanging.connect(valueChanging)

            for ch2 in child.children():
                ch2.sigValueChanging.connect(valueChanging)

        return p

    def dict_to_params(self, param_dict, name="Group"):
        ptree_param_dicts = []
        ctr = 0
        for key in param_dict.keys():
            entry = param_dict[key]

            if type(entry) == str:
                d = {"name": key, "type": "str", "value": entry}
            elif type(entry) == int:
                d = {"name": key, "type": "int", "value": entry}
            elif type(entry) == list:
                d = {"name": key, "type": "list", "values": entry}
            elif type(entry) == float:
                d = {"name": key, "type": "float", "value": entry}
            elif type(entry) == bool:
                d = {"name": key, "type": "bool", "value": entry}
            elif type(entry) == dict:
                d = self.dict_to_params(entry, name="Subgroup")[0]
                d["name"] = key + "_" + str(ctr)
                ctr += 1
            else:
                print(f"Can't handle type {type(entry)}")

            ptree_param_dicts.append(d)

        ptree_init = [{"name": name, "type": "group", "children": ptree_param_dicts}]

        return ptree_init

    def init_ptree(self, config_dict, name="Group"):
        ptree_init = self.dict_to_params(config_dict, name)
        parameters = Parameter.create(
            name="ptree_init", type="group", children=ptree_init
        )
        params = self.setup_ptree_params(parameters, config_dict)
        ptree = ParameterTree()
        ptree.setParameters(params, showTop=False)

        return ptree

    @pyqtSlot()
    def create_workspace_clicked(self):
        """Performs checks and coordinates workspace creation on button press."""
        logger.debug("Creating workspace: ")
        # Set the path to the data file
        vol_path = Path(self.data_filepth_linedt.text())
        if not vol_path.is_file():
            err_str = f"No data file exists at {vol_path}!"
            logger.error(err_str)
            self.button_feedback_response(
                err_str, self.create_workspace_button, "maroon"
            )
        else:
            self.workspace_config["datasets_dir"] = str(vol_path.parent)
            self.workspace_config["vol_fname"] = str(vol_path.name)
            dataset_name = self.h5_intpth_linedt.text()
            self.workspace_config["dataset_name"] = str(dataset_name).strip("/")
            # Set the workspace name
            ws_name = self.ws_name_linedt_1.text()
            self.workspace_config["workspace_name"] = ws_name
            # Set the downsample factor
            ds_factor = self.downsample_spinner.value()
            self.workspace_config["downsample_by"] = ds_factor
            # Set the ROI limits if they exist
            if self.roi_limits:
                self.workspace_config["roi_limits"] = self.roi_limits
            try:
                response = init_ws(self.workspace_config)
                _, error = response
                if not error:
                    self.button_feedback_response(
                        "Workspace created sucessfully",
                        self.create_workspace_button,
                        "green",
                    )
                    # Update the workspace name in the 'Run' section
                    self.ws_name_linedt_2.setText(self.ws_name_linedt_1.text())
            except WorkspaceException as e:
                logger.exception(e)
                self.button_feedback_response(
                    str(e), self.create_workspace_button, "maroon"
                )

    def button_feedback_response(self, message, button, colour_str, timeout=2):
        """Changes button colour and displays feedback message for a limited time period.

        Args:
            message (str): Message to display in button.
            button (PyQt5.QWidgets.QBushButton): The button to manipulate.
            colour_str (str): The standard CSS colour string or hex code describing the colour to change the button to.
        """
        timeout *= 1000
        msg_old = button.text()
        col_old = button.palette().button().color
        txt_col_old = button.palette().buttonText().color
        button.setText(message)
        button.setStyleSheet(f"background-color: {colour_str}; color: white")
        timer = QTimer()
        timer.singleShot(
            timeout, lambda: self.reset_button(button, msg_old, col_old, txt_col_old)
        )

    @pyqtSlot()
    def reset_button(self, button, msg_old, col_old, txt_col_old):
        """Sets a button back to its original display settings.

        Args:
            button (PyQt5.QWidgets.QBushButton): The button to manipulate.
            msg_old (str): Message to display in button.
            col_old (str): The standard CSS colour string or hex code describing the colour to change the button to.
            txt_col_old (str): The standard CSS colour string or hex code describing the colour to change the button text to.
        """
        button.setStyleSheet(f"background-color: {col_old().name()}")
        button.setStyleSheet(f"color: {txt_col_old().name()}")
        button.setText(msg_old)
        button.update()

    @pyqtSlot()
    def output_config_clicked(self):
        """Outputs pipeline config YAML file on button click."""
        out_fname = "pipeline_cfg.yml"
        logger.debug(f"Outputting pipeline config: {out_fname}")
        with open(out_fname, "w") as outfile:
            yaml.dump(
                self.pipeline_config, outfile, default_flow_style=False, sort_keys=False
            )

    def get_ssh_params(self):
        ssh_host = self.ssh_host_linedt.text()
        ssh_user = self.ssh_username_linedt.text()
        ssh_port = int(self.ssh_port_linedt.text())
        return ssh_host, ssh_user, ssh_port

    def start_server_over_ssh(self):
        params = self.get_ssh_params()
        if not all(params):
            logger.error("Not all SSH parameters given! Not connecting to SSH.")
        ssh_host, ssh_user, ssh_port = params
        # Pop up dialog to ask for password
        text, ok = QInputDialog.getText(
            None, "Login", f"Password for {ssh_user}@{ssh_host}", QLineEdit.Password
        )
        if ok and text:
            self.ssh_worker = SSHWorker(params, text, self.run_config)
            self.ssh_thread = QThread(self)
            self.ssh_worker.moveToThread(self.ssh_thread)
            self.ssh_worker.button_message_signal.connect(self.send_msg_to_run_button)
            self.ssh_worker.error_signal.connect(self.on_ssh_error)
            self.ssh_worker.finished.connect(self.start_client)
            self.ssh_worker.update_ip_linedt_signal.connect(self.update_ip_linedt)
            self.ssh_thread.started.connect(self.ssh_worker.start_server_over_ssh)
            self.ssh_thread.start()

    def closeEvent(self, event):
        reply = QMessageBox.question(
            self,
            "Quit",
            "Are you sure you want to quit? " "The server will be stopped.",
            QMessageBox.Yes | QMessageBox.No,
            QMessageBox.No,
        )
        if reply == QMessageBox.Yes:
            event.accept()
        else:
            event.ignore()

    @pyqtSlot()
    def on_ssh_error(self):
        self.ssh_error = True

    @pyqtSlot(str)
    def update_ip_linedt(self, ip):
        self.server_ip_linedt.setText(ip)

    @pyqtSlot(list)
    def send_msg_to_run_button(self, param_list):
        self.button_feedback_response(
            param_list[0], self.run_button, param_list[1], param_list[2]
        )

    @pyqtSlot()
    def run_clicked(self):
        """Starts SuRVoS2 server and client as subprocesses when 'Run' button pressed.

        Raises:
            Exception: If survos.py not found.
        """
        self.ssh_error = (
            False  # Flag which will be set to True if there is an SSH error
        )
        command_dir = os.getcwd()
        self.script_fullname = os.path.join(command_dir, "survos.py")
        if not os.path.isfile(self.script_fullname):
            raise Exception("{}: Script not found".format(self.script_fullname))
        # Retrieve the parameters from the fields TODO: Put some error checking in
        self.run_config["workspace_name"] = self.ws_name_linedt_2.text()
        self.run_config["server_port"] = self.server_port_linedt.text()
        # Temporary measure to check whether the workspace exists or not
        full_ws_path = os.path.join(
            Config["model.chroot"], self.run_config["workspace_name"]
        )
        if not os.path.isdir(full_ws_path):
            logger.error(
                f"No workspace can be found at {full_ws_path}, Not starting SuRVoS."
            )
            self.button_feedback_response(
                f"Workspace {self.run_config['workspace_name']} does not appear to exist!",
                self.run_button,
                "maroon",
            )
            return
        # Try some fancy SSH stuff here
        if self.ssh_button.isChecked():
            self.start_server_over_ssh()
        else:
            self.server_process = subprocess.Popen(
                [
                    "python",
                    self.script_fullname,
                    "start_server",
                    self.run_config["workspace_name"],
                    self.run_config["server_port"],
                ]
            )
            try:
                outs, errs = self.server_process.communicate(timeout=10)
                print(f"OUTS: {outs, errs}")
            except subprocess.TimeoutExpired:
                pass

            self.start_client()

    def start_client(self):
        if not self.ssh_error:
            self.button_feedback_response(
                "Starting Client.", self.run_button, "green", 7
            )
            self.run_config["server_ip"] = self.server_ip_linedt.text()
            self.client_process = subprocess.Popen(
                [
                    "python",
                    self.script_fullname,
                    "nu_gui",
                    self.run_config["workspace_name"],
                    str(self.run_config["server_ip"])
                    + ":"
                    + str(self.run_config["server_port"]),
                ]
            )