class SuccessPage(QWizardPage):
    def __init__(self, parent):
        super().__init__(parent)
        self.setTitle("Installation successful")
        self._label = HyperTextLabel()
        layout = QVBoxLayout(self)
        use_julia_check_box = QCheckBox("Use this Julia with Spine Toolbox")
        self._create_kernel_check_box = QCheckBox(
            "Create a Jupyter kernel for this Julia")
        self.registerField("use_julia", use_julia_check_box)
        self.registerField("create_kernel", self._create_kernel_check_box)
        layout.addWidget(self._label)
        layout.addStretch()
        layout.addWidget(use_julia_check_box)
        layout.addWidget(self._create_kernel_check_box)
        layout.addStretch()
        layout.addStretch()
        use_julia_check_box.clicked.connect(self._handle_use_julia_clicked)

    @Slot(bool)
    def _handle_use_julia_clicked(self, checked=False):
        self._create_kernel_check_box.setChecked(checked)
        self._create_kernel_check_box.setEnabled(checked)

    def initializePage(self):
        self._label.setText(
            f"Julia executable created at <b>{self.wizard().julia_exe}</b>")
        self.setField("use_julia", True)
        self.setField("create_kernel", True)

    def nextId(self):
        return -1
示例#2
0
    def paint(self, painter, option, index):
        checked = bool(index.data())
        checkbox = QCheckBox()

        if (index.flags() & Qt.ItemIsEditable) > 0:
            checkbox.setEnabled(True)
        else:
            checkbox.setEnabled(False)

        # Implement tristate checkboxe for folder nodes
        if checked:
            checkbox.setCheckState(Qt.Checked)
        else:
            checkbox.setCheckState(Qt.Unchecked)

        if self.parent():
            checkbox.setStyleSheet(self.parent().styleSheet())

        width = option.widget.columnWidth(1)
        height = option.widget.rowHeight(0)

        painter.save()
        painter.translate(option.rect.topLeft())
        checkbox.rect = option.rect
        checkbox.setFixedSize(width, height)
        checkbox.render(painter, QPoint(0, 0))
        painter.restore()
class CheckBoxAction(QWidget):
    def __init__(self,
                 parent=None,
                 text: str = "",
                 checked: bool = False,
                 onState: str = "Enabled",
                 offState: str = "Disabled"):
        super().__init__(parent=parent)
        self.onState = onState
        self.offState = offState
        self.setLayout(QtWidgets.QHBoxLayout(self))
        self.avoidInternalChecking = False
        self.label = QLabel(text)
        self.layout().addWidget(self.label)
        self.layout().setMargin(1)
        self.check = QCheckBox(self)
        self.layout().addWidget(self.check)
        self.check.setChecked(checked)
        self.check.setObjectName("QCheckBoxAction")
        self.check.stateChanged.connect(self.changeText)
        self.changeText()

    def winIsLight(self) -> bool:
        mode = darkdetect.isLight()
        if (mode != None):
            return mode
        else:
            return True

    def setText(self, text: str) -> None:
        self.label.setText(text)

    def setEnabled(self, enabled: bool) -> None:
        self.check.setEnabled(enabled)
        self.changeText()

    def isChecked(self) -> bool:
        return self.check.isChecked()

    def setCheckedWithoutInternalChecking(self, value: bool) -> None:
        self.avoidInternalChecking = True
        return self.check.setChecked(value)

    def setChecked(self, value: bool) -> None:
        return self.check.setChecked(value)

    def changeText(self) -> None:
        if (self.check.isChecked()):
            self.check.setText(self.onState)
        else:
            self.check.setText(self.offState)
示例#4
0
    def paint(self, painter, option, index):
        checked = bool(index.data())
        checkbox = QCheckBox()

        if (index.flags() & Qt.ItemIsEditable) > 0:
            checkbox.setEnabled(True)
        else:
            checkbox.setEnabled(False)

        if checked:
            checkbox.setCheckState(Qt.Checked)
        else:
            checkbox.setCheckState(Qt.Unchecked)

        checkbox.rect = self.getCheckBoxRect(option)

        #checkbox.setCheckState(QStyle.State_Enabled)

        style = '''QCheckBox, QRadioButton {
                color: #546E7A;
            }

            QCheckBox::indicator::unchecked  {
                background-color: #FFFFFF;
                border: 1px solid #536D79;
            }

            QCheckBox::indicator::checked, QTreeView::indicator::checked {
                background-color: qradialgradient(cx:0.5, cy:0.5, fx:0.25, fy:0.15, radius:0.3, stop:0 #80CBC4, stop:1 #FFFFFF);
                border: 1px solid #536D79;
            }


            QCheckBox::indicator:disabled, QRadioButton::indicator:disabled, QTreeView::indicator:disabled {
                background-color: #444444;			/* Not sure what this looks like */
            }

            QCheckBox::indicator::checked:disabled, QRadioButton::indicator::checked:disabled, QTreeView::indicator::checked:disabled {  
                background-color: qradialgradient(cx:0.5, cy:0.5, fx:0.25, fy:0.15, radius:0.3, stop:0 #BBBBBB, stop:1 #444444); /* Not sure what this looks like */
            }

            '''
        checkbox.setStyleSheet(style)

        painter.save()
        painter.translate(option.rect.topLeft())
        checkbox.render(painter, QPoint(0, 0))
        painter.restore()
示例#5
0
    def add_horizontal_splitter(self, layout):
        splitter = QSplitter()
        splitter.setOrientation(Qt.Orientation.Vertical)
        layout.addWidget(splitter)

        # ADD TOP
        top_widget = QWidget(splitter)
        top = QVBoxLayout(top_widget)

        # NORMAL
        btn = FillPushButton('Push Button')
        top.layout().addWidget(btn)

        #DISABLED
        btn = FillPushButton('Push Button Disabled')
        btn.setEnabled(False)
        top.layout().addWidget(btn)

        # ADD BOTTOM
        bottom_widget = QWidget(splitter)
        right = QGridLayout(bottom_widget)

        # NORMAL
        cbx = QCheckBox('Check Box')
        right.addWidget(cbx)

        #DISABLED
        cbx = QCheckBox('Check Box Disabled')
        cbx.setEnabled(False)
        right.addWidget(cbx)

        rob = QRadioButton('Radio Button')
        right.addWidget(rob)

        rob = QRadioButton('Radio Button Disabled')
        rob.setEnabled(False)
        right.addWidget(rob)
    def WidgetBlockOnCheck(Activation: QtWidgets.QCheckBox, activate: bool):
        # print(Activation.__class__.__name__)

        if Activation.__class__.__name__ == "QCheckBox":
            if Activation.isChecked() == activate:
                Activation.setChecked(activate)
                Activation.setEnabled(activate)
            else:
                Activation.setChecked(False)
                Activation.setEnabled(activate)

        elif Activation.__class__.__name__ == "QPushButton":
            Activation.setEnabled(activate)
示例#7
0
class ColorDialog(QDialog):
    def __init__(self, model, font_metric, parent=None):
        super().__init__(parent)

        self.setWindowTitle('Color Options')

        self.model = model
        self.font_metric = font_metric
        self.main_window = parent

        self.createDialogLayout()

    def createDialogLayout(self):

        self.createGeneralTab()

        self.cellTable = self.createDomainTable(self.main_window.cellsModel)
        self.matTable = self.createDomainTable(self.main_window.materialsModel)
        self.tabs = {
            'cell': self.createDomainTab(self.cellTable),
            'material': self.createDomainTab(self.matTable),
            'temperature': self.createPropertyTab('temperature'),
            'density': self.createPropertyTab('density')
        }

        self.tab_bar = QTabWidget()
        self.tab_bar.setMaximumHeight(800)
        self.tab_bar.setSizePolicy(QSizePolicy.Expanding,
                                   QSizePolicy.Expanding)
        self.tab_bar.addTab(self.generalTab, 'General')
        self.tab_bar.addTab(self.tabs['cell'], 'Cells')
        self.tab_bar.addTab(self.tabs['material'], 'Materials')
        self.tab_bar.addTab(self.tabs['temperature'], 'Temperature')
        self.tab_bar.addTab(self.tabs['density'], 'Density')

        self.createButtonBox()

        self.colorDialogLayout = QVBoxLayout()
        self.colorDialogLayout.addWidget(self.tab_bar)
        self.colorDialogLayout.addWidget(self.buttonBox)
        self.setLayout(self.colorDialogLayout)

    def createGeneralTab(self):

        main_window = self.main_window

        # Masking options
        self.maskingCheck = QCheckBox('')
        self.maskingCheck.stateChanged.connect(main_window.toggleMasking)

        button_width = self.font_metric.boundingRect("XXXXXXXXXX").width()
        self.maskColorButton = QPushButton()
        self.maskColorButton.setCursor(QtCore.Qt.PointingHandCursor)
        self.maskColorButton.setFixedWidth(button_width)
        self.maskColorButton.setFixedHeight(self.font_metric.height() * 1.5)
        self.maskColorButton.clicked.connect(main_window.editMaskingColor)

        # Highlighting options
        self.hlCheck = QCheckBox('')
        self.hlCheck.stateChanged.connect(main_window.toggleHighlighting)

        self.hlColorButton = QPushButton()
        self.hlColorButton.setCursor(QtCore.Qt.PointingHandCursor)
        self.hlColorButton.setFixedWidth(button_width)
        self.hlColorButton.setFixedHeight(self.font_metric.height() * 1.5)
        self.hlColorButton.clicked.connect(main_window.editHighlightColor)

        self.alphaBox = QDoubleSpinBox()
        self.alphaBox.setRange(0, 1)
        self.alphaBox.setSingleStep(.05)
        self.alphaBox.valueChanged.connect(main_window.editAlpha)

        self.seedBox = QSpinBox()
        self.seedBox.setRange(1, 999)
        self.seedBox.valueChanged.connect(main_window.editSeed)

        # General options
        self.bgButton = QPushButton()
        self.bgButton.setCursor(QtCore.Qt.PointingHandCursor)
        self.bgButton.setFixedWidth(button_width)
        self.bgButton.setFixedHeight(self.font_metric.height() * 1.5)
        self.bgButton.clicked.connect(main_window.editBackgroundColor)

        self.colorbyBox = QComboBox(self)
        self.colorbyBox.addItem("material")
        self.colorbyBox.addItem("cell")
        self.colorbyBox.addItem("temperature")
        self.colorbyBox.addItem("density")
        self.colorbyBox.currentTextChanged[str].connect(
            main_window.editColorBy)

        self.universeLevelBox = QComboBox(self)
        self.universeLevelBox.addItem('all')
        for i in range(self.model.max_universe_levels):
            self.universeLevelBox.addItem(str(i))
        self.universeLevelBox.currentTextChanged[str].connect(
            main_window.editUniverseLevel)

        # Overlap plotting
        self.overlapCheck = QCheckBox('', self)
        overlap_connector = partial(main_window.toggleOverlaps)
        self.overlapCheck.stateChanged.connect(overlap_connector)

        self.overlapColorButton = QPushButton()
        self.overlapColorButton.setCursor(QtCore.Qt.PointingHandCursor)
        self.overlapColorButton.setFixedWidth(button_width)
        self.overlapColorButton.setFixedHeight(self.font_metric.height() * 1.5)
        self.overlapColorButton.clicked.connect(main_window.editOverlapColor)

        self.colorResetButton = QPushButton("&Reset Colors")
        self.colorResetButton.setCursor(QtCore.Qt.PointingHandCursor)
        self.colorResetButton.clicked.connect(main_window.resetColors)

        formLayout = QFormLayout()
        formLayout.setAlignment(QtCore.Qt.AlignHCenter)
        formLayout.setFormAlignment(QtCore.Qt.AlignHCenter)
        formLayout.setLabelAlignment(QtCore.Qt.AlignLeft)

        formLayout.addRow('Masking:', self.maskingCheck)
        formLayout.addRow('Mask Color:', self.maskColorButton)
        formLayout.addRow(HorizontalLine())
        formLayout.addRow('Highlighting:', self.hlCheck)
        formLayout.addRow('Highlight Color:', self.hlColorButton)
        formLayout.addRow('Highlight Alpha:', self.alphaBox)
        formLayout.addRow('Highlight Seed:', self.seedBox)
        formLayout.addRow(HorizontalLine())
        formLayout.addRow('Background Color:          ', self.bgButton)
        formLayout.addRow(HorizontalLine())
        formLayout.addRow('Show Overlaps:', self.overlapCheck)
        formLayout.addRow('Overlap Color:', self.overlapColorButton)
        formLayout.addRow(HorizontalLine())
        formLayout.addRow('Color Plot By:', self.colorbyBox)
        formLayout.addRow('Universe Level:', self.universeLevelBox)
        formLayout.addRow(self.colorResetButton, None)

        generalLayout = QHBoxLayout()
        innerWidget = QWidget()
        generalLayout.setAlignment(QtCore.Qt.AlignVCenter)
        innerWidget.setLayout(formLayout)
        generalLayout.addStretch(1)
        generalLayout.addWidget(innerWidget)
        generalLayout.addStretch(1)

        self.generalTab = QWidget()
        self.generalTab.setLayout(generalLayout)

    def createDomainTable(self, domainmodel):

        domainTable = QTableView()
        domainTable.setModel(domainmodel)
        domainTable.setItemDelegate(DomainDelegate(domainTable))
        domainTable.verticalHeader().setVisible(False)
        domainTable.resizeColumnsToContents()
        domainTable.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        domainTable.horizontalHeader().setSectionResizeMode(
            1, QHeaderView.Stretch)

        return domainTable

    def createDomainTab(self, domaintable):

        domainTab = QWidget()
        domainTab.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        domainLayout = QVBoxLayout()
        domainLayout.addWidget(domaintable)
        domainTab.setLayout(domainLayout)

        return domainTab

    def createPropertyTab(self, property_kind):
        propertyTab = QWidget()
        propertyTab.property_kind = property_kind
        propertyTab.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        propertyLayout = QVBoxLayout()

        propertyTab.minMaxCheckBox = QCheckBox()
        propertyTab.minMaxCheckBox.setCheckable(True)
        connector1 = partial(self.main_window.toggleUserMinMax,
                             property=property_kind)
        propertyTab.minMaxCheckBox.stateChanged.connect(connector1)

        propertyTab.minBox = ScientificDoubleSpinBox(self)
        propertyTab.minBox.setMaximum(1E9)
        propertyTab.minBox.setMinimum(0)
        propertyTab.maxBox = ScientificDoubleSpinBox(self)
        propertyTab.maxBox.setMaximum(1E9)
        propertyTab.maxBox.setMinimum(0)

        connector2 = partial(self.main_window.editColorbarMin,
                             property_type=property_kind)
        propertyTab.minBox.valueChanged.connect(connector2)
        connector3 = partial(self.main_window.editColorbarMax,
                             property_type=property_kind)
        propertyTab.maxBox.valueChanged.connect(connector3)

        propertyTab.colormapBox = QComboBox(self)
        cmaps = sorted(m for m in mcolormaps.datad if not m.endswith("_r"))
        for cmap in cmaps:
            propertyTab.colormapBox.addItem(cmap)

        connector = partial(self.main_window.editColorMap,
                            property_type=property_kind)

        propertyTab.colormapBox.currentTextChanged[str].connect(connector)

        propertyTab.dataIndicatorCheckBox = QCheckBox()
        propertyTab.dataIndicatorCheckBox.setCheckable(True)
        connector4 = partial(self.main_window.toggleDataIndicatorCheckBox,
                             property=property_kind)
        propertyTab.dataIndicatorCheckBox.stateChanged.connect(connector4)

        propertyTab.colorBarScaleCheckBox = QCheckBox()
        propertyTab.colorBarScaleCheckBox.setCheckable(True)
        connector5 = partial(self.main_window.toggleColorbarScale,
                             property=property_kind)
        propertyTab.colorBarScaleCheckBox.stateChanged.connect(connector5)

        formLayout = QFormLayout()
        formLayout.setAlignment(QtCore.Qt.AlignHCenter)
        formLayout.setFormAlignment(QtCore.Qt.AlignHCenter)
        formLayout.setLabelAlignment(QtCore.Qt.AlignLeft)

        formLayout.addRow('Colormap:', propertyTab.colormapBox)

        formLayout.addRow('Custom Min/Max', propertyTab.minMaxCheckBox)
        formLayout.addRow('Data Indicator', propertyTab.dataIndicatorCheckBox)
        formLayout.addRow('Log Scale', propertyTab.colorBarScaleCheckBox)
        formLayout.addRow(HorizontalLine())
        formLayout.addRow('Max: ', propertyTab.maxBox)
        formLayout.addRow('Min: ', propertyTab.minBox)

        propertyTab.setLayout(formLayout)

        return propertyTab

    def updateDataIndicatorVisibility(self):
        av = self.model.activeView
        for key, val in av.data_indicator_enabled.items():
            self.tabs[key].dataIndicatorCheckBox.setChecked(val)

    def updateColorMaps(self):
        cmaps = self.model.activeView.colormaps
        for key, val in cmaps.items():
            idx = self.tabs[key].colormapBox.findText(
                val, QtCore.Qt.MatchFixedString)
            if idx >= 0:
                self.tabs[key].colormapBox.setCurrentIndex(idx)

    def updateColorMinMax(self):
        minmax = self.model.activeView.user_minmax
        for key, val in minmax.items():
            self.tabs[key].minBox.setValue(val[0])
            self.tabs[key].maxBox.setValue(val[1])
        custom_minmax = self.model.activeView.use_custom_minmax
        for key, val, in custom_minmax.items():
            self.tabs[key].minMaxCheckBox.setChecked(val)
            self.tabs[key].minBox.setEnabled(val)
            self.tabs[key].maxBox.setEnabled(val)

    def updateColorbarScale(self):
        av = self.model.activeView
        for key, val in av.color_scale_log.items():
            self.tabs[key].colorBarScaleCheckBox.setChecked(val)

    def createButtonBox(self):

        applyButton = QPushButton("Apply Changes")
        applyButton.clicked.connect(self.main_window.applyChanges)
        closeButton = QPushButton("Close")
        closeButton.clicked.connect(self.hide)

        buttonLayout = QHBoxLayout()
        buttonLayout.addStretch(1)
        buttonLayout.addWidget(applyButton)
        buttonLayout.addWidget(closeButton)

        self.buttonBox = QWidget()
        self.buttonBox.setLayout(buttonLayout)

    def updateDialogValues(self):

        self.updateMasking()
        self.updateMaskingColor()
        self.updateColorMaps()
        self.updateColorMinMax()
        self.updateColorbarScale()
        self.updateDataIndicatorVisibility()
        self.updateHighlighting()
        self.updateHighlightColor()
        self.updateAlpha()
        self.updateSeed()
        self.updateBackgroundColor()
        self.updateColorBy()
        self.updateUniverseLevel()
        self.updateDomainTabs()
        self.updateOverlap()
        self.updateOverlapColor()

    def updateMasking(self):
        masking = self.model.activeView.masking

        self.maskingCheck.setChecked(masking)
        self.maskColorButton.setDisabled(not masking)

        if masking:
            self.cellTable.showColumn(4)
            self.matTable.showColumn(4)
        else:
            self.cellTable.hideColumn(4)
            self.matTable.hideColumn(4)

    def updateMaskingColor(self):
        color = self.model.activeView.maskBackground
        style_values = "border-radius: 8px; background-color: rgb{}"
        self.maskColorButton.setStyleSheet(style_values.format(str(color)))

    def updateHighlighting(self):
        highlighting = self.model.activeView.highlighting

        self.hlCheck.setChecked(highlighting)
        self.hlColorButton.setDisabled(not highlighting)
        self.alphaBox.setDisabled(not highlighting)
        self.seedBox.setDisabled(not highlighting)

        if highlighting:
            self.cellTable.showColumn(5)
            self.cellTable.hideColumn(2)
            self.cellTable.hideColumn(3)
            self.matTable.showColumn(5)
            self.matTable.hideColumn(2)
            self.matTable.hideColumn(3)
        else:
            self.cellTable.hideColumn(5)
            self.cellTable.showColumn(2)
            self.cellTable.showColumn(3)
            self.matTable.hideColumn(5)
            self.matTable.showColumn(2)
            self.matTable.showColumn(3)

    def updateHighlightColor(self):
        color = self.model.activeView.highlightBackground
        style_values = "border-radius: 8px; background-color: rgb{}"
        self.hlColorButton.setStyleSheet(style_values.format(str(color)))

    def updateAlpha(self):
        self.alphaBox.setValue(self.model.activeView.highlightAlpha)

    def updateSeed(self):
        self.seedBox.setValue(self.model.activeView.highlightSeed)

    def updateBackgroundColor(self):
        color = self.model.activeView.domainBackground
        self.bgButton.setStyleSheet("border-radius: 8px;"
                                    "background-color: rgb%s" % (str(color)))

    def updateOverlapColor(self):
        color = self.model.activeView.overlap_color
        self.overlapColorButton.setStyleSheet("border-radius: 8px;"
                                              "background-color: rgb%s" %
                                              (str(color)))

    def updateOverlap(self):
        colorby = self.model.activeView.colorby
        overlap_val = self.model.activeView.color_overlaps
        if colorby in ('cell', 'material'):
            self.overlapCheck.setChecked(overlap_val)

    def updateColorBy(self):
        colorby = self.model.activeView.colorby
        self.colorbyBox.setCurrentText(colorby)
        self.overlapCheck.setEnabled(colorby in ("cell", "material"))
        self.universeLevelBox.setEnabled(colorby == 'cell')

    def updateUniverseLevel(self):
        level = self.model.activeView.level
        if level == -1:
            self.universeLevelBox.setCurrentText('all')
        else:
            self.universeLevelBox.setCurrentText(str(level))

    def updateDomainTabs(self):
        self.cellTable.setModel(self.main_window.cellsModel)
        self.matTable.setModel(self.main_window.materialsModel)
示例#8
0
class IndexSche(QWidget):
    def __init__(self):
        super().__init__()
        self._init_ui()
        self._init_conn()

    def _init_ui(self):
        # self._btn_cron=QRadioButton(self.tr("Cron"))
        # self._btn_realtime=QRadioButton(self.tr("RealTime"))
        vlayout = QVBoxLayout()
        self.setLayout(vlayout)

        _group_cron = QGroupBox(self.tr("Cron"))
        vlayout.addWidget(_group_cron)

        _formLayout_Cron = QFormLayout()
        _group_cron.setLayout(_formLayout_Cron)

        _formLayout_Cron.addWidget(QLabel("xxxx"))
        self._label_cron_week = QLineEdit()
        self._label_cron_hour = QLineEdit()
        self._label_cron_minu = QLineEdit()
        _formLayout_Cron.addRow(self.tr("星期日"), self._label_cron_week)
        _formLayout_Cron.addRow(self.tr("小时"), self._label_cron_hour)
        _formLayout_Cron.addRow(self.tr("分钟"), self._label_cron_minu)

        self._btn_enable = QPushButton(self.tr("Enable"))
        self._btn_disable = QPushButton(self.tr("Disable"))
        # self._btn_cancel = QPushButton(self.tr("Cancel"))
        hlayout = QHBoxLayout()
        hlayout.addWidget(self._btn_enable)
        hlayout.addWidget(self._btn_disable)
        # hlayout.addWidget(self._btn_cancel)
        # self._btn_cron=QDialogButtonBox(QDialogButtonBox)

        _formLayout_Cron.addWidget(QLabel("ddd"))
        _formLayout_Cron.addRow(hlayout)

        _group_realtime = QGroupBox(self.tr("RealTime"))
        vlayout.addWidget(_group_realtime)

        formLayout = QFormLayout()
        _group_realtime.setLayout(formLayout)

        formLayout.addWidget(QLabel(self.tr("xxx")))

        self._cbk_startOnStart = QCheckBox()
        self._cbk_startNow = QCheckBox()
        self._cbk_startNow.setEnabled(False)
        formLayout.addRow(self._cbk_startOnStart,
                          QLabel(self.tr("Start on System Start")))
        formLayout.addRow(self._cbk_startNow, QLabel(self.tr("Start Now")))

        pass

    def _init_conn(self):
        self._btn_enable.clicked.connect(self._cron_enable)
        self._btn_disable.clicked.connect(self._cron_disable)
        self._cbk_startOnStart.toggled.connect(self._cbk_onstart_toggled)
        self._cbk_startNow.toggled.connect(self._cbk_start_now_toggled)
        pass

    def _cbk_onstart_toggled(self, checked):
        if checked:
            self._cbk_startNow.setEnabled(True)
        ## TODO imple

        pass

    def _cbk_start_now_toggled(self, checked):
        pass

    def _cron_enable(self):
        pass

    def _cron_disable(self):
        pass
示例#9
0
class MainWidget(QWidget):

    format_dict = {
        "JPEG(jpg)": "jpeg",
        "WebP": "webp",
        "Gif": "gif",
        "PNG": "png"
    }

    def __init__(self, parent):
        super().__init__()
        self.par = parent
        self.InitUI()

    def InitUI(self):
        self.selected_dir = str()
        self.option_array = [1, 0, 0, 0]
        self.main_layout = QVBoxLayout()
        self.make_dir_groupbox()
        self.make_func_groupbox()
        self.make_sys_layout()
        self.status_bar = QStatusBar()
        self.setEnabled(True)

        self.setLayout(self.main_layout)

    def make_dir_groupbox(self):
        dir_groupbox = QGroupBox("Select Directory")
        dir_layout = QVBoxLayout()
        self.loaded_dir_label = QLabel(self.selected_dir)
        self.loaded_dir_label.setStyleSheet("max-height: 24px;"
                                            "background-color: #FFFFFF;"
                                            "border-style: solid;"
                                            "border-width: 1px;"
                                            "border-color: #000000")
        dir_layout.addWidget(self.loaded_dir_label)
        self.load_dir_button = QPushButton("Load Directory", self)
        self.load_dir_button.clicked.connect(self.load_dir)
        dir_layout.addWidget(self.load_dir_button)
        dir_groupbox.setLayout(dir_layout)
        self.main_layout.addWidget(dir_groupbox)

    def make_func_groupbox(self):
        func_layout = QHBoxLayout()

        self.encode_widget = RadioBox(encode_dict)
        encode_layout = self.encode_widget.make_radio_box("UTF-8 with BOM")
        self.encode_groupbox = QGroupBox("Encode to: ")
        self.encode_groupbox.setLayout(encode_layout)
        self.encode_groupbox.setCheckable(True)
        self.encode_groupbox.clicked.connect(self.option_set)
        func_layout.addWidget(self.encode_groupbox)

        self.fmt_groupbox = QGroupBox("Convert image format")
        fmt_hbox = QHBoxLayout()
        self.format_widget_from = RadioBox(self.format_dict)
        format_layout_from = self.format_widget_from.make_radio_box("WebP")
        fmt_from_groupbox = QGroupBox("Convert image from: ")
        fmt_from_groupbox.setLayout(format_layout_from)
        fmt_hbox.addWidget(fmt_from_groupbox)

        self.format_widget_to = RadioBox(self.format_dict)
        format_layout_to = self.format_widget_to.make_radio_box("PNG")
        fmt_to_groupbox = QGroupBox("Convert image to: ")
        fmt_to_groupbox.setLayout(format_layout_to)
        fmt_hbox.addWidget(fmt_to_groupbox)
        self.fmt_groupbox.setLayout(fmt_hbox)
        self.fmt_groupbox.setCheckable(True)
        func_layout.addWidget(self.fmt_groupbox)

        option_groupbox = QGroupBox("OPTION: ")
        option_layout = QVBoxLayout()
        self.change_txt_yn = QCheckBox("Change image ext in CSV/ERB", self)
        self.change_txt_yn.toggle()
        self.change_txt_yn.clicked.connect(self.option_set)
        self.backup_yn = QCheckBox("Place files in Result directory", self)
        self.backup_yn.clicked.connect(self.option_set)

        option_layout.addWidget(self.change_txt_yn)
        option_layout.addWidget(self.backup_yn)
        option_groupbox.setLayout(option_layout)
        func_layout.addWidget(option_groupbox)

        self.main_layout.addLayout(func_layout)

    def make_sys_layout(self):
        sys_layout = QHBoxLayout()
        self.run_button = QPushButton("RUN", self)
        self.run_button.setEnabled(False)
        self.run_button.clicked.connect(self.run_process)
        self.prog_bar = QProgressBar()
        self.prog_bar.setMinimum(0)
        sys_layout.addWidget(self.prog_bar)
        sys_layout.stretch(1)
        sys_layout.addWidget(self.run_button)
        self.main_layout.addLayout(sys_layout)

    def load_dir(self):
        f_dialog = QFileDialog(self, "불러올 폴더를 선택하세요.", os.path.curdir)
        f_dialog.setFileMode(QFileDialog.DirectoryOnly)
        self.selected_dir = f_dialog.getExistingDirectory(
            self, "불러올 폴더를 선택하세요.")
        if not self.selected_dir:
            self.selected_dir = str()
            self.run_button.setEnabled(False)
        else:
            self.run_button.setEnabled(True)
        self.loaded_dir_label.setText(self.selected_dir)
        return self.selected_dir

    def option_set(self):
        if not self.encode_groupbox.isChecked():
            self.change_txt_yn.setEnabled(False)
        else:
            self.change_txt_yn.setEnabled(True)
        self.option_array[0] = self.change_txt_yn.checkState()
        self.option_array[1] = self.backup_yn.checkState()
        self.option_array[2] = self.encode_groupbox.isChecked()
        self.option_array[3] = self.fmt_groupbox.isChecked()
        return self.option_array

    def run_process(self):
        self.option_set()
        self.setEnabled(False)
        target_array = (
            self.encode_widget.result,
            self.format_widget_from.result,
            self.format_widget_to.result,
            self.selected_dir,
        )
        bar_array = (self.prog_bar, self.status_bar)
        self.threadclass = MyThread(target_array, bar_array, self.option_array,
                                    self)
        self.work_started = 0
        self.threadclass.processed.connect(self.progbar_set)
        self.threadclass.finished.connect(self.donebox)
        self.threadclass.start()

    def open_result(self):
        if self.option_array[1]:
            result_path = "Result\\"
        else:
            result_path = self.selected_dir
        os.startfile(result_path)

    @Slot(int)
    def progbar_set(self, progress_stat):
        if not self.work_started:
            self.prog_bar.setMaximum(progress_stat)
            self.work_started += 1
        else:
            self.prog_bar.setValue(progress_stat)

    @Slot(bool)
    def donebox(self):
        msgbox = QMessageBox(
            QMessageBox.Warning,
            "Done!",
            "Do you want to quit?",
            buttons=QMessageBox.Yes | QMessageBox.No,
            parent=self,
        )
        open_result = msgbox.addButton("Open Result Folder",
                                       QMessageBox.AcceptRole)
        msgbox.setDefaultButton(QMessageBox.Yes)
        close_yn = msgbox.exec_()
        if close_yn == QMessageBox.No:
            self.threadclass.wait()
            # debugpy.debug_this_thread()
            self.close()
            self.par.intiGUI()
        else:
            if close_yn == QMessageBox.AcceptRole:
                self.open_result()
            QCoreApplication.instance().quit()
示例#10
0
class MuxSettingTab(QWidget):
    tab_clicked_signal = Signal()
    start_muxing_signal = Signal()
    update_task_bar_progress_signal = Signal(int)
    update_task_bar_paused_signal = Signal()
    update_task_bar_clear_signal = Signal()

    def __init__(self):
        super().__init__()
        self.create_widgets()
        self.setup_widgets()
        self.connect_signals()

    def connect_signals(self):
        self.tab_clicked_signal.connect(self.tab_clicked)

        self.destination_path_button.clicked.connect(self.open_select_destination_folder_dialog)

        self.only_keep_those_audios_checkBox.stateChanged.connect(
            self.only_keep_those_audios_multi_choose_comboBox.check_box_state_changed)

        self.only_keep_those_subtitles_checkBox.stateChanged.connect(
            self.only_keep_those_subtitles_multi_choose_comboBox.check_box_state_changed)

        self.make_this_audio_default_checkBox.disable_combo_box.connect(self.disable_make_this_audio_default_comboBox)

        self.make_this_subtitle_default_checkBox.disable_combo_box.connect(
            self.disable_make_this_subtitle_default_comboBox)

        self.control_queue_button.add_to_queue_clicked_signal.connect(self.add_to_queue_button_clicked)
        self.control_queue_button.start_multiplexing_clicked_signal.connect(self.start_multiplexing_button_clicked)
        self.control_queue_button.pause_multiplexing_clicked_signal.connect(self.pause_multiplexing_button_clicked)

        self.clear_job_queue_button.clicked.connect(self.clear_job_queue_button_clicked)

        self.only_keep_those_audios_multi_choose_comboBox.closeList.connect(self.only_keep_those_audios_close_list)

        self.only_keep_those_subtitles_multi_choose_comboBox.closeList.connect(
            self.only_keep_those_subtitles_close_list)

        self.make_this_audio_default_comboBox.currentTextChanged.connect(
            self.make_this_audio_default_comboBox_text_changed)

        self.make_this_subtitle_default_comboBox.currentTextChanged.connect(
            self.make_this_subtitle_default_comboBox_text_changed)

        self.abort_on_errors_checkBox.stateChanged.connect(self.abort_on_errors_state_changed)

        self.keep_log_file_checkBox.stateChanged.connect(self.keep_log_file_state_changed)
        self.job_queue_layout.update_task_bar_progress_signal.connect(self.update_task_bar_progress)
        self.job_queue_layout.paused_done_signal.connect(self.paused_done)
        self.job_queue_layout.cancel_done_signal.connect(self.cancel_done)
        self.job_queue_layout.finished_all_jobs_signal.connect(self.finished_all_jobs)
        self.job_queue_layout.pause_from_error_occurred_signal.connect(self.pause_multiplexing_button_clicked)

    def setup_widgets(self):
        self.setup_mux_setting_groupBox()
        self.setup_job_queue_groupBox()
        self.setup_destination_path_label()
        self.setup_destination_path_lineEdit()
        self.setup_destination_path_button()
        self.setup_abort_on_errors_checkBox()
        self.setup_discard_old_attachments_checkBox()
        self.setup_keep_log_file_checkBox()
        self.setup_clear_job_queue_button()
        self.setup_tool_tip_hint()
        self.setup_layouts()

    def setup_layouts(self):
        self.setup_MainLayout()
        self.mux_setting_groupBox.setLayout(self.mux_setting_layout)
        self.job_queue_groupBox.setLayout(self.job_queue_layout)
        self.setup_mux_tools_layout_first_row()
        self.setup_mux_tools_layout_second_row()
        self.setup_mux_setting_layout()
        self.setLayout(self.MainLayout)

    # noinspection PyAttributeOutsideInit
    def create_widgets(self):
        self.MainLayout = QVBoxLayout()
        self.mux_setting_groupBox = QGroupBox(self)
        self.job_queue_groupBox = QGroupBox(self)
        self.mux_setting_layout = QGridLayout()
        self.job_queue_layout = JobQueueLayout()
        self.destination_path_label = QLabel()
        self.destination_path_lineEdit = QLineEdit()
        self.destination_path_button = QPushButton()
        self.only_keep_those_audios_checkBox = OnlyKeepThoseAudiosCheckBox()
        self.only_keep_those_subtitles_checkBox = OnlyKeepThoseSubtitlesCheckBox()
        self.only_keep_those_audios_multi_choose_comboBox = AudioTracksCheckableComboBox()
        self.only_keep_those_subtitles_multi_choose_comboBox = SubtitleTracksCheckableComboBox()
        self.make_this_audio_default_checkBox = MakeThisAudioDefaultCheckBox()
        self.make_this_subtitle_default_checkBox = MakeThisSubtitleDefaultCheckBox()
        self.make_this_audio_default_comboBox = MakeThisTrackDefaultComboBox()
        self.make_this_subtitle_default_comboBox = MakeThisTrackDefaultComboBox()
        self.abort_on_errors_checkBox = QCheckBox()
        self.discard_old_attachments_checkBox = QCheckBox()
        self.keep_log_file_checkBox = QCheckBox()
        self.control_queue_button = ControlQueueButton()
        self.clear_job_queue_button = QPushButton()
        self.mux_tools_layout_first_row = QHBoxLayout()
        self.mux_tools_layout_second_row = QHBoxLayout()
        self.job_queue_tools_layout = QHBoxLayout()

    def setup_mux_setting_layout(self):
        self.mux_setting_layout.addWidget(self.destination_path_label, 0, 0)
        self.mux_setting_layout.addWidget(self.destination_path_lineEdit, 0, 1)
        self.mux_setting_layout.addWidget(self.destination_path_button, 0, 2)
        self.mux_setting_layout.addWidget(self.only_keep_those_audios_checkBox, 1, 0)
        self.mux_setting_layout.addWidget(self.only_keep_those_subtitles_checkBox, 2, 0)
        self.mux_setting_layout.addLayout(self.mux_tools_layout_first_row, 1, 1)
        self.mux_setting_layout.addLayout(self.mux_tools_layout_second_row, 2, 1)

    def setup_mux_tools_layout_first_row(self):
        self.mux_tools_layout_first_row.addWidget(self.only_keep_those_audios_multi_choose_comboBox, 2)
        self.mux_tools_layout_first_row.addWidget(self.make_this_audio_default_checkBox, 1)
        self.mux_tools_layout_first_row.addWidget(self.make_this_audio_default_comboBox, 2)
        self.mux_tools_layout_first_row.addWidget(self.abort_on_errors_checkBox, 1)
        self.mux_tools_layout_first_row.addWidget(self.keep_log_file_checkBox)

    def setup_mux_tools_layout_second_row(self):
        self.mux_tools_layout_second_row.addWidget(self.only_keep_those_subtitles_multi_choose_comboBox, 2)
        self.mux_tools_layout_second_row.addWidget(self.make_this_subtitle_default_checkBox, 1)
        self.mux_tools_layout_second_row.addWidget(self.make_this_subtitle_default_comboBox, 2)
        self.mux_tools_layout_second_row.addWidget(self.control_queue_button, 1)
        self.mux_tools_layout_second_row.addWidget(self.clear_job_queue_button, 1)

    def setup_clear_job_queue_button(self):
        self.clear_job_queue_button.setText("Clear All")
        self.clear_job_queue_button.setIcon(GlobalFiles.CleanIcon)
        self.clear_job_queue_button.setDisabled(True)

    def setup_keep_log_file_checkBox(self):
        self.keep_log_file_checkBox.setText("Keep Log File")
        self.keep_log_file_checkBox.setToolTip("log file will located in the source folder after finished muxing")

    def setup_discard_old_attachments_checkBox(self):
        self.discard_old_attachments_checkBox.setText("Discard Old Attachments ")

    def setup_abort_on_errors_checkBox(self):
        self.abort_on_errors_checkBox.setText("Abort On Errors")
        self.abort_on_errors_checkBox.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred)

    def setup_destination_path_button(self):
        self.destination_path_button.setIcon(GlobalFiles.SelectFolderIcon)

    def setup_destination_path_lineEdit(self):
        self.destination_path_lineEdit.setPlaceholderText("Enter Destination Folder Path")
        self.destination_path_lineEdit.setClearButtonEnabled(True)

    def setup_destination_path_label(self):
        self.destination_path_label.setText("Videos Destination Folder :")

    def setup_MainLayout(self):
        self.MainLayout.addWidget(self.mux_setting_groupBox)
        self.MainLayout.addWidget(self.job_queue_groupBox)

    def setup_job_queue_groupBox(self):
        self.job_queue_groupBox.setTitle("Job Queue")

    def setup_mux_setting_groupBox(self):
        self.mux_setting_groupBox.setTitle("Mux Setting")

    def paintEvent(self, event: QPaintEvent):
        self.update_widgets_size()
        super().paintEvent(event)

    def resizeEvent(self, event: QResizeEvent):
        self.job_queue_layout.update_layout()
        super().resizeEvent(event)

    def update_widgets_size(self):
        self.only_keep_those_subtitles_multi_choose_comboBox.resize(
            self.only_keep_those_audios_multi_choose_comboBox.width(),
            self.only_keep_those_audios_multi_choose_comboBox.height(),
        )

        self.make_this_subtitle_default_checkBox.resize(
            self.make_this_audio_default_checkBox.width(),
            self.make_this_audio_default_checkBox.height(),
        )
        self.make_this_subtitle_default_checkBox.move(
            self.make_this_audio_default_checkBox.x(),
            self.make_this_subtitle_default_checkBox.y(),
        )

        self.make_this_subtitle_default_comboBox.resize(
            self.make_this_audio_default_comboBox.width(),
            self.make_this_audio_default_comboBox.height(),
        )
        self.make_this_subtitle_default_comboBox.move(
            self.make_this_audio_default_comboBox.x(),
            self.make_this_subtitle_default_comboBox.y(),
        )

        self.control_queue_button.move(
            self.abort_on_errors_checkBox.x(),
            self.control_queue_button.y(),
        )

        self.clear_job_queue_button.move(
            self.control_queue_button.x() + self.control_queue_button.width() + 5,
            self.clear_job_queue_button.y(),
        )

    def open_select_destination_folder_dialog(self):
        temp_folder_path = QFileDialog.getExistingDirectory(self, caption="Choose Destination Folder",
                                                            dir=GlobalSetting.LAST_DIRECTORY_PATH, )
        if temp_folder_path == "" or temp_folder_path.isspace():
            return
        elif Path(temp_folder_path) == Path(GlobalSetting.VIDEO_SOURCE_PATH):
            invalid_dialog = InvalidPathDialog(
                error_message="Source and destination videos can't be in the same folder")
            invalid_dialog.execute()
            return
        else:
            self.destination_path_lineEdit.setText(str(Path(temp_folder_path)))
            GlobalSetting.LAST_DIRECTORY_PATH = self.destination_path_lineEdit.text()
            GlobalSetting.DESTINATION_FOLDER_PATH = self.destination_path_lineEdit.text()

    def check_destination_path(self):
        temp_destination_path = self.destination_path_lineEdit.text()
        try:
            if temp_destination_path == "" or temp_destination_path.isspace():
                temp_destination_path = "[Empty Path]"
                raise Exception(
                    "[WinError 998] Empty path is Not a valid path : " + temp_destination_path)
            # check if system is windows so path must have # SOME_LETTER:\
            if os.name == 'nt':
                if temp_destination_path[1:3] != ":\\" and self.destination_path_lineEdit.text()[
                                                           1:3] != ":/":
                    raise Exception("[WinError 999] Not a valid path : " + temp_destination_path)
            makedirs(temp_destination_path, exist_ok=True)
            ## test if i can write into this path:
            test_file_name = str(time.time()) + ".txt"
            test_file_name_absolute = os.path.join(Path(temp_destination_path), Path(test_file_name))
            try:
                with open(test_file_name_absolute, 'w+') as test_file:
                    test_file.write("Test")
                os.remove(test_file_name_absolute)
            except Exception as e:
                write_to_log_file(e)
                invaild_dialog = InvalidPathDialog(window_title="Permission Denied",
                                                   error_message="MKV Muxing Batch GUI lacks write "
                                                                 "permissions on Destination folder")
                invaild_dialog.execute()
                self.destination_path_lineEdit.setText(GlobalSetting.DESTINATION_FOLDER_PATH)
                return False
        except Exception as e:
            write_to_log_file(e)
            error_message = ""
            if temp_destination_path == "[Empty Path]":
                error_message = "Enter a valid destination path"
            else:
                error_message = temp_destination_path + "\nisn't a valid path!"
            invalid_dialog = InvalidPathDialog(error_message=error_message)
            invalid_dialog.execute()
            self.destination_path_lineEdit.setText(GlobalSetting.DESTINATION_FOLDER_PATH)
            return False
        if Path(temp_destination_path) == Path(GlobalSetting.VIDEO_SOURCE_PATH):
            invalid_dialog = InvalidPathDialog(
                error_message="Source and destination videos can't be in the same folder")
            invalid_dialog.execute()
            self.destination_path_lineEdit.setText(GlobalSetting.DESTINATION_FOLDER_PATH)
            return False
        GlobalSetting.DESTINATION_FOLDER_PATH = temp_destination_path
        return True

    def setup_tool_tip_hint(self):
        self.only_keep_those_subtitles_multi_choose_comboBox.set_tool_tip_hint()
        self.only_keep_those_audios_multi_choose_comboBox.set_tool_tip_hint()
        self.make_this_subtitle_default_checkBox.set_tool_tip_hint_no_check()
        self.make_this_audio_default_checkBox.set_tool_tip_hint_no_check()

    def add_to_queue_button_clicked(self):
        self.job_queue_layout.setup_queue()
        self.enable_muxing_setting()
        if not GlobalSetting.JOB_QUEUE_EMPTY:
            self.disable_editable_widgets()
            self.control_queue_button.set_state_start_multiplexing()
            self.clear_job_queue_button.setDisabled(False)
            change_global_LogFilePath()
        else:
            self.enable_editable_widgets()
            self.setup_enable_options_for_mkv_only_options()

    def tab_clicked(self):
        self.job_queue_layout.show_necessary_table_columns()
        self.setup_enable_options_for_mkv_only_options()

    def setup_enable_options_for_mkv_only_options(self):
        if GlobalSetting.JOB_QUEUE_EMPTY:
            if GlobalSetting.VIDEO_SOURCE_MKV_ONLY:
                self.only_keep_those_audios_checkBox.setEnabled(True)
                self.only_keep_those_subtitles_checkBox.setEnabled(True)
                self.make_this_audio_default_checkBox.setEnabled(True)
                self.make_this_subtitle_default_checkBox.setEnabled(True)
                self.only_keep_those_audios_checkBox.setToolTip("")
                self.only_keep_those_subtitles_checkBox.setToolTip("")
                self.make_this_audio_default_comboBox.setToolTip("")
                self.make_this_subtitle_default_comboBox.setToolTip("")
                self.setup_tool_tip_hint()
            else:

                self.only_keep_those_subtitles_checkBox.setCheckState(Qt.Unchecked)
                self.only_keep_those_audios_checkBox.setCheckState(Qt.Unchecked)
                self.make_this_audio_default_checkBox.setCheckState(Qt.Unchecked)
                self.make_this_subtitle_default_checkBox.setCheckState(Qt.Unchecked)

                self.only_keep_those_audios_checkBox.setEnabled(False)
                self.only_keep_those_subtitles_checkBox.setEnabled(False)
                self.make_this_audio_default_checkBox.setEnabled(False)
                self.make_this_subtitle_default_checkBox.setEnabled(False)
                self.only_keep_those_audios_checkBox.setToolTip("<b>[Disabled]</b> Only works when video files "
                                                                "are Mkv only")
                self.only_keep_those_subtitles_checkBox.setToolTip("<b>[Disabled]</b> Only works when video files "
                                                                   "are Mkv only")

                self.make_this_audio_default_checkBox.setToolTip("<b>[Disabled]</b> Only works when video files "
                                                                 "are Mkv only")

                self.make_this_subtitle_default_checkBox.setToolTip("<b>[Disabled]</b> Only works when video files "
                                                                    "are Mkv only")
                self.make_this_audio_default_comboBox.setToolTip("<b>[Disabled]</b> Only works when video files "
                                                                 "are Mkv only")
                self.make_this_subtitle_default_comboBox.setToolTip("<b>[Disabled]</b> Only works when video files "
                                                                    "are Mkv only")
                self.only_keep_those_audios_multi_choose_comboBox.setToolTip(
                    "<b>[Disabled]</b> Only works when video files "
                    "are Mkv only")
                self.only_keep_those_subtitles_multi_choose_comboBox.setToolTip(
                    "<b>[Disabled]</b> Only works when video files "
                    "are Mkv only")

    def clear_job_queue_button_clicked(self):
        self.job_queue_layout.clear_queue()
        self.control_queue_button.set_state_add_to_queue()
        self.clear_job_queue_button.setDisabled(True)
        self.control_queue_button.setDisabled(False)
        self.enable_editable_widgets()
        self.enable_muxing_setting()
        self.setup_enable_options_for_mkv_only_options()
        self.update_task_bar_clear_signal.emit()

    def disable_editable_widgets(self):
        self.only_keep_those_subtitles_checkBox.setEnabled(False)
        self.only_keep_those_subtitles_multi_choose_comboBox.setEnabled(False)
        self.only_keep_those_audios_checkBox.setEnabled(False)
        self.only_keep_those_audios_multi_choose_comboBox.setEnabled(False)
        self.make_this_subtitle_default_checkBox.setEnabled(False)
        self.make_this_subtitle_default_comboBox.setEnabled(False)
        self.make_this_audio_default_checkBox.setEnabled(False)
        self.make_this_audio_default_comboBox.setEnabled(False)

    def enable_editable_widgets(self):
        self.only_keep_those_subtitles_checkBox.setEnabled(True)
        self.only_keep_those_subtitles_multi_choose_comboBox.setEnabled(
            self.only_keep_those_subtitles_checkBox.isChecked())
        self.only_keep_those_audios_checkBox.setEnabled(True)
        self.only_keep_those_audios_multi_choose_comboBox.setEnabled(self.only_keep_those_audios_checkBox.isChecked())
        self.make_this_subtitle_default_checkBox.setEnabled(True)
        self.make_this_subtitle_default_comboBox.setEnabled(self.make_this_subtitle_default_checkBox.isChecked())
        self.make_this_audio_default_checkBox.setEnabled(True)
        self.make_this_audio_default_comboBox.setEnabled(self.make_this_audio_default_checkBox.isChecked())

    def only_keep_those_audios_close_list(self):
        GlobalSetting.MUX_SETTING_ONLY_KEEP_THOSE_AUDIOS_LANGUAGES = self.only_keep_those_audios_multi_choose_comboBox.languages
        GlobalSetting.MUX_SETTING_ONLY_KEEP_THOSE_AUDIOS_TRACKS = self.only_keep_those_audios_multi_choose_comboBox.tracks

    def only_keep_those_subtitles_close_list(self):
        GlobalSetting.MUX_SETTING_ONLY_KEEP_THOSE_SUBTITLES_LANGUAGES = self.only_keep_those_subtitles_multi_choose_comboBox.languages
        GlobalSetting.MUX_SETTING_ONLY_KEEP_THOSE_SUBTITLES_TRACKS = self.only_keep_those_subtitles_multi_choose_comboBox.tracks

    def disable_make_this_subtitle_default_comboBox(self, state):
        self.make_this_subtitle_default_comboBox.setDisabled(state)
        if state:
            self.make_this_subtitle_default_comboBox.setCurrentIndex(-1)

    def disable_make_this_audio_default_comboBox(self, state):
        self.make_this_audio_default_comboBox.setDisabled(state)
        if state:
            self.make_this_audio_default_comboBox.setCurrentIndex(-1)

    def make_this_audio_default_comboBox_text_changed(self):
        GlobalSetting.MUX_SETTING_MAKE_THIS_AUDIO_DEFAULT_TRACK = str(
            self.make_this_audio_default_comboBox.currentText())

    def make_this_subtitle_default_comboBox_text_changed(self):
        GlobalSetting.MUX_SETTING_MAKE_THIS_SUBTITLE_DEFAULT_TRACK = str(
            self.make_this_subtitle_default_comboBox.currentText())

    def update_task_bar_progress(self, new_progress):
        self.update_task_bar_progress_signal.emit(new_progress)

    def enable_muxing_setting(self):
        self.destination_path_lineEdit.setEnabled(True)
        self.destination_path_button.setEnabled(True)
        self.abort_on_errors_checkBox.setEnabled(True)
        self.keep_log_file_checkBox.setEnabled(True)

    def disable_muxing_setting(self):
        self.destination_path_lineEdit.setEnabled(False)
        self.destination_path_button.setEnabled(False)
        self.abort_on_errors_checkBox.setEnabled(False)
        self.keep_log_file_checkBox.setEnabled(False)

    @staticmethod
    def abort_on_errors_state_changed(state):
        GlobalSetting.MUX_SETTING_ABORT_ON_ERRORS = bool(state)

    @staticmethod
    def keep_log_file_state_changed(state):
        GlobalSetting.MUX_SETTING_KEEP_LOG_FILE = bool(state)

    def start_multiplexing_button_clicked(self):
        at_least_one_muxing_setting_has_been_selected = check_if_at_least_one_muxing_setting_has_been_selected()
        if at_least_one_muxing_setting_has_been_selected:
            destination_path_valid = self.check_destination_path()
            if destination_path_valid:
                self.setup_log_file()
                self.control_queue_button.set_state_pause_multiplexing()
                self.disable_muxing_setting()
                self.job_queue_layout.start_muxing()
                self.start_muxing_signal.emit()
                self.clear_job_queue_button.setDisabled(True)

    def pause_multiplexing_button_clicked(self):
        self.job_queue_layout.pause_muxing()
        self.control_queue_button.setDisabled(True)
        self.control_queue_button.set_state_pausing_multiplexing()

    def paused_done(self):
        self.control_queue_button.set_state_resume_multiplexing()
        self.clear_job_queue_button.setDisabled(False)
        self.control_queue_button.setDisabled(False)
        self.update_task_bar_paused_signal.emit()

    def cancel_done(self):
        self.disable_editable_widgets()
        self.enable_muxing_setting()
        self.control_queue_button.set_state_start_multiplexing()
        self.clear_job_queue_button.setDisabled(False)
        change_global_LogFilePath()

    def finished_all_jobs(self):
        self.enable_editable_widgets()
        self.enable_muxing_setting()
        self.setup_enable_options_for_mkv_only_options()
        self.control_queue_button.set_state_start_multiplexing()
        self.control_queue_button.setDisabled(True)
        self.clear_job_queue_button.setDisabled(False)
        self.update_task_bar_clear_signal.emit()
        GlobalSetting.JOB_QUEUE_EMPTY = True
        check_if_want_to_keep_log_file()

    def setup_log_file(self):
        if self.control_queue_button.state == "START":
            open(GlobalFiles.LogFilePath, 'w+').close()
示例#11
0
class ComparisonWidget(ToolWidget):
    def __init__(self, filename, image, parent=None):
        super(ComparisonWidget, self).__init__(parent)

        load_button = QPushButton(self.tr('Load reference image...'))
        self.comp_label = QLabel(self.tr('Comparison:'))
        self.normal_radio = QRadioButton(self.tr('Normal'))
        self.normal_radio.setToolTip(self.tr('Show reference (raw pixels)'))
        self.normal_radio.setChecked(True)
        self.difference_radio = QRadioButton(self.tr('Difference'))
        self.difference_radio.setToolTip(
            self.tr('Show evidence/reference difference'))
        self.ssim_radio = QRadioButton(self.tr('SSIM Map'))
        self.ssim_radio.setToolTip(self.tr('Structure similarity quality map'))
        self.butter_radio = QRadioButton(self.tr('Butteraugli'))
        self.butter_radio.setToolTip(
            self.tr('Butteraugli spatial changes heatmap'))
        self.gray_check = QCheckBox(self.tr('Grayscale'))
        self.gray_check.setToolTip(self.tr('Show desaturated output'))
        self.equalize_check = QCheckBox(self.tr('Equalized'))
        self.equalize_check.setToolTip(self.tr('Apply histogram equalization'))
        self.last_radio = self.normal_radio
        self.metric_button = QPushButton(self.tr('Compute metrics'))
        self.metric_button.setToolTip(
            self.tr('Image quality assessment metrics'))

        self.evidence = image
        self.reference = self.difference = self.ssim_map = self.butter_map = None
        basename = os.path.basename(filename)
        self.evidence_viewer = ImageViewer(
            self.evidence, None, self.tr('Evidence: {}'.format(basename)))
        self.reference_viewer = ImageViewer(np.full_like(self.evidence, 127),
                                            None, self.tr('Reference'))

        self.table_widget = QTableWidget(21, 3)
        self.table_widget.setHorizontalHeaderLabels(
            [self.tr('Metric'),
             self.tr('Value'),
             self.tr('Better')])
        self.table_widget.setItem(0, 0, QTableWidgetItem(self.tr('RMSE')))
        self.table_widget.setItem(
            0, 2, QTableWidgetItem(QIcon('gui/icons/low.svg'), '(0)'))
        self.table_widget.item(0, 0).setToolTip(
            self.
            tr('Root Mean Square Error (RMSE) is commonly used to compare \n'
               'the difference between the reference and evidence images \n'
               'by directly computing the variation in pixel values. \n'
               'The combined image is close to the reference image when \n'
               'RMSE value is zero. RMSE is a good indicator of the spectral \n'
               'quality of the reference image.'))
        self.table_widget.setItem(1, 0, QTableWidgetItem(self.tr('SAM')))
        self.table_widget.setItem(
            1, 2, QTableWidgetItem(QIcon('gui/icons/low.svg'), '(0)'))
        self.table_widget.item(1, 0).setToolTip(
            self.
            tr('It computes the spectral angle between the pixel, vector of the \n'
               'evidence image and reference image. It is worked out in either \n'
               'degrees or radians. It is performed on a pixel-by-pixel base. \n'
               'A SAM equal to zero denotes the absence of spectral distortion.'
               ))
        self.table_widget.setItem(2, 0, QTableWidgetItem(self.tr('ERGAS')))
        self.table_widget.setItem(
            2, 2, QTableWidgetItem(QIcon('gui/icons/low.svg'), '(0)'))
        self.table_widget.item(2, 0).setToolTip(
            self.
            tr('It is used to compute the quality of reference image in terms \n'
               'of normalized average error of each band of the reference image. \n'
               'Increase in the value of ERGAS indicates distortion in the \n'
               'reference image, lower value of ERGAS indicates that it is \n'
               'similar to the reference image.'))
        self.table_widget.setItem(3, 0, QTableWidgetItem(self.tr('MB')))
        self.table_widget.setItem(
            3, 2, QTableWidgetItem(QIcon('gui/icons/low.svg'), '(0)'))
        self.table_widget.item(3, 0).setToolTip(
            self.
            tr('Mean Bias is the difference between the mean of the evidence \n'
               'image and reference image. The ideal value is zero and indicates \n'
               'that the evidence and reference images are similar. Mean value \n'
               'refers to the grey level of pixels in an image.'))
        self.table_widget.setItem(4, 0, QTableWidgetItem(self.tr('PFE')))
        self.table_widget.setItem(
            4, 2, QTableWidgetItem(QIcon('gui/icons/low.svg'), '(0)'))
        self.table_widget.item(4, 0).setToolTip(
            self.
            tr('It computes the norm of the difference between the corresponding \n'
               'pixels of the reference and fused image to the norm of the reference \n'
               'image. When the calculated value is zero, it indicates that both the \n'
               'reference and fused images are similar and value will be increased \n'
               'when the merged image is not similar to the reference image.'))
        self.table_widget.setItem(5, 0, QTableWidgetItem(self.tr('PSNR')))
        self.table_widget.setItem(
            5, 2,
            QTableWidgetItem(QIcon('gui/icons/high.svg'),
                             '(+' + u'\u221e' + ')'))
        self.table_widget.item(5, 0).setToolTip(
            self.
            tr('It is widely used metric it is computed by the number of gray levels \n'
               'in the image divided by the corresponding pixels in the evidence and \n'
               'the reference images. When the value is high, both images are similar.'
               ))
        self.table_widget.setItem(6, 0, QTableWidgetItem(self.tr('PSNR-B')))
        self.table_widget.setItem(
            6, 2,
            QTableWidgetItem(QIcon('gui/icons/high.svg'),
                             '(+' + u'\u221e' + ')'))
        self.table_widget.item(6, 0).setToolTip(
            self.tr('PSNR with Blocking Effect Factor.'))
        self.table_widget.setItem(7, 0, QTableWidgetItem(self.tr('SSIM')))
        self.table_widget.setItem(
            7, 2, QTableWidgetItem(QIcon('gui/icons/high.svg'), '(1)'))
        self.table_widget.item(7, 0).setToolTip(
            self.
            tr('SSIM is used to compare the local patterns of pixel intensities between \n'
               ' the reference and fused images. The range varies between -1 to 1. \n'
               'The value 1 indicates the reference and fused images are similar.'
               ))
        self.table_widget.setItem(8, 0, QTableWidgetItem(self.tr('MS-SSIM')))
        self.table_widget.setItem(
            8, 2, QTableWidgetItem(QIcon('gui/icons/high.svg'), '(1)'))
        self.table_widget.item(8, 0).setToolTip(
            self.tr('Multiscale version of SSIM.'))
        self.table_widget.setItem(9, 0, QTableWidgetItem(self.tr('RASE')))
        self.table_widget.setItem(
            9, 2, QTableWidgetItem(QIcon('gui/icons/low.svg'), '(0)'))
        self.table_widget.item(9, 0).setToolTip(
            self.tr('Relative average spectral error'))
        self.table_widget.setItem(10, 0, QTableWidgetItem(self.tr('SCC')))
        self.table_widget.setItem(
            10, 2, QTableWidgetItem(QIcon('gui/icons/high.svg'), '(1)'))
        self.table_widget.item(10, 0).setToolTip(
            self.tr('Spatial Correlation Coefficient'))
        self.table_widget.setItem(11, 0, QTableWidgetItem(self.tr('UQI')))
        self.table_widget.setItem(
            11, 2, QTableWidgetItem(QIcon('gui/icons/high.svg'), '(1)'))
        self.table_widget.item(11, 0).setToolTip(
            self.tr('Universal Image Quality Index'))
        self.table_widget.setItem(12, 0, QTableWidgetItem(self.tr('VIF-P')))
        self.table_widget.setItem(
            12, 2, QTableWidgetItem(QIcon('gui/icons/high.svg'), '(1)'))
        self.table_widget.item(12, 0).setToolTip(
            self.tr('Pixel-based Visual Information Fidelity'))
        self.table_widget.setItem(13, 0,
                                  QTableWidgetItem(self.tr('SSIMulacra')))
        self.table_widget.setItem(
            13, 2, QTableWidgetItem(QIcon('gui/icons/low.svg'), '(0)'))
        self.table_widget.item(13, 0).setToolTip(
            self.tr('Structural SIMilarity Unveiling Local '
                    'And Compression Related Artifacts'))
        self.table_widget.setItem(14, 0,
                                  QTableWidgetItem(self.tr('Butteraugli')))
        self.table_widget.setItem(
            14, 2, QTableWidgetItem(QIcon('gui/icons/low.svg'), '(0)'))
        self.table_widget.item(14, 0).setToolTip(
            self.tr('Estimate psychovisual error'))
        self.table_widget.setItem(15, 0,
                                  QTableWidgetItem(self.tr('Correlation')))
        self.table_widget.setItem(
            15, 2, QTableWidgetItem(QIcon('gui/icons/high.svg'), '(1)'))
        self.table_widget.item(15,
                               0).setToolTip(self.tr('Histogram correlation'))
        self.table_widget.setItem(16, 0,
                                  QTableWidgetItem(self.tr('Chi-Square')))
        self.table_widget.setItem(
            16, 2, QTableWidgetItem(QIcon('gui/icons/low.svg'), '(0)'))
        self.table_widget.item(16,
                               0).setToolTip(self.tr('Histogram Chi-Square'))
        self.table_widget.setItem(17, 0,
                                  QTableWidgetItem(self.tr('Chi-Square 2')))
        self.table_widget.setItem(
            17, 2, QTableWidgetItem(QIcon('gui/icons/low.svg'), '(0)'))
        self.table_widget.item(17,
                               0).setToolTip(self.tr('Alternative Chi-Square'))
        self.table_widget.setItem(18, 0,
                                  QTableWidgetItem(self.tr('Intersection')))
        self.table_widget.setItem(
            18, 2,
            QTableWidgetItem(QIcon('gui/icons/high.svg'),
                             '(+' + u'\u221e' + ')'))
        self.table_widget.item(18,
                               0).setToolTip(self.tr('Histogram intersection'))
        self.table_widget.setItem(19, 0,
                                  QTableWidgetItem(self.tr('Hellinger')))
        self.table_widget.setItem(
            19, 2, QTableWidgetItem(QIcon('gui/icons/low.svg'), '(0)'))
        self.table_widget.item(19, 0).setToolTip(
            self.tr('Histogram Hellinger distance'))
        self.table_widget.setItem(20, 0,
                                  QTableWidgetItem(self.tr('Divergence')))
        self.table_widget.setItem(
            20, 2, QTableWidgetItem(QIcon('gui/icons/low.svg'), '(0)'))
        self.table_widget.item(20, 0).setToolTip(
            self.tr('Kullback-Leibler divergence'))

        for i in range(self.table_widget.rowCount()):
            modify_font(self.table_widget.item(i, 0), bold=True)
        self.table_widget.setSelectionMode(QAbstractItemView.SingleSelection)
        self.table_widget.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.table_widget.resizeColumnsToContents()
        self.table_widget.setMaximumWidth(250)
        self.table_widget.setAlternatingRowColors(True)
        self.stopped = False

        self.comp_label.setEnabled(False)
        self.normal_radio.setEnabled(False)
        self.difference_radio.setEnabled(False)
        self.ssim_radio.setEnabled(False)
        self.butter_radio.setEnabled(False)
        self.gray_check.setEnabled(False)
        self.equalize_check.setEnabled(False)
        self.metric_button.setEnabled(False)
        self.table_widget.setEnabled(False)

        load_button.clicked.connect(self.load)
        self.normal_radio.clicked.connect(self.change)
        self.difference_radio.clicked.connect(self.change)
        self.butter_radio.clicked.connect(self.change)
        self.gray_check.stateChanged.connect(self.change)
        self.equalize_check.stateChanged.connect(self.change)
        self.ssim_radio.clicked.connect(self.change)
        self.evidence_viewer.viewChanged.connect(
            self.reference_viewer.changeView)
        self.reference_viewer.viewChanged.connect(
            self.evidence_viewer.changeView)
        self.metric_button.clicked.connect(self.metrics)

        top_layout = QHBoxLayout()
        top_layout.addWidget(load_button)
        top_layout.addStretch()
        top_layout.addWidget(self.comp_label)
        top_layout.addWidget(self.normal_radio)
        top_layout.addWidget(self.difference_radio)
        top_layout.addWidget(self.ssim_radio)
        top_layout.addWidget(self.butter_radio)
        top_layout.addWidget(self.gray_check)
        top_layout.addWidget(self.equalize_check)

        metric_layout = QVBoxLayout()
        index_label = QLabel(self.tr('Image Quality Assessment'))
        index_label.setAlignment(Qt.AlignCenter)
        modify_font(index_label, bold=True)
        metric_layout.addWidget(index_label)
        metric_layout.addWidget(self.table_widget)
        metric_layout.addWidget(self.metric_button)

        center_layout = QHBoxLayout()
        center_layout.addWidget(self.evidence_viewer)
        center_layout.addWidget(self.reference_viewer)
        center_layout.addLayout(metric_layout)

        main_layout = QVBoxLayout()
        main_layout.addLayout(top_layout)
        main_layout.addLayout(center_layout)
        self.setLayout(main_layout)

    def load(self):
        filename, basename, reference = load_image(self)
        if filename is None:
            return
        if reference.shape != self.evidence.shape:
            QMessageBox.critical(
                self, self.tr('Error'),
                self.tr('Evidence and reference must have the same size!'))
            return
        self.reference = reference
        self.reference_viewer.set_title(
            self.tr('Reference: {}'.format(basename)))
        self.difference = norm_mat(cv.absdiff(self.evidence, self.reference))

        self.comp_label.setEnabled(True)
        self.normal_radio.setEnabled(True)
        self.difference_radio.setEnabled(True)
        self.ssim_radio.setEnabled(False)
        self.butter_radio.setEnabled(False)
        self.gray_check.setEnabled(True)
        self.equalize_check.setEnabled(True)
        self.metric_button.setEnabled(True)
        for i in range(self.table_widget.rowCount()):
            self.table_widget.setItem(i, 1, QTableWidgetItem())
        self.normal_radio.setChecked(True)
        self.table_widget.setEnabled(False)
        self.change()

    def change(self):
        if self.normal_radio.isChecked():
            result = self.reference
            self.gray_check.setEnabled(False)
            self.equalize_check.setEnabled(False)
            self.last_radio = self.normal_radio
        elif self.difference_radio.isChecked():
            result = self.difference
            self.gray_check.setEnabled(True)
            self.equalize_check.setEnabled(True)
            self.last_radio = self.difference_radio
        elif self.ssim_radio.isChecked():
            result = self.ssim_map
            self.gray_check.setEnabled(False)
            self.equalize_check.setEnabled(True)
            self.last_radio = self.ssim_radio
        elif self.butter_radio.isChecked():
            result = self.butter_map
            self.gray_check.setEnabled(True)
            self.equalize_check.setEnabled(False)
            self.last_radio = self.butter_radio
        else:
            self.last_radio.setChecked(True)
            return
        if self.equalize_check.isChecked():
            result = equalize_img(result)
        if self.gray_check.isChecked():
            result = desaturate(result)
        self.reference_viewer.update_original(result)

    def metrics(self):
        progress = QProgressDialog(self.tr('Computing metrics...'),
                                   self.tr('Cancel'), 1,
                                   self.table_widget.rowCount(), self)
        progress.canceled.connect(self.cancel)
        progress.setWindowModality(Qt.WindowModal)
        img1 = cv.cvtColor(self.evidence, cv.COLOR_BGR2GRAY)
        img2 = cv.cvtColor(self.reference, cv.COLOR_BGR2GRAY)
        x = img1.astype(np.float64)
        y = img2.astype(np.float64)

        rmse = self.rmse(x, y)
        progress.setValue(1)
        if self.stopped:
            return
        sam = sewar.sam(img1, img2)
        progress.setValue(2)
        if self.stopped:
            return
        ergas = sewar.ergas(img1, img2)
        progress.setValue(3)
        if self.stopped:
            return
        mb = self.mb(x, y)
        progress.setValue(4)
        if self.stopped:
            return
        pfe = self.pfe(x, y)
        progress.setValue(5)
        if self.stopped:
            return
        psnr = self.psnr(x, y)
        progress.setValue(6)
        if self.stopped:
            return
        try:
            psnrb = sewar.psnrb(img1, img2)
        except NameError:
            # FIXME: C'\`e un bug in psnrb (https://github.com/andrewekhalel/sewar/issues/17)
            psnrb = 0
        progress.setValue(7)
        if self.stopped:
            return
        ssim, self.ssim_map = self.ssim(x, y)
        progress.setValue(8)
        if self.stopped:
            return
        mssim = sewar.msssim(img1, img2).real
        progress.setValue(9)
        if self.stopped:
            return
        rase = sewar.rase(img1, img2)
        progress.setValue(10)
        if self.stopped:
            return
        scc = sewar.scc(img1, img2)
        progress.setValue(11)
        if self.stopped:
            return
        uqi = sewar.uqi(img1, img2)
        progress.setValue(12)
        if self.stopped:
            return
        vifp = sewar.vifp(img1, img2)
        progress.setValue(13)
        if self.stopped:
            return
        ssimul = self.ssimul(img1, img2)
        progress.setValue(14)
        if self.stopped:
            return
        butter, self.butter_map = self.butter(img1, img2)
        progress.setValue(15)
        if self.stopped:
            return

        sizes = [256, 256, 256]
        ranges = [0, 256] * 3
        channels = [0, 1, 2]
        hist1 = cv.calcHist([self.evidence], channels, None, sizes, ranges)
        hist2 = cv.calcHist([self.reference], channels, None, sizes, ranges)
        correlation = cv.compareHist(hist1, hist2, cv.HISTCMP_CORREL)
        progress.setValue(16)
        if self.stopped:
            return
        chi_square = cv.compareHist(hist1, hist2, cv.HISTCMP_CHISQR)
        progress.setValue(17)
        if self.stopped:
            return
        chi_square2 = cv.compareHist(hist1, hist2, cv.HISTCMP_CHISQR_ALT)
        progress.setValue(18)
        if self.stopped:
            return
        intersection = cv.compareHist(hist1, hist2, cv.HISTCMP_INTERSECT)
        progress.setValue(19)
        if self.stopped:
            return
        hellinger = cv.compareHist(hist1, hist2, cv.HISTCMP_HELLINGER)
        progress.setValue(20)
        if self.stopped:
            return
        divergence = cv.compareHist(hist1, hist2, cv.HISTCMP_KL_DIV)
        progress.setValue(21)

        self.table_widget.setItem(0, 1,
                                  QTableWidgetItem('{:.2f}'.format(rmse)))
        self.table_widget.setItem(1, 1, QTableWidgetItem('{:.4f}'.format(sam)))
        self.table_widget.setItem(2, 1,
                                  QTableWidgetItem('{:.2f}'.format(ergas)))
        self.table_widget.setItem(3, 1, QTableWidgetItem('{:.4f}'.format(mb)))
        self.table_widget.setItem(4, 1, QTableWidgetItem('{:.2f}'.format(pfe)))
        if psnr > 0:
            self.table_widget.setItem(
                5, 1, QTableWidgetItem('{:.2f} dB'.format(psnr)))
        else:
            self.table_widget.setItem(
                5, 1, QTableWidgetItem('+' + u'\u221e' + ' dB'))
        self.table_widget.setItem(6, 1,
                                  QTableWidgetItem('{:.2f}'.format(psnrb)))
        self.table_widget.setItem(7, 1,
                                  QTableWidgetItem('{:.4f}'.format(ssim)))
        self.table_widget.setItem(8, 1,
                                  QTableWidgetItem('{:.4f}'.format(mssim)))
        self.table_widget.setItem(9, 1,
                                  QTableWidgetItem('{:.2f}'.format(rase)))
        self.table_widget.setItem(10, 1,
                                  QTableWidgetItem('{:.4f}'.format(scc)))
        self.table_widget.setItem(11, 1,
                                  QTableWidgetItem('{:.4f}'.format(uqi)))
        self.table_widget.setItem(12, 1,
                                  QTableWidgetItem('{:.4f}'.format(vifp)))
        self.table_widget.setItem(13, 1,
                                  QTableWidgetItem('{:.4f}'.format(ssimul)))
        self.table_widget.setItem(14, 1,
                                  QTableWidgetItem('{:.2f}'.format(butter)))
        self.table_widget.setItem(
            15, 1, QTableWidgetItem('{:.2f}'.format(correlation)))
        self.table_widget.setItem(
            16, 1, QTableWidgetItem('{:.2f}'.format(chi_square)))
        self.table_widget.setItem(
            17, 1, QTableWidgetItem('{:.2f}'.format(chi_square2)))
        self.table_widget.setItem(
            18, 1, QTableWidgetItem('{:.2f}'.format(intersection)))
        self.table_widget.setItem(19, 1,
                                  QTableWidgetItem('{:.2f}'.format(hellinger)))
        self.table_widget.setItem(
            20, 1, QTableWidgetItem('{:.2f}'.format(divergence)))
        self.table_widget.resizeColumnsToContents()
        self.table_widget.setEnabled(True)
        self.metric_button.setEnabled(False)
        self.ssim_radio.setEnabled(True)
        self.butter_radio.setEnabled(True)

    def cancel(self):
        self.stopped = True

    @staticmethod
    def rmse(x, y):
        return np.sqrt(np.mean(np.square(x - y)))

    @staticmethod
    def mb(x, y):
        mx = np.mean(x)
        my = np.mean(y)
        return (mx - my) / mx

    @staticmethod
    def pfe(x, y):
        return np.linalg.norm(x - y) / np.linalg.norm(x) * 100

    @staticmethod
    def ssim(x, y):
        c1 = 6.5025
        c2 = 58.5225
        k = (11, 11)
        s = 1.5
        x2 = x**2
        y2 = y**2
        xy = x * y
        mu_x = cv.GaussianBlur(x, k, s)
        mu_y = cv.GaussianBlur(y, k, s)
        mu_x2 = mu_x**2
        mu_y2 = mu_y**2
        mu_xy = mu_x * mu_y
        s_x2 = cv.GaussianBlur(x2, k, s) - mu_x2
        s_y2 = cv.GaussianBlur(y2, k, s) - mu_y2
        s_xy = cv.GaussianBlur(xy, k, s) - mu_xy
        t1 = 2 * mu_xy + c1
        t2 = 2 * s_xy + c2
        t3 = t1 * t2
        t1 = mu_x2 + mu_y2 + c1
        t2 = s_x2 + s_y2 + c2
        t1 *= t2
        ssim_map = cv.divide(t3, t1)
        ssim = cv.mean(ssim_map)[0]
        return ssim, 255 - norm_mat(ssim_map, to_bgr=True)

    @staticmethod
    def corr(x, y):
        return np.corrcoef(x, y)[0, 1]

    @staticmethod
    def psnr(x, y):
        k = np.mean(np.square(x - y))
        if k == 0:
            return -1
        return 20 * math.log10((255**2) / k)

    @staticmethod
    def butter(x, y):
        try:
            exe = butter_exe()
            if exe is None:
                raise FileNotFoundError
            temp_dir = QTemporaryDir()
            if temp_dir.isValid():
                filename1 = os.path.join(temp_dir.path(), 'img1.png')
                cv.imwrite(filename1, x)
                filename2 = os.path.join(temp_dir.path(), 'img2.png')
                cv.imwrite(filename2, y)
                filename3 = os.path.join(temp_dir.path(), 'map.ppm')
                p = run([exe, filename1, filename2, filename3], stdout=PIPE)
                value = float(p.stdout)
                heatmap = cv.imread(filename3, cv.IMREAD_COLOR)
                return value, heatmap
        except FileNotFoundError:
            return -1, cv.cvtColor(np.full_like(x, 127), cv.COLOR_GRAY2BGR)

    @staticmethod
    def ssimul(x, y):
        try:
            exe = ssimul_exe()
            if exe is None:
                raise FileNotFoundError
            temp_dir = QTemporaryDir()
            if temp_dir.isValid():
                filename1 = os.path.join(temp_dir.path(), 'img1.png')
                cv.imwrite(filename1, x)
                filename2 = os.path.join(temp_dir.path(), 'img2.png')
                cv.imwrite(filename2, y)
                p = run([exe, filename1, filename2], stdout=PIPE)
                value = float(p.stdout)
                return value
        except FileNotFoundError:
            return -1
示例#12
0
class MainView(QMainWindow):
    applicationClosed = Signal()

    def __init__(self):
        super().__init__()

        menuBar = self.menuBar()
        menuBar.addMenu(self.createFileMenu())
        menuBar.addMenu(self.createSettingsMenu())
        menuBar.addMenu(self.createHelpMenu())

        self.statusBar().showMessage("Ready")

        connectionStatusGroupBox = self.createConnectionStatusGroup()
        positionIndicationGroupBox = self.createPositionIndicationGroup()
        turnTableControlGroupBox = self.createTurnTableControlGroup()

        mainWidgetLayout = QVBoxLayout()
        mainWidgetLayout.addWidget(connectionStatusGroupBox)
        mainWidgetLayout.addWidget(positionIndicationGroupBox)
        mainWidgetLayout.addWidget(turnTableControlGroupBox)

        mainWidgetGroup = QGroupBox()
        mainWidgetGroup.setLayout(mainWidgetLayout)

        self.setCentralWidget(mainWidgetGroup)


#------------------------------------------------------------------------------
# Interface Creation Helper Functions
#------------------------------------------------------------------------------
    def createFileMenu(self):
        self.loadZeroPositionDataAction = QAction("Load Zero Position", self)
        self.saveZeroPositionDataAction = QAction("Save Zero Position", self)
        exitAction = QAction(
            "E&xit", 
            self, 
            shortcut="Ctrl+Q", 
            statusTip="Exit the application", 
            triggered=self.close
        )

        fileMenu = QMenu("File")
        fileMenu.addAction(self.loadZeroPositionDataAction)
        fileMenu.addAction(self.saveZeroPositionDataAction)
        fileMenu.addSeparator()
        fileMenu.addAction(exitAction)

        return fileMenu


    def createSettingsMenu(self):
        self.applicationSettingsAction = QAction("Settings", self)

        settingsManagerMenu = QMenu("Settings")
        settingsManagerMenu.addAction(self.applicationSettingsAction)

        return settingsManagerMenu


    def createHelpMenu(self):
        helpViewAction = QAction("Help", self)
        aboutViewAction = QAction("About", self)

        helpMenu = QMenu("Help")
        helpMenu.addAction(helpViewAction)
        helpMenu.addAction(aboutViewAction)

        return helpMenu


    def createPositionIndicationGroup(self):
        self.currentPositionLineEdit = createReadOnlyLineEdit("+0.000")
        self.targetPositionLineEdit = createReadOnlyLineEdit("+0.000")
        self.positionErrorLineEdit = createReadOnlyLineEdit("+0.000")

        self.setCurrentPositionAsZeroButton = QPushButton("Set As Zero")        
        self.resetZeroPositionButton = QPushButton("Reset Zero")

        positionInformationLayout = QGridLayout()
        positionInformationLayout.addWidget(createPositionLabel("Current"), 0, 0)
        positionInformationLayout.addWidget(createPositionLabel("Target"), 0, 1)
        positionInformationLayout.addWidget(createPositionLabel("Error"), 0, 2)
        positionInformationLayout.addWidget(self.currentPositionLineEdit, 1, 0)
        positionInformationLayout.addWidget(self.targetPositionLineEdit, 1, 1)
        positionInformationLayout.addWidget(self.positionErrorLineEdit, 1, 2)
        positionInformationLayout.addWidget(self.setCurrentPositionAsZeroButton, 2, 0)
        positionInformationLayout.addWidget(self.resetZeroPositionButton, 3, 0)

        positionGroupBox = QGroupBox("Azimuth")
        positionGroupBox.setLayout(positionInformationLayout)

        return positionGroupBox


    def createTurnTableControlGroup(self):
        self.gotoPositionSpinBox = createDoubleSpinBox() 
        self.stepSizeSpinBox = createDoubleSpinBox()

        self.goPushButton = QPushButton("Go")
        self.goPushButton.setEnabled(False)
        self.goPushButton.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_G))

        self.stepPushButton = QPushButton("Step")
        self.stepPushButton.setEnabled(False)
        self.stepPushButton.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_S))

        self.enableSteppingCheckBox = QCheckBox("Enable Stepping")
        self.enableSteppingCheckBox.setEnabled(False)
        self.enableSteppingCheckBox.clicked.connect(self.stepPushButton.setEnabled)

        self.stopPushButton = QPushButton("STOP")
        self.stopPushButton.setEnabled(False)
        self.stopPushButton.pressed.connect(lambda: self.motorVoltageSlider.setValue(0.000))
        self.stopPushButton.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_T))

        turnTableVoltageControlGroup = self.createTurnTableVoltageControlGroup()

        turnTableControlGridLayout = QGridLayout()
        turnTableControlGridLayout.addWidget(QLabel("Go To Position:"), 0, 0)
        turnTableControlGridLayout.addWidget(self.gotoPositionSpinBox, 0, 1)
        turnTableControlGridLayout.addWidget(self.goPushButton, 0, 2)
        turnTableControlGridLayout.addWidget(QLabel("Step Size:"), 1, 0)
        turnTableControlGridLayout.addWidget(self.stepSizeSpinBox, 1, 1)
        turnTableControlGridLayout.addWidget(self.stepPushButton, 1, 2)
        turnTableControlGridLayout.addWidget(self.enableSteppingCheckBox, 1, 3)
        turnTableControlGridLayout.addWidget(self.stopPushButton, 2, 3)
        turnTableControlGridLayout.addWidget(turnTableVoltageControlGroup, 3, 0, 3, 4)

        turnTableGroupBox = QGroupBox("Turn Table Controls")
        turnTableGroupBox.setLayout(turnTableControlGridLayout)

        return turnTableGroupBox


    def createTurnTableVoltageControlGroup(self):
        self.motorVoltageSlider = DoubleSlider(orientation=Qt.Orientation.Horizontal)
        self.motorVoltageSlider.setTickPosition(QSlider.TickPosition.TicksBothSides)
        self.motorVoltageSlider.setTickInterval(200)
        self.motorVoltageSlider.setEnabled(False)

        self.motorVoltageSliderValueSpinBox = createDoubleSpinBox()
        self.motorVoltageSliderValueSpinBox.setEnabled(False)

        self.motorVoltageSliderValueSpinBox.valueChanged.connect(self.motorVoltageSlider.setValue)
        self.motorVoltageSlider.doubleValueChanged.connect(self.motorVoltageSliderValueSpinBox.setValue)

        self.resetVoltageButton = QPushButton("Reset")
        self.resetVoltageButton.setEnabled(False)
        self.resetVoltageButton.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_R))
        self.resetVoltageButton.pressed.connect(lambda: self.motorVoltageSlider.setValue(0.000))

        sliderGridLayout = QGridLayout()
        sliderGridLayout.addWidget(QLabel("Min Voltage"), 0, 0, 1, 1)
        sliderGridLayout.addWidget(QLabel("Max Voltage"), 0, 4, 1, 1)
        sliderGridLayout.addWidget(self.motorVoltageSlider, 1, 0, 1, 5)
        sliderGridLayout.addWidget(self.motorVoltageSliderValueSpinBox, 2, 1, 1, 1)
        sliderGridLayout.addWidget(self.resetVoltageButton, 2, 4, 1, 1)

        voltageControlGroupBox = QGroupBox()
        voltageControlGroupBox.setLayout(sliderGridLayout)

        return voltageControlGroupBox


    def createConnectionStatusGroup(self):
        connectionStatusGroup = QGroupBox("Connection Status")

        self.shaftEncoderConnectionStatusLineEdit = createReadOnlyLineEdit("Disconnected")
        self.motorControllerConnectionStatusLineEdit = createReadOnlyLineEdit("Disconnected")
        self.watchDogConnectionStatusLineEdit = createReadOnlyLineEdit("Disconnected")
        self.tcpServerConnectionStatusLineEdit = createReadOnlyLineEdit("Disconnected")

        self.connectButton = QPushButton("Connect")

        self.disconnectButton = QPushButton("Disconnect")
        self.disconnectButton.setEnabled(False)

        connectionButtonLayout = QHBoxLayout()
        connectionButtonLayout.addWidget(self.connectButton)
        connectionButtonLayout.addWidget(self.disconnectButton)

        connectionStatusLayout = QFormLayout()
        connectionStatusLayout.addRow(QLabel("Shaft Encoder: "), self.shaftEncoderConnectionStatusLineEdit)
        connectionStatusLayout.addRow(QLabel("Motor Controller: "), self.motorControllerConnectionStatusLineEdit)
        connectionStatusLayout.addRow(QLabel("Watchdog: "), self.watchDogConnectionStatusLineEdit)
        connectionStatusLayout.addRow(QLabel("TCP Server: "), self.tcpServerConnectionStatusLineEdit)
        connectionStatusLayout.addRow(connectionButtonLayout)

        connectionStatusGroup.setLayout(connectionStatusLayout)

        return connectionStatusGroup


    def closeEvent(self, event):
        messageBox = QMessageBox()
        messageBox.setText("The Turn Table is currently still running.")
        messageBox.setInformativeText("Are you sure you want to exit?")
        messageBox.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel)
        messageBox.setDefaultButton(QMessageBox.Cancel)
        
        response = messageBox.exec_()

        if response == QMessageBox.Ok:
            self.applicationClosed.emit()
            event.accept()
        elif response == QMessageBox.Cancel:
            event.ignore()


#------------------------------------------------------------------------------
# Callback functions for handling GUI events
#------------------------------------------------------------------------------
    def updatePositionLineEdits(self, newPosition):
        updatePositionLineEdit(self.currentPositionLineEdit, newPosition.current)
        updatePositionLineEdit(self.targetPositionLineEdit, newPosition.target)
        updatePositionLineEdit(self.positionErrorLineEdit, newPosition.error)


    def updateConnectionStatusLineEdits(self, shaftEncoder: bool, motorController: bool, watchdog: bool, tcpServer: bool):
        updateConnectionStatusLineEdit(self.shaftEncoderConnectionStatusLineEdit, shaftEncoder)
        updateConnectionStatusLineEdit(self.motorControllerConnectionStatusLineEdit, motorController)
        updateConnectionStatusLineEdit(self.watchDogConnectionStatusLineEdit, watchdog)
        updateConnectionStatusLineEdit(self.tcpServerConnectionStatusLineEdit, tcpServer)


    def toggleControls(self):
        toggleControlEnable(self.enableSteppingCheckBox)
        toggleControlEnable(self.stopPushButton)
        toggleControlEnable(self.goPushButton)
        toggleControlEnable(self.connectButton)
        toggleControlEnable(self.disconnectButton)
        toggleControlEnable(self.motorVoltageSlider)
        toggleControlEnable(self.motorVoltageSliderValueSpinBox)
        toggleControlEnable(self.resetVoltageButton)
示例#13
0
class MixedDistributionChart(QDialog):
    def __init__(self, parent=None, show_mode=True, toolbar=False, use_animation=False):
        flags = Qt.Window | Qt.WindowTitleHint | Qt.CustomizeWindowHint | Qt.WindowCloseButtonHint
        super().__init__(parent=parent, f=flags)
        self.setWindowTitle(self.tr("Mixed Distribution Chart"))
        self.figure = plt.figure(figsize=(4, 3))
        self.axes = self.figure.subplots()
        self.canvas = FigureCanvas(self.figure)
        self.toolbar = NavigationToolbar(self.canvas, self)
        self.main_layout = QGridLayout(self)
        self.main_layout.addWidget(self.toolbar, 0, 0, 1, 2)
        self.main_layout.addWidget(self.canvas, 1, 0, 1, 2)
        if not toolbar:
            self.toolbar.hide()
        self.supported_scales = [("log-linear", self.tr("Log-linear")),
                                 ("log", self.tr("Log")),
                                 ("phi", self.tr("φ")),
                                 ("linear", self.tr("Linear"))]

        self.scale_label = QLabel(self.tr("Scale"))
        self.scale_combo_box = QComboBox()
        self.scale_combo_box.addItems([name for key, name in self.supported_scales])
        self.scale_combo_box.currentIndexChanged.connect(self.update_chart)
        self.main_layout.addWidget(self.scale_label, 2, 0)
        self.main_layout.addWidget(self.scale_combo_box, 2, 1)
        self.interval_label = QLabel(self.tr("Interval [ms]"))
        self.interval_input = QSpinBox()
        self.interval_input.setRange(0, 10000)
        self.interval_input.setValue(30)
        self.interval_input.valueChanged.connect(self.update_animation)
        self.main_layout.addWidget(self.interval_label, 3, 0)
        self.main_layout.addWidget(self.interval_input, 3, 1)
        self.repeat_check_box = QCheckBox(self.tr("Repeat"))
        self.repeat_check_box.setChecked(False)
        self.repeat_check_box.stateChanged.connect(self.update_animation)
        self.save_button = QPushButton(self.tr("Save"))
        self.save_button.clicked.connect(self.save_animation)
        self.main_layout.addWidget(self.repeat_check_box, 4, 0)
        self.main_layout.addWidget(self.save_button, 4, 1)
        self.show_mode = show_mode
        self.animation = None
        self.last_model = None
        self.last_result = None

        if not use_animation:
            self.interval_label.setVisible(False)
            self.interval_input.setVisible(False)
            self.repeat_check_box.setVisible(False)
            self.save_button.setVisible(False)

        self.normal_msg = QMessageBox(parent=self)
        self.file_dialog = QFileDialog(parent=self)

    @property
    def scale(self) -> str:
        index = self.scale_combo_box.currentIndex()
        key, name = self.supported_scales[index]
        return key

    @property
    def transfer(self) -> typing.Callable:
        if self.scale == "log-linear":
            return lambda classes_φ: convert_φ_to_μm(classes_φ)
        elif self.scale == "log":
            return lambda classes_φ: np.log(convert_φ_to_μm(classes_φ))
        elif self.scale == "phi":
            return lambda classes_φ: classes_φ
        elif self.scale == "linear":
            return lambda classes_φ: convert_φ_to_μm(classes_φ)

    @property
    def xlabel(self) -> str:
        if self.scale == "log-linear":
            return self.tr("Grain-size [μm]")
        elif self.scale == "log":
            return self.tr("Ln(grain-size in μm)")
        elif self.scale == "phi":
            return self.tr("Grain-size [φ]")
        elif self.scale == "linear":
            return self.tr("Grain-size [μm]")

    @property
    def ylabel(self) -> str:
        return self.tr("Frequency")

    @property
    def xlog(self) -> bool:
        if self.scale == "log-linear":
            return True
        else:
            return False

    @property
    def interval(self) -> float:
        return self.interval_input.value()

    @property
    def repeat(self) -> bool:
        return self.repeat_check_box.isChecked()

    def show_demo(self):
        demo_model = get_demo_view_model()
        self.show_model(demo_model)

    def update_chart(self):
        if self.last_model is not None:
            self.show_model(self.last_model)
        elif self.last_result is not None:
            self.show_result(self.last_result)

    def update_animation(self):
        if self.last_result is not None:
            self.show_result(self.last_result)

    def show_model(self, model: SSUViewModel, quick=False):
        if self.animation is not None:
            self.animation._stop()
            self.animation = None
        if not quick:
            self.last_result = None

            self.last_model = model
            self.interval_label.setEnabled(False)
            self.interval_input.setEnabled(False)
            self.repeat_check_box.setEnabled(False)
            self.save_button.setEnabled(False)

            self.axes.clear()
            x = self.transfer(model.classes_φ)
            if self.xlog:
                self.axes.set_xscale("log")
            self.axes.set_title(model.title)
            self.axes.set_xlabel(self.xlabel)
            self.axes.set_ylabel(self.ylabel)
            self.target = self.axes.plot(x, model.target, c="#ffffff00", marker=".", ms=8, mfc="black", mew=0.0, label=self.tr("Target"))[0]
            # scatter can not be modified from the tool bar
            # self.target = self.axes.scatter(x, model.target, c="black", s=1)
            self.axes.set_xlim(x[0], x[-1])
            self.axes.set_ylim(0.0, round(np.max(model.target)*1.2, 2))
            self.mixed = self.axes.plot(x, model.mixed, c="black", label=self.tr("Mixed"))[0]
            self.components = [self.axes.plot(x, distribution*fraction, c=plt.get_cmap()(i), label=model.component_prefix+str(i+1))[0] for i, (distribution, fraction) in enumerate(zip(model.distributions, model.fractions))]
            if self.show_mode:
                modes = [self.transfer(model.classes_φ[np.unravel_index(np.argmax(distribution), distribution.shape)])  for distribution in model.distributions]
                colors = [plt.get_cmap()(i) for i in range(model.n_components)]
                self.vlines = self.axes.vlines(modes, 0.0, round(np.max(model.target)*1.2, 2), colors=colors)
            if model.n_components < 6:
                self.axes.legend(loc="upper left")
            self.figure.tight_layout()
            self.canvas.draw()
        else:
            self.mixed.set_ydata(model.mixed)
            for comp, distribution, fraction in zip(self.components, model.distributions, model.fractions):
                comp.set_ydata(distribution*fraction)
            if self.show_mode:
                modes = [self.transfer(model.classes_φ[np.unravel_index(np.argmax(distribution), distribution.shape)])  for distribution in model.distributions]
                self.vlines.set_offsets(modes)
            self.canvas.draw()

    def show_result(self, result: SSUResult):
        if self.animation is not None:
            self.animation._stop()
            self.animation = None
        self.last_model = None
        self.last_result = result
        self.interval_label.setEnabled(True)
        self.interval_input.setEnabled(True)
        self.repeat_check_box.setEnabled(True)
        self.save_button.setEnabled(True)

        models = iter(result.view_models)
        first = next(models)
        x = self.transfer(first.classes_φ)
        self.axes.cla()
        if self.xlog:
            self.axes.set_xscale("log")
        self.axes.set_title(first.title)
        self.axes.set_xlabel(self.xlabel)
        self.axes.set_ylabel(self.ylabel)
        self.target = self.axes.plot(x, first.target, c="#ffffff00", marker=".", ms=8, mfc="black", mew=0.0)[0]
        self.axes.set_xlim(x[0], x[-1])
        self.axes.set_ylim(0.0, round(np.max(first.target)*1.2, 2))
        self.figure.tight_layout()
        # self.canvas.draw()
        colors = [plt.get_cmap()(i) for i in range(first.n_components)]
        def init():
            model = first
            self.mixed = self.axes.plot(x, model.mixed, c="black")[0]
            self.components = [self.axes.plot(x, distribution*fraction, c=plt.get_cmap()(i))[0] for i, (distribution, fraction) in enumerate(zip(model.distributions, model.fractions))]
            if self.show_mode:
                modes = [self.transfer(model.classes_φ[np.unravel_index(np.argmax(distribution), distribution.shape)])  for distribution in model.distributions]
                self.vlines = self.axes.vlines(modes, 0.0, round(np.max(model.target)*1.2, 2), colors=colors)
            return self.mixed, self.vlines, *self.components
        def animate(current):
            model = current
            self.mixed.set_ydata(current.mixed)
            for line, distribution, fraction in zip(self.components, model.distributions, model.fractions):
                line.set_ydata(distribution*fraction)
            if self.show_mode:
                self.vlines.remove()
                modes = [self.transfer(model.classes_φ[np.unravel_index(np.argmax(distribution), distribution.shape)])  for distribution in model.distributions]
                self.vlines = self.axes.vlines(modes, 0.0, round(np.max(model.target)*1.2, 2), colors=colors)
            return self.mixed, self.vlines, *self.components
        self.animation = FuncAnimation(self.figure, animate, frames=models, init_func=init,
                                       interval=self.interval, blit=True,
                                       repeat=self.repeat, repeat_delay=3.0, save_count=result.n_iterations)

    def save_animation(self):
        if self.last_result is not None:
            filename, format_str = self.file_dialog.getSaveFileName(self, self.tr("Save the animation of this SSU result"), None, self.tr("MPEG-4 Video File (*.mp4);;Graphics Interchange Format (*.gif)"))
            if filename is None or filename == "":
                return
            progress = QProgressDialog(self)
            progress.setRange(0, 100)
            progress.setLabelText(self.tr("Saving Animation [{0} Frames]").format(self.last_result.n_iterations))
            canceled = False
            def save_callback(i, n):
                if progress.wasCanceled():
                    nonlocal canceled
                    canceled = True
                    raise StopIteration()
                progress.setValue((i+1)/n*100)
                QCoreApplication.processEvents()
            self.show_result(self.last_result)
            # plt.rcParams["savefig.dpi"] = 120.0
            if "*.gif" in format_str:
                if not ImageMagickWriter.isAvailable():
                    self.normal_msg.setWindowTitle(self.tr("Error"))
                    self.normal_msg.setText(self.tr("ImageMagick is not installed, please download and install it from its offical website (https://imagemagick.org/index.php)."))
                    self.normal_msg.exec_()
                else:
                    self.animation.save(filename, writer="imagemagick", fps=30, progress_callback=save_callback)
            elif "*.mp4" in format_str:
                if not FFMpegWriter.isAvailable():
                    self.normal_msg.setWindowTitle(self.tr("Error"))
                    self.normal_msg.setText(self.tr("FFMpeg is not installed, please download and install it from its offical website (https://ffmpeg.org/)."))
                    self.normal_msg.exec_()
                else:
                    self.animation.save(filename, writer="ffmpeg", fps=30, progress_callback=save_callback)
            # plt.rcParams["savefig.dpi"] = 300.0
            if not canceled:
                progress.setValue(100)
示例#14
0
class Find(QDialog):
    def __init__(self, parent=None):

        QDialog.__init__(self, parent)

        self.parent = parent

        self.lastMatch = None

        self.initUI()

    def initUI(self):

        # Button to search the document for something
        findButton = QPushButton("Find", self)
        findButton.clicked.connect(self.find)

        # Button to replace the last finding
        replaceButton = QPushButton("Replace", self)
        replaceButton.clicked.connect(self.replace)

        # Button to remove all findings
        allButton = QPushButton("Replace all", self)
        allButton.clicked.connect(self.replaceAll)

        # Normal mode - radio button
        self.normalRadio = QRadioButton("Normal", self)
        self.normalRadio.toggled.connect(self.normalMode)

        # Regular Expression Mode - radio button
        self.regexRadio = QRadioButton("RegEx", self)
        self.regexRadio.toggled.connect(self.regexMode)

        # The field into which to type the query
        self.findField = QTextEdit(self)
        self.findField.resize(250, 50)

        # The field into which to type the text to replace the
        # queried text
        self.replaceField = QTextEdit(self)
        self.replaceField.resize(250, 50)

        optionsLabel = QLabel("Options: ", self)

        # Case Sensitivity option
        self.caseSens = QCheckBox("Case sensitive", self)

        # Whole Words option
        self.wholeWords = QCheckBox("Whole words", self)

        # Layout the objects on the screen
        layout = QGridLayout()

        layout.addWidget(self.findField, 1, 0, 1, 4)
        layout.addWidget(self.normalRadio, 2, 2)
        layout.addWidget(self.regexRadio, 2, 3)
        layout.addWidget(findButton, 2, 0, 1, 2)

        layout.addWidget(self.replaceField, 3, 0, 1, 4)
        layout.addWidget(replaceButton, 4, 0, 1, 2)
        layout.addWidget(allButton, 4, 2, 1, 2)

        # Add some spacing
        spacer = QWidget(self)

        spacer.setFixedSize(0, 10)

        layout.addWidget(spacer, 5, 0)

        layout.addWidget(optionsLabel, 6, 0)
        layout.addWidget(self.caseSens, 6, 1)
        layout.addWidget(self.wholeWords, 6, 2)

        self.setGeometry(300, 300, 360, 250)
        self.setWindowTitle("Find and Replace")
        self.setLayout(layout)

        # By default the normal mode is activated
        self.normalRadio.setChecked(True)

    def find(self):

        # Grab the parent's text
        text = self.parent.paged_text_edit.toPlainText()

        # And the text to find
        query = self.findField.toPlainText()

        # If the 'Whole Words' checkbox is checked, we need to append
        # and prepend a non-alphanumeric character
        if self.wholeWords.isChecked():
            query = r'\W' + query + r'\W'

        # By default regexes are case sensitive but usually a search isn't
        # case sensitive by default, so we need to switch this around here
        flags = 0 if self.caseSens.isChecked() else re.I

        # Compile the pattern
        pattern = re.compile(query, flags)

        # If the last match was successful, start at position after the last
        # match's start, else at 0
        start = self.lastMatch.start() + 1 if self.lastMatch else 0

        # The actual search
        self.lastMatch = pattern.search(text, start)

        if self.lastMatch:

            start = self.lastMatch.start()
            end = self.lastMatch.end()

            # If 'Whole words' is checked, the selection would include the two
            # non-alphanumeric characters we included in the search, which need
            # to be removed before marking them.
            if self.wholeWords.isChecked():
                start += 1
                end -= 1

            self.moveCursor(start, end)

        else:
            # We set the cursor to the end if the search was unsuccessful
            self.parent.paged_text_edit.moveCursor(QTextCursor.End)

    def replace(self):

        # Grab the text cursor
        cursor = self.parent.paged_text_edit.textCursor()

        # TODO Prevent replace from replacing text not searched for
        # Check if cursor selection is same as search text
        # Security
        if self.lastMatch and cursor.hasSelection():

            # We insert the new text, which will override the selected
            # text
            cursor.insertText(self.replaceField.toPlainText())

            # And set the new cursor
            self.parent.paged_text_edit.setTextCursor(cursor)

    def replaceAll(self):

        # Set lastMatch to None so that the search
        # starts from the beginning of the document
        self.lastMatch = None

        # Initial find() call so that lastMatch is
        # potentially not None anymore
        self.find()

        # Replace and find until find is None again
        while self.lastMatch:
            self.replace()
            self.find()

    def regexMode(self):

        # First uncheck the checkboxes
        self.caseSens.setChecked(False)
        self.wholeWords.setChecked(False)

        # Then disable them (gray them out)
        self.caseSens.setEnabled(False)
        self.wholeWords.setEnabled(False)

    def normalMode(self):

        # Enable checkboxes (un-gray them)
        self.caseSens.setEnabled(True)
        self.wholeWords.setEnabled(True)

    def moveCursor(self, start, end):

        # We retrieve the QTextCursor object from the parent's QTextEdit
        cursor = self.parent.paged_text_edit.textCursor()

        # Then we set the position to the beginning of the last match
        cursor.setPosition(start)

        # Next we move the Cursor by over the match and pass the KeepAnchor parameter
        # which will make the cursor select the the match's text
        cursor.movePosition(QTextCursor.Right, QTextCursor.KeepAnchor,
                            end - start)

        # And finally we set this new cursor as the parent's
        self.parent.paged_text_edit.setTextCursor(cursor)
class RenameOptionsView(QWidget):
    """View responsible for holding renaming options.
    
    Attributes:
        layout (QVBoxLayout): Main layout of view.
        frame_layout (QVBoxLayout: Layout of frame which holds options.
        frame (QFrame): Frame surrounding options.
        prefix_h_layout (QHBoxLayout): Layout holding prefix options.
        complete_rename_h_layout (QHBoxLayout): Layout holding complete rename options.
        search_and_replace_h_layout (QHBoxLayout): Layout holding search and replace options.
        renumber_h_layout (QHBoxLayout): Layout holding renumber options.
        remove_ext_h_layout (QHBoxLayout): Layout holding remove options.
        change_ext_h_layout (QHBoxLayout): Layout holding change extension options.
        create_backup_h_layout (QHBoxLayout): Layout holding backup options.
        preview_h_layout (QHBoxLayout): Layout holding preview options.
        start_lbl (QLabel): Label for renumbering start.
        padding_lbl (QLabel): Label for renumbering padding.
        add_prefix_cb (QCheckBox): Used to signify the user wants to add a prefix to the renaming.
        prefix (QLineEdit): prefix to add.
        complete_rename_cb (QCheckBox): Used to signify the user wants to completely rename the file.
        new_name (QLineEdit): New name used when renaming.
        search_and_replace_cb (QCheckBox): Used to signify the user wants to partially rename files.
        find (QLineEdit): When searching and replacing this is what the user wants to search for.
        replace (QLineEdit): When searching and replacing this is what the user wants to replace with.
        renumber_cb (QCheckBox): Used to signify the user wants to renumber while renaming.
        start_num (QSpinBox): Number to start with when renumbering files.
        padding (QComboBox): Padding to apply to renaming when renumbering files.
        dot_cb (QCheckBox): When checked a dot will be used to separate the renumber from the name.
        remove_ext_cb (QCheckBox): Used to signify the user wants to remove extensions when renaming.
        backup_files_cb (QCheckBox): Used to signify the user wants to backup old files before renaming.
        change_ext_cb (QCheckBox): Used to signify the user wants to change the extension while renaming.
        change_ext (QLineEdit): New extension to add to the renamed file.
        preview_cb (QCheckBox): Used to signify the user wants to preview the rename before renaming.
    """
    def __init__(self):
        super(RenameOptionsView, self).__init__()
        self.layout = QVBoxLayout()
        self.frame_layout = QVBoxLayout()
        self.options_lbl = QLabel(prefs.OPTIONS)
        self.frame = QFrame()
        self.prefix_h_layout = QHBoxLayout()
        self.complete_rename_h_layout = QHBoxLayout()
        self.search_and_replace_h_layout = QHBoxLayout()
        self.renumber_h_layout = QHBoxLayout()
        self.remove_ext_h_layout = QHBoxLayout()
        self.change_ext_h_layout = QHBoxLayout()
        self.create_backup_h_layout = QHBoxLayout()
        self.preview_h_layout = QHBoxLayout()
        self.start_lbl = QLabel(prefs.START_NUM)
        self.padding_lbl = QLabel(prefs.PADDING)
        self.add_prefix_cb = QCheckBox(prefs.PREFIX)
        self.prefix = QLineEdit(prefs.PREFIX_DEFAULT)
        self.complete_rename_cb = QCheckBox(prefs.COMPLETE_RENAME)
        self.new_name = QLineEdit(prefs.COMPLETE_RENAME_DEFAULT)
        self.search_and_replace_cb = QCheckBox(prefs.SEARCH_AND_REPLACE)
        self.find = QLineEdit(prefs.SEARCH_AND_REPLACE_DEFAULT)
        self.replace = QLineEdit(prefs.REPLACE_WITH_DEFAULT)
        self.renumber_cb = QCheckBox(prefs.RENUMBER)
        self.start_num = QSpinBox()
        self.padding = QComboBox()
        self.dot_cb = QCheckBox(prefs.USE_DOT)
        self.remove_ext_cb = QCheckBox(prefs.REMOVE_EXT)
        self.backup_files_cb = QCheckBox(prefs.BACKUP)
        self.change_ext_cb = QCheckBox(prefs.CHANGE_EXT)
        self.change_ext = QLineEdit(prefs.CHANGE_EXT_DEFAULT)
        self.preview_cb = QCheckBox(prefs.PREVIEW)

        self._configure()

    def _configure(self) -> None:
        """Configure the RenameOptionsView."""
        self.frame.setLayout(self.frame_layout)
        self.frame.setFrameStyle(QFrame.StyledPanel | QFrame.Sunken)
        self.layout.addWidget(self.options_lbl)
        self.layout.addWidget(self.frame)
        self.add_prefix_cb.setToolTip(prefs.PREFIX_TOOLTIP)
        self.prefix.setDisabled(True)
        self.prefix.setMaximumWidth(prefs.PREFIX_WIDTH)
        self.prefix.setMinimumWidth(prefs.PREFIX_WIDTH)
        self.complete_rename_cb.setToolTip(prefs.COMPLETE_RENAME_TOOLTIP)
        self.new_name.setDisabled(True)
        self.new_name.setMaximumWidth(prefs.NEW_NAME_WIDTH)
        self.new_name.setMinimumWidth(prefs.NEW_NAME_WIDTH)
        self.search_and_replace_cb.setToolTip(prefs.SEARCH_AND_REPLACE_TOOLTIP)
        self.find.setDisabled(True)
        self.find.setMinimumWidth(prefs.FIND_WIDTH)
        self.find.setMaximumWidth(prefs.FIND_WIDTH)
        self.replace.setDisabled(True)
        self.replace.setMaximumWidth(prefs.REPLACE_WIDTH)
        self.replace.setMinimumWidth(prefs.REPLACE_WIDTH)
        self.renumber_cb.setToolTip(prefs.RENUMBER_TOOLTIP)
        self.start_num.setToolTip(prefs.START_NUM_TOOLTIP)
        self.start_num.setDisabled(True)
        self.start_num.setValue(prefs.START_NUM_DEFAULT)
        self.start_num.setMinimumWidth(prefs.START_NUM_MIN_WIDTH)
        self.padding.setToolTip(prefs.PADDING_TOOLTIP)
        self.padding.setDisabled(True)
        self.padding.addItems([str(x) for x in range(10)])
        self.padding.setCurrentIndex(4)
        self.padding.setMinimumWidth(prefs.PADDING_MIN_WIDTH)
        self.dot_cb.setToolTip(prefs.USE_DOT_TOOLTIP)
        self.dot_cb.setDisabled(True)
        self.dot_cb.setChecked(True)
        self.dot_cb.setMinimumWidth(prefs.DOT_WIDTH)
        self.dot_cb.setMaximumWidth(prefs.DOT_WIDTH)
        self.remove_ext_cb.setToolTip(prefs.REMOVE_EXT_TOOLTIP)
        self.change_ext.setToolTip(prefs.CHANGE_EXT_TOOLTIP)
        self.change_ext.setDisabled(True)
        self.backup_files_cb.setToolTip(prefs.BACKUP_TOOLTIP)
        self.backup_files_cb.setChecked(True)
        self.preview_cb.setToolTip(prefs.PREVIEW_TOOLTIP)
        self.preview_cb.setChecked(True)

        self.prefix_h_layout.addWidget(self.add_prefix_cb)
        self.prefix_h_layout.addWidget(self.prefix)
        self.frame_layout.addLayout(self.prefix_h_layout)

        self.complete_rename_h_layout.addWidget(self.complete_rename_cb)
        self.complete_rename_h_layout.addWidget(self.new_name)
        self.frame_layout.addLayout(self.complete_rename_h_layout)

        self.search_and_replace_h_layout.addWidget(self.search_and_replace_cb)
        self.search_and_replace_h_layout.addWidget(self.find)
        self.search_and_replace_h_layout.addWidget(self.replace)
        self.frame_layout.addLayout(self.search_and_replace_h_layout)

        self.renumber_h_layout.addWidget(self.renumber_cb)
        self.renumber_h_layout.addStretch(1)
        self.renumber_h_layout.addWidget(self.start_lbl)
        self.renumber_h_layout.addWidget(self.start_num)
        self.renumber_h_layout.addSpacerItem(QSpacerItem(*prefs.SPACER_SIZE))
        self.renumber_h_layout.addWidget(self.padding_lbl)
        self.renumber_h_layout.addWidget(self.padding)
        self.renumber_h_layout.addSpacerItem(QSpacerItem(*prefs.SPACER_SIZE))
        self.renumber_h_layout.addWidget(self.dot_cb)
        self.frame_layout.addLayout(self.renumber_h_layout)

        self.change_ext_h_layout.addWidget(self.change_ext_cb)
        self.change_ext_h_layout.addWidget(self.change_ext)
        self.frame_layout.addLayout(self.change_ext_h_layout)

        self.remove_ext_h_layout.addWidget(self.remove_ext_cb)
        self.frame_layout.addLayout(self.remove_ext_h_layout)

        self.create_backup_h_layout.addWidget(self.backup_files_cb)
        self.frame_layout.addLayout(self.create_backup_h_layout)

        self.preview_h_layout.addWidget(self.preview_cb)
        self.frame_layout.addLayout(self.preview_h_layout)

        self.frame_layout.addSpacerItem(QSpacerItem(*prefs.SPACER_SIZE))

        self.setLayout(self.layout)

    def disable_change_ext(self) -> None:
        """Disable change extension."""
        self.change_ext.setDisabled(True)

    def disable_dot(self) -> None:
        """Disable dot checkbox."""
        self.dot_cb.setDisabled(True)

    def disable_find(self) -> None:
        """Disable find."""
        self.find.setDisabled(True)

    def disable_new_name(self) -> None:
        """Disable new name."""
        print("disable new name")
        self.new_name.setDisabled(True)

    def disable_padding(self) -> None:
        """Disable padding."""
        self.padding.setDisabled(True)

    def disable_prefix(self) -> None:
        """Disable prefix."""
        self.prefix.setDisabled(True)

    def disable_start_num(self) -> None:
        """Disable start num."""
        self.start_num.setDisabled(True)

    def disable_replace(self) -> None:
        """Disable replace."""
        self.replace.setDisabled(True)

    def enable_change_ext(self) -> None:
        """Disable change extension."""
        self.change_ext.setDisabled(False)

    def enable_dot(self) -> None:
        """Enable dot checkbox."""
        self.dot_cb.setEnabled(True)

    def enable_find(self) -> None:
        """Enable find."""
        self.find.setEnabled(True)

    def enable_new_name(self) -> None:
        """Enable new name."""
        print("enable new name.")
        self.new_name.setEnabled(True)

    def enable_padding(self) -> None:
        """Enable padding."""
        self.padding.setEnabled(True)

    def enable_prefix(self) -> None:
        """Enable prefix."""
        self.prefix.setEnabled(True)

    def enable_replace(self) -> None:
        """Enable replace."""
        self.replace.setEnabled(True)

    def enable_start_num(self) -> None:
        """Enable start num."""
        self.start_num.setEnabled(True)

    def get_add_prefix(self) -> bool:
        """Return if end user wants to add a prefix and it is not the default value."""
        result = self.get_prefix_checked()
        if result and self.get_prefix() == prefs.PREFIX_DEFAULT:
            result = False
        return result

    def get_do_backup(self) -> bool:
        """Return if end user wants to backup files."""
        return self.backup_files_cb.isChecked()

    def get_change_ext(self) -> bool:
        """Return if the change extension checkbox is checked."""
        return self.change_ext_cb.isChecked()

    def get_do_complete_rename(self) -> bool:
        """Get if end user wants to completely rename."""
        return self.complete_rename_cb.isChecked()

    def get_dot(self) -> str:
        """Return dot string based on end users configuration.

        Note:
            If the end user has not enable using dot separators an empty string will be returned.
        """
        return "." if self.get_do_dot() else ""

    def get_do_dot(self) -> bool:
        """Return if the end user wants to use dot separators when renaming."""
        return self.dot_cb.isChecked()

    def get_do_change_ext(self) -> bool:
        """Return if the end user wants to change the extension."""
        result = self.change_ext_cb.isChecked()
        if self.get_new_ext() == prefs.CHANGE_EXT_DEFAULT:
            return False
        return result

    def get_do_padding(self) -> bool:
        """Return if the end user wants to add padding."""
        return False if self.get_padding() == 0 else True

    def get_do_preview(self) -> bool:
        """Return if the end user wants to preview changes."""
        return self.preview_cb.isChecked()

    def get_do_rename(self) -> bool:
        """Return if end user wants to rename the full item and it is not the default value."""
        result = self.complete_rename_cb.isChecked()
        if result and self.get_new_name() == prefs.COMPLETE_RENAME_DEFAULT:
            result = False
        return result

    def get_do_renumber(self) -> bool:
        """Return if the end user wants to renumber."""
        return self.renumber_cb.isChecked()

    def get_do_search(self) -> bool:
        """Return if end user wants to perform a search and replace AND it is not the default values respectfully.

        Note:
            If you only want to know if search and replace is checked use get_search_and_replace.
        """
        result = self.search_and_replace_cb.isChecked()
        if result and (self.get_find() == prefs.SEARCH_AND_REPLACE_DEFAULT
                       or self.get_replace() == prefs.REPLACE_WITH_DEFAULT):
            result = False
        return result

    def get_do_search_and_replace(self) -> bool:
        """Return if end user wants to perform a search and replace."""
        return self.search_and_replace_cb.isChecked()

    def get_find(self) -> str:
        """Return find value."""
        return str(self.find.text())

    def get_new_ext(self) -> str:
        """Return new ext."""
        return str(self.change_ext.text())

    def get_new_name(self) -> str:
        """Return new_name value."""
        return str(self.new_name.text())

    def get_padding(self) -> int:
        """Return the current padding value."""
        return int(self.padding.currentText())

    def get_prefix_checked(self) -> bool:
        """Return if the prefix checkbox is checked."""
        return self.add_prefix_cb.isChecked()

    def get_prefix(self) -> str:
        """Return the current prefix value end user has entered."""
        return str(self.prefix.text())

    def get_remove_ext(self) -> bool:
        """Return if end user has checked the remove extension checkbox."""
        return self.remove_ext_cb.isChecked()

    def get_replace(self) -> str:
        """Return the current replace value end user has entered."""
        return str(self.replace.text())

    def get_start_num(self) -> int:
        """Return start number from view."""
        return int(self.start_num.value())

    def set_change_ext_style(self, style: str) -> None:
        """Set style of change extension.

        Args:
            style: Style sheet applied to change extension.
        """
        self.change_ext.setStyleSheet(style)

    def set_disabled(self) -> None:
        """Disable View."""
        self.setDisabled(True)

    def set_enable(self) -> None:
        """Enable View."""
        self.setEnabled(True)

    def set_find(self, value: str) -> None:
        """Set the value of find.

        Args:
            value: Value applied to find
        """
        self.find.setText(value)

    def set_find_style(self, style: str) -> None:
        """Set style of find.

        Args:
            style: Style sheet applied to find.
        """
        self.find.setStyleSheet(style)

    def set_new_name(self, value: str) -> None:
        """Set the value of new name.

        Args:
            value: Value applied to new_name
        """
        self.new_name.setText(value)

    def set_new_name_style(self, style: str) -> None:
        """Set style of new_name.
        
        Args:
            style: Style sheet applied to new_name.
        """
        self.new_name.setStyleSheet(style)

    def set_prefix(self, value: str) -> None:
        """Set the value of prefix.

        Args:
            value: Value applied to prefix
        """
        self.prefix.setText(value)

    def set_prefix_style(self, style: str) -> None:
        """Set style of prefix.

        Args:
            style: Style sheet applied to prefix.
        """
        self.prefix.setStyleSheet(style)

    def set_remove_ext(self, state: bool) -> None:
        """Set the remove_ext checkbox as checked or unchecked.

        Args:
            state: Check state of remove_ext.
        """
        self.remove_ext_cb.setCheckState(Qt.Checked if state else Qt.Unchecked)

    def set_replace(self, value: str) -> None:
        """Set the value of replace.

        Args:
            value: Value applied to replace
        """
        self.replace.setText(value)

    def set_replace_style(self, style: str) -> None:
        """Set style of replace.

        Args:
            style: Style sheet applied to replace.
        """
        self.replace.setStyleSheet(style)
示例#16
0
class HistWidget(ToolWidget):
    def __init__(self, image, parent=None):
        super(ToolWidget, self).__init__(parent)

        self.rgb_radio = QRadioButton(self.tr('RGB'))
        self.rgb_radio.setChecked(True)
        self.last_radio = self.rgb_radio
        self.red_radio = QRadioButton(self.tr('Red'))
        self.green_radio = QRadioButton(self.tr('Green'))
        self.blue_radio = QRadioButton(self.tr('Blue'))
        self.value_radio = QRadioButton(self.tr('Value'))
        self.log_check = QCheckBox(self.tr('Log scale'))
        self.grid_check = QCheckBox(self.tr('Show grid'))
        self.marker_check = QCheckBox(self.tr('Show markers'))
        self.marker_check.setToolTip(
            self.tr('Show plot markers for min(--), avg(-), max(-.)'))
        self.start_slider = ParamSlider([0, 255],
                                        8,
                                        0,
                                        label='Start:',
                                        bold=True)
        self.end_slider = ParamSlider([0, 255],
                                      8,
                                      255,
                                      label='End:',
                                      bold=True)

        channels = cv.split(cv.cvtColor(image, cv.COLOR_BGR2RGB))
        channels.append(cv.cvtColor(image, cv.COLOR_BGR2GRAY))
        self.hist = [compute_hist(c) for c in channels]
        rows, cols, chans = image.shape
        pixels = rows * cols
        unique = np.unique(np.reshape(image, (pixels, chans)), axis=0).shape[0]
        unique_ratio = unique / pixels * 100
        unique_label = QLabel(
            self.tr('total pixels = {}, unique colors = {} ({:.2f}%) '.format(
                pixels, unique, unique_ratio)))
        modify_font(unique_label, italic=True)

        self.rgb_radio.clicked.connect(self.redraw)
        self.red_radio.clicked.connect(self.redraw)
        self.green_radio.clicked.connect(self.redraw)
        self.blue_radio.clicked.connect(self.redraw)
        self.value_radio.clicked.connect(self.redraw)
        self.log_check.stateChanged.connect(self.redraw)
        self.grid_check.stateChanged.connect(self.redraw)
        self.marker_check.stateChanged.connect(self.redraw)
        self.start_slider.valueChanged.connect(self.redraw)
        self.end_slider.valueChanged.connect(self.redraw)

        self.table_widget = QTableWidget(8, 2)
        self.table_widget.setHorizontalHeaderLabels(
            [self.tr('Property'), self.tr('Value')])
        self.table_widget.setItem(0, 0,
                                  QTableWidgetItem(self.tr('Least frequent')))
        self.table_widget.setItem(1, 0,
                                  QTableWidgetItem(self.tr('Most frequent')))
        self.table_widget.setItem(2, 0,
                                  QTableWidgetItem(self.tr('Average level')))
        self.table_widget.setItem(3, 0,
                                  QTableWidgetItem(self.tr('Median level')))
        self.table_widget.setItem(4, 0, QTableWidgetItem(self.tr('Deviation')))
        self.table_widget.setItem(5, 0,
                                  QTableWidgetItem(self.tr('Pixel count')))
        self.table_widget.setItem(6, 0,
                                  QTableWidgetItem(self.tr('Percentile')))
        self.table_widget.setItem(7, 0,
                                  QTableWidgetItem(self.tr('Smoothness')))
        for i in range(self.table_widget.rowCount()):
            modify_font(self.table_widget.item(i, 0), bold=True)
        self.table_widget.setSelectionMode(QAbstractItemView.SingleSelection)
        self.table_widget.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.table_widget.resizeColumnsToContents()
        self.table_widget.setAlternatingRowColors(True)
        self.table_widget.setMaximumWidth(200)

        figure = Figure()
        plot_canvas = FigureCanvas(figure)
        # plot_canvas.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
        self.axes = plot_canvas.figure.subplots()
        self.redraw()
        figure.set_tight_layout(True)

        right_layout = QVBoxLayout()
        table_label = QLabel(self.tr('Range properties'))
        modify_font(table_label, bold=True)
        table_label.setAlignment(Qt.AlignCenter)
        right_layout.addWidget(table_label)
        right_layout.addWidget(self.table_widget)
        right_layout.addWidget(self.marker_check)
        right_layout.addWidget(self.start_slider)
        right_layout.addWidget(self.end_slider)

        center_layout = QHBoxLayout()
        center_layout.addWidget(plot_canvas)
        center_layout.addLayout(right_layout)

        bottom_layout = QHBoxLayout()
        bottom_layout.addWidget(self.rgb_radio)
        bottom_layout.addWidget(self.red_radio)
        bottom_layout.addWidget(self.green_radio)
        bottom_layout.addWidget(self.blue_radio)
        bottom_layout.addWidget(self.value_radio)
        bottom_layout.addWidget(self.log_check)
        bottom_layout.addWidget(self.grid_check)
        bottom_layout.addStretch()
        bottom_layout.addWidget(unique_label)

        main_layout = QVBoxLayout()
        main_layout.addLayout(center_layout)
        main_layout.addLayout(bottom_layout)
        self.setLayout(main_layout)

    def redraw(self):
        x = np.arange(256)
        alpha = 0.25
        rgb = self.rgb_radio.isChecked()
        red = self.red_radio.isChecked()
        green = self.green_radio.isChecked()
        blue = self.blue_radio.isChecked()
        value = self.value_radio.isChecked()
        grid = self.grid_check.isChecked()
        log = self.log_check.isChecked()
        try:
            self.axes.clear()
        except RecursionError:
            return
        y = None
        if value:
            y = self.hist[3]
            self.axes.step(x, y, 'k')
            self.axes.fill_between(x,
                                   y,
                                   alpha=alpha,
                                   facecolor='k',
                                   step='pre')
        else:
            # TODO: Ottimizzare facendo un ciclo senza ripetere le istruzioni
            if red or rgb:
                y = self.hist[0]
                self.axes.step(x, y, 'r')
                self.axes.fill_between(x,
                                       y,
                                       alpha=alpha,
                                       facecolor='r',
                                       step='pre')
            if green or rgb:
                y = self.hist[1]
                self.axes.step(x, y, 'g')
                self.axes.fill_between(x,
                                       y,
                                       alpha=alpha,
                                       facecolor='g',
                                       step='pre')
            if blue or rgb:
                y = self.hist[2]
                self.axes.step(x, y, 'b')
                self.axes.fill_between(x,
                                       y,
                                       alpha=alpha,
                                       facecolor='b',
                                       step='pre')
        if log:
            self.axes.set_yscale('log')
            self.axes.set_ylim(bottom=1)
        else:
            self.axes.set_yscale('linear')
            self.axes.set_ylim(bottom=0)
        self.axes.set_xlim([-1, 256])
        self.axes.set_xlabel(self.tr('intensity value'))
        self.axes.set_ylabel(self.tr('pixel count'))
        self.axes.set_xticks([0, 64, 128, 192, 255])
        self.axes.grid(grid, which='both')

        if rgb:
            self.table_widget.setEnabled(False)
            self.marker_check.setEnabled(False)
            self.start_slider.setEnabled(False)
            self.end_slider.setEnabled(False)
            for i in range(self.table_widget.rowCount()):
                if self.table_widget.item(i, 1) is not None:
                    self.table_widget.item(i, 1).setText('')
                    self.table_widget.item(i, 1).setBackgroundColor(
                        QColor('white'))
        else:
            self.table_widget.setEnabled(True)
            self.marker_check.setEnabled(True)
            self.start_slider.setEnabled(True)
            self.end_slider.setEnabled(True)
            start = self.start_slider.value()
            end = self.end_slider.value()
            if end <= start:
                end = start + 1
            elif start >= end:
                start = end - 1
            total = np.sum(y)
            x = x[start:end + 1]
            y = y[start:end + 1]
            count = np.sum(y)
            if count != 0:
                argmin = np.argmin(y) + start
                argmax = np.argmax(y) + start
                mean = np.round(np.sum(x * y) / count, 2)
                stddev = np.round(np.sqrt(np.sum(((x - mean)**2) * y) / count),
                                  2)
                median = np.argmax(np.cumsum(y) > count / 2) + start
                percent = np.round(count / total * 100, 2)
                y = y / np.max(y)
                sweep = len(y)
                smooth = 0
                if sweep > 2:
                    for i in range(1, sweep - 1):
                        h0 = y[i - 1]
                        h1 = y[i]
                        h2 = y[i + 1]
                        smooth += abs((h0 + h2) / 2 - h1)
                    smooth = np.round((1 - (smooth / (sweep - 2))) * 100, 2)
                if self.marker_check.isChecked():
                    self.axes.axvline(argmin, linestyle='--', color='m')
                    self.axes.axvline(mean, linestyle='-', color='m')
                    self.axes.axvline(argmax, linestyle='-.', color='m')
            else:
                argmin = argmax = mean = stddev = median = percent = smooth = 0
            self.table_widget.setItem(0, 1, QTableWidgetItem(str(argmin)))
            self.table_widget.setItem(1, 1, QTableWidgetItem(str(argmax)))
            self.table_widget.setItem(2, 1, QTableWidgetItem(str(mean)))
            self.table_widget.setItem(3, 1, QTableWidgetItem(str(median)))
            self.table_widget.setItem(4, 1, QTableWidgetItem(str(stddev)))
            self.table_widget.setItem(5, 1, QTableWidgetItem(str(count)))
            self.table_widget.setItem(6, 1,
                                      QTableWidgetItem(str(percent) + '%'))
            self.table_widget.setItem(7, 1,
                                      QTableWidgetItem(str(smooth) + '%'))
            if smooth <= 80:
                self.table_widget.item(7, 1).setBackgroundColor(
                    QColor.fromHsv(0, 96, 255))
            elif smooth <= 90:
                self.table_widget.item(7, 1).setBackgroundColor(
                    QColor.fromHsv(30, 96, 255))
            elif smooth <= 95:
                self.table_widget.item(7, 1).setBackgroundColor(
                    QColor.fromHsv(60, 96, 255))
            else:
                self.table_widget.item(7, 1).setBackgroundColor(
                    QColor.fromHsv(90, 96, 255))
            self.table_widget.resizeColumnsToContents()
            if start != 0 or end != 255:
                self.axes.axvline(start, linestyle=':', color='k')
                self.axes.axvline(end, linestyle=':', color='k')
                _, top = self.axes.get_ylim()
                self.axes.fill_between(np.arange(start, end + 1),
                                       top,
                                       facecolor='y',
                                       alpha=alpha * 2)
        self.axes.figure.canvas.draw()
class ChannelArithmeticDialog(ieb.ImarisExtensionBase):
    """
    Channel Arithmetic and Beyond
    =============================
    `View on GitHub <https://github.com/niaid/imaris_extensions>`_

    This program enables one to specify arithmetic expressions which are used to
    create new channels. The basic arithmetic operations are supported: +,-,*,/,**.
    More advanced operations that run short `SimpleITK <https://simpleitk.org/>`_
    code snippets are also supported.

    Channels are referenced using square brackets and the channel index, starting
    at **zero**. To apply an expression to all channels, use the channel index 'i'.

    When creating a single new channel, the arithmetic expression consists of literal
    channel numbers, one can select a name and color for the new channel. When
    creating multiple new channels, the arithmetic expression is applied to all channels,
    the postfix '_modified' is appended to the original channel names and the original
    color is copied over. Note that for all channels created by the program the
    channel description will include the arithmetic expression used to create that
    channel. This transparently supports your efforts to conduct reproducible
    research.

    Because an Imaris image has a specific pixel type (8, 16, 32 bit unsigned integer
    and 32 bit floating point) all computations are performed using a 32 bit floating
    point representation and then clamped to the range of the image's pixel type.

    The program allows you to use the same expression on multiple files. In this
    case literal channel values are limited by the number of shared channels. Thus,
    if working with two files one with three channels and one with four channels,
    the valid literal channel values are limited to 0, 1, 2. We cannot use 3 as it does not
    exist in all files. On the other hand, if our autofluorescence channel is one
    of these channels, e.g. channel 0, we can subtract it from all channels in
    both files, `[i]-[0]`.

    Basic Examples
    --------------

    Multiply channels zero and three:

    .. code-block:: Python

      [0]*[3]

    Multiply channels zero and three and subtract the result from channel four:

    .. code-block:: Python

      [4] - ([0]*[3])

    Duplicate all channels:

    .. code-block:: Python

      [i]

    Subtract channel zero from all channels:

    .. code-block:: Python

      [i]-[0]


    Advanced Examples
    -----------------

    Threshold channel one using a value of 100, resulting image is binary
    values in {0,1}:

    .. code-block:: Python

      [1]>100

    Threshold a specific channel to create a binary result using the Otsu
    filter:

    .. code-block:: Python

      sitk.OtsuThreshold([1], 0, 1)

    Threshold a specific channel retaining the values above the threshold:

    .. code-block:: Python

      sitk.Cast([1]>100, sitk.sitkFloat32)*[1]

    Threshold a specific channel, get all connected components, then
    sort the components according to size, discarding those smaller than a minimum
    size and create a binary mask corresponding to the largest component, which is
    the first label(second largest component label is 2 etc.)

    .. code-block:: Python

      sitk.RelabelComponent(sitk.ConnectedComponent([1]>100), minimumObjectSize = 50)==1

    Create a binary mask representing the colocalization of two channels,
    intensity values below 20 are considred noise:

    .. code-block:: Python

      ([1]>20)*([2]>20)

    Create a binary mask representing the colocalization of two channels.
    We are interested in all pixels in channel 2 that have a value above 20
    and that are less than 1.0um away from pixels in channel 1 that have a value
    above 100 (**note**: this operation yields different results when run using
    a slice-by-slice approach vs. a volumetric approach):

    .. code-block:: Python

        (sitk.Cast([2]>20, sitk.sitkFloat32) *
         sitk.Abs(sitk.SignedMaurerDistanceMap([1]>100, insideIsPositive=False, squaredDistance=False, useImageSpacing=True)))<=1.0

    Create a binary mask using thresholding and then perform morphological
    closing (dilation followed by erosion) with distance maps, useful
    for filling holes:

    .. code-block:: Python

      sitk.SignedMaurerDistanceMap(sitk.SignedMaurerDistanceMap([1]>100, insideIsPositive=False, squaredDistance=False, useImageSpacing=True) < 1.0, insideIsPositive=False, squaredDistance=False, useImageSpacing=True)<-1.0

    Create a binary mask using thresholding and then perform morphological
    opening (erosion followed by dilation) with distance maps, useful
    for removing small islands:

    .. code-block:: Python

      sitk.SignedMaurerDistanceMap(sitk.SignedMaurerDistanceMap([1]>100, insideIsPositive=False, squaredDistance=False, useImageSpacing=True) < -0.2, insideIsPositive=False, squaredDistance=False, useImageSpacing=True)<0.2
    """  # noqa

    def __init__(self):
        super(ChannelArithmeticDialog, self).__init__()
        # Channel indexes in the arithmetic calculator are denoted using a
        # regular expression: one or more digits in square brackets (e.g. [1234]).
        # First digit is zero and nothing afterwards or first digit is in [1-9] and
        # there are possibly more digits afterwards.
        # Starting index is zero.
        self.channel_pattern = re.compile(r"\[(0|[1-9]\d*)\]")

        # Use QT's global threadpool, documentation says: "This global thread pool
        # automatically maintains an optimal number of threads based on the
        # number of cores in the CPU."
        self.threadpool = QThreadPool.globalInstance()

        # Configure the help dialog.
        self.help_dialog = HelpDialog(w=700, h=500)
        self.help_dialog.setWindowTitle("Channel Arithmetic Help")
        self.help_dialog.set_rst_text(
            inspect.getdoc(self), pygments_css_file_name="pygments_dark.css")

        self.__create_gui()
        self.setWindowTitle("Channel Arithmetic")
        self.processing_error = False

        self.show()

    def __create_gui(self):
        menu_bar = self.menuBar()
        # Force menubar to be displayed in the application on OSX/Linux, otherwise it
        # is displayed in the system menubar
        menu_bar.setNativeMenuBar(False)
        self.help_button = QPushButton("Help")
        self.help_button.clicked.connect(self.help_dialog.show)
        menu_bar.setCornerWidget(self.help_button, Qt.TopLeftCorner)

        central_widget = QWidget(self)
        gui_layout = QVBoxLayout()
        central_widget.setLayout(gui_layout)
        self.setCentralWidget(central_widget)

        select_files_widget = self.__create_select_files_widget()
        arithmetic_widget = self.__create_arithmetic_widget()

        self.stack = QStackedWidget(self)
        self.stack.addWidget(select_files_widget)
        self.stack.addWidget(arithmetic_widget)
        gui_layout.addWidget(self.stack)

        self.status_bar = self.statusBar()

    def closeEvent(self, event):
        """
        Override the closeEvent method so that clicking the 'x' button also
        closes all of the dialogs.
        """
        self.help_dialog.close()
        event.accept()

    def __create_arithmetic_widget(self):
        wid = QWidget(self)
        arithmetic_layout = QVBoxLayout()
        wid.setLayout(arithmetic_layout)

        self.valid_indexes_label = QLabel("")
        arithmetic_layout.addWidget(self.valid_indexes_label)

        layout = QHBoxLayout()
        layout.setAlignment(Qt.AlignLeft)
        layout.addWidget(QLabel("Enter new channel arithmetic expression:"))
        arithmetic_layout.addLayout(layout)

        self.arithmetic_expression_text_edit = QTextEdit()
        arithmetic_layout.addWidget(self.arithmetic_expression_text_edit)

        self.slice_by_slice_checkbox = QCheckBox(
            "Slice by slice (smaller memory footprint).")
        arithmetic_layout.addWidget(self.slice_by_slice_checkbox)

        layout = QHBoxLayout()
        layout.addWidget(QLabel("New channel name:"))
        self.new_channel_name_line_edit = QLineEdit()
        layout.addWidget(self.new_channel_name_line_edit)
        arithmetic_layout.addLayout(layout)

        layout = QHBoxLayout()
        layout.addWidget(QLabel("New channel color:"))
        self.new_channel_color_button = QPushButton()
        self.new_channel_color_button.clicked.connect(
            self.__select_color_callback)
        layout.addWidget(self.new_channel_color_button)
        arithmetic_layout.addLayout(layout)

        self.apply_button = QPushButton("Apply")
        self.apply_button.clicked.connect(self.__channel_arithmetic_wrapper)
        arithmetic_layout.addWidget(self.apply_button)

        progress_wid = QWidget()
        self.progress_grid_layout = QGridLayout()
        progress_wid.setLayout(self.progress_grid_layout)
        scroll_area = QScrollArea()
        scroll_area.setWidget(progress_wid)
        scroll_area.setWidgetResizable(True)
        arithmetic_layout.addWidget(scroll_area)

        layout = QHBoxLayout()
        layout.setAlignment(Qt.AlignLeft)
        self.processing_prev_button = QPushButton("Prev")
        self.processing_prev_button.clicked.connect(
            lambda: self.stack.setCurrentIndex(0))
        layout.addWidget(self.processing_prev_button)
        arithmetic_layout.addLayout(layout)

        return wid

    def __configure_and_show_arithmetic_widget(self):
        file_names = self.input_files_edit.toPlainText().split("\n")
        num_channels = []
        problematic_images = []
        for file_name in file_names:
            try:
                meta_data = sio.read_metadata(file_name)
                num_channels.append(len(meta_data["channels_information"]))
            except Exception:
                problematic_images.append(file_name)
        if problematic_images:
            self._error_function(
                "Problem encountered reading the following file(s):\n" +
                "\n".join(problematic_images))
            return
        self.max_channel_index = min(num_channels) - 1
        self.valid_indexes_label.setText(
            f"Valid channel indexes: 0...{self.max_channel_index}, i")
        self.arithmetic_expression_text_edit.clear()
        self.slice_by_slice_checkbox.setChecked(False)
        self.new_channel_name_line_edit.clear()

        # Remove all widgets from layout, done in reverse order because
        # removing from the begining shifts the rest of the items
        for i in reversed(range(self.progress_grid_layout.count())):
            self.progress_grid_layout.itemAt(i).widget().setParent(None)

        for i, file_name in enumerate(file_names):
            self.progress_grid_layout.addWidget(
                QLabel(os.path.basename(file_name)), i, 0)
            progress_bar = QProgressBar()
            progress_bar.setMaximum(100)
            self.progress_grid_layout.addWidget(progress_bar, i, 1)

        self.stack.setCurrentIndex(1)

    def __create_select_files_widget(self):
        wid = QWidget()
        input_layout = QVBoxLayout()
        wid.setLayout(input_layout)

        layout = QHBoxLayout()
        layout.addWidget(QLabel("File names:"))
        layout.setAlignment(Qt.AlignLeft)
        button = QPushButton("Browse")
        button.setToolTip("Select input files for arithmetic operation.")
        button.clicked.connect(self.__browse_select_input_callback)
        layout.addWidget(button)
        input_layout.addLayout(layout)

        self.input_files_edit = QTextEdit()
        self.input_files_edit.setReadOnly(True)
        input_layout.addWidget(self.input_files_edit)

        layout = QHBoxLayout()
        layout.setAlignment(Qt.AlignRight)
        self.input_files_next_button = QPushButton("Next")
        self.input_files_next_button.setEnabled(False)
        self.input_files_next_button.clicked.connect(
            self.__configure_and_show_arithmetic_widget)
        layout.addWidget(self.input_files_next_button)
        input_layout.addLayout(layout)

        return wid

    def __browse_select_input_callback(self):
        file_names, _ = QFileDialog.getOpenFileNames(
            self,
            "QFileDialog.getOpenFileNames()",
            "",
            "Imaris Images (*.ims);;All Files (*)",
        )
        if file_names:
            self.input_files_edit.setText("\n".join(file_names))
            self.input_files_next_button.setEnabled(True)

    def __select_color_callback(self):
        color = QColorDialog.getColor()
        if color.isValid():
            self.new_channel_color_button.setStyleSheet(
                f"background-color :rgb({color.red()},{color.green()},{color.blue()})"
            )

    def __channel_arithmetic_wrapper(self):
        # Get the arithmetic expression after removing all whitespace
        arithmetic_expression = "".join(
            self.arithmetic_expression_text_edit.toPlainText().split())
        color = self.new_channel_color_button.palette().button().color()

        if arithmetic_expression:
            # Get the explicit channel indexes that appear in the expression and
            # check that they are in the valid range.
            channel_indexes = re.findall(self.channel_pattern,
                                         arithmetic_expression)
            invalid_channels = [
                ci for ci in channel_indexes
                if int(ci) not in range(self.max_channel_index + 1)
            ]
            if invalid_channels:
                self._error_function(
                    "The following channels specified in the arithmetic expression"
                    +
                    f" are outside the valid range [0,{self.max_channel_index}]: "
                    + ", ".join(invalid_channels))
                return

            # Disable the UI interaction during computation
            self.arithmetic_expression_text_edit.setReadOnly(True)
            self.slice_by_slice_checkbox.setEnabled(False)
            self.new_channel_name_line_edit.setReadOnly(True)
            self.new_channel_color_button.setEnabled(False)
            self.apply_button.setEnabled(False)
            self.processing_prev_button.setEnabled(False)

            QApplication.setOverrideCursor(Qt.WaitCursor)
            file_names = self.input_files_edit.toPlainText().split("\n")
            self.num_threads_left = len(file_names)
            for i, input_file_name in enumerate(file_names):
                # Configure and perform computation in another thread.
                arithmetic_calculator = ArithmeticCalculator(
                    self.channel_pattern)
                arithmetic_calculator.signals.finished.connect(
                    self.__arithmetic_finished)
                arithmetic_calculator.signals.processing_error.connect(
                    self._processing_error_function)
                arithmetic_calculator.signals.progress_signal.connect(
                    self.progress_grid_layout.itemAtPosition(
                        i, 1).widget().setValue)
                arithmetic_calculator.signals.update_state_signal.connect(
                    self.status_bar.showMessage)
                arithmetic_calculator.input_file_name = input_file_name
                arithmetic_calculator.arithmetic_expression = arithmetic_expression
                arithmetic_calculator.new_channel_color = [
                    color.red() / 255.0,
                    color.green() / 255.0,
                    color.blue() / 255.0,
                ]
                arithmetic_calculator.new_channel_alpha = color.alpha() / 255.0
                arithmetic_calculator.new_channel_name = (
                    self.new_channel_name_line_edit.text().strip())
                arithmetic_calculator.slice_by_slice = (
                    self.slice_by_slice_checkbox.isChecked())
                self.threadpool.start(arithmetic_calculator)
        else:
            self._error_function(
                "No action taken: arithmetic expression not set.")

    def __arithmetic_finished(self):
        self.num_threads_left = self.num_threads_left - 1
        if self.num_threads_left == 0:
            QApplication.restoreOverrideCursor()
            self.status_bar.clearMessage()
            for i in range(self.progress_grid_layout.rowCount()):
                self.progress_grid_layout.itemAtPosition(
                    i, 1).widget().setValue(0)
            # Enable the UI interaction after computation
            self.arithmetic_expression_text_edit.setReadOnly(False)
            self.slice_by_slice_checkbox.setEnabled(True)
            self.new_channel_name_line_edit.setReadOnly(False)
            self.new_channel_color_button.setEnabled(True)
            self.apply_button.setEnabled(True)
            self.processing_prev_button.setEnabled(True)

            # Inform the user that the calculations completed. If processing errors
            # occured then the desired operation may not have happened, but the
            # calculation was completed.
            QMessageBox().information(self, "Message",
                                      "Calculation completed.")
            self.processing_error = False
示例#18
0
class AudioInfoDialog(QDialog):
    def __init__(self,
                 audios_name,
                 audios_delay,
                 audios_language,
                 audios_track_name,
                 audios_set_default,
                 audios_set_forced,
                 audios_default_value_delay,
                 audios_default_value_language,
                 audios_default_value_track_name,
                 audios_default_value_set_default,
                 audios_default_value_set_forced,
                 audio_set_default_disabled=False,
                 audio_set_forced_disabled=False,
                 disable_edit=False,
                 parent=None):
        super().__init__(parent)
        self.window_title = "Audio Info"
        self.state = "no"
        self.audios_count = len(audios_delay)

        self.messageIcon = QLabel()
        self.audio_tab_comboBox = InfoCellDialogTabComboBox(
            hint="Audios Groups")
        for i in range(self.audios_count):
            self.audio_tab_comboBox.addItem("Audio #" + str(i + 1))
        self.audio_tab_comboBox.setCurrentIndex(0)
        self.audio_tab_comboBox.currentIndexChanged.connect(
            self.update_current_audio_index)
        self.current_audio_index = 0

        self.disable_edit = disable_edit
        self.current_audio_name = audios_name
        self.current_audio_language = audios_language
        self.current_audio_delay = audios_delay
        self.current_audio_track_name = audios_track_name
        self.current_audio_set_default = audios_set_default
        self.current_audio_set_forced = audios_set_forced

        self.default_audio_language = audios_default_value_language
        self.default_audio_delay = audios_default_value_delay
        self.default_audio_track_name = audios_default_value_track_name
        self.default_audio_set_default = audios_default_value_set_default
        self.default_audio_set_forced = audios_default_value_set_forced

        self.audio_set_default_disabled = audio_set_default_disabled
        self.audio_set_forced_disabled = audio_set_forced_disabled

        self.audio_name_label = QLabel("Audio Name:")
        self.audio_name_value = QLabel(
            str(self.current_audio_name[self.current_audio_index]))
        width_to_be_fixed = 0
        for i in range(len(self.current_audio_name)):
            width_to_be_fixed = max(
                width_to_be_fixed,
                self.audio_name_value.fontMetrics().boundingRect(
                    self.current_audio_name[i]).width())
        self.audio_name_value.setFixedWidth(width_to_be_fixed + 10)
        self.audio_delay_label = QLabel("Audio Delay:")
        self.audio_delay_spin = QDoubleSpinBox()
        self.setup_audio_delay_spin()

        self.audio_language_label = QLabel("Audio Language:")
        self.audio_language_comboBox = QComboBox()
        self.setup_audio_language_comboBox()

        self.audio_track_name_label = QLabel("Audio Track Name:")
        self.audio_track_name_lineEdit = QLineEdit()
        self.setup_audio_track_name_lineEdit()

        self.audio_set_forced_label = QLabel("Audio Forced State:")
        self.audio_set_forced_checkBox = QCheckBox()
        self.setup_audio_set_forced_checkBox()

        self.audio_set_default_label = QLabel("Audio Default State:")
        self.audio_set_default_checkBox = QCheckBox()
        self.setup_audio_set_default_checkBox()

        self.yes_button = QPushButton("OK")
        self.no_button = QPushButton("Cancel")
        self.reset_button = QPushButton("Reset To Default")

        self.buttons_layout = QHBoxLayout()
        self.audio_delay_layout = QHBoxLayout()
        self.audio_language_layout = QHBoxLayout()
        self.audio_track_name_layout = QHBoxLayout()
        self.audio_set_default_layout = QHBoxLayout()
        self.audio_set_forced_layout = QHBoxLayout()
        self.buttons_layout.addWidget(QLabel(""), stretch=3)
        self.buttons_layout.addWidget(self.reset_button, stretch=2)
        self.buttons_layout.addWidget(self.yes_button, stretch=2)
        self.buttons_layout.addWidget(self.no_button, stretch=2)
        self.buttons_layout.addWidget(QLabel(""), stretch=3)
        self.audio_setting_layout = QGridLayout()
        self.audio_editable_setting_layout = QFormLayout()
        self.audio_editable_setting_layout.addRow(self.audio_name_label,
                                                  self.audio_name_value)
        self.audio_editable_setting_layout.addRow(
            self.audio_track_name_label, self.audio_track_name_lineEdit)
        self.audio_editable_setting_layout.addRow(self.audio_language_label,
                                                  self.audio_language_comboBox)
        self.audio_editable_setting_layout.addRow(self.audio_delay_label,
                                                  self.audio_delay_spin)
        self.audio_editable_setting_layout.addRow(
            self.audio_set_default_label, self.audio_set_default_checkBox)
        self.audio_editable_setting_layout.addRow(
            self.audio_set_forced_label, self.audio_set_forced_checkBox)
        self.audio_setting_layout.addWidget(self.audio_tab_comboBox, 0, 0)
        self.audio_setting_layout.addLayout(self.audio_editable_setting_layout,
                                            1, 0, 5, 2)
        self.audio_setting_layout.addWidget(self.messageIcon, 1, 3, 5, -1)

        self.main_layout = QGridLayout()
        self.main_layout.addLayout(self.audio_setting_layout, 0, 0, 2, 3)
        self.main_layout.addLayout(self.buttons_layout, 2, 0, 1, -1)
        self.main_layout.setContentsMargins(20, 20, 20, 20)
        self.setLayout(self.main_layout)

        self.setup_ui()
        self.signal_connect()

    def setup_ui(self):
        self.disable_question_mark_window()
        self.messageIcon.setPixmap(
            QtGui.QPixmap(GlobalFiles.AudioIconPath).scaledToHeight(100))
        self.set_dialog_values()
        self.set_default_buttons()
        if self.audio_set_default_disabled:
            self.audio_set_default_disable()
        if self.audio_set_forced_disabled:
            self.audio_set_forced_disable()
        if self.disable_edit:
            self.audio_track_name_lineEdit.setEnabled(False)
            self.audio_language_comboBox.setEnabled(False)
            self.audio_delay_spin.setEnabled(False)
            self.audio_set_default_checkBox.setEnabled(False)
            self.audio_set_forced_checkBox.setEnabled(False)
            self.reset_button.setEnabled(False)

        self.setup_tool_tip_hint_audio_set_default()
        self.setup_tool_tip_hint_audio_set_forced()

    def signal_connect(self):
        self.audio_track_name_lineEdit.textEdited.connect(
            self.update_current_audio_track_name)
        self.audio_delay_spin.editingFinished.connect(
            self.update_current_audio_delay)
        self.audio_language_comboBox.currentTextChanged.connect(
            self.update_current_audio_language)
        self.audio_set_default_checkBox.stateChanged.connect(
            self.update_current_audio_set_default)
        self.audio_set_forced_checkBox.stateChanged.connect(
            self.update_current_audio_set_forced)
        self.yes_button.clicked.connect(self.click_yes)
        self.no_button.clicked.connect(self.click_no)
        self.reset_button.clicked.connect(self.reset_audio_setting)

    def click_yes(self):
        self.state = "yes"
        self.close()

    def click_no(self):
        self.state = "no"
        self.close()

    def set_dialog_values(self):
        self.setWindowTitle(self.window_title)
        self.setWindowIcon(GlobalFiles.InfoSettingIcon)

    def disable_question_mark_window(self):
        self.setWindowFlag(Qt.WindowContextHelpButtonHint, on=False)

    def increase_message_font_size(self, value):
        message_font = self.message.font()
        message_font.setPointSize(self.message.fontInfo().pointSize() + value)
        self.message.setFont(message_font)

    def set_default_buttons(self):
        self.yes_button.setDefault(True)
        self.yes_button.setFocus()

    def showEvent(self, a0: QtGui.QShowEvent) -> None:
        super().showEvent(a0)
        self.setFixedSize(self.size())

    def setup_audio_track_name_lineEdit(self):
        self.audio_track_name_lineEdit.setClearButtonEnabled(True)
        self.audio_track_name_lineEdit.setText(
            self.current_audio_track_name[self.current_audio_index])

    def setup_audio_language_comboBox(self):
        self.audio_language_comboBox.addItems(AllAudiosLanguages)
        self.audio_language_comboBox.setCurrentIndex(
            AllAudiosLanguages.index(
                self.current_audio_language[self.current_audio_index]))
        self.audio_language_comboBox.setMaxVisibleItems(8)
        self.audio_language_comboBox.setStyleSheet(
            "QComboBox { combobox-popup: 0; }")

    def setup_audio_delay_spin(self):
        # self.audio_delay_spin.setMaximumWidth(screen_size.width() // 16)
        self.audio_delay_spin.setDecimals(3)
        self.audio_delay_spin.setMinimum(-9999.0)
        self.audio_delay_spin.setMaximum(9999.0)
        self.audio_delay_spin.setSingleStep(0.5)
        self.audio_delay_spin.setValue(
            float(self.current_audio_delay[self.current_audio_index]))

    def setup_audio_set_default_checkBox(self):
        self.audio_set_default_checkBox.setText("Set Default")
        self.audio_set_default_checkBox.setChecked(
            bool(self.current_audio_set_default[self.current_audio_index]))

    def setup_audio_set_forced_checkBox(self):
        self.audio_set_forced_checkBox.setText("Set Forced")
        self.audio_set_forced_checkBox.setChecked(
            bool(self.current_audio_set_forced[self.current_audio_index]))

    def update_current_audio_track_name(self):
        self.current_audio_track_name[self.current_audio_index] = str(
            self.audio_track_name_lineEdit.text())

    def update_current_audio_delay(self):
        self.current_audio_delay[self.current_audio_index] = round(
            self.audio_delay_spin.value(), 5)

    def update_current_audio_language(self):
        self.current_audio_language[self.current_audio_index] = str(
            self.audio_language_comboBox.currentText())

    def update_current_audio_set_default(self):
        new_state = self.audio_set_default_checkBox.checkState() == Qt.Checked
        self.current_audio_set_default[self.current_audio_index] = new_state
        if new_state:
            for i in range(len(self.current_audio_set_default)):
                if i != self.current_audio_index:
                    self.current_audio_set_default[i] = False

    def update_current_audio_set_forced(self):
        new_state = self.audio_set_forced_checkBox.checkState() == Qt.Checked
        self.current_audio_set_forced[self.current_audio_index] = new_state
        if new_state:
            for i in range(len(self.current_audio_set_forced)):
                if i != self.current_audio_index:
                    self.current_audio_set_forced[i] = False

    def reset_audio_setting(self):
        self.current_audio_language[
            self.current_audio_index] = self.default_audio_language[
                self.current_audio_index]
        self.current_audio_delay[
            self.current_audio_index] = self.default_audio_delay[
                self.current_audio_index]
        self.current_audio_track_name[
            self.current_audio_index] = self.default_audio_track_name[
                self.current_audio_index]
        self.current_audio_set_default[
            self.current_audio_index] = self.default_audio_set_default[
                self.current_audio_index]
        self.current_audio_set_forced[
            self.current_audio_index] = self.default_audio_set_forced[
                self.current_audio_index]

        self.audio_language_comboBox.setCurrentIndex(
            AllAudiosLanguages.index(
                self.current_audio_language[self.current_audio_index]))
        self.audio_delay_spin.setValue(
            float(self.current_audio_delay[self.current_audio_index]))
        self.audio_track_name_lineEdit.setText(
            self.current_audio_track_name[self.current_audio_index])
        self.audio_set_default_checkBox.setChecked(
            bool(self.current_audio_set_default[self.current_audio_index]))
        self.audio_set_forced_checkBox.setChecked(
            bool(self.current_audio_set_forced[self.current_audio_index]))

    def audio_set_default_disable(self):
        self.audio_set_default_checkBox.setDisabled(True)

    def audio_set_forced_disable(self):
        self.audio_set_forced_checkBox.setDisabled(True)

    def setup_tool_tip_hint_audio_set_default(self):
        if self.audio_set_default_checkBox.isEnabled():
            self.audio_set_default_checkBox.setToolTip(
                "<nobr>set this audio to be the default audio track "
                "when play")
            self.audio_set_default_checkBox.setToolTipDuration(12000)
        else:
            self.audio_set_default_checkBox.setToolTip(
                "<nobr>set this audio to be the default audio track when play<br><b>Disabled</b> because "
                "option "
                "<b>make this audio default</b> is enabled on mux setting tab "
            )
            self.audio_set_default_checkBox.setToolTipDuration(12000)

    def setup_tool_tip_hint_audio_set_forced(self):
        if self.audio_set_forced_checkBox.isEnabled():
            self.audio_set_forced_checkBox.setToolTip(
                "<nobr>set this audio to be the forced audio track when "
                "play")
            self.audio_set_forced_checkBox.setToolTipDuration(12000)
        else:
            self.audio_set_forced_checkBox.setToolTip(
                "<nobr>set this audio to be the forced audio track when play<br><b>Disabled</b> because "
                "option "
                "<b>make this audio default and forced</b> is enabled on mux setting tab "
            )
            self.audio_set_forced_checkBox.setToolTipDuration(12000)

    def update_current_audio_index(self, new_index):
        self.current_audio_index = new_index
        self.audio_delay_spin.setValue(
            float(self.current_audio_delay[self.current_audio_index]))
        self.audio_set_default_checkBox.setChecked(
            bool(self.current_audio_set_default[self.current_audio_index]))
        self.audio_set_forced_checkBox.setChecked(
            bool(self.current_audio_set_forced[self.current_audio_index]))
        self.audio_language_comboBox.setCurrentIndex(
            AllAudiosLanguages.index(
                self.current_audio_language[self.current_audio_index]))
        self.audio_track_name_lineEdit.setText(
            self.current_audio_track_name[self.current_audio_index])
        self.audio_name_value.setText(
            str(self.current_audio_name[self.current_audio_index]))

    def execute(self):
        self.exec_()
示例#19
0
class PlotsWidget(ToolWidget):
    def __init__(self, image, parent=None):
        super(PlotsWidget, self).__init__(parent)

        choices = ['Red', 'Green', 'Blue', 'Hue', 'Saturation', 'Value']
        self.xaxis_combo = QComboBox()
        self.xaxis_combo.addItems(choices)
        self.xaxis_combo.setCurrentIndex(3)
        self.yaxis_combo = QComboBox()
        self.yaxis_combo.addItems(choices)
        self.yaxis_combo.setCurrentIndex(4)
        self.zaxis_combo = QComboBox()
        self.zaxis_combo.addItems(choices)
        self.zaxis_combo.setCurrentIndex(5)
        self.sampling_spin = QSpinBox()
        levels = int(np.log2(min(image.shape[:-1])))
        self.sampling_spin.setRange(0, levels)
        self.sampling_spin.setSpecialValueText(self.tr('Off'))
        # self.sampling_spin.setSuffix(self.tr(' level(s)'))
        self.sampling_spin.setValue(1)
        self.size_spin = QSpinBox()
        self.size_spin.setRange(1, 10)
        self.size_spin.setValue(1)
        self.size_spin.setSuffix(self.tr(' pt'))
        self.style_combo = QComboBox()
        self.markers = [',', '.', 'o', '8', 's', 'p', 'P', '*', 'h', 'H', 'X', 'D']
        self.style_combo.addItems(
            ['pixel', 'point', 'circle', 'octa', 'square', 'penta', 'plus', 'star', 'hexa1', 'hexa2', 'cross', 'diamond'])
        self.alpha_spin = QDoubleSpinBox()
        self.alpha_spin.setRange(0, 1)
        self.alpha_spin.setDecimals(2)
        self.alpha_spin.setSingleStep(0.05)
        self.alpha_spin.setValue(1)
        self.colors_check = QCheckBox(self.tr('Show colors'))
        self.grid_check = QCheckBox(self.tr('Show grid'))
        self.norm_check = QCheckBox(self.tr('Normalized'))
        self.total_label = QLabel()

        img = np.copy(image)
        self.colors = [None] * (levels + 1)
        for scale in range(levels + 1):
            rgb = cv.cvtColor(img.astype(np.float32) / 255, cv.COLOR_BGR2RGB)
            hsv = cv.cvtColor(rgb, cv.COLOR_RGB2HSV)
            hsv[:, :, 0] /= 360
            shape = (img.shape[0] * img.shape[1], img.shape[2])
            self.colors[scale] = np.concatenate((np.reshape(rgb, shape), np.reshape(hsv, shape)), axis=1)
            img = cv.pyrDown(img)

        figure2 = Figure()
        plot2_canvas = FigureCanvas(figure2)
        self.axes2 = plot2_canvas.figure.subplots()
        toolbar2 = NavigationToolbar(plot2_canvas, self)
        plot2_layout = QVBoxLayout()
        plot2_layout.addWidget(plot2_canvas)
        plot2_layout.addWidget(toolbar2)
        plot2_widget = QWidget()
        plot2_widget.setLayout(plot2_layout)

        figure3 = Figure()
        plot3_canvas = FigureCanvas(figure3)
        self.axes3 = plot3_canvas.figure.add_subplot(111, projection='3d')
        toolbar3 = NavigationToolbar(plot3_canvas, self)
        plot3_layout = QVBoxLayout()
        plot3_layout.addWidget(plot3_canvas)
        plot3_layout.addWidget(toolbar3)
        plot3_widget = QWidget()
        plot3_widget.setLayout(plot3_layout)

        self.tab_widget = QTabWidget()
        self.tab_widget.addTab(plot2_widget, '2D Plot')
        self.tab_widget.addTab(plot3_widget, '3D Plot')
        self.redraw()
        figure2.set_tight_layout(True)
        figure3.set_tight_layout(True)

        self.xaxis_combo.currentIndexChanged.connect(self.redraw)
        self.yaxis_combo.currentIndexChanged.connect(self.redraw)
        self.zaxis_combo.currentIndexChanged.connect(self.redraw)
        self.sampling_spin.valueChanged.connect(self.redraw)
        self.size_spin.valueChanged.connect(self.redraw)
        self.style_combo.currentIndexChanged.connect(self.redraw)
        self.alpha_spin.valueChanged.connect(self.redraw)
        self.colors_check.stateChanged.connect(self.redraw)
        self.grid_check.stateChanged.connect(self.redraw)
        self.norm_check.stateChanged.connect(self.redraw)
        self.tab_widget.currentChanged.connect(self.redraw)

        params_layout = QGridLayout()
        params_layout.addWidget(QLabel(self.tr('X axis:')), 0, 0)
        params_layout.addWidget(self.xaxis_combo, 0, 1)
        params_layout.addWidget(QLabel(self.tr('Y axis:')), 1, 0)
        params_layout.addWidget(self.yaxis_combo, 1, 1)
        params_layout.addWidget(QLabel(self.tr('Z axis:')), 2, 0)
        params_layout.addWidget(self.zaxis_combo, 2, 1)
        params_layout.addWidget(QLabel(self.tr('Subsampling:')), 0, 2)
        params_layout.addWidget(self.sampling_spin, 0, 3)
        params_layout.addWidget(QLabel(self.tr('Point size:')), 1, 2)
        params_layout.addWidget(self.size_spin, 1, 3)
        # params_layout.addWidget(QLabel(self.tr('Point style:')), 2, 2)
        # params_layout.addWidget(self.style_combo, 3, 4)
        params_layout.addWidget(QLabel(self.tr('Point alpha:')), 2, 2)
        params_layout.addWidget(self.alpha_spin, 2, 3)
        params_layout.addWidget(self.colors_check, 0, 4)
        params_layout.addWidget(self.grid_check, 1, 4)
        params_layout.addWidget(self.total_label, 2, 4)
        bottom_layout = QHBoxLayout()
        bottom_layout.addLayout(params_layout)
        bottom_layout.addStretch()

        main_layout = QVBoxLayout()
        main_layout.addWidget(self.tab_widget)
        main_layout.addLayout(bottom_layout)
        self.setLayout(main_layout)

    def redraw(self):
        start = time()
        v = self.sampling_spin.value()
        x = self.colors[v][:, self.xaxis_combo.currentIndex()]
        y = self.colors[v][:, self.yaxis_combo.currentIndex()]
        s = self.size_spin.value()**2
        c = None if not self.colors_check.isChecked() else self.colors[v][:, :3]

        if self.tab_widget.currentIndex() == 0:
            self.zaxis_combo.setEnabled(False)
            self.grid_check.setEnabled(True)
            self.alpha_spin.setEnabled(True)
            a = self.alpha_spin.value()
            xlim = self.axes2.get_xlim()
            ylim = self.axes2.get_ylim()
            self.axes2.clear()
            self.axes2.set_facecolor([0.5] * 3 if c is not None else [1.0] * 3)
            self.axes2.scatter(x, y, s, c, '.', alpha=a)
            self.axes2.set_xlabel(self.xaxis_combo.currentText())
            self.axes2.set_ylabel(self.yaxis_combo.currentText())
            self.axes2.grid(self.grid_check.isChecked(), which='both')
            self.axes2.set_xlim(xlim)
            self.axes2.set_ylim(ylim)
            self.axes2.figure.canvas.draw()
        else:
            self.zaxis_combo.setEnabled(True)
            self.grid_check.setEnabled(False)
            self.alpha_spin.setEnabled(False)
            z = self.colors[v][:, self.zaxis_combo.currentIndex()]
            self.axes3.clear()
            self.axes3.set_facecolor([0.5] * 3 if c is not None else [1.0] * 3)
            self.axes3.scatter(x, y, z, s=s, c=c, marker='.', depthshade=True)
            self.axes3.set_xlabel(self.xaxis_combo.currentText())
            self.axes3.set_ylabel(self.yaxis_combo.currentText())
            self.axes3.set_zlabel(self.zaxis_combo.currentText())
            self.axes3.grid(self.grid_check.isChecked(), which='both')
            self.axes3.figure.canvas.draw()

        self.total_label.setText(self.tr('[{} points]'.format(len(x))))
        self.info_message.emit('Plot redraw = {}'.format(elapsed_time(start)))
示例#20
0
class ComparisonWidget(ToolWidget):
    def __init__(self, image, parent=None):
        super(ComparisonWidget, self).__init__(parent)

        load_button = QPushButton(self.tr('Load reference...'))
        self.file_label = QLabel()
        modify_font(self.file_label, bold=True)
        self.comp_label = QLabel(self.tr('Comparison:'))
        self.normal_radio = QRadioButton(self.tr('Normal'))
        self.normal_radio.setChecked(True)
        self.diff_radio = QRadioButton(self.tr('Difference'))
        self.gray_check = QCheckBox(self.tr('Grayscale'))
        self.equal_check = QCheckBox(self.tr('Equalized'))
        self.map_radio = QRadioButton(self.tr('SSIM Map'))
        self.last_radio = self.normal_radio

        self.comp_label.setEnabled(False)
        self.normal_radio.setEnabled(False)
        self.diff_radio.setEnabled(False)
        self.gray_check.setEnabled(False)
        self.equal_check.setEnabled(False)
        self.map_radio.setEnabled(False)

        self.evidence = image
        self.reference = np.full_like(image, 127)
        self.difference = np.full_like(image, 127)
        self.equalized = np.full_like(image, 127)
        self.indexmap = np.full_like(image, 127)
        self.evidence_viewer = ImageViewer(self.evidence, None,
                                           self.tr('Evidence'))
        self.reference_viewer = ImageViewer(self.reference, None,
                                            self.tr('Reference'))

        self.table_widget = QTableWidget(5, 2)
        self.table_widget.setHorizontalHeaderLabels(
            [self.tr('Index'), self.tr('Value')])
        self.table_widget.setItem(0, 0, QTableWidgetItem(self.tr('MSE')))
        self.table_widget.setItem(1, 0, QTableWidgetItem(self.tr('COVAR')))
        self.table_widget.setItem(2, 0, QTableWidgetItem(self.tr('PSNR')))
        self.table_widget.setItem(3, 0, QTableWidgetItem(self.tr('SSIM')))
        self.table_widget.setItem(4, 0, QTableWidgetItem(self.tr('CORR')))
        for i in range(self.table_widget.rowCount()):
            modify_font(self.table_widget.item(i, 0), bold=True)
        self.table_widget.setEnabled(False)

        load_button.clicked.connect(self.load)
        self.normal_radio.toggled.connect(self.change)
        self.diff_radio.toggled.connect(self.change)
        self.gray_check.stateChanged.connect(self.change)
        self.equal_check.stateChanged.connect(self.change)
        self.map_radio.toggled.connect(self.change)
        self.evidence_viewer.view_changed.connect(
            self.reference_viewer.change_view)
        self.reference_viewer.view_changed.connect(
            self.evidence_viewer.change_view)

        top_layout = QHBoxLayout()
        top_layout.addWidget(load_button)
        top_layout.addWidget(self.file_label)
        top_layout.addStretch()
        top_layout.addWidget(self.comp_label)
        top_layout.addWidget(self.normal_radio)
        top_layout.addWidget(self.map_radio)
        top_layout.addWidget(self.diff_radio)
        top_layout.addWidget(self.gray_check)
        top_layout.addWidget(self.equal_check)

        index_layout = QVBoxLayout()
        index_label = QLabel(self.tr('Image Quality Assessment'))
        modify_font(index_label, bold=True)
        index_layout.addWidget(index_label)
        index_layout.addWidget(self.table_widget)

        center_layout = QHBoxLayout()
        center_layout.addWidget(self.evidence_viewer)
        center_layout.addWidget(self.reference_viewer)
        center_layout.addLayout(index_layout)

        main_layout = QVBoxLayout()
        main_layout.addLayout(top_layout)
        main_layout.addLayout(center_layout)
        self.setLayout(main_layout)

    def load(self):
        filename, basename, image = load_image(self)
        if filename is None:
            return
        if image.shape != self.evidence.shape:
            QMessageBox.critical(
                self, self.tr('Error'),
                self.tr('Evidence and reference must have the same size!'))
            return
        self.file_label.setText(basename)
        self.reference = image
        self.difference = normalize_mat(
            cv.absdiff(self.evidence, self.reference))
        self.equalized = cv.merge(
            [cv.equalizeHist(c) for c in cv.split(self.difference)])

        x = cv.cvtColor(self.evidence, cv.COLOR_BGR2GRAY).astype(np.float32)
        y = cv.cvtColor(self.reference, cv.COLOR_BGR2GRAY).astype(np.float32)
        mse = self.mse(x, y)
        covar = self.covar(x, y)
        psnr = self.psnr(mse)
        ssim, self.indexmap = self.ssim(x, y)
        corr = self.corr(x, y)
        self.table_widget.setItem(0, 1, QTableWidgetItem('{:.4f}'.format(mse)))
        self.table_widget.setItem(1, 1,
                                  QTableWidgetItem('{:.4f}'.format(covar)))
        if psnr > 0:
            self.table_widget.setItem(
                2, 1, QTableWidgetItem('{:.2f} dB'.format(psnr)))
        else:
            self.table_widget.setItem(
                2, 1, QTableWidgetItem('+' + u'\u221e' + ' dB'))
        self.table_widget.setItem(3, 1,
                                  QTableWidgetItem('{:.4f}'.format(ssim)))
        self.table_widget.setItem(4, 1,
                                  QTableWidgetItem('{:.4f}'.format(corr)))
        self.table_widget.setEnabled(True)

        self.comp_label.setEnabled(True)
        self.normal_radio.setEnabled(True)
        self.diff_radio.setEnabled(True)
        self.gray_check.setEnabled(True)
        self.equal_check.setEnabled(True)
        self.map_radio.setEnabled(True)
        self.change()

    def change(self):
        self.gray_check.setEnabled(False)
        self.equal_check.setEnabled(False)
        if self.normal_radio.isChecked():
            self.reference_viewer.update_original(self.reference)
            self.last_radio = self.normal_radio
        elif self.diff_radio.isChecked():
            self.gray_check.setEnabled(True)
            self.equal_check.setEnabled(True)
            if self.equal_check.isChecked():
                result = self.equalized
            else:
                result = self.difference
            if self.gray_check.isChecked():
                result = desaturate(result)
            self.reference_viewer.update_original(result)
            self.last_radio = self.diff_radio
        elif self.map_radio.isChecked():
            self.reference_viewer.update_original(self.indexmap)
            self.last_radio = self.map_radio
        else:
            self.last_radio.setChecked(True)

    @staticmethod
    def ssim(x, y):
        c1 = 6.5025
        c2 = 58.5225
        k = (11, 11)
        s = 1.5
        x2 = x**2
        y2 = y**2
        xy = x * y
        mu_x = cv.GaussianBlur(x, k, s)
        mu_y = cv.GaussianBlur(y, k, s)
        mu_x2 = mu_x**2
        mu_y2 = mu_y**2
        mu_xy = mu_x * mu_y
        s_x2 = cv.GaussianBlur(x2, k, s) - mu_x2
        s_y2 = cv.GaussianBlur(y2, k, s) - mu_y2
        s_xy = cv.GaussianBlur(xy, k, s) - mu_xy
        t1 = 2 * mu_xy + c1
        t2 = 2 * s_xy + c2
        t3 = t1 * t2
        t1 = mu_x2 + mu_y2 + c1
        t2 = s_x2 + s_y2 + c2
        t1 *= t2
        indexmap = cv.divide(t3, t1)
        ssim = cv.mean(indexmap)[0]
        return ssim, 255 - normalize_mat(indexmap, to_bgr=True)

    @staticmethod
    def corr(x, y):
        return np.corrcoef(x, y)[0, 1]

    @staticmethod
    def covar(x, y):
        return np.std(cv.absdiff(x, y))

    @staticmethod
    def mse(x, y):
        return cv.mean(cv.pow(x - y, 2))[0]

    @staticmethod
    def psnr(mse):
        k = math.sqrt(mse)
        if k == 0:
            return -1
        return 20 * math.log10((255**2) / k)
示例#21
0
文件: gui.py 项目: phyblas/mmdpaimaya
class Natang_mmdmaya(QWidget):
    '''
    mmdモデルからmayaへインポートするためのウィンドウ
    '''
    def __init__(self, parent):
        QWidget.__init__(self)
        self.setAcceptDrops(True)
        self.parent = parent
        self.setWindowFlags(Qt.WindowStaysOnTopHint)
        self.setWindowTitle('~ MMD > Maya ~')
        self.setStyleSheet('font-size: 15px; color: #ddf;')
        vbl = QVBoxLayout()
        self.setLayout(vbl)

        self.file_khatangton = os.path.join(os.path.dirname(__file__), 'asset',
                                            'khatangton1.txt')
        try:
            with open(self.file_khatangton, 'r', encoding='utf-8') as f:
                chue_tem_file = f.readline().split('=')[-1].strip()
                satsuan = f.readline().split('=')[-1].strip()
                yaek_poly = int(f.readline().split('=')[-1].strip())
                ao_bs = int(f.readline().split('=')[-1].strip())
                ao_kraduk = int(f.readline().split('=')[-1].strip())
                watsadu = int(f.readline().split('=')[-1].strip())
                pit_mai = int(f.readline().split('=')[-1].strip())
        except:
            chue_tem_file = ''
            satsuan = '8'
            yaek_poly = 0
            ao_bs = 1
            ao_kraduk = 1
            watsadu = 4
            pit_mai = 1

        hbl = QHBoxLayout()
        vbl.addLayout(hbl)
        hbl.addWidget(QLabel('ファイル'))
        self.le_chue_file = QLineEdit(chue_tem_file)
        hbl.addWidget(self.le_chue_file)
        self.le_chue_file.setFixedWidth(300)
        self.le_chue_file.textChanged.connect(self.chue_thuk_kae)
        self.btn_khon_file = QPushButton('...')
        hbl.addWidget(self.btn_khon_file)
        self.btn_khon_file.clicked.connect(self.khon_file)

        hbl = QHBoxLayout()
        vbl.addLayout(hbl)
        hbl.addWidget(QLabel('尺度'))
        self.le_satsuan = QLineEdit(satsuan)
        hbl.addWidget(self.le_satsuan)
        self.le_satsuan.setFixedWidth(100)
        self.le_satsuan.textEdited.connect(self.satsuan_thuk_kae)
        hbl.addWidget(QLabel('×'))
        hbl.addStretch()

        self.cb_yaek_poly = QCheckBox('材質ごとにポリゴンを分割する')
        vbl.addWidget(self.cb_yaek_poly)

        self.cb_ao_kraduk = QCheckBox('骨も作る')
        vbl.addWidget(self.cb_ao_kraduk)

        self.cb_ao_bs = QCheckBox('ブレンドシェープも作る')
        vbl.addWidget(self.cb_ao_bs)

        hbl = QHBoxLayout()
        vbl.addLayout(hbl)
        hbl.addWidget(QLabel('材質'))
        self.cbb_watsadu = QComboBox()
        hbl.addWidget(self.cbb_watsadu)
        self.cbb_watsadu.addItem('無い')
        self.cbb_watsadu.addItem('blinn')
        self.cbb_watsadu.addItem('phong')
        self.cbb_watsadu.addItem('lambert')
        self.cbb_watsadu.addItem('standardSurface')
        hbl.addStretch()

        hbl = QHBoxLayout()
        vbl.addLayout(hbl)
        hbl.addStretch()
        self.btn_roem_sang = QPushButton('作成開始')
        hbl.addWidget(self.btn_roem_sang)
        self.btn_roem_sang.clicked.connect(self.roem_sang)
        self.btn_roem_sang.setFixedSize(220, 50)
        self.chue_thuk_kae(self.le_chue_file.text())
        self.cb_pit = QCheckBox('終わったらこの\nウィンドウを閉じる')
        hbl.addWidget(self.cb_pit)

        self.cb_yaek_poly.setChecked(yaek_poly)
        self.cb_ao_kraduk.setChecked(ao_kraduk)
        self.cb_ao_bs.setChecked(ao_bs)
        self.cbb_watsadu.setCurrentIndex(watsadu)
        self.cb_yaek_poly.toggled.connect(
            lambda: self.chue_thuk_kae(self.le_chue_file.text()))
        self.cb_pit.setChecked(pit_mai)

    def chue_thuk_kae(self, chue_file):  # ファイルの名前が更新されたら
        sakun = chue_file.split('.')[-1]
        sang_dai = (sakun.lower() in ['pmd', 'pmx', 'x'])
        self.btn_roem_sang.setEnabled(sang_dai)
        self.btn_roem_sang.setStyleSheet(
            ['text-decoration: line-through; color: #aab;', ''][sang_dai])
        dai = chue_file[-2:] != '.x' and not self.cb_yaek_poly.isChecked()
        self.cb_ao_kraduk.setEnabled(dai)
        self.cb_ao_bs.setEnabled(dai)
        self.cb_ao_kraduk.setStyleSheet(
            ['text-decoration: line-through; color: #aab;', ''][dai])
        self.cb_ao_bs.setStyleSheet(
            ['text-decoration: line-through; color: #aab;', ''][dai])

    def khon_file(self):  # 読み込むファイルをブラウスする
        self.setWindowFlags(self.windowFlags() & ~Qt.WindowStaysOnTopHint)
        chue_file, ok = QFileDialog.getOpenFileName(
            filter='PMD/PMX/X (*.pmd *.pmx *.x)')
        if (ok):
            self.le_chue_file.setText(chue_file)
        self.setWindowFlags(self.windowFlags() | Qt.WindowStaysOnTopHint)
        self.show()

    def dragEnterEvent(self, e):
        if (e.mimeData().hasUrls()):  # ファイルがドラッグされたら使える
            e.accept()

    def dropEvent(self, e):
        self.le_chue_file.setText(
            e.mimeData().urls()[0].toLocalFile())  # ドラッグされたファイルの名前を取得する

    def satsuan_thuk_kae(self, kha):  # 尺度が更新されたら
        try:
            float(kha)
        except:
            self.le_satsuan.setText('1')

    def roem_sang(self):  # ボタンがクリックされたら、作成は開始
        chue_tem_file = self.le_chue_file.text()
        try:
            satsuan = float(self.le_satsuan.text())
        except:
            self.le_satsuan.setText('1')
            satsuan = 1.
        yaek_poly = self.cb_yaek_poly.isChecked()
        ao_bs = not yaek_poly and self.cb_ao_bs.isChecked()
        ao_kraduk = not yaek_poly and self.cb_ao_kraduk.isChecked()
        watsadu = self.cbb_watsadu.currentIndex()
        # ここでmayaのシーンの中でモデルを作る
        pmxpaimaya.sang(chue_tem_file, satsuan, yaek_poly, ao_bs, ao_kraduk,
                        watsadu)

        pit_mai = self.cb_pit.isChecked()  # 今回使った設定を保存しておく
        with open(self.file_khatangton, 'w', encoding='utf-8') as f:
            f.write('ファイルの名前 = %s\n' % chue_tem_file)
            f.write('尺度 = %f\n' % satsuan)
            f.write('ポリゴンの分割 = %d\n' % yaek_poly)
            f.write('ブレンドシェープ = %d\n' % ao_bs)
            f.write('ジョイント = %d\n' % ao_kraduk)
            f.write('材質 = %d\n' % watsadu)
            f.write('閉じる = %d\n' % pit_mai)

        if (pit_mai):  # 終わったらこのウィンドウを閉じる
            self.close()

    def keyPressEvent(self, e):  # escが押されたら閉じる
        if (e.key() == Qt.Key_Escape):
            self.close()

    def closeEvent(self, e):
        self.parent.natangyoi['mmdmaya'] = None
class PreferencesGeneralPage(QWidget):

    preferencesChanged = Signal()


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

        # Title
        title = QLabel(self.tr("<strong style=\"font-size:large;\">{0}</strong>").format(self.title()))

        #
        # Content: Geometry & State

        self._chkRestoreApplicationGeometry = QCheckBox(self.tr("Save and restore the application geometry"))
        self._chkRestoreApplicationGeometry.stateChanged.connect(self._onPreferencesChanged)

        self._chkRestoreApplicationState = QCheckBox(self.tr("Save and restore the application state"))
        self._chkRestoreApplicationState.stateChanged.connect(self._onPreferencesChanged)

        geometryStateLayout = QVBoxLayout()
        geometryStateLayout.addWidget(self._chkRestoreApplicationGeometry)
        geometryStateLayout.addWidget(self._chkRestoreApplicationState)

        geometryStateGroup = QGroupBox(self.tr("Geometry && State"))
        geometryStateGroup.setLayout(geometryStateLayout)

        #
        # Content: Recently Opened Documents

        self._spbMaximumRecentDocuments = QSpinBox()
        self._spbMaximumRecentDocuments.setRange(0, 25)
        self._spbMaximumRecentDocuments.setToolTip(self.tr("Maximum number of recently opened documents"))
        self._spbMaximumRecentDocuments.valueChanged.connect(self._onPreferencesChanged)
        self._spbMaximumRecentDocuments.valueChanged[int].connect(self._onMaximumRecentDocumentsChanged)

        self._chkRestoreRecentDocuments = QCheckBox(self.tr("Save and restore documents"))
        self._chkRestoreRecentDocuments.stateChanged.connect(self._onPreferencesChanged)

        recentDocumentsFormLayout = QFormLayout()
        recentDocumentsFormLayout.addRow(self.tr("Number of documents"), self._spbMaximumRecentDocuments)

        recentDocumentsLayout = QVBoxLayout()
        recentDocumentsLayout.addLayout(recentDocumentsFormLayout)
        recentDocumentsLayout.addWidget(self._chkRestoreRecentDocuments)

        recentDocumentsGroup = QGroupBox(self.tr("Recently Opened Documents"))
        recentDocumentsGroup.setLayout(recentDocumentsLayout)

        # Main layout
        self._layout = QVBoxLayout(self)
        self._layout.addWidget(title)
        self._layout.addWidget(geometryStateGroup)
        self._layout.addWidget(recentDocumentsGroup)
        self._layout.addStretch(1)


    def setZeroMargins(self):

        self._layout.setContentsMargins(0, 0, 0, 0)


    def title(self):

        return self.tr("General")


    def _onPreferencesChanged(self):

        self.preferencesChanged.emit()


    def _onMaximumRecentDocumentsChanged(self, val):

        self._chkRestoreRecentDocuments.setEnabled(val > 0)


    def setRestoreApplicationGeometry(self, checked):

        self._chkRestoreApplicationGeometry.setChecked(checked)


    def restoreApplicationGeometry(self):

        return self._chkRestoreApplicationGeometry.isChecked()


    def setRestoreApplicationState(self, checked):

        self._chkRestoreApplicationState.setChecked(checked)


    def restoreApplicationState(self):

        return self._chkRestoreApplicationState.isChecked()


    def setMaximumRecentDocuments(self, val):

        self._spbMaximumRecentDocuments.setValue(val)


    def maximumRecentDocuments(self):

        return self._spbMaximumRecentDocuments.value()


    def setRestoreRecentDocuments(self, checked):

        self._chkRestoreRecentDocuments.setChecked(checked)


    def restoreRecentDocuments(self):

        return self._chkRestoreRecentDocuments.isChecked()
示例#23
0
class TrainingPanel(Panel):

    def __init__(self, datasets, testing_panel, threads):
        super().__init__()
        if isinstance(testing_panel, TestingPanel):
            self.testing_panel = testing_panel
        else:
            raise TypeError('"testing_panel" must be the instance of '
                            '"TestingPanel"')
        self.datasets = datasets
        self.threads = threads

        self.__set_execution_ui()
        self.__set_options_ui()
        self.__set_outputs_ui()
        self.__set_graphic_ui()

    def __set_execution_ui(self):
        group_box = QGroupBox('Training Execution')
        inner_layout = QHBoxLayout()
        group_box.setLayout(inner_layout)

        self.data_selector = QComboBox()
        self.data_selector.addItems(list(self.datasets.keys()))
        self.data_selector.setStatusTip('Select the training dataset.')

        self.start_btn = QPushButton('Train')
        self.start_btn.setStatusTip('Start training.')
        self.start_btn.clicked.connect(self.__run)

        self.stop_btn = QPushButton('Stop')
        self.stop_btn.setStatusTip('Force the training stop running.')
        self.stop_btn.setDisabled(True)

        self.multicore_cb = QCheckBox('Multicore')
        self.multicore_cb.setStatusTip('Use multiprocessing in calculating '
                                       'fitting for populations.')
        self.multicore_cb.setChecked(True)

        inner_layout.addWidget(self.data_selector, 1)
        inner_layout.addWidget(self.start_btn)
        inner_layout.addWidget(self.stop_btn)
        inner_layout.addWidget(self.multicore_cb)

        self._layout.addWidget(group_box)

    def __set_options_ui(self):
        group_box = QGroupBox('Training Options')
        inner_layout = QFormLayout()
        group_box.setLayout(inner_layout)

        self.iter_times = QSpinBox()
        self.iter_times.setRange(1, 1000000)
        self.iter_times.setValue(200)
        self.iter_times.setStatusTip('The total iterating times for training.')

        self.population_size = QSpinBox()
        self.population_size.setRange(1, 100000)
        self.population_size.setValue(100)
        self.population_size.setStatusTip('The population size for the PSO.')

        self.inertia_weight = QDoubleSpinBox()
        self.inertia_weight.setRange(0, 50)
        self.inertia_weight.setValue(1)
        self.inertia_weight.setSingleStep(0.1)
        self.inertia_weight.setStatusTip('The inertia weight of the velocity '
                                         ' for each individual.')

        self.cognitive_const_rand_upper = QDoubleSpinBox()
        self.cognitive_const_rand_upper.setRange(0, 50)
        self.cognitive_const_rand_upper.setValue(2)
        self.cognitive_const_rand_upper.setSingleStep(0.1)
        self.cognitive_const_rand_upper.setStatusTip(
            'The random upper bound for cognitive accelerate constant.')

        self.social_const_rand_upper = QDoubleSpinBox()
        self.social_const_rand_upper.setRange(0, 50)
        self.social_const_rand_upper.setValue(3)
        self.social_const_rand_upper.setSingleStep(0.1)
        self.social_const_rand_upper.setStatusTip(
            'The random upper bound for social accelerate constant.')

        self.v_max = QDoubleSpinBox()
        self.v_max.setRange(0.5, 100)
        self.v_max.setValue(5)
        self.v_max.setSingleStep(1)
        self.v_max.setStatusTip('The maximum of velocity for each individual.')

        self.nneuron = QSpinBox()
        self.nneuron.setRange(1, 100)
        self.nneuron.setValue(6)
        self.nneuron.setStatusTip('The number of RBFN neuron.')

        self.sd_max = QDoubleSpinBox()
        self.sd_max.setRange(0.01, 20)
        self.sd_max.setValue(10)
        self.sd_max.setSingleStep(0.1)
        self.sd_max.setStatusTip('The random range maximum of standard '
                                 'deviation of each neuron in RBFN (only for '
                                 'initialization).')

        inner_layout.addRow('Iterating Times:', self.iter_times)
        inner_layout.addRow('Population Size:', self.population_size)
        inner_layout.addRow('Inertia Weight:', self.inertia_weight)
        inner_layout.addRow('Cognitive Const Upper:',
                            self.cognitive_const_rand_upper)
        inner_layout.addRow('Social Const Upper:',
                            self.social_const_rand_upper)
        inner_layout.addRow('Maximum of Velocity:', self.v_max)
        inner_layout.addRow('Number of Neuron:', self.nneuron)
        inner_layout.addRow('Maximum of SD:', self.sd_max)

        self._layout.addWidget(group_box)

    def __set_outputs_ui(self):
        group_box = QGroupBox('Training Details')
        inner_layout = QFormLayout()
        group_box.setLayout(inner_layout)

        self.current_iter_time = QLabel('--')
        self.current_error = QLabel('--')
        self.avg_error = QLabel('--')
        self.global_best_error = QLabel('--')
        self.total_best_error = QLabel('--')
        self.progressbar = QProgressBar()

        self.current_iter_time.setAlignment(Qt.AlignCenter)
        self.current_error.setAlignment(Qt.AlignCenter)
        self.avg_error.setAlignment(Qt.AlignCenter)
        self.global_best_error.setAlignment(Qt.AlignCenter)
        self.total_best_error.setAlignment(Qt.AlignCenter)

        self.current_iter_time.setStatusTip('The current iterating time of '
                                            'the PSO.')
        self.current_error.setStatusTip('The current error from the fitting '
                                        'function. ("( )": normalized error)')
        self.avg_error.setStatusTip('The average error from the fitting '
                                    'function in current iteration.  ("( )": '
                                    'normalized error)')
        self.global_best_error.setStatusTip(
            'The error of global best individual from the fitting function in '
            'current iteration.  ("( )": normalized error)')
        self.total_best_error.setStatusTip(
            'The error of total best individual from the fitting function in '
            'training.  ("( )": normalized error)')

        inner_layout.addRow('Current Iterating Time:', self.current_iter_time)
        inner_layout.addRow('Current Error:', self.current_error)
        inner_layout.addRow('Average Error:', self.avg_error)
        inner_layout.addRow('Global Best Error:', self.global_best_error)
        inner_layout.addRow('Total Best Error:', self.total_best_error)
        inner_layout.addRow(self.progressbar)

        self._layout.addWidget(group_box)

    def __set_graphic_ui(self):
        group_box = QGroupBox('Error Line Charts:')
        inner_layout = QVBoxLayout()
        group_box.setLayout(inner_layout)

        self.err_chart = ErrorLineChart(1)
        self.err_chart.setStatusTip('The history of error from the fitting '
                                    'of the PSO for each data.')
        self.__err_x = 1

        self.iter_err_chart = ErrorLineChart(
            3, ('Avg', 'Global Best', 'Total Best'))
        self.iter_err_chart.setStatusTip('The history of average and least '
                                         'error from the fitting of the PSO '
                                         'for each iteration.')
        self.iter_err_chart.setMinimumHeight(150)

        inner_layout.addWidget(QLabel('Current Error'))
        inner_layout.addWidget(self.err_chart)
        inner_layout.addWidget(QLabel('Average Error'))
        inner_layout.addWidget(self.iter_err_chart)
        self._layout.addWidget(group_box)

    @Slot()
    def __init_widgets(self):
        self.start_btn.setDisabled(True)
        self.stop_btn.setEnabled(True)
        self.multicore_cb.setDisabled(True)
        self.data_selector.setDisabled(True)
        self.iter_times.setDisabled(True)
        self.population_size.setDisabled(True)
        self.inertia_weight.setDisabled(True)
        self.cognitive_const_rand_upper.setDisabled(True)
        self.social_const_rand_upper.setDisabled(True)
        self.v_max.setDisabled(True)
        self.nneuron.setDisabled(True)
        self.sd_max.setDisabled(True)
        self.err_chart.clear()
        self.iter_err_chart.clear()
        self.__err_x = 1

    @Slot()
    def __reset_widgets(self):
        self.start_btn.setEnabled(True)
        self.stop_btn.setDisabled(True)
        self.multicore_cb.setEnabled(True)
        self.data_selector.setEnabled(True)
        self.iter_times.setEnabled(True)
        self.population_size.setEnabled(True)
        self.inertia_weight.setEnabled(True)
        self.cognitive_const_rand_upper.setEnabled(True)
        self.social_const_rand_upper.setEnabled(True)
        self.v_max.setEnabled(True)
        self.nneuron.setEnabled(True)
        self.sd_max.setEnabled(True)
        self.progressbar.setMinimum(0)
        self.progressbar.setMaximum(100)

    @Slot()
    def __indicate_busy(self):
        self.progressbar.setMinimum(0)
        self.progressbar.setMaximum(0)

    @Slot(int)
    def __show_current_iter_time(self, value):
        self.current_iter_time.setText(str(value + 1))
        self.progressbar.setValue(value + 1)

    @Slot(float)
    def __show_current_error(self, value):
        self.current_error.setText('{:.5f} ({:.5f})'.format(value, value / 40))
        self.err_chart.append_point(self.__err_x, value)
        self.__err_x += 1

    @Slot(float, float, float)
    def __show_iter_error(self, avg, glob, total):
        self.avg_error.setText('{:.5f} ({:.5f})'.format(avg, avg / 40))
        self.global_best_error.setText(
            '{:.5f} ({:.5f})'.format(glob, glob / 40))
        self.total_best_error.setText(
            '{:.5f} ({:.5f})'.format(total, total / 40))
        self.iter_err_chart.append_point(
            int(self.current_iter_time.text()), total, 2)
        self.iter_err_chart.append_point(
            int(self.current_iter_time.text()), glob, 1)
        self.iter_err_chart.append_point(
            int(self.current_iter_time.text()), avg, 0)

    def __run(self):
        self.progressbar.setMaximum(self.iter_times.value())

        self.__current_dataset = self.datasets[
            self.data_selector.currentText()]

        self.__pso = PSO(self.iter_times.value(), self.population_size.value(),
                         self.inertia_weight.value(),
                         self.cognitive_const_rand_upper.value(),
                         self.social_const_rand_upper.value(),
                         self.v_max.value(), self.nneuron.value(),
                         self.__current_dataset, self.sd_max.value(),
                         is_multicore=self.multicore_cb.isChecked())
        self.threads.append(self.__pso)
        self.stop_btn.clicked.connect(self.__pso.stop)
        self.__pso.started.connect(self.__init_widgets)
        self.__pso.finished.connect(self.__reset_widgets)
        self.__pso.sig_current_iter_time.connect(self.__show_current_iter_time)
        self.__pso.sig_current_error.connect(self.__show_current_error)
        self.__pso.sig_iter_error.connect(self.__show_iter_error)
        self.__pso.sig_indicate_busy.connect(self.__indicate_busy)
        self.__pso.sig_console.connect(self.testing_panel.print_console)
        self.__pso.sig_rbfn.connect(self.testing_panel.load_rbfn)
        self.__pso.start()
示例#24
0
class MainWindow(QMainWindow):

    def __init__(self, app, parent=None):
        super(MainWindow, self).__init__(parent)
        self.imagesDir = app.dir + '/images/'
        self.setWindowIcon(QIcon(self.imagesDir + 'icon.png'))
        self.path = ''

        self.settings = QSettings()
        self.lastDir = self.settings.value('lastDir', '')

        self.setMinimumWidth(540)

        self.supportedFormats = []
        for f in QImageReader.supportedImageFormats():
            self.supportedFormats.append(str(f.data(), encoding="utf-8"))

        self.fileWatcher = QFileSystemWatcher()
        self.fileWatcher.fileChanged.connect(self.fileChanged)

        # widgets
        self.showPixmapWidget = None

        self.tileWidthSpinBox = QSpinBox()
        self.tileWidthSpinBox.setValue(16)
        self.tileWidthSpinBox.setFixedWidth(50)
        self.tileWidthSpinBox.setMinimum(1)

        self.tileHeightSpinBox = QSpinBox()
        self.tileHeightSpinBox.setValue(16)
        self.tileHeightSpinBox.setFixedWidth(50)
        self.tileHeightSpinBox.setMinimum(1)

        self.paddingSpinBox = QSpinBox()
        self.paddingSpinBox.setFixedWidth(50)
        self.paddingSpinBox.setMinimum(1)

        self.transparentCheckbox = QCheckBox("Transparent")
        self.transparentCheckbox.setChecked(True)
        self.transparentCheckbox.stateChanged.connect(self.transparentChanged)

        self.backgroundColorEdit = ColorEdit()
        self.backgroundColorEdit.setEnabled(False)
        self.backgroundColorLabel = QLabel("Background color:")
        self.backgroundColorLabel.setEnabled(False)

        self.forcePotCheckBox = QCheckBox("Force PoT")
        self.forcePotCheckBox.setChecked(True)
        self.forcePotCheckBox.stateChanged.connect(self.forcePotChanged)

        self.reorderTilesCheckBox = QCheckBox("Reorder tiles")

        self.generateAndExportButton = QPushButton("Generate and export")
        self.generateAndExportButton.setFixedHeight(32)
        self.generateAndExportButton.clicked.connect(self.generateAndExportClicked)
        self.generateAndExportButton.setEnabled(False)

        self.pixmapWidget = PixmapWidget()
        self.pixmapWidget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        self.pixmapWidget.setPixmap(self.createDropTextPixmap())
        self.pixmapWidget.dropSignal.connect(self.fileDropped)
        self.pixmapWidget.setMinimumHeight(300)

        # load settings
        self.tileWidthSpinBox.setValue(int(self.settings.value('tileWidth', 16)))
        self.tileHeightSpinBox.setValue(int(self.settings.value('tileHeight', 16)))
        self.paddingSpinBox.setValue(int(self.settings.value('padding', 1)))
        self.forcePotCheckBox.setChecked(True if self.settings.value('forcePot', 'true') == 'true' else False)
        self.reorderTilesCheckBox.setChecked(True if self.settings.value('reorderTiles', 'false') == 'true' else False)
        self.transparentCheckbox.setChecked(True if self.settings.value('transparent', 'false') == 'true' else False)
        self.backgroundColorEdit.setColorText(str(self.settings.value('backgroundColor', '#FF00FF')))
        self.restoreGeometry(QByteArray(self.settings.value('MainWindow/geometry')))
        self.restoreState(QByteArray(self.settings.value('MainWindow/windowState')))

        # layout
        hl1 = QHBoxLayout()
        hl1.setContentsMargins(5, 5, 5, 5)
        hl1.addWidget(QLabel("Tile width:"))
        hl1.addSpacing(5)
        hl1.addWidget(self.tileWidthSpinBox)
        hl1.addSpacing(15)
        hl1.addWidget(QLabel("Tile height:"))
        hl1.addSpacing(5)
        hl1.addWidget(self.tileHeightSpinBox)
        hl1.addSpacing(15)
        hl1.addWidget(QLabel("Padding:"))
        hl1.addSpacing(5)
        hl1.addWidget(self.paddingSpinBox)
        hl1.addSpacing(15)
        hl1.addWidget(self.forcePotCheckBox)
        hl1.addSpacing(15)
        hl1.addWidget(self.reorderTilesCheckBox)
        hl1.addStretch()

        hl2 = QHBoxLayout()
        hl2.setContentsMargins(5, 5, 5, 5)
        hl2.addWidget(self.transparentCheckbox)
        hl2.addSpacing(15)
        hl2.addWidget(self.backgroundColorLabel)
        hl2.addSpacing(5)
        hl2.addWidget(self.backgroundColorEdit)
        hl2.addStretch()

        hl3 = QHBoxLayout()
        hl3.setContentsMargins(5, 5, 5, 5)
        hl3.addWidget(self.generateAndExportButton)

        vl = QVBoxLayout()
        vl.setContentsMargins(0, 0, 0, 0)
        vl.setSpacing(0)
        vl.addLayout(hl1)
        vl.addLayout(hl2)
        vl.addWidget(self.pixmapWidget)
        vl.addLayout(hl3)

        w = QWidget()
        w.setLayout(vl)
        self.setCentralWidget(w)

        self.setTitle()

    def setTitle(self):
        p = ' - ' + os.path.basename(self.path) if self.path else ''
        self.setWindowTitle(QCoreApplication.applicationName() + ' ' + QCoreApplication.applicationVersion() + p)

    def createDropTextPixmap(self):
        pixmap = QPixmap(481, 300)
        pixmap.fill(QColor("#333333"))
        painter = QPainter(pixmap)
        font = QFont("Arial")
        font.setPixelSize(28)
        font.setBold(True)
        fm = QFontMetrics(font)
        painter.setFont(font)
        painter.setPen(QPen(QColor("#888888"), 1))
        text = "Drop the tileset image here"
        x = (pixmap.width()-fm.width(text))/2
        y = (pixmap.height()+fm.height())/2
        painter.drawText(x, y, text)
        del painter
        return pixmap

    def fileDropped(self, path):
        path = str(path)
        name, ext = os.path.splitext(path)
        ext = ext[1:]
        if not ext in self.supportedFormats:
            QMessageBox.warning(self, "Warning", "The dropped file is not supported")
            return
        pixmap = QPixmap(path)
        if pixmap.isNull():
            QMessageBox.warning(self, "Warning", "Can't load the image")
            return
        if self.path:
            self.fileWatcher.removePath(self.path)
        self.path = path
        self.fileWatcher.addPath(self.path)
        self.pixmapWidget.setPixmap(pixmap)
        self.generateAndExportButton.setEnabled(True)
        self.setTitle()
        self.activateWindow()

    def fileChanged(self, path):
        #self.fileDropped(path)
        pass

    def transparentChanged(self):
        e = self.transparentCheckbox.isChecked()
        self.backgroundColorEdit.setEnabled(not e)
        self.backgroundColorLabel.setEnabled(not e)

    def forcePotChanged(self):
        e = self.forcePotCheckBox.isChecked()
        self.reorderTilesCheckBox.setEnabled(e)

    def generateAndExportClicked(self):

        g = Generator()
        g.tileWidth = self.tileWidthSpinBox.value()
        g.tileHeight = self.tileHeightSpinBox.value()
        g.forcePot = self.forcePotCheckBox.isChecked()
        g.isTransparent = self.transparentCheckbox.isChecked()
        g.bgColor = self.backgroundColorEdit.getColor()
        g.reorder = self.reorderTilesCheckBox.isChecked()
        g.padding = self.paddingSpinBox.value()

        target = g.create(self.pixmapWidget.pixmap);

        # export
        self.lastDir = os.path.dirname(self.path)
        targetPath = QFileDialog.getSaveFileName(self, 'Export', self.lastDir, 'PNG (*.png)')
        if targetPath:
            target.save(targetPath[0])
            showPixmap = QPixmap.fromImage(target)
            if self.showPixmapWidget:
                self.showPixmapWidget.deleteLater()
                del self.showPixmapWidget
            self.showPixmapWidget = PixmapWidget()
            self.showPixmapWidget.setWindowIcon(self.windowIcon())
            self.showPixmapWidget.setWindowTitle(os.path.basename(targetPath[0]))
            self.showPixmapWidget.resize(showPixmap.width(), showPixmap.height())
            self.showPixmapWidget.setPixmap(showPixmap)
            self.showPixmapWidget.show()

    def closeEvent(self, event):
        if self.showPixmapWidget:
            self.showPixmapWidget.close()

        # save settings
        self.settings.setValue('tileWidth', self.tileWidthSpinBox.value())
        self.settings.setValue('tileHeight', self.tileHeightSpinBox.value())
        self.settings.setValue('padding', self.paddingSpinBox.value())
        self.settings.setValue('forcePot', self.forcePotCheckBox.isChecked())
        self.settings.setValue('reorderTiles', self.reorderTilesCheckBox.isChecked())
        self.settings.setValue('transparent', self.transparentCheckbox.isChecked())
        self.settings.setValue('backgroundColor', self.backgroundColorEdit.getColor().name())
        self.settings.setValue('lastDir', self.lastDir)
        self.settings.setValue('MainWindow/geometry', self.saveGeometry())
        self.settings.setValue('MainWindow/windowState', self.saveState())

        super(MainWindow, self).closeEvent(event)
示例#25
0
class NGL_HKLViewer(QWidget):
    def __init__(self, parent=None):
        super(NGL_HKLViewer, self).__init__(parent)

        self.verbose = 0
        self.UseOSbrowser = False
        self.jscriptfname = ""
        self.devmode = False
        for e in sys.argv:
            if "verbose" in e:
                self.verbose = e.split("verbose=")[1]
            if "UseOSbrowser" in e:
                self.UseOSbrowser = e.split("UseOSbrowser=")[1]
            if "jscriptfname" in e:
                self.jscriptfname = e.split("jscriptfname=")[1]
            if "devmode" in e:
                self.devmode = True

        self.zmq_context = None
        self.bufsize = 20000

        self.originalPalette = QApplication.palette()

        self.openFileNameButton = QPushButton("Load reflection file")
        self.openFileNameButton.setDefault(True)
        self.openFileNameButton.clicked.connect(self.OpenReflectionsFile)

        self.debugbutton = QPushButton("Debug")
        self.debugbutton.clicked.connect(self.DebugInteractively)

        self.settingsbtn = QPushButton("Settings")
        self.settingsbtn.clicked.connect(self.SettingsDialog)

        self.mousemoveslider = QSlider(Qt.Horizontal)
        self.mousemoveslider.setMinimum(0)
        self.mousemoveslider.setMaximum(300)
        self.mousemoveslider.setValue(0)
        self.mousemoveslider.sliderReleased.connect(
            self.onFinalMouseSensitivity)
        self.mousemoveslider.valueChanged.connect(self.onMouseSensitivity)
        self.mousesensitxtbox = QLineEdit('')
        self.mousesensitxtbox.setReadOnly(True)
        self.fontspinBox = QDoubleSpinBox()
        self.fontspinBox.setSingleStep(1)
        self.fontspinBox.setRange(4, 50)
        self.font = QFont()
        self.font.setFamily(self.font.defaultFamily())
        self.fontspinBox.setValue(self.font.pointSize())
        #self.fontspinBox.setValue(self.font.pixelSize())
        self.fontspinBox.valueChanged.connect(self.onFontsizeChanged)
        self.Fontsize_labeltxt = QLabel()
        self.Fontsize_labeltxt.setText("Font size:")

        self.cameraPerspectCheckBox = QCheckBox()
        self.cameraPerspectCheckBox.setText("Perspective camera")
        self.cameraPerspectCheckBox.clicked.connect(self.onCameraPerspect)
        self.cameraPerspectCheckBox.setCheckState(Qt.Unchecked)

        self.settingsform = SettingsForm(self)

        self.MillerComboBox = QComboBox()
        self.MillerComboBox.activated.connect(self.onMillerComboSelchange)
        #self.MillerComboBox.setSizeAdjustPolicy(QComboBox.AdjustToContents)

        self.MillerLabel = QLabel()
        self.MillerLabel.setText("Selected HKL Scene")

        self.HKLnameedit = QLineEdit('')
        self.HKLnameedit.setReadOnly(True)
        self.textInfo = QTextEdit()
        self.textInfo.setLineWrapMode(QTextEdit.NoWrap)
        self.textInfo.setReadOnly(True)

        labels = [
            "Label", "Type", "no. of HKLs", "Span of HKLs", "Min Max data",
            "Min Max sigmas", "d_min, d_max", "Symmetry unique", "Anomalous"
        ]
        self.millertable = QTableWidget(0, len(labels))
        self.millertable.setHorizontalHeaderLabels(labels)
        self.millertable.horizontalHeader().setDefaultAlignment(Qt.AlignLeft)
        # don't allow editing this table
        self.millertable.setEditTriggers(QTableWidget.NoEditTriggers)

        self.createExpansionBox()
        self.createFileInfoBox()
        self.CreateSliceTabs()
        self.createRadiiScaleGroupBox()
        self.createBinsBox()
        self.CreateFunctionTabs()

        mainLayout = QGridLayout()
        mainLayout.addWidget(self.FileInfoBox, 0, 0)
        mainLayout.addWidget(self.MillerLabel, 1, 0)
        mainLayout.addWidget(self.MillerComboBox, 2, 0)
        mainLayout.addWidget(self.functionTabWidget, 3, 0)
        mainLayout.addWidget(self.settingsbtn, 4, 0, 1, 1)

        #import code, traceback; code.interact(local=locals(), banner="".join( traceback.format_stack(limit=10) ) )
        if self.UseOSbrowser == False:
            self.BrowserBox = QWebEngineView()
            mainLayout.addWidget(self.BrowserBox, 0, 1, 5, 3)
            self.BrowserBox.setUrl("https://cctbx.github.io/")
            #self.BrowserBox.setUrl("https://webglreport.com/")
            #self.BrowserBox.loadFinished.connect(self.onLoadFinished)
            mainLayout.setColumnStretch(2, 1)

        mainLayout.setRowStretch(0, 1)
        mainLayout.setRowStretch(1, 0)
        mainLayout.setRowStretch(2, 1)
        mainLayout.setRowStretch(3, 1)
        mainLayout.setColumnStretch(4, 0)
        self.setLayout(mainLayout)

        self.setWindowTitle("HKL-Viewer")
        self.cctbxproc = None
        self.LaunchCCTBXPython()
        self.out = None
        self.err = None
        self.comboviewwidth = 0
        self.hklscenes_arrays = []
        self.array_infotpls = []
        self.matching_arrays = []
        self.bin_infotpls = None
        self.bin_opacities = None
        self.html_url = ""
        self.spacegroups = []
        self.info = []
        self.infostr = ""
        self.fileisvalid = False
        self.NewFileLoaded = False
        self.NewHKLscenes = False
        self.updatingNbins = False
        self.binstableitemchanges = False

        self.show()

    def SettingsDialog(self):
        self.settingsform.show()

    def update(self):
        if self.cctbxproc:
            if self.cctbxproc.stdout:
                print(self.cctbxproc.stdout.read().decode("utf-8"))
            if self.cctbxproc.stderr:
                print(self.cctbxproc.stderr.read().decode("utf-8"))
        if self.out:
            print(self.out.decode("utf-8"))
        if self.err:
            print(self.err.decode("utf-8"))
        if self.zmq_context:
            try:
                msg = self.socket.recv(
                    flags=zmq.NOBLOCK
                )  #To empty the socket from previous messages
                msgstr = msg.decode()
                self.infodict = eval(msgstr)
                #print("received from cctbx: " + str(self.infodict))
                if self.infodict:

                    if self.infodict.get("hklscenes_arrays"):
                        self.hklscenes_arrays = self.infodict.get(
                            "hklscenes_arrays", [])

                    if self.infodict.get("array_infotpls"):
                        self.array_infotpls = self.infodict.get(
                            "array_infotpls", [])

                    if self.infodict.get("bin_data_label"):
                        self.BinDataComboBox.setCurrentText(
                            self.infodict["bin_data_label"])

                    if self.infodict.get("bin_infotpls"):
                        self.bin_infotpls = self.infodict["bin_infotpls"]

                        self.nbins = len(self.bin_infotpls)
                        self.updatingNbins = True
                        self.Nbins_spinBox.setValue(self.nbins)
                        self.updatingNbins = False
                        self.binstable.clearContents()
                        self.binstable.setRowCount(self.nbins)
                        for row, bin_infotpl in enumerate(self.bin_infotpls):
                            for col, elm in enumerate(bin_infotpl):
                                # only allow changing the last column with opacity values
                                if col != 3:
                                    item = QTableWidgetItem(str(elm))
                                else:
                                    item = QTableWidgetItem()
                                    item.setFlags(Qt.ItemIsUserCheckable
                                                  | Qt.ItemIsEnabled)
                                    item.setCheckState(Qt.Checked)
                                item.setFlags(item.flags() ^ Qt.ItemIsEditable)
                                self.binstable.setItem(row, col, item)
                        if self.bin_opacities:
                            self.update_table_opacities()

                    if self.infodict.get("bin_opacities"):
                        self.bin_opacities = self.infodict["bin_opacities"]
                        if self.binstable.rowCount() > 0:
                            self.update_table_opacities()

                    if self.infodict.get("html_url"):
                        self.html_url = self.infodict["html_url"]
                        if self.UseOSbrowser == False:
                            self.BrowserBox.setUrl(self.html_url)
                            # workaround for background colour bug in chromium
                            # https://bugreports.qt.io/browse/QTBUG-41960
                            self.BrowserBox.page().setBackgroundColor(
                                QColor(100, 100, 100, 1.0))

                    if self.infodict.get("spacegroups"):
                        self.spacegroups = self.infodict.get("spacegroups", [])
                        self.SpaceGroupComboBox.clear()
                        self.SpaceGroupComboBox.addItems(self.spacegroups)

                    if self.infodict.get("merge_data"):
                        self.mergedata = self.infodict["merge_data"]

                    currentinfostr = ""
                    if self.infodict.get("info"):
                        currentinfostr = self.infodict.get("info", [])

                    if self.infodict.get("NewFileLoaded"):
                        self.NewFileLoaded = self.infodict.get(
                            "NewFileLoaded", False)

                    if self.infodict.get("NewHKLscenes"):
                        self.NewHKLscenes = self.infodict.get(
                            "NewHKLscenes", False)

                    self.fileisvalid = True
                    #print("ngl_hkl_infodict: " + str(ngl_hkl_infodict))

                    if currentinfostr:
                        #print(currentinfostr)
                        self.infostr += currentinfostr + "\n"
                        # display no more than self.bufsize bytes of text
                        self.infostr = self.infostr[-self.bufsize:]
                        self.textInfo.setPlainText(self.infostr)
                        self.textInfo.verticalScrollBar().setValue(
                            self.textInfo.verticalScrollBar().maximum())

                    if self.NewFileLoaded and self.NewHKLscenes:
                        #if self.mergedata == True : val = Qt.CheckState.Checked
                        #if self.mergedata == None : val = Qt.CheckState.PartiallyChecked
                        #if self.mergedata == False : val = Qt.CheckState.Unchecked
                        #self.mergecheckbox.setCheckState(val )
                        #print("got hklscenes: " + str(self.hklscenes_arrays))

                        self.MillerComboBox.clear()
                        self.MillerComboBox.addItems(
                            [e[3] for e in self.hklscenes_arrays])
                        self.MillerComboBox.setCurrentIndex(
                            -1)  # unselect the first item in the list
                        self.comboviewwidth = 0
                        for e in self.hklscenes_arrays:
                            self.comboviewwidth = max(
                                self.comboviewwidth,
                                self.MillerComboBox.fontMetrics().width(e[3]))
                        self.MillerComboBox.view().setMinimumWidth(
                            self.comboviewwidth)

                        self.millertable.clearContents()
                        self.millertable.setRowCount(len(
                            self.hklscenes_arrays))
                        for n, millarr in enumerate(self.array_infotpls):
                            for m, elm in enumerate(millarr):
                                self.millertable.setItem(
                                    n, m, QTableWidgetItem(str(elm)))
                        self.functionTabWidget.setDisabled(True)
                        self.NewFileLoaded = False

                    if self.NewHKLscenes:
                        self.BinDataComboBox.clear()
                        self.BinDataComboBox.addItems(
                            ["Resolution"] +
                            [e[3] for e in self.hklscenes_arrays])
                        self.BinDataComboBox.view().setMinimumWidth(
                            self.comboviewwidth)
                        #self.BinDataComboBox.setCurrentIndex(-1) # unselect the first item in the list
                        self.NewHKLscenes = False

            except Exception as e:
                errmsg = str(e)
                if "Resource temporarily unavailable" not in errmsg:
                    print(errmsg + traceback.format_exc(limit=10))
                pass

    def onFinalMouseSensitivity(self):
        val = self.mousemoveslider.value() / 100.0
        self.NGL_HKL_command(
            'NGL_HKLviewer.viewer.NGL.mouse_sensitivity = %f' % val)

    def onMouseSensitivity(self):
        val = self.mousemoveslider.value() / 100.0
        self.mousesensitxtbox.setText("%2.2f" % val)

    def onFontsizeChanged(self, val):
        font = app.font()
        font.setPointSize(val)
        app.setFont(font)
        self.settingsform.setFixedSize(self.settingsform.sizeHint())

    def onCameraPerspect(self, val):
        if self.cameraPerspectCheckBox.isChecked():
            self.NGL_HKL_command("NGL_HKLviewer.camera_type = perspective")
        else:
            self.NGL_HKL_command("NGL_HKLviewer.camera_type = orthographic")

    def MergeData(self):
        if self.mergecheckbox.checkState() == Qt.CheckState.Checked:
            self.NGL_HKL_command('NGL_HKLviewer.mergedata = True')
        if self.mergecheckbox.checkState() == Qt.CheckState.PartiallyChecked:
            self.NGL_HKL_command('NGL_HKLviewer.mergedata = None')
        if self.mergecheckbox.checkState() == Qt.CheckState.Unchecked:
            self.NGL_HKL_command('NGL_HKLviewer.mergedata = False')

    def ExpandToP1(self):
        if self.expandP1checkbox.isChecked():
            self.NGL_HKL_command('NGL_HKLviewer.viewer.expand_to_p1 = True')
        else:
            self.NGL_HKL_command('NGL_HKLviewer.viewer.expand_to_p1 = False')

    def ExpandAnomalous(self):
        if self.expandAnomalouscheckbox.isChecked():
            self.NGL_HKL_command(
                'NGL_HKLviewer.viewer.expand_anomalous = True')
        else:
            self.NGL_HKL_command(
                'NGL_HKLviewer.viewer.expand_anomalous = False')

    def showSysAbsent(self):
        if self.sysabsentcheckbox.isChecked():
            self.NGL_HKL_command(
                'NGL_HKLviewer.viewer.show_systematic_absences = True')
        else:
            self.NGL_HKL_command(
                'NGL_HKLviewer.viewer.show_systematic_absences = False')

    def showMissing(self):
        if self.missingcheckbox.isChecked():
            self.NGL_HKL_command('NGL_HKLviewer.viewer.show_missing = True')
        else:
            self.NGL_HKL_command('NGL_HKLviewer.viewer.show_missing = False')

    def showOnlyMissing(self):
        if self.onlymissingcheckbox.isChecked():
            self.NGL_HKL_command(
                'NGL_HKLviewer.viewer.show_only_missing = True')
        else:
            self.NGL_HKL_command(
                'NGL_HKLviewer.viewer.show_only_missing = False')

    def showSlice(self):
        if self.showslicecheckbox.isChecked():
            self.NGL_HKL_command('NGL_HKLviewer.viewer.slice_mode = True')
            if self.expandP1checkbox.isChecked():
                self.NGL_HKL_command("""NGL_HKLviewer.viewer {
                                                       expand_to_p1 = True
                                                       inbrowser = False
                                                    }
                             """)
            if self.expandAnomalouscheckbox.isChecked():
                self.NGL_HKL_command("""NGL_HKLviewer.viewer {
                                                       expand_anomalous = True
                                                       inbrowser = False
                                                     }
                             """)
        else:
            self.NGL_HKL_command("""NGL_HKLviewer.viewer {
                                                      slice_mode = False
                                                      inbrowser = True
                                                    }
                            """)

    def onSliceComboSelchange(self, i):
        rmin = self.array_infotpls[self.MillerComboBox.currentIndex()][3][0][i]
        rmax = self.array_infotpls[self.MillerComboBox.currentIndex()][3][1][i]
        self.sliceindexspinBox.setRange(rmin, rmax)
        self.NGL_HKL_command("NGL_HKLviewer.viewer.slice_axis = %s" %
                             self.sliceaxis[i])

    def onSliceIndexChanged(self, val):
        self.sliceindex = val
        self.NGL_HKL_command("NGL_HKLviewer.viewer.slice_index = %d" %
                             self.sliceindex)

    def onBindataComboSelchange(self, i):
        if self.BinDataComboBox.currentText():
            if self.BinDataComboBox.currentIndex() > 0:
                bin_scene_label = str(self.BinDataComboBox.currentIndex() - 1)
            else:
                bin_scene_label = "Resolution"
            self.NGL_HKL_command("NGL_HKLviewer.bin_scene_label = %s" %
                                 bin_scene_label)

    def update_table_opacities(self, allalpha=None):
        bin_opacitieslst = eval(self.bin_opacities)
        self.binstable_isready = False
        for binopacity in bin_opacitieslst:
            if not allalpha:
                alpha = float(binopacity.split(",")[0])
            else:
                alpha = allalpha
            bin = int(binopacity.split(",")[1])
            item = QTableWidgetItem()
            item.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled)
            if alpha < 0.5:
                item.setCheckState(Qt.Unchecked)
            else:
                item.setCheckState(Qt.Checked)
            item.setFlags(item.flags() ^ Qt.ItemIsEditable)
            self.binstable.setItem(bin, 3, item)
        self.binstable_isready = True

    def SetOpaqueAll(self):
        if self.binstableitemchanges:
            return
        bin_opacitieslst = eval(self.bin_opacities)
        nbins = len(bin_opacitieslst)
        sum = 0
        for binopacity in bin_opacitieslst:
            sum += float(binopacity.split(",")[0])
        if sum >= nbins:
            self.OpaqueAllCheckbox.setCheckState(Qt.Checked)
        if sum == 0:
            self.OpaqueAllCheckbox.setCheckState(Qt.Unchecked)
        if sum > 0.0 and sum < nbins:
            self.OpaqueAllCheckbox.setCheckState(Qt.PartiallyChecked)

    def onBinsTableItemChanged(self, item):
        row = item.row()
        column = item.column()
        try:
            if item.checkState() == Qt.Unchecked:
                newval = 0
            else:
                newval = 1.0
            if column == 3 and self.binstable_isready:  # changing opacity
                assert (newval <= 1.0 and newval >= 0.0)
                bin_opacitieslst = eval(self.bin_opacities)
                bin_opacitieslst[row] = str(newval) + ', ' + str(row)
                self.bin_opacities = str(bin_opacitieslst)
                self.SetOpaqueAll()
                self.NGL_HKL_command(
                    'NGL_HKLviewer.viewer.NGL.bin_opacities = "%s"' %
                    self.bin_opacities)
        except Exception as e:
            print(str(e))
            #self.binstable.currentItem().setText( self.currentSelectedBinsTableVal)

    def onBinsTableItemSelectionChanged(self):
        row = self.binstable.currentItem().row()
        column = self.binstable.currentItem().column()
        self.currentSelectedBinsTableVal = self.binstable.currentItem().text()
        #print( "in itemSelectionChanged " + self.currentSelectedBinsTableVal)

    def onOpaqueAll(self):
        self.binstableitemchanges = True
        bin_opacitieslst = eval(self.bin_opacities)
        nbins = len(bin_opacitieslst)
        bin_opacitieslst = []
        self.binstable_isready = False
        if self.OpaqueAllCheckbox.isChecked():
            for i in range(nbins):
                bin_opacitieslst.append("1.0, %d" % i)
        else:
            for i in range(nbins):
                bin_opacitieslst.append("0.0, %d" % i)
        self.bin_opacities = str(bin_opacitieslst)
        self.NGL_HKL_command('NGL_HKLviewer.viewer.NGL.bin_opacities = "%s"' %
                             self.bin_opacities)
        self.binstableitemchanges = False
        self.binstable_isready = True

    """
  def onLoadFinished(self, val):
    pass
    #print("web page finished loading now")


  def onBinsTableitemActivated(self, item):
    row = item.row()
    column = item.column()
    currentval = item.text()
    #print( "in itemActivated " + currentval)


  def onBinsTableCellentered(self, row, col):
    pass
    #print( "in Cellentered " + self.binstable.currentItem().text() )


  def onBinsTableCellPressed(self, row, col):
    pass
    #print( "in CellPressed " + self.binstable.currentItem().text() )
  """

    def onNbinsChanged(self, val):
        self.nbins = val
        if not self.updatingNbins:  # avoid possible endless loop to cctbx
            self.NGL_HKL_command("NGL_HKLviewer.nbins = %d" % self.nbins)

    def onRadiiScaleChanged(self, val):
        self.radii_scale = val
        self.NGL_HKL_command("""
      NGL_HKLviewer.viewer {
        nth_power_scale_radii = %f
        scale = %f
      }
      """ % (self.nth_power_scale, self.radii_scale))

    def onPowerScaleChanged(self, val):
        self.nth_power_scale = val
        self.NGL_HKL_command("""
      NGL_HKLviewer.viewer {
        nth_power_scale_radii = %f
        scale = %f
      }
      """ % (self.nth_power_scale, self.radii_scale))

    def onManualPowerScale(self):
        if self.ManualPowerScalecheckbox.isChecked():
            self.NGL_HKL_command(
                'NGL_HKLviewer.viewer.nth_power_scale_radii = %f' %
                self.nth_power_scale)
            self.power_scale_spinBox.setEnabled(True)
        else:
            self.NGL_HKL_command(
                'NGL_HKLviewer.viewer.nth_power_scale_radii = -1.0')
            self.power_scale_spinBox.setEnabled(False)
            self.nth_power_scale = -1.0

    def OpenReflectionsFile(self):
        options = QFileDialog.Options()
        fileName, filtr = QFileDialog.getOpenFileName(
            self, "Load reflections file", "",
            "All Files (*);;MTZ Files (*.mtz);;CIF (*.cif)", "", options)
        if fileName:
            self.HKLnameedit.setText(fileName)
            #self.infostr = ""
            self.textInfo.setPlainText("")
            self.fileisvalid = False
            self.NGL_HKL_command('NGL_HKLviewer.filename = "%s"' % fileName)
            self.MillerComboBox.clear()
            self.BinDataComboBox.clear()

    def createExpansionBox(self):
        self.SpaceGroupComboBox = QComboBox()
        self.SpaceGroupComboBox.activated.connect(self.SpacegroupSelchange)

        self.SpacegroupLabel = QLabel()
        self.SpacegroupLabel.setText("Space Subgroups")

        self.mergecheckbox = QCheckBox()
        self.mergecheckbox.setText("Merge data")
        #self.mergecheckbox.setTristate (True)
        self.mergecheckbox.clicked.connect(self.MergeData)

        self.expandP1checkbox = QCheckBox()
        self.expandP1checkbox.setText("Expand to P1")
        self.expandP1checkbox.clicked.connect(self.ExpandToP1)

        self.expandAnomalouscheckbox = QCheckBox()
        self.expandAnomalouscheckbox.setText("Show Friedel pairs")
        self.expandAnomalouscheckbox.clicked.connect(self.ExpandAnomalous)

        self.sysabsentcheckbox = QCheckBox()
        self.sysabsentcheckbox.setText("Show Systematic Absences")
        self.sysabsentcheckbox.clicked.connect(self.showSysAbsent)

        self.missingcheckbox = QCheckBox()
        self.missingcheckbox.setText("Show Missing")
        self.missingcheckbox.clicked.connect(self.showMissing)

        self.onlymissingcheckbox = QCheckBox()
        self.onlymissingcheckbox.setText("Only Show Missing")
        self.onlymissingcheckbox.clicked.connect(self.showOnlyMissing)

        self.ExpansionBox = QGroupBox("Expansions")
        layout = QGridLayout()
        layout.addWidget(self.SpacegroupLabel, 0, 0)
        layout.addWidget(self.SpaceGroupComboBox, 0, 1)
        #layout.addWidget(self.mergecheckbox,             1, 0)
        layout.addWidget(self.expandP1checkbox, 1, 0)
        layout.addWidget(self.expandAnomalouscheckbox, 1, 1)
        layout.addWidget(self.sysabsentcheckbox, 2, 0)
        layout.addWidget(self.missingcheckbox, 3, 0)
        layout.addWidget(self.onlymissingcheckbox, 3, 1)
        layout.setRowStretch(0, 0)
        layout.setRowStretch(1, 0)
        layout.setRowStretch(2, 0)
        layout.setRowStretch(3, 1)
        self.ExpansionBox.setLayout(layout)

    def CreateSliceTabs(self):
        self.showslicecheckbox = QCheckBox()
        self.showslicecheckbox.setText("Show Slice")
        self.showslicecheckbox.clicked.connect(self.showSlice)

        self.sliceindexspinBox = QDoubleSpinBox()
        self.sliceindex = 0
        self.sliceindexspinBox.setValue(self.sliceindex)
        self.sliceindexspinBox.setDecimals(0)
        self.sliceindexspinBox.setSingleStep(1)
        self.sliceindexspinBox.setRange(0, 20)
        self.sliceindexspinBox.valueChanged.connect(self.onSliceIndexChanged)

        self.SliceLabelComboBox = QComboBox()
        self.SliceLabelComboBox.activated.connect(self.onSliceComboSelchange)
        self.sliceaxis = ["h", "k", "l"]
        self.SliceLabelComboBox.addItems(self.sliceaxis)

        self.sliceTabWidget = QTabWidget()
        tab1 = QWidget()
        layout1 = QGridLayout()
        layout1.addWidget(self.showslicecheckbox, 0, 0, 1, 1)
        layout1.addWidget(self.SliceLabelComboBox, 0, 1, 1, 1)
        layout1.addWidget(self.sliceindexspinBox, 0, 2, 1, 1)
        tab1.setLayout(layout1)

        tab2 = QWidget()
        layout2 = QGridLayout()

        self.hvec_spinBox = QDoubleSpinBox(self.sliceTabWidget)
        self.hvecval = 2.0
        self.hvec_spinBox.setValue(self.hvecval)
        self.hvec_spinBox.setDecimals(2)
        self.hvec_spinBox.setSingleStep(0.5)
        self.hvec_spinBox.setRange(-100.0, 10.0)
        self.hvec_spinBox.valueChanged.connect(self.onHvecChanged)
        self.hvec_Label = QLabel()
        self.hvec_Label.setText("H")
        layout2.addWidget(self.hvec_Label, 0, 0, 1, 1)
        layout2.addWidget(self.hvec_spinBox, 0, 1, 1, 1)

        self.kvec_spinBox = QDoubleSpinBox(self.sliceTabWidget)
        self.kvecval = 0.0
        self.kvec_spinBox.setValue(self.kvecval)
        self.kvec_spinBox.setDecimals(2)
        self.kvec_spinBox.setSingleStep(0.5)
        self.kvec_spinBox.setRange(-100.0, 100.0)
        self.kvec_spinBox.valueChanged.connect(self.onKvecChanged)
        self.kvec_Label = QLabel()
        self.kvec_Label.setText("K")
        layout2.addWidget(self.kvec_Label, 1, 0, 1, 1)
        layout2.addWidget(self.kvec_spinBox, 1, 1, 1, 1)

        self.lvec_spinBox = QDoubleSpinBox(self.sliceTabWidget)
        self.lvecval = 0.0
        self.lvec_spinBox.setValue(self.lvecval)
        self.lvec_spinBox.setDecimals(2)
        self.lvec_spinBox.setSingleStep(0.5)
        self.lvec_spinBox.setRange(-100.0, 100.0)
        self.lvec_spinBox.valueChanged.connect(self.onLvecChanged)
        self.lvec_Label = QLabel()
        self.lvec_Label.setText("L")
        layout2.addWidget(self.lvec_Label, 2, 0, 1, 1)
        layout2.addWidget(self.lvec_spinBox, 2, 1, 1, 1)

        self.hkldist_spinBox = QDoubleSpinBox(self.sliceTabWidget)
        self.hkldistval = 0.0
        self.hkldist_spinBox.setValue(self.hkldistval)
        self.hkldist_spinBox.setDecimals(2)
        self.hkldist_spinBox.setSingleStep(0.5)
        self.hkldist_spinBox.setRange(-100.0, 100.0)
        self.hkldist_spinBox.valueChanged.connect(self.onHKLdistChanged)
        self.hkldist_Label = QLabel()
        self.hkldist_Label.setText("Distance from Origin")
        layout2.addWidget(self.hkldist_Label, 3, 0, 1, 1)
        layout2.addWidget(self.hkldist_spinBox, 3, 1, 1, 1)

        self.clipwidth_spinBox = QDoubleSpinBox(self.sliceTabWidget)
        self.clipwidthval = 0.5
        self.clipwidth_spinBox.setValue(self.clipwidthval)
        self.clipwidth_spinBox.setDecimals(2)
        self.clipwidth_spinBox.setSingleStep(0.05)
        self.clipwidth_spinBox.setRange(0.0, 100.0)
        self.clipwidth_spinBox.valueChanged.connect(self.onClipwidthChanged)
        self.clipwidth_Label = QLabel()
        self.clipwidth_Label.setText("Clip Plane Width")
        layout2.addWidget(self.clipwidth_Label, 4, 0, 1, 1)
        layout2.addWidget(self.clipwidth_spinBox, 4, 1, 1, 1)

        self.ClipBox = QGroupBox("Normal Vector to Clip Plane")
        self.ClipBox.setLayout(layout2)

        layout3 = QGridLayout()
        self.ClipPlaneChkBox = QCheckBox(self.sliceTabWidget)
        self.ClipPlaneChkBox.setText(
            "Use clip plane normal to HKL vector pointing out")
        self.ClipPlaneChkBox.clicked.connect(self.onClipPlaneChkBox)

        layout3.addWidget(self.ClipPlaneChkBox, 0, 0)
        layout3.addWidget(self.ClipBox, 1, 0)
        tab2.setLayout(layout3)
        self.sliceTabWidget.addTab(tab1, "Explicit Slicing")
        self.sliceTabWidget.addTab(tab2, "Clip Plane Slicing")
        self.ClipBox.setDisabled(True)

    def onClipPlaneChkBox(self):
        if self.ClipPlaneChkBox.isChecked():
            self.ClipBox.setDisabled(False)
            philstr = """NGL_HKLviewer.normal_clip_plane {
  h = %s
  k = %s
  l = %s
  hkldist = %s
  clipwidth = %s
}
  NGL_HKLviewer.viewer.NGL.fixorientation = %s

      """ %(self.hvecval, self.kvecval, self.lvecval, self.hkldistval, self.clipwidthval, \
                                    str(self.fixedorientcheckbox.isChecked()) )
            self.NGL_HKL_command(philstr)
        else:
            self.ClipBox.setDisabled(True)
            self.NGL_HKL_command(
                "NGL_HKLviewer.normal_clip_plane.clipwidth = None")

    def onClipwidthChanged(self, val):
        self.clipwidthval = val
        self.NGL_HKL_command("NGL_HKLviewer.normal_clip_plane.clipwidth = %f" %
                             self.clipwidthval)

    def onHKLdistChanged(self, val):
        self.hkldistval = val
        self.NGL_HKL_command("NGL_HKLviewer.normal_clip_plane.hkldist = %f" %
                             self.hkldistval)

    def onHvecChanged(self, val):
        self.hvecval = val
        self.NGL_HKL_command("NGL_HKLviewer.normal_clip_plane.h = %f" %
                             self.hvecval)

    def onKvecChanged(self, val):
        self.kvecval = val
        self.NGL_HKL_command("NGL_HKLviewer.normal_clip_plane.k = %f" %
                             self.kvecval)

    def onLvecChanged(self, val):
        self.lvecval = val
        self.NGL_HKL_command("NGL_HKLviewer.normal_clip_plane.l = %f" %
                             self.lvecval)

    def onFixedorient(self):
        self.NGL_HKL_command('NGL_HKLviewer.viewer.NGL.fixorientation = %s' \
                                        %str(self.fixedorientcheckbox.isChecked()))

    def onMillerComboSelchange(self, i):
        self.NGL_HKL_command("NGL_HKLviewer.scene_id = %d" % i)
        #self.MillerComboBox.setCurrentIndex(i)
        if self.MillerComboBox.currentText():
            self.functionTabWidget.setEnabled(True)
            self.expandAnomalouscheckbox.setEnabled(True)
            # don' allow anomalous expansion for data that's already anomalous
            for arrayinfo in self.array_infotpls:
                isanomalous = arrayinfo[-1]
                label = arrayinfo[0]
                if isanomalous and label == self.MillerComboBox.currentText(
                )[:len(label)]:
                    self.expandAnomalouscheckbox.setDisabled(True)
        else:
            self.functionTabWidget.setDisabled(True)

        self.SpaceGroupComboBox.clear()
        self.SpaceGroupComboBox.addItems(self.spacegroups)
        # need to supply issymunique flag in infotuple
        #if self.hklscenes_arrays[ i ][6] == 0:
        #  self.mergecheckbox.setEnabled(True)
        #else:
        #  self.mergecheckbox.setEnabled(False)

    def createFileInfoBox(self):
        self.FileInfoBox = QGroupBox("Reflection File Information")
        layout = QGridLayout()
        layout.addWidget(self.openFileNameButton, 0, 0, 1, 2)
        if self.devmode:
            layout.addWidget(self.debugbutton, 0, 2, 1, 1)
        layout.addWidget(self.HKLnameedit, 1, 0, 1, 3)
        layout.addWidget(self.millertable, 2, 0, 1, 3)
        layout.addWidget(self.textInfo, 3, 0, 1, 3)
        #layout.setColumnStretch(1, 2)
        self.FileInfoBox.setLayout(layout)

    def createRadiiScaleGroupBox(self):
        self.RadiiScaleGroupBox = QGroupBox("Radii Size of HKL Spheres")

        self.ManualPowerScalecheckbox = QCheckBox()
        self.ManualPowerScalecheckbox.setText(
            "Manual Power Scaling of Sphere Radii")
        self.ManualPowerScalecheckbox.clicked.connect(self.onManualPowerScale)

        self.power_scale_spinBox = QDoubleSpinBox(self.RadiiScaleGroupBox)
        self.nth_power_scale = 0.5
        self.power_scale_spinBox.setValue(self.nth_power_scale)
        self.power_scale_spinBox.setDecimals(2)
        self.power_scale_spinBox.setSingleStep(0.05)
        self.power_scale_spinBox.setRange(0.0, 1.0)
        self.power_scale_spinBox.valueChanged.connect(self.onPowerScaleChanged)
        self.power_scale_spinBox.setEnabled(False)
        self.powerscaleLabel = QLabel()
        self.powerscaleLabel.setText("Power scale Factor")

        self.radii_scale_spinBox = QDoubleSpinBox(self.RadiiScaleGroupBox)
        self.radii_scale = 1.0
        self.radii_scale_spinBox.setValue(self.radii_scale)
        self.radii_scale_spinBox.setDecimals(1)
        self.radii_scale_spinBox.setSingleStep(0.1)
        self.radii_scale_spinBox.setRange(0.2, 2.0)
        self.radii_scale_spinBox.valueChanged.connect(self.onRadiiScaleChanged)
        self.radiiscaleLabel = QLabel()
        self.radiiscaleLabel.setText("Linear Scale Factor")

        layout = QGridLayout()
        layout.addWidget(self.ManualPowerScalecheckbox, 1, 0, 1, 2)
        layout.addWidget(self.powerscaleLabel, 2, 0, 1, 2)
        layout.addWidget(self.power_scale_spinBox, 2, 1, 1, 2)
        layout.addWidget(self.radiiscaleLabel, 3, 0, 1, 2)
        layout.addWidget(self.radii_scale_spinBox, 3, 1, 1, 2)
        layout.setColumnStretch(0, 1)
        layout.setColumnStretch(1, 0)
        self.RadiiScaleGroupBox.setLayout(layout)

    def createBinsBox(self):
        self.binstable = QTableWidget(0, 4)
        self.binstable_isready = False
        labels = [
            "no. of HKLs", "lower bin value", "upper bin value", "opacity"
        ]
        self.binstable.setHorizontalHeaderLabels(labels)
        self.binstable.horizontalHeader().setDefaultAlignment(Qt.AlignLeft)
        self.bindata_labeltxt = QLabel()
        self.bindata_labeltxt.setText("Data binned:")
        self.Nbins_spinBox = QSpinBox()
        self.Nbins_spinBox.setSingleStep(1)
        self.Nbins_spinBox.setRange(1, 40)
        self.Nbins_spinBox.valueChanged.connect(self.onNbinsChanged)
        self.Nbins_labeltxt = QLabel()
        self.Nbins_labeltxt.setText("Number of bins:")

        self.OpaqueAllCheckbox = QCheckBox()
        #self.OpaqueAllCheckbox.setTristate()
        self.OpaqueAllCheckbox.setText("Show all data in bins")
        self.OpaqueAllCheckbox.clicked.connect(self.onOpaqueAll)

        self.binstable.itemChanged.connect(self.onBinsTableItemChanged)
        self.binstable.itemSelectionChanged.connect(
            self.onBinsTableItemSelectionChanged)
        self.BinDataComboBox = QComboBox()
        self.BinDataComboBox.activated.connect(self.onBindataComboSelchange)
        self.BinsGroupBox = QGroupBox("Bins")
        layout = QGridLayout()
        layout.addWidget(self.bindata_labeltxt, 0, 0)
        layout.addWidget(self.BinDataComboBox, 0, 1)
        layout.addWidget(self.Nbins_labeltxt, 0, 2)
        layout.addWidget(self.Nbins_spinBox, 0, 3)
        layout.addWidget(self.OpaqueAllCheckbox, 1, 2)
        layout.addWidget(self.binstable, 2, 0, 1, 4)
        layout.setColumnStretch(0, 0)
        layout.setColumnStretch(1, 2)
        layout.setColumnStretch(3, 1)
        self.BinsGroupBox.setLayout(layout)

    def DebugInteractively(self):
        import code, traceback
        code.interact(local=locals(),
                      banner="".join(traceback.format_stack(limit=10)))

    def CreateFunctionTabs(self):
        self.functionTabWidget = QTabWidget()
        tab1 = QWidget()
        layout1 = QGridLayout()
        layout1.addWidget(self.ExpansionBox, 0, 0)
        layout1.setRowStretch(0, 0)
        tab1.setLayout(layout1)

        tab2 = QWidget()
        layout2 = QGridLayout()

        self.fixedorientcheckbox = QCheckBox(self.sliceTabWidget)
        self.fixedorientcheckbox.setText(
            "Fix orientation but allow zoom and translation")
        self.fixedorientcheckbox.clicked.connect(self.onFixedorient)
        layout2.addWidget(self.fixedorientcheckbox, 0, 0)

        layout2.addWidget(self.sliceTabWidget, 1, 0)
        tab2.setLayout(layout2)

        tab3 = QWidget()
        layout3 = QGridLayout()
        layout3.addWidget(self.RadiiScaleGroupBox, 0, 0)
        tab3.setLayout(layout3)

        tab4 = QWidget()
        layout4 = QGridLayout()
        layout4.addWidget(self.BinsGroupBox, 0, 0)
        tab4.setLayout(layout4)

        self.functionTabWidget.addTab(tab1, "Expand")
        self.functionTabWidget.addTab(tab2, "Slice")
        self.functionTabWidget.addTab(tab3, "Size")
        self.functionTabWidget.addTab(tab4, "Bins")
        self.functionTabWidget.setDisabled(True)

    def SpacegroupSelchange(self, i):
        self.NGL_HKL_command("NGL_HKLviewer.spacegroup_choice = %d" % i)

    def find_free_port(self):
        import socket
        s = socket.socket()
        s.bind(('', 0))  # Bind to a free port provided by the host.
        port = s.getsockname()[1]
        s.close()
        return port

    def LaunchCCTBXPython(self):
        self.sockport = self.find_free_port()
        self.zmq_context = zmq.Context()
        self.socket = self.zmq_context.socket(zmq.PAIR)
        self.socket.bind("tcp://127.0.0.1:%s" % self.sockport)
        try:
            msg = self.socket.recv(
                flags=zmq.NOBLOCK)  #To empty the socket from previous messages
        except Exception as e:
            pass
        cmdargs = 'cctbx.python.bat -i -c "from crys3d.hklview import cmdlineframes;' \
         + ' myHKLview = cmdlineframes.HKLViewFrame(useGuiSocket=%s, high_quality=True,' %self.sockport \
         + ' jscriptfname = \'%s\', ' %self.jscriptfname \
         + ' verbose=%s, UseOSBrowser= %s )"\n' %(self.verbose, str(self.UseOSbrowser))
        self.cctbxproc = subprocess.Popen(cmdargs,
                                          shell=True,
                                          stdin=subprocess.PIPE,
                                          stdout=sys.stdout,
                                          stderr=sys.stderr)
        #time.sleep(1)

    def NGL_HKL_command(self, cmdstr):
        #print("sending:\n" + cmdstr)
        self.socket.send(bytes(cmdstr, "utf-8"))
示例#26
0
class HistWidget(ToolWidget):
    def __init__(self, image, parent=None):
        super(ToolWidget, self).__init__(parent)

        self.value_radio = QRadioButton(self.tr('Value'))
        self.value_radio.setChecked(True)
        self.last_radio = self.value_radio
        self.red_radio = QRadioButton(self.tr('Red'))
        self.green_radio = QRadioButton(self.tr('Green'))
        self.blue_radio = QRadioButton(self.tr('Blue'))
        self.rgb_radio = QRadioButton(self.tr('RGB'))
        self.smooth_check = QCheckBox(self.tr('Smooth line'))
        self.smooth_check.setToolTip(self.tr('Interpolated values plot'))
        self.log_check = QCheckBox(self.tr('Log scale'))
        self.log_check.setToolTip(self.tr('Y-axes logarithmic scale'))
        self.grid_check = QCheckBox(self.tr('Show grid'))
        self.grid_check.setToolTip(self.tr('Display XY main grid lines'))
        self.marker_check = QCheckBox(self.tr('Show markers'))
        self.marker_check.setToolTip(
            self.tr('Show plot markers for min(--), avg(-), max(-.)'))
        self.start_slider = ParamSlider([0, 255], 8, 0, bold=True)
        self.end_slider = ParamSlider([0, 255], 8, 255, bold=True)

        channels = cv.split(cv.cvtColor(image, cv.COLOR_BGR2RGB))
        channels.append(cv.cvtColor(image, cv.COLOR_BGR2GRAY))
        self.hist = [compute_hist(c) for c in channels]
        rows, cols, chans = image.shape
        pixels = rows * cols
        self.unique_colors = np.unique(np.reshape(image, (pixels, chans)),
                                       axis=0).shape[0]
        self.unique_ratio = np.round(self.unique_colors / pixels * 100, 2)

        self.value_radio.clicked.connect(self.redraw)
        self.red_radio.clicked.connect(self.redraw)
        self.green_radio.clicked.connect(self.redraw)
        self.blue_radio.clicked.connect(self.redraw)
        self.rgb_radio.clicked.connect(self.redraw)
        self.smooth_check.stateChanged.connect(self.redraw)
        self.log_check.stateChanged.connect(self.redraw)
        self.grid_check.stateChanged.connect(self.redraw)
        self.marker_check.stateChanged.connect(self.redraw)
        self.start_slider.valueChanged.connect(self.redraw)
        self.end_slider.valueChanged.connect(self.redraw)

        self.table_widget = QTableWidget(13, 2)
        self.table_widget.setHorizontalHeaderLabels(
            [self.tr('Property'), self.tr('Value')])
        self.table_widget.setItem(0, 0,
                                  QTableWidgetItem(self.tr('Least frequent')))
        self.table_widget.item(0, 0).setToolTip(
            self.tr('Value that appears less'))
        self.table_widget.setItem(1, 0,
                                  QTableWidgetItem(self.tr('Most frequent')))
        self.table_widget.item(1, 0).setToolTip(
            self.tr('Value that appears more'))
        self.table_widget.setItem(2, 0,
                                  QTableWidgetItem(self.tr('Average level')))
        self.table_widget.item(2,
                               0).setToolTip(self.tr('Histogram mean value'))
        self.table_widget.setItem(3, 0,
                                  QTableWidgetItem(self.tr('Median level')))
        self.table_widget.item(3,
                               0).setToolTip(self.tr('Histogram median value'))
        self.table_widget.setItem(4, 0, QTableWidgetItem(self.tr('Deviation')))
        self.table_widget.item(4, 0).setToolTip(
            self.tr('Histogram standard deviation'))
        self.table_widget.setItem(5, 0,
                                  QTableWidgetItem(self.tr('Pixel count')))
        self.table_widget.item(5, 0).setToolTip(
            self.tr('Total values in current range'))
        self.table_widget.setItem(6, 0,
                                  QTableWidgetItem(self.tr('Percentile')))
        self.table_widget.item(6, 0).setToolTip(
            self.tr('Percentage of total pixels'))
        self.table_widget.setItem(7, 0,
                                  QTableWidgetItem(self.tr('Nonzero range')))
        self.table_widget.item(7, 0).setToolTip(
            self.tr('Minimal range without empty bins'))
        self.table_widget.setItem(8, 0,
                                  QTableWidgetItem(self.tr('Empty bins')))
        self.table_widget.item(8, 0).setToolTip(
            self.tr('Number of missing values'))
        self.table_widget.setItem(9, 0,
                                  QTableWidgetItem(self.tr('Unique colors')))
        self.table_widget.item(9,
                               0).setToolTip(self.tr('Unique RGB color count'))
        self.table_widget.setItem(10, 0,
                                  QTableWidgetItem(self.tr('Unique ratio')))
        self.table_widget.item(10, 0).setToolTip(
            self.tr('Unique colors vs total pixels'))
        self.table_widget.setItem(11, 0,
                                  QTableWidgetItem(self.tr('Smoothness')))
        self.table_widget.item(11, 0).setToolTip(
            self.tr('Estimated correlation among bin values'))
        self.table_widget.setItem(12, 0, QTableWidgetItem(self.tr('Fullness')))
        self.table_widget.item(12, 0).setToolTip(
            self.tr('Area covered vs total size'))
        for i in range(self.table_widget.rowCount()):
            modify_font(self.table_widget.item(i, 0), bold=True)
        self.table_widget.setSelectionMode(QAbstractItemView.SingleSelection)
        self.table_widget.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.table_widget.setAlternatingRowColors(True)
        self.table_widget.setMinimumWidth(200)
        self.table_widget.resizeColumnsToContents()

        figure = Figure()
        plot_canvas = FigureCanvas(figure)
        self.axes = plot_canvas.figure.subplots()
        self.redraw()
        figure.set_tight_layout(True)

        range_layout = QGridLayout()
        range_layout.addWidget(QLabel(self.tr('Start:')), 0, 0)
        range_layout.addWidget(self.start_slider, 0, 1)
        range_layout.addWidget(QLabel(self.tr('End:')), 1, 0)
        range_layout.addWidget(self.end_slider, 1, 1)

        right_frame = QFrame()
        right_layout = QVBoxLayout()
        right_layout.addWidget(self.table_widget)
        right_layout.addLayout(range_layout)
        right_frame.setLayout(right_layout)

        center_split = QSplitter()
        center_split.addWidget(plot_canvas)
        center_split.addWidget(right_frame)

        bottom_layout = QHBoxLayout()
        bottom_layout.addWidget(QLabel(self.tr('Channel:')))
        bottom_layout.addWidget(self.value_radio)
        bottom_layout.addWidget(self.red_radio)
        bottom_layout.addWidget(self.green_radio)
        bottom_layout.addWidget(self.blue_radio)
        bottom_layout.addWidget(self.rgb_radio)
        bottom_layout.addStretch()
        bottom_layout.addWidget(self.smooth_check)
        bottom_layout.addWidget(self.log_check)
        bottom_layout.addWidget(self.grid_check)
        bottom_layout.addWidget(self.marker_check)

        main_layout = QVBoxLayout()
        main_layout.addWidget(center_split)
        main_layout.addLayout(bottom_layout)
        self.setLayout(main_layout)

    def redraw(self):
        x = np.arange(256)
        alpha = 0.25
        rgb = self.rgb_radio.isChecked()
        red = self.red_radio.isChecked()
        green = self.green_radio.isChecked()
        blue = self.blue_radio.isChecked()
        value = self.value_radio.isChecked()
        smoothness = self.smooth_check.isChecked()
        grid = self.grid_check.isChecked()
        log = self.log_check.isChecked()
        try:
            self.axes.clear()
        except RecursionError:
            return
        y = None
        step = None if smoothness else 'mid'
        if value:
            y = self.hist[3]
            if smoothness:
                self.axes.plot(x, y, 'k')
            else:
                self.axes.step(x, y, 'k', where='mid')
            self.axes.fill_between(x, y, alpha=alpha, facecolor='k', step=step)
        else:
            if red or rgb:
                y = self.hist[0]
                if smoothness:
                    self.axes.plot(x, y, 'r')
                else:
                    self.axes.step(x, y, 'r', where='mid')
                self.axes.fill_between(x,
                                       y,
                                       alpha=alpha,
                                       facecolor='r',
                                       step=step)
            if green or rgb:
                y = self.hist[1]
                if smoothness:
                    self.axes.plot(x, y, 'g')
                else:
                    self.axes.step(x, y, 'g', where='mid')
                self.axes.fill_between(x,
                                       y,
                                       alpha=alpha,
                                       facecolor='g',
                                       step=step)
            if blue or rgb:
                y = self.hist[2]
                if smoothness:
                    self.axes.plot(x, y, 'b')
                else:
                    self.axes.step(x, y, 'b', where='mid')
                self.axes.fill_between(x,
                                       y,
                                       alpha=alpha,
                                       facecolor='b',
                                       step=step)
        if log:
            self.axes.set_yscale('log')
            self.axes.set_ylim(bottom=1)
        else:
            self.axes.set_yscale('linear')
            self.axes.set_ylim(bottom=0)
        self.axes.set_xlim([0, 255])
        self.axes.set_xlabel(self.tr('intensity value'))
        self.axes.set_ylabel(self.tr('pixel count'))
        self.axes.set_xticks([0, 64, 128, 192, 255])
        self.axes.grid(grid, which='both')

        if rgb:
            self.table_widget.setEnabled(False)
            self.marker_check.setEnabled(False)
            self.start_slider.setEnabled(False)
            self.end_slider.setEnabled(False)
            for i in range(self.table_widget.rowCount()):
                if self.table_widget.item(i, 1) is not None:
                    self.table_widget.item(i, 1).setText('')
                    self.table_widget.item(i, 1).setBackgroundColor(
                        QColor('white'))
        else:
            self.table_widget.setEnabled(True)
            self.marker_check.setEnabled(True)
            self.start_slider.setEnabled(True)
            self.end_slider.setEnabled(True)
            start = self.start_slider.value()
            end = self.end_slider.value()
            if end <= start:
                end = start + 1
            elif start >= end:
                start = end - 1
            total = np.sum(y)
            x = x[start:end + 1]
            y = y[start:end + 1]
            count = np.sum(y)
            if count != 0:
                argmin = np.argmin(y) + start
                argmax = np.argmax(y) + start
                mean = np.round(np.sum(x * y) / count, 2)
                stddev = np.round(np.sqrt(np.sum(((x - mean)**2) * y) / count),
                                  2)
                median = np.argmax(np.cumsum(y) > count / 2) + start
                percent = np.round(count / total * 100, 2)
                empty = len(x) - np.count_nonzero(y)
                nonzero = [
                    np.nonzero(y)[0][0] + start,
                    np.nonzero(y)[0][-1] + start
                ]
                fullness = np.round(count / (255 * np.max(y)) * 100, 2)
                y = y / np.max(y)
                sweep = len(y)
                smoothness = 0
                if sweep >= 5:
                    for i in range(2, sweep - 2):
                        yl = 2 * y[i - 1] - y[i - 2]
                        yr = 2 * y[i + 1] - y[i + 2]
                        smoothness += abs(y[i] - (yl + yr) / 2)
                    smoothness = np.round(
                        (1 - (smoothness / (sweep - 2))) * 100, 2)
                if self.marker_check.isChecked():
                    self.axes.axvline(argmin, linestyle='--', color='m')
                    self.axes.axvline(mean, linestyle='-', color='m')
                    self.axes.axvline(argmax, linestyle='-.', color='m')
                    self.axes.axvline(median, linestyle=':', color='m')
            else:
                argmin = argmax = mean = stddev = median = percent = smoothness = empty = nonzero = fullness = 0

            self.table_widget.setItem(0, 1, QTableWidgetItem(str(argmin)))
            self.table_widget.setItem(1, 1, QTableWidgetItem(str(argmax)))
            self.table_widget.setItem(2, 1, QTableWidgetItem(str(mean)))
            self.table_widget.setItem(3, 1, QTableWidgetItem(str(median)))
            self.table_widget.setItem(4, 1, QTableWidgetItem(str(stddev)))
            self.table_widget.setItem(5, 1, QTableWidgetItem(str(count)))
            self.table_widget.setItem(6, 1,
                                      QTableWidgetItem(str(percent) + '%'))
            self.table_widget.setItem(7, 1, QTableWidgetItem(str(nonzero)))
            self.table_widget.setItem(8, 1, QTableWidgetItem(str(empty)))
            self.table_widget.setItem(
                9, 1, QTableWidgetItem(str(self.unique_colors)))
            self.table_widget.setItem(
                10, 1, QTableWidgetItem(str(self.unique_ratio) + '%'))
            color_by_value(self.table_widget.item(10, 1), self.unique_ratio,
                           [25, 50, 75])
            self.table_widget.setItem(11, 1,
                                      QTableWidgetItem(str(smoothness) + '%'))
            color_by_value(self.table_widget.item(11, 1), smoothness,
                           [80, 90, 95])
            self.table_widget.setItem(12, 1,
                                      QTableWidgetItem(str(fullness) + '%'))
            color_by_value(self.table_widget.item(12, 1), fullness,
                           [5, 10, 20])
            self.table_widget.resizeColumnsToContents()
            if start != 0 or end != 255:
                self.axes.axvline(start, linestyle=':', color='k')
                self.axes.axvline(end, linestyle=':', color='k')
                _, top = self.axes.get_ylim()
                self.axes.fill_between(np.arange(start, end + 1),
                                       top,
                                       facecolor='y',
                                       alpha=alpha * 2)
        self.axes.figure.canvas.draw()
示例#27
0
class SCOUTS(QMainWindow):
    """Main Window Widget for SCOUTS."""
    style = {
        'title': 'QLabel {font-size: 18pt; font-weight: 600}',
        'header': 'QLabel {font-size: 12pt; font-weight: 520}',
        'label': 'QLabel {font-size: 10pt}',
        'button': 'QPushButton {font-size: 10pt}',
        'md button': 'QPushButton {font-size: 12pt}',
        'run button': 'QPushButton {font-size: 18pt; font-weight: 600}',
        'line edit': 'QLineEdit {font-size: 10pt}',
        'checkbox': 'QCheckBox {font-size: 10pt}',
        'radio button': 'QRadioButton {font-size: 10pt}'
    }

    def __init__(self) -> None:
        """SCOUTS Constructor. Defines all aspects of the GUI."""

        # ###
        # ### Main Window setup
        # ###

        # Inherits from QMainWindow
        super().__init__()
        self.rootdir = get_project_root()
        self.threadpool = QThreadPool()
        # Sets values for QMainWindow
        self.setWindowTitle("SCOUTS")
        self.setWindowIcon(
            QIcon(
                os.path.abspath(os.path.join(self.rootdir, 'src',
                                             'scouts.ico'))))
        # Creates StackedWidget as QMainWindow's central widget
        self.stacked_pages = QStackedWidget(self)
        self.setCentralWidget(self.stacked_pages)
        # Creates Widgets for individual "pages" and adds them to the StackedWidget
        self.main_page = QWidget()
        self.samples_page = QWidget()
        self.gating_page = QWidget()
        self.pages = (self.main_page, self.samples_page, self.gating_page)
        for page in self.pages:
            self.stacked_pages.addWidget(page)
        # ## Sets widget at program startup
        self.stacked_pages.setCurrentWidget(self.main_page)

        # ###
        # ### MAIN PAGE
        # ###

        # Main page layout
        self.main_layout = QVBoxLayout(self.main_page)

        # Title section
        # Title
        self.title = QLabel(self.main_page)
        self.title.setText('SCOUTS - Single Cell Outlier Selector')
        self.title.setStyleSheet(self.style['title'])
        self.title.adjustSize()
        self.main_layout.addWidget(self.title)

        # ## Input section
        # Input header
        self.input_header = QLabel(self.main_page)
        self.input_header.setText('Input settings')
        self.input_header.setStyleSheet(self.style['header'])
        self.main_layout.addChildWidget(self.input_header)
        self.input_header.adjustSize()
        self.main_layout.addWidget(self.input_header)
        # Input frame
        self.input_frame = QFrame(self.main_page)
        self.input_frame.setFrameShape(QFrame.StyledPanel)
        self.input_frame.setLayout(QFormLayout())
        self.main_layout.addWidget(self.input_frame)
        # Input button
        self.input_button = QPushButton(self.main_page)
        self.input_button.setStyleSheet(self.style['button'])
        self.set_icon(self.input_button, 'x-office-spreadsheet')
        self.input_button.setObjectName('input')
        self.input_button.setText(' Select input file (.xlsx or .csv)')
        self.input_button.clicked.connect(self.get_path)
        # Input path box
        self.input_path = QLineEdit(self.main_page)
        self.input_path.setObjectName('input_path')
        self.input_path.setStyleSheet(self.style['line edit'])
        # Go to sample naming page
        self.samples_button = QPushButton(self.main_page)
        self.samples_button.setStyleSheet(self.style['button'])
        self.set_icon(self.samples_button, 'preferences-other')
        self.samples_button.setText(' Name samples...')
        self.samples_button.clicked.connect(self.goto_samples_page)
        # Go to gating page
        self.gates_button = QPushButton(self.main_page)
        self.gates_button.setStyleSheet(self.style['button'])
        self.set_icon(self.gates_button, 'preferences-other')
        self.gates_button.setText(' Gating && outlier options...')
        self.gates_button.clicked.connect(self.goto_gates_page)
        # Add widgets above to input frame Layout
        self.input_frame.layout().addRow(self.input_button, self.input_path)
        self.input_frame.layout().addRow(self.samples_button)
        self.input_frame.layout().addRow(self.gates_button)

        # ## Analysis section
        # Analysis header
        self.analysis_header = QLabel(self.main_page)
        self.analysis_header.setText('Analysis settings')
        self.analysis_header.setStyleSheet(self.style['header'])
        self.analysis_header.adjustSize()
        self.main_layout.addWidget(self.analysis_header)
        # Analysis frame
        self.analysis_frame = QFrame(self.main_page)
        self.analysis_frame.setFrameShape(QFrame.StyledPanel)
        self.analysis_frame.setLayout(QVBoxLayout())
        self.main_layout.addWidget(self.analysis_frame)
        # Cutoff text
        self.cutoff_text = QLabel(self.main_page)
        self.cutoff_text.setText('Type of outlier to select:')
        self.cutoff_text.setToolTip(
            'Choose whether to select outliers using the cutoff value from a reference\n'
            'sample (OutR) or by using the cutoff value calculated for each sample\n'
            'individually (OutS)')
        self.cutoff_text.setStyleSheet(self.style['label'])
        # Cutoff button group
        self.cutoff_group = QButtonGroup(self)
        # Cutoff by sample
        self.cutoff_sample = QRadioButton(self.main_page)
        self.cutoff_sample.setText('OutS')
        self.cutoff_sample.setObjectName('sample')
        self.cutoff_sample.setStyleSheet(self.style['radio button'])
        self.cutoff_sample.setChecked(True)
        self.cutoff_group.addButton(self.cutoff_sample)
        # Cutoff by reference
        self.cutoff_reference = QRadioButton(self.main_page)
        self.cutoff_reference.setText('OutR')
        self.cutoff_reference.setObjectName('ref')
        self.cutoff_reference.setStyleSheet(self.style['radio button'])
        self.cutoff_group.addButton(self.cutoff_reference)
        # Both cutoffs
        self.cutoff_both = QRadioButton(self.main_page)
        self.cutoff_both.setText('both')
        self.cutoff_both.setObjectName('sample ref')
        self.cutoff_both.setStyleSheet(self.style['radio button'])
        self.cutoff_group.addButton(self.cutoff_both)
        # Markers text
        self.markers_text = QLabel(self.main_page)
        self.markers_text.setStyleSheet(self.style['label'])
        self.markers_text.setText('Show results for:')
        self.markers_text.setToolTip(
            'Individual markers: for each marker, select outliers\n'
            'Any marker: select cells that are outliers for AT LEAST one marker'
        )
        # Markers button group
        self.markers_group = QButtonGroup(self)
        # Single marker
        self.single_marker = QRadioButton(self.main_page)
        self.single_marker.setText('individual markers')
        self.single_marker.setObjectName('single')
        self.single_marker.setStyleSheet(self.style['radio button'])
        self.single_marker.setChecked(True)
        self.markers_group.addButton(self.single_marker)
        # Any marker
        self.any_marker = QRadioButton(self.main_page)
        self.any_marker.setText('any marker')
        self.any_marker.setObjectName('any')
        self.any_marker.setStyleSheet(self.style['radio button'])
        self.markers_group.addButton(self.any_marker)
        # Both methods
        self.both_methods = QRadioButton(self.main_page)
        self.both_methods.setText('both')
        self.both_methods.setObjectName('single any')
        self.both_methods.setStyleSheet(self.style['radio button'])
        self.markers_group.addButton(self.both_methods)
        # Tukey text
        self.tukey_text = QLabel(self.main_page)
        self.tukey_text.setStyleSheet(self.style['label'])
        # Tukey button group
        self.tukey_text.setText('Tukey factor:')
        self.tukey_group = QButtonGroup(self)
        # Low Tukey value
        self.tukey_low = QRadioButton(self.main_page)
        self.tukey_low.setText('1.5')
        self.tukey_low.setStyleSheet(self.style['radio button'])
        self.tukey_low.setChecked(True)
        self.tukey_group.addButton(self.tukey_low)
        # High Tukey value
        self.tukey_high = QRadioButton(self.main_page)
        self.tukey_high.setText('3.0')
        self.tukey_high.setStyleSheet(self.style['radio button'])
        self.tukey_group.addButton(self.tukey_high)
        # Add widgets above to analysis frame layout
        self.analysis_frame.layout().addWidget(self.cutoff_text)
        self.cutoff_buttons = QHBoxLayout()
        for button in self.cutoff_group.buttons():
            self.cutoff_buttons.addWidget(button)
        self.analysis_frame.layout().addLayout(self.cutoff_buttons)
        self.analysis_frame.layout().addWidget(self.markers_text)
        self.markers_buttons = QHBoxLayout()
        for button in self.markers_group.buttons():
            self.markers_buttons.addWidget(button)
        self.analysis_frame.layout().addLayout(self.markers_buttons)
        self.analysis_frame.layout().addWidget(self.tukey_text)
        self.tukey_buttons = QHBoxLayout()
        for button in self.tukey_group.buttons():
            self.tukey_buttons.addWidget(button)
        self.tukey_buttons.addWidget(QLabel())  # aligns row with 2 buttons
        self.analysis_frame.layout().addLayout(self.tukey_buttons)

        # ## Output section
        # Output header
        self.output_header = QLabel(self.main_page)
        self.output_header.setText('Output settings')
        self.output_header.setStyleSheet(self.style['header'])
        self.output_header.adjustSize()
        self.main_layout.addWidget(self.output_header)
        # Output frame
        self.output_frame = QFrame(self.main_page)
        self.output_frame.setFrameShape(QFrame.StyledPanel)
        self.output_frame.setLayout(QFormLayout())
        self.main_layout.addWidget(self.output_frame)
        # Output button
        self.output_button = QPushButton(self.main_page)
        self.output_button.setStyleSheet(self.style['button'])
        self.set_icon(self.output_button, 'folder')
        self.output_button.setObjectName('output')
        self.output_button.setText(' Select output folder')
        self.output_button.clicked.connect(self.get_path)
        # Output path box
        self.output_path = QLineEdit(self.main_page)
        self.output_path.setStyleSheet(self.style['line edit'])
        # Generate CSV checkbox
        self.output_csv = QCheckBox(self.main_page)
        self.output_csv.setText('Export multiple text files (.csv)')
        self.output_csv.setStyleSheet(self.style['checkbox'])
        self.output_csv.setChecked(True)
        # Generate XLSX checkbox
        self.output_excel = QCheckBox(self.main_page)
        self.output_excel.setText('Export multiple Excel spreadsheets (.xlsx)')
        self.output_excel.setStyleSheet(self.style['checkbox'])
        self.output_excel.clicked.connect(self.enable_single_excel)
        # Generate single, large XLSX checkbox
        self.single_excel = QCheckBox(self.main_page)
        self.single_excel.setText(
            'Also save one multi-sheet Excel spreadsheet')
        self.single_excel.setToolTip(
            'After generating all Excel spreadsheets, SCOUTS combines them into '
            'a single\nExcel spreadsheet where each sheet corresponds to an output'
            'file from SCOUTS')
        self.single_excel.setStyleSheet(self.style['checkbox'])
        self.single_excel.setEnabled(False)
        self.single_excel.clicked.connect(self.memory_warning)
        # Add widgets above to output frame layout
        self.output_frame.layout().addRow(self.output_button, self.output_path)
        self.output_frame.layout().addRow(self.output_csv)
        self.output_frame.layout().addRow(self.output_excel)
        self.output_frame.layout().addRow(self.single_excel)

        # ## Run & help-quit section
        # Run button (stand-alone)
        self.run_button = QPushButton(self.main_page)
        self.set_icon(self.run_button, 'system-run')
        self.run_button.setText(' Run!')
        self.run_button.setStyleSheet(self.style['run button'])
        self.main_layout.addWidget(self.run_button)
        self.run_button.clicked.connect(self.run)
        # Help-quit frame (invisible)
        self.helpquit_frame = QFrame(self.main_page)
        self.helpquit_frame.setLayout(QHBoxLayout())
        self.helpquit_frame.layout().setMargin(0)
        self.main_layout.addWidget(self.helpquit_frame)
        # Help button
        self.help_button = QPushButton(self.main_page)
        self.set_icon(self.help_button, 'help-about')
        self.help_button.setText(' Help')
        self.help_button.setStyleSheet(self.style['md button'])
        self.help_button.clicked.connect(self.get_help)
        # Quit button
        self.quit_button = QPushButton(self.main_page)
        self.set_icon(self.quit_button, 'process-stop')
        self.quit_button.setText(' Quit')
        self.quit_button.setStyleSheet(self.style['md button'])
        self.quit_button.clicked.connect(self.close)
        # Add widgets above to help-quit layout
        self.helpquit_frame.layout().addWidget(self.help_button)
        self.helpquit_frame.layout().addWidget(self.quit_button)

        # ###
        # ### SAMPLES PAGE
        # ###

        # Samples page layout
        self.samples_layout = QVBoxLayout(self.samples_page)

        # ## Title section
        # Title
        self.samples_title = QLabel(self.samples_page)
        self.samples_title.setText('Name your samples')
        self.samples_title.setStyleSheet(self.style['title'])
        self.samples_title.adjustSize()
        self.samples_layout.addWidget(self.samples_title)
        # Subtitle
        self.samples_subtitle = QLabel(self.samples_page)
        string = (
            'Please name the samples to be analysed by SCOUTS.\n\nSCOUTS searches the first '
            'column of your data\nand locates the exact string as part of the sample name.'
        )
        self.samples_subtitle.setText(string)
        self.samples_subtitle.setStyleSheet(self.style['label'])
        self.samples_subtitle.adjustSize()
        self.samples_layout.addWidget(self.samples_subtitle)

        # ## Sample addition section
        # Sample addition frame
        self.samples_frame = QFrame(self.samples_page)
        self.samples_frame.setFrameShape(QFrame.StyledPanel)
        self.samples_frame.setLayout(QGridLayout())
        self.samples_layout.addWidget(self.samples_frame)
        # Sample name box
        self.sample_name = QLineEdit(self.samples_page)
        self.sample_name.setStyleSheet(self.style['line edit'])
        self.sample_name.setPlaceholderText('Sample name ...')
        # Reference check
        self.is_reference = QCheckBox(self.samples_page)
        self.is_reference.setText('Reference?')
        self.is_reference.setStyleSheet(self.style['checkbox'])
        # Add sample to table
        self.add_sample_button = QPushButton(self.samples_page)
        QShortcut(QKeySequence("Return"), self.add_sample_button,
                  self.write_to_sample_table)
        self.set_icon(self.add_sample_button, 'list-add')
        self.add_sample_button.setText(' Add sample (Enter)')
        self.add_sample_button.setStyleSheet(self.style['button'])
        self.add_sample_button.clicked.connect(self.write_to_sample_table)
        # Remove sample from table
        self.remove_sample_button = QPushButton(self.samples_page)
        QShortcut(QKeySequence("Delete"), self.remove_sample_button,
                  self.remove_from_sample_table)
        self.set_icon(self.remove_sample_button, 'list-remove')
        self.remove_sample_button.setText(' Remove sample (Del)')
        self.remove_sample_button.setStyleSheet(self.style['button'])
        self.remove_sample_button.clicked.connect(
            self.remove_from_sample_table)
        # Add widgets above to sample addition layout
        self.samples_frame.layout().addWidget(self.sample_name, 0, 0)
        self.samples_frame.layout().addWidget(self.is_reference, 1, 0)
        self.samples_frame.layout().addWidget(self.add_sample_button, 0, 1)
        self.samples_frame.layout().addWidget(self.remove_sample_button, 1, 1)

        # ## Sample table
        self.sample_table = QTableWidget(self.samples_page)
        self.sample_table.setColumnCount(2)
        self.sample_table.setHorizontalHeaderItem(0,
                                                  QTableWidgetItem('Sample'))
        self.sample_table.setHorizontalHeaderItem(
            1, QTableWidgetItem('Reference?'))
        self.sample_table.horizontalHeader().setSectionResizeMode(
            0, QHeaderView.Stretch)
        self.sample_table.horizontalHeader().setSectionResizeMode(
            1, QHeaderView.ResizeToContents)
        self.samples_layout.addWidget(self.sample_table)

        # ## Save & clear buttons
        # Save & clear frame (invisible)
        self.saveclear_frame = QFrame(self.samples_page)
        self.saveclear_frame.setLayout(QHBoxLayout())
        self.saveclear_frame.layout().setMargin(0)
        self.samples_layout.addWidget(self.saveclear_frame)
        # Clear samples button
        self.clear_samples = QPushButton(self.samples_page)
        self.set_icon(self.clear_samples, 'edit-delete')
        self.clear_samples.setText(' Clear table')
        self.clear_samples.setStyleSheet(self.style['md button'])
        self.clear_samples.clicked.connect(self.prompt_clear_data)
        # Save samples button
        self.save_samples = QPushButton(self.samples_page)
        self.set_icon(self.save_samples, 'document-save')
        self.save_samples.setText(' Save samples')
        self.save_samples.setStyleSheet(self.style['md button'])
        self.save_samples.clicked.connect(self.goto_main_page)
        # Add widgets above to save & clear layout
        self.saveclear_frame.layout().addWidget(self.clear_samples)
        self.saveclear_frame.layout().addWidget(self.save_samples)

        # ###
        # ### GATING PAGE
        # ###

        # Gating page layout
        self.gating_layout = QVBoxLayout(self.gating_page)

        # ## Title section
        # Title
        self.gates_title = QLabel(self.gating_page)
        self.gates_title.setText('Gating & outlier options')
        self.gates_title.setStyleSheet(self.style['title'])
        self.gates_title.adjustSize()
        self.gating_layout.addWidget(self.gates_title)

        # ## Gating options section
        # Gating header
        self.gate_header = QLabel(self.gating_page)
        self.gate_header.setText('Gating')
        self.gate_header.setStyleSheet(self.style['header'])
        self.gate_header.adjustSize()
        self.gating_layout.addWidget(self.gate_header)

        # Gating frame
        self.gate_frame = QFrame(self.gating_page)
        self.gate_frame.setFrameShape(QFrame.StyledPanel)
        self.gate_frame.setLayout(QFormLayout())
        self.gating_layout.addWidget(self.gate_frame)
        # Gating button group
        self.gating_group = QButtonGroup(self)
        # Do not gate samples
        self.no_gates = QRadioButton(self.gating_page)
        self.no_gates.setObjectName('no_gate')
        self.no_gates.setText("Don't gate samples")
        self.no_gates.setStyleSheet(self.style['radio button'])
        self.no_gates.setChecked(True)
        self.gating_group.addButton(self.no_gates)
        self.no_gates.clicked.connect(self.activate_gate)
        # CyToF gating
        self.cytof_gates = QRadioButton(self.gating_page)
        self.cytof_gates.setObjectName('cytof')
        self.cytof_gates.setText('Mass Cytometry gating')
        self.cytof_gates.setStyleSheet(self.style['radio button'])
        self.cytof_gates.setToolTip(
            'Exclude cells for which the average expression of all\n'
            'markers is below the selected value')
        self.gating_group.addButton(self.cytof_gates)
        self.cytof_gates.clicked.connect(self.activate_gate)
        # CyToF gating spinbox
        self.cytof_gates_value = QDoubleSpinBox(self.gating_page)
        self.cytof_gates_value.setMinimum(0)
        self.cytof_gates_value.setMaximum(1)
        self.cytof_gates_value.setValue(0.1)
        self.cytof_gates_value.setSingleStep(0.05)
        self.cytof_gates_value.setEnabled(False)
        # scRNA-Seq gating
        self.rnaseq_gates = QRadioButton(self.gating_page)
        self.rnaseq_gates.setText('scRNA-Seq gating')
        self.rnaseq_gates.setStyleSheet(self.style['radio button'])
        self.rnaseq_gates.setToolTip(
            'When calculating cutoff, ignore reads below the selected value')
        self.rnaseq_gates.setObjectName('rnaseq')
        self.gating_group.addButton(self.rnaseq_gates)
        self.rnaseq_gates.clicked.connect(self.activate_gate)
        # scRNA-Seq gating spinbox
        self.rnaseq_gates_value = QDoubleSpinBox(self.gating_page)
        self.rnaseq_gates_value.setMinimum(0)
        self.rnaseq_gates_value.setMaximum(10)
        self.rnaseq_gates_value.setValue(0)
        self.rnaseq_gates_value.setSingleStep(1)
        self.rnaseq_gates_value.setEnabled(False)
        # export gated population checkbox
        self.export_gated = QCheckBox(self.gating_page)
        self.export_gated.setText('Export gated cells as an output file')
        self.export_gated.setStyleSheet(self.style['checkbox'])
        self.export_gated.setEnabled(False)
        # Add widgets above to Gate frame layout
        self.gate_frame.layout().addRow(self.no_gates, QLabel())
        self.gate_frame.layout().addRow(self.cytof_gates,
                                        self.cytof_gates_value)
        self.gate_frame.layout().addRow(self.rnaseq_gates,
                                        self.rnaseq_gates_value)
        self.gate_frame.layout().addRow(self.export_gated, QLabel())

        # ## Outlier options section
        # Outlier header
        self.outlier_header = QLabel(self.gating_page)
        self.outlier_header.setText('Outliers')
        self.outlier_header.setStyleSheet(self.style['header'])
        self.outlier_header.adjustSize()
        self.gating_layout.addWidget(self.outlier_header)
        # Outlier frame
        self.outlier_frame = QFrame(self.gating_page)
        self.outlier_frame.setFrameShape(QFrame.StyledPanel)
        self.outlier_frame.setLayout(QVBoxLayout())
        self.gating_layout.addWidget(self.outlier_frame)
        # Top outliers information
        self.top_outliers = QLabel(self.gating_page)
        self.top_outliers.setStyleSheet(self.style['label'])
        self.top_outliers.setText(
            'By default, SCOUTS selects the top outliers from the population')
        self.top_outliers.setStyleSheet(self.style['label'])
        # Bottom outliers data
        self.bottom_outliers = QCheckBox(self.gating_page)
        self.bottom_outliers.setText('Include results for low outliers')
        self.bottom_outliers.setStyleSheet(self.style['checkbox'])
        # Non-outliers data
        self.not_outliers = QCheckBox(self.gating_page)
        self.not_outliers.setText('Include results for non-outliers')
        self.not_outliers.setStyleSheet(self.style['checkbox'])
        # Add widgets above to Gate frame layout
        self.outlier_frame.layout().addWidget(self.top_outliers)
        self.outlier_frame.layout().addWidget(self.bottom_outliers)
        self.outlier_frame.layout().addWidget(self.not_outliers)

        # ## Save/back button
        self.save_gates = QPushButton(self.gating_page)
        self.set_icon(self.save_gates, 'go-next')
        self.save_gates.setText(' Back to main menu')
        self.save_gates.setStyleSheet(self.style['md button'])
        self.gating_layout.addWidget(self.save_gates)
        self.save_gates.clicked.connect(self.goto_main_page)

        # ## Add empty label to take vertical space
        self.empty_label = QLabel(self.gating_page)
        self.empty_label.setSizePolicy(QSizePolicy.Expanding,
                                       QSizePolicy.Expanding)
        self.gating_layout.addWidget(self.empty_label)

    # ###
    # ### ICON SETTING
    # ###

    def set_icon(self, widget: QWidget, icon: str) -> None:
        """Associates an icon to a widget."""
        i = QIcon()
        i.addPixmap(
            QPixmap(
                os.path.abspath(
                    os.path.join(self.rootdir, 'src', 'default_icons',
                                 f'{icon}.svg'))))
        widget.setIcon(QIcon.fromTheme(icon, i))

    # ###
    # ### STACKED WIDGET PAGE SWITCHING
    # ###

    def goto_main_page(self) -> None:
        """Switches stacked widget pages to the main page."""
        self.stacked_pages.setCurrentWidget(self.main_page)

    def goto_samples_page(self) -> None:
        """Switches stacked widget pages to the samples table page."""
        self.stacked_pages.setCurrentWidget(self.samples_page)

    def goto_gates_page(self) -> None:
        """Switches stacked widget pages to the gating & other options page."""
        self.stacked_pages.setCurrentWidget(self.gating_page)

    # ###
    # ### MAIN PAGE GUI LOGIC
    # ###

    def get_path(self) -> None:
        """Opens a dialog box and sets the chosen file/folder path, depending on the caller widget."""
        options = QFileDialog.Options()
        options |= QFileDialog.DontUseNativeDialog
        sender_name = self.sender().objectName()
        if sender_name == 'input':
            query, _ = QFileDialog.getOpenFileName(self,
                                                   "Select file",
                                                   "",
                                                   "All Files (*)",
                                                   options=options)
        elif sender_name == 'output':
            query = QFileDialog.getExistingDirectory(self,
                                                     "Select Directory",
                                                     options=options)
        else:
            return
        if query:
            getattr(self, f'{sender_name}_path').setText(query)

    def enable_single_excel(self) -> None:
        """Enables checkbox for generating a single Excel output."""
        if self.output_excel.isChecked():
            self.single_excel.setEnabled(True)
        else:
            self.single_excel.setEnabled(False)
            self.single_excel.setChecked(False)

    # ###
    # ### SAMPLE NAME/SAMPLE TABLE GUI LOGIC
    # ###

    def write_to_sample_table(self) -> None:
        """Writes data to sample table."""
        table = self.sample_table
        ref = 'no'
        sample = self.sample_name.text()
        if sample:
            for cell in range(table.rowCount()):
                item = table.item(cell, 0)
                if item.text() == sample:
                    self.same_sample()
                    return
            if self.is_reference.isChecked():
                for cell in range(table.rowCount()):
                    item = table.item(cell, 1)
                    if item.text() == 'yes':
                        self.more_than_one_reference()
                        return
                ref = 'yes'
            sample = QTableWidgetItem(sample)
            is_reference = QTableWidgetItem(ref)
            is_reference.setFlags(Qt.ItemIsEnabled)
            row_position = table.rowCount()
            table.insertRow(row_position)
            table.setItem(row_position, 0, sample)
            table.setItem(row_position, 1, is_reference)
            self.is_reference.setChecked(False)
            self.sample_name.setText('')

    def remove_from_sample_table(self) -> None:
        """Removes data from sample table."""
        table = self.sample_table
        rows = set(index.row() for index in table.selectedIndexes())
        for index in sorted(rows, reverse=True):
            self.sample_table.removeRow(index)

    def prompt_clear_data(self) -> None:
        """Prompts option to clear all data in the sample table."""
        if self.confirm_clear_data():
            table = self.sample_table
            while table.rowCount():
                self.sample_table.removeRow(0)

    # ###
    # ### GATING GUI LOGIC
    # ###

    def activate_gate(self) -> None:
        """Activates/deactivates buttons related to gating."""
        if self.sender().objectName() == 'no_gate':
            self.cytof_gates_value.setEnabled(False)
            self.rnaseq_gates_value.setEnabled(False)
            self.export_gated.setEnabled(False)
            self.export_gated.setChecked(False)
        elif self.sender().objectName() == 'cytof':
            self.cytof_gates_value.setEnabled(True)
            self.rnaseq_gates_value.setEnabled(False)
            self.export_gated.setEnabled(True)
        elif self.sender().objectName() == 'rnaseq':
            self.cytof_gates_value.setEnabled(False)
            self.rnaseq_gates_value.setEnabled(True)
            self.export_gated.setEnabled(True)

    # ###
    # ### CONNECT SCOUTS TO ANALYTICAL MODULES
    # ###

    def run(self) -> None:
        """Runs SCOUTS as a Worker, based on user input in the GUI."""
        try:
            data = self.parse_input()
        except Exception as error:
            trace = traceback.format_exc()
            self.propagate_error((error, trace))
        else:
            data['widget'] = self
            worker = Worker(func=start_scouts, **data)
            worker.signals.started.connect(self.analysis_has_started)
            worker.signals.finished.connect(self.analysis_has_finished)
            worker.signals.success.connect(self.success_message)
            worker.signals.error.connect(self.propagate_error)
            self.threadpool.start(worker)

    def parse_input(self) -> Dict:
        """Returns user input on the GUI as a dictionary."""
        # Input and output
        input_dict = {
            'input_file': str(self.input_path.text()),
            'output_folder': str(self.output_path.text())
        }
        if not input_dict['input_file'] or not input_dict['output_folder']:
            raise NoIOPathError
        # Set cutoff by reference or by sample rule
        input_dict['cutoff_rule'] = self.cutoff_group.checkedButton(
        ).objectName()  # 'sample', 'ref', 'sample ref'
        # Outliers for each individual marker or any marker in row
        input_dict['marker_rule'] = self.markers_group.checkedButton(
        ).objectName()  # 'single', 'any', 'single any'
        # Tukey factor used for calculating cutoff
        input_dict['tukey_factor'] = float(
            self.tukey_group.checkedButton().text())  # '1.5', '3.0'
        # Output settings
        input_dict['export_csv'] = True if self.output_csv.isChecked(
        ) else False
        input_dict['export_excel'] = True if self.output_excel.isChecked(
        ) else False
        input_dict['single_excel'] = True if self.single_excel.isChecked(
        ) else False
        # Retrieve samples from sample table
        input_dict['sample_list'] = []
        for tuples in self.yield_samples_from_table():
            input_dict['sample_list'].append(tuples)
        if not input_dict['sample_list']:
            raise NoSampleError
        # Set gate cutoff (if any)
        input_dict['gating'] = self.gating_group.checkedButton().objectName(
        )  # 'no_gate', 'cytof', 'rnaseq'
        input_dict['gate_cutoff_value'] = None
        if input_dict['gating'] != 'no_gate':
            input_dict['gate_cutoff_value'] = getattr(
                self, f'{input_dict["gating"]}_gates_value').value()
        input_dict['export_gated'] = True if self.export_gated.isChecked(
        ) else False
        # Generate results for non-outliers
        input_dict['non_outliers'] = False
        if self.not_outliers.isChecked():
            input_dict['non_outliers'] = True
        # Generate results for bottom outliers
        input_dict['bottom_outliers'] = False
        if self.bottom_outliers.isChecked():
            input_dict['bottom_outliers'] = True
        # return dictionary with all gathered inputs
        return input_dict

    def yield_samples_from_table(
            self) -> Generator[Tuple[str, str], None, None]:
        """Yields sample names from the sample table."""
        table = self.sample_table
        for cell in range(table.rowCount()):
            sample_name = table.item(cell, 0).text()
            sample_type = table.item(cell, 1).text()
            yield sample_name, sample_type

    # ###
    # ### MESSAGE BOXES
    # ###

    def analysis_has_started(self) -> None:
        """Disables run button while SCOUTS analysis is underway."""
        self.run_button.setText(' Working...')
        self.run_button.setEnabled(False)

    def analysis_has_finished(self) -> None:
        """Enables run button after SCOUTS analysis has finished."""
        self.run_button.setEnabled(True)
        self.run_button.setText(' Run!')

    def success_message(self) -> None:
        """Info message box used when SCOUTS finished without errors."""
        title = "Analysis finished!"
        mes = "Your analysis has finished. No errors were reported."
        if self.stacked_pages.isEnabled() is True:
            QMessageBox.information(self, title, mes)

    def memory_warning(self) -> None:
        """Warning message box used when user wants to generate a single excel file."""
        if self.sender().isChecked():
            title = 'Memory warning!'
            mes = (
                "Depending on your dataset, this option can consume a LOT of memory and take"
                " a long time to process. Please make sure that your computer can handle it!"
            )
            QMessageBox.information(self, title, mes)

    def same_sample(self) -> None:
        """Error message box used when the user tries to input the same sample twice in the sample table."""
        title = 'Error: sample name already in table'
        mes = (
            "Sorry, you can't do this because this sample name is already in the table. "
            "Please select a different name.")
        QMessageBox.critical(self, title, mes)

    def more_than_one_reference(self) -> None:
        """Error message box used when the user tries to input two reference samples in the sample table."""
        title = "Error: more than one reference selected"
        mes = (
            "Sorry, you can't do this because there is already a reference column in the table. "
            "Please remove it before adding a reference.")
        QMessageBox.critical(self, title, mes)

    def confirm_clear_data(self) -> bool:
        """Question message box used to confirm user action of clearing sample table."""
        title = 'Confirm Action'
        mes = "Table will be cleared. Are you sure?"
        reply = QMessageBox.question(self, title, mes,
                                     QMessageBox.Yes | QMessageBox.No,
                                     QMessageBox.No)
        if reply == QMessageBox.Yes:
            return True
        return False

    # ###
    # ### EXCEPTIONS & ERRORS
    # ###

    def propagate_error(self, error: Tuple[Exception, str]) -> None:
        """Calls the appropriate error message box based on type of Exception raised."""
        if isinstance(error[0], NoIOPathError):
            self.no_io_path_error_message()
        elif isinstance(error[0], NoReferenceError):
            self.no_reference_error_message()
        elif isinstance(error[0], NoSampleError):
            self.no_sample_error_message()
        elif isinstance(error[0], PandasInputError):
            self.pandas_input_error_message()
        elif isinstance(error[0], SampleNamingError):
            self.sample_naming_error_message()
        else:
            self.generic_error_message(error)

    def no_io_path_error_message(self) -> None:
        """Message displayed when the user did not include an input file path, or an output folder path."""
        title = 'Error: no file/folder'
        message = ("Sorry, no input file and/or output folder was provided. "
                   "Please add the path to the necessary file/folder.")
        QMessageBox.critical(self, title, message)

    def no_reference_error_message(self) -> None:
        """Message displayed when the user wants to analyse cutoff based on a reference, but did not specify what
        sample corresponds to the reference."""
        title = "Error: No reference selected"
        message = (
            "Sorry, no reference sample was found on the sample list, but analysis was set to "
            "reference. Please add a reference sample, or change the rule for cutoff calculation."
        )
        QMessageBox.critical(self, title, message)

    def no_sample_error_message(self) -> None:
        """Message displayed when the user did not add any samples to the sample table."""
        title = "Error: No samples selected"
        message = (
            "Sorry, the analysis cannot be performed because no sample names were input. "
            "Please add your sample names.")
        QMessageBox.critical(self, title, message)

    def pandas_input_error_message(self) -> None:
        """Message displayed when the input file cannot be read (likely because it is not a Excel or csv file)."""
        title = 'Error: unexpected input file'
        message = (
            "Sorry, the input file could not be read. Please make sure that "
            "the data is save in a valid format (supported formats are: "
            ".csv, .xlsx).")
        QMessageBox.critical(self, title, message)

    def sample_naming_error_message(self) -> None:
        """Message displayed when none of the sample names passed by the user are found in the input DataFrame."""
        title = 'Error: sample names not in input file'
        message = (
            "Sorry, your sample names were not found in the input file. Please "
            "make sure that the names were typed correctly (case-sensitive).")
        QMessageBox.critical(self, title, message)

    def generic_error_message(self, error: Tuple[Exception, str]) -> None:
        """Error message box used to display any error message (including traceback) for any uncaught errors."""
        title = 'An error occurred!'
        name, trace = error
        QMessageBox.critical(self, title,
                             f"{str(name)}\n\nfull traceback:\n{trace}")

    def not_implemented_error_message(self) -> None:
        """Error message box used when the user accesses a functionality that hasn't been implemented yet."""
        title = "Not yet implemented"
        mes = "Sorry, this functionality has not been implemented yet."
        QMessageBox.critical(self, title, mes)

    # ###
    # ### HELP & QUIT
    # ###

    @staticmethod
    def get_help() -> None:
        """Opens SCOUTS documentation on the browser. Called when the user clicks the "help" button"""
        webbrowser.open('https://scouts.readthedocs.io/en/master/')

    def closeEvent(self, event: QEvent) -> None:
        """Defines the message box for when the user wants to quit SCOUTS."""
        title = 'Quit SCOUTS'
        mes = "Are you sure you want to quit?"
        reply = QMessageBox.question(self, title, mes,
                                     QMessageBox.Yes | QMessageBox.No,
                                     QMessageBox.No)
        if reply == QMessageBox.Yes:
            self.stacked_pages.setEnabled(False)
            message = self.quit_message()
            waiter = Waiter(waiter_func=self.threadpool.activeThreadCount)
            waiter.signals.started.connect(message.show)
            waiter.signals.finished.connect(message.destroy)
            waiter.signals.finished.connect(sys.exit)
            self.threadpool.start(waiter)
        event.ignore()

    def quit_message(self) -> QDialog:
        """Displays a window while SCOUTS is exiting"""
        message = QDialog(self)
        message.setWindowTitle('Exiting SCOUTS')
        message.resize(300, 50)
        label = QLabel('SCOUTS is exiting, please wait...', message)
        label.setStyleSheet(self.style['label'])
        label.adjustSize()
        label.setAlignment(Qt.AlignCenter)
        label.move(int((message.width() - label.width()) / 2),
                   int((message.height() - label.height()) / 2))
        return message
示例#28
0
class View(QMainWindow):
    def __init__(self, model, controller):
        super().__init__()

        self._model = model
        self._controller = controller
        self.segmentcursor = False
        self.togglecolors = {"#1f77b4": "m", "m": "#1f77b4"}

        #################################################################
        # define GUI layout and connect input widgets to external slots #
        #################################################################

        self.setWindowTitle("biopeaks")
        self.setGeometry(50, 50, 1750, 750)
        self.setWindowIcon(QIcon(":/python_icon.png"))

        # figure0 for signal
        self.figure0 = Figure()
        self.canvas0 = FigureCanvas(self.figure0)
        # Enforce minimum height, otherwise resizing with self.splitter causes
        # mpl to throw an error because figure is resized to height 0. The
        # widget can still be fully collapsed with self.splitter-
        self.canvas0.setMinimumHeight(1)  # in pixels
        self.ax00 = self.figure0.add_subplot(1, 1, 1)
        self.ax00.set_frame_on(False)
        self.figure0.subplots_adjust(left=0.04, right=0.98, bottom=0.25)
        self.line00 = None
        self.scat = None
        self.segmentspan = None

        # figure1 for marker
        self.figure1 = Figure()
        self.canvas1 = FigureCanvas(self.figure1)
        self.canvas1.setMinimumHeight(1)
        self.ax10 = self.figure1.add_subplot(1, 1, 1, sharex=self.ax00)
        self.ax10.get_xaxis().set_visible(False)
        self.ax10.set_frame_on(False)
        self.figure1.subplots_adjust(left=0.04, right=0.98)
        self.line10 = None

        # figure2 for statistics
        self.figure2 = Figure()
        self.canvas2 = FigureCanvas(self.figure2)
        self.canvas2.setMinimumHeight(1)
        self.ax20 = self.figure2.add_subplot(3, 1, 1, sharex=self.ax00)
        self.ax20.get_xaxis().set_visible(False)
        self.ax20.set_frame_on(False)
        self.line20 = None
        self.ax21 = self.figure2.add_subplot(3, 1, 2, sharex=self.ax00)
        self.ax21.get_xaxis().set_visible(False)
        self.ax21.set_frame_on(False)
        self.line21 = None
        self.ax22 = self.figure2.add_subplot(3, 1, 3, sharex=self.ax00)
        self.ax22.get_xaxis().set_visible(False)
        self.ax22.set_frame_on(False)
        self.line22 = None
        self.figure2.subplots_adjust(left=0.04, right=0.98)

        # navigation bar
        self.navitools = CustomNavigationToolbar(self.canvas0, self)

        # peak editing
        self.editcheckbox = QCheckBox("editable", self)
        self.editcheckbox.stateChanged.connect(self._model.set_peakseditable)

        # peak saving batch
        self.savecheckbox = QCheckBox("save during batch processing", self)
        self.savecheckbox.stateChanged.connect(self._model.set_savebatchpeaks)

        # peak auto-correction batch
        self.correctcheckbox = QCheckBox("correct during batch processing",
                                         self)
        self.correctcheckbox.stateChanged.connect(
            self._model.set_correctbatchpeaks)

        # selecting stats for saving
        self.periodcheckbox = QCheckBox("period", self)
        self.periodcheckbox.stateChanged.connect(
            lambda: self.select_stats("period"))
        self.ratecheckbox = QCheckBox("rate", self)
        self.ratecheckbox.stateChanged.connect(
            lambda: self.select_stats("rate"))
        self.tidalampcheckbox = QCheckBox("tidal amplitude", self)
        self.tidalampcheckbox.stateChanged.connect(
            lambda: self.select_stats("tidalamp"))

        # channel selection
        self.sigchanmenulabel = QLabel("biosignal")
        self.sigchanmenu = QComboBox(self)
        self.sigchanmenu.addItem("A1")
        self.sigchanmenu.addItem("A2")
        self.sigchanmenu.addItem("A3")
        self.sigchanmenu.addItem("A4")
        self.sigchanmenu.addItem("A5")
        self.sigchanmenu.addItem("A6")
        self.sigchanmenu.currentTextChanged.connect(self._model.set_signalchan)
        # initialize with default value
        self._model.set_signalchan(self.sigchanmenu.currentText())

        self.markerchanmenulabel = QLabel("marker")
        self.markerchanmenu = QComboBox(self)
        self.markerchanmenu.addItem("none")
        self.markerchanmenu.addItem("I1")
        self.markerchanmenu.addItem("I2")
        self.markerchanmenu.addItem("A1")
        self.markerchanmenu.addItem("A2")
        self.markerchanmenu.addItem("A3")
        self.markerchanmenu.addItem("A4")
        self.markerchanmenu.addItem("A5")
        self.markerchanmenu.addItem("A6")
        self.markerchanmenu.currentTextChanged.connect(
            self._model.set_markerchan)
        # initialize with default value
        self._model.set_markerchan(self.markerchanmenu.currentText())

        # processing mode (batch or single file)
        self.batchmenulabel = QLabel("mode")
        self.batchmenu = QComboBox(self)
        self.batchmenu.addItem("single file")
        self.batchmenu.addItem("multiple files")
        self.batchmenu.currentTextChanged.connect(self._model.set_batchmode)
        self.batchmenu.currentTextChanged.connect(self.toggle_options)
        # initialize with default value
        self._model.set_batchmode(self.batchmenu.currentText())
        self.toggle_options(self.batchmenu.currentText())

        # modality selection
        self.modmenulabel = QLabel("modality")
        self.modmenu = QComboBox(self)
        self.modmenu.addItem("ECG")
        self.modmenu.addItem("PPG")
        self.modmenu.addItem("RESP")
        self.modmenu.currentTextChanged.connect(self._model.set_modality)
        self.modmenu.currentTextChanged.connect(self.toggle_options)
        # initialize with default value
        self._model.set_modality(self.modmenu.currentText())
        self.toggle_options(self.modmenu.currentText())

        # segment selection; this widget can be openend / set visible from
        # the menu and closed from within itself (see mapping of segmentermap);
        # it provides utilities to select a segment from the signal
        self.segmentermap = QSignalMapper(self)
        self.segmenter = QDockWidget("select a segment", self)
        # disable closing such that widget can only be closed by confirming
        # selection or custom button
        self.segmenter.setFeatures(QDockWidget.NoDockWidgetFeatures)
        # Limit number of decimals to four.
        regex = QRegExp("[0-9]*\.?[0-9]{4}")
        validator = QRegExpValidator(regex)

        self.startlabel = QLabel("start")
        self.startedit = QLineEdit()
        self.startedit.setValidator(validator)

        self.endlabel = QLabel("end")
        self.endedit = QLineEdit()
        self.endedit.setValidator(validator)

        segmentfromcursor = QAction(QIcon(":/mouse_icon.png"),
                                    "select with mouse", self)
        segmentfromcursor.triggered.connect(self.enable_segmentedit)
        self.startedit.addAction(segmentfromcursor, QLineEdit.TrailingPosition)
        self.endedit.addAction(segmentfromcursor, QLineEdit.TrailingPosition)

        self.previewedit = QPushButton("preview segment")
        lambdafn = lambda: self._model.set_segment(
            [self.startedit.text(), self.endedit.text()])
        self.previewedit.clicked.connect(lambdafn)

        self.confirmedit = QPushButton("confirm segment")
        self.confirmedit.clicked.connect(self._controller.segment_signal)
        self.confirmedit.clicked.connect(self.segmentermap.map)
        self.segmentermap.setMapping(self.confirmedit, 0)

        self.abortedit = QPushButton("abort segmentation")
        self.abortedit.clicked.connect(self.segmentermap.map)
        # reset the segment to None
        self.segmentermap.setMapping(self.abortedit, 2)

        self.segmenterlayout = QFormLayout()
        self.segmenterlayout.addRow(self.startlabel, self.startedit)
        self.segmenterlayout.addRow(self.endlabel, self.endedit)
        self.segmenterlayout.addRow(self.previewedit)
        self.segmenterlayout.addRow(self.confirmedit)
        self.segmenterlayout.addRow(self.abortedit)
        self.segmenterwidget = QWidget()
        self.segmenterwidget.setLayout(self.segmenterlayout)
        self.segmenter.setWidget(self.segmenterwidget)

        self.segmenter.setVisible(False)
        self.segmenter.setAllowedAreas(Qt.RightDockWidgetArea)
        self.addDockWidget(Qt.RightDockWidgetArea, self.segmenter)

        # Set up dialog to gather user input for custom files.

        regex = QRegExp("[1-9][0-9]")
        validator = QRegExpValidator(regex)

        self.signallabel = QLabel("biosignal column")
        self.signaledit = QLineEdit()
        self.signaledit.setValidator(validator)

        self.markerlabel = QLabel("marker column")
        self.markeredit = QLineEdit()
        self.markeredit.setValidator(validator)

        regex = QRegExp("[0-9]{2}")
        validator = QRegExpValidator(regex)

        self.headerrowslabel = QLabel("number of header rows")
        self.headerrowsedit = QLineEdit()
        self.headerrowsedit.setValidator(validator)

        regex = QRegExp("[0-9]{5}")
        validator = QRegExpValidator(regex)

        self.sfreqlabel = QLabel("sampling rate")
        self.sfreqedit = QLineEdit()
        self.sfreqedit.setValidator(validator)

        self.separatorlabel = QLabel("column separator")
        self.separatormenu = QComboBox(self)
        self.separatormenu.addItem("comma")
        self.separatormenu.addItem("tab")
        self.separatormenu.addItem("colon")
        self.separatormenu.addItem("space")

        self.continuecustomfile = QPushButton("continue loading file")
        self.continuecustomfile.clicked.connect(self.set_customheader)

        self.customfiledialog = QDialog()
        self.customfiledialog.setWindowTitle("custom file info")
        self.customfiledialog.setWindowIcon(QIcon(":/file_icon.png"))
        self.customfiledialog.setWindowFlags(
            Qt.WindowCloseButtonHint
        )  # remove help button by only setting close button
        self.customfilelayout = QFormLayout()
        self.customfilelayout.addRow(self.signallabel, self.signaledit)
        self.customfilelayout.addRow(self.markerlabel, self.markeredit)
        self.customfilelayout.addRow(self.separatorlabel, self.separatormenu)
        self.customfilelayout.addRow(self.headerrowslabel, self.headerrowsedit)
        self.customfilelayout.addRow(self.sfreqlabel, self.sfreqedit)
        self.customfilelayout.addRow(self.continuecustomfile)
        self.customfiledialog.setLayout(self.customfilelayout)

        # set up menubar
        menubar = self.menuBar()

        # signal menu
        signalmenu = menubar.addMenu("biosignal")

        openSignal = signalmenu.addMenu("load")
        openEDF = QAction("EDF", self)
        openEDF.triggered.connect(lambda: self._model.set_filetype("EDF"))
        openEDF.triggered.connect(self._controller.get_fpaths)
        openSignal.addAction(openEDF)
        openOpenSignals = QAction("OpenSignals", self)
        openOpenSignals.triggered.connect(
            lambda: self._model.set_filetype("OpenSignals"))
        openOpenSignals.triggered.connect(self._controller.get_fpaths)
        openSignal.addAction(openOpenSignals)
        openCustom = QAction("Custom", self)
        openCustom.triggered.connect(
            lambda: self._model.set_filetype("Custom"))
        openCustom.triggered.connect(lambda: self.customfiledialog.exec_())
        openSignal.addAction(openCustom)

        segmentSignal = QAction("select segment", self)
        segmentSignal.triggered.connect(self.segmentermap.map)
        self.segmentermap.setMapping(segmentSignal, 1)
        signalmenu.addAction(segmentSignal)

        self.segmentermap.mapped.connect(self.toggle_segmenter)

        saveSignal = QAction("save", self)
        saveSignal.triggered.connect(self._controller.get_wpathsignal)
        signalmenu.addAction(saveSignal)

        # peak menu
        peakmenu = menubar.addMenu("peaks")

        findPeaks = QAction("find", self)
        findPeaks.triggered.connect(self._controller.find_peaks)
        peakmenu.addAction(findPeaks)

        autocorrectPeaks = QAction("autocorrect", self)
        autocorrectPeaks.triggered.connect(self._controller.autocorrect_peaks)
        peakmenu.addAction(autocorrectPeaks)

        savePeaks = QAction("save", self)
        savePeaks.triggered.connect(self._controller.get_wpathpeaks)
        peakmenu.addAction(savePeaks)

        loadPeaks = QAction("load", self)
        loadPeaks.triggered.connect(self._controller.get_rpathpeaks)
        peakmenu.addAction(loadPeaks)

        # stats menu
        statsmenu = menubar.addMenu("statistics")

        calculateStats = QAction("calculate", self)
        calculateStats.triggered.connect(self._controller.calculate_stats)
        statsmenu.addAction(calculateStats)

        saveStats = QAction("save", self)
        saveStats.triggered.connect(self._controller.get_wpathstats)
        statsmenu.addAction(saveStats)

        # set up status bar to display error messages and current file path
        self.statusBar = QStatusBar()
        self.setStatusBar(self.statusBar)
        self.progressBar = QProgressBar(self)
        self.progressBar.setRange(0, 1)
        self.statusBar.addPermanentWidget(self.progressBar)
        self.currentFile = QLabel()
        self.statusBar.addPermanentWidget(self.currentFile)

        # set up the central widget containing the plot and navigationtoolbar
        self.centwidget = QWidget()
        self.setCentralWidget(self.centwidget)

        # connect canvas0 to keyboard and mouse input for peak editing;
        # only widgets (e.g. canvas) that currently have focus capture
        # keyboard input: "You must enable keyboard focus for a widget if
        # it processes keyboard events."
        self.canvas0.setFocusPolicy(Qt.ClickFocus)
        self.canvas0.setFocus()
        self.canvas0.mpl_connect("key_press_event",
                                 self._controller.edit_peaks)
        self.canvas0.mpl_connect("button_press_event", self.get_xcursor)

        # arrange the three figure canvases in splitter object
        self.splitter = QSplitter(Qt.Vertical)
        # setting opaque resizing to false is important, since resizing gets
        # very slow otherwise once axes are populated
        self.splitter.setOpaqueResize(False)
        self.splitter.addWidget(self.canvas0)
        self.splitter.addWidget(self.canvas1)
        self.splitter.addWidget(self.canvas2)
        self.splitter.setChildrenCollapsible(False)

        # define GUI layout
        self.vlayout0 = QVBoxLayout(self.centwidget)
        self.vlayout1 = QVBoxLayout()
        self.vlayoutA = QFormLayout()
        self.vlayoutB = QFormLayout()
        self.vlayoutC = QVBoxLayout()
        self.vlayoutD = QVBoxLayout()
        self.hlayout0 = QHBoxLayout()

        self.optionsgroupA = QGroupBox("processing options")
        self.vlayoutA.addRow(self.modmenulabel, self.modmenu)
        self.vlayoutA.addRow(self.batchmenulabel, self.batchmenu)
        self.optionsgroupA.setLayout(self.vlayoutA)

        self.optionsgroupB = QGroupBox("channels")
        self.vlayoutB.addRow(self.sigchanmenulabel, self.sigchanmenu)
        self.vlayoutB.addRow(self.markerchanmenulabel, self.markerchanmenu)
        self.optionsgroupB.setLayout(self.vlayoutB)

        self.optionsgroupC = QGroupBox("peaks")
        self.vlayoutC.addWidget(self.editcheckbox)
        self.vlayoutC.addWidget(self.savecheckbox)
        self.vlayoutC.addWidget(self.correctcheckbox)
        self.optionsgroupC.setLayout(self.vlayoutC)

        self.optionsgroupD = QGroupBox("select statistics for saving")
        self.vlayoutD.addWidget(self.periodcheckbox)
        self.vlayoutD.addWidget(self.ratecheckbox)
        self.vlayoutD.addWidget(self.tidalampcheckbox)
        self.optionsgroupD.setLayout(self.vlayoutD)

        self.vlayout1.addWidget(self.optionsgroupA)
        self.vlayout1.addWidget(self.optionsgroupB)
        self.vlayout1.addWidget(self.optionsgroupC)
        self.vlayout1.addWidget(self.optionsgroupD)
        self.optionsgroupwidget = QWidget()
        self.optionsgroupwidget.setLayout(self.vlayout1)
        self.optionsgroup = QDockWidget("configurations", self)
        self.optionsgroup.setAllowedAreas(Qt.LeftDockWidgetArea)
        self.toggleoptionsgroup = self.optionsgroup.toggleViewAction()
        self.toggleoptionsgroup.setText("show/hide configurations")
        menubar.addAction(self.toggleoptionsgroup)
        self.optionsgroup.setWidget(self.optionsgroupwidget)
        self.addDockWidget(Qt.LeftDockWidgetArea, self.optionsgroup)

        self.vlayout0.addWidget(self.splitter)

        self.hlayout0.addWidget(self.navitools)
        self.vlayout0.addLayout(self.hlayout0)

        ##############################################
        # connect output widgets to external signals #
        ##############################################
        self._model.signal_changed.connect(self.plot_signal)
        self._model.marker_changed.connect(self.plot_marker)
        self._model.peaks_changed.connect(self.plot_peaks)
        self._model.period_changed.connect(self.plot_period)
        self._model.rate_changed.connect(self.plot_rate)
        self._model.tidalamp_changed.connect(self.plot_tidalamp)
        self._model.path_changed.connect(self.display_path)
        self._model.segment_changed.connect(self.plot_segment)
        self._model.status_changed.connect(self.display_status)
        self._model.progress_changed.connect(self.display_progress)
        self._model.model_reset.connect(self.reset_plot)

    ###########
    # methods #
    ###########

    def plot_signal(self, value):
        self.ax00.clear()
        self.ax00.relim()
        # reset navitools history
        self.navitools.update()
        self.line00 = self.ax00.plot(self._model.sec, value, zorder=1)
        self.ax00.set_xlabel("seconds", fontsize="large", fontweight="heavy")
        self.canvas0.draw()
#        print("plot_signal listening")
#        print(self.ax0.collections, self.ax0.patches, self.ax0.artists)

    def plot_peaks(self, value):
        # self.scat is listed in ax.collections
        if self.ax00.collections:
            self.ax00.collections[0].remove()
        self.scat = self.ax00.scatter(self._model.sec[value],
                                      self._model.signal[value],
                                      c="m",
                                      zorder=2)
        self.canvas0.draw()
#        print("plot_peaks listening")
#        print(self.ax0.collections, self.ax0.patches, self.ax0.artists)

    def plot_segment(self, value):
        # If an invalid signal has been selected reset the segmenter interface.
        if value is None:
            self.toggle_segmenter(1)
            return
        if self.ax00.patches:  # self.segementspan is listed in ax.patches
            self.ax00.patches[0].remove()
        self.segmentspan = self.ax00.axvspan(value[0],
                                             value[1],
                                             color="m",
                                             alpha=0.25)
        self.canvas0.draw()
        self.confirmedit.setEnabled(True)
#        print(self.ax0.collections, self.ax0.patches, self.ax0.artists)

    def plot_marker(self, value):
        self.ax10.clear()
        self.ax10.relim()
        self.line10 = self.ax10.plot(value[0], value[1])
        self.canvas1.draw()
#        print("plot_marker listening")

    def plot_period(self, value):
        self.ax20.clear()
        self.ax20.relim()
        self.navitools.home()
        if self._model.savestats["period"]:
            self.line20 = self.ax20.plot(self._model.sec, value, c="m")
        else:
            self.line20 = self.ax20.plot(self._model.sec, value)
        self.ax20.set_ylim(bottom=min(value), top=max(value))
        self.ax20.set_title("period", pad=0, fontweight="heavy")
        self.ax20.grid(True, axis="y")
        self.navitools.update()
        self.canvas2.draw()
#        print("plot_period listening")

    def plot_rate(self, value):
        self.ax21.clear()
        self.ax21.relim()
        self.navitools.home()
        if self._model.savestats["rate"]:
            self.line21 = self.ax21.plot(self._model.sec, value, c="m")
        else:
            self.line21 = self.ax21.plot(self._model.sec, value)
        self.ax21.set_ylim(bottom=min(value), top=max(value))
        self.ax21.set_title("rate", pad=0, fontweight="heavy")
        self.ax21.grid(True, axis="y")
        self.navitools.update()
        self.canvas2.draw()
#        print("plot_rate listening")

    def plot_tidalamp(self, value):
        self.ax22.clear()
        self.ax22.relim()
        self.navitools.home()
        if self._model.savestats["tidalamp"]:
            self.line22 = self.ax22.plot(self._model.sec, value, c="m")
        else:
            self.line22 = self.ax22.plot(self._model.sec, value)
        self.ax22.set_ylim(bottom=min(value), top=max(value))
        self.ax22.set_title("amplitude", pad=0, fontweight="heavy")
        self.ax22.grid(True, axis="y")
        self.navitools.update()
        self.canvas2.draw()
#        print("plot_tidalamp listening")

    def display_path(self, value):
        self.currentFile.setText(value)

    def display_status(self, status):
        # display status until new status is set
        self.statusBar.showMessage(status)

    def display_progress(self, value):
        # if value is 0, the progressbar indicates a busy state
        self.progressBar.setRange(0, value)

    def toggle_segmenter(self, value):
        if not self._model.loaded:
            return
        # Open segmenter when called from signalmenu or clear segmenter
        # upon selection of invalid segment.
        if value == 1:
            self.segmenter.setVisible(True)
            self.confirmedit.setEnabled(False)
            self.startedit.clear()
            self.endedit.clear()
            if self.ax00.patches:
                self.ax00.patches[0].remove()
                self.canvas0.draw()
        # Close segmenter after segment has been confirmed.
        elif value == 0:
            self.segmenter.setVisible(False)
            if self.ax00.patches:
                self.ax00.patches[0].remove()
                self.canvas0.draw()
        # Close segmenter after segmentation has been aborted (reset
        # segment).
        elif value == 2:
            self._model.set_segment([0,
                                     0])  # This will reset the model to None
            self.segmenter.setVisible(False)
            if self.ax00.patches:
                self.ax00.patches[0].remove()
                self.canvas0.draw()

    def enable_segmentedit(self):
        # disable peak editing to avoid interference
        self.editcheckbox.setChecked(False)
        if self.startedit.hasFocus():
            self.segmentcursor = "start"
        elif self.endedit.hasFocus():
            self.segmentcursor = "end"

    def set_customheader(self):
        """Populate the customheader with inputs from the customfiledialog"""

        # Check if one of the mandatory fields is missing.
        mandatoryfields = self.signaledit.text() and self.headerrowsedit.text(
        ) and self.sfreqedit.text()

        if not mandatoryfields:
            self._model.status = (
                "Please provide values for 'biosignal column'"
                ", 'number of header rows' and 'sampling"
                " rate'.")
            return

        seps = {"comma": ",", "tab": "\t", "colon": ":", "space": " "}
        self._model.customheader = dict.fromkeys(
            self._model.customheader, None
        )  # reset header here since it cannot be reset in controller.get_fpaths()

        self._model.customheader["signalidx"] = int(self.signaledit.text())
        self._model.customheader["skiprows"] = int(self.headerrowsedit.text())
        self._model.customheader["sfreq"] = int(self.sfreqedit.text())
        self._model.customheader["separator"] = seps[
            self.separatormenu.currentText()]
        if self.markeredit.text():  # not mandatory
            self._model.customheader["markeridx"] = int(self.markeredit.text())

        self.customfiledialog.done(QDialog.Accepted)  # close the dialog window
        self._controller.get_fpaths()  # move on to file selection

    def get_xcursor(self, event):
        # event.button 1 corresponds to left mouse button
        if event.button != 1:
            return
        # limit number of decimal places to two
        if self.segmentcursor == "start":
            self.startedit.selectAll()
            self.startedit.insert("{:.2f}".format(event.xdata))
        elif self.segmentcursor == "end":
            self.endedit.selectAll()
            self.endedit.insert("{:.2f}".format(event.xdata))
        # disable segment cursor again after value has been set
        self.segmentcursor = False

    def select_stats(self, event):
        """
        select or deselect statistics to be saved; toggle boolean with xor
        operator ^=, toggle color with dictionary
        """
        self._model.savestats[event] ^= True
        line = None
        if event == "period":
            if self.line20:
                line = self.line20[0]
        elif event == "rate":
            if self.line21:
                line = self.line21[0]
        elif event == "tidalamp":
            if self.line22:
                line = self.line22[0]
        if line:
            line.set_color(self.togglecolors[line.get_color()])
        self.canvas2.draw()

    def toggle_options(self, event):
        if event in ["ECG", "PPG"]:
            self.tidalampcheckbox.setEnabled(False)
            self.tidalampcheckbox.setChecked(False)
            self.ax22.set_visible(False)
            self.canvas2.draw()
        elif event == "RESP":
            self.tidalampcheckbox.setEnabled(True)
            self.ax22.set_visible(True)
            self.canvas2.draw()
        elif event == "multiple files":
            self.editcheckbox.setEnabled(False)
            self.editcheckbox.setChecked(False)
            self.savecheckbox.setEnabled(True)
            self.correctcheckbox.setEnabled(True)
            self.markerchanmenu.setEnabled(False)
        elif event == "single file":
            self.editcheckbox.setEnabled(True)
            self.markerchanmenu.setEnabled(True)
            self.savecheckbox.setEnabled(False)
            self.savecheckbox.setChecked(False)
            self.correctcheckbox.setEnabled(False)
            self.correctcheckbox.setChecked(False)

    def reset_plot(self):
        self.ax00.clear()
        self.ax00.relim()
        self.line00 = None
        self.scat = None
        self.segmentspan = None
        self.ax10.clear()
        self.ax10.relim()
        self.line10 = None
        self.ax20.clear()
        self.ax20.relim()
        self.line20 = None
        self.ax21.clear()
        self.ax21.relim()
        self.line21 = None
        self.ax22.clear()
        self.ax22.relim()
        self.line22 = None
        self.canvas0.draw()
        self.canvas1.draw()
        self.canvas2.draw()
        self.navitools.update()
        self.currentFile.clear()
示例#29
0
class MagnifierWidget(ToolWidget):
    def __init__(self, image, parent=None):
        super(MagnifierWidget, self).__init__(parent)

        self.equalize_radio = QRadioButton(self.tr("Equalization"))
        self.equalize_radio.setToolTip(self.tr("RGB histogram equalization"))
        self.contrast_radio = QRadioButton(self.tr("Auto Contrast"))
        self.contrast_radio.setToolTip(self.tr("Compress luminance tonality"))
        self.centile_spin = QSpinBox()
        self.centile_spin.setRange(0, 100)
        self.centile_spin.setValue(20)
        self.centile_spin.setSuffix(self.tr(" %"))
        self.centile_spin.setToolTip(self.tr("Histogram percentile amount"))
        self.channel_check = QCheckBox(self.tr("By channel"))
        self.channel_check.setToolTip(self.tr("Independent RGB compression"))
        self.equalize_radio.setChecked(True)
        self.last_radio = self.equalize_radio

        self.image = image
        self.viewer = ImageViewer(self.image, self.image)
        self.change()

        self.viewer.viewChanged.connect(self.process)
        self.equalize_radio.clicked.connect(self.change)
        self.contrast_radio.clicked.connect(self.change)
        self.centile_spin.valueChanged.connect(self.change)
        self.channel_check.stateChanged.connect(self.change)

        top_layout = QHBoxLayout()
        top_layout.addWidget(QLabel(self.tr("Mode:")))
        top_layout.addWidget(self.equalize_radio)
        top_layout.addWidget(self.contrast_radio)
        top_layout.addWidget(self.centile_spin)
        top_layout.addWidget(self.channel_check)
        top_layout.addStretch()

        main_layout = QVBoxLayout()
        main_layout.addLayout(top_layout)
        main_layout.addWidget(self.viewer)
        self.setLayout(main_layout)

    def process(self, rect):
        y1 = rect.top()
        y2 = rect.bottom()
        x1 = rect.left()
        x2 = rect.right()
        roi = self.image[y1:y2, x1:x2]
        if self.equalize_radio.isChecked():
            self.centile_spin.setEnabled(False)
            self.channel_check.setEnabled(False)
            roi = equalize_img(roi)
            self.last_radio = self.equalize_radio
        elif self.contrast_radio.isChecked():
            self.centile_spin.setEnabled(True)
            self.channel_check.setEnabled(True)
            centile = self.centile_spin.value() / 200
            if self.channel_check.isChecked():
                roi = cv.merge(
                    [cv.LUT(c, auto_lut(c, centile)) for c in cv.split(roi)])
            else:
                roi = cv.LUT(
                    roi, auto_lut(cv.cvtColor(roi, cv.COLOR_BGR2GRAY),
                                  centile))
            self.last_radio = self.contrast_radio
        else:
            self.last_radio.setChecked(True)
            return
        processed = np.copy(self.image)
        processed[y1:y2, x1:x2] = roi
        self.viewer.update_processed(processed)

    def change(self):
        self.process(self.viewer.get_rect())
示例#30
0
class PilotControls(QHBoxLayout):
    def __init__(self, roster: Optional[FlightRoster], idx: int) -> None:
        super().__init__()
        self.roster = roster
        self.pilot_index = idx

        self.selector = PilotSelector(roster, idx)
        self.selector.currentIndexChanged.connect(self.on_pilot_changed)
        self.addWidget(self.selector)

        self.player_checkbox = QCheckBox(text="Player")
        self.player_checkbox.setToolTip("Checked if this pilot is a player.")
        self.on_pilot_changed(self.selector.currentIndex())
        self.addWidget(self.player_checkbox)

        self.player_checkbox.toggled.connect(self.on_player_toggled)

    @property
    def pilot(self) -> Optional[Pilot]:
        if self.roster is None or self.pilot_index >= self.roster.max_size:
            return None
        return self.roster.pilots[self.pilot_index]

    def on_player_toggled(self, checked: bool) -> None:
        pilot = self.pilot
        if pilot is None:
            logging.error(
                "Cannot toggle state of a pilot when none is selected")
            return
        pilot.player = checked

    def on_pilot_changed(self, index: int) -> None:
        pilot = self.selector.itemData(index)
        self.player_checkbox.blockSignals(True)
        try:
            self.player_checkbox.setChecked(pilot is not None and pilot.player)
        finally:
            self.player_checkbox.blockSignals(False)

    def update_available_pilots(self) -> None:
        self.selector.rebuild()

    def enable_and_reset(self) -> None:
        self.selector.rebuild()
        self.player_checkbox.setEnabled(True)
        self.on_pilot_changed(self.selector.currentIndex())

    def disable_and_clear(self) -> None:
        self.selector.rebuild()
        self.player_checkbox.blockSignals(True)
        try:
            self.player_checkbox.setEnabled(False)
            self.player_checkbox.setChecked(False)
        finally:
            self.player_checkbox.blockSignals(False)

    def replace(self, new_roster: Optional[FlightRoster]) -> None:
        self.roster = new_roster
        if self.roster is None or self.pilot_index >= self.roster.max_size:
            self.disable_and_clear()
        else:
            self.enable_and_reset()
        self.selector.replace(new_roster)
示例#31
0
class SubtitleInfoDialog(QDialog):
    def __init__(self,
                 subtitle_name="Test",
                 subtitle_delay=0.0,
                 subtitle_language=Default_Subtitle_Language,
                 subtitle_track_name="Test",
                 subtitle_set_default=False,
                 subtitle_set_forced=False,
                 subtitle_default_value_delay=0.0,
                 subtitle_default_value_language=Default_Subtitle_Language,
                 subtitle_default_value_track_name="Test",
                 subtitle_default_value_set_default=False,
                 subtitle_default_value_set_forced=False,
                 subtitle_set_default_disabled=False,
                 subtitle_set_forced_disabled=False,
                 disable_edit=False,
                 parent=None):
        super().__init__(parent)
        self.window_title = "Subtitle Info"
        self.state = "no"
        self.messageIcon = QLabel()

        self.disable_edit = disable_edit

        self.current_subtitle_language = str(subtitle_language)
        self.current_subtitle_delay = str(subtitle_delay)
        self.current_subtitle_track_name = str(subtitle_track_name)
        self.current_subtitle_set_default = subtitle_set_default
        self.current_subtitle_set_forced = subtitle_set_forced

        self.default_subtitle_language = str(subtitle_default_value_language)
        self.default_subtitle_delay = str(subtitle_default_value_delay)
        self.default_subtitle_track_name = str(
            subtitle_default_value_track_name)
        self.default_subtitle_set_default = subtitle_default_value_set_default
        self.default_subtitle_set_forced = subtitle_default_value_set_forced

        self.subtitle_set_default_disabled = subtitle_set_default_disabled
        self.subtitle_set_forced_disabled = subtitle_set_forced_disabled

        self.subtitle_name_label = QLabel("Subtitle Name:")
        self.subtitle_name_value = QLabel(str(subtitle_name))

        self.subtitle_delay_label = QLabel("Subtitle Delay:")
        self.subtitle_delay_spin = QDoubleSpinBox()
        self.setup_subtitle_delay_spin()

        self.subtitle_language_label = QLabel("Subtitle Language:")
        self.subtitle_language_comboBox = QComboBox()
        self.setup_subtitle_language_comboBox()

        self.subtitle_track_name_label = QLabel("Subtitle Track Name:")
        self.subtitle_track_name_lineEdit = QLineEdit()
        self.setup_subtitle_track_name_lineEdit()

        self.subtitle_set_forced_label = QLabel("Subtitle Forced State:")
        self.subtitle_set_forced_checkBox = QCheckBox()
        self.setup_subtitle_set_forced_checkBox()

        self.subtitle_set_default_label = QLabel("Subtitle Default State:")
        self.subtitle_set_default_checkBox = QCheckBox()
        self.setup_subtitle_set_default_checkBox()

        self.yes_button = QPushButton("OK")
        self.no_button = QPushButton("Cancel")
        self.reset_button = QPushButton("Reset To Default")

        self.buttons_layout = QHBoxLayout()
        self.subtitle_delay_layout = QHBoxLayout()
        self.subtitle_language_layout = QHBoxLayout()
        self.subtitle_track_name_layout = QHBoxLayout()
        self.subtitle_set_default_layout = QHBoxLayout()
        self.subtitle_set_forced_layout = QHBoxLayout()
        self.buttons_layout.addWidget(QLabel(""), stretch=3)
        self.buttons_layout.addWidget(self.reset_button, stretch=2)
        self.buttons_layout.addWidget(self.yes_button, stretch=2)
        self.buttons_layout.addWidget(self.no_button, stretch=2)
        self.buttons_layout.addWidget(QLabel(""), stretch=3)
        self.subtitle_setting_layout = QGridLayout()
        self.subtitle_changeble_setting_layout = QFormLayout()
        self.subtitle_changeble_setting_layout.addRow(self.subtitle_name_label,
                                                      self.subtitle_name_value)
        self.subtitle_changeble_setting_layout.addRow(
            self.subtitle_track_name_label, self.subtitle_track_name_lineEdit)
        self.subtitle_changeble_setting_layout.addRow(
            self.subtitle_language_label, self.subtitle_language_comboBox)
        self.subtitle_changeble_setting_layout.addRow(
            self.subtitle_delay_label, self.subtitle_delay_spin)
        self.subtitle_changeble_setting_layout.addRow(
            self.subtitle_set_default_label,
            self.subtitle_set_default_checkBox)
        self.subtitle_changeble_setting_layout.addRow(
            self.subtitle_set_forced_label, self.subtitle_set_forced_checkBox)

        self.subtitle_setting_layout.addLayout(
            self.subtitle_changeble_setting_layout, 0, 0, 5, 2)
        self.subtitle_setting_layout.addWidget(self.messageIcon, 0, 3, 5, -1)

        self.main_layout = QGridLayout()
        self.main_layout.addLayout(self.subtitle_setting_layout, 0, 0, 2, 3)
        self.main_layout.addLayout(self.buttons_layout, 2, 0, 1, -1)
        self.main_layout.setContentsMargins(20, 20, 20, 20)
        self.setLayout(self.main_layout)

        self.setup_ui()
        self.signal_connect()

    def setup_ui(self):
        self.disable_question_mark_window()
        self.messageIcon.setPixmap(
            QtGui.QPixmap(GlobalFiles.SubtitleIconPath).scaledToHeight(100))
        self.set_dialog_values()
        self.set_default_buttons()
        if self.subtitle_set_default_disabled:
            self.subtitle_set_default_disable()
        if self.subtitle_set_forced_disabled:
            self.subtitle_set_forced_disable()
        if self.disable_edit:
            self.subtitle_track_name_lineEdit.setEnabled(False)
            self.subtitle_language_comboBox.setEnabled(False)
            self.subtitle_delay_spin.setEnabled(False)
            self.subtitle_set_default_checkBox.setEnabled(False)
            self.subtitle_set_forced_checkBox.setEnabled(False)
            self.reset_button.setEnabled(False)

        self.setup_tool_tip_hint_subtitle_set_default()
        self.setup_tool_tip_hint_subtitle_set_forced()

    def signal_connect(self):
        self.subtitle_track_name_lineEdit.textEdited.connect(
            self.update_current_subtitle_track_name)
        self.subtitle_delay_spin.editingFinished.connect(
            self.update_current_subtitle_delay)
        self.subtitle_language_comboBox.currentTextChanged.connect(
            self.update_current_subtitle_language)
        self.subtitle_set_default_checkBox.stateChanged.connect(
            self.update_current_subtitle_set_default)
        self.subtitle_set_forced_checkBox.stateChanged.connect(
            self.update_current_subtitle_set_forced)
        self.yes_button.clicked.connect(self.click_yes)
        self.no_button.clicked.connect(self.click_no)
        self.reset_button.clicked.connect(self.reset_subtitle_setting)

    def click_yes(self):
        self.state = "yes"
        self.close()

    def click_no(self):
        self.close()

    def set_dialog_values(self):
        self.setWindowTitle(self.window_title)
        self.setWindowIcon(GlobalFiles.InfoSettingIcon)

    def disable_question_mark_window(self):
        self.setWindowFlag(Qt.WindowContextHelpButtonHint, on=False)

    def increase_message_font_size(self, value):
        message_font = self.message.font()
        message_font.setPointSize(self.message.fontInfo().pointSize() + value)
        self.message.setFont(message_font)

    def set_default_buttons(self):
        self.yes_button.setDefault(True)
        self.yes_button.setFocus()

    def showEvent(self, a0: QtGui.QShowEvent) -> None:
        super().showEvent(a0)
        self.setFixedSize(self.size())

    def setup_subtitle_track_name_lineEdit(self):
        self.subtitle_track_name_lineEdit.setClearButtonEnabled(True)
        self.subtitle_track_name_lineEdit.setText(
            self.current_subtitle_track_name)

    def setup_subtitle_language_comboBox(self):
        self.subtitle_language_comboBox.addItems(AllSubtitlesLanguages)
        self.subtitle_language_comboBox.setCurrentIndex(
            AllSubtitlesLanguages.index(self.current_subtitle_language))
        self.subtitle_language_comboBox.setMaxVisibleItems(8)
        self.subtitle_language_comboBox.setStyleSheet(
            "QComboBox { combobox-popup: 0; }")

    def setup_subtitle_delay_spin(self):
        # self.subtitle_delay_spin.setMaximumWidth(screen_size.width() // 16)
        self.subtitle_delay_spin.setDecimals(3)
        self.subtitle_delay_spin.setMinimum(-9999.0)
        self.subtitle_delay_spin.setMaximum(9999.0)
        self.subtitle_delay_spin.setSingleStep(0.5)
        self.subtitle_delay_spin.setValue(float(self.current_subtitle_delay))

    def setup_subtitle_set_default_checkBox(self):
        self.subtitle_set_default_checkBox.setText("Set Default")
        self.subtitle_set_default_checkBox.setChecked(
            bool(self.current_subtitle_set_default))

    def setup_subtitle_set_forced_checkBox(self):
        self.subtitle_set_forced_checkBox.setText("Set Forced")
        self.subtitle_set_forced_checkBox.setChecked(
            bool(self.current_subtitle_set_forced))

    def update_current_subtitle_track_name(self):
        self.current_subtitle_track_name = str(
            self.subtitle_track_name_lineEdit.text())

    def update_current_subtitle_delay(self):
        self.current_subtitle_delay = round(self.subtitle_delay_spin.value(),
                                            5)

    def update_current_subtitle_language(self):
        self.current_subtitle_language = str(
            self.subtitle_language_comboBox.currentText())

    def update_current_subtitle_set_default(self):
        self.current_subtitle_set_default = (
            self.subtitle_set_default_checkBox.checkState() == Qt.Checked)

    def update_current_subtitle_set_forced(self):
        self.current_subtitle_set_forced = (
            self.subtitle_set_forced_checkBox.checkState() == Qt.Checked)

    def reset_subtitle_setting(self):
        self.current_subtitle_language = self.default_subtitle_language
        self.current_subtitle_delay = self.default_subtitle_delay
        self.current_subtitle_track_name = self.default_subtitle_track_name
        self.current_subtitle_set_default = self.default_subtitle_set_default
        self.current_subtitle_set_forced = self.default_subtitle_set_forced

        self.subtitle_language_comboBox.setCurrentIndex(
            AllSubtitlesLanguages.index(self.current_subtitle_language))
        self.subtitle_delay_spin.setValue(float(self.current_subtitle_delay))
        self.subtitle_track_name_lineEdit.setText(
            self.current_subtitle_track_name)
        self.subtitle_set_default_checkBox.setChecked(
            bool(self.current_subtitle_set_default))
        self.subtitle_set_forced_checkBox.setChecked(
            bool(self.current_subtitle_set_forced))

    def subtitle_set_default_disable(self):
        self.subtitle_set_default_checkBox.setDisabled(True)

    def subtitle_set_forced_disable(self):
        self.subtitle_set_forced_checkBox.setDisabled(True)

    def setup_tool_tip_hint_subtitle_set_default(self):
        if self.subtitle_set_default_checkBox.isEnabled():
            self.subtitle_set_default_checkBox.setToolTip(
                "<nobr>set this subtitle to be the default subtitle track "
                "when play")
            self.subtitle_set_default_checkBox.setToolTipDuration(12000)
        else:
            self.subtitle_set_default_checkBox.setToolTip(
                "<nobr>set this subtitle to be the default subtitle track when play<br><b>Disabled</b> because "
                "option "
                "<b>make this subtitle default</b> is enabled on mux setting tab "
            )
            self.subtitle_set_default_checkBox.setToolTipDuration(12000)

    def setup_tool_tip_hint_subtitle_set_forced(self):
        if self.subtitle_set_forced_checkBox.isEnabled():
            self.subtitle_set_forced_checkBox.setToolTip(
                "<nobr>set this subtitle to be the forced subtitle track when "
                "play")
            self.subtitle_set_forced_checkBox.setToolTipDuration(12000)
        else:
            self.subtitle_set_forced_checkBox.setToolTip(
                "<nobr>set this subtitle to be the forced subtitle track when play<br><b>Disabled</b> because "
                "option "
                "<b>make this subtitle default and forced</b> is enabled on mux setting tab "
            )
            self.subtitle_set_forced_checkBox.setToolTipDuration(12000)

    def execute(self):
        self.exec_()
示例#32
0
class MainWindow(QWidget):
    def __init__(self, appctx):
        super().__init__()

        self.appctx = appctx
        self.api = None
        self.settings = Settings()
        self.gateway = Gateway()
        self.timers: Dict[str, QTimer] = {}

        self.init_ui()

    def init_ui(self):
        self.vbox = QVBoxLayout()
        self.hbox = QHBoxLayout()

        # Set layout
        self.setLayout(self.vbox)

        # Group list
        vbox3 = QVBoxLayout()
        group_frame = QGroupBox("Device Groups")
        group_frame.setLayout(QVBoxLayout())
        self.group_list = QListWidget()
        self.group_list.itemPressed.connect(self.group_selected)
        group_frame.layout().addWidget(self.group_list)
        vbox3.addWidget(group_frame)

        # Sliders
        self.group_toggle = QCheckBox("Power")
        self.group_toggle.setEnabled(False)
        vbox3.addWidget(self.group_toggle)
        vbox3.addWidget(QLabel("Brightness"))
        self.group_brightness_slider = QSlider(Qt.Orientation.Horizontal)
        self.group_brightness_slider.setEnabled(False)
        self.group_brightness_slider.sliderMoved.connect(
            self.group_brightness_changed)
        vbox3.addWidget(self.group_brightness_slider)
        vbox3.addWidget(QLabel("Color Temperature"))
        self.group_color_slider = QSlider(Qt.Orientation.Horizontal)
        self.group_color_slider.setEnabled(False)
        self.group_color_slider.sliderMoved.connect(self.group_color_changed)
        vbox3.addWidget(self.group_color_slider)

        self.hbox.addLayout(vbox3)

        # moods
        mood_frame = QGroupBox("Moods")
        mood_frame.setLayout(QVBoxLayout())
        self.mood_list = QListWidget()
        self.mood_list.itemPressed.connect(self.mood_selected)
        mood_frame.layout().addWidget(self.mood_list)

        self.hbox.addWidget(mood_frame)

        # Devices in group
        vbox2 = QVBoxLayout()

        device_frame = QGroupBox("Devices in Group")
        device_frame.setLayout(QVBoxLayout())
        self.device_list = QListWidget()
        self.device_list.setEnabled(False)
        self.device_list.itemPressed.connect(self.device_selected)
        device_frame.layout().addWidget(self.device_list)
        vbox2.addWidget(device_frame)

        # Sliders
        self.device_toggle = QCheckBox("Power")
        self.device_toggle.setEnabled(False)
        vbox2.addWidget(self.device_toggle)
        vbox2.addWidget(QLabel("Brightness"))
        self.brightness_slider = QSlider(Qt.Orientation.Horizontal)
        self.brightness_slider.setEnabled(False)
        self.brightness_slider.sliderMoved.connect(self.brightness_changed)
        vbox2.addWidget(self.brightness_slider)
        vbox2.addWidget(QLabel("Color Temperature"))
        self.color_slider = QSlider(Qt.Orientation.Horizontal)
        self.color_slider.setEnabled(False)
        self.color_slider.sliderMoved.connect(self.color_changed)
        vbox2.addWidget(self.color_slider)

        self.hbox.addLayout(vbox2)
        self.vbox.addLayout(self.hbox)

        # Settings button
        icon = QIcon(resource_path('icons/settings.png'))
        self.settings_button = QPushButton(icon, "Settings")
        self.settings_button.pressed.connect(self.settings_pressed)
        self.vbox.addWidget(self.settings_button)

        self.setWindowTitle('TradfriGUI')
        self.re_init()

    def re_init(self):
        if self.settings.gateway_ip is None or self.settings.gateway_ip == '':
            self.settings_pressed()
        self.api = get_api(self.settings)

        self.device_list.clear()
        self.group_list.clear()

        if self.api is None:
            return

        groups = self.api(self.gateway.get_groups())
        if len(groups) == 0:
            self.group_list.setEnabled(False)
            # TODO: load devices directly

        for group in groups:
            item = self.api(group)
            list_item = QListWidgetItem(item.name, self.group_list)
            setattr(list_item, 'api_item', item)

    def group_selected(self):
        current_item = self.group_list.currentItem()
        item = getattr(current_item, 'api_item', None)
        if item is None:
            return
        # refresh from gateway
        item = self.api(self.gateway.get_group(item.id))

        # load moods
        self.mood_list.clear()
        moods = self.api(item.moods())
        for m in moods:
            mood = self.api(m)
            list_item = QListWidgetItem(mood.name, self.mood_list)
            setattr(list_item, 'api_item', mood)

        # load devices
        devices = item.members()
        self.device_list.clear()

        # determine shared state and add devices to list
        state = False
        color_temp = False
        min_color = 10000
        max_color = 0
        color = []
        brightness = []
        for d in devices:
            device = self.api(d)
            if device.has_light_control:
                if device.light_control.lights[0].state:
                    state = True
                if device.light_control.can_set_dimmer:
                    if device.light_control.lights[0].state:
                        brightness.append(
                            device.light_control.lights[0].dimmer)
                    else:
                        brightness.append(0)
                if device.light_control.can_set_temp:
                    color_temp = True
                    min_color = min(min_color, device.light_control.min_mireds)
                    max_color = max(max_color, device.light_control.max_mireds)
                    color.append(device.light_control.lights[0].color_temp)
            list_item = QListWidgetItem(device.name, self.device_list)
            setattr(list_item, 'api_item', device)

        if len(brightness) > 0:
            brightness = int(sum(brightness) / len(brightness))
        else:
            brightness = 0

        if len(color) > 0:
            color = int(sum(color) / len(color))
        else:
            color = min_color

        # enable device list and controls
        self.device_list.setEnabled(True)

        self.group_brightness_slider.setEnabled(True)
        self.group_brightness_slider.setMinimum(0)
        self.group_brightness_slider.setMaximum(254)
        self.group_brightness_slider.setSingleStep(16)
        self.group_brightness_slider.setValue(brightness)

        if color_temp:
            self.group_color_slider.setEnabled(True)
            self.group_color_slider.setMinimum(min_color)
            self.group_color_slider.setMaximum(max_color)
            self.group_color_slider.setSingleStep(
                int((max_color - min_color) / 10))
            self.group_color_slider.setValue(color)
        else:
            self.group_color_slider.setEnabled(False)

        self.group_toggle.setEnabled(True)
        try:
            self.group_toggle.stateChanged.disconnect(self.group_toggled)
        except RuntimeError:
            pass  # Disconnect failed because nothing was connected
        self.group_toggle.setCheckState(
            Qt.CheckState.Checked if state else Qt.CheckState.Unchecked)
        self.group_toggle.stateChanged.connect(self.group_toggled)

        self.brightness_slider.setEnabled(False)
        self.color_slider.setEnabled(False)
        self.device_toggle.setEnabled(False)

    def device_selected(self):
        current_item = self.device_list.currentItem()
        item = getattr(current_item, 'api_item', None)
        if item is None:
            return
        # refresh from gateway
        item = self.api(self.gateway.get_device(item.id))

        # enable appropriate controls
        if item.has_light_control:
            ctrl = item.light_control
            if ctrl.can_set_dimmer:
                self.brightness_slider.setEnabled(True)
                self.brightness_slider.setMinimum(0)
                self.brightness_slider.setMaximum(254)
                self.brightness_slider.setSingleStep(16)
                self.brightness_slider.setValue(ctrl.lights[0].dimmer)
            else:
                self.brightness_slider.setEnabled(False)
            if ctrl.can_set_temp:
                self.color_slider.setEnabled(True)
                self.color_slider.setMinimum(ctrl.min_mireds)
                self.color_slider.setMaximum(ctrl.max_mireds)
                self.color_slider.setSingleStep(
                    int((ctrl.max_mireds - ctrl.min_mireds) / 10))
                self.color_slider.setValue(ctrl.lights[0].color_temp)
            else:
                self.color_slider.setEnabled(False)
            self.device_toggle.setEnabled(True)
            try:
                self.device_toggle.stateChanged.disconnect(self.device_toggled)
            except RuntimeError:
                pass  # disconnect failed because nothing was connected
            self.device_toggle.setCheckState(
                Qt.CheckState.Checked if ctrl.lights[0].state else Qt.
                CheckState.Unchecked)
            self.device_toggle.stateChanged.connect(self.device_toggled)
        else:
            self.brightness_slider.setEnabled(False)
            self.color_slider.setEnabled(False)
            self.device_toggle.setEnabled(False)

    def mood_selected(self):
        current_group = self.group_list.currentItem()
        group = getattr(current_group, 'api_item', None)
        if group is None:
            return
        # refresh from gateway
        group = self.api(self.gateway.get_group(group.id))

        current_mood = self.mood_list.currentItem()
        mood = getattr(current_mood, 'api_item', None)
        if mood is None:
            return

        self.api(group.activate_mood(mood.id))

    def group_brightness_changed(self):
        current_item = self.group_list.currentItem()
        if current_item is None:
            return
        item = getattr(current_item, 'api_item', None)
        command = item.set_dimmer(self.group_brightness_slider.value(),
                                  transition_time=2)

        self.queue_command('group_brightness', command)

    def group_color_changed(self):
        current_item = self.group_list.currentItem()
        if current_item is None:
            return
        item = getattr(current_item, 'api_item', None)

        command = item.set_color_temp(self.group_color_slider.value(),
                                      transition_time=2)

        self.queue_command('group_color', command)

    def brightness_changed(self):
        current_item = self.device_list.currentItem()
        if current_item is None:
            return
        item = getattr(current_item, 'api_item', None)
        command = item.light_control.set_dimmer(self.brightness_slider.value(),
                                                transition_time=2)

        self.queue_command('device_brightness_{}'.format(item.id), command)

    def color_changed(self):
        current_item = self.device_list.currentItem()
        if current_item is None:
            return
        item = getattr(current_item, 'api_item', None)
        command = item.light_control.set_color_temp(self.color_slider.value(),
                                                    transition_time=2)

        self.queue_command('device_color_{}'.format(item.id), command)

    def device_toggled(self):
        current_item = self.device_list.currentItem()
        if current_item is None:
            return
        item = getattr(current_item, 'api_item', None)
        command = item.light_control.set_state(
            self.device_toggle.checkState() == Qt.CheckState.Checked)
        self.api(command)

    def group_toggled(self):
        current_item = self.group_list.currentItem()
        if current_item is None:
            return
        item = getattr(current_item, 'api_item', None)
        command = item.set_state(
            self.group_toggle.checkState() == Qt.CheckState.Checked)
        self.api(command)

    def settings_pressed(self):
        config = ConfigWindow(self.appctx, self)
        config.setWindowModality(Qt.ApplicationModal)
        config.exec_()

        # reload settings
        self.settings = config.settings

        # re-initialize window
        self.re_init()

    def queue_command(self, name, command):
        timer = self.timers.get(name, None)
        if timer is None:
            timer = QTimer()
            timer.setInterval(200)
            timer.setSingleShot(True)
            timer.timeout.connect(self.timeout)
            timer.start()

        setattr(timer, 'command', command)
        self.timers[name] = timer

    def timeout(self):
        remove = []
        for key, item in self.timers.items():
            if item.isActive() == False:
                cmd = getattr(item, 'command')
                self.api(cmd)
                remove.append(key)
        for key in remove:
            del self.timers[key]