Ejemplo n.º 1
0
class FrequencyWidget(ToolWidget):
    def __init__(self, image, parent=None):
        super(FrequencyWidget, self).__init__(parent)

        self.split_spin = QSpinBox()
        self.split_spin.setRange(0, 100)
        self.split_spin.setValue(15)
        self.split_spin.setSuffix(self.tr(" %"))

        self.smooth_spin = QSpinBox()
        self.smooth_spin.setRange(0, 100)
        self.smooth_spin.setValue(25)
        self.smooth_spin.setSuffix(self.tr(" %"))
        self.smooth_spin.setSpecialValueText(self.tr("Off"))

        self.thr_spin = QSpinBox()
        self.thr_spin.setRange(0, 100)
        self.thr_spin.setValue(0)
        self.thr_spin.setSuffix(self.tr(" %"))
        self.thr_spin.setSpecialValueText(self.tr("Off"))

        self.zero_label = QLabel()
        modify_font(self.zero_label, italic=True)

        self.filter_spin = QSpinBox()
        self.filter_spin.setRange(0, 15)
        self.filter_spin.setValue(0)
        self.filter_spin.setSuffix(self.tr(" px"))
        self.filter_spin.setSpecialValueText(self.tr("Off"))

        self.split_spin.valueChanged.connect(self.process)
        self.smooth_spin.valueChanged.connect(self.process)
        self.thr_spin.valueChanged.connect(self.process)
        self.filter_spin.valueChanged.connect(self.postprocess)

        self.image = image
        gray = cv.cvtColor(self.image, cv.COLOR_BGR2GRAY)
        rows, cols = gray.shape
        height = cv.getOptimalDFTSize(rows)
        width = cv.getOptimalDFTSize(cols)
        padded = cv.copyMakeBorder(gray, 0, height - rows, 0, width - cols, cv.BORDER_CONSTANT)
        self.dft = np.fft.fftshift(cv.dft(padded.astype(np.float32), flags=cv.DFT_COMPLEX_OUTPUT))
        self.magnitude0, self.phase0 = cv.cartToPolar(self.dft[:, :, 0], self.dft[:, :, 1])
        self.magnitude0 = cv.normalize(cv.log(self.magnitude0), None, 0, 255, cv.NORM_MINMAX)
        self.phase0 = cv.normalize(self.phase0, None, 0, 255, cv.NORM_MINMAX)
        self.magnitude = self.phase = None

        self.low_viewer = ImageViewer(self.image, self.image, self.tr("Low frequency"), export=True)
        self.high_viewer = ImageViewer(self.image, self.image, self.tr("High frequency"), export=True)
        self.mag_viewer = ImageViewer(self.image, None, self.tr("DFT Magnitude"), export=True)
        self.phase_viewer = ImageViewer(self.image, None, self.tr("DFT Phase"), export=True)
        self.process()

        self.low_viewer.viewChanged.connect(self.high_viewer.changeView)
        self.high_viewer.viewChanged.connect(self.low_viewer.changeView)
        self.mag_viewer.viewChanged.connect(self.phase_viewer.changeView)
        self.phase_viewer.viewChanged.connect(self.mag_viewer.changeView)

        top_layout = QHBoxLayout()
        top_layout.addWidget(QLabel(self.tr("Separation:")))
        top_layout.addWidget(self.split_spin)
        top_layout.addWidget(QLabel(self.tr("Smooth:")))
        top_layout.addWidget(self.smooth_spin)
        top_layout.addWidget(QLabel(self.tr("Threshold:")))
        top_layout.addWidget(self.thr_spin)
        top_layout.addWidget(QLabel(self.tr("Filter:")))
        top_layout.addWidget(self.filter_spin)
        top_layout.addWidget(self.zero_label)
        top_layout.addStretch()

        center_layout = QGridLayout()
        center_layout.addWidget(self.low_viewer, 0, 0)
        center_layout.addWidget(self.high_viewer, 0, 1)
        center_layout.addWidget(self.mag_viewer, 1, 0)
        center_layout.addWidget(self.phase_viewer, 1, 1)

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

    def process(self):
        start = time()
        rows, cols, _ = self.dft.shape
        mask = np.zeros((rows, cols), np.float32)
        half = np.sqrt(rows ** 2 + cols ** 2) / 2
        radius = int(half * self.split_spin.value() / 100)
        mask = cv.circle(mask, (cols // 2, rows // 2), radius, 1, cv.FILLED)
        kernel = 2 * int(half * self.smooth_spin.value() / 100) + 1
        mask = cv.GaussianBlur(mask, (kernel, kernel), 0)
        mask /= np.max(mask)
        threshold = int(self.thr_spin.value() / 100 * 255)
        if threshold > 0:
            mask[self.magnitude0 < threshold] = 0
            zeros = (mask.size - np.count_nonzero(mask)) / mask.size * 100
        else:
            zeros = 0
        self.zero_label.setText(self.tr("(zeroed coefficients = {:.2f}%)").format(zeros))
        mask2 = np.repeat(mask[:, :, np.newaxis], 2, axis=2)

        rows0, cols0, _ = self.image.shape
        low = cv.idft(np.fft.ifftshift(self.dft * mask2), flags=cv.DFT_SCALE)
        low = norm_mat(cv.magnitude(low[:, :, 0], low[:, :, 1])[:rows0, :cols0], to_bgr=True)
        self.low_viewer.update_processed(low)
        high = cv.idft(np.fft.ifftshift(self.dft * (1 - mask2)), flags=cv.DFT_SCALE)
        high = norm_mat(cv.magnitude(high[:, :, 0], high[:, :, 1]), to_bgr=True)
        self.high_viewer.update_processed(np.copy(high[: self.image.shape[0], : self.image.shape[1]]))
        self.magnitude = (self.magnitude0 * mask).astype(np.uint8)
        self.phase = (self.phase0 * mask).astype(np.uint8)
        self.postprocess()
        self.info_message.emit(self.tr(f"Frequency Split = {elapsed_time(start)}"))

    def postprocess(self):
        kernel = 2 * self.filter_spin.value() + 1
        if kernel >= 3:
            magnitude = cv.GaussianBlur(self.magnitude, (kernel, kernel), 0)
            phase = cv.GaussianBlur(self.phase, (kernel, kernel), 0)
            # phase = cv.medianBlur(self.phase, kernel)
        else:
            magnitude = self.magnitude
            phase = self.phase
        self.mag_viewer.update_original(cv.cvtColor(magnitude, cv.COLOR_GRAY2BGR))
        self.phase_viewer.update_original(cv.cvtColor(phase, cv.COLOR_GRAY2BGR))
Ejemplo n.º 2
0
class FrequencyWidget(ToolWidget):
    def __init__(self, image, parent=None):
        super(FrequencyWidget, self).__init__(parent)

        self.ampl_radio = QRadioButton(self.tr('Amplitude'))
        self.ampl_radio.setChecked(True)
        self.phase_radio = QRadioButton(self.tr('Phase'))
        self.dct_radio = QRadioButton(self.tr('DCT Map'))
        self.last_radio = self.ampl_radio
        self.thr_spin = QSpinBox()
        self.thr_spin.setRange(0, 255)
        self.thr_spin.setSpecialValueText(self.tr('Off'))
        self.ratio_label = QLabel()
        self.filter_check = QCheckBox(self.tr('Filter'))

        self.ampl_radio.clicked.connect(self.process)
        self.phase_radio.clicked.connect(self.process)
        self.dct_radio.clicked.connect(self.process)
        self.thr_spin.valueChanged.connect(self.process)
        self.filter_check.stateChanged.connect(self.process)

        gray = cv.cvtColor(image, cv.COLOR_BGR2GRAY)
        rows, cols = gray.shape
        height = cv.getOptimalDFTSize(rows)
        width = cv.getOptimalDFTSize(cols)
        padded = cv.copyMakeBorder(gray, 0, height - rows, 0, width - cols,
                                   cv.BORDER_CONSTANT).astype(np.float32)
        planes = cv.merge([padded, np.zeros_like(padded)])
        dft = cv.split(np.fft.fftshift(cv.dft(planes)))
        mag, phase = cv.cartToPolar(dft[0], dft[1])
        dct = cv.dct(padded)
        self.result = [
            normalize_mat(img)
            for img in [cv.log(mag), phase, cv.log(dct)]
        ]

        self.image = image
        self.viewer = ImageViewer(self.image, None)
        self.process()

        top_layout = QHBoxLayout()
        top_layout.addWidget(QLabel(self.tr('Coefficients:')))
        top_layout.addWidget(self.ampl_radio)
        top_layout.addWidget(self.phase_radio)
        top_layout.addWidget(self.dct_radio)
        top_layout.addWidget(self.filter_check)
        top_layout.addStretch()
        top_layout.addWidget(QLabel(self.tr('Threshold:')))
        top_layout.addWidget(self.thr_spin)
        top_layout.addWidget(self.ratio_label)

        main_layout = QVBoxLayout()
        main_layout.addLayout(top_layout)
        main_layout.addWidget(self.viewer)
        self.setLayout(main_layout)

    def process(self):
        if self.ampl_radio.isChecked():
            output = self.result[0]
            self.last_radio = self.ampl_radio
        elif self.phase_radio.isChecked():
            output = self.result[1]
            self.last_radio = self.phase_radio
        elif self.dct_radio.isChecked():
            output = self.result[2]
            self.last_radio = self.dct_radio
        else:
            self.last_radio.setChecked(True)
            return
        if self.filter_check.isChecked():
            output = cv.medianBlur(output, 3)
        thr = self.thr_spin.value()
        if thr > 0:
            _, output = cv.threshold(output, thr, 0, cv.THRESH_TOZERO)
            zeros = (1.0 - cv.countNonZero(output) / output.size) * 100
        else:
            zeros = 0
        self.ratio_label.setText('(masked = {:.1f}%)'.format(zeros))
        self.viewer.update_original(cv.cvtColor(output, cv.COLOR_GRAY2BGR))
Ejemplo n.º 3
0
class StereoWidget(ToolWidget):
    def __init__(self, image, parent=None):
        super(StereoWidget, self).__init__(parent)

        gray = cv.cvtColor(image, cv.COLOR_BGR2GRAY)
        small = cv.resize(gray, None, None, 1, 0.5)
        start = 10
        end = small.shape[1] // 3
        diff = np.fromiter([
            cv.mean(cv.absdiff(small[:, i:], small[:, :-i]))[0]
            for i in range(start, end)
        ], np.float32)
        _, maximum, _, argmax = cv.minMaxLoc(np.ediff1d(diff))
        if maximum < 2:
            error_label = QLabel(self.tr("Unable to detect stereogram!"))
            modify_font(error_label, bold=True)
            error_label.setStyleSheet("color: #FF0000")
            error_label.setAlignment(Qt.AlignCenter)
            main_layout = QVBoxLayout()
            main_layout.addWidget(error_label)
            self.setLayout(main_layout)
            return

        offset = argmax[1] + start
        a = image[:, offset:]
        b = image[:, :-offset]
        self.pattern = norm_img(cv.absdiff(a, b))
        temp = cv.cvtColor(self.pattern, cv.COLOR_BGR2GRAY)
        thr, _ = cv.threshold(temp, 0, 255, cv.THRESH_TRIANGLE)
        self.silhouette = cv.medianBlur(
            gray_to_bgr(cv.threshold(temp, thr, 255, cv.THRESH_BINARY)[1]), 3)
        a = cv.cvtColor(a, cv.COLOR_BGR2GRAY)
        b = cv.cvtColor(b, cv.COLOR_BGR2GRAY)
        flow = cv.calcOpticalFlowFarneback(a, b, None, 0.5, 5, 15, 5, 5, 1.2,
                                           cv.OPTFLOW_FARNEBACK_GAUSSIAN)[:, :,
                                                                          0]
        self.depth = gray_to_bgr(norm_mat(flow))
        flow = np.repeat(cv.normalize(flow, None, 0, 1,
                                      cv.NORM_MINMAX)[:, :, np.newaxis],
                         3,
                         axis=2)
        self.shaded = cv.normalize(
            self.pattern.astype(np.float32) * flow, None, 0, 255,
            cv.NORM_MINMAX).astype(np.uint8)
        self.viewer = ImageViewer(self.pattern, None, export=True)

        self.pattern_radio = QRadioButton(self.tr("Pattern"))
        self.pattern_radio.setChecked(True)
        self.pattern_radio.setToolTip(
            self.tr("Difference between raw and aligned image"))
        self.silhouette_radio = QRadioButton(self.tr("Silhouette"))
        self.silhouette_radio.setToolTip(
            self.tr("Apply threshold to discovered pattern"))
        self.depth_radio = QRadioButton(self.tr("Depth"))
        self.depth_radio.setToolTip(
            self.tr("Estimate 3D depth using optical flow"))
        self.shaded_radio = QRadioButton(self.tr("Shaded"))
        self.shaded_radio.setToolTip(
            self.tr("Combine pattern and depth information"))

        self.silhouette_radio.clicked.connect(self.process)
        self.pattern_radio.clicked.connect(self.process)
        self.depth_radio.clicked.connect(self.process)
        self.shaded_radio.clicked.connect(self.process)

        top_layout = QHBoxLayout()
        top_layout.addWidget(QLabel(self.tr("Mode:")))
        top_layout.addWidget(self.pattern_radio)
        top_layout.addWidget(self.silhouette_radio)
        top_layout.addWidget(self.depth_radio)
        top_layout.addWidget(self.shaded_radio)
        top_layout.addStretch()
        main_layout = QVBoxLayout()
        main_layout.addLayout(top_layout)
        main_layout.addWidget(self.viewer)
        self.setLayout(main_layout)

    def process(self):
        if self.pattern_radio.isChecked():
            output = self.pattern
        elif self.silhouette_radio.isChecked():
            output = self.silhouette
        elif self.depth_radio.isChecked():
            output = self.depth
        elif self.shaded_radio.isChecked():
            output = self.shaded
        else:
            return
        self.viewer.update_original(output)
Ejemplo n.º 4
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(f"Evidence: {basename}"))
        self.reference_viewer = ImageViewer(np.full_like(self.evidence, 127),
                                            None, self.tr("Reference"))

        self.table_widget = QTableWidget(20, 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("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("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("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("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("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("icons/high.svg"), "(+" + "\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('icons/high.svg'), '(+' + u'\u221e' + ')'))
        # self.table_widget.item(6, 0).setToolTip(self.tr('PSNR with Blocking Effect Factor.'))
        self.table_widget.setItem(6, 0, QTableWidgetItem(self.tr("SSIM")))
        self.table_widget.setItem(
            6, 2, QTableWidgetItem(QIcon("icons/high.svg"), "(1)"))
        self.table_widget.item(6, 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(7, 0, QTableWidgetItem(self.tr("MS-SSIM")))
        self.table_widget.setItem(
            7, 2, QTableWidgetItem(QIcon("icons/high.svg"), "(1)"))
        self.table_widget.item(7, 0).setToolTip(
            self.tr("Multiscale version of SSIM."))
        self.table_widget.setItem(8, 0, QTableWidgetItem(self.tr("RASE")))
        self.table_widget.setItem(
            8, 2, QTableWidgetItem(QIcon("icons/low.svg"), "(0)"))
        self.table_widget.item(8, 0).setToolTip(
            self.tr("Relative average spectral error"))
        self.table_widget.setItem(9, 0, QTableWidgetItem(self.tr("SCC")))
        self.table_widget.setItem(
            9, 2, QTableWidgetItem(QIcon("icons/high.svg"), "(1)"))
        self.table_widget.item(9, 0).setToolTip(
            self.tr("Spatial Correlation Coefficient"))
        self.table_widget.setItem(10, 0, QTableWidgetItem(self.tr("UQI")))
        self.table_widget.setItem(
            10, 2, QTableWidgetItem(QIcon("icons/high.svg"), "(1)"))
        self.table_widget.item(10, 0).setToolTip(
            self.tr("Universal Image Quality Index"))
        self.table_widget.setItem(11, 0, QTableWidgetItem(self.tr("VIF-P")))
        self.table_widget.setItem(
            11, 2, QTableWidgetItem(QIcon("icons/high.svg"), "(1)"))
        self.table_widget.item(11, 0).setToolTip(
            self.tr("Pixel-based Visual Information Fidelity"))
        self.table_widget.setItem(12, 0,
                                  QTableWidgetItem(self.tr("SSIMulacra")))
        self.table_widget.setItem(
            12, 2, QTableWidgetItem(QIcon("icons/low.svg"), "(0)"))
        self.table_widget.item(12, 0).setToolTip(
            self.tr("Structural SIMilarity Unveiling Local "
                    "And Compression Related Artifacts"))
        self.table_widget.setItem(13, 0,
                                  QTableWidgetItem(self.tr("Butteraugli")))
        self.table_widget.setItem(
            13, 2, QTableWidgetItem(QIcon("icons/low.svg"), "(0)"))
        self.table_widget.item(13, 0).setToolTip(
            self.tr("Estimate psychovisual error"))
        self.table_widget.setItem(14, 0,
                                  QTableWidgetItem(self.tr("Correlation")))
        self.table_widget.setItem(
            14, 2, QTableWidgetItem(QIcon("icons/high.svg"), "(1)"))
        self.table_widget.item(14,
                               0).setToolTip(self.tr("Histogram correlation"))
        self.table_widget.setItem(15, 0,
                                  QTableWidgetItem(self.tr("Chi-Square")))
        self.table_widget.setItem(
            15, 2, QTableWidgetItem(QIcon("icons/low.svg"), "(0)"))
        self.table_widget.item(15,
                               0).setToolTip(self.tr("Histogram Chi-Square"))
        self.table_widget.setItem(16, 0,
                                  QTableWidgetItem(self.tr("Chi-Square 2")))
        self.table_widget.setItem(
            16, 2, QTableWidgetItem(QIcon("icons/low.svg"), "(0)"))
        self.table_widget.item(16,
                               0).setToolTip(self.tr("Alternative Chi-Square"))
        self.table_widget.setItem(17, 0,
                                  QTableWidgetItem(self.tr("Intersection")))
        self.table_widget.setItem(
            17, 2,
            QTableWidgetItem(QIcon("icons/high.svg"), "(+" + "\u221e" + ")"))
        self.table_widget.item(17,
                               0).setToolTip(self.tr("Histogram intersection"))
        self.table_widget.setItem(18, 0,
                                  QTableWidgetItem(self.tr("Hellinger")))
        self.table_widget.setItem(
            18, 2, QTableWidgetItem(QIcon("icons/low.svg"), "(0)"))
        self.table_widget.item(18, 0).setToolTip(
            self.tr("Histogram Hellinger distance"))
        self.table_widget.setItem(19, 0,
                                  QTableWidgetItem(self.tr("Divergence")))
        self.table_widget.setItem(
            19, 2, QTableWidgetItem(QIcon("icons/low.svg"), "(0)"))
        self.table_widget.item(19, 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(f"Reference: {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(f"{rmse:.2f}"))
        self.table_widget.setItem(1, 1, QTableWidgetItem(f"{sam:.4f}"))
        self.table_widget.setItem(2, 1, QTableWidgetItem(f"{ergas:.2f}"))
        self.table_widget.setItem(3, 1, QTableWidgetItem(f"{mb:.4f}"))
        self.table_widget.setItem(4, 1, QTableWidgetItem(f"{pfe:.2f}"))
        if psnr > 0:
            self.table_widget.setItem(5, 1, QTableWidgetItem(f"{psnr:.2f} dB"))
        else:
            self.table_widget.setItem(5, 1,
                                      QTableWidgetItem("+" + "\u221e" + " dB"))
        # self.table_widget.setItem(6, 1, QTableWidgetItem('{:.2f}'.format(psnrb)))
        self.table_widget.setItem(6, 1, QTableWidgetItem(f"{ssim:.4f}"))
        self.table_widget.setItem(7, 1, QTableWidgetItem(f"{mssim:.4f}"))
        self.table_widget.setItem(8, 1, QTableWidgetItem(f"{rase:.2f}"))
        self.table_widget.setItem(9, 1, QTableWidgetItem(f"{scc:.4f}"))
        self.table_widget.setItem(10, 1, QTableWidgetItem(f"{uqi:.4f}"))
        self.table_widget.setItem(11, 1, QTableWidgetItem(f"{vifp:.4f}"))
        self.table_widget.setItem(12, 1, QTableWidgetItem(f"{ssimul:.4f}"))
        self.table_widget.setItem(13, 1, QTableWidgetItem(f"{butter:.2f}"))
        self.table_widget.setItem(14, 1,
                                  QTableWidgetItem(f"{correlation:.2f}"))
        self.table_widget.setItem(15, 1, QTableWidgetItem(f"{chi_square:.2f}"))
        self.table_widget.setItem(16, 1,
                                  QTableWidgetItem(f"{chi_square2:.2f}"))
        self.table_widget.setItem(17, 1,
                                  QTableWidgetItem(f"{intersection:.2f}"))
        self.table_widget.setItem(18, 1, QTableWidgetItem(f"{hellinger:.2f}"))
        self.table_widget.setItem(19, 1, QTableWidgetItem(f"{divergence:.2f}"))
        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)
                if p.returncode == 0:
                    value = float(p.stdout)
                    heatmap = cv.imread(filename3, cv.IMREAD_COLOR)
                else:
                    raise ValueError
                return value, heatmap
        except (FileNotFoundError, ValueError) as _:
            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)
                if p.returncode == 0:
                    value = float(p.stdout)
                else:
                    raise ValueError
                return value
        except (FileNotFoundError, ValueError) as _:
            return -1
Ejemplo n.º 5
0
class ComparisonWidget(ToolWidget):
    def __init__(self, filename, image, parent=None):
        super(ComparisonWidget, self).__init__(parent)

        load_button = QPushButton(self.tr('Load reference...'))
        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.ssim_radio = QRadioButton(self.tr('SSIM Map'))
        self.butter_radio = QRadioButton(self.tr('Butteraugli'))
        self.gray_check = QCheckBox(self.tr('Grayscale'))
        self.equal_check = QCheckBox(self.tr('Equalized'))
        self.last_radio = self.normal_radio
        self.metric_button = QPushButton(self.tr('Compute'))

        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(15, 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('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('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('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('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('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('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('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('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('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('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('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('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('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('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('icons/low.svg'), '(0)'))
        self.table_widget.item(14, 0).setToolTip(
            self.tr('Estimate psychovisual error'))

        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.stopped = False

        self.comp_label.setEnabled(False)
        self.normal_radio.setEnabled(False)
        self.diff_radio.setEnabled(False)
        self.ssim_radio.setEnabled(False)
        self.butter_radio.setEnabled(False)
        self.gray_check.setEnabled(False)
        self.equal_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.diff_radio.clicked.connect(self.change)
        self.butter_radio.clicked.connect(self.change)
        self.gray_check.stateChanged.connect(self.change)
        self.equal_check.stateChanged.connect(self.change)
        self.ssim_radio.clicked.connect(self.change)
        self.evidence_viewer.viewChanged.connect(
            self.reference_viewer.change_view)
        self.reference_viewer.viewChanged.connect(
            self.evidence_viewer.change_view)
        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.diff_radio)
        top_layout.addWidget(self.ssim_radio)
        top_layout.addWidget(self.butter_radio)
        top_layout.addWidget(self.gray_check)
        top_layout.addWidget(self.equal_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 = normalize_mat(
            cv.absdiff(self.evidence, self.reference))

        self.comp_label.setEnabled(True)
        self.normal_radio.setEnabled(True)
        self.diff_radio.setEnabled(True)
        self.ssim_radio.setEnabled(False)
        self.butter_radio.setEnabled(False)
        self.gray_check.setEnabled(True)
        self.equal_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.equal_check.setEnabled(False)
            self.last_radio = self.normal_radio
        elif self.diff_radio.isChecked():
            result = self.difference
            self.gray_check.setEnabled(True)
            self.equal_check.setEnabled(True)
            self.last_radio = self.diff_radio
        elif self.ssim_radio.isChecked():
            result = self.ssim_map
            self.gray_check.setEnabled(False)
            self.equal_check.setEnabled(True)
            self.last_radio = self.ssim_radio
        elif self.butter_radio.isChecked():
            result = self.butter_map
            self.gray_check.setEnabled(True)
            self.equal_check.setEnabled(False)
            self.last_radio = self.butter_radio
        else:
            self.last_radio.setChecked(True)
            return
        if self.equal_check.isChecked():
            result = equalize_image(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
        psnrb = sewar.psnrb(img1, img2)
        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)

        self.table_widget.setItem(0, 1,
                                  QTableWidgetItem('{:.4f}'.format(rmse)))
        self.table_widget.setItem(1, 1, QTableWidgetItem('{:.4f}'.format(sam)))
        self.table_widget.setItem(2, 1,
                                  QTableWidgetItem('{:.4f}'.format(ergas)))
        self.table_widget.setItem(3, 1, QTableWidgetItem('{:.4f}'.format(mb)))
        self.table_widget.setItem(4, 1, QTableWidgetItem('{:.4f}'.format(pfe)))
        if psnr > 0:
            self.table_widget.setItem(
                5, 1, QTableWidgetItem('{:.4f} dB'.format(psnr)))
        else:
            self.table_widget.setItem(
                5, 1, QTableWidgetItem('+' + u'\u221e' + ' dB'))
        self.table_widget.setItem(6, 1,
                                  QTableWidgetItem('{:.4f}'.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('{:.4f}'.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('{:.4f}'.format(butter)))
        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 - normalize_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
Ejemplo n.º 6
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)