Source code for image_functions.image_functions

"""
Image functions/utilities.
"""

import os
import hashlib
import tempfile
import subprocess
from PIL import Image, ImageChops
import cv2
from skimage.metrics import structural_similarity as ssim
import uuid
from typing import Optional

[docs] def sha256_hash(filepath: str) -> str: """Compute the SHA-256 hash of a file.""" with open(filepath, "rb") as f: return hashlib.sha256(f.read()).hexdigest()
[docs] def compare_images(image1_path: str, image2_path: str) -> None: """ Compare two images using multiple techniques: - SHA-256 hash comparison (byte-level) - Pixel-by-pixel comparison using Pillow (ImageChops) - Structural Similarity Index (SSIM) using scikit-image - ImageMagick 'compare' with Absolute Error (AE) metric Outputs detailed results and stores an ImageMagick diff image in a unique temporary folder based on a UUID. Parameters: image1_path (str): Path to the first image. image2_path (str): Path to the second image. """ print(f"🔍 Comparing:\n Image 1: {image1_path}\n Image 2: {image2_path}\n") # --- Hash comparison --- hash1: str = sha256_hash(image1_path) hash2: str = sha256_hash(image2_path) print(f"🔐 Hash 1: {hash1}") print(f"🔐 Hash 2: {hash2}") print(f"📢 Hash message: {'✅ Hashes match' if hash1 == hash2 else '❌ Hashes differ'}\n") # --- ImageChops pixel difference --- img1: Image.Image = Image.open(image1_path) img2: Image.Image = Image.open(image2_path) print(f"🖼️ Image resolution: {img1.width}×{img1.height}") if img1.size != img2.size or img1.mode != img2.mode: print("⚠️ ImageChops skipped: images differ in size or mode\n") else: diff_bbox: Optional[tuple[int, int, int, int]] = ImageChops.difference(img1, img2).getbbox() print(f"🧮 ImageChops.difference(img1, img2).getbbox(): {diff_bbox}") if diff_bbox is None: print("📢 ImageChops message: ✅ Images are pixel-perfect identical\n") else: bbox_width: int = diff_bbox[2] - diff_bbox[0] bbox_height: int = diff_bbox[3] - diff_bbox[1] print(f"📢 ImageChops message: ❌ Images differ within bounding box {diff_bbox}, covering {bbox_width}×{bbox_height} pixels\n") # --- SSIM comparison --- img1_gray = cv2.imread(image1_path, cv2.IMREAD_GRAYSCALE) img2_gray = cv2.imread(image2_path, cv2.IMREAD_GRAYSCALE) if img1_gray.shape != img2_gray.shape: img2_gray = cv2.resize(img2_gray, (img1_gray.shape[1], img1_gray.shape[0])) ssim_score: float ssim_score, _ = ssim(img1_gray, img2_gray, full=True) print(f"📏 SSIM score: {ssim_score:.4f}") if ssim_score == 1.0: print("📢 SSIM message: ✅ Visually identical\n") elif ssim_score > 0.95: print("📢 SSIM message: 👍 Very similar\n") else: print("📢 SSIM message: ❌ Visually different\n") # --- ImageMagick metric comparison --- unique_id: str = uuid.uuid4().hex output_dir: str = os.path.join(tempfile.gettempdir(), unique_id) os.makedirs(output_dir, exist_ok=True) temp_diff_path: str = os.path.join(output_dir, "imagemagick_diff_output.png") try: result = subprocess.run( ["magick", "compare", "-metric", "AE", image1_path, image2_path, temp_diff_path], stderr=subprocess.PIPE, stdout=subprocess.PIPE, text=True, check=True ) # This is rare — identical images may not trigger an error metric_output: str = result.stderr.strip() except FileNotFoundError: print("❌ Error: ImageMagick 'magick' command not found. Make sure ImageMagick is installed and in your PATH.\n") return except subprocess.CalledProcessError as e: metric_output = e.stderr.strip() print(f"📊 ImageMagick metric (Absolute Error): {metric_output}") if metric_output.strip().startswith("0"): print("🗑️ ImageMagick message: ✅ No difference detected — skipping diff image output\n") else: print(f"📁 ImageMagick difference stored in: {temp_diff_path}") print("📢 ImageMagick message: ❌ Difference detected\n")