Team Ai
Apppublic

eho69/image-preprocessing

sourceHugging Faceapache-2.0updated 8mo agoView on Hugging Face
1likes
app.py1076 linesDownload Raw Back to root
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()