ElBeh's picture
Upload image_processing.py
b599026 verified
# image_processing.py
from PIL import Image
import numpy as np
import cv2
from io import BytesIO
import matplotlib.pyplot as plt
import matplotlib.cm as cm
try:
import pywt
PYWT_AVAILABLE = True
except ImportError:
PYWT_AVAILABLE = False
def laplacian_highpass(img):
"""Applies Laplacian high-pass filter to emphasize high frequencies."""
arr = np.array(img)
# Convert to grayscale if needed
if len(arr.shape) == 3:
gray = cv2.cvtColor(arr, cv2.COLOR_RGB2GRAY)
else:
gray = arr
# Apply Laplacian
laplacian = cv2.Laplacian(gray, cv2.CV_64F, ksize=3)
# Normalize to 0-255 (fixed for NumPy 2.x)
laplacian_norm = np.absolute(laplacian)
if np.max(laplacian_norm) > 0:
laplacian_norm = (laplacian_norm / np.max(laplacian_norm)) * 255.0
laplacian_norm = np.clip(laplacian_norm, 0, 255).astype(np.uint8)
else:
laplacian_norm = np.zeros_like(laplacian_norm, dtype=np.uint8)
return Image.fromarray(laplacian_norm)
def fft_spectrum(img):
"""Computes 2D FFT and visualizes log-scaled magnitude spectrum with viridis colormap."""
arr = np.array(img)
# Convert to grayscale
if len(arr.shape) == 3:
gray = cv2.cvtColor(arr, cv2.COLOR_RGB2GRAY)
else:
gray = arr
# Compute FFT
f_transform = np.fft.fft2(gray)
f_shift = np.fft.fftshift(f_transform)
# Magnitude spectrum (log scale)
magnitude_spectrum = np.abs(f_shift)
magnitude_spectrum = np.log1p(magnitude_spectrum)
# Normalize to 0-1 (fixed for NumPy 2.x)
if np.max(magnitude_spectrum) > 0:
magnitude_spectrum = magnitude_spectrum / np.max(magnitude_spectrum)
else:
magnitude_spectrum = np.zeros_like(magnitude_spectrum)
# Apply viridis colormap (blue-green-yellow)
viridis = cm.get_cmap('viridis')
colored = viridis(magnitude_spectrum) # Returns RGBA in range 0-1
# Convert to RGB (remove alpha channel) and scale to 0-255
rgb = (colored[:, :, :3] * 255).astype(np.uint8)
return Image.fromarray(rgb)
def error_level_analysis(img, quality=90):
"""Performs Error Level Analysis via JPEG re-compression."""
arr = np.array(img)
# Save with specified quality
buffer = BytesIO()
Image.fromarray(arr).save(buffer, format='JPEG', quality=int(quality))
buffer.seek(0)
# Reload compressed image
compressed_img = Image.open(buffer)
compressed_arr = np.array(compressed_img)
# Compute difference
diff = cv2.absdiff(arr, compressed_arr)
# Enhance differences
diff = cv2.multiply(diff, 10)
# Convert to grayscale if color
if len(diff.shape) == 3:
diff = cv2.cvtColor(diff, cv2.COLOR_RGB2GRAY)
return Image.fromarray(diff)
def wavelet_decomposition(img):
"""Decomposes image into wavelet subbands with proper visualization"""
if not PYWT_AVAILABLE:
# Fallback: return grayscale
arr = np.array(img)
if len(arr.shape) == 3:
gray = cv2.cvtColor(arr, cv2.COLOR_RGB2GRAY)
else:
gray = arr
return Image.fromarray(gray)
arr = np.array(img)
# Convert to grayscale
if len(arr.shape) == 3:
gray = cv2.cvtColor(arr, cv2.COLOR_RGB2GRAY)
else:
gray = arr
# Perform 2D wavelet decomposition
coeffs = pywt.dwt2(gray, 'haar')
LL, (LH, HL, HH) = coeffs
# Different normalization for LL vs high-frequency bands
def normalize_lowfreq(band):
"""Standard normalization for LL (approximation)"""
if np.max(band) > 0:
band = (band / np.max(band)) * 255.0
return np.clip(band, 0, 255).astype(np.uint8)
return np.zeros_like(band, dtype=np.uint8)
def normalize_highfreq(band, amplification=30):
"""Amplified normalization for high-frequency details"""
band = np.abs(band)
# Amplify before normalization
band = band * amplification
# Clip to prevent overflow
band = np.clip(band, 0, 255)
# Normalize to full range
if np.max(band) > 0:
band = (band / np.max(band)) * 255.0
return band.astype(np.uint8)
return np.zeros_like(band, dtype=np.uint8)
LL_norm = normalize_lowfreq(LL)
LH_norm = normalize_highfreq(LH) # Horizontal edges
HL_norm = normalize_highfreq(HL) # Vertical edges
HH_norm = normalize_highfreq(HH) # Diagonal edges/noise
# Combine into single image (2x2 grid)
top = np.hstack([LL_norm, LH_norm])
bottom = np.hstack([HL_norm, HH_norm])
combined = np.vstack([top, bottom])
return Image.fromarray(combined)
def noise_extraction(img):
"""Extracts and amplifies noise via high-pass filter."""
arr = np.array(img)
# Convert to grayscale
if len(arr.shape) == 3:
gray = cv2.cvtColor(arr, cv2.COLOR_RGB2GRAY)
else:
gray = arr
# Apply strong Gaussian blur
blurred = cv2.GaussianBlur(gray, (15, 15), 0)
# Subtract to get high-frequency content (noise)
noise = cv2.subtract(gray, blurred)
# Amplify noise
noise = cv2.multiply(noise, 5)
return Image.fromarray(noise)
def ycbcr_channels(img):
"""Converts to YCbCr and visualizes chrominance channels."""
arr = np.array(img)
# Convert RGB to YCbCr
if len(arr.shape) == 3:
ycbcr = cv2.cvtColor(arr, cv2.COLOR_RGB2YCrCb)
# Extract channels
Y, Cr, Cb = cv2.split(ycbcr)
# Create combined visualization (Y on top, Cr and Cb side by side below)
h, w = Y.shape
# Resize Cr and Cb to half width
Cr_resized = cv2.resize(Cr, (w//2, h//2))
Cb_resized = cv2.resize(Cb, (w//2, h//2))
# Combine
bottom = np.hstack([Cr_resized, Cb_resized])
Y_resized = cv2.resize(Y, (w, h//2))
combined = np.vstack([Y_resized, bottom])
return Image.fromarray(combined)
else:
return Image.fromarray(arr)
def gradient_magnitude(img):
"""Computes gradient magnitude using Sobel operator."""
arr = np.array(img)
# Convert to grayscale
if len(arr.shape) == 3:
gray = cv2.cvtColor(arr, cv2.COLOR_RGB2GRAY)
else:
gray = arr
# Compute gradients
grad_x = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=3)
grad_y = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=3)
# Compute magnitude
magnitude = np.sqrt(grad_x**2 + grad_y**2)
# Normalize to 0-255 (fixed for NumPy 2.x)
if np.max(magnitude) > 0:
magnitude = (magnitude / np.max(magnitude)) * 255.0
magnitude = np.clip(magnitude, 0, 255).astype(np.uint8)
else:
magnitude = np.zeros_like(magnitude, dtype=np.uint8)
return Image.fromarray(magnitude)
def histogram_stretching(img):
"""Applies CLAHE for adaptive contrast enhancement"""
arr = np.array(img)
if len(arr.shape) == 3:
# Convert to LAB color space
lab = cv2.cvtColor(arr, cv2.COLOR_RGB2LAB)
l, a, b = cv2.split(lab)
# Apply CLAHE to L channel
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
l_clahe = clahe.apply(l)
# Merge and convert back
lab_clahe = cv2.merge([l_clahe, a, b])
result = cv2.cvtColor(lab_clahe, cv2.COLOR_LAB2RGB)
return Image.fromarray(result)
else:
# For grayscale
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
result = clahe.apply(arr)
return Image.fromarray(result)
def process_image(slider_input, transformation, quality=90):
"""Applies the selected transformation."""
# Extract image from slider input
if slider_input is None:
return None
# If it's a tuple, take the first image
if isinstance(slider_input, tuple):
img = slider_input[0]
else:
img = slider_input
if img is None:
return None
# Select the corresponding function
transform_functions = {
"Laplacian High-Pass": laplacian_highpass,
"FFT Spectrum": fft_spectrum,
"Error Level Analysis": lambda img: error_level_analysis(img, quality),
"Wavelet Decomposition": wavelet_decomposition,
"Noise Extraction": noise_extraction,
"YCbCr Channels": ycbcr_channels,
"Gradient Magnitude": gradient_magnitude,
"Histogram Stretching": histogram_stretching,
"None": lambda img: img # Add None transformation
}
transform_func = transform_functions.get(transformation)
if transform_func is None:
return (img, img)
transformed = transform_func(img)
# Return as tuple for ImageSlider
return (img, transformed)