erstelle die dragndrop funktionen vollständig und füge auch feedback mechanismen hinzu um den nutzer zu visualisieren das die maus ein objekt droppen kann wenn die maus sich im drop bereich befindet. Verbesser das gesamte gui, style und layout sowie die farb pallette. Verbesser auch den sonstigen code weiter und nenne muir dann das vollständige skript der gesamten anwendung.

import sys
import os
import cv2
import numpy as np
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtGui import QImage, QPixmap
from PyQt5.QtWidgets import (
    QLabel, QTabWidget, QTextEdit, QLineEdit, QPushButton,
    QListWidget, QVBoxLayout, QFileDialog, QHBoxLayout, QGroupBox,
    QFormLayout, QSlider, QCheckBox, QProgressBar, QMainWindow, QApplication
)
from PyQt5.QtCore import Qt, QThread, pyqtSignal, QMutex, QMutexLocker
from skimage.metrics import structural_similarity as ssi
from PIL import Image

# Dark mode stylesheet
DARK_STYLE = """
QWidget {
    background-color: #2b2b2b;
    color: #f0f0f0;
    font-family: 'Segoe UI', sans-serif;
    font-size: 10pt;
}

QPushButton {
    background-color: #3c3c3c;
    border: 1px solid #5a5a5a;
    padding: 5px;
    border-radius: 5px;
}

QPushButton:hover {
    background-color: #505050;
}

QPushButton:pressed {
    background-color: #383838;
}

QLineEdit, QTextEdit, QListWidget, QLabel, QSlider, QGroupBox {
    background-color: #3c3c3c;
    border: 1px solid #5a5a5a;
    padding: 5px;
    border-radius: 5px;
    color: #f0f0f0;
}

QSlider::groove:horizontal {
    border: 1px solid #5a5a5a;
    height: 8px;
    background: #4a4a4a;
    border-radius: 4px;
}

QSlider::handle:horizontal {
    background: #8f8f91;
    border: 1px solid #5a5a5a;
    width: 18px;
    margin: -5px 0;
    border-radius: 9px;
}

QProgressBar {
    background-color: #3c3c3c;
    border: 1px solid #5a5a5a;
    border-radius: 5px;
    text-align: center;
}

QProgressBar::chunk {
    background-color: #5a5a5a;
    width: 20px;
    margin: 0.5px;
}

QTabWidget::pane { /* The tab widget frame */
    border-top: 2px solid #444;
}

QTabBar::tab {
    background: #3c3c3c;
    border: 1px solid #444;
    padding: 5px;
    border-bottom: none;
    border-top-left-radius: 4px;
    border-top-right-radius: 4px;
    margin-right: 2px;
}

QTabBar::tab:selected, QTabBar::tab:hover {
    background: #5a5a5a;
}
"""

class ImageLoaderThread(QThread):
    progress = pyqtSignal(int)
    finished = pyqtSignal(list)
    
    def __init__(self, directory):
        super().__init__()
        self.directory = directory

    def run(self):
        image_files = []
        # Collect all image files from directory and subdirectories
        for root, dirs, files in os.walk(self.directory):
            for file in files:
                if file.lower().endswith(('.png', '.jpg', '.jpeg', '.gif', '.bmp', '.tiff', '.webp')):
                    image_files.append(os.path.join(root, file))
        total_files = len(image_files)
        for idx, file in enumerate(image_files, 1):
            progress_percent = int((idx / total_files) * 100) if total_files > 0 else 100
            self.progress.emit(progress_percent)
        self.finished.emit(image_files)

class PreviewLabel(QLabel):
    def __init__(self):
        super().__init__()
        self.original_pixmap = None
        self.setFixedSize(300, 200)
        self.setAlignment(Qt.AlignCenter)
        self.setStyleSheet("background-color: #5a5a5a; border: 1px solid #444; border-radius: 5px;")

    def setPixmap(self, pixmap):
        if pixmap != self.original_pixmap:
            self.original_pixmap = pixmap
        scaled_pixmap = pixmap.scaled(
            self.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation
        )
        super().setPixmap(scaled_pixmap)

    def enterEvent(self, event):
        if self.original_pixmap:
            zoomed_pixmap = self.original_pixmap.scaled(
                int(1.2 * self.width()),
                int(1.2 * self.height()),
                Qt.KeepAspectRatio,
                Qt.SmoothTransformation
            )
            super().setPixmap(zoomed_pixmap)

    def leaveEvent(self, event):
        if self.original_pixmap:
            super().setPixmap(
                self.original_pixmap.scaled(
                    self.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation
                )
            )

class DropLineEdit(QLineEdit):
    def __init__(self, accept_dir=False, accept_file=False, parent=None):
        super().__init__(parent)
        self.accept_dir = accept_dir
        self.accept_file = accept_file
        self.setAcceptDrops(True)

    def dragEnterEvent(self, event):
        if event.mimeData().hasUrls():
            urls = event.mimeData().urls()
            if len(urls) == 1:
                url = urls[0].toLocalFile()
                if self.accept_file and os.path.isfile(url):
                    event.acceptProposedAction()
                elif self.accept_dir and os.path.isdir(url):
                    event.acceptProposedAction()
        else:
            event.ignore()

    def dropEvent(self, event):
        urls = event.mimeData().urls()
        if len(urls) == 1:
            url = urls[0].toLocalFile()
            if self.accept_file and os.path.isfile(url):
                self.setText(url)
            elif self.accept_dir and os.path.isdir(url):
                self.setText(url)

class FrameExtractor(QtCore.QObject):
    progress = pyqtSignal(int)
    log = pyqtSignal(str)
    finished = pyqtSignal(list)

    def __init__(self, video_path, output_dir, sharpness_threshold, overlap_threshold,
                 brightness_adjustment, shadow_removal_enabled, contrast_adjustment,
                 saturation_adjustment):
        super().__init__()
        self.video_path = video_path
        self.output_dir = output_dir
        self.sharpness_threshold = sharpness_threshold
        self.overlap_threshold = overlap_threshold
        self.brightness_adjustment = brightness_adjustment
        self.shadow_removal_enabled = shadow_removal_enabled
        self.contrast_adjustment = contrast_adjustment
        self.saturation_adjustment = saturation_adjustment

    def log_message(self, message):
        self.log.emit(message)

    def measure_sharpness(self, frame):
        gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
        lap = cv2.Laplacian(gray, cv2.CV_64F)
        return lap.var()

    def frames_overlap(self, frame1, frame2):
        # Histogram comparison
        hist1 = cv2.calcHist([frame1], [0, 1, 2], None, [8,8,8], [0,256,0,256,0,256])
        hist2 = cv2.calcHist([frame2], [0, 1, 2], None, [8,8,8], [0,256,0,256,0,256])
        cv2.normalize(hist1, hist1)
        cv2.normalize(hist2, hist2)
        similarity = cv2.compareHist(hist1, hist2, cv2.HISTCMP_CORREL)
        return similarity  # Value between -1 and 1

    def adjust_brightness(self, frame):
        hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
        h, s, v = cv2.split(hsv)
        v = np.clip(v + self.brightness_adjustment, 0, 255).astype(np.uint8)
        final_hsv = cv2.merge((h, s, v))
        return cv2.cvtColor(final_hsv, cv2.COLOR_HSV2BGR)

    def adjust_contrast(self, frame):
        # Contrast adjustment scaling factor
        alpha = 1 + self.contrast_adjustment / 100.0  # scale factor
        return cv2.convertScaleAbs(frame, alpha=alpha, beta=0)

    def adjust_saturation(self, frame):
        hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
        h, s, v = cv2.split(hsv)
        s = np.clip(s + self.saturation_adjustment, 0, 255).astype(np.uint8)
        final_hsv = cv2.merge((h, s, v))
        return cv2.cvtColor(final_hsv, cv2.COLOR_HSV2BGR)

    def shadow_removal(self, frame):
        if not self.shadow_removal_enabled:
            return frame

        lab = cv2.cvtColor(frame, cv2.COLOR_BGR2LAB)
        l_channel, a_channel, b_channel = cv2.split(lab)
        clahe = cv2.createCLAHE(clipLimit=3.0, tileGridSize=(8, 8))
        cl = clahe.apply(l_channel)
        limg = cv2.merge((cl, a_channel, b_channel))
        return cv2.cvtColor(limg, cv2.COLOR_LAB2BGR)

    def sharpen_image(self, frame):
        kernel = np.array([[0, -1, 0],
                           [-1, 5, -1],
                           [0, -1, 0]])
        return cv2.filter2D(frame, -1, kernel)

    def run(self):
        try:
            cap = cv2.VideoCapture(self.video_path)
            if not cap.isOpened():
                self.log_message("Failed to open video file.")
                self.finished.emit([])
                return

            total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
            selected_frames = []
            successful_frame_count = 0

            basename = os.path.splitext(os.path.basename(self.video_path))[0]

            previous_frame = None

            for i in range(total_frames):
                ret, frame = cap.read()
                if not ret:
                    break

                sharpness = self.measure_sharpness(frame)
                if sharpness < self.sharpness_threshold:
                    # Frame is too blurry; skip
                    continue

                if previous_frame is not None:
                    overlap = self.frames_overlap(previous_frame, frame)
                    if overlap >= self.overlap_threshold:
                        # Apply image processing
                        processed_frame = self.adjust_brightness(frame)
                        processed_frame = self.adjust_contrast(processed_frame)
                        processed_frame = self.adjust_saturation(processed_frame)
                        processed_frame = self.shadow_removal(processed_frame)
                        processed_frame = self.sharpen_image(processed_frame)

                        frame_name = f"{basename}_frame_{successful_frame_count:05d}.png"
                        frame_path = os.path.join(self.output_dir, frame_name)
                        cv2.imwrite(frame_path, processed_frame)
                        selected_frames.append(frame_path)
                        successful_frame_count += 1
                        previous_frame = processed_frame.copy()

                        if successful_frame_count % 10 == 0 or successful_frame_count == 1:
                            self.log_message(f"Frame {i}: Successfully extracted {successful_frame_count} frames.")

                else:
                    # First frame that passes sharpness threshold
                    processed_frame = self.adjust_brightness(frame)
                    processed_frame = self.adjust_contrast(processed_frame)
                    processed_frame = self.adjust_saturation(processed_frame)
                    processed_frame = self.shadow_removal(processed_frame)
                    processed_frame = self.sharpen_image(processed_frame)

                    frame_name = f"{basename}_frame_{successful_frame_count:05d}.png"
                    frame_path = os.path.join(self.output_dir, frame_name)
                    cv2.imwrite(frame_path, processed_frame)
                    selected_frames.append(frame_path)
                    successful_frame_count += 1
                    previous_frame = processed_frame.copy()

                    if successful_frame_count % 10 == 0 or successful_frame_count == 1:
                        self.log_message(f"Frame {i}: Successfully extracted {successful_frame_count} frames.")

                progress_percent = int((i + 1) / total_frames * 100)
                if (i + 1) % 10 == 0 or i == total_frames -1:
                    self.progress.emit(progress_percent)

            cap.release()
            self.finished.emit(selected_frames)
        except Exception as e:
            self.log_message(f"Error during extraction: {str(e)}")
            self.finished.emit([])

class FrameExtractorThread(QThread):
    def __init__(self, extractor):
        super().__init__()
        self.extractor = extractor

    def run(self):
        self.extractor.run()

class FrameExtractorUI(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Video Frame Extractor")
        self.setup_ui()
    
    def setup_ui(self):
        layout = QVBoxLayout(self)

        # Video Selection
        video_layout = QHBoxLayout()
        self.video_path_edit = DropLineEdit(accept_file=True)
        self.video_path_edit.setPlaceholderText("Drag and drop a video file here or click Browse")
        self.video_path_edit.setToolTip("Select a video file by browsing or dragging and dropping it here.")
        browse_button = QPushButton("Browse")
        browse_button.setCursor(Qt.PointingHandCursor)
        browse_button.setToolTip("Browse to select a video file.")
        browse_button.clicked.connect(self.browse_video)
        video_layout.addWidget(QLabel("Video File:"))
        video_layout.addWidget(self.video_path_edit)
        video_layout.addWidget(browse_button)
        layout.addLayout(video_layout)

        # Output Directory Selection
        output_layout = QHBoxLayout()
        self.output_path_edit = DropLineEdit(accept_dir=True)
        self.output_path_edit.setPlaceholderText("Drag and drop an output folder here or click Browse")
        self.output_path_edit.setToolTip("Select an output folder by browsing or dragging and dropping it here.")
        browse_output_button = QPushButton("Browse")
        browse_output_button.setCursor(Qt.PointingHandCursor)
        browse_output_button.setToolTip("Browse to select an output folder.")
        browse_output_button.clicked.connect(self.browse_output)
        output_layout.addWidget(QLabel("Output Folder:"))
        output_layout.addWidget(self.output_path_edit)
        output_layout.addWidget(browse_output_button)
        layout.addLayout(output_layout)

        # Settings Group
        settings_group = QGroupBox("Settings")
        settings_layout = QFormLayout()

        # Sharpness Threshold
        sharpness_layout = QHBoxLayout()
        self.sharpness_slider = QSlider(Qt.Horizontal)
        self.sharpness_slider.setMinimum(100)
        self.sharpness_slider.setMaximum(1000)
        self.sharpness_slider.setValue(300)
        self.sharpness_slider.setToolTip("Set the minimum sharpness threshold for frame selection.")
        self.sharpness_value = QLabel("300")
        self.sharpness_slider.valueChanged.connect(
            lambda val: self.sharpness_value.setText(str(val))
        )
        sharpness_layout.addWidget(self.sharpness_slider)
        sharpness_layout.addWidget(self.sharpness_value)
        settings_layout.addRow("Sharpness Threshold:", sharpness_layout)

        # Overlap Threshold (using correlation, set between 0 and 1)
        overlap_layout = QHBoxLayout()
        self.overlap_slider = QSlider(Qt.Horizontal)
        self.overlap_slider.setMinimum(0)
        self.overlap_slider.setMaximum(100)
        self.overlap_slider.setValue(50)
        self.overlap_slider.setToolTip("Set the overlap threshold to determine frame similarity.")
        self.overlap_value = QLabel("0.50")
        self.overlap_slider.valueChanged.connect(
            lambda val: self.overlap_value.setText(f"{val / 100:.2f}")
        )
        overlap_layout.addWidget(self.overlap_slider)
        overlap_layout.addWidget(self.overlap_value)
        settings_layout.addRow("Overlap Threshold:", overlap_layout)

        # Brightness Adjustment
        brightness_layout = QHBoxLayout()
        self.brightness_slider = QSlider(Qt.Horizontal)
        self.brightness_slider.setMinimum(-100)
        self.brightness_slider.setMaximum(100)
        self.brightness_slider.setValue(0)
        self.brightness_slider.setToolTip("Adjust the brightness of the extracted frames.")
        self.brightness_value = QLabel("0")
        self.brightness_slider.valueChanged.connect(
            lambda val: self.brightness_value.setText(str(val))
        )
        brightness_layout.addWidget(self.brightness_slider)
        brightness_layout.addWidget(self.brightness_value)
        settings_layout.addRow("Brightness Adjustment:", brightness_layout)

        # Contrast Adjustment
        contrast_layout = QHBoxLayout()
        self.contrast_slider = QSlider(Qt.Horizontal)
        self.contrast_slider.setMinimum(-100)
        self.contrast_slider.setMaximum(100)
        self.contrast_slider.setValue(0)
        self.contrast_slider.setToolTip("Adjust the contrast of the extracted frames.")
        self.contrast_value = QLabel("0")
        self.contrast_slider.valueChanged.connect(
            lambda val: self.contrast_value.setText(str(val))
        )
        contrast_layout.addWidget(self.contrast_slider)
        contrast_layout.addWidget(self.contrast_value)
        settings_layout.addRow("Contrast Adjustment:", contrast_layout)

        # Saturation Adjustment
        saturation_layout = QHBoxLayout()
        self.saturation_slider = QSlider(Qt.Horizontal)
        self.saturation_slider.setMinimum(-100)
        self.saturation_slider.setMaximum(100)
        self.saturation_slider.setValue(0)
        self.saturation_slider.setToolTip("Adjust the saturation of the extracted frames.")
        self.saturation_value = QLabel("0")
        self.saturation_slider.valueChanged.connect(
            lambda val: self.saturation_value.setText(str(val))
        )
        saturation_layout.addWidget(self.saturation_slider)
        saturation_layout.addWidget(self.saturation_value)
        settings_layout.addRow("Saturation Adjustment:", saturation_layout)

        # Shadow Removal
        self.shadow_removal_checkbox = QCheckBox("Enable Shadow Removal")
        self.shadow_removal_checkbox.setChecked(True)
        self.shadow_removal_checkbox.setToolTip("Enable or disable shadow removal in the extracted frames.")
        settings_layout.addRow(self.shadow_removal_checkbox)

        settings_group.setLayout(settings_layout)
        layout.addWidget(settings_group)

        # Start Button
        self.start_button = QPushButton("Start Extraction")
        self.start_button.setCursor(Qt.PointingHandCursor)
        self.start_button.setToolTip("Start the frame extraction process.")
        self.start_button.clicked.connect(self.start_extraction)
        layout.addWidget(self.start_button)

        # Progress Bar and Label
        progress_layout = QHBoxLayout()
        self.progress_bar = QProgressBar()
        self.progress_bar.setValue(0)
        self.progress_bar.setToolTip("Shows the progress of the frame extraction.")
        self.progress_label = QLabel("Processing: 0%")
        progress_layout.addWidget(self.progress_label)
        progress_layout.addWidget(self.progress_bar)
        layout.addLayout(progress_layout)

        # Log Text
        self.log_text = QTextEdit()
        self.log_text.setReadOnly(True)
        self.log_text.setToolTip("Displays log messages during frame extraction.")
        layout.addWidget(QLabel("Log:"))
        layout.addWidget(self.log_text)

        # Selected Frames List
        self.selected_frames_list = QListWidget()
        self.selected_frames_list.setToolTip("List of frames extracted. Click to preview.")
        self.selected_frames_list.itemClicked.connect(self.preview_frame)
        layout.addWidget(QLabel("Selected Frames:"))
        layout.addWidget(self.selected_frames_list)

        # Remove Frame Button
        remove_button = QPushButton("Remove Selected Frame")
        remove_button.setCursor(Qt.PointingHandCursor)
        remove_button.setToolTip("Remove the selected frame from the list.")
        remove_button.clicked.connect(self.remove_selected_frame)
        layout.addWidget(remove_button)

        # Preview Section
        self.preview_label = QLabel("Preview:")
        layout.addWidget(self.preview_label)
        self.preview_image = PreviewLabel()
        layout.addWidget(self.preview_image)

        # Add stretch to push everything up
        layout.addStretch()

    def browse_video(self):
        file_dialog = QFileDialog()
        path, _ = file_dialog.getOpenFileName(
            self, "Select Video File", "", "Videos (*.mp4 *.avi *.mov *.mkv)"
        )
        if path:
            self.video_path_edit.setText(path)

    def browse_output(self):
        dir_dialog = QFileDialog()
        path = dir_dialog.getExistingDirectory(self, "Select Output Folder")
        if path:
            self.output_path_edit.setText(path)

    def start_extraction(self):
        video_path = self.video_path_edit.text()
        output_dir = self.output_path_edit.text()
        sharpness_threshold = self.sharpness_slider.value()
        overlap_threshold = self.overlap_slider.value() / 100.0  # Scale to 0-1
        brightness_adjustment = self.brightness_slider.value()
        contrast_adjustment = self.contrast_slider.value()
        saturation_adjustment = self.saturation_slider.value()
        shadow_removal_enabled = self.shadow_removal_checkbox.isChecked()

        if not os.path.isfile(video_path):
            QtWidgets.QMessageBox.critical(self, "Error", "Please select a valid video file.")
            return
        if not os.path.isdir(output_dir):
            # Try to create the output directory
            try:
                os.makedirs(output_dir, exist_ok=True)
            except Exception as e:
                QtWidgets.QMessageBox.critical(self, "Error", f"Failed to create output folder: {str(e)}")
                return

        self.start_button.setEnabled(False)
        self.log_text.clear()
        self.progress_bar.setValue(0)
        self.progress_label.setText("Processing: 0%")
        self.selected_frames_list.clear()
        self.preview_image.clear()

        self.extractor = FrameExtractor(
            video_path, output_dir, sharpness_threshold, overlap_threshold,
            brightness_adjustment, shadow_removal_enabled, contrast_adjustment,
            saturation_adjustment
        )

        self.thread = FrameExtractorThread(self.extractor)
        self.extractor.moveToThread(self.thread)

        self.thread.started.connect(self.extractor.run)
        self.extractor.progress.connect(self.update_progress)
        self.extractor.log.connect(self.update_log)
        self.extractor.finished.connect(self.extraction_finished)
        self.extractor.finished.connect(self.thread.quit)
        self.extractor.finished.connect(self.extractor.deleteLater)
        self.thread.finished.connect(self.thread.deleteLater)

        self.thread.start()

    def update_progress(self, value):
        self.progress_bar.setValue(value)
        self.progress_label.setText(f"Processing: {value}%")

    def update_log(self, message):
        self.log_text.append(message)

    def extraction_finished(self, frames):
        total_extracted = len(frames)
        self.log_text.append(f"Extraction completed. {total_extracted} frames extracted.")
        self.start_button.setEnabled(True)
        self.selected_frames_list.addItems(frames)

    def remove_selected_frame(self):
        selected_items = self.selected_frames_list.selectedItems()
        if not selected_items:
            return
        for item in selected_items:
            self.selected_frames_list.takeItem(self.selected_frames_list.row(item))
        self.preview_image.clear()

    def preview_frame(self, item):
        frame_path = item.text()
        if not os.path.isfile(frame_path):
            self.log_text.append(f"Preview not available: {frame_path} does not exist.")
            return
        image = QImage(frame_path)
        if image.isNull():
            self.log_text.append(f"Cannot load image: {frame_path}")
            return
        pixmap = QPixmap.fromImage(image)
        self.preview_image.setPixmap(pixmap)

class ImageQualityChecker(QtCore.QObject):
    log = pyqtSignal(str)
    progress = pyqtSignal(int)
    finished = pyqtSignal(list)

    def __init__(self):
        super().__init__()
        self.image_files = []  # List to hold image file paths
        self.result_files = []  # List for selected files based on quality
        self.min_quality = 0  # Minimum quality threshold
        self.mutex = QMutex()

    def load_images(self, files):
        with QMutexLocker(self.mutex):
            self.image_files = list(files)

    def compute_quality(self, image_path, reference_gray):
        try:
            image = Image.open(image_path).convert('RGB')
            # Calculate brightness
            brightness = self.compute_brightness(image)
            # Calculate sharpness using OpenCV
            cv_image = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR)
            gray = cv2.cvtColor(cv_image, cv2.COLOR_BGR2GRAY)
            lap_var = cv2.Laplacian(gray, cv2.CV_64F).var()
            # Normalize sharpness to 0-100
            sharpness = min(100, int(lap_var / 100.0))
            # Structural Similarity Index
            ssim_score = 100
            if reference_gray is not None:
                try:
                    ssim_index = ssi(reference_gray, gray)
                    ssim_score = int(ssim_index * 100)
                except Exception as e:
                    self.log.emit(f"SSIM error for {os.path.basename(image_path)}: {str(e)}")
                    ssim_score = 0
            # Combine metrics
            quality = min(100, (brightness + sharpness + ssim_score) // 3)
            return quality
        except Exception as e:
            self.log.emit(f"Error processing {os.path.basename(image_path)}: {str(e)}")
            return 0

    def compute_brightness(self, image: Image.Image) -> int:
        grayscale_image = image.convert('L')
        histogram = grayscale_image.histogram()
        total_pixels = sum(histogram)
        brightness = sum(i * hist for i, hist in enumerate(histogram)) / total_pixels
        return int((brightness / 255) * 100)

    def evaluate_quality(self, min_quality):
        self.result_files.clear()
        with QMutexLocker(self.mutex):
            images = list(self.image_files)

        if not images:
            self.log.emit("No images loaded for quality evaluation.")
            self.finished.emit([])
            return

        reference_gray = None
        if images:
            try:
                reference = cv2.imread(images[0], cv2.IMREAD_GRAYSCALE)
                if reference is not None:
                    reference_gray = reference
            except Exception as e:
                self.log.emit(f"Failed to load reference image: {str(e)}")
                reference_gray = None

        total = len(images)
        for idx, file in enumerate(images):
            quality = self.compute_quality(file, reference_gray)
            if quality >= min_quality:
                self.result_files.append(file)
                self.log.emit(f"{os.path.basename(file)} - Quality: {quality}")
            progress_percent = int((idx + 1) / total * 100) if total > 0 else 100
            if (idx + 1) % 10 == 0 or idx == total - 1:
                self.progress.emit(progress_percent)

        self.finished.emit(self.result_files)

    def get_results(self):
        return self.result_files

class ImageQualityCheckerThread(QThread):
    def __init__(self, checker, min_quality):
        super().__init__()
        self.checker = checker
        self.min_quality = min_quality

    def run(self):
        self.checker.evaluate_quality(self.min_quality)

class ImageQualityCheckerUI(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Image Quality Checker")
        self.setup_ui()
        self.image_quality_checker = ImageQualityChecker()
        self.setup_signals()

    def setup_ui(self):
        layout = QVBoxLayout(self)

        # Load Images Button
        load_layout = QHBoxLayout()
        self.load_button = QPushButton("Load Images")
        self.load_button.setCursor(Qt.PointingHandCursor)
        self.load_button.setToolTip("Load images from a folder by browsing or dragging and dropping the folder here.")
        load_layout.addWidget(self.load_button)
        self.load_path_edit = DropLineEdit(accept_dir=True)
        self.load_path_edit.setPlaceholderText("Drag and drop a folder here or click Load")
        self.load_path_edit.setToolTip("Drag and drop a folder containing images or click Load to browse.")
        load_layout.addWidget(self.load_path_edit)
        layout.addLayout(load_layout)
        self.load_button.clicked.connect(self.browse_folder)
        # Connect to load images when path_edit is updated
        self.load_path_edit.textChanged.connect(self.load_images_from_path)

        # Min Quality Input
        min_quality_layout = QHBoxLayout()
        self.min_quality_label = QLabel("Min Quality (0-100):")
        self.min_quality_entry = QLineEdit()
        self.min_quality_entry.setPlaceholderText("Enter minimum quality")
        self.min_quality_entry.setToolTip("Enter the minimum quality threshold. Images with quality above this will be selected.")
        min_quality_layout.addWidget(self.min_quality_label)
        min_quality_layout.addWidget(self.min_quality_entry)
        layout.addLayout(min_quality_layout)

        # Evaluate Button
        self.evaluate_button = QPushButton("Evaluate Quality")
        self.evaluate_button.setCursor(Qt.PointingHandCursor)
        self.evaluate_button.setToolTip("Start evaluating the quality of the loaded images.")
        self.evaluate_button.clicked.connect(self.evaluate_quality)
        layout.addWidget(self.evaluate_button)

        # Progress Bar and Label
        progress_layout = QHBoxLayout()
        self.progress_bar = QProgressBar()
        self.progress_bar.setValue(0)
        self.progress_bar.setToolTip("Shows the progress of the quality evaluation.")
        self.progress_label = QLabel("Progress: 0%")
        progress_layout.addWidget(self.progress_label)
        progress_layout.addWidget(self.progress_bar)
        layout.addLayout(progress_layout)

        # Log Text
        self.result_text = QTextEdit()
        self.result_text.setReadOnly(True)
        self.result_text.setToolTip("Displays log messages during quality evaluation.")
        layout.addWidget(QLabel("Results:"))
        layout.addWidget(self.result_text)

        # Selected Results List
        self.selected_results_list = QListWidget()
        self.selected_results_list.setToolTip("List of high-quality images. Click to preview.")
        self.selected_results_list.itemClicked.connect(self.preview_image_clicked)
        layout.addWidget(QLabel("Selected High-Quality Images:"))
        layout.addWidget(self.selected_results_list)

        # Remove Image Button
        remove_button = QPushButton("Remove Selected Image")
        remove_button.setCursor(Qt.PointingHandCursor)
        remove_button.setToolTip("Remove the selected image from the results.")
        remove_button.clicked.connect(self.remove_selected_image)
        layout.addWidget(remove_button)

        # Preview Section
        self.preview_label = QLabel("Preview:")
        layout.addWidget(self.preview_label)
        self.preview_image = PreviewLabel()
        layout.addWidget(self.preview_image)

        # Add stretch
        layout.addStretch()

    def setup_signals(self):
        self.image_quality_checker.log.connect(self.update_log)
        self.image_quality_checker.progress.connect(self.update_progress)
        self.image_quality_checker.finished.connect(self.evaluation_finished)

    def browse_folder(self):
        dir_dialog = QFileDialog()
        path = dir_dialog.getExistingDirectory(self, "Select Image Folder")
        if path:
            self.load_path_edit.setText(path)

    def load_images_from_path(self, path):
        if os.path.isdir(path):
            self.load_button.setEnabled(False)
            self.result_text.append("Loading images...")
            self.progress_bar.setValue(0)
            self.progress_label.setText("Loading images: 0%")
            self.selected_results_list.clear()
            self.preview_image.clear()
            self.image_loader_thread = ImageLoaderThread(path)
            self.image_loader_thread.progress.connect(self.update_progress)
            self.image_loader_thread.finished.connect(self.on_images_loaded)
            self.image_loader_thread.start()

    def on_images_loaded(self, image_files):
        self.image_quality_checker.load_images(image_files)
        self.update_listbox()
        self.load_button.setEnabled(True)
        self.result_text.append(f"{len(image_files)} images loaded.")

    def update_listbox(self):
        self.selected_results_list.clear()
        self.selected_results_list.addItems([os.path.basename(f) for f in self.image_quality_checker.image_files])
        self.preview_image.clear()

    def evaluate_quality(self):
        min_quality_text = self.min_quality_entry.text()
        try:
            min_quality = int(min_quality_text)
            if not (0 <= min_quality <= 100):
                raise ValueError
            self.image_quality_checker.min_quality = min_quality
        except ValueError:
            QtWidgets.QMessageBox.critical(
                self, "Invalid Input", "Please enter a valid number between 0 and 100 for the minimum quality."
            )
            return

        if not self.image_quality_checker.image_files:
            QtWidgets.QMessageBox.information(
                self, "No Images", "Please load images before evaluating quality."
            )
            return

        self.result_text.clear()
        self.evaluate_button.setEnabled(False)
        self.load_button.setEnabled(False)
        self.selected_results_list.clear()
        self.preview_image.clear()
        self.result_text.append("Starting quality evaluation...\n")

        self.thread = ImageQualityCheckerThread(self.image_quality_checker, self.image_quality_checker.min_quality)
        self.thread.started.connect(lambda: self.result_text.append("Quality evaluation started...\n"))
        self.image_quality_checker.finished.connect(self.thread.quit)
        self.thread.finished.connect(self.thread.deleteLater)
        self.image_quality_checker.finished.connect(self.evaluation_finished)
        self.thread.start()

    def update_log(self, message):
        self.result_text.append(message)

    def update_progress(self, value):
        self.progress_bar.setValue(value)
        self.progress_label.setText(f"Progress: {value}%")

    def evaluation_finished(self, results):
        self.evaluate_button.setEnabled(True)
        self.load_button.setEnabled(True)
        if results:
            self.result_text.append("\nEvaluation completed.")
            self.result_text.append(f"Total images meeting quality criteria: {len(results)}")
            self.selected_results_list.addItems([os.path.basename(f) for f in results])
        else:
            self.result_text.append("\nNo images meet the minimum quality requirement.")
        self.progress_bar.setValue(100)
        self.progress_label.setText("Progress: 100%")

    def remove_selected_image(self):
        selected_items = self.selected_results_list.selectedItems()
        if not selected_items:
            return
        for item in selected_items:
            self.selected_results_list.takeItem(self.selected_results_list.row(item))
        self.preview_image.clear()

    def preview_image_clicked(self, item):
        # Get the full path from image_quality_checker's image_files
        image_name = item.text()
        full_path = None
        for path in self.image_quality_checker.get_results():
            if os.path.basename(path) == image_name:
                full_path = path
                break
        if not full_path or not os.path.isfile(full_path):
            self.result_text.append(f"Preview not available: {full_path} does not exist.")
            return
        image = QImage(full_path)
        if image.isNull():
            self.result_text.append(f"Cannot load image: {full_path}")
            return
        pixmap = QPixmap.fromImage(image)
        self.preview_image.setPixmap(pixmap)

class MainWindow(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Image Processing Application")
        self.setGeometry(100, 100, 1000, 800)
        self.setup_ui()

    def setup_ui(self):
        self.tabs = QTabWidget()
        self.setCentralWidget(self.tabs)

        self.frame_extractor_widget = FrameExtractorUI()
        self.image_quality_checker_widget = ImageQualityCheckerUI()

        self.tabs.addTab(self.frame_extractor_widget, "Video Frame Extractor")
        self.tabs.addTab(self.image_quality_checker_widget, "Image Quality Checker")

def main():
    app = QApplication(sys.argv)
    app.setStyle("Fusion")
    app.setStyleSheet(DARK_STYLE)
    window = MainWindow()
    window.show()
    sys.exit(app.exec_())

if __name__ == "__main__":
    main()