Carregar ficheiros para "/"
This commit is contained in:
286
document_compare.py
Normal file
286
document_compare.py
Normal file
@ -0,0 +1,286 @@
|
||||
from PySide6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QPushButton,
|
||||
QLabel, QComboBox, QScrollArea, QSplitter,
|
||||
QMessageBox)
|
||||
from PySide6.QtCore import Qt
|
||||
from PySide6.QtGui import QImage, QPixmap
|
||||
import fitz
|
||||
from database import get_session
|
||||
from models import Document, DocumentVersion
|
||||
import difflib
|
||||
import os
|
||||
from PIL import Image
|
||||
import numpy as np
|
||||
import cv2
|
||||
|
||||
class DocumentCompare(QWidget):
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.doc1 = None
|
||||
self.doc2 = None
|
||||
self.current_page = 0
|
||||
self.setup_ui()
|
||||
|
||||
def setup_ui(self):
|
||||
layout = QVBoxLayout(self)
|
||||
|
||||
# Document selection
|
||||
select_layout = QHBoxLayout()
|
||||
|
||||
# First document
|
||||
doc1_layout = QVBoxLayout()
|
||||
doc1_layout.addWidget(QLabel("Documento 1:"))
|
||||
self.doc1_combo = QComboBox()
|
||||
self.doc1_combo.currentIndexChanged.connect(self.load_comparison)
|
||||
doc1_layout.addWidget(self.doc1_combo)
|
||||
select_layout.addLayout(doc1_layout)
|
||||
|
||||
# Second document
|
||||
doc2_layout = QVBoxLayout()
|
||||
doc2_layout.addWidget(QLabel("Documento 2:"))
|
||||
self.doc2_combo = QComboBox()
|
||||
self.doc2_combo.currentIndexChanged.connect(self.load_comparison)
|
||||
doc2_layout.addWidget(self.doc2_combo)
|
||||
select_layout.addLayout(doc2_layout)
|
||||
|
||||
layout.addLayout(select_layout)
|
||||
|
||||
# Navigation
|
||||
nav_layout = QHBoxLayout()
|
||||
|
||||
self.prev_btn = QPushButton("Anterior")
|
||||
self.prev_btn.clicked.connect(self.prev_page)
|
||||
nav_layout.addWidget(self.prev_btn)
|
||||
|
||||
self.page_label = QLabel("Página 0 de 0")
|
||||
nav_layout.addWidget(self.page_label)
|
||||
|
||||
self.next_btn = QPushButton("Próxima")
|
||||
self.next_btn.clicked.connect(self.next_page)
|
||||
nav_layout.addWidget(self.next_btn)
|
||||
|
||||
nav_layout.addStretch()
|
||||
|
||||
self.diff_mode_combo = QComboBox()
|
||||
self.diff_mode_combo.addItems(["Visual", "Texto", "Híbrido"])
|
||||
self.diff_mode_combo.currentTextChanged.connect(self.load_comparison)
|
||||
nav_layout.addWidget(self.diff_mode_combo)
|
||||
|
||||
layout.addLayout(nav_layout)
|
||||
|
||||
# Comparison view
|
||||
self.splitter = QSplitter(Qt.Horizontal)
|
||||
|
||||
# Left document
|
||||
self.left_scroll = QScrollArea()
|
||||
self.left_scroll.setWidgetResizable(True)
|
||||
self.left_label = QLabel()
|
||||
self.left_label.setAlignment(Qt.AlignCenter)
|
||||
self.left_scroll.setWidget(self.left_label)
|
||||
self.splitter.addWidget(self.left_scroll)
|
||||
|
||||
# Right document
|
||||
self.right_scroll = QScrollArea()
|
||||
self.right_scroll.setWidgetResizable(True)
|
||||
self.right_label = QLabel()
|
||||
self.right_label.setAlignment(Qt.AlignCenter)
|
||||
self.right_scroll.setWidget(self.right_label)
|
||||
self.splitter.addWidget(self.right_scroll)
|
||||
|
||||
# Difference view
|
||||
self.diff_scroll = QScrollArea()
|
||||
self.diff_scroll.setWidgetResizable(True)
|
||||
self.diff_label = QLabel()
|
||||
self.diff_label.setAlignment(Qt.AlignCenter)
|
||||
self.diff_scroll.setWidget(self.diff_label)
|
||||
self.splitter.addWidget(self.diff_scroll)
|
||||
|
||||
layout.addWidget(self.splitter)
|
||||
|
||||
# Difference summary
|
||||
self.diff_summary = QLabel()
|
||||
layout.addWidget(self.diff_summary)
|
||||
|
||||
self.refresh_documents()
|
||||
|
||||
def refresh_documents(self):
|
||||
try:
|
||||
session = get_session()
|
||||
documents = session.query(Document).all()
|
||||
|
||||
# Update combo boxes
|
||||
self.doc1_combo.clear()
|
||||
self.doc2_combo.clear()
|
||||
|
||||
for doc in documents:
|
||||
item_text = f"{doc.file_name} ({doc.marca} {doc.modelo})"
|
||||
self.doc1_combo.addItem(item_text, doc.id)
|
||||
self.doc2_combo.addItem(item_text, doc.id)
|
||||
|
||||
session.close()
|
||||
except Exception as e:
|
||||
QMessageBox.critical(self, "Erro",
|
||||
f"Erro ao carregar documentos: {str(e)}")
|
||||
|
||||
def load_comparison(self):
|
||||
if (self.doc1_combo.currentData() is None or
|
||||
self.doc2_combo.currentData() is None):
|
||||
return
|
||||
|
||||
try:
|
||||
session = get_session()
|
||||
|
||||
# Load documents
|
||||
doc1 = session.query(Document).get(self.doc1_combo.currentData())
|
||||
doc2 = session.query(Document).get(self.doc2_combo.currentData())
|
||||
|
||||
if doc1 and doc2:
|
||||
self.doc1 = fitz.open(doc1.file_path)
|
||||
self.doc2 = fitz.open(doc2.file_path)
|
||||
self.current_page = 0
|
||||
self.update_comparison()
|
||||
|
||||
session.close()
|
||||
except Exception as e:
|
||||
QMessageBox.critical(self, "Erro",
|
||||
f"Erro ao carregar documentos: {str(e)}")
|
||||
|
||||
def update_comparison(self):
|
||||
if not self.doc1 or not self.doc2:
|
||||
return
|
||||
|
||||
try:
|
||||
# Update page navigation
|
||||
max_pages = min(len(self.doc1), len(self.doc2))
|
||||
self.page_label.setText(f"Página {self.current_page + 1} de {max_pages}")
|
||||
|
||||
# Get pages
|
||||
page1 = self.doc1[self.current_page]
|
||||
page2 = self.doc2[self.current_page]
|
||||
|
||||
# Process based on comparison mode
|
||||
mode = self.diff_mode_combo.currentText()
|
||||
|
||||
if mode == "Visual":
|
||||
self.compare_visual(page1, page2)
|
||||
elif mode == "Texto":
|
||||
self.compare_text(page1, page2)
|
||||
else: # Hybrid
|
||||
self.compare_hybrid(page1, page2)
|
||||
|
||||
except Exception as e:
|
||||
QMessageBox.critical(self, "Erro",
|
||||
f"Erro ao comparar páginas: {str(e)}")
|
||||
|
||||
def compare_visual(self, page1, page2):
|
||||
# Convert pages to images
|
||||
pix1 = page1.get_pixmap()
|
||||
pix2 = page2.get_pixmap()
|
||||
|
||||
# Convert to numpy arrays
|
||||
img1 = np.frombuffer(pix1.samples, dtype=np.uint8).reshape(
|
||||
pix1.height, pix1.width, 3)
|
||||
img2 = np.frombuffer(pix2.samples, dtype=np.uint8).reshape(
|
||||
pix2.height, pix2.width, 3)
|
||||
|
||||
# Resize images to same size
|
||||
height = max(img1.shape[0], img2.shape[0])
|
||||
width = max(img1.shape[1], img2.shape[1])
|
||||
|
||||
img1 = cv2.resize(img1, (width, height))
|
||||
img2 = cv2.resize(img2, (width, height))
|
||||
|
||||
# Calculate difference
|
||||
diff = cv2.absdiff(img1, img2)
|
||||
diff_highlighted = img2.copy()
|
||||
diff_highlighted[diff.mean(axis=2) > 30] = [0, 0, 255] # Red for differences
|
||||
|
||||
# Convert to QPixmap and display
|
||||
self.display_image(img1, self.left_label)
|
||||
self.display_image(img2, self.right_label)
|
||||
self.display_image(diff_highlighted, self.diff_label)
|
||||
|
||||
# Update summary
|
||||
diff_percentage = (diff.mean(axis=2) > 30).mean() * 100
|
||||
self.diff_summary.setText(
|
||||
f"Diferença visual: {diff_percentage:.2f}% da página")
|
||||
|
||||
def compare_text(self, page1, page2):
|
||||
# Extract text
|
||||
text1 = page1.get_text()
|
||||
text2 = page2.get_text()
|
||||
|
||||
# Compare text
|
||||
diff = difflib.unified_diff(
|
||||
text1.splitlines(),
|
||||
text2.splitlines(),
|
||||
lineterm=''
|
||||
)
|
||||
|
||||
# Format differences
|
||||
diff_html = []
|
||||
for line in diff:
|
||||
if line.startswith('+'):
|
||||
diff_html.append(f'<span style="background-color: #aaffaa">{line}</span>')
|
||||
elif line.startswith('-'):
|
||||
diff_html.append(f'<span style="background-color: #ffaaaa">{line}</span>')
|
||||
else:
|
||||
diff_html.append(line)
|
||||
|
||||
# Display original texts and differences
|
||||
self.left_label.setText(f"<pre>{text1}</pre>")
|
||||
self.right_label.setText(f"<pre>{text2}</pre>")
|
||||
self.diff_label.setText(f"<pre>{'<br>'.join(diff_html)}</pre>")
|
||||
|
||||
# Update summary
|
||||
changes = len([l for l in diff_html if l.startswith('<span')])
|
||||
self.diff_summary.setText(
|
||||
f"Diferenças encontradas: {changes} linhas modificadas")
|
||||
|
||||
def compare_hybrid(self, page1, page2):
|
||||
# Combine visual and text comparison
|
||||
self.compare_visual(page1, page2)
|
||||
|
||||
# Add text comparison summary
|
||||
text1 = page1.get_text()
|
||||
text2 = page2.get_text()
|
||||
|
||||
# Calculate text similarity
|
||||
similarity = difflib.SequenceMatcher(None, text1, text2).ratio()
|
||||
|
||||
# Update summary
|
||||
current_summary = self.diff_summary.text()
|
||||
self.diff_summary.setText(
|
||||
f"{current_summary}\nSimilaridade do texto: {similarity:.2f}%")
|
||||
|
||||
def display_image(self, img, label):
|
||||
height, width = img.shape[:2]
|
||||
bytes_per_line = 3 * width
|
||||
|
||||
q_img = QImage(img.data, width, height, bytes_per_line, QImage.Format_RGB888)
|
||||
pixmap = QPixmap.fromImage(q_img)
|
||||
|
||||
# Scale to fit while maintaining aspect ratio
|
||||
scaled_pixmap = pixmap.scaled(
|
||||
label.size(),
|
||||
Qt.KeepAspectRatio,
|
||||
Qt.SmoothTransformation
|
||||
)
|
||||
label.setPixmap(scaled_pixmap)
|
||||
|
||||
def prev_page(self):
|
||||
if self.current_page > 0:
|
||||
self.current_page -= 1
|
||||
self.update_comparison()
|
||||
|
||||
def next_page(self):
|
||||
if (self.doc1 and self.doc2 and
|
||||
self.current_page < min(len(self.doc1), len(self.doc2)) - 1):
|
||||
self.current_page += 1
|
||||
self.update_comparison()
|
||||
|
||||
def closeEvent(self, event):
|
||||
if self.doc1:
|
||||
self.doc1.close()
|
||||
if self.doc2:
|
||||
self.doc2.close()
|
||||
super().closeEvent(event)
|
||||
Reference in New Issue
Block a user