{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":113558,"databundleVersionId":14174843,"sourceType":"competition"}],"dockerImageVersionId":31153,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"\n\n# **Multi-Class Image Classifier w/ Auto Grouping**\n","metadata":{}},{"cell_type":"markdown","source":"**Given the diversity of image types, an effective approach is to group them first before performing authenticity classification on each group. This notebook demonstrates that grouping methodology.**","metadata":{}},{"cell_type":"markdown","source":"\n## **Pipeline Explanation**\n\nThis is a **rule-based image classification system** that automatically groups images into 6 categories based on visual characteristics without requiring machine learning training.\n\n### **Pipeline Flow Overview:**\n\n```\n1. IMAGE LOADING → 2. DIMENSION ANALYSIS → 3. ASPECT RATIO CHECK → 4. VISUAL FEATURE EXTRACTION → 5. RULE-BASED CLASSIFICATION\n```\n\n### **Detailed Pipeline Stages:**\n\n#### **1. Image Collection & Preprocessing**\n- **Function**: `get_limited_image_paths()`\n- **Purpose**: Recursively scans directories for image files (JPG, PNG, JPEG)\n- **Limitation**: Processes maximum 300 images by default to manage computational load\n- **Output**: List of valid image file paths\n\n#### **2. Dimension & Aspect Ratio Analysis**\n- **Extracts**: Image width, height, and aspect ratio (width/height)\n- **Priority Check**: Identifies extreme aspect ratios first\n- **Thresholds**:\n  - **Wide images**: Aspect ratio ≥ 2.5 (panoramas, landscapes)\n  - **Tall images**: Aspect ratio ≤ 0.4 (portraits, vertical shots)\n\n#### **3. Visual Feature Extraction**\n- **Image Normalization**: Resizes all images to 224×224 pixels\n- **Key Features Calculated**:\n  - **Brightness**: Average pixel intensity\n  - **Variance**: Pixel value variation (texture complexity)\n  - **Color Dominance**: RGB channel averages\n\n#### **4. Rule-Based Classification Logic**\n\n**Priority System:**\n1. **Extreme Aspect Ratios** (Highest Priority)\n   - Group 1: Extreme Wide (AR ≥ 2.5)\n   - Group 2: Extreme Tall (AR ≤ 0.4)\n\n2. **Visual Features** (Normal Aspect Ratios)\n   - Group 3: Bright & High Variance (brightness > 170, variance > 4000)\n   - Group 4: Dark & Low Variance (brightness < 100, variance < 2000)\n   - Group 5: Green Dominant (green channel > red + 15 and > blue + 15)\n   - Group 6: Neutral & Balanced (default for remaining images)\n\n#### **5. Confidence Scoring**\n- Each classification includes a confidence score\n- Higher confidence for more extreme characteristics\n- Aspect ratio classifications get bonus confidence based on extremity\n\n#### **6. Results Visualization**\n- **Statistical Summary**: Counts, averages, percentages per group\n- **Sample Display**: Shows representative images from each category\n- **Grouped Visualization**: Creates a comprehensive grid of classified images\n\n### **Key Advantages:**\n\n1. **No Training Required**: Rule-based approach works immediately\n2. **Computationally Efficient**: Much faster than ML models\n3. **Interpretable**: Clear rules make classifications understandable\n4. **Customizable**: Thresholds can be easily adjusted\n5. **Handles Diverse Images**: Works with various image types and sizes\n\n### **Use Cases:**\n- **Image Organization**: Automatically sort photo libraries\n- **Content Analysis**: Understand visual characteristics of image datasets\n- **Preprocessing**: Group images before more sophisticated ML analysis\n- **Quality Assessment**: Identify image types and characteristics\n\n### **Output Groups:**\n1. **Extreme Wide** - Landscape panoramas\n2. **Extreme Tall** - Portrait/vertical images  \n3. **Bright & High Variance** - Detailed, vibrant scenes\n4. **Dark & Low Variance** - Low-light, uniform images\n5. **Green Dominant** - Nature/vegetation scenes\n6. **Neutral & Balanced** - Standard, well-balanced images\n\nThis pipeline provides a quick, efficient way to automatically categorize images based on fundamental visual properties without the complexity of machine learning model training.","metadata":{}},{"cell_type":"code","source":"import tensorflow as tf\nfrom tensorflow import keras\nimport numpy as np\nimport matplotlib.pyplot as plt\nfrom pathlib import Path\nfrom PIL import Image\n\n# ===========================\n# 1. Get Limited Image Paths\n# ===========================\n\ndef get_limited_image_paths(data_dir, max_images=300):\n    \"\"\"Get first max_images image paths from directory\"\"\"\n    data_path = Path(data_dir)\n    \n    if not data_path.exists():\n        print(f\"❌ Directory does not exist: {data_dir}\")\n        return []\n    \n    image_paths = []\n    \n    # Recursively search for image files\n    for ext in ['*.jpg', '*.jpeg', '*.png', '*.JPG', '*.JPEG', '*.PNG']:\n        found_images = list(data_path.rglob(ext))\n        for img_path in found_images:\n            if len(image_paths) < max_images:\n                image_paths.append(img_path)\n            else:\n                break\n        if len(image_paths) >= max_images:\n            break\n    \n    print(f\"Found {len(image_paths)} images in {data_dir}\")\n    return image_paths","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def simple_classify_and_group(data_dir, max_images=300, num_classes=6):\n    \"\"\"\n    Shape-First Image Classification Pipeline (6 Classes)\n    \n    PIPELINE FLOW:\n    ┌─────────────────────────────────────────────────────┐\n    │ 1. Load Image & Extract Dimensions                  │\n    │    → Width, Height, Aspect Ratio                    │\n    └─────────────────────┬───────────────────────────────┘\n                          │\n    ┌─────────────────────▼───────────────────────────────┐\n    │ 2. PRIORITY: Check Extreme Aspect Ratios            │\n    │    → Wide (≥1.8) → Group 1                          │\n    │    → Tall (≤0.6) → Group 2                          │\n    └─────────────────────┬───────────────────────────────┘\n                          │ If normal aspect ratio (0.6-1.8)\n    ┌─────────────────────▼───────────────────────────────┐\n    │ 3. Extract Visual Features (224×224 normalized)     │\n    │    → Brightness (pixel mean)                        │\n    │    → Variance (pixel variance)                      │\n    │    → RGB channel averages                           │\n    └─────────────────────┬───────────────────────────────┘\n                          │\n    ┌─────────────────────▼───────────────────────────────┐\n    │ 4. Feature-Based Classification (4 Groups)          │\n    │    → Bright + High Var → Group 3                    │\n    │    → Dark + Low Var → Group 4                       │\n    │    → Green Dominant → Group 5                       │\n    │    → Others → Group 6 (Neutral)                     │\n    └─────────────────────────────────────────────────────┘\n    \n    CLASSIFICATION GROUPS:\n    - Group 1: Extreme Wide (AR≥1.8) - Panoramas, landscapes\n    - Group 2: Extreme Tall (AR≤0.6) - Portraits, vertical shots\n    - Group 3: Bright & High Variance - Detailed, vibrant images\n    - Group 4: Dark & Low Variance - Low-light, uniform images\n    - Group 5: Green Dominant - Nature, vegetation scenes\n    - Group 6: Neutral & Balanced - Standard balanced images\n    \"\"\"\n    \n    print(\"\\n\" + \"=\"*70)\n    print(f\"IMPROVED 6-CLASS CLASSIFICATION (Aspect Ratio + Features)\")\n    print(f\"Processing first {max_images} images\")\n    print(\"=\"*70)\n    \n    # Get image paths\n    image_paths = get_limited_image_paths(data_dir, max_images)\n    \n    if len(image_paths) == 0:\n        print(\"❌ No images found!\")\n        return {}, []\n    \n    # Define 6 class names\n    class_names = [\n        \"Group_1_ExtremeWide\",       # Extreme Horizontal\n        \"Group_2_ExtremeTall\",       # Extreme Vertical\n        \"Group_3_Bright_HighVar\",    # Bright and High Variation\n        \"Group_4_Dark_LowVar\",       # Dark and Low Variation\n        \"Group_5_GreenDominant\",     # Green Dominant\n        \"Group_6_Neutral_Balanced\"   # Neutral\n    ]\n    \n    grouped_images = {class_name: [] for class_name in class_names}\n    \n    print(f\"\\nProcessing {len(image_paths)} images...\")\n    \n    # Aspect ratio thresholds\n    WIDE_THRESHOLD = 2.5    # Threshold for wide image detection\n    TALL_THRESHOLD = 0.4    # Threshold for tall image detection\n    \n    for i, img_path in enumerate(image_paths):\n        if (i + 1) % 50 == 0:\n            print(f\"  Processed {i + 1}/{len(image_paths)} images...\")\n        \n        try:\n            # 1. Get image dimensions and aspect ratio\n            with Image.open(img_path) as original_img:\n                width, height = original_img.size\n                aspect_ratio = width / height if height != 0 else 1.0\n            \n            # 2. Load and extract features\n            img = keras.preprocessing.image.load_img(img_path, target_size=(224, 224))\n            img_array = keras.preprocessing.image.img_to_array(img)\n            \n            # Feature extraction\n            brightness = np.mean(img_array)\n            variance = np.var(img_array)\n            r, g, b = np.mean(img_array, axis=(0, 1))\n            \n            # 3. Classification logic\n            # Priority 1: Extreme aspect ratios\n            if aspect_ratio >= WIDE_THRESHOLD:\n                class_idx = 0  # Extreme Wide\n                confidence = min(0.95, 0.7 + (aspect_ratio - WIDE_THRESHOLD) * 0.1)\n            \n            elif aspect_ratio <= TALL_THRESHOLD:\n                class_idx = 1  # Extreme Tall\n                confidence = min(0.95, 0.7 + (TALL_THRESHOLD - aspect_ratio) * 0.2)\n            \n            # Priority 2: Feature-based classification for normal aspect ratios\n            elif brightness > 170 and variance > 4000:\n                class_idx = 2  # Bright & High Variance\n                confidence = min(0.90, 0.6 + variance / 15000)\n            \n            elif brightness < 100 and variance < 2000:\n                class_idx = 3  # Dark & Low Variance\n                confidence = min(0.90, 0.6 + (100 - brightness) / 100)\n            \n            elif g > (r + 15) and g > (b + 15):\n                class_idx = 4  # Green Dominant\n                confidence = min(0.90, 0.6 + (g - max(r, b)) / 100)\n            \n            else:\n                class_idx = 5  # Neutral & Balanced (default)\n                confidence = 0.65\n            \n            predicted_class = class_names[class_idx]\n            \n            grouped_images[predicted_class].append({\n                'path': str(img_path),\n                'confidence': confidence,\n                'brightness': brightness,\n                'variance': variance,\n                'width': width,\n                'height': height,\n                'aspect_ratio': aspect_ratio,\n                'avg_r': r,\n                'avg_g': g,\n                'avg_b': b\n            })\n            \n        except Exception as e:\n            print(f\"⚠️ Error processing {img_path}: {e}\")\n    \n    # Print detailed results\n    print(\"\\n\" + \"=\"*70)\n    print(f\"CLASSIFICATION RESULTS (6 CLASSES)\")\n    print(\"=\"*70)\n    \n    total_classified = 0\n    for class_name in class_names:\n        count = len(grouped_images[class_name])\n        total_classified += count\n        \n        if count > 0:\n            avg_brightness = np.mean([img['brightness'] for img in grouped_images[class_name]])\n            avg_variance = np.mean([img['variance'] for img in grouped_images[class_name]])\n            avg_aspect = np.mean([img['aspect_ratio'] for img in grouped_images[class_name]])\n            avg_confidence = np.mean([img['confidence'] for img in grouped_images[class_name]])\n            \n            print(f\"\\n{class_name}\")\n            print(f\"  Count         : {count:3d} images ({count/len(image_paths)*100:5.1f}%)\")\n            print(f\"  Brightness    : {avg_brightness:6.1f}\")\n            print(f\"  Variance      : {avg_variance:8.1f}\")\n            print(f\"  Aspect Ratio  : {avg_aspect:5.2f}\")\n            print(f\"  Confidence    : {avg_confidence:5.2f}\")\n        else:\n            print(f\"\\n{class_name}\")\n            print(f\"  Count         : {count:3d} images\")\n    \n    print(\"\\n\" + \"=\"*70)\n    print(f\"Total classified: {total_classified}/{len(image_paths)}\")\n    print(\"=\"*70)\n    \n    return grouped_images, class_names\n\n\ndef display_sample_images_per_class(grouped_images, class_names, samples_per_class=3):\n    \"\"\"\n    Display sample images from each class\n    \"\"\"\n    print(\"\\n\" + \"=\"*70)\n    print(\"SAMPLE IMAGES FROM EACH CLASS\")\n    print(\"=\"*70)\n    \n    for class_name in class_names:\n        images = grouped_images[class_name]\n        if len(images) == 0:\n            print(f\"\\n{class_name}: No images\")\n            continue\n        \n        # Get sample indices\n        num_samples = min(samples_per_class, len(images))\n        sample_indices = np.linspace(0, len(images)-1, num_samples, dtype=int)\n        \n        print(f\"\\n{class_name} ({len(images)} images)\")\n        print(\"-\" * 70)\n        \n        fig, axes = plt.subplots(1, num_samples, figsize=(5*num_samples, 5))\n        if num_samples == 1:\n            axes = [axes]\n        \n        for idx, sample_idx in enumerate(sample_indices):\n            img_info = images[sample_idx]\n            img = keras.preprocessing.image.load_img(img_info['path'])\n            \n            axes[idx].imshow(img)\n            axes[idx].axis('off')\n            title = f\"Aspect: {img_info['aspect_ratio']:.2f}\\n\"\n            title += f\"Bright: {img_info['brightness']:.0f}\\n\"\n            title += f\"Conf: {img_info['confidence']:.2f}\"\n            axes[idx].set_title(title, fontsize=10)\n        \n        plt.tight_layout()\n        plt.show()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ===========================\n# 3. Display Grouped Images \n# ===========================\n\ndef display_simple_grouped_images(grouped_images, class_names, images_per_class=6,\n                                 save_path='grouped_images_6classes.png'):\n    \"\"\"Display images grouped by simple classification\"\"\"\n    \n    print(\"\\n\" + \"=\"*70)\n    print(f\"GENERATING GROUPED IMAGE DISPLAY ({len(class_names)} CLASSES)\")\n    print(\"=\"*70)\n    \n    # Filter out empty classes\n    non_empty_classes = [cls for cls in class_names if len(grouped_images[cls]) > 0]\n    \n    if not non_empty_classes:\n        print(\"No images to display!\")\n        return\n    \n    # Create figure\n    fig = plt.figure(figsize=(20, 3 * len(non_empty_classes)))\n    fig.suptitle(f'Images Grouped into {len(class_names)} Classes', \n                 fontsize=16, fontweight='bold', y=0.995)\n    \n    for class_idx, class_name in enumerate(non_empty_classes):\n        images = grouped_images[class_name]\n        \n        # Select images to display\n        display_images = images[:images_per_class]\n        num_display = len(display_images)\n        \n        # Display images for this class\n        for img_idx in range(images_per_class):\n            ax = plt.subplot(len(non_empty_classes), images_per_class, \n                             class_idx * images_per_class + img_idx + 1)\n            \n            if img_idx < num_display:\n                img_data = display_images[img_idx]\n                \n                # Load and display image\n                img = keras.preprocessing.image.load_img(img_data['path'])\n                ax.imshow(img)\n                \n                # Add confidence info\n                confidence = img_data['confidence']\n                ax.set_title(f\"Conf: {confidence:.1%}\", fontsize=8)\n                \n                # Add class name on first image\n                if img_idx == 0:\n                    ax.text(-0.15, 0.5, f\"{class_name}\\n({len(images)} images)\", \n                            transform=ax.transAxes,\n                            fontsize=10, fontweight='bold',\n                            rotation=90, va='center', ha='right')\n            \n            ax.axis('off')\n    \n    plt.tight_layout()\n    plt.savefig(save_path, dpi=150, bbox_inches='tight')\n    print(f\"✓ Grouped images saved to '{save_path}'\")\n    \n    return fig\n\n# ===========================\n# 4. Main Pipeline\n# ===========================\n\ndef main(data_dir, max_images=300):\n    print(\"\\n\" + \"=\"*70)\n    print(f\"PROCESSING UP TO {max_images} IMAGES INTO 6 CLASSES\")\n    print(\"=\"*70)\n    \n    # Run simple classification based on image characteristics\n    print(\"\\n1. Running image-based classification (6 classes)...\")\n    grouped_images, class_names = simple_classify_and_group(data_dir, max_images, num_classes=6)\n    \n    if not grouped_images or all(len(v) == 0 for v in grouped_images.values()):\n        print(\"❌ No images were classified. Cannot proceed.\")\n        return None, None\n    \n    # Display results\n    display_simple_grouped_images(grouped_images, class_names)\n\n    # Summary\n    print(\"\\n\" + \"=\"*70)\n    print(\"SUMMARY\")\n    print(\"=\"*70)\n    total_classified = sum(len(v) for v in grouped_images.values())\n    print(f\"📊 Total images processed: {total_classified}\")\n\n    return grouped_images, class_names\n\n# ===========================\n# 5. Execute\n# ===========================\n\nif __name__ == \"__main__\":\n    # Update this path to your actual directory\n    DATA_DIR = '/kaggle/input/recodai-luc-scientific-image-forgery-detection/train_images/authentic'\n    \n    try:\n        test_paths = get_limited_image_paths(DATA_DIR, max_images=1000)\n        \n        if len(test_paths) == 0:\n            print(f\"\\n❌ No images found in {DATA_DIR}\")\n            print(\"\\n💡 Suggestions:\")\n            print(\"   1. Check if the directory path is correct\")\n            print(\"   2. Verify the directory contains image files\")\n            print(\"   3. Check file permissions\")\n        elif len(test_paths) < 1001:\n            print(f\"\\n⚠️  Found only {len(test_paths)} images\")\n            print(f\"Proceeding with available {len(test_paths)} images...\")\n            grouped_images, class_names = main(DATA_DIR, max_images=len(test_paths))\n        else:\n            print(f\"\\n✓ Found {len(test_paths)} images.\")\n            grouped_images, class_names = main(DATA_DIR, max_images=1000)\n            \n    except Exception as e:\n        print(f\"\\n❌ Error: {e}\")\n        import traceback\n        traceback.print_exc()","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}