Exemple #1
0
    def load_ui_from_file(self):
        """
        Loads the UI interface from file.
        """
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)

        # Protocol terminal
        self.ui.textEdit_terminal.setReadOnly(1)
        font = QFont()
        font.setPointSize(10)
        self.ui.textEdit_terminal.setFont(font)

        # Viewer button
        self.ui.pushButton_Viewer.setEnabled(False)
Exemple #2
0
class MainWindow(QMainWindow):
    """
    Defines the mainWindow class for the neurodecode GUI.
    """

    hide_recordTerminal = pyqtSignal(bool)
    signal_error = pyqtSignal(str)

    #----------------------------------------------------------------------
    def __init__(self):
        """
        Constructor.
        """
        super(MainWindow, self).__init__()

        self.cfg_struct = None  # loaded module containing all param possible values
        self.cfg_subject = None  # loaded module containing subject specific values
        self.paramsWidgets = {}  # dict of all the created parameters widgets

        self.load_ui_from_file()

        self.redirect_stdout()

        self.connect_signals_to_slots()

        # Define in which modality we are
        self.modality = None

        # Recording process
        self.record_terminal = None
        self.recordLogger = logging.getLogger('recorder')
        self.recordLogger.propagate = False
        init_logger(self.recordLogger)

        # To display errors
        self.error_dialog = QErrorMessage(self)
        self.error_dialog.setWindowTitle('Warning')

        # Mp sharing variables
        self.record_state = mp.Value('i', 0)
        self.protocol_state = mp.Value('i', 0)
        self.lsl_state = mp.Value('i', 0)
        self.viewer_state = mp.Value('i', 0)

        # Disable widgets
        self.ui.groupBox_Modality.setEnabled(False)
        self.ui.groupBox_Launch.setEnabled(False)

    # ----------------------------------------------------------------------
    def redirect_stdout(self):
        """
        Create Queue and redirect sys.stdout to this queue.
        Create thread that will listen on the other end of the queue, and send the text to the textedit_terminal.
        """
        queue = mp.Queue()

        self.thread = QThread()

        self.my_receiver = MyReceiver(queue)
        self.my_receiver.mysignal.connect(self.on_terminal_append)
        self.my_receiver.moveToThread(self.thread)

        self.thread.started.connect(self.my_receiver.run)
        self.thread.start()

        redirect_stdout_to_queue(logger, self.my_receiver.queue, 'INFO')

    #----------------------------------------------------------------------
    def load_ui_from_file(self):
        """
        Loads the UI interface from file.
        """
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)

        # Protocol terminal
        self.ui.textEdit_terminal.setReadOnly(1)
        font = QFont()
        font.setPointSize(10)
        self.ui.textEdit_terminal.setFont(font)

        # Viewer button
        self.ui.pushButton_Viewer.setEnabled(False)

    #----------------------------------------------------------------------
    def clear_params(self):
        """
        Clear all previously loaded params widgets.
        """

        if self.ui.scrollAreaWidgetContents_Basics.layout() != None:
            QWidget().setLayout(self.ui.scrollAreaWidgetContents_Adv.layout())
            QWidget().setLayout(
                self.ui.scrollAreaWidgetContents_Basics.layout())

    # ----------------------------------------------------------------------
    def extract_value_from_module(self, key, values):
        """
        Extracts the subject's specific value associated with key.
        key = parameter name.
        values = list of all the parameters values.
        """
        for v in values:
            if v[0] == key:
                return v[1]

    # ----------------------------------------------------------------------
    def disp_params(self, cfg_template_module, cfg_module):
        """
        Displays the parameters in the corresponding UI scrollArea.
        cfg = config module
        """

        self.clear_params()
        # Extract the parameters and their possible values from the template modules.
        params = inspect.getmembers(cfg_template_module)

        # Extract the chosen values from the subject's specific module.
        all_chosen_values = inspect.getmembers(cfg_module)

        filePath = self.ui.lineEdit_pathSearch.text()

        self.directions = ()

        # Iterates over the classes
        for par in range(2):
            param = inspect.getmembers(params[par][1])
            # Create layouts
            layout = QFormLayout()

            # Iterates over the list
            for p in param:
                # Remove useless attributes
                if '__' in p[0]:
                    continue

                # Iterates over the dict
                for key, values in p[1].items():
                    chosen_value = self.extract_value_from_module(
                        key, all_chosen_values)

                    # For the feedback directions [offline and online].
                    if 'DIRECTIONS' in key:
                        self.directions = values

                        if self.modality is 'offline':
                            nb_directions = 4
                            directions = Connect_Directions(
                                key, chosen_value, values, nb_directions)

                        elif self.modality is 'online':
                            chosen_events = [
                                event[1] for event in chosen_value
                            ]
                            chosen_value = [val[0] for val in chosen_value]
                            nb_directions = len(chosen_value)
                            directions = Connect_Directions_Online(
                                key, chosen_value, values, nb_directions,
                                chosen_events, [None])

                        directions.signal_paramChanged[str, list].connect(
                            self.on_guichanges)
                        self.paramsWidgets.update({key: directions})
                        layout.addRow(key, directions.l)

                    # For the special case of choosing the trigger classes to train on
                    elif 'TRIGGER_DEF' in key:

                        trigger_def = Connect_Directions(
                            key, chosen_value, [None], 4)
                        trigger_def.signal_paramChanged[str, list].connect(
                            self.on_guichanges)
                        self.paramsWidgets.update({key: trigger_def})
                        layout.addRow(key, trigger_def.l)

                    # For providing a folder path.
                    elif 'PATH' in key:

                        #  Automatic data path if not specified
                        if 'DATA_PATH' in key and not chosen_value:
                            p_path = Path(self.ui.lineEdit_pathSearch.text()
                                          )  # Path to subject protocol
                            if self.modality == 'trainer':
                                chosen_value = str(
                                    Path(os.environ['NEUROD_DATA']) /
                                    p_path.parent.name / p_path.name /
                                    'offline' / 'fif')
                            else:
                                chosen_value = str(
                                    Path(os.environ['NEUROD_DATA']) /
                                    p_path.parent.name / p_path.name /
                                    self.modality)
                            setattr(self.cfg_subject, key, chosen_value)

                        pathfolderfinder = PathFolderFinder(
                            key, os.environ['NEUROD_DATA'], chosen_value)
                        pathfolderfinder.signal_pathChanged[str, str].connect(
                            self.on_guichanges)
                        pathfolderfinder.signal_error[str].connect(
                            self.on_error)
                        self.paramsWidgets.update({key: pathfolderfinder})
                        layout.addRow(key, pathfolderfinder.layout)

                        if not chosen_value:
                            self.signal_error[str].emit(
                                key +
                                ' is empty! Provide a path before starting.')
                        continue

                    # For providing a file path.
                    elif 'FILE' in key:

                        #  Automatic decoder path if not specified
                        if 'DECODER_FILE' in key and not chosen_value:
                            p_path = Path(self.ui.lineEdit_pathSearch.text()
                                          )  # Path to subject protocol
                            chosen_value = str(
                                Path(os.environ['NEUROD_DATA']) /
                                p_path.parent.name / p_path.name / 'offline' /
                                'fif' / 'classifier' / 'classifier-64bit.pkl')
                            setattr(self.cfg_subject, key, chosen_value)

                        #  Automatic trigger path if not specified
                        if 'TRIGGER_FILE' in key and not chosen_value:
                            p_path = Path(self.ui.lineEdit_pathSearch.text()
                                          )  # Path to subject protocol
                            chosen_value = str(
                                Path(p_path.parent) / 'triggerdef.ini')
                            setattr(self.cfg_subject, key, chosen_value)

                        pathfilefinder = PathFileFinder(key, chosen_value)
                        pathfilefinder.signal_pathChanged[str, str].connect(
                            self.on_guichanges)
                        pathfilefinder.signal_error[str].connect(self.on_error)
                        self.paramsWidgets.update({key: pathfilefinder})
                        layout.addRow(key, pathfilefinder.layout)

                        if not chosen_value:
                            self.signal_error[str].emit(
                                key +
                                ' is empty! Provide a file before starting.')

                    # To select specific electrodes
                    elif '_CHANNELS' in key or 'CHANNELS_' in key:
                        self.channels = read_params_from_file(
                            Path(self.cfg_subject.DATA_PATH),
                            'channelsList.txt')
                        ch_select = Channel_Select(key, self.channels,
                                                   chosen_value)
                        ch_select.signal_paramChanged[str, list].connect(
                            self.on_guichanges)
                        self.paramsWidgets.update({key: ch_select})
                        layout.addRow(key, ch_select.layout)

                    elif 'BIAS' in key:
                        #  Add None to the list in case of no bias wanted
                        self.directions = tuple([None] + list(self.directions))
                        bias = Connect_Bias(key, self.directions, chosen_value)
                        bias.signal_paramChanged[str, object].connect(
                            self.on_guichanges)
                        self.paramsWidgets.update({key: bias})
                        layout.addRow(key, bias.l)

                    # For all the int values.
                    elif values is int:
                        spinBox = Connect_SpinBox(key, chosen_value)
                        spinBox.signal_paramChanged[str, int].connect(
                            self.on_guichanges)
                        self.paramsWidgets.update({key: spinBox})
                        layout.addRow(key, spinBox)

                    # For all the float values.
                    elif values is float:
                        doublespinBox = Connect_DoubleSpinBox(
                            key, chosen_value)
                        doublespinBox.signal_paramChanged[str, float].connect(
                            self.on_guichanges)
                        self.paramsWidgets.update({key: doublespinBox})
                        layout.addRow(key, doublespinBox)

                    # For parameters with multiple non-fixed values in a list (user can modify them)
                    elif values is list:
                        modifiable_list = Connect_Modifiable_List(
                            key, chosen_value)
                        modifiable_list.signal_paramChanged[str, list].connect(
                            self.on_guichanges)
                        self.paramsWidgets.update({key: modifiable_list})
                        layout.addRow(key, modifiable_list)
                        continue

                    #  For parameters containing a string to modify
                    elif values is str:
                        lineEdit = Connect_LineEdit(key, chosen_value)
                        lineEdit.signal_paramChanged[str, str].connect(
                            self.on_guichanges)
                        lineEdit.signal_paramChanged[str, type(None)].connect(
                            self.on_guichanges)
                        self.paramsWidgets.update({key: lineEdit})
                        layout.addRow(key, lineEdit)

                    # For parameters with multiple fixed values.
                    elif type(values) is tuple:
                        comboParams = Connect_ComboBox(key, chosen_value,
                                                       values)
                        comboParams.signal_paramChanged[str, object].connect(
                            self.on_guichanges)
                        comboParams.signal_additionalParamChanged[
                            str, dict].connect(self.on_guichanges)
                        self.paramsWidgets.update({key: comboParams})
                        layout.addRow(key, comboParams.layout)
                        continue

                    # For parameters with multiple non-fixed values in a dict (user can modify them)
                    elif type(values) is dict:
                        try:
                            selection = chosen_value['selected']
                            comboParams = Connect_ComboBox(
                                key, chosen_value, values)
                            comboParams.signal_paramChanged[
                                str, object].connect(self.on_guichanges)
                            comboParams.signal_additionalParamChanged[
                                str, dict].connect(self.on_guichanges)
                            self.paramsWidgets.update({key: comboParams})
                            layout.addRow(key, comboParams.layout)
                        except:
                            modifiable_dict = Connect_Modifiable_Dict(
                                key, chosen_value, values)
                            modifiable_dict.signal_paramChanged[
                                str, dict].connect(self.on_guichanges)
                            self.paramsWidgets.update({key: modifiable_dict})
                            layout.addRow(key, modifiable_dict)

                # Add a horizontal line to separate parameters' type.
                if p != param[-1]:
                    separator = QFrame()
                    separator.setFrameShape(QFrame.HLine)
                    separator.setFrameShadow(QFrame.Sunken)
                    layout.addRow(separator)

                # Display the parameters according to their types.
                if params[par][0] == 'Basic':
                    self.ui.scrollAreaWidgetContents_Basics.setLayout(layout)
                elif params[par][0] == 'Advanced':
                    self.ui.scrollAreaWidgetContents_Adv.setLayout(layout)

        # Connect inter-widgets signals and slots
        if self.modality == 'trainer':
            self.paramsWidgets['TRIGGER_FILE'].signal_pathChanged[
                str, str].connect(trigger_def.on_new_tdef_file)
            self.paramsWidgets['TRIGGER_FILE'].on_selected()

        if self.modality == 'online':
            self.paramsWidgets['TRIGGER_FILE'].signal_pathChanged[
                str, str].connect(directions.on_new_tdef_file)
            self.paramsWidgets['TRIGGER_FILE'].on_selected()

            self.paramsWidgets['DECODER_FILE'].signal_pathChanged[
                str, str].connect(directions.on_new_decoder_file)
            self.paramsWidgets['DECODER_FILE'].on_selected()

    # ----------------------------------------------------------------------
    def load_config(self, cfg_file):
        """
        Dynamic loading of a config file.
        Format the lib to fit the previous developed neurodecode code if subject specific file (not for the templates).
        cfg_file: tuple containing the path and the config file name.
        """
        if self.cfg_subject == None or cfg_file[
                1] not in self.cfg_subject.__file__:
            # Dynamic loading
            sys.path.append(cfg_file[0])
            cfg_module = import_module(cfg_file[1].split('.')[0])
        else:
            cfg_module = reload(self.cfg_subject)

        return cfg_module

    #----------------------------------------------------------------------
    def load_all_params(self, cfg_template, cfg_file):
        """
        Loads the params structure and assign the subject/s specific value.
        It also checks the sanity of the loaded params according to the protocol.
        """
        try:
            # Loads the subject's specific values
            self.cfg_subject = self.load_config(cfg_file)

            # Loads the template
            if self.cfg_struct == None or cfg_template[
                    1] not in self.cfg_struct.__file__:
                self.cfg_struct = self.load_config(cfg_template)

            # Display parameters on the GUI
            self.disp_params(self.cfg_struct, self.cfg_subject)

            # Check the parameters integrity
            self.m.check_config(self.cfg_subject)

        except Exception as e:
            # print(traceback.format_exc())
            self.signal_error[str].emit(str(e))

    @pyqtSlot(str, str)
    @pyqtSlot(str, bool)
    @pyqtSlot(str, list)
    @pyqtSlot(str, float)
    @pyqtSlot(str, int)
    @pyqtSlot(str, dict)
    @pyqtSlot(str, tuple)
    @pyqtSlot(str, type(None))
    # ----------------------------------------------------------------------
    def on_guichanges(self, name, new_Value):
        """
        Apply the modification to the corresponding param of the cfg module

        name = parameter name
        new_value = new str value to to change in the module
        """

        # In case of a dict containing several option (contains 'selected')
        try:
            tmp = getattr(self.cfg_subject, name)
            tmp['selected'] = new_Value['selected']
            tmp[new_Value['selected']] = new_Value[new_Value['selected']]
            setattr(self.cfg_subject, name, tmp)
        # In case of simple data format
        except:
            setattr(self.cfg_subject, name, new_Value)

        print("The parameter %s has been changed to %s" %
              (name, getattr(self.cfg_subject, name)))

    # ----------------------------------------------------------------------
    def look_for_subject_file(self, modality):
        '''
        Look if the subject config file is contained in the subject folder
        
        modality = offline, trainer or online
        '''
        is_found = False
        cfg_file = None
        cfg_path = Path(self.ui.lineEdit_pathSearch.text())

        for f in glob(os.fspath(cfg_path / "*.py"), recursive=False):
            fileName = os.path.split(f)[-1]
            if modality in fileName and 'structure' not in fileName:
                is_found = True
                cfg_file = f
                break
        return is_found, cfg_file

    #----------------------------------------------------------------------
    def find_structure_file(self, cfg_file, modality):
        """
        Find the structure config file associated with the subject config file
        
        cfg_file: subject specific config file
        modality = offline, trainer or online
        """
        # Find the config template
        tmp = cfg_file.split('.')[0]  # Remove the .py
        self.protocol = tmp.split('-')[-1]  # Extract the protocol name
        template_path = Path(
            os.environ['NEUROD_ROOT']
        ) / 'neurodecode' / 'config_files' / self.protocol / 'structure_files'

        for f in glob(os.fspath(template_path / "*.py"), recursive=False):
            fileName = os.path.split(f)[-1]
            if modality in fileName and 'structure' in fileName:
                return f

    #----------------------------------------------------------------------
    def prepare_config_files(self, modality):
        """
        Find both the subject config file and the associated structure config
        file paths
        """
        is_found, cfg_file = self.look_for_subject_file(modality)

        if is_found is False:
            self.error_dialog.showMessage(
                'Config file missing: copy an ' + modality +
                ' config file to the subject folder or create a new subjet')
            return None, None
        else:
            cfg_template = self.find_structure_file(cfg_file, modality)
            cfg_file = os.path.split(cfg_file)
            cfg_template = os.path.split(cfg_template)

            return cfg_file, cfg_template

    # ----------------------------------------------------------------------
    def load_protocol_module(self, module_name):
        """
        Load or reload the protocol's module associated with the modality
        
        module_name = name of the module to load
        """
        if module_name not in sys.modules:
            path2protocol = os.path.split(
                self.ui.lineEdit_pathSearch.text())[0]
            sys.path.append(path2protocol)
            self.m = import_module(module_name)
        else:
            self.m = reload(sys.modules[module_name])

    # ----------------------------------------------------------------------
    @pyqtSlot()
    def on_click_offline(self):
        """
        Loads the Offline parameters.
        """
        self.modality = 'offline'

        cfg_file, cfg_template = self.prepare_config_files(self.modality)
        module_name = 'offline_' + self.protocol

        self.load_protocol_module(module_name)

        self.ui.checkBox_Record.setChecked(True)
        self.ui.checkBox_Record.setEnabled(False)

        if cfg_file and cfg_template:
            self.load_all_params(cfg_template, cfg_file)

        self.ui.groupBox_Launch.setEnabled(True)

    # ----------------------------------------------------------------------
    @pyqtSlot()
    def on_click_train(self):
        """
        Loads the Training parameters.
        """
        self.modality = 'trainer'

        cfg_file, cfg_template = self.prepare_config_files(self.modality)
        module_name = 'trainer_' + self.protocol

        self.load_protocol_module(module_name)

        self.ui.checkBox_Record.setChecked(False)
        self.ui.checkBox_Record.setEnabled(False)

        if cfg_file and cfg_template:
            self.load_all_params(cfg_template, cfg_file)

        self.ui.groupBox_Launch.setEnabled(True)

    #----------------------------------------------------------------------
    @pyqtSlot()
    def on_click_online(self):
        """
        Loads the Online parameters.
        """
        self.modality = 'online'

        cfg_file, cfg_template = self.prepare_config_files(self.modality)
        module_name = 'online_' + self.protocol

        self.load_protocol_module(module_name)

        self.ui.checkBox_Record.setChecked(True)
        self.ui.checkBox_Record.setEnabled(True)

        if cfg_file and cfg_template:
            self.load_all_params(cfg_template, cfg_file)

        self.ui.groupBox_Launch.setEnabled(True)

    #----------------------------------------------------------------------v
    @pyqtSlot()
    def on_click_start(self):
        """
        Launch the selected protocol. It can be Offline, Train or Online.
        """
        self.record_dir = Path(self.cfg_subject.DATA_PATH)

        # Find the selected amp and save it in the cfg
        if self.modality != 'trainer':
            amp = self.ui.comboBox_LSL.currentData()
            if not amp:
                self.signal_error[str].emit('No LSL amplifier specified.')
                return
            setattr(self.cfg_subject, 'AMP_NAME', amp['name'])
            setattr(self.cfg_subject, 'AMP_SERIAL', amp['serial'])

        # Prepare the pickable config class
        ccfg = cfg_class(self.cfg_subject)

        with self.record_state.get_lock():
            self.record_state.value = 0

        if not self.protocol_state.value:
            self.ui.textEdit_terminal.clear()

            # Recording shared variable + recording terminal
            if self.ui.checkBox_Record.isChecked():

                if not self.record_terminal:
                    self.record_terminal = GuiTerminal(self.recordLogger,
                                                       'INFO', self.width())
                    self.hide_recordTerminal[bool].connect(
                        self.record_terminal.setHidden)

                else:
                    self.record_terminal.textEdit.clear()
                    self.record_terminal.textEdit.insertPlainText(
                        'Waiting for the recording to start...\n')
                    self.hide_recordTerminal[bool].emit(False)

                # Protocol shared variable
                with self.protocol_state.get_lock():
                    self.protocol_state.value = 2  #  0=stop, 1=start, 2=wait

                processesToLaunch = [('recording', recorder.run_gui, [self.record_state, self.protocol_state, self.record_dir, self.recordLogger, amp['name'], amp['serial'], False, self.record_terminal.my_receiver.queue]), \
                                     ('protocol', self.m.run, [ccfg, self.protocol_state, self.my_receiver.queue])]

            else:
                # Protocol shared variable
                with self.protocol_state.get_lock():
                    self.protocol_state.value = 1  #  0=stop, 1=start, 2=wait

                processesToLaunch = [
                    ('protocol', self.m.run,
                     [ccfg, self.protocol_state, self.my_receiver.queue])
                ]

            launchedProcess = Thread(target=self.launching_subprocesses,
                                     args=processesToLaunch)
            launchedProcess.start()
            logger.info(self.modality + ' protocol starting...')
            self.ui.pushButton_Start.setText('Stop')

        else:
            with self.protocol_state.get_lock():
                self.protocol_state.value = 0

            time.sleep(2)
            self.hide_recordTerminal[bool].emit(True)
            self.ui.pushButton_Start.setText('Start')

    #----------------------------------------------------------------------
    @pyqtSlot(str)
    def on_terminal_append(self, text):
        """
        Writes to the QtextEdit_terminal the redirected stdout.
        """
        self.ui.textEdit_terminal.moveCursor(QTextCursor.End)
        self.ui.textEdit_terminal.insertPlainText(text)

    @pyqtSlot()
    #----------------------------------------------------------------------
    def on_click_newSubject(self):
        """
        Instance a Connect_NewSubject QDialog class
        """
        qdialog = Connect_NewSubject(self, self.ui.lineEdit_pathSearch)
        qdialog.signal_error[str].connect(self.on_error)
        qdialog.accepted.connect(self.on_enable_modality)

    # ----------------------------------------------------------------------
    @pyqtSlot()
    def on_click_pathSearch(self):
        """
        Opens the File dialog window when the search button is pressed.
        """

        path_name = QFileDialog.getExistingDirectory(
            caption="Choose the subject's directory",
            directory=os.environ['NEUROD_SCRIPTS'])
        if path_name:
            self.ui.lineEdit_pathSearch.clear()
            self.ui.lineEdit_pathSearch.insert(path_name)
            self.on_enable_modality()

    #----------------------------------------------------------------------
    def on_error(self, errorMsg):
        """
        Display the error message into a QErrorMessage
        """
        self.error_dialog.showMessage(errorMsg)

    #----------------------------------------------------------------------
    def on_click_save_params_to_file(self):
        """
        Save the params to a config_file
        """
        filePath, fileName = os.path.split(self.cfg_subject.__file__)
        fileName = fileName.split('.')[0]  # Remove the .py

        file = self.cfg_subject.__file__.split(
            '.')[0] + '_' + datetime.now().strftime('%m.%d.%d.%M') + '.py'
        filePath = QFileDialog.getSaveFileName(self, 'Save config file', file,
                                               'python(*.py)')
        if filePath[0]:
            save_params_to_file(filePath[0], cfg_class(self.cfg_subject))

    @pyqtSlot(list)
    #----------------------------------------------------------------------
    def fill_comboBox_lsl(self, amp_list):
        """
        Fill the comboBox with the available lsl streams
        """
        # Clear the comboBox_lsl first
        self.ui.comboBox_LSL.clear()

        for amp in amp_list:
            amp_formated = '{} ({})'.format(amp[1], amp[2])
            self.ui.comboBox_LSL.addItem(amp_formated, {
                'name': amp[1],
                'serial': amp[2]
            })
        self.ui.pushButton_LSL.setText('Search')
        self.ui.pushButton_Viewer.setEnabled(True)

    #----------------------------------------------------------------------
    def on_click_lsl_button(self):
        """
        Find the available lsl streams and display them in the comboBox_LSL
        """
        if self.lsl_state.value == 1:

            with self.lsl_state.get_lock():
                self.lsl_state.value = 0

            self.lsl_thread.terminate()
            self.lsl_thread.wait()
            self.ui.pushButton_LSL.setText('Search')

        else:
            self.ui.textEdit_terminal.clear()

            with self.lsl_state.get_lock():
                self.lsl_state.value = 1

            self.lsl_thread = search_lsl_streams_thread(self.lsl_state, logger)
            self.lsl_thread.signal_lsl_found[list].connect(
                self.fill_comboBox_lsl)
            self.lsl_thread.start()

            self.ui.pushButton_LSL.setText('Stop')

    #----------------------------------------------------------------------
    def on_click_start_viewer(self):
        """
        Launch the viewer to check the signals in a seperate process 
        """
        # Start Viewer
        if not self.viewer_state.value:
            self.ui.textEdit_terminal.clear()

            with self.viewer_state.get_lock():
                self.viewer_state.value = 1

            amp = self.ui.comboBox_LSL.currentData()
            viewerprocess = mp.Process(
                target=instantiate_scope,
                args=[amp, self.viewer_state, logger, self.my_receiver.queue])
            viewerprocess.start()

            self.ui.pushButton_Viewer.setText('Stop')

        # Stop Viewer
        else:
            with self.viewer_state.get_lock():
                self.viewer_state.value = 0

            self.ui.pushButton_Viewer.setEnabled(True)
            self.ui.pushButton_Viewer.setText('Viewer')

    @pyqtSlot()
    #----------------------------------------------------------------------
    def on_enable_modality(self):
        """
        Enable the modalities groupBox if the provided path exists
        """
        subjectFolder = self.ui.lineEdit_pathSearch.text()

        if subjectFolder:
            exist = os.path.isdir(subjectFolder)

            if not exist:
                self.signal_error[str].emit(
                    'The provided subject folder does not exists.')
            else:
                self.ui.groupBox_Modality.setEnabled(True)

    #----------------------------------------------------------------------
    def connect_signals_to_slots(self):
        """Connects the signals to the slots"""

        # New subject button
        self.ui.pushButton_NewSubject.clicked.connect(self.on_click_newSubject)

        # Search Subject folder Search button
        self.ui.pushButton_Search.clicked.connect(self.on_click_pathSearch)

        # Enable modality when subject folder path is given
        self.ui.lineEdit_pathSearch.editingFinished.connect(
            self.on_enable_modality)

        # Offline button
        self.ui.pushButton_Offline.clicked.connect(self.on_click_offline)

        # Train button
        self.ui.pushButton_Train.clicked.connect(self.on_click_train)

        # Online button
        self.ui.pushButton_Online.clicked.connect(self.on_click_online)

        # Start button
        self.ui.pushButton_Start.clicked.connect(self.on_click_start)

        # Save conf file
        self.ui.actionSave_config_file.triggered.connect(
            self.on_click_save_params_to_file)

        # Error dialog
        self.signal_error[str].connect(self.on_error)

        # Start viewer button
        self.ui.pushButton_Viewer.clicked.connect(self.on_click_start_viewer)

        # LSL button
        self.ui.pushButton_LSL.clicked.connect(self.on_click_lsl_button)

    #----------------------------------------------------------------------
    def launching_subprocesses(*args):
        """
        Launch subprocesses
        
        processesToLaunch = list of tuple containing the functions to launch
        and their args
        """
        launchedProcesses = dict()

        for p in args[1:]:
            launchedProcesses[p[0]] = mp.Process(target=p[1], args=p[2])
            launchedProcesses[p[0]].start()

        # Wait that the protocol is finished to stop recording
        launchedProcesses['protocol'].join()

        try:
            launchedProcesses['recording']
            recordState = args[1][2][0]  #  Sharing variable
            with recordState.get_lock():
                recordState.value = 0
        except:
            pass