{"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","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":"nvidiaTeslaT4","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":"code","source":"# thanks to antonoof's work (https://www.kaggle.com/code/antonoof/eda-r-cnn-model), his guide is very good","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Imports\nimport os, cv2, json, torch, torchvision, numpy as np, pandas as pd\nfrom PIL import Image\nfrom tqdm import tqdm\nimport torch.nn.functional as F\nfrom torch.utils.data import Dataset\nimport matplotlib.pyplot as plt\nfrom torchvision import transforms\nfrom sklearn.model_selection import train_test_split\nimport warnings\nwarnings.filterwarnings('ignore')\n\ndevice = 'cuda' if torch.cuda.is_available() else 'cpu'\nprint(device)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-31T05:09:09.714152Z","iopub.execute_input":"2025-10-31T05:09:09.714338Z","iopub.status.idle":"2025-10-31T05:09:18.112474Z","shell.execute_reply.started":"2025-10-31T05:09:09.714321Z","shell.execute_reply":"2025-10-31T05:09:18.111613Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Load and Prepare Data\n\nLet's load all our images into memory for faster training","metadata":{}},{"cell_type":"code","source":"import os\nimport cv2\nimport numpy as np\nimport torch\nfrom torch.utils.data import Dataset, DataLoader\nimport matplotlib.pyplot as plt\n\n# Paths\nBASE = '/kaggle/input/recodai-luc-scientific-image-forgery-detection'\nauthentic_path = f'{BASE}/train_images/authentic'\nforged_path = f'{BASE}/train_images/forged'\nmask_path = f'{BASE}/train_masks'\ntest_path = f'{BASE}/test_images'\n\n# Lists to store data\nall_images = []\nall_labels = []\nall_masks = []\n\nprint(\"Loading all images into memory...\")\n\n# Load authentic images\nfor fname in os.listdir(authentic_path):\n    if fname.endswith('.jpg') or fname.endswith('.png'):\n        img = cv2.imread(os.path.join(authentic_path, fname))\n        img = cv2.resize(img, (224, 224))\n        all_images.append(img)\n        all_labels.append(0)  # 0 = authentic\n        all_masks.append(None)\n\n# Load forged images\nfor fname in os.listdir(forged_path):\n    if fname.endswith('.jpg') or fname.endswith('.png'):\n        img = cv2.imread(os.path.join(forged_path, fname))\n        img = cv2.resize(img, (224, 224))\n        all_images.append(img)\n        all_labels.append(1)  # 1 = forged\n\n        # Load mask safely\n        mask_fname = fname.replace('.jpg', '.npy').replace('.png', '.npy')\n        mask_path_full = os.path.join(mask_path, mask_fname)\n\n        mask = np.zeros((224, 224), dtype=np.uint8)  # default mask\n        if os.path.exists(mask_path_full):\n            loaded_mask = np.load(mask_path_full)\n            if loaded_mask is not None and loaded_mask.ndim == 2 and loaded_mask.size > 0:\n                mask = cv2.resize(loaded_mask, (224, 224))\n        all_masks.append(mask)\n\nprint(f\"Loaded {len(all_images)} images total\")\nprint(f\"Authentic: {all_labels.count(0)}, Forged: {all_labels.count(1)}\")\n\n# Custom Dataset\nclass ForgeryDataset(Dataset):\n    def __init__(self, images, labels, masks):\n        self.images = images\n        self.labels = labels\n        self.masks = masks\n\n    def __len__(self):\n        return len(self.images)\n\n    def __getitem__(self, idx):\n        img = self.images[idx]\n        label = self.labels[idx]\n        img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n        img = img.astype(np.float32) / 255.0\n        img = torch.from_numpy(img).permute(2, 0, 1)  # Channels first\n\n        # Handle masks and bounding boxes\n        if label == 1:  # Forged\n            mask = self.masks[idx]\n            if mask is None:\n                mask = np.zeros((224, 224))\n            boxes = []\n            if mask.max() > 0:\n                contours, _ = cv2.findContours(mask.astype(np.uint8), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)\n                for cnt in contours:\n                    x, y, w, h = cv2.boundingRect(cnt)\n                    boxes.append([x, y, x+w, y+h])\n            if len(boxes) == 0:\n                boxes = torch.zeros((0, 4), dtype=torch.float32)\n                labels_t = torch.zeros((0,), dtype=torch.int64)\n            else:\n                boxes = torch.tensor(boxes, dtype=torch.float32)\n                labels_t = torch.ones((len(boxes),), dtype=torch.int64)\n        else:  # Authentic\n            boxes = torch.zeros((0, 4), dtype=torch.float32)\n            labels_t = torch.zeros((0,), dtype=torch.int64)\n\n        target = {\n            'boxes': boxes,\n            'labels': labels_t,\n            'image_id': torch.tensor([idx])\n        }\n\n        return img, target\n\n# Create dataset instance\ndataset = ForgeryDataset(all_images, all_labels, all_masks)\n\n# Function to plot sample images with bounding boxes\ndef plot_samples(dataset, num_samples=5):\n    plt.figure(figsize=(15, 5))\n    for i in range(num_samples):\n        img, target = dataset[i]\n        img_np = img.permute(1, 2, 0).numpy()\n        plt.subplot(1, num_samples, i+1)\n        plt.imshow(img_np)\n        plt.axis('off')\n\n        boxes = target['boxes']\n        for box in boxes:\n            x1, y1, x2, y2 = box\n            rect = plt.Rectangle((x1, y1), x2-x1, y2-y1, fill=False, color='red', linewidth=2)\n            plt.gca().add_patch(rect)\n    plt.show()\n\n# Plot 5 sample images\nplot_samples(dataset, num_samples=5)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-31T05:20:26.079975Z","iopub.execute_input":"2025-10-31T05:20:26.080234Z","iopub.status.idle":"2025-10-31T05:23:15.664238Z","shell.execute_reply.started":"2025-10-31T05:20:26.080216Z","shell.execute_reply":"2025-10-31T05:23:15.663383Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\n\n# Count images per class\nauthentic_count = all_labels.count(0)\nforged_count = all_labels.count(1)\n\n# Bar plot\nplt.figure(figsize=(6,4))\nplt.bar(['Authentic', 'Forged'], [authentic_count, forged_count], color=['green', 'red'])\nplt.title('Number of Images per Class')\nplt.ylabel('Count')\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-31T05:23:27.663806Z","iopub.execute_input":"2025-10-31T05:23:27.664567Z","iopub.status.idle":"2025-10-31T05:23:27.772564Z","shell.execute_reply.started":"2025-10-31T05:23:27.664544Z","shell.execute_reply":"2025-10-31T05:23:27.771984Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Create Custom Dataset\n\nSimple dataset that returns images and their bounding boxes","metadata":{}},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class ForgeryDataset(Dataset):\n    def __init__(self, images, labels, masks):\n        self.images = images\n        self.labels = labels\n        self.masks = masks\n        \n    def __len__(self):\n        return len(self.images)\n    \n    def __getitem__(self, idx):\n        img = self.images[idx]\n        label = self.labels[idx]\n        \n        # Convert BGR to RGB (OpenCV loads as BGR)\n        img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n        \n        # Normalize to [0, 1] range\n        img = img.astype(np.float32) / 255.0\n        \n        # Convert to tensor (channels first)\n        img = torch.from_numpy(img).permute(2, 0, 1)\n        \n        if label == 1:  # Forged image\n            mask = self.masks[idx]\n            if mask is None:\n                mask = np.zeros((224, 224))\n            \n            # Find bounding box from mask\n            if mask.max() > 0:\n                # Find contours\n                contours, _ = cv2.findContours(\n                    mask.astype(np.uint8), \n                    cv2.RETR_EXTERNAL, \n                    cv2.CHAIN_APPROX_SIMPLE\n                )\n                \n                boxes = []\n                for cnt in contours:\n                    x, y, w, h = cv2.boundingRect(cnt)\n                    boxes.append([x, y, x+w, y+h])\n                \n                if len(boxes) == 0:\n                    boxes = torch.zeros((0, 4), dtype=torch.float32)\n                    labels_t = torch.zeros((0,), dtype=torch.int64)\n                else:\n                    boxes = torch.tensor(boxes, dtype=torch.float32)\n                    labels_t = torch.ones((len(boxes),), dtype=torch.int64)\n            else:\n                boxes = torch.zeros((0, 4), dtype=torch.float32)\n                labels_t = torch.zeros((0,), dtype=torch.int64)\n        else:  # Authentic\n            boxes = torch.zeros((0, 4), dtype=torch.float32)\n            labels_t = torch.zeros((0,), dtype=torch.int64)\n        \n        target = {\n            'boxes': boxes,\n            'labels': labels_t,\n            'image_id': torch.tensor([idx])\n        }\n        \n        return img, target","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-31T05:23:31.744423Z","iopub.execute_input":"2025-10-31T05:23:31.744692Z","iopub.status.idle":"2025-10-31T05:23:31.753108Z","shell.execute_reply.started":"2025-10-31T05:23:31.744673Z","shell.execute_reply":"2025-10-31T05:23:31.752419Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\nimport torch\nfrom torch.utils.data import DataLoader\n\n# Create the dataset\ndataset = ForgeryDataset(all_images, all_labels, all_masks)\n\n# Function to plot samples with bounding boxes\ndef plot_samples(dataset, num_samples=5):\n    plt.figure(figsize=(15, 5))\n    for i in range(num_samples):\n        img, target = dataset[i]\n        # Convert tensor to numpy image (C,H,W -> H,W,C)\n        img_np = img.permute(1, 2, 0).numpy()\n        \n        plt.subplot(1, num_samples, i+1)\n        plt.imshow(img_np)\n        plt.axis('off')\n        \n        boxes = target['boxes']\n        # Draw bounding boxes\n        for box in boxes:\n            x1, y1, x2, y2 = box\n            rect = plt.Rectangle(\n                (x1, y1), x2-x1, y2-y1,\n                fill=False, color='red', linewidth=2\n            )\n            plt.gca().add_patch(rect)\n    plt.show()\n\n# Plot 5 sample images\nplot_samples(dataset, num_samples=5)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-31T05:23:34.937419Z","iopub.execute_input":"2025-10-31T05:23:34.937912Z","iopub.status.idle":"2025-10-31T05:23:35.159477Z","shell.execute_reply.started":"2025-10-31T05:23:34.937887Z","shell.execute_reply":"2025-10-31T05:23:35.158765Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Split Data\n\n70/30 split for training and validation","metadata":{}},{"cell_type":"code","source":"# Use sklearn for splitting - it's more reliable\ntrain_idx, val_idx = train_test_split(\n    range(len(all_images)), \n    test_size=0.3,  # 30% validation\n    shuffle=True\n)\n\n# Create train/val datasets\ntrain_images = [all_images[i] for i in train_idx]\ntrain_labels = [all_labels[i] for i in train_idx]\ntrain_masks = [all_masks[i] for i in train_idx]\n\nval_images = [all_images[i] for i in val_idx]\nval_labels = [all_labels[i] for i in val_idx]\nval_masks = [all_masks[i] for i in val_idx]\n\ntrain_dataset = ForgeryDataset(train_images, train_labels, train_masks)\nval_dataset = ForgeryDataset(val_images, val_labels, val_masks)\n\nprint(f\"Train: {len(train_dataset)}, Val: {len(val_dataset)}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-31T05:23:40.848548Z","iopub.execute_input":"2025-10-31T05:23:40.849303Z","iopub.status.idle":"2025-10-31T05:23:40.857357Z","shell.execute_reply.started":"2025-10-31T05:23:40.849276Z","shell.execute_reply":"2025-10-31T05:23:40.856535Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Build Model\n\nUsing Faster R-CNN - it's faster than Mask R-CNN and we can generate masks from bounding boxes!","metadata":{}},{"cell_type":"markdown","source":"## Training Setup\n\nUsing Adam optimizer - it adapts learning rate automatically which is better than SGD","metadata":{}},{"cell_type":"markdown","source":"Forgery_Detector/\n│\n├─ data/\n│   ├─ train/        # images\n│   ├─ val/          # images\n│   └─ annotations/  # COCO or VOC format annotations\n│\n├─ models/\n│   └─ fasterrcnn_offline.pth  # optional pretrained offline model\n│\n├─ train.py           # training script\n├─ detect.py          # inference & visualization\n└─ utils.py           # helper functions (dataset, collate_fn, visualization)\n","metadata":{}},{"cell_type":"code","source":"import torch\nfrom torch.utils.data import DataLoader\nfrom torchvision.datasets import VOCDetection\nimport torchvision.transforms as T\nimport matplotlib.pyplot as plt\nimport cv2\n\n# Collate function for detection\ndef collate_fn(batch):\n    return tuple(zip(*batch))\n\n# Simple visualization\ndef visualize_boxes(image, boxes, labels=None):\n    img = image.copy()\n    for i, box in enumerate(boxes):\n        x1, y1, x2, y2 = box.int()\n        color = (255,0,0)\n        cv2.rectangle(img, (x1, y1), (x2, y2), color, 2)\n        if labels:\n            cv2.putText(img, str(labels[i]), (x1, y1-5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 1)\n    plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))\n    plt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-31T05:31:12.205781Z","iopub.execute_input":"2025-10-31T05:31:12.206428Z","iopub.status.idle":"2025-10-31T05:31:12.214463Z","shell.execute_reply.started":"2025-10-31T05:31:12.206402Z","shell.execute_reply":"2025-10-31T05:31:12.213765Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nimport matplotlib.pyplot as plt\nimport cv2\n\n# Collate function for detection datasets\ndef collate_fn(batch):\n    return tuple(zip(*batch))\n\n# Visualize bounding boxes\ndef visualize_boxes(image, boxes, labels=None):\n    img = image.copy()\n    for i, box in enumerate(boxes):\n        x1, y1, x2, y2 = box.int()\n        color = (255,0,0)\n        cv2.rectangle(img, (x1, y1), (x2, y2), color, 2)\n        if labels:\n            cv2.putText(img, str(labels[i]), (x1, y1-5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 1)\n    plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))\n    plt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-31T05:33:06.265219Z","iopub.execute_input":"2025-10-31T05:33:06.265941Z","iopub.status.idle":"2025-10-31T05:33:06.271241Z","shell.execute_reply.started":"2025-10-31T05:33:06.265910Z","shell.execute_reply":"2025-10-31T05:33:06.270490Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ----- Helper functions (replaces utils.py) -----\n\nimport torch\nimport matplotlib.pyplot as plt\nimport cv2\n\n# Collate function for object detection\ndef collate_fn(batch):\n    return tuple(zip(*batch))\n\n# Visualize bounding boxes\ndef visualize_boxes(image, boxes, labels=None):\n    img = image.copy()\n    for i, box in enumerate(boxes):\n        x1, y1, x2, y2 = box.int()\n        color = (255,0,0)\n        cv2.rectangle(img, (x1, y1), (x2, y2), color, 2)\n        if labels:\n            cv2.putText(img, str(labels[i]), (x1, y1-5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 1)\n    plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))\n    plt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-31T05:33:51.747164Z","iopub.execute_input":"2025-10-31T05:33:51.747674Z","iopub.status.idle":"2025-10-31T05:33:51.753398Z","shell.execute_reply.started":"2025-10-31T05:33:51.747653Z","shell.execute_reply":"2025-10-31T05:33:51.752619Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nimport torchvision\nfrom torchvision.models.detection import FasterRCNN\nfrom torchvision.models.detection.faster_rcnn import FastRCNNPredictor\nfrom torchvision.models import resnet18\nfrom torch import nn\n\n# 1️⃣ Create a small ResNet18 backbone (offline)\nbackbone = resnet18(weights=None)  # no download\nbackbone = nn.Sequential(*list(backbone.children())[:-2])  # remove last layers\nbackbone.out_channels = 512  # required by FasterRCNN\n\n# 2️⃣ Create Faster R-CNN model\nnum_classes = 2  # background + forgery\nmodel = FasterRCNN(backbone, num_classes=num_classes)\n\n# 3️⃣ Move to GPU if available\ndevice = torch.device('cuda') if torch.cuda.is_available() else torch.device('cpu')\nmodel.to(device)\n\nprint(\"Offline Faster R-CNN (ResNet18 backbone) ready to use!\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-31T05:37:21.723479Z","iopub.execute_input":"2025-10-31T05:37:21.724203Z","iopub.status.idle":"2025-10-31T05:37:22.387950Z","shell.execute_reply.started":"2025-10-31T05:37:21.724176Z","shell.execute_reply":"2025-10-31T05:37:22.387273Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from torch.utils.data import Dataset, DataLoader\n\nclass CustomDataset(Dataset):\n    def __init__(self, images, targets, transforms=None):\n        self.images = images\n        self.targets = targets\n        self.transforms = transforms\n\n    def __getitem__(self, idx):\n        img = self.images[idx]\n        target = self.targets[idx]\n        if self.transforms:\n            img = self.transforms(img)\n        return img, target\n\n    def __len__(self):\n        return len(self.images)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-31T05:37:48.547723Z","iopub.execute_input":"2025-10-31T05:37:48.548447Z","iopub.status.idle":"2025-10-31T05:37:48.553250Z","shell.execute_reply.started":"2025-10-31T05:37:48.548421Z","shell.execute_reply":"2025-10-31T05:37:48.552376Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nfrom torch import nn\nfrom torchvision.models.detection import FasterRCNN\nfrom torchvision.models.detection.backbone_utils import resnet_fpn_backbone\nfrom torchvision.models.detection.faster_rcnn import FastRCNNPredictor\n\ndevice = torch.device(\"cuda\") if torch.cuda.is_available() else torch.device(\"cpu\")\n\n# 1️⃣ Create ResNet18 backbone with FPN (fully offline)\nbackbone = resnet_fpn_backbone('resnet18', pretrained=False)  # no download\n\n# 2️⃣ Create Faster R-CNN model\nnum_classes = 2  # background + forgery\nmodel = FasterRCNN(backbone, num_classes=num_classes)\nmodel.to(device)\n\nprint(\"Offline Faster R-CNN with ResNet18+FPN ready!\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-31T05:39:19.203258Z","iopub.execute_input":"2025-10-31T05:39:19.203949Z","iopub.status.idle":"2025-10-31T05:39:19.500318Z","shell.execute_reply.started":"2025-10-31T05:39:19.203927Z","shell.execute_reply":"2025-10-31T05:39:19.499670Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\nimport cv2\nimport torch\n\ndef visualize_boxes(image, boxes, labels=None):\n    img = image.copy()\n    for i, box in enumerate(boxes):\n        x1, y1, x2, y2 = box\n        # Convert to Python ints\n        x1, y1, x2, y2 = int(x1), int(y1), int(x2), int(y2)\n        color = (255,0,0)\n        cv2.rectangle(img, (x1, y1), (x2, y2), color, 2)\n        if labels:\n            cv2.putText(img, str(labels[i]), (x1, y1-5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 1)\n    plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))\n    plt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-31T05:39:30.998788Z","iopub.execute_input":"2025-10-31T05:39:30.999336Z","iopub.status.idle":"2025-10-31T05:39:31.004479Z","shell.execute_reply.started":"2025-10-31T05:39:30.999314Z","shell.execute_reply":"2025-10-31T05:39:31.003906Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Example dummy batch\nbatch = [(torch.randn(3,224,224), {\"boxes\": torch.tensor([[10,10,50,50]]), \"labels\": torch.tensor([1])})]\nimages, targets = collate_fn(batch)\nprint(images, targets)\n\n# Example visualization\nimport numpy as np\ndummy_image = np.zeros((100,100,3), dtype=np.uint8)\ndummy_boxes = torch.tensor([[10,10,50,50]])\nvisualize_boxes(dummy_image, dummy_boxes)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-31T05:39:34.674281Z","iopub.execute_input":"2025-10-31T05:39:34.674835Z","iopub.status.idle":"2025-10-31T05:39:34.785704Z","shell.execute_reply.started":"2025-10-31T05:39:34.674813Z","shell.execute_reply":"2025-10-31T05:39:34.785097Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\n\nroot_dir = \"/kaggle/input/recodai-luc-scientific-image-forgery-detection\"\nprint(os.listdir(root_dir))  # see what files/folders exist\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-31T05:44:47.102691Z","iopub.execute_input":"2025-10-31T05:44:47.103461Z","iopub.status.idle":"2025-10-31T05:44:47.109487Z","shell.execute_reply.started":"2025-10-31T05:44:47.103435Z","shell.execute_reply":"2025-10-31T05:44:47.108765Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\n\ntrain_path = \"/kaggle/input/recodai-luc-scientific-image-forgery-detection/train_images\"\nprint(os.listdir(train_path))  # see all files/folders\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-31T05:47:02.710659Z","iopub.execute_input":"2025-10-31T05:47:02.710956Z","iopub.status.idle":"2025-10-31T05:47:02.716303Z","shell.execute_reply.started":"2025-10-31T05:47:02.710933Z","shell.execute_reply":"2025-10-31T05:47:02.715615Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"data_loader = DataLoader(\n    dataset, \n    batch_size=2, \n    shuffle=True, \n    collate_fn=collate_fn,\n    num_workers=4 \n)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-31T06:04:20.273912Z","iopub.execute_input":"2025-10-31T06:04:20.274605Z","iopub.status.idle":"2025-10-31T06:04:20.278200Z","shell.execute_reply.started":"2025-10-31T06:04:20.274580Z","shell.execute_reply":"2025-10-31T06:04:20.277412Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from torchvision import transforms\n\ntransform = transforms.Compose([\n    transforms.ToTensor()\n])\n\ndef __getitem__(self, idx):\n    img_path = self.image_paths[idx]\n    img = Image.open(img_path).convert(\"RGB\")\n    img_tensor = transform(img)\n    target = {\n        \"boxes\": torch.tensor([[0, 0, img.width, img.height]], dtype=torch.float32),\n        \"labels\": torch.tensor([self.labels[idx]], dtype=torch.int64)\n    }\n    return img_tensor, target\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-31T06:04:26.126693Z","iopub.execute_input":"2025-10-31T06:04:26.127399Z","iopub.status.idle":"2025-10-31T06:04:26.132121Z","shell.execute_reply.started":"2025-10-31T06:04:26.127373Z","shell.execute_reply":"2025-10-31T06:04:26.131255Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class ForgeryDataset(Dataset):\n    def __init__(self, root_dir, transform=None):\n        self.images = []\n        self.labels = []\n        self.transform = transform\n        for label, subfolder in enumerate([\"authentic\", \"forged\"]):\n            folder_path = os.path.join(root_dir, subfolder)\n            for f in os.listdir(folder_path):\n                if f.endswith((\".png\", \".jpg\", \".jpeg\", \".tif\")):\n                    img = Image.open(os.path.join(folder_path, f)).convert(\"RGB\")\n                    self.images.append(img)\n                    self.labels.append(label)\n        print(f\"Loaded {len(self.images)} images into memory\")\n\n    def __len__(self):\n        return len(self.images)\n\n    def __getitem__(self, idx):\n        img = self.images[idx]\n        if self.transform:\n            img = self.transform(img)\n        target = {\n            \"boxes\": torch.tensor([[0, 0, img.width, img.height]], dtype=torch.float32),\n            \"labels\": torch.tensor([self.labels[idx]], dtype=torch.int64)\n        }\n        return img, target\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-31T06:05:25.323216Z","iopub.execute_input":"2025-10-31T06:05:25.323716Z","iopub.status.idle":"2025-10-31T06:05:25.329937Z","shell.execute_reply.started":"2025-10-31T06:05:25.323696Z","shell.execute_reply":"2025-10-31T06:05:25.329228Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"data_loader = DataLoader(\n    dataset, \n    batch_size=2, \n    shuffle=True, \n    collate_fn=collate_fn,\n    num_workers=4,\n    pin_memory=True\n)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-31T06:05:40.828032Z","iopub.execute_input":"2025-10-31T06:05:40.828285Z","iopub.status.idle":"2025-10-31T06:05:40.831941Z","shell.execute_reply.started":"2025-10-31T06:05:40.828267Z","shell.execute_reply":"2025-10-31T06:05:40.831246Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nfrom torch.utils.data import Dataset, DataLoader\nimport torchvision\nfrom torchvision.models.detection.faster_rcnn import FastRCNNPredictor\nfrom PIL import Image\nimport os\n\ndevice = torch.device('cuda') if torch.cuda.is_available() else torch.device('cpu')\n\n# -----------------------------\n# Collate function\n# -----------------------------\ndef collate_fn(batch):\n    return tuple(zip(*batch))\n\nclass ForgeryDataset(Dataset):\n    def __init__(self, root_dir):\n        self.image_paths = []\n        self.labels = []  # 0 = authentic, 1 = forged\n\n        # List all images\n        for f in os.listdir(root_dir):\n            if f.endswith((\".png\", \".jpg\", \".jpeg\", \".tif\")):\n                self.image_paths.append(os.path.join(root_dir, f))\n                \n                # Example: if filename contains \"forged\" -> label 1, else 0\n                if \"forged\" in f.lower():\n                    self.labels.append(1)\n                else:\n                    self.labels.append(0)\n\n        print(f\"Found {len(self.image_paths)} images\")\n\n    def __len__(self):\n        return len(self.image_paths)\n\n    def __getitem__(self, idx):\n        img_path = self.image_paths[idx]\n        img = Image.open(img_path).convert(\"RGB\")\n        img_tensor = torchvision.transforms.functional.to_tensor(img)\n\n        # Placeholder: full image as bounding box\n        target = {\n            \"boxes\": torch.tensor([[0, 0, img.width, img.height]], dtype=torch.float32),\n            \"labels\": torch.tensor([self.labels[idx]], dtype=torch.int64)\n        }\n        return img_tensor, target\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-31T06:05:45.920367Z","iopub.execute_input":"2025-10-31T06:05:45.921017Z","iopub.status.idle":"2025-10-31T06:05:45.927999Z","shell.execute_reply.started":"2025-10-31T06:05:45.920992Z","shell.execute_reply":"2025-10-31T06:05:45.927208Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Adam optimizer is better for this task\noptimizer = torch.optim.Adam(model.parameters(), lr=0.001)\n\n# Cosine annealing scheduler - modern approach\nscheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=10)\n\nNUM_EPOCHS = 15\nBATCH_SIZE = 1  # Process one image at a time for stability","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-31T06:05:52.150202Z","iopub.execute_input":"2025-10-31T06:05:52.150726Z","iopub.status.idle":"2025-10-31T06:05:52.155181Z","shell.execute_reply.started":"2025-10-31T06:05:52.150704Z","shell.execute_reply":"2025-10-31T06:05:52.154458Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from torchvision import transforms\n\ntransform = transforms.Compose([\n    transforms.ToTensor()\n])\n\ndata_loader = DataLoader(\n    dataset, \n    batch_size=4,        \n    shuffle=True,\n    collate_fn=collate_fn,\n    num_workers=4,       # parallel loading\n    pin_memory=True      # GPU fast transfer\n)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-31T06:07:27.356674Z","iopub.execute_input":"2025-10-31T06:07:27.357481Z","iopub.status.idle":"2025-10-31T06:07:27.361339Z","shell.execute_reply.started":"2025-10-31T06:07:27.357447Z","shell.execute_reply":"2025-10-31T06:07:27.360684Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from torch.utils.data import Dataset, DataLoader\nfrom PIL import Image\nimport os\nimport torchvision.transforms as transforms\n\ntransform = transforms.Compose([\n    transforms.Resize((224,224)),\n    transforms.ToTensor()\n])\n\nclass ForgeryDataset(Dataset):\n    def __init__(self, root_dir, transform=None):\n        self.transform = transform\n        self.samples = []\n        for label, subfolder in enumerate([\"authentic\", \"forged\"]):\n            folder = os.path.join(root_dir, subfolder)\n            for f in os.listdir(folder):\n                if f.lower().endswith((\".png\", \".jpg\", \".jpeg\", \".tif\")):\n                    self.samples.append((os.path.join(folder, f), label))\n    \n    def __len__(self):\n        return len(self.samples)\n\n    def __getitem__(self, idx):\n        path, label = self.samples[idx]\n        img = Image.open(path).convert(\"RGB\")\n        if self.transform:\n            img = self.transform(img)\n        return img, label\n\ndataset = ForgeryDataset(\"/kaggle/input/recodai-luc-scientific-image-forgery-detection/train_images\", transform)\ndata_loader = DataLoader(dataset, batch_size=16, shuffle=True, num_workers=2, pin_memory=True)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-31T06:11:34.359930Z","iopub.execute_input":"2025-10-31T06:11:34.360612Z","iopub.status.idle":"2025-10-31T06:11:34.380763Z","shell.execute_reply.started":"2025-10-31T06:11:34.360585Z","shell.execute_reply":"2025-10-31T06:11:34.380236Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torchvision import models, transforms\nfrom torch.utils.data import Dataset, DataLoader\nfrom PIL import Image\nimport os\nimport matplotlib.pyplot as plt\n\n# -----------------------------\n# Device\n# -----------------------------\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nprint(\"Using device:\", device)\n\n# -----------------------------\n# Custom Dataset\n# -----------------------------\nclass ForgeryDataset(Dataset):\n    def __init__(self, root_dir, transform=None):\n        self.image_paths = []\n        self.labels = []  # 0 = authentic, 1 = forged\n        self.transform = transform\n\n        for label, subfolder in enumerate([\"authentic\", \"forged\"]):\n            folder_path = os.path.join(root_dir, subfolder)\n            for f in os.listdir(folder_path):\n                if f.endswith((\".png\", \".jpg\", \".jpeg\", \".tif\")):\n                    self.image_paths.append(os.path.join(folder_path, f))\n                    self.labels.append(label)\n        print(f\"Found {len(self.image_paths)} images\")\n\n    def __len__(self):\n        return len(self.image_paths)\n\n    def __getitem__(self, idx):\n        img_path = self.image_paths[idx]\n        img = Image.open(img_path).convert(\"RGB\")\n\n        if self.transform:\n            img = self.transform(img)\n\n        label = torch.tensor(self.labels[idx], dtype=torch.long)\n        return img, label\n\n\n# -----------------------------\n# Transforms\n# -----------------------------\ntransform = transforms.Compose([\n    transforms.Resize((224, 224)),\n    transforms.ToTensor()\n])\n\n# -----------------------------\n# Dataset & DataLoader\n# -----------------------------\ntrain_dir = \"/kaggle/input/recodai-luc-scientific-image-forgery-detection/train_images\"\ndataset = ForgeryDataset(train_dir, transform=transform)\ndata_loader = DataLoader(dataset, batch_size=16, shuffle=True)\n\n# -----------------------------\n# Offline Model (No Download)\n# -----------------------------\nmodel = models.resnet18(pretrained=False)  # offline, no internet\nmodel.fc = nn.Linear(model.fc.in_features, 2)  # 2 classes: authentic/forged\nmodel.to(device)\n\n# -----------------------------\n# Loss & Optimizer\n# -----------------------------\ncriterion = nn.CrossEntropyLoss()\noptimizer = optim.Adam(model.parameters(), lr=0.001)\n\n# -----------------------------\n# Training Loop\n# -----------------------------\nnum_epochs = 3\nlosses = []\n\nfor epoch in range(num_epochs):\n    model.train()\n    running_loss = 0.0\n    for images, labels in data_loader:\n        images, labels = images.to(device), labels.to(device)\n\n        optimizer.zero_grad()\n        outputs = model(images)\n        loss = criterion(outputs, labels)\n        loss.backward()\n        optimizer.step()\n\n        running_loss += loss.item()\n\n    avg_loss = running_loss / len(data_loader)\n    losses.append(avg_loss)\n    print(f\"Epoch [{epoch+1}/{num_epochs}] Loss: {avg_loss:.4f}\")\n\n# -----------------------------\n# Save Model\n# -----------------------------\ntorch.save(model.state_dict(), \"resnet18_forgery_offline.pth\")\nprint(\"✅ Model saved as resnet18_forgery_offline.pth\")\n\n# -----------------------------\n# Plot Loss Curve\n# -----------------------------\nplt.figure(figsize=(6,4))\nplt.plot(range(1, num_epochs+1), losses, marker='o')\nplt.title(\"Training Loss Curve (Offline ResNet18)\")\nplt.xlabel(\"Epoch\")\nplt.ylabel(\"Loss\")\nplt.grid(True)\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-31T06:18:14.798664Z","iopub.execute_input":"2025-10-31T06:18:14.799035Z","iopub.status.idle":"2025-10-31T06:28:52.640193Z","shell.execute_reply.started":"2025-10-31T06:18:14.799010Z","shell.execute_reply":"2025-10-31T06:28:52.639411Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Helper Function: Box to Mask Conversion\n\nSince Faster R-CNN gives us boxes, we convert them to masks for submission","metadata":{}},{"cell_type":"code","source":"def boxes_to_mask(boxes, image_shape):\n    \"\"\"\n    Convert bounding boxes to a binary mask\n    \"\"\"\n    mask = np.zeros(image_shape, dtype=np.uint8)\n    \n    for box in boxes:\n        x1, y1, x2, y2 = box\n        x1, y1, x2, y2 = int(x1), int(y1), int(x2), int(y2)\n        \n        # Fill the box region\n        mask[y1:y2, x1:x2] = 1\n    \n    return mask","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-31T06:29:56.263998Z","iopub.execute_input":"2025-10-31T06:29:56.264271Z","iopub.status.idle":"2025-10-31T06:29:56.268723Z","shell.execute_reply.started":"2025-10-31T06:29:56.264252Z","shell.execute_reply":"2025-10-31T06:29:56.268033Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## RLE Encoding\n\nEncode masks as run-length for submission","metadata":{}},{"cell_type":"code","source":"def encode_rle(mask):\n    \"\"\"\n    Simple RLE encoding\n    \"\"\"\n    # Flatten mask row by row\n    pixels = mask.flatten()\n    \n    # Add 0s at start and end for easier calculation\n    pixels = np.concatenate([[0], pixels, [0]])\n    \n    # Find run starts and ends\n    runs = np.where(pixels[1:] != pixels[:-1])[0] + 1\n    \n    # Convert to lengths\n    runs[1::2] = runs[1::2] - runs[::2]\n    \n    # Format as JSON\n    result = {\n        \"counts\": runs.tolist(),\n        \"size\": [mask.shape[0], mask.shape[1]]\n    }\n    \n    return json.dumps(result)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-31T06:29:58.148841Z","iopub.execute_input":"2025-10-31T06:29:58.149164Z","iopub.status.idle":"2025-10-31T06:29:58.153743Z","shell.execute_reply.started":"2025-10-31T06:29:58.149143Z","shell.execute_reply":"2025-10-31T06:29:58.153200Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Generate Test Predictions\n\nProcess test images and create submission","metadata":{}},{"cell_type":"code","source":"import numpy as np\nimport json\n\ndef encode_rle(mask):\n    \"\"\"\n    Simple RLE encoding of a binary mask.\n    \n    Args:\n        mask (np.ndarray): 2D binary mask of shape (height, width)\n    \n    Returns:\n        str: JSON string with RLE counts and mask size\n    \"\"\"\n    # Flatten mask row by row\n    pixels = mask.flatten()\n    \n    # Add 0s at start and end to detect runs at boundaries\n    pixels = np.concatenate([[0], pixels, [0]])\n    \n    # Find where pixel values change (start/end of runs)\n    runs = np.where(pixels[1:] != pixels[:-1])[0] + 1\n    \n    # Convert starts/ends into lengths\n    runs[1::2] = runs[1::2] - runs[::2]\n    \n    # Format result as JSON\n    result = {\n        \"counts\": runs.tolist(),\n        \"size\": [mask.shape[0], mask.shape[1]]\n    }\n    \n    return json.dumps(result)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-31T06:39:10.159239Z","iopub.execute_input":"2025-10-31T06:39:10.159715Z","iopub.status.idle":"2025-10-31T06:39:10.164802Z","shell.execute_reply.started":"2025-10-31T06:39:10.159690Z","shell.execute_reply":"2025-10-31T06:39:10.164106Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"MODEL_PATH = \"/home/user/projects/models/my_model.h5\"\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-31T06:40:45.729918Z","iopub.execute_input":"2025-10-31T06:40:45.730159Z","iopub.status.idle":"2025-10-31T06:40:45.733730Z","shell.execute_reply.started":"2025-10-31T06:40:45.730141Z","shell.execute_reply":"2025-10-31T06:40:45.733178Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nprint(os.getcwd())  # Current working directory\nprint(os.listdir()) # Files in current directory\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-31T06:40:58.643263Z","iopub.execute_input":"2025-10-31T06:40:58.643969Z","iopub.status.idle":"2025-10-31T06:40:58.648387Z","shell.execute_reply.started":"2025-10-31T06:40:58.643946Z","shell.execute_reply":"2025-10-31T06:40:58.647575Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\nfrom torchvision import models\n\nDEVICE = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nMODEL_PATH = \"/kaggle/working/resnet18_forgery_offline.pth\"\n\n# Load ResNet18\nmodel = models.resnet18(pretrained=False)\n\n# FC layer matches checkpoint (2 outputs)\nmodel.fc = nn.Linear(model.fc.in_features, 2)  # **2 outputs** because checkpoint has 2\n\n# Load checkpoint\nmodel.load_state_dict(torch.load(MODEL_PATH, map_location=DEVICE))\nmodel = model.to(DEVICE)\nmodel.eval()\n\nprint(\"Model loaded successfully!\")\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-01T01:51:49.743428Z","iopub.execute_input":"2025-11-01T01:51:49.743686Z","iopub.status.idle":"2025-11-01T01:51:50.151826Z","shell.execute_reply.started":"2025-11-01T01:51:49.743668Z","shell.execute_reply":"2025-11-01T01:51:50.150913Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\n\n# Example data — replace this with your actual predictions\npredictions = [0, 1, 0, 1]\nids = [1, 2, 3, 4]\n\nsubmission = pd.DataFrame({\n    'id': ids,\n    'label': predictions\n})\n\n# Save it to /kaggle/working/\nsubmission.to_csv('/kaggle/working/submission.csv', index=False)\n\nprint(\"✅ Submission file created successfully!\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-01T01:52:21.140731Z","iopub.execute_input":"2025-11-01T01:52:21.141253Z","iopub.status.idle":"2025-11-01T01:52:21.153852Z","shell.execute_reply.started":"2025-11-01T01:52:21.141229Z","shell.execute_reply":"2025-11-01T01:52:21.152988Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\nsubmission = pd.read_csv('/kaggle/working/submission.csv')\nsubmission.head()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-01T01:52:46.702595Z","iopub.execute_input":"2025-11-01T01:52:46.703285Z","iopub.status.idle":"2025-11-01T01:52:46.727668Z","shell.execute_reply.started":"2025-11-01T01:52:46.703263Z","shell.execute_reply":"2025-11-01T01:52:46.726895Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}