Beispiel #1
0
def check_running(config_path):
    pipe_parser = custom_config_parser()
    pipe_parser.load(f"{config_path}/PIPE")
    sh_parser = custom_config_parser()
    sh_parser.load(f"{config_path}/run_quandenser.sh")

    exit_code = int(pipe_parser.get("exit_code"))
    if pipe_parser.get("started") == "true":
        pipe_parser.write("started", "")  # Reset
        pid = pipe_parser.get("pid")
        output_path = sh_parser.get("OUTPUT_PATH")
        output_label = sh_parser.get("OUTPUT_PATH_LABEL")
        output_path = output_path + output_label
        if pid == "":
            msg = QMessageBox()
            msg.setIcon(QMessageBox.Critical)
            msg.setWindowTitle("Critical fail")
            msg.setText(f"Unable to start nextflow. Check console output or stdout.txt at \n{output_path}\nfor more information")
            msg.exec_()
        else:
            now = datetime.datetime.now()
            with open(f"{config_path}/jobs.txt", 'a') as job_file:
                job_file.write(pid + '\t' + output_path + '\t' + now.strftime("%Y-%m-%d %H:%M") + ' ' + time.localtime().tm_zone + '\n')
            pipe_parser.write('pid', '', isString=False)  # Reset pid
            msg = QMessageBox()
            msg.setIcon(QMessageBox.Information)
            msg.setWindowTitle("Started")
            msg.setText("Quandenser started")
            msg.setDetailedText(f"PID of the process is: {pid}")
            msg.exec_()
    def __init__(self, nf_settings_path):
        super(batch_file_viewer, self).__init__(parent=None)
        self.nf_settings_parser = custom_config_parser()
        self.nf_settings_parser.load(nf_settings_path)
        self.setRowCount(20)
        self.setColumnCount(2)
        # Fill all places so there are no "None" types in the table
        for row in range(self.rowCount()):
            for column in range(self.columnCount()):
                item = QTableWidgetItem()
                item.setText('')
                self.setItem(row, column, item)

        self.original_background = item.background()
        self.clipboard = QGuiApplication.clipboard()

        self.cellChanged.connect(
            self.check_cell)  # Needs to be after "filling for loop" above
        self.header = self.horizontalHeader()
        self.header.setSectionResizeMode(0, QHeaderView.Stretch)
        self.setHorizontalHeaderLabels(["MS files", "Label"])
        self.header.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        self.header.customContextMenuRequested.connect(self.right_click_menu)
        self.saved_text = ''
        self.store_re_text = ''
Beispiel #3
0
 def __init__(self, nf_settings_path, id=0):
     super(resume_folder_viewer, self).__init__(parent=None)
     self.type = type
     self.id = id
     self.nf_settings_parser = custom_config_parser()
     self.nf_settings_parser.load(nf_settings_path)
     self.textChanged.connect(self.check_text)
 def __init__(self, sh_path, id=0):
     super(label_writer, self).__init__(parent=None)
     self.type = type
     self.id = id
     self.sh_parser = custom_config_parser()
     self.sh_parser.load(sh_path)
     self.textChanged.connect(self.check_text)
Beispiel #5
0
 def __init__(self, parameter, nf_settings_path):
     super(publish_checkbox, self).__init__(parent=None)
     self.nf_settings_path = nf_settings_path
     self.nf_settings_parser = custom_config_parser()
     self.nf_settings_parser.load(self.nf_settings_path)
     self.parameter = parameter
     self.default()
     self.stateChanged.connect(self.check_check)
 def __init__(self, parameter, nf_settings_path):
     super(set_cpus, self).__init__(parent=None)
     self.nf_settings_path = nf_settings_path
     self.nf_settings_parser = custom_config_parser()
     self.nf_settings_parser.load(self.nf_settings_path)
     self.parameter = parameter
     self.valueChanged.connect(self.check_value)
     self.default()
 def __init__(self, process, nf_settings_path):
     super(set_time, self).__init__(parent=None)
     self.nf_settings_path = nf_settings_path
     self.nf_settings_parser = custom_config_parser()
     self.nf_settings_parser.load(self.nf_settings_path)
     self.process = process
     self.default()
     self.textChanged.connect(self.check_text)
Beispiel #8
0
 def __init__(self, argument, nf_settings_path):
     super(cluster_arguments,self).__init__(parent = None)
     self.nf_settings_path = nf_settings_path
     self.nf_settings_parser = custom_config_parser()
     self.nf_settings_parser.load(self.nf_settings_path)
     self.argument = argument
     self.default()
     self.textChanged.connect(self.check_text)
Beispiel #9
0
 def __init__(self, parameter, settings_file):
     super(choose_option, self).__init__(parent=None)
     self.parameter = parameter
     self.addItems(["true", "false"])
     self.parser = custom_config_parser()
     self.parser.load(settings_file)
     self.settings_file = settings_file
     self.default()
     self.currentIndexChanged.connect(self.selectionchange)
Beispiel #10
0
def check_corrupt(config_path):
    # Check for corrupt files/old/missing
    installed_parser = custom_config_parser()
    packed_parser = custom_config_parser()

    if not os.path.isdir(f"{config_path}"):
        WARNING(f"Missing config directory {config_path}. Initalizing directory")
        os.makedirs(config_path)

    files = ["ui.config",
             "nf.config",
             "PIPE",
             "jobs.txt",
             "run_quandenser.nf",
             "run_quandenser.sh",
             "nextflow"]
    for file in files:
        corrupted = False
        if not os.path.isfile(f"{config_path}/{file}"):
            WARNING(f"Missing file {file}. Installing file")
            shutil.copyfile(f"config/{file}", f"{config_path}/{file}")
            os.chmod(f"{config_path}/{file}", 0o700)  # Only user will get access
        else:  # check corrupt/old versions of config
            if (file.split('.')[-1] in ['config', 'sh'] or file == 'PIPE') and file != 'ui.config':
                installed_parser.load(f"{config_path}/{file}")
                installed_parameters = installed_parser.get_params()
                packed_parser.load(f"config/{file}")
                packed_parameters = packed_parser.get_params()
                if not installed_parameters == packed_parameters:
                    corrupted = True
            elif file == "nf.config":  # Check for old versions
                lines = open(f"{config_path}/{file}", 'r').readlines()
                if not any("slurm_cluster" in line for line in lines):  # Very specific
                    corrupted=True
            else:
                if not filecmp.cmp(f"{config_path}/{file}", f"config/{file}") and file not in ['ui.config', 'jobs.txt']:  # check if files are the same
                    corrupted = True

        if corrupted:
            WARNING(f"Detected old or corrupt version of {file}. Replacing file")
            os.remove(f"{config_path}/{file}")
            shutil.copyfile(f"config/{file}", f"{config_path}/{file}")
            os.chmod(f"{config_path}/{file}", 0o700)  # Only user will get access
 def __init__(self, argument, nf_settings_path):
     super(additional_arguments, self).__init__(parent=None)
     self.nf_settings_path = nf_settings_path
     self.nf_settings_parser = custom_config_parser()
     self.nf_settings_parser.load(self.nf_settings_path)
     self.argument = argument
     if "password" in self.argument:
         self.setEchoMode(QLineEdit.Password)  # Hide password
     self.setText('')
     self.default()
     self.textChanged.connect(self.check_text)
Beispiel #12
0
 def __init__(self, parameter, settings_file):
     super(choose_option, self).__init__(parent=None)
     self.parameter = parameter
     self.addItems(["true", "false"])
     self.parser = custom_config_parser()
     self.parser.load(settings_file)
     self.settings_file = settings_file
     self.default()
     # Fix for long combo boxes
     delegate = QtWidgets.QStyledItemDelegate()
     self.setItemDelegate(delegate)
     self.currentIndexChanged.connect(self.selectionchange)
 def __init__(self, parameter, nf_settings_path):
     super(parameter_setter_double, self).__init__(parent=None)
     self.nf_settings_path = nf_settings_path
     self.nf_settings_parser = custom_config_parser()
     self.nf_settings_parser.load(self.nf_settings_path)
     self.parameter = parameter
     if self.parameter == "params.precursor_window":  # Crux
         self.setSuffix(" ppm")
         self.setMaximum(9999999)
     elif self.parameter == "params.fold_change_eval":  # Triqler
         self.setSingleStep(0.05)
     self.default()
     self.valueChanged.connect(self.check_value)
Beispiel #14
0
 def __init__(self, pipe_path, type='ms'):
     super(file_chooser, self).__init__(parent=None)
     self.setFixedWidth(200)
     self.type = type
     self.id = id
     self.pipe_parser = custom_config_parser()
     self.pipe_parser.load(pipe_path)
     if self.type == 'ms':
         self.setText('Choose MS files')
     elif self.type == 'fasta':
         self.setText('Choose fasta file')
     elif self.type == 'directory':
         self.setText('Choose output directory')
     self.clicked.connect(self.open_window)
    def reset(self):
        files = ["ui.config", "nf.config", "run_quandenser.sh"]
        for file in files:
            print(Fore.YELLOW + f"Replaced {file}" + Fore.RESET)
            os.remove(f"{self.config_path}/{file}")
            shutil.copyfile(f"config/{file}", f"{self.config_path}/{file}")
            os.chmod(f"{self.config_path}/{file}",
                     0o700)  # Only user will get access

        nf_parser = custom_config_parser()
        nf_parser.load(self.config_path + "/nf.config")

        # Read parent
        window = self.window()
        self.recurse_children(window)
Beispiel #16
0
def init_tab5(paths):
    tab5 = QWidget()
    tab5_layout = QVBoxLayout()
    tab5.setLayout(tab5_layout)

    pipe_parser = custom_config_parser()
    pipe_parser.load(paths['pipe'])
    if pipe_parser.get('disable-opengl') in ['false', '']:
        tab5_about = about()
    else:
        tab5_about = tooltip_label("OpenGL disabled",
                                   "OpenGL disabled",
                                   style=True)
    tab5_layout.addWidget(tab5_about)
    return tab5
 def __init__(self, parameter, nf_settings_path):
     super(parameter_setter_single, self).__init__(parent=None)
     self.nf_settings_path = nf_settings_path
     self.nf_settings_parser = custom_config_parser()
     self.nf_settings_parser.load(self.nf_settings_path)
     self.parameter = parameter
     if self.parameter == "params.missed_cleavages":
         self.setMaximum(500)
         self.setSuffix(" cleavages")
     elif self.parameter == "params.max_missing":
         self.setMaximum(1000)
         self.setSuffix(" features")
     else:
         self.setMaximum(9999999)
     self.default()
     self.valueChanged.connect(self.check_value)
Beispiel #18
0
 def __init__(self, parameter, settings_file):
     super(choose_option, self).__init__(parent=None)
     self.parameter = parameter
     self.parser = custom_config_parser()
     self.parser.load(settings_file)
     self.settings_file = settings_file
     if self.parameter == 'workflow':
         self.addItems(["Full", "MSconvert", "Quandenser"])
     elif 'parallel' in self.parameter:
         self.addItems(["true", "false"])
     elif self.parameter == 'profile':
         self.addItems(["local", "cluster"])
     elif self.parameter == 'process.executor':
         self.addItems(["slurm"])
     else:
         self.addItems(["true", "false"])
     self.default()
     self.currentIndexChanged.connect(self.selectionchange)
Beispiel #19
0
    def __init__(self):
        super().__init__()
        self.title = 'Quandenser-pipeline'
        self.setWindowIcon(QIcon('images/logo.png'))
        QFontDatabase.addApplicationFont("fonts/rockwell.ttf")
        self.resize(1000, 800)

        # Check file integrety
        check_corrupt(config_path)
        self.paths = {}
        self.paths['config'] = f"{config_path}"
        self.paths['ui'] = f"{config_path}/ui.config"
        self.paths['nf'] = f"{config_path}/nf.config"
        self.paths['sh'] = f"{config_path}/run_quandenser.sh"
        self.paths['pipe'] = f"{config_path}/PIPE"
        self.paths['jobs'] = f"{config_path}/jobs.txt"

        # Open pipe and read
        self.pipe_parser = custom_config_parser()
        self.pipe_parser.load(self.paths['pipe'])
        check_running(config_path)
        self.pipe_parser.write(
            'exit_code', '2', isString=False
        )  # Add error code 2. If we manage to load, change to 1

        # To restore settings of window
        self.settings_obj = QtCore.QSettings(self.paths['ui'],
                                             QtCore.QSettings.IniFormat)
        # Restore window's previous geometry from file
        self.setMinimumWidth(300)
        self.setMinimumHeight(200)
        self.initUI()
        if os.path.exists(self.paths['ui']):
            self.restoreGeometry(self.settings_obj.value("windowGeometry"))
            self.restoreState(self.settings_obj.value("State_main"))
            children = self.children()
            for child in children:
                self.recurse_children(child, save=False)
        self.show()
 def __init__(self, parameter, settings_file):
     super(choose_option, self).__init__(parent=None)
     self.parameter = parameter
     self.parser = custom_config_parser()
     self.parser.load(settings_file)
     self.settings_file = settings_file
     # Fix for long combo boxes
     delegate = QtWidgets.QStyledItemDelegate()
     self.setItemDelegate(delegate)
     if self.parameter == 'workflow':
         self.addItems(["Full", "MSconvert", "Quandenser"])
     elif 'parallel' in self.parameter:
         self.addItems(["true", "false"])
     elif self.parameter == 'profile':
         self.addItems(["local", "cluster"])
     elif self.parameter == 'process.executor':
         self.addItems([
             "slurm", "pbs", "pbspro", "sge", "lsf", "moab", "nqsii",
             "condor", "ignite"
         ])
     else:
         self.addItems(["true", "false"])
     self.default()
     self.currentIndexChanged.connect(self.selectionchange)
Beispiel #21
0
    def run(self):
        # Load settings
        self.pipe_parser = custom_config_parser()
        self.pipe_parser.load(self.pipe_path)
        self.nf_settings_parser = custom_config_parser()
        self.nf_settings_parser.load(self.nf_settings_path)
        self.sh_parser = custom_config_parser()
        self.sh_parser.load(self.sh_script_path)

        # Read parent
        parent = self.parentWidget()

        # OUTPUT_DIRECTORY #
        children = parent.findChildren(QLineEdit)
        for child in children:  # This is so I can have whatever order in widgets I want
            if child.type == 'directory':
                break  # Will keep child
        output_path = child.text()
        if not os.path.isdir(output_path):
            ERROR('Not a valid output path')
            return 1
        # Change output parameters in both nf_settings and sh
        self.sh_parser.write("OUTPUT_PATH", output_path)  # In sh
        self.nf_settings_parser.write("params.output_path",
                                      output_path)  # In sh

        # OUTPUT_LABEL #
        label = self.sh_parser.get('OUTPUT_PATH_LABEL')
        if label != '':  # Check if label has been set
            label = re.sub("_*", '', label)  # Remove previous indexing
            index = 0
            while True:
                if os.path.isdir(output_path + label + "_" + str(index)):
                    index += 1
                else:
                    label = label + "_" + str(index)
                    self.nf_settings_parser.write("params.output_label", label)
                    self.sh_parser.write('OUTPUT_PATH_LABEL', label)
                    break
        else:
            self.nf_settings_parser.write("params.output_label", '')

        # BATCH_FILE #
        child = parent.findChildren(QTableWidget)[0]
        full_table = []
        for row in range(child.rowCount()):
            if not child.item(row, 0).text() == '':
                if not os.path.isfile(child.item(row, 0).text()):
                    ERROR(f"File in row {row} does not exist")
                    return 1
                elif self.nf_settings_parser.get('params.workflow') in [
                        "MSconvert", "Quandenser"
                ] and child.item(row, 1).text() == '':
                    label = 'A'  # Add junk labeling
                elif child.item(
                        row, 1).text() == '' and self.nf_settings_parser.get(
                            'params.workflow') == "Full":
                    ERROR(
                        f"File in row {row} is missing a label (Full workflow enabled)"
                    )
                    return 1
                elif child.item(row, 1).text() != '':
                    label = child.item(row, 1).text()
                input_string = child.item(row, 0).text() + '\t' + label + '\n'
                full_table.append(input_string)
        if full_table == []:
            ERROR('No files choosen')
            return 1
        with open(f"{output_path}/file_list.txt", 'w') as file:
            for line in full_table:
                file.write(line)
        batch_file_path = f"{output_path}/file_list.txt"
        self.nf_settings_parser.write("params.batch_file", batch_file_path)

        # DATABASE_FILE #
        children = parent.findChildren(QLineEdit)
        for child in children:  # This is so I can have whatever order in widgets I want
            if child.type == 'file':
                break  # Will keep child
        database_path = child.text()
        self.nf_settings_parser.write("params.db", database_path)
        workflow = self.nf_settings_parser.get("params.workflow")
        if workflow == "Full" and not os.path.isfile(database_path):
            ERROR(
                "You must choose an database if you are running the full pipeline"
            )
            return 1

        # EMAIL #
        email = self.nf_settings_parser.get("params.email")
        if email != '':
            # Need to add -N here, since without it, nextflow will display a warning
            self.sh_parser.write("EMAIL_NOTIFICATION", f"-N {email}")
        else:
            self.sh_parser.write("EMAIL_NOTIFICATION", f"")

        # CUSTOM MOUNTS #
        custom_mounts = self.pipe_parser.get('custom_mounts').replace(
            '\r', '').replace('\n', '')
        self.nf_settings_parser.write('params.custom_mounts', custom_mounts)

        # Generate random hash for nextflow
        random_hash = secrets.token_urlsafe(16)
        self.nf_settings_parser.write('params.random_hash', random_hash)

        # Set pipe to launch nextflow pipeline
        self.pipe_parser.write('exit_code', '0', isString=False)
        self.window().close()
def init_tab2(paths):
    tab2 = QWidget()
    tab2_layout = QHBoxLayout()
    tab2.setLayout(tab2_layout)
    filler = QLabel('')
    filler.setFixedWidth(1)

    # Left box
    tab2_leftbox = QWidget()
    tab2_leftbox_layout = QVBoxLayout()
    tab2_leftbox.setLayout(tab2_leftbox_layout)

    # Top
    tab2_leftbox_top = QWidget()
    tab2_leftbox_top_layout = QFormLayout()
    tab2_leftbox_top.setLayout(tab2_leftbox_top_layout)

    tab2_choose_option_workflow = choose_option('workflow', paths['nf'])
    tab2_choose_option_boxcar_convert = choose_option('boxcar_convert',
                                                      paths['nf'])
    tab2_choose_option_profile = choose_option('profile', paths['sh'])
    tab2_choose_option_parallel_msconvert = choose_option(
        'parallel_msconvert', paths['nf'])
    tab2_choose_option_parallel_quandenser = choose_option(
        'parallel_quandenser', paths['nf'])

    # Always visible
    tab2_leftbox_top_layout.addRow(QLabel('Choose workflow'),
                                   tab2_choose_option_workflow)
    tab2_leftbox_top_layout.addRow(QLabel('Profile'),
                                   tab2_choose_option_profile)
    tab2_leftbox_top_layout.addRow(QLabel('Enable boxcar conversion'),
                                   tab2_choose_option_boxcar_convert)
    tab2_leftbox_top_layout.addRow(QLabel('Enable parallel MSconvert'),
                                   tab2_choose_option_parallel_msconvert)
    tab2_leftbox_top_layout.addRow(QLabel('Enable parallel quandenser'),
                                   tab2_choose_option_parallel_quandenser)
    tab2_leftbox_top_layout.addRow(filler, filler)  # Empty space
    tab2_leftbox_layout.addWidget(tab2_leftbox_top)

    # Bottom, these will be hidden or shown depending on profile option
    tab2_hidden = QWidget()
    tab2_hidden.hidden_object = True
    tab2_hidden_layout = QStackedLayout()
    tab2_hidden.setLayout(tab2_hidden_layout)

    # Stack 1: Empty layout. Cluster disabled
    tab2_hidden_stack_1 = QWidget()
    tab2_hidden_stack_1_layout = QFormLayout()
    tab2_hidden_stack_1.setLayout(tab2_hidden_stack_1_layout)

    # Stack 2: Regular quandenser, cluster enabled
    tab2_hidden_stack_2 = QWidget()
    tab2_hidden_stack_2_layout = QFormLayout()
    tab2_hidden_stack_2_layout.setVerticalSpacing(0)
    tab2_hidden_stack_2.setLayout(tab2_hidden_stack_2_layout)

    stack_2_widgets = []
    stack_2_widgets.append(get_tooltip('cluster-type'))
    stack_2_widgets.append(choose_option("process.executor", paths['nf']))
    stack_2_widgets.append(filler)
    stack_2_widgets.append(get_tooltip('cluster-arguments'))
    stack_2_widgets.append(
        cluster_arguments("process.clusterOptions", paths['nf']))
    stack_2_widgets.append(filler)
    stack_2_widgets.append(get_tooltip('cluster-queue'))
    stack_2_widgets.append(cluster_arguments("process.queue", paths['nf']))
    stack_2_widgets.append(filler)
    stack_2_widgets.append(QLabel('MSconvert cpus + time'))
    stack_2_widgets.append(set_cpus("msconvert_cpus", paths['nf']))
    stack_2_widgets.append(set_time("msconvert_time", paths['nf']))
    stack_2_widgets.append(QLabel('Boxcar convert cpus + time'))
    stack_2_widgets.append(set_cpus("boxcar_convert_cpus", paths['nf']))
    stack_2_widgets.append(set_time("boxcar_convert_time", paths['nf']))
    stack_2_widgets.append(QLabel('Quandenser cpus + time'))
    stack_2_widgets.append(set_cpus("quandenser_cpus", paths['nf']))
    stack_2_widgets.append(set_time("quandenser_time", paths['nf']))
    stack_2_widgets.append(QLabel('Tide cpus + time'))
    stack_2_widgets.append(set_cpus("tide_search_cpus", paths['nf']))
    stack_2_widgets.append(set_time("tide_search_time", paths['nf']))
    stack_2_widgets.append(QLabel('Triqler cpus + time'))
    stack_2_widgets.append(set_cpus("triqler_cpus", paths['nf']))
    stack_2_widgets.append(set_time("triqler_time", paths['nf']))

    list_itr = iter(stack_2_widgets)
    for label in list_itr:
        combine_widget = QWidget()
        combine_widget_layout = QFormLayout()
        combine_widget.setLayout(combine_widget_layout)
        widget1, widget2 = next(list_itr), next(list_itr)
        combine_widget_layout.addRow(widget1, widget2)
        tab2_hidden_stack_2_layout.addRow(label, combine_widget)

    # Stack 3: Parallel quandenser, cluster enabled
    tab2_hidden_stack_3 = QWidget()
    tab2_hidden_stack_3_layout = QFormLayout()
    tab2_hidden_stack_3_layout.setVerticalSpacing(0)
    tab2_hidden_stack_3.setLayout(tab2_hidden_stack_3_layout)

    stack_3_widgets = []
    stack_3_widgets.append(get_tooltip('cluster-type'))
    stack_3_widgets.append(choose_option("process.executor", paths['nf']))
    stack_3_widgets.append(filler)
    stack_3_widgets.append(get_tooltip('cluster-arguments'))
    stack_3_widgets.append(
        cluster_arguments("process.clusterOptions", paths['nf']))
    stack_3_widgets.append(filler)
    stack_3_widgets.append(get_tooltip('cluster-queue'))
    stack_3_widgets.append(cluster_arguments("process.queue", paths['nf']))
    stack_3_widgets.append(filler)
    stack_3_widgets.append(QLabel('MSconvert cpus + time'))
    stack_3_widgets.append(set_cpus("msconvert_cpus", paths['nf']))
    stack_3_widgets.append(set_time("msconvert_time", paths['nf']))
    stack_3_widgets.append(QLabel('Boxcar convert cpus + time'))
    stack_3_widgets.append(set_cpus("boxcar_convert_cpus", paths['nf']))
    stack_3_widgets.append(set_time("boxcar_convert_time", paths['nf']))
    stack_3_widgets.append(QLabel('Quandenser p1 cpus + time'))
    stack_3_widgets.append(set_cpus("quandenser_parallel_1_cpus", paths['nf']))
    stack_3_widgets.append(set_time("quandenser_parallel_1_time", paths['nf']))
    stack_3_widgets.append(QLabel('Quandenser p2 cpus + time'))
    stack_3_widgets.append(set_cpus("quandenser_parallel_2_cpus", paths['nf']))
    stack_3_widgets.append(set_time("quandenser_parallel_2_time", paths['nf']))
    stack_3_widgets.append(QLabel('Quandenser p3 cpus + time'))
    stack_3_widgets.append(set_cpus("quandenser_parallel_3_cpus", paths['nf']))
    stack_3_widgets.append(set_time("quandenser_parallel_3_time", paths['nf']))
    stack_3_widgets.append(QLabel('Quandenser p4 cpus + time'))
    stack_3_widgets.append(set_cpus("quandenser_parallel_4_cpus", paths['nf']))
    stack_3_widgets.append(set_time("quandenser_parallel_4_time", paths['nf']))
    stack_3_widgets.append(QLabel('Tide cpus + time'))
    stack_3_widgets.append(set_cpus("tide_search_cpus", paths['nf']))
    stack_3_widgets.append(set_time("tide_search_time", paths['nf']))
    stack_3_widgets.append(QLabel('Triqler cpus + time'))
    stack_3_widgets.append(set_cpus("triqler_cpus", paths['nf']))
    stack_3_widgets.append(set_time("triqler_time", paths['nf']))
    stack_3_widgets.extend([filler, filler, filler])  # Empty space

    list_itr = iter(stack_3_widgets)
    for label in list_itr:
        combine_widget = QWidget()
        combine_widget_layout = QFormLayout()
        combine_widget.setLayout(combine_widget_layout)
        widget1, widget2 = next(list_itr), next(list_itr)
        combine_widget_layout.addRow(widget1, widget2)
        tab2_hidden_stack_3_layout.addRow(label, combine_widget)

    # Add stacks
    tab2_hidden_layout.addWidget(tab2_hidden_stack_1)
    tab2_hidden_layout.addWidget(tab2_hidden_stack_2)
    tab2_hidden_layout.addWidget(tab2_hidden_stack_3)

    # Add hidden stacked layout
    tab2_leftbox_layout.addWidget(tab2_hidden)

    # Right box
    tab2_rightbox = QWidget()
    tab2_rightbox_layout = QHBoxLayout()
    tab2_rightbox.setLayout(tab2_rightbox_layout)

    pipe_parser = custom_config_parser()
    pipe_parser.load(paths['pipe'])
    if pipe_parser.get('disable-opengl') in ['false', '']:
        tab2_workflow = workflow()
        tab2_rightbox_layout.addWidget(tab2_workflow)
    else:
        tab2_workflow = tooltip_label("OpenGL disabled",
                                      "OpenGL disabled",
                                      style=True)

    tab2_rightbox_layout.addWidget(tab2_workflow)
    tab2_layout.addWidget(tab2_rightbox)

    # Combine
    tab2_layout.addWidget(tab2_leftbox)
    tab2_layout.addWidget(tab2_rightbox)
    return tab2