{"metadata":{"kernelspec":{"name":"python3","display_name":"Python 3","language":"python"},"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"}],"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"id":"de37aa40","cell_type":"markdown","source":"\n# 🔬 Scientific Image Forgery Detection - Starter Notebook\n\nThis notebook serves as a **complete starter solution** for the **Scientific Image Forgery Detection** challenge.\n\n---\n### 🧠 Overview\n- **Task:** Pixel-level segmentation of manipulated regions in scientific/biomedical images.  \n- **Goal:** Predict whether an image is `authentic` or submit a **run-length encoded (RLE)** mask of forged regions.  \n- **Metric:** A modified **F1-score** designed for segmentation masks.\n---\n","metadata":{}},{"id":"abe5ed21","cell_type":"code","source":"# Cell 1: Imports and Setup\nimport os\nimport numpy as np\nimport pandas as pd\nimport cv2\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import f1_score, precision_score, recall_score\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import Dataset, DataLoader\nimport torch.nn.functional as F\nimport albumentations as A\nfrom albumentations.pytorch import ToTensorV2\nimport warnings\nwarnings.filterwarnings('ignore')\n\n# Set random seeds for reproducibility\ntorch.manual_seed(42)\nnp.random.seed(42)\n\n# Check if GPU is available\ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\nprint(f\"Using device: {device}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T17:25:18.003818Z","iopub.execute_input":"2025-10-24T17:25:18.004095Z","iopub.status.idle":"2025-10-24T17:25:24.909936Z","shell.execute_reply.started":"2025-10-24T17:25:18.004076Z","shell.execute_reply":"2025-10-24T17:25:24.909076Z"}},"outputs":[],"execution_count":null},{"id":"57975214-0693-48ad-9dc3-52b6b8ec8a08","cell_type":"code","source":"# Cell 2: Fixed Data Exploration with Subdirectory Handling\nimport os\nfrom pathlib import Path\n\n# Load sample submission\nsample_submission = pd.read_csv('/kaggle/input/recodai-luc-scientific-image-forgery-detection/sample_submission.csv')\nprint(\"Sample submission shape:\", sample_submission.shape)\nprint(\"Sample submission head:\")\nprint(sample_submission.head())\n\n# Define paths\ntrain_images_dir = '/kaggle/input/recodai-luc-scientific-image-forgery-detection/train_images'\ntrain_masks_dir = '/kaggle/input/recodai-luc-scientific-image-forgery-detection/train_masks'\n\ndef explore_directory_structure(root_dir):\n    \"\"\"Recursively explore directory structure\"\"\"\n    print(f\"Exploring: {root_dir}\")\n    for root, dirs, files in os.walk(root_dir):\n        level = root.replace(root_dir, '').count(os.sep)\n        indent = ' ' * 2 * level\n        print(f\"{indent}{os.path.basename(root)}/\")\n        subindent = ' ' * 2 * (level + 1)\n        for file in files[:5]:  # Show first 5 files\n            print(f\"{subindent}{file}\")\n        if len(files) > 5:\n            print(f\"{subindent}... and {len(files) - 5} more files\")\n        if not files and not dirs:\n            print(f\"{subindent}(empty)\")\n\nprint(\"=== TRAIN IMAGES DIRECTORY STRUCTURE ===\")\nexplore_directory_structure(train_images_dir)\n\nprint(\"\\n=== TRAIN MASKS DIRECTORY STRUCTURE ===\")\nexplore_directory_structure(train_masks_dir)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T17:35:36.761558Z","iopub.execute_input":"2025-10-24T17:35:36.761884Z","iopub.status.idle":"2025-10-24T17:35:41.053695Z","shell.execute_reply.started":"2025-10-24T17:35:36.761863Z","shell.execute_reply":"2025-10-24T17:35:41.052917Z"}},"outputs":[],"execution_count":null},{"id":"4066bad4-9082-46df-b2de-b29be0b895b4","cell_type":"code","source":"# Cell 3: Get All Image and Mask Files Recursively\ndef get_all_image_files(root_dir, extensions=('.jpg', '.jpeg', '.png', '.tiff', '.tif')):\n    \"\"\"Get all image files recursively from directory\"\"\"\n    image_files = []\n    for root, dirs, files in os.walk(root_dir):\n        for file in files:\n            if file.lower().endswith(extensions):\n                full_path = os.path.join(root, file)\n                image_files.append(full_path)\n    return sorted(image_files)\n\ndef get_relative_path(full_path, base_dir):\n    \"\"\"Get relative path from base directory\"\"\"\n    return os.path.relpath(full_path, base_dir)\n\n# Get all training images and masks\ntrain_image_files = get_all_image_files(train_images_dir)\ntrain_mask_files = get_all_image_files(train_masks_dir)\n\nprint(f\"Found {len(train_image_files)} training images\")\nprint(f\"Found {len(train_mask_files)} training masks\")\n\n# Show some examples\nprint(\"\\nFirst 10 training images:\")\nfor img_path in train_image_files[:10]:\n    rel_path = get_relative_path(img_path, train_images_dir)\n    print(f\"  {rel_path}\")\n\nprint(\"\\nFirst 10 training masks:\")\nfor mask_path in train_mask_files[:10]:\n    rel_path = get_relative_path(mask_path, train_masks_dir)\n    print(f\"  {rel_path}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T17:36:39.256316Z","iopub.execute_input":"2025-10-24T17:36:39.257099Z","iopub.status.idle":"2025-10-24T17:36:41.364862Z","shell.execute_reply.started":"2025-10-24T17:36:39.257070Z","shell.execute_reply":"2025-10-24T17:36:41.364029Z"}},"outputs":[],"execution_count":null},{"id":"08816651-06fc-4c32-8294-7adbec0243a9","cell_type":"code","source":"# Cell 4: Create Proper Training DataFrame\n# Get all image paths\nforged_images = [os.path.join(train_images_dir, 'forged', f) for f in os.listdir(os.path.join(train_images_dir, 'forged'))]\nauthentic_images = [os.path.join(train_images_dir, 'authentic', f) for f in os.listdir(os.path.join(train_images_dir, 'authentic'))]\n\n# Get all mask paths (they are .npy files in root)\nmask_files = [os.path.join(train_masks_dir, f) for f in os.listdir(train_masks_dir) if f.endswith('.npy')]\n\nprint(f\"Forged images: {len(forged_images)}\")\nprint(f\"Authentic images: {len(authentic_images)}\")\nprint(f\"Masks: {len(mask_files)}\")\n\n# Since masks are only for forged images (2751 masks for 2751 forged images)\n# Let's create a mapping based on the counts\ntrain_data = []\n\n# Add forged images with masks\nfor i, img_path in enumerate(forged_images):\n    if i < len(mask_files):  # Ensure we have a corresponding mask\n        case_id = os.path.splitext(os.path.basename(img_path))[0]\n        mask_path = mask_files[i]  # Assuming they're in the same order\n        \n        train_data.append({\n            'case_id': case_id,\n            'image_path': img_path,\n            'mask_path': mask_path,\n            'is_forged': 1\n        })\n\n# Add authentic images (no masks, or empty masks)\nfor img_path in authentic_images:\n    case_id = os.path.splitext(os.path.basename(img_path))[0]\n    \n    train_data.append({\n        'case_id': case_id,\n        'image_path': img_path,\n        'mask_path': None,  # No mask for authentic images\n        'is_forged': 0\n    })\n\ntrain_df = pd.DataFrame(train_data)\nprint(f\"Created training DataFrame with {len(train_df)} samples\")\nprint(f\"Forged: {train_df['is_forged'].sum()}, Authentic: {len(train_df) - train_df['is_forged'].sum()}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T17:47:07.664189Z","iopub.execute_input":"2025-10-24T17:47:07.664924Z","iopub.status.idle":"2025-10-24T17:47:07.703295Z","shell.execute_reply.started":"2025-10-24T17:47:07.664899Z","shell.execute_reply":"2025-10-24T17:47:07.702666Z"}},"outputs":[],"execution_count":null},{"id":"de121c1c-1ba5-4079-82db-3834a00c4598","cell_type":"code","source":"# Cell 5: Dataset Class for .npy Masks\nclass ScientificForgeryDataset(Dataset):\n    def __init__(self, df, transform=None, img_size=512):\n        self.df = df\n        self.transform = transform\n        self.img_size = img_size\n        \n    def __len__(self):\n        return len(self.df)\n    \n    def __getitem__(self, idx):\n        row = self.df.iloc[idx]\n        \n        # Load image\n        image = cv2.imread(row['image_path'])\n        image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n        \n        # Load mask\n        if row['mask_path'] is not None and os.path.exists(row['mask_path']):\n            mask = np.load(row['mask_path']).astype(np.uint8)\n        else:\n            # For authentic images, create empty mask\n            mask = np.zeros(image.shape[:2], dtype=np.uint8)\n        \n        # Resize if needed\n        if image.shape[:2] != (self.img_size, self.img_size):\n            image = cv2.resize(image, (self.img_size, self.img_size))\n            mask = cv2.resize(mask, (self.img_size, self.img_size))\n        \n        # Apply transformations\n        if self.transform:\n            augmented = self.transform(image=image, mask=mask)\n            image = augmented['image']\n            mask = augmented['mask']\n        \n        return image, mask, row['case_id']","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T17:47:25.377074Z","iopub.execute_input":"2025-10-24T17:47:25.377366Z","iopub.status.idle":"2025-10-24T17:47:25.383917Z","shell.execute_reply.started":"2025-10-24T17:47:25.377346Z","shell.execute_reply":"2025-10-24T17:47:25.383256Z"}},"outputs":[],"execution_count":null},{"id":"b3cab695-17e0-4e93-937e-de1e5fad603a","cell_type":"code","source":"# Cell 6: Data Transforms\ndef get_transforms(phase='train', img_size=512):\n    if phase == 'train':\n        return A.Compose([\n            A.HorizontalFlip(p=0.5),\n            A.VerticalFlip(p=0.5),\n            A.RandomRotate90(p=0.5),\n            A.ShiftScaleRotate(shift_limit=0.05, scale_limit=0.05, rotate_limit=15, p=0.5),\n            A.RandomBrightnessContrast(brightness_limit=0.2, contrast_limit=0.2, p=0.3),\n            A.GaussNoise(var_limit=(10.0, 50.0), p=0.2),\n            A.GaussianBlur(blur_limit=3, p=0.2),\n            A.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),\n            ToTensorV2(),\n        ])\n    else:\n        return A.Compose([\n            A.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),\n            ToTensorV2(),\n        ])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T17:47:32.966392Z","iopub.execute_input":"2025-10-24T17:47:32.966941Z","iopub.status.idle":"2025-10-24T17:47:32.972463Z","shell.execute_reply.started":"2025-10-24T17:47:32.966916Z","shell.execute_reply":"2025-10-24T17:47:32.971720Z"}},"outputs":[],"execution_count":null},{"id":"543337d0-5c18-494d-9487-bd3b2fc79ee3","cell_type":"code","source":"# Cell 7: Enhanced U-Net Model\nclass UNet(nn.Module):\n    def __init__(self, in_channels=3, out_channels=1):\n        super(UNet, self).__init__()\n        \n        # Encoder\n        self.enc1 = self._block(in_channels, 64)\n        self.enc2 = self._block(64, 128)\n        self.enc3 = self._block(128, 256)\n        self.enc4 = self._block(256, 512)\n        \n        self.pool = nn.MaxPool2d(2)\n        \n        # Bottleneck\n        self.bottleneck = self._block(512, 1024)\n        \n        # Decoder\n        self.upconv4 = nn.ConvTranspose2d(1024, 512, kernel_size=2, stride=2)\n        self.dec4 = self._block(1024, 512)\n        \n        self.upconv3 = nn.ConvTranspose2d(512, 256, kernel_size=2, stride=2)\n        self.dec3 = self._block(512, 256)\n        \n        self.upconv2 = nn.ConvTranspose2d(256, 128, kernel_size=2, stride=2)\n        self.dec2 = self._block(256, 128)\n        \n        self.upconv1 = nn.ConvTranspose2d(128, 64, kernel_size=2, stride=2)\n        self.dec1 = self._block(128, 64)\n        \n        self.final = nn.Conv2d(64, out_channels, kernel_size=1)\n    \n    def _block(self, in_channels, out_channels):\n        return 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        # Encoder\n        e1 = self.enc1(x)\n        e2 = self.enc2(self.pool(e1))\n        e3 = self.enc3(self.pool(e2))\n        e4 = self.enc4(self.pool(e3))\n        \n        # Bottleneck\n        bottleneck = self.bottleneck(self.pool(e4))\n        \n        # Decoder\n        d4 = self.upconv4(bottleneck)\n        d4 = torch.cat([d4, e4], dim=1)\n        d4 = self.dec4(d4)\n        \n        d3 = self.upconv3(d4)\n        d3 = torch.cat([d3, e3], dim=1)\n        d3 = self.dec3(d3)\n        \n        d2 = self.upconv2(d3)\n        d2 = torch.cat([d2, e2], dim=1)\n        d2 = self.dec2(d2)\n        \n        d1 = self.upconv1(d2)\n        d1 = torch.cat([d1, e1], dim=1)\n        d1 = self.dec1(d1)\n        \n        return self.final(d1)\n\nmodel = UNet().to(device)\nprint(f\"Model parameters: {sum(p.numel() for p in model.parameters()):,}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T17:47:39.062402Z","iopub.execute_input":"2025-10-24T17:47:39.062738Z","iopub.status.idle":"2025-10-24T17:47:39.355004Z","shell.execute_reply.started":"2025-10-24T17:47:39.062713Z","shell.execute_reply":"2025-10-24T17:47:39.354363Z"}},"outputs":[],"execution_count":null},{"id":"be856ba7-6cb0-4fd5-a559-ade3c8a4920c","cell_type":"code","source":"# Cell 8: Prepare Data Loaders\n# Split data\ntrain_df, val_df = train_test_split(train_df, test_size=0.2, random_state=42, stratify=train_df['is_forged'])\n\nprint(f\"Training samples: {len(train_df)}\")\nprint(f\"Validation samples: {len(val_df)}\")\n\n# Create datasets\ntrain_dataset = ScientificForgeryDataset(train_df, transform=get_transforms('train', 256), img_size=256)\nval_dataset = ScientificForgeryDataset(val_df, transform=get_transforms('val', 256), img_size=256)\n\n# Create data loaders\nBATCH_SIZE = 16\ntrain_loader = DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True, num_workers=2)\nval_loader = DataLoader(val_dataset, batch_size=BATCH_SIZE, shuffle=False, num_workers=2)\n\nprint(\"Data loaders created successfully!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T17:47:45.198576Z","iopub.execute_input":"2025-10-24T17:47:45.199391Z","iopub.status.idle":"2025-10-24T17:47:45.220274Z","shell.execute_reply.started":"2025-10-24T17:47:45.199366Z","shell.execute_reply":"2025-10-24T17:47:45.219344Z"}},"outputs":[],"execution_count":null},{"id":"bf90e8c5-984e-4346-946c-9d132403bdac","cell_type":"code","source":"# Cell 9: Fixed Dataset Class and Training\nclass ScientificForgeryDataset(Dataset):\n    def __init__(self, df, transform=None, img_size=512):\n        self.df = df.reset_index(drop=True)\n        self.transform = transform\n        self.img_size = img_size\n        \n    def __len__(self):\n        return len(self.df)\n    \n    def __getitem__(self, idx):\n        row = self.df.iloc[idx]\n        \n        try:\n            # Load image\n            image = cv2.imread(row['image_path'])\n            if image is None:\n                raise ValueError(f\"Could not load image: {row['image_path']}\")\n            image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n            \n            # Load mask with error handling\n            if row['mask_path'] is not None and os.path.exists(row['mask_path']):\n                mask = np.load(row['mask_path'])\n                # Ensure mask is 2D and proper type\n                if mask.ndim > 2:\n                    mask = mask.squeeze()\n                mask = mask.astype(np.uint8)\n            else:\n                # For authentic images, create empty mask\n                mask = np.zeros(image.shape[:2], dtype=np.uint8)\n            \n            # Ensure mask has proper dimensions\n            if mask.shape != image.shape[:2]:\n                # If shapes don't match, create empty mask or resize\n                mask = np.zeros(image.shape[:2], dtype=np.uint8)\n            \n            # Resize if needed\n            if image.shape[:2] != (self.img_size, self.img_size):\n                image = cv2.resize(image, (self.img_size, self.img_size))\n                mask = cv2.resize(mask, (self.img_size, self.img_size))\n            \n            # Apply transformations\n            if self.transform:\n                augmented = self.transform(image=image, mask=mask)\n                image = augmented['image']\n                mask = augmented['mask']\n            \n            return image, mask, row['case_id']\n            \n        except Exception as e:\n            print(f\"Error loading sample {idx}: {e}\")\n            # Return a dummy sample\n            image = np.random.randint(0, 255, (self.img_size, self.img_size, 3), dtype=np.uint8)\n            mask = np.zeros((self.img_size, self.img_size), dtype=np.uint8)\n            \n            if self.transform:\n                augmented = self.transform(image=image, mask=mask)\n                image = augmented['image']\n                mask = augmented['mask']\n            \n            return image, mask, f\"error_{idx}\"\n\n# Recreate datasets with fixed class\ntrain_dataset = ScientificForgeryDataset(train_df, transform=get_transforms('train', 256), img_size=256)\nval_dataset = ScientificForgeryDataset(val_df, transform=get_transforms('val', 256), img_size=256)\n\n# Create data loaders with error handling\nBATCH_SIZE = 16\ntrain_loader = DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True, num_workers=0)  # Set workers=0 to avoid multiprocessing issues\nval_loader = DataLoader(val_dataset, batch_size=BATCH_SIZE, shuffle=False, num_workers=0)\n\nprint(\"Fixed data loaders created successfully!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T17:49:03.957239Z","iopub.execute_input":"2025-10-24T17:49:03.958270Z","iopub.status.idle":"2025-10-24T17:49:03.983742Z","shell.execute_reply.started":"2025-10-24T17:49:03.958237Z","shell.execute_reply":"2025-10-24T17:49:03.983059Z"}},"outputs":[],"execution_count":null},{"id":"0f052207-8052-4092-9ee2-be2766ef9806","cell_type":"code","source":"# Cell 10: Fixed Dataset Class with Proper Data Types\nclass ScientificForgeryDataset(Dataset):\n    def __init__(self, df, transform=None, img_size=512):\n        self.df = df.reset_index(drop=True)\n        self.transform = transform\n        self.img_size = img_size\n        \n    def __len__(self):\n        return len(self.df)\n    \n    def __getitem__(self, idx):\n        row = self.df.iloc[idx]\n        \n        try:\n            # Load image\n            image = cv2.imread(row['image_path'])\n            if image is None:\n                raise ValueError(f\"Could not load image: {row['image_path']}\")\n            image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n            \n            # Load mask with error handling\n            if row['mask_path'] is not None and os.path.exists(row['mask_path']):\n                mask = np.load(row['mask_path'])\n                # Ensure mask is 2D and proper type\n                if mask.ndim > 2:\n                    mask = mask.squeeze()\n                mask = mask.astype(np.float32)  # Use float32 for masks\n            else:\n                # For authentic images, create empty mask\n                mask = np.zeros(image.shape[:2], dtype=np.float32)\n            \n            # Ensure mask has proper dimensions\n            if mask.shape != image.shape[:2]:\n                # If shapes don't match, create empty mask or resize\n                mask = np.zeros(image.shape[:2], dtype=np.float32)\n            \n            # Resize if needed\n            if image.shape[:2] != (self.img_size, self.img_size):\n                image = cv2.resize(image, (self.img_size, self.img_size))\n                mask = cv2.resize(mask, (self.img_size, self.img_size))\n            \n            # Apply transformations - ensure both are uint8 for albumentations\n            image_uint8 = image.astype(np.uint8)\n            mask_uint8 = (mask * 255).astype(np.uint8)  # Convert back to uint8 for albumentations\n            \n            if self.transform:\n                augmented = self.transform(image=image_uint8, mask=mask_uint8)\n                image = augmented['image']\n                mask = augmented['mask'].float() / 255.0  # Convert back to float for training\n            \n            return image, mask, row['case_id']\n            \n        except Exception as e:\n            print(f\"Error loading sample {idx}: {e}\")\n            # Return a dummy sample\n            image = np.random.randint(0, 255, (self.img_size, self.img_size, 3), dtype=np.uint8)\n            mask = np.zeros((self.img_size, self.img_size), dtype=np.float32)\n            \n            if self.transform:\n                augmented = self.transform(image=image, mask=(mask * 255).astype(np.uint8))\n                image = augmented['image']\n                mask = augmented['mask'].float() / 255.0\n            \n            return image, mask, f\"error_{idx}\"\n\n# Recreate datasets with fixed class\ntrain_dataset = ScientificForgeryDataset(train_df, transform=get_transforms('train', 256), img_size=256)\nval_dataset = ScientificForgeryDataset(val_df, transform=get_transforms('val', 256), img_size=256)\n\n# Create data loaders with error handling\nBATCH_SIZE = 16\ntrain_loader = DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True, num_workers=0)\nval_loader = DataLoader(val_dataset, batch_size=BATCH_SIZE, shuffle=False, num_workers=0)\n\nprint(\"Fixed data loaders created successfully!\")\n\n# Test one batch to verify it works\nprint(\"Testing one batch...\")\nfor images, masks, case_ids in train_loader:\n    print(f\"Images shape: {images.shape}, dtype: {images.dtype}\")\n    print(f\"Masks shape: {masks.shape}, dtype: {masks.dtype}\")\n    print(f\"Sample case IDs: {case_ids[:3]}\")\n    break","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T17:52:51.686552Z","iopub.execute_input":"2025-10-24T17:52:51.686881Z","iopub.status.idle":"2025-10-24T17:52:52.211323Z","shell.execute_reply.started":"2025-10-24T17:52:51.686861Z","shell.execute_reply":"2025-10-24T17:52:52.210605Z"}},"outputs":[],"execution_count":null},{"id":"987f6a27-1c7a-4162-8db6-0548107dedec","cell_type":"code","source":"# Cell 11: Training Function\ndef train_proper_model(model, train_loader, val_loader, epochs=5):\n    optimizer = optim.Adam(model.parameters(), lr=1e-4)\n    criterion = nn.BCEWithLogitsLoss()\n    \n    train_losses = []\n    val_losses = []\n    \n    for epoch in range(epochs):\n        # Training\n        model.train()\n        train_loss = 0.0\n        batch_count = 0\n        \n        for images, masks, _ in train_loader:\n            try:\n                images, masks = images.to(device), masks.to(device)\n                \n                optimizer.zero_grad()\n                outputs = model(images)\n                loss = criterion(outputs, masks.unsqueeze(1))\n                loss.backward()\n                optimizer.step()\n                \n                train_loss += loss.item()\n                batch_count += 1\n                \n                if batch_count % 20 == 0:\n                    print(f'Batch {batch_count}, Loss: {loss.item():.4f}')\n                    \n            except Exception as e:\n                print(f\"Training batch error: {e}\")\n                continue\n        \n        # Validation\n        model.eval()\n        val_loss = 0.0\n        val_batch_count = 0\n        \n        with torch.no_grad():\n            for images, masks, _ in val_loader:\n                try:\n                    images, masks = images.to(device), masks.to(device)\n                    outputs = model(images)\n                    loss = criterion(outputs, masks.unsqueeze(1))\n                    val_loss += loss.item()\n                    val_batch_count += 1\n                except Exception as e:\n                    print(f\"Validation batch error: {e}\")\n                    continue\n        \n        if batch_count > 0 and val_batch_count > 0:\n            train_epoch_loss = train_loss / batch_count\n            val_epoch_loss = val_loss / val_batch_count\n            \n            train_losses.append(train_epoch_loss)\n            val_losses.append(val_epoch_loss)\n            \n            print(f'Epoch {epoch+1}/{epochs}, Train Loss: {train_epoch_loss:.4f}, Val Loss: {val_epoch_loss:.4f}')\n        else:\n            print(f'Epoch {epoch+1}/{epochs} skipped due to errors')\n    \n    # Plot training history if we have data\n    if train_losses:\n        plt.figure(figsize=(10, 5))\n        plt.plot(train_losses, label='Train Loss')\n        plt.plot(val_losses, label='Val Loss')\n        plt.xlabel('Epoch')\n        plt.ylabel('Loss')\n        plt.legend()\n        plt.title('Training History')\n        plt.show()\n    \n    return model\n\nprint(\"Starting training...\")\ntrained_model = train_proper_model(model, train_loader, val_loader, epochs=5)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T17:53:06.026827Z","iopub.execute_input":"2025-10-24T17:53:06.027422Z","iopub.status.idle":"2025-10-24T18:19:48.347436Z","shell.execute_reply.started":"2025-10-24T17:53:06.027402Z","shell.execute_reply":"2025-10-24T18:19:48.346823Z"}},"outputs":[],"execution_count":null},{"id":"50406d39-e21a-4fe7-80f8-0a78d90ba381","cell_type":"code","source":"# Cell 12: Quick Model Test\ndef quick_model_test(model, test_loader):\n    \"\"\"Quick test to see if model is working\"\"\"\n    model.eval()\n    \n    with torch.no_grad():\n        for images, masks, case_ids in test_loader:\n            images, masks = images.to(device), masks.to(device)\n            outputs = model(images)\n            predictions = torch.sigmoid(outputs)\n            \n            print(f\"Input range: [{images.min():.3f}, {images.max():.3f}]\")\n            print(f\"Output range: [{outputs.min():.3f}, {outputs.max():.3f}]\")\n            print(f\"Prediction range: [{predictions.min():.3f}, {predictions.max():.3f}]\")\n            print(f\"Mask range: [{masks.min():.3f}, {masks.max():.3f}]\")\n            \n            # Calculate basic metrics\n            pred_binary = (predictions > 0.5).float()\n            accuracy = (pred_binary == masks.unsqueeze(1)).float().mean()\n            print(f\"Sample accuracy: {accuracy.item():.4f}\")\n            break\n\nprint(\"Testing model output...\")\nquick_model_test(trained_model, val_loader)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T18:19:49.338048Z","iopub.execute_input":"2025-10-24T18:19:49.338588Z","iopub.status.idle":"2025-10-24T18:19:50.266183Z","shell.execute_reply.started":"2025-10-24T18:19:49.338560Z","shell.execute_reply":"2025-10-24T18:19:50.265311Z"}},"outputs":[],"execution_count":null},{"id":"5feb1fd4-552a-456e-a3d8-5ec1a91cbfda","cell_type":"code","source":"# Cell 13: RLE Encoding Function\ndef rle_encode(mask):\n    \"\"\"\n    Encode mask to run-length encoding\n    \"\"\"\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    return ' '.join(str(x) for x in runs)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T18:19:50.267884Z","iopub.execute_input":"2025-10-24T18:19:50.268159Z","iopub.status.idle":"2025-10-24T18:19:50.273045Z","shell.execute_reply.started":"2025-10-24T18:19:50.268142Z","shell.execute_reply":"2025-10-24T18:19:50.272177Z"}},"outputs":[],"execution_count":null},{"id":"e76d7031-d39a-45b3-a503-6648b863bf94","cell_type":"code","source":"# Cell 14: Test Image Prediction\ndef predict_test_image(model, image_path, device, img_size=256):\n    \"\"\"Predict forgery for a test image\"\"\"\n    try:\n        # Load image\n        image = cv2.imread(image_path)\n        if image is None:\n            return \"authentic\"\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, (img_size, img_size))\n        \n        # Transform\n        transform = get_transforms('val', img_size)\n        augmented = transform(image=image_resized)\n        image_tensor = augmented['image'].unsqueeze(0).to(device)\n        \n        # Predict\n        model.eval()\n        with torch.no_grad():\n            output = model(image_tensor)\n            pred_mask = torch.sigmoid(output).squeeze().cpu().numpy()\n            pred_mask_binary = (pred_mask > 0.5).astype(np.uint8)\n        \n        # Resize back to original\n        pred_mask_resized = cv2.resize(pred_mask_binary, (original_shape[1], original_shape[0]))\n        \n        # Check if any forgery detected\n        if pred_mask_resized.sum() == 0:\n            return \"authentic\"\n        else:\n            return rle_encode(pred_mask_resized)\n            \n    except Exception as e:\n        print(f\"Error processing image: {e}\")\n        return \"authentic\"","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T18:19:50.273807Z","iopub.execute_input":"2025-10-24T18:19:50.274075Z","iopub.status.idle":"2025-10-24T18:19:50.289222Z","shell.execute_reply.started":"2025-10-24T18:19:50.274051Z","shell.execute_reply":"2025-10-24T18:19:50.288424Z"}},"outputs":[],"execution_count":null},{"id":"837d3c31-d4d6-4781-961a-a089b7c703f6","cell_type":"code","source":"# Cell 14: Create Submission\n# Load sample submission\nsample_submission = pd.read_csv('/kaggle/input/recodai-luc-scientific-image-forgery-detection/sample_submission.csv')\nprint(f\"Processing {len(sample_submission)} test cases...\")\n\n# Create submissions\nsubmissions = []\ntest_images_dir = '/kaggle/input/recodai-luc-scientific-image-forgery-detection/test_images'\n\nfor i, case_id in enumerate(sample_submission['case_id']):\n    if i % 100 == 0:\n        print(f\"Processed {i}/{len(sample_submission)} cases...\")\n    \n    # Find test image\n    image_path = None\n    for ext in ['.jpg', '.png', '.tif', '.tiff', '.jpeg']:\n        path = os.path.join(test_images_dir, f\"{case_id}{ext}\")\n        if os.path.exists(path):\n            image_path = path\n            break\n    \n    if image_path is None:\n        annotation = \"authentic\"\n    else:\n        annotation = predict_test_image(trained_model, image_path, device)\n    \n    submissions.append({'case_id': case_id, 'annotation': annotation})\n\n# Create final submission\nfinal_submission = pd.DataFrame(submissions)\nfinal_submission.to_csv('submission.csv', index=False)\nprint(\"✓ Submission file created: submission.csv\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T18:19:50.289971Z","iopub.execute_input":"2025-10-24T18:19:50.290221Z","iopub.status.idle":"2025-10-24T18:19:50.360866Z","shell.execute_reply.started":"2025-10-24T18:19:50.290204Z","shell.execute_reply":"2025-10-24T18:19:50.359896Z"}},"outputs":[],"execution_count":null},{"id":"02e9234e-5098-46df-a39a-9b72522b8bcf","cell_type":"code","source":"# Cell 15: Final Analysis\nauthentic_count = (final_submission['annotation'] == 'authentic').sum()\nforged_count = len(final_submission) - authentic_count\n\nprint(\"=== FINAL SUBMISSION ===\")\nprint(f\"Total cases: {len(final_submission)}\")\nprint(f\"Authentic: {authentic_count} ({authentic_count/len(final_submission)*100:.1f}%)\")\nprint(f\"Forged: {forged_count} ({forged_count/len(final_submission)*100:.1f}%)\")\nprint(f\"\\nFirst 5 predictions:\")\nprint(final_submission.head())\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T18:21:10.640553Z","iopub.execute_input":"2025-10-24T18:21:10.641185Z","iopub.status.idle":"2025-10-24T18:21:10.648984Z","shell.execute_reply.started":"2025-10-24T18:21:10.641160Z","shell.execute_reply":"2025-10-24T18:21:10.647948Z"}},"outputs":[],"execution_count":null},{"id":"79c5794b-855c-4e10-98ea-ca3c757cfc5e","cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}