Example #1
0
 def run(self):
     """
     Runs the Matlab script
     """
     config = Config()
     subprocess.run(
         [config.get_matlab_path(), '-nodisplay', '-r', self.matlab_script])
Example #2
0
    def closeEvent(self, event):
        """Override the QWidget closing event to check if there are unsaved
        modifications

        :param event: closing event
        """

        if self.check_unsaved_modifications() == False or self.test:
            can_exit = True

        else:
            self.pop_up_close = PopUpQuit(self.project)
            self.pop_up_close.save_as_signal.connect(self.saveChoice)
            self.pop_up_close.exec()
            can_exit = self.pop_up_close.can_exit()

        if can_exit:
            self.project.unsaveModifications()

            # Clean up
            config = Config()
            opened_projects = config.get_opened_projects()
            opened_projects.remove(self.project.folder)
            config.set_opened_projects(opened_projects)
            self.remove_raw_files_useless()

            event.accept()
        else:
            event.ignore()
    def loadSavedProjects(self):
        """Load the savedProjects dictionary from the saved_projects.yml file.

        If the saved_projects.yml file is not existing, it is created with the
        "{paths: []}" value and the returned dictionnary is {paths: []}.

        :returns: the dictionary

        """
        config = Config()

        try:

            with open(
                    os.path.join(config.get_config_path(),
                                 'saved_projects.yml'), 'r') as stream:

                try:
                    if version.parse(yaml.__version__) > version.parse("5.1"):
                        return yaml.load(stream, Loader=yaml.FullLoader)
                    else:
                        return yaml.load(stream)

                except yaml.YAMLError as exc:
                    print(exc)

        except FileNotFoundError as exc:

            with open(
                    os.path.join(config.get_config_path(),
                                 'saved_projects.yml'), 'w') as stream:
                yaml.dump({'paths': []}, stream, default_flow_style=False)

                return {'paths': []}
Example #4
0
def set_projects_directory_as_default(dialog):
    """
    Sets the projects directory as default

    :param dialog: current file dialog
    """
    config = Config()
    projects_directory = config.get_projects_save_path()
    if not os.path.exists(projects_directory):
        os.makedirs(projects_directory)
    dialog.setDirectory(projects_directory)
Example #5
0
    def verify_slices(self, file_paths):
        """Make 'Show all slices' checkbox unclickable if len(file_paths) > 1.

        :param file_paths: the selected documents
        """
        # Updating the config
        self.config = Config()
        if len(file_paths) > 1:
            self.config.setShowAllSlices(False)
            self.check_box_slices.setCheckState(Qt.Unchecked)
            self.check_box_slices.setCheckable(False)
        else:
            self.check_box_slices.setCheckable(True)
        self.show_slices(file_paths)
    def saveSavedProjects(self):
        """Saves the savedProjects dictionary to the saved_projects.yml
        file."""

        config = Config()

        with (open(os.path.join(config.get_config_path(),
                                'saved_projects.yml'),
                   'w',
                   encoding='utf8')) as configfile:
            yaml.dump(self.savedProjects,
                      configfile,
                      default_flow_style=False,
                      allow_unicode=True)
    def rows_borders_added(self, links):
        """Add the links and the added row to the good rows

        :param links: Old links to reput
        """

        # Plus added to the last row
        sources_images_dir = Config().getSourceImageDir()
        add_search_bar_label = ClickableLabel()
        add_search_bar_label.setObjectName('plus')
        add_search_bar_picture = QPixmap(
            os.path.relpath(os.path.join(sources_images_dir,
                                         "green_plus.png")))
        add_search_bar_picture = add_search_bar_picture.scaledToHeight(20)
        add_search_bar_label.setPixmap(add_search_bar_picture)
        add_search_bar_label.clicked.connect(self.add_search_bar)
        self.rows[len(self.rows) - 1][6] = add_search_bar_label

        # Link added to every row, except the first one
        for i in range(1, len(self.rows)):
            row = self.rows[i]
            link_choice = QComboBox()
            link_choice.setObjectName('link')
            link_choice.addItem("AND")
            link_choice.addItem("OR")
            if len(links) >= i:
                link_choice.setCurrentText(links[i - 1])
            row[0] = link_choice
Example #8
0
 def update_package_library_action(self):
     """Update the package library action depending on the mode."""
     if Config().get_clinical_mode() == True:
         self.action_package_library.setDisabled(True)
         # self.action_install_processes.setDisabled(True)
     else:
         self.action_package_library.setEnabled(True)
Example #9
0
    def create_view_actions(self):
        """Create the actions and their shortcuts in each menu"""

        self.action_create.setShortcut('Ctrl+N')
        self.action_open.setShortcut('Ctrl+O')
        self.action_save.setShortcut('Ctrl+S')
        self.addAction(self.action_save)

        self.action_save_as.setShortcut('Ctrl+Shift+S')
        self.addAction(self.action_save_as)

        self.action_import.setShortcut('Ctrl+I')

        for i in range(self.config.get_max_projects()):
            self.saved_projects_actions.append(
                QAction(self,
                        visible=False,
                        triggered=self.open_recent_project))
        if Config().get_clinical_mode() == True:
            self.action_package_library.setDisabled(True)
        else:
            self.action_package_library.setEnabled(True)

        self.action_exit.setShortcut('Ctrl+W')

        self.action_undo.setShortcut('Ctrl+Z')

        self.action_redo.setShortcut('Ctrl+Y')

        # if Config().get_clinical_mode() == True:
        #     self.action_install_processes.setDisabled(True)
        # else:
        #     self.action_install_processes.setEnabled(True)

        # Connection of the several triggered signals of the actions to some
        # other methods
        self.action_create.triggered.connect(self.create_project_pop_up)
        self.action_open.triggered.connect(self.open_project_pop_up)
        self.action_exit.triggered.connect(self.close)
        self.action_save.triggered.connect(self.save)
        self.action_save_as.triggered.connect(self.save_as)
        self.action_import.triggered.connect(self.import_data)
        self.action_see_all_projects.triggered.connect(self.see_all_projects)
        self.action_project_properties.triggered.connect(
            self.project_properties_pop_up)
        self.action_software_preferences.triggered.connect(
            self.software_preferences_pop_up)
        self.action_package_library.triggered.connect(
            self.package_library_pop_up)
        self.action_undo.triggered.connect(self.undo)
        self.action_redo.triggered.connect(self.redo)
        self.action_documentation.triggered.connect(self.documentation)
        self.action_install_processes_folder.triggered.connect(
            lambda: self.install_processes_pop_up(folder=True))
        self.action_install_processes_zip.triggered.connect(
            lambda: self.install_processes_pop_up(folder=False))
Example #10
0
    def verify_slices(self, file_paths):
        """Make 'Show all slices' checkbox unclickable if len(file_paths) > 1.

        :param file_paths: the selected documents
        """
        # Updating the config
        self.config = Config()
        if len(file_paths) > 1:
            self.config.setShowAllSlices(False)
            self.check_box_slices.setCheckState(Qt.Unchecked)
            self.check_box_slices.setCheckable(False)
        else:
            self.check_box_slices.setCheckable(True)
        self.show_slices(file_paths)

        if self.config.isRadioView() == True:
            self.label_orientation.setText("Radiological orientation")

        else:
            self.label_orientation.setText("Neurological orientation")
Example #11
0
    def create_tabs(self):
        """Create the tabs and initializes the DataBrowser and PipelineManager
        classes."""
        self.config = Config()

        self.tabs.setAutoFillBackground(False)
        self.tabs.setStyleSheet('QTabBar{font-size:16pt;text-align: center}')
        self.tabs.setMovable(True)

        self.tabs.addTab(self.data_browser, "Data Browser")

        # To uncomment when the Data Viewer will be created
        # self.image_viewer = ImageViewer()
        self.tabs.addTab(self.image_viewer, "Data Viewer")
        self.tabs.addTab(self.pipeline_manager, "Pipeline Manager")

        self.tabs.currentChanged.connect(self.tab_changed)
        vertical_layout = QVBoxLayout()
        vertical_layout.addWidget(self.tabs)

        self.centralWindow.setLayout(vertical_layout)
Example #12
0
    def create_project_pop_up(self):
        """Create a new project."""

        if self.check_unsaved_modifications():
            self.pop_up_close = PopUpQuit(self.project)
            self.pop_up_close.save_as_signal.connect(self.saveChoice)
            self.pop_up_close.exec()
            can_switch = self.pop_up_close.can_exit()

        else:
            can_switch = True
        if can_switch:
            # Opens a pop-up when the 'New project' action is clicked and
            # updates the recent projects
            self.exPopup = PopUpNewProject()

            if self.exPopup.exec_():

                self.project.session.unsave_modifications()
                self.remove_raw_files_useless()  # We remove the useless
                # files from the old project

                file_name = self.exPopup.selectedFiles()
                self.exPopup.get_filename(self.exPopup.selectedFiles())
                file_name = self.exPopup.relative_path

                # Removing the old project from the list of currently opened
                # projects
                config = Config()
                opened_projects = config.get_opened_projects()
                opened_projects.remove(self.project.folder)
                config.set_opened_projects(opened_projects)

                self.project = Project(self.exPopup.relative_path, True)

                self.update_project(file_name)  # project updated everywhere
Example #13
0
    def layout_view(self):
        """Create the layout."""

        sources_images_dir = Config().getSourceImageDir()

        self.button_cross.setStyleSheet('background-color:rgb(255, 255, 255);')
        self.button_cross.setIcon(
            QIcon(os.path.join(sources_images_dir, 'gray_cross.png')))
        self.button_cross.clicked.connect(self.reset_search_bar)

        search_bar_layout = QHBoxLayout()
        search_bar_layout.addWidget(self.rapid_search)
        search_bar_layout.addWidget(self.button_cross)

        push_button_tags = QPushButton("Visualized tags")
        push_button_tags.clicked.connect(self.update_tags)

        self.push_button_tag_filter.clicked.connect(self.update_tag_to_filter)

        push_button_ok = QPushButton("OK")
        push_button_ok.clicked.connect(self.ok_clicked)

        push_button_cancel = QPushButton("Cancel")
        push_button_cancel.clicked.connect(self.close)

        # Layout
        buttons_layout = QHBoxLayout()
        buttons_layout.addWidget(push_button_tags)
        buttons_layout.addWidget(self.push_button_tag_filter)
        buttons_layout.addStretch(1)
        buttons_layout.addWidget(push_button_ok)
        buttons_layout.addWidget(push_button_cancel)

        main_layout = QVBoxLayout()
        main_layout.addLayout(search_bar_layout)
        main_layout.addWidget(self.advanced_search)
        main_layout.addWidget(self.table_data)
        main_layout.addLayout(buttons_layout)

        self.setLayout(main_layout)

        screen_resolution = QApplication.instance().desktop().screenGeometry()
        width, height = screen_resolution.width(), screen_resolution.height()
        self.setMinimumWidth(0.6 * width)
        self.setMinimumHeight(0.8 * height)
Example #14
0
        def _clean_up():

            global main_window
            config = Config()
            opened_projects = config.get_opened_projects()

            try:
                opened_projects.remove(main_window.project.folder)
                config.set_opened_projects(opened_projects)
                main_window.remove_raw_files_useless()

            except AttributeError as e:
                opened_projects = []
                config.set_opened_projects(opened_projects)

            print("\nClean up before closing mia done ...\n")
Example #15
0
    def create_view_window(self):
        """Create the main window view."""
        sources_images_dir = Config().getSourceImageDir()
        app_icon = QIcon(os.path.join(sources_images_dir, 'brain_mri.jpeg'))
        self.setWindowIcon(app_icon)
        background_color = self.config.getBackgroundColor()
        text_color = self.config.getTextColor()

        if self.config.dev_mode:
            self.windowName += " (Developer mode)"
        self.windowName += " - "

        self.setStyleSheet("background-color:" + background_color + ";color:" +
                           text_color + ";")
        self.statusBar().showMessage('Please create a new project (Ctrl+N) or '
                                     'open an existing project (Ctrl+O)')

        self.setWindowTitle(self.windowName + self.projectName)
Example #16
0
    def fill_last_tag(self):
        """
        Fills the cells corresponding to the last selected tag
        """

        # Cells of the last tag
        for col in range(self.idx_last_tag + 1, self.nb_col):
            nb_scans_ok = 0
            # Creating a tag_list that will contain
            # couples tag_name/tag_value that
            # will then querying the Database
            for row in range(self.nb_row):
                tag_list = []
                for idx_first_columns in range(self.idx_last_tag + 1):
                    tag_name = self.table.horizontalHeaderItem(
                        idx_first_columns).text()
                    tag_type = self.project.session.get_field(
                        COLLECTION_CURRENT, tag_name).field_type
                    value_str = self.table.item(row, idx_first_columns).data(
                        Qt.EditRole)
                    value_database = table_to_database(value_str, tag_type)
                    tag_list.append([tag_name, value_database])
                tag_last_columns = self.push_buttons[-1].text()
                tag_last_columns_type = self.project.session.get_field(
                    COLLECTION_CURRENT, tag_last_columns).field_type
                value_last_columns_str = self.table.horizontalHeaderItem(
                    col).data(Qt.EditRole)
                value_last_columns_database = table_to_database(
                    value_last_columns_str, tag_last_columns_type)
                tag_list.append(
                    [tag_last_columns, value_last_columns_database])

                item = QTableWidgetItem()
                item.setFlags(QtCore.Qt.ItemIsEnabled)
                # Getting the list of the scans that corresponds to the couples
                # tag_name/tag_values
                generator_scans = self.project.session.filter_documents(
                    COLLECTION_CURRENT, self.prepare_filter(tag_list))

                # List of scans created, given the generator
                list_scans = [
                    getattr(scan, TAG_FILENAME) for scan in generator_scans
                ]

                sources_images_dir = Config().getSourceImageDir()
                if list_scans:
                    icon = QIcon(
                        os.path.join(sources_images_dir, 'green_v.png'))
                    length = len(list_scans)
                    nb_scans_ok += length
                    text = str(length)
                    item.setText(text)
                    tool_tip = ''
                    # Setting as tooltip all the corresponding scans
                    for tpl in list_scans:
                        tool_tip += (tpl + '\n')
                    tool_tip = tool_tip[:-1]
                    item.setToolTip(tool_tip)
                else:
                    icon = QIcon(
                        os.path.join(sources_images_dir, 'red_cross.png'))
                item.setIcon(icon)
                self.table.setItem(row, col, item)

            item = QTableWidgetItem()
            item.setText(str(nb_scans_ok))
            item.setFont(self.font)
            self.table.setItem(self.nb_row, col, item)
Example #17
0
    def __init__(self, project):
        """
        Initialization of the Count Table

        :param project: current project in the software
        """

        super().__init__()

        self.project = project

        # Issue #147: Allow to interact with database while the popup is open
        # self.setModal(True)
        self.setWindowTitle("Count table")

        # Font
        self.font = QFont()
        self.font.setBold(True)

        # values_list will contain the different values of each selected tag
        self.values_list = [[], []]

        self.label_tags = QLabel('Tags: ')

        # Each push button will allow the user to add a tag to the count table
        push_button_tag_1 = QPushButton()
        push_button_tag_1.setText("Tag n°1")
        push_button_tag_1.clicked.connect(lambda: self.select_tag(0))

        push_button_tag_2 = QPushButton()
        push_button_tag_2.setText("Tag n°2")
        push_button_tag_2.clicked.connect(lambda: self.select_tag(1))

        # The list of all the push buttons
        # (the user can add as many as he or she wants)
        self.push_buttons = []
        self.push_buttons.insert(0, push_button_tag_1)
        self.push_buttons.insert(1, push_button_tag_2)

        # Labels to add/remove a tag (a push button)
        sources_images_dir = Config().getSourceImageDir()
        self.remove_tag_label = ClickableLabel()
        remove_tag_picture = QPixmap(
            os.path.relpath(os.path.join(sources_images_dir, "red_minus.png")))
        remove_tag_picture = remove_tag_picture.scaledToHeight(20)
        self.remove_tag_label.setPixmap(remove_tag_picture)
        self.remove_tag_label.clicked.connect(self.remove_tag)

        self.add_tag_label = ClickableLabel()
        self.add_tag_label.setObjectName('plus')
        add_tag_picture = QPixmap(
            os.path.relpath(os.path.join(sources_images_dir,
                                         "green_plus.png")))
        add_tag_picture = add_tag_picture.scaledToHeight(15)
        self.add_tag_label.setPixmap(add_tag_picture)
        self.add_tag_label.clicked.connect(self.add_tag)

        # Push button that is pressed to launch the computations
        self.push_button_count = QPushButton()
        self.push_button_count.setText('Count scans')
        self.push_button_count.clicked.connect(self.count_scans)

        # The table that will be filled
        self.table = QTableWidget()

        # Layouts
        self.v_box_final = QVBoxLayout()
        self.setLayout(self.v_box_final)
        self.refresh_layout()
Example #18
0
    def preferences(self):
        '''Preferences for the dataviewer
        '''
        #Get initial config:
        im_sec = Config().getViewerFramerate()
        config = Config().getViewerConfig()
        ref = Config().get_referential()

        dialog = Qt.QDialog()
        dialog.setWindowTitle('Preferences')
        dialog.resize(600, 400)
        layout = Qt.QVBoxLayout()
        layout.setContentsMargins(25, 25, 25, 25)
        dialog.setLayout(layout)

        #Change Neuro/Radio configuration
        config_layout = QHBoxLayout()
        title_config = Qt.QLabel()
        title_config.setText('Configuration: ')
        box = Qt.QComboBox()
        box.addItem('Neuro')
        box.addItem('Radio')
        config_layout.addWidget(title_config)
        config_layout.addWidget(box)
        if config == 'radio':
            box.setCurrentIndex(1)

        #set automatic time frame rate
        frame_rate_layout = QHBoxLayout()
        title = Qt.QLabel()
        title.setText('Automatic time image display:')
        slider = Qt.QSlider(Qt.Qt.Horizontal)
        slider.setRange(1, 100)
        slider.setValue(int(im_sec))
        size = QtCore.QSize(180, 15)
        slider.setMinimumSize(size)
        slow_label = Qt.QLabel()
        fast_label = Qt.QLabel()
        slow_label.setText('slow')
        fast_label.setText('fast')
        frame_rate_layout.addWidget(title)
        frame_rate_layout.addWidget(slow_label)
        frame_rate_layout.addWidget(slider)
        frame_rate_layout.addWidget(fast_label)
        frame_rate_layout.insertSpacing(1, 200)

        #Change referential
        ref_layout = QHBoxLayout()
        title_ref = Qt.QLabel()
        title_ref.setText('Referential: ')
        box2 = Qt.QComboBox()
        box2.addItem('World Coordinates')
        box2.addItem('Image referential')
        ref_layout.addWidget(title_ref)
        ref_layout.addWidget(box2)
        box2.setCurrentIndex(int(ref))

        #Set general vertical layout
        layout.addLayout(config_layout)
        layout.addLayout(frame_rate_layout)
        layout.addLayout(ref_layout)
        layout.addStretch(1)

        #Save and cancel buttons
        hlay = Qt.QHBoxLayout()
        layout.addLayout(hlay)
        ok = Qt.QPushButton('Save')
        hlay.addStretch(1)
        hlay.addWidget(ok)
        ok.clicked.connect(dialog.accept)
        ok.setDefault(True)
        cancel = Qt.QPushButton('Cancel')
        hlay.addWidget(cancel)
        cancel.clicked.connect(dialog.reject)
        hlay.addStretch(1)

        res = dialog.exec_()

        if res == Qt.QDialog.Accepted:
            new_config = box.currentText().lower()
            new_ref = box2.currentIndex()

            #Save Config parameters and reload images
            #when config and referential have changed
            Config().setViewerFramerate(slider.value())
            Config().setViewerConfig(new_config)
            Config().set_referential(new_ref)
            if new_config != config:
                self.anaviewer.changeConfig(new_config)
            if new_ref != ref:
                self.anaviewer.changeRef()
Example #19
0
    def __init__(self, project):
        """Initialise the MiniViewer object

        :param project: current project in the software

        """
        super().__init__()

        self.project = project
        self.first_time = True

        # The MiniViewer is set hidden to give more space to the data_browser
        self.setHidden(True)

        # When multiple selection, limiting the number of thumbnails to
        # max_scans
        self.max_scans = 4

        # Config that allows to read the software preferences
        self.config = Config()

        # Initializing some components of the MiniViewer
        self.labels = QWidget()
        self.frame = QFrame()
        self.scroll_area = QScrollArea()
        self.scroll_area.setWidget(self.frame)
        self.frame_final = QFrame()

        self.label_nb_slices = QLabel()
        self.label_nb_slices.setText("Maximum number of slices: ")

        self.line_edit_nb_slices = QLineEdit()
        self.line_edit_nb_slices.setText(str(self.config.getNbAllSlicesMax()))
        self.line_edit_nb_slices.returnPressed.connect(self.update_nb_slices)

        # All these objects are depending on the number of scans to visualize
        self.im_2D = []
        self.slider_3D = []
        self.slider_4D = []
        self.slider_5D = []
        self.txt_slider_3D = []
        self.txt_slider_4D = []
        self.txt_slider_5D = []
        self.label3D = []
        self.label4D = []
        self.label5D = []
        self.imageLabel = []
        self.img = []
        self.label_description = []

        # Layouts
        self.createLayouts()
        self.setLayout(self.v_box_final)

        # Checkboxes
        self.check_box_slices = QCheckBox('Show all slices (no cursors)')
        if self.config.getShowAllSlices() == True:
            self.check_box_slices.setCheckState(Qt.Checked)
        else:
            self.check_box_slices.setCheckState(Qt.Unchecked)
        self.check_box_slices.stateChanged.connect(
            self.update_visualization_method)

        self.check_box_cursors = QCheckBox('Chain cursors')
        self.check_box_cursors.setToolTip("Allows to connect all cursors "
                                          "when selecting multiple documents")
        if self.config.getChainCursors() == True:
            self.check_box_cursors.setCheckState(Qt.Checked)
        else:
            self.check_box_cursors.setCheckState(Qt.Unchecked)
        self.check_box_cursors.stateChanged.connect(
            self.check_box_cursors_state_changed)

        self.file_paths = ""
Example #20
0
    def import_data(self):
        """Call the import software (MRI File Manager), reads the imported
        files and loads them into the database.

        """
        # Opens the conversion software to convert the MRI files in Nifti/Json
        config = Config()
        home = expanduser("~")
        code_exit = subprocess.call([
            'java', '-Xmx4096M', '-jar',
            config.get_mri_conv_path(), '[ProjectsDir] ' + home,
            '[ExportNifti] ' +
            os.path.join(self.project.folder, 'data', 'raw_data'),
            '[ExportToMIA] PatientName-StudyName-'
            'CreationDate-SeqNumber-Protocol-'
            'SequenceName-AcquisitionTime', 'CloseAfterExport'
        ])

        # 'NoLogExport'if we don't want log export

        if code_exit == 0:

            # Database filled
            new_scans = data_loader.read_log(self.project, self)

            # Table updated
            documents = self.project.session.get_documents_names(
                COLLECTION_CURRENT)
            self.data_browser.table_data.scans_to_visualize = documents
            self.data_browser.table_data.scans_to_search = documents
            self.data_browser.table_data.add_columns()
            self.data_browser.table_data.fill_headers()
            self.data_browser.table_data.add_rows(new_scans)
            self.data_browser.reset_search_bar()
            self.data_browser.frame_advanced_search.setHidden(True)
            self.data_browser.advanced_search.rows = []

        elif code_exit == 100:  # User only close mri_conv and do nothing
            pass

        else:
            print(
                "\nmri_conv, did not work properly. Current absolute path to "
                "MRIManager.jar defined in File > MIA Preferences:\n{0}\n".
                format(config.get_mri_conv_path()))

            if not os.path.isfile(config.get_mri_conv_path()):
                mssgText = ("Warning: mri_conv did not work properly. The "
                            "current absolute path to MRIManager.jar doesn't "
                            "seem to be correctly defined.\nCurrent absolute "
                            "path to MRIManager.jar defined in\nFile > MIA "
                            "Preferences:\n{0}".format(
                                config.get_mri_conv_path()))

            else:
                mssgText = ("Warning : mri_conv did not work properly. Please "
                            "check if the currently installed mri_conv Java "
                            "ARchive is not corrupted.\nCurrent absolute path "
                            "to MRIManager.jar defined in\nFile > MIA "
                            "Preferences:\n{0}".format(
                                config.get_mri_conv_path()))

            msg = QMessageBox()
            msg.setIcon(QMessageBox.Warning)
            msg.setWindowTitle("populse_mia - Warning: " "Data import issue!")
            msg.setText(mssgText)
            msg.setStandardButtons(QMessageBox.Ok)
            msg.buttonClicked.connect(msg.close)
            msg.exec()
Example #21
0
    def filter_documents(self):
        '''Filter documents already loaded in the Databrowser
        '''
        dialog = Qt.QDialog()
        dialog.setWindowTitle('Filter documents')
        dialog.resize(1150, 500)
        layout = Qt.QVBoxLayout()
        dialog.setLayout(layout)

        #Some specific filtering
        #QLineEdit for research
        self.search_bar = RapidSearch(dialog)
        self.search_bar.textChanged.connect(self.search_str)
        #Cancel search button
        sources_images_dir = Config().getSourceImageDir()
        button_cross = QToolButton()
        button_cross.setStyleSheet('background-color:rgb(255, 255, 255);')
        button_cross.setIcon(
            QIcon(os.path.join(sources_images_dir, 'gray_cross.png')))
        button_cross.clicked.connect(self.reset_search_bar)

        title = Qt.QLabel()
        title.setText('Search by FileName: ')

        layout.addWidget(title)

        search_bar_layout = QHBoxLayout()
        search_bar_layout.addWidget(self.search_bar)
        search_bar_layout.addSpacing(3)
        search_bar_layout.addWidget(button_cross)
        #Add layout to dialogBox
        layout.addLayout(search_bar_layout)
        layout.addSpacing(8)

        self.table_data = TableDataBrowser(
            self.project,
            self,
            self.project.session.get_shown_tags(),
            False,
            True,
            link_viewer=False)
        layout.addWidget(self.table_data)
        hlay = Qt.QHBoxLayout()
        layout.addLayout(hlay)
        ok = Qt.QPushButton('Import')
        hlay.addWidget(ok)
        ok.clicked.connect(dialog.accept)
        ok.setDefault(True)
        cancel = Qt.QPushButton('Cancel')
        hlay.addWidget(cancel)
        cancel.clicked.connect(dialog.reject)
        hlay.addStretch(1)

        # Reducing the list of scans to selection
        all_scans = self.table_data.scans_to_visualize
        self.table_data.scans_to_visualize = self.documents
        self.table_data.scans_to_search = self.documents
        self.table_data.update_visualized_rows(all_scans)

        res = dialog.exec_()
        if res == Qt.QDialog.Accepted:
            points = self.table_data.selectedIndexes()
            result_names = []
            for point in points:
                row = point.row()
                # We get the FileName of the scan from the first row
                scan_name = self.table_data.item(row, 0).text()
                value = self.project.session.get_value(COLLECTION_CURRENT,
                                                       scan_name, TAG_FILENAME)
                value = os.path.abspath(
                    os.path.join(self.project.folder, value))
                result_names.append(value)
            self.display_files(result_names)
    def add_search_bar(self):
        """Create and define the advanced research bar."""
        row_layout = []

        # NOT choice
        not_choice = QComboBox()
        not_choice.setObjectName('not')
        not_choice.addItem("")
        not_choice.addItem("NOT")

        # Field choice
        field_choice = QComboBox()
        field_choice.setObjectName('field')
        if len(self.tags_list) > 0:
            for tag in self.tags_list:
                field_choice.addItem(tag)
        else:
            for tag in self.project.session.get_shown_tags():
                field_choice.addItem(tag)
        field_choice.model().sort(0)
        field_choice.addItem("All visualized tags")

        # Value choice
        condition_value = QLineEdit()
        condition_value.setObjectName('value')

        # Condition choice
        condition_choice = QComboBox()
        condition_choice.setObjectName('condition')
        condition_choice.addItem("==")
        condition_choice.addItem("!=")
        condition_choice.addItem(">=")
        condition_choice.addItem("<=")
        condition_choice.addItem(">")
        condition_choice.addItem("<")
        condition_choice.addItem("BETWEEN")
        condition_choice.addItem("IN")
        condition_choice.addItem("CONTAINS")
        condition_choice.addItem("HAS VALUE")
        condition_choice.addItem("HAS NO VALUE")
        condition_choice.model().sort(0)

        # Signal to update the placeholder text of the value
        condition_choice.currentTextChanged.connect(
            lambda: self.displayValueRules(condition_choice, condition_value))

        # Signal to update the list of conditions, depending on the tag type
        field_choice.currentTextChanged.connect(
            lambda: self.displayConditionRules(field_choice, condition_choice))

        # Minus to remove the row
        sources_images_dir = Config().getSourceImageDir()
        remove_row_label = ClickableLabel()
        remove_row_picture = QPixmap(
            os.path.relpath(os.path.join(sources_images_dir, "red_minus.png")))
        remove_row_picture = remove_row_picture.scaledToHeight(30)
        remove_row_label.setPixmap(remove_row_picture)

        # Everything appended to the row
        row_layout.append(None)  # Link room
        row_layout.append(not_choice)
        row_layout.append(field_choice)
        row_layout.append(condition_choice)
        row_layout.append(condition_value)
        row_layout.append(remove_row_label)
        row_layout.append(None)  # Add row room

        # Signal to remove the row
        remove_row_label.clicked.connect(lambda: self.remove_row(row_layout))

        self.rows.append(row_layout)

        self.refresh_search()

        self.displayConditionRules(field_choice, condition_choice)
Example #23
0
    def switch_project(self, file_path, name):
        """Check if it's possible to open the selected project
        and quit the current one.

        :param file_path: raw file_path
        :param name: project name

        :return: Boolean
        """
        # /!\ file_path and path are the same param

        # Switching project only if it's a different one
        if file_path != self.project.folder:

            # If the file exists
            if os.path.exists(os.path.join(file_path)):
                # If it is a MIA project
                if os.path.exists(os.path.join(
                        file_path, "properties", "properties.yml")) \
                    and os.path.exists(os.path.join(
                        file_path, "database", "mia.db")) \
                    and os.path.exists(os.path.join(
                        file_path, "data", "raw_data")) \
                    and os.path.exists(os.path.join(
                        file_path, "data", "derived_data")) \
                    and os.path.exists(os.path.join(
                        file_path, "data", "downloaded_data")) \
                    and os.path.exists(os.path.join(
                        file_path, "filters")):

                    # We check for invalid scans in the project

                    try:
                        temp_database = Project(file_path, False)
                    except IOError:
                        msg = QMessageBox()
                        msg.setIcon(QMessageBox.Warning)
                        msg.setText("project already opened")
                        msg.setInformativeText("The project at " +
                                               str(file_path) +
                                               " is already opened in another "
                                               "instance of the software.")
                        msg.setWindowTitle("Warning")
                        msg.setStandardButtons(QMessageBox.Ok)
                        msg.buttonClicked.connect(msg.close)
                        msg.exec()
                        return False
                    problem_list = data_loader.verify_scans(temp_database)

                    # Message if invalid files
                    if problem_list:
                        str_msg = ""
                        for element in problem_list:
                            str_msg += element + "\n\n"
                        msg = QMessageBox()
                        msg.setIcon(QMessageBox.Warning)
                        msg.setText(
                            "These files have been modified or removed since "
                            "they have been converted for the first time:")
                        msg.setInformativeText(str_msg)
                        msg.setWindowTitle("Warning")
                        msg.setStandardButtons(QMessageBox.Ok)
                        msg.buttonClicked.connect(msg.close)
                        msg.exec()

                    self.project.session.unsave_modifications()
                    self.remove_raw_files_useless()
                    # We remove the useless files from the old project

                    # project removed from the opened projects list
                    config = Config()
                    opened_projects = config.get_opened_projects()
                    opened_projects.remove(self.project.folder)
                    config.set_opened_projects(opened_projects)

                    self.project = temp_database  # New Database

                    self.update_project(file_path)
                    # project updated everywhere

                    return True

                # Not a MIA project
                else:
                    msg = QMessageBox()
                    msg.setIcon(QMessageBox.Warning)
                    msg.setText("The project selected isn't a "
                                "valid MIA project")
                    msg.setInformativeText("The project selected " + name +
                                           " isn't a MIA project"
                                           ".\nPlease select a "
                                           "valid one.")
                    msg.setWindowTitle("Warning")
                    msg.setStandardButtons(QMessageBox.Ok)
                    msg.buttonClicked.connect(msg.close)
                    msg.exec()
                    return False

            # The project doesn't exist anymore
            else:
                msg = QMessageBox()
                msg.setIcon(QMessageBox.Warning)
                msg.setText("The project selected doesn't exist anymore")
                msg.setInformativeText("The project selected " + name +
                                       " doesn't exist anymore."
                                       "\nPlease select "
                                       "another one.")
                msg.setWindowTitle("Warning")
                msg.setStandardButtons(QMessageBox.Ok)
                msg.buttonClicked.connect(msg.close)
                msg.exec()
                return False
Example #24
0
    def save_project_as(self):
        """Open a pop-up to save the current project as"""

        self.exPopup = PopUpSaveProjectAs()
        if self.exPopup.exec_():

            old_folder = self.project.folder
            file_name = self.exPopup.relative_path

            database_path = os.path.join(
                os.path.relpath(self.exPopup.relative_path), 'database')
            properties_path = os.path.join(
                os.path.relpath(self.exPopup.relative_path), 'properties')
            filters_path = os.path.join(
                os.path.relpath(self.exPopup.relative_path), 'filters')
            data_path = os.path.join(
                os.path.relpath(self.exPopup.relative_path), 'data')

            raw_data_path = os.path.join(data_path, 'raw_data')
            derived_data_path = os.path.join(data_path, 'derived_data')
            downloaded_data_path = os.path.join(data_path, 'downloaded_data')

            # List of projects updated
            if not self.test:
                self.saved_projects_list = self.saved_projects.addSavedProject(
                    file_name)
            self.update_recent_projects_actions()

            os.makedirs(self.exPopup.relative_path)

            os.mkdir(data_path)
            os.mkdir(raw_data_path)
            os.mkdir(derived_data_path)
            os.mkdir(downloaded_data_path)
            os.mkdir(filters_path)

            # Data files copied
            if os.path.exists(os.path.join(old_folder, 'data')):
                for filename in glob.glob(
                        os.path.join(os.path.relpath(old_folder), 'data',
                                     'raw_data', '*')):
                    shutil.copy(
                        filename,
                        os.path.join(os.path.relpath(data_path), 'raw_data'))
                for filename in glob.glob(
                        os.path.join(os.path.relpath(old_folder), 'data',
                                     'derived_data', '*')):
                    shutil.copy(
                        filename,
                        os.path.join(os.path.relpath(data_path),
                                     'derived_data'))
                for filename in glob.glob(
                        os.path.join(os.path.relpath(old_folder), 'data',
                                     'downloaded_data', '*')):
                    shutil.copy(
                        filename,
                        os.path.join(os.path.relpath(data_path),
                                     'downloaded_data'))

            if os.path.exists(os.path.join(old_folder, 'filters')):
                for filename in glob.glob(
                        os.path.join(os.path.relpath(old_folder), 'filters',
                                     '*')):
                    shutil.copy(filename,
                                os.path.join(os.path.relpath(filters_path)))

            # First we register the Database before commiting the last
            # pending modifications
            shutil.copy(
                os.path.join(os.path.relpath(old_folder), 'database',
                             'mia.db'),
                os.path.join(os.path.relpath(old_folder), 'database',
                             'mia_before_commit.db'))

            # We commit the last pending modifications
            self.project.saveModifications()

            os.mkdir(properties_path)
            shutil.copy(
                os.path.join(os.path.relpath(old_folder),
                             'properties', 'properties.yml'),
                os.path.relpath(properties_path))

            # We copy the Database with all the modifications commited in
            # the new project
            os.mkdir(os.path.relpath(database_path))
            shutil.copy(
                os.path.join(os.path.relpath(old_folder), 'database',
                             'mia.db'), os.path.relpath(database_path))

            # We remove the Database with all the modifications saved in
            # the old project
            os.remove(
                os.path.join(os.path.relpath(old_folder), 'database',
                             'mia.db'))

            # We reput the Database without the last modifications
            # in the old project
            shutil.copy(
                os.path.join(os.path.relpath(old_folder), 'database',
                             'mia_before_commit.db'),
                os.path.join(os.path.relpath(old_folder), 'database',
                             'mia.db'))

            os.remove(
                os.path.join(os.path.relpath(old_folder), 'database',
                             'mia_before_commit.db'))

            self.remove_raw_files_useless()
            # We remove the useless files from the old project

            # Removing the old project from the list of
            # currently opened projects
            config = Config()
            opened_projects = config.get_opened_projects()
            opened_projects.remove(self.project.folder)
            config.set_opened_projects(opened_projects)

            # project updated everywhere
            self.project = Project(self.exPopup.relative_path, False)
            self.project.setName(os.path.basename(self.exPopup.relative_path))
            self.project.setDate(datetime.now().strftime('%d/%m/%Y %H:%M:%S'))
            self.project.saveModifications()

            self.update_project(file_name, call_update_table=False)
            # project updated everywhere

            # If some files have been set in the pipeline editors,
            # display a warning message
            if self.pipeline_manager.pipelineEditorTabs.has_pipeline_nodes():
                msg = QMessageBox()
                msg.setIcon(QMessageBox.Warning)
                msg.setText("This action moves the current database. "
                            "All pipelines will need to be initialized "
                            "again before they can run.")
                msg.setWindowTitle("Warning")
                msg.setStandardButtons(QMessageBox.Ok)
                msg.buttonClicked.connect(msg.close)
                msg.exec()
Example #25
0
class MainWindow(QMainWindow):
    """Initialize software appearance and define interactions with the user.
..

    Methods:
        - __init__ : initialise the object MainWindow
        - add_clinical_tags: add the clinical tags to the database and the
          data browser
        - check_unsaved_modifications: check if there are differences
          between the current project and the database
        - closeEvent: override the closing event to check if there are
          unsaved modifications
        - create_view_actions: create the actions in each menu
        - create_view_menus: create the menu-bar
        - create_view_window: create the main window view
        - create_project_pop_up: create a new project
        - create_tabs: create the tabs
        - documentation: open the documentation in a web browser
        - import_data: call the import software (MRI File Manager)
        - install_processes_pop_up: open the install processes pop-up
        - open_project_pop_up: open a pop-up to open a project and updates
          the recent projects
        - open_recent_project: open a recent project
        - package_library_pop_up: open the package library pop-up
        - project_properties_pop_up: open the project properties pop-up
        - redo: redo the last action made by the user
        - remove_raw_files_useless: remove the useless raw files of the
          current project
        - save: save either the current project or the current pipeline
        - save_as: save either the current project or the current pipeline
          under a new name
        - save_project_as: open a pop-up to save the current project as
        - saveChoice: checks if the project needs to be saved as or just saved
        - see_all_projects: open a pop-up to show the recent projects
        - software_preferences_pop_up: open the MIA2 preferences pop-up
        - switch_project: switches project if it's possible
        - tab_changed: method called when the tab is changed
        - undo: undoes the last action made by the user
        - update_package_library_action: update the package library action
          depending on the mode
        - update_project: update the project once the database has been
          updated
        - update_recent_projects_actions: update the list of recent projects

    """
    def __init__(self, project, test=False, deleted_projects=None):
        """Main window class, initializes the software appearance and defines
        interactions with the user.

            :Parameter:
                - :project: current project in the software
                - :test: boolean if the widget is launched from unit tests
                     or not
                - :deleted_projects: projects that have been deleted

        """
        super(MainWindow, self).__init__()

        QApplication.restoreOverrideCursor()

        # We associate these methods and the instance to be able to call them
        # from anywhere.
        QCoreApplication.instance().title = self.windowTitle
        QCoreApplication.instance().set_title = self.setWindowTitle

        if deleted_projects is not None and deleted_projects:
            self.msg = PopUpDeletedProject(deleted_projects)

        self.config = Config()
        self.config.setSourceImageDir(
            os.path.join(
                os.path.dirname(os.path.dirname(os.path.realpath(__file__))),
                "sources_images"))
        self.windowName = "MIA - Multiparametric Image Analysis"
        self.projectName = "Unnamed project"
        self.project = project
        self.test = test

        self.saved_projects = SavedProjects()
        self.saved_projects_list = self.saved_projects.pathsList

        self.saved_projects_actions = []

        # Define main window view
        self.create_view_window()

        # Initialize menu
        self.menu_file = self.menuBar().addMenu('File')
        self.menu_edition = self.menuBar().addMenu('Edit')
        self.menu_help = self.menuBar().addMenu('Help')
        self.menu_about = self.menuBar().addMenu('About')
        self.menu_more = self.menuBar().addMenu('More')
        self.menu_install_process = QMenu('Install processes', self)
        self.menu_saved_projects = QMenu('Saved projects', self)

        # Initialize tabs
        self.tabs = QTabWidget()
        self.data_browser = DataBrowser(self.project, self)
        self.image_viewer = QLabel("Coming soon...")
        self.pipeline_manager = PipelineManagerTab(self.project, [], self)
        self.centralWindow = QWidget()

        # Initialize menu actions
        sources_images_dir = Config().getSourceImageDir()
        self.action_save_project = self.menu_file.addAction("Save project")
        self.action_save_project_as = self.menu_file.addAction("Save "
                                                               "project as")
        self.action_create = QAction('New project', self)
        self.action_open = QAction('Open project', self)
        self.action_save = QAction('Save', self)
        self.action_save_as = QAction('Save as', self)
        self.action_import = QAction(
            QIcon(os.path.join(sources_images_dir, 'Blue.png')), 'Import',
            self)
        self.action_see_all_projects = QAction('See all projects', self)
        self.action_project_properties = QAction('Project properties', self)
        self.action_software_preferences = QAction('MIA preferences', self)
        self.action_package_library = QAction('Package library manager', self)
        self.action_exit = QAction(
            QIcon(os.path.join(sources_images_dir, 'exit.png')), 'Exit', self)
        self.action_undo = QAction('Undo', self)
        self.action_redo = QAction('Redo', self)
        self.action_documentation = QAction('Documentation', self)
        self.action_install_processes_folder = QAction('From folder', self)
        self.action_install_processes_zip = QAction('From zip file', self)

        # Connect actions & menus views
        self.create_view_actions()
        self.create_view_menus()

        # Create Tabs
        self.create_tabs()
        self.setCentralWidget(self.centralWindow)
        self.showMaximized()

    def add_clinical_tags(self):
        """Add the clinical tags to the database and the data browser"""

        added_tags = self.project.add_clinical_tags()
        for tag in added_tags:
            column = self.data_browser.table_data.get_index_insertion(tag)
            self.data_browser.table_data.add_column(column, tag)

    def check_unsaved_modifications(self):
        """Check if there are differences between the current project and the
        database.

        :return: Boolean. True if there are unsaved modifications,
           False otherwise
        """
        if self.project.isTempProject:
            if len(self.project.session.get_documents_names(
                    COLLECTION_CURRENT)) > 0:
                return True
            else:
                return False
        elif self.project.hasUnsavedModifications():
            return True
        else:
            return False

    def closeEvent(self, event):
        """Override the QWidget closing event to check if there are unsaved
        modifications

        :param event: closing event
        """

        if self.check_unsaved_modifications() == False or self.test:
            can_exit = True

        else:
            self.pop_up_close = PopUpQuit(self.project)
            self.pop_up_close.save_as_signal.connect(self.saveChoice)
            self.pop_up_close.exec()
            can_exit = self.pop_up_close.can_exit()

        if can_exit:
            self.project.unsaveModifications()

            # Clean up
            config = Config()
            opened_projects = config.get_opened_projects()
            opened_projects.remove(self.project.folder)
            config.set_opened_projects(opened_projects)
            self.remove_raw_files_useless()

            event.accept()
        else:
            event.ignore()

    def create_view_actions(self):
        """Create the actions and their shortcuts in each menu"""

        self.action_create.setShortcut('Ctrl+N')
        self.action_open.setShortcut('Ctrl+O')
        self.action_save.setShortcut('Ctrl+S')
        self.addAction(self.action_save)

        self.action_save_as.setShortcut('Ctrl+Shift+S')
        self.addAction(self.action_save_as)

        self.action_import.setShortcut('Ctrl+I')

        for i in range(self.config.get_max_projects()):
            self.saved_projects_actions.append(
                QAction(self,
                        visible=False,
                        triggered=self.open_recent_project))
        if Config().get_clinical_mode() == True:
            self.action_package_library.setDisabled(True)
        else:
            self.action_package_library.setEnabled(True)

        self.action_exit.setShortcut('Ctrl+W')

        self.action_undo.setShortcut('Ctrl+Z')

        self.action_redo.setShortcut('Ctrl+Y')

        # if Config().get_clinical_mode() == True:
        #     self.action_install_processes.setDisabled(True)
        # else:
        #     self.action_install_processes.setEnabled(True)

        # Connection of the several triggered signals of the actions to some
        # other methods
        self.action_create.triggered.connect(self.create_project_pop_up)
        self.action_open.triggered.connect(self.open_project_pop_up)
        self.action_exit.triggered.connect(self.close)
        self.action_save.triggered.connect(self.save)
        self.action_save_as.triggered.connect(self.save_as)
        self.action_import.triggered.connect(self.import_data)
        self.action_see_all_projects.triggered.connect(self.see_all_projects)
        self.action_project_properties.triggered.connect(
            self.project_properties_pop_up)
        self.action_software_preferences.triggered.connect(
            self.software_preferences_pop_up)
        self.action_package_library.triggered.connect(
            self.package_library_pop_up)
        self.action_undo.triggered.connect(self.undo)
        self.action_redo.triggered.connect(self.redo)
        self.action_documentation.triggered.connect(self.documentation)
        self.action_install_processes_folder.triggered.connect(
            lambda: self.install_processes_pop_up(folder=True))
        self.action_install_processes_zip.triggered.connect(
            lambda: self.install_processes_pop_up(folder=False))

    def create_view_menus(self):
        """Create the menu-bar view."""

        self.menu_more.addMenu(self.menu_install_process)

        # Actions in the "File" menu
        self.menu_file.addAction(self.action_create)
        self.menu_file.addAction(self.action_open)

        self.action_save_project.triggered.connect(self.saveChoice)
        self.action_save_project_as.triggered.connect(self.save_project_as)

        self.menu_file.addSeparator()
        self.menu_file.addAction(self.action_import)
        self.menu_file.addSeparator()
        self.menu_file.addMenu(self.menu_saved_projects)
        for i in range(self.config.get_max_projects()):
            self.menu_saved_projects.addAction(self.saved_projects_actions[i])
        self.menu_saved_projects.addSeparator()
        self.menu_saved_projects.addAction(self.action_see_all_projects)
        self.menu_file.addSeparator()
        self.menu_file.addAction(self.action_software_preferences)
        self.menu_file.addAction(self.action_project_properties)
        self.menu_file.addAction(self.action_package_library)
        self.menu_file.addSeparator()
        self.menu_file.addAction(self.action_exit)
        self.update_recent_projects_actions()

        # Actions in the "Edition" menu
        self.menu_edition.addAction(self.action_undo)
        self.menu_edition.addAction(self.action_redo)

        # Actions in the "Help" menu
        self.menu_help.addAction(self.action_documentation)
        self.menu_help.addAction('Credits')

        # Actions in the "More > Install processes" menu
        self.menu_install_process.addAction(
            self.action_install_processes_folder)
        self.menu_install_process.addAction(self.action_install_processes_zip)

    def create_view_window(self):
        """Create the main window view."""
        sources_images_dir = Config().getSourceImageDir()
        app_icon = QIcon(os.path.join(sources_images_dir, 'brain_mri.jpeg'))
        self.setWindowIcon(app_icon)
        background_color = self.config.getBackgroundColor()
        text_color = self.config.getTextColor()

        if self.config.dev_mode:
            self.windowName += " (Developer mode)"
        self.windowName += " - "

        self.setStyleSheet("background-color:" + background_color + ";color:" +
                           text_color + ";")
        self.statusBar().showMessage('Please create a new project (Ctrl+N) or '
                                     'open an existing project (Ctrl+O)')

        self.setWindowTitle(self.windowName + self.projectName)

    def create_project_pop_up(self):
        """Create a new project."""

        if self.check_unsaved_modifications():
            self.pop_up_close = PopUpQuit(self.project)
            self.pop_up_close.save_as_signal.connect(self.saveChoice)
            self.pop_up_close.exec()
            can_switch = self.pop_up_close.can_exit()

        else:
            can_switch = True
        if can_switch:
            # Opens a pop-up when the 'New project' action is clicked and
            # updates the recent projects
            self.exPopup = PopUpNewProject()

            if self.exPopup.exec_():

                self.project.session.unsave_modifications()
                self.remove_raw_files_useless()  # We remove the useless
                # files from the old project

                file_name = self.exPopup.selectedFiles()
                self.exPopup.get_filename(self.exPopup.selectedFiles())
                file_name = self.exPopup.relative_path

                # Removing the old project from the list of currently opened
                # projects
                config = Config()
                opened_projects = config.get_opened_projects()
                opened_projects.remove(self.project.folder)
                config.set_opened_projects(opened_projects)

                self.project = Project(self.exPopup.relative_path, True)

                self.update_project(file_name)  # project updated everywhere

    def create_tabs(self):
        """Create the tabs and initializes the DataBrowser and PipelineManager
        classes."""
        self.config = Config()

        self.tabs.setAutoFillBackground(False)
        self.tabs.setStyleSheet('QTabBar{font-size:16pt;text-align: center}')
        self.tabs.setMovable(True)

        self.tabs.addTab(self.data_browser, "Data Browser")

        # To uncomment when the Data Viewer will be created
        # self.image_viewer = ImageViewer()
        self.tabs.addTab(self.image_viewer, "Data Viewer")
        self.tabs.addTab(self.pipeline_manager, "Pipeline Manager")

        self.tabs.currentChanged.connect(self.tab_changed)
        vertical_layout = QVBoxLayout()
        vertical_layout.addWidget(self.tabs)

        self.centralWindow.setLayout(vertical_layout)

    @staticmethod
    def documentation():
        """Open the documentation in a web browser."""
        webbrowser.open(
            'https://populse.github.io/populse_mia/html/index.html')

    def install_processes_pop_up(self, folder=False):
        """Open the install processes pop-up.

        :param folder: boolean, True if installing from a folder
        """
        self.pop_up_install_processes = InstallProcesses(self, folder=folder)
        self.pop_up_install_processes.show()
        self.pop_up_install_processes.process_installed.connect(
            self.pipeline_manager.processLibrary.update_process_library)
        self.pop_up_install_processes.process_installed.connect(
            self.pipeline_manager.processLibrary.pkg_library.update_config)

    def import_data(self):
        """Call the import software (MRI File Manager), reads the imported
        files and loads them into the database.

        """
        # Opens the conversion software to convert the MRI files in Nifti/Json
        config = Config()
        home = expanduser("~")
        code_exit = subprocess.call([
            'java', '-Xmx4096M', '-jar',
            config.get_mri_conv_path(), '[ProjectsDir] ' + home,
            '[ExportNifti] ' +
            os.path.join(self.project.folder, 'data', 'raw_data'),
            '[ExportToMIA] PatientName-StudyName-'
            'CreationDate-SeqNumber-Protocol-'
            'SequenceName-AcquisitionTime', 'CloseAfterExport'
        ])

        # 'NoLogExport'if we don't want log export

        if code_exit == 0:

            # Database filled
            new_scans = data_loader.read_log(self.project, self)

            # Table updated
            documents = self.project.session.get_documents_names(
                COLLECTION_CURRENT)
            self.data_browser.table_data.scans_to_visualize = documents
            self.data_browser.table_data.scans_to_search = documents
            self.data_browser.table_data.add_columns()
            self.data_browser.table_data.fill_headers()
            self.data_browser.table_data.add_rows(new_scans)
            self.data_browser.reset_search_bar()
            self.data_browser.frame_advanced_search.setHidden(True)
            self.data_browser.advanced_search.rows = []

        elif code_exit == 100:  # User only close mri_conv and do nothing
            pass

        else:
            print(
                "\nmri_conv, did not work properly. Current absolute path to "
                "MRIManager.jar defined in File > MIA Preferences:\n{0}\n".
                format(config.get_mri_conv_path()))

            if not os.path.isfile(config.get_mri_conv_path()):
                mssgText = ("Warning: mri_conv did not work properly. The "
                            "current absolute path to MRIManager.jar doesn't "
                            "seem to be correctly defined.\nCurrent absolute "
                            "path to MRIManager.jar defined in\nFile > MIA "
                            "Preferences:\n{0}".format(
                                config.get_mri_conv_path()))

            else:
                mssgText = ("Warning : mri_conv did not work properly. Please "
                            "check if the currently installed mri_conv Java "
                            "ARchive is not corrupted.\nCurrent absolute path "
                            "to MRIManager.jar defined in\nFile > MIA "
                            "Preferences:\n{0}".format(
                                config.get_mri_conv_path()))

            msg = QMessageBox()
            msg.setIcon(QMessageBox.Warning)
            msg.setWindowTitle("populse_mia - Warning: " "Data import issue!")
            msg.setText(mssgText)
            msg.setStandardButtons(QMessageBox.Ok)
            msg.buttonClicked.connect(msg.close)
            msg.exec()

    def open_project_pop_up(self):
        """Open a pop-up to open a project and updates the recent projects."""
        # Ui_Dialog() is defined in pop_ups.py
        # We check for unsaved modifications
        if self.check_unsaved_modifications():

            # If there are unsaved modifications, we ask the user what he
            # wants to do
            self.pop_up_close = PopUpQuit(self.project)
            self.pop_up_close.save_as_signal.connect(self.saveChoice)
            self.pop_up_close.exec()
            can_switch = self.pop_up_close.can_exit()

        else:
            can_switch = True

        # We can open a new project
        if can_switch:
            self.exPopup = PopUpOpenProject()
            if self.exPopup.exec_():
                file_name = self.exPopup.selectedFiles()
                self.exPopup.get_filename(file_name)
                file_name = self.exPopup.relative_path

                self.switch_project(file_name, self.exPopup.name)
                # We switch the project

    def open_recent_project(self):
        """Open a recent project."""
        # We check for unsaved modifications
        if self.check_unsaved_modifications():

            # If there are unsaved modifications, we ask the user what he
            # wants to do
            self.pop_up_close = PopUpQuit(self.project)
            self.pop_up_close.save_as_signal.connect(self.saveChoice)
            self.pop_up_close.exec()
            can_switch = self.pop_up_close.can_exit()

        else:
            can_switch = True

        # We can open a new project
        if can_switch:
            action = self.sender()
            if action:
                file_name = action.data()
                entire_path = os.path.abspath(file_name)
                path, name = os.path.split(entire_path)
                relative_path = os.path.relpath(file_name)
                self.switch_project(relative_path, name)
                # We switch the project

    def package_library_pop_up(self):
        """Open the package library pop-up"""

        self.pop_up_package_library = PackageLibraryDialog(self)
        self.pop_up_package_library.setGeometry(300, 200, 800, 600)
        self.pop_up_package_library.show()
        self.pop_up_package_library.signal_save.connect(
            self.pipeline_manager.processLibrary.update_process_library)

    def project_properties_pop_up(self):
        """Open the project properties pop-up"""

        old_tags = self.project.session.get_shown_tags()
        self.pop_up_settings = PopUpProperties(self.project, self.data_browser,
                                               old_tags)
        self.pop_up_settings.setGeometry(300, 200, 800, 600)
        self.pop_up_settings.show()

        if self.pop_up_settings.exec_():
            self.data_browser.table_data.update_visualized_columns(
                old_tags, self.project.session.get_shown_tags())

    def redo(self):
        """Redo the last action made by the user."""
        if self.tabs.tabText(self.tabs.currentIndex()).replace(
                "&", "", 1) == 'Data Browser':
            # In Data Browser
            self.project.redo(self.data_browser.table_data)
            # Action remade in the Database
        elif self.tabs.tabText(self.tabs.currentIndex()).replace(
                "&", "", 1) == 'Pipeline Manager':
            # In Pipeline Manager
            self.pipeline_manager.redo()

    def remove_raw_files_useless(self):
        """Remove the useless raw files of the current project"""

        # If it's unnamed project, we can remove the whole project
        if self.project.isTempProject:
            self.project.database.__exit__(None, None, None)
            shutil.rmtree(self.project.folder)
        else:
            for filename in glob.glob(
                    os.path.join(os.path.relpath(self.project.folder), 'data',
                                 'raw_data', '*')):
                scan = os.path.basename(filename)
                # The file is removed only if it's not a scan in the project,
                # and if it's not a logExport
                # Json files associated to nii files are kept for the raw_
                # data folder
                file_name, file_extension = os.path.splitext(scan)
                file_in_database = False
                for database_scan in self.project.session.get_documents_names(
                        COLLECTION_CURRENT):
                    if file_name in database_scan:
                        file_in_database = True
                if "logExport" in scan:
                    file_in_database = True
                if not file_in_database:
                    os.remove(filename)
            for filename in glob.glob(
                    os.path.join(os.path.relpath(self.project.folder), 'data',
                                 'derived_data', '*')):
                scan = os.path.basename(filename)
                # The file is removed only if it's not a scan in the project,
                # and if it's not a logExport
                if (self.project.session.get_document(
                        COLLECTION_CURRENT,
                        os.path.join("data", "derived_data", scan)) is None
                        and "logExport" not in scan):
                    os.remove(filename)
            for filename in glob.glob(
                    os.path.join(os.path.relpath(self.project.folder), 'data',
                                 'downloaded_data', '*')):
                scan = os.path.basename(filename)
                # The file is removed only if it's not a scan in the project,
                # and if it's not a logExport
                if (self.project.session.get_document(
                        COLLECTION_CURRENT,
                        os.path.join("data", "downloaded_data", scan)) is None
                        and "logExport" not in scan):
                    os.remove(filename)
            self.project.database.__exit__(None, None, None)

    def save(self):
        """Save either the current project or the current pipeline"""

        if self.tabs.tabText(self.tabs.currentIndex()).replace(
                "&", "", 1) == 'Data Browser':
            # In Data Browser
            self.saveChoice()
        elif self.tabs.tabText(self.tabs.currentIndex()).replace(
                "&", "", 1) == 'Pipeline Manager':
            # In Pipeline Manager
            self.pipeline_manager.savePipeline()

    def save_as(self):
        """Save either the current project or the current pipeline under a new
        name.
        """
        if self.tabs.tabText(self.tabs.currentIndex()).replace(
                "&", "", 1) == 'Data Browser':
            # In Data Browser
            self.save_project_as()
        elif self.tabs.tabText(self.tabs.currentIndex()).replace(
                "&", "", 1) == 'Pipeline Manager':
            # In Pipeline Manager
            self.pipeline_manager.savePipelineAs()

    def save_project_as(self):
        """Open a pop-up to save the current project as"""

        self.exPopup = PopUpSaveProjectAs()
        if self.exPopup.exec_():

            old_folder = self.project.folder
            file_name = self.exPopup.relative_path

            database_path = os.path.join(
                os.path.relpath(self.exPopup.relative_path), 'database')
            properties_path = os.path.join(
                os.path.relpath(self.exPopup.relative_path), 'properties')
            filters_path = os.path.join(
                os.path.relpath(self.exPopup.relative_path), 'filters')
            data_path = os.path.join(
                os.path.relpath(self.exPopup.relative_path), 'data')

            raw_data_path = os.path.join(data_path, 'raw_data')
            derived_data_path = os.path.join(data_path, 'derived_data')
            downloaded_data_path = os.path.join(data_path, 'downloaded_data')

            # List of projects updated
            if not self.test:
                self.saved_projects_list = self.saved_projects.addSavedProject(
                    file_name)
            self.update_recent_projects_actions()

            os.makedirs(self.exPopup.relative_path)

            os.mkdir(data_path)
            os.mkdir(raw_data_path)
            os.mkdir(derived_data_path)
            os.mkdir(downloaded_data_path)
            os.mkdir(filters_path)

            # Data files copied
            if os.path.exists(os.path.join(old_folder, 'data')):
                for filename in glob.glob(
                        os.path.join(os.path.relpath(old_folder), 'data',
                                     'raw_data', '*')):
                    shutil.copy(
                        filename,
                        os.path.join(os.path.relpath(data_path), 'raw_data'))
                for filename in glob.glob(
                        os.path.join(os.path.relpath(old_folder), 'data',
                                     'derived_data', '*')):
                    shutil.copy(
                        filename,
                        os.path.join(os.path.relpath(data_path),
                                     'derived_data'))
                for filename in glob.glob(
                        os.path.join(os.path.relpath(old_folder), 'data',
                                     'downloaded_data', '*')):
                    shutil.copy(
                        filename,
                        os.path.join(os.path.relpath(data_path),
                                     'downloaded_data'))

            if os.path.exists(os.path.join(old_folder, 'filters')):
                for filename in glob.glob(
                        os.path.join(os.path.relpath(old_folder), 'filters',
                                     '*')):
                    shutil.copy(filename,
                                os.path.join(os.path.relpath(filters_path)))

            # First we register the Database before commiting the last
            # pending modifications
            shutil.copy(
                os.path.join(os.path.relpath(old_folder), 'database',
                             'mia.db'),
                os.path.join(os.path.relpath(old_folder), 'database',
                             'mia_before_commit.db'))

            # We commit the last pending modifications
            self.project.saveModifications()

            os.mkdir(properties_path)
            shutil.copy(
                os.path.join(os.path.relpath(old_folder),
                             'properties', 'properties.yml'),
                os.path.relpath(properties_path))

            # We copy the Database with all the modifications commited in
            # the new project
            os.mkdir(os.path.relpath(database_path))
            shutil.copy(
                os.path.join(os.path.relpath(old_folder), 'database',
                             'mia.db'), os.path.relpath(database_path))

            # We remove the Database with all the modifications saved in
            # the old project
            os.remove(
                os.path.join(os.path.relpath(old_folder), 'database',
                             'mia.db'))

            # We reput the Database without the last modifications
            # in the old project
            shutil.copy(
                os.path.join(os.path.relpath(old_folder), 'database',
                             'mia_before_commit.db'),
                os.path.join(os.path.relpath(old_folder), 'database',
                             'mia.db'))

            os.remove(
                os.path.join(os.path.relpath(old_folder), 'database',
                             'mia_before_commit.db'))

            self.remove_raw_files_useless()
            # We remove the useless files from the old project

            # Removing the old project from the list of
            # currently opened projects
            config = Config()
            opened_projects = config.get_opened_projects()
            opened_projects.remove(self.project.folder)
            config.set_opened_projects(opened_projects)

            # project updated everywhere
            self.project = Project(self.exPopup.relative_path, False)
            self.project.setName(os.path.basename(self.exPopup.relative_path))
            self.project.setDate(datetime.now().strftime('%d/%m/%Y %H:%M:%S'))
            self.project.saveModifications()

            self.update_project(file_name, call_update_table=False)
            # project updated everywhere

            # If some files have been set in the pipeline editors,
            # display a warning message
            if self.pipeline_manager.pipelineEditorTabs.has_pipeline_nodes():
                msg = QMessageBox()
                msg.setIcon(QMessageBox.Warning)
                msg.setText("This action moves the current database. "
                            "All pipelines will need to be initialized "
                            "again before they can run.")
                msg.setWindowTitle("Warning")
                msg.setStandardButtons(QMessageBox.Ok)
                msg.buttonClicked.connect(msg.close)
                msg.exec()

    def saveChoice(self):
        """Check if the project needs to be 'saved as' or just 'saved'."""
        if self.project.isTempProject:
            self.save_project_as()
        else:
            self.project.saveModifications()

    def see_all_projects(self):
        """Open a pop-up to show the recent projects."""
        # Ui_Dialog() is defined in pop_ups.py
        self.exPopup = PopUpSeeAllProjects(self.saved_projects, self)
        if self.exPopup.exec_():
            file_path = self.exPopup.relative_path
            if not self.test:
                self.saved_projects_list = self.saved_projects.addSavedProject(
                    file_path)
            self.update_recent_projects_actions()

    def software_preferences_pop_up(self):
        """Open the MIA2 preferences pop-up."""

        self.pop_up_preferences = PopUpPreferences(self)
        self.pop_up_preferences.setGeometry(300, 200, 800, 600)
        self.pop_up_preferences.show()
        self.pop_up_preferences.use_clinical_mode_signal.connect(
            self.add_clinical_tags)

        # Modifying the options in the Pipeline Manager
        # (verify if clinical mode)
        self.pop_up_preferences.signal_preferences_change.connect(
            self.pipeline_manager.update_clinical_mode)
        self.pop_up_preferences.signal_preferences_change.connect(
            self.update_package_library_action)

    def switch_project(self, file_path, name):
        """Check if it's possible to open the selected project
        and quit the current one.

        :param file_path: raw file_path
        :param name: project name

        :return: Boolean
        """
        # /!\ file_path and path are the same param

        # Switching project only if it's a different one
        if file_path != self.project.folder:

            # If the file exists
            if os.path.exists(os.path.join(file_path)):
                # If it is a MIA project
                if os.path.exists(os.path.join(
                        file_path, "properties", "properties.yml")) \
                    and os.path.exists(os.path.join(
                        file_path, "database", "mia.db")) \
                    and os.path.exists(os.path.join(
                        file_path, "data", "raw_data")) \
                    and os.path.exists(os.path.join(
                        file_path, "data", "derived_data")) \
                    and os.path.exists(os.path.join(
                        file_path, "data", "downloaded_data")) \
                    and os.path.exists(os.path.join(
                        file_path, "filters")):

                    # We check for invalid scans in the project

                    try:
                        temp_database = Project(file_path, False)
                    except IOError:
                        msg = QMessageBox()
                        msg.setIcon(QMessageBox.Warning)
                        msg.setText("project already opened")
                        msg.setInformativeText("The project at " +
                                               str(file_path) +
                                               " is already opened in another "
                                               "instance of the software.")
                        msg.setWindowTitle("Warning")
                        msg.setStandardButtons(QMessageBox.Ok)
                        msg.buttonClicked.connect(msg.close)
                        msg.exec()
                        return False
                    problem_list = data_loader.verify_scans(temp_database)

                    # Message if invalid files
                    if problem_list:
                        str_msg = ""
                        for element in problem_list:
                            str_msg += element + "\n\n"
                        msg = QMessageBox()
                        msg.setIcon(QMessageBox.Warning)
                        msg.setText(
                            "These files have been modified or removed since "
                            "they have been converted for the first time:")
                        msg.setInformativeText(str_msg)
                        msg.setWindowTitle("Warning")
                        msg.setStandardButtons(QMessageBox.Ok)
                        msg.buttonClicked.connect(msg.close)
                        msg.exec()

                    self.project.session.unsave_modifications()
                    self.remove_raw_files_useless()
                    # We remove the useless files from the old project

                    # project removed from the opened projects list
                    config = Config()
                    opened_projects = config.get_opened_projects()
                    opened_projects.remove(self.project.folder)
                    config.set_opened_projects(opened_projects)

                    self.project = temp_database  # New Database

                    self.update_project(file_path)
                    # project updated everywhere

                    return True

                # Not a MIA project
                else:
                    msg = QMessageBox()
                    msg.setIcon(QMessageBox.Warning)
                    msg.setText("The project selected isn't a "
                                "valid MIA project")
                    msg.setInformativeText("The project selected " + name +
                                           " isn't a MIA project"
                                           ".\nPlease select a "
                                           "valid one.")
                    msg.setWindowTitle("Warning")
                    msg.setStandardButtons(QMessageBox.Ok)
                    msg.buttonClicked.connect(msg.close)
                    msg.exec()
                    return False

            # The project doesn't exist anymore
            else:
                msg = QMessageBox()
                msg.setIcon(QMessageBox.Warning)
                msg.setText("The project selected doesn't exist anymore")
                msg.setInformativeText("The project selected " + name +
                                       " doesn't exist anymore."
                                       "\nPlease select "
                                       "another one.")
                msg.setWindowTitle("Warning")
                msg.setStandardButtons(QMessageBox.Ok)
                msg.buttonClicked.connect(msg.close)
                msg.exec()
                return False

    def tab_changed(self):
        """Update the window when the tab is changed"""

        if self.tabs.tabText(self.tabs.currentIndex()).replace(
                "&", "", 1) == 'Data Browser':
            # data_browser refreshed after working with pipelines
            old_scans = self.data_browser.table_data.scans_to_visualize
            documents = self.project.session.get_documents_names(
                COLLECTION_CURRENT)

            self.data_browser.table_data.add_columns()
            self.data_browser.table_data.fill_headers()

            self.data_browser.table_data.add_rows(documents)

            self.data_browser.table_data.scans_to_visualize = documents
            self.data_browser.table_data.scans_to_search = documents

            self.data_browser.table_data.itemChanged.disconnect()
            self.data_browser.table_data.fill_cells_update_table()
            self.data_browser.table_data.itemChanged.connect(
                self.data_browser.table_data.change_cell_color)

            self.data_browser.table_data.update_visualized_rows(old_scans)

            # Advanced search + search_bar opened
            old_search = self.project.currentFilter.search_bar
            self.data_browser.reset_search_bar()
            self.data_browser.search_bar.setText(old_search)

            if len(self.project.currentFilter.nots) > 0:
                self.data_browser.frame_advanced_search.setHidden(False)
                self.data_browser.advanced_search.scans_list = \
                    self.data_browser.table_data.scans_to_visualize
                self.data_browser.advanced_search.show_search()
                self.data_browser.advanced_search.apply_filter(
                    self.project.currentFilter)

        elif self.tabs.tabText(self.tabs.currentIndex()).replace(
                "&", "", 1) == 'Pipeline Manager':
            # Pipeline Manager
            # The pending modifications must be saved before
            # working with pipelines (auto_commit)
            if self.project.hasUnsavedModifications():
                msg = QMessageBox()
                msg.setIcon(QMessageBox.Warning)
                msg.setText("Unsaved modifications in the Data Browser !")
                msg.setInformativeText(
                    "There are unsaved modifications in the database, "
                    "you need to save or remove them before working "
                    "with pipelines.")
                msg.setWindowTitle("Warning")
                save_button = QPushButton("Save")
                save_button.clicked.connect(self.project.saveModifications)
                unsave_button = QPushButton("Not Save")
                unsave_button.clicked.connect(self.project.unsaveModifications)
                msg.addButton(save_button, QMessageBox.AcceptRole)
                msg.addButton(unsave_button, QMessageBox.AcceptRole)
                msg.exec()

    def undo(self):
        """Undo the last action made by the user."""
        if self.tabs.tabText(self.tabs.currentIndex()).replace(
                "&", "", 1) == 'Data Browser':
            # In Data Browser
            self.project.undo(self.data_browser.table_data)
            # Action reverted in the Database
        elif self.tabs.tabText(self.tabs.currentIndex()).replace(
                "&", "", 1) == 'Pipeline Manager':
            # In Pipeline Manager
            self.pipeline_manager.undo()

    def update_package_library_action(self):
        """Update the package library action depending on the mode."""
        if Config().get_clinical_mode() == True:
            self.action_package_library.setDisabled(True)
            # self.action_install_processes.setDisabled(True)
        else:
            self.action_package_library.setEnabled(True)
            # self.action_install_processes.setEnabled(True)

    def update_project(self, file_path, call_update_table=True):
        """Update the project once the database has been updated.
        Update the database, the window title and the recent and saved
        projects menus.

        :param file_path: File name of the new project
        :param call_update_table: boolean, True if we need to call
        """

        self.data_browser.update_database(self.project)
        # Database update data_browser
        self.pipeline_manager.update_project(self.project)

        if call_update_table:
            self.data_browser.table_data.update_table()  # Table updated

        # Window name updated
        if self.project.isTempProject:
            self.projectName = 'Unnamed project'
        else:
            self.projectName = self.project.getName()
        self.setWindowTitle(self.windowName + self.projectName)

        # List of project updated
        if not self.test:
            self.saved_projects_list = self.saved_projects.addSavedProject(
                file_path)
        self.update_recent_projects_actions()

    def update_recent_projects_actions(self):
        """Update the list of recent projects."""
        if self.saved_projects_list:
            for i in range(
                    min(len(self.saved_projects_list),
                        self.config.get_max_projects())):
                text = os.path.basename(self.saved_projects_list[i])
                self.saved_projects_actions[i].setText(text)
                self.saved_projects_actions[i].setData(
                    self.saved_projects_list[i])
                self.saved_projects_actions[i].setVisible(True)
Example #26
0
    def __init__(self, project, scan_list, main_window):
        """
        Initialization of the IterationTable widget.

        :param project: current project in the software
        :param scan_list: list of the selected database files
        :param main_window: software's main_window
        """

        QWidget.__init__(self)

        # Necessary for using MIA bricks
        ProcessMIA.project = project

        self.project = project

        if not scan_list:
            self.scan_list = self.project.session.get_documents_names(
                COLLECTION_CURRENT)
        else:
            self.scan_list = scan_list

        self.main_window = main_window
        self.iterated_tag = None

        # values_list will contain the different values of each selected tag
        self.values_list = [[], []]
        self.all_tag_values = []

        # Checkbox to choose to iterate the pipeline or not
        self.check_box_iterate = QCheckBox("Iterate pipeline")
        self.check_box_iterate.stateChanged.connect(
            self.emit_iteration_table_updated)

        # Label "Iterate over:"
        self.label_iterate = QLabel("Iterate over:")

        # Label that displays the name of the selected tag
        self.iterated_tag_label = QLabel("Select a tag")

        # Push button to select the tag to iterate
        self.iterated_tag_push_button = QPushButton("Select")
        self.iterated_tag_push_button.clicked.connect(
            self.select_iteration_tag)

        # QComboBox
        self.combo_box = QComboBox()
        self.combo_box.currentIndexChanged.connect(self.update_table)

        # filter
        self.filter_button = QPushButton('Filter')
        self.filter_button.clicked.connect(self.filter_values)

        # QTableWidget
        self.iteration_table = QTableWidget()

        # Label tag
        self.label_tags = QLabel("Tags to visualize:")

        # Each push button will allow the user to visualize a tag in
        # the iteration browser
        push_button_tag_1 = QPushButton()
        push_button_tag_1.setText("SequenceName")
        push_button_tag_1.clicked.connect(
            lambda: self.select_visualized_tag(0))

        push_button_tag_2 = QPushButton()
        push_button_tag_2.setText("AcquisitionDate")
        push_button_tag_2.clicked.connect(
            lambda: self.select_visualized_tag(1))

        # The list of all the push buttons
        # (the user can add as many as he or she wants)
        self.push_buttons = []
        self.push_buttons.insert(0, push_button_tag_1)
        self.push_buttons.insert(1, push_button_tag_2)

        # Labels to add/remove a tag (a push button)
        self.add_tag_label = ClickableLabel()
        self.add_tag_label.setObjectName('plus')
        sources_images_dir = Config().getSourceImageDir()
        add_tag_picture = QPixmap(
            os.path.relpath(os.path.join(sources_images_dir,
                                         "green_plus.png")))
        add_tag_picture = add_tag_picture.scaledToHeight(15)
        self.add_tag_label.setPixmap(add_tag_picture)
        self.add_tag_label.clicked.connect(self.add_tag)

        self.remove_tag_label = ClickableLabel()
        remove_tag_picture = QPixmap(
            os.path.relpath(os.path.join(sources_images_dir, "red_minus.png")))
        remove_tag_picture = remove_tag_picture.scaledToHeight(20)
        self.remove_tag_label.setPixmap(remove_tag_picture)
        self.remove_tag_label.clicked.connect(self.remove_tag)

        # Layout
        self.v_layout = QVBoxLayout()
        self.setLayout(self.v_layout)
        self.refresh_layout()
Example #27
0
    def __init__(self, project_root_folder, new_project):
        """Initialization of the project class.

        :param project_root_folder: project's path
        :param new_project: project's object
        """

        if project_root_folder is None:
            self.isTempProject = True
            self.folder = os.path.relpath(tempfile.mkdtemp())
        else:
            self.isTempProject = False
            self.folder = project_root_folder

        # Checks that the project is not already opened
        config = Config()
        opened_projects = config.get_opened_projects()
        if self.folder not in opened_projects:
            opened_projects.append(self.folder)
            config.set_opened_projects(opened_projects)
        else:
            raise IOError("The project at " + str(self.folder) +
                          " is already opened "
                          "in another instance "
                          "of the software.")

        self.database = DatabaseMIA(
            'sqlite:///' + os.path.join(self.folder, 'database', 'mia.db'))
        self.session = self.database.__enter__()

        if new_project:

            if not os.path.exists(self.folder):
                os.makedirs(self.folder)

            if not os.path.exists(os.path.join(self.folder, "database")):
                os.makedirs(os.path.join(self.folder, "database"))

            if not os.path.exists(os.path.join(self.folder, "filters")):
                os.makedirs(os.path.join(self.folder, "filters"))

            if not os.path.exists(os.path.join(self.folder, "data")):
                os.makedirs(os.path.join(self.folder, "data"))

            if not os.path.exists(os.path.join(self.folder, "data",
                                               "raw_data")):
                os.makedirs(os.path.join(self.folder, "data", "raw_data"))

            if not os.path.exists(
                    os.path.join(self.folder, "data", "derived_data")):
                os.makedirs(os.path.join(self.folder, "data", "derived_data"))

            if not os.path.exists(
                    os.path.join(self.folder, "data", "downloaded_data")):
                os.makedirs(
                    os.path.join(self.folder, "data", "downloaded_data"))

            # Properties file created
            os.mkdir(os.path.join(self.folder, 'properties'))
            if self.isTempProject:
                name = "Unnamed project"
            else:
                name = os.path.basename(self.folder)
            properties = dict(
                name=name,
                date=datetime.now().strftime('%d/%m/%Y %H:%M:%S'),
                sorted_tag=TAG_FILENAME,
                sort_order=0)
            with open(os.path.join(self.folder, 'properties',
                                   'properties.yml'), 'w', encoding='utf8') \
                    as propertyfile:
                yaml.dump(properties,
                          propertyfile,
                          default_flow_style=False,
                          allow_unicode=True)

            # Adding current and initial collections
            self.session.add_collection(COLLECTION_CURRENT, TAG_FILENAME, True,
                                        TAG_ORIGIN_BUILTIN, None, None)
            self.session.add_collection(COLLECTION_INITIAL, TAG_FILENAME, True,
                                        TAG_ORIGIN_BUILTIN, None, None)
            self.session.add_collection(COLLECTION_BRICK, BRICK_ID, False,
                                        TAG_ORIGIN_BUILTIN, None, None)

            # Tags manually added
            self.session.add_field(COLLECTION_CURRENT, TAG_CHECKSUM,
                                   FIELD_TYPE_STRING, "Path checksum", False,
                                   TAG_ORIGIN_BUILTIN, None, None)
            self.session.add_field(COLLECTION_INITIAL, TAG_CHECKSUM,
                                   FIELD_TYPE_STRING, "Path checksum", False,
                                   TAG_ORIGIN_BUILTIN, None, None)
            # TODO Maybe remove checksum tag from populse_mia.itial table
            self.session.add_field(COLLECTION_CURRENT, TAG_TYPE,
                                   FIELD_TYPE_STRING, "Path type", True,
                                   TAG_ORIGIN_BUILTIN, None, None)
            self.session.add_field(COLLECTION_INITIAL, TAG_TYPE,
                                   FIELD_TYPE_STRING, "Path type", True,
                                   TAG_ORIGIN_BUILTIN, None, None)
            self.session.add_field(COLLECTION_CURRENT, TAG_EXP_TYPE,
                                   FIELD_TYPE_STRING, "Path exp type", True,
                                   TAG_ORIGIN_BUILTIN, None, None)
            self.session.add_field(COLLECTION_INITIAL, TAG_EXP_TYPE,
                                   FIELD_TYPE_STRING, "Path exp type", True,
                                   TAG_ORIGIN_BUILTIN, None, None)
            self.session.add_field(COLLECTION_CURRENT, TAG_BRICKS,
                                   FIELD_TYPE_LIST_STRING, "Path bricks", True,
                                   TAG_ORIGIN_BUILTIN, None, None)
            self.session.add_field(COLLECTION_INITIAL, TAG_BRICKS,
                                   FIELD_TYPE_LIST_STRING, "Path bricks", True,
                                   TAG_ORIGIN_BUILTIN, None, None)

            self.session.add_field(COLLECTION_BRICK, BRICK_NAME,
                                   FIELD_TYPE_STRING, "Brick name", False,
                                   TAG_ORIGIN_BUILTIN, None, None)
            self.session.add_field(COLLECTION_BRICK, BRICK_INPUTS,
                                   FIELD_TYPE_JSON, "Brick input(s)", False,
                                   TAG_ORIGIN_BUILTIN, None, None)
            self.session.add_field(COLLECTION_BRICK, BRICK_OUTPUTS,
                                   FIELD_TYPE_JSON, "Brick output(s)", False,
                                   TAG_ORIGIN_BUILTIN, None, None)
            self.session.add_field(COLLECTION_BRICK, BRICK_INIT,
                                   FIELD_TYPE_STRING, "Brick init status",
                                   False, TAG_ORIGIN_BUILTIN, None, None)
            self.session.add_field(COLLECTION_BRICK, BRICK_INIT_TIME,
                                   FIELD_TYPE_DATETIME, "Brick init time",
                                   False, TAG_ORIGIN_BUILTIN, None, None)
            self.session.add_field(COLLECTION_BRICK, BRICK_EXEC,
                                   FIELD_TYPE_STRING, "Brick exec status",
                                   False, TAG_ORIGIN_BUILTIN, None, None)
            self.session.add_field(COLLECTION_BRICK, BRICK_EXEC_TIME,
                                   FIELD_TYPE_DATETIME, "Brick exec time",
                                   False, TAG_ORIGIN_BUILTIN, None, None)

            # Adding default tags for the clinical mode
            if config.get_clinical_mode() == True:
                for clinical_tag in CLINICAL_TAGS:
                    if clinical_tag == "Age":
                        field_type = FIELD_TYPE_INTEGER
                    else:
                        field_type = FIELD_TYPE_STRING
                    self.session.add_field(COLLECTION_CURRENT, clinical_tag,
                                           field_type, clinical_tag, True,
                                           TAG_ORIGIN_BUILTIN, None, None)
                    self.session.add_field(COLLECTION_INITIAL, clinical_tag,
                                           field_type, clinical_tag, True,
                                           TAG_ORIGIN_BUILTIN, None, None)

            self.session.save_modifications()
            # Base modifications, do not count for unsaved modifications

        self.properties = self.loadProperties()

        self._unsavedModifications = False
        self.undos = []
        self.redos = []
        self.init_filters()
    def __init__(self, project, scans_list, process, node_name, plug_name,
                 node_controller, main_window):
        """
        Initialization of the PlugFilter widget

        :param project: current project in the software
        :param scans_list: list of database files to filter
        :param process: process instance of the selected node
        :param node_name: name of the current node
        :param plug_name: name of the selected node plug
        :param node_controller: parent node controller
        :param main_window: parent main window
        """

        super(PlugFilter, self).__init__(None)

        from populse_mia.user_interface.data_browser.rapid_search import \
            RapidSearch
        from populse_mia.data_manager.project import \
            COLLECTION_CURRENT

        self.project = project
        self.node_controller = node_controller
        self.main_window = main_window
        self.process = process
        self.plug_name = plug_name

        # If the filter is saved in the node plug (not the case now)
        # if hasattr(self.process, 'filters'):
        #    if self.plug_name in self.process.filters.keys():
        #         print("Already a filter for {0} plug of {1} process".format(
        #                 self.plug_name, self.process.name))
        #         # TODO: fill the advanced search with the corresponding
        #          filter:orphan:

        # Verifying that the scan names begin not with a "/" or a "\"
        if scans_list:
            scans_list_copy = []
            for scan in scans_list:
                scan_no_pfolder = scan.replace(self.project.folder, "")
                if scan_no_pfolder[0] in ["\\", "/"]:
                    scan_no_pfolder = scan_no_pfolder[1:]
                scans_list_copy.append(scan_no_pfolder)

            self.scans_list = scans_list_copy

        # If there is no element in scans_list, this means that all the scans
        # of the database needs to be taken into account
        else:
            self.scans_list = self.project.session.get_documents_names(
                COLLECTION_CURRENT)

        self.setWindowTitle("Filter - " + node_name + " - " + plug_name)

        # Graphical components
        self.table_data = TableDataBrowser(self.project,
                                           self,
                                           self.node_controller.visibles_tags,
                                           False,
                                           True,
                                           link_viewer=False)

        # Reducing the list of scans to selection
        all_scans = self.table_data.scans_to_visualize
        self.table_data.scans_to_visualize = self.scans_list
        self.table_data.scans_to_search = self.scans_list
        self.table_data.update_visualized_rows(all_scans)

        search_bar_layout = QHBoxLayout()

        self.rapid_search = RapidSearch(self)
        self.rapid_search.textChanged.connect(partial(self.search_str))

        sources_images_dir = Config().getSourceImageDir()
        self.button_cross = QToolButton()
        self.button_cross.setStyleSheet('background-color:rgb(255, 255, 255);')
        self.button_cross.setIcon(
            QIcon(os.path.join(sources_images_dir, 'gray_cross.png')))
        self.button_cross.clicked.connect(self.reset_search_bar)

        search_bar_layout.addWidget(self.rapid_search)
        search_bar_layout.addWidget(self.button_cross)

        self.advanced_search = AdvancedSearch(
            self.project,
            self,
            self.scans_list,
            self.node_controller.visibles_tags,
            from_pipeline=True)
        self.advanced_search.show_search()

        push_button_tags = QPushButton("Visualized tags")
        push_button_tags.clicked.connect(self.update_tags)

        self.push_button_tag_filter = QPushButton(TAG_FILENAME)
        self.push_button_tag_filter.clicked.connect(self.update_tag_to_filter)

        push_button_ok = QPushButton("OK")
        push_button_ok.clicked.connect(self.ok_clicked)

        push_button_cancel = QPushButton("Cancel")
        push_button_cancel.clicked.connect(self.close)

        # Layout
        buttons_layout = QHBoxLayout()
        buttons_layout.addWidget(push_button_tags)
        buttons_layout.addWidget(self.push_button_tag_filter)
        buttons_layout.addStretch(1)
        buttons_layout.addWidget(push_button_ok)
        buttons_layout.addWidget(push_button_cancel)

        main_layout = QVBoxLayout()
        main_layout.addLayout(search_bar_layout)
        main_layout.addWidget(self.advanced_search)
        main_layout.addWidget(self.table_data)
        main_layout.addLayout(buttons_layout)

        self.setLayout(main_layout)

        screen_resolution = QApplication.instance().desktop().screenGeometry()
        width, height = screen_resolution.width(), screen_resolution.height()
        self.setMinimumWidth(0.6 * width)
        self.setMinimumHeight(0.8 * height)
Example #29
0
    def __init__(self, project, test=False, deleted_projects=None):
        """Main window class, initializes the software appearance and defines
        interactions with the user.

            :Parameter:
                - :project: current project in the software
                - :test: boolean if the widget is launched from unit tests
                     or not
                - :deleted_projects: projects that have been deleted

        """
        super(MainWindow, self).__init__()

        QApplication.restoreOverrideCursor()

        # We associate these methods and the instance to be able to call them
        # from anywhere.
        QCoreApplication.instance().title = self.windowTitle
        QCoreApplication.instance().set_title = self.setWindowTitle

        if deleted_projects is not None and deleted_projects:
            self.msg = PopUpDeletedProject(deleted_projects)

        self.config = Config()
        self.config.setSourceImageDir(
            os.path.join(
                os.path.dirname(os.path.dirname(os.path.realpath(__file__))),
                "sources_images"))
        self.windowName = "MIA - Multiparametric Image Analysis"
        self.projectName = "Unnamed project"
        self.project = project
        self.test = test

        self.saved_projects = SavedProjects()
        self.saved_projects_list = self.saved_projects.pathsList

        self.saved_projects_actions = []

        # Define main window view
        self.create_view_window()

        # Initialize menu
        self.menu_file = self.menuBar().addMenu('File')
        self.menu_edition = self.menuBar().addMenu('Edit')
        self.menu_help = self.menuBar().addMenu('Help')
        self.menu_about = self.menuBar().addMenu('About')
        self.menu_more = self.menuBar().addMenu('More')
        self.menu_install_process = QMenu('Install processes', self)
        self.menu_saved_projects = QMenu('Saved projects', self)

        # Initialize tabs
        self.tabs = QTabWidget()
        self.data_browser = DataBrowser(self.project, self)
        self.image_viewer = QLabel("Coming soon...")
        self.pipeline_manager = PipelineManagerTab(self.project, [], self)
        self.centralWindow = QWidget()

        # Initialize menu actions
        sources_images_dir = Config().getSourceImageDir()
        self.action_save_project = self.menu_file.addAction("Save project")
        self.action_save_project_as = self.menu_file.addAction("Save "
                                                               "project as")
        self.action_create = QAction('New project', self)
        self.action_open = QAction('Open project', self)
        self.action_save = QAction('Save', self)
        self.action_save_as = QAction('Save as', self)
        self.action_import = QAction(
            QIcon(os.path.join(sources_images_dir, 'Blue.png')), 'Import',
            self)
        self.action_see_all_projects = QAction('See all projects', self)
        self.action_project_properties = QAction('Project properties', self)
        self.action_software_preferences = QAction('MIA preferences', self)
        self.action_package_library = QAction('Package library manager', self)
        self.action_exit = QAction(
            QIcon(os.path.join(sources_images_dir, 'exit.png')), 'Exit', self)
        self.action_undo = QAction('Undo', self)
        self.action_redo = QAction('Redo', self)
        self.action_documentation = QAction('Documentation', self)
        self.action_install_processes_folder = QAction('From folder', self)
        self.action_install_processes_zip = QAction('From zip file', self)

        # Connect actions & menus views
        self.create_view_actions()
        self.create_view_menus()

        # Create Tabs
        self.create_tabs()
        self.setCentralWidget(self.centralWindow)
        self.showMaximized()
Example #30
0
class MiniViewer(QWidget):
    """MiniViewer that allows to rapidly visualize scans either with a single
    image per scan with cursors to move in five dimensions or with all images
    of the greater dimension of the scan.

    When the latter is selected, the displayed images depends on their
    dimension:

        - 3D: display all the slices.
        - 4D: display the middle slice of the third dimension for each time
           of the fourth dimension.
        - 5D: display the middle slice of the third dimension for the first
           time of the fourth dimension for each time of the fifth dimension.

    Note:
        - idx corresponds to the index of the displayed image
        - idx in [0, self.max_scans]
        - most of the class's attributes are lists of 0 to self.max_scans
           elements

    .. Methods:
        - __init__: initialise the MiniViewer object
        - boxSlider: create sliders, their connections and thumbnail labels
          for a selected index
        - changePosValue: change the value of a cursor for the selected index
        - check_box_cursors_state_changed: updates the config file
        - clearLayouts: clear the final layout
        - create_slider: create a slider
        - createDimensionLabels: create the dimension labels for the
          selected index
        - createFieldValue: create a field where will be displayed the
          position of a cursor
        - createLayouts: create the layouts
        - displayPosValue: display the position of each cursor for the
          selected index
        - enableSliders: enable each slider of the selected index
        - image_to_pixmap: create a 2D pixmap from a N-D Nifti image
        - image2DModifications: apply modifications to the image to
          display it correctly
        - indexImage: update the sliders values depending on the size of
          the selected image
        - navigImage: display the 2D image for the selected index
        - openTagsPopUp: opens a pop-up to select the legend of the thumbnails
        - setThumbnail: set the thumbnail tag value under the image frame
        - show_slices: create the thumbnails from the selected file paths
        - update_nb_slices: update the config file and the thumbnails
        - update_visualization_method: update the config file and the
          thumbnails
        - verify_slices: verify the number of selected documents

    """
    def __init__(self, project):
        """Initialise the MiniViewer object

        :param project: current project in the software

        """
        super().__init__()

        self.project = project
        self.first_time = True

        # The MiniViewer is set hidden to give more space to the data_browser
        self.setHidden(True)

        # When multiple selection, limiting the number of thumbnails to
        # max_scans
        self.max_scans = 4

        # Config that allows to read the software preferences
        self.config = Config()

        # Initializing some components of the MiniViewer
        self.labels = QWidget()
        self.frame = QFrame()
        self.scroll_area = QScrollArea()
        self.scroll_area.setWidget(self.frame)
        self.frame_final = QFrame()

        self.label_nb_slices = QLabel()
        self.label_nb_slices.setText("Maximum number of slices: ")

        self.line_edit_nb_slices = QLineEdit()
        self.line_edit_nb_slices.setText(str(self.config.getNbAllSlicesMax()))
        self.line_edit_nb_slices.returnPressed.connect(self.update_nb_slices)

        # All these objects are depending on the number of scans to visualize
        self.im_2D = []
        self.slider_3D = []
        self.slider_4D = []
        self.slider_5D = []
        self.txt_slider_3D = []
        self.txt_slider_4D = []
        self.txt_slider_5D = []
        self.label3D = []
        self.label4D = []
        self.label5D = []
        self.imageLabel = []
        self.img = []
        self.label_description = []

        # Layouts
        self.createLayouts()
        self.setLayout(self.v_box_final)

        # Checkboxes
        self.check_box_slices = QCheckBox('Show all slices (no cursors)')
        if self.config.getShowAllSlices() == True:
            self.check_box_slices.setCheckState(Qt.Checked)
        else:
            self.check_box_slices.setCheckState(Qt.Unchecked)
        self.check_box_slices.stateChanged.connect(
            self.update_visualization_method)

        self.check_box_cursors = QCheckBox('Chain cursors')
        self.check_box_cursors.setToolTip("Allows to connect all cursors "
                                          "when selecting multiple documents")
        if self.config.getChainCursors() == True:
            self.check_box_cursors.setCheckState(Qt.Checked)
        else:
            self.check_box_cursors.setCheckState(Qt.Unchecked)
        self.check_box_cursors.stateChanged.connect(
            self.check_box_cursors_state_changed)

        self.file_paths = ""

    def boxSlider(self, idx):
        """Define horizontal sliders connections and thumbnail labels.

        :param idx: the selected index
        """
        self.slider_3D.insert(idx, self.create_slider(0, 0, 0))
        self.slider_4D.insert(idx, self.create_slider(0, 0, 0))
        self.slider_5D.insert(idx, self.create_slider(0, 0, 0))

        self.slider_3D[idx].valueChanged.connect(
            partial(self.changePosValue, idx, 1))
        self.slider_4D[idx].valueChanged.connect(
            partial(self.changePosValue, idx, 2))
        self.slider_5D[idx].valueChanged.connect(
            partial(self.changePosValue, idx, 3))

        self.txt_slider_3D.insert(idx, self.createFieldValue())
        self.txt_slider_4D.insert(idx, self.createFieldValue())
        self.txt_slider_5D.insert(idx, self.createFieldValue())

    def check_box_cursors_state_changed(self):
        """Update the config file.

        Called when the state of the checkbox to chain the cursors changes.
        """
        if self.check_box_cursors.checkState() == Qt.Checked:
            self.config.setChainCursors(True)
        elif self.check_box_cursors.checkState() == Qt.Unchecked:
            self.config.setChainCursors(False)

    def clearLayouts(self):
        """Clear the final layout"""

        for i in reversed(range(self.v_box_final.count())):
            if self.v_box_final.itemAt(i).widget() is not None:
                self.v_box_final.itemAt(i).widget().setParent(None)

    def changePosValue(self, idx, cursor_to_change):
        """
        Change the value of a cursor for the selected index.

        :param idx: the selected index
        :param cursor_to_change: the cursor to change (1, 2 or 3)
        """
        # If the "Chain cursors" mode is not selected, there is nothing to do
        if self.check_box_cursors.checkState() == Qt.Unchecked:
            self.navigImage(idx)
        else:
            # Checking with cursor has been modified
            if cursor_to_change == 1:
                cursor = self.slider_3D
            elif cursor_to_change == 2:
                cursor = self.slider_4D
            else:
                cursor = self.slider_5D

            # Loop on the thumbnails
            for idx_loop in range(min(self.max_scans, len(self.file_paths))):
                # Disconnecting the connection when changing other cursors
                # values
                cursor[idx_loop].valueChanged.disconnect()
                # Do something only when the cursor is not the one that has
                # been changed by the user
                if idx_loop != idx:
                    if cursor[idx].value() == cursor[idx].maximum():
                        value = cursor[idx_loop].maximum()

                    elif cursor[idx].value() == cursor[idx].minimum():
                        value = cursor[idx_loop].minimum()

                    else:
                        # Updating the new value as the value of the cursor
                        # that has been changed by the user
                        value = round((cursor[idx_loop].maximum() + 1) *
                                      (cursor[idx].value() + 1) /
                                      max(1, cursor[idx].maximum() + 1))
                        value = min(cursor[idx_loop].maximum(), value - 1)
                        value = max(0, int(value))
                    cursor[idx_loop].setValue(value)

                # Changing the image to show
                self.navigImage(idx_loop)
                # Reconnecting
                cursor[idx_loop].valueChanged.connect(
                    partial(self.changePosValue, idx_loop, cursor_to_change))

    def createDimensionLabels(self, idx):
        """Create the dimension labels for the selected index.

        :param idx: the selected index
        """
        font = QFont()
        font.setPointSize(9)

        self.label3D.insert(idx, QLabel())
        self.label4D.insert(idx, QLabel())
        self.label5D.insert(idx, QLabel())

        self.label3D[idx].setFont(font)
        self.label4D[idx].setFont(font)
        self.label5D[idx].setFont(font)

        self.label3D[idx].setText('3D: ')
        self.label4D[idx].setText('4D: ')
        self.label5D[idx].setText('5D: ')

    def createFieldValue(self):
        """Create a field where will be displayed the position of a cursor.

        :return: the corresponding field
        """
        fieldValue = QLineEdit()
        fieldValue.setEnabled(False)
        fieldValue.setFixedWidth(50)
        fieldValue.setAlignment(Qt.AlignCenter)
        fieldValue.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
        font = QFont()
        font.setPointSize(9)
        fieldValue.setFont(font)
        return fieldValue

    def createLayouts(self):
        """Create the layouts."""

        self.h_box_images = QHBoxLayout()
        self.h_box_images.setSpacing(10)
        self.v_box = QVBoxLayout()
        self.v_box_final = QVBoxLayout()
        self.h_box_slider_3D = QHBoxLayout()
        self.h_box_slider_4D = QHBoxLayout()
        self.h_box_slider_5D = QHBoxLayout()
        self.v_box_sliders = QVBoxLayout()
        self.h_box = QHBoxLayout()
        self.h_box_check_box = QHBoxLayout()
        self.v_box_thumb = QVBoxLayout()
        self.h_box_thumb = QHBoxLayout()

    def create_slider(self, maxm=0, minm=0, pos=0):
        """Generate an horizontal slider.

        :param maxm: slider's maximum
        :param minm: slider's minimum
        :param pos: slider's initial value
        :return: the slider object
        """
        slider = QSlider(Qt.Horizontal)
        slider.setFocusPolicy(Qt.StrongFocus)
        slider.setTickInterval(1)
        slider.setMaximum(maxm)
        slider.setMinimum(minm)
        slider.setValue(pos)
        slider.setEnabled(False)
        return slider

    def displayPosValue(self, idx):
        """Display the position of each cursor for the selected index.

        :param idx: the selected index
        """
        self.txt_slider_3D[idx].setText(
            str(self.slider_3D[idx].value() + 1) + ' / ' +
            str(self.slider_3D[idx].maximum() + 1))
        self.txt_slider_4D[idx].setText(
            str(self.slider_4D[idx].value() + 1) + ' / ' +
            str(self.slider_4D[idx].maximum() + 1))
        self.txt_slider_5D[idx].setText(
            str(self.slider_5D[idx].value() + 1) + ' / ' +
            str(self.slider_5D[idx].maximum() + 1))

    def enableSliders(self, idx):
        """Enable all the horizontal slider.

        :param idx: the slider's index
        """
        self.slider_3D[idx].setEnabled(True)
        self.slider_4D[idx].setEnabled(True)
        self.slider_5D[idx].setEnabled(True)

    def image_to_pixmap(self, im, i):
        """Create a 2D pixmap from a N-D Nifti image.

        :param im: Nifti image
        :param i: index of the slide
        :return: the corresponding pixmap
        """

        # The image to display depends on the dimension of the image
        # In the 3D case, each slice is displayed
        if len(im.shape) == 3:
            im_2D = im.get_data()[:, :, i].copy()

        # In the 4D case, each middle slice of the 3D dimension is displayed
        # for each time in the 4D dimension
        elif len(im.shape) == 4:
            im_3D = im.get_data()[:, :, :, i].copy()
            middle_slice = int(im_3D.shape[2] / 2)
            im_2D = im_3D[:, :, middle_slice]

        # In the 5D case, each first time of the 4D dimension and
        # its middle slice of the 3D dimension is displayed
        elif len(im.shape) == 5:
            im_4D = im.get_data()[:, :, :, :, i].copy()
            im_3D = im_4D[:, :, :, 1]
            middle_slice = int(im_3D.shape[2] / 2)
            im_2D = im_3D[:, :, middle_slice]

        else:
            im_2D = [0]

        # Making some pixel modification to display the image correctly
        im_2D = self.image2DModifications(0, im_2D)

        w, h = im_2D.shape

        im_Qt = QImage(im_2D.data, w, h, QImage.Format_Indexed8)
        pixm = QPixmap.fromImage(im_Qt)

        return pixm

    def image2DModifications(self, idx, im2D=None):
        """Apply modifications to display the image correctly.

        :param idx: the selected index
        :param im2D: image to modify
        """

        display_size = (128, 128)
        display_type = np.uint8  # this MUST be an integer data type
        display_pctl = 0.5  # percentile (0.5%) of values to clip at the low and high end of intensities.
        display_max = np.iinfo(display_type).max
        display_min = np.iinfo(display_type).min

        im2d_provided = im2D is not None
        if not im2d_provided:
            im2D = self.im_2D[idx]

        # Resize image first, for three reasons:
        #  1 - it may slightly changes the intensity scale, so re-scaling should be done after this
        #  2 - rescaling before rotation is slightly faster, specially for large images (> display_size).
        #  3 - rescaling may alter the occurrence of nan or infinite values (e.g. an image may become all-nan)
        # anti_aliasing keyword is defined in skimage since version 0.14.0
        if verCmp(sk.__version__, '0.14.0', 'sup'):
            im2D = resize(im2D,
                          display_size,
                          mode='constant',
                          anti_aliasing=False)
        else:
            im2D = resize(im2D, display_size, mode='constant')

        # Rescale image while handling Nans and infinite values
        im_mask = np.isfinite(im2D)
        if np.any(im_mask):  # if we have any finite value to work with
            im2D -= np.percentile(
                im2D[im_mask],
                display_pctl)  # shift the lower percentile chosen to 0.0
            im_max = np.percentile(
                im2D[im_mask],
                100.0 - display_pctl)  # determine max from upper percentile
            if im_max > 0:  # avoid dividing by zero
                im2D *= (display_max -
                         display_min) / im_max  # re-scale to display range
            im2D += display_min  # shift lowest value to lower limit of display range

        np.clip(
            im2D, display_min, display_max,
            im2D)  # clip all values to display range, remove infinite values
        im2D = im2D.astype(
            display_type
        )  # convert to integer display data type. NaNs get converted to 0.

        im2D = np.rot90(im2D, 3).copy(
        )  # Rotate. Copy array to avoid negative strides (Qt doesn't handle that)

        if im2d_provided:
            return im2D
        else:
            self.im_2D[idx] = im2D

    def indexImage(self, idx):
        """Update all slider values according to the size of the current image.

        :param idx: the selected index
        """
        # Getting the sliders value
        sl3D = self.slider_3D[idx].value()
        sl4D = self.slider_4D[idx].value()
        sl5D = self.slider_5D[idx].value()

        # Depending on the dimension, reading the image data and
        # changing the cursors maximum
        if len(self.img[idx].shape) == 3:
            self.im_2D.insert(idx, self.img[idx].get_data()[:, :, sl3D].copy())
            self.slider_3D[idx].setMaximum(self.img[idx].shape[2] - 1)
            self.slider_4D[idx].setMaximum(0)
            self.slider_5D[idx].setMaximum(0)
        if len(self.img[idx].shape) == 4:
            self.im_2D.insert(
                idx, self.img[idx].get_data()[:, :, sl3D, sl4D].copy())
            self.slider_3D[idx].setMaximum(self.img[idx].shape[2] - 1)
            self.slider_4D[idx].setMaximum(self.img[idx].shape[3] - 1)
            self.slider_5D[idx].setMaximum(0)
        if len(self.img[idx].shape) == 5:
            self.im_2D.insert(
                idx, self.img[idx].get_data()[:, :, sl3D, sl4D, sl5D].copy())
            self.slider_3D[idx].setMaximum(self.img[idx].shape[2] - 1)
            self.slider_4D[idx].setMaximum(self.img[idx].shape[3] - 1)
            self.slider_5D[idx].setMaximum(self.img[idx].shape[4] - 1)

    def navigImage(self, idx):
        """Display the 2D image for the selected index.

        :param idx: the selected index
        """
        self.indexImage(idx)
        self.displayPosValue(idx)

        self.image2DModifications(idx)
        w, h = self.im_2D[idx].shape

        image = QImage(self.im_2D[idx].data, w, h, QImage.Format_Indexed8)
        pixm = QPixmap.fromImage(image)
        self.imageLabel[idx].setPixmap(pixm)

    def openTagsPopUp(self):
        """Open a pop-up to select the legend of the thumbnails."""

        self.popUp = PopUpSelectTag(self.project)
        self.popUp.setWindowTitle("Select the image viewer tag")
        if self.popUp.exec_():
            self.verify_slices(self.file_paths)

    def setThumbnail(self, file_path_base_name, idx):
        """Set the thumbnail tag value under the image frame.

        :param file_path_base_name: basename of the selected path
        :param idx: index of the image
        """
        # Looking for the tag value to display as a legend of the thumbnail
        for scan in self.project.session.get_documents_names(
                COLLECTION_CURRENT):
            if scan == file_path_base_name:
                value = self.project.session.get_value(
                    COLLECTION_CURRENT, scan, self.config.getThumbnailTag())
                if value is not None:
                    self.label_description[idx].setText(
                        str(value)[:self.nb_char_max])
                else:
                    self.label_description[idx].setText(
                        data_browser.not_defined_value[:self.nb_char_max])
                self.label_description[idx].setToolTip(
                    os.path.basename(self.config.getThumbnailTag()))

    def show_slices(self, file_paths):
        """Creates the thumbnails from the selected file paths.

        :param file_paths: the selected file paths
        """

        # If it's the first time that this function is called, the MiniViewer
        # has to be shown
        if self.first_time:
            self.setHidden(False)
            self.first_time = False

        # If the user has willingly hidden the MiniViewer, the Processes are
        # not made
        if self.isHidden():
            pass
        else:
            self.do_nothing = [False] * len(file_paths)

            self.file_paths = file_paths
            self.max_scans = len(file_paths)

            self.setMinimumHeight(220)

            self.clearLayouts()

            self.frame = QFrame(self)
            self.frame_final = QFrame(self)

            # Limiting the legend of the thumbnails
            self.nb_char_max = 60

            font = QFont()
            font.setPointSize(9)

            # Reading the images from the file paths
            for idx, file_path in enumerate(self.file_paths.copy()):
                try:
                    self.img.insert(idx, nib.load(file_path))
                except nib.filebasedimages.ImageFileError:
                    print("Error while trying to display the image " +
                          file_path)
                    self.file_paths.remove(file_path)
                except FileNotFoundError:
                    print("File " + file_path + " not existing")
                    self.file_paths.remove(file_path)

            # If we are in the "cursors" display mode
            if self.check_box_slices.checkState() == Qt.Unchecked:

                # Layout to aligne each thumbnail (image + cursors)
                self.h_box_thumb = QHBoxLayout()

                # idx represents the index of the selected image
                for idx in range(min(self.max_scans, len(self.file_paths))):
                    if not self.do_nothing[idx]:

                        # Creating sliders and labels
                        self.boxSlider(idx)
                        self.enableSliders(idx)
                        self.createDimensionLabels(idx)

                        # Getting the sliders value and reading the image data
                        self.indexImage(idx)

                        # Making some pixel modification to display the image
                        # correctly
                        self.image2DModifications(idx)

                    self.displayPosValue(idx)

                    w, h = self.im_2D[idx].shape

                    im_Qt = QImage(self.im_2D[idx].data, w, h,
                                   QImage.Format_Indexed8)
                    pixm = QPixmap.fromImage(im_Qt)

                    file_path_base_name = os.path.basename(
                        self.file_paths[idx])

                    # imageLabel is the label where the image is displayed
                    # (as a pixmap)
                    self.imageLabel.insert(idx, QLabel(self))
                    self.imageLabel[idx].setPixmap(pixm)
                    self.imageLabel[idx].setToolTip(file_path_base_name)

                    self.label_description.insert(idx, ClickableLabel())
                    self.label_description[idx].setFont(font)
                    self.label_description[idx].clicked.connect(
                        self.openTagsPopUp)

                    # Looking for the tag value to display as a
                    # legend of the thumbnail
                    file_path_base_name = os.path.relpath(
                        self.file_paths[idx], self.project.folder)
                    self.setThumbnail(file_path_base_name, idx)

                    # Layout that corresponds to the 3D dimension
                    self.h_box_slider_3D = QHBoxLayout()
                    self.h_box_slider_3D.addWidget(self.label3D[idx])
                    self.h_box_slider_3D.addWidget(self.txt_slider_3D[idx])
                    self.h_box_slider_3D.addWidget(self.slider_3D[idx])

                    # Layout that corresponds to the 4D dimension
                    self.h_box_slider_4D = QHBoxLayout()
                    self.h_box_slider_4D.addWidget(self.label4D[idx])
                    self.h_box_slider_4D.addWidget(self.txt_slider_4D[idx])
                    self.h_box_slider_4D.addWidget(self.slider_4D[idx])

                    # Layout that corresponds to the 5D dimension
                    self.h_box_slider_5D = QHBoxLayout()
                    self.h_box_slider_5D.addWidget(self.label5D[idx])
                    self.h_box_slider_5D.addWidget(self.txt_slider_5D[idx])
                    self.h_box_slider_5D.addWidget(self.slider_5D[idx])

                    # Layout for the three sliders
                    self.v_box_sliders = QVBoxLayout()
                    self.v_box_sliders.addLayout(self.h_box_slider_3D)
                    self.v_box_sliders.addLayout(self.h_box_slider_4D)
                    self.v_box_sliders.addLayout(self.h_box_slider_5D)

                    # Layout that corresponds to the image + the sliders
                    self.h_box = QHBoxLayout()
                    self.h_box.addWidget(self.imageLabel[idx])
                    self.h_box.addLayout(self.v_box_sliders)

                    # Layout that corresponds to the image and sliders +
                    # the description
                    self.v_box_thumb = QVBoxLayout()
                    self.v_box_thumb.addLayout(self.h_box)
                    self.v_box_thumb.addWidget(self.label_description[idx])

                    # Layout that will contain all the thumbnails
                    self.h_box_thumb.addLayout(self.v_box_thumb)

                self.frame.setLayout(self.h_box_thumb)

            # If we are in the "all slices" display mode
            else:

                self.h_box_images = QHBoxLayout()
                self.h_box_images.setSpacing(10)
                self.v_box_scans = QVBoxLayout()

                # idx represents the index of the selected image
                for idx in range(len(self.file_paths)):
                    file_path_base_name = os.path.relpath(
                        self.file_paths[idx], self.project.folder)

                    self.label_description.insert(idx, ClickableLabel())
                    self.label_description[idx].setFont(font)
                    self.label_description[idx].clicked.connect(
                        self.openTagsPopUp)

                    # Looking for the tag value to display as a legend
                    # of the thumbnail
                    self.setThumbnail(file_path_base_name, idx)

                    # Depending of the dimension of the image,
                    # the legend of each image and the number of images to
                    # display will change
                    if not self.do_nothing[idx]:
                        if len(self.img[idx].shape) == 3:
                            nb_slices = self.img[idx].shape[2]
                            txt = "Slice n°"
                        elif len(self.img[idx].shape) == 4:
                            nb_slices = self.img[idx].shape[3]
                            txt = "Time n°"
                        elif len(self.img[idx].shape) == 5:
                            nb_slices = self.img[idx].shape[4]
                            txt = "Study n°"
                        else:
                            nb_slices = 0

                        # Limiting the number of images to the number
                        # chosen by the user
                        for i in range(
                                min(nb_slices,
                                    int(self.line_edit_nb_slices.text()))):
                            pixm = self.image_to_pixmap(self.img[idx], i)

                            self.v_box = QVBoxLayout()

                            # label corresponds to the label where one image
                            # is displayed
                            label = QLabel(self)
                            label.setPixmap(pixm)
                            label.setToolTip(
                                os.path.basename(self.file_paths[idx]))

                            # Legend of the image (depends on the number
                            # of dimensions)
                            label_info = QLabel()
                            label_info.setFont(font)
                            label_info.setText(txt + str(i + 1))
                            label_info.setAlignment(QtCore.Qt.AlignCenter)

                            self.v_box.addWidget(label)
                            self.v_box.addWidget(label_info)

                            # This layout allows to chain each image
                            self.h_box_images.addLayout(self.v_box)
                        self.v_box_scans.addLayout(self.h_box_images)
                        self.v_box_scans.addWidget(self.label_description[idx])
                self.frame.setLayout(self.v_box_scans)

            # Adding a scroll area if the thumbnails are too large
            self.scroll_area = QScrollArea()
            self.scroll_area.setWidget(self.frame)

            self.h_box_check_box = QHBoxLayout()

            if self.check_box_slices.isChecked():
                self.h_box_check_box.addStretch(1)
                self.label_nb_slices.setHidden(False)
                self.line_edit_nb_slices.setHidden(False)
                self.h_box_check_box.addWidget(self.label_nb_slices)
                self.h_box_check_box.addWidget(self.line_edit_nb_slices)
                self.check_box_cursors.setHidden(True)
            else:
                self.check_box_cursors.setHidden(False)
                self.h_box_check_box.addWidget(self.check_box_cursors)
                self.h_box_check_box.addStretch(1)
                self.label_nb_slices.setHidden(True)
                self.line_edit_nb_slices.setHidden(True)

            self.h_box_check_box.addWidget(self.check_box_slices)

            self.v_box_final.addLayout(self.h_box_check_box)
            self.v_box_final.addWidget(self.scroll_area)

    def update_nb_slices(self):
        """Update the config file and the thumbnails.

        Called when the number of slices to visualize changes.
        """
        nb_slices = self.line_edit_nb_slices.text()
        self.config.setNbAllSlicesMax(nb_slices)
        self.verify_slices(self.file_paths)

    def update_visualization_method(self):
        """Update the config file and the thumbnails.

        Called when the state of the checkbox to show all slices changes.
        """
        if self.check_box_slices.checkState() == Qt.Checked:
            self.config.setShowAllSlices(True)
        elif self.check_box_slices.checkState() == Qt.Unchecked:
            self.config.setShowAllSlices(False)
        self.verify_slices(self.file_paths)

    def verify_slices(self, file_paths):
        """Make 'Show all slices' checkbox unclickable if len(file_paths) > 1.

        :param file_paths: the selected documents
        """
        # Updating the config
        self.config = Config()
        if len(file_paths) > 1:
            self.config.setShowAllSlices(False)
            self.check_box_slices.setCheckState(Qt.Unchecked)
            self.check_box_slices.setCheckable(False)
        else:
            self.check_box_slices.setCheckable(True)
        self.show_slices(file_paths)