示例#1
0
class GUICropBrainStem(QWidget):
    def __init__(self, stack, parent=None):
        super(GUICropBrainStem, self).__init__(parent)

        self.stack = stack
        self.fileLocationManager = FileLocationManager(self.stack)
        self.sqlController = SqlController()
        self.sqlController.get_animal_info(self.stack)

        self.valid_sections = self.sqlController.get_valid_sections(stack)
        self.valid_section_keys = sorted(list(self.valid_sections))

        self.curr_section_index = 0
        self.curr_section = None

        self.active_selection = ''
        self.img_width = 2001
        self.img_height = 1001

        self.rostral = -1
        self.caudal = -1
        self.ventral = -1
        self.dorsal = -1
        self.first_slice = -1
        self.last_slice = -1

        self.init_ui()

        self.b_rostral.clicked.connect(
            lambda: self.click_button(self.b_rostral))
        self.b_caudal.clicked.connect(lambda: self.click_button(self.b_caudal))
        self.b_dorsal.clicked.connect(lambda: self.click_button(self.b_dorsal))
        self.b_ventral.clicked.connect(
            lambda: self.click_button(self.b_ventral))
        self.b_first_slice.clicked.connect(
            lambda: self.click_button(self.b_first_slice))
        self.b_last_slice.clicked.connect(
            lambda: self.click_button(self.b_last_slice))
        self.b_done.clicked.connect(lambda: self.click_button(self.b_done))
        self.viewer.click.connect(self.click_photo)

    def init_ui(self):
        self.font_h1 = QFont("Arial", 32)
        self.font_p1 = QFont("Arial", 16)

        self.grid_top = QGridLayout()
        self.grid_body_upper = QGridLayout()
        self.grid_body = QGridLayout()
        self.grid_body_lower = QGridLayout()

        self.resize(1600, 1100)

        # Grid Top
        self.e_title = QLineEdit()
        self.e_title.setAlignment(Qt.AlignCenter)
        self.e_title.setFont(self.font_h1)
        self.e_title.setReadOnly(True)
        self.e_title.setText("Setup Sorted Filenames")
        self.e_title.setFrame(False)
        self.grid_top.addWidget(self.e_title, 0, 0)

        self.b_help = QPushButton("HELP")
        self.b_help.setDefault(True)
        self.b_help.setEnabled(True)
        self.grid_top.addWidget(self.b_help, 0, 1)

        # Grid BODY UPPER
        self.e_filename = QLineEdit()
        self.e_filename.setAlignment(Qt.AlignCenter)
        self.e_filename.setFont(self.font_p1)
        self.e_filename.setReadOnly(True)
        self.e_filename.setText("Filename: ")
        self.grid_body_upper.addWidget(self.e_filename, 0, 2)

        self.e_section = QLineEdit()
        self.e_section.setAlignment(Qt.AlignCenter)
        self.e_section.setFont(self.font_p1)
        self.e_section.setReadOnly(True)
        self.e_section.setText("Section: ")
        self.grid_body_upper.addWidget(self.e_section, 0, 3)

        # Grid BODY
        self.viewer = ImageViewer(self)
        self.grid_body.addWidget(self.viewer, 0, 0)

        # Grid BODY LOWER
        self.b_rostral = QPushButton("Rostral Limit:")
        self.grid_body_lower.addWidget(self.b_rostral, 0, 0)

        self.e_rostral = QLineEdit()
        self.e_rostral.setAlignment(Qt.AlignCenter)
        self.e_rostral.setFont(self.font_p1)
        self.grid_body_lower.addWidget(self.e_rostral, 0, 1)

        self.b_caudal = QPushButton("Caudal Limit:")
        self.grid_body_lower.addWidget(self.b_caudal, 1, 0)

        self.e_caudal = QLineEdit()
        self.e_caudal.setAlignment(Qt.AlignCenter)
        self.e_caudal.setFont(self.font_p1)
        self.grid_body_lower.addWidget(self.e_caudal, 1, 1)

        self.b_dorsal = QPushButton("Dorsal Limit:")
        self.grid_body_lower.addWidget(self.b_dorsal, 0, 2)

        self.e_dorsal = QLineEdit()
        self.e_dorsal.setAlignment(Qt.AlignCenter)
        self.e_dorsal.setFont(self.font_p1)
        self.grid_body_lower.addWidget(self.e_dorsal, 0, 3)

        self.b_ventral = QPushButton("Ventral Limit:")
        self.grid_body_lower.addWidget(self.b_ventral, 1, 2)

        self.e_ventral = QLineEdit()
        self.e_ventral.setAlignment(Qt.AlignCenter)
        self.e_ventral.setFont(self.font_p1)
        self.grid_body_lower.addWidget(self.e_ventral, 1, 3)

        self.b_first_slice = QPushButton("Mark as FIRST Slice With Brainstem:")
        self.grid_body_lower.addWidget(self.b_first_slice, 0, 4)

        self.e_first_slice = QLineEdit()
        self.e_first_slice.setAlignment(Qt.AlignCenter)
        self.e_first_slice.setFont(self.font_p1)
        self.grid_body_lower.addWidget(self.e_first_slice, 0, 5)

        self.b_last_slice = QPushButton("Mark as LAST Slice With Brainstem:")
        self.grid_body_lower.addWidget(self.b_last_slice, 1, 4)

        self.e_last_slice = QLineEdit()
        self.e_last_slice.setAlignment(Qt.AlignCenter)
        self.e_last_slice.setFont(self.font_p1)
        self.grid_body_lower.addWidget(self.e_last_slice, 1, 5)

        self.b_done = QPushButton("DONE")
        self.grid_body_lower.addWidget(self.b_done, 1, 6)

        # Super grid
        self.supergrid = QGridLayout()
        self.supergrid.addLayout(self.grid_top, 0, 0)
        self.supergrid.addLayout(self.grid_body_upper, 1, 0)
        self.supergrid.addLayout(self.grid_body, 2, 0)
        self.supergrid.addLayout(self.grid_body_lower, 3, 0)

        # Set layout and window title
        self.setLayout(self.supergrid)
        self.setWindowTitle("Q")

    def set_curr_section(self, section_index=-1):
        """
        Sets the current section to the section passed in.
        Will automatically update curr_section, prev_section, and next_section.
        Updates the header fields and loads the current section image.
        """
        if section_index == -1:
            section_index = self.curr_section_index

        # Update curr, prev, and next section
        self.curr_section_index = section_index
        self.curr_section = self.valid_sections[self.valid_section_keys[
            self.curr_section_index]]

        # Update the section and filename at the top
        self.e_filename.setText(self.curr_section['destination'])
        self.e_section.setText(str(self.curr_section['section_number']))

        # Get filepath of "curr_section" and set it as viewer's photo
        img_fp = os.path.join(self.fileLocationManager.thumbnail_prep,
                              self.curr_section['destination'])
        if os.path.isfile(img_fp):
            img = cv2.imread(img_fp) * 3
            self.img_height, self.img_width, channel = img.shape

            if self.rostral != -1:
                x_coordinate = int(self.rostral)
                img[:, x_coordinate - 2:x_coordinate + 2, :] = np.ones(
                    (self.img_height, 4, 3)) * 255
            if self.caudal != -1:
                x_coordinate = int(self.caudal)
                img[:, x_coordinate - 2:x_coordinate + 2, :] = np.ones(
                    (self.img_height, 4, 3)) * 255
            if self.dorsal != -1:
                y_coordinate = int(self.dorsal)
                img[y_coordinate - 2:y_coordinate + 2, :, :] = np.ones(
                    (4, self.img_width, 3)) * 255
            if self.ventral != -1:
                y_coordinate = int(self.ventral)
                img[y_coordinate - 2:y_coordinate + 2, :, :] = np.ones(
                    (4, self.img_width, 3)) * 255

            qImg = QImage(img.data, self.img_width, self.img_height,
                          3 * self.img_width, QImage.Format_RGB888)
            self.viewer.set_photo(QPixmap(qImg))

        # Update the internal crop values based on text boxes
        #self.updateCropVals()

    def get_valid_section_index(self, section_index):
        if section_index >= len(self.valid_sections):
            return 0
        elif section_index < 0:
            return len(self.valid_sections) - 1
        else:
            return section_index

    def click_button(self, button):
        if button in [
                self.b_rostral, self.b_caudal, self.b_dorsal, self.b_ventral
        ]:
            self.viewer.set_drag_mode(0)

            if button == self.b_rostral:
                self.active_selection = 'rostral'
            elif button == self.b_caudal:
                self.active_selection = 'caudal'
            elif button == self.b_dorsal:
                self.active_selection = 'dorsal'
            elif button == self.b_ventral:
                self.active_selection = 'ventral'

        # Prep2 section limits
        elif button in [self.b_first_slice, self.b_last_slice]:
            if button == self.b_first_slice:
                self.first_slice = int(self.curr_section)
                self.e_first_slice.setText(str(self.curr_section))
            elif button == self.b_last_slice:
                self.last_slice = int(self.curr_section)
                self.e_last_slice.setText(str(self.curr_section))

        elif button == self.b_done:
            if -1 in [
                    self.rostral, self.caudal, self.dorsal, self.ventral,
                    self.first_slice, self.last_slice
            ]:
                QMessageBox.about(self, "Popup Message",
                                  "Make sure all six fields have values!")
                return
            elif self.rostral >= self.caudal:
                QMessageBox.about(
                    self, "Popup Message",
                    "Rostral Limit must be smaller than caudal limit!")
                return
            elif self.dorsal >= self.ventral:
                QMessageBox.about(
                    self, "Popup Message",
                    "Dorsal Limit must be smaller than Ventral limit!")
                return
            elif self.first_slice >= self.last_slice:
                QMessageBox.about(self, "Popup Message",
                                  "Last slice must be after the first slice!")
                return

            try:
                QMessageBox.about(
                    self, "Popup Message",
                    "This operation will take roughly 1.5 minutes per image.")
                self.setCurrSection(self.curr_section)
                stain = stack_metadata[stack]['stain']
                os.subprocess.call([
                    'python', 'a_script_preprocess_6.py', stack, stain, '-l',
                    str(self.rostral),
                    str(self.caudal),
                    str(self.dorsal),
                    str(self.ventral),
                    str(self.first_slice),
                    str(self.last_slice)
                ])
                sys.exit(app.exec_())
            except Exception as e:
                sys.stderr.write('\n ********************************\n')
                sys.stderr.write(str(e))
                sys.stderr.write('\n ********************************\n')

    def click_photo(self, pos):
        if self.viewer.dragMode() == QGraphicsView.NoDrag:
            x = pos.x()
            y = pos.y()
            print('%d, %d' % (pos.x(), pos.y()))

            scale_factor = 1.0 / self.viewer.scale_factor

            if self.active_selection == '':
                pass
            elif self.active_selection == 'rostral':
                self.rostral = int(x * scale_factor)
                self.rostral = min(self.rostral, self.img_width - 5)
                self.rostral = max(self.rostral, 5)
                self.e_rostral.setText(str(self.rostral))
            elif self.active_selection == 'caudal':
                self.caudal = int(x * scale_factor)
                self.caudal = min(self.caudal, self.img_width - 5)
                self.caudal = max(self.caudal, 5)
                self.e_caudal.setText(str(self.caudal))
            elif self.active_selection == 'dorsal':
                self.dorsal = int(y * scale_factor)
                self.dorsal = min(self.dorsal, self.img_height - 5)
                self.dorsal = max(self.dorsal, 5)
                self.e_dorsal.setText(str(self.dorsal))
            elif self.active_selection == 'ventral':
                self.ventral = int(y * scale_factor)
                self.ventral = min(self.ventral, self.img_height - 5)
                self.ventral = max(self.ventral, 5)
                self.e_ventral.setText(str(self.ventral))

            self.active_selection = ''
            self.viewer.set_drag_mode(1)

    def updateCropVals(self):
        if self.e_rostral.text() != '':
            try:
                self.rostral = int(self.e_rostral.text())
            except:
                self.rostral = -1
                self.e_rostral.setText("")
        if self.e_caudal.text() != '':
            try:
                self.caudal = int(self.e_caudal.text())
            except:
                self.caudal = -1
                self.e_caudal.setText("")
        if self.e_dorsal.text() != '':
            try:
                self.dorsal = int(self.e_dorsal.text())
            except:
                self.dorsal = -1
                self.e_dorsal.setText("")
        if self.e_ventral.text() != '':
            try:
                self.ventral = int(self.e_ventral.text())
            except:
                self.ventral = -1
                self.e_ventral.setText("")
        if self.e_first_slice.text() != '':
            try:
                self.first_slice = int(self.e_first_slice.text())
            except:
                self.first_slice = -1
                self.e_first_slice.setText("")
        if self.e_last_slice.text() != '':
            try:
                self.last_slice = int(self.e_last_slice.text())
            except:
                self.last_slice = -1
                self.e_last_slice.setText("")

    def keyPressEvent(self, event):
        try:
            key = event.key()
        except AttributeError:
            key = event

        if key == 91:  # [
            index = self.get_valid_section_index(self.curr_section_index - 1)
            self.set_curr_section(index)
        elif key == 93:  # ]
            index = self.get_valid_section_index(self.curr_section_index + 1)
            self.set_curr_section(index)
        elif key == 16777220:  # Enter
            index = self.get_valid_section_index(self.curr_section_index)
            self.set_curr_section(index)
        else:
            print(key)

    def closeEvent(self, event):
        sys.exit(app.exec_())
示例#2
0
class GUISortedFilenames(QWidget):
    def __init__(self, stack, parent=None):
        super(GUISortedFilenames, self).__init__(parent)

        self.stack = stack
        self.fileLocationManager = FileLocationManager(self.stack)
        self.sqlController = SqlController()
        self.sqlController.get_animal_info(self.stack)

        self.valid_sections = self.sqlController.get_valid_sections(stack)
        self.valid_section_keys = sorted(list(self.valid_sections))

        self.curr_section_index = 0
        self.curr_section = None

        self.init_ui()

        self.b_rotate_left.clicked.connect(
            lambda: self.click_button(self.b_rotate_left))
        self.b_rotate_right.clicked.connect(
            lambda: self.click_button(self.b_rotate_right))
        self.b_flip_vertical.clicked.connect(
            lambda: self.click_button(self.b_flip_vertical))
        self.b_flip_horozontal.clicked.connect(
            lambda: self.click_button(self.b_flip_horozontal))
        self.b_move_left.clicked.connect(
            lambda: self.click_button(self.b_move_left))
        self.b_move_right.clicked.connect(
            lambda: self.click_button(self.b_move_right))
        self.b_quality.currentIndexChanged.connect(
            lambda: self.click_button(self.b_quality))
        self.b_remove.clicked.connect(lambda: self.click_button(self.b_remove))
        self.b_help.clicked.connect(lambda: self.click_button(self.b_help))
        self.b_done.clicked.connect(lambda: self.click_button(self.b_done))

        self.set_curr_section(self.curr_section_index)

    def init_ui(self):
        self.font_h1 = QFont("Arial", 32)
        self.font_p1 = QFont("Arial", 16)

        self.grid_top = QGridLayout()
        self.grid_body_upper = QGridLayout()
        self.grid_body = QGridLayout()
        self.grid_body_lower = QGridLayout()

        self.resize(1600, 1100)

        # Grid Top
        self.e_title = QLineEdit()
        self.e_title.setAlignment(Qt.AlignCenter)
        self.e_title.setFont(self.font_h1)
        self.e_title.setReadOnly(True)
        self.e_title.setText("Setup Sorted Filenames")
        self.e_title.setFrame(False)
        self.grid_top.addWidget(self.e_title, 0, 0)

        self.b_help = QPushButton("HELP")
        self.b_help.setDefault(True)
        self.b_help.setEnabled(True)
        self.grid_top.addWidget(self.b_help, 0, 1)

        # Grid BODY UPPER
        self.e_filename = QLineEdit()
        self.e_filename.setAlignment(Qt.AlignCenter)
        self.e_filename.setFont(self.font_p1)
        self.e_filename.setReadOnly(True)
        self.e_filename.setText("Filename: ")
        self.grid_body_upper.addWidget(self.e_filename, 0, 2)

        self.e_section = QLineEdit()
        self.e_section.setAlignment(Qt.AlignCenter)
        self.e_section.setFont(self.font_p1)
        self.e_section.setReadOnly(True)
        self.e_section.setText("Section: ")
        self.grid_body_upper.addWidget(self.e_section, 0, 3)

        # Grid BODY
        self.viewer = ImageViewer(self)
        self.grid_body.addWidget(self.viewer, 0, 0)

        # Grid BODY LOWER
        self.b_flip_vertical = QPushButton("Flip vertically")
        self.grid_body_lower.addWidget(self.b_flip_vertical, 0, 0)

        self.b_flip_horozontal = QPushButton("Flop horizontally")
        self.grid_body_lower.addWidget(self.b_flip_horozontal, 0, 1)

        self.b_rotate_left = QPushButton("Rotate Left")
        self.grid_body_lower.addWidget(self.b_rotate_left, 0, 2)

        self.b_rotate_right = QPushButton("Rotate Right")
        self.grid_body_lower.addWidget(self.b_rotate_right, 0, 3)

        self.b_move_left = QPushButton("<--   Move Section Left   <--")
        #self.grid_body_lower.addWidget(self.b_move_left, 1, 0)

        self.b_move_right = QPushButton("-->   Move Section Right   -->")
        #self.grid_body_lower.addWidget(self.b_move_right, 1, 1)

        self.b_quality = QComboBox()
        self.b_quality.addItems([
            'Section quality: unusable', 'Section quality: blurry',
            'Section quality: good'
        ])
        #self.grid_body_lower.addWidget(self.b_quality, 1, 2)

        self.b_remove = QPushButton("Remove section")
        #self.grid_body_lower.addWidget(self.b_remove, 1, 3)

        self.progress = QProgressBar(self)
        self.grid_body_lower.addWidget(self.progress, 2, 0, 1, 3)
        self.progress.hide()

        self.b_done = QPushButton("Finished")
        self.grid_body_lower.addWidget(self.b_done, 2, 3)

        # Super grid
        self.supergrid = QGridLayout()
        self.supergrid.addLayout(self.grid_top, 0, 0)
        self.supergrid.addLayout(self.grid_body_upper, 1, 0)
        self.supergrid.addLayout(self.grid_body, 2, 0)
        self.supergrid.addLayout(self.grid_body_lower, 3, 0)

        # Set layout and window title
        self.setLayout(self.supergrid)
        self.setWindowTitle("Q")

    def set_curr_section(self, section_index=-1):
        """
        Sets the current section to the section passed in.
        Will automatically update curr_section, prev_section, and next_section.
        Updates the header fields and loads the current section image.
        """
        if section_index == -1:
            section_index = self.curr_section_index

        # Update curr, prev, and next section
        self.curr_section_index = section_index
        self.curr_section = self.valid_sections[self.valid_section_keys[
            self.curr_section_index]]

        # Update the section and filename at the top
        self.e_filename.setText(self.curr_section['destination'])
        self.e_section.setText(str(self.curr_section['section_number']))

        # Get filepath of "curr_section" and set it as viewer's photo
        img_fp = os.path.join(self.fileLocationManager.thumbnail_prep,
                              self.curr_section['destination'])
        self.viewer.set_photo(img_fp)

        # Update the quality selection in the bottom left
        index = self.b_quality.findText(self.curr_section['quality'],
                                        Qt.MatchFixedString)
        if index >= 0:
            self.b_quality.setCurrentIndex(index)

    def get_valid_section_index(self, section_index):
        if section_index >= len(self.valid_sections):
            return 0
        elif section_index < 0:
            return len(self.valid_sections) - 1
        else:
            return section_index

    def click_button(self, button):
        if button == self.b_quality:
            curr_section = self.valid_sections[self.valid_section_keys[
                self.curr_section_index]]
            curr_section['quality'] = self.b_quality.currentText()
            self.sqlController.save_valid_sections(self.valid_sections)

        elif button in [self.b_move_left, self.b_move_right, self.b_remove]:
            if button == self.b_move_left:
                self.sqlController.move_section(
                    self.stack, self.curr_section['section_number'], -1)
            elif button == self.b_move_right:
                self.sqlController.move_section(
                    self.stack, self.curr_section['section_number'], 1)
            elif button == self.b_remove:
                result = self.message_box(
                    'Are you sure you want to totally remove this section from this brain?\n\n'
                    +
                    'Warning: The image will be marked as irrelevant to the current brain!',
                    True)

                # The answer is Yes
                if result == 2:
                    # Remove the current section from "self.valid_sections
                    self.sqlController.inactivate_section(
                        self.stack, self.curr_section['section_number'])

                    self.valid_sections = self.sqlController.get_valid_sections(
                        self.stack)
                    self.valid_section_keys = sorted(list(self.valid_sections))

                    if self.curr_section_index == 0:
                        self.curr_section_index = len(
                            self.valid_section_keys) - 1
                    else:
                        self.curr_section_index = self.curr_section_index - 1
            else:
                pass

            # Update the Viewer info and displayed image
            self.valid_sections = self.sqlController.get_valid_sections(
                self.stack)
            self.valid_section_keys = sorted(list(self.valid_sections))
            self.set_curr_section(self.curr_section_index)

        elif button in [
                self.b_flip_vertical, self.b_flip_horozontal,
                self.b_rotate_right, self.b_rotate_left
        ]:
            """
            Transform_type must be "rotate", "flip", or "flop".
            These transformations get applied to all the active sections. The actual
            conversions take place on the thumbnails and the raw files.
            The transformed raw files get placed in the preps/oriented dir.
            """

            index = [
                self.b_flip_vertical, self.b_flip_horozontal,
                self.b_rotate_right, self.b_rotate_left
            ].index(button)
            op = ['flip', 'flop', 'right', 'left'][index]

            size = len(self.valid_sections.values()) - 1
            self.progress_bar(True, size)

            for index, section in enumerate(self.valid_sections.values()):
                thumbnail_path = os.path.join(
                    self.fileLocationManager.thumbnail_prep,
                    section['destination'])
                if os.path.isfile(thumbnail_path):
                    self.transform_image(thumbnail_path, op)

                self.progress.setValue(index)

            self.progress_bar(False, size)
            self.set_curr_section(section_index=-1)

        elif button == self.b_help:
            self.message_box(
                'This GUI is used to align slices to each other. The shortcut commands are as follows: \n\n'
                + '-  `[`: Go back one section. \n' +
                '-  `]`: Go forward one section. \n\n' +
                'Use the buttons on the bottom panel to move', False)

        elif button == self.b_done:
            self.message_box(
                "All selected operations will now be performed on the full sized raw images"
                +
                "This may take an hour or two, depending on how many operations are queued.",
                False)

            # Apply the transformations to the real images
            self.apply_queued_transformations()

            self.sqlController.set_step_completed_in_progress_ini(
                self.stack, '1-4_setup_sorted_filenames')
            self.sqlController.set_step_completed_in_progress_ini(
                self.stack, '1-5_setup_orientations')
            sys.exit(app.exec_())

    def transform_image(self, filename, op):
        def get_last_2d(data):
            if data.ndim <= 2:
                return data
            m, n = data.shape[-2:]
            return data.flat[:m * n].reshape(m, n)

        img = io.imread(filename)
        img = get_last_2d(img)

        # Rotating a multidimensional image has to be done backwards.
        # To rotate right, do np.rot(img, 3), to rotate left, do np.rot(img, 1)
        if op == 'left':
            img = np.rot90(img, 3)
        elif op == 'right':
            img = np.rot90(img, 1)
        elif op == 'flip':
            img = np.flipud(img)
        elif op == 'flop':
            img = np.fliplr(img)

        os.unlink(filename)
        io.imsave(filename, img)
        self.save_to_web_thumbnail(filename, img)

    def save_to_web_thumbnail(self, filename, img):
        filename = os.path.basename(filename)
        png_file = os.path.splitext(filename)[0] + '.png'
        png_path = os.path.join(self.fileLocationManager.thumbnail_web,
                                png_file)
        if os.path.exists(png_path):
            os.unlink(png_path)
        io.imsave(png_path, img)

    def apply_queued_transformations(self):
        pass

    def progress_bar(self, show, max_value):
        if show:
            self.progress.setMaximum(max_value)
            self.progress.show()
        else:
            self.progress.hide()

        self.b_quality.setDisabled(show)
        self.b_move_left.setDisabled(show)
        self.b_move_right.setDisabled(show)
        self.b_flip_vertical.setDisabled(show)
        self.b_flip_horozontal.setDisabled(show)
        self.b_rotate_right.setDisabled(show)
        self.b_rotate_left.setDisabled(show)
        self.b_remove.setDisabled(show)
        self.b_done.setDisabled(show)

    def message_box(self, text, is_warn):
        msg_box = QMessageBox()
        msg_box.setText(text)

        if is_warn:
            msg_box.addButton(QPushButton('Cancel'), QMessageBox.RejectRole)
            msg_box.addButton(QPushButton('No'), QMessageBox.NoRole)
            msg_box.addButton(QPushButton('Yes'), QMessageBox.YesRole)

        return msg_box.exec_()

    def keyPressEvent(self, event):
        try:
            key = event.key()
        except AttributeError:
            key = event

        if key == 91:  # [
            index = self.get_valid_section_index(self.curr_section_index - 1)
            self.set_curr_section(index)
        elif key == 93:  # ]
            index = self.get_valid_section_index(self.curr_section_index + 1)
            self.set_curr_section(index)
        else:
            print(key)

    def closeEvent(self, event):
        sys.exit(app.exec_())