Exemplo n.º 1
0
    def compute_map(self):
        if self.map is None:
            start = time()
            self.map_button.setText(
                self.tr('Computing heatmap, please wait...'))
            modify_font(self.map_button, bold=False, italic=True)
            QCoreApplication.processEvents()

            mapp, valid, range0, range1, imgsize, other = noiseprint_blind_post(
                self.noise, self.image0)
            if mapp is None:
                QMessageBox.critical(self, self.tr('Error'),
                                     self.tr('Too many invalid blocks!'))
                return
            self.map = cv.applyColorMap(
                genMappUint8(mapp, valid, range0, range1, imgsize),
                cv.COLORMAP_JET)
            self.map_viewer.update_processed(self.map)
            elapsed = time() - start

            self.map_button.setText(
                self.tr('Heatmap computed ({:.1f} s)'.format(elapsed)))
            modify_font(self.map_button, bold=False, italic=False)
            self.map_button.setCheckable(True)
        self.map_button.setChecked(True)
        self.noise_viewer.viewChanged.connect(self.map_viewer.changeView)
        self.map_viewer.viewChanged.connect(self.noise_viewer.changeView)
Exemplo n.º 2
0
 def __init__(self, filename, parent=None):
     super(LocationWidget, self).__init__(parent)
     self.temp_dir = QTemporaryDir()
     if self.temp_dir.isValid():
         with exiftool.ExifTool(exiftool_exe()) as et:
             temp_file = os.path.join(self.temp_dir.path(), "geo.html")
             metadata = et.get_metadata(filename)
             try:
                 lat = metadata["Composite:GPSLatitude"]
                 long = metadata["Composite:GPSLongitude"]
             except KeyError:
                 label = QLabel(self.tr("Geolocation data not found!"))
                 modify_font(label, bold=True)
                 label.setStyleSheet("color: #FF0000")
                 label.setAlignment(Qt.AlignCenter)
                 layout = QVBoxLayout()
                 layout.addWidget(label)
                 self.setLayout(layout)
                 return
             html = '<iframe src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d2948.532014673314!2d{}!3d{}!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x0%3A0x0!2zNDLCsDIxJzA5LjAiTiA3McKwMDUnMjguMiJX!5e0!3m2!1sit!2sit!4v1590074026898!5m2!1sit!2sit" width="600" height="450" frameborder="0" style="border:0;" allowfullscreen="" aria-hidden="false" tabindex="0"></iframe>'.format(
                 long, lat)
             with open(temp_file, "w") as file:
                 file.write(html)
             web_view = QWebEngineView()
             web_view.load(QUrl("file://" + temp_file))
             layout = QVBoxLayout()
             layout.addWidget(web_view)
             self.setLayout(layout)
Exemplo n.º 3
0
    def __init__(self, image, parent=None):
        super(SplicingWidget, self).__init__(parent)

        self.image = image
        self.image0 = cv.cvtColor(self.image, cv.COLOR_BGR2GRAY).astype(
            np.float32) / 255
        self.noise = self.map = None

        self.noise_button = QPushButton(self.tr('(1/2) Estimate noise'))
        modify_font(self.noise_button, bold=True)
        gray = np.full_like(self.image, 127)
        self.noise_viewer = ImageViewer(self.image,
                                        gray,
                                        self.tr('Estimated noise print'),
                                        export=True)
        self.map_button = QPushButton(self.tr('(2/2) Compute heatmap'))
        modify_font(self.map_button, bold=True)
        self.map_button.setEnabled(False)
        self.map_viewer = ImageViewer(self.image, gray,
                                      self.tr('Splicing probability heatmap'))

        self.noise_button.clicked.connect(self.estimate_noise)
        self.noise_button.toggled.connect(self.estimate_noise)
        self.map_button.clicked.connect(self.compute_map)
        self.map_button.toggled.connect(self.compute_map)

        main_layout = QGridLayout()
        main_layout.addWidget(self.noise_viewer, 0, 0)
        main_layout.addWidget(self.noise_button, 1, 0)
        main_layout.addWidget(self.map_viewer, 0, 1)
        main_layout.addWidget(self.map_button, 1, 1)
        self.setLayout(main_layout)
Exemplo n.º 4
0
 def process(self):
     if self.prob is None:
         return
     mask = self.var < self.variance_spin.value()
     if self.filter_check.isChecked():
         prob = cv.medianBlur(self.prob.astype(np.float32), 3)
     else:
         prob = self.prob.astype(np.float32)
     if self.showprob_check.isChecked():
         output = np.repeat(prob[:, :, np.newaxis], 3, axis=2)
         output[mask] = 0
     else:
         thr = self.threshold_spin.value()
         output = np.zeros((prob.shape[0], prob.shape[1], 3))
         blue, green, red = cv.split(output)
         blue[mask] = 1
         green[prob < thr] = 1
         green[mask] = 0
         red[prob >= thr] = 1
         red[mask] = 0
         output = cv.merge((blue, green, red))
     output = cv.convertScaleAbs(output, None, 255)
     output = cv.resize(output, None, None, self.block, self.block,
                        cv.INTER_LINEAR)
     self.viewer.update_processed(
         np.copy(output[:self.image.shape[0], :self.image.shape[1]]))
     avgprob = cv.mean(prob, 1 - mask.astype(np.uint8))[0] * 100
     self.avgprob_label.setText(self.tr(
         'Average = {:.2f}%'.format(avgprob)))
     modify_font(self.avgprob_label, italic=False, bold=True)
     self.process_button.setEnabled(False)
Exemplo n.º 5
0
 def show_error(self, message):
     error_label = QLabel(message)
     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)
Exemplo n.º 6
0
 def search(self, pattern, row, col, direction):
     nocase = not self.case_button.isChecked()
     word = self.word_button.isChecked()
     regex = self.regex_button.isChecked()
     matches = 0
     index = 0
     if direction > 0:
         row_range = range(self.table_widget.rowCount() - 1, -1, -1)
         col_range = range(self.table_widget.columnCount() - 1, -1, -1)
     else:
         row_range = range(self.table_widget.rowCount())
         col_range = range(self.table_widget.columnCount())
     for i in row_range:
         for j in col_range:
             item = self.table_widget.item(i, j)
             if item is not None:
                 text = item.text()
                 if regex:
                     match = QRegularExpression(pattern).match(
                         text).hasMatch()
                 else:
                     if nocase:
                         text = text.lower()
                         pattern = pattern.lower()
                     if word:
                         match = text == pattern
                     else:
                         match = pattern in text
                 if match and pattern:
                     self.table_widget.item(i, j).setBackground(Qt.yellow)
                     if (direction > 0 and
                         (i > row or i == row and j > col)) or (
                             direction < 0 and
                             (i < row or i == row and j < col)):
                         self.table_widget.setCurrentCell(i, j)
                         index = matches
                     matches += 1
                 else:
                     self.table_widget.item(i,
                                            j).setBackground(Qt.transparent)
     if pattern:
         self.matches_label.setVisible(True)
         if matches > 0:
             match = matches - index if direction > 0 else index + 1
             self.matches_label.setText(
                 self.tr(f"match #{match}/{matches}"))
             self.matches_label.setStyleSheet("color: #000000")
             modify_font(self.matches_label, bold=True)
         else:
             self.matches_label.setText(self.tr("not found!"))
             self.matches_label.setStyleSheet("color: #FF0000")
             modify_font(self.matches_label, italic=True)
     else:
         self.matches_label.setText("")
Exemplo n.º 7
0
 def create_table(matrix):
     table_widget = QTableWidget(DCT_SIZE, DCT_SIZE)
     hsv = np.array([[[0, 192, 255]]])
     maximum = clip_value(np.max(matrix) - 1, minv=1)
     for i in range(DCT_SIZE):
         for j in range(DCT_SIZE):
             value = matrix[i, j]
             item = QTableWidgetItem(str(value))
             item.setTextAlignment(Qt.AlignCenter)
             hsv[0, 0, 0] = 64 - 64 * ((value - 1) / maximum)
             rgb = cv.cvtColor(hsv.astype(np.uint8), cv.COLOR_HSV2RGB)
             item.setBackgroundColor(
                 QColor(rgb[0, 0, 0], rgb[0, 0, 1], rgb[0, 0, 2]))
             table_widget.setItem(i, j, item)
     table_widget.resizeRowsToContents()
     table_widget.resizeColumnsToContents()
     table_widget.setEditTriggers(QAbstractItemView.NoEditTriggers)
     table_widget.setSelectionMode(QAbstractItemView.SingleSelection)
     modify_font(table_widget, mono=True)
     return table_widget
Exemplo n.º 8
0
    def estimate_noise(self):
        if self.noise is None:
            start = time()
            self.noise_button.setText(
                self.tr('Estimating noise, please wait...'))
            modify_font(self.noise_button, bold=False, italic=True)
            QCoreApplication.processEvents()

            qf = estimate_qf(self.image)
            self.noise = genNoiseprint(self.image0, qf, model_name='net')
            vmin, vmax, _, _ = cv.minMaxLoc(self.noise[34:-34, 34:-34])
            self.noise_viewer.update_processed(
                norm_mat(self.noise.clip(vmin, vmax), to_bgr=True))
            elapsed = time() - start

            self.noise_button.setText(
                self.tr('Noise estimated ({:.1f} s)'.format(elapsed)))
            modify_font(self.noise_button, bold=False, italic=False)
            self.map_button.setEnabled(True)
            self.noise_button.setCheckable(True)
        self.noise_button.setChecked(True)
Exemplo n.º 9
0
    def __init__(self, interval, step, ticks, reset=0, suffix='', parent=None):
        super(ParamSlider, self).__init__(parent)

        self.slider = QSlider(Qt.Horizontal)
        self.slider.setRange(interval[0], interval[1])
        self.slider.setTickPosition(QSlider.TicksBelow)
        self.slider.setTickInterval((interval[1] - interval[0] + 1) / ticks)
        self.slider.setSingleStep(1)
        # self.slider.setPageStep(step)
        self.slider.setPageStep(1)
        self.slider.setValue(reset)
        self.slider.mouseDoubleClickEvent = self.double_click
        self.label = QLabel()
        modify_font(self.label, bold=True)
        self.suffix = suffix
        self.reset = reset
        self.sync(reset)
        self.slider.valueChanged.connect(self.sync)

        layout = QHBoxLayout()
        layout.addWidget(self.slider)
        layout.addWidget(self.label)
        self.setLayout(layout)
Exemplo n.º 10
0
    def __init__(self, parent=None):
        super(ToolTree, self).__init__(parent)
        group_names = []
        tool_names = []
        tool_infos = []
        tool_progress = [
        ]  # 0 = da fare, 1 = iniziato, 2 = funzionante, 3 = completo

        # [0]
        group_names.append(self.tr('[General]'))
        tool_names.append([
            self.tr('Original Image'),
            self.tr('File Digest'),
            self.tr('Hex Editor'),
            self.tr('Reverse Search')
        ])
        tool_infos.append([
            self.tr(
                'Display the unaltered reference image for visual inspection'),
            self.
            tr('Retrieve file information and compute many hashes and ballistics'
               ),
            self.
            tr('Open an external hexadecimal editor to show and edit raw bytes'
               ),
            self.tr(
                'Use online search services to find visually similar images')
        ])
        tool_progress.extend([3, 3, 2, 2])

        # [1]
        group_names.append(self.tr('[Metadata]'))
        tool_names.append([
            self.tr('Header Structure'),
            self.tr('Metadata Extraction'),
            self.tr('Thumbnail Analysis'),
            self.tr('Geolocation data')
        ])
        tool_infos.append([
            self.
            tr('Dump the physical EXIF structure and display an interactive view'
               ),
            self.
            tr('Scan through file metadata and gather all available information'
               ),
            self.tr(
                'Extract optional embedded thumbnail and compare with original'
            ),
            self.
            tr('Retrieve optional geo-location data and show it on a world map'
               )
        ])
        tool_progress.extend([3, 3, 3, 2])

        # [2]
        group_names.append(self.tr('[Inspection]'))
        tool_names.append([
            self.tr('Enhancing Magnifier'),
            self.tr('Reference Comparison'),
            self.tr('Global Adjustments'),
            self.tr('Fourier Transform')
        ])
        tool_infos.append([
            self.
            tr('Use a loupe with visual enhancement for better identifying forgeries'
               ),
            self.
            tr('Open a synchronized double view to compare two different pictures'
               ),
            self.
            tr('Apply standard adjustments (contrast, brightness, hue, saturation)'
               ),
            self.
            tr('Compute amplitude and phase components of the 2D Fourier Transform'
               )
        ])
        tool_progress.extend([2, 2, 2, 3])

        # [3]
        group_names.append(self.tr('[JPEG]'))
        tool_names.append([
            self.tr('Quality Estimation'),
            self.tr('Error Level Analysis'),
            self.tr('Multiple Compression'),
            self.tr('DCT Dimples Map')
        ])
        tool_infos.append([
            self.
            tr('Extract quantization tables and estimate last saved JPEG quality'
               ),
            self.
            tr('Show pixel-level difference against different compression levels'
               ),
            self.
            tr('Use residuals to detect multiple compressions at different levels'
               ),
            self.
            tr('Analyze periodic quantization artifacts to detect manipulations'
               )
        ])
        tool_progress.extend([2, 3, 1, 0])

        # [4]
        group_names.append(self.tr('[Colors]'))
        tool_names.append([
            self.tr('RGB/HSV Plots'),
            self.tr('PCA Projection'),
            self.tr('Pixel Statistics'),
            self.tr('Space Conversion')
        ])
        tool_infos.append([
            self.tr(
                'Display interactive 2D and 3D plots of RGB and HSV pixel data'
            ),
            self.
            tr('Use color PCA to project RGB values onto reduced vector spaces'
               ),
            self.tr(
                'Compute minimum/maximum/average RGB values for every pixel'),
            self.
            tr('Convert color channels into RGB/HSV/YCbCr/Lab/Luv/CMYK spaces')
        ])
        tool_progress.extend([0, 2, 3, 2])

        # [5]
        group_names.append(self.tr('[Tonality]'))
        tool_names.append([
            self.tr('Luminance Gradient'),
            self.tr('Echo Edge Filter'),
            self.tr('Correlation Plot'),
            self.tr('Wavelet Threshold')
        ])
        tool_infos.append([
            self.
            tr('Analyze horizontal/vertical brightness variations across the image'
               ),
            self.
            tr('Use derivative filters to reveal artificial out-of-focus zones'
               ),
            self.tr(
                'Exploit spatial correlation patterns among neighboring pixels'
            ),
            self.
            tr('Reconstruct image with different wavelet coefficient thresholds'
               )
        ])
        tool_progress.extend([2, 3, 0, 0])

        # [6]
        group_names.append(self.tr('[Noise]'))
        tool_names.append([
            self.tr('Noise Estimation'),
            self.tr('Min/Max Deviation'),
            self.tr('Image Bit Planes'),
            self.tr('Frequency Separation')
        ])
        tool_infos.append([
            self.tr(
                'Estimate and visualize gaussian noise components of the image'
            ),
            self.
            tr('Highlight pixels deviating from block-based min/max statistics'
               ),
            self.
            tr('Visualize bit planes values to find different noise patterns'),
            self.
            tr('Estimate high/low frequency components of the luminance channel'
               )
        ])
        tool_progress.extend([3, 2, 3, 0])

        # [7]
        group_names.append(self.tr('[Tampering]'))
        tool_names.append([
            self.tr('Contrast Enhancement'),
            self.tr('Region Cloning'),
            self.tr('Image Resampling'),
            self.tr('Composite Splicing')
        ])
        tool_infos.append([
            self.tr(
                'Analyze color distribuions to detect contrast enhancements'),
            self.
            tr('Use feature descriptors for copy/rotate clone area detection'),
            self.
            tr('Analyze 2D pixel interpolation for detecting resampling traces'
               ),
            self.tr(
                'Exploit DCT statistics for automatic splicing zone detection')
        ])
        tool_progress.extend([0, 0, 0, 0])

        count = 0
        for i, group in enumerate(group_names):
            group_item = QTreeWidgetItem()
            group_item.setText(0, group)
            font = group_item.font(0)
            font.setBold(True)
            group_item.setFont(0, font)
            group_item.setData(0, Qt.UserRole, False)
            group_item.setIcon(0, QIcon('icons/{}.svg'.format(i)))
            for j, tool in enumerate(tool_names[i]):
                tool_item = QTreeWidgetItem(group_item)
                tool_item.setText(0, tool)
                tool_item.setData(0, Qt.UserRole, True)
                tool_item.setData(0, Qt.UserRole + 1, i)
                tool_item.setData(0, Qt.UserRole + 2, j)
                tool_item.setToolTip(0, tool_infos[i][j])
                if tool_progress[count] == 0:
                    modify_font(tool_item, italic=True)
                count += 1
            self.addTopLevelItem(group_item)
        self.expandAll()
        self.setColumnCount(1)
        self.header().setVisible(False)
        self.setMaximumWidth(300)
        self.version = '{:.2f}a'.format(sum(tool_progress) / (count * 3))
Exemplo n.º 11
0
 def forward_changed(self, rect, scaling, horizontal, vertical):
     self.zoom_label.setText('{:.2f}%'.format(scaling * 100))
     modify_font(self.zoom_label, scaling == 1)
     self.viewChanged.emit(rect, scaling, horizontal, vertical)
Exemplo n.º 12
0
    def __init__(self,
                 original,
                 processed,
                 title=None,
                 parent=None,
                 export=False):
        super(ImageViewer, self).__init__(parent)
        if original is None and processed is None:
            raise ValueError(
                self.tr('ImageViewer.__init__: Empty image received'))
        if original is None and processed is not None:
            original = processed
        self.original = original
        self.processed = processed
        if self.original is not None and self.processed is None:
            self.view = DynamicView(self.original)
        else:
            self.view = DynamicView(self.processed)

        # view_label = QLabel(self.tr('View:'))
        self.original_radio = QRadioButton(self.tr('Original'))
        self.original_radio.setToolTip(
            self.tr('Show the original image for comparison'))
        self.process_radio = QRadioButton(self.tr('Processed'))
        self.process_radio.setToolTip(
            self.tr('Show result of the current processing'))
        self.zoom_label = QLabel()
        full_button = QToolButton()
        full_button.setText(self.tr('100%'))
        fit_button = QToolButton()
        fit_button.setText(self.tr('Fit'))
        height, width, _ = self.original.shape
        size_label = QLabel(self.tr('[{}x{} px]'.format(height, width)))
        export_button = QToolButton()
        export_button.setToolTip(self.tr('Export current image to PNG'))
        # export_button.setText(self.tr('Export...'))
        export_button.setIcon(QIcon('icons/export.svg'))

        tool_layout = QHBoxLayout()
        tool_layout.addWidget(QLabel(self.tr('Zoom:')))
        tool_layout.addWidget(self.zoom_label)
        # tool_layout.addWidget(full_button)
        # tool_layout.addWidget(fit_button)
        tool_layout.addStretch()
        if processed is not None:
            # tool_layout.addWidget(view_label)
            tool_layout.addWidget(self.original_radio)
            tool_layout.addWidget(self.process_radio)
            tool_layout.addStretch()
        tool_layout.addWidget(size_label)
        if export or processed is not None:
            tool_layout.addWidget(export_button)
        if processed is not None:
            self.original_radio.setChecked(False)
            self.process_radio.setChecked(True)
            self.toggle_mode(False)

        vert_layout = QVBoxLayout()
        if title is not None:
            self.title_label = QLabel(title)
            modify_font(self.title_label, bold=True)
            self.title_label.setAlignment(Qt.AlignCenter)
            vert_layout.addWidget(self.title_label)
        else:
            self.title_label = None
        vert_layout.addWidget(self.view)
        vert_layout.addLayout(tool_layout)
        self.setLayout(vert_layout)

        self.original_radio.toggled.connect(self.toggle_mode)
        fit_button.clicked.connect(self.view.zoom_fit)
        full_button.clicked.connect(self.view.zoom_full)
        export_button.clicked.connect(self.export_image)
        self.view.viewChanged.connect(self.forward_changed)
Exemplo n.º 13
0
    def __init__(self, parent=None):
        super(ToolTree, self).__init__(parent)
        group_names = []
        tool_names = []
        tool_infos = []
        tool_progress = [
        ]  # 0 = da fare, 1 = debug, 2 = funzionante, 3 = completo

        # [0]
        group_names.append(self.tr("[General]"))
        tool_names.append([
            self.tr("Original Image"),
            self.tr("File Digest"),
            self.tr("Hex Editor"),
            self.tr("Similar Search")
        ])
        tool_infos.append([
            self.tr(
                "Display the unaltered reference image for visual inspection"),
            self.
            tr("Retrieve physical file information, crypto and perceptual hashes"
               ),
            self.
            tr("Open an external hexadecimal editor to show and edit raw bytes"
               ),
            self.tr(
                "Browse online search services to find visually similar images"
            ),
        ])
        tool_progress.extend([3, 3, 2, 2])

        # [1]
        group_names.append(self.tr("[Metadata]"))
        tool_names.append([
            self.tr("Header Structure"),
            self.tr("EXIF Full Dump"),
            self.tr("Thumbnail Analysis"),
            self.tr("Geolocation data"),
        ])
        tool_infos.append([
            self.
            tr("Dump the file header structure and display an interactive view"
               ),
            self.
            tr("Scan through file metadata and gather all available information"
               ),
            self.tr(
                "Extract optional embedded thumbnail and compare with original"
            ),
            self.tr(
                "Retrieve optional geolocation data and show it on a world map"
            ),
        ])
        tool_progress.extend([3, 3, 3, 2])

        # [2]
        group_names.append(self.tr("[Inspection]"))
        tool_names.append([
            self.tr("Enhancing Magnifier"),
            self.tr("Channel Histogram"),
            self.tr("Global Adjustments"),
            self.tr("Reference Comparison"),
        ])
        tool_infos.append([
            self.
            tr("Magnifying glass with enhancements for better identifying forgeries"
               ),
            self.
            tr("Display single color channels or RGB composite interactive histogram"
               ),
            self.
            tr("Apply standard image adjustments (brightness, hue, saturation, ...)"
               ),
            self.
            tr("Open a synchronized double view for comparison with another picture"
               ),
        ])
        tool_progress.extend([3, 3, 3, 3])

        # [3]
        group_names.append(self.tr("[Detail]"))
        tool_names.append([
            self.tr("Luminance Gradient"),
            self.tr("Echo Edge Filter"),
            self.tr("Wavelet Threshold"),
            self.tr("Frequency Split"),
        ])
        tool_infos.append([
            self.
            tr("Analyze horizontal/vertical brightness variations across the image"
               ),
            self.
            tr("Use derivative filters to reveal artificial out-of-focus regions"
               ),
            self.
            tr("Reconstruct image with different wavelet coefficient thresholds"
               ),
            self.tr(
                "Divide image luminance into high and low frequency components"
            ),
        ])
        tool_progress.extend([3, 3, 3, 3])

        # [4]
        group_names.append(self.tr("[Colors]"))
        tool_names.append([
            self.tr("RGB/HSV Plots"),
            self.tr("Space Conversion"),
            self.tr("PCA Projection"),
            self.tr("Pixel Statistics"),
        ])
        tool_infos.append([
            self.
            tr("Display interactive 2D and 3D plots of RGB and HSV pixel values"
               ),
            self.
            tr("Convert RGB channels into HSV/YCbCr/Lab/Luv/CMYK/Gray spaces"),
            self.tr(
                "Use color PCA to project pixel onto most salient components"),
            self.tr(
                "Compute minimum/maximum/average RGB values for every pixel"),
        ])
        tool_progress.extend([3, 3, 3, 3])

        # [5]
        group_names.append(self.tr("[Noise]"))
        tool_names.append([
            self.tr("Noise Separation"),
            self.tr("Min/Max Deviation"),
            self.tr("Bit Plane Values"),
            self.tr("PRNU Identification"),
        ])
        tool_infos.append([
            self.tr(
                "Estimate and extract different kind of image noise components"
            ),
            self.
            tr("Highlight pixels deviating from block-based min/max statistics"
               ),
            self.
            tr("Show individual bit planes to find inconsistent noise patterns"
               ),
            self.
            tr("Exploit sensor pattern noise introduced by different cameras"),
        ])
        tool_progress.extend([3, 3, 3, 0])

        # [6]
        group_names.append(self.tr("[JPEG]"))
        tool_names.append([
            self.tr("Quality Estimation"),
            self.tr("Error Level Analysis"),
            self.tr("Multiple Compression"),
            self.tr("JPEG Ghost Maps"),
        ])
        tool_infos.append([
            self.
            tr("Extract quantization tables and estimate last saved JPEG quality"
               ),
            self.tr(
                "Show pixel-wise differences against a fixed compression level"
            ),
            self.tr(
                "Use a machine learning model to detect multiple compression"),
            self.
            tr("Highlight traces of different compressions in difference images"
               ),
        ])
        tool_progress.extend([3, 3, 0, 0])

        # [7]
        group_names.append(self.tr("[Tampering]"))
        tool_names.append([
            self.tr("Contrast Enhancement"),
            self.tr("Copy-Move Forgery"),
            self.tr("Composite Splicing"),
            self.tr("Image Resampling"),
        ])
        tool_infos.append([
            self.tr(
                "Analyze color distributions to detect contrast enhancements"),
            self.tr(
                "Use invariant feature descriptors to detect cloned regions"),
            self.
            tr("Exploit DCT statistics for automatic splicing zone detection"),
            self.
            tr("Estimate 2D pixel interpolation for detecting resampling traces"
               ),
        ])
        tool_progress.extend([3, 2, 3, 0])

        # [8]
        group_names.append(self.tr("[Various]"))
        tool_names.append([
            self.tr("Median Filtering"),
            self.tr("Illuminant Map"),
            self.tr("Dead/Hot Pixels"),
            self.tr("Stereogram Decoder"),
        ])
        tool_infos.append([
            self.tr(
                "Detect nonlinear processing traces left by median filtering"),
            self.tr(
                "Estimate scene local light direction on estimated 3D surfaces"
            ),
            self.tr(
                "Detect and fix dead/hot pixels caused by sensor imperfections"
            ),
            self.tr(
                "Decode 3D images concealed inside crossed-eye autostereograms"
            ),
        ])
        tool_progress.extend([2, 0, 0, 3])

        count = 0
        for i, group in enumerate(group_names):
            group_item = QTreeWidgetItem()
            group_item.setText(0, group)
            font = group_item.font(0)
            font.setBold(True)
            group_item.setFont(0, font)
            group_item.setData(0, Qt.UserRole, False)
            group_item.setIcon(0, QIcon(f"icons/{i}.svg"))
            for j, tool in enumerate(tool_names[i]):
                tool_item = QTreeWidgetItem(group_item)
                tool_item.setText(0, tool)
                tool_item.setData(0, Qt.UserRole, True)
                tool_item.setData(0, Qt.UserRole + 1, i)
                tool_item.setData(0, Qt.UserRole + 2, j)
                tool_item.setToolTip(0, tool_infos[i][j])
                if tool_progress[count] == 0:
                    modify_font(tool_item, italic=True)
                count += 1
            self.addTopLevelItem(group_item)
        self.expandAll()
        self.setColumnCount(1)
        self.header().setVisible(False)
        self.setMaximumWidth(300)
        self.version = f"{sum(tool_progress) / 100:.2f}a"
Exemplo n.º 14
0
    def __init__(self, image, parent=None):
        super(PcaWidget, self).__init__(parent)

        self.comp_combo = QComboBox()
        self.comp_combo.addItems([self.tr('#{}'.format(i + 1)) for i in range(3)])

        self.distvect_radio = QRadioButton(self.tr('Vector Distance'))
        self.cross_radio = QRadioButton(self.tr('Cross Correlation'))
        self.distvect_radio.setChecked(True)
        self.last_radio = self.distvect_radio

        self.image = image
        self.components = []
        rows, cols, dims = self.image.shape
        bgr = np.reshape(self.image, (rows * cols, dims)).astype(np.float32)
        m, eigen_vec, eigen_val = cv.PCACompute2(bgr, np.array([]))
        p = self.image.astype(np.float32) - m
        for v in eigen_vec:
            c = np.cross(p, v)
            d = np.linalg.norm(c, axis=2) / np.linalg.norm(v)
            distance = normalize_mat(d, to_bgr=True)
            cross = cv.merge([normalize_mat(x) for x in cv.split(c)])
            self.components.extend([distance, cross])

        table_data = [[m[0, 2], m[0, 1], m[0, 0]],
                      [eigen_vec[0, 2], eigen_vec[0, 1], eigen_vec[0, 0]],
                      [eigen_vec[1, 2], eigen_vec[1, 1], eigen_vec[1, 0]],
                      [eigen_vec[2, 2], eigen_vec[2, 1], eigen_vec[2, 0]],
                      [eigen_val[2, 0], eigen_val[1, 0], eigen_val[0, 0]]]
        table_widget = QTableWidget(5, 4)
        table_widget.setHorizontalHeaderLabels([
            self.tr('Element'), self.tr('Red'), self.tr('Green'), self.tr('Blue')])
        table_widget.setItem(0, 0, QTableWidgetItem(self.tr('Mean color')))
        table_widget.setItem(1, 0, QTableWidgetItem(self.tr('Eigen vect 1')))
        table_widget.setItem(2, 0, QTableWidgetItem(self.tr('Eigen vect 2')))
        table_widget.setItem(3, 0, QTableWidgetItem(self.tr('Eigen vect 3')))
        table_widget.setItem(4, 0, QTableWidgetItem(self.tr('Eigen values')))
        for i in range(len(table_data)):
            modify_font(table_widget.item(i, 0), bold=True)
            for j in range(len(table_data[i])):
                table_widget.setItem(i, j + 1, QTableWidgetItem(str(table_data[i][j])))
        # item = QTableWidgetItem()
        # item.setBackgroundColor(QColor(m[0, 2], m[0, 1], m[0, 0]))
        # table_widget.setItem(0, 4, item)
        # table_widget.resizeRowsToContents()
        # table_widget.resizeColumnsToContents()
        table_widget.setEditTriggers(QAbstractItemView.NoEditTriggers)
        table_widget.setSelectionMode(QAbstractItemView.SingleSelection)
        table_widget.setMaximumHeight(190)

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

        self.comp_combo.currentIndexChanged.connect(self.process)
        self.distvect_radio.clicked.connect(self.process)
        self.cross_radio.clicked.connect(self.process)

        top_layout = QHBoxLayout()
        top_layout.addWidget(QLabel(self.tr('Component:')))
        top_layout.addWidget(self.comp_combo)
        top_layout.addWidget(QLabel(self.tr('Projection:')))
        top_layout.addWidget(self.distvect_radio)
        top_layout.addWidget(self.cross_radio)
        top_layout.addStretch()
        bottom_layout = QHBoxLayout()
        bottom_layout.addWidget(table_widget)

        main_layout = QVBoxLayout()
        main_layout.addLayout(top_layout)
        main_layout.addWidget(self.viewer)
        main_layout.addLayout(bottom_layout)
        self.setLayout(main_layout)
Exemplo n.º 15
0
    def __init__(self, image, parent=None):
        super(CloningWidget, self).__init__(parent)

        self.detector_combo = QComboBox()
        self.detector_combo.addItems(
            [self.tr('BRISK'),
             self.tr('ORB'),
             self.tr('AKAZE')])
        self.detector_combo.setCurrentIndex(0)
        self.detector_combo.setToolTip(
            self.tr('Algorithm used for localization and description'))
        self.response_spin = QSpinBox()
        self.response_spin.setRange(0, 100)
        self.response_spin.setSuffix(self.tr('%'))
        self.response_spin.setValue(90)
        self.response_spin.setToolTip(
            self.tr('Maximum keypoint response to perform matching'))
        self.matching_spin = QSpinBox()
        self.matching_spin.setRange(1, 100)
        self.matching_spin.setSuffix(self.tr('%'))
        self.matching_spin.setValue(20)
        self.matching_spin.setToolTip(
            self.tr('Maximum metric difference to accept matching'))
        self.distance_spin = QSpinBox()
        self.distance_spin.setRange(1, 100)
        self.distance_spin.setSuffix(self.tr('%'))
        self.distance_spin.setValue(15)
        self.distance_spin.setToolTip(
            self.tr('Maximum distance between matches in the same cluster'))
        self.cluster_spin = QSpinBox()
        self.cluster_spin.setRange(1, 20)
        self.cluster_spin.setValue(5)
        self.cluster_spin.setToolTip(
            self.tr('Minimum number of keypoints to create a new cluster'))
        self.kpts_check = QCheckBox(self.tr('Show keypoints'))
        self.kpts_check.setToolTip(self.tr('Show keypoint coverage'))
        self.nolines_check = QCheckBox(self.tr('Hide lines'))
        self.nolines_check.setToolTip(self.tr('Disable match line drawing'))
        self.process_button = QToolButton()
        self.process_button.setText(self.tr('Process'))
        self.process_button.setToolTip(self.tr('Perform automatic detection'))
        modify_font(self.process_button, bold=True)
        self.status_label = QLabel()
        self.mask_label = QLabel()
        self.mask_button = QToolButton()
        self.mask_button.setText(self.tr('Load mask...'))
        self.mask_button.setToolTip(
            self.tr('Load an image to be used as mask'))
        self.onoff_button = QToolButton()
        self.onoff_button.setText(self.tr('OFF'))
        self.onoff_button.setCheckable(True)
        self.onoff_button.setToolTip(self.tr('Toggle keypoint detection mask'))

        self.image = image
        self.viewer = ImageViewer(self.image, self.image)
        self.gray = cv.cvtColor(self.image, cv.COLOR_BGR2GRAY)
        self.total = self.kpts = self.desc = self.matches = self.clusters = self.mask = None
        self.canceled = False

        self.detector_combo.currentIndexChanged.connect(self.update_detector)
        self.response_spin.valueChanged.connect(self.update_detector)
        self.matching_spin.valueChanged.connect(self.update_matching)
        self.distance_spin.valueChanged.connect(self.update_cluster)
        self.cluster_spin.valueChanged.connect(self.update_cluster)
        self.nolines_check.stateChanged.connect(self.process)
        self.kpts_check.stateChanged.connect(self.process)
        self.process_button.clicked.connect(self.process)
        self.mask_button.clicked.connect(self.load_mask)
        self.onoff_button.toggled.connect(self.toggle_mask)
        self.onoff_button.setEnabled(False)

        top_layout = QHBoxLayout()
        top_layout.addWidget(QLabel(self.tr('Detector:')))
        top_layout.addWidget(self.detector_combo)
        top_layout.addWidget(QLabel(self.tr('Response:')))
        top_layout.addWidget(self.response_spin)
        top_layout.addWidget(QLabel(self.tr('Matching:')))
        top_layout.addWidget(self.matching_spin)
        top_layout.addWidget(QLabel(self.tr('Distance:')))
        top_layout.addWidget(self.distance_spin)
        top_layout.addWidget(QLabel(self.tr('Cluster:')))
        top_layout.addWidget(self.cluster_spin)
        top_layout.addWidget(self.nolines_check)
        top_layout.addWidget(self.kpts_check)
        top_layout.addStretch()

        bottom_layout = QHBoxLayout()
        bottom_layout.addWidget(self.process_button)
        bottom_layout.addWidget(self.status_label)
        bottom_layout.addStretch()
        bottom_layout.addWidget(self.mask_button)
        bottom_layout.addWidget(self.onoff_button)

        main_layout = QVBoxLayout()
        main_layout.addLayout(top_layout)
        main_layout.addLayout(bottom_layout)
        main_layout.addWidget(self.viewer)
        self.setLayout(main_layout)
Exemplo n.º 16
0
 def cancel(self):
     self.canceled = True
     self.status_label.setText(self.tr('Processing interrupted!'))
     modify_font(self.status_label, bold=False, italic=False)
Exemplo n.º 17
0
    def __init__(self, filename, image, parent=None):
        super(QualityWidget, self).__init__(parent)

        x = np.arange(1, 101)
        y = loss_curve(image)
        tail = 5
        qm = np.argmin(y[:-tail]) + 1
        if qm == 100 - tail:
            qm = 100

        figure = Figure()
        canvas = FigureCanvas(figure)
        axes = canvas.figure.subplots()
        axes.plot(x, y * 100, label="compression loss")
        axes.fill_between(x, y * 100, alpha=0.2)
        axes.axvline(qm,
                     linestyle=":",
                     color="k",
                     label=f"min error (q = {qm})")
        xt = axes.get_xticks()
        xt = np.append(xt, 1)
        axes.set_xticks(xt)
        axes.set_xlim([1, 100])
        axes.set_ylim([0, 100])
        axes.set_xlabel(self.tr("JPEG quality (%)"))
        axes.set_ylabel(self.tr("average error (%)"))
        axes.grid(True, which="both")
        axes.legend(loc="upper center")
        axes.figure.canvas.draw()
        figure.set_tight_layout(True)

        main_layout = QVBoxLayout()
        main_layout.addWidget(canvas)

        MRK = b"\xFF"
        SOI = b"\xD8"
        DQT = b"\xDB"
        # DHT = b'\xC4'
        MSK = b"\x0F"
        PAD = b"\x00"

        MAX_TABLES = 2
        LEN_OFFSET = 2
        LUMA_IDX = 0
        CHROMA_IDX = 1

        luma = np.zeros((DCT_SIZE, DCT_SIZE), dtype=int)
        chroma = np.zeros((DCT_SIZE, DCT_SIZE), dtype=int)
        temp_file = QTemporaryFile()
        try:
            if temp_file.open():
                copyfile(filename, temp_file.fileName())
                subprocess.run(
                    [
                        exiftool_exe(), "-all=", "-overwrite_original",
                        temp_file.fileName()
                    ],
                    stdout=subprocess.DEVNULL,
                    stderr=subprocess.DEVNULL,
                )
                found = False
                with open(temp_file.fileName(), "rb") as file:
                    first = file.read(1)
                    if first not in [MRK, SOI]:
                        raise ValueError(self.tr("File is not a JPEG image!"))
                    while True:
                        if not self.find_next(file, [MRK, DQT, PAD]):
                            break
                        length = file.read(1)[0] - LEN_OFFSET
                        if length <= 0 or length % (TABLE_SIZE + 1) != 0:
                            continue
                        while length > 0:
                            mode = file.read(1)
                            if not mode:
                                break
                            index = mode[0] & MSK[0]
                            if index >= MAX_TABLES:
                                break
                            length -= 1
                            for k in range(TABLE_SIZE):
                                b = file.read(1)[0]
                                if not b:
                                    break
                                length -= 1
                                i, j = ZIG_ZAG[k]
                                if index == LUMA_IDX:
                                    luma[i, j] = b
                                elif index == CHROMA_IDX:
                                    chroma[i, j] = b
                            else:
                                found = True
            if not found:
                raise ValueError(self.tr("Unable to find JPEG tables!"))

            levels = [(1 - (np.mean(t.ravel()[1:]) - 1) / 254) * 100
                      for t in [luma, chroma]]
            distance = np.zeros(101)
            for qm in range(101):
                lu, ch = cv.split(get_tables(qm))
                lu_diff = np.mean(cv.absdiff(luma, lu))
                ch_diff = np.mean(cv.absdiff(chroma, ch))
                distance[qm] = (lu_diff + 2 * ch_diff) / 3
            closest = np.argmin(distance)
            deviation = distance[closest]
            if deviation == 0:
                quality = closest
                message = "(standard tables)"
            else:
                quality = int(np.round(closest - deviation))
                message = f"(deviation from standard tables = {deviation:.4f})"
            if quality == 0:
                quality = 1
            quality_label = QLabel(
                self.tr(
                    f"[JPEG FORMAT] Last saved quality: {quality}% {message}"))
            modify_font(quality_label, bold=True)

            luma_label = QLabel(
                self.
                tr(f"Luminance Quantization Table (level = {levels[0]:.2f}%)\n"
                   ))
            luma_label.setAlignment(Qt.AlignCenter)
            modify_font(luma_label, underline=True)
            luma_table = self.create_table(luma)
            luma_table.setFixedSize(420, 190)

            chroma_label = QLabel(
                self.
                tr(f"Chrominance Quantization Table (level = {levels[1]:.2f}%)\n"
                   ))
            chroma_label.setAlignment(Qt.AlignCenter)
            modify_font(chroma_label, underline=True)
            chroma_table = self.create_table(chroma)
            chroma_table.setFixedSize(420, 190)

            table_layout = QGridLayout()
            table_layout.addWidget(luma_label, 0, 0)
            table_layout.addWidget(luma_table, 1, 0)
            table_layout.addWidget(chroma_label, 0, 1)
            table_layout.addWidget(chroma_table, 1, 1)
            table_layout.addWidget(quality_label, 2, 0, 1, 2)
            main_layout.addLayout(table_layout)

        except ValueError:
            modelfile = "models/jpeg_qf.mdl"
            try:
                model = load(modelfile)
                limit = model.best_ntree_limit if hasattr(
                    model, "best_ntree_limit") else None
                # f = self.get_features(image)
                # p = model.predict_proba(f, ntree_limit=limit)[0, 0]
                qp = model.predict(np.reshape(y, (1, len(y))),
                                   ntree_limit=limit)[0]
                # if p > 0.5:
                #     p = 2 * (p - 0.5) * 100
                #     output = self.tr('Uncompressed image (p = {:.2f}%)'.format(p))
                # else:
                #     p = (1 - 2 * p) * 100
                #     output = self.tr('Compressed image (p = {:.2f}%) ---> Estimated JPEG quality = {}%'.format(p, qm))
                message = self.tr(
                    f"[LOSSLESS FORMAT] Estimated last saved quality = {qp:.1f}%{'' if qp <= 99 else ' (uncompressed)'}"
                )
                if qp == 100:
                    message += " (uncompressed)"
                prob_label = QLabel(message)
                modify_font(prob_label, bold=True)
                main_layout.addWidget(prob_label)
            except FileNotFoundError:
                QMessageBox.critical(
                    self, self.tr("Error"),
                    self.tr(f'Model not found ("{modelfile}")!'))

        main_layout.addStretch()
        self.setLayout(main_layout)
Exemplo n.º 18
0
    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)
Exemplo n.º 19
0
 def cancel(self):
     self.canceled = True
     self.total = self.kpts = self.desc = self.matches = self.clusters = None
     self.status_label.setText(self.tr('Processing interrupted!'))
     modify_font(self.status_label, bold=False, italic=False)
Exemplo n.º 20
0
    def __init__(self,
                 table,
                 headers,
                 bold=True,
                 mono=True,
                 tooltips=None,
                 align=False,
                 search=True,
                 parent=None):
        super(TableWidget, self).__init__(parent)

        self.table_widget = QTableWidget(len(table), len(table[0]))
        for i, row in enumerate(table):
            for j, item in enumerate(row):
                if item is not None:
                    self.table_widget.setItem(i, j,
                                              QTableWidgetItem(str(item)))
                    if tooltips is not None:
                        self.table_widget.setToolTip(tooltips[i][j])
                    modify_font(self.table_widget.item(i, j),
                                bold=bold and j == 0,
                                mono=mono)
                    if align:
                        self.table_widget.item(i, j).setTextAlignment(
                            Qt.AlignRight)

        self.table_headers = headers
        self.table_widget.setHorizontalHeaderLabels(self.table_headers)
        self.table_widget.setSelectionMode(QAbstractItemView.SingleSelection)
        self.table_widget.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.table_widget.resizeColumnsToContents()
        self.table_widget.setAlternatingRowColors(True)
        self.table_widget.itemDoubleClicked.connect(self.copy)

        search_layout = QHBoxLayout()
        search_layout.addWidget(QLabel(self.tr('Search:')))
        self.search_edit = QLineEdit()
        self.search_edit.textChanged.connect(self.start)
        self.search_edit.returnPressed.connect(self.next)
        search_layout.addWidget(self.search_edit)

        clear_button = QToolButton()
        clear_button.setIcon(QIcon('icons/clear.svg'))
        clear_button.setShortcut(QKeySequence.DeleteCompleteLine)
        clear_button.setToolTip(self.tr('Clear pattern'))
        clear_button.clicked.connect(self.search_edit.clear)
        search_layout.addWidget(clear_button)

        self.case_button = QToolButton()
        self.case_button.setText(self.tr('Aa'))
        self.case_button.setCheckable(True)
        self.case_button.toggled.connect(self.start)
        self.case_button.setToolTip(self.tr('Case sensitive'))
        search_layout.addWidget(self.case_button)

        self.word_button = QToolButton()
        self.word_button.setText(self.tr('W'))
        self.word_button.setCheckable(True)
        self.word_button.toggled.connect(self.start)
        self.word_button.setToolTip(self.tr('Whole words'))
        search_layout.addWidget(self.word_button)

        self.regex_button = QToolButton()
        self.regex_button.setText(self.tr('.*'))
        self.regex_button.setCheckable(True)
        self.regex_button.toggled.connect(self.start)
        self.regex_button.setToolTip(self.tr('Regular expression'))
        search_layout.addWidget(self.regex_button)

        prev_button = QToolButton()
        prev_button.setIcon(QIcon('icons/up.svg'))
        prev_button.setShortcut(QKeySequence.FindPrevious)
        prev_button.clicked.connect(self.previous)
        prev_button.setToolTip(self.tr('Previous occurence'))
        search_layout.addWidget(prev_button)

        next_button = QToolButton()
        next_button.setIcon(QIcon('icons/down.svg'))
        next_button.setShortcut(QKeySequence.FindNext)
        next_button.clicked.connect(self.next)
        next_button.setToolTip(self.tr('Next occurence'))
        search_layout.addWidget(next_button)

        self.matches_label = QLabel()
        search_layout.addWidget(self.matches_label)
        search_layout.addStretch()

        export_button = QToolButton()
        export_button.setText(self.tr('Export...'))
        export_button.clicked.connect(self.export)
        search_layout.addWidget(export_button)

        main_layout = QVBoxLayout()
        main_layout.addWidget(self.table_widget)
        if search:
            main_layout.addLayout(search_layout)
        self.setLayout(main_layout)
Exemplo n.º 21
0
    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)
Exemplo n.º 22
0
    def process(self):
        start = time()
        self.canceled = False
        self.status_label.setText(self.tr('Processing, please wait...'))
        algorithm = self.detector_combo.currentIndex()
        response = 100 - self.response_spin.value()
        matching = self.matching_spin.value() / 100 * 255
        distance = self.distance_spin.value() / 100
        cluster = self.cluster_spin.value()
        modify_font(self.status_label, bold=False, italic=True)
        QCoreApplication.processEvents()

        if self.kpts is None:
            if algorithm == 0:
                detector = cv.BRISK_create()
            elif algorithm == 1:
                detector = cv.ORB_create()
            elif algorithm == 2:
                detector = cv.AKAZE_create()
            else:
                return
            mask = self.mask if self.onoff_button.isChecked() else None
            self.kpts, self.desc = detector.detectAndCompute(self.gray, mask)
            self.total = len(self.kpts)
            responses = np.array([k.response for k in self.kpts])
            strongest = (cv.normalize(responses, None, 0, 100, cv.NORM_MINMAX)
                         >= response).flatten()
            self.kpts = list(compress(self.kpts, strongest))
            if len(self.kpts) > 30000:
                QMessageBox.warning(
                    self, self.tr('Warning'),
                    self.
                    tr('Too many keypoints found ({}), please reduce response value'
                       .format(self.total)))
                self.kpts = self.desc = None
                self.total = 0
                self.status_label.setText('')
                return
            self.desc = self.desc[strongest]

        if self.matches is None:
            matcher = cv.BFMatcher_create(cv.NORM_HAMMING, True)
            self.matches = matcher.radiusMatch(self.desc, self.desc, matching)
            if self.matches is None:
                self.status_label.setText(
                    self.tr('No keypoint match found with current settings'))
                modify_font(self.status_label, italic=False, bold=True)
                return
            self.matches = [
                item for sublist in self.matches for item in sublist
            ]
            self.matches = [
                m for m in self.matches if m.queryIdx != m.trainIdx
            ]

        if not self.matches:
            self.clusters = []
        elif self.clusters is None:
            self.clusters = []
            min_dist = distance * np.min(self.gray.shape) / 2
            kpts_a = np.array([p.pt for p in self.kpts])
            ds = np.linalg.norm([
                kpts_a[m.queryIdx] - kpts_a[m.trainIdx] for m in self.matches
            ],
                                axis=1)
            self.matches = [
                m for i, m in enumerate(self.matches) if ds[i] > min_dist
            ]

            total = len(self.matches)
            progress = QProgressDialog(self.tr('Clustering matches...'),
                                       self.tr('Cancel'), 0, total, self)
            progress.canceled.connect(self.cancel)
            progress.setWindowModality(Qt.WindowModal)
            for i in range(total):
                match0 = self.matches[i]
                d0 = ds[i]
                query0 = match0.queryIdx
                train0 = match0.trainIdx
                group = [match0]

                for j in range(i + 1, total):
                    match1 = self.matches[j]
                    query1 = match1.queryIdx
                    train1 = match1.trainIdx
                    if query1 == train0 and train1 == query0:
                        continue
                    d1 = ds[j]
                    if np.abs(d0 - d1) > min_dist:
                        continue

                    a0 = np.array(self.kpts[query0].pt)
                    b0 = np.array(self.kpts[train0].pt)
                    a1 = np.array(self.kpts[query1].pt)
                    b1 = np.array(self.kpts[train1].pt)

                    aa = np.linalg.norm(a0 - a1)
                    bb = np.linalg.norm(b0 - b1)
                    ab = np.linalg.norm(a0 - b1)
                    ba = np.linalg.norm(b0 - a1)

                    if not (0 < aa < min_dist and 0 < bb < min_dist
                            or 0 < ab < min_dist and 0 < ba < min_dist):
                        continue
                    for g in group:
                        if g.queryIdx == train1 and g.trainIdx == query1:
                            break
                    else:
                        group.append(match1)

                if len(group) >= cluster:
                    self.clusters.append(group)
                progress.setValue(i)
                if self.canceled:
                    self.update_detector()
                    return
            progress.close()

        output = np.copy(self.image)
        hsv = np.zeros((1, 1, 3))
        nolines = self.nolines_check.isChecked()
        show_kpts = self.kpts_check.isChecked()

        if show_kpts:
            for kpt in self.kpts:
                cv.circle(output, (int(kpt.pt[0]), int(kpt.pt[1])), 2,
                          (250, 227, 72))

        angles = []
        for c in self.clusters:
            for m in c:
                ka = self.kpts[m.queryIdx]
                pa = tuple(map(int, ka.pt))
                sa = int(np.round(ka.size))
                kb = self.kpts[m.trainIdx]
                pb = tuple(map(int, kb.pt))
                sb = int(np.round(kb.size))
                angle = np.arctan2(pb[1] - pa[1], pb[0] - pa[0])
                if angle < 0:
                    angle += np.pi
                angles.append(angle)
                hsv[0, 0, 0] = angle / np.pi * 180
                hsv[0, 0, 1] = 255
                hsv[0, 0, 2] = m.distance / matching * 255
                rgb = cv.cvtColor(hsv.astype(np.uint8), cv.COLOR_HSV2BGR)
                rgb = tuple([int(x) for x in rgb[0, 0]])
                cv.circle(output, pa, sa, rgb, 1, cv.LINE_AA)
                cv.circle(output, pb, sb, rgb, 1, cv.LINE_AA)
                if not nolines:
                    cv.line(output, pa, pb, rgb, 1, cv.LINE_AA)

        regions = 0
        if angles:
            angles = np.reshape(np.array(angles, dtype=np.float32),
                                (len(angles), 1))
            if np.std(angles) < 0.1:
                regions = 1
            else:
                criteria = (cv.TERM_CRITERIA_EPS + cv.TERM_CRITERIA_MAX_ITER,
                            10, 1.0)
                attempts = 10
                flags = cv.KMEANS_PP_CENTERS
                compact = [
                    cv.kmeans(angles, k, None, criteria, attempts, flags)[0]
                    for k in range(1, 11)
                ]
                compact = cv.normalize(np.array(compact), None, 0, 1,
                                       cv.NORM_MINMAX)
                regions = np.argmax(compact < 0.005) + 1
        self.viewer.update_processed(output)
        self.process_button.setEnabled(False)
        modify_font(self.status_label, italic=False, bold=True)
        self.status_label.setText(
            self.
            tr('Keypoints: {} --> Filtered: {} --> Matches: {} --> Clusters: {} --> Regions: {}'
               .format(self.total, len(self.kpts), len(self.matches),
                       len(self.clusters), regions)))
        self.info_message.emit(
            self.tr('Copy-Move Forgery = {}'.format(elapsed_time(start))))
Exemplo n.º 23
0
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)
        QApplication.setApplicationName('Sherloq')
        QApplication.setOrganizationName('Guido Bartoli')
        QApplication.setOrganizationDomain('www.guidobartoli.com')
        QApplication.setApplicationVersion(ToolTree().version)
        QApplication.setWindowIcon(QIcon('icons/sherloq_white.png'))
        self.setWindowTitle('{} {}'.format(QApplication.applicationName(),
                                           QApplication.applicationVersion()))
        self.mdi_area = QMdiArea()
        self.setCentralWidget(self.mdi_area)
        self.filename = None
        self.image = None
        modify_font(self.statusBar(), bold=True)

        tree_dock = QDockWidget(self.tr('TOOLS'), self)
        tree_dock.setObjectName('tree_dock')
        tree_dock.setAllowedAreas(Qt.LeftDockWidgetArea
                                  | Qt.RightDockWidgetArea)
        self.addDockWidget(Qt.LeftDockWidgetArea, tree_dock)
        self.tree_widget = ToolTree()
        self.tree_widget.setObjectName('tree_widget')
        self.tree_widget.itemDoubleClicked.connect(self.open_tool)
        tree_dock.setWidget(self.tree_widget)

        tools_action = tree_dock.toggleViewAction()
        tools_action.setText(self.tr('Show tools'))
        tools_action.setToolTip(self.tr('Toggle toolset visibility'))
        tools_action.setShortcut(QKeySequence(Qt.Key_Tab))
        tools_action.setObjectName('tools_action')
        tools_action.setIcon(QIcon('icons/tools.svg'))

        help_action = QAction(self.tr('Show help'), self)
        help_action.setToolTip(self.tr('Toggle online help'))
        help_action.setShortcut(QKeySequence.HelpContents)
        help_action.setObjectName('help_action')
        help_action.setIcon(QIcon('icons/help.svg'))
        help_action.setCheckable(True)
        help_action.setEnabled(False)

        load_action = QAction(self.tr('&Load image...'), self)
        load_action.setToolTip(self.tr('Load an image to analyze'))
        load_action.setShortcut(QKeySequence.Open)
        load_action.triggered.connect(self.load_file)
        load_action.setObjectName('load_action')
        load_action.setIcon(QIcon('icons/load.svg'))

        quit_action = QAction(self.tr('&Quit'), self)
        quit_action.setToolTip(self.tr('Exit from Sherloq'))
        quit_action.setShortcut(QKeySequence.Quit)
        quit_action.triggered.connect(self.close)
        quit_action.setObjectName('quit_action')
        quit_action.setIcon(QIcon('icons/quit.svg'))

        tabbed_action = QAction(self.tr('&Tabbed'), self)
        tabbed_action.setToolTip(self.tr('Toggle tabbed view for window area'))
        tabbed_action.setShortcut(QKeySequence(Qt.Key_F10))
        tabbed_action.setCheckable(True)
        tabbed_action.triggered.connect(self.toggle_view)
        tabbed_action.setObjectName('tabbed_action')
        tabbed_action.setIcon(QIcon('icons/tabbed.svg'))

        prev_action = QAction(self.tr('&Previous'), self)
        prev_action.setToolTip(self.tr('Select the previous tool window'))
        prev_action.setShortcut(QKeySequence.PreviousChild)
        prev_action.triggered.connect(self.mdi_area.activatePreviousSubWindow)
        prev_action.setObjectName('prev_action')
        prev_action.setIcon(QIcon('icons/previous.svg'))

        next_action = QAction(self.tr('&Next'), self)
        next_action.setToolTip(self.tr('Select the next tool window'))
        next_action.setShortcut(QKeySequence.NextChild)
        next_action.triggered.connect(self.mdi_area.activateNextSubWindow)
        next_action.setObjectName('next_action')
        next_action.setIcon(QIcon('icons/next.svg'))

        tile_action = QAction(self.tr('&Tile'), self)
        tile_action.setToolTip(
            self.tr('Arrange windows into non-overlapping views'))
        tile_action.setShortcut(QKeySequence(Qt.Key_F11))
        tile_action.triggered.connect(self.mdi_area.tileSubWindows)
        tile_action.setObjectName('tile_action')
        tile_action.setIcon(QIcon('icons/tile.svg'))

        cascade_action = QAction(self.tr('&Cascade'), self)
        cascade_action.setToolTip(
            self.tr('Arrange windows into overlapping views'))
        cascade_action.setShortcut(QKeySequence(Qt.Key_F12))
        cascade_action.triggered.connect(self.mdi_area.cascadeSubWindows)
        cascade_action.setObjectName('cascade_action')
        cascade_action.setIcon(QIcon('icons/cascade.svg'))

        close_action = QAction(self.tr('Close &All'), self)
        close_action.setToolTip(self.tr('Close all open tool windows'))
        close_action.setShortcut(QKeySequence(Qt.CTRL + Qt.SHIFT + Qt.Key_W))
        close_action.triggered.connect(self.mdi_area.closeAllSubWindows)
        close_action.setObjectName('close_action')
        close_action.setIcon(QIcon('icons/close.svg'))

        self.full_action = QAction(self.tr('Full screen'), self)
        self.full_action.setToolTip(self.tr('Switch to full screen mode'))
        self.full_action.setShortcut(QKeySequence.FullScreen)
        self.full_action.triggered.connect(self.change_view)
        self.full_action.setObjectName('full_action')
        self.full_action.setIcon(QIcon('icons/full.svg'))

        self.normal_action = QAction(self.tr('Normal view'), self)
        self.normal_action.setToolTip(self.tr('Back to normal view mode'))
        self.normal_action.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_F12))
        self.normal_action.triggered.connect(self.change_view)
        self.normal_action.setObjectName('normal_action')
        self.normal_action.setIcon(QIcon('icons/normal.svg'))

        about_action = QAction(self.tr('&About...'), self)
        about_action.setToolTip(self.tr('Information about this program'))
        about_action.triggered.connect(self.show_about)
        about_action.setObjectName('about_action')
        about_action.setIcon(QIcon('icons/sherloq_alpha.png'))

        about_qt_action = QAction(self.tr('About &Qt'), self)
        about_qt_action.setToolTip(
            self.tr('Information about the Qt Framework'))
        about_qt_action.triggered.connect(QApplication.aboutQt)
        about_qt_action.setIcon(QIcon('icons/Qt.svg'))

        file_menu = self.menuBar().addMenu(self.tr('&File'))
        file_menu.addAction(load_action)
        file_menu.addSeparator()
        self.recent_actions = [None] * self.max_recent
        for i in range(len(self.recent_actions)):
            self.recent_actions[i] = QAction(self)
            self.recent_actions[i].setVisible(False)
            self.recent_actions[i].triggered.connect(self.open_recent)
            file_menu.addAction(self.recent_actions[i])
        file_menu.addSeparator()
        file_menu.addAction(quit_action)

        view_menu = self.menuBar().addMenu(self.tr('&View'))
        view_menu.addAction(tools_action)
        view_menu.addAction(help_action)
        view_menu.addSeparator()
        view_menu.addAction(self.full_action)
        view_menu.addAction(self.normal_action)

        window_menu = self.menuBar().addMenu(self.tr('&Window'))
        window_menu.addAction(prev_action)
        window_menu.addAction(next_action)
        window_menu.addSeparator()
        window_menu.addAction(tile_action)
        window_menu.addAction(cascade_action)
        window_menu.addAction(tabbed_action)
        window_menu.addSeparator()
        window_menu.addAction(close_action)

        help_menu = self.menuBar().addMenu(self.tr('&Help'))
        help_menu.addAction(help_action)
        help_menu.addSeparator()
        help_menu.addAction(about_action)
        help_menu.addAction(about_qt_action)

        main_toolbar = self.addToolBar(self.tr('&Toolbar'))
        main_toolbar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
        main_toolbar.addAction(load_action)
        main_toolbar.addSeparator()
        main_toolbar.addAction(tools_action)
        main_toolbar.addAction(help_action)
        main_toolbar.addSeparator()
        main_toolbar.addAction(prev_action)
        main_toolbar.addAction(next_action)
        main_toolbar.addSeparator()
        main_toolbar.addAction(tile_action)
        main_toolbar.addAction(cascade_action)
        main_toolbar.addAction(tabbed_action)
        main_toolbar.addAction(close_action)
        # main_toolbar.addSeparator()
        # main_toolbar.addAction(self.normal_action)
        # main_toolbar.addAction(self.full_action)
        main_toolbar.setAllowedAreas(Qt.TopToolBarArea | Qt.BottomToolBarArea)
        main_toolbar.setObjectName('main_toolbar')

        settings = QSettings()
        settings.beginGroup('main_window')
        self.restoreGeometry(settings.value('geometry'))
        self.restoreState(settings.value('state'))
        self.recent_files = settings.value('recent_files')
        if self.recent_files is None:
            self.recent_files = []
        elif not isinstance(self.recent_files, list):
            self.recent_files = [self.recent_files]
        self.update_recent()
        settings.endGroup()

        prev_action.setEnabled(False)
        next_action.setEnabled(False)
        tile_action.setEnabled(False)
        cascade_action.setEnabled(False)
        close_action.setEnabled(False)
        tabbed_action.setEnabled(False)
        self.tree_widget.setEnabled(False)
        self.showNormal()
        self.normal_action.setEnabled(False)
        self.show_message(self.tr('Ready'))
Exemplo n.º 24
0
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)
        QApplication.setApplicationName("Sherloq")
        QApplication.setOrganizationName("Guido Bartoli")
        QApplication.setOrganizationDomain("www.guidobartoli.com")
        QApplication.setApplicationVersion(ToolTree().version)
        QApplication.setWindowIcon(QIcon("icons/sherloq_white.png"))
        self.setWindowTitle(
            f"{QApplication.applicationName()} {QApplication.applicationVersion()}"
        )
        self.mdi_area = QMdiArea()
        self.setCentralWidget(self.mdi_area)
        self.filename = None
        self.image = None
        modify_font(self.statusBar(), bold=True)

        tree_dock = QDockWidget(self.tr("TOOLS"), self)
        tree_dock.setObjectName("tree_dock")
        tree_dock.setAllowedAreas(Qt.LeftDockWidgetArea
                                  | Qt.RightDockWidgetArea)
        self.addDockWidget(Qt.LeftDockWidgetArea, tree_dock)
        self.tree_widget = ToolTree()
        self.tree_widget.setObjectName("tree_widget")
        self.tree_widget.itemDoubleClicked.connect(self.open_tool)
        tree_dock.setWidget(self.tree_widget)

        tools_action = tree_dock.toggleViewAction()
        tools_action.setText(self.tr("Show tools"))
        tools_action.setToolTip(self.tr("Toggle toolset visibility"))
        tools_action.setShortcut(QKeySequence(Qt.Key_Tab))
        tools_action.setObjectName("tools_action")
        tools_action.setIcon(QIcon("icons/tools.svg"))

        help_action = QAction(self.tr("Show help"), self)
        help_action.setToolTip(self.tr("Toggle online help"))
        help_action.setShortcut(QKeySequence.HelpContents)
        help_action.setObjectName("help_action")
        help_action.setIcon(QIcon("icons/help.svg"))
        help_action.setCheckable(True)
        help_action.setEnabled(False)

        load_action = QAction(self.tr("&Load image..."), self)
        load_action.setToolTip(self.tr("Load an image to analyze"))
        load_action.setShortcut(QKeySequence.Open)
        load_action.triggered.connect(self.load_file)
        load_action.setObjectName("load_action")
        load_action.setIcon(QIcon("icons/load.svg"))

        quit_action = QAction(self.tr("&Quit"), self)
        quit_action.setToolTip(self.tr("Exit from Sherloq"))
        quit_action.setShortcut(QKeySequence.Quit)
        quit_action.triggered.connect(self.close)
        quit_action.setObjectName("quit_action")
        quit_action.setIcon(QIcon("icons/quit.svg"))

        tabbed_action = QAction(self.tr("&Tabbed"), self)
        tabbed_action.setToolTip(self.tr("Toggle tabbed view for window area"))
        tabbed_action.setShortcut(QKeySequence(Qt.Key_F10))
        tabbed_action.setCheckable(True)
        tabbed_action.triggered.connect(self.toggle_view)
        tabbed_action.setObjectName("tabbed_action")
        tabbed_action.setIcon(QIcon("icons/tabbed.svg"))

        prev_action = QAction(self.tr("&Previous"), self)
        prev_action.setToolTip(self.tr("Select the previous tool window"))
        prev_action.setShortcut(QKeySequence.PreviousChild)
        prev_action.triggered.connect(self.mdi_area.activatePreviousSubWindow)
        prev_action.setObjectName("prev_action")
        prev_action.setIcon(QIcon("icons/previous.svg"))

        next_action = QAction(self.tr("&Next"), self)
        next_action.setToolTip(self.tr("Select the next tool window"))
        next_action.setShortcut(QKeySequence.NextChild)
        next_action.triggered.connect(self.mdi_area.activateNextSubWindow)
        next_action.setObjectName("next_action")
        next_action.setIcon(QIcon("icons/next.svg"))

        tile_action = QAction(self.tr("&Tile"), self)
        tile_action.setToolTip(
            self.tr("Arrange windows into non-overlapping views"))
        tile_action.setShortcut(QKeySequence(Qt.Key_F11))
        tile_action.triggered.connect(self.mdi_area.tileSubWindows)
        tile_action.setObjectName("tile_action")
        tile_action.setIcon(QIcon("icons/tile.svg"))

        cascade_action = QAction(self.tr("&Cascade"), self)
        cascade_action.setToolTip(
            self.tr("Arrange windows into overlapping views"))
        cascade_action.setShortcut(QKeySequence(Qt.Key_F12))
        cascade_action.triggered.connect(self.mdi_area.cascadeSubWindows)
        cascade_action.setObjectName("cascade_action")
        cascade_action.setIcon(QIcon("icons/cascade.svg"))

        close_action = QAction(self.tr("Close &All"), self)
        close_action.setToolTip(self.tr("Close all open tool windows"))
        close_action.setShortcut(QKeySequence(Qt.CTRL + Qt.SHIFT + Qt.Key_W))
        close_action.triggered.connect(self.mdi_area.closeAllSubWindows)
        close_action.setObjectName("close_action")
        close_action.setIcon(QIcon("icons/close.svg"))

        self.full_action = QAction(self.tr("Full screen"), self)
        self.full_action.setToolTip(self.tr("Switch to full screen mode"))
        self.full_action.setShortcut(QKeySequence.FullScreen)
        self.full_action.triggered.connect(self.change_view)
        self.full_action.setObjectName("full_action")
        self.full_action.setIcon(QIcon("icons/full.svg"))

        self.normal_action = QAction(self.tr("Normal view"), self)
        self.normal_action.setToolTip(self.tr("Back to normal view mode"))
        self.normal_action.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_F12))
        self.normal_action.triggered.connect(self.change_view)
        self.normal_action.setObjectName("normal_action")
        self.normal_action.setIcon(QIcon("icons/normal.svg"))

        about_action = QAction(self.tr("&About..."), self)
        about_action.setToolTip(self.tr("Information about this program"))
        about_action.triggered.connect(self.show_about)
        about_action.setObjectName("about_action")
        about_action.setIcon(QIcon("icons/sherloq_alpha.png"))

        about_qt_action = QAction(self.tr("About &Qt"), self)
        about_qt_action.setToolTip(
            self.tr("Information about the Qt Framework"))
        about_qt_action.triggered.connect(QApplication.aboutQt)
        about_qt_action.setIcon(QIcon("icons/Qt.svg"))

        file_menu = self.menuBar().addMenu(self.tr("&File"))
        file_menu.addAction(load_action)
        file_menu.addSeparator()
        self.recent_actions = [None] * self.max_recent
        for i in range(len(self.recent_actions)):
            self.recent_actions[i] = QAction(self)
            self.recent_actions[i].setVisible(False)
            self.recent_actions[i].triggered.connect(self.open_recent)
            file_menu.addAction(self.recent_actions[i])
        file_menu.addSeparator()
        file_menu.addAction(quit_action)

        view_menu = self.menuBar().addMenu(self.tr("&View"))
        view_menu.addAction(tools_action)
        view_menu.addAction(help_action)
        view_menu.addSeparator()
        view_menu.addAction(self.full_action)
        view_menu.addAction(self.normal_action)

        window_menu = self.menuBar().addMenu(self.tr("&Window"))
        window_menu.addAction(prev_action)
        window_menu.addAction(next_action)
        window_menu.addSeparator()
        window_menu.addAction(tile_action)
        window_menu.addAction(cascade_action)
        window_menu.addAction(tabbed_action)
        window_menu.addSeparator()
        window_menu.addAction(close_action)

        help_menu = self.menuBar().addMenu(self.tr("&Help"))
        help_menu.addAction(help_action)
        help_menu.addSeparator()
        help_menu.addAction(about_action)
        help_menu.addAction(about_qt_action)

        main_toolbar = self.addToolBar(self.tr("&Toolbar"))
        main_toolbar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
        main_toolbar.addAction(load_action)
        main_toolbar.addSeparator()
        main_toolbar.addAction(tools_action)
        main_toolbar.addAction(help_action)
        main_toolbar.addSeparator()
        main_toolbar.addAction(prev_action)
        main_toolbar.addAction(next_action)
        main_toolbar.addSeparator()
        main_toolbar.addAction(tile_action)
        main_toolbar.addAction(cascade_action)
        main_toolbar.addAction(tabbed_action)
        main_toolbar.addAction(close_action)
        # main_toolbar.addSeparator()
        # main_toolbar.addAction(self.normal_action)
        # main_toolbar.addAction(self.full_action)
        main_toolbar.setAllowedAreas(Qt.TopToolBarArea | Qt.BottomToolBarArea)
        main_toolbar.setObjectName("main_toolbar")

        settings = QSettings()
        settings.beginGroup("main_window")
        self.restoreGeometry(settings.value("geometry"))
        self.restoreState(settings.value("state"))
        self.recent_files = settings.value("recent_files")
        if self.recent_files is None:
            self.recent_files = []
        elif not isinstance(self.recent_files, list):
            self.recent_files = [self.recent_files]
        self.update_recent()
        settings.endGroup()

        prev_action.setEnabled(False)
        next_action.setEnabled(False)
        tile_action.setEnabled(False)
        cascade_action.setEnabled(False)
        close_action.setEnabled(False)
        tabbed_action.setEnabled(False)
        self.tree_widget.setEnabled(False)
        self.showNormal()
        self.normal_action.setEnabled(False)
        self.show_message(self.tr("Ready"))