{"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":14878066,"sourceType":"competition"}],"dockerImageVersionId":31240,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\nimport cv2\nimport numpy as np\nimport pandas as pd\nfrom tqdm import tqdm\n\nimport torch\nimport torch.nn as nn\nfrom torch.utils.data import Dataset, DataLoader\n\n\nimport albumentations as A\nfrom albumentations.pytorch import ToTensorV2\n\nfrom torchvision.models.segmentation import deeplabv3_resnet50","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-12-25T13:37:59.296718Z","iopub.execute_input":"2025-12-25T13:37:59.297027Z","iopub.status.idle":"2025-12-25T13:37:59.301579Z","shell.execute_reply.started":"2025-12-25T13:37:59.297006Z","shell.execute_reply":"2025-12-25T13:37:59.300801Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"TRAIN_FORGED = \"/kaggle/input/recodai-luc-scientific-image-forgery-detection/train_images/forged\"\nTRAIN_MASKS  = \"/kaggle/input/recodai-luc-scientific-image-forgery-detection/train_masks\"\n\nTEST_IMAGES  = \"/kaggle/input/recodai-luc-scientific-image-forgery-detection/test_images\"","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-25T13:38:03.046575Z","iopub.execute_input":"2025-12-25T13:38:03.046862Z","iopub.status.idle":"2025-12-25T13:38:03.050860Z","shell.execute_reply.started":"2025-12-25T13:38:03.046839Z","shell.execute_reply":"2025-12-25T13:38:03.050197Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\n\nimg_name = os.listdir(TRAIN_FORGED)[0]\n\nimage = cv2.imread(os.path.join(TRAIN_FORGED, img_name))\nimage = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n\nraw_mask = np.load(os.path.join(TRAIN_MASKS, img_name.replace(\".png\", \".npy\")))\nmask = (raw_mask.sum(axis=0) > 0).astype(np.uint8)\n\nplt.figure(figsize=(12,4))\n\nplt.subplot(1,3,1)\nplt.title(\"Image\")\nplt.imshow(image)\nplt.axis(\"off\")\n\nplt.subplot(1,3,2)\nplt.title(\"Mask\")\nplt.imshow(mask, cmap=\"gray\")\nplt.axis(\"off\")\n\nplt.subplot(1,3,3)\nplt.title(\"Overlay\")\nplt.imshow(image)\nplt.imshow(mask, cmap=\"Reds\", alpha=0.4)\nplt.axis(\"off\")\n\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-25T13:38:45.661831Z","iopub.execute_input":"2025-12-25T13:38:45.662151Z","iopub.status.idle":"2025-12-25T13:38:46.217645Z","shell.execute_reply.started":"2025-12-25T13:38:45.662126Z","shell.execute_reply":"2025-12-25T13:38:46.216964Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_tfms = A.Compose([\n    A.Resize(256, 256),\n    A.HorizontalFlip(p=0.5),\n    A.VerticalFlip(p=0.5),\n    A.Normalize(),\n    ToTensorV2()\n])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-25T13:39:29.112271Z","iopub.execute_input":"2025-12-25T13:39:29.112550Z","iopub.status.idle":"2025-12-25T13:39:29.119831Z","shell.execute_reply.started":"2025-12-25T13:39:29.112531Z","shell.execute_reply":"2025-12-25T13:39:29.119239Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class ForgeryDataset(Dataset):\n    def __init__(self, img_dir, mask_dir=None, transform=None):\n        self.img_dir = img_dir\n        self.mask_dir = mask_dir\n        self.images = sorted(os.listdir(img_dir))\n        self.transform = transform\n\n    def __len__(self):\n        return len(self.images)\n\n    def __getitem__(self, idx):\n        img_name = self.images[idx]\n\n        image = cv2.imread(os.path.join(self.img_dir, img_name))\n        image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n\n        if self.mask_dir:\n            raw_mask = np.load(\n                os.path.join(self.mask_dir, img_name.replace(\".png\", \".npy\"))\n            )\n            mask = (raw_mask.sum(axis=0) > 0).astype(np.float32)\n        else:\n            mask = np.zeros(image.shape[:2], dtype=np.float32)\n\n        if self.transform:\n            augmented = self.transform(image=image, mask=mask)\n            image = augmented[\"image\"]\n            mask = augmented[\"mask\"].unsqueeze(0)\n\n        return image, mask","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-25T13:39:51.671716Z","iopub.execute_input":"2025-12-25T13:39:51.672071Z","iopub.status.idle":"2025-12-25T13:39:51.678933Z","shell.execute_reply.started":"2025-12-25T13:39:51.672019Z","shell.execute_reply":"2025-12-25T13:39:51.678213Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"ds = ForgeryDataset(TRAIN_FORGED, TRAIN_MASKS, train_tfms)\nimg, mask = ds[0]\n\nprint(\"Image:\", img.shape)\nprint(\"Mask :\", mask.shape)\nprint(\"Mask values:\", torch.unique(mask))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-25T13:40:00.676570Z","iopub.execute_input":"2025-12-25T13:40:00.677431Z","iopub.status.idle":"2025-12-25T13:40:00.774715Z","shell.execute_reply.started":"2025-12-25T13:40:00.677404Z","shell.execute_reply":"2025-12-25T13:40:00.773922Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_loader = DataLoader(\n    ForgeryDataset(TRAIN_FORGED, TRAIN_MASKS, train_tfms),\n    batch_size=8,\n    shuffle=True,\n    num_workers=2,\n    pin_memory=True\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-25T13:40:07.392470Z","iopub.execute_input":"2025-12-25T13:40:07.392750Z","iopub.status.idle":"2025-12-25T13:40:07.399522Z","shell.execute_reply.started":"2025-12-25T13:40:07.392730Z","shell.execute_reply":"2025-12-25T13:40:07.398777Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def dice_loss(pred, target, smooth=1.0):\n    pred = torch.sigmoid(pred)\n    intersection = (pred * target).sum()\n    return 1 - (2. * intersection + smooth) / (\n        pred.sum() + target.sum() + smooth\n    )\n\ndef combined_loss(pred, target):\n    bce = nn.BCEWithLogitsLoss()(pred, target)\n    dice = dice_loss(pred, target)\n    return bce + dice\n\nloss_fn = combined_loss","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-25T13:40:15.032605Z","iopub.execute_input":"2025-12-25T13:40:15.033154Z","iopub.status.idle":"2025-12-25T13:40:15.037870Z","shell.execute_reply.started":"2025-12-25T13:40:15.033132Z","shell.execute_reply":"2025-12-25T13:40:15.037058Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from torchvision.models.segmentation import deeplabv3_resnet50\nimport torch.nn as nn\n\nmodel = deeplabv3_resnet50(\n    weights=None,              # no Deeplab pretrained weights\n    weights_backbone=None,     # no ResNet pretrained weights\n    num_classes=1              # directly set output channels\n)\n\ndevice = \"cuda\" if torch.cuda.is_available() else \"cpu\"\nmodel = model.to(device)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-25T13:40:22.361688Z","iopub.execute_input":"2025-12-25T13:40:22.361984Z","iopub.status.idle":"2025-12-25T13:40:23.114474Z","shell.execute_reply.started":"2025-12-25T13:40:22.361963Z","shell.execute_reply":"2025-12-25T13:40:23.113764Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"optimizer = torch.optim.AdamW(model.parameters(), lr=1e-4)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-25T13:40:28.641910Z","iopub.execute_input":"2025-12-25T13:40:28.642512Z","iopub.status.idle":"2025-12-25T13:40:28.646976Z","shell.execute_reply.started":"2025-12-25T13:40:28.642487Z","shell.execute_reply":"2025-12-25T13:40:28.646158Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"EPOCHS = 10\n\nfor epoch in range(EPOCHS):\n    model.train()\n    epoch_loss = 0\n\n    for imgs, masks in tqdm(train_loader):\n        imgs, masks = imgs.to(device), masks.to(device)\n\n        optimizer.zero_grad()\n        outputs = model(imgs)[\"out\"]\n        loss = loss_fn(outputs, masks)\n\n        loss.backward()\n        optimizer.step()\n\n        epoch_loss += loss.item()\n\n    avg_loss = epoch_loss / len(train_loader)\n    print(f\"Epoch {epoch+1}/{EPOCHS} - Avg Loss: {avg_loss:.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-25T13:40:37.657678Z","iopub.execute_input":"2025-12-25T13:40:37.658278Z","iopub.status.idle":"2025-12-25T14:02:09.636870Z","shell.execute_reply.started":"2025-12-25T13:40:37.658253Z","shell.execute_reply":"2025-12-25T14:02:09.636117Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"THRESH = 0.4\nMIN_AREA = 100\nAUTH_AREA = 150\n\nprint(THRESH, MIN_AREA, AUTH_AREA)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-25T14:02:09.638372Z","iopub.execute_input":"2025-12-25T14:02:09.638853Z","iopub.status.idle":"2025-12-25T14:02:09.643416Z","shell.execute_reply.started":"2025-12-25T14:02:09.638827Z","shell.execute_reply":"2025-12-25T14:02:09.642777Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import cv2\nimport numpy as np\n\ndef post_process_mask(prob_mask, thresh, min_area):\n    binary = (prob_mask > thresh).astype(np.uint8)\n\n    num_labels, labels, stats, _ = cv2.connectedComponentsWithStats(\n        binary, connectivity=8\n    )\n\n    clean_mask = np.zeros_like(binary)\n\n    for i in range(1, num_labels):\n        if stats[i, cv2.CC_STAT_AREA] >= min_area:\n            clean_mask[labels == i] = 1\n\n    return clean_mask","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-25T14:02:09.643981Z","iopub.execute_input":"2025-12-25T14:02:09.644182Z","iopub.status.idle":"2025-12-25T14:02:09.659598Z","shell.execute_reply.started":"2025-12-25T14:02:09.644168Z","shell.execute_reply":"2025-12-25T14:02:09.658927Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def predict_with_tta(model, image):\n    preds = []\n\n    for aug in [None, \"h\", \"v\"]:\n        img = image.copy()\n\n        if aug == \"h\":\n            img = np.fliplr(img)\n        elif aug == \"v\":\n            img = np.flipud(img)\n\n        augmented = test_tfms(image=img)\n        inp = augmented[\"image\"].unsqueeze(0).to(device)\n\n        pred = torch.sigmoid(model(inp)[\"out\"]).cpu().numpy()[0, 0]\n\n        if aug == \"h\":\n            pred = np.fliplr(pred)\n        elif aug == \"v\":\n            pred = np.flipud(pred)\n\n        preds.append(pred)\n\n    return np.mean(preds, axis=0)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-25T14:02:09.661490Z","iopub.execute_input":"2025-12-25T14:02:09.662021Z","iopub.status.idle":"2025-12-25T14:02:09.673651Z","shell.execute_reply.started":"2025-12-25T14:02:09.662003Z","shell.execute_reply":"2025-12-25T14:02:09.673051Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def rle_encode(mask):\n    pixels = mask.flatten(order='F')\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-12-25T14:02:09.674300Z","iopub.execute_input":"2025-12-25T14:02:09.674539Z","iopub.status.idle":"2025-12-25T14:02:09.694363Z","shell.execute_reply.started":"2025-12-25T14:02:09.674516Z","shell.execute_reply":"2025-12-25T14:02:09.693677Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test_tfms = A.Compose([\n    A.Resize(256, 256),\n    A.Normalize(),\n    ToTensorV2()\n])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-25T14:02:09.695142Z","iopub.execute_input":"2025-12-25T14:02:09.695390Z","iopub.status.idle":"2025-12-25T14:02:09.707704Z","shell.execute_reply.started":"2025-12-25T14:02:09.695368Z","shell.execute_reply":"2025-12-25T14:02:09.707236Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"sample_sub = pd.read_csv(\n    \"/kaggle/input/recodai-luc-scientific-image-forgery-detection/sample_submission.csv\"\n)\nsample_sub","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-25T14:02:09.708331Z","iopub.execute_input":"2025-12-25T14:02:09.709156Z","iopub.status.idle":"2025-12-25T14:02:09.746313Z","shell.execute_reply.started":"2025-12-25T14:02:09.709131Z","shell.execute_reply":"2025-12-25T14:02:09.745776Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model.eval()\n\nwith torch.no_grad():\n    # There is only ONE test image in this competition\n    img_name = sorted(os.listdir(TEST_IMAGES))[0]\n\n    image = cv2.imread(os.path.join(TEST_IMAGES, img_name))\n    image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n\n    prob = predict_with_tta(model, image)\n\n    mask = post_process_mask(\n        prob,\n        thresh=THRESH,\n        min_area=MIN_AREA\n    )\n\n    if mask.sum() < AUTH_AREA:\n        annotation = \"authentic\"\n    else:\n        rle = rle_encode(mask)\n        annotation = rle if rle.strip() != \"\" else \"authentic\"","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-25T14:02:09.746809Z","iopub.execute_input":"2025-12-25T14:02:09.746981Z","iopub.status.idle":"2025-12-25T14:02:10.023598Z","shell.execute_reply.started":"2025-12-25T14:02:09.746968Z","shell.execute_reply":"2025-12-25T14:02:10.023003Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"sample_sub[\"annotation\"] = annotation\nsample_sub","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-25T14:02:10.024363Z","iopub.execute_input":"2025-12-25T14:02:10.024573Z","iopub.status.idle":"2025-12-25T14:02:10.032938Z","shell.execute_reply.started":"2025-12-25T14:02:10.024557Z","shell.execute_reply":"2025-12-25T14:02:10.032403Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"sample_sub.to_csv(\"submission.csv\", index=False)\nprint(\"Saved submission.csv\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-25T14:02:10.034507Z","iopub.execute_input":"2025-12-25T14:02:10.034922Z","iopub.status.idle":"2025-12-25T14:02:10.050843Z","shell.execute_reply.started":"2025-12-25T14:02:10.034906Z","shell.execute_reply":"2025-12-25T14:02:10.050256Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(sample_sub.dtypes)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-25T14:02:10.051448Z","iopub.execute_input":"2025-12-25T14:02:10.051676Z","iopub.status.idle":"2025-12-25T14:02:10.060935Z","shell.execute_reply.started":"2025-12-25T14:02:10.051654Z","shell.execute_reply":"2025-12-25T14:02:10.060217Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}