{"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":"gpu","dataSources":[{"sourceId":113558,"databundleVersionId":14174843,"sourceType":"competition"}],"dockerImageVersionId":31154,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"#  ****Scientific Image Forgery Detection****","metadata":{}},{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nimport cv2\nimport os\nfrom pathlib import Path\nfrom tqdm import tqdm\nimport warnings\nwarnings.filterwarnings('ignore')\n\n# Deep Learning imports\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import Dataset, DataLoader\nimport torchvision.transforms as transforms\n\n# Sklearn\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import f1_score, precision_score, recall_score","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T18:57:30.099528Z","iopub.execute_input":"2025-10-24T18:57:30.099998Z","iopub.status.idle":"2025-10-24T18:57:30.114099Z","shell.execute_reply.started":"2025-10-24T18:57:30.099980Z","shell.execute_reply":"2025-10-24T18:57:30.113461Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class Config:\n    RANDOM_STATE = 42\n    BATCH_SIZE = 4  # Reduced for stability\n    IMG_SIZE = (256, 256)\n    EPOCHS = 10\n    LR = 1e-4\n    DEVICE = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n    \nconfig = Config()\nprint(f\"Device: {config.DEVICE}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T18:57:30.114852Z","iopub.execute_input":"2025-10-24T18:57:30.115120Z","iopub.status.idle":"2025-10-24T18:57:30.131691Z","shell.execute_reply.started":"2025-10-24T18:57:30.115098Z","shell.execute_reply":"2025-10-24T18:57:30.130832Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# **Data Exploration and EDA**","metadata":{}},{"cell_type":"code","source":"# Data paths\nDATA_DIR = Path('/kaggle/input/recodai-luc-scientific-image-forgery-detection')\nTRAIN_IMAGE_DIR = DATA_DIR / 'train_images'\nTEST_IMAGE_DIR = DATA_DIR / 'test_images'\nTRAIN_MASK_DIR = DATA_DIR / 'train_masks'\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T18:57:30.401653Z","iopub.execute_input":"2025-10-24T18:57:30.401907Z","iopub.status.idle":"2025-10-24T18:57:30.406044Z","shell.execute_reply.started":"2025-10-24T18:57:30.401890Z","shell.execute_reply":"2025-10-24T18:57:30.405260Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Find all image files\ndef find_all_image_files(directory):\n    image_extensions = ['*.png', '*.jpg', '*.jpeg']\n    images = []\n    \n    if not directory.exists():\n        return images\n    \n    for ext in image_extensions:\n        images.extend(directory.rglob(ext))\n    \n    return sorted(images)\n\ntrain_images = find_all_image_files(TRAIN_IMAGE_DIR)\ntest_images = find_all_image_files(TEST_IMAGE_DIR)\ntrain_masks = list(TRAIN_MASK_DIR.glob('*.npy')) if TRAIN_MASK_DIR.exists() else []\n\nprint(f\"Training images: {len(train_images)}\")\nprint(f\"Test images: {len(test_images)}\")\nprint(f\"Training masks: {len(train_masks)}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T18:57:30.794047Z","iopub.execute_input":"2025-10-24T18:57:30.794621Z","iopub.status.idle":"2025-10-24T18:57:33.123909Z","shell.execute_reply.started":"2025-10-24T18:57:30.794600Z","shell.execute_reply":"2025-10-24T18:57:33.123087Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Create dataset mapping\ndef create_dataset_mapping():\n    image_mask_pairs = []\n    \n    mask_ids = {mask_path.stem for mask_path in train_masks}\n    \n    for image_path in train_images:\n        image_id = image_path.stem\n        mask_path = TRAIN_MASK_DIR / f\"{image_id}.npy\"\n        \n        has_forgery = mask_path.exists()\n        \n        image_mask_pairs.append({\n            'image_id': image_id,\n            'image_path': image_path,\n            'mask_path': mask_path if has_forgery else None,\n            'has_forgery': has_forgery\n        })\n    \n    return pd.DataFrame(image_mask_pairs)\n\ndf = create_dataset_mapping()\nprint(f\"Total samples: {len(df)}\")\nprint(f\"Forged images: {df['has_forgery'].sum()}\")\nprint(f\"Authentic images: {(~df['has_forgery']).sum()}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T18:57:33.125330Z","iopub.execute_input":"2025-10-24T18:57:33.125628Z","iopub.status.idle":"2025-10-24T18:57:35.264901Z","shell.execute_reply.started":"2025-10-24T18:57:33.125605Z","shell.execute_reply":"2025-10-24T18:57:35.264114Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def visualize_samples(df, num_samples=4):\n    forged_samples = df[df['has_forgery'] == True].sample(min(num_samples, len(df[df['has_forgery'] == True])))\n    \n    fig, axes = plt.subplots(2, len(forged_samples), figsize=(15, 6))\n    \n    if len(forged_samples) == 1:\n        axes = axes.reshape(2, 1)\n    \n    for idx, (_, sample) in enumerate(forged_samples.iterrows()):\n        try:\n            image = cv2.imread(str(sample['image_path']))\n            image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n            \n            mask = np.load(sample['mask_path'])\n            if mask.ndim == 3 and mask.shape[0] == 1:\n                mask = mask[0]\n            \n            axes[0, idx].imshow(image)\n            axes[0, idx].set_title(f\"Image: {sample['image_id']}\")\n            axes[0, idx].axis('off')\n            \n            axes[1, idx].imshow(mask, cmap='hot')\n            axes[1, idx].set_title(\"Forgery Mask\")\n            axes[1, idx].axis('off')\n            \n        except Exception as e:\n            print(f\"Error visualizing {sample['image_id']}: {e}\")\n    \n    plt.tight_layout()\n    plt.show()\n\nvisualize_samples(df)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T18:57:35.265717Z","iopub.execute_input":"2025-10-24T18:57:35.265984Z","iopub.status.idle":"2025-10-24T18:57:37.470541Z","shell.execute_reply.started":"2025-10-24T18:57:35.265967Z","shell.execute_reply":"2025-10-24T18:57:37.469702Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Fixed Dataset Class with proper mask handling\nclass ForgeryDataset(Dataset):\n    def __init__(self, df, is_train=True):\n        self.df = df.reset_index(drop=True)\n        self.is_train = is_train\n        \n    def __len__(self):\n        return len(self.df)\n    \n    def __getitem__(self, idx):\n        sample = self.df.iloc[idx]\n        \n        try:\n            # Load image\n            image = cv2.imread(str(sample['image_path']))\n            if image is None:\n                # Create consistent dummy image\n                image = np.ones((512, 512, 3), dtype=np.uint8) * 128\n            else:\n                image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n            \n            # Initialize mask\n            mask = np.zeros((image.shape[0], image.shape[1]), dtype=np.float32)\n            \n            # Load mask if available\n            if self.is_train and sample['has_forgery'] and sample['mask_path'] is not None:\n                try:\n                    mask_data = np.load(sample['mask_path']).astype(np.float32)\n                    \n                    # Fix mask shape to always be 2D\n                    if mask_data.ndim == 3:\n                        if mask_data.shape[0] == 1:  # (1, H, W)\n                            mask_data = mask_data[0]\n                        elif mask_data.shape[2] == 1:  # (H, W, 1)\n                            mask_data = mask_data[:, :, 0]\n                        else:\n                            # Take first channel if multiple channels\n                            mask_data = mask_data[:, :, 0]\n                    \n                    # Ensure mask matches image dimensions\n                    if mask_data.shape[:2] == image.shape[:2]:\n                        mask = mask_data\n                    else:\n                        # Resize mask to match image if dimensions don't match\n                        mask = cv2.resize(mask_data, (image.shape[1], image.shape[0]))\n                        \n                except Exception as e:\n                    print(f\"Mask loading error for {sample['image_id']}: {e}\")\n                    mask = np.zeros((image.shape[0], image.shape[1]), dtype=np.float32)\n            \n            # Resize both to consistent size\n            image_resized = cv2.resize(image, config.IMG_SIZE)\n            mask_resized = cv2.resize(mask, config.IMG_SIZE)\n            \n            # Convert to tensor\n            image_tensor = torch.from_numpy(image_resized).permute(2, 0, 1).float() / 255.0\n            mask_tensor = torch.from_numpy(mask_resized).unsqueeze(0).float()\n            \n            return image_tensor, mask_tensor, sample['image_id']\n            \n        except Exception as e:\n            # Return consistent dummy data on error\n            print(f\"Error loading {sample['image_id']}: {e}\")\n            image_tensor = torch.ones((3, *config.IMG_SIZE)).float() / 255.0\n            mask_tensor = torch.zeros((1, *config.IMG_SIZE)).float()\n            return image_tensor, mask_tensor, sample['image_id']","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T18:57:37.471595Z","iopub.execute_input":"2025-10-24T18:57:37.471832Z","iopub.status.idle":"2025-10-24T18:57:37.482440Z","shell.execute_reply.started":"2025-10-24T18:57:37.471815Z","shell.execute_reply":"2025-10-24T18:57:37.481462Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# **Prepare Data and Start Training**","metadata":{}},{"cell_type":"code","source":"# Prepare data for training\ntrain_df, val_df = train_test_split(\n    df, \n    test_size=0.2, \n    random_state=config.RANDOM_STATE,\n    stratify=df['has_forgery']\n)\n\nprint(f\"Training samples: {len(train_df)}\")\nprint(f\"Validation samples: {len(val_df)}\")\n\ntrain_dataset = ForgeryDataset(train_df, is_train=True)\nval_dataset = ForgeryDataset(val_df, is_train=True)\n\ntrain_loader = DataLoader(train_dataset, batch_size=config.BATCH_SIZE, shuffle=True, num_workers=0)\nval_loader = DataLoader(val_dataset, batch_size=config.BATCH_SIZE, shuffle=False, num_workers=0)\n\n# Test one batch to ensure it works\nprint(\"Testing data loader...\")\nfor images, masks, _ in train_loader:\n    print(f\"Image batch shape: {images.shape}\")\n    print(f\"Mask batch shape: {masks.shape}\")\n    break\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T18:57:37.485050Z","iopub.execute_input":"2025-10-24T18:57:37.485431Z","iopub.status.idle":"2025-10-24T18:57:37.967354Z","shell.execute_reply.started":"2025-10-24T18:57:37.485386Z","shell.execute_reply":"2025-10-24T18:57:37.966528Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# U-Net Model\nclass DoubleConv(nn.Module):\n    def __init__(self, in_channels, out_channels):\n        super().__init__()\n        self.double_conv = nn.Sequential(\n            nn.Conv2d(in_channels, out_channels, 3, padding=1),\n            nn.BatchNorm2d(out_channels),\n            nn.ReLU(inplace=True),\n            nn.Conv2d(out_channels, out_channels, 3, padding=1),\n            nn.BatchNorm2d(out_channels),\n            nn.ReLU(inplace=True)\n        )\n\n    def forward(self, x):\n        return self.double_conv(x)\n\nclass UNet(nn.Module):\n    def __init__(self, n_channels=3, n_classes=1):\n        super(UNet, self).__init__()\n        self.n_channels = n_channels\n        self.n_classes = n_classes\n\n        self.inc = DoubleConv(n_channels, 64)\n        self.down1 = nn.Sequential(nn.MaxPool2d(2), DoubleConv(64, 128))\n        self.down2 = nn.Sequential(nn.MaxPool2d(2), DoubleConv(128, 256))\n        self.down3 = nn.Sequential(nn.MaxPool2d(2), DoubleConv(256, 512))\n        \n        self.up1 = nn.ConvTranspose2d(512, 256, 2, stride=2)\n        self.conv1 = DoubleConv(512, 256)\n        self.up2 = nn.ConvTranspose2d(256, 128, 2, stride=2)\n        self.conv2 = DoubleConv(256, 128)\n        self.up3 = nn.ConvTranspose2d(128, 64, 2, stride=2)\n        self.conv3 = DoubleConv(128, 64)\n        \n        self.outc = nn.Conv2d(64, n_classes, 1)\n        self.sigmoid = nn.Sigmoid()\n\n    def forward(self, x):\n        x1 = self.inc(x)\n        x2 = self.down1(x1)\n        x3 = self.down2(x2)\n        x4 = self.down3(x3)\n        \n        x = self.up1(x4)\n        x = torch.cat([x, x3], dim=1)\n        x = self.conv1(x)\n        \n        x = self.up2(x)\n        x = torch.cat([x, x2], dim=1)\n        x = self.conv2(x)\n        \n        x = self.up3(x)\n        x = torch.cat([x, x1], dim=1)\n        x = self.conv3(x)\n        \n        logits = self.outc(x)\n        return self.sigmoid(logits)\n\nmodel = UNet(n_channels=3, n_classes=1).to(config.DEVICE)\nprint(f\"Model parameters: {sum(p.numel() for p in model.parameters()):,}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T18:57:37.968321Z","iopub.execute_input":"2025-10-24T18:57:37.968673Z","iopub.status.idle":"2025-10-24T18:57:38.047019Z","shell.execute_reply.started":"2025-10-24T18:57:37.968642Z","shell.execute_reply":"2025-10-24T18:57:38.046164Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Loss function\nclass DiceBCELoss(nn.Module):\n    def __init__(self):\n        super(DiceBCELoss, self).__init__()\n\n    def forward(self, pred, target):\n        smooth = 1.0\n        pred_flat = pred.view(-1)\n        target_flat = target.view(-1)\n        \n        intersection = (pred_flat * target_flat).sum()\n        dice_loss = 1 - (2. * intersection + smooth) / (pred_flat.sum() + target_flat.sum() + smooth)\n        bce_loss = nn.BCELoss()(pred_flat, target_flat)\n        \n        return dice_loss + bce_loss\n\ncriterion = DiceBCELoss()\noptimizer = optim.Adam(model.parameters(), lr=config.LR)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T18:57:38.047903Z","iopub.execute_input":"2025-10-24T18:57:38.048262Z","iopub.status.idle":"2025-10-24T18:57:38.054231Z","shell.execute_reply.started":"2025-10-24T18:57:38.048237Z","shell.execute_reply":"2025-10-24T18:57:38.053449Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Fixed Training function with error handling\ndef train_model(model, train_loader, val_loader, criterion, optimizer, epochs):\n    train_losses = []\n    best_loss = float('inf')\n    \n    for epoch in range(epochs):\n        model.train()\n        train_loss = 0.0\n        processed_batches = 0\n        \n        pbar = tqdm(train_loader, desc=f'Epoch {epoch+1}/{epochs}')\n        for batch_idx, (images, masks, _) in enumerate(pbar):\n            try:\n                # Skip if batch is empty\n                if images.shape[0] == 0:\n                    continue\n                    \n                images = images.to(config.DEVICE)\n                masks = masks.to(config.DEVICE)\n                \n                # Skip if shapes don't match\n                if images.shape[2:] != masks.shape[2:]:\n                    continue\n                \n                optimizer.zero_grad()\n                outputs = model(images)\n                loss = criterion(outputs, masks)\n                loss.backward()\n                optimizer.step()\n                \n                train_loss += loss.item()\n                processed_batches += 1\n                pbar.set_postfix({'Loss': f'{loss.item():.4f}'})\n                \n            except Exception as e:\n                print(f\"Error in batch {batch_idx}: {e}\")\n                continue\n        \n        if processed_batches == 0:\n            print(f\"Epoch {epoch+1}: No batches processed, skipping...\")\n            continue\n            \n        avg_train_loss = train_loss / processed_batches\n        train_losses.append(avg_train_loss)\n        \n        print(f'Epoch {epoch+1}: Train Loss: {avg_train_loss:.4f}')\n        \n        if avg_train_loss < best_loss:\n            best_loss = avg_train_loss\n            torch.save(model.state_dict(), 'best_model.pth')\n            print(f'New best model saved! Loss: {best_loss:.4f}')\n    \n    return train_losses\n\nprint(\"Starting training...\")\ntrain_losses = train_model(model, train_loader, val_loader, criterion, optimizer, config.EPOCHS)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T18:57:38.055027Z","iopub.execute_input":"2025-10-24T18:57:38.055220Z","iopub.status.idle":"2025-10-24T19:06:36.373321Z","shell.execute_reply.started":"2025-10-24T18:57:38.055205Z","shell.execute_reply":"2025-10-24T19:06:36.372425Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Plot training loss\nif train_losses:\n    plt.figure(figsize=(10, 5))\n    plt.plot(train_losses, label='Train Loss')\n    plt.xlabel('Epoch')\n    plt.ylabel('Loss')\n    plt.legend()\n    plt.title('Training Loss')\n    plt.show()\nelse:\n    print(\"No training data to plot\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T19:06:36.374326Z","iopub.execute_input":"2025-10-24T19:06:36.374639Z","iopub.status.idle":"2025-10-24T19:06:36.567610Z","shell.execute_reply.started":"2025-10-24T19:06:36.374621Z","shell.execute_reply":"2025-10-24T19:06:36.566797Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# RLE Encoding function\ndef rle_encode(mask):\n    pixels = mask.flatten()\n    pixels = np.concatenate([[0], pixels, [0]])\n    runs = np.where(pixels[1:] != pixels[:-1])[0] + 1\n    runs[1::2] -= runs[::2]\n    \n    if len(runs) == 0:\n        return \"[]\"\n    else:\n        return \"[\" + \" \".join(str(x) for x in runs) + \"]\"","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T19:06:36.568531Z","iopub.execute_input":"2025-10-24T19:06:36.568806Z","iopub.status.idle":"2025-10-24T19:06:36.573971Z","shell.execute_reply.started":"2025-10-24T19:06:36.568780Z","shell.execute_reply":"2025-10-24T19:06:36.573264Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# **Submission**","metadata":{}},{"cell_type":"code","source":"def create_final_submission():\n    \"\"\"Create submission with exact format: case_id,annotation\"\"\"\n    \n    # Initialize model (use trained if available, otherwise create new)\n    try:\n        model.load_state_dict(torch.load('best_model.pth'))\n        print(\"Using trained model for predictions\")\n    except:\n        print(\"Using default model for predictions\")\n        model = UNet(n_channels=3, n_classes=1).to(config.DEVICE)\n    \n    model.eval()\n    predictions = []\n    \n    for test_img_path in tqdm(test_images, desc=\"Creating predictions\"):\n        case_id = test_img_path.stem\n        \n        try:\n            # Load and process image\n            image = cv2.imread(str(test_img_path))\n            if image is None:\n                predictions.append({'case_id': case_id, 'annotation': 'authentic'})\n                continue\n                \n            original_shape = image.shape[:2]\n            image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n            \n            # Resize for model\n            image_resized = cv2.resize(image, config.IMG_SIZE)\n            image_tensor = torch.from_numpy(image_resized).permute(2, 0, 1).float().unsqueeze(0) / 255.0\n            image_tensor = image_tensor.to(config.DEVICE)\n            \n            # Predict\n            with torch.no_grad():\n                pred = model(image_tensor)\n                pred_mask = pred.squeeze().cpu().numpy()\n            \n            # Process prediction\n            pred_mask_resized = cv2.resize(pred_mask, (original_shape[1], original_shape[0]))\n            binary_mask = (pred_mask_resized > 0.3).astype(np.uint8)  # Lower threshold for sensitivity\n            \n            # Decide authentic vs forgery\n            if np.sum(binary_mask) < 50:  # Small area threshold\n                annotation = 'authentic'\n            else:\n                annotation = rle_encode(binary_mask)\n                \n        except:\n            annotation = 'authentic'\n        \n        predictions.append({'case_id': case_id, 'annotation': annotation})\n    \n    # Create DataFrame with exact column order\n    submission_df = pd.DataFrame(predictions)[['case_id', 'annotation']]\n    \n    # Ensure proper sorting\n    try:\n        submission_df = submission_df.sort_values('case_id', key=lambda x: x.astype(int))\n    except:\n        submission_df = submission_df.sort_values('case_id')\n    \n    return submission_df\n\n# Create and save final submission\nfinal_submission = create_final_submission()\nfinal_submission.to_csv('submission.csv', index=False)\n\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T19:08:08.278169Z","iopub.execute_input":"2025-10-24T19:08:08.278730Z","iopub.status.idle":"2025-10-24T19:08:08.416209Z","shell.execute_reply.started":"2025-10-24T19:08:08.278705Z","shell.execute_reply":"2025-10-24T19:08:08.415343Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"final_submission.head(10)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T19:08:26.807617Z","iopub.execute_input":"2025-10-24T19:08:26.808143Z","iopub.status.idle":"2025-10-24T19:08:26.815396Z","shell.execute_reply.started":"2025-10-24T19:08:26.808122Z","shell.execute_reply":"2025-10-24T19:08:26.814743Z"}},"outputs":[],"execution_count":null}]}