{"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":14878066,"sourceType":"competition"}],"dockerImageVersionId":31240,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"id":"7ed02acf","cell_type":"code","source":"import torch\n\n# Check if GPU is available\ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\nprint(f'Using device: {device}')\n\n# If GPU is available, print GPU details\nif torch.cuda.is_available():\n    print(f'GPU Name: {torch.cuda.get_device_name(0)}')\n    print(f'GPU Memory: {torch.cuda.get_device_properties(0).total_memory / 1024**3:.2f} GB')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-01T19:42:47.359863Z","iopub.execute_input":"2026-01-01T19:42:47.360163Z","iopub.status.idle":"2026-01-01T19:42:47.364924Z","shell.execute_reply.started":"2026-01-01T19:42:47.360144Z","shell.execute_reply":"2026-01-01T19:42:47.364149Z"}},"outputs":[],"execution_count":null},{"id":"61109525","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\"\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-01T19:42:47.366232Z","iopub.execute_input":"2026-01-01T19:42:47.366476Z","iopub.status.idle":"2026-01-01T19:42:47.381126Z","shell.execute_reply.started":"2026-01-01T19:42:47.366456Z","shell.execute_reply":"2026-01-01T19:42:47.380481Z"}},"outputs":[],"execution_count":null},{"id":"4659786f","cell_type":"code","source":"import os\nimport cv2\nimport numpy as np\nimport torch\nimport torch.nn as nn\nfrom torch.utils.data import Dataset, DataLoader\nfrom tqdm import tqdm\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-01T19:42:47.381610Z","iopub.execute_input":"2026-01-01T19:42:47.381776Z","iopub.status.idle":"2026-01-01T19:42:47.396122Z","shell.execute_reply.started":"2026-01-01T19:42:47.381763Z","shell.execute_reply":"2026-01-01T19:42:47.395444Z"}},"outputs":[],"execution_count":null},{"id":"766ab7ac","cell_type":"code","source":"class ForgeryDataset(Dataset):\n    def __init__(self, img_dir, mask_dir, img_size=256):\n        self.img_dir = img_dir\n        self.mask_dir = mask_dir\n        self.img_size = img_size\n        self.images = sorted(os.listdir(img_dir))\n\n    def __len__(self):\n        return len(self.images)\n\n    def __getitem__(self, idx):\n        img_name = self.images[idx]\n\n        # ---- Load image ----\n        img = cv2.imread(os.path.join(self.img_dir, img_name), cv2.IMREAD_GRAYSCALE)\n        img = cv2.resize(img, (self.img_size, self.img_size))\n        img = img / 255.0\n        img = torch.tensor(img, dtype=torch.float32).unsqueeze(0)\n\n        # ---- Load & merge masks ----\n        raw_mask = np.load(os.path.join(self.mask_dir, img_name.replace(\".png\", \".npy\")))\n        mask = (raw_mask.sum(axis=0) > 0).astype(np.uint8)\n\n        # Resize mask (IMPORTANT: nearest interpolation)\n        mask = cv2.resize(mask, (self.img_size, self.img_size), interpolation=cv2.INTER_NEAREST)\n        mask = torch.tensor(mask, dtype=torch.float32).unsqueeze(0)\n\n        return img, mask\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-01T19:42:47.396743Z","iopub.execute_input":"2026-01-01T19:42:47.396972Z","iopub.status.idle":"2026-01-01T19:42:47.409972Z","shell.execute_reply.started":"2026-01-01T19:42:47.396954Z","shell.execute_reply":"2026-01-01T19:42:47.409341Z"}},"outputs":[],"execution_count":null},{"id":"68c552c6","cell_type":"code","source":"class UNet(nn.Module):\n    def __init__(self):\n        super().__init__()\n\n        def block(in_c, out_c):\n            return nn.Sequential(\n                nn.Conv2d(in_c, out_c, 3, padding=1),\n                nn.ReLU(inplace=True),\n                nn.Conv2d(out_c, out_c, 3, padding=1),\n                nn.ReLU(inplace=True),\n            )\n\n        self.enc1 = block(1, 64)\n        self.enc2 = block(64, 128)\n        self.pool = nn.MaxPool2d(2)\n\n        self.bottleneck = block(128, 256)\n\n        self.up2 = nn.ConvTranspose2d(256, 128, 2, stride=2)\n        self.dec2 = block(256, 128)\n\n        self.up1 = nn.ConvTranspose2d(128, 64, 2, stride=2)\n        self.dec1 = block(128, 64)\n\n        self.out = nn.Conv2d(64, 1, 1)\n\n    def forward(self, x):\n        e1 = self.enc1(x)\n        e2 = self.enc2(self.pool(e1))\n\n        b = self.bottleneck(self.pool(e2))\n\n        d2 = self.up2(b)\n        d2 = self.dec2(torch.cat([d2, e2], dim=1))\n\n        d1 = self.up1(d2)\n        d1 = self.dec1(torch.cat([d1, e1], dim=1))\n\n        return torch.sigmoid(self.out(d1))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-01T19:42:47.411256Z","iopub.execute_input":"2026-01-01T19:42:47.411826Z","iopub.status.idle":"2026-01-01T19:42:47.426385Z","shell.execute_reply.started":"2026-01-01T19:42:47.411802Z","shell.execute_reply":"2026-01-01T19:42:47.425773Z"}},"outputs":[],"execution_count":null},{"id":"f96d28b4","cell_type":"code","source":"import torch\n\nprint(\"CUDA available:\", torch.cuda.is_available())\nprint(\"CUDA device count:\", torch.cuda.device_count())\n\nif torch.cuda.is_available():\n    for i in range(torch.cuda.device_count()):\n        print(i, torch.cuda.get_device_name(i))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-01T19:42:47.427005Z","iopub.execute_input":"2026-01-01T19:42:47.427599Z","iopub.status.idle":"2026-01-01T19:42:47.443055Z","shell.execute_reply.started":"2026-01-01T19:42:47.427573Z","shell.execute_reply":"2026-01-01T19:42:47.442359Z"}},"outputs":[],"execution_count":null},{"id":"055da760","cell_type":"code","source":"device = torch.device(\"cuda:0\")\ndevice","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-01T19:42:47.443757Z","iopub.execute_input":"2026-01-01T19:42:47.443996Z","iopub.status.idle":"2026-01-01T19:42:47.459337Z","shell.execute_reply.started":"2026-01-01T19:42:47.443971Z","shell.execute_reply":"2026-01-01T19:42:47.458606Z"}},"outputs":[],"execution_count":null},{"id":"d3e56e6f","cell_type":"code","source":"device = \"cuda\" if torch.cuda.is_available() else \"cpu\"\n\ndataset = ForgeryDataset(TRAIN_FORGED, TRAIN_MASKS)\nloader = DataLoader(dataset, batch_size=1, shuffle=True)\n\nmodel = UNet().to(device)\ncriterion = nn.BCELoss()\noptimizer = torch.optim.Adam(model.parameters(), lr=1e-4)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-01T19:42:47.460085Z","iopub.execute_input":"2026-01-01T19:42:47.460318Z","iopub.status.idle":"2026-01-01T19:42:50.286306Z","shell.execute_reply.started":"2026-01-01T19:42:47.460294Z","shell.execute_reply":"2026-01-01T19:42:50.285691Z"}},"outputs":[],"execution_count":null},{"id":"2643df6a","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(loader, desc=f\"Epoch {epoch+1}/{EPOCHS}\"):\n        imgs, masks = imgs.to(device), masks.to(device)\n\n        preds = model(imgs)\n        loss = criterion(preds, masks)\n\n        optimizer.zero_grad()\n        loss.backward()\n        optimizer.step()\n\n        epoch_loss += loss.item()\n\n    print(f\"Epoch {epoch+1} Loss: {epoch_loss / len(loader):.4f}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-01T19:42:50.287822Z","iopub.execute_input":"2026-01-01T19:42:50.288214Z","iopub.status.idle":"2026-01-01T20:09:50.332920Z","shell.execute_reply.started":"2026-01-01T19:42:50.288195Z","shell.execute_reply":"2026-01-01T20:09:50.332160Z"}},"outputs":[],"execution_count":null},{"id":"ae827bba","cell_type":"code","source":"torch.save(model.state_dict(), \"unet_forgery.pth\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-01T20:09:50.333603Z","iopub.execute_input":"2026-01-01T20:09:50.333850Z","iopub.status.idle":"2026-01-01T20:09:50.352483Z","shell.execute_reply.started":"2026-01-01T20:09:50.333833Z","shell.execute_reply":"2026-01-01T20:09:50.351760Z"}},"outputs":[],"execution_count":null},{"id":"5ca92a07","cell_type":"code","source":"import os\nimport cv2\nimport torch\nimport numpy as np\nimport pandas as pd\nfrom tqdm import tqdm\n\n\nTEST_IMAGES = r\"/kaggle/input/recodai-luc-scientific-image-forgery-detection/test_images\"\nMODEL_PATH = \"unet_forgery.pth\"\n\n\nIMG_SIZE = 256        # MUST be same as training\nMASK_THRESHOLD = 0.5  # sigmoid threshold\nAREA_THRESHOLD = 50   # avoids false positives\n\n\n\ndef rle_encode(mask):\n    pixels = mask.flatten(order=\"C\")\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(map(str, runs)) + \"]\"\n\n\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n\nmodel = UNet().to(device)\nmodel.load_state_dict(torch.load(MODEL_PATH, map_location=device))\nmodel.eval()\n\n\n\nsubmission = []\n\ntest_images = sorted(os.listdir(TEST_IMAGES))\n\nwith torch.no_grad():\n    for case_id, img_name in enumerate(tqdm(test_images), start=1):\n        img_path = os.path.join(TEST_IMAGES, img_name)\n\n        # ---- Load & preprocess image ----\n        img = cv2.imread(img_path, cv2.IMREAD_GRAYSCALE)\n        img = cv2.resize(img, (IMG_SIZE, IMG_SIZE))\n        img = img / 255.0\n\n        img_tensor = (\n            torch.tensor(img, dtype=torch.float32)\n            .unsqueeze(0)\n            .unsqueeze(0)\n            .to(device)\n        )\n\n        # ---- Predict mask ----\n        pred = model(img_tensor)[0, 0].cpu().numpy()\n        binary_mask = (pred > MASK_THRESHOLD).astype(np.uint8)\n\n        # ---- Decide authentic vs forged ----\n        if binary_mask.sum() < AREA_THRESHOLD:\n            annotation = \"authentic\"\n        else:\n            annotation = rle_encode(binary_mask)\n\n        submission.append({\n            \"case_id\": case_id,\n            \"annotation\": annotation\n        })\n\n\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-01T20:13:30.089678Z","iopub.execute_input":"2026-01-01T20:13:30.090012Z","iopub.status.idle":"2026-01-01T20:13:30.547498Z","shell.execute_reply.started":"2026-01-01T20:13:30.089985Z","shell.execute_reply":"2026-01-01T20:13:30.546854Z"}},"outputs":[],"execution_count":null},{"id":"242f5227-e942-499a-834c-549b6107b51f","cell_type":"code","source":"submission_df = pd.DataFrame(submission)\nsubmission_df.to_csv(\"submission.csv\", index=False)\n\nsubmission_df.head()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-01T20:13:32.174770Z","iopub.execute_input":"2026-01-01T20:13:32.175147Z","iopub.status.idle":"2026-01-01T20:13:32.200515Z","shell.execute_reply.started":"2026-01-01T20:13:32.175126Z","shell.execute_reply":"2026-01-01T20:13:32.199658Z"}},"outputs":[],"execution_count":null},{"id":"fb5f7b61-ae64-4931-85b2-e3a2408d2840","cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}