{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":103103,"databundleVersionId":13042974,"sourceType":"competition"}],"dockerImageVersionId":31192,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-11-29T10:46:31.169402Z","iopub.execute_input":"2025-11-29T10:46:31.169925Z","iopub.status.idle":"2025-11-29T10:46:33.41184Z","shell.execute_reply.started":"2025-11-29T10:46:31.169895Z","shell.execute_reply":"2025-11-29T10:46:33.410979Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================================\n# 🔍 PATH FINDER UTILITY - RUN THIS FIRST!\n# ============================================================================\n# This cell explores the Kaggle input directory and shows you \n# exactly what files and folders exist.\n\nimport os\nfrom pathlib import Path\n\ndef explore_directory(path, level=0, max_depth=3, max_files=10):\n    \"\"\"\n    Recursively explore directory structure.\n    \n    Args:\n        path: Directory path to explore\n        level: Current depth level\n        max_depth: Maximum depth to explore\n        max_files: Maximum files to show per directory\n    \"\"\"\n    if level > max_depth:\n        return\n    \n    indent = \"│   \" * level\n    \n    try:\n        items = sorted(os.listdir(path))\n    except PermissionError:\n        print(f\"{indent}├── [Permission Denied]\")\n        return\n    \n    # Separate directories and files\n    dirs = [item for item in items if os.path.isdir(os.path.join(path, item))]\n    files = [item for item in items if os.path.isfile(os.path.join(path, item))]\n    \n    # Show directories\n    for d in dirs:\n        full_path = os.path.join(path, d)\n        file_count = len([f for f in os.listdir(full_path) if os.path.isfile(os.path.join(full_path, f))])\n        dir_count = len([f for f in os.listdir(full_path) if os.path.isdir(os.path.join(full_path, f))])\n        print(f\"{indent}📁 {d}/ ({file_count} files, {dir_count} subdirs)\")\n        explore_directory(full_path, level + 1, max_depth, max_files)\n    \n    # Show files (limited)\n    for i, f in enumerate(files[:max_files]):\n        size = os.path.getsize(os.path.join(path, f))\n        size_str = f\"{size/1024:.1f}KB\" if size < 1024*1024 else f\"{size/(1024*1024):.1f}MB\"\n        print(f\"{indent}📄 {f} ({size_str})\")\n    \n    if len(files) > max_files:\n        print(f\"{indent}... and {len(files) - max_files} more files\")\n\n\n# ============================================================================\n# EXPLORE KAGGLE INPUT DIRECTORY\n# ============================================================================\n\nprint(\"=\" * 70)\nprint(\"🔍 KAGGLE INPUT DIRECTORY STRUCTURE\")\nprint(\"=\" * 70)\n\n# The base input path on Kaggle\nINPUT_BASE = Path(\"/kaggle/input\")\n\n# List all datasets attached to this notebook\nprint(\"\\n📦 ATTACHED DATASETS:\")\nprint(\"-\" * 50)\n\nif INPUT_BASE.exists():\n    datasets = list(INPUT_BASE.iterdir())\n    for dataset in datasets:\n        print(f\"\\n🗂️  Dataset: {dataset.name}\")\n        print(f\"    Full Path: {dataset}\")\n        explore_directory(dataset, level=1, max_depth=3, max_files=5)\nelse:\n    print(\"❌ /kaggle/input does not exist!\")\n    print(\"   Are you running this on Kaggle?\")\n\nprint(\"\\n\" + \"=\" * 70)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-29T10:46:33.413227Z","iopub.execute_input":"2025-11-29T10:46:33.413541Z","iopub.status.idle":"2025-11-29T10:46:33.59808Z","shell.execute_reply.started":"2025-11-29T10:46:33.413519Z","shell.execute_reply":"2025-11-29T10:46:33.597364Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# What datasets are attached?\nimport os\nprint(\"Attached Datasets:\")\nprint(os.listdir(\"/kaggle/input\"))\n###################\n# What's inside the dataset?\nimport os\ndataset_name = \"alpha-dent\"  # Change this if different\nbase_path = f\"/kaggle/input/{dataset_name}\"\n\nprint(f\"Contents of {base_path}:\")\nfor item in os.listdir(base_path):\n    full_path = os.path.join(base_path, item)\n    item_type = \"📁 Folder\" if os.path.isdir(full_path) else \"📄 File\"\n    print(f\"  {item_type}: {item}\")\n\n####################\n# Where are the images?\nfrom glob import glob\n\n# Search for all jpg files\njpg_files = glob(\"/kaggle/input/**/*.jpg\", recursive=True)\n\nprint(f\"Found {len(jpg_files)} JPG files\")\nprint(\"\\nSample paths:\")\nfor f in jpg_files[:5]:\n    print(f\"  {f}\")\n\n#####################\n\n# Where are the labels?\nfrom glob import glob\n\n# Search for all txt files\ntxt_files = glob(\"/kaggle/input/**/*.txt\", recursive=True)\n\nprint(f\"Found {len(txt_files)} TXT label files\")\nprint(\"\\nSample paths:\")\nfor f in txt_files[:5]:\n    print(f\"  {f}\")\n\n#################\n\n# Where is the YAML file?\nfrom glob import glob\n\nyaml_files = glob(\"/kaggle/input/**/*.yaml\", recursive=True)\n\nprint(\"YAML files found:\")\nfor f in yaml_files:\n    print(f\"  {f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-29T10:46:33.599229Z","iopub.execute_input":"2025-11-29T10:46:33.59952Z","iopub.status.idle":"2025-11-29T10:46:33.669396Z","shell.execute_reply.started":"2025-11-29T10:46:33.599498Z","shell.execute_reply":"2025-11-29T10:46:33.668607Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================================\n# 🔧 AUTO-CONFIGURE PATHS\n# ============================================================================\n\nimport os\nfrom pathlib import Path\nfrom glob import glob\n\ndef auto_discover_paths():\n    \"\"\"\n    Automatically discover and configure all dataset paths.\n    Returns a dictionary of paths.\n    \"\"\"\n    \n    paths = {}\n    \n    # Base Kaggle paths\n    paths['INPUT_BASE'] = Path(\"/kaggle/input\")\n    paths['WORKING'] = Path(\"/kaggle/working\")\n    \n    # Find the dataset folder (assumes only one dataset attached)\n    datasets = list(paths['INPUT_BASE'].iterdir())\n    \n    if len(datasets) == 0:\n        raise FileNotFoundError(\"No datasets found in /kaggle/input!\")\n    \n    # Use the first dataset (or you can specify by name)\n    paths['DATASET'] = datasets[0]\n    \n    print(f\"✓ Found dataset: {paths['DATASET'].name}\")\n    \n    # Auto-discover images folder\n    possible_image_dirs = [\n        paths['DATASET'] / \"images\",\n        paths['DATASET'] / \"train\",\n        paths['DATASET'] / \"data\" / \"images\",\n    ]\n    \n    for img_dir in possible_image_dirs:\n        if img_dir.exists():\n            paths['IMAGES'] = img_dir\n            break\n    \n    # Auto-discover labels folder\n    possible_label_dirs = [\n        paths['DATASET'] / \"labels\",\n        paths['DATASET'] / \"annotations\",\n        paths['DATASET'] / \"data\" / \"labels\",\n    ]\n    \n    for lbl_dir in possible_label_dirs:\n        if lbl_dir.exists():\n            paths['LABELS'] = lbl_dir\n            break\n    \n    # Auto-discover train/valid/test splits\n    if 'IMAGES' in paths:\n        for split in ['train', 'valid', 'val', 'test']:\n            split_path = paths['IMAGES'] / split\n            if split_path.exists():\n                paths[f'IMAGES_{split.upper()}'] = split_path\n                \n                # Count images\n                img_count = len(list(split_path.glob(\"*.jpg\"))) + len(list(split_path.glob(\"*.png\")))\n                print(f\"  ✓ {split}: {img_count} images\")\n    \n    if 'LABELS' in paths:\n        for split in ['train', 'valid', 'val', 'test']:\n            split_path = paths['LABELS'] / split\n            if split_path.exists():\n                paths[f'LABELS_{split.upper()}'] = split_path\n    \n    # Find YAML config\n    yaml_files = list(paths['DATASET'].glob(\"*.yaml\")) + list(paths['DATASET'].glob(\"**/*.yaml\"))\n    if yaml_files:\n        paths['YAML_CONFIG'] = yaml_files[0]\n        print(f\"  ✓ YAML Config: {yaml_files[0].name}\")\n    \n    return paths\n\n\n# Run auto-discovery\nprint(\"=\" * 60)\nprint(\"🔧 AUTO-DISCOVERING DATASET PATHS\")\nprint(\"=\" * 60)\n\nPATHS = auto_discover_paths()\n\nprint(\"\\n\" + \"=\" * 60)\nprint(\"📋 DISCOVERED PATHS:\")\nprint(\"=\" * 60)\n\nfor key, value in PATHS.items():\n    print(f\"  {key}: {value}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-29T10:46:33.671023Z","iopub.execute_input":"2025-11-29T10:46:33.67128Z","iopub.status.idle":"2025-11-29T10:46:33.712285Z","shell.execute_reply.started":"2025-11-29T10:46:33.67126Z","shell.execute_reply":"2025-11-29T10:46:33.71153Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================================\n# CORRECTED CONFIG CLASS FOR ALPHA-DENT\n# ============================================================================\n\nfrom pathlib import Path\n\nclass Config:\n    \"\"\"Central configuration with auto-discovered paths.\"\"\"\n    \n    # ==================== INPUT PATHS (READ-ONLY) ====================\n    INPUT_PATH = Path(\"/kaggle/input/alpha-dent\")\n    \n    # Images\n    TRAIN_IMAGES = INPUT_PATH / \"images\" / \"train\"\n    VALID_IMAGES = INPUT_PATH / \"images\" / \"valid\"\n    TEST_IMAGES = INPUT_PATH / \"images\" / \"test\"\n    \n    # Labels\n    TRAIN_LABELS = INPUT_PATH / \"labels\" / \"train\"\n    VALID_LABELS = INPUT_PATH / \"labels\" / \"valid\"\n    \n    # YAML Config\n    YAML_CONFIG = INPUT_PATH / \"yolo_seg_train.yaml\"\n    \n    # ==================== OUTPUT PATHS (READ-WRITE) ====================\n    WORKING_PATH = Path(\"/kaggle/working\")\n    \n    # Processed data (Tri-Modal images go here)\n    PROCESSED_PATH = WORKING_PATH / \"processed_data\"\n    PROCESSED_TRAIN_IMAGES = PROCESSED_PATH / \"images\" / \"train\"\n    PROCESSED_VALID_IMAGES = PROCESSED_PATH / \"images\" / \"valid\"\n    PROCESSED_TEST_IMAGES = PROCESSED_PATH / \"images\" / \"test\"\n    PROCESSED_TRAIN_LABELS = PROCESSED_PATH / \"labels\" / \"train\"\n    PROCESSED_VALID_LABELS = PROCESSED_PATH / \"labels\" / \"valid\"\n    \n    # Model outputs\n    MODEL_PATH = WORKING_PATH / \"models\"\n    RESULTS_PATH = WORKING_PATH / \"results\"\n    XAI_PATH = WORKING_PATH / \"xai_visualizations\"\n    \n    # ==================== CLASS NAMES ====================\n    CLASS_NAMES = {\n        0: 'Abrasion',\n        1: 'Filling',\n        2: 'Crown',\n        3: 'Caries_C1',\n        4: 'Caries_C2',\n        5: 'Caries_C3',\n        6: 'Caries_C4',\n        7: 'Caries_C5',\n        8: 'Caries_C6'\n    }\n    \n    NUM_CLASSES = 9\n    \n    # ==================== TRAINING CONFIG ====================\n    IMAGE_SIZE = 640\n    BATCH_SIZE = 4\n    EPOCHS = 10\n    SEED = 42\n\n\n# Verify paths exist\nprint(\"=\" * 60)\nprint(\"🔍 VERIFYING PATHS\")\nprint(\"=\" * 60)\n\npaths_to_check = [\n    (\"Input Path\", Config.INPUT_PATH),\n    (\"Train Images\", Config.TRAIN_IMAGES),\n    (\"Valid Images\", Config.VALID_IMAGES),\n    (\"Test Images\", Config.TEST_IMAGES),\n    (\"Train Labels\", Config.TRAIN_LABELS),\n    (\"Valid Labels\", Config.VALID_LABELS),\n    (\"YAML Config\", Config.YAML_CONFIG),\n]\n\nall_valid = True\nfor name, path in paths_to_check:\n    exists = path.exists()\n    status = \"✓\" if exists else \"✗\"\n    print(f\"  {status} {name}: {path}\")\n    if not exists:\n        all_valid = False\n\nif all_valid:\n    print(\"\\n✅ All paths verified successfully!\")\nelse:\n    print(\"\\n⚠️ Some paths are missing. Please check the dataset structure.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-29T10:46:33.713039Z","iopub.execute_input":"2025-11-29T10:46:33.713281Z","iopub.status.idle":"2025-11-29T10:46:33.723564Z","shell.execute_reply.started":"2025-11-29T10:46:33.713254Z","shell.execute_reply":"2025-11-29T10:46:33.72283Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================================\n# CELL 1: ENVIRONMENT SETUP & LIBRARY INSTALLATION\n# ============================================================================\n# This cell installs all required libraries for the Super-Pipeline\n# Run this first and restart the kernel if prompted\n\n!pip install ultralytics -q\n!pip install sahi -q\n!pip install grad-cam -q\n!pip install albumentations -q\n\n# Verify installations\nimport pkg_resources\nprint(\"=\" * 60)\nprint(\"INSTALLED PACKAGES:\")\nprint(\"=\" * 60)\nfor pkg in ['ultralytics', 'sahi', 'grad-cam']:\n    try:\n        version = pkg_resources.get_distribution(pkg).version\n        print(f\"✓ {pkg}: {version}\")\n    except:\n        print(f\"✗ {pkg}: NOT INSTALLED\")\nprint(\"=\" * 60)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-29T10:46:33.724316Z","iopub.execute_input":"2025-11-29T10:46:33.724513Z","iopub.status.idle":"2025-11-29T11:09:59.418547Z","shell.execute_reply.started":"2025-11-29T10:46:33.724496Z","shell.execute_reply":"2025-11-29T11:09:59.417709Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================================\n# CELL 2: IMPORT LIBRARIES & GLOBAL CONFIGURATION\n# ============================================================================\n\nimport os\nimport cv2\nimport numpy as np\nimport pandas as pd\nimport shutil\nimport yaml\nimport re\nimport matplotlib.pyplot as plt\nfrom pathlib import Path\nfrom tqdm.notebook import tqdm\nfrom glob import glob\nimport warnings\nwarnings.filterwarnings('ignore')\n\n# Ultralytics YOLO\nfrom ultralytics import YOLO\n\n# SAHI for sliced inference\nfrom sahi import AutoDetectionModel\nfrom sahi.predict import get_sliced_prediction\nfrom sahi.utils.cv import visualize_object_predictions\n\n# PyTorch for XAI\nimport torch\nimport torch.nn.functional as F\n\n# ============================================================================\n# GLOBAL CONFIGURATION\n# ============================================================================\n\nclass Config:\n    \"\"\"Central configuration for the entire pipeline.\"\"\"\n    \n    # Paths (Kaggle Structure)\n    INPUT_PATH = Path(\"/kaggle/input/alpha-dent\")\n    WORKING_PATH = Path(\"/kaggle/working\")\n    \n    # Processed Data Paths\n    PROCESSED_PATH = WORKING_PATH / \"processed_data\"\n    PROCESSED_TRAIN_IMAGES = PROCESSED_PATH / \"images\" / \"train\"\n    PROCESSED_VALID_IMAGES = PROCESSED_PATH / \"images\" / \"valid\"\n    PROCESSED_TEST_IMAGES = PROCESSED_PATH / \"images\" / \"test\"\n    PROCESSED_TRAIN_LABELS = PROCESSED_PATH / \"labels\" / \"train\"\n    PROCESSED_VALID_LABELS = PROCESSED_PATH / \"labels\" / \"valid\"\n    \n    # Model Paths\n    MODEL_PATH = WORKING_PATH / \"models\"\n    RESULTS_PATH = WORKING_PATH / \"results\"\n    XAI_PATH = WORKING_PATH / \"xai_visualizations\"\n    \n    # Class Mapping (RENAMED CLASSES)\n    CLASS_NAMES = {\n        0: 'Abrasion',\n        1: 'Filling',\n        2: 'Crown',\n        3: 'Caries_C1',\n        4: 'Caries_C2',\n        5: 'Caries_C3',\n        6: 'Caries_C4',\n        7: 'Caries_C5',\n        8: 'Caries_C6'\n    }\n    \n    NUM_CLASSES = len(CLASS_NAMES)\n    \n    # Training Configuration\n    IMAGE_SIZE = 640\n    BATCH_SIZE = 4\n    EPOCHS = 10  # Short for demo; increase for better results\n    \n    # SAHI Configuration\n    SAHI_SLICE_HEIGHT = 640\n    SAHI_SLICE_WIDTH = 640\n    SAHI_OVERLAP_RATIO = 0.2\n    CONFIDENCE_THRESHOLD = 0.25\n    \n    # CLAHE Configuration\n    CLAHE_CLIP_LIMIT = 2.0\n    CLAHE_TILE_SIZE = (8, 8)\n    \n    # Random Seed\n    SEED = 42\n\n# Set random seeds\nnp.random.seed(Config.SEED)\ntorch.manual_seed(Config.SEED)\n\nprint(\"=\" * 60)\nprint(\"CONFIGURATION LOADED\")\nprint(\"=\" * 60)\nprint(f\"Input Path: {Config.INPUT_PATH}\")\nprint(f\"Working Path: {Config.WORKING_PATH}\")\nprint(f\"Number of Classes: {Config.NUM_CLASSES}\")\nprint(f\"Class Names: {list(Config.CLASS_NAMES.values())}\")\nprint(\"=\" * 60)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-29T11:09:59.419484Z","iopub.execute_input":"2025-11-29T11:09:59.41978Z","iopub.status.idle":"2025-11-29T11:10:00.004254Z","shell.execute_reply.started":"2025-11-29T11:09:59.419754Z","shell.execute_reply":"2025-11-29T11:10:00.00241Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================================\n# CELL 3: CREATE DIRECTORY STRUCTURE\n# ============================================================================\n\ndef create_directories():\n    \"\"\"Create all necessary directories for the pipeline.\"\"\"\n    \n    directories = [\n        Config.PROCESSED_PATH,\n        Config.PROCESSED_TRAIN_IMAGES,\n        Config.PROCESSED_VALID_IMAGES,\n        Config.PROCESSED_TEST_IMAGES,\n        Config.PROCESSED_TRAIN_LABELS,\n        Config.PROCESSED_VALID_LABELS,\n        Config.MODEL_PATH,\n        Config.RESULTS_PATH,\n        Config.XAI_PATH\n    ]\n    \n    for dir_path in directories:\n        dir_path.mkdir(parents=True, exist_ok=True)\n        print(f\"✓ Created: {dir_path}\")\n    \n    print(\"\\n\" + \"=\" * 60)\n    print(\"ALL DIRECTORIES CREATED SUCCESSFULLY\")\n    print(\"=\" * 60)\n\ncreate_directories()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-29T11:10:00.005189Z","iopub.status.idle":"2025-11-29T11:10:00.005564Z","shell.execute_reply.started":"2025-11-29T11:10:00.005371Z","shell.execute_reply":"2025-11-29T11:10:00.005388Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================================\n# CELL 4: DATASET EXPLORATION\n# ============================================================================\n\ndef explore_dataset():\n    \"\"\"Explore and understand the dataset structure.\"\"\"\n    \n    print(\"=\" * 60)\n    print(\"DATASET EXPLORATION\")\n    print(\"=\" * 60)\n    \n    # Check directory structure\n    base_path = Config.INPUT_PATH\n    \n    # Count images in each split\n    train_images = list((base_path / \"images\" / \"train\").glob(\"*.jpg\"))\n    valid_images = list((base_path / \"images\" / \"valid\").glob(\"*.jpg\"))\n    test_images = list((base_path / \"images\" / \"test\").glob(\"*.jpg\"))\n    \n    print(f\"\\n📁 IMAGES:\")\n    print(f\"   Training Images: {len(train_images)}\")\n    print(f\"   Validation Images: {len(valid_images)}\")\n    print(f\"   Test Images: {len(test_images)}\")\n    \n    # Count labels\n    train_labels = list((base_path / \"labels\" / \"train\").glob(\"*.txt\"))\n    valid_labels = list((base_path / \"labels\" / \"valid\").glob(\"*.txt\"))\n    \n    print(f\"\\n📁 LABELS:\")\n    print(f\"   Training Labels: {len(train_labels)}\")\n    print(f\"   Validation Labels: {len(valid_labels)}\")\n    \n    # Sample image info\n    if train_images:\n        sample_img = cv2.imread(str(train_images[0]))\n        print(f\"\\n📐 SAMPLE IMAGE INFO:\")\n        print(f\"   Filename: {train_images[0].name}\")\n        print(f\"   Resolution: {sample_img.shape[1]} x {sample_img.shape[0]}\")\n        print(f\"   Channels: {sample_img.shape[2]}\")\n        print(f\"   Size: {os.path.getsize(train_images[0]) / (1024*1024):.2f} MB\")\n    \n    # Parse filename to extract metadata\n    print(f\"\\n🏷️ FILENAME STRUCTURE EXAMPLE:\")\n    sample_name = train_images[0].stem if train_images else \"p001_F_32_001\"\n    print(f\"   Filename: {sample_name}\")\n    \n    # Extract metadata using regex\n    pattern = r'p(\\d+)_([MF])_(\\d+)_(\\d+)'\n    match = re.match(pattern, sample_name)\n    if match:\n        patient_id, gender, age, image_id = match.groups()\n        print(f\"   Patient ID: {patient_id}\")\n        print(f\"   Gender: {'Female' if gender == 'F' else 'Male'}\")\n        print(f\"   Age: {age} years\")\n        print(f\"   Image ID: {image_id}\")\n    \n    return train_images, valid_images, test_images\n\ntrain_images, valid_images, test_images = explore_dataset()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-29T11:10:00.007182Z","iopub.status.idle":"2025-11-29T11:10:00.007658Z","shell.execute_reply.started":"2025-11-29T11:10:00.007345Z","shell.execute_reply":"2025-11-29T11:10:00.007357Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================================\n# CELL 5: TRI-MODAL PREPROCESSING FUNCTIONS\n# ============================================================================\n\nclass TriModalPreprocessor:\n    \"\"\"\n    Creates a 3-channel image from a single grayscale X-ray:\n    - Channel 1 (R): Original Grayscale\n    - Channel 2 (G): CLAHE Enhanced (highlights caries)\n    - Channel 3 (B): Sobel Edge Detection (highlights geometry/crowns)\n    \"\"\"\n    \n    def __init__(self, clahe_clip_limit=2.0, clahe_tile_size=(8, 8)):\n        self.clahe = cv2.createCLAHE(\n            clipLimit=clahe_clip_limit, \n            tileGridSize=clahe_tile_size\n        )\n    \n    def apply_clahe(self, gray_image):\n        \"\"\"Apply CLAHE for contrast enhancement.\"\"\"\n        return self.clahe.apply(gray_image)\n    \n    def apply_sobel_edges(self, gray_image):\n        \"\"\"Apply Sobel edge detection for geometry extraction.\"\"\"\n        # Compute gradients in X and Y directions\n        sobel_x = cv2.Sobel(gray_image, cv2.CV_64F, 1, 0, ksize=3)\n        sobel_y = cv2.Sobel(gray_image, cv2.CV_64F, 0, 1, ksize=3)\n        \n        # Compute magnitude\n        magnitude = cv2.magnitude(sobel_x, sobel_y)\n        \n        # Normalize to 0-255\n        edges = cv2.normalize(magnitude, None, 0, 255, cv2.NORM_MINMAX)\n        return edges.astype(np.uint8)\n    \n    def create_trimodal_image(self, image_path):\n        \"\"\"\n        Create the 3-channel Tri-Modal image.\n        \n        Args:\n            image_path: Path to the input image\n            \n        Returns:\n            trimodal_image: 3-channel numpy array (H, W, 3)\n        \"\"\"\n        # Read image\n        img = cv2.imread(str(image_path))\n        \n        if img is None:\n            raise ValueError(f\"Could not read image: {image_path}\")\n        \n        # Convert to grayscale if needed\n        if len(img.shape) == 3:\n            gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)\n        else:\n            gray = img\n        \n        # Channel 1: Original Grayscale\n        channel_1 = gray\n        \n        # Channel 2: CLAHE Enhanced\n        channel_2 = self.apply_clahe(gray)\n        \n        # Channel 3: Sobel Edges\n        channel_3 = self.apply_sobel_edges(gray)\n        \n        # Merge channels (BGR format for OpenCV)\n        trimodal_image = cv2.merge([channel_1, channel_2, channel_3])\n        \n        return trimodal_image\n    \n    def visualize_channels(self, image_path, figsize=(15, 5)):\n        \"\"\"Visualize all three channels separately.\"\"\"\n        trimodal = self.create_trimodal_image(image_path)\n        \n        fig, axes = plt.subplots(1, 4, figsize=figsize)\n        \n        # Original\n        original = cv2.imread(str(image_path))\n        axes[0].imshow(cv2.cvtColor(original, cv2.COLOR_BGR2RGB))\n        axes[0].set_title(\"Original Image\")\n        axes[0].axis('off')\n        \n        # Channel 1: Grayscale\n        axes[1].imshow(trimodal[:, :, 0], cmap='gray')\n        axes[1].set_title(\"Ch1: Grayscale\")\n        axes[1].axis('off')\n        \n        # Channel 2: CLAHE\n        axes[2].imshow(trimodal[:, :, 1], cmap='gray')\n        axes[2].set_title(\"Ch2: CLAHE (Caries)\")\n        axes[2].axis('off')\n        \n        # Channel 3: Edges\n        axes[3].imshow(trimodal[:, :, 2], cmap='gray')\n        axes[3].set_title(\"Ch3: Sobel (Geometry)\")\n        axes[3].axis('off')\n        \n        plt.tight_layout()\n        plt.savefig(Config.RESULTS_PATH / \"trimodal_visualization.png\", dpi=150)\n        plt.show()\n        \n        return trimodal\n\n# Initialize preprocessor\npreprocessor = TriModalPreprocessor(\n    clahe_clip_limit=Config.CLAHE_CLIP_LIMIT,\n    clahe_tile_size=Config.CLAHE_TILE_SIZE\n)\n\n# Visualize on a sample image\nif train_images:\n    print(\"Visualizing Tri-Modal Preprocessing on Sample Image...\")\n    sample_trimodal = preprocessor.visualize_channels(train_images[0])\n    print(f\"Tri-Modal Image Shape: {sample_trimodal.shape}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-29T11:10:00.009366Z","iopub.status.idle":"2025-11-29T11:10:00.009764Z","shell.execute_reply.started":"2025-11-29T11:10:00.009569Z","shell.execute_reply":"2025-11-29T11:10:00.009585Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================================\n# CELL 6: PROCESS ENTIRE DATASET WITH TRI-MODAL PREPROCESSING\n# ============================================================================\n\ndef process_dataset_split(\n    input_image_dir, \n    input_label_dir, \n    output_image_dir, \n    output_label_dir,\n    preprocessor,\n    max_images=None  # Set to a number to limit for testing\n):\n    \"\"\"\n    Process all images in a split with Tri-Modal preprocessing.\n    \n    Args:\n        input_image_dir: Path to input images\n        input_label_dir: Path to input labels\n        output_image_dir: Path to save processed images\n        output_label_dir: Path to save labels\n        preprocessor: TriModalPreprocessor instance\n        max_images: Maximum number of images to process (None for all)\n    \"\"\"\n    \n    image_paths = list(Path(input_image_dir).glob(\"*.jpg\"))\n    \n    if max_images:\n        image_paths = image_paths[:max_images]\n    \n    processed_count = 0\n    error_count = 0\n    \n    print(f\"Processing {len(image_paths)} images...\")\n    \n    for img_path in tqdm(image_paths, desc=\"Processing\"):\n        try:\n            # Create Tri-Modal image\n            trimodal_img = preprocessor.create_trimodal_image(img_path)\n            \n            # Save processed image\n            output_img_path = output_image_dir / img_path.name\n            cv2.imwrite(str(output_img_path), trimodal_img)\n            \n            # Copy corresponding label file\n            label_filename = img_path.stem + \".txt\"\n            input_label_path = input_label_dir / label_filename\n            output_label_path = output_label_dir / label_filename\n            \n            if input_label_path.exists():\n                shutil.copy(str(input_label_path), str(output_label_path))\n            \n            processed_count += 1\n            \n        except Exception as e:\n            print(f\"Error processing {img_path.name}: {e}\")\n            error_count += 1\n    \n    return processed_count, error_count\n\n\ndef process_test_images(\n    input_image_dir, \n    output_image_dir, \n    preprocessor,\n    max_images=None\n):\n    \"\"\"Process test images (no labels).\"\"\"\n    \n    image_paths = list(Path(input_image_dir).glob(\"*.jpg\"))\n    \n    if max_images:\n        image_paths = image_paths[:max_images]\n    \n    processed_count = 0\n    \n    print(f\"Processing {len(image_paths)} test images...\")\n    \n    for img_path in tqdm(image_paths, desc=\"Processing Test\"):\n        try:\n            trimodal_img = preprocessor.create_trimodal_image(img_path)\n            output_img_path = output_image_dir / img_path.name\n            cv2.imwrite(str(output_img_path), trimodal_img)\n            processed_count += 1\n        except Exception as e:\n            print(f\"Error processing {img_path.name}: {e}\")\n    \n    return processed_count\n\n\n# ============================================================================\n# EXECUTE PREPROCESSING\n# ============================================================================\n\nprint(\"=\" * 60)\nprint(\"STARTING TRI-MODAL PREPROCESSING\")\nprint(\"=\" * 60)\n\n# Process Training Set\nprint(\"\\n📁 Processing TRAINING set...\")\ntrain_processed, train_errors = process_dataset_split(\n    input_image_dir=Config.INPUT_PATH / \"images\" / \"train\",\n    input_label_dir=Config.INPUT_PATH / \"labels\" / \"train\",\n    output_image_dir=Config.PROCESSED_TRAIN_IMAGES,\n    output_label_dir=Config.PROCESSED_TRAIN_LABELS,\n    preprocessor=preprocessor,\n    max_images=None  # Set to 100 for quick testing\n)\nprint(f\"   ✓ Processed: {train_processed}, Errors: {train_errors}\")\n\n# Process Validation Set\nprint(\"\\n📁 Processing VALIDATION set...\")\nvalid_processed, valid_errors = process_dataset_split(\n    input_image_dir=Config.INPUT_PATH / \"images\" / \"valid\",\n    input_label_dir=Config.INPUT_PATH / \"labels\" / \"valid\",\n    output_image_dir=Config.PROCESSED_VALID_IMAGES,\n    output_label_dir=Config.PROCESSED_VALID_LABELS,\n    preprocessor=preprocessor,\n    max_images=None\n)\nprint(f\"   ✓ Processed: {valid_processed}, Errors: {valid_errors}\")\n\n# Process Test Set\nprint(\"\\n📁 Processing TEST set...\")\ntest_processed = process_test_images(\n    input_image_dir=Config.INPUT_PATH / \"images\" / \"test\",\n    output_image_dir=Config.PROCESSED_TEST_IMAGES,\n    preprocessor=preprocessor,\n    max_images=None\n)\nprint(f\"   ✓ Processed: {test_processed}\")\n\nprint(\"\\n\" + \"=\" * 60)\nprint(\"TRI-MODAL PREPROCESSING COMPLETE\")\nprint(\"=\" * 60)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-29T11:10:00.011526Z","iopub.status.idle":"2025-11-29T11:10:00.011939Z","shell.execute_reply.started":"2025-11-29T11:10:00.011729Z","shell.execute_reply":"2025-11-29T11:10:00.011746Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================================\n# CELL 7: CREATE CUSTOM YAML CONFIGURATION FOR YOLO\n# ============================================================================\n\ndef create_custom_yaml():\n    \"\"\"\n    Create a custom YAML configuration file for YOLOv8 training.\n    This file tells YOLO where to find the data and what classes to use.\n    \"\"\"\n    \n    yaml_content = {\n        'path': str(Config.PROCESSED_PATH),\n        'train': 'images/train',\n        'val': 'images/valid',\n        'test': 'images/test',\n        'nc': Config.NUM_CLASSES,\n        'names': list(Config.CLASS_NAMES.values())\n    }\n    \n    yaml_path = Config.PROCESSED_PATH / \"custom_data.yaml\"\n    \n    with open(yaml_path, 'w') as f:\n        yaml.dump(yaml_content, f, default_flow_style=False, sort_keys=False)\n    \n    print(\"=\" * 60)\n    print(\"CUSTOM YAML CONFIGURATION CREATED\")\n    print(\"=\" * 60)\n    print(f\"Path: {yaml_path}\")\n    print(\"\\nContent:\")\n    print(\"-\" * 40)\n    with open(yaml_path, 'r') as f:\n        print(f.read())\n    print(\"-\" * 40)\n    \n    return yaml_path\n\nyaml_path = create_custom_yaml()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-29T11:10:00.013068Z","iopub.status.idle":"2025-11-29T11:10:00.013372Z","shell.execute_reply.started":"2025-11-29T11:10:00.013247Z","shell.execute_reply":"2025-11-29T11:10:00.013259Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================================\n# CELL 8: TRAIN YOLOv8 SEGMENTATION MODEL\n# ============================================================================\n\ndef train_yolo_model(yaml_path, epochs=10, imgsz=640, batch_size=4):\n    \"\"\"\n    Train YOLOv8 Large Segmentation model on the processed dataset.\n    \n    Args:\n        yaml_path: Path to the custom data YAML file\n        epochs: Number of training epochs\n        imgsz: Image size for training\n        batch_size: Batch size\n        \n    Returns:\n        model: Trained YOLO model\n        results: Training results\n    \"\"\"\n    \n    print(\"=\" * 60)\n    print(\"TRAINING YOLOv8-L SEGMENTATION MODEL\")\n    print(\"=\" * 60)\n    \n    # Initialize model with pretrained weights\n    model = YOLO('yolov8l-seg.pt')\n    \n    print(f\"\\n📊 Training Configuration:\")\n    print(f\"   Model: YOLOv8-L Segmentation\")\n    print(f\"   Epochs: {epochs}\")\n    print(f\"   Image Size: {imgsz}\")\n    print(f\"   Batch Size: {batch_size}\")\n    print(f\"   Data Config: {yaml_path}\")\n    \n    # Train the model\n    results = model.train(\n        data=str(yaml_path),\n        epochs=epochs,\n        imgsz=imgsz,\n        batch=batch_size,\n        project=str(Config.MODEL_PATH),\n        name='alphadent_trimodal',\n        exist_ok=True,\n        pretrained=True,\n        optimizer='AdamW',\n        lr0=0.001,\n        lrf=0.01,\n        momentum=0.937,\n        weight_decay=0.0005,\n        warmup_epochs=3,\n        warmup_momentum=0.8,\n        box=7.5,\n        cls=0.5,\n        dfl=1.5,\n        pose=12.0,\n        kobj=1.0,\n        label_smoothing=0.0,\n        nbs=64,\n        hsv_h=0.015,\n        hsv_s=0.7,\n        hsv_v=0.4,\n        degrees=0.0,\n        translate=0.1,\n        scale=0.5,\n        shear=0.0,\n        perspective=0.0,\n        flipud=0.0,\n        fliplr=0.5,\n        mosaic=1.0,\n        mixup=0.0,\n        copy_paste=0.0,\n        device=0,  # Use GPU 0\n        workers=4,\n        seed=Config.SEED,\n        verbose=True,\n        val=True,\n        plots=True,\n        save=True\n    )\n    \n    print(\"\\n\" + \"=\" * 60)\n    print(\"TRAINING COMPLETE\")\n    print(\"=\" * 60)\n    \n    # Save best model path\n    best_model_path = Config.MODEL_PATH / \"alphadent_trimodal\" / \"weights\" / \"best.pt\"\n    print(f\"Best Model Saved: {best_model_path}\")\n    \n    return model, results, best_model_path\n\n\n# Execute Training\nmodel, training_results, best_model_path = train_yolo_model(\n    yaml_path=yaml_path,\n    epochs=Config.EPOCHS,\n    imgsz=Config.IMAGE_SIZE,\n    batch_size=Config.BATCH_SIZE\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-29T11:10:00.014849Z","iopub.status.idle":"2025-11-29T11:10:00.015212Z","shell.execute_reply.started":"2025-11-29T11:10:00.015034Z","shell.execute_reply":"2025-11-29T11:10:00.015051Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================================\n# CELL 9: SAHI (SLICING AIDED HYPER INFERENCE) SETUP\n# ============================================================================\n\nclass SAHIInference:\n    \"\"\"\n    SAHI-based inference for high-resolution dental X-rays.\n    Slices large images into tiles, performs inference, and stitches results.\n    \"\"\"\n    \n    def __init__(\n        self, \n        model_path,\n        confidence_threshold=0.25,\n        slice_height=640,\n        slice_width=640,\n        overlap_ratio=0.2,\n        device='cuda:0'\n    ):\n        self.model_path = model_path\n        self.confidence_threshold = confidence_threshold\n        self.slice_height = slice_height\n        self.slice_width = slice_width\n        self.overlap_ratio = overlap_ratio\n        self.device = device\n        \n        # Initialize SAHI detection model\n        self.detection_model = AutoDetectionModel.from_pretrained(\n            model_type='yolov8',\n            model_path=str(model_path),\n            confidence_threshold=confidence_threshold,\n            device=device\n        )\n        \n        print(\"=\" * 60)\n        print(\"SAHI INFERENCE ENGINE INITIALIZED\")\n        print(\"=\" * 60)\n        print(f\"Model Path: {model_path}\")\n        print(f\"Slice Size: {slice_height} x {slice_width}\")\n        print(f\"Overlap Ratio: {overlap_ratio}\")\n        print(f\"Confidence Threshold: {confidence_threshold}\")\n        print(f\"Device: {device}\")\n    \n    def predict_single_image(self, image_path, visualize=False):\n        \"\"\"\n        Perform sliced prediction on a single high-resolution image.\n        \n        Args:\n            image_path: Path to the image\n            visualize: Whether to create visualization\n            \n        Returns:\n            result: SAHI prediction result\n        \"\"\"\n        result = get_sliced_prediction(\n            image=str(image_path),\n            detection_model=self.detection_model,\n            slice_height=self.slice_height,\n            slice_width=self.slice_width,\n            overlap_height_ratio=self.overlap_ratio,\n            overlap_width_ratio=self.overlap_ratio,\n            perform_standard_pred=True,\n            postprocess_type=\"GREEDYNMM\",\n            postprocess_match_metric=\"IOS\",\n            postprocess_match_threshold=0.5,\n            verbose=0\n        )\n        \n        if visualize:\n            self._visualize_prediction(image_path, result)\n        \n        return result\n    \n    def _visualize_prediction(self, image_path, result):\n        \"\"\"Visualize predictions on the image.\"\"\"\n        image = cv2.imread(str(image_path))\n        image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n        \n        # Draw predictions\n        for pred in result.object_prediction_list:\n            bbox = pred.bbox\n            category = pred.category\n            score = pred.score.value\n            \n            # Draw bounding box\n            x1, y1, x2, y2 = int(bbox.minx), int(bbox.miny), int(bbox.maxx), int(bbox.maxy)\n            cv2.rectangle(image, (x1, y1), (x2, y2), (0, 255, 0), 2)\n            \n            # Draw label\n            label = f\"{Config.CLASS_NAMES.get(category.id, category.name)}: {score:.2f}\"\n            cv2.putText(image, label, (x1, y1 - 10), \n                       cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)\n        \n        plt.figure(figsize=(15, 10))\n        plt.imshow(image)\n        plt.title(f\"SAHI Predictions: {Path(image_path).name}\")\n        plt.axis('off')\n        plt.tight_layout()\n        plt.savefig(Config.RESULTS_PATH / f\"sahi_pred_{Path(image_path).stem}.png\", dpi=150)\n        plt.show()\n    \n    def predict_batch(self, image_dir, max_images=None):\n        \"\"\"\n        Perform predictions on all images in a directory.\n        \n        Args:\n            image_dir: Path to directory containing images\n            max_images: Maximum number of images to process\n            \n        Returns:\n            all_results: Dictionary mapping image names to predictions\n        \"\"\"\n        image_paths = list(Path(image_dir).glob(\"*.jpg\"))\n        \n        if max_images:\n            image_paths = image_paths[:max_images]\n        \n        all_results = {}\n        \n        print(f\"Running SAHI inference on {len(image_paths)} images...\")\n        \n        for img_path in tqdm(image_paths, desc=\"SAHI Inference\"):\n            result = self.predict_single_image(img_path, visualize=False)\n            all_results[img_path.name] = result\n        \n        return all_results\n\n\n# Initialize SAHI\nprint(\"\\nInitializing SAHI Inference Engine...\")\nsahi_engine = SAHIInference(\n    model_path=best_model_path,\n    confidence_threshold=Config.CONFIDENCE_THRESHOLD,\n    slice_height=Config.SAHI_SLICE_HEIGHT,\n    slice_width=Config.SAHI_SLICE_WIDTH,\n    overlap_ratio=Config.SAHI_OVERLAP_RATIO\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-29T11:10:00.017588Z","iopub.status.idle":"2025-11-29T11:10:00.017928Z","shell.execute_reply.started":"2025-11-29T11:10:00.017746Z","shell.execute_reply":"2025-11-29T11:10:00.017757Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================================\n# CELL 10: MULTIMODAL POST-PROCESSING (AGE-BASED FILTERING)\n# ============================================================================\n\nclass MultimodalPostProcessor:\n    \"\"\"\n    Post-processor that uses patient metadata (extracted from filename)\n    to refine predictions based on medical knowledge.\n    \"\"\"\n    \n    def __init__(self, class_names):\n        self.class_names = class_names\n        \n        # Define age-based rules\n        self.rules = {\n            'child_penalty': {\n                'age_threshold': 12,\n                'affected_classes': [0, 2],  # Abrasion, Crown\n                'penalty_factor': 0.5  # Reduce confidence by 50%\n            },\n            'senior_boost': {\n                'age_threshold': 50,\n                'affected_classes': [0],  # Abrasion\n                'boost_factor': 1.2  # Increase confidence by 20%\n            }\n        }\n    \n    def extract_metadata_from_filename(self, filename):\n        \"\"\"\n        Extract patient metadata from filename.\n        Format: p001_F_32_001.jpg\n        \n        Returns:\n            dict: {'patient_id': str, 'gender': str, 'age': int, 'image_id': str}\n        \"\"\"\n        stem = Path(filename).stem\n        \n        # Pattern for training/validation images\n        pattern = r'p(\\d+)_([MF])_(\\d+)_(\\d+)'\n        match = re.match(pattern, stem)\n        \n        if match:\n            patient_id, gender, age, image_id = match.groups()\n            return {\n                'patient_id': patient_id,\n                'gender': 'Female' if gender == 'F' else 'Male',\n                'age': int(age),\n                'image_id': image_id,\n                'has_metadata': True\n            }\n        \n        # Test images don't have metadata\n        return {\n            'patient_id': None,\n            'gender': None,\n            'age': None,\n            'image_id': None,\n            'has_metadata': False\n        }\n    \n    def apply_age_filter(self, predictions, age):\n        \"\"\"\n        Apply age-based filtering rules to predictions.\n        \n        Args:\n            predictions: List of SAHI ObjectPrediction objects\n            age: Patient age (int or None)\n            \n        Returns:\n            filtered_predictions: List of filtered predictions\n        \"\"\"\n        if age is None:\n            return predictions  # No filtering if age unknown\n        \n        filtered_predictions = []\n        \n        for pred in predictions:\n            class_id = pred.category.id\n            original_score = pred.score.value\n            adjusted_score = original_score\n            \n            # Rule 1: Child Penalty\n            if age < self.rules['child_penalty']['age_threshold']:\n                if class_id in self.rules['child_penalty']['affected_classes']:\n                    adjusted_score *= self.rules['child_penalty']['penalty_factor']\n            \n            # Rule 2: Senior Boost\n            if age >= self.rules['senior_boost']['age_threshold']:\n                if class_id in self.rules['senior_boost']['affected_classes']:\n                    adjusted_score *= self.rules['senior_boost']['boost_factor']\n                    adjusted_score = min(adjusted_score, 1.0)  # Cap at 1.0\n            \n            # Only keep predictions above threshold after adjustment\n            if adjusted_score >= Config.CONFIDENCE_THRESHOLD:\n                # Create modified prediction with adjusted score\n                pred.score.value = adjusted_score\n                filtered_predictions.append(pred)\n        \n        return filtered_predictions\n    \n    def process_results(self, sahi_results):\n        \"\"\"\n        Process all SAHI results with multimodal filtering.\n        \n        Args:\n            sahi_results: Dictionary mapping filenames to SAHI results\n            \n        Returns:\n            processed_results: Dictionary with filtered predictions and metadata\n        \"\"\"\n        processed_results = {}\n        \n        print(\"=\" * 60)\n        print(\"APPLYING MULTIMODAL POST-PROCESSING\")\n        print(\"=\" * 60)\n        \n        for filename, result in tqdm(sahi_results.items(), desc=\"Post-Processing\"):\n            # Extract metadata\n            metadata = self.extract_metadata_from_filename(filename)\n            \n            # Get predictions\n            original_predictions = result.object_prediction_list\n            original_count = len(original_predictions)\n            \n            # Apply age filter\n            filtered_predictions = self.apply_age_filter(\n                original_predictions, \n                metadata['age']\n            )\n            filtered_count = len(filtered_predictions)\n            \n            # Store results\n            processed_results[filename] = {\n                'metadata': metadata,\n                'original_count': original_count,\n                'filtered_count': filtered_count,\n                'predictions': filtered_predictions,\n                'removed_count': original_count - filtered_count\n            }\n        \n        # Summary statistics\n        total_original = sum(r['original_count'] for r in processed_results.values())\n        total_filtered = sum(r['filtered_count'] for r in processed_results.values())\n        total_removed = sum(r['removed_count'] for r in processed_results.values())\n        \n        print(\"\\n📊 POST-PROCESSING SUMMARY:\")\n        print(f\"   Original Predictions: {total_original}\")\n        print(f\"   After Filtering: {total_filtered}\")\n        print(f\"   Removed: {total_removed} ({100*total_removed/max(total_original,1):.1f}%)\")\n        \n        return processed_results\n\n\n# Initialize Post-Processor\npostprocessor = MultimodalPostProcessor(Config.CLASS_NAMES)\n\n# Example: Extract metadata from sample filename\nsample_metadata = postprocessor.extract_metadata_from_filename(\"p001_F_32_001.jpg\")\nprint(\"\\n📋 SAMPLE METADATA EXTRACTION:\")\nfor key, value in sample_metadata.items():\n    print(f\"   {key}: {value}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-29T11:10:00.019289Z","iopub.status.idle":"2025-11-29T11:10:00.019664Z","shell.execute_reply.started":"2025-11-29T11:10:00.019491Z","shell.execute_reply":"2025-11-29T11:10:00.019508Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================================\n# CELL 11: RUN FULL INFERENCE PIPELINE ON VALIDATION SET\n# ============================================================================\n\ndef run_full_inference_pipeline(sahi_engine, postprocessor, image_dir, max_images=10):\n    \"\"\"\n    Run the complete inference pipeline:\n    1. SAHI sliced inference\n    2. Multimodal post-processing\n    \n    Args:\n        sahi_engine: SAHIInference instance\n        postprocessor: MultimodalPostProcessor instance\n        image_dir: Path to images\n        max_images: Maximum images to process (for demo)\n        \n    Returns:\n        final_results: Processed results dictionary\n    \"\"\"\n    print(\"=\" * 60)\n    print(\"RUNNING FULL INFERENCE PIPELINE\")\n    print(\"=\" * 60)\n    \n    # Step 1: SAHI Inference\n    print(\"\\n🔍 STEP 1: SAHI Sliced Inference\")\n    sahi_results = sahi_engine.predict_batch(image_dir, max_images=max_images)\n    \n    # Step 2: Multimodal Post-Processing\n    print(\"\\n🧠 STEP 2: Multimodal Post-Processing\")\n    final_results = postprocessor.process_results(sahi_results)\n    \n    return final_results, sahi_results\n\n\n# Run on Validation Set (limit to 10 images for demo)\nfinal_results, raw_sahi_results = run_full_inference_pipeline(\n    sahi_engine=sahi_engine,\n    postprocessor=postprocessor,\n    image_dir=Config.PROCESSED_VALID_IMAGES,\n    max_images=10\n)\n\n# Visualize one result\nsample_image = list(final_results.keys())[0]\nprint(f\"\\n📸 SAMPLE RESULT: {sample_image}\")\nprint(f\"   Age: {final_results[sample_image]['metadata']['age']}\")\nprint(f\"   Gender: {final_results[sample_image]['metadata']['gender']}\")\nprint(f\"   Original Detections: {final_results[sample_image]['original_count']}\")\nprint(f\"   After Filtering: {final_results[sample_image]['filtered_count']}\")\n\n# Visualize with SAHI\nsample_path = Config.PROCESSED_VALID_IMAGES / sample_image\nsahi_engine.predict_single_image(sample_path, visualize=True)# ============================================================================\n# CELL 11: RUN FULL INFERENCE PIPELINE ON VALIDATION SET\n# ============================================================================\n\ndef run_full_inference_pipeline(sahi_engine, postprocessor, image_dir, max_images=10):\n    \"\"\"\n    Run the complete inference pipeline:\n    1. SAHI sliced inference\n    2. Multimodal post-processing\n    \n    Args:\n        sahi_engine: SAHIInference instance\n        postprocessor: MultimodalPostProcessor instance\n        image_dir: Path to images\n        max_images: Maximum images to process (for demo)\n        \n    Returns:\n        final_results: Processed results dictionary\n    \"\"\"\n    print(\"=\" * 60)\n    print(\"RUNNING FULL INFERENCE PIPELINE\")\n    print(\"=\" * 60)\n    \n    # Step 1: SAHI Inference\n    print(\"\\n🔍 STEP 1: SAHI Sliced Inference\")\n    sahi_results = sahi_engine.predict_batch(image_dir, max_images=max_images)\n    \n    # Step 2: Multimodal Post-Processing\n    print(\"\\n🧠 STEP 2: Multimodal Post-Processing\")\n    final_results = postprocessor.process_results(sahi_results)\n    \n    return final_results, sahi_results\n\n\n# Run on Validation Set (limit to 10 images for demo)\nfinal_results, raw_sahi_results = run_full_inference_pipeline(\n    sahi_engine=sahi_engine,\n    postprocessor=postprocessor,\n    image_dir=Config.PROCESSED_VALID_IMAGES,\n    max_images=10\n)\n\n# Visualize one result\nsample_image = list(final_results.keys())[0]\nprint(f\"\\n📸 SAMPLE RESULT: {sample_image}\")\nprint(f\"   Age: {final_results[sample_image]['metadata']['age']}\")\nprint(f\"   Gender: {final_results[sample_image]['metadata']['gender']}\")\nprint(f\"   Original Detections: {final_results[sample_image]['original_count']}\")\nprint(f\"   After Filtering: {final_results[sample_image]['filtered_count']}\")\n\n# Visualize with SAHI\nsample_path = Config.PROCESSED_VALID_IMAGES / sample_image\nsahi_engine.predict_single_image(sample_path, visualize=True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-29T11:10:00.020691Z","iopub.status.idle":"2025-11-29T11:10:00.020964Z","shell.execute_reply.started":"2025-11-29T11:10:00.020842Z","shell.execute_reply":"2025-11-29T11:10:00.020855Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================================\n# CELL 12: EXPLAINABLE AI (XAI) WITH EIGENCAM\n# ============================================================================\n\nclass YOLOExplainer:\n    \"\"\"\n    Explainable AI for YOLO segmentation model using GradCAM-style techniques.\n    Visualizes which regions of the image the model focuses on.\n    \"\"\"\n    \n    def __init__(self, model_path, class_names, device='cuda:0'):\n        self.device = device\n        self.class_names = class_names\n        \n        # Load YOLO model\n        self.model = YOLO(str(model_path))\n        \n        print(\"=\" * 60)\n        print(\"XAI EXPLAINER INITIALIZED\")\n        print(\"=\" * 60)\n    \n    def get_activation_maps(self, image_path, target_class=None):\n        \"\"\"\n        Generate activation maps showing model focus areas.\n        \n        Args:\n            image_path: Path to input image\n            target_class: Specific class to highlight (None for all)\n            \n        Returns:\n            heatmap: Activation heatmap\n            overlay: Image with heatmap overlay\n        \"\"\"\n        # Read and preprocess image\n        image = cv2.imread(str(image_path))\n        image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n        original_size = image.shape[:2]\n        \n        # Run inference to get predictions\n        results = self.model.predict(\n            source=str(image_path),\n            conf=0.25,\n            save=False,\n            verbose=False\n        )\n        \n        if len(results) == 0 or len(results[0].boxes) == 0:\n            print(\"No detections found!\")\n            return None, image_rgb\n        \n        # Create attention map based on detection boxes and masks\n        attention_map = np.zeros(original_size, dtype=np.float32)\n        \n        for i, box in enumerate(results[0].boxes):\n            class_id = int(box.cls[0])\n            confidence = float(box.conf[0])\n            \n            # Filter by target class if specified\n            if target_class is not None and class_id != target_class:\n                continue\n            \n            # Get bounding box coordinates\n            x1, y1, x2, y2 = map(int, box.xyxy[0])\n            \n            # Add to attention map (weighted by confidence)\n            attention_map[y1:y2, x1:x2] += confidence\n        \n        # Normalize attention map\n        if attention_map.max() > 0:\n            attention_map = attention_map / attention_map.max()\n        \n        # Apply Gaussian blur for smoother visualization\n        attention_map = cv2.GaussianBlur(attention_map, (51, 51), 0)\n        \n        # Create heatmap\n        heatmap = cv2.applyColorMap(\n            (attention_map * 255).astype(np.uint8), \n            cv2.COLORMAP_JET\n        )\n        heatmap = cv2.cvtColor(heatmap, cv2.COLOR_BGR2RGB)\n        \n        # Create overlay\n        overlay = cv2.addWeighted(image_rgb, 0.6, heatmap, 0.4, 0)\n        \n        return attention_map, overlay, results[0]\n    \n    def visualize_explanation(self, image_path, target_class=None, figsize=(20, 6)):\n        \"\"\"\n        Create comprehensive XAI visualization.\n        \n        Args:\n            image_path: Path to input image\n            target_class: Class ID to focus on (None for all)\n            figsize: Figure size\n        \"\"\"\n        print(f\"\\n🔍 Generating XAI Explanation for: {Path(image_path).name}\")\n        \n        attention_map, overlay, results = self.get_activation_maps(\n            image_path, \n            target_class\n        )\n        \n        if attention_map is None:\n            print(\"Cannot generate explanation - no detections found.\")\n            return\n        \n        # Read original image\n        original = cv2.imread(str(image_path))\n        original = cv2.cvtColor(original, cv2.COLOR_BGR2RGB)\n        \n        # Create figure\n        fig, axes = plt.subplots(1, 4, figsize=figsize)\n        \n        # Panel 1: Original Image\n        axes[0].imshow(original)\n        axes[0].set_title(\"Original Tri-Modal Image\", fontsize=12)\n        axes[0].axis('off')\n        \n        # Panel 2: Detections\n        detection_img = original.copy()\n        for box in results.boxes:\n            x1, y1, x2, y2 = map(int, box.xyxy[0])\n            class_id = int(box.cls[0])\n            conf = float(box.conf[0])\n            \n            cv2.rectangle(detection_img, (x1, y1), (x2, y2), (0, 255, 0), 3)\n            label = f\"{self.class_names.get(class_id, class_id)}: {conf:.2f}\"\n            cv2.putText(detection_img, label, (x1, y1 - 10), \n                       cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)\n        \n        axes[1].imshow(detection_img)\n        axes[1].set_title(\"Detected Objects\", fontsize=12)\n        axes[1].axis('off')\n        \n        # Panel 3: Attention Heatmap\n        im = axes[2].imshow(attention_map, cmap='jet')\n        axes[2].set_title(\"Attention Heatmap\", fontsize=12)\n        axes[2].axis('off')\n        plt.colorbar(im, ax=axes[2], fraction=0.046, pad=0.04)\n        \n        # Panel 4: Overlay\n        axes[3].imshow(overlay)\n        axes[3].set_title(\"XAI Overlay (Model Focus)\", fontsize=12)\n        axes[3].axis('off')\n        \n        plt.tight_layout()\n        \n        # Save visualization\n        save_path = Config.XAI_PATH / f\"xai_{Path(image_path).stem}.png\"\n        plt.savefig(save_path, dpi=150, bbox_inches='tight')\n        plt.show()\n        \n        print(f\"✓ XAI Visualization saved to: {save_path}\")\n        \n        # Print detection summary\n        print(\"\\n📋 DETECTION SUMMARY:\")\n        for box in results.boxes:\n            class_id = int(box.cls[0])\n            conf = float(box.conf[0])\n            print(f\"   • {self.class_names.get(class_id, class_id)}: {conf:.2%} confidence\")\n    \n    def explain_specific_class(self, image_path, class_id):\n        \"\"\"\n        Generate explanation for a specific class only.\n        \n        Args:\n            image_path: Path to image\n            class_id: Class ID to explain (0-8)\n        \"\"\"\n        class_name = self.class_names.get(class_id, f\"Class {class_id}\")\n        print(f\"\\n🎯 Focusing on: {class_name}\")\n        \n        self.visualize_explanation(image_path, target_class=class_id)\n\n\n# Initialize XAI Explainer\nexplainer = YOLOExplainer(\n    model_path=best_model_path,\n    class_names=Config.CLASS_NAMES\n)\n\n# Generate XAI visualizations for sample images\nprint(\"\\n\" + \"=\" * 60)\nprint(\"GENERATING XAI EXPLANATIONS\")\nprint(\"=\" * 60)\n\n# Get sample validation images\nsample_valid_images = list(Config.PROCESSED_VALID_IMAGES.glob(\"*.jpg\"))[:3]\n\nfor img_path in sample_valid_images:\n    explainer.visualize_explanation(img_path)\n    print(\"-\" * 40)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-29T11:10:00.022465Z","iopub.status.idle":"2025-11-29T11:10:00.022751Z","shell.execute_reply.started":"2025-11-29T11:10:00.022634Z","shell.execute_reply":"2025-11-29T11:10:00.022646Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================================\n# CELL 13: GENERATE SUBMISSION FILE FOR KAGGLE\n# ============================================================================\n\ndef generate_submission(sahi_engine, test_image_dir, output_path):\n    \"\"\"\n    Generate submission file for Kaggle competition.\n    \n    Args:\n        sahi_engine: SAHIInference instance\n        test_image_dir: Path to test images\n        output_path: Path to save submission file\n    \"\"\"\n    print(\"=\" * 60)\n    print(\"GENERATING SUBMISSION FILE\")\n    print(\"=\" * 60)\n    \n    test_images = list(Path(test_image_dir).glob(\"*.jpg\"))\n    print(f\"Found {len(test_images)} test images\")\n    \n    submission_data = []\n    \n    for img_path in tqdm(test_images, desc=\"Processing Test Images\"):\n        # Run SAHI inference\n        result = sahi_engine.predict_single_image(img_path, visualize=False)\n        \n        # Extract predictions\n        for pred in result.object_prediction_list:\n            class_id = pred.category.id\n            confidence = pred.score.value\n            bbox = pred.bbox\n            \n            # Get mask if available\n            mask_rle = \"\"  # Convert mask to RLE if needed\n            \n            submission_data.append({\n                'image_id': img_path.stem,\n                'class_id': class_id,\n                'class_name': Config.CLASS_NAMES.get(class_id, f\"class_{class_id}\"),\n                'confidence': confidence,\n                'x_min': bbox.minx,\n                'y_min': bbox.miny,\n                'x_max': bbox.maxx,\n                'y_max': bbox.maxy\n            })\n    \n    # Create DataFrame\n    submission_df = pd.DataFrame(submission_data)\n    \n    # Save to CSV\n    submission_df.to_csv(output_path, index=False)\n    \n    print(f\"\\n✓ Submission saved to: {output_path}\")\n    print(f\"   Total predictions: {len(submission_df)}\")\n    print(f\"\\nSubmission Preview:\")\n    print(submission_df.head(10))\n    \n    return submission_df\n\n\n# Generate submission\nsubmission_path = Config.RESULTS_PATH / \"submission.csv\"\nsubmission_df = generate_submission(\n    sahi_engine=sahi_engine,\n    test_image_dir=Config.PROCESSED_TEST_IMAGES,\n    output_path=submission_path\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-29T11:10:00.023861Z","iopub.status.idle":"2025-11-29T11:10:00.024208Z","shell.execute_reply.started":"2025-11-29T11:10:00.024006Z","shell.execute_reply":"2025-11-29T11:10:00.02402Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================================\n# CELL 14: FINAL SUMMARY & RESULTS\n# ============================================================================\n\ndef print_final_summary():\n    \"\"\"Print comprehensive summary of the entire pipeline.\"\"\"\n    \n    print(\"=\" * 70)\n    print(\"🦷 ALPHA-DENT DENTAL X-RAY SEGMENTATION - FINAL SUMMARY\")\n    print(\"=\" * 70)\n    \n    print(\"\\n📌 PIPELINE COMPONENTS IMPLEMENTED:\")\n    print(\"-\" * 50)\n    \n    print(\"\"\"\n    ┌─────────────────────────────────────────────────────────────────┐\n    │  1. TRI-MODAL PREPROCESSING                                     │\n    │     ├── Channel 1: Original Grayscale                           │\n    │     ├── Channel 2: CLAHE (Contrast Enhancement for Caries)      │\n    │     └── Channel 3: Sobel Edges (Geometry for Crowns/Fillings)   │\n    ├─────────────────────────────────────────────────────────────────┤\n    │  2. YOLOv8-L SEGMENTATION                                       │\n    │     ├── Pre-trained on COCO                                     │\n    │     ├── Fine-tuned on Tri-Modal dental images                   │\n    │     └── 9 Custom Classes (Abrasion, Filling, Crown, Caries 1-6) │\n    ├─────────────────────────────────────────────────────────────────┤\n    │  3. SAHI (SLICING AIDED HYPER INFERENCE)                        │\n    │     ├── Handles 15MP high-resolution images                     │\n    │     ├── 640x640 tiles with 20% overlap                          │\n    │     └── Automatic stitching of predictions                      │\n    ├─────────────────────────────────────────────────────────────────┤\n    │  4. MULTIMODAL POST-PROCESSING                                  │\n    │     ├── Extracts metadata from filename (Age, Gender)           │\n    │     ├── Rule: Children < 12 → Penalize Abrasion/Crown           │\n    │     └── Rule: Seniors > 50 → Boost Abrasion confidence          │\n    ├─────────────────────────────────────────────────────────────────┤\n    │  5. EXPLAINABLE AI (XAI)                                        │\n    │     ├── Attention-based heatmap visualization                   │\n    │     ├── Shows which image regions triggered predictions         │\n    │     └── Validates model is looking at correct dental features   │\n    └─────────────────────────────────────────────────────────────────┘\n    \"\"\")\n    \n    print(\"\\n📊 CLASS MAPPING:\")\n    print(\"-\" * 50)\n    for class_id, class_name in Config.CLASS_NAMES.items():\n        print(f\"   {class_id}: {class_name}\")\n    \n    print(\"\\n📁 OUTPUT FILES:\")\n    print(\"-\" * 50)\n    print(f\"   • Trained Model: {Config.MODEL_PATH}/alphadent_trimodal/weights/best.pt\")\n    print(f\"   • Submission: {Config.RESULTS_PATH}/submission.csv\")\n    print(f\"   • XAI Visualizations: {Config.XAI_PATH}/\")\n    \n    print(\"\\n🚀 NEXT STEPS FOR IMPROVEMENT:\")\n    print(\"-\" * 50)\n    print(\"\"\"\n    1. Increase training epochs (10 → 50+)\n    2. Use larger model (yolov8x-seg.pt)\n    3. Add more augmentations (rotation, brightness)\n    4. Implement ensemble of multiple models\n    5. Use test-time augmentation (TTA)\n    6. Fine-tune SAHI tile size based on object sizes\n    \"\"\")\n    \n    print(\"=\" * 70)\n    print(\"✅ PIPELINE EXECUTION COMPLETE\")\n    print(\"=\" * 70)\n\n\nprint_final_summary()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-29T11:10:00.026822Z","iopub.status.idle":"2025-11-29T11:10:00.027505Z","shell.execute_reply.started":"2025-11-29T11:10:00.027307Z","shell.execute_reply":"2025-11-29T11:10:00.027326Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}