eho69/image-preprocessing
1
1import gradio as gr2import cv23import numpy as np4from PIL import Image5import matplotlib.pyplot as plt6import io7 8def create_histogram(image, title="Histogram"):9 """Create histogram for grayscale or RGB image"""10 fig, ax = plt.subplots(figsize=(8, 4))11 12 if len(image.shape) == 2: # Grayscale13 hist = cv2.calcHist([image], [0], None, [256], [0, 256])14 ax.plot(hist, color='black')15 ax.set_xlim([0, 256])16 ax.set_xlabel('Pixel Intensity')17 ax.set_ylabel('Frequency')18 ax.set_title(title)19 ax.grid(True, alpha=0.3)20 else: # RGB21 colors = ('r', 'g', 'b')22 for i, color in enumerate(colors):23 hist = cv2.calcHist([image], [i], None, [256], [0, 256])24 ax.plot(hist, color=color, label=color.upper())25 ax.set_xlim([0, 256])26 ax.set_xlabel('Pixel Intensity')27 ax.set_ylabel('Frequency')28 ax.set_title(title)29 ax.legend()30 ax.grid(True, alpha=0.3)31 32 # Convert plot to image33 buf = io.BytesIO()34 plt.tight_layout()35 plt.savefig(buf, format='png', dpi=100, bbox_inches='tight')36 buf.seek(0)37 plot_image = Image.open(buf)38 plt.close()39 40 return np.array(plot_image)41 42 43def get_pixel_info(image, x, y):44 """Get detailed pixel information"""45 if image is None:46 return "No image loaded"47 48 h, w = image.shape[:2]49 if x < 0 or x >= w or y < 0 or y >= h:50 return "Click within image bounds"51 52 if len(image.shape) == 2: # Grayscale53 pixel_value = image[y, x]54 info = f"""55**Pixel Information at ({x}, {y})**56- **Gray Value**: {pixel_value}57- **Image Size**: {w} x {h}58"""59 else: # RGB60 b, g, r = image[y, x]61 info = f"""62**Pixel Information at ({x}, {y})**63- **RGB**: ({r}, {g}, {b})64- **Hex**: #{r:02x}{g:02x}{b:02x}65- **Image Size**: {w} x {h}66"""67 return info68 69 70def apply_clahe(image, clip_limit, tile_size):71 """Apply CLAHE with adjustable parameters"""72 if isinstance(image, Image.Image):73 image = np.array(image)74 75 # Convert to grayscale if RGB76 if len(image.shape) == 3:77 gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)78 else:79 gray = image80 81 # Apply CLAHE82 clahe = cv2.createCLAHE(clipLimit=clip_limit, tileGridSize=(tile_size, tile_size))83 result = clahe.apply(gray)84 85 # Create histograms86 hist_before = create_histogram(gray, "Histogram - Before CLAHE")87 hist_after = create_histogram(result, "Histogram - After CLAHE")88 89 # Convert back to RGB for display90 result_rgb = cv2.cvtColor(result, cv2.COLOR_GRAY2RGB)91 gray_rgb = cv2.cvtColor(gray, cv2.COLOR_GRAY2RGB)92 93 info = f"""94### CLAHE Applied95- **Clip Limit**: {clip_limit}96- **Tile Size**: {tile_size}x{tile_size}97- **Effect**: Enhances local contrast by equalizing histograms in small tiles98- **Use Case**: Improves visibility in shadowed or low-contrast regions99"""100 101 return result_rgb, hist_before, hist_after, info102 103 104def apply_gaussian_blur(image, kernel_size, sigma):105 """Apply Gaussian blur with adjustable parameters"""106 if isinstance(image, Image.Image):107 image = np.array(image)108 109 # Ensure kernel size is odd110 if kernel_size % 2 == 0:111 kernel_size += 1112 113 result = cv2.GaussianBlur(image, (kernel_size, kernel_size), sigma)114 115 info = f"""116### Gaussian Blur Applied117- **Kernel Size**: {kernel_size}x{kernel_size}118- **Sigma**: {sigma}119- **Effect**: Smooths image by averaging pixels with Gaussian weights120- **Use Case**: Noise reduction, preparing for edge detection121"""122 123 return result, info124 125 126def apply_bilateral_filter(image, diameter, sigma_color, sigma_space):127 """Apply bilateral filter with adjustable parameters"""128 if isinstance(image, Image.Image):129 image = np.array(image)130 131 # Convert to grayscale for processing132 if len(image.shape) == 3:133 gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)134 else:135 gray = image136 137 result = cv2.bilateralFilter(gray, diameter, sigma_color, sigma_space)138 139 # Convert back to RGB140 result_rgb = cv2.cvtColor(result, cv2.COLOR_GRAY2RGB)141 142 info = f"""143### Bilateral Filter Applied144- **Diameter**: {diameter}145- **Sigma Color**: {sigma_color}146- **Sigma Space**: {sigma_space}147- **Effect**: Edge-preserving smoothing filter148- **Use Case**: Noise reduction while keeping edges sharp149"""150 151 return result_rgb, info152 153 154def apply_median_filter(image, kernel_size):155 """Apply median filter"""156 if isinstance(image, Image.Image):157 image = np.array(image)158 159 # Ensure kernel size is odd160 if kernel_size % 2 == 0:161 kernel_size += 1162 163 result = cv2.medianBlur(image, kernel_size)164 165 info = f"""166### Median Filter Applied167- **Kernel Size**: {kernel_size}x{kernel_size}168- **Effect**: Replaces each pixel with median of surrounding pixels169- **Use Case**: Excellent for removing salt-and-pepper noise170"""171 172 return result, info173 174 175def apply_morphology(image, operation, kernel_size, iterations):176 """Apply morphological operations"""177 if isinstance(image, Image.Image):178 image = np.array(image)179 180 # Convert to grayscale181 if len(image.shape) == 3:182 gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)183 else:184 gray = image185 186 # Create kernel187 kernel = np.ones((kernel_size, kernel_size), np.uint8)188 189 # Apply operation190 if operation == "Erosion":191 result = cv2.erode(gray, kernel, iterations=iterations)192 desc = "Shrinks white regions, removes small white noise"193 elif operation == "Dilation":194 result = cv2.dilate(gray, kernel, iterations=iterations)195 desc = "Expands white regions, fills small holes"196 elif operation == "Opening":197 result = cv2.morphologyEx(gray, cv2.MORPH_OPEN, kernel, iterations=iterations)198 desc = "Erosion followed by dilation, removes small white noise"199 elif operation == "Closing":200 result = cv2.morphologyEx(gray, cv2.MORPH_CLOSE, kernel, iterations=iterations)201 desc = "Dilation followed by erosion, fills small holes"202 elif operation == "Gradient":203 result = cv2.morphologyEx(gray, cv2.MORPH_GRADIENT, kernel)204 desc = "Difference between dilation and erosion, shows outlines"205 else:206 result = gray207 desc = "No operation"208 209 # Convert back to RGB210 result_rgb = cv2.cvtColor(result, cv2.COLOR_GRAY2RGB)211 212 info = f"""213### Morphological Operation: {operation}214- **Kernel Size**: {kernel_size}x{kernel_size}215- **Iterations**: {iterations}216- **Effect**: {desc}217"""218 219 return result_rgb, info220 221 222def apply_edge_detection(image, method, threshold1, threshold2):223 """Apply edge detection methods"""224 if isinstance(image, Image.Image):225 image = np.array(image)226 227 # Convert to grayscale228 if len(image.shape) == 3:229 gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)230 else:231 gray = image232 233 if method == "Canny":234 edges = cv2.Canny(gray, threshold1, threshold2)235 desc = "Multi-stage edge detection algorithm"236 elif method == "Sobel":237 sobelx = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=5)238 sobely = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=5)239 edges = np.sqrt(sobelx**2 + sobely**2)240 edges = np.uint8(edges / edges.max() * 255)241 desc = "Gradient-based edge detection"242 elif method == "Laplacian":243 edges = cv2.Laplacian(gray, cv2.CV_64F)244 edges = np.uint8(np.abs(edges))245 desc = "Second derivative edge detection"246 else:247 edges = gray248 desc = "No operation"249 250 # Convert to RGB251 edges_rgb = cv2.cvtColor(edges, cv2.COLOR_GRAY2RGB)252 253 info = f"""254### Edge Detection: {method}255- **Method**: {desc}256- **Threshold 1**: {threshold1}257- **Threshold 2**: {threshold2}258"""259 260 return edges_rgb, info261 262 263def apply_color_space(image, color_space):264 """Convert to different color spaces"""265 if isinstance(image, Image.Image):266 image = np.array(image)267 268 if color_space == "RGB":269 result = image270 desc = "Standard Red-Green-Blue color space"271 elif color_space == "HSV":272 result = cv2.cvtColor(image, cv2.COLOR_RGB2HSV)273 desc = "Hue-Saturation-Value: Separates color from intensity"274 elif color_space == "LAB":275 result = cv2.cvtColor(image, cv2.COLOR_RGB2LAB)276 desc = "Perceptually uniform color space"277 elif color_space == "Grayscale":278 gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)279 result = cv2.cvtColor(gray, cv2.COLOR_GRAY2RGB)280 desc = "Single channel intensity image"281 elif color_space == "YCrCb":282 result = cv2.cvtColor(image, cv2.COLOR_RGB2YCrCb)283 desc = "Luma and chroma components"284 else:285 result = image286 desc = "No conversion"287 288 # Create histogram289 hist = create_histogram(result if color_space != "Grayscale" else gray, f"Histogram - {color_space}")290 291 info = f"""292### Color Space: {color_space}293- **Description**: {desc}294"""295 296 return result, hist, info297 298 299def apply_thresholding(image, method, threshold_value, max_value):300 """Apply different thresholding methods"""301 if isinstance(image, Image.Image):302 image = np.array(image)303 304 # Convert to grayscale305 if len(image.shape) == 3:306 gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)307 else:308 gray = image309 310 if method == "Binary":311 _, result = cv2.threshold(gray, threshold_value, max_value, cv2.THRESH_BINARY)312 desc = "Pixels > threshold become max_value, others become 0"313 elif method == "Binary Inverse":314 _, result = cv2.threshold(gray, threshold_value, max_value, cv2.THRESH_BINARY_INV)315 desc = "Inverse of binary threshold"316 elif method == "Truncate":317 _, result = cv2.threshold(gray, threshold_value, max_value, cv2.THRESH_TRUNC)318 desc = "Pixels > threshold become threshold value"319 elif method == "To Zero":320 _, result = cv2.threshold(gray, threshold_value, max_value, cv2.THRESH_TOZERO)321 desc = "Pixels < threshold become 0"322 elif method == "Otsu":323 _, result = cv2.threshold(gray, 0, max_value, cv2.THRESH_BINARY + cv2.THRESH_OTSU)324 desc = "Automatic threshold calculation using Otsu's method"325 elif method == "Adaptive Mean":326 result = cv2.adaptiveThreshold(gray, max_value, cv2.ADAPTIVE_THRESH_MEAN_C, 327 cv2.THRESH_BINARY, 11, 2)328 desc = "Threshold calculated for small regions using mean"329 elif method == "Adaptive Gaussian":330 result = cv2.adaptiveThreshold(gray, max_value, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, 331 cv2.THRESH_BINARY, 11, 2)332 desc = "Threshold calculated for small regions using Gaussian weights"333 else:334 result = gray335 desc = "No thresholding"336 337 # Convert to RGB338 result_rgb = cv2.cvtColor(result, cv2.COLOR_GRAY2RGB)339 340 info = f"""341### Thresholding: {method}342- **Threshold Value**: {threshold_value}343- **Max Value**: {max_value}344- **Effect**: {desc}345"""346 347 return result_rgb, info348 349 350def apply_grabcut(image, margin_percent, iterations):351 """352 Apply GrabCut algorithm for background subtraction353 354 GrabCut is an interactive foreground extraction algorithm that uses355 graph cuts and Gaussian Mixture Models (GMM) to separate foreground356 from background.357 """358 if isinstance(image, Image.Image):359 image = np.array(image)360 361 # Ensure RGB format362 if len(image.shape) == 2:363 image = cv2.cvtColor(image, cv2.COLOR_GRAY2RGB)364 365 # Create a copy for processing366 img = image.copy()367 h, w = img.shape[:2]368 369 # Create mask (0 = background, 1 = foreground, 2 = probably background, 3 = probably foreground)370 mask = np.zeros(img.shape[:2], np.uint8)371 372 # Initialize background and foreground models (used internally by GrabCut)373 bgd_model = np.zeros((1, 65), np.float64)374 fgd_model = np.zeros((1, 65), np.float64)375 376 # Define rectangle around the object (margin from edges)377 margin_h = int(h * margin_percent / 100)378 margin_w = int(w * margin_percent / 100)379 rect = (margin_w, margin_h, w - 2*margin_w, h - 2*margin_h)380 381 # Apply GrabCut algorithm382 # Iterations: more iterations = more accurate but slower383 cv2.grabCut(img, mask, rect, bgd_model, fgd_model, iterations, cv2.GC_INIT_WITH_RECT)384 385 # Create binary mask where foreground (1 or 3) = 1, background (0 or 2) = 0386 mask_binary = np.where((mask == 2) | (mask == 0), 0, 1).astype('uint8')387 388 # Extract foreground389 foreground = img * mask_binary[:, :, np.newaxis]390 391 # Create visualization showing the mask392 mask_vis = mask_binary * 255393 mask_rgb = cv2.cvtColor(mask_vis, cv2.COLOR_GRAY2RGB)394 395 # Create combined view: Original | Mask | Foreground396 # Resize for side-by-side display397 scale = 0.33398 h_new, w_new = int(h * scale), int(w * scale)399 400 original_small = cv2.resize(image, (w_new, h_new))401 mask_small = cv2.resize(mask_rgb, (w_new, h_new))402 foreground_small = cv2.resize(foreground, (w_new, h_new))403 404 # Concatenate horizontally405 combined = np.hstack([original_small, mask_small, foreground_small])406 407 # Add labels408 label_height = 30409 labeled_img = np.zeros((combined.shape[0] + label_height, combined.shape[1], 3), dtype=np.uint8)410 labeled_img[label_height:, :] = combined411 412 # Add text labels413 cv2.putText(labeled_img, "Original", (10, 20), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 1)414 cv2.putText(labeled_img, "Mask", (w_new + 10, 20), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 1)415 cv2.putText(labeled_img, "Foreground", (2*w_new + 10, 20), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 1)416 417 info = f"""418### GrabCut Background Subtraction Applied419- **Margin**: {margin_percent}% from edges420- **Iterations**: {iterations}421- **Algorithm**: Graph cuts with Gaussian Mixture Models422- **Output**: Shows Original | Mask | Extracted Foreground423- **Use Case**: Object extraction, background removal, photo editing424 425**How it works**:4261. Rectangle defines initial foreground region (inside margins)4272. GMM models learn foreground/background color distributions4283. Graph cuts optimize the boundary between them4294. White mask = foreground, Black = background430"""431 432 return labeled_img, foreground, info433 434 435def apply_fourier_filter(image, filter_type, cutoff_freq):436 """437 Apply Fourier Transform filtering in frequency domain438 439 Fourier Transform decomposes image into frequency components.440 Low frequencies = smooth regions, High frequencies = edges/details441 """442 if isinstance(image, Image.Image):443 image = np.array(image)444 445 # Convert to grayscale for Fourier446 if len(image.shape) == 3:447 gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)448 else:449 gray = image450 451 # Apply Fourier Transform452 f = np.fft.fft2(gray)453 fshift = np.fft.fftshift(f) # Shift zero frequency to center454 455 # Get magnitude spectrum for visualization456 magnitude_spectrum = 20 * np.log(np.abs(fshift) + 1)457 458 # Create filter mask459 rows, cols = gray.shape460 crow, ccol = rows // 2, cols // 2461 462 # Create coordinate matrices463 y, x = np.ogrid[:rows, :cols]464 distance = np.sqrt((x - ccol)**2 + (y - crow)**2)465 466 if filter_type == "Low-Pass (Blur)":467 # Allow low frequencies, block high frequencies468 mask = np.zeros((rows, cols), np.uint8)469 mask[distance <= cutoff_freq] = 1470 desc = "Removes high frequencies (edges/details), keeps smooth regions"471 472 elif filter_type == "High-Pass (Sharpen)":473 # Block low frequencies, allow high frequencies474 mask = np.ones((rows, cols), np.uint8)475 mask[distance <= cutoff_freq] = 0476 desc = "Removes low frequencies (smooth regions), keeps edges/details"477 478 elif filter_type == "Band-Pass":479 # Allow middle frequencies480 mask = np.zeros((rows, cols), np.uint8)481 mask[(distance >= cutoff_freq/2) & (distance <= cutoff_freq)] = 1482 desc = "Keeps only middle-range frequencies"483 484 elif filter_type == "Band-Stop (Notch)":485 # Block middle frequencies486 mask = np.ones((rows, cols), np.uint8)487 mask[(distance >= cutoff_freq/2) & (distance <= cutoff_freq)] = 0488 desc = "Removes middle-range frequencies, keeps very low and very high"489 else:490 mask = np.ones((rows, cols), np.uint8)491 desc = "No filtering"492 493 # Apply mask494 fshift_filtered = fshift * mask495 496 # Inverse Fourier Transform497 f_ishift = np.fft.ifftshift(fshift_filtered)498 img_back = np.fft.ifft2(f_ishift)499 img_back = np.real(img_back)500 501 # Normalize to 0-255502 img_back = np.clip(img_back, 0, 255).astype(np.uint8)503 504 # Convert back to RGB505 result_rgb = cv2.cvtColor(img_back, cv2.COLOR_GRAY2RGB)506 507 # Create visualization of spectrum508 magnitude_vis = np.clip(magnitude_spectrum, 0, 255).astype(np.uint8)509 magnitude_rgb = cv2.cvtColor(magnitude_vis, cv2.COLOR_GRAY2RGB)510 511 info = f"""512### Fourier Transform Filtering Applied513- **Filter Type**: {filter_type}514- **Cutoff Frequency**: {cutoff_freq} pixels515- **Effect**: {desc}516- **How it works**: Transforms to frequency domain, filters, transforms back517- **Use Case**: Periodic noise removal, sharpening, custom filtering518"""519 520 return result_rgb, magnitude_rgb, info521 522 523def apply_gray_world(image, percentile):524 """525 Apply Gray-World color constancy algorithm526 527 Assumes the average color of the scene should be gray.528 Adjusts color channels to achieve this, correcting color casts.529 """530 if isinstance(image, Image.Image):531 image = np.array(image)532 533 if len(image.shape) != 3:534 return cv2.cvtColor(image, cv2.COLOR_GRAY2RGB), "Image must be in color for Gray-World"535 536 # Convert to float537 img_float = image.astype(np.float32)538 539 # Calculate average or percentile of each channel540 if percentile == 50:541 # Standard Gray-World: use mean542 avg_r = np.mean(img_float[:, :, 0])543 avg_g = np.mean(img_float[:, :, 1])544 avg_b = np.mean(img_float[:, :, 2])545 method = "Mean"546 else:547 # Robust Gray-World: use percentile (less sensitive to outliers)548 avg_r = np.percentile(img_float[:, :, 0], percentile)549 avg_g = np.percentile(img_float[:, :, 1], percentile)550 avg_b = np.percentile(img_float[:, :, 2], percentile)551 method = f"{percentile}th Percentile"552 553 # Calculate gray value (average of all channels)554 gray_value = (avg_r + avg_g + avg_b) / 3555 556 # Calculate scaling factors557 scale_r = gray_value / (avg_r + 1e-6)558 scale_g = gray_value / (avg_g + 1e-6)559 scale_b = gray_value / (avg_b + 1e-6)560 561 # Apply scaling562 result = img_float.copy()563 result[:, :, 0] = np.clip(result[:, :, 0] * scale_r, 0, 255)564 result[:, :, 1] = np.clip(result[:, :, 1] * scale_g, 0, 255)565 result[:, :, 2] = np.clip(result[:, :, 2] * scale_b, 0, 255)566 567 result = result.astype(np.uint8)568 569 info = f"""570### Gray-World Color Constancy Applied571- **Method**: {method}572- **Scaling Factors**: R={scale_r:.3f}, G={scale_g:.3f}, B={scale_b:.3f}573- **Effect**: Removes color cast by balancing channel averages574- **Assumption**: Average scene color should be neutral gray575- **Use Case**: Correct lighting color casts (blue/yellow/green tints)576 577**Original Averages**: R={avg_r:.1f}, G={avg_g:.1f}, B={avg_b:.1f}578**Target Gray**: {gray_value:.1f}579"""580 581 return result, info582 583 584def apply_anisotropic_diffusion(image, iterations, kappa, gamma):585 """586 Apply Anisotropic Diffusion (Perona-Malik)587 588 Edge-preserving smoothing that reduces noise while maintaining edges.589 Diffusion is stronger in smooth regions, weaker near edges.590 """591 if isinstance(image, Image.Image):592 image = np.array(image)593 594 # Convert to grayscale595 if len(image.shape) == 3:596 gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)597 else:598 gray = image599 600 # Convert to float601 img = gray.astype(np.float32)602 603 # Perform anisotropic diffusion604 for _ in range(iterations):605 # Calculate gradients in 4 directions606 gradN = np.roll(img, 1, axis=0) - img # North607 gradS = np.roll(img, -1, axis=0) - img # South608 gradE = np.roll(img, -1, axis=1) - img # East609 gradW = np.roll(img, 1, axis=1) - img # West610 611 # Calculate diffusion coefficients (edge-stopping function)612 # Option 1: Exponential (preserves wide regions)613 cN = np.exp(-(gradN / kappa) ** 2)614 cS = np.exp(-(gradS / kappa) ** 2)615 cE = np.exp(-(gradE / kappa) ** 2)616 cW = np.exp(-(gradW / kappa) ** 2)617 618 # Update image619 img = img + gamma * (cN * gradN + cS * gradS + cE * gradE + cW * gradW)620 621 # Clip and convert back622 result = np.clip(img, 0, 255).astype(np.uint8)623 result_rgb = cv2.cvtColor(result, cv2.COLOR_GRAY2RGB)624 625 info = f"""626### Anisotropic Diffusion Applied627- **Iterations**: {iterations}628- **Kappa (Edge threshold)**: {kappa}629- **Gamma (Step size)**: {gamma}630- **Algorithm**: Perona-Malik diffusion631- **Effect**: Smooths noise while preserving edges632- **How it works**: Diffusion is adaptive - strong in flat regions, weak at edges633- **Use Case**: Medical imaging, noise reduction with edge preservation634 635**Parameters Guide**:636- Kappa: Controls what's considered an edge (10-50 typical)637- Gamma: Controls diffusion speed (0.1-0.25 typical, must be โค0.25 for stability)638- Iterations: More = more smoothing (5-20 typical)639"""640 641 return result_rgb, info642 643 644def analyze_image_stats(image):645 """Provide detailed statistical analysis"""646 if isinstance(image, Image.Image):647 image = np.array(image)648 649 if len(image.shape) == 3:650 gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)651 channels = cv2.split(image)652 653 stats = f"""654### Image Statistics655 656**Dimensions**: {image.shape[1]} x {image.shape[0]} pixels657 658**RGB Channel Statistics**:659- **Red**: Mean={np.mean(channels[0]):.2f}, Std={np.std(channels[0]):.2f}, Min={np.min(channels[0])}, Max={np.max(channels[0])}660- **Green**: Mean={np.mean(channels[1]):.2f}, Std={np.std(channels[1]):.2f}, Min={np.min(channels[1])}, Max={np.max(channels[1])}661- **Blue**: Mean={np.mean(channels[2]):.2f}, Std={np.std(channels[2]):.2f}, Min={np.min(channels[2])}, Max={np.max(channels[2])}662 663**Grayscale Statistics**:664- **Mean Intensity**: {np.mean(gray):.2f}665- **Standard Deviation**: {np.std(gray):.2f}666- **Min Value**: {np.min(gray)}667- **Max Value**: {np.max(gray)}668- **Median**: {np.median(gray):.2f}669 670**Brightness Assessment**: {"Dark" if np.mean(gray) < 85 else "Medium" if np.mean(gray) < 170 else "Bright"}671**Contrast Assessment**: {"Low" if np.std(gray) < 30 else "Medium" if np.std(gray) < 60 else "High"}672"""673 else:674 stats = f"""675### Image Statistics676 677**Dimensions**: {image.shape[1]} x {image.shape[0]} pixels678 679**Grayscale Statistics**:680- **Mean Intensity**: {np.mean(image):.2f}681- **Standard Deviation**: {np.std(image):.2f}682- **Min Value**: {np.min(image)}683- **Max Value**: {np.max(image)}684- **Median**: {np.median(image):.2f}685 686**Brightness Assessment**: {"Dark" if np.mean(image) < 85 else "Medium" if np.mean(image) < 170 else "Bright"}687**Contrast Assessment**: {"Low" if np.std(image) < 30 else "Medium" if np.std(image) < 60 else "High"}688"""689 690 return stats691 692 693# Create Gradio Interface694with gr.Blocks(title="Image Preprocessing Analyzer", theme=gr.themes.Soft()) as demo:695 gr.Markdown("""696 # ๐ฌ Image Preprocessing Analyzer697 ### Understand Image Processing at Pixel Level698 699 Upload an image and explore various preprocessing techniques with real-time parameter adjustments.700 See histograms, pixel-level information, and understand how each filter affects your image.701 """)702 703 with gr.Row():704 input_image = gr.Image(label="Upload Image", type="pil", height=400)705 original_hist = gr.Image(label="Original Histogram")706 707 with gr.Row():708 stats_output = gr.Markdown(label="Image Statistics")709 710 # Update stats when image is loaded711 input_image.change(712 fn=lambda img: (analyze_image_stats(img), create_histogram(np.array(img), "Original Histogram")) if img else ("No image", None),713 inputs=[input_image],714 outputs=[stats_output, original_hist]715 )716 717 with gr.Tabs():718 # CLAHE Tab719 with gr.TabItem("๐จ CLAHE (Contrast Enhancement)"):720 gr.Markdown("""721 **CLAHE** (Contrast Limited Adaptive Histogram Equalization) enhances local contrast.722 Adjust parameters to see how it affects different image regions.723 """)724 725 with gr.Row():726 clahe_clip = gr.Slider(0.5, 10.0, value=2.0, step=0.5, label="Clip Limit")727 clahe_tile = gr.Slider(2, 32, value=8, step=2, label="Tile Size")728 729 clahe_btn = gr.Button("Apply CLAHE", variant="primary")730 731 with gr.Row():732 clahe_output = gr.Image(label="Result")733 clahe_hist_before = gr.Image(label="Histogram - Before")734 735 with gr.Row():736 clahe_hist_after = gr.Image(label="Histogram - After")737 clahe_info = gr.Markdown()738 739 clahe_btn.click(740 fn=apply_clahe,741 inputs=[input_image, clahe_clip, clahe_tile],742 outputs=[clahe_output, clahe_hist_before, clahe_hist_after, clahe_info]743 )744 745 # Smoothing Filters Tab746 with gr.TabItem("๐ Smoothing Filters"):747 filter_type = gr.Radio(748 ["Gaussian Blur", "Bilateral Filter", "Median Filter"],749 value="Gaussian Blur",750 label="Filter Type"751 )752 753 with gr.Row():754 with gr.Column():755 # Gaussian parameters756 gauss_kernel = gr.Slider(1, 31, value=5, step=2, label="Kernel Size (Gaussian)")757 gauss_sigma = gr.Slider(0, 10, value=0, step=0.5, label="Sigma (Gaussian)")758 759 with gr.Column():760 # Bilateral parameters761 bilat_diameter = gr.Slider(1, 15, value=9, step=2, label="Diameter (Bilateral)")762 bilat_sigma_color = gr.Slider(1, 150, value=75, step=5, label="Sigma Color (Bilateral)")763 bilat_sigma_space = gr.Slider(1, 150, value=75, step=5, label="Sigma Space (Bilateral)")764 765 with gr.Column():766 # Median parameters767 median_kernel = gr.Slider(1, 31, value=5, step=2, label="Kernel Size (Median)")768 769 smooth_btn = gr.Button("Apply Filter", variant="primary")770 771 with gr.Row():772 smooth_output = gr.Image(label="Result")773 smooth_info = gr.Markdown()774 775 def apply_smoothing(image, filter_type, gk, gs, bd, bsc, bss, mk):776 if filter_type == "Gaussian Blur":777 return apply_gaussian_blur(image, gk, gs)778 elif filter_type == "Bilateral Filter":779 return apply_bilateral_filter(image, bd, bsc, bss)780 else:781 return apply_median_filter(image, mk)782 783 smooth_btn.click(784 fn=apply_smoothing,785 inputs=[input_image, filter_type, gauss_kernel, gauss_sigma, 786 bilat_diameter, bilat_sigma_color, bilat_sigma_space, median_kernel],787 outputs=[smooth_output, smooth_info]788 )789 790 # Edge Detection Tab791 with gr.TabItem("๐ Edge Detection"):792 edge_method = gr.Radio(793 ["Canny", "Sobel", "Laplacian"],794 value="Canny",795 label="Edge Detection Method"796 )797 798 with gr.Row():799 edge_thresh1 = gr.Slider(0, 255, value=50, step=5, label="Threshold 1")800 edge_thresh2 = gr.Slider(0, 255, value=150, step=5, label="Threshold 2")801 802 edge_btn = gr.Button("Detect Edges", variant="primary")803 804 with gr.Row():805 edge_output = gr.Image(label="Result")806 edge_info = gr.Markdown()807 808 edge_btn.click(809 fn=apply_edge_detection,810 inputs=[input_image, edge_method, edge_thresh1, edge_thresh2],811 outputs=[edge_output, edge_info]812 )813 814 # Morphological Operations Tab815 with gr.TabItem("๐ฒ Morphological Operations"):816 morph_op = gr.Radio(817 ["Erosion", "Dilation", "Opening", "Closing", "Gradient"],818 value="Closing",819 label="Operation"820 )821 822 with gr.Row():823 morph_kernel = gr.Slider(1, 21, value=3, step=2, label="Kernel Size")824 morph_iter = gr.Slider(1, 5, value=1, step=1, label="Iterations")825 826 morph_btn = gr.Button("Apply Operation", variant="primary")827 828 with gr.Row():829 morph_output = gr.Image(label="Result")830 morph_info = gr.Markdown()831 832 morph_btn.click(833 fn=apply_morphology,834 inputs=[input_image, morph_op, morph_kernel, morph_iter],835 outputs=[morph_output, morph_info]836 )837 838 # Color Spaces Tab839 with gr.TabItem("๐จ Color Spaces"):840 color_space = gr.Radio(841 ["RGB", "HSV", "LAB", "YCrCb", "Grayscale"],842 value="RGB",843 label="Color Space"844 )845 846 color_btn = gr.Button("Convert Color Space", variant="primary")847 848 with gr.Row():849 color_output = gr.Image(label="Result")850 color_hist = gr.Image(label="Histogram")851 852 color_info = gr.Markdown()853 854 color_btn.click(855 fn=apply_color_space,856 inputs=[input_image, color_space],857 outputs=[color_output, color_hist, color_info]858 )859 860 # Thresholding Tab861 with gr.TabItem("โซโช Thresholding"):862 thresh_method = gr.Radio(863 ["Binary", "Binary Inverse", "Truncate", "To Zero", "Otsu", 864 "Adaptive Mean", "Adaptive Gaussian"],865 value="Binary",866 label="Thresholding Method"867 )868 869 with gr.Row():870 thresh_value = gr.Slider(0, 255, value=127, step=1, label="Threshold Value")871 thresh_max = gr.Slider(0, 255, value=255, step=1, label="Max Value")872 873 thresh_btn = gr.Button("Apply Threshold", variant="primary")874 875 with gr.Row():876 thresh_output = gr.Image(label="Result")877 thresh_info = gr.Markdown()878 879 thresh_btn.click(880 fn=apply_thresholding,881 inputs=[input_image, thresh_method, thresh_value, thresh_max],882 outputs=[thresh_output, thresh_info]883 )884 885 # GrabCut Background Subtraction Tab886 with gr.TabItem("โ๏ธ Background Subtraction (GrabCut)"):887 gr.Markdown("""888 **GrabCut** is an advanced algorithm for extracting foreground objects from images.889 It uses graph cuts and Gaussian Mixture Models to intelligently separate foreground from background.890 891 Perfect for: Product photography, portrait backgrounds, object isolation892 """)893 894 with gr.Row():895 grabcut_margin = gr.Slider(5, 25, value=10, step=1, label="Margin from Edges (%)")896 grabcut_iter = gr.Slider(1, 10, value=5, step=1, label="Iterations")897 898 grabcut_btn = gr.Button("Extract Foreground", variant="primary")899 900 with gr.Row():901 grabcut_output = gr.Image(label="Comparison View (Original | Mask | Foreground)")902 903 with gr.Row():904 grabcut_foreground = gr.Image(label="Extracted Foreground (Full Size)")905 grabcut_info = gr.Markdown()906 907 grabcut_btn.click(908 fn=apply_grabcut,909 inputs=[input_image, grabcut_margin, grabcut_iter],910 outputs=[grabcut_output, grabcut_foreground, grabcut_info]911 )912 913 # Fourier Transform Filtering Tab914 with gr.TabItem("๐ Fourier Transform Filtering"):915 gr.Markdown("""916 **Fourier Transform** decomposes images into frequency components.917 Filter in frequency domain to remove periodic noise or enhance specific features.918 """)919 920 with gr.Row():921 fourier_type = gr.Radio(922 ["Low-Pass (Blur)", "High-Pass (Sharpen)", "Band-Pass", "Band-Stop (Notch)"],923 value="Low-Pass (Blur)",924 label="Filter Type"925 )926 fourier_cutoff = gr.Slider(10, 200, value=30, step=5, label="Cutoff Frequency (pixels)")927 928 fourier_btn = gr.Button("Apply Fourier Filter", variant="primary")929 930 with gr.Row():931 fourier_output = gr.Image(label="Filtered Result")932 fourier_spectrum = gr.Image(label="Frequency Spectrum")933 934 fourier_info = gr.Markdown()935 936 fourier_btn.click(937 fn=apply_fourier_filter,938 inputs=[input_image, fourier_type, fourier_cutoff],939 outputs=[fourier_output, fourier_spectrum, fourier_info]940 )941 942 # Gray-World Color Constancy Tab943 with gr.TabItem("๐จ Color Constancy (Gray-World)"):944 gr.Markdown("""945 **Gray-World Algorithm** corrects color casts caused by lighting.946 Assumes the average color of a scene should be neutral gray.947 """)948 949 with gr.Row():950 grayworld_percentile = gr.Slider(951 40, 60, value=50, step=5,952 label="Percentile (50=Mean, 40-45=Robust to highlights)"953 )954 955 grayworld_btn = gr.Button("Apply Gray-World", variant="primary")956 957 with gr.Row():958 grayworld_output = gr.Image(label="Color Corrected")959 grayworld_info = gr.Markdown()960 961 grayworld_btn.click(962 fn=apply_gray_world,963 inputs=[input_image, grayworld_percentile],964 outputs=[grayworld_output, grayworld_info]965 )966 967 # Anisotropic Diffusion Tab968 with gr.TabItem("๐ฌ Anisotropic Diffusion"):969 gr.Markdown("""970 **Anisotropic Diffusion** (Perona-Malik) performs edge-preserving smoothing.971 Reduces noise while maintaining sharp edges - ideal for medical imaging.972 """)973 974 with gr.Row():975 aniso_iter = gr.Slider(1, 30, value=10, step=1, label="Iterations")976 aniso_kappa = gr.Slider(5, 100, value=20, step=5, label="Kappa (Edge threshold)")977 aniso_gamma = gr.Slider(0.05, 0.25, value=0.15, step=0.05, label="Gamma (Step size)")978 979 aniso_btn = gr.Button("Apply Anisotropic Diffusion", variant="primary")980 981 with gr.Row():982 aniso_output = gr.Image(label="Smoothed Result")983 aniso_info = gr.Markdown()984 985 aniso_btn.click(986 fn=apply_anisotropic_diffusion,987 inputs=[input_image, aniso_iter, aniso_kappa, aniso_gamma],988 outputs=[aniso_output, aniso_info]989 )990 991 # Documentation992 with gr.Accordion("๐ Filter Documentation", open=False):993 gr.Markdown("""994 ### Filter Explanations995 996 #### CLAHE (Contrast Limited Adaptive Histogram Equalization)997 - **Purpose**: Enhance local contrast in images998 - **How it works**: Divides image into tiles and equalizes histogram in each tile999 - **Clip Limit**: Controls contrast enhancement (higher = more enhancement)1000 - **Tile Size**: Size of local regions (smaller = more local adaptation)1001 - **Use Case**: Medical imaging, underwater images, shadowed regions1002 1003 #### Gaussian Blur1004 - **Purpose**: Smooth images and reduce noise1005 - **How it works**: Weighted average of neighboring pixels using Gaussian function1006 - **Kernel Size**: Larger = more blur1007 - **Sigma**: Standard deviation of Gaussian (0 = auto-calculated)1008 - **Use Case**: Preprocessing for edge detection, noise reduction1009 1010 #### Bilateral Filter1011 - **Purpose**: Edge-preserving smoothing1012 - **How it works**: Averages pixels but preserves edges by considering both spatial and color distance1013 - **Diameter**: Size of pixel neighborhood1014 - **Sigma Color**: How much color difference matters1015 - **Sigma Space**: How much spatial distance matters1016 - **Use Case**: Noise reduction while keeping edges sharp1017 1018 #### Median Filter1019 - **Purpose**: Remove salt-and-pepper noise1020 - **How it works**: Replaces each pixel with median of surrounding pixels1021 - **Kernel Size**: Size of neighborhood1022 - **Use Case**: Impulse noise removal1023 1024 #### Morphological Operations1025 - **Erosion**: Shrinks white regions, removes small noise1026 - **Dilation**: Expands white regions, fills small holes1027 - **Opening**: Erosion then dilation, removes small objects1028 - **Closing**: Dilation then erosion, fills small holes1029 - **Gradient**: Difference between dilation and erosion, shows boundaries1030 1031 #### Edge Detection1032 - **Canny**: Multi-stage algorithm, best overall edge detector1033 - **Sobel**: Gradient-based, sensitive to horizontal/vertical edges1034 - **Laplacian**: Second derivative, sensitive to rapid intensity changes1035 1036 #### Thresholding1037 - **Binary**: Simple cutoff threshold1038 - **Otsu**: Automatically finds optimal threshold1039 - **Adaptive**: Different thresholds for different regions1040 1041 #### GrabCut Background Subtraction1042 - **Purpose**: Extract foreground objects from images1043 - **How it works**: Uses graph cuts and Gaussian Mixture Models (GMM)1044 - **Margin**: Defines initial foreground region (rectangle inside margins)1045 - **Iterations**: More iterations = more accurate segmentation (but slower)1046 - **Use Case**: Product photography, portrait background removal, object isolation1047 - **Algorithm**: Iteratively learns color distributions of foreground/background1048 - **Output**: Binary mask and extracted foreground object1049 1050 #### Fourier Transform Filtering1051 - **Purpose**: Filter images in frequency domain1052 - **How it works**: Converts to frequency domain, applies filter, converts back1053 - **Low-Pass**: Removes high frequencies (edges), keeps smooth regions โ blur effect1054 - **High-Pass**: Removes low frequencies (smooth regions), keeps edges โ sharpen effect1055 - **Band-Pass**: Keeps only middle-range frequencies1056 - **Band-Stop**: Removes middle-range frequencies (notch filter)1057 - **Use Case**: Periodic noise removal, custom filtering, pattern analysis1058 1059 #### Gray-World Color Constancy1060 - **Purpose**: Correct color casts from lighting1061 - **How it works**: Assumes average scene color should be neutral gray1062 - **Method**: Balances RGB channels so their average equals gray1063 - **Percentile**: 50=standard mean, 40-45=robust to bright highlights1064 - **Use Case**: Indoor/outdoor lighting correction, white balance adjustment1065 1066 #### Anisotropic Diffusion1067 - **Purpose**: Edge-preserving noise reduction1068 - **How it works**: Perona-Malik diffusion - smooths flat regions, preserves edges1069 - **Kappa**: Edge threshold (10-50 typical, higher = more edges preserved)1070 - **Gamma**: Diffusion speed (0.1-0.25, must be โค0.25 for stability)1071 - **Iterations**: More = more smoothing (5-20 typical)1072 - **Use Case**: Medical imaging, noise reduction without edge loss1073 """)1074 1075if __name__ == "__main__":1076 demo.launch()