diff --git a/dataset-viewer-stable.py b/dataset-viewer-stable.py deleted file mode 100644 index 056a1d5..0000000 --- a/dataset-viewer-stable.py +++ /dev/null @@ -1,247 +0,0 @@ -import os -import sys -import gc # For explicit garbage collection when we need it -from dataclasses import dataclass -from typing import Optional, Dict -from PyQt6.QtWidgets import ( - QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, - QLabel, QTextEdit, QPushButton, QListWidget, QScrollArea, - QSizePolicy, QProgressBar, QStatusBar -) -from PyQt6.QtCore import Qt, QSize, QTimer -from PyQt6.QtGui import QPixmap, QImage -from PIL import Image, ImageQt -import psutil # For monitoring system resources - -@dataclass -class ImageInfo: - """Keeps track of our image data - like a little passport for each image!""" - path: str - width: int - height: int - pixmap: Optional[QPixmap] = None - last_accessed: float = 0 # We'll use this to know which images we can forget about - -class MemoryFriendlyViewer(QMainWindow): - def __init__(self): - super().__init__() - self.setWindowTitle("✨ Cozy Dataset Viewer") - self.setGeometry(100, 100, 1200, 800) - - # Our image memory management stuff - self.image_cache: Dict[str, ImageInfo] = {} - self.max_cache_size = 5 # We'll only keep 5 images in memory at a time - self.current_image_path: Optional[str] = None - - # Set up our cozy UI - self.init_ui() - - # Set up our memory watchdog - self.setup_memory_monitor() - - # Load those images! - self.load_images() - - def init_ui(self): - """Sets up our user interface - like arranging furniture in a cozy room!""" - main_widget = QWidget() - self.setCentralWidget(main_widget) - layout = QHBoxLayout(main_widget) - - # Left side - Our image list and preview - left_panel = QWidget() - left_layout = QVBoxLayout(left_panel) - - # Status bar for loading - self.progress_bar = QProgressBar() - self.progress_bar.hide() - - # Image list - self.image_list = QListWidget() - self.image_list.itemClicked.connect(self.safe_show_image) - - # Scrollable image preview - self.scroll_area = QScrollArea() - self.scroll_area.setWidgetResizable(True) - self.image_preview = QLabel() - self.image_preview.setAlignment(Qt.AlignmentFlag.AlignCenter) - self.image_preview.setSizePolicy( - QSizePolicy.Policy.Expanding, - QSizePolicy.Policy.Expanding - ) - self.scroll_area.setWidget(self.image_preview) - - left_layout.addWidget(self.progress_bar) - left_layout.addWidget(QLabel("Images:")) - left_layout.addWidget(self.image_list) - left_layout.addWidget(self.scroll_area) - - # Right side - Text editor - right_panel = QWidget() - right_layout = QVBoxLayout(right_panel) - - self.text_edit = QTextEdit() - self.save_button = QPushButton("💾 Save Caption") - self.save_button.clicked.connect(self.safe_save_text) - - right_layout.addWidget(QLabel("Caption:")) - right_layout.addWidget(self.text_edit) - right_layout.addWidget(self.save_button) - - # Put it all together - layout.addWidget(left_panel, stretch=2) - layout.addWidget(right_panel, stretch=1) - - # Status bar for memory info - self.statusBar().showMessage("Ready to rock! 🚀") - - def setup_memory_monitor(self): - """Sets up our memory watchdog - like having a friendly bouncer at a club!""" - self.memory_timer = QTimer() - self.memory_timer.timeout.connect(self.check_memory_usage) - self.memory_timer.start(5000) # Check every 5 seconds - - def check_memory_usage(self): - """Keeps an eye on our memory usage - like watching your snack supplies!""" - process = psutil.Process() - memory_use = process.memory_info().rss / 1024 / 1024 # Convert to MB - self.statusBar().showMessage(f"Memory usage: {memory_use:.1f}MB | Cache size: {len(self.image_cache)} images") - - # If we're using too much memory, let's clean up - if memory_use > 500: # 500MB threshold - self.cleanup_cache() - gc.collect() # Tell Python to take out the trash - - def cleanup_cache(self): - """Cleans up our image cache - like tidying your room but for computer memory!""" - if len(self.image_cache) > self.max_cache_size: - # Find images we haven't looked at in a while - current_images = sorted( - self.image_cache.items(), - key=lambda x: x[1].last_accessed - )[:-self.max_cache_size] - - # Forget about them (but keep the size info!) - for path, info in current_images: - if path != self.current_image_path: - info.pixmap = None - - def safe_show_image(self, item): - """Shows an image safely - like carefully opening a precious photo album!""" - try: - image_path = item.text() - self.current_image_path = image_path - - # Load or retrieve image info - if image_path not in self.image_cache: - self.load_image_info(image_path) - elif self.image_cache[image_path].pixmap is None: - self.load_image_info(image_path) - - # Update the display - self.update_image_display() - self.load_caption(image_path) - - except Exception as e: - print(f"Oops! Something went wrong showing the image: {e}") - self.statusBar().showMessage("Had a little trouble with that image 😅") - - def load_image_info(self, path: str): - """Loads image information carefully - like reading a delicate old book!""" - try: - with Image.open(path) as img: - # Handle different image types - if img.mode in ('RGBA', 'LA'): - background = Image.new('RGB', img.size, 'WHITE') - background.paste(img, mask=img.split()[-1]) - img = background - elif img.mode != 'RGB': - img = img.convert('RGB') - - # Convert to Qt - qim = ImageQt.ImageQt(img) - pixmap = QPixmap.fromImage(QImage(qim)) - - # Store the info - self.image_cache[path] = ImageInfo( - path=path, - width=img.width, - height=img.height, - pixmap=pixmap - ) - except Exception as e: - print(f"Couldn't load image {path}: {e}") - self.statusBar().showMessage("That image was a bit camera shy 📸") - - def update_image_display(self): - """Updates the image display - like adjusting a picture frame!""" - if not self.current_image_path or self.current_image_path not in self.image_cache: - return - - info = self.image_cache[self.current_image_path] - if info.pixmap: - # Scale it to fit - viewport_size = self.scroll_area.viewport().size() - scaled_pixmap = info.pixmap.scaled( - viewport_size, - Qt.AspectRatioMode.KeepAspectRatio, - Qt.TransformationMode.SmoothTransformation - ) - self.image_preview.setPixmap(scaled_pixmap) - - def load_images(self): - """Loads images from the current folder - like browsing a photo album!""" - try: - self.progress_bar.show() - files = [f for f in os.listdir() if f.lower().endswith(('.png', '.jpg', '.jpeg'))] - self.progress_bar.setMaximum(len(files)) - - for i, file in enumerate(files): - self.image_list.addItem(file) - self.progress_bar.setValue(i + 1) - - self.progress_bar.hide() - except Exception as e: - print(f"Couldn't load images: {e}") - self.statusBar().showMessage("Had some trouble finding images 🔍") - - def safe_save_text(self): - """Saves captions safely - like writing in your diary with care!""" - try: - if not self.current_image_path: - return - - text_path = os.path.splitext(self.current_image_path)[0] + '.txt' - with open(text_path, 'w', encoding='utf-8') as f: - f.write(self.text_edit.toPlainText()) - - self.save_button.setText("✅ Saved!") - QTimer.singleShot(2000, lambda: self.save_button.setText("💾 Save Caption")) - - except Exception as e: - print(f"Couldn't save caption: {e}") - self.statusBar().showMessage("Oops, had trouble saving that caption 📝") - - def load_caption(self, image_path: str): - """Loads captions - like reading notes from a friend!""" - try: - text_path = os.path.splitext(image_path)[0] + '.txt' - if os.path.exists(text_path): - with open(text_path, 'r', encoding='utf-8') as f: - self.text_edit.setText(f.read()) - else: - self.text_edit.clear() - except Exception as e: - print(f"Couldn't load caption: {e}") - self.statusBar().showMessage("That caption was playing hide and seek 🙈") - - def resizeEvent(self, event): - """Handles window resizing - like adjusting your glasses to see better!""" - super().resizeEvent(event) - self.update_image_display() - -if __name__ == '__main__': - app = QApplication(sys.argv) - window = MemoryFriendlyViewer() - window.show() - sys.exit(app.exec()) diff --git a/future toys/themed-viewer-setup.py b/future toys/themed-viewer-setup.py deleted file mode 100644 index 01b951f..0000000 --- a/future toys/themed-viewer-setup.py +++ /dev/null @@ -1,82 +0,0 @@ -class ThemeManager: - """Manages our fancy game-inspired themes! ✨""" - - def __init__(self): - # Base path for theme resources - self.theme_path = Path("themes") - - # Theme-specific style templates - self.style_templates = { - "nier": """ - QMainWindow { - background-image: url(themes/nier/background.png); - background-repeat: no-repeat; - background-position: center; - } - - QListWidget { - background-color: rgba(255, 255, 255, 0.1); - border: 1px solid rgba(255, 255, 255, 0.3); - border-radius: 5px; - } - - QPushButton { - background-color: rgba(200, 200, 200, 0.1); - border: 1px solid rgba(255, 255, 255, 0.5); - color: #ffffff; - padding: 5px 15px; - font-family: 'Arial'; /* We can change this to match Nier's font */ - } - - QPushButton:hover { - background-color: rgba(255, 255, 255, 0.2); - } - - QLabel#imageLabel { - border: 2px solid rgba(255, 255, 255, 0.3); - background-color: rgba(0, 0, 0, 0.2); - } - """, - - "ffxiv": """ - QMainWindow { - background-image: url(themes/ffxiv/background.png); - border: 2px solid #8b7355; - } - - QListWidget { - background-color: rgba(20, 20, 20, 0.8); - border: 1px solid #8b7355; - color: #e0c9a6; - } - - QPushButton { - background-color: #2a2117; - border: 1px solid #8b7355; - color: #e0c9a6; - border-radius: 3px; - } - - QPushButton:hover { - background-color: #3d3122; - border: 1px solid #c4a977; - } - """ - } - - def load_theme_resources(self, theme_name: str): - """Loads all our pretty theme elements! 🎨""" - theme_folder = self.theme_path / theme_name - resources = { - "background": theme_folder / "background.png", - "button_normal": theme_folder / "button.png", - "button_hover": theme_folder / "button_hover.png", - "frame": theme_folder / "frame.png", - "loading": theme_folder / "loading.gif" # For our fancy loading animation! - } - return resources - - def apply_theme(self, widget, theme_name: str): - """Makes everything look gorgeous! ✨""" - if theme_name in self.style_templates: - widget.setStyleSheet(self.style_templates[theme_name]) diff --git a/icon.png b/icon.png deleted file mode 100644 index 699ef9b..0000000 Binary files a/icon.png and /dev/null differ diff --git a/main.py b/main.py new file mode 100644 index 0000000..8e396c2 --- /dev/null +++ b/main.py @@ -0,0 +1,9 @@ +import sys +from PyQt6.QtWidgets import QApplication +from ui import MainWindow # Import our main window class + +if __name__ == "__main__": + app = QApplication(sys.argv) + window = MainWindow() # Initialize our main window. + window.show() + sys.exit(app.exec()) \ No newline at end of file diff --git a/metadata_parser.py b/metadata_parser.py new file mode 100644 index 0000000..0e6fbf0 --- /dev/null +++ b/metadata_parser.py @@ -0,0 +1,94 @@ +import png +import zlib +import json +import re +def parse_metadata(file_path): + metadata = {} + formatted_text = "" + try: + with open(file_path, "rb") as f: + png_data = f.read() + reader = png.Reader(bytes=png_data) + chunks = reader.chunks() + + text_chunk = b"" + for chunk_name, chunk_data in chunks: + if chunk_name == b"tEXt" or chunk_name == b"iTXt": + try: + if chunk_name == b"tEXt": + key, value = chunk_data.split(b"\x00", 1) + elif chunk_name == b"iTXt": + key, compression_method, _, value = chunk_data.split(b"\x00", 3) + if compression_method == b'\x00': + # Try zlib decompression only if needed + try: + value = zlib.decompress(value) + except Exception as e: + print("Error decompressing: ", e) + continue; # skip the rest of this loop iteration, and try the next one + if key == b"parameters": + text_chunk = value + break # stop when you find it + except Exception as e: + print("Error reading chunk: ", e) + + if text_chunk: + try: + # Try to decode the value, it may contain utf8 or other encodings + text = text_chunk.decode('utf-8', errors='ignore') + print(f"Decoded text: {text}") + except Exception as e: + print("Error decoding: ", e) + return formatted_text + try: + # Regex to find the prompt and the json string: + m = re.search(r"^(.*?)?(?:Negative prompt: (.*?))?(?:\nSteps: (.*?))?(?:\nSampler: (.*?))?(?:\nSchedule type: (.*?))?(?:\nCFG scale: (.*?))?(?:\nSeed: (.*?))?(?:\nSize: (.*?))?(?:\nModel hash: (.*?))?(?:\nModel: (.*?))?(?:\nVAE hash: (.*?))?(?:\nVAE: (.*?))?(?:\nDenoising strength: (.*?))?(?:\nClip skip: (.*?))?(?:\nHashes: (.*?))?(?:\n.*)?$", text, re.MULTILINE) + if m is not None: + + formatted_text = "" + if m.group(1): + formatted_text += f"prompt: {m.group(1).strip()}\n" + if m.group(2): + formatted_text += f"negative_prompt: {m.group(2).strip()}\n" + if m.group(3): + formatted_text += f"steps: {m.group(3).strip()}\n" + if m.group(4): + formatted_text += f"sampler: {m.group(4).strip()}\n" + if m.group(5): + formatted_text += f"schedule_type: {m.group(5).strip()}\n" + if m.group(6): + formatted_text += f"cfg_scale: {m.group(6).strip()}\n" + if m.group(7): + formatted_text += f"seed: {m.group(7).strip()}\n" + if m.group(8): + formatted_text += f"size: {m.group(8).strip()}\n" + if m.group(9): + formatted_text += f"model_hash: {m.group(9).strip()}\n" + if m.group(10): + formatted_text += f"model: {m.group(10).strip()}\n" + if m.group(11): + formatted_text += f"vae_hash: {m.group(11).strip()}\n" + if m.group(12): + formatted_text += f"vae: {m.group(12).strip()}\n" + if m.group(13): + formatted_text += f"denoising_strength: {m.group(13).strip()}\n" + if m.group(14): + formatted_text += f"clip_skip: {m.group(14).strip()}\n" + if m.group(15): + formatted_text += f"hashes: {m.group(15).strip()}" + return formatted_text + except Exception as e: + print("Error parsing text", e) + try: + # try to load as json + metadata = json.loads(text) + formatted_text = "" + for key, value in metadata.items(): + formatted_text += f"{key}: {value}\n" + return formatted_text + + except Exception as e: + print("Error parsing json directly", e) + except Exception as e: + print("Error reading png file: ", e) + return formatted_text \ No newline at end of file diff --git a/old files for reference/Dataset-Tools-dev-claude.zip b/old files for reference/Dataset-Tools-dev-claude.zip deleted file mode 100644 index cbbc42f..0000000 Binary files a/old files for reference/Dataset-Tools-dev-claude.zip and /dev/null differ diff --git a/old files for reference/complete-stable-viewer.py b/old files for reference/complete-stable-viewer.py deleted file mode 100644 index 84bc9bb..0000000 --- a/old files for reference/complete-stable-viewer.py +++ /dev/null @@ -1,298 +0,0 @@ -import os -import sys -import json -import time -import psutil -from functools import lru_cache -from dataclasses import dataclass, asdict -from typing import Optional, Dict -from PyQt6.QtWidgets import ( - QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, - QLabel, QPlainTextEdit, QPushButton, QListWidget, QScrollArea, - QSizePolicy, QProgressBar, QStatusBar, QFileDialog, QListWidgetItem, - QSlider, QSpinBox, QCheckBox, QGroupBox, QMessageBox -) -from PyQt6.QtCore import Qt, QSize, QTimer -from PyQt6.QtGui import QPixmap, QIcon -from PIL import Image, ImageQt - -def check_requirements(): - """Makes sure we have all our tools before starting!""" - required_packages = { - 'PyQt6': 'PyQt6', - 'Pillow': 'PIL', - 'psutil': 'psutil' - } - - missing = [] - for package, import_name in required_packages.items(): - try: - __import__(import_name) - except ImportError: - missing.append(package) - - if missing: - print(f"Installing required packages: {', '.join(missing)}") - try: - import subprocess - for package in missing: - subprocess.check_call([sys.executable, '-m', 'pip', 'install', package]) - print("All set! Requirements installed successfully!") - except Exception as e: - print(f"Couldn't install requirements: {e}") - sys.exit(1) - -@dataclass -class ViewerConfig: - """Our app's preferences!""" - max_memory_mb: int = 500 - thumbnail_size: int = 100 - window_width: int = 1200 - window_height: int = 800 - - @classmethod - def load(cls, path: str = "viewer_config.json") -> 'ViewerConfig': - try: - if os.path.exists(path): - with open(path, 'r') as f: - return cls(**json.load(f)) - except Exception as e: - print(f"Using default settings: {e}") - return cls() - - def save(self, path: str = "viewer_config.json") -> None: - try: - with open(path, 'w') as f: - json.dump(asdict(self), f, indent=2) - except Exception as e: - print(f"Couldn't save settings: {e}") - -class DatasetViewer(QMainWindow): - def __init__(self): - super().__init__() - self.config = ViewerConfig.load() - self.setWindowTitle("Dataset Viewer") - self.setMinimumSize(self.config.window_width, self.config.window_height) - - # Initialize our trackers - self.current_folder = os.getcwd() - self.image_cache = {} - self.current_image = None - - self.init_ui() - self.setup_memory_monitor() - self.load_folder(self.current_folder) - - def init_ui(self): - """Setting up our workspace""" - main_widget = QWidget() - self.setCentralWidget(main_widget) - layout = QVBoxLayout(main_widget) - - # Folder controls - folder_layout = QHBoxLayout() - self.folder_button = QPushButton("📁 Choose Folder") - self.folder_button.clicked.connect(self.choose_folder) - self.status_label = QLabel("Ready!") - folder_layout.addWidget(self.folder_button) - folder_layout.addWidget(self.status_label, stretch=1) - - # Progress tracking - self.progress_bar = QProgressBar() - self.progress_bar.hide() - - # Main content area - content_layout = QHBoxLayout() - - # Left side: Image list - left_panel = QWidget() - left_layout = QVBoxLayout(left_panel) - self.file_list = QListWidget() - self.file_list.setIconSize(QSize(self.config.thumbnail_size, - self.config.thumbnail_size)) - self.file_list.itemClicked.connect(self.safe_load_image) - left_layout.addWidget(self.file_list) - - # Center: Image preview - self.scroll_area = QScrollArea() - self.scroll_area.setWidgetResizable(True) - self.image_label = QLabel() - self.image_label.setAlignment(Qt.AlignmentFlag.AlignCenter) - self.image_label.setSizePolicy( - QSizePolicy.Policy.Expanding, - QSizePolicy.Policy.Expanding - ) - self.scroll_area.setWidget(self.image_label) - - # Right: Text editor - right_panel = QWidget() - right_layout = QVBoxLayout(right_panel) - self.text_edit = QPlainTextEdit() - self.save_button = QPushButton("💾 Save") - self.save_button.clicked.connect(self.safe_save_text) - right_layout.addWidget(QLabel("Caption:")) - right_layout.addWidget(self.text_edit) - right_layout.addWidget(self.save_button) - - # Put it all together - content_layout.addWidget(left_panel, stretch=1) - content_layout.addWidget(self.scroll_area, stretch=2) - content_layout.addWidget(right_panel, stretch=1) - - layout.addLayout(folder_layout) - layout.addWidget(self.progress_bar) - layout.addLayout(content_layout) - - self.statusBar().showMessage("Ready!") - - def setup_memory_monitor(self): - """Sets up our memory watchdog""" - self.memory_timer = QTimer() - self.memory_timer.timeout.connect(self.check_memory) - self.memory_timer.start(5000) # Check every 5 seconds - - def check_memory(self): - """Keeps an eye on memory usage""" - process = psutil.Process() - memory_use = process.memory_info().rss / 1024 / 1024 - self.statusBar().showMessage(f"Memory: {memory_use:.1f}MB") - - if memory_use > self.config.max_memory_mb: - self.cleanup_cache() - - def choose_folder(self): - """Opens folder picker""" - folder = QFileDialog.getExistingDirectory( - self, - "Choose Folder", - self.current_folder - ) - if folder: - self.current_folder = folder - self.cleanup_cache() - self.load_folder(folder) - - def load_folder(self, folder_path): - """Loads images from selected folder""" - self.progress_bar.show() - self.file_list.clear() - - try: - files = [f for f in os.listdir(folder_path) - if f.lower().endswith(('.png', '.jpg', '.jpeg'))] - - self.progress_bar.setMaximum(len(files)) - - for i, file in enumerate(files): - image_path = os.path.join(folder_path, file) - thumb = self.create_thumbnail(image_path) - if thumb: - item = QListWidgetItem(QIcon(thumb), file) - self.file_list.addItem(item) - self.progress_bar.setValue(i + 1) - - self.progress_bar.hide() - self.status_label.setText(f"Found {len(files)} images") - - except Exception as e: - self.progress_bar.hide() - QMessageBox.warning(self, "Oops!", f"Trouble loading folder: {e}") - - def safe_load_image(self, item): - """Safely loads selected image""" - image_path = os.path.join(self.current_folder, item.text()) - - try: - with Image.open(image_path) as img: - if img.mode in ('RGBA', 'LA'): - background = Image.new('RGB', img.size, 'WHITE') - background.paste(img, mask=img.split()[-1]) - img = background - elif img.mode != 'RGB': - img = img.convert('RGB') - - pixmap = QPixmap.fromImage(ImageQt.ImageQt(img)) - scaled = pixmap.scaled( - self.scroll_area.viewport().size(), - Qt.AspectRatioMode.KeepAspectRatio, - Qt.TransformationMode.SmoothTransformation - ) - self.image_label.setPixmap(scaled) - self.current_image = image_path - self.load_text(image_path) - - except Exception as e: - QMessageBox.warning(self, "Oops!", f"Trouble loading image: {e}") - - @lru_cache(maxsize=100) - def create_thumbnail(self, image_path): - """Creates thumbnails efficiently""" - try: - with Image.open(image_path) as img: - if img.mode in ('RGBA', 'LA'): - background = Image.new('RGB', img.size, 'WHITE') - background.paste(img, mask=img.split()[-1]) - img = background - elif img.mode != 'RGB': - img = img.convert('RGB') - - size = self.config.thumbnail_size - ratio = min(size/img.width, size/img.height) - new_size = (int(img.width * ratio), int(img.height * ratio)) - thumb = img.resize(new_size, Image.Resampling.LANCZOS) - - return QPixmap.fromImage(ImageQt.ImageQt(thumb)) - except Exception as e: - print(f"Thumbnail creation error for {image_path}: {e}") - return None - - def load_text(self, image_path): - """Loads caption if it exists""" - text_path = os.path.splitext(image_path)[0] + '.txt' - try: - if os.path.exists(text_path): - with open(text_path, 'r', encoding='utf-8') as f: - self.text_edit.setPlainText(f.read()) - else: - self.text_edit.clear() - except Exception as e: - QMessageBox.warning(self, "Oops!", f"Trouble loading caption: {e}") - - def safe_save_text(self): - """Safely saves caption text""" - if not self.current_image: - return - - text_path = os.path.splitext(self.current_image)[0] + '.txt' - try: - with open(text_path, 'w', encoding='utf-8') as f: - f.write(self.text_edit.toPlainText()) - - self.save_button.setText("✅ Saved!") - QTimer.singleShot(2000, lambda: self.save_button.setText("💾 Save")) - - except Exception as e: - QMessageBox.warning(self, "Oops!", f"Trouble saving caption: {e}") - - def cleanup_cache(self): - """Cleans up memory when needed""" - self.image_cache.clear() - self.create_thumbnail.cache_clear() - - def resizeEvent(self, event): - """Handles window resizing""" - super().resizeEvent(event) - if self.current_image and self.image_label.pixmap(): - scaled = self.image_label.pixmap().scaled( - self.scroll_area.viewport().size(), - Qt.AspectRatioMode.KeepAspectRatio, - Qt.TransformationMode.SmoothTransformation - ) - self.image_label.setPixmap(scaled) - -if __name__ == '__main__': - check_requirements() - app = QApplication(sys.argv) - window = DatasetViewer() - window.show() - sys.exit(app.exec()) diff --git a/sample images/Screenshot 2024-12-02 at 21.32.09.png b/sample images/Screenshot 2024-12-02 at 21.32.09.png deleted file mode 100644 index ffe2cab..0000000 Binary files a/sample images/Screenshot 2024-12-02 at 21.32.09.png and /dev/null differ diff --git a/sample images/Screenshot 2024-12-02 at 21.34.51.png b/sample images/Screenshot 2024-12-02 at 21.34.51.png deleted file mode 100644 index 945ce02..0000000 Binary files a/sample images/Screenshot 2024-12-02 at 21.34.51.png and /dev/null differ diff --git a/sample images/Screenshot 2024-12-02 at 21.35.02.png b/sample images/Screenshot 2024-12-02 at 21.35.02.png deleted file mode 100644 index 8dfc91e..0000000 Binary files a/sample images/Screenshot 2024-12-02 at 21.35.02.png and /dev/null differ diff --git a/ui.py b/ui.py new file mode 100644 index 0000000..e48ddd8 --- /dev/null +++ b/ui.py @@ -0,0 +1,225 @@ +from PyQt6.QtWidgets import ( + QMainWindow, + QWidget, + QVBoxLayout, + QHBoxLayout, + QPushButton, + QLabel, + QFileDialog, + QProgressBar, + QListWidget, + QAbstractItemView, + QTextEdit +) +from PyQt6.QtCore import Qt, QThread, pyqtSignal +from PyQt6.QtGui import QFont, QPixmap +from widgets import FileLoader +import os +import imghdr +from metadata_parser import parse_metadata +import json # import json here +import re # import re here + +class MainWindow(QMainWindow): + def __init__(self): + super().__init__() + + # Set a default font for the app + # app_font = QFont("Arial", 12) + # self.setFont(app_font) + + self.setWindowTitle("Dataset Viewer") + self.setGeometry(100, 100, 800, 600) # x, y, width, height + self.setMinimumSize(800, 600) # set minimum size for standard window. + + # Central widget to hold our layout + central_widget = QWidget() + self.setCentralWidget(central_widget) + + # Main layout + main_layout = QHBoxLayout(central_widget) + + # Left panel layout + left_panel = QWidget() + left_layout = QVBoxLayout(left_panel) + main_layout.addWidget(left_panel) + + # Folder Path Label + self.current_folder_label = QLabel("Current Folder: None") + left_layout.addWidget(self.current_folder_label) + + # Placeholder UI + self.open_folder_button = QPushButton("Open Folder") + self.open_folder_button.clicked.connect(self.open_folder) + left_layout.addWidget(self.open_folder_button) + + # Placeholder label, you can remove this later + self.message_label = QLabel("Select a folder!") + left_layout.addWidget(self.message_label) + + # File list (replaced QLabel with QListWidget) + self.files_list = QListWidget() + self.files_list.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection) + self.files_list.itemClicked.connect(self.on_file_selected) + left_layout.addWidget(self.files_list) + + # Add a progress bar for file loading + self.progress_bar = QProgressBar() + self.progress_bar.hide() + left_layout.addWidget(self.progress_bar) + + # Right panel Layout + right_panel = QWidget() + right_layout = QVBoxLayout(right_panel) + main_layout.addWidget(right_panel) + + # Image preview area + self.image_preview = QLabel() + self.image_preview.setAlignment(Qt.AlignmentFlag.AlignCenter) + self.image_preview.setMinimumHeight(300) + right_layout.addWidget(self.image_preview) + + + # Metadata Box + self.metadata_box = QTextEdit() + self.metadata_box.setReadOnly(True) + right_layout.addWidget(self.metadata_box) + + # Prompt Text + self.prompt_text = QLabel() + self.prompt_text.setText("Prompt Info will show here") + right_layout.addWidget(self.prompt_text) + + + # Text File Content Area + self.text_content = QTextEdit() + self.text_content.setReadOnly(True) + right_layout.addWidget(self.text_content) + + + self.file_loader = None + self.file_list = [] + self.image_list = [] + self.text_files = [] + self.current_folder = None + + def open_folder(self): + # Open a dialog to select the folder + folder_path = QFileDialog.getExistingDirectory(self, "Select a folder") + if folder_path: + # Call the file loading function + self.load_files(folder_path) + + def clear_files(self): + if self.file_loader: + self.file_loader.clear_files() + self.file_list = [] + self.image_list = [] + self.text_files = [] + self.files_list.clear() + self.image_preview.clear() + self.text_content.clear() + self.prompt_text.clear() + self.metadata_box.clear() + + def load_files(self, folder_path): + # Start background loading of files using QThread + self.current_folder = folder_path + self.current_folder_label.setText(f"Current Folder: {folder_path}") + self.message_label.setText("Loading files...") + self.progress_bar.setValue(0) + self.progress_bar.show() + + if self.file_loader: + self.file_loader.finished.disconnect() + self.file_loader = FileLoader(folder_path) + self.file_loader.progress.connect(self.update_progress) + self.file_loader.finished.connect(self.on_files_loaded) + self.file_loader.start() + + def update_progress(self, progress): + # Update progress bar + self.progress_bar.setValue(progress) + + def on_files_loaded(self, image_list, text_files, loaded_folder): + if self.current_folder != loaded_folder: + # We are loading files from a different folder + # than what's currently selected, so we need to ignore this. + return + self.image_list = image_list + self.text_files = text_files + # update the message and hide the loading bar + self.message_label.setText(f"Loaded {len(self.image_list)} images and {len(self.text_files)} text files") + self.progress_bar.hide() + + # Clear and populate the QListWidget + self.files_list.clear() + self.files_list.addItems(self.image_list) + self.files_list.addItems(self.text_files) + + + def on_file_selected(self, item): + file_path = item.text() + self.message_label.setText(f"Selected {file_path}") + + # Clear any previous selection + self.image_preview.clear() + self.text_content.clear() + self.prompt_text.clear() + self.metadata_box.clear() + + if file_path.lower().endswith(('.png','.jpg','.jpeg','.webp')): + # Load the image + self.load_image_preview(file_path) + self.load_metadata(file_path) + + if file_path.lower().endswith('.txt'): + # Load the text file + self.load_text_file(file_path) + + def load_metadata(self, file_path): + metadata = None + try: + if imghdr.what(file_path) == 'png': + metadata = parse_metadata(file_path) + elif imghdr.what(file_path) in ['jpeg', 'jpg']: + # Add support for jpeg here later. + print("no metadata support for jpeg yet") + metadata = None + else: + metadata = None + except Exception as e: + print("Error loading metadata:", e) + metadata = None + + if metadata is not None: + self.metadata_box.setText(metadata) + + # check if the formatted metadata contains `prompt`, otherwise ignore it. + if "prompt:" in metadata: + try: + # search for the string, and return it. + prompt_regex = re.search(r"prompt: (.*?)\n", metadata) + if prompt_regex is not None: + self.prompt_text.setText(prompt_regex.group(1)) + except Exception as e: + print("Error getting prompt: ", e) + else: + self.prompt_text.setText("No Prompt found") + else: + self.metadata_box.setText("No metadata found") + self.prompt_text.setText("No Prompt found") + + def load_image_preview(self, file_path): + # load image file + pixmap = QPixmap(file_path) + # scale the image + self.image_preview.setPixmap(pixmap.scaled(self.image_preview.size(), Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation)) + + def load_text_file(self, file_path): + try: + with open(file_path, 'r') as f: + content = f.read() + self.text_content.setText(content) + except Exception as e: + self.text_content.setText(f"Error loading text file: {e}") \ No newline at end of file diff --git a/widgets.py b/widgets.py new file mode 100644 index 0000000..186b435 --- /dev/null +++ b/widgets.py @@ -0,0 +1,43 @@ +import os +from PyQt6.QtCore import QThread, pyqtSignal + + +class FileLoader(QThread): + finished = pyqtSignal(list, list, str) + progress = pyqtSignal(int) + + def __init__(self, folder_path): + super().__init__() + self.folder_path = folder_path + self.files = [] + self.images = [] + self.text_files = [] + + def run(self): + self.images, self.text_files = self._scan_directory(self.folder_path) + self.finished.emit(self.images, self.text_files, self.folder_path) + + def _scan_directory(self, folder_path): + files = [] + images = [] + text_files = [] + # Gather paths to all files in the selected folder + try: + all_files = [os.path.join(folder_path, f) for f in os.listdir(folder_path)] + except FileNotFoundError: + return images, text_files + total_files = len(all_files) + progress = 0 + for index, file_path in enumerate(all_files): + if os.path.isfile(file_path): + # Filter the file types as needed + if file_path.lower().endswith(('.png','.jpg','.jpeg','.webp')): + images.append(file_path) + if file_path.lower().endswith(('.txt')): + text_files.append(file_path) + progress = (index + 1)/total_files * 100 + self.progress.emit(int(progress)) + return images, text_files + + def clear_files(self): + self.files = [] \ No newline at end of file